//======================================== // MotifInterface.C // // Motif client interface for ZNibbles // (should make it X11 only) // // ZNibbles // Vincent Mallet 1997, 1998 //======================================== // $Id: MotifInterface.C,v 1.13 1999/05/12 11:43:50 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 #include #include #include #include "creer_socket.h" #include "LongObject.H" #include "Nibble.H" #include "Player.H" #include "Trame.H" #include "World.H" #include "Options.H" #include "motifutil.H" #include "menus.H" #include "UserList.H" #include "NibblesArea.H" #include "MotifInterface.H" #include "resources.inc" // default resources //=========================== // Static class variables //=========================== int MotifInterface::dead_server = 0; int MotifInterface::hack_socket_client = 0; //============================ // Constructor //============================ MotifInterface::MotifInterface(World& wr) : BaseInterface(wr), tx(512), sent(0), toplevel(0), main_window(0), nibblesarea(*this), debug(0), two_key(0) { } // Initializes the Motif Interface (called by World::Init()) void MotifInterface::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(); 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(); own_name = options.get_player_name(); display_version(); if (two_key) cout << "Two-key mode enabled" << endl; // connect pipe_handler to SIGPIPE signal // ('broken pipe' signal) memset(&action, 0, sizeof(action)); 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); } cout << "Connecting to ZNibbles server: " \ << options.get_host_name() << ":" << options.get_port() << "... " << endl; // create and bind socket to any port port = 0; if ((socket_client = creer_socket(SOCK_STREAM, &port, &client_address)) == -1) { fprintf(stderr,"Unable to create client socket\n"); exit(2); } if (debug) cout << "Client socket created on port: " << ntohs(client_address.sin_port) << 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(); cout << "Connection accepted\n" << 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()); cout << "initializing interface..." << endl; make(argc, argv); } void MotifInterface::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); } } // Actually create this interface void MotifInterface::make(int argc, char **argv) { Widget form; XtSetLanguageProc(NULL, (XtLanguageProc) NULL, NULL); toplevel = XtVaAppInitialize(&context, "ZNibbles", NULL, 0, &argc, argv, ViMresources, NULL); XtVaSetValues(toplevel, XmNtitle, "ZNibbles", NULL, NULL); // XtAddEventHandler(toplevel, (EventMask) 0, True, (XtEventHandler) _XEditResCheckMessages, 0); XtAddEventHandler(toplevel, StructureNotifyMask, False, EventHandler, (XtPointer) this); main_window = XtVaCreateManagedWidget("MainWindow", xmMainWindowWidgetClass, toplevel, NULL); display = XtDisplay(main_window); menus . make(main_window); about . make(main_window); form = XtVaCreateManagedWidget("form", xmFormWidgetClass, main_window, NULL); horzpane . make(form); userlist . make(horzpane.widget); textarea . make(horzpane.widget); nibblesarea .make(form); horzpane . set_objects(userlist.widget, textarea.widget); menus.set_about(&about); XmMainWindowSetAreas(main_window, menus.widget, NULL, NULL, NULL, form); XtAppAddWorkProc(context, myWorkProc, (XtPointer) this); XtSetKeyboardFocus(userlist.widget, nibblesarea.widget); XtSetKeyboardFocus(textarea.widget, nibblesarea.widget); not_configured = 1; // we're not configured yet ! } void MotifInterface::display_version() { cerr << "ZNibbles v" VERSION " - A little silly game - " << "(c) Vincent Mallet 1997, 1998, 1999 - vmallet@enst.fr" << endl << endl; } void MotifInterface::display_version_short() { cout << "ZNibbles Motif Client " VERSION << endl; } void MotifInterface::display_help(char *name) { cout << "Usage: " << name << " [OPTION].. PLAYERNAME" << endl; cout << endl; cout << "Start a ZNibbles Motif client and connect to the specified nibbles server." << endl; cout << endl; cout << " -n, --host-name=HOST connect to server HOST [default is localhost]" << endl; cout << " -p, --port=NUM connect to port NUM of server [default is 5051]" << endl; cout << " -m, --message-file=FILE load predefined messages from file FILE" << endl; cout << " -t, --twokey control worm with only two keys, LEFT and RIGHT" << endl; cout << " -i, --enable-stdin enable standard input for sending messages" << endl; cout << " -d, --debug enable debug output" << endl; cout << " -V, --version print version number, then exit" << endl; cout << " -h, --help show this message and exit" << endl; cout << endl; cout << "Report bugs to ." << endl; } void MotifInterface::display_play_help() { cout << endl; cout << "Welcome to ZNibbles!" << endl; cout << endl; cout << "Valid keys while playing:" << endl << endl << " Up, Down, Left, Right: move worm 1" << endl << endl << " p/u : pause/unpause game" << endl << endl << " P/U : pause/unpause _your_ worm" << endl << endl << " 1, 2, ... 0 : send predefined messages to other players" << endl << endl << " Ctrl+Q : quit game" << endl << endl << " Have fun!" << endl; } void MotifInterface::pipe_handler(int sig) { cerr << "--- Lost Server Connection! ---" << endl; sig++; // warnings.. dead_server = 1; } void MotifInterface::stop_handler(int sig) { cerr << "--- Quitting game! ---" << 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); exit(0); } } // called when a player is added to the game. void MotifInterface::add_player(Player& p) { char s[200]; sprintf(s, "#%2d %-25.25s", p.get_number(), p.get_name()); userlist.add_entry(s); display_system_message("Joined the game!\n", &p); } // called when a player is removed from the game void MotifInterface::kill_player(Player& p, int reason) { char s[200]; sprintf(s, "#%2d %-25.25s", p.get_number(), p.get_name()); userlist.remove_entry(s); sprintf(s, "LEFT the game! (reason=%d) (score=%d) (frags=%d) (best=%d)\n", reason, p.get_score(), p.get_frag(), p.get_best_length()); display_system_message(s, &p); } // display a message sent by a user void MotifInterface::display_message(Player& from, char *msg, int priv) { char buf[300]; if (priv) { // cout << "Got *private* Message: '" << msg << "'" << endl; sprintf(buf, "[Private] %s> %s\n", from.get_name(), msg); } else { // cout << "Message: " << from.name << "> " << msg << endl; sprintf(buf, "%s> %s\n", from.get_name(), msg); } textarea.add_line(buf); } // display a message sent by the system void MotifInterface::display_system_message(char *msg, Player *p, int color) // default p=NULL, color=0 { char buf[200]; color++; if (p) sprintf(buf, "** %s %s", p->get_name(), msg); else sprintf(buf, "** %s", msg); textarea.add_line(buf); } // Start the Motif Interface void MotifInterface::run(void) { reconfigure(); // set up everything XtRealizeWidget(toplevel); // et c'est parti... XtAppMainLoop(context); } void MotifInterface:: EventHandler(Widget w, XtPointer client_data, XEvent *event, char *z) { MotifInterface * mythis = (MotifInterface *) client_data; w = w; z = z; switch (event->type) { case ConfigureNotify: // printf("ConfigureNotify\n"); if (mythis->not_configured) mythis->configure(); // mythis->scale->configure(); break; case DestroyNotify: // printf("DestroyNotify\n"); break; default: // printf(" (bah, autre notify)\n"); break; } } // configure some elements of the interface // whose parameters are known only after the // first mapping of the workwindow void MotifInterface:: configure(void) { horzpane.nice_cursor(XC_sb_h_double_arrow); userlist.nice_cursor(XC_hand1); nibblesarea.nice_cursor(XC_xterm); // textarea->nice_cursor(XC_xterm); not_configured = 0; // do it only once } // reconfigure quelques elements en reponse // a un changement de parametres utilisateurs // (apres Alt+C par exemple) void MotifInterface:: reconfigure(void) { nibblesarea.set_config(); } void MotifInterface::check_stdin() { // any broadcast message to be sent? if (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); } } // Work Procedure Boolean MotifInterface::myWorkProc(XtPointer closure) { MotifInterface& mythis = (MotifInterface&) * (MotifInterface *) closure; Trame& mt = mythis.t; Trame& mtx = mythis.tx; static int done = 0; if (!done && !dead_server) { if (!read_ready(mythis.socket_client, 20000) || mt.receive_from(mythis.socket_client)) { // read time out, or read error mythis.check_stdin(); } else { // read a frame from server mythis.check_stdin(); switch(mt.peek_char()) { case TRAME_ERROR: break; // @@ we'll see that later case WORLD_DESC: mythis.world.read_description(mt); mythis.nibblesarea.make2(mythis.world.x_dim, mythis.world.y_dim, mythis.toplevel); if (mythis.debug) mythis.world.display(); mtx.reset(); mtx.put_char(CYCLE_ACK); mtx.send_to(mythis.socket_client); mythis.world.draw(); mythis.nibblesarea.draw_from_map(mythis.world.map); mythis.nibblesarea.redraw2(); break; case CHANGE_NOTIFY: mt.get_char(); mythis.world.read_changes(mt); break; case VOID_TRAME: { if (mythis.debug) cout << "got VOID_TRAME " << endl; mt.get_char(); char *p = mt.get_string(); if (p && strcmp(p, "w") == 0) mythis.world.display(); else if (p && strcmp(p, "d") == 0) { mythis.world.draw(); mythis.world.map.display(); } else if (p && strcmp(p, "D") == 0) { mythis.world.build_maptype(); mythis.world.map.display_t(); } } break; case TEXT_MESSAGE: { mt.get_char(); // skip packet id int from_id = mt.get_int(); char *msg = mt.get_string(); mythis.display_message(mythis.world.lookup_player(from_id), msg, 1); } break; case YOUR_OTHER_PLAYER: { mt.get_char(); int my_other_player_id = mt.get_int(); my_other_player_id++; // @@ for warnings //@@ do something } break; case CYCLE_NOTIFY: { mythis.sent = 0; mt.get_char(); mythis.world.cycle(); mythis.world.read_changes(mt); mtx.reset(); mtx.put_char(CYCLE_ACK); mythis.send_direction(); // if there's one in the queue.. mtx.send_to(mythis.socket_client); mythis.world.draw(); mythis.nibblesarea.draw_from_map(mythis.world.map); } break; case QUIT_GAME: if (mythis.debug) cout << "Got QUIT_GAME" << endl; cout << "Client shutting down.... " << endl; done = 1; break; default: if (mythis.debug) cout << "got unknown frame type: " << (int) mt.peek_char() << endl; break; } } return False; } else { if (mythis.debug) printf("moaaa lost server connection?\n"); if (mythis.socket_client) { close(mythis.socket_client); mythis.socket_client = 0; } if (1) return True; // we wont use the work proc anymore } return False; } void MotifInterface::send_direction(Direction direction) // default direction=0 { if (direction) dirs.append(direction); if (!sent && dirs.length()) { send_direction0(dirs.front(), !direction); dirs.remove_front(); sent = 1; } } void MotifInterface::send_direction0(Direction direction, int do_append) { if (!do_append) tx.reset(); tx.put_char(PLAYER_CHANGEDIR); tx.put_char(direction); if (!do_append) tx.send_to(socket_client); } void MotifInterface::pause_request(int pause_type) { tx.reset(); tx.put_char(pause_type); tx.send_to(socket_client); } void MotifInterface::join_game() { if (debug) cout << "Sending welcome message..." << endl; t.put_char(JOIN_GAME); t.put_string(own_name); t.put_byte(two_key); t.send_to(socket_client); t.set_timeout(0); } void MotifInterface::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); }