diff --git a/README.md b/README.md index d59d93e48..9fb3ca438 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,9 @@ The long-term goal of this project is to provide a viable alternative to Minecra ## Project status -This list is non exhaustive. +This list is non complete. + +See also the roadmap for 1.0.0 [here](https://github.com/Unarelith/OpenMiner/wiki/Roadmap). ### Implemented features @@ -97,15 +99,15 @@ This list is non exhaustive. - World loading/saving (see [#26](https://github.com/Unarelith/OpenMiner/issues/26)) - Texture pack system (partially implemented, see [#34](https://github.com/Unarelith/OpenMiner/issues/34)) - Entities ([#90](https://github.com/Unarelith/OpenMiner/pull/90)) +- Day/night cycle with sun/moon/stars ([#154](https://github.com/Unarelith/OpenMiner/pull/154)) ### Missing features - Fluid propagation ([#62](https://github.com/Unarelith/OpenMiner/issues/62)) -- Day/night cycle with sun/moon display ([#73](https://github.com/Unarelith/OpenMiner/issues/73)) - Seed-based worldgen ([#116](https://github.com/Unarelith/OpenMiner/issues/116)) - Cave tunnels ([#118](https://github.com/Unarelith/OpenMiner/issues/118)) -- Clouds ([#52](https://github.com/Unarelith/OpenMiner/pull/52)) -- Particle system +- Clouds ([#156](https://github.com/Unarelith/OpenMiner/issues/156)) +- Particle system ([#155](https://github.com/Unarelith/OpenMiner/issues/155)) ## Screenshots diff --git a/docs/lua-api-sky.md b/docs/lua-api-sky.md index 6ed6cc01f..7f0ec5abd 100644 --- a/docs/lua-api-sky.md +++ b/docs/lua-api-sky.md @@ -13,6 +13,31 @@ mod:sky { fog_color = { day = {50, 153, 204}, }, + + daylight_cycle = { + speed = 1.0 + }, + + objects = { + sun = { + texture = "texture-sun", + size = 256, + }, + + moon = { + texture = "texture-moon_phases", + size = 256, + phases = { + count = 8, + size = 32 + } + }, + + stars = { + count = 1000, + size = 4, + } + } } ``` @@ -29,9 +54,26 @@ color = { } ``` -Possible values: +Attributes: + +- `day`: sky color at midday + +### `daylight_cycle` + +Day/night cycle parameters. + +Example: +```lua +daylight_cycle = { + speed = 1.0 +} +``` + +The example above is the minimal code required to add a day/night cycle to a sky. + +Attributes: -- `day`: Sky color at midday +- `speed`: speed of the cycle (default: `1.0`) ### `fog_color` @@ -44,9 +86,9 @@ fog_color = { } ``` -Possible values: +Attributes: -- `day`: Fog color at midday +- `day`: fog color at midday ### `id` @@ -59,3 +101,63 @@ id = "sky_nether" IDs are usually of the form `mod:sky` but the `mod:` prefix is prepended automatically so it's not needed. +### `objects` + +#### `moon` + +Moon attributes table. + +Example: +```lua +moon = { + texture = "texture-moon_phases" + size = 256, + phases = { + count = 8, + size = 32 + } +}, +``` + +Attributes: + +- `texture`: texture to use (without texture, the moon will use this color: (240, 240, 240)) +- `size`: size of the moon (default: `256`) +- `phases` + - `count`: amount of phases (default: `1`) + - `size`: size of the phase texture (default: `32`) + +#### `sun` + +Sun attribute table. + +Example: +```lua +sun = { + texture = "texture-sun", + size = 256, +}, +``` + +Attributes: + +- `texture`: texture to use (without texture, the sun will use this color: (255, 255, 0)) +- `size`: size of the sun (default: `256`) + +#### `stars` + +Stars attribute table. + +Example: +```lua +stars = { + count = 1000, + size = 4, +} +``` + +Attributes: + +- `count`: size of the sun (default: `1000`) +- `size`: size of the sun (default: `4`) + diff --git a/docs/network-protocol.md b/docs/network-protocol.md index d38d77d68..cd2abfa5a 100644 --- a/docs/network-protocol.md +++ b/docs/network-protocol.md @@ -47,6 +47,14 @@ _This packet has no field._ ### Clientbound +#### ServerTick + +Packet sent at the beginning of every server tick. + +| Field name | Field type | Notes | +| ------------- | ----------- | ---------------------------------------------------- | +| Current time | u64 | Current time in the server | + #### ServerClosed | Field name | Field type | Notes | diff --git a/external/gamekit b/external/gamekit index 1813cb06e..fb10f8bbf 160000 --- a/external/gamekit +++ b/external/gamekit @@ -1 +1 @@ -Subproject commit 1813cb06ef71c3d6f1ed80ca18a9f6b424af1c54 +Subproject commit fb10f8bbf8bcf88e77020a961412f52b83a125e6 diff --git a/mods/default/blocks.lua b/mods/default/blocks.lua index db0db4ab1..9a62b6684 100644 --- a/mods/default/blocks.lua +++ b/mods/default/blocks.lua @@ -152,6 +152,9 @@ mod:block { name = "Glowstone", tiles = "glowstone.png", is_light_source = true, + groups = { + om_material_stone = 1 + }, } mod:block { @@ -338,6 +341,9 @@ mod:block { id = "redstone_lamp", name = "Redstone Lamp", tiles = "redstone_lamp_off.png", + groups = { + om_material_stone = 1 + }, states = { { is_light_source = true, tiles = "redstone_lamp_on.png" } diff --git a/mods/default/sky.lua b/mods/default/sky.lua index f7726df8f..2c032ec5b 100644 --- a/mods/default/sky.lua +++ b/mods/default/sky.lua @@ -35,6 +35,31 @@ mod:sky { fog_color = { day = {50, 153, 204}, }, + + daylight_cycle = { + speed = 1.0 + }, + + objects = { + sun = { + texture = "texture-sun", -- FIXME: Use a path instead like block attribute 'tiles' + size = 256, + }, + + moon = { + texture = "texture-moon_phases", -- FIXME: ^ + size = 256, + phases = { + count = 8, + size = 32 + } + }, + + stars = { + count = 1000, + size = 4, + } + } } mod:sky { diff --git a/mods/default/tools.lua b/mods/default/tools.lua index 0e9f4a3b7..002abc92e 100644 --- a/mods/default/tools.lua +++ b/mods/default/tools.lua @@ -49,7 +49,7 @@ function register_tool(name, material, mining_speed, harvest_capability) elseif name == "shovel" then tool_def.effective_on = { "group:om_material_dirt", - "group:om_material:sand" + "group:om_material_sand" } tool_def.on_item_activated = function(pos, block, player, world, client, server) @@ -61,6 +61,10 @@ function register_tool(name, material, mining_speed, harvest_capability) tool_def.effective_on = { "group:om_material_wood" } + elseif name == "pickaxe" then + tool_def.effective_on = { + "group:om_material_stone" + } end mod:item(tool_def) diff --git a/resources/config/textures.xml b/resources/config/textures.xml index 32a4574df..2082e678a 100644 --- a/resources/config/textures.xml +++ b/resources/config/textures.xml @@ -2,7 +2,9 @@ + + diff --git a/resources/shaders/fog.f.glsl b/resources/shaders/fog.f.glsl index cf5ba69da..f814fc027 100644 --- a/resources/shaders/fog.f.glsl +++ b/resources/shaders/fog.f.glsl @@ -1,10 +1,11 @@ #version 120 uniform vec4 u_fogColor; +uniform vec4 u_skyColor; vec4 fog(vec4 color, float fogCoord, float fogStart, float fogEnd) { float fog = clamp((fogEnd - fogCoord) / (fogEnd - fogStart), 0.0, 1.0); - return mix(u_fogColor, color, fog); + return mix(vec4(u_skyColor.r, u_skyColor.g, u_skyColor.b, u_fogColor.a), color, fog); } diff --git a/resources/shaders/game.f.glsl b/resources/shaders/game.f.glsl index be92a4452..714baacb6 100644 --- a/resources/shaders/game.f.glsl +++ b/resources/shaders/game.f.glsl @@ -10,8 +10,12 @@ varying float v_blockFace; varying float v_dist; uniform int u_renderDistance; + uniform sampler2D u_tex; +uniform vec4 u_skyColor; +uniform float u_sunlightIntensity; + // Get light color vec4 light(vec4 color, vec3 lightColor, vec4 lightPosition, float ambientIntensity, float diffuseIntensity); @@ -24,6 +28,9 @@ void main() { float blockFace = floor(v_blockFace + 0.5); float lightCheck = floor(v_lightValue.x + 0.5); + // Discard if the pixel is too far away + if(blockFace > -1. && v_dist > u_renderDistance) discard; + // Get current pixel color and apply multiplier on grayscale textures vec4 color = v_color; if (v_texCoord.x > -0.99 && v_texCoord.y > -0.99) { @@ -50,6 +57,8 @@ void main() { float minBrightness = 2.0 / 16.0; if (lightCheck != -1.) { + float sunlight = clamp(v_lightValue.x * u_sunlightIntensity, 3, 15); + float ambientIntensity = max(max(v_lightValue.x, v_lightValue.y) / 16.0, minBrightness); float diffuseIntensity = max(v_lightValue.x, v_lightValue.y) / 32.0; @@ -64,7 +73,9 @@ void main() { if (blockFace == 2. || blockFace == 3.) ambientIntensity = max(ambientIntensity * 0.9, minBrightness); - color = light(color, vec3(1.0, 1.0, 1.0), v_coord3d, ambientIntensity, diffuseIntensity); + float lightval = clamp(sunlight / 15.0, v_lightValue.y / 15.0, 1.0); + + color = light(color, vec3(lightval, lightval, lightval), v_coord3d, ambientIntensity, diffuseIntensity); } color.rgb *= v_ambientOcclusion; diff --git a/resources/shaders/game.v.glsl b/resources/shaders/game.v.glsl index 5f3fe4ccc..1205300a3 100644 --- a/resources/shaders/game.v.glsl +++ b/resources/shaders/game.v.glsl @@ -49,7 +49,7 @@ void main() { v_lightValue = lightValue; if (ambientOcclusion != 5) { - const float aovalues[] = float[](0.5, 0.75, 0.9, 1.0); + const float aovalues[] = float[](0.2, 0.45, 0.75, 1.0); v_ambientOcclusion = aovalues[int(ambientOcclusion)]; } else { v_ambientOcclusion = 1.0; diff --git a/resources/shaders/skybox.f.glsl b/resources/shaders/skybox.f.glsl new file mode 100644 index 000000000..ed9f1d4eb --- /dev/null +++ b/resources/shaders/skybox.f.glsl @@ -0,0 +1,27 @@ +#version 120 + +varying vec4 v_color; +varying vec4 v_coord3d; +varying vec2 v_texCoord; + +uniform sampler2D u_tex; + +uniform vec4 u_skyColor; +uniform vec4 u_starColor; + +void main() { + vec4 color = v_color; + + if (v_texCoord.x > -0.99 && v_texCoord.y > -0.99) { + color = texture2D(u_tex, v_texCoord); + color += u_skyColor; + } + else if (color.a == 0) { + color = u_starColor; + } + + if (color.a < 0.3) discard; + + gl_FragColor = color; +} + diff --git a/resources/shaders/skybox.v.glsl b/resources/shaders/skybox.v.glsl new file mode 100644 index 000000000..36391927b --- /dev/null +++ b/resources/shaders/skybox.v.glsl @@ -0,0 +1,22 @@ +#version 120 + +attribute vec4 color; +attribute vec4 coord3d; +attribute vec2 texCoord; + +varying vec4 v_color; +varying vec4 v_coord3d; +varying vec2 v_texCoord; + +uniform mat4 u_modelMatrix; +uniform mat4 u_projectionMatrix; +uniform mat4 u_viewMatrix; + +void main() { + v_coord3d = u_modelMatrix * vec4(coord3d.xyz, 1.0); + v_color = color; + v_texCoord = texCoord; + + gl_Position = u_projectionMatrix * u_viewMatrix * v_coord3d; +} + diff --git a/resources/textures/moon_phases.png b/resources/textures/moon_phases.png new file mode 100644 index 000000000..82d7c4b44 Binary files /dev/null and b/resources/textures/moon_phases.png differ diff --git a/resources/textures/sun.png b/resources/textures/sun.png new file mode 100644 index 000000000..9b0559e44 Binary files /dev/null and b/resources/textures/sun.png differ diff --git a/source/client/core/Config.cpp b/source/client/core/Config.cpp index cc22b77d1..1a893c170 100644 --- a/source/client/core/Config.cpp +++ b/source/client/core/Config.cpp @@ -50,11 +50,12 @@ bool Config::isCrosshairVisible = true; // Graphics u16 Config::renderDistance = 8; +u8 Config::ambientOcclusion = 1; bool Config::isSmoothLightingEnabled = true; -bool Config::isAmbientOcclusionEnabled = false; bool Config::isWireframeModeEnabled = false; bool Config::isFullscreenModeEnabled = false; bool Config::isVerticalSyncEnabled = true; +bool Config::isStarRenderingEnabled = true; float Config::cameraFOV = 70.0f; u16 Config::screenWidth = 1600; u16 Config::screenHeight = 1050; @@ -90,16 +91,17 @@ void Config::loadConfigFromFile(const char *filename) { isCrosshairVisible = lua["isCrosshairVisible"].get_or(isCrosshairVisible); renderDistance = lua["renderDistance"].get_or(renderDistance); + ambientOcclusion = std::clamp(lua["ambientOcclusion"].get_or(ambientOcclusion), 0, 2); isSmoothLightingEnabled = lua["isSmoothLightingEnabled"].get_or(isSmoothLightingEnabled); - isAmbientOcclusionEnabled = lua["isAmbientOcclusionEnabled"].get_or(isAmbientOcclusionEnabled); isWireframeModeEnabled = lua["isWireframeModeEnabled"].get_or(isWireframeModeEnabled); isFullscreenModeEnabled = lua["isFullscreenModeEnabled"].get_or(isFullscreenModeEnabled); isVerticalSyncEnabled = lua["isVerticalSyncEnabled"].get_or(isVerticalSyncEnabled); + isStarRenderingEnabled = lua["isStarRenderingEnabled"].get_or(isStarRenderingEnabled); cameraFOV = lua["cameraFOV"].get_or(cameraFOV); screenWidth = lua["screenWidth"].get_or(screenWidth); screenHeight = lua["screenHeight"].get_or(screenHeight); - guiScale = lua["guiScale"].get_or(guiScale); - mipmapLevels = lua["mipmapLevels"].get_or(mipmapLevels); + guiScale = std::clamp(lua["guiScale"].get_or(guiScale), 1, 3); + mipmapLevels = std::clamp(lua["mipmapLevels"].get_or(mipmapLevels), 0, 4); mouseSensitivity = lua["mouseSensitivity"].get_or(mouseSensitivity); @@ -126,11 +128,12 @@ void Config::saveConfigToFile(const char *filename) { file << "isCrosshairVisible = " << (isCrosshairVisible ? "true" : "false") << std::endl; file << std::endl; file << "renderDistance = " << renderDistance << std::endl; + file << "ambientOcclusion = " << (u16)ambientOcclusion << std::endl; file << "isSmoothLightingEnabled = " << (isSmoothLightingEnabled ? "true" : "false") << std::endl; - file << "isAmbientOcclusionEnabled = " << (isAmbientOcclusionEnabled ? "true" : "false") << std::endl; file << "isWireframeModeEnabled = " << (isWireframeModeEnabled ? "true" : "false") << std::endl; file << "isFullscreenModeEnabled = " << (isFullscreenModeEnabled ? "true" : "false") << std::endl; file << "isVerticalSyncEnabled = " << (isVerticalSyncEnabled ? "true" : "false") << std::endl; + file << "isStarRenderingEnabled = " << (isStarRenderingEnabled ? "true" : "false") << std::endl; file << "cameraFOV = " << cameraFOV << std::endl; file << "screenWidth = " << screenWidth << std::endl; file << "screenHeight = " << screenHeight << std::endl; diff --git a/source/client/core/Config.hpp b/source/client/core/Config.hpp index 84764ea82..3ce6aa17b 100644 --- a/source/client/core/Config.hpp +++ b/source/client/core/Config.hpp @@ -45,11 +45,12 @@ namespace Config { // Graphics extern u16 renderDistance; + extern u8 ambientOcclusion; extern bool isSmoothLightingEnabled; - extern bool isAmbientOcclusionEnabled; extern bool isWireframeModeEnabled; extern bool isFullscreenModeEnabled; extern bool isVerticalSyncEnabled; + extern bool isStarRenderingEnabled; extern float cameraFOV; extern u16 screenWidth; extern u16 screenHeight; diff --git a/source/client/graphics/CelestialObject.cpp b/source/client/graphics/CelestialObject.cpp new file mode 100644 index 000000000..858199dec --- /dev/null +++ b/source/client/graphics/CelestialObject.cpp @@ -0,0 +1,116 @@ +/* + * ===================================================================================== + * + * OpenMiner + * + * Copyright (C) 2018-2020 Unarelith, Quentin Bazin + * Copyright (C) 2019-2020 the OpenMiner contributors (see CONTRIBUTORS.md) + * + * This file is part of OpenMiner. + * + * OpenMiner is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * OpenMiner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenMiner; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * ===================================================================================== + */ +#include +#include +#include +#include + +#include "CelestialObject.hpp" +#include "GameTime.hpp" +#include "Vertex.hpp" + +CelestialObject::CelestialObject() { +} + +void CelestialObject::setTexture(const std::string &textureName) { + if (textureName.empty()) return; + + m_texture = &gk::ResourceHandler::getInstance().get(textureName); + + m_isUpdateNeeded = true; +} + +void CelestialObject::updateVertexBuffer() const { + if (!m_width || !m_height) { + gkError() << "Can't update vertex buffer for celestial object of size 0"; + return; + } + + Vertex vertices[4] = { + // Rectangle vertices + {{0, m_width, 0, -1}}, + {{0, 0, 0, -1}}, + {{0, 0, m_height, -1}}, + {{0, m_width, m_height, -1}}, + }; + + for (u8 i = 0 ; i < 4 ; ++i) { + vertices[i].color[0] = m_color.r; + vertices[i].color[1] = m_color.g; + vertices[i].color[2] = m_color.b; + vertices[i].color[3] = m_color.a; + } + + if (m_texture) { + gk::FloatRect texRect{0, 0, 1, 1}; + + if (m_phaseCount && m_phaseSize && m_currentPhase < m_phaseCount) { + u16 currentPhaseX = m_currentPhase % (m_texture->getSize().x / m_phaseSize); + u16 currentPhaseY = m_currentPhase / (m_texture->getSize().x / m_phaseSize); + texRect.x = currentPhaseX * m_phaseSize / float(m_texture->getSize().x); + texRect.y = currentPhaseY * m_phaseSize / float(m_texture->getSize().y); + texRect.sizeX = m_phaseSize / float(m_texture->getSize().x); + texRect.sizeY = m_phaseSize / float(m_texture->getSize().y); + } + + vertices[0].texCoord[0] = texRect.x + texRect.sizeX; + vertices[0].texCoord[1] = texRect.y; + vertices[1].texCoord[0] = texRect.x; + vertices[1].texCoord[1] = texRect.y; + vertices[2].texCoord[0] = texRect.x; + vertices[2].texCoord[1] = texRect.y + texRect.sizeY; + vertices[3].texCoord[0] = texRect.x + texRect.sizeX; + vertices[3].texCoord[1] = texRect.y + texRect.sizeY; + } + + gk::VertexBuffer::bind(&m_vbo); + m_vbo.setData(sizeof(vertices), vertices, GL_STATIC_DRAW); + gk::VertexBuffer::bind(nullptr); + + m_isUpdateNeeded = false; +} + +void CelestialObject::draw(gk::RenderTarget &target, gk::RenderStates states) const { + if (m_isUpdateNeeded) + updateVertexBuffer(); + + states.transform.rotate(-GameTime::getCurrentTime(m_rotationOffset, m_rotationSpeed) * 360.f, m_rotationAxis); + states.transform *= getTransform(); + + states.vertexAttributes = VertexAttribute::All; + + if (m_texture) + states.texture = m_texture; + + static const GLubyte indices[] = { + 0, 1, 3, + 3, 1, 2 + }; + + target.drawElements(m_vbo, GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, indices, states); +} + diff --git a/source/client/graphics/CelestialObject.hpp b/source/client/graphics/CelestialObject.hpp new file mode 100644 index 000000000..fa1c90f97 --- /dev/null +++ b/source/client/graphics/CelestialObject.hpp @@ -0,0 +1,76 @@ +/* + * ===================================================================================== + * + * OpenMiner + * + * Copyright (C) 2018-2020 Unarelith, Quentin Bazin + * Copyright (C) 2019-2020 the OpenMiner contributors (see CONTRIBUTORS.md) + * + * This file is part of OpenMiner. + * + * OpenMiner is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * OpenMiner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenMiner; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * ===================================================================================== + */ +#ifndef CELESTIALOBJECT_HPP_ +#define CELESTIALOBJECT_HPP_ + +#include +#include +#include +#include + +class CelestialObject : public gk::Drawable, public gk::Transformable { + public: + CelestialObject(); + + float width() const { return m_width; } + float height() const { return m_height; } + + void setColor(const gk::Color &color) { m_color = color; m_isUpdateNeeded = true; } + void setSize(float width, float height) { m_width = width; m_height = height; m_isUpdateNeeded = true; } + void setTexture(const std::string &textureName); + void setPhaseCount(u16 phaseCount, u16 phaseSize) { m_phaseCount = phaseCount; m_phaseSize = phaseSize; m_isUpdateNeeded = true; } + void setCurrentPhase(u16 currentPhase) const { if (m_currentPhase != currentPhase) { m_currentPhase = currentPhase; m_isUpdateNeeded = true; } } + void setRotationOffset(u16 rotationOffset) { m_rotationOffset = rotationOffset; } + void setRotationSpeed(float rotationSpeed) { m_rotationSpeed = rotationSpeed; } + void setRotationAxis(const gk::Vector3f &rotationAxis) { m_rotationAxis = rotationAxis; } + + private: + void updateVertexBuffer() const; + + void draw(gk::RenderTarget &target, gk::RenderStates states) const override; + + gk::VertexBuffer m_vbo; + + gk::Color m_color = gk::Color::White; + + float m_width = 0.f; + float m_height = 0.f; + + const gk::Texture *m_texture = nullptr; + + mutable bool m_isUpdateNeeded = true; + + u16 m_phaseCount = 0; + u16 m_phaseSize = 0; + mutable u16 m_currentPhase = 0; + + u16 m_rotationOffset = 0; + float m_rotationSpeed = 1.f; + gk::Vector3f m_rotationAxis{0, 1, 0}; +}; + +#endif // CELESTIALOBJECT_HPP_ diff --git a/source/client/graphics/PlayerBox.cpp b/source/client/graphics/PlayerBox.cpp index c02ce6f45..a7b34dc91 100644 --- a/source/client/graphics/PlayerBox.cpp +++ b/source/client/graphics/PlayerBox.cpp @@ -272,7 +272,7 @@ void PlayerBox::updateVertexBuffer() { void PlayerBox::draw(gk::RenderTarget &target, gk::RenderStates states) const { // Subtract the camera position - see comment in ClientWorld::draw() - gk::Vector3d cameraPosition = m_camera.getDPosition(); + const gk::Vector3d &cameraPosition = m_camera.getDPosition(); states.transform.translate(m_x - cameraPosition.x, m_y - cameraPosition.y, m_z - cameraPosition.z); states.transform.rotate(m_viewAngleH, gk::Vector3{0, 0, 1}); diff --git a/source/client/graphics/Skybox.cpp b/source/client/graphics/Skybox.cpp new file mode 100644 index 000000000..ed714b777 --- /dev/null +++ b/source/client/graphics/Skybox.cpp @@ -0,0 +1,123 @@ +/* + * ===================================================================================== + * + * OpenMiner + * + * Copyright (C) 2018-2020 Unarelith, Quentin Bazin + * Copyright (C) 2019-2020 the OpenMiner contributors (see CONTRIBUTORS.md) + * + * This file is part of OpenMiner. + * + * OpenMiner is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * OpenMiner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenMiner; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * ===================================================================================== + */ +#include "ClientWorld.hpp" +#include "GameTime.hpp" +#include "Sky.hpp" +#include "Skybox.hpp" + +Skybox::Skybox(gk::Camera &camera, ClientWorld &world) : m_camera(camera), m_world(world) { + m_shader.createProgram(); + m_shader.addShader(GL_VERTEX_SHADER, "resources/shaders/skybox.v.glsl"); + m_shader.addShader(GL_FRAGMENT_SHADER, "resources/shaders/skybox.f.glsl"); + m_shader.linkProgram(); +} + +void Skybox::loadSky(const Sky &sky) { + const Sky::SunDefinition &sun = sky.sunDefinition(); + m_sun = CelestialObject{}; + m_sun.setSize(sun.size, sun.size); + m_sun.setPosition(500, -m_sun.width() / 2, -m_sun.height() / 2); + m_sun.setRotationSpeed(sky.daylightCycleSpeed()); + + try { + m_sun.setTexture(sun.texture); + } + catch (...) { + m_sun.setColor(gk::Color::Yellow); + gkWarning() << "Failed to load sun texture" << sun.texture; + } + + const Sky::MoonDefinition &moon = sky.moonDefinition(); + m_moon = CelestialObject{}; + m_moon.setSize(moon.size, moon.size); + m_moon.setPosition(-500, -m_moon.width() / 2, -m_moon.height() / 2); + m_moon.setPhaseCount(moon.phaseCount, moon.phaseSize); + m_moon.setCurrentPhase(0); + m_moon.setRotationSpeed(sky.daylightCycleSpeed()); + + try { + m_moon.setTexture(moon.texture); + } + catch (...) { + m_moon.setColor(gk::Color{240, 240, 240}); + gkWarning() << "Failed to load moon texture" << sun.texture; + } + + const Sky::StarsDefinition &stars = sky.starsDefinition(); + m_stars.clear(); + m_stars.reserve(stars.count); + for (int i = 0 ; i < stars.count ; ++i) { + auto &star = m_stars.emplace_back(); + star.setColor(gk::Color{0, 0, 0, 0}); + star.setSize(stars.size, stars.size); + + glm::vec3 v{rand() % 256, rand() % 256, rand() % 256}; + v = glm::normalize(v); + v *= 600 * (rand() % 2 * 2 - 1); + star.setPosition(v.x, v.y, v.z); + // star.setPosition(650 * ((rand() % 2) * 2 - 1), (rand() % 500) * 2 - 500, (rand() % 500) * 2 - 500); + + star.setRotationOffset(rand() % GameTime::dayLength); + star.setRotationSpeed(sky.daylightCycleSpeed()); + star.setRotationAxis({0, 1, 0}); + // Maybe sometimes stars could have a random axis? + // star.setRotationAxis({rand() % 100 / 100.f, rand() % 100 / 100.f, rand() % 100 / 100.f}); + } +} + +void Skybox::draw(gk::RenderTarget &target, gk::RenderStates states) const { + if (!m_world.sky()) return; + + float time = GameTime::getCurrentTime(0, m_world.sky()->daylightCycleSpeed()); + gk::Color skyColor = GameTime::getSkyColorFromTime(*m_world.sky(), time); + gk::Color starColor = m_world.sky()->color(); + + gk::Shader::bind(&m_shader); + m_shader.setUniform("u_skyColor", skyColor); + m_shader.setUniform("u_starColor", starColor); + gk::Shader::bind(nullptr); + + m_moon.setCurrentPhase((GameTime::getTicks() / GameTime::dayLength) % 8); + + states.shader = &m_shader; + + // Subtract the camera position - see comment in ClientWorld::draw() + const gk::Vector3d &cameraPosition = m_camera.getDPosition(); + states.transform.translate(cameraPosition.x, cameraPosition.y, cameraPosition.z - 50); + + if (m_sun.width() && m_sun.height()) + target.draw(m_sun, states); + + if (m_moon.width() && m_moon.height()) + target.draw(m_moon, states); + + if (Config::isStarRenderingEnabled && skyColor != starColor) { + for (auto &it : m_stars) + target.draw(it, states); + } +} + diff --git a/source/client/graphics/Skybox.hpp b/source/client/graphics/Skybox.hpp new file mode 100644 index 000000000..f54a9f7dc --- /dev/null +++ b/source/client/graphics/Skybox.hpp @@ -0,0 +1,58 @@ +/* + * ===================================================================================== + * + * OpenMiner + * + * Copyright (C) 2018-2020 Unarelith, Quentin Bazin + * Copyright (C) 2019-2020 the OpenMiner contributors (see CONTRIBUTORS.md) + * + * This file is part of OpenMiner. + * + * OpenMiner is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * OpenMiner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenMiner; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * ===================================================================================== + */ +#ifndef SKYBOX_HPP_ +#define SKYBOX_HPP_ + +#include +#include + +#include "CelestialObject.hpp" + +class ClientWorld; +class Sky; + +class Skybox : public gk::Drawable, public gk::Transformable { + public: + Skybox(gk::Camera &camera, ClientWorld &world); + + void loadSky(const Sky &sky); + + private: + void draw(gk::RenderTarget &target, gk::RenderStates states) const override; + + gk::Camera &m_camera; + ClientWorld &m_world; + + gk::Shader m_shader; + + CelestialObject m_sun; + CelestialObject m_moon; + + std::vector m_stars; +}; + +#endif // SKYBOX_HPP_ diff --git a/source/client/hud/BlockCursor.cpp b/source/client/hud/BlockCursor.cpp index 53da58a78..cd8c573a8 100644 --- a/source/client/hud/BlockCursor.cpp +++ b/source/client/hud/BlockCursor.cpp @@ -282,7 +282,7 @@ void BlockCursor::draw(gk::RenderTarget &target, gk::RenderStates states) const glCheck(glEnable(GL_DEPTH_TEST)); // Subtract the camera position - see comment in ClientWorld::draw() - gk::Vector3d cameraPosition = m_player.camera().getDPosition(); + const gk::Vector3d &cameraPosition = m_player.camera().getDPosition(); states.transform.translate(m_selectedBlock.x - cameraPosition.x, m_selectedBlock.y - cameraPosition.y, m_selectedBlock.z - cameraPosition.z); glCheck(glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)); diff --git a/source/client/hud/DebugOverlay.cpp b/source/client/hud/DebugOverlay.cpp index 1c67a9c41..15ef4b30a 100644 --- a/source/client/hud/DebugOverlay.cpp +++ b/source/client/hud/DebugOverlay.cpp @@ -33,6 +33,7 @@ #include "ClientWorld.hpp" #include "Config.hpp" #include "DebugOverlay.hpp" +#include "GameTime.hpp" DebugOverlay::DebugOverlay(const ClientPlayer &player, const ClientWorld &world) : m_player(player), m_world(world) @@ -84,6 +85,19 @@ void DebugOverlay::update() { stream << "Alive entities: " << m_world.scene().registry().alive(); stream << '\n'; stream << "Chunk updates: " << ClientChunk::chunkUpdatesPerSec; + stream << '\n'; + stream << "Ticks: " << GameTime::getTicks(); + stream << '\n'; + stream << "TPS: " << GameTime::getTicksPerSecond(); + stream << '\n'; + stream << "Game time: "; + + u32 day = GameTime::getCurrentDay(); + u16 hour = GameTime::getCurrentHour(); + u16 minute = GameTime::getCurrentMinute(); + stream << "Day " << day << " "; + stream << (hour < 10 ? "0" : "") << hour << ":"; + stream << (minute < 10 ? "0" : "") << minute; m_positionText.setString(stream.str()); } diff --git a/source/client/network/ClientCommandHandler.cpp b/source/client/network/ClientCommandHandler.cpp index 1ee3a4646..794224a04 100644 --- a/source/client/network/ClientCommandHandler.cpp +++ b/source/client/network/ClientCommandHandler.cpp @@ -37,6 +37,7 @@ #include "ConnectionErrorState.hpp" #include "DrawableComponent.hpp" #include "DrawableDef.hpp" +#include "GameTime.hpp" #include "LuaGUIState.hpp" #include "NetworkComponent.hpp" #include "PositionComponent.hpp" @@ -167,6 +168,14 @@ void ClientCommandHandler::setupCallbacks() { } }); + m_client.setCommandCallback(Network::Command::ServerTick, [this](Network::Packet &packet) { + if (!m_isSingleplayer) { // FIXME + sf::Uint64 ticks; + packet >> ticks; + GameTime::setTicks(ticks); + } + }); + m_client.setCommandCallback(Network::Command::ServerClosed, [this](Network::Packet &packet) { std::string message; packet >> message; diff --git a/source/client/states/GameState.cpp b/source/client/states/GameState.cpp index c3de47466..c86f1f9fa 100644 --- a/source/client/states/GameState.cpp +++ b/source/client/states/GameState.cpp @@ -24,14 +24,16 @@ * * ===================================================================================== */ +#include +#include #include #include #include #include -#include #include +#include #include #include #include @@ -40,6 +42,7 @@ #include "Events.hpp" #include "GameKey.hpp" #include "GameState.hpp" +#include "GameTime.hpp" #include "KeyboardHandler.hpp" #include "LuaGUIState.hpp" #include "PauseMenuState.hpp" @@ -56,6 +59,7 @@ GameState::GameState() m_clientCommandHandler.setupCallbacks(); m_camera.setAspectRatio((float)Config::screenWidth / Config::screenHeight); + m_camera.setFarClippingPlane(1000.0f); m_world.setClient(m_clientCommandHandler); m_world.setCamera(m_player.camera()); @@ -181,22 +185,19 @@ void GameState::update() { m_client.update(); - // Update far plane using render distance - static u16 oldRenderDistance = Config::renderDistance; - if (Config::renderDistance != oldRenderDistance) { - m_camera.setFarClippingPlane(Config::renderDistance * CHUNK_MAXSIZE); - oldRenderDistance = Config::renderDistance; + static const Sky *sky = nullptr; + if (sky != m_world.sky() && m_world.sky()) { + sky = m_world.sky(); + m_skybox.loadSky(*sky); } } void GameState::initShaders() { m_shader.createProgram(); - m_shader.addShader(GL_VERTEX_SHADER, "resources/shaders/game.v.glsl"); m_shader.addShader(GL_FRAGMENT_SHADER, "resources/shaders/light.f.glsl"); m_shader.addShader(GL_FRAGMENT_SHADER, "resources/shaders/fog.f.glsl"); m_shader.addShader(GL_FRAGMENT_SHADER, "resources/shaders/game.f.glsl"); - m_shader.linkProgram(); m_fbo.loadShader("screen"); @@ -207,15 +208,35 @@ void GameState::onGuiScaleChanged(const GuiScaleChangedEvent &event) { } void GameState::draw(gk::RenderTarget &target, gk::RenderStates states) const { - // FIXME: This uniform is not used anymore since water/leaves effects are disabled - // gk::Shader::bind(&m_shader); - // m_shader.setUniform("u_time", gk::GameClock::getInstance().getTicks()); - // gk::Shader::bind(nullptr); + gk::Shader::bind(&m_shader); + + if (m_world.sky()) { + if (m_world.sky()->daylightCycleSpeed()) { + float time = GameTime::getCurrentTime(0, m_world.sky()->daylightCycleSpeed()); + const gk::Color &color = GameTime::getSkyColorFromTime(*m_world.sky(), time); + glClearColor(color.r, color.g, color.b, color.a); + + m_shader.setUniform("u_skyColor", color); + m_shader.setUniform("u_sunlightIntensity", GameTime::getSunlightIntensityFromTime(time)); + } + else { + const gk::Color &color = m_world.sky()->color(); + glClearColor(color.r, color.g, color.b, color.a); + + m_shader.setUniform("u_skyColor", m_world.sky()->color()); + m_shader.setUniform("u_sunlightIntensity", 1.f); + } + } + + gk::Shader::bind(nullptr); m_fbo.begin(); states.shader = &m_shader; + target.setView(m_camera); + + target.draw(m_skybox, states); target.draw(m_world, states); for (auto &it : m_playerBoxes) diff --git a/source/client/states/GameState.hpp b/source/client/states/GameState.hpp index 47f6b6cf9..5b1956a62 100644 --- a/source/client/states/GameState.hpp +++ b/source/client/states/GameState.hpp @@ -43,6 +43,7 @@ #include "KeyboardHandler.hpp" #include "PlayerBox.hpp" #include "Registry.hpp" +#include "Skybox.hpp" class TextureAtlas; @@ -99,6 +100,8 @@ class GameState : public gk::ApplicationState { KeyboardHandler *m_keyboardHandler; bool m_areModKeysLoaded = false; + + Skybox m_skybox{m_camera, m_world}; }; #endif // GAMESTATE_HPP_ diff --git a/source/client/states/SettingsMenuState.cpp b/source/client/states/SettingsMenuState.cpp index cf4faac67..a2874572d 100644 --- a/source/client/states/SettingsMenuState.cpp +++ b/source/client/states/SettingsMenuState.cpp @@ -215,13 +215,21 @@ void SettingsMenuState::addGraphicsButtons() { Config::isSmoothLightingEnabled = !Config::isSmoothLightingEnabled; button.setText(std::string("Smooth Lighting: ") + (Config::isSmoothLightingEnabled ? "ON" : "OFF")); - m_aoButton->setEnabled(!Config::isSmoothLightingEnabled); - World::isReloadRequested = true; }); - m_aoButton = &addToggleButton("Ambient Occlusion", Config::isAmbientOcclusionEnabled, true); - m_aoButton->setEnabled(!Config::isSmoothLightingEnabled); + const std::string aoValueNames[3] = { + "OFF", + "Fast", + "Fancy" + }; + + m_menuWidget.addButton(std::string("Ambient Occlusion: ") + aoValueNames[Config::ambientOcclusion], [&, aoValueNames] (TextButton &button) { + Config::ambientOcclusion = (Config::ambientOcclusion + 1) % (Config::isSmoothLightingEnabled ? 3 : 2); + button.setText(std::string("Ambient Occlusion: ") + aoValueNames[Config::ambientOcclusion]); + + World::isReloadRequested = true; + }); m_menuWidget.addSlider("GUI Scale: " + std::to_string(Config::guiScale), [this] (SliderWidget &slider, u32 eventType) { slider.setText("GUI Scale: " + std::to_string(slider.getCurrentValue())); @@ -259,6 +267,8 @@ void SettingsMenuState::addGraphicsButtons() { slider.setText("Mipmap Levels: " + std::to_string(Config::mipmapLevels)); }, 0, 4, Config::mipmapLevels); + addToggleButton("Star Rendering", Config::isStarRenderingEnabled, false); + updateWidgetPosition(); } diff --git a/source/client/world/ChunkBuilder.cpp b/source/client/world/ChunkBuilder.cpp index 407bc7f64..6aabef520 100644 --- a/source/client/world/ChunkBuilder.cpp +++ b/source/client/world/ChunkBuilder.cpp @@ -260,7 +260,7 @@ inline void ChunkBuilder::addFace(s8f x, s8f y, s8f z, s8f f, const ClientChunk } auto addVertex = [&](u8 v) { - if (!Config::isAmbientOcclusionEnabled || Config::isSmoothLightingEnabled) + if (Config::ambientOcclusion != 1 || blockState.isLightSource()) vertices[v].ambientOcclusion = 5; if (blockState.drawType() == BlockDrawType::Liquid) @@ -411,10 +411,8 @@ inline u8 ChunkBuilder::getLightForVertex(Light light, s8f x, s8f y, s8f z, cons // continue; // If the chunk is initialized, add the light value to the total - if (lightValues[i] != -1) { - // float strength = ((surroundingBlocks[i] - normal == gk::Vector3i{x, y, z}) ? 1 : Config::aoStrength); - // total += lightValues[i] * strength; - // count += strength; + // But only add dark blocks if AO is set on Smooth Lighting + if (lightValues[i] != -1 && (Config::ambientOcclusion == 2 || lightValues[i] != 0)) { total += lightValues[i]; ++count; } diff --git a/source/client/world/ClientWorld.cpp b/source/client/world/ClientWorld.cpp index 398303e0e..cba91c1af 100644 --- a/source/client/world/ClientWorld.cpp +++ b/source/client/world/ClientWorld.cpp @@ -112,8 +112,7 @@ void ClientWorld::changeDimension(u16 dimensionID) { const Sky &sky = Registry::getInstance().getSkyFromStringID(dimension.sky()); m_sky = &sky; - glCheck(glClearColor(sky.color().r, sky.color().g, sky.color().b, sky.color().a)); - + // glCheck(glClearColor(sky.color().r, sky.color().g, sky.color().b, sky.color().a)); } void ClientWorld::receiveChunkData(Network::Packet &packet) { diff --git a/source/client/world/ClientWorld.hpp b/source/client/world/ClientWorld.hpp index d9aecc13b..0d57ffe67 100644 --- a/source/client/world/ClientWorld.hpp +++ b/source/client/world/ClientWorld.hpp @@ -68,6 +68,8 @@ class ClientWorld : public World, public gk::Drawable { std::size_t loadedChunkCount() const { return m_chunks.size(); } + const Sky *sky() const { return m_sky; } + private: void createChunkNeighbours(ClientChunk *chunk); diff --git a/source/common/core/GameTime.cpp b/source/common/core/GameTime.cpp new file mode 100644 index 000000000..ce5749de6 --- /dev/null +++ b/source/common/core/GameTime.cpp @@ -0,0 +1,72 @@ +/* + * ===================================================================================== + * + * OpenMiner + * + * Copyright (C) 2018-2020 Unarelith, Quentin Bazin + * Copyright (C) 2019-2020 the OpenMiner contributors (see CONTRIBUTORS.md) + * + * This file is part of OpenMiner. + * + * OpenMiner is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * OpenMiner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenMiner; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * ===================================================================================== + */ +#include + +#include + +#include "GameTime.hpp" +#include "Sky.hpp" + +u64 GameTime::s_ticks = 0; +u16 GameTime::s_ticksPerSecond = 0; + +float GameTime::getCurrentTime(float offset, float speed) { + return std::fmod((s_ticks + dayStartOffset) * daySpeed * speed + offset, dayLength) / dayLength; +} + +float GameTime::getSunlightIntensityFromTime(float time) { + static const float pi = 3.1415927f; + return std::clamp(0.5f + std::sin(2 * pi * time) * 2.0f, 0.0f, 1.0f); +} + +gk::Color GameTime::getSkyColorFromTime(const Sky &sky, float time) { + float sunlight = getSunlightIntensityFromTime(time); + + gk::Color skyColor = sky.color(); + skyColor.r = std::clamp(sunlight - (1.f - skyColor.r), 0.0f, skyColor.r); + skyColor.g = std::clamp(sunlight - (1.f - skyColor.g), 0.0f, skyColor.g); + skyColor.b = std::clamp(sunlight - (1.f - skyColor.b), 0.0f, skyColor.b); + skyColor.a = 1.f; + + return skyColor; +} + +void GameTime::updateTpsCounter() { + static u64 tpsTimer = gk::GameClock::getInstance().getTicks(true); + static u64 tpsStart = s_ticks; + + if (tpsStart > s_ticks) + tpsStart = s_ticks; + + u64 currentClockTicks = gk::GameClock::getInstance().getTicks(true); + if (currentClockTicks - tpsTimer > 1000) { + s_ticksPerSecond = floor((s_ticks - tpsStart) / ((currentClockTicks - tpsTimer) / 1000.0f) + 0.5f); + tpsTimer = currentClockTicks; + tpsStart = s_ticks; + } +} + diff --git a/source/common/core/GameTime.hpp b/source/common/core/GameTime.hpp new file mode 100644 index 000000000..01178c9da --- /dev/null +++ b/source/common/core/GameTime.hpp @@ -0,0 +1,70 @@ +/* + * ===================================================================================== + * + * OpenMiner + * + * Copyright (C) 2018-2020 Unarelith, Quentin Bazin + * Copyright (C) 2019-2020 the OpenMiner contributors (see CONTRIBUTORS.md) + * + * This file is part of OpenMiner. + * + * OpenMiner is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * OpenMiner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenMiner; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * ===================================================================================== + */ +#ifndef GAMETIME_HPP_ +#define GAMETIME_HPP_ + +#include + +class Sky; + +class GameTime { + public: + static constexpr float daySpeed = 1.f; + static constexpr u32 dayLength = 24000; + static constexpr u32 dayStartOffset = 0; + + // Note: These 3 functions are only needed in the client + static float getCurrentTime(float offset = 0.f, float speed = 1.f); + static float getSunlightIntensityFromTime(float time); + static gk::Color getSkyColorFromTime(const Sky &sky, float time); + + static void incrementTicks() { ++s_ticks; updateTpsCounter(); } + static void setTicks(u64 ticks) { s_ticks = ticks; updateTpsCounter(); } + + static u16 getTicksPerSecond() { return s_ticksPerSecond; } + static u64 getTicks() { return s_ticks; } + + static u32 getCurrentDay() { + return (s_ticks + dayStartOffset + 6000.f) / 1000.f / 24 + 1; + } + + static u8 getCurrentHour() { + return u64((s_ticks + dayStartOffset + 6000.f) / 1000.f) % 24; + } + + static u8 getCurrentMinute() { + return u64((s_ticks + dayStartOffset + 6000.f) / 1000.f * 60.0f) % 60; + } + + private: + static void updateTpsCounter(); + + static u64 s_ticks; + static u16 s_ticksPerSecond; +}; + +#endif // GAMETIME_HPP_ diff --git a/source/common/network/Network.cpp b/source/common/network/Network.cpp index d89f91007..9f1c8384e 100644 --- a/source/common/network/Network.cpp +++ b/source/common/network/Network.cpp @@ -37,6 +37,7 @@ std::string Network::commandToString(Network::Command command) { {Network::Command::ClientOk, "ClientOk"}, {Network::Command::ClientRefused, "ClientRefused"}, + {Network::Command::ServerTick, "ServerTick"}, {Network::Command::ServerClosed, "ServerClosed"}, {Network::Command::ChunkData, "ChunkData"}, diff --git a/source/common/network/Network.hpp b/source/common/network/Network.hpp index 9a883effb..ed8b3ff04 100644 --- a/source/common/network/Network.hpp +++ b/source/common/network/Network.hpp @@ -40,40 +40,41 @@ namespace Network { ClientOk = 0x02, ClientRefused = 0x03, - ServerClosed = 0x04, + ServerTick = 0x04, + ServerClosed = 0x05, - ChunkData = 0x05, - ChunkRequest = 0x06, + ChunkData = 0x06, + ChunkRequest = 0x07, - PlayerPlaceBlock = 0x07, - PlayerDigBlock = 0x08, - PlayerInvUpdate = 0x09, - PlayerPosUpdate = 0x0a, - PlayerRotUpdate = 0x0b, - PlayerSpawn = 0x0c, - PlayerChangeDimension = 0x0d, - PlayerHeldItemChanged = 0x0e, + PlayerPlaceBlock = 0x08, + PlayerDigBlock = 0x09, + PlayerInvUpdate = 0x0a, + PlayerPosUpdate = 0x0b, + PlayerRotUpdate = 0x0c, + PlayerSpawn = 0x0d, + PlayerChangeDimension = 0x0e, + PlayerHeldItemChanged = 0x0f, - BlockUpdate = 0x0f, - BlockActivated = 0x10, - BlockGUIData = 0x11, - BlockInvUpdate = 0x12, - BlockDataUpdate = 0x13, + BlockUpdate = 0x10, + BlockActivated = 0x11, + BlockGUIData = 0x12, + BlockInvUpdate = 0x13, + BlockDataUpdate = 0x14, - ItemActivated = 0x14, + ItemActivated = 0x15, - RegistryData = 0x15, + RegistryData = 0x16, - ChatMessage = 0x16, + ChatMessage = 0x17, - EntitySpawn = 0x17, - EntityDespawn = 0x18, - EntityPosition = 0x19, - EntityRotation = 0x1a, - EntityAnimation = 0x1b, - EntityDrawableDef = 0x1c, + EntitySpawn = 0x18, + EntityDespawn = 0x19, + EntityPosition = 0x1a, + EntityRotation = 0x1b, + EntityAnimation = 0x1c, + EntityDrawableDef = 0x1d, - KeyPressed = 0x1d, + KeyPressed = 0x1e, }; std::string commandToString(Command command); diff --git a/source/common/world/Sky.cpp b/source/common/world/Sky.cpp index 0f69ac814..820b51134 100644 --- a/source/common/world/Sky.cpp +++ b/source/common/world/Sky.cpp @@ -33,10 +33,20 @@ Sky::Sky(u16 id, const std::string &stringID) { } void Sky::serialize(sf::Packet &packet) const { - packet << m_id << m_stringID << m_color << m_fogColor; + packet << m_id << m_stringID << m_color << m_fogColor + << m_sunDefinition.texture << m_sunDefinition.size + << m_moonDefinition.texture << m_moonDefinition.size + << m_moonDefinition.phaseCount << m_moonDefinition.phaseSize + << m_starsDefinition.count << m_starsDefinition.size + << m_daylightCycleSpeed; } void Sky::deserialize(sf::Packet &packet) { - packet >> m_id >> m_stringID >> m_color >> m_fogColor; + packet >> m_id >> m_stringID >> m_color >> m_fogColor + >> m_sunDefinition.texture >> m_sunDefinition.size + >> m_moonDefinition.texture >> m_moonDefinition.size + >> m_moonDefinition.phaseCount >> m_moonDefinition.phaseSize + >> m_starsDefinition.count >> m_starsDefinition.size + >> m_daylightCycleSpeed; } diff --git a/source/common/world/Sky.hpp b/source/common/world/Sky.hpp index 57b1ad35e..31b20d75e 100644 --- a/source/common/world/Sky.hpp +++ b/source/common/world/Sky.hpp @@ -49,12 +49,46 @@ class Sky : public gk::ISerializable { void setColor(const gk::Color &color) { m_color = color; } void setFogColor(const gk::Color &fogColor) { m_fogColor = fogColor; } + struct SunDefinition { + std::string texture; + float size; + }; + + struct MoonDefinition { + std::string texture; + float size; + u16 phaseCount; + u16 phaseSize; + }; + + struct StarsDefinition { + u16 count; + float size; + }; + + const SunDefinition &sunDefinition() const { return m_sunDefinition; } + const MoonDefinition &moonDefinition() const { return m_moonDefinition; } + const StarsDefinition &starsDefinition() const { return m_starsDefinition; } + + void setSunDefinition(const SunDefinition &sunDefinition) { m_sunDefinition = sunDefinition; } + void setMoonDefinition(const MoonDefinition &moonDefinition) { m_moonDefinition = moonDefinition; } + void setStarsDefinition(const StarsDefinition &starsDefinition) { m_starsDefinition = starsDefinition; } + + float daylightCycleSpeed() const { return m_daylightCycleSpeed; } + void setDaylightCycleSpeed(float daylightCycleSpeed) { m_daylightCycleSpeed = daylightCycleSpeed; } + private: u16 m_id; std::string m_stringID; gk::Color m_color; gk::Color m_fogColor; + + SunDefinition m_sunDefinition; + MoonDefinition m_moonDefinition; + StarsDefinition m_starsDefinition; + + float m_daylightCycleSpeed = 0.f; }; #endif // SKY_HPP_ diff --git a/source/server/core/ServerApplication.cpp b/source/server/core/ServerApplication.cpp index 247e8db6d..9bcc11778 100644 --- a/source/server/core/ServerApplication.cpp +++ b/source/server/core/ServerApplication.cpp @@ -28,6 +28,7 @@ #include "BlockGeometry.hpp" #include "Events.hpp" +#include "GameTime.hpp" #include "ServerApplication.hpp" #include "ServerBlock.hpp" #include "ServerConfig.hpp" @@ -193,7 +194,18 @@ int ServerApplication::run(bool isProtected) { } void ServerApplication::update() { - m_worldController.update(); + static u64 lastTick = m_clock.getTicks() / 50; + bool doTick = false; + if (lastTick < m_clock.getTicks() / 50) { + lastTick = m_clock.getTicks() / 50; + doTick = true; + + GameTime::incrementTicks(); + + m_serverCommandHandler.sendServerTick(); + } + + m_worldController.update(doTick); if (m_clock.getTicks() % 100 < 10) { for (auto &it : m_players) { diff --git a/source/server/lua/loader/LuaSkyLoader.cpp b/source/server/lua/loader/LuaSkyLoader.cpp index 84fae4bb0..b2b775abc 100644 --- a/source/server/lua/loader/LuaSkyLoader.cpp +++ b/source/server/lua/loader/LuaSkyLoader.cpp @@ -50,5 +50,54 @@ void LuaSkyLoader::loadSky(const sol::table &table) const { u8 a = fogColor["day"][4].get_or(255); sky.setFogColor(gk::Color{r, g, b, a}); } + + if (sol::object obj = table["daylight_cycle"] ; obj.valid()) { + sol::table daylightCycleTable = obj.as(); + sky.setDaylightCycleSpeed(daylightCycleTable["speed"].get_or(0.f)); + } + + loadObjects(sky, table); +} + +void LuaSkyLoader::loadObjects(Sky &sky, const sol::table &table) const { + if (sol::object obj = table["objects"] ; obj.valid()) { + sol::table objectsTable = obj.as(); + + if (sol::object obj = objectsTable["sun"] ; obj.valid()) { + sol::table sunTable = obj.as(); + + Sky::SunDefinition sunDefinition; + sunDefinition.texture = sunTable["texture"].get_or(""); + sunDefinition.size = sunTable["size"].get_or(256); + + sky.setSunDefinition(sunDefinition); + } + + if (sol::object obj = objectsTable["moon"] ; obj.valid()) { + sol::table moonTable = obj.as(); + + Sky::MoonDefinition moonDefinition; + moonDefinition.texture = moonTable["texture"].get_or(""); + moonDefinition.size = moonTable["size"].get_or(256); + + if (sol::object obj = moonTable["phases"] ; obj.valid()) { + sol::table phasesTable = obj.as(); + moonDefinition.phaseCount = phasesTable["count"].get_or(1); + moonDefinition.phaseSize = phasesTable["size"].get_or(32); + } + + sky.setMoonDefinition(moonDefinition); + } + + if (sol::object obj = objectsTable["stars"] ; obj.valid()) { + sol::table starsTable = obj.as(); + + Sky::StarsDefinition starsDefinition; + starsDefinition.count = starsTable["count"].get_or(1000); + starsDefinition.size = starsTable["size"].get_or(4); + + sky.setStarsDefinition(starsDefinition); + } + } } diff --git a/source/server/lua/loader/LuaSkyLoader.hpp b/source/server/lua/loader/LuaSkyLoader.hpp index 9799b2201..599eea80d 100644 --- a/source/server/lua/loader/LuaSkyLoader.hpp +++ b/source/server/lua/loader/LuaSkyLoader.hpp @@ -30,6 +30,7 @@ #include class LuaMod; +class Sky; class LuaSkyLoader { public: @@ -38,6 +39,8 @@ class LuaSkyLoader { void loadSky(const sol::table &table) const; private: + void loadObjects(Sky &sky, const sol::table &table) const; + LuaMod &m_mod; }; diff --git a/source/server/network/ChatCommandHandler.cpp b/source/server/network/ChatCommandHandler.cpp index f17411ff0..34e0ce7e6 100644 --- a/source/server/network/ChatCommandHandler.cpp +++ b/source/server/network/ChatCommandHandler.cpp @@ -29,6 +29,7 @@ #include "ChatCommandHandler.hpp" #include "ClientInfo.hpp" +#include "GameTime.hpp" #include "ServerCommandHandler.hpp" #include "ServerConfig.hpp" #include "WorldController.hpp" @@ -113,12 +114,62 @@ void ChatCommandHandler::optionCommand(const std::vector &command, } } -void ChatCommandHandler::stopCommand(const std::vector &, ClientInfo &client) const { - m_server.sendChatMessage(0, "Stopping server...", &client); - m_server.stopServer(); +void ChatCommandHandler::stopCommand(const std::vector &command, ClientInfo &client) const { + if (command.size() > 1) + m_server.sendChatMessage(0, "Usage: /stop", &client); + else { + m_server.sendChatMessage(0, "Stopping server...", &client); + m_server.stopServer(); + } } -void ChatCommandHandler::teleportationCommand(const std::vector &command, ClientInfo &client) const { +void ChatCommandHandler::timeCommand(const std::vector &command, ClientInfo &client) const { + if (command.size() != 3 || (command.at(1) != "set" && command.at(1) != "add")) { + m_server.sendChatMessage(0, "Usage: /time ", &client); + } + else if (command.at(1) == "set") { + static const std::unordered_map values = { + {"day", 1000}, + {"noon", 6000}, + {"sunset", 11000}, + {"night", 13000}, + {"midnight", 18000}, + {"sunrise", 1000}, + }; + + if (auto it = values.find(command.at(2)) ; it != values.end()) { + GameTime::setTicks(it->second); + + m_server.sendChatMessage(0, "Time set to " + std::to_string(it->second), &client); + } + else { + try { + u64 ticks = std::stoull(command.at(2)); + + GameTime::setTicks(ticks); + + m_server.sendChatMessage(0, "Time set to " + std::to_string(ticks), &client); + } + catch (...) { + m_server.sendChatMessage(0, "Invalid time", &client); + } + } + } + else if (command.at(1) == "add") { + try { + u64 ticks = std::stoull(command.at(2)); + + GameTime::setTicks(GameTime::getTicks() + ticks); + + m_server.sendChatMessage(0, "Added " + std::to_string(ticks) + " to the time", &client); + } + catch (...) { + m_server.sendChatMessage(0, "Invalid time", &client); + } + } +} + +void ChatCommandHandler::tpCommand(const std::vector &command, ClientInfo &client) const { if (command.size() != 4) { m_server.sendChatMessage(0, "Usage: /tp x y z", &client); } @@ -134,9 +185,16 @@ void ChatCommandHandler::teleportationCommand(const std::vector &co m_server.sendChatMessage(0, "Teleported to " + std::to_string(x) + " " + std::to_string(y) + " " + std::to_string(z), &client); } - catch (std::out_of_range &e) { + catch (...) { m_server.sendChatMessage(0, "Invalid coordinates", &client); } } } +void ChatCommandHandler::tpsCommand(const std::vector &command, ClientInfo &client) const { + if (command.size() > 1) + m_server.sendChatMessage(0, "Usage: /tps", &client); + else + m_server.sendChatMessage(0, "TPS: " + std::to_string(GameTime::getTicksPerSecond()), &client); +} + diff --git a/source/server/network/ChatCommandHandler.hpp b/source/server/network/ChatCommandHandler.hpp index 7428fef76..023095c85 100644 --- a/source/server/network/ChatCommandHandler.hpp +++ b/source/server/network/ChatCommandHandler.hpp @@ -48,7 +48,9 @@ class ChatCommandHandler { void helpCommand(const std::vector &command, ClientInfo &client) const; void optionCommand(const std::vector &command, ClientInfo &client) const; void stopCommand(const std::vector &command, ClientInfo &client) const; - void teleportationCommand(const std::vector &command, ClientInfo &client) const; + void timeCommand(const std::vector &command, ClientInfo &client) const; + void tpCommand(const std::vector &command, ClientInfo &client) const; + void tpsCommand(const std::vector &command, ClientInfo &client) const; ServerCommandHandler &m_server; WorldController &m_worldController; @@ -57,7 +59,9 @@ class ChatCommandHandler { {"help", &ChatCommandHandler::helpCommand}, {"option", &ChatCommandHandler::optionCommand}, {"stop", &ChatCommandHandler::stopCommand}, - {"tp", &ChatCommandHandler::teleportationCommand}, + {"time", &ChatCommandHandler::timeCommand}, + {"tp", &ChatCommandHandler::tpCommand}, + {"tps", &ChatCommandHandler::tpsCommand}, }; }; diff --git a/source/server/network/ServerCommandHandler.cpp b/source/server/network/ServerCommandHandler.cpp index b7ef936cb..bbbd57510 100644 --- a/source/server/network/ServerCommandHandler.cpp +++ b/source/server/network/ServerCommandHandler.cpp @@ -27,6 +27,7 @@ #include "AnimationComponent.hpp" #include "BlockData.hpp" #include "DrawableDef.hpp" +#include "GameTime.hpp" #include "NetworkComponent.hpp" #include "PlayerList.hpp" #include "Registry.hpp" @@ -37,6 +38,16 @@ #include "ServerItem.hpp" #include "WorldController.hpp" +void ServerCommandHandler::sendServerTick(const ClientInfo *client) const { + Network::Packet packet; + packet << Network::Command::ServerTick << (sf::Uint64)GameTime::getTicks(); + + if (!client) + m_server.sendToAllClients(packet); + else + client->tcpSocket->send(packet); +} + void ServerCommandHandler::sendServerClosed(const std::string &message, const ClientInfo *client) const { Network::Packet packet; packet << Network::Command::ServerClosed << message; diff --git a/source/server/network/ServerCommandHandler.hpp b/source/server/network/ServerCommandHandler.hpp index b40d05b2b..3238e4698 100644 --- a/source/server/network/ServerCommandHandler.hpp +++ b/source/server/network/ServerCommandHandler.hpp @@ -61,6 +61,7 @@ class ServerCommandHandler { ServerCommandHandler(ScriptEngine &scriptEngine, Server &server, WorldController &worldController, PlayerList &players, Registry ®istry) : m_scriptEngine(scriptEngine), m_server(server), m_worldController(worldController), m_players(players), m_registry(registry) {} + void sendServerTick(const ClientInfo *client = nullptr) const; void sendServerClosed(const std::string &message, const ClientInfo *client = nullptr) const; void sendBlockDataUpdate(s32 x, s32 y, s32 z, const BlockData *blockData, const ClientInfo *client = nullptr) const; void sendBlockInvUpdate(s32 x, s32 y, s32 z, const Inventory &inventory, const ClientInfo *client = nullptr) const; diff --git a/source/server/world/ServerWorld.cpp b/source/server/world/ServerWorld.cpp index 26f325bc3..f3253170f 100644 --- a/source/server/world/ServerWorld.cpp +++ b/source/server/world/ServerWorld.cpp @@ -36,28 +36,26 @@ #include "ServerPlayer.hpp" #include "ServerWorld.hpp" -void ServerWorld::update() { - if (m_lastTick < m_clock.getTicks() / 50) { - m_lastTick = m_clock.getTicks() / 50; - - for (auto &it : m_chunks) { +void ServerWorld::update(bool doTick) { + for (auto &it : m_chunks) { + if (doTick) it.second->tick(*this, *m_server); - if (it.second->areAllNeighboursLoaded()) { - it.second->updateLights(); - } + if (it.second->areAllNeighboursLoaded()) { + it.second->updateLights(); + } - if (it.second->isInitialized() && !it.second->isSent()) { - for (auto &client : m_server->server().info().clients()) - if (m_players.getPlayer(client.playerName)->dimension() == m_dimension.id()) - sendChunkData(client, *it.second.get()); + if (it.second->isInitialized() && !it.second->isSent()) { + for (auto &client : m_server->server().info().clients()) + if (m_players.getPlayer(client.playerName)->dimension() == m_dimension.id()) + sendChunkData(client, *it.second.get()); - // gkDebug() << "Chunk updated at" << it.second->x() << it.second->y() << it.second->z(); - } + // gkDebug() << "Chunk updated at" << it.second->x() << it.second->y() << it.second->z(); } } - m_scene.update(); + if (doTick) + m_scene.update(); } void ServerWorld::createChunkNeighbours(ServerChunk &chunk) { diff --git a/source/server/world/ServerWorld.hpp b/source/server/world/ServerWorld.hpp index 8e9735dd3..0191963d5 100644 --- a/source/server/world/ServerWorld.hpp +++ b/source/server/world/ServerWorld.hpp @@ -51,7 +51,7 @@ class ServerWorld : public World { ServerWorld(PlayerList &players, const Dimension &dimension, gk::GameClock &clock) : m_players(players), m_dimension(dimension), m_terrainGenerator(dimension), m_clock(clock), m_scene(players) {} - void update(); + void update(bool doTick); void createChunkNeighbours(ServerChunk &chunk); void sendChunkData(const ClientInfo &client, ServerChunk &chunk); @@ -80,8 +80,6 @@ class ServerWorld : public World { ChunkMap m_chunks; - u32 m_lastTick = 0; - TerrainGenerator m_terrainGenerator; ServerCommandHandler *m_server = nullptr; diff --git a/source/server/world/WorldController.cpp b/source/server/world/WorldController.cpp index 6ac2fff38..1c5869984 100644 --- a/source/server/world/WorldController.cpp +++ b/source/server/world/WorldController.cpp @@ -42,9 +42,9 @@ void WorldController::clearEntities() { it.scene().clear(); } -void WorldController::update() { +void WorldController::update(bool doTick) { for (auto &it : m_worldList) - it.update(); + it.update(doTick); } ServerWorld &WorldController::getWorld(u16 dimension) { diff --git a/source/server/world/WorldController.hpp b/source/server/world/WorldController.hpp index 08d55e2b7..1d6977d31 100644 --- a/source/server/world/WorldController.hpp +++ b/source/server/world/WorldController.hpp @@ -47,7 +47,7 @@ class WorldController { void clearEntities(); - void update(); + void update(bool doTick); void load(const std::string &worldName) { m_worldSaveBackend->load(worldName); } void save(const std::string &worldName) { m_worldSaveBackend->save(worldName); } diff --git a/source/server/world/save/WorldSaveBasicBackend.cpp b/source/server/world/save/WorldSaveBasicBackend.cpp index 12e77c2c2..878b8cce6 100644 --- a/source/server/world/save/WorldSaveBasicBackend.cpp +++ b/source/server/world/save/WorldSaveBasicBackend.cpp @@ -31,6 +31,7 @@ #include #include "ComponentType.hpp" +#include "GameTime.hpp" #include "Network.hpp" #include "NetworkComponent.hpp" #include "Registry.hpp" @@ -112,6 +113,10 @@ void WorldSaveBasicBackend::load(const std::string &name) { auto &player = m_playerList.addPlayer(username, false); player.deserialize(save); } + + sf::Uint64 ticks; + save >> ticks; + GameTime::setTicks(ticks); } // gkInfo() << "Loading done."; @@ -168,6 +173,8 @@ void WorldSaveBasicBackend::save(const std::string &name) { save << it.second; } + save << (sf::Uint64)GameTime::getTicks(); + file.write((const char *)save.getData(), save.getDataSize()); // gkInfo() << "Saving done.";