/* * 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 #include #include #include #ifdef HAVE_DES_H #include #else # ifdef HAVE_CRYPT_H # include # endif #endif #ifndef __CEXTRACT__ #include #endif #ifdef HAVE_TIME_H #include #endif #ifndef WIN32 # include # include #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; }