/* * This file is part of Deliantra, the Roguelike Realtime MMORPG. * * Copyright (©) 2005,2006,2007,2008 Marc Alexander Lehmann / Robin Redeker / the Deliantra team * Copyright (©) 2002,2007 Mark Wedel & Crossfire Development Team * Copyright (©) 1992,2007 Frank Tore Johansen * * Deliantra 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 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 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 = 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); } 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 = 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); } op->drop_and_destroy (); } 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 (QUERY_FLAG (gen, 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 (); CLEAR_FLAG (op, 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 = arch_to_object (gen->other_arch); } 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 (); } void remove_force (object *op) { if (--op->duration > 0) return; if (op->env) switch (op->subtype) { case FORCE_CONFUSION: CLEAR_FLAG (op->env, FLAG_CONFUSED); new_draw_info (NDI_UNIQUE, 0, op->env, "You regain your senses.\n"); default: CLEAR_FLAG (op, FLAG_APPLIED); change_abil (op->env, op); op->env->update_stats (); } op->destroy (); } void remove_blindness (object *op) { if (--op->stats.food > 0) return; CLEAR_FLAG (op, FLAG_APPLIED); if (op->env) { change_abil (op->env, op); op->env->update_stats (); } op->destroy (); } void poison_more (object *op) { if (op->env == NULL || !QUERY_FLAG (op->env, 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) { CLEAR_FLAG (op, 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); } 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->speed) op->value = 0; else op->set_speed (0); } if ((int) op->stats.wc < (NUM_ANIMATIONS (op) / 2 + 1)) { op->move_block = 0; CLEAR_FLAG (op, 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 (!QUERY_FLAG (tmp, FLAG_NO_PICK) || QUERY_FLAG (tmp, FLAG_CAN_ROLL) || QUERY_FLAG (tmp, FLAG_ALIVE)) break; if (!tmp) { if (op->arch->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 (QUERY_FLAG (tmp, 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 (!QUERY_FLAG (tmp, FLAG_ALIVE) && (!QUERY_FLAG (tmp, FLAG_NO_PICK) || QUERY_FLAG (tmp, 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 (!QUERY_FLAG (tmp, FLAG_NO_PICK) || QUERY_FLAG (tmp, FLAG_CAN_ROLL) || QUERY_FLAG (tmp, 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) SET_FLAG (op, 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 */ 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 */ 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); } } 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_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; SET_ANIMATION (op, op->stats.wc); update_object (op, UP_OBJ_FACE); if ((unsigned char) 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 (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 */ } 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 along its course. op is the arrow or thrown object. */ void move_arrow (object *op) { int was_reflected; if (!op->map) { LOG (llevError, "BUG: Arrow had no map.\n"); 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; } } /* if the arrow is moving too slow.. stop it. 0.5 was chosen as lower values look rediculous. */ if (op->speed < 0.5 && op->type == ARROW) { 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 (QUERY_FLAG (tmp, 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 (QUERY_FLAG (tmp, 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 (!QUERY_FLAG (op, 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 */ /* decrease the speed as it flies. 0.05 means a standard bow will shoot * about 17 squares. Tune as needed. */ op->speed -= 0.05; /* Move the arrow. */ op->move_to (pos); } void change_object (object *op) { /* Doesn`t handle linked objs yet */ int i, j; 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 (!QUERY_FLAG (op, 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 (i = 0; i < op->stats.food; i++) { object *tmp = arch_to_object (op->other_arch); tmp->stats.hp = op->stats.hp; /* The only variable it keeps. */ if (env) env->insert (tmp); else { 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 (!QUERY_FLAG (tmp, FLAG_IS_FLOOR)) break; /* If nothing above us to move, nothing to do */ if (!tmp || QUERY_FLAG (tmp, 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. */ void move_player_changer (object *op) { object *player; object *walk; 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) { if (INVOKE_OBJECT (TRIGGER, op, ARG_OBJECT (player))) return; player = op->above; for (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 */ void move_player_mover (object *op) { int dir = op->stats.sp; sint16 nx, ny; maptile *m; /* Determine direction now for random movers so we do the right thing */ if (!dir) dir = rndm (1, 8); for (object *victim = op->ms ().bot; victim; 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) { op->remove (); 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 (object *nextmover = m->at (nx, ny).bot; nextmover; nextmover = nextmover->above) { if (nextmover->type == PLAYERMOVER) nextmover->speed_left = -.99f; if (QUERY_FLAG (nextmover, 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 move_object (victim, 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, -FABS (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 (!QUERY_FLAG (creator, 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 != NULL; ob = ob->below, i++) { if (rndm (0, i) == 0) { ob_to_copy = ob; } } new_ob = ob_to_copy->deep_clone (); CLEAR_FLAG (new_ob, 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 SET_FLAG (new_ob, FLAG_IDENTIFIED); insert_ob_in_map_at (new_ob, creator->map, creator, 0, creator->x, creator->y); if (QUERY_FLAG (new_ob, 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 (!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? void move_mapscript (object *op) { op->set_speed (0); cfperl_mapscript_activate (op, true, op, 0); } 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 needs 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 auf 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 (QUERY_FLAG (op, FLAG_IS_A_TEMPLATE))) return; if (expect_false (INVOKE_OBJECT (TICK, op))) return; if (QUERY_FLAG (op, FLAG_MONSTER)) if (move_monster (op) || QUERY_FLAG (op, FLAG_FREED)) return; if (QUERY_FLAG (op, FLAG_ANIMATE) && op->anim_speed == 0) { animate_object (op, op->contr ? op->facing : op->direction); if (QUERY_FLAG (op, 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 (QUERY_FLAG (op, FLAG_CHANGING) && !op->state) { change_object (op); return; } 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 { op->remove (); // TODO: really necessary? if (QUERY_FLAG (op, 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; } }