/* * Copyright (c) 2002, Stefan Farfeleder * $Id: server.cc,v 1.7 2002/09/19 10:42:02 stefan Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "clock.h" #include "collision.h" #include "conffile.h" #include "exception.h" #include "game.h" #include "item_s.h" #include "network.h" #include "object_s.h" #include "obstacle_s.h" #include "parse.h" #include "person_s.h" #include "server_util.h" #include "weapon_s.h" using std::queue; using std::string; using std::vector; using std::ostringstream; using namespace JFK::server; const double PI = 3.14159265358979323846; static volatile sig_atomic_t sig_caught = 0; /* per client data */ struct client_s { client_s() : id(-1), name_rcvd(false) {} client_s(objhandle oh) : id(-1), p(oh), name_rcvd(false) {} int id; /* id of this client for network_server */ objpair p; /* pointer to its person object pair */ bool name_rcvd; /* we're waiting for a name to be sent before doing anything */ }; class game { public: game(); ~game(); void main_loop(); private: void load_level(const char* levelfile); void handle_logins(); void handle_requests(); void handle_logouts(); void handle_objects(); void reappear(); objhandle new_boundingbox(); objhandle new_person(); void broadcast_new_object(const objhandle o, int to); unsigned long next_id() { return last_id++; } struct reap { double reappear_time; objhandle obj; }; JFK::network_server* ns; vector obj; vector client; JFK::clock clock; unsigned long last_id; /* used by next_id() */ string levelname; /* file to read level from */ double width; /* width of the map */ double height; /* height of the map */ double total_time; /* passed time since server was started */ queue reap_list; /* objects to reappear */ JFK::conffile cf; /* configuration file object */ }; static const JFK::variable vars[] = { { "port", JFK::DEFAULT_PORT, NULL }, { "level", NULL, NULL }, }; #if !defined(_WIN32) #define CONFFILE "~/.jfkserverrc" #else #define CONFFILE "jfkserver.ini" #endif game::game() : ns(NULL), last_id(0), width(1280), height(960), total_time(0), cf(vars, sizeof vars / sizeof *vars, CONFFILE) { cf.read(); ns = new JFK::network_server(cf.getvar("port")); load_level(cf.getvar("level").c_str()); } game::~game() { delete ns; } void game::load_level(const char* levelfile) { lvlhandler handler[] = { { "obstacle", NULL, &new_obstacle, 0.0, 0.0 }, { "item", NULL, &new_item, 0.0, 0.0 }, { "weapon", NULL, &new_weapon, 0.0, 0.0 }, { "width", &width, NULL, 800.0, 8000.0 }, { "height", &height, NULL, 600.0, 6000.0 }, }; const size_t handler_n = sizeof handler / sizeof *handler; string line; std::ifstream lstream(levelfile); if (!lstream.is_open()) { throw JFK::exception(string("can't open '") + levelfile + '\''); } levelname = levelfile; for (size_t linenr = 1; std::getline(lstream, line); linenr++) { ostringstream err; /* ignore empty or commented lines */ if (line.length() == 0 || line[0] == '#') continue; lvlhandler* h = std::find(handler, handler + handler_n, line); /* not found */ if (h == handler + handler_n) { err << "error at line " << linenr; throw JFK::exception(err.str()); } /* = */ if (h->attr != NULL) { size_t equalsign = line.find('=', h->name.length()); if (equalsign == h->name.npos) { err << "error at line " << linenr; throw JFK::exception(err.str()); } *h->attr = JFK::strtodbl(line.substr(equalsign + 1)); if (h->min > *h->attr || *h->attr > h->max) { err << h->name << " must be in [" << h->min << ", " << h->max << "]"; throw JFK::exception(err.str()); } } /* */ else { objhandle o = h->cf(line.substr(h->name.length())); o->id = next_id(); obj.push_back(o); } } /* add the bounding box object */ obj.push_back(new_boundingbox()); } objhandle game::new_boundingbox() { double dx = width / 2; double dy = height / 2; obstacle* ret = new obstacle(JFK::OBS_BOUNDINGBOX); ret->x = dx; ret->y = dy; ret->p.push_back(point(-dx, -dy)); ret->p.push_back(point( dx, -dy)); ret->p.push_back(point( dx, dy)); ret->p.push_back(point(-dx, dy)); ret->id = next_id(); return ret; } objhandle game::new_person() { person* ret = new person(""); ret->id = next_id(); double a = PI * std::rand() / (RAND_MAX + 1.0); ret->dx = cos(a); ret->dy = sin(a); size_t i; /* search a position not in an obstacle */ do { ret->x = (width - 2 * ret->r) * std::rand() / (RAND_MAX + 1.0) + ret->r; ret->y = (height - 2 * ret->r) * std::rand() / (RAND_MAX + 1.0) + ret->r; for (i = 0; i < obj.size(); i++) { if (obj[i]->classname() != "obstacle") continue; obstacle* o = dynamic_cast(obj[i].operator->()); if (o->type != JFK::OBS_BOUNDINGBOX && in_obstacle(ret, o)) break; } } while (i < obj.size()); return ret; } /* * Tell the client 'to' about the new object 'o'. */ void game::broadcast_new_object(const objhandle o, int to) { ostringstream objstr; objstr << JFK::CMD_NEW << ' ' << o->classname() << ' ' << o->id << ' ' << o->tostring(); ns->send(objstr.str(), to); } /* * A 'person' object is created and the client is appended to the 'client' * array. The former one won't be added to 'obj' until its name arrived! */ void game::handle_logins() { string newclientname; int newclientid; while (ns->new_client(&newclientname, &newclientid)) { std::cout << "new client " << newclientid << " (" << newclientname << ")" << std::endl; client.push_back(new_person()); client.back().id = newclientid; } } void game::handle_requests() { for (size_t i = 0; i < client.size(); i++) { string msg; /* save old values */ client[i].p.do_backup(); person* p = dynamic_cast(client[i].p.operator->()); /* ok, so this isn't a good place for it, but it works */ if (p->blocked) { p->blocked = false; p->update = true; } while (ns->receive(&msg, client[i].id)) { std::cout << "received (from " << client[i].id << "): " << msg << std::endl; /* The very first line from the client is treated as its name */ if (!client[i].name_rcvd) { p->name = msg; if (!name_ok(p->name)) { std::cerr << "bad name '" << p->name << "' from " << client[i].id << std::endl; ns->remove_client(client[i].id); client[i] = client.back(); client.resize(client.size() - 1); p->suicide = true; break; } client[i].name_rcvd = true; /* send description of the level */ ostringstream levelstr; levelstr << " width=" << width << " height=" << height; ns->send(levelstr.str(), client[i].id); /* send the new person object to all clients */ broadcast_new_object(p, JFK::TO_ALL); ostringstream personstr; personstr << JFK::CMD_NEW << " ownperson " << p->id << ' ' << p->tostring(); ns->send(personstr.str(), client[i].id); /* include the new client into TO_ALL */ ns->enable_client(client[i].id); /* send all (other) objects to the new client */ for (size_t j = 0; j < obj.size(); j++) { broadcast_new_object(obj[j], client[i].id); } /* notify client that all objects have been transferred */ ns->send(JFK::CMD_LEVEL_TRANSFERRED, client[i].id); obj.push_back(client[i].p); continue; } if (msg == JFK::CMD_MOVE_FORWARD) p->v = 2; else if (msg == JFK::CMD_MOVE_BACK) p->v = -1; else if (msg == JFK::CMD_MOVE_STOP) p->v = 0; else if (msg == JFK::CMD_TURN_LEFT) p->w = -0.06; else if (msg == JFK::CMD_TURN_RIGHT) p->w = 0.06; else if (msg == JFK::CMD_TURN_STOP) p->w = 0; else if (msg == JFK::CMD_FIRE_START) p->firing = true; else if (msg == JFK::CMD_FIRE_STOP) p->firing = false; else if (msg == JFK::CMD_CHANGE_WEAPON) { /* this loop assumes that we have at least one weapon */ do { p->curweapon = (p->curweapon + 1) % JFK::MAX_WEAPON; } while (!p->hasweapon[p->curweapon]); } else if (msg.COMPARE(0, JFK::CMD_MSG.length(), JFK::CMD_MSG) == 0) { /* position in msg where the actual text begins */ size_t msgbegin = JFK::CMD_MSG.length() + 1; size_t colonpos = msg.find(':', msgbegin); int to = JFK::TO_ALL; if (colonpos != msg.npos) { string name = msg.substr(msgbegin, colonpos - msgbegin); /* search if we have a client with that name */ for (size_t j = 0; j < client.size(); j++) { person* t = dynamic_cast (client[j].p.operator->()); if (name == t->name) { msgbegin = colonpos + 1; to = client[j].id; break; } } } /* rebuild the message with the name of the sender */ msg = JFK::CMD_MSG + ' ' + p->name + ':' + msg.substr(msgbegin); ns->send(msg, to); } else { std::cerr << "bad request from " << client[i].id << std::endl; ns->remove_client(client[i].id); client[i] = client.back(); client.resize(client.size() - 1); p->suicide = true; /* client[i] is now possibly another client, so try it again */ i--; break; } p->update = true; } } } void game::handle_logouts() { int clientid; while (ns->lost_client(&clientid)) { std::cout << "lost client " << clientid << std::endl; size_t i; size_t old_size = client.size(); /* search client with this id - could be more efficient */ for (i = 0; i < old_size; i++) if (client[i].id == clientid) { client[i].p->suicide = true; client[i] = client.back(); client.resize(client.size() - 1); break; } if (i == old_size) throw JFK::exception("id unknown"); } } void game::handle_objects() { unsigned long timepassed = clock.diff(); total_time += timepassed * JFK::GAME_SPEED; reappear(); /* First save the old values of all objects */ for (size_t i = 0; i < obj.size(); i++) { /* don't overwrite backups from persons who received a request this round */ if (!obj[i]->update) obj[i].do_backup(); } /* check collisions for each pair of objects */ for (size_t i = 0; i < obj.size(); i++) { for (size_t j = i + 1; j < obj.size(); j++) { object* obj_i = obj[i].operator->(); object* obj_j = obj[j].operator->(); if (collision(obj_i, obj_j, timepassed * JFK::GAME_SPEED)) { obj_i->delegate_collision(obj_j); obj_j->delegate_collision(obj_i); } } } /* do action(), move(), remove objects that committed suicide and send * updates to the clients */ for (size_t i = 0; i < obj.size(); i++) { objhandle newobj = obj[i]->action(timepassed * JFK::GAME_SPEED); if (newobj) { newobj->id = next_id(); obj.push_back(newobj); broadcast_new_object(newobj, JFK::TO_ALL); } if (obj[i]->suicide) { /* tell the clients to remove this object */ ostringstream delstr; delstr << JFK::CMD_DELETE << ' ' << obj[i]->id; ns->send(delstr.str(), JFK::TO_ALL); /* append this object to reap_list if it wants to reappear */ if (obj[i]->reappear > 0) { reap r = { total_time + obj[i]->reappear, obj[i] }; reap_list.push(r); } if (obj[i]->classname() == "person") { person* oldp = dynamic_cast(obj[i].operator->()); /* create a grave */ objhandle grave = new item(JFK::GRAVE); grave->x = oldp->x; grave->y = oldp->y; grave->id = next_id(); obj.push_back(grave); broadcast_new_object(grave, JFK::TO_ALL); size_t j; /* create weapons */ for (j = 0; j < JFK::MAX_WEAPON; j++) { if (oldp->hasweapon[j] && j != JFK::WATERGUN) { double dx = 40.0 * std::rand() / RAND_MAX - 20; double dy = 40.0 * std::rand() / RAND_MAX - 20; objhandle w = new weapon(j); w->x = oldp->x + dx; w->y = oldp->y + dy; w->id = next_id(); obj.push_back(w); broadcast_new_object(w, JFK::TO_ALL); } } /* search the client of this person */ for (j = 0; j < client.size(); j++) { if (client[j].p->id == obj[i]->id) break; } /* client still connected, must create a new person */ if (j != client.size()) { objhandle p = new_person(); person* newp = dynamic_cast(p.operator->()); /* copy the name and the score, increase the deaths */ newp->name = oldp->name; newp->score = oldp->score; newp->deaths = oldp->deaths + 1; /* all other clients get "person" ... */ ns->disable_client(client[j].id); broadcast_new_object(newp, JFK::TO_ALL); ns->enable_client(client[j].id); /* ... but this one "ownperson" */ ostringstream personstr; personstr << JFK::CMD_NEW << " ownperson " << newp->id << ' ' << newp->tostring(); ns->send(personstr.str(), client[j].id); obj.push_back(p); client[j].p = obj.back(); } } /* remove this objpair from the 'obj' vector */ obj[i] = obj.back(); obj.resize(obj.size() - 1); i--; continue; } obj[i]->move(timepassed * JFK::GAME_SPEED); string diff = obj[i]->diffstring(obj[i].get_backup()); if (diff.length() > 0) { ostringstream updstr; updstr << JFK::CMD_UPDATE << ' ' << obj[i]->id << ' ' << diff; ns->send(updstr.str(), JFK::TO_ALL); } obj[i]->update = false; } } void game::reappear() { /* look for objects whose reappear_time has passed and reactivate them */ while (!reap_list.empty() && reap_list.front().reappear_time < total_time) { obj.push_back(reap_list.front().obj); /* XXX: obj.back() wants new id? */ obj.back()->suicide = false; /* obj.back() must /not/ be a JFK::person */ broadcast_new_object(obj.back(), JFK::TO_ALL); reap_list.pop(); } } void game::main_loop() { clock.start(); while (sig_caught == 0) { ns->dispatch(); handle_logins(); handle_requests(); handle_logouts(); handle_objects(); clock.delay(1000); } } static void signal_handler(int sig) { sig_caught = sig; } int main(int argc, char* argv[]) { const char* progname = "jfkserver"; if (argc >= 1) progname = argv[0]; std::srand((unsigned)std::time(NULL)); /* catch SIGINT and SIGTERM */ signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); try { game g; g.main_loop(); std::cerr << progname << ": caught signal " << sig_caught << std::endl; } catch (const JFK::exception& e) { std::cerr << progname << ": " << e.msg() << std::endl; return EXIT_FAILURE; } return 0; }