1444 lines
50 KiB
C
1444 lines
50 KiB
C
/*
|
|
* static char *rcsid_server_c =
|
|
* "$Id: server.c 11578 2009-02-23 22:02:27Z lalo $";
|
|
*/
|
|
|
|
/*
|
|
CrossFire, A Multiplayer game for X-windows
|
|
|
|
Copyright (C) 2006 Mark Wedel & Crossfire Development Team
|
|
Copyright (C) 1992 Frank Tore Johansen
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
The authors can be reached via e-mail at crossfire-devel@real-time.com
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* Main server functions.
|
|
*/
|
|
|
|
#include <global.h>
|
|
#include <object.h>
|
|
#include <tod.h>
|
|
#include <version.h>
|
|
|
|
#ifdef HAVE_DES_H
|
|
#include <des.h>
|
|
#else
|
|
# ifdef HAVE_CRYPT_H
|
|
# include <crypt.h>
|
|
# endif
|
|
#endif
|
|
|
|
#ifndef __CEXTRACT__
|
|
#include <sproto.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_TIME_H
|
|
#include <time.h>
|
|
#endif
|
|
|
|
#ifndef WIN32
|
|
# include <unistd.h>
|
|
# include <sys/types.h>
|
|
#endif
|
|
|
|
#include <../random_maps/random_map.h>
|
|
#include <../random_maps/rproto.h>
|
|
#include "path.h"
|
|
|
|
/** Ingame days. */
|
|
static const char days[7][4] = {
|
|
"Sun",
|
|
"Mon",
|
|
"Tue",
|
|
"Wed",
|
|
"Thu",
|
|
"Fri",
|
|
"Sat"
|
|
};
|
|
|
|
/**
|
|
* Displays basic game version information.
|
|
*
|
|
* @param op
|
|
* who is requesting the verison information.
|
|
*/
|
|
void version(object *op) {
|
|
draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_ADMIN, MSG_TYPE_ADMIN_VERSION,
|
|
"This is Crossfire v%s",
|
|
"This is Crossfire v%s",
|
|
FULL_VERSION);
|
|
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_ADMIN, MSG_TYPE_ADMIN_VERSION,
|
|
"The authors can be reached at crossfire@metalforge.org", NULL);
|
|
|
|
}
|
|
|
|
/**
|
|
* Gives basic start information to player.
|
|
*
|
|
* @param op
|
|
* player.
|
|
*/
|
|
void start_info(object *op) {
|
|
draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_ADMIN, MSG_TYPE_ADMIN_LOGIN,
|
|
"Welcome to Crossfire, v%s!\nPress `?' for help\n",
|
|
"Welcome to Crossfire, v%s!\nPress `?' for help\n",
|
|
VERSION);
|
|
|
|
draw_ext_info_format(NDI_UNIQUE|NDI_ALL|NDI_DK_ORANGE, 5, op,
|
|
MSG_TYPE_ADMIN, MSG_TYPE_ADMIN_PLAYER,
|
|
"%s entered the game.",
|
|
"%s entered the game.",
|
|
op->name);
|
|
|
|
if (!op->contr->name_changed) {
|
|
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_ADMIN, MSG_TYPE_ADMIN_LOGIN,
|
|
"Note that you must set your name with the name command to enter the highscore list.", NULL);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Encrypt a string. Used for password storage on disk.
|
|
*
|
|
* Really, there is no reason to crypt the passwords any system. But easier
|
|
* to just leave this enabled for backward compatibility. Put the
|
|
* simple case at top - no encryption - makes it easier to read.
|
|
*
|
|
* @param str
|
|
* string to crypt.
|
|
* @param salt
|
|
* salt to crypt with.
|
|
* @return
|
|
* crypted str.
|
|
* @todo make thread-safe?
|
|
*/
|
|
char *crypt_string(char *str, char *salt) {
|
|
#if defined(WIN32) || (defined(__FreeBSD__) && !defined(HAVE_LIBDES))
|
|
return(str);
|
|
#else
|
|
static const char *const c = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";
|
|
char s[2];
|
|
|
|
if (salt == NULL)
|
|
s[0] = c[RANDOM()%(int)strlen(c)],
|
|
s[1] = c[RANDOM()%(int)strlen(c)];
|
|
else
|
|
s[0] = salt[0],
|
|
s[1] = salt[1];
|
|
|
|
# ifdef HAVE_LIBDES
|
|
return (char *)des_crypt(str, s);
|
|
# endif
|
|
/* Default case - just use crypt */
|
|
return (char *)crypt(str, s);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Check if 2 passwords match.
|
|
*
|
|
* @param typed
|
|
* entered password. Not crypted.
|
|
* @param crypted
|
|
* password to check against. Must be crypted.
|
|
* @return
|
|
* 1 if the passwords match, 0 else.
|
|
*/
|
|
int check_password(char *typed, char *crypted) {
|
|
return !strcmp(crypt_string(typed, crypted), crypted);
|
|
}
|
|
|
|
/**
|
|
* This is a basic little function to put the player back to his
|
|
* savebed. We do some error checking - its possible that the
|
|
* savebed map may no longer exist, so we make sure the player
|
|
* goes someplace.
|
|
*
|
|
* @param op
|
|
* player.
|
|
*/
|
|
void enter_player_savebed(object *op) {
|
|
mapstruct *oldmap = op->map;
|
|
object *tmp;
|
|
|
|
tmp = get_object();
|
|
|
|
EXIT_PATH(tmp) = add_string(op->contr->savebed_map);
|
|
EXIT_X(tmp) = op->contr->bed_x;
|
|
EXIT_Y(tmp) = op->contr->bed_y;
|
|
enter_exit(op, tmp);
|
|
/* If the player has not changed maps and the name does not match
|
|
* that of the savebed, his savebed map is gone. Lets go back
|
|
* to the emergency path. Update what the players savebed is
|
|
* while we're at it.
|
|
*/
|
|
if (oldmap == op->map && strcmp(op->contr->savebed_map, oldmap->path)) {
|
|
LOG(llevDebug, "Player %s savebed location %s is invalid - going to emergency location (%s)\n", settings.emergency_mapname, op->name, op->contr->savebed_map);
|
|
strcpy(op->contr->savebed_map, settings.emergency_mapname);
|
|
op->contr->bed_x = settings.emergency_x;
|
|
op->contr->bed_y = settings.emergency_y;
|
|
free_string(op->contr->savebed_map);
|
|
EXIT_PATH(tmp) = add_string(op->contr->savebed_map);
|
|
EXIT_X(tmp) = op->contr->bed_x;
|
|
EXIT_Y(tmp) = op->contr->bed_y;
|
|
enter_exit(op, tmp);
|
|
}
|
|
free_object(tmp);
|
|
}
|
|
|
|
/**
|
|
* Moves the player and pets from current map (if any) to
|
|
* new map.
|
|
*
|
|
* @param op
|
|
* player to move.
|
|
* @param newmap
|
|
* @param x
|
|
* @param y
|
|
* new location. If (x, y) point to an out of map point, will use default map coordinates.
|
|
*/
|
|
static void enter_map(object *op, mapstruct *newmap, int x, int y) {
|
|
mapstruct *oldmap = op->map;
|
|
|
|
if (out_of_map(newmap, x, y)) {
|
|
LOG(llevError, "enter_map: supplied coordinates are not within the map! (%s: %d, %d)\n", newmap->path, x, y);
|
|
x = MAP_ENTER_X(newmap);
|
|
y = MAP_ENTER_Y(newmap);
|
|
if (out_of_map(newmap, x, y)) {
|
|
LOG(llevError, "enter_map: map %s provides invalid default enter location (%d, %d) > (%d, %d)\n", newmap->path, x, y, MAP_WIDTH(newmap), MAP_HEIGHT(newmap));
|
|
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_FAILURE,
|
|
"The exit is closed", NULL);
|
|
return;
|
|
}
|
|
}
|
|
/* try to find a spot for the player */
|
|
if (ob_blocked(op, newmap, x, y)) { /* First choice blocked */
|
|
/* We try to find a spot for the player, starting closest in.
|
|
* We could use find_first_free_spot, but that doesn't randomize it at all,
|
|
* So for example, if the north space is free, you would always end up there even
|
|
* if other spaces around are available.
|
|
* Note that for the second and third calls, we could start at a position other
|
|
* than one, but then we could end up on the other side of walls and so forth.
|
|
*/
|
|
int i = find_free_spot(op, newmap, x, y, 1, SIZEOFFREE1+1);
|
|
if (i == -1) {
|
|
i = find_free_spot(op, newmap, x, y, 1, SIZEOFFREE2+1);
|
|
if (i == -1)
|
|
i = find_free_spot(op, newmap, x, y, 1, SIZEOFFREE);
|
|
}
|
|
if (i != -1) {
|
|
x += freearr_x[i];
|
|
y += freearr_y[i];
|
|
} else {
|
|
/* not much we can do in this case. */
|
|
LOG(llevInfo, "enter_map: Could not find free spot for player - will dump on top of object (%s: %d, %d)\n", newmap->path, x, y);
|
|
}
|
|
} /* end if looking for free spot */
|
|
|
|
/* If it is a player login, he has yet to be inserted anyplace.
|
|
* otherwise, we need to deal with removing the playe here.
|
|
*/
|
|
if (!QUERY_FLAG(op, FLAG_REMOVED))
|
|
remove_ob(op);
|
|
if (op->map != NULL) {
|
|
/* Lauwenmark : Here we handle the MAPLEAVE global event */
|
|
execute_global_event(EVENT_MAPLEAVE, op, op->map);
|
|
}
|
|
/* remove_ob clears these so they must be reset after the remove_ob call */
|
|
op->x = x;
|
|
op->y = y;
|
|
op->map = newmap;
|
|
insert_ob_in_map(op, op->map, NULL, INS_NO_WALK_ON);
|
|
|
|
/* Lauwenmark : Here we handle the MAPENTER global event */
|
|
execute_global_event(EVENT_MAPENTER, op, op->map);
|
|
|
|
if (op->contr) {
|
|
send_background_music(op->contr, newmap->background_music);
|
|
}
|
|
|
|
newmap->timeout = 0;
|
|
op->enemy = NULL;
|
|
|
|
if (op->contr) {
|
|
strcpy(op->contr->maplevel, newmap->path);
|
|
op->contr->count = 0;
|
|
}
|
|
|
|
/* Update any golems */
|
|
if (op->type == PLAYER && op->contr->ranges[range_golem] != NULL) {
|
|
int i = find_free_spot(op->contr->ranges[range_golem], newmap, x, y, 1, SIZEOFFREE);
|
|
remove_ob(op->contr->ranges[range_golem]);
|
|
if (i == -1) {
|
|
remove_friendly_object(op->contr->ranges[range_golem]);
|
|
free_object(op->contr->ranges[range_golem]);
|
|
op->contr->ranges[range_golem] = NULL;
|
|
op->contr->golem_count = 0;
|
|
} else {
|
|
object *tmp;
|
|
|
|
for (tmp = op->contr->ranges[range_golem]; tmp != NULL; tmp = tmp->more) {
|
|
tmp->x = x+freearr_x[i]+(tmp->arch == NULL ? 0 : tmp->arch->clone.x);
|
|
tmp->y = y+freearr_y[i]+(tmp->arch == NULL ? 0 : tmp->arch->clone.y);
|
|
tmp->map = newmap;
|
|
}
|
|
insert_ob_in_map(op->contr->ranges[range_golem], newmap, NULL, 0);
|
|
op->contr->ranges[range_golem]->direction = find_dir_2(op->x-op->contr->ranges[range_golem]->x, op->y-op->contr->ranges[range_golem]->y);
|
|
}
|
|
}
|
|
op->direction = 0;
|
|
|
|
/* since the players map is already loaded, we don't need to worry
|
|
* about pending objects.
|
|
*/
|
|
remove_all_pets();
|
|
|
|
/* If the player is changing maps, we need to do some special things
|
|
* Do this after the player is on the new map - otherwise the force swap of the
|
|
* old map does not work.
|
|
*/
|
|
if (oldmap != newmap) {
|
|
if (oldmap) { /* adjust old map */
|
|
if (oldmap->players <= 0) /* can be less than zero due to errors in tracking this */
|
|
set_map_timeout(oldmap);
|
|
}
|
|
}
|
|
swap_below_max(newmap->path);
|
|
|
|
if (op->type == PLAYER)
|
|
map_newmap_cmd(&op->contr->socket);
|
|
}
|
|
|
|
/**
|
|
* Applies the map timeout.
|
|
*
|
|
* @param oldmap
|
|
* map to process.
|
|
*/
|
|
void set_map_timeout(mapstruct *oldmap) {
|
|
#if MAP_MAXTIMEOUT
|
|
oldmap->timeout = MAP_TIMEOUT(oldmap);
|
|
/* Do MINTIMEOUT first, so that MAXTIMEOUT is used if that is
|
|
* lower than the min value.
|
|
*/
|
|
#if MAP_MINTIMEOUT
|
|
if (oldmap->timeout < MAP_MINTIMEOUT) {
|
|
oldmap->timeout = MAP_MINTIMEOUT;
|
|
}
|
|
#endif
|
|
if (oldmap->timeout > MAP_MAXTIMEOUT) {
|
|
oldmap->timeout = MAP_MAXTIMEOUT;
|
|
}
|
|
#else
|
|
/* save out the map */
|
|
swap_map(oldmap);
|
|
#endif /* MAP_MAXTIMEOUT */
|
|
}
|
|
|
|
/**
|
|
* Takes a path and replaces all / with _
|
|
* We do a strcpy so that we do not change the original string.
|
|
*
|
|
* @param file
|
|
* path to clean.
|
|
* @param newpath
|
|
* buffer that will contain the cleaned path. Should be at least as long as file.
|
|
* @param size
|
|
* length of newpath.
|
|
* @return
|
|
* newpath.
|
|
*/
|
|
static char *clean_path(const char *file, char *newpath, int size) {
|
|
char *cp;
|
|
|
|
snprintf(newpath, size, "%s", file);
|
|
for (cp = newpath; *cp != '\0'; cp++) {
|
|
if (*cp == '/')
|
|
*cp = '_';
|
|
}
|
|
return newpath;
|
|
}
|
|
|
|
/**
|
|
* Takes a path and replaces all _ with /
|
|
* This basically undoes clean_path().
|
|
* We do a strcpy so that we do not change the original string.
|
|
* We are smart enough to start after the last / in case we
|
|
* are getting passed a string that points to a unique map
|
|
* path.
|
|
*
|
|
* @param src
|
|
* path to unclean.
|
|
* @param newpath
|
|
* buffer that will contain the uncleaned path. Should be at least as long as file.
|
|
* @param size
|
|
* length of newpath.
|
|
* @return
|
|
* newpath.
|
|
*/
|
|
static char *unclean_path(const char *src, char *newpath, int size) {
|
|
char *cp;
|
|
|
|
cp = strrchr(src, '/');
|
|
if (cp)
|
|
snprintf(newpath, size, "%s", cp+1);
|
|
else
|
|
snprintf(newpath, size, "%s", src);
|
|
|
|
for (cp = newpath; *cp != '\0'; cp++) {
|
|
if (*cp == '_')
|
|
*cp = '/';
|
|
}
|
|
return newpath;
|
|
}
|
|
|
|
|
|
/**
|
|
* The player is trying to enter a randomly generated map. In this case, generate the
|
|
* random map as needed.
|
|
*
|
|
* @param pl
|
|
* player.
|
|
* @param exit_ob
|
|
* exit containing random map parameters.
|
|
*/
|
|
static void enter_random_map(object *pl, object *exit_ob) {
|
|
mapstruct *new_map;
|
|
char newmap_name[HUGE_BUF], *cp;
|
|
static int reference_number = 0;
|
|
RMParms rp;
|
|
|
|
memset(&rp, 0, sizeof(RMParms));
|
|
rp.Xsize = -1;
|
|
rp.Ysize = -1;
|
|
rp.region = get_region_by_map(exit_ob->map);
|
|
if (exit_ob->msg)
|
|
set_random_map_variable(&rp, exit_ob->msg);
|
|
rp.origin_x = exit_ob->x;
|
|
rp.origin_y = exit_ob->y;
|
|
strcpy(rp.origin_map, pl->map->path);
|
|
|
|
/* If we have a final_map, use it as a base name to give some clue
|
|
* as where the player is. Otherwise, use the origin map.
|
|
* Take the last component (after the last slash) to give
|
|
* shorter names without bogus slashes.
|
|
*/
|
|
if (rp.final_map[0]) {
|
|
cp = strrchr(rp.final_map, '/');
|
|
if (!cp)
|
|
cp = rp.final_map;
|
|
} else {
|
|
char buf[HUGE_BUF];
|
|
|
|
cp = strrchr(rp.origin_map, '/');
|
|
if (!cp)
|
|
cp = rp.origin_map;
|
|
/* Need to strip of any trailing digits, if it has them */
|
|
snprintf(buf, sizeof(buf), "%s", cp);
|
|
while (isdigit(buf[strlen(buf)-1]))
|
|
buf[strlen(buf)-1] = 0;
|
|
cp = buf;
|
|
}
|
|
|
|
snprintf(newmap_name, sizeof(newmap_name), "/random/%s%04d", cp+1, reference_number++);
|
|
|
|
/* now to generate the actual map. */
|
|
new_map = generate_random_map(newmap_name, &rp, NULL);
|
|
|
|
/* Update the exit_ob so it now points directly at the newly created
|
|
* random maps. Not that it is likely to happen, but it does mean that a
|
|
* exit in a unique map leading to a random map will not work properly.
|
|
* It also means that if the created random map gets reset before
|
|
* the exit leading to it, that the exit will no longer work.
|
|
*/
|
|
if (new_map) {
|
|
int x, y;
|
|
|
|
x = EXIT_X(exit_ob) = MAP_ENTER_X(new_map);
|
|
y = EXIT_Y(exit_ob) = MAP_ENTER_Y(new_map);
|
|
EXIT_PATH(exit_ob) = add_string(newmap_name);
|
|
snprintf(new_map->path, sizeof(new_map->path), "%s", newmap_name);
|
|
enter_map(pl, new_map, x, y);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The player is trying to enter a non-randomly generated template map. In this
|
|
* case, use a map file for a template.
|
|
*
|
|
* @param pl
|
|
* player.
|
|
* @param exit_ob
|
|
* exit containing template map parameters.
|
|
*/
|
|
static void enter_fixed_template_map(object *pl, object *exit_ob) {
|
|
mapstruct *new_map;
|
|
char tmpnum[32], exitpath[HUGE_BUF], resultname[HUGE_BUF], tmpstring[HUGE_BUF], *sourcemap;
|
|
char new_map_name[MAX_BUF];
|
|
|
|
/* Split the exit path string into two parts, one
|
|
* for where to store the map, and one for were
|
|
* to generate the map from.
|
|
*/
|
|
snprintf(exitpath, sizeof(exitpath), "%s", EXIT_PATH(exit_ob)+2);
|
|
sourcemap = strchr(exitpath, '!');
|
|
if (!sourcemap) {
|
|
draw_ext_info_format(NDI_UNIQUE, 0, pl,
|
|
MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_FAILURE,
|
|
"The %s is closed.",
|
|
"The %s is closed.",
|
|
exit_ob->name);
|
|
/* Should only occur when no source map is set.
|
|
*/
|
|
LOG(llevError, "enter_fixed_template_map: Exit %s (%d,%d) on map %s has no source template.\n", exit_ob->name, exit_ob->x, exit_ob->y, exit_ob->map->path);
|
|
return;
|
|
}
|
|
*sourcemap++ = '\0';
|
|
|
|
/* If we are not coming from a template map, we can use relative directories
|
|
* for the map to generate from.
|
|
*/
|
|
if (!exit_ob->map->is_template) {
|
|
/* We can't use exitpath directly, as sourcemap points there. */
|
|
path_combine_and_normalize(exit_ob->map->path, sourcemap, tmpstring, sizeof(tmpstring));
|
|
snprintf(exitpath, sizeof(exitpath), "%s", tmpstring);
|
|
sourcemap = exitpath;
|
|
}
|
|
|
|
/* Do replacement of %x, %y, and %n to the x coord of the exit, the y coord
|
|
* of the exit, and the name of the map the exit is on, respectively.
|
|
*/
|
|
snprintf(tmpnum, sizeof(tmpnum), "%d", exit_ob->x);
|
|
replace(exitpath, "%x", tmpnum, resultname, sizeof(resultname));
|
|
|
|
snprintf(tmpnum, sizeof(tmpnum), "%d", exit_ob->y);
|
|
snprintf(tmpstring, sizeof(tmpstring), "%s", resultname);
|
|
replace(tmpstring, "%y", tmpnum, resultname, sizeof(resultname));
|
|
|
|
snprintf(tmpstring, sizeof(tmpstring), "%s", resultname);
|
|
replace(tmpstring, "%n", exit_ob->map->name, resultname, sizeof(resultname));
|
|
|
|
/* If we are coming from another template map, use reletive paths unless
|
|
* indicated otherwise.
|
|
*/
|
|
if (exit_ob->map->is_template && (resultname[0] != '/')) {
|
|
path_combine_and_normalize(exit_ob->map->path, resultname, new_map_name, sizeof(new_map_name));
|
|
} else {
|
|
create_template_pathname(resultname, new_map_name, sizeof(new_map_name));
|
|
}
|
|
|
|
/* Attempt to load the map, if unable to, then
|
|
* create the map from the template.
|
|
*/
|
|
new_map = ready_map_name(new_map_name, MAP_PLAYER_UNIQUE);
|
|
if (!new_map) {
|
|
char path[MAX_BUF];
|
|
|
|
create_pathname(sourcemap, path, MAX_BUF);
|
|
new_map = load_original_map(path, MAP_PLAYER_UNIQUE);
|
|
if (new_map)
|
|
fix_auto_apply(new_map);
|
|
}
|
|
|
|
if (new_map) {
|
|
/* set the path of the map to where it should be
|
|
* so we don't just save over the source map.
|
|
*/
|
|
snprintf(new_map->path, sizeof(new_map->path), "%s", new_map_name);
|
|
new_map->is_template = 1;
|
|
enter_map(pl, new_map, EXIT_X(exit_ob), EXIT_Y(exit_ob));
|
|
} else {
|
|
draw_ext_info_format(NDI_UNIQUE, 0, pl,
|
|
MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_FAILURE,
|
|
"The %s is closed.",
|
|
"The %s is closed.",
|
|
exit_ob->name);
|
|
/* Should only occur when an invalid source map is set.
|
|
*/
|
|
LOG(llevDebug, "enter_fixed_template_map: Exit %s (%d,%d) on map %s leads no where.\n", exit_ob->name, exit_ob->x, exit_ob->y, exit_ob->map->path);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The player is trying to enter a randomly generated template map. In this
|
|
* case, generate the map as needed.
|
|
*
|
|
* @param pl
|
|
* player.
|
|
* @param exit_ob
|
|
* exit containing random template map parameters.
|
|
*/
|
|
static void enter_random_template_map(object *pl, object *exit_ob) {
|
|
mapstruct *new_map;
|
|
char tmpnum[32], resultname[HUGE_BUF], tmpstring[HUGE_BUF];
|
|
char new_map_name[MAX_BUF];
|
|
RMParms rp;
|
|
|
|
/* Do replacement of %x, %y, and %n to the x coord of the exit, the y coord
|
|
* of the exit, and the name of the map the exit is on, respectively.
|
|
*/
|
|
snprintf(tmpnum, sizeof(tmpnum), "%d", exit_ob->x);
|
|
replace(EXIT_PATH(exit_ob)+3, "%x", tmpnum, resultname, sizeof(resultname));
|
|
|
|
snprintf(tmpnum, sizeof(tmpnum), "%d", exit_ob->y);
|
|
snprintf(tmpstring, sizeof(tmpstring), "%s", resultname);
|
|
replace(tmpstring, "%y", tmpnum, resultname, sizeof(resultname));
|
|
|
|
snprintf(tmpstring, sizeof(tmpstring), "%s", resultname);
|
|
replace(tmpstring, "%n", exit_ob->map->name, resultname, sizeof(resultname));
|
|
|
|
/* If we are coming from another template map, use reletive paths unless
|
|
* indicated otherwise.
|
|
*/
|
|
if (exit_ob->map->is_template && (resultname[0] != '/')) {
|
|
path_combine_and_normalize(exit_ob->map->path, resultname, new_map_name, sizeof(new_map_name));
|
|
} else {
|
|
create_template_pathname(resultname, new_map_name, sizeof(new_map_name));
|
|
}
|
|
|
|
new_map = ready_map_name(new_map_name, MAP_PLAYER_UNIQUE);
|
|
if (!new_map) {
|
|
memset(&rp, 0, sizeof(RMParms));
|
|
rp.Xsize = -1;
|
|
rp.Ysize = -1;
|
|
rp.region = get_region_by_map(exit_ob->map);
|
|
if (exit_ob->msg)
|
|
set_random_map_variable(&rp, exit_ob->msg);
|
|
rp.origin_x = exit_ob->x;
|
|
rp.origin_y = exit_ob->y;
|
|
strcpy(rp.origin_map, pl->map->path);
|
|
|
|
/* now to generate the actual map. */
|
|
new_map = generate_random_map(new_map_name, &rp, NULL);
|
|
}
|
|
|
|
/* Update the exit_ob so it now points directly at the newly created
|
|
* random maps. Not that it is likely to happen, but it does mean that a
|
|
* exit in a unique map leading to a random map will not work properly.
|
|
* It also means that if the created random map gets reset before
|
|
* the exit leading to it, that the exit will no longer work.
|
|
*/
|
|
if (new_map) {
|
|
int x, y;
|
|
|
|
x = EXIT_X(exit_ob) = MAP_ENTER_X(new_map);
|
|
y = EXIT_Y(exit_ob) = MAP_ENTER_Y(new_map);
|
|
new_map->is_template = 1;
|
|
enter_map(pl, new_map, x, y);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Player is entering a unique map.
|
|
*
|
|
* @param op
|
|
* player.
|
|
* @param exit_ob
|
|
* exit containing unique map information.
|
|
*/
|
|
static void enter_unique_map(object *op, object *exit_ob) {
|
|
char apartment[HUGE_BUF], path[MAX_BUF];
|
|
mapstruct *newmap;
|
|
|
|
if (EXIT_PATH(exit_ob)[0] == '/') {
|
|
snprintf(apartment, sizeof(apartment), "%s/%s/%s/%s", settings.localdir, settings.playerdir, op->name, clean_path(EXIT_PATH(exit_ob), path, sizeof(path)));
|
|
newmap = ready_map_name(apartment, MAP_PLAYER_UNIQUE);
|
|
if (!newmap) {
|
|
create_pathname(EXIT_PATH(exit_ob), path, sizeof(path));
|
|
newmap = load_original_map(path, MAP_PLAYER_UNIQUE);
|
|
if (newmap)
|
|
fix_auto_apply(newmap);
|
|
}
|
|
} else { /* relative directory */
|
|
char reldir[HUGE_BUF], tmpc[HUGE_BUF], *cp;
|
|
|
|
if (exit_ob->map->unique) {
|
|
|
|
unclean_path(exit_ob->map->path, reldir, sizeof(reldir));
|
|
|
|
/* Need to copy this over, as clean_path only has one static return buffer */
|
|
clean_path(reldir, tmpc, sizeof(tmpc));
|
|
/* Remove final component, if any */
|
|
if ((cp = strrchr(tmpc, '_')) != NULL)
|
|
*cp = 0;
|
|
|
|
snprintf(apartment, sizeof(apartment), "%s/%s/%s/%s_%s", settings.localdir, settings.playerdir, op->name, tmpc, clean_path(EXIT_PATH(exit_ob), path, sizeof(path)));
|
|
|
|
newmap = ready_map_name(apartment, MAP_PLAYER_UNIQUE);
|
|
if (!newmap) {
|
|
create_pathname(path_combine_and_normalize(reldir, EXIT_PATH(exit_ob), tmpc, sizeof(tmpc)), path, sizeof(path));
|
|
newmap = load_original_map(path, MAP_PLAYER_UNIQUE);
|
|
if (newmap)
|
|
fix_auto_apply(newmap);
|
|
}
|
|
} else {
|
|
/* The exit is unique, but the map we are coming from is not unique. So
|
|
* use the basic logic - don't need to demangle the path name
|
|
*/
|
|
path_combine_and_normalize(exit_ob->map->path, EXIT_PATH(exit_ob), reldir, sizeof(reldir));
|
|
snprintf(apartment, sizeof(apartment), "%s/%s/%s/%s", settings.localdir, settings.playerdir, op->name, clean_path(reldir, path, sizeof(path)));
|
|
newmap = ready_map_name(apartment, MAP_PLAYER_UNIQUE);
|
|
if (!newmap) {
|
|
path_combine_and_normalize(exit_ob->map->path, EXIT_PATH(exit_ob), reldir, sizeof(reldir));
|
|
newmap = ready_map_name(reldir, 0);
|
|
if (newmap)
|
|
fix_auto_apply(newmap);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newmap) {
|
|
snprintf(newmap->path, sizeof(newmap->path), "%s", apartment);
|
|
newmap->unique = 1;
|
|
enter_map(op, newmap, EXIT_X(exit_ob), EXIT_Y(exit_ob));
|
|
} else {
|
|
draw_ext_info_format(NDI_UNIQUE, 0, op,
|
|
MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_FAILURE,
|
|
"The %s is closed.",
|
|
"The %s is closed.",
|
|
exit_ob->name);
|
|
/* Perhaps not critical, but I would think that the unique maps
|
|
* should be new enough this does not happen. This also creates
|
|
* a strange situation where some players could perhaps have visited
|
|
* such a map before it was removed, so they have the private
|
|
* map, but other players can't get it anymore.
|
|
*/
|
|
LOG(llevDebug, "enter_unique_map: Exit %s (%d,%d) on map %s is leads no where.\n", exit_ob->name, exit_ob->x, exit_ob->y, exit_ob->map->path);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Tries to move 'op' to exit_ob.
|
|
*
|
|
* This is used when loading the player.
|
|
*
|
|
* Largely redone by MSW 2001-01-21 - this function was overly complex
|
|
* and had some obscure bugs.
|
|
*
|
|
* @param op
|
|
* character or monster that is using the exit.
|
|
* @param exit_ob
|
|
* exit object (boat, door, teleporter, etc.). if null, then op->contr->maplevel contains that map to
|
|
* move the object to.
|
|
*/
|
|
void enter_exit(object *op, object *exit_ob) {
|
|
#define PORTAL_DESTINATION_NAME "Town portal destination" /* this one should really be in a header file */
|
|
object *tmp;
|
|
/* It may be nice to support other creatures moving across
|
|
* exits, but right now a lot of the code looks at op->contr,
|
|
* so thta is an RFE.
|
|
*/
|
|
if (op->type != PLAYER)
|
|
return;
|
|
|
|
/* Need to remove player from transport */
|
|
if (op->contr->transport)
|
|
ob_apply(op->contr->transport, op, AP_UNAPPLY);
|
|
|
|
/* First, lets figure out what map the player is going to go to */
|
|
if (exit_ob) {
|
|
/* check to see if we make a template map */
|
|
if (EXIT_PATH(exit_ob) && EXIT_PATH(exit_ob)[1] == '@') {
|
|
if (EXIT_PATH(exit_ob)[2] == '!') {
|
|
/* generate a template map randomly */
|
|
enter_random_template_map(op, exit_ob);
|
|
} else {
|
|
/* generate a template map from a fixed template */
|
|
enter_fixed_template_map(op, exit_ob);
|
|
}
|
|
}
|
|
/* check to see if we make a randomly generated map */
|
|
else if (EXIT_PATH(exit_ob) && EXIT_PATH(exit_ob)[1] == '!') {
|
|
enter_random_map(op, exit_ob);
|
|
} else if (QUERY_FLAG(exit_ob, FLAG_UNIQUE)) {
|
|
enter_unique_map(op, exit_ob);
|
|
} else {
|
|
int x = EXIT_X(exit_ob), y = EXIT_Y(exit_ob);
|
|
/* 'Normal' exits that do not do anything special
|
|
* Simple enough we don't need another routine for it.
|
|
*/
|
|
mapstruct *newmap;
|
|
if (exit_ob->map) {
|
|
char tmp_path[HUGE_BUF];
|
|
|
|
path_combine_and_normalize(exit_ob->map->path, EXIT_PATH(exit_ob), tmp_path, sizeof(tmp_path));
|
|
newmap = ready_map_name(tmp_path, 0);
|
|
/* Random map was previously generated, but is no longer about. Lets generate a new
|
|
* map.
|
|
*/
|
|
if (!newmap && !strncmp(EXIT_PATH(exit_ob), "/random/", 8)) {
|
|
/* Maps that go down have a message set. However, maps that go
|
|
* up, don't. If the going home has reset, there isn't much
|
|
* point generating a random map, because it won't match the maps.
|
|
*/
|
|
if (exit_ob->msg) {
|
|
enter_random_map(op, exit_ob);
|
|
} else {
|
|
draw_ext_info_format(NDI_UNIQUE, 0, op,
|
|
MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_FAILURE,
|
|
"The %s is closed.",
|
|
"The %s is closed.",
|
|
exit_ob->name);
|
|
return;
|
|
}
|
|
|
|
/* For exits that cause damages (like pits). Don't know if any
|
|
* random maps use this or not.
|
|
*/
|
|
if (exit_ob->stats.dam && op->type == PLAYER)
|
|
hit_player(op, exit_ob->stats.dam, exit_ob, exit_ob->attacktype, 1);
|
|
return;
|
|
}
|
|
} else {
|
|
/* For word of recall and other force objects
|
|
* They contain the full pathname of the map to go back to,
|
|
* so we don't need to normalize it.
|
|
* But we do need to see if it is unique or not
|
|
*/
|
|
if (!strncmp(EXIT_PATH(exit_ob), settings.localdir, strlen(settings.localdir)))
|
|
newmap = ready_map_name(EXIT_PATH(exit_ob), MAP_PLAYER_UNIQUE);
|
|
else
|
|
newmap = ready_map_name(EXIT_PATH(exit_ob), 0);
|
|
}
|
|
if (!newmap) {
|
|
if (exit_ob->name)
|
|
draw_ext_info_format(NDI_UNIQUE, 0, op,
|
|
MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_FAILURE,
|
|
"The %s is closed.",
|
|
"The %s is closed.",
|
|
exit_ob->name);
|
|
/* don't cry to momma if name is not set - as in tmp objects
|
|
* used by the savebed code and character creation */
|
|
return;
|
|
}
|
|
|
|
/* This supports the old behaviour, but it really should not be used.
|
|
* I will note for example that with this method, it is impossible to
|
|
* set 0,0 destination coordinates. Really, if we want to support
|
|
* using the new maps default coordinates, the exit ob should use
|
|
* something like -1, -1 so it is clear to do that.
|
|
*/
|
|
if (x == 0 && y == 0) {
|
|
x = MAP_ENTER_X(newmap);
|
|
y = MAP_ENTER_Y(newmap);
|
|
LOG(llevDebug, "enter_exit: Exit %s (%d,%d) on map %s is 0 destination coordinates\n",
|
|
exit_ob->name ? exit_ob->name : "(none)", exit_ob->x, exit_ob->y,
|
|
exit_ob->map ? exit_ob->map->path : "(none)");
|
|
}
|
|
|
|
/* mids 02/13/2002 if exit is damned, update players death & WoR home-position and delete town portal */
|
|
if (QUERY_FLAG(exit_ob, FLAG_DAMNED)) {
|
|
/* remove an old force with a slaying field == PORTAL_DESTINATION_NAME */
|
|
for (tmp = op->inv; tmp != NULL; tmp = tmp->below) {
|
|
if (tmp->type == FORCE && tmp->slaying && !strcmp(tmp->slaying, PORTAL_DESTINATION_NAME))
|
|
break;
|
|
}
|
|
if (tmp) {
|
|
remove_ob(tmp);
|
|
free_object(tmp);
|
|
}
|
|
|
|
path_combine_and_normalize(exit_ob->map->path, EXIT_PATH(exit_ob), op->contr->savebed_map, sizeof(op->contr->savebed_map));
|
|
op->contr->bed_x = EXIT_X(exit_ob), op->contr->bed_y = EXIT_Y(exit_ob);
|
|
save_player(op, 1);
|
|
/* LOG(llevDebug, "enter_exit: Taking damned exit %s to (%d,%d) on map %s\n",
|
|
* exit_ob->name ? exit_ob->name : "(none)", exit_ob->x, exit_ob->y,
|
|
* path_combine_and_normalize(exit_ob->map->path, EXIT_PATH(exit_ob))); */
|
|
}
|
|
|
|
enter_map(op, newmap, x, y);
|
|
}
|
|
/* For exits that cause damages (like pits) */
|
|
if (exit_ob->stats.dam && op->type == PLAYER)
|
|
hit_player(op, exit_ob->stats.dam, exit_ob, exit_ob->attacktype, 1);
|
|
} else {
|
|
int flags = 0;
|
|
mapstruct *newmap;
|
|
|
|
/* Hypothetically, I guess its possible that a standard map matches
|
|
* the localdir, but that seems pretty unlikely - unlikely enough that
|
|
* I'm not going to attempt to try to deal with that possibility.
|
|
* We use the fact that when a player saves on a unique map, it prepends
|
|
* the localdir to that name. So its an easy way to see of the map is
|
|
* unique or not.
|
|
*/
|
|
if (!strncmp(op->contr->maplevel, settings.localdir, strlen(settings.localdir)))
|
|
flags = MAP_PLAYER_UNIQUE;
|
|
|
|
/* newmap returns the map (if already loaded), or loads it for us. */
|
|
newmap = ready_map_name(op->contr->maplevel, flags);
|
|
if (!newmap) {
|
|
LOG(llevError, "enter_exit: Pathname to map does not exist! (%s)\n", op->contr->maplevel);
|
|
newmap = ready_map_name(settings.emergency_mapname, 0);
|
|
op->x = settings.emergency_x;
|
|
op->y = settings.emergency_y;
|
|
/* If we can't load the emergency map, something is probably really
|
|
* screwed up, so bail out now.
|
|
*/
|
|
if (!newmap) {
|
|
LOG(llevError, "enter_exit: could not load emergency map? Fatal error\n");
|
|
abort();
|
|
}
|
|
}
|
|
enter_map(op, newmap, op->x, op->y);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Do all player-related stuff before objects have been updated.
|
|
*
|
|
* @sa process_players2().
|
|
*/
|
|
static void process_players1(void) {
|
|
int flag;
|
|
player *pl, *plnext;
|
|
|
|
/* Basically, we keep looping until all the players have done their actions. */
|
|
for (flag = 1; flag != 0; ) {
|
|
flag = 0;
|
|
for (pl = first_player; pl != NULL; pl = plnext) {
|
|
plnext = pl->next; /* In case a player exits the game in handle_player() */
|
|
|
|
if (pl->ob == NULL)
|
|
continue;
|
|
|
|
/** Handle DM follow command */
|
|
if (pl->followed_player) {
|
|
player *followed = find_player_partial_name(pl->followed_player);
|
|
if (followed && followed->ob && followed->ob->map) {
|
|
rv_vector rv;
|
|
|
|
get_rangevector(pl->ob, followed->ob, &rv, 0);
|
|
if (rv.distance > 4) {
|
|
int space = find_free_spot(pl->ob, followed->ob->map, followed->ob->x, followed->ob->y, 1, 25);
|
|
if (space == -1)
|
|
/** This is a DM, just teleport on the top of player. */
|
|
space = 0;
|
|
remove_ob(pl->ob);
|
|
insert_ob_in_map_at(pl->ob, followed->ob->map, NULL, 0, followed->ob->x+freearr_x[space], followed->ob->y+freearr_y[space]);
|
|
map_newmap_cmd(&pl->socket);
|
|
}
|
|
} else {
|
|
draw_ext_info_format(NDI_UNIQUE, 0, pl->ob, MSG_TYPE_ADMIN, MSG_TYPE_ADMIN_DM, "Player %s left or ambiguous name.", NULL, pl->followed_player);
|
|
FREE_AND_CLEAR_STR(pl->followed_player);
|
|
}
|
|
} /** End of follow */
|
|
|
|
if (pl->ob->speed_left > 0) {
|
|
if (handle_newcs_player(pl->ob))
|
|
flag = 1;
|
|
} /* end if player has speed left */
|
|
|
|
/* If the player is not actively playing, don't make a
|
|
* backup save - nothing to save anyway. Plus, the
|
|
* map may not longer be valid. This can happen when the
|
|
* player quits - they exist for purposes of tracking on the map,
|
|
* but don't actually reside on any actual map.
|
|
*/
|
|
if (QUERY_FLAG(pl->ob, FLAG_REMOVED))
|
|
continue;
|
|
|
|
#ifdef AUTOSAVE
|
|
/* check for ST_PLAYING state so that we don't try to save off when
|
|
* the player is logging in.
|
|
*/
|
|
if ((pl->last_save_tick+AUTOSAVE) < pticks && pl->state == ST_PLAYING) {
|
|
/* Don't save the player on unholy ground. Instead, increase the
|
|
* tick time so it will be about 10 seconds before we try and save
|
|
* again.
|
|
*/
|
|
if (get_map_flags(pl->ob->map, NULL, pl->ob->x, pl->ob->y, NULL, NULL)&P_NO_CLERIC) {
|
|
pl->last_save_tick += 100;
|
|
} else {
|
|
save_player(pl->ob, 1);
|
|
pl->last_save_tick = pticks;
|
|
check_score(pl->ob, 1);
|
|
}
|
|
}
|
|
#endif
|
|
} /* end of for loop for all the players */
|
|
} /* for flag */
|
|
for(pl=first_player;pl!=NULL;pl=pl->next) {
|
|
pl->socket.sounds_this_tick = 0;
|
|
if (settings.casting_time == TRUE) {
|
|
if (pl->ob->casting_time > 0){
|
|
pl->ob->casting_time--;
|
|
}
|
|
}
|
|
do_some_living(pl->ob);
|
|
/* draw(pl->ob);*/ /* updated in socket code */
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Do all player-related stuff after objects have been updated.
|
|
*
|
|
* @sa process_players1().
|
|
*
|
|
* @todo explain why 2 passes for players.
|
|
*/
|
|
static void process_players2(void) {
|
|
player *pl;
|
|
|
|
/* Then check if any players should use weapon-speed instead of speed */
|
|
for (pl=first_player;pl!=NULL;pl=pl->next) {
|
|
|
|
/* The code that did weapon_sp handling here was out of place -
|
|
* this isn't called until after the player has finished there
|
|
* actions, and is thus out of place. All we do here is bounds
|
|
* checking.
|
|
*/
|
|
if (pl->has_hit) {
|
|
if (pl->ob->speed_left > pl->weapon_sp) pl->ob->speed_left = pl->weapon_sp;
|
|
|
|
/* This needs to be here - if the player is running, we need to
|
|
* clear this each tick, but new commands are not being received
|
|
* so execute_newserver_command() is never called
|
|
*/
|
|
pl->has_hit=0;
|
|
|
|
} else if (pl->ob->speed_left > pl->ob->speed)
|
|
pl->ob->speed_left = pl->ob->speed;
|
|
}
|
|
}
|
|
|
|
#define SPEED_DEBUG
|
|
|
|
/**
|
|
* Process all active objects.
|
|
*/
|
|
void process_events(void) {
|
|
object *op;
|
|
object marker;
|
|
tag_t tag;
|
|
|
|
process_players1();
|
|
|
|
memset(&marker, 0, sizeof(object));
|
|
/* Put marker object at beginning of active list */
|
|
marker.active_next = active_objects;
|
|
|
|
if (marker.active_next)
|
|
marker.active_next->active_prev = ▮
|
|
marker.active_prev = NULL;
|
|
active_objects = ▮
|
|
|
|
while (marker.active_next) {
|
|
op = marker.active_next;
|
|
tag = op->count;
|
|
|
|
/* Move marker forward - swap op and marker */
|
|
op->active_prev = marker.active_prev;
|
|
|
|
if (op->active_prev)
|
|
op->active_prev->active_next = op;
|
|
else
|
|
active_objects = op;
|
|
|
|
marker.active_next = op->active_next;
|
|
|
|
if (marker.active_next)
|
|
marker.active_next->active_prev = ▮
|
|
marker.active_prev = op;
|
|
op->active_next = ▮
|
|
|
|
/* Now process op */
|
|
if (QUERY_FLAG(op, FLAG_FREED)) {
|
|
LOG(llevError, "BUG: process_events(): Free object on list\n");
|
|
op->speed = 0;
|
|
update_ob_speed(op);
|
|
continue;
|
|
}
|
|
|
|
/* I've seen occasional crashes due to this - the object is removed,
|
|
* and thus the map it points to (last map it was on) may be bogus
|
|
* The real bug is to try to find out the cause of this - someone
|
|
* is probably calling remove_ob without either an insert_ob or
|
|
* free_object afterwards, leaving an object dangling. But I'd
|
|
* rather log this and continue on instead of crashing.
|
|
* Don't remove players - when a player quits, the object is in
|
|
* sort of a limbo, of removed, but something we want to keep
|
|
* around.
|
|
*/
|
|
if (QUERY_FLAG(op, FLAG_REMOVED)
|
|
&& op->type != PLAYER
|
|
&& op->map
|
|
&& op->map->in_memory != MAP_IN_MEMORY) {
|
|
StringBuffer *sb;
|
|
char *diff;
|
|
|
|
LOG(llevError, "BUG: process_events(): Removed object on list\n");
|
|
sb = stringbuffer_new();
|
|
dump_object(op, sb);
|
|
diff = stringbuffer_finish(sb);
|
|
LOG(llevError, "%s\n", diff);
|
|
free(diff);
|
|
free_object(op);
|
|
continue;
|
|
}
|
|
|
|
if (!op->speed) {
|
|
LOG(llevError, "BUG: process_events(): Object %s has no speed, but is on active list\n", op->arch->name);
|
|
update_ob_speed(op);
|
|
continue;
|
|
}
|
|
|
|
if (op->map == NULL
|
|
&& op->env == NULL
|
|
&& op->name
|
|
&& op->type != MAP) {
|
|
LOG(llevError, "BUG: process_events(): Object without map or inventory is on active list: %s (%d)\n", op->name, op->count);
|
|
op->speed = 0;
|
|
update_ob_speed(op);
|
|
continue;
|
|
}
|
|
|
|
/* Seen some cases where process_object() is crashing because
|
|
* the object is on a swapped out map. But can't be sure if
|
|
* something in the chain of events caused the object to
|
|
* change maps or was just never removed - this will
|
|
* give some clue as to its state before call to
|
|
* process_object
|
|
*/
|
|
if (op->map && op->map->in_memory != MAP_IN_MEMORY) {
|
|
LOG(llevError, "BUG: process_events(): Processing object on swapped out map: %s (%d), map=%s\n", op->name, op->count, op->map->path);
|
|
}
|
|
|
|
/* Animate the object. Bug of feature that andim_speed
|
|
* is based on ticks, and not the creatures speed?
|
|
*/
|
|
if ((op->anim_speed && op->last_anim >= op->anim_speed)
|
|
|| (op->temp_animation_id && op->last_anim >= op->temp_anim_speed)) {
|
|
op->state++;
|
|
if ((op->type == PLAYER) || (op->type == MONSTER))
|
|
animate_object(op, op->facing);
|
|
else
|
|
animate_object(op, op->direction);
|
|
op->last_anim = 1;
|
|
} else {
|
|
op->last_anim++;
|
|
}
|
|
|
|
if (op->speed_left > 0) {
|
|
--op->speed_left;
|
|
process_object(op);
|
|
if (was_destroyed(op, tag))
|
|
continue;
|
|
}
|
|
if (settings.casting_time == TRUE && op->casting_time > 0)
|
|
op->casting_time--;
|
|
if (op->speed_left <= 0)
|
|
op->speed_left += FABS(op->speed);
|
|
}
|
|
|
|
/* Remove marker object from active list */
|
|
if (marker.active_prev != NULL)
|
|
marker.active_prev->active_next = NULL;
|
|
else
|
|
active_objects = NULL;
|
|
|
|
process_players2();
|
|
}
|
|
|
|
/**
|
|
* Remove temporary map files.
|
|
*
|
|
* @todo check logic, why is file only removed if map is in memory?
|
|
*/
|
|
void clean_tmp_files(void) {
|
|
mapstruct *m, *next;
|
|
|
|
LOG(llevInfo, "Cleaning up...\n");
|
|
|
|
/* We save the maps - it may not be intuitive why, but if there are unique
|
|
* items, we need to save the map so they get saved off. Perhaps we should
|
|
* just make a special function that only saves the unique items.
|
|
*/
|
|
for (m = first_map; m != NULL; m = next) {
|
|
next = m->next;
|
|
if (m->in_memory == MAP_IN_MEMORY) {
|
|
/* If we want to reuse the temp maps, swap it out (note that will also
|
|
* update the log file. Otherwise, save the map (mostly for unique item
|
|
* stuff). Note that the clean_tmp_map is called after the end of
|
|
* the for loop but is in the #else bracket. IF we are recycling the maps,
|
|
* we certainly don't want the temp maps removed.
|
|
*/
|
|
|
|
/* XXX The above comment is dead wrong */
|
|
if (settings.recycle_tmp_maps == TRUE)
|
|
swap_map(m);
|
|
else {
|
|
save_map(m, SAVE_MODE_NORMAL); /* note we save here into a overlay map */
|
|
clean_tmp_map(m);
|
|
}
|
|
}
|
|
}
|
|
write_todclock(); /* lets just write the clock here */
|
|
}
|
|
|
|
/** Clean up everything and exit. */
|
|
void cleanup(void) {
|
|
LOG(llevDebug, "Cleanup called. freeing data.\n");
|
|
clean_tmp_files();
|
|
write_book_archive();
|
|
|
|
#ifdef MEMORY_DEBUG
|
|
free_all_maps();
|
|
free_style_maps();
|
|
#endif
|
|
cleanupPlugins();
|
|
#ifdef MEMORY_DEBUG
|
|
free_all_archs();
|
|
free_all_treasures();
|
|
free_all_images();
|
|
free_all_newserver();
|
|
free_all_recipes();
|
|
free_all_readable();
|
|
free_all_god();
|
|
free_all_anim();
|
|
free_loader();
|
|
free_globals();
|
|
free_server();
|
|
free_all_object_data();
|
|
/* See what the string data that is out there that hasn't been freed. */
|
|
/* LOG(llevDebug, "%s", ss_dump_table(0xff));*/
|
|
#endif
|
|
exit(0);
|
|
}
|
|
|
|
/**
|
|
* Player logs out, or was disconnected.
|
|
*
|
|
* @param pl
|
|
* player.
|
|
* @param draw_exit
|
|
* if set, display leaving message to other players.
|
|
* @todo check for pl != NULL should include the 'left the game', just in case (or remove it?)
|
|
*/
|
|
void leave(player *pl, int draw_exit) {
|
|
|
|
if (pl != NULL) {
|
|
pl->socket.status = Ns_Dead;
|
|
LOG(llevInfo, "LOGOUT: Player named %s from ip %s\n", pl->ob->name, pl->socket.host);
|
|
|
|
check_score(pl->ob, 1);
|
|
|
|
/* If this player is the captain of the transport, need to do
|
|
* some extra work. By the time we get here, remove_ob()
|
|
* should have already been called.
|
|
*/
|
|
if (pl->transport && pl->transport->contr == pl) {
|
|
/* If inv is a non player, inv->contr will be NULL, but that
|
|
* is OK.
|
|
*/
|
|
if (pl->transport->inv)
|
|
pl->transport->contr = pl->transport->inv->contr;
|
|
else
|
|
pl->transport->contr = NULL;
|
|
|
|
if (pl->transport->contr) {
|
|
char name[MAX_BUF];
|
|
|
|
query_name(pl->transport, name, MAX_BUF);
|
|
draw_ext_info_format(NDI_UNIQUE, 0, pl->transport->contr->ob,
|
|
MSG_TYPE_ADMIN, MSG_TYPE_ADMIN_PLAYER,
|
|
"%s has left. You are now the captain of %s",
|
|
"%s has left. You are now the captain of %s",
|
|
pl->ob->name, name);
|
|
}
|
|
}
|
|
|
|
if (pl->ob->map) {
|
|
if (pl->ob->map->in_memory == MAP_IN_MEMORY)
|
|
pl->ob->map->timeout = MAP_TIMEOUT(pl->ob->map);
|
|
/* we need to update player count, since remove_ob() isn't called */
|
|
if (!pl->hidden)
|
|
pl->ob->map->players--;
|
|
pl->ob->map = NULL;
|
|
}
|
|
pl->ob->type = DEAD_OBJECT; /* To avoid problems with inventory window */
|
|
}
|
|
/* If a hidden dm dropped connection do not create
|
|
* inconsistencies by showing that they have left the game
|
|
*/
|
|
if (!(QUERY_FLAG(pl->ob, FLAG_WIZ) && pl->ob->contr->hidden)
|
|
&& (pl != NULL && draw_exit) && (pl->state != ST_GET_NAME && pl->state != ST_GET_PASSWORD && pl->state != ST_CONFIRM_PASSWORD))
|
|
draw_ext_info_format(NDI_UNIQUE|NDI_ALL|NDI_DK_ORANGE, 5, NULL,
|
|
MSG_TYPE_ADMIN, MSG_TYPE_ADMIN_PLAYER,
|
|
"%s left the game.",
|
|
"%s left the game.",
|
|
pl->ob->name);
|
|
}
|
|
|
|
/**
|
|
* Checks if server should be started.
|
|
*
|
|
* @return
|
|
* 1 if play is forbidden, 0 if ok.
|
|
* @todo document forbidden stuff.
|
|
*/
|
|
int forbid_play(void) {
|
|
#if !defined(_IBMR2) && !defined(___IBMR2) && defined(PERM_FILE)
|
|
char buf[MAX_BUF], day[MAX_BUF];
|
|
FILE *fp;
|
|
time_t clock;
|
|
struct tm *tm;
|
|
int i, start, stop, forbit = 0, comp;
|
|
|
|
clock = time(NULL);
|
|
tm = (struct tm *)localtime(&clock);
|
|
|
|
snprintf(buf, sizeof(buf), "%s/%s", settings.confdir, PERM_FILE);
|
|
if ((fp = open_and_uncompress(buf, 0, &comp)) == NULL)
|
|
return 0;
|
|
|
|
while (fgets(buf, sizeof(buf), fp)) {
|
|
if (buf[0] == '#')
|
|
continue;
|
|
if (!strncmp(buf, "msg", 3)) {
|
|
if (forbit)
|
|
while (fgets(buf, sizeof(buf), fp)) /* print message */
|
|
fputs(buf, logfile);
|
|
break;
|
|
} else if (sscanf(buf, "%s %d%*c%d\n", day, &start, &stop) != 3) {
|
|
LOG(llevDebug, "Warning: Incomplete line in permission file ignored.\n");
|
|
continue;
|
|
}
|
|
|
|
for (i = 0; i < 7; i++) {
|
|
if (!strncmp(buf, days[i], 3)
|
|
&& (tm->tm_wday == i)
|
|
&& (tm->tm_hour >= start)
|
|
&& (tm->tm_hour < stop))
|
|
forbit = 1;
|
|
}
|
|
}
|
|
|
|
close_and_delete(fp, comp);
|
|
|
|
return forbit;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
extern unsigned long todtick;
|
|
|
|
/**
|
|
* Collection of functions to call from time to time.
|
|
*
|
|
* Modified 2000-1-14 MSW to use the global pticks count to determine how
|
|
* often to do things. This will allow us to spred them out more often.
|
|
* I use prime numbers for the factor count - in that way, it is less likely
|
|
* these actions will fall on the same tick (compared to say using 500/2500/15000
|
|
* which would mean on that 15,000 tick count a whole bunch of stuff gets
|
|
* done). Of course, there can still be times where multiple specials are
|
|
* done on the same tick, but that will happen very infrequently
|
|
*
|
|
* I also think this code makes it easier to see how often we really are
|
|
* doing the various things.
|
|
*/
|
|
static void do_specials(void) {
|
|
|
|
#ifdef WATCHDOG
|
|
if (!(pticks%503))
|
|
watchdog();
|
|
#endif
|
|
|
|
if (!(pticks%PTICKS_PER_CLOCK))
|
|
tick_the_clock();
|
|
|
|
if (!(pticks%509))
|
|
flush_old_maps(); /* Clears the tmp-files of maps which have reset */
|
|
|
|
if (!(pticks%2503))
|
|
fix_weight(); /* Hack to fix weightproblems caused by bugs */
|
|
|
|
if (!(pticks%2521))
|
|
metaserver_update(); /* 2500 ticks is about 5 minutes */
|
|
|
|
if (!(pticks%5003))
|
|
write_book_archive();
|
|
|
|
if (!(pticks%5009))
|
|
clean_friendly_list();
|
|
|
|
if (!(pticks%5011))
|
|
obsolete_parties();
|
|
|
|
if (!(pticks%12503))
|
|
fix_luck();
|
|
}
|
|
|
|
/**
|
|
* Server main function.
|
|
*
|
|
* @param argc
|
|
* length of argv.
|
|
* @param argv
|
|
* command-line options.
|
|
* @return
|
|
* 0.
|
|
*/
|
|
int server_main(int argc, char **argv) {
|
|
#ifdef WIN32 /* ---win32 this sets the win32 from 0d0a to 0a handling */
|
|
_fmode = _O_BINARY;
|
|
bRunning = 1;
|
|
#endif
|
|
|
|
#ifndef WIN32
|
|
/* Here we check that we aren't root or suid */
|
|
if (getuid() == 0 || geteuid() == 0) {
|
|
fputs("Don't run crossfire as root, it is unsupported.\n", stderr);
|
|
fputs("Instead run it as a normal unprivileged user.\n", stderr);
|
|
fputs("Aborting...\n", stderr);
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
#ifdef DEBUG_MALLOC_LEVEL
|
|
malloc_debug(DEBUG_MALLOC_LEVEL);
|
|
#endif
|
|
|
|
settings.argc = argc;
|
|
settings.argv = argv;
|
|
init(argc, argv);
|
|
initPlugins(); /* GROS - Init the Plugins */
|
|
#ifdef WIN32
|
|
while (bRunning) {
|
|
#else
|
|
for (;;) {
|
|
#endif
|
|
nroferrors = 0;
|
|
|
|
do_server();
|
|
process_events(); /* "do" something with objects with speed */
|
|
cftimer_process_timers(); /* Process the crossfire Timers */
|
|
/* Lauwenmark : Here we handle the CLOCK global event */
|
|
execute_global_event(EVENT_CLOCK);
|
|
check_active_maps(); /* Removes unused maps after a certain timeout */
|
|
do_specials(); /* Routines called from time to time. */
|
|
|
|
sleep_delta(); /* Sleep proper amount of time before next tick */
|
|
}
|
|
emergency_save(0);
|
|
cleanup();
|
|
return 0;
|
|
}
|