/* * This file is part of Deliantra, the Roguelike Realtime MMORPG. * * Copyright (©) 2005,2006,2007,2008 Marc Alexander Lehmann / Robin Redeker / the Deliantra team * Copyright (©) 2001,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 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 */ #include #include #include #include #include #include // macro for this check, it had been inconsistent in this file. #define IS_FLOOR(x) ((x)->type == FLOOR || QUERY_FLAG((x), FLAG_IS_FLOOR)) /** * Check if objects on a square interfere with building */ static int can_build_over (maptile *map, object *tmp, int x, int y) { object *ob; ob = GET_MAP_OB (map, x, y); while (ob) { /* if ob is not a marking rune or floor, then check special cases */ if (ob->arch->archname != shstr_rune_mark && !IS_FLOOR (ob)) { switch (tmp->type) { case SIGN: case MAGIC_EAR: /* Allow signs and magic ears to be built on books */ if (ob->type != BOOK) return 0; break; case BUTTON: case DETECTOR: case PEDESTAL: case T_HANDLE: /* Allow buttons and levers to be built under gates */ if (ob->type != GATE && ob->type != DOOR) return 0; break; default: return 0; } } ob = ob->above; } return 1; } /** * Erases marking runes at specified location */ static void remove_marking_runes (maptile *map, int x, int y) { object *rune; object *next; rune = GET_MAP_OB (map, x, y); while (rune) { next = rune->above; if (rune->type == SIGN && rune->arch->archname == shstr_rune_mark) rune->destroy (); rune = next; } } /** * Returns an unused value for 'connected'. * \param map: map for which to find a value * \return 'connected' value with no item, or -1 if failure. * * Tries 1000 random values, then returns -1. */ static shstr_tmp find_unused_connected_value (maptile *map) { for (int i = 1000; --i; ) { char buf[64]; snprintf (buf, sizeof (buf), "built-%x", rndm (0xf0000000U) + 0x10000000U); shstr id (buf); if (!map->find_link (id)) return id; } return shstr_tmp (); } /** * Returns the marking rune on the square, for purposes of building connections */ static object * get_connection_rune (object *pl, int x, int y) { object *rune = GET_MAP_OB (pl->map, x, y); while (rune && (rune->type != SIGN || rune->arch->archname != shstr_rune_mark)) rune = rune->above; return rune; } /** * Helper function for door/button/connected item building. * * Will search the specified spot for a marking rune. * If not found, returns -1 * Else, searches a force in op's inventory matching the map's name * and the rune's text. * If found, returns the connection value associated * else searches a new connection value, and adds the force to the player. */ static shstr_tmp find_or_create_connection_for_map (object *pl, int x, int y, object *rune) { object *force; if (!rune) rune = get_connection_rune (pl, x, y); if (!rune) { new_draw_info (NDI_UNIQUE, 0, pl, "You need to put a marking rune with the group name."); return shstr_tmp (); } /* Now, find force in player's inventory */ force = pl->inv; while (force && ((force->type != FORCE) || !force->slaying || force->slaying != pl->map->path || !force->msg || force->msg != rune->msg)) force = force->below; if (!force) /* No force, need to create & insert one */ { /* Find unused value */ shstr_tmp id = find_unused_connected_value (pl->map); if (!id) { new_draw_info (NDI_UNIQUE, 0, pl, "Could not create more groups."); return shstr_tmp (); } force = get_archetype (FORCE_NAME); force->slaying = pl->map->path; force->msg = rune->msg; force->race = id; force->set_speed (0); insert_ob_in_ob (force, pl); return id; } /* Found the force, everything's easy. */ return force->race; } /** * Returns the book/scroll on the current square, for purposes of building */ static object * get_msg_book (object *pl, int x, int y) { object *book = GET_MAP_OB (pl->map, x, y); while (book && (book->type != BOOK)) book = book->above; return book; } /** * Make the built object inherit the msg of books that are used with it. * For objects already invisible (i.e. magic mouths & ears), also make it * it inherit the face and the name with "talking " prepended. */ static int adjust_sign_msg (object *pl, int x, int y, object *tmp) { object *book; char buf[MAX_BUF]; char buf2[MAX_BUF]; book = get_msg_book (pl, x, y); if (!book) { new_draw_info (NDI_UNIQUE, 0, pl, "You need to put a book or scroll with the message."); return -1; } tmp->msg = book->msg; if (tmp->invisible) { if (book->custom_name) snprintf (buf, sizeof (buf), "talking %s", &book->custom_name); else snprintf (buf, sizeof (buf), "talking %s", &book->name); tmp->name = buf; if (book->name_pl) { snprintf (buf2, sizeof (buf2), "talking %s", &book->name_pl); tmp->name_pl = buf2; } tmp->face = book->face; tmp->invisible = 0; } book->destroy (); return 0; } /** * Returns first item of type BUILDABLE_WALL. */ static object * get_wall (maptile *map, int x, int y) { object *wall = GET_MAP_OB (map, x, y); while (wall && (BUILDABLE_WALL != wall->type)) wall = wall->above; return wall; } /** * Fixes walls around specified spot * * \param map is the map * \param x * \param y are the position to fix * * Basically it ensures the correct wall is put where needed. * * Note: x & y must be valid map coordinates. */ static void fix_walls (maptile *map, int x, int y) { char archetype[MAX_BUF]; char *underscore; struct archetype *new_arch; /* First, find the wall on that spot */ object *wall = get_wall (map, x, y); if (!wall) /* Nothing -> bail out */ return; /* Find base name */ assign (archetype, wall->arch->archname); underscore = strchr (archetype, '_'); /* search for the first _ before a number */ while (underscore && !isdigit (*(underscore + 1))) underscore = strchr (underscore + 1, '_'); if (!underscore || !isdigit (*(underscore + 1))) /* Not in a format we can change, bail out */ return; underscore++; *underscore = '\0'; int connect = 0; if (x > 0 && get_wall (map, x - 1, y)) connect |= 1; if (x < map->width - 1 && get_wall (map, x + 1, y)) connect |= 2; if (y > 0 && get_wall (map, x, y - 1)) connect |= 4; if (y < map->height - 1 && get_wall (map, x, y + 1)) connect |= 8; // one bit per dir, 1 left, 2 right, 4 up, 8 down static const char *walltype[16] = { "0", "1_3", "1_4", "2_1_2", "1_2", "2_2_4", "2_2_1", "3_1", "1_1", "2_2_3", "2_2_2", "3_3", "2_1_1", "3_4", "3_2", "4" }; strcat (archetype, walltype [connect]); /* * Before anything, make sure the archetype does exist... * If not, prolly an error... */ new_arch = archetype::find (archetype); if (!new_arch) return; /* Now delete current wall, and insert new one * We save flags to avoid any trouble with buildable/non buildable, and so on */ object::flags_t old_flags = wall->flag; // elmex: this is where C++ pays off wall->destroy (); wall = new_arch->instance (); wall->type = BUILDABLE_WALL; insert_ob_in_map_at (wall, map, NULL, INS_ABOVE_FLOOR_ONLY, x, y); wall->flag = old_flags; } /** * \brief Floor building function * * Floors can be build: * - on existing floors, with or without a detector/button * - on an existing wall, with or without a floor under it * * Note: this function will inconditionally change squares around (x, y) * so don't call it with x == 0 for instance! */ static void apply_builder_floor (object *pl, object *material, int x, int y) { object *tmp, *above; object *above_floor; /* Item above floor, if any */ struct archetype *new_floor; struct archetype *new_wall; int i, xt, yt, floor_removed; char message[MAX_BUF]; sprintf (message, "You change the floor to better suit your tastes."); /* * Now the building part... * First, remove wall(s) and floor(s) at position x, y */ above_floor = NULL; new_wall = NULL; floor_removed = 0; tmp = GET_MAP_OB (pl->map, x, y); if (tmp) { while (tmp) { above = tmp->above; if (BUILDABLE_WALL == tmp->type) { /* There was a wall, remove it & keep its archetype to make new walls */ new_wall = tmp->arch; tmp->destroy (); sprintf (message, "You destroy the wall and redo the floor."); } else if (IS_FLOOR (tmp)) { tmp->destroy (); floor_removed = 1; } else { if (floor_removed) { /* This is the first item that was above the floor */ above_floor = tmp; floor_removed = 0; } } tmp = above; } } /* Now insert our floor */ new_floor = archetype::find (material->slaying); if (!new_floor) { /* Not found, log & bail out */ LOG (llevError, "apply_builder_floor: unable to find archetype %s.\n", &material->slaying); return; } tmp = new_floor->instance (); SET_FLAG (tmp, FLAG_IS_BUILDABLE); SET_FLAG (tmp, FLAG_UNIQUE); SET_FLAG (tmp, FLAG_IS_FLOOR); tmp->type = FLOOR; insert_ob_in_map_at (tmp, pl->map, above_floor, above_floor ? INS_BELOW_ORIGINATOR : INS_ON_TOP, x, y); /* * Next step: make sure there are either walls or floors around the new square * Since building, you can have: blocking view / floor / wall / nothing */ for (i = 1; i <= 8; i++) { xt = x + freearr_x[i]; yt = y + freearr_y[i]; tmp = GET_MAP_OB (pl->map, xt, yt); if (!tmp) { /* Must insert floor & wall */ tmp = new_floor->instance (); /* Better make the floor unique */ SET_FLAG (tmp, FLAG_UNIQUE); SET_FLAG (tmp, FLAG_IS_BUILDABLE); tmp->type = FLOOR; insert_ob_in_map_at (tmp, pl->map, 0, 0, xt, yt); /* Insert wall if exists. Note: if it doesn't, the map is weird... */ if (new_wall) { tmp = new_wall->instance (); SET_FLAG (tmp, FLAG_IS_BUILDABLE); tmp->type = BUILDABLE_WALL; insert_ob_in_map_at (tmp, pl->map, 0, 0, xt, yt); } } } /* Finally fixing walls to ensure nice continuous walls * Note: 2 squares around are checked, because potentially we added walls around the building * spot, so need to check that those new walls connect correctly */ for (xt = x - 2; xt <= x + 2; xt++) for (yt = y - 2; yt <= y + 2; yt++) if (!OUT_OF_REAL_MAP (pl->map, xt, yt)) fix_walls (pl->map, xt, yt); /* Now remove raw item from inventory */ material->decrease (); /* And tell player about the fix */ new_draw_info (NDI_UNIQUE, 0, pl, message); } /** * Wall building function * * Walls can be build: * - on a floor without anything else * - on an existing wall, with or without a floor */ static void apply_builder_wall (object *pl, object *material, int x, int y) { object *current_wall; object *tmp; int xt, yt; struct archetype *new_wall; char message[MAX_BUF]; remove_marking_runes (pl->map, x, y); /* Grab existing wall, if any */ current_wall = NULL; tmp = GET_MAP_OB (pl->map, x, y); while (tmp && !current_wall) { if (BUILDABLE_WALL == tmp->type) current_wall = tmp; tmp = tmp->above; } /* Find the raw wall in inventory */ sprintf (message, "You build a wall."); /* Now we can actually insert the wall */ new_wall = archetype::find (material->slaying); if (!new_wall) { LOG (llevError, "apply_builder_wall: unable to find archetype %s\n", &material->slaying); return; } tmp = new_wall->instance (); tmp->type = BUILDABLE_WALL; SET_FLAG (tmp, FLAG_IS_BUILDABLE); insert_ob_in_map_at (tmp, pl->map, 0, INS_ABOVE_FLOOR_ONLY, x, y); /* If existing wall, remove it, no need to fix other walls */ if (current_wall) { current_wall->destroy (); fix_walls (pl->map, x, y); sprintf (message, "You redecorate the wall to better suit your tastes."); } else { /* Else fix all walls around */ for (xt = x - 1; xt <= x + 1; xt++) for (yt = y - 1; yt <= y + 1; yt++) { if (OUT_OF_REAL_MAP (pl->map, xt, yt)) continue; fix_walls (pl->map, xt, yt); } } /* Now remove item from inventory */ material->decrease (); /* And tell player what happened */ new_draw_info (NDI_UNIQUE, 0, pl, message); } /** * Generic item builder. * * Item must be put on a square with a floor, you can have something under. * Archetype of created object is item->slaying (raw material). * Type of inserted item is tested for specific cases (doors & such). * Item is inserted above the floor, unless Str == 1 (only for detectors i guess) */ static void apply_builder_item (object *pl, object *item, int x, int y) { object *tmp; struct archetype *arch; int insert_flag; object *floor; object *con_rune; /* Find floor */ floor = GET_MAP_OB (pl->map, x, y); if (!floor) { new_draw_info (NDI_UNIQUE, 0, pl, "Invalid square."); return; } while (floor && !IS_FLOOR (floor)) floor = floor->above; if (!floor) { new_draw_info (NDI_UNIQUE, 0, pl, "This square has no floor, you can't build here."); return; } /* Create item, set flag, insert in map */ arch = archetype::find (item->slaying); if (!arch) return; tmp = arch->instance (); if (!floor->flag[FLAG_IS_BUILDABLE] || floor->above && !can_build_over (pl->map, tmp, x, y)) /* Floor has something on top that interferes with building */ { new_draw_info (NDI_UNIQUE, 0, pl, "You can't build here."); return; } SET_FLAG (tmp, FLAG_IS_BUILDABLE); SET_FLAG (tmp, FLAG_NO_PICK); /* * Str 1 is a flag that the item [pedestal] should go below the floor. * Items under the floor on non-unique maps will not be saved, * so make the item itself unique in this situation. */ insert_flag = item->stats.Str == 1 ? INS_BELOW_ORIGINATOR : INS_ABOVE_FLOOR_ONLY; if (insert_flag == INS_BELOW_ORIGINATOR && !pl->map->no_reset) SET_FLAG (tmp, FLAG_UNIQUE); shstr_tmp connected; switch (tmp->type) { case DOOR: case GATE: case BUTTON: case DETECTOR: case TIMED_GATE: case PEDESTAL: case T_HANDLE: case MAGIC_EAR: case SIGN: /* Signs don't need a connection, but but magic mouths do. */ if (tmp->type == SIGN && tmp->arch->archname != shstr_magic_mouth) break; con_rune = get_connection_rune (pl, x, y); connected = find_or_create_connection_for_map (pl, x, y, con_rune); if (!connected) { /* Player already informed of failure by the previous function */ tmp->destroy (); return; } /* Remove marking rune */ con_rune->destroy (); } /* For magic mouths/ears, and signs, take the msg from a book of scroll */ if ((tmp->type == SIGN) || (tmp->type == MAGIC_EAR)) if (adjust_sign_msg (pl, x, y, tmp) == -1) { tmp->destroy (); return; } insert_ob_in_map_at (tmp, pl->map, floor, insert_flag, x, y); if (connected) tmp->add_link (pl->map, connected); new_draw_info_format (NDI_UNIQUE, 0, pl, "You build the %s", query_name (tmp)); item->decrease (); } /** * Item remover. * * Removes first buildable item, either under or above the floor */ static void apply_builder_remove (object *pl, int dir) { object *item; int x, y; x = pl->x + freearr_x[dir]; y = pl->y + freearr_y[dir]; /* Check square */ item = GET_MAP_OB (pl->map, x, y); if (!item) { /* Should not happen with previous tests, but we never know */ new_draw_info (NDI_UNIQUE, 0, pl, "Invalid square."); LOG (llevError, "apply_builder_remove: (null) square at (%d, %d, %s)\n", x, y, &pl->map->path); return; } if (IS_FLOOR (item)) item = item->above; if (!item) new_draw_info (NDI_UNIQUE, 0, pl, "Nothing to remove."); else if (item->type == BUILDABLE_WALL) new_draw_info (NDI_UNIQUE, 0, pl, "Can't remove a wall with that, build a floor."); else if (!item->flag [FLAG_IS_BUILDABLE]) new_draw_info_format (NDI_UNIQUE, 0, pl, "You can't remove the %s, it's not buildable!", query_name (item)); else { new_draw_info_format (NDI_UNIQUE, 0, pl, "You remove the %s", query_name (item)); item->destroy (); } } /** * Global building function * * This is the general map building function. Called when the player 'fires' a builder * or remover object. */ void apply_map_builder (object *pl, int dir) { object *builder; object *tmp; object *tmp2; int x, y; if (!pl->type == PLAYER) return; /*if ( !player->map->unique ) { new_draw_info( NDI_UNIQUE, 0, player, "You can't build outside a unique map." ); return; } */ if (dir == 0) { new_draw_info (NDI_UNIQUE, 0, pl, "You can't build or destroy under yourself."); return; } x = pl->x + freearr_x[dir]; y = pl->y + freearr_y[dir]; if ((1 > x) || (1 > y) || ((pl->map->width - 2) < x) || ((pl->map->height - 2) < y)) { new_draw_info (NDI_UNIQUE, 0, pl, "Can't build on map edge..."); return; } /* * Check specified square * The square must have only buildable items * Exception: marking runes are all right, * since they are used for special things like connecting doors / buttons */ tmp = GET_MAP_OB (pl->map, x, y); if (!tmp) { /* Nothing, meaning player is standing next to an undefined square... */ LOG (llevError, "apply_map_builder: undefined square at (%d, %d, %s)\n", x, y, &pl->map->path); new_draw_info (NDI_UNIQUE, 0, pl, "You'd better not build here, it looks weird."); return; } tmp2 = find_marked_object (pl); while (tmp) { if (!QUERY_FLAG (tmp, FLAG_IS_BUILDABLE) && (tmp->type != SIGN || tmp->arch->archname != shstr_rune_mark)) { /* The item building function already has it's own special * checks for this */ if (!tmp2 || tmp2->subtype != ST_MAT_ITEM) { new_draw_info (NDI_UNIQUE, 0, pl, "You can't build here."); return; } } tmp = tmp->above; } /* Now we know the square is ok */ builder = pl->contr->ranged_ob; if (builder->subtype == ST_BD_REMOVE) /* Remover -> call specific function and bail out */ { apply_builder_remove (pl, dir); return; } if (builder->subtype == ST_BD_BUILD) /* * Builder. * Find marked item to build, call specific function */ { tmp = tmp2; if (!tmp) { new_draw_info (NDI_UNIQUE, 0, pl, "You need to mark raw materials to use."); return; } if (tmp->type != MATERIAL) { new_draw_info (NDI_UNIQUE, 0, pl, "You can't use the marked item to build."); return; } switch (tmp->subtype) { case ST_MAT_FLOOR: apply_builder_floor (pl, tmp, x, y); return; case ST_MAT_WALL: apply_builder_wall (pl, tmp, x, y); return; case ST_MAT_ITEM: apply_builder_item (pl, tmp, x, y); return; default: new_draw_info (NDI_UNIQUE, 0, pl, "Don't know how to apply this material, sorry."); LOG (llevError, "apply_map_builder: invalid material subtype %d\n", tmp->subtype); return; } } /* Here, it means the builder has an invalid type */ new_draw_info (NDI_UNIQUE, 0, pl, "Don't know how to apply this tool, sorry."); LOG (llevError, "apply_map_builder: invalid builder subtype %d\n", builder->subtype); }