/*
* This file is part of Deliantra, the Roguelike Realtime MMORPG.
*
* Copyright (©) 2005,2006,2007,2008,2009,2010,2011 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
* Copyright (©) 2002 Mark Wedel & Crossfire Development Team
* Copyright (©) 1992 Frank Tore Johansen
*
* Deliantra is free software: you can redistribute it and/or modify it under
* the terms of the Affero GNU General Public License as published by the
* Free Software Foundation, either version 3 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 Affero GNU General Public License
* and the GNU General Public License along with this program. If not, see
* .
*
* The authors can be reached via e-mail to
*/
/*
* Routines that is executed from objects based on their speed have been
* collected in this file.
*/
#include
#include
#include
/* The following removes doors. The functions check to see if similar
* doors are next to the one that is being removed, and if so, set it
* so those will be removed shortly (in a cascade like fashion.)
*/
void
remove_door (object *op)
{
for (int i = 1; i < SIZEOFFREE1 + 1; i += 2)
{
object *tmp;
mapxy pos (op);
pos.move (i);
if (pos.normalise ()
&& (tmp = present (DOOR, pos.m, pos.x, pos.y)))
{
tmp->set_speed (0.1f);
tmp->speed_left = -0.2f;
}
}
if (op->other_arch)
{
object *tmp = op->other_arch->instance ();
tmp->x = op->x;
tmp->y = op->y;
tmp->map = op->map;
tmp->level = op->level;
insert_ob_in_map (tmp, op->map, op, 0);
}
op->drop_and_destroy ();
}
void
remove_door2 (object *op)
{
int i;
object *tmp;
for (i = 1; i < 9; i += 2)
{
tmp = present (LOCKED_DOOR, op->map, op->x + freearr_x[i], op->y + freearr_y[i]);
if (tmp && tmp->slaying == op->slaying)
{ /* same key both doors */
tmp->set_speed (0.1f);
tmp->speed_left = -0.2f;
}
}
if (op->other_arch)
{
tmp = op->other_arch->instance ();
tmp->x = op->x;
tmp->y = op->y;
tmp->map = op->map;
tmp->level = op->level;
insert_ob_in_map (tmp, op->map, op, 0);
}
op->drop_and_destroy ();
}
static void
generate_monster (object *gen)
{
if (!gen->map)
return;
if (GENERATE_SPEED (gen) && rndm (0, GENERATE_SPEED (gen) - 1))
return;
// sleeping generators won't generate, this will make monsters like
// centipedes not generate more centipedes when being asleep.
if (gen->flag [FLAG_SLEEP])
return;
object *op;
int dir;
if (gen->flag [FLAG_CONTENT_ON_GEN])
{
// either copy one item from the inventory...
if (!gen->inv)
return;
// first select one item from the inventory
int index = 0;
for (object *tmp = gen->inv; tmp; tmp = tmp->below)
if (!rndm (++index))
op = tmp;
dir = find_free_spot (op, gen->map, gen->x, gen->y, 1, SIZEOFFREE1 + 1);
if (dir < 0)
return;
op = op->deep_clone ();
op->clr_flag (FLAG_IS_A_TEMPLATE);
unflag_inv (op, FLAG_IS_A_TEMPLATE);
}
else if (gen->other_arch)
{
// ...or use other_arch
dir = find_free_spot (gen->other_arch, gen->map, gen->x, gen->y, 1, SIZEOFFREE1 + 1);
if (dir < 0)
return;
op = gen->other_arch->instance ();
}
else
return;
op->expand_tail ();
mapxy pos (gen); pos.move (dir);
if (pos.insert (op, gen))
{
if (rndm (0, 9))
generate_artifact (op, gen->map->difficulty);
if (op->has_random_items ())
create_treasure (op->randomitems, op, GT_APPLY, gen->map->difficulty);
return;
}
op->destroy ();
}
static void
remove_force (object *op)
{
if (--op->duration > 0)
return;
if (op->env)
switch (op->subtype)
{
case FORCE_CONFUSION:
op->env->clr_flag (FLAG_CONFUSED);
new_draw_info (NDI_UNIQUE, 0, op->env, "You regain your senses.\n");
default:
op->clr_flag (FLAG_APPLIED);
change_abil (op->env, op);
op->env->update_stats ();
}
op->destroy ();
}
static void
remove_blindness (object *op)
{
if (--op->stats.food > 0)
return;
op->clr_flag (FLAG_APPLIED);
if (op->env)
{
change_abil (op->env, op);
op->env->update_stats ();
}
op->destroy ();
}
static void
poison_more (object *op)
{
if (op->env == NULL || !op->env->flag [FLAG_ALIVE] || op->env->stats.hp < 0)
{
op->destroy ();
return;
}
if (op->stats.food == 1)
{
/* need to unapply the object before update_stats is called, else fix_player
* will not do anything.
*/
if (op->env->type == PLAYER)
{
op->clr_flag (FLAG_APPLIED);
op->env->update_stats ();
new_draw_info (NDI_UNIQUE, 0, op->env, "You feel much better now.");
}
op->destroy ();
return;
}
if (op->env->type == PLAYER)
{
op->env->stats.food--;
new_draw_info (NDI_UNIQUE, 0, op->env, "You feel very sick...");
}
hit_player (op->env, op->stats.dam, op, AT_INTERNAL, 1);
}
static void
move_gate (object *op)
{ /* 1 = going down, 0 = going up */
object *tmp;
if (op->stats.wc < 0 || (int) op->stats.wc >= NUM_ANIMATIONS (op))
{
LOG (llevError, "Gate error: animation was %d, max=%d\n", op->stats.wc, NUM_ANIMATIONS (op));
op->stats.wc = 0;
}
/* We're going down */
if (op->value)
{
if (--op->stats.wc <= 0)
{ /* Reached bottom, let's stop */
op->stats.wc = 0;
if (op->arch->has_active_speed ())
op->value = 0;
else
op->set_speed (0);
}
if ((int) op->stats.wc < (NUM_ANIMATIONS (op) / 2 + 1))
{
op->move_block = 0;
op->clr_flag (FLAG_BLOCKSVIEW);
update_all_los (op->map, op->x, op->y);
}
SET_ANIMATION (op, op->stats.wc);
update_object (op, UP_OBJ_CHANGE);
return;
}
/* We're going up */
/* First, lets see if we are already at the top */
if ((unsigned char) op->stats.wc == (NUM_ANIMATIONS (op) - 1))
{
/* Check to make sure that only non pickable and non rollable
* objects are above the gate. If so, we finish closing the gate,
* otherwise, we fall through to the code below which should lower
* the gate slightly.
*/
for (tmp = op->above; tmp; tmp = tmp->above)
if (!tmp->flag [FLAG_NO_PICK] || tmp->flag [FLAG_CAN_ROLL] || tmp->flag [FLAG_ALIVE])
break;
if (!tmp)
{
if (op->arch->has_active_speed ())
op->value = 1;
else
op->set_speed (0);
return;
}
}
if (op->stats.food)
{ /* The gate is going temporarily down */
if (--op->stats.wc <= 0)
{ /* Gone all the way down? */
op->stats.food = 0; /* Then let's try again */
op->stats.wc = 0;
}
}
else
{ /* The gate is still going up */
op->stats.wc++;
if (op->stats.wc >= NUM_ANIMATIONS (op))
op->stats.wc = NUM_ANIMATIONS (op) - 1;
/* If there is something on top of the gate, we try to roll it off.
* If a player/monster, we don't roll, we just hit them with damage
*/
if (op->stats.wc >= NUM_ANIMATIONS (op) / 2)
{
/* Halfway or further, check blocks */
/* First, get the top object on the square. */
for (tmp = op->above; tmp && tmp->above; tmp = tmp->above)
;
if (tmp)
{
if (tmp->flag [FLAG_ALIVE])
{
hit_player (tmp, random_roll (0, op->stats.dam, tmp, PREFER_LOW), op, AT_PHYSICAL, 1);
op->play_sound (sound_find ("blocked_gate"));
if (tmp->type == PLAYER)
new_draw_info_format (NDI_UNIQUE, 0, tmp, "You are crushed by the %s!", &op->name);
}
/* If the object is not alive, and the object either can
* be picked up or the object rolls, move the object
* off the gate.
*/
else if (!tmp->flag [FLAG_ALIVE] && (!tmp->flag [FLAG_NO_PICK] || tmp->flag [FLAG_CAN_ROLL]))
{
/* If it has speed, it should move itself, otherwise: */
int i = find_free_spot (tmp, op->map, op->x, op->y, 1, SIZEOFFREE1 + 1);
/* If there is a free spot, move the object someplace */
if (i > 0)
{
mapxy pos (tmp);
pos.move (i);
if (pos.normalise ())
tmp->move_to (pos);
}
}
}
/* See if there is still anything blocking the gate */
for (tmp = op->above; tmp; tmp = tmp->above)
if (!tmp->flag [FLAG_NO_PICK] || tmp->flag [FLAG_CAN_ROLL] || tmp->flag [FLAG_ALIVE])
break;
/* IF there is, start putting the gate down */
if (tmp)
op->stats.food = 1;
else
{
op->move_block = MOVE_ALL;
if (!op->arch->stats.ac)
op->set_flag (FLAG_BLOCKSVIEW);
update_all_los (op->map, op->x, op->y);
}
} /* gate is halfway up */
SET_ANIMATION (op, op->stats.wc);
update_object (op, UP_OBJ_CHANGE);
} /* gate is going up */
}
/* hp : how long door is open/closed
* maxhp : initial value for hp
* sp : 1 = open, 0 = close
*/
static void
move_timed_gate (object *op)
{
int v = op->value;
if (op->stats.sp)
{
move_gate (op);
if (op->value != v) /* change direction ? */
op->stats.sp = 0;
return;
}
if (--op->stats.hp <= 0)
{ /* keep gate down */
move_gate (op);
if (op->value != v)
op->set_speed (0);
}
}
/* slaying: name of the thing the detector is to look for
* speed: frequency of 'glances'
* connected: connected value of detector
* sp: 1 if detection sets buttons
* -1 if detection unsets buttons
*/
static void
move_detector (object *op)
{
object *tmp;
int last = op->value;
int detected;
detected = 0;
for (tmp = op->ms ().bot; tmp && !detected; tmp = tmp->above)
{
object *tmp2;
if (op->stats.hp)
{
for (tmp2 = tmp->inv; tmp2; tmp2 = tmp2->below)
{
if (op->slaying && op->slaying == tmp->name)
detected = 1;
if (tmp2->type == FORCE && tmp2->slaying && tmp2->slaying == op->slaying)
detected = 1;
}
}
if (op->slaying && op->slaying == tmp->name)
detected = 1;
else if (tmp->type == SPECIAL_KEY && tmp->slaying == op->slaying)
detected = 1;
}
/* the detector sets the button if detection is found */
if (op->stats.sp == 1)
{
if (detected && last == 0)
{
op->value = 1;
push_button (op, tmp);
}
if (!detected && last == 1)
{
op->value = 0;
push_button (op, tmp);
}
}
else
{ /* in this case, we unset buttons */
if (detected && last == 1)
{
op->value = 0;
push_button (op, tmp);
}
if (!detected && last == 0)
{
op->value = 1;
push_button (op, tmp);
}
}
}
void
animate_trigger (object *op)
{
if ((unsigned char) ++op->stats.wc >= NUM_ANIMATIONS (op))
{
op->stats.wc = 0;
check_trigger (op, NULL);
}
else
{
SET_ANIMATION (op, op->stats.wc);
update_object (op, UP_OBJ_FACE);
}
}
static void
move_hole (object *op)
{ /* 1 = opening, 0 = closing */
if (op->value)
{ /* We're opening */
if (--op->stats.wc <= 0)
{ /* Opened, let's stop */
op->stats.wc = 0;
op->set_speed (0);
/* Hard coding this makes sense for holes I suppose */
op->move_on = MOVE_WALK;
for (object *next, *tmp = op->above; tmp; tmp = next)
{
next = tmp->above;
move_apply (op, tmp, tmp);
}
}
SET_ANIMATION (op, op->stats.wc);
update_object (op, UP_OBJ_CHANGE);
return;
}
/* We're closing */
op->move_on = 0;
op->stats.wc++;
if (op->stats.wc >= NUM_ANIMATIONS (op))
op->stats.wc = NUM_ANIMATIONS (op) - 1;
SET_ANIMATION (op, op->stats.wc);
update_object (op, UP_OBJ_CHANGE);
if (op->stats.wc == (NUM_ANIMATIONS (op) - 1))
op->set_speed (0); /* closed, let's stop */
}
/* stop_item() returns a pointer to the stopped object. The stopped object
* may or may not have been removed from maps or inventories. It will not
* have been merged with other items.
*
* This function assumes that only items on maps need special treatment.
*
* If the object can't be stopped, or it was destroyed while trying to stop
* it, NULL is returned.
*
* fix_stopped_item() should be used if the stopped item should be put on
* the map.
*/
object *
stop_item (object *op)
{
if (op->map == NULL)
return op;
switch (op->type)
{
case THROWN_OBJ:
{
object *payload = op->inv;
if (payload == NULL)
return NULL;
payload->remove ();
op->destroy ();
return payload;
}
case ARROW:
if (op->has_active_speed ())
op = fix_stopped_arrow (op);
return op;
default:
return op;
}
}
/* fix_stopped_item() - put stopped item where stop_item() had found it.
* Inserts item into the old map, or merges it if it already is on the map.
*
* 'map' must be the value of op->map before stop_item() was called.
*/
void
fix_stopped_item (object *op, maptile *map, object *originator)
{
if (map == NULL)
return;
if (op->flag [FLAG_REMOVED])
insert_ob_in_map (op, map, originator, 0);
else if (op->type == ARROW)
merge_ob (op, NULL); /* only some arrows actually need this */
}
object *
fix_stopped_arrow (object *op)
{
if (rndm (0, 99) < op->stats.food)
{
/* Small chance of breaking */
op->destroy ();
return NULL;
}
op->set_speed (0);
op->direction = 0;
op->move_on = 0;
op->move_type = 0;
op->skill = 0; // really?
// restore original wc, dam, attacktype and slaying
op->stats.wc = op->stats.sp;
op->stats.dam = op->stats.hp;
op->attacktype = op->stats.grace;
op->slaying = op->custom_name;
/* Reset these to defaults, so that object::can_merge will work properly */
op->custom_name = 0;
op->stats.sp = 0;
op->stats.hp = 0;
op->stats.grace = 0;
op->level = 0;
op->face = op->arch->face;
op->owner = 0;
update_object (op, UP_OBJ_CHANGE);
return op;
}
/* stop_arrow() - what to do when a non-living flying object
* has to stop. Sept 96 - I added in thrown object code in
* here too. -b.t.
*
* Returns a pointer to the stopped object (which will have been removed
* from maps or inventories), or NULL if was destroyed.
*/
static void
stop_arrow (object *op)
{
if (INVOKE_OBJECT (STOP, op))
return;
if (op->inv)
{
// replace this by straightforward drop to ground?
object *payload = op->inv;
payload->owner = 0;
insert_ob_in_map (payload, op->map, payload, 0);
op->destroy ();
}
else
{
op = fix_stopped_arrow (op);
if (op)
merge_ob (op, 0);
}
}
/* Move an arrow or throwen_obj along its course. op is the arrow or thrown object.
*/
void
move_arrow (object *op)
{
int was_reflected;
if (!op->map)
{
LOG (llevError | logBacktrace, "BUG: Arrow %s had no map.\n", op->debug_desc ());
op->destroy ();
return;
}
/* we need to stop thrown objects at some point. Like here. */
if (op->type == THROWN_OBJ)
{
/* If the object that the THROWN_OBJ encapsulates disappears,
* we need to have this object go away also - otherwise, you get
* left over remnants on the map. Where this currently happens
* is if the player throws a bomb - the bomb explodes on its own,
* but this object sticks around. We could handle the cleanup in the
* bomb code, but there are potential other cases where that could happen,
* and it is easy enough to clean it up here.
*/
if (!op->inv)
{
op->destroy ();
return;
}
if (op->last_sp-- < 0)
{
stop_arrow (op);
return;
}
}
/* decrease the speed as it flies. 0.05 means a standard bow will shoot
* about 17 squares. Tune as needed.
*/
op->set_speed (op->speed - 0.05);
/* if the arrow is moving too slow.. stop it. 0.5 was chosen as lower
values look rediculous. */
if (op->speed < (op->type == ARROW ? 0.5 : MIN_ACTIVE_SPEED))
{
stop_arrow (op);
return;
}
/* Calculate target map square */
was_reflected = 0;
mapxy pos (op); pos.move (op->direction);
if (!pos.normalise ())
{
stop_arrow (op);
return;
}
/* only need to look for living creatures if this flag is set */
if (pos->flags () & P_IS_ALIVE)
{
object *tmp;
for (tmp = pos->bot; tmp; tmp = tmp->above)
if (tmp->flag [FLAG_ALIVE])
break;
/* Not really fair, but don't let monsters hit themselves with
* their own arrow - this can be because they fire it then
* move into it.
*/
if (tmp && tmp != op->owner)
{
/* Found living object, but it is reflecting the missile. Update
* as below. (Note that for living creatures there is a small
* chance that reflect_missile fails.)
*/
if (tmp->flag [FLAG_REFL_MISSILE] && (rndm (0, 99)) < (90 - op->level / 10))
{
int number = op->face;
op->direction = absdir (op->direction + 4);
update_turn_face (op);
was_reflected = 1; /* skip normal movement calculations */
}
else
{
/* Attack the object. */
op = hit_with_arrow (op, tmp);
if (!op)
return;
}
} /* if this is not hitting its owner */
} /* if there is something alive on this space */
if (OB_TYPE_MOVE_BLOCK (op, pos->move_block))
{
int retry = 0;
/* if the object doesn't reflect, stop the arrow from moving
* note that this code will now catch cases where a monster is
* on a wall but has reflecting - the arrow won't reflect.
* Mapmakers shouldn't put monsters on top of wall in the first
* place, so I don't consider that a problem.
*/
if (!op->flag [FLAG_REFLECTING] || !rndm (0, 19))
{
stop_arrow (op);
return;
}
else
{
/* If one of the major directions (n,s,e,w), just reverse it */
if (op->direction & 1)
{
op->direction = absdir (op->direction + 4);
retry = 1;
}
/* There were two blocks with identical code -
* use this retry here to make this one block
* that did the same thing.
*/
while (retry < 2)
{
retry++;
/* Need to check for P_OUT_OF_MAP: if the arrow is travelling
* over a corner in a tiled map, it is possible that
* op->direction is within an adjacent map but either
* op->direction-1 or op->direction+1 does not exist.
*/
mapxy pos1 (pos); pos1.move (absdir (op->direction - 1));
bool left = pos1.normalise () && OB_TYPE_MOVE_BLOCK (op, pos1->move_block);
mapxy pos2 (pos); pos2.move (absdir (op->direction + 1));
bool right = pos2.normalise () && OB_TYPE_MOVE_BLOCK (op, pos2->move_block);
if (left == right)
op->direction = absdir (op->direction + 4);
else if (left)
op->direction = absdir (op->direction + 2);
else if (right)
op->direction = absdir (op->direction - 2);
/* If this space is not out of the map and not blocked, valid space -
* don't need to retry again.
*/
mapxy pos3 (pos); pos3.move (op->direction);
if (pos3.normalise () && !OB_TYPE_MOVE_BLOCK (op, pos3->move_block))
break;
}
/* Couldn't find a direction to move the arrow to - just
* stop it from moving.
*/
if (retry == 2)
{
stop_arrow (op);
return;
}
/* update object image for new facing */
/* many thrown objects *don't* have more than one face */
if (op->has_anim ())
op->set_anim_frame (op->direction);
} /* object is reflected */
} /* object ran into a wall */
/* Move the arrow. */
op->move_to (pos);
}
static void
change_object (object *op)
{ /* Doesn`t handle linked objs yet */
if (!op->other_arch)
{
LOG (llevError, "Change object (%s) without other_arch error.\n", op->debug_desc ());
return;
}
/* In non-living items only change when food value is 0 */
if (!op->flag [FLAG_ALIVE])
{
if (op->stats.food-- > 0)
return;
op->stats.food = 1; /* so 1 other_arch is made */
}
object *env = op->env;
op->remove ();
for (int i = 0; i < op->stats.food; i++)
{
object *tmp = op->other_arch->instance ();
tmp->stats.hp = op->stats.hp; /* The only variable it keeps. */
if (env)
env->insert (tmp);
else
{
int j = find_first_free_spot (tmp, op->map, op->x, op->y);
if (j < 0) /* No free spot */
tmp->destroy ();
else
{
mapxy pos (op); pos.move (j);
if (pos.normalise ())
pos.insert (tmp, op);
}
}
}
op->destroy ();
}
void
move_teleporter (object *op)
{
object *tmp, *head = op;
/* if this is a multipart teleporter, handle the other parts
* The check for speed isn't strictly needed - basically, if
* there is an old multipart teleporter in which the other parts
* have speed, we don't really want to call it twice for the same
* function - in fact, as written below, part N would get called
* N times without the speed check.
*/
if (op->more && !op->more->has_active_speed ())
move_teleporter (op->more);
if (op->head)
head = op->head;
for (tmp = op->above; tmp; tmp = tmp->above)
if (!tmp->flag [FLAG_IS_FLOOR])
break;
/* If nothing above us to move, nothing to do */
if (!tmp || tmp->flag [FLAG_WIZPASS])
return;
if (EXIT_PATH (head))
{
if (tmp->type == PLAYER)
{
if (INVOKE_OBJECT (TRIGGER, op, ARG_OBJECT (tmp)))
return;
tmp->enter_exit (head);
}
else
/* Currently only players can transfer maps */
return;
}
else if (EXIT_X (head) || EXIT_Y (head))
{
if (out_of_map (head->map, EXIT_X (head), EXIT_Y (head)))
{
LOG (llevError, "Removed illegal teleporter.\n");
head->destroy ();
return;
}
if (INVOKE_OBJECT (TRIGGER, op, ARG_OBJECT (tmp)))
return;
transfer_ob (tmp, EXIT_X (head), EXIT_Y (head), 0, head);
}
else
{
/* Random teleporter */
if (INVOKE_OBJECT (TRIGGER, op, ARG_OBJECT (tmp)))
return;
teleport (head, TELEPORTER, tmp);
}
}
/* This object will teleport someone to a different map
and will also apply changes to the player from its inventory.
This was invented for giving classes, but there's no reason it
can't be generalized.
*/
static void
move_player_changer (object *op)
{
if (!op->above || !EXIT_PATH (op))
return;
/* This isn't all that great - means that the player_mover
* needs to be on top.
*/
if (op->above->type == PLAYER)
{
object *player = op->above;
if (INVOKE_OBJECT (TRIGGER, op, ARG_OBJECT (player)))
return;
for (object *walk = op->inv; walk; walk = walk->below)
apply_changes_to_player (player, walk);
player->update_stats ();
esrv_send_inventory (op->above, op->above);
esrv_update_item (UPD_FACE, op->above, op->above);
/* update players death & WoR home-position */
if (*EXIT_PATH (op) == '/')
{
player->contr->savebed_map = EXIT_PATH (op);
player->contr->bed_x = EXIT_X (op);
player->contr->bed_y = EXIT_Y (op);
}
else
LOG (llevDebug, "WARNING: destination '%s' in player_changer must be an absolute path!\n", &EXIT_PATH (op));
op->above->enter_exit (op);
}
}
/* firewalls fire other spells.
* The direction of the wall is stored in op->stats.sp.
* walls can have hp, so they can be torn down.
*/
void
move_firewall (object *op)
{
object *spell;
if (!op->map)
return; /* dm has created a firewall in his inventory */
spell = op->inv;
if (!spell || spell->type != SPELL)
spell = op->other_arch;
if (!spell)
{
LOG (llevError, "move_firewall: no spell specified (%s, %s, %d, %d)\n", &op->name, &op->map->name, op->x, op->y);
return;
}
cast_spell (op, op, op->stats.sp ? op->stats.sp : rndm (1, 8), spell, NULL);
}
/* move_player_mover: this function takes a "player mover" as an
* argument, and performs the function of a player mover, which is:
*
* a player mover finds any players that are sitting on it. It
* moves them in the op->stats.sp direction. speed is how often it'll move.
* If attacktype is nonzero it will paralyze the player. If lifesave is set,
* it'll dissapear after hp+1 moves. If hp is set and attacktype is set,
* it'll paralyze the victim for hp*his speed/op->speed
*/
static void
move_player_mover (object *op)
{
int dir = 0;
for (object *victim = op->ms ().bot; victim; victim = victim->above)
{
if (victim->flag [FLAG_ALIVE]
&& !victim->flag [FLAG_WIZPASS]
&& (victim->move_type & op->move_type || !victim->move_type))
{
if (op->flag [FLAG_LIFESAVE] && op->stats.hp-- < 0)
{
op->destroy ();
return;
}
/* Determine direction only once so we do the right thing */
// why is it the right thing, though?
if (!dir)
dir = op->stats.sp ? op->stats.sp : rndm (1, 8);
sint16 nx = op->x + freearr_x[dir];
sint16 ny = op->y + freearr_y[dir];
maptile *m = op->map;
if (get_map_flags (m, &m, nx, ny, &nx, &ny) & P_OUT_OF_MAP)
{
LOG (llevError, "move_player_mover: Trying to push player off the map! map=%s (%d, %d)\n", &m->path, op->x, op->y);
return;
}
if (victim->head)
victim = victim->head;
if (should_director_abort (op, victim))
return;
for (object *nextmover = m->at (nx, ny).bot; nextmover; nextmover = nextmover->above)
{
if (nextmover->type == PLAYERMOVER)
nextmover->speed_left = -.99f;
if (nextmover->flag [FLAG_ALIVE])
op->speed_left = -1.1f; /* wait until the next thing gets out of the way */
}
if (victim->type == PLAYER)
{
/* only level >=1 movers move people */
if (op->level)
{
/* Following is a bit of hack. We need to make sure it
* is cleared, otherwise the player will get stuck in
* place. This can happen if the player used a spell to
* get to this space.
*/
victim->contr->fire_on = 0;
victim->speed_left = 1.f;
move_player (victim, dir);
}
else
return;
}
else
victim->move (dir);
if (!op->stats.maxsp && op->attacktype)
op->stats.maxsp = 2;
if (op->attacktype)
{ /* flag to paralyze the player */
victim->speed_left = max (-5.f, -op->stats.maxsp * victim->speed / op->speed);
}
}
}
}
/*
* Will duplicate a specified object placed on top of it.
* connected: what will trigger it.
* level: multiplier. 0 to destroy.
* other_arch: the object to look for and duplicate.
*/
void
move_duplicator (object *op)
{
object *tmp;
if (!op->other_arch)
{
LOG (llevInfo, "Duplicator with no other_arch! %d %d %s\n", op->x, op->y, op->map ? &op->map->path : "nullmap");
return;
}
if (op->above == NULL)
return;
for (tmp = op->above; tmp; tmp = tmp->above)
{
if (op->other_arch->archname == tmp->arch->archname)
{
if (op->level <= 0)
tmp->destroy ();
else
{
uint64 new_nrof = (uint64) tmp->nrof * op->level;
if (new_nrof >= 1UL << 31)
new_nrof = 1UL << 31;
tmp->nrof = new_nrof;
}
break;
}
}
}
/* move_creator (by peterm)
* it has the creator object create it's other_arch right on top of it.
* connected: what will trigger it
* hp: how many times it may create before stopping
* lifesave: if set, it'll never disappear but will go on creating
* everytime it's triggered
* other_arch: the object to create
* Note this can create large objects, however, in that case, it
* has to make sure that there is in fact space for the object.
* It should really do this for small objects also, but there is
* more concern with large objects, most notably a part being placed
* outside of the map which would cause the server to crash
*/
void
move_creator (object *creator)
{
object *new_ob;
if (!creator->flag [FLAG_LIFESAVE] && --creator->stats.hp < 0)
{
creator->stats.hp = -1;
return;
}
if (creator->inv)
{
object *ob;
int i;
object *ob_to_copy;
/* select random object from inventory to copy */
ob_to_copy = creator->inv;
for (ob = creator->inv->below, i = 1; ob; ob = ob->below, i++)
{
if (rndm (0, i) == 0)
{
ob_to_copy = ob;
}
}
new_ob = ob_to_copy->deep_clone ();
new_ob->clr_flag (FLAG_IS_A_TEMPLATE);
unflag_inv (new_ob, FLAG_IS_A_TEMPLATE);
}
else
{
if (!creator->other_arch)
{
LOG (llevError, "move_creator: Creator doesn't have other arch set: %s (%s, %d, %d)\n",
&creator->name, &creator->map->path, creator->x, creator->y);
return;
}
new_ob = object_create_arch (creator->other_arch);
fix_generated_item (new_ob, creator, 0, 0, GT_MINIMAL);
}
/* Make sure this multipart object fits */
if (new_ob->arch->more && new_ob->blocked (creator->map, creator->x, creator->y))
{
new_ob->destroy ();
return;
}
// for now lets try to identify everything generated here, it mostly
// happens automated, so this will at least fix many identify-experience holes
if (new_ob->need_identify ())
new_ob->set_flag (FLAG_IDENTIFIED);
insert_ob_in_map_at (new_ob, creator->map, creator, 0, creator->x, creator->y);
if (new_ob->flag [FLAG_FREED])
return;
if (creator->slaying)
new_ob->name = new_ob->title = creator->slaying;
}
/* move_marker --peterm@soda.csua.berkeley.edu
when moved, a marker will search for a player sitting above
it, and insert an invisible, weightless force into him
with a specific code as the slaying field.
At that time, it writes the contents of its own message
field to the player. The marker will decrement hp to
0 and then delete itself every time it grants a mark.
unless hp was zero to start with, in which case it is infinite.*/
void
move_marker (object *op)
{
if (object *tmp = op->ms ().player ())
{
/* remove an old force with a slaying field == op->name */
if (object *force = tmp->force_find (op->name))
force->destroy ();
if (op->slaying && !tmp->force_find (op->slaying))
{
tmp->force_add (op->slaying, op->stats.food);
if (op->msg)
new_draw_info (NDI_UNIQUE | NDI_NAVY, 0, tmp, op->msg);
if (op->stats.hp > 0)
{
op->stats.hp--;
if (op->stats.hp == 0)
{
/* marker expires--granted mark number limit */
op->destroy ();
return;
}
}
}
}
}
// mapscript objects activate themselves (only) then their timer fires
// TODO: maybe they should simply trigger the link like any other object?
static void
move_mapscript (object *op)
{
op->set_speed (0);
cfperl_mapscript_activate (op, true, op, 0);
}
static void
move_lamp (object *op)
{
// if the lamp/torch is off, we should disable it.
if (!op->glow_radius)
{
op->set_speed (0);
return;
}
else
{
// check whether the face might need to be updated
// (currently this is needed to have already switched on torches
// on maps, as they just set the glow_radius in the archetype)
if (op->other_arch
&& (
(op->flag [FLAG_ANIMATE] != op->other_arch->flag [FLAG_ANIMATE])
|| (op->flag [FLAG_ANIMATE]
? (op->animation_id != op->other_arch->animation_id)
: (op->face != op->other_arch->face))
))
get_animation_from_arch (op, op->other_arch);
}
// lamps and torches on maps don't use up their fuel
if (op->is_on_map ())
return;
if (op->stats.food > 0)
{
op->stats.food--;
return;
}
apply_lamp (op, false);
}
void
process_object (object *op)
{
if (expect_false (op->flag [FLAG_IS_A_TEMPLATE]))
return;
if (expect_false (INVOKE_OBJECT (TICK, op)))
return;
if (op->flag [FLAG_MONSTER])
if (move_monster (op) || op->flag [FLAG_FREED])
return;
if (op->flag [FLAG_ANIMATE] && op->anim_speed == 0)
{
animate_object (op, op->contr ? op->facing : op->direction);
if (op->flag [FLAG_SEE_ANYWHERE])
make_sure_seen (op);
}
if (expect_false (
op->flag [FLAG_GENERATOR]
|| op->flag [FLAG_CHANGING]
|| op->flag [FLAG_IS_USED_UP]
))
{
if (op->flag [FLAG_CHANGING] && !op->state)
{
change_object (op);
return;
}
if (op->flag [FLAG_GENERATOR] && !op->flag [FLAG_FRIENDLY])
generate_monster (op);
if (op->flag [FLAG_IS_USED_UP] && --op->stats.food <= 0)
{
if (op->flag [FLAG_APPLIED])
remove_force (op);
else
{
op->remove (); // TODO: really necessary?
if (op->flag [FLAG_SEE_ANYWHERE])
make_sure_not_seen (op);
op->drop_and_destroy ();
}
return;
}
}
switch (op->type)
{
case SPELL_EFFECT:
move_spell_effect (op);
break;
case ROD:
case HORN:
regenerate_rod (op);
break;
case FORCE:
case POTION_EFFECT:
remove_force (op);
break;
case BLINDNESS:
remove_blindness (op);
break;
case POISONING:
poison_more (op);
break;
case DISEASE:
move_disease (op);
break;
case SYMPTOM:
move_symptom (op);
break;
case THROWN_OBJ:
case ARROW:
move_arrow (op);
break;
case DOOR:
remove_door (op);
break;
case LOCKED_DOOR:
remove_door2 (op);
break;
case TELEPORTER:
move_teleporter (op);
break;
case GOLEM:
move_golem (op);
break;
case EARTHWALL:
hit_player (op, 2, op, AT_PHYSICAL, 1);
break;
case FIREWALL:
move_firewall (op);
if (op->stats.maxsp)
animate_turning (op);
break;
case MOOD_FLOOR:
do_mood_floor (op);
break;
case GATE:
move_gate (op);
break;
case TIMED_GATE:
move_timed_gate (op);
break;
case TRIGGER:
case TRIGGER_BUTTON:
case TRIGGER_PEDESTAL:
case TRIGGER_ALTAR:
animate_trigger (op);
break;
case DETECTOR:
move_detector (op);
case DIRECTOR:
if (op->stats.maxsp)
animate_turning (op);
break;
case HOLE:
move_hole (op);
break;
case DEEP_SWAMP:
move_deep_swamp (op);
break;
case RUNE:
case TRAP:
move_rune (op);
break;
case PLAYERMOVER:
move_player_mover (op);
break;
case CREATOR:
move_creator (op);
break;
case MARKER:
move_marker (op);
break;
case PLAYER_CHANGER:
move_player_changer (op);
break;
case PEACEMAKER:
move_peacemaker (op);
break;
case PLAYER:
// players have their own speed-management, so undo the --speed_left
++op->speed_left;
break;
case MAPSCRIPT:
move_mapscript (op);
break;
case LAMP:
case TORCH:
move_lamp (op);
break;
}
}