#include #include #include #include #include #include "gfx/types.h" #include "net/constants.h" #include "net/message_protocol.h" #include "client_network.h" #include "network_manager.h" #include "game_state.h" #include "terminal.h" #include "ui/desktop.h" #include "ui/i_window_content.h" #include "ui/ui_window.h" #include "ui/editor.h" #include "ui/login_screen.h" #include #include #include #include "debug/debug_overlay.h" void GameState::_init_desktop(void) { _desktop = std::make_unique(_screen_width, _screen_height, this); auto term = std::make_unique(this); auto term_window = std::make_unique("Terminal", 100, 100, 800, 500); term_window->set_content(std::move(term)); _desktop->add_window(std::move(term_window)); } void GameState::_run_server(void) { try { NetworkManager server("bettola_sp.db"); server.start(SINGLE_PLAYER_PORT); /* * Server's start() method is non-blocking, but NetworkManager * object must be kept alive. We'll just loop forever and let the OS * clean up the thread when main exits ;) */ while(true) { std::this_thread::sleep_for(std::chrono::seconds(5)); } } catch(const std::exception& e) { fprintf(stderr, "Single-player server thread exception: %s\n", e.what()); } } GameState::GameState(void) : _current_screen(Screen::MAIN_MENU), _screen_width(0), _screen_height(0), _is_single_player(false), _show_debug_overlay(false) {_debug_overlay = std::make_unique();} GameState::~GameState(void) = default; void GameState::init(int screen_width, int screen_height) { _screen_width = screen_width; _screen_height = screen_height; /* Create and connect the network client. */ _network = std::make_unique(); _main_menu = std::make_unique(screen_width, screen_height); } void GameState::start_single_player_now(int screen_width, int screen_height) { _is_single_player = true; _screen_width = screen_width; _screen_height = screen_height; fprintf(stdout, "Starting in single-player mode...\n"); std::thread server_thread(&GameState::_run_server, this); server_thread.detach(); std::this_thread::sleep_for(std::chrono::milliseconds(200)); _network = std::make_unique(); if(!_network->connect("127.0.0.1", SINGLE_PLAYER_PORT)) { /* TODO: Handle connection failure. */ } _current_screen = Screen::DESKTOP; _init_desktop(); } void GameState::handle_event(SDL_Event* event, int screen_width, int screen_height) { if(event->type == SDL_EVENT_KEY_DOWN && event->key.key == SDLK_D && (event->key.mod & SDL_KMOD_CTRL)) { _show_debug_overlay = !_show_debug_overlay; return; /* Consume the event. */ } switch(_current_screen) { case Screen::MAIN_MENU: if(_main_menu) { _main_menu->handle_event(event); } break; case Screen::LOGIN: if(_login_screen) { _login_screen->handle_event(event); } break; case Screen::BOOTING: /* TODO: */ break; case Screen::DESKTOP: if(_desktop) { _desktop->handle_event(event, screen_width, screen_height); } break; } } void GameState::update(float dt, int draw_calls, int shape_verts, int text_verts) { _debug_overlay->update(dt, draw_calls, shape_verts, text_verts); switch(_current_screen) { case Screen::MAIN_MENU: { if(!_main_menu) break; Screen next_screen = _main_menu->update(dt); if(next_screen != Screen::MAIN_MENU) { const MenuButton* clicked_button = _main_menu->get_clicked_button(); if(clicked_button) { _is_single_player = clicked_button->is_single_player; } _current_screen = next_screen; _main_menu.reset(); /* Free mem. */ if(_current_screen == Screen::LOGIN) { /* Connect to server. */ if(_is_single_player) { fprintf(stdout, "Starting in single-player mode...\n"); std::thread server_thread(&GameState::_run_server, this); server_thread.detach(); std::this_thread::sleep_for(std::chrono::milliseconds(200)); if(!_network->connect("127.0.0.1", SINGLE_PLAYER_PORT)) { /* TODO: Handle connection failure. */ } } else { if(!_network->connect("127.0.0.1", MULTIPLAYER_PORT)) { /* TODO: Handle connection failure. */ } } _login_screen = std::make_unique(_screen_width, _screen_height); } } break; } case Screen::LOGIN: { if(_login_screen && _login_screen->is_login_attempted()) { std::string username = _login_screen->get_username(); std::string password = _login_screen->get_password(); if(_login_screen->is_new_account_mode()) { std::string hostname = _login_screen->get_hostname(); _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_CREATE_ACCOUNT, {username, password, hostname})); } else { _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_LOGIN, {username, password})); } _login_screen->clear_login_attempt(); /* Try to spam my server now b.tch! */ } /* Check for server response to our login attempt. */ std::string server_msg; while(_network->poll_message(server_msg)) { net_protocol::Opcode opcode; std::vector args; net_protocol::parse_message(server_msg, opcode, args); switch(opcode) { case net_protocol::Opcode::S2C_LOGIN_SUCCESS: case net_protocol::Opcode::S2C_CREATE_ACCOUNT_SUCCESS: _current_screen = Screen::BOOTING; _login_screen.reset(); /* Free mem. */ _boot_sequence = std::make_unique(); break; case net_protocol::Opcode::S2C_LOGIN_FAIL: _login_screen->set_error_message(args.empty() ? "Login failed." : args[0]); break; case net_protocol::Opcode::S2C_CREATE_ACCOUNT_FAIL: _login_screen->set_error_message(args.empty() ? "Account creation failed." : args[0]); break; default: fprintf(stderr, "Recieved unexpected opcode %d during login.\n", static_cast(opcode)); break; } /* If we successfully changed screen, stop processing messages for this frame. */ if(_current_screen == Screen::BOOTING) { break; } } break; } case Screen::BOOTING: { if(!_boot_sequence) break; /* Shouldn't happen. */ if(_boot_sequence->is_finished()) { _current_screen = Screen::DESKTOP; _init_desktop(); _boot_sequence.reset(); /* Free mem. */ } break; } case Screen::DESKTOP: { std::string server_msg; while(_network->poll_message(server_msg)) { net_protocol::Opcode opcode; std::vector args; net_protocol::parse_message(server_msg, opcode, args); switch(opcode) { case net_protocol::Opcode::S2C_FILE_DATA: if(args.size() == 2) { auto editor = std::make_unique(args[0]); editor->set_buffer_content(args[1]); auto editor_window = std::make_unique(args[0].c_str(), 200, 200, 600, 400); editor_window->set_content(std::move(editor)); _desktop->add_window(std::move(editor_window)); } break; case net_protocol::Opcode::S2C_DISCONNECT: { IWindowContent* content = _desktop->get_focused_window() ? _desktop->get_focused_window()->get_content() : nullptr; Terminal* terminal = dynamic_cast(content); if(terminal) { terminal->add_history("Connection closed."); } } break; case net_protocol::Opcode::S2C_CLOSE_WINDOW: { if(_desktop) { UIWindow* focused_window = _desktop->get_focused_window(); if(focused_window) { focused_window->close(); } } } break; case net_protocol::Opcode::S2C_COMMAND_RESPONSE: { if(!args.empty()) { IWindowContent* content = _desktop->get_focused_window() ? _desktop->get_focused_window()->get_content() : nullptr; Terminal* terminal = dynamic_cast(content); if(!terminal) continue; /* Server sends "output\nprompt", split them. */ size_t last_newline = args[0].find_last_of('\n'); if(last_newline != std::string::npos) { std::string prompt = args[0].substr(last_newline+1); terminal->set_prompt(prompt); std::string output = args[0].substr(0, last_newline); if(!output.empty()) { /* Split multiline output and push each line to history. */ std::stringstream ss(output); std::string line; while(std::getline(ss, line, '\n')) { terminal->add_history(line); } } } else { terminal->add_history(args[0]); } } } break; } } if(_desktop) { _desktop->update(dt, _screen_width, _screen_height); } break; } } } void GameState::send_network_command(const std::string& command) { if(_network && _network->is_connected()) { _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_COMMAND, {command})); } } void GameState::render(const RenderContext& context) { switch(_current_screen) { case Screen::MAIN_MENU: if(_main_menu) { _main_menu->render(context.ui_renderer); } break; case Screen::LOGIN: if(_login_screen) { _login_screen->render(context); } break; case Screen::BOOTING: if(_boot_sequence) { _boot_sequence->render(context.ui_renderer); } break; case Screen::DESKTOP: if(_desktop) { _desktop->render(context); } break; } if(_show_debug_overlay) { _debug_overlay->render(context.ui_renderer); } } void GameState::send_file_write_request(const std::string& path, const std::string& content) { _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_WRITE_FILE, {path, content})); } void GameState::send_file_read_request(const std::string& path) { _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_READ_FILE, {path})); }