134 Commits

Author SHA1 Message Date
791f6bf4c3 Bunch of edits. 2025-06-09 20:50:23 -05:00
54eb43044d Finalize Pause menu and setup of settings menu. 2025-04-12 16:32:14 -04:00
6f0d7d734f Added functionality to PauseMenu, other various edits. 2025-04-10 14:57:22 -05:00
35298ec28d Putting together PauseMenu functionality. 2025-04-10 13:58:51 -04:00
1cbed8ce7f Pause Menu Visual Design Revision 1 2025-04-09 04:36:16 -05:00
79d8720282 More Pause Menu Boilerplate 2025-04-09 04:09:55 -05:00
c40c7c3cdf Merge pull request 'Various edits going live.' (#56) from edits into master
Reviewed-on: #56
2025-04-08 14:59:55 -04:00
f154c256d8 Wacky error where std::optional was not included, and it caused a problem for builds on another platform. 2025-04-08 14:57:10 -04:00
c5109c2c1b Implement basis for Pause Menu. 2025-04-07 15:17:52 -05:00
ab0fd9455c More Edits 2025-04-07 14:30:30 -04:00
b7b06cd48b Assets 2025-04-03 13:35:09 -04:00
f9e4f93aaa Add TODO notes for tile lighting stuff coming soon. 2025-04-02 15:27:58 -05:00
f82157d240 Various Edits 2025-04-01 18:57:01 -04:00
b10d1e30dc Command additions and minor fixes 2025-03-31 16:38:12 -04:00
e748bf95ae Upgrade to latest JUI with bug fixes 2025-03-31 16:37:58 -04:00
28dda7d12d Place gravel. 2025-03-31 16:37:45 -04:00
15d816f04f Added custom movement code for when player is noclipping. 2025-03-31 16:37:31 -04:00
d9d8f7f3a2 Adjusted tile pallets, added gravel. 2025-03-31 16:37:07 -04:00
0ee1ef5592 Parse tile 'solid' from json 2025-03-31 16:36:36 -04:00
0d7306b9ae Cleanup LocalWorld 2025-03-31 16:36:21 -04:00
d55a64163b Add GameSession::GetLocalPlayerEntity 2025-03-31 16:36:04 -04:00
c7f98a1be6 Add noclip member to entity 2025-03-31 16:35:44 -04:00
4538aa963b Update LocalWorld.cpp
cleanup
2025-03-29 16:54:00 -04:00
60dbd6d725 Decently-well working, doing some profiling currently. 2025-03-29 14:31:57 -04:00
5cd84258d8 Adding More Tile Functions (Non-building commit). 2025-03-28 01:26:31 -04:00
6e9224ead6 Add Tile Functions 2025-03-27 23:35:52 -04:00
1a09845770 Fixed a silly, got it working. 2025-03-27 18:18:11 -04:00
f94c8b8e72 Segfault on usage of std::function from ticc_funcs. 2025-03-27 17:44:21 -04:00
3dd9d65964 Now scoping out Item loading. 2025-03-22 15:31:58 -04:00
7d71f32d09 Refactoring tile loading procedure. 2025-03-22 15:07:50 -04:00
a53bc42a82 Re-enabling features: hotbar icons, name, plank pattern. 2025-03-22 14:07:43 -04:00
06ec0ca885 Make sure to manually reload cmake-build-debug after first build. 2025-03-22 13:53:10 -04:00
c9b037bf39 It works!!! All tiles are currently blue, and basically all tile features need to be re-implemented, however. 2025-03-22 13:39:12 -04:00
41749893a5 Few more edits for the night 2025-03-21 03:52:17 -05:00
79f77a4fcf Lots more partial progress on the data-driven refactoring. 2025-03-21 03:00:21 -05:00
e0367b971c Large Refactor, half-finished, will not compile or run currently. 2025-03-20 23:52:51 -04:00
dfb60a45c4 JSON tile metadata parse test, WIP. 2025-03-19 15:45:54 -04:00
9a10ff81d0 Building out container class, move classes to their own headers. 2025-03-19 15:45:42 -04:00
27f748ceca Include v2i_hash 2025-03-19 15:45:02 -04:00
6b37c03079 Move Vector2i custom hash to separate header file. 2025-03-19 15:44:44 -04:00
23fb2892a9 Put into namespace 2025-03-19 15:44:30 -04:00
50205fef99 Update jstick dependency 2025-03-19 15:44:16 -04:00
4553a0c8ac Add 'container w h' command 2025-03-19 15:44:00 -04:00
8072af2c49 Test Tile JSON data 2025-03-19 15:43:46 -04:00
d9de2502ea Fix data folder 2025-03-19 12:49:18 -04:00
83afdcaebc Merge remote-tracking branch 'origin/master'
# Conflicts:
#	ClientApp/main.cpp
2025-03-19 12:48:26 -04:00
e6f67e1052 Fix data folder 2025-03-19 12:47:54 -04:00
2f19b910de Update main.cpp
Fix missing jjx include
2025-03-19 12:32:24 -04:00
13cc7f2104 Merge branch 'master' of https://git.redacted.cc/Josh/ReCaveGame 2025-03-19 11:47:26 -04:00
0dcb7d5a73 Update World.cpp
band-aid fix on chunk-server thread to prevent busy-waiting until we do it correctly with std::mutex & std::condition_variable.
2025-03-19 11:47:10 -04:00
ba39db02d3 Integrating JSON, testing with externally-defining tile data. 2025-03-19 03:56:10 -04:00
10e82cd4aa Work-in-progress Item system code. 2025-03-19 02:48:22 -04:00
a599775eee Add command stubs 2025-03-19 02:48:07 -04:00
185016ed49 Update changed name 2025-03-19 02:47:54 -04:00
8a8f1321b9 Update changed name 2025-03-19 02:47:48 -04:00
84bc213229 Add XBox360 controller support to hotbar, tile editor, and zoom. 2025-03-19 02:47:36 -04:00
de8b45018a Add freeze_in_void property to PhysicsEntity 2025-03-19 02:46:19 -04:00
c604b69487 Tweak player falling, prevent sprite spazzing. 2025-03-19 02:46:03 -04:00
8a3ed23302 Added Core::GetAllTiles(), added tile display_name property. 2025-03-19 02:45:14 -04:00
6d1652e0af Add airtime property to entities. Will be used for fall-damage calculation, among other things. 2025-03-19 02:42:58 -04:00
699c1021c1 Fix Console Input Box color 2025-03-19 02:42:34 -04:00
4e0c1dac86 Added Camera freemove and follow. Camera returns to follow target if free-movement isn't done for 1.25 seconds 2025-03-19 02:42:20 -04:00
b2e4732b14 Add TileTool::WorldEditorEnabledByDefault 2025-03-19 02:41:30 -04:00
e912645a24 Add World::GetEntities 2025-03-19 02:41:10 -04:00
a7063675dc Edit gitignore 2025-03-19 02:40:43 -04:00
f81098d7b2 Small Fix 2025-03-18 19:31:13 -05:00
cc00350dc3 Add test Container Window, building out the items system. 2025-03-18 19:26:22 -05:00
809abae082 Added a "Running-Jump" mechanic where you can jump further horizontally while moving fast enough. 2025-03-18 12:45:34 -04:00
f2512dddf1 Tweaked Air Resistance and added horizontal friction while standing on tiles. 2025-03-18 01:01:55 -04:00
7d5d8bcd46 Merge remote-tracking branch 'origin/master' 2025-03-18 00:34:24 -04:00
e547dd0045 Integrated XBox360 controller support for camera and player movement. 2025-03-18 00:34:08 -04:00
6aeef77904 ValidCoords is no longer required with Vector2i 2025-03-17 21:17:14 -04:00
83731c08f0 Update World.hpp
Improve performance when zooming out all the way.
2025-03-16 13:38:05 -04:00
0b9c5eb449 Hacked in AssetService to the splash screen, still needs some polish. 2025-03-02 04:26:50 -05:00
4a11961c0f Graphics bug fix.
Fixed a bug that would cause visible lines to occur between chunks on some graphics drivers when the camera was moved by less than 1 pixel.
2025-03-01 23:11:19 -05:00
f978605005 Working now. 2025-02-28 22:30:26 -05:00
d4468421f7 Packaging libraries into release build WIP 2025-02-28 22:16:58 -05:00
84765fbaf4 Tune RandomTileTickRate, and include random tile counter in chunk serialization. 2025-02-28 01:05:53 -05:00
7ae6003e23 Performance optimization.
Check if this chunk has any random tick tiles before running random tile tick on it.

When at normal zoom level this has little impact, But when you zoom out really far it's helpful.
2025-02-27 18:56:21 -05:00
7707d6d3a6 WINDOWS BUILD!!! (Where's my player texture). 2025-02-27 04:08:10 -06:00
0f776f2a6f Heavily refactored CaveGameWindow, pulled TileTool into GameSession. 2025-02-27 04:10:00 -05:00
d058774519 Particles Test Success. 2025-02-27 04:08:51 -05:00
2e55d42733 Various small fixes. initialization of widgets and such. 2025-02-27 04:08:35 -05:00
c5015b608d Fixed a very weird deallocation problem causing segmentation fault on close. Made the credits look nicer too. 2025-02-27 04:07:50 -05:00
0a033fc103 Tracked down and fixed segmentation fault upon close. 2025-02-27 00:13:13 -05:00
2cc0ca2033 Changes by william 2025-02-26 15:49:54 -05:00
06021b9396 Small changes 2025-02-26 15:48:59 -05:00
7c46f2939b Small changes 2025-02-26 15:20:17 -05:00
5127d50f04 Small changes, removed useless files too. 2025-02-26 12:22:54 -06:00
baf1f517a5 Added grid and tileactivity commands, Grid render doesn't appear to work though. 2025-02-25 19:52:26 -05:00
54a704a091 Tuned collision response and physics, player animation outline. 2025-02-25 17:49:53 -05:00
b8a59d98c5 Player collision response and movement physics v1. 2025-02-25 16:57:06 -05:00
24790c5c0c Refactored camera movement to feel more consistent regardless of framerate. 2025-02-25 12:17:18 -05:00
55746d45b9 Add FPS limiter. 2025-02-24 16:16:15 -05:00
48b303260d Remote Edits 2025-02-24 11:52:13 -06:00
0a174af0e2 Spawn player with P, will be working on collisions and physics soon. 2025-02-21 14:47:08 -06:00
1f18f08244 So close to a windows build!! 2025-02-21 14:09:42 -06:00
d82c821e6f Added WorldEdit step buttons. 2025-02-19 03:34:20 -06:00
6ba265e9e2 Uploading Edits 2025-02-16 20:07:19 -05:00
fba1546a9b Changes 2025-02-13 19:27:48 -05:00
01edeb9e37 Add Console Command List Data Structure 2025-02-12 22:34:47 -05:00
dd6a284b6f Hacky logger integration, other fixes. 2025-02-12 14:02:38 -05:00
f20dec85ce Update CaveGameWindow.cpp
Fix out of bounds read causing sigseg when nothing is entered.
2025-02-12 11:56:52 -05:00
0121af5178 Current State !! <3 2025-02-12 11:44:50 -05:00
3bd4716fcd Added tile_sim button to ReWorldEdit 2025-02-11 20:58:22 -05:00
9adc5c9ac7 Update JGL, JUI, and mcolor to latest. 2025-02-11 20:56:02 -05:00
a5f52f7f73 Yee 2025-02-10 14:43:12 -05:00
3c8e194f00 New Title Texture 2025-02-06 00:44:17 -05:00
a6cf319c1c Testing out new Title Image. 2025-02-05 00:04:04 -05:00
be1e04eac9 Prototyping AnimatedSprite code, refactored explosion 2025-02-02 20:43:07 -05:00
d27119ef28 Revising README 2025-02-01 04:55:21 -05:00
2fb24c60b2 Simplified tool window code. 2025-02-01 04:55:12 -05:00
e2719d9954 Add GameSession::PassMouseWheel override 2025-02-01 04:54:51 -05:00
40db7f9378 Hotbar now listens to scrollwheel, cleaned up header file. 2025-02-01 04:54:38 -05:00
7af8d41e1e Add Scene::PassMouseWheel 2025-02-01 04:54:07 -05:00
cc497fdb1e Shift+ScrollWheel now changes the Brush Size, and updates the gui accordingly. 2025-02-01 04:53:48 -05:00
f0cdddebcc Merge remote-tracking branch 'origin/master'
# Conflicts:
#	Client/src/Client/GameSession.cpp
2025-01-31 15:54:18 -05:00
85db6dd46d Migrate hotbar code to separate class. 2025-01-31 15:53:44 -05:00
9375776835 Migrate hotbar code to separate class. 2025-01-31 15:49:58 -05:00
abed11e447 Update GameSession.cpp
Make it actually build.
2025-01-31 00:20:50 -05:00
65f383cbc1 Wood and brick pattern adjustments. 2025-01-30 15:39:55 -05:00
8f7e2f04df GetChunkCoordinatesAtCell and GetTileCoordinatesAtCell very weird error trying to move to implementation. 2025-01-30 13:23:57 -05:00
1b94a2c2ce Remove excess. 2025-01-30 13:23:30 -05:00
b78081d6b9 Chunk code cleanup 2025-01-30 13:23:14 -05:00
b211df9c8f Base Draw function for tile. 2025-01-30 13:22:41 -05:00
ae797f91b5 Awesome Wood. 2025-01-30 02:14:02 -05:00
98b340379b Decent Wood. 2025-01-30 02:09:22 -05:00
915371004d Shitty Wood 2025-01-30 01:56:36 -05:00
a2ec22aab7 Re-implemented debug lines. 2025-01-29 07:46:04 -05:00
c069af8ed5 Various fixes, game running nice. 2025-01-28 19:07:53 -05:00
c92d74a20c Edit Tile Tool to make proper circle. 2025-01-28 17:05:57 -05:00
e2a1724bcf Adding documentation - file headers. 2025-01-28 16:53:45 -05:00
fe730ee3b5 Performance optimization 2025-01-28 12:46:22 -05:00
fc303375ad Performance optimization 2025-01-28 10:41:26 -05:00
115 changed files with 4847 additions and 2157 deletions

1
.gitignore vendored
View File

@@ -3,4 +3,5 @@
/.ccls-cache
/compile_commands.json
/cmake-build-debug
/cmake-build-release
/build

View File

@@ -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)

View File

@@ -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)

View File

@@ -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;
};
}

View File

@@ -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/
};
}

View File

@@ -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

View File

@@ -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:
};
}

View 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:
};
}

View File

@@ -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"},
};

View File

@@ -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;
};
}

View 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:
};
}

View File

@@ -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();
};
}

View File

@@ -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:

View File

@@ -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
}
}
}

View 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:
};
}

View File

@@ -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:

View File

@@ -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;
};
}

View File

@@ -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:
};
}

View File

@@ -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();
};
}

View File

@@ -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>

View 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:
};
}

View File

@@ -1,9 +0,0 @@
#pragma once
#include <JUI/JUI.hpp>
#include <JUI/Widgets/Scene.hpp>
namespace CaveGame::Client
{
}

View File

@@ -1,3 +0,0 @@
#pragma once
#include <Core/Chunk.hpp>

View File

@@ -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});
}

View File

@@ -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 {

View File

@@ -1 +0,0 @@
#include <Client/Console.hpp>

View File

@@ -0,0 +1,6 @@
#include <Client/ContainerWindow.hpp>
namespace CaveGame::Client {
}

View File

@@ -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;
}

View File

@@ -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});
}

View File

@@ -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();
}
}
}
}

View File

@@ -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;
}

View 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;
}

View File

@@ -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);
}
}

View File

@@ -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();

View 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); }
}

View 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});
}

View File

@@ -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; }

View File

@@ -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() {

View 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;}

View File

@@ -1,2 +0,0 @@
#include <Client/WindowWidgetManager.hpp>

View File

@@ -1 +0,0 @@
#include "Client/temp.hpp"

View File

@@ -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)

View File

@@ -0,0 +1,9 @@
{
"veins": {
"clay": {
"hi-pass-scale-x": 200,
"hi-pass-scale-y": 205,
"hi-pass-offset": 0
}
}
}

View 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"
}
]

View 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": [
]
}

View 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"
}
]

View 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
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 730 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -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;
};
}

View 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;
};
}

View File

@@ -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;
}

View File

@@ -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; }
}

View File

@@ -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)

View File

@@ -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:
};
}

View File

@@ -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;
};

View File

@@ -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()

View File

@@ -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
}
}
};
}
};

View 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;
};
}

View File

@@ -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,
};
};*/
}

View File

@@ -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:
};

View File

@@ -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);
};
}

View File

@@ -1,8 +0,0 @@
//
// Created by dawsh on 11/10/24.
//
#ifndef RECAVEGAME_GAMEPROTOCOL_HPP
#define RECAVEGAME_GAMEPROTOCOL_HPP
#endif //RECAVEGAME_GAMEPROTOCOL_HPP

View File

@@ -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;

View File

@@ -1 +0,0 @@
#pragma once

View File

@@ -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;

View File

@@ -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
}
}*/
}

View 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();
}

View File

@@ -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:
};
}

View 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);
}

View File

@@ -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);

View 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);

View File

@@ -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);
};
}

View File

@@ -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;
};
}

View File

@@ -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 &registry;
}
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
}

View File

@@ -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);

View 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
};

View 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:
};
}

View File

@@ -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;
}
}*/
}

View 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();
}

View File

@@ -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);
};
}

View 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));
}
};

View File

@@ -1,6 +0,0 @@
#include <Core/Animator2D.hpp>
namespace CaveGame::Core
{
}

View File

@@ -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();
}

View File

@@ -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;}
}

View File

@@ -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; }

View File

@@ -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});
}
}
}
}
}

View File

@@ -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];

View File

@@ -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
View File

@@ -0,0 +1,6 @@
#include <Core/Item.hpp>
#include <string>
#include <map>
namespace CaveGame::Core {
}

View 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);}
}

View 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 {};
}

Some files were not shown because too many files have changed in this diff Show More