Compare commits
134 Commits
Prerelease
...
master
Author | SHA1 | Date | |
---|---|---|---|
791f6bf4c3 | |||
54eb43044d | |||
6f0d7d734f | |||
35298ec28d | |||
1cbed8ce7f | |||
79d8720282 | |||
c40c7c3cdf | |||
f154c256d8 | |||
c5109c2c1b | |||
ab0fd9455c | |||
b7b06cd48b | |||
f9e4f93aaa | |||
f82157d240 | |||
b10d1e30dc | |||
e748bf95ae | |||
28dda7d12d | |||
15d816f04f | |||
d9d8f7f3a2 | |||
0ee1ef5592 | |||
0d7306b9ae | |||
d55a64163b | |||
c7f98a1be6 | |||
4538aa963b | |||
60dbd6d725 | |||
5cd84258d8 | |||
6e9224ead6 | |||
1a09845770 | |||
f94c8b8e72 | |||
3dd9d65964 | |||
7d71f32d09 | |||
a53bc42a82 | |||
06ec0ca885 | |||
c9b037bf39 | |||
41749893a5 | |||
79f77a4fcf | |||
e0367b971c | |||
dfb60a45c4 | |||
9a10ff81d0 | |||
27f748ceca | |||
6b37c03079 | |||
23fb2892a9 | |||
50205fef99 | |||
4553a0c8ac | |||
8072af2c49 | |||
d9de2502ea | |||
83afdcaebc | |||
e6f67e1052 | |||
2f19b910de | |||
13cc7f2104 | |||
0dcb7d5a73 | |||
ba39db02d3 | |||
10e82cd4aa | |||
a599775eee | |||
185016ed49 | |||
8a8f1321b9 | |||
84bc213229 | |||
de8b45018a | |||
c604b69487 | |||
8a3ed23302 | |||
6d1652e0af | |||
699c1021c1 | |||
4e0c1dac86 | |||
b2e4732b14 | |||
e912645a24 | |||
a7063675dc | |||
f81098d7b2 | |||
cc00350dc3 | |||
809abae082 | |||
f2512dddf1 | |||
7d5d8bcd46 | |||
e547dd0045 | |||
6aeef77904 | |||
83731c08f0 | |||
0b9c5eb449 | |||
4a11961c0f | |||
f978605005 | |||
d4468421f7 | |||
84765fbaf4 | |||
7ae6003e23 | |||
7707d6d3a6 | |||
0f776f2a6f | |||
d058774519 | |||
2e55d42733 | |||
c5015b608d | |||
0a033fc103 | |||
2cc0ca2033 | |||
06021b9396 | |||
7c46f2939b | |||
5127d50f04 | |||
baf1f517a5 | |||
54a704a091 | |||
b8a59d98c5 | |||
24790c5c0c | |||
55746d45b9 | |||
48b303260d | |||
0a174af0e2 | |||
1f18f08244 | |||
d82c821e6f | |||
6ba265e9e2 | |||
fba1546a9b | |||
01edeb9e37 | |||
dd6a284b6f | |||
f20dec85ce | |||
0121af5178 | |||
3bd4716fcd | |||
9adc5c9ac7 | |||
a5f52f7f73 | |||
3c8e194f00 | |||
a6cf319c1c | |||
be1e04eac9 | |||
d27119ef28 | |||
2fb24c60b2 | |||
e2719d9954 | |||
40db7f9378 | |||
7af8d41e1e | |||
cc497fdb1e | |||
f0cdddebcc | |||
85db6dd46d | |||
9375776835 | |||
abed11e447 | |||
65f383cbc1 | |||
8f7e2f04df | |||
1b94a2c2ce | |||
b78081d6b9 | |||
b211df9c8f | |||
ae797f91b5 | |||
98b340379b | |||
915371004d | |||
a2ec22aab7 | |||
c069af8ed5 | |||
c92d74a20c | |||
e2a1724bcf | |||
fe730ee3b5 | |||
fc303375ad |
1
.gitignore
vendored
@@ -3,4 +3,5 @@
|
||||
/.ccls-cache
|
||||
/compile_commands.json
|
||||
/cmake-build-debug
|
||||
/cmake-build-release
|
||||
/build
|
||||
|
@@ -1,6 +1,8 @@
|
||||
# Build Options
|
||||
set(CLIENT_BUILD_WITH_STEAM false)
|
||||
|
||||
#cmake_policy(SET CMP0169 OLD)
|
||||
|
||||
cmake_minimum_required(VERSION 3.18..3.29)
|
||||
project(ReCaveGame
|
||||
VERSION 1.0
|
||||
@@ -11,27 +13,38 @@ if (PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
|
||||
message(FATAL_ERROR "In-source builds are not allowed")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
|
||||
# Set for profiling
|
||||
#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0")
|
||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0")
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -march=native -ffast-math -ftree-parallelize-loops=6 -funroll-loops -mfma -flto=auto -finline-functions -fprefetch-loop-arrays -floop-nest-optimize")
|
||||
if (UNIX)
|
||||
# TODO: Enable ALL optimization flags for RELEASE builds.
|
||||
|
||||
#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3")
|
||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -floop-nest-optimize -funroll-loops")
|
||||
endif()
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
|
||||
#if(CMAKE_BUILD_TYPE EQUAL "Release")
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib")
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib")
|
||||
#endif()
|
||||
include(cmake/CPM.cmake)
|
||||
|
||||
CPMAddPackage(
|
||||
NAME mcolor
|
||||
URL https://git.redacted.cc/maxine/mcolor/archive/Prerelease-5.zip
|
||||
URL https://git.redacted.cc/maxine/mcolor/archive/Release-1.zip
|
||||
)
|
||||
|
||||
CPMAddPackage(
|
||||
NAME jjx
|
||||
URL https://git.redacted.cc/josh/jjx/archive/Release-1.1.zip
|
||||
)
|
||||
|
||||
CPMAddPackage(
|
||||
NAME jlog
|
||||
URL https://git.redacted.cc/josh/jlog/archive/Prerelease-17.zip
|
||||
URL https://git.redacted.cc/josh/jlog/archive/Prerelease-19.zip
|
||||
)
|
||||
|
||||
CPMAddPackage(
|
||||
@@ -39,19 +52,24 @@ CPMAddPackage(
|
||||
URL https://git.redacted.cc/josh/j3ml/archive/3.4.5.zip
|
||||
)
|
||||
|
||||
CPMAddPackage(
|
||||
NAME jstick
|
||||
URL https://git.redacted.cc/josh/jstick/archive/Prerelease-4.zip
|
||||
)
|
||||
|
||||
CPMAddPackage(
|
||||
NAME ReWindow
|
||||
URL https://git.redacted.cc/Redacted/ReWindow/archive/Prerelease-31.zip
|
||||
URL https://git.redacted.cc/Redacted/ReWindow/archive/Prerelease-34.zip
|
||||
)
|
||||
|
||||
CPMAddPackage(
|
||||
NAME JGL
|
||||
URL https://git.redacted.cc/josh/JGL/archive/Prerelease-46.zip
|
||||
URL https://git.redacted.cc/josh/JGL/archive/Prerelease-58.zip
|
||||
)
|
||||
|
||||
CPMAddPackage(
|
||||
NAME JUI
|
||||
URL https://git.redacted.cc/josh/ReJUI/archive/Prerelease-5.6.zip
|
||||
URL https://git.redacted.cc/josh/ReJUI/archive/Prerelease-6.2.zip
|
||||
)
|
||||
|
||||
CPMAddPackage(
|
||||
@@ -61,7 +79,7 @@ CPMAddPackage(
|
||||
|
||||
CPMAddPackage(
|
||||
NAME Sockets
|
||||
URL https://git.redacted.cc/josh/Sockets/archive/Prerelease-2.zip
|
||||
URL https://git.redacted.cc/josh/Sockets/archive/Prerelease-3.1.zip
|
||||
)
|
||||
|
||||
add_subdirectory(Core)
|
||||
|
@@ -8,7 +8,7 @@ if (UNIX)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
add_library(CaveClient SHARED ${CaveClient_SRC})
|
||||
add_library(CaveClient STATIC ${CaveClient_SRC})
|
||||
endif()
|
||||
|
||||
target_include_directories(CaveClient PUBLIC
|
||||
@@ -20,7 +20,6 @@ target_include_directories(CaveClient PUBLIC
|
||||
|
||||
target_include_directories(CaveClient PUBLIC "include")
|
||||
|
||||
|
||||
set_target_properties(CaveClient PROPERTIES LINKER_LANGUAGE CXX)
|
||||
|
||||
target_link_libraries(CaveClient PUBLIC CaveCore J3ML JGL JUI)
|
||||
|
@@ -1,3 +1,14 @@
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2025
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file AssetService.hpp
|
||||
/// @desc Manages game asset data.
|
||||
/// @edit 3/31/2025
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
/// The AssetService is a class / static library that manages on-file game assets.
|
||||
/// This class service provides asynchronous loading of game content, with introspection for hooking to a loading menu.
|
||||
|
||||
@@ -9,18 +20,22 @@
|
||||
#include <iostream>
|
||||
#include <JGL/types/Texture.h>
|
||||
#include <JGL/types/Font.h>
|
||||
#include <queue>
|
||||
#include <Core/Singleton.hpp>
|
||||
#include <Core/Macros.hpp>
|
||||
|
||||
namespace CaveGame::Client
|
||||
{
|
||||
namespace CaveGame::Client {
|
||||
|
||||
using namespace JGL;
|
||||
|
||||
enum AssetType { DATA, TEXT, AUDIO, MODEL, TEXTURE, FONT, SHADER };
|
||||
enum AssetLifestyle { STATIC, STREAMED};
|
||||
|
||||
struct Asset {
|
||||
std::string name;
|
||||
std::string metadata;
|
||||
std::string type;
|
||||
|
||||
struct AssetRequest {
|
||||
std::string name;
|
||||
std::filesystem::path path;
|
||||
AssetType type;
|
||||
};
|
||||
|
||||
class AssetService {
|
||||
@@ -30,8 +45,9 @@ namespace CaveGame::Client
|
||||
static AssetService* Get();
|
||||
|
||||
|
||||
JGL::Texture* player_sprite;
|
||||
JGL::Texture* explosion_sprite;
|
||||
JGL::Texture *player_sprite;
|
||||
JGL::Texture *explosion_sprite;
|
||||
JGL::Texture *title_img;
|
||||
|
||||
AssetService();
|
||||
|
||||
@@ -39,36 +55,56 @@ namespace CaveGame::Client
|
||||
|
||||
void TempLoadSimple();
|
||||
|
||||
/// Performs one "Load Cycle" on the current thread. This means we will attempt to load a number of queued items until we've spent at least maxTTL seconds.
|
||||
void LoadCycle(float maxTTL = 1e-3f);
|
||||
|
||||
unsigned int TotalAssetsQueued() const;
|
||||
unsigned int TotalAssetsLoaded() const;
|
||||
unsigned int AssetsToBeLoaded() const;
|
||||
|
||||
JGL::Texture* GetTexture(const std::string& textureName) {
|
||||
std::shared_ptr<Texture> GetTexture(const std::string &textureName) {
|
||||
return textures[textureName];
|
||||
}
|
||||
JGL::Font* GetFont(const std::string& fontName) {
|
||||
return fonts[fontName];
|
||||
|
||||
std::shared_ptr<JGL::Font> GetFont(const std::string &fontName) {
|
||||
//return fonts[fontName];
|
||||
}
|
||||
|
||||
void EnqueueTexture(const std::string& texName);
|
||||
void EnqueueFont(const std::string& fontName);
|
||||
void EnqueueTexture(const std::string& name, const std::filesystem::path &path);
|
||||
void EnqueueFont(const std::string& name, const std::filesystem::path& path);
|
||||
void EnqueueSound(const std::string& name, const std::filesystem::path& path);
|
||||
|
||||
void EnqueueDefaultAssetsFolder();
|
||||
void LoadAllFromQueue();
|
||||
|
||||
void EnqueueContents(const std::filesystem::path& folder) {
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(folder)) {
|
||||
if (entry.is_regular_file()) {
|
||||
std::cout << entry << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Performs one "Load Cycle" on the current thread. This means we will attempt to load a number of queued items until we've spent at least maxTTL seconds.
|
||||
bool LoadFromQueue(float maxTimeExpenditure = 1e-3f);
|
||||
|
||||
bool LoadAsset(const AssetRequest& request);
|
||||
|
||||
|
||||
bool IsLoadComplete() const { return queue.empty(); }
|
||||
|
||||
std::string LastAsset() const { return last_asset_processed; }
|
||||
|
||||
unsigned int TotalQueued() const { return total_queued; }
|
||||
unsigned int TotalLoaded() const { return total_loaded; }
|
||||
|
||||
/// Read the manifest.json file to know which assets to enqueue for the loading screen sequence.
|
||||
void ParseManifest();
|
||||
|
||||
/// Load certain critical assets immediately.
|
||||
void PreloadCertainAssets();
|
||||
|
||||
protected:
|
||||
std::unordered_map<std::string, JGL::Font*> fonts;
|
||||
std::unordered_map<std::string, JGL::Texture*> textures;
|
||||
|
||||
/// @returns only the filename of the given path.
|
||||
static std::string FilenameFromPath(const std::filesystem::path& path);
|
||||
|
||||
/// @returns only the filename, without a file extension, of the given path.
|
||||
static std::string FilenameFromPathWithoutExtension(const std::filesystem::path& path);
|
||||
|
||||
protected:
|
||||
std::unordered_map<std::string, std::shared_ptr<Font>> fonts;
|
||||
std::unordered_map<std::string, std::shared_ptr<Texture>> textures;
|
||||
std::queue<AssetRequest> queue;
|
||||
std::string last_asset_processed;
|
||||
|
||||
unsigned int total_queued = 0;
|
||||
unsigned int total_loaded = 0;
|
||||
|
||||
};
|
||||
}
|
||||
|
@@ -86,6 +86,7 @@ namespace CaveGame::Client
|
||||
Matrix4x4 Transform() const;
|
||||
|
||||
|
||||
void Move(const Vector2& v);
|
||||
/// Moves the camera to the left at a given rate times the base camera speed.
|
||||
void MoveLeft(float rate = 1.f);
|
||||
/// Moves the camera to the right at a given rate times the base camera speed.
|
||||
@@ -130,11 +131,13 @@ namespace CaveGame::Client
|
||||
float rotation = 0.f;
|
||||
Vector2 target = {0, 0};
|
||||
Vector2 viewport_size = {800, 600};
|
||||
float speed = 0.5f;
|
||||
float lerp_factor = 10.f;
|
||||
float move_speed = 150.f;
|
||||
float move_smoothing = 0.975f;
|
||||
bool lerp_to_target = true;
|
||||
bool freecam = true;
|
||||
float free_move_cooldown = 1.25f;
|
||||
float last_free_move = 0.f;
|
||||
private:
|
||||
|
||||
// https://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/
|
||||
};
|
||||
}
|
@@ -1,3 +1,14 @@
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2025
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file Chat.hpp
|
||||
/// @desc The client-side user interface for in-game chat.
|
||||
/// @edit 1/28/2025
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CaveGame::Client
|
||||
|
@@ -1,46 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <JUI/Widgets/Window.hpp>
|
||||
#include <JUI/Widgets/TextInputForm.hpp>
|
||||
|
||||
namespace CaveGame::Client
|
||||
{
|
||||
class Console : public JUI::Window
|
||||
{
|
||||
public:
|
||||
Event<std::string> OnInput;
|
||||
explicit Console(Widget* parent) : JUI::Window(parent)
|
||||
{
|
||||
this->SetTitle("Console");
|
||||
input_box = new JUI::TextInputForm(this->GetViewportInstance());
|
||||
input_box->Size(JUI::UDim2(0, 20, 1, 0));
|
||||
input_box->Position(JUI::UDim2(0, -20, 0, 1));
|
||||
|
||||
input_box->OnReturn += [this] (const std::string& msg) { OnInput.Invoke(msg); };
|
||||
|
||||
}
|
||||
|
||||
void ObserveKeyInput(Key key, bool pressed) override
|
||||
{
|
||||
if (!IsVisible())
|
||||
return;
|
||||
|
||||
Widget::ObserveKeyInput(key, pressed);
|
||||
}
|
||||
bool IsOpen() const { return console_open;}
|
||||
void SetOpen(bool open) {
|
||||
console_open = open;
|
||||
|
||||
if (open)
|
||||
//input_box.Gra
|
||||
|
||||
Visible(open);
|
||||
|
||||
}
|
||||
protected:
|
||||
JUI::TextInputForm* input_box;
|
||||
JUI::Rect* message_history;
|
||||
bool console_open = false;
|
||||
private:
|
||||
};
|
||||
}
|
50
Client/include/Client/ContainerWindow.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2025
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file ContainerWindow.hpp
|
||||
/// @desc The UI window for a container that holds items.
|
||||
/// @edit 3/18/2025
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <JUI/Widgets/Window.hpp>
|
||||
#include <Core/Container.hpp>
|
||||
|
||||
namespace CaveGame {
|
||||
|
||||
using namespace JUI::UDimLiterals;
|
||||
|
||||
/// An inventory container window widget.
|
||||
class ContainerWindow : public JUI::Window
|
||||
{
|
||||
public:
|
||||
ContainerWindow(Widget* parent, int rows, int cols, std::string title = "Container")
|
||||
: JUI::Window(parent)
|
||||
{
|
||||
auto layout = this->ViewportInstance();
|
||||
this->Title(title);
|
||||
this->Size(JUI::UDim2::FromPixels(48*rows, 48*cols));
|
||||
this->MinSize(Vector2(48*rows, 48*cols));
|
||||
this->SetResizable(false);
|
||||
|
||||
for (int row = 0; row < rows; row++) {
|
||||
for (int col = 0; col < cols; col++) {
|
||||
auto* cell = new JUI::Rect(layout);
|
||||
cell->Name(std::format("cell_{},{}", row, col));
|
||||
cell->Size({48_px, 48_px});
|
||||
cell->Position(JUI::UDim2::FromPixels(48*row, 48*col));
|
||||
}
|
||||
}
|
||||
}
|
||||
void SetContainerSize(int rows, int cols);
|
||||
void RebuildContainerElements();
|
||||
protected:
|
||||
Core::Container* contents = nullptr;
|
||||
private:
|
||||
};
|
||||
|
||||
}
|
@@ -1,6 +1,15 @@
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2025
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file CreditsWindow.hpp
|
||||
/// @desc The UI window for the credits.
|
||||
/// @edit 1/28/2025
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "JUI/Widgets/Window.hpp"
|
||||
#include "JUI/Widgets/TextRect.hpp"
|
||||
#include "JUI/Widgets/ListLayout.hpp"
|
||||
@@ -9,29 +18,34 @@ namespace CaveGame::Client
|
||||
{
|
||||
using namespace JUI;
|
||||
|
||||
struct Accreditation {
|
||||
unsigned int text_size;
|
||||
Color4 text_color;
|
||||
std::string content;
|
||||
};
|
||||
|
||||
// We currently lack Rich Text, so we'll do this...
|
||||
std::vector<std::tuple<uint, Color4, std::string>> Credits =
|
||||
{
|
||||
{34, Colors::White, "cavegame"},
|
||||
{20, Colors::White, "A Redacted Software Product"},
|
||||
{18, Colors::Yellows::Moccasin, "Josh O'Leary"},
|
||||
{14, Colors::White, "Programming & Design"},
|
||||
{18, Colors::White, "William Redacted"},
|
||||
{14, Colors::White, "Redacted3D Game Technologies"},
|
||||
{18, Colors::White, "Ash Tray"},
|
||||
{14, Colors::White, "Art & Design"},
|
||||
{18, Colors::White, "Evan Walter"},
|
||||
{14, Colors::White, "Music"},
|
||||
|
||||
{18, Colors::White, "Maxine Hayes"},
|
||||
{18, Colors::White, "Karl Darling"},
|
||||
{18, Colors::White, "bumpylegoman02 "},
|
||||
|
||||
|
||||
{32, Colors::White, "Made Possible By "},
|
||||
{24, Colors::Purples::Violet, "Asperger's Syndrome"},
|
||||
{16, Colors::Gray, "(c) 2024 redacted.cc"},
|
||||
// NOTE: this must be static const, or the program will segfault upon exit.
|
||||
static const std::vector<Accreditation> Credits = {
|
||||
{50, Colors::White, "CaveGame"},
|
||||
{12, Colors::Gray, "Version 3 Alpha, Prerelease-3"},
|
||||
{24, Colors::Blues::LightSteelBlue, "A Redacted Software Product"},
|
||||
{20, Colors::Yellows::Moccasin, "Josh O'Leary"},
|
||||
{14, Colors::Grays::LightSlateGray, "Programming & Design"},
|
||||
{20, Colors::Grays::DarkSlateGray, "William Redacted"},
|
||||
{14, Colors::Grays::LightSlateGray, "Programming"},
|
||||
{20, Colors::Reds::LightCoral, "Ash Tray"},
|
||||
{14, Colors::Grays::LightSlateGray, "Art & Design"},
|
||||
{18, Colors::White, "Special Thanks"},
|
||||
{12, Colors::Grays::LightSlateGray, "Maxine Hayes"},
|
||||
{12, Colors::White, "Evan Walter"},
|
||||
{12, Colors::Grays::LightSlateGray, "Karl Darling"},
|
||||
{12, Colors::White, "bumpylegoman02"},
|
||||
{12, Colors::Grays::LightSlateGray, "ConcurrentSquared"},
|
||||
{12, Colors::White, "Chase Carlson"},
|
||||
{16, Colors::White, "Made Possible By "},
|
||||
{14, Colors::Purples::Violet, "Asperger's Syndrome"},
|
||||
{16, Colors::Gray, "(c) 2019-2025 redacted.cc"},
|
||||
|
||||
};
|
||||
|
||||
|
@@ -1,131 +1,89 @@
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2025
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file GameSession.hpp
|
||||
/// @desc The scene that runs and manages a client game session.
|
||||
/// @edit 1/28/2025
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Client/Scene.hpp>
|
||||
#include <J3ML/LinearAlgebra.hpp>
|
||||
#include <Core/Entity.hpp>
|
||||
#include <Core/Player.hpp>
|
||||
#include <Core/World.hpp>
|
||||
#include "LocalWorld.hpp"
|
||||
#include "Hotbar.hpp"
|
||||
#include <JUI/Widgets/Scene.hpp>
|
||||
#include <JUI/Widgets/Rect.hpp>
|
||||
#include <JUI/Widgets/TextRect.hpp>
|
||||
#include <Core/Item.hpp>
|
||||
#include "TileTool.hpp"
|
||||
#include "ContainerWindow.hpp"
|
||||
#include <Client/PauseMenu.hpp>
|
||||
|
||||
namespace CaveGame::Client {
|
||||
using namespace Core;
|
||||
|
||||
class LootTable
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
class LootTable {};
|
||||
class CraftingStation {};
|
||||
|
||||
|
||||
class Container
|
||||
{
|
||||
public:
|
||||
Container(int rows, int columns);
|
||||
|
||||
protected:
|
||||
private:
|
||||
};
|
||||
|
||||
class Storage : Container {};
|
||||
class PlayerHotbar : Container {};
|
||||
class PlayerPockets : Container {};
|
||||
class PlayerBackpack : Container {};
|
||||
class TradeMenu : Container {};
|
||||
|
||||
class PlayerInventory : Container
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
class ContainerGUI {
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
class HotbarGUI
|
||||
{
|
||||
public:
|
||||
protected:
|
||||
private:
|
||||
};
|
||||
class ContainerGUI {};
|
||||
|
||||
using namespace JUI::UDimLiterals;
|
||||
using CaveGame::Core::Entity;
|
||||
class GameSession : public Scene {
|
||||
public:
|
||||
|
||||
Core::TileID GetHotbarTile() const {
|
||||
return HotbarItems[active_hotbar_slot];
|
||||
}
|
||||
Event<> OnSessionExit;
|
||||
Event<> RequestToggleSettings;
|
||||
|
||||
Core::TileID HotbarItems[10] = {
|
||||
Core::TileID::GRASS, Core::TileID::STONE, Core::TileID::WATER, Core::TileID::LAVA, Core::TileID::GREEN_MOSS, Core::TileID::SAND,
|
||||
Core::TileID::CLAY, Core::TileID::CLOUD, Core::TileID::COBBLESTONE, Core::TileID::ICE
|
||||
};
|
||||
GameSession() = default;
|
||||
GameSession(bool createNewWorld);
|
||||
|
||||
explicit GameSession(bool createNewWorld);
|
||||
void Update(float elapsed) override;
|
||||
|
||||
void Draw() override;
|
||||
|
||||
void Load() override;
|
||||
|
||||
void Unload() override;
|
||||
void PassWindowSize(const J3ML::LinearAlgebra::Vector2 &size) override;
|
||||
void PassMouseInput(unsigned int, bool) override
|
||||
{ }
|
||||
void PassMouseMovement(const J3ML::LinearAlgebra::Vector2 &pos) override
|
||||
{ }
|
||||
void PassMouseInput(unsigned int a, bool b) override;
|
||||
|
||||
void PassMouseMovement(const J3ML::LinearAlgebra::Vector2 &pos) override;
|
||||
|
||||
void PassMouseWheel(int wheel) override;
|
||||
|
||||
void PassKeyInput(Key key, bool pressed) override;
|
||||
|
||||
void SaveAndExit();
|
||||
|
||||
void ConstructHUD();
|
||||
|
||||
void UnloadHUD();
|
||||
|
||||
Core::TileID GetTileUnderMouse();
|
||||
uint16_t GetTileIDUnderMouse();
|
||||
|
||||
void HUDTick()
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
hotbar_elements[i]->Size({44_px, 48_px});
|
||||
hotbar_elements[i]->CornerRounding(5);
|
||||
hotbar_elements[i]->SetBorderWidth(1.5f);
|
||||
hotbar_elements[i]->AnchorPoint({0.f, 0.8f});
|
||||
}
|
||||
[[nodiscard]] JUI::Scene* HUD() const { return hud; }
|
||||
LocalWorld* World() const { return world;}
|
||||
TileHotbar Hotbar() const { return hotbar;}
|
||||
|
||||
Core::TileID active_item = HotbarItems[active_hotbar_slot];
|
||||
void WorldEditToolControlsUpdate(float elapsed);
|
||||
void ToggleWorldEdit();
|
||||
TileTool* WorldEditToolWindow() const { return tile_tool;}
|
||||
|
||||
auto* data = GetByNumeric(active_item);
|
||||
|
||||
item_label->SetContent(data->Name());
|
||||
|
||||
hotbar_elements[active_hotbar_slot]->CornerRounding(8);
|
||||
hotbar_elements[active_hotbar_slot]->SetBorderWidth(1.5f);
|
||||
hotbar_elements[active_hotbar_slot]->Size({58_px, 56_px});
|
||||
hotbar_elements[active_hotbar_slot]->AnchorPoint({0.f, 0.75f});
|
||||
}
|
||||
|
||||
LocalWorld* world;
|
||||
|
||||
|
||||
JUI::Scene* hud;
|
||||
JUI::Rect* hotbar_elements[10];
|
||||
JUI::TextRect* item_label;
|
||||
Core::Player* GetLocalPlayerEntity();
|
||||
protected:
|
||||
int active_hotbar_slot = 0;
|
||||
TileTool* tile_tool = nullptr;
|
||||
LocalWorld* world = nullptr;
|
||||
TileHotbar hotbar;
|
||||
JUI::Scene* hud = nullptr;
|
||||
PauseMenuWidget* pause_menu = nullptr;
|
||||
Vector2 mouse_pos = {0,0};
|
||||
RNG tool_rng;
|
||||
|
||||
private:
|
||||
|
||||
void WorldEditToolDrawTiles(TileID tile);
|
||||
void WorldEditToolDrawTiles(int x, int y, int radius, int density, TileID tile);
|
||||
void WorldEditToolDrawOverlay();
|
||||
Vector2 MouseWorldPos() const;
|
||||
};
|
||||
}
|
||||
|
76
Client/include/Client/Hotbar.hpp
Normal file
@@ -0,0 +1,76 @@
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2025
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file Hotbar.hpp
|
||||
/// @desc The hotbar controls and user-interface.
|
||||
/// @edit 1/28/2025
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <JUI/Widgets/Scene.hpp>
|
||||
#include <JUI/Widgets/Rect.hpp>
|
||||
#include <JUI/Widgets/TextRect.hpp>
|
||||
#include <Core/Item.hpp>
|
||||
#include <Core/Data.hpp>
|
||||
#include <Client/LocalWorld.hpp>
|
||||
|
||||
namespace CaveGame::Client
|
||||
{
|
||||
/// The input and gui driver for the temporary tile hotbar.
|
||||
/// Basically, this object controls what items are in the hotbar, and which slot is selected.
|
||||
/// The visual appearance is applied to JUI::Widgets that are part of the GameSession::hud JUI::Scene.
|
||||
/// @see GameSession::Update
|
||||
|
||||
// TODO: Refactor TileHotbar into a derived JUI Widget?
|
||||
|
||||
class TileHotbar {
|
||||
public:
|
||||
void NextSlot();
|
||||
void PrevSlot();
|
||||
Core::TileID GetCurrentSlotTileID() const;
|
||||
int GetSlotIndex() const;
|
||||
JUI::Rect *GetRootWidget() const;
|
||||
JUI::Rect *GetSlotWidget(int slotIdx) const;
|
||||
Core::TileID GetSlotTileID(int slotIdx) const;
|
||||
void SetSlotTileID(int slotIdx, Core::TileID tid);
|
||||
|
||||
void Load(LocalWorld * world);
|
||||
void OnKeyInput(const Key& key, bool pressed);
|
||||
void OnMouseWheel(int mwheeldir)
|
||||
{
|
||||
if (mwheeldir > 0)
|
||||
NextSlot();
|
||||
if (mwheeldir < 0)
|
||||
PrevSlot();
|
||||
}
|
||||
|
||||
void Unload()
|
||||
{
|
||||
// TODO: Run this in GameSession
|
||||
//delete hud;
|
||||
}
|
||||
|
||||
void Update(float elapsed);
|
||||
void Draw() { }
|
||||
protected:
|
||||
static constexpr int slot_count = 10;
|
||||
Core::TileID slots[slot_count]{};
|
||||
/*= {
|
||||
//Core::TileID::GRASS, Core::TileID::STONE, Core::TileID::WATER, Core::TileID::LAVA, Core::TileID::GREEN_MOSS, Core::TileID::SAND,
|
||||
//Core::TileID::CLAY, Core::TileID::OAK_PLANK, Core::TileID::COBBLESTONE, Core::TileID::STONE_BRICK
|
||||
};*/
|
||||
/*TileID Slot(int index)
|
||||
{
|
||||
switch(index)
|
||||
}*/
|
||||
int slot_index = 1;
|
||||
JUI::Rect* hotbar_elements[10];
|
||||
JUI::TextRect* item_label;
|
||||
JUI::Rect* hotbar_root;
|
||||
private:
|
||||
};
|
||||
}
|
@@ -1,3 +1,14 @@
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2025
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file LocalWorld.hpp
|
||||
/// @desc The client implementation for game world behavior.
|
||||
/// @edit 1/28/2025
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Core/World.hpp>
|
||||
@@ -8,10 +19,10 @@
|
||||
#include <thread>
|
||||
#include <Core/ConcurrentQueue.hpp>
|
||||
#include <Core/ThreadPool.hpp>
|
||||
#include "Particle.hpp"
|
||||
|
||||
namespace CaveGame::Client
|
||||
{
|
||||
using Core::ConcurrentQueue;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class AsyncChunkRenderer : public ThreadPool
|
||||
@@ -20,52 +31,117 @@ namespace CaveGame::Client
|
||||
|
||||
};
|
||||
|
||||
/// An extension of the world object that provides rendering for the game world.
|
||||
class LocalWorld : public CaveGame::Core::World
|
||||
|
||||
/// A class object that renders an overlay on chunks for debugging. This includes grid-lines, cell coordinates, activity stats, etc.
|
||||
class ChunkDebugGizmo
|
||||
{
|
||||
public:
|
||||
Camera2D camera;
|
||||
Vector2 mouse_pos;
|
||||
|
||||
void DrawGrid(const AABB2D& bounds) {
|
||||
Vector2 viewport_topleft = bounds.minPoint;
|
||||
Vector2 viewport_bottomright = bounds.maxPoint;
|
||||
|
||||
int nearest_grid_left = Math::Floor(viewport_topleft.x / Core::Chunk::ChunkSize);
|
||||
int nearest_grid_right = Math::Floor(viewport_bottomright.x / Core::Chunk::ChunkSize);
|
||||
|
||||
for (int x = nearest_grid_left; x <= nearest_grid_right; x++) {
|
||||
auto top = Vector2(x * Core::Chunk::ChunkSize, viewport_topleft.y);
|
||||
auto bottom = Vector2(x * Core::Chunk::ChunkSize, viewport_bottomright.y);
|
||||
JGL::J2D::DrawLine(grid_color, top, bottom, 1 / bounds.Width());
|
||||
}
|
||||
|
||||
int nearest_grid_top = Math::Floor(viewport_topleft.y / Core::Chunk::ChunkSize);
|
||||
int nearest_grid_bottom = Math::Floor(viewport_bottomright.y / Core::Chunk::ChunkSize);
|
||||
|
||||
for (int y = nearest_grid_top; y <= nearest_grid_bottom; y++) {
|
||||
auto left = Vector2(viewport_topleft.x, y * Core::Chunk::ChunkSize);
|
||||
auto right = Vector2(viewport_bottomright.x, y * Core::Chunk::ChunkSize);
|
||||
JGL::J2D::DrawLine(grid_color, left, right, 1 / bounds.Height());
|
||||
}
|
||||
}
|
||||
void DrawCellCoords(const Vector2i& coords, float scale) {
|
||||
// TODO: Fix offset on text.
|
||||
JGL::J2D::DrawString(Colors::Black, std::format("{}, {}", coords.x, coords.y), coords.x * Core::Chunk::ChunkSize, (coords.y * Core::Chunk::ChunkSize)-5, scale, 10);
|
||||
}
|
||||
void DrawStats(Core::Chunk* chunk, float scale) {
|
||||
Vector2i cell = chunk->GetChunkCell();
|
||||
Vector2i coords = chunk->GetChunkRealCoordinates();
|
||||
|
||||
JGL::J2D::DrawString(Colors::Black, std::format("RenderTarget Age: {}", chunk->time_since_refresh), coords.x, coords.y, scale, 10);
|
||||
}
|
||||
void DrawHeatmap() {}
|
||||
|
||||
void Enable(bool enable = true)
|
||||
{
|
||||
this->enabled = enable;
|
||||
}
|
||||
void Disable() { Enable(false); }
|
||||
[[nodiscard]] bool IsEnabled() const { return enabled; }
|
||||
|
||||
protected:
|
||||
bool enabled = false;
|
||||
Color4 grid_color = {128, 128, 128, 128};
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
/// An extension of the world object that provides rendering for the game world.
|
||||
class LocalWorld : public CaveGame::Core::World {
|
||||
|
||||
public:
|
||||
JGL::Font font;
|
||||
LocalWorld(const std::string& world_name, int seed, bool overwrite);
|
||||
void PostInit();
|
||||
void Draw();
|
||||
void Update(float elapsed) override;
|
||||
void RefreshAll() override;
|
||||
void DebugChunks(bool enabled);
|
||||
|
||||
unsigned int GetRenderTargetCount() const;
|
||||
|
||||
void SaveAndExit() override;
|
||||
|
||||
void SetShowTileActivity(bool enabled);
|
||||
|
||||
bool IsShowTileActivityEnabled() const;
|
||||
|
||||
void RenderTile(const Core::TileID &tid, int wx, int wy, int tx, int ty);
|
||||
|
||||
//Camera2D* Camera() { return &camera;}
|
||||
|
||||
void Emit(const Particle& p);
|
||||
|
||||
protected:
|
||||
/// Determines whether a given chunk coordinate lies within the viewable space, with an optional 'oversize' parameter.
|
||||
/// @param extraChunkRadius Specifies how many extra chunks can be considered within the viewport when they are in fact, just outside of the viewport.
|
||||
/// @note This is used to overdraw
|
||||
bool IsChunkCellWithinViewport(const Vector2i& coords, int extraChunkRadius = 0) const;
|
||||
void RenderChunk(const Vector2i& coords);
|
||||
void LookForChunksNeedLoading();
|
||||
void LookForChunksNeedUnloading();
|
||||
void RenderChunkTexture(const Vector2i &coords, JGL::RenderTarget* destination, Core::Chunk* chunk);
|
||||
/// Checks for missing, or out-of-date, cached sprites of chunks, and renders them.
|
||||
void CheckCachedChunkSprites();
|
||||
static Color4 GetSkyColorInterpolatedForTimeOfDay(float time);
|
||||
static Color4 GetSkyColorBaseForTimeOfDay(float time);
|
||||
void DrawSky();
|
||||
|
||||
public:
|
||||
Camera2D camera;
|
||||
Vector2 mouse_pos;
|
||||
ChunkDebugGizmo chunk_debug;
|
||||
JGL::Font font;
|
||||
protected:
|
||||
|
||||
float check_chunks_timer = 0.f;
|
||||
|
||||
std::unordered_map<Vector2, JGL::RenderTarget*> cached_chunk_sprites;
|
||||
std::unordered_map<Vector2i, JGL::RenderTarget*> cached_chunk_sprites;
|
||||
JGL::RenderTarget* canvas_render_target;
|
||||
|
||||
std::vector<Particle> particles;
|
||||
|
||||
/// Determines whether a given chunk coordinate lies within the viewable space, with an optional 'oversize' parameter.
|
||||
/// @param extraChunkRadius Specifies how many extra chunks can be considered within the viewport when they are in fact, just outside of the viewport.
|
||||
/// @note This is used to overdraw
|
||||
bool IsChunkCellWithinViewport(const Vector2& coords, int extraChunkRadius = 0) const;
|
||||
|
||||
void RenderChunk(const Vector2& coords);
|
||||
|
||||
void LookForChunksNeedLoading();
|
||||
void LookForChunksNeedUnloading();
|
||||
bool show_tile_activity = false;
|
||||
|
||||
void RenderChunkTexture(const Vector2 &coords, JGL::RenderTarget* destination, Core::Chunk* chunk);
|
||||
|
||||
/// Render chunk boundaries and relative coordinates, around the camera.
|
||||
void DrawChunkGrid() const;
|
||||
/// Checks for missing, or out-of-date, cached sprites of chunks, and renders them.
|
||||
void CheckCachedChunkSprites();
|
||||
|
||||
static Color4 GetSkyColorInterpolatedForTimeOfDay(float time);
|
||||
|
||||
static Color4 GetSkyColorBaseForTimeOfDay(float time);
|
||||
|
||||
void DrawSky();
|
||||
};
|
||||
}
|
@@ -1,3 +1,14 @@
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2025
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file MainMenu.hpp
|
||||
/// @desc The scene for the home screen, aka main menu.
|
||||
/// @edit 1/28/2025
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Client/Scene.hpp>
|
||||
@@ -19,7 +30,9 @@ namespace CaveGame::Client
|
||||
public:
|
||||
Event<SingleplayerSessionInfo> RequestWorld;
|
||||
Event<> RequestQuit;
|
||||
Event<> RequestShowCredits;
|
||||
Event<> RequestToggleCredits;
|
||||
Event<> RequestToggleSettings;
|
||||
|
||||
MainMenu();
|
||||
void Update(float elapsed) override;
|
||||
void Draw() override;
|
||||
@@ -31,11 +44,12 @@ namespace CaveGame::Client
|
||||
void PassMouseInput(unsigned int, bool) override;
|
||||
void PassMouseMovement(const J3ML::LinearAlgebra::Vector2 &pos) override;
|
||||
protected:
|
||||
JUI::Scene* scene;
|
||||
JUI::TextRect* title;
|
||||
JUI::Rect* button_group;
|
||||
JUI::Rect* changelog;
|
||||
JGL::Texture* bg; // TODO: RAII on this
|
||||
JUI::Scene* scene = nullptr;
|
||||
JUI::Rect* title = nullptr;
|
||||
JUI::Rect* button_group = nullptr;
|
||||
JUI::Rect* changelog = nullptr;
|
||||
std::shared_ptr<JGL::Texture> bg = nullptr; // TODO: RAII on this
|
||||
|
||||
Vector2 mpos {0,0};
|
||||
Vector2 goal {0,0};
|
||||
private:
|
||||
|
@@ -1,3 +1,14 @@
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2025
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file Particle.hpp
|
||||
/// @desc Client-side particle engine, still a work-in-progress.
|
||||
/// @edit 1/28/2025
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
#pragma once
|
||||
#include <Color4.hpp>
|
||||
#include <J3ML/LinearAlgebra/Vector3.hpp>
|
||||
@@ -8,66 +19,26 @@ namespace CaveGame::Client
|
||||
|
||||
struct Particle
|
||||
{
|
||||
Vector3 position;
|
||||
Vector3 velocity;
|
||||
Vector2 position;
|
||||
Vector2 velocity;
|
||||
Color4 color;
|
||||
float size, angle, weight;
|
||||
float size;
|
||||
float life; // Remaining life of the particle. If < 0: dead and unused.
|
||||
float angle;
|
||||
float weight;
|
||||
void Update(float elapsed)
|
||||
{
|
||||
life -= elapsed;
|
||||
if (life <= 0)
|
||||
return;
|
||||
|
||||
position += velocity * elapsed;
|
||||
}
|
||||
|
||||
void Draw()
|
||||
{
|
||||
JGL::J2D::DrawPoint(color, position, size);
|
||||
}
|
||||
};
|
||||
|
||||
const int MaxParticles = 100000;
|
||||
Particle ParticlesContainer[MaxParticles];
|
||||
|
||||
|
||||
int LastUsedParticle = 0;
|
||||
|
||||
int FindUnusedParticle() {
|
||||
for (int i = LastUsedParticle; i < MaxParticles; i++) {
|
||||
if (ParticlesContainer[i].life < 0) {
|
||||
LastUsedParticle = i;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < LastUsedParticle; i++) {
|
||||
if (ParticlesContainer[i].life < 0) {
|
||||
LastUsedParticle = i;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// All particles are taken, override the first one.
|
||||
return 0;
|
||||
}
|
||||
|
||||
void StepParticles(float elapsed)
|
||||
{
|
||||
for (int i = 0; i < MaxParticles; i++) {
|
||||
Particle& p = ParticlesContainer[i];
|
||||
|
||||
// Decrease life
|
||||
p.life -= elapsed;
|
||||
|
||||
if (p.life <= 0.f){
|
||||
// Particles that just died will be put at the end of the buffer in SortParticles()
|
||||
//p.cameradistance = -1.f;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Simulate simple physics: gravity only, no collisions.
|
||||
}
|
||||
}
|
||||
|
||||
void DrawParticles()
|
||||
{
|
||||
for (int i = 0; i < MaxParticles; i++)
|
||||
{
|
||||
Particle& p = ParticlesContainer[i];
|
||||
|
||||
if (p.life <= 0)
|
||||
continue;
|
||||
|
||||
//JGL::J2D::D
|
||||
}
|
||||
}
|
||||
}
|
||||
|
52
Client/include/Client/PauseMenu.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <JUI/Widgets/Rect.hpp>
|
||||
#include <JUI/Widgets/ListLayout.hpp>
|
||||
#include <JUI/Widgets/TextButton.hpp>
|
||||
|
||||
namespace CaveGame::Client
|
||||
{
|
||||
using namespace JUI::UDimLiterals;
|
||||
|
||||
/// CaveGame's pause menu is implemented as a JUI::Rect overlay, parented to the GameSession::HUD().
|
||||
class PauseMenuWidget : public JUI::Rect {
|
||||
public:
|
||||
Event<> OnResumeButtonPressed;
|
||||
Event<> OnSettingsButtonPressed;
|
||||
Event<> OnQuitButtonPressed;
|
||||
|
||||
/// The default constructor initializes the pause menu, subordinate widgets, and lays out the visual properties of each.
|
||||
PauseMenuWidget();
|
||||
|
||||
/// Construct the Pause menu by specifying it's parent widget.
|
||||
/// It is safe to assume the parent will **ALWAYS** be the Scene* instance GameSession::hud.
|
||||
explicit PauseMenuWidget(Widget* parent);
|
||||
|
||||
bool ObserveMouseInput(JUI::MouseButton btn, bool pressed) override;
|
||||
|
||||
/// Switches the PauseMenu to it's alternate state, which is either "Open" or "Closed".
|
||||
void Toggle();
|
||||
|
||||
/// Sets the state of the PauseMenu.
|
||||
void SetOpen(bool value);
|
||||
|
||||
/// @return The state of the PauseMenu.
|
||||
[[nodiscard]] bool GetOpen() const;
|
||||
|
||||
/// Sets the state of the PauseMenu to "Open".
|
||||
void Open();
|
||||
|
||||
/// Sets the state of the Paus
|
||||
void Close();
|
||||
|
||||
protected:
|
||||
JUI::Rect* button_box;
|
||||
JUI::VerticalListLayout* button_list;
|
||||
JUI::TextButton* resume_btn;
|
||||
JUI::TextButton* settings_btn;
|
||||
JUI::TextButton* quit_btn;
|
||||
|
||||
bool is_open;
|
||||
private:
|
||||
};
|
||||
}
|
@@ -1,8 +1,19 @@
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2025
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file Scene.hpp
|
||||
/// @desc The base class for game scenes. A scene is a self-contained logical grouping of game activity.
|
||||
/// @edit 1/28/2025
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <J3ML/LinearAlgebra/Vector2.hpp>
|
||||
#include "SceneManager.hpp"
|
||||
#include <rewindow/types/key.h>
|
||||
#include <ReWindow/types/Key.h>
|
||||
|
||||
namespace CaveGame::Client
|
||||
{
|
||||
@@ -16,9 +27,6 @@ namespace CaveGame::Client
|
||||
Scene();
|
||||
virtual ~Scene() = default;
|
||||
|
||||
// TODO if the overriding function *has* to call the original then you're not doing it right.
|
||||
// Whether or not a scene is active should return if CurrentScene == this - Redacted.
|
||||
|
||||
/// This function is called by the SceneManager when a scene is changed.
|
||||
/// @note Make sure to call this base when overriding in derived Scene classes.
|
||||
virtual void Load();
|
||||
@@ -45,10 +53,12 @@ namespace CaveGame::Client
|
||||
/// @param pressed True if the key was pressed, false if the key was released.
|
||||
virtual void PassKeyInput(Key key, bool pressed) {}
|
||||
|
||||
/// This function is called by the SceneManagert when user mouse movement is detected.
|
||||
/// This function is called by the SceneManager when user mouse movement is detected.
|
||||
/// @param pos The up-to-date mouse position.
|
||||
virtual void PassMouseMovement(const Vector2& pos) {}
|
||||
|
||||
virtual void PassMouseWheel(int wheel) {}
|
||||
|
||||
/// Returns whether this scene is currently the "Active" scene.
|
||||
[[nodiscard]] bool Active() const;
|
||||
protected:
|
||||
|
@@ -1,3 +1,14 @@
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2025
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file SceneManager.hpp
|
||||
/// @desc A mixin class that implements management, running, and transition of scene objects. Only one scene is active at a given time.
|
||||
/// @edit 1/28/2025
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CaveGame::Client
|
||||
@@ -10,7 +21,10 @@ namespace CaveGame::Client
|
||||
void ChangeScene(Scene* new_scene);
|
||||
Scene* CurrentScene();
|
||||
protected:
|
||||
void UpdateSceneState(float elapsed);
|
||||
protected:
|
||||
Scene* next_scene = nullptr;
|
||||
Scene* current_scene = nullptr;
|
||||
private:
|
||||
Scene* prev_scene = nullptr;
|
||||
};
|
||||
}
|
@@ -1,7 +1,77 @@
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2025
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file SettingsMenu.hpp
|
||||
/// @desc The window menu for in-game options.
|
||||
/// @edit 1/28/2025
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <JUI/Widgets/Window.hpp>
|
||||
#include <JUI/Widgets/Collapsible.hpp>
|
||||
#include <JUI/Widgets/ListLayout.hpp>
|
||||
#include <Core/Singleton.hpp>
|
||||
|
||||
namespace CaveGame::Client
|
||||
{
|
||||
|
||||
namespace CaveGame::Client {
|
||||
|
||||
// TODO: Analyze behavior of singleton on an object that requires specific initialization, like this.
|
||||
class SettingsMenu : public JUI::Window
|
||||
{
|
||||
public:
|
||||
SettingsMenu()
|
||||
{
|
||||
|
||||
using namespace JUI::UDimLiterals;
|
||||
|
||||
Title("Settings");
|
||||
Size({400_px, 400_px});
|
||||
|
||||
auto* root_layout = new JUI::VerticalListLayout(this->ViewportInstance());
|
||||
|
||||
general_section = new JUI::Collapsible(root_layout);
|
||||
general_section->Size({100_percent, 100_px});
|
||||
general_section->Title("General");
|
||||
|
||||
auto* gen_layout = new JUI::VerticalListLayout(general_section);
|
||||
|
||||
|
||||
sound_section = new JUI::Collapsible(root_layout);
|
||||
sound_section->Size({100_percent, 100_px});
|
||||
sound_section->Title("Sound");
|
||||
|
||||
auto* snd_layout = new JUI::VerticalListLayout(sound_section);
|
||||
|
||||
graphics_section = new JUI::Collapsible(root_layout);
|
||||
graphics_section->Size({100_percent, 100_px});
|
||||
graphics_section->Title("Graphics");
|
||||
|
||||
auto* gfx_layout = new JUI::VerticalListLayout(graphics_section);
|
||||
|
||||
input_section = new JUI::Collapsible(root_layout);
|
||||
input_section->Size({100_percent, 100_px});
|
||||
input_section->Title("Input");
|
||||
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
explicit SettingsMenu(Widget* parent) : SettingsMenu()
|
||||
{
|
||||
Parent(parent);
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected:
|
||||
JUI::Collapsible* general_section;
|
||||
JUI::Collapsible* sound_section;
|
||||
JUI::Collapsible* graphics_section;
|
||||
JUI::Collapsible* input_section;
|
||||
private:
|
||||
};
|
||||
}
|
@@ -1,4 +1,13 @@
|
||||
///
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2025
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file Splash.hpp
|
||||
/// @desc The start-screen for the game. Displays our logo, and a loading bar.
|
||||
/// @edit 1/28/2025
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -11,23 +20,6 @@ namespace CaveGame::Client
|
||||
/// The splash screen is responsible for displaying the Redacted Software Logo and then showing the loading bar.
|
||||
class Splash : public Scene
|
||||
{
|
||||
private:
|
||||
float splash_timer = 1.5f;
|
||||
float load_percent = 0.f;
|
||||
JGL::Texture* splash;
|
||||
|
||||
std::array<JGL::RenderTarget*, 16> column_textures{};
|
||||
|
||||
int column_width = 0;
|
||||
|
||||
std::vector<float> scroll_offsets;
|
||||
std::vector<std::vector<char>> matrix;
|
||||
|
||||
void ComputeMatrixGlyphTable();
|
||||
void ComputeMatrixTextureCache();
|
||||
|
||||
void DrawMatrix();
|
||||
void DrawProgressBar();
|
||||
public:
|
||||
Splash();
|
||||
~Splash() override;
|
||||
@@ -36,8 +28,23 @@ namespace CaveGame::Client
|
||||
void Load() override;
|
||||
void Unload() override;
|
||||
|
||||
//void PassFont(JGL::Font passed);
|
||||
|
||||
[[nodiscard]] bool SplashComplete() const;
|
||||
protected:
|
||||
float splash_timer = 1.5f;
|
||||
float load_percent = 0.f;
|
||||
std::shared_ptr<JGL::Texture> splash = nullptr;
|
||||
|
||||
std::array<JGL::RenderTarget*, 16> column_textures{};
|
||||
int column_width = 0;
|
||||
std::vector<float> scroll_offsets;
|
||||
std::vector<std::vector<char>> matrix;
|
||||
|
||||
protected:
|
||||
void ComputeMatrixGlyphTable();
|
||||
void ComputeMatrixTextureCache();
|
||||
|
||||
void DrawMatrix();
|
||||
void DrawProgressBar();
|
||||
|
||||
};
|
||||
}
|
@@ -1,3 +1,15 @@
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2025
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file StatsWindow.hpp
|
||||
/// @desc Window that displays debugging stats.
|
||||
/// @edit 1/28/2025
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <JUI/Widgets/Window.hpp>
|
||||
|
71
Client/include/Client/TileTool.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2025
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file TileTool.hpp
|
||||
/// @desc Re-WorldEdit Terrain Editor Tool.
|
||||
/// @edit 1/28/2025
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <JUI/Widgets/Window.hpp>
|
||||
#include <JUI/Widgets/Slider.hpp>
|
||||
#include <JUI/Widgets/Checkbox.hpp>
|
||||
#include <JUI/Widgets/ListLayout.hpp>
|
||||
|
||||
namespace CaveGame::Client {
|
||||
|
||||
using namespace JUI;
|
||||
|
||||
enum class BrushShape {
|
||||
Circle,
|
||||
Square,
|
||||
Diamond,
|
||||
Cross,
|
||||
X
|
||||
};
|
||||
|
||||
|
||||
|
||||
class TileTool : public JUI::Window
|
||||
{
|
||||
static const bool WorldEditorEnabledByDefault = false;
|
||||
public:
|
||||
Event<float> BrushSizeChanged;
|
||||
Event<float> BrushPercentChanged;
|
||||
Event<bool> TileSimulationDisabledChanged;
|
||||
Event<int> TileSimulationStep;
|
||||
|
||||
float BrushRadius();
|
||||
void BrushRadius(float size);
|
||||
float BrushDensity() const;
|
||||
void BrushDensity(float percent);
|
||||
|
||||
|
||||
|
||||
explicit TileTool(Widget* parent);
|
||||
void Enable(bool value);
|
||||
bool IsEnabled() const { return enabled; }
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
float brush_radius = 8.f;
|
||||
float brush_density = 100.f;
|
||||
protected:
|
||||
const int row_height = 20;
|
||||
const std::string tool_title = "Re-WorldEdit";
|
||||
Slider* brush_size_slider;
|
||||
Slider* brush_percent_slider;
|
||||
TextRect* tool_size_label;
|
||||
TextButton* step_btn;
|
||||
TextButton* step2_btn;
|
||||
TextButton* step3_btn;
|
||||
bool enabled = false;
|
||||
bool tile_sim_disabled = false;
|
||||
private:
|
||||
};
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <JUI/JUI.hpp>
|
||||
#include <JUI/Widgets/Scene.hpp>
|
||||
|
||||
namespace CaveGame::Client
|
||||
{
|
||||
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Core/Chunk.hpp>
|
@@ -1,6 +1,9 @@
|
||||
#include <Client/AssetService.hpp>
|
||||
|
||||
#include <thread>
|
||||
#include <JGL/types/Texture.h>
|
||||
#include <JJX/JSON.hpp>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
static CaveGame::Client::AssetService* singleton;
|
||||
|
||||
@@ -8,16 +11,148 @@ CaveGame::Client::AssetService * CaveGame::Client::AssetService::Get() { return
|
||||
|
||||
CaveGame::Client::AssetService::AssetService() {
|
||||
singleton = this;
|
||||
|
||||
ParseManifest();
|
||||
}
|
||||
|
||||
void CaveGame::Client::AssetService::TempLoadSimple() {
|
||||
player_sprite = new JGL::Texture("assets/textures/player.png", JGL::TextureFilteringMode::NEAREST);
|
||||
explosion_sprite = new JGL::Texture("assets/textures/explosion.png", JGL::TextureFilteringMode::NEAREST);
|
||||
|
||||
|
||||
std::string CaveGame::Client::AssetService::FilenameFromPath(const std::filesystem::path &path) {
|
||||
return path.string().substr(path.string().find_last_of("/\\") + 1);
|
||||
}
|
||||
|
||||
void CaveGame::Client::AssetService::EnqueueDefaultAssetsFolder() {
|
||||
//EnqueueContents("assets");
|
||||
std::string CaveGame::Client::AssetService::FilenameFromPathWithoutExtension(const std::filesystem::path &path) {
|
||||
auto base = path.string().substr(path.string().find_last_of("/\\") + 1);
|
||||
|
||||
textures.emplace("player", new JGL::Texture("assets/textures/player.png"));
|
||||
std::string::size_type const p(base.find_last_of('.'));
|
||||
return base.substr(0, p);
|
||||
}
|
||||
|
||||
void CaveGame::Client::AssetService::EnqueueTexture(const std::string& name, const std::filesystem::path &path) {
|
||||
queue.push({name, path, AssetType::TEXTURE});
|
||||
total_queued++;
|
||||
}
|
||||
|
||||
void CaveGame::Client::AssetService::EnqueueFont(const std::string& name, const std::filesystem::path &path) {
|
||||
queue.push({name, path, AssetType::FONT});
|
||||
total_queued++;
|
||||
}
|
||||
|
||||
void CaveGame::Client::AssetService::EnqueueSound(const std::string& name, const std::filesystem::path &path) {
|
||||
queue.push({name, path, AssetType::AUDIO});
|
||||
total_queued++;
|
||||
}
|
||||
|
||||
bool CaveGame::Client::AssetService::LoadAsset(const CaveGame::Client::AssetRequest &request) {
|
||||
last_asset_processed = request.path.string();
|
||||
|
||||
switch(request.type) {
|
||||
case AssetType::TEXTURE: {
|
||||
if (textures.contains(request.name)) // TODO: Note repeat request.
|
||||
return true;
|
||||
|
||||
auto texture = std::make_shared<Texture>(request.path, FilteringMode::NEAREST);
|
||||
textures[request.name] = texture;
|
||||
}
|
||||
default: {
|
||||
// TODO: We don't support this asset type yet!!!
|
||||
}
|
||||
}
|
||||
last_asset_processed = request.path.string(); //FilenameFromPath(request.path);
|
||||
total_loaded++;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CaveGame::Client::AssetService::LoadFromQueue(float maxTimeExpenditure) {
|
||||
|
||||
using fsec = std::chrono::duration<float>;
|
||||
using clock = std::chrono::high_resolution_clock;
|
||||
|
||||
if (queue.empty())
|
||||
return true;
|
||||
|
||||
|
||||
auto start = clock::now();
|
||||
|
||||
//while (!queue.empty() && maxTimeExpenditure > 0.f)
|
||||
//{
|
||||
AssetRequest request = queue.front();
|
||||
queue.pop();
|
||||
|
||||
if (!LoadAsset(request))
|
||||
{
|
||||
std::cerr << "bruh" << std::endl;
|
||||
// Failed to load!!!
|
||||
}
|
||||
auto cur_time = clock::now();
|
||||
fsec fs = cur_time - start;
|
||||
maxTimeExpenditure -= fs.count();
|
||||
//std::this_thread::sleep_for(250ms);
|
||||
//}
|
||||
return queue.empty();
|
||||
|
||||
}
|
||||
|
||||
void CaveGame::Client::AssetService::LoadAllFromQueue() {
|
||||
|
||||
while (!queue.empty())
|
||||
{
|
||||
AssetRequest request = queue.front();
|
||||
queue.pop();
|
||||
if (!LoadAsset(request))
|
||||
{
|
||||
std::cerr << "bruh" << std::endl;
|
||||
// Failed to load!!!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CaveGame::Client::AssetService::ParseManifest() {
|
||||
std::string contents = read_file("assets/data/manifest.json");
|
||||
|
||||
using namespace JJX;
|
||||
|
||||
auto [obj, errcode] = json::parse(contents);
|
||||
|
||||
json::object catalog = obj.as_object();
|
||||
|
||||
// TODO: in this case, calling obj.at("textures") results in a bad_alloc?
|
||||
|
||||
if (catalog.contains("textures")) {
|
||||
json::array texlist = catalog.at("textures").as_array();
|
||||
for (auto& texture_entry : texlist) {
|
||||
std::string name = texture_entry[0].string.value();
|
||||
std::string path = texture_entry[1].string.value();
|
||||
EnqueueTexture(name, path);
|
||||
}
|
||||
}
|
||||
|
||||
if (catalog.contains("fonts")) {
|
||||
for (auto &font_entry: catalog["fonts"].as_array()) {
|
||||
std::string name = font_entry[0].string.value();
|
||||
std::string path = font_entry[1].string.value();
|
||||
EnqueueFont(name, path);
|
||||
}
|
||||
}
|
||||
|
||||
if (catalog.contains("sfx")) {
|
||||
for (auto &sfx_entry: catalog["sfx"].as_array()) {
|
||||
std::string name = sfx_entry[0].string.value();
|
||||
std::string path = sfx_entry[1].string.value();
|
||||
EnqueueSound(name, path);
|
||||
}
|
||||
}
|
||||
|
||||
if (catalog.contains("music")) {
|
||||
for (auto &sfx_entry: catalog["music"].as_array()) {
|
||||
std::string name = sfx_entry[0].string.value();
|
||||
std::string path = sfx_entry[1].string.value();
|
||||
EnqueueSound(name, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CaveGame::Client::AssetService::PreloadCertainAssets() {
|
||||
LoadAsset({"redacted", "assets/textures/redacted.png", AssetType::TEXTURE});
|
||||
LoadAsset({"title", "assets/textures/title_1.png", AssetType::TEXTURE});
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
#include <Client/Camera2D.hpp>
|
||||
#include <rewindow/inputservice.hpp>
|
||||
#include <ReWindow/InputService.h>
|
||||
#include <jstick.hpp>
|
||||
|
||||
namespace CaveGame::Client
|
||||
{
|
||||
@@ -11,39 +12,63 @@ namespace CaveGame::Client
|
||||
{
|
||||
// TODO: implement freecam panning via mouse.
|
||||
|
||||
|
||||
|
||||
Vector2 m = {0,0};
|
||||
|
||||
if (jstick::GetLeftThumbstickAxis().Magnitude() > 0.f)
|
||||
m = jstick::GetLeftThumbstickAxisNormalized();
|
||||
|
||||
if (m.Magnitude() > 0.1f)
|
||||
Move(m*elapsed);
|
||||
|
||||
if (InputService::IsKeyDown(Keys::LeftArrow))
|
||||
MoveLeft();
|
||||
MoveLeft(elapsed);
|
||||
if (InputService::IsKeyDown(Keys::RightArrow))
|
||||
MoveRight();
|
||||
MoveRight(elapsed);
|
||||
if (InputService::IsKeyDown(Keys::UpArrow))
|
||||
MoveUp();
|
||||
MoveUp(elapsed);
|
||||
if (InputService::IsKeyDown(Keys::DownArrow))
|
||||
MoveDown();
|
||||
MoveDown(elapsed);
|
||||
|
||||
if (InputService::IsKeyDown(Keys::LeftBracket))
|
||||
Rotate(-5.f*elapsed);
|
||||
if (InputService::IsKeyDown(Keys::RightBracket))
|
||||
Rotate(5.f*elapsed);
|
||||
|
||||
if (InputService::IsKeyDown(Keys::Minus))
|
||||
if (InputService::IsKeyDown(Keys::Minus) || jstick::IsButtonDown(jstick::XBoxButton::Back))
|
||||
ZoomOut(elapsed);
|
||||
if (InputService::IsKeyDown(Keys::Equals))
|
||||
if (InputService::IsKeyDown(Keys::Equals) || jstick::IsButtonDown(jstick::XBoxButton::Start))
|
||||
ZoomIn(elapsed);
|
||||
}
|
||||
|
||||
void Camera2D::Update(float elapsed) {
|
||||
|
||||
last_free_move += elapsed;
|
||||
|
||||
|
||||
|
||||
|
||||
float t = 1 - move_smoothing;
|
||||
float step = 1.f - Math::Pow(t, elapsed);
|
||||
//float step = Math::Exp(- (move_smoothing*elapsed) );
|
||||
|
||||
if (freecam)
|
||||
UpdateFreecamControls(elapsed);
|
||||
|
||||
if (lerp_to_target)
|
||||
position = Vector2::Lerp(position, target, Math::Min(elapsed*lerp_factor, 1.f));
|
||||
else
|
||||
position = target;
|
||||
if (last_free_move >= free_move_cooldown)
|
||||
{
|
||||
if (lerp_to_target)
|
||||
position = Vector2::Lerp(position, target, step);
|
||||
else
|
||||
position = target;
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// If **really** close, snap to the final position to stabilize.
|
||||
if (position.DistanceSq(target) < 1e-3f)
|
||||
{
|
||||
if (position.DistanceSq(target) < 1e-7f) {
|
||||
position = target;
|
||||
}
|
||||
|
||||
@@ -95,20 +120,30 @@ namespace CaveGame::Client
|
||||
|
||||
Vector2 Camera2D::Position() const { return position; }
|
||||
|
||||
void Camera2D::Move(const Vector2& dir)
|
||||
{
|
||||
position += dir * move_speed;
|
||||
last_free_move = 0;
|
||||
}
|
||||
|
||||
void Camera2D::MoveLeft(float rate) {
|
||||
target -= Vector2(speed*rate, 0);
|
||||
position -= Vector2(move_speed * rate, 0);
|
||||
last_free_move = 0;
|
||||
}
|
||||
|
||||
void Camera2D::MoveRight(float rate) {
|
||||
target += Vector2(speed*rate, 0);
|
||||
position += Vector2(move_speed * rate, 0);
|
||||
last_free_move = 0;
|
||||
}
|
||||
|
||||
void Camera2D::MoveUp(float rate) {
|
||||
target -= Vector2(0, speed*rate);
|
||||
position -= Vector2(0, move_speed * rate);
|
||||
last_free_move = 0;
|
||||
}
|
||||
|
||||
void Camera2D::MoveDown(float rate) {
|
||||
target += Vector2(0, speed*rate);
|
||||
position += Vector2(0, move_speed * rate);
|
||||
last_free_move = 0;
|
||||
}
|
||||
|
||||
void Camera2D::PassViewportSize(const Vector2 &size) {
|
||||
@@ -140,9 +175,9 @@ namespace CaveGame::Client
|
||||
|
||||
void Camera2D::Target(const Vector2 &pos) { target = pos; }
|
||||
|
||||
float Camera2D::PositionSmoothingFactor() const { return lerp_factor;}
|
||||
float Camera2D::PositionSmoothingFactor() const { return move_smoothing;}
|
||||
|
||||
void Camera2D::PositionSmoothingFactor(float smoothing) { lerp_factor = smoothing;}
|
||||
void Camera2D::PositionSmoothingFactor(float smoothing) { move_smoothing = smoothing;}
|
||||
|
||||
bool Camera2D::PositionSmoothingEnabled() const { return lerp_to_target;}
|
||||
|
||||
@@ -152,10 +187,10 @@ namespace CaveGame::Client
|
||||
position = newPos;
|
||||
}
|
||||
|
||||
float Camera2D::MoveSpeed() const { return speed;}
|
||||
float Camera2D::MoveSpeed() const { return move_speed;}
|
||||
|
||||
void Camera2D::MoveSpeed(float value) {
|
||||
speed = value;
|
||||
move_speed = value;
|
||||
}
|
||||
|
||||
AABB2D Camera2D::ScaledViewportOversized(float oversize) const {
|
||||
|
@@ -1 +0,0 @@
|
||||
#include <Client/Console.hpp>
|
6
Client/src/Client/ContainerWindow.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#include <Client/ContainerWindow.hpp>
|
||||
|
||||
|
||||
namespace CaveGame::Client {
|
||||
|
||||
}
|
@@ -1,16 +1,18 @@
|
||||
#include <Client/CreditsWindow.hpp>
|
||||
|
||||
JUI::TextRect* line_item(const std::string& content, int size, const Color4& color = Colors::White)
|
||||
{
|
||||
int line_index = 0;
|
||||
JUI::TextRect* line_item(const std::string& content, int size, const Color4& color = Colors::White) {
|
||||
auto* item = new JUI::TextRect();
|
||||
item->SetFont(JGL::Fonts::Jupiteroid);
|
||||
item->Font(JGL::Fonts::Jupiteroid);
|
||||
item->Size({0,4+size,1.f,0.f});
|
||||
item->SetTextSize(size);
|
||||
item->SetContent(content);
|
||||
item->AlignLeft();
|
||||
item->TextSize(size);
|
||||
item->Content(content);
|
||||
item->AlignCenterHorizontally();
|
||||
item->AlignTop();
|
||||
item->BGColor({0,0,0,0});
|
||||
item->SetTextColor(color);
|
||||
item->TextColor(color);
|
||||
item->BorderWidth(0);
|
||||
item->LayoutOrder(line_index++);
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -18,22 +20,28 @@ JUI::Window *CaveGame::Client::CreateCreditsWindowWidget(JUI::Widget *parent) {
|
||||
|
||||
auto* credits_window = new JUI::Window(parent);
|
||||
credits_window->SetTitleFont(JGL::Fonts::Jupiteroid);
|
||||
credits_window->SetTitle("Credits");
|
||||
credits_window->MinSize({50, 50});
|
||||
credits_window->Size({300, 400, 0, 0});
|
||||
credits_window->Title("Credits");
|
||||
//credits_window->MinSize({250, 450});
|
||||
//credits_window->Size({300, 450, 0, 0});
|
||||
credits_window->Visible(false);
|
||||
credits_window->ViewportInstance()->BGColor({48, 48, 48});
|
||||
|
||||
auto* listing = new JUI::VerticalListLayout(credits_window->GetViewportInstance());
|
||||
listing->Margin(4_px);
|
||||
listing->Padding(5_px);
|
||||
auto* listing = new JUI::VerticalListLayout(credits_window->ViewportInstance());
|
||||
listing->LayoutOrder(JUI::LayoutOrder::V::BOTTOM);
|
||||
//listing->Margin(4_px);
|
||||
//listing->Padding(5_px);
|
||||
|
||||
int min_height = 0;
|
||||
for (auto[size, color, msg] : Credits)
|
||||
for (auto line : Credits)
|
||||
{
|
||||
listing->Add(line_item(msg, size, color));
|
||||
min_height += size;
|
||||
listing->Add(line_item(line.content, line.text_size, line.text_color));
|
||||
min_height += line.text_size + 6;
|
||||
}
|
||||
|
||||
credits_window->MinSize({300, (float)min_height});
|
||||
credits_window->Size(UDim2::FromPixels(300, min_height));
|
||||
credits_window->SetResizable(false);
|
||||
|
||||
/*
|
||||
line_item("CaveGame", 40, listing);
|
||||
line_item("A Redacted Software Product", 16, listing);
|
||||
@@ -50,7 +58,7 @@ JUI::Window *CaveGame::Client::CreateCreditsWindowWidget(JUI::Widget *parent) {
|
||||
line_item("Karl Darling", 24, listing);
|
||||
line_item("Financial Advisor", 16, listing);*/
|
||||
|
||||
credits_window->Size({300, Math::Max(400, min_height), 0, 0});
|
||||
//credits_window->Size({300, Math::Max(400, min_height), 0, 0});
|
||||
|
||||
return credits_window;
|
||||
}
|
||||
|
@@ -2,27 +2,5 @@
|
||||
#include <Core/Entity.hpp>
|
||||
#include <Core/Player.hpp>
|
||||
#include <JGL/JGL.h>
|
||||
#include <rewindow/inputservice.hpp>
|
||||
#include <ReWindow/InputService.h>
|
||||
|
||||
// TODO: Move this shit to Client/Player.cpp
|
||||
void CaveGame::Core::Player::Draw() {
|
||||
auto myAsset = Client::AssetService::Get()->player_sprite;
|
||||
JGL::J2D::DrawPartialSprite(myAsset, position, {0,0}, {16, 24});
|
||||
JGL::J2D::OutlineRect(Colors::Red, position, bounding_box);
|
||||
}
|
||||
|
||||
void CaveGame::Core::Player::Update(float elapsed) {
|
||||
Humanoid::Update(elapsed);
|
||||
|
||||
if (InputService::IsKeyDown(Keys::A))
|
||||
WalkLeft();
|
||||
|
||||
if (InputService::IsKeyDown(Keys::D))
|
||||
WalkRight();
|
||||
|
||||
if (InputService::IsKeyDown(Keys::W))
|
||||
Jump();
|
||||
|
||||
if (InputService::IsKeyDown(Keys::S))
|
||||
Accelerate({0, -1});
|
||||
}
|
@@ -4,17 +4,27 @@
|
||||
|
||||
|
||||
namespace CaveGame::Core {
|
||||
|
||||
|
||||
void Explosion::DrawFrame(const Texture* texture, const AABB2D& quad, const Vector2& pos, const Vector2& scale)
|
||||
{
|
||||
JGL::J2D::DrawPartialSprite(texture, pos, quad.minPoint, quad.maxPoint, rotation, {0.5f, 0.5f}, scale);
|
||||
}
|
||||
|
||||
void Explosion::Draw() {
|
||||
if (!HasDetonated()) { return; }
|
||||
if (anim_timer >= 1.25f) { return; }
|
||||
|
||||
if (HasDetonated()) {
|
||||
if (anim_timer < 1.25f) {
|
||||
//JGL::J2D::Begin();
|
||||
float draw_radius = radius * 1.25f;
|
||||
|
||||
float draw_radius = radius * 1.25f;
|
||||
auto* tex = Client::AssetService::Get()->explosion_sprite;
|
||||
|
||||
auto* tex = Client::AssetService::Get()->explosion_sprite;
|
||||
Vector2 pos = position - (Vector2(draw_radius));
|
||||
Vector2 scale {draw_radius/16.f, draw_radius/16.f};
|
||||
|
||||
if (anim_timer > (4.f / 5.f)) {
|
||||
DrawFrame(tex, CurrentFrame(), pos, scale);
|
||||
|
||||
/*if (anim_timer > (4.f / 5.f)) {
|
||||
JGL::J2D::DrawPartialSprite(tex, position-(Vector2(draw_radius)), SP_EXPLOSION4.minPoint, SP_EXPLOSION4.maxPoint, rotation, {0.5f,0.5f}, {draw_radius/16.f, draw_radius/16.f});
|
||||
} else if (anim_timer > (3.f / 5.f)) {
|
||||
JGL::J2D::DrawPartialSprite(tex, position-(Vector2(draw_radius)), SP_EXPLOSION3.minPoint, SP_EXPLOSION3.maxPoint, rotation, {0.5f, 0.5f}, {draw_radius/16.f, draw_radius/16.f});
|
||||
@@ -24,10 +34,8 @@ namespace CaveGame::Core {
|
||||
JGL::J2D::DrawPartialSprite(tex, position-(Vector2(draw_radius)), SP_EXPLOSION1.minPoint, SP_EXPLOSION1.maxPoint, rotation, {0.5f, 0.5f}, {draw_radius/16.f, draw_radius/16.f});
|
||||
} else {
|
||||
JGL::J2D::DrawPartialSprite(tex, position-(Vector2(draw_radius)), SP_EXPLOSION0.minPoint, SP_EXPLOSION0.maxPoint, rotation, {0.5f, 0.5f}, {draw_radius/16.f, draw_radius/16.f});
|
||||
}
|
||||
}*/
|
||||
|
||||
//JGL::J2D::End();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,13 +1,18 @@
|
||||
#include <Client/GameSession.hpp>
|
||||
#include <Core/Loggers.hpp>
|
||||
#include <Client/SettingsMenu.hpp>
|
||||
#include <JUI/Widgets/Rect.hpp>
|
||||
#include <JUI/Widgets/ListLayout.hpp>
|
||||
#include <JUI/UDim.hpp>
|
||||
#include <JUI/UDim2.hpp>
|
||||
#include <rewindow/inputservice.hpp>
|
||||
#include <ReWindow/InputService.h>
|
||||
|
||||
#include "JUI/Widgets/TextRect.hpp"
|
||||
#include "JUI/UDim.hpp"
|
||||
#include <JUI/Widgets/ImageRect.hpp>
|
||||
#include <JUI/Widgets/Image.hpp>
|
||||
#include <Core/Player.hpp>
|
||||
#include "jstick.hpp"
|
||||
|
||||
void CaveGame::Client::GameSession::Unload() {
|
||||
Scene::Unload();
|
||||
@@ -16,38 +21,208 @@ void CaveGame::Client::GameSession::Unload() {
|
||||
|
||||
void CaveGame::Client::GameSession::Load() {
|
||||
Scene::Load();
|
||||
ConstructHUD();
|
||||
|
||||
world->PostInit();
|
||||
Logs::Info("Building player HUD.");
|
||||
hud = new JUI::Scene();
|
||||
|
||||
pause_menu = new PauseMenuWidget(hud);
|
||||
pause_menu->OnQuitButtonPressed += [this] (){ SaveAndExit();};
|
||||
pause_menu->OnResumeButtonPressed += [this] () { pause_menu->Close(); };
|
||||
pause_menu->OnSettingsButtonPressed += [this] () { RequestToggleSettings.Invoke(); };
|
||||
|
||||
hotbar = TileHotbar();
|
||||
// TODO: Redundant, use the constructor.
|
||||
hotbar.Load(world);
|
||||
hotbar.GetRootWidget()->Parent(hud);
|
||||
|
||||
auto conn = jstick::ButtonPressed += [&, this] (jstick::XBoxButton btn) mutable {
|
||||
if (btn == jstick::XBoxButton::BumperL)
|
||||
hotbar.PrevSlot();
|
||||
|
||||
if (btn == jstick::XBoxButton::BumperR)
|
||||
hotbar.NextSlot();
|
||||
};
|
||||
|
||||
//conn.Invoke(jstick::XBoxButton::BumperR);
|
||||
|
||||
//jstick::ButtonPressed.Invoke(jstick::XBoxButton::BumperL);
|
||||
|
||||
tile_tool = new TileTool(hud);
|
||||
|
||||
tile_tool->TileSimulationDisabledChanged += [this] (bool value) {
|
||||
World()->SetTileSimulationEnabled(!value);
|
||||
};
|
||||
|
||||
tile_tool->TileSimulationStep += [this] (int steps) {
|
||||
for (int i = 0; i < steps; i++)
|
||||
World()->DoTileTiccs(0.f);
|
||||
};
|
||||
}
|
||||
|
||||
void CaveGame::Client::GameSession::Draw() {
|
||||
world->Draw();
|
||||
hud->Draw();
|
||||
//for (auto& entity: entity_list)
|
||||
//{
|
||||
// entity->Draw();
|
||||
//}
|
||||
|
||||
WorldEditToolDrawOverlay();
|
||||
}
|
||||
|
||||
void CaveGame::Client::GameSession::SaveAndExit()
|
||||
{
|
||||
void CaveGame::Client::GameSession::SaveAndExit() {
|
||||
world->SaveAndExit();
|
||||
OnSessionExit.Invoke();
|
||||
}
|
||||
|
||||
|
||||
void CaveGame::Client::GameSession::WorldEditToolDrawTiles(int x, int y, int radius, int density, TileID tile) {
|
||||
tool_rng = RNG(x + y);
|
||||
World()->SetTile(x, y, tile);
|
||||
|
||||
for (int dx = -radius; dx <= radius; ++dx)
|
||||
for (int dy = -radius; dy <= radius; ++dy)
|
||||
if (density >= 100 || density > tool_rng.Float(0, 99))
|
||||
if ( Math::Abs(dx*dx) + Math::Abs(dy*dy) < (radius*radius))
|
||||
World()->SetTile(x+dx, y+dy, tile);
|
||||
}
|
||||
|
||||
bool following = false;
|
||||
Vector2 last = {0,0};
|
||||
|
||||
|
||||
Vector2 CaveGame::Client::GameSession::MouseWorldPos() const {
|
||||
return World()->camera.ScreenToWorld(mouse_pos);
|
||||
}
|
||||
|
||||
void CaveGame::Client::GameSession::WorldEditToolDrawTiles(TileID tile) {
|
||||
Vector2 tile_radius = {0.5f, 0.5f};
|
||||
Vector2 world_coords = MouseWorldPos() - tile_radius;
|
||||
//Vector2i rounded_coords = {Math::FloorInt(world_coords.x), Math::FloorInt(world_coords.y)};
|
||||
|
||||
|
||||
int radius = Math::FloorInt(tile_tool->BrushRadius());
|
||||
int density = Math::FloorInt(tile_tool->BrushDensity());
|
||||
int x = Math::FloorInt(world_coords.x);
|
||||
int y = Math::FloorInt(world_coords.y);
|
||||
|
||||
Vector2 floor_wc = Vector2(x, y);
|
||||
|
||||
float dist = world_coords.Distance(last);
|
||||
|
||||
//if (dist > 1)
|
||||
//{
|
||||
for (int i = 0; i < dist; i++)
|
||||
{
|
||||
|
||||
Vector2 step = last.Lerp(world_coords, i / dist);
|
||||
|
||||
x = Math::FloorInt(step.x);
|
||||
y = Math::FloorInt(step.y);
|
||||
|
||||
WorldEditToolDrawTiles(x, y, radius, density, tile);
|
||||
|
||||
}
|
||||
last = floor_wc;
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
void CaveGame::Client::GameSession::WorldEditToolControlsUpdate(float elapsed){
|
||||
if (InputService::IsMouseButtonDown(MouseButtons::Left) || jstick::GetLeftTriggerNormalized() > 0.1f) {
|
||||
if (!following)
|
||||
{
|
||||
last = MouseWorldPos();
|
||||
following = true;
|
||||
}
|
||||
|
||||
|
||||
WorldEditToolDrawTiles(Tiles()["air"].numeric_id);
|
||||
} else {
|
||||
if (following)
|
||||
following = false;
|
||||
}
|
||||
|
||||
|
||||
if (InputService::IsMouseButtonDown(MouseButtons::Right) || jstick::GetRightTriggerNormalized() > 0.1f) {
|
||||
if (!following)
|
||||
{
|
||||
last = MouseWorldPos();
|
||||
following = true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
WorldEditToolDrawTiles(hotbar.GetCurrentSlotTileID());
|
||||
} else
|
||||
if (following)
|
||||
following = false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool lp = false;
|
||||
bool rp = false;
|
||||
|
||||
void CaveGame::Client::GameSession::Update(float elapsed) {
|
||||
world->Update(elapsed);
|
||||
hud->Update(elapsed);
|
||||
HUDTick();
|
||||
//for (auto& entity: entity_list)
|
||||
//{
|
||||
// entity->Update(elapsed);
|
||||
//}
|
||||
hotbar.Update(elapsed);
|
||||
|
||||
Vector2 transformed = World()->camera.ScreenToWorld(mouse_pos);
|
||||
|
||||
// Find closest player to camera and target them.
|
||||
for (auto* e : world->GetEntities()) {
|
||||
if ( dynamic_cast<Player*>(e) != nullptr)
|
||||
{
|
||||
world->camera.Target(e->Position());
|
||||
}
|
||||
}
|
||||
|
||||
// Move the tile cursor when controller thumbstick is moved.
|
||||
Vector2 rstick = {0,0};
|
||||
if (jstick::GetRightThumbstickAxis().Magnitude() > 0)
|
||||
rstick = jstick::GetRightThumbstickAxisNormalized();
|
||||
|
||||
if (rstick.Magnitude() > 0.1f)
|
||||
{
|
||||
float joystick_cursor_speed = 250.f;
|
||||
mouse_pos += rstick*elapsed*joystick_cursor_speed;
|
||||
}
|
||||
}
|
||||
|
||||
void CaveGame::Client::GameSession::WorldEditToolDrawOverlay()
|
||||
{
|
||||
JGL::J2D::Begin();
|
||||
|
||||
auto camera = World()->camera;
|
||||
|
||||
// TODO: The following Translation, Rotation, Scale transformation code is duplicated between here and LocalWorld.cpp:Draw.
|
||||
|
||||
// Shift the origin to the center of the screen.
|
||||
glTranslatef(camera.HalfSizeOffset().x, camera.HalfSizeOffset().y, 0);
|
||||
|
||||
// Apply rotation, zoom, and translation.
|
||||
glRotatef(camera.Rotation(), 0, 0, 1);
|
||||
glScalef(camera.Zoom(), camera.Zoom(), 1);
|
||||
|
||||
glTranslatef((int64_t) -camera.Position().x, (int64_t) -camera.Position().y, 0);
|
||||
|
||||
auto mpos = Vector2(mouse_pos.x, mouse_pos.y);
|
||||
|
||||
auto pos = camera.ScreenToWorld(mpos);
|
||||
|
||||
JGL::J2D::OutlineCircle(Colors::Red, pos, tile_tool->BrushRadius());
|
||||
|
||||
JGL::J2D::DrawPoint({128, 128, 128, 128}, pos, 1.1f);
|
||||
|
||||
JGL::J2D::End();
|
||||
}
|
||||
|
||||
|
||||
CaveGame::Client::GameSession::GameSession(bool overwite_world) : Scene() {
|
||||
world = new LocalWorld("test_world", 0, overwite_world);
|
||||
|
||||
// Spawn in the player when the world is first loaded up.
|
||||
world->AddEntity(new Core::Player({0, 0}));
|
||||
//hud = new JUI::Scene();
|
||||
}
|
||||
|
||||
@@ -56,109 +231,38 @@ void CaveGame::Client::GameSession::PassWindowSize(const Vector2 &size) {
|
||||
hud->SetViewportSize(size);
|
||||
}
|
||||
|
||||
void CaveGame::Client::GameSession::PassMouseInput(unsigned int a, bool b) {
|
||||
hud->ObserveMouseInput((JUI::MouseButton)a, b);
|
||||
}
|
||||
|
||||
void CaveGame::Client::GameSession::PassMouseMovement(const J3ML::LinearAlgebra::Vector2 &pos) {
|
||||
|
||||
mouse_pos = pos;
|
||||
world->mouse_pos = pos;
|
||||
hud->ObserveMouseMovement(pos);
|
||||
}
|
||||
|
||||
void CaveGame::Client::GameSession::PassMouseWheel(int wheel) {
|
||||
if (InputService::IsKeyDown(Keys::LeftShift)) {
|
||||
float radius = tile_tool->BrushRadius();
|
||||
radius -= wheel / 2.f;
|
||||
radius = Math::Max(radius, 0.45f); // TODO: perform clamping inside the setter maybe?
|
||||
tile_tool->BrushRadius(radius);
|
||||
} else {
|
||||
// TODO: hud->ObserveMouseWheel(wheel);
|
||||
hotbar.OnMouseWheel(wheel);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CaveGame::Client::GameSession::PassKeyInput(Key key, bool pressed) {
|
||||
// DO NOT chain input conditions with else; you will introduce biasing
|
||||
|
||||
if (key == Keys::One)
|
||||
active_hotbar_slot = 0;
|
||||
if (key == Keys::Two)
|
||||
active_hotbar_slot = 1;
|
||||
if (key == Keys::Three)
|
||||
active_hotbar_slot = 2;
|
||||
if (key == Keys::Four)
|
||||
active_hotbar_slot = 3;
|
||||
if (key == Keys::Five)
|
||||
active_hotbar_slot = 4;
|
||||
if (key == Keys::Six)
|
||||
active_hotbar_slot = 5;
|
||||
if (key == Keys::Seven)
|
||||
active_hotbar_slot = 6;
|
||||
if (key == Keys::Eight)
|
||||
active_hotbar_slot = 7;
|
||||
if (key == Keys::Nine)
|
||||
active_hotbar_slot = 8;
|
||||
if (key == Keys::Zero)
|
||||
active_hotbar_slot = 9;
|
||||
|
||||
|
||||
/*if (key == Keys::LeftArrow)
|
||||
{
|
||||
world->camera.MoveLeft();
|
||||
}
|
||||
if (key == Keys::RightArrow)
|
||||
{
|
||||
world->camera.MoveRight();
|
||||
}
|
||||
if (key == Keys::UpArrow)
|
||||
{
|
||||
world->camera.MoveUp();
|
||||
}
|
||||
if (key == Keys::DownArrow)
|
||||
{
|
||||
world->camera.MoveDown();
|
||||
}*/
|
||||
}
|
||||
|
||||
void CaveGame::Client::GameSession::ConstructHUD() {
|
||||
|
||||
Logs::Info("Building player HUD.");
|
||||
|
||||
hud = new JUI::Scene();
|
||||
|
||||
auto* hotbar_rect = new JUI::Rect(hud);
|
||||
hotbar_rect->BGColor(Colors::LightGray);
|
||||
hotbar_rect->BorderColor(Colors::White);
|
||||
hotbar_rect->SetBorderWidth(1.5f);
|
||||
hotbar_rect->CornerRounding(8);
|
||||
hotbar_rect->Size({488_px, 18_px});
|
||||
hotbar_rect->Position({50_percent, 98_percent});
|
||||
hotbar_rect->AnchorPoint({0.5f, 1.f});
|
||||
hotbar_rect->BorderMode(JUI::BorderMode::Outline);
|
||||
|
||||
item_label = new JUI::TextRect(hotbar_rect);
|
||||
item_label->BGColor({0,0,0,0});
|
||||
item_label->AnchorPoint({1,0});
|
||||
item_label->SetContent("Item Name Here");
|
||||
item_label->SetTextColor(Colors::White);
|
||||
item_label->SetTextSize(22);
|
||||
item_label->Size({200_px, 30_px});
|
||||
item_label->Position({0_percent, JUI::UDim(-25, 0)});
|
||||
item_label->AlignRight();
|
||||
|
||||
auto* layout = new JUI::HorizontalListLayout(hotbar_rect);
|
||||
layout->PaddingLeft(4_px);
|
||||
layout->PaddingRight(4_px);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
auto item = GetByNumeric(HotbarItems[i]);
|
||||
|
||||
auto* cell = new JUI::Rect(layout);
|
||||
cell->Name("hotbar_" + std::to_string(i));
|
||||
cell->BGColor(Colors::LightGray);
|
||||
cell->BorderColor(Colors::White);
|
||||
cell->Size({48_px, 48_px});
|
||||
cell->AnchorPoint({0.f, 0.8f});
|
||||
cell->CornerRounding(5);
|
||||
cell->SetBorderWidth(1.5f);
|
||||
cell->BorderMode(JUI::BorderMode::Outline);
|
||||
cell->PaddingBottom(10_px);
|
||||
cell->PaddingRight(5_px);
|
||||
cell->BGColor(item->base_color);
|
||||
|
||||
auto* amount_label = new JUI::TextRect(cell);
|
||||
amount_label->BGColor({0,0,0,0});
|
||||
amount_label->AnchorPoint({1,1});
|
||||
amount_label->SetContent("99x");
|
||||
amount_label->SetTextColor(Colors::White);
|
||||
amount_label->SetTextSize(18);
|
||||
amount_label->Size({30_px, 30_px});
|
||||
amount_label->Position({95_percent, 95_percent});
|
||||
amount_label->AlignRight();
|
||||
|
||||
hotbar_elements[i] = cell;
|
||||
}
|
||||
if (key == Keys::Escape && pressed)
|
||||
pause_menu->Toggle();
|
||||
|
||||
hud->ObserveKeyInput(key, pressed);
|
||||
hotbar.OnKeyInput(key, pressed);
|
||||
}
|
||||
|
||||
void CaveGame::Client::GameSession::UnloadHUD() {
|
||||
@@ -166,7 +270,26 @@ void CaveGame::Client::GameSession::UnloadHUD() {
|
||||
delete hud;
|
||||
}
|
||||
|
||||
CaveGame::Core::TileID CaveGame::Client::GameSession::GetTileUnderMouse() {
|
||||
Vector2 coords = world->camera.ScreenToWorld(InputService::GetMousePosition());
|
||||
uint16_t CaveGame::Client::GameSession::GetTileIDUnderMouse() {
|
||||
auto ipos = InputService::GetMousePosition();
|
||||
Vector2 pos = Vector2(ipos.x, ipos.y);
|
||||
|
||||
Vector2 coords = world->camera.ScreenToWorld(pos);
|
||||
return world->GetTile(coords.x, coords.y);
|
||||
}
|
||||
|
||||
void CaveGame::Client::GameSession::ToggleWorldEdit() {
|
||||
tile_tool->Enable(!tile_tool->IsEnabled());
|
||||
}
|
||||
|
||||
CaveGame::Core::Player *CaveGame::Client::GameSession::GetLocalPlayerEntity() {
|
||||
for (auto* e : world->GetEntities()) {
|
||||
auto maybe_plr = dynamic_cast<Core::Player *>(e);
|
||||
if (maybe_plr != nullptr) {
|
||||
return maybe_plr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
178
Client/src/Client/Hotbar.cpp
Normal file
@@ -0,0 +1,178 @@
|
||||
#include <Client/Hotbar.hpp>
|
||||
#include <JUI/Widgets/ListLayout.hpp>
|
||||
#include <JUI/Widgets/Image.hpp>
|
||||
#include <Core/TileRegistry.hpp>
|
||||
|
||||
using namespace JUI::UDimLiterals;
|
||||
using namespace CaveGame::Core;
|
||||
|
||||
void CaveGame::Client::TileHotbar::Load(CaveGame::Client::LocalWorld *world) {
|
||||
|
||||
|
||||
|
||||
slots[0] = Tiles()["grass"].numeric_id;
|
||||
slots[1] = Tiles()["stone"].numeric_id;
|
||||
slots[2] = Tiles()["water"].numeric_id;
|
||||
slots[3] = Tiles()["lava"].numeric_id;
|
||||
slots[4] = Tiles()["green-moss"].numeric_id;
|
||||
slots[5] = Tiles()["sand"].numeric_id;
|
||||
slots[6] = Tiles()["clay"].numeric_id;
|
||||
slots[7] = Tiles()["oak-plank"].numeric_id;
|
||||
slots[8] = Tiles()["cobblestone"].numeric_id;
|
||||
slots[9] = Tiles()["stone-brick"].numeric_id;
|
||||
|
||||
hotbar_root = new JUI::Rect();
|
||||
hotbar_root->BGColor(Colors::LightGray);
|
||||
hotbar_root->BorderColor(Colors::White);
|
||||
hotbar_root->BorderWidth(1.5f);
|
||||
hotbar_root->CornerRounding(8);
|
||||
hotbar_root->Size({488_px, 18_px});
|
||||
hotbar_root->Position({50_percent, 98_percent});
|
||||
hotbar_root->AnchorPoint({0.5f, 1.f});
|
||||
hotbar_root->BorderMode(JUI::BorderMode::Outline);
|
||||
|
||||
item_label = new JUI::TextRect(hotbar_root);
|
||||
item_label->BorderWidth(0);
|
||||
item_label->BGColor({0,0,0,0});
|
||||
item_label->AnchorPoint({1,0});
|
||||
item_label->Content("Item Name Here");
|
||||
item_label->TextColor(Colors::White);
|
||||
item_label->TextSize(22);
|
||||
item_label->Size({200_px, 30_px});
|
||||
item_label->Position({0_percent, JUI::UDim(-25, 0)});
|
||||
item_label->AlignRight();
|
||||
|
||||
auto* layout = new JUI::HorizontalListLayout(hotbar_root);
|
||||
layout->PaddingLeft(4_px);
|
||||
layout->PaddingRight(4_px);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
auto item = Tiles().Get(slots[i]);
|
||||
|
||||
auto* cell = new JUI::Rect(layout);
|
||||
cell->Name("hotbar_" + std::to_string(i));
|
||||
cell->BGColor(Colors::LightGray);
|
||||
cell->BorderColor(Colors::White);
|
||||
cell->Size({48_px, 48_px});
|
||||
cell->AnchorPoint({0.f, 0.8f});
|
||||
cell->CornerRounding(5);
|
||||
cell->BorderWidth(1.5f);
|
||||
cell->BorderMode(JUI::BorderMode::Outline);
|
||||
cell->PaddingBottom(10_px);
|
||||
cell->PaddingRight(5_px);
|
||||
cell->BGColor(item.color);
|
||||
|
||||
// Add tile-sample rendertarget as item icon.
|
||||
auto* img = new JUI::Image(cell);
|
||||
img->FitImageToParent(true);
|
||||
img->Padding({2_px});
|
||||
|
||||
int icon_size = 16;
|
||||
|
||||
auto* canvas_texture = new Texture(Vector2i( icon_size, icon_size));
|
||||
auto* tile_icon_canvas = new RenderTarget(canvas_texture, {0, 0, 0, 0});
|
||||
|
||||
JGL::J2D::Begin(tile_icon_canvas, nullptr, true);
|
||||
for (int x = 0; x < icon_size; x++)
|
||||
for (int y = 0; y < icon_size; y++)
|
||||
world->RenderTile(item.numeric_id, x, y, x, y);
|
||||
JGL::J2D::End();
|
||||
|
||||
delete tile_icon_canvas;
|
||||
img->Content(canvas_texture);
|
||||
|
||||
auto* amount_label = new JUI::TextRect(cell);
|
||||
amount_label->BGColor({0,0,0,0});
|
||||
amount_label->AnchorPoint({1,1});
|
||||
amount_label->Content("99x");
|
||||
amount_label->TextColor(Colors::White);
|
||||
amount_label->TextSize(18);
|
||||
amount_label->Size({30_px, 30_px});
|
||||
amount_label->Position({95_percent, 95_percent});
|
||||
amount_label->AlignRight();
|
||||
amount_label->BorderWidth(0);
|
||||
|
||||
hotbar_elements[i] = cell;
|
||||
}
|
||||
}
|
||||
|
||||
void CaveGame::Client::TileHotbar::OnKeyInput(const Key& key, bool pressed) {
|
||||
if (key == Keys::One)
|
||||
slot_index = 0;
|
||||
if (key == Keys::Two)
|
||||
slot_index = 1;
|
||||
if (key == Keys::Three)
|
||||
slot_index = 2;
|
||||
if (key == Keys::Four)
|
||||
slot_index = 3;
|
||||
if (key == Keys::Five)
|
||||
slot_index = 4;
|
||||
if (key == Keys::Six)
|
||||
slot_index = 5;
|
||||
if (key == Keys::Seven)
|
||||
slot_index = 6;
|
||||
if (key == Keys::Eight)
|
||||
slot_index = 7;
|
||||
if (key == Keys::Nine)
|
||||
slot_index = 8;
|
||||
if (key == Keys::Zero)
|
||||
slot_index = 9;
|
||||
}
|
||||
|
||||
void CaveGame::Client::TileHotbar::Update(float elapsed) {
|
||||
// TODO: Subtly Animate the UI. (JUI has Tweens on the roadmap.)
|
||||
|
||||
// Reset appearance of all slots.
|
||||
for (auto & hotbar_element : hotbar_elements)
|
||||
{
|
||||
hotbar_element->Size({44_px, 48_px});
|
||||
hotbar_element->CornerRounding(5);
|
||||
hotbar_element->BorderWidth(1.5f);
|
||||
hotbar_element->AnchorPoint({0.f, 0.8f});
|
||||
}
|
||||
|
||||
// Update item name label.
|
||||
Core::TileID active_item = slots[slot_index];
|
||||
item_label->Content(Tiles()[active_item].display_name);
|
||||
|
||||
// Set appearance of selected slot.
|
||||
hotbar_elements[slot_index]->CornerRounding(8);
|
||||
hotbar_elements[slot_index]->BorderWidth(1.5f);
|
||||
hotbar_elements[slot_index]->Size({58_px, 56_px});
|
||||
hotbar_elements[slot_index]->AnchorPoint({0.f, 0.75f});
|
||||
}
|
||||
|
||||
CaveGame::Core::TileID CaveGame::Client::TileHotbar::GetSlotTileID(int slotIdx) const {
|
||||
return slots[slotIdx];
|
||||
}
|
||||
|
||||
void CaveGame::Client::TileHotbar::SetSlotTileID(int slotIdx, CaveGame::Core::TileID tid) {
|
||||
slots[slotIdx] = tid;
|
||||
}
|
||||
|
||||
JUI::Rect *CaveGame::Client::TileHotbar::GetSlotWidget(int slotIdx) const {
|
||||
return hotbar_elements[slotIdx];
|
||||
}
|
||||
|
||||
JUI::Rect *CaveGame::Client::TileHotbar::GetRootWidget() const { return hotbar_root; }
|
||||
|
||||
int CaveGame::Client::TileHotbar::GetSlotIndex() const { return slot_index; }
|
||||
|
||||
CaveGame::Core::TileID CaveGame::Client::TileHotbar::GetCurrentSlotTileID() const {
|
||||
return slots[slot_index];
|
||||
}
|
||||
|
||||
void CaveGame::Client::TileHotbar::PrevSlot() {
|
||||
if (slot_index > 0)
|
||||
slot_index -= 1;
|
||||
else
|
||||
slot_index = slot_count-1;
|
||||
}
|
||||
|
||||
void CaveGame::Client::TileHotbar::NextSlot() {
|
||||
if (slot_index < (slot_count-1))
|
||||
slot_index += 1;
|
||||
else
|
||||
slot_index = 0;
|
||||
}
|
@@ -5,8 +5,10 @@
|
||||
#include <fstream>
|
||||
#include <Core/Loggers.hpp>
|
||||
#include <JUI/Widgets/Scene.hpp>
|
||||
#include <Core/TileRegistry.hpp>
|
||||
|
||||
namespace CaveGame::Client {
|
||||
using namespace CaveGame::Core;
|
||||
|
||||
LocalWorld::LocalWorld(const std::string& world_name, int seed, bool overwrite)
|
||||
: World(world_name, seed, overwrite) {
|
||||
@@ -20,60 +22,26 @@ namespace CaveGame::Client {
|
||||
//font = JGL::Font("assets/fonts/Jupiteroid.ttf");
|
||||
}
|
||||
|
||||
void LocalWorld::DrawChunkGrid() const {
|
||||
Vector2 viewport_topleft = camera.ScaledViewport().minPoint;
|
||||
Vector2 viewport_bottomright = camera.ScaledViewport().maxPoint;
|
||||
|
||||
int nearest_grid_left = Math::Floor(viewport_topleft.x / Core::Chunk::ChunkSize);
|
||||
int nearest_grid_right = Math::Floor(viewport_bottomright.x / Core::Chunk::ChunkSize);
|
||||
|
||||
for (int x = nearest_grid_left; x <= nearest_grid_right; x++) {
|
||||
auto top = Vector2(x * Core::Chunk::ChunkSize, viewport_topleft.y);
|
||||
auto bottom = Vector2(x * Core::Chunk::ChunkSize, viewport_bottomright.y);
|
||||
JGL::J2D::DrawLine(Colors::Red, top, bottom);
|
||||
}
|
||||
|
||||
int nearest_grid_top = Math::Floor(viewport_topleft.y / Core::Chunk::ChunkSize);
|
||||
int nearest_grid_bottom = Math::Floor(viewport_bottomright.y / Core::Chunk::ChunkSize);
|
||||
|
||||
for (int y = nearest_grid_top; y <= nearest_grid_bottom; y++) {
|
||||
auto left = Vector2(viewport_topleft.x, y * Core::Chunk::ChunkSize);
|
||||
auto right = Vector2(viewport_bottomright.x, y * Core::Chunk::ChunkSize);
|
||||
JGL::J2D::DrawLine(Colors::Red, left, right);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LocalWorld::CheckCachedChunkSprites()
|
||||
{
|
||||
void LocalWorld::CheckCachedChunkSprites() {
|
||||
for (auto& [chunk_pos, chunk] : loaded_chunks) {
|
||||
if (IsChunkCellWithinViewport(chunk_pos))
|
||||
{
|
||||
// No rendertarget for this chunk.
|
||||
if (!cached_chunk_sprites.contains(chunk_pos))
|
||||
{
|
||||
chunk->touched = false;
|
||||
auto* target = new JGL::RenderTarget({Core::Chunk::ChunkSize, Core::Chunk::ChunkSize}, {0, 0, 0, 0});
|
||||
RenderChunkTexture(chunk_pos, target, chunk);
|
||||
cached_chunk_sprites.insert({chunk_pos, target});
|
||||
}
|
||||
// rendertarget needs updating.
|
||||
else if (chunk->touched) {
|
||||
// TODO: Modify RenderTarget in place.
|
||||
chunk->touched = false;
|
||||
if (!IsChunkCellWithinViewport(chunk_pos))
|
||||
continue;
|
||||
|
||||
auto* target = cached_chunk_sprites[chunk_pos];
|
||||
RenderChunkTexture(chunk_pos, target, chunk);
|
||||
|
||||
|
||||
//cached_chunk_sprites.erase(chunk_pos);
|
||||
//auto* target = new JGL::RenderTarget({Core::Chunk::ChunkSize, Core::Chunk::ChunkSize}, {0,0,0,0});
|
||||
//RenderChunkTexture(chunk_pos, target, chunk);
|
||||
//cached_chunk_sprites.insert({chunk_pos, target});
|
||||
|
||||
}
|
||||
auto it = cached_chunk_sprites.find(chunk_pos);
|
||||
if (it != cached_chunk_sprites.end()) {
|
||||
if (!chunk->touched)
|
||||
continue;
|
||||
|
||||
chunk->touched = false;
|
||||
RenderChunkTexture(chunk_pos, it->second, chunk);
|
||||
}
|
||||
|
||||
auto* chunk_sprite = new RenderTarget(Vector2i(Chunk::ChunkSize), Colors::Transparent,false,
|
||||
SampleRate::NONE, FilteringMode::MIPMAP_NEAREST);
|
||||
RenderChunkTexture(chunk_pos, chunk_sprite, chunk);
|
||||
cached_chunk_sprites.insert({chunk_pos, chunk_sprite});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +56,7 @@ namespace CaveGame::Client {
|
||||
|
||||
return Color4::Lerp(color_low, color_high, delta);
|
||||
}
|
||||
|
||||
Color4 LocalWorld::GetSkyColorBaseForTimeOfDay(float time) {
|
||||
if (time >= 23*60)
|
||||
return Colors::Black;
|
||||
@@ -142,17 +111,22 @@ namespace CaveGame::Client {
|
||||
//if ()
|
||||
|
||||
}
|
||||
/*
|
||||
void LocalWorld::DrawBeforeProjection(){ }
|
||||
void LocalWorld::DrawDuringProjection() { }
|
||||
void LocalWorld::DrawAfterProjection() { }
|
||||
*/
|
||||
|
||||
void LocalWorld::Draw() {
|
||||
CheckCachedChunkSprites();
|
||||
|
||||
JGL::J2D::Begin();
|
||||
|
||||
|
||||
//glClearColor(Colors::Blues::SkyBlue.RN(), Colors::Blues::SkyBlue.GN(), Colors::Blues::SkyBlue.BN(),1.f);
|
||||
|
||||
DrawSky();
|
||||
|
||||
J2D::Begin();
|
||||
|
||||
// TODO: The following Translation, Rotation, Scale transformation code is duplicated between here and CaveGameWindow.cpp:Draw.
|
||||
// THIS BELONGS IN THE CAMERA ITSELF. You "Render" the camera first, And everything else after such that the camera can observe it. - Redacted.
|
||||
|
||||
// Shift the origin to the center of the screen.
|
||||
glTranslatef(camera.HalfSizeOffset().x, camera.HalfSizeOffset().y, 0);
|
||||
|
||||
@@ -162,35 +136,39 @@ namespace CaveGame::Client {
|
||||
glRotatef(camera.Rotation(), 0, 0, 1);
|
||||
glScalef(camera.Zoom(), camera.Zoom(), 1);
|
||||
|
||||
glTranslatef(-camera.Position().x, -camera.Position().y, 0);
|
||||
|
||||
glTranslatef((int64_t) -camera.Position().x, (int64_t) -camera.Position().y, 0);
|
||||
|
||||
// Draw the cached RenderTargets for our chunks.
|
||||
for (const auto& [chunk_pos, chunk] : loaded_chunks)
|
||||
{
|
||||
if (IsChunkCellWithinViewport(chunk_pos))
|
||||
{
|
||||
RenderChunk(chunk_pos);
|
||||
|
||||
//JGL::J2D::DrawString(Colors::Black, std::format("{}, {}", chunk_pos.x, chunk_pos.y), chunk.GetChunkRealCoordinates().x, chunk.GetChunkRealCoordinates().y,
|
||||
// 1, 8, font);
|
||||
// Debug Grid
|
||||
if (chunk_debug.IsEnabled())
|
||||
{
|
||||
chunk_debug.DrawStats(chunk, 0.99f / camera.Zoom());
|
||||
chunk_debug.DrawCellCoords(chunk_pos, 0.99f / camera.Zoom());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Debug Grid
|
||||
//DrawChunkGrid();
|
||||
|
||||
|
||||
|
||||
// Banana for scale.
|
||||
JGL::J2D::FillRect(Colors::Blue, {0,0}, {64, 64});
|
||||
JGL::J2D::FillRect(Colors::Green, {0,0}, {32, 32});
|
||||
JGL::J2D::FillRect(Colors::Yellow, {0,0}, {16, 16});
|
||||
JGL::J2D::FillRect(Colors::Red, {0,0}, {8, 8});
|
||||
JGL::J2D::FillRect(Colors::White, {0,0}, {1, 1});
|
||||
if (chunk_debug.IsEnabled())
|
||||
chunk_debug.DrawGrid(camera.ScaledViewport());
|
||||
|
||||
for (auto* entity : entities) {
|
||||
entity->Draw();
|
||||
}
|
||||
|
||||
for (auto& p : particles)
|
||||
{
|
||||
if (p.life > 0)
|
||||
p.Draw();
|
||||
}
|
||||
|
||||
|
||||
JGL::J2D::End();
|
||||
|
||||
@@ -202,9 +180,9 @@ namespace CaveGame::Client {
|
||||
return cached_chunk_sprites.size();
|
||||
}
|
||||
|
||||
bool LocalWorld::IsChunkCellWithinViewport(const Vector2 &coords, int extraChunkRadius) const {
|
||||
bool LocalWorld::IsChunkCellWithinViewport(const Vector2i &coords, int extraChunkRadius) const {
|
||||
int extraChunkComputedPixelsRequired = extraChunkRadius*Core::Chunk::ChunkSize;
|
||||
AABB2D chunk_bounding_box = AABB2D(coords*Core::Chunk::ChunkSize, coords*Core::Chunk::ChunkSize+Vector2(Core::Chunk::ChunkSize));
|
||||
AABB2D chunk_bounding_box = AABB2D(Vector2(coords)*Core::Chunk::ChunkSize, Vector2(coords)*Core::Chunk::ChunkSize+Vector2(Core::Chunk::ChunkSize));
|
||||
return Core::Solver::AABB2Dvs(
|
||||
camera.ScaledViewportOversized(extraChunkComputedPixelsRequired), chunk_bounding_box);
|
||||
}
|
||||
@@ -212,8 +190,142 @@ namespace CaveGame::Client {
|
||||
RNG rng;
|
||||
|
||||
|
||||
void LocalWorld::RenderTile(const TileID& t_id, int wx, int wy, int tx, int ty)
|
||||
{
|
||||
|
||||
void LocalWorld::RenderChunkTexture(const Vector2 &coords, JGL::RenderTarget* destination, Core::Chunk* chunk)
|
||||
using namespace CaveGame::Core;
|
||||
|
||||
static Tile stone = Tiles()["stone"];
|
||||
|
||||
static TileID air = Tiles()["air"].numeric_id;
|
||||
static TileID void_tile = Tiles()["void"].numeric_id;
|
||||
static TileID cobblestone = Tiles()["cobblestone"].numeric_id;
|
||||
static TileID stone_id = stone.numeric_id;
|
||||
static TileID oak_plank = Tiles()["oak-plank"].numeric_id;
|
||||
static TileID stone_brick = Tiles()["stone-brick"].numeric_id;
|
||||
|
||||
|
||||
|
||||
// TODO: Migrate custom tile render code to an override Draw method in tile classes?
|
||||
// TODO: See class Tile::Draw to see why this is halted.
|
||||
|
||||
if (t_id == air || t_id == void_tile)
|
||||
return;
|
||||
|
||||
|
||||
Color4 t_color;
|
||||
|
||||
|
||||
const Core::Tile& t_data = Tiles()[t_id];
|
||||
|
||||
if (t_id == cobblestone) {
|
||||
float val = generator.Perlin(wx, wy, 4, 4, 0.25f, 1.5f);
|
||||
|
||||
|
||||
unsigned int rand = generator.ColorMap(stone.pallet->size(), wx, wy);
|
||||
t_color = t_data.pallet->operator[](rand);
|
||||
|
||||
if (val > 0.40f || val < -0.40f) {
|
||||
t_color.r += 32;
|
||||
t_color.g += 32;
|
||||
t_color.b += 32;
|
||||
} else if (val > 0.15f || val < -0.10f)
|
||||
{ } else {
|
||||
t_color.r -= 64;
|
||||
t_color.g -= 64;
|
||||
t_color.b -= 64;
|
||||
}
|
||||
|
||||
|
||||
//Core::Tiles::Cobblestone.color_pallet
|
||||
} else if (t_id == oak_plank) {
|
||||
|
||||
// TODO: Make each plank have a slightly different base color.
|
||||
|
||||
int plank_height = 4;
|
||||
|
||||
int plank_row = ((int) Math::Floor(wy / plank_height));
|
||||
|
||||
int plank_row_idx = plank_row % 5;
|
||||
|
||||
int plank_length_modulus = 12;
|
||||
|
||||
uint8_t base_r = 212;// - shift;
|
||||
uint8_t base_g = 184;// - shift;
|
||||
uint8_t base_b = 125;// - shift;
|
||||
|
||||
|
||||
if (plank_row_idx == 0) { plank_length_modulus = 14; }
|
||||
if (plank_row_idx == 1) { plank_length_modulus = 19; }
|
||||
if (plank_row_idx == 2) { plank_length_modulus = 21; }
|
||||
if (plank_row_idx == 3) { plank_length_modulus = 17; }
|
||||
if (plank_row_idx == 4) { plank_length_modulus = 27; }
|
||||
|
||||
int plank_col = (int) Math::Floor(wx / plank_length_modulus);
|
||||
|
||||
uint8_t plank_base_rng = generator.ColorMap(20, plank_col, plank_row);
|
||||
uint8_t rng = generator.ColorMap(25, wx, wy);
|
||||
|
||||
base_r -= plank_base_rng + rng;
|
||||
base_g -= plank_base_rng + rng;
|
||||
base_b -= plank_base_rng + rng;
|
||||
|
||||
|
||||
if (Math::Abs(wy % plank_height) == (plank_height-1) || Math::Abs((wx + (plank_row * plank_height)) % plank_length_modulus) == 1) {
|
||||
base_r += 30;
|
||||
base_g += 25;
|
||||
base_b += 20;
|
||||
}
|
||||
|
||||
if (wy % plank_height == 0 || (wx + (plank_row * plank_height)) % plank_length_modulus == 0) {
|
||||
base_r -= 60;
|
||||
base_g -= 45;
|
||||
base_b -= 30;
|
||||
}
|
||||
|
||||
t_color = {base_r, base_g, base_b};
|
||||
} else if (t_id == Tiles()["stone-brick"].numeric_id) {
|
||||
|
||||
|
||||
uint8_t shift = generator.ColorMap(30, wx, wy);
|
||||
uint8_t base_r = 130 - shift;
|
||||
uint8_t base_g = 125 - shift;
|
||||
uint8_t base_b = 120 - shift;
|
||||
|
||||
int brick_height = 5;
|
||||
int brick_length = 9;
|
||||
int brick_row = ((int)Math::Floor(wy / brick_height));
|
||||
|
||||
int brick_offset = 0;
|
||||
|
||||
if (brick_row % 2 == 0)
|
||||
brick_offset = 3;
|
||||
|
||||
|
||||
if (Math::Abs(wy) % brick_height == (brick_height-1) || (Math::Abs(wx) + brick_offset) % brick_length == 1)
|
||||
{
|
||||
base_r += 35;
|
||||
base_g += 35;
|
||||
base_b += 30;
|
||||
}
|
||||
|
||||
if (wy % brick_height == 0 || (wx + brick_offset) % brick_length == 0) {
|
||||
base_r -= 55;
|
||||
base_g -= 60;
|
||||
base_b -= 55;
|
||||
}
|
||||
t_color = {base_r, base_g, base_b};
|
||||
} else if (t_data.pallet.has_value()) {
|
||||
unsigned int rand = generator.ColorMap(t_data.pallet->size(), wx, wy);
|
||||
t_color = t_data.pallet.value()[rand];
|
||||
} else {
|
||||
t_color = t_data.color;
|
||||
}
|
||||
|
||||
JGL::J2D::DrawPoint(t_color, tx, ty);
|
||||
}
|
||||
|
||||
void LocalWorld::RenderChunkTexture(const Vector2i &coords, JGL::RenderTarget* destination, Core::Chunk* chunk)
|
||||
{
|
||||
|
||||
#define DEBUG_TILE_UPDATES
|
||||
@@ -221,16 +333,9 @@ namespace CaveGame::Client {
|
||||
using CaveGame::Core::TileID;
|
||||
|
||||
TileID t_id;
|
||||
Core::Tile* t_data;
|
||||
Color4 t_color;
|
||||
Core::Tile t_data;
|
||||
|
||||
|
||||
//std::vector<Vector2> stone_coords;
|
||||
//std::vector<Vector2> dirt_coords;
|
||||
//std::vector<Vector2> grass_coords;
|
||||
//std::vector<Vector2> clay_coords;
|
||||
//std::vector<Vector2> mud_coords;
|
||||
JGL::J2D::Begin(destination, true);
|
||||
JGL::J2D::Begin(destination, nullptr, true);
|
||||
for (int x = 0; x < Core::Chunk::ChunkSize; x++)
|
||||
{
|
||||
for (int y = 0; y < Core::Chunk::ChunkSize; y++)
|
||||
@@ -239,26 +344,25 @@ namespace CaveGame::Client {
|
||||
|
||||
Vector2 relative_tile_coords = Vector2(x, y);
|
||||
const Vector2& tile_coords = relative_tile_coords;
|
||||
int wx = (x*Core::Chunk::ChunkSize) + x;
|
||||
int wy = (y*Core::Chunk::ChunkSize) + y;
|
||||
int wx = (coords.x*Core::Chunk::ChunkSize) + x;
|
||||
int wy = (coords.y*Core::Chunk::ChunkSize) + y;
|
||||
|
||||
//if (t_id == TileID::AIR || t_id == TileID::VOID) // Air
|
||||
//continue;
|
||||
|
||||
RenderTile(t_id, wx, wy, x, y);
|
||||
|
||||
if (show_tile_activity)
|
||||
{
|
||||
if (chunk->GetTileUpdateFlag(x, y))
|
||||
JGL::J2D::DrawPoint(Color4(255, 0, 0, 64), tile_coords);
|
||||
|
||||
if (chunk->GetTileUpdateBufferFlag(x, y))
|
||||
JGL::J2D::DrawPoint(Color4(255, 0, 0, 64), tile_coords);
|
||||
}
|
||||
|
||||
|
||||
#ifdef DEBUG_TILE_UPDATES
|
||||
if (chunk->GetTileUpdateFlag(x, y))
|
||||
JGL::J2D::DrawPoint(Color4(255, 0, 0, 128), tile_coords);
|
||||
|
||||
if (chunk->GetTileUpdateBufferFlag(x, y))
|
||||
JGL::J2D::DrawPoint(Color4(255, 0, 0, 128), tile_coords);
|
||||
#endif
|
||||
|
||||
|
||||
if (t_id == TileID::AIR || t_id == TileID::VOID) // Air
|
||||
continue;
|
||||
|
||||
|
||||
|
||||
|
||||
t_data = Core::GetByNumeric(t_id);
|
||||
/*t_data = Core::GetByNumeric(t_id);
|
||||
|
||||
if (t_id == TileID::COBBLESTONE) {
|
||||
float val = generator.Perlin(wx, wy, 12, 12, 12, 2);
|
||||
@@ -272,6 +376,13 @@ namespace CaveGame::Client {
|
||||
else
|
||||
t_color = Core::Tiles::Cobblestone.color_pallet[3];
|
||||
//Core::Tiles::Cobblestone.color_pallet
|
||||
} else if (t_id == TileID::OAK_PLANK) {
|
||||
|
||||
if (Math::Mod(y, 3) == 0 || Math::Mod(x, 9) == 0) {
|
||||
t_color = Colors::Browns::Brown;
|
||||
} else
|
||||
t_color = Colors::Browns::BurlyWood;
|
||||
|
||||
} else if (t_data->has_color_pallet) {
|
||||
uint rand = generator.ColorMap(t_data->color_pallet.size(), x, y);
|
||||
t_color = t_data->color_pallet[rand];
|
||||
@@ -279,21 +390,21 @@ namespace CaveGame::Client {
|
||||
t_color = t_data->base_color;
|
||||
}
|
||||
|
||||
JGL::J2D::DrawPoint(t_color, tile_coords);
|
||||
JGL::J2D::DrawPoint(t_color, tile_coords);*/
|
||||
|
||||
}
|
||||
}
|
||||
JGL::J2D::End();
|
||||
}
|
||||
|
||||
Vector2 GetChunkRealCoordinates(const Vector2& cell) {
|
||||
Vector2i GetChunkRealCoordinates(const Vector2i& cell) {
|
||||
return cell * Core::Chunk::ChunkSize;
|
||||
}
|
||||
|
||||
void LocalWorld::RenderChunk(const Vector2& coords) {
|
||||
void LocalWorld::RenderChunk(const Vector2i& coords) {
|
||||
auto it = cached_chunk_sprites.find(coords);
|
||||
if (it != cached_chunk_sprites.end())
|
||||
JGL::J2D::DrawRenderTarget(it->second, GetChunkRealCoordinates(coords));
|
||||
JGL::J2D::DrawRenderTarget(it->second, Vector2(GetChunkRealCoordinates(coords)));
|
||||
}
|
||||
|
||||
void LocalWorld::LookForChunksNeedUnloading() {
|
||||
@@ -332,7 +443,7 @@ namespace CaveGame::Client {
|
||||
{
|
||||
for (int y = lower_bound_v; y <= upper_bound_v; y++)
|
||||
{
|
||||
Vector2 cell(x, y);
|
||||
Vector2i cell(x, y);
|
||||
if (!HasChunkAtCell(cell) && !AwaitingChunkAtCell(cell))
|
||||
RequestChunk(cell);
|
||||
}
|
||||
@@ -344,6 +455,24 @@ namespace CaveGame::Client {
|
||||
|
||||
camera.Update(elapsed);
|
||||
|
||||
|
||||
for (auto& particle : particles)
|
||||
{
|
||||
if (particle.life > 0)
|
||||
particle.Update(elapsed);
|
||||
}
|
||||
|
||||
std::erase_if(particles, [&](Particle& p) {return p.life <= 0; });
|
||||
|
||||
/*std::vector<Particle>::iterator it;
|
||||
for (it = particles.begin(); it != particles.end();)
|
||||
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
if (it->life <= 0)
|
||||
it = particles.erase(it);
|
||||
//else
|
||||
//it->Update(elapsed);
|
||||
}*/
|
||||
|
||||
check_chunks_timer += elapsed;
|
||||
|
||||
// TODO: Move this block to World class
|
||||
@@ -382,6 +511,25 @@ namespace CaveGame::Client {
|
||||
chunks_in_waiting.clear();
|
||||
}
|
||||
|
||||
void LocalWorld::DebugChunks(bool enabled) {
|
||||
chunk_debug.Enable(enabled);
|
||||
}
|
||||
|
||||
void LocalWorld::SaveAndExit() {
|
||||
World::SaveAndExit();
|
||||
|
||||
}
|
||||
|
||||
void LocalWorld::SetShowTileActivity(bool enabled) {
|
||||
show_tile_activity = enabled;
|
||||
}
|
||||
|
||||
bool LocalWorld::IsShowTileActivityEnabled() const { return show_tile_activity;}
|
||||
|
||||
void LocalWorld::Emit(const Particle &p) {
|
||||
particles.push_back(p);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@@ -2,6 +2,9 @@
|
||||
#include <JUI/Widgets/TextRect.hpp>
|
||||
#include "JUI/Widgets/ListLayout.hpp"
|
||||
#include "JUI/Widgets/Button.hpp"
|
||||
#include "JUI/Widgets/Image.hpp"
|
||||
#include "Client/AssetService.hpp"
|
||||
#include <Client/SettingsMenu.hpp>
|
||||
|
||||
CaveGame::Client::MainMenu::MainMenu() : Scene()
|
||||
{
|
||||
@@ -29,12 +32,12 @@ void CaveGame::Client::MainMenu::Draw() {
|
||||
JGL::J2D::DrawSprite(*bg, {-(mpos.x/2.f),-mpos.y*2.f}, 0.f, {0,0}, {aspect,aspect});
|
||||
JGL::J2D::End();
|
||||
scene->Draw();
|
||||
|
||||
}
|
||||
|
||||
void CaveGame::Client::MainMenu::Load() {
|
||||
|
||||
bg = new JGL::Texture("assets/textures/bg.png");
|
||||
bg = AssetService::Get()->GetTexture("bg");
|
||||
|
||||
|
||||
Scene::Load();
|
||||
}
|
||||
@@ -52,17 +55,17 @@ T* create_widget(JUI::Widget* parent)
|
||||
JUI::TextButton* CaveGame::Client::MainMenu::create_mainmenu_btn(const std::string& content, JUI::Widget* parent)
|
||||
{
|
||||
auto* btn = create_widget<JUI::TextButton>(parent);
|
||||
btn->SetFont(JGL::Fonts::Jupiteroid);
|
||||
btn->SetTextSize(24);
|
||||
btn->SetTextColor(Colors::White);
|
||||
btn->BaseBGColor(Colors::LightGray);
|
||||
btn->Font(JGL::Fonts::Jupiteroid);
|
||||
btn->TextSize(24);
|
||||
btn->TextColor(Colors::White);
|
||||
btn->BaseBGColor(Colors::DarkGray);
|
||||
btn->HoveredBGColor(Colors::Blues::LightSteelBlue);
|
||||
btn->BGColor(btn->BaseBGColor());
|
||||
|
||||
btn->Size({0, 40, 1.f, 0});
|
||||
btn->Enable();
|
||||
btn->Center();
|
||||
btn->SetContent(content);
|
||||
btn->Content(content);
|
||||
return btn;
|
||||
}
|
||||
|
||||
@@ -71,55 +74,61 @@ void CaveGame::Client::MainMenu::BuildWidgets() {
|
||||
using namespace JUI;
|
||||
|
||||
title = new TextRect(scene);
|
||||
title->SetFont(JGL::Fonts::Jupiteroid);
|
||||
title->SetContent("CaveGame");
|
||||
title->SetTextColor(Colors::White);
|
||||
title->SetTextSize(64);
|
||||
//title->SetFont(JGL::Fonts::Jupiteroid);
|
||||
//title->SetContent("CaveGame");
|
||||
//title->SetTextColor(Colors::White);
|
||||
//title->SetTextSize(64);
|
||||
title->Position({0, 0, 0.5f, 0.2f});
|
||||
title->Size({0, 0, 0.3f, 0.15f});
|
||||
title->Size({0, 0, 0.5f, 0.25f});
|
||||
title->AnchorPoint({0.5f, 0.5f});
|
||||
title->Center();
|
||||
//title->Center();
|
||||
title->BGColor({0,0,0,0});
|
||||
title->SetBorderStyling(Colors::Cyans::Cyan, 1);
|
||||
title->BorderWidth(0);
|
||||
|
||||
auto* content = new JUI::Image(title);
|
||||
// TODO: Unsafe!
|
||||
content->Content(AssetService::Get()->GetTexture("title").get());
|
||||
content->FitImageToParent(true);
|
||||
|
||||
button_group = new Rect(scene);
|
||||
button_group->Size({250, 400, 0, 0});
|
||||
button_group->Position({0, 0, 0.5f, 0.3f});
|
||||
button_group->Position({0, 0, 0.5f, 0.4f});
|
||||
button_group->AnchorPoint({0.5f, 0.f});
|
||||
button_group->BGColor({0,0,0,0});
|
||||
button_group->SetBorderStyling(Colors::Cyans::Cyan, 1);
|
||||
button_group->BorderWidth(0);
|
||||
|
||||
auto *button_list = new JUI::VerticalListLayout(button_group);
|
||||
button_list->PaddingBottom(5_px);
|
||||
|
||||
auto *new_world_btn = create_mainmenu_btn("New World", button_list);
|
||||
|
||||
new_world_btn->OnReleaseEvent += [this](Vector2 pos, MouseButton btn, bool b)
|
||||
new_world_btn->OnReleaseEvent += [this](Vector2 pos, JUI::MouseButton btn, bool b)
|
||||
{
|
||||
RequestWorld.Invoke({.NewWorld = true});
|
||||
};
|
||||
|
||||
auto *load_world_btn = create_mainmenu_btn("Load World", button_list);
|
||||
|
||||
load_world_btn->OnReleaseEvent += [this](Vector2 pos, MouseButton btn, bool b)
|
||||
load_world_btn->OnReleaseEvent += [this](Vector2 pos, JUI::MouseButton btn, bool b)
|
||||
{
|
||||
RequestWorld.Invoke({.NewWorld = false});
|
||||
};
|
||||
|
||||
|
||||
//auto *sp_btn = create_mainmenu_btn("Singleplayer", button_list);
|
||||
auto *settings_btn = create_mainmenu_btn("Settings", button_list);
|
||||
|
||||
//sp_btn->OnReleaseEvent += [this](Vector2 pos, MouseButton btn, bool b) {
|
||||
// if (b)
|
||||
// RequestWorld.Invoke({});
|
||||
//};
|
||||
settings_btn->OnReleaseEvent += [this] (Vector2 pos, JUI::MouseButton btn, bool b) mutable {
|
||||
if (b)
|
||||
RequestToggleSettings.Invoke();
|
||||
};
|
||||
|
||||
auto *mp_btn = create_mainmenu_btn("Multiplayer", button_list);
|
||||
auto *credits_btn = create_mainmenu_btn("Credits", button_list);
|
||||
|
||||
credits_btn->OnReleaseEvent += [this](Vector2 pos, MouseButton btn, bool b) {
|
||||
credits_btn->OnReleaseEvent += [this](Vector2 pos, JUI::MouseButton btn, bool b) {
|
||||
if (b)
|
||||
RequestShowCredits.Invoke();
|
||||
RequestToggleCredits.Invoke();
|
||||
};
|
||||
|
||||
// TODO: Replace with IconButton with steam logo
|
||||
@@ -127,7 +136,7 @@ void CaveGame::Client::MainMenu::BuildWidgets() {
|
||||
auto *quit_btn = create_mainmenu_btn("Quit", button_list);
|
||||
|
||||
|
||||
quit_btn->OnReleaseEvent += [this](Vector2 pos, MouseButton btn, bool b)
|
||||
quit_btn->OnReleaseEvent += [this](Vector2 pos, JUI::MouseButton btn, bool b)
|
||||
{
|
||||
if (b)
|
||||
RequestQuit.Invoke();
|
||||
|
98
Client/src/Client/PauseMenu.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include <Client/PauseMenu.hpp>
|
||||
|
||||
namespace CaveGame::Client
|
||||
{
|
||||
|
||||
PauseMenuWidget::PauseMenuWidget() {
|
||||
|
||||
this->Size({100_percent, 100_percent}); // Full Screen.
|
||||
this->BGColor({32, 32, 32, 128}); // Transparent Gray.
|
||||
this->ZIndex(1);
|
||||
|
||||
button_box = new JUI::Rect(this);
|
||||
button_box->Size({150_px, 170_px});
|
||||
button_box->AnchorPoint({0.f, 1.f});
|
||||
button_box->Position({50_px, 100_percent - 50_px});
|
||||
button_box->BGColor(Colors::Transparent);
|
||||
button_box->BorderWidth(0);
|
||||
|
||||
button_list = new JUI::VerticalListLayout(button_box);
|
||||
button_list->PaddingBottom(5_px);
|
||||
|
||||
|
||||
resume_btn = new JUI::TextButton(button_list);
|
||||
resume_btn->Size({100_percent, 50_px});
|
||||
resume_btn->CornerRounding(7);
|
||||
resume_btn->Center();
|
||||
resume_btn->TextSize(24);
|
||||
resume_btn->TextColor(Colors::White);
|
||||
resume_btn->BaseBGColor(Colors::DarkGray);
|
||||
resume_btn->HoveredBGColor(Colors::Blues::LightSteelBlue);
|
||||
resume_btn->BGColor(resume_btn->BaseBGColor());
|
||||
resume_btn->Content("Resume");
|
||||
|
||||
resume_btn->OnClickEvent += [this] (Vector2 pos, JUI::MouseButton state) {
|
||||
OnResumeButtonPressed.Invoke();
|
||||
};
|
||||
|
||||
settings_btn = new JUI::TextButton(button_list);
|
||||
settings_btn->Size({100_percent, 50_px});
|
||||
settings_btn->CornerRounding(7);
|
||||
settings_btn->Center();
|
||||
settings_btn->TextSize(24);
|
||||
settings_btn->TextColor(Colors::White);
|
||||
settings_btn->BaseBGColor(Colors::DarkGray);
|
||||
settings_btn->HoveredBGColor(Colors::Blues::LightSteelBlue);
|
||||
settings_btn->BGColor(settings_btn->BaseBGColor());
|
||||
settings_btn->Content("Settings");
|
||||
|
||||
settings_btn->OnClickEvent += [this] (Vector2 pos, JUI::MouseButton state) {
|
||||
OnSettingsButtonPressed.Invoke();
|
||||
};
|
||||
|
||||
quit_btn = new JUI::TextButton(button_list);
|
||||
quit_btn->Size({100_percent, 50_px});
|
||||
quit_btn->CornerRounding(7);
|
||||
quit_btn->Center();
|
||||
quit_btn->TextSize(24);
|
||||
quit_btn->TextColor(Colors::White);
|
||||
quit_btn->BaseBGColor(Colors::DarkGray);
|
||||
quit_btn->HoveredBGColor(Colors::Blues::LightSteelBlue);
|
||||
quit_btn->BGColor(quit_btn->BaseBGColor());
|
||||
quit_btn->Content("Save & Exit World");
|
||||
|
||||
quit_btn->OnClickEvent += [this] (Vector2 pos, JUI::MouseButton state) {
|
||||
OnQuitButtonPressed.Invoke();
|
||||
};
|
||||
|
||||
// Default-initialize as closed.
|
||||
this->Close();
|
||||
}
|
||||
|
||||
PauseMenuWidget::PauseMenuWidget(Widget *parent): PauseMenuWidget() {
|
||||
this->Parent(parent);
|
||||
}
|
||||
|
||||
bool PauseMenuWidget::ObserveMouseInput(JUI::MouseButton btn, bool pressed) {
|
||||
if (is_open)
|
||||
return Rect::ObserveMouseInput(btn, pressed);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PauseMenuWidget::Toggle() {
|
||||
SetOpen(!GetOpen());
|
||||
}
|
||||
|
||||
void PauseMenuWidget::SetOpen(bool value) {
|
||||
is_open = value;
|
||||
this->Visible(is_open);
|
||||
}
|
||||
|
||||
bool PauseMenuWidget::GetOpen() const { return is_open; }
|
||||
|
||||
void PauseMenuWidget::Open() { SetOpen(true); }
|
||||
|
||||
void PauseMenuWidget::Close() { SetOpen(false); }
|
||||
}
|
||||
|
89
Client/src/Client/Player.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#include <Core/Player.hpp>
|
||||
#include <Client/AssetService.hpp>
|
||||
#include <Core/Entity.hpp>
|
||||
#include <JGL/JGL.h>
|
||||
#include <ReWindow/InputService.h>
|
||||
#include <jstick.hpp>
|
||||
|
||||
|
||||
// TODO: Move this shit to Client/Player.cpp
|
||||
void CaveGame::Core::Player::Draw() {
|
||||
|
||||
Direction dir = Direction::None;
|
||||
|
||||
if (!facing_left)
|
||||
dir = Direction::Horizontal;
|
||||
|
||||
|
||||
AABB2D quad = Frame_Idle;
|
||||
|
||||
// TODO: Work on conditions to make the player sprite not rapidly change to 'falling' when dropping 1-tall steps.
|
||||
if (!on_ground && ( airtime > 0.1f || Math::Abs(velocity.y) > 10.f ))
|
||||
{
|
||||
if (velocity.y < 0)
|
||||
quad = Frame_Ascend;
|
||||
else
|
||||
quad = Frame_Descend;
|
||||
} else if (walking) { // Math::Abs(velocity.x) > 2) {
|
||||
//float clamped_velocity = (Math::Clamp(Math::Abs(velocity.x) - 5.f, 0.f, 50.f) / 20.f);
|
||||
float walk_cycle = Math::Mod(anim_timer*2.5f, 1);
|
||||
|
||||
if (walk_cycle > 2.f/3.f) {
|
||||
quad = Frame_Walk3;
|
||||
} else if (walk_cycle > 1.f / 3.f) {
|
||||
quad = Frame_Walk2;
|
||||
} else {
|
||||
quad = Frame_Walk1;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Utilize land quad when falling enough to take fall damage, player can't move for a half second.
|
||||
|
||||
auto myAsset = Client::AssetService::Get()->GetTexture("player");
|
||||
JGL::J2D::DrawPartialSprite(myAsset.get(), RenderTopLeft(), quad.minPoint, {16, 24}, 0, {0,0}, {1,1}, Colors::White, dir);
|
||||
JGL::J2D::OutlineRect(Colors::Red, RenderTopLeft(), texture_center * 2.f);
|
||||
JGL::J2D::OutlineRect(Colors::Blue, TopLeft(), bounding_box);
|
||||
J2D::DrawString(Colors::White, std::format("vel: {},{}", Math::Round(velocity.x, 2), Math::Round(velocity.y, 2)), position.x, position.y-8, 8, 0.5f);
|
||||
J2D::DrawString(Colors::White, std::format("ct: {} cd: {}", coll_tests, coll_hits), position.x, position.y-16, 8, 0.5f);
|
||||
}
|
||||
|
||||
void CaveGame::Core::Player::Update(float elapsed) {
|
||||
Humanoid::Update(elapsed);
|
||||
|
||||
anim_timer += elapsed;
|
||||
walking = false;
|
||||
|
||||
|
||||
Vector2 dpad = jstick::GetDPadAxis();
|
||||
|
||||
if (noclip) {
|
||||
if (InputService::IsKeyDown(Keys::A) || dpad.x <= -0.5f)
|
||||
Accelerate({-500*elapsed, 0});
|
||||
|
||||
if (InputService::IsKeyDown(Keys::D) || dpad.x >= +0.5f)
|
||||
Accelerate({500*elapsed, 0});
|
||||
|
||||
if (InputService::IsKeyDown(Keys::W) || dpad.y <= -0.5f)
|
||||
Accelerate({0, -500*elapsed});
|
||||
|
||||
if (InputService::IsKeyDown(Keys::S) || dpad.y >= 0.5f)
|
||||
Accelerate({0, 500*elapsed});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (InputService::IsKeyDown(Keys::A) || dpad.x <= -0.5f)
|
||||
WalkLeft(elapsed);
|
||||
|
||||
if (InputService::IsKeyDown(Keys::D) || dpad.x >= +0.5f)
|
||||
WalkRight(elapsed);
|
||||
|
||||
if (InputService::IsKeyDown(Keys::W) || dpad.y <= -0.5f)
|
||||
Jump(elapsed);
|
||||
|
||||
|
||||
|
||||
|
||||
//if (InputService::IsKeyDown(Keys::S))
|
||||
//Accelerate({0, -1});
|
||||
}
|
@@ -6,11 +6,31 @@ namespace CaveGame::Client
|
||||
{
|
||||
void SceneManager::ChangeScene(Scene* new_scene)
|
||||
{
|
||||
if (current_scene != nullptr)
|
||||
current_scene->Unload();
|
||||
next_scene = new_scene;
|
||||
|
||||
current_scene = new_scene;
|
||||
current_scene->Load();
|
||||
//if (current_scene != nullptr)
|
||||
// current_scene->Unload();
|
||||
|
||||
//current_scene = new_scene;
|
||||
//current_scene->Load();
|
||||
}
|
||||
|
||||
void SceneManager::UpdateSceneState(float elapsed)
|
||||
{
|
||||
if (next_scene != nullptr)
|
||||
{
|
||||
if (current_scene != nullptr)
|
||||
{
|
||||
current_scene->Unload();
|
||||
prev_scene = current_scene;
|
||||
}
|
||||
|
||||
current_scene = next_scene;
|
||||
current_scene->Load();
|
||||
|
||||
|
||||
next_scene = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Scene* SceneManager::CurrentScene() { return current_scene; }
|
||||
|
@@ -1,13 +1,10 @@
|
||||
#include <Client/Splash.hpp>
|
||||
#include <JGL/JGL.h>
|
||||
|
||||
#include "Client/AssetService.hpp"
|
||||
CaveGame::Client::Splash::Splash() : Scene()
|
||||
{
|
||||
|
||||
//font = JGL::Font();
|
||||
|
||||
ComputeMatrixGlyphTable();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -43,11 +40,11 @@ void CaveGame::Client::Splash::ComputeMatrixTextureCache()
|
||||
Vector2i column_size = {(int) glyph_measurement.x, (int) glyph_measurement.y * 100};
|
||||
auto* column = new JGL::RenderTarget(column_size, {0, 0, 0, 0}, false);
|
||||
|
||||
JGL::J2D::Begin(column, true);
|
||||
JGL::J2D::Begin(column, nullptr, true);
|
||||
|
||||
for (int col = 0; col < 50; col++) {
|
||||
Color4 text_col = Color4(32, 192, 92, 255 - (col*4));
|
||||
JGL::J2D::DrawString(text_col, std::to_string(rand() % 2), 0, glyph_measurement.y * col, 1, 16);
|
||||
JGL::J2D::DrawString(text_col, std::to_string(rand() % 2), 0, glyph_measurement.y * col, 16);
|
||||
}
|
||||
JGL::J2D::End();
|
||||
|
||||
@@ -72,6 +69,8 @@ void CaveGame::Client::Splash::DrawProgressBar()
|
||||
float progress_bar_length = bar_length * load_percent;
|
||||
|
||||
JGL::J2D::FillRect(Colors::White, bar_pos, {progress_bar_length, bar_height});
|
||||
|
||||
JGL::J2D::DrawString(Colors::Black, AssetService::Get()->LastAsset(), bar_pos.x, bar_pos.y, 1, 16);
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +99,7 @@ void CaveGame::Client::Splash::Draw()
|
||||
|
||||
Vector2 middle = screen_dimensions/2.f;
|
||||
|
||||
// TODO: Implement draw-point offset to maintain sensible aspect ratio on the image.
|
||||
// TODO: Compute screen aspect ratio to derive ideal splash image scale.
|
||||
|
||||
|
||||
JGL::J2D::Begin();
|
||||
@@ -116,35 +115,34 @@ void CaveGame::Client::Splash::Draw()
|
||||
void CaveGame::Client::Splash::Update(float elapsed)
|
||||
{
|
||||
|
||||
if (load_percent < 1)
|
||||
load_percent += elapsed/2.f;
|
||||
else
|
||||
load_percent = 1;
|
||||
AssetService::Get()->LoadFromQueue();
|
||||
|
||||
splash_timer -= elapsed;
|
||||
|
||||
load_percent = (float)AssetService::Get()->TotalLoaded() / (float)AssetService::Get()->TotalQueued();
|
||||
|
||||
}
|
||||
|
||||
void CaveGame::Client::Splash::Load() {
|
||||
column_textures.fill(nullptr);
|
||||
splash = new JGL::Texture("assets/textures/redacted.png");
|
||||
splash = AssetService::Get()->GetTexture("redacted"); //new JGL::Texture("assets/textures/redacted.png");
|
||||
ComputeMatrixTextureCache();
|
||||
|
||||
Scene::Load();
|
||||
|
||||
}
|
||||
|
||||
void CaveGame::Client::Splash::Unload() {
|
||||
for (auto& r : column_textures)
|
||||
delete r;
|
||||
delete splash;
|
||||
|
||||
splash = nullptr;
|
||||
|
||||
column_textures.fill(nullptr);
|
||||
Scene::Unload();
|
||||
}
|
||||
|
||||
bool CaveGame::Client::Splash::SplashComplete() const {
|
||||
return splash_timer < 0;
|
||||
return AssetService::Get()->IsLoadComplete();
|
||||
//return splash_timer < 0;
|
||||
}
|
||||
|
||||
CaveGame::Client::Splash::~Splash() {
|
||||
|
151
Client/src/Client/TileTool.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
#include <Client/TileTool.hpp>
|
||||
#include <Core/Loggers.hpp>
|
||||
|
||||
CaveGame::Client::TileTool::TileTool(JUI::Widget *parent) : JUI::Window(parent)
|
||||
{
|
||||
this->Title(tool_title);
|
||||
this->MinSize({200, 400});
|
||||
this->Size({200, 400, 0, 0});
|
||||
this->Visible(false);
|
||||
|
||||
auto* column_layout = new HorizontalListLayout(this->ViewportInstance());
|
||||
|
||||
auto* col_left = new Rect(column_layout);
|
||||
col_left->BGColor({64, 64, 64, 128});
|
||||
col_left->Size({0, 0, 0.35f, 1.f});
|
||||
|
||||
auto* left_row_layout = new VerticalListLayout(col_left);
|
||||
|
||||
tool_size_label = new TextRect(left_row_layout);
|
||||
tool_size_label->Size({0, row_height, 1, 0});
|
||||
tool_size_label->Content("Size: 8");
|
||||
tool_size_label->BGColor(Colors::Transparent);
|
||||
//tool_size_label->LayoutOrder(1);
|
||||
|
||||
auto* tool_percent_label = new TextRect(left_row_layout);
|
||||
tool_percent_label->Size({0, row_height, 1, 0});
|
||||
tool_percent_label->Content("Percent: 100%");
|
||||
tool_percent_label->BGColor(Colors::Transparent);
|
||||
|
||||
auto* tile_sim_label = new TextRect(left_row_layout);
|
||||
tile_sim_label->Size({0, row_height, 1, 0});
|
||||
tile_sim_label->Content("Pause Tile Sim:");
|
||||
tile_sim_label->BGColor(Colors::Transparent);
|
||||
|
||||
auto* col_right = new Rect(column_layout);
|
||||
col_right->BGColor({128, 128, 128, 128});
|
||||
col_right->Size({0, 0, 0.65f, 1.f});
|
||||
|
||||
auto* right_row_layout = new VerticalListLayout(col_right);
|
||||
|
||||
brush_size_slider = new Slider(right_row_layout);
|
||||
|
||||
brush_size_slider->Size({0, row_height, 1, 0});
|
||||
brush_size_slider->Maximum(1.f);
|
||||
brush_size_slider->Minimum(0.01f);
|
||||
brush_size_slider->Interval(0.001f);
|
||||
brush_size_slider->ValueChanged += [&, this] (float val) mutable
|
||||
{
|
||||
float newval = val * 50;
|
||||
tool_size_label->Content(std::format("Size: {}", Math::Round(newval, 1)));
|
||||
BrushSizeChanged(newval);
|
||||
};
|
||||
brush_size_slider->CurrentValue(0.5f);
|
||||
brush_size_slider->SetClicked(false);
|
||||
|
||||
|
||||
brush_percent_slider = new Slider(right_row_layout);
|
||||
brush_percent_slider->Size({0, row_height, 1, 0});
|
||||
brush_percent_slider->Maximum(1.f);
|
||||
brush_percent_slider->Minimum(0.f);
|
||||
brush_percent_slider->Interval(0.001f);
|
||||
brush_percent_slider->ValueChanged += [&, this, tool_percent_label] (float val) mutable
|
||||
{
|
||||
float newval = val * 100.f;
|
||||
tool_percent_label->Content(std::format("Percent: {}%", Math::Floor(newval)));
|
||||
BrushPercentChanged(newval);
|
||||
};
|
||||
brush_percent_slider->CurrentValue(1.f);
|
||||
brush_percent_slider->SetClicked(false);
|
||||
|
||||
|
||||
auto* tile_sim_checkbox = new Checkbox(right_row_layout);
|
||||
tile_sim_checkbox->Size({row_height, row_height, 0, 0});
|
||||
tile_sim_checkbox->OnClickEvent += [&, this] (Vector2 _, JUI::MouseButton _2) mutable {
|
||||
tile_sim_disabled = !tile_sim_disabled;
|
||||
TileSimulationDisabledChanged.Invoke(tile_sim_disabled);
|
||||
|
||||
// Set the visual state of the "step" buttonns
|
||||
step_btn->SetEnabled(tile_sim_disabled);
|
||||
step2_btn->SetEnabled(tile_sim_disabled);
|
||||
step3_btn->SetEnabled(tile_sim_disabled);
|
||||
};
|
||||
|
||||
|
||||
|
||||
step_btn = new TextButton(left_row_layout);
|
||||
step_btn->Size({100_percent, UDim(row_height, 0)});
|
||||
step_btn->Content("Step");
|
||||
step_btn->BGColor(Colors::LightGray);
|
||||
step_btn->BaseBGColor(Colors::LightGray);
|
||||
step_btn->Disable();
|
||||
step_btn->OnClickEvent += [&, this] (auto a, auto b) mutable {
|
||||
TileSimulationStep.Invoke(1);
|
||||
};
|
||||
|
||||
step2_btn = new TextButton(left_row_layout);
|
||||
step2_btn->Size({100_percent, UDim(row_height, 0)});
|
||||
step2_btn->Content("Step 10");
|
||||
step2_btn->BGColor(Colors::LightGray);
|
||||
step2_btn->BaseBGColor(Colors::LightGray);
|
||||
step2_btn->Disable();
|
||||
step2_btn->OnClickEvent += [&, this] (auto a, auto b) mutable {
|
||||
TileSimulationStep.Invoke(10);
|
||||
};
|
||||
|
||||
step3_btn = new TextButton(left_row_layout);
|
||||
step3_btn->Size({100_percent, UDim(row_height, 0)});
|
||||
step3_btn->Content("Step 100");
|
||||
step3_btn->BGColor(Colors::LightGray);
|
||||
step3_btn->BaseBGColor(Colors::LightGray);
|
||||
step3_btn->Disable();
|
||||
step3_btn->OnClickEvent += [&, this] (auto a, auto b) mutable {
|
||||
TileSimulationStep.Invoke(100);
|
||||
};
|
||||
|
||||
BrushSizeChanged += [this] (float value) mutable {
|
||||
brush_radius = value;
|
||||
};
|
||||
|
||||
BrushPercentChanged += [this] (float value) mutable {
|
||||
brush_density = value;
|
||||
};
|
||||
|
||||
|
||||
Enable(WorldEditorEnabledByDefault);
|
||||
}
|
||||
|
||||
void CaveGame::Client::TileTool::BrushRadius(float size) {
|
||||
brush_size_slider->CurrentValue(size/50.f);
|
||||
tool_size_label->Content(std::format("Size: {}", Math::Round(size, 1)));
|
||||
brush_radius = size;
|
||||
}
|
||||
|
||||
|
||||
void CaveGame::Client::TileTool::Enable(bool value) {
|
||||
this->enabled = value;
|
||||
|
||||
CaveGame::Logs::Info("Tile Editor Tool Toggled");
|
||||
|
||||
this->Visible(enabled);
|
||||
}
|
||||
|
||||
float CaveGame::Client::TileTool::BrushRadius() { return brush_radius;}
|
||||
|
||||
void CaveGame::Client::TileTool::BrushDensity(float percent) {
|
||||
brush_percent_slider->CurrentValue(percent / 100.f);
|
||||
tool_size_label->Content(std::format("Density: {}", Math::Round(percent, 1)));
|
||||
brush_density = percent;
|
||||
}
|
||||
|
||||
float CaveGame::Client::TileTool::BrushDensity() const { return brush_density;}
|
@@ -1,2 +0,0 @@
|
||||
#include <Client/WindowWidgetManager.hpp>
|
||||
|
@@ -1 +0,0 @@
|
||||
#include "Client/temp.hpp"
|
@@ -8,6 +8,8 @@ file(GLOB_RECURSE CaveClientApp_SRC "src/*.cpp")
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/assets/"
|
||||
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/assets)
|
||||
|
||||
|
||||
|
||||
if (CLIENT_BUILD_WITH_STEAM)
|
||||
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/steam_appid.txt
|
||||
@@ -25,7 +27,7 @@ target_include_directories(CaveClientApp PUBLIC
|
||||
|
||||
target_include_directories(CaveClientApp PUBLIC "include")
|
||||
|
||||
target_link_libraries(CaveClientApp PUBLIC CaveClient ReWindowLibrary J3ML JGL )
|
||||
target_link_libraries(CaveClientApp PUBLIC CaveClient ReWindow J3ML JGL )
|
||||
|
||||
target_compile_definitions(CaveClientApp PUBLIC CAVE_CLIENT)
|
||||
|
||||
@@ -34,10 +36,20 @@ if (CLIENT_BUILD_WITH_STEAM)
|
||||
target_compile_definitions(CaveClientApp PUBLIC STEAM_BUILD)
|
||||
|
||||
add_library(SteamworksSDK SHARED IMPORTED)
|
||||
set_target_properties(SteamworksSDK PROPERTIES IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/assets/steamworks_sdk/linux64/libsteam_api.so")
|
||||
|
||||
# TODO: Can we legally ship the steam SDK with source code?
|
||||
#set_target_properties(SteamworksSDK PROPERTIES IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/assets/steamworks_sdk/linux64/libsteam_api.so")
|
||||
|
||||
|
||||
target_include_directories(CaveClientApp PUBLIC "../Steam/")
|
||||
target_link_libraries(CaveClientApp PUBLIC SteamworksSDK)
|
||||
endif()
|
||||
|
||||
|
||||
add_custom_command(TARGET CaveClientApp POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${PROJECT_BINARY_DIR}/lib/" ${CMAKE_CURRENT_BINARY_DIR}/lib
|
||||
COMMENT "Copied libraries to build directory")
|
||||
|
||||
#file(COPY "${PROJECT_BINARY_DIR}/lib/"
|
||||
#DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/lib)
|
||||
|
||||
|
9
ClientApp/assets/data/generator.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"veins": {
|
||||
"clay": {
|
||||
"hi-pass-scale-x": 200,
|
||||
"hi-pass-scale-y": 205,
|
||||
"hi-pass-offset": 0
|
||||
}
|
||||
}
|
||||
}
|
100
ClientApp/assets/data/items.json
Normal file
@@ -0,0 +1,100 @@
|
||||
[
|
||||
{
|
||||
"mnemonic-id": "gel",
|
||||
"display-name": "Gel",
|
||||
"tooltip": "",
|
||||
"stack": 9999,
|
||||
"value": 1,
|
||||
"rarity": 0,
|
||||
"sprite": "gel.png",
|
||||
"usable" : true
|
||||
},
|
||||
{
|
||||
"mnemonic-id": "glowstick",
|
||||
"display-name": "Glowstick",
|
||||
"tooltip": "",
|
||||
"stack": 9999,
|
||||
"value": 1,
|
||||
"rarity": 0,
|
||||
"sprite": "gel.png",
|
||||
"usable" : true
|
||||
},
|
||||
{
|
||||
"mnemonic-id": "copper-bar",
|
||||
"display-name": "Copper Bar",
|
||||
"sprite:" : "ingot"
|
||||
},
|
||||
{
|
||||
"mnemonic-id": "iron-bar",
|
||||
"display-name": "Iron Bar",
|
||||
"sprite:" : "ingot"
|
||||
},
|
||||
{
|
||||
"mnemonic-id": "silver-bar",
|
||||
"display-name": "Silver Bar",
|
||||
"sprite:" : "ingot"
|
||||
},
|
||||
{
|
||||
"mnemonic-id": "tungsten-bar",
|
||||
"display-name": "Tungsten Bar",
|
||||
"sprite:" : "ingot"
|
||||
},
|
||||
{
|
||||
"mnemonic-id": "platinum-bar",
|
||||
"display-name": "Platinum Bar",
|
||||
"sprite:" : "ingot.png"
|
||||
},
|
||||
{
|
||||
"mnemonic-id": "gold-bar",
|
||||
"display-name": "Gold Bar",
|
||||
"sprite:" : "ingot.png"
|
||||
},
|
||||
{
|
||||
"mnemonic-id": "lead-bar",
|
||||
"display-name": "Lead Bar"
|
||||
},
|
||||
{
|
||||
"mnemonic-id": "ziggy-stardust",
|
||||
"display-name": "Ziggy Stardust"
|
||||
},
|
||||
{
|
||||
"mnemonic-id": "nimdoc",
|
||||
"display-name": "Nimdoc"
|
||||
},
|
||||
{
|
||||
"mnemonic-id": "sawns",
|
||||
"display-name": "Sawed-off Shotgun"
|
||||
},
|
||||
{
|
||||
"mnemonic-id": "occupied-dwellinator",
|
||||
"display-name": "Occupied Dwellinator"
|
||||
},
|
||||
{
|
||||
"mnemonic-id": "copper-dagger",
|
||||
"display-name": "Copper Dagger",
|
||||
"sprite": "dagger.png",
|
||||
"stack": 1,
|
||||
"tags": ["dagger", "blade", "melee", "metal", "copper"]
|
||||
},
|
||||
{
|
||||
"mnemonic-id": "iron-dagger",
|
||||
"display-name": "Iron Dagger",
|
||||
"sprite": "dagger.png",
|
||||
"stack": 1,
|
||||
"tags": ["dagger", "blade", "melee", "metal", "iron"]
|
||||
},
|
||||
{
|
||||
"mnemonic-id": "rusty-iron-dagger",
|
||||
"display-name": "Rusty Iron Dagger",
|
||||
"sprite": "dagger.png",
|
||||
"stack": 1,
|
||||
|
||||
"tags": ["dagger", "blade", "melee", "metal", "iron", "rusty"]
|
||||
},
|
||||
{
|
||||
"mnemonic-id": "smoke-n-dip"
|
||||
},
|
||||
{
|
||||
"mnemonic-id": "blick-axe"
|
||||
}
|
||||
]
|
18
ClientApp/assets/data/manifest.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"textures": [
|
||||
["ingot", "assets/textures/ingot.png"],
|
||||
["player", "assets/textures/player.png"],
|
||||
["bg", "assets/textures/bg.png"],
|
||||
["explosion", "assets/textures/explosion.png"],
|
||||
["redacted", "assets/textures/redacted.png"],
|
||||
["title", "assets/textures/title_1.png"],
|
||||
["gill_potion", "assets/textures/gill_potion.png"],
|
||||
["honey_jar", "assets/textures/honey_jar.png"],
|
||||
["hp_potion", "assets/textures/hp_potion.png"],
|
||||
["hp_potion_2x", "assets/textures/hp_potion_2x.png"],
|
||||
["hp_potion_3x", "assets/textures/hp_potion_3x.png"]
|
||||
],
|
||||
"music": [
|
||||
|
||||
]
|
||||
}
|
20
ClientApp/assets/data/recipes.json
Normal file
@@ -0,0 +1,20 @@
|
||||
[
|
||||
{ "station": "furnace", "consume" : ["copper-ore", 3], "produce" : "copper-bar" },
|
||||
{ "station": "furnace", "consume" : ["iron-ore", 3], "produce" : "iron-bar" },
|
||||
{ "station": "furnace", "consume" : ["silver-ore", 3], "produce" : "silver-bar" },
|
||||
{ "station": "furnace", "consume" : ["tungsten-ore", 3], "produce" : "tungsten-bar" },
|
||||
{ "station": "furnace", "consume" : ["platinum-ore", 3], "produce" : "platinum-bar" },
|
||||
{ "station": "furnace", "consume" : ["gold-ore", 3], "produce" : "gold-bar" },
|
||||
{ "station": "furnace", "consume" : ["lead-ore", 3], "produce" : "lead-bar" },
|
||||
{
|
||||
"station": "any",
|
||||
"consume" : "wood-plank",
|
||||
"produce" : ["stick", 8]
|
||||
},
|
||||
{
|
||||
"station": "alchemy-lab",
|
||||
"consume" : "lead-nugget",
|
||||
"produce" : "gold-nugget",
|
||||
"catalyst": "philosophers-stone"
|
||||
}
|
||||
]
|
365
ClientApp/assets/data/tiles.json
Normal file
@@ -0,0 +1,365 @@
|
||||
[
|
||||
{
|
||||
"mnemonic-id": "void",
|
||||
"display-name": "Void",
|
||||
"item-tooltip": "How did you even get this?",
|
||||
"solid": true,
|
||||
"color": "#FFFFFFFF",
|
||||
"hardcoded-id": 65535,
|
||||
"render": false
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "air",
|
||||
"display-name" : "Air",
|
||||
"solid": true,
|
||||
"color": "#FFFFFFFF",
|
||||
"hardcoded-id": 0,
|
||||
"render": false
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "stone",
|
||||
"display-name" : "Stone",
|
||||
"solid": true,
|
||||
"color": [112, 128, 144],
|
||||
"pallet": [[112, 122, 148], [119, 136, 153], [121, 115, 138]]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "dirt",
|
||||
"display-name" : "Dirt",
|
||||
"solid": true,
|
||||
"color": [210, 105, 30],
|
||||
"pallet": [[210, 125, 30], [195, 105, 40], [210, 105, 30]]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "mud",
|
||||
"display-name" : "Mud",
|
||||
"solid": true,
|
||||
"color": [139, 69, 19]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "limestone",
|
||||
"display-name" : "Limestone",
|
||||
"solid": true,
|
||||
"color": [238, 232, 170]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "basalt",
|
||||
"display-name" : "Basalt",
|
||||
"solid": true,
|
||||
"color": [105, 105, 105]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "cobblestone",
|
||||
"display-name" : "Cobblestone",
|
||||
"solid": true,
|
||||
"color": [112, 128, 144],
|
||||
"pallet": [[64, 64, 64], [92, 92, 92], [112, 128, 144], [119, 136, 153]],
|
||||
"drops" : null
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "red-moss",
|
||||
"display-name" : "Red Moss",
|
||||
"solid": true,
|
||||
"color": "#CC4444"
|
||||
},
|
||||
|
||||
{
|
||||
"mnemonic-id" : "brown-moss",
|
||||
"display-name" : "Brown Moss",
|
||||
"solid": true,
|
||||
"color": "#BB7755"
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "green-moss",
|
||||
"display-name" : "Green Moss",
|
||||
"solid": true,
|
||||
"color": "#006633",
|
||||
"pallet": ["#007733", "#006644", "#116633"]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "lava-moss",
|
||||
"display-name" : "Lava Moss",
|
||||
"solid": true,
|
||||
"color": "#DD6655"
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "granite",
|
||||
"display-name" : "Granite",
|
||||
"solid": true,
|
||||
|
||||
"color": "#FFFFFF",
|
||||
"drops" : null
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "marble",
|
||||
"display-name" : "Marble",
|
||||
"solid": true,
|
||||
"color": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "grass",
|
||||
"display-name" : "Grass",
|
||||
"solid": true,
|
||||
"forced-ticc-func": "grass-forced",
|
||||
"random-ticc-func": "grass-random",
|
||||
"color": [124, 252, 0],
|
||||
"pallet": [[126, 252, 5], [122, 238, 0], [124, 248, 12]]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "glowy-grass",
|
||||
"display-name" : "Glowy Grass",
|
||||
"solid": true,
|
||||
"color": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "vine",
|
||||
"display-name" : "Vine",
|
||||
"solid": false,
|
||||
"color": [32, 139, 34],
|
||||
"forced-ticc-func": "vine-forced",
|
||||
"random-ticc-func": "vine-random"
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "sand",
|
||||
"display-name" : "Sand",
|
||||
"solid": true,
|
||||
"forced-ticc-func": "sand-grav",
|
||||
"color": [238, 232, 170],
|
||||
"pallet": [[238, 232, 170], [232, 238, 160], [218, 212, 175]]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "gravel",
|
||||
"display-name" : "Gravel",
|
||||
"forced-ticc-func": "sand-grav",
|
||||
"solid": true,
|
||||
"color": [102, 118, 124],
|
||||
"pallet": [[92, 102, 128], [82, 82, 82], [102, 102, 102], [132, 132, 132], [89, 116, 123], [121, 95, 108]]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "white-sand",
|
||||
"display-name" : "White Sand",
|
||||
"solid": false,
|
||||
"forced-ticc-func": "sand-grav",
|
||||
"color": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "red-sand",
|
||||
"display-name" : "Red Sand",
|
||||
"solid": false,
|
||||
"forced-ticc-func": "sand-grav",
|
||||
"color": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "black-sand",
|
||||
"display-name" : "Black Sand",
|
||||
"solid": false,
|
||||
"forced-ticc-func": "sand-grav",
|
||||
"color": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "sandstone",
|
||||
"display-name" : "Sandstone",
|
||||
"solid": false,
|
||||
"color": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "white-sandstone",
|
||||
"display-name" : "White Sandstone",
|
||||
"solid": false,
|
||||
"color": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "black-sandstone",
|
||||
"display-name" : "Black Sandstone",
|
||||
"solid": false,
|
||||
"color": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "ash",
|
||||
"display-name" : "Ash",
|
||||
"solid": false,
|
||||
"color": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "clay",
|
||||
"display-name" : "Clay",
|
||||
"solid": false,
|
||||
"color": "#660000"
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "snow",
|
||||
"display-name" : "Snow",
|
||||
"solid": false,
|
||||
"color": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "ice",
|
||||
"display-name" : "Ice",
|
||||
"solid": false,
|
||||
"color": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "slush",
|
||||
"display-name" : "Slush",
|
||||
"solid": false,
|
||||
"color": "#FFFFFF",
|
||||
"pallet": []
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "stone-brick",
|
||||
"display-name" : "Gray Brick",
|
||||
"solid": false,
|
||||
"color": "#A0A0A0",
|
||||
"pallet": []
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "copper-ore",
|
||||
"display-name" : "Copper Ore",
|
||||
"solid": true,
|
||||
"color": [255, 140, 0],
|
||||
"pallet": [[255, 140, 0], [234, 150, 3], [241, 138, 5]]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "tin-ore",
|
||||
"display-name" : "Tin Ore",
|
||||
"solid": true,
|
||||
"color": [255, 140, 0],
|
||||
"pallet": [[255, 140, 0], [234, 150, 3], [241, 138, 5]]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "iron-ore",
|
||||
"display-name" : "Iron Ore",
|
||||
"solid": true,
|
||||
"color": [255, 140, 0],
|
||||
"pallet": [[255, 140, 0], [234, 150, 3], [241, 138, 5]]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "lead-ore",
|
||||
"display-name" : "Lead Ore",
|
||||
"solid": true,
|
||||
"color": [255, 140, 0],
|
||||
"pallet": [[255, 140, 0], [234, 150, 3], [241, 138, 5]]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "silver-ore",
|
||||
"display-name" : "Silver Ore",
|
||||
"solid": true,
|
||||
"color": [255, 140, 0],
|
||||
"pallet": [[255, 140, 0], [234, 150, 3], [241, 138, 5]]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "tungsten-ore",
|
||||
"display-name" : "Tungsten Ore",
|
||||
"solid": true,
|
||||
"color": [255, 140, 0],
|
||||
"pallet": [[255, 140, 0], [234, 150, 3], [241, 138, 5]]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "gold-ore",
|
||||
"display-name" : "Gold Ore",
|
||||
"solid": true,
|
||||
"color": [255, 140, 0],
|
||||
"pallet": [[255, 140, 0], [234, 150, 3], [241, 138, 5]]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "platinum-ore",
|
||||
"display-name" : "Platinum Ore",
|
||||
"solid": true,
|
||||
"color": [255, 140, 0],
|
||||
"pallet": [[255, 140, 0], [234, 150, 3], [241, 138, 5]]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "cobalt-ore",
|
||||
"display-name" : "Cobalt Ore",
|
||||
"solid": true,
|
||||
"color": [255, 140, 0],
|
||||
"pallet": [[255, 140, 0], [234, 150, 3], [241, 138, 5]]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "cobalt-ore",
|
||||
"display-name" : "Cobalt Ore",
|
||||
"solid": true,
|
||||
"color": [255, 140, 0],
|
||||
"pallet": [[255, 140, 0], [234, 150, 3], [241, 138, 5]]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "titanium-ore",
|
||||
"display-name" : "Titanium Ore",
|
||||
"solid": true,
|
||||
"color": [255, 140, 0],
|
||||
"pallet": [[255, 140, 0], [234, 150, 3], [241, 138, 5]]
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "uranium-ore",
|
||||
"display-name" : "Uranium Ore",
|
||||
"solid": true,
|
||||
"color": [255, 140, 0],
|
||||
"pallet": [[255, 140, 0], [234, 150, 3], [241, 138, 5]]
|
||||
},
|
||||
|
||||
{
|
||||
"mnemonic-id" : "oak-plank",
|
||||
"display-name" : "Oak Plank",
|
||||
"solid": true,
|
||||
"color": [222, 184, 135]
|
||||
},
|
||||
|
||||
{
|
||||
"mnemonic-id" : "water",
|
||||
"display-name" : "Water",
|
||||
"solid": true,
|
||||
"color": "#0000FF",
|
||||
"pallet": [],
|
||||
"drops" : null
|
||||
},
|
||||
|
||||
{
|
||||
"mnemonic-id" : "blood",
|
||||
"display-name" : "Blood",
|
||||
"solid": true,
|
||||
"color": "#0000FF",
|
||||
"pallet": [],
|
||||
"drops" : null
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "sludge",
|
||||
"display-name" : "Sludge",
|
||||
"solid": true,
|
||||
"color": "#0000FF",
|
||||
"pallet": [],
|
||||
"drops" : null
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "lava",
|
||||
"display-name" : "Lava",
|
||||
"solid": true,
|
||||
"color": "#0000FF",
|
||||
"pallet": [],
|
||||
"drops" : null
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "ectoplasm",
|
||||
"display-name" : "Ectoplasm",
|
||||
"solid": true,
|
||||
"color": "#0000FF",
|
||||
"pallet": [],
|
||||
"drops" : null
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "milk",
|
||||
"display-name" : "Milk",
|
||||
"solid": true,
|
||||
"color": "#0000FF",
|
||||
"pallet": [],
|
||||
"drops" : null
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "honey",
|
||||
"display-name" : "Honey",
|
||||
"solid": true,
|
||||
|
||||
"color": "#0000FF",
|
||||
"pallet": [],
|
||||
"drops" : null
|
||||
}
|
||||
]
|
BIN
ClientApp/assets/textures/ash_wip_potions.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
ClientApp/assets/textures/gill_potion.png
Normal file
After Width: | Height: | Size: 402 B |
BIN
ClientApp/assets/textures/honey_jar.png
Normal file
After Width: | Height: | Size: 730 B |
BIN
ClientApp/assets/textures/hp_potion.png
Normal file
After Width: | Height: | Size: 691 B |
BIN
ClientApp/assets/textures/hp_potion_2x.png
Normal file
After Width: | Height: | Size: 748 B |
BIN
ClientApp/assets/textures/hp_potion_3x.png
Normal file
After Width: | Height: | Size: 779 B |
BIN
ClientApp/assets/textures/hp_potion_4x.png
Normal file
After Width: | Height: | Size: 818 B |
BIN
ClientApp/assets/textures/ingot.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
ClientApp/assets/textures/speedball.png
Normal file
After Width: | Height: | Size: 718 B |
BIN
ClientApp/assets/textures/title_1.png
Normal file
After Width: | Height: | Size: 36 KiB |
@@ -1,3 +1,14 @@
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2024
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file CaveGameWindow.hpp
|
||||
/// @desc The Client App Window. See ReWindow's Documentation.
|
||||
/// @edit 11/1/2024
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Client/AssetService.hpp>
|
||||
@@ -6,105 +17,261 @@
|
||||
#include <Client/Splash.hpp>
|
||||
#include <Client/MainMenu.hpp>
|
||||
#include <Client/GameSession.hpp>
|
||||
#include <Client/WindowWidgetManager.hpp>
|
||||
#include <JUI/Widgets/ListLayout.hpp>
|
||||
#include <JUI/UDim.hpp>
|
||||
#include <rewindow/types/window.h>
|
||||
#include <ReWindow/types/Window.h>
|
||||
#include <JUI/Widgets/Window.hpp>
|
||||
#include <Client/Scene.hpp>
|
||||
#include <Client/SceneManager.hpp>
|
||||
#include <Client/Console.hpp>
|
||||
#include <JUI/Widgets/CommandLine.hpp>
|
||||
#include <Client/TileTool.hpp>
|
||||
#include "Command.hpp"
|
||||
#include <Core/Player.hpp>
|
||||
#include <Core/Explosion.hpp>
|
||||
|
||||
namespace CaveGame::ClientApp
|
||||
{
|
||||
|
||||
namespace CaveGame::ClientApp {
|
||||
|
||||
using CaveGame::Client::Scene;
|
||||
using CaveGame::Client::SceneManager;
|
||||
|
||||
using CommandArgs = std::vector<std::string>;
|
||||
|
||||
/// The main program class. Everything originates here.
|
||||
/// This class is derived from RWindow class.
|
||||
class CaveGameWindow : public ReWindow::RWindow, public SceneManager
|
||||
{
|
||||
class CaveGameWindow : public ReWindow::OpenGLWindow, public SceneManager {
|
||||
public:
|
||||
[[nodiscard]] bool InGameSession() const;
|
||||
[[nodiscard]] bool InMainMenu() const;
|
||||
Client::AssetService assets;
|
||||
CaveGame::Client::Splash* splash_ctx;
|
||||
CaveGame::Client::MainMenu* menu_ctx;
|
||||
CaveGame::Client::GameSession* game_ctx;
|
||||
JUI::Scene* wm;
|
||||
JUI::Window* settings_window;
|
||||
JUI::Window* credits_window;
|
||||
Client::Console* console_window;
|
||||
JUI::Window* stats_window;
|
||||
//Scene* current_scene = nullptr;
|
||||
bool render_grid = true;
|
||||
bool generate_grid = true;
|
||||
bool mbd = false;
|
||||
float our_avg = 0.f;
|
||||
|
||||
/// Constructs and returns an instance of the game program.
|
||||
/// @param title
|
||||
/// @param width
|
||||
/// @param height
|
||||
CaveGameWindow(const std::string& title, int width, int height);
|
||||
|
||||
~CaveGameWindow();
|
||||
|
||||
bool InGame() const;
|
||||
/// Destructor called when deleting the CaveGameWindow.
|
||||
~CaveGameWindow() override;
|
||||
|
||||
/// This function runs the totality of the game loop, start to finish.
|
||||
/// Calls Open, Init, Gameloop(), and Cleanup() in that order
|
||||
/// @note The game loop runs until Die() is called, at which point final cleanup is performed.
|
||||
void Run();
|
||||
|
||||
/// This function triggers an internal signal that the game window is ready to close.
|
||||
/// @note This does not immediately close the window, rather, time is given to allow for final data saving and other processes.
|
||||
void Die();
|
||||
|
||||
|
||||
|
||||
void create_console_window();
|
||||
|
||||
void create_stats_window();
|
||||
|
||||
void create_settings_window();
|
||||
|
||||
void create_window_widgets();
|
||||
|
||||
void Die(); void MarkReadyToClose(bool close = true);
|
||||
/// This function sets up the initial program state, particularly that which must be performed **after** the window is opened.
|
||||
void Init();
|
||||
void tile_draw(int x, int y, int radius, Core::TileID tile);
|
||||
/// This function cleans up any data or assets before closing the game.
|
||||
void Cleanup();
|
||||
|
||||
/// This function performs logic calculation routines, once per refresh.
|
||||
void Update(float elapsed);
|
||||
|
||||
/// This function performs rendering routines, once per refresh.
|
||||
void Draw();
|
||||
|
||||
public:
|
||||
void OnRefresh(float elapsed) override;
|
||||
void OnMouseButtonDown(const ReWindow::MouseButtonDownEvent &ev) override;
|
||||
void OnMouseButtonUp(const ReWindow::MouseButtonUpEvent &ev) override;
|
||||
void OnKeyUp(const ReWindow::KeyUpEvent &ev) override;
|
||||
void OnKeyDown(const ReWindow::KeyDownEvent& ev) override;
|
||||
void OnMouseMove(const ReWindow::MouseMoveEvent &ev) override;
|
||||
bool OnResizeRequest(const ReWindow::WindowResizeRequestEvent &ev) override;
|
||||
/// @return True when a flag is set that indicates the game is "preparing" to close. Certain procedures may still be taking place, such as saving and serialization.
|
||||
bool ReadyToClose() const;
|
||||
|
||||
/// Returns the game's console GUI, which accepts commands and displays log messages.
|
||||
JUI::CommandLine* Console();
|
||||
/// Returns the Scene that holds global menu windows, which can appear in any game-context.
|
||||
JUI::Scene* WindowManager();
|
||||
/// Returns the active game-world session, if it exists.
|
||||
Client::GameSession* GameSession();
|
||||
|
||||
/// @return the user's mouse coordinates from the RWindow layer.
|
||||
[[nodiscard]] Vector2 GetMouseV2() const;
|
||||
/// @return the window's size from the RWindow layer.
|
||||
[[nodiscard]] Vector2 GetSizeV2() const;
|
||||
/// @return true if the game is currently in-session.
|
||||
[[nodiscard]] bool InGame() const;
|
||||
/// @returns true if the game is currently in the main menu.
|
||||
[[nodiscard]] bool InMainMenu() const;
|
||||
|
||||
public:
|
||||
#pragma region Input Callbacks
|
||||
/// Called by the window on each frame.
|
||||
void OnRefresh(float elapsed) override;
|
||||
/// Called by the window upon the user pressing a mouse button.
|
||||
void OnMouseButtonDown(const ReWindow::MouseButtonDownEvent &ev) override;
|
||||
/// Called by the window upon the user releasing a mouse button.
|
||||
void OnMouseButtonUp(const ReWindow::MouseButtonUpEvent &ev) override;
|
||||
/// Called by the window upon the user scrolling a mouse wheel.
|
||||
void OnMouseWheel(const ReWindow::MouseWheelEvent &ev) override;
|
||||
/// Called by the window upon the user releasing a keyboard key.
|
||||
void OnKeyUp(const ReWindow::KeyUpEvent &ev) override;
|
||||
/// Called by the window upon the user pressing a keyboard key.
|
||||
void OnKeyDown(const ReWindow::KeyDownEvent& ev) override;
|
||||
/// Called by the window upon the user moving their pointer device, currently just mice.
|
||||
void OnMouseMove(const ReWindow::MouseMoveEvent &ev) override;
|
||||
/// Called by the window when it receives a request from the operating-system to resize.
|
||||
bool OnResizeRequest(const ReWindow::WindowResizeRequestEvent &ev) override;
|
||||
/// Called by the window **before** it closes.
|
||||
void OnClosing() override;
|
||||
#pragma endregion
|
||||
public:
|
||||
/// Forwards a message to the console log.
|
||||
void Log(const std::string& msg, const Color4& color = Colors::White);
|
||||
bool HasCommand(const std::string &command);
|
||||
Command GetCommand(const std::string &command);
|
||||
protected:
|
||||
|
||||
/// Calls Step() in a loop until the window wants to close.
|
||||
void Gameloop();
|
||||
/// Runs exactly one iteration of the game loop. Currently, this is one call to Update(), and then to Draw().
|
||||
/// @see Refresh().
|
||||
void Step();
|
||||
/// Creates the in-game console menu.
|
||||
void create_console_window();
|
||||
void create_stats_window();
|
||||
void create_settings_window();
|
||||
void create_window_widgets();
|
||||
/// Draws a sequence of 'debug' information strings to the screen, in a descending list.
|
||||
void draw_debug_info(std::vector<std::string> tokens);
|
||||
void CreateMenuWindows();
|
||||
/// Constructs the Splash screen, Main Menu screen, and In-game session.
|
||||
void CreateContexts();
|
||||
void Gameloop();
|
||||
void Step();
|
||||
|
||||
bool wanna_die = false; // This field indicates the program is ready to close.
|
||||
|
||||
|
||||
// TODO: Refactor this into irrelevance.
|
||||
void InGameControls(float elapsed);
|
||||
void OnConsoleCommandInput(const std::string &command);
|
||||
/// Toggles tile simulation if we are in-game.
|
||||
bool ToggleTileSim(const CommandArgs& args);
|
||||
/// Opens a singleplayer session.
|
||||
/// @param info A structure containing information for joining a singleplayer world.
|
||||
void OpenWorld(Client::SingleplayerSessionInfo info);
|
||||
|
||||
/// Logs help information to the console. Called when the user runs the 'help' command.
|
||||
bool HelpCommand(const CommandArgs& args);
|
||||
/// Shows a list of every command label.
|
||||
bool ListCommand(const CommandArgs& args);
|
||||
bool TileListCmd(const CommandArgs& args);
|
||||
bool ItemListCmd(const CommandArgs& args);
|
||||
|
||||
|
||||
bool NoclipCmd(const CommandArgs &args);
|
||||
|
||||
bool FpsLimitCmd(const CommandArgs &args);
|
||||
|
||||
/// This table defines the supported console commands, their aliases, and the callback lambda.
|
||||
#pragma region Commands
|
||||
const std::vector<Command> commands {
|
||||
{"help", {}, [this](const CommandArgs& args) { HelpCommand(args); }},
|
||||
{"list", {}, [this](const CommandArgs& args) { ListCommand(args); }},
|
||||
{"quit", {"q"}, [this](const CommandArgs& args) { Close(); }},
|
||||
{"worldedit", {"we"}, [this](const CommandArgs& args) {
|
||||
if (InGame())
|
||||
GameSession()->ToggleWorldEdit();
|
||||
//tile_tool->Enable(!tile_tool->IsEnabled());
|
||||
return true;
|
||||
}},
|
||||
{"tilesim", {"q"}, [this](const CommandArgs& args) { return ToggleTileSim(args);}},
|
||||
{"grid", {"q"}, [this](const CommandArgs& args) {}},
|
||||
{"credits", {"q"}, [this](const CommandArgs& args) { credits_window->Toggle(); }},
|
||||
{"settings", {"q"}, [this](const CommandArgs& args) {}},
|
||||
{"spawn", {}, [this](const CommandArgs& args) {
|
||||
if (!InGame())
|
||||
return Log("Error: This command is only available when in a world!", Colors::Red);
|
||||
|
||||
if (args.size() == 1)
|
||||
return Log("Available entities: player, explosion, particle");
|
||||
|
||||
if (args[1] == "player")
|
||||
{
|
||||
auto pos = GameSession()->World()->camera.ScreenToWorld(GetMouseV2());
|
||||
auto* plr = new CaveGame::Core::Player(pos);
|
||||
GameSession()->World()->AddEntity(plr);
|
||||
return;
|
||||
}
|
||||
if (args[1] == "explosion")
|
||||
{
|
||||
auto pos = GameSession()->World()->camera.ScreenToWorld(GetMouseV2());
|
||||
auto* plr = new CaveGame::Core::Explosion(GameSession()->World(), pos, 1.f, GameSession()->WorldEditToolWindow()->BrushRadius());
|
||||
GameSession()->World()->AddEntity(plr);
|
||||
return;
|
||||
}
|
||||
if (args[1] == "particle")
|
||||
{
|
||||
/*auto pos = GameSession()->World()->camera.ScreenToWorld(GetMouseV2());
|
||||
auto* plr = new CaveGame::Core::Player(pos);
|
||||
GameSession()->World()->AddEntity(plr);
|
||||
return;*/
|
||||
}
|
||||
}},
|
||||
{"settings", {"q"}, [this](const CommandArgs& args) {}},
|
||||
{"give", {"q"}, [this](const CommandArgs& args) {}},
|
||||
{"tp", {"q"}, [this](const CommandArgs& args) {}},
|
||||
{"freecam", {"q"}, [this](const CommandArgs& args) {}},
|
||||
{"god", {"q"}, [this](const CommandArgs& args) {}},
|
||||
{"fullbright", {"q"}, [this](const CommandArgs& args) {}},
|
||||
{"connect", {"q"}, [this](const CommandArgs& args) {}},
|
||||
{"disconnect", {"q"}, [this](const CommandArgs& args) {}},
|
||||
{"server", {"q"}, [this](const CommandArgs& args) {}},
|
||||
{"sandbox", {"q"}, [this](const CommandArgs& args) {}},
|
||||
{"crash", {"q"}, [this](const CommandArgs& args) {}},
|
||||
{"time", {"q"}, [this](const CommandArgs& args) {}},
|
||||
{"juidebug", {"q"}, [this](const CommandArgs& args) {}},
|
||||
{"fpslimit", {"fps"}, [this](const CommandArgs& args) { return FpsLimitCmd(args); }},
|
||||
{"grid", {}, [this](const CommandArgs& args) {
|
||||
if (current_scene == game_ctx)
|
||||
{
|
||||
game_ctx->World()->chunk_debug.Enable(!game_ctx->World()->chunk_debug.IsEnabled());
|
||||
} else
|
||||
Log("Must be in-game to use this command!");
|
||||
}},
|
||||
{"tileactivity", {}, [this](const CommandArgs& args) {
|
||||
if (current_scene == game_ctx)
|
||||
{
|
||||
game_ctx->World()->SetShowTileActivity(!game_ctx->World()->IsShowTileActivityEnabled());
|
||||
} else
|
||||
Log("Must be in-game to use this command!");
|
||||
}},
|
||||
{"save-world", {}, [this](const CommandArgs& args) {
|
||||
if (InGame())
|
||||
GameSession()->World()->Save();
|
||||
}},
|
||||
{"tile-list", {}, [this](const CommandArgs& args) { return TileListCmd(args); }},
|
||||
{"item-list", {}, [this](const CommandArgs& args) { return ItemListCmd(args); }},
|
||||
{"container", {}, [this](const CommandArgs& args) {
|
||||
Log(std::format("Size: {}", args.size()));
|
||||
if (args.size() == 3)
|
||||
{
|
||||
int w = std::stoi(args[1]);
|
||||
int h = std::stoi(args[2]);
|
||||
|
||||
GameSession()->HUD()->Add(new ContainerWindow(GameSession()->HUD(), w, h, "Test Container"));
|
||||
}
|
||||
}},
|
||||
{"noclip", {}, [this](const CommandArgs& args) { return NoclipCmd(args); }}
|
||||
};
|
||||
|
||||
Command InvalidCommand {
|
||||
"n/a", {}, [this](const CommandArgs& args) {
|
||||
Log("Nope");
|
||||
}
|
||||
};
|
||||
#pragma endregion
|
||||
|
||||
|
||||
|
||||
protected: // Complex class members.
|
||||
Client::AssetService assets;
|
||||
CaveGame::Client::Splash* splash_ctx = nullptr;
|
||||
CaveGame::Client::MainMenu* menu_ctx = nullptr;
|
||||
CaveGame::Client::GameSession* game_ctx = nullptr;
|
||||
JUI::Scene* wm = nullptr;
|
||||
JUI::Window* settings_window = nullptr;
|
||||
JUI::Window* credits_window = nullptr;
|
||||
|
||||
JUI::CommandLine* console_window = nullptr;
|
||||
JUI::Window* stats_window = nullptr;
|
||||
protected: // Primitive class members.
|
||||
/// This field indicates the program is ready to close.
|
||||
bool wanna_die = false;
|
||||
bool render_grid = true;
|
||||
bool generate_grid = true;
|
||||
bool mbd = false;
|
||||
float our_avg = 0.f;
|
||||
float max_fps = 60.f;
|
||||
|
||||
float tool_radius = 8.f;
|
||||
|
||||
void OnMouseWheel(const ReWindow::MouseWheelEvent &) override;
|
||||
};
|
||||
}
|
16
ClientApp/include/ClientApp/Command.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
namespace CaveGame::ClientApp {
|
||||
|
||||
using CommandArgs = std::vector<std::string>;
|
||||
|
||||
struct Command {
|
||||
std::string name;
|
||||
std::vector<std::string> aliases;
|
||||
std::function<void(CommandArgs)> callback;
|
||||
};
|
||||
}
|
@@ -13,29 +13,38 @@
|
||||
#include <steam_api.h>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <JGL/JGL.h>
|
||||
#include <ClientApp/CaveGameWindow.hpp>
|
||||
#include <Core/Loggers.hpp>
|
||||
#include <rewindow/logger/logger.h>
|
||||
#include <ReWindow/Logger.h>
|
||||
#include <Core/Tile.hpp>
|
||||
#include <JGL/logger/logger.h>
|
||||
#include <JJX/JSON.hpp>
|
||||
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
mcolor::windowsSaneify();
|
||||
// Hide logs from engine components so we can focus on CaveGame.
|
||||
JGL::Logger::Warning.EnableConsole(false);
|
||||
JGL::Logger::Debug.EnableConsole(false);
|
||||
ReWindow::Logger::Debug.EnableConsole(false);
|
||||
ReWindow::Logger::Debug.EnableFile(false);
|
||||
//ReWindow::Logger::Debug.EnableFile(false);
|
||||
|
||||
// Reduce verbosity of our base logger.
|
||||
CaveGame::Logs::Info.IncludeLocation(false);
|
||||
|
||||
// Let 'em know we're coming.
|
||||
CaveGame::Logs::Info("Starting client program.");
|
||||
|
||||
// TODO: Steam
|
||||
// If we compiled with steam, poke the system for the steam API, and launch with it, if we have it.
|
||||
#ifdef STEAM_BUILD
|
||||
CaveGame::Logs::Info.Log("Running steam build.");
|
||||
bool steam_success = SteamAPI_Init();
|
||||
#endif
|
||||
|
||||
|
||||
auto* window = new CaveGame::ClientApp::CaveGameWindow("Re-CaveGame", 800, 600);
|
||||
//window->SetResizable(true);
|
||||
window->Run();
|
||||
@@ -47,6 +56,6 @@ int main(int argc, char** argv) {
|
||||
#endif
|
||||
|
||||
CaveGame::Logs::Info.Log("Exiting client program.");
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
@@ -1,17 +1,31 @@
|
||||
#include <Client/CreditsWindow.hpp>
|
||||
#include "ClientApp/CaveGameWindow.hpp"
|
||||
#include <Client/SettingsMenu.hpp>
|
||||
|
||||
#include <bits/random.h>
|
||||
#include <Core/Explosion.hpp>
|
||||
#include <Core/Loggers.hpp>
|
||||
#include <Core/Player.hpp>
|
||||
#include <rewindow/inputservice.hpp>
|
||||
#include <ReWindow/InputService.h>
|
||||
#include <Core/Macros.hpp>
|
||||
#include <jstick.hpp>
|
||||
#include <Core/ItemRegistry.hpp>
|
||||
#include <Core/TileRegistry.hpp>
|
||||
#include <JJX/JSON.hpp>
|
||||
#include <JUI/Widgets/FpsGraph.hpp>
|
||||
|
||||
namespace CaveGame::ClientApp
|
||||
{
|
||||
CaveGameWindow::CaveGameWindow(const std::string& title, int width, int height): ReWindow::RWindow(title, width, height)
|
||||
namespace CaveGame::ClientApp {
|
||||
|
||||
using namespace CaveGame::Core;
|
||||
using namespace CaveGame::Client;
|
||||
|
||||
void ReadRecipesAndRegister() {}
|
||||
|
||||
CaveGameWindow::CaveGameWindow(const std::string& title, int width, int height): ReWindow::OpenGLWindow(title, width, height, 2, 1)
|
||||
{
|
||||
Logs::Info("Parsing Tile Data.");
|
||||
CaveGame::Core::LoadTileMetadata();
|
||||
Logs::Info("Parsing Item Data.");
|
||||
CaveGame::Core::LoadItemMetadata();
|
||||
Logs::Info("Creating game window.");
|
||||
|
||||
CreateContexts();
|
||||
@@ -27,8 +41,23 @@ namespace CaveGame::ClientApp
|
||||
delete wm;
|
||||
|
||||
Logs::Info("Closing game window.");
|
||||
// The base destructor will get called automatically - Redacted.
|
||||
//RWindow::~RWindow();
|
||||
}
|
||||
|
||||
void CaveGameWindow::OpenWorld(Client::SingleplayerSessionInfo info)
|
||||
{
|
||||
Logs::Info(std::format("Game session requested.", info.NewWorld));
|
||||
game_ctx = new CaveGame::Client::GameSession(info.NewWorld);
|
||||
|
||||
// TODO: Parse Info to construct gameworld files.
|
||||
ChangeScene(game_ctx);
|
||||
|
||||
game_ctx->RequestToggleSettings += [&, this] {
|
||||
settings_window->Toggle();
|
||||
};
|
||||
|
||||
game_ctx->OnSessionExit += [&, this] {
|
||||
ChangeScene(menu_ctx);
|
||||
};
|
||||
}
|
||||
|
||||
void CaveGameWindow::CreateContexts()
|
||||
@@ -37,21 +66,15 @@ namespace CaveGame::ClientApp
|
||||
splash_ctx = new CaveGame::Client::Splash();
|
||||
Logs::Info("Building main menu.");
|
||||
menu_ctx = new CaveGame::Client::MainMenu();
|
||||
game_ctx = new CaveGame::Client::GameSession();
|
||||
|
||||
menu_ctx->RequestWorld += [this](Client::SingleplayerSessionInfo info) {
|
||||
Logs::Info(std::format("Game session requested.", info.NewWorld));
|
||||
game_ctx = new CaveGame::Client::GameSession(info.NewWorld);
|
||||
// TODO: Parse Info to construct gameworld files.
|
||||
ChangeScene(game_ctx);
|
||||
};
|
||||
menu_ctx->RequestQuit += [this]() {
|
||||
Die();
|
||||
};
|
||||
menu_ctx->RequestShowCredits += [this]()
|
||||
{
|
||||
credits_window->Visible(true);
|
||||
OpenWorld(info);
|
||||
};
|
||||
menu_ctx->RequestQuit += [this] { Die(); };
|
||||
menu_ctx->RequestToggleCredits += [this]{ credits_window->Toggle(); };
|
||||
menu_ctx->RequestToggleSettings += [this] { settings_window->Toggle(); };
|
||||
|
||||
game_ctx = new CaveGame::Client::GameSession();
|
||||
}
|
||||
|
||||
void CaveGameWindow::CreateMenuWindows()
|
||||
@@ -59,9 +82,25 @@ namespace CaveGame::ClientApp
|
||||
wm = new JUI::Scene();
|
||||
}
|
||||
|
||||
void CaveGameWindow::Step()
|
||||
{
|
||||
this->ManagedRefresh();
|
||||
void CaveGameWindow::Step() {
|
||||
|
||||
auto begin = GetTimestamp();
|
||||
Refresh();
|
||||
auto possible_end = GetTimestamp();
|
||||
|
||||
float dt = ComputeElapsedFrameTimeSeconds(begin, possible_end);
|
||||
|
||||
if (this->max_fps > 0) {
|
||||
float min_delta = 1.f / max_fps;
|
||||
float delta_diff = min_delta - dt;
|
||||
// TODO: only guaranteed to sleep *at least* as long as we request!
|
||||
// Fake bias to sleep for less time than we want, so it averages out?
|
||||
//std::this_thread::sleep_for(std::chrono::microseconds((long long)(delta_diff*1000*500)));
|
||||
|
||||
}
|
||||
auto sure_end = GetTimestamp();
|
||||
dt = ComputeElapsedFrameTimeSeconds(begin, sure_end);
|
||||
UpdateFrameTiming(dt);
|
||||
}
|
||||
|
||||
void CaveGameWindow::Gameloop()
|
||||
@@ -73,19 +112,42 @@ namespace CaveGame::ClientApp
|
||||
|
||||
void CaveGameWindow::Run()
|
||||
{
|
||||
this->SetRenderer(RenderingAPI::OPENGL);
|
||||
this->Open();
|
||||
int js = 0;//jstick::Connect();
|
||||
|
||||
//this->SetRenderer(RenderingAPI::OPENGL);
|
||||
bool success = this->Open();
|
||||
|
||||
if (!success)
|
||||
{
|
||||
Logs::Info("Error initializing!!");
|
||||
return;
|
||||
}
|
||||
|
||||
this->Init();
|
||||
this->SetResizable(true);
|
||||
this->SetVsyncEnabled(false);
|
||||
|
||||
this->assets.PreloadCertainAssets();
|
||||
this->assets.ParseManifest();
|
||||
|
||||
//for (int i = 0; i < 10; i++)
|
||||
//{
|
||||
//assets.EnqueueTexture("assets/textures/redacted.png");
|
||||
//assets.EnqueueTexture("assets/textures/bg.png");
|
||||
//assets.EnqueueTexture("assets/textures/player.png");
|
||||
//assets.EnqueueTexture("assets/textures/explosion.png");
|
||||
//assets.EnqueueTexture("assets/textures/title_1.png");
|
||||
//assets.EnqueueTexture("assets/textures/ash_wip_potions.png");
|
||||
//}
|
||||
|
||||
|
||||
ChangeScene(splash_ctx);
|
||||
//OpenWorld({.NewWorld = false});
|
||||
//this->ChangeScene(this->game_ctx);
|
||||
|
||||
// TODO: Implement Resource/Asset manager rather than passing asset references around like this.
|
||||
this->menu_ctx->BuildWidgets();
|
||||
|
||||
this->assets.TempLoadSimple();
|
||||
this->assets.EnqueueDefaultAssetsFolder();
|
||||
this->ChangeScene(this->splash_ctx);
|
||||
|
||||
Gameloop();
|
||||
|
||||
processOnClose();
|
||||
@@ -101,18 +163,27 @@ namespace CaveGame::ClientApp
|
||||
|
||||
void CaveGameWindow::create_settings_window()
|
||||
{
|
||||
settings_window = new JUI::Window(wm);
|
||||
settings_window->SetTitleFont(JGL::Fonts::Jupiteroid);
|
||||
settings_window->SetTitle("Settings");
|
||||
settings_window->Visible(false);
|
||||
settings_window = new Client::SettingsMenu(wm);
|
||||
}
|
||||
|
||||
|
||||
void CaveGameWindow::create_window_widgets()
|
||||
{
|
||||
create_console_window();
|
||||
credits_window = Client::CreateCreditsWindowWidget(wm);
|
||||
create_stats_window();
|
||||
create_settings_window();
|
||||
|
||||
|
||||
// TODO: Swap out with FpsGraphWindow on next JUI release.
|
||||
auto* graph_window = new JUI::Window(wm);
|
||||
graph_window->Name("FPS Graph");
|
||||
graph_window->Title("FPS Graph");
|
||||
graph_window->Size({500_px, 50_px});
|
||||
|
||||
auto* graph = new JUI::FpsGraph(graph_window->Content());
|
||||
graph->Name("Graph");
|
||||
graph->Size({500_px, 50_px});
|
||||
}
|
||||
|
||||
void CaveGameWindow::Init()
|
||||
@@ -121,9 +192,11 @@ namespace CaveGame::ClientApp
|
||||
gladLoadGL();
|
||||
|
||||
// Init Josh Graphics
|
||||
bool result = JGL::Init(GetSize(), 0.f, 0.f);
|
||||
auto size = GetSize();
|
||||
Vector2i vsize = Vector2i(size.x, size.y);
|
||||
bool result = JGL::Init(vsize, 0.f, 0.f);
|
||||
//JGL::InitTextEngine();
|
||||
JGL::Update(GetSize());
|
||||
JGL::Update(vsize);
|
||||
|
||||
// Fetch core assets
|
||||
// TODO: Asset manager? Need to share references and pointers of assets between many files..
|
||||
@@ -134,25 +207,13 @@ namespace CaveGame::ClientApp
|
||||
glDepthFunc(GL_LESS);
|
||||
glDepthMask(GL_TRUE);
|
||||
|
||||
// TODO: Delete when we update to the next release of JGL
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // NOTE: This MUST be called for text rendering to work properly!!!
|
||||
|
||||
// Construct menu elements
|
||||
create_window_widgets();
|
||||
}
|
||||
|
||||
void CaveGameWindow::tile_draw(int x, int y, int radius, Core::TileID tile)
|
||||
{
|
||||
|
||||
game_ctx->world->SetTile(x, y, tile);
|
||||
|
||||
for (int dx = -radius; dx <= radius; ++dx)
|
||||
{
|
||||
for (int dy = -radius; dy <= radius; ++dy)
|
||||
{
|
||||
if (Math::Abs(dx)+Math::Abs(dy) < radius)
|
||||
game_ctx->world->SetTile(x+dx, y+dy, tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CaveGameWindow::draw_debug_info(std::vector<std::string> tokens)
|
||||
{
|
||||
int font_size = 14;
|
||||
@@ -168,7 +229,7 @@ namespace CaveGame::ClientApp
|
||||
{
|
||||
text_size = JGL::Fonts::Jupiteroid.MeasureString(token, font_size);
|
||||
JGL::J2D::FillRect(bg_color, pos, text_size);
|
||||
JGL::J2D::DrawString(text_color, token, pos.x, pos.y, 1.f, font_size);
|
||||
JGL::J2D::DrawString(text_color, token, pos.x, pos.y, font_size);
|
||||
pos.y += text_size.y;
|
||||
}
|
||||
JGL::J2D::End();
|
||||
@@ -176,56 +237,28 @@ namespace CaveGame::ClientApp
|
||||
|
||||
void CaveGameWindow::InGameControls(float elapsed)
|
||||
{
|
||||
// TODO: Ideally, we want LocalWorld class to manage it's camera input.
|
||||
// however, we cannot use the "polling" set of input methods in the Context, GameSession, or LocalWorld classes,
|
||||
// so we had to bring this code up here.
|
||||
// @maxi suggests function pointers and closure.
|
||||
// Tile placer tool code
|
||||
|
||||
/*
|
||||
if (IsKeyDown(Keys::LeftArrow))
|
||||
game_ctx->world->camera.MoveLeft();
|
||||
if (IsKeyDown(Keys::RightArrow))
|
||||
game_ctx->world->camera.MoveRight();
|
||||
if (IsKeyDown(Keys::UpArrow))
|
||||
game_ctx->world->camera.MoveUp();
|
||||
if (IsKeyDown(Keys::DownArrow))
|
||||
game_ctx->world->camera.MoveDown();
|
||||
// Temp hack until Input Focus Priority is implemented.
|
||||
for (auto window : wm->GetChildren())
|
||||
if (window->IsVisible() && window->IsMouseInside())
|
||||
return;
|
||||
|
||||
if (IsKeyDown(Keys::LeftBracket))
|
||||
game_ctx->world->camera.Rotate(-5*elapsed);
|
||||
if (IsKeyDown(Keys::RightBracket))
|
||||
game_ctx->world->camera.Rotate(5*elapsed);
|
||||
// NOTE: Don't use else conditions for input code, you'll inadvertently add a bias for one key over the other.
|
||||
|
||||
if (IsKeyDown(Keys::Minus))
|
||||
game_ctx->world->camera.ZoomIn(-elapsed);
|
||||
if (IsKeyDown(Keys::Equals))
|
||||
game_ctx->world->camera.ZoomIn(elapsed);
|
||||
*/
|
||||
|
||||
if (IsMouseButtonDown(MouseButtons::Left))
|
||||
{
|
||||
|
||||
Vector2 transformed = game_ctx->world->camera.ScreenToWorld(GetMouseCoordinates()) - Vector2{0.5f, 0.5f};
|
||||
tile_draw(transformed.x, transformed.y, tool_radius, Core::TileID::AIR);
|
||||
}
|
||||
|
||||
if (IsMouseButtonDown(MouseButtons::Right))
|
||||
{
|
||||
|
||||
Vector2 transformed = game_ctx->world->camera.ScreenToWorld(GetMouseCoordinates()) - Vector2{0.5f, 0.5f};
|
||||
|
||||
tile_draw(transformed.x, transformed.y, tool_radius, game_ctx->GetHotbarTile());
|
||||
|
||||
/*game_ctx->world->SetTile(transformed.x, transformed.y, Core::TileID::GRASS);
|
||||
game_ctx->world->SetTile(transformed.x+1, transformed.y, Core::TileID::GRASS);
|
||||
game_ctx->world->SetTile(transformed.x-1, transformed.y, Core::TileID::GRASS);
|
||||
game_ctx->world->SetTile(transformed.x, transformed.y+1, Core::TileID::GRASS);
|
||||
game_ctx->world->SetTile(transformed.x, transformed.y-1, Core::TileID::GRASS);*/
|
||||
}
|
||||
// TODO: Nuke this once JUI can consume input.
|
||||
if (InGame())
|
||||
GameSession()->WorldEditToolControlsUpdate(elapsed);
|
||||
}
|
||||
|
||||
void CaveGameWindow::Update(float elapsed)
|
||||
{
|
||||
jstick::ReadEventLoop();
|
||||
|
||||
SceneManager::UpdateSceneState(elapsed);
|
||||
|
||||
// Update floating windows.
|
||||
wm->Update(elapsed);
|
||||
|
||||
// TODO: We can't tell the game to change scenes from within a scene, currently.
|
||||
if (current_scene == splash_ctx && splash_ctx->SplashComplete())
|
||||
@@ -234,26 +267,27 @@ namespace CaveGame::ClientApp
|
||||
// Update Current Scene
|
||||
if (current_scene != nullptr)
|
||||
{
|
||||
current_scene->PassWindowSize(GetSize());
|
||||
auto isize = GetSize();
|
||||
Vector2 size = Vector2(isize.x, isize.y);
|
||||
current_scene->PassWindowSize(size);
|
||||
current_scene->Update(elapsed);
|
||||
// TODO: current_scene->PassInputState(...); ?
|
||||
}
|
||||
|
||||
// Update floating windows.
|
||||
wm->Update(elapsed);
|
||||
|
||||
|
||||
if (current_scene == game_ctx)
|
||||
if (current_scene == GameSession())
|
||||
{
|
||||
game_ctx->world->mouse_pos = GetMouseCoordinates();
|
||||
InGameControls(elapsed);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void CaveGameWindow::Draw()
|
||||
{
|
||||
JGL::Update(GetSize());
|
||||
auto isize = GetSize();
|
||||
Vector2i size = Vector2i(isize.x, isize.y);
|
||||
|
||||
JGL::Update(size);
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
@@ -267,8 +301,7 @@ namespace CaveGame::ClientApp
|
||||
// Draw floating windows.
|
||||
wm->Draw();
|
||||
|
||||
std::vector<std::string> debug_lines =
|
||||
{
|
||||
std::vector<std::string> debug_lines = {
|
||||
"Re-CaveGame - Redacted Software",
|
||||
std::format("frame: {}", refresh_count),
|
||||
std::format("delta: {}ms", J3ML::Math::Round(delta_time*1000)),
|
||||
@@ -277,45 +310,17 @@ namespace CaveGame::ClientApp
|
||||
};
|
||||
|
||||
|
||||
if (current_scene == game_ctx) {
|
||||
if (current_scene == GameSession()) {
|
||||
u16 id = game_ctx->GetTileIDUnderMouse();
|
||||
Core::Tile data = Tiles().Get(id);
|
||||
|
||||
Core::TileID id = game_ctx->GetTileUnderMouse();
|
||||
|
||||
Core::Tile* data = Core::GetByNumeric(id);
|
||||
|
||||
debug_lines.push_back(std::format("tile: {} id: {}", data->MnemonicID(), (uint)data->NumericID()));
|
||||
debug_lines.push_back(std::format("entities: {}", game_ctx->world->GetCurrentEntityCount()));
|
||||
debug_lines.push_back(std::format("tile_simulation: {}", game_ctx->world->GetTileSimulationEnabled()));
|
||||
debug_lines.push_back(std::format("rendertargets: {}", game_ctx->world->GetRenderTargetCount()));
|
||||
debug_lines.push_back(std::format("tile: {} id: {}", data.mnemonic_id, (unsigned int)data.numeric_id));
|
||||
debug_lines.push_back(std::format("entities: {}", game_ctx->World()->GetCurrentEntityCount()));
|
||||
debug_lines.push_back(std::format("tile_simulation: {}", game_ctx->World()->GetTileSimulationEnabled()));
|
||||
debug_lines.push_back(std::format("rendertargets: {}", game_ctx->World()->GetRenderTargetCount()));
|
||||
}
|
||||
|
||||
draw_debug_info(debug_lines);
|
||||
|
||||
if (current_scene == game_ctx) {
|
||||
|
||||
JGL::J2D::Begin();
|
||||
|
||||
auto camera = game_ctx->world->camera;
|
||||
|
||||
// Shift the origin to the center of the screen.
|
||||
glTranslatef(camera.HalfSizeOffset().x, camera.HalfSizeOffset().y, 0);
|
||||
|
||||
//DrawSky();
|
||||
|
||||
// Apply rotation, zoom, and translation.
|
||||
glRotatef(camera.Rotation(), 0, 0, 1);
|
||||
glScalef(camera.Zoom(), camera.Zoom(), 1);
|
||||
|
||||
glTranslatef(-camera.Position().x, -camera.Position().y, 0);
|
||||
|
||||
auto pos = camera.ScreenToWorld(currentMouse.Position);
|
||||
|
||||
JGL::J2D::OutlineCircle(Colors::Red, pos, tool_radius);
|
||||
|
||||
JGL::J2D::DrawPoint({128, 128, 128, 128}, pos);
|
||||
|
||||
JGL::J2D::End();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -329,7 +334,7 @@ namespace CaveGame::ClientApp
|
||||
if (glError != GL_NO_ERROR) {
|
||||
Logs::Error(std::format("OpenGL Error: {}", glError));
|
||||
}
|
||||
GLSwapBuffers();
|
||||
SwapBuffers();
|
||||
|
||||
}
|
||||
|
||||
@@ -346,7 +351,7 @@ namespace CaveGame::ClientApp
|
||||
if (ev.key == Keys::Grave) {
|
||||
|
||||
// TODO: Key Input Hierarchy: Determine and handle priority of where certain keys go.
|
||||
console_window->Visible(!console_window->IsVisible());
|
||||
console_window->SetOpen(!console_window->IsOpen());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -357,30 +362,29 @@ namespace CaveGame::ClientApp
|
||||
|
||||
void CaveGameWindow::OnKeyDown(const ReWindow::KeyDownEvent &ev) {
|
||||
|
||||
if (current_scene == game_ctx) {
|
||||
if (current_scene == GameSession()) {
|
||||
|
||||
if (ev.key == Keys::F5)
|
||||
game_ctx->world->RefreshAll();
|
||||
GameSession()->World()->RefreshAll();
|
||||
|
||||
if (ev.key == Keys::F7)
|
||||
game_ctx->world->SetTileSimulationEnabled(!game_ctx->world->GetTileSimulationEnabled());
|
||||
GameSession()->World()->SetTileSimulationEnabled(!GameSession()->World()->GetTileSimulationEnabled());
|
||||
|
||||
if (ev.key == Keys::F6) {
|
||||
std::srand(std::time(nullptr));
|
||||
game_ctx->world->SetSeed(std::rand());
|
||||
GameSession()->World()->SetSeed(std::rand());
|
||||
}
|
||||
|
||||
if (ev.key == Keys::Escape) {
|
||||
game_ctx->SaveAndExit();
|
||||
ChangeScene(menu_ctx);
|
||||
}
|
||||
// TODO: "Pause menu"
|
||||
|
||||
if (ev.key == Keys::P) {
|
||||
auto coords = game_ctx->world->camera.ScreenToWorld(InputService::GetMousePosition());
|
||||
auto* plr = new Core::Explosion(game_ctx->world, coords, 1.f, tool_radius);
|
||||
//auto* plr2 = new Core::Player(coords);
|
||||
//game_ctx->world->AddEntity(plr2);
|
||||
game_ctx->world->AddEntity(plr);
|
||||
if (Console()->IsOpen())
|
||||
Console()->SetOpen(false);
|
||||
|
||||
|
||||
//GameSession()->SaveAndExit();
|
||||
// TODO: GameContext needs mechanism to **inform** the higher-level object that we are done.
|
||||
//ChangeScene(menu_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,36 +437,32 @@ namespace CaveGame::ClientApp
|
||||
RWindow::OnMouseButtonUp(ev);
|
||||
}
|
||||
|
||||
void CaveGameWindow::OnMouseMove(const ReWindow::MouseMoveEvent& ev)
|
||||
{
|
||||
void CaveGameWindow::OnMouseMove(const ReWindow::MouseMoveEvent& ev) {
|
||||
|
||||
Vector2 pos = Vector2(ev.Position.x, ev.Position.y);
|
||||
|
||||
if (current_scene != nullptr)
|
||||
current_scene->PassMouseMovement(ev.Position);
|
||||
current_scene->PassMouseMovement(pos);
|
||||
|
||||
wm->ObserveMouseMovement(ev.Position);
|
||||
wm->ObserveMouseMovement(pos);
|
||||
|
||||
RWindow::OnMouseMove(ev);
|
||||
}
|
||||
|
||||
bool CaveGameWindow::OnResizeRequest(const ReWindow::WindowResizeRequestEvent& ev)
|
||||
{
|
||||
bool CaveGameWindow::OnResizeRequest(const ReWindow::WindowResizeRequestEvent& ev) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CaveGameWindow::OnMouseWheel(const ReWindow::MouseWheelEvent &ev) {
|
||||
tool_radius -= ev.WheelMovement/2.f;
|
||||
if (current_scene)
|
||||
current_scene->PassMouseWheel(ev.WheelMovement);
|
||||
|
||||
|
||||
tool_radius = Math::Max(tool_radius, 0.45f);
|
||||
// wm->ObserveMouseWheel(ev.WheelMovement);
|
||||
}
|
||||
|
||||
void CaveGameWindow::Die() { wanna_die = true; }
|
||||
|
||||
bool CaveGameWindow::InGameSession() const {
|
||||
return (current_scene == game_ctx);
|
||||
}
|
||||
|
||||
bool CaveGameWindow::InMainMenu() const {
|
||||
return (current_scene == menu_ctx);
|
||||
}
|
||||
@@ -472,26 +472,97 @@ namespace CaveGame::ClientApp
|
||||
}
|
||||
|
||||
void CaveGameWindow::OnClosing() {
|
||||
if (current_scene == game_ctx)
|
||||
if (current_scene == GameSession())
|
||||
{
|
||||
game_ctx->SaveAndExit();
|
||||
GameSession()->SaveAndExit();
|
||||
}
|
||||
|
||||
wanna_die = true;
|
||||
}
|
||||
|
||||
using namespace std::placeholders;
|
||||
|
||||
std::string str_tolower(const std::string& s)
|
||||
{
|
||||
std::string copy = s;
|
||||
std::transform(copy.begin(), copy.end(), copy.begin(), ::tolower);
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool CaveGameWindow::HasCommand(const std::string& command)
|
||||
{
|
||||
for (const Command& c : commands) {
|
||||
if (c.name == command)
|
||||
return true;
|
||||
for(const std::string& alias : c.aliases)
|
||||
if (alias == command)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Command CaveGameWindow::GetCommand(const std::string& command) {
|
||||
for (Command c : commands) {
|
||||
if (c.name == command)
|
||||
return c;
|
||||
|
||||
for (auto& alias : c.aliases)
|
||||
{
|
||||
if (command == alias)
|
||||
return c;
|
||||
}
|
||||
}
|
||||
// TODO: sensible error.
|
||||
return InvalidCommand;
|
||||
}
|
||||
|
||||
|
||||
void CaveGameWindow::OnConsoleCommandInput(const std::string& command_sequence) {
|
||||
|
||||
|
||||
auto tokens = string_split(command_sequence, ' ');
|
||||
if (tokens.empty())
|
||||
return;
|
||||
|
||||
std::string command_str = str_tolower(tokens[0]);
|
||||
|
||||
if (!HasCommand(command_str)) {
|
||||
console_window->Log("Invalid command!");
|
||||
return;
|
||||
}
|
||||
|
||||
Command cmd = GetCommand(command_str);
|
||||
|
||||
|
||||
cmd.callback(tokens);
|
||||
|
||||
}
|
||||
|
||||
void CaveGameWindow::create_console_window() {
|
||||
console_window = new Client::Console(this->wm);
|
||||
console_window = new JUI::CommandLine(this->wm);
|
||||
|
||||
console_window->Visible(false);
|
||||
|
||||
CaveGame::Logs::Info.OnLog += [this](std::string m, Color4 c) {Log(m, c); };
|
||||
CaveGame::Logs::Debug.OnLog += [this](std::string m, Color4 c) {Log(m, c); };
|
||||
CaveGame::Logs::Warning.OnLog += [this](std::string m, Color4 c) {Log(m, c); };
|
||||
CaveGame::Logs::Error.OnLog += [this](std::string m, Color4 c) {Log(m, c); };
|
||||
CaveGame::Logs::Fatal.OnLog += [this](std::string m, Color4 c) {Log(m, c); };
|
||||
|
||||
CaveGame::Logs::Client.OnLog += [this](std::string m, Color4 c) {Log(m, c); };
|
||||
CaveGame::Logs::Server.OnLog += [this](std::string m, Color4 c) {Log(m, c); };
|
||||
CaveGame::Logs::Generator.OnLog += [this](std::string m, Color4 c) {Log(m, c); };
|
||||
CaveGame::Logs::Lighting.OnLog += [this](std::string m, Color4 c) {Log(m, c); };
|
||||
|
||||
|
||||
//console_window->
|
||||
console_window->OnInput += [this] (const std::string& msg)
|
||||
{
|
||||
auto tokens = string_split(msg, ' ');
|
||||
if (tokens[0] == "tp")
|
||||
{
|
||||
std::cout << "Teleport Command" << std::endl;
|
||||
}
|
||||
|
||||
OnConsoleCommandInput(msg);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -499,6 +570,105 @@ namespace CaveGame::ClientApp
|
||||
return current_scene == game_ctx;
|
||||
}
|
||||
|
||||
Vector2 CaveGameWindow::GetMouseV2() const {
|
||||
auto coords = GetMouseCoordinates();
|
||||
return Vector2(coords.x, coords.y);
|
||||
}
|
||||
|
||||
Vector2 CaveGameWindow::GetSizeV2() const {
|
||||
auto isize = GetSize();
|
||||
return Vector2(isize.x, isize.y);
|
||||
}
|
||||
|
||||
void CaveGameWindow::Log(const std::string &msg, const Color4 &color) {
|
||||
console_window->Log(msg, color);
|
||||
}
|
||||
|
||||
bool CaveGameWindow::HelpCommand(const CommandArgs &args) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CaveGameWindow::ListCommand(const CommandArgs &args) {
|
||||
for (Command command: commands)
|
||||
{
|
||||
Log(std::format("{}", command.name));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Client::GameSession *CaveGameWindow::GameSession() { return game_ctx; }
|
||||
|
||||
JUI::Scene *CaveGameWindow::WindowManager() { return wm;}
|
||||
|
||||
JUI::CommandLine *CaveGameWindow::Console() { return console_window; }
|
||||
|
||||
bool CaveGameWindow::ToggleTileSim(const CommandArgs &args) {
|
||||
if (InGame())
|
||||
{
|
||||
GameSession()->World()->SetTileSimulationEnabled(!GameSession()->World()->GetTileSimulationEnabled());
|
||||
return true;
|
||||
}
|
||||
|
||||
Log("ERROR: Not in game context!", Colors::Red);
|
||||
return false;
|
||||
}
|
||||
|
||||
void CaveGameWindow::MarkReadyToClose(bool close) { wanna_die = close;}
|
||||
|
||||
bool CaveGameWindow::TileListCmd(const CommandArgs &args) {
|
||||
|
||||
// TODO: Tile iterator that returns vector of **valid** tiles.
|
||||
const auto& tile_list = Core::Tiles().GetTileArray();
|
||||
|
||||
for (const Core::Tile& tile : tile_list) {
|
||||
if (tile.numeric_id == 0 && tile.mnemonic_id != "air")
|
||||
continue; // Empty tile?
|
||||
Log(std::format("ID: {}, Mnemonic: {}, DisplayName: {}", tile.numeric_id, tile.mnemonic_id, tile.display_name));
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CaveGameWindow::ItemListCmd(const CommandArgs &args) {
|
||||
auto item_list = Items().GetItemMap();
|
||||
|
||||
for (const auto& [name, item] : item_list) {
|
||||
//if (item != nullptr)
|
||||
Log(std::format("Mnemonic: {}", name));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CaveGameWindow::NoclipCmd(const CommandArgs& args) {
|
||||
Log("Bet");
|
||||
if (InGame()) {
|
||||
auto plr = GameSession()->GetLocalPlayerEntity();
|
||||
if (plr != nullptr)
|
||||
plr->SetNoclip(!plr->IsNoclip());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CaveGameWindow::FpsLimitCmd(const CommandArgs& args) {
|
||||
if (args.size() < 2) {
|
||||
Log(std::format("Current FPS Limit: {}fps", max_fps));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
int val = stoi(args[1]);
|
||||
std::cout << val << std::endl;
|
||||
max_fps = val;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CaveGameWindow::ReadyToClose() const { return wanna_die; }
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@@ -18,5 +18,7 @@ target_include_directories(CaveCore PUBLIC ${J3ML_SOURCE_DIR}/include)
|
||||
target_include_directories(CaveCore PUBLIC ${mcolor_SOURCE_DIR}/include)
|
||||
target_include_directories(CaveCore PUBLIC ${jjx_SOURCE_DIR}/include)
|
||||
target_include_directories(CaveCore PUBLIC ${Sockets_SOURCE_DIR}/include)
|
||||
target_include_directories(CaveCore PUBLIC ${JGL_SOURCE_DIR}/include)
|
||||
target_include_directories(CaveCore PUBLIC ${jstick_SOURCE_DIR}/include)
|
||||
|
||||
target_link_libraries(CaveCore PUBLIC J3ML jjx mcolor Sockets)
|
||||
target_link_libraries(CaveCore PUBLIC J3ML JGL jjx mcolor Sockets jstick)
|
@@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
#include <J3ML/LinearAlgebra/Vector2.hpp>
|
||||
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
|
||||
struct Frame {
|
||||
Vector2 origin;
|
||||
Vector2 offset;
|
||||
Vector2 size;
|
||||
|
||||
|
||||
};
|
||||
|
||||
class Animator2D {
|
||||
public:
|
||||
Animator2D() { }
|
||||
void AddFrame(const std::string& frame_id, const Frame& frame);
|
||||
protected:
|
||||
private:
|
||||
};
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <Core/Macros.hpp>
|
||||
|
||||
class ArgsParser
|
||||
{
|
||||
public:
|
||||
ArgsParser(int argc, char** argv);
|
||||
explicit ArgsParser(std::vector<std::string> argv);
|
||||
|
||||
explicit ArgsParser(const std::string& args_str);
|
||||
|
||||
explicit ArgsParser(const std::string& exec, const std::vector<std::string>& tok);
|
||||
|
||||
bool FlagPresent(const std::string& flag) const;
|
||||
|
||||
std::vector<std::string> GetFlags() const {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string executable;
|
||||
std::string full_args;
|
||||
std::vector<std::string> tokens;
|
||||
};
|
@@ -16,6 +16,7 @@
|
||||
#include "Serialization.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <bitset>
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
@@ -32,13 +33,21 @@ namespace CaveGame::Core
|
||||
|
||||
/// Constructs a chunk with empty (air) tiles, located at the given chunk-cell.
|
||||
/// @param cell_coords Specifies the chunk-cell this chunk will occupy.
|
||||
explicit Chunk(const Vector2 &cell);
|
||||
explicit Chunk(const Vector2i &cell);
|
||||
|
||||
/// Constructs a chunk from a filesystem path. This constructor will load and parse the chunk file.
|
||||
explicit Chunk(const Vector2 &cell, const std::filesystem::path &file);
|
||||
explicit Chunk(const Vector2i &cell, const std::filesystem::path &file);
|
||||
|
||||
/// Returns the TileID enumeration that occupies the given tile-cell.
|
||||
TileID GetTile(int x, int y) const;
|
||||
[[nodiscard]] inline TileID GetTile(int x, int y) const {
|
||||
/* This is kind-of a cheat, If x or y is negative, the cast to unsigned will wrap it around to a large positive.
|
||||
* That makes it so you only have to check two conditions rather than 4 - Redacted. */
|
||||
if ((unsigned int) x >= (unsigned int)ChunkSize || (unsigned int) y >= (unsigned int)ChunkSize)
|
||||
throw std::runtime_error("Out of bounds!");
|
||||
return tiles[x][y];
|
||||
}
|
||||
|
||||
inline TileID GetTile(const Vector2i& coords) const { return GetTile(coords.x, coords.y); }
|
||||
|
||||
/// Sets the tile ID at the given tile-cell.
|
||||
/// @param trigger_tile_updates Whether to force adjacent tiles to update.
|
||||
@@ -48,27 +57,28 @@ namespace CaveGame::Core
|
||||
void SetTileSilent(int x, int y, TileID t);
|
||||
|
||||
/// Returns the value of the update flag field at the given tile-cell.
|
||||
bool GetTileUpdateFlag(int x, int y) const;
|
||||
[[nodiscard]] bool GetTileUpdateFlag(int x, int y) const;
|
||||
|
||||
bool GetTileUpdateBufferFlag(int x, int y) const;
|
||||
[[nodiscard]] bool GetTileUpdateBufferFlag(int x, int y) const;
|
||||
|
||||
[[nodiscard]] bool HasUpdate() const { return has_update; };
|
||||
void SetTileUpdateBufferFlag(int x, int y, bool flag = true);
|
||||
|
||||
/// Sets the value of the update flag field at the given tile-cell.A
|
||||
/// Sets the value of the update flag field at the given tile-cell.
|
||||
void SetTileUpdateFlag(int x, int y, bool flag = true);
|
||||
|
||||
/// Returns the "chunk coordinates" that this chunk occupies.
|
||||
[[nodiscard]] Vector2 GetChunkCell() const;
|
||||
[[nodiscard]] Vector2i GetChunkCell() const;
|
||||
|
||||
/// Returns the world-coordinates of the top-leftmost tile of this chunk.
|
||||
[[nodiscard]] Vector2 GetChunkRealCoordinates() const;
|
||||
[[nodiscard]] Vector2i GetChunkRealCoordinates() const;
|
||||
|
||||
[[nodiscard]] const TileID* ptr() const { return &tiles[0][0];}
|
||||
|
||||
|
||||
[[nodiscard]] std::array<TileID, ChunkSize * ChunkSize> DataContiguous() const;
|
||||
|
||||
void write(Buffer& buf) {
|
||||
write_u32(buf, tiles_with_random_tick_count);
|
||||
write_f32(buf, cell.x);
|
||||
write_f32(buf, cell.y);
|
||||
|
||||
@@ -79,15 +89,13 @@ namespace CaveGame::Core
|
||||
}
|
||||
}
|
||||
|
||||
void read(Buffer& buf)
|
||||
{
|
||||
void read(Buffer& buf) {
|
||||
tiles_with_random_tick_count = read_u32(buf);
|
||||
cell.x = read_f32(buf);
|
||||
cell.y = read_f32(buf);
|
||||
|
||||
for (int x = 0; x < ChunkSize; x++)
|
||||
{
|
||||
for (int y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
for (int x = 0; x < ChunkSize; x++) {
|
||||
for (int y = 0; y < ChunkSize; y++) {
|
||||
SetTile(x, y, (TileID) read_u8(buf));
|
||||
}
|
||||
}
|
||||
@@ -102,24 +110,34 @@ namespace CaveGame::Core
|
||||
|
||||
void SwapTileUpdateBuffers();
|
||||
|
||||
bool FirstPassCompleted() const;
|
||||
[[nodiscard]] bool FirstPassCompleted() const;
|
||||
|
||||
bool SecondPassCompleted() const;
|
||||
[[nodiscard]] bool SecondPassCompleted() const;
|
||||
|
||||
void SetFirstPassCompleted(bool complete = true);
|
||||
|
||||
void SetSecondPassCompleted(bool complete = true);
|
||||
|
||||
// TODO: Don't do this unless debugging, iterating over every chunk just to increment this number is slow.
|
||||
void Update(float delta)
|
||||
{
|
||||
time_since_refresh += delta;
|
||||
}
|
||||
|
||||
public:
|
||||
bool touched;
|
||||
// For to the rendertarget!!
|
||||
float time_since_refresh = 0.f;
|
||||
int tiles_with_random_tick_count = 0;
|
||||
protected:
|
||||
Vector2 cell;
|
||||
Vector2i cell;
|
||||
TileID tiles[ChunkSize][ChunkSize];
|
||||
bool tagged_for_update[ChunkSize][ChunkSize];
|
||||
bool update_buffer[ChunkSize][ChunkSize];
|
||||
std::bitset<ChunkSize * ChunkSize> tagged_for_update;
|
||||
std::bitset<ChunkSize * ChunkSize> update_buffer;
|
||||
std::vector<Vector2i> needs_update;
|
||||
bool first_pass_complete = false;
|
||||
bool second_pass_complete = false;
|
||||
|
||||
bool has_update = false;
|
||||
};
|
||||
|
||||
constexpr std::size_t Chunk::BufferSizeBytes()
|
||||
|
@@ -18,10 +18,7 @@
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <type_traits>
|
||||
#include <wait.h>
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
//#include <wait.h>
|
||||
|
||||
/// A simple C++11 Concurrent Queue based on std::queue.
|
||||
/// Supports waiting operations for retrieving an element when it's empty.
|
||||
@@ -139,7 +136,4 @@ namespace CaveGame::Core
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
};
|
46
Core/include/Core/Container.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <J3ML/LinearAlgebra.hpp>
|
||||
#include <map>
|
||||
#include <Core/Itemstack.hpp>
|
||||
#include "Core/v2i_hash.hpp"
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
enum RestrictListMode
|
||||
{
|
||||
ALLOWLIST,
|
||||
DENYLIST
|
||||
};
|
||||
|
||||
class Container {
|
||||
public:
|
||||
|
||||
RestrictListMode whitelist_mode;
|
||||
|
||||
Container(int rows, int columns);
|
||||
|
||||
ItemStack &At(const Vector2i &cell);
|
||||
|
||||
/// Returns how much of the stack would be left-over if inserted into this container.
|
||||
/// Storing onto existing non-full item stacks of the same type will occur.
|
||||
int CanFitItemStack(const ItemStack &item)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool IsSlotEmpty(const Vector2i& cell)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int Insert(const ItemStack &item)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
private:
|
||||
std::unordered_map<Vector2i, ItemStack> contents;
|
||||
};
|
||||
}
|
@@ -2,11 +2,15 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
// TODO: Consider implementing traits.
|
||||
|
||||
enum class TileID : std::uint16_t
|
||||
using TileID = uint16_t;
|
||||
|
||||
|
||||
/*enum class TileID : std::uint16_t
|
||||
{
|
||||
//
|
||||
AIR = 0,
|
||||
@@ -151,6 +155,6 @@ namespace CaveGame::Core
|
||||
TORCH_EMBER = 256,
|
||||
VOID = 65535,
|
||||
|
||||
};
|
||||
};*/
|
||||
|
||||
}
|
@@ -38,12 +38,21 @@ namespace CaveGame::Core {
|
||||
virtual void Draw() = 0;
|
||||
|
||||
virtual void Update(float elapsed) = 0;
|
||||
virtual void PhysicsUpdate(float elapsed) {}
|
||||
|
||||
Vector2 Position() const;
|
||||
|
||||
Vector2 TopLeft() const { return Position()-(bounding_box/2.f);}
|
||||
Vector2 RenderTopLeft() const { return Position()-(texture_center);}
|
||||
Vector2 Centroid() const { return Position();}
|
||||
AABB2D TranslatedBoundingBoxAABB() const { }
|
||||
|
||||
Vector2 Size() const;
|
||||
|
||||
virtual void CollisionTest(ITileMap* map, float step) {}
|
||||
|
||||
void SetNoclip(bool value);
|
||||
[[nodiscard]] bool IsNoclip() const;
|
||||
protected:
|
||||
int health;
|
||||
int max_health;
|
||||
@@ -51,8 +60,16 @@ namespace CaveGame::Core {
|
||||
float age;
|
||||
Vector2 position;
|
||||
Vector2 bounding_box;
|
||||
Vector2 texture_center;
|
||||
Vector2 render_center;
|
||||
std::vector<StatusEffect> active_effects;
|
||||
Color4 color;
|
||||
bool on_ground;
|
||||
float airtime = 0;
|
||||
float climbing_skill = 55;
|
||||
bool facing_left = true;
|
||||
bool walking = false;
|
||||
bool noclip = false;
|
||||
private:
|
||||
|
||||
};
|
||||
|
@@ -1,12 +1,13 @@
|
||||
#pragma once
|
||||
#include "Animator2D.hpp"
|
||||
#include "Sprite.hpp"
|
||||
#include "Entity.hpp"
|
||||
|
||||
#include <JGL/types/RenderTarget.h>
|
||||
#include <Core/TileRegistry.hpp>
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
|
||||
class Explosion : public Entity, public Animator2D {
|
||||
class Explosion : public Entity, public Sprite {
|
||||
public:
|
||||
|
||||
const AABB2D SP_EXPLOSION0 {{0, 0}, {32, 32}};
|
||||
@@ -24,18 +25,16 @@ namespace CaveGame::Core
|
||||
detonated = true;
|
||||
health = 0;
|
||||
|
||||
|
||||
|
||||
for (int x = -radius; x <= radius; x++) {
|
||||
for (int y = -radius; y <= radius; y++) {
|
||||
float dist = Vector2::LengthSquared(Vector2(x, y));
|
||||
if (dist <= radius*radius) {
|
||||
wrld->SetTile(position.x + x, position.y + y, TileID::AIR);
|
||||
wrld->SetTile(position.x + x, position.y + y, Tiles()["air"].numeric_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool HasDetonated() const;
|
||||
|
||||
void Draw() override;
|
||||
@@ -49,7 +48,13 @@ namespace CaveGame::Core
|
||||
bool detonated = false;
|
||||
float anim_timer = 0.f;
|
||||
ITileMap* wrld;
|
||||
AABB2D quad;
|
||||
private:
|
||||
void DrawFrame(const JGL::Texture *texture, const AABB2D &quad, const Vector2 &pos, const Vector2 &scale);
|
||||
|
||||
AABB2D CurrentFrame() const;
|
||||
|
||||
void SetFrame(const AABB2D &frame_quad);
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -1,8 +0,0 @@
|
||||
//
|
||||
// Created by dawsh on 11/10/24.
|
||||
//
|
||||
|
||||
#ifndef RECAVEGAME_GAMEPROTOCOL_HPP
|
||||
#define RECAVEGAME_GAMEPROTOCOL_HPP
|
||||
|
||||
#endif //RECAVEGAME_GAMEPROTOCOL_HPP
|
@@ -53,11 +53,11 @@ namespace CaveGame::Core
|
||||
static constexpr float ClayVeinNoise = 0.175f;
|
||||
static constexpr float ClayVeinRampFactor = 2.f;
|
||||
|
||||
const Vector2 SiltVeinHiPassScale = {220.f, 205.f};
|
||||
const Vector2 SiltVeinHiPassScale = {85.f, 95.f};
|
||||
static constexpr float SiltVeinHiPassOffset = 5.f;
|
||||
static constexpr float SiltVeinHiPassOutputScale = 2.2f;
|
||||
|
||||
const Vector2 SiltVeinLoPassScale = {27.f, 25.f};
|
||||
const Vector2 SiltVeinLoPassScale = {15.f, 16.f};
|
||||
static constexpr float SiltVeinLoPassOffset = 422.f;
|
||||
static constexpr float SiltVeinLoPassOutputScale = 1.25f;
|
||||
|
||||
@@ -160,8 +160,7 @@ namespace CaveGame::Core
|
||||
void SecondPass(ITileMap* world, Chunk* chunk);
|
||||
|
||||
|
||||
uint ColorMap(int range, int wx, int wy);
|
||||
|
||||
unsigned int ColorMap(int range, int wx, int wy);
|
||||
|
||||
// TODO: Implement SecondPass, the catch is, it **may** need to load an arbitrary amount of adjacent chunks to correctly place structures.
|
||||
// TODO: Expert Mode: How do we keep it threaded separately while still accomplishing the above?
|
||||
@@ -183,7 +182,7 @@ namespace CaveGame::Core
|
||||
float GetPrecomputedWhiteNoise2D(int x, int y) const;
|
||||
|
||||
protected:
|
||||
int seed = 0;
|
||||
int seed = 42069;
|
||||
SimplexNoise simplex;
|
||||
PerlinNoise perlin;
|
||||
|
||||
|
@@ -1 +0,0 @@
|
||||
#pragma once
|
@@ -1,17 +1,21 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Core/Data.hpp>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
using TileState = uint16_t;
|
||||
using TileID = uint16_t;
|
||||
|
||||
|
||||
class ITileMap
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] virtual TileID GetTile(int x, int y) const = 0;
|
||||
|
||||
virtual void SetTile(int x, int y, TileID tile, bool flag_update = true) = 0;
|
||||
//virtual void SetTile(int x, int y, const std::string& tile_name, bool flag_update = true) = 0;
|
||||
virtual void SwapTile(int source_x, int source_y, int dest_x, int dest_y, bool flag_update = true) = 0;
|
||||
[[nodiscard]] virtual bool GetTileUpdateFlag(int x, int y) const = 0;
|
||||
virtual void SetTileUpdateFlag(int x, int y, bool flag = true) = 0;
|
||||
|
@@ -1,12 +1,31 @@
|
||||
#include <set>
|
||||
#include <Core/Registry.hpp>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Color4.hpp>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <J3ML/Math.hpp>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
//#include <Core/Registry.hpp>
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
|
||||
/// Implementation of items
|
||||
/// 1. Need to create a TileItem associated with each tile, automatically.
|
||||
/// 2. Not a strict requirement, but might like to be able to read item data from a data file.
|
||||
/// This would remove the need to re-compile the entire program when items are changed.
|
||||
/// 3. Each item in-game should be a single instance of the Item class.
|
||||
/// 4. Items are represented via ItemStacks and ItemStack entities, which represent quantities of items
|
||||
/// in a container, or dropped on the ground.
|
||||
/// 5. Support Item Animated Sprites
|
||||
/// 6. Support lots of unique item abilities and effects.
|
||||
/// 7. Support attaching custom metadata to certain items.
|
||||
|
||||
|
||||
/// This enum defines combinatorial category flags to items to enable smart sorting.
|
||||
enum class ItemCategory: u8
|
||||
enum class ItemCategory
|
||||
{
|
||||
ANY, TILE_ITEM, ORE, TOOL, WEAPON, ARMOR, CLOTHING, FOOD, POTION, INGREDIENT, MEME, PLANT, METAL, ORGANIC, BUILDING_BLOCK,
|
||||
FLUID, SOIL, GAS, LIGHT_SOURCE, CRAFTING_STATION,
|
||||
@@ -15,7 +34,7 @@ namespace CaveGame::Core
|
||||
|
||||
/// This enum defines a mutually-exclusive (one-at-a-time) 'Modifier' that items may randomly spawn with.
|
||||
/// It will affect item stats in a variety of ways, and some will even have special coding.
|
||||
enum class ItemModifiers : u8 {
|
||||
enum class ItemModifiers {
|
||||
|
||||
// These represent low-quality, crappy items, often with comical effects.
|
||||
FLIMSY, RUSTY, BROKEN, BENDY, RUBBERY, FRAGILE, FILTHY, DRAB, DULL, SCARRED, BURNED,
|
||||
@@ -45,18 +64,33 @@ namespace CaveGame::Core
|
||||
ANCIENT, VICTORIOUS, CONDEMNED, CLEAN, LUCKY, POWERFUL, SUPER,
|
||||
};
|
||||
|
||||
class Item
|
||||
/*class ItemComponent
|
||||
{
|
||||
public:
|
||||
|
||||
};
|
||||
|
||||
class UseCooldown {};
|
||||
class Consumable {};
|
||||
class Drinkable {};
|
||||
class Eatable {};*/
|
||||
|
||||
struct Item
|
||||
{
|
||||
std::string mnemonic;
|
||||
std::string display_name;
|
||||
std::string tooltip;
|
||||
std::string author;
|
||||
int max_stack;
|
||||
int value;
|
||||
std::vector<std::string> tags;
|
||||
std::vector<std::string> aliases;
|
||||
|
||||
std::optional<Color4> sprite_color;
|
||||
|
||||
protected:
|
||||
private:
|
||||
};
|
||||
|
||||
class EmptyBottle : public Item
|
||||
{
|
||||
REGISTER("EmptyBottle", Item);
|
||||
};
|
||||
|
||||
class ItemFilter
|
||||
{
|
||||
@@ -72,14 +106,15 @@ namespace CaveGame::Core
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
namespace Items {
|
||||
|
||||
static const Item Straw;
|
||||
|
||||
static const Item Gel;
|
||||
//static const Item Gel {"gel", "Gel", };
|
||||
|
||||
static const Item Glowstick;
|
||||
static const Item RoastChicken;
|
||||
//static const Item RoastChicken {"roast-chicken", "Roast Chicken"};
|
||||
static const Item PumpkinPie;
|
||||
static const Item ShepherdsPie;
|
||||
|
||||
@@ -195,7 +230,7 @@ namespace CaveGame::Core
|
||||
static const Item EctoplasmBucket;
|
||||
#pragma endregion
|
||||
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
}
|
||||
|
45
Core/include/Core/ItemRegistry.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <Core/Singleton.hpp>
|
||||
#include <Core/Item.hpp>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
namespace CaveGame::Core {
|
||||
|
||||
/// This object contains the list of items in the game, which is pulled from json files at runtime.
|
||||
class ItemRegistry : public Singleton<ItemRegistry> {
|
||||
public:
|
||||
|
||||
/// Adds an item into the game's list, using the mnemonic ID as a key for the lookup table.
|
||||
/// @see Item.
|
||||
void Register(const Item& data);
|
||||
|
||||
/// @return True if an item with the given mnemonic string-id, or alias, exists in the item list.
|
||||
bool Exists(const std::string& name);
|
||||
|
||||
/// @return The Item associated with the given mnemonic string-id, or alias.
|
||||
/// @throws std::runtime_error if no item with the given mnemonic is found.
|
||||
const Item& Get(const std::string& name);
|
||||
|
||||
/// @return The full list of items added to the game.
|
||||
std::unordered_map<std::string, Item> GetItemMap();
|
||||
|
||||
/// @see ItemRegistry::Get()
|
||||
const Item& operator[](const std::string& name);
|
||||
protected:
|
||||
std::unordered_map<std::string, Item> registered_items;
|
||||
};
|
||||
|
||||
/// @return the global item registry singleton.
|
||||
static ItemRegistry& Items() { return ItemRegistry::Instance();}
|
||||
|
||||
/// Reads, parses, and registers items from a given JSON file path.
|
||||
bool LoadItemMetadata(const std::filesystem::path& path);
|
||||
|
||||
/// Reads, parses, and registers items from a pre-set list of JSON files.
|
||||
bool LoadItemMetadata();
|
||||
|
||||
}
|
@@ -1,7 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <Core/Item.hpp>
|
||||
#include <Core/PhysicsEntity.hpp>
|
||||
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
class ItemStack : PhysicsEntity {};
|
||||
struct ItemStack {
|
||||
Core::Item* type;
|
||||
uint16_t stack;
|
||||
};
|
||||
|
||||
class ItemStackEntity : public PhysicsEntity, public ItemStack {
|
||||
public:
|
||||
protected:
|
||||
private:
|
||||
};
|
||||
}
|
23
Core/include/Core/JsonConversions.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <Color4.hpp>
|
||||
#include <Colors.hpp>
|
||||
#include <JJX/JSON.hpp>
|
||||
#include <map>
|
||||
|
||||
namespace JsonConversions {
|
||||
using namespace JJX;
|
||||
|
||||
/// Parses an RGBA color structure from a json value, which may be one of the following:
|
||||
/// \n * An array of 3 to 4 integers.
|
||||
/// \n * A hexadecimal color-code string.
|
||||
/// \n * A JSON object with 'r', 'g', 'b', and optional 'a' fields.
|
||||
/// @return A Color4 structure.
|
||||
Color4 parse_color(const json::value &v);
|
||||
|
||||
|
||||
/// Parses a vector of strings from a json value, which may one string, or a JSON array of strings.
|
||||
/// @return A vector containing the parsed strings, or an empty vector if the format is invalid.
|
||||
std::vector<std::string> parse_string_list(const json::value& v);
|
||||
|
||||
}
|
@@ -1,23 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <jlog/Logger.hpp>
|
||||
#include <Event.h>
|
||||
|
||||
namespace CaveGame::Logs
|
||||
{
|
||||
|
||||
using namespace jlog;
|
||||
using Logger = GenericLogger;
|
||||
|
||||
extern Logger Info;
|
||||
extern Logger Debug;
|
||||
extern Logger Warning;
|
||||
extern Logger Error;
|
||||
extern Logger Fatal;
|
||||
std::string format_timestamp(Timestamp ts);
|
||||
|
||||
extern Logger Client;
|
||||
extern Logger Server;
|
||||
extern Logger Generator;
|
||||
extern Logger Lighting;
|
||||
std::string format_source_location(const std::source_location & location);
|
||||
|
||||
class CaveGameLogger : public GenericLogger {
|
||||
public:
|
||||
Event<std::string, Color4> OnLog;
|
||||
|
||||
CaveGameLogger(const std::string &context,
|
||||
std::ofstream &file,
|
||||
Color4 contextColor = Colors::White,
|
||||
Color4 timestampColor = Colors::Purples::Fuchsia,
|
||||
Color4 locationColor = Colors::Pinks::Pink,
|
||||
Color4 pointerColor = Colors::Pinks::LightPink,
|
||||
Color4 messageColor = Colors::Greens::LightGreen);
|
||||
|
||||
void Log(const std::string &message, const std::source_location &location = std::source_location::current(), const jlog::Timestamp &ts = Timestamp()) override;
|
||||
};
|
||||
|
||||
|
||||
extern CaveGameLogger Info;
|
||||
extern CaveGameLogger Debug;
|
||||
extern CaveGameLogger Warning;
|
||||
extern CaveGameLogger Error;
|
||||
extern CaveGameLogger Fatal;
|
||||
|
||||
extern CaveGameLogger Client;
|
||||
extern CaveGameLogger Server;
|
||||
extern CaveGameLogger Generator;
|
||||
extern CaveGameLogger Lighting;
|
||||
|
||||
void SetAllEnableConsole(bool log_to_console);
|
||||
void SetAllEnableFile(bool log_to_file);
|
||||
|
@@ -4,7 +4,10 @@
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <numeric>
|
||||
#include <filesystem>
|
||||
|
||||
std::vector<std::string> string_split(const std::string& s, char delim);
|
||||
|
||||
std::string string_build(const std::vector<std::string> &list, const std::string& delim = " ");
|
||||
std::string string_build(const std::vector<std::string> &list, const std::string& delim = " ");
|
||||
|
||||
std::string read_file(const std::filesystem::path& file_path);
|
@@ -1,11 +1,28 @@
|
||||
#pragma once
|
||||
#include "Entity.hpp"
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2025
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file PhysicsEntity.hpp
|
||||
/// @desc Abstract class of a physically-simulated game object.
|
||||
/// @edit 1/28/2025
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Core/Entity.hpp>
|
||||
#include <Core/TileRegistry.hpp>
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
class PhysicsEntity : public Entity {
|
||||
public:
|
||||
constexpr static const float gravity = 9.81f;
|
||||
constexpr static const float air_resistance = 2.45f;
|
||||
|
||||
|
||||
bool freeze_in_void = true;
|
||||
explicit PhysicsEntity(const Vector2& spawnPoint);
|
||||
|
||||
[[nodiscard]] Vector2 Velocity() const;
|
||||
@@ -14,67 +31,27 @@ namespace CaveGame::Core
|
||||
|
||||
void Accelerate(const Vector2& vector);
|
||||
|
||||
virtual void PhysicsUpdate(float elapsed);
|
||||
void PhysicsUpdate(float elapsed) override;
|
||||
|
||||
void CollisionTest(ITileMap* map, float elapsed) override
|
||||
{
|
||||
// TODO: Mechanism for figuring out if you're already stuck inside of tiles: i.e. sand
|
||||
void CollisionTest(ITileMap* map, float elapsed) override;
|
||||
|
||||
for (int x = -1; x <= bounding_box.x; x++) {
|
||||
for (int y = -1; y <= bounding_box.y; y++) {
|
||||
Vector2 tileBoxPos = Vector2(Math::Floor(position.x), Math::Floor(position.y)) + Vector2(x,y);
|
||||
void Update(float elapsed) override;
|
||||
|
||||
TileID id = map->GetTile(tileBoxPos.x, tileBoxPos.y);
|
||||
|
||||
if (id == TileID::VOID || id == TileID::AIR)
|
||||
continue;
|
||||
|
||||
Vector2 entity_centroid = position + (bounding_box / 2.f);
|
||||
Vector2 entity_halfbox = bounding_box / 2.f;
|
||||
|
||||
Vector2 tile_centroid = tileBoxPos + Vector2{0.5f, 0.5f};
|
||||
Vector2 tile_halfbox = Vector2{0.5f, 0.5f};
|
||||
|
||||
|
||||
if (Solver::AABB2Dvs(entity_centroid, entity_halfbox, tile_centroid, tile_halfbox)) {
|
||||
Vector2 separation = Solver::SolveAABB(entity_centroid, entity_halfbox, tile_centroid, tile_halfbox);
|
||||
|
||||
Vector2 normal = Solver::GetNormalForAABB(separation, velocity);
|
||||
|
||||
next_position += separation;
|
||||
|
||||
// Touched top.
|
||||
if (normal.y == -1)
|
||||
velocity.y = 0;
|
||||
|
||||
if (normal.y == 1)
|
||||
velocity.y = -velocity.y;
|
||||
|
||||
//if (normal.x != 0)
|
||||
// velocity = {-velocity.x, velocity.y};
|
||||
|
||||
// TODO: Refine condition for "step-up"s
|
||||
if (normal.x != 0 && velocity.y == 0)
|
||||
next_position.y -= 1;
|
||||
|
||||
next_position += separation;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Update(float elapsed) override {
|
||||
|
||||
// TODO: Sophisticated mechanism to maintain locked-timestep, multiple-iteration physics steps.
|
||||
PhysicsUpdate(elapsed);
|
||||
|
||||
}
|
||||
virtual inline float Mass() { return 40;}
|
||||
|
||||
protected:
|
||||
Vector2 velocity;
|
||||
Vector2 next_position;
|
||||
float mass;
|
||||
int coll_tests;
|
||||
int coll_hits;
|
||||
Vector2 last_normal;
|
||||
private:
|
||||
void ApplyHorizontalFriction(float elapsed);
|
||||
|
||||
void ApplyGravityForce(float elapsed);
|
||||
|
||||
void ApplyAirResistance(float coefficient, float elapsed);
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -1,31 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "Humanoid.hpp"
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
namespace CaveGame::Core {
|
||||
|
||||
class Player : public Humanoid {
|
||||
public:
|
||||
|
||||
const AABB2D Frame_Idle {{0, 0}, {16, 24}};
|
||||
const AABB2D Frame_Walk1 {{16, 0}, {16, 24}};
|
||||
const AABB2D Frame_Walk2 {{32, 0}, {16, 24}};
|
||||
const AABB2D Frame_Walk3 {{48, 0}, {16, 24}};
|
||||
const AABB2D Frame_Land {{64, 0}, {16, 24}};
|
||||
const AABB2D Frame_Ascend {{80, 0}, {16, 24}};
|
||||
const AABB2D Frame_Descend {{96, 0}, {16, 24}};
|
||||
const AABB2D Frame_Gore1 {{112, 0}, {16, 24}};
|
||||
const AABB2D Frame_Gore2 {{128, 0}, {16, 24}};
|
||||
const AABB2D Frame_Push1 {{144, 0}, {16, 24}};
|
||||
const AABB2D Frame_Push2 {{160, 0}, {16, 24}};
|
||||
|
||||
explicit Player(const Vector2& spawnPoint);
|
||||
|
||||
|
||||
void Draw() override;
|
||||
void Update(float elapsed) override;
|
||||
void PhysicsUpdate(float elapsed) override;
|
||||
void Jump() {
|
||||
Accelerate({0, 1});
|
||||
void Jump(float elapsed) {
|
||||
// TODO: Make it so the player falls **slightly** slower
|
||||
|
||||
Vector2 current_velocity = this->Velocity();
|
||||
|
||||
float horiz = 0;
|
||||
if (current_velocity.x > 10.f)
|
||||
horiz = Math::Clamp(current_velocity.x, 10.f, 300.f);
|
||||
|
||||
if (current_velocity.x < -10.f)
|
||||
horiz = Math::Clamp(current_velocity.x, -300.f, 10.f);
|
||||
|
||||
if (on_ground) {
|
||||
Vector2 projection = Vector2(horiz*elapsed, -16000*elapsed);
|
||||
|
||||
Accelerate(projection);
|
||||
on_ground = false;
|
||||
} else
|
||||
Accelerate({0, -100*elapsed});
|
||||
}
|
||||
void Climb();
|
||||
void Descend();
|
||||
void Crouch();
|
||||
void WalkLeft() {
|
||||
Accelerate({-1, 0});
|
||||
void WalkLeft(float elapsed) {
|
||||
Accelerate({-200*elapsed, 0});
|
||||
facing_left = true;
|
||||
walking = true;
|
||||
}
|
||||
void WalkRight() {
|
||||
Accelerate({1, 0});
|
||||
void WalkRight(float elapsed) {
|
||||
Accelerate({200*elapsed, 0});
|
||||
facing_left = false;
|
||||
walking = true;
|
||||
}
|
||||
protected:
|
||||
// TODO: Duplicated in Explosion.hpp. Refactor into Sprite class?
|
||||
float anim_timer = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -1,428 +0,0 @@
|
||||
/// Framework for performing registration of object factories.
|
||||
//
|
||||
// The major points of the framework are:
|
||||
// - header-only library
|
||||
// - no need to declare registration ahead on the base class,
|
||||
// which means header need to be included only in files
|
||||
// declaring subclasses that register themselves.
|
||||
// - registration is done via a macro called inside the declaration
|
||||
// of the class, which reduces boilerplate code, and incidentally makes
|
||||
// the registration name available to the class.
|
||||
// - ingle macro supports constructors with arbitrary number of arguments.
|
||||
// - class can be registered for different constructors, making
|
||||
// it easy to implement logic that try to instantiate an object
|
||||
// from different parameters.
|
||||
// - builtin mechanism to override registered classes, making dependency
|
||||
// injection e.g. for tests very easy.
|
||||
//
|
||||
// Basic usage
|
||||
// -----------
|
||||
// Given an interface:
|
||||
//
|
||||
// class Shape {
|
||||
// public:
|
||||
// virtual ~Shape() {}
|
||||
// virtual void Draw() const = 0;
|
||||
// };
|
||||
//
|
||||
// The framework allows to annotate with REGISTER macro any subclass:
|
||||
//
|
||||
// class Circle : public Shape {
|
||||
// REGISTER("Circle", Shape);
|
||||
// public:
|
||||
// void Draw() const override { ... }
|
||||
// };
|
||||
//
|
||||
// The first parameter of the macro can be any string and does not have to
|
||||
// match the name of the class:
|
||||
//
|
||||
// class Rect : public Shape {
|
||||
// REGISTER("Rectangle", Shape);
|
||||
// public:
|
||||
// void Draw() const override { ... }
|
||||
// };
|
||||
//
|
||||
// Note that the annotation is done only inside the derived classes.
|
||||
// Nothing needs to be added to the Shape class, and no declaration
|
||||
// other than the Shape class needs to be done. The annotation can be
|
||||
// put inside the private, protected or public section of the class.
|
||||
// The annotation adds not bytes to the class, whose size is equal to
|
||||
// the class would have without the annotation.
|
||||
//
|
||||
// With the annotation, a Shape instance can be created from a string:
|
||||
//
|
||||
// std::unique_ptr<Shape> shape = Registry<Shape>::New("Rect");
|
||||
// shape->Draw(); // will draw a rectangle!
|
||||
//
|
||||
// The function returns a unique_ptr<> which makes ownership clear and simple.
|
||||
// If no class is registered, it will return a null unique_ptr<>.
|
||||
// The CanNew() predicate can be used to check if New() would succeed without
|
||||
// actually creating an instance.
|
||||
//
|
||||
// Advanced usage
|
||||
// --------------
|
||||
//
|
||||
// The basic usage considered classes with parameter-less constructor.
|
||||
// The REGISTER macro can also be used with classes taking arbitrary
|
||||
// parameters. For example, shapes with injectable parameters
|
||||
// can be registered using REGISTER() macro with extra parameters matching
|
||||
// the constructor signature:
|
||||
//
|
||||
// class Ellipsis : public Shape {
|
||||
// REGISTER("Ellipsis", Shape, const std::string&);
|
||||
// public:
|
||||
// explicit Ellipsis(const std::string& params) { ... }
|
||||
// void Draw() const override { ... }
|
||||
// };
|
||||
//
|
||||
// The class can be instantiated using Registry<> with extra parameters
|
||||
// matching the constructor signature:
|
||||
//
|
||||
// auto shape = Registry<Shape, const string&>::New("Ellipsis");
|
||||
// shape->Draw(); // will draw a circle!
|
||||
//
|
||||
// One very interesting benefit of this approach is that client code can
|
||||
// extend the supported types without editing the base class or
|
||||
// any of the existing registered classes. The code below simply test
|
||||
// if a class can be instantiated with a parameter, and otherwise
|
||||
// tries to instantiate without parameters:
|
||||
//
|
||||
// int main(int argc, char **argv) {
|
||||
// for (int i = 1; i + 1 < argc; i += 2) {
|
||||
// const std::string key = argv[i];
|
||||
// const std::string params = argv[i + 1];
|
||||
// if (Registry<Shape, const std::string &>::CanNew(key, params)) {
|
||||
// Registry<Shape, const std::string &>::New(key, params)->Draw();
|
||||
// } else if (Registry<Shape>::CanNew(key)) {
|
||||
// Registry<Shape>::New(key)->Draw();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Additionally, it is possible to register a class with different constructors
|
||||
// so it works with old client code that only uses Registry<Shape> and
|
||||
// some client code like the one above:
|
||||
//
|
||||
// class Ellipsis : public Shape {
|
||||
// REGISTER("Ellipsis", Shape);
|
||||
// REGISTER("Ellipsis", Shape, const std::string&);
|
||||
// public:
|
||||
// explicit Ellipsis(const std::string& params = "") { ... }
|
||||
// void Draw() const override { ... }
|
||||
// };
|
||||
//
|
||||
// Testing
|
||||
// -------
|
||||
//
|
||||
// When testing client code using registered classes, it may be not desired to
|
||||
// actually instantiate real classes. If the Draw() implementations for example
|
||||
// require a graphic context to be active, calling those functions in an
|
||||
// offscreen automated test will fail. Injectors can be used to temporarily
|
||||
// replace registered classes by arbitrary factories:
|
||||
//
|
||||
// class FakeShape : public Shape {
|
||||
// public:
|
||||
// void Draw() override {}
|
||||
// };
|
||||
//
|
||||
// TEST(Draw, WorkingCase) {
|
||||
// const Registry<Shape>::Injector injectors[] =
|
||||
// Registry<Shape>::Injector("Circle", []{ return new FakeShape});
|
||||
// Registry<Shape>::Injector("Rectangle", []{ return new FakeShape});
|
||||
// Registry<Shape>::Injector("Circle", []{ return new FakeShape});
|
||||
// EXPECT_TRUE(FunctionUsingRegistryForShape());
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// Note that it may be necessary to specify the return type of lambda function
|
||||
// for code to compile as in [] -> Shape* { return .... }
|
||||
//
|
||||
// Injectors can also be used as static global variables to perform
|
||||
// registration of a class *outside* of the class, in replacement for
|
||||
// the REGISTER macro. This is useful to register classes whose code
|
||||
// cannot be edited.
|
||||
//
|
||||
// Injectors can also be used to define global or local name alias, as
|
||||
// illustrated by the example REGISTER_ALIAS macro in this file.
|
||||
//
|
||||
// REGISTER_ALIAS(Shape, "Rectangle", "Rect");
|
||||
//
|
||||
// Goodies
|
||||
// -------
|
||||
//
|
||||
// Classes registered with the REGISTER macro can know the key under
|
||||
// which they are registered. The following code:
|
||||
//
|
||||
// Registry<Vehicle>::GetKeyFor<Circle>()
|
||||
//
|
||||
// will return the string "Circle". This works on object classes,
|
||||
// passed as template parameter to GetKeyFor(), and not on object
|
||||
// instances. So there is no such functionality as:
|
||||
//
|
||||
// std::unique_ptr<Shape> shape = new Circle();
|
||||
// std::cout << Registry<Vehicle>::GetKeyFor(*shape); // Not implemented
|
||||
//
|
||||
// as it would require runtime type identification. It is possible to
|
||||
// extend the framework to support it though, by storing `type_info`
|
||||
// objects in the registry.
|
||||
//
|
||||
// The Registry<> class can also be used to list all keys that are
|
||||
// registered, along with the filename and line number at which the
|
||||
// registration is defined. It does not list though the type associated
|
||||
// to the key. Here again, it is pretty easy to extend the framework
|
||||
// to provide such functionality using type_info objects.
|
||||
//
|
||||
// Even though not necessary, one can define intermediate macros to
|
||||
// reduce boilerplate code even more. For the Shape example above,
|
||||
// one could define:
|
||||
//
|
||||
// #define REGISTER_SHAPE(KEY, ARGS...) REGISTER(#KEY, Shape, ##ARGS)
|
||||
//
|
||||
// with which the Ellipsis class above would simply be written:
|
||||
//
|
||||
// class Ellipsis : public Shape {
|
||||
// REGISTER_SHAPE(Ellipsis);
|
||||
// REGISTER_SHAPE(Ellipsis, const std::string &);
|
||||
//
|
||||
// public:
|
||||
// };
|
||||
//
|
||||
// Limitations
|
||||
// -----------
|
||||
// The code requires a C++11 compliant compiler.
|
||||
//
|
||||
// The code relies on the a GNU preprocessor feature for variadic macros:
|
||||
//
|
||||
// #define MACRO(x, opt...) call(x, ##opt)
|
||||
//
|
||||
// which extends to call(x) when opt is empty and call(x, ...) otherwise.
|
||||
//
|
||||
// None of the static methods of Registry<> can be called from
|
||||
// a global static, as it would result in an initialization order fiasco.
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <typeinfo>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#define REGISTER(KEY, TYPE, ARGS...) REGISTER_AT(__LINE__, KEY, TYPE ##ARGS)
|
||||
|
||||
#define REGISTER_ALIAS(TYPE, NAME, ALIAS) \
|
||||
REGISTER_ALIAS_AT(__LINE__, TYPE, NAME, ALIAS)
|
||||
|
||||
#define REGISTER_ALIAS_AT(LINE, TYPE, NAME, ALIAS) \
|
||||
Registry<TYPE>::Injector CONCAT_TOKENS(_xd_injector, LINE)(ALIAS, []() { \
|
||||
return Registry<TYPE>::New(NAME).release(); \
|
||||
}, __FILE__, STRINGIFY(LINE)); \
|
||||
static_assert(true, "") // enforce ; at EOL
|
||||
|
||||
|
||||
namespace factory {
|
||||
template<typename T, class... Args>
|
||||
class Registry {
|
||||
public:
|
||||
// Return 'true' if there is a class registered for `key` for
|
||||
// a constructor with signature (Args... args).
|
||||
//
|
||||
// This function can not be called from any static initializer
|
||||
// or it creates initializer order fiasco.
|
||||
static bool CanNew(const std::string &key, Args... args) {
|
||||
return GetEntry(key, args...).first;
|
||||
}
|
||||
|
||||
// If there is a class registered for `key` for a constructor
|
||||
// with signature (Args... args), instantiate an object of that class
|
||||
// passing args to the constructor. Returns a null pointer otherwise.
|
||||
//
|
||||
// This function can not be called from any static initializer
|
||||
// or it creates initializer order fiasco.
|
||||
static std::unique_ptr<T> New(const std::string &key, Args... args) {
|
||||
std::unique_ptr<T> result;
|
||||
auto entry = GetEntry(key, args...);
|
||||
if (entry.first) {
|
||||
result.reset(entry.second->function(args...));
|
||||
}
|
||||
registry_mutex_.unlock();
|
||||
return std::move(result);
|
||||
}
|
||||
|
||||
// Return the key under which class `C` is registered. The header
|
||||
// defining that class must be included by code calling this
|
||||
// function. If class is not registered, there will be a compile-
|
||||
// time failure.
|
||||
template<typename C>
|
||||
static const char *GetKeyFor() {
|
||||
return C::_xd_key(static_cast<const T *>(nullptr),
|
||||
std::function<void(Args...)>());
|
||||
}
|
||||
|
||||
// Returns the list of keys registered for the registry.
|
||||
// Keys corresponding to injectors (see below) are suffixed with
|
||||
// a star.
|
||||
//
|
||||
// This function can not be called from any static initializer
|
||||
// or it creates initializer order fiasco.
|
||||
static std::vector<std::string> GetKeys() {
|
||||
std::vector<std::string> keys;
|
||||
registry_mutex_.lock();
|
||||
for (const auto &iter: *GetRegistry()) {
|
||||
keys.emplace_back(iter.first);
|
||||
}
|
||||
for (const auto &iter: *GetInjectors()) {
|
||||
keys.emplace_back(iter.first + "*");
|
||||
}
|
||||
registry_mutex_.unlock();
|
||||
return keys;
|
||||
}
|
||||
|
||||
// Like GetKeys() function, but also returns the filename
|
||||
// and line number of the corresponding REGISTER() macros.
|
||||
// For injectors, the filename and line number are those
|
||||
// passed to the injector constructor.
|
||||
static std::vector<std::string> GetKeysWithLocations() {
|
||||
std::vector<std::string> keys;
|
||||
registry_mutex_.lock();
|
||||
for (const auto &iter: *GetRegistry()) {
|
||||
keys.emplace_back(std::string(iter.second.file) + ":" +
|
||||
std::string(iter.second.line) + ": " + iter.first);
|
||||
}
|
||||
for (const auto &iter: *GetInjectors()) {
|
||||
keys.emplace_back(std::string(iter.second.file) + ":" +
|
||||
std::string(iter.second.line) + ": " + iter.first +
|
||||
"*");
|
||||
}
|
||||
registry_mutex_.unlock();
|
||||
return keys;
|
||||
}
|
||||
|
||||
// Helper class which uses RAII to inject a factory which will be used
|
||||
// instead of any class registered with the same key, for any call
|
||||
// within the scope of the variable.
|
||||
// If there are two injectors in the same scope for the same key,
|
||||
// the last one takes precedence and cancels the first one, which will
|
||||
// never be active, even if the second one gets out of scope.
|
||||
struct Injector {
|
||||
const std::string &key;
|
||||
|
||||
Injector(const std::string &key,
|
||||
const std::function<T *(Args...)> &function,
|
||||
const char *file = "undefined", const char *line = "undefined")
|
||||
: key(key) {
|
||||
registry_mutex_.lock();
|
||||
const Entry entry = {file, line, function};
|
||||
GetInjectors()->insert(std::make_pair(key, entry));
|
||||
registry_mutex_.unlock();
|
||||
}
|
||||
|
||||
~Injector() {
|
||||
registry_mutex_.lock();
|
||||
GetInjectors()->erase(key);
|
||||
registry_mutex_.unlock();
|
||||
}
|
||||
};
|
||||
|
||||
//***************************************************************************
|
||||
// Implementation details that can't be made private because used in macros
|
||||
//***************************************************************************
|
||||
typedef std::function<T *(Args...)> function_t;
|
||||
|
||||
struct Registerer {
|
||||
Registerer(function_t function, const std::string &key, const char *file,
|
||||
const char *line) {
|
||||
const Entry entry = {file, line, function};
|
||||
registry_mutex_.lock();
|
||||
GetRegistry()->insert(std::make_pair(key, entry));
|
||||
registry_mutex_.unlock();
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
const char *const file;
|
||||
const char *const line;
|
||||
const function_t function;
|
||||
};
|
||||
typedef std::map<std::string, Entry> EntryMap;
|
||||
|
||||
// The registry and injectors are created on demand using static variables
|
||||
// inside a static method so that there is no order initialization fiasco.
|
||||
static EntryMap *GetRegistry() {
|
||||
static EntryMap registry;
|
||||
return ®istry;
|
||||
}
|
||||
|
||||
static EntryMap *GetInjectors() {
|
||||
static EntryMap injectors;
|
||||
return &injectors;
|
||||
};
|
||||
static std::mutex registry_mutex_;
|
||||
|
||||
static std::pair<bool, const Entry *> GetEntry(const std::string &key,
|
||||
Args... args) {
|
||||
registry_mutex_.lock();
|
||||
auto it = GetInjectors()->find(key);
|
||||
bool found = (it != GetInjectors()->end());
|
||||
if (!found) {
|
||||
it = GetRegistry()->find(key);
|
||||
found = (it != GetRegistry()->end());
|
||||
}
|
||||
registry_mutex_.unlock();
|
||||
return std::make_pair(found, &(it->second));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, class... Args>
|
||||
std::mutex Registry<T, Args...>::registry_mutex_;
|
||||
|
||||
//*****************************************************************************
|
||||
// Implementation details of REGISTER() macro.
|
||||
//
|
||||
// Creates uniquely named traits class and functions which forces the
|
||||
// instantiation of a TypeRegisterer class with a static member doing the
|
||||
// actual registration in the registry. Note that TypeRegisterer being a
|
||||
// template, there is no violation of the One Definition Rule. The use of
|
||||
// Trait class is way to pass back information from the class where the
|
||||
// macro is called, to the definition of TypeRegisterer static member.
|
||||
// This works only because the Trait functions do not reference any other
|
||||
// static variable, or it would create an initialization order fiasco.
|
||||
//*****************************************************************************
|
||||
template<typename Trait, typename base_type, typename derived_type,
|
||||
typename... Args>
|
||||
struct TypeRegisterer {
|
||||
static const typename Registry<base_type, Args...>::Registerer instance;
|
||||
};
|
||||
|
||||
template<typename Trait, typename base_type, typename derived_type,
|
||||
typename... Args>
|
||||
const typename Registry<base_type, Args...>::Registerer
|
||||
TypeRegisterer<Trait, base_type, derived_type, Args...>::instance(
|
||||
[](Args... args) { return new derived_type(args...); },
|
||||
Trait::key(), Trait::file(), Trait::line());
|
||||
|
||||
#define CONCAT_TOKENS(x, y) x##y
|
||||
#define STRINGIFY(x) #x
|
||||
|
||||
#define REGISTER_AT(LINE, KEY, TYPE, ARGS...) \
|
||||
friend class ::factory::Registry<TYPE, ##ARGS>; \
|
||||
struct CONCAT_TOKENS(_xd_Trait, LINE) { \
|
||||
static const char *key() { return KEY; } \
|
||||
static const char *file() { return __FILE__; } \
|
||||
static const char *line() { return STRINGIFY(LINE); } \
|
||||
}; \
|
||||
const void *CONCAT_TOKENS(_xd_unused, LINE)() const { \
|
||||
return &::factory::TypeRegisterer<CONCAT_TOKENS(_xd_Trait, LINE), TYPE, \
|
||||
std::decay<decltype(*this)>::type, \
|
||||
##ARGS>::instance; \
|
||||
} \
|
||||
static const char *_xd_key(const TYPE *, std::function<void(ARGS)>) { \
|
||||
return KEY; \
|
||||
} \
|
||||
static_assert(true, "") // enforce ; at EOL
|
||||
|
||||
}
|
@@ -73,7 +73,7 @@ namespace CaveGame::Core
|
||||
/// Reads a 8-byte float from the buffer, via reinterpreting from uint64_t, and advances the index by 8.
|
||||
double read_f64(Buffer& buffer);
|
||||
|
||||
void write_string(Buffer& buffer, const char* value, uint length);
|
||||
void write_string(Buffer& buffer, const char* value, unsigned int length);
|
||||
|
||||
void write_string(Buffer& buffer, std::string value);
|
||||
|
||||
|
39
Core/include/Core/Singleton.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
/// CaveGame - A procedural 2D platformer sandbox.
|
||||
/// Created by Josh O'Leary @ Redacted Software, 2020-2024
|
||||
/// Contact: josh@redacted.cc
|
||||
/// Contributors: william@redacted.cc maxi@redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file Singleton.hpp
|
||||
/// @desc An abstract class template that creates a Singleton.
|
||||
/// @edit 3/20/2025
|
||||
/// @auth Josh O'Leary
|
||||
|
||||
#pragma once
|
||||
|
||||
/// Template class for creating a Meyers' Singleton.
|
||||
/// One and only one instance of this class can exist, and is alive throughout the lifetime of the program.
|
||||
/// https://vlsiuniverse.blogspot.com/2016/04/meyers-singleton-pattern.html
|
||||
/// The instance is lazy-initialized, meaning that:
|
||||
/// if it is not a compile-time constant, and does not have a constructor,
|
||||
/// it is only created upon the first call to Instance().
|
||||
/// As per C++11, this is considered thread-safe out of the box.
|
||||
template <class T>
|
||||
class Singleton
|
||||
{
|
||||
public:
|
||||
static T& Instance()
|
||||
{
|
||||
// Since it's a static scoped variable, if the class has already been created, it won't be created again.
|
||||
// And it **is** thread safe (C++11 or later),
|
||||
static T instance;
|
||||
return instance;
|
||||
}
|
||||
protected:
|
||||
Singleton() = default;
|
||||
virtual ~Singleton() = default;
|
||||
Singleton(const Singleton&) = delete; // Copy constructor
|
||||
Singleton(Singleton&&) = delete; // Move constructor
|
||||
Singleton& operator=(const Singleton&) = delete; // Copy assign
|
||||
Singleton& operator=(Singleton&&) = delete; // Move assign
|
||||
};
|
59
Core/include/Core/Sprite.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
#include <J3ML/LinearAlgebra/Vector2.hpp>
|
||||
#include <JGL/JGL.h>
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
// TODO: Rect2D class that is distinct from AABB2D
|
||||
|
||||
struct Frame {
|
||||
Vector2 offset;
|
||||
Vector2 size;
|
||||
Vector2 origin;
|
||||
|
||||
//AABB2D quad;
|
||||
};
|
||||
|
||||
struct Rect2D {
|
||||
Vector2 position;
|
||||
Vector2 size;
|
||||
};
|
||||
|
||||
struct Keyframe {
|
||||
Vector2 transform_scale;
|
||||
Vector2 transform_offset;
|
||||
|
||||
|
||||
Frame frame;
|
||||
float time;
|
||||
};
|
||||
|
||||
struct Animation {
|
||||
std::vector<Keyframe> frames;
|
||||
};
|
||||
|
||||
|
||||
class Sprite {
|
||||
public:
|
||||
/// The default constructor does not initialize any members.
|
||||
Sprite() { }
|
||||
|
||||
/// Constructs a not-animated sprite from a source texture, which assumes.
|
||||
Sprite(JGL::Texture* source);
|
||||
Sprite(JGL::Texture* source, int width, int height);
|
||||
Sprite(JGL::Texture* source, Vector2i size);
|
||||
Sprite(JGL::Texture* source, int offset_x, int offset_y, int width, int height);
|
||||
Sprite(JGL::Texture* source, Vector2i offset, Vector2i size);
|
||||
void AddFrame(const std::string& frame_id, const Frame& frame);
|
||||
void SetCurrentFrame();
|
||||
void DrawFrame();
|
||||
void DrawCurrentFrame();
|
||||
|
||||
//void PlayAnimation(const KeyframeAnimation& anim);
|
||||
protected:
|
||||
JGL::Texture* source;
|
||||
std::vector<Frame> frames;
|
||||
std::vector<Animation> animations;
|
||||
private:
|
||||
};
|
||||
}
|
@@ -4,14 +4,73 @@
|
||||
#include <Color4.hpp>
|
||||
#include <Colors.hpp>
|
||||
#include <Core/Data.hpp>
|
||||
#include <Core/Registry.hpp>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <Core/Interfaces.hpp>
|
||||
#include <J3ML/LinearAlgebra/Vector2i.hpp>
|
||||
#include <J3ML/J3ML.hpp>
|
||||
|
||||
|
||||
namespace CaveGame::Core {
|
||||
using J3ML::LinearAlgebra::Vector2i;
|
||||
using TileID = u16;
|
||||
|
||||
struct Tile;
|
||||
|
||||
using ColorPallet = std::vector<Color4>;
|
||||
using TileTiccFunc = std::function<void(const Tile& data, ITileMap* world, int x, int y)>;
|
||||
|
||||
void SandGravTiccFunc(const Tile& data, ITileMap* world, int x, int y);
|
||||
void LiquidSettleTiccFunc(const Tile& data, ITileMap* world, int x, int y);
|
||||
void GrassRandomTiccFunc(const Tile& data, ITileMap* world, int x, int y);
|
||||
void GrassForcedTiccFunc(const Tile& data, ITileMap* world, int x, int y);
|
||||
void MossRandomTiccFunc(const Tile& data, ITileMap* world, int x, int y);
|
||||
void MossForcedTiccFunc(const Tile& data, ITileMap* world, int x, int y);
|
||||
void VineRandomTiccFunc(const Tile& data, ITileMap* world, int x, int y);
|
||||
void VineForcedTiccFunc(const Tile& data, ITileMap* world, int x, int y);
|
||||
|
||||
|
||||
static std::map<std::string, TileTiccFunc> ticc_funcs;
|
||||
|
||||
// TODO: Tile light filling algorithm.
|
||||
// TODO: Ambient light from surface / certain biomes.
|
||||
// TODO: Directional lights.
|
||||
// TODO: Entity lights.
|
||||
// TODO: Tinting when interact with certain tiles.
|
||||
// TODO: Research recursive flood-fill algorithm.
|
||||
|
||||
struct light {
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
uint8_t intensity;
|
||||
|
||||
};
|
||||
|
||||
// TODO: Implement params for tile color interaction
|
||||
// * Opacity
|
||||
// * Illumination
|
||||
// * What if we want to make tiles reactive, glow under UV, absorb light energy?
|
||||
|
||||
struct Tile {
|
||||
std::string mnemonic_id;
|
||||
std::string display_name;
|
||||
std::optional<TileID> assigned_numeric_id;
|
||||
TileID numeric_id;
|
||||
std::optional<ColorPallet> pallet;
|
||||
bool solid = true;
|
||||
bool collides = false;
|
||||
bool does_random_ticc = false;
|
||||
bool does_forced_ticc = false;
|
||||
Color4 color;
|
||||
TileTiccFunc forced_ticc_func;
|
||||
TileTiccFunc random_ticc_func;
|
||||
std::vector<std::string> tags;
|
||||
bool gravity;
|
||||
bool opaque = true;
|
||||
std::optional<Color3> illumination;
|
||||
|
||||
};
|
||||
/*
|
||||
*
|
||||
* Tile and TileTrait example structure
|
||||
@@ -85,18 +144,6 @@ namespace CaveGame::Core {
|
||||
};
|
||||
*/
|
||||
|
||||
enum TileFlags : uint16_t {
|
||||
SOLID = 0x0000,
|
||||
NONSOLID = !SOLID,
|
||||
/*
|
||||
NonSolid = 0x0000,
|
||||
Solid = 0x0002,
|
||||
RandomTick = 0x0004,
|
||||
ForcedTick = 0x0008,
|
||||
HasColorPallet = 0x0010,
|
||||
*/
|
||||
};
|
||||
|
||||
// Use this for space-efficiency and convert to Color3 for doing color manipulations.
|
||||
// Struct allocation is cheaper.
|
||||
struct Light3 {
|
||||
@@ -104,117 +151,42 @@ namespace CaveGame::Core {
|
||||
uint8_t b;
|
||||
uint8_t g;
|
||||
};
|
||||
/*std::unordered_map<std::string, TileTiccFunc> tile_functions {
|
||||
{"check-spread", [](const Tile& data, ITileMap* world, int x, int y){
|
||||
|
||||
class TestTile;
|
||||
}},
|
||||
{"check-decay", [](const Tile& data, ITileMap* world, int x, int y){}},
|
||||
{"check-decay-spread", [](const Tile& data, ITileMap* world, int x, int y){}},
|
||||
{"sand-ticc", [](const Tile& data, ITileMap* world, int x, int y){}},
|
||||
{"water-ticc", [](const Tile& data, ITileMap* world, int x, int y){}},
|
||||
{"liquid-ticc", [](const Tile& data, ITileMap* world, int x, int y)
|
||||
{
|
||||
|
||||
class TileTrait {
|
||||
public:
|
||||
virtual void Invoke(ITileMap* world, int x, int y) {};
|
||||
};
|
||||
}}
|
||||
};*/
|
||||
|
||||
// Tile trait code to be fully implemented
|
||||
/*//////////////////////////////////////////////////////////////////////////////*/
|
||||
class TestTile
|
||||
{
|
||||
public:
|
||||
TileID numeric_id = TileID::VOID;
|
||||
std::string mnemonic_id;
|
||||
public:
|
||||
Color4 base_color;
|
||||
std::vector<Color4> color_pallet;
|
||||
//TileFlags flags = NonSolid;
|
||||
public:
|
||||
TestTile(TileID id, std::string mnemonic);
|
||||
TileID NumericID() { return numeric_id; }
|
||||
std::string MnemonicID() { return mnemonic_id; }
|
||||
/*
|
||||
bool HasTrait(TileTrait* tr) {
|
||||
Tile* d;
|
||||
if (Tile* d = dynamic_cast<Tile*>(tr); d != nullptr) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
//TileFlags Flags();
|
||||
//bool HasFlag(TileFlags f);
|
||||
virtual void InvokeTraits(ITileMap* world, int x, int y) {};
|
||||
};
|
||||
|
||||
class Ticker : public TileTrait {};
|
||||
class RandomTicker : public Ticker {};
|
||||
class ForcedTicker : public Ticker {};
|
||||
|
||||
class OnCondition : public TileTrait
|
||||
class OnCondition
|
||||
{
|
||||
public:
|
||||
OnCondition();
|
||||
virtual bool Condition(ITileMap* world, int x, int y) { return true; }
|
||||
virtual void Perform(ITileMap* world, int x, int y) {}
|
||||
virtual void Invoke(ITileMap* world, int x, int y) override {
|
||||
virtual void Invoke(ITileMap* world, int x, int y) {
|
||||
if (Condition(world, x, y))
|
||||
Perform(world, x, y);
|
||||
}
|
||||
};
|
||||
|
||||
class DecayTo : public OnCondition
|
||||
{
|
||||
public:
|
||||
TestTile* dt;
|
||||
public:
|
||||
DecayTo(TestTile* dt) : OnCondition(), dt(dt) {};
|
||||
virtual bool Condition(ITileMap* world, int x, int y) override { return true; }
|
||||
virtual void Perform(ITileMap* world, int x, int y) override {
|
||||
world->SetTile(x, y, dt->NumericID());
|
||||
}
|
||||
};
|
||||
|
||||
class Spreader : public OnCondition
|
||||
{
|
||||
public:
|
||||
TestTile* st;
|
||||
public:
|
||||
Spreader(TestTile* st) : OnCondition(), st(st) {};
|
||||
virtual bool Condition(ITileMap* world, int x, int y) override {
|
||||
return (world->GetTile(x, y) == st->NumericID()) && world->HasAdjacentOrDiagonalAirBlock(x, y);
|
||||
}
|
||||
virtual void Perform(ITileMap* world, int x, int y) override {
|
||||
world->SetTile(x, y, st->NumericID());
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
class Suffocator : public TileTrait
|
||||
{
|
||||
public:
|
||||
Suffocator() : TileTrait() {};
|
||||
public:
|
||||
virtual void Invoke(ITileMap* world, int x, int y) override {
|
||||
if (!world->HasAdjacentOrDiagonalAirBlock(x, y))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
};*/
|
||||
/*//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
class TileGroup {};
|
||||
|
||||
class Tile;
|
||||
|
||||
///
|
||||
void RegisterTile(Tile* data);
|
||||
|
||||
Tile* GetByNumeric(TileID id);
|
||||
|
||||
Tile* GetByName(const std::string& name);
|
||||
|
||||
/// Tiles are instantiated as static members in the Tiles namespace.
|
||||
/// Chunks store tiles by their numeric ID, which can be used to retrieve the tile
|
||||
class Tile
|
||||
/*class Tile
|
||||
{
|
||||
protected:
|
||||
TileID numeric_id = TileID::VOID;
|
||||
std::string mnemonic_id;
|
||||
std::string display_name = "";
|
||||
public:
|
||||
Color4 base_color;
|
||||
std::vector<Color4> color_pallet;
|
||||
@@ -226,9 +198,16 @@ namespace CaveGame::Core {
|
||||
Tile();
|
||||
Tile(TileID id, const std::string& name, const Color4& color);
|
||||
Tile(TileID id, const std::string& name, const Color4& color, const std::vector<Color4>& pallet);
|
||||
virtual void Draw(int wx, int wy, int tx, int ty)
|
||||
{
|
||||
// TODO: Calling JGL requires being in the client code.
|
||||
// Maybe have Client/Tile.cpp implementation?
|
||||
//JGL::J2D::DrawPoint()
|
||||
}
|
||||
|
||||
[[nodiscard]] TileID NumericID() const;
|
||||
[[nodiscard]] std::string MnemonicID() const;
|
||||
std::string Name() const { return mnemonic_id; }
|
||||
std::string Name() const;
|
||||
virtual bool DoesRandomTicc() const { return false; }
|
||||
virtual bool DoesForcedTicc() const { return false;}
|
||||
virtual bool Solid() const { return false;}
|
||||
@@ -238,10 +217,10 @@ namespace CaveGame::Core {
|
||||
virtual bool ShouldSpread(ITileMap* world, int x, int y, TileID spreads_to) const;
|
||||
virtual bool ShouldSuffocate(ITileMap* world, int x, int y) const;
|
||||
virtual bool DecayCheck(ITileMap* world, TileState state, int x, int y, TileID decays_to);
|
||||
virtual bool SpreadCheck(ITileMap* world, TileState state, int x, int y, TileID spreads_to) {}
|
||||
};
|
||||
virtual bool SpreadCheck(ITileMap* world, TileState state, int x, int y, TileID spreads_to) { return false;}
|
||||
};*/
|
||||
|
||||
class LiquidTile : public Tile
|
||||
/*class LiquidTile : public Tile
|
||||
{
|
||||
public:
|
||||
LiquidTile();
|
||||
@@ -292,7 +271,7 @@ namespace CaveGame::Core {
|
||||
}
|
||||
world->SwapTile(x, y, rng_roll ? (x + 2) : (x - 2), y);
|
||||
return;
|
||||
}*/
|
||||
}
|
||||
|
||||
//if (world->GetTile(rng_roll ? (x + 1) : (x - 1), y) == TileID::AIR) {
|
||||
TileID rollright = rng_roll ? right : left;
|
||||
@@ -371,7 +350,7 @@ namespace CaveGame::Core {
|
||||
world->SetTile(x-1, y+1, numeric_id);
|
||||
world->SetTile(x, y, TileID::AIR);
|
||||
return;
|
||||
}*/
|
||||
}
|
||||
|
||||
//if (world->GetTile(x-1, y) == TileID::AIR && world->GetTile(x+1, y) == TileID::AIR) {
|
||||
//world->SetTile(x-1, y, numeric_id);
|
||||
@@ -504,7 +483,7 @@ namespace CaveGame::Core {
|
||||
#define TILE static const Tile
|
||||
|
||||
|
||||
namespace Tiles {
|
||||
/*namespace Tiles {
|
||||
using ID = Core::TileID;
|
||||
static const Tile Void {ID::VOID, "Void", Colors::Transparent};
|
||||
|
||||
@@ -538,6 +517,8 @@ namespace CaveGame::Core {
|
||||
static const Tile RedSandstone{ID::RED_SANDSTONE, "Red Sandstone", Colors::Reds::Firebrick};
|
||||
static const Tile BlackSandstone {ID::BLACK_SANDSTONE, "Black Sandstone", Colors::Black};
|
||||
|
||||
static const Tile GrayBrick {ID::STONE_BRICK, "Gray Brick", Colors::Grays::DimGray};
|
||||
|
||||
static const Tile Ash {ID::ASH, "Ash", Colors::Grays::DarkSlateGray};
|
||||
static const Tile Clay {ID::CLAY, "Clay", Colors::Browns::Brown, {{164, 42, 42}, {164, 38, 42}, {172, 42, 52}, {164, 58, 62}}};
|
||||
static const Tile Silt {ID::SILT, "Silt", Colors::Browns::SaddleBrown};
|
||||
@@ -607,7 +588,7 @@ namespace CaveGame::Core {
|
||||
|
||||
static const SaplingTile OakSapling;
|
||||
static const Tile OakLog {ID::OAK_LOG, "Oak Log", Colors::Browns::BurlyWood};
|
||||
static const Tile OakPlank;
|
||||
static const Tile OakPlank {ID::OAK_PLANK, "Oak Plank", Colors::Browns::BurlyWood};
|
||||
static const Tile OakPlatform;
|
||||
static const Tile OakLeaf;
|
||||
|
||||
@@ -656,6 +637,6 @@ namespace CaveGame::Core {
|
||||
|
||||
static const Tile Rope;
|
||||
|
||||
}
|
||||
}*/
|
||||
|
||||
}
|
||||
|
83
Core/include/Core/TileRegistry.hpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
#include "Singleton.hpp"
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include "Color4.hpp"
|
||||
#include "JJX/JSON.hpp"
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include <array>
|
||||
#include <Core/Tile.hpp>
|
||||
#include <J3ML/J3ML.hpp>
|
||||
#include <map>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
|
||||
namespace CaveGame::Core {
|
||||
//class ITileMap;
|
||||
|
||||
//using ColorPallet = std::vector<Color4>;
|
||||
//using TileTiccFunc = std::function<void(const Tile& data, ITileMap* world, int x, int y)>;
|
||||
|
||||
/*std::unordered_map<std::string, TileTiccFunc> tile_functions {
|
||||
{"check-spread", [](const Tile& data, ITileMap* world, int x, int y){
|
||||
|
||||
}},
|
||||
{"check-decay"},
|
||||
{"check-decay-spread"},
|
||||
{"sand-ticc"},
|
||||
{"water-ticc"},
|
||||
};*/
|
||||
|
||||
//using TileID = uint16_t;
|
||||
|
||||
/// A Singleton that stores tile data.
|
||||
class TileRegistry : public Singleton<TileRegistry> {
|
||||
|
||||
public:
|
||||
static constexpr int MaxTiles = 65536;
|
||||
|
||||
///
|
||||
void Register(Tile data);
|
||||
|
||||
const Tile &Get(const std::string &mnemonic);
|
||||
|
||||
const Tile &Get(u16 id);
|
||||
|
||||
// TODO: Why convert to vector?
|
||||
std::vector<Tile> GetTileList();
|
||||
|
||||
const std::array<Tile, MaxTiles>& GetTileArray();
|
||||
bool Exists(const std::string& mnemonic);
|
||||
bool Exists(u16 id);
|
||||
const Tile& GetByMnemonicID(const std::string& mnemonic);
|
||||
const Tile& GetByNumericID(uint16_t ID);
|
||||
|
||||
uint16_t MnemonicToNumeric(const std::string& mnemonic);
|
||||
std::string NumericToMnemonic(uint16_t ID);
|
||||
const Tile& operator[](const std::string& mnemonic);
|
||||
const Tile& operator[](u16 ID);
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
void Register(TileID ID, const Tile &data);
|
||||
|
||||
protected:
|
||||
|
||||
std::array<bool, MaxTiles> consumed_ids{};
|
||||
std::array<Tile, MaxTiles> registered_tiles{};
|
||||
std::unordered_map<std::string, u16> name_to_id_mapping{};
|
||||
std::unordered_map<u16, std::string> id_to_name_mapping{};
|
||||
u16 current_index = 0;
|
||||
private:
|
||||
};
|
||||
|
||||
/// Convenient access to the tile registry.
|
||||
static TileRegistry& Tiles() { return TileRegistry::Instance();}
|
||||
|
||||
bool LoadTileMetadata(const std::filesystem::path& path);
|
||||
|
||||
bool LoadTileMetadata();
|
||||
}
|
@@ -8,25 +8,13 @@
|
||||
#include <future>
|
||||
#include "ConcurrentQueue.hpp"
|
||||
#include "Entity.hpp"
|
||||
|
||||
#include "Core/v2i_hash.hpp"
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
/// Implements a naiive-AF hashing algorithm for Vector2 class
|
||||
/// So we can use it as unique keys in std::unordered_map
|
||||
template<>
|
||||
struct std::hash<Vector2>
|
||||
{
|
||||
std::size_t operator()(const Vector2& k) const
|
||||
{
|
||||
return Math::Pow(k.x, 3) + Math::Pow(k.y, 2);
|
||||
}
|
||||
};
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
|
||||
namespace CaveGame::Core {
|
||||
class WorldAsyncIOThreadManager
|
||||
{
|
||||
|
||||
@@ -35,22 +23,26 @@ namespace CaveGame::Core
|
||||
/// The world class manages and coordinates terrain generation, chunk file IO, thread synchronization, and entity logic.
|
||||
class World : public ITileMap {
|
||||
public:
|
||||
constexpr static float TileTiccRate = 48.f; // Ticcs per second.
|
||||
constexpr static uint RandomTileTickCoefficient = 100;
|
||||
Event<> OnNextDay;
|
||||
Event<> OnAutosave;
|
||||
|
||||
constexpr static unsigned int RandomTileTickCoefficient = 128;
|
||||
constexpr static float PeriodicAutosaveIntervalSeconds = 30.f;
|
||||
constexpr static float PhysicsTickrate = 100.f; // Desired physics Ticks per second.
|
||||
World() = default;
|
||||
~World() = default;
|
||||
|
||||
explicit World(const std::string &worldName, int seed = 0, bool overwrite = false);
|
||||
|
||||
Vector2 GetChunkCoordinatesAtCell(int x, int y) const {
|
||||
|
||||
Vector2i GetChunkCoordinatesAtCell(int x, int y) const {
|
||||
float chunkX = Math::Floor((float)x / Chunk::ChunkSize);
|
||||
float chunkY = Math::Floor((float)y / Chunk::ChunkSize);
|
||||
|
||||
return Vector2(chunkX, chunkY);
|
||||
return Vector2i(chunkX, chunkY);
|
||||
}
|
||||
|
||||
Vector2 GetTileCoordinatesAtCell(int x, int y) const {
|
||||
Vector2i GetTileCoordinatesAtCell(int x, int y) const {
|
||||
float tileX = Math::Mod(x, Chunk::ChunkSize);
|
||||
float tileY = Math::Mod(y, Chunk::ChunkSize);
|
||||
|
||||
@@ -59,24 +51,39 @@ namespace CaveGame::Core
|
||||
if (tileY < 0)
|
||||
tileY = Chunk::ChunkSize + tileY;
|
||||
|
||||
return Vector2(tileX, tileY);
|
||||
return Vector2i(tileX, tileY);
|
||||
}
|
||||
|
||||
/// Returns the chunk-coordinate for the given set of tile coordinates.
|
||||
/// @param x world-space tile row.
|
||||
/// @param y world-space tile column.
|
||||
//Vector2i GetChunkCoordinatesAtCell(int x, int y) const;
|
||||
|
||||
/// Returns the local-space tile coordinates [0,32] from the given set of world-space tile coordinates.
|
||||
/// @param x world-space tile row.
|
||||
/// @param y world-space tile column.
|
||||
//Vector2i GetTileCoordinatesAtCell(int x, int y) const;
|
||||
|
||||
/// Returns the numeric ID of the tile a the given world-space coordinates.
|
||||
TileID GetTile(int x, int y) const override;
|
||||
|
||||
/// Sets the tile stored at the given location.
|
||||
void SetTile(int x, int y, TileID t, bool flag_update = true) override;
|
||||
|
||||
/// Swaps two tiles at given locations in-place.
|
||||
void SwapTile(int source_x, int source_y, int dest_x, int dest_y, bool flag_update = true) override;
|
||||
|
||||
/// Returns whether tile simulation logic is enabled.
|
||||
bool GetTileSimulationEnabled() const;
|
||||
|
||||
/// Set whether tile simulation logic is enabled.
|
||||
void SetTileSimulationEnabled(bool enabled);
|
||||
|
||||
int GetSeed() const {
|
||||
return generator.GetSeed();
|
||||
}
|
||||
void SetSeed(int seed) {
|
||||
generator.SetSeed(seed);
|
||||
}
|
||||
/// Returns the World Seed.
|
||||
int GetSeed() const;
|
||||
|
||||
/// Sets the World Seed.
|
||||
void SetSeed(int seed);
|
||||
|
||||
|
||||
TileState GetTileState(int x, int y) const override { return 0; /* TODO: Implement tile state field */ }
|
||||
@@ -90,63 +97,78 @@ namespace CaveGame::Core
|
||||
// TODO: Doesn't really belong here.
|
||||
Vector2 ToUnitDirection(float rotation);
|
||||
|
||||
Chunk* GetChunkAtCell(const Vector2 &cell);
|
||||
Chunk* GetChunkAtCell(const Vector2i &cell);
|
||||
|
||||
std::unordered_map<Vector2, Chunk*> GetChunkList();
|
||||
std::unordered_map<Vector2i, Chunk*> GetChunkList();
|
||||
|
||||
void DoRandomTileTicks();
|
||||
|
||||
void DoForcedTileTicks();
|
||||
void DoRandomTileTick(const Vector2i& coords, Chunk* chunk);
|
||||
|
||||
void DoRandomTileTick(const Vector2& coords, Chunk* chunk);
|
||||
void DoForcedTileTick(const Vector2i& coords, Chunk* chunk);
|
||||
|
||||
void DoForcedTileTick(const Vector2& coords, Chunk* chunk);
|
||||
void DoTileTiccs(float elapsed);
|
||||
|
||||
virtual void Update(float elapsed);
|
||||
|
||||
virtual void RefreshAll();
|
||||
|
||||
virtual void AutoSaveAsync() { }
|
||||
|
||||
virtual void AutoSave();
|
||||
void Save();
|
||||
void SaveAsync()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// Gracefully saves all game state and closes the play session.
|
||||
virtual void SaveAndExit();
|
||||
|
||||
/// Returns whether or not the world currently has a chunk loaded at the specified chunk coordinates.
|
||||
/// @note Valid Chunk coordinates are integers only.
|
||||
bool HasChunkAtCell(const Vector2 &cell) const;
|
||||
bool HasChunkAtCell(const Vector2i &cell) const;
|
||||
|
||||
bool HasChunkOnFile(const Vector2 &cell) const;
|
||||
bool HasChunkOnFile(const Vector2i &cell) const;
|
||||
|
||||
bool AwaitingChunkAtCell(const Vector2 &cell) const;
|
||||
bool AwaitingChunkAtCell(const Vector2i &cell) const;
|
||||
|
||||
void ChunkServerThread();
|
||||
|
||||
std::filesystem::path GetWorldRootPath() const;
|
||||
|
||||
std::filesystem::path GetChunkFullPath(const Vector2 &cell) const;
|
||||
std::filesystem::path GetChunkFullPath(const Vector2i &cell) const;
|
||||
|
||||
|
||||
/// Adds the entity pointer to the world, via std::move, this may leave the original entity pointer empty.
|
||||
void AddEntity(Entity* e);
|
||||
|
||||
std::vector<Entity*> GetEntities() { return entities;}
|
||||
|
||||
|
||||
/// Returns the number of extant entities in this world right now.
|
||||
unsigned int GetCurrentEntityCount() const;
|
||||
|
||||
/// Marks that the chunk at the given cell coordinates is needed.
|
||||
/// This will be picked up by the generator thread, and either generated, or loaded from disk.
|
||||
void RequestChunk(const Vector2& cell);
|
||||
void DropChunk(const Vector2& cell);
|
||||
void RequestChunk(const Vector2i& cell);
|
||||
void DropChunk(const Vector2i& cell);
|
||||
|
||||
void SetTileTiccRate(float ticcrate);
|
||||
float GetTileTiccRate() const { return tile_ticc_frequency; }
|
||||
|
||||
protected:
|
||||
void SaveChunkToFile(const Vector2 &cell, Chunk* chunk);
|
||||
bool ValidCoords(const Vector2 &cell) const;
|
||||
void SaveChunkToFile(const Vector2i &cell, Chunk* chunk);
|
||||
virtual void AutoSaveAsync() {
|
||||
OnAutosave.Invoke();
|
||||
Logs::Info("Autosaving...");
|
||||
|
||||
Save();
|
||||
Logs::Info("Autosave successful!");
|
||||
}
|
||||
virtual void AutoSave();
|
||||
|
||||
protected:
|
||||
std::vector<Entity*> entities;
|
||||
const std::filesystem::path worlds {"worlds"};
|
||||
std::unordered_map<Vector2, Chunk*> loaded_chunks;
|
||||
std::unordered_map<Vector2i, Chunk*> loaded_chunks;
|
||||
Generator generator;
|
||||
float time_of_day;
|
||||
std::string world_name;
|
||||
@@ -155,15 +177,21 @@ namespace CaveGame::Core
|
||||
bool simulate_tiles = true;
|
||||
std::thread chunk_thread;
|
||||
std::atomic<bool> run_chunk_thread = true;
|
||||
ConcurrentQueue<Vector2> RequestedChunks;
|
||||
std::vector<Vector2> chunks_in_waiting;
|
||||
ConcurrentQueue<Vector2i> RequestedChunks;
|
||||
std::vector<Vector2i> chunks_in_waiting;
|
||||
ConcurrentQueue<Core::Chunk*> ServedChunks;
|
||||
|
||||
float tile_ticc_counter;
|
||||
float tile_ticc_counter = 0;
|
||||
float physics_ticc = 0;
|
||||
|
||||
// Ticcs per second.
|
||||
float tile_ticc_frequency = 24.f;
|
||||
|
||||
|
||||
bool save_in_progress = false;
|
||||
private:
|
||||
|
||||
|
||||
|
||||
void SimulateTiles(float elapsed);
|
||||
};
|
||||
}
|
14
Core/include/Core/v2i_hash.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include "J3ML/LinearAlgebra/Vector2i.hpp"
|
||||
|
||||
using namespace J3ML::LinearAlgebra;
|
||||
template<>
|
||||
struct std::hash<Vector2i>
|
||||
{
|
||||
std::size_t operator()(const Vector2i& k) const
|
||||
{
|
||||
// Read 2x int32_t into one int64_t and return std::hash<int64_t> to prevent collisions.
|
||||
// This doesn't really gain any performance until you zoom out a lot.
|
||||
return std::hash<int64_t>()(((int64_t) k.x << 32) | ((int64_t) k.y));
|
||||
}
|
||||
};
|
@@ -1,6 +0,0 @@
|
||||
#include <Core/Animator2D.hpp>
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
|
||||
}
|
@@ -1,31 +0,0 @@
|
||||
#include <Core/ArgsParser.hpp>
|
||||
|
||||
ArgsParser::ArgsParser(int argc, char **argv) {
|
||||
executable = std::string(argv[0]);
|
||||
tokens = std::vector<std::string>(argv + 1, argv + argc);
|
||||
for (auto& token : tokens)
|
||||
full_args.append(std::string(token).append(" "));
|
||||
}
|
||||
|
||||
ArgsParser::ArgsParser(std::vector<std::string> argv) {
|
||||
executable = argv[0];
|
||||
full_args = string_build(argv);
|
||||
tokens = {argv.begin() + 1, argv.end()};
|
||||
}
|
||||
|
||||
ArgsParser::ArgsParser(const std::string &args_str) {
|
||||
full_args = args_str;
|
||||
std::vector<std::string> temp_tokens = string_split(full_args, ' ');
|
||||
tokens = {temp_tokens.begin() + 1, temp_tokens.end()};
|
||||
}
|
||||
|
||||
ArgsParser::ArgsParser(const std::string &exec, const std::vector<std::string> &tok) {
|
||||
executable = exec;
|
||||
tokens = tok;
|
||||
for (auto& token : tokens)
|
||||
full_args.append(std::string(token).append(" "));
|
||||
}
|
||||
|
||||
bool ArgsParser::FlagPresent(const std::string &flag) const {
|
||||
return std::ranges::find(tokens, flag) != tokens.end();
|
||||
}
|
@@ -3,6 +3,7 @@
|
||||
#include "Core/Simplex.hpp"
|
||||
#include "Core/Data.hpp"
|
||||
#include <iostream>
|
||||
#include <Core/TileRegistry.hpp>
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
@@ -10,15 +11,19 @@ namespace CaveGame::Core
|
||||
if (!(0 <= x && x < ChunkSize && 0 <= y && y < ChunkSize))
|
||||
assert("Out of bounds!");
|
||||
|
||||
if (tiles[x][y] != t)
|
||||
{
|
||||
if (tiles[x][y] != t) {
|
||||
const Tile& old_tile = Tiles().GetByNumericID(tiles[x][y]);
|
||||
const Tile& new_tile = Tiles().GetByNumericID(t);
|
||||
|
||||
if (old_tile.does_random_ticc)
|
||||
tiles_with_random_tick_count--;
|
||||
if (new_tile.does_random_ticc)
|
||||
tiles_with_random_tick_count++;
|
||||
|
||||
tiles[x][y] = t;
|
||||
touched = true;
|
||||
|
||||
if (trigger_tile_updates)
|
||||
{
|
||||
SetTileUpdateFlag(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,31 +32,38 @@ namespace CaveGame::Core
|
||||
|
||||
bool Chunk::GetTileUpdateFlag(int x, int y) const
|
||||
{
|
||||
return tagged_for_update[x][y];
|
||||
size_t index = y * ChunkSize + x;
|
||||
return tagged_for_update.test(index);
|
||||
}
|
||||
|
||||
bool Chunk::GetTileUpdateBufferFlag(int x, int y) const {
|
||||
return update_buffer[x][y];
|
||||
size_t index = y * ChunkSize + x;
|
||||
return update_buffer.test(index);
|
||||
}
|
||||
|
||||
void Chunk::SetTileUpdateBufferFlag(int x, int y, bool flag) {
|
||||
update_buffer[x][y] = flag;
|
||||
size_t index = y * ChunkSize + x;
|
||||
|
||||
if (flag)
|
||||
update_buffer.set(index);
|
||||
else
|
||||
update_buffer.reset(index);
|
||||
}
|
||||
|
||||
void Chunk::SetTileUpdateFlag(int x, int y, bool flag)
|
||||
{
|
||||
tagged_for_update[x][y] = flag;
|
||||
size_t index = y * ChunkSize + x;
|
||||
|
||||
if (flag)
|
||||
tagged_for_update.set(index),
|
||||
has_update = true;
|
||||
else
|
||||
tagged_for_update.reset(index);
|
||||
}
|
||||
|
||||
TileID Chunk::GetTile(int x, int y) const {
|
||||
if (!(0 <= x && x < ChunkSize || 0 <= y && y < ChunkSize))
|
||||
throw std::runtime_error("Out of bounds!");
|
||||
return tiles[x][y];
|
||||
}
|
||||
Chunk::Chunk(const Vector2i& cell_coords) : cell(cell_coords), touched(false), update_buffer{}, tagged_for_update{}, tiles{} {}
|
||||
|
||||
Chunk::Chunk(const Vector2& cell_coords) : cell(cell_coords), touched(false), update_buffer{}, tagged_for_update{}, tiles{} {}
|
||||
|
||||
Chunk::Chunk(const Vector2& cell_coords, const std::filesystem::path& file): Chunk(cell_coords)
|
||||
Chunk::Chunk(const Vector2i& cell_coords, const std::filesystem::path& file): Chunk(cell_coords)
|
||||
{
|
||||
std::ifstream inp;
|
||||
|
||||
@@ -60,14 +72,16 @@ namespace CaveGame::Core
|
||||
int length = inp.tellg();
|
||||
inp.seekg(0, std::ios::beg);
|
||||
|
||||
char buffer[length];
|
||||
char* buffer = new char[length];
|
||||
inp.read(buffer, length);
|
||||
inp.close();
|
||||
|
||||
SetData(buffer);
|
||||
|
||||
delete buffer;
|
||||
}
|
||||
|
||||
Vector2 Chunk::GetChunkRealCoordinates() const {
|
||||
Vector2i Chunk::GetChunkRealCoordinates() const {
|
||||
return GetChunkCell()*Core::Chunk::ChunkSize;
|
||||
}
|
||||
|
||||
@@ -83,8 +97,9 @@ namespace CaveGame::Core
|
||||
}
|
||||
|
||||
void Chunk::SwapTileUpdateBuffers() {
|
||||
memcpy(update_buffer, tagged_for_update, sizeof(update_buffer));
|
||||
std::memset(tagged_for_update, 0, sizeof(tagged_for_update));
|
||||
memcpy(&update_buffer, &tagged_for_update, sizeof(tagged_for_update));
|
||||
tagged_for_update.reset();
|
||||
has_update = false;
|
||||
}
|
||||
|
||||
bool Chunk::FirstPassCompleted() const { return first_pass_complete;}
|
||||
@@ -95,6 +110,6 @@ namespace CaveGame::Core
|
||||
|
||||
void Chunk::SetSecondPassCompleted(bool complete) { second_pass_complete = complete; }
|
||||
|
||||
Vector2 Chunk::GetChunkCell() const { return cell;}
|
||||
Vector2i Chunk::GetChunkCell() const { return cell;}
|
||||
|
||||
}
|
||||
|
@@ -11,3 +11,9 @@ int CaveGame::Core::Entity::MaxHealth() const { return max_health; }
|
||||
Vector2 CaveGame::Core::Entity::Position() const { return position;}
|
||||
|
||||
Vector2 CaveGame::Core::Entity::Size() const { return bounding_box;}
|
||||
|
||||
void CaveGame::Core::Entity::SetNoclip(bool value) {
|
||||
this->noclip = value;
|
||||
}
|
||||
|
||||
bool CaveGame::Core::Entity::IsNoclip() const { return this->noclip; }
|
||||
|
@@ -1,12 +1,19 @@
|
||||
#include <Core/Explosion.hpp>
|
||||
|
||||
namespace CaveGame::Core {
|
||||
|
||||
void Explosion::SetFrame(const AABB2D& frame_quad)
|
||||
{
|
||||
this->quad = frame_quad;
|
||||
}
|
||||
|
||||
AABB2D Explosion::CurrentFrame() const { return quad;}
|
||||
|
||||
|
||||
bool Explosion::HasDetonated() const { return detonated;}
|
||||
|
||||
void Explosion::Update(float elapsed) {
|
||||
|
||||
|
||||
|
||||
//Entity::Update(elapsed);
|
||||
|
||||
if (fuse > 0.f)
|
||||
@@ -19,5 +26,27 @@ namespace CaveGame::Core {
|
||||
if (HasDetonated())
|
||||
anim_timer += elapsed;
|
||||
|
||||
if (HasDetonated()) {
|
||||
if (anim_timer < 1.25f) {
|
||||
if (anim_timer > (4.f / 5.f)) {
|
||||
SetFrame(SP_EXPLOSION4);
|
||||
//JGL::J2D::DrawPartialSprite(tex, position-(Vector2(draw_radius)), SP_EXPLOSION4.minPoint, SP_EXPLOSION4.maxPoint, rotation, {0.5f,0.5f}, {draw_radius/16.f, draw_radius/16.f});
|
||||
} else if (anim_timer > (3.f / 5.f)) {
|
||||
SetFrame(SP_EXPLOSION3);
|
||||
//JGL::J2D::DrawPartialSprite(tex, position-(Vector2(draw_radius)), SP_EXPLOSION3.minPoint, SP_EXPLOSION3.maxPoint, rotation, {0.5f, 0.5f}, {draw_radius/16.f, draw_radius/16.f});
|
||||
} else if (anim_timer > (2.f / 5.f)) {
|
||||
SetFrame(SP_EXPLOSION2);
|
||||
//JGL::J2D::DrawPartialSprite(tex, position-(Vector2(draw_radius)), SP_EXPLOSION2.minPoint, SP_EXPLOSION2.maxPoint, rotation, {0.5f, 0.5f}, {draw_radius/16.f, draw_radius/16.f});
|
||||
} else if (anim_timer > (1.f / 5.f)) {
|
||||
SetFrame(SP_EXPLOSION1);
|
||||
//JGL::J2D::DrawPartialSprite(tex, position-(Vector2(draw_radius)), SP_EXPLOSION1.minPoint, SP_EXPLOSION1.maxPoint, rotation, {0.5f, 0.5f}, {draw_radius/16.f, draw_radius/16.f});
|
||||
} else {
|
||||
SetFrame(SP_EXPLOSION0);
|
||||
//JGL::J2D::DrawPartialSprite(tex, position-(Vector2(draw_radius)), SP_EXPLOSION0.minPoint, SP_EXPLOSION0.maxPoint, rotation, {0.5f, 0.5f}, {draw_radius/16.f, draw_radius/16.f});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
#include <Core/Generator.hpp>
|
||||
#include <Core/Data.hpp>
|
||||
#include <Core/TileRegistry.hpp>
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
@@ -39,12 +40,12 @@ namespace CaveGame::Core
|
||||
float rand = GetPrecomputedWhiteNoise2D(wx, wy)*(TopSoilDepth/2.f);
|
||||
|
||||
if (wy > 1000)
|
||||
return TileID::STONE;
|
||||
return Tiles()["stone"].numeric_id;
|
||||
|
||||
if (depth < 0) { return TileID::AIR; }
|
||||
else if (depth < 2.5f) { return TileID::GRASS; }
|
||||
else if (depth < TopSoilDepth-rand) { return TileID::DIRT; }
|
||||
else { return TileID::STONE; }
|
||||
if (depth < 0) { return Tiles().MnemonicToNumeric("air"); }
|
||||
else if (depth < 2.5f) { return Tiles().MnemonicToNumeric("grass");; }
|
||||
else if (depth < TopSoilDepth-rand) { return Tiles().MnemonicToNumeric("dirt"); }
|
||||
else { return Tiles().MnemonicToNumeric("stone");; }
|
||||
}
|
||||
|
||||
float Generator::Perlin(int wx, int wy, float hScale, float vScale, float offset, float outputScale) {
|
||||
@@ -122,7 +123,7 @@ namespace CaveGame::Core
|
||||
}
|
||||
|
||||
if (cave_erosion > -CaveErosionRange && CaveErosionRange > cave_erosion) {
|
||||
return TileID::AIR;
|
||||
return Tiles()["air"].numeric_id;
|
||||
}
|
||||
|
||||
|
||||
@@ -136,54 +137,54 @@ namespace CaveGame::Core
|
||||
// Yes, sometimes we re-use a pass to generate another tile entirely. Why?
|
||||
// We don't need the full parameter space for clay generation, as it's rarer, and it gives
|
||||
// more variety to dirt blob generation, for example.
|
||||
if (clay_pass > 0.85f) { return TileID::CLAY; }
|
||||
if (clay_pass < -0.75f) { return TileID::DIRT; }
|
||||
if (clay_pass > 0.85f) { return Tiles()["clay"].numeric_id; }
|
||||
if (clay_pass < -0.75f) { return Tiles()["dirt"].numeric_id; }
|
||||
|
||||
// Chunks of silt and dirt.
|
||||
float silt_pass = ComputeOre(wx, wy, SiltVeinHiPassScale, SiltVeinLoPassScale, SiltVeinHiPassOffset, SiltVeinLoPassOffset,
|
||||
SiltVeinHiPassOutputScale, SiltVeinLoPassOutputScale, SiltVeinNoise, SiltVeinRampFactor);
|
||||
|
||||
if (silt_pass > 0.85f) { return TileID::SILT; }
|
||||
if (silt_pass < -0.75f) { return TileID::DIRT; }
|
||||
if (silt_pass > 0.85f) { return Tiles()["gravel"].numeric_id; }
|
||||
if (silt_pass < -0.85f) { return Tiles()["gravel"].numeric_id; }
|
||||
|
||||
// Chunks of stone.
|
||||
float stone_pass = ComputeOre(wx, wy, StoneVeinHiPassScale, StoneVeinLoPassScale, StoneVeinHiPassOffset, StoneVeinLoPassOffset,
|
||||
StoneVeinHiPassOutputScale, StoneVeinLoPassOutputScale, StoneVeinNoise, StoneVeinRampFactor);
|
||||
|
||||
// Chunks of stone
|
||||
if (stone_pass > 0.7f) { return TileID::STONE; }
|
||||
if (stone_pass < -0.7f) { return TileID::STONE; }
|
||||
if (stone_pass > 0.7f) { return Tiles()["stone"].numeric_id; }
|
||||
if (stone_pass < -0.7f) { return Tiles()["stone"].numeric_id; }
|
||||
|
||||
// Chunks of dirt
|
||||
float dirt_pass = ComputeOre(wx, wy, DirtVeinHiPassScale, DirtVeinLoPassScale, DirtVeinHiPassOffset, DirtVeinLoPassOffset,
|
||||
DirtVeinHiPassOutputScale, DirtVeinLoPassOutputScale, DirtVeinNoise, DirtVeinRampFactor);
|
||||
|
||||
if (-0.65 > dirt_pass && dirt_pass < 0.65f) { return TileID::DIRT; }
|
||||
if (-0.65 > dirt_pass && dirt_pass < 0.65f) { return Tiles()["dirt"].numeric_id; }
|
||||
|
||||
// Coal veins
|
||||
float coal_pass = ComputeOre(wx, wy, CoalVeinHiPassScale, CoalVeinLoPassScale, CoalVeinHiPassOffset, CoalVeinLoPassOffset,
|
||||
CoalVeinHiPassOutputScale, CoalVeinLoPassOutputScale, CoalVeinNoise, CoalVeinRampFactor);
|
||||
|
||||
if (coal_pass > 0.75f) { return TileID::COAL_ORE; }
|
||||
if (coal_pass < -0.75f) { return TileID::COAL_ORE;}
|
||||
if (coal_pass > 0.75f) { return Tiles()["coal-ore"].numeric_id; }
|
||||
if (coal_pass < -0.75f) { return Tiles()["coal-ore"].numeric_id;}
|
||||
|
||||
// Copper ore veins
|
||||
float copper_pass = ComputeOre(wx, wy, CopperVeinHiPassScale, CopperVeinLoPassScale, CopperVeinHiPassOffset, CopperVeinLoPassOffset,
|
||||
CopperVeinHiPassOutputScale, CopperVeinLoPassOutputScale, CopperVeinNoise, CopperVeinRampFactor);
|
||||
|
||||
if (copper_pass > 0.8f) { return TileID::COPPER_ORE; }
|
||||
if (copper_pass > 0.8f) { return Tiles()["copper-ore"].numeric_id; }
|
||||
|
||||
// Tin ore veins
|
||||
float tin_pass = ComputeOre(wx, wy, TinVeinHiPassScale, TinVeinLoPassScale, TinVeinHiPassOffset, TinVeinLoPassOffset,
|
||||
TinVeinHiPassOutputScale, TinVeinLoPassOutputScale, TinVeinNoise, TinVeinRampFactor);
|
||||
|
||||
if (tin_pass > 0.85f) { return TileID::TIN_ORE; }
|
||||
if (tin_pass > 0.85f) { return Tiles()["tin-ore"].numeric_id; }
|
||||
|
||||
// Iron ore veins
|
||||
float iron_pass = ComputeOre(wx, wy, IronVeinHiPassScale, IronVeinLoPassScale, IronVeinHiPassOffset, IronVeinLoPassOffset,
|
||||
IronVeinHiPassOutputScale, IronVeinLoPassOutputScale, IronVeinNoise, IronVeinRampFactor);
|
||||
|
||||
if (iron_pass > 0.90f) { return TileID::IRON_ORE;}
|
||||
if (iron_pass > 0.90f) { return Tiles()["iron-ore"].numeric_id;}
|
||||
|
||||
if (depth > 1000) {
|
||||
// Lead ore veins
|
||||
@@ -236,7 +237,7 @@ namespace CaveGame::Core
|
||||
}
|
||||
|
||||
void Generator::FirstPass(CaveGame::Core::Chunk* chunk) {
|
||||
Vector2 real_coords = chunk->GetChunkRealCoordinates();
|
||||
Vector2i real_coords = chunk->GetChunkRealCoordinates();
|
||||
|
||||
for (int x = 0; x < Chunk::ChunkSize; x++) {
|
||||
for (int y = 0; y < Chunk::ChunkSize; y++) {
|
||||
@@ -252,7 +253,7 @@ namespace CaveGame::Core
|
||||
}
|
||||
|
||||
void Generator::SecondPass(ITileMap *world, Chunk *chunk) {
|
||||
Vector2 real_coords = chunk->GetChunkRealCoordinates();
|
||||
Vector2i real_coords = chunk->GetChunkRealCoordinates();
|
||||
|
||||
for (int x = 0; x < Chunk::ChunkSize; x++) {
|
||||
for (int y = 0; y < Chunk::ChunkSize; y++) {
|
||||
@@ -271,16 +272,16 @@ namespace CaveGame::Core
|
||||
continue;
|
||||
|
||||
|
||||
if (chunk->GetTile(x, y) != TileID::GRASS)
|
||||
if (chunk->GetTile(x, y) != Tiles()["grass"].numeric_id) {}
|
||||
continue;
|
||||
|
||||
if (world->GetTile(wx, wy-1) != TileID::AIR || world->GetTile(wx, wy-2) != TileID::AIR || world->GetTile(wx, wy-3) != TileID::AIR)
|
||||
if (world->GetTile(wx, wy-1) != Tiles()["air"].numeric_id || world->GetTile(wx, wy-2) != Tiles()["air"].numeric_id || world->GetTile(wx, wy-3) != Tiles()["air"].numeric_id )
|
||||
continue;
|
||||
|
||||
int treeHeight = 5 + (GetPrecomputedWhiteNoise1D(wx) * 12);
|
||||
|
||||
for (int tree_y = 0; tree_y < treeHeight; tree_y++) {
|
||||
world->SetTile(wx, wy-tree_y, TileID::OAK_LOG);
|
||||
world->SetTile(wx, wy-tree_y, Tiles()["oak-log"].numeric_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,7 +289,7 @@ namespace CaveGame::Core
|
||||
chunk->SetSecondPassCompleted(true);
|
||||
}
|
||||
|
||||
uint Generator::ColorMap(int range, int wx, int wy) {
|
||||
unsigned int Generator::ColorMap(int range, int wx, int wy) {
|
||||
wx = ModInt(wx, PrecomputedWhiteNoiseResolution);
|
||||
wy = ModInt(wy, PrecomputedWhiteNoiseResolution);
|
||||
float norm = precomputed_white_noise_2D[wx][wy];
|
||||
|
@@ -1,13 +1,19 @@
|
||||
#include <Core/Interfaces.hpp>
|
||||
#include <Core/TileRegistry.hpp>
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
bool ITileMap::IsNonSolidTile(int x, int y) const
|
||||
{
|
||||
TileID tile = GetTile(x, y);
|
||||
if (tile == TileID::AIR || tile == TileID::VINE || tile == TileID::WATER)
|
||||
|
||||
//if (tile == TileID::AIR || tile == TileID::VINE || tile == TileID::WATER)
|
||||
//return true;
|
||||
|
||||
if (tile == 0 || !Tiles().GetByNumericID(tile).solid)
|
||||
return true;
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
6
Core/src/Core/Item.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#include <Core/Item.hpp>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
namespace CaveGame::Core {
|
||||
}
|
69
Core/src/Core/ItemRegistry.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#include <Core/ItemRegistry.hpp>
|
||||
#include <Core/Macros.hpp>
|
||||
#include <filesystem>
|
||||
#include <JJX/JSON.hpp>
|
||||
#include <Core/JsonConversions.hpp>
|
||||
#include "Core/Loggers.hpp"
|
||||
|
||||
namespace CaveGame::Core
|
||||
{
|
||||
|
||||
bool LoadItemMetadata(const std::filesystem::path &path) {
|
||||
using namespace JJX;
|
||||
std::string content = read_file(path);
|
||||
|
||||
auto [data, parse_error] = json::parse(content);
|
||||
|
||||
if (data.type != json::value_type::array) {
|
||||
// TODO: Error
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto& entry : data.as_array()) {
|
||||
auto entry_obj = entry.as_object();
|
||||
Core::Item item;
|
||||
|
||||
item.mnemonic = entry_obj["mnemonic-id"].string.value_or("");
|
||||
item.display_name = entry_obj["display-name"].string.value_or("");
|
||||
|
||||
if (entry_obj.contains("tags"))
|
||||
item.tags = JsonConversions::parse_string_list(entry_obj["tags"]);
|
||||
|
||||
|
||||
// TODO: Support multiple overlaid "Sprites" with assigned colors.
|
||||
// TODO: Support animated sprites.
|
||||
std::string sprite_name;
|
||||
|
||||
Items().Register(item);
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadItemMetadata() {
|
||||
if (!LoadItemMetadata("assets/data/items.json")) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ItemRegistry::Register(const Item& data) {
|
||||
|
||||
Logs::Info(std::format("\tRegister Item: id:{} display_name:{} stack:{} value:{}", data.mnemonic, data.display_name, data.max_stack, data.value));
|
||||
|
||||
registered_items.emplace(data.mnemonic, data);
|
||||
}
|
||||
|
||||
bool ItemRegistry::Exists(const std::string &name) {
|
||||
return registered_items.contains(name);
|
||||
}
|
||||
|
||||
const Item &ItemRegistry::Get(const std::string &name) {
|
||||
return registered_items.at(name);
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, Item> ItemRegistry::GetItemMap() { return registered_items;}
|
||||
|
||||
const Item &ItemRegistry::operator[](const std::string &name) { return Get(name);}
|
||||
|
||||
}
|
46
Core/src/Core/JsonConversions.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include <Core/JsonConversions.hpp>
|
||||
|
||||
Color4 JsonConversions::parse_color(const JJX::json::value &v) {
|
||||
|
||||
if (v.type == json::value_type::string)
|
||||
return Color4::FromHex(v.string.value());
|
||||
else if (v.type == json::value_type::array) {
|
||||
auto color_array = v.as_array();
|
||||
int r = color_array[0].number.value();
|
||||
int g = color_array[1].number.value();
|
||||
int b = color_array[2].number.value();
|
||||
int a = 255;
|
||||
if (color_array.value::array.value().size() == 4)
|
||||
a = color_array[3].number.value();
|
||||
return Color4(r, g, b, a);
|
||||
|
||||
} else if (v.type == json::value_type::object) {
|
||||
auto color_obj = v.operator std::map<std::string, json::value>();
|
||||
int r = color_obj["r"].number.value();
|
||||
int g = color_obj["g"].number.value();
|
||||
int b = color_obj["b"].number.value();
|
||||
|
||||
int a = 255;
|
||||
|
||||
if (color_obj.contains("a"))
|
||||
a = color_obj["a"].number.value();
|
||||
|
||||
return Color4(r, g, b, a);
|
||||
|
||||
}
|
||||
return Colors::Transparent;
|
||||
}
|
||||
|
||||
std::vector<std::string> JsonConversions::parse_string_list(const JJX::json::value &v) {
|
||||
// TODO: Log an error if the json value is an invalid type.
|
||||
if (v.type == json::value_type::string) {
|
||||
return {v.string.value()};
|
||||
}else if (v.type == json::value_type::array) {
|
||||
std::vector<std::string> retval;
|
||||
|
||||
for (auto& token : v.array.value())
|
||||
retval.push_back(token.string.value());
|
||||
return retval;
|
||||
}
|
||||
return {};
|
||||
}
|