/* * static char *rcsid_build_map = * "$Id: build_map.c 11578 2009-02-23 22:02:27Z lalo $"; */ /* CrossFire, A Multiplayer game for X-windows Copyright (C) 2001-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 to crossfire-devel@real-time.com */ /** * @file * This file handles all ingame construction functions: builders, destroyers, building materials. * * Basically, those enable a player to alter in real-time a map. * * @todo document building, forces used to store connection values, ... */ #include #include #include #include #include #include /** * Check if objects on a square interfere with building. * * @param map * map we're building on. * @param new_item * item the player is trying to build. * @param x * @param y * coordinates where to build. * @return * 0 if tmp can't be built on the spot, 1 if it can be built. */ static int can_build_over(struct mapdef *map, object *new_item, short x, short y) { object *ob; for (ob = GET_MAP_OB(map, x, y); ob; ob = ob->above) { if (strcmp(ob->arch->name, "rune_mark") == 0) /* you can always build on marking runes, used for connected building things. */ continue; if ((ob->head && QUERY_FLAG(ob->head, FLAG_IS_BUILDABLE)) || (!ob->head && QUERY_FLAG(ob, FLAG_IS_BUILDABLE))) /* Check for the flag is required, as this function * can be called recursively on different spots. */ continue; switch (new_item->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 CF_HANDLE: /* Allow buttons and levers to be built under gates */ if (ob->type != GATE && ob->type != DOOR) return 0; break; default: return 0; } } /* If item being built is multi-tile, need to check other parts too. */ if (new_item->more) return can_build_over(map, new_item->more, x+new_item->more->arch->clone.x-new_item->arch->clone.x, y+new_item->more->arch->clone.y-new_item->arch->clone.y); return 1; } /** * Returns the marking rune on the square, for purposes of building connections. * * @param pl * player trying to build. * @param x * @param y * coordinates to search. * @return * marking rune, NULL if none found. */ static object *get_connection_rune(object *pl, short x, short y) { object *rune; rune = GET_MAP_OB(pl->map, x, y); while (rune && ((rune->type != SIGN) || (strcmp(rune->arch->name, "rune_mark")))) rune = rune->above; return rune; } /** * Returns the book/scroll on the current square, for purposes of building * * @param pl * player trying to build. * @param x * @param y * coordinates to search. * @return * book, NULL if none found. */ static object *get_msg_book(object *pl, short x, short y) { object *book; book = GET_MAP_OB(pl->map, x, y); while (book && (book->type != BOOK)) book = book->above; return book; } /** * Returns first item of type WALL. * * @param map * @param x * @param y * where to search. * @return * wall, or NULL if none found. * @todo isn't there a similar function somewhere? put this in a common library? * investigate possible merge with retrofit_joined_wall() used for random maps */ static object *get_wall(struct mapdef *map, int x, int y) { object *wall; wall = GET_MAP_OB(map, x, y); while (wall && (wall->type != WALL)) wall = wall->above; return wall; } /** * Erases all marking runes at specified location (before building a wall) * * @param map * @param x * @param y * coordinates to erase runes at. */ static void remove_marking_runes(struct mapdef *map, short x, short y) { object *rune; object *next; rune = GET_MAP_OB(map, x, y); while (rune) { next = rune->above; if ((rune->type == SIGN) && (!strcmp(rune->arch->name, "rune_mark"))) { remove_ob(rune); free_object(rune); } rune = next; } } /** * 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. * * The book is removed during the operation. * * @param pl * player building. * @param x * @param y * building coordinates. * @param tmp * object that is being built. * @return * -1 if no text found, 0 if ok to build. */ static int adjust_sign_msg(object *pl, short x, short y, object *tmp) { object *book; char buf[MAX_BUF]; char buf2[MAX_BUF]; book = get_msg_book(pl, x, y); if (!book) { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "You need to put a book or scroll with the message.", NULL); return -1; } tmp->msg = book->msg; add_refcount(tmp->msg); if (tmp->invisible) { if (book->custom_name != NULL) { snprintf(buf, sizeof(buf), "talking %s", book->custom_name); } else { snprintf(buf, sizeof(buf), "talking %s", book->name); } if (tmp->name) free_string(tmp->name); tmp->name = add_string(buf); if (book->name_pl != NULL) { snprintf(buf2, sizeof(buf2), "talking %s", book->name_pl); if (tmp->name_pl) free_string(tmp->name_pl); tmp->name_pl = add_string(buf2); } tmp->face = book->face; tmp->invisible = 0; } remove_ob(book); free_object(book); return 0; } /** * Returns an unused value for 'connected'. * * Tries 1000 random values, then returns -1. * * @param map * map for which to find a value * @return * 'connected' value with no item, or -1 if failure. */ static int find_unused_connected_value(struct mapdef *map) { int connected = 0; int itest = 0; oblinkpt *obp; while (itest++ < 1000) { connected = 1+rand()%20000; for (obp = map->buttons; obp && (obp->value != connected); obp = obp->next) ; if (!obp) return connected; } return -1; } /** * 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. * * @param pl * player building. * @param x * @param y * coordinates where to build. * @param rune * rune used to indicate the connection value. If NULL, building is searched for one. * @return * -1 for failure, else connection value. */ static int find_or_create_connection_for_map(object *pl, short x, short y, object *rune) { object *force; int connected; if (!rune) rune = get_connection_rune(pl, x, y); if (!rune || !rune->msg) { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "You need to put a marking rune with the group name.", NULL); return -1; } /* Now, find force in player's inventory */ force = pl->inv; while (force && ((force->type != FORCE) || (!force->slaying) || (strcmp(force->slaying, pl->map->path)) || (!force->msg) || (strcmp(force->msg, rune->msg)))) force = force->below; if (!force) { /* No force, need to create & insert one */ /* Find unused value */ connected = find_unused_connected_value(pl->map); if (connected == -1) { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "Could not create more groups.", NULL); return -1; } force = create_archetype(FORCE_NAME); force->speed = 0; update_ob_speed(force); force->slaying = add_string(pl->map->path); force->msg = add_string(rune->msg); force->path_attuned = connected; insert_ob_in_ob(force, pl); return connected; } /* Found the force, everything's easy. */ return force->path_attuned; } /** * Fixes walls around specified spot * * Basically it ensures the correct wall is put where needed. * * @note * x & y must be valid map coordinates. * * @param map * @param x * @param y * position to fix. * @todo * investigate possible merge with retrofit_joined_wall() used for random maps */ static void fix_walls(struct mapdef *map, int x, int y) { int connect; object *wall; char archetype[MAX_BUF]; char *underscore; uint32 old_flags[4]; struct archt *new_arch; int flag; int len; int has_window; /* First, find the wall on that spot */ wall = get_wall(map, x, y); if (!wall) /* Nothing -> bail out */ return; /* Find base name */ strncpy(archetype, wall->arch->name, sizeof(archetype)); archetype[sizeof(archetype)-1] = '\0'; underscore = strchr(archetype, '_'); if (!underscore) /* Not in a format we can change, bail out */ return; has_window = 0; if (!strcmp(underscore+1, "win1")) has_window = 1; else if (!strcmp(underscore+1, "win2")) has_window = 1; else if (!isdigit(*(underscore+1))) return; underscore++; *underscore = '\0'; len = sizeof(archetype)-strlen(archetype)-2; connect = 0; if ((x > 0) && get_wall(map, x-1, y)) connect |= 1; if ((x < MAP_WIDTH(map)-1) && get_wall(map, x+1, y)) connect |= 2; if ((y > 0) && get_wall(map, x, y-1)) connect |= 4; if ((y < MAP_HEIGHT(map)-1) && get_wall(map, x, y+1)) connect |= 8; switch (connect) { case 0: strncat(archetype, "0", len); break; case 1: strncat(archetype, "1_3", len); break; case 2: strncat(archetype, "1_4", len); break; case 3: if (has_window) { strncat(archetype, "win2", len); } else { strncat(archetype, "2_1_2", len); } break; case 4: strncat(archetype, "1_2", len); break; case 5: strncat(archetype, "2_2_4", len); break; case 6: strncat(archetype, "2_2_1", len); break; case 7: strncat(archetype, "3_1", len); break; case 8: strncat(archetype, "1_1", len); break; case 9: strncat(archetype, "2_2_3", len); break; case 10: strncat(archetype, "2_2_2", len); break; case 11: strncat(archetype, "3_3", len); break; case 12: if (has_window) { strncat(archetype, "win1", len); } else { strncat(archetype, "2_1_1", len); } break; case 13: strncat(archetype, "3_4", len); break; case 14: strncat(archetype, "3_2", len); break; case 15: strncat(archetype, "4", len); break; } /* * No need to change anything if the old and new names are identical. */ if (!strncmp(archetype, wall->arch->name, sizeof(archetype))) return; /* * Before anything, make sure the archetype does exist... * If not, prolly an error... */ new_arch = find_archetype(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 */ for (flag = 0; flag < 4; flag++) old_flags[flag] = wall->flags[flag]; remove_ob(wall); free_object(wall); wall = arch_to_object(new_arch); wall->type = WALL; insert_ob_in_map_at(wall, map, NULL, INS_ABOVE_FLOOR_ONLY, x, y); for (flag = 0; flag < 4; flag++) wall->flags[flag] = old_flags[flag]; } /** * Floor building function. * * Floors can be built: * - 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! * * @param pl * player building. * @param new_floor * new floor object * @param x * @param y * where to build. * @return * 1 if the floor was built */ static int apply_builder_floor(object *pl, object *new_floor, short x, short y) { object *tmp; object *above_floor; /* Item above floor, if any */ object *floor; /* Floor which would be removed if required */ struct archt *new_wall; int i, xt, yt, wall_removed; char message[MAX_BUF]; snprintf(message, sizeof(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; floor = NULL; new_wall = NULL; wall_removed = 0; tmp = GET_MAP_OB(pl->map, x, y); while (tmp) { object *above; above = tmp->above; if (WALL == tmp->type) { /* There was a wall, remove it & keep its archetype to make new walls */ new_wall = tmp->arch; remove_ob(tmp); free_object(tmp); snprintf(message, sizeof(message), "You destroy the wall and redo the floor."); wall_removed = 1; if (floor != NULL) { remove_ob(floor); free_object(floor); floor = NULL; } } else if ((FLOOR == tmp->type) || (QUERY_FLAG(tmp, FLAG_IS_FLOOR))) { floor = tmp; } else { if (floor != NULL) above_floor = tmp; } tmp = above; } if (wall_removed == 0 && floor != NULL) { if (floor->arch == new_floor->arch) { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "You feel too lazy to redo the exact same floor.", NULL); free_object(new_floor); return 0; } } SET_FLAG(new_floor, FLAG_UNIQUE); SET_FLAG(new_floor, FLAG_IS_FLOOR); new_floor->type = FLOOR; insert_ob_in_map_at(new_floor, pl->map, above_floor, above_floor ? INS_BELOW_ORIGINATOR : INS_ON_TOP, x, y); /* if there was a floor, remove it */ if (floor) { remove_ob(floor); free_object(floor); floor = NULL; } /* * 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 = arch_to_object(new_floor->arch); /* 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, NULL, 0, xt, yt); /* Insert wall if exists. Note: if it doesn't, the map is weird... */ if (new_wall) { tmp = arch_to_object(new_wall); SET_FLAG(tmp, FLAG_IS_BUILDABLE); tmp->type = WALL; insert_ob_in_map_at(tmp, pl->map, NULL, 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); } /* Tell player about the fix */ draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, message, NULL); return 1; } /** * Wall building function * * Walls can be built: * - on a floor without anything else * - on an existing wall, with or without a floor * * @param pl * player building. * @param new_wall * new wall object * @param x * @param y * where to build. * @return * 1 if the wall was built */ static int apply_builder_wall(object *pl, object *new_wall, short x, short y) { object *current_wall; char message[MAX_BUF]; remove_marking_runes(pl->map, x, y); current_wall = get_wall(pl->map, x, y); if (current_wall) { char current_basename[MAX_BUF]; char new_basename[MAX_BUF]; char *underscore; /* Check if the old and new archetypes have the same prefix */ strncpy(current_basename, current_wall->arch->name, sizeof(current_basename)); current_basename[sizeof(current_basename)-1] = '\0'; underscore = strchr(current_basename, '_'); if (underscore && isdigit(*(underscore+1))) { underscore++; *underscore = '\0'; } strncpy(new_basename, new_wall->arch->name, sizeof(new_basename)); new_basename[sizeof(new_basename)-1] = '\0'; underscore = strchr(new_basename, '_'); if (underscore && isdigit(*(underscore+1))) { underscore++; *underscore = '\0'; } if (!strncmp(current_basename, new_basename, sizeof(new_basename))) { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "You feel too lazy to redo the exact same wall.", NULL); free_object(new_wall); return 0; } } snprintf(message, sizeof(message), "You build a wall."); new_wall->type = WALL; if (current_wall) { /* If existing wall, replace it, no need to fix other walls */ remove_ob(current_wall); free_object(current_wall); insert_ob_in_map_at(new_wall, pl->map, NULL, INS_ABOVE_FLOOR_ONLY, x, y); fix_walls(pl->map, x, y); snprintf(message, sizeof(message), "You redecorate the wall to better suit your tastes."); } else { int xt, yt; /* Else insert new wall and fix all walls around */ insert_ob_in_map_at(new_wall, pl->map, NULL, INS_ABOVE_FLOOR_ONLY, x, y); 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); } } /* Tell player what happened */ draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, message, NULL); return 1; } /** * Window building function * * Windows can be built only on top of existing vertical or horizontal walls * (*wall_2_1_1 or *wall_2_1_2). * * @param pl * player building. * @param new_wall_win * new windowed wall object * @param x * @param y * where to build. * @return * 1 if the window was built */ static int apply_builder_window(object *pl, object *new_wall_win, short x, short y) { object *current_wall; char archetype[MAX_BUF]; struct archt *new_arch; object *window; uint32 old_flags[4]; int flag; /* Too bad, we never use the window contained in the building material */ free_object(new_wall_win); current_wall = get_wall(pl->map, x, y); if (current_wall) { char *underscore; strncpy(archetype, current_wall->arch->name, sizeof(archetype)); archetype[sizeof(archetype)-1] = '\0'; underscore = strchr(archetype, '_'); if (underscore) { underscore++; /* Check if the current wall has a window */ if (!strcmp(underscore, "win1") || !strcmp(underscore, "win2")) { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "You feel too lazy to redo the window.", NULL); return 0; } if (!strcmp(underscore, "2_1_1")) strcpy(underscore, "win1"); else if (!strcmp(underscore, "2_1_2")) strcpy(underscore, "win2"); else { /* Wrong wall orientation */ draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "You cannot build a window in that wall.", NULL); return 0; } } } else { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "There is no wall there.", NULL); return 0; } new_arch = find_archetype(archetype); if (!new_arch) { /* That type of wall doesn't have corresponding window archetypes */ draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "You cannot build a window in that wall.", NULL); return 0; } /* Now delete current wall, and insert new one with a window * We save flags to avoid any trouble with buildable/non buildable, and so on */ for (flag = 0; flag < 4; flag++) old_flags[flag] = current_wall->flags[flag]; remove_ob(current_wall); free_object(current_wall); window = arch_to_object(new_arch); window->type = WALL; insert_ob_in_map_at(window, pl->map, NULL, INS_ABOVE_FLOOR_ONLY, x, y); for (flag = 0; flag < 4; flag++) window->flags[flag] = old_flags[flag]; /* Tell player what happened */ draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "You build a window in the wall.", NULL); return 1; } /** * Generic item builder. * * Item must be put on a square with a floor, you can have something under. * * Type of inserted item is tested for specific cases (doors & such). * * Item is inserted above the floor. * * Note that it is the responsability of the caller to check whether the space is buildable or not. * * @param pl * player building. * @param new_item * new item being built * @param x * @param y * where to build. * @return * 1 if the item was built */ static int apply_builder_item(object *pl, object *new_item, short x, short y) { int insert_flag; object *floor; object *con_rune; int connected; char name[MAX_BUF]; /* Find floor */ floor = GET_MAP_OB(pl->map, x, y); if (!floor) { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "Invalid square.", NULL); free_object(new_item); return 0; } while (floor && (floor->type != FLOOR) && (!QUERY_FLAG(floor, FLAG_IS_FLOOR))) floor = floor->above; if (!floor) { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "This square has no floor, you can't build here.", NULL); free_object(new_item); return 0; } SET_FLAG(new_item, FLAG_NO_PICK); /* * This doesn't work on non unique maps. pedestals under floor will not be saved... * insert_flag = (material->stats.Str == 1) ? INS_BELOW_ORIGINATOR : INS_ABOVE_FLOOR_ONLY; */ insert_flag = INS_ABOVE_FLOOR_ONLY; connected = 0; con_rune = NULL; switch (new_item->type) { case DOOR: case GATE: case BUTTON: case DETECTOR: case TIMED_GATE: case PEDESTAL: case CF_HANDLE: case MAGIC_EAR: case SIGN: /* Signs don't need a connection, but but magic mouths do. */ if (new_item->type == SIGN && strcmp(new_item->arch->name, "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 == -1) { /* Player already informed of failure by the previous function */ free_object(new_item); return 0; } } /* For magic mouths/ears, and signs, take the msg from a book of scroll */ if ((new_item->type == SIGN) || (new_item->type == MAGIC_EAR)) { if (adjust_sign_msg(pl, x, y, new_item) == -1) { free_object(new_item); return 0; } } if (con_rune != NULL) { /* Remove marking rune */ remove_ob(con_rune); free_object(con_rune); } insert_ob_in_map_at(new_item, pl->map, floor, insert_flag, x, y); if (connected != 0) add_button_link(new_item, pl->map, connected); query_name(new_item, name, MAX_BUF); draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "You build the %s", "You build the %s", name); return 1; } /** * Item remover. * * Removes first buildable item, either under or above the floor * * @param pl * player removing an item. * @param dir * direction the player is trying to remove. */ void apply_builder_remove(object *pl, int dir) { object *item; short x, y; char name[MAX_BUF]; 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 */ draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "Invalid square.", NULL); LOG(llevError, "apply_builder_remove: (null) square at (%d, %d, %s)\n", x, y, pl->map->path); return; } if (item->type == FLOOR || QUERY_FLAG(item, FLAG_IS_FLOOR)) item = item->above; if (!item) { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "Nothing to remove.", NULL); return; } /* Now remove object, with special cases (buttons & such) */ switch (item->type) { case WALL: draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "Can't remove a wall with that, build a floor.", NULL); return; case DOOR: case BUTTON: case GATE: case TIMED_GATE: case DETECTOR: case PEDESTAL: case CF_HANDLE: case MAGIC_EAR: case SIGN: /* Special case: must unconnect */ if (QUERY_FLAG(item, FLAG_IS_LINKED)) remove_button_link(item); /* Fall through */ default: /* Remove generic item */ query_name(item, name, MAX_BUF); draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "You remove the %s", "You remove the %s", name); remove_ob(item); free_object(item); } } /** * Global building function * * This is the general map building function. Called when the player 'fires' a * builder or remover object. * * @param pl * player building or removing. * @param dir * building direction. */ void apply_map_builder(object *pl, int dir) { object *builder; object *tmp; short x, y; if (!pl->type == PLAYER) return; if (dir == 0) { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "You can't build or destroy under yourself.", NULL); return; } x = pl->x+freearr_x[dir]; y = pl->y+freearr_y[dir]; if ((1 > x) || (1 > y) || ((MAP_WIDTH(pl->map)-2) < x) || ((MAP_HEIGHT(pl->map)-2) < y)) { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "Can't build on map edge.", NULL); 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); draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "You'd better not build here, it looks weird.", NULL); return; } builder = pl->contr->ranges[range_builder]; if (builder->subtype != ST_BD_BUILD) { while (tmp) { if (!QUERY_FLAG(tmp, FLAG_IS_BUILDABLE) && ((tmp->type != SIGN) || (strcmp(tmp->arch->name, "rune_mark")))) { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "You can't build here.", NULL); return; } tmp = tmp->above; } } /* Now we know the square is ok */ 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) { object *material; struct archt *new_arch; object *new_item; int built = 0; /* Builder -> find material, get new item, call specific function */ /* find the marked item to buld */ material = find_marked_object(pl); if (!material) { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "You need to mark raw materials to use.", NULL); return; } if (material->type != MATERIAL) { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "You can't use the marked item to build.", NULL); return; } /* create a new object from the raw materials */ new_arch = find_archetype(material->slaying); if (!new_arch) { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "You can't use this strange material.", NULL); LOG(llevError, "apply_map_builder: unable to find archetype %s\n", material->slaying); return; } new_item = object_create_arch(new_arch); SET_FLAG(new_item, FLAG_IS_BUILDABLE); if (!can_build_over(pl->map, new_item, x, y)) { draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "You can't build here.", NULL); return; } /* insert the new object in the map */ switch (material->subtype) { case ST_MAT_FLOOR: built = apply_builder_floor(pl, new_item, x, y); break; case ST_MAT_WALL: built = apply_builder_wall(pl, new_item, x, y); break; case ST_MAT_ITEM: built = apply_builder_item(pl, new_item, x, y); break; case ST_MAT_WINDOW: built = apply_builder_window(pl, new_item, x, y); break; default: draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "Don't know how to apply this material, sorry.", NULL); LOG(llevError, "apply_map_builder: invalid material subtype %d\n", material->subtype); break; } if (built) decrease_ob(material); return; } /* Here, it means the builder has an invalid type */ draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_APPLY, MSG_TYPE_APPLY_BUILD, "Don't know how to apply this tool, sorry.", NULL); LOG(llevError, "apply_map_builder: invalid builder subtype %d\n", builder->subtype); }