updated docs
[rlserver.git] / menu.c
blobe9169ef0231c3efb5d7c9037958473b9446508a3
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdint.h>
4 #include <string.h>
5 #include <sys/socket.h>
6 #include <sys/stat.h>
7 #include <pthread.h>
8 #include <time.h>
9 #include "util.h"
10 #include "telnet.h"
11 #include "users.h"
12 #include "games.h"
13 #include "server.h"
14 #include "run.h"
15 #include "log.h"
18 extern server_config config;
20 extern session_info *sessionlist;
21 extern pthread_mutex_t sessionlist_mutex;
23 #define BUF_SIZE 1 * 1024
26 // return 1 if enter is pressed
27 static int input (char c, char *str, int *pos, int len, char *out, int *out_pos)
29 // enter
30 if (c == '\n')
32 str[*pos] = 0;
33 out[*out_pos] = 0;
34 return 1;
37 // backspace
38 if (c == 8 || c == 127)
40 if (*pos > 0) *pos -= 1;
42 out[*out_pos] = 8;
43 if (*out_pos < len) *out_pos += 1;
45 else if (*pos < len - 1)
47 str[*pos] = c;
48 if (*pos < len) *pos += 1;
50 out[*out_pos] = c;
51 if (*out_pos < len) *out_pos += 1;
54 return 0;
59 static int recv_input (session_info *sess, char *inp, int len)
61 char buf[BUF_SIZE], out[BUF_SIZE];
62 int pos = 0;
65 while (1)
67 int opos = 0;
69 int n = recv(sess->socket, buf, BUF_SIZE, 0);
70 if (n == -1) return -1;
72 n = translate_telnet_input(buf, n, sess);
73 if (n > 0)
75 int i;
76 for (i = 0; i < n; i++)
78 if (input(buf[i], inp, &pos, len, out, &opos))
80 return pos;
85 // echo
86 if (send(sess->socket, out, opos, 0) == -1) return -1;
92 static int print_and_wait (session_info *sess, const char *msg)
94 char buf[BUF_SIZE];
97 if (send(sess->socket, msg, strlen(msg) + 1, 0) == -1) return -1;
99 while (1)
101 // wait for a keypress
102 int n = recv(sess->socket, buf, BUF_SIZE, 0);
103 if (n == -1) return -1;
105 if (translate_telnet_input(buf, n, sess) > 0) break;
108 return 0;
112 #define REG_INPUTS 3
114 static int register_menu (session_info *sess)
116 char data[REG_INPUTS][BUF_SIZE];
117 int id, r = 0;
118 const char *const prompts[REG_INPUTS] =
120 "\r\nUser name (3-16 chars, alphabet and digits only, start with a letter): ",
121 "\r\nPassword (4-16 chars, it is INSECURE, so do not use the same as email): ",
122 "\r\nEmail (optional): "
124 int lengths[REG_INPUTS][2] = { {MIN_USER_NAME_LEN, USER_NAME_LEN}, {MIN_USER_PASS_LEN, USER_PASS_LEN}, {0, USER_EMAIL_LEN} };
125 char buf[BUF_SIZE];
126 user usr;
129 // ask user for data
130 while (r < REG_INPUTS)
132 int len;
134 if (send(sess->socket, prompts[r], strlen(prompts[r]), 0) == -1) return -1;
136 len = recv_input(sess, data[r], lengths[r][1]);
137 if (len < 0) return -1;
139 if (len < lengths[r][0])
141 int l = sprintf(buf, "\r\nminimum %d symbols, try again: ", lengths[r][0]);
142 if (send(sess->socket, buf, l, 0) == -1) return -1;
143 continue;
146 r++;
150 // try to register using the data
151 id = add_user(&usr, data[0], data[1], data[2]);
152 if (id > 0)
154 int l;
156 sess->user_id = usr.id;
157 strcpy (sess->user_name, usr.name);
159 write_log (LOG_DEBUG, "registered user %s", usr.name);
161 l = sprintf(buf, "\r\nRegistration succeded\r\n");
163 if (send(sess->socket, buf, l, 0) == -1) return -1;
165 else
167 int l;
168 if (id == -1)
170 l = sprintf(buf, "\r\nInvalid name\r\n");
172 else if (id == -4)
174 l = sprintf(buf, "\r\nUser with such name already exists\r\n");
176 else
178 write_log (LOG_ERROR, "user registration failed, error %d", id);
180 l = sprintf(buf, "\r\nRegistration failed\r\n");
183 if (send(sess->socket, buf, l, 0) == -1) return -1;
186 // wait for a keypress
187 int n = recv(sess->socket, buf, BUF_SIZE, 0);
188 if (n == -1) return -1;
190 return 0;
195 static int login_menu (session_info *sess)
197 const char login_prompt[] = "\r\nUser name: ";
198 const char pass_prompt[] = "\r\nPassword: ";
199 char name[USER_NAME_LEN], pass[USER_PASS_LEN];
200 int id = 0;
201 user usr;
204 if (send(sess->socket, login_prompt, sizeof(login_prompt) - 1, 0) == -1) return -1;
205 if (recv_input(sess, name, USER_NAME_LEN) < 0) return -1;
207 if (send(sess->socket, pass_prompt, sizeof(pass_prompt) - 1, 0) == -1) return -1;
208 if (recv_input(sess, pass, USER_PASS_LEN) < 0) return -1;
210 // check user name and password
211 if (check_user_login(&usr, name, pass) < 0)
213 if (print_and_wait(sess, "\r\nLogin failed\r\n") == -1) return -1;
214 return -2;
217 // disallow simultaneous connections
218 if (user_is_connected(usr.id))
220 if (print_and_wait(sess, "\r\nYou are already logged in\r\n") == -1) return -1;
221 return -3;
224 sess->user_id = usr.id;
225 strcpy (sess->user_name, usr.name);
226 write_log (LOG_DEBUG, "user %s logs in", usr.name);
228 return id;
233 static int passwd_menu (session_info *sess)
235 const char old_prompt[] = "\r\nOld Password: ";
236 const char new_prompt[] = "\r\nNew Password: ";
237 char pass[USER_PASS_LEN];
238 user usr;
239 int len;
242 // ask for old password first
243 if (send(sess->socket, old_prompt, sizeof(old_prompt) - 1, 0) == -1) return -1;
244 if (recv_input(sess, pass, USER_PASS_LEN) < 0) return -1;
246 // check old password
247 if (check_user_login(&usr, sess->user_name, pass) < 0)
249 if (print_and_wait(sess, "\r\nWrong password\r\n") == -1) return -1;
250 return -2;
253 // ask for new password
254 if (send(sess->socket, new_prompt, sizeof(new_prompt) - 1, 0) == -1) return -1;
255 len = recv_input(sess, pass, USER_PASS_LEN);
256 if (len < 0) return -1;
258 if (len < MIN_USER_PASS_LEN)
260 if (print_and_wait(sess, "\r\nNew password is too short\r\n") == -1) return -1;
261 return -3;
264 if (change_password(sess->user_id, pass) == 0)
266 if (print_and_wait(sess, "\r\nPassword has been changed\r\n") == -1) return -1;
269 return 0;
274 static int observe (session_info *sess)
276 uint8_t buf[BUF_SIZE];
279 while (1)
281 int n = recv(sess->socket, buf, BUF_SIZE, 0);
282 if (n == -1) return -1;
284 if (buf[0] == 'q') break;
287 return 0;
292 static int observer_menu (session_info *sess)
294 char header[] = TELNET_CLS "\tGame\t\t\t\tUser\t\t Idle\t\tTerminal\r\n";
295 char buf[BUF_SIZE];
296 session_info *s;
297 int n, l, gn, count = 0;
298 time_t now;
300 char inp[20];
303 if (send(sess->socket, header, sizeof(header) - 1, 0) == -1) return -1;
305 pthread_mutex_lock (&sessionlist_mutex);
306 now = time(NULL);
307 for (s = sessionlist; s != NULL; s = s->next)
309 int idle;
311 if (s->game[0] == 0 || s->observer_count == -1) continue;
313 idle = now - s->last_activity;
315 l = sprintf(buf, "%3d\t%s", count + 1, s->game);
316 for (; l < 4 * 8; l++) buf[l] = ' ';
318 l += sprintf(&buf[l], "\t%s", s->user_name);
319 for (; l < 6 * 8; l++) buf[l] = ' ';
321 l += sprintf(&buf[l], "\t%3dm %02ds\t%dx%d\r\n", idle / 60, idle % 60, s->term_wid, s->term_hgt);
322 if (send(sess->socket, buf, l, 0) == -1) return -1;
323 count++;
325 pthread_mutex_unlock (&sessionlist_mutex);
326 l = sprintf(buf, "\r\n%d games total\r\n\r\n"
327 "Input number to observe or anything else to return to the main menu\r\n"
328 "You can press 'q' anytime to stop observing\r\n", count);
329 if (send(sess->socket, buf, l, 0) == -1) return -1;
332 // get number
333 n = recv_input(sess, inp, 20);
334 if (n == -1) return -1;
336 gn = atoi(inp);
337 if (gn > 0 && gn <= count)
339 // try to observe the specified game
340 if (add_observer(gn - 1, sess->socket))
342 int r = observe(sess);
343 stop_observing (sess);
344 if (r == -1) return -1;
348 return 0;
353 // if score == 1, display highscore list and exit rather than playing
354 static int play_menu (session_info *sess, int score)
356 const char msg[] = TELNET_CLS "Select a game by entering its id\r\n";
357 game_info *gamelist, *g;
358 char buf[BUF_SIZE];
359 int n;
360 char inp[20];
363 gamelist = load_gamelist();
365 if (send(sess->socket, msg, sizeof(msg), 0) == -1)
367 free_gamelist (gamelist);
368 return -1;
371 for (g = gamelist; g != NULL; g = g->next)
373 if (score && !g->cmd_score[0]) continue;
375 int l = sprintf(buf, "%8s: %s\r\n", g->id, g->name);
377 if (send(sess->socket, buf, l, 0) == -1)
379 free_gamelist (gamelist);
380 return -1;
385 // get id
386 n = recv_input(sess, inp, 20);
387 if (n == -1) return -1;
389 // find and run game with such id
390 for (g = gamelist; g != NULL; g = g->next)
392 if (score && !g->cmd_score[0]) continue;
394 if (strcmp(inp, g->id) == 0)
396 // hack for displaying score
397 if (score)
399 sess->observer_count = -1;
400 strcpy (g->cmd, g->cmd_score);
403 run_game (sess, g);
404 free_gamelist (gamelist);
406 if (print_and_wait(sess, "\r\nYou have left the game\r\n") == -1) return -1;
408 if (sess->observer_count == -1) sess->observer_count = 0;
410 return 1;
414 free_gamelist (gamelist);
416 return 0;
421 static void run_editor (session_info *sess, const game_info *g, int i)
423 game_info ed;
424 char name[BUF_SIZE];
425 int dirln;
428 // prefix with game directory name
429 if (g->dir[0]) dirln = sprintf(name, "%s/", g->dir);
430 else dirln = 0;
432 // copy file name to bufer
433 strcat (&name[dirln], g->files[i].copy);
435 // set up a a game struct for editor
436 memset (&ed, 0, sizeof(game_info));
437 strcpy (ed.id, "editor");
438 strcpy (ed.name, "editor");
439 ed.enter = '\n';
440 snprintf (ed.cmd, GAME_COMMAND_LEN, "%s %s", config.editor, name);
442 // disable observing
443 sess->observer_count = -1;
445 run_game (sess, &ed);
447 // enable observing again
448 sess->observer_count = 0;
453 static int edit_menu (session_info *sess)
455 const char msg[] = TELNET_CLS "Select a file to edit by entering its number\r\n";
456 game_info *gamelist, *g;
457 char buf[BUF_SIZE], name[BUF_SIZE];
458 int n, idx = 1;
459 char inp[20];
462 gamelist = load_gamelist();
464 if (send(sess->socket, msg, sizeof(msg), 0) == -1)
466 free_gamelist (gamelist);
467 return -1;
470 g = gamelist;
471 while (g != NULL)
473 struct stat st;
474 int l, i, dirln = 0;
476 for (i = 0; i < GAME_FILES; i++)
478 if (!g->files[i].file[0]) break;
479 if (!g->files[i].allow_edit) continue;
481 // prefix with game directory name, expanding user name
482 if (g->dir[0])
484 str_replace (name, BUF_SIZE, g->dir, "$USER$", sess->user_name);
485 strcat (name, "/");
486 dirln = strlen(name);
489 // copy file name to bufer, expanding user name
490 str_replace (&name[dirln], BUF_SIZE, g->files[i].copy, "$USER$", sess->user_name);
491 // file does not exist
492 if (stat(name, &st) != 0) continue;
494 l = sprintf(buf, "%3d: [%s] %s\r\n", idx, g->name, name);
496 if (send(sess->socket, buf, l, 0) == -1)
498 free_gamelist (gamelist);
499 return -1;
502 // we (ab)use the allow_edit to store index
503 // to find the file when the user enters a number
504 g->files[i].allow_edit = 0x80000000 | idx;
506 idx++;
509 g = g->next;
513 // get id
514 n = recv_input(sess, inp, 20);
515 if (n == -1) return -1;
517 n = 0x80000000 | atoi(inp);
519 // find and run game with such id
520 g = gamelist;
521 while (g != NULL)
523 int i;
525 for (i = 0; i < GAME_FILES; i++)
527 if (!g->files[i].file[0]) break;
528 if (g->files[i].allow_edit != n) continue;
530 run_editor (sess, g, i);
532 free_gamelist (gamelist);
534 return 0;
537 g = g->next;
540 free_gamelist (gamelist);
542 return 0;
547 static int print_main_menu (session_info *sess)
549 char msg[BUF_SIZE];
550 const char *reg, *name, *play;
553 // XXX this is ugly, clean it up someday
555 if (sess->user_id < 0)
557 reg = "r) Register as a user (needed in order to play)";
558 name = "player of roguelikes";
559 play = "l) Log in to play";
561 else
563 reg = "c) Change password";
564 name = sess->user_name;
565 play = "p) Play\r\n\te) Edit game options files";
568 int l = snprintf(msg, BUF_SIZE, TELNET_CLS "Welcome to the roguelike server, %s!\r\n"
569 "\t%s\r\n"
570 "\to) Observe running games\r\n"
571 "\ts) View high-score lists\r\n"
572 "\t%s\r\n"
573 "\tq) Leave\r\n", name, play, reg);
575 if (send(sess->socket, msg, l, 0) == -1) return -1;
576 return 0;
581 int main_menu (session_info *sess)
583 if (print_main_menu(sess) == -1) return -1;
585 while (1)
587 char buf[BUF_SIZE];
588 int n = recv(sess->socket, buf, BUF_SIZE, 0);
589 if (n == -1) return -1;
591 n = translate_telnet_input(buf, n, sess);
592 if (n > 0)
594 if (buf[0] == 'p' && sess->user_id >= 0)
596 if (play_menu(sess, 0) == -1) return -1;
597 if (print_main_menu(sess) == -1) return -1;
600 if (buf[0] == 's')
602 if (play_menu(sess, 1) == -1) return -1;
603 if (print_main_menu(sess) == -1) return -1;
606 if (buf[0] == 'e' && sess->user_id >= 0)
608 if (edit_menu(sess) == -1) return -1;
609 if (print_main_menu(sess) == -1) return -1;
612 if (buf[0] == 'l' && sess->user_id < 0)
614 if (login_menu(sess) == -1) return -1;
615 if (print_main_menu(sess) == -1) return -1;
618 if (buf[0] == 'r' && sess->user_id < 0)
620 if (register_menu(sess) == -1) return -1;
621 if (print_main_menu(sess) == -1) return -1;
624 if (buf[0] == 'c' && sess->user_id >= 0)
626 if (passwd_menu(sess) == -1) return -1;
627 if (print_main_menu(sess) == -1) return -1;
630 if (buf[0] == 'o')
632 if (observer_menu(sess) == -1) return -1;
633 if (print_main_menu(sess) == -1) return -1;
636 if (buf[0] == 'q') return 0;