Compare commits
20 Commits
841ff371da
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 83f744cf68 | |||
| af04a9b313 | |||
| fb6f902f28 | |||
| b060784bb7 | |||
| 9a2e03b146 | |||
| b81296a4f2 | |||
| 554d6198fd | |||
| 70b5277f87 | |||
| 3375f56492 | |||
| 03ee76e0a5 | |||
| a7f11f8842 | |||
| 3cec6a5541 | |||
| 1f0a1d4f47 | |||
| 2d230ecd2e | |||
| 2a58c3b24b | |||
| 475fb5a847 | |||
| 06d2cbdc12 | |||
| 6ed9a15177 | |||
|
|
a9121b58a0 | ||
|
|
83ba5699ac |
41
.github/workflows/build.yml
vendored
41
.github/workflows/build.yml
vendored
@@ -1,41 +0,0 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Game
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Project
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: "recursive"
|
||||
|
||||
- name: "Install dependencies"
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y \
|
||||
gnome-desktop-testing libasound2-dev libpulse-dev libaudio-dev libjack-dev libsndio-dev \
|
||||
libusb-1.0-0-dev libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev \
|
||||
libxss-dev libxtst-dev libwayland-dev libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev \
|
||||
libgles2-mesa-dev libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev
|
||||
|
||||
- name: CMake Build
|
||||
run: |
|
||||
mkdir build
|
||||
cmake -S . -B ./build -DSDL_UNIX_CONSOLE_BUILD=ON
|
||||
make -C build
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: linux-build
|
||||
path: build/snivy
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
build/
|
||||
build-web/
|
||||
concept/
|
||||
resources/
|
||||
release/
|
||||
out/
|
||||
|
||||
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
@@ -6,7 +6,7 @@
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "cmake: build debug",
|
||||
"program": "${workspaceFolder}/out/build/linux-debug/bin/Debug/snivy",
|
||||
"program": "${workspaceFolder}/out/build/linux-debug/bin/Debug/shweets-sim",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/out/build/linux-debug/bin/Debug",
|
||||
@@ -26,7 +26,7 @@
|
||||
],
|
||||
"windows": {
|
||||
"type": "cppvsdbg",
|
||||
"program": "${workspaceFolder}/out/build/x64-Debug/bin/Debug/snivy.exe",
|
||||
"program": "${workspaceFolder}/out/build/x64-Debug/bin/Debug/shweets-sim.exe",
|
||||
"cwd": "${workspaceFolder}/out/build/x64-Debug/bin/Debug"
|
||||
}
|
||||
},
|
||||
@@ -36,7 +36,7 @@
|
||||
"request": "launch",
|
||||
"noDebug": true,
|
||||
"preLaunchTask": "cmake: build release",
|
||||
"program": "${workspaceFolder}/out/build/linux-release/bin/Release/snivy",
|
||||
"program": "${workspaceFolder}/out/build/linux-release/bin/Release/shweets-sim",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/out/build/linux-release/bin/Release",
|
||||
"environment": [],
|
||||
@@ -45,7 +45,7 @@
|
||||
"miDebuggerPath": "/usr/bin/gdb",
|
||||
"windows": {
|
||||
"type": "cppvsdbg",
|
||||
"program": "${workspaceFolder}/out/build/x64-Release/bin/Release/snivy.exe",
|
||||
"program": "${workspaceFolder}/out/build/x64-Release/bin/Release/shweets-sim.exe",
|
||||
"cwd": "${workspaceFolder}/out/build/x64-Release/bin/Release"
|
||||
}
|
||||
},
|
||||
|
||||
10
.vscode/tasks.json
vendored
10
.vscode/tasks.json
vendored
@@ -59,7 +59,7 @@
|
||||
"label": "linux: run debug",
|
||||
"type": "shell",
|
||||
"linux": {
|
||||
"command": "${workspaceFolder}/out/build/linux-debug/bin/Debug/snivy"
|
||||
"command": "${workspaceFolder}/out/build/linux-debug/bin/Debug/shweets-sim"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/out/build/linux-debug/bin/Debug"
|
||||
@@ -74,7 +74,7 @@
|
||||
"label": "linux: run release",
|
||||
"type": "shell",
|
||||
"linux": {
|
||||
"command": "${workspaceFolder}/out/build/linux-release/bin/Release/snivy"
|
||||
"command": "${workspaceFolder}/out/build/linux-release/bin/Release/shweets-sim"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/out/build/linux-release/bin/Release"
|
||||
@@ -112,7 +112,7 @@
|
||||
"label": "web: serve",
|
||||
"type": "shell",
|
||||
"linux": {
|
||||
"command": "sh -lc 'PID_FILE=/tmp/snivy-web-http.pid; LOG_FILE=/tmp/snivy-web-http.log; WEB_ROOT=out/build/web; URL=http://127.0.0.1:8000/bin/Release/index.html; [ -f \"$WEB_ROOT/bin/Release/index.html\" ] || { echo \"Web output not found at $WEB_ROOT/bin/Release/index.html (run web: build)\"; exit 1; }; if [ -f \"$PID_FILE\" ] && kill -0 \"$(cat \"$PID_FILE\")\" 2>/dev/null; then kill \"$(cat \"$PID_FILE\")\" >/dev/null 2>&1 || true; rm -f \"$PID_FILE\"; fi; nohup python3 -m http.server 8000 --bind 127.0.0.1 --directory \"$WEB_ROOT\" >\"$LOG_FILE\" 2>&1 & echo $! >\"$PID_FILE\"; echo \"Started web server on http://127.0.0.1:8000 (root: $WEB_ROOT)\"; READY=0; for _ in $(seq 1 40); do if command -v curl >/dev/null 2>&1; then curl -fsS \"$URL\" >/dev/null 2>&1 && READY=1 && break; elif command -v wget >/dev/null 2>&1; then wget -qO- \"$URL\" >/dev/null 2>&1 && READY=1 && break; fi; sleep 0.1; done; [ \"$READY\" = \"1\" ] || { echo \"Web server did not become ready. See /tmp/snivy-web-http.log\"; exit 1; }'"
|
||||
"command": "sh -lc 'PID_FILE=/tmp/shweets-sim-web-http.pid; LOG_FILE=/tmp/shweets-sim-web-http.log; WEB_ROOT=out/build/web; URL=http://127.0.0.1:8000/bin/Release/index.html; [ -f \"$WEB_ROOT/bin/Release/index.html\" ] || { echo \"Web output not found at $WEB_ROOT/bin/Release/index.html (run web: build)\"; exit 1; }; if [ -f \"$PID_FILE\" ] && kill -0 \"$(cat \"$PID_FILE\")\" 2>/dev/null; then kill \"$(cat \"$PID_FILE\")\" >/dev/null 2>&1 || true; rm -f \"$PID_FILE\"; fi; nohup python3 -m http.server 8000 --bind 127.0.0.1 --directory \"$WEB_ROOT\" >\"$LOG_FILE\" 2>&1 & echo $! >\"$PID_FILE\"; echo \"Started web server on http://127.0.0.1:8000 (root: $WEB_ROOT)\"; READY=0; for _ in $(seq 1 40); do if command -v curl >/dev/null 2>&1; then curl -fsS \"$URL\" >/dev/null 2>&1 && READY=1 && break; elif command -v wget >/dev/null 2>&1; then wget -qO- \"$URL\" >/dev/null 2>&1 && READY=1 && break; fi; sleep 0.1; done; [ \"$READY\" = \"1\" ] || { echo \"Web server did not become ready. See /tmp/shweets-sim-web-http.log\"; exit 1; }'"
|
||||
},
|
||||
"windows": {
|
||||
"command": "echo \"web tasks are configured for Linux (emsdk + python + chromium).\" && exit 1"
|
||||
@@ -123,7 +123,7 @@
|
||||
"label": "web: stop server",
|
||||
"type": "shell",
|
||||
"linux": {
|
||||
"command": "sh -lc 'PID_FILE=/tmp/snivy-web-http.pid; if [ -f \"$PID_FILE\" ] && kill -0 \"$(cat \"$PID_FILE\")\" 2>/dev/null; then kill \"$(cat \"$PID_FILE\")\" && rm -f \"$PID_FILE\" && echo \"Stopped web server\"; else rm -f \"$PID_FILE\" && echo \"Web server not running\"; fi'"
|
||||
"command": "sh -lc 'PID_FILE=/tmp/shweets-sim-web-http.pid; if [ -f \"$PID_FILE\" ] && kill -0 \"$(cat \"$PID_FILE\")\" 2>/dev/null; then kill \"$(cat \"$PID_FILE\")\" && rm -f \"$PID_FILE\" && echo \"Stopped web server\"; else rm -f \"$PID_FILE\" && echo \"Web server not running\"; fi'"
|
||||
},
|
||||
"windows": {
|
||||
"command": "echo \"web tasks are configured for Linux (emsdk + python + chromium).\" && exit 1"
|
||||
@@ -154,7 +154,7 @@
|
||||
"label": "web: run",
|
||||
"type": "shell",
|
||||
"linux": {
|
||||
"command": "sh -lc 'set -e; if ! command -v emcmake >/dev/null 2>&1; then if [ -n \"$EMSDK\" ] && [ -f \"$EMSDK/emsdk_env.sh\" ]; then . \"$EMSDK/emsdk_env.sh\" >/dev/null 2>&1; elif [ -f \"$HOME/emsdk/emsdk_env.sh\" ]; then . \"$HOME/emsdk/emsdk_env.sh\" >/dev/null 2>&1; elif [ -f \"../emsdk/emsdk_env.sh\" ]; then . \"../emsdk/emsdk_env.sh\" >/dev/null 2>&1; else echo \"Emscripten not found. Install emsdk or set EMSDK env var.\"; exit 1; fi; fi; emcmake cmake -S . -B out/build/web -DCMAKE_BUILD_TYPE=Release; cmake --build out/build/web -j$(nproc); PID_FILE=/tmp/snivy-web-http.pid; LOG_FILE=/tmp/snivy-web-http.log; WEB_ROOT=out/build/web; URL=http://127.0.0.1:8000/bin/Release/index.html; [ -f \"$WEB_ROOT/bin/Release/index.html\" ] || { echo \"Web output not found at $WEB_ROOT/bin/Release/index.html\"; exit 1; }; if [ -f \"$PID_FILE\" ] && kill -0 \"$(cat \"$PID_FILE\")\" 2>/dev/null; then kill \"$(cat \"$PID_FILE\")\" >/dev/null 2>&1 || true; rm -f \"$PID_FILE\"; fi; nohup python3 -m http.server 8000 --bind 127.0.0.1 --directory \"$WEB_ROOT\" >\"$LOG_FILE\" 2>&1 & echo $! >\"$PID_FILE\"; echo \"Started web server on http://127.0.0.1:8000 (root: $WEB_ROOT)\"; READY=0; for _ in $(seq 1 40); do if command -v curl >/dev/null 2>&1; then curl -fsS \"$URL\" >/dev/null 2>&1 && READY=1 && break; elif command -v wget >/dev/null 2>&1; then wget -qO- \"$URL\" >/dev/null 2>&1 && READY=1 && break; fi; sleep 0.1; done; [ \"$READY\" = \"1\" ] || { echo \"Web server did not become ready. See /tmp/snivy-web-http.log\"; exit 1; }; (xdg-open \"$URL\" >/dev/null 2>&1 || chromium --new-tab \"$URL\" >/dev/null 2>&1 || chromium-browser --new-tab \"$URL\" >/dev/null 2>&1 || google-chrome --new-tab \"$URL\" >/dev/null 2>&1 || google-chrome-stable --new-tab \"$URL\" >/dev/null 2>&1 || python3 -m webbrowser \"$URL\" >/dev/null 2>&1 || { echo \"Could not launch browser automatically. Open: $URL\"; exit 0; }) & sleep 0.15; echo \"Requested browser open: $URL\"'"
|
||||
"command": "sh -lc 'set -e; if ! command -v emcmake >/dev/null 2>&1; then if [ -n \"$EMSDK\" ] && [ -f \"$EMSDK/emsdk_env.sh\" ]; then . \"$EMSDK/emsdk_env.sh\" >/dev/null 2>&1; elif [ -f \"$HOME/emsdk/emsdk_env.sh\" ]; then . \"$HOME/emsdk/emsdk_env.sh\" >/dev/null 2>&1; elif [ -f \"../emsdk/emsdk_env.sh\" ]; then . \"../emsdk/emsdk_env.sh\" >/dev/null 2>&1; else echo \"Emscripten not found. Install emsdk or set EMSDK env var.\"; exit 1; fi; fi; emcmake cmake -S . -B out/build/web -DCMAKE_BUILD_TYPE=Release; cmake --build out/build/web -j$(nproc); PID_FILE=/tmp/shweets-sim-web-http.pid; LOG_FILE=/tmp/shweets-sim-web-http.log; WEB_ROOT=out/build/web; URL=http://127.0.0.1:8000/bin/Release/index.html; [ -f \"$WEB_ROOT/bin/Release/index.html\" ] || { echo \"Web output not found at $WEB_ROOT/bin/Release/index.html\"; exit 1; }; if [ -f \"$PID_FILE\" ] && kill -0 \"$(cat \"$PID_FILE\")\" 2>/dev/null; then kill \"$(cat \"$PID_FILE\")\" >/dev/null 2>&1 || true; rm -f \"$PID_FILE\"; fi; nohup python3 -m http.server 8000 --bind 127.0.0.1 --directory \"$WEB_ROOT\" >\"$LOG_FILE\" 2>&1 & echo $! >\"$PID_FILE\"; echo \"Started web server on http://127.0.0.1:8000 (root: $WEB_ROOT)\"; READY=0; for _ in $(seq 1 40); do if command -v curl >/dev/null 2>&1; then curl -fsS \"$URL\" >/dev/null 2>&1 && READY=1 && break; elif command -v wget >/dev/null 2>&1; then wget -qO- \"$URL\" >/dev/null 2>&1 && READY=1 && break; fi; sleep 0.1; done; [ \"$READY\" = \"1\" ] || { echo \"Web server did not become ready. See /tmp/shweets-sim-web-http.log\"; exit 1; }; (xdg-open \"$URL\" >/dev/null 2>&1 || chromium --new-tab \"$URL\" >/dev/null 2>&1 || chromium-browser --new-tab \"$URL\" >/dev/null 2>&1 || google-chrome --new-tab \"$URL\" >/dev/null 2>&1 || google-chrome-stable --new-tab \"$URL\" >/dev/null 2>&1 || python3 -m webbrowser \"$URL\" >/dev/null 2>&1 || { echo \"Could not launch browser automatically. Open: $URL\"; exit 0; }) & sleep 0.15; echo \"Requested browser open: $URL\"'"
|
||||
},
|
||||
"windows": {
|
||||
"command": "echo \"web tasks are configured for Linux (emsdk + python + chromium).\" && exit 1"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.27)
|
||||
project(snivy LANGUAGES C CXX)
|
||||
project(shweets-sim LANGUAGES C CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
@@ -109,10 +109,12 @@ set (TINYXML2_SOURCES ${TINYXML2_DIR}/tinyxml2.cpp)
|
||||
file(GLOB PROJECT_SRC CONFIGURE_DEPENDS
|
||||
include/*.cpp
|
||||
src/*.cpp
|
||||
src/render/*.cpp
|
||||
src/resource/*.cpp
|
||||
src/resource/xml/*.cpp
|
||||
src/state/*.cpp
|
||||
src/state/main/*.cpp
|
||||
src/state/play/*.cpp
|
||||
src/state/play/arcade/*.cpp
|
||||
src/state/select/*.cpp
|
||||
src/entity/*.cpp
|
||||
src/window/*.cpp
|
||||
@@ -130,7 +132,7 @@ add_executable(${PROJECT_NAME}
|
||||
if(WIN32 AND NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
|
||||
enable_language(RC)
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/snivy.rc"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shweets-sim.rc"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Icon.ico"
|
||||
)
|
||||
endif()
|
||||
@@ -199,21 +201,27 @@ if(NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
|
||||
endif()
|
||||
|
||||
set(PROJECT_RESOURCES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/resources")
|
||||
set(PROJECT_RESOURCES_BINARY_DIR "$<TARGET_FILE_DIR:${PROJECT_NAME}>/resources")
|
||||
set(PROJECT_RESOURCES_BINARY_DIR "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/resources")
|
||||
if(EXISTS "${PROJECT_RESOURCES_DIR}")
|
||||
file(GLOB_RECURSE PROJECT_RESOURCE_FILES CONFIGURE_DEPENDS
|
||||
"${PROJECT_RESOURCES_DIR}/*")
|
||||
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
|
||||
add_custom_target(copy_resources ALL
|
||||
list(FILTER PROJECT_RESOURCE_FILES EXCLUDE REGEX ".*/characters/[^/]+\\.zip$")
|
||||
set(COPY_RESOURCES_STAMP "${CMAKE_BINARY_DIR}/CMakeFiles/copy_resources.stamp")
|
||||
add_custom_command(
|
||||
OUTPUT "${COPY_RESOURCES_STAMP}"
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DSRC_DIR="${PROJECT_RESOURCES_DIR}"
|
||||
-DDST_DIR="${PROJECT_RESOURCES_BINARY_DIR}"
|
||||
-DSRC_DIR=${PROJECT_RESOURCES_DIR}
|
||||
-DDST_DIR=${PROJECT_RESOURCES_BINARY_DIR}
|
||||
-DSTAMP_FILE=${COPY_RESOURCES_STAMP}
|
||||
-P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/copy_resources.cmake"
|
||||
WORKING_DIRECTORY "$<TARGET_FILE_DIR:${PROJECT_NAME}>"
|
||||
DEPENDS ${PROJECT_RESOURCE_FILES}
|
||||
COMMENT "Copying resources directory")
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/copy_resources.cmake"
|
||||
COMMENT "Copying resources directory"
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(copy_resources ALL
|
||||
DEPENDS "${COPY_RESOURCES_STAMP}")
|
||||
add_dependencies(${PROJECT_NAME} copy_resources)
|
||||
endif()
|
||||
set(HAS_PROJECT_RESOURCES TRUE)
|
||||
else()
|
||||
set(HAS_PROJECT_RESOURCES FALSE)
|
||||
@@ -242,7 +250,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
|
||||
if(HAS_PROJECT_RESOURCES)
|
||||
target_link_options(${PROJECT_NAME} PRIVATE
|
||||
"--preload-file"
|
||||
"${PROJECT_RESOURCES_DIR}@resources"
|
||||
"${PROJECT_RESOURCES_BINARY_DIR}@resources"
|
||||
)
|
||||
endif()
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
@@ -253,12 +261,23 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DBIN_DIR="$<TARGET_FILE_DIR:${PROJECT_NAME}>"
|
||||
-P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/create_index_zip.cmake"
|
||||
COMMENT "Creating snivy-web.zip from Emscripten output")
|
||||
COMMENT "Creating shweets-sim-web.zip from Emscripten output")
|
||||
else()
|
||||
find_package(OpenGL REQUIRED COMPONENTS OpenGL)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE OpenGL::GL)
|
||||
endif()
|
||||
|
||||
if(WIN32 AND NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DBIN_ROOT="${CMAKE_BINARY_DIR}/bin"
|
||||
-DTARGET_DIR="$<TARGET_FILE_DIR:${PROJECT_NAME}>"
|
||||
-DEXE_FILE="$<TARGET_FILE_NAME:${PROJECT_NAME}>"
|
||||
-DPACKAGE_NAME="shweets-sim-win32"
|
||||
-P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/create_windows_zip.cmake"
|
||||
COMMENT "Creating shweets-sim-win32 package")
|
||||
endif()
|
||||
|
||||
message(STATUS "System: ${CMAKE_SYSTEM_NAME}")
|
||||
message(STATUS "Project: ${PROJECT_NAME}")
|
||||
message(STATUS "Compiler: ${CMAKE_CXX_COMPILER}")
|
||||
|
||||
BIN
Icon.ico
BIN
Icon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 6.9 KiB |
601
MODDING.md
Normal file
601
MODDING.md
Normal file
@@ -0,0 +1,601 @@
|
||||
# Modding Shweet's Sim
|
||||
|
||||
Want to add characters or modify existing ones or mod Shweet's Sim in general or otherwise want to know how the game works? Here's how.
|
||||
|
||||
# Animation Format
|
||||
|
||||
Shweet's Sim uses a semi-proprietary format called ".anm2" for tweened character animations, sourced from a spritesheet. This file format comes from the game _The Binding of Isaac: Rebirth_ (a game I mod on my own time) for animations.
|
||||
|
||||
You can either use my own animation editor for that game and this one (recommended), [Anm2Ed](https://github.com/ShweetsStuff/anm2ed), or if you have that game on Steam, you can find that game's own proprietary animation editor in that game's Steam folder and then in tools/IsaacAnimationEditor. I can't guarantee the stability or perfect efficacy of either, but Anm2Ed has crash mitigations and autosave (but of course, save often, for whatever you do).
|
||||
|
||||
Anm2Ed has slightly more extensions to the base format; in particular, something called "Regions", which allows you to set spritesheet regions (areas in the spritesheet that can be repurposed) to quickly reference in animations; previously, one would have to manually create a perfectly spaced spritesheet and then manually set values; this is no longer the case, so using "Regions" is recommended. For the leanest files, in Anm2Ed, go to Settings -> Configure -> File -> Compatibility, and set it to "Anm2Ed Limited".
|
||||
|
||||
My process for making characters is that they're typically comprised of many "cells" (small graphics). I make these cells their own file, and then in the editor, I drag them onto "Spritesheets" and then right click -> merge them, with the "Make Spritesheet Regions" option enabled. Then, right click -> Pack on a spritesheet to optimize it all nice for one big spritesheet atlas with everything in one place. It won't produce a very comprehensible spritesheet, but it works just fine (not my problem). Make sure to save the spritesheets you're using once you're done. You can sort regions around by clicking, holding and moving them around.
|
||||
|
||||
If you'd like a quick primer on how to use either program, check either of [these](https://www.youtube.com/watch?v=qU_GMo2l7NY) [videos](https://www.youtube.com/watch?v=QiXVM6Gwzlw) out. Much of the information is interchangeable between the programs, though both are a little out of date on their current version.
|
||||
|
||||
For each "stage" of the character (graphical change), an animation equivalent will be expected. Stages are zero-indexed in this context; so the first stage would be 0. If you have an idle animation and want that for the first, starting stage, you'd need to name the animation "Idle0", for example, and then reference that in character.xml (more on that later). Don't manually input the number for the animation in any file, just put in the first part, "Idle"; the game will handle which stage-animation plays when.
|
||||
|
||||
Know that despite Anm2Ed supporting sounds for animations, don't expect these to work in context based on names; use the .xml sound attributes when applicable. You can add sounds for fluff, though.
|
||||
|
||||
# Resources
|
||||
|
||||
There's two folders inside resources; "characters" and "font".
|
||||
|
||||
"font" is just a font containing the font used in the selection screen. That's literally it. This technically isn't needed; Dear ImGui has its own font that will be used if this font is missing.
|
||||
|
||||
EVERYTHING ELSE is stored inside bespoke character archives (.zips) in the "characters" folder. This has all data associated with characters. Think of characters more as tailored game experiences rather than literally being just the characters. Not only is there the character graphics, but backgrounds, items, parameters, etc. These are intensely customizable to suit whatever experience you'd like (within the confines of the engine, of course).
|
||||
|
||||
Shweet's Sim uses a collection of [XML](https://en.wikipedia.org/wiki/XML) files to parse data; make sure to brush up on the format. The engine can expect six files in the archive's root:
|
||||
|
||||
- areas.xml
|
||||
- character.xml
|
||||
- cursor.xml
|
||||
- dialogue.xml
|
||||
- items.xml
|
||||
- menu.xml
|
||||
- skill_check.xml
|
||||
|
||||
(dialogue.xml may or may not be optional; but in future updates I'll make sure of it, for dialogueless characters).
|
||||
|
||||
If you're making your own character, your best bet would probably be just to copy the Snivy character and edit it based on your needs, just as a helpful start.
|
||||
|
||||
# Character
|
||||
|
||||
## areas.xml
|
||||
"Areas" refers to the backgrounds of the game. I'd planned for the game to dynamically switch areas based on stage (in case you want a character to become a blob that grows bigger than a city or whatever) but this isn't implemented. I'd just have one entry in here for the game's background.
|
||||
|
||||
### Areas
|
||||
#### TextureRootPath (path)
|
||||
Working folder/directory of textures the areas will use.
|
||||
|
||||
#### Area
|
||||
#### Texture (path)
|
||||
The file path of the area's texture (background).
|
||||
#### Gravity (float)
|
||||
Gravity the area has; applies to items' velocities per tick.
|
||||
#### Friction (float)
|
||||
Friction of the area; applies to items' velocities when grounded or hitting walls.
|
||||
#### AirResistance (float)
|
||||
Air resistance of the area; applies to items' velocities when airborne.
|
||||
|
||||
## character.xml
|
||||
This is the main character file where much of the functionality is stored.
|
||||
|
||||
### Character
|
||||
#### Name (string)
|
||||
Name of the character; will appear in dialogue, stats, etc.
|
||||
#### TextureRootPath (path)
|
||||
Working folder/directory of where used textures will be contained within.
|
||||
#### SoundRootPath (path)
|
||||
Working folder/directory of where used sounds will be contained within.
|
||||
#### Render (path)
|
||||
Texture for the character's "render" (i.e., a typical full-body display), will show in the Select screen.
|
||||
#### Portrait (path)
|
||||
Texture for the character's "portrait" (i.e., a cropped profile view), will show in the Select screen.
|
||||
#### Anm2 (path)
|
||||
Character's "anm2" file (uses TextureRootPath); should have all the character's animations.
|
||||
#### Description (string)
|
||||
A general description of the character; will show in the Select screen.
|
||||
#### Author (string)
|
||||
The author of the character.
|
||||
#### Weight (float, kilograms)
|
||||
The character's starting weight, in kilograms.
|
||||
#### Capacity (float, calories)
|
||||
The character's starting capacity, in calories.
|
||||
#### CapacityMin (float, calories)
|
||||
The character's minimum capacity, in calories.
|
||||
#### CapacityMax (float, calories)
|
||||
The character's maximum capacity, in calories. Know that max capacity is determined by Capacity * CapacityMaxMultiplier; this determines the max of the "base" capacity.
|
||||
#### CapacityMaxMultiplier (float)
|
||||
Determines the effective max capacity; will be capacity times this number.
|
||||
#### CapacityIfOverStuffedOnDigestBonus (float, percent)
|
||||
When a character is over stuffed (i.e., over base capacity), the character will an additional capacity when digesting based on how overstuffed they are, based on how many calories over the base capacity.
|
||||
#### CaloriesToKilogram (float)
|
||||
Determines how many calories become a kilogram (1 cal -> X kg).
|
||||
#### DigestionRate (float, percent/tick)
|
||||
The base digestion rate, in percent per tick (60 ticks per second).
|
||||
#### DigestionRateMin (float, percent/tick)
|
||||
The minimum digestion rate for the character, in percent per tick.
|
||||
#### DigestionRateMax (float, percent/tick)
|
||||
The maximum digestion rate for the character, in percent per tick.
|
||||
#### DigestionTimerMax (int, ticks)
|
||||
When digesting, the digestion bar will count down, and then when it hits 0, the current calories will be digested. This determines how long this takes, in ticks.
|
||||
#### EatSpeed (float)
|
||||
A multiplier that speeds/slows down the eating animation, at base.
|
||||
#### EatSpeedMin (float)
|
||||
Determines the minimum eating speed multiplier.
|
||||
#### EatSpeedMax (float)
|
||||
Determines the maximum eating speed multiplier.
|
||||
#### GurgleChance (float, percent)
|
||||
Determines how often the character will gurgle (see the Gurgle sounds later) per tick.
|
||||
#### GurgleCapacityMultiplier (float)
|
||||
Per the character's capacity, multiplies the character's gurgle chance based on the percent of capacity filled (based on max capacity). Higher capacity = higher gurgle chance, using this number at maximum.
|
||||
#### DialoguePoolID (string)
|
||||
Determines the character's base dialogue options (for the "How are you feeling?" option in Chat). This is effectively "Stage 1"'s dialogue; each stage should have its own dialogue pool (see later). Also see dialogue.xml for how "Pools" work.
|
||||
### AlternateSpritesheet
|
||||
Determines the alternate spritesheet of the character, if applicable (in Pokemon terms, the "shiny").
|
||||
#### Texture (path)
|
||||
The alternate spritesheet texture; uses TextureRootPath.
|
||||
#### Sound (path)
|
||||
The sound that will play when the spritesheet is set to alternate (either on new game, or with an item that has IsToggleSpritesheet; see items.xml); uses SoundRootPath.
|
||||
#### ID (int)
|
||||
ID of spritesheet that the alternate spritesheet will replace in the character's .anm2.
|
||||
#### ChanceOnNewGame (float, percent)
|
||||
Chance of rolling for the alternate spritesheet on starting a new game, in percent.
|
||||
### Stages
|
||||
A "stage" represents each visual change of the character as the weight increases. By default, there's always one stage; adding more here will add additional stages.
|
||||
#### Threshold (float, kilograms)
|
||||
The weight threshold to reach this stage, in kilograms.
|
||||
#### DialoguePoolID (string)
|
||||
Determines the stage's dialogue options (for the "How are you feeling?" option in Chat). Also see dialogue.xml for how "Pools" work.
|
||||
### Animations
|
||||
The character's animations. Know that a lot of character animations are typically easily played/activated through Dialogue; this is just for animations that aren't reliant on that system. Know that _these animations should just be the base name_; all animations are expected to have a stage number after them, so don't include the number for these.
|
||||
### Start
|
||||
The animation name that will first play when starting a new game; used for like a character's introduction.
|
||||
### Idle
|
||||
The idle animation that the character will regularly return to.
|
||||
### IdleFull
|
||||
When over capacity, this idle animation will be used instead.
|
||||
### StageUp
|
||||
When going to a new stage after digestion and going over a stage's threshold, this animation will play.
|
||||
#### Animation
|
||||
The name of the animation to be used for each of these.
|
||||
### Overrides
|
||||
"Overrides" are the term I use for when some animations will be tweaked by the game engine for effect (blinking and talking being the ones used). There are two elements; "Talk" and "Blink" for this. Typically, how this is handled is that each animation with have an invisible blinking/talking layer (just the graphic that blinks), which will change when called for. Again, review the .anm2 format for what a "layer" is. All that's sourced is just the spritesheet crop in these cases; so don't worry about how the blink/talk layers are set up.
|
||||
### Override
|
||||
#### LayerSource (string)
|
||||
The layer in the animation which will be sourced for the override.
|
||||
#### LayerDestination (string)
|
||||
The layer in the animation which the source will be applied to.
|
||||
### EatAreas
|
||||
An "eat area" is where the food should be dragged to. I'm pretty much expecting this to only be a mouth for most characters but hey maybe you want the character to also eat through their butt or something. Again, not something that's well-developed at the moment.
|
||||
### EatArea
|
||||
#### Null (string)
|
||||
The null area in the animation where food will be checked for. Again, review the .anm2 format.
|
||||
#### Animation (string)
|
||||
The animation that will play when food is dragged to it.
|
||||
#### Event (string)
|
||||
The event that will be checked for, detrermining the time the food is actually eaten/chewed. Again, review the .anm2 format.
|
||||
### ExpandAreas
|
||||
The areas which will expand based on capacity percent; usually a stomach. This adds additional scale onto the layer of an animation. This also can scale a null as well.
|
||||
ExpandAreas are presently hardcoded to scale with both capacity and weight at a 50/50 ratio; this could be changed in the future for custom ratios.
|
||||
#### ExpandArea
|
||||
#### Layer (string)
|
||||
The layer of the animation that will expand.
|
||||
#### Null (string)
|
||||
The null of the animation that will expand.
|
||||
#### ScaleAdd (float, percent)
|
||||
The scale that will be added at maximum. Know that scale is a percent; a {100, 100} scale is the default and is the normal scale of an animation.
|
||||
### InteractAreas
|
||||
The areas on a character which can be interacted with; usually for belly rubs, kisses, etc.
|
||||
### InteractArea
|
||||
#### Type ("Rub", "Kiss", "Smack")
|
||||
Three types are presently hard-coded in, but this is kind of hacky and custom support for different interactions may later be added. The cursor will need to be set from the "Tools" in order for each interact area to be activated.
|
||||
#### Null (string)
|
||||
The null in which the interact area can be triggered. Again, review the .anm2 format.
|
||||
#### Animation (string)
|
||||
The animation that will play when the interact area is activated (hovering and clicking).
|
||||
#### AnimationFull (string)
|
||||
The above, but when character is full.
|
||||
#### AnimationCursorHover (string)
|
||||
The animation the cursor will play when hovering over the interact area.
|
||||
#### AnimationCursorActive (string)
|
||||
The animation the cursor will play when holding down click over the interact area.
|
||||
#### DialoguePoolID (string)
|
||||
The dialogue pool which will be drawn from when activating an interact area (see dialogue.xml).
|
||||
### Sound
|
||||
An interact area can play multiple sounds when interacting; add additional Sound elements to achieve this. Don't worry about repeatedly-loaded sounds; the game will cache them beforehand for efficiency.
|
||||
#### Path (path)
|
||||
The path of the sound being used.
|
||||
### Sounds
|
||||
Sounds that play in some contexts.
|
||||
### Digest
|
||||
Sounds that will play when the character begins digesting (digestion bar full).
|
||||
### Gurgle
|
||||
Ambient sounds that will play randomly based on capacity; see GurgleChance and GurgleCapacityMultiplier above.
|
||||
#### Sound (path)
|
||||
The path of the sound for the specific sound type.
|
||||
|
||||
## cursor.xml
|
||||
Determines the cursor appearance and behavior.
|
||||
### TextureRootPath (path)
|
||||
Working folder/directory of textures the cursor will use.
|
||||
#### SoundRootPath (path)
|
||||
Working folder/directory of sounds the cursor will use.
|
||||
#### Anm2 (path)
|
||||
The anm2 file the cursor will use (depends on TextureRootPath).
|
||||
### Animations
|
||||
### Idle
|
||||
The cursor's default idle animation.
|
||||
### Hover
|
||||
The cursor's hover animation, when hovering over an item.
|
||||
### Grab
|
||||
The cursor's grab animation, when grabbing an item.
|
||||
### Pan
|
||||
The cursor's pan animation, when panning with middle mouse button.
|
||||
### Zoom
|
||||
The cursor's zoom animation, when zooming in/out with the mouse wheel.
|
||||
### Return
|
||||
The cursor's return animation, when holding right click to return an item to the inventory (or to dispose of it if chewed.)
|
||||
#### Animation (string)
|
||||
The animation the cursor will use in these contexts.
|
||||
### Sounds
|
||||
Know that multiple of these sounds can be defined for each context.
|
||||
### Grab
|
||||
The sound that will play when grabbing an item.
|
||||
### Release
|
||||
The sound that will play when releasing an item.
|
||||
### Throw
|
||||
The sound that will play when throwing an item (releasing when dragging quickly).
|
||||
#### Sound (path)
|
||||
The path of the sound for these respective contexts.
|
||||
|
||||
## dialogue.xml
|
||||
The collection of character dialogue; likely the biggest file, depending on your needs.
|
||||
### Dialogue
|
||||
### Entries
|
||||
### Entry
|
||||
Each bit of dialogue is referred to as an "entry".
|
||||
#### ID (string)
|
||||
Name for the entry. Other entries will rely on this for dialogue chains.
|
||||
#### Next (string)
|
||||
The ID of the entry that will follow after this one. If no Next entry, the text will not continue. Make sure to connect these.
|
||||
#### Text (string)
|
||||
The actual dialogue content of the entry. Should support Unicode, provided the font contains the characters.
|
||||
#### Animation (string)
|
||||
A character animation that will play at the beginning of the dialogue. This may be expanded into the future to allow animations to play dynamically per dialogue index. Again, make sure it's just the base name for the animation, no stage numbers.
|
||||
### Choice
|
||||
Dialogue can be branching; simply added "Choice" elements into the Entry element.
|
||||
#### Next (string)
|
||||
The dialogue the choice will lead to.
|
||||
#### Text (string)
|
||||
The text of the choice; will appear as a series of buttons in the text window.
|
||||
### Pools
|
||||
A "pool" of dialogue refers to a collection of entries that can be randomly picked in some contexts.
|
||||
### Pool
|
||||
#### ID (string)
|
||||
The name of the dialogue pool; can be referenced elsewhere.
|
||||
### PoolEntry
|
||||
### ID (string)
|
||||
The name of the entry; to be added to the pool.
|
||||
|
||||
### Start
|
||||
This dialogue will play upon a new game, once its animation has concluded.
|
||||
#### Animation (string)
|
||||
The name of the animation that will be played.
|
||||
#### ID (string)
|
||||
The name of the entry that will be played.
|
||||
### End
|
||||
The dialogue that will play upon completion of the game (character hitting max stage).
|
||||
#### ID (string)
|
||||
The name of the entry that will be played.
|
||||
### Help
|
||||
The dialogue that will be play when the player presses the "Help" button in "Chat".
|
||||
#### ID (string)
|
||||
The name of the entry that will be played.
|
||||
|
||||
### StageUp
|
||||
Dialogue that will play after a character undergoes a stage up.
|
||||
#### PoolID (string)
|
||||
The name of the pool that entries will be drawn from.
|
||||
### Random
|
||||
The dialogue that will be play when the player presses the "Let's chat!" button in "Chat".
|
||||
#### PoolID (string)
|
||||
The name of the pool that entries will be drawn from.
|
||||
### Feed
|
||||
The dialogue that will be play when the user begins holding a food item.
|
||||
#### PoolID (string)
|
||||
The name of the pool that entries will be drawn from.
|
||||
### FeedFull
|
||||
The dialogue that will be play when the user begins holding a food item, when the character is over capacity.
|
||||
#### PoolID (string)
|
||||
The name of the pool that entries will be drawn from.
|
||||
### Eat
|
||||
The dialogue that will be play after the character finishes a food item.
|
||||
#### PoolID (string)
|
||||
The name of the pool that entries will be drawn from.
|
||||
### EatFull
|
||||
The dialogue that will be play after the character finishes a food item, when the character is over capacity.
|
||||
#### PoolID (string)
|
||||
The name of the pool that entries will be drawn from.
|
||||
### Full
|
||||
The dialogue that will be play when the character is completely full and will deny the user feeding them.
|
||||
#### PoolID (string)
|
||||
The name of the pool that entries will be drawn from.
|
||||
### Throw
|
||||
The dialogue that will be play when the user throws food.
|
||||
#### PoolID (string)
|
||||
The name of the pool that entries will be drawn from.
|
||||
### LowCapacity
|
||||
The dialogue that will be play when the character is presented a food item completely over their maximum capacity.
|
||||
#### PoolID (string)
|
||||
The name of the pool that entries will be drawn from.
|
||||
### Digest
|
||||
The dialogue that will be play when digesting is finished.
|
||||
#### PoolID (string)
|
||||
The name of the pool that entries will be drawn from.
|
||||
### FoodTaken
|
||||
The dialogue that will play when food is removed from the null area during a character's eating animation.
|
||||
#### PoolID (string)
|
||||
The name of the pool that entries will be drawn from.
|
||||
### FoodTakenFull
|
||||
The dialogue that will play when food is removed from the null area during a character's eating animation, when the character is over capacity.
|
||||
#### PoolID (string)
|
||||
The name of the pool that entries will be drawn from.
|
||||
|
||||
## items.xml
|
||||
Has all items and their parameters, alongside additional behavior.
|
||||
|
||||
### ItemSchema
|
||||
#### TextureRootPath (path)
|
||||
Working folder/directory of where used textures will be contained within.
|
||||
#### SoundRootPath (path)
|
||||
Working folder/directory of where used sounds will be contained within.
|
||||
#### BaseAnm2 (path)
|
||||
The base anm2 file that items will use. Items can technically have their own .anm2 and animate accordingly, but I haven't tested this much.
|
||||
### Animations
|
||||
### Chew
|
||||
When items are chewed, play this animation. Items can have chew counts and the animation will adapt accordingly, so be sure to have chew animations for chew counts; 1 chew = Chew1, 2 chew = Chew2, etc.
|
||||
#### Animation (string)
|
||||
The name of the animation. Only the base part ("Chew", usually)
|
||||
### Sounds
|
||||
The various item sounds. Multiple of the same element can be defined to randomly play from a selection.
|
||||
### Bounce
|
||||
The sound that will play when an item is bounced (hitting a floor/wall/ceiling with velocity).
|
||||
#### Sound (path)
|
||||
The path to the sound that will play, based on SoundRootPath.
|
||||
### Dispose
|
||||
The sound that will play when an item is disposed of (when an item has chewed and is right clicked).
|
||||
#### Sound (path)
|
||||
The path to the sound that will play, based on SoundRootPath.
|
||||
### Summon
|
||||
The sound that will play when an item is spawned (clicking on it in inventory).
|
||||
#### Sound (path)
|
||||
The path to the sound that will play, based on SoundRootPath.
|
||||
### Return
|
||||
The sound that will play when an item is returned to inventory (right clicking on it in world).
|
||||
#### Sound (path)
|
||||
The path to the sound that will play, based on SoundRootPath.
|
||||
### Categories
|
||||
The kinds of items present. Will appear in inventory tooltips.
|
||||
### Category
|
||||
#### Name (string)
|
||||
The name of the category.
|
||||
#### IsEdible (bool)
|
||||
Determines if the category of item can be eaten by the character.
|
||||
### Flavors
|
||||
Item flavors (for food). Honestly have no meaning but flavor text at the moment but whatever.
|
||||
### Flavor
|
||||
#### Name (string)
|
||||
Name of the flavor.
|
||||
### Rarities
|
||||
Different rarities of item. Shows in inventory and each can have their own drop chance.
|
||||
### Rarity
|
||||
#### Name (string)
|
||||
Name of the rarity.
|
||||
#### Chance (float, percent)
|
||||
Base chance of finding an item, per winning a play of the "play" game. See play.xml for how this is modified.
|
||||
#### Sound (path)
|
||||
Sound that will play when an item is found in the "play" game.
|
||||
#### IsHidden (bool)
|
||||
If true, items with this rarity will not be previewed in the inventory, unlesss possessed. Assumed false if not present.
|
||||
### Items
|
||||
#### TextureRootPath (path)
|
||||
Working folder/directory of where used item textures will be contained within.
|
||||
#### ChewCount (int)
|
||||
Base chew count for items; will be chewed this many times before being erased.
|
||||
Chew count will determine how many calories/digestion bonus/etc. are given per bite (0 chew = all, 2 chew = divided by 3)
|
||||
#### SpritesheetID (int)
|
||||
Item textures will use this spritesheet ID on the base anm2.
|
||||
#### QuantityMax (int)
|
||||
How many items of a type can be possessed at once.
|
||||
### Item
|
||||
An individual item entry.
|
||||
#### Name (string)
|
||||
The name of the item.
|
||||
#### Texture (path)
|
||||
The texture the item uses; uses TextureRootPath.
|
||||
#### Description (string)
|
||||
Flavor text for the item.
|
||||
#### Category (string)
|
||||
The category the item uses; as defined in Categories.
|
||||
#### Rarity (string)
|
||||
The rarity the item uses; as defined in Rarities.
|
||||
#### Anm2 (path; optional)
|
||||
The custom anm2 the item uses, if needed.
|
||||
#### Flavor (string; optional)
|
||||
The flavor the item uses; as defined in Flavors.
|
||||
#### Calories (float; optional)
|
||||
The amount of calories in an item has (if food).
|
||||
#### DigestionBonus (float, percent; optional)
|
||||
The additional digestion rate in percent the item will give if eaten.
|
||||
#### EatSpeedBonus (float; optional)
|
||||
The additional eat speed multiplier the item will give if eaten.
|
||||
#### Gravity (float; optional)
|
||||
The item's gravity; will use the default gravity if not available.
|
||||
#### ChewCount (int; optional)
|
||||
An item's custom chew count.
|
||||
#### UpgradeID (string; optional)
|
||||
The name of another item that this item will be able to be upgraded to.
|
||||
#### UpgradeCount (int; optional)
|
||||
The amount of this item it will take to upgrade to the upgrade item specified in UpgradeID.
|
||||
#### IsPlayReward (bool; optional)
|
||||
The item will be given out when the reward is hit in play (see play.xml)
|
||||
#### IsToggleSpritesheet (bool; optional)
|
||||
When used in the inventory, will toggle the character's spritesheet (in Pokemon terms, toggling normal/shiny palettes).
|
||||
|
||||
## menu.xml
|
||||
Determines menu and general UI appearance and behavior.
|
||||
|
||||
### MenuSchema
|
||||
#### SoundRootPath (path)
|
||||
Working folder/directory of where used sounds will be contained within.
|
||||
#### FontRootPath (path)
|
||||
Working folder/directory of where used fonts will be contained within.
|
||||
#### Font (path)
|
||||
The font the menu will use, based on FontRootPath.
|
||||
#### Rounding (float)
|
||||
The rounding of corners the UI will use.
|
||||
### Sounds
|
||||
Menu sounds.
|
||||
### Open
|
||||
Will play when opening a sliding menu panel.
|
||||
#### Sound (path)
|
||||
The sound that will play, based on SoundRootPath.
|
||||
### Close
|
||||
Will play when closing a sliding menu panel.
|
||||
#### Sound (path)
|
||||
The sound that will play, based on SoundRootPath.
|
||||
### Hover
|
||||
Will play when hovering over a widget in a menu.
|
||||
#### Sound (path)
|
||||
The sound that will play, based on SoundRootPath.
|
||||
### Select
|
||||
Will play when clicking on a widget in a menu.
|
||||
#### Sound (path)
|
||||
The sound that will play, based on SoundRootPath.
|
||||
### CheatsActivated
|
||||
Sound that will play after entering a special code to activate cheats.
|
||||
#### Sound (path)
|
||||
The sound that will play, based on SoundRootPath.
|
||||
|
||||
## skill_check.xml
|
||||
Determines behavior and appearance of the "Skill Check" minigame.
|
||||
|
||||
### SkillCheck
|
||||
#### SoundRootPath (path)
|
||||
Working folder/directory of where used sounds will be contained within.
|
||||
#### RewardScore (int)
|
||||
The play score where a rewarded item will be given (see items.xml)
|
||||
#### RewardScoreBonus (float, percent)
|
||||
Based on the player's score, will add additional bonus to being rewarded items.
|
||||
#### RewardGradeBonus (float, percent)
|
||||
Based on the player's grades for a round of play, will add additional bonus to being rewarded items.
|
||||
#### SpeedMin (float, unit/tick)
|
||||
The beginning/base speed of the bar in the minigame.
|
||||
#### SpeedMax (float, unit/tick)
|
||||
The maximum speed the bar in the minigame can achieve.
|
||||
#### SpeedScoreBonus (float)
|
||||
The additional speed of the bar based on the player's score.
|
||||
#### RangeBase (float)
|
||||
A "range" is each area in the minigame that can be hit. This is the base size of one. Per each grade, it's halved. Range size decreases as minigame progresses.
|
||||
#### RangeMin (float)
|
||||
The minimum size of a range; achievable at high scores.
|
||||
#### RangeScoreBonus (float)
|
||||
Really a negative. The range size will decrease this much per point of score.
|
||||
#### EndTimerMax (int)
|
||||
The period of time between plays of the minigame.
|
||||
#### EndTimerFailureMax (int)
|
||||
When a player fails (no ranges hit), the minigame will pause fo this amount of time, until restarting.
|
||||
### Sounds
|
||||
### Fall
|
||||
The sound that plays when an item falls from being rewarded.
|
||||
#### Sound (path)
|
||||
The sound that will play, based on SoundRootPath.
|
||||
### ScoreLoss
|
||||
The sound that plays when the bar goes over the edge and loops, deducting a point.
|
||||
#### Sound (path)
|
||||
The sound that will play, based on SoundRootPath.
|
||||
### HighScore
|
||||
The sound that plays when the player achieves a high score. Only heard when a high score has been set during the play session.
|
||||
#### Sound (path)
|
||||
The sound that will play, based on SoundRootPath.
|
||||
### HighScoreLoss
|
||||
The sound that plays when the player has achieved a high score, and then fails.
|
||||
#### Sound (path)
|
||||
The sound that will play, based on SoundRootPath.
|
||||
### RewardScore
|
||||
The sound that plays when the player has hit the reward score.
|
||||
#### Sound (path)
|
||||
The sound that will play, based on SoundRootPath.
|
||||
### Grades
|
||||
A "grade" is a rank the player can get. This determines the count of ranges in the minigame; each range corresponds to a grade.
|
||||
### Grade
|
||||
#### Name (string)
|
||||
The name of the grade; will appear when the player hits its respective range in the minigame.
|
||||
#### NamePlural (string)
|
||||
The plural name of the grade; will appear in Stats.
|
||||
#### Value (int)
|
||||
The reward value of the grade, in points.
|
||||
#### Weight (float)
|
||||
The "weight" of the grade; used to determine accuracy score in Stats.
|
||||
#### IsFailure (bool)
|
||||
If the player hits this grade's respective range, will count as failure.
|
||||
#### DialoguePoolID (string; optional)
|
||||
If the player hits this grade, the character will speak dialogue from this pool (see dialogue.xml)
|
||||
#### Sound (path)
|
||||
This sound will play when the grade is hit.
|
||||
|
||||
# Saves
|
||||
|
||||
Outside of resources, Shweet's Sim also has a few files it writes outside of the game. You will find these in %AppData%/shweets-sim on Windows and ~/.local/share/shweets-sim on Linux.
|
||||
|
||||
## settings.xml
|
||||
Stores general game settings and configuration; beyond invididual characters.
|
||||
### MeasurementSystem ("Imperial", "Metric")
|
||||
Determines measurement system (kg/lb).
|
||||
### Volume (int, percent)
|
||||
Master volume.
|
||||
### ColorR, ColorG, ColorB (float, 0-1)
|
||||
Red/Green/Blue components of menu color.
|
||||
### WindowX, WindowY (int)
|
||||
Window position.
|
||||
### WindowW/H (int)
|
||||
Window size.
|
||||
|
||||
## *.save
|
||||
Saves are per-character; will be named "[blank].save", stored in "saves" folder, from the name of the character's archive.
|
||||
|
||||
### Save
|
||||
#### IsPostgame (bool)
|
||||
Determines if the game has been completed (character's max stage has been reached); if true, will enable cheats.
|
||||
#### IsAlternateSpritesheet (bool)
|
||||
Determines if the character's spritesheet is using the alternate version (in Pokemon, would be the "shiny" version
|
||||
|
||||
### Character
|
||||
#### Weight (float)
|
||||
Character's current weight, in kilograms.
|
||||
#### Calories (float)
|
||||
Character's current consumed calories.
|
||||
#### Capacity (float)
|
||||
Character's capacity, in calories. Remember, max capacity effectively is capacity * CapacityMaxMultiplier (from character.xml)
|
||||
#### DigestionRate (float, percent)
|
||||
Character's digestion rate in percent, per game tick (game ticks 60 times per second).
|
||||
#### EatSpeed (float)
|
||||
Eat speed multiplier for the character's Eat animation.
|
||||
#### IsDigesting (bool)
|
||||
Determines if character is currently digesting (when the bar is going down)
|
||||
#### DigestionProgress (float, percent)
|
||||
Character's digestion progress. Digestion max is always 100%.
|
||||
#### DigestionTimer (int)
|
||||
When digestion bar is going down, this is the remaining time to 0 (in ticks)
|
||||
#### TotalCaloriesConsumed (float)
|
||||
Total calories consumed by the character, per save file.
|
||||
#### TotalFoodItemsEaten (int)
|
||||
How many food items have been completely consumed by the character, per save file.
|
||||
### SkillCheck
|
||||
#### TotalPlays (int)
|
||||
However many times the "play" game has been attempted (hitting the bar counts as one "play")
|
||||
#### HighScore (int)
|
||||
Highest score the player has achieved in the "play" game.
|
||||
#### BestCombo (int)
|
||||
Highest combo the player has achieved (how many successful hits the player has gotten in one session)
|
||||
#### Grades
|
||||
Play grades are the ratings the game gives based on where the player hit.
|
||||
##### ID (int)
|
||||
ID of grade being tracked (see play.xml)
|
||||
##### Count (int)
|
||||
How many times the grade has been hit.
|
||||
|
||||
### Inventory
|
||||
Items.
|
||||
#### ID (int)
|
||||
ID of item being tracked (see items.xml)
|
||||
#### Quantity (int)
|
||||
Count of the item.
|
||||
|
||||
|
||||
# Conclusion
|
||||
|
||||
Hopefully this'll give you the resources you need to start making your own characters. If you need any help with this guide, or with clarification on anything, I'm available. Additionally, the game is [licensed as free software](https://github.com/ShweetsStuff/shweets-sim), meaning if you're stuck the code should give you a clue (though I apologize for the lack of comments. Self-documenting code though, am I right? :^) )
|
||||
@@ -1,8 +1,8 @@
|
||||
# Feed Snivy
|
||||
# Shweet's Sim
|
||||
|
||||

|
||||
|
||||
This Is A Video Game Where You Feed The Snivy.
|
||||
Interactive character simulation game.
|
||||
|
||||
[Resources](https://shweetz.net/files/games/feed-snivy/resources.zip)
|
||||
|
||||
|
||||
@@ -1,31 +1,41 @@
|
||||
if(NOT DEFINED SRC_DIR OR NOT DEFINED DST_DIR)
|
||||
message(FATAL_ERROR "SRC_DIR and DST_DIR must be defined")
|
||||
if(NOT DEFINED SRC_DIR OR NOT DEFINED DST_DIR OR NOT DEFINED STAMP_FILE)
|
||||
message(FATAL_ERROR "SRC_DIR, DST_DIR, and STAMP_FILE must be defined")
|
||||
endif()
|
||||
|
||||
set(CHARACTERS_DIR "${SRC_DIR}/characters")
|
||||
set(CHARACTERS_ZIP_SCRIPT "${CHARACTERS_DIR}/zip")
|
||||
|
||||
if(EXISTS "${CHARACTERS_ZIP_SCRIPT}")
|
||||
execute_process(
|
||||
COMMAND "${CHARACTERS_ZIP_SCRIPT}"
|
||||
WORKING_DIRECTORY "${CHARACTERS_DIR}"
|
||||
RESULT_VARIABLE ZIP_SCRIPT_RESULT
|
||||
)
|
||||
if(NOT ZIP_SCRIPT_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Failed running ${CHARACTERS_ZIP_SCRIPT} (exit code ${ZIP_SCRIPT_RESULT})")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
file(REMOVE_RECURSE "${DST_DIR}")
|
||||
file(MAKE_DIRECTORY "${DST_DIR}")
|
||||
|
||||
# Copy all resources except characters/ contents.
|
||||
# Copy all resources except character source folders and any stray character zips.
|
||||
file(COPY "${SRC_DIR}/" DESTINATION "${DST_DIR}"
|
||||
PATTERN "characters/*" EXCLUDE)
|
||||
PATTERN "characters/*" EXCLUDE
|
||||
PATTERN "characters/*.zip" EXCLUDE)
|
||||
|
||||
# Copy only .zip archives from resources/characters.
|
||||
file(MAKE_DIRECTORY "${DST_DIR}/characters")
|
||||
file(GLOB CHARACTER_ZIPS "${CHARACTERS_DIR}/*.zip")
|
||||
if(CHARACTER_ZIPS)
|
||||
file(COPY ${CHARACTER_ZIPS} DESTINATION "${DST_DIR}/characters")
|
||||
if(EXISTS "${CHARACTERS_DIR}")
|
||||
file(GLOB CHILDREN RELATIVE "${CHARACTERS_DIR}" "${CHARACTERS_DIR}/*")
|
||||
foreach(CHILD IN LISTS CHILDREN)
|
||||
set(CHARACTER_SOURCE_DIR "${CHARACTERS_DIR}/${CHILD}")
|
||||
if(NOT IS_DIRECTORY "${CHARACTER_SOURCE_DIR}")
|
||||
continue()
|
||||
endif()
|
||||
|
||||
file(GLOB_RECURSE CHARACTER_CONTENTS RELATIVE "${CHARACTER_SOURCE_DIR}" "${CHARACTER_SOURCE_DIR}/*")
|
||||
if(NOT CHARACTER_CONTENTS)
|
||||
message(STATUS "Skipping empty character directory: ${CHARACTER_SOURCE_DIR}")
|
||||
continue()
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND "${CMAKE_COMMAND}" -E tar cf "${DST_DIR}/characters/${CHILD}.zip" --format=zip ${CHARACTER_CONTENTS}
|
||||
WORKING_DIRECTORY "${CHARACTER_SOURCE_DIR}"
|
||||
RESULT_VARIABLE ZIP_GENERATE_RESULT
|
||||
)
|
||||
if(NOT ZIP_GENERATE_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Failed generating ${DST_DIR}/characters/${CHILD}.zip (exit code ${ZIP_GENERATE_RESULT})")
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
file(WRITE "${STAMP_FILE}" "resources copied\n")
|
||||
|
||||
@@ -2,7 +2,7 @@ if(NOT DEFINED BIN_DIR OR BIN_DIR STREQUAL "")
|
||||
message(FATAL_ERROR "BIN_DIR is required")
|
||||
endif()
|
||||
|
||||
set(ARCHIVE_PATH "${BIN_DIR}/snivy-web.zip")
|
||||
set(ARCHIVE_PATH "${BIN_DIR}/shweets-sim-web.zip")
|
||||
file(REMOVE "${ARCHIVE_PATH}")
|
||||
|
||||
file(GLOB INDEX_OUTPUTS "${BIN_DIR}/index.*")
|
||||
@@ -16,7 +16,7 @@ endforeach()
|
||||
|
||||
if(FILES_TO_ZIP)
|
||||
execute_process(
|
||||
COMMAND "${CMAKE_COMMAND}" -E tar cf "snivy-web.zip" --format=zip ${FILES_TO_ZIP}
|
||||
COMMAND "${CMAKE_COMMAND}" -E tar cf "shweets-sim-web.zip" --format=zip ${FILES_TO_ZIP}
|
||||
WORKING_DIRECTORY "${BIN_DIR}"
|
||||
RESULT_VARIABLE ZIP_RESULT
|
||||
)
|
||||
@@ -24,5 +24,5 @@ if(FILES_TO_ZIP)
|
||||
message(FATAL_ERROR "Failed creating ${ARCHIVE_PATH}")
|
||||
endif()
|
||||
else()
|
||||
message(WARNING "No index.* files found in ${BIN_DIR}; skipping snivy-web.zip creation")
|
||||
message(WARNING "No index.* files found in ${BIN_DIR}; skipping shweets-sim-web.zip creation")
|
||||
endif()
|
||||
|
||||
47
cmake/create_windows_zip.cmake
Normal file
47
cmake/create_windows_zip.cmake
Normal file
@@ -0,0 +1,47 @@
|
||||
if(NOT DEFINED BIN_ROOT OR BIN_ROOT STREQUAL "")
|
||||
message(FATAL_ERROR "BIN_ROOT is required")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED TARGET_DIR OR TARGET_DIR STREQUAL "")
|
||||
message(FATAL_ERROR "TARGET_DIR is required")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED EXE_FILE OR EXE_FILE STREQUAL "")
|
||||
message(FATAL_ERROR "EXE_FILE is required")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED PACKAGE_NAME OR PACKAGE_NAME STREQUAL "")
|
||||
set(PACKAGE_NAME "snivy-win32")
|
||||
endif()
|
||||
|
||||
set(EXE_PATH "${TARGET_DIR}/${EXE_FILE}")
|
||||
set(PACKAGE_DIR "${BIN_ROOT}/${PACKAGE_NAME}")
|
||||
set(ARCHIVE_PATH "${BIN_ROOT}/${PACKAGE_NAME}.zip")
|
||||
set(TARGET_RESOURCES_DIR "${TARGET_DIR}/resources")
|
||||
set(BIN_RESOURCES_DIR "${BIN_ROOT}/resources")
|
||||
|
||||
file(MAKE_DIRECTORY "${BIN_ROOT}")
|
||||
file(REMOVE_RECURSE "${PACKAGE_DIR}")
|
||||
file(REMOVE "${ARCHIVE_PATH}")
|
||||
file(MAKE_DIRECTORY "${PACKAGE_DIR}")
|
||||
|
||||
if(EXISTS "${EXE_PATH}")
|
||||
file(COPY "${EXE_PATH}" DESTINATION "${PACKAGE_DIR}")
|
||||
else()
|
||||
message(FATAL_ERROR "Executable not found: ${EXE_PATH}")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${TARGET_RESOURCES_DIR}")
|
||||
file(COPY "${TARGET_RESOURCES_DIR}" DESTINATION "${PACKAGE_DIR}")
|
||||
elseif(EXISTS "${BIN_RESOURCES_DIR}")
|
||||
file(COPY "${BIN_RESOURCES_DIR}" DESTINATION "${PACKAGE_DIR}")
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND "${CMAKE_COMMAND}" -E tar cf "${PACKAGE_NAME}.zip" --format=zip "${PACKAGE_NAME}"
|
||||
WORKING_DIRECTORY "${BIN_ROOT}"
|
||||
RESULT_VARIABLE ZIP_RESULT
|
||||
)
|
||||
if(NOT ZIP_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Failed creating ${ARCHIVE_PATH}")
|
||||
endif()
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 688 KiB |
@@ -236,17 +236,25 @@ namespace game::entity
|
||||
if (auto animation = animation_get(); animation && animation->isLoop) currentQueuedPlay = QueuedPlay{};
|
||||
|
||||
if (!queuedPlay.empty())
|
||||
{
|
||||
if (!animationMap.contains(queuedPlay.animation))
|
||||
{
|
||||
logger.error(std::string("Animation \"" + queuedPlay.animation + "\" does not exist! Unable to play!"));
|
||||
if (!defaultAnimation.empty()) queue_default_animation();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& index = animationMap.at(queuedPlay.animation);
|
||||
if (queuedPlay.isPlayAfterAnimation)
|
||||
nextQueuedPlay = queuedPlay;
|
||||
else if (index != animationIndex && currentQueuedPlay.isInterruptible)
|
||||
else if ((state == STOPPED || index != animationIndex) && currentQueuedPlay.isInterruptible)
|
||||
{
|
||||
play(queuedPlay.animation, queuedPlay.mode, queuedPlay.time, queuedPlay.speedMultiplier);
|
||||
currentQueuedPlay = queuedPlay;
|
||||
}
|
||||
queuedPlay = QueuedPlay{};
|
||||
}
|
||||
}
|
||||
|
||||
auto animation = animation_get();
|
||||
if (!animation || animation->frameNum == 1 || mode == SET || state == STOPPED) return;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include "../canvas.hpp"
|
||||
#include "../render/canvas.hpp"
|
||||
#include "../resource/xml/anm2.hpp"
|
||||
|
||||
namespace game::entity
|
||||
|
||||
@@ -22,10 +22,10 @@ namespace game::entity
|
||||
auto& save = data.save;
|
||||
auto saveIsValid = save.is_valid();
|
||||
|
||||
capacity = saveIsValid ? save.capacity : data.capacity;
|
||||
weight = saveIsValid ? save.weight : data.weight;
|
||||
digestionRate = saveIsValid ? save.digestionRate : data.digestionRate;
|
||||
eatSpeed = saveIsValid ? save.eatSpeed : data.eatSpeed;
|
||||
capacity = saveIsValid ? save.capacity : (double)data.capacity;
|
||||
weight = saveIsValid ? save.weight : (double)data.weight;
|
||||
digestionRate = saveIsValid ? save.digestionRate : (double)data.digestionRate;
|
||||
eatSpeed = saveIsValid ? save.eatSpeed : (double)data.eatSpeed;
|
||||
|
||||
calories = saveIsValid ? save.calories : 0;
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace game::entity
|
||||
|
||||
float Character::weight_get(measurement::System system)
|
||||
{
|
||||
return system == measurement::IMPERIAL ? weight * (float)measurement::KG_TO_LB : weight;
|
||||
return system == measurement::IMPERIAL ? weight * (double)measurement::KG_TO_LB : weight;
|
||||
}
|
||||
|
||||
int Character::stage_from_weight_get(float checkWeight) const
|
||||
@@ -99,7 +99,7 @@ namespace game::entity
|
||||
{
|
||||
if (stageIndex == -1) stageIndex = this->stage;
|
||||
|
||||
float threshold = data.weight;
|
||||
double threshold = data.weight;
|
||||
|
||||
if (!data.stages.empty())
|
||||
{
|
||||
@@ -111,7 +111,7 @@ namespace game::entity
|
||||
threshold = data.stages[stageIndex - 1].threshold;
|
||||
}
|
||||
|
||||
return system == measurement::IMPERIAL ? threshold * (float)measurement::KG_TO_LB : threshold;
|
||||
return system == measurement::IMPERIAL ? threshold * (double)measurement::KG_TO_LB : threshold;
|
||||
}
|
||||
|
||||
float Character::stage_threshold_next_get(measurement::System system) const
|
||||
@@ -122,23 +122,35 @@ namespace game::entity
|
||||
float Character::stage_progress_get()
|
||||
{
|
||||
auto currentStage = stage_get();
|
||||
if (currentStage >= stage_max_get()) return 1.0f;
|
||||
if (currentStage >= stage_max_get()) return 1.0;
|
||||
|
||||
auto currentThreshold = stage_threshold_get(currentStage);
|
||||
auto nextThreshold = stage_threshold_get(currentStage + 1);
|
||||
if (nextThreshold <= currentThreshold) return 1.0f;
|
||||
if (nextThreshold <= currentThreshold) return 1.0;
|
||||
|
||||
return (weight - currentThreshold) / (nextThreshold - currentThreshold);
|
||||
}
|
||||
|
||||
float Character::digestion_rate_get() { return digestionRate * 60; }
|
||||
float Character::digestion_rate_get() { return digestionRate * 60.0; }
|
||||
|
||||
float Character::max_capacity() const { return capacity * data.capacityMaxMultiplier; }
|
||||
bool Character::is_over_capacity() const { return calories > capacity; }
|
||||
bool Character::is_max_capacity() const { return calories >= max_capacity(); }
|
||||
float Character::capacity_percent_get() const { return calories / max_capacity(); }
|
||||
|
||||
std::string Character::animation_name_convert(const std::string& name) { return std::format("{}{}", name, stage); }
|
||||
std::string Character::animation_append_id_get() const
|
||||
{
|
||||
if (stage <= 0) return {};
|
||||
auto stageIndex = stage - 1;
|
||||
if (stageIndex < 0 || stageIndex >= (int)data.stages.size()) return {};
|
||||
return data.stages[stageIndex].animationAppendID;
|
||||
}
|
||||
|
||||
std::string Character::animation_name_convert(const std::string& name)
|
||||
{
|
||||
auto appendID = animation_append_id_get();
|
||||
return appendID.empty() ? name : name + appendID;
|
||||
}
|
||||
void Character::play_convert(const std::string& animation, Mode playMode, float startAtTime,
|
||||
float speedMultiplierValue)
|
||||
{
|
||||
@@ -148,8 +160,7 @@ namespace game::entity
|
||||
void Character::expand_areas_apply()
|
||||
{
|
||||
auto stageProgress = stage_progress_get();
|
||||
auto capacityProgress = isDigesting
|
||||
? (float)calories / max_capacity() * (float)digestionTimer / data.digestionTimerMax
|
||||
auto capacityProgress = isDigesting ? (double)calories / max_capacity() * (double)digestionTimer / data.digestionTimerMax
|
||||
: calories / max_capacity();
|
||||
|
||||
for (int i = 0; i < (int)data.expandAreas.size(); i++)
|
||||
@@ -158,8 +169,8 @@ namespace game::entity
|
||||
auto& overrideLayer = overrides[expandAreaOverrideLayerIDs[i]];
|
||||
auto& overrideNull = overrides[expandAreaOverrideNullIDs[i]];
|
||||
|
||||
auto stageScaleAdd = ((expandArea.scaleAdd * stageProgress) * 0.5f);
|
||||
auto capacityScaleAdd = ((expandArea.scaleAdd * capacityProgress) * 0.5f);
|
||||
auto stageScaleAdd = ((double)expandArea.scaleAdd * stageProgress) * 0.5;
|
||||
auto capacityScaleAdd = ((double)expandArea.scaleAdd * capacityProgress) * 0.5;
|
||||
|
||||
auto scaleAdd =
|
||||
glm::clamp(glm::vec2(), glm::vec2(stageScaleAdd + capacityScaleAdd), glm::vec2(expandArea.scaleAdd));
|
||||
@@ -189,7 +200,7 @@ namespace game::entity
|
||||
isStageUp = false;
|
||||
}
|
||||
|
||||
if (nextQueuedPlay.empty()) queue_idle_animation();
|
||||
if (nextQueuedPlay.empty() && !isTalking) queue_idle_animation();
|
||||
}
|
||||
|
||||
Actor::tick();
|
||||
@@ -201,20 +212,21 @@ namespace game::entity
|
||||
if (digestionTimer <= 0)
|
||||
{
|
||||
auto increment = calories * data.caloriesToKilogram;
|
||||
auto nextWeight = glm::clamp(weight + increment, data.weight, data.weightMax);
|
||||
|
||||
if (is_over_capacity())
|
||||
{
|
||||
auto capacityMaxCalorieDifference = (calories - capacity);
|
||||
auto overCapacityPercent = capacityMaxCalorieDifference / (max_capacity() - capacity);
|
||||
auto capacityIncrement =
|
||||
(overCapacityPercent * data.capacityIfOverStuffedOnDigestBonus) * capacityMaxCalorieDifference;
|
||||
capacity = glm::clamp(data.capacityMin, capacity + capacityIncrement, data.capacityMax);
|
||||
(double)((overCapacityPercent * data.capacityIfOverStuffedOnDigestBonus) * capacityMaxCalorieDifference);
|
||||
capacity = glm::clamp(capacity + capacityIncrement, (double)data.capacityMin, (double)data.capacityMax);
|
||||
}
|
||||
|
||||
totalCaloriesConsumed += calories;
|
||||
calories = 0;
|
||||
|
||||
if (auto nextStage = stage_from_weight_get(weight + increment); nextStage > stage_from_weight_get(weight))
|
||||
if (auto nextStage = stage_from_weight_get((double)nextWeight); nextStage > stage_from_weight_get(weight))
|
||||
{
|
||||
queuedPlay = QueuedPlay{};
|
||||
nextQueuedPlay = QueuedPlay{};
|
||||
@@ -226,7 +238,7 @@ namespace game::entity
|
||||
else
|
||||
isJustDigested = true;
|
||||
|
||||
weight += increment;
|
||||
weight = (double)nextWeight;
|
||||
|
||||
isDigesting = false;
|
||||
digestionTimer = data.digestionTimerMax;
|
||||
@@ -258,7 +270,7 @@ namespace game::entity
|
||||
auto talk_reset = [&]()
|
||||
{
|
||||
isTalking = false;
|
||||
talkTimer = 0.0f;
|
||||
talkTimer = 0.0;
|
||||
talkOverride.frame = FrameOptional();
|
||||
};
|
||||
|
||||
@@ -277,9 +289,9 @@ namespace game::entity
|
||||
talkOverride.frame.size = frame.size;
|
||||
talkOverride.frame.pivot = frame.pivot;
|
||||
|
||||
talkTimer += 1.0f;
|
||||
talkTimer += 1.0;
|
||||
|
||||
if (talkTimer > animationTalkDurations.at(animationIndex)) talkTimer = 0.0f;
|
||||
if (talkTimer > animationTalkDurations.at(animationIndex)) talkTimer = 0.0;
|
||||
}
|
||||
else
|
||||
talk_reset();
|
||||
@@ -301,7 +313,7 @@ namespace game::entity
|
||||
auto blink_reset = [&]()
|
||||
{
|
||||
isBlinking = false;
|
||||
blinkTimer = 0.0f;
|
||||
blinkTimer = 0.0;
|
||||
blinkOverride.frame = FrameOptional();
|
||||
};
|
||||
|
||||
@@ -320,7 +332,7 @@ namespace game::entity
|
||||
blinkOverride.frame.size = frame.size;
|
||||
blinkOverride.frame.pivot = frame.pivot;
|
||||
|
||||
blinkTimer += 1.0f;
|
||||
blinkTimer += 1.0;
|
||||
|
||||
if (blinkTimer >= blinkDuration) blink_reset();
|
||||
}
|
||||
@@ -335,24 +347,19 @@ namespace game::entity
|
||||
|
||||
void Character::queue_play(QueuedPlay play)
|
||||
{
|
||||
if (isStageUp) return;
|
||||
queuedPlay = play;
|
||||
queuedPlay.animation = animation_name_convert(queuedPlay.animation);
|
||||
}
|
||||
|
||||
void Character::queue_idle_animation()
|
||||
{
|
||||
if (isStageUp) return;
|
||||
if (data.animations.idle.empty()) return;
|
||||
queue_play(
|
||||
{is_over_capacity() && !data.animations.idleFull.empty() ? data.animations.idleFull : data.animations.idle});
|
||||
}
|
||||
|
||||
void Character::queue_interact_area_animation(resource::xml::Character::InteractArea& interactArea)
|
||||
{
|
||||
if (interactArea.animation.empty()) return;
|
||||
queue_play({is_over_capacity() && !interactArea.animationFull.empty() ? interactArea.animationFull
|
||||
: interactArea.animation});
|
||||
}
|
||||
|
||||
void Character::spritesheet_set(SpritesheetType type)
|
||||
{
|
||||
switch (type)
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace game::entity
|
||||
std::vector<int> animationBlinkDurations{};
|
||||
|
||||
bool isStageUp{};
|
||||
bool isStageUpDuring{};
|
||||
bool isJustStageUp{};
|
||||
bool isJustStageFinal{};
|
||||
|
||||
@@ -84,9 +85,9 @@ namespace game::entity
|
||||
void tick();
|
||||
void play_convert(const std::string&, Mode = PLAY, float time = 0.0f, float speedMultiplier = 1.0f);
|
||||
void queue_idle_animation();
|
||||
void queue_interact_area_animation(resource::xml::Character::InteractArea&);
|
||||
void queue_play(QueuedPlay);
|
||||
|
||||
std::string animation_append_id_get() const;
|
||||
std::string animation_name_convert(const std::string& name);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "../util/interact_type.hpp"
|
||||
#include "actor.hpp"
|
||||
|
||||
namespace game::entity
|
||||
@@ -16,7 +15,7 @@ namespace game::entity
|
||||
};
|
||||
|
||||
State state{DEFAULT};
|
||||
InteractType mode{InteractType::RUB};
|
||||
int interactTypeID{-1};
|
||||
|
||||
Cursor() = default;
|
||||
Cursor(resource::xml::Anm2&);
|
||||
|
||||
@@ -10,9 +10,9 @@ using namespace glm;
|
||||
|
||||
namespace game::entity
|
||||
{
|
||||
Item::Item(Anm2 _anm2, glm::ivec2 _position, int _schemaID, int _chewCount, int _animationIndex, glm::vec2 _velocity,
|
||||
Item::Item(Anm2 _anm2, glm::ivec2 _position, int _schemaID, int _durability, int _animationIndex, glm::vec2 _velocity,
|
||||
float _rotation)
|
||||
: Actor(_anm2, _position, SET, 0.0f, _animationIndex), schemaID(_schemaID), chewCount(_chewCount),
|
||||
: Actor(_anm2, _position, SET, 0.0f, _animationIndex), schemaID(_schemaID), durability(_durability),
|
||||
velocity(_velocity)
|
||||
{
|
||||
|
||||
|
||||
@@ -14,12 +14,12 @@ namespace game::entity
|
||||
|
||||
int schemaID{};
|
||||
int rotationOverrideID{};
|
||||
int chewCount{};
|
||||
int durability{};
|
||||
|
||||
glm::vec2 velocity{};
|
||||
float angularVelocity{};
|
||||
|
||||
Item(resource::xml::Anm2, glm::ivec2 position, int id, int chewCount = 0, int animationIndex = -1,
|
||||
Item(resource::xml::Anm2, glm::ivec2 position, int id, int durability = 0, int animationIndex = -1,
|
||||
glm::vec2 velocity = {}, float rotation = 0.0f);
|
||||
void update();
|
||||
};
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace game
|
||||
|
||||
logger.info("Initializing...");
|
||||
|
||||
if (!PHYSFS_init((argc > 0 && argv && argv[0]) ? argv[0] : "snivy"))
|
||||
if (!PHYSFS_init((argc > 0 && argv && argv[0]) ? argv[0] : "shweets-sim"))
|
||||
{
|
||||
logger.fatal(std::format("Failed to initialize PhysicsFS: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())));
|
||||
isError = true;
|
||||
@@ -85,12 +85,12 @@ namespace game
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
static constexpr glm::vec2 SIZE = {1600, 900};
|
||||
window = SDL_CreateWindow("Feed Snivy", SIZE.x, SIZE.y, SDL_WINDOW_OPENGL);
|
||||
window = SDL_CreateWindow("Shweet's Sim", SIZE.x, SIZE.y, SDL_WINDOW_OPENGL);
|
||||
#else
|
||||
|
||||
SDL_PropertiesID windowProperties = SDL_CreateProperties();
|
||||
|
||||
SDL_SetStringProperty(windowProperties, SDL_PROP_WINDOW_CREATE_TITLE_STRING, "Feed Snivy");
|
||||
SDL_SetStringProperty(windowProperties, SDL_PROP_WINDOW_CREATE_TITLE_STRING, "Shweet's Sim");
|
||||
SDL_SetNumberProperty(windowProperties, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, (long)settings.windowSize.x);
|
||||
SDL_SetNumberProperty(windowProperties, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, (long)settings.windowSize.y);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "log.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <format>
|
||||
#include <exception>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "util/imgui.hpp"
|
||||
#include "util/math.hpp"
|
||||
#include "../util/imgui.hpp"
|
||||
#include "../util/math.hpp"
|
||||
|
||||
using namespace glm;
|
||||
using namespace game::resource;
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <glad/glad.h>
|
||||
#endif
|
||||
|
||||
#include "resource/shader.hpp"
|
||||
#include "../resource/shader.hpp"
|
||||
#include <glm/ext/matrix_clip_space.hpp>
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
#include <glm/glm.hpp>
|
||||
@@ -3,7 +3,9 @@
|
||||
#include <SDL3/SDL_properties.h>
|
||||
|
||||
#include "../log.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <format>
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace game::util;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "../log.hpp"
|
||||
|
||||
#include <format>
|
||||
|
||||
using namespace game::util;
|
||||
|
||||
static ImFont* default_font_fallback_get()
|
||||
|
||||
@@ -14,11 +14,9 @@ namespace game::resource
|
||||
|
||||
public:
|
||||
static constexpr auto NORMAL = 20;
|
||||
static constexpr auto ABOVE_AVERAGE = 24;
|
||||
static constexpr auto BIG = 30;
|
||||
static constexpr auto HEADER_1 = 24;
|
||||
static constexpr auto HEADER_2 = 30;
|
||||
static constexpr auto HEADER_3 = 40;
|
||||
static constexpr auto HEADER_2 = 50;
|
||||
static constexpr auto HEADER_1 = 60;
|
||||
|
||||
Font() = default;
|
||||
Font(const std::filesystem::path&, float = NORMAL);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "../log.hpp"
|
||||
|
||||
#include <format>
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
Shader::Shader(const char* vertex, const char* fragment)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL_surface.h>
|
||||
#include <string>
|
||||
#include <format>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "../log.hpp"
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
const std::string& AnimationEntryCollection::get()
|
||||
std::string* AnimationEntryCollection::get()
|
||||
{
|
||||
return at(util::vector::random_index_weighted(*this, [](const auto& entry) { return entry.weight; })).animation;
|
||||
if (empty()) return nullptr;
|
||||
return &at(util::vector::random_index_weighted(*this, [](const auto& entry) { return entry.weight; })).animation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace game::resource::xml
|
||||
class AnimationEntryCollection : public std::vector<AnimationEntry>
|
||||
{
|
||||
public:
|
||||
const std::string& get();
|
||||
std::string* get();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "../../log.hpp"
|
||||
|
||||
#include <format>
|
||||
#include <ranges>
|
||||
|
||||
using namespace tinyxml2;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "area.hpp"
|
||||
|
||||
#include "../../log.hpp"
|
||||
#include <format>
|
||||
|
||||
#include "util.hpp"
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "../../util/preferences.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include <format>
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
@@ -39,28 +41,33 @@ namespace game::resource::xml
|
||||
query_anm2(root, "Anm2", archive, textureRootPath, anm2);
|
||||
query_string_attribute(root, "Name", &name);
|
||||
|
||||
root->QueryFloatAttribute("Weight", &weight);
|
||||
query_vec3(root, "ColorR", "ColorG", "ColorB", color);
|
||||
|
||||
root->QueryFloatAttribute("Capacity", &capacity);
|
||||
root->QueryFloatAttribute("CapacityMin", &capacityMin);
|
||||
root->QueryFloatAttribute("CapacityMax", &capacityMax);
|
||||
root->QueryFloatAttribute("CapacityMaxMultiplier", &capacityMaxMultiplier);
|
||||
root->QueryFloatAttribute("CapacityIfOverStuffedOnDigestBonus", &capacityIfOverStuffedOnDigestBonus);
|
||||
root->QueryDoubleAttribute("Weight", &weight);
|
||||
root->QueryDoubleAttribute("WeightMax", &weightMax);
|
||||
|
||||
root->QueryFloatAttribute("CaloriesToKilogram", &caloriesToKilogram);
|
||||
root->QueryDoubleAttribute("Capacity", &capacity);
|
||||
root->QueryDoubleAttribute("CapacityMin", &capacityMin);
|
||||
root->QueryDoubleAttribute("CapacityMax", &capacityMax);
|
||||
root->QueryDoubleAttribute("CapacityMaxMultiplier", &capacityMaxMultiplier);
|
||||
root->QueryDoubleAttribute("CapacityIfOverStuffedOnDigestBonus", &capacityIfOverStuffedOnDigestBonus);
|
||||
|
||||
root->QueryFloatAttribute("DigestionRate", &digestionRate);
|
||||
root->QueryFloatAttribute("DigestionRateMin", &digestionRateMin);
|
||||
root->QueryFloatAttribute("DigestionRateMax", &digestionRateMax);
|
||||
root->QueryDoubleAttribute("CaloriesToKilogram", &caloriesToKilogram);
|
||||
|
||||
root->QueryDoubleAttribute("DigestionRate", &digestionRate);
|
||||
root->QueryDoubleAttribute("DigestionRateMin", &digestionRateMin);
|
||||
root->QueryDoubleAttribute("DigestionRateMax", &digestionRateMax);
|
||||
root->QueryIntAttribute("DigestionTimerMax", &digestionTimerMax);
|
||||
|
||||
root->QueryFloatAttribute("EatSpeed", &eatSpeed);
|
||||
root->QueryFloatAttribute("EatSpeedMin", &eatSpeedMin);
|
||||
root->QueryFloatAttribute("EatSpeedMax", &eatSpeedMax);
|
||||
root->QueryDoubleAttribute("EatSpeed", &eatSpeed);
|
||||
root->QueryDoubleAttribute("EatSpeedMin", &eatSpeedMin);
|
||||
root->QueryDoubleAttribute("EatSpeedMax", &eatSpeedMax);
|
||||
|
||||
root->QueryFloatAttribute("BlinkChance", &blinkChance);
|
||||
root->QueryFloatAttribute("GurgleChance", &gurgleChance);
|
||||
root->QueryFloatAttribute("GurgleCapacityMultiplier", &gurgleCapacityMultiplier);
|
||||
root->QueryDoubleAttribute("BlinkChance", &blinkChance);
|
||||
root->QueryDoubleAttribute("GurgleChance", &gurgleChance);
|
||||
root->QueryDoubleAttribute("GurgleCapacityMultiplier", &gurgleCapacityMultiplier);
|
||||
|
||||
root->QueryIntAttribute("TextBlipPeriodBase", &textBlipPeriodBase);
|
||||
|
||||
auto dialoguePath = physfs::Path(archive + "/" + "dialogue.xml");
|
||||
|
||||
@@ -96,6 +103,7 @@ namespace game::resource::xml
|
||||
|
||||
if (auto element = root->FirstChildElement("Sounds"))
|
||||
{
|
||||
query_sound_entry_collection(element, "Blip", archive, soundRootPath, sounds.blip);
|
||||
query_sound_entry_collection(element, "Digest", archive, soundRootPath, sounds.digest);
|
||||
query_sound_entry_collection(element, "Gurgle", archive, soundRootPath, sounds.gurgle);
|
||||
}
|
||||
@@ -123,6 +131,7 @@ namespace game::resource::xml
|
||||
child->QueryFloatAttribute("Threshold", &stage.threshold);
|
||||
child->QueryIntAttribute("AreaID", &stage.areaID);
|
||||
dialogue.query_pool_id(child, "DialoguePoolID", stage.pool.id);
|
||||
query_string_attribute(child, "AnimationAppendID", &stage.animationAppendID);
|
||||
stages.emplace_back(std::move(stage));
|
||||
}
|
||||
}
|
||||
@@ -157,6 +166,15 @@ namespace game::resource::xml
|
||||
|
||||
if (auto element = root->FirstChildElement("InteractAreas"))
|
||||
{
|
||||
auto interact_type_id_get = [&](const std::string& typeName)
|
||||
{
|
||||
for (int i = 0; i < (int)interactTypeNames.size(); i++)
|
||||
if (interactTypeNames[i] == typeName) return i;
|
||||
|
||||
interactTypeNames.emplace_back(typeName);
|
||||
return (int)interactTypeNames.size() - 1;
|
||||
};
|
||||
|
||||
for (auto child = element->FirstChildElement("InteractArea"); child;
|
||||
child = child->NextSiblingElement("InteractArea"))
|
||||
{
|
||||
@@ -165,12 +183,12 @@ namespace game::resource::xml
|
||||
if (child->FindAttribute("Layer")) query_layer_id(child, "Layer", anm2, interactArea.layerID);
|
||||
|
||||
query_null_id(child, "Null", anm2, interactArea.nullID);
|
||||
query_string_attribute(child, "Animation", &interactArea.animation);
|
||||
query_string_attribute(child, "AnimationFull", &interactArea.animationFull);
|
||||
query_string_attribute(child, "AnimationCursorHover", &interactArea.animationCursorHover);
|
||||
query_string_attribute(child, "AnimationCursorActive", &interactArea.animationCursorActive);
|
||||
query_sound_entry_collection(child, "Sound", archive, soundRootPath, interactArea.sound, "Path");
|
||||
dialogue.query_pool_id(child, "DialoguePoolID", interactArea.pool.id);
|
||||
dialogue.query_pool_id(child, "DialoguePoolIDFull", interactArea.poolFull.id);
|
||||
query_bool_attribute(child, "IsHold", &interactArea.isHold);
|
||||
child->QueryFloatAttribute("DigestionBonusRub", &interactArea.digestionBonusRub);
|
||||
child->QueryFloatAttribute("DigestionBonusClick", &interactArea.digestionBonusClick);
|
||||
child->QueryFloatAttribute("Time", &interactArea.time);
|
||||
@@ -179,9 +197,7 @@ namespace game::resource::xml
|
||||
|
||||
std::string typeString{};
|
||||
query_string_attribute(child, "Type", &typeString);
|
||||
|
||||
for (int i = 0; i < (int)std::size(INTERACT_TYPE_STRINGS); i++)
|
||||
if (typeString == INTERACT_TYPE_STRINGS[i]) interactArea.type = (InteractType)i;
|
||||
if (!typeString.empty()) interactArea.typeID = interact_type_id_get(typeString);
|
||||
|
||||
interactAreas.emplace_back(std::move(interactArea));
|
||||
}
|
||||
@@ -208,10 +224,13 @@ namespace game::resource::xml
|
||||
else
|
||||
logger.warning(std::format("No character cursor.xml file found: {}", path.string()));
|
||||
|
||||
if (auto playSchemaPath = physfs::Path(archive + "/" + "play.xml"); playSchemaPath.is_valid())
|
||||
playSchema = Play(playSchemaPath, dialogue);
|
||||
if (auto skillCheckSchemaPath = physfs::Path(archive + "/" + "skill_check.xml"); skillCheckSchemaPath.is_valid())
|
||||
skillCheckSchema = SkillCheck(skillCheckSchemaPath, dialogue);
|
||||
else
|
||||
logger.warning(std::format("No character play.xml file found: {}", path.string()));
|
||||
logger.warning(std::format("No character skill_check.xml file found: {}", path.string()));
|
||||
|
||||
if (auto stringsPath = physfs::Path(archive + "/" + "strings.xml"); stringsPath.is_valid())
|
||||
strings = Strings(stringsPath);
|
||||
|
||||
logger.info(std::format("Initialized character: {}", name));
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#include "../../util/interact_type.hpp"
|
||||
#include "../audio.hpp"
|
||||
#include "animation_entry.hpp"
|
||||
#include "anm2.hpp"
|
||||
@@ -12,8 +11,9 @@
|
||||
#include "dialogue.hpp"
|
||||
#include "item.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "play.hpp"
|
||||
#include "save.hpp"
|
||||
#include "skill_check.hpp"
|
||||
#include "strings.hpp"
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
@@ -25,6 +25,7 @@ namespace game::resource::xml
|
||||
float threshold{};
|
||||
int areaID{};
|
||||
Dialogue::PoolReference pool{-1};
|
||||
std::string animationAppendID{};
|
||||
};
|
||||
|
||||
struct EatArea
|
||||
@@ -43,16 +44,16 @@ namespace game::resource::xml
|
||||
|
||||
struct InteractArea
|
||||
{
|
||||
std::string animation{};
|
||||
std::string animationFull{};
|
||||
std::string animationCursorActive{};
|
||||
std::string animationCursorHover{};
|
||||
SoundEntryCollection sound{};
|
||||
|
||||
int nullID{-1};
|
||||
int layerID{-1};
|
||||
InteractType type{(InteractType)-1};
|
||||
int typeID{-1};
|
||||
bool isHold{};
|
||||
Dialogue::PoolReference pool{-1};
|
||||
Dialogue::PoolReference poolFull{-1};
|
||||
|
||||
float digestionBonusRub{};
|
||||
float digestionBonusClick{};
|
||||
@@ -73,6 +74,7 @@ namespace game::resource::xml
|
||||
|
||||
struct Sounds
|
||||
{
|
||||
SoundEntryCollection blip{};
|
||||
SoundEntryCollection gurgle{};
|
||||
SoundEntryCollection digest{};
|
||||
};
|
||||
@@ -97,7 +99,8 @@ namespace game::resource::xml
|
||||
Item itemSchema{};
|
||||
Menu menuSchema{};
|
||||
Cursor cursorSchema{};
|
||||
Play playSchema{};
|
||||
SkillCheck skillCheckSchema{};
|
||||
Strings strings{};
|
||||
|
||||
Save save{};
|
||||
|
||||
@@ -107,32 +110,37 @@ namespace game::resource::xml
|
||||
|
||||
Sounds sounds{};
|
||||
|
||||
glm::vec3 color{0.120f, 0.515f, 0.115f};
|
||||
|
||||
std::vector<Stage> stages{};
|
||||
std::vector<ExpandArea> expandAreas{};
|
||||
std::vector<EatArea> eatAreas{};
|
||||
std::vector<std::string> interactTypeNames{};
|
||||
std::vector<InteractArea> interactAreas{};
|
||||
|
||||
AlternateSpritesheet alternateSpritesheet{};
|
||||
|
||||
std::string name{};
|
||||
std::filesystem::path path{};
|
||||
float weight{50};
|
||||
float capacity{2000.0f};
|
||||
float capacityMin{2000.0f};
|
||||
float capacityMax{99999.0f};
|
||||
float capacityMaxMultiplier{1.5f};
|
||||
float capacityIfOverStuffedOnDigestBonus{0.25f};
|
||||
float caloriesToKilogram{1000.0f};
|
||||
float digestionRate{0.05f};
|
||||
float digestionRateMin{0.0f};
|
||||
float digestionRateMax{0.25f};
|
||||
double weight{50};
|
||||
double weightMax{1000};
|
||||
double capacity{2000.0};
|
||||
double capacityMin{2000.0};
|
||||
double capacityMax{99999.0};
|
||||
double capacityMaxMultiplier{1.5};
|
||||
double capacityIfOverStuffedOnDigestBonus{0.25};
|
||||
double caloriesToKilogram{1000.0};
|
||||
double digestionRate{0.05};
|
||||
double digestionRateMin{0.0};
|
||||
double digestionRateMax{0.25};
|
||||
int digestionTimerMax{60};
|
||||
float eatSpeed{1.0f};
|
||||
float eatSpeedMin{1.0f};
|
||||
float eatSpeedMax{3.0f};
|
||||
float blinkChance{1.0f};
|
||||
float gurgleChance{1.0f};
|
||||
float gurgleCapacityMultiplier{1.0f};
|
||||
int textBlipPeriodBase{3};
|
||||
double eatSpeed{1.0};
|
||||
double eatSpeedMin{1.0};
|
||||
double eatSpeedMax{3.0};
|
||||
double blinkChance{1.0};
|
||||
double gurgleChance{1.0};
|
||||
double gurgleCapacityMultiplier{1.0};
|
||||
Dialogue::PoolReference pool{-1};
|
||||
|
||||
Character() = default;
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "../../util/preferences.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include <format>
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
@@ -39,7 +41,8 @@ namespace game::resource::xml
|
||||
|
||||
query_string_attribute(root, "Name", &name);
|
||||
query_string_attribute(root, "Description", &description);
|
||||
query_string_attribute(root, "Author", &author);
|
||||
query_string_attribute(root, "Credits", &credits);
|
||||
query_vec3(root, "ColorR", "ColorG", "ColorB", color);
|
||||
root->QueryFloatAttribute("Weight", &weight);
|
||||
|
||||
if (auto element = root->FirstChildElement("Stages"))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <glm/glm.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -22,11 +23,12 @@ namespace game::resource::xml
|
||||
Texture portrait{};
|
||||
Texture render{};
|
||||
Save save{};
|
||||
glm::vec3 color{0.120f, 0.515f, 0.115f};
|
||||
|
||||
int stages{1};
|
||||
|
||||
std::string name{};
|
||||
std::string author{};
|
||||
std::string credits{};
|
||||
std::string description{};
|
||||
std::filesystem::path path{};
|
||||
float weight{50};
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "../../log.hpp"
|
||||
|
||||
#include <format>
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
#include "../../log.hpp"
|
||||
#include "../../util/math.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <format>
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
@@ -96,6 +99,8 @@ namespace game::resource::xml
|
||||
|
||||
id++;
|
||||
}
|
||||
|
||||
entrySelectionOrder.assign(entries.size(), -1);
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Pools"))
|
||||
@@ -156,15 +161,32 @@ namespace game::resource::xml
|
||||
}
|
||||
}
|
||||
|
||||
int Dialogue::Pool::get() const
|
||||
int Dialogue::entry_pick(Pool& pool)
|
||||
{
|
||||
if (this->empty()) return -1;
|
||||
auto index = rand() % this->size();
|
||||
return this->at(index);
|
||||
if (pool.empty()) return -1;
|
||||
|
||||
std::vector<int> unselected{};
|
||||
for (auto id : pool)
|
||||
if (id >= 0 && id < (int)entrySelectionOrder.size() && entrySelectionOrder[id] < 0) unselected.emplace_back(id);
|
||||
|
||||
std::vector<int> candidates = unselected.empty() ? std::vector<int>(pool.begin(), pool.end()) : unselected;
|
||||
if (candidates.empty()) return -1;
|
||||
|
||||
auto oldestOrder = entrySelectionOrder[candidates.front()];
|
||||
for (auto id : candidates)
|
||||
oldestOrder = std::min(oldestOrder, entrySelectionOrder[id]);
|
||||
|
||||
std::vector<int> oldestCandidates{};
|
||||
for (auto id : candidates)
|
||||
if (entrySelectionOrder[id] == oldestOrder) oldestCandidates.emplace_back(id);
|
||||
|
||||
auto pickedID = oldestCandidates.at(rand() % oldestCandidates.size());
|
||||
entrySelectionOrder[pickedID] = selectionCounter++;
|
||||
return pickedID;
|
||||
}
|
||||
Dialogue::Entry* Dialogue::get(int id) { return &entries.at(id); }
|
||||
Dialogue::Entry* Dialogue::get(Dialogue::EntryReference& entry) { return &entries.at(entry.id); }
|
||||
Dialogue::Entry* Dialogue::get(const std::string& string) { return &entries.at(entryIDMap.at(string)); }
|
||||
Dialogue::Entry* Dialogue::get(Dialogue::PoolReference& pool) { return &entries.at(pools.at(pool.id).get()); }
|
||||
Dialogue::Entry* Dialogue::get(Dialogue::Pool& pool) { return &entries.at(pool.get()); }
|
||||
Dialogue::Entry* Dialogue::get(Dialogue::PoolReference& pool) { return &entries.at(entry_pick(pools.at(pool.id))); }
|
||||
Dialogue::Entry* Dialogue::get(Dialogue::Pool& pool) { return &entries.at(entry_pick(pool)); }
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <string>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "../../util/physfs.hpp"
|
||||
|
||||
@@ -45,11 +46,7 @@ namespace game::resource::xml
|
||||
inline bool is_valid() const { return id != -1; };
|
||||
};
|
||||
|
||||
class Pool : public std::vector<int>
|
||||
{
|
||||
public:
|
||||
int get() const;
|
||||
};
|
||||
class Pool : public std::vector<int> {};
|
||||
|
||||
std::map<std::string, int> entryIDMap;
|
||||
std::map<int, std::string> entryIDMapReverse;
|
||||
@@ -57,6 +54,8 @@ namespace game::resource::xml
|
||||
|
||||
std::vector<Pool> pools{};
|
||||
std::map<std::string, int> poolMap{};
|
||||
std::vector<long long> entrySelectionOrder{};
|
||||
long long selectionCounter{};
|
||||
|
||||
EntryReference start{-1};
|
||||
EntryReference end{-1};
|
||||
@@ -83,6 +82,7 @@ namespace game::resource::xml
|
||||
Entry* get(Dialogue::EntryReference&);
|
||||
Entry* get(Dialogue::Pool&);
|
||||
Entry* get(Dialogue::PoolReference&);
|
||||
int entry_pick(Pool&);
|
||||
void query_entry_id(tinyxml2::XMLElement* element, const char* name, int& id);
|
||||
void query_pool_id(tinyxml2::XMLElement* element, const char* name, int& id);
|
||||
inline bool is_valid() const { return isValid; };
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "../../log.hpp"
|
||||
|
||||
#include <format>
|
||||
|
||||
#include "util.hpp"
|
||||
|
||||
using namespace tinyxml2;
|
||||
@@ -85,6 +87,22 @@ namespace game::resource::xml
|
||||
query_sound_entry_collection(element, "Dispose", archive, soundRootPath, sounds.dispose);
|
||||
query_sound_entry_collection(element, "Return", archive, soundRootPath, sounds.return_);
|
||||
query_sound_entry_collection(element, "Summon", archive, soundRootPath, sounds.summon);
|
||||
query_sound_entry_collection(element, "Upgrade", archive, soundRootPath, sounds.upgrade);
|
||||
query_sound_entry_collection(element, "UpgradeFail", archive, soundRootPath, sounds.upgradeFail);
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Items"))
|
||||
{
|
||||
int id{};
|
||||
|
||||
for (auto child = element->FirstChildElement("Item"); child; child = child->NextSiblingElement("Item"))
|
||||
{
|
||||
std::string name{};
|
||||
query_string_attribute(child, "Name", &name);
|
||||
stringToIDMap[name] = id;
|
||||
idToStringMap[id] = name;
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Items"))
|
||||
@@ -92,7 +110,8 @@ namespace game::resource::xml
|
||||
std::string itemTextureRootPath{};
|
||||
query_string_attribute(element, "TextureRootPath", &itemTextureRootPath);
|
||||
|
||||
element->QueryIntAttribute("ChewCount", &chewCount);
|
||||
element->QueryIntAttribute("Durability", &durability);
|
||||
if (element->FindAttribute("ChewCount")) element->QueryIntAttribute("ChewCount", &durability);
|
||||
element->QueryIntAttribute("QuantityMax", &quantityMax);
|
||||
|
||||
for (auto child = element->FirstChildElement("Item"); child; child = child->NextSiblingElement("Item"))
|
||||
@@ -103,11 +122,31 @@ namespace game::resource::xml
|
||||
query_string_attribute(child, "Description", &item.description);
|
||||
|
||||
query_float_optional_attribute(child, "Calories", item.calories);
|
||||
query_float_optional_attribute(child, "CapacityBonus", item.capacityBonus);
|
||||
query_optional_vec3(child, "ColorR", "ColorG", "ColorB", item.color);
|
||||
query_float_optional_attribute(child, "DigestionBonus", item.digestionBonus);
|
||||
query_float_optional_attribute(child, "EatSpeedBonus", item.eatSpeedBonus);
|
||||
query_float_optional_attribute(child, "Gravity", item.gravity);
|
||||
query_int_optional_attribute(child, "ChewCount", item.chewCount);
|
||||
query_bool_attribute(child, "IsPlayReward", &item.isPlayReward);
|
||||
|
||||
query_int_optional_attribute(child, "Durability", item.durability);
|
||||
if (!item.durability.has_value()) query_int_optional_attribute(child, "ChewCount", item.durability);
|
||||
|
||||
if (child->FindAttribute("UpgradeID"))
|
||||
{
|
||||
std::string upgradeIDString{};
|
||||
query_string_attribute(child, "UpgradeID", &upgradeIDString);
|
||||
|
||||
if (!upgradeIDString.empty() && stringToIDMap.contains(upgradeIDString))
|
||||
item.upgradeID = stringToIDMap[upgradeIDString];
|
||||
else if (upgradeIDString.empty())
|
||||
logger.warning(std::format("Empty UpgradeID ({})", item.name));
|
||||
else
|
||||
logger.warning(std::format("Could not find item ID for UpgradeID: {} ({})", upgradeIDString, item.name));
|
||||
|
||||
query_int_optional_attribute(child, "UpgradeCount", item.upgradeCount);
|
||||
}
|
||||
|
||||
query_bool_attribute(child, "IsSkillCheckReward", &item.isSkillCheckReward);
|
||||
query_bool_attribute(child, "IsToggleSpritesheet", &item.isToggleSpritesheet);
|
||||
|
||||
std::string categoryString{};
|
||||
@@ -138,7 +177,7 @@ namespace game::resource::xml
|
||||
{
|
||||
auto& item = items[i];
|
||||
pools[item.rarityID].emplace_back(i);
|
||||
if (item.isPlayReward) rewardItemPool.emplace_back(i);
|
||||
if (item.isSkillCheckReward) skillCheckRewardItemPool.emplace_back(i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int)rarities.size(); i++)
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "../audio.hpp"
|
||||
#include "anm2.hpp"
|
||||
#include "sound_entry.hpp"
|
||||
@@ -42,13 +44,17 @@ namespace game::resource::xml
|
||||
std::string description{UNDEFINED};
|
||||
int categoryID{};
|
||||
int rarityID{};
|
||||
std::optional<int> upgradeCount{};
|
||||
std::optional<int> upgradeID{};
|
||||
std::optional<int> flavorID;
|
||||
std::optional<float> calories{};
|
||||
std::optional<float> capacityBonus{};
|
||||
std::optional<glm::vec3> color{};
|
||||
std::optional<float> eatSpeedBonus{};
|
||||
std::optional<float> digestionBonus{};
|
||||
std::optional<float> gravity{};
|
||||
std::optional<int> chewCount{};
|
||||
bool isPlayReward{};
|
||||
std::optional<int> durability{};
|
||||
bool isSkillCheckReward{};
|
||||
bool isToggleSpritesheet{};
|
||||
};
|
||||
|
||||
@@ -63,11 +69,15 @@ namespace game::resource::xml
|
||||
SoundEntryCollection return_{};
|
||||
SoundEntryCollection dispose{};
|
||||
SoundEntryCollection summon{};
|
||||
SoundEntryCollection upgrade{};
|
||||
SoundEntryCollection upgradeFail{};
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, int> categoryMap{};
|
||||
std::unordered_map<std::string, int> rarityMap{};
|
||||
std::unordered_map<std::string, int> flavorMap{};
|
||||
std::unordered_map<std::string, int> stringToIDMap{};
|
||||
std::unordered_map<int, std::string> idToStringMap{};
|
||||
|
||||
using Pool = std::vector<int>;
|
||||
|
||||
@@ -79,12 +89,12 @@ namespace game::resource::xml
|
||||
|
||||
std::vector<int> rarityIDsSortedByChance{};
|
||||
std::unordered_map<int, Pool> pools{};
|
||||
Pool rewardItemPool{};
|
||||
Pool skillCheckRewardItemPool{};
|
||||
|
||||
Animations animations{};
|
||||
Sounds sounds{};
|
||||
Anm2 baseAnm2{};
|
||||
int chewCount{2};
|
||||
int durability{2};
|
||||
int quantityMax{99};
|
||||
|
||||
bool isValid{};
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "../../log.hpp"
|
||||
|
||||
#include <format>
|
||||
|
||||
#include "util.hpp"
|
||||
|
||||
using namespace tinyxml2;
|
||||
@@ -34,6 +36,7 @@ namespace game::resource::xml
|
||||
query_sound_entry_collection(element, "Close", archive, soundRootPath, sounds.close);
|
||||
query_sound_entry_collection(element, "Hover", archive, soundRootPath, sounds.hover);
|
||||
query_sound_entry_collection(element, "Select", archive, soundRootPath, sounds.select);
|
||||
query_sound_entry_collection(element, "CheatsActivated", archive, soundRootPath, sounds.cheatsActivated);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace game::resource::xml
|
||||
SoundEntryCollection close{};
|
||||
SoundEntryCollection hover{};
|
||||
SoundEntryCollection select{};
|
||||
SoundEntryCollection cheatsActivated{};
|
||||
};
|
||||
|
||||
Sounds sounds{};
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "../../log.hpp"
|
||||
|
||||
#include <format>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include "../../util/web_filesystem.hpp"
|
||||
#endif
|
||||
@@ -20,15 +22,13 @@ namespace game::resource::xml
|
||||
XMLDocument document;
|
||||
auto pathString = path.string();
|
||||
|
||||
// Fail silently if there's no save.
|
||||
auto result = document.LoadFile(pathString.c_str());
|
||||
|
||||
if (result == XML_ERROR_FILE_NOT_FOUND || result == XML_ERROR_FILE_COULD_NOT_BE_OPENED) return;
|
||||
|
||||
if (result != XML_SUCCESS)
|
||||
{
|
||||
logger.error(
|
||||
std::format("Could not initialize character save file: {} ({})", pathString, document.ErrorStr()));
|
||||
logger.error(std::format("Could not initialize character save file: {} ({})", pathString, document.ErrorStr()));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,9 @@ namespace game::resource::xml
|
||||
element->QueryIntAttribute("TotalFoodItemsEaten", &totalFoodItemsEaten);
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Play"))
|
||||
auto element = root->FirstChildElement("SkillCheck");
|
||||
if (!element) element = root->FirstChildElement("Play");
|
||||
if (element)
|
||||
{
|
||||
element->QueryIntAttribute("TotalPlays", &totalPlays);
|
||||
element->QueryIntAttribute("HighScore", &highScore);
|
||||
@@ -89,7 +91,8 @@ namespace game::resource::xml
|
||||
{
|
||||
Item item{};
|
||||
child->QueryIntAttribute("ID", &item.id);
|
||||
child->QueryIntAttribute("ChewCount", &item.chewCount);
|
||||
child->QueryIntAttribute("Durability", &item.durability);
|
||||
if (child->FindAttribute("ChewCount")) child->QueryIntAttribute("ChewCount", &item.durability);
|
||||
child->QueryFloatAttribute("PositionX", &item.position.x);
|
||||
child->QueryFloatAttribute("PositionY", &item.position.y);
|
||||
child->QueryFloatAttribute("VelocityX", &item.velocity.x);
|
||||
@@ -128,13 +131,13 @@ namespace game::resource::xml
|
||||
characterElement->SetAttribute("TotalCaloriesConsumed", totalCaloriesConsumed);
|
||||
characterElement->SetAttribute("TotalFoodItemsEaten", totalFoodItemsEaten);
|
||||
|
||||
auto playElement = element->InsertNewChildElement("Play");
|
||||
auto skillCheckElement = element->InsertNewChildElement("SkillCheck");
|
||||
|
||||
playElement->SetAttribute("TotalPlays", totalPlays);
|
||||
playElement->SetAttribute("HighScore", highScore);
|
||||
playElement->SetAttribute("BestCombo", bestCombo);
|
||||
skillCheckElement->SetAttribute("TotalPlays", totalPlays);
|
||||
skillCheckElement->SetAttribute("HighScore", highScore);
|
||||
skillCheckElement->SetAttribute("BestCombo", bestCombo);
|
||||
|
||||
auto gradesElement = playElement->InsertNewChildElement("Grades");
|
||||
auto gradesElement = skillCheckElement->InsertNewChildElement("Grades");
|
||||
|
||||
for (auto& [i, count] : gradeCounts)
|
||||
{
|
||||
@@ -160,7 +163,7 @@ namespace game::resource::xml
|
||||
auto itemElement = itemsElement->InsertNewChildElement("Item");
|
||||
|
||||
itemElement->SetAttribute("ID", item.id);
|
||||
itemElement->SetAttribute("ChewCount", item.chewCount);
|
||||
itemElement->SetAttribute("Durability", item.durability);
|
||||
itemElement->SetAttribute("PositionX", item.position.x);
|
||||
itemElement->SetAttribute("PositionY", item.position.y);
|
||||
itemElement->SetAttribute("VelocityX", item.velocity.x);
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace game::resource::xml
|
||||
struct Item
|
||||
{
|
||||
int id{};
|
||||
int chewCount{};
|
||||
int durability{};
|
||||
glm::vec2 position{};
|
||||
glm::vec2 velocity{};
|
||||
float rotation{};
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "../../log.hpp"
|
||||
|
||||
#include <format>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include "../../util/web_filesystem.hpp"
|
||||
#endif
|
||||
@@ -21,8 +23,7 @@ namespace game::resource::xml
|
||||
|
||||
if (document.LoadFile(pathString.c_str()) != XML_SUCCESS)
|
||||
{
|
||||
logger.error(
|
||||
std::format("Could not initialize character save file: {} ({})", pathString, document.ErrorStr()));
|
||||
logger.error(std::format("Could not initialize character save file: {} ({})", pathString, document.ErrorStr()));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -32,13 +33,10 @@ namespace game::resource::xml
|
||||
query_string_attribute(root, "MeasurementSystem", &measurementSystemString);
|
||||
measurementSystem = measurementSystemString == "Imperial" ? measurement::IMPERIAL : measurement::METRIC;
|
||||
root->QueryIntAttribute("Volume", &volume);
|
||||
root->QueryFloatAttribute("ColorR", &color.r);
|
||||
root->QueryFloatAttribute("ColorG", &color.g);
|
||||
root->QueryFloatAttribute("ColorB", &color.b);
|
||||
root->QueryFloatAttribute("WindowX", &windowPosition.x);
|
||||
root->QueryFloatAttribute("WindowY", &windowPosition.y);
|
||||
root->QueryIntAttribute("WindowW", &windowSize.x);
|
||||
root->QueryIntAttribute("WindowH", &windowSize.y);
|
||||
query_vec3(root, "ColorR", "ColorG", "ColorB", color);
|
||||
query_vec2(root, "WindowX", "WindowY", windowPosition);
|
||||
query_ivec2(root, "WindowW", "WindowH", windowSize);
|
||||
query_bool_attribute(root, "IsUseCharacterColor", &isUseCharacterColor);
|
||||
}
|
||||
|
||||
logger.info(std::format("Initialized settings: {}", pathString));
|
||||
@@ -57,13 +55,10 @@ namespace game::resource::xml
|
||||
|
||||
element->SetAttribute("MeasurementSystem", measurementSystem == measurement::IMPERIAL ? "Imperial" : "Metric");
|
||||
element->SetAttribute("Volume", volume);
|
||||
element->SetAttribute("ColorR", color.r);
|
||||
element->SetAttribute("ColorG", color.g);
|
||||
element->SetAttribute("ColorB", color.b);
|
||||
element->SetAttribute("WindowX", windowPosition.x);
|
||||
element->SetAttribute("WindowY", windowPosition.y);
|
||||
element->SetAttribute("WindowW", windowSize.x);
|
||||
element->SetAttribute("WindowH", windowSize.y);
|
||||
set_vec3_attribute(element, "ColorR", "ColorG", "ColorB", color);
|
||||
set_vec2_attribute(element, "WindowX", "WindowY", windowPosition);
|
||||
set_ivec2_attribute(element, "WindowW", "WindowH", windowSize);
|
||||
set_bool_attribute(element, "IsUseCharacterColor", isUseCharacterColor);
|
||||
|
||||
document.InsertFirstChild(element);
|
||||
|
||||
|
||||
@@ -21,8 +21,9 @@ namespace game::resource::xml
|
||||
|
||||
util::measurement::System measurementSystem{util::measurement::METRIC};
|
||||
int volume{50};
|
||||
bool isUseCharacterColor{true};
|
||||
|
||||
glm::vec3 color{0.120f, 0.515f, 0.115f};
|
||||
glm::vec3 color{0.120f, 0.120f, 0.120f};
|
||||
glm::ivec2 windowSize{1600, 900};
|
||||
glm::vec2 windowPosition{};
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
#include "play.hpp"
|
||||
#include "skill_check.hpp"
|
||||
|
||||
#include "../../log.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include <format>
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
Play::Play(const physfs::Path& path, Dialogue& dialogue)
|
||||
SkillCheck::SkillCheck(const physfs::Path& path, Dialogue& dialogue)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
@@ -60,8 +62,8 @@ namespace game::resource::xml
|
||||
}
|
||||
|
||||
isValid = true;
|
||||
logger.info(std::format("Initialized play schema: {}", path.c_str()));
|
||||
logger.info(std::format("Initialized skill check schema: {}", path.c_str()));
|
||||
}
|
||||
|
||||
bool Play::is_valid() const { return isValid; };
|
||||
bool SkillCheck::is_valid() const { return isValid; };
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
class Play
|
||||
class SkillCheck
|
||||
{
|
||||
public:
|
||||
struct Grade
|
||||
@@ -48,8 +48,8 @@ namespace game::resource::xml
|
||||
|
||||
bool isValid{};
|
||||
|
||||
Play() = default;
|
||||
Play(const util::physfs::Path&, Dialogue&);
|
||||
SkillCheck() = default;
|
||||
SkillCheck(const util::physfs::Path&, Dialogue&);
|
||||
|
||||
bool is_valid() const;
|
||||
};
|
||||
@@ -4,13 +4,15 @@
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
Audio& SoundEntryCollection::get()
|
||||
Audio* SoundEntryCollection::get()
|
||||
{
|
||||
return at(util::vector::random_index_weighted(*this, [](const auto& entry) { return entry.weight; })).sound;
|
||||
if (empty()) return nullptr;
|
||||
return &at(util::vector::random_index_weighted(*this, [](const auto& entry) { return entry.weight; })).sound;
|
||||
}
|
||||
|
||||
void SoundEntryCollection::play()
|
||||
{
|
||||
at(util::vector::random_index_weighted(*this, [](const auto& entry) { return entry.weight; })).play();
|
||||
if (empty()) return;
|
||||
if (auto audio = get()) audio->play();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace game::resource::xml
|
||||
class SoundEntryCollection : public std::vector<SoundEntry>
|
||||
{
|
||||
public:
|
||||
Audio& get();
|
||||
Audio* get();
|
||||
void play();
|
||||
};
|
||||
}
|
||||
|
||||
48
src/resource/xml/strings.cpp
Normal file
48
src/resource/xml/strings.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "strings.hpp"
|
||||
|
||||
#include "../../log.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include <format>
|
||||
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::string definition_element_name_get(const Strings::Definition& definition)
|
||||
{
|
||||
std::string name = definition.attribute;
|
||||
if (name.rfind("Text", 0) == 0) name.replace(0, 4, "String");
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
Strings::Strings()
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
values[i] = definitions[i].fallback;
|
||||
}
|
||||
|
||||
Strings::Strings(const util::physfs::Path& path)
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
values[i] = definitions[i].fallback;
|
||||
|
||||
XMLDocument document;
|
||||
if (document_load(path, document) != XML_SUCCESS) return;
|
||||
|
||||
auto root = document.RootElement();
|
||||
if (!root) return;
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
if (auto element = root->FirstChildElement(definition_element_name_get(definitions[i]).c_str()))
|
||||
query_string_attribute(element, "Text", &values[i]);
|
||||
|
||||
isValid = true;
|
||||
logger.info(std::format("Initialized strings: {}", path.c_str()));
|
||||
}
|
||||
|
||||
const std::string& Strings::get(Type type) const { return values[type]; }
|
||||
}
|
||||
159
src/resource/xml/strings.hpp
Normal file
159
src/resource/xml/strings.hpp
Normal file
@@ -0,0 +1,159 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../util/physfs.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
#define GAME_XML_STRING_LIST(X) \
|
||||
X(MenuTabInteract, "TextMenuTabInteract", "Interact") \
|
||||
X(MenuTabArcade, "TextMenuTabArcade", "Arcade") \
|
||||
X(MenuTabInventory, "TextMenuTabInventory", "Inventory") \
|
||||
X(MenuTabSettings, "TextMenuTabSettings", "Settings") \
|
||||
X(MenuTabCheats, "TextMenuTabCheats", "Cheats") \
|
||||
X(MenuTabDebug, "TextMenuTabDebug", "Debug") \
|
||||
X(MenuOpenTooltip, "TextMenuOpenTooltip", "Open Main Menu") \
|
||||
X(MenuCloseTooltip, "TextMenuCloseTooltip", "Close Main Menu") \
|
||||
X(InteractChatButton, "TextInteractChatButton", "Let's chat!") \
|
||||
X(InteractHelpButton, "TextInteractHelpButton", "Help") \
|
||||
X(InteractFeelingButton, "TextInteractFeelingButton", "How are you feeling?") \
|
||||
X(InteractWeightFormat, "TextInteractWeightFormat", "Weight: %0.2f %s (Stage: %i)") \
|
||||
X(InteractCapacityFormat, "TextInteractCapacityFormat", "Capacity: %0.0f kcal (Max: %0.0f kcal)") \
|
||||
X(InteractDigestionRateFormat, "TextInteractDigestionRateFormat", "Digestion Rate: %0.2f%%/sec") \
|
||||
X(InteractEatingSpeedFormat, "TextInteractEatingSpeedFormat", "Eating Speed: %0.2fx") \
|
||||
X(InteractTotalCaloriesFormat, "TextInteractTotalCaloriesFormat", "Total Calories Consumed: %0.0f kcal") \
|
||||
X(InteractTotalFoodItemsFormat, "TextInteractTotalFoodItemsFormat", "Total Food Items Eaten: %i") \
|
||||
X(SettingsMeasurementSystem, "TextSettingsMeasurementSystem", "Measurement System") \
|
||||
X(SettingsMetric, "TextSettingsMetric", "Metric") \
|
||||
X(SettingsMetricTooltip, "TextSettingsMetricTooltip", "Use kilograms (kg).") \
|
||||
X(SettingsImperial, "TextSettingsImperial", "Imperial") \
|
||||
X(SettingsImperialTooltip, "TextSettingsImperialTooltip", "Use pounds (lbs).") \
|
||||
X(SettingsSound, "TextSettingsSound", "Sound") \
|
||||
X(SettingsVolume, "TextSettingsVolume", "Volume") \
|
||||
X(SettingsVolumeTooltip, "TextSettingsVolumeTooltip", "Adjust master volume.") \
|
||||
X(SettingsAppearance, "TextSettingsAppearance", "Appearance") \
|
||||
X(SettingsUseCharacterColor, "TextSettingsUseCharacterColor", "Use Character Color") \
|
||||
X(SettingsUseCharacterColorTooltip, "TextSettingsUseCharacterColorTooltip", \
|
||||
"When playing, the UI will use the character's preset UI color.") \
|
||||
X(SettingsColor, "TextSettingsColor", "Color") \
|
||||
X(SettingsColorTooltip, "TextSettingsColorTooltip", "Change the UI color.") \
|
||||
X(SettingsResetButton, "TextSettingsResetButton", "Reset to Default") \
|
||||
X(SettingsSaveButton, "TextSettingsSaveButton", "Save") \
|
||||
X(SettingsSaveTooltip, "TextSettingsSaveTooltip", "Save the game.\n(Note: the game autosaves frequently.)") \
|
||||
X(SettingsReturnToCharactersButton, "TextSettingsReturnToCharactersButton", "Return to Characters") \
|
||||
X(SettingsReturnToCharactersTooltip, "TextSettingsReturnToCharactersTooltip", \
|
||||
"Go back to the character selection screen.\nProgress will be saved.") \
|
||||
X(ToastCheatsUnlocked, "TextToastCheatsUnlocked", "Cheats unlocked!") \
|
||||
X(ToastSaving, "TextToastSaving", "Saving...") \
|
||||
X(ToolsHomeButton, "TextToolsHomeButton", "Home") \
|
||||
X(ToolsHomeTooltip, "TextToolsHomeTooltip", "Reset camera view.\n(Shortcut: Home)") \
|
||||
X(ToolsOpenTooltip, "TextToolsOpenTooltip", "Open Tools") \
|
||||
X(ToolsCloseTooltip, "TextToolsCloseTooltip", "Close Tools") \
|
||||
X(DebugCursorScreenFormat, "TextDebugCursorScreenFormat", "Cursor Pos (Screen): %0.0f, %0.0f") \
|
||||
X(DebugCursorWorldFormat, "TextDebugCursorWorldFormat", "Cursor Pos (World): %0.0f, %0.0f") \
|
||||
X(DebugAnimations, "TextDebugAnimations", "Animations") \
|
||||
X(DebugNowPlayingFormat, "TextDebugNowPlayingFormat", "Now Playing: %s") \
|
||||
X(DebugDialogue, "TextDebugDialogue", "Dialogue") \
|
||||
X(DebugShowNulls, "TextDebugShowNulls", "Show Nulls (Hitboxes)") \
|
||||
X(DebugShowWorldBounds, "TextDebugShowWorldBounds", "Show World Bounds") \
|
||||
X(DebugItem, "TextDebugItem", "Item") \
|
||||
X(DebugHeld, "TextDebugHeld", "Held") \
|
||||
X(DebugItemTypeFormat, "TextDebugItemTypeFormat", "Type: %i") \
|
||||
X(DebugItemPositionFormat, "TextDebugItemPositionFormat", "Position: %0.0f, %0.0f") \
|
||||
X(DebugItemVelocityFormat, "TextDebugItemVelocityFormat", "Velocity: %0.0f, %0.0f") \
|
||||
X(DebugItemDurabilityFormat, "TextDebugItemDurabilityFormat", "Durability: %i") \
|
||||
X(InventoryEmptyHint, "TextInventoryEmptyHint", "Check the \"Arcade\" tab to earn rewards!") \
|
||||
X(InventoryFlavorFormat, "TextInventoryFlavorFormat", "Flavor: %s") \
|
||||
X(InventoryCaloriesFormat, "TextInventoryCaloriesFormat", "%0.0f kcal") \
|
||||
X(InventoryDurabilityFormat, "TextInventoryDurabilityFormat", "Durability: %i") \
|
||||
X(InventoryCapacityBonusFormat, "TextInventoryCapacityBonusFormat", "Capacity Bonus: +%0.0f kcal") \
|
||||
X(InventoryDigestionRateBonusFormat, "TextInventoryDigestionRateBonusFormat", "Digestion Rate Bonus: +%0.2f%% / sec") \
|
||||
X(InventoryDigestionRatePenaltyFormat, "TextInventoryDigestionRatePenaltyFormat", "Digestion Rate Penalty: %0.2f%% / sec") \
|
||||
X(InventoryEatSpeedBonusFormat, "TextInventoryEatSpeedBonusFormat", "Eat Speed Bonus: +%0.2f%% / sec") \
|
||||
X(InventoryEatSpeedPenaltyFormat, "TextInventoryEatSpeedPenaltyFormat", "Eat Speed Penalty: %0.2f%% / sec") \
|
||||
X(InventoryUpgradePreviewFormat, "TextInventoryUpgradePreviewFormat", "Upgrade: %ix -> %s") \
|
||||
X(InventoryUnknown, "TextInventoryUnknown", "???") \
|
||||
X(InventorySpawnButton, "TextInventorySpawnButton", "Spawn") \
|
||||
X(InventoryUpgradeButton, "TextInventoryUpgradeButton", "Upgrade") \
|
||||
X(InventoryUpgradeAllButton, "TextInventoryUpgradeAllButton", "Upgrade All") \
|
||||
X(InventoryUpgradeNoPath, "TextInventoryUpgradeNoPath", "This item cannot be upgraded.") \
|
||||
X(InventoryUpgradeNeedsTemplate, "TextInventoryUpgradeNeedsTemplate", "Needs {}x to upgrade into {}!") \
|
||||
X(InventoryUpgradeOneTemplate, "TextInventoryUpgradeOneTemplate", "Use {}x to upgrade into 1x {}.") \
|
||||
X(InventoryUpgradeAllTemplate, "TextInventoryUpgradeAllTemplate", "Use {}x to upgrade into {}x {}.") \
|
||||
X(ArcadeSkillCheckName, "TextArcadeSkillCheckName", "Skill Check") \
|
||||
X(ArcadeSkillCheckDescription, "TextArcadeSkillCheckDescription", \
|
||||
"Test your timing to build score, chain combos, and earn rewards based on your performance.") \
|
||||
X(ArcadePlayButton, "TextArcadePlayButton", "Play") \
|
||||
X(ArcadeStatsButton, "TextArcadeStatsButton", "Stats") \
|
||||
X(ArcadeBackButton, "TextArcadeBackButton", "Back") \
|
||||
X(ArcadeBestFormat, "TextArcadeBestFormat", "Best: %i pts (%ix)") \
|
||||
X(ArcadeTotalSkillChecksFormat, "TextArcadeTotalSkillChecksFormat", "Total Skill Checks: %i") \
|
||||
X(ArcadeAccuracyFormat, "TextArcadeAccuracyFormat", "Accuracy: %0.2f%%") \
|
||||
X(InfoProgressMax, "TextInfoProgressMax", "MAX") \
|
||||
X(InfoProgressToNextStage, "TextInfoProgressToNextStage", "To Next Stage") \
|
||||
X(InfoStageProgressFormat, "TextInfoStageProgressFormat", "Stage: %i/%i (%0.1f%%)") \
|
||||
X(InfoMaxedOut, "TextInfoMaxedOut", "Maxed out!") \
|
||||
X(InfoStageStartFormat, "TextInfoStageStartFormat", "Start: %0.2f %s") \
|
||||
X(InfoStageCurrentFormat, "TextInfoStageCurrentFormat", "Current: %0.2f %s") \
|
||||
X(InfoStageNextFormat, "TextInfoStageNextFormat", "Next: %0.2f %s") \
|
||||
X(InfoDigestion, "TextInfoDigestion", "Digestion") \
|
||||
X(InfoDigesting, "TextInfoDigesting", "Digesting...") \
|
||||
X(InfoDigestionInProgress, "TextInfoDigestionInProgress", "Digestion in progress...") \
|
||||
X(InfoGiveFoodToStartDigesting, "TextInfoGiveFoodToStartDigesting", "Give food to start digesting!") \
|
||||
X(InfoDigestionRateFormat, "TextInfoDigestionRateFormat", "Rate: %0.2f%% / sec") \
|
||||
X(InfoEatingSpeedFormat, "TextInfoEatingSpeedFormat", "Eating Speed: %0.2fx") \
|
||||
X(SkillCheckScoreFormat, "TextSkillCheckScoreFormat", "Score: %i pts (%ix)") \
|
||||
X(SkillCheckBestFormat, "TextSkillCheckBestFormat", "Best: %i pts (%ix)") \
|
||||
X(SkillCheckInstructions, "TextSkillCheckInstructions", "Match the line to the colored areas with Space/click! Better performance, better rewards!") \
|
||||
X(SkillCheckScoreLoss, "TextSkillCheckScoreLoss", "-1") \
|
||||
X(SkillCheckRewardToast, "TextSkillCheckRewardToast", "Fantastic score! Congratulations!") \
|
||||
X(SkillCheckHighScoreToast, "TextSkillCheckHighScoreToast", "High Score!") \
|
||||
X(SkillCheckGradeSuccessTemplate, "TextSkillCheckGradeSuccessTemplate", "{} (+{})") \
|
||||
X(SkillCheckMenuButton, "TextSkillCheckMenuButton", "Menu") \
|
||||
X(CheatsCalories, "TextCheatsCalories", "Calories") \
|
||||
X(CheatsCapacity, "TextCheatsCapacity", "Capacity") \
|
||||
X(CheatsWeight, "TextCheatsWeight", "Weight") \
|
||||
X(CheatsWeightFormat, "TextCheatsWeightFormat", "%0.2f kg") \
|
||||
X(CheatsStage, "TextCheatsStage", "Stage") \
|
||||
X(CheatsDigestionRate, "TextCheatsDigestionRate", "Digestion Rate") \
|
||||
X(CheatsDigestionRateFormat, "TextCheatsDigestionRateFormat", "%0.2f% / tick") \
|
||||
X(CheatsEatSpeed, "TextCheatsEatSpeed", "Eat Speed") \
|
||||
X(CheatsEatSpeedFormat, "TextCheatsEatSpeedFormat", "%0.2fx") \
|
||||
X(CheatsDigestButton, "TextCheatsDigestButton", "Digest") \
|
||||
X(CheatsInventory, "TextCheatsInventory", "Inventory")
|
||||
|
||||
class Strings
|
||||
{
|
||||
public:
|
||||
enum Type
|
||||
{
|
||||
#define X(type, attr, fallback) type,
|
||||
GAME_XML_STRING_LIST(X)
|
||||
#undef X
|
||||
Count
|
||||
};
|
||||
|
||||
struct Definition
|
||||
{
|
||||
const char* attribute;
|
||||
const char* fallback;
|
||||
};
|
||||
|
||||
inline static constexpr std::array<Definition, Count> definitions{{
|
||||
#define X(type, attr, fallback) {attr, fallback},
|
||||
GAME_XML_STRING_LIST(X)
|
||||
#undef X
|
||||
}};
|
||||
|
||||
std::array<std::string, Count> values{};
|
||||
|
||||
bool isValid{};
|
||||
|
||||
Strings();
|
||||
Strings(const util::physfs::Path&);
|
||||
|
||||
const std::string& get(Type) const;
|
||||
};
|
||||
}
|
||||
@@ -12,6 +12,8 @@ using namespace game::util;
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
XMLError query_result_merge(XMLError result, XMLError next) { return result == XML_SUCCESS ? next : result; }
|
||||
|
||||
XMLError query_string_attribute(XMLElement* element, const char* attribute, std::string* value)
|
||||
{
|
||||
const char* temp = nullptr;
|
||||
@@ -45,6 +47,60 @@ namespace game::resource::xml
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError query_ivec2(XMLElement* element, const char* attributeX, const char* attributeY, glm::ivec2& value)
|
||||
{
|
||||
auto result = element->QueryIntAttribute(attributeX, &value.x);
|
||||
result = query_result_merge(result, element->QueryIntAttribute(attributeY, &value.y));
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError query_vec2(XMLElement* element, const char* attributeX, const char* attributeY, glm::vec2& value)
|
||||
{
|
||||
auto result = element->QueryFloatAttribute(attributeX, &value.x);
|
||||
result = query_result_merge(result, element->QueryFloatAttribute(attributeY, &value.y));
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError query_vec3(XMLElement* element, const char* attributeX, const char* attributeY, const char* attributeZ,
|
||||
glm::vec3& value)
|
||||
{
|
||||
auto result = element->QueryFloatAttribute(attributeX, &value.x);
|
||||
result = query_result_merge(result, element->QueryFloatAttribute(attributeY, &value.y));
|
||||
result = query_result_merge(result, element->QueryFloatAttribute(attributeZ, &value.z));
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError set_bool_attribute(XMLElement* element, const char* attribute, bool value)
|
||||
{
|
||||
element->SetAttribute(attribute, value ? "true" : "false");
|
||||
return XML_SUCCESS;
|
||||
}
|
||||
|
||||
XMLError set_ivec2_attribute(XMLElement* element, const char* attributeX, const char* attributeY,
|
||||
const glm::ivec2& value)
|
||||
{
|
||||
element->SetAttribute(attributeX, value.x);
|
||||
element->SetAttribute(attributeY, value.y);
|
||||
return XML_SUCCESS;
|
||||
}
|
||||
|
||||
XMLError set_vec2_attribute(XMLElement* element, const char* attributeX, const char* attributeY,
|
||||
const glm::vec2& value)
|
||||
{
|
||||
element->SetAttribute(attributeX, value.x);
|
||||
element->SetAttribute(attributeY, value.y);
|
||||
return XML_SUCCESS;
|
||||
}
|
||||
|
||||
XMLError set_vec3_attribute(XMLElement* element, const char* attributeX, const char* attributeY,
|
||||
const char* attributeZ, const glm::vec3& value)
|
||||
{
|
||||
element->SetAttribute(attributeX, value.x);
|
||||
element->SetAttribute(attributeY, value.y);
|
||||
element->SetAttribute(attributeZ, value.z);
|
||||
return XML_SUCCESS;
|
||||
}
|
||||
|
||||
XMLError query_float_optional_attribute(XMLElement* element, const char* attribute, std::optional<float>& value)
|
||||
{
|
||||
value.emplace();
|
||||
@@ -61,6 +117,29 @@ namespace game::resource::xml
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError query_optional_vec3(XMLElement* element, const char* attributeX, const char* attributeY,
|
||||
const char* attributeZ, std::optional<glm::vec3>& value)
|
||||
{
|
||||
auto hasX = element->FindAttribute(attributeX);
|
||||
auto hasY = element->FindAttribute(attributeY);
|
||||
auto hasZ = element->FindAttribute(attributeZ);
|
||||
|
||||
if (!hasX && !hasY && !hasZ)
|
||||
{
|
||||
value.reset();
|
||||
return XML_NO_ATTRIBUTE;
|
||||
}
|
||||
|
||||
value = glm::vec3();
|
||||
auto result = XML_SUCCESS;
|
||||
|
||||
if (hasX) result = query_result_merge(result, element->QueryFloatAttribute(attributeX, &value->x));
|
||||
if (hasY) result = query_result_merge(result, element->QueryFloatAttribute(attributeY, &value->y));
|
||||
if (hasZ) result = query_result_merge(result, element->QueryFloatAttribute(attributeZ, &value->z));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError document_load(const physfs::Path& path, XMLDocument& document)
|
||||
{
|
||||
if (!path.is_valid())
|
||||
@@ -84,102 +163,139 @@ namespace game::resource::xml
|
||||
return result;
|
||||
}
|
||||
|
||||
void query_event_id(XMLElement* element, const char* name, const Anm2& anm2, int& eventID)
|
||||
XMLError query_event_id(XMLElement* element, const char* name, const Anm2& anm2, int& eventID)
|
||||
{
|
||||
std::string string{};
|
||||
query_string_attribute(element, name, &string);
|
||||
auto result = query_string_attribute(element, name, &string);
|
||||
if (result != XML_SUCCESS) return result;
|
||||
|
||||
if (anm2.eventMap.contains(string))
|
||||
{
|
||||
eventID = anm2.eventMap.at(string);
|
||||
return XML_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.error(std::format("Could not query anm2 event ID: {} ({})", string, anm2.path));
|
||||
eventID = -1;
|
||||
return XML_ERROR_PARSING_ATTRIBUTE;
|
||||
}
|
||||
}
|
||||
|
||||
void query_layer_id(XMLElement* element, const char* name, const Anm2& anm2, int& layerID)
|
||||
XMLError query_layer_id(XMLElement* element, const char* name, const Anm2& anm2, int& layerID)
|
||||
{
|
||||
std::string string{};
|
||||
query_string_attribute(element, name, &string);
|
||||
auto result = query_string_attribute(element, name, &string);
|
||||
if (result != XML_SUCCESS) return result;
|
||||
|
||||
if (anm2.layerMap.contains(string))
|
||||
{
|
||||
layerID = anm2.layerMap.at(string);
|
||||
return XML_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.error(std::format("Could not query anm2 layer ID: {} ({})", string, anm2.path));
|
||||
layerID = -1;
|
||||
return XML_ERROR_PARSING_ATTRIBUTE;
|
||||
}
|
||||
}
|
||||
|
||||
void query_null_id(XMLElement* element, const char* name, const Anm2& anm2, int& nullID)
|
||||
XMLError query_null_id(XMLElement* element, const char* name, const Anm2& anm2, int& nullID)
|
||||
{
|
||||
std::string string{};
|
||||
query_string_attribute(element, name, &string);
|
||||
auto result = query_string_attribute(element, name, &string);
|
||||
if (result != XML_SUCCESS) return result;
|
||||
|
||||
if (anm2.nullMap.contains(string))
|
||||
{
|
||||
nullID = anm2.nullMap.at(string);
|
||||
return XML_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.error(std::format("Could not query anm2 null ID: {} ({})", string, anm2.path));
|
||||
nullID = -1;
|
||||
return XML_ERROR_PARSING_ATTRIBUTE;
|
||||
}
|
||||
}
|
||||
|
||||
void query_anm2(XMLElement* element, const char* name, const std::string& archive, const std::string& rootPath,
|
||||
XMLError query_anm2(XMLElement* element, const char* name, const std::string& archive, const std::string& rootPath,
|
||||
Anm2& anm2, Anm2::Flags flags)
|
||||
{
|
||||
std::string string{};
|
||||
query_string_attribute(element, name, &string);
|
||||
auto result = query_string_attribute(element, name, &string);
|
||||
if (result != XML_SUCCESS) return result;
|
||||
|
||||
anm2 = Anm2(physfs::Path(archive + "/" + rootPath + "/" + string), flags);
|
||||
return XML_SUCCESS;
|
||||
}
|
||||
|
||||
void query_texture(XMLElement* element, const char* name, const std::string& archive, const std::string& rootPath,
|
||||
XMLError query_texture(XMLElement* element, const char* name, const std::string& archive, const std::string& rootPath,
|
||||
Texture& texture)
|
||||
{
|
||||
std::string string{};
|
||||
query_string_attribute(element, name, &string);
|
||||
auto result = query_string_attribute(element, name, &string);
|
||||
if (result != XML_SUCCESS) return result;
|
||||
|
||||
texture = Texture(physfs::Path(archive + "/" + rootPath + "/" + string));
|
||||
return XML_SUCCESS;
|
||||
}
|
||||
|
||||
void query_sound(XMLElement* element, const char* name, const std::string& archive, const std::string& rootPath,
|
||||
XMLError query_sound(XMLElement* element, const char* name, const std::string& archive, const std::string& rootPath,
|
||||
Audio& sound)
|
||||
{
|
||||
std::string string{};
|
||||
query_string_attribute(element, name, &string);
|
||||
auto result = query_string_attribute(element, name, &string);
|
||||
if (result != XML_SUCCESS) return result;
|
||||
|
||||
sound = Audio(physfs::Path(archive + "/" + rootPath + "/" + string));
|
||||
return XML_SUCCESS;
|
||||
}
|
||||
|
||||
void query_font(XMLElement* element, const char* name, const std::string& archive, const std::string& rootPath,
|
||||
XMLError query_font(XMLElement* element, const char* name, const std::string& archive, const std::string& rootPath,
|
||||
Font& font)
|
||||
{
|
||||
std::string string{};
|
||||
query_string_attribute(element, name, &string);
|
||||
auto result = query_string_attribute(element, name, &string);
|
||||
if (result != XML_SUCCESS) return result;
|
||||
|
||||
font = Font(physfs::Path(archive + "/" + rootPath + "/" + string));
|
||||
return XML_SUCCESS;
|
||||
}
|
||||
|
||||
void query_animation_entry(XMLElement* element, AnimationEntry& animationEntry)
|
||||
XMLError query_animation_entry(XMLElement* element, AnimationEntry& animationEntry)
|
||||
{
|
||||
query_string_attribute(element, "Animation", &animationEntry.animation);
|
||||
element->QueryFloatAttribute("Weight", &animationEntry.weight);
|
||||
auto result = query_string_attribute(element, "Animation", &animationEntry.animation);
|
||||
result = query_result_merge(result, element->QueryFloatAttribute("Weight", &animationEntry.weight));
|
||||
return result;
|
||||
}
|
||||
|
||||
void query_animation_entry_collection(XMLElement* element, const char* name,
|
||||
XMLError query_animation_entry_collection(XMLElement* element, const char* name,
|
||||
AnimationEntryCollection& animationEntryCollection)
|
||||
{
|
||||
auto result = XML_SUCCESS;
|
||||
for (auto child = element->FirstChildElement(name); child; child = child->NextSiblingElement(name))
|
||||
query_animation_entry(child, animationEntryCollection.emplace_back());
|
||||
result = query_result_merge(result, query_animation_entry(child, animationEntryCollection.emplace_back()));
|
||||
return result;
|
||||
}
|
||||
|
||||
void query_sound_entry(XMLElement* element, const std::string& archive, const std::string& rootPath,
|
||||
XMLError query_sound_entry(XMLElement* element, const std::string& archive, const std::string& rootPath,
|
||||
SoundEntry& soundEntry, const std::string& attributeName)
|
||||
{
|
||||
query_sound(element, attributeName.c_str(), archive, rootPath, soundEntry.sound);
|
||||
element->QueryFloatAttribute("Weight", &soundEntry.weight);
|
||||
auto result = query_sound(element, attributeName.c_str(), archive, rootPath, soundEntry.sound);
|
||||
result = query_result_merge(result, element->QueryFloatAttribute("Weight", &soundEntry.weight));
|
||||
return result;
|
||||
}
|
||||
|
||||
void query_sound_entry_collection(XMLElement* element, const char* name, const std::string& archive,
|
||||
XMLError query_sound_entry_collection(XMLElement* element, const char* name, const std::string& archive,
|
||||
const std::string& rootPath, SoundEntryCollection& soundEntryCollection,
|
||||
const std::string& attributeName)
|
||||
{
|
||||
auto result = XML_SUCCESS;
|
||||
for (auto child = element->FirstChildElement(name); child; child = child->NextSiblingElement(name))
|
||||
query_sound_entry(child, archive, rootPath, soundEntryCollection.emplace_back(), attributeName);
|
||||
result = query_result_merge(
|
||||
result, query_sound_entry(child, archive, rootPath, soundEntryCollection.emplace_back(), attributeName));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <tinyxml2.h>
|
||||
|
||||
#include "animation_entry.hpp"
|
||||
@@ -19,32 +20,44 @@ namespace game::resource::xml
|
||||
tinyxml2::XMLError query_bool_attribute(tinyxml2::XMLElement*, const char*, bool*);
|
||||
tinyxml2::XMLError query_path_attribute(tinyxml2::XMLElement*, const char*, std::filesystem::path*);
|
||||
tinyxml2::XMLError query_color_attribute(tinyxml2::XMLElement*, const char*, float*);
|
||||
tinyxml2::XMLError query_ivec2(tinyxml2::XMLElement*, const char*, const char*, glm::ivec2&);
|
||||
tinyxml2::XMLError query_vec2(tinyxml2::XMLElement*, const char*, const char*, glm::vec2&);
|
||||
tinyxml2::XMLError query_vec3(tinyxml2::XMLElement*, const char*, const char*, const char*, glm::vec3&);
|
||||
tinyxml2::XMLError set_bool_attribute(tinyxml2::XMLElement*, const char*, bool);
|
||||
tinyxml2::XMLError set_ivec2_attribute(tinyxml2::XMLElement*, const char*, const char*, const glm::ivec2&);
|
||||
tinyxml2::XMLError set_vec2_attribute(tinyxml2::XMLElement*, const char*, const char*, const glm::vec2&);
|
||||
tinyxml2::XMLError set_vec3_attribute(tinyxml2::XMLElement*, const char*, const char*, const char*,
|
||||
const glm::vec3&);
|
||||
tinyxml2::XMLError query_float_optional_attribute(tinyxml2::XMLElement* element, const char* attribute,
|
||||
std::optional<float>& value);
|
||||
tinyxml2::XMLError query_int_optional_attribute(tinyxml2::XMLElement* element, const char* attribute,
|
||||
std::optional<int>& value);
|
||||
tinyxml2::XMLError query_optional_vec3(tinyxml2::XMLElement* element, const char* attributeX, const char* attributeY,
|
||||
const char* attributeZ, std::optional<glm::vec3>& value);
|
||||
|
||||
void query_event_id(tinyxml2::XMLElement* element, const char* name, const Anm2& anm2, int& eventID);
|
||||
void query_layer_id(tinyxml2::XMLElement* element, const char* name, const Anm2& anm2, int& layerID);
|
||||
void query_null_id(tinyxml2::XMLElement* element, const char* name, const Anm2& anm2, int& nullID);
|
||||
tinyxml2::XMLError query_event_id(tinyxml2::XMLElement* element, const char* name, const Anm2& anm2, int& eventID);
|
||||
tinyxml2::XMLError query_layer_id(tinyxml2::XMLElement* element, const char* name, const Anm2& anm2, int& layerID);
|
||||
tinyxml2::XMLError query_null_id(tinyxml2::XMLElement* element, const char* name, const Anm2& anm2, int& nullID);
|
||||
|
||||
void query_anm2(tinyxml2::XMLElement* element, const char* name, const std::string& archive,
|
||||
tinyxml2::XMLError query_anm2(tinyxml2::XMLElement* element, const char* name, const std::string& archive,
|
||||
const std::string& rootPath, Anm2& anm2, Anm2::Flags flags = {});
|
||||
void query_texture(tinyxml2::XMLElement* element, const char* name, const std::string& archive,
|
||||
tinyxml2::XMLError query_texture(tinyxml2::XMLElement* element, const char* name, const std::string& archive,
|
||||
const std::string& rootPath, Texture& texture);
|
||||
void query_sound(tinyxml2::XMLElement* element, const char* name, const std::string& archive,
|
||||
tinyxml2::XMLError query_sound(tinyxml2::XMLElement* element, const char* name, const std::string& archive,
|
||||
const std::string& rootPath, Audio& sound);
|
||||
void query_font(tinyxml2::XMLElement* element, const char* name, const std::string& archive,
|
||||
tinyxml2::XMLError query_font(tinyxml2::XMLElement* element, const char* name, const std::string& archive,
|
||||
const std::string& rootPath, Font& font);
|
||||
|
||||
void query_animation_entry(tinyxml2::XMLElement* element, AnimationEntry& animationEntry);
|
||||
void query_animation_entry_collection(tinyxml2::XMLElement* element, const char* name,
|
||||
tinyxml2::XMLError query_animation_entry(tinyxml2::XMLElement* element, AnimationEntry& animationEntry);
|
||||
tinyxml2::XMLError query_animation_entry_collection(tinyxml2::XMLElement* element, const char* name,
|
||||
AnimationEntryCollection& animationEntryCollection);
|
||||
|
||||
void query_sound_entry(tinyxml2::XMLElement* element, const std::string& archive, const std::string& rootPath,
|
||||
SoundEntry& soundEntry, const std::string& attributeName = "Sound");
|
||||
void query_sound_entry_collection(tinyxml2::XMLElement* element, const char* name, const std::string& archive,
|
||||
const std::string& rootPath, SoundEntryCollection& soundEntryCollection,
|
||||
tinyxml2::XMLError query_sound_entry(tinyxml2::XMLElement* element, const std::string& archive,
|
||||
const std::string& rootPath, SoundEntry& soundEntry,
|
||||
const std::string& attributeName = "Sound");
|
||||
tinyxml2::XMLError query_sound_entry_collection(tinyxml2::XMLElement* element, const char* name,
|
||||
const std::string& archive, const std::string& rootPath,
|
||||
SoundEntryCollection& soundEntryCollection,
|
||||
const std::string& attributeName = "Sound");
|
||||
|
||||
tinyxml2::XMLError document_load(const util::physfs::Path&, tinyxml2::XMLDocument&);
|
||||
|
||||
@@ -31,8 +31,8 @@ namespace game
|
||||
case SELECT:
|
||||
select.tick();
|
||||
break;
|
||||
case MAIN:
|
||||
main.tick(resources);
|
||||
case PLAY:
|
||||
play.tick(resources);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -52,7 +52,7 @@ namespace game
|
||||
ImGui_ImplSDL3_ProcessEvent(&event);
|
||||
if (event.type == SDL_EVENT_QUIT)
|
||||
{
|
||||
if (type == MAIN) main.exit(resources);
|
||||
if (type == PLAY) play.exit(resources);
|
||||
isRunning = false;
|
||||
}
|
||||
if (!isRunning) return;
|
||||
@@ -68,30 +68,30 @@ namespace game
|
||||
select.update(resources);
|
||||
if (select.info.isNewGame || select.info.isContinue)
|
||||
{
|
||||
Main::Game game = select.info.isNewGame ? Main::NEW_GAME : Main::CONTINUE;
|
||||
if (game == Main::NEW_GAME) resources.character_save_set(select.characterIndex, resource::xml::Save());
|
||||
Play::Game game = select.info.isNewGame ? Play::NEW_GAME : Play::CONTINUE;
|
||||
if (game == Play::NEW_GAME) resources.character_save_set(select.characterIndex, resource::xml::Save());
|
||||
|
||||
main.set(resources, select.characterIndex, game);
|
||||
type = MAIN;
|
||||
play.set(resources, select.characterIndex, game);
|
||||
type = PLAY;
|
||||
|
||||
select.info.isNewGame = false;
|
||||
select.info.isContinue = false;
|
||||
}
|
||||
break;
|
||||
case MAIN:
|
||||
main.update(resources);
|
||||
if (main.menu.configuration.isGoToSelect)
|
||||
case PLAY:
|
||||
play.update(resources);
|
||||
if (play.menu.settingsMenu.isGoToSelect)
|
||||
{
|
||||
main.exit(resources);
|
||||
play.exit(resources);
|
||||
type = SELECT;
|
||||
main.menu.configuration.isGoToSelect = false;
|
||||
play.menu.settingsMenu.isGoToSelect = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
auto isHideCursor = type == MAIN;
|
||||
auto isHideCursor = type == PLAY;
|
||||
if (isHideCursor != isCursorHidden)
|
||||
{
|
||||
if (isHideCursor)
|
||||
@@ -104,6 +104,8 @@ namespace game
|
||||
|
||||
void State::render()
|
||||
{
|
||||
auto& color =
|
||||
resources.settings.isUseCharacterColor && type == PLAY ? play.character.data.color : resources.settings.color;
|
||||
auto windowSize = resources.settings.windowSize;
|
||||
#ifndef __EMSCRIPTEN__
|
||||
SDL_GetWindowSize(window, &windowSize.x, &windowSize.y);
|
||||
@@ -111,7 +113,7 @@ namespace game
|
||||
|
||||
canvas.bind();
|
||||
canvas.size_set(windowSize);
|
||||
canvas.clear(vec4(resources.settings.color, 1.0f));
|
||||
canvas.clear(vec4(color, 1.0f));
|
||||
canvas.unbind();
|
||||
|
||||
switch (type)
|
||||
@@ -119,8 +121,8 @@ namespace game
|
||||
case SELECT:
|
||||
select.render(resources, canvas);
|
||||
break;
|
||||
case MAIN:
|
||||
main.render(resources, canvas);
|
||||
case PLAY:
|
||||
play.render(resources, canvas);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "canvas.hpp"
|
||||
#include "render/canvas.hpp"
|
||||
#include "resources.hpp"
|
||||
|
||||
#include "state/main.hpp"
|
||||
#include "state/play.hpp"
|
||||
#include "state/select.hpp"
|
||||
|
||||
#include "entity/cursor.hpp"
|
||||
@@ -22,7 +22,7 @@ namespace game
|
||||
|
||||
enum Type
|
||||
{
|
||||
MAIN,
|
||||
PLAY,
|
||||
SELECT
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace game
|
||||
|
||||
Resources resources;
|
||||
|
||||
state::Main main;
|
||||
state::Play play;
|
||||
state::Select select;
|
||||
|
||||
void tick();
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
#include "configuration.hpp"
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <imgui.h>
|
||||
|
||||
#include "../util/math.hpp"
|
||||
#include "../util/imgui/style.hpp"
|
||||
#include "../util/imgui/widget.hpp"
|
||||
#include "../util/measurement.hpp"
|
||||
|
||||
using namespace game::util;
|
||||
using namespace game::util::imgui;
|
||||
|
||||
namespace game::state
|
||||
{
|
||||
void Configuration::update(Resources& resources, Mode mode)
|
||||
{
|
||||
auto& settings = resources.settings;
|
||||
auto& measurementSystem = settings.measurementSystem;
|
||||
auto& volume = settings.volume;
|
||||
auto& color = settings.color;
|
||||
|
||||
ImGui::SeparatorText("Measurement System");
|
||||
WIDGET_FX(ImGui::RadioButton("Metric", (int*)&measurementSystem, measurement::METRIC));
|
||||
ImGui::SetItemTooltip("%s", "Use kilograms (kg).");
|
||||
ImGui::SameLine();
|
||||
WIDGET_FX(ImGui::RadioButton("Imperial", (int*)&measurementSystem, measurement::IMPERIAL));
|
||||
ImGui::SetItemTooltip("%s", "Use pounds (lbs).");
|
||||
|
||||
ImGui::SeparatorText("Sound");
|
||||
if (WIDGET_FX(ImGui::SliderInt("Volume", &volume, 0, 100, "%d%%")))
|
||||
resources.volume_set(math::to_unit((float)volume));
|
||||
ImGui::SetItemTooltip("%s", "Adjust master volume.");
|
||||
|
||||
ImGui::SeparatorText("Appearance");
|
||||
|
||||
if (WIDGET_FX(
|
||||
ImGui::ColorEdit3("Color", value_ptr(color), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoTooltip)))
|
||||
style::color_set(color);
|
||||
ImGui::SetItemTooltip("%s", "Change the UI color.");
|
||||
|
||||
ImGui::Separator();
|
||||
if (WIDGET_FX(ImGui::Button("Reset to Default", ImVec2(-FLT_MIN, 0)))) settings = resource::xml::Settings();
|
||||
|
||||
if (mode == MAIN)
|
||||
{
|
||||
ImGui::Separator();
|
||||
|
||||
if (WIDGET_FX(ImGui::Button("Save", ImVec2(-FLT_MIN, 0)))) isSave = true;
|
||||
ImGui::SetItemTooltip("%s", "Save the game.\n(Note: the game autosaves frequently.)");
|
||||
|
||||
if (WIDGET_FX(ImGui::Button("Return to Characters", ImVec2(-FLT_MIN, 0)))) isGoToSelect = true;
|
||||
ImGui::SetItemTooltip("%s", "Go back to the character selection screen.\nProgress will be saved.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../resources.hpp"
|
||||
|
||||
namespace game::state
|
||||
{
|
||||
class Configuration
|
||||
{
|
||||
public:
|
||||
enum Mode
|
||||
{
|
||||
SELECT,
|
||||
MAIN
|
||||
};
|
||||
|
||||
bool isGoToSelect{};
|
||||
bool isSave{};
|
||||
|
||||
void update(Resources&, Mode = SELECT);
|
||||
};
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#include "chat.hpp"
|
||||
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
|
||||
using namespace game::resource;
|
||||
using namespace game::util::imgui;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
void Chat::update(Resources&, Text& text, entity::Character& character)
|
||||
{
|
||||
auto& dialogue = character.data.dialogue;
|
||||
auto size = ImGui::GetContentRegionAvail();
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
|
||||
if (dialogue.random.is_valid())
|
||||
if (WIDGET_FX(ImGui::Button("Let's chat!", ImVec2(size.x, 0))))
|
||||
text.set(dialogue.get(dialogue.random), character);
|
||||
|
||||
ImGui::PopFont();
|
||||
|
||||
if (dialogue.help.is_valid())
|
||||
if (WIDGET_FX(ImGui::Button("Help", ImVec2(size.x, 0)))) text.set(dialogue.get(dialogue.help), character);
|
||||
|
||||
auto stage = glm::clamp(0, character.stage_get(), character.stage_max_get());
|
||||
auto& pool = stage > 0 ? character.data.stages.at(stage - 1).pool : character.data.pool;
|
||||
|
||||
if (pool.is_valid())
|
||||
if (WIDGET_FX(ImGui::Button("How are you feeling?", ImVec2(size.x, 0)))) text.set(dialogue.get(pool), character);
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
#include "cheats.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
#include "../../util/imgui/input_int_ex.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
void Cheats::update(Resources&, entity::Character& character, Inventory& inventory, Text& text)
|
||||
{
|
||||
static constexpr auto FEED_INCREMENT = 100.0f;
|
||||
|
||||
if (ImGui::BeginChild("##Cheats"))
|
||||
{
|
||||
|
||||
if (WIDGET_FX(ImGui::Button("Feed")))
|
||||
{
|
||||
character.calories = std::min(character.calories + FEED_INCREMENT, character.max_capacity());
|
||||
character.queue_idle_animation();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (WIDGET_FX(ImGui::Button("Starve")))
|
||||
{
|
||||
character.calories = std::max(0.0f, character.calories - FEED_INCREMENT);
|
||||
character.queue_idle_animation();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (WIDGET_FX(ImGui::Button("Digest"))) character.digestionProgress = entity::Character::DIGESTION_MAX;
|
||||
|
||||
auto stage = character.stage + 1;
|
||||
if (WIDGET_FX(ImGui::SliderInt("Stage", &stage, 1, (int)character.data.stages.size() + 1)))
|
||||
{
|
||||
character.stage = glm::clamp(0, stage - 1, (int)character.data.stages.size());
|
||||
character.weight =
|
||||
character.stage == 0 ? character.data.weight : character.data.stages.at(character.stage - 1).threshold;
|
||||
character.queue_idle_animation();
|
||||
}
|
||||
|
||||
WIDGET_FX(ImGui::SliderFloat("Capacity", &character.capacity, character.data.capacityMin,
|
||||
character.data.capacityMax, "%0.0f kcal"));
|
||||
|
||||
WIDGET_FX(ImGui::SliderFloat("Digestion Rate", &character.digestionRate, character.data.digestionRateMin,
|
||||
character.data.digestionRateMax, "%0.2f% / tick"));
|
||||
WIDGET_FX(ImGui::SliderFloat("Eat Speed", &character.eatSpeed, character.data.eatSpeedMin,
|
||||
character.data.eatSpeedMax, "%0.2fx"));
|
||||
|
||||
ImGui::SeparatorText("Animations");
|
||||
ImGui::Text("Now Playing: %s", character.animationMapReverse.at(character.animationIndex).c_str());
|
||||
|
||||
auto childSize = ImVec2(0, ImGui::GetContentRegionAvail().y / 3);
|
||||
|
||||
if (ImGui::BeginChild("##Animations", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
for (int i = 0; i < (int)character.animations.size(); i++)
|
||||
{
|
||||
auto& animation = character.animations[i];
|
||||
ImGui::PushID(i);
|
||||
if (WIDGET_FX(ImGui::Selectable(animation.name.c_str())))
|
||||
character.play(animation.name.c_str(), entity::Actor::PLAY_FORCE);
|
||||
ImGui::SetItemTooltip("%s", animation.name.c_str());
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SeparatorText("Dialogue");
|
||||
|
||||
if (ImGui::BeginChild("##Dialogue", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
for (int i = 0; i < (int)character.data.dialogue.entries.size(); i++)
|
||||
{
|
||||
auto& entry = character.data.dialogue.entries[i];
|
||||
ImGui::PushID(i);
|
||||
if (WIDGET_FX(ImGui::Selectable(entry.name.c_str()))) text.set(&entry, character);
|
||||
ImGui::SetItemTooltip("%s", entry.name.c_str());
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SeparatorText("Inventory");
|
||||
|
||||
if (ImGui::BeginChild("##Inventory", ImGui::GetContentRegionAvail(), ImGuiChildFlags_Borders))
|
||||
{
|
||||
auto& schema = character.data.itemSchema;
|
||||
|
||||
ImGui::PushItemWidth(100);
|
||||
for (int i = 0; i < (int)schema.items.size(); i++)
|
||||
{
|
||||
auto& item = schema.items[i];
|
||||
ImGui::PushID(i);
|
||||
WIDGET_FX(input_int_range(item.name.c_str(), &inventory.values[i], 0, schema.quantityMax, 1, 5));
|
||||
ImGui::SetItemTooltip("%s", item.name.c_str());
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
#include "debug.hpp"
|
||||
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace game::util::imgui;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
void Debug::update(entity::Character& character, entity::Cursor& cursor, ItemManager& itemManager, Canvas& canvas)
|
||||
{
|
||||
auto cursorPosition = canvas.screen_position_convert(cursor.position);
|
||||
|
||||
ImGui::Text("Cursor Pos (Screen): %0.0f, %0.0f", cursor.position.x, cursor.position.y);
|
||||
ImGui::Text("Cursor Pos (World): %0.0f, %0.0f", cursorPosition.x, cursorPosition.y);
|
||||
|
||||
WIDGET_FX(ImGui::Checkbox("Show Nulls (Hitboxes)", &character.isShowNulls));
|
||||
WIDGET_FX(ImGui::Checkbox("Show World Bounds", &isBoundsDisplay));
|
||||
|
||||
if (!itemManager.items.empty())
|
||||
{
|
||||
ImGui::SeparatorText("Item");
|
||||
|
||||
for (int i = 0; i < (int)itemManager.items.size(); i++)
|
||||
{
|
||||
auto& item = itemManager.items[i];
|
||||
if (itemManager.heldItemIndex == i) ImGui::Text("Held");
|
||||
ImGui::Text("Type: %i", item.schemaID);
|
||||
ImGui::Text("Position: %0.0f, %0.0f", item.position.x, item.position.y);
|
||||
ImGui::Text("Velocity: %0.0f, %0.0f", item.velocity.x, item.velocity.y);
|
||||
ImGui::Text("Chew Count: %i", item.chewCount);
|
||||
ImGui::Separator();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
#include "inventory.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <format>
|
||||
#include <ranges>
|
||||
|
||||
#include "../../util/color.hpp"
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
#include "../../util/math.hpp"
|
||||
|
||||
using namespace game::util;
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::entity;
|
||||
using namespace game::resource;
|
||||
using namespace glm;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
void Inventory::tick()
|
||||
{
|
||||
for (auto& [i, actor] : actors)
|
||||
actor.tick();
|
||||
}
|
||||
|
||||
void Inventory::update(Resources& resources, ItemManager& itemManager, entity::Character& character)
|
||||
{
|
||||
auto& schema = character.data.itemSchema;
|
||||
|
||||
if (!itemManager.returnItemIDs.empty())
|
||||
{
|
||||
for (auto& id : itemManager.returnItemIDs)
|
||||
values[id]++;
|
||||
itemManager.returnItemIDs.clear();
|
||||
}
|
||||
|
||||
if (ImGui::BeginChild("##Inventory Child"))
|
||||
{
|
||||
auto cursorPos = ImGui::GetCursorPos();
|
||||
auto cursorStartX = ImGui::GetCursorPosX();
|
||||
|
||||
auto size = ImVec2(SIZE, SIZE);
|
||||
|
||||
for (int i = 0; i < (int)schema.items.size(); i++)
|
||||
{
|
||||
auto& item = schema.items[i];
|
||||
auto& quantity = values[i];
|
||||
auto& category = schema.categories[item.categoryID];
|
||||
auto& calories = item.calories;
|
||||
auto& digestionBonus = item.digestionBonus;
|
||||
auto& eatSpeedBonus = item.eatSpeedBonus;
|
||||
auto& rarity = schema.rarities[item.rarityID];
|
||||
|
||||
quantity = glm::clamp(0, quantity, schema.quantityMax);
|
||||
if (rarity.isHidden && quantity <= 0) continue;
|
||||
|
||||
ImGui::PushID(i);
|
||||
|
||||
ImGui::SetCursorPos(cursorPos);
|
||||
auto cursorScreenPos = ImGui::GetCursorScreenPos();
|
||||
|
||||
if (!actors.contains(i))
|
||||
{
|
||||
actors[i] = Actor(schema.anm2s[i], {}, Actor::SET);
|
||||
rects[i] = actors[i].rect();
|
||||
}
|
||||
auto& rect = rects[i];
|
||||
auto rectSize = vec2(rect.z, rect.w);
|
||||
|
||||
auto previewScale = (size.x <= 0.0f || size.y <= 0.0f || rectSize.x <= 0.0f || rectSize.y <= 0.0f ||
|
||||
!std::isfinite(rectSize.x) || !std::isfinite(rectSize.y))
|
||||
? 0.0f
|
||||
: std::min(size.x / rectSize.x, size.y / rectSize.y);
|
||||
|
||||
auto previewSize = rectSize * previewScale;
|
||||
auto canvasSize = ivec2(std::max(1.0f, previewSize.x), std::max(1.0f, previewSize.y));
|
||||
if (!canvases.contains(i)) canvases.emplace((int)i, Canvas(canvasSize, Canvas::FLIP));
|
||||
auto& canvas = canvases[i];
|
||||
canvas.zoom = math::to_percent(previewScale);
|
||||
canvas.pan = vec2(rect.x, rect.y);
|
||||
canvas.bind();
|
||||
canvas.size_set(canvasSize);
|
||||
canvas.clear();
|
||||
|
||||
actors[i].render(resources.shaders[shader::TEXTURE], resources.shaders[shader::RECT], canvas);
|
||||
canvas.unbind();
|
||||
|
||||
ImGui::BeginDisabled(quantity < 1);
|
||||
if (WIDGET_FX(ImGui::ImageButton("##Image Button", canvas.texture, size, ImVec2(), ImVec2(1, 1), ImVec4(),
|
||||
quantity <= 0 ? ImVec4(0, 0, 0, 1) : ImVec4(1, 1, 1, 1))) &&
|
||||
quantity > 0)
|
||||
{
|
||||
if (category.isEdible)
|
||||
{
|
||||
if (itemManager.items.size() + 1 >= ItemManager::LIMIT)
|
||||
character.data.itemSchema.sounds.dispose.play();
|
||||
else
|
||||
{
|
||||
character.data.itemSchema.sounds.summon.play();
|
||||
itemManager.queuedItemIDs.emplace_back(i);
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
else if (item.isToggleSpritesheet)
|
||||
{
|
||||
character.spritesheet_set(character.spritesheetType == Character::NORMAL ? Character::ALTERNATE
|
||||
: Character::NORMAL);
|
||||
character.data.alternateSpritesheet.sound.play();
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
if (quantity > 0)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
|
||||
|
||||
ImGui::Text("%s (x%i)", item.name.c_str(), quantity);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
|
||||
|
||||
ImGui::Text("-- %s (%s) --", category.name.c_str(), rarity.name.c_str());
|
||||
if (item.flavorID.has_value()) ImGui::Text("Flavor: %s", schema.flavors[*item.flavorID].name.c_str());
|
||||
if (calories.has_value()) ImGui::Text("%0.0f kcal", *calories);
|
||||
if (digestionBonus.has_value())
|
||||
{
|
||||
if (*digestionBonus > 0)
|
||||
ImGui::Text("Digestion Rate Bonus: +%0.2f%% / sec", *digestionBonus * 60.0f);
|
||||
else if (digestionBonus < 0)
|
||||
ImGui::Text("Digestion Rate Penalty: %0.2f%% / sec", *digestionBonus * 60.0f);
|
||||
}
|
||||
if (eatSpeedBonus.has_value())
|
||||
{
|
||||
if (*eatSpeedBonus > 0)
|
||||
ImGui::Text("Eat Speed Bonus: +%0.2f%% / sec", *eatSpeedBonus);
|
||||
else if (eatSpeedBonus < 0)
|
||||
ImGui::Text("Eat Speed Penalty: %0.2f%% / sec", *eatSpeedBonus);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted(item.description.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
|
||||
ImGui::TextUnformatted("???");
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
|
||||
|
||||
auto text = std::format("x{}", quantity);
|
||||
auto textPos = ImVec2(cursorScreenPos.x + size.x - ImGui::CalcTextSize(text.c_str()).x,
|
||||
cursorScreenPos.y + size.y - ImGui::GetTextLineHeightWithSpacing());
|
||||
ImGui::GetWindowDrawList()->AddText(textPos, ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_Text)),
|
||||
text.c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
auto increment = ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x;
|
||||
cursorPos.x += increment;
|
||||
|
||||
if (cursorPos.x + increment > ImGui::GetContentRegionAvail().x)
|
||||
{
|
||||
cursorPos.x = cursorStartX;
|
||||
cursorPos.y += increment;
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
if (count() == 0) ImGui::Text("Check the \"Play\" tab to earn rewards!");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
int Inventory::count()
|
||||
{
|
||||
int count{};
|
||||
for (auto& [type, quantity] : values)
|
||||
count += quantity;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
#include "stats.hpp"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "../../util/measurement.hpp"
|
||||
|
||||
using namespace game::resource;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
void Stats::update(Resources& resources, Play& play, entity::Character& character)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
|
||||
ImGui::TextUnformatted(character.data.name.c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
auto& playSchema = character.data.playSchema;
|
||||
auto& system = resources.settings.measurementSystem;
|
||||
auto weight = character.weight_get(system);
|
||||
auto weightUnit = system == measurement::IMPERIAL ? "lbs" : "kg";
|
||||
|
||||
ImGui::Text("Weight: %0.2f %s (Stage: %i)", weight, weightUnit, character.stage_get() + 1);
|
||||
ImGui::Text("Capacity: %0.0f kcal (Max: %0.0f kcal)", character.capacity, character.max_capacity());
|
||||
ImGui::Text("Digestion Rate: %0.2f%%/sec", character.digestion_rate_get());
|
||||
ImGui::Text("Eating Speed: %0.2fx", character.eatSpeed);
|
||||
|
||||
ImGui::SeparatorText("Totals");
|
||||
|
||||
ImGui::Text("Total Calories Consumed: %0.0f kcal", character.totalCaloriesConsumed);
|
||||
ImGui::Text("Total Food Items Eaten: %i", character.totalFoodItemsEaten);
|
||||
|
||||
ImGui::SeparatorText("Play");
|
||||
|
||||
ImGui::Text("Best: %i pts (%ix)", play.highScore, play.bestCombo);
|
||||
ImGui::Text("Total Plays: %i", play.totalPlays);
|
||||
|
||||
for (int i = 0; i < (int)playSchema.grades.size(); i++)
|
||||
{
|
||||
auto& grade = playSchema.grades[i];
|
||||
ImGui::Text("%s: %i", grade.namePlural.c_str(), play.gradeCounts[i]);
|
||||
}
|
||||
|
||||
ImGui::Text("Accuracy: %0.2f%%", play.accuracy_score_get(character));
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
#include "../../resources.hpp"
|
||||
|
||||
#include "play.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
class Stats
|
||||
{
|
||||
public:
|
||||
void update(Resources&, Play&, entity::Character&);
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "main.hpp"
|
||||
#include "play.hpp"
|
||||
#include "play/style.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <glm/glm.hpp>
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
@@ -10,13 +12,26 @@
|
||||
#include "../util/math.hpp"
|
||||
|
||||
using namespace game::resource;
|
||||
using namespace game::resource::xml;
|
||||
using namespace game::util;
|
||||
using namespace game::state::main;
|
||||
using namespace game::state::play;
|
||||
using namespace glm;
|
||||
|
||||
namespace game::state
|
||||
{
|
||||
World::Focus Main::focus_get()
|
||||
namespace
|
||||
{
|
||||
int durability_animation_index_get(const resource::xml::Item& schema, const resource::xml::Anm2& anm2, int durability,
|
||||
int durabilityMax)
|
||||
{
|
||||
if (durability >= durabilityMax) return -1;
|
||||
|
||||
auto animationName = schema.animations.chew + std::to_string(std::max(0, durability));
|
||||
return anm2.animationMap.contains(animationName) ? anm2.animationMap.at(animationName) : -1;
|
||||
}
|
||||
}
|
||||
|
||||
World::Focus Play::focus_get()
|
||||
{
|
||||
if (!isWindows) return World::CENTER;
|
||||
|
||||
@@ -26,7 +41,7 @@ namespace game::state
|
||||
: World::CENTER;
|
||||
}
|
||||
|
||||
void Main::set(Resources& resources, int selectedCharacterIndex, enum Game game)
|
||||
void Play::set(Resources& resources, int selectedCharacterIndex, enum Game game)
|
||||
{
|
||||
auto& data = resources.character_get(selectedCharacterIndex);
|
||||
auto& saveData = data.save;
|
||||
@@ -34,12 +49,15 @@ namespace game::state
|
||||
auto& dialogue = data.dialogue;
|
||||
auto& menuSchema = data.menuSchema;
|
||||
this->characterIndex = selectedCharacterIndex;
|
||||
cheatCodeIndex = 0;
|
||||
cheatCodeStartTime = 0.0;
|
||||
|
||||
character =
|
||||
entity::Character(data, vec2(World::BOUNDS.x + World::BOUNDS.z * 0.5f, World::BOUNDS.w - World::BOUNDS.y));
|
||||
character.digestionRate = glm::clamp(data.digestionRateMin, character.digestionRate, data.digestionRateMax);
|
||||
character.eatSpeed = glm::clamp(data.eatSpeedMin, character.eatSpeed, data.eatSpeedMax);
|
||||
character.capacity = glm::clamp(data.capacityMin, character.capacity, data.capacityMax);
|
||||
character.digestionRate =
|
||||
glm::clamp(character.digestionRate, (float)data.digestionRateMin, (float)data.digestionRateMax);
|
||||
character.eatSpeed = glm::clamp(character.eatSpeed, (float)data.eatSpeedMin, (float)data.eatSpeedMax);
|
||||
character.capacity = glm::clamp(character.capacity, (float)data.capacityMin, (float)data.capacityMax);
|
||||
|
||||
auto isAlternateSpritesheet =
|
||||
(game == NEW_GAME && math::random_percent_roll(data.alternateSpritesheet.chanceOnNewGame));
|
||||
@@ -55,6 +73,7 @@ namespace game::state
|
||||
characterManager = CharacterManager{};
|
||||
|
||||
cursor = entity::Cursor(character.data.cursorSchema.anm2);
|
||||
cursor.interactTypeID = character.data.interactTypeNames.empty() ? -1 : 0;
|
||||
|
||||
menu.inventory = Inventory{};
|
||||
for (auto& [id, quantity] : saveData.inventory)
|
||||
@@ -67,28 +86,34 @@ namespace game::state
|
||||
for (auto& item : saveData.items)
|
||||
{
|
||||
auto& anm2 = itemSchema.anm2s.at(item.id);
|
||||
auto chewAnimation = itemSchema.animations.chew + std::to_string(item.chewCount);
|
||||
auto animationIndex = item.chewCount > 0 ? anm2.animationMap[chewAnimation] : -1;
|
||||
auto& schemaItem = itemSchema.items.at(item.id);
|
||||
auto durabilityMax = schemaItem.durability.value_or(itemSchema.durability);
|
||||
auto animationIndex = durability_animation_index_get(itemSchema, anm2, item.durability, durabilityMax);
|
||||
auto& saveItem = itemSchema.anm2s.at(item.id);
|
||||
itemManager.items.emplace_back(saveItem, item.position, item.id, item.chewCount, animationIndex, item.velocity,
|
||||
itemManager.items.emplace_back(saveItem, item.position, item.id, item.durability, animationIndex, item.velocity,
|
||||
item.rotation);
|
||||
}
|
||||
|
||||
imgui::style::rounding_set(menuSchema.rounding);
|
||||
imgui::widget::sounds_set(&menuSchema.sounds.hover, &menuSchema.sounds.select);
|
||||
play::style::color_set(resources, character);
|
||||
|
||||
menu.play = Play(character);
|
||||
menu.play.totalPlays = saveData.totalPlays;
|
||||
menu.play.highScore = saveData.highScore;
|
||||
menu.play.bestCombo = saveData.bestCombo;
|
||||
menu.play.gradeCounts = saveData.gradeCounts;
|
||||
menu.play.isHighScoreAchieved = saveData.highScore > 0 ? true : false;
|
||||
|
||||
menu.isChat = character.data.dialogue.help.is_valid() || character.data.dialogue.random.is_valid();
|
||||
menu.arcade = Arcade(character);
|
||||
menu.arcade.skillCheck.totalPlays = saveData.totalPlays;
|
||||
menu.arcade.skillCheck.highScore = saveData.highScore;
|
||||
menu.arcade.skillCheck.bestCombo = saveData.bestCombo;
|
||||
menu.arcade.skillCheck.gradeCounts = saveData.gradeCounts;
|
||||
menu.arcade.skillCheck.isHighScoreAchieved = saveData.highScore > 0 ? true : false;
|
||||
|
||||
text.entry = nullptr;
|
||||
text.isEnabled = false;
|
||||
|
||||
#if DEBUG
|
||||
menu.isCheats = true;
|
||||
#else
|
||||
menu.isCheats = false;
|
||||
#endif
|
||||
|
||||
isPostgame = saveData.isPostgame;
|
||||
if (character.stage_get() >= character.stage_max_get()) isPostgame = true;
|
||||
if (isPostgame) menu.isCheats = true;
|
||||
@@ -107,18 +132,34 @@ namespace game::state
|
||||
character.queue_play({.animation = dialogue.start.animation, .isInterruptible = false});
|
||||
character.tick();
|
||||
isStart = true;
|
||||
isStartBegin = false;
|
||||
isStartEnd = false;
|
||||
}
|
||||
|
||||
if (isPostgame)
|
||||
{
|
||||
isEnd = true;
|
||||
isEndBegin = true;
|
||||
isEndEnd = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
isEnd = false;
|
||||
isEndBegin = false;
|
||||
isEndEnd = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Main::exit(Resources& resources)
|
||||
void Play::exit(Resources& resources)
|
||||
{
|
||||
imgui::style::color_set(resources.settings.color);
|
||||
imgui::style::rounding_set();
|
||||
imgui::widget::sounds_set(nullptr, nullptr);
|
||||
ImGui::GetIO().FontDefault = resources.font.get();
|
||||
save(resources);
|
||||
}
|
||||
|
||||
void Main::tick(Resources&)
|
||||
void Play::tick(Resources&)
|
||||
{
|
||||
character.tick();
|
||||
cursor.tick();
|
||||
@@ -130,11 +171,57 @@ namespace game::state
|
||||
item.tick();
|
||||
}
|
||||
|
||||
void Main::update(Resources& resources)
|
||||
void Play::update(Resources& resources)
|
||||
{
|
||||
static constexpr std::array<ImGuiKey, 10> CHEAT_CODE = {
|
||||
ImGuiKey_UpArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow, ImGuiKey_DownArrow, ImGuiKey_LeftArrow,
|
||||
ImGuiKey_RightArrow, ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_B, ImGuiKey_A};
|
||||
static constexpr std::array<ImGuiKey, 6> CHEAT_INPUT_KEYS = {
|
||||
ImGuiKey_UpArrow, ImGuiKey_DownArrow, ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_B, ImGuiKey_A};
|
||||
static constexpr auto CHEAT_CODE_INPUT_TIME_SECONDS = 5.0;
|
||||
|
||||
auto focus = focus_get();
|
||||
auto& dialogue = character.data.dialogue;
|
||||
|
||||
if (!menu.isCheats)
|
||||
{
|
||||
for (auto key : CHEAT_INPUT_KEYS)
|
||||
{
|
||||
if (!ImGui::IsKeyPressed(key, false)) continue;
|
||||
|
||||
if (key == CHEAT_CODE[cheatCodeIndex])
|
||||
{
|
||||
cheatCodeIndex++;
|
||||
cheatCodeStartTime = ImGui::GetTime();
|
||||
}
|
||||
else if (key == CHEAT_CODE[0])
|
||||
{
|
||||
cheatCodeIndex = 1;
|
||||
cheatCodeStartTime = ImGui::GetTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
cheatCodeIndex = 0;
|
||||
cheatCodeStartTime = 0.0;
|
||||
}
|
||||
|
||||
if (cheatCodeIndex >= (int)CHEAT_CODE.size())
|
||||
{
|
||||
menu.isCheats = true;
|
||||
cheatCodeIndex = 0;
|
||||
cheatCodeStartTime = 0.0;
|
||||
toasts.push(character.data.strings.get(Strings::ToastCheatsUnlocked));
|
||||
character.data.menuSchema.sounds.cheatsActivated.play();
|
||||
}
|
||||
}
|
||||
|
||||
if (cheatCodeIndex > 0 && (ImGui::GetTime() - cheatCodeStartTime > CHEAT_CODE_INPUT_TIME_SECONDS))
|
||||
{
|
||||
cheatCodeIndex = 0;
|
||||
cheatCodeStartTime = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
if (isWindows)
|
||||
{
|
||||
menu.update(resources, itemManager, character, cursor, text, worldCanvas);
|
||||
@@ -199,6 +286,7 @@ namespace game::state
|
||||
isEndEnd = true;
|
||||
isEnd = false;
|
||||
isPostgame = true;
|
||||
menu.isCheats = true;
|
||||
world.character_focus(character, worldCanvas, focus_get());
|
||||
}
|
||||
}
|
||||
@@ -211,15 +299,15 @@ namespace game::state
|
||||
cursor.update();
|
||||
world.update(character, cursor, worldCanvas, focus);
|
||||
|
||||
if (autosaveTime += ImGui::GetIO().DeltaTime; autosaveTime > AUTOSAVE_TIME || menu.configuration.isSave)
|
||||
if (autosaveTime += ImGui::GetIO().DeltaTime; autosaveTime > AUTOSAVE_TIME || menu.settingsMenu.isSave)
|
||||
{
|
||||
save(resources);
|
||||
autosaveTime = 0;
|
||||
menu.configuration.isSave = false;
|
||||
menu.settingsMenu.isSave = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Main::render(Resources& resources, Canvas& canvas)
|
||||
void Play::render(Resources& resources, Canvas& canvas)
|
||||
{
|
||||
auto& textureShader = resources.shaders[shader::TEXTURE];
|
||||
auto& rectShader = resources.shaders[shader::RECT];
|
||||
@@ -256,7 +344,7 @@ namespace game::state
|
||||
canvas.unbind();
|
||||
}
|
||||
|
||||
void Main::save(Resources& resources)
|
||||
void Play::save(Resources& resources)
|
||||
{
|
||||
resource::xml::Save save;
|
||||
|
||||
@@ -270,10 +358,10 @@ namespace game::state
|
||||
save.digestionTimer = character.digestionTimer;
|
||||
save.totalCaloriesConsumed = character.totalCaloriesConsumed;
|
||||
save.totalFoodItemsEaten = character.totalFoodItemsEaten;
|
||||
save.totalPlays = menu.play.totalPlays;
|
||||
save.highScore = menu.play.highScore;
|
||||
save.bestCombo = menu.play.bestCombo;
|
||||
save.gradeCounts = menu.play.gradeCounts;
|
||||
save.totalPlays = menu.arcade.skillCheck.totalPlays;
|
||||
save.highScore = menu.arcade.skillCheck.highScore;
|
||||
save.bestCombo = menu.arcade.skillCheck.bestCombo;
|
||||
save.gradeCounts = menu.arcade.skillCheck.gradeCounts;
|
||||
save.isPostgame = isPostgame;
|
||||
save.isAlternateSpritesheet = character.spritesheetType == entity::Character::ALTERNATE;
|
||||
|
||||
@@ -284,7 +372,7 @@ namespace game::state
|
||||
}
|
||||
|
||||
for (auto& item : itemManager.items)
|
||||
save.items.emplace_back(item.schemaID, item.chewCount, item.position, item.velocity,
|
||||
save.items.emplace_back(item.schemaID, item.durability, item.position, item.velocity,
|
||||
*item.overrides[item.rotationOverrideID].frame.rotation);
|
||||
|
||||
save.isValid = true;
|
||||
@@ -292,6 +380,6 @@ namespace game::state
|
||||
resources.character_save_set(characterIndex, save);
|
||||
save.serialize(character.data.save_path_get());
|
||||
|
||||
toasts.push("Saving...");
|
||||
toasts.push(character.data.strings.get(Strings::ToastSaving));
|
||||
}
|
||||
};
|
||||
@@ -2,19 +2,19 @@
|
||||
|
||||
#include "../resources.hpp"
|
||||
|
||||
#include "main/area_manager.hpp"
|
||||
#include "main/character_manager.hpp"
|
||||
#include "main/info.hpp"
|
||||
#include "main/item_manager.hpp"
|
||||
#include "main/menu.hpp"
|
||||
#include "main/text.hpp"
|
||||
#include "main/toasts.hpp"
|
||||
#include "main/tools.hpp"
|
||||
#include "main/world.hpp"
|
||||
#include "play/area_manager.hpp"
|
||||
#include "play/character_manager.hpp"
|
||||
#include "play/info.hpp"
|
||||
#include "play/item_manager.hpp"
|
||||
#include "play/menu.hpp"
|
||||
#include "play/text.hpp"
|
||||
#include "play/toasts.hpp"
|
||||
#include "play/tools.hpp"
|
||||
#include "play/world.hpp"
|
||||
|
||||
namespace game::state
|
||||
{
|
||||
class Main
|
||||
class Play
|
||||
{
|
||||
public:
|
||||
static constexpr auto AUTOSAVE_TIME = 30.0f;
|
||||
@@ -28,20 +28,22 @@ namespace game::state
|
||||
entity::Character character;
|
||||
entity::Cursor cursor;
|
||||
|
||||
main::Info info;
|
||||
main::Menu menu;
|
||||
main::Tools tools;
|
||||
main::Text text;
|
||||
main::World world;
|
||||
main::Toasts toasts;
|
||||
main::ItemManager itemManager{};
|
||||
main::CharacterManager characterManager{};
|
||||
main::AreaManager areaManager{};
|
||||
play::Info info;
|
||||
play::Menu menu;
|
||||
play::Tools tools;
|
||||
play::Text text;
|
||||
play::World world;
|
||||
play::Toasts toasts;
|
||||
play::ItemManager itemManager{};
|
||||
play::CharacterManager characterManager{};
|
||||
play::AreaManager areaManager{};
|
||||
|
||||
int characterIndex{};
|
||||
int areaIndex{};
|
||||
|
||||
float autosaveTime{};
|
||||
int cheatCodeIndex{};
|
||||
double cheatCodeStartTime{};
|
||||
|
||||
bool isWindows{true};
|
||||
|
||||
@@ -55,15 +57,15 @@ namespace game::state
|
||||
|
||||
bool isPostgame{};
|
||||
|
||||
Canvas worldCanvas{main::World::SIZE};
|
||||
Canvas worldCanvas{play::World::SIZE};
|
||||
|
||||
Main() = default;
|
||||
Play() = default;
|
||||
void set(Resources&, int characterIndex, Game = CONTINUE);
|
||||
void exit(Resources& resources);
|
||||
void update(Resources&);
|
||||
void tick(Resources&);
|
||||
void render(Resources&, Canvas&);
|
||||
void save(Resources&);
|
||||
main::World::Focus focus_get();
|
||||
play::World::Focus focus_get();
|
||||
};
|
||||
};
|
||||
76
src/state/play/arcade.cpp
Normal file
76
src/state/play/arcade.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "arcade.hpp"
|
||||
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::resource::xml;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
Arcade::Arcade(entity::Character& character) : skillCheck(character) {}
|
||||
|
||||
void Arcade::tick() { skillCheck.tick(); }
|
||||
|
||||
void Arcade::update(Resources& resources, entity::Character& character, Inventory& inventory, Text& text)
|
||||
{
|
||||
auto available = ImGui::GetContentRegionAvail();
|
||||
auto& strings = character.data.strings;
|
||||
|
||||
if (view == SKILL_CHECK)
|
||||
{
|
||||
if (skillCheck.update(resources, character, inventory, text)) view = MENU;
|
||||
return;
|
||||
}
|
||||
|
||||
auto buttonHeight = ImGui::GetFrameHeightWithSpacing();
|
||||
auto childSize = ImVec2(available.x, std::max(0.0f, available.y - buttonHeight));
|
||||
|
||||
if (ImGui::BeginChild("##Arcade Child", childSize))
|
||||
{
|
||||
if (view == MENU)
|
||||
{
|
||||
auto buttonWidth = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
|
||||
ImGui::TextUnformatted(strings.get(Strings::ArcadeSkillCheckName).c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::TextWrapped("%s", strings.get(Strings::ArcadeSkillCheckDescription).c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadePlayButton).c_str(), ImVec2(buttonWidth, 0))))
|
||||
view = SKILL_CHECK;
|
||||
ImGui::SameLine();
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeStatsButton).c_str(), ImVec2(buttonWidth, 0))))
|
||||
view = SKILL_CHECK_STATS;
|
||||
}
|
||||
else if (view == SKILL_CHECK_STATS)
|
||||
{
|
||||
auto& schema = character.data.skillCheckSchema;
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
|
||||
ImGui::TextUnformatted(strings.get(Strings::ArcadeSkillCheckName).c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text(strings.get(Strings::ArcadeBestFormat).c_str(), skillCheck.highScore, skillCheck.bestCombo);
|
||||
ImGui::Text(strings.get(Strings::ArcadeTotalSkillChecksFormat).c_str(), skillCheck.totalPlays);
|
||||
|
||||
for (int i = 0; i < (int)schema.grades.size(); i++)
|
||||
{
|
||||
auto& grade = schema.grades[i];
|
||||
ImGui::Text("%s: %i", grade.namePlural.c_str(), skillCheck.gradeCounts[i]);
|
||||
}
|
||||
|
||||
ImGui::Text(strings.get(Strings::ArcadeAccuracyFormat).c_str(), skillCheck.accuracy_score_get(character));
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
if (view == SKILL_CHECK_STATS)
|
||||
{
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeBackButton).c_str()))) view = MENU;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/state/play/arcade.hpp
Normal file
26
src/state/play/arcade.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "arcade/skill_check.hpp"
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
class Arcade
|
||||
{
|
||||
public:
|
||||
enum View
|
||||
{
|
||||
MENU,
|
||||
SKILL_CHECK,
|
||||
SKILL_CHECK_STATS
|
||||
};
|
||||
|
||||
SkillCheck skillCheck{};
|
||||
View view{MENU};
|
||||
|
||||
Arcade() = default;
|
||||
Arcade(entity::Character&);
|
||||
|
||||
void tick();
|
||||
void update(Resources&, entity::Character&, Inventory&, Text&);
|
||||
};
|
||||
}
|
||||
@@ -1,27 +1,30 @@
|
||||
#include "play.hpp"
|
||||
#include "skill_check.hpp"
|
||||
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
#include "../../util/math.hpp"
|
||||
#include "../../../util/imgui.hpp"
|
||||
#include "../../../util/imgui/widget.hpp"
|
||||
#include "../../../util/math.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <format>
|
||||
#include <ranges>
|
||||
|
||||
using namespace game::util;
|
||||
using namespace game::entity;
|
||||
using namespace game::resource;
|
||||
using namespace game::resource::xml;
|
||||
using namespace glm;
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
float Play::accuracy_score_get(entity::Character& character)
|
||||
float SkillCheck::accuracy_score_get(entity::Character& character)
|
||||
{
|
||||
if (totalPlays == 0) return 0.0f;
|
||||
|
||||
auto& schema = character.data.playSchema;
|
||||
auto& schema = character.data.skillCheckSchema;
|
||||
|
||||
float combinedWeight{};
|
||||
|
||||
@@ -34,9 +37,9 @@ namespace game::state::main
|
||||
return glm::clamp(0.0f, math::to_percent(combinedWeight / totalPlays), 100.0f);
|
||||
}
|
||||
|
||||
Play::Challenge Play::challenge_generate(entity::Character& character)
|
||||
SkillCheck::Challenge SkillCheck::challenge_generate(entity::Character& character)
|
||||
{
|
||||
auto& schema = character.data.playSchema;
|
||||
auto& schema = character.data.skillCheckSchema;
|
||||
|
||||
Challenge newChallenge;
|
||||
|
||||
@@ -61,46 +64,52 @@ namespace game::state::main
|
||||
return newChallenge;
|
||||
}
|
||||
|
||||
Play::Play(entity::Character& character) { challenge = challenge_generate(character); }
|
||||
SkillCheck::SkillCheck(entity::Character& character) { challenge = challenge_generate(character); }
|
||||
|
||||
void Play::tick()
|
||||
void SkillCheck::tick()
|
||||
{
|
||||
for (auto& [i, actor] : itemActors)
|
||||
actor.tick();
|
||||
}
|
||||
|
||||
void Play::update(Resources& resources, entity::Character& character, Inventory& inventory, Text& text)
|
||||
bool SkillCheck::update(Resources& resources, entity::Character& character, Inventory& inventory, Text& text)
|
||||
{
|
||||
static constexpr auto BG_COLOR_MULTIPLIER = 0.5f;
|
||||
static constexpr ImVec4 LINE_COLOR = ImVec4(1, 1, 1, 1);
|
||||
static constexpr ImVec4 PERFECT_COLOR = ImVec4(1, 1, 1, 0.50);
|
||||
static constexpr auto LINE_HEIGHT = 2.0f;
|
||||
static constexpr auto LINE_HEIGHT = 5.0f;
|
||||
static constexpr auto LINE_WIDTH_BONUS = 10.0f;
|
||||
static constexpr auto TOAST_MESSAGE_SPEED = 1.0f;
|
||||
static constexpr auto ITEM_FALL_GRAVITY = 2400.0f;
|
||||
|
||||
auto& dialogue = character.data.dialogue;
|
||||
auto& schema = character.data.playSchema;
|
||||
auto& schema = character.data.skillCheckSchema;
|
||||
auto& itemSchema = character.data.itemSchema;
|
||||
auto& strings = character.data.strings;
|
||||
auto& style = ImGui::GetStyle();
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
auto position = ImGui::GetCursorScreenPos();
|
||||
auto size = ImGui::GetContentRegionAvail();
|
||||
auto spacing = ImGui::GetTextLineHeightWithSpacing();
|
||||
auto& io = ImGui::GetIO();
|
||||
auto menuButtonHeight = ImGui::GetFrameHeightWithSpacing();
|
||||
size.y = std::max(0.0f, size.y - menuButtonHeight);
|
||||
|
||||
auto cursorPos = ImGui::GetCursorPos();
|
||||
|
||||
ImGui::Text("Score: %i pts (%ix)", score, combo);
|
||||
auto bestString = std::format("Best: {} pts({}x)", highScore, bestCombo);
|
||||
ImGui::Text(strings.get(Strings::SkillCheckScoreFormat).c_str(), score, combo);
|
||||
std::array<char, 128> bestBuffer{};
|
||||
std::snprintf(bestBuffer.data(), bestBuffer.size(), strings.get(Strings::SkillCheckBestFormat).c_str(), highScore,
|
||||
bestCombo);
|
||||
auto bestString = std::string(bestBuffer.data());
|
||||
ImGui::SetCursorPos(ImVec2(size.x - ImGui::CalcTextSize(bestString.c_str()).x, cursorPos.y));
|
||||
|
||||
ImGui::Text("Best: %i pts (%ix)", highScore, bestCombo);
|
||||
ImGui::Text(strings.get(Strings::SkillCheckBestFormat).c_str(), highScore, bestCombo);
|
||||
|
||||
if (score == 0 && isActive)
|
||||
{
|
||||
ImGui::SetCursorPos(ImVec2(style.WindowPadding.x, size.y - style.WindowPadding.y));
|
||||
ImGui::TextWrapped("Match the line to the colored areas with Space/click! Better performance, better rewards!");
|
||||
ImGui::TextWrapped("%s", strings.get(Strings::SkillCheckInstructions).c_str());
|
||||
}
|
||||
|
||||
auto barMin = ImVec2(position.x + (size.x * 0.5f) - (spacing * 0.5f), position.y + (spacing * 2.0f));
|
||||
@@ -193,8 +202,11 @@ namespace game::state::main
|
||||
score--;
|
||||
schema.sounds.scoreLoss.play();
|
||||
auto toastMessagePosition =
|
||||
ImVec2(barMin.x - ImGui::CalcTextSize("-1").x - ImGui::GetTextLineHeightWithSpacing(), lineMin.y);
|
||||
toasts.emplace_back("-1", toastMessagePosition, schema.endTimerMax, schema.endTimerMax);
|
||||
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::SkillCheckScoreLoss).c_str()).x -
|
||||
ImGui::GetTextLineHeightWithSpacing(),
|
||||
lineMin.y);
|
||||
toasts.emplace_back(strings.get(Strings::SkillCheckScoreLoss), toastMessagePosition, schema.endTimerMax,
|
||||
schema.endTimerMax);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +214,7 @@ namespace game::state::main
|
||||
auto barButtonSize = ImVec2(barMax.x - barMin.x, barMax.y - barMin.y);
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Space) ||
|
||||
WIDGET_FX(ImGui::InvisibleButton("##PlayBar", barButtonSize, ImGuiButtonFlags_PressedOnClick)))
|
||||
WIDGET_FX(ImGui::InvisibleButton("##SkillCheckBar", barButtonSize, ImGuiButtonFlags_PressedOnClick)))
|
||||
{
|
||||
int gradeID{};
|
||||
|
||||
@@ -234,7 +246,7 @@ namespace game::state::main
|
||||
schema.sounds.rewardScore.play();
|
||||
isRewardScoreAchieved = true;
|
||||
|
||||
for (auto& itemID : itemSchema.rewardItemPool)
|
||||
for (auto& itemID : itemSchema.skillCheckRewardItemPool)
|
||||
{
|
||||
inventory.values[itemID]++;
|
||||
if (!itemActors.contains(itemID))
|
||||
@@ -258,10 +270,10 @@ namespace game::state::main
|
||||
}
|
||||
|
||||
auto toastMessagePosition =
|
||||
ImVec2(barMin.x - ImGui::CalcTextSize("Fantastic score!\nCongratulations!").x -
|
||||
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::SkillCheckRewardToast).c_str()).x -
|
||||
ImGui::GetTextLineHeightWithSpacing(),
|
||||
lineMin.y + (ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y));
|
||||
toasts.emplace_back("Fantastic score! Congratulations!", toastMessagePosition, schema.endTimerMax,
|
||||
toasts.emplace_back(strings.get(Strings::SkillCheckRewardToast), toastMessagePosition, schema.endTimerMax,
|
||||
schema.endTimerMax);
|
||||
}
|
||||
|
||||
@@ -274,9 +286,11 @@ namespace game::state::main
|
||||
isHighScoreAchievedThisRun = true;
|
||||
schema.sounds.highScore.play();
|
||||
auto toastMessagePosition =
|
||||
ImVec2(barMin.x - ImGui::CalcTextSize("High Score!").x - ImGui::GetTextLineHeightWithSpacing(),
|
||||
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::SkillCheckHighScoreToast).c_str()).x -
|
||||
ImGui::GetTextLineHeightWithSpacing(),
|
||||
lineMin.y + ImGui::GetTextLineHeightWithSpacing());
|
||||
toasts.emplace_back("High Score!", toastMessagePosition, schema.endTimerMax, schema.endTimerMax);
|
||||
toasts.emplace_back(strings.get(Strings::SkillCheckHighScoreToast), toastMessagePosition,
|
||||
schema.endTimerMax, schema.endTimerMax);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,12 +349,12 @@ namespace game::state::main
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
score = 0;
|
||||
if (isHighScoreAchieved) schema.sounds.highScoreLoss.play();
|
||||
combo = 0;
|
||||
if (isHighScoreAchievedThisRun) schema.sounds.highScoreLoss.play();
|
||||
if (highScore > 0) isHighScoreAchieved = true;
|
||||
isRewardScoreAchieved = false;
|
||||
isHighScoreAchievedThisRun = true;
|
||||
isHighScoreAchievedThisRun = false;
|
||||
highScoreStart = highScore;
|
||||
isGameOver = true;
|
||||
}
|
||||
@@ -351,7 +365,9 @@ namespace game::state::main
|
||||
|
||||
queuedChallenge = challenge_generate(character);
|
||||
|
||||
auto string = grade.isFailure ? grade.name : std::format("{} (+{})", grade.name, grade.value);
|
||||
auto string = grade.isFailure ? grade.name
|
||||
: std::vformat(strings.get(Strings::SkillCheckGradeSuccessTemplate),
|
||||
std::make_format_args(grade.name, grade.value));
|
||||
auto toastMessagePosition =
|
||||
ImVec2(barMin.x - ImGui::CalcTextSize(string.c_str()).x - ImGui::GetTextLineHeightWithSpacing(), lineMin.y);
|
||||
toasts.emplace_back(string, toastMessagePosition, endTimerMax, endTimerMax);
|
||||
@@ -429,5 +445,8 @@ namespace game::state::main
|
||||
if (fallingItem.position.y > position.y + size.y) items.erase(items.begin() + i--);
|
||||
}
|
||||
ImGui::PopClipRect();
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(position.x, position.y + size.y + ImGui::GetStyle().ItemSpacing.y));
|
||||
return WIDGET_FX(ImGui::Button(strings.get(Strings::SkillCheckMenuButton).c_str()));
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../canvas.hpp"
|
||||
#include "../../entity/actor.hpp"
|
||||
#include "../../entity/character.hpp"
|
||||
#include "../../resources.hpp"
|
||||
#include "../../../render/canvas.hpp"
|
||||
#include "../../../entity/actor.hpp"
|
||||
#include "../../../entity/character.hpp"
|
||||
#include "../../../resources.hpp"
|
||||
|
||||
#include "inventory.hpp"
|
||||
#include "text.hpp"
|
||||
#include "../inventory.hpp"
|
||||
#include "../text.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
class Play
|
||||
class SkillCheck
|
||||
{
|
||||
|
||||
public:
|
||||
@@ -77,11 +77,11 @@ namespace game::state::main
|
||||
std::unordered_map<int, glm::vec4> itemRects{};
|
||||
std::unordered_map<int, Canvas> itemCanvases{};
|
||||
|
||||
Play() = default;
|
||||
Play(entity::Character&);
|
||||
SkillCheck() = default;
|
||||
SkillCheck(entity::Character&);
|
||||
Challenge challenge_generate(entity::Character&);
|
||||
void tick();
|
||||
void update(Resources&, entity::Character&, Inventory&, Text&);
|
||||
bool update(Resources&, entity::Character&, Inventory&, Text&);
|
||||
float accuracy_score_get(entity::Character&);
|
||||
};
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
using namespace game::resource;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
int AreaManager::get(entity::Character& character)
|
||||
{
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
class AreaManager
|
||||
{
|
||||
@@ -8,7 +8,7 @@
|
||||
using namespace game::resource::xml;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
void CharacterManager::update(entity::Character& character, entity::Cursor& cursor, Text& text, Canvas& canvas)
|
||||
{
|
||||
@@ -36,12 +36,19 @@ namespace game::state::main
|
||||
|
||||
isInteractingPrevious = isInteracting;
|
||||
isHoveringPrevious = isHovering;
|
||||
isHoldInteractingPrevious = isHoldInteracting;
|
||||
isHovering = false;
|
||||
if (!isInteracting) isHoldInteracting = false;
|
||||
|
||||
if (isJustStoppedInteracting)
|
||||
if (isJustStoppedHoldInteracting)
|
||||
{
|
||||
cursor.queue_play({cursor.defaultAnimation});
|
||||
if (character.queuedPlay.empty()) character.queue_idle_animation();
|
||||
isJustStoppedHoldInteracting = false;
|
||||
}
|
||||
else if (isJustStoppedInteracting)
|
||||
{
|
||||
cursor.queue_play({cursor.defaultAnimation});
|
||||
if (cursor.mode == RUB) character.queue_idle_animation();
|
||||
isJustStoppedInteracting = false;
|
||||
}
|
||||
|
||||
@@ -58,7 +65,7 @@ namespace game::state::main
|
||||
auto rect = character.null_frame_rect(interactArea.nullID);
|
||||
|
||||
if (cursor.state == entity::Cursor::DEFAULT && math::is_point_in_rectf(rect, cursorWorldPosition) &&
|
||||
!isImguiCaptureMouse && interactArea.type == cursor.mode)
|
||||
!isImguiCaptureMouse && interactArea.typeID == cursor.interactTypeID)
|
||||
{
|
||||
cursor.state = entity::Cursor::HOVER;
|
||||
cursor.queue_play({interactArea.animationCursorHover});
|
||||
@@ -68,8 +75,8 @@ namespace game::state::main
|
||||
if (isMouseLeftClick)
|
||||
{
|
||||
isInteracting = true;
|
||||
isHoldInteracting = interactArea.isHold;
|
||||
interactArea.sound.play();
|
||||
lastInteractType = cursor.mode;
|
||||
|
||||
if (interactArea.digestionBonusClick > 0 && character.calories > 0 && !character.isDigesting)
|
||||
character.digestionProgress += interactArea.digestionBonusClick;
|
||||
@@ -82,14 +89,18 @@ namespace game::state::main
|
||||
interact_area_override_tick, interactArea.scaleEffectCycles));
|
||||
}
|
||||
|
||||
if (interactArea.pool.is_valid() && text.is_interruptible())
|
||||
text.set(dialogue.get(interactArea.pool), character);
|
||||
if (text.is_interruptible())
|
||||
{
|
||||
auto& pool = character.is_over_capacity() && interactArea.poolFull.is_valid() ? interactArea.poolFull
|
||||
: interactArea.pool;
|
||||
if (pool.is_valid())
|
||||
text.set(dialogue.get(pool), character);
|
||||
}
|
||||
}
|
||||
|
||||
if (isInteracting)
|
||||
{
|
||||
cursor.state = entity::Cursor::ACTION;
|
||||
character.queue_interact_area_animation(interactArea);
|
||||
cursor.queue_play({interactArea.animationCursorActive});
|
||||
|
||||
if (interactArea.digestionBonusRub > 0 && character.calories > 0 && !character.isDigesting)
|
||||
@@ -105,11 +116,13 @@ namespace game::state::main
|
||||
isImguiCaptureMouse)
|
||||
{
|
||||
isInteracting = false;
|
||||
isHoldInteracting = false;
|
||||
interactAreaID = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (isInteracting != isInteractingPrevious && !isInteracting) isJustStoppedInteracting = true;
|
||||
if (isHoldInteracting != isHoldInteractingPrevious && !isHoldInteracting) isJustStoppedHoldInteracting = true;
|
||||
if (isHovering != isHoveringPrevious && !isHovering) isJustStoppedHovering = true;
|
||||
|
||||
cursorWorldPositionPrevious = cursorWorldPosition;
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "../../entity/cursor.hpp"
|
||||
#include "text.hpp"
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
class CharacterManager
|
||||
{
|
||||
@@ -15,8 +15,10 @@ namespace game::state::main
|
||||
bool isHoveringPrevious{};
|
||||
bool isJustStoppedInteracting{};
|
||||
bool isJustStoppedHovering{};
|
||||
bool isHoldInteracting{};
|
||||
bool isHoldInteractingPrevious{};
|
||||
bool isJustStoppedHoldInteracting{};
|
||||
int interactAreaID{-1};
|
||||
InteractType lastInteractType{(InteractType)-1};
|
||||
|
||||
glm::vec2 cursorWorldPositionPrevious{};
|
||||
std::string queuedAnimation{};
|
||||
75
src/state/play/cheats.cpp
Normal file
75
src/state/play/cheats.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "cheats.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
#include "../../util/imgui/input_int_ex.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::util;
|
||||
using namespace game::resource::xml;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
void Cheats::update(Resources&, entity::Character& character, Inventory& inventory)
|
||||
{
|
||||
auto& strings = character.data.strings;
|
||||
|
||||
if (ImGui::BeginChild("##Cheats"))
|
||||
{
|
||||
auto stage = character.stage + 1;
|
||||
|
||||
auto weight_update = [&]() { character.queue_idle_animation(); };
|
||||
|
||||
WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsCalories).c_str(), &character.calories, 0,
|
||||
character.max_capacity(), "%0.0f kcal"));
|
||||
WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsCapacity).c_str(), &character.capacity,
|
||||
character.data.capacityMin, character.data.capacityMax, "%0.0f kcal"));
|
||||
|
||||
if (WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsWeight).c_str(), &character.weight,
|
||||
character.data.weight, character.data.weightMax,
|
||||
strings.get(Strings::CheatsWeightFormat).c_str())))
|
||||
weight_update();
|
||||
|
||||
if (WIDGET_FX(ImGui::SliderInt(strings.get(Strings::CheatsStage).c_str(), &stage, 1,
|
||||
(int)character.data.stages.size() + 1)))
|
||||
{
|
||||
character.stage = glm::clamp(0, stage - 1, (int)character.data.stages.size());
|
||||
character.weight =
|
||||
character.stage == 0 ? character.data.weight : character.data.stages.at(character.stage - 1).threshold;
|
||||
weight_update();
|
||||
}
|
||||
|
||||
WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsDigestionRate).c_str(), &character.digestionRate,
|
||||
character.data.digestionRateMin, character.data.digestionRateMax,
|
||||
strings.get(Strings::CheatsDigestionRateFormat).c_str()));
|
||||
WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsEatSpeed).c_str(), &character.eatSpeed,
|
||||
character.data.eatSpeedMin, character.data.eatSpeedMax,
|
||||
strings.get(Strings::CheatsEatSpeedFormat).c_str()));
|
||||
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::CheatsDigestButton).c_str())))
|
||||
character.digestionProgress = entity::Character::DIGESTION_MAX;
|
||||
|
||||
ImGui::SeparatorText(strings.get(Strings::CheatsInventory).c_str());
|
||||
|
||||
if (ImGui::BeginChild("##Inventory", ImGui::GetContentRegionAvail(), ImGuiChildFlags_Borders))
|
||||
{
|
||||
auto& schema = character.data.itemSchema;
|
||||
|
||||
ImGui::PushItemWidth(100);
|
||||
for (int i = 0; i < (int)schema.items.size(); i++)
|
||||
{
|
||||
auto& item = schema.items[i];
|
||||
ImGui::PushID(i);
|
||||
WIDGET_FX(input_int_range(item.name.c_str(), &inventory.values[i], 0, schema.quantityMax, 1, 5));
|
||||
ImGui::SetItemTooltip("%s", item.name.c_str());
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,11 @@
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
class Cheats
|
||||
{
|
||||
public:
|
||||
|
||||
void update(Resources&, entity::Character&, Inventory&, Text&);
|
||||
void update(Resources&, entity::Character&, Inventory&);
|
||||
};
|
||||
}
|
||||
74
src/state/play/debug.cpp
Normal file
74
src/state/play/debug.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "debug.hpp"
|
||||
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::resource::xml;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
void Debug::update(entity::Character& character, entity::Cursor& cursor, ItemManager& itemManager, Canvas& canvas,
|
||||
Text& text)
|
||||
{
|
||||
auto& strings = character.data.strings;
|
||||
auto cursorPosition = canvas.screen_position_convert(cursor.position);
|
||||
|
||||
ImGui::Text(strings.get(Strings::DebugCursorScreenFormat).c_str(), cursor.position.x, cursor.position.y);
|
||||
ImGui::Text(strings.get(Strings::DebugCursorWorldFormat).c_str(), cursorPosition.x, cursorPosition.y);
|
||||
|
||||
ImGui::SeparatorText(strings.get(Strings::DebugAnimations).c_str());
|
||||
ImGui::Text(strings.get(Strings::DebugNowPlayingFormat).c_str(), character.animationMapReverse.at(character.animationIndex).c_str());
|
||||
|
||||
auto childSize = ImVec2(0, ImGui::GetContentRegionAvail().y / 3);
|
||||
|
||||
if (ImGui::BeginChild("##Animations", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
for (int i = 0; i < (int)character.animations.size(); i++)
|
||||
{
|
||||
auto& animation = character.animations[i];
|
||||
ImGui::PushID(i);
|
||||
if (WIDGET_FX(ImGui::Selectable(animation.name.c_str())))
|
||||
character.play(animation.name.c_str(), entity::Actor::PLAY_FORCE);
|
||||
ImGui::SetItemTooltip("%s", animation.name.c_str());
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SeparatorText(strings.get(Strings::DebugDialogue).c_str());
|
||||
|
||||
if (ImGui::BeginChild("##Dialogue", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
for (int i = 0; i < (int)character.data.dialogue.entries.size(); i++)
|
||||
{
|
||||
auto& entry = character.data.dialogue.entries[i];
|
||||
ImGui::PushID(i);
|
||||
if (WIDGET_FX(ImGui::Selectable(entry.name.c_str()))) text.set(&entry, character);
|
||||
ImGui::SetItemTooltip("%s", entry.name.c_str());
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
WIDGET_FX(ImGui::Checkbox(strings.get(Strings::DebugShowNulls).c_str(), &character.isShowNulls));
|
||||
WIDGET_FX(ImGui::Checkbox(strings.get(Strings::DebugShowWorldBounds).c_str(), &isBoundsDisplay));
|
||||
|
||||
if (!itemManager.items.empty())
|
||||
{
|
||||
ImGui::SeparatorText(strings.get(Strings::DebugItem).c_str());
|
||||
|
||||
for (int i = 0; i < (int)itemManager.items.size(); i++)
|
||||
{
|
||||
auto& item = itemManager.items[i];
|
||||
if (itemManager.heldItemIndex == i) ImGui::TextUnformatted(strings.get(Strings::DebugHeld).c_str());
|
||||
ImGui::Text(strings.get(Strings::DebugItemTypeFormat).c_str(), item.schemaID);
|
||||
ImGui::Text(strings.get(Strings::DebugItemPositionFormat).c_str(), item.position.x, item.position.y);
|
||||
ImGui::Text(strings.get(Strings::DebugItemVelocityFormat).c_str(), item.velocity.x, item.velocity.y);
|
||||
ImGui::Text(strings.get(Strings::DebugItemDurabilityFormat).c_str(), item.durability);
|
||||
ImGui::Separator();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,17 @@
|
||||
#include "../../entity/cursor.hpp"
|
||||
|
||||
#include "item_manager.hpp"
|
||||
#include "text.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
class Debug
|
||||
{
|
||||
public:
|
||||
bool isBoundsDisplay{};
|
||||
|
||||
void update(entity::Character&, entity::Cursor& cursor, ItemManager&, Canvas& canvas);
|
||||
void update(entity::Character&, entity::Cursor&, ItemManager&, Canvas&, Text&);
|
||||
};
|
||||
}
|
||||
@@ -9,15 +9,17 @@
|
||||
#include <format>
|
||||
|
||||
using namespace game::resource;
|
||||
using namespace game::resource::xml;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
void Info::update(Resources& resources, entity::Character& character)
|
||||
{
|
||||
static constexpr auto WIDTH_MULTIPLIER = 0.30f;
|
||||
static constexpr auto HEIGHT_MULTIPLIER = 4.0f;
|
||||
|
||||
auto& strings = character.data.strings;
|
||||
auto& style = ImGui::GetStyle();
|
||||
auto windowSize = imgui::to_ivec2(ImGui::GetMainViewport()->Size);
|
||||
|
||||
@@ -45,26 +47,29 @@ namespace game::state::main
|
||||
auto unitString = (system == measurement::IMPERIAL ? "lbs" : "kg");
|
||||
|
||||
auto weightString = util::string::format_commas(weight, 2) + " " + unitString;
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::ABOVE_AVERAGE);
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_1);
|
||||
ImGui::TextUnformatted(weightString.c_str());
|
||||
ImGui::SetItemTooltip("%s", weightString.c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
auto stageProgress = character.stage_progress_get();
|
||||
ImGui::ProgressBar(stageProgress, ImVec2(ImGui::GetContentRegionAvail().x, 0),
|
||||
stage >= stageMax ? "MAX" : "To Next Stage");
|
||||
strings.get(stage >= stageMax ? Strings::InfoProgressMax
|
||||
: Strings::InfoProgressToNextStage)
|
||||
.c_str());
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
ImGui::Text("Stage: %i/%i (%0.1f%%)", stage + 1, stageMax + 1, math::to_percent(stageProgress));
|
||||
ImGui::Text(strings.get(Strings::InfoStageProgressFormat).c_str(), stage + 1, stageMax + 1,
|
||||
math::to_percent(stageProgress));
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, imgui::to_imvec4(color::GRAY));
|
||||
if (stage >= stageMax)
|
||||
ImGui::Text("Maxed out!");
|
||||
ImGui::TextUnformatted(strings.get(Strings::InfoMaxedOut).c_str());
|
||||
else
|
||||
{
|
||||
ImGui::Text("Start: %0.2f %s", stageWeight, unitString);
|
||||
ImGui::Text("Current: %0.2f %s", weight, unitString);
|
||||
ImGui::Text("Next: %0.2f %s", stageNextWeight, unitString);
|
||||
ImGui::Text(strings.get(Strings::InfoStageStartFormat).c_str(), stageWeight, unitString);
|
||||
ImGui::Text(strings.get(Strings::InfoStageCurrentFormat).c_str(), weight, unitString);
|
||||
ImGui::Text(strings.get(Strings::InfoStageNextFormat).c_str(), stageNextWeight, unitString);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::EndTooltip();
|
||||
@@ -82,7 +87,7 @@ namespace game::state::main
|
||||
auto overstuffedPercent = std::max(0.0f, (calories - capacity) / (character.max_capacity() - capacity));
|
||||
auto caloriesColor = ImVec4(1.0f, 1.0f - overstuffedPercent, 1.0f - overstuffedPercent, 1.0f);
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::ABOVE_AVERAGE);
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_1);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, caloriesColor);
|
||||
auto caloriesString = std::format("{:.0f} kcal / {:.0f} kcal", calories,
|
||||
character.is_over_capacity() ? character.max_capacity() : character.capacity);
|
||||
@@ -95,14 +100,16 @@ namespace game::state::main
|
||||
? (float)character.digestionTimer / character.data.digestionTimerMax
|
||||
: character.digestionProgress / entity::Character::DIGESTION_MAX;
|
||||
ImGui::ProgressBar(digestionProgress, ImVec2(ImGui::GetContentRegionAvail().x, 0),
|
||||
character.isDigesting ? "Digesting..." : "Digestion");
|
||||
strings.get(character.isDigesting ? Strings::InfoDigesting
|
||||
: Strings::InfoDigestion)
|
||||
.c_str());
|
||||
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
if (character.isDigesting)
|
||||
ImGui::TextUnformatted("Digestion in progress...");
|
||||
ImGui::TextUnformatted(strings.get(Strings::InfoDigestionInProgress).c_str());
|
||||
else if (digestionProgress <= 0.0f)
|
||||
ImGui::TextUnformatted("Give food to start digesting!");
|
||||
ImGui::TextUnformatted(strings.get(Strings::InfoGiveFoodToStartDigesting).c_str());
|
||||
else
|
||||
ImGui::Text("%0.2f%%", math::to_percent(digestionProgress));
|
||||
|
||||
@@ -110,8 +117,8 @@ namespace game::state::main
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
|
||||
|
||||
ImGui::Text("Rate: %0.2f%% / sec", character.digestion_rate_get());
|
||||
ImGui::Text("Eating Speed: %0.2fx", character.eatSpeed);
|
||||
ImGui::Text(strings.get(Strings::InfoDigestionRateFormat).c_str(), character.digestion_rate_get());
|
||||
ImGui::Text(strings.get(Strings::InfoEatingSpeedFormat).c_str(), character.eatSpeed);
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
class Info
|
||||
{
|
||||
57
src/state/play/interact.cpp
Normal file
57
src/state/play/interact.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include "interact.hpp"
|
||||
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
#include "../../util/measurement.hpp"
|
||||
|
||||
using namespace game::resource;
|
||||
using namespace game::resource::xml;
|
||||
using namespace game::util;
|
||||
using namespace game::util::imgui;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
void Interact::update(Resources& resources, Text& text, entity::Character& character)
|
||||
{
|
||||
auto& dialogue = character.data.dialogue;
|
||||
auto& strings = character.data.strings;
|
||||
auto size = ImGui::GetContentRegionAvail();
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
|
||||
|
||||
if (dialogue.random.is_valid())
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InteractChatButton).c_str(), ImVec2(size.x, 0))))
|
||||
text.set(dialogue.get(dialogue.random), character);
|
||||
|
||||
ImGui::PopFont();
|
||||
|
||||
if (dialogue.help.is_valid())
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InteractHelpButton).c_str(), ImVec2(size.x, 0))))
|
||||
text.set(dialogue.get(dialogue.help), character);
|
||||
|
||||
auto stage = glm::clamp(0, character.stage_get(), character.stage_max_get());
|
||||
auto& pool = stage > 0 ? character.data.stages.at(stage - 1).pool : character.data.pool;
|
||||
|
||||
if (pool.is_valid())
|
||||
if (WIDGET_FX(
|
||||
ImGui::Button(strings.get(Strings::InteractFeelingButton).c_str(), ImVec2(size.x, 0))))
|
||||
text.set(dialogue.get(pool), character);
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
|
||||
ImGui::SeparatorText(character.data.name.c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
auto& system = resources.settings.measurementSystem;
|
||||
auto weight = character.weight_get(system);
|
||||
auto weightUnit = system == measurement::IMPERIAL ? "lbs" : "kg";
|
||||
|
||||
ImGui::Text(strings.get(Strings::InteractWeightFormat).c_str(), weight, weightUnit,
|
||||
character.stage_get() + 1);
|
||||
ImGui::Text(strings.get(Strings::InteractCapacityFormat).c_str(), character.capacity,
|
||||
character.max_capacity());
|
||||
ImGui::Text(strings.get(Strings::InteractDigestionRateFormat).c_str(), character.digestion_rate_get());
|
||||
ImGui::Text(strings.get(Strings::InteractEatingSpeedFormat).c_str(), character.eatSpeed);
|
||||
ImGui::Separator();
|
||||
ImGui::Text(strings.get(Strings::InteractTotalCaloriesFormat).c_str(), character.totalCaloriesConsumed);
|
||||
ImGui::Text(strings.get(Strings::InteractTotalFoodItemsFormat).c_str(), character.totalFoodItemsEaten);
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
class Chat
|
||||
class Interact
|
||||
{
|
||||
public:
|
||||
void update(Resources&, Text&, entity::Character&);
|
||||
446
src/state/play/inventory.cpp
Normal file
446
src/state/play/inventory.cpp
Normal file
@@ -0,0 +1,446 @@
|
||||
#include "inventory.hpp"
|
||||
#include "style.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <format>
|
||||
#include <ranges>
|
||||
#include <tuple>
|
||||
|
||||
#include "../../util/color.hpp"
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/imgui/style.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
#include "../../util/math.hpp"
|
||||
|
||||
using namespace game::util;
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::entity;
|
||||
using namespace game::resource;
|
||||
using namespace glm;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
using Strings = resource::xml::Strings;
|
||||
|
||||
void Inventory::tick()
|
||||
{
|
||||
for (auto& [i, actor] : actors)
|
||||
actor.tick();
|
||||
}
|
||||
|
||||
void Inventory::update(Resources& resources, ItemManager& itemManager, entity::Character& character)
|
||||
{
|
||||
static constexpr auto INFO_CHILD_HEIGHT_MULTIPLIER = 1.0f / 3.0f;
|
||||
|
||||
auto& schema = character.data.itemSchema;
|
||||
auto& strings = character.data.strings;
|
||||
|
||||
auto quantity_get = [&](int itemID) -> int&
|
||||
{
|
||||
auto& quantity = values[itemID];
|
||||
quantity = glm::clamp(0, quantity, schema.quantityMax);
|
||||
return quantity;
|
||||
};
|
||||
|
||||
auto is_possible_to_upgrade_get = [&](const resource::xml::Item::Entry& item)
|
||||
{
|
||||
return item.upgradeID.has_value() && item.upgradeCount.has_value() &&
|
||||
schema.idToStringMap.contains(*item.upgradeID);
|
||||
};
|
||||
|
||||
auto is_able_to_upgrade_get = [&](const resource::xml::Item::Entry& item, int quantity)
|
||||
{ return is_possible_to_upgrade_get(item) && quantity >= *item.upgradeCount; };
|
||||
|
||||
auto item_summary_draw = [&](const resource::xml::Item::Entry& item, int quantity)
|
||||
{
|
||||
auto& category = schema.categories[item.categoryID];
|
||||
auto& rarity = schema.rarities[item.rarityID];
|
||||
auto durability = item.durability.value_or(schema.durability);
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
ImGui::TextWrapped("%s (x%i)", item.name.c_str(), quantity);
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
|
||||
ImGui::TextWrapped("-- %s (%s) --", category.name.c_str(), rarity.name.c_str());
|
||||
if (item.flavorID.has_value())
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryFlavorFormat).c_str(),
|
||||
schema.flavors[*item.flavorID].name.c_str());
|
||||
if (item.calories.has_value())
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryCaloriesFormat).c_str(), *item.calories);
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryDurabilityFormat).c_str(), durability);
|
||||
if (item.capacityBonus.has_value())
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryCapacityBonusFormat).c_str(), *item.capacityBonus);
|
||||
if (item.digestionBonus.has_value())
|
||||
{
|
||||
if (*item.digestionBonus > 0)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryDigestionRateBonusFormat).c_str(),
|
||||
*item.digestionBonus * 60.0f);
|
||||
else if (*item.digestionBonus < 0)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryDigestionRatePenaltyFormat).c_str(),
|
||||
*item.digestionBonus * 60.0f);
|
||||
}
|
||||
if (item.eatSpeedBonus.has_value())
|
||||
{
|
||||
if (*item.eatSpeedBonus > 0)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryEatSpeedBonusFormat).c_str(), *item.eatSpeedBonus);
|
||||
else if (*item.eatSpeedBonus < 0)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryEatSpeedPenaltyFormat).c_str(), *item.eatSpeedBonus);
|
||||
}
|
||||
if (is_possible_to_upgrade_get(item))
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryUpgradePreviewFormat).c_str(), *item.upgradeCount,
|
||||
schema.idToStringMap.at(*item.upgradeID).c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::Separator();
|
||||
};
|
||||
|
||||
auto item_details_draw = [&](const resource::xml::Item::Entry& item, int quantity)
|
||||
{
|
||||
item_summary_draw(item, quantity);
|
||||
|
||||
if (ImGui::BeginChild("##Info Description Child", ImGui::GetContentRegionAvail()))
|
||||
ImGui::TextWrapped("%s", item.description.c_str());
|
||||
ImGui::EndChild();
|
||||
};
|
||||
|
||||
auto item_tooltip_draw = [&](const resource::xml::Item::Entry& item, int quantity)
|
||||
{
|
||||
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f);
|
||||
item_summary_draw(item, quantity);
|
||||
ImGui::TextWrapped("%s", item.description.c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
};
|
||||
|
||||
auto item_unknown_draw = [&]()
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f);
|
||||
ImGui::TextWrapped("%s", strings.get(Strings::InventoryUnknown).c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::PopFont();
|
||||
};
|
||||
|
||||
auto item_use = [&](int itemID)
|
||||
{
|
||||
auto& item = schema.items[itemID];
|
||||
auto& category = schema.categories[item.categoryID];
|
||||
auto& quantity = quantity_get(itemID);
|
||||
|
||||
if (quantity <= 0) return;
|
||||
|
||||
if (category.isEdible)
|
||||
{
|
||||
if (itemManager.items.size() + 1 >= ItemManager::LIMIT)
|
||||
character.data.itemSchema.sounds.dispose.play();
|
||||
else
|
||||
{
|
||||
character.data.itemSchema.sounds.summon.play();
|
||||
itemManager.queuedItemIDs.emplace_back(itemID);
|
||||
quantity--;
|
||||
if (quantity <= 0) selectedItemID = -1;
|
||||
}
|
||||
}
|
||||
else if (item.isToggleSpritesheet)
|
||||
{
|
||||
character.spritesheet_set(character.spritesheetType == Character::NORMAL ? Character::ALTERNATE
|
||||
: Character::NORMAL);
|
||||
character.data.alternateSpritesheet.sound.play();
|
||||
quantity--;
|
||||
}
|
||||
};
|
||||
|
||||
auto item_upgrade = [&](int itemID, bool isAll)
|
||||
{
|
||||
auto& item = schema.items[itemID];
|
||||
auto& quantity = quantity_get(itemID);
|
||||
|
||||
if (!is_possible_to_upgrade_get(item))
|
||||
{
|
||||
schema.sounds.upgradeFail.play();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_able_to_upgrade_get(item, quantity))
|
||||
{
|
||||
schema.sounds.upgradeFail.play();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAll)
|
||||
{
|
||||
while (quantity >= *item.upgradeCount)
|
||||
{
|
||||
values.at(*item.upgradeID)++;
|
||||
quantity -= *item.upgradeCount;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
values.at(*item.upgradeID)++;
|
||||
quantity -= *item.upgradeCount;
|
||||
}
|
||||
|
||||
schema.sounds.upgrade.play();
|
||||
|
||||
if (quantity < *item.upgradeCount && selectedItemID == itemID) selectedItemID = *item.upgradeID;
|
||||
};
|
||||
|
||||
auto item_canvas_get = [&](int itemID, ImVec2 size)
|
||||
{
|
||||
if (!actors.contains(itemID))
|
||||
{
|
||||
actors[itemID] = Actor(schema.anm2s[itemID], {}, Actor::SET);
|
||||
rects[itemID] = actors[itemID].rect();
|
||||
}
|
||||
|
||||
auto& rect = rects[itemID];
|
||||
auto rectSize = vec2(rect.z, rect.w);
|
||||
auto previewScale = (size.x <= 0.0f || size.y <= 0.0f || rectSize.x <= 0.0f || rectSize.y <= 0.0f ||
|
||||
!std::isfinite(rectSize.x) || !std::isfinite(rectSize.y))
|
||||
? 0.0f
|
||||
: std::min(size.x / rectSize.x, size.y / rectSize.y);
|
||||
|
||||
auto previewSize = rectSize * previewScale;
|
||||
auto canvasSize = ivec2(std::max(1.0f, previewSize.x), std::max(1.0f, previewSize.y));
|
||||
if (!canvases.contains(itemID)) canvases.emplace(itemID, Canvas(canvasSize, Canvas::FLIP));
|
||||
|
||||
auto& canvas = canvases[itemID];
|
||||
canvas.zoom = math::to_percent(previewScale);
|
||||
canvas.pan = vec2(rect.x, rect.y);
|
||||
canvas.bind();
|
||||
canvas.size_set(canvasSize);
|
||||
canvas.clear();
|
||||
actors[itemID].render(resources.shaders[shader::TEXTURE], resources.shaders[shader::RECT], canvas);
|
||||
canvas.unbind();
|
||||
|
||||
return std::tuple<Canvas&, glm::vec4&>(canvas, rect);
|
||||
};
|
||||
|
||||
if (!itemManager.returnItemIDs.empty())
|
||||
{
|
||||
for (auto& id : itemManager.returnItemIDs)
|
||||
values[id]++;
|
||||
itemManager.returnItemIDs.clear();
|
||||
}
|
||||
|
||||
if (ImGui::BeginChild("##Inventory Child", ImGui::GetContentRegionAvail(), ImGuiChildFlags_None,
|
||||
ImGuiWindowFlags_NoScrollbar))
|
||||
{
|
||||
auto inventoryCount = count();
|
||||
auto available = ImGui::GetContentRegionAvail();
|
||||
auto isItemSelected = selectedItemID >= 0 && selectedItemID < (int)schema.items.size();
|
||||
auto isInfoVisible = isItemSelected || inventoryCount == 0;
|
||||
auto infoChildHeight =
|
||||
isInfoVisible ? available.y * INFO_CHILD_HEIGHT_MULTIPLIER + ImGui::GetStyle().ItemSpacing.y * 2.0f : 0.0f;
|
||||
auto inventoryChildHeight =
|
||||
isInfoVisible ? available.y - infoChildHeight - ImGui::GetStyle().ItemSpacing.y : available.y;
|
||||
auto childSize = ImVec2(available.x, inventoryChildHeight);
|
||||
auto infoChildSize = ImVec2(available.x, infoChildHeight);
|
||||
|
||||
if (ImGui::BeginChild("##Inventory List Child", childSize))
|
||||
{
|
||||
auto cursorPos = ImGui::GetCursorPos();
|
||||
auto cursorStartX = ImGui::GetCursorPosX();
|
||||
bool isAnyInventoryItemHovered{};
|
||||
|
||||
auto size = ImVec2(SIZE, SIZE);
|
||||
|
||||
for (int i = 0; i < (int)schema.items.size(); i++)
|
||||
{
|
||||
auto& item = schema.items[i];
|
||||
auto& quantity = quantity_get(i);
|
||||
auto& rarity = schema.rarities[item.rarityID];
|
||||
auto hasItemColor = item.color.has_value();
|
||||
|
||||
if (rarity.isHidden && quantity <= 0) continue;
|
||||
|
||||
ImGui::PushID(i);
|
||||
|
||||
ImGui::SetCursorPos(cursorPos);
|
||||
auto cursorScreenPos = ImGui::GetCursorScreenPos();
|
||||
auto [canvas, rect] = item_canvas_get(i, size);
|
||||
auto isSelected = selectedItemID == i;
|
||||
if (hasItemColor) imgui::style::color_set(*item.color);
|
||||
|
||||
if (isSelected)
|
||||
{
|
||||
auto selectedColor = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, selectedColor);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, selectedColor);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, selectedColor);
|
||||
}
|
||||
|
||||
auto isPressed =
|
||||
WIDGET_FX(ImGui::ImageButton("##Image Button", canvas.texture, size, ImVec2(), ImVec2(1, 1), ImVec4(),
|
||||
quantity <= 0 ? ImVec4(0, 0, 0, 0.5f) : ImVec4(1, 1, 1, 1)));
|
||||
if (isSelected) ImGui::PopStyleColor(3);
|
||||
isAnyInventoryItemHovered = isAnyInventoryItemHovered || ImGui::IsItemHovered();
|
||||
if (isPressed) selectedItemID = i;
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && quantity > 0) item_use(i);
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
if (quantity > 0)
|
||||
item_tooltip_draw(item, quantity);
|
||||
else
|
||||
item_unknown_draw();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
|
||||
auto text = std::format("x{}", quantity);
|
||||
auto textPos = ImVec2(cursorScreenPos.x + size.x - ImGui::CalcTextSize(text.c_str()).x,
|
||||
cursorScreenPos.y + size.y - ImGui::GetTextLineHeightWithSpacing());
|
||||
ImGui::GetWindowDrawList()->AddText(textPos, ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_Text)),
|
||||
text.c_str());
|
||||
ImGui::PopFont();
|
||||
if (hasItemColor) style::color_set(resources, character);
|
||||
|
||||
auto increment = ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x;
|
||||
cursorPos.x += increment;
|
||||
|
||||
if (cursorPos.x + increment > ImGui::GetContentRegionAvail().x)
|
||||
{
|
||||
cursorPos.x = cursorStartX;
|
||||
cursorPos.y += increment;
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !isAnyInventoryItemHovered)
|
||||
selectedItemID = -1;
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
isItemSelected = selectedItemID >= 0 && selectedItemID < (int)schema.items.size();
|
||||
auto selectedQuantity = isItemSelected ? quantity_get(selectedItemID) : 0;
|
||||
auto isSelectedItemKnown = isItemSelected && selectedQuantity > 0;
|
||||
auto selectedItemHasColor = isItemSelected && schema.items[selectedItemID].color.has_value();
|
||||
|
||||
if (isInfoVisible &&
|
||||
ImGui::BeginChild("##Info Child", infoChildSize, ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar))
|
||||
{
|
||||
if (selectedItemHasColor) imgui::style::color_set(*schema.items[selectedItemID].color);
|
||||
ImGui::Separator();
|
||||
auto isButtonChildVisible = selectedQuantity > 0;
|
||||
ImGui::PushFont(resources.font.get(), Font::HEADER_2);
|
||||
auto buttonRowHeight = ImGui::GetFrameHeight();
|
||||
auto buttonChildHeight =
|
||||
isButtonChildVisible ? buttonRowHeight * 2.0f + ImGui::GetStyle().ItemSpacing.y * 5.0f : 0.0f;
|
||||
auto buttonChildSize = ImVec2(ImGui::GetContentRegionAvail().x, buttonChildHeight);
|
||||
auto infoBodySize =
|
||||
ImVec2(ImGui::GetContentRegionAvail().x,
|
||||
ImGui::GetContentRegionAvail().y - buttonChildSize.y -
|
||||
(isButtonChildVisible ? ImGui::GetStyle().ItemSpacing.y : 0.0f));
|
||||
ImGui::PopFont();
|
||||
|
||||
if (ImGui::BeginChild("##Info Content Child", infoBodySize))
|
||||
{
|
||||
if (!isItemSelected)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
ImGui::TextWrapped("%s", strings.get(Strings::InventoryEmptyHint).c_str());
|
||||
ImGui::PopFont();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& item = schema.items[selectedItemID];
|
||||
|
||||
if (isSelectedItemKnown)
|
||||
item_details_draw(item, selectedQuantity);
|
||||
else
|
||||
item_unknown_draw();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
if (isButtonChildVisible &&
|
||||
ImGui::BeginChild("##Info Actions Child", buttonChildSize, ImGuiChildFlags_None,
|
||||
ImGuiWindowFlags_NoScrollbar))
|
||||
{
|
||||
auto& selectedItem = schema.items[selectedItemID];
|
||||
auto canUseSelectedItem = true;
|
||||
auto canUpgradeSelectedItem = is_able_to_upgrade_get(selectedItem, selectedQuantity);
|
||||
auto rowTwoButtonSize = row_widget_size_get(2);
|
||||
|
||||
auto upgrade_item_name_get = [&]() -> std::string
|
||||
{
|
||||
if (!selectedItem.upgradeID.has_value()) return {};
|
||||
return schema.items.at(*selectedItem.upgradeID).name;
|
||||
};
|
||||
|
||||
auto upgrade_tooltip_get = [&](bool isAll)
|
||||
{
|
||||
if (!is_possible_to_upgrade_get(selectedItem))
|
||||
return strings.get(Strings::InventoryUpgradeNoPath);
|
||||
|
||||
auto upgradeItemName = upgrade_item_name_get();
|
||||
auto upgradeCount = *selectedItem.upgradeCount;
|
||||
|
||||
if (!canUpgradeSelectedItem)
|
||||
return std::vformat(strings.get(Strings::InventoryUpgradeNeedsTemplate),
|
||||
std::make_format_args(upgradeCount, upgradeItemName));
|
||||
|
||||
if (!isAll)
|
||||
return std::vformat(strings.get(Strings::InventoryUpgradeOneTemplate),
|
||||
std::make_format_args(upgradeCount, upgradeItemName));
|
||||
|
||||
auto upgradedCount = selectedQuantity / upgradeCount;
|
||||
return std::vformat(strings.get(Strings::InventoryUpgradeAllTemplate),
|
||||
std::make_format_args(upgradeCount, upgradedCount, upgradeItemName));
|
||||
};
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Dummy(ImVec2(0, ImGui::GetStyle().ItemSpacing.y));
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
|
||||
ImGui::BeginDisabled(!canUseSelectedItem);
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InventorySpawnButton).c_str(),
|
||||
{ImGui::GetContentRegionAvail().x, 0})))
|
||||
item_use(selectedItemID);
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(!canUpgradeSelectedItem);
|
||||
if (WIDGET_FX(
|
||||
ImGui::Button(strings.get(Strings::InventoryUpgradeButton).c_str(), rowTwoButtonSize)))
|
||||
item_upgrade(selectedItemID, false);
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
|
||||
ImGui::SetItemTooltip("%s", upgrade_tooltip_get(false).c_str());
|
||||
ImGui::PopFont();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InventoryUpgradeAllButton).c_str(),
|
||||
rowTwoButtonSize)))
|
||||
item_upgrade(selectedItemID, true);
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
|
||||
ImGui::SetItemTooltip("%s", upgrade_tooltip_get(true).c_str());
|
||||
ImGui::PopFont();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::PopFont();
|
||||
}
|
||||
if (isButtonChildVisible) ImGui::EndChild();
|
||||
if (selectedItemHasColor) style::color_set(resources, character);
|
||||
}
|
||||
if (isInfoVisible) ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
int Inventory::count()
|
||||
{
|
||||
int count{};
|
||||
for (auto& [type, quantity] : values)
|
||||
count += quantity;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
class Inventory
|
||||
{
|
||||
@@ -19,6 +19,7 @@ namespace game::state::main
|
||||
std::unordered_map<int, entity::Actor> actors{};
|
||||
std::unordered_map<int, glm::vec4> rects{};
|
||||
std::unordered_map<int, Canvas> canvases{};
|
||||
int selectedItemID{-1};
|
||||
|
||||
void tick();
|
||||
void update(Resources&, ItemManager&, entity::Character&);
|
||||
@@ -13,8 +13,20 @@ using namespace game::resource;
|
||||
using namespace game::util;
|
||||
using namespace glm;
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
namespace
|
||||
{
|
||||
int durability_animation_index_get(const resource::xml::Item& schema, const resource::xml::Anm2& anm2, int durability,
|
||||
int durabilityMax)
|
||||
{
|
||||
if (durability >= durabilityMax) return -1;
|
||||
|
||||
auto animationName = schema.animations.chew + std::to_string(std::max(0, durability));
|
||||
return anm2.animationMap.contains(animationName) ? anm2.animationMap.at(animationName) : -1;
|
||||
}
|
||||
}
|
||||
|
||||
void ItemManager::update(entity::Character& character, entity::Cursor& cursor, AreaManager& areaManager, Text& text,
|
||||
const glm::vec4& bounds, Canvas& canvas)
|
||||
{
|
||||
@@ -54,7 +66,7 @@ namespace game::state::main
|
||||
if (isJustItemHeldStopped || isJustItemThrown)
|
||||
{
|
||||
cursor.queue_default_animation();
|
||||
if (!isJustItemThrown) character.queue_idle_animation();
|
||||
if (!isJustItemThrown && character.queuedPlay.empty()) character.queue_idle_animation();
|
||||
isJustItemHeldStopped = false;
|
||||
isJustItemThrown = false;
|
||||
}
|
||||
@@ -70,11 +82,19 @@ namespace game::state::main
|
||||
math::random_in_range(spawnBounds.y, spawnBounds.y + spawnBounds.w));
|
||||
|
||||
auto& itemSchema = character.data.itemSchema;
|
||||
items.emplace_back(itemSchema.anm2s.at(id), position, id);
|
||||
auto& item = itemSchema.items.at(id);
|
||||
auto& anm2 = itemSchema.anm2s.at(id);
|
||||
auto durabilityMax = item.durability.value_or(itemSchema.durability);
|
||||
auto animationIndex = durability_animation_index_get(itemSchema, anm2, 0, durabilityMax);
|
||||
items.emplace_back(anm2, position, id, 0, animationIndex);
|
||||
}
|
||||
queuedItemIDs.clear();
|
||||
|
||||
if (isMouseRightDown) cursor.queue_play({cursorSchema.animations.return_.get()});
|
||||
if (isMouseRightDown)
|
||||
{
|
||||
auto animation = cursorSchema.animations.return_.get();
|
||||
if (animation) cursor.queue_play({*animation});
|
||||
}
|
||||
|
||||
if (auto heldItem = vector::find(items, heldItemIndex))
|
||||
{
|
||||
@@ -93,15 +113,15 @@ namespace game::state::main
|
||||
|
||||
if (schema.categories[item.categoryID].isEdible)
|
||||
{
|
||||
auto& chewCountMax = item.chewCount.has_value() ? *item.chewCount : schema.chewCount;
|
||||
auto caloriesChew = item.calories.has_value() ? *item.calories / (chewCountMax + 1) : 0;
|
||||
auto isCanEat = character.calories + caloriesChew <= character.max_capacity();
|
||||
auto& durabilityMax = item.durability.has_value() ? *item.durability : schema.durability;
|
||||
auto caloriesPerBite = item.calories.has_value() && durabilityMax > 0 ? *item.calories / durabilityMax : 0;
|
||||
auto isCanEat = character.calories + caloriesPerBite <= character.max_capacity();
|
||||
|
||||
if (isJustItemHeld)
|
||||
{
|
||||
if (isCanEat)
|
||||
text.set(dialogue.get(isOverCapacity ? dialogue.feedFull : dialogue.feed), character);
|
||||
else if (caloriesChew > character.capacity)
|
||||
else if (caloriesPerBite > character.capacity)
|
||||
text.set(dialogue.get(dialogue.lowCapacity), character);
|
||||
else
|
||||
text.set(dialogue.get(dialogue.full), character);
|
||||
@@ -121,37 +141,44 @@ namespace game::state::main
|
||||
|
||||
if (character.playedEventID == eatArea.eventID)
|
||||
{
|
||||
heldItem->chewCount++;
|
||||
heldItem->durability++;
|
||||
character.consume_played_event();
|
||||
|
||||
auto chewAnimation = schema.animations.chew + std::to_string(heldItem->chewCount);
|
||||
auto animationIndex = heldItem->chewCount > 0 ? heldItem->animationMap[chewAnimation] : -1;
|
||||
|
||||
heldItem->play(animationIndex, entity::Actor::SET);
|
||||
|
||||
character.calories += caloriesChew;
|
||||
character.totalCaloriesConsumed += caloriesChew;
|
||||
character.calories += caloriesPerBite;
|
||||
character.totalCaloriesConsumed += caloriesPerBite;
|
||||
|
||||
if (item.capacityBonus.has_value())
|
||||
{
|
||||
character.capacity += *item.capacityBonus / durabilityMax;
|
||||
character.capacity =
|
||||
glm::clamp(character.capacity, (float)character.data.capacityMin, (float)character.data.capacityMax);
|
||||
}
|
||||
if (item.eatSpeedBonus.has_value())
|
||||
{
|
||||
character.eatSpeed += *item.eatSpeedBonus / (chewCountMax + 1);
|
||||
character.eatSpeed =
|
||||
glm::clamp(character.data.eatSpeedMin, character.eatSpeed, character.data.eatSpeedMax);
|
||||
character.eatSpeed += *item.eatSpeedBonus / durabilityMax;
|
||||
character.eatSpeed = glm::clamp(character.eatSpeed, (float)character.data.eatSpeedMin,
|
||||
(float)character.data.eatSpeedMax);
|
||||
}
|
||||
if (item.digestionBonus.has_value())
|
||||
{
|
||||
character.digestionRate += *item.digestionBonus / (chewCountMax + 1);
|
||||
character.digestionRate = glm::clamp(character.data.digestionRateMin, character.digestionRate,
|
||||
character.data.digestionRateMax);
|
||||
character.digestionRate += *item.digestionBonus / durabilityMax;
|
||||
character.digestionRate = glm::clamp(character.digestionRate, (float)character.data.digestionRateMin,
|
||||
(float)character.data.digestionRateMax);
|
||||
}
|
||||
|
||||
if (heldItem->chewCount > chewCountMax)
|
||||
if (heldItem->durability >= durabilityMax)
|
||||
{
|
||||
isQueueFinishFood = true;
|
||||
character.totalFoodItemsEaten++;
|
||||
queuedRemoveItemIndex = heldItemIndex;
|
||||
heldItemIndex = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto animationIndex =
|
||||
durability_animation_index_get(schema, *heldItem, heldItem->durability, durabilityMax);
|
||||
heldItem->play(animationIndex, entity::Actor::SET);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +200,7 @@ namespace game::state::main
|
||||
|
||||
// Food stolen
|
||||
if (auto animation = character.animation_get(character.animation_name_convert(eatArea.animation));
|
||||
character.is_playing(animation->name) && !isOverCapacity)
|
||||
animation && character.is_playing(animation->name))
|
||||
{
|
||||
if (!math::is_point_in_rectf(rect, heldItem->position))
|
||||
text.set(dialogue.get(isOverCapacity ? dialogue.foodTakenFull : dialogue.foodTaken), character);
|
||||
@@ -208,7 +235,7 @@ namespace game::state::main
|
||||
if (math::is_point_in_rectf(item.rect(), cursorPosition) && !isImguiCaptureMouse)
|
||||
{
|
||||
isItemHovered = true;
|
||||
cursor.queue_play({cursorSchema.animations.hover.get()});
|
||||
if (auto animation = cursorSchema.animations.hover.get()) cursor.queue_play({*animation});
|
||||
cursor.state = entity::Cursor::HOVER;
|
||||
|
||||
if (isMouseLeftClicked)
|
||||
@@ -220,7 +247,7 @@ namespace game::state::main
|
||||
if (isMouseLeftDown)
|
||||
{
|
||||
isItemHeld = true;
|
||||
cursor.queue_play({cursorSchema.animations.grab.get()});
|
||||
if (auto animation = cursorSchema.animations.grab.get()) cursor.queue_play({*animation});
|
||||
cursor.state = entity::Cursor::ACTION;
|
||||
heldItemIndex = i;
|
||||
heldItemMoveIndex = i;
|
||||
@@ -228,7 +255,7 @@ namespace game::state::main
|
||||
|
||||
if (isMouseRightClicked)
|
||||
{
|
||||
if (item.chewCount > 0)
|
||||
if (item.durability > 0)
|
||||
schema.sounds.dispose.play();
|
||||
else
|
||||
{
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "area_manager.hpp"
|
||||
#include "text.hpp"
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
class ItemManager
|
||||
{
|
||||
@@ -1,19 +1,23 @@
|
||||
#include "menu.hpp"
|
||||
|
||||
#include "style.hpp"
|
||||
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/imgui/style.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace game::util;
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::resource::xml;
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
void Menu::tick()
|
||||
{
|
||||
inventory.tick();
|
||||
play.tick();
|
||||
arcade.tick();
|
||||
}
|
||||
|
||||
void Menu::update(Resources& resources, ItemManager& itemManager, entity::Character& character,
|
||||
@@ -22,6 +26,7 @@ namespace game::state::main
|
||||
static constexpr auto WIDTH_MULTIPLIER = 0.30f;
|
||||
|
||||
auto& schema = character.data.menuSchema;
|
||||
auto& strings = character.data.strings;
|
||||
|
||||
auto style = ImGui::GetStyle();
|
||||
auto& io = ImGui::GetIO();
|
||||
@@ -55,47 +60,44 @@ namespace game::state::main
|
||||
if (ImGui::BeginTabBar("##Options"))
|
||||
{
|
||||
|
||||
if (isChat && WIDGET_FX(ImGui::BeginTabItem("Chat")))
|
||||
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabInteract).c_str())))
|
||||
{
|
||||
chat.update(resources, text, character);
|
||||
interact.update(resources, text, character);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem("Play")))
|
||||
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabArcade).c_str())))
|
||||
{
|
||||
play.update(resources, character, inventory, text);
|
||||
arcade.update(resources, character, inventory, text);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem("Items")))
|
||||
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabInventory).c_str())))
|
||||
{
|
||||
inventory.update(resources, itemManager, character);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem("Stats")))
|
||||
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabSettings).c_str())))
|
||||
{
|
||||
stats.update(resources, play, character);
|
||||
settingsMenu.update(resources, SettingsMenu::PLAY, &strings);
|
||||
if (settingsMenu.isJustColorSet) style::color_set(resources, character);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem("Settings")))
|
||||
if (isCheats && WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabCheats).c_str())))
|
||||
{
|
||||
configuration.update(resources, Configuration::MAIN);
|
||||
cheats.update(resources, character, inventory);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (isCheats && WIDGET_FX(ImGui::BeginTabItem("Cheats")))
|
||||
#if DEBUG
|
||||
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabDebug).c_str())))
|
||||
{
|
||||
cheats.update(resources, character, inventory, text);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (isDebug && WIDGET_FX(ImGui::BeginTabItem("Debug")))
|
||||
{
|
||||
debug.update(character, cursor, itemManager, canvas);
|
||||
debug.update(character, cursor, itemManager, canvas, text);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
@@ -119,7 +121,9 @@ namespace game::state::main
|
||||
|
||||
if (t <= 0.0f || t >= 1.0f)
|
||||
{
|
||||
ImGui::SetItemTooltip(isOpen ? "Close Main Menu" : "Open Main Menu");
|
||||
ImGui::SetItemTooltip("%s", strings.get(isOpen ? Strings::MenuCloseTooltip
|
||||
: Strings::MenuOpenTooltip)
|
||||
.c_str());
|
||||
if (result)
|
||||
{
|
||||
isOpen = !isOpen;
|
||||
@@ -2,41 +2,37 @@
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "../configuration.hpp"
|
||||
#include "../settings_menu.hpp"
|
||||
|
||||
#include "chat.hpp"
|
||||
#include "arcade.hpp"
|
||||
#include "cheats.hpp"
|
||||
#include "debug.hpp"
|
||||
#include "play.hpp"
|
||||
#include "stats.hpp"
|
||||
#include "interact.hpp"
|
||||
#include "inventory.hpp"
|
||||
#include "text.hpp"
|
||||
|
||||
#include "../../util/imgui/window_slide.hpp"
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
class Menu
|
||||
{
|
||||
public:
|
||||
Play play;
|
||||
Chat chat;
|
||||
Arcade arcade;
|
||||
Interact interact;
|
||||
Cheats cheats;
|
||||
Debug debug;
|
||||
Stats stats;
|
||||
Inventory inventory;
|
||||
|
||||
state::Configuration configuration;
|
||||
state::SettingsMenu settingsMenu;
|
||||
|
||||
#if DEBUG
|
||||
bool isCheats{true};
|
||||
bool isDebug{true};
|
||||
#else
|
||||
bool isCheats{};
|
||||
bool isDebug{};
|
||||
#endif
|
||||
|
||||
bool isOpen{true};
|
||||
bool isChat{true};
|
||||
util::imgui::WindowSlide slide{};
|
||||
|
||||
void tick();
|
||||
14
src/state/play/style.hpp
Normal file
14
src/state/play/style.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
#include "../../resources.hpp"
|
||||
#include "../../util/imgui/style.hpp"
|
||||
|
||||
namespace game::state::play::style
|
||||
{
|
||||
inline void color_set(Resources& resources, const entity::Character& character)
|
||||
{
|
||||
game::util::imgui::style::color_set(resources.settings.isUseCharacterColor ? character.data.color
|
||||
: resources.settings.color);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
const char* utf8_advance_chars(const char* text, const char* end, int count)
|
||||
{
|
||||
@@ -28,7 +28,7 @@ namespace game::state::main
|
||||
return it;
|
||||
}
|
||||
|
||||
void Text::set(resource::xml::Dialogue::Entry* dialogueEntry, entity::Character& character)
|
||||
void Text::set(resource::xml::Dialogue::Entry* dialogueEntry, entity::Character& character, bool isInterruptible)
|
||||
{
|
||||
if (!dialogueEntry) return;
|
||||
this->entry = dialogueEntry;
|
||||
@@ -37,9 +37,12 @@ namespace game::state::main
|
||||
index = 0;
|
||||
time = 0.0f;
|
||||
isEnabled = true;
|
||||
if (!dialogueEntry->animation.empty())
|
||||
character.queue_play({.animation = dialogueEntry->animation, .isInterruptible = isInterruptible});
|
||||
if (dialogueEntry->text.empty())
|
||||
isEnabled = false;
|
||||
else
|
||||
character.isTalking = true;
|
||||
if (!dialogueEntry->animation.empty()) character.queue_play({dialogueEntry->animation});
|
||||
if (dialogueEntry->text.empty()) isEnabled = false;
|
||||
}
|
||||
|
||||
void Text::tick(entity::Character& character)
|
||||
@@ -47,6 +50,8 @@ namespace game::state::main
|
||||
if (!entry || isFinished) return;
|
||||
|
||||
index++;
|
||||
auto blipPeriod = character.data.textBlipPeriodBase;
|
||||
if (blipPeriod > 0 && index % blipPeriod == 0) character.data.sounds.blip.play();
|
||||
|
||||
if (index >= ImTextCountCharsFromUtf8(entry->text.c_str(), entry->text.c_str() + entry->text.size()))
|
||||
{
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include "../../resources.hpp"
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
class Text
|
||||
{
|
||||
@@ -21,7 +21,7 @@ namespace game::state::main
|
||||
bool isEnabled{true};
|
||||
float time{};
|
||||
|
||||
void set(resource::xml::Dialogue::Entry*, entity::Character&);
|
||||
void set(resource::xml::Dialogue::Entry*, entity::Character&, bool isInterruptible = true);
|
||||
void tick(entity::Character&);
|
||||
void update(entity::Character&);
|
||||
bool is_interruptible() const;
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <imgui.h>
|
||||
#include <ranges>
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
void Toasts::tick()
|
||||
{
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
class Toasts
|
||||
{
|
||||
@@ -7,8 +7,9 @@
|
||||
|
||||
using namespace game::util;
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::resource::xml;
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
void Tools::update(entity::Character& character, entity::Cursor& cursor, World& world, World::Focus focus,
|
||||
Canvas& canvas)
|
||||
@@ -18,6 +19,7 @@ namespace game::state::main
|
||||
auto style = ImGui::GetStyle();
|
||||
auto& io = ImGui::GetIO();
|
||||
auto& schema = character.data.menuSchema;
|
||||
auto& strings = character.data.strings;
|
||||
|
||||
slide.update(isOpen, io.DeltaTime);
|
||||
|
||||
@@ -47,21 +49,21 @@ namespace game::state::main
|
||||
{
|
||||
auto buttonSize = imgui::to_imvec2(vec2(ImGui::GetContentRegionAvail().x));
|
||||
|
||||
auto cursor_mode_button = [&](const char* name, InteractType mode)
|
||||
auto cursor_mode_button = [&](const std::string& name, int interactTypeID)
|
||||
{
|
||||
auto isMode = cursor.mode == mode;
|
||||
auto isMode = cursor.interactTypeID == interactTypeID;
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,
|
||||
ImGui::GetStyleColorVec4(isMode ? ImGuiCol_ButtonHovered : ImGuiCol_Button));
|
||||
if (WIDGET_FX(ImGui::Button(name, buttonSize))) cursor.mode = mode;
|
||||
if (WIDGET_FX(ImGui::Button(name.c_str(), buttonSize))) cursor.interactTypeID = interactTypeID;
|
||||
ImGui::PopStyleColor();
|
||||
};
|
||||
|
||||
if (WIDGET_FX(ImGui::Button("Home", buttonSize))) world.character_focus(character, canvas, focus);
|
||||
ImGui::SetItemTooltip("%s", "Reset camera view.\n(Shortcut: Home)");
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ToolsHomeButton).c_str(), buttonSize)))
|
||||
world.character_focus(character, canvas, focus);
|
||||
ImGui::SetItemTooltip("%s", strings.get(Strings::ToolsHomeTooltip).c_str());
|
||||
|
||||
cursor_mode_button("Rub", InteractType::RUB);
|
||||
cursor_mode_button("Kiss", InteractType::KISS);
|
||||
cursor_mode_button("Smack", InteractType::SMACK);
|
||||
for (int i = 0; i < (int)character.data.interactTypeNames.size(); i++)
|
||||
cursor_mode_button(character.data.interactTypeNames[i], i);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
@@ -83,7 +85,9 @@ namespace game::state::main
|
||||
|
||||
if (t <= 0.0f || t >= 1.0f)
|
||||
{
|
||||
ImGui::SetItemTooltip(isOpen ? "Close Tools" : "Open Tools");
|
||||
ImGui::SetItemTooltip("%s", strings.get(isOpen ? Strings::ToolsCloseTooltip
|
||||
: Strings::ToolsOpenTooltip)
|
||||
.c_str());
|
||||
if (result)
|
||||
{
|
||||
isOpen = !isOpen;
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "../../util/imgui/window_slide.hpp"
|
||||
#include "world.hpp"
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
class Tools
|
||||
{
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
void World::set(entity::Character& character, Canvas& canvas, Focus focus)
|
||||
{
|
||||
@@ -22,15 +22,16 @@ namespace game::state::main
|
||||
auto& pan = canvas.pan;
|
||||
auto& zoom = canvas.zoom;
|
||||
auto& io = ImGui::GetIO();
|
||||
bool isPan{true};
|
||||
auto isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
|
||||
auto isMouseLeftDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||
auto isCtrlDown = ImGui::IsKeyDown(ImGuiMod_Ctrl);
|
||||
auto panMultiplier = ZOOM_BASE / zoom;
|
||||
|
||||
if (!ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) && !ImGui::IsAnyItemActive())
|
||||
{
|
||||
if ((isMouseMiddleDown) && isPan)
|
||||
if ((isMouseMiddleDown) || (isMouseLeftDown && isCtrlDown))
|
||||
{
|
||||
cursor.queue_play({cursorSchema.animations.pan.get()});
|
||||
if (auto animation = cursorSchema.animations.pan.get()) cursor.queue_play({*animation});
|
||||
pan -= imgui::to_vec2(io.MouseDelta) * panMultiplier;
|
||||
}
|
||||
|
||||
@@ -43,7 +44,7 @@ namespace game::state::main
|
||||
auto zoomFactorBefore = math::to_unit(zoomBefore);
|
||||
auto cursorWorld = pan + (cursorPos / zoomFactorBefore);
|
||||
|
||||
cursor.queue_play({cursorSchema.animations.zoom.get()});
|
||||
if (auto animation = cursorSchema.animations.zoom.get()) cursor.queue_play({*animation});
|
||||
|
||||
zoom = glm::clamp(ZOOM_MIN, zoom + (io.MouseWheel * ZOOM_STEP), ZOOM_MAX);
|
||||
|
||||
@@ -61,6 +62,7 @@ namespace game::state::main
|
||||
{
|
||||
static constexpr float MENU_WIDTH_MULTIPLIER = 0.30f;
|
||||
static constexpr float TOOLS_WIDTH_MULTIPLIER = 0.10f;
|
||||
static constexpr float INFO_HEIGHT_MULTIPLIER = 4.0f;
|
||||
static constexpr float PADDING = 100.0f;
|
||||
|
||||
auto rect = character.rect();
|
||||
@@ -71,13 +73,18 @@ namespace game::state::main
|
||||
|
||||
rect = {rect.x - PADDING * 0.5f, rect.y - PADDING * 0.5f, rect.z + PADDING, rect.w + PADDING};
|
||||
|
||||
auto zoomFactor = std::min((float)canvas.size.x / rect.z, (float)canvas.size.y / rect.w);
|
||||
auto infoHeightPixels =
|
||||
ImGui::GetTextLineHeightWithSpacing() * INFO_HEIGHT_MULTIPLIER + ImGui::GetStyle().WindowPadding.y * 2.0f;
|
||||
auto usableHeightPixels = std::max(1.0f, (float)canvas.size.y - infoHeightPixels);
|
||||
|
||||
auto zoomFactor = std::min((float)canvas.size.x / rect.z, usableHeightPixels / rect.w);
|
||||
canvas.zoom = glm::clamp(ZOOM_MIN, math::to_percent(zoomFactor), ZOOM_MAX);
|
||||
zoomFactor = math::to_unit(canvas.zoom);
|
||||
|
||||
auto rectCenter = glm::vec2(rect.x + rect.z * 0.5f, rect.y + rect.w * 0.5f);
|
||||
auto viewSizeWorld = glm::vec2(canvas.size) / zoomFactor;
|
||||
canvas.pan = rectCenter - (vec2(viewSizeWorld.x, viewSizeWorld.y) * 0.5f);
|
||||
auto infoHeightWorld = infoHeightPixels / zoomFactor;
|
||||
canvas.pan = rectCenter - vec2(viewSizeWorld.x * 0.5f, (viewSizeWorld.y + infoHeightWorld) * 0.5f);
|
||||
auto menuWidthWorld = (canvas.size.x * MENU_WIDTH_MULTIPLIER) / zoomFactor;
|
||||
auto toolsWidthWorld = (canvas.size.x * TOOLS_WIDTH_MULTIPLIER) / zoomFactor;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../canvas.hpp"
|
||||
#include "../../render/canvas.hpp"
|
||||
#include "../../entity/character.hpp"
|
||||
|
||||
#include "character_manager.hpp"
|
||||
#include "item_manager.hpp"
|
||||
|
||||
namespace game::state::main
|
||||
namespace game::state::play
|
||||
{
|
||||
class World
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "../canvas.hpp"
|
||||
#include "../render/canvas.hpp"
|
||||
|
||||
#include "select/characters.hpp"
|
||||
#include "select/info.hpp"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user