/* * This file contains all of the OS-dependent stuff: * startup, signals, BSD sockets for tcp/ip, i/o, timing. * * The data flow for input is: * Game_loop ---> Read_from_descriptor ---> Read * Game_loop ---> Read_from_buffer * * The data flow for output is: * Game_loop ---> Process_Output ---> Write_to_descriptor -> Write * * The OS-dependent functions are Read_from_descriptor and Write_to_descriptor. * -- Furey 26 Jan 1993 */ #include <sys/types.h> #include <sys/time.h> #include <ctype.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> #include <crypt.h> #include "merc.h" /* * Malloc debugging stuff. */ #if defined(sun) #undef MALLOC_DEBUG #endif #if defined(MALLOC_DEBUG) #include <malloc.h> extern int malloc_debug args((int)); extern int malloc_verify args((void)); #endif #include <signal.h> #if defined(apollo) #undef __attribute #endif #include <fcntl.h> #include <netdb.h> #include <netinet/in.h> #include <sys/socket.h> #include <arpa/telnet.h> const char echo_off_str[] = { IAC, WILL, TELOPT_ECHO, '\0' }; const char echo_on_str[] = { IAC, WONT, TELOPT_ECHO, '\0' }; const char go_ahead_str[] = { IAC, GA, '\0' }; /* * Global variables. */ DESCRIPTOR_DATA *descriptor_free; /* Free list for descriptors */ DESCRIPTOR_DATA *descriptor_list; /* All open descriptors */ DESCRIPTOR_DATA *d_next; /* Next descriptor in loop */ bool merc_down; /* Shutdown */ time_t current_time; /* Time of this pulse */ void game_loop_unix args((int control)); int init_socket args((int port)); void new_descriptor args((int control)); bool read_from_descriptor args((DESCRIPTOR_DATA * d)); bool write_to_descriptor args((int desc, char *txt, int length)); /* * Other local functions (OS-independent). */ bool check_parse_name args((char *name)); bool check_playing args((DESCRIPTOR_DATA * d, char *name)); int main args((int argc, char **argv)); void nanny args((DESCRIPTOR_DATA * d, char *argument)); bool process_output args((DESCRIPTOR_DATA * d, bool fPrompt)); void read_from_buffer args((DESCRIPTOR_DATA * d)); int main(int argc, char **argv) { struct timeval now_time; int port; int control; /* * Memory debugging if needed. */ #if defined(MALLOC_DEBUG) malloc_debug(2); #endif /* * Init time. */ gettimeofday(&now_time, NULL); current_time = (time_t) now_time.tv_sec; /* * Get the port number. */ port = 1234; if (argc > 1) { if (!is_number(argv[1])) { fprintf(stderr, "Usage: %s [port #]\n", argv[0]); exit(1); } else if ((port = atoi(argv[1])) <= 1024) { fprintf(stderr, "Port number must be above 1024.\n"); exit(1); } } /* * Run the game. */ control = init_socket(port); boot_db(); game_loop_unix(control); close(control); /* * That's all, folks. */ exit(0); return 0; } int init_socket(int port) { static struct sockaddr_in sa_zero; struct sockaddr_in sa; int x = 1; int fd; if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Init_socket: socket"); exit(1); } if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &x, sizeof(x)) < 0) { perror("Init_socket: SO_REUSEADDR"); close(fd); exit(1); } #if defined(SO_DONTLINGER) && !defined(SYSV) { struct linger ld; ld.l_onoff = 1; ld.l_linger = 1000; if (setsockopt(fd, SOL_SOCKET, SO_DONTLINGER, (char *) &ld, sizeof(ld)) < 0) { perror("Init_socket: SO_DONTLINGER"); close(fd); exit(1); } } #endif sa = sa_zero; sa.sin_family = AF_INET; sa.sin_port = htons(port); if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) { perror("Init_socket: bind"); close(fd); exit(1); } if (listen(fd, 3) < 0) { perror("Init_socket: listen"); close(fd); exit(1); } return fd; } void game_loop_unix(int control) { static struct timeval null_time; struct timeval last_time; signal(SIGPIPE, SIG_IGN); gettimeofday(&last_time, NULL); current_time = (time_t) last_time.tv_sec; /* Main loop */ while (!merc_down) { fd_set in_set; fd_set out_set; fd_set exc_set; DESCRIPTOR_DATA *d; int maxdesc; #if defined(MALLOC_DEBUG) if (malloc_verify() != 1) abort(); #endif /* * Poll all active descriptors. */ FD_ZERO(&in_set); FD_ZERO(&out_set); FD_ZERO(&exc_set); FD_SET(control, &in_set); maxdesc = control; for (d = descriptor_list; d; d = d->next) { maxdesc = UMAX(maxdesc, d->descriptor); FD_SET(d->descriptor, &in_set); FD_SET(d->descriptor, &out_set); FD_SET(d->descriptor, &exc_set); } if (select(maxdesc + 1, &in_set, &out_set, &exc_set, &null_time) < 0) { perror("Game_loop: select: poll"); exit(1); } /* * New connection? */ if (FD_ISSET(control, &in_set)) new_descriptor(control); /* * Kick out the freaky folks. */ for (d = descriptor_list; d != NULL; d = d_next) { d_next = d->next; if (FD_ISSET(d->descriptor, &exc_set)) { FD_CLR(d->descriptor, &in_set); FD_CLR(d->descriptor, &out_set); d->outtop = 0; close_socket(d); } } /* * Process input. */ for (d = descriptor_list; d != NULL; d = d_next) { d_next = d->next; d->fcommand = false; if (FD_ISSET(d->descriptor, &in_set)) { if (!read_from_descriptor(d)) { FD_CLR(d->descriptor, &out_set); d->outtop = 0; close_socket(d); continue; } } read_from_buffer(d); if (d->incomm[0] != '\0') { d->fcommand = true; if (d->connected == CON_PLAYING) { if (d->showstr_point) show_string(d, d->incomm); else if (!strcasecmp(d->incomm, "credits")) { char help_greeting[MAX_STRING_LENGTH]; sprintf(help_greeting, "\r\n Original game idea, concept, and design:\r\n\r\n Katja Nyboe [Superwoman] (katz@freja.diku.dk)\r\n Tom Madsen [Stormbringer] (noop@freja.diku.dk)\r\n Hans Henrik Staerfeldt [God] (bombman@freja.diku.dk)\r\n Michael Seifert [Papi] (seifert@freja.diku.dk)\r\n Sebastian Hammer [Quinn] (quinn@freja.diku.dk)\r\n\r\n Additional contributions from:\r\n\r\nMichael Curran - the player title collection and additional locations.\r\nRagnar Loenn - the bulletin board.\r\nBill Wisner - for being the first to successfully port the game,\r\n uncovering several old bugs, uh, inconsistencies,\r\n in the process.\r\n\r\nAnd: Mads Haar and Stephan Dahl for additional locations.\r\n\r\nDeveloped at: DIKU -- The Department of Computer Science\r\n at the University of Copenhagen.\r\n" ); write_to_buffer(d, help_greeting, 0); } else if (!strcasecmp(d->incomm, "help merc")) { char help_greeting[MAX_STRING_LENGTH]; sprintf(help_greeting, "\r\n[Note: this entry may not be removed or altered or you will face legal]\r\n action. See our license.txt.]\r\nThis mud is based on Merc 2.2, created by Kahn, Hatchet, and Furey. Merc 2.2\r\nis an upgrade from Merc 2.1, created by Furey, Hatchet, and Kahn. Merc 2.2\r\nis available as Merc_22.tar.gz from ferkel.ucsb.edu (most files moved to\r\nftp.tcp.com), ftp.math.okstate.edu, marble.bu.edu, zen.btc.uwe.ac.uk\r\nE-mail to 'merc-request@kpc.com' to join the merc mailing list.\r\n\r\nThanks to ...\r\n ... Diku Mud for starting it all.\r\n ... The Free Software Foundation and DJ Delorie for kick-ass tools.\r\n ... Copper Mud and Alfa Mud for releasing their code and worlds.\r\n ... Aod of Generic for ... well, everything. You're a hoopy frood, Aod.\r\n ... Alander for many ideas and contributions.\r\n ... John Brothers of Silly for permission to use Silly code and worlds.\r\n ... Zrin for administering the mailing list.\r\n ... Abaddon for proofreading our comm.c.\r\n ... Hind, Quin, Vic, Diavolo, Oleg, and others for porting help.\r\n ... Diavolo, Grodyn, Morgenes, Da Pub, Loki, Talen, Kalgen, Sludge,\r\n The Crew of Salems Lot, and others for code and bug fixes.\r\n ... N'Atas-Ha for MOBPrograms and the pager skeleton and Blackstar\r\n for the improvements and ideas to the pager.\r\n ... Raff, Doctor, VampLestat, Nirrad, Tyrst, PinkF, Chris for worlds.\r\n ... Ikee, Chaos, Kraze, Maxx, Thoth, Zolstead, Zavod, VampLestat, Cyric,\r\n Kelvin, and Jackal for extensively playtesting MERC 2.2 and providing\r\n constructive input.\r\n ... the players and imps of Mud Dome, Final Mud, Mud Lite, Vego Mud, Rivers\r\n of Mud, Ruhr Mud, Mystic Realms, 4th Realm, Dragon Mud, and Salems Lot\r\n for bug reports, ideas, new code, and hours of enjoyment.\r\n\r\nShare and enjoy.\r\nMERC Industries\r\n\r\n" ); write_to_buffer(d, help_greeting, 0); } /* else */ // d->incomm is player command. Do something with it here. // interpret( d->character, d->incomm ); } else nanny(d, d->incomm); d->incomm[0] = '\0'; } } /* * Autonomous game motion. */ // update_handler( ); /* * Output. */ for (d = descriptor_list; d != NULL; d = d_next) { d_next = d->next; if ((d->fcommand || d->outtop > 0) && FD_ISSET(d->descriptor, &out_set)) { if (!process_output(d, true)) { d->outtop = 0; close_socket(d); } } } /* * Synchronize to a clock. * Sleep( last_time + 1/PULSE_PER_SECOND - now ). * Careful here of signed versus unsigned arithmetic. */ { struct timeval now_time; long secDelta; long usecDelta; gettimeofday(&now_time, NULL); usecDelta = ((int) last_time.tv_usec) - ((int) now_time.tv_usec) + 1000000 / PULSE_PER_SECOND; secDelta = ((int) last_time.tv_sec) - ((int) now_time.tv_sec); while (usecDelta < 0) { usecDelta += 1000000; secDelta -= 1; } while (usecDelta >= 1000000) { usecDelta -= 1000000; secDelta += 1; } if (secDelta > 0 || (secDelta == 0 && usecDelta > 0)) { struct timeval stall_time; stall_time.tv_usec = usecDelta; stall_time.tv_sec = secDelta; if (select(0, NULL, NULL, NULL, &stall_time) < 0) { perror("Game_loop: select: stall"); exit(1); } } } gettimeofday(&last_time, NULL); current_time = (time_t) last_time.tv_sec; } return; } void new_descriptor(int control) { static DESCRIPTOR_DATA d_zero; char buf[MAX_STRING_LENGTH]; DESCRIPTOR_DATA *dnew; struct sockaddr_in sock; struct hostent *from; int desc; int size; size = sizeof(sock); getsockname(control, (struct sockaddr *) &sock, (socklen_t *) & size); if ( (desc = accept(control, (struct sockaddr *) &sock, (socklen_t *) & size)) < 0) { perror("New_descriptor: accept"); return; } #if !defined(FNDELAY) #define FNDELAY O_NDELAY #endif if (fcntl(desc, F_SETFL, FNDELAY) == -1) { perror("New_descriptor: fcntl: FNDELAY"); return; } /* * Cons a new descriptor. */ if (descriptor_free == NULL) { dnew = (DESCRIPTOR_DATA *) alloc_perm(sizeof(*dnew)); } else { dnew = descriptor_free; descriptor_free = descriptor_free->next; } *dnew = d_zero; dnew->descriptor = desc; dnew->connected = CON_GET_NAME; dnew->showstr_head = NULL; dnew->showstr_point = NULL; dnew->outsize = 2000; dnew->outbuf = (char *) alloc_mem(dnew->outsize); size = sizeof(sock); if (getpeername(desc, (struct sockaddr *) &sock, (socklen_t *) & size) < 0) { perror("New_descriptor: getpeername"); dnew->host = str_dup("(unknown)"); } else { /* * Would be nice to use inet_ntoa here but it takes a struct arg, * which ain't very compatible between gcc and system libraries. */ int addr; addr = ntohl(sock.sin_addr.s_addr); sprintf(buf, "%d.%d.%d.%d", (addr >> 24) & 0xFF, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, (addr) & 0xFF); from = gethostbyaddr((char *) &sock.sin_addr, sizeof(sock.sin_addr), AF_INET); dnew->host = str_dup(from ? from->h_name : buf); } /* * Init descriptor data. */ dnew->next = descriptor_list; descriptor_list = dnew; /* * Send the greeting. */ { char help_greeting[MAX_STRING_LENGTH]; sprintf(help_greeting, "\r\n M E R C\r\n\r\n Diku Mud was created by Hans Henrik St{rfeldt, Katja Nyboe,\r\n Tom Madsen, Michael Seifert, and Sebastian Hammer.\r\n\r\n D I K U\r\n\r\n Thanks to all the people who have released their Mud code, worlds,\r\n ideas, and bug fixes. 'If I have seen far it is because I have\r\n stood on the shoulders of giants.' -- Isaac Newton\r\n\r\n M U D\r\n\r\n 24 Nov 1993 Merc 2.2 (Happy Thanksgiving!) Kahn, Hatchet, Furey\r\n\r\n Mercnet v0.01, Copyright (c) 1997-2003 Bobby Bailey\r\n\r\nBy what name do you wish to be known? "); write_to_buffer(dnew, help_greeting, 0); } return; } void close_socket(DESCRIPTOR_DATA * dclose) { CHAR_DATA *ch; if (dclose->outtop > 0) process_output(dclose, false); if (dclose->snoop_by != NULL) { write_to_buffer(dclose->snoop_by, "Your victim has left the game.\n\r", 0); } { DESCRIPTOR_DATA *d; for (d = descriptor_list; d != NULL; d = d->next) { if (d->snoop_by == dclose) d->snoop_by = NULL; } } if ((ch = dclose->character) != NULL) { if (dclose->connected == CON_PLAYING) { ch->desc = NULL; } else { free_char(dclose->character); } } if (d_next == dclose) d_next = d_next->next; if (dclose == descriptor_list) { descriptor_list = descriptor_list->next; } else { DESCRIPTOR_DATA *d; for (d = descriptor_list; d && d->next != dclose; d = d->next) ; if (d != NULL) d->next = dclose->next; } close(dclose->descriptor); free_string(dclose->host); dclose->next = descriptor_free; descriptor_free = dclose; return; } bool read_from_descriptor(DESCRIPTOR_DATA * d) { int iStart; /* Hold horses if pending command already. */ if (d->incomm[0] != '\0') return true; /* Check for overflow. */ iStart = strlen(d->inbuf); if (iStart >= (int) sizeof(d->inbuf) - 10) { write_to_descriptor(d->descriptor, "\n\r*** PUT A LID ON IT!!! ***\n\r", 0); return false; } /* Snarf input. */ for (;;) { int nRead; nRead = read(d->descriptor, d->inbuf + iStart, sizeof(d->inbuf) - 10 - iStart); if (nRead > 0) { iStart += nRead; if (d->inbuf[iStart - 1] == '\n' || d->inbuf[iStart - 1] == '\r') break; } else if (nRead == 0) return false; else if (errno == EWOULDBLOCK) break; else { perror("Read_from_descriptor"); return false; } } d->inbuf[iStart] = '\0'; return true; } /* * Transfer one line from input buffer to input line. */ void read_from_buffer(DESCRIPTOR_DATA * d) { int i, j, k; /* * Hold horses if pending command already. */ if (d->incomm[0] != '\0') return; /* * Look for at least one new line. */ for (i = 0; d->inbuf[i] != '\n' && d->inbuf[i] != '\r'; i++) { if (d->inbuf[i] == '\0') return; } /* * Canonical input processing. */ for (i = 0, k = 0; d->inbuf[i] != '\n' && d->inbuf[i] != '\r'; i++) { if (k >= MAX_INPUT_LENGTH - 2) { write_to_descriptor(d->descriptor, "Line too long.\n\r", 0); /* skip the rest of the line */ for (; d->inbuf[i] != '\0'; i++) { if (d->inbuf[i] == '\n' || d->inbuf[i] == '\r') break; } d->inbuf[i] = '\n'; d->inbuf[i + 1] = '\0'; break; } if (d->inbuf[i] == '\b' && k > 0) --k; else if (isascii(d->inbuf[i]) && isprint(d->inbuf[i])) d->incomm[k++] = d->inbuf[i]; } /* * Finish off the line. */ if (k == 0) d->incomm[k++] = ' '; d->incomm[k] = '\0'; /* * Deal with bozos with #repeat 1000 ... */ if (k > 1 || d->incomm[0] == '!') { if (d->incomm[0] != '!' && strcmp(d->incomm, d->inlast)) { d->repeat = 0; } else { if (++d->repeat >= 20) { write_to_descriptor(d->descriptor, "\n\r*** PUT A LID ON IT!!! ***\n\r", 0); strcpy(d->incomm, "quit"); } } } /* * Do '!' substitution. */ if (d->incomm[0] == '!') strcpy(d->incomm, d->inlast); else strcpy(d->inlast, d->incomm); /* * Shift the input buffer. */ while (d->inbuf[i] == '\n' || d->inbuf[i] == '\r') i++; for (j = 0; (d->inbuf[j] = d->inbuf[i + j]) != '\0'; j++) ; return; } /* * Low level output function. */ bool process_output(DESCRIPTOR_DATA * d, bool fPrompt) { extern bool merc_down; /* * Bust a prompt. */ if (fPrompt && !merc_down && d->connected == CON_PLAYING) { if (d->showstr_point) write_to_buffer(d, "[Please type (c)ontinue, (r)efresh, (b)ack, (h)elp, (q)uit, or RETURN]: ", 0); else { CHAR_DATA *ch; ch = d->original ? d->original : d->character; send_to_char("\n\r\n\r--> ", ch); write_to_buffer(d, go_ahead_str, 0); } } /* * Short-circuit if nothing to write. */ if (d->outtop == 0) return true; /* * Snoop-o-rama. */ if (d->snoop_by != NULL) { write_to_buffer(d->snoop_by, "% ", 2); write_to_buffer(d->snoop_by, d->outbuf, d->outtop); } /* * OS-dependent output. */ if (!write_to_descriptor(d->descriptor, d->outbuf, d->outtop)) { d->outtop = 0; return false; } else { d->outtop = 0; return true; } } /* * Append onto an output buffer. */ void write_to_buffer(DESCRIPTOR_DATA * d, const char *txt, int length) { /* * Find length in case caller didn't. */ if (length <= 0) length = strlen(txt); /* * Initial \n\r if needed. */ if (d->outtop == 0 && !d->fcommand) { d->outbuf[0] = '\n'; d->outbuf[1] = '\r'; d->outtop = 2; } /* * Expand the buffer as needed. */ while (d->outtop + length >= d->outsize) { char *outbuf; outbuf = (char *) alloc_mem(2 * d->outsize); strncpy(outbuf, d->outbuf, d->outtop); free_mem(d->outbuf, d->outsize); d->outbuf = outbuf; d->outsize *= 2; } /* * Copy. */ strcpy(d->outbuf + d->outtop, txt); d->outtop += length; return; } /* * Lowest level output function. * Write a block of text to the file descriptor. * If this gives errors on very long blocks (like 'ofind all'), * try lowering the max block size. */ bool write_to_descriptor(int desc, char *txt, int length) { int iStart; int nWrite; int nBlock; if (length <= 0) length = strlen(txt); for (iStart = 0; iStart < length; iStart += nWrite) { nBlock = UMIN(length - iStart, 4096); if ((nWrite = write(desc, txt + iStart, nBlock)) < 0) { perror("Write_to_descriptor"); return false; } } return true; } /* * Deal with sockets that haven't logged in yet. */ void nanny(DESCRIPTOR_DATA * d, char *argument) { char buf[MAX_STRING_LENGTH]; CHAR_DATA *ch; char *pwdnew; char *p; int lines; while (isspace(*argument)) argument++; ch = d->character; switch (d->connected) { default: close_socket(d); return; case CON_GET_NAME: if (argument[0] == '\0') { close_socket(d); return; } argument[0] = UPPER(argument[0]); if (!check_parse_name(argument)) { write_to_buffer(d, "Illegal name, try another.\n\rName: ", 0); return; } if (char_free == NULL) { ch = (CHAR_DATA *) alloc_perm(sizeof(*ch)); } else { ch = char_free; char_free = char_free->next; } clear_char(ch); d->character = ch; ch->desc = d; ch->name = str_dup(argument); ch->pwd = str_dup(""); ch->desc->pagelen = 20; /* New player */ /* New characters with same name fix by Salem's Lot */ if (check_playing(d, ch->name)) return; sprintf(buf, "Did I get that right, %s (Y/N)? ", argument); write_to_buffer(d, buf, 0); d->connected = CON_CONFIRM_NEW_NAME; return; break; case CON_GET_OLD_PASSWORD: write_to_buffer(d, "\n\r", 2); if (strcmp(crypt(argument, ch->pwd), ch->pwd)) { write_to_buffer(d, "Wrong password.\n\r", 0); close_socket(d); return; } write_to_buffer(d, echo_on_str, 0); if (check_playing(d, ch->name)) return; lines = ch->desc->pagelen; ch->desc->pagelen = 20; ch->desc->pagelen = lines; d->connected = CON_READ_MOTD; break; case CON_CONFIRM_NEW_NAME: switch (*argument) { case 'y': case 'Y': sprintf(buf, "New character.\n\rGive me a password for %s: %s", ch->name, echo_off_str); write_to_buffer(d, buf, 0); d->connected = CON_GET_NEW_PASSWORD; break; case 'n': case 'N': write_to_buffer(d, "Ok, what IS it, then? ", 0); free_char(d->character); d->character = NULL; d->connected = CON_GET_NAME; break; default: write_to_buffer(d, "Please type Yes or No? ", 0); break; } break; case CON_GET_NEW_PASSWORD: write_to_buffer(d, "\n\r", 2); if (strlen(argument) < 5) { write_to_buffer(d, "Password must be at least five characters long.\n\rPassword: ", 0); return; } pwdnew = crypt(argument, ch->name); for (p = pwdnew; *p != '\0'; p++) { if (*p == '~') { write_to_buffer(d, "New password not acceptable, try again.\n\rPassword: ", 0); return; } } free_string(ch->pwd); ch->pwd = str_dup(pwdnew); write_to_buffer(d, "Please retype password: ", 0); d->connected = CON_CONFIRM_NEW_PASSWORD; break; case CON_CONFIRM_NEW_PASSWORD: write_to_buffer(d, "\n\r", 2); if (strcmp(crypt(argument, ch->pwd), ch->pwd)) { write_to_buffer(d, "Passwords don't match.\n\rRetype password: ", 0); d->connected = CON_GET_NEW_PASSWORD; return; } write_to_buffer(d, echo_on_str, 0); write_to_buffer(d, "\n\r", 2); ch->desc->pagelen = 20; d->connected = CON_READ_MOTD; break; case CON_READ_MOTD: ch->next = char_list; char_list = ch; d->connected = CON_PLAYING; send_to_char ("\n\rWelcome to Merc Diku Mud. May your visit here be ... Mercenary.\n\r", ch); break; } return; } /* * Parse a name for acceptability. */ bool check_parse_name(char *name) { /* * Length restrictions. */ if (strlen(name) < 3) return false; if (strlen(name) > 12) return false; /* * Alphanumerics only. * Lock out IllIll twits. */ { char *pc; bool fIll; fIll = true; for (pc = name; *pc != '\0'; pc++) { if (!isalpha(*pc)) return false; if (LOWER(*pc) != 'i' && LOWER(*pc) != 'l') fIll = false; } if (fIll) return false; } return true; } /* * Check if already playing. */ bool check_playing(DESCRIPTOR_DATA * d, char *name) { DESCRIPTOR_DATA *dold; for (dold = descriptor_list; dold; dold = dold->next) { if (dold != d && dold->character != NULL && dold->connected != CON_GET_NAME && dold->connected != CON_GET_OLD_PASSWORD && !strcasecmp(name, dold->original ? dold->original->name : dold->character->name)) { write_to_buffer(d, "Already playing.\n\rName: ", 0); d->connected = CON_GET_NAME; if (d->character != NULL) { free_char(d->character); d->character = NULL; } return true; } } return false; } /* * Write to one char. */ void send_to_char(const char *txt, CHAR_DATA * ch) { if (txt == NULL || ch->desc == NULL) return; free_string(ch->desc->showstr_head); ch->desc->showstr_head = str_dup(txt); ch->desc->showstr_point = ch->desc->showstr_head; show_string(ch->desc, ""); } /* The heart of the pager. Thanks to N'Atas-Ha, ThePrincedom for porting this SillyMud code for MERC 2.0 and laying down the groundwork. Thanks to Blackstar, hopper.cs.uiowa.edu 4000 for which the improvements to the pager was modeled from. - Kahn */ void show_string(struct descriptor_data *d, char *input) { char buffer[MAX_STRING_LENGTH]; char buf[MAX_INPUT_LENGTH]; register char *scan, *chk; int lines = 0, toggle = 1; one_argument(input, buf); switch (UPPER(buf[0])) { case '\0': case 'C': /* show next page of text */ lines = 0; break; case 'R': /* refresh current page of text */ lines = -1 - (d->pagelen); break; case 'B': /* scroll back a page of text */ lines = -(2 * d->pagelen); break; case 'H': /* Show some help */ write_to_buffer(d, "C, or Return = continue, R = redraw this page,\n\r", 0); write_to_buffer(d, "B = back one page, H = this help, Q or other keys = exit.\n\r\n\r", 0); lines = -1 - (d->pagelen); break; default: /*otherwise, stop the text viewing */ if (d->showstr_head) { free_string(d->showstr_head); d->showstr_head = str_dup(""); } free_string(d->showstr_point); d->showstr_point = str_dup(""); return; } /* do any backing up necessary */ if (lines < 0) { for (scan = d->showstr_point; scan > d->showstr_head; scan--) if ((*scan == '\n') || (*scan == '\r')) { toggle = -toggle; if (toggle < 0) if (!(++lines)) break; } d->showstr_point = scan; } /* show a chunk */ lines = 0; toggle = 1; for (scan = buffer;; scan++, d->showstr_point++) if (((*scan = *d->showstr_point) == '\n' || *scan == '\r') && (toggle = -toggle) < 0) lines++; else if (!*scan || (d->character && lines >= d->pagelen)) { *scan = '\0'; write_to_buffer(d, buffer, strlen(buffer)); /* See if this is the end (or near the end) of the string */ for (chk = d->showstr_point; isspace(*chk); chk++); if (!*chk) { if (d->showstr_head) { free_string(d->showstr_head); d->showstr_head = 0; } d->showstr_point = 0; } return; } return; }