//====================================== // World.C // // ZNibbles World - the real class // // ZNibbles // Vincent Mallet 1997 //====================================== // $Id: World.C,v 1.11 1999/05/12 01:45:46 vmallet Exp $ /* ZNibbles - a small multiplayer game * Copyright (C) 1997, 1998, 1999 Vincent Mallet - vmallet@enst.fr * * 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. */ /* * Code may look somewhat complex. This is partly due to * the fact that this class is used both by clients and * servers. Client's World reflect server's world. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include "World.H" #include "BaseInterface.H" #include "Object.H" #include "LongObject.H" #include "Movable.H" #include "Nibble.H" #include "Player.H" #include "Trame.H" //================ // constructor //================ World:: World(int num) : Base(*this), number(num), playcycle(0), x_dim(0), y_dim(0), players_dead_cycle(7), maxplayers(0), maxobjects(0), paused(0), cycle_trame(4096), special(0), longest_worm(0), nbplayers(0), nbobjects(0), debug(0) { // default is no interface interface = new VoidInterface(*this); // real construction takes place in server_make() } //=================== // destructor //=================== World:: ~World() { // here we add if (1) to avoid bogus compilers problems if (1) for (Pix p = players.last(); p; players.prev(p)) delete players(p); for (Pix p = objects.last(); p; objects.prev(p)) delete objects(p); } // Actual construction of the world, server side. void World::server_make(int x, int y, int pmax, int omax) { x_dim = x; y_dim = y; maxplayers = pmax; maxobjects = omax; map.make(x_dim, y_dim); LongObject& Lo = * new LongObject(*this); Lo.auto_position(map); // test Lo.grow(D_DOWN); Lo.grow(D_RIGHT); Lo.grow(D_RIGHT); Lo.grow(D_RIGHT); Lo.add_type(map); // test LongObject& Lo2 = * new LongObject(*this); Lo2.auto_position(map); // test Lo2.grow(D_LEFT); Lo2.grow(D_LEFT); Lo2.grow(D_DOWN); Lo2.grow(D_DOWN); Lo2.grow(D_DOWN); Lo2.grow(D_DOWN); Lo2.add_type(map); // test add_object(Lo); add_object(Lo2); } //===================== // Dealing with objects //===================== void World::add_object(_Object& obj) { objects.append(&obj); nbobjects++; } _Object& World::lookup_object(int obj_id) { Pix p = lookup_object0(obj_id); if (!p) { // here it's really bad. Have to raise an exception ! if (debug) std::cerr << "World::lookup_object(): invalid object id=" << obj_id \ << " !!!" << std::endl; return * objects.front(); /// @@ NNNNOOOOOONNNNNNNNNNNNNNNNNNNNNNNN! } return *objects(p); } void World::update_objects() { for (Pix p = objects.first(); p; objects.next(p)) if ((objects(p)->instance_of(CL_MOVABLE))) { Movable& mv = *(Movable *) objects(p); if (mv.player_id) mv.player = & lookup_player(mv.player_id); } } _Object * World::read_new_object(Trame &t) { _Object *obj = NULL; switch(t.peek_char()) { case NEW_WORM: break; case NEW_NIBBLE: obj = new Nibble(*this, NT_STANDARD); break; case NEW_LONGOBJ: obj = new LongObject(*this); break; case NEW_MOVABLE: obj = new Movable(*this); break; default: if (debug) std::cerr << "World::read_new_object(): unknown object!\n"; break; } if (obj) { obj->read_description(t); add_object(*obj); } return obj; } // remove a specified object from world void World::remove_object(int obj_id) { Pix p = lookup_object0(obj_id); if (p) remove_object0(p); else { if (debug) std::cerr << "remove_object(): invalid object_id! id=" << obj_id << std::endl; } } // Removes all dying worms from world void World::remove_dead() { for (Pix p = objects.first(); p; ) { if (objects(p)->dying) remove_object0(p); // advance to next in list if (p != NULL) objects.next(p); else p = objects.first(); } } // ===================================== // Dealing with players // ===================================== void World::add_player(Player& p) { players.append(&p); nbplayers++; } Player& World::lookup_player(int player_id) { Pix p; for (p = players.first(); p; players.next(p)) if (players(p)->id == player_id) break; if (!p) { // la, c'est tres grave. Faut faire une exception! if (debug) std::cerr << "World::lookup_player(): invalid player id=" << player_id \ << " !!!" << std::endl; return * new Player(*this); // <- en attendant mieux. } return *players(p); } void World::read_new_player(Trame &t) { Player& p = * new Player(*this); p.read_description(t); add_player(p); interface->add_player(p); if (p.get_number() == _hack_own_player_number) interface->set_own_player(p); } void World::server_add_player(int socknum) { /* unlimited number of players! if (nbplayers >= maxplayers) { close(socknum); std::cerr << "Too many players ! \n"; return; } */ Trame trame; trame.set_timeout(5000); if (-1 == trame.receive_from(socknum)) { if (debug) std::cerr << "World::server_add_player(): cannot read welcome packet " \ "from client" << std::endl; close(socknum); return; } if (trame.peek_char() != JOIN_GAME) { if (debug) std::cerr << "World::server_add_player(): Should have got JOIN_GAME ( " << (int) JOIN_GAME << "), got " << (int) trame.peek_char() << std::endl; return; } trame.get_char(); Player& p = * new Player(*this, socknum); p.set_number(first_free_player()); p.set_name(trame.get_string()); p.set_twokey(trame.get_byte()); send_description(p.socket_number); add_player(p); trame.reset(); trame.put_char(CHANGE_NOTIFY); p.add_description(trame); send_to_all(trame); } // add a player (server side), using the same socket as another // player socket. // @@ find a better void World::server_add_player_other(int socknum, Trame& trame, Trame& cycle) { Trame trameZ; trameZ.set_timeout(1); if (debug) std::cout << "World::server_add_player_other(): adding a subplayer" << std::endl; Player& p = * new Player(*this, socknum); p.set_dependent(1); // TRUE p.set_number(first_free_player()); p.set_name(trame.get_string()); p.set_twokey(trame.get_byte()); add_player(p); trameZ.reset(); trameZ.put_char(CHANGE_NOTIFY); p.add_description(trameZ); send_to_all(trameZ); trameZ.reset(); trameZ.put_char(YOUR_OTHER_PLAYER); trameZ.put_int(p.get_id()); trameZ.send_to(p.get_socket_number()); } void World::remove_player(int player_id) { Pix p; for (p = players.first(); p; players.next(p)) if (players(p)->id == player_id) break; if (!p) { // la, c'est tres grave. Faut faire une exception! if (debug) std::cerr << "World::remove_player(): invalid player id=" << player_id \ << " !!!" << std::endl; return; } if (players(p)->worm_id) { _Object &obj = lookup_object(players(p)->worm_id); obj.dying = 1; } delete players(p); players.del(p); nbplayers--; } void World::server_remove_player(Player *player, int reason) { //@@ this can be optimized (ie, useless if no dependencies) if (!player->is_dependent()) { for (Pix p = players.first(); p; players.next(p)) { if (players(p)->is_dependent() && players(p)->get_socket_number() == player->get_socket_number()) { cycle_trame.put_char(PLAYER_QUIT); cycle_trame.put_int(players(p)->get_id()); cycle_trame.put_char(reason); // reason remove_player(players(p)->get_id()); p = players.first(); } } } cycle_trame.put_char(PLAYER_QUIT); cycle_trame.put_int(player->get_id()); cycle_trame.put_char(reason); // reason remove_player(player->get_id()); } //======================================== // Other world related stuff //======================================== // Called each cycle by the server void World::server_cycle() { playcycle++; // prepare the data frame for this cycle cycle_trame.reset(); cycle_trame.put_char(CYCLE_NOTIFY); //en attendant, puisque on ne s'en sert pas: // cycle_trame.put_short(playcycle); // clean up dead corpses remove_dead(); // update map build_maptype(); if (!paused) { // et on cycle une premiere fois // here we add if(1) to avoid bogus compilers problems if (1) for (Pix p = objects.first(); p; objects.next(p)) objects(p)->server_cycle(); // update map build_maptype(); // check for collisions // here we add if(1) to avoid bogus compilers problems if (1) for (Pix p = objects.first(); p; objects.next(p)) objects(p)->check_collide(); // on fini par cycler les joueurs. for (Pix p = players.first(); p; players.next(p)) players(p)->server_cycle(); // notre petite vie... own_cycle(); } } // Called each cycle by clients void World::cycle() { playcycle++; // clean-up.. remove_dead(); // update map build_maptype(); if (!paused) { // here we add if(1) to avoid bogus compilers problems if (1) for (Pix p = objects.first(); p; objects.next(p)) objects(p)->cycle(); build_maptype(); for (Pix p = objects.first(); p; objects.next(p)) objects(p)->check_collide(); } } void World::own_cycle() { if (!special--) { special = 3000; // int stype = (rand() % (NT_MAXSPECIAL - 1)) + 1; int stype = NT_CUT; Nibble& nib = * new Nibble(*this, stype, rand()%15 + 1); nib.auto_position(map); nib.add_type(map); nib.add_description(cycle_trame); add_object(nib); } // every six cycles we look if we need a new nibble // @@ 6 should be a parameter somewhere if (! (playcycle % 6)) { int nbnib = 0; for (Pix p = objects.first(); p; objects.next(p)) if (objects(p)->instance_of(CL_NIBBLE)) nbnib++; // 20, 15: a mettre en parametre if (nbnib < 20) { Nibble& nib = * new Nibble(*this, NT_STANDARD, rand()%15 + 1); nib.auto_position(map); nib.add_type(map); nib.add_description(cycle_trame); add_object(nib); } } // every 26 cycles we look if we need a new worm // @@ 26 should be a parameter somewhere if (!_server_no_computer && ! (playcycle % 26)) { int nbworm = 0; for (Pix p = objects.first(); p; objects.next(p)) if (objects(p)->instance_of(CL_MOVABLE) && ((Movable *) objects(p))->player_id == 0) nbworm++; // std::cout << "actually I have " << nbworm << " own worms..." << std::endl; // @@ 2: a mettre en parametre if (nbworm < 2) { // std::cout << "adding one.. heehe" << std::endl; Movable& worm = * new Movable(*this); worm.auto_position(map); worm.add_type(map); worm.full_length = rand() % 15 + 1; worm.add_description(cycle_trame); add_object(worm); } } } // Send complete world description to client // on specified socket (this is for server) void World::send_description(int socknum) { Trame t(4096); t.put_char(WORLD_DESC); t.put_int(id); t.put_char(number); t.put_short(playcycle); t.put_short(x_dim); t.put_short(y_dim); t.put_short(maxplayers); t.put_short(maxobjects); t.put_short(nbplayers); t.put_short(nbobjects); t.put_short(paused); t.put_short(longest_worm); // here we add if(1) to avoid bogus compilers problems if (1) for (Pix p = players.first(); p; players.next(p)) players(p)->add_description(t); for (Pix p = objects.first(); p; objects.next(p)) objects(p)->add_description(t); t.send_to(socknum); } // read and update complete world description // from given frame (this is for clients) void World::read_description(Trame& t) { if (t.get_char() != WORLD_DESC) { if (debug) std::cerr << " petard le bordel geant! \n"; exit(1); } id = t.get_int(); number = t.get_char(); playcycle = t.get_short(); x_dim = t.get_short(); y_dim = t.get_short(); maxplayers = t.get_short(); maxobjects = t.get_short(); int nbplay = t.get_short(); int nbobj = t.get_short(); paused = t.get_short(); longest_worm = t.get_short(); map.make(x_dim, y_dim); // here we add if(1) to avoid bogus compilers problems if (1) for (int i = 0; i < nbplay; i++) read_new_player(t); for (int i = 0; i < nbobj; i++) read_new_object(t); update_objects(); //@@ the hack for our own player number (clients only) _hack_own_player_number = first_free_player(); } // lecture d'une trame de tour de jeu, // typiquement pour les clients void World::read_changes(Trame &t) { while (!t.eot()) { switch(t.peek_char()) { case NEW_NIBBLE: case NEW_LONGOBJ: case NEW_MOVABLE: { _Object * obj = read_new_object(t); if (obj && (obj->instance_of(CL_MOVABLE))) { Movable& mv = *(Movable *) obj; if (mv.player_id) { mv.player = & lookup_player(mv.player_id); mv.player->worm_id = mv.id; } } } break; case PLAYER_DESC: read_new_player(t); break; case PLAYER_QUIT: { t.get_char(); // skip msg id int player_id = t.get_int(); short reason = t.get_char(); //reason interface->kill_player(lookup_player(player_id), reason); remove_player(player_id); } break; case CHANGE_DIRECTION: { t.get_char(); int lid = t.get_int(); short ldir = t.get_char(); ((Movable &) lookup_object(lid)).direction = ldir; } break; case TEXT_MESSAGE: { t.get_char(); // skip msg id int from_id = t.get_int(); char *msg = t.get_string(); // pas a jour; pour tester interface->display_message(lookup_player(from_id), msg, 0); } break; case PAUSE_GAME: { t.get_char(); // skip msg id int pid = t.get_int(); interface->display_system_message("Paused the game **\n", &lookup_player(pid)); paused = 1; } break; case UNPAUSE_GAME: { t.get_char(); // skip msg id int pid = t.get_int(); interface->display_system_message("UnPaused the game **\n", &lookup_player(pid)); paused = 0; } break; case PAUSE_OWN: { t.get_char(); // skip msg id int pid = t.get_int(); Player &p = lookup_player(pid); if (p.worm_id) { interface->display_system_message("** Paused its own worm\n", &p); ((Movable&) lookup_object(p.worm_id)).paused = 1; } } break; case UNPAUSE_OWN: { t.get_char(); // skip msg id int pid = t.get_int(); Player &p = lookup_player(pid); if (p.worm_id) { interface->display_system_message("UnPaused its own worm\n", &p); ((Movable&) lookup_object(p.worm_id)).paused = 0; } } break; default: if (debug) std::cerr<< "read_changes(): unexpected trame Id == " \ << (int) t.peek_char() << std::endl; t.get_char(); if (debug) std::cerr<< "read_changes(): left in trame = "; t.dump_left(); break; } } } void World::draw(void) { map.clear(); for (Pix p = objects.first(); p; objects.next(p)) objects(p)->draw(map); } void World::build_maptype() { map.clear(); for (Pix p = objects.first(); p; objects.next(p)) objects(p)->add_type(map); } // for debug purposes void World::display() { std::cout << "ID: " << id << " WORLD"; std::cout << " dims=" << x_dim << "x" << y_dim; std::cout << " cycle=" << playcycle << std::endl; std::cout << " " << nbplayers << " players " << std::endl; // here we add if(1) to avoid bogus compilers problems if (1) for (Pix p = players.first(); p; players.next(p)) players(p)->display(); std::cout << " " << nbobjects << " objects " << std::endl; for (Pix p = objects.first(); p; objects.next(p)) objects(p)->display(); } void World::quit_game() { Trame t; t.put_char(QUIT_GAME); send_to_all(t); close_all_sockets(); } void World::get_client_responses() { cycle_trame.reset(); cycle_trame.put_char(CHANGE_NOTIFY); fd_set rfds, rfds2; struct timeval tv; int retval, maxfd; FD_ZERO(&rfds2); int numokay = 0; for (Pix p = players.first(); p; players.next(p)) { // if (players(p)->is_alive == IA_ALIVE) if (players(p)->is_dependent()) { numokay++; } else { players(p)->is_alive = QR_IACHECK; FD_SET(players(p)->socket_number, &rfds2); } } int numchecked = 0; int done = 0; static Trame t; while (numokay < nbplayers && !done) { FD_ZERO(&rfds); maxfd = -1; for (Pix p = players.first(); p; players.next(p)) if (!players(p)->is_dependent() && players(p)->is_alive == QR_IACHECK) { FD_SET(players(p)->socket_number, &rfds); maxfd = (maxfd > players(p)->socket_number) ? maxfd : players(p)->socket_number; } // plus personne n'est a attendre... (dans le cas ou ils sont DEAD) if (maxfd < 0) break; tv.tv_sec = 3; tv.tv_usec = 0; // 1 sec retval = select(maxfd + 1, &rfds, NULL, NULL, &tv); if (retval < 0) { if (debug) std::cerr << "World::get_client_responses(): bug quelque part!" << std::endl; return; // bah violent comme d'hab. } if (retval == 0) done = 1; else { for (Pix p = players.first(); p && retval; players.next(p)) { if (! players(p)->is_dependent() && players(p)->is_alive == QR_IACHECK && FD_ISSET(players(p)->socket_number, &rfds)) { retval--; if (FD_ISSET(players(p)->socket_number, &rfds2)) { numchecked++; FD_CLR(players(p)->socket_number, &rfds2); } int nbread = 0; t.set_timeout(2000); // two seconds timeout; while (t.receive_from(players(p)->socket_number) >= 0) { read_player_response(* players(p), t); t.set_timeout(0); // non bloquant pour les autres nbread++; } // si erreur lecture: player killed. // sinon, si player a repondu, on le compte if (!nbread) players(p)->is_alive = QR_DEAD; else if (players(p)->is_alive == QR_STILLTHERE) numokay++; } } } } // on tue les players perdus. if (numokay != nbplayers) { Pix p = players.first(); while (p) { if (!players(p)->is_dependent() && players(p)->is_alive != QR_STILLTHERE) { interface->display_system_message("World::get_client_responses(): " \ "LOOSING A PLAYER!\n"); server_remove_player(players(p), players(p)->is_alive); p = players.first(); } else players.next(p); } } // si necessaire on envoie la trame aux clients if (cycle_trame.size() > 2) send_to_all(cycle_trame); } // process input coming from a client (for servers only) void World::read_player_response(Player& p, Trame& t) { int done = 0; while (!t.eot() && !done) { switch(t.peek_char()) { case PLAYER_CHANGEDIR: { t.get_char(); // skip msg id int newdir = t.get_char(); if (p.worm_id && !paused) { Movable & mv = (Movable &) lookup_object(p.worm_id); if (mv.two_keys) newdir = mv.two_key_translate(newdir); mv.direction = newdir; cycle_trame.put_char(CHANGE_DIRECTION); cycle_trame.put_int(mv.id); cycle_trame.put_char(newdir); } } break; case PLAYER_CHANGEDIR_OTHER: { t.get_char(); // skip msg id int worm_id = t.get_int(); int newdir = t.get_char(); if (debug) std::cout << "subplayer dir: ( " << worm_id << ") to " << newdir << std::endl; Movable & mv = (Movable &) lookup_object(worm_id); Player * other_player = NULL; if (mv.instance_of(CL_MOVABLE)) { other_player = mv.get_player(); } if (other_player == NULL || other_player->socket_number != p.socket_number) { if (debug) std::cerr << "World::PLAYER_CHANGEDIR_OTHER: bad id: " << worm_id \ << std::endl; } else { if (!paused) { if (mv.two_keys) newdir = mv.two_key_translate(newdir); mv.direction = newdir; cycle_trame.put_char(CHANGE_DIRECTION); cycle_trame.put_int(mv.id); cycle_trame.put_char(newdir); } } } break; case JOIN_GAME_OTHER: { t.get_char(); // skip msg id server_add_player_other(p.get_socket_number(), t, cycle_trame); } break; case TEXT_MESSAGE: { // std::cout << "got TEXT_MESSAGE == '"; t.get_char(); // skip msg id int dest_id = t.get_int(); char *msg = t.get_string(); // warning: use it fast! // std::cout << msg << "'" << std::endl; if (!dest_id) { // broadcast message cycle_trame.put_char(TEXT_MESSAGE); cycle_trame.put_int(p.id); cycle_trame.put_string(msg); } else { Player &p = lookup_player(dest_id); // should check for exceptions Trame tmp_trame(strlen(msg)+10); tmp_trame.put_char(TEXT_MESSAGE); cycle_trame.put_int(p.id); tmp_trame.put_string(msg); tmp_trame.send_to(p.socket_number); } } break; case PAUSE_GAME: t.get_char(); //skip msg id if (!paused) { cycle_trame.put_char(PAUSE_GAME); cycle_trame.put_int(p.id); paused = 1; } break; case UNPAUSE_GAME: t.get_char(); //skip msg id if (paused) { cycle_trame.put_char(UNPAUSE_GAME); cycle_trame.put_int(p.id); paused = 0; } break; case PAUSE_OWN: t.get_char(); //skip msg id if (p.worm_id && !((Movable&) lookup_object(p.worm_id)).paused) { cycle_trame.put_char(PAUSE_OWN); cycle_trame.put_int(p.id); ((Movable&) lookup_object(p.worm_id)).paused = 1; } break; case UNPAUSE_OWN: t.get_char(); //skip msg id if (p.worm_id && ((Movable&) lookup_object(p.worm_id)).paused) { cycle_trame.put_char(UNPAUSE_OWN); cycle_trame.put_int(p.id); ((Movable&) lookup_object(p.worm_id)).paused = 0; } break; case CYCLE_ACK: t.get_char(); p.is_alive = QR_STILLTHERE; // a priori, oui. Peut etre modifie // par le reste break; case QUIT_GAME: { t.get_char(); int reason = t.get_char(); switch(reason) { case QR_ASKEDFORIT: case QR_SIGINT: case QR_SIGTERM: p.is_alive = reason; break; default: p.is_alive = QR_UNKNOWN; break; } } break; case TRAME_ERROR: if (debug) std::cerr << "World::read_player_response(): id=" << p.id \ << " sent an error trame:! " << (int) t.peek_char() << std::endl; done = 1; break; default: if (debug) { std::cerr << "World::read_player_response(): id=" << p.id \ << " sent an unknown message: " << (int) t.peek_char() << std::endl; std::cerr << " ignoring rest of frame="; t.dump_left(); } break; } } } //====================================== // Private stuff //====================================== int World::is_free_player_number(int num) { Pix p; for (p = players.first(); p; players.next(p)) if (players(p)->get_number() == num) break; return !p; } int World::first_free_player() { int num = 1; while (!is_free_player_number(num)) num++; return num; } void World::send_to_all(Trame& t) { for (Pix p = players.first(); p; players.next(p)) if (! players(p)->is_dependent()) t.send_to(players(p)->socket_number); } void World::close_all_sockets() // a revoir dans la semantique { for (Pix p = players.first(); p; players.next(p)) if (! players(p)->is_dependent()) close(players(p)->socket_number); } Pix World::lookup_object0(int obj_id) { Pix p; for (p = objects.first(); p; objects.next(p)) if (objects(p)->id == obj_id) break; return p; } void World::remove_object0(Pix& p) { //FAST std::cout << "? killing id=" << objects(p)->id << std::endl; delete objects(p); objects.del(p, -1); nbobjects--; }