235 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
f9e14016ef Fix 2025-01-27 02:19:15 -05:00
6131372d48 Add image to README.md 2025-01-27 02:18:49 -05:00
8c430def05 Scrollwheel changes tile tool radius. CXX Flags optimization 2025-01-27 02:07:33 -05:00
a425e2bc78 Partially finished stuff... 2025-01-27 01:02:43 -05:00
3498d514ad Added Udp test code. 2025-01-27 01:02:02 -05:00
69b2cbc32b Added write and read to buffer to Chunk 2025-01-27 01:01:46 -05:00
b24f1e9039 Added Logger Control Functions. 2025-01-27 00:55:03 -05:00
9720f82315 Fix Name Signature 2025-01-27 00:54:41 -05:00
414ed4a711 implemented Reflection::type_name() 2025-01-27 00:54:12 -05:00
9df6bbbda2 Added string_split and string_build to Macros 2025-01-27 00:53:32 -05:00
24e7474769 Added ArgsParser class. 2025-01-27 00:53:08 -05:00
e2e26a484b Half-precision floating point serialization. 2025-01-24 15:28:18 -05:00
4909052c4d Serialization test. 2025-01-23 15:10:42 -05:00
0e8f6600a0 String serialization. 2025-01-23 14:57:00 -05:00
71b95b2615 Primitive type serialization 2025-01-23 13:00:36 -05:00
350e93da09 More serializer code 2025-01-22 01:12:47 -05:00
eaf6c3d6fb Buffer struct and read/write members. 2025-01-21 20:49:53 -06:00
6cf8bb3d05 ServerApp demo program 2025-01-21 16:26:40 -05:00
1f0cc1bbfc Network Code Notes 2025-01-21 10:32:09 -06:00
c80b9482a4 Merge remote-tracking branch 'origin/master' 2025-01-20 11:28:13 -05:00
4d7e8d2fc8 ServerApp demo program 2025-01-20 11:28:07 -05:00
86e00e7b35 Update LocalWorld.cpp
Found & fixed a memory error that would repeatedly cause a chunk that has already been generated to be requested again.
2025-01-18 15:10:01 -05:00
a0c0ccdf5e Merge remote-tracking branch 'origin/master' 2025-01-18 14:27:21 -05:00
5caf84560e Trying to setup ServerApp program. 2025-01-18 14:27:16 -05:00
5b819e5a87 Update Chunk.cpp
Fixed a case where a tile would be flagged for update directly after creation based on data from the previous stack frame (usually the perlin noise-map).
2025-01-18 14:22:21 -05:00
daddf0e257 Improve memory safety.
Fixed a case where RenderTargets occasionally wouldn't get deleted.
2025-01-18 13:37:28 -05:00
b075e221c4 Implement AABB2DvsPoint and SolveAABBvsPoint, working to optimize entity-vs-tile collisions. 2025-01-18 00:56:34 -05:00
f6152d9df7 IsChunkCellWithinViewport extraChunkRadius parameter for supporting overdraw. 2025-01-18 00:55:58 -05:00
3fac7e61ce Add GameServer header files 2025-01-18 00:55:31 -05:00
ebdcae945f Add Console header 2025-01-18 00:55:19 -05:00
916e44c7e5 Add Chat header 2025-01-18 00:55:08 -05:00
9295435304 Edit Client/CMakeLists 2025-01-18 00:54:57 -05:00
4445c084dd Added console controls to CaveGameWindow 2025-01-18 00:54:20 -05:00
4ec166c1d9 Added Camera2D::ScaledViewportOversized 2025-01-18 00:53:59 -05:00
fd2391e03b Ensure you can't request the same chunk more than once. 2025-01-18 00:07:04 -05:00
7a47f47388 Improve memory safety. 2025-01-17 23:34:15 -05:00
e77304cac2 const auto& instead of copying. 2025-01-17 23:33:59 -05:00
ac54243e4f Improve memory safety + fix high disk usage. 2025-01-17 22:53:13 -05:00
79958d395d Improve memory safety
Fixed a memory leak in LocalWorld, line 350 where a new chunk was allocated and then the pointer was overwritten immediately afterwards so the memory would never get released.
2025-01-17 21:16:02 -05:00
dfb20b548e Update Scene.hpp
Make note of a warcrime in the design.
2025-01-17 20:17:32 -05:00
0ee0dca397 Improve memory safety
Fixed a case where after the splash screen was over the RenderTargets & Texture created in it weren't deleted.
2025-01-17 20:13:27 -05:00
5a48438e11 working on fluids revamp 2025-01-17 13:07:20 -05:00
823889cc09 Working on implementing tile trait system. 2025-01-17 11:16:26 -05:00
09773fbe5c Merge remote-tracking branch 'origin/master' 2025-01-16 13:42:13 -05:00
9c9debbf5d Working on shyt 2025-01-16 13:42:08 -05:00
e2e6918c3b Fixed potential buffer overflow found by Rich 2025-01-16 10:05:29 -05:00
0a254808c4 Merge remote-tracking branch 'origin/master' 2025-01-16 10:02:45 -05:00
b8ac6e8d9c started work on cleaning up code. More performance improvements 2025-01-16 10:02:31 -05:00
145ad027f8 Update dependencies. 2025-01-15 23:04:38 -05:00
dc3e923a46 Performance optimization
We know the chunk size at compile time because constexpr. So we do std::array for datacontiguous.
2025-01-14 23:32:27 -05:00
5d7e8d8eb3 Added explosion.png 2025-01-14 16:45:48 -05:00
d6ba89c257 Working to implement animated explosions. Running into JGL issues. 2025-01-14 15:34:51 -05:00
37dc8c1f5e Slight performance improvement with tile ticks 2025-01-13 16:42:50 -05:00
9c7a5a0201 Make gravity tile go brrish 2025-01-11 15:34:10 -05:00
695789afa9 Update Tile.cpp 2025-01-11 00:54:28 -05:00
15aec2cfe6 SwapTile + Better sand & water. 2025-01-11 00:17:49 -05:00
3b17ac0ee3 Bunch of refactors stuff 2025-01-10 22:57:37 -05:00
da23a91de1 Debugging Insane Tile Boundary Issue 2025-01-10 18:45:38 -05:00
438a7ae632 Finished what I was initially working on for ComputerTile 2025-01-10 18:41:40 -05:00
bd8ce016bf Various minor changes, added visualizer to tile updates. 2025-01-10 17:50:07 -05:00
c456d04da5 Make sure it actually compiles 2025-01-10 15:01:41 -05:00
d8c27dafb8 Various Refactoring 2025-01-10 14:58:37 -05:00
860f544467 Tests 2025-01-07 19:16:40 -05:00
34a9092f59 Collaborative edits 2025-01-06 23:49:02 -05:00
51803d37f5 Naiive optimization attempt. 2025-01-06 19:41:49 -05:00
7fe651b195 Removed lock from ConcurrentQueue::empty() 2025-01-06 19:04:02 -05:00
2f7ed84e2a Added documentation to Chunk and Scene 2025-01-06 18:49:18 -05:00
a75043ef66 Added documentation to Camera2D class 2025-01-06 17:29:28 -05:00
5d042e9353 Add documentation 2025-01-06 13:45:18 -06:00
57ceb8adce Various Refactors 2025-01-03 00:22:43 -05:00
2ee33d4575 Organizing & Documenting 2025-01-01 11:18:46 -05:00
59794655a0 Fix Typo 2024-12-31 10:06:12 -06:00
24fa29408a Added water vs sand interaction. 2024-12-31 10:05:56 -06:00
14960eec4f Added player icon. 2024-12-31 00:41:22 -05:00
e3b8901bcd Added Sand, Water, Lava Tiles physics. 2024-12-31 00:41:08 -05:00
534ae32f36 Added color variation to sand tiles. 2024-12-27 15:30:24 -05:00
929231f0ff Temporary default hotbar!! 2024-12-27 15:26:53 -05:00
3fb2db4fd4 Implement initial test of entity physics, and entity vs terrain collision with high-density of colliders. (Decent performance w/o optimizations!) 2024-12-27 14:38:22 -05:00
2418ddfc46 Integrated AssetService 2024-12-27 14:37:30 -05:00
cd2d119abb Moved AssetService class to Client package 2024-12-27 14:37:19 -05:00
e01bc5f7b8 Fixed the tarded error. 2024-12-26 14:52:04 -05:00
52aad685d9 I have no idea what this error means. 2024-12-25 18:36:02 -05:00
32d27767c3 I have no idea what this error means. 2024-12-25 16:51:22 -05:00
8e8ad07d59 Generator - Implementing parameters for various ore generations, refactoring and documenting functions. 2024-12-22 06:57:29 -06:00
88d7ead713 Generator Refactor 2024-12-22 05:45:10 -06:00
b4860c14c9 Merge remote-tracking branch 'origin/master' 2024-12-21 01:12:19 -06:00
d36818cf66 Moved ClayVeinHiPass values into a single Vector2 value 2024-12-21 02:10:53 -05:00
0f38031c1e Made GetSkyColor functions static 2024-12-21 01:43:39 -05:00
44ec829aa0 Tile Implementations 2 2024-12-19 15:30:12 -06:00
20e60ba613 Implementing Sky Color 2024-12-18 18:24:26 -05:00
a656383741 Finished what I was initially working on for ComputerTile 2024-12-18 16:51:11 -05:00
8529649dc2 Changes by maxine 2024-12-17 21:55:51 -05:00
ede437eabd Changes by maxine 2024-12-17 19:51:10 -05:00
9875e5d0b0 Generator features. 2024-12-17 19:50:58 -05:00
717182c35c Tile Implementations 2024-12-17 15:20:59 -06:00
fcd580cc3b Coding in tile features. 2024-12-16 22:08:32 -05:00
232aed317a Added tons of WIP game data. 2024-12-14 04:58:12 -05:00
96417bf66d Small changes 2024-12-13 13:32:19 -05:00
c64a21a1f1 Creating world entity list, and appropriate memory management for it. 2024-12-11 15:40:41 -06:00
099782a17a Camera2D 2024-12-11 11:04:16 -06:00
f566b800fa Refactors 1 2024-12-11 01:47:29 -05:00
133 changed files with 8282 additions and 1791 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,34 +13,63 @@ 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")
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/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-19.zip
)
CPMAddPackage(
NAME J3ML
URL https://git.redacted.cc/josh/j3ml/archive/3.4.3.zip
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-26.zip
URL https://git.redacted.cc/Redacted/ReWindow/archive/Prerelease-34.zip
)
CPMAddPackage(
NAME JGL
URL https://git.redacted.cc/josh/JGL/archive/Prerelease-42.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.5.zip
URL https://git.redacted.cc/josh/ReJUI/archive/Prerelease-6.2.zip
)
CPMAddPackage(
@@ -46,6 +77,11 @@ CPMAddPackage(
URL https://git.redacted.cc/josh/jjx/archive/Prerelease-2.zip
)
CPMAddPackage(
NAME Sockets
URL https://git.redacted.cc/josh/Sockets/archive/Prerelease-3.1.zip
)
add_subdirectory(Core)
add_subdirectory(Server)
add_subdirectory(Client)

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

@@ -0,0 +1,110 @@
/// 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.
#pragma once
#include <string>
#include <unordered_map>
#include <filesystem>
#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 {
using namespace JGL;
enum AssetType { DATA, TEXT, AUDIO, MODEL, TEXTURE, FONT, SHADER };
enum AssetLifestyle { STATIC, STREAMED};
struct AssetRequest {
std::string name;
std::filesystem::path path;
AssetType type;
};
class AssetService {
private:
public:
static AssetService* Get();
JGL::Texture *player_sprite;
JGL::Texture *explosion_sprite;
JGL::Texture *title_img;
AssetService();
~AssetService() {}
void TempLoadSimple();
std::shared_ptr<Texture> GetTexture(const std::string &textureName) {
return textures[textureName];
}
std::shared_ptr<JGL::Font> GetFont(const std::string &fontName) {
//return fonts[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 LoadAllFromQueue();
/// 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:
/// @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

@@ -21,6 +21,8 @@ namespace CaveGame::Client
/// The default constructor does not initialize any values.
Camera2D();
// TODO: Implement camera shaking for explosions and other effects.
void ApplyShake(const Vector2& impulse);
/// Performs one iteration of the camera's internal logic.
/// @param elapsed The delta-time (in seconds) between this iteration and the last.
void Update(float elapsed);
@@ -28,6 +30,8 @@ namespace CaveGame::Client
AABB2D Viewport() const;
/// Returns the bounds of the screen-viewport, in gameworld-coordinates, with scaling by the camera's zoom.
AABB2D ScaledViewport() const;
AABB2D ScaledViewportOversized(float oversize) const;
/// Returns the target coordinates of this camera. The camera will move over time to reach this point.
Vector2 Target() const;
/// Sets the target coordinates of this camera. The camera will move over time to reach this point.
@@ -45,9 +49,18 @@ namespace CaveGame::Client
/// Sets the amount of zoom in the camera transformation.
void Zoom(float val);
/// Enables free camera movement controls.
void SetFreeCam(bool freeCam);
/// Returns whether free camera controls are enabled.
bool GetFreeCam() const;
/// Zooms the camera in by the given amount.
void ZoomIn(float amt);
/// Zooms the camera out by the given amount.
void ZoomOut(float amt);
/// Returns the amount of rotation (in degrees) in the camera transformation.
[[nodiscard]] float Rotation() const;
@@ -57,13 +70,10 @@ namespace CaveGame::Client
/// Rotates the camera by the given amount (in degrees).
void Rotate(float angles);
Vector2 ScreenCenterPosition() const;
Vector2 ScreenTopLeftPosition() const;
Vector2 ScreenToWorld(const Vector2& device_coordinates) const
{
Vector2 pos = ScaledTopLeft() + (device_coordinates/zoom);
return pos;
}
/// Converts top-left origin screen coordinates into world-space coordinates, with respect to zoom scale.
Vector2 ScreenToWorld(const Vector2& device_coordinates) const;
/// Converts world-space coordinates into top-left origin screen coordinates, with respect to zoom scale.
Vector2 WorldToScreen(const Vector2& position) const;
/// Returns the current cartesian coordinates of the camera transformation.
@@ -76,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.
@@ -93,10 +104,15 @@ namespace CaveGame::Client
/// Returns the gameworld-coordinates at the top-left of the screen, with scaling taken into account.
[[nodiscard]] Vector2 ScaledTopLeft() const;
/// Returns the world-space coordinates at the focal point of the camera. Generally, this should also be the
/// world-space coordinates of the center of the screen.
[[nodiscard]] Vector2 Center() const;
/// Returns the "radius" of the viewport. (size / 2.f)
[[nodiscard]] Vector2 HalfSizeOffset() const;
/// Returns the "radius" of the viewport, scaled by the camera zoom.
[[nodiscard]] Vector2 ScaledHalfSizeOffset() const;
/// Returns the base movement speed of the camera.
@@ -104,17 +120,24 @@ namespace CaveGame::Client
/// Sets the base movement speed of the camera.
void MoveSpeed(float value);
protected:
/// Process user-input for freecam.
void UpdateFreecamControls(float delta);
protected:
Vector2 velocity;
Vector2 position;
Vector2 final_pos;
float zoom = 1.f;
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

@@ -0,0 +1,17 @@
/// 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,13 +0,0 @@
#pragma once
#include <JUI/Widgets/Window.hpp>
namespace CaveGame::Client
{
class Console : public JUI::Window
{
public:
protected:
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,114 +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:
Event<> OnSessionExit;
Event<> RequestToggleSettings;
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();
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});
}
uint16_t GetTileIDUnderMouse();
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});
}
[[nodiscard]] JUI::Scene* HUD() const { return hud; }
LocalWorld* World() const { return world;}
TileHotbar Hotbar() const { return hotbar;}
LocalWorld* world;
void WorldEditToolControlsUpdate(float elapsed);
void ToggleWorldEdit();
TileTool* WorldEditToolWindow() const { return tile_tool;}
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,62 +19,129 @@
#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
{
public:
};
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:
//JGL::Font font;
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:
LocalWorld(const std::string& world_name, int seed, bool overwrite);
void PostInit();
void Draw();
void Update(float elapsed) override;
void RefreshAll() override;
Camera2D camera;
std::thread chunk_thread;
std::atomic<bool> run_chunk_thread = true;
ConcurrentQueue<Vector2> RequestedChunks;
std::vector<Vector2> chunks_in_waiting;
ConcurrentQueue<Core::Chunk> ServedChunks;
Vector2 mouse_pos;
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:
std::unordered_map<Vector2, JGL::RenderTarget*> cached_chunk_sprites;
bool IsChunkCellWithinViewport(const Vector2& coords) const;
// TODO: Chunk should render itself, no? Logical Separation issue here aswell...
void RenderChunk(const Vector2& coords, const Core::Chunk& chunk);
/// 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 RequestChunk(const Vector2& cell);
void ChunkServerThread();
void RenderChunkTexture(const Vector2 &coords, JGL::RenderTarget* destination, const Core::Chunk& chunk);
/// Render chunk boundaries and relative coordinates, around the camera.
void DrawChunkGrid() const;
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<Vector2i, JGL::RenderTarget*> cached_chunk_sprites;
JGL::RenderTarget* canvas_render_target;
std::vector<Particle> particles;
bool show_tile_activity = false;
bool AwaitingChunkAtCell(const Vector2 &cell);
};
}

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

@@ -0,0 +1,44 @@
/// 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>
#include <JGL/JGL.h>
namespace CaveGame::Client
{
struct Particle
{
Vector2 position;
Vector2 velocity;
Color4 color;
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);
}
};
}

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,28 +1,65 @@
/// 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
{
class SceneManager;
/// The scene class is a self-contained environment for logic, input, and rendering.
class Scene
{
public:
Scene();
virtual ~Scene() = default;
/// 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();
/// 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 Unload();
/// This function is called by the SceneManager every frame. It is not called when the scene is inactive.
virtual void Update(float elapsed) = 0;
/// This function is called by the SceneManager every frame. It is not called when the scene is inactive.
virtual void Draw() = 0;
/// This function is called by the SceneManager when the game window changes dimensions.
virtual void PassWindowSize(const Vector2& size);
/// This function is called by the SceneManager when user mouse button input is detected.
/// @param mouse_btn A number indicating the mouse button ID.
/// @param pressed True if the button was pressed, false if the button was released.
virtual void PassMouseInput(unsigned int mouse_btn, bool pressed) {}
/// This function is called by the SceneManager when user key input is detected.
/// @param key An enumeration indicating the key.
/// @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 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:
bool active;

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
{
c
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
@@ -13,33 +22,29 @@ namespace CaveGame::Client
{
public:
Splash();
//~Splash();
~Splash() override;
void Draw() override;
void Update(float elapsed) override;
void Load() override;
void Unload() override;
//void PassFont(JGL::Font passed);
bool SplashComplete() const;
private:
[[nodiscard]] bool SplashComplete() const;
protected:
float splash_timer = 1.5f;
float load_percent = 0.f;
JGL::Texture* splash; // TODO: RAII on this
//JGL::Font font; // TODO: RAII on this
float increment = 0;
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

@@ -0,0 +1,32 @@
/// 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>
#include <JUI/Widgets/ListLayout.hpp>
#include <JUI/Widgets/TextRect.hpp>
namespace CaveGame::Client {
using namespace JUI;
class StatsWindow : public JUI::Window
{
public:
void AddLine(const std::string& info)
{ }
protected:
private:
};
}

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

@@ -0,0 +1,158 @@
#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;
CaveGame::Client::AssetService * CaveGame::Client::AssetService::Get() { return singleton; }
CaveGame::Client::AssetService::AssetService() {
singleton = this;
ParseManifest();
}
std::string CaveGame::Client::AssetService::FilenameFromPath(const std::filesystem::path &path) {
return path.string().substr(path.string().find_last_of("/\\") + 1);
}
std::string CaveGame::Client::AssetService::FilenameFromPathWithoutExtension(const std::filesystem::path &path) {
auto base = path.string().substr(path.string().find_last_of("/\\") + 1);
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,4 +1,6 @@
#include <Client/Camera2D.hpp>
#include <ReWindow/InputService.h>
#include <jstick.hpp>
namespace CaveGame::Client
{
@@ -6,15 +8,67 @@ namespace CaveGame::Client
}
void Camera2D::UpdateFreecamControls(float elapsed)
{
// 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(elapsed);
if (InputService::IsKeyDown(Keys::RightArrow))
MoveRight(elapsed);
if (InputService::IsKeyDown(Keys::UpArrow))
MoveUp(elapsed);
if (InputService::IsKeyDown(Keys::DownArrow))
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) || jstick::IsButtonDown(jstick::XBoxButton::Back))
ZoomOut(elapsed);
if (InputService::IsKeyDown(Keys::Equals) || jstick::IsButtonDown(jstick::XBoxButton::Start))
ZoomIn(elapsed);
}
void Camera2D::Update(float elapsed) {
if (lerp_to_target)
position = Vector2::Lerp(position, target, Math::Min(elapsed*lerp_factor, 1.f));
else
position = target;
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 (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;
}
@@ -33,13 +87,24 @@ namespace CaveGame::Client
void Camera2D::Zoom(float val) {
zoom = val;
zoom = Math::Clamp(0.125f, zoom, 50.f);
zoom = Math::Clamp(zoom, 0.125f, 50.f);
}
void Camera2D::ZoomIn(float amt) {
zoom += amt;
// Scale zoom amount to be relative to current scaling.
amt *= zoom;
Zoom(Zoom()+amt);
}
void Camera2D::ZoomOut(float amt)
{
// Scale zoom amount to be relative to current scaling.
amt *= zoom;
Zoom(Zoom()-amt);
zoom = Math::Clamp(0.125f, zoom, 50.f);
}
float Camera2D::Rotation() const { return rotation; }
@@ -48,22 +113,37 @@ namespace CaveGame::Client
void Camera2D::Rotate(float angles) { rotation+=angles;}
Vector2 Camera2D::ScreenToWorld(const Vector2 &device_coordinates) const {
Vector2 pos = ScaledTopLeft() + (device_coordinates/zoom);
return pos;
}
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) {
@@ -95,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;}
@@ -107,9 +187,15 @@ 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 {
return AABB2D(
position - ScaledHalfSizeOffset() - Vector2(oversize),
position + ScaledHalfSizeOffset() + Vector2(oversize*2));
}
}

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

@@ -0,0 +1,6 @@
#include <Client/AssetService.hpp>
#include <Core/Entity.hpp>
#include <Core/Player.hpp>
#include <JGL/JGL.h>
#include <ReWindow/InputService.h>

View File

@@ -0,0 +1,41 @@
#include <Client/AssetService.hpp>
#include <Core/Explosion.hpp>
#include <JGL/JGL.h>
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; }
float draw_radius = radius * 1.25f;
auto* tex = Client::AssetService::Get()->explosion_sprite;
Vector2 pos = position - (Vector2(draw_radius));
Vector2 scale {draw_radius/16.f, draw_radius/16.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});
} else if (anim_timer > (2.f / 5.f)) {
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)) {
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,11 +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.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();
@@ -14,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();
}
@@ -54,109 +231,65 @@ 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* 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);
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() {
Logs::Info("Unloading player HUD.");
delete hud;
}
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,11 +5,13 @@
#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) {
: World(world_name, seed, overwrite) {
Logs::Info("Spinning up ChunkServerThread");
chunk_thread = std::thread(&LocalWorld::ChunkServerThread, this);
chunk_thread.detach();
@@ -20,101 +22,153 @@ namespace CaveGame::Client {
//font = JGL::Font("assets/fonts/Jupiteroid.ttf");
}
void LocalWorld::DrawChunkGrid() const {
void LocalWorld::CheckCachedChunkSprites() {
for (auto& [chunk_pos, chunk] : loaded_chunks) {
if (!IsChunkCellWithinViewport(chunk_pos))
continue;
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});
}
}
Color4 LocalWorld::GetSkyColorInterpolatedForTimeOfDay(float time) {
float rounded_low = Math::Floor(time);
float rounded_high = Math::Ceil(time);
float delta = (time - rounded_low);
Color4 color_low = GetSkyColorBaseForTimeOfDay(rounded_low);
Color4 color_high = GetSkyColorBaseForTimeOfDay(rounded_high);
return Color4::Lerp(color_low, color_high, delta);
}
Color4 LocalWorld::GetSkyColorBaseForTimeOfDay(float time) {
if (time >= 23*60)
return Colors::Black;
else if (time >= 22*60)
return Colors::Black;
else if (time >= 21*60)
return Colors::White;
else if (time >= 20*60)
return Colors::White;
else if (time >= 19*60)
return Colors::White;
else if (time >= 18*60)
return Colors::White;
else if (time >= 17*60)
return Colors::White;
else if (time >= 16*60)
return Colors::White;
else if (time >= 15*60)
return Colors::White;
return Colors::White;
}
void LocalWorld::DrawSky() {
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);
Color4 base_bg_color = Colors::Blues::CornflowerBlue;
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);
float space_starts_at = -2000.f;
float depth = camera.Position().y;
// Draw space bg.
if (depth < -space_starts_at) {
float t = Math::Clamp( -((camera.Position().y+space_starts_at) / space_starts_at), 0.f, 1.f);
base_bg_color = base_bg_color.Lerp(Colors::Blues::MidnightBlue, t);
}
int nearest_grid_top = Math::Floor(viewport_topleft.y / Core::Chunk::ChunkSize);
int nearest_grid_bottom = Math::Floor(viewport_bottomright.y / Core::Chunk::ChunkSize);
float deep_starts_at = 2000.f;
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()
{
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;
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});
}
}
// Draw deep bg.
if (depth > deep_starts_at) {
float t = Math::Clamp((camera.Position().y-deep_starts_at) / deep_starts_at, 0.f, 1.f);
base_bg_color = base_bg_color.Lerp(Colors::Black, t);
}
glClearColor(base_bg_color.RN(), base_bg_color.GN(), base_bg_color.BN(),1.f);
//if ()
}
/*
void LocalWorld::DrawBeforeProjection(){ }
void LocalWorld::DrawDuringProjection() { }
void LocalWorld::DrawAfterProjection() { }
*/
void LocalWorld::Draw() {
CheckCachedChunkSprites();
JGL::J2D::Begin();
DrawSky();
J2D::Begin();
glClearColor(0.5f,0.5f,1.f,1.f);
// 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);
//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);
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)
for (const auto& [chunk_pos, chunk] : loaded_chunks)
{
if (IsChunkCellWithinViewport(chunk_pos))
RenderChunk(chunk_pos, chunk);
{
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();
if (chunk_debug.IsEnabled())
chunk_debug.DrawGrid(camera.ScaledViewport());
for (auto* entity : entities) {
entity->Draw();
}
Vector2 transformed = camera.ScreenToWorld(mouse_pos);
for (auto& p : particles)
{
if (p.life > 0)
p.Draw();
}
JGL::J2D::OutlineCircle(Colors::Red, transformed, 5);
// Banana for scale.
JGL::J2D::FillRect(Colors::Yellow, {0,0}, {32, 48});
JGL::J2D::End();
@@ -122,126 +176,276 @@ namespace CaveGame::Client {
//hud->Draw();
}
bool LocalWorld::IsChunkCellWithinViewport(const Vector2 &coords) const {
AABB2D chunk_bounding_box = AABB2D(coords*Core::Chunk::ChunkSize, coords*Core::Chunk::ChunkSize+Vector2(Core::Chunk::ChunkSize));
return Core::Solver::AABB2Dvs(
camera.ScaledViewport(), chunk_bounding_box);
unsigned int LocalWorld::GetRenderTargetCount() const {
return cached_chunk_sprites.size();
}
void LocalWorld::RenderChunkTexture(const Vector2 &coords, JGL::RenderTarget* destination, const Core::Chunk& chunk)
bool LocalWorld::IsChunkCellWithinViewport(const Vector2i &coords, int extraChunkRadius) const {
int extraChunkComputedPixelsRequired = extraChunkRadius*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);
}
RNG rng;
void LocalWorld::RenderTile(const TileID& t_id, int wx, int wy, int tx, int ty)
{
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
using CaveGame::Core::TileID;
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;
TileID t_id;
Core::Tile t_data;
JGL::J2D::Begin(destination, nullptr, true);
for (int x = 0; x < Core::Chunk::ChunkSize; x++)
{
for (int y = 0; y < Core::Chunk::ChunkSize; y++)
{
TileID tile = chunk.GetTile(x, y);
t_id = chunk->GetTile(x, y);
Vector2 relative_tile_coords = Vector2(x, y);
Vector2 real_tile_coords = relative_tile_coords;
const Vector2& tile_coords = relative_tile_coords;
int wx = (coords.x*Core::Chunk::ChunkSize) + x;
int wy = (coords.y*Core::Chunk::ChunkSize) + y;
if (tile == TileID::AIR) // Air
continue;
//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);
}
/*else if (tile == TileID::STONE) // Stone
stone_coords.push_back(real_tile_coords);
else if (tile == TileID::DIRT) // Dirt
dirt_coords.push_back(real_tile_coords);
else if (tile == TileID::GRASS) // Grass
grass_coords.push_back(real_tile_coords);
else if (tile == TileID::CLAY) // Ore
clay_coords.push_back(real_tile_coords);
else if (tile == TileID::MUD) // Water
mud_coords.push_back(real_tile_coords);*/
/*t_data = Core::GetByNumeric(t_id);
if (t_id == TileID::COBBLESTONE) {
float val = generator.Perlin(wx, wy, 12, 12, 12, 2);
if (val > 0.60f || val < -0.60f)
t_color = Core::Tiles::Cobblestone.color_pallet[1];
else if (val > 0.40f || val < -0.40f)
t_color = Core::Tiles::Cobblestone.color_pallet[0];
else if (val > 0.2f || val < -0.2f)
t_color = Core::Tiles::Cobblestone.color_pallet[2];
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];
} else {
t_color = t_data->base_color;
}
JGL::J2D::DrawPoint(t_color, tile_coords);*/
}
}
JGL::J2D::Begin(destination, true);
JGL::J2D::DrawPoints(Colors::Gray, stone_coords.data(), stone_coords.size());
JGL::J2D::DrawPoints(Colors::Browns::Chocolate, dirt_coords.data(), dirt_coords.size());
JGL::J2D::DrawPoints(Colors::Green, grass_coords.data(), grass_coords.size());
JGL::J2D::DrawPoints(Colors::Reds::Firebrick, clay_coords.data(), clay_coords.size());
JGL::J2D::DrawPoints(Colors::Browns::BurlyWood, mud_coords.data(), mud_coords.size());
JGL::J2D::End();
}
void LocalWorld::RenderChunk(const Vector2& coords, const Core::Chunk& chunk) {
auto it = cached_chunk_sprites.find(coords);
if (it != cached_chunk_sprites.end())
JGL::J2D::DrawRenderTarget(it->second, chunk.GetChunkRealCoordinates());
Vector2i GetChunkRealCoordinates(const Vector2i& cell) {
return cell * Core::Chunk::ChunkSize;
}
void LocalWorld::LookForChunksNeedUnloading()
{
void LocalWorld::RenderChunk(const Vector2i& coords) {
auto it = cached_chunk_sprites.find(coords);
if (it != cached_chunk_sprites.end())
JGL::J2D::DrawRenderTarget(it->second, Vector2(GetChunkRealCoordinates(coords)));
}
void LocalWorld::LookForChunksNeedUnloading() {
for (auto it = loaded_chunks.begin(); it != loaded_chunks.end();)
{
const auto coords = it->first;
if (!IsChunkCellWithinViewport(coords))
{
if (!IsChunkCellWithinViewport(coords, 2)) {
// TODO: Move off main thread.
SaveChunkToFile(coords, it->second);
//delete it->second;
delete it->second;
loaded_chunks.erase(it++);
cached_chunk_sprites.erase(coords);
} else
{
++it;
auto sprite_it = cached_chunk_sprites.find(coords);
if (sprite_it != cached_chunk_sprites.end())
delete sprite_it->second, cached_chunk_sprites.erase(sprite_it);
}
else
++it;
}
}
bool LocalWorld::AwaitingChunkAtCell(const Vector2& cell)
{
int cnt = std::count(chunks_in_waiting.begin(), chunks_in_waiting.end(), cell);
if (cnt > 1)
{
// Not good.
std::cerr << "Fuckiddy fuck" << std::endl;
return false;
}
if (cnt == 1)
return true;
return false;
}
//void LocalWorld::LookForChunksNeedLoading()
void LocalWorld::LookForChunksNeedLoading()
{
Vector2 viewport_topleft = camera.ScaledViewportOversized(2*Core::Chunk::ChunkSize).minPoint;
Vector2 viewport_bottomright = camera.ScaledViewportOversized(2*Core::Chunk::ChunkSize).maxPoint;
// TODO: Implement mechanism to load chunk on separate thread.
// TODO: ConcurrentQueue<Vector2> RequestedChunks;
// TODO: Chunk Generator / Loader From File on separate thread.
int lower_bound_h = Math::Floor(viewport_topleft.x / Core::Chunk::ChunkSize)/*-2*/;
int upper_bound_h = Math::Floor(viewport_bottomright.x / Core::Chunk::ChunkSize)/*+2*/;
// TODO: Make sure an extra layer of chunks beyond the viewport is generated.
// This will limit the amount of "pop-in" the player will notice.
Vector2 viewport_topleft = camera.ScaledViewport().minPoint;
Vector2 viewport_bottomright = camera.ScaledViewport().maxPoint;
int lower_bound_h = Math::Floor(viewport_topleft.x / Core::Chunk::ChunkSize);
int upper_bound_h = Math::Floor(viewport_bottomright.x / Core::Chunk::ChunkSize);
int lower_bound_v = Math::Floor(viewport_topleft.y / Core::Chunk::ChunkSize);
int upper_bound_v = Math::Floor(viewport_bottomright.y / Core::Chunk::ChunkSize);
int lower_bound_v = Math::Floor(viewport_topleft.y / Core::Chunk::ChunkSize)/*-2*/;
int upper_bound_v = Math::Floor(viewport_bottomright.y / Core::Chunk::ChunkSize)/*+2*/;
for (int x = lower_bound_h; x <= upper_bound_h; x++)
{
for (int y = lower_bound_v; y <= upper_bound_v; y++)
{
Vector2 cell = Vector2(x,y);
if (!HasChunkAtCell(cell) || !AwaitingChunkAtCell(cell))
Vector2i cell(x, y);
if (!HasChunkAtCell(cell) && !AwaitingChunkAtCell(cell))
RequestChunk(cell);
}
}
}
@@ -251,69 +455,81 @@ namespace CaveGame::Client {
camera.Update(elapsed);
LookForChunksNeedUnloading();
LookForChunksNeedLoading();
// TODO: Unload chunks once they're offscreen for a certain time threshold.
// Check our generator queue for complete chunks, and pull them into our loaded chunks.
while (!ServedChunks.empty())
for (auto& particle : particles)
{
Core::Chunk c = Core::Chunk({0,0});
ServedChunks.front_pop(c);
loaded_chunks.emplace(c.GetChunkCell(), c);
if (std::find(chunks_in_waiting.begin(), chunks_in_waiting.end(), c.GetChunkCell()) != chunks_in_waiting.end()) {
chunks_in_waiting.erase(std::remove(chunks_in_waiting.begin(), chunks_in_waiting.end(), c.GetChunkCell()), chunks_in_waiting.end());
}
if (particle.life > 0)
particle.Update(elapsed);
}
}
void LocalWorld::RequestChunk(const Vector2 &cell) {
// TODO: Ensure we cannot request the same chunk twice..
if (std::find(chunks_in_waiting.begin(), chunks_in_waiting.end(), cell) == chunks_in_waiting.end())
{
std::erase_if(particles, [&](Particle& p) {return p.life <= 0; });
RequestedChunks.push(cell);
chunks_in_waiting.push_back(cell);
}
}
/*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);
}*/
void LocalWorld::ChunkServerThread() {
while (run_chunk_thread)
{
while (!RequestedChunks.empty())
check_chunks_timer += elapsed;
// TODO: Move this block to World class
if (check_chunks_timer > 1/8.f) {
// TODO: These procedures need to be performed asynchronously.
check_chunks_timer = 0.f;
LookForChunksNeedUnloading();
LookForChunksNeedLoading();
//auto a1 = std::async(&LocalWorld::LookForChunksNeedLoading, this);
// TODO: Unload chunks once they're offscreen for a certain time threshold.
// Check our generator queue for complete chunks, and pull them into our loaded chunks.
while (!ServedChunks.empty())
{
Vector2 cell;
RequestedChunks.front_pop(cell);
Core::Chunk* c = nullptr;
ServedChunks.front_pop(c);
loaded_chunks.emplace(c->GetChunkCell(), c);
if (HasChunkOnFile(cell)) {
ServedChunks.emplace(Core::Chunk(cell, GetChunkFullPath(cell)));
} else {
Core::Chunk chunk(cell);
generator.FirstPass(chunk);
//SaveChunkToFile(cell, chunk);
ServedChunks.emplace(chunk);
}
if (std::find(chunks_in_waiting.begin(), chunks_in_waiting.end(), c->GetChunkCell()) != chunks_in_waiting.end())
chunks_in_waiting.erase(std::remove(chunks_in_waiting.begin(), chunks_in_waiting.end(), c->GetChunkCell()), chunks_in_waiting.end());
//generator.FirstPass()
//std::this_thread::sleep_for(100ms);
//generator.SecondPass(this, c);
}
//std::this_thread::sleep_for(250ns);
}
}
void LocalWorld::RefreshAll() {
//World::RefreshAll();
cached_chunk_sprites.clear();
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,6 @@
#include <Client/Particle.hpp>
namespace CaveGame::Client {
}

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();
}
@@ -40,14 +37,14 @@ void CaveGame::Client::Splash::ComputeMatrixTextureCache()
for (int i = 0; i < column_textures.size(); i++)
{
Vector2 column_size = {glyph_measurement.x, glyph_measurement.y * 100};
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,29 +115,36 @@ 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() {
splash = new JGL::Texture("assets/textures/redacted.png");
column_textures.fill(nullptr);
splash = AssetService::Get()->GetTexture("redacted"); //new JGL::Texture("assets/textures/redacted.png");
ComputeMatrixTextureCache();
Scene::Load();
}
void CaveGame::Client::Splash::Unload() {
Scene::Unload();
for (auto& r : column_textures)
delete r;
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() {
Splash::Unload();
}

View File

@@ -0,0 +1 @@
#

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,17 +27,29 @@ 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)
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: 10 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: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -1,105 +1,277 @@
/// 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 "J3ML/Geometry.hpp"
#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 "JUI/Widgets/Window.hpp"
#include "Client/Scene.hpp"
#include "Client/SceneManager.hpp"
#include <Client/AssetService.hpp>
namespace CaveGame::ClientApp
{
#include <J3ML/Geometry.hpp>
#include <Client/Splash.hpp>
#include <Client/MainMenu.hpp>
#include <Client/GameSession.hpp>
#include <JUI/Widgets/ListLayout.hpp>
#include <JUI/UDim.hpp>
#include <ReWindow/types/Window.h>
#include <JUI/Widgets/Window.hpp>
#include <Client/Scene.hpp>
#include <Client/SceneManager.hpp>
#include <JUI/Widgets/CommandLine.hpp>
#include <Client/TileTool.hpp>
#include "Command.hpp"
#include <Core/Player.hpp>
#include <Core/Explosion.hpp>
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;
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;
JUI::Window* 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();
/// 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();
/// 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();
/// @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;
};
}

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,33 +13,38 @@
#include <steam_api.h>
#endif
#include <JGL/JGL.h>
#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
//srand(0);
CaveGame::Core::DefineTiles();
//for (auto& tile : result)
//std:: cout << tile << std::endl;
auto* window = new CaveGame::ClientApp::CaveGameWindow("Re-CaveGame", 800, 600);
//window->SetResizable(true);
window->Run();
@@ -51,6 +56,6 @@ int main(int argc, char** argv) {
#endif
CaveGame::Logs::Info.Log("Exiting client program.");
return 0;
}
}

View File

@@ -1,11 +1,31 @@
#include <Client/CreditsWindow.hpp>
#include "ClientApp/CaveGameWindow.hpp"
#include <Core/Loggers.hpp>
#include <Client/SettingsMenu.hpp>
namespace CaveGame::ClientApp
{
CaveGameWindow::CaveGameWindow(const std::string& title, int width, int height): ReWindow::RWindow(title, width, height)
#include <Core/Explosion.hpp>
#include <Core/Loggers.hpp>
#include <Core/Player.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 {
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();
@@ -14,13 +34,30 @@ namespace CaveGame::ClientApp
CaveGameWindow::~CaveGameWindow() {
Logs::Info("Closing game window.");
DestroyWindow();
Logs::Info("Removing game objects.");
delete splash_ctx;
delete menu_ctx;
delete game_ctx;
delete wm;
Logs::Info("Closing game window.");
}
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()
@@ -29,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()
@@ -51,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()
@@ -65,17 +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->ChangeScene(this->splash_ctx);
Gameloop();
processOnClose();
@@ -91,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()
@@ -111,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..
@@ -124,6 +207,9 @@ 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();
}
@@ -143,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();
@@ -151,59 +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 (IsKeyDown(Keys::R))
game_ctx->world->RefreshAll();
if (IsMouseButtonDown(MouseButtons::Left))
{
Vector2 transformed = game_ctx->world->camera.ScreenToWorld(GetMouseCoordinates());
game_ctx->world->SetTile(transformed.x, transformed.y, Core::TileID::AIR);
game_ctx->world->SetTile(transformed.x+1, transformed.y, Core::TileID::AIR);
game_ctx->world->SetTile(transformed.x-1, transformed.y, Core::TileID::AIR);
game_ctx->world->SetTile(transformed.x, transformed.y+1, Core::TileID::AIR);
game_ctx->world->SetTile(transformed.x, transformed.y-1, Core::TileID::AIR);
}
if (IsMouseButtonDown(MouseButtons::Right))
{
Vector2 transformed = game_ctx->world->camera.ScreenToWorld(GetMouseCoordinates());
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())
@@ -212,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);
@@ -245,16 +301,26 @@ namespace CaveGame::ClientApp
// Draw floating windows.
wm->Draw();
draw_debug_info({
"Re-CaveGame - Redacted Software",
std::format("frame: {}", refresh_count),
std::format("delta: {}ms", J3ML::Math::Round(delta_time*1000)),
std::format("fps: {}", J3ML::Math::Round(refresh_rate)),
std::format("avg: {}", J3ML::Math::Round(avg_refresh_rate))
});
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)),
std::format("fps: {}", J3ML::Math::Round(refresh_rate)),
std::format("avg: {}", J3ML::Math::Round(avg_refresh_rate))
};
if (current_scene == GameSession()) {
u16 id = game_ctx->GetTileIDUnderMouse();
Core::Tile data = Tiles().Get(id);
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);
}
@@ -268,12 +334,11 @@ namespace CaveGame::ClientApp
if (glError != GL_NO_ERROR) {
Logs::Error(std::format("OpenGL Error: {}", glError));
}
GLSwapBuffers();
SwapBuffers();
}
void CaveGameWindow::OnKeyUp(const ReWindow::KeyUpEvent& ev)
{
void CaveGameWindow::OnKeyUp(const ReWindow::KeyUpEvent& ev) {
if (current_scene != nullptr)
current_scene->PassKeyInput(ev.key, false);
@@ -283,22 +348,53 @@ namespace CaveGame::ClientApp
if (ev.key == Keys::V)
render_grid = !render_grid;
if (ev.key == Keys::Grave) {
// TODO: Key Input Hierarchy: Determine and handle priority of where certain keys go.
console_window->SetOpen(!console_window->IsOpen());
return;
}
//RWindow::OnKeyUp(ev);
wm->ObserveKeyInput(ev.key, false);
}
void CaveGameWindow::OnKeyDown(const ReWindow::KeyDownEvent &ev) {
if (ev.key == Keys::Escape)
if (current_scene == game_ctx) {
game_ctx->SaveAndExit();
ChangeScene(menu_ctx);
}
//Die();
if (current_scene == GameSession()) {
if (ev.key == Keys::F5)
GameSession()->World()->RefreshAll();
if (ev.key == Keys::F7)
GameSession()->World()->SetTileSimulationEnabled(!GameSession()->World()->GetTileSimulationEnabled());
if (ev.key == Keys::F6) {
std::srand(std::time(nullptr));
GameSession()->World()->SetSeed(std::rand());
}
if (ev.key == Keys::Escape) {
// TODO: "Pause menu"
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);
}
}
if (ev.key == Keys::Grave)
return;
if (current_scene != nullptr)
current_scene->PassKeyInput(ev.key, true);
wm->ObserveKeyInput(ev.key, true);
}
@@ -341,29 +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::Die() { wanna_die = true; }
void CaveGameWindow::OnMouseWheel(const ReWindow::MouseWheelEvent &ev) {
if (current_scene)
current_scene->PassMouseWheel(ev.WheelMovement);
bool CaveGameWindow::InGameSession() const {
return (current_scene == game_ctx);
// wm->ObserveMouseWheel(ev.WheelMovement);
}
void CaveGameWindow::Die() { wanna_die = true; }
bool CaveGameWindow::InMainMenu() const {
return (current_scene == menu_ctx);
}
@@ -373,14 +472,203 @@ 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 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)
{
OnConsoleCommandInput(msg);
};
}
bool CaveGameWindow::InGame() const {
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

@@ -15,7 +15,10 @@ target_include_directories(CaveCore PUBLIC "include")
set_target_properties(CaveCore PROPERTIES LINKER_LANGUAGE CXX)
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)
target_link_libraries(CaveCore PUBLIC J3ML JGL jjx mcolor Sockets jstick)

View File

@@ -0,0 +1,56 @@
#pragma once
namespace CaveGame::Core
{
/// A biome defines a region in the world with distinct characteristics, such as:
// Background textures / effects
// Terrain Generation
// Entity Spawns
// Generated Structures / Dungeons
// Music / Ambiance
// Weather
// Physics Modifiers
// Multiple biomes may be occur in a place at a time.
class Biome { };
#define BIOME static const Biome
/// List of biomes.
namespace Biomes {
/// The world is split into horizontal slices, each controlled by a higher-level "Elevation Biome"
#pragma region Elevation Biomes
BIOME Space; // -1000 < Y
BIOME Surface; // 1000 < Y <= -1000
BIOME Subsurface; // 2000 < Y <= 1000
BIOME Underground; // 10000 < T <= 2000
BIOME Deep; // 50000 < Y <= 10000
BIOME Mantle; // Y <= 50000
#pragma endregion
#pragma region Environmental Biomes
BIOME Grassland;
BIOME Forest;
BIOME DenseForest;
BIOME Tundra;
BIOME Taiga;
BIOME RedwoodForest;
BIOME WinterWonderland;
BIOME AshForest;
BIOME Desert;
BIOME Badlands;
BIOME BloodForest;
BIOME BlackForest;
BIOME Mountains;
BIOME SuperMountains;
BIOME Marsh;
BIOME Lake;
BIOME ContaminationZome;
BIOME Jungle;
BIOME FungusForest;
#pragma endregion
}
}

View File

@@ -5,7 +5,7 @@
/// This work is dedicated to the public domain.
/// @file Chunk.hpp
/// @desc A subdivision of the gameworld into discretized blocks.
/// @desc A subdivision of the game-world into discrete blocks.
/// @edit 12/2/2024
#pragma once
@@ -13,94 +13,140 @@
#include <J3ML/Geometry/QuadTree.hpp>
#include "Tile.hpp"
#include "Data.hpp"
#include "Serialization.hpp"
#include <filesystem>
#include <fstream>
#include <bitset>
namespace CaveGame::Core
{
class Chunk {
public:
static constexpr int ChunkSize = 256;
static constexpr int ChunkSize = 128;
public:
/// The default constructor does not initialize any members.
Chunk() {}
Chunk() = default;
~Chunk() = default;
~Chunk()
{
}
/// 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_coords);
explicit Chunk(const Vector2i &cell);
explicit Chunk(const Vector2 &cell_coords, const std::filesystem::path &file) : Chunk(cell_coords)
{
std::ifstream inp;
/// Constructs a chunk from a filesystem path. This constructor will load and parse the chunk file.
explicit Chunk(const Vector2i &cell, const std::filesystem::path &file);
inp.open(file, std::ios::binary | std::ios::in);
inp.seekg(0, std::ios::end);
int length = inp.tellg();
inp.seekg(0, std::ios::beg);
char buffer[length];
inp.read(buffer, length);
inp.close();
SetData(buffer);
/// Returns the TileID enumeration that occupies the given tile-cell.
[[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];
}
TileID GetTile(int x, int y) const;
void SetTile(int x, int y, TileID t);
inline TileID GetTile(const Vector2i& coords) const { return GetTile(coords.x, coords.y); }
[[nodiscard]] Vector2 GetChunkCell() const;
[[nodiscard]] Vector2 GetChunkRealCoordinates() const;
/// Sets the tile ID at the given tile-cell.
/// @param trigger_tile_updates Whether to force adjacent tiles to update.
void SetTile(int x, int y, TileID t, bool trigger_tile_updates = true);
/// Sets the tile ID at the given tile-cell, and explicitly does not update adjacent tiles.
void SetTileSilent(int x, int y, TileID t);
/// Returns the value of the update flag field at the given tile-cell.
[[nodiscard]] bool GetTileUpdateFlag(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.
void SetTileUpdateFlag(int x, int y, bool flag = true);
/// Returns the "chunk coordinates" that this chunk occupies.
[[nodiscard]] Vector2i GetChunkCell() const;
/// Returns the world-coordinates of the top-leftmost tile of this chunk.
[[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);
[[nodiscard]] std::vector<TileID> DataContiguous() const
{
std::vector<TileID> data;
data.reserve(ChunkSize*ChunkSize);
for (int x = 0; x < ChunkSize; x++) {
for (int y = 0; y < ChunkSize; y++) {
data.push_back(tiles[x][y]);
}
}
return data;
}
void SetData(char* buffer)
{
auto* data = reinterpret_cast<TileID*>(buffer);
for (int x = 0; x < ChunkSize; x++)
{
for (int y = 0; y < ChunkSize; y++)
{
tiles[x][y] = data[y+(x*ChunkSize)];
write_u8(buf, (uint8_t)GetTile(x, y));
}
}
}
[[nodiscard]] static constexpr std::size_t BufferSizeBytes()
{
return (ChunkSize*ChunkSize)*sizeof(TileID);
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++) {
SetTile(x, y, (TileID) read_u8(buf));
}
}
}
[[nodiscard]] static constexpr std::size_t BufferSize()
void SetData(char* buffer);
[[nodiscard]] static constexpr std::size_t BufferSizeBytes();
[[nodiscard]] static constexpr std::size_t BufferSize();
void SwapTileUpdateBuffers();
[[nodiscard]] bool FirstPassCompleted() 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)
{
return (ChunkSize*ChunkSize);
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];
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()
{
return (ChunkSize*ChunkSize)*sizeof(TileID);
}
constexpr std::size_t Chunk::BufferSize()
{
return (ChunkSize*ChunkSize);
}
}

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.
@@ -44,6 +41,18 @@ namespace CaveGame::Core
condition_variable.notify_one();
}
bool contains(const T& value)
{
std::unique_lock<std::mutex> lock(this->mutex);
std::queue<T, Container> copy = queue;
while(!copy.empty())
if (copy.front() == value)
return true;
else
copy.pop();
return false;
}
void push(const T& e)
{
std::unique_lock<std::mutex> lock(mutex);
@@ -60,7 +69,7 @@ namespace CaveGame::Core
}
bool empty() {
std::unique_lock<std::mutex> lock(this->mutex);
//std::unique_lock<std::mutex> lock(this->mutex);
return queue.empty();
}
@@ -127,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,29 +2,32 @@
#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,
STONE,
DIRT,
MUD,
DIRT, MUD,
GRASS,
MOSSY_STONE, LIMESTONE, BASALT, COBBLESTONE,
MOSSY_STONE,
LIMESTONE,
BASALT,
COBBLESTONE,
WET_CEMENT,
CEMENT,
CONCRETE,
MUD_BRICK,
CLAY_BRICK,
STONE_BRICK,
SANDSTONE,
SAND_BRICK,
GRANITE,
SMOOTH_GRANTITE,
SLATE,
@@ -34,17 +37,24 @@ namespace CaveGame::Core
GYPSUM,
PUMICE,
QUARTZ,
MARBLE,
BRIMSTONE,
OBSIDIAN,
HARDENED_CLAY, CLAY,
SILT, LOAM, QUAGMIRE, ASH,
GLOWY_GRASS, DRY_GRASS, DEAD_GRASS, GMO_GRASS, FAKE_GRASS,
MOSS,
GREEN_MOSS, RED_MOSS, BROWN_MOSS, LAVA_MOSS,
SNOW, PACKED_SNOW,
ICE, PACKED_ICE, ICE_BRICK, THIN_ICE,
SAND, WET_SAND, WHITE_SAND, BLACK_SAND, GRAVEL, DUST,
SLUSH,
SAND, WET_SAND,
SANDSTONE, SAND_BRICK,
RED_SAND, RED_SANDSTONE, RED_SAND_BRICK,
WHITE_SAND, WHITE_SANDSTONE, WHITE_SAND_BRICK,
BLACK_SAND, BLACK_SANDSTONE, BLACK_SAND_BRICK,
GRAVEL,
DUST,
GLASS, CRACKED_GLASS,
RED_STAINED_GLASS,
ORANGE_STAINED_GLASS,
@@ -112,6 +122,10 @@ namespace CaveGame::Core
COPPER_ORE,
IRON_ORE,
COAL_ORE,
LEAD_ORE,
SILVER_ORE,
GOLD_ORE,
TUNGSTEN_ORE,
NICKEL_ORE,
CHROMIUM_ORE,
TITANIUM_ORE,
@@ -120,21 +134,27 @@ namespace CaveGame::Core
URANIUM_ORE,
MAGNETITE_ORE,
METEORITE_ORE,
PLATINUM_ORE,
COBALT_ORE,
SOVITE, PYRITE, CINNABAR,
CLOUD,
WATER, LAVA, SLUDGE, OIL,
BLOOD, PISS, HONEY, ECTOPLASM,
COPPER_WIRE, GOLD_WIRE,
RED_WIRE, GREEN_WIRE,
YELLOW_WIRE, BLUE_WIRE,
AND_GATE, NOR_GATE, XOR_GATE,
LED,
TORCH_BASE,
TORCH_EMBER,
TORCH_EMBER = 256,
VOID = 65535,
};
};*/
}

View File

@@ -1,23 +1,58 @@
#pragma once
#include <Event.h>
#include <J3ML/LinearAlgebra.hpp>
#include <vector>
#include "Color4.hpp"
#include "Interfaces.hpp"
#include "Loggers.hpp"
#include "SimpleAABBSolver.hpp"
namespace CaveGame::Core {
class StatusEffect {
};
class Lifeform {
public:
virtual int Health() const;
virtual void SetHealth(int hp);
};
class DamageSource {
};
class Entity {
public:
int Health() const { return health; }
int MaxHealth() const { return max_health; }
Event<> Died;
explicit Entity(const Vector2& spawnPoint);
int Health() const;
int MaxHealth() const;
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;
@@ -25,71 +60,21 @@ 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:
};
class PhysicsEntity : public Entity {
public:
[[nodiscard]] Vector2 Velocity() const { return velocity; }
[[nodiscard]] Vector2 EstimatedNextPosition() const { return next_position; }
protected:
Vector2 velocity;
Vector2 next_position;
float mass;
private:
};
class Humanoid : public PhysicsEntity {
public:
virtual int BaseDefense() const;
virtual int ArmorDefense() const
{
}
virtual int DefenseModifier() const;
int Defense() const
{
}
protected:
};
class Projectile : public PhysicsEntity
{
};
class Arrow : public Projectile {};
class Fireball : public Projectile {};
class Icecicle : public Projectile {};
class Player : public Humanoid {
public:
void Jump();
void Climb();
void Descend();
void Crouch();
void WalkLeft();
void WalkRight();
protected:
};
class Zombie : public Humanoid {
public:
protected:
};
class ItemStack : PhysicsEntity {};
class PhysicalParticle {};
}
}

View File

@@ -1,8 +1,60 @@
//
// Created by dawsh on 9/26/24.
//
#pragma once
#include "Sprite.hpp"
#include "Entity.hpp"
#include <JGL/types/RenderTarget.h>
#include <Core/TileRegistry.hpp>
#ifndef RECAVEGAME_EXPLOSION_HPP
#define RECAVEGAME_EXPLOSION_HPP
namespace CaveGame::Core
{
#endif //RECAVEGAME_EXPLOSION_HPP
class Explosion : public Entity, public Sprite {
public:
const AABB2D SP_EXPLOSION0 {{0, 0}, {32, 32}};
const AABB2D SP_EXPLOSION1 {{32, 0}, {32, 32}};
const AABB2D SP_EXPLOSION2 {{64, 0}, {32, 32}};
const AABB2D SP_EXPLOSION3 {{96, 0}, {32, 32}};
const AABB2D SP_EXPLOSION4 {{128, 0}, {32, 32}};
Explosion(ITileMap* world, const Vector2& position, float force, float radius, float fuse = 0.f, bool damage_tiles = true) : Entity(position) {
rotation = 0; //Math::Radians( std::rand() % 360);
this->wrld = world;
this->radius = radius;
this->fuse = fuse;
}
void Detonate() {
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, Tiles()["air"].numeric_id);
}
}
}
}
bool HasDetonated() const;
void Draw() override;
void Update(float elapsed) override;
protected:
float fuse = 0.f;
float radius;
float force;
float rotation = 0;
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

@@ -0,0 +1,53 @@
#pragma once
namespace CaveGame::Core
{
class Furniture {
};
/// Furniture objects are multi-tile structures that aren't **necessarily** furniture in the normal sense, many are.
/// Furniture can be considered the CaveGame Engine's term for a contiguous structure that is larger than one tile.
/// This is differentiated from a generated structure in that each pixel of a Furniture object is bound to the whole, and cannot be broken in isolation.
namespace Furnitures {
static const Furniture Door;
static const Furniture TrapDoor;
static const Furniture Chair;
static const Furniture Table;
static const Furniture Workbench;
static const Furniture Campfire;
static const Furniture Barrel;
static const Furniture Safe;
static const Furniture Chest;
static const Furniture Furnace;
static const Furniture Fireplace;
static const Furniture Anvil;
static const Furniture HeavyWorkbench;
static const Furniture Grindstone;
static const Furniture BlastFurnace;
static const Furniture Printer3D;
static const Furniture CookingPot;
static const Furniture AlchemyStation;
static const Furniture ChemistrySet;
static const Furniture Bed;
static const Furniture PiggyBank;
static const Furniture Shelf;
static const Furniture Ladder;
static const Furniture Painting;
static const Furniture Signpost;
static const Furniture Blackboard;
static const Furniture Fridge;
static const Furniture Cooler;
static const Furniture Torch;
static const Furniture Lantern;
static const Furniture Candle;
static const Furniture Bottle;
static const Furniture CoffeeMug;
static const Furniture BeerMug;
static const Furniture Book;
static const Furniture
}
}

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

@@ -14,34 +14,157 @@ namespace CaveGame::Core
class Generator
{
public:
static constexpr int PrecomputedWhiteNoiseResolution = 2048;
/// Defines the sample count for the pre-generated RNG lookup table.
static constexpr int PrecomputedWhiteNoiseResolution = 2048;
/// Shifts the ground-level of terrain vertically by this many tiles.
#pragma region Surface Parameters
static constexpr float BaseSurfaceLvl = 100.f;
static constexpr float HeightMapHighPassAmplitude = 700.f;
static constexpr float HeightMapHighPassScale = 700.f;
static constexpr float HeightMapLowPassAmplitude = 85.f;
static constexpr float HeightMapLowPassScale = 64.f;
static constexpr float TopSoilDepth = 225.f;
static constexpr float TopSoilDepth = 345.f;
#pragma endregion
#pragma region Cave Parameters
static constexpr float CaveInputScale = 800.f;
static constexpr float CaveOutputScale = 1.25f;
static constexpr float CaveErosionRange = 0.035f;
static constexpr float CaveOutputScale = 1.20f;
static constexpr float CaveErosionRange = 0.080f;
static constexpr float CaveWhiteNoise = 0.005f;
static constexpr float CaveAdditiveInputScale = 80.f;
static constexpr float CaveAdditiveOutputScale = 0.25f;
static constexpr float CaveAdditiveOutputScale = 0.45f;
static constexpr float SurfaceCaveShrinkDepth = 100;
static constexpr float SurfaceCaveShrinkFactor = 1.5f;
#pragma endregion
#pragma region Ore Vein Parameters
const Vector2 ClayVeinHiPassScale = {200.f, 205.f};
static constexpr float ClayVeinHiPassOffset = 0.f;
static constexpr float ClayVeinHiPassOutputScale = 2.2f;
const Vector2 ClayVeinLoPassScale = {17.f, 15.f};
static constexpr float ClayVeinLoPassOffset = 420.f;
static constexpr float ClayVeinLoPassOutputScale = 1.2f;
static constexpr float ClayVeinNoise = 0.175f;
static constexpr float ClayVeinRampFactor = 2.f;
const Vector2 SiltVeinHiPassScale = {85.f, 95.f};
static constexpr float SiltVeinHiPassOffset = 5.f;
static constexpr float SiltVeinHiPassOutputScale = 2.2f;
const Vector2 SiltVeinLoPassScale = {15.f, 16.f};
static constexpr float SiltVeinLoPassOffset = 422.f;
static constexpr float SiltVeinLoPassOutputScale = 1.25f;
static constexpr float SiltVeinNoise = 0.175f;
static constexpr float SiltVeinRampFactor = 2.f;
const Vector2 StoneVeinHiPassScale = {30.f, 30.f};
static constexpr float StoneVeinHiPassOffset = 666.f;
static constexpr float StoneVeinHiPassOutputScale = 2.4f;
const Vector2 StoneVeinLoPassScale = {220.f, 220.f};
static constexpr float StoneVeinLoPassOffset = 0.5f;
static constexpr float StoneVeinLoPassOutputScale = 1.75f;
static constexpr float StoneVeinNoise = 0.25f;
static constexpr float StoneVeinRampFactor = 1.75f;
const Vector2 DirtVeinHiPassScale = {30.f, 30.f};
static constexpr float DirtVeinHiPassOffset = 12.f;
static constexpr float DirtVeinHiPassOutputScale = 1.35f;
const Vector2 DirtVeinLoPassScale = {90.f, 90.f};
static constexpr float DirtVeinLoPassOffset = 0.5f;
static constexpr float DirtVeinLoPassOutputScale = 0.75f;
const float DirtVeinNoise = 0.05f;
static constexpr float DirtVeinRampFactor = 0.5f;
const Vector2 CoalVeinHiPassScale = {20.f, 20.f};
static constexpr float CoalVeinHiPassOffset = 12.f;
static constexpr float CoalVeinHiPassOutputScale = 1.05f;
const Vector2 CoalVeinLoPassScale = {120.f, 120.f};
static constexpr float CoalVeinLoPassOffset = 0.5f;
static constexpr float CoalVeinLoPassOutputScale = 0.83f;
static constexpr float CoalVeinNoise = 0.45f;
static constexpr float CoalVeinRampFactor = 0.75f;
const Vector2 CopperVeinHiPassScale = {30.f, 30.f};
static constexpr float CopperVeinHiPassOffset = 12.f;
static constexpr float CopperVeinHiPassOutputScale = 1.35f;
const Vector2 CopperVeinLoPassScale = {90.f, 90.f};
static constexpr float CopperVeinLoPassOffset = 0.5f;
static constexpr float CopperVeinLoPassOutputScale = 0.75f;
static constexpr float CopperVeinNoise = 0.05f;
static constexpr float CopperVeinRampFactor = 0.5f;
const Vector2 TinVeinHiPassScale = {30.f, 30.f};
static constexpr float TinVeinHiPassOffset = 12.f;
static constexpr float TinVeinHiPassOutputScale = 1.35f;
const Vector2 TinVeinLoPassScale = {90.f, 90.f};
static constexpr float TinVeinLoPassOffset = 0.5f;
static constexpr float TinVeinLoPassOutputScale = 0.75f;
static constexpr float TinVeinNoise = 0.05f;
static constexpr float TinVeinRampFactor = 0.5f;
const Vector2 IronVeinHiPassScale = {30.f, 30.f};
static constexpr float IronVeinHiPassOffset = 12.f;
static constexpr float IronVeinHiPassOutputScale = 1.35f;
const Vector2 IronVeinLoPassScale = {90.f, 90.f};
static constexpr float IronVeinLoPassOffset = 0.5f;
static constexpr float IronVeinLoPassOutputScale = 0.75f;
static constexpr float IronVeinNoise = 0.05f;
static constexpr float IronVeinRampFactor = 0.5f;
#pragma endregion
public:
Generator() = default;
//Generator() = default;
explicit Generator(int seed);
TileID HeightMap(float depth, int wx, int wy);
TileID ComputeTile(int wx, int wy);
void FirstPass(Chunk& chunk);
int GetSeed() const { return seed;}
void SetSeed(int newSeed) { seed = newSeed; }
TileID HeightMap(float depth, int wx, int wy);
float Perlin(int wx, int wy, float hScale, float vScale, float offset, float outputScale);
float Octave(int wx, int wy, float hScale, float vScale, float offset, float outputScale, int octaves);
/// Returns the sum of hi+lo and (hi*lo) / div, resulting in something approximating noise iterated in 2 octaves.
float AddSumAndPartialProduct(float hi, float lo, float div);
TileID HiLoSelect(float pass, float upperBound, float lowerBound, TileID hiSelect, TileID loSelect, TileID fallback);
/// This function returns a floating point number (Usually in the range [-1, 1]) to be used as a condition for
/// whether ores should spawn at this location, given a set of noise parameters.
/// Ores are generated by computing two Perlin Noise passes, taking the sum of adding and multiplying, then adding in random noise.
/// Hi-Pass is used to define macro (larger-scale) features of ore veins, and Lo-Pass adds minor detailing for a more organic appearance.
float ComputeOre(int wx, int wy, const Vector2& hiPass, const Vector2& loPass, float hiOffset, float loOffset,
float hiScale, float loScale, float noise, float ramp);
TileID ComputeTile(int wx, int wy);
void FirstPass(Chunk* chunk);
void SecondPass(ITileMap* world, Chunk* chunk);
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?
uint PlaceTile();
int ModInt(int x, int mod) const {
x = Math::Abs(x);
@@ -52,12 +175,14 @@ namespace CaveGame::Core
return x % mod;
}
/// Returns a value between 0 and 1.
float GetPrecomputedWhiteNoise1D(int x) const;
/// Returns a value between 0 and 1.
float GetPrecomputedWhiteNoise2D(int x, int y) const;
protected:
int seed = 0;
int seed = 42069;
SimplexNoise simplex;
PerlinNoise perlin;

View File

@@ -0,0 +1,26 @@
#pragma once
#include <Core/PhysicsEntity.hpp>
namespace CaveGame::Core
{
class Humanoid : public PhysicsEntity {
public:
explicit Humanoid(const Vector2& spawnPoint);
virtual int BaseDefense() const { return 0; }
virtual int ArmorDefense() const { return 0; }
virtual int DefenseModifier() const { return 0;}
int Defense() const { return 0; }
void Update(float elapsed) override;
void PhysicsUpdate(float elapsed) override;
protected:
};
class Zombie : public Humanoid {
public:
protected:
};
}

View File

@@ -1,33 +1,31 @@
#pragma once
#include <Core/Data.hpp>
#include <cstdint>
#include <string>
namespace CaveGame::Core
{
using TileState = uint16_t;
using TileID = uint16_t;
class ITileMap
{
public:
virtual TileID GetTile(int x, int y) const = 0;
virtual void SetTile(int x, int y, TileID tile) = 0;
[[nodiscard]] virtual TileID GetTile(int x, int y) const = 0;
virtual TileState GetTileState(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;
//[[nodiscard]] virtual bool GetTileUpdateBufferFlag(int x, int y) const = 0;
//virtual void SetTileUpdateBufferFlag(int x, int y, bool flag = true);
[[nodiscard]] virtual TileState GetTileState(int x, int y) const = 0;
virtual void SetTileState(int x, int y, TileState state) = 0;
bool GrassSpreadable(int x, int y) const;
bool IsNonSolidTile(int x, int y) const;
bool GrassShouldSuffocate(int x, int y) const;
void GrassRandomTicc(int wx, int wy);
bool IsSolidTile(int x, int y) const;
bool HasAdjacentOrDiagonalAirBlock(int x, int y) const;
bool HasAdjacentAirBlock(int x, int y) const;
[[nodiscard]] bool IsNonSolidTile(int x, int y) const;
[[nodiscard]] bool IsSolidTile(int x, int y) const;
[[nodiscard]] bool HasAdjacentOrDiagonalAirBlock(int x, int y) const;
[[nodiscard]] bool HasAdjacentAirBlock(int x, int y) const;
};
}

View File

@@ -1,21 +1,40 @@
#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, ORE, TOOL, WEAPON, ARMOR, CLOTHING, FOOD, POTION, INGREDIENT, MEME, PLANT, METAL, ORGANIC, BUILDING_BLOCK,
ANY, TILE_ITEM, ORE, TOOL, WEAPON, ARMOR, CLOTHING, FOOD, POTION, INGREDIENT, MEME, PLANT, METAL, ORGANIC, BUILDING_BLOCK,
FLUID, SOIL, GAS, LIGHT_SOURCE, CRAFTING_STATION,
};
/// 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
{
@@ -68,6 +102,136 @@ namespace CaveGame::Core
std::set<std::string> BannedItems;
};
namespace StatusEffects {
}
/*
namespace Items {
static const Item Straw;
//static const Item Gel {"gel", "Gel", };
static const Item Glowstick;
//static const Item RoastChicken {"roast-chicken", "Roast Chicken"};
static const Item PumpkinPie;
static const Item ShepherdsPie;
static const Item CopperBar;
static const Item IronBar;
static const Item SilverBar;
static const Item TungstenBar;
static const Item PlatinumBar;
static const Item GoldBar;
static const Item LeadBar;
static const Item ZiggyStardust;
static const Item Nimdoc;
static const Item OccupiedDwellinator;
static const Item SawnoffShotty;
#pragma region Tools
#pragma endregion
#pragma region Weapons
static const Item GutsyBlade;
#pragma region Daggers
static const Item CopperDagger;
static const Item IronDagger;
static const Item TinDagger;
static const Item SilverDagger;
#pragma endregion
#pragma endregion
#pragma region SciFi
static const Item Shrinkray;
static const Item PortalGun;
static const Item GravityGun;
static const Item TauCannon;
static const Item PhotonSabre;
#pragma endregion
#pragma region Armor
#pragma endregion
#pragma region Accessories
static const Item KittenEars;
static const Item WolfEars;
#pragma region
#pragma region Accessories
#pragma endregion
#pragma endregion
#pragma region Foodstuff
static const Item Wheat;
static const Item Rice;
#pragma region Fruit
static const Item Apple;
static const Item Orange;
static const Item Lemon;
static const Item Carrot;
static const Item Potato;
static const Item Onion;
static const Item Banana;
static const Item MelonSlice;
static const Item Tomato;
#pragma endregion
#pragma region Vegetables
#pragma endregion
#pragma region Cooked Dishes
#pragma endregion
#pragma region Soups
#pragma region Drinks
static const Item HotCocoa;
static const Item AppleCider;
static const Item MelonJuice;
#pragma endregion
#pragma region Potions
static const Item FeatherfallPotion;
static const Item HP100Potion;
static const Item HP250Potion;
static const Item HP500Potion;
static const Item HP2XPotion;
static const Item HP5XPotion;
static const Item HP10XPotion;
#pragma endregion
#pragma region Bottles
static const Item EmptyBottle;
static const Item WaterBottle;
static const Item HoneyBottle;
static const Item IceBottle;
static const Item OilBottle;
static const Item MilkBottle;
static const Item SludgeBottle;
static const Item EctoplasmBottle;
#pragma endregion
#pragma region Buckets
static const Item EmptyBucket;
static const Item WaterBucket;
static const Item IceBucket;
static const Item LavaBucket;
static const Item MilkBucket;
static const Item OilBucket;
static const Item SludgeBucket;
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

@@ -0,0 +1,20 @@
#pragma once
#include <cstdint>
#include <Core/Item.hpp>
#include <Core/PhysicsEntity.hpp>
namespace CaveGame::Core
{
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,56 @@
#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;
extern Logger Network;
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);
void EnableAllToConsole();
void EnableAllToFile();
void DisableAllToConsole();
void DisableAllToFile();
void EnableAll();
void DisableAll();
void SetIncludeSourceLocationAll(bool inc_location);
void SetIncludeTimestampAll(bool inc_timestamp);
void EnableSourceLocationAll();
void DisableSourceLocationAll();
void EnableTimestampAll();
void DisableTimestampAll();
}

View File

@@ -1,8 +1,13 @@
#pragma once
#include <vector>
#include <string>
#include <sstream>
#include <numeric>
#include <filesystem>
namespace CaveGame::Core
{
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 read_file(const std::filesystem::path& file_path);

View File

@@ -10,7 +10,6 @@ namespace CaveGame::Core
OctaveNoise(int seed) : perlin(seed)
{ }
double Noise(int octaves, double x, double y, double z)
{
double scale = 1.f;

View File

@@ -0,0 +1,57 @@
/// 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;
[[nodiscard]] Vector2 EstimatedNextPosition() const;
void Accelerate(const Vector2& vector);
void PhysicsUpdate(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;
void Update(float elapsed) override;
virtual inline float Mass() { return 40;}
protected:
Vector2 velocity;
Vector2 next_position;
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

@@ -0,0 +1,64 @@
#pragma once
#include "Humanoid.hpp"
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(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(float elapsed) {
Accelerate({-200*elapsed, 0});
facing_left = true;
walking = true;
}
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

@@ -0,0 +1,14 @@
#pragma once
namespace CaveGame::Core
{
class Projectile : public PhysicsEntity
{
};
class Arrow : public Projectile {};
class Fireball : public Projectile {};
class Icecicle : public Projectile {};
}

View File

@@ -0,0 +1,18 @@
#pragma once
#include <algorithm>
#include <type_traits>
#include <vector>
#include <array>
#include <memory>
namespace CaveGame::Core
{
template <typename T, typename GetBox, typename Equal = std::equal_to<T>, typename = float>
class Quadtree
{
};
}

View File

@@ -0,0 +1,63 @@
#pragma once
// This next line assumes C++17; otherwise, replace it with
// your own string view implementation
#include <string_view>
#if __cplusplus >= 202002L
# include <source_location>
#endif
namespace Reflection
{
template <typename T> constexpr std::string_view type_name();
template <>
constexpr std::string_view type_name<void>()
{ return "void"; }
namespace detail {
using type_name_prober = void;
template <typename T>
constexpr std::string_view wrapped_type_name()
{
#if __cplusplus >= 202002L
return std::source_location::current().function_name();
#else
# if defined(__clang__) || defined(__GNUC__)
return __PRETTY_FUNCTION__;
# elif defined(_MSC_VER)
return __FUNCSIG__;
# else
# error "Unsupported compiler"
# endif
#endif // __cplusplus >= 202002L
}
constexpr std::size_t wrapped_type_name_prefix_length()
{
return wrapped_type_name<type_name_prober>()
.find(type_name<type_name_prober>());
}
constexpr std::size_t wrapped_type_name_suffix_length()
{
return wrapped_type_name<type_name_prober>().length()
- wrapped_type_name_prefix_length()
- type_name<type_name_prober>().length();
}
} // namespace detail
template <typename T>
constexpr std::string_view type_name()
{
constexpr auto wrapped_name = detail::wrapped_type_name<T>();
constexpr auto prefix_length = detail::wrapped_type_name_prefix_length();
constexpr auto suffix_length = detail::wrapped_type_name_suffix_length();
constexpr auto type_name_length =
wrapped_name.length() - prefix_length - suffix_length;
return wrapped_name.substr(prefix_length, type_name_length);
}
}

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

@@ -0,0 +1,12 @@
#include <cinttypes>
#include <Core/Serialization.hpp>
#pragma once
namespace CaveGame::Core
{
class Serializable {
virtual std::size_t SizeInBytes() = 0;
virtual ByteBuffer& Serialize(ByteBuffer& b) = 0;
virtual ByteBuffer& Deserialize(ByteBuffer& b) = 0;
};
}

View File

@@ -0,0 +1,94 @@
#pragma once
#include <cstdint>
#include <cstddef>
#include <string>
namespace CaveGame::Core
{
/// Primitive Serialization Functions.
/// A data container structure which keeps track of the current position.
struct Buffer {
size_t size;
size_t index;
uint8_t* data;
};
/// Converts a 16-bit unsigned integer into a floating point number. @see float_to_u16.
float u16_to_float(uint16_t value);
/// Converts a floating point number to a 16-bit unsigned integer. This in effect, drops the accuracy of the float in exchange for compression.
uint16_t float_to_u16(float);
/// Writes a 1-byte unsigned int to the buffer, in network-byte-order, and advances the index by 1.
void write_u8(Buffer& buffer, uint8_t value);
/// Writes a 2-byte unsigned int to the buffer, in network-byte-order, and advances the index by 2.
void write_u16(Buffer& buffer, uint16_t value);
/// Writes a 4-byte unsigned int to the buffer, in network-byte-order, and advances the index by 4.
void write_u32(Buffer& buffer, uint32_t value);
/// Writes a 8-byte unsigned int to the buffer, in network-byte-order, and advances the index by 8.
void write_u64(Buffer& buffer, uint64_t value);
/// Reads a 1-byte unsigned int from the buffer, from network-byte-order, and advances the index by 1.
uint8_t read_u8(Buffer& buffer);
/// Reads a 2-byte unsigned int from the buffer, from network-byte-order, and advances the index by 2.
uint16_t read_u16(Buffer& buffer);
/// Reads a 4-byte unsigned int from the buffer, from network-byte-order, and advances the index by 4.
uint32_t read_u32(Buffer& buffer);
/// Reads a 8-byte unsigned int from the buffer, from network-byte-order, and advances the index by 8.
uint64_t read_u64(Buffer& buffer);
/// Writes a 1-byte signed int to the buffer, in network-byte-order, and advances the index by 1.
void write_s8(Buffer& buffer, int8_t value);
/// Writes a 2-byte signed int to the buffer, in network-byte-order, and advances the index by 2.
void write_s16(Buffer& buffer, int16_t value);
/// Writes a 4-byte signed int to the buffer, in network-byte-order, and advances the index by 4.
void write_s32(Buffer& buffer, int32_t value);
/// Writes a 8-byte signed int to the buffer, in network-byte-order, and advances the index by 8.
void write_s64(Buffer& buffer, int64_t value);
/// Reads a 1-byte signed int from the buffer, from network-byte-order, and advances the index by 1.
int8_t read_s8(Buffer& buffer);
/// Reads a 2-byte signed int from the buffer, from network-byte-order, and advances the index by 2.
int16_t read_s16(Buffer& buffer);
/// Reads a 4-byte signed int from the buffer, from network-byte-order, and advances the index by 4.
int32_t read_s32(Buffer& buffer);
/// Reads a 8-byte signed int from the buffer, from network-byte-order, and advances the index by 8.
int64_t read_s64(Buffer& buffer);
/// Writes a half-precision float to the buffer, via reinterpreting as uint16_t, and advances the index by 2.
void write_f16(Buffer& buffer, float value);
/// Writes a 4-byte float to the buffer, via reinterpreting as uint32_t, and advances the index by 4.
void write_f32(Buffer& buffer, float value);
/// Writes a 8-byte float to the buffer, via reinterpreting as uint64_t, and advances the index by 8.
void write_f64(Buffer& buffer, double value);
/// Reads a half-precision float from the buffer, via reinterpreting from uint16_t, and advances the index by 2.
float read_f16(Buffer& buffer);
/// Reads a 4-byte float from the buffer, via reinterpreting from uint32_t, and advances the index by 4.
float read_f32(Buffer& buffer);
/// 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, unsigned int length);
void write_string(Buffer& buffer, std::string value);
std::string read_string(Buffer& buffer);
class ByteBuffer
{
Buffer buff;
/// Write a uint8_t.
ByteBuffer& operator<<(uint8_t& val);
/// Write a uint16_t.
ByteBuffer& operator<<(uint16_t& val);
/// Write a uint32_t.
ByteBuffer& operator<<(uint32_t& val);
};
}

View File

@@ -71,16 +71,20 @@ namespace CaveGame::Core
static LineSegTestResult LineSegment2Dvs(LineSegment2D s1, LineSegment2D s2);
bool AABB2Dvs(const Vector2& posA, const Vector2& sizeA, const Vector2& posB, const Vector2& sizeB);
bool AABB2Dvs(const AABB2D& a, const AABB2D& b);
bool AABB2DvsPoint(const Vector2& pos, const Vector2& size, const Vector2& point);
static Vector2 SolveAABB(const Vector2& posA, const Vector2& sizeA, const Vector2& posB, const Vector2& sizeB);
bool AABB2DvsPoint(const AABB2D& box, const Vector2& point);
static Vector2 GetNormalForAABB(const Vector2& separation, const Vector2& velocity);
Vector2 SolveAABBvsPoint(const Vector2& pos, const Vector2& size, const Vector2& point);
static Vector2 SolveAABB(const AABB2D& a, const AABB2D& b);
Vector2 SolveAABB(const Vector2& posA, const Vector2& sizeA, const Vector2& posB, const Vector2& sizeB);
Vector2 GetNormalForAABB(const Vector2& separation, const Vector2& velocity);
Vector2 SolveAABB(const AABB2D& a, const AABB2D& b);
}
}

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

@@ -0,0 +1,33 @@
#pragma once
namespace CaveGame::Core
{
class StatusEffect {};
namespace Effects {
static const StatusEffect Comfy;
static const StatusEffect Hungry;
static const StatusEffect Thirsty;
static const StatusEffect Starving;
static const StatusEffect Dehydrated;
static const StatusEffect WellFed;
static const StatusEffect Bleeding;
static const StatusEffect Healing;
static const StatusEffect Poisoned;
static const StatusEffect Irradiated;
static const StatusEffect Glowing;
static const StatusEffect Burning;
static const StatusEffect Frozen;
static const StatusEffect Hot;
static const StatusEffect Cold;
static const StatusEffect Frostbite;
static const StatusEffect HeatStroke;
static const StatusEffect BrokenBone;
static const StatusEffect Dizzy;
static const StatusEffect Drunk;
static const StatusEffect Stoned;
static const StatusEffect Blind;
};
}

View File

@@ -0,0 +1,38 @@
#pragma once
namespace CaveGame::Core
{
/// A structure is a multi-tile object that is usually generated into the terrain.
// TODO: Structure Designer
class Structure {};
#define STRUCTURE static const Structure
/// List of structures in-game.
namespace Structures
{
STRUCTURE TreeGrove;
STRUCTURE LargeTree;
STRUCTURE MegaTree;
STRUCTURE Meadow;
STRUCTURE Pyramid;
STRUCTURE SkyIsland;
STRUCTURE UndergroundCabin;
STRUCTURE RuinedHouse;
STRUCTURE Shrine;
STRUCTURE Castle;
STRUCTURE SandCastle;
STRUCTURE IceFortress;
STRUCTURE Campsite;
STRUCTURE Settlement;
STRUCTURE Village;
STRUCTURE Mineshaft;
STRUCTURE SatanicTemple;
STRUCTURE LavaTube;
STRUCTURE
};
}

View File

@@ -4,15 +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
{
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
@@ -86,108 +144,332 @@ namespace CaveGame::Core
};
*/
enum TileFlags : uint16_t
{
SOLID,
NONSOLID = !SOLID,
};
// Use this for space-efficiency and convert to Color3 for doing color manipulations.
// Struct allocation is cheaper.
struct Light3
{
struct Light3 {
uint8_t r;
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){
}},
{"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 TileGroup {};
class Tile
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) {
if (Condition(world, x, y))
Perform(world, x, y);
}
};
/// 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
{
protected:
TileID numeric_id = TileID::VOID;
std::string mnemonic_id;
std::string display_name = "";
public:
Color4 base_color;
std::vector<Color4> color_pallet;
bool collides;
bool does_random_ticc;
bool has_color_pallet = false;
bool solid = true;
public:
Tile(TileID numeric, const std::string& name, Color4 color)
: numeric_id(numeric), mnemonic_id(name), base_color(color)
{}
virtual TileID NumericID() const { return numeric_id; };
virtual std::string MnemonicID() const { return mnemonic_id; };
bool DoesRandomTicc() const { return does_random_ticc; }
virtual Color4 ComputeColor(TileState state, int x, int y) {}
virtual void ForcedTicc(ITileMap* world, TileState state, int x, int y) {}
virtual void RandomTicc(ITileMap* world, TileState state, int x, int y) {}
template <TileID TDecaysTo>
void DecayTo(ITileMap *world, TileState state, int x, int y)
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)
{
world->SetTile(x, y, TDecaysTo);
// TODO: Calling JGL requires being in the client code.
// Maybe have Client/Tile.cpp implementation?
//JGL::J2D::DrawPoint()
}
void DecayTo(ITileMap *world, TileState state, int x, int y, TileID TDecaysTo)
{
world->SetTile(x, y, TDecaysTo);
[[nodiscard]] TileID NumericID() const;
[[nodiscard]] std::string MnemonicID() const;
std::string Name() const;
virtual bool DoesRandomTicc() const { return false; }
virtual bool DoesForcedTicc() const { return false;}
virtual bool Solid() const { return false;}
virtual void ForcedTicc(ITileMap* world, TileState state, int x, int y) {}
virtual void RandomTicc(ITileMap* world, TileState state, int x, int y) {}
virtual void DecayTo(ITileMap *world, TileState state, int x, int y, TileID TDecaysTo);
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) { return false;}
};*/
/*class LiquidTile : public Tile
{
public:
LiquidTile();
LiquidTile(TileID id, const std::string& name, const Color4& color, const std::vector<Color4>& pallet);
LiquidTile(TileID numeric, const std::string& name, Color4 color);
bool Solid() const override { return true;}
bool DoesForcedTicc() const override { return true;}
void ForcedTicc(ITileMap *world, TileState state, int x, int y) override {
// Supremely schizophrenic water solving which still doesn't 100% work.
// Please fix this...
TileID below = world->GetTile(x, y+1);
TileID above = world->GetTile(x, y-1);
TileID left = world->GetTile(x-1, y);
TileID right = world->GetTile(x+1, y);
TileID lookaheadLeft = world->GetTile(x-2, y);
TileID lookaheadRight = world->GetTile(x+2, y);
bool rng_roll = rand() % 2 == 0;
if (below == TileID::AIR) {
if (world->GetTile(x, y+2) == TileID::AIR) {
world->SetTile(x, y+2, numeric_id);
world->SetTile(x, y, TileID::AIR);
return;
}
world->SetTile(x, y+1, numeric_id);
world->SetTile(x, y, TileID::AIR);
return;
}
//if (below == TileID::WATER) {
/*
if (world->GetTile(rng_roll ? (x + 1) : (x - 1), y) == TileID::AIR)
{
if (world->GetTile(rng_roll ? (x + 2) : (x - 2), y) == TileID::AIR)
{
if (world->GetTile(rng_roll ? (x + 3) : (x - 3), y) == TileID::AIR)
{
world->SwapTile(x, y, rng_roll ? (x + 3) : (x - 3), y);
return;
}
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;
TileID rollleft = !rng_roll ? right : left;
if (rollright == TileID::AIR) {
world->SwapTile(x, y, rng_roll ? (x + 1) : (x - 1), y);
return;
//} else if (world->GetTile(!rng_roll ? (x + 1) : (x - 1), y) == TileID::AIR)
} else if (rollleft == TileID::AIR)
{
world->SwapTile(x, y, !rng_roll ? (x + 1) : (x - 1), y);
return;
}
if ((lookaheadRight == right) && (right == TileID::AIR)) {
world->SetTile(x +1 , y, TileID::WATER);
world->SetTile(x +2 , y, TileID::WATER);
return;
}
if ((lookaheadLeft == left) && (left == TileID::AIR)){
world->SetTile(x -1 , y, TileID::WATER);
world->SetTile(x -2 , y, TileID::WATER);
return;
}
if (below == TileID::WATER && above == TileID::AIR) {
//if (world->GetTile(x + 1, y) == TileID::AIR && world->GetTile(x + 2, y) == TileID::WATER) {
if (right == TileID::AIR && lookaheadRight == TileID::WATER) {
world->SetTile(x + 1, y, rng_roll ? TileID::WATER : TileID::AIR);
return;
}
//if (world->GetTile(x - 1, y) == TileID::AIR && world->GetTile(x - 2, y) == TileID::WATER) {
if (left == TileID::AIR && lookaheadLeft == TileID::WATER) {
world->SetTile(x - 1, y, rng_roll ? TileID::WATER : TileID::AIR);
return;
}
}
// }
/*if (world->GetTile(x-1, y) == TileID::AIR) {
if (world->GetTile(x-2, y) == TileID::AIR) {
world->SetTile(x-2, y, numeric_id);
world->SetTile(x, y, TileID::AIR);
return;
}
world->SetTile(x-1, y, numeric_id);
world->SetTile(x, y, TileID::AIR);
return;
}
if (world->GetTile(x+1, y) == TileID::AIR) {
if (world->GetTile(x+2, y) == TileID::AIR) {
world->SetTile(x+2, y, numeric_id);
world->SetTile(x, y, TileID::AIR);
return;
}
world->SetTile(x+1, y, numeric_id);
world->SetTile(x, y, TileID::AIR);
return;
}
if (world->GetTile(x+1, y+1) == TileID::AIR) {
world->SetTile(x+1, y+1, numeric_id);
world->SetTile(x, y, TileID::AIR);
return;
}
if (world->GetTile(x-1, y+1) == TileID::AIR) {
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);
//world->SetTile(x, y, TileID::AIR);
//return;
//}
if (world->GetTile(x-1, y) == TileID::AIR) {
world->SetTile(x-1, y, numeric_id);
world->SetTile(x, y, TileID::AIR);
return;
}
if (world->GetTile(x+1, y) == TileID::AIR) {
world->SetTile(x+1, y, numeric_id);
world->SetTile(x, y, TileID::AIR);
return;
}
}
};
class WaterTile : public LiquidTile
{
public:
WaterTile();
WaterTile(TileID id, const std::string& name, const Color4& color);
void ForcedTicc(ITileMap* world, TileState state, int x, int y) override {
if (world->GetTile(x, y-1) == TileID::LAVA) {
world->SetTile(x, y-1, TileID::OBSIDIAN);
world->SetTile(x, y, TileID::AIR);
return;
}
if (world->GetTile(x, y+1) == TileID::LAVA) {
world->SetTile(x, y+1, TileID::OBSIDIAN);
world->SetTile(x, y, TileID::AIR);
return;
}
if (world->GetTile(x-1, y) == TileID::LAVA) {
world->SetTile(x-1, y, TileID::OBSIDIAN);
world->SetTile(x, y, TileID::AIR);
return;
}
if (world->GetTile(x+1, y) == TileID::LAVA) {
world->SetTile(x+1, y, TileID::OBSIDIAN);
world->SetTile(x, y, TileID::AIR);
return;
}
LiquidTile::ForcedTicc(world, state, x, y);
};
bool DoesForcedTicc() const override { return true;}
};
class SoilTile : public Tile
{
public:
TileID decays_to;
SoilTile(TileID numeric, const std::string& name, Color4 color, TileID decays_target)
: Tile(numeric, name, color)
{
decays_to = decays_target;
does_random_ticc = true;
}
void RandomTicc(ITileMap *world, TileState state, int x, int y) override
{
if (!world->HasAdjacentOrDiagonalAirBlock(x, y))
DecayTo(world, state, x, y, decays_to);
}
//void CanGrowGrass
SoilTile();
SoilTile(TileID id, const std::string& name, const Color4& color, const std::vector<Color4>& pallet, TileID decays_target);
SoilTile(TileID numeric, const std::string& name, Color4 color, TileID decays_target);
bool DoesRandomTicc() const override { return true; }
void RandomTicc(ITileMap *world, TileState state, int x, int y) override;
bool SpreadCheck(ITileMap* world, TileState state, int x, int y, TileID spreads_to) override;
};
class DirtTile : public SoilTile
{
};
class GrassyTile : public SoilTile
{
class GrassyTile : public SoilTile {
public:
GrassyTile(TileID numeric, const std::string& name, Color4 color)
: SoilTile(numeric, name, color, TileID::DIRT)
{
does_random_ticc = true;
}
GrassyTile();
GrassyTile(TileID id, const std::string& name, const Color4& color, const std::vector<Color4>& pallet, TileID decays_target);
GrassyTile(TileID numeric, const std::string& name, Color4 color, TileID decays_target);
GrassyTile(TileID id, const std::string& name, const Color4& color, const std::vector<Color4>& pallet);
GrassyTile(TileID numeric, const std::string& name, Color4 color);
void RandomTicc(ITileMap *world, TileState state, int x, int y) override;
};
void RandomTicc(ITileMap *world, TileState state, int x, int y) override
{
if (!world->HasAdjacentOrDiagonalAirBlock(x, y))
{
DecayTo(world, state, x, y, decays_to);
return;
}
class MossyTile : public SoilTile {
public:
MossyTile();
MossyTile(TileID id, const std::string& name, const Color4& color, TileID decays_target);
MossyTile(TileID id, const std::string &name, const Color4 &color, const std::vector<Color4> &pallet);
MossyTile(TileID numeric, const std::string &name, Color4 color);
void RandomTicc(ITileMap *world, TileState state, int x, int y) override;
};
class VineTile : public Tile {
public:
VineTile();
VineTile(TileID id, const std::string& name, const Color4& color);
VineTile(TileID id, const std::string& name, const Color4& color, const std::vector<Color4>& pallet);
bool Solid() const override { return true;}
bool DoesRandomTicc() const override { return true; }
void ForcedTicc(ITileMap* world, TileState state, int x, int y) override;
void RandomTicc(ITileMap* world, TileState state, int x, int y) override;
bool DecayCheck(ITileMap* world, TileState state, int x, int y, TileID decays_to) override;
bool ShouldSpread(ITileMap* world, int x, int y, TileID spreads_to) const override;
bool SpreadCheck(ITileMap* world, TileState state, int x, int y, TileID spreads_to) override;
};
class GravityTile : public Tile {
public:
GravityTile();
GravityTile(TileID id, const std::string& name, const Color4& color);
GravityTile(TileID id, const std::string& name, const Color4& color, const std::vector<Color4>& pallet);
inline bool DoesForcedTicc() const override { return true;}
inline bool DoesRandomTicc() const override { return true;}
inline void ForcedTicc(ITileMap* world, TileState state, int x, int y) override;
};
class SaplingTile : public Tile {
public:
world->GrassRandomTicc(x, y);
}
};
class GasTile : public Tile
@@ -195,17 +477,166 @@ namespace CaveGame::Core
};
// TODO: Constexpr-qualify Color4
void DoGrassRandomTicc(ITileMap *world, TileState state, int x, int y);
#define TILE static const Tile
void RegisterTile(Tile* data);
Tile* GetByNumeric(TileID id);
/*namespace Tiles {
using ID = Core::TileID;
static const Tile Void {ID::VOID, "Void", Colors::Transparent};
Tile* GetByName(const std::string& name);
static const Tile Air {ID::AIR, "Air", Colors::Transparent};
static const Tile Stone {ID::STONE, "Stone", Colors::Grays::SlateGray, {Colors::Grays::SlateGray, Colors::Grays::LightSlateGray}};
static const Tile Dirt {ID::DIRT, "Dirt", Colors::Browns::Chocolate, {Colors::Browns::Chocolate, {210, 125, 30},{195, 105, 40}}};
static const Tile Mud {ID::MUD, "Mud", Colors::Browns::SaddleBrown};
static const Tile Limestone {ID::LIMESTONE, "Limestone", Colors::Yellows::PaleGoldenrod};
static const Tile Basalt {ID::BASALT, "Basalt", Colors::Grays::DimGray};
static const Tile Cobblestone {ID::COBBLESTONE, "Cobblestone", Colors::Grays::SlateGray, {Colors::Grays::SlateGray, Colors::Grays::LightSlateGray, {64, 64, 64}, {92, 92, 92}}};
static const MossyTile RedMoss {ID::RED_MOSS, "Red Moss ", Colors::Reds::Crimson, ID::STONE};
static const MossyTile BrownMoss {ID::BROWN_MOSS, "Brown Moss", Colors::Browns::Brown, ID::STONE};
static const MossyTile GreenMoss {ID::GREEN_MOSS, "Green Moss", Colors::Greens::LimeGreen, ID::STONE};
static const MossyTile LavaMoss {ID::LAVA_MOSS, "Lava Moss", Colors::Reds::LightCoral, ID::STONE};
static const Tile Granite {ID::GRANITE, "Granite", Colors::Whites::AntiqueWhite};
static const Tile Marble {ID::MARBLE, "Marble", Colors::Whites::GhostWhite};
static const GrassyTile Grass {ID::GRASS, "Grass", Colors::Greens::LawnGreen,
{{126, 252, 5}, {122, 238, 0}, {124, 248, 12}}};
static const GrassyTile GlowyGrass {ID::GLOWY_GRASS, "Glowy Grass", Colors::Blues::PowderBlue};
void DefineTiles();
static const VineTile Vine {ID::VINE, "Vine", Colors::Greens::ForestGreen};
}
static const GravityTile Sand {ID::SAND, "Sand", Colors::Yellows::PaleGoldenrod, {{238, 232, 170}, {232, 238, 160}, {218, 212, 175}}};
static const Tile WhiteSand {ID::WHITE_SAND, "White Sand", Colors::Whites::SeaShell};
static const Tile RedSand {ID::RED_SAND, "Red Sand", Colors::Reds::Firebrick};
static const Tile BlackSand {ID::BLACK_SAND, "Black Sand", Colors::Black};
static const Tile Sandstone {ID::SANDSTONE, "Sandstone", Colors::Yellows::PaleGoldenrod};
static const Tile WhiteSandstone {ID::WHITE_SANDSTONE, "White Sandstone", Colors::Whites::SeaShell};
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};
static const Tile Snow {ID::SNOW, "Snow", Colors::Whites::MintCream};
static const Tile Ice {ID::ICE, "Ice", Colors::Blues::LightSteelBlue};
static const Tile Slush {ID::SLUSH, "Slush", Colors::Blues::LightBlue};
static const Tile Cactus {ID::CACTUS, "Cactus", Colors::Greens::Olive};
static const Tile CactusFlower {ID::BLOOMING_CACTUS, "Cactus Flower", Colors::Reds::Salmon};
static const Tile Bamboo;
static const Tile Sugarcane;
static const Tile VineFlower;
static const Tile FlowerStem;
static const Tile RedFlowerPetal;
static const Tile BlueFlowerPetal;
static const Tile YellowFlowerPetal;
static const Tile WhiteFlowerPetal;
static const Tile CopperWire {ID::COPPER_WIRE, "Copper Wire", Colors::Oranges::DarkOrange};
static const Tile GoldWire {ID::GOLD_WIRE, "Gold Wire", Colors::Oranges::Gold};
static const Tile RedWire {ID::RED_WIRE, "Red Wire", Colors::Red};
static const Tile GreenWire {ID::GREEN_WIRE, "Green Wire", Colors::Green};
static const Tile YellowWire {ID::YELLOW_WIRE, "Yellow Wire", Colors::Yellow};
static const Tile BlueWire {ID::BLUE_WIRE, "Blue Wire", Colors::Blue};
static const Tile LED;
static const Tile TNT;
static const Tile Cloud {ID::CLOUD, "Cloud", Colors::Whites::Azure};
static const Tile ThinIce;
static const Tile RainCloud;
static const Tile Cobweb;
static const Tile Coal {ID::COAL_ORE, "Coal Ore", Colors::Black};
// TODO: Add AluminiumOre
// TODO: Add ChromiumOre
// TODO: Add NickelOre
// TODO: Add ArsenicOre
// TODO: Add GalliumOre
static const Tile CopperOre {ID::COPPER_ORE, "Copper Ore", Colors::Oranges::DarkOrange, {{255, 140, 0}, {234, 150, 3}, {241, 138, 5}}};
static const Tile TinOre {ID::TIN_ORE, "Tin Ore", Colors::Grays::Gainsboro};
static const Tile IronOre {ID::IRON_ORE, "Iron Ore", Colors::Pinks::LightPink};
static const Tile LeadOre {ID::LEAD_ORE, "Lead Ore", Colors::Grays::DarkSlateGray};
static const Tile SilverOre {ID::SILVER_ORE, "Silver Ore", Colors::Grays::Silver};
static const Tile TungstenOre {ID::TUNGSTEN_ORE, "Tungsten Ore", Colors::Grays::DimGray};
static const Tile GoldOre {ID::GOLD_ORE, "Gold Ore", Colors::Oranges::Gold};
static const Tile PlatinumOre {ID::PLATINUM_ORE, "Platinum Ore", Colors::Grays::Silver};
static const Tile CobaltOre {ID::COBALT_ORE, "Cobalt Ore", Colors::Blues::MediumSlateBlue};
static const Tile TitaniumOre {ID::TITANIUM_ORE, "Titanium Ore", Colors::Grays::DimGray};
static const Tile UraniumOre {ID::URANIUM_ORE, "Uranium Ore", Colors::Greens::GreenYellow};
static const Tile Diamond;
static const Tile Emerald;
static const Tile Amethyst;
static const Tile Topaz;
static const Tile Sapphire;
static const Tile Ruby;
static const Tile Amber;
static const Tile Obsidian {ID::OBSIDIAN, "Obsidian", Colors::Purples::DarkMagenta};
static const SaplingTile OakSapling;
static const Tile OakLog {ID::OAK_LOG, "Oak Log", Colors::Browns::BurlyWood};
static const Tile OakPlank {ID::OAK_PLANK, "Oak Plank", Colors::Browns::BurlyWood};
static const Tile OakPlatform;
static const Tile OakLeaf;
static const Tile AshLog;
static const Tile AshLeaf;
static const Tile AshPlank;
static const Tile PalmLog;
static const Tile PalmLeaf;
static const Tile PalmPlank;
static const Tile PineLog;
static const Tile PinePlank;
static const Tile PinePlatform;
static const Tile PineLeaf;
static const Tile RedwoodLog;
static const Tile RedwoodPlank;
static const Tile RedwoodPlatform;
static const Tile RedwoodLeaf;
static const Tile EbonyLog;
static const Tile EbonyPlank;
static const Tile EbonyPlatform;
static const Tile EbonyLeaf;
static const Tile MapleLog;
static const Tile MapleLeaf;
static const Tile MaplePlank;
static const Tile BirchLog;
static const Tile BirchLeaf;
static const Tile BirchPlank;
static const WaterTile Water {ID::WATER, "Water", Colors::Blues::DodgerBlue};
static const LiquidTile Blood;
static const LiquidTile Sludge;
static const LiquidTile Lava {ID::LAVA, "Lava", Colors::Red};
static const LiquidTile MuddyWater;
static const LiquidTile Ectoplasm;
static const LiquidTile Oil;
static const LiquidTile Honey;
static const LiquidTile Milk;
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();
}

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