bettola/client/src/game_state.cpp

323 lines
11 KiB
C++

#include <cstdio>
#include <memory>
#include <sstream>
#include <thread>
#include <chrono>
#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 <SDL3/SDL_events.h>
#include <ui/main_menu.h>
#include <ui/boot_sequence.h>
#include "debug/debug_overlay.h"
void GameState::_init_desktop(void) {
_desktop = std::make_unique<Desktop>(_screen_width, _screen_height, this);
auto term = std::make_unique<Terminal>(this);
auto term_window = std::make_unique<UIWindow>("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<DebugOverlay>();}
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<ClientNetwork>();
_main_menu = std::make_unique<MainMenu>(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<ClientNetwork>();
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<LoginScreen>(_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<std::string> 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<BootSequence>();
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<int>(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<std::string> 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<Editor>(args[0]);
editor->set_buffer_content(args[1]);
auto editor_window = std::make_unique<UIWindow>(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<Terminal*>(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<Terminal*>(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}));
}