//======================================== // GtkInterface.C // // GTK+ client interface for ZNibbles // // This is loosely based on the (already bad) // code of MotifInterface // // ZNibbles // Vincent Mallet 1999 //======================================== // $Id: GtkInterface.C,v 1.12 1999/05/12 11:43:29 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. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "creer_socket.h" #include "GtkInterface.H" #include "Menus.H" #include "UserList.H" #include "GtkPlayer.H" #include "Options.H" #include "LongObject.H" #include "Nibble.H" #include "Player.H" #include "Trame.H" #include "World.H" #define INITIAL_NIBBLESAREA_WIDTH 200 #define INITIAL_NIBBLESAREA_HEIGHT 200 //=========================== // Static class variables //=========================== int GtkInterface::_dead_server = 0; int GtkInterface::_hack_socket_client = 0; static int nnn=0; //============================ // Constructor //============================ GtkInterface::GtkInterface(World& wr) : BaseInterface(wr), tx(512), _errors(0), main_window(0), menus(*this), userlist(*this), nibblesarea(*this), infobar(this), _debug(0), _two_key(0), _other_player(0) { } //======================================================== // Initializes the Gtk Interface (called by World::Init()) //======================================================== void GtkInterface::init(int argc, char *argv[]) { struct sockaddr_in server_address; struct sockaddr_in client_address; int port; // server port we should connect to struct hostent * hp; // server address struct sigaction action; Options & options = * new Options(); // we already have called gtk_init() in the ClientGtk.C main function. options.set_option_set(OPTIONS_CLIENT_SET); if (!options.parse(argc, argv)) { fprintf(stderr, "Usage: %s [OPTION].. PLAYERNAME\n\n", *argv); fprintf(stderr, "Try `%s --help` for more options.\n", *argv); exit(2); } if (options.is_help()) { display_help(*argv); exit(0); } if (options.is_version()) { display_version_short(); exit(0); } _two_key = options.is_twokey(); _debug = options.is_debug(); _stdin_input = options.is_stdin_input(); _own_name = options.get_player_name(); display_version(); if (_two_key) std::cout << "Two-key mode enabled" << std::endl; // connect pipe_handler to SIGPIPE signal // ('broken pipe' signal) action.sa_flags = 0; action.sa_handler = pipe_handler; sigaction(SIGPIPE, &action, NULL); // get IP address of server if (NULL == (hp = gethostbyname(options.get_host_name()))) { fprintf(stderr,"unknown host: %s\n", options.get_host_name()); exit(2); } std::cout << "Connecting to ZNibbles server: " \ << options.get_host_name() << ":" \ << options.get_port() << "... " << std::endl; // create and bind socket to any port port = 0; if (-1 == (_socket_client = creer_socket(SOCK_STREAM, &port, &client_address))) { fprintf(stderr,"Unable to create client socket\n"); exit(2); } if (_debug) std::cout << "Client socket created on port: " \ << ntohs(client_address.sin_port) << std::endl; // Build server address server_address.sin_family = AF_INET; server_address.sin_port = htons(options.get_port()); memcpy(&server_address.sin_addr.s_addr, hp->h_addr, hp->h_length); // try to connect to server if(connect(_socket_client, (struct sockaddr *) & server_address, sizeof(server_address)) == -1) { perror("Connection to server failed"); fprintf(stderr, "Have you started the ZNibbles Server ?\n"); exit(2); } display_play_help(); std::cout << "Connection accepted\n" << std::endl; _hack_socket_client = _socket_client; // hack for signal handling action.sa_handler = stop_handler; sigaction(SIGINT, &action, NULL); sigaction(SIGTERM, &action, NULL); sigaction(SIGUSR1, &action, NULL); init_messages(options.get_message_file()); std::cout << "initializing interface..." << std::endl; make(argc, argv); } void GtkInterface::init_messages(char *filename) { FILE *f; // blatently ripped from our loved Duke3D predefined_messages[0] = "An inspiration for birth control."; predefined_messages[1] = "You're gonna die for that!"; predefined_messages[2] = "It hurts to be you."; predefined_messages[3] = "Lucky Son of a Bitch."; predefined_messages[4] = "Hmmm....Payback time."; predefined_messages[5] = "You bottom dwelling scum sucker."; predefined_messages[6] = "Damn, you're ugly."; predefined_messages[7] = "Ha ha ha...Wasted!"; predefined_messages[8] = "You suck!"; predefined_messages[9] = "AARRRGHHHHH!!!"; if (NULL != filename && NULL != (f = fopen(filename, "rt"))) { char buf[200]; for (int i = 0; i < 10 && fgets(buf, 199, f); i++) predefined_messages[i] = strdup(buf); fclose(f); } } // react to a destroy event (get a chance to disconnect // from server) void GtkInterface::destroy(GtkWidget *widget, gpointer data) // it is static { GtkInterface * mythis = (GtkInterface *) data; // fake a SIGUSR1 mythis->stop_handler(SIGUSR1); } // react to a keypress event gint GtkInterface::key_cb(GtkWidget *widget, GdkEventKey *event, GtkInterface *pmythis) { GtkInterface& mythis = *pmythis; switch(event->keyval) { case GDK_Down: mythis._main_player->send_direction(D_DOWN); break; case GDK_Up: mythis._main_player->send_direction(D_UP); break; case GDK_Left: mythis._main_player->send_direction(D_LEFT); break; case GDK_Right: mythis._main_player->send_direction(D_RIGHT); break; case 'd': if (mythis._other_player != NULL) mythis._other_player->send_direction(D_DOWN); break; case 'e': if (mythis._other_player != NULL) mythis._other_player->send_direction(D_UP); break; case 's': if (mythis._other_player != NULL) mythis._other_player->send_direction(D_LEFT); break; case 'f': if (mythis._other_player != NULL) mythis._other_player->send_direction(D_RIGHT); break; case 'p': mythis.pause_request(PAUSE_GAME); break; case 'u': mythis.pause_request(UNPAUSE_GAME); break; case 'P': mythis.pause_request(PAUSE_OWN); break; case 'U': mythis.pause_request(UNPAUSE_OWN); break; case 'z': { // if (_debug) std::cout << "Sending welcome message..." << std::endl; Trame tzz; tzz.reset(); tzz.put_char(JOIN_GAME_OTHER); tzz.put_string("TOUDIOU"); tzz.put_byte(0); tzz.send_to(mythis._socket_client); } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': mythis.send_predefined_message(event->keyval - '0'); break; default: if (mythis._debug) std::cout << "key=" << event->keyval << std::endl; return FALSE; } // the signal was for us. No need for others to deal with it. gtk_signal_emit_stop_by_name( GTK_OBJECT( widget ), "key_press_event" ); return TRUE; } // react to a configure event gint GtkInterface::configure_event (GtkWidget *widget, GdkEventConfigure *event, GtkInterface *pthis) { if (pthis->_debug) std::cerr << "GtkInterface:::configure_event()"<_debug) std::cerr << "GtkInterface:::expose_event()"<." << std::endl; } void GtkInterface::display_play_help() { std::cout << std::endl; std::cout << "Welcome to ZNibbles!" << std::endl; std::cout << std::endl; std::cout << "Valid keys while playing:" << std::endl << std::endl << " Up, Down, Left, Right: move worm 1" << std::endl << std::endl << " p/u : pause/unpause game" << std::endl << std::endl << " P/U : pause/unpause _your_ worm" << std::endl << std::endl << " 1, 2, ... 0 : send predefined messages to other players" << std::endl << std::endl << " Ctrl+Q : quit game" << std::endl << std::endl << " Have fun!" << std::endl; } void GtkInterface::pipe_handler(int sig) { std::cerr << "--- Lost Server Connection! ---" << std::endl; sig++; // warnings.. _dead_server = 1; } void GtkInterface::stop_handler(int sig) { std::cerr << "--- Quitting game! ---" << std::endl; if (_hack_socket_client) { struct timeval tv; Trame t(32); t.put_char(QUIT_GAME); switch(sig) { case SIGINT: t.put_char(QR_SIGINT); break; case SIGTERM: t.put_char(QR_SIGTERM); break; case SIGUSR1: t.put_char(QR_ASKEDFORIT); break; default: t.put_char(QR_UNKNOWN); break; } t.send_to(_hack_socket_client); // wait for 500ms before closing socket.. tv.tv_sec = 0; tv.tv_usec = 300000; select(0, NULL, NULL, NULL, &tv); close(_hack_socket_client); tv.tv_sec = 0; tv.tv_usec = 200000; select(0, NULL, NULL, NULL, &tv); gtk_main_quit(); } } // void GtkInterface::read_world_desc(Trame& input_frame) // { // world.read_description(input_frame); // nibblesarea.make2(world.x_dim, world.y_dim); // mtx.reset(); // mtx.put_char(CYCLE_ACK); // mtx.send_to(_socket_client); // if (_debug) // world.display(); // world.draw(); // nibblesarea.draw_from_map(world.map); // nibblesarea.redraw(); // } // called when a player is added to the game. void GtkInterface::add_player(Player& player) { GtkPlayer *gtk_player = new GtkPlayer(*this); gtk_player->set_player(&player); _players.append(gtk_player); userlist.add_entry(* gtk_player); display_system_message("Joined the game!\n", &player); } // called when a player is removed from the game void GtkInterface::kill_player(Player& player, int reason) { char s[200]; for (Pix p = _players.first(); p; _players.next(p)) { if (_players(p)->get_player() == &player) { userlist.remove_entry(* _players(p)); delete _players(p); _players.del(p, -1); break; } } sprintf(s, "LEFT the game! (reason=%d) (score=%d) (frags=%d) (best=%d)\n", reason, player.get_score(), player.get_frag(), player.get_best_length()); display_system_message(s, &player); } // display a message sent by a user void GtkInterface::display_message(Player& from, char *msg, int priv) { char buf[300]; if (priv) { // std::cout << "Got *private* Message: '" << msg << "'" << std::endl; sprintf(buf, "[Private] %s> %s\n", from.get_name(), msg); } else { // std::cout << "Message: " << from.get_name() << "> " << msg << std::endl; sprintf(buf, "%s> %s\n", from.get_name(), msg); } messagearea.add_line(buf, nibblesarea.get_player_color(from)); } // display a message sent by the system void GtkInterface::display_system_message(char *msg, Player *p, int color) // default p=NULL, color=0 { char buf[200]; color++; if (p) { char *lines[5]; GdkColor *colors[5]; lines[0] = "** "; lines[1] = p->get_name(); lines[2] = " "; lines[3] = msg; lines[4] = NULL; colors[0] = colors[2] = colors[3] = &main_window->style->black; colors[1] = nibblesarea.get_player_color(*p); colors[4] = NULL; messagearea.add_lines(lines, colors); } else { sprintf(buf, "** %s", msg); messagearea.add_line(buf, &main_window->style->black); } } // Start the Gtk Interface void GtkInterface::run(void) { gtk_widget_show(main_window); // Enter the main event loop gtk_main(); } // Send the "Welcome packet" to the server. void GtkInterface::join_game() { if (_debug) std::cout << "Sending welcome message..." << std::endl; t.put_char(JOIN_GAME); t.put_string(_own_name); t.put_byte(_two_key); t.send_to(_socket_client); t.set_timeout(1); } void GtkInterface::check_stdin() { // any broadcast message to be sent? if (_stdin_input && read_ready(STDIN_FILENO)) { char s[500]; fgets(s, 499, stdin); tx.reset(); tx.put_char(TEXT_MESSAGE); tx.put_int(0); // broadcast tx.put_string(s); tx.send_to(_socket_client); } } void GtkInterface::handle_server_input_static(void *pthis, gint source, GdkInputCondition cond) { //DBG std::cerr << "!" ; ((GtkInterface *)pthis)->handle_server_input(); } void GtkInterface::handle_server_input() { //DBG std::cerr << nnn << " " ; //DBG nnn = 0; static int done = 0; //DBG std::cerr<<"<"; if (t.receive_from(_socket_client)) { _errors++; std::cerr << "GtkInterface::handle_server_input(): read error" << std::endl; if (_errors >= 5) { pipe_handler(SIGPIPE); } return; } _errors = 0; //DBG std::cerr<<">"; //DBG std::cerr << "X"; switch(t.peek_char()) { case TRAME_ERROR: break; // @@ we'll see that later case WORLD_DESC: do_world_desc(); break; case CHANGE_NOTIFY: t.get_char(); world.read_changes(t); break; case YOUR_OTHER_PLAYER: do_your_other_player(); break; case VOID_TRAME: do_void_trame(); break; case CYCLE_NOTIFY: do_cycle_notify(); break; case TEXT_MESSAGE: { t.get_char(); // skip packet id int from_id = t.get_int(); char *msg = t.get_string(); display_message(world.lookup_player(from_id), msg, 1); } break; case QUIT_GAME: if (_debug) std::cout << "Got QUIT_GAME" << std::endl; std::cout << "Client shutting down.... " << std::endl; done = 1; break; default: if (_debug) std::cout << "got unknown frame type: " << (int) t.peek_char() << std::endl; break; } } // action to take when a WORLD_DESC message is received void GtkInterface::do_world_desc() { world.read_description(t); nibblesarea.make2(world.x_dim, world.y_dim); if (_debug) world.display(); tx.reset(); tx.put_char(CYCLE_ACK); tx.send_to(_socket_client); world.draw(); nibblesarea.draw_from_map(world.map); nibblesarea.redraw(); } // action to take when a YOUR_OTHER_PLAYER message is received void GtkInterface::do_your_other_player() { t.get_char(); // skip message id int my_other_player_id = t.get_int(); if (_debug) std::cout << "my new player: " << my_other_player_id << std::endl; Player& p = world.lookup_player(my_other_player_id); _other_player = lookup_gtk_player(p); if (_other_player == NULL) { if (_debug) std::cout << "GtkInterface::do_your_other_player(): bad id: " \ << my_other_player_id << std::endl; return; } // create an InfoBar for this player InfoBar * new_infobar = new InfoBar(this); new_infobar->make(); gtk_box_pack_start (GTK_BOX (_hbox2), new_infobar->get_widget(), FALSE, FALSE, 0); new_infobar->set_color(nibblesarea.get_player_color(*_other_player->get_player())); _other_player->set_frame(&tx); _other_player->set_socket(_socket_client); _other_player->set_infobar(new_infobar); _other_player->activate_observation(); } // action to take when a VOID_TRAME message is received void GtkInterface::do_void_trame() { if (_debug) std::cout << "got VOID_TRAME " << std::endl; t.get_char(); char *p = t.get_string(); if (p && strcmp(p, "w") == 0) world.display(); else if (p && strcmp(p, "d") == 0) { world.draw(); world.map.display(); } else if (p && strcmp(p, "D") == 0) { world.build_maptype(); world.map.display_t(); } } // action to take when a CYCLE_NOTIFY message is received void GtkInterface::do_cycle_notify() { _main_player->clear_sent(); if (_other_player != NULL) _other_player->clear_sent(); t.get_char(); world.cycle(); world.read_changes(t); tx.reset(); tx.put_char(CYCLE_ACK); // if there's one in the queue.. _main_player->send_direction(); // will update tx (see set_own_player()) if (_other_player != NULL) _other_player->send_direction(); // will update tx (see set_own_player()) tx.send_to(_socket_client); world.draw(); nibblesarea.draw_from_map(world.map); } // Work Procedure - called when GTK is idle. gint GtkInterface::myWorkProc(GtkInterface *pmythis) { GtkInterface& mythis = *pmythis; nnn++; static int done = 0; if (!done && !_dead_server) { if (!read_ready(mythis._socket_client, 1000)) { // read time out mythis.check_stdin(); } else { // read a frame from server mythis.handle_server_input(); } return TRUE; } else { if (mythis._debug) printf("moaaa lost server connection?\n"); if (mythis._socket_client) { close(mythis._socket_client); mythis._socket_client = 0; } return FALSE; // we wont use the work proc anymore } return TRUE; } void GtkInterface::send_predefined_message(int num) { tx.reset(); tx.put_char(TEXT_MESSAGE); tx.put_int(0); // broadcast tx.put_string(predefined_messages[num]); tx.send_to(_socket_client); } // Lookup a GtkPlayer given a specified player GtkPlayer * GtkInterface::lookup_gtk_player(Player& player) { for (Pix p = _players.first(); p; _players.next(p)) if (_players(p)->get_player() == &player) return _players(p); return NULL; } void GtkInterface::pause_request(int pause_type) { tx.reset(); tx.put_char(pause_type); tx.send_to(_socket_client); } void GtkInterface::set_own_player(Player& p) { GtkPlayer * gtk_player = lookup_gtk_player(p); if (gtk_player == NULL) { if (_debug) std::cerr << "GtkInterface::set_own_player(): SERIOUS: player not found !" \ << std::endl; return; } gtk_player->set_frame(&tx); gtk_player->set_socket(_socket_client); gtk_player->set_infobar(&infobar); gtk_player->activate_observation(); gtk_player->set_master(TRUE); _main_player = gtk_player; infobar.set_color(nibblesarea.get_player_color(p)); } void GtkInterface::activate_other_player() { if (_other_player == NULL) { if (_debug) std::cout << "Sending welcome message..." << std::endl; Trame tzz; char s[100]; sprintf(s, "Friend of %.80s", _own_name); tzz.reset(); tzz.put_char(JOIN_GAME_OTHER); tzz.put_string(s); tzz.put_byte(0); tzz.send_to(_socket_client); display_system_message("2ND PLAYER: Keys are s,d,f,e (left, down, right, up)\n"); } }