1112 lines
32 KiB
C
1112 lines
32 KiB
C
/*
|
|
* 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 <global.h>
|
|
#include <living.h>
|
|
#include <spells.h>
|
|
#include <skills.h>
|
|
#include <tod.h>
|
|
#include <sproto.h>
|
|
|
|
/**
|
|
* 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);
|
|
}
|