server-1.12/server/time.c

779 lines
22 KiB
C

/*
* static char *rcsid_time_c =
* "$Id: time.c 11578 2009-02-23 22:02:27Z lalo $";
*/
/*
CrossFire, A Multiplayer game for X-windows
Copyright (C) 2006 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
* Routines that is executed from objects based on their speed have been
* collected in this file.
*/
#include <global.h>
#include <spells.h>
#ifndef __CEXTRACT__
#include <sproto.h>
#endif
/**
* Remove non locked 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.)
*
* @sa remove_locked_door().
*
* @param op
* door to remove.
*/
void remove_door(object *op) {
int i;
object *tmp;
for (i = 1; i < 9; i += 2)
if ((tmp = present(DOOR, op->map, op->x+freearr_x[i], op->y+freearr_y[i])) != NULL) {
tmp->speed = 0.1;
update_ob_speed(tmp);
tmp->speed_left = -0.2;
}
if (op->other_arch) {
tmp = arch_to_object(op->other_arch);
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);
}
remove_ob(op);
free_object(op);
}
/**
* Same as remove_door() but for locked doors.
*
* @param op
* door to remove.
*/
void remove_locked_door(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->speed = 0.1;
update_ob_speed(tmp);
tmp->speed_left = -0.2;
}
}
if (op->other_arch) {
tmp = arch_to_object(op->other_arch);
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);
}
remove_ob(op);
free_object(op);
}
/**
* Will generate a monster according to parameters of generator.
*
* What is generated should be in the generator's inventory.
*
* See generate_monster() for the main generator function.
*
* @param gen
* generator.
*/
static void generate_monster_inv(object *gen) {
int i;
int nx, ny;
object *op, *head = NULL;
const char *code;
int qty = 0;
/* Code below assumes the generator is on a map, as it tries
* to place the monster on the map. So if the generator
* isn't on a map, complain and exit.
*/
if (gen->map == NULL) {
LOG(llevError,"Generator (%s) not on a map?\n", gen->name);
return;
}
/*First count number of objects in inv*/
for (op = gen->inv; op; op = op->below)
qty++;
if (!qty) {
LOG(llevError,"Generator (%s) has no inventory in generate_monster_inv?\n", gen->name);
return;/*No inventory*/
}
qty=rndm(0,qty-1);
for (op=gen->inv;qty;qty--)
op=op->below;
i=find_multi_free_spot_within_radius(op, gen, &nx, &ny);
if (i==-1)
return;
head=object_create_clone(op);
CLEAR_FLAG(head, FLAG_IS_A_TEMPLATE);
unflag_inv(head, FLAG_IS_A_TEMPLATE);
if (rndm(0, 9))
generate_artifact(head, gen->map->difficulty);
code = get_ob_key_value(gen, "generator_code");
if (code) {
set_ob_key_value(head, "generator_code", code, 1);
}
insert_ob_in_map_at(head,gen->map,gen,0,nx,ny);
if (QUERY_FLAG(head, FLAG_FREED)) return;
fix_multipart_object(head);
if (HAS_RANDOM_ITEMS(head))
create_treasure(head->randomitems, head, GT_APPLY,
gen->map->difficulty, 0);
}
/**
* Generate a monster from the other_arch field.
*
* See generate_monster() for the main generator function.
*
* @param gen
* generator.
*/
static void generate_monster_arch(object *gen) {
int i;
int nx, ny;
object *op, *head = NULL, *prev = NULL;
archetype *at = gen->other_arch;
const char *code;
if(gen->other_arch == NULL) {
LOG(llevError, "Generator without other_arch: %s\n", gen->name);
return;
}
/* Code below assumes the generator is on a map, as it tries
* to place the monster on the map. So if the generator
* isn't on a map, complain and exit.
*/
if (gen->map == NULL) {
LOG(llevError,"Generator (%s) not on a map?\n", gen->name);
return;
}
i = find_multi_free_spot_within_radius(&at->clone, gen, &nx, &ny);
if (i == -1) return;
while (at != NULL) {
op = arch_to_object(at);
op->x = nx + at->clone.x;
op->y = ny + at->clone.y;
if (head != NULL)
op->head = head, prev->more=op;
if (rndm(0, 9)) generate_artifact(op, gen->map->difficulty);
code = get_ob_key_value(gen, "generator_code");
if (code) {
set_ob_key_value(head, "generator_code", code, 1);
}
insert_ob_in_map(op, gen->map, gen,0);
if (QUERY_FLAG(op, FLAG_FREED)) return;
if(HAS_RANDOM_ITEMS(op))
create_treasure(op->randomitems, op, GT_APPLY,
gen->map->difficulty, 0);
if(head == NULL)
head = op;
prev = op;
at = at->more;
}
}
/**
* Main generator function. Will generate a monster based on the parameters.
*
* @param gen
* generator.
*/
static void generate_monster(object *gen) {
sint8 children;
sint8 max_children;
sint8 x, y;
object *tmp;
const char *code, *value;
if (GENERATE_SPEED(gen)&&rndm(0, GENERATE_SPEED(gen)-1))
return;
value = get_ob_key_value(gen, "generator_max_map");
if (value) {
max_children = (sint8)strtol(value, NULL, 10);
if (max_children < 1)
return;
code = get_ob_key_value(gen, "generator_code");
if (code) {
/* Generator has a limit and has created some,
* so count how many already exist
*/
children = 0;
for (x = 0; x < MAP_WIDTH(gen->map); x++) {
for (y = 0; y < MAP_HEIGHT(gen->map); y++) {
for (tmp = GET_MAP_OB(gen->map, x, y); tmp != NULL; tmp = tmp->above) {
value = get_ob_key_value(tmp, "generator_code");
if (value && value == code) {
children++;
}
}
}
}
/* and return without generating if there are already enough */
if (children >= max_children+1)
return;
} else {
/* Generator has a limit, but hasn't created anything yet,
* so no need to count, just set code and go
*/
value = get_ob_key_value(gen, "generator_name");
if (value) {
set_ob_key_value(gen, "generator_code", value, 1);
} else if (gen->name) {
set_ob_key_value(gen, "generator_code", gen->name, 1);
} else {
set_ob_key_value(gen, "generator_code", "generator", 1);
}
}
} /* If this has a max map generator limit */
if (QUERY_FLAG(gen, FLAG_CONTENT_ON_GEN))
generate_monster_inv(gen);
else
generate_monster_arch(gen);
}
/**
* Move for ::FORCE objects.
*
* @param op
* force to test.
* @todo rename to move_force?
*/
static void remove_force(object *op) {
if (--op->duration > 0) {
check_spell_expiry(op);
return;
}
switch (op->subtype) {
case FORCE_CONFUSION:
if (op->env != NULL) {
CLEAR_FLAG(op->env, FLAG_CONFUSED);
draw_ext_info(NDI_UNIQUE, 0, op->env,
MSG_TYPE_ATTRIBUTE, MSG_TYPE_ATTRIBUTE_BAD_EFFECT_END,
"You regain your senses.", NULL);
}
break;
case FORCE_TRANSFORMED_ITEM:
/* The force is into the item that was created */
if (op->env != NULL && op->inv != NULL) {
object *inv = op->inv;
object *pl = get_player_container(op);
remove_ob(inv);
inv->weight = (inv->nrof ? (sint32)(op->env->weight/inv->nrof) : op->env->weight);
if (op->env->env) {
insert_ob_in_ob(inv, op->env->env);
if (pl) {
char name[HUGE_BUF];
query_short_name(inv, name, HUGE_BUF);
draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_ITEM, MSG_TYPE_ITEM_CHANGE,
"Your %s recovers its original form.",
"Your %s recovers its original form.",
name);
}
} else {
/* Object on map */
inv->x = op->env->x;
inv->y = op->env->y;
insert_ob_in_map(inv, op->env->map, NULL, 0);
}
inv = op->env;
remove_ob(op);
free_object(op);
remove_ob(inv);
}
return;
default:
break;
}
if (op->env != NULL) {
CLEAR_FLAG(op, FLAG_APPLIED);
change_abil(op->env, op);
fix_object(op->env);
}
remove_ob(op);
free_object(op);
}
/**
* Animate a ::TRIGGER.
*
* @param op
* trigger.
*/
static 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);
}
}
/**
* Move a ::HOLE.
*
* @param op
* hole to move.
*/
static void move_hole(object *op) { /* 1 = opening, 0 = closing */
object *next, *tmp;
if (op->value) { /* We're opening */
if (--op->stats.wc <= 0) { /* Opened, let's stop */
op->stats.wc = 0;
op->speed = 0;
update_ob_speed(op);
/* Hard coding this makes sense for holes I suppose */
op->move_on = MOVE_WALK;
for (tmp = op->above; tmp != NULL; tmp = next) {
next = tmp->above;
ob_move_on(op, tmp, tmp);
}
}
op->state = op->stats.wc;
animate_object(op, 0);
update_object(op, UP_OBJ_FACE);
return;
}
/* We're closing */
op->move_on = 0;
op->stats.wc++;
if ((int)op->stats.wc >= NUM_ANIMATIONS(op))
op->stats.wc = NUM_ANIMATIONS(op)-1;
op->state = op->stats.wc;
animate_object(op, 0);
update_object(op, UP_OBJ_FACE);
if ((unsigned char)op->stats.wc == (NUM_ANIMATIONS(op)-1)) {
op->speed = 0;
update_ob_speed(op); /* closed, let's stop */
return;
}
}
/**
* An item (::ARROW or such) stops moving.
*
* 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.
*
* @param op
* object to check.
* @return
* pointer to stopped object, NULL if destroyed or can't be stopped.
*/
object *stop_item(object *op) {
if (free_no_drop(op))
return NULL;
if (op->map == NULL)
return op;
switch (op->type) {
case THROWN_OBJ: {
object *payload = op->inv;
if (payload == NULL)
return NULL;
remove_ob(payload);
remove_ob(op);
free_object(op);
return payload;
}
case ARROW:
if (op->speed >= MIN_ACTIVE_SPEED)
op = fix_stopped_arrow(op);
return op;
default:
return op;
}
}
/**
* 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.
*
* @param op
* object to stop.
* @param map
* must be the value of op->map before stop_item() was called.
* @param originator
* what caused op to be stopped.
*/
void fix_stopped_item(object *op, mapstruct *map, object *originator) {
if (map == NULL)
return;
if (QUERY_FLAG(op, 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 */
}
/**
* An ::ARROW stops moving.
*
* @param op
* arrow stopping.
* @return
* arrow, or NULL if it was broken.
*/
object *fix_stopped_arrow(object *op) {
if (free_no_drop(op))
return NULL;
if (rndm(0, 99) < op->stats.food) {
/* Small chance of breaking */
remove_ob(op);
free_object(op);
return NULL;
}
op->direction = 0;
op->move_on = 0;
op->move_type = 0;
op->speed = 0;
update_ob_speed(op);
op->stats.wc = op->stats.sp;
op->stats.dam = op->stats.hp;
op->attacktype = op->stats.grace;
if (op->slaying != NULL)
FREE_AND_CLEAR_STR(op->slaying);
if (op->skill != NULL)
FREE_AND_CLEAR_STR(op->skill);
if (op->spellarg != NULL) {
op->slaying = add_string(op->spellarg);
free(op->spellarg);
op->spellarg = NULL;
} else
op->slaying = NULL;
/* Reset these to zero, so that can_merge will work properly */
op->spellarg = NULL;
op->stats.sp = 0;
op->stats.hp = 0;
op->stats.grace = 0;
op->level = 0;
op->face = op->arch->clone.face;
op->owner = NULL; /* So that stopped arrows will be saved */
update_object(op, UP_OBJ_FACE);
return op;
}
/**
* Check whether the given object is FLAG_NO_DROP. If so, (optionally) remove
* and free it.
*
* @param op
* the object to check
* @return
* whether the object was freed
*/
int free_no_drop(object *op) {
if (!QUERY_FLAG(op, FLAG_NO_DROP)) {
return 0;
}
if (!QUERY_FLAG(op, FLAG_REMOVED)) {
remove_ob(op);
}
free_object2(op, 1);
return 1;
}
/**
* Replaces op with its other_arch if it has reached its end of life.
*
* This routine doesnt seem to work for "inanimate" objects that
* are being carried, ie a held torch leaps from your hands!.
* Modified this routine to allow held objects. b.t.
*
* @param op
* object to change. Will be removed and replaced.
*/
static void change_object(object *op) { /* Doesn`t handle linked objs yet */
object *tmp, *env;
int i, j;
if (op->other_arch == NULL) {
LOG(llevError, "Change object (%s) without other_arch error.\n", op->name);
return;
}
/* In non-living items only change when food value is 0 */
if (!QUERY_FLAG(op, FLAG_ALIVE)) {
if (op->stats.food-- > 0)
return;
else
op->stats.food = 1; /* so 1 other_arch is made */
}
env = op->env;
remove_ob(op);
for (i = 0; i < NROFNEWOBJS(op); i++) {
tmp = arch_to_object(op->other_arch);
if (op->type == LAMP)
tmp->stats.food = op->stats.food-1;
tmp->stats.hp = op->stats.hp; /* The only variable it keeps. */
if (env) {
tmp->x = env->x,
tmp->y = env->y;
tmp = insert_ob_in_ob(tmp, env);
} else {
j = find_first_free_spot(tmp, op->map, op->x, op->y);
if (j == -1) /* No free spot */
free_object(tmp);
else {
tmp->x = op->x+freearr_x[j],
tmp->y = op->y+freearr_y[j];
insert_ob_in_map(tmp, op->map, op, 0);
}
}
}
free_object(op);
}
/**
* Move for ::FIREWALL.
*
* 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.
*
* @param op
* firewall.
*/
void move_firewall(object *op) {
object *spell;
if (!op->map)
return; /* dm has created a firewall in his inventory */
spell = op->inv;
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);
}
/**
* This function takes a ::PLAYERMOVER 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
*
* @param op
* mover.
*/
void move_player_mover(object *op) {
object *victim, *nextmover;
int dir = op->stats.sp;
sint16 nx, ny;
mapstruct *m;
/* Determine direction now for random movers so we do the right thing */
if (!dir)
dir = rndm(1, 8);
for (victim = GET_MAP_OB(op->map, op->x, op->y); victim != NULL; victim = victim->above) {
if (QUERY_FLAG(victim, FLAG_ALIVE)
&& !QUERY_FLAG(victim, FLAG_WIZPASS)
&& (victim->move_type&op->move_type || !victim->move_type)) {
if (victim->head)
victim = victim->head;
if (QUERY_FLAG(op, FLAG_LIFESAVE)&&op->stats.hp-- < 0) {
remove_ob(op);
free_object(op);
return;
}
nx = op->x+freearr_x[dir];
ny = op->y+freearr_y[dir];
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 (should_director_abort(op, victim))
return;
for (nextmover = GET_MAP_OB(m, nx, ny); nextmover != NULL; nextmover = nextmover->above) {
if (nextmover->type == PLAYERMOVER)
nextmover->speed_left = -.99;
if (QUERY_FLAG(nextmover, FLAG_ALIVE)) {
op->speed_left = -1.1; /* 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 = -FABS(victim->speed);
move_player(victim, dir);
} else
return;
} else
move_object(victim, dir);
if (!op->stats.maxsp && op->attacktype)
op->stats.maxsp = 2.0;
if (op->attacktype) { /* flag to paralyze the player */
victim->speed_left = -FABS(op->stats.maxsp*victim->speed/op->speed);
/* Not sure why, but for some chars on metalforge, they
* would sometimes get -inf speed_left, and from the
* description, it could only happen here, so just put
* a lower sanity limit. My only guess is that the
* mover has 0 speed.
*/
if (victim->speed_left < -5.0)
victim->speed_left = -5.0;
}
}
}
}
/**
* Main object move function.
*
* @param op
* object to move.
* @return
* 0 if object didn't move, 1 else?
* @todo remove unused return value?
*/
int process_object(object *op) {
if (QUERY_FLAG(op, FLAG_IS_A_TEMPLATE))
return 0;
/* Lauwenmark: Handle for plugin time event */
if (execute_event(op, EVENT_TIME, NULL, NULL, NULL, SCRIPT_FIX_NOTHING) != 0)
return 0;
if (QUERY_FLAG(op, FLAG_MONSTER))
if (move_monster(op) || QUERY_FLAG(op, FLAG_FREED))
return 1;
if ((QUERY_FLAG(op, FLAG_ANIMATE) && op->anim_speed == 0)
|| (op->temp_animation_id && op->temp_anim_speed == 0)) {
op->state++;
if (op->type == PLAYER)
animate_object(op, op->facing);
else
animate_object(op, op->direction);
if (QUERY_FLAG(op, FLAG_SEE_ANYWHERE))
make_sure_seen(op);
}
if (QUERY_FLAG(op, FLAG_CHANGING) && !op->state) {
change_object(op);
return 1;
}
if (QUERY_FLAG(op, FLAG_GENERATOR) && !QUERY_FLAG(op, FLAG_FRIENDLY))
generate_monster(op);
if (QUERY_FLAG(op, FLAG_IS_USED_UP) && --op->stats.food <= 0) {
if (QUERY_FLAG(op, FLAG_APPLIED))
remove_force(op);
else {
remove_ob(op);
if (QUERY_FLAG(op, FLAG_SEE_ANYWHERE))
make_sure_not_seen(op);
free_object(op);
}
return 1;
}
return (ob_process(op) == METHOD_OK ? 1 : 0);
}
void legacy_remove_force(object *op) {
remove_force(op);
}
void legacy_animate_trigger(object *op) {
animate_trigger(op);
}
void legacy_move_hole(object *op) {
move_hole(op);
}