/* core/gameserver.h This file is part of the Osirion project and is distributed under the terms of the GNU General Public License version 2 */ #include #include #include "auxiliary/functions.h" #include "core/application.h" #include "core/cvar.h" #include "core/func.h" #include "core/gameserver.h" #include "core/loader.h" #include "core/parser.h" #include "core/physics.h" #include "core/range.h" #include "core/netserver.h" #include "filesystem/filesystem.h" #include "sys/sys.h" namespace core { Player *console_find_player(std::string const & target) { Player *targetplayer = server()->find_player(target); if (!targetplayer) { con_print << "^BPlayer '" + target + "^B' not found"; } return targetplayer; } void func_who(std::string const &args) { server()->list_players(); } void func_time(std::string const &args) { using namespace std; // system time int year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0, milliseconds = 0; sys::get_localtime(year, month, day, hour, min, sec, milliseconds); con_print << "Local time: " << std::setw(4) << std::setfill('0') << year << "-" << std::setw(2) << std::setfill('0') << month << "-" << std::setw(2) << std::setfill('0') << day << " " << std::setw(2) << hour << ":" << std::setw(2) << min << ":" << std::setw(2) << sec << " " ; // uptime float uptime = core::game()->time(); const int uptime_days = (int) floorf(uptime / (24.0f * 3600.0f)); uptime -= uptime_days * 24.0f * 3600.0f; const int uptime_hours = (int) floorf(uptime / 3600.0f); uptime -= uptime_hours * 3600.0f; const int uptime_minutes = (int) floorf(uptime / 60.0f); uptime -= uptime_minutes * 60.0f; const int uptime_seconds = (int) floorf(uptime); con_print << "Uptime: "; if (uptime_days > 0) { con_print << uptime_days << " " << aux::plural("day", uptime_days) << " "; } con_print << std::setfill('0') << std::setw(2) << uptime_hours << ":" << std::setfill('0') << std::setw(2) << uptime_minutes << ":" << std::setfill('0') << std::setw(2) << uptime_seconds << std::endl; } void func_mute(std::string const &args) { Player *targetplayer = 0; if (!(targetplayer = console_find_player(args))) return; if (targetplayer->mute()) { con_print << "^BPlayer " + targetplayer->name() + "^B has already been muted"; return; } targetplayer->player_mute = true; server()->broadcast("^B" + targetplayer->name() + "^B has been muted", targetplayer); targetplayer->send("^WYou have been muted"); } void func_unmute(std::string const &args) { Player *targetplayer = 0; if (!(targetplayer = console_find_player(args))) return; if (!targetplayer->mute()) { con_print << "^BPlayer " + targetplayer->name() + "^B has not been muted"; return; } targetplayer->player_mute = false; server()->broadcast("^B" + targetplayer->name() + "^B has been unmuted", targetplayer); targetplayer->send("^WYou have been unmuted"); } void func_kick(std::string const &args) { std::stringstream str(args); std::string name; if (str >> name) { Player *targetplayer = 0; if (!(targetplayer = console_find_player(name))) return; std::string reason; if (args.size() > name.size() + 1) reason.assign(args.substr(name.size() + 1)); else reason.assign("forcefully removed."); server()->kick(targetplayer, reason); } } void func_grant_rcon(std::string const &args) { Player *targetplayer = 0; if (!(targetplayer = console_find_player(args))) return; } void func_revoke_rcon(std::string const &args) { Player *targetplayer = 0; if (!(targetplayer = console_find_player(args))) return; } GameServer *GameServer::server_instance = 0; GameServer::GameServer() : GameInterface() { con_print << "^BInitializing game server...\n"; server_instance = this; server_network = 0; server_previoustime = 0; server_maxplayerid = 1; if (Cvar::sv_dedicated->value() || Cvar::sv_private->value()) { server_mode = MultiPlayer; } else { server_mode = SinglePlayer; } // create the default infotype for zones Zone::set_infotype(new InfoType("zone")); // create the default infotype for entities Entity::set_infotype(new InfoType("entity")); Physics::init(); server_module = Loader::init(); if (!server_module) { con_error << "No module loaded.\n"; abort(); return; } if (!server_module->running()) { con_error << "Could not initialize module '" << server_module->name() << "'\n"; abort(); return; } set_interactive(server_module->interactive()); if (interactive()) { //FIXME interferes with command line because of cmd.exec load_config(); } else { server_mode = SinglePlayer; } // set the name of the game core::Cvar::set("g_name", server_module->name().c_str(), core::Cvar::Game | core::Cvar::ReadOnly); con_print << " module '^B" << server_module->name() << "^N'\n"; if (mode() == MultiPlayer) { server_network = new NetServer(Cvar::net_host->str(), (unsigned int) Cvar::net_port->value()); if (!server_network->valid()) { delete server_network; server_network = 0; con_error << "Could not initialize network server\n"; abort(); return; } } else { server_network = 0; } Func *func = 0; /* -- admin functions -- */ // FIXME these have to become shared functions and check the player's admin level func = Func::add("mute", func_mute); func->set_info("[player] mute a player"); func = Func::add("unmute", func_unmute); func->set_info("[player] unmute a player"); func = Func::add("kick", func_kick); func->set_info("[player] [reason] kick a player from the server"); /* -- shared functions --*/ func = Func::add("time", func_time, true); func->set_info("get the server uptime and current server localtime"); func = Func::add("who", func_who, true); func->set_info("get a list of connected players"); if (!Cvar::sv_dedicated->value()) { player_connect(localplayer()); } set_timestamp(server_timer.timestamp()); server_previoustime = timestamp(); set_running(true); } GameServer::~GameServer() { set_running(false); con_print << "^BShutting down game server...\n"; if (server_network) { delete server_network; server_network = 0; } if (server_module->interactive()) save_config(); // clear game data clear(); if (server_module) { if (!Cvar::sv_dedicated->value()) player_disconnect(localplayer()); delete server_module; server_module = 0; } Func::remove("kick"); Func::remove("mute"); Func::remove("unmute"); /* Func::remove("grant_rcon"); Func::remove("revoke_rcon"); */ Func::remove("time"); Func::remove("who"); Physics::done(); Entity::set_infotype(0); server_instance = 0; } Info *GameServer::request_info(unsigned int id) { return Info::find(id); } Inventory *GameServer::request_inventory(Entity *entity) { return (entity ? entity->inventory() : 0); } // a player sends a global chat message void GameServer::shout(Player *player, std::string const &message) { if (!message.size()) return; if (player->mute()) { player->send("^WYou have been muted"); return; } // TODO strip color codes if requested std::string notification("^B"); notification.append(player->name()); notification.append("^B: "); notification.append(message); broadcast(Message::Public, notification); } // a player sends a local chat message void GameServer::say(Player *player, std::string const &message) { if (!message.size()) return; if(!player->zone()) return; if (player->mute()) { player->send("^WYou have been muted"); return; } // TODO strip color codes if requested std::string notification("^B"); notification.append(player->name()); notification.append("^B:^N "); notification.append(message); broadcast(player->zone(), notification); } // a player send a prvate mesage to another player void GameServer::private_message(Player *player, std::string const &args) { if (!args.size()) return; if (player->mute()) { player->send("^WYou have been muted"); return; } std::string target; std::stringstream argstr(args); if (!(argstr >> target)) { return; } core::Player *targetplayer = core::server()->find_player(target); if (!targetplayer) { player->send("^BPlayer " + target + "^B not found"); return; } std::string text(args.substr(target.size())); message(player, Message::Private, "^FTo ^B" + targetplayer->name() + "^F:" + text); message(targetplayer, Message::Private, "^FFrom ^B" + player->name() + "^F:" + text); } // FIXME kicked by void GameServer::kick(Player *player, std::string const &reason) { if (!server_network) { con_print << "Not running a networked server" << std::endl; return; } NetClient *client = player->client(); if (client) { broadcast("^B" + player->name() + "^B has been kicked: " + reason, player); server_network->send_message(client, Message::Info, "^WYou have been kicked: " + reason); server_network->send_disconnect(client); } else { con_print << "Network client not found" << std::endl; } } // server sends a message on a specified channel to a single player void GameServer::message(Player *player, const Message::Channel channel, const std::string text) { if (!text.size()) { return; } NetClient *client = player->client(); if (client) { // this player is a network client, send message over network server_network->send_message(client, channel, text); } else if (player == localplayer()) { // local player, send message to the local application application()->notify_message(channel, text); } } // server broadcasts a message on a specified channel to all players void GameServer::broadcast(const Message::Channel channel, const std::string text, Player *ignore_player) { if (!text.size()) return; for (Players::iterator it = players().begin(); it != players().end(); ++it) { Player *player = (*it); NetClient *client = player->client(); if (player != ignore_player) { if (client) { // this player is a network client, send message over network server_network->send_message(client, channel, text); } else if (player == localplayer()) { // local player, send message to the local application application()->notify_message(channel, text); } } } // console is not in the player list if (Cvar::sv_dedicated->value()) { localplayer()->message(channel, text); } } // server broadcasts an "info" message to all players void GameServer::broadcast(const std::string text, Player *ignore_player) { broadcast(Message::Info, text, ignore_player); } // server broadcasts a message to all players in a particular zone void GameServer::broadcast(Zone *zone, std::string const text, Player *ignore_player) { if (!text.size()) return; for (Players::iterator it = players().begin(); it != players().end(); ++it) { Player *player = (*it); if ((player->zone() == zone) && (player != ignore_player)) { Player *player = (*it); player->message(Message::Local, text); } } // console is not in the player list if (Cvar::sv_dedicated->value()) { std::string notification(zone->label()); notification += ' '; notification.append(text); localplayer()->message(Message::Local, notification); } } // server broadcasts a sound event to all players void GameServer::broadcast_sound(const std::string name, Player *ignore_player) { if (!name.size()) return; for (Players::iterator it = players().begin(); it != players().end(); ++it) { Player *player = (*it); if (player != ignore_player) { Player *player = (*it); player->sound(name); } } } // server sends a messagebox to a single player void GameServer::messagebox(Player *player, const std::string & text, const std::string &label1, const std::string command1, const std::string &label2, const std::string command2) { if (!text.size()) { return; } NetClient *client = player->client(); if (client) { // this player is a network client, send message over network server_network->send_messagebox(client, text, label1, command1, label2, command2); } else if (player == localplayer()) { // local player, send message to the local application application()->notify_messagebox(text, label1, command1, label2, command2); } } // execute a command for a remote player void GameServer::exec(Player *player, std::string const & cmdline) { std::string command; std::stringstream cmdstream; cmdstream.str(cmdline); cmdstream >> command; Func *function = Func::find(command); if (function) { std::string args; if (cmdline.size() > command.size() + 1) args.assign(cmdline.substr(command.size() + 1)); if ((function ->flags() & Func::Game) == Func::Game) { if ((function ->flags() & Func::Target) == Func::Target) { unsigned int id = 0; if ((cmdstream >> id)) { Entity *entity = Entity::find(id); if (entity) function->exec(player, entity); } } else { function->exec(player, args); } } else if ((function->flags() & Func::Shared) == Func::Shared) { // enable rcon buffering console()->set_rcon(true); function->exec(args); while (console()->rconbuf().size()) { player->message(Message::RCon, (*console()->rconbuf().begin())); console()->rconbuf().pop_front(); } // disable rcon buffering console()->set_rcon(false); } return; } std::string message("Unknown command '"); message.append(command); message.append("^N'"); player->send(message); } void GameServer::player_connect(Player *player) { if (Cvar::sv_dedicated->value() && (player == localplayer())) { return; } player->player_id = server_maxplayerid; server_maxplayerid++; // notify the game module server_module->player_connect(player); // manage player list game_players.push_back(player); set_playerlist_timestamp(server_timer.timestamp()); } void GameServer::player_disconnect(Player *player) { // notify the game module server_module->player_disconnect(player); // print a message std::string message("^B"); message.append(player->name()); message.append("^B disconnects."); broadcast(message, player); // clear all player assets player->clear_assets(); // manage player list std::list:: iterator it = game_players.begin(); while ((it != game_players.end()) && ((*it)->id() != player->id())) { ++it; } if (it != game_players.end()) { game_players.erase(it); } set_playerlist_timestamp(server_timer.timestamp()); } void GameServer::frame(const unsigned long timestamp) { if (error()) return; // process incoming network messages if (server_network) { server_network->receive(); if (server_network->error()) { abort(); return; } } if (localplayer()->dirty()) localplayer()->update_info(); /*if (!Cvar::sv_dedicated->value()) { update_clientstate(); }*/ if ((Cvar::sv_dedicated->value() || Cvar::sv_private->value())) { if (core::Cvar::sv_framerate->value()) { const unsigned long server_framelength = (unsigned long) ::floorf(1000.0f / core::Cvar::sv_framerate->value()); if (server_timer.timestamp() < server_previoustime + server_framelength) { return; } } } server_previoustime = this->timestamp(); set_timestamp(server_timer.timestamp()); if (server_network) { server_network->transmit(); if (server_network->error()) { abort(); return; } } const unsigned long elapsed = (this->timestamp() - server_previoustime); const unsigned long keepalive_timeout = (Cvar::sv_keepalive ? 1000 * (unsigned long) Cvar::sv_keepalive->value() : (unsigned long) 0 ); // run a physics frame Physics::frame(elapsed); // reset zone keepalive state for (Zone::Registry::iterator zit = Zone::registry().begin(); zit != Zone::registry().end(); ++zit) { Zone *zone= (*zit).second; // Note: zone->keepalive_run() is set to true by EntityControlable::frame() zone->set_keepalive_run(false); } // update zone keepalive state for (Players::iterator it = game_players.begin(); it != game_players.end(); ++it) { Entity *view = (*it)->view(); if (view && view->zone()) { // add player view to keepalive bounding box if (!view->zone()->keepalive_run()) { view->zone()->keepalive_box().assign(view->location()); view->zone()->set_keepalive_run(true); } else { view->zone()->keepalive_box().expand(view->location()); } } else { EntityControlable *controlable = (*it)->control(); if (controlable && controlable->zone()) { // add player controlable to keepalive bounding box if (!controlable->zone()->keepalive_run()) { controlable->zone()->keepalive_box().assign(controlable->location()); controlable->zone()->set_keepalive_run(true); } else { controlable->zone()->keepalive_box().expand(controlable->location()); } } } } // run entity game frames for (Entity::Registry::iterator it = Entity::registry().begin(); it != Entity::registry().end(); ++it) { Entity *entity = (*it).second; if (!entity->died()) { entity->frame(elapsed); } } // expand zone keepalive bounding box for (Zone::Registry::iterator zit = Zone::registry().begin(); zit != Zone::registry().end(); ++zit) { Zone *zone= (*zit).second; if (zone->keepalive_run()) { zone->keepalive_box().expand(range::fxdistance * 0.5f); } } // run upkeep frames for (Entity::Registry::iterator it = Entity::registry().begin(); it != Entity::registry().end(); ++it) { Entity *entity = (*it).second; Zone *zone = entity->zone(); if (zone && entity->has_flag(Entity::KeepAlive) && !entity->died()) { // reset timeout counter if the zone is active if (zone->keepalive_run() && zone->keepalive_box().inside(entity->location())) { entity->set_keepalive(this->timestamp()); } // run upkeep if the keepalive timeout has elapsed if (entity->keepalive() + keepalive_timeout < this->timestamp()) { entity->upkeep(this->timestamp()); } } } // run a frame on the module if (server_module) { if (server_module->running()) server_module->frame(elapsed); if (server_module->error()) { abort(); return; } } // engine state updates for each player for (Players::iterator it = game_players.begin(); it != game_players.end(); ++it) { Player *player = (*it); Entity *view = player->view(); EntityControlable *control = player->control(); // the player is viewing an entity if (view) { // the view has changed zone if (view->zone() != player->zone()) { if (control) { // player is docked at a jumping entity if (control->state() == Entity::Docked) { control->get_location().assign(view->location()); control->set_zone(view->zone()); player->set_zone(view->zone()); } else { player->set_zone(control->zone()); player->set_view(0); } control->set_dirty(); } else { // spectator following a jumping entity player->set_zone(view->zone()); } player->set_dirty(); } // view is to be deleted if (view->died()) { if (control) { // player is docked at deleted entity if (control->state() == Entity::Docked) { control->get_location().assign(view->location()); control->set_state(Entity::Normal); control->set_dirty(); } else { player->set_view(0); } } else { // spectator following a deleted entity player->set_view(0); } player->set_dirty(); } } } if (server_network) { // send network updates server_network->frame(this->timestamp()); } // remove deleted entities and mark remaining entities as updated for (Entity::Registry::iterator it = Entity::registry().begin(); it != Entity::registry().end();) { // remove deleted entities if ((*it).second->died()) { delete (*it).second; (*it).second = 0; Entity::registry().erase(it++); } else { (*it).second->clear_updates(); ++it; } } if (localplayer()->zonechange()) { application()->notify_zonechange(); localplayer()->set_zonechange(false); } } void GameServer::save_config() { std::string filename(filesystem::writedir()); filename.append("game.cfg"); std::ofstream ofs(filename.c_str()); if (!ofs.is_open()) { con_warn << "Could not write " << filename << std::endl; return; } con_print << " writing configuration to " << filename << std::endl; ofs << "# game.cfg - " << server_module->name() << " configuration" << std::endl; ofs << "# this file is automaticly generated" << std::endl; ofs << std::endl; for (Cvar::Registry::iterator it = Cvar::registry().begin(); it != Cvar::registry().end(); ++it) { if ((((*it).second->flags() & Cvar::Archive) == Cvar::Archive) && (((*it).second->flags() & Cvar::Game) == Cvar::Game)) { ofs << "# " << (*it).first << " " << (*it).second->info() << std::endl; ofs << "set " << (*it).first << " " << (*it).second->str() << std::endl; ofs << std::endl; } } ofs.close(); } void GameServer::load_config() { std::string filename(filesystem::writedir()); filename.append("game.cfg"); std::ifstream ifs(filename.c_str(), std::ifstream::in); if (!ifs.is_open()) { con_warn << "Could not read " << filename << std::endl; return; } con_print << " reading configuration from " << filename << std::endl; char line[MAXCMDSIZE]; while (ifs.getline(line, MAXCMDSIZE - 1)) { if (line[0] && line[0] != '#' && line[0] != ';') CommandBuffer::exec(line); } } }