server-1.12/server/monster.c

2257 lines
75 KiB
C

/*
* static char *rcsid_monster_c =
* "$Id: monster.c 11644 2009-04-15 06:24:55Z mwedel $";
*/
/*
CrossFire, A Multiplayer game for X-windows
Copyright (C) 2002-2007 Mark Wedel & Crossfire Development Team
Copyright (C) 1992 Frank Tore Johansen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
The authors can be reached via e-mail at crossfire-devel@real-time.com
*/
/**
* @file
* This deals with monster moving, attacking, using items and such.
* The core function is move_monster().
*/
#include <assert.h>
#include <global.h>
#ifndef __CEXTRACT__
#include <sproto.h>
#include <spells.h>
#include <skills.h>
#endif
static int can_hit(object *ob1, object *ob2, rv_vector *rv);
static int monster_cast_spell(object *head, object *part, object *pl, int dir, rv_vector *rv);
static int monster_use_scroll(object *head, object *part, object *pl, int dir, rv_vector *rv);
static int monster_use_skill(object *head, object *part, object *pl, int dir);
static int monster_use_range(object *head, object *part, object *pl, int dir);
static int monster_use_bow(object *head, object *part, object *pl, int dir);
static void monster_check_pickup(object *monster);
static int monster_can_pick(object *monster, object *item);
static void monster_apply_below(object *monster);
static int dist_att(int dir, object *ob, object *enemy, object *part, rv_vector *rv);
static int run_att(int dir, object *ob, object *enemy, object *part, rv_vector *rv);
static int hitrun_att(int dir, object *ob, object *enemy);
static int wait_att(int dir, object *ob, object *enemy, object *part, rv_vector *rv);
static int disthit_att(int dir, object *ob, object *enemy, object *part, rv_vector *rv);
static int wait_att2(int dir, object *ob, object *enemy, object *part, rv_vector *rv);
static void circ1_move(object *ob);
static void circ2_move(object *ob);
static void pace_movev(object *ob);
static void pace_moveh(object *ob);
static void pace2_movev(object *ob);
static void pace2_moveh(object *ob);
static void rand_move(object *ob);
static int talk_to_npc(object *op, object *npc, const char *txt, int *talked);
#define MIN_MON_RADIUS 3 /* minimum monster detection radius */
/**
* Checks npc->enemy and returns that enemy if still valid,
* NULL otherwise.
* This is map tile aware.
* If this returns an enemy, the range vector rv should also be
* set to sane values.
*
* @param npc
* monster we're considering
* @param[out] rv
* will contain vector to go to enemy if function returns not NULL.
* @return
* valid enemy for npc.
*/
object *check_enemy(object *npc, rv_vector *rv) {
/* if this is pet, let him attack the same enemy as his owner
* TODO: when there is no ower enemy, try to find a target,
* which CAN attack the owner. */
if ((npc->attack_movement&HI4) == PETMOVE) {
if (npc->owner == NULL)
npc->enemy = NULL;
else if (npc->enemy == NULL)
npc->enemy = npc->owner->enemy;
}
/* periodically, a monster mayu change its target. Also, if the object
* has been destroyed, etc, clear the enemy.
* TODO: this should be changed, because it invokes to attack forced or
* attacked monsters to leave the attacker alone, before it is destroyed
*/
/* i had removed the random target leave, this invokes problems with friendly
* objects, getting attacked and defending herself - they don't try to attack
* again then but perhaps get attack on and on
* If we include a aggravated flag in , we can handle evil vs evil and good vs good
* too. */
if (npc->enemy) {
/* I broke these if's apart to better be able to see what
* the grouping checks are. Code is the same.
*/
if (QUERY_FLAG(npc->enemy, FLAG_REMOVED)
|| QUERY_FLAG(npc->enemy, FLAG_FREED)
|| !on_same_map(npc, npc->enemy)
|| npc == npc->enemy
|| QUERY_FLAG(npc, FLAG_NEUTRAL)
|| QUERY_FLAG(npc->enemy, FLAG_NEUTRAL))
npc->enemy = NULL;
else if (QUERY_FLAG(npc, FLAG_FRIENDLY) && (
(QUERY_FLAG(npc->enemy, FLAG_FRIENDLY) && !(should_arena_attack(npc, npc->owner, npc->enemy)))
|| ((npc->enemy->type == PLAYER) && !(should_arena_attack(npc, npc->owner, npc->enemy)))
|| npc->enemy == npc->owner))
npc->enemy = NULL;
else if (!QUERY_FLAG(npc, FLAG_FRIENDLY)
&& (!QUERY_FLAG(npc->enemy, FLAG_FRIENDLY) && npc->enemy->type != PLAYER))
npc->enemy = NULL;
/* I've noticed that pets could sometimes get an arrow as the
* target enemy - this code below makes sure the enemy is something
* that should be attacked. My guess is that the arrow hits
* the creature/owner, and so the creature then takes that
* as the enemy to attack.
*/
else if (!QUERY_FLAG(npc->enemy, FLAG_MONSTER)
&& !QUERY_FLAG(npc->enemy, FLAG_GENERATOR)
&& npc->enemy->type != PLAYER
&& npc->enemy->type != GOLEM)
npc->enemy = NULL;
}
return can_detect_enemy(npc, npc->enemy, rv) ? npc->enemy : NULL;
}
/**
* Returns the nearest living creature (monster or generator).
* Modified to deal with tiled maps properly.
* Also fixed logic so that monsters in the lower directions were more
* likely to be skipped - instead of just skipping the 'start' number
* of direction, revisit them after looking at all the other spaces.
*
* Note that being this may skip some number of spaces, it will
* not necessarily find the nearest living creature - it basically
* chooses one from within a 3 space radius, and since it skips
* the first few directions, it could very well choose something
* 3 spaces away even though something directly north is closer.
*
* This function is map tile aware.
*
* @param npc
* monster to consider
* @return
* living creature, or NULL if none found.
*/
object *find_nearest_living_creature(object *npc) {
int i, mflags;
sint16 nx, ny;
mapstruct *m;
object *tmp;
int search_arr[SIZEOFFREE];
get_search_arr(search_arr);
for (i = 0; i < SIZEOFFREE; i++) {
/* modified to implement smart searching using search_arr
* guidance array to determine direction of search order
*/
nx = npc->x+freearr_x[search_arr[i]];
ny = npc->y+freearr_y[search_arr[i]];
m = npc->map;
mflags = get_map_flags(m, &m, nx, ny, &nx, &ny);
if (mflags&P_OUT_OF_MAP)
continue;
if (mflags&P_IS_ALIVE) {
tmp = GET_MAP_OB(m, nx, ny);
while (tmp != NULL
&& !QUERY_FLAG(tmp, FLAG_MONSTER)
&& !QUERY_FLAG(tmp, FLAG_GENERATOR)
&& tmp->type != PLAYER)
tmp = tmp->above;
if (!tmp) {
LOG(llevDebug, "find_nearest_living_creature: map %s (%d,%d) has is_alive set but did not find a monster?\n", m->path, nx, ny);
} else {
if (can_see_monsterP(m, nx, ny, i))
return tmp;
}
} /* is something living on this space */
}
return NULL; /* nothing found */
}
/**
* Tries to find an enmy for npc. We pass the range vector since
* our caller will find the information useful.
* Currently, only move_monster calls this function.
* Fix function so that we always make calls to get_rangevector
* if we have a valid target - function as not doing so in
* many cases.
*
* @param npc
* monster we're considering.
* @param[out] rv
* vector that will contain how to reach the target. Must not be NULL.
* @return
* enemy npc wants to attack, or NULL if nont found.
*/
static object *find_enemy(object *npc, rv_vector *rv) {
object *attacker, *tmp = NULL;
attacker = npc->attacked_by; /* save this for later use. This can be a attacker. */
npc->attacked_by = NULL; /* always clear the attacker entry */
/* if we berserk, we don't care about others - we attack all we can find */
if (QUERY_FLAG(npc, FLAG_BERSERK)) {
tmp = find_nearest_living_creature(npc);
if (tmp)
get_rangevector(npc, tmp, rv, 0);
return tmp;
}
/* Here is the main enemy selection.
* We want this: if there is an enemy, attack him until its not possible or
* one of both is dead.
* If we have no enemy and we are...
* a monster: try to find a player, a pet or a friendly monster
* a friendly: only target a monster which is targeting you first or targeting a player
* a neutral: fight a attacker (but there should be none), then do nothing
* a pet: attack player enemy or a monster
*/
/* pet move */
if ((npc->attack_movement&HI4) == PETMOVE) {
tmp = get_pet_enemy(npc, rv);
if (tmp)
get_rangevector(npc, tmp, rv, 0);
return tmp;
}
/* we check our old enemy. */
if ((tmp = check_enemy(npc, rv)) == NULL) {
if (attacker) { /* if we have an attacker, check him */
/* we want be sure this is the right one! */
if (attacker->count == npc->attacked_by_count) {
/* TODO: thats not finished */
/* we don't want a fight evil vs evil or good against non evil */
if (QUERY_FLAG(npc, FLAG_NEUTRAL)
|| QUERY_FLAG(attacker, FLAG_NEUTRAL) /* neutral */
|| (QUERY_FLAG(npc, FLAG_FRIENDLY) && QUERY_FLAG(attacker, FLAG_FRIENDLY))
|| (!QUERY_FLAG(npc, FLAG_FRIENDLY) && (!QUERY_FLAG(attacker, FLAG_FRIENDLY) && attacker->type != PLAYER)))
CLEAR_FLAG(npc, FLAG_SLEEP); /* skip it, but lets wakeup */
else if (on_same_map(npc, attacker)) { /* thats the only thing we must know... */
CLEAR_FLAG(npc, FLAG_SLEEP); /* well, NOW we really should wake up! */
npc->enemy = attacker;
get_rangevector(npc, attacker, rv, 0);
return attacker; /* yes, we face our attacker! */
}
}
}
/* we have no legal enemy or attacker, so we try to target a new one */
if (!QUERY_FLAG(npc, FLAG_UNAGGRESSIVE)
&& !QUERY_FLAG(npc, FLAG_FRIENDLY)
&& !QUERY_FLAG(npc, FLAG_NEUTRAL)) {
npc->enemy = get_nearest_player(npc);
if (npc->enemy)
tmp = check_enemy(npc, rv);
}
}
return tmp;
}
/**
* Sees if this monster should wake up.
* Currently, this is only called from move_monster, and
* if enemy is set, then so should be rv.
* @param op
* monster to check.
* @param enemy
* enemy that can cause to wake up.
* @param[out] rv
* vector pointing to enemy.
* @return
* 1 if the monster should wake up, 0 otherwise.
* @note
* will return 0 if enemy is NULL.
*/
static int check_wakeup(object *op, object *enemy, rv_vector *rv) {
int radius = MAX(op->stats.Wis, MIN_MON_RADIUS);
/* Trim work - if no enemy, no need to do anything below */
if (!enemy)
return 0;
/* blinded monsters can only find nearby objects to attack */
if (QUERY_FLAG(op, FLAG_BLIND))
radius = MIN_MON_RADIUS;
/* This covers the situation where the monster is in the dark
* and has an enemy. If the enemy has no carried light (or isnt
* glowing!) then the monster has trouble finding the enemy.
* Remember we already checked to see if the monster can see in
* the dark. */
else if (op->map
&& op->map->darkness > 0
&& enemy
&& !enemy->invisible
&& !stand_in_light(enemy)
&& (!QUERY_FLAG(op, FLAG_SEE_IN_DARK) || !QUERY_FLAG(op, FLAG_SEE_INVISIBLE))) {
int dark = radius/(op->map->darkness);
radius = (dark > MIN_MON_RADIUS) ? (dark+1) : MIN_MON_RADIUS;
} else if (!QUERY_FLAG(op, FLAG_SLEEP))
return 1;
/* enemy should already be on this map, so don't really need to check
* for that.
*/
if (rv->distance < (QUERY_FLAG(enemy, FLAG_STEALTH) ? (radius/2)+1 : radius)) {
CLEAR_FLAG(op, FLAG_SLEEP);
return 1;
}
return 0;
}
/**
* Makes an object move in a random direction.
*
* @param op
* object to move.
* @return
* 1 if moved, 0 else.
*/
static int move_randomly(object *op) {
int i;
/* Give up to 15 chances for a monster to move randomly */
for (i = 0; i < 15; i++) {
if (move_object(op, RANDOM()%8+1))
return 1;
}
return 0;
}
#define MAX_EXPLORE 5000
/**
* Computes a path from source to target. Takes into account walls, other living things, and such.
* Only works if both items are on same map.
*
* @param source
* what wants to move.
* @param target
* target to go to.
* @param default_dir
* general direction from source to target.
* @return
* direction to go into. Will be default_dir if no path found.
* @todo cache path, smart ajustment and such things to not compute all the time ; try directions randomly.
*/
int compute_path(object *source, object *target, int default_dir) {
char *path;
int explore_x[MAX_EXPLORE], explore_y[MAX_EXPLORE];
int current = 0, dir, max = 1, size, x, y, check_dir;
if (target->map != source->map)
return default_dir;
/* printf("compute_path (%d, %d) => (%d, %d)\n", source->x, source->y, target->x, target->y);*/
size = source->map->width*source->map->height;
path = calloc(size, sizeof(char));
if (path == NULL) {
fatal(OUT_OF_MEMORY);
}
explore_x[0] = target->x;
explore_y[0] = target->y;
while (current < max) {
for (check_dir = 0; check_dir < 8; check_dir++) {
dir = absdir(default_dir+check_dir);
x = explore_x[current]+freearr_x[dir];
y = explore_y[current]+freearr_y[dir];
if (x == source->x && y == source->y) {
/* LOG(llevDebug, "compute_path => %d\n", absdir(dir+4));*/
free(path);
return absdir(dir+4);
}
if (OUT_OF_REAL_MAP(source->map, x, y))
continue;
if (ob_blocked(source, source->map, x, y))
continue;
assert(source->map->height*x+y >= 0);
assert(source->map->height*x+y < size);
if (path[source->map->height*x+y] == 0) {
assert(max < MAX_EXPLORE);
explore_x[max] = x;
explore_y[max] = y;
path[source->map->height*x+y] = absdir(dir+4);
/* printf("explore[%d] => (%d, %d) %d\n", max, x, y, path[source->map->height*x+y]);*/
max++;
if (max == MAX_EXPLORE) {
free(path);
return default_dir;
}
}
}
current++;
}
free(path);
return default_dir;
}
/**
* For a monster, regenerate hp and sp, potentially clear scared status.
*
* @param op
* monster. Must have FLAG_MONSTER set.
*/
static void monster_do_living(object *op) {
assert(QUERY_FLAG(op, FLAG_MONSTER));
/* generate hp, if applicable */
if (op->stats.Con > 0 && op->stats.hp < op->stats.maxhp) {
/* last heal is in funny units. Dividing by speed puts
* the regeneration rate on a basis of time instead of
* #moves the monster makes. The scaling by 8 is
* to capture 8th's of a hp fraction regens
*
* Cast to sint32 before comparing to maxhp since otherwise an (sint16)
* overflow might produce monsters with negative hp.
*/
op->last_heal += (int)((float)(8*op->stats.Con)/FABS(op->speed));
op->stats.hp = MIN((sint32)op->stats.hp+op->last_heal/32, op->stats.maxhp); /* causes Con/4 hp/tick */
op->last_heal %= 32;
/* So if the monster has gained enough HP that they are no longer afraid */
if (QUERY_FLAG(op, FLAG_RUN_AWAY)
&& op->stats.hp >= (signed short)(((float)op->run_away/(float)100)*(float)op->stats.maxhp))
CLEAR_FLAG(op, FLAG_RUN_AWAY);
if (op->stats.hp > op->stats.maxhp)
op->stats.hp = op->stats.maxhp;
}
/* generate sp, if applicable */
if (op->stats.Pow > 0 && op->stats.sp < op->stats.maxsp) {
/* last_sp is in funny units. Dividing by speed puts
* the regeneration rate on a basis of time instead of
* #moves the monster makes. The scaling by 8 is
* to capture 8th's of a sp fraction regens
*
* Cast to sint32 before comparing to maxhp since otherwise an (sint16)
* overflow might produce monsters with negative sp.
*/
op->last_sp += (int)((float)(8*op->stats.Pow)/FABS(op->speed));
op->stats.sp = MIN(op->stats.sp+op->last_sp/128, op->stats.maxsp); /* causes Pow/16 sp/tick */
op->last_sp %= 128;
}
/* this should probably get modified by many more values.
* (eg, creatures resistance to fear, level, etc. )
*/
if (QUERY_FLAG(op, FLAG_SCARED) && !(RANDOM()%20)) {
CLEAR_FLAG(op, FLAG_SCARED); /* Time to regain some "guts"... */
}
}
/**
* Makes a monster without any enemy move.
*
* @param op
* monster, must have FLAG_MONSTER set.
* @return
* 1 if monster was removed, 0 else.
*/
static int monster_move_no_enemy(object *op) {
assert(QUERY_FLAG(op, FLAG_MONSTER));
if (QUERY_FLAG(op, FLAG_ONLY_ATTACK)) {
remove_ob(op);
free_object(op);
return 1;
}
/* Probably really a bug for a creature to have both
* stand still and a movement type set.
*/
if (!QUERY_FLAG(op, FLAG_STAND_STILL)) {
if (op->attack_movement&HI4) {
switch (op->attack_movement&HI4) {
case(PETMOVE):
pet_move(op);
break;
case(CIRCLE1):
circ1_move(op);
break;
case(CIRCLE2):
circ2_move(op);
break;
case(PACEV):
pace_movev(op);
break;
case(PACEH):
pace_moveh(op);
break;
case(PACEV2):
pace2_movev(op);
break;
case(PACEH2):
pace2_moveh(op);
break;
case(RANDO):
rand_move(op);
break;
case(RANDO2):
move_randomly(op);
break;
}
return 0;
} else if (QUERY_FLAG(op, FLAG_RANDOM_MOVE))
move_randomly(op);
} /* stand still */
return 0;
}
/**
* Main monster processing routine.
*
* Will regenerate spell points, hit points.
* Moves the monster, handle attack, item applying, pickup, ...
*
* @param op
* monster to process.
* @return
* 1 if the object has been freed, otherwise 0.
*/
int move_monster(object *op) {
int dir, diff;
object *owner, *enemy, *part, *oph = op;
rv_vector rv;
/* Monsters not on maps don't do anything. These monsters are things
* Like royal guards in city dwellers inventories.
*/
if (!op->map)
return 0;
/* for target facing, we copy this value here for fast access */
if (oph->head) /* force update the head - one arch one pic */
oph = oph->head;
if (QUERY_FLAG(op, FLAG_NO_ATTACK)) /* we never ever attack */
enemy = op->enemy = NULL;
else if ((enemy = find_enemy(op, &rv))) {
/* we have an enemy, just tell him we want him dead */
enemy->attacked_by = op; /* our ptr */
enemy->attacked_by_count = op->count; /* our tag */
}
if (QUERY_FLAG(op, FLAG_SLEEP)
|| QUERY_FLAG(op, FLAG_BLIND)
|| ((op->map->darkness > 0) && !QUERY_FLAG(op, FLAG_SEE_IN_DARK) && !QUERY_FLAG(op, FLAG_SEE_INVISIBLE))) {
if (!check_wakeup(op, enemy, &rv))
return 0;
}
/* check if monster pops out of hidden spot */
if (op->hide)
do_hidden_move(op);
if (op->pick_up)
monster_check_pickup(op);
if (op->will_apply)
monster_apply_below(op); /* Check for items to apply below */
monster_do_living(op);
/* If we don't have an enemy, do special movement or the like */
if (!enemy) {
return monster_move_no_enemy(op);
} /* no enemy */
/* We have an enemy. Block immediately below is for pets */
if ((op->attack_movement&HI4) == PETMOVE && (owner = get_owner(op)) != NULL && !on_same_map(op, owner)) {
follow_owner(op, owner);
/* If the pet was unable to follow the owner, free it */
if (QUERY_FLAG(op, FLAG_REMOVED) && FABS(op->speed) > MIN_ACTIVE_SPEED) {
remove_friendly_object(op);
free_object(op);
return 1;
}
return 0;
}
/* doppleganger code to change monster facing to that of the nearest
* player. Hmm. The code is here, but no monster in the current
* arch set uses it.
*/
if ((op->race != NULL)&& strcmp(op->race, "doppleganger") == 0) {
op->face = enemy->face;
if (op->name)
free_string(op->name);
add_refcount(op->name = enemy->name);
}
/* Calculate range information for closest body part - this
* is used for the 'skill' code, which isn't that smart when
* it comes to figuring it out - otherwise, giants throw boulders
* into themselves.
*/
get_rangevector(op, enemy, &rv, 0);
if (op->direction != rv.direction) {
op->direction = rv.direction;
op->facing = op->direction;
if (op->animation_id)
animate_object(op, op->direction);
}
/* Move the check for scared up here - if the monster was scared,
* we were not doing any of the logic below, so might as well save
* a few cpu cycles.
*/
if (!QUERY_FLAG(op, FLAG_SCARED)) {
rv_vector rv1;
/* now we test every part of an object .... this is a real ugly piece of code */
for (part = op; part != NULL; part = part->more) {
get_rangevector(part, enemy, &rv1, 0x1);
dir = rv1.direction;
/* hm, not sure about this part - in original was a scared flag here too
* but that we test above... so can be old code here
*/
if (QUERY_FLAG(op, FLAG_RUN_AWAY))
dir = absdir(dir+4);
if (QUERY_FLAG(op, FLAG_CONFUSED))
dir = absdir(dir+RANDOM()%3+RANDOM()%3-2);
if (QUERY_FLAG(op, FLAG_CAST_SPELL) && !(RANDOM()%3)) {
if (monster_cast_spell(op, part, enemy, dir, &rv1))
return 0;
}
if (QUERY_FLAG(op, FLAG_READY_SCROLL) && !(RANDOM()%3)) {
if (monster_use_scroll(op, part, enemy, dir, &rv1))
return 0;
}
if (QUERY_FLAG(op, FLAG_READY_RANGE) && !(RANDOM()%3)) {
if (monster_use_range(op, part, enemy, dir))
return 0;
}
if (QUERY_FLAG(op, FLAG_READY_SKILL) && !(RANDOM()%3)) {
if (monster_use_skill(op, rv.part, enemy, rv.direction))
return 0;
}
if (QUERY_FLAG(op, FLAG_READY_BOW) && !(RANDOM()%2)) {
if (monster_use_bow(op, part, enemy, dir))
return 0;
}
} /* for processing of all parts */
} /* If not scared */
/* code below is for when we didn't use a range attack or a skill, so
* either move or hit with hth attack. */
part = rv.part;
dir = rv.direction;
if (QUERY_FLAG(op, FLAG_SCARED) || QUERY_FLAG(op, FLAG_RUN_AWAY))
dir = absdir(dir+4);
else if (!can_hit(part, enemy, &rv))
dir = compute_path(op, enemy, rv.direction);
if (QUERY_FLAG(op, FLAG_CONFUSED))
dir = absdir(dir+RANDOM()%3+RANDOM()%3-2);
if ((op->attack_movement&LO4) && !QUERY_FLAG(op, FLAG_SCARED)) {
switch (op->attack_movement&LO4) {
case DISTATT:
dir = dist_att(dir, op, enemy, part, &rv);
break;
case RUNATT:
dir = run_att(dir, op, enemy, part, &rv);
break;
case HITRUN:
dir = hitrun_att(dir, op, enemy);
break;
case WAITATT:
dir = wait_att(dir, op, enemy, part, &rv);
break;
case RUSH: /* default - monster normally moves towards player */
case ALLRUN:
break;
case DISTHIT:
dir = disthit_att(dir, op, enemy, part, &rv);
break;
case WAIT2:
dir = wait_att2(dir, op, enemy, part, &rv);
break;
default:
LOG(llevDebug, "Illegal low mon-move: %d\n", op->attack_movement&LO4);
}
}
if (!dir)
return 0;
if (!QUERY_FLAG(op, FLAG_STAND_STILL)) {
if (move_object(op, dir)) /* Can the monster move directly toward player? */
return 0;
if (QUERY_FLAG(op, FLAG_SCARED)
|| !can_hit(part, enemy, &rv)
|| QUERY_FLAG(op, FLAG_RUN_AWAY)) {
/* Try move around corners if !close */
int maxdiff = (QUERY_FLAG(op, FLAG_ONLY_ATTACK) || RANDOM()&1) ? 1 : 2;
for (diff = 1; diff <= maxdiff; diff++) {
/* try different detours */
int m = 1-(RANDOM()&2); /* Try left or right first? */
if (move_object(op, absdir(dir+diff*m))
|| move_object(op, absdir(dir-diff*m)))
return 0;
}
}
} /* if monster is not standing still */
/*
* Eneq(@csd.uu.se): Patch to make RUN_AWAY or SCARED monsters move a random
* direction if they can't move away.
*/
if (!QUERY_FLAG(op, FLAG_ONLY_ATTACK)
&& (QUERY_FLAG(op, FLAG_RUN_AWAY) || QUERY_FLAG(op, FLAG_SCARED)))
if (move_randomly(op))
return 0;
/*
* Try giving the monster a new enemy - the player that is closest
* to it. In this way, it won't just keep trying to get to a target
* that is inaccessible.
* This could be more clever - it should go through a list of several
* enemies, as it is now, you could perhaps get situations where there
* are two players flanking the monster at close distance, but which
* the monster can't get to, and a third one at a far distance that
* the monster could get to - as it is, the monster won't look at that
* third one.
*/
if (!QUERY_FLAG(op, FLAG_FRIENDLY) && enemy == op->enemy) {
object *nearest_player = get_nearest_player(op);
if (nearest_player && nearest_player != enemy && !can_hit(part, enemy, &rv)) {
op->enemy = NULL;
enemy = nearest_player;
}
}
if (!QUERY_FLAG(op, FLAG_SCARED) && can_hit(part, enemy, &rv)) {
/* The adjustement to wc that was here before looked totally bogus -
* since wc can in fact get negative, that would mean by adding
* the current wc, the creature gets better? Instead, just
* add a fixed amount - nasty creatures that are runny away should
* still be pretty nasty.
*/
if (QUERY_FLAG(op, FLAG_RUN_AWAY)) {
part->stats.wc += 10;
(void)skill_attack(enemy, part, 0, NULL, NULL);
part->stats.wc -= 10;
} else
(void)skill_attack(enemy, part, 0, NULL, NULL);
} /* if monster is in attack range */
if (QUERY_FLAG(part, FLAG_FREED)) /* Might be freed by ghost-attack or hit-back */
return 1;
if (QUERY_FLAG(op, FLAG_ONLY_ATTACK)) {
remove_ob(op);
free_object(op);
return 1;
}
return 0;
}
/**
* Checks if monster can hit in hand-to-hand combat. Multitile aware.
*
* @param ob1
* monster trying to hit.
* @param ob2
* target to hit.
* @param rv
* vector from ob1 to ob2.
* @return
* 1 if ob1 is adjacent to ob2, 0 else.
* @todo
* rename to something more clear (is_adjacent?).
*/
static int can_hit(object *ob1, object *ob2, rv_vector *rv) {
object *more;
rv_vector rv1;
if (QUERY_FLAG(ob1, FLAG_CONFUSED)&&!(RANDOM()%3))
return 0;
if (abs(rv->distance_x) < 2 && abs(rv->distance_y) < 2)
return 1;
/* check all the parts of ob2 - just because we can't get to
* its head doesn't mean we don't want to pound its feet
*/
for (more = ob2->more; more != NULL; more = more->more) {
get_rangevector(ob1, more, &rv1, 0);
if (abs(rv1.distance_x) < 2 && abs(rv1.distance_y) < 2)
return 1;
}
return 0;
}
/**
* Checks if a monster should cast a spell.
*
* Note that this function does not check to see if the monster can
* in fact cast the spell (sp dependencies and what not.) That is because
* this function is also sued to see if the monster should use spell items
* (rod/horn/wand/scroll).
*
* Note that there are certainly other offensive spells that could be
* included, but I decided to leave out the spells that may kill more
* monsters than players (eg, disease).
*
* This could be a lot smarter - if there are few monsters around,
* then disease might not be as bad. Likewise, if the monster is damaged,
* the right type of healing spell could be useful.
*
* @param monster
* monster trying to cast a spell.
* @param spell_ob
* spell considered.
* @return
* 1 is monster should cast spell sp, 0 else.
* @todo improve logic, take enemy into consideration.
*/
static int monster_should_cast_spell(object *monster, object *spell_ob) {
if (spell_ob->subtype == SP_BOLT
|| spell_ob->subtype == SP_BULLET
|| spell_ob->subtype == SP_EXPLOSION
|| spell_ob->subtype == SP_CONE
|| spell_ob->subtype == SP_BOMB
|| spell_ob->subtype == SP_SMITE
|| spell_ob->subtype == SP_MAGIC_MISSILE
|| spell_ob->subtype == SP_SUMMON_GOLEM
|| spell_ob->subtype == SP_MAGIC_WALL
|| spell_ob->subtype == SP_SUMMON_MONSTER
|| spell_ob->subtype == SP_MOVING_BALL
|| spell_ob->subtype == SP_SWARM
|| spell_ob->subtype == SP_INVISIBLE)
return 1;
return 0;
}
/** Maximum number of spells to consider when choosing a spell for a monster. */
#define MAX_KNOWN_SPELLS 20
/**
* Selects a spell to cast for a monster.
*
* Returns a randomly selected spell. This logic is still
* less than ideal. This code also only seems to deal with
* wizard spells, as the check is against sp, and not grace.
* can monsters know cleric spells?
*
* @param monster
* monster trying to cast a spell.
* @return
* spell to cast, NULL if none suitable found.
* @note
* Will only consider the first MAX_KNOWN_SPELLS spells found.
*/
static object *monster_choose_random_spell(object *monster) {
object *altern[MAX_KNOWN_SPELLS];
object *tmp;
int i = 0;
for (tmp = monster->inv; tmp != NULL; tmp = tmp->below)
if (tmp->type == SPELLBOOK || tmp->type == SPELL) {
/* Check and see if it's actually a useful spell.
* If its a spellbook, the spell is actually the inventory item.
* if it is a spell, then it is just the object itself.
*/
if (monster_should_cast_spell(monster, (tmp->type == SPELLBOOK) ? tmp->inv : tmp)) {
altern[i++] = tmp;
if (i == MAX_KNOWN_SPELLS)
break;
}
}
if (!i)
return NULL;
return altern[RANDOM()%i];
}
/**
* Tries to make a (part of a) monster cast a spell.
*
* Handles sp/gr limits, and confusion.
*
* @param head
* head of the monster.
* @param part
* part of the monster that we use to cast.
* @param pl
* target.
* @param dir
* direction to cast.
* @param rv
* vector describing where the enemy is.
* @return
* 1 if monster casted a spell, 0 else.
*/
static int monster_cast_spell(object *head, object *part, object *pl, int dir, rv_vector *rv) {
object *spell_item;
object *owner;
rv_vector rv1;
/* If you want monsters to cast spells over friends, this spell should
* be removed. It probably should be in most cases, since monsters still
* don't care about residual effects (ie, casting a cone which may have a
* clear path to the player, the side aspects of the code will still hit
* other monsters)
*/
if (!(dir = path_to_player(part, pl, 0)))
return 0;
if (QUERY_FLAG(head, FLAG_FRIENDLY) && (owner = get_owner(head)) != NULL) {
get_rangevector(head, owner, &rv1, 0x1);
if (dirdiff(dir, rv1.direction) < 2) {
return 0; /* Might hit owner with spell */
}
}
if (QUERY_FLAG(head, FLAG_CONFUSED))
dir = absdir(dir+RANDOM()%3+RANDOM()%3-2);
/* If the monster hasn't already chosen a spell, choose one
* I'm not sure if it really make sense to pre-select spells (events
* could be different by the time the monster goes again).
*/
if (head->spellitem == NULL) {
if ((spell_item = monster_choose_random_spell(head)) == NULL) {
LOG(llevMonster, "Turned off spells in %s\n", head->name);
CLEAR_FLAG(head, FLAG_CAST_SPELL); /* Will be turned on when picking up book */
return 0;
}
if (spell_item->type == SPELLBOOK) {
if (!spell_item->inv) {
LOG(llevError, "spellbook %s does not contain a spell?\n", spell_item->name);
return 0;
}
spell_item = spell_item->inv;
}
} else
spell_item = head->spellitem;
if (!spell_item)
return 0;
/* Best guess this is a defensive/healing spell */
if (spell_item->range <= 1 || spell_item->stats.dam < 0)
dir = 0;
/* Monster doesn't have enough spell-points */
if (head->stats.sp < SP_level_spellpoint_cost(head, spell_item, SPELL_MANA))
return 0;
if (head->stats.grace < SP_level_spellpoint_cost(head, spell_item, SPELL_GRACE))
return 0;
head->stats.sp -= SP_level_spellpoint_cost(head, spell_item, SPELL_MANA);
head->stats.grace -= SP_level_spellpoint_cost(head, spell_item, SPELL_GRACE);
/* set this to null, so next time monster will choose something different */
head->spellitem = NULL;
return cast_spell(part, part, dir, spell_item, NULL);
}
/**
* Tries to make a (part of a) monster apply a spell.
*
* @param head
* head of the monster.
* @param part
* part of the monster that we use to cast.
* @param pl
* target.
* @param dir
* direction to cast.
* @param rv
* vector describing where the enemy is.
* @return
* 1 if monster applied a scroll, 0 else.
*/
static int monster_use_scroll(object *head, object *part, object *pl, int dir, rv_vector *rv) {
object *scroll;
object *owner;
rv_vector rv1;
/* If you want monsters to cast spells over friends, this spell should
* be removed. It probably should be in most cases, since monsters still
* don't care about residual effects (ie, casting a cone which may have a
* clear path to the player, the side aspects of the code will still hit
* other monsters)
*/
if (!(dir = path_to_player(part, pl, 0)))
return 0;
if (QUERY_FLAG(head, FLAG_FRIENDLY) && (owner = get_owner(head)) != NULL) {
get_rangevector(head, owner, &rv1, 0x1);
if (dirdiff(dir, rv1.direction) < 2) {
return 0; /* Might hit owner with spell */
}
}
if (QUERY_FLAG(head, FLAG_CONFUSED))
dir = absdir(dir+RANDOM()%3+RANDOM()%3-2);
for (scroll = head->inv; scroll; scroll = scroll->below)
if (scroll->type == SCROLL && monster_should_cast_spell(head, scroll->inv))
break;
/* Used up all his scrolls, so nothing do to */
if (!scroll) {
CLEAR_FLAG(head, FLAG_READY_SCROLL);
return 0;
}
/* Spell should be cast on caster (ie, heal, strength) */
if (scroll->inv->range == 0)
dir = 0;
/* Face the direction that we want to cast. */
head->direction = dir;
head->facing = head->direction;
if (head->animation_id)
animate_object(head, head->direction);
ob_apply(scroll, part, 0);
return 1;
}
/**
* monster_use_skill()-implemented 95-04-28 to allow monster skill use.
* Note that monsters do not need the skills SK_MELEE_WEAPON and
* SK_MISSILE_WEAPON to make those respective attacks, if we
* required that we would drastically increase the memory
* requirements of CF!!
*
* The skills we are treating here are all but those. -b.t.
*
* At the moment this is only useful for throwing, perhaps for
* stealing. TODO: This should be more integrated in the game. -MT, 25.11.01
*
* Will switch between at most 2 skills.
*
* @param head
* head of the monster.
* @param part
* part of the monster that may use a skill.
* @param pl
* target.
* @param dir
* direction to cast.
* @return
* 1 if monster used a skill, 0 else.
* @todo
* improve skill logic? Fix comments.
*/
static int monster_use_skill(object *head, object *part, object *pl, int dir) {
object *skill, *owner;
if (!(dir = path_to_player(part, pl, 0)))
return 0;
if (QUERY_FLAG(head, FLAG_FRIENDLY) && (owner = get_owner(head)) != NULL) {
int dir2 = find_dir_2(head->x-owner->x, head->y-owner->y);
if (dirdiff(dir, dir2) < 1)
return 0; /* Might hit owner with skill -thrown rocks for example ?*/
}
if (QUERY_FLAG(head, FLAG_CONFUSED))
dir = absdir(dir+RANDOM()%3+RANDOM()%3-2);
/* skill selection - monster will use the next unused skill.
* well...the following scenario will allow the monster to
* toggle between 2 skills. One day it would be nice to make
* more skills available to monsters.
*/
for (skill = head->inv; skill != NULL; skill = skill->below)
if (skill->type == SKILL && skill != head->chosen_skill) {
head->chosen_skill = skill;
break;
}
if (!skill && !head->chosen_skill) {
LOG(llevDebug, "Error: Monster %s (%d) has FLAG_READY_SKILL without skill.\n", head->name, head->count);
CLEAR_FLAG(head, FLAG_READY_SKILL);
return 0;
}
/* use skill */
return do_skill(head, part, head->chosen_skill, dir, NULL);
}
/**
* Monster will use a ranged attack (HORN, WAND, ...).
*
* @param head
* head of the monster.
* @param part
* part of the monster that can do a range attack.
* @param pl
* target.
* @param dir
* direction to fire.
* @return
* 1 if monster casted a spell, 0 else.
*/
static int monster_use_range(object *head, object *part, object *pl, int dir) {
object *wand, *owner;
int at_least_one = 0;
if (!(dir = path_to_player(part, pl, 0)))
return 0;
if (QUERY_FLAG(head, FLAG_FRIENDLY) && (owner = get_owner(head)) != NULL) {
int dir2 = find_dir_2(head->x-owner->x, head->y-owner->y);
if (dirdiff(dir, dir2) < 2)
return 0; /* Might hit owner with spell */
}
if (QUERY_FLAG(head, FLAG_CONFUSED))
dir = absdir(dir+RANDOM()%3+RANDOM()%3-2);
for (wand = head->inv; wand != NULL; wand = wand->below) {
if (wand->type == WAND) {
/* Found a wand, let's see if it has charges left */
at_least_one = 1;
if (wand->stats.food <= 0)
continue;
cast_spell(head, wand, dir, wand->inv, NULL);
drain_wand_charge(wand);
/* Success */
return 1;
} else if (wand->type == ROD || wand->type == HORN) {
/* Found rod/horn, let's use it if possible */
at_least_one = 1;
if (wand->stats.hp < MAX(wand->inv->stats.sp, wand->inv->stats.grace))
continue;
/* drain charge before casting spell - can be a case where the
* spell destroys the monster, and rod, so if done after, results
* in crash.
*/
drain_rod_charge(wand);
cast_spell(head, wand, dir, wand->inv, NULL);
/* Success */
return 1;
}
}
if (at_least_one)
return 0;
LOG(llevError, "Error: Monster %s (%d) HAS_READY_RANG() without wand/horn/rod.\n", head->name, head->count);
CLEAR_FLAG(head, FLAG_READY_RANGE);
return 0;
}
/**
* Tries to make a (part of a) monster fire a bow.
*
* Handles confusion effect.
*
* @param head
* head of the monster.
* @param part
* part of the monster that we use to cast.
* @param pl
* target.
* @param dir
* direction to cast.
* @return
* 1 if monster fired something, 0 else.
*/
static int monster_use_bow(object *head, object *part, object *pl, int dir) {
object *owner;
rv_vector rv;
sint16 x, y;
mapstruct *map;
get_rangevector(part, pl, &rv, 1);
if (rv.distance > 100)
/* Too far */
return 0;
if (rv.distance_x != 0 && rv.distance_y != 0 && abs(rv.distance_x) != abs(rv.distance_y))
/* Player must be on same horizontal, vertical or diagonal line. */
return 0;
dir = absdir(find_dir_2(rv.distance_x, rv.distance_y)+4);
if (QUERY_FLAG(head, FLAG_FRIENDLY))
owner = get_owner(head);
else
owner = NULL;
/* The monster can possibly fire, let's see if the path is ok for an arrow. */
x = part->x;
y = part->y;
map = part->map;
while (x != pl->x || y != pl->y || map != pl->map) {
x += freearr_x[dir];
y += freearr_y[dir];
map = get_map_from_coord(map, &x, &y);
if (!map) {
LOG(llevError, "monster_use_bow: no map but still path exists??\n");
return 0;
}
if ((GET_MAP_MOVE_BLOCK(map, x, y)&MOVE_FLY_LOW) == MOVE_FLY_LOW)
return 0;
if (owner && owner->x == x && owner->y == y && owner->map == map)
/* Don't hit owner! */
return 0;
}
/* Finally, path is clear, can fire. */
if (QUERY_FLAG(head, FLAG_CONFUSED))
dir = absdir(dir+RANDOM()%3+RANDOM()%3-2);
/* in server/player.c */
return fire_bow(head, NULL, dir, 0, part->x, part->y);
}
/**
* Checks if using weapon 'item' would be better for 'who'.
* This is a very simplistic check - also checking things
* like speed and ac are also relevant.
*
* @param who
* creature considering to apply item.
* @param item
* item to check.
* @return
* 1 if item is a better object, 0 else.
*/
static int check_good_weapon(object *who, object *item) {
object *other_weap;
int val = 0, i;
for (other_weap = who->inv; other_weap != NULL; other_weap = other_weap->below)
if (other_weap->type == item->type && QUERY_FLAG(other_weap, FLAG_APPLIED))
break;
if (other_weap == NULL) /* No other weapons */
return 1;
/* Rather than go through and apply the new one, and see if it is
* better, just do some simple checks
* Put some multipliers for things that hvae several effects,
* eg, magic affects both damage and wc, so it has more weight
*/
val = item->stats.dam-other_weap->stats.dam;
val += (item->magic-other_weap->magic)*3;
/* Monsters don't really get benefits from things like regen rates
* from items. But the bonus for their stats are very important.
*/
for (i = 0; i < NUM_STATS; i++)
val += (get_attr_value(&item->stats, i)-get_attr_value(&other_weap->stats, i))*2;
if (val > 0)
return 1;
else
return 0;
}
/**
* Checks if using armor 'item' would be better for 'who'.
* This is a very simplistic check - also checking things
* like speed and ac are also relevant.
*
* @param who
* creature considering to apply item.
* @param item
* item to check.
* @return
* 1 if item is a better object, 0 else.
*/
static int check_good_armour(object *who, object *item) {
object *other_armour;
int val = 0, i;
for (other_armour = who->inv; other_armour != NULL; other_armour = other_armour->below)
if (other_armour->type == item->type && QUERY_FLAG(other_armour, FLAG_APPLIED))
break;
if (other_armour == NULL) /* No other armour, use the new */
return 1;
/* Like above function , see which is better */
val = item->stats.ac-other_armour->stats.ac;
val = (item->resist[ATNR_PHYSICAL]-other_armour->resist[ATNR_PHYSICAL])/5;
val += (item->magic-other_armour->magic)*3;
/* for the other protections, do weigh them very much in the equation -
* it is the armor protection which is most important, because there is
* no good way to know what the player may attack the monster with.
* So if the new item has better protection than the old, give that higher
* value. If the reverse, then decrease the value of this item some.
*/
for (i = 1; i < NROFATTACKS; i++) {
if (item->resist[i] > other_armour->resist[i])
val++;
else if (item->resist[i] < other_armour->resist[i])
val--;
}
/* Very few armours have stats, so not much need to worry about those. */
if (val > 0)
return 1;
else
return 0;
}
/**
* Checks for items that monster can pick up.
*
* Vick's (vick(at)bern.docs.uu.se) fix 921030 for the sweeper blob.
* Each time the blob passes over some treasure, it will
* grab it a.s.a.p.
*
* Eneq((at)csd.uu.se): This can now be defined in the archetypes, added code
* to handle this.
*
* This function was seen be continueing looping at one point (tmp->below
* became a recursive loop. It may be better to call monster_check_apply
* after we pick everything up, since that function may call others which
* affect stacking on this space.
*
* @param monster
* monster that can pick up items.
*/
static void monster_check_pickup(object *monster) {
object *tmp, *next;
int next_tag;
for (tmp = monster->below; tmp != NULL; tmp = next) {
next = tmp->below;
next_tag = next ? next->count : 0;
if (monster_can_pick(monster, tmp)) {
remove_ob(tmp);
tmp = insert_ob_in_ob(tmp, monster);
(void)monster_check_apply(monster, tmp);
}
/* We could try to re-establish the cycling, of the space, but probably
* not a big deal to just bail out.
*/
if (next && was_destroyed(next, next_tag))
return;
}
}
/*
* monster_can_pick(): If the monster is interested in picking up
* the item, then return 0. Otherwise 0.
* Instead of pick_up, flags for "greed", etc, should be used.
* I've already utilized flags for bows, wands, rings, etc, etc. -Frank.
*/
static int monster_can_pick(object *monster, object *item) {
int flag = 0;
int i;
if (!can_pick(monster, item))
return 0;
if (QUERY_FLAG(item, FLAG_UNPAID))
return 0;
if (monster->pick_up&64) /* All */
flag = 1;
else {
if (IS_WEAPON(item))
flag = (monster->pick_up&8) || QUERY_FLAG(monster, FLAG_USE_WEAPON);
else if (IS_ARMOR(item))
flag = (monster->pick_up&16) || QUERY_FLAG(monster, FLAG_USE_ARMOUR);
else if (IS_SHIELD(item))
flag = (monster->pick_up&16) || QUERY_FLAG(monster, FLAG_USE_SHIELD);
else switch (item->type) {
case MONEY:
case GEM:
flag = monster->pick_up&2;
break;
case FOOD:
flag = monster->pick_up&4;
break;
case SKILL:
flag = QUERY_FLAG(monster, FLAG_CAN_USE_SKILL);
break;
case RING:
flag = QUERY_FLAG(monster, FLAG_USE_RING);
break;
case WAND:
case HORN:
case ROD:
flag = QUERY_FLAG(monster, FLAG_USE_RANGE);
break;
case SPELLBOOK:
flag = (monster->arch != NULL && QUERY_FLAG((&monster->arch->clone), FLAG_CAST_SPELL));
break;
case SCROLL:
flag = QUERY_FLAG(monster, FLAG_USE_SCROLL);
break;
case BOW:
case ARROW:
flag = QUERY_FLAG(monster, FLAG_USE_BOW);
break;
}
/* Simplistic check - if the monster has a location to equip it, he will
* pick it up. Note that this doesn't handle cases where an item may
* use several locations.
*/
for (i = 0; i < NUM_BODY_LOCATIONS; i++) {
if (monster->body_info[i] && item->body_info[i]) {
flag = 1;
break;
}
}
}
if (((!(monster->pick_up&32)) && flag) || ((monster->pick_up&32) && (!flag)))
return 1;
return 0;
}
/*
* monster_apply_below():
* Vick's (vick@bern.docs.uu.se) @921107 -> If a monster who's
* eager to apply things, encounters something apply-able,
* then make him apply it
*/
static void monster_apply_below(object *monster) {
object *tmp, *next;
for (tmp = monster->below; tmp != NULL; tmp = next) {
next = tmp->below;
switch (tmp->type) {
case CF_HANDLE:
case TRIGGER:
if (monster->will_apply&WILL_APPLY_HANDLE)
manual_apply(monster, tmp, 0);
break;
case TREASURE:
if (monster->will_apply&WILL_APPLY_TREASURE)
manual_apply(monster, tmp, 0);
break;
}
if (QUERY_FLAG(tmp, FLAG_IS_FLOOR))
break;
}
}
/*
* monster_check_apply() is meant to be called after an item is
* inserted in a monster.
* If an item becomes outdated (monster found a better item),
* a pointer to that object is returned, so it can be dropped.
* (so that other monsters can pick it up and use it)
* Note that as things are now, monsters never drop something -
* they can pick up all that they can use.
*/
/* Sept 96, fixed this so skills will be readied -b.t.*/
void monster_check_apply(object *mon, object *item) {
int flag = 0;
if (item->type == SPELLBOOK
&& mon->arch != NULL
&& (QUERY_FLAG((&mon->arch->clone), FLAG_CAST_SPELL))) {
SET_FLAG(mon, FLAG_CAST_SPELL);
return;
}
/* If for some reason, this item is already applied, no more work to do */
if (QUERY_FLAG(item, FLAG_APPLIED))
return;
/* Might be better not to do this - if the monster can fire a bow,
* it is possible in his wanderings, he will find one to use. In
* which case, it would be nice to have ammo for it.
*/
if (QUERY_FLAG(mon, FLAG_USE_BOW) && item->type == ARROW) {
/* Check for the right kind of bow */
object *bow;
for (bow = mon->inv; bow != NULL; bow = bow->below)
if (bow->type == BOW && bow->race == item->race) {
SET_FLAG(mon, FLAG_READY_BOW);
LOG(llevMonster, "Found correct bow for arrows.\n");
return; /* nothing more to do for arrows */
}
}
if (item->type == TREASURE && mon->will_apply&WILL_APPLY_TREASURE)
flag = 1;
/* Eating food gets hp back */
else if (item->type == FOOD && mon->will_apply&WILL_APPLY_FOOD)
flag = 1;
else if (item->type == SCROLL && QUERY_FLAG(mon, FLAG_USE_SCROLL)) {
if (!item->inv)
LOG(llevDebug, "Monster %d having scroll %d with empty inventory!\n", mon->count, item->count);
else if (monster_should_cast_spell(mon, item->inv))
SET_FLAG(mon, FLAG_READY_SCROLL);
/* Don't use it right now */
return;
} else if (item->type == WEAPON)
flag = check_good_weapon(mon, item);
else if (IS_ARMOR(item) || IS_SHIELD(item))
flag = check_good_armour(mon, item);
/* Should do something more, like make sure this is a better item */
else if (item->type == RING)
flag = 1;
else if (item->type == WAND || item->type == ROD || item->type == HORN) {
/* We never really 'ready' the wand/rod/horn, because that would mean the
* weapon would get undone.
*/
if (!(can_apply_object(mon, item)&CAN_APPLY_NOT_MASK)) {
SET_FLAG(mon, FLAG_READY_RANGE);
SET_FLAG(item, FLAG_APPLIED);
}
return;
} else if (item->type == BOW) {
/* We never really 'ready' the bow, because that would mean the
* weapon would get undone.
*/
if (!(can_apply_object(mon, item)&CAN_APPLY_NOT_MASK))
SET_FLAG(mon, FLAG_READY_BOW);
return;
} else if (item->type == SKILL) {
/*
* skills are specials: monsters must have the 'FLAG_READY_SKILL' flag set,
* else they can't use the skill...
* Skills also don't need to get applied, so return now.
*/
SET_FLAG(mon, FLAG_READY_SKILL);
return;
}
/* if we don't match one of the above types, return now.
* can_apply_object will say that we can apply things like flesh,
* bolts, and whatever else, because it only checks against the
* body_info locations.
*/
if (!flag)
return;
/* Check to see if the monster can use this item. If not, no need
* to do further processing. Note that can_apply_object already checks
* for the CAN_USE flags.
*/
if (can_apply_object(mon, item)&CAN_APPLY_NOT_MASK)
return;
/* should only be applying this item, not unapplying it.
* also, ignore status of curse so they can take off old armour.
* monsters have some advantages after all.
*/
manual_apply(mon, item, AP_APPLY|AP_IGNORE_CURSE);
return;
}
void npc_call_help(object *op) {
int x, y, mflags;
object *npc;
sint16 sx, sy;
mapstruct *m;
for (x = -3; x < 4; x++)
for (y = -3; y < 4; y++) {
m = op->map;
sx = op->x+x;
sy = op->y+y;
mflags = get_map_flags(m, &m, sx, sy, &sx, &sy);
/* If nothing alive on this space, no need to search the space. */
if ((mflags&P_OUT_OF_MAP) || !(mflags&P_IS_ALIVE))
continue;
for (npc = GET_MAP_OB(m, sx, sy); npc != NULL; npc = npc->above)
if (QUERY_FLAG(npc, FLAG_ALIVE) && QUERY_FLAG(npc, FLAG_UNAGGRESSIVE))
npc->enemy = op->enemy;
}
}
static int dist_att(int dir, object *ob, object *enemy, object *part, rv_vector *rv) {
if (can_hit(part, enemy, rv))
return dir;
if (rv->distance < 10)
return absdir(dir+4);
else if (rv->distance > 18)
return dir;
return 0;
}
static int run_att(int dir, object *ob, object *enemy, object *part, rv_vector *rv) {
if ((can_hit(part, enemy, rv) && ob->move_status < 20) || ob->move_status < 20) {
ob->move_status++;
return (dir);
} else if (ob->move_status > 20)
ob->move_status = 0;
return absdir(dir+4);
}
static int hitrun_att(int dir, object *ob, object *enemy) {
if (ob->move_status++ < 25)
return dir;
else if (ob->move_status < 50)
return absdir(dir+4);
else
ob->move_status = 0;
return absdir(dir+4);
}
static int wait_att(int dir, object *ob, object *enemy, object *part, rv_vector *rv) {
int inrange = can_hit(part, enemy, rv);
if (ob->move_status || inrange)
ob->move_status++;
if (ob->move_status == 0)
return 0;
else if (ob->move_status < 10)
return dir;
else if (ob->move_status < 15)
return absdir(dir+4);
ob->move_status = 0;
return 0;
}
static int disthit_att(int dir, object *ob, object *enemy, object *part, rv_vector *rv) {
/* The logic below here looked plain wrong before. Basically, what should
* happen is that if the creatures hp percentage falls below run_away,
* the creature should run away (dir+4)
* I think its wrong for a creature to have a zero maxhp value, but
* at least one map has this set, and whatever the map contains, the
* server should try to be resilant enough to avoid the problem
*/
if (ob->stats.maxhp && (ob->stats.hp*100)/ob->stats.maxhp < ob->run_away)
return absdir(dir+4);
return dist_att(dir, ob, enemy, part, rv);
}
static int wait_att2(int dir, object *ob, object *enemy, object *part, rv_vector *rv) {
if (rv->distance < 9)
return absdir(dir+4);
return 0;
}
static void circ1_move(object *ob) {
static const int circle [12] = { 3, 3, 4, 5, 5, 6, 7, 7, 8, 1, 1, 2 };
if (++ob->move_status > 11)
ob->move_status = 0;
if (!(move_object(ob, circle[ob->move_status])))
(void)move_object(ob, RANDOM()%8+1);
}
static void circ2_move(object *ob) {
static const int circle[20] = { 3, 3, 3, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 1, 1, 1, 2, 2 };
if (++ob->move_status > 19)
ob->move_status = 0;
if (!(move_object(ob, circle[ob->move_status])))
(void)move_object(ob, RANDOM()%8+1);
}
static void pace_movev(object *ob) {
if (ob->move_status++ > 6)
ob->move_status = 0;
if (ob->move_status < 4)
(void)move_object(ob, 5);
else
(void)move_object(ob, 1);
}
static void pace_moveh(object *ob) {
if (ob->move_status++ > 6)
ob->move_status = 0;
if (ob->move_status < 4)
(void)move_object(ob, 3);
else
(void)move_object(ob, 7);
}
static void pace2_movev(object *ob) {
if (ob->move_status++ > 16)
ob->move_status = 0;
if (ob->move_status < 6)
(void)move_object(ob, 5);
else if (ob->move_status < 8)
return;
else if (ob->move_status < 13)
(void)move_object(ob, 1);
else
return;
}
static void pace2_moveh(object *ob) {
if (ob->move_status++ > 16)
ob->move_status = 0;
if (ob->move_status < 6)
(void)move_object(ob, 3);
else if (ob->move_status < 8)
return;
else if (ob->move_status < 13)
(void)move_object(ob, 7);
else
return;
}
static void rand_move(object *ob) {
int i;
if (ob->move_status < 1
|| ob->move_status > 8
|| !(move_object(ob, ob->move_status || !(RANDOM()%9))))
for (i = 0; i < 5; i++)
if (move_object(ob, ob->move_status = RANDOM()%8+1))
return;
}
void check_earthwalls(object *op, mapstruct *m, int x, int y) {
object *tmp;
for (tmp = GET_MAP_OB(m, x, y); tmp != NULL; tmp = tmp->above) {
if (tmp->type == EARTHWALL) {
hit_player(tmp, op->stats.dam, op, AT_PHYSICAL, 1);
return;
}
}
}
void check_doors(object *op, mapstruct *m, int x, int y) {
object *tmp;
for (tmp = GET_MAP_OB(m, x, y); tmp != NULL; tmp = tmp->above) {
if (tmp->type == DOOR) {
hit_player(tmp, 1000, op, AT_PHYSICAL, 1);
return;
}
}
}
/**
* This function looks for an object or creature that is listening to said text.
*
* There is a rare event that the orig_map is used for - basically, if
* a player says the magic word that gets him teleported off the map,
* it can result in the new map putting the object count too high,
* which forces the swap out of some other map. In some cases, the
* map the player was just on now gets swapped out - thus, the
* object on that map are no longer in memory. So check to see if the
* players map changes, and if so, don't process any further.
* If it does change, most likely we don't care about the results
* of further conversation. Also, depending on the value of i,
* the conversation would continue on the new map, which probably isn't
* what is really wanted either.
*
* @param op who is saying something.
* @param txt what is said.
*/
void communicate(object *op, const char *txt) {
object *npc;
int i, mflags, talked = 0;
sint16 x, y;
mapstruct *mp, *orig_map = op->map;
char buf[MAX_BUF];
snprintf(buf, sizeof(buf), "%s says: %s", op->name, txt);
if (op->type == PLAYER) {
ext_info_map(NDI_WHITE, op->map, MSG_TYPE_COMMUNICATION, MSG_TYPE_COMMUNICATION_SAY, buf, NULL);
}
/* Note that this loop looks pretty inefficient to me - we look and try to talk
* to every object within 2 spaces. It would seem that if we trim this down to
* only try to talk to objects with npc->msg set, things would be a lot more efficient,
* but I'm not sure if there are any objects out there that don't have a message and instead
* rely sorely on events - MSW 2009-04-14
*/
for (i = 0; i <= SIZEOFFREE2; i++) {
mp = op->map;
x = op->x+freearr_x[i];
y = op->y+freearr_y[i];
mflags = get_map_flags(mp, &mp, x, y, &x, &y);
if (mflags&P_OUT_OF_MAP)
continue;
for (npc = GET_MAP_OB(mp, x, y); npc != NULL; npc = npc->above) {
talk_to_npc(op, npc, txt, &talked);
if (orig_map != op->map) {
LOG(llevDebug, "Warning: Forced to swap out very recent map - MAX_OBJECTS should probably be increased\n");
return;
}
}
}
/* if talked is set, then the talk_to_npc() wrote out this information, so
* don't do it again.
*/
if (!talked) {
}
}
/**
* Checks the messages of a NPC for a matching text. Will not call
* plugin events. Called by talk_to_npc().
* @param op who is saying something
* @param npc object that gets a chance to reply.
* @param txt text being said.
* @param talked did op already talk? Will be modified if this function makes op talk.
* @return 1 if npc talked, 0 else.
*/
static int do_talk_npc(object *op, object *npc, const char *txt, int *talked) {
char buf[MAX_BUF];
struct_dialog_reply *reply;
struct_dialog_message *message;
if (!get_dialog_message(npc, txt, &message, &reply))
return 0;
if (reply) {
snprintf(buf, sizeof(buf), "%s %s: %s", op->name, (reply->type == rt_reply ? "replies" : "asks"), reply->message);
ext_info_map(NDI_WHITE, op->map, MSG_TYPE_COMMUNICATION, MSG_TYPE_COMMUNICATION_SAY, buf, NULL);
*talked = 1;
}
#if 0
/* let the caller handle this reply - no reason we need to. Leaving this in for the
* time being, as I don't completely understand what all of this is trying to do.
* MSW 2009-04-14
*/
else if (!*talked) {
*talked = 1;
snprintf(buf, sizeof(buf), "%s says: %s", op->name, txt);
ext_info_map(NDI_WHITE, op->map, MSG_TYPE_COMMUNICATION, MSG_TYPE_COMMUNICATION_SAY, buf, NULL);
}
#endif
if (npc->type == MAGIC_EAR) {
ext_info_map(NDI_NAVY|NDI_UNIQUE, npc->map, MSG_TYPE_DIALOG, MSG_TYPE_DIALOG_MAGIC_MOUTH, message->message, NULL);
use_trigger(npc);
} else {
npc_say(npc, message->message);
reply = message->replies;
if (reply) {
draw_ext_info(NDI_WHITE, 0, op, MSG_TYPE_COMMUNICATION, MSG_TYPE_COMMUNICATION_SAY, "Replies:", NULL);
while (reply) {
draw_ext_info_format(NDI_WHITE, 0, op, MSG_TYPE_COMMUNICATION, MSG_TYPE_COMMUNICATION_SAY, " - %s: %s", NULL, reply->reply, reply->message);
reply = reply->next;
}
}
}
return 1;
}
/**
* Simple function to have some NPC say something.
* @param npc who should say something.
* @param cp what is being said.
*/
void npc_say(object *npc, const char *cp) {
char buf[HUGE_BUF], name[MAX_BUF];
query_name(npc, name, sizeof(name));
snprintf(buf, sizeof(buf), "%s says: %s", name, cp);
ext_info_map(NDI_NAVY|NDI_UNIQUE, npc->map, MSG_TYPE_DIALOG, MSG_TYPE_DIALOG_NPC,
buf, buf);
}
/**
* Give an object the chance to handle something being said.
* Plugin hooks will be called, including in the NPC's inventory.
*
* @param op who is talking.
* @param npc object to try to talk to. Can be an NPC or a MAGIC_EAR.
* @param txt what op is saying.
* @param talked did op already talk? Can be modified by this function.
* @return 0 if text was handled by a plugin or not handled, 1 if handled internally by the server.
*/
static int talk_to_npc(object *op, object *npc, const char *txt, int *talked) {
object *cobj;
/* Move this commone area up here - shouldn't cost much extra cpu
* time, and makes the function more readable */
/* Lauwenmark: Handle for plugin say event */
if (execute_event(npc, EVENT_SAY, op, NULL, txt, SCRIPT_FIX_ALL) != 0)
return 0;
/* Lauwenmark - Here we let the objects inside inventories hear and answer, too. */
/* This allows the existence of "intelligent" weapons you can discuss with */
for (cobj = npc->inv; cobj != NULL; cobj = cobj->below) {
if (execute_event(cobj, EVENT_SAY, npc, NULL, txt, SCRIPT_FIX_ALL) != 0)
return 0;
}
if (op == npc)
return 0;
return do_talk_npc(op, npc, txt, talked);
}
/* find_mon_throw_ob() - modeled on find_throw_ob
* This is probably overly simplistic as it is now - We want
* monsters to throw things like chairs and other pieces of
* furniture, even if they are not good throwable objects.
* Probably better to have the monster throw a throwable object
* first, then throw any non equipped weapon.
*/
object *find_mon_throw_ob(object *op) {
object *tmp = NULL;
if (op->head)
tmp = op->head;
else
tmp = op;
/* New throw code: look through the inventory. Grap the first legal is_thrown
* marked item and throw it to the enemy.
*/
for (tmp = op->inv; tmp; tmp = tmp->below) {
/* Can't throw invisible objects or items that are applied */
if (tmp->invisible || QUERY_FLAG(tmp, FLAG_APPLIED))
continue;
if (QUERY_FLAG(tmp, FLAG_IS_THROWN))
break;
}
#ifdef DEBUG_THROW
{
char what[MAX_BUF];
query_name(tmp, what, MAX_BUF);
LOG(llevDebug, "%s chooses to throw: %s (%d)\n", op->name, !(tmp) ? "(nothing)" : what, tmp ? tmp->count : -1);
}
#endif
return tmp;
}
/* determine if we can 'detect' the enemy. Check for walls blocking the
* los. Also, just because its hidden/invisible, we may be sensitive/smart
* enough (based on Wis & Int) to figure out where the enemy is. -b.t.
* modified by MSW to use the get_rangevector so that map tiling works
* properly. I also so odd code in place that checked for x distance
* OR y distance being within some range - that seemed wrong - both should
* be within the valid range. MSW 2001-08-05
* Returns 0 if enemy can not be detected, 1 if it is detected
*/
int can_detect_enemy(object *op, object *enemy, rv_vector *rv) {
int radius = MIN_MON_RADIUS, hide_discovery;
/* null detection for any of these condtions always */
if (!op || !enemy || !op->map || !enemy->map)
return 0;
/* If the monster (op) has no way to get to the enemy, do nothing */
if (!on_same_map(op, enemy))
return 0;
get_rangevector(op, enemy, rv, 0);
/* Monsters always ignore the DM */
if ((op->type != PLAYER) && QUERY_FLAG(enemy, FLAG_WIZ))
return 0;
/* simple check. Should probably put some range checks in here. */
if (can_see_enemy(op, enemy))
return 1;
/* The rest of this is for monsters. Players are on their own for
* finding enemies!
*/
if (op->type == PLAYER)
return 0;
/* Quality invisible? Bah, we wont see them w/o SEE_INVISIBLE
* flag (which was already checked) in can_see_enmy (). Lets get out of here
*/
if (enemy->invisible && (!enemy->contr || (!enemy->contr->tmp_invis && !enemy->contr->hidden)))
return 0;
/* use this for invis also */
hide_discovery = op->stats.Int/5;
/* Determine Detection radii */
if (!enemy->hide) /* to detect non-hidden (eg dark/invis enemy) */
radius = MAX((op->stats.Wis/5)+1, MIN_MON_RADIUS);
else { /* a level/INT/Dex adjustment for hiding */
object *sk_hide;
int bonus = (op->level/2)+(op->stats.Int/5);
if (enemy->type == PLAYER) {
if ((sk_hide = find_skill_by_number(enemy, SK_HIDING)))
bonus -= sk_hide->level;
else {
LOG(llevError, "can_detect_enemy() got hidden player w/o hiding skill!\n");
make_visible(enemy);
radius = MAX(radius, MIN_MON_RADIUS);
}
} else /* enemy is not a player */
bonus -= enemy->level;
radius += bonus/5;
hide_discovery += bonus*5;
} /* else creature has modifiers for hiding */
/* Radii stealth adjustment. Only if you are stealthy
* will you be able to sneak up closer to creatures */
if (QUERY_FLAG(enemy, FLAG_STEALTH))
radius = radius/2, hide_discovery = hide_discovery/3;
/* Radii adjustment for enemy standing in the dark */
if (op->map->darkness > 0 && !stand_in_light(enemy)) {
/* on dark maps body heat can help indicate location with infravision
* undead don't have body heat, so no benefit detecting them.
*/
if (QUERY_FLAG(op, FLAG_SEE_IN_DARK) && !is_true_undead(enemy))
radius += op->map->darkness/2;
else
radius -= op->map->darkness/2;
/* op next to a monster (and not in complete darkness)
* the monster should have a chance to see you.
*/
if (radius < MIN_MON_RADIUS && op->map->darkness < 5 && rv->distance <= 1)
radius = MIN_MON_RADIUS;
} /* if on dark map */
/* Lets not worry about monsters that have incredible detection
* radii, we only need to worry here about things the player can
* (potentially) see. This is 13, as that is the maximum size the player
* may have for their map - in that way, creatures at the edge will
* do something. Note that the distance field in the
* vector is real distance, so in theory this should be 18 to
* find that.
*/
if (radius > 13)
radius = 13;
/* Enemy in range! Now test for detection */
if ((int)rv->distance <= radius) {
/* ah, we are within range, detected? take cases */
if (!enemy->invisible) /* enemy in dark squares... are seen! */
return 1;
/* hidden or low-quality invisible */
if (enemy->hide && (rv->distance <= 1) && (RANDOM()%100 <= hide_discovery)) {
make_visible(enemy);
/* inform players of new status */
if (enemy->type == PLAYER && player_can_view(enemy, op))
draw_ext_info_format(NDI_UNIQUE, 0, enemy, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE,
"You are discovered by %s!",
"You are discovered by %s!",
op->name);
return 1; /* detected enemy */
} else if (enemy->invisible) {
/* Change this around - instead of negating the invisible, just
* return true so that the mosnter that managed to detect you can
* do something to you. Decreasing the duration of invisible
* doesn't make a lot of sense IMO, as a bunch of stupid creatures
* can then basically negate the spell. The spell isn't negated -
* they just know where you are!
*/
if ((RANDOM()%50) <= hide_discovery) {
if (enemy->type == PLAYER) {
char name[MAX_BUF];
query_name(op, name, MAX_BUF);
draw_ext_info_format(NDI_UNIQUE, 0, enemy, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE,
"You see %s noticing your position.",
"You see %s noticing your position.",
name);
}
return 1;
}
}
} /* within range */
/* Wasn't detected above, so still hidden */
return 0;
}
/* determine if op stands in a lighted square. This is not a very
* intellegent algorithm. For one thing, we ignore los here, SO it
* is possible for a bright light to illuminate a player on the
* other side of a wall (!).
*/
int stand_in_light(object *op) {
sint16 nx, ny;
mapstruct *m;
if (!op)
return 0;
if (op->glow_radius > 0)
return 1;
if (op->map) {
int x, y, x1, y1;
/* Check the spaces with the max light radius to see if any of them
* have lights, and if any of them light the player enough, then return 1.
*/
for (x = op->x-MAX_LIGHT_RADII; x <= op->x+MAX_LIGHT_RADII; x++) {
for (y = op->y-MAX_LIGHT_RADII; y <= op->y+MAX_LIGHT_RADII; y++) {
m = op->map;
nx = x;
ny = y;
if (get_map_flags(m, &m, nx, ny, &nx, &ny)&P_OUT_OF_MAP)
continue;
x1 = abs(x-op->x)*abs(x-op->x);
y1 = abs(y-op->y)*abs(y-op->y);
if (isqrt(x1+y1) < GET_MAP_LIGHT(m, nx, ny))
return 1;
}
}
}
return 0;
}
/*
* assuming no walls/barriers, lets check to see if its *possible*
* to see an enemy. Note, "detection" is different from "seeing".
* See can_detect_enemy() for more details. -b.t.
* return 0 if can't be seen, 1 if can be
*/
int can_see_enemy(object *op, object *enemy) {
object *looker = op->head ? op->head : op;
/* safety */
if (!looker || !enemy || !QUERY_FLAG(looker, FLAG_ALIVE))
return 0;
/* we dont give a full treatment of xrays here (shorter range than normal,
* see through walls). Should we change the code elsewhere to make you
* blind even if you can xray?
*/
if (QUERY_FLAG(looker, FLAG_BLIND) && !QUERY_FLAG(looker, FLAG_XRAYS))
return 0;
/* checking for invisible things */
if (enemy->invisible) {
/* HIDDEN ENEMY. by definition, you can't see hidden stuff!
* However, if you carry any source of light, then the hidden
* creature is seeable (and stupid) */
if (has_carried_lights(enemy)) {
if (enemy->hide) {
make_visible(enemy);
draw_ext_info(NDI_UNIQUE, 0, enemy, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE,
"Your light reveals your hiding spot!",
NULL);
}
return 1;
} else if (enemy->hide)
return 0;
/* Invisible enemy. Break apart the check for invis undead/invis looker
* into more simple checks - the QUERY_FLAG doesn't return 1/0 values,
* and making it a conditional makes the code pretty ugly.
*/
if (!QUERY_FLAG(looker, FLAG_SEE_INVISIBLE)) {
if (makes_invisible_to(enemy, looker))
return 0;
}
} else if (looker->type == PLAYER) /* for players, a (possible) shortcut */
if (player_can_view(looker, enemy))
return 1;
/* ENEMY IN DARK MAP. Without infravision, the enemy is not seen
* unless they carry a light or stand in light. Darkness doesnt
* inhibit the undead per se (but we should give their archs
* CAN_SEE_IN_DARK, this is just a safety
* we care about the enemy maps status, not the looker.
* only relevant for tiled maps, but it is possible that the
* enemy is on a bright map and the looker on a dark - in that
* case, the looker can still see the enemy
*/
if (enemy->map->darkness > 0
&& !stand_in_light(enemy)
&& (!QUERY_FLAG(looker, FLAG_SEE_IN_DARK) || !is_true_undead(looker) || !QUERY_FLAG(looker, FLAG_XRAYS)))
return 0;
return 1;
}