diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ee15f4..c7a93d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,7 +55,8 @@ include_directories( dependencies/nanogui/ext/nanovg/src dependencies/optional-lite dependencies/tinyobjloader - lib/include) + lib/include + include) FILE( GLOB SOURCE_FILES diff --git a/Doxyfile b/Doxyfile index c779a5c..2c6e820 100644 --- a/Doxyfile +++ b/Doxyfile @@ -415,19 +415,19 @@ EXTRACT_ALL = YES # be included in the documentation. # The default value is: NO. -EXTRACT_PRIVATE = NO +EXTRACT_PRIVATE = YES # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. -EXTRACT_PACKAGE = NO +EXTRACT_PACKAGE = YES # If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. -EXTRACT_STATIC = NO +EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, @@ -758,7 +758,7 @@ WARN_LOGFILE = # spaces. # Note: If this tag is empty the current directory is searched. -INPUT = src/ +INPUT = src/ include/ lib/src/ lib/include/ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses diff --git a/include/cli.h b/include/cli.h new file mode 100644 index 0000000..f607bbf --- /dev/null +++ b/include/cli.h @@ -0,0 +1,32 @@ +#ifndef KONSTRUCTS_CLI_H +#define KONSTRUCTS_CLI_H + +#include "settings.h" + +namespace konstructs { + + /** + * This class contains cli logic + */ + class Cli { + + public: + + /** + * Print the cli command line options to stdout + */ + static void print_usage(); + + /** + * Simple argument parser that updates the settings struct + * @param argc From main() + * @param argv From main() + * @param settings The settings object to update + */ + static void argument_parser(int argc, char **argv, Settings *settings); + }; + +} + + +#endif //KONSTRUCTS_CLI_H diff --git a/include/gui.h b/include/gui.h new file mode 100644 index 0000000..1c26994 --- /dev/null +++ b/include/gui.h @@ -0,0 +1,88 @@ +#ifndef KONSTRUCTS_GUI_H +#define KONSTRUCTS_GUI_H + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wignored-attributes" +#include +#pragma GCC diagnostic pop + +#include "settings.h" +#include "konstructs.h" + +using Eigen::Vector2i; +using Eigen::Vector2f; +using nanogui::Screen; + +#define KONSTRUCTS_GUI_MENU_NORMAL 0 +#define KONSTRUCTS_GUI_MENU_POPUP 1 +#define KONSTRUCTS_GUI_MENU_RECONNECT 2 + +namespace konstructs { + + class GUI: public nanogui::Screen { + public: + GUI(Settings settings); + + /** + * Called by GLFW when the mouse scroll wheel is used. + */ + virtual bool scrollEvent(const Vector2i &p, const Vector2f &rel); + + /** + * Called by GLFW when a mouse button is pressed + */ + virtual bool mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers); + + /** + * Called by GLEW when a keyboard button is pressed + */ + virtual bool keyboardEvent(int key, int scancode, int action, int modifiers); + + virtual void draw(NVGcontext *ctx); + virtual void drawContents(); + + private: + + /** + * Display the main menu + * @param state KONSTRUCTS_GUI_MENU_NORMAL = No extra popups + * KONSTRUCTS_GUI_MENU_POPUP = A popup message + * KONSTRUCTS_GUI_MENU_RECONNECT = Server connection retry dialog + * @param message A message to be displayed + */ + void show_menu(int state, string message); + + /** + * Holds various konstructs data like for example shaders + * and the chunk processing facility. This is mainly here + * for legacy reasons and may be removed in the future. + */ + Konstructs konstructs_data; + + /** + * Show or hide the mouse pointer. + * @param state Mouse pointer state + */ + void show_pointer(bool state); + + /** + * Connect to the server, this is a convenience method wrapping + * network.setup_connection(..). + */ + bool connect(); + + /** + * Translate GLFW mouse button presses to 1 to 3 + * @param button The GLFW mouse button ID + */ + int translate_button(int button); + + bool menu_state; + Settings settings; + Network network; + nanogui::Window *window; + }; +} + + +#endif //KONSTRUCTS_GUI_H diff --git a/include/konstructs.h b/include/konstructs.h new file mode 100644 index 0000000..3d5a94e --- /dev/null +++ b/include/konstructs.h @@ -0,0 +1,149 @@ +#ifndef KONSTRUCTS_KONSTRUCTS_H +#define KONSTRUCTS_KONSTRUCTS_H + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wignored-attributes" +#include +#pragma GCC diagnostic pop + +#if defined(WIN32) +#define _WINSOCKAPI_ + #include + #include +#else +#include +#endif +#include +#include +#include +#include +#include "tiny_obj_loader.h" +#include "optional.hpp" +#include "matrix.h" +#include "shader.h" +#include "crosshair_shader.h" +#include "block.h" +#include "chunk.h" +#include "world.h" +#include "chunk_shader.h" +#include "sky_shader.h" +#include "selection_shader.h" +#include "hud.h" +#include "hud_shader.h" +#include "player_shader.h" +#include "textures.h" +#include "util.h" +#include "settings.h" +#include "network.h" + +#define KONSTRUCTS_APP_TITLE "Konstructs" +#define MOUSE_CLICK_DELAY_IN_FRAMES 15 + +namespace konstructs { + class Konstructs: public nanogui::Screen { + public: + + Konstructs(Settings settings); + ~Konstructs(); + + /** + * Called by GLFW when the mouse scroll wheel is used. + */ + virtual bool scrollEvent(const Vector2i &p, const Vector2f &rel); + + /** + * Called by GLFW when a mouse button is pressed + */ + virtual bool mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers); + + /** + * Called by GLEW when a keyboard button is pressed + */ + virtual bool keyboardEvent(int key, int scancode, int action, int modifiers); + + virtual void draw(NVGcontext *ctx); + virtual void drawContents(); + + // Members that are called from gui.cpp + Hud hud; + HudShader hud_shader; + + private: + + /** This function uses nanovg to print text on top of the screen. This is + * used for both the debug screen and messages sent from the server. + */ + void print_top_text(); + + /** + * Update view distance variable dependent on your framerate. + */ + bool update_view_distance(); + + /** + * If view distance has updated, set a new view radius. + */ + void update_radius(); + + /** + * Manage the mouse pointer. + */ + void handle_mouse(); + + /** + * Manage the keyboard. + */ + void handle_keys(); + + /** + * Close the hud and send a message to the server. + */ + void close_hud(); + + /** + * Returns the time of day + */ + float time_of_day(); + + /** + * TODO: What do this? + */ + float daylight(); + + /** + * Open and display the nanogui main menu + */ + void show_menu(int state, string message); + + BlockTypeInfo blocks; + CrosshairShader crosshair_shader; + int radius; + float view_distance; + float near_distance; + int day_length; + World world; + SkyShader sky_shader; + ChunkShader chunk_shader; + SelectionShader selection_shader; + PlayerShader *player_shader; + ChunkModelFactory model_factory; + Player player; + Vector3i player_chunk; + optional> looking_at; + double px; + double py; + FPS fps; + double last_frame; + bool debug_text_enabled; + nanogui::Window *window; + uint32_t frame; + uint32_t faces; + uint32_t max_faces; + double frame_time; + uint32_t click_delay; + Settings settings; + Network network; + }; +}; + +#endif //KONSTRUCTS_KONSTRUCTS_H diff --git a/include/network.h b/include/network.h new file mode 100644 index 0000000..8947c64 --- /dev/null +++ b/include/network.h @@ -0,0 +1,79 @@ +#ifndef KONSTRUCTS_NETWORK_H +#define KONSTRUCTS_NETWORK_H + +#if defined(WIN32) +#define _WINSOCKAPI_ + #include + #include +#else +#include +#endif +#include +#include +#include +#include +#include "tiny_obj_loader.h" +#include "optional.hpp" +#include "matrix.h" +#include "shader.h" +#include "crosshair_shader.h" +#include "block.h" +#include "chunk.h" +#include "world.h" +#include "client.h" +#include "chunk_shader.h" +#include "sky_shader.h" +#include "selection_shader.h" +#include "hud.h" +#include "hud_shader.h" +#include "player_shader.h" +#include "textures.h" +#include "util.h" +#include "settings.h" + +namespace konstructs { + class Network { + public: + Network(Settings settings); + void handle_network(Player player, + ChunkModelFactory *model_factory, + World world, + int radius, + uint32_t frame, + Hud hud, + GLFWwindow *mGLFWWindow, + PlayerShader *player_shader, + Vector3i player_chunk, + BlockTypeInfo blocks); + bool setup_connection(Settings::Server server, GLFWwindow *mGLFWWindow); + Client* get_client(); + + private: + void handle_packet(konstructs::Packet *packet, + Hud hud, + GLFWwindow *mGLFWWindow, + PlayerShader *player_shader, + Player player, + Vector3i player_chunk, + int radius, + BlockTypeInfo blocks); + void handle_player_packet(const string &str, + Player player, + Vector3i player_chunk, + int radius); + void handle_other_player_packet(const string &str, + PlayerShader *player_shader); + void handle_delete_other_player_packet(const string &str, + PlayerShader *player_shader); + void handle_block_type(const string &str, BlockTypeInfo blocks); + void handle_texture(konstructs::Packet *packet); + void handle_belt(const string &str, Hud hud); + void handle_inventory(const string &str, Hud hud); + void handle_held_stack(const string &str, Hud hud); + void handle_time(const string &str); + + Client client; + }; +}; + +#endif //KONSTRUCTS_NETWORK_H diff --git a/include/platform.h b/include/platform.h new file mode 100644 index 0000000..d02058b --- /dev/null +++ b/include/platform.h @@ -0,0 +1,22 @@ + +#ifndef KONSTRUCTS_PLATFORM_H +#define KONSTRUCTS_PLATFORM_H + +namespace konstructs { + + /** + * This class contains platform specific code + */ + class Platform { + + public: + + /** + * Init the winsock ddl under WIN32, do nothing on other platforms. + */ + static int init_winsock(); + }; +} + + +#endif //KONSTRUCTS_PLATFORM_H diff --git a/src/cli.cpp b/src/cli.cpp new file mode 100644 index 0000000..f25be12 --- /dev/null +++ b/src/cli.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include "cli.h" + +using namespace konstructs; + +void Cli::print_usage() { + std::cout << ("OPTIONS: -h/--help - Show this help") << std::endl; + std::cout << (" -s/--server
- Server to enter") << std::endl; + std::cout << (" -u/--username - Username to login") << std::endl; + std::cout << (" -p/--password - Passworld to login") << std::endl; + exit(0); +} + +void Cli::argument_parser(int argc, char **argv, Settings *settings) { + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + Cli::print_usage(); + } + if (strcmp(argv[i], "--server") == 0 || strcmp(argv[i], "-s") == 0) { + if (!argv[i+1]) { + Cli::print_usage(); + } else { + settings->server.address = argv[i+1]; + ++i; + } + } + if (strcmp(argv[i], "--username") == 0 || strcmp(argv[i], "-u") == 0) { + if (!argv[i+1]) { + Cli::print_usage(); + } else { + settings->server.username = argv[i+1]; + ++i; + } + } + if (strcmp(argv[i], "--password") == 0 || strcmp(argv[i], "-p") == 0) { + if (!argv[i+1]) { + Cli::print_usage(); + } else { + settings->server.password = argv[i+1]; + ++i; + } + } + if (strcmp(argv[i], "--debug") == 0 || strcmp(argv[i], "-d") == 0) { + settings->client.debug = true; + } + } +} diff --git a/src/gui.cpp b/src/gui.cpp new file mode 100644 index 0000000..27dd0be --- /dev/null +++ b/src/gui.cpp @@ -0,0 +1,143 @@ +#include "gui.h" + +using Eigen::Vector2i; +using Eigen::Vector2f; +using nanogui::Screen; +using namespace konstructs; + +GUI::GUI(Settings settings) : + Screen(Eigen::Vector2i(settings.client.window_width, + settings.client.window_height), + "Konstructs"), + konstructs_data(settings), + menu_state(false), + settings(settings), + network(settings) { + + performLayout(mNVGContext); + show_pointer(false); + Settings::Server server = settings.server; + if (server.username.size() > 0 && server.password.size() > 0 && server.address.size() > 0) { + connect(); + } else { + show_menu(KONSTRUCTS_GUI_MENU_NORMAL, string("Connect to a server")); + } + +} + +bool GUI::scrollEvent(const Vector2i &p, const Vector2f &rel) { + konstructs_data.hud.scroll(rel[1]); +} + +bool GUI::mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers) { + if (konstructs_data.hud.get_interactive()) { + if (down) { + double x, y; + glfwGetCursorPos(mGLFWWindow, &x, &y); + + auto clicked_at = konstructs_data.hud_shader.clicked_at(x, y, mSize.x(), mSize.y()); + + if (clicked_at) { + Vector2i pos = *clicked_at; + if (konstructs_data.hud.active(pos)) { + int index = pos[0] + pos[1] * 17; + network.get_client()->click_inventory(index, translate_button(button)); + } + } + } + } else if (!menu_state) { + // Clicking at the window captures the mouse pointer + glfwSetInputMode(mGLFWWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + } + return nanogui::Screen::mouseButtonEvent(p, button, down, modifiers); +} + +bool GUI::keyboardEvent(int key, int scancode, int action, int modifiers) { + // TODO +} + +void GUI::draw(NVGcontext *ctx) { + Screen::draw(ctx); +} + +void GUI::drawContents() { + // TODO +} + +void GUI::show_menu(int state, string message) { + using namespace nanogui; + + show_pointer(true); + glActiveTexture(GL_TEXTURE0); + + FormHelper *gui = new FormHelper(this); + window = gui->addWindow({0, 0}, "Main Menu"); + gui->setFixedSize({125, 20}); + + if (state == KONSTRUCTS_GUI_MENU_POPUP) { + // Popup message + + auto dlg = new MessageDialog(this, MessageDialog::Type::Warning, "Server connection", message); + } else if (state == KONSTRUCTS_GUI_MENU_RECONNECT) { + // Popup message with connect/cancel buttons. + + auto dlg = new MessageDialog(this, MessageDialog::Type::Warning, + "Server connection", message, + "Reconnect", "Cancel", true); + dlg->setCallback([&](int result) { + if (result == 0) { + window->dispose(); + menu_state = false; + connect(); + } + }); + } + + gui->addVariable("Server address", settings.server.address); + gui->addVariable("Username", settings.server.username); + gui->addVariable("Password", settings.server.password); + gui->addButton("Connect", [&]() { + if (settings.server.username != "" && + settings.server.password != "" && + settings.server.address != "") { + window->dispose(); + menu_state = false; + if (connect()) { + save_settings(settings); + } + } + }); + + window->center(); + performLayout(mNVGContext); + menu_state = true; +} + +void GUI::show_pointer(bool state) { + if (state) { + glfwSetInputMode(mGLFWWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } else { + glfwSetInputMode(mGLFWWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + } +} + +bool GUI::connect() { + if (network.setup_connection(settings.server, mGLFWWindow)) { + show_pointer(false); + return true; + } else { + show_menu(KONSTRUCTS_GUI_MENU_RECONNECT, "Error: Connection failed"); + return false; + } +} + +int GUI::translate_button(int button) { + switch (button) { + case GLFW_MOUSE_BUTTON_1: + return 1; + case GLFW_MOUSE_BUTTON_2: + return 2; + case GLFW_MOUSE_BUTTON_3: + return 3; + } +} diff --git a/src/konstructs.cpp b/src/konstructs.cpp new file mode 100644 index 0000000..e6e01e3 --- /dev/null +++ b/src/konstructs.cpp @@ -0,0 +1,358 @@ +#include "konstructs.h" +#include "network.h" + +using std::cout; +using std::cerr; +using std::endl; +using namespace konstructs; +using nonstd::optional; +using nonstd::nullopt; +using std::pair; + +Konstructs::Konstructs(Settings settings) : + player(0, Vector3f(0.0f, 0.0f, 0.0f), 0.0f, 0.0f), + px(0), py(0), + model_factory(blocks), + radius(settings.client.radius_start), + view_distance((float) settings.client.radius_start * CHUNK_SIZE), + near_distance(0.125f), + sky_shader(settings.client.field_of_view, SKY_TEXTURE, near_distance), + chunk_shader(settings.client.field_of_view, BLOCK_TEXTURES, DAMAGE_TEXTURE, SKY_TEXTURE, near_distance, + load_chunk_vertex_shader(), load_chunk_fragment_shader()), + hud_shader(17, 14, INVENTORY_TEXTURE, BLOCK_TEXTURES, FONT_TEXTURE, HEALTH_BAR_TEXTURE), + selection_shader(settings.client.field_of_view, near_distance, 0.52), + day_length(600), + last_frame(glfwGetTime()), + looking_at(nullopt), + hud(17, 14, 9), + debug_text_enabled(false), + frame(0), + click_delay(0), + settings(settings), + network(settings) { + + blocks.is_plant[SOLID_TYPE] = 0; + blocks.is_obstacle[SOLID_TYPE] = 1; + blocks.is_transparent[SOLID_TYPE] = 0; + blocks.state[SOLID_TYPE] = STATE_SOLID; + memset(&fps, 0, sizeof(fps)); + + tinyobj::shape_t shape = load_player(); + player_shader = new PlayerShader(settings.client.field_of_view, PLAYER_TEXTURE, SKY_TEXTURE, + near_distance, shape); +} + +Konstructs::~Konstructs() { + delete player_shader; +} + +bool Konstructs::keyboardEvent(int key, int scancode, int action, int modifiers) { + if (nanogui::Screen::keyboardEvent(key, scancode, action, modifiers)) { + return true; + } + if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { + if (hud.get_interactive()) { + close_hud(); + } else { + glfwSetInputMode(mGLFWWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } + } else if (key == GLFW_KEY_F1 && action == GLFW_PRESS) { + // TODO: implement this again when time has come ;) + } else if (key == settings.keys.debug && action == GLFW_PRESS) { + debug_text_enabled = !debug_text_enabled; + } else if (key == settings.keys.fly + && action == GLFW_PRESS + && settings.client.debug) { + player.fly(); + } else if (key == settings.keys.tertiary && action == GLFW_PRESS) { + if (hud.get_interactive()) { + close_hud(); + } else if (network.get_client()->is_connected()) { + if (looking_at) { + auto &l = *looking_at; + uint8_t direction = direction_from_vector(l.first.position, l.second.position); + uint8_t rotation = rotation_from_vector(direction, player.camera_direction()); + network.get_client()->click_at(1, l.second.position, 3, hud.get_selection(), direction, rotation); + } else { + network.get_client()->click_at(0, Vector3i::Zero(), 3, hud.get_selection(), 0, 0); + } + } + } else if (key > 48 && key < 58 && action == GLFW_PRESS) { + hud.set_selected(key - 49); + } else { + return false; + } + return true; +} + +void Konstructs::draw(NVGcontext *ctx) { + Screen::draw(ctx); +} + +void Konstructs::drawContents() { + using namespace nanogui; + update_fps(&fps); + frame++; + if (network.get_client()->is_connected()) { + network.handle_network(player, + &model_factory, + world, + radius, + frame, + hud, + mGLFWWindow, + player_shader, + player_chunk, + blocks); + handle_keys(); + handle_mouse(); + looking_at = player.looking_at(world, blocks); + glClear(GL_DEPTH_BUFFER_BIT); + for (auto model : model_factory.fetch_models()) { + chunk_shader.add(model); + } + sky_shader.render(player, mSize.x(), mSize.y(), time_of_day(), view_distance); + glClear(GL_DEPTH_BUFFER_BIT); + faces = chunk_shader.render(player, mSize.x(), mSize.y(), + daylight(), time_of_day(), radius, + view_distance, player_chunk); + if (faces > max_faces) { + max_faces = faces; + } + player_shader->render(player, mSize.x(), mSize.y(), + daylight(), time_of_day(), view_distance); + if (looking_at && !hud.get_interactive() && false) { // TODO !menu_state) { + selection_shader.render(player, mSize.x(), mSize.y(), + looking_at->second.position, view_distance); + } + glClear(GL_DEPTH_BUFFER_BIT); + if (!hud.get_interactive() && false ) { // TODO !menu_state) { + crosshair_shader.render(mSize.x(), mSize.y()); + } + double mx, my; + glfwGetCursorPos(mGLFWWindow, &mx, &my); + hud_shader.render(mSize.x(), mSize.y(), mx, my, hud, blocks); + update_radius(); + print_top_text(); + } +} + +/** This function uses nanovg to print text on top of the screen. This is + * used for both the debug screen and messages sent from the server. + */ +void Konstructs::print_top_text() { + int width, height; + glfwGetFramebufferSize(mGLFWWindow, &width, &height); + + ostringstream os; + if (debug_text_enabled) { + double frame_fps = 1.15 / frame_time; + os << std::fixed << std::setprecision(2); + os << "Server: " << settings.server.address + << " user: " << settings.server.username + << " x: " << player.position(0) + << " y: " << player.position(1) + << " z: " << player.position(2) + << std::endl; + if (looking_at) { + auto l = *looking_at; + uint8_t direction = direction_from_vector(l.first.position, l.second.position); + uint8_t rotation = rotation_from_vector(direction, player.camera_direction()); + os << "Pointing at x: " << l.second.position(0) << ", " + << "y: " << l.second.position(1) << ", " + << "z: " << l.second.position(2) << ", " + << "dir: " << direction_to_string[direction] << ", " + << "rot: " << rotation_to_string[rotation] + << std::endl; + } else { + os << "Pointing at nothing." << std::endl; + } + os << "View distance: " << view_distance << " (" << radius << "/" << network.get_client()->get_loaded_radius() << ") " + << "faces: " << faces << "(" << max_faces << ") " + << "FPS: " << fps.fps << "(" << frame_fps << ")" << endl; + os << "Chunks: " << world.size() << " " + << "models: " << chunk_shader.size() << endl; + os << "Model factory, waiting: " << model_factory.waiting() << " " + << "created: " << model_factory.total_created() << " " + << "empty: " << model_factory.total_empty() << " " + << "total: " << model_factory.total() << endl; + + } + + glActiveTexture(GL_TEXTURE0); + nvgFontBlur(mNVGContext, 0.8f); + nvgFontSize(mNVGContext, 20.0f); + nvgTextBox(mNVGContext, 10, 20, width - 10, os.str().c_str(), NULL); +} + +bool Konstructs::update_view_distance() { + double frame_fps = 1.15 / frame_time; + float fps = settings.client.frames_per_second; + + if (frame_fps > 0.0 && frame_fps < fps && radius > 1) { + view_distance = view_distance - (float) CHUNK_SIZE * 0.2f * ((fps - (float) frame_fps) / fps); + return true; + } else if (frame_fps >= fps + && radius < settings.client.radius_max + && model_factory.waiting() == 0 + && radius <= network.get_client()->get_loaded_radius()) { + view_distance = view_distance + 0.05f; + return true; + } else { + return false; + } +} + +void Konstructs::update_radius() { + if (update_view_distance()) { + int new_radius = (int) (view_distance / (float) CHUNK_SIZE) + 1; + radius = new_radius; + network.get_client()->set_radius(radius); + } +} + +// TODO: Remove +bool Konstructs::scrollEvent(const Vector2i &p, const Vector2f &rel) {} +bool Konstructs::mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers) {} + +void Konstructs::handle_mouse() { + /* + int exclusive = + glfwGetInputMode(mGLFWWindow, GLFW_CURSOR) == GLFW_CURSOR_DISABLED; + if (exclusive && (px || py)) { + double mx, my; + glfwGetCursorPos(mGLFWWindow, &mx, &my); + float m = 0.0025; + float drx = (mx - px) * m; + float dry = (my - py) * m; + + player.rotate_x(dry); + player.rotate_y(drx); + px = mx; + py = my; + + if (click_delay == 0) { + if (looking_at) { + auto &l = *looking_at; + uint8_t direction = direction_from_vector(l.first.position, l.second.position); + uint8_t rotation = rotation_from_vector(direction, player.camera_direction()); + if (glfwGetMouseButton(mGLFWWindow, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS) { + click_delay = MOUSE_CLICK_DELAY_IN_FRAMES; + network.get_client()->click_at(1, l.second.position, translate_button(GLFW_MOUSE_BUTTON_1), hud.get_selection(), + direction, rotation); + } else if (glfwGetMouseButton(mGLFWWindow, GLFW_MOUSE_BUTTON_2) == GLFW_PRESS && + player.can_place(l.first.position, world, blocks)) { + optional selected = hud.selected(); + if (selected) { + BlockData block = {selected->type, selected->health, + DIRECTION_UP, + ROTATION_IDENTITY + }; + if (blocks.is_orientable[block.type]) { + block.direction = direction; + block.rotation = rotation; + } + auto chunk_opt = + world.chunk_by_block(l.first.position); + if (chunk_opt) { + ChunkData updated_chunk = + chunk_opt->set(l.first.position, block); + world.insert(updated_chunk); + model_factory.create_models({updated_chunk.position}, world); + } + } + click_delay = MOUSE_CLICK_DELAY_IN_FRAMES; + network.get_client()->click_at(1, l.first.position, translate_button(GLFW_MOUSE_BUTTON_2), hud.get_selection(), + direction, rotation); + } else if (glfwGetMouseButton(mGLFWWindow, GLFW_MOUSE_BUTTON_3) == GLFW_PRESS) { + click_delay = MOUSE_CLICK_DELAY_IN_FRAMES; + network.get_client()->click_at(1, l.second.position, translate_button(GLFW_MOUSE_BUTTON_3), hud.get_selection(), + direction, rotation); + } + } else if (glfwGetMouseButton(mGLFWWindow, GLFW_MOUSE_BUTTON_3) == GLFW_PRESS) { + click_delay = MOUSE_CLICK_DELAY_IN_FRAMES; + network.get_client()->click_at(0, Vector3i::Zero(), translate_button(GLFW_MOUSE_BUTTON_3), hud.get_selection(), + 0, 0); + } + } else { + click_delay--; + } + } else { + glfwGetCursorPos(mGLFWWindow, &px, &py); + } + */ +} + +void Konstructs::handle_keys() { + int sx = 0; + int sz = 0; + bool jump = false; + bool sneak = false; + double now = glfwGetTime(); + double dt = now - last_frame; + frame_time = now - last_frame; + dt = MIN(dt, 0.2); + dt = MAX(dt, 0.0); + last_frame = now; + if (glfwGetKey(mGLFWWindow, settings.keys.up)) { + sz--; + } + if (glfwGetKey(mGLFWWindow, settings.keys.down)) { + sz++; + } + if (glfwGetKey(mGLFWWindow, settings.keys.left)) { + sx--; + } + if (glfwGetKey(mGLFWWindow, settings.keys.right)) { + sx++; + } + if (glfwGetKey(mGLFWWindow, settings.keys.jump)) { + jump = true; + } + if (glfwGetKey(mGLFWWindow, settings.keys.sneak)) { + sneak = true; + } + network.get_client()->position(player.update_position(sz, sx, (float) dt, world, + blocks, near_distance, jump, sneak), + player.rx(), player.ry()); + Vector3i new_chunk(chunked_vec(player.camera())); + if (new_chunk != player_chunk) { + player_chunk = new_chunk; + network.get_client()->set_player_chunk(player_chunk); + } +} + +void Konstructs::close_hud() { + hud.set_interactive(false); + glfwSetInputMode(mGLFWWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + network.get_client()->close_inventory(); + for (int i = 0; i < 17; i++) { + for (int j = 1; j < 14; j++) { + Vector2i pos(i, j); + hud.reset_background(pos); + hud.reset_stack(pos); + } + } +} + +float Konstructs::time_of_day() { + if (day_length <= 0) { + return 0.5; + } + float t; + t = glfwGetTime(); + t = t / day_length; + t = t - (int) t; + return t; +} + +float Konstructs::daylight() { + float timer = time_of_day(); + if (timer < 0.5) { + float t = (timer - 0.25) * 100; + return 1 / (1 + powf(2, -t)); + } else { + float t = (timer - 0.85) * 100; + return 1 - 1 / (1 + powf(2, -t)); + } +} diff --git a/src/main.cpp b/src/main.cpp index a2b6530..e8c5e0e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,839 +1,29 @@ - +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wignored-attributes" #include -#if defined(WIN32) - #define _WINSOCKAPI_ - #include - #include -#else - #include -#endif +#pragma GCC diagnostic pop + #include #include -#include -#include -#include "tiny_obj_loader.h" -#include "optional.hpp" -#include "matrix.h" -#include "shader.h" -#include "crosshair_shader.h" -#include "block.h" -#include "chunk.h" -#include "world.h" -#include "chunk_shader.h" -#include "sky_shader.h" -#include "selection_shader.h" -#include "hud.h" -#include "hud_shader.h" -#include "player_shader.h" -#include "textures.h" -#include "util.h" - -#define KONSTRUCTS_APP_TITLE "Konstructs" -#define MAX_PENDING_CHUNKS 64 -#define MOUSE_CLICK_DELAY_IN_FRAMES 15 +#include "platform.h" +#include "cli.h" +#include "gui.h" -using std::cout; -using std::cerr; -using std::endl; using namespace konstructs; -using nonstd::optional; -using nonstd::nullopt; -using std::pair; - -void print_usage(); - -void glfw_error(int error_code, const char *error_string); - -class Konstructs: public nanogui::Screen { -public: - Konstructs(Settings settings) : - nanogui::Screen(Eigen::Vector2i(settings.client.window_width, - settings.client.window_height), - KONSTRUCTS_APP_TITLE), - player(0, Vector3f(0.0f, 0.0f, 0.0f), 0.0f, 0.0f), - px(0), py(0), - model_factory(blocks), - radius(settings.client.radius_start), - client(settings.client.debug), - view_distance((float)settings.client.radius_start*CHUNK_SIZE), - near_distance(0.125f), - sky_shader(settings.client.field_of_view, SKY_TEXTURE, near_distance), - chunk_shader(settings.client.field_of_view, BLOCK_TEXTURES, DAMAGE_TEXTURE, SKY_TEXTURE, near_distance, - load_chunk_vertex_shader(), load_chunk_fragment_shader()), - hud_shader(17, 14, INVENTORY_TEXTURE, BLOCK_TEXTURES, FONT_TEXTURE, HEALTH_BAR_TEXTURE), - selection_shader(settings.client.field_of_view, near_distance, 0.52), - day_length(600), - last_frame(glfwGetTime()), - looking_at(nullopt), - hud(17, 14, 9), - menu_state(false), - debug_text_enabled(false), - frame(0), - click_delay(0), - settings(settings) { - - using namespace nanogui; - performLayout(mNVGContext); - glfwSetInputMode(mGLFWWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED); - Settings::Server server = settings.server; - if (server.username.size() > 0 && server.password.size() > 0 && server.address.size() > 0) { - setup_connection(); - } else { - show_menu(0, string("Connect to a server")); - } - blocks.is_plant[SOLID_TYPE] = 0; - blocks.is_obstacle[SOLID_TYPE] = 1; - blocks.is_transparent[SOLID_TYPE] = 0; - blocks.state[SOLID_TYPE] = STATE_SOLID; - memset(&fps, 0, sizeof(fps)); - - tinyobj::shape_t shape = load_player(); - player_shader = new PlayerShader(settings.client.field_of_view, PLAYER_TEXTURE, SKY_TEXTURE, - near_distance, shape); - } - - ~Konstructs() { - delete player_shader; - } - - virtual bool scrollEvent(const Vector2i &p, const Vector2f &rel) { - hud.scroll(rel[1]); - return true; - } - - virtual bool mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers) { - if(hud.get_interactive()) { - if(down) { - double x, y; - glfwGetCursorPos(mGLFWWindow, &x, &y); - - auto clicked_at = hud_shader.clicked_at(x, y, mSize.x(), mSize.y()); - - if(clicked_at) { - Vector2i pos = *clicked_at; - if(hud.active(pos)) { - int index = pos[0] + pos[1] * 17; - client.click_inventory(index, translate_button(button)); - } - } - } - } else if(!menu_state) { - // Clicking at the window captures the mouse pointer - glfwSetInputMode(mGLFWWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED); - } - return Screen::mouseButtonEvent(p, button, down, modifiers); - } - - virtual bool keyboardEvent(int key, int scancode, int action, int modifiers) { - if (Screen::keyboardEvent(key, scancode, action, modifiers)) { - return true; - } - if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { - if(hud.get_interactive()) { - close_hud(); - } else { - glfwSetInputMode(mGLFWWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL); - } - } else if (key == GLFW_KEY_F1 && action == GLFW_PRESS) { - // TODO: implement this again when time has come ;) - /*if (!menu_state) { - show_menu("","",""); - } else { - hide_menu(); - }*/ - } else if (key == settings.keys.debug && action == GLFW_PRESS) { - debug_text_enabled = !debug_text_enabled; - } else if (key == settings.keys.fly - && action == GLFW_PRESS - && settings.client.debug) { - player.fly(); - } else if(key == settings.keys.tertiary && action == GLFW_PRESS) { - if(hud.get_interactive()) { - close_hud(); - } else if (client.is_connected()) { - if(looking_at) { - auto &l = *looking_at; - uint8_t direction = direction_from_vector(l.first.position, l.second.position); - uint8_t rotation = rotation_from_vector(direction, player.camera_direction()); - client.click_at(1, l.second.position, 3, hud.get_selection(), direction, rotation); - } else { - client.click_at(0, Vector3i::Zero(), 3, hud.get_selection(), 0, 0); - } - } - } else if(key > 48 && key < 58 && action == GLFW_PRESS) { - hud.set_selected(key - 49); - } else { - return false; - } - return true; - } - - virtual void draw(NVGcontext *ctx) { - Screen::draw(ctx); - } - - virtual void drawContents() { - using namespace nanogui; - update_fps(&fps); - frame++; - if (client.is_connected()) { - handle_network(); - handle_keys(); - handle_mouse(); - looking_at = player.looking_at(world, blocks); - glClear(GL_DEPTH_BUFFER_BIT); - for(auto model : model_factory.fetch_models()) { - chunk_shader.add(model); - } - sky_shader.render(player, mSize.x(), mSize.y(), time_of_day(), view_distance); - glClear(GL_DEPTH_BUFFER_BIT); - faces = chunk_shader.render(player, mSize.x(), mSize.y(), - daylight(), time_of_day(), radius, - view_distance, player_chunk); - if(faces > max_faces) { - max_faces = faces; - } - player_shader->render(player, mSize.x(), mSize.y(), - daylight(), time_of_day(), view_distance); - if(looking_at && !hud.get_interactive() && !menu_state) { - selection_shader.render(player, mSize.x(), mSize.y(), - looking_at->second.position, view_distance); - } - glClear(GL_DEPTH_BUFFER_BIT); - if(!hud.get_interactive() && !menu_state) { - crosshair_shader.render(mSize.x(), mSize.y()); - } - double mx, my; - glfwGetCursorPos(mGLFWWindow, &mx, &my); - hud_shader.render(mSize.x(), mSize.y(), mx, my, hud, blocks); - update_radius(); - print_top_text(); - } else if(!menu_state) { - show_menu(2, client.get_error_message()); - } - } - -private: - - /** This function uses nanovg to print text on top of the screen. This is - * used for both the debug screen and messages sent from the server. - */ - void print_top_text() { - int width, height; - glfwGetFramebufferSize(mGLFWWindow, &width, &height); - - ostringstream os; - if (debug_text_enabled) { - double frame_fps = 1.15 / frame_time; - os << std::fixed << std::setprecision(2); - os << "Server: " << settings.server.address - << " user: " << settings.server.username - << " x: " << player.position(0) - << " y: " << player.position(1) - << " z: " << player.position(2) - << std::endl; - if(looking_at) { - auto l = *looking_at; - uint8_t direction = direction_from_vector(l.first.position, l.second.position); - uint8_t rotation = rotation_from_vector(direction, player.camera_direction()); - os << "Pointing at x: " << l.second.position(0) << ", " - << "y: " << l.second.position(1) << ", " - << "z: " << l.second.position(2) << ", " - << "dir: " << direction_to_string[direction] << ", " - << "rot: " << rotation_to_string[rotation] - << std::endl; - } else { - os << "Pointing at nothing." << std::endl; - } - os << "View distance: " << view_distance << " (" << radius << "/" << client.get_loaded_radius() << ") " - << "faces: " << faces << "(" << max_faces << ") " - << "FPS: " << fps.fps << "(" << frame_fps << ")" << endl; - os << "Chunks: " << world.size() << " " - << "models: " << chunk_shader.size() << endl; - os << "Model factory, waiting: " << model_factory.waiting() << " " - << "created: " << model_factory.total_created() << " " - << "empty: " << model_factory.total_empty() << " " - << "total: " << model_factory.total() << endl; - - } - - glActiveTexture(GL_TEXTURE0); - nvgFontBlur(mNVGContext, 0.8f); - nvgFontSize(mNVGContext, 20.0f); - nvgTextBox(mNVGContext, 10, 20, width - 10, os.str().c_str(), NULL); - } - - int translate_button(int button) { - switch(button) { - case GLFW_MOUSE_BUTTON_1: - return 1; - case GLFW_MOUSE_BUTTON_2: - return 2; - case GLFW_MOUSE_BUTTON_3: - return 3; - } - } - - bool update_view_distance() { - double frame_fps = 1.15 / frame_time; - float fps = settings.client.frames_per_second; - - if(frame_fps > 0.0 && frame_fps < fps && radius > 1) { - view_distance = view_distance - (float)CHUNK_SIZE * 0.2f * ((fps - (float)frame_fps) / fps); - return true; - } else if(frame_fps >= fps - && radius < settings.client.radius_max - && model_factory.waiting() == 0 - && radius <= client.get_loaded_radius()) { - view_distance = view_distance + 0.05f; - return true; - } else { - return false; - } - } - - void update_radius() { - if (update_view_distance()) { - int new_radius = (int)(view_distance / (float)CHUNK_SIZE) + 1; - radius = new_radius; - client.set_radius(radius); - } - } - - void handle_mouse() { - int exclusive = - glfwGetInputMode(mGLFWWindow, GLFW_CURSOR) == GLFW_CURSOR_DISABLED; - if (exclusive && (px || py)) { - double mx, my; - glfwGetCursorPos(mGLFWWindow, &mx, &my); - float m = 0.0025; - float drx = (mx - px) * m; - float dry = (my - py) * m; - - player.rotate_x(dry); - player.rotate_y(drx); - px = mx; - py = my; - - if(click_delay == 0) { - if(looking_at) { - auto &l = *looking_at; - uint8_t direction = direction_from_vector(l.first.position, l.second.position); - uint8_t rotation = rotation_from_vector(direction, player.camera_direction()); - if(glfwGetMouseButton(mGLFWWindow, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS) { - click_delay = MOUSE_CLICK_DELAY_IN_FRAMES; - client.click_at(1, l.second.position, translate_button(GLFW_MOUSE_BUTTON_1), hud.get_selection(), - direction, rotation); - } else if(glfwGetMouseButton(mGLFWWindow, GLFW_MOUSE_BUTTON_2) == GLFW_PRESS && - player.can_place(l.first.position, world, blocks)) { - optional selected = hud.selected(); - if(selected) { - BlockData block = { selected->type, selected->health, - DIRECTION_UP, - ROTATION_IDENTITY - }; - if(blocks.is_orientable[block.type]) { - block.direction = direction; - block.rotation = rotation; - } - auto chunk_opt = - world.chunk_by_block(l.first.position); - if(chunk_opt) { - ChunkData updated_chunk = - chunk_opt->set(l.first.position, block); - world.insert(updated_chunk); - model_factory.create_models({updated_chunk.position}, world); - } - } - click_delay = MOUSE_CLICK_DELAY_IN_FRAMES; - client.click_at(1, l.first.position, translate_button(GLFW_MOUSE_BUTTON_2), hud.get_selection(), - direction, rotation); - } else if(glfwGetMouseButton(mGLFWWindow, GLFW_MOUSE_BUTTON_3) == GLFW_PRESS) { - click_delay = MOUSE_CLICK_DELAY_IN_FRAMES; - client.click_at(1, l.second.position, translate_button(GLFW_MOUSE_BUTTON_3), hud.get_selection(), - direction, rotation); - } - } else if(glfwGetMouseButton(mGLFWWindow, GLFW_MOUSE_BUTTON_3) == GLFW_PRESS) { - click_delay = MOUSE_CLICK_DELAY_IN_FRAMES; - client.click_at(0, Vector3i::Zero(), translate_button(GLFW_MOUSE_BUTTON_3), hud.get_selection(), - 0, 0); - } - } else { - click_delay--; - } - } else { - glfwGetCursorPos(mGLFWWindow, &px, &py); - } - } - - void handle_keys() { - int sx = 0; - int sz = 0; - bool jump = false; - bool sneak = false; - double now = glfwGetTime(); - double dt = now - last_frame; - frame_time = now - last_frame; - dt = MIN(dt, 0.2); - dt = MAX(dt, 0.0); - last_frame = now; - if(glfwGetKey(mGLFWWindow, settings.keys.up)) { - sz--; - } - if(glfwGetKey(mGLFWWindow, settings.keys.down)) { - sz++; - } - if(glfwGetKey(mGLFWWindow, settings.keys.left)) { - sx--; - } - if(glfwGetKey(mGLFWWindow, settings.keys.right)) { - sx++; - } - if(glfwGetKey(mGLFWWindow, settings.keys.jump)) { - jump = true; - } - if(glfwGetKey(mGLFWWindow, settings.keys.sneak)) { - sneak = true; - } - client.position(player.update_position(sz, sx, (float)dt, world, - blocks, near_distance, jump, sneak), - player.rx(), player.ry()); - Vector3i new_chunk(chunked_vec(player.camera())); - if(new_chunk != player_chunk) { - player_chunk = new_chunk; - client.set_player_chunk(player_chunk); - } - } - - void close_hud() { - hud.set_interactive(false); - glfwSetInputMode(mGLFWWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED); - client.close_inventory(); - for(int i = 0; i < 17; i++) { - for(int j = 1; j < 14; j++) { - Vector2i pos(i, j); - hud.reset_background(pos); - hud.reset_stack(pos); - } - } - } - - void handle_network() { - for(auto packet : client.receive(100)) { - handle_packet(packet.get()); - } - Vector3f pos = player.position; - Vector3i player_chunk(chunked(pos[0]), chunked(pos[2]), chunked(pos[1])); - - auto prio = client.receive_prio_chunk(player_chunk); - - model_factory.update_player_chunk(player_chunk); - /* Insert prio chunk into world */ - if(prio) { - world.insert(*prio); - model_factory.create_models({(*prio).position}, world); - } - auto new_chunks = client.receive_chunks(1); - if(!new_chunks.empty()) { - std::vector positions; - positions.reserve(new_chunks.size()); - for(auto chunk : new_chunks) { - world.insert(chunk); - positions.push_back(chunk.position); - } - model_factory.create_models(positions, world); - } - if(frame % 7883 == 0) { - /* Book keeping */ - world.delete_unused_chunks(player_chunk, radius + KEEP_EXTRA_CHUNKS); - } - - } - - void handle_packet(konstructs::Packet *packet) { - switch(packet->type) { - case 'P': - handle_other_player_packet(packet->to_string()); - break; - case 'D': - handle_delete_other_player_packet(packet->to_string()); - break; - case 'U': - handle_player_packet(packet->to_string()); - break; - case 'W': - handle_block_type(packet->to_string()); - break; - case 'M': - handle_texture(packet); - break; - case 'G': - handle_belt(packet->to_string()); - break; - case 'I': - handle_inventory(packet->to_string()); - hud.set_interactive(true); - glfwSetInputMode(mGLFWWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL); - break; - case 'i': - handle_held_stack(packet->to_string()); - break; - case 'T': - handle_time(packet->to_string()); - break; - default: - cout << "UNKNOWN: " << packet->type << endl; - break; - } - } - - void handle_player_packet(const string &str) { - int pid; - float x, y, z, rx, ry; - - if(sscanf(str.c_str(), ",%d,%f,%f,%f,%f,%f", - &pid, &x, &y, &z, &rx, &ry) != 6) { - throw std::runtime_error(str); - } - player = Player(pid, Vector3f(x, y, z), rx, ry); - player_chunk = chunked_vec(player.camera()); - client.set_player_chunk(player_chunk); - client.set_radius(radius); - client.set_logged_in(true); - } - - void handle_other_player_packet(const string &str) { - int pid; - float x, y, z, rx, ry; - if(sscanf(str.c_str(), ",%d,%f,%f,%f,%f,%f", - &pid, &x, &y, &z, &rx, &ry) != 6) { - throw std::runtime_error(str); - } - player_shader->add(Player(pid, Vector3f(x, y, z), rx, ry)); - } - - void handle_delete_other_player_packet(const string &str) { - int pid; - - if(sscanf(str.c_str(), ",%d", - &pid) != 1) { - throw std::runtime_error(str); - } - player_shader->remove(pid); - } - - void handle_block_type(const string &str) { - int w, obstacle, transparent, left, right, top, bottom, front, back, orientable; - char shape[16]; - char state[16]; - if(sscanf(str.c_str(), ",%d,%15[^,],%15[^,],%d,%d,%d,%d,%d,%d,%d,%d,%d", - &w, shape, state, &obstacle, &transparent, &left, &right, - &top, &bottom, &front, &back, &orientable) != 12) { - throw std::runtime_error(str); - } - blocks.is_plant[w] = strncmp(shape, "plant", 16) == 0; - if(strncmp(state, "solid", 16) == 0) { - blocks.state[w] = STATE_SOLID; - } else if(strncmp(state, "liquid", 16) == 0) { - blocks.state[w] = STATE_LIQUID; - } else if(strncmp(state, "gas", 16) == 0) { - blocks.state[w] = STATE_GAS; - } else if(strncmp(state, "plasma", 16) == 0) { - blocks.state[w] = STATE_PLASMA; - } else { - throw std::invalid_argument("Invalid block type state received!"); - } - blocks.is_obstacle[w] = obstacle; - blocks.is_transparent[w] = transparent; - blocks.is_orientable[w] = orientable; - blocks.blocks[w][0] = left; - blocks.blocks[w][1] = right; - blocks.blocks[w][2] = top; - blocks.blocks[w][3] = bottom; - blocks.blocks[w][4] = front; - blocks.blocks[w][5] = back; - } - - void handle_texture(konstructs::Packet *packet) { - GLuint texture; - glGenTextures(1, &texture); - glActiveTexture(GL_TEXTURE0 + BLOCK_TEXTURES); - glBindTexture(GL_TEXTURE_2D, texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - load_png_texture_from_buffer(packet->buffer(), packet->size); - } - - void handle_belt(const string &str) { - uint32_t column, size, type, health; - if(sscanf(str.c_str(), ",%u,%u,%u,%u", - &column, &size, &type, &health) != 4) { - throw std::runtime_error(str); - } - - if(size < 1) { - hud.reset_belt(column); - } else { - hud.set_belt(column, {size, (uint16_t)type, (uint16_t)health}); - } - } - - void handle_inventory(const string &str) { - uint32_t index, size, type, health; - if(sscanf(str.c_str(), ",%u,%u,%u,%u", - &index, &size, &type, &health) != 4) { - throw std::runtime_error(str); - } - uint32_t row = index / 17; - uint32_t column = index % 17; - Vector2i pos(column, row); - - if(type == -1) { - hud.reset_background(pos); - hud.reset_stack(pos); - } else { - hud.set_background(pos, 2); - hud.set_stack(pos, {size, (uint16_t)type, (uint16_t)health}); - } - } - - void handle_held_stack(const string &str) { - uint32_t amount, type; - if(sscanf(str.c_str(), ",%u,%u", - &amount, &type) != 2) { - throw std::runtime_error(str); - } - if(type == -1) { - hud.reset_held(); - } else { - hud.set_held({amount, (uint16_t)type}); - } - } - - void handle_time(const string &str) { - long time_value; - if(sscanf(str.c_str(), ",%lu", &time_value) != 1) { - throw std::runtime_error(str); - } - glfwSetTime((double)time_value); - } - - float time_of_day() { - if (day_length <= 0) { - return 0.5; - } - float t; - t = glfwGetTime(); - t = t / day_length; - t = t - (int)t; - return t; - } - - float daylight() { - float timer = time_of_day(); - if (timer < 0.5) { - float t = (timer - 0.25) * 100; - return 1 / (1 + powf(2, -t)); - } else { - float t = (timer - 0.85) * 100; - return 1 - 1 / (1 + powf(2, -t)); - } - } - - - void show_menu(int state, string message) { - using namespace nanogui; - - glfwSetInputMode(mGLFWWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL); - glActiveTexture(GL_TEXTURE0); - - FormHelper *gui = new FormHelper(this); - window = gui->addWindow({0,0}, "Main Menu"); - gui->setFixedSize({125, 20}); - - if (state == 1) { - // Popup message - - auto dlg = new MessageDialog(this, MessageDialog::Type::Warning, "Server connection", message); - } else if (state == 2) { - // Popup message with connect/cancel buttons. - - auto dlg = new MessageDialog(this, MessageDialog::Type::Warning, - "Server connection", message, - "Reconnect", "Cancel", true); - dlg->setCallback([&](int result) { - if (result == 0) { - window->dispose(); - menu_state = false; - setup_connection(); - } - }); - } - - #if defined(KONSTRUCTS_SINGLE_PLAYER) - gui->addGroup("Singleplayer game"); - gui->addButton("Play", [&]() { - settings.server.username = "singleplayer"; - settings.server.password = "singleplayer"; - settings.server.address = "localhost"; - window->dispose(); - menu_state = false; - setup_connection(); - }); - gui->addGroup("Multiplayer"); - #endif - gui->addVariable("Server address", settings.server.address); - gui->addVariable("Username", settings.server.username); - gui->addVariable("Password", settings.server.password); - gui->addButton("Connect", [&]() { - if (settings.server.username != "" && - settings.server.password != "" && - settings.server.address != "") { - // Note: The mouse pointer is intentionally not locked here. - // See: setup_connection() - window->dispose(); - menu_state = false; - setup_connection(); - save_settings(settings); - } - }); - - window->center(); - performLayout(mNVGContext); - menu_state = true; - } - - void setup_connection() { - try { - client.open_connection(settings.server); - load_textures(); - client.set_connected(true); - - // Lock the mouse _after_ a successful connection. This prevents the - // player to get stuck if he or she connects to a server that drops - // the SYN. - glfwSetInputMode(mGLFWWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED); - } catch(const std::exception& ex) { - show_menu(1, client.get_error_message()); - } - } - - BlockTypeInfo blocks; - CrosshairShader crosshair_shader; - int radius; - float view_distance; - float near_distance; - int day_length; - World world; - SkyShader sky_shader; - ChunkShader chunk_shader; - SelectionShader selection_shader; - HudShader hud_shader; - PlayerShader *player_shader; - ChunkModelFactory model_factory; - Client client; - Player player; - Vector3i player_chunk; - optional> looking_at; - Hud hud; - double px; - double py; - FPS fps; - double last_frame; - bool menu_state; - bool debug_text_enabled; - nanogui::Window *window; - uint32_t frame; - uint32_t faces; - uint32_t max_faces; - double frame_time; - uint32_t click_delay; - Settings settings; -}; - -#ifdef WIN32 -int init_winsock() { - WORD wVersionRequested; - WSADATA wsaData; - int err; - - wVersionRequested = MAKEWORD(2, 2); - err = WSAStartup(wVersionRequested, &wsaData); - if (err != 0) { - printf("WSAStartup failed with error: %d\n", err); - return 1; - } - - if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { - printf("Could not find a usable version of Winsock.dll\n"); - WSACleanup(); - return 1; - } - - return 0; -} -#else -int init_winsock() { - return 0; -} -#endif - -void print_usage() { - printf("OPTIONS: -h/--help - Show this help\n"); - printf(" -s/--server
- Server to enter\n"); - printf(" -u/--username - Username to login\n"); - printf(" -p/--password - Passworld to login\n\n"); - exit(0); -} void glfw_error(int error_code, const char *error_string) { - cout << "GLFW Error[" << error_code << "]: " << error_string << endl; + std::cout << "GLFW Error[" << error_code << "]: " << error_string << std::endl; } - -int main(int argc, char ** argv) { +int main(int argc, char **argv) { Settings settings; load_settings(settings); save_settings(settings); - if (argc > 1) { - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { - print_usage(); - } - if (strcmp(argv[i], "--server") == 0 || strcmp(argv[i], "-s") == 0) { - if (!argv[i+1]) { - print_usage(); - } else { - settings.server.address = argv[i+1]; - ++i; - } - } - if (strcmp(argv[i], "--username") == 0 || strcmp(argv[i], "-u") == 0) { - if (!argv[i+1]) { - print_usage(); - } else { - settings.server.username = argv[i+1]; - ++i; - } - } - if (strcmp(argv[i], "--password") == 0 || strcmp(argv[i], "-p") == 0) { - if (!argv[i+1]) { - print_usage(); - } else { - settings.server.password = argv[i+1]; - ++i; - } - } - if (strcmp(argv[i], "--debug") == 0 || strcmp(argv[i], "-d") == 0) { - settings.client.debug = true; - } - } - - } + Cli::argument_parser(argc, argv, &settings); - if (init_winsock()) { + if (Platform::init_winsock()) { printf("Failed to load winsock"); return 1; } @@ -843,7 +33,7 @@ int main(int argc, char ** argv) { nanogui::init(); { - nanogui::ref app = new Konstructs(settings); + nanogui::ref app = new GUI(settings); app->drawAll(); app->setVisible(true); nanogui::mainloop(); @@ -852,10 +42,10 @@ int main(int argc, char ** argv) { nanogui::shutdown(); } catch (const std::runtime_error &e) { std::string error_msg = std::string("Caught a fatal error: ") + std::string(e.what()); - #if defined(WIN32) + #ifdef WIN32 MessageBoxA(nullptr, error_msg.c_str(), NULL, MB_ICONERROR | MB_OK); #else - std::cerr << error_msg << endl; + std::cerr << error_msg << std::endl; #endif return -1; } diff --git a/src/network.cpp b/src/network.cpp new file mode 100644 index 0000000..33e5f09 --- /dev/null +++ b/src/network.cpp @@ -0,0 +1,251 @@ +#include "network.h" + +using std::cout; +using std::cerr; +using std::endl; +using namespace konstructs; +using nonstd::optional; +using nonstd::nullopt; +using std::pair; + +Network::Network(Settings settings) : client(settings.client.debug) {} + +void Network::handle_network(Player player, + ChunkModelFactory *model_factory, + World world, + int radius, + uint32_t frame, + Hud hud, + GLFWwindow *mGLFWWindow, + PlayerShader *player_shader, + Vector3i player_chunk, + BlockTypeInfo blocks) { + for (auto packet : client.receive(100)) { + handle_packet(packet.get(), hud, mGLFWWindow, player_shader, player, player_chunk, radius, blocks); + } + Vector3f pos = player.position; + Vector3i _player_chunk(chunked(pos[0]), chunked(pos[2]), chunked(pos[1])); + + auto prio = client.receive_prio_chunk(_player_chunk); + + model_factory->update_player_chunk(_player_chunk); + /* Insert prio chunk into world */ + if (prio) { + world.insert(*prio); + model_factory->create_models({(*prio).position}, world); + } + auto new_chunks = client.receive_chunks(1); + if (!new_chunks.empty()) { + std::vector positions; + positions.reserve(new_chunks.size()); + for (auto chunk : new_chunks) { + world.insert(chunk); + positions.push_back(chunk.position); + } + model_factory->create_models(positions, world); + } + if (frame % 7883 == 0) { + /* Book keeping */ + world.delete_unused_chunks(_player_chunk, radius + KEEP_EXTRA_CHUNKS); + } + +} + +void Network::handle_packet(konstructs::Packet *packet, + Hud hud, + GLFWwindow *mGLFWWindow, + PlayerShader *player_shader, + Player player, + Vector3i player_chunk, + int radius, + BlockTypeInfo blocks) { + switch (packet->type) { + case 'P': + handle_other_player_packet(packet->to_string(), player_shader); + break; + case 'D': + handle_delete_other_player_packet(packet->to_string(), player_shader); + break; + case 'U': + handle_player_packet(packet->to_string(), player, player_chunk, radius); + break; + case 'W': + handle_block_type(packet->to_string(), blocks); + break; + case 'M': + handle_texture(packet); + break; + case 'G': + handle_belt(packet->to_string(), hud); + break; + case 'I': + handle_inventory(packet->to_string(), hud); + hud.set_interactive(true); + glfwSetInputMode(mGLFWWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + break; + case 'i': + handle_held_stack(packet->to_string(), hud); + break; + case 'T': + handle_time(packet->to_string()); + break; + default: + cout << "UNKNOWN: " << packet->type << endl; + break; + } +} + +void Network::handle_player_packet(const string &str, + Player player, + Vector3i player_chunk, + int radius) { + int pid; + float x, y, z, rx, ry; + + if (sscanf(str.c_str(), ",%d,%f,%f,%f,%f,%f", + &pid, &x, &y, &z, &rx, &ry) != 6) { + throw std::runtime_error(str); + } + player = Player(pid, Vector3f(x, y, z), rx, ry); + player_chunk = chunked_vec(player.camera()); + client.set_player_chunk(player_chunk); + client.set_radius(radius); + client.set_logged_in(true); +} + +void Network::handle_other_player_packet(const string &str, + PlayerShader *player_shader) { + int pid; + float x, y, z, rx, ry; + if (sscanf(str.c_str(), ",%d,%f,%f,%f,%f,%f", + &pid, &x, &y, &z, &rx, &ry) != 6) { + throw std::runtime_error(str); + } + player_shader->add(Player(pid, Vector3f(x, y, z), rx, ry)); +} + +void Network::handle_delete_other_player_packet(const string &str, + PlayerShader *player_shader) { + int pid; + + if (sscanf(str.c_str(), ",%d", + &pid) != 1) { + throw std::runtime_error(str); + } + player_shader->remove(pid); +} + +void Network::handle_block_type(const string &str, BlockTypeInfo blocks) { + int w, obstacle, transparent, left, right, top, bottom, front, back, orientable; + char shape[16]; + char state[16]; + if (sscanf(str.c_str(), ",%d,%15[^,],%15[^,],%d,%d,%d,%d,%d,%d,%d,%d,%d", + &w, shape, state, &obstacle, &transparent, &left, &right, + &top, &bottom, &front, &back, &orientable) != 12) { + throw std::runtime_error(str); + } + blocks.is_plant[w] = strncmp(shape, "plant", 16) == 0; + if (strncmp(state, "solid", 16) == 0) { + blocks.state[w] = STATE_SOLID; + } else if (strncmp(state, "liquid", 16) == 0) { + blocks.state[w] = STATE_LIQUID; + } else if (strncmp(state, "gas", 16) == 0) { + blocks.state[w] = STATE_GAS; + } else if (strncmp(state, "plasma", 16) == 0) { + blocks.state[w] = STATE_PLASMA; + } else { + throw std::invalid_argument("Invalid block type state received!"); + } + blocks.is_obstacle[w] = obstacle; + blocks.is_transparent[w] = transparent; + blocks.is_orientable[w] = orientable; + blocks.blocks[w][0] = left; + blocks.blocks[w][1] = right; + blocks.blocks[w][2] = top; + blocks.blocks[w][3] = bottom; + blocks.blocks[w][4] = front; + blocks.blocks[w][5] = back; +} + +void Network::handle_texture(konstructs::Packet *packet) { + GLuint texture; + glGenTextures(1, &texture); + glActiveTexture(GL_TEXTURE0 + BLOCK_TEXTURES); + glBindTexture(GL_TEXTURE_2D, texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + load_png_texture_from_buffer(packet->buffer(), packet->size); +} + +void Network::handle_belt(const string &str, Hud hud) { + uint32_t column, size, type, health; + if (sscanf(str.c_str(), ",%u,%u,%u,%u", + &column, &size, &type, &health) != 4) { + throw std::runtime_error(str); + } + + if (size < 1) { + hud.reset_belt(column); + } else { + hud.set_belt(column, {size, (uint16_t) type, (uint16_t) health}); + } +} + +void Network::handle_inventory(const string &str, Hud hud) { + uint32_t index, size, type, health; + if (sscanf(str.c_str(), ",%u,%u,%u,%u", + &index, &size, &type, &health) != 4) { + throw std::runtime_error(str); + } + uint32_t row = index / 17; + uint32_t column = index % 17; + Vector2i pos(column, row); + + if (type == -1) { + hud.reset_background(pos); + hud.reset_stack(pos); + } else { + hud.set_background(pos, 2); + hud.set_stack(pos, {size, (uint16_t) type, (uint16_t) health}); + } +} + +void Network::handle_held_stack(const string &str, Hud hud) { + uint32_t amount, type; + if (sscanf(str.c_str(), ",%u,%u", + &amount, &type) != 2) { + throw std::runtime_error(str); + } + if (type == -1) { + hud.reset_held(); + } else { + hud.set_held({amount, (uint16_t) type}); + } +} + +void Network::handle_time(const string &str) { + long time_value; + if (sscanf(str.c_str(), ",%lu", &time_value) != 1) { + throw std::runtime_error(str); + } + glfwSetTime((double) time_value); +} + +bool Network::setup_connection(Settings::Server server, GLFWwindow *mGLFWWindow) { + try { + client.open_connection(server); + /* TODO: We do not really know if the server accepted our protocol + version, or our username/password combo here. set_connected + will release a lock and start to receive data, and fail. */ + load_textures(); + client.set_connected(true); + return true; + } catch (const std::exception &ex) { + std::cerr << client.get_error_message() << std::endl; + return false; + } +} + +Client* Network::get_client() { + return &client; +} \ No newline at end of file diff --git a/src/platform.cpp b/src/platform.cpp new file mode 100644 index 0000000..9eecac7 --- /dev/null +++ b/src/platform.cpp @@ -0,0 +1,34 @@ +#include "platform.h" +#include +#ifdef WIN32 +#define _WINSOCKAPI_ +#include +#include +#endif + +using namespace konstructs; + +int Platform::init_winsock() { +#ifdef WIN32 + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD(2, 2); + err = WSAStartup(wVersionRequested, &wsaData); + if (err != 0) { + printf("WSAStartup failed with error: %d\n", err); + return 1; + } + + if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { + printf("Could not find a usable version of Winsock.dll\n"); + WSACleanup(); + return 1; + } + + return 0; +#else + return 0; +#endif +} \ No newline at end of file