Compare commits
5
Commits
92b8fcae24
...
167f1a5ddd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
167f1a5ddd | ||
|
|
1cf4d1ea85 | ||
|
|
1a0db1abbd | ||
|
|
2d11cddc5e | ||
|
|
84e8ab1a2c |
No files matched your search
+30
-9
@@ -1,11 +1,32 @@
|
|||||||
|
BasedOnStyle: LLVM
|
||||||
|
IndentWidth: 4
|
||||||
ColumnLimit: 120
|
ColumnLimit: 120
|
||||||
PointerAlignment: Left
|
BreakBeforeBraces: Custom
|
||||||
ReferenceAlignment: Left
|
BraceWrapping:
|
||||||
AllowShortFunctionsOnASingleLine: All
|
AfterCaseLabel: true
|
||||||
AllowShortIfStatementsOnASingleLine: true
|
AfterClass: true
|
||||||
CommentPragmas: '^'
|
AfterControlStatement: Always
|
||||||
BreakBeforeBraces: Allman
|
AfterEnum: true
|
||||||
NamespaceIndentation: All
|
AfterFunction: true
|
||||||
FixNamespaceComments: false
|
AfterNamespace: true
|
||||||
|
AfterStruct: true
|
||||||
|
AfterUnion: true
|
||||||
|
AfterExternBlock: true
|
||||||
|
BeforeCatch: true
|
||||||
|
BeforeElse: true
|
||||||
|
BeforeLambdaBody: false
|
||||||
|
BeforeWhile: true
|
||||||
|
SplitEmptyFunction: false
|
||||||
|
SplitEmptyRecord: false
|
||||||
|
RemoveBracesLLVM: true
|
||||||
|
InsertBraces: false
|
||||||
|
SortIncludes: Never
|
||||||
|
AllowShortFunctionsOnASingleLine: None
|
||||||
|
AllowShortIfStatementsOnASingleLine: Never
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AllowShortBlocksOnASingleLine: Never
|
||||||
|
AllowShortLambdasOnASingleLine: Inline
|
||||||
|
AccessModifierOffset: -4
|
||||||
IndentCaseLabels: true
|
IndentCaseLabels: true
|
||||||
IndentPPDirectives: BeforeHash
|
NamespaceIndentation: None
|
||||||
|
PointerAlignment: Left
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
CompileFlags:
|
|
||||||
CompilationDatabase: out/build/linux-debug
|
|
||||||
Add: [-std=gnu++23]
|
|
||||||
+4
-5
@@ -1,9 +1,8 @@
|
|||||||
build/
|
build/
|
||||||
build-web/
|
build-web/
|
||||||
concept/
|
concept/
|
||||||
release/
|
__pycache__/
|
||||||
out/
|
tools/
|
||||||
external/
|
CLAUDE.md
|
||||||
.codex
|
|
||||||
AGENTS.md
|
|
||||||
resources/icons/
|
resources/icons/
|
||||||
|
*-autosave.kra
|
||||||
+11
-15
@@ -1,18 +1,14 @@
|
|||||||
[submodule "external/SDL"]
|
|
||||||
path = external/SDL
|
|
||||||
url = https://github.com/libsdl-org/SDL
|
|
||||||
[submodule "external/SDL_mixer"]
|
|
||||||
path = external/SDL_mixer
|
|
||||||
url = https://github.com/libsdl-org/SDL_mixer
|
|
||||||
[submodule "external/imgui"]
|
[submodule "external/imgui"]
|
||||||
path = external/imgui
|
path = external/imgui
|
||||||
url = https://github.com/ocornut/imgui
|
url = https://github.com/ocornut/imgui
|
||||||
[submodule "external/glm"]
|
[submodule "external/sdl"]
|
||||||
path = external/glm
|
path = external/sdl
|
||||||
url = https://github.com/g-truc/glm
|
url = https://github.com/libsdl-org/SDL.git
|
||||||
[submodule "external/tinyxml2"]
|
branch = release-3.4.x
|
||||||
path = external/tinyxml2
|
[submodule "external/stb"]
|
||||||
url = https://github.com/leethomason/tinyxml2
|
path = external/stb
|
||||||
[submodule "external/physfs"]
|
url = https://github.com/nothings/stb.git
|
||||||
path = external/physfs
|
[submodule "external/simdjson"]
|
||||||
url = https://github.com/icculus/physfs
|
path = external/simdjson
|
||||||
|
url = https://github.com/simdjson/simdjson
|
||||||
|
shallow = true
|
||||||
Vendored
-61
@@ -1,61 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Debug (CMake Debug preset)",
|
|
||||||
"type": "cppdbg",
|
|
||||||
"request": "launch",
|
|
||||||
"preLaunchTask": "cmake: build debug",
|
|
||||||
"program": "${workspaceFolder}/out/build/linux-debug/bin/Debug/shweets-sim",
|
|
||||||
"args": [],
|
|
||||||
"stopAtEntry": false,
|
|
||||||
"cwd": "${workspaceFolder}/out/build/linux-debug/bin/Debug",
|
|
||||||
"environment": [],
|
|
||||||
"externalConsole": false,
|
|
||||||
"MIMode": "gdb",
|
|
||||||
"miDebuggerPath": "/usr/bin/gdb",
|
|
||||||
"setupCommands": [
|
|
||||||
{
|
|
||||||
"description": "Enable pretty-printing for gdb",
|
|
||||||
"text": "-enable-pretty-printing"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Set disassembly flavor to Intel",
|
|
||||||
"text": "-gdb-set disassembly-flavor intel"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"windows": {
|
|
||||||
"type": "cppvsdbg",
|
|
||||||
"program": "${workspaceFolder}/out/build/x64-Debug/bin/Debug/shweets-sim.exe",
|
|
||||||
"cwd": "${workspaceFolder}/out/build/x64-Debug/bin/Debug"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Run (CMake Release preset)",
|
|
||||||
"type": "cppdbg",
|
|
||||||
"request": "launch",
|
|
||||||
"noDebug": true,
|
|
||||||
"preLaunchTask": "cmake: build release",
|
|
||||||
"program": "${workspaceFolder}/out/build/linux-release/bin/Release/shweets-sim",
|
|
||||||
"args": [],
|
|
||||||
"cwd": "${workspaceFolder}/out/build/linux-release/bin/Release",
|
|
||||||
"environment": [],
|
|
||||||
"externalConsole": false,
|
|
||||||
"MIMode": "gdb",
|
|
||||||
"miDebuggerPath": "/usr/bin/gdb",
|
|
||||||
"windows": {
|
|
||||||
"type": "cppvsdbg",
|
|
||||||
"program": "${workspaceFolder}/out/build/x64-Release/bin/Release/shweets-sim.exe",
|
|
||||||
"cwd": "${workspaceFolder}/out/build/x64-Release/bin/Release"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Web (Emscripten + Chromium)",
|
|
||||||
"type": "node-terminal",
|
|
||||||
"request": "launch",
|
|
||||||
"preLaunchTask": "web: run",
|
|
||||||
"command": "echo \"Web task complete. If Chromium did not open, run task: web: open chromium\"",
|
|
||||||
"cwd": "${workspaceFolder}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Vendored
-165
@@ -1,165 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "cmake: configure debug",
|
|
||||||
"type": "shell",
|
|
||||||
"linux": {
|
|
||||||
"command": "cmake --preset linux-debug"
|
|
||||||
},
|
|
||||||
"windows": {
|
|
||||||
"command": "cmake --preset x64-Debug"
|
|
||||||
},
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "cmake: build debug",
|
|
||||||
"type": "shell",
|
|
||||||
"linux": {
|
|
||||||
"command": "cmake --build --preset linux-debug"
|
|
||||||
},
|
|
||||||
"windows": {
|
|
||||||
"command": "cmake --build --preset x64-Debug"
|
|
||||||
},
|
|
||||||
"dependsOn": "cmake: configure debug",
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
"isDefault": true
|
|
||||||
},
|
|
||||||
"problemMatcher": [
|
|
||||||
"$gcc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "cmake: configure release",
|
|
||||||
"type": "shell",
|
|
||||||
"linux": {
|
|
||||||
"command": "cmake --preset linux-release"
|
|
||||||
},
|
|
||||||
"windows": {
|
|
||||||
"command": "cmake --preset x64-Release"
|
|
||||||
},
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "cmake: build release",
|
|
||||||
"type": "shell",
|
|
||||||
"linux": {
|
|
||||||
"command": "cmake --build --preset linux-release"
|
|
||||||
},
|
|
||||||
"windows": {
|
|
||||||
"command": "cmake --build --preset x64-Release"
|
|
||||||
},
|
|
||||||
"dependsOn": "cmake: configure release",
|
|
||||||
"problemMatcher": [
|
|
||||||
"$gcc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "linux: run debug",
|
|
||||||
"type": "shell",
|
|
||||||
"linux": {
|
|
||||||
"command": "${workspaceFolder}/out/build/linux-debug/bin/Debug/shweets-sim"
|
|
||||||
},
|
|
||||||
"options": {
|
|
||||||
"cwd": "${workspaceFolder}/out/build/linux-debug/bin/Debug"
|
|
||||||
},
|
|
||||||
"windows": {
|
|
||||||
"command": "echo \"linux: run debug is Linux-only\" && exit 1"
|
|
||||||
},
|
|
||||||
"dependsOn": "cmake: build debug",
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "linux: run release",
|
|
||||||
"type": "shell",
|
|
||||||
"linux": {
|
|
||||||
"command": "${workspaceFolder}/out/build/linux-release/bin/Release/shweets-sim"
|
|
||||||
},
|
|
||||||
"options": {
|
|
||||||
"cwd": "${workspaceFolder}/out/build/linux-release/bin/Release"
|
|
||||||
},
|
|
||||||
"windows": {
|
|
||||||
"command": "echo \"linux: run release is Linux-only\" && exit 1"
|
|
||||||
},
|
|
||||||
"dependsOn": "cmake: build release",
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "web: configure",
|
|
||||||
"type": "shell",
|
|
||||||
"linux": {
|
|
||||||
"command": "sh -lc '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'"
|
|
||||||
},
|
|
||||||
"windows": {
|
|
||||||
"command": "echo \"web tasks are configured for Linux (emsdk + python + chromium).\" && exit 1"
|
|
||||||
},
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "web: build",
|
|
||||||
"type": "shell",
|
|
||||||
"linux": {
|
|
||||||
"command": "sh -lc '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; cmake --build out/build/web -j$(nproc)'"
|
|
||||||
},
|
|
||||||
"windows": {
|
|
||||||
"command": "echo \"web tasks are configured for Linux (emsdk + python + chromium).\" && exit 1"
|
|
||||||
},
|
|
||||||
"dependsOn": "web: configure",
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "web: serve",
|
|
||||||
"type": "shell",
|
|
||||||
"linux": {
|
|
||||||
"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"
|
|
||||||
},
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "web: stop server",
|
|
||||||
"type": "shell",
|
|
||||||
"linux": {
|
|
||||||
"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"
|
|
||||||
},
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "web: open chromium",
|
|
||||||
"type": "shell",
|
|
||||||
"linux": {
|
|
||||||
"command": "sh -lc 'URL=http://127.0.0.1:8000/bin/Release/index.html; (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) & sleep 0.15; echo \"Requested browser open: $URL\"'"
|
|
||||||
},
|
|
||||||
"windows": {
|
|
||||||
"command": "start http://127.0.0.1:8000/index.html"
|
|
||||||
},
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "web: prepare",
|
|
||||||
"dependsOrder": "sequence",
|
|
||||||
"dependsOn": [
|
|
||||||
"web: build",
|
|
||||||
"web: serve"
|
|
||||||
],
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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/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"
|
|
||||||
},
|
|
||||||
"problemMatcher": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
+56
-266
@@ -1,284 +1,74 @@
|
|||||||
cmake_minimum_required(VERSION 3.27)
|
cmake_minimum_required(VERSION 3.24)
|
||||||
project(shweets-sim LANGUAGES C CXX)
|
if(POLICY CMP0205)
|
||||||
|
cmake_policy(SET CMP0205 NEW)
|
||||||
|
endif()
|
||||||
|
project(shweets_sim LANGUAGES C CXX)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 23)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
set(BUILD_SHARED_LIBS OFF)
|
|
||||||
|
# Two builds: Release (the default) and Debug.
|
||||||
|
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
set(CMAKE_BUILD_TYPE Release)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
set(CMAKE_SUPPRESS_REGENERATION ON)
|
add_compile_options(/utf-8)
|
||||||
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD OFF)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
|
# Release builds: -O2, link-time optimization, unused code and data dropped, and the symbols stripped.
|
||||||
if(NOT CMAKE_CONFIGURATION_TYPES)
|
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||||
if(NOT CMAKE_BUILD_TYPE)
|
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)
|
||||||
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE)
|
add_compile_options("$<$<CONFIG:Release>:-O2;-ffunction-sections;-fdata-sections>")
|
||||||
endif()
|
add_link_options("$<$<CONFIG:Release>:-Wl,--gc-sections;-s>")
|
||||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(MSVC)
|
|
||||||
set(CMAKE_C_FLAGS_DEBUG "/Od /Zi" CACHE STRING "" FORCE)
|
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "/W4 /permissive- /Od /Zi" CACHE STRING "" FORCE)
|
|
||||||
set(CMAKE_C_FLAGS_RELEASE "/O2 /DNDEBUG" CACHE STRING "" FORCE)
|
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "/W4 /permissive- /O2 /DNDEBUG" CACHE STRING "" FORCE)
|
|
||||||
else()
|
|
||||||
set(CMAKE_C_FLAGS_DEBUG "-O0 -g" CACHE STRING "" FORCE)
|
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Wpedantic -O0 -g" CACHE STRING "" FORCE)
|
|
||||||
set(CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "" FORCE)
|
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "-Wall -Wpedantic -O3 -DNDEBUG" CACHE STRING "" FORCE)
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(SDL_STATIC ON CACHE BOOL "" FORCE)
|
# SDL: static, with only the video (OpenGL) and audio pieces the game uses.
|
||||||
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||||
if (NOT WIN32)
|
set(SDL_STATIC ON CACHE BOOL "" FORCE)
|
||||||
set(SDL_ALSA ON CACHE BOOL "" FORCE)
|
set(SDL_OPENGL ON CACHE BOOL "" FORCE)
|
||||||
set(SDL_ALSA_SHARED ON CACHE BOOL "" FORCE)
|
set(SDL_TEST_LIBRARY OFF CACHE BOOL "" FORCE)
|
||||||
set(SDL_JACK ON CACHE BOOL "" FORCE)
|
set(SDL_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||||
set(SDL_JACK_SHARED ON CACHE BOOL "" FORCE)
|
foreach(OPTION RENDER GPU VULKAN CAMERA HAPTIC HIDAPI JOYSTICK POWER SENSOR DIALOG TRAY OPENGLES OFFSCREEN DUMMYVIDEO
|
||||||
set(SDL_PIPEWIRE ON CACHE BOOL "" FORCE)
|
DISKAUDIO DUMMYAUDIO OSS JACK SNDIO KMSDRM IBUS LIBURING)
|
||||||
set(SDL_PIPEWIRE_SHARED ON CACHE BOOL "" FORCE)
|
set(SDL_${OPTION} OFF CACHE BOOL "" FORCE)
|
||||||
set(SDL_SNDIO ON CACHE BOOL "" FORCE)
|
endforeach()
|
||||||
set(SDL_SNDIO_SHARED ON CACHE BOOL "" FORCE)
|
add_subdirectory(external/sdl EXCLUDE_FROM_ALL)
|
||||||
endif ()
|
|
||||||
set(SDL_HAPTIC OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDL_JOYSTICK OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDL_SENSOR OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDL_HIDAPI OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDL_CAMERA OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDL_TRAY OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDL_VULKAN OFF CACHE BOOL "" FORCE)
|
|
||||||
add_subdirectory(external/SDL EXCLUDE_FROM_ALL)
|
|
||||||
|
|
||||||
set(SDLMIXER_DEPS_SHARED OFF CACHE BOOL "" FORCE)
|
# simdjson: its single-file build, static, without exceptions, with only the AVX2 parser and the portable one.
|
||||||
set(SDLMIXER_AIFF OFF CACHE BOOL "" FORCE)
|
add_library(simdjson STATIC external/simdjson/singleheader/simdjson.cpp)
|
||||||
set(SDLMIXER_AU OFF CACHE BOOL "" FORCE)
|
target_include_directories(simdjson SYSTEM PUBLIC external/simdjson/singleheader)
|
||||||
set(SDLMIXER_FLAC_LIBFLAC OFF CACHE BOOL "" FORCE)
|
target_compile_definitions(simdjson PUBLIC SIMDJSON_EXCEPTIONS=0 SIMDJSON_IMPLEMENTATION_ICELAKE=0
|
||||||
set(SDLMIXER_FLAC_DRFLAC OFF CACHE BOOL "" FORCE)
|
SIMDJSON_IMPLEMENTATION_WESTMERE=0)
|
||||||
set(SDLMIXER_GME OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDLMIXER_MOD_XMP OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDLMIXER_MP3_DRMP3 OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDLMIXER_MP3_MPG123 OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDLMIXER_MIDI_FLUIDSYNTH OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDLMIXER_MIDI_TIMIDITY OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDLMIXER_OPUS ON CACHE BOOL "" FORCE)
|
|
||||||
set(SDLMIXER_VOC OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDLMIXER_VORBIS_STB ON CACHE BOOL "" FORCE)
|
|
||||||
set(SDLMIXER_VORBIS_VORBISFILE OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDLMIXER_VORBIS_TREMOR OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDLMIXER_WAVE ON CACHE BOOL "" FORCE)
|
|
||||||
set(SDLMIXER_WAVPACK OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDLMIXER_TEST OFF CACHE BOOL "" FORCE)
|
|
||||||
set(SDLMIXER_INSTALL OFF CACHE BOOL "" FORCE)
|
|
||||||
add_subdirectory(external/SDL_mixer EXCLUDE_FROM_ALL)
|
|
||||||
|
|
||||||
set(PHYSFS_BUILD_SHARED OFF CACHE BOOL "" FORCE)
|
# Dear ImGui: the core, and its SDL3 and OpenGL 3 backends.
|
||||||
set(PHYSFS_BUILD_STATIC ON CACHE BOOL "" FORCE)
|
add_library(imgui STATIC external/imgui/imgui.cpp external/imgui/imgui_draw.cpp external/imgui/imgui_tables.cpp
|
||||||
set(PHYSFS_BUILD_TEST OFF CACHE BOOL "" FORCE)
|
external/imgui/imgui_widgets.cpp external/imgui/backends/imgui_impl_sdl3.cpp
|
||||||
set(PHYSFS_BUILD_DOCS OFF CACHE BOOL "" FORCE)
|
external/imgui/backends/imgui_impl_opengl3.cpp)
|
||||||
set(PHYSFS_BUILD_WX_TEST OFF CACHE BOOL "" FORCE)
|
target_include_directories(imgui SYSTEM PUBLIC external/imgui external/imgui/backends)
|
||||||
set(PHYSFS_ARCHIVE_ZIP ON CACHE BOOL "" FORCE)
|
target_compile_definitions(imgui PUBLIC IMGUI_DISABLE_OBSOLETE_FUNCTIONS IMGUI_DISABLE_DEMO_WINDOWS)
|
||||||
set(PHYSFS_ARCHIVE_7Z OFF CACHE BOOL "" FORCE)
|
target_link_libraries(imgui PUBLIC SDL3::SDL3-static)
|
||||||
set(PHYSFS_ARCHIVE_GRP OFF CACHE BOOL "" FORCE)
|
|
||||||
set(PHYSFS_ARCHIVE_HOG OFF CACHE BOOL "" FORCE)
|
|
||||||
set(PHYSFS_ARCHIVE_PAK OFF CACHE BOOL "" FORCE)
|
|
||||||
set(PHYSFS_ARCHIVE_WAD OFF CACHE BOOL "" FORCE)
|
|
||||||
set(PHYSFS_ARCHIVE_DIR ON CACHE BOOL "" FORCE)
|
|
||||||
set(PHYSFS_DISABLE_INSTALL ON CACHE BOOL "" FORCE)
|
|
||||||
add_subdirectory(external/physfs)
|
|
||||||
|
|
||||||
set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/imgui)
|
file(GLOB SOURCES CONFIGURE_DEPENDS src/*.cpp)
|
||||||
set(TINYXML2_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/tinyxml2)
|
add_executable(shweets_sim ${SOURCES})
|
||||||
set(GLM_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/glm)
|
target_include_directories(shweets_sim SYSTEM PRIVATE external/stb)
|
||||||
set(PHYSFS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/physfs)
|
target_link_libraries(shweets_sim PRIVATE imgui simdjson)
|
||||||
|
target_compile_definitions(shweets_sim PRIVATE "$<$<CONFIG:Debug>:DEBUG>")
|
||||||
|
set_target_properties(shweets_sim PROPERTIES OUTPUT_NAME shweets-sim)
|
||||||
|
|
||||||
set(IMGUI_SOURCES
|
file(CREATE_LINK ${CMAKE_SOURCE_DIR}/resources ${CMAKE_BINARY_DIR}/resources COPY_ON_ERROR SYMBOLIC)
|
||||||
${IMGUI_DIR}/imgui.cpp
|
|
||||||
${IMGUI_DIR}/imgui_draw.cpp
|
|
||||||
${IMGUI_DIR}/imgui_tables.cpp
|
|
||||||
${IMGUI_DIR}/imgui_widgets.cpp
|
|
||||||
${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp
|
|
||||||
${IMGUI_DIR}/backends/imgui_impl_sdl3.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
set (TINYXML2_SOURCES ${TINYXML2_DIR}/tinyxml2.cpp)
|
if(WIN32)
|
||||||
|
set_target_properties(shweets_sim PROPERTIES WIN32_EXECUTABLE ON)
|
||||||
file(GLOB PROJECT_SRC CONFIGURE_DEPENDS
|
target_sources(shweets_sim PRIVATE shweets-sim.rc)
|
||||||
include/*.cpp
|
|
||||||
src/developer/*.cpp
|
|
||||||
src/*.cpp
|
|
||||||
src/render/*.cpp
|
|
||||||
src/resource/*.cpp
|
|
||||||
src/state/*.cpp
|
|
||||||
src/state/play/*.cpp
|
|
||||||
src/state/play/menu/*.cpp
|
|
||||||
src/state/play/item/*.cpp
|
|
||||||
src/state/select/*.cpp
|
|
||||||
src/window/*.cpp
|
|
||||||
src/util/*.cpp
|
|
||||||
src/util/imgui/*.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME}
|
|
||||||
${PROJECT_SRC}
|
|
||||||
${IMGUI_SOURCES}
|
|
||||||
${TINYXML2_SOURCES}
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/glad/glad.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
if(WIN32 AND NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
|
|
||||||
enable_language(RC)
|
|
||||||
target_sources(${PROJECT_NAME} PRIVATE
|
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/shweets-sim.rc"
|
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/Icon.ico"
|
|
||||||
)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
# Emscripten: WebGL2, the resources preloaded, the prefs folder kept in browser storage (web/pre.js), and a page whose
|
||||||
"$<$<CONFIG:Debug>:DEBUG=1>"
|
# canvas fills the window.
|
||||||
"$<$<NOT:$<CONFIG:Debug>>:DEBUG=0>"
|
if(EMSCRIPTEN)
|
||||||
)
|
set_target_properties(shweets_sim PROPERTIES OUTPUT_NAME index SUFFIX ".html")
|
||||||
|
target_link_options(shweets_sim PRIVATE -sALLOW_MEMORY_GROWTH=1 -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2
|
||||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
-lidbfs.js "--preload-file=${CMAKE_SOURCE_DIR}/resources@/resources"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
"--shell-file=${CMAKE_SOURCE_DIR}/web/index.html" "--pre-js=${CMAKE_SOURCE_DIR}/web/pre.js")
|
||||||
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/bin/Debug"
|
|
||||||
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin/Release"
|
|
||||||
)
|
|
||||||
|
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/external/SDL/include
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/external/SDL_mixer/include
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/external
|
|
||||||
${IMGUI_DIR}
|
|
||||||
${IMGUI_DIR}/backends
|
|
||||||
${GLM_DIR}
|
|
||||||
${TINYXML2_DIR}
|
|
||||||
include/glad
|
|
||||||
include/KHR
|
|
||||||
include
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE SDL3::SDL3-static SDL3_mixer::SDL3_mixer-static physfs-static)
|
|
||||||
if (WIN32 AND TARGET SDL3::SDL3main)
|
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE SDL3::SDL3main)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
|
|
||||||
if(MSVC)
|
|
||||||
target_compile_options(${PROJECT_NAME} PRIVATE
|
|
||||||
"$<$<CONFIG:Release>:/O2>"
|
|
||||||
"$<$<CONFIG:Release>:/DNDEBUG>"
|
|
||||||
)
|
|
||||||
if(WIN32)
|
|
||||||
target_link_options(${PROJECT_NAME} PRIVATE
|
|
||||||
"/SUBSYSTEM:WINDOWS"
|
|
||||||
"/ENTRY:mainCRTStartup"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
target_link_options(${PROJECT_NAME} PRIVATE
|
|
||||||
"$<$<CONFIG:Release>:/DEBUG:NONE>"
|
|
||||||
"$<$<CONFIG:Release>:/INCREMENTAL:NO>"
|
|
||||||
"$<$<CONFIG:Release>:/OPT:REF>"
|
|
||||||
"$<$<CONFIG:Release>:/OPT:ICF>"
|
|
||||||
)
|
|
||||||
else()
|
|
||||||
target_compile_options(${PROJECT_NAME} PRIVATE
|
|
||||||
"$<$<CONFIG:Release>:-O3>"
|
|
||||||
"$<$<CONFIG:Release>:-DNDEBUG>"
|
|
||||||
)
|
|
||||||
if(WIN32)
|
|
||||||
target_link_options(${PROJECT_NAME} PRIVATE
|
|
||||||
"-mwindows"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
target_link_options(${PROJECT_NAME} PRIVATE
|
|
||||||
"$<$<CONFIG:Release>:-s>"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(PROJECT_RESOURCES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/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}/*")
|
|
||||||
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}
|
|
||||||
-DSTAMP_FILE=${COPY_RESOURCES_STAMP}
|
|
||||||
-P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/copy_resources.cmake"
|
|
||||||
DEPENDS ${PROJECT_RESOURCE_FILES}
|
|
||||||
"${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)
|
|
||||||
set(HAS_PROJECT_RESOURCES TRUE)
|
|
||||||
else()
|
|
||||||
set(HAS_PROJECT_RESOURCES FALSE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION .)
|
|
||||||
if(HAS_PROJECT_RESOURCES)
|
|
||||||
install(DIRECTORY "${PROJECT_RESOURCES_DIR}/" DESTINATION resources)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
|
|
||||||
set(EMSCRIPTEN_SHELL_FILE "${CMAKE_CURRENT_SOURCE_DIR}/web/index.html")
|
|
||||||
target_link_options(${PROJECT_NAME} PRIVATE
|
|
||||||
"-sMIN_WEBGL_VERSION=2"
|
|
||||||
"-sMAX_WEBGL_VERSION=2"
|
|
||||||
"-sFULL_ES2=1"
|
|
||||||
"-sUSE_OGG=1"
|
|
||||||
"-sUSE_VORBIS=1"
|
|
||||||
"-sALLOW_MEMORY_GROWTH=1"
|
|
||||||
"-sFORCE_FILESYSTEM=1"
|
|
||||||
"-sASYNCIFY"
|
|
||||||
"-lidbfs.js"
|
|
||||||
"--shell-file"
|
|
||||||
"${EMSCRIPTEN_SHELL_FILE}"
|
|
||||||
)
|
|
||||||
if(HAS_PROJECT_RESOURCES)
|
|
||||||
target_link_options(${PROJECT_NAME} PRIVATE
|
|
||||||
"--preload-file"
|
|
||||||
"${PROJECT_RESOURCES_BINARY_DIR}@resources"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
|
||||||
OUTPUT_NAME "index"
|
|
||||||
SUFFIX ".html")
|
|
||||||
|
|
||||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
-DBIN_DIR="$<TARGET_FILE_DIR:${PROJECT_NAME}>"
|
|
||||||
-P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/create_index_zip.cmake"
|
|
||||||
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}")
|
|
||||||
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 6,
|
|
||||||
"cmakeMinimumRequired": {
|
|
||||||
"major": 3,
|
|
||||||
"minor": 27,
|
|
||||||
"patch": 0
|
|
||||||
},
|
|
||||||
"configurePresets": [
|
|
||||||
{
|
|
||||||
"name": "linux-debug",
|
|
||||||
"displayName": "Linux Debug",
|
|
||||||
"description": "Ninja Debug (Linux, native)",
|
|
||||||
"generator": "Ninja",
|
|
||||||
"binaryDir": "${sourceDir}/out/build/linux-debug",
|
|
||||||
"cacheVariables": {
|
|
||||||
"CMAKE_BUILD_TYPE": "Debug",
|
|
||||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/linux-debug"
|
|
||||||
},
|
|
||||||
"condition": {
|
|
||||||
"type": "equals",
|
|
||||||
"lhs": "${hostSystemName}",
|
|
||||||
"rhs": "Linux"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "linux-release",
|
|
||||||
"displayName": "Linux Release",
|
|
||||||
"description": "Ninja Release (Linux, native)",
|
|
||||||
"generator": "Ninja",
|
|
||||||
"binaryDir": "${sourceDir}/out/build/linux-release",
|
|
||||||
"cacheVariables": {
|
|
||||||
"CMAKE_BUILD_TYPE": "Release",
|
|
||||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/linux-release"
|
|
||||||
},
|
|
||||||
"condition": {
|
|
||||||
"type": "equals",
|
|
||||||
"lhs": "${hostSystemName}",
|
|
||||||
"rhs": "Linux"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "win32-debug",
|
|
||||||
"displayName": "Win32 Debug",
|
|
||||||
"description": "Visual Studio 2022 x64 Debug",
|
|
||||||
"generator": "Visual Studio 17 2022",
|
|
||||||
"architecture": "x64",
|
|
||||||
"binaryDir": "${sourceDir}/out/build/win32-debug",
|
|
||||||
"cacheVariables": {
|
|
||||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/win32-debug"
|
|
||||||
},
|
|
||||||
"condition": {
|
|
||||||
"type": "equals",
|
|
||||||
"lhs": "${hostSystemName}",
|
|
||||||
"rhs": "Windows"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "win32-release",
|
|
||||||
"displayName": "Win32 Release",
|
|
||||||
"description": "Visual Studio 2022 x64 Release",
|
|
||||||
"generator": "Visual Studio 17 2022",
|
|
||||||
"architecture": "x64",
|
|
||||||
"binaryDir": "${sourceDir}/out/build/win32-release",
|
|
||||||
"cacheVariables": {
|
|
||||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/win32-release"
|
|
||||||
},
|
|
||||||
"condition": {
|
|
||||||
"type": "equals",
|
|
||||||
"lhs": "${hostSystemName}",
|
|
||||||
"rhs": "Windows"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"buildPresets": [
|
|
||||||
{
|
|
||||||
"name": "linux-debug",
|
|
||||||
"configurePreset": "linux-debug"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "linux-release",
|
|
||||||
"configurePreset": "linux-release"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "win32-debug",
|
|
||||||
"configurePreset": "win32-debug",
|
|
||||||
"configuration": "Debug"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "win32-release",
|
|
||||||
"configurePreset": "win32-release",
|
|
||||||
"configuration": "Release"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "win32-debug",
|
|
||||||
"generator": "Ninja",
|
|
||||||
"configurationType": "Debug",
|
|
||||||
"inheritEnvironments": [
|
|
||||||
"msvc_x64_x64"
|
|
||||||
],
|
|
||||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
|
||||||
"cmakeCommandArgs": "",
|
|
||||||
"buildCommandArgs": "",
|
|
||||||
"ctestCommandArgs": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "win32-release",
|
|
||||||
"generator": "Ninja",
|
|
||||||
"configurationType": "Release",
|
|
||||||
"inheritEnvironments": [
|
|
||||||
"msvc_x64_x64"
|
|
||||||
],
|
|
||||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
|
||||||
"cmakeCommandArgs": "",
|
|
||||||
"buildCommandArgs": "",
|
|
||||||
"ctestCommandArgs": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
+285
-482
@@ -4,526 +4,329 @@ Want to add characters or modify existing ones or mod Shweet's Sim in general or
|
|||||||
|
|
||||||
# Animation Format
|
# 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.
|
Shweet's Sim uses ".anm2" files for tweened character animations, sourced from spritesheets. The format comes from the game _The Binding of Isaac: Rebirth_ (a game I mod on my own time), but the game reads the JSON flavor of it saved by my own animation editor, [Anm2Ed](https://github.com/ShweetsStuff/anm2ed). In Anm2Ed, turn on Save as JSON (Settings → Configure → Serialization), or convert an existing file with `anm2ed convert file.anm2 --format json`. Keep the `.anm2` extension. Isaac's own editor can't open these.
|
||||||
|
|
||||||
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 "Regions", which let you 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.
|
||||||
|
|
||||||
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.
|
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.
|
If you'd like a quick primer on how to use the editor, check either of [these](https://www.youtube.com/watch?v=qU_GMo2l7NY) [videos](https://www.youtube.com/watch?v=QiXVM6Gwzlw) out. They're a little out of date, but much of it still applies.
|
||||||
|
|
||||||
Stage-specific character animations are controlled by `AppendStringForAnimations` on each stage in `character.xml`. The base stage uses no suffix. Later stages append that stage's configured suffix.
|
The game plays spritesheets, regions, layers, nulls, layer groups, events, trigger sounds, and the position, crop, pivot, scale, rotation, tint, color offset, visibility and easing of keyframes. It doesn't play skew, depth, blur, custom shaders or animation layers. Animation groups (the editor's folders) are fine; the game just looks animations up by name, so keep those unique.
|
||||||
|
|
||||||
|
Stage-specific character animations are controlled by `animationSuffix` on each stage in `character.json`. The base stage uses no suffix. Later stages append that stage's suffix.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
- stage 1: `Look`
|
- stage 1: `Look`
|
||||||
- stage 2 with `AppendStringForAnimations="Stage2"`: `LookStage2`
|
- stage 2 with `"animationSuffix": "Stage2"`: `LookStage2`
|
||||||
- stage 5 with `AppendStringForAnimations="Stage5"`: `LookStage5`
|
- stage 5 with `"animationSuffix": "Stage5"`: `LookStage5`
|
||||||
|
|
||||||
Animation references in XML should still use just the base name like `Look`, `Neutral`, or `Eat`. The game appends the current stage's suffix automatically.
|
Animation names in the JSON files should still use just the base name like `Look`, `Neutral`, or `Eat`. The game appends the current stage's suffix automatically.
|
||||||
|
|
||||||
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.
|
Know that despite Anm2Ed supporting sounds for animations, don't expect these to work in context based on names; use the JSON sound lists when applicable. You can add sounds for fluff, though (trigger sounds play).
|
||||||
|
|
||||||
# Resources
|
# Resources
|
||||||
|
|
||||||
There's two folders inside resources; "characters" and "font".
|
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.
|
"font" is just a font containing the font used in the selection screen. That's literally it.
|
||||||
|
|
||||||
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).
|
EVERYTHING ELSE is stored inside character folders in the "characters" folder; one folder per character, named however you like (the folder name is also the name of its save file). 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. A character archive can contain these files in its root:
|
Shweet's Sim uses [JSON](https://en.wikipedia.org/wiki/JSON) files for its data; make sure to brush up on the format. A character folder holds these files:
|
||||||
|
|
||||||
- areas.xml
|
- character.json
|
||||||
- character.xml
|
- items.json
|
||||||
- cursor.xml
|
- dialogue.json
|
||||||
- dialogue.xml
|
|
||||||
- items.xml
|
|
||||||
- menu.xml
|
|
||||||
- strings.xml
|
|
||||||
|
|
||||||
The game currently expects `character.xml`. The rest are optional in the sense that the loader will warn and continue if some are missing, but most real characters will want nearly all of them.
|
`character.json` is required; a folder without one isn't listed. The other two are optional, but most real characters will want both.
|
||||||
|
|
||||||
|
Every path in these files is relative to the character's folder (e.g. `"sfx/blip.ogg"` or `"gfx/items/apple.png"`). Paths inside an .anm2 are relative to the .anm2. Anything left out takes its default, so only write what you need. Wherever sounds are listed, one is picked at random each time.
|
||||||
|
|
||||||
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.
|
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
|
# character.json
|
||||||
|
|
||||||
## 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.
|
|
||||||
#### Anm2RootPath (path)
|
|
||||||
Working folder/directory of anm2 files the areas will use. If omitted, TextureRootPath is used.
|
|
||||||
#### WorldBoundsNull (string)
|
|
||||||
The area ANM2 null used to set world bounds. Defaults to `Bounds`.
|
|
||||||
#### ItemSpawnNull (string)
|
|
||||||
The area ANM2 null used to choose random item spawn positions. Items can spawn at any point inside this null rect. If omitted or invalid, WorldBoundsNull is used.
|
|
||||||
|
|
||||||
#### Area
|
|
||||||
#### Texture (path)
|
|
||||||
The file path of the area's texture (background).
|
|
||||||
#### Anm2 (path)
|
|
||||||
The file path of the area's animated background. If this is set, it is used instead of Texture.
|
|
||||||
#### 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.
|
|
||||||
|
|
||||||
## character.xml
|
|
||||||
This is the main character file where much of the functionality is stored.
|
This is the main character file where much of the functionality is stored.
|
||||||
|
|
||||||
### Character
|
```json
|
||||||
#### Name (string)
|
{
|
||||||
Name of the character; will appear in dialogue, stats, etc.
|
"name": "Snivy",
|
||||||
#### TextureRootPath (path)
|
"anm2": "gfx/character/snivy.anm2",
|
||||||
Working folder/directory of where used textures will be contained within.
|
"weight": 8.1,
|
||||||
#### SoundRootPath (path)
|
"stages": [{"weight": 10, "dialoguePool": "Stage2", "animationSuffix": "Stage2"}],
|
||||||
Working folder/directory of where used sounds will be contained within.
|
"animations": {"start": "Appear", "idle": "Neutral", "idleFull": "NeutralFull", "stageUp": "StageUp"},
|
||||||
#### Render (path)
|
"sounds": {"digest": ["sfx/digest1.ogg"], "gurgle": ["sfx/gurgle1.ogg", "sfx/gurgle2.ogg"]}
|
||||||
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.
|
|
||||||
#### Credits (string)
|
|
||||||
Credits text shown in the Select screen's Credits tab.
|
|
||||||
#### WeightKilograms (double)
|
|
||||||
The character's starting weight, in kilograms.
|
|
||||||
#### WeightMaxKilograms (double)
|
|
||||||
Maximum weight the character can reach.
|
|
||||||
#### CapacityCalories (double)
|
|
||||||
The character's starting capacity, in calories.
|
|
||||||
#### CapacityMinCalories (double)
|
|
||||||
The character's minimum capacity, in calories.
|
|
||||||
#### CapacityMaxCalories (double)
|
|
||||||
Maximum base capacity.
|
|
||||||
#### CapacityOverstuffedMultiplier (double)
|
|
||||||
Multiplier used when computing the effective full threshold from current base capacity.
|
|
||||||
#### CapacityIfOverStuffedOnDigestBonus (double, multiplier)
|
|
||||||
Additional capacity gained on digest when the character was over base capacity.
|
|
||||||
#### CaloriesToKilogram (double)
|
|
||||||
Determines how many calories become a kilogram (1 cal -> X kg).
|
|
||||||
#### DigestionRatePerSecond (double, percent/second)
|
|
||||||
The base digestion rate, in percent per second.
|
|
||||||
#### DigestionRatePerSecondMin (double, percent/second)
|
|
||||||
The minimum digestion rate for the character, in percent per second.
|
|
||||||
#### DigestionRatePerSecondMax (double, percent/second)
|
|
||||||
The maximum digestion rate for the character, in percent per second.
|
|
||||||
#### 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.
|
|
||||||
#### BlinkChance (double, percent)
|
|
||||||
Chance each tick to trigger a blink override.
|
|
||||||
#### GurgleChance (double, percent)
|
|
||||||
Determines how often the character will gurgle (see the Gurgle sounds later) per tick.
|
|
||||||
#### GurgleChanceFromCapacityMultiplier (double)
|
|
||||||
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.
|
|
||||||
#### DialoguePool (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.
|
|
||||||
#### ColorR, ColorG, ColorB (float, 0-1)
|
|
||||||
UI accent color for the character.
|
|
||||||
### 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.
|
|
||||||
#### ThresholdKilograms (float)
|
|
||||||
The weight threshold to reach this stage, in kilograms.
|
|
||||||
#### AreaID (int)
|
|
||||||
Reserved for background switching. Parsed, but not currently used by gameplay.
|
|
||||||
#### DialoguePool (string)
|
|
||||||
Determines the stage's dialogue options (for the "How are you feeling?" option in Chat). Also see dialogue.xml for how "Pools" work.
|
|
||||||
#### AppendStringForAnimations (string)
|
|
||||||
Suffix appended to base animation names while this stage is active. Usually `Stage2`, `Stage3`, etc.
|
|
||||||
### 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.
|
|
||||||
### AnimationOverrides
|
|
||||||
"AnimationOverrides" 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.
|
|
||||||
### Talk / Blink
|
|
||||||
#### 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.
|
|
||||||
#### EventToActivate (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.
|
|
||||||
#### ScaleMultiplierMaximum (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.
|
|
||||||
### InteractAreaTypes
|
|
||||||
Default behavior for each interaction type. These entries should not have `Null`; they define shared cursor animations, sounds, particles, and bonuses for concrete `InteractAreas`.
|
|
||||||
### InteractArea
|
|
||||||
#### Type (string)
|
|
||||||
The interaction type name. Concrete interact areas with the same `Type` inherit unspecified values from this definition.
|
|
||||||
#### GlyphFromIconFont (string)
|
|
||||||
The icon-font glyph used for this interaction in the tools bar.
|
|
||||||
#### IsHold (bool)
|
|
||||||
If true, the interaction is held rather than clicked once.
|
|
||||||
#### 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.
|
|
||||||
#### DigestionBonusOnHover (float)
|
|
||||||
Digestion bonus applied repeatedly while hovering with a held interaction.
|
|
||||||
#### DigestionBonusOnClick (float)
|
|
||||||
Digestion bonus applied when a click-based interaction lands.
|
|
||||||
#### TimeTicks (float)
|
|
||||||
Used by click effects such as smacks. If omitted, the engine uses a short default duration.
|
|
||||||
#### TimeToActivateNewDialogueTicks (int)
|
|
||||||
Ticks that must pass without this interact area being interacted with before it can start a new dialogue.
|
|
||||||
#### ScaleEffectAmplitude (float)
|
|
||||||
Visual scale effect amount for click interactions.
|
|
||||||
#### ScaleEffectCycles (float)
|
|
||||||
How many oscillations the scale effect performs.
|
|
||||||
### InteractAreas
|
|
||||||
The concrete areas on a character which can be interacted with; usually for belly rubs, kisses, etc.
|
|
||||||
### InteractArea
|
|
||||||
#### Type (string)
|
|
||||||
The interaction type to use. This should match one of the entries in `InteractAreaTypes`.
|
|
||||||
#### Null (string)
|
|
||||||
The null in which the interact area can be triggered. Again, review the .anm2 format.
|
|
||||||
#### Layer (string; optional)
|
|
||||||
Layer used for scale-effect interactions such as smacks.
|
|
||||||
#### DialoguePool (string)
|
|
||||||
The dialogue pool which will be drawn from when activating an interact area (see dialogue.xml).
|
|
||||||
#### DialoguePoolFull (string)
|
|
||||||
Dialogue pool used when the character is over capacity.
|
|
||||||
### 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.
|
|
||||||
### Particle
|
|
||||||
An interact area can trigger multiple particle effects.
|
|
||||||
#### Type (string)
|
|
||||||
The particle type from `particles.xml`.
|
|
||||||
### Actions
|
|
||||||
Sounds that play in character-level contexts. Each action can contain multiple `Sound` elements.
|
|
||||||
### 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 GurgleChanceFromCapacityMultiplier above.
|
|
||||||
### Sound
|
|
||||||
#### Path (path)
|
|
||||||
The path of the sound for the specific action.
|
|
||||||
|
|
||||||
## cursor.xml
|
| Key | Type | Meaning |
|
||||||
Determines the cursor appearance and behavior.
|
|---|---|---|
|
||||||
### TextureRootPath (path)
|
| `name` | string | Name of the character; will appear in dialogue, stats, etc. Defaults to the folder name. |
|
||||||
Working folder/directory of textures the cursor will use.
|
| `description` | string | A general description of the character; will show in the Select screen. |
|
||||||
#### SoundRootPath (path)
|
| `credits` | string | Credits text shown in the Select screen's Credits tab. |
|
||||||
Working folder/directory of sounds the cursor will use.
|
| `render` | path | Texture for the character's "render" (i.e., a typical full-body display), will show in the Select screen. |
|
||||||
#### Anm2 (path)
|
| `portrait` | path | Texture for the character's "portrait" (i.e., a cropped profile view), will show in the Select screen. |
|
||||||
The anm2 file the cursor will use (depends on TextureRootPath).
|
| `anm2` | path | The character's anm2 file; should have all the character's animations. |
|
||||||
### Animations
|
| `color` | [r, g, b], 0-1 | UI accent color for the character. |
|
||||||
### Idle
|
| `weight` | float, kg | The character's starting weight. |
|
||||||
The cursor's default idle animation.
|
| `weightMax` | float, kg | Maximum weight the character can reach. |
|
||||||
### Hover
|
| `capacity` | float, kcal | The character's starting capacity. |
|
||||||
The cursor's hover animation, when hovering over an item.
|
| `capacityMin`, `capacityMax` | float, kcal | The range capacity stays within. |
|
||||||
### Grab
|
| `overstuffedMultiplier` | float | The character can eat past capacity up to capacity times this. |
|
||||||
The cursor's grab animation, when grabbing an item.
|
| `overstuffedDigestBonus` | float | Additional capacity gained on digest when the character was over capacity (scaled by how far over). |
|
||||||
### Pan
|
| `caloriesToKilogram` | float | How many kilograms each calorie digested becomes. |
|
||||||
The cursor's pan animation, when panning with middle mouse button.
|
| `digestionRate` | float, %/second | The base digestion rate. |
|
||||||
### Zoom
|
| `digestionRateMin`, `digestionRateMax` | float, %/second | The range the digestion rate stays within. |
|
||||||
The cursor's zoom animation, when zooming in/out with the mouse wheel.
|
| `digestionTime` | 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. |
|
||||||
### Return
|
| `blinkChance` | float, % | Chance each tick to blink. |
|
||||||
The cursor's return animation, when holding right click on a spawned item.
|
| `gurgleChance` | float | How often the character will gurgle (see `sounds`) per tick, scaled by how full they are. |
|
||||||
#### Animation (string)
|
| `gurgleCapacityMultiplier` | float | Multiplies the gurgle chance at full capacity. Higher capacity = higher gurgle chance. |
|
||||||
The animation the cursor will use in these contexts.
|
| `dialoguePool` | string | The character's base dialogue pool for "How are you feeling?". This is effectively "Stage 1"'s dialogue; each stage should have its own dialogue pool. |
|
||||||
### Actions
|
|
||||||
Know that multiple sounds can be defined for each action by adding `Sound` children.
|
|
||||||
### 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 (path)
|
|
||||||
The path of the sound for these respective contexts.
|
|
||||||
|
|
||||||
## dialogue.xml
|
### alternate
|
||||||
The collection of character dialogue; likely the biggest file, depending on your needs.
|
The alternate spritesheet of the character, if applicable (in Pokemon terms, the "shiny").
|
||||||
### Dialogue
|
|
||||||
#### SoundRootPath (path)
|
|
||||||
Working folder/directory of where dialogue sounds will be contained within.
|
|
||||||
#### DelayTicks (int)
|
|
||||||
How many update ticks pass before the next dialogue character is revealed.
|
|
||||||
#### BlipDelayTicks (int)
|
|
||||||
How many displayed dialogue characters elapse between blip sounds. `3` means every third displayed character.
|
|
||||||
### TextPlayback
|
|
||||||
Groups the dialogue text reveal settings.
|
|
||||||
### Blip
|
|
||||||
Dialogue blip sound. One is played periodically while text reveals. Add one or more `Sound` children.
|
|
||||||
### Sound
|
|
||||||
#### Path (path)
|
|
||||||
The path to the sound that will play, based on SoundRootPath.
|
|
||||||
### DelayCharacters
|
|
||||||
Groups character-specific delay rules.
|
|
||||||
### DelayCharacter
|
|
||||||
#### Character (string)
|
|
||||||
The character that should add an extra delay while text reveals.
|
|
||||||
#### DelayTicks (int)
|
|
||||||
Extra update ticks to pause after this character.
|
|
||||||
#### IsDisableTalk (bool)
|
|
||||||
If true, the talking override pauses during this character's delay.
|
|
||||||
### Entries
|
|
||||||
### Entry
|
|
||||||
Each bit of dialogue is referred to as an "entry".
|
|
||||||
#### Label (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.
|
|
||||||
### Effects
|
|
||||||
Dialogue effects can be added as child elements of an `Entry`. `Small` and `Big` are fixed text scale effects. `Silent` suppresses blips and character talking for its range. `Oscillation`, `Shake`, and `Screenshake` use engine defaults for their strength/timing values; do not write `Amplitude`, `Frequency`, `Period`, `Magnitude`, or `TimeTicks` for those effects. `Scene` hides windows and items and disables interaction until the active dialogue chain reaches and finishes its terminal entry. If another `Scene` appears while one is active, it is ignored. `Scale` and `Rainbow` are no longer supported.
|
|
||||||
### 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
|
|
||||||
#### Label (string)
|
|
||||||
The name of the dialogue pool; can be referenced elsewhere.
|
|
||||||
### Entry
|
|
||||||
#### Label (string)
|
|
||||||
The name of the entry; to be added to the pool.
|
|
||||||
|
|
||||||
### Hooks
|
| Key | Type | Meaning |
|
||||||
Groups dialogue hooks. Hooks connect gameplay events to either a specific entry or a pool.
|
|---|---|---|
|
||||||
### Hook
|
| `texture` | path | The alternate spritesheet texture. |
|
||||||
#### Type (string)
|
| `sound` | path | Plays when the spritesheet is set to alternate (either on new game, or with an item that has `isToggleSpritesheet`). |
|
||||||
The gameplay hook name. Current hook names include `Start`, `End`, `Help`, `StageUp`, `Random`, `Feed`, `FeedFull`, `Eat`, `EatFull`, `Full`, `Throw`, `LowCapacity`, `Digest`, `FoodTaken`, and `FoodTakenFull`.
|
| `spritesheet` | int | The spritesheet of the character's anm2 it replaces. Defaults to 0. |
|
||||||
#### Entry (string)
|
| `chance` | float, % | Chance of rolling the alternate spritesheet when starting a new game. |
|
||||||
The entry to play for this hook.
|
|
||||||
#### Pool (string)
|
|
||||||
The pool to draw from for this hook.
|
|
||||||
#### Value (int)
|
|
||||||
Optional hook-specific value. `StageUp` uses the displayed stage number. For a matching hook type, value-specific hooks override general hooks, then entry hooks override pool hooks.
|
|
||||||
|
|
||||||
## items.xml
|
### stages
|
||||||
Has all items and their parameters, alongside additional behavior.
|
A "stage" represents each visual change of the character as the weight increases. By default, there's always one stage; each entry in this list adds another.
|
||||||
|
|
||||||
### ItemSchema
|
| Key | Type | Meaning |
|
||||||
#### TextureRootPath (path)
|
|---|---|---|
|
||||||
Working folder/directory of where used textures will be contained within.
|
| `weight` | float, kg | The weight threshold to reach this stage. |
|
||||||
#### SoundRootPath (path)
|
| `dialoguePool` | string | The stage's dialogue pool for "How are you feeling?". |
|
||||||
Working folder/directory of where used sounds will be contained within.
|
| `animationSuffix` | string | Suffix appended to base animation names while this stage is active. Usually `Stage2`, `Stage3`, etc. |
|
||||||
#### BaseAnm2 (path)
|
| `area` | int | Switches to this area (from `areas.list`) from this stage on. |
|
||||||
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.
|
|
||||||
#### BaseSpawnDelayTicks (int)
|
|
||||||
Base cooldown before another item can be spawned.
|
|
||||||
#### ItemSpawnLimit (int)
|
|
||||||
Maximum number of spawned world items at once.
|
|
||||||
#### SpawnDelayStageMultiplier (float)
|
|
||||||
Multiplies the spawn delay once per character stage. Values below `1.0` make item spawning faster as stage increases.
|
|
||||||
### Actions
|
|
||||||
The various item actions. Each action can contain multiple `Sound` elements to randomly play from a selection.
|
|
||||||
### Bounce
|
|
||||||
The sound that will play when an item is bounced (hitting a floor/wall/ceiling with velocity).
|
|
||||||
### Dispose
|
|
||||||
The sound that will play when an item is disposed of.
|
|
||||||
### Summon
|
|
||||||
The sound that will play when an item is spawned.
|
|
||||||
### NewItem
|
|
||||||
The sound that will play when an item is discovered in the Catalog for the first time.
|
|
||||||
### Return
|
|
||||||
The sound that will play when an item is returned.
|
|
||||||
### Sound
|
|
||||||
#### Path (path)
|
|
||||||
The path to the sound that will play, based on SoundRootPath.
|
|
||||||
### Categories
|
|
||||||
The kinds of items present. Will appear in Catalog tooltips.
|
|
||||||
### Category
|
|
||||||
#### Name (string)
|
|
||||||
The name of the category.
|
|
||||||
#### UseMode (string)
|
|
||||||
Controls how items in this category are used after spawning. `Edible` marks food that can be eaten. `Tool` marks non-food tools.
|
|
||||||
### 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 the Catalog and each can have their own drop chance.
|
|
||||||
### Rarity
|
|
||||||
#### Name (string)
|
|
||||||
Name of the rarity.
|
|
||||||
#### Chance (float, percent)
|
|
||||||
Relative rarity weight for item systems.
|
|
||||||
#### Sound (path)
|
|
||||||
Sound that will play when an item of this rarity is awarded.
|
|
||||||
#### IsHiddenInCatalog (bool)
|
|
||||||
If true, this rarity is marked as hidden in the Catalog. Undiscovered items still appear as unknown silhouettes.
|
|
||||||
### Items
|
|
||||||
#### TextureRootPath (path)
|
|
||||||
Working folder/directory of where used item textures will be contained within.
|
|
||||||
### 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).
|
|
||||||
#### CapacityBonus (float; optional)
|
|
||||||
Capacity increase granted as the item is eaten.
|
|
||||||
#### DigestionBonus (float, percent; optional)
|
|
||||||
The additional digestion rate in percent the item will give if eaten.
|
|
||||||
#### Gravity (float; optional)
|
|
||||||
The item's gravity; will use the default gravity if not available.
|
|
||||||
#### ColorR, ColorG, ColorB (float, 0-1; optional)
|
|
||||||
Optional item UI color. Missing components default to `0` if any color component is provided.
|
|
||||||
#### IsToggleSpritesheet (bool; optional)
|
|
||||||
When used as a tool, will toggle the character's spritesheet (in Pokemon terms, toggling normal/shiny palettes).
|
|
||||||
|
|
||||||
## strings.xml
|
### animations
|
||||||
Character-specific text strings.
|
The character's animations. Know that a lot of character animations are typically easily played through dialogue; this is just for animations that aren't reliant on that system. These are just the base names.
|
||||||
See [`src/resource/xml/strings.hpp`](src/resource/xml/strings.hpp) for how strings map to in-game displays.
|
|
||||||
|
|
||||||
## menu.xml
|
| Key | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `start` | Plays first 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 goes over a stage's threshold, this animation will play. |
|
||||||
|
|
||||||
|
### talk, blink
|
||||||
|
"talk" and "blink" are animations tweaked by the game for effect. Typically, each animation will have an invisible blinking/talking layer (just the graphic that blinks), which will change when called for. 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.
|
||||||
|
|
||||||
|
| Key | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `source` | The layer in the animation which will be sourced. |
|
||||||
|
| `destination` | 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.
|
||||||
|
|
||||||
|
| Key | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `null` | The null in the animation where food will be checked for. |
|
||||||
|
| `animation` | The animation that will play when food is dragged to it. |
|
||||||
|
| `event` | The event that will be checked for, determining the time the food is actually eaten/chewed. |
|
||||||
|
|
||||||
|
### expandAreas
|
||||||
|
The areas which will expand based on how full the character is and their progress to the next stage (50/50); usually a stomach. This adds scale onto a layer and a null of every animation.
|
||||||
|
|
||||||
|
| Key | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `layer` | The layer that will expand. |
|
||||||
|
| `null` | The null that will expand. |
|
||||||
|
| `scaleMax` | The scale (in percent) that will be added at maximum. Know that a scale of 100 is the normal scale of an animation. |
|
||||||
|
|
||||||
|
### interactTypes, interactAreas
|
||||||
|
`interactTypes` is an object of interaction types by name (each gets a button in the tools bar), giving each type's defaults. `interactAreas` is a list of the concrete areas on a character which can be interacted with; usually for belly rubs, kisses, etc. Each area names its `type` and takes all of that type's settings, changing whichever it gives itself.
|
||||||
|
|
||||||
|
| Key | Type | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `type` | string | (Areas only) The interaction type to use. |
|
||||||
|
| `null` | string | (Areas) The null in which the interact area can be triggered. |
|
||||||
|
| `layer` | string | The layer that the scale effect (e.g. smacks) applies to. |
|
||||||
|
| `glyph` | string | The icon-font glyph used for this type in the tools bar. |
|
||||||
|
| `isHold` | bool | If true, the interaction is held rather than clicked once. |
|
||||||
|
| `cursorHover` | string | The animation the cursor will play when hovering over the interact area. |
|
||||||
|
| `cursorActive` | string | The animation the cursor will play when interacting. |
|
||||||
|
| `animation` | string | A character animation to play on interacting, when the area has no dialogue pool. |
|
||||||
|
| `idleOnEnd` | string | The idle animation the character uses after interacting (e.g. turning around). |
|
||||||
|
| `dialoguePool` | string | The dialogue pool which will be drawn from when interacting. |
|
||||||
|
| `dialoguePoolFull` | string | Dialogue pool used when the character is over capacity. |
|
||||||
|
| `dialogueCooldown` | int, ticks | Ticks before interacting can start new dialogue again. |
|
||||||
|
| `digestionClick` | float, % | Digestion added each click. |
|
||||||
|
| `digestionHover` | float, % | Digestion added each tick the cursor moves while held. |
|
||||||
|
| `scaleAmplitude` | float, % | Size of the scale effect on clicking. |
|
||||||
|
| `scaleCycles` | float | How many oscillations the scale effect performs. Defaults to 1. |
|
||||||
|
| `scaleTime` | float, ticks | How long the scale effect lasts. Defaults to 20. |
|
||||||
|
| `sounds` | paths | Sounds played when interacting. |
|
||||||
|
| `particles` | strings | Particles (see `particles`) spawned when interacting. |
|
||||||
|
|
||||||
|
### sounds
|
||||||
|
| Key | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `digest` | Plays when the character begins digesting (digestion bar full). |
|
||||||
|
| `gurgle` | Ambient sounds that play randomly based on capacity; see `gurgleChance`. |
|
||||||
|
|
||||||
|
### areas
|
||||||
|
"Areas" refers to the backgrounds of the game. Stages can switch between them (see `stages`).
|
||||||
|
|
||||||
|
| Key | Type | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `anm2` | path | The anm2 the areas use. |
|
||||||
|
| `boundsNull` | string | The null setting the world's bounds. Defaults to `Bounds`. |
|
||||||
|
| `spawnNull` | string | The null items spawn at random points within. If omitted or missing, `boundsNull` is used. |
|
||||||
|
| `list` | list | The areas. Each can set its own `anm2`, its `gravity` (applied to items' velocities per tick), `friction` (applied when items are grounded or hit walls) and `airResistance`. |
|
||||||
|
|
||||||
|
### cursor
|
||||||
|
Determines the cursor appearance and behavior. Without one, the system cursor is used.
|
||||||
|
|
||||||
|
| Key | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `anm2` | The anm2 file the cursor will use. |
|
||||||
|
| `animations` | `idle`, `click`, `hover` (over an item), `grab` (an item), `pan` (with middle mouse), `zoom` (with the mouse wheel) and `dispose` (right clicking an item). |
|
||||||
|
| `sounds` | `grab` (grabbing an item), `release` (letting go) and `throw` (letting go while dragging quickly). |
|
||||||
|
| `particles` | `grab`: particles spawned when grabbing an item. |
|
||||||
|
|
||||||
|
### menu
|
||||||
Determines menu and general UI appearance and behavior.
|
Determines menu and general UI appearance and behavior.
|
||||||
|
|
||||||
### Menu
|
| Key | Meaning |
|
||||||
#### SoundRootPath (path)
|
|---|---|
|
||||||
Working folder/directory of where used sounds will be contained within.
|
| `font` | The font the menu will use. |
|
||||||
#### FontRootPath (path)
|
| `iconsFont` | The font holding the tools bar's glyphs. |
|
||||||
Working folder/directory of where used fonts will be contained within.
|
| `rounding` | The rounding of corners the UI will use. |
|
||||||
#### Font (path)
|
| `homeGlyph` | The glyph of the tools bar's Home button. |
|
||||||
The font the menu will use, based on FontRootPath.
|
| `sounds` | `open` and `close` (a sliding panel), `hover` and `select` (a widget), and `cheats` (after entering a special code to activate cheats). |
|
||||||
#### ButtonRounding (float)
|
|
||||||
The rounding of corners the UI will use.
|
### particles
|
||||||
### Actions
|
An object of particles by name, each with the `anm2` and `animation` it plays once where spawned.
|
||||||
Menu sound actions. Each action can contain one or more `Sound` elements.
|
|
||||||
### Open
|
### strings
|
||||||
Will play when opening a sliding menu panel.
|
Replaces UI text, by name. See [`src/strings.h`](src/strings.h) for every name and its default, e.g. `"strings": {"MenuTabInventory": "Pantry"}`.
|
||||||
### Close
|
|
||||||
Will play when closing a sliding menu panel.
|
# items.json
|
||||||
### Hover
|
|
||||||
Will play when hovering over a widget in a menu.
|
Has all items and their parameters, alongside additional behavior.
|
||||||
### Select
|
|
||||||
Will play when clicking on a widget in a menu.
|
```json
|
||||||
### CheatsActivated
|
{
|
||||||
Sound that will play after entering a special code to activate cheats.
|
"anm2": "gfx/items/item.anm2",
|
||||||
### Sound
|
"spawnDelay": 600,
|
||||||
#### Path (path)
|
"categories": {"Food": "edible", "Tool": "tool"},
|
||||||
The sound that will play, based on SoundRootPath.
|
"rarities": {"Common": {"weight": 1, "sound": "sfx/common.ogg"}},
|
||||||
|
"items": [
|
||||||
|
{"name": "Apple", "texture": "gfx/items/apple.png", "category": "Food", "rarity": "Common", "calories": 95}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Key | Type | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `anm2` | path | The base anm2 file that items will use. |
|
||||||
|
| `spritesheet` | int | The spritesheet of the anm2 that an item's `texture` replaces. Defaults to 0. |
|
||||||
|
| `spawnDelay` | int, ticks | Base cooldown before another item can be spawned. |
|
||||||
|
| `spawnDelayMultiplier` | float | Multiplies the spawn delay once per character stage. Values below `1.0` make item spawning faster as stage increases. |
|
||||||
|
| `spawnLimit` | int | Maximum number of items in the world at once. |
|
||||||
|
| `sounds` | | `bounce` (an item hitting a floor/wall/ceiling), `dispose` (an item disposed of), `summon` (an item spawned) and `newItem` (an item found for the first time). |
|
||||||
|
| `particles` | | `dispose` and `summon`. |
|
||||||
|
| `categories` | object | The kinds of items, by name; will appear in Catalog tooltips. Each says how its items are used: `edible` (food that can be eaten) or `tool`. |
|
||||||
|
| `rarities` | object | Rarities, by name. Each has a `weight` (relative chance of spawning) and a `sound` that plays when one spawns. |
|
||||||
|
|
||||||
|
### items
|
||||||
|
| Key | Type | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `name` | string | The name of the item. Saves keep items by name. |
|
||||||
|
| `description` | string | Flavor text for the item. |
|
||||||
|
| `texture` | path | The texture the item uses. |
|
||||||
|
| `anm2` | path | The item's own anm2, if needed. |
|
||||||
|
| `category` | string | The item's category. |
|
||||||
|
| `rarity` | string | The item's rarity. |
|
||||||
|
| `flavor` | string | Flavor text (for food); honestly has no meaning but whatever. |
|
||||||
|
| `calories` | float | The amount of calories the item has (if food). |
|
||||||
|
| `capacityBonus` | float | Capacity increase granted as the item is eaten. |
|
||||||
|
| `digestionBonus` | float, %/second | The digestion rate added as the item is eaten. |
|
||||||
|
| `gravity` | float | The item's gravity; will use the area's if not given. |
|
||||||
|
| `color` | [r, g, b], 0-1 | Optional UI color for the item. |
|
||||||
|
| `isToggleSpritesheet` | bool | When used as a tool on the character, will toggle the character's spritesheet (in Pokemon terms, toggling normal/shiny palettes). |
|
||||||
|
|
||||||
|
# dialogue.json
|
||||||
|
|
||||||
|
The collection of character dialogue; likely the biggest file, depending on your needs.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"delay": 2,
|
||||||
|
"blipDelay": 2,
|
||||||
|
"blip": ["sfx/blip.ogg"],
|
||||||
|
"pauses": {".": {"delay": 5}},
|
||||||
|
"hooks": {"Start": "Start", "Eat": "Eat"},
|
||||||
|
"pools": {"Eat": ["Eat1", "Eat2"]},
|
||||||
|
"entries": {
|
||||||
|
"Start": {
|
||||||
|
"text": "Hey there, human!",
|
||||||
|
"animation": "Smug",
|
||||||
|
"choices": [{"text": "Hi!", "next": "Start2"}],
|
||||||
|
"effects": [{"type": "shake", "start": 4, "end": 9}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Key | Type | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `delay` | int, ticks | How many ticks pass before the next dialogue character is revealed. |
|
||||||
|
| `blipDelay` | int | How many displayed dialogue characters pass between blip sounds. `3` means every third displayed character. |
|
||||||
|
| `blip` | paths | Dialogue blip sounds, played periodically while text reveals. |
|
||||||
|
| `pauses` | object | Extra `delay` (in ticks) after particular characters. With `isSilent`, talking and blips also pause during it. |
|
||||||
|
| `hooks` | object | Connects gameplay events to a pool (or else an entry) by name. The hook names are `Start`, `End`, `Help`, `StageUp`, `Random`, `Feed`, `FeedFull`, `Eat`, `EatFull`, `Full`, `Throw`, `LowCapacity`, `Digest`, `FoodTaken` and `FoodTakenFull`. `Random` and `Help` add the "Let's chat!" and "Help" buttons. |
|
||||||
|
| `pools` | object | A "pool" of dialogue is a list of entry labels, one picked at random when used. |
|
||||||
|
| `entries` | object | Each bit of dialogue is referred to as an "entry", by its label. |
|
||||||
|
|
||||||
|
### entries
|
||||||
|
| Key | Type | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `text` | string | The actual dialogue content of the entry. Supports Unicode, provided the font contains the characters. An entry without text just plays its animation. |
|
||||||
|
| `animation` | string | A character animation that will play at the beginning of the dialogue. Again, just the base name for the animation, no stage numbers. |
|
||||||
|
| `next` | string | The label of the entry that will follow this one. If no next entry, the text will not continue. Make sure to connect these. |
|
||||||
|
| `choices` | list | Dialogue can be branching; each choice has its `text` (shown as a button) and the `next` entry it leads to. |
|
||||||
|
| `effects` | list | Effects over the text, each with a `type`, and a `start` and `end` (the characters it covers, from `start` up to `end`). |
|
||||||
|
|
||||||
|
### effects
|
||||||
|
| Type | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `color` | Colors the text, by `color` ([r, g, b], 0-1). |
|
||||||
|
| `big`, `small` | Fixed text scales. |
|
||||||
|
| `oscillation`, `shake` | Moves the text, with engine defaults. |
|
||||||
|
| `silent` | Suppresses blips and character talking for its range. |
|
||||||
|
| `screenshake` | Shakes the screen when its `start` comes up. |
|
||||||
|
| `sound` | Plays its `sound` (a path) when its `start` comes up. |
|
||||||
|
| `animation` | Plays its `animation` when its `start` comes up. |
|
||||||
|
| `delay` | Changes the reveal speed to its `delay` (in ticks) from its `start` on. |
|
||||||
|
| `scene` | Hides windows and items and disables interaction until the dialogue chain reaches and finishes its last entry. If another scene appears while one is active, it is ignored. |
|
||||||
|
|
||||||
# Saves
|
# 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.
|
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. Each is a plain text file of `key=value` lines.
|
||||||
|
|
||||||
## settings.xml
|
## settings.txt
|
||||||
Stores general game settings and configuration; beyond invididual characters.
|
Stores general game settings and configuration; beyond individual characters.
|
||||||
### MeasurementSystem ("Imperial", "Metric")
|
|
||||||
Determines measurement system (kg/g or lb/oz).
|
|
||||||
### Volume (int, percent)
|
|
||||||
Master volume.
|
|
||||||
### IsAudioMuted (bool)
|
|
||||||
If true, all audio is muted.
|
|
||||||
### ColorR, ColorG, ColorB (float, 0-1)
|
|
||||||
Red/Green/Blue components of menu color.
|
|
||||||
### WindowX, WindowY (int)
|
|
||||||
Window position.
|
|
||||||
### WindowW/H (int)
|
|
||||||
Window size.
|
|
||||||
|
|
||||||
## *.save
|
| Key | Meaning |
|
||||||
Saves are per-character; will be named "[blank].save", stored in "saves" folder, from the name of the character's archive.
|
|---|---|
|
||||||
|
| `measurement` | `0` for metric (kg/g), `1` for imperial (lb/oz). |
|
||||||
|
| `volume` | Master volume, in percent. |
|
||||||
|
| `mute` | `1` if all audio is muted. |
|
||||||
|
| `color` | Red, green and blue (0-1) of the menu color. |
|
||||||
|
| `character_color` | `1` to use the character's color while playing. |
|
||||||
|
| `width`, `height` | Window size. |
|
||||||
|
| `x`, `y` | Window position. |
|
||||||
|
| `last_character` | The character being played at exit, which the game continues on start. |
|
||||||
|
| `console` | `1` enables the developer console (the grave key). |
|
||||||
|
|
||||||
### Save
|
## saves/<character>.txt
|
||||||
#### IsPostgame (bool)
|
A character's progress: their weight, calories, capacity, digestion, totals and playtime, the camera, and the items found (`discovered=` lines) and lying in the world (`item=` lines, as position, velocity, rotation and name).
|
||||||
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
|
|
||||||
#### WeightKilograms (float)
|
|
||||||
Character's current weight, in kilograms.
|
|
||||||
#### Calories (float)
|
|
||||||
Character's current consumed calories.
|
|
||||||
#### CapacityCalories (float)
|
|
||||||
Character's capacity, in calories. Remember, max capacity effectively is capacity * CapacityOverstuffedMultiplier (from character.xml)
|
|
||||||
#### DigestionRatePerSecond (float, percent/second)
|
|
||||||
Character's digestion rate in percent per second.
|
|
||||||
#### 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.
|
|
||||||
#### TotalItemsSpawned (int)
|
|
||||||
How many items have been spawned, per save file.
|
|
||||||
#### TotalItemsConsumed (int)
|
|
||||||
How many items have been completely consumed by the character, per save file.
|
|
||||||
### Items
|
|
||||||
World items currently spawned outside the Catalog.
|
|
||||||
#### ID (int)
|
|
||||||
Item ID from `items.xml`.
|
|
||||||
#### PositionX, PositionY (float)
|
|
||||||
World position.
|
|
||||||
#### VelocityX, VelocityY (float)
|
|
||||||
World velocity.
|
|
||||||
### Discovered
|
|
||||||
Catalog item IDs that have been discovered.
|
|
||||||
#### ID (int)
|
|
||||||
Item ID from `items.xml`.
|
|
||||||
#### Rotation (float)
|
|
||||||
Current rotation.
|
|
||||||
|
|
||||||
|
|
||||||
# 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? :^) )
|
|
||||||
@@ -11,22 +11,55 @@ This application was developed with the assistance of a large language model.
|
|||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
After cloning and enter the repository's directory, make sure to initialize the submodules:
|
After cloning and entering the repository's directory, make sure to initialize the submodules:
|
||||||
|
|
||||||
```git submodule update --init --recursive```
|
```git submodule update --init --recursive```
|
||||||
|
|
||||||
### Windows
|
Everything (SDL, Dear ImGui, simdjson, stb_vorbis) is built statically from the submodules. The game's resources are in `resources/` and get linked next to the executable (copied, where links aren't allowed). Release is the default build; add `-DCMAKE_BUILD_TYPE=Debug` to the first command for a debug one.
|
||||||
Visual Studio is recommended for build.
|
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
|
Needs the X11/Wayland and audio headers SDL builds against.
|
||||||
|
|
||||||
```
|
```
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake ..
|
cmake ..
|
||||||
make
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
If using VSCode, several tasks are available to quickly run and build.
|
### Windows
|
||||||
|
|
||||||
### Emscripten
|
With Visual Studio or MinGW:
|
||||||
Run the VSCode "web" task. Make sure you have the Emscripten SDK accessible at the EMSDK path variable.
|
|
||||||
|
```
|
||||||
|
cmake -B build
|
||||||
|
cmake --build build --config Release
|
||||||
|
```
|
||||||
|
|
||||||
|
### Web
|
||||||
|
|
||||||
|
Needs the [Emscripten SDK](https://emscripten.org/docs/getting_started/downloads.html).
|
||||||
|
|
||||||
|
```
|
||||||
|
emcmake cmake -B build-web
|
||||||
|
cmake --build build-web
|
||||||
|
```
|
||||||
|
|
||||||
|
This makes `index.html`, `.js`, `.wasm` and `.data`. Serve all four.
|
||||||
|
|
||||||
|
## Saves
|
||||||
|
|
||||||
|
Settings are kept in `settings.txt`, and each character's progress in `saves/<character>.txt`, as plain `key=value` lines. They're in `~/.local/share/shweets-sim` on Linux, `%AppData%/shweets-sim` on Windows, and the browser's storage on the web.
|
||||||
|
|
||||||
|
## Developer Console
|
||||||
|
|
||||||
|
Add `console=1` to `settings.txt`, then press the grave key (`` ` ``) in game. `help` lists the commands.
|
||||||
|
|
||||||
|
## Modding
|
||||||
|
|
||||||
|
See [MODDING.md](MODDING.md).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See [LICENSE](LICENSE).
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
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")
|
|
||||||
|
|
||||||
file(REMOVE_RECURSE "${DST_DIR}")
|
|
||||||
file(MAKE_DIRECTORY "${DST_DIR}")
|
|
||||||
|
|
||||||
# Copy all resources except character source folders and any stray character zips.
|
|
||||||
file(COPY "${SRC_DIR}/" DESTINATION "${DST_DIR}"
|
|
||||||
PATTERN "characters/*" EXCLUDE
|
|
||||||
PATTERN "characters/*.zip" EXCLUDE)
|
|
||||||
|
|
||||||
file(MAKE_DIRECTORY "${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")
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
if(NOT DEFINED BIN_DIR OR BIN_DIR STREQUAL "")
|
|
||||||
message(FATAL_ERROR "BIN_DIR is required")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(ARCHIVE_PATH "${BIN_DIR}/shweets-sim-web.zip")
|
|
||||||
file(REMOVE "${ARCHIVE_PATH}")
|
|
||||||
|
|
||||||
file(GLOB INDEX_OUTPUTS "${BIN_DIR}/index.*")
|
|
||||||
set(FILES_TO_ZIP "")
|
|
||||||
foreach(FILE_PATH IN LISTS INDEX_OUTPUTS)
|
|
||||||
if(NOT FILE_PATH STREQUAL ARCHIVE_PATH)
|
|
||||||
get_filename_component(FILE_NAME "${FILE_PATH}" NAME)
|
|
||||||
list(APPEND FILES_TO_ZIP "${FILE_NAME}")
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
if(FILES_TO_ZIP)
|
|
||||||
execute_process(
|
|
||||||
COMMAND "${CMAKE_COMMAND}" -E tar cf "shweets-sim-web.zip" --format=zip ${FILES_TO_ZIP}
|
|
||||||
WORKING_DIRECTORY "${BIN_DIR}"
|
|
||||||
RESULT_VARIABLE ZIP_RESULT
|
|
||||||
)
|
|
||||||
if(NOT ZIP_RESULT EQUAL 0)
|
|
||||||
message(FATAL_ERROR "Failed creating ${ARCHIVE_PATH}")
|
|
||||||
endif()
|
|
||||||
else()
|
|
||||||
message(WARNING "No index.* files found in ${BIN_DIR}; skipping shweets-sim-web.zip creation")
|
|
||||||
endif()
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
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()
|
|
||||||
Vendored
-1
Submodule external/SDL deleted from 770b38b4f1.
Vendored
-1
Submodule external/SDL_mixer deleted from 4182794ea4.
Vendored
-1
Submodule external/glm deleted from a583c59e16.
Vendored
-1
Submodule external/physfs deleted from d70c3fcf06.
+1
Submodule external/sdl added at 32e9d379f6.
+1
Submodule external/simdjson added at 449f38e637.
+1
Submodule external/stb added at d14d46f6b4.
Vendored
-1
Submodule external/tinyxml2 deleted from 36ff404c34.
@@ -1,311 +0,0 @@
|
|||||||
#ifndef __khrplatform_h_
|
|
||||||
#define __khrplatform_h_
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Copyright (c) 2008-2018 The Khronos Group Inc.
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
** copy of this software and/or associated documentation files (the
|
|
||||||
** "Materials"), to deal in the Materials without restriction, including
|
|
||||||
** without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
** distribute, sublicense, and/or sell copies of the Materials, and to
|
|
||||||
** permit persons to whom the Materials are furnished to do so, subject to
|
|
||||||
** the following conditions:
|
|
||||||
**
|
|
||||||
** The above copyright notice and this permission notice shall be included
|
|
||||||
** in all copies or substantial portions of the Materials.
|
|
||||||
**
|
|
||||||
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Khronos platform-specific types and definitions.
|
|
||||||
*
|
|
||||||
* The master copy of khrplatform.h is maintained in the Khronos EGL
|
|
||||||
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
|
|
||||||
* The last semantic modification to khrplatform.h was at commit ID:
|
|
||||||
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
|
|
||||||
*
|
|
||||||
* Adopters may modify this file to suit their platform. Adopters are
|
|
||||||
* encouraged to submit platform specific modifications to the Khronos
|
|
||||||
* group so that they can be included in future versions of this file.
|
|
||||||
* Please submit changes by filing pull requests or issues on
|
|
||||||
* the EGL Registry repository linked above.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* See the Implementer's Guidelines for information about where this file
|
|
||||||
* should be located on your system and for more details of its use:
|
|
||||||
* http://www.khronos.org/registry/implementers_guide.pdf
|
|
||||||
*
|
|
||||||
* This file should be included as
|
|
||||||
* #include <KHR/khrplatform.h>
|
|
||||||
* by Khronos client API header files that use its types and defines.
|
|
||||||
*
|
|
||||||
* The types in khrplatform.h should only be used to define API-specific types.
|
|
||||||
*
|
|
||||||
* Types defined in khrplatform.h:
|
|
||||||
* khronos_int8_t signed 8 bit
|
|
||||||
* khronos_uint8_t unsigned 8 bit
|
|
||||||
* khronos_int16_t signed 16 bit
|
|
||||||
* khronos_uint16_t unsigned 16 bit
|
|
||||||
* khronos_int32_t signed 32 bit
|
|
||||||
* khronos_uint32_t unsigned 32 bit
|
|
||||||
* khronos_int64_t signed 64 bit
|
|
||||||
* khronos_uint64_t unsigned 64 bit
|
|
||||||
* khronos_intptr_t signed same number of bits as a pointer
|
|
||||||
* khronos_uintptr_t unsigned same number of bits as a pointer
|
|
||||||
* khronos_ssize_t signed size
|
|
||||||
* khronos_usize_t unsigned size
|
|
||||||
* khronos_float_t signed 32 bit floating point
|
|
||||||
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
|
|
||||||
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
|
|
||||||
* nanoseconds
|
|
||||||
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
|
|
||||||
* khronos_boolean_enum_t enumerated boolean type. This should
|
|
||||||
* only be used as a base type when a client API's boolean type is
|
|
||||||
* an enum. Client APIs which use an integer or other type for
|
|
||||||
* booleans cannot use this as the base type for their boolean.
|
|
||||||
*
|
|
||||||
* Tokens defined in khrplatform.h:
|
|
||||||
*
|
|
||||||
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
|
|
||||||
*
|
|
||||||
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
|
|
||||||
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
|
|
||||||
*
|
|
||||||
* Calling convention macros defined in this file:
|
|
||||||
* KHRONOS_APICALL
|
|
||||||
* KHRONOS_APIENTRY
|
|
||||||
* KHRONOS_APIATTRIBUTES
|
|
||||||
*
|
|
||||||
* These may be used in function prototypes as:
|
|
||||||
*
|
|
||||||
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
|
|
||||||
* int arg1,
|
|
||||||
* int arg2) KHRONOS_APIATTRIBUTES;
|
|
||||||
*/
|
|
||||||
|
|
||||||
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
|
|
||||||
# define KHRONOS_STATIC 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------
|
|
||||||
* Definition of KHRONOS_APICALL
|
|
||||||
*-------------------------------------------------------------------------
|
|
||||||
* This precedes the return type of the function in the function prototype.
|
|
||||||
*/
|
|
||||||
#if defined(KHRONOS_STATIC)
|
|
||||||
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
|
|
||||||
* header compatible with static linking. */
|
|
||||||
# define KHRONOS_APICALL
|
|
||||||
#elif defined(_WIN32)
|
|
||||||
# define KHRONOS_APICALL __declspec(dllimport)
|
|
||||||
#elif defined (__SYMBIAN32__)
|
|
||||||
# define KHRONOS_APICALL IMPORT_C
|
|
||||||
#elif defined(__ANDROID__)
|
|
||||||
# define KHRONOS_APICALL __attribute__((visibility("default")))
|
|
||||||
#else
|
|
||||||
# define KHRONOS_APICALL
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------
|
|
||||||
* Definition of KHRONOS_APIENTRY
|
|
||||||
*-------------------------------------------------------------------------
|
|
||||||
* This follows the return type of the function and precedes the function
|
|
||||||
* name in the function prototype.
|
|
||||||
*/
|
|
||||||
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
|
|
||||||
/* Win32 but not WinCE */
|
|
||||||
# define KHRONOS_APIENTRY __stdcall
|
|
||||||
#else
|
|
||||||
# define KHRONOS_APIENTRY
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------
|
|
||||||
* Definition of KHRONOS_APIATTRIBUTES
|
|
||||||
*-------------------------------------------------------------------------
|
|
||||||
* This follows the closing parenthesis of the function prototype arguments.
|
|
||||||
*/
|
|
||||||
#if defined (__ARMCC_2__)
|
|
||||||
#define KHRONOS_APIATTRIBUTES __softfp
|
|
||||||
#else
|
|
||||||
#define KHRONOS_APIATTRIBUTES
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------
|
|
||||||
* basic type definitions
|
|
||||||
*-----------------------------------------------------------------------*/
|
|
||||||
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Using <stdint.h>
|
|
||||||
*/
|
|
||||||
#include <stdint.h>
|
|
||||||
typedef int32_t khronos_int32_t;
|
|
||||||
typedef uint32_t khronos_uint32_t;
|
|
||||||
typedef int64_t khronos_int64_t;
|
|
||||||
typedef uint64_t khronos_uint64_t;
|
|
||||||
#define KHRONOS_SUPPORT_INT64 1
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 1
|
|
||||||
/*
|
|
||||||
* To support platform where unsigned long cannot be used interchangeably with
|
|
||||||
* inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t.
|
|
||||||
* Ideally, we could just use (u)intptr_t everywhere, but this could result in
|
|
||||||
* ABI breakage if khronos_uintptr_t is changed from unsigned long to
|
|
||||||
* unsigned long long or similar (this results in different C++ name mangling).
|
|
||||||
* To avoid changes for existing platforms, we restrict usage of intptr_t to
|
|
||||||
* platforms where the size of a pointer is larger than the size of long.
|
|
||||||
*/
|
|
||||||
#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__)
|
|
||||||
#if __SIZEOF_POINTER__ > __SIZEOF_LONG__
|
|
||||||
#define KHRONOS_USE_INTPTR_T
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#elif defined(__VMS ) || defined(__sgi)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Using <inttypes.h>
|
|
||||||
*/
|
|
||||||
#include <inttypes.h>
|
|
||||||
typedef int32_t khronos_int32_t;
|
|
||||||
typedef uint32_t khronos_uint32_t;
|
|
||||||
typedef int64_t khronos_int64_t;
|
|
||||||
typedef uint64_t khronos_uint64_t;
|
|
||||||
#define KHRONOS_SUPPORT_INT64 1
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 1
|
|
||||||
|
|
||||||
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Win32
|
|
||||||
*/
|
|
||||||
typedef __int32 khronos_int32_t;
|
|
||||||
typedef unsigned __int32 khronos_uint32_t;
|
|
||||||
typedef __int64 khronos_int64_t;
|
|
||||||
typedef unsigned __int64 khronos_uint64_t;
|
|
||||||
#define KHRONOS_SUPPORT_INT64 1
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 1
|
|
||||||
|
|
||||||
#elif defined(__sun__) || defined(__digital__)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Sun or Digital
|
|
||||||
*/
|
|
||||||
typedef int khronos_int32_t;
|
|
||||||
typedef unsigned int khronos_uint32_t;
|
|
||||||
#if defined(__arch64__) || defined(_LP64)
|
|
||||||
typedef long int khronos_int64_t;
|
|
||||||
typedef unsigned long int khronos_uint64_t;
|
|
||||||
#else
|
|
||||||
typedef long long int khronos_int64_t;
|
|
||||||
typedef unsigned long long int khronos_uint64_t;
|
|
||||||
#endif /* __arch64__ */
|
|
||||||
#define KHRONOS_SUPPORT_INT64 1
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 1
|
|
||||||
|
|
||||||
#elif 0
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Hypothetical platform with no float or int64 support
|
|
||||||
*/
|
|
||||||
typedef int khronos_int32_t;
|
|
||||||
typedef unsigned int khronos_uint32_t;
|
|
||||||
#define KHRONOS_SUPPORT_INT64 0
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 0
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Generic fallback
|
|
||||||
*/
|
|
||||||
#include <stdint.h>
|
|
||||||
typedef int32_t khronos_int32_t;
|
|
||||||
typedef uint32_t khronos_uint32_t;
|
|
||||||
typedef int64_t khronos_int64_t;
|
|
||||||
typedef uint64_t khronos_uint64_t;
|
|
||||||
#define KHRONOS_SUPPORT_INT64 1
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 1
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Types that are (so far) the same on all platforms
|
|
||||||
*/
|
|
||||||
typedef signed char khronos_int8_t;
|
|
||||||
typedef unsigned char khronos_uint8_t;
|
|
||||||
typedef signed short int khronos_int16_t;
|
|
||||||
typedef unsigned short int khronos_uint16_t;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Types that differ between LLP64 and LP64 architectures - in LLP64,
|
|
||||||
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
|
|
||||||
* to be the only LLP64 architecture in current use.
|
|
||||||
*/
|
|
||||||
#ifdef KHRONOS_USE_INTPTR_T
|
|
||||||
typedef intptr_t khronos_intptr_t;
|
|
||||||
typedef uintptr_t khronos_uintptr_t;
|
|
||||||
#elif defined(_WIN64)
|
|
||||||
typedef signed long long int khronos_intptr_t;
|
|
||||||
typedef unsigned long long int khronos_uintptr_t;
|
|
||||||
#else
|
|
||||||
typedef signed long int khronos_intptr_t;
|
|
||||||
typedef unsigned long int khronos_uintptr_t;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(_WIN64)
|
|
||||||
typedef signed long long int khronos_ssize_t;
|
|
||||||
typedef unsigned long long int khronos_usize_t;
|
|
||||||
#else
|
|
||||||
typedef signed long int khronos_ssize_t;
|
|
||||||
typedef unsigned long int khronos_usize_t;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if KHRONOS_SUPPORT_FLOAT
|
|
||||||
/*
|
|
||||||
* Float type
|
|
||||||
*/
|
|
||||||
typedef float khronos_float_t;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if KHRONOS_SUPPORT_INT64
|
|
||||||
/* Time types
|
|
||||||
*
|
|
||||||
* These types can be used to represent a time interval in nanoseconds or
|
|
||||||
* an absolute Unadjusted System Time. Unadjusted System Time is the number
|
|
||||||
* of nanoseconds since some arbitrary system event (e.g. since the last
|
|
||||||
* time the system booted). The Unadjusted System Time is an unsigned
|
|
||||||
* 64 bit value that wraps back to 0 every 584 years. Time intervals
|
|
||||||
* may be either signed or unsigned.
|
|
||||||
*/
|
|
||||||
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
|
|
||||||
typedef khronos_int64_t khronos_stime_nanoseconds_t;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Dummy value used to pad enum types to 32 bits.
|
|
||||||
*/
|
|
||||||
#ifndef KHRONOS_MAX_ENUM
|
|
||||||
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Enumerated boolean type
|
|
||||||
*
|
|
||||||
* Values other than zero should be considered to be true. Therefore
|
|
||||||
* comparisons should not be made against KHRONOS_TRUE.
|
|
||||||
*/
|
|
||||||
typedef enum {
|
|
||||||
KHRONOS_FALSE = 0,
|
|
||||||
KHRONOS_TRUE = 1,
|
|
||||||
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
|
|
||||||
} khronos_boolean_enum_t;
|
|
||||||
|
|
||||||
#endif /* __khrplatform_h_ */
|
|
||||||
File diff suppressed because it is too large.
Load diff
-15776
File diff suppressed because it is too large.
Load diff
@@ -1,3 +0,0 @@
|
|||||||
<Areas TextureRootPath="gfx/areas" BaseAnm2="area.anm2" WorldBoundsNull="Bounds" ItemSpawnNull="ItemSpawn">
|
|
||||||
<Area Texture="area.png" Gravity="0.95" Friction="0.75" AirResistance="0.975" />
|
|
||||||
</Areas>
|
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
{
|
||||||
|
"name": "Braixen",
|
||||||
|
"description": "A fire-wielding, enigmatic Fox Pokémon. You're her apprentice!",
|
||||||
|
"credits": "Art, Animation, Dialogue: ShweetMagnet Sound Effects: Pokemon, skunkwhoplunks Commissioned by @FatGirIsHMU on Twitter",
|
||||||
|
"render": "gfx/preview/render.png",
|
||||||
|
"portrait": "gfx/preview/portrait.png",
|
||||||
|
"anm2": "gfx/character/braixen.anm2",
|
||||||
|
"color": [0.901961, 0.156863, 0.160784],
|
||||||
|
"weight": 32,
|
||||||
|
"weightMax": 250,
|
||||||
|
"capacity": 1000,
|
||||||
|
"capacityMin": 1000,
|
||||||
|
"capacityMax": 10000,
|
||||||
|
"overstuffedMultiplier": 1.5,
|
||||||
|
"overstuffedDigestBonus": 0.2,
|
||||||
|
"caloriesToKilogram": 0.001,
|
||||||
|
"digestionRate": 1.5,
|
||||||
|
"digestionRateMax": 4.5,
|
||||||
|
"digestionTime": 120,
|
||||||
|
"blinkChance": 0.5,
|
||||||
|
"gurgleChance": 0.0005,
|
||||||
|
"gurgleCapacityMultiplier": 10,
|
||||||
|
"dialoguePool": "Stage1",
|
||||||
|
"stages": [
|
||||||
|
{"weight": 35, "dialoguePool": "Stage2", "animationSuffix": "Stage2"},
|
||||||
|
{"weight": 45, "dialoguePool": "Stage3", "animationSuffix": "Stage3"},
|
||||||
|
{"weight": 55, "dialoguePool": "Stage4", "animationSuffix": "Stage4"},
|
||||||
|
{"weight": 70, "dialoguePool": "Stage5", "animationSuffix": "Stage5"},
|
||||||
|
{"weight": 95, "dialoguePool": "Stage6", "animationSuffix": "Stage6"},
|
||||||
|
{"weight": 125, "dialoguePool": "Stage7", "animationSuffix": "Stage7"},
|
||||||
|
{"weight": 160, "dialoguePool": "Stage8", "animationSuffix": "Stage8"},
|
||||||
|
{"weight": 200, "dialoguePool": "Stage9", "animationSuffix": "Stage9"},
|
||||||
|
{"weight": 250, "dialoguePool": "Stage10", "animationSuffix": "Stage10"}
|
||||||
|
],
|
||||||
|
"animations": {"start": "Appear", "idle": "Idle", "idleFull": "NeutralFull", "stageUp": "StageUp"},
|
||||||
|
"talk": {"source": "Talk", "destination": "Mouth"},
|
||||||
|
"blink": {"source": "Blink", "destination": "Eye"},
|
||||||
|
"eatAreas": [
|
||||||
|
{"null": "Mouth", "animation": "Eat", "event": "Eat"}
|
||||||
|
],
|
||||||
|
"expandAreas": [
|
||||||
|
{"layer": "Torso", "null": "Belly", "scaleMax": 1.25}
|
||||||
|
],
|
||||||
|
"sounds": {
|
||||||
|
"digest": ["sfx/digest1.ogg", "sfx/digest2.ogg", "sfx/digest3.ogg"],
|
||||||
|
"gurgle": ["sfx/gurgle1.ogg", "sfx/gurgle2.ogg", "sfx/gurgle3.ogg", "sfx/gurgle4.ogg", "sfx/gurgle5.ogg"]
|
||||||
|
},
|
||||||
|
"interactTypes": {
|
||||||
|
"Rub": {
|
||||||
|
"glyph": "2",
|
||||||
|
"isHold": true,
|
||||||
|
"cursorHover": "Hover",
|
||||||
|
"cursorActive": "Rub",
|
||||||
|
"sounds": ["sfx/rub.ogg"],
|
||||||
|
"particles": ["Circle"]
|
||||||
|
},
|
||||||
|
"Smack": {
|
||||||
|
"glyph": "3",
|
||||||
|
"cursorHover": "SmackHover",
|
||||||
|
"cursorActive": "Smack",
|
||||||
|
"dialogueCooldown": 300,
|
||||||
|
"digestionClick": 0.25,
|
||||||
|
"scaleAmplitude": 1,
|
||||||
|
"scaleCycles": 3,
|
||||||
|
"sounds": ["sfx/smack1.ogg", "sfx/smack2.ogg", "sfx/smack3.ogg", "sfx/smack4.ogg", "sfx/smack5.ogg"],
|
||||||
|
"particles": ["Circle"]
|
||||||
|
},
|
||||||
|
"Kiss": {
|
||||||
|
"glyph": "4",
|
||||||
|
"cursorHover": "KissHover",
|
||||||
|
"cursorActive": "Kiss",
|
||||||
|
"sounds": ["sfx/kiss1.ogg", "sfx/kiss2.ogg", "sfx/kiss3.ogg", "sfx/kiss4.ogg", "sfx/kiss5.ogg"],
|
||||||
|
"particles": ["Circle"]
|
||||||
|
},
|
||||||
|
"Turn Around": {
|
||||||
|
"glyph": "5",
|
||||||
|
"sounds": ["sfx/turn.ogg"],
|
||||||
|
"particles": ["Circle"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interactAreas": [
|
||||||
|
{"type": "Rub", "null": "Head", "dialoguePool": "HeadRub", "dialoguePoolFull": "HeadRubFull"},
|
||||||
|
{"type": "Rub", "null": "Belly", "cursorActive": "Grab", "dialoguePool": "BellyRub", "dialoguePoolFull": "BellyRubFull", "digestionHover": 0.1},
|
||||||
|
{"type": "Kiss", "null": "Mouth", "dialoguePool": "MouthKiss", "dialoguePoolFull": "MouthKiss"},
|
||||||
|
{"type": "Kiss", "null": "Belly", "dialoguePool": "BellyKiss", "dialoguePoolFull": "BellyKiss"},
|
||||||
|
{"type": "Smack", "null": "Belly", "layer": "Torso", "dialoguePool": "BellySmack", "dialoguePoolFull": "BellySmack"},
|
||||||
|
{"type": "Smack", "null": "ButtFront", "layer": "LegFront", "dialoguePool": "ButtSmack"},
|
||||||
|
{"type": "Smack", "null": "ButtBack", "layer": "LegBack", "idleOnEnd": "IdleBack", "dialoguePool": "ButtSmack"},
|
||||||
|
{"type": "Turn Around", "null": "FullBody", "animation": "IdleBack", "idleOnEnd": "IdleBack", "cursorHover": "TurnAround"},
|
||||||
|
{"type": "Turn Around", "null": "FullBodyBack", "animation": "Idle", "idleOnEnd": "Idle", "cursorHover": "TurnAroundBack"}
|
||||||
|
],
|
||||||
|
"areas": {
|
||||||
|
"anm2": "gfx/areas/area.anm2",
|
||||||
|
"boundsNull": "Bounds",
|
||||||
|
"spawnNull": "ItemSpawn",
|
||||||
|
"list": [
|
||||||
|
{"gravity": 0.95, "friction": 0.75, "airResistance": 0.975}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cursor": {
|
||||||
|
"anm2": "gfx/cursor/cursor.anm2",
|
||||||
|
"animations": {"idle": "Default", "click": "Click", "hover": "Hover", "grab": "Grab", "pan": "Pan", "zoom": "Zoom", "dispose": "Dispose"},
|
||||||
|
"sounds": {
|
||||||
|
"grab": ["sfx/grab.ogg"],
|
||||||
|
"release": ["sfx/release.ogg"],
|
||||||
|
"throw": ["sfx/throw.ogg", "sfx/throw2.ogg", "sfx/throw3.ogg"]
|
||||||
|
},
|
||||||
|
"particles": {
|
||||||
|
"grab": ["Circle"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"font": "font/font.ttf",
|
||||||
|
"iconsFont": "font/icons.ttf",
|
||||||
|
"rounding": 10.0,
|
||||||
|
"homeGlyph": "1",
|
||||||
|
"sounds": {
|
||||||
|
"open": ["sfx/open.ogg"],
|
||||||
|
"close": ["sfx/close.ogg"],
|
||||||
|
"hover": ["sfx/hover.ogg", "sfx/hover2.ogg", "sfx/hover3.ogg"],
|
||||||
|
"select": ["sfx/select.ogg"],
|
||||||
|
"cheats": ["sfx/cheatsActivated.ogg"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"particles": {
|
||||||
|
"Circle": {"anm2": "gfx/particle/circle.anm2", "animation": "Main"},
|
||||||
|
"Puff": {"anm2": "gfx/particle/puff.anm2", "animation": "Main"}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
<Character Name="Braixen" TextureRootPath="gfx" SoundRootPath="sfx" Render="preview/render.png" Portrait="preview/portrait.png" Anm2="character/braixen.anm2" Description="A fire-wielding, enigmatic Fox Pokémon. You're her apprentice!" Credits="Art, Animation, Dialogue: ShweetMagnet Sound Effects: Pokemon, skunkwhoplunks Commissioned by @FatGirIsHMU on Twitter" WeightKilograms="32" WeightMaxKilograms="250" CapacityCalories="1000" CapacityMinCalories="1000" CapacityMaxCalories="10000" CapacityOverstuffedMultiplier="1.5" CapacityIfOverStuffedOnDigestBonus="0.2" CaloriesToKilogram="0.001" DigestionTimerMax="120" BlinkChance="0.5" GurgleChance="0.00050000002" GurgleChanceFromCapacityMultiplier="10" DialoguePool="Stage1" ColorR="0.90196079" ColorG="0.15686275" ColorB="0.16078432" BlipDelayTicks="4" DelayTicks="4" DigestionRatePerSecond="1.5" DigestionRatePerSecondMax="4.5">
|
|
||||||
<Stages>
|
|
||||||
<Stage ThresholdKilograms="35" DialoguePool="Stage2" AppendStringForAnimations="Stage2" />
|
|
||||||
<Stage ThresholdKilograms="45" DialoguePool="Stage3" AppendStringForAnimations="Stage3" />
|
|
||||||
<Stage ThresholdKilograms="55" DialoguePool="Stage4" AppendStringForAnimations="Stage4" />
|
|
||||||
<Stage ThresholdKilograms="70" DialoguePool="Stage5" AppendStringForAnimations="Stage5" />
|
|
||||||
<Stage ThresholdKilograms="95" DialoguePool="Stage6" AppendStringForAnimations="Stage6" />
|
|
||||||
<Stage ThresholdKilograms="125" DialoguePool="Stage7" AppendStringForAnimations="Stage7" />
|
|
||||||
<Stage ThresholdKilograms="160" DialoguePool="Stage8" AppendStringForAnimations="Stage8" />
|
|
||||||
<Stage ThresholdKilograms="200" DialoguePool="Stage9" AppendStringForAnimations="Stage9" />
|
|
||||||
<Stage ThresholdKilograms="250" DialoguePool="Stage10" AppendStringForAnimations="Stage10" />
|
|
||||||
</Stages>
|
|
||||||
<Animations>
|
|
||||||
<Start Animation="Appear" />
|
|
||||||
<Idle Animation="Idle" />
|
|
||||||
<IdleFull Animation="NeutralFull" />
|
|
||||||
<StageUp Animation="StageUp" />
|
|
||||||
</Animations>
|
|
||||||
<AnimationOverrides>
|
|
||||||
<Talk LayerSource="Talk" LayerDestination="Mouth" />
|
|
||||||
<Blink LayerSource="Blink" LayerDestination="Eye" />
|
|
||||||
</AnimationOverrides>
|
|
||||||
<EatAreas>
|
|
||||||
<EatArea Null="Mouth" Animation="Eat" EventToActivate="Eat" />
|
|
||||||
</EatAreas>
|
|
||||||
<Actions>
|
|
||||||
<Digest>
|
|
||||||
<Sound Path="digest1.ogg" />
|
|
||||||
<Sound Path="digest2.ogg" />
|
|
||||||
<Sound Path="digest3.ogg" />
|
|
||||||
</Digest>
|
|
||||||
<Gurgle>
|
|
||||||
<Sound Path="gurgle1.ogg" />
|
|
||||||
<Sound Path="gurgle2.ogg" />
|
|
||||||
<Sound Path="gurgle3.ogg" />
|
|
||||||
<Sound Path="gurgle4.ogg" />
|
|
||||||
<Sound Path="gurgle5.ogg" />
|
|
||||||
</Gurgle>
|
|
||||||
</Actions>
|
|
||||||
<ExpandAreas>
|
|
||||||
<ExpandArea Layer="Torso" Null="Belly" ScaleMultiplierMaximum="1.25" />
|
|
||||||
</ExpandAreas>
|
|
||||||
<InteractAreaTypes>
|
|
||||||
<InteractArea Type="Rub" GlyphFromIconFont="2" IsHold="true" AnimationCursorHover="Hover" AnimationCursorActive="Rub">
|
|
||||||
<Sound Path="rub.ogg" />
|
|
||||||
<Particle Type="Circle" />
|
|
||||||
</InteractArea>
|
|
||||||
<InteractArea Type="Smack" GlyphFromIconFont="3" AnimationCursorHover="SmackHover" AnimationCursorActive="Smack" TimeToActivateNewDialogueTicks="300" ScaleEffectAmplitude="1" ScaleEffectCycles="3" DigestionBonusOnClick="0.25">
|
|
||||||
<Sound Path="smack1.ogg" />
|
|
||||||
<Sound Path="smack2.ogg" />
|
|
||||||
<Sound Path="smack3.ogg" />
|
|
||||||
<Sound Path="smack4.ogg" />
|
|
||||||
<Sound Path="smack5.ogg" />
|
|
||||||
<Particle Type="Circle" />
|
|
||||||
</InteractArea>
|
|
||||||
<InteractArea Type="Kiss" GlyphFromIconFont="4" AnimationCursorHover="KissHover" AnimationCursorActive="Kiss">
|
|
||||||
<Sound Path="kiss1.ogg" />
|
|
||||||
<Sound Path="kiss2.ogg" />
|
|
||||||
<Sound Path="kiss3.ogg" />
|
|
||||||
<Sound Path="kiss4.ogg" />
|
|
||||||
<Sound Path="kiss5.ogg" />
|
|
||||||
<Particle Type="Circle" />
|
|
||||||
</InteractArea>
|
|
||||||
<InteractArea Type="Turn Around" GlyphFromIconFont="5">
|
|
||||||
<Sound Path="turn.ogg" />
|
|
||||||
<Particle Type="Circle" />
|
|
||||||
</InteractArea>
|
|
||||||
</InteractAreaTypes>
|
|
||||||
<InteractAreas>
|
|
||||||
<InteractArea Type="Rub" Null="Head" DialoguePool="HeadRub" DialoguePoolFull="HeadRubFull" />
|
|
||||||
<InteractArea Type="Rub" Null="Belly" AnimationCursorActive="Grab" DialoguePool="BellyRub" DigestionBonusOnHover="0.1" DialoguePoolFull="BellyRubFull" />
|
|
||||||
<InteractArea Type="Kiss" Null="Mouth" DialoguePool="MouthKiss" DialoguePoolFull="MouthKiss" />
|
|
||||||
<InteractArea Type="Kiss" Null="Belly" DialoguePool="BellyKiss" DialoguePoolFull="BellyKiss" />
|
|
||||||
<InteractArea Type="Smack" Null="Belly" DialoguePool="BellySmack" Layer="Torso" DialoguePoolFull="BellySmack" />
|
|
||||||
<InteractArea Type="Smack" Null="ButtFront" DialoguePool="ButtSmack" Layer="LegFront" />
|
|
||||||
<InteractArea Type="Smack" Null="ButtBack" DialoguePool="ButtSmack" Layer="LegBack" DefaultAnimationOnEnd="IdleBack" />
|
|
||||||
<InteractArea Type="Turn Around" Null="FullBody" AnimationCursorHover="TurnAround" Animation="IdleBack" DefaultAnimationOnEnd="IdleBack" />
|
|
||||||
<InteractArea Type="Turn Around" Null="FullBodyBack" AnimationCursorHover="TurnAroundBack" Animation="Idle" DefaultAnimationOnEnd="Idle" />
|
|
||||||
</InteractAreas>
|
|
||||||
</Character>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<Cursor TextureRootPath="gfx" SoundRootPath="sfx" Anm2="cursor/cursor.anm2">
|
|
||||||
<Actions>
|
|
||||||
<Idle Animation="Default" />
|
|
||||||
<Click Animation="Click" />
|
|
||||||
<Hover Animation="Hover" />
|
|
||||||
<Grab Animation="Grab">
|
|
||||||
<Sound Path="grab.ogg" />
|
|
||||||
<Particle Type="Circle" />
|
|
||||||
</Grab>
|
|
||||||
<Release>
|
|
||||||
<Sound Path="release.ogg" />
|
|
||||||
</Release>
|
|
||||||
<Pan Animation="Pan" />
|
|
||||||
<Zoom Animation="Zoom" />
|
|
||||||
<Dispose Animation="Dispose" />
|
|
||||||
<Throw>
|
|
||||||
<Sound Path="throw.ogg" />
|
|
||||||
<Sound Path="throw2.ogg" />
|
|
||||||
<Sound Path="throw3.ogg" />
|
|
||||||
</Throw>
|
|
||||||
</Actions>
|
|
||||||
</Cursor>
|
|
||||||
@@ -0,0 +1,348 @@
|
|||||||
|
{
|
||||||
|
"delay": 2,
|
||||||
|
"blipDelay": 2,
|
||||||
|
"blip": ["sfx/blip.ogg"],
|
||||||
|
"pauses": {
|
||||||
|
".": {"delay": 8, "isSilent": true},
|
||||||
|
",": {"delay": 8, "isSilent": true},
|
||||||
|
"!": {"delay": 8, "isSilent": true},
|
||||||
|
"?": {"delay": 8, "isSilent": true}
|
||||||
|
},
|
||||||
|
"hooks": {"Start": "StartAppear"},
|
||||||
|
"pools": {
|
||||||
|
"ButtSmack": ["ButtSmack", "ButtSmack2", "ButtSmack3", "ButtSmack4", "ButtSmack5", "ButtSmack6", "ButtSmack7", "ButtSmack8", "ButtSmack9", "ButtSmack10"]
|
||||||
|
},
|
||||||
|
"entries": {
|
||||||
|
"StartAppear": {
|
||||||
|
"animation": "Appear",
|
||||||
|
"next": "Start",
|
||||||
|
"effects": [
|
||||||
|
{"type": "scene"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Start": {
|
||||||
|
"text": "Lovely, lovely, lovely!! You must be my new apprentice~!",
|
||||||
|
"animation": "Sweet",
|
||||||
|
"choices": [
|
||||||
|
{"text": "W-wait, huh?!", "next": "StartA-1"},
|
||||||
|
{"text": "That's me!", "next": "StartB-1"}
|
||||||
|
],
|
||||||
|
"effects": [
|
||||||
|
{"type": "oscillation", "start": 40, "end": 54},
|
||||||
|
{"type": "oscillation", "start": 16, "end": 22},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"start": 40,
|
||||||
|
"end": 54,
|
||||||
|
"color": [1, 0.25, 0.25]
|
||||||
|
},
|
||||||
|
{"type": "screenshake", "start": 44, "end": 44},
|
||||||
|
{"type": "sound", "start": 44, "end": 44, "sound": "sfx/boom.ogg"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"StartA-1": {
|
||||||
|
"text": "I'm not mistaken! My blazing bright eyes can see into your soul! Without a doubt, you are my apprentice!",
|
||||||
|
"animation": "Surprised",
|
||||||
|
"next": "StartA-2",
|
||||||
|
"effects": [
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"start": 93,
|
||||||
|
"end": 103,
|
||||||
|
"color": [1, 0.25, 0.25]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"StartB-1": {
|
||||||
|
"text": "Excellent! You've gotten the message! Splendid! So splendid!",
|
||||||
|
"animation": "Wicked",
|
||||||
|
"next": "StartA-2",
|
||||||
|
"effects": [
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"start": 93,
|
||||||
|
"end": 103,
|
||||||
|
"color": [1, 0.25, 0.25]
|
||||||
|
},
|
||||||
|
{"type": "shake", "start": 51, "end": 59}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"StartA-2": {
|
||||||
|
"text": "Of course you would be! You've got a certain...spirit, about you! Your own ember, burning furiously within you!",
|
||||||
|
"animation": "Wicked",
|
||||||
|
"next": "StartA-3",
|
||||||
|
"effects": [
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"start": 47,
|
||||||
|
"end": 53,
|
||||||
|
"color": [1, 0.25, 0.25]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"start": 76,
|
||||||
|
"end": 81,
|
||||||
|
"color": [1, 0.25, 0.25]
|
||||||
|
},
|
||||||
|
{"type": "shake", "start": 91, "end": 100}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"StartA-3": {"text": "Now, I must say...my practice isn't perfect yet. I am still a learner in the Art, and my pursuit is...unique.", "animation": "Thinking", "next": "StartA-4"},
|
||||||
|
"StartA-4": {
|
||||||
|
"text": "The school I'm enrolled in...encompasses only me. I'm the founder, and the sole teacher and student! Until you came along, that is. Call me Braixen. I shall be your companion!",
|
||||||
|
"animation": "Smug",
|
||||||
|
"next": "StartA-5",
|
||||||
|
"effects": [
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"start": 140,
|
||||||
|
"end": 147,
|
||||||
|
"color": [1, 0.25, 0.25]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"StartA-5": {"text": "I should tell you about my philosophy, then! What the Art really is! To master the eternal flame, the unyielding ember, one must embrace it...fully!", "animation": "Thinking", "next": "StartA-6"},
|
||||||
|
"StartA-6": {
|
||||||
|
"text": "You see, my apprentice, what is more \"embracing\" than using...one's own body for the sake of the Art?! I can't believe no one else has considered such a thing!!",
|
||||||
|
"animation": "Excited",
|
||||||
|
"next": "StartA-7",
|
||||||
|
"effects": [
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"start": 62,
|
||||||
|
"end": 76,
|
||||||
|
"color": [1, 0.25, 0.25]
|
||||||
|
},
|
||||||
|
{"type": "oscillation", "start": 62, "end": 76}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"StartA-7": {
|
||||||
|
"text": "Listen closely! Your purpose is to turn my thin, meager body into an splendid host of the Art! My stomach a vast, boiling, bubbling cauldron! My physique abundant with heat and energy!",
|
||||||
|
"animation": "Stern",
|
||||||
|
"next": "StartA-8",
|
||||||
|
"effects": [
|
||||||
|
{"type": "oscillation", "start": 69, "end": 82}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"StartA-8": {
|
||||||
|
"text": "...hard to understand? Fine, then, I'll use more pedestrian language. I want you to make me fat! As fat as possible!",
|
||||||
|
"animation": "Wicked",
|
||||||
|
"next": "StartA-9",
|
||||||
|
"effects": [
|
||||||
|
{"type": "big", "start": 69, "end": 96},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"start": 92,
|
||||||
|
"end": 96,
|
||||||
|
"color": [1, 0.25, 0.25]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"start": 101,
|
||||||
|
"end": 105,
|
||||||
|
"color": [1, 0.25, 0.25]
|
||||||
|
},
|
||||||
|
{"type": "screenshake", "start": 92, "end": 93}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"StartA-9": {
|
||||||
|
"text": "Some have called it \"ridiculous\". Unhealthy. Absurd! Well, I call them lost! Fools who will never truly grasp the reality of the Art!",
|
||||||
|
"animation": "Pout",
|
||||||
|
"next": "StartA-10",
|
||||||
|
"effects": [
|
||||||
|
{"type": "shake", "start": 71, "end": 75}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"StartA-10": {"text": "Your role now is to serve me well, and rejoice; for you, my apprentice, shall also scale the mountain of awakening through this!", "animation": "Smug", "next": "StartA-11"},
|
||||||
|
"StartA-11": {
|
||||||
|
"text": "Journey to faraway lands! Battle fierce opponents! Conquer and claim what foodstuffs you can scrounge to stoke the flame inside of my belly!",
|
||||||
|
"animation": "Horny",
|
||||||
|
"next": "StartA-12",
|
||||||
|
"effects": [
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"start": 105,
|
||||||
|
"end": 121,
|
||||||
|
"color": [1, 0.25, 0.25]
|
||||||
|
},
|
||||||
|
{"type": "shake", "start": 105, "end": 120}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"StartA-12": {
|
||||||
|
"text": "It will require dilligence, perserverance and perhaps...sacrifice. But, my student, following the Art shall yield many...rewards.",
|
||||||
|
"animation": "Horny",
|
||||||
|
"next": "StartA-13",
|
||||||
|
"effects": [
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"color": [1, 0.25, 0.25]
|
||||||
|
},
|
||||||
|
{"type": "shake"},
|
||||||
|
{"type": "animation", "start": 67, "animation": "Horny"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"StartA-13": {
|
||||||
|
"text": "I shall grace you with permanent authority over my vessel. I do not mind. I trust you. You bare the same ember as I. I know this!",
|
||||||
|
"animation": "Smug",
|
||||||
|
"next": "StartA-14",
|
||||||
|
"effects": [
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"color": [1, 0.25, 0.25]
|
||||||
|
},
|
||||||
|
{"type": "shake"},
|
||||||
|
{"type": "animation", "animation": "Horny"},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"start": 105,
|
||||||
|
"end": 110,
|
||||||
|
"color": [1, 0.25, 0.25]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"StartA-14": {
|
||||||
|
"text": "Now, venture forth! Heed thy call! Feed me! FEED ME! ...and do know that I can soothe any urges that may arise from witnessing such splendor.",
|
||||||
|
"animation": "Wicked",
|
||||||
|
"effects": [
|
||||||
|
{"type": "shake", "start": 44, "end": 51},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"start": 53,
|
||||||
|
"end": 142,
|
||||||
|
"color": [0.498039, 0.498039, 0.498039]
|
||||||
|
},
|
||||||
|
{"type": "small", "start": 53, "end": 142},
|
||||||
|
{"type": "animation", "start": 5, "animation": "Sweet"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Help": {
|
||||||
|
"text": "Yes, my dear apprentice! What do you need assistance with?",
|
||||||
|
"animation": "Curious",
|
||||||
|
"choices": [
|
||||||
|
{"text": "You", "next": "HelpYou"},
|
||||||
|
{"text": "Choice"}
|
||||||
|
],
|
||||||
|
"effects": [
|
||||||
|
{"type": "oscillation", "start": 4, "end": 23}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"HelpYou": {"text": "M-Me?! Apprentice, are you...daft? Did you not heed my instructions I spoke to you earlier?", "animation": "Shocked", "next": "HelpYou2"},
|
||||||
|
"HelpYou2": {
|
||||||
|
"text": "Your mission is to follow my instructions in the Art! What's the Art?! Bah! Simpleton!",
|
||||||
|
"animation": "Angry",
|
||||||
|
"next": "HelpYou3",
|
||||||
|
"effects": [
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"start": 49,
|
||||||
|
"end": 52,
|
||||||
|
"color": [1, 0.25, 0.25]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"start": 65,
|
||||||
|
"end": 68,
|
||||||
|
"color": [1, 0.25, 0.25]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"HelpYou3": {
|
||||||
|
"text": "Fire! Flames! Heat! Energy! Mass! That's what the Art is! And to sum it up, I need to embrace it by GETTING FAT!!!",
|
||||||
|
"animation": "Angry",
|
||||||
|
"effects": [
|
||||||
|
{"type": "screenshake", "start": 101, "end": 110},
|
||||||
|
{"type": "shake", "start": 101, "end": 121},
|
||||||
|
{"type": "sound", "start": 101, "end": 110, "sound": "sfx/boom.ogg"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"HelpYou4": {
|
||||||
|
"text": "Fire! Flames! Heat! Energy! Mass! That's what the Art is! And to sum it up, I need to embrace it by GETTING FAT!!!",
|
||||||
|
"animation": "Angry",
|
||||||
|
"effects": [
|
||||||
|
{"type": "screenshake", "start": 101, "end": 110},
|
||||||
|
{"type": "shake", "start": 101, "end": 121},
|
||||||
|
{"type": "sound", "start": 101, "end": 110, "sound": "sfx/boom.ogg"},
|
||||||
|
{"type": "animation", "start": 101, "animation": "Horny"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ButtSmack": {
|
||||||
|
"text": "KYAH!!!",
|
||||||
|
"animation": "SurprisedBack",
|
||||||
|
"effects": [
|
||||||
|
{"type": "big", "end": 7},
|
||||||
|
{"type": "shake", "end": 7},
|
||||||
|
{"type": "screenshake"},
|
||||||
|
{"type": "screenshake"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ButtSmack2": {
|
||||||
|
"text": "UWAH!!",
|
||||||
|
"animation": "SurprisedBack",
|
||||||
|
"effects": [
|
||||||
|
{"type": "big", "end": 6},
|
||||||
|
{"type": "shake", "end": 6},
|
||||||
|
{"type": "screenshake"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ButtSmack3": {
|
||||||
|
"text": "MY REAR END!!",
|
||||||
|
"animation": "SurprisedBack",
|
||||||
|
"effects": [
|
||||||
|
{"type": "big", "end": 13},
|
||||||
|
{"type": "shake", "end": 13},
|
||||||
|
{"type": "screenshake"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ButtSmack4": {
|
||||||
|
"text": "H-HEY!!",
|
||||||
|
"animation": "SurprisedBack",
|
||||||
|
"effects": [
|
||||||
|
{"type": "big", "end": 7},
|
||||||
|
{"type": "shake", "end": 7},
|
||||||
|
{"type": "screenshake"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ButtSmack5": {
|
||||||
|
"text": "O-OUCH!!",
|
||||||
|
"animation": "SurprisedBack",
|
||||||
|
"effects": [
|
||||||
|
{"type": "big", "end": 8},
|
||||||
|
{"type": "shake", "end": 8},
|
||||||
|
{"type": "screenshake"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ButtSmack6": {
|
||||||
|
"text": "G-GENTLE!!",
|
||||||
|
"animation": "SurprisedBack",
|
||||||
|
"effects": [
|
||||||
|
{"type": "big", "end": 10},
|
||||||
|
{"type": "shake", "end": 10},
|
||||||
|
{"type": "screenshake"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ButtSmack7": {
|
||||||
|
"text": "WAH!!",
|
||||||
|
"animation": "SurprisedBack",
|
||||||
|
"effects": [
|
||||||
|
{"type": "big", "end": 5},
|
||||||
|
{"type": "shake", "end": 5},
|
||||||
|
{"type": "screenshake"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ButtSmack8": {"text": "G-goodness!", "animation": "SurprisedBack"},
|
||||||
|
"ButtSmack9": {
|
||||||
|
"text": "Eager, are you?!",
|
||||||
|
"animation": "SurprisedBack",
|
||||||
|
"effects": [
|
||||||
|
{"type": "shake", "end": 16}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ButtSmack10": {
|
||||||
|
"text": "Aren't you bold?!",
|
||||||
|
"animation": "SurprisedBack",
|
||||||
|
"effects": [
|
||||||
|
{"type": "shake", "end": 16}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
<Dialogue DelayTicks="2" BlipDelayTicks="2" SoundRootPath="sfx">
|
|
||||||
<TextPlayback>
|
|
||||||
<Blip>
|
|
||||||
<Sound Path="blip.ogg"/>
|
|
||||||
</Blip>
|
|
||||||
<DelayCharacters>
|
|
||||||
<DelayCharacter Character="." DelayTicks="8" IsDisableTalk="true"/>
|
|
||||||
<DelayCharacter Character="," DelayTicks="8" IsDisableTalk="true"/>
|
|
||||||
<DelayCharacter Character="!" DelayTicks="8" IsDisableTalk="true"/>
|
|
||||||
<DelayCharacter Character="?" DelayTicks="8" IsDisableTalk="true"/>
|
|
||||||
</DelayCharacters>
|
|
||||||
</TextPlayback>
|
|
||||||
<Entries>
|
|
||||||
<Entry Animation="Appear" Label="StartAppear" Next="Start">
|
|
||||||
<Scene/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="Start" Text="Lovely, lovely, lovely!! You must be my new apprentice~!" Animation="Sweet">
|
|
||||||
<Oscillation End="54" Start="40"/>
|
|
||||||
<Oscillation End="22" Start="16"/>
|
|
||||||
<Choice Text="W-wait, huh?!" Next="StartA-1"/>
|
|
||||||
<Choice Text="That's me!" Next="StartB-1"/>
|
|
||||||
<Color ColorR="1" ColorG="0.25" ColorB="0.25" End="54" Start="40"/>
|
|
||||||
<Screenshake End="44" Start="44"/>
|
|
||||||
<Sound End="44" Sound="boom.ogg" Start="44"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="StartA-1" Text="I'm not mistaken! My blazing bright eyes can see into your soul! Without a doubt, you are my apprentice!" Next="StartA-2" Animation="Surprised">
|
|
||||||
<Color ColorR="1" ColorG="0.25" ColorB="0.25" End="103" Start="93"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="StartB-1" Text="Excellent! You've gotten the message! Splendid! So splendid!" Next="StartA-2" Animation="Wicked">
|
|
||||||
<Color ColorR="1" ColorG="0.25" ColorB="0.25" End="103" Start="93"/>
|
|
||||||
<Shake End="59" Start="51"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="StartA-2" Text="Of course you would be! You've got a certain...spirit, about you! Your own ember, burning furiously within you!" Next="StartA-3" Animation="Wicked">
|
|
||||||
<Color ColorR="1" ColorG="0.25" ColorB="0.25" End="53" Start="47"/>
|
|
||||||
<Color ColorR="1" ColorG="0.25" ColorB="0.25" End="81" Start="76"/>
|
|
||||||
<Shake End="100" Start="91"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="StartA-3" Text="Now, I must say...my practice isn't perfect yet. I am still a learner in the Art, and my pursuit is...unique." Animation="Thinking" Next="StartA-4"/>
|
|
||||||
<Entry Label="StartA-4" Text="The school I'm enrolled in...encompasses only me. I'm the founder, and the sole teacher and student! Until you came along, that is. Call me Braixen. I shall be your companion!" Animation="Smug" Next="StartA-5">
|
|
||||||
<Color ColorR="1" ColorG="0.25" ColorB="0.25" End="147" Start="140"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="StartA-5" Text="I should tell you about my philosophy, then! What the Art really is! To master the eternal flame, the unyielding ember, one must embrace it...fully!" Animation="Thinking" Next="StartA-6"/>
|
|
||||||
<Entry Label="StartA-6" Text="You see, my apprentice, what is more "embracing" than using...one's own body for the sake of the Art?! I can't believe no one else has considered such a thing!!" Animation="Excited" Next="StartA-7">
|
|
||||||
<Color ColorR="1" ColorG="0.25" ColorB="0.25" End="76" Start="62"/>
|
|
||||||
<Oscillation End="76" Start="62"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="StartA-7" Text="Listen closely! Your purpose is to turn my thin, meager body into an splendid host of the Art! My stomach a vast, boiling, bubbling cauldron! My physique abundant with heat and energy!" Animation="Stern" Next="StartA-8">
|
|
||||||
<Oscillation End="82" Start="69"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="StartA-8" Text="...hard to understand? Fine, then, I'll use more pedestrian language.
|
|
||||||
I want you to make me fat!
|
|
||||||
As fat as possible!" Animation="Wicked" Next="StartA-9">
|
|
||||||
<Big End="96" Start="69"/>
|
|
||||||
<Color ColorR="1" ColorG="0.25" ColorB="0.25" End="96" Start="92"/>
|
|
||||||
<Color ColorR="1" ColorG="0.25" ColorB="0.25" End="105" Start="101"/>
|
|
||||||
<Screenshake End="93" Start="92"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="StartA-9" Text="Some have called it "ridiculous". Unhealthy. Absurd! Well, I call them lost! Fools who will never truly grasp the reality of the Art!" Animation="Pout" Next="StartA-10">
|
|
||||||
<Shake End="75" Start="71"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="StartA-10" Text="Your role now is to serve me well, and rejoice; for you, my apprentice, shall also scale the mountain of awakening through this!" Animation="Smug" Next="StartA-11"/>
|
|
||||||
<Entry Label="StartA-11" Text="Journey to faraway lands! Battle fierce opponents! Conquer and claim what foodstuffs you can scrounge to stoke the flame inside of my belly!" Animation="Horny" Next="StartA-12">
|
|
||||||
<Color ColorR="1" ColorG="0.25" ColorB="0.25" End="121" Start="105"/>
|
|
||||||
<Shake End="120" Start="105"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="StartA-12" Text="It will require dilligence, perserverance and perhaps...sacrifice. But, my student, following the Art shall yield many...rewards." Animation="Horny" Next="StartA-13">
|
|
||||||
<Color ColorR="1" ColorG="0.25" ColorB="0.25"/>
|
|
||||||
<Shake/>
|
|
||||||
<Animation Animation="Horny" Start="67"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="StartA-13" Text="I shall grace you with permanent authority over my vessel. I do not mind. I trust you. You bare the same ember as I. I know this!" Animation="Smug" Next="StartA-14">
|
|
||||||
<Color ColorR="1" ColorG="0.25" ColorB="0.25"/>
|
|
||||||
<Shake/>
|
|
||||||
<Animation Animation="Horny"/>
|
|
||||||
<Color ColorR="1" ColorG="0.25" ColorB="0.25" End="110" Start="105"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="StartA-14" Text="Now, venture forth! Heed thy call! Feed me! FEED ME!
|
|
||||||
...and do know that I can soothe any urges that may arise from witnessing such splendor." Animation="Wicked">
|
|
||||||
<Shake End="51" Start="44"/>
|
|
||||||
<Color ColorR="0.49803922" ColorG="0.49803922" ColorB="0.49803922" End="142" Start="53"/>
|
|
||||||
<Small End="142" Start="53"/>
|
|
||||||
<Animation Animation="Sweet" Start="5"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Animation="Curious" Label="Help" Text="Yes, my dear apprentice! What do you need assistance with?">
|
|
||||||
<Oscillation End="23" Start="4"/>
|
|
||||||
<Choice Text="You" Next="HelpYou"/>
|
|
||||||
<Choice Text="Choice"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Animation="Shocked" Label="HelpYou" Text="M-Me?! Apprentice, are you...daft? Did you not heed my instructions I spoke to you earlier?" Next="HelpYou2"/>
|
|
||||||
<Entry Animation="Angry" Label="HelpYou2" Text="Your mission is to follow my instructions in the Art! What's the Art?! Bah! Simpleton!" Next="HelpYou3">
|
|
||||||
<Color ColorR="1" ColorG="0.25" ColorB="0.25" End="52" Start="49"/>
|
|
||||||
<Color ColorR="1" ColorG="0.25" ColorB="0.25" End="68" Start="65"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Animation="Angry" Label="HelpYou3" Text="Fire! Flames! Heat! Energy! Mass! That's what the Art is! And to sum it up, I need to embrace it by GETTING FAT!!!">
|
|
||||||
<Screenshake End="110" Start="101"/>
|
|
||||||
<Shake End="121" Start="101"/>
|
|
||||||
<Sound End="110" Start="101" Sound="boom.ogg"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Animation="Angry" Label="HelpYou4" Text="Fire! Flames! Heat! Energy! Mass! That's what the Art is! And to sum it up, I need to embrace it by GETTING FAT!!!">
|
|
||||||
<Screenshake End="110" Start="101"/>
|
|
||||||
<Shake End="121" Start="101"/>
|
|
||||||
<Sound End="110" Start="101" Sound="boom.ogg"/>
|
|
||||||
<Animation Animation="Horny" Start="101"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="ButtSmack" Text="KYAH!!!" Animation="SurprisedBack">
|
|
||||||
<Big End="7"/>
|
|
||||||
<Shake End="7"/>
|
|
||||||
<Screenshake/>
|
|
||||||
<Screenshake/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="ButtSmack2" Text="UWAH!!" Animation="SurprisedBack">
|
|
||||||
<Big End="6"/>
|
|
||||||
<Shake End="6"/>
|
|
||||||
<Screenshake/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="ButtSmack3" Text="MY REAR END!!" Animation="SurprisedBack">
|
|
||||||
<Big End="13"/>
|
|
||||||
<Shake End="13"/>
|
|
||||||
<Screenshake/>
|
|
||||||
</Entry>
|
|
||||||
<Screenshake/>
|
|
||||||
<Entry Label="ButtSmack4" Text="H-HEY!!" Animation="SurprisedBack">
|
|
||||||
<Big End="7"/>
|
|
||||||
<Shake End="7"/>
|
|
||||||
<Screenshake/>
|
|
||||||
</Entry>
|
|
||||||
<Screenshake/>
|
|
||||||
<Entry Label="ButtSmack5" Text="O-OUCH!!" Animation="SurprisedBack">
|
|
||||||
<Big End="8"/>
|
|
||||||
<Shake End="8"/>
|
|
||||||
<Screenshake/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="ButtSmack6" Text="G-GENTLE!!" Animation="SurprisedBack">
|
|
||||||
<Big End="10"/>
|
|
||||||
<Shake End="10"/>
|
|
||||||
<Screenshake/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="ButtSmack7" Text="WAH!!" Animation="SurprisedBack">
|
|
||||||
<Big End="5"/>
|
|
||||||
<Shake End="5"/>
|
|
||||||
<Screenshake/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="ButtSmack8" Text="G-goodness!" Animation="SurprisedBack"/>
|
|
||||||
<Entry Label="ButtSmack9" Text="Eager, are you?!" Animation="SurprisedBack">
|
|
||||||
<Shake End="16"/>
|
|
||||||
</Entry>
|
|
||||||
<Entry Label="ButtSmack10" Text="Aren't you bold?!" Animation="SurprisedBack">
|
|
||||||
<Shake End="16"/>
|
|
||||||
</Entry>
|
|
||||||
</Entries>
|
|
||||||
<Hooks>
|
|
||||||
<Hook Type="Start" Entry="StartAppear"/>
|
|
||||||
</Hooks>
|
|
||||||
<Pool Label="ButtSmack">
|
|
||||||
<Entry Label="ButtSmack"/>
|
|
||||||
<Entry Label="ButtSmack2"/>
|
|
||||||
<Entry Label="ButtSmack3"/>
|
|
||||||
<Entry Label="ButtSmack4"/>
|
|
||||||
<Entry Label="ButtSmack5"/>
|
|
||||||
<Entry Label="ButtSmack6"/>
|
|
||||||
<Entry Label="ButtSmack7"/>
|
|
||||||
<Entry Label="ButtSmack8"/>
|
|
||||||
<Entry Label="ButtSmack9"/>
|
|
||||||
<Entry Label="ButtSmack10"/>
|
|
||||||
</Pool>
|
|
||||||
</Dialogue>
|
|
||||||
@@ -1,76 +1,59 @@
|
|||||||
<AnimatedActor>
|
{
|
||||||
<Info CreatedBy="robot" CreatedOn="07/06/2026 09:29:37 PM" Fps="30" Version="0"/>
|
"anm2ed": 1,
|
||||||
<Content>
|
"info": {"createdOn": "2026-07-06T21:29:37"},
|
||||||
<Spritesheets>
|
"spritesheets": [
|
||||||
<Spritesheet Id="0" Path="area.png"/>
|
{
|
||||||
</Spritesheets>
|
"path": "area.png",
|
||||||
<Layers>
|
"regions": [
|
||||||
<Layer Id="0" Name="BG" SpritesheetId="0"/>
|
{
|
||||||
</Layers>
|
"name": "Main",
|
||||||
<Nulls>
|
"size": [1080, 720],
|
||||||
<Null Id="0" Name="Bounds" ShowRect="true"/>
|
"pivot": [540, 680]
|
||||||
<Null Id="1" Name="ItemSpawn" ShowRect="true"/>
|
}
|
||||||
</Nulls>
|
]
|
||||||
<Events/>
|
}
|
||||||
</Content>
|
],
|
||||||
<Animations DefaultAnimation="Default">
|
"layers": [
|
||||||
<Animation Name="Default" FrameNum="1" Loop="true">
|
{"name": "BG"}
|
||||||
<RootAnimation>
|
],
|
||||||
<Frame XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
"nulls": [
|
||||||
</RootAnimation>
|
{"name": "Bounds", "showRect": true},
|
||||||
<LayerAnimations>
|
{"name": "ItemSpawn", "showRect": true}
|
||||||
<LayerAnimation LayerId="0" Visible="true">
|
],
|
||||||
<Frame XPivot="540" YPivot="680" XCrop="0" YCrop="0" Width="1080" Height="720" XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
"defaultAnimation": "Default",
|
||||||
</LayerAnimation>
|
"animations": {
|
||||||
</LayerAnimations>
|
"Default": {
|
||||||
<NullAnimations>
|
"root": [
|
||||||
<NullAnimation NullId="0" Visible="true">
|
{}
|
||||||
<Frame XPosition="0" YPosition="-275" Delay="1" Visible="true" XScale="800" YScale="600" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
],
|
||||||
</NullAnimation>
|
"layers": [
|
||||||
<NullAnimation NullId="1" Visible="true">
|
{
|
||||||
<Frame XPosition="1" YPosition="-503" Delay="1" Visible="true" XScale="426" YScale="144" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
"layer": 0,
|
||||||
</NullAnimation>
|
"frames": [
|
||||||
</NullAnimations>
|
{"region": 0}
|
||||||
<Triggers/>
|
]
|
||||||
</Animation>
|
}
|
||||||
</Animations>
|
],
|
||||||
<SourceDocument>
|
"nulls": [
|
||||||
<Info CreatedBy="robot" CreatedOn="07/06/2026 09:29:37 PM" Fps="30" Version="0"/>
|
{
|
||||||
<Content>
|
"null": 0,
|
||||||
<Spritesheets>
|
"frames": [
|
||||||
<Spritesheet Id="0" Path="area.png">
|
{
|
||||||
<Region Id="0" Name="Main" XCrop="0" YCrop="0" Width="1080" Height="720" XPivot="540" YPivot="680"/>
|
"pos": [0, -275],
|
||||||
</Spritesheet>
|
"scale": [800, 600]
|
||||||
</Spritesheets>
|
}
|
||||||
<Layers>
|
]
|
||||||
<Layer Id="0" Name="BG" SpritesheetId="0"/>
|
},
|
||||||
</Layers>
|
{
|
||||||
<Nulls>
|
"null": 1,
|
||||||
<Null Id="0" Name="Bounds" ShowRect="true"/>
|
"frames": [
|
||||||
<Null Id="1" Name="ItemSpawn" ShowRect="true"/>
|
{
|
||||||
</Nulls>
|
"pos": [1, -503],
|
||||||
<Events/>
|
"scale": [426, 144]
|
||||||
</Content>
|
}
|
||||||
<Animations DefaultAnimation="Default">
|
]
|
||||||
<Animation Name="Default" FrameNum="1" Loop="true">
|
}
|
||||||
<RootAnimation>
|
]
|
||||||
<Frame XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
}
|
||||||
</RootAnimation>
|
}
|
||||||
<LayerAnimations>
|
}
|
||||||
<LayerAnimation LayerId="0" Visible="true">
|
|
||||||
<Frame RegionId="0" XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
|
||||||
</LayerAnimation>
|
|
||||||
</LayerAnimations>
|
|
||||||
<NullAnimations>
|
|
||||||
<NullAnimation NullId="0" Visible="true">
|
|
||||||
<Frame XPosition="0" YPosition="-275" Delay="1" Visible="true" XScale="800" YScale="600" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
|
||||||
</NullAnimation>
|
|
||||||
<NullAnimation NullId="1" Visible="true">
|
|
||||||
<Frame XPosition="1" YPosition="-503" Delay="1" Visible="true" XScale="426" YScale="144" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
|
||||||
</NullAnimation>
|
|
||||||
</NullAnimations>
|
|
||||||
<Triggers/>
|
|
||||||
</Animation>
|
|
||||||
</Animations>
|
|
||||||
</SourceDocument>
|
|
||||||
</AnimatedActor>
|
|
||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,29 +1,37 @@
|
|||||||
<AnimatedActor>
|
{
|
||||||
<Info CreatedBy="robot" CreatedOn="12/06/2025 11:49:27 PM" Fps="30" Version="0"/>
|
"anm2ed": 1,
|
||||||
<Content>
|
"info": {"createdOn": "2025-12-06T23:49:27"},
|
||||||
<Spritesheets>
|
"spritesheets": [
|
||||||
<Spritesheet Id="0" Path="item_001_poke_puff_basic_sweet.png">
|
{
|
||||||
<Region Id="0" Name="48x48" XCrop="0" YCrop="0" Width="48" Height="48" XPivot="24" YPivot="24"/>
|
"path": "item_001_poke_puff_basic_sweet.png",
|
||||||
</Spritesheet>
|
"regions": [
|
||||||
</Spritesheets>
|
{
|
||||||
<Layers>
|
"name": "48x48",
|
||||||
<Layer Id="0" Name="Main" SpritesheetId="0"/>
|
"size": [48, 48],
|
||||||
</Layers>
|
"pivot": [24, 24]
|
||||||
<Nulls/>
|
}
|
||||||
<Events/>
|
]
|
||||||
</Content>
|
}
|
||||||
<Animations DefaultAnimation="Default">
|
],
|
||||||
<Animation Name="Default" FrameNum="1" Loop="true">
|
"layers": [
|
||||||
<RootAnimation>
|
{"name": "Main"}
|
||||||
<Frame XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="125" YScale="125" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
],
|
||||||
</RootAnimation>
|
"defaultAnimation": "Default",
|
||||||
<LayerAnimations>
|
"animations": {
|
||||||
<LayerAnimation LayerId="0" Visible="true">
|
"Default": {
|
||||||
<Frame XPivot="24" YPivot="24" XCrop="0" YCrop="0" Width="48" Height="48" XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
"root": [
|
||||||
</LayerAnimation>
|
{
|
||||||
</LayerAnimations>
|
"scale": [125, 125]
|
||||||
<NullAnimations/>
|
}
|
||||||
<Triggers/>
|
],
|
||||||
</Animation>
|
"layers": [
|
||||||
</Animations>
|
{
|
||||||
</AnimatedActor>
|
"layer": 0,
|
||||||
|
"frames": [
|
||||||
|
{"region": 0}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,48 @@
|
|||||||
<AnimatedActor>
|
{
|
||||||
<Info CreatedBy="robot" CreatedOn="" Fps="30" Version="0"/>
|
"anm2ed": 1,
|
||||||
<Content>
|
"info": {},
|
||||||
<Spritesheets>
|
"spritesheets": [
|
||||||
<Spritesheet Id="0" Path="circle.png">
|
{
|
||||||
<Region Id="0" Name="Main" XCrop="0" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"path": "circle.png",
|
||||||
</Spritesheet>
|
"regions": [
|
||||||
</Spritesheets>
|
{
|
||||||
<Layers>
|
"name": "Main",
|
||||||
<Layer Id="0" Name="Main" SpritesheetId="0"/>
|
"size": [128, 128],
|
||||||
</Layers>
|
"pivot": [64, 64],
|
||||||
<Nulls/>
|
"origin": "center"
|
||||||
<Events/>
|
}
|
||||||
</Content>
|
]
|
||||||
<Animations DefaultAnimation="Spawn">
|
}
|
||||||
<Animation Name="Spawn" FrameNum="11" Loop="false">
|
],
|
||||||
<RootAnimation>
|
"layers": [
|
||||||
<Frame XPosition="0" YPosition="0" Delay="11" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
{"name": "Main"}
|
||||||
</RootAnimation>
|
],
|
||||||
<LayerAnimations>
|
"defaultAnimation": "Spawn",
|
||||||
<LayerAnimation LayerId="0" Visible="true">
|
"animations": {
|
||||||
<Frame RegionId="0" XPosition="0" YPosition="0" XScale="10" YScale="10" Delay="10" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"Spawn": {
|
||||||
<Frame RegionId="0" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="1" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="0" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"length": 11,
|
||||||
</LayerAnimation>
|
"loop": false,
|
||||||
</LayerAnimations>
|
"root": [
|
||||||
<NullAnimations/>
|
{"delay": 11}
|
||||||
<Triggers/>
|
],
|
||||||
</Animation>
|
"layers": [
|
||||||
</Animations>
|
{
|
||||||
</AnimatedActor>
|
"layer": 0,
|
||||||
|
"frames": [
|
||||||
|
{
|
||||||
|
"region": 0,
|
||||||
|
"scale": [10, 10],
|
||||||
|
"delay": 10,
|
||||||
|
"interpolation": "linear"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"region": 0,
|
||||||
|
"tint": [255, 255, 255, 0],
|
||||||
|
"interpolation": "linear"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,44 +1,99 @@
|
|||||||
<AnimatedActor>
|
{
|
||||||
<Info CreatedBy="robot" CreatedOn="" Fps="30" Version="0"/>
|
"anm2ed": 1,
|
||||||
<Content>
|
"info": {},
|
||||||
<Spritesheets>
|
"spritesheets": [
|
||||||
<Spritesheet Id="0" Path="puff.png">
|
{
|
||||||
<Region Id="0" Name="1" XCrop="0" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"path": "puff.png",
|
||||||
<Region Id="1" Name="2" XCrop="128" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"regions": [
|
||||||
<Region Id="2" Name="3" XCrop="256" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
{
|
||||||
<Region Id="3" Name="4" XCrop="384" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"name": "1",
|
||||||
<Region Id="4" Name="5" XCrop="512" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"size": [128, 128],
|
||||||
<Region Id="5" Name="6" XCrop="640" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"pivot": [64, 64],
|
||||||
<Region Id="6" Name="7" XCrop="768" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"origin": "center"
|
||||||
<Region Id="7" Name="8" XCrop="896" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
},
|
||||||
</Spritesheet>
|
{
|
||||||
</Spritesheets>
|
"name": "2",
|
||||||
<Layers>
|
"crop": [128, 0],
|
||||||
<Layer Id="0" Name="Main" SpritesheetId="0"/>
|
"size": [128, 128],
|
||||||
</Layers>
|
"pivot": [64, 64],
|
||||||
<Nulls/>
|
"origin": "center"
|
||||||
<Events/>
|
},
|
||||||
</Content>
|
{
|
||||||
<Animations DefaultAnimation="Main">
|
"name": "3",
|
||||||
<Animation Name="Main" FrameNum="16" Loop="false">
|
"crop": [256, 0],
|
||||||
<RootAnimation>
|
"size": [128, 128],
|
||||||
<Frame XPosition="0" YPosition="0" Delay="15" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"pivot": [64, 64],
|
||||||
<Frame XPosition="0" YPosition="-5" Delay="1" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="32" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"origin": "center"
|
||||||
</RootAnimation>
|
},
|
||||||
<LayerAnimations>
|
{
|
||||||
<LayerAnimation LayerId="0" Visible="true">
|
"name": "4",
|
||||||
<Frame RegionId="0" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"crop": [384, 0],
|
||||||
<Frame RegionId="1" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"size": [128, 128],
|
||||||
<Frame RegionId="2" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"pivot": [64, 64],
|
||||||
<Frame RegionId="3" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"origin": "center"
|
||||||
<Frame RegionId="4" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
},
|
||||||
<Frame RegionId="5" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
{
|
||||||
<Frame RegionId="6" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"name": "5",
|
||||||
<Frame RegionId="7" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"crop": [512, 0],
|
||||||
</LayerAnimation>
|
"size": [128, 128],
|
||||||
</LayerAnimations>
|
"pivot": [64, 64],
|
||||||
<NullAnimations/>
|
"origin": "center"
|
||||||
<Triggers/>
|
},
|
||||||
</Animation>
|
{
|
||||||
</Animations>
|
"name": "6",
|
||||||
</AnimatedActor>
|
"crop": [640, 0],
|
||||||
|
"size": [128, 128],
|
||||||
|
"pivot": [64, 64],
|
||||||
|
"origin": "center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "7",
|
||||||
|
"crop": [768, 0],
|
||||||
|
"size": [128, 128],
|
||||||
|
"pivot": [64, 64],
|
||||||
|
"origin": "center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "8",
|
||||||
|
"crop": [896, 0],
|
||||||
|
"size": [128, 128],
|
||||||
|
"pivot": [64, 64],
|
||||||
|
"origin": "center"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"layers": [
|
||||||
|
{"name": "Main"}
|
||||||
|
],
|
||||||
|
"defaultAnimation": "Main",
|
||||||
|
"animations": {
|
||||||
|
"Main": {
|
||||||
|
"length": 16,
|
||||||
|
"loop": false,
|
||||||
|
"root": [
|
||||||
|
{"delay": 15, "interpolation": "linear"},
|
||||||
|
{
|
||||||
|
"pos": [0, -5],
|
||||||
|
"tint": [255, 255, 255, 32],
|
||||||
|
"interpolation": "linear"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"layers": [
|
||||||
|
{
|
||||||
|
"layer": 0,
|
||||||
|
"frames": [
|
||||||
|
{"region": 0, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 1, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 2, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 3, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 4, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 5, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 6, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 7, "delay": 2, "interpolation": "linear"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"anm2": "gfx/items/item.anm2",
|
||||||
|
"spritesheet": 0,
|
||||||
|
"spawnDelay": 600,
|
||||||
|
"spawnDelayMultiplier": 0.75,
|
||||||
|
"spawnLimit": 5,
|
||||||
|
"sounds": {
|
||||||
|
"bounce": ["sfx/bounce.ogg", "sfx/bounce2.ogg", "sfx/bounce3.ogg"],
|
||||||
|
"dispose": ["sfx/dispose.ogg"],
|
||||||
|
"summon": ["sfx/summon.ogg"]
|
||||||
|
},
|
||||||
|
"particles": {
|
||||||
|
"dispose": ["Puff"],
|
||||||
|
"summon": ["Puff"]
|
||||||
|
},
|
||||||
|
"categories": {"Food": "edible", "Tool": "tool"},
|
||||||
|
"rarities": {
|
||||||
|
"Common": {"weight": 1.0, "sound": "sfx/common.ogg"},
|
||||||
|
"Uncommon": {"weight": 0.75, "sound": "sfx/uncommon.ogg"},
|
||||||
|
"Rare": {"weight": 0.5, "sound": "sfx/rare.ogg"},
|
||||||
|
"Epic": {"weight": 0.25, "sound": "sfx/epic.ogg"},
|
||||||
|
"Legendary": {"weight": 0.125, "sound": "sfx/legendary.ogg"},
|
||||||
|
"???": {"weight": 0.001, "sound": "sfx/impossible.ogg"},
|
||||||
|
"Special": {"weight": 0.0, "sound": "sfx/special.ogg"}
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{"name": "Poké Puff (Basic, Sweet)", "description": "A basic sweet Poké Puff.", "category": "Food", "rarity": "Common", "flavor": "Sweet", "texture": "gfx/items/item_001_poke_puff_basic_sweet.png", "calories": 100.0},
|
||||||
|
{"name": "Poké Puff (Basic, Mint)", "description": "A basic minty Poké Puff.", "category": "Food", "rarity": "Common", "flavor": "Mint", "texture": "gfx/items/item_002_poke_puff_basic_mint.png", "calories": 100.0},
|
||||||
|
{"name": "Poké Puff (Basic, Citrus)", "description": "A basic citrusy Poké Puff.", "category": "Food", "rarity": "Common", "flavor": "Citrus", "texture": "gfx/items/item_003_poke_puff_basic_citrus.png", "calories": 100.0},
|
||||||
|
{"name": "Poké Puff (Basic, Mocha)", "description": "A basic mocha Poké Puff.", "category": "Food", "rarity": "Common", "flavor": "Mocha", "texture": "gfx/items/item_004_poke_puff_basic_mocha.png", "calories": 100.0},
|
||||||
|
{"name": "Poké Puff (Basic, Spice)", "description": "A basic spice Poké Puff.", "category": "Food", "rarity": "Common", "flavor": "Spice", "texture": "gfx/items/item_005_poke_puff_basic_spice.png", "calories": 100.0},
|
||||||
|
{"name": "Poké Puff (Frosted, Sweet)", "description": "A frosted sweet Poké Puff.", "category": "Food", "rarity": "Uncommon", "flavor": "Sweet", "texture": "gfx/items/item_006_poke_puff_frosted_sweet.png", "calories": 250.0},
|
||||||
|
{"name": "Poké Puff (Frosted, Mint)", "description": "A frosted minty Poké Puff.", "category": "Food", "rarity": "Uncommon", "flavor": "Mint", "texture": "gfx/items/item_007_poke_puff_frosted_mint.png", "calories": 250.0},
|
||||||
|
{"name": "Poké Puff (Frosted, Citrus)", "description": "A frosted citrusy Poké Puff.", "category": "Food", "rarity": "Uncommon", "flavor": "Mint", "texture": "gfx/items/item_008_poke_puff_frosted_citrus.png", "calories": 250.0},
|
||||||
|
{"name": "Poké Puff (Frosted, Mocha)", "description": "A frosted mocha Poké Puff.", "category": "Food", "rarity": "Uncommon", "flavor": "Mocha", "texture": "gfx/items/item_009_poke_puff_frosted_mocha.png", "calories": 250.0},
|
||||||
|
{"name": "Poké Puff (Frosted, Spice)", "description": "A frosted spice Poké Puff.", "category": "Food", "rarity": "Uncommon", "flavor": "Spice", "texture": "gfx/items/item_010_poke_puff_frosted_spice.png", "calories": 250.0},
|
||||||
|
{"name": "Poké Puff (Fancy, Sweet)", "description": "A fancy sweet Poké Puff, adorned with a cherry.", "category": "Food", "rarity": "Rare", "flavor": "Sweet", "texture": "gfx/items/item_011_poke_puff_fancy_sweet.png", "calories": 500.0},
|
||||||
|
{"name": "Poké Puff (Fancy, Mint)", "description": "A fancy minty Poké Puff, adorned with a crescent moon-shaped candy.", "category": "Food", "rarity": "Rare", "flavor": "Mint", "texture": "gfx/items/item_012_poke_puff_fancy_mint.png", "calories": 500.0},
|
||||||
|
{"name": "Poké Puff (Fancy, Citrus)", "description": "A fancy citrus Poké Puff, adorned with an orange slice.", "category": "Food", "rarity": "Rare", "flavor": "Citrus", "texture": "gfx/items/item_013_poke_puff_fancy_citrus.png", "calories": 500.0},
|
||||||
|
{"name": "Poké Puff (Fancy, Mocha)", "description": "A fancy mocha Poké Puff, adorned with a morsel of white chocolate.", "category": "Food", "rarity": "Rare", "flavor": "Mocha", "texture": "gfx/items/item_014_poke_puff_fancy_mocha.png", "calories": 500.0},
|
||||||
|
{"name": "Poké Puff (Fancy, Spice)", "description": "A fancy spice Poké Puff, adorned with a morsel of dark chocolate.", "category": "Food", "rarity": "Rare", "flavor": "Spice", "texture": "gfx/items/item_015_poke_puff_fancy_spice.png", "calories": 500.0},
|
||||||
|
{"name": "Poké Puff (Deluxe, Sweet)", "description": "A deluxe sweet Poké Puff; frosted and adorned with a cherry.", "category": "Food", "rarity": "Epic", "flavor": "Sweet", "texture": "gfx/items/item_016_poke_puff_deluxe_sweet.png", "calories": 1000.0},
|
||||||
|
{"name": "Poké Puff (Deluxe, Mint)", "description": "A deluxe minty Poké Puff; frosted and adorned with a cinnamon stick.", "category": "Food", "rarity": "Epic", "flavor": "Mint", "texture": "gfx/items/item_017_poke_puff_deluxe_mint.png", "calories": 1000.0},
|
||||||
|
{"name": "Poké Puff (Deluxe, Citrus)", "description": "A deluxe citrusy Poké Puff; frosted and adorned with an orange slice.", "category": "Food", "rarity": "Epic", "flavor": "Citrus", "texture": "gfx/items/item_018_poke_puff_deluxe_citrus.png", "calories": 1000.0},
|
||||||
|
{"name": "Poké Puff (Deluxe, Mocha)", "description": "A deluxe mocha Poké Puff; frosted and adorned with a morsel of white chocolate.", "category": "Food", "rarity": "Epic", "flavor": "Mocha", "texture": "gfx/items/item_019_poke_puff_deluxe_mocha.png", "calories": 1000.0},
|
||||||
|
{"name": "Poké Puff (Deluxe, Spice)", "description": "A deluxe spice Poké Puff; frosted and adorned with a morsel of dark chocolate.", "category": "Food", "rarity": "Epic", "flavor": "Spice", "texture": "gfx/items/item_020_poke_puff_deluxe_spice.png", "calories": 1000.0},
|
||||||
|
{"name": "Poké Puff (Supreme Spring)", "description": "A supreme Poké Puff that tastes like the sweet cherry blossoms of spring.", "category": "Food", "rarity": "Legendary", "flavor": "Sweet", "texture": "gfx/items/item_021_poke_puff_supreme_spring.png", "calories": 2500.0},
|
||||||
|
{"name": "Poké Puff (Supreme Summer)", "description": "A supreme Poké Puff that tastes like a tropical summer vacation.", "category": "Food", "rarity": "Legendary", "flavor": "Citrus", "texture": "gfx/items/item_022_poke_puff_supreme_summer.png", "calories": 2500.0},
|
||||||
|
{"name": "Poké Puff (Supreme Autumn)", "description": "A supreme Poké Puff that tastes like a bountiful autumn harvest.", "category": "Food", "rarity": "Legendary", "flavor": "Citrus", "texture": "gfx/items/item_023_poke_puff_supreme_autumn.png", "calories": 2500.0},
|
||||||
|
{"name": "Poké Puff (Supreme Winter)", "description": "A supreme Poké Puff that tastes like a frosty winter wonderland.", "category": "Food", "rarity": "Legendary", "flavor": "Spice", "texture": "gfx/items/item_024_poke_puff_supreme_winter.png", "calories": 2500.0},
|
||||||
|
{"name": "Poké Puff (Supreme Wish)", "description": "A supreme Poké Puff that tastes like a cherished birthday celebration.\nIt also is the biggest calorie nuke ever.\nCan Snivy eat it!?\nAlso, statistically, it might be your birthday today, right?", "category": "Food", "rarity": "???", "flavor": "Spice", "texture": "gfx/items/item_025_poke_puff_supreme_wish.png", "calories": 10000.0},
|
||||||
|
{"name": "Poké Puff (Supreme Honor)", "description": "A supreme Poké Puff that tastes like a monumental victory.\nYou've earned it...if Braixen can eat it!", "category": "Food", "rarity": "Special", "flavor": "Mocha", "texture": "gfx/items/item_026_poke_puff_supreme_honor.png", "calories": 2500.0, "digestionBonus": 0.75},
|
||||||
|
{"name": "Cheri Berry", "description": "A spherical red berry that cures paralysis\n...oh, and it also excites one's stomach.", "category": "Food", "rarity": "Uncommon", "flavor": "Spicy", "texture": "gfx/items/item_027_cheri_berry.png", "calories": 25.0, "digestionBonus": 0.04998},
|
||||||
|
{"name": "Tamato Berry", "description": "A spiny crimson berry that lowers speed and raises friendship.\n...oh, and it also greatly excites one's stomach.", "category": "Food", "rarity": "Rare", "flavor": "Spicy", "texture": "gfx/items/item_028_tamato_berry.png", "calories": 50.0, "digestionBonus": 0.24999},
|
||||||
|
{"name": "Touga Berry", "description": "An obscure, searing-red berry from faraway lands.\nIt cures confusion...and it'll make one's stomach a burning inferno.", "category": "Food", "rarity": "Epic", "flavor": "Spicy", "texture": "gfx/items/item_029_touga_berry.png", "calories": 100.0, "digestionBonus": 0.4998},
|
||||||
|
{"name": "Pomeg Berry", "description": "cG9tZWcgYmVycnkgcG9tZWcgYmVycnkgcG9tZWcgYmVycnk=", "category": "Food", "rarity": "???", "flavor": "None", "texture": "gfx/items/item_030_pomeg_berry.png", "calories": -2500.0, "digestionBonus": 6.0, "gravity": 0.05},
|
||||||
|
{"name": "Shiny Charm", "description": "Can change a Pokémon's color scheme...", "category": "Tool", "rarity": "???", "texture": "gfx/items/item_031_shiny_charm.png", "isToggleSpritesheet": true}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
<ItemSchema BaseSpawnDelayTicks="600" ItemSpawnLimit="5" SpawnDelayStageMultiplier="0.75" TextureRootPath="gfx" SoundRootPath="sfx" BaseAnm2="items/item.anm2">
|
|
||||||
<Actions>
|
|
||||||
<Bounce>
|
|
||||||
<Sound Path="bounce.ogg" />
|
|
||||||
<Sound Path="bounce2.ogg" />
|
|
||||||
<Sound Path="bounce3.ogg" />
|
|
||||||
</Bounce>
|
|
||||||
<Dispose>
|
|
||||||
<Sound Path="dispose.ogg" />
|
|
||||||
<Particle Type="Puff" />
|
|
||||||
</Dispose>
|
|
||||||
<Summon>
|
|
||||||
<Sound Path="summon.ogg" />
|
|
||||||
<Particle Type="Puff" />
|
|
||||||
</Summon>
|
|
||||||
<Return>
|
|
||||||
<Sound Path="return.ogg" />
|
|
||||||
<Particle Type="Puff" />
|
|
||||||
</Return>
|
|
||||||
</Actions>
|
|
||||||
<Categories>
|
|
||||||
<Category Name="Invalid" />
|
|
||||||
<Category Name="Food" UseMode="Edible" />
|
|
||||||
<Category Name="Tool" UseMode="Tool" />
|
|
||||||
</Categories>
|
|
||||||
<Flavors>
|
|
||||||
<Flavor Name="None" />
|
|
||||||
<Flavor Name="Sweet" />
|
|
||||||
<Flavor Name="Bitter" />
|
|
||||||
<Flavor Name="Spicy" />
|
|
||||||
<Flavor Name="Mint" />
|
|
||||||
<Flavor Name="Citrus" />
|
|
||||||
<Flavor Name="Mocha" />
|
|
||||||
<Flavor Name="Spice" />
|
|
||||||
</Flavors>
|
|
||||||
<Rarities>
|
|
||||||
<Rarity Name="Common" Weight="1.0" Sound="common.ogg" />
|
|
||||||
<Rarity Name="Uncommon" Weight="0.75" Sound="uncommon.ogg" />
|
|
||||||
<Rarity Name="Rare" Weight="0.5" Sound="rare.ogg" />
|
|
||||||
<Rarity Name="Epic" Weight="0.25" Sound="epic.ogg" />
|
|
||||||
<Rarity Name="Legendary" Weight="0.125" Sound="legendary.ogg" />
|
|
||||||
<Rarity Name="???" Weight="0.001" IsHiddenInCatalog="true" Sound="impossible.ogg" />
|
|
||||||
<Rarity Name="Special" Weight="0.0" IsHiddenInCatalog="true" Sound="special.ogg" />
|
|
||||||
</Rarities>
|
|
||||||
<Items TextureRootPath="gfx/items" SpritesheetID="0">
|
|
||||||
<Item Name="Poké Puff (Basic, Sweet)" Texture="item_001_poke_puff_basic_sweet.png" Description="A basic sweet Poké Puff." Category="Food" Flavor="Sweet" Rarity="Common" Calories="100.0" />
|
|
||||||
<Item Name="Poké Puff (Basic, Mint)" Texture="item_002_poke_puff_basic_mint.png" Description="A basic minty Poké Puff." Category="Food" Flavor="Mint" Rarity="Common" Calories="100.0" />
|
|
||||||
<Item Name="Poké Puff (Basic, Citrus)" Texture="item_003_poke_puff_basic_citrus.png" Description="A basic citrusy Poké Puff." Category="Food" Flavor="Citrus" Rarity="Common" Calories="100.0" />
|
|
||||||
<Item Name="Poké Puff (Basic, Mocha)" Texture="item_004_poke_puff_basic_mocha.png" Description="A basic mocha Poké Puff." Category="Food" Flavor="Mocha" Rarity="Common" Calories="100.0" />
|
|
||||||
<Item Name="Poké Puff (Basic, Spice)" Texture="item_005_poke_puff_basic_spice.png" Description="A basic spice Poké Puff." Category="Food" Flavor="Spice" Rarity="Common" Calories="100.0" />
|
|
||||||
<Item Name="Poké Puff (Frosted, Sweet)" Texture="item_006_poke_puff_frosted_sweet.png" Description="A frosted sweet Poké Puff." Category="Food" Flavor="Sweet" Rarity="Uncommon" Calories="250.0" />
|
|
||||||
<Item Name="Poké Puff (Frosted, Mint)" Texture="item_007_poke_puff_frosted_mint.png" Description="A frosted minty Poké Puff." Category="Food" Flavor="Mint" Rarity="Uncommon" Calories="250.0" />
|
|
||||||
<Item Name="Poké Puff (Frosted, Citrus)" Texture="item_008_poke_puff_frosted_citrus.png" Description="A frosted citrusy Poké Puff." Category="Food" Flavor="Mint" Rarity="Uncommon" Calories="250.0" />
|
|
||||||
<Item Name="Poké Puff (Frosted, Mocha)" Texture="item_009_poke_puff_frosted_mocha.png" Description="A frosted mocha Poké Puff." Category="Food" Flavor="Mocha" Rarity="Uncommon" Calories="250.0" />
|
|
||||||
<Item Name="Poké Puff (Frosted, Spice)" Texture="item_010_poke_puff_frosted_spice.png" Description="A frosted spice Poké Puff." Category="Food" Flavor="Spice" Rarity="Uncommon" Calories="250.0" />
|
|
||||||
<Item Name="Poké Puff (Fancy, Sweet)" Texture="item_011_poke_puff_fancy_sweet.png" Description="A fancy sweet Poké Puff, adorned with a cherry." Category="Food" Flavor="Sweet" Rarity="Rare" Calories="500.0" />
|
|
||||||
<Item Name="Poké Puff (Fancy, Mint)" Texture="item_012_poke_puff_fancy_mint.png" Description="A fancy minty Poké Puff, adorned with a crescent moon-shaped candy." Category="Food" Flavor="Mint" Rarity="Rare" Calories="500.0" />
|
|
||||||
<Item Name="Poké Puff (Fancy, Citrus)" Texture="item_013_poke_puff_fancy_citrus.png" Description="A fancy citrus Poké Puff, adorned with an orange slice." Category="Food" Flavor="Citrus" Rarity="Rare" Calories="500.0" />
|
|
||||||
<Item Name="Poké Puff (Fancy, Mocha)" Texture="item_014_poke_puff_fancy_mocha.png" Description="A fancy mocha Poké Puff, adorned with a morsel of white chocolate." Category="Food" Flavor="Mocha" Rarity="Rare" Calories="500.0" />
|
|
||||||
<Item Name="Poké Puff (Fancy, Spice)" Texture="item_015_poke_puff_fancy_spice.png" Description="A fancy spice Poké Puff, adorned with a morsel of dark chocolate." Category="Food" Flavor="Spice" Rarity="Rare" Calories="500.0" />
|
|
||||||
<Item Name="Poké Puff (Deluxe, Sweet)" Texture="item_016_poke_puff_deluxe_sweet.png" Description="A deluxe sweet Poké Puff; frosted and adorned with a cherry." Category="Food" Flavor="Sweet" Rarity="Epic" Calories="1000.0" />
|
|
||||||
<Item Name="Poké Puff (Deluxe, Mint)" Texture="item_017_poke_puff_deluxe_mint.png" Description="A deluxe minty Poké Puff; frosted and adorned with a cinnamon stick." Category="Food" Flavor="Mint" Rarity="Epic" Calories="1000.0" />
|
|
||||||
<Item Name="Poké Puff (Deluxe, Citrus)" Texture="item_018_poke_puff_deluxe_citrus.png" Description="A deluxe citrusy Poké Puff; frosted and adorned with an orange slice." Category="Food" Flavor="Citrus" Rarity="Epic" Calories="1000.0" />
|
|
||||||
<Item Name="Poké Puff (Deluxe, Mocha)" Texture="item_019_poke_puff_deluxe_mocha.png" Description="A deluxe mocha Poké Puff; frosted and adorned with a morsel of white chocolate." Category="Food" Flavor="Mocha" Rarity="Epic" Calories="1000.0" />
|
|
||||||
<Item Name="Poké Puff (Deluxe, Spice)" Texture="item_020_poke_puff_deluxe_spice.png" Description="A deluxe spice Poké Puff; frosted and adorned with a morsel of dark chocolate." Category="Food" Flavor="Spice" Rarity="Epic" Calories="1000.0" />
|
|
||||||
<Item Name="Poké Puff (Supreme Spring)" Texture="item_021_poke_puff_supreme_spring.png" Description="A supreme Poké Puff that tastes like the sweet cherry blossoms of spring." Category="Food" Flavor="Sweet" Rarity="Legendary" Calories="2500.0" />
|
|
||||||
<Item Name="Poké Puff (Supreme Summer)" Texture="item_022_poke_puff_supreme_summer.png" Description="A supreme Poké Puff that tastes like a tropical summer vacation." Category="Food" Flavor="Citrus" Rarity="Legendary" Calories="2500.0" />
|
|
||||||
<Item Name="Poké Puff (Supreme Autumn)" Texture="item_023_poke_puff_supreme_autumn.png" Description="A supreme Poké Puff that tastes like a bountiful autumn harvest." Category="Food" Flavor="Citrus" Rarity="Legendary" Calories="2500.0" />
|
|
||||||
<Item Name="Poké Puff (Supreme Winter)" Texture="item_024_poke_puff_supreme_winter.png" Description="A supreme Poké Puff that tastes like a frosty winter wonderland." Category="Food" Flavor="Spice" Rarity="Legendary" Calories="2500.0" />
|
|
||||||
<Item Name="Poké Puff (Supreme Wish)" Texture="item_025_poke_puff_supreme_wish.png" Description="A supreme Poké Puff that tastes like a cherished birthday celebration. It also is the biggest calorie nuke ever. Can Snivy eat it!? Also, statistically, it might be your birthday today, right?" Category="Food" Flavor="Spice" Rarity="???" Calories="10000.0" />
|
|
||||||
<Item Name="Poké Puff (Supreme Honor)" Texture="item_026_poke_puff_supreme_honor.png" Description="A supreme Poké Puff that tastes like a monumental victory. You've earned it...if Braixen can eat it!" Category="Food" Flavor="Mocha" Rarity="Special" Calories="2500.0" DigestionBonus="0.0125" />
|
|
||||||
<Item Name="Cheri Berry" Texture="item_027_cheri_berry.png" Description="A spherical red berry that cures paralysis ...oh, and it also excites one's stomach." Category="Food" Flavor="Spicy" Rarity="Uncommon" Calories="25.0" DigestionBonus="0.000833" />
|
|
||||||
<Item Name="Tamato Berry" Texture="item_028_tamato_berry.png" Description="A spiny crimson berry that lowers speed and raises friendship. ...oh, and it also greatly excites one's stomach." Category="Food" Flavor="Spicy" Rarity="Rare" Calories="50.0" DigestionBonus="0.0041665" />
|
|
||||||
<Item Name="Touga Berry" Texture="item_029_touga_berry.png" Description="An obscure, searing-red berry from faraway lands. It cures confusion...and it'll make one's stomach a burning inferno." Category="Food" Flavor="Spicy" Rarity="Epic" Calories="100.0" DigestionBonus="0.00833" />
|
|
||||||
<Item Name="Pomeg Berry" Texture="item_030_pomeg_berry.png" Description="cG9tZWcgYmVycnkgcG9tZWcgYmVycnkgcG9tZWcgYmVycnk=" Category="Food" Flavor="None" Rarity="???" Calories="-2500.0" DigestionBonus="0.1" Gravity="0.05" />
|
|
||||||
<Item Name="Shiny Charm" Texture="item_031_shiny_charm.png" Description="Can change a Pokémon's color scheme..." Category="Tool" Rarity="???" IsToggleSpritesheet="true" />
|
|
||||||
</Items>
|
|
||||||
</ItemSchema>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<Menu SoundRootPath="sfx" FontRootPath="font" Font="font.ttf" IconsFont="icons.ttf" ButtonRounding="10.0">
|
|
||||||
<Home GlyphFromIconFont="1" />
|
|
||||||
<Actions>
|
|
||||||
<Open>
|
|
||||||
<Sound Path="open.ogg" />
|
|
||||||
</Open>
|
|
||||||
<Close>
|
|
||||||
<Sound Path="close.ogg" />
|
|
||||||
</Close>
|
|
||||||
<Hover>
|
|
||||||
<Sound Path="hover.ogg" />
|
|
||||||
<Sound Path="hover2.ogg" />
|
|
||||||
<Sound Path="hover3.ogg" />
|
|
||||||
</Hover>
|
|
||||||
<Select>
|
|
||||||
<Sound Path="select.ogg" />
|
|
||||||
</Select>
|
|
||||||
<CheatsActivated>
|
|
||||||
<Sound Path="cheatsActivated.ogg" />
|
|
||||||
</CheatsActivated>
|
|
||||||
</Actions>
|
|
||||||
</Menu>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<Particles Anm2RootPath="gfx/particle">
|
|
||||||
<Particle Type="Circle" Anm2="circle.anm2" Animation="Main" />
|
|
||||||
<Particle Type="Puff" Anm2="puff.anm2" Animation="Main" />
|
|
||||||
</Particles>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<Areas TextureRootPath="gfx/areas" BaseAnm2="area.anm2" WorldBoundsNull="Bounds" ItemSpawnNull="ItemSpawn">
|
|
||||||
<Area Texture="area.png" Gravity="0.95" Friction="0.75" AirResistance="0.975" />
|
|
||||||
</Areas>
|
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
{
|
||||||
|
"name": "Princess Mini",
|
||||||
|
"description": "A sprightly, pampered, petite princess. Don't call her \"Mint\"!",
|
||||||
|
"credits": "Art, Animation, Dialogue: ShweetMagnet Character: 2x/MerchantAnon Sound Effects: JC Sounds (CC-BY 4.0), artisticdude (CC0), Kronbits (CC0), Vehicle (CC0), pixabay.org, ShweetMagnet Font: DPComic",
|
||||||
|
"render": "gfx/preview/render.png",
|
||||||
|
"portrait": "gfx/preview/portrait.png",
|
||||||
|
"anm2": "gfx/character/mini.anm2",
|
||||||
|
"color": [0.188, 0.651, 0.424],
|
||||||
|
"weight": 40,
|
||||||
|
"weightMax": 100,
|
||||||
|
"capacity": 1250,
|
||||||
|
"capacityMin": 1250,
|
||||||
|
"capacityMax": 10000,
|
||||||
|
"overstuffedMultiplier": 1.5,
|
||||||
|
"overstuffedDigestBonus": 0.2,
|
||||||
|
"caloriesToKilogram": 0.001,
|
||||||
|
"digestionRate": 1.5,
|
||||||
|
"digestionRateMax": 4.5,
|
||||||
|
"digestionTime": 120,
|
||||||
|
"blinkChance": 0.5,
|
||||||
|
"gurgleChance": 0.0005,
|
||||||
|
"gurgleCapacityMultiplier": 10,
|
||||||
|
"dialoguePool": "Stage1",
|
||||||
|
"stages": [
|
||||||
|
{"weight": 45, "dialoguePool": "Stage2", "animationSuffix": "Stage2"},
|
||||||
|
{"weight": 60, "dialoguePool": "Stage3", "animationSuffix": "Stage3"},
|
||||||
|
{"weight": 70, "dialoguePool": "Stage4", "animationSuffix": "Stage4"},
|
||||||
|
{"weight": 90, "dialoguePool": "Stage5", "animationSuffix": "Stage5"}
|
||||||
|
],
|
||||||
|
"animations": {"start": "Appear", "idle": "Neutral", "idleFull": "NeutralFull", "stageUp": "StageUp"},
|
||||||
|
"talk": {"source": "Talk", "destination": "Mouth"},
|
||||||
|
"blink": {"source": "Blink", "destination": "Eye"},
|
||||||
|
"eatAreas": [
|
||||||
|
{"null": "Mouth", "animation": "Eat", "event": "Eat"}
|
||||||
|
],
|
||||||
|
"expandAreas": [
|
||||||
|
{"layer": "Torso", "null": "Belly", "scaleMax": 2.5}
|
||||||
|
],
|
||||||
|
"sounds": {
|
||||||
|
"digest": ["sfx/digest1.ogg", "sfx/digest2.ogg", "sfx/digest3.ogg"],
|
||||||
|
"gurgle": ["sfx/gurgle1.ogg", "sfx/gurgle2.ogg", "sfx/gurgle3.ogg", "sfx/gurgle4.ogg", "sfx/gurgle5.ogg"]
|
||||||
|
},
|
||||||
|
"interactTypes": {
|
||||||
|
"Rub": {
|
||||||
|
"glyph": "2",
|
||||||
|
"isHold": true,
|
||||||
|
"cursorHover": "Hover",
|
||||||
|
"cursorActive": "Rub",
|
||||||
|
"sounds": ["sfx/rub.ogg"],
|
||||||
|
"particles": ["Circle"]
|
||||||
|
},
|
||||||
|
"Smack": {
|
||||||
|
"glyph": "3",
|
||||||
|
"cursorHover": "SmackHover",
|
||||||
|
"cursorActive": "Smack",
|
||||||
|
"dialogueCooldown": 40,
|
||||||
|
"digestionClick": 0.25,
|
||||||
|
"scaleAmplitude": 1,
|
||||||
|
"scaleCycles": 3,
|
||||||
|
"sounds": ["sfx/smack1.ogg", "sfx/smack2.ogg", "sfx/smack3.ogg", "sfx/smack4.ogg", "sfx/smack5.ogg"],
|
||||||
|
"particles": ["Circle"]
|
||||||
|
},
|
||||||
|
"Kiss": {
|
||||||
|
"glyph": "4",
|
||||||
|
"cursorHover": "KissHover",
|
||||||
|
"cursorActive": "Kiss",
|
||||||
|
"sounds": ["sfx/kiss1.ogg", "sfx/kiss2.ogg", "sfx/kiss3.ogg", "sfx/kiss4.ogg", "sfx/kiss5.ogg"],
|
||||||
|
"particles": ["Circle"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interactAreas": [
|
||||||
|
{"type": "Rub", "null": "Head", "dialoguePool": "HeadRub", "dialoguePoolFull": "HeadRubFull"},
|
||||||
|
{"type": "Rub", "null": "Belly", "cursorActive": "Grab", "dialoguePool": "BellyRub", "dialoguePoolFull": "BellyRubFull", "digestionHover": 0.1},
|
||||||
|
{"type": "Kiss", "null": "Mouth", "dialoguePool": "MouthKiss", "dialoguePoolFull": "MouthKiss"},
|
||||||
|
{"type": "Kiss", "null": "Belly", "dialoguePool": "BellyKiss", "dialoguePoolFull": "BellyKiss"},
|
||||||
|
{"type": "Smack", "null": "Belly", "layer": "Torso", "dialoguePool": "BellySmack", "dialoguePoolFull": "BellySmack"}
|
||||||
|
],
|
||||||
|
"areas": {
|
||||||
|
"anm2": "gfx/areas/area.anm2",
|
||||||
|
"boundsNull": "Bounds",
|
||||||
|
"spawnNull": "ItemSpawn",
|
||||||
|
"list": [
|
||||||
|
{"gravity": 0.95, "friction": 0.75, "airResistance": 0.975}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cursor": {
|
||||||
|
"anm2": "gfx/cursor/cursor.anm2",
|
||||||
|
"animations": {"idle": "Default", "click": "Click", "hover": "Hover", "grab": "Grab", "pan": "Pan", "zoom": "Zoom", "dispose": "Dispose"},
|
||||||
|
"sounds": {
|
||||||
|
"grab": ["sfx/grab.ogg"],
|
||||||
|
"release": ["sfx/release.ogg"],
|
||||||
|
"throw": ["sfx/throw1.ogg", "sfx/throw2.ogg", "sfx/throw3.ogg"]
|
||||||
|
},
|
||||||
|
"particles": {
|
||||||
|
"grab": ["Circle"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"font": "font/font.ttf",
|
||||||
|
"iconsFont": "font/icons.ttf",
|
||||||
|
"rounding": 10.0,
|
||||||
|
"homeGlyph": "1",
|
||||||
|
"sounds": {
|
||||||
|
"open": ["sfx/open.ogg"],
|
||||||
|
"close": ["sfx/close.ogg"],
|
||||||
|
"hover": ["sfx/hover1.ogg", "sfx/hover2.ogg", "sfx/hover3.ogg"],
|
||||||
|
"select": ["sfx/select.ogg"],
|
||||||
|
"cheats": ["sfx/cheatsActivated.ogg"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"particles": {
|
||||||
|
"Circle": {"anm2": "gfx/particle/circle.anm2", "animation": "Main"},
|
||||||
|
"Puff": {"anm2": "gfx/particle/puff.anm2", "animation": "Main"}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
<Character Name="Princess Mini" TextureRootPath="gfx" SoundRootPath="sfx" Render="preview/render.png" Portrait="preview/portrait.png" Anm2="character/mini.anm2" Description="A sprightly, pampered, petite princess. Don't call her "Mint"!" Credits="Art, Animation, Dialogue: ShweetMagnet Character: 2x/MerchantAnon Sound Effects: JC Sounds (CC-BY 4.0), artisticdude (CC0), Kronbits (CC0), Vehicle (CC0), pixabay.org, ShweetMagnet Font: DPComic" WeightKilograms="40" WeightMaxKilograms="100" CapacityCalories="1250" CapacityMinCalories="1250" CapacityMaxCalories="10000" CapacityOverstuffedMultiplier="1.5" CapacityIfOverStuffedOnDigestBonus="0.2" CaloriesToKilogram="0.001" DigestionRatePerSecond="1.5" DigestionRatePerSecondMax="4.5" DigestionTimerMax="120" BlinkChance="0.5" GurgleChance="0.00050000002" GurgleChanceFromCapacityMultiplier="10" DialoguePool="Stage1" ColorR="0.18799999" ColorG="0.65100002" ColorB="0.42399999">
|
|
||||||
<Stages>
|
|
||||||
<Stage ThresholdKilograms="45" DialoguePool="Stage2" AppendStringForAnimations="Stage2" />
|
|
||||||
<Stage ThresholdKilograms="60" DialoguePool="Stage3" AppendStringForAnimations="Stage3" />
|
|
||||||
<Stage ThresholdKilograms="70" DialoguePool="Stage4" AppendStringForAnimations="Stage4" />
|
|
||||||
<Stage ThresholdKilograms="90" DialoguePool="Stage5" AppendStringForAnimations="Stage5" />
|
|
||||||
</Stages>
|
|
||||||
<Animations>
|
|
||||||
<Start Animation="Appear" />
|
|
||||||
<Idle Animation="Neutral" />
|
|
||||||
<IdleFull Animation="NeutralFull" />
|
|
||||||
<StageUp Animation="StageUp" />
|
|
||||||
</Animations>
|
|
||||||
<AnimationOverrides>
|
|
||||||
<Talk LayerSource="Talk" LayerDestination="Mouth" />
|
|
||||||
<Blink LayerSource="Blink" LayerDestination="Eye" />
|
|
||||||
</AnimationOverrides>
|
|
||||||
<EatAreas>
|
|
||||||
<EatArea Null="Mouth" Animation="Eat" EventToActivate="Eat" />
|
|
||||||
</EatAreas>
|
|
||||||
<Actions>
|
|
||||||
<Digest>
|
|
||||||
<Sound Path="digest1.ogg" />
|
|
||||||
<Sound Path="digest2.ogg" />
|
|
||||||
<Sound Path="digest3.ogg" />
|
|
||||||
</Digest>
|
|
||||||
<Gurgle>
|
|
||||||
<Sound Path="gurgle1.ogg" />
|
|
||||||
<Sound Path="gurgle2.ogg" />
|
|
||||||
<Sound Path="gurgle3.ogg" />
|
|
||||||
<Sound Path="gurgle4.ogg" />
|
|
||||||
<Sound Path="gurgle5.ogg" />
|
|
||||||
</Gurgle>
|
|
||||||
</Actions>
|
|
||||||
<ExpandAreas>
|
|
||||||
<ExpandArea Layer="Torso" Null="Belly" ScaleMultiplierMaximum="2.5" />
|
|
||||||
</ExpandAreas>
|
|
||||||
<InteractAreaTypes>
|
|
||||||
<InteractArea Type="Rub" GlyphFromIconFont="2" IsHold="true" AnimationCursorHover="Hover" AnimationCursorActive="Rub">
|
|
||||||
<Sound Path="rub.ogg" />
|
|
||||||
<Particle Type="Circle" />
|
|
||||||
</InteractArea>
|
|
||||||
<InteractArea Type="Smack" GlyphFromIconFont="3" AnimationCursorHover="SmackHover" AnimationCursorActive="Smack" TimeToActivateNewDialogueTicks="40" ScaleEffectAmplitude="1" ScaleEffectCycles="3" DigestionBonusOnClick="0.25">
|
|
||||||
<Sound Path="smack1.ogg" />
|
|
||||||
<Sound Path="smack2.ogg" />
|
|
||||||
<Sound Path="smack3.ogg" />
|
|
||||||
<Sound Path="smack4.ogg" />
|
|
||||||
<Sound Path="smack5.ogg" />
|
|
||||||
<Particle Type="Circle" />
|
|
||||||
</InteractArea>
|
|
||||||
<InteractArea Type="Kiss" GlyphFromIconFont="4" AnimationCursorHover="KissHover" AnimationCursorActive="Kiss">
|
|
||||||
<Sound Path="kiss1.ogg" />
|
|
||||||
<Sound Path="kiss2.ogg" />
|
|
||||||
<Sound Path="kiss3.ogg" />
|
|
||||||
<Sound Path="kiss4.ogg" />
|
|
||||||
<Sound Path="kiss5.ogg" />
|
|
||||||
<Particle Type="Circle" />
|
|
||||||
</InteractArea>
|
|
||||||
</InteractAreaTypes>
|
|
||||||
<InteractAreas>
|
|
||||||
<InteractArea Type="Rub" Null="Head" DialoguePool="HeadRub" DialoguePoolFull="HeadRubFull" />
|
|
||||||
<InteractArea Type="Rub" Null="Belly" AnimationCursorActive="Grab" DialoguePool="BellyRub" DigestionBonusOnHover="0.1" DialoguePoolFull="BellyRubFull" />
|
|
||||||
<InteractArea Type="Kiss" Null="Mouth" DialoguePool="MouthKiss" DialoguePoolFull="MouthKiss" />
|
|
||||||
<InteractArea Type="Kiss" Null="Belly" DialoguePool="BellyKiss" DialoguePoolFull="BellyKiss" />
|
|
||||||
<InteractArea Type="Smack" Null="Belly" DialoguePool="BellySmack" Layer="Torso" DialoguePoolFull="BellySmack" />
|
|
||||||
</InteractAreas>
|
|
||||||
</Character>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<Cursor TextureRootPath="gfx" SoundRootPath="sfx" Anm2="cursor/cursor.anm2">
|
|
||||||
<Actions>
|
|
||||||
<Idle Animation="Default" />
|
|
||||||
<Click Animation="Click" />
|
|
||||||
<Hover Animation="Hover" />
|
|
||||||
<Grab Animation="Grab">
|
|
||||||
<Sound Path="grab.ogg" />
|
|
||||||
<Particle Type="Circle" />
|
|
||||||
</Grab>
|
|
||||||
<Release>
|
|
||||||
<Sound Path="release.ogg" />
|
|
||||||
</Release>
|
|
||||||
<Pan Animation="Pan" />
|
|
||||||
<Zoom Animation="Zoom" />
|
|
||||||
<Dispose Animation="Dispose" />
|
|
||||||
<Throw>
|
|
||||||
<Sound Path="throw1.ogg" />
|
|
||||||
<Sound Path="throw2.ogg" />
|
|
||||||
<Sound Path="throw3.ogg" />
|
|
||||||
</Throw>
|
|
||||||
</Actions>
|
|
||||||
</Cursor>
|
|
||||||
@@ -0,0 +1,810 @@
|
|||||||
|
{
|
||||||
|
"delay": 2,
|
||||||
|
"blipDelay": 3,
|
||||||
|
"blip": ["sfx/blip.ogg"],
|
||||||
|
"pauses": {
|
||||||
|
".": {"delay": 8, "isSilent": true},
|
||||||
|
"!": {"delay": 8, "isSilent": true},
|
||||||
|
",": {"delay": 8, "isSilent": true},
|
||||||
|
"-": {"delay": 8, "isSilent": true},
|
||||||
|
"\"": {"delay": 8, "isSilent": true}
|
||||||
|
},
|
||||||
|
"hooks": {"Start": "StartBegin", "End": "End", "Help": "Help", "StageUp": "StageUp", "Random": "Random", "Feed": "Feed", "FeedFull": "FeedFull", "Eat": "Eat", "EatFull": "EatFull", "Full": "Full", "Throw": "Throw", "LowCapacity": "LowCapacity", "Digest": "Digest", "FoodTaken": "FoodTaken", "FoodTakenFull": "FoodTakenFull"},
|
||||||
|
"pools": {
|
||||||
|
"Stage1": ["Stage1-1", "Stage1-2", "Stage1-3"],
|
||||||
|
"Stage2": ["Stage2-1", "Stage2-2", "Stage2-3"],
|
||||||
|
"Stage3": ["Stage3-1", "Stage3-2", "Stage3-3"],
|
||||||
|
"Stage4": ["Stage4-1", "Stage4-2", "Stage4-3"],
|
||||||
|
"Stage5": ["Stage5-1", "Stage5-2", "Stage5-3"],
|
||||||
|
"Random": ["Random1", "Random2", "Random3", "Random4", "Random5", "Random6", "Random7", "Random8", "Random9", "Random10", "Random11", "Random12", "Random13", "Random14", "Random15", "Random16", "Random17", "Random18", "Random19", "Random20", "Random21", "Random22", "Random23", "Random24", "Random25", "Random26", "Random27"],
|
||||||
|
"Digest": ["Digest-1", "Digest-2", "Digest-3", "Digest-4", "Digest-5", "Digest-6", "Digest-7", "Digest-8", "Digest-9", "Digest-10", "Digest-11", "Digest-12", "Digest-13", "Digest-14", "Digest-15"],
|
||||||
|
"Throw": ["Throw-1", "Throw-2", "Throw-3", "Throw-4", "Throw-5", "Throw-6", "Throw-7", "Throw-8", "Throw-9", "Throw-10", "Throw-11", "Throw-12", "Throw-13", "Throw-14", "Throw-15"],
|
||||||
|
"Feed": ["Feed-1", "Feed-2", "Feed-3", "Feed-4", "Feed-5", "Feed-6", "Feed-7", "Feed-8", "Feed-9", "Feed-10", "Feed-11", "Feed-12", "Feed-13", "Feed-14", "Feed-15"],
|
||||||
|
"FeedFull": ["FeedFull-1", "FeedFull-2", "FeedFull-3", "FeedFull-4", "FeedFull-5", "FeedFull-6", "FeedFull-7", "FeedFull-8", "FeedFull-9", "FeedFull-10", "FeedFull-11", "FeedFull-12", "FeedFull-13", "FeedFull-14", "FeedFull-15"],
|
||||||
|
"Eat": ["Eat-1", "Eat-2", "Eat-3", "Eat-4", "Eat-5", "Eat-6", "Eat-7", "Eat-8", "Eat-9", "Eat-10", "Eat-11", "Eat-12", "Eat-13", "Eat-14", "Eat-15"],
|
||||||
|
"EatFull": ["EatFull-1", "EatFull-2", "EatFull-3", "EatFull-4", "EatFull-5", "EatFull-6", "EatFull-7", "EatFull-8", "EatFull-9", "EatFull-10", "EatFull-11", "EatFull-12", "EatFull-13", "EatFull-14", "EatFull-15"],
|
||||||
|
"Full": ["Full-1", "Full-2", "Full-3", "Full-4", "Full-5", "Full-6", "Full-7", "Full-8", "Full-9", "Full-10", "Full-11", "Full-12", "Full-13", "Full-14", "Full-15"],
|
||||||
|
"LowCapacity": ["LowCapacity-1", "LowCapacity-2", "LowCapacity-3", "LowCapacity-4", "LowCapacity-5", "LowCapacity-6", "LowCapacity-7", "LowCapacity-8", "LowCapacity-9", "LowCapacity-10", "LowCapacity-11", "LowCapacity-12", "LowCapacity-13", "LowCapacity-14", "LowCapacity-15"],
|
||||||
|
"FoodTaken": ["FoodTaken-1", "FoodTaken-2", "FoodTaken-3", "FoodTaken-4", "FoodTaken-5", "FoodTaken-6", "FoodTaken-7", "FoodTaken-8", "FoodTaken-9", "FoodTaken-10", "FoodTaken-11", "FoodTaken-12", "FoodTaken-13", "FoodTaken-14", "FoodTaken-15"],
|
||||||
|
"FoodTakenFull": ["FoodTakenFull-1", "FoodTakenFull-2", "FoodTakenFull-3", "FoodTakenFull-4", "FoodTakenFull-5", "FoodTakenFull-6", "FoodTakenFull-7", "FoodTakenFull-8", "FoodTakenFull-9", "FoodTakenFull-10", "FoodTakenFull-11", "FoodTakenFull-12", "FoodTakenFull-13", "FoodTakenFull-14", "FoodTakenFull-15"],
|
||||||
|
"StageUp": ["StageUp-1", "StageUp-2", "StageUp-3", "StageUp-4", "StageUp-5", "StageUp-6", "StageUp-7", "StageUp-8", "StageUp-9", "StageUp-10", "StageUp-11", "StageUp-12", "StageUp-13", "StageUp-14", "StageUp-15"],
|
||||||
|
"MouthKiss": ["MouthKiss-1", "MouthKiss-2", "MouthKiss-3", "MouthKiss-4", "MouthKiss-5", "MouthKiss-6", "MouthKiss-7", "MouthKiss-8", "MouthKiss-9", "MouthKiss-10"],
|
||||||
|
"BellyKiss": ["BellyKiss-1", "BellyKiss-2", "BellyKiss-3", "BellyKiss-4", "BellyKiss-5"],
|
||||||
|
"HeadRub": ["HeadRub-1", "HeadRub-2", "HeadRub-3", "HeadRub-4", "HeadRub-5"],
|
||||||
|
"BellyRub": ["BellyRub-1", "BellyRub-2", "BellyRub-3", "BellyRub-4", "BellyRub-5"],
|
||||||
|
"BellySmack": ["BellySmack-1", "BellySmack-2", "BellySmack-3", "BellySmack-4", "BellySmack-5"],
|
||||||
|
"HeadRubFull": ["HeadRubFull-1", "HeadRubFull-2", "HeadRubFull-3", "HeadRubFull-4", "HeadRubFull-5"],
|
||||||
|
"BellyRubFull": ["BellyRubFull-1", "BellyRubFull-2", "BellyRubFull-3", "BellyRubFull-4", "BellyRubFull-5"]
|
||||||
|
},
|
||||||
|
"entries": {
|
||||||
|
"StartBegin": {
|
||||||
|
"animation": "Appear",
|
||||||
|
"next": "Start",
|
||||||
|
"effects": [
|
||||||
|
{"type": "scene"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Start": {
|
||||||
|
"text": "AaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAAAAAAAAAaa!!!!!",
|
||||||
|
"animation": "Shocked",
|
||||||
|
"choices": [
|
||||||
|
{"text": "Who're you?", "next": "StartC1"},
|
||||||
|
{"text": "Wait, I...", "next": "StartC2"},
|
||||||
|
{"text": "Mini!!!", "next": "StartC3"}
|
||||||
|
],
|
||||||
|
"effects": [
|
||||||
|
{"type": "oscillation", "end": 54},
|
||||||
|
{"type": "big", "end": 54},
|
||||||
|
{"type": "screenshake"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"StartC1": {"text": "!!!!!!!!!!!!!!!!", "animation": "Grumpy", "next": "StartC1-2"},
|
||||||
|
"StartC1-2": {"text": "I'm the famous Princess Mini! That's right! Princess M-I-N-I! No \"T\"!", "animation": "Shocked", "next": "StartC1-3"},
|
||||||
|
"StartC1-3": {"text": "Get that right, please! Just to say that! A LOT of people don't get that right...", "animation": "Thinking", "next": "StartC1-4"},
|
||||||
|
"StartC1-4": {"text": "But, um...right! I'm a cutie patooie little candy princess!!!", "animation": "Excited", "next": "StartC1-5"},
|
||||||
|
"StartC1-5": {"text": "I rule my own kingdom! I mean, sometimes! Not much going on, though...", "animation": "Concerned", "next": "StartB"},
|
||||||
|
"StartC2": {"text": "Yes! You have to be the new retainer! It's gotta be you!", "animation": "Thinking", "next": "StartC2-2"},
|
||||||
|
"StartC2-2": {"text": "I don't know who else would be allowed to waltz into my quarters...", "animation": "Grumpy", "next": "StartC2-3"},
|
||||||
|
"StartC2-3": {"text": "...because an intruder would clearly be beheaded already if they got here!", "animation": "Thinking", "next": "StartC2-4"},
|
||||||
|
"StartC2-4": {"text": "So, yup! You gotta be the new retainer I was looking for!", "animation": "Excited", "next": "StartB"},
|
||||||
|
"StartC3": {"text": "HI!!!!!!!!!!", "animation": "Excited", "next": "StartB"},
|
||||||
|
"StartB": {"text": "It'll be good to get to know you anyway! You seem professional!", "animation": "Neutral", "next": "StartB-2"},
|
||||||
|
"StartB-2": {"text": "All right, then! I'm not one for interviews. Let's see how you'll treat me!!", "animation": "Excited"},
|
||||||
|
"Help": {
|
||||||
|
"text": "Yes, my dear retainer? You need help? Why, I'd expect you to already know how to treat a cute princess like me, but if you're that clueless...!",
|
||||||
|
"animation": "Smug",
|
||||||
|
"choices": [
|
||||||
|
{"text": "You", "next": "HelpC1"},
|
||||||
|
{"text": "Interact", "next": "HelpC2"},
|
||||||
|
{"text": "Catalog", "next": "HelpC4"},
|
||||||
|
{"text": "Settings", "next": "HelpC5"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"HelpC1": {"text": "Myself?! You need \"help\" with me?! What, do you think I'm a problem case or something?!", "animation": "Grumpy", "next": "HelpC1-2"},
|
||||||
|
"HelpC1-2": {"text": "...fine! I guess you need to take a princess' own advice as to how to treat them!!", "animation": "Glance", "next": "HelpC1-3"},
|
||||||
|
"HelpC1-3": {"text": "Your task as a retainer is to treat me right, of course! Protect and pamper me! Serve the kingdom!", "animation": "Excited", "next": "HelpC1-4"},
|
||||||
|
"HelpC1-4": {"text": "Right now...um...not much going on. There aren't any wars or anything...in fact...", "animation": "Thinking", "next": "HelpC1-5"},
|
||||||
|
"HelpC1-5": {"text": "...it's pretty peaceful among the Candy Kingdoms. We have something of an informal peace treaty in place.", "animation": "Neutral", "next": "HelpC1-6"},
|
||||||
|
"HelpC1-6": {"text": "Yeah, PB won that contest, a long time ago, and rubbed it in all of our faces...", "animation": "Thinking", "next": "HelpC1-7"},
|
||||||
|
"HelpC1-7": {"text": "...but since that day, and since the departure of that special someone, we've simply been...living in peace.", "animation": "Neutral", "next": "HelpC1-8"},
|
||||||
|
"HelpC1-8": {"text": "N-never mind all that! Here, how about something to do: get me some food! Your princess is hungry! Try finding some stuff!!", "animation": "Neutral", "next": "HelpC1-9"},
|
||||||
|
"HelpC1-9": {"text": "I'll eat whatever you serve me...and digest it in time. Um. Yeah.", "animation": "Concerned", "next": "HelpC1-10"},
|
||||||
|
"HelpC1-10": {"text": "Oh, and, well...I suppose you are the lucky few...who I'd trust to touch me!!", "animation": "Surprised", "next": "HelpC1-11"},
|
||||||
|
"HelpC1-11": {"text": "Be gentle, or it's gonna be OFF WITH YOUR HEAD!!", "animation": "Grumpy", "next": "HelpC1-12"},
|
||||||
|
"HelpC1-12": {"text": "...kidding! I do like a good massage, though! I suppose I trust you...", "animation": "Neutral", "next": "HelpC1-13"},
|
||||||
|
"HelpC1-13": {"text": "Oh, and yes, a few more instructions: use the \"middle mouse button\" or \"CTRL and click\" to pan around. Nooooooooo clue what those are!", "animation": "Thinking"},
|
||||||
|
"HelpC2": {"text": "You're doing that right now, silly retainer!!", "animation": "Smug", "next": "HelpC2-2"},
|
||||||
|
"HelpC2-2": {"text": "But, feel free to strike up a conversation! I LOVE TALKING!!", "animation": "Excited", "next": "HelpC2-3"},
|
||||||
|
"HelpC2-3": {"text": "And do find some splendid snacks to serve me!", "animation": "Excited", "next": "HelpC2-4"},
|
||||||
|
"HelpC2-4": {"text": "Also, um...w-wait...why are there numbers there..? How are you getting those numbers about me?!", "animation": "Shocked", "next": "HelpC2-5"},
|
||||||
|
"HelpC2-5": {"text": "So c-creepy! But, um...kind of cool, actually...", "animation": "Thinking"},
|
||||||
|
"HelpC4": {"text": "Oh, curious about the castle's Catalog?", "animation": "Thinking", "next": "HelpC4-2"},
|
||||||
|
"HelpC4-2": {
|
||||||
|
"text": "Our world is full of surprising and interesting foodstuffs!!",
|
||||||
|
"animation": "Surprised",
|
||||||
|
"next": "HelpC4-3",
|
||||||
|
"effects": [
|
||||||
|
{"type": "oscillation", "start": 13, "end": 17}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"HelpC4-3": {"text": "You ought to find and document them all, of course!!", "animation": "Excited", "next": "HelpC4-4"},
|
||||||
|
"HelpC4-4": {"text": "Find some items! If it's food, give me a taste of it!", "animation": "Thinking", "next": "HelpC4-5"},
|
||||||
|
"HelpC4-5": {"text": "...and once you've spawned some, have a look at what it just was in the Catalog!!", "animation": "Neutral"},
|
||||||
|
"HelpC5": {"text": "Questions? Comments? Concerns? Sorry, I'm the princess and executive here! I rule here! Those go into the /trash/!", "animation": "Smug", "next": "HelpC5-2"},
|
||||||
|
"HelpC5-2": {"text": "...but, well, if you are having some issues with your experience, I'm sure you being a big and strong retainer can fix them.", "next": "HelpC5-3"},
|
||||||
|
"HelpC5-3": {"text": "Go into the 'settings' menu and change things as you please..."},
|
||||||
|
"End": {
|
||||||
|
"text": "Retainer!! My dear, dear retainer...",
|
||||||
|
"animation": "Concerned",
|
||||||
|
"next": "End-2",
|
||||||
|
"effects": [
|
||||||
|
{"type": "scene"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"End-2": {"text": "I'd like to have a little chat with you.", "animation": "Concerned", "next": "End-3"},
|
||||||
|
"End-3": {"text": "I've figured it out! I've figured out your plan all along!!", "animation": "Grumpy", "next": "End-4"},
|
||||||
|
"End-4": {"text": "Are you actually my so-called \"retainer\"? Or some perverted, exploitative jerk, who managed to find their way here...", "animation": "Grumpy", "next": "End-5"},
|
||||||
|
"End-5": {"text": "...who, in the vicinity of a royal like me, could've done any number of heinous, unthinkable things...", "animation": "Thinking", "next": "End-6"},
|
||||||
|
"End-6": {"text": "...but, instead, chose to make me...OBESE?!", "animation": "Grumpy", "next": "End-7"},
|
||||||
|
"End-7": {"text": "I don't even know what to say!! You do this to a poor, innocent, upper strata elite member of society such as me?!", "animation": "Thinking", "next": "End-8"},
|
||||||
|
"End-8": {"text": "I...I...", "animation": "Sad", "next": "End-9"},
|
||||||
|
"End-9": {"text": "...I love it!! It's kind of cute!! My belly's like a marshmallow and I can squish it like one!! Look!! Teehee~!!!", "animation": "BellyPoke", "next": "End-10"},
|
||||||
|
"End-10": {"text": "Maybe things aren't all bad with you. If anything else, you've kept me certainly entertained!!", "animation": "Smug", "next": "End-11"},
|
||||||
|
"End-11": {"text": "Fine, then! If you weren't my retainer before...I'll certainly keep you around.", "animation": "Thinking", "next": "End-12"},
|
||||||
|
"End-12": {"text": "I'll use my Magic Mini Abilities to prevent myself from getting any larger, though...", "animation": "Smug", "next": "End-13"},
|
||||||
|
"End-13": {"text": "Yeah, my Magic Mini Abilities. Didn't know I had them? And you call yourself my servant?", "animation": "Smug", "next": "End-14"},
|
||||||
|
"End-14": {"text": "I just can't risk my dress getting torn, though! My apologies!!", "animation": "Neutral", "next": "End-15"},
|
||||||
|
"End-15": {"text": "...anyways! I suppose a good retainer like you has earned a gift. Check that menu for a secret Mini surprise~", "animation": "BnuuyHold", "next": "End-16"},
|
||||||
|
"End-16": {"text": "Hang around as long as you wish...just know your place. Wahahah!", "animation": "Smug", "next": "End-17"},
|
||||||
|
"End-17": {"text": "Happy April Fool's, if that's the date right now~!!", "animation": "Smug"},
|
||||||
|
"Stage1-1": {"text": "How am I doing? Well, AWESOME!!", "animation": "Excited", "next": "Stage1-1A"},
|
||||||
|
"Stage1-1A": {"text": "I've met you now, and you seem...cool! Quiet, but cool!! I hear a proper lady is supposed to find the strong, silent type dashing!", "animation": "Thinking", "next": "Stage1-1A-2"},
|
||||||
|
"Stage1-1A-2": {"text": "You almost remind me of...", "animation": "Concerned", "next": "Stage1-1A-3"},
|
||||||
|
"Stage1-1A-3": {"text": "...never mind. You can't be them. I'm not sure anybody could! But that's...fine.", "animation": "Neutral"},
|
||||||
|
"Stage1-2": {"text": "I'm doing SUPER!!", "animation": "Excited", "next": "Stage1-2A"},
|
||||||
|
"Stage1-2A": {"text": "You'll treat a princess like me right, right?!", "animation": "Smug", "next": "Stage1-2A-2"},
|
||||||
|
"Stage1-2A-2": {"text": "We can hang out, and chat, and play games, and stuff!!", "animation": "Excited", "next": "Stage1-2A-3"},
|
||||||
|
"Stage1-2A-3": {"text": "I'm ready to have a ton of FUN!!", "animation": "Smug"},
|
||||||
|
"Stage1-3": {"text": "I'm doing simply...epic!!", "animation": "Smug", "next": "Stage1-3A"},
|
||||||
|
"Stage1-3A": {"text": "I've heard that's topical lingo!", "animation": "Thinking", "next": "Stage1-3A-2"},
|
||||||
|
"Stage1-3A-2": {"text": "...what's that look for? That's so decade-ago coded?!", "animation": "Grumpy", "next": "Stage1-3A-3"},
|
||||||
|
"Stage1-3A-3": {"text": "...fine, then!! I'll be poggers chungus six-seven, just for you!!", "animation": "Excited"},
|
||||||
|
"Stage2-1": {"text": "So, you are an experienced retainer, right? Do you know how to treat a princess correctly..?", "animation": "Concerned", "next": "Stage2-1A"},
|
||||||
|
"Stage2-1A": {"text": "I mean, I trust you! No shade against you!", "animation": "Glance", "next": "Stage2-1A-2"},
|
||||||
|
"Stage2-1A-2": {"text": "But, I dunno...something feels different. I don't know what, but...", "animation": "Thinking", "next": "Stage2-1A-3"},
|
||||||
|
"Stage2-1A-3": {"text": "...like something feels...heavier? I don't quite know.", "animation": "Neutral", "next": "Stage2-1A-4"},
|
||||||
|
"Stage2-1A-4": {"text": "Maybe it's all of those years of inaction! Meeting someone new is changing things up.", "animation": "Excited"},
|
||||||
|
"Stage2-2": {"text": "Hmf...dear retainer, I feel something's changed!", "animation": "Concerned", "next": "Stage2-2A"},
|
||||||
|
"Stage2-2A": {"text": "I haven't got any idea what has! But, well, being that you're so professional, you can diagnose it!", "animation": "Thinking", "next": "Stage2-2A-2"},
|
||||||
|
"Stage2-2A-2": {"text": "What do you think has changed about me?", "animation": "Neutral", "next": "Stage2-2A-3"},
|
||||||
|
"Stage2-2A-3": {"text": "...oh, right. You're the silent type. Well, that expression of yours seems positive, so you must know what you're doing.", "animation": "Thinking"},
|
||||||
|
"Stage2-3": {"text": "Hey, just a question...do you think I look cuter, as of late?", "animation": "Thinking", "next": "Stage2-3A"},
|
||||||
|
"Stage2-3A": {"text": "I-I mean! I'm always the cutest of the princesses, but I don't know...even cuter?", "animation": "Shocked", "next": "Stage2-3A-2"},
|
||||||
|
"Stage2-3A-2": {"text": "You can be honest! I won't chop off your head or anything if you say I'm not!", "animation": "Thinking", "next": "Stage2-3A-3"},
|
||||||
|
"Stage2-3A-3": {"text": "...because I wouldn't be doing the chopping! One of my executioners would!", "animation": "Smug", "next": "Stage2-3A-4"},
|
||||||
|
"Stage2-3A-4": {"text": "Haha, just kidding! I mean, I COULD chop off your head, if I wanted to...", "animation": "Neutral", "next": "Stage2-3A-5"},
|
||||||
|
"Stage2-3A-5": {"text": "I'm a killer at manning a guillotine or a battle axe...", "animation": "Thinking", "next": "Stage2-3A-6"},
|
||||||
|
"Stage2-3A-6": {"text": "...you just haven't seen me do it!", "animation": "Smug", "next": "Stage2-3A-7"},
|
||||||
|
"Stage2-3A-7": {"text": "But, well. If you WEREN'T making me cuter, THEN the decapitation would come!", "animation": "Shocked", "next": "Stage2-3A-8"},
|
||||||
|
"Stage2-3A-8": {"text": "...I'm rambling. Sorry.", "animation": "Sad"},
|
||||||
|
"Stage3-1": {"text": "...", "animation": "Concerned", "next": "Stage3-1A"},
|
||||||
|
"Stage3-1A": {"text": "Okay, you're SURE you're making me cuter, retainer?", "animation": "Thinking", "next": "Stage3-1A-2"},
|
||||||
|
"Stage3-1A-2": {"text": "I've eaten quite heartily as of late...so you are treating me well...", "animation": "Concerned", "next": "Stage3-1A-3"},
|
||||||
|
"Stage3-1A-3": {"text": "...but, I don't know. I think sometime ago Chocolate told me that eating a lot was bad for you.", "animation": "Thinking", "next": "Stage3-1A-4"},
|
||||||
|
"Stage3-1A-4": {"text": "...she also has a pretty big chest. So maybe she was just trying to hide her secrets from me!!", "animation": "Shocked", "next": "Stage3-1A-5"},
|
||||||
|
"Stage3-1A-5": {"text": "And Creme, a proper lady, told me that a good princess has an \"ample bosom\"!!", "animation": "Concerned", "next": "Stage3-1A-6"},
|
||||||
|
"Stage3-1A-6": {"text": "And Chocolate's always so annoying, being so stubborn and silent!! So she's gotta be wrong!", "animation": "Excited", "next": "Stage3-1A-7"},
|
||||||
|
"Stage3-1A-7": {"text": "So, retainer, keep doing what you're doing! Let's beat that meanie Chocolate!!", "animation": "Neutral"},
|
||||||
|
"Stage3-2": {"text": "...So, dear retainer, I have a question!!", "animation": "Shocked", "next": "Stage3-2A"},
|
||||||
|
"Stage3-2A": {"text": "I'm just wondering what kind of skills you have...", "animation": "Thinking", "next": "Stage3-2A-2"},
|
||||||
|
"Stage3-2A-2": {"text": "...because, well, yes, you are good at pampering me. But that isn't the only skill a good retainer has...", "animation": "Thinking", "next": "Stage3-2A-3"},
|
||||||
|
"Stage3-2A-3": {"text": "...some of the best retainers have all sorts of skills! Cooking, cleaning, protection...", "animation": "Neutral", "next": "Stage3-2A-4"},
|
||||||
|
"Stage3-2A-4": {"text": "...I'm just wondering...is tailoring a skill of one of yours?", "animation": "Thinking", "next": "Stage3-2A-5"},
|
||||||
|
"Stage3-2A-5": {"text": "...um...j-just asking. No reason.", "animation": "Glance"},
|
||||||
|
"Stage3-3": {"text": "Wow, dear retainer, you sure have been treating me right..!", "animation": "Excited", "next": "Stage3-3A"},
|
||||||
|
"Stage3-3A": {"text": "I mean, you've been giving me a lot of yummy food. And the yummy food tastes good.", "animation": "Thinking", "next": "Stage3-3A-2"},
|
||||||
|
"Stage3-3A-2": {"text": "...and when food tastes good, it makes my head give off the happy feeling.", "animation": "Neutral", "next": "Stage3-3A-3"},
|
||||||
|
"Stage3-3A-3": {"text": "So, SCIENTIFICALLY speaking...the more food I eat, the yummier it is, the happier I feel!", "animation": "Neutral", "next": "Stage3-3A-4"},
|
||||||
|
"Stage3-3A-4": {"text": "I talked to Peep about this once, and she understood, completely.", "animation": "Thinking", "next": "Stage3-3A-5"},
|
||||||
|
"Stage3-3A-5": {"text": "...she then swallowed one of her Peeps whole and started batting her hands on her head.", "animation": "Neutral", "next": "Stage3-3A-6"},
|
||||||
|
"Stage3-3A-6": {"text": "Peep has some strange feelings, rattling around...I wish I could understand her, sometimes.", "animation": "Thinking"},
|
||||||
|
"Stage4-1": {"text": "RETAINER!!!", "animation": "Grumpy", "next": "Stage4-1A"},
|
||||||
|
"Stage4-1A": {"text": "Something has definitely changed!!", "animation": "Shocked", "next": "Stage4-1A-2"},
|
||||||
|
"Stage4-1A-2": {"text": "While you occupied, playing some silly games, I heard a 'rrrrriiiippp'...", "animation": "Thinking", "next": "Stage4-1A-3"},
|
||||||
|
"Stage4-1A-3": {"text": "...and I looked at a mirror, and I saw a bit of my butt through my dress!!", "animation": "Shocked", "next": "Stage4-1A-4"},
|
||||||
|
"Stage4-1A-4": {"text": "Are you SURE this is princessly behavior? Are you SURE this is expected?!", "animation": "Grumpy", "next": "Stage4-1A-5"},
|
||||||
|
"Stage4-1A-5": {"text": "...", "animation": "Glance", "next": "Stage4-1A-6"},
|
||||||
|
"Stage4-1A-6": {"text": "Okay. Well, again, you SEEM like you know what you're doing...", "animation": "Concerned", "next": "Stage4-1A-7"},
|
||||||
|
"Stage4-1A-7": {"text": "...so I place my full, unabashed trust in you!!", "animation": "Excited"},
|
||||||
|
"Stage4-2": {"text": "This is a dumb question, retainer...", "animation": "Concerned", "next": "Stage4-2A"},
|
||||||
|
"Stage4-2A": {"text": "...but am I pregnant?!", "animation": "Thinking", "next": "Stage4-2A-2"},
|
||||||
|
"Stage4-2A-2": {"text": "I mean, I've never been before, and, well...I heard the main thing is that your tummy gets all big...", "animation": "Concerned", "next": "Stage4-2A-3"},
|
||||||
|
"Stage4-2A-3": {"text": "Look at it! It's big and round, now! Clearly, there's a Mini Mini in there!!", "animation": "BellyPoke", "next": "Stage4-2A-4"},
|
||||||
|
"Stage4-2A-4": {"text": "...or maybe it's just a 'food baby', something that PB tells me about...", "animation": "Thinking", "next": "Stage4-2A-5"},
|
||||||
|
"Stage4-2A-5": {"text": "Regardless, I'm gonna be a mom!! Or at least look like one!! Yay!!", "animation": "Excited"},
|
||||||
|
"Stage4-3": {"text": "Retainer, you know my name, yes?", "animation": "Glance", "next": "Stage4-3A"},
|
||||||
|
"Stage4-3A": {"text": "...well, you should if you've been around me this long. I mean, I did tell you.", "animation": "Thinking", "next": "Stage4-3A-2"},
|
||||||
|
"Stage4-3A-2": {"text": "...It's 'Mini'. M-I-N-I. You know what that word means, yes?", "animation": "Concerned", "next": "Stage4-3A-3"},
|
||||||
|
"Stage4-3A-3": {"text": "Small! Petite! Tiny!", "animation": "Grumpy", "next": "Stage4-3A-4"},
|
||||||
|
"Stage4-3A-4": {"text": "Now, tell me, does THIS big jiggly thing on me now make me live up to my name?!", "animation": "BellyPoke", "next": "Stage4-3A-5"},
|
||||||
|
"Stage4-3A-5": {"text": "Soon, I ought to be called Princess Mega, instead..!", "animation": "Glance", "next": "Stage4-3A-6"},
|
||||||
|
"Stage4-3A-6": {"text": "...", "animation": "Thinking", "next": "Stage4-3A-7"},
|
||||||
|
"Stage4-3A-7": {"text": "...retainer, do you think that would be a good or bad thing..?", "animation": "Smug"},
|
||||||
|
"Stage5-1": {"text": "Retainer...", "animation": "Thinking", "next": "Stage5-1A"},
|
||||||
|
"Stage5-1A": {"text": "I had hopes for you! Treating me right!", "animation": "Grumpy", "next": "Stage5-1A-2"},
|
||||||
|
"Stage5-1A-2": {"text": "I had doubted it for awhile...oh, it's just bloating. Oh, it's just some passing feeling...", "animation": "Thinking", "next": "Stage5-1A-3"},
|
||||||
|
"Stage5-1A-3": {"text": "...but, no! I'm FAT! That's what's happening! That's what you did to me!!", "animation": "Shocked", "next": "Stage5-1A-4"},
|
||||||
|
"Stage5-1A-4": {"text": "...", "animation": "Glance", "next": "Stage5-1A-5"},
|
||||||
|
"Stage5-1A-5": {"text": "...was that your intention since the very beginning?! To fatten me up?!", "animation": "Shocked", "next": "Stage5-1A-6"},
|
||||||
|
"Stage5-1A-6": {"text": "..I mean...it's...not TOO bad...kinda squishy...", "animation": "BellyPoke", "next": "Stage5-1A-7"},
|
||||||
|
"Stage5-1A-7": {"text": "...", "animation": "Excited", "next": "Stage5-1A-8"},
|
||||||
|
"Stage5-1A-8": {"text": "Okay. I'm fat now. This is fine. I think some people might like that!!", "animation": "Excited"},
|
||||||
|
"Stage5-2": {"text": "Kinda tight in these clothes...", "animation": "Thinking", "next": "Stage5-2A"},
|
||||||
|
"Stage5-2A": {"text": "I think I will need your tailoring skills, rrrrright about now..!!", "animation": "Concerned", "next": "Stage5-2A-2"},
|
||||||
|
"Stage5-2A-2": {"text": "Because, if I gain any more weight.......RIIIIIIP!!!", "animation": "Shocked", "next": "Stage5-2A-3"},
|
||||||
|
"Stage5-2A-3": {"text": "Is that what you like, you freaky retainer, you?! Poor old Mini outgrowing her iconic dress?!", "animation": "Grumpy", "next": "Stage5-2A-4"},
|
||||||
|
"Stage5-2A-4": {"text": "Tighter and tighter, her butt getting huge and her belly sagging and growing more?!", "animation": "Concerned", "next": "Stage5-2A-5"},
|
||||||
|
"Stage5-2A-5": {"text": "Well, YOU'RE going to be responsible for making me a new dress when I inevitably ruin it!!", "animation": "Grumpy", "next": "Stage5-2A-6"},
|
||||||
|
"Stage5-2A-6": {"text": "Soon enough, I'll be bigger than the other princesses combined! Think of that! From Mini to Mega!", "animation": "Shocked", "next": "Stage5-2A-7"},
|
||||||
|
"Stage5-2A-7": {"text": "...", "animation": "Glance", "next": "Stage5-2A-8"},
|
||||||
|
"Stage5-2A-8": {"text": "Ugh, and you probably LIKE that...", "animation": "Glance", "next": "Stage5-2A-9"},
|
||||||
|
"Stage5-2A-9": {"text": "...why does that thought excite me, too?!", "animation": "Excited"},
|
||||||
|
"Stage5-3": {"text": "Hm...you think the other princesses will be jealous of me, now..?", "animation": "Thinking", "next": "Stage5-3A"},
|
||||||
|
"Stage5-3A": {"text": "\"Oh, there goes Mini, and her big fat butt!", "animation": "Smug", "next": "Stage5-3A-2"},
|
||||||
|
"Stage5-3A-2": {"text": "\"Look at that big wobbling gut she has, now! What happened to the skinny Mini?!\"", "animation": "Smug", "next": "Stage5-3A-3"},
|
||||||
|
"Stage5-3A-3": {"text": "Peanut Butter will be mad 'cuz I'm not exercising...", "animation": "Thinking", "next": "Stage5-3A-4"},
|
||||||
|
"Stage5-3A-4": {"text": "Chocolate will give me her typical trademark grimace, and say bluntly, \"you got fat.\"", "animation": "Thinking", "next": "Stage5-3A-5"},
|
||||||
|
"Stage5-3A-5": {"text": "Creme might think I'm unprincessly now...", "animation": "Concerned", "next": "Stage5-3A-6"},
|
||||||
|
"Stage5-3A-6": {"text": "...and Peep? Honestly, I don't know what she would think! But she's my best friend, and supports me always...", "animation": "Smug", "next": "Stage5-3A-7"},
|
||||||
|
"Stage5-3A-7": {"text": "Yeah! I know she will! She's a little doughy herself under that corset!", "animation": "Thinking", "next": "Stage5-3A-8"},
|
||||||
|
"Stage5-3A-8": {"text": "...don't ask how I know that! But I bet she'll love the new me!!", "animation": "Excited"},
|
||||||
|
"Random1": {
|
||||||
|
"text": "Ah, are you familiar with that pink Princess Peep, of her own kingdom?",
|
||||||
|
"animation": "Thinking",
|
||||||
|
"choices": [
|
||||||
|
{"text": "Yes", "next": "Random1C1"},
|
||||||
|
{"text": "No", "next": "Random1C2"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Random1C1": {"text": "Oh, good, so you're acquainted with her...eccentricities.", "animation": "Shocked", "next": "Random1B"},
|
||||||
|
"Random1C2": {"text": "Ah, doesn't ring a bell? Well, let me go ahead and tell you!", "animation": "Thinking", "next": "Random1B"},
|
||||||
|
"Random1B": {"text": "She's a strange one! I've always felt she was a little different than the other princesses...", "animation": "Shocked", "next": "Random1B-2"},
|
||||||
|
"Random1B-2": {"text": "She's awfully quiet, usually. Then, when she does speak up, it'll be a slow, maybe a bit rambling...", "animation": "Thinking", "next": "Random1B-3"},
|
||||||
|
"Random1B-3": {"text": "And some of her behavior is...a little strange. She'll bat at herself sometimes...", "animation": "Neutral", "next": "Random1B-4"},
|
||||||
|
"Random1B-4": {"text": "Oh, right, descriptions. She's a taller princess, with pink bangs, and always has a gentle smile on her face...", "animation": "Neutral", "next": "Random1B-5"},
|
||||||
|
"Random1B-5": {"text": "Her crown's a darker pink with hearts on them!! So cute!!", "animation": "Excited", "next": "Random1B-6"},
|
||||||
|
"Random1B-6": {"text": "Her kingdom's comprised of these little creatures called 'Peeps'. Pink, bird little things, that taste like marshmallow...", "animation": "Thinking", "next": "Random1B-7"},
|
||||||
|
"Random1B-7": {"text": "In fact, I think she's the only non-Peep thing in her whole kingdom!!", "animation": "Excited", "next": "Random1B-8"},
|
||||||
|
"Random1B-8": {"text": "Ohmigosh, it gets really crazy with her retainers! Now, I know you're strong and cool and all...", "animation": "Thinking", "next": "Random1B-9"},
|
||||||
|
"Random1B-9": {"text": "...but those retainers of hers are no joke! They're huge and totally shredded!!", "animation": "Shocked", "next": "Random1B-10"},
|
||||||
|
"Random1B-10": {"text": "I worry about what I say about her sometimes, because I fret that one of their big fists will pummel my smug little face into a pancake..!!", "animation": "Concerned", "next": "Random1B-11"},
|
||||||
|
"Random1B-11": {"text": "But she's my best friend, and she's reassured me no one will ever hurt me! So, it's not all bad...", "animation": "Neutral"},
|
||||||
|
"Random2": {
|
||||||
|
"text": "Do you know that tall, burly, stubborn Princess Chocolate?",
|
||||||
|
"animation": "Thinking",
|
||||||
|
"choices": [
|
||||||
|
{"text": "Yes", "next": "Random2C1"},
|
||||||
|
{"text": "No", "next": "Random2C2"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Random2C1": {"text": "Ah! So you're aware of how...imposing she can be..!!", "animation": "Shocked", "next": "Random2B"},
|
||||||
|
"Random2C2": {"text": "Ah, doesn't ring a bell? Well, let me go ahead and tell you!", "animation": "Neutral", "next": "Random2B"},
|
||||||
|
"Random2B": {"text": "She's definitely the strongest of our neighboring kingdoms...", "animation": "Thinking", "next": "Random2B-2"},
|
||||||
|
"Random2B-2": {"text": "Unlike her food namesake, she's certainly not brittle!", "animation": "Concerned", "next": "Random2B-3"},
|
||||||
|
"Random2B-3": {"text": "Chocolate's tall and she's a little chiseled! She's got dreads, and some gold on her outfit...", "animation": "Thinking", "next": "Random2B-4"},
|
||||||
|
"Random2B-4": {"text": "She's a girl of not many words. Whenever she speaks up, we listen.", "animation": "Shocked", "next": "Random2B-5"},
|
||||||
|
"Random2B-5": {"text": "If we all got into a big fight...I think Chocolate would win..!", "animation": "Thinking", "next": "Random2B-6"},
|
||||||
|
"Random2B-6": {"text": "...except when I sneak in with a steel chair and jump up and smack her head with it!!", "animation": "Smug", "next": "Random2B-7"},
|
||||||
|
"Random2B-7": {"text": "Haha, just kidding, I wouldn't hurt her, but she's not one for jokes...", "animation": "Thinking", "next": "Random2B-8"},
|
||||||
|
"Random2B-8": {"text": "Hundreds of years ago, before there was peace in our region, her kind were a race of fiercesome warriors...", "animation": "Neutral", "next": "Random2B-9"},
|
||||||
|
"Random2B-9": {"text": "They tamed vicious, powerful, chocolatey beasts!!", "animation": "Shocked", "next": "Random2B-10"},
|
||||||
|
"Random2B-10": {"text": "Whenever I visit her keep, there's a lot of traditional artifacts and other displays of her culture.", "animation": "Thinking", "next": "Random2B-11"},
|
||||||
|
"Random2B-11": {"text": "I try and tune that out, 'cuz it's boring!! Who cares about stuff that happened so long ago?!", "animation": "Grumpy", "next": "Random2B-12"},
|
||||||
|
"Random2B-12": {"text": "And she always bosses me around whenever I'm around. \"That's not royal behavior. Be assertive. Be proud.\"", "animation": "Grumpy", "next": "Random2B-13"},
|
||||||
|
"Random2B-13": {"text": "I think she should shut up!! But, sometimes, when I'm alone, I wonder if she has a point...", "animation": "Thinking", "next": "Random2B-14"},
|
||||||
|
"Random2B-14": {"text": "...oh, and FYI, she has the biggest boobs of us. I'm sure you believe 'flat is justice' though, so...", "animation": "Smug"},
|
||||||
|
"Random3": {
|
||||||
|
"text": "Ugh...do you know that uptight snob, Princess Creme?",
|
||||||
|
"animation": "Concerned",
|
||||||
|
"choices": [
|
||||||
|
{"text": "Yes", "next": "Random3C1"},
|
||||||
|
{"text": "No", "next": "Random3C2"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Random3C1": {"text": "Oh, so you know what a...meanie she is!!", "animation": "Shocked", "next": "Random3B"},
|
||||||
|
"Random3C2": {"text": "I'll tell you what a jerk she is!!", "animation": "Neutral", "next": "Random3B"},
|
||||||
|
"Random3B": {"text": "Princess Creme is probably the most \"royal\" of all of us...", "animation": "Thinking", "next": "Random3B-2"},
|
||||||
|
"Random3B-2": {"text": "...a royal pain in the butt!!", "animation": "Grumpy", "next": "Random3B-3"},
|
||||||
|
"Random3B-3": {"text": "I mean, she's my friend...she kinda has to be a friend around all of us...", "animation": "Thinking", "next": "Random3B-4"},
|
||||||
|
"Random3B-4": {"text": "...but whenever I come over to her castle, it's always about following the rules and being polite..!", "animation": "Grumpy", "next": "Random3B-5"},
|
||||||
|
"Random3B-5": {"text": "I can't take it! Yeah, I'm a princess, but I don't want that to be my whole life!!", "animation": "Grumpy", "next": "Random3B-6"},
|
||||||
|
"Random3B-6": {"text": "She's the most prim and proper. Her heels click-clack wherever she goes. She's got like a waterfall of creme-y hair...", "animation": "Concerned", "next": "Random3B-7"},
|
||||||
|
"Random3B-7": {"text": "When it's tea-time, always an obligation whenever I go to her place, she reprimands me constantly...", "animation": "Thinking", "next": "Random3B-8"},
|
||||||
|
"Random3B-8": {"text": "\"Dear Princess Mini! Please hold the tea cup correctly, as I've shown you!\"", "animation": "Grumpy", "next": "Random3B-9"},
|
||||||
|
"Random3B-9": {"text": "Shut up!! Who cares?! It's a cup! You hold it however you like and you drink it however you like!! Goes down the same!!", "animation": "Grumpy", "next": "Random3B-10"},
|
||||||
|
"Random3B-10": {"text": "Always the commands about the most arbitrary things!! Which fork to use and when!!", "animation": "Thinking", "next": "Random3B-11"},
|
||||||
|
"Random3B-11": {"text": "In my castle, there's NO RULES!! And that's how it should be!!", "animation": "Smug", "next": "Random3B-12"},
|
||||||
|
"Random3B-12": {"text": "Her kingdom's extremely well-kept, too. It's like a sickeningly creamy, perfect paradise. Everything's in order.", "animation": "Thinking", "next": "Random3B-13"},
|
||||||
|
"Random3B-13": {"text": "And she's a snob about her name...it's C-R-E-M-E, not C-R-E-A-M, yes, we get it...", "animation": "Grumpy", "next": "Random3B-14"},
|
||||||
|
"Random3B-14": {"text": "Yeah, people call me...\"Mint\"...but I don't have a huge meltdown over it!! Not a HUGE huge one, anyway..!", "animation": "Thinking", "next": "Random3B-15"},
|
||||||
|
"Random3B-15": {"text": "...I just think she's a big sore loser about that contest we had! She came in dead last!!", "animation": "Smug", "next": "Random3B-16"},
|
||||||
|
"Random3B-16": {"text": "She's got secrets, alright. I feel like I've seen them. Naughty, naughty secrets...that Creme...", "animation": "Thinking"},
|
||||||
|
"Random4": {
|
||||||
|
"text": "Are you familiar with that sporty, tomboy princess, Princess Peanut Butter?",
|
||||||
|
"animation": "Thinking",
|
||||||
|
"choices": [
|
||||||
|
{"text": "Yes", "next": "Random4C1"},
|
||||||
|
{"text": "No", "next": "Random4C2"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Random4C1": {"text": "Ah, so you do know about her perky personality...", "animation": "Neutral", "next": "Random4B"},
|
||||||
|
"Random4C2": {"text": "Oh, she's a handful, alright...", "animation": "Thinking", "next": "Random4B"},
|
||||||
|
"Random4B": {"text": "Princess Peanut Butter, or PB for short...is a total meathead!", "animation": "Neutral", "next": "Random4B-2"},
|
||||||
|
"Random4B-2": {"text": "She's always up and active and energized whenever I'm around. Being around her is a workout, both mentally and physically...", "animation": "Grumpy", "next": "Random4B-3"},
|
||||||
|
"Random4B-3": {"text": "It's kind of weird though, because despite being so intense all the time, she still wears a big dress and has big long hair...", "animation": "Thinking", "next": "Random4B-4"},
|
||||||
|
"Random4B-4": {"text": "That's not optimal in a sporting event! Someone could tug it and she'd plummet right down onto the turf!!", "animation": "Shocked", "next": "Random4B-5"},
|
||||||
|
"Random4B-5": {"text": "Anyway! She's definitely one for contests. In fact, she actually won that super-important contest we all competed in...", "animation": "Thinking", "next": "Random4B-6"},
|
||||||
|
"Random4B-6": {"text": "Now, I don't think she won the popular vote...but her supporters pulled through for her in the end...", "animation": "Neutral", "next": "Random4B-7"},
|
||||||
|
"Random4B-7": {"text": "And when you're hanging out with her, it's like you gotta keep up with her.", "animation": "Concerned", "next": "Random4B-8"},
|
||||||
|
"Random4B-8": {"text": "Look at me! Do you think I could win at any of these competitions?! I'm good at looking cute, that's about it!", "animation": "Glance", "next": "Random4B-9"},
|
||||||
|
"Random4B-9": {"text": "She's got a pretty tight bond with Princess Chocolate. Chocolate and Peanut Butter...they're really good with each other...", "animation": "Thinking", "next": "Random4B-10"},
|
||||||
|
"Random4B-10": {"text": "I wonder if the two have any secrets behind the scenes...", "animation": "Smug", "next": "Random4B-11"},
|
||||||
|
"Random4B-11": {"text": "...oh, and they arm-wrestle a lot. They're pretty good at it.", "animation": "Thinking", "next": "Random4B-12"},
|
||||||
|
"Random4B-12": {"text": "Friendly rivals, close friends...you think there might be something more between them?", "animation": "Smug"},
|
||||||
|
"Random5": {"text": "You wanna know some fun trivia about me?!", "next": "Random5A"},
|
||||||
|
"Random5A": {"text": "Well, alright then! You know, these pigtails aren't just a style choice!", "animation": "Smug", "next": "Random5A-2"},
|
||||||
|
"Random5A-2": {"text": "They're reminiscent of my favorite animals...bnuuies, and their two cute floppy ears!!", "animation": "Excited", "next": "Random5A-3"},
|
||||||
|
"Random5A-3": {"text": "Yeah! Bnuuies. Singular 'bnuuy'. Cute little creatures that frolic around my kingdom~", "animation": "Smug", "next": "Random5A-4"},
|
||||||
|
"Random5A-4": {"text": "Look!! Look here!! This is my favorite little creature!!", "animation": "BnuuyHold", "next": "Random5A-5"},
|
||||||
|
"Random5A-5": {"text": "Bnuuies leave little tasty eggs everywhere and they're super yummy!!", "animation": "BnuuyHold", "next": "Random5A-6"},
|
||||||
|
"Random5A-6": {"text": "This one in particular isn't alive...at least, I don't think so...", "animation": "BnuuyHold", "next": "Random5A-7"},
|
||||||
|
"Random5A-7": {"text": "I don't know...sometimes, I wake up, and Bnuuy's moved, and there's a little basket of eggs around...", "animation": "BnuuyHold", "next": "Random5A-8"},
|
||||||
|
"Random5A-8": {"text": "Maybe this one wakes up on certain days and gives me gifts?", "animation": "BnuuyHold", "next": "Random5A-9"},
|
||||||
|
"Random5A-9": {"text": "I feel like, as long as Bnuuy is nearby...I'm safe!!", "animation": "BnuuyHold"},
|
||||||
|
"Random6": {
|
||||||
|
"text": "You've seen some of my kingdom's delicacies, yes?",
|
||||||
|
"animation": "Excited",
|
||||||
|
"choices": [
|
||||||
|
{"text": "Yes", "next": "Random6C1"},
|
||||||
|
{"text": "No", "next": "Random6C2"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Random6C1": {"text": "Splendid!! Don't we have the best and most cute treats ever?!", "animation": "Neutral", "next": "Random6B"},
|
||||||
|
"Random6C2": {"text": "Really?!?! Well, you gotta see them!!", "animation": "Thinking", "next": "Random6B"},
|
||||||
|
"Random6B": {"text": "We specialize in little eggs, gifts given to us by our Bnuuy friends~", "animation": "Excited", "next": "Random6B-2"},
|
||||||
|
"Random6B-2": {"text": "They provide all of one's nutritional needs! I mean, I don't know what's in 'em, but I've pretty much eaten them my whole life...", "animation": "Thinking", "next": "Random6B-3"},
|
||||||
|
"Random6B-3": {"text": "They're cute and petite, dare I say, Mini, just like me~", "animation": "Excited", "next": "Random6B-4"},
|
||||||
|
"Random6B-4": {"text": "What do they taste like?! You know what, I'm gonna keep that a secret~", "animation": "Smug", "next": "Random6B-5"},
|
||||||
|
"Random6B-5": {"text": "I can tell you what they DON'T taste like, though...MINT!!", "animation": "Grumpy"},
|
||||||
|
"Random7": {
|
||||||
|
"text": "You've seen some of the food that comes from Princess Peep's kingdom, yeah?",
|
||||||
|
"animation": "Thinking",
|
||||||
|
"choices": [
|
||||||
|
{"text": "Yes", "next": "Random7C1"},
|
||||||
|
{"text": "No", "next": "Random7C2"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Random7C1": {"text": "Look...I enjoy it!!", "animation": "Neutral", "next": "Random7B"},
|
||||||
|
"Random7C2": {"text": "Oh, you'll see it, I'm sure. It's quite distinctive.", "animation": "Thinking", "next": "Random7B"},
|
||||||
|
"Random7B": {"text": "It's super weird...", "animation": "Thinking", "next": "Random7B-2"},
|
||||||
|
"Random7B-2": {"text": "In her world, her kingdom, everything is Peep. All is Peep. They're hot pink bird-like...things.", "animation": "Concerned", "next": "Random7B-3"},
|
||||||
|
"Random7B-3": {"text": "You'll go to a farm, and find Peeps growing. You'll go underground, it's Peeps.", "animation": "Glance", "next": "Random7B-4"},
|
||||||
|
"Random7B-4": {"text": "And those eyes...their little beady eyes...sometimes they're animate, sometimes they're not.", "animation": "Thinking", "next": "Random7B-5"},
|
||||||
|
"Random7B-5": {"text": "Even if they aren't living, and they're just tasty treats...I feel like they stare with some unseen force behind them...", "animation": "Concerned", "next": "Random7B-6"},
|
||||||
|
"Random7B-6": {"text": "And sometimes they don't look right. Sometimes a Peep has multiple heads, multiple tails...", "animation": "Concerned", "next": "Random7B-7"},
|
||||||
|
"Random7B-7": {"text": "I try not to think too hard about it. Somehow, something feels...sinister, about it all...", "animation": "Thinking", "next": "Random7B-8"},
|
||||||
|
"Random7B-8": {"text": "...look, all that being said, they are actually incredibly delicious and addicting. Once you have one, you wanna have another...", "animation": "Shocked", "next": "Random7B-9"},
|
||||||
|
"Random7B-9": {"text": "...and they're filling, too. Caloric. Very dense. Very fluffy. Very...fattening...", "animation": "Thinking", "next": "Random7B-10"},
|
||||||
|
"Random7B-10": {"text": "I'll eat them happily, and I eat lots of them whenever I hang out with Peep!!", "animation": "Excited", "next": "Random7B-11"},
|
||||||
|
"Random7B-11": {"text": "Just...be mindful of giving me them. You're not trying to make me FAT or whatever, right?!", "animation": "Concerned"},
|
||||||
|
"Random8": {
|
||||||
|
"text": "I suppose you can guess what kind of food hails from Princess Chocolate's kingdom, yeah?",
|
||||||
|
"animation": "Thinking",
|
||||||
|
"choices": [
|
||||||
|
{"text": "Yes", "next": "Random8C1"},
|
||||||
|
{"text": "No", "next": "Random8C2"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Random8C1": {"text": "Yeah, you probably know what it is...", "animation": "Neutral", "next": "Random8B"},
|
||||||
|
"Random8C2": {"text": "Well, you can probably guess.", "animation": "Thinking", "next": "Random8B"},
|
||||||
|
"Random8B": {"text": "Chocolate's food has roots in her kingdom's history...", "animation": "Thinking", "next": "Random8B-2"},
|
||||||
|
"Random8B-2": {"text": "Now, I don't tune in too much to her history lessons, but Choco Kisses were apparently used in some old religious rites...", "animation": "Neutral", "next": "Random8B-3"},
|
||||||
|
"Random8B-3": {"text": "And Choco Tacos are apparently a culinary tradition...", "animation": "Thinking", "next": "Random8B-4"},
|
||||||
|
"Random8B-4": {"text": "Choco Bars are more of a recent invention, though. I think one of her great-ancestors wanted to \"modernize\" her kingdom, and a mass-produced food was on the list..", "animation": "Thinking", "next": "Random8B-5"},
|
||||||
|
"Random8B-5": {"text": "I mean, they're good. And it's the real stuff. Very intense flavor. Very good, though.", "animation": "Neutral", "next": "Random8B-6"},
|
||||||
|
"Random8B-6": {"text": "I dunno if she's so proud of them, though, but she upholds traditions...even ones she might not personally like...", "animation": "Thinking", "next": "Random8B-7"},
|
||||||
|
"Random8B-7": {"text": "I gotta say though, the food is especially filling. It's overwhelming and tough, just like she is.", "animation": "Shocked", "next": "Random8B-8"},
|
||||||
|
"Random8B-8": {"text": "I feel like, when I eat some of her food, it's like...I can just keep going, y'know? Like my tummy can take more!!", "animation": "Concerned"},
|
||||||
|
"Random9": {
|
||||||
|
"text": "Oh, you've seen Princess Creme's food offerings, have you?",
|
||||||
|
"animation": "Thinking",
|
||||||
|
"choices": [
|
||||||
|
{"text": "Yes", "next": "Random9C1"},
|
||||||
|
{"text": "No", "next": "Random9C2"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Random9C1": {"text": "Oh, they're splendid and immaculately presented, just like her kingdom!", "animation": "Neutral", "next": "Random9B"},
|
||||||
|
"Random9C2": {"text": "I have my reservations about the makers, but...the food is good...", "animation": "Thinking", "next": "Random9B"},
|
||||||
|
"Random9B": {"text": "Princess Creme holds her kingdom and its exports to a very high standard of quality...", "animation": "Thinking", "next": "Random9B-2"},
|
||||||
|
"Random9B-2": {"text": "Not only does she dress up herself and her land, but so too is her food! Presentation is very important to her!", "animation": "Shocked", "next": "Random9B-3"},
|
||||||
|
"Random9B-3": {"text": "Cakes, cupcakes, ice cream...all stuff that just makes your tummy grumble and gurgle!", "animation": "Concerned", "next": "Random9B-4"},
|
||||||
|
"Random9B-4": {"text": "I mean, I'm NOT lactose intolerant...but, I dunno, my tummy still makes quite a sound after I have some...", "animation": "BellyPoke", "next": "Random9B-5"},
|
||||||
|
"Random9B-5": {"text": "One thing I do wonder, though...where does all that cream come from? Creamy, thick, dense...", "animation": "Concerned", "next": "Random9B-6"},
|
||||||
|
"Random9B-6": {"text": "...do you have any theories?", "animation": "Thinking"},
|
||||||
|
"Random10": {
|
||||||
|
"text": "You know of the food Princess Peanut Butter's kingdom serves, yes?",
|
||||||
|
"animation": "Thinking",
|
||||||
|
"choices": [
|
||||||
|
{"text": "Yes", "next": "Random10C1"},
|
||||||
|
{"text": "No", "next": "Random10C2"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Random10C1": {"text": "Yeah...it's...kind of obvious...", "animation": "Smug", "next": "Random10B"},
|
||||||
|
"Random10C2": {"text": "Hm...maybe you're as dense as peanut butter itself...", "animation": "Thinking", "next": "Random10B"},
|
||||||
|
"Random10B": {"text": "Peanut Butter's grub packs a peanut-butter powered punch!!", "animation": "Shocked", "next": "Random10B-2"},
|
||||||
|
"Random10B-2": {"text": "One quick bite of her grub, and one will be energized, with the power of protein!!", "animation": "Thinking", "next": "Random10B-3"},
|
||||||
|
"Random10B-3": {"text": "I mean, like...I wouldn't want to like...go to the gym, or something, because of it. That's booooooring!!", "animation": "Smug", "next": "Random10B-4"},
|
||||||
|
"Random10B-4": {"text": "She's even got her own peanut butter brand!! With her stupid smug face on it!!", "animation": "Shocked", "next": "Random10B-5"},
|
||||||
|
"Random10B-5": {"text": "I dunno. I feel like it's not ideal to eat her stuff if you're not a meathead...", "animation": "Thinking", "next": "Random10B-6"},
|
||||||
|
"Random10B-6": {"text": "It's like, if you took her stuff, and put in a big shake, you'd probably balloon up!!", "animation": "Concerned", "next": "Random10B-7"},
|
||||||
|
"Random10B-7": {"text": "Just, um, be careful with what you're serving me, yeah?", "animation": "Shocked", "next": "Random10B-8"},
|
||||||
|
"Random10B-8": {"text": "Oh, and...duh, peanut butter can be kinda hard to chew...especially in its rough form...", "animation": "Thinking", "next": "Random10B-9"},
|
||||||
|
"Random10B-9": {"text": "I mean...we've all eaten peanut butter out of the jar. I'm not ashamed to admit it!!", "animation": "Shocked"},
|
||||||
|
"Random11": {"text": "Ages ago...", "animation": "Thinking", "next": "Random11A"},
|
||||||
|
"Random11A": {"text": "At the end of that great contest of ours...", "animation": "Thinking", "next": "Random11A-2"},
|
||||||
|
"Random11A-2": {"text": "...well, BEFORE then, but...around that time in general...", "animation": "Concerned", "next": "Random11A-3"},
|
||||||
|
"Random11A-3": {"text": "...Myself, and my four princess-y friends, made good acquaintances with one nobleman...", "animation": "Shocked", "next": "Random11A-4"},
|
||||||
|
"Random11A-4": {"text": "Mr. Knight...that name...that valiant, brave man...", "animation": "Thinking", "next": "Random11A-5"},
|
||||||
|
"Random11A-5": {"text": "He fought for each of us. It's as if, the souls of hundreds, maybe thousands of man filled his vessel on that one fateful April Fool's...", "animation": "Thinking", "next": "Random11A-6"},
|
||||||
|
"Random11A-6": {"text": "It was a one day war. And with him by our sides, we all felt we could win...", "animation": "Thinking", "next": "Random11A-7"},
|
||||||
|
"Random11A-7": {"text": "But...when that day passed...when the contest was over, and victory was declared...", "animation": "Concerned", "next": "Random11A-8"},
|
||||||
|
"Random11A-8": {"text": "...he departed. At the docks, at the coast of our lands. We all left...and gave t-tearful goodbies...", "animation": "Sad", "next": "Random11A-9"},
|
||||||
|
"Random11A-9": {"text": "...", "animation": "Sad", "next": "Random11A-10"},
|
||||||
|
"Random11A-10": {"text": "...We...I...miss him!!", "animation": "Cry", "next": "Random11A-11"},
|
||||||
|
"Random11A-11": {"text": "I just...I just wonder...if he's...still around...in some form...", "animation": "Sad", "next": "Random11A-12"},
|
||||||
|
"Random11A-12": {"text": "My dear retainer...do you embody...the same sort of spirit? In some, transcendent way...do you reflect him?", "animation": "Sad", "next": "Random11A-13"},
|
||||||
|
"Random11A-13": {"text": "I...hope you can treat me well. Support me. J-just like...just l-like...", "animation": "Sad", "next": "Random11A-14"},
|
||||||
|
"Random11A-14": {"text": "...h-him.", "animation": "Neutral"},
|
||||||
|
"Random12": {"text": "You know, there's one weird thing about myself and my royal friends...", "animation": "Thinking", "next": "Random12A"},
|
||||||
|
"Random12A": {"text": "We all have bangs!! Big, eye-covering bangs!!", "animation": "Shocked", "next": "Random12A-2"},
|
||||||
|
"Random12A-2": {"text": "I mean...it's a trendy hairstyle, okay?!", "animation": "Grumpy", "next": "Random12A-3"},
|
||||||
|
"Random12A-3": {"text": "Being able to see clearly?! Baw, who needs that? People don't want to look at eyes!! Our cute smiles are enough!!", "animation": "Thinking", "next": "Random12A-4"},
|
||||||
|
"Random12A-4": {"text": "Easy to style, too! Just let it grow over your face!! It hardly needs to be combed!!", "animation": "Excited"},
|
||||||
|
"Random13": {"text": "You know, it might not seem like it...but I AM looking at you.", "animation": "Neutral", "next": "Random13A"},
|
||||||
|
"Random13A": {"text": "What, you think I'm not? You think I'm blind or something, and am looking elsewhere instead of at (You)?", "animation": "Thinking", "next": "Random13A-2"},
|
||||||
|
"Random13A-2": {"text": "I bet you think I don't have eyes, and maybe I wear bangs to hide my freakish face!!", "animation": "Grumpy", "next": "Random13A-3"},
|
||||||
|
"Random13A-3": {"text": "Betcha betcha betcha..!!", "animation": "Grumpy", "next": "Random13A-4"},
|
||||||
|
"Random13A-4": {"text": "Those who see under the bangs...why, they typically get their heads chopped off!!", "animation": "Smug", "next": "Random13A-5"},
|
||||||
|
"Random13A-5": {"text": "But...you are my trusted retainer...and I don't know what I'd do without you...", "animation": "Thinking", "next": "Random13A-6"},
|
||||||
|
"Random13A-6": {"text": "So...", "animation": "Smug", "next": "Random13A-7"},
|
||||||
|
"Random13A-7": {"text": "...", "animation": "EyeShow", "next": "Random13A-8"},
|
||||||
|
"Random13A-8": {"text": "No one will ever believe you.", "animation": "Smug"},
|
||||||
|
"Random14": {"text": "Wanna know what our world looks like? Gah, if only I had a map on me...", "animation": "Concerned", "next": "Random14A"},
|
||||||
|
"Random14A": {"text": "It's kind of one big island. We've all claimed our own little chunks, and live in peace. Borders haven't changed in centuries...", "animation": "Thinking", "next": "Random14A-2"},
|
||||||
|
"Random14A-2": {"text": "It's mostly divided by biome and climate.", "animation": "Thinking", "next": "Random14A-3"},
|
||||||
|
"Random14A-3": {"text": "My kingdom is flat, with vast praries and mystical forests...", "animation": "Thinking", "next": "Random14A-4"},
|
||||||
|
"Random14A-4": {"text": "Peanut Butter's place is a little arid and dry. Peanut butter dunes everywhere which their kingdom harvests.", "animation": "Thinking", "next": "Random14A-5"},
|
||||||
|
"Random14A-5": {"text": "Peep's kingdom is a very strange, chaotic place. Peeps flock everywhere; both living and non-living.", "animation": "Neutral", "next": "Random14A-6"},
|
||||||
|
"Random14A-6": {"text": "Chocolate's kingdom is vast and mountainous, with tall mountains of chocolate that grace against the skies, and temples of worship...", "animation": "Thinking", "next": "Random14A-7"},
|
||||||
|
"Random14A-7": {"text": "...and Creme's kingdom is probably the most developed, urbanized and ornate.", "animation": "Thinking", "next": "Random14A-8"},
|
||||||
|
"Random14A-8": {"text": "We're very close to Four Channel...and we have good relations with that nation.", "animation": "Shocked", "next": "Random14A-9"},
|
||||||
|
"Random14A-9": {"text": "...though, I feel like we've been ignored by the mainland, ever since that fateful day.", "animation": "Glance", "next": "Random14A-10"},
|
||||||
|
"Random14A-10": {"text": "We pop up in discussions sometimes over there, particularly on the anniversary of our contest.", "animation": "Thinking", "next": "Random14A-11"},
|
||||||
|
"Random14A-11": {"text": "But that place is SO chaotic, though. It's all divided into states and they frequently have sour relationships with each other!!", "animation": "Shocked", "next": "Random14A-12"},
|
||||||
|
"Random14A-12": {"text": "I think they can learn to be a little more tolerant of each other. I mean, they're all pretty similar, they're just divided up by their national interests.", "animation": "Thinking", "next": "Random14A-13"},
|
||||||
|
"Random14A-13": {"text": "We feel a particularly close kinship to the State of /v/. Strangely, we feel some sense of...belonging, there...", "animation": "Thinking", "next": "Random14A-14"},
|
||||||
|
"Random14A-14": {"text": "But, I dunno about that place...the strange thing is, they define themselves by their electronic entertainment...", "animation": "Thinking", "next": "Random14A-15"},
|
||||||
|
"Random14A-15": {"text": "...but, when they're NOT discussing it, they're far more civil! Otherwise, it's practically lawlessness!!", "animation": "Shocked", "next": "Random14A-16"},
|
||||||
|
"Random14A-16": {"text": "I don't get it! Why dwell in a place that HATE the thing it's all about?!", "animation": "Shocked"},
|
||||||
|
"Random15": {"text": "You know, Peanut Butter won that contest, a long time ago...", "animation": "Thinking", "next": "Random15A"},
|
||||||
|
"Random15A": {"text": "But, you know the expression, \"lost the battle, won the war?\" That's me!!", "animation": "Smug", "next": "Random15A-2"},
|
||||||
|
"Random15A-2": {"text": "I hardly ever see anyone discussing the supposed \"great victory\" of Peanut Butter anymore!!", "animation": "Shocked", "next": "Random15A-3"},
|
||||||
|
"Random15A-3": {"text": "But, I still see that I have passionate fans!! Adoring, loving ones!!", "animation": "Smug", "next": "Random15A-4"},
|
||||||
|
"Random15A-4": {"text": "Vast amounts of illustrations of me! Glorious propaganda for the Mini Machine!! Wahahaha!!", "animation": "Smug", "next": "Random15A-5"},
|
||||||
|
"Random15A-5": {"text": "...some...indecent ones too!! Crazy, weird, freakish things with me, and my friends!! I, um...", "animation": "Shocked", "next": "Random15A-6"},
|
||||||
|
"Random15A-6": {"text": "...well, have you also heard the expression, \"no such thing as bad publicity?!", "animation": "Smug", "next": "Random15A-7"},
|
||||||
|
"Random15A-7": {"text": "Beat that, PB!! I have wayyyyyyy more lewds than you do!!", "animation": "Smug"},
|
||||||
|
"Random16": {"text": "...are you wondering how old I am?!", "animation": "Thinking", "next": "Random16A"},
|
||||||
|
"Random16A": {"text": "First, rude. Never ask a girl her age!!", "animation": "Grumpy", "next": "Random16A-2"},
|
||||||
|
"Random16A-2": {"text": "Second...I'm a princess! A monarch, a ruler! I can't be the leader of this kingdom if I'm just some annoying, immature child!", "animation": "Shocked", "next": "Random16A-3"},
|
||||||
|
"Random16A-3": {"text": "Third...do you think because someone doesn't have BIG JIGGLING BOOBS and TWO MOONS FOR A BUTT, they're not mature?!", "animation": "Grumpy", "next": "Random16A-4"},
|
||||||
|
"Random16A-4": {"text": "Fourth...I'm a cartoon.", "animation": "Neutral", "next": "Random16A-5"},
|
||||||
|
"Random16A-5": {"text": "I'm as old as you want me to be~", "animation": "Smug"},
|
||||||
|
"Random17": {"text": "Ages ago, on one April Fool's...", "animation": "Thinking", "next": "Random17A"},
|
||||||
|
"Random17A": {"text": "This was before my time, so this is all hearsay and history, but...", "animation": "Shocked", "next": "Random17A-2"},
|
||||||
|
"Random17A-2": {"text": "...denizens of the Four Channel were forcefully given names.", "animation": "Concerned", "next": "Random17A-3"},
|
||||||
|
"Random17A-3": {"text": "They're a nation that prides themselves on their collectivity, their lack of individuality, their anonymity!!", "animation": "Thinking", "next": "Random17A-4"},
|
||||||
|
"Random17A-4": {"text": "...so, on one strange day, they awoke, and suddenly, every time they spoke, their name had changed.", "animation": "Shocked", "next": "Random17A-5"},
|
||||||
|
"Random17A-5": {"text": "Many were displeased. How dare their rules take away their namelessness, an important tenet of their culture!!", "animation": "Thinking", "next": "Random17A-6"},
|
||||||
|
"Random17A-6": {"text": "In fact, it's disgraceful over there to desire a name! Many of those who embrace one earn the infamous title of...\"Tripfag\".", "animation": "Shocked", "next": "Random17A-7"},
|
||||||
|
"Random17A-7": {"text": "So, this day was quite the shake up! Some found their randomly given names humorous, some wanted the names of some choice figures...", "animation": "Thinking", "next": "Random17A-8"},
|
||||||
|
"Random17A-8": {"text": "And, above all, NOBODY wanted the most abominable name...", "animation": "Concerned", "next": "Random17A-9"},
|
||||||
|
"Random17A-9": {"text": "...\"Anthony Burch\".", "animation": "Shocked", "next": "Random17A-10"},
|
||||||
|
"Random17A-10": {"text": "...to be frank, this is just some story I read somewhere. I have no idea what some of this stuff means...", "animation": "Smug"},
|
||||||
|
"Random18": {"text": "Many years ago, on one April Fool's...", "animation": "Thinking", "next": "Random18A"},
|
||||||
|
"Random18A": {"text": "Four Channel was in a state of utter anarchy!!", "animation": "Shocked", "next": "Random18A-2"},
|
||||||
|
"Random18A-2": {"text": "Borders were blended together, almost arbitrarily!! National interests mixed, without a care in the world!!", "animation": "Thinking", "next": "Random18A-3"},
|
||||||
|
"Random18A-3": {"text": "Cartoon and comic book nerds having to contend with...professional chefs...", "animation": "Concerned", "next": "Random18A-4"},
|
||||||
|
"Random18A-4": {"text": "The bulkiest bodybuilders in their world...matched with library bookworms! The ultimate specimens!!", "animation": "Thinking", "next": "Random18A-5"},
|
||||||
|
"Random18A-5": {"text": "Fashionistas and photographers...", "animation": "Shocked", "next": "Random18A-6"},
|
||||||
|
"Random18A-6": {"text": "Gearheads and mechaheads...car and giant robot enthusiasts!!", "animation": "Concerned", "next": "Random18A-7"},
|
||||||
|
"Random18A-7": {"text": "Craziest yet: the most schizophrenic political conspiracy theorists...matched together with those who want to do indecent things to cartoon horses!!", "animation": "Sad", "next": "Random18A-8"},
|
||||||
|
"Random18A-8": {"text": "The consequences of this sudden fusion of these states...some say it's still felt to this very day..!", "animation": "Shocked", "next": "Random18A-9"},
|
||||||
|
"Random18A-9": {"text": "Thankfully, it was just one, deranged, twisted day...what evil souls would do that to their own nation?!", "animation": "Sad"},
|
||||||
|
"Random19": {"text": "Ah! I suppose I should tell you the tale of...that day. That faithful contest.", "animation": "Excited", "next": "Random19A"},
|
||||||
|
"Random19A": {"text": "That one fateful April Fool's, the crossover between the holiday of fools and the holiday of the Lord...", "animation": "Thinking", "next": "Random19A-2"},
|
||||||
|
"Random19A-2": {"text": "I mean, someone's lord, I think!! I associate it more with yummy candy and eggs and bnuuies!!", "animation": "Excited", "next": "Random19A-3"},
|
||||||
|
"Random19A-3": {"text": "But it was also our day! A fearsome day it was!!", "animation": "Shocked", "next": "Random19A-4"},
|
||||||
|
"Random19A-4": {"text": "Five tasty teams. Creme, Peep, myself, Peanut Butter and Chocolate...", "animation": "Thinking", "next": "Random19A-5"},
|
||||||
|
"Random19A-5": {"text": "What was the ambition? Why were we fighting in the first place? To be frank, I don't even know...", "animation": "Thinking", "next": "Random19A-6"},
|
||||||
|
"Random19A-6": {"text": "And, of course, we had our brave soldiers, fighting and supporting their sides!!", "animation": "Excited", "next": "Random19A-7"},
|
||||||
|
"Random19A-7": {"text": "I don't even know how they came to our aid, but they were a part of our fighting forces, and did their best!!", "animation": "Excited", "next": "Random19A-8"},
|
||||||
|
"Random19A-8": {"text": "I remember the accumulation of points! How did our teams earn points? I don't know!!", "animation": "Thinking", "next": "Random19A-9"},
|
||||||
|
"Random19A-9": {"text": "It was a one-day, bloody war...many succumbed to the terrors. And hedonism, in some cases. 'Specially Creme.", "animation": "Thinking", "next": "Random19A-10"},
|
||||||
|
"Random19A-10": {"text": "It was on that day where me and my princess friends came to the public's attention!!", "animation": "Excited", "next": "Random19A-11"},
|
||||||
|
"Random19A-11": {"text": "But, by the end...the winner had been declared...", "animation": "Shocked", "next": "Random19A-12"},
|
||||||
|
"Random19A-12": {"text": "...it was that Peanut Butter, that spunky girl, who led her zealous army to victory...", "animation": "Thinking", "next": "Random19A-13"},
|
||||||
|
"Random19A-13": {"text": "I felt like I won, though!! I'm the girl the masses cared most for!! It was obvious!!", "animation": "Smug", "next": "Random19A-14"},
|
||||||
|
"Random19A-14": {"text": "And dare I say...RIGGED!!", "animation": "Grumpy"},
|
||||||
|
"Random20": {"text": "Hey, do you Like me, dear retainer?", "animation": "Thinking", "next": "Random20A"},
|
||||||
|
"Random20A": {"text": "I mean, like, Like like. Capital L like. Liking. Thumbs up!", "animation": "Shocked", "next": "Random20A-2"},
|
||||||
|
"Random20A-2": {"text": "Because on one April Fool's, everyone couldn't stop Liking everything.", "animation": "Neutral", "next": "Random20A-3"},
|
||||||
|
"Random20A-3": {"text": "I mean, liking things is nice...but if Liking something makes the more liked things appear most prominently...", "animation": "Thinking", "next": "Random20A-4"},
|
||||||
|
"Random20A-4": {"text": "...then, how can people have balanced, diverse conversations?", "animation": "Shocked", "next": "Random20A-5"},
|
||||||
|
"Random20A-5": {"text": "Sometimes, you need to see the controversial opinions! They might make you mad, but they open up new ways of thinking...", "animation": "Neutral", "next": "Random20A-6"},
|
||||||
|
"Random20A-6": {"text": "It's not to say that all opinions are necessarily correct, or should be agreed with, but...everyone should have a voice!", "animation": "Neutral", "next": "Random20A-7"},
|
||||||
|
"Random20A-7": {"text": "Now, if you agree with my post, make sure to give it a Like!! It goes a long way for the algorithm...", "animation": "Smug", "next": "Random20A-8"},
|
||||||
|
"Random20A-8": {"text": "...wait, what am I saying?!", "animation": "Shocked"},
|
||||||
|
"Random21": {"text": "Tell me, retainer: did you ever have a widespread pandemic where you're from?!", "animation": "Concerned", "next": "Random21A"},
|
||||||
|
"Random21A": {"text": "Because, on one April Fool's, myself and my princess friends all had to be quarantined...", "animation": "Thinking", "next": "Random21A-2"},
|
||||||
|
"Random21A-2": {"text": "There was an insidious virus that was going around! It made your words green with disease!!", "animation": "Shocked", "next": "Random21A-3"},
|
||||||
|
"Random21A-3": {"text": "It was contagious, too!!", "animation": "Shocked", "next": "Random21A-4"},
|
||||||
|
"Random21A-4": {"text": "Mysteriously, though, it just lasted for one day. Everyone got sick, and then...poof.", "animation": "Thinking", "next": "Random21A-5"},
|
||||||
|
"Random21A-5": {"text": "Were you as fortunate, if you ever had such a thing?", "animation": "Thinking"},
|
||||||
|
"Random22": {"text": "Dear retainer...do you know the treachery of...Canadians?!", "animation": "Thinking", "next": "Random22A"},
|
||||||
|
"Random22A": {"text": "Those frostbitten, beer-swillin', hose-head hockey nuts!!", "animation": "Shocked", "next": "Random22A-2"},
|
||||||
|
"Random22A-2": {"text": "On one April Fool's, Canadians had infiltrated the land of Four Channel!!", "animation": "Shocked", "next": "Random22A-3"},
|
||||||
|
"Random22A-3": {"text": "We had to deploy the Rakes to dispose of those leafs...", "animation": "Thinking", "next": "Random22A-4"},
|
||||||
|
"Random22A-4": {"text": "...but, thankfully, the panic soon subsided, and the nationwide fear left us just as quickly as it came.", "animation": "Neutral", "next": "Random22A-5"},
|
||||||
|
"Random22A-5": {"text": "I fret that there still may be Canadians walking among us, though...", "animation": "Shocked", "next": "Random22A-6"},
|
||||||
|
"Random22A-6": {"text": "...actually, wait. I just realized I don't know where Canada is on a map. Or what a Canada even is!!", "animation": "Concerned"},
|
||||||
|
"Random23": {"text": "Retainer, are you heckin' poggers?", "animation": "Smug", "next": "Random23A"},
|
||||||
|
"Random23A": {"text": ":Pog: :BibleThump: :FeelsOkayMan: :ClownWorld: :TrollFace: :Desu:", "animation": "Smug", "next": "Random23A-2"},
|
||||||
|
"Random23A-2": {"text": "...wait, you should be seeing me speak funny pictures!! Where'd they go?!", "animation": "Concerned", "next": "Random23A-3"},
|
||||||
|
"Random23A-3": {"text": "Ugh!! I thought we were back during that one April Fool's...you could talk, and funny pictures would come out of your mouth!!'", "animation": "Thinking", "next": "Random23A-4"},
|
||||||
|
"Random23A-4": {"text": "Grrrr!! Now I just embarrassed myself!! I thought it was still happening!!", "animation": "Grumpy", "next": "Random23A-5"},
|
||||||
|
"Random23A-5": {"text": "I'm still heckin' poggers though, right, dear retainer?!", "animation": "Sad"},
|
||||||
|
"Random24": {"text": "Retainer, do you fret that everything in your life is...artificial?", "animation": "Sad", "next": "Random24A"},
|
||||||
|
"Random24A": {"text": "Like, it's generated by some higher intelligence. With no higher thought. Just an algorithm, a chain of \"if-else\" statements...", "animation": "Concerned", "next": "Random24A-2"},
|
||||||
|
"Random24A-2": {"text": "On one April Fool's, there was this vast feeling of this...like the people I talked to weren't even real!!", "animation": "Thinking", "next": "Random24A-3"},
|
||||||
|
"Random24A-3": {"text": "Their comments generated by some unthinking software, made to mimic real speech!!", "animation": "Sad", "next": "Random24A-4"},
|
||||||
|
"Random24A-4": {"text": "But that feeling passed after the day came, and the world returned to normal...", "animation": "Neutral", "next": "Random24A-5"},
|
||||||
|
"Random24A-5": {"text": "Thankfully, we now live in a world, free of artificial intelligence impersonating humans!!", "animation": "Thinking", "next": "Random24A-6"},
|
||||||
|
"Random24A-6": {"text": "I'm sure your world is equally as liberated, dear retainer!!", "animation": "Excited"},
|
||||||
|
"Random25": {"text": "Are you much of a business-savvy person?!", "animation": "Thinking", "next": "Random25A"},
|
||||||
|
"Random25A": {"text": "You know, keeping track of markets, of companies, of their stock value...", "animation": "Shocked", "next": "Random25A-2"},
|
||||||
|
"Random25A-2": {"text": "I don't know about all of that stuff! I like thinking about bnuuies and cute things more!!", "animation": "Smug", "next": "Random25A-3"},
|
||||||
|
"Random25A-3": {"text": "I leave that sort of thing to my retainers! They do all the actual boring governing and planning...", "animation": "Glance", "next": "Random25A-4"},
|
||||||
|
"Random25A-4": {
|
||||||
|
"text": "Are you financially savvy, then? You know all about that money and wealth stuff, right?",
|
||||||
|
"animation": "Shocked",
|
||||||
|
"choices": [
|
||||||
|
{"text": "A little...", "next": "Random25AC1"},
|
||||||
|
{"text": "Uhhhhh....", "next": "Random25AC2"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Random25AC1": {"text": "Oh, good! You would've been a big help on a certain day, then!!", "animation": "Thinking", "next": "Random25AB"},
|
||||||
|
"Random25AC2": {"text": "What?! What did I even hire you for, then?!", "animation": "Grumpy", "next": "Random25AB"},
|
||||||
|
"Random25AB": {"text": "A little while back, on April Fool's, Four Channel opened up a big stock market!! The Yotsuba Stock Exchange!!", "animation": "Shocked", "next": "Random25AB-2"},
|
||||||
|
"Random25AB-2": {"text": "I would've gone all in $CHUCK or $BANE, maybe $CLOWN or $DESU...", "animation": "Thinking", "next": "Random25AB-3"},
|
||||||
|
"Random25AB-3": {"text": "$BOOB would be an unwise investment, at least I know that...", "animation": "Concerned", "next": "Random25AB-4"},
|
||||||
|
"Random25AB-4": {"text": "...but, well, I didn't go all in on anything, because the thought of me potentially losing my entire treasury made me sad...", "animation": "Sad", "next": "Random25AB-5"},
|
||||||
|
"Random25AB-5": {"text": "If only you were there, dear retainer, perhaps we could've /madeit/!", "animation": "Excited", "next": "Random25AB-6"},
|
||||||
|
"Random25AB-6": {"text": "Maybe we're all ngmi, though...", "animation": "Sad"},
|
||||||
|
"Random26": {"text": "Are you much of an efficiency nut?", "animation": "Thinking", "next": "Random26A"},
|
||||||
|
"Random26A": {"text": "You know, everything being in order, efficient, rapidly progressing, not bloated, not saddled with bureaucracy or fraud or waste!!", "animation": "Shocked", "next": "Random26A-2"},
|
||||||
|
"Random26A-2": {"text": "Kinda like how Creme runs things, to be honest...", "animation": "Thinking", "next": "Random26A-3"},
|
||||||
|
"Random26A-3": {"text": "A-Anyways!! On one April Fool's, I heard Four Channel established their own efficiency department!!", "animation": "Shocked", "next": "Random26A-4"},
|
||||||
|
"Random26A-4": {"text": "Things started out reasonable. A cut there, a bit there...maybe the idea was fine initially...", "animation": "Thinking", "next": "Random26A-5"},
|
||||||
|
"Random26A-5": {"text": "But then, they got really crazy with it. In the name of \"efficiency\", they cut out whole chunks of their own nation!!", "animation": "Shocked", "next": "Random26A-6"},
|
||||||
|
"Random26A-6": {"text": "All the denizens had to move somewhere else!! Even somewhere they had no connection or interest in!!", "animation": "Shocked", "next": "Random26A-7"},
|
||||||
|
"Random26A-7": {"text": "So much for efficiency! That's unjust tyranny!! You need some degree of separation and delegation, right?!", "animation": "Shocked", "next": "Random26A-8"},
|
||||||
|
"Random26A-8": {"text": "It got so intense, that the whole of Four Channel was blended all into one main area! It was utter chaos!!", "animation": "Concerned", "next": "Random26A-9"},
|
||||||
|
"Random26A-9": {"text": "But, thankfully, the day after, those dummies must've figured out that it was a bad idea, and cut the whole thing.", "animation": "Thinking", "next": "Random26A-10"},
|
||||||
|
"Random26A-10": {"text": "Well, I mean...in a way, I guess they were at least ruthlessly effective and competent...even if their execution was misguided...", "animation": "Thinking", "next": "Random26A-11"},
|
||||||
|
"Random26A-11": {"text": "Imagine that...an efficiency department that only cuts a little bit, and just...gives up and dissolves.", "animation": "Thinking", "next": "Random26A-12"},
|
||||||
|
"Random26A-12": {"text": "...", "animation": "Neutral", "next": "Random26A-13"},
|
||||||
|
"Random26A-13": {"text": "...I'm just a cute little princess. I don't know much about governing, so don't take my words too seriously!!", "animation": "Smug"},
|
||||||
|
"Random27": {"text": "Retainer, are you good at answering Captchas?", "animation": "Thinking", "next": "Random27A"},
|
||||||
|
"Random27A": {"text": "Y'know, those letters and numbers that appear before making a social media post!", "animation": "Shocked", "next": "Random27A-2"},
|
||||||
|
"Random27A-2": {"text": "Or...it used to be, back in the day. You'd get such a thing! Now it's a humiliation ritual!!", "animation": "Thinking", "next": "Random27A-3"},
|
||||||
|
"Random27A-3": {"text": "They make you identify all sorts of abstract shapes and things just so you have the privilege of posting!", "animation": "Concerned", "next": "Random27A-4"},
|
||||||
|
"Random27A-4": {"text": "...", "animation": "Thinking", "next": "Random27A-5"},
|
||||||
|
"Random27A-5": {"text": "I don't have a joke. That April Fool's joke sucked. If you know, you know.", "animation": "Grumpy"},
|
||||||
|
"Digest-1": {"text": "Oof, my tummy was making an awful lot of noise...", "animation": "NeutralFull"},
|
||||||
|
"Digest-2": {"text": "Heavens, that probably was too much...", "animation": "NeutralFull"},
|
||||||
|
"Digest-3": {"text": "Retainer, are you sure you're treating me right?", "animation": "NeutralFull"},
|
||||||
|
"Digest-4": {"text": "Are you sure you're giving me a proper diet?!", "animation": "NeutralFull"},
|
||||||
|
"Digest-5": {"text": "I suppose I could have some more, now...", "animation": "NeutralFull"},
|
||||||
|
"Digest-6": {"text": "My dress isn't as tight, now...", "animation": "NeutralFull"},
|
||||||
|
"Digest-7": {"text": "I feel better, now!!", "animation": "NeutralFull"},
|
||||||
|
"Digest-8": {"text": "All that yummy food seems to be gone, now...", "animation": "NeutralFull"},
|
||||||
|
"Digest-9": {"text": "Aah...", "animation": "NeutralFull"},
|
||||||
|
"Digest-10": {"text": "Oooh...", "animation": "NeutralFull"},
|
||||||
|
"Digest-11": {"text": "My stomach has settled, now...", "animation": "NeutralFull"},
|
||||||
|
"Digest-12": {"text": "Another round then, dear retainer...", "animation": "NeutralFull"},
|
||||||
|
"Digest-13": {"text": "Feels good...", "animation": "NeutralFull"},
|
||||||
|
"Digest-14": {"text": "Ah, back to being Mini...", "animation": "NeutralFull"},
|
||||||
|
"Digest-15": {"text": "Something feels different...", "animation": "NeutralFull"},
|
||||||
|
"Throw-1": {"text": "Look, we can have a food fight later!!", "animation": "Excited"},
|
||||||
|
"Throw-2": {"text": "Yay, food fight!!", "animation": "Excited"},
|
||||||
|
"Throw-3": {"text": "I didn't know you knew how to have fun, retainer!!", "animation": "Shocked"},
|
||||||
|
"Throw-4": {"text": "Haha, so funny!!", "animation": "Excited"},
|
||||||
|
"Throw-5": {"text": "Wow, you've got good throwing skills!!", "animation": "Excited"},
|
||||||
|
"Throw-6": {"text": "Wow, that thing went flying!!", "animation": "Excited"},
|
||||||
|
"Throw-7": {"text": "Look at it go!!", "animation": "Shocked"},
|
||||||
|
"Throw-8": {"text": "Zooooooooom!!", "animation": "Excited"},
|
||||||
|
"Throw-9": {"text": "VVVVVvsssssssshooooom!!", "animation": "Excited"},
|
||||||
|
"Throw-10": {"text": "Whoooooosh!!", "animation": "Excited"},
|
||||||
|
"Throw-11": {"text": "Food fight!!", "animation": "Shocked"},
|
||||||
|
"Throw-12": {"text": "So you're not so stuck-up!!", "animation": "Shocked"},
|
||||||
|
"Throw-13": {"text": "Silly retainer!!", "animation": "Shocked"},
|
||||||
|
"Throw-14": {"text": "...okay, but you WILL clean up afterwards, right?!", "animation": "Concerned"},
|
||||||
|
"Throw-15": {"text": "Wow!!", "animation": "Shocked"},
|
||||||
|
"Feed-1": {"text": "Aah, serving your princess!!", "animation": "Excited"},
|
||||||
|
"Feed-2": {"text": "Ooh, gimme gimme!!", "animation": "Excited"},
|
||||||
|
"Feed-3": {"text": "Looks tasty!!", "animation": "Excited"},
|
||||||
|
"Feed-4": {"text": "A gift? For me?!", "animation": "Excited"},
|
||||||
|
"Feed-5": {"text": "Yummy!!", "animation": "Excited"},
|
||||||
|
"Feed-6": {"text": "Yaayyy!!", "animation": "Excited"},
|
||||||
|
"Feed-7": {"text": "Ooh!!", "animation": "Excited"},
|
||||||
|
"Feed-8": {"text": "Oh, my tummy was rumbling!", "animation": "Excited"},
|
||||||
|
"Feed-9": {"text": "Mini hungers!!", "animation": "Excited"},
|
||||||
|
"Feed-10": {"text": "Ahh..!", "animation": "Excited"},
|
||||||
|
"Feed-11": {"text": "Aaaah...", "animation": "Excited"},
|
||||||
|
"Feed-12": {"text": "Food, food!!", "animation": "Excited"},
|
||||||
|
"Feed-13": {"text": "Tasty, tasty!!", "animation": "Excited"},
|
||||||
|
"Feed-14": {"text": "Yes!!", "animation": "Excited"},
|
||||||
|
"Feed-15": {"text": "Serve your princess!!", "animation": "Excited"},
|
||||||
|
"FeedFull-1": {"text": "Um, retainer...my tummy hurts...", "animation": "Concerned"},
|
||||||
|
"FeedFull-2": {"text": "My tummy is making grumbles...", "animation": "Concerned"},
|
||||||
|
"FeedFull-3": {"text": "Are you sure..?", "animation": "Concerned"},
|
||||||
|
"FeedFull-4": {"text": "Well, I do need to be a big girl...", "animation": "Concerned"},
|
||||||
|
"FeedFull-5": {"text": "I-if you want me to, retainer...", "animation": "Concerned"},
|
||||||
|
"FeedFull-6": {"text": "I can...try and fit it in...for you...", "animation": "Concerned"},
|
||||||
|
"FeedFull-7": {"text": "Okay, if you want me to...", "animation": "Concerned"},
|
||||||
|
"FeedFull-8": {"text": "I am a little...full...", "animation": "Concerned"},
|
||||||
|
"FeedFull-9": {"text": "This is going to make me cuter, right..?", "animation": "Concerned"},
|
||||||
|
"FeedFull-10": {"text": "Still..?", "animation": "Shocked"},
|
||||||
|
"FeedFull-11": {"text": "There's more..?!", "animation": "Shocked"},
|
||||||
|
"FeedFull-12": {"text": "It's inconsiderate to leave uneaten food, so...", "animation": "Shocked"},
|
||||||
|
"FeedFull-13": {"text": "It's impolite to not eat what's served...that's what Creme told me...", "animation": "Shocked"},
|
||||||
|
"FeedFull-14": {"text": "Okay...", "animation": "Shocked"},
|
||||||
|
"FeedFull-15": {"text": "I'll fit it in...", "animation": "Concerned"},
|
||||||
|
"Eat-1": {"text": "Splendid!!", "animation": "Excited"},
|
||||||
|
"Eat-2": {"text": "I'll have seconds!!", "animation": "Excited"},
|
||||||
|
"Eat-3": {"text": "That was so yummy..!!", "animation": "Excited"},
|
||||||
|
"Eat-4": {"text": "You serve me well, retainer!!", "animation": "Excited"},
|
||||||
|
"Eat-5": {"text": "So tasty!!", "animation": "Excited"},
|
||||||
|
"Eat-6": {"text": "Yum yum!!", "animation": "Excited"},
|
||||||
|
"Eat-7": {"text": "Yummy, yummy!!", "animation": "Excited"},
|
||||||
|
"Eat-8": {"text": "So gooood!!", "animation": "Excited"},
|
||||||
|
"Eat-9": {"text": "Tasty tasty!!", "animation": "Excited"},
|
||||||
|
"Eat-10": {"text": "Good!!", "animation": "Excited"},
|
||||||
|
"Eat-11": {"text": "That tasted great!!", "animation": "Excited"},
|
||||||
|
"Eat-12": {"text": "I could have some more...", "animation": "Excited"},
|
||||||
|
"Eat-13": {"text": "Perhaps I could use a little more...", "animation": "Excited"},
|
||||||
|
"Eat-14": {"text": "More, maybe...", "animation": "Thinking"},
|
||||||
|
"Eat-15": {"text": "Maybe another one...", "animation": "Thinking"},
|
||||||
|
"EatFull-1": {"text": "Mmmgh...", "animation": "Concerned"},
|
||||||
|
"EatFull-2": {"text": "Nnngh...", "animation": "Concerned"},
|
||||||
|
"EatFull-3": {"text": "Agh...", "animation": "Concerned"},
|
||||||
|
"EatFull-4": {"text": "That's the last one...right?", "animation": "Concerned"},
|
||||||
|
"EatFull-5": {"text": "Retainer...y-you can stop now...", "animation": "Concerned"},
|
||||||
|
"EatFull-6": {"text": "O-okay, we should slow down...", "animation": "Concerned"},
|
||||||
|
"EatFull-7": {"text": "Surely this is the last gift of yours, yes..?", "animation": "Concerned"},
|
||||||
|
"EatFull-8": {"text": "It tasted good, but...I'm stuffed....", "animation": "Concerned"},
|
||||||
|
"EatFull-9": {"text": "L-lovely, but maybe we can stop now..?", "animation": "Concerned"},
|
||||||
|
"EatFull-10": {"text": "M-maybe slow it down, a little..?", "animation": "Concerned"},
|
||||||
|
"EatFull-11": {"text": "I know you spoil me, but is this really appropriate..?", "animation": "Concerned"},
|
||||||
|
"EatFull-12": {"text": "L-last one, right?", "animation": "Concerned"},
|
||||||
|
"EatFull-13": {"text": "Ugh...", "animation": "Concerned"},
|
||||||
|
"EatFull-14": {"text": "Mmf...", "animation": "Concerned"},
|
||||||
|
"EatFull-15": {"text": "Um...", "animation": "Concerned"},
|
||||||
|
"Full-1": {"text": "I can't take any more!!", "animation": "Thinking"},
|
||||||
|
"Full-2": {"text": "I'm gonna blow up if you give me more gifts!!", "animation": "Thinking"},
|
||||||
|
"Full-3": {"text": "I think I'd throw up if I ate more...", "animation": "Concerned"},
|
||||||
|
"Full-4": {"text": "No more, retainer..!", "animation": "Concerned"},
|
||||||
|
"Full-5": {"text": "No more!!", "animation": "Shocked"},
|
||||||
|
"Full-6": {"text": "I'm good, thanks...", "animation": "Shocked"},
|
||||||
|
"Full-7": {"text": "I'm just a little princess, not a hog!!", "animation": "Shocked"},
|
||||||
|
"Full-8": {"text": "Stop it!!", "animation": "Shocked"},
|
||||||
|
"Full-9": {"text": "Please, I can't eat anymore!!", "animation": "Shocked"},
|
||||||
|
"Full-10": {"text": "Peanut Butter could probably eat this much, I can't..!", "animation": "Shocked"},
|
||||||
|
"Full-11": {"text": "I'm Mini, okay? Not Mega!!", "animation": "Shocked"},
|
||||||
|
"Full-12": {"text": "Peep probably could eat more than me, and I'm not Peep!!", "animation": "Shocked"},
|
||||||
|
"Full-13": {"text": "P-put that back into the catalog, please...", "animation": "Concerned"},
|
||||||
|
"Full-14": {"text": "I'm not as big as Chocolate, I can't fit that much into this body..!", "animation": "Shocked"},
|
||||||
|
"Full-15": {"text": "S-save it for later, okay?!", "animation": "Shocked"},
|
||||||
|
"LowCapacity-1": {"text": "How do you expect to fit THAT into this tiny body?!", "animation": "Shocked"},
|
||||||
|
"LowCapacity-2": {"text": "Are you kidding me?! I can't eat that!!", "animation": "Shocked"},
|
||||||
|
"LowCapacity-3": {"text": "Who do you take me for, a super-fat janny? That's too much!!", "animation": "Shocked"},
|
||||||
|
"LowCapacity-4": {"text": "Too much!! Get something smaller!!", "animation": "Shocked"},
|
||||||
|
"LowCapacity-5": {"text": "Get some common sense!! Something smaller, please!!", "animation": "Shocked"},
|
||||||
|
"LowCapacity-6": {"text": "I'm Mini, give me some Mini proportions!!", "animation": "Shocked"},
|
||||||
|
"LowCapacity-7": {"text": "I can't wolf food down big food like a janitor does Hot Pockets!!", "animation": "Shocked"},
|
||||||
|
"LowCapacity-8": {"text": "Something for my tiny figure, please!!", "animation": "Shocked"},
|
||||||
|
"LowCapacity-9": {"text": "Smaller, please...!", "animation": "Shocked"},
|
||||||
|
"LowCapacity-10": {"text": "Make it more Mini, if you can..!", "animation": "Shocked"},
|
||||||
|
"LowCapacity-11": {"text": "I'd just explode if I ate that!!", "animation": "Shocked"},
|
||||||
|
"LowCapacity-12": {"text": "Bnuuy, what do you think? I'd die if I ate that, right?", "animation": "BnuuyHold"},
|
||||||
|
"LowCapacity-13": {"text": "I like it small, just like me...", "animation": "Shocked"},
|
||||||
|
"LowCapacity-14": {"text": "My tummy can't possibly contain something like that!!", "animation": "Shocked"},
|
||||||
|
"LowCapacity-15": {"text": "Let's shrink the proportions, please...", "animation": "Thinking"},
|
||||||
|
"FoodTaken-1": {"text": "Hey, you can't just...do that!!", "animation": "Concerned"},
|
||||||
|
"FoodTaken-2": {"text": "Do that again and it'll be off with your head!!", "animation": "Grumpy"},
|
||||||
|
"FoodTaken-3": {"text": "I'm...I'm gonna headbutt you if you take my food!!", "animation": "Grumpy"},
|
||||||
|
"FoodTaken-4": {"text": "Meanie, give that back!!", "animation": "Sad"},
|
||||||
|
"FoodTaken-5": {"text": "Waaaahhhh!!", "animation": "Sad"},
|
||||||
|
"FoodTaken-6": {"text": "That was supposed to be mine!!", "animation": "Sad"},
|
||||||
|
"FoodTaken-7": {"text": "Y-you promised me that...", "animation": "Sad"},
|
||||||
|
"FoodTaken-8": {"text": "Please, h-hand that back!!", "animation": "Sad"},
|
||||||
|
"FoodTaken-9": {"text": "D-don't take food from me..!!", "animation": "Concerned"},
|
||||||
|
"FoodTaken-10": {"text": "I thought that was my gift...", "animation": "Concerned"},
|
||||||
|
"FoodTaken-11": {"text": "JERK!!", "animation": "Grumpy"},
|
||||||
|
"FoodTaken-12": {"text": "Meanie!!", "animation": "Grumpy"},
|
||||||
|
"FoodTaken-13": {"text": "Stop that!!", "animation": "Grumpy"},
|
||||||
|
"FoodTaken-14": {"text": "My name was on that..!", "animation": "Sad"},
|
||||||
|
"FoodTaken-15": {"text": "Why are you so mean, retainer..?", "animation": "Grumpy"},
|
||||||
|
"FoodTakenFull-1": {"text": "Oh, so you now care about me...", "animation": "NeutralFull"},
|
||||||
|
"FoodTakenFull-2": {"text": "T-thanks...", "animation": "NeutralFull"},
|
||||||
|
"FoodTakenFull-3": {"text": "I finally got through to you...", "animation": "NeutralFull"},
|
||||||
|
"FoodTakenFull-4": {"text": "Y-yeah, just...put that back.", "animation": "NeutralFull"},
|
||||||
|
"FoodTakenFull-5": {"text": "Thank you, retainer...", "animation": "NeutralFull"},
|
||||||
|
"FoodTakenFull-6": {"text": "How...chivalrous...", "animation": "NeutralFull"},
|
||||||
|
"FoodTakenFull-7": {"text": "Oh, thank goodness...", "animation": "NeutralFull"},
|
||||||
|
"FoodTakenFull-8": {"text": "So...knightly...", "animation": "NeutralFull"},
|
||||||
|
"FoodTakenFull-9": {"text": "Heavens, you finally relented...", "animation": "NeutralFull"},
|
||||||
|
"FoodTakenFull-10": {"text": "Right, then, let's put that back...", "animation": "NeutralFull"},
|
||||||
|
"FoodTakenFull-11": {"text": "No more food for ol' Mini...", "animation": "NeutralFull"},
|
||||||
|
"FoodTakenFull-12": {"text": "Too much pampering, you see...", "animation": "NeutralFull"},
|
||||||
|
"FoodTakenFull-13": {"text": "So you're finally done spoling me...", "animation": "NeutralFull"},
|
||||||
|
"FoodTakenFull-14": {"text": "T-thanks... you finally see the light...", "animation": "NeutralFull"},
|
||||||
|
"FoodTakenFull-15": {"text": "You were being TOO good of a servant...", "animation": "NeutralFull"},
|
||||||
|
"StageUp-1": {"text": "Um, retainer, do you think I look different..?", "animation": "Shocked"},
|
||||||
|
"StageUp-2": {"text": "Did something just happen?", "animation": "Concerned"},
|
||||||
|
"StageUp-3": {"text": "I felt something bad just happened, and you were responsible..!", "animation": "Shocked"},
|
||||||
|
"StageUp-4": {"text": "You know, I feel really good now for whatever reason.", "animation": "Smug"},
|
||||||
|
"StageUp-5": {"text": "Why does my dress feel tighter..?", "animation": "Concerned"},
|
||||||
|
"StageUp-6": {"text": "Something is off...", "animation": "Thinking"},
|
||||||
|
"StageUp-7": {"text": "Retainer, are you sure you know what you're doing?!", "animation": "Thinking"},
|
||||||
|
"StageUp-8": {"text": "Am I \"Mini\" anymore..?", "animation": "Thinking"},
|
||||||
|
"StageUp-9": {"text": "I feel...different..", "animation": "Thinking"},
|
||||||
|
"StageUp-10": {"text": "Um...retainer...", "animation": "Concerned"},
|
||||||
|
"StageUp-11": {"text": "It's...getting kind of tight...", "animation": "Concerned"},
|
||||||
|
"StageUp-12": {"text": "Too...tight...", "animation": "NeutralFull"},
|
||||||
|
"StageUp-13": {"text": "Nnngh...", "animation": "NeutralFull"},
|
||||||
|
"StageUp-14": {"text": "I...this isn't...right...", "animation": "NeutralFull"},
|
||||||
|
"StageUp-15": {"text": "I'm...I'm still...Mini...", "animation": "Concerned"},
|
||||||
|
"MouthKiss-1": {"text": "Uwah!!", "animation": "Embarrassed"},
|
||||||
|
"MouthKiss-2": {"text": "Y-you can't just...d-do that!!", "animation": "Embarrassed"},
|
||||||
|
"MouthKiss-3": {"text": "D-Did I give you permission to do that?!", "animation": "Embarrassed"},
|
||||||
|
"MouthKiss-4": {"text": "I don't think t-that's in your retainer duties!!", "animation": "Embarrassed"},
|
||||||
|
"MouthKiss-5": {"text": "O-ooh..!", "animation": "Embarrassed"},
|
||||||
|
"MouthKiss-6": {"text": "You can't just..!!", "animation": "Embarrassed"},
|
||||||
|
"MouthKiss-7": {"text": "Kissing a princess?! You're not my...", "animation": "Embarrassed"},
|
||||||
|
"MouthKiss-8": {"text": "I...um...", "animation": "Glance"},
|
||||||
|
"MouthKiss-9": {"text": "...d-do that again.", "animation": "Glance"},
|
||||||
|
"MouthKiss-10": {"text": "A kiss..?!", "animation": "Embarrassed"},
|
||||||
|
"BellyKiss-1": {"text": "Why are you..?!", "animation": "Embarrassed"},
|
||||||
|
"BellyKiss-2": {"text": "Why are you...kissing my..?!", "animation": "Embarrassed"},
|
||||||
|
"BellyKiss-3": {"text": "A-are you trying to give me raspberries?!", "animation": "Shocked"},
|
||||||
|
"BellyKiss-4": {"text": "Q-quit it, that tickles..!", "animation": "Embarrassed"},
|
||||||
|
"BellyKiss-5": {"text": "S-stop that..!", "animation": "Embarrassed"},
|
||||||
|
"HeadRub-1": {"text": "G-gentle with the bangs, please!!", "animation": "HeadRub"},
|
||||||
|
"HeadRub-2": {"text": "You can't just p-pet me like an a-animal!!", "animation": "HeadRub"},
|
||||||
|
"HeadRub-3": {"text": "M-maybe head pats aren't so bad...", "animation": "HeadRub"},
|
||||||
|
"HeadRub-4": {"animation": "HeadRub"},
|
||||||
|
"HeadRub-5": {"text": "I could get used to these little head pats...", "animation": "HeadRub"},
|
||||||
|
"BellyRub-1": {"text": "Eep! Be gentle with my tummy!!", "animation": "BellyRub"},
|
||||||
|
"BellyRub-2": {"text": "T-that feels nice on my tummy...", "animation": "BellyRub"},
|
||||||
|
"BellyRub-3": {"text": "...", "animation": "BellyRub"},
|
||||||
|
"BellyRub-4": {"text": "Oh...that does help the pressure a bit!!", "animation": "BellyRub"},
|
||||||
|
"BellyRub-5": {"text": "Just don't jiggle me too much, okay?!", "animation": "BellyRub"},
|
||||||
|
"BellySmack-1": {"text": "Eek!! Hey! Don't just smack a princess's tummy!!", "animation": "Shocked"},
|
||||||
|
"BellySmack-2": {"text": "Dummy!! My belly is not a drum!!", "animation": "Grumpy"},
|
||||||
|
"BellySmack-3": {"text": "Nnngh...h-hey, be careful with that!!", "animation": "Concerned"},
|
||||||
|
"BellySmack-4": {"text": "You're bold, aren't you?!", "animation": "Grumpy"},
|
||||||
|
"BellySmack-5": {"text": "If you leave a print on me, it's off with your head!!", "animation": "Grumpy"},
|
||||||
|
"HeadRubFull-1": {"text": "G-gentle with the bangs, please!!", "animation": "HeadRubFull"},
|
||||||
|
"HeadRubFull-2": {"text": "You can't just p-pet me like an a-animal!!", "animation": "HeadRubFull"},
|
||||||
|
"HeadRubFull-3": {"text": "M-maybe head pats aren't so bad...", "animation": "HeadRubFull"},
|
||||||
|
"HeadRubFull-4": {"animation": "HeadRubFull"},
|
||||||
|
"HeadRubFull-5": {"text": "I could get used to these little head pats...", "animation": "HeadRubFull"},
|
||||||
|
"BellyRubFull-1": {"text": "Eep! Be gentle with my tummy!!", "animation": "BellyRubFull"},
|
||||||
|
"BellyRubFull-2": {"text": "T-that feels nice on my tummy...", "animation": "BellyRubFull"},
|
||||||
|
"BellyRubFull-3": {"text": "...", "animation": "BellyRubFull"},
|
||||||
|
"BellyRubFull-4": {"text": "Oh...that does help the pressure a bit!!", "animation": "BellyRubFull"},
|
||||||
|
"BellyRubFull-5": {"text": "Just don't jiggle me too much, okay?!", "animation": "BellyRubFull"}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,76 +1,59 @@
|
|||||||
<AnimatedActor>
|
{
|
||||||
<Info CreatedBy="robot" CreatedOn="07/06/2026 09:29:37 PM" Fps="30" Version="0"/>
|
"anm2ed": 1,
|
||||||
<Content>
|
"info": {"createdOn": "2026-07-06T21:29:37"},
|
||||||
<Spritesheets>
|
"spritesheets": [
|
||||||
<Spritesheet Id="0" Path="area.png"/>
|
{
|
||||||
</Spritesheets>
|
"path": "area.png",
|
||||||
<Layers>
|
"regions": [
|
||||||
<Layer Id="0" Name="BG" SpritesheetId="0"/>
|
{
|
||||||
</Layers>
|
"name": "Main",
|
||||||
<Nulls>
|
"size": [1200, 900],
|
||||||
<Null Id="0" Name="Bounds" ShowRect="true"/>
|
"pivot": [600, 750]
|
||||||
<Null Id="1" Name="ItemSpawn" ShowRect="true"/>
|
}
|
||||||
</Nulls>
|
]
|
||||||
<Events/>
|
}
|
||||||
</Content>
|
],
|
||||||
<Animations DefaultAnimation="Default">
|
"layers": [
|
||||||
<Animation Name="Default" FrameNum="1" Loop="true">
|
{"name": "BG"}
|
||||||
<RootAnimation>
|
],
|
||||||
<Frame XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
"nulls": [
|
||||||
</RootAnimation>
|
{"name": "Bounds", "showRect": true},
|
||||||
<LayerAnimations>
|
{"name": "ItemSpawn", "showRect": true}
|
||||||
<LayerAnimation LayerId="0" Visible="true">
|
],
|
||||||
<Frame XPivot="600" YPivot="750" XCrop="0" YCrop="0" Width="1200" Height="900" XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
"defaultAnimation": "Default",
|
||||||
</LayerAnimation>
|
"animations": {
|
||||||
</LayerAnimations>
|
"Default": {
|
||||||
<NullAnimations>
|
"root": [
|
||||||
<NullAnimation NullId="0" Visible="true">
|
{}
|
||||||
<Frame XPosition="0" YPosition="-300" Delay="1" Visible="true" XScale="1000" YScale="800" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
],
|
||||||
</NullAnimation>
|
"layers": [
|
||||||
<NullAnimation NullId="1" Visible="true">
|
{
|
||||||
<Frame XPosition="-10" YPosition="-637" Delay="1" Visible="true" XScale="733" YScale="120" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
"layer": 0,
|
||||||
</NullAnimation>
|
"frames": [
|
||||||
</NullAnimations>
|
{"region": 0}
|
||||||
<Triggers/>
|
]
|
||||||
</Animation>
|
}
|
||||||
</Animations>
|
],
|
||||||
<SourceDocument>
|
"nulls": [
|
||||||
<Info CreatedBy="robot" CreatedOn="07/06/2026 09:29:37 PM" Fps="30" Version="0"/>
|
{
|
||||||
<Content>
|
"null": 0,
|
||||||
<Spritesheets>
|
"frames": [
|
||||||
<Spritesheet Id="0" Path="area.png">
|
{
|
||||||
<Region Id="0" Name="Main" XCrop="0" YCrop="0" Width="1200" Height="900" XPivot="600" YPivot="750"/>
|
"pos": [0, -300],
|
||||||
</Spritesheet>
|
"scale": [1000, 800]
|
||||||
</Spritesheets>
|
}
|
||||||
<Layers>
|
]
|
||||||
<Layer Id="0" Name="BG" SpritesheetId="0"/>
|
},
|
||||||
</Layers>
|
{
|
||||||
<Nulls>
|
"null": 1,
|
||||||
<Null Id="0" Name="Bounds" ShowRect="true"/>
|
"frames": [
|
||||||
<Null Id="1" Name="ItemSpawn" ShowRect="true"/>
|
{
|
||||||
</Nulls>
|
"pos": [-10, -637],
|
||||||
<Events/>
|
"scale": [733, 120]
|
||||||
</Content>
|
}
|
||||||
<Animations DefaultAnimation="Default">
|
]
|
||||||
<Animation Name="Default" FrameNum="1" Loop="true">
|
}
|
||||||
<RootAnimation>
|
]
|
||||||
<Frame XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
}
|
||||||
</RootAnimation>
|
}
|
||||||
<LayerAnimations>
|
}
|
||||||
<LayerAnimation LayerId="0" Visible="true">
|
|
||||||
<Frame RegionId="0" XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
|
||||||
</LayerAnimation>
|
|
||||||
</LayerAnimations>
|
|
||||||
<NullAnimations>
|
|
||||||
<NullAnimation NullId="0" Visible="true">
|
|
||||||
<Frame XPosition="0" YPosition="-300" Delay="1" Visible="true" XScale="1000" YScale="800" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
|
||||||
</NullAnimation>
|
|
||||||
<NullAnimation NullId="1" Visible="true">
|
|
||||||
<Frame XPosition="-10" YPosition="-637" Delay="1" Visible="true" XScale="733" YScale="120" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
|
||||||
</NullAnimation>
|
|
||||||
</NullAnimations>
|
|
||||||
<Triggers/>
|
|
||||||
</Animation>
|
|
||||||
</Animations>
|
|
||||||
</SourceDocument>
|
|
||||||
</AnimatedActor>
|
|
||||||
+67798
-158211
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,27 +1,31 @@
|
|||||||
<AnimatedActor>
|
{
|
||||||
<Info CreatedBy="robot" CreatedOn="12/06/2025 11:49:27 PM" Fps="30" Version="0"/>
|
"anm2ed": 1,
|
||||||
<Content>
|
"info": {"createdOn": "2025-12-06T23:49:27"},
|
||||||
<Spritesheets>
|
"spritesheets": [
|
||||||
<Spritesheet Id="0" Path="item_001_peep.png"/>
|
{"path": "item_001_peep.png"}
|
||||||
</Spritesheets>
|
],
|
||||||
<Layers>
|
"layers": [
|
||||||
<Layer Id="0" Name="Main" SpritesheetId="0"/>
|
{"name": "Main"}
|
||||||
</Layers>
|
],
|
||||||
<Nulls/>
|
"defaultAnimation": "Default",
|
||||||
<Events/>
|
"animations": {
|
||||||
</Content>
|
"Default": {
|
||||||
<Animations DefaultAnimation="Default">
|
"root": [
|
||||||
<Animation Name="Default" FrameNum="1" Loop="true">
|
{
|
||||||
<RootAnimation>
|
"scale": [125, 125]
|
||||||
<Frame XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="125" YScale="125" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
}
|
||||||
</RootAnimation>
|
],
|
||||||
<LayerAnimations>
|
"layers": [
|
||||||
<LayerAnimation LayerId="0" Visible="true">
|
{
|
||||||
<Frame XPosition="0" YPosition="0" XPivot="24" YPivot="24" XCrop="0" YCrop="0" Width="48" Height="48" XScale="100" YScale="100" Delay="1" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
"layer": 0,
|
||||||
</LayerAnimation>
|
"frames": [
|
||||||
</LayerAnimations>
|
{
|
||||||
<NullAnimations/>
|
"size": [48, 48],
|
||||||
<Triggers/>
|
"pivot": [24, 24]
|
||||||
</Animation>
|
}
|
||||||
</Animations>
|
]
|
||||||
</AnimatedActor>
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +1,31 @@
|
|||||||
<AnimatedActor>
|
{
|
||||||
<Info CreatedBy="robot" CreatedOn="12/06/2025 11:49:27 PM" Fps="30" Version="0"/>
|
"anm2ed": 1,
|
||||||
<Content>
|
"info": {"createdOn": "2025-12-06T23:49:27"},
|
||||||
<Spritesheets>
|
"spritesheets": [
|
||||||
<Spritesheet Id="0" Path="item_015_pb_jar.png"/>
|
{"path": "item_015_pb_jar.png"}
|
||||||
</Spritesheets>
|
],
|
||||||
<Layers>
|
"layers": [
|
||||||
<Layer Id="0" Name="Main" SpritesheetId="0"/>
|
{"name": "Main"}
|
||||||
</Layers>
|
],
|
||||||
<Nulls/>
|
"defaultAnimation": "Default",
|
||||||
<Events/>
|
"animations": {
|
||||||
</Content>
|
"Default": {
|
||||||
<Animations DefaultAnimation="Default">
|
"root": [
|
||||||
<Animation Name="Default" FrameNum="1" Loop="true">
|
{
|
||||||
<RootAnimation>
|
"scale": [125, 125]
|
||||||
<Frame XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="125" YScale="125" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
}
|
||||||
</RootAnimation>
|
],
|
||||||
<LayerAnimations>
|
"layers": [
|
||||||
<LayerAnimation LayerId="0" Visible="true">
|
{
|
||||||
<Frame XPosition="0" YPosition="0" XPivot="32" YPivot="32" XCrop="0" YCrop="0" Width="64" Height="64" XScale="100" YScale="100" Delay="1" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
"layer": 0,
|
||||||
</LayerAnimation>
|
"frames": [
|
||||||
</LayerAnimations>
|
{
|
||||||
<NullAnimations/>
|
"size": [64, 64],
|
||||||
<Triggers/>
|
"pivot": [32, 32]
|
||||||
</Animation>
|
}
|
||||||
</Animations>
|
]
|
||||||
</AnimatedActor>
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,48 @@
|
|||||||
<AnimatedActor>
|
{
|
||||||
<Info CreatedBy="robot" CreatedOn="" Fps="30" Version="0"/>
|
"anm2ed": 1,
|
||||||
<Content>
|
"info": {},
|
||||||
<Spritesheets>
|
"spritesheets": [
|
||||||
<Spritesheet Id="0" Path="circle.png">
|
{
|
||||||
<Region Id="0" Name="Main" XCrop="0" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"path": "circle.png",
|
||||||
</Spritesheet>
|
"regions": [
|
||||||
</Spritesheets>
|
{
|
||||||
<Layers>
|
"name": "Main",
|
||||||
<Layer Id="0" Name="Main" SpritesheetId="0"/>
|
"size": [128, 128],
|
||||||
</Layers>
|
"pivot": [64, 64],
|
||||||
<Nulls/>
|
"origin": "center"
|
||||||
<Events/>
|
}
|
||||||
</Content>
|
]
|
||||||
<Animations DefaultAnimation="Spawn">
|
}
|
||||||
<Animation Name="Spawn" FrameNum="11" Loop="false">
|
],
|
||||||
<RootAnimation>
|
"layers": [
|
||||||
<Frame XPosition="0" YPosition="0" Delay="11" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
{"name": "Main"}
|
||||||
</RootAnimation>
|
],
|
||||||
<LayerAnimations>
|
"defaultAnimation": "Spawn",
|
||||||
<LayerAnimation LayerId="0" Visible="true">
|
"animations": {
|
||||||
<Frame RegionId="0" XPosition="0" YPosition="0" XScale="10" YScale="10" Delay="10" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"Spawn": {
|
||||||
<Frame RegionId="0" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="1" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="0" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"length": 11,
|
||||||
</LayerAnimation>
|
"loop": false,
|
||||||
</LayerAnimations>
|
"root": [
|
||||||
<NullAnimations/>
|
{"delay": 11}
|
||||||
<Triggers/>
|
],
|
||||||
</Animation>
|
"layers": [
|
||||||
</Animations>
|
{
|
||||||
</AnimatedActor>
|
"layer": 0,
|
||||||
|
"frames": [
|
||||||
|
{
|
||||||
|
"region": 0,
|
||||||
|
"scale": [10, 10],
|
||||||
|
"delay": 10,
|
||||||
|
"interpolation": "linear"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"region": 0,
|
||||||
|
"tint": [255, 255, 255, 0],
|
||||||
|
"interpolation": "linear"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,44 +1,99 @@
|
|||||||
<AnimatedActor>
|
{
|
||||||
<Info CreatedBy="robot" CreatedOn="" Fps="30" Version="0"/>
|
"anm2ed": 1,
|
||||||
<Content>
|
"info": {},
|
||||||
<Spritesheets>
|
"spritesheets": [
|
||||||
<Spritesheet Id="0" Path="puff.png">
|
{
|
||||||
<Region Id="0" Name="1" XCrop="0" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"path": "puff.png",
|
||||||
<Region Id="1" Name="2" XCrop="128" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"regions": [
|
||||||
<Region Id="2" Name="3" XCrop="256" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
{
|
||||||
<Region Id="3" Name="4" XCrop="384" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"name": "1",
|
||||||
<Region Id="4" Name="5" XCrop="512" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"size": [128, 128],
|
||||||
<Region Id="5" Name="6" XCrop="640" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"pivot": [64, 64],
|
||||||
<Region Id="6" Name="7" XCrop="768" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"origin": "center"
|
||||||
<Region Id="7" Name="8" XCrop="896" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
},
|
||||||
</Spritesheet>
|
{
|
||||||
</Spritesheets>
|
"name": "2",
|
||||||
<Layers>
|
"crop": [128, 0],
|
||||||
<Layer Id="0" Name="Main" SpritesheetId="0"/>
|
"size": [128, 128],
|
||||||
</Layers>
|
"pivot": [64, 64],
|
||||||
<Nulls/>
|
"origin": "center"
|
||||||
<Events/>
|
},
|
||||||
</Content>
|
{
|
||||||
<Animations DefaultAnimation="Main">
|
"name": "3",
|
||||||
<Animation Name="Main" FrameNum="16" Loop="false">
|
"crop": [256, 0],
|
||||||
<RootAnimation>
|
"size": [128, 128],
|
||||||
<Frame XPosition="0" YPosition="0" Delay="15" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"pivot": [64, 64],
|
||||||
<Frame XPosition="0" YPosition="-5" Delay="1" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="32" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"origin": "center"
|
||||||
</RootAnimation>
|
},
|
||||||
<LayerAnimations>
|
{
|
||||||
<LayerAnimation LayerId="0" Visible="true">
|
"name": "4",
|
||||||
<Frame RegionId="0" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"crop": [384, 0],
|
||||||
<Frame RegionId="1" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"size": [128, 128],
|
||||||
<Frame RegionId="2" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"pivot": [64, 64],
|
||||||
<Frame RegionId="3" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"origin": "center"
|
||||||
<Frame RegionId="4" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
},
|
||||||
<Frame RegionId="5" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
{
|
||||||
<Frame RegionId="6" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"name": "5",
|
||||||
<Frame RegionId="7" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"crop": [512, 0],
|
||||||
</LayerAnimation>
|
"size": [128, 128],
|
||||||
</LayerAnimations>
|
"pivot": [64, 64],
|
||||||
<NullAnimations/>
|
"origin": "center"
|
||||||
<Triggers/>
|
},
|
||||||
</Animation>
|
{
|
||||||
</Animations>
|
"name": "6",
|
||||||
</AnimatedActor>
|
"crop": [640, 0],
|
||||||
|
"size": [128, 128],
|
||||||
|
"pivot": [64, 64],
|
||||||
|
"origin": "center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "7",
|
||||||
|
"crop": [768, 0],
|
||||||
|
"size": [128, 128],
|
||||||
|
"pivot": [64, 64],
|
||||||
|
"origin": "center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "8",
|
||||||
|
"crop": [896, 0],
|
||||||
|
"size": [128, 128],
|
||||||
|
"pivot": [64, 64],
|
||||||
|
"origin": "center"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"layers": [
|
||||||
|
{"name": "Main"}
|
||||||
|
],
|
||||||
|
"defaultAnimation": "Main",
|
||||||
|
"animations": {
|
||||||
|
"Main": {
|
||||||
|
"length": 16,
|
||||||
|
"loop": false,
|
||||||
|
"root": [
|
||||||
|
{"delay": 15, "interpolation": "linear"},
|
||||||
|
{
|
||||||
|
"pos": [0, -5],
|
||||||
|
"tint": [255, 255, 255, 32],
|
||||||
|
"interpolation": "linear"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"layers": [
|
||||||
|
{
|
||||||
|
"layer": 0,
|
||||||
|
"frames": [
|
||||||
|
{"region": 0, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 1, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 2, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 3, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 4, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 5, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 6, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 7, "delay": 2, "interpolation": "linear"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
{
|
||||||
|
"anm2": "gfx/items/item.anm2",
|
||||||
|
"spritesheet": 0,
|
||||||
|
"spawnDelay": 600,
|
||||||
|
"spawnDelayMultiplier": 0.75,
|
||||||
|
"spawnLimit": 5,
|
||||||
|
"sounds": {
|
||||||
|
"bounce": ["sfx/bounce1.ogg", "sfx/bounce2.ogg", "sfx/bounce3.ogg"],
|
||||||
|
"dispose": ["sfx/dispose.ogg"],
|
||||||
|
"summon": ["sfx/summon.ogg"]
|
||||||
|
},
|
||||||
|
"particles": {
|
||||||
|
"dispose": ["Puff"],
|
||||||
|
"summon": ["Puff"]
|
||||||
|
},
|
||||||
|
"categories": {"Food": "edible", "Tool": "tool"},
|
||||||
|
"rarities": {
|
||||||
|
"Common": {"weight": 1.0, "sound": "sfx/common.ogg"},
|
||||||
|
"Uncommon": {"weight": 0.5, "sound": "sfx/uncommon.ogg"},
|
||||||
|
"Rare": {"weight": 0.25, "sound": "sfx/rare.ogg"}
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "Peep",
|
||||||
|
"description": "A pretty pink marshmallow confection sourced from the fields of Princess Peep's kingdom.\nTasty and filling.\nIt seems to stare at you...",
|
||||||
|
"category": "Food",
|
||||||
|
"rarity": "Common",
|
||||||
|
"flavor": "Peep",
|
||||||
|
"texture": "gfx/items/item_001_peep.png",
|
||||||
|
"color": [0.98, 0.506, 0.612],
|
||||||
|
"calories": 250.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Peeep",
|
||||||
|
"description": "A mutated marshmallow mound found irregularly growing in quarries within Princess Peep's kingdom.\nDelectable and will stuff you.\nIt looks deep within you...",
|
||||||
|
"category": "Food",
|
||||||
|
"rarity": "Uncommon",
|
||||||
|
"flavor": "Peep",
|
||||||
|
"texture": "gfx/items/item_002_peeep.png",
|
||||||
|
"anm2": "gfx/items/item_big.anm2",
|
||||||
|
"color": [0.98, 0.506, 0.612],
|
||||||
|
"calories": 650.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Peeeep",
|
||||||
|
"description": "An abominable amalgamation of a confectionary sourced from deepest reaches of Princess Peep's kingdom.\nUtterly scrumptious and overwhelming.\nIt peeps.",
|
||||||
|
"category": "Food",
|
||||||
|
"rarity": "Rare",
|
||||||
|
"flavor": "Peep",
|
||||||
|
"texture": "gfx/items/item_003_peeeep.png",
|
||||||
|
"anm2": "gfx/items/item_big.anm2",
|
||||||
|
"color": [0.98, 0.506, 0.612],
|
||||||
|
"calories": 1400.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mini Egg",
|
||||||
|
"description": "A curious, tasty egg hailing from Princess Mini's kingdom that one can just pop into their mouth with ease.\n(What does 'Mini' taste like..?)",
|
||||||
|
"category": "Food",
|
||||||
|
"rarity": "Common",
|
||||||
|
"flavor": "Mini",
|
||||||
|
"texture": "gfx/items/item_004_mini_egg.png",
|
||||||
|
"color": [0.188, 0.651, 0.424],
|
||||||
|
"calories": 75.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mini Bnuuy",
|
||||||
|
"description": "Princess Mini's favorite fluffy friend, in tiny, confectionary form! Try the ears first!",
|
||||||
|
"category": "Food",
|
||||||
|
"rarity": "Uncommon",
|
||||||
|
"flavor": "Mini",
|
||||||
|
"texture": "gfx/items/item_005_mini_bnuuy.png",
|
||||||
|
"color": [0.188, 0.651, 0.424],
|
||||||
|
"calories": 175.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mini Basket",
|
||||||
|
"description": "A smorgasbord of egg-shaped confectionary delights, all wrapped up in a cozy little basket!\nStuff 'em all into your mouth at once!\n(They won't hatch, right?!)",
|
||||||
|
"category": "Food",
|
||||||
|
"rarity": "Rare",
|
||||||
|
"flavor": "Mini",
|
||||||
|
"texture": "gfx/items/item_006_mini_basket.png",
|
||||||
|
"color": [0.188, 0.651, 0.424],
|
||||||
|
"calories": 350.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Creme Cupcake",
|
||||||
|
"description": "A splendid treat from the bakeries of Princess Creme's kingdom.\nDon't ask where the icing comes from.\nTickles the tummy a little.",
|
||||||
|
"category": "Food",
|
||||||
|
"rarity": "Common",
|
||||||
|
"flavor": "Creme",
|
||||||
|
"texture": "gfx/items/item_007_creme_cupcake.png",
|
||||||
|
"color": [0.808, 0.776, 0.302],
|
||||||
|
"calories": 100.0,
|
||||||
|
"digestionBonus": 0.075
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cool Whipped Cake",
|
||||||
|
"description": "A slice of an easily-pronouncable delectable dessert, commonly served in Princess Creme's kingdom.\nCan make a tummy grumble.",
|
||||||
|
"category": "Food",
|
||||||
|
"rarity": "Uncommon",
|
||||||
|
"flavor": "Creme",
|
||||||
|
"texture": "gfx/items/item_008_cool_whipped_cake.png",
|
||||||
|
"anm2": "gfx/items/item_big.anm2",
|
||||||
|
"color": [0.808, 0.776, 0.302],
|
||||||
|
"calories": 250.0,
|
||||||
|
"digestionBonus": 0.15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Triple Scoop",
|
||||||
|
"description": "\"TWO SCOOPS?!\"\nNo, three!\nA bountiful, delightfully prepared treat that is a fantastic delicacy in Princess Creme's kingdom.\nYou aren't lactose intolerant...RIGHT?!",
|
||||||
|
"category": "Food",
|
||||||
|
"rarity": "Rare",
|
||||||
|
"flavor": "Creme",
|
||||||
|
"texture": "gfx/items/item_009_triple_scoop.png",
|
||||||
|
"anm2": "gfx/items/item_big.anm2",
|
||||||
|
"color": [0.808, 0.776, 0.302],
|
||||||
|
"calories": 500.0,
|
||||||
|
"digestionBonus": 0.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Choco Kiss",
|
||||||
|
"description": "A teardrop-shaped kiss of chocolately goodness, that'll help expand one's stomach.\nThe ribbon bit is edible, don't worry.\nSomehow, it can make a bell-like chime...",
|
||||||
|
"category": "Food",
|
||||||
|
"rarity": "Common",
|
||||||
|
"flavor": "Chocolate",
|
||||||
|
"texture": "gfx/items/item_010_choco_kiss.png",
|
||||||
|
"color": [0.282, 0.141, 0],
|
||||||
|
"calories": 100.0,
|
||||||
|
"capacityBonus": 15.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Choco Bar",
|
||||||
|
"description": "A classic segmented snack in a wrapper.\nIt's made with real cocoa, so chocolate snobs won't annoy you.\nCan help push one's stomach capacity.",
|
||||||
|
"category": "Food",
|
||||||
|
"rarity": "Uncommon",
|
||||||
|
"flavor": "Chocolate",
|
||||||
|
"texture": "gfx/items/item_011_choco_bar.png",
|
||||||
|
"color": [0.282, 0.141, 0],
|
||||||
|
"calories": 250.0,
|
||||||
|
"capacityBonus": 35.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Choco Taco",
|
||||||
|
"description": "An oozing, gooey treat rife with chocolately calories.\nA traditional homeland favorite in Princess Chocolate's kingdom.\nFolklore tells of denizens eating these to bare mighty stomachs.",
|
||||||
|
"category": "Food",
|
||||||
|
"rarity": "Rare",
|
||||||
|
"flavor": "Chocolate",
|
||||||
|
"texture": "gfx/items/item_012_choco_taco.png",
|
||||||
|
"color": [0.282, 0.141, 0],
|
||||||
|
"calories": 500.0,
|
||||||
|
"capacityBonus": 70.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PB Cup",
|
||||||
|
"description": "A petite, peanut-butter loaded treat, a favorite from Princess Peanut Butter's kingdom.\nGives you a quick peanut butter kick.\nMake sure to not eat the wrapper!",
|
||||||
|
"category": "Food",
|
||||||
|
"rarity": "Common",
|
||||||
|
"flavor": "Peanut Butter",
|
||||||
|
"texture": "gfx/items/item_013_pb_cup.png",
|
||||||
|
"color": [0.847, 0.686, 0.055],
|
||||||
|
"calories": 100.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PB Puffs",
|
||||||
|
"description": "\"PB Puffs, PB Puffs! Peanut butter chocolate flavor!\"\nA glorious union of flavor, emblematic of the tight bond between the kingdoms of Princess Peanut Butter and Chocolate.\nPerfect for an energizing breakfast.",
|
||||||
|
"category": "Food",
|
||||||
|
"rarity": "Uncommon",
|
||||||
|
"flavor": "Peanut Butter",
|
||||||
|
"texture": "gfx/items/item_014_pb_puffs.png",
|
||||||
|
"anm2": "gfx/items/item_big.anm2",
|
||||||
|
"color": [0.847, 0.686, 0.055],
|
||||||
|
"calories": 250.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PB Jar",
|
||||||
|
"description": "\"What the hell do you mean, a 'P*ter P*n peanut butter alert'?!\"\nIt's a whole jar of peanut butter, a prime export from Princess Peanut Butter's kingdom!\nPacked with powerful protein!\nTough to chew...",
|
||||||
|
"category": "Food",
|
||||||
|
"rarity": "Rare",
|
||||||
|
"flavor": "Peanut Butter",
|
||||||
|
"texture": "gfx/items/item_015_pb_jar.png",
|
||||||
|
"anm2": "gfx/items/item_big.anm2",
|
||||||
|
"color": [0.847, 0.686, 0.055],
|
||||||
|
"calories": 2000.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
<ItemSchema BaseSpawnDelayTicks="600" ItemSpawnLimit="5" SpawnDelayStageMultiplier="0.75" TextureRootPath="gfx" SoundRootPath="sfx" BaseAnm2="items/item.anm2">
|
|
||||||
<Actions>
|
|
||||||
<Bounce>
|
|
||||||
<Sound Path="bounce1.ogg" />
|
|
||||||
<Sound Path="bounce2.ogg" />
|
|
||||||
<Sound Path="bounce3.ogg" />
|
|
||||||
</Bounce>
|
|
||||||
<Dispose>
|
|
||||||
<Sound Path="dispose.ogg" />
|
|
||||||
<Particle Type="Puff" />
|
|
||||||
</Dispose>
|
|
||||||
<Summon>
|
|
||||||
<Sound Path="summon.ogg" />
|
|
||||||
<Particle Type="Puff" />
|
|
||||||
</Summon>
|
|
||||||
<Return>
|
|
||||||
<Sound Path="return.ogg" />
|
|
||||||
<Particle Type="Puff" />
|
|
||||||
</Return>
|
|
||||||
</Actions>
|
|
||||||
<Categories>
|
|
||||||
<Category Name="Invalid" />
|
|
||||||
<Category Name="Food" UseMode="Edible" />
|
|
||||||
<Category Name="Tool" UseMode="Tool" />
|
|
||||||
</Categories>
|
|
||||||
<Flavors>
|
|
||||||
<Flavor Name="None" />
|
|
||||||
<Flavor Name="Peep" />
|
|
||||||
<Flavor Name="Mini" />
|
|
||||||
<Flavor Name="Creme" />
|
|
||||||
<Flavor Name="Chocolate" />
|
|
||||||
<Flavor Name="Peanut Butter" />
|
|
||||||
</Flavors>
|
|
||||||
<Rarities>
|
|
||||||
<Rarity Name="Common" Weight="1.0" Sound="common.ogg" />
|
|
||||||
<Rarity Name="Uncommon" Weight="0.5" Sound="uncommon.ogg" />
|
|
||||||
<Rarity Name="Rare" Weight="0.25" Sound="rare.ogg" />
|
|
||||||
</Rarities>
|
|
||||||
<Items TextureRootPath="gfx/items" SpritesheetID="0">
|
|
||||||
<Item Name="Peep" Texture="item_001_peep.png" ColorR="0.980" ColorG="0.506" ColorB="0.612" Description="A pretty pink marshmallow confection sourced from the fields of Princess Peep's kingdom. Tasty and filling. It seems to stare at you..." Category="Food" Flavor="Peep" Rarity="Common" Calories="250.0" />
|
|
||||||
<Item Name="Peeep" Texture="item_002_peeep.png" Anm2="items/item_big.anm2" ColorR="0.980" ColorG="0.506" ColorB="0.612" Description="A mutated marshmallow mound found irregularly growing in quarries within Princess Peep's kingdom. Delectable and will stuff you. It looks deep within you..." Category="Food" Flavor="Peep" Rarity="Uncommon" Calories="650.0" />
|
|
||||||
<Item Name="Peeeep" Texture="item_003_peeeep.png" Anm2="items/item_big.anm2" ColorR="0.980" ColorG="0.506" ColorB="0.612" Description="An abominable amalgamation of a confectionary sourced from deepest reaches of Princess Peep's kingdom. Utterly scrumptious and overwhelming. It peeps." Category="Food" Flavor="Peep" Rarity="Rare" Calories="1400.0" />
|
|
||||||
<Item Name="Mini Egg" Texture="item_004_mini_egg.png" ColorR="0.188" ColorG="0.651" ColorB="0.424" Description="A curious, tasty egg hailing from Princess Mini's kingdom that one can just pop into their mouth with ease. (What does 'Mini' taste like..?)" Category="Food" Flavor="Mini" Rarity="Common" Calories="75.0" />
|
|
||||||
<Item Name="Mini Bnuuy" Texture="item_005_mini_bnuuy.png" ColorR="0.188" ColorG="0.651" ColorB="0.424" Description="Princess Mini's favorite fluffy friend, in tiny, confectionary form! Try the ears first!" Category="Food" Flavor="Mini" Rarity="Uncommon" Calories="175.0" />
|
|
||||||
<Item Name="Mini Basket" Texture="item_006_mini_basket.png" ColorR="0.188" ColorG="0.651" ColorB="0.424" Description="A smorgasbord of egg-shaped confectionary delights, all wrapped up in a cozy little basket! Stuff 'em all into your mouth at once! (They won't hatch, right?!)" Category="Food" Flavor="Mini" Rarity="Rare" Calories="350.0" />
|
|
||||||
<Item Name="Creme Cupcake" Texture="item_007_creme_cupcake.png" ColorR="0.808" ColorG="0.776" ColorB="0.302" Description="A splendid treat from the bakeries of Princess Creme's kingdom. Don't ask where the icing comes from. Tickles the tummy a little." Category="Food" Flavor="Creme" Rarity="Common" Calories="100.0" DigestionBonus="0.00125" />
|
|
||||||
<Item Name="Cool Whipped Cake" Texture="item_008_cool_whipped_cake.png" Anm2="items/item_big.anm2" ColorR="0.808" ColorG="0.776" ColorB="0.302" Description="A slice of an easily-pronouncable delectable dessert, commonly served in Princess Creme's kingdom. Can make a tummy grumble." Category="Food" Flavor="Creme" Rarity="Uncommon" Calories="250.0" DigestionBonus="0.0025" />
|
|
||||||
<Item Name="Triple Scoop" Texture="item_009_triple_scoop.png" Anm2="items/item_big.anm2" ColorR="0.808" ColorG="0.776" ColorB="0.302" Description=""TWO SCOOPS?!" No, three! A bountiful, delightfully prepared treat that is a fantastic delicacy in Princess Creme's kingdom. You aren't lactose intolerant...RIGHT?!" Category="Food" Flavor="Creme" Rarity="Rare" Calories="500.0" DigestionBonus="0.005" />
|
|
||||||
<Item Name="Choco Kiss" Texture="item_010_choco_kiss.png" ColorR="0.282" ColorG="0.141" ColorB="0.000" Description="A teardrop-shaped kiss of chocolately goodness, that'll help expand one's stomach. The ribbon bit is edible, don't worry. Somehow, it can make a bell-like chime..." Category="Food" Flavor="Chocolate" Rarity="Common" Calories="100.0" CapacityBonus="15.0" />
|
|
||||||
<Item Name="Choco Bar" Texture="item_011_choco_bar.png" ColorR="0.282" ColorG="0.141" ColorB="0.000" Description="A classic segmented snack in a wrapper. It's made with real cocoa, so chocolate snobs won't annoy you. Can help push one's stomach capacity." Category="Food" Flavor="Chocolate" Rarity="Uncommon" Calories="250.0" CapacityBonus="35.0" />
|
|
||||||
<Item Name="Choco Taco" Texture="item_012_choco_taco.png" ColorR="0.282" ColorG="0.141" ColorB="0.000" Description="An oozing, gooey treat rife with chocolately calories. A traditional homeland favorite in Princess Chocolate's kingdom. Folklore tells of denizens eating these to bare mighty stomachs." Category="Food" Flavor="Chocolate" Rarity="Rare" Calories="500.0" CapacityBonus="70.0" />
|
|
||||||
<Item Name="PB Cup" Texture="item_013_pb_cup.png" ColorR="0.847" ColorG="0.686" ColorB="0.055" Description="A petite, peanut-butter loaded treat, a favorite from Princess Peanut Butter's kingdom. Gives you a quick peanut butter kick. Make sure to not eat the wrapper!" Category="Food" Flavor="Peanut Butter" Rarity="Common" Calories="100.0" />
|
|
||||||
<Item Name="PB Puffs" Texture="item_014_pb_puffs.png" Anm2="items/item_big.anm2" ColorR="0.847" ColorG="0.686" ColorB="0.055" Description=""PB Puffs, PB Puffs! Peanut butter chocolate flavor!" A glorious union of flavor, emblematic of the tight bond between the kingdoms of Princess Peanut Butter and Chocolate. Perfect for an energizing breakfast." Category="Food" Flavor="Peanut Butter" Rarity="Uncommon" Calories="250.0" />
|
|
||||||
<Item Name="PB Jar" Texture="item_015_pb_jar.png" Anm2="items/item_big.anm2" ColorR="0.847" ColorG="0.686" ColorB="0.055" Description=""What the hell do you mean, a 'P*ter P*n peanut butter alert'?!" It's a whole jar of peanut butter, a prime export from Princess Peanut Butter's kingdom! Packed with powerful protein! Tough to chew..." Category="Food" Flavor="Peanut Butter" Rarity="Rare" Calories="2000.0" />
|
|
||||||
</Items>
|
|
||||||
</ItemSchema>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<Menu SoundRootPath="sfx" FontRootPath="font" Font="font.ttf" IconsFont="icons.ttf" ButtonRounding="10.0">
|
|
||||||
<Home GlyphFromIconFont="1" />
|
|
||||||
<Actions>
|
|
||||||
<Open>
|
|
||||||
<Sound Path="open.ogg" />
|
|
||||||
</Open>
|
|
||||||
<Close>
|
|
||||||
<Sound Path="close.ogg" />
|
|
||||||
</Close>
|
|
||||||
<Hover>
|
|
||||||
<Sound Path="hover1.ogg" />
|
|
||||||
<Sound Path="hover2.ogg" />
|
|
||||||
<Sound Path="hover3.ogg" />
|
|
||||||
</Hover>
|
|
||||||
<Select>
|
|
||||||
<Sound Path="select.ogg" />
|
|
||||||
</Select>
|
|
||||||
<CheatsActivated>
|
|
||||||
<Sound Path="cheatsActivated.ogg" />
|
|
||||||
</CheatsActivated>
|
|
||||||
</Actions>
|
|
||||||
</Menu>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<Particles Anm2RootPath="gfx/particle">
|
|
||||||
<Particle Type="Circle" Anm2="circle.anm2" Animation="Main" />
|
|
||||||
<Particle Type="Puff" Anm2="puff.anm2" Animation="Main" />
|
|
||||||
</Particles>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<Strings>
|
|
||||||
</Strings>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<Areas TextureRootPath="gfx/areas" BaseAnm2="area.anm2" WorldBoundsNull="Bounds" ItemSpawnNull="ItemSpawn">
|
|
||||||
<Area Texture="area.png" Gravity="0.95" Friction="0.75" AirResistance="0.975" />
|
|
||||||
</Areas>
|
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
{
|
||||||
|
"name": "Snivy",
|
||||||
|
"description": "A Grass Snake Pokémon with rather large ambitions.",
|
||||||
|
"credits": "Art, Animation, Dialogue: ShweetMagnet Character - Pokémon Sound Effects - Pokémon; ShweetMagnet",
|
||||||
|
"render": "gfx/preview/render.png",
|
||||||
|
"portrait": "gfx/preview/portrait.png",
|
||||||
|
"anm2": "gfx/character/snivy.anm2",
|
||||||
|
"color": [0.12, 0.515, 0.115],
|
||||||
|
"weight": 8.1,
|
||||||
|
"weightMax": 200,
|
||||||
|
"capacity": 300,
|
||||||
|
"capacityMin": 300,
|
||||||
|
"capacityMax": 10000,
|
||||||
|
"overstuffedMultiplier": 1.5,
|
||||||
|
"overstuffedDigestBonus": 0.25,
|
||||||
|
"caloriesToKilogram": 0.0005,
|
||||||
|
"digestionRate": 1.5,
|
||||||
|
"digestionRateMax": 4.5,
|
||||||
|
"digestionTime": 120,
|
||||||
|
"blinkChance": 0.5,
|
||||||
|
"gurgleChance": 0.0005,
|
||||||
|
"gurgleCapacityMultiplier": 10,
|
||||||
|
"dialoguePool": "Stage1",
|
||||||
|
"alternate": {"texture": "gfx/character/snivy_alt.png", "sound": "sfx/shiny.ogg", "chance": 0.001},
|
||||||
|
"stages": [
|
||||||
|
{"weight": 10, "dialoguePool": "Stage2", "animationSuffix": "Stage2"},
|
||||||
|
{"weight": 12.5, "dialoguePool": "Stage3", "animationSuffix": "Stage3"},
|
||||||
|
{"weight": 17.5, "dialoguePool": "Stage4", "animationSuffix": "Stage4"},
|
||||||
|
{"weight": 27.5, "dialoguePool": "Stage5", "animationSuffix": "Stage5"},
|
||||||
|
{"weight": 40, "dialoguePool": "Stage6", "animationSuffix": "Stage6"},
|
||||||
|
{"weight": 55, "dialoguePool": "Stage7", "animationSuffix": "Stage7"},
|
||||||
|
{"weight": 75, "dialoguePool": "Stage8", "animationSuffix": "Stage8"},
|
||||||
|
{"weight": 100, "dialoguePool": "Stage9", "animationSuffix": "Stage9"},
|
||||||
|
{"weight": 130, "dialoguePool": "Stage10", "animationSuffix": "Stage10"}
|
||||||
|
],
|
||||||
|
"animations": {"start": "Appear", "idle": "Neutral", "idleFull": "NeutralFull", "stageUp": "StageUp"},
|
||||||
|
"talk": {"source": "Talk", "destination": "Mouth"},
|
||||||
|
"blink": {"source": "Blink", "destination": "Head"},
|
||||||
|
"eatAreas": [
|
||||||
|
{"null": "Mouth", "animation": "Eat", "event": "Eat"}
|
||||||
|
],
|
||||||
|
"expandAreas": [
|
||||||
|
{"layer": "Torso", "null": "Belly", "scaleMax": 7.5},
|
||||||
|
{"layer": "Tail", "null": "Tail", "scaleMax": 7.5}
|
||||||
|
],
|
||||||
|
"sounds": {
|
||||||
|
"digest": ["sfx/digest1.ogg", "sfx/digest2.ogg", "sfx/digest3.ogg"],
|
||||||
|
"gurgle": ["sfx/gurgle1.ogg", "sfx/gurgle2.ogg", "sfx/gurgle3.ogg", "sfx/gurgle4.ogg", "sfx/gurgle5.ogg"]
|
||||||
|
},
|
||||||
|
"interactTypes": {
|
||||||
|
"Rub": {
|
||||||
|
"glyph": "2",
|
||||||
|
"isHold": true,
|
||||||
|
"cursorHover": "Hover",
|
||||||
|
"cursorActive": "Rub",
|
||||||
|
"sounds": ["sfx/rub.ogg"],
|
||||||
|
"particles": ["Circle"]
|
||||||
|
},
|
||||||
|
"Smack": {
|
||||||
|
"glyph": "3",
|
||||||
|
"cursorHover": "SmackHover",
|
||||||
|
"cursorActive": "Smack",
|
||||||
|
"dialogueCooldown": 40,
|
||||||
|
"scaleAmplitude": 5,
|
||||||
|
"scaleCycles": 3,
|
||||||
|
"sounds": ["sfx/smack1.ogg", "sfx/smack2.ogg", "sfx/smack3.ogg", "sfx/smack4.ogg", "sfx/smack5.ogg"],
|
||||||
|
"particles": ["Circle"]
|
||||||
|
},
|
||||||
|
"Kiss": {
|
||||||
|
"glyph": "4",
|
||||||
|
"cursorHover": "KissHover",
|
||||||
|
"cursorActive": "Kiss",
|
||||||
|
"sounds": ["sfx/kiss1.ogg", "sfx/kiss2.ogg", "sfx/kiss3.ogg", "sfx/kiss4.ogg", "sfx/kiss5.ogg"],
|
||||||
|
"particles": ["Circle"]
|
||||||
|
},
|
||||||
|
"Turn Around": {
|
||||||
|
"glyph": "5",
|
||||||
|
"sounds": ["sfx/turn.ogg"],
|
||||||
|
"particles": ["Circle"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interactAreas": [
|
||||||
|
{"type": "Rub", "null": "Head", "dialoguePool": "HeadRub", "dialoguePoolFull": "HeadRubFull"},
|
||||||
|
{"type": "Rub", "null": "HeadBack", "idleOnEnd": "NeutralBack", "dialoguePool": "HeadRubBack", "dialoguePoolFull": "HeadRubBackFull"},
|
||||||
|
{"type": "Rub", "null": "Belly", "cursorActive": "Grab", "dialoguePool": "BellyRub", "dialoguePoolFull": "BellyRubFull", "digestionHover": 0.1},
|
||||||
|
{"type": "Rub", "null": "Tail", "cursorActive": "Grab", "dialoguePool": "TailRub", "dialoguePoolFull": "TailRubFull"},
|
||||||
|
{"type": "Rub", "null": "TailBack", "idleOnEnd": "NeutralBack", "cursorActive": "Grab", "dialoguePool": "TailRubBack", "dialoguePoolFull": "TailRubBackFull"},
|
||||||
|
{"type": "Kiss", "null": "Mouth", "dialoguePool": "MouthKiss", "dialoguePoolFull": "MouthKiss"},
|
||||||
|
{"type": "Kiss", "null": "Belly", "dialoguePool": "BellyKiss", "dialoguePoolFull": "BellyKiss"},
|
||||||
|
{"type": "Kiss", "null": "Tail", "dialoguePool": "TailKiss", "dialoguePoolFull": "TailKiss"},
|
||||||
|
{"type": "Smack", "null": "Belly", "layer": "Torso", "dialoguePool": "BellySmack", "dialoguePoolFull": "BellySmack", "digestionClick": 0.25},
|
||||||
|
{"type": "Smack", "null": "Tail", "layer": "Tail", "dialoguePool": "TailSmack", "dialoguePoolFull": "TailSmack"},
|
||||||
|
{"type": "Smack", "null": "TailBack", "layer": "Tail", "idleOnEnd": "NeutralBack", "dialoguePool": "TailSmackBack", "dialoguePoolFull": "TailSmackBack"},
|
||||||
|
{"type": "Turn Around", "null": "FullBody", "animation": "NeutralBack", "idleOnEnd": "NeutralBack", "cursorHover": "TurnAround"},
|
||||||
|
{"type": "Turn Around", "null": "FullBodyBack", "animation": "Neutral", "idleOnEnd": "Neutral", "cursorHover": "TurnAroundBack"}
|
||||||
|
],
|
||||||
|
"areas": {
|
||||||
|
"anm2": "gfx/areas/area.anm2",
|
||||||
|
"boundsNull": "Bounds",
|
||||||
|
"spawnNull": "ItemSpawn",
|
||||||
|
"list": [
|
||||||
|
{"gravity": 0.95, "friction": 0.75, "airResistance": 0.975}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cursor": {
|
||||||
|
"anm2": "gfx/cursor/cursor.anm2",
|
||||||
|
"animations": {"idle": "Default", "click": "Click", "hover": "Hover", "grab": "Grab", "pan": "Pan", "zoom": "Zoom", "dispose": "Dispose"},
|
||||||
|
"sounds": {
|
||||||
|
"grab": ["sfx/grab.ogg"],
|
||||||
|
"release": ["sfx/release.ogg"],
|
||||||
|
"throw": ["sfx/throw.ogg", "sfx/throw2.ogg", "sfx/throw3.ogg"]
|
||||||
|
},
|
||||||
|
"particles": {
|
||||||
|
"grab": ["Circle"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"font": "font/font.ttf",
|
||||||
|
"iconsFont": "font/icons.ttf",
|
||||||
|
"rounding": 10.0,
|
||||||
|
"homeGlyph": "1",
|
||||||
|
"sounds": {
|
||||||
|
"open": ["sfx/open.ogg"],
|
||||||
|
"close": ["sfx/close.ogg"],
|
||||||
|
"hover": ["sfx/hover.ogg", "sfx/hover2.ogg", "sfx/hover3.ogg"],
|
||||||
|
"select": ["sfx/select.ogg"],
|
||||||
|
"cheats": ["sfx/cheatsActivated.ogg"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"particles": {
|
||||||
|
"Circle": {"anm2": "gfx/particle/circle.anm2", "animation": "Main"},
|
||||||
|
"Puff": {"anm2": "gfx/particle/puff.anm2", "animation": "Main"}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
<Character Name="Snivy" TextureRootPath="gfx" SoundRootPath="sfx" Render="preview/render.png" Portrait="preview/portrait.png" Anm2="character/snivy.anm2" Description="A Grass Snake Pokémon with rather large ambitions." Credits="Art, Animation, Dialogue: ShweetMagnet Character - Pokémon Sound Effects - Pokémon; ShweetMagnet" WeightKilograms="8.1000004" WeightMaxKilograms="200" CapacityCalories="300" CapacityMinCalories="300" CapacityMaxCalories="10000" CapacityOverstuffedMultiplier="1.5" CapacityIfOverStuffedOnDigestBonus="0.25" CaloriesToKilogram="0.00050000002" DigestionTimerMax="120" BlinkChance="0.5" GurgleChance="0.00050000002" GurgleChanceFromCapacityMultiplier="10" DialoguePool="Stage1" ColorR="0.12" ColorG="0.51499999" ColorB="0.115" DigestionRatePerSecond="1.5" DigestionRatePerSecondMax="4.5">
|
|
||||||
<AlternateSpritesheet Texture="character/snivy_alt.png" Sound="shiny.ogg" ChanceOnNewGame="0.001" />
|
|
||||||
<Stages>
|
|
||||||
<Stage ThresholdKilograms="10" DialoguePool="Stage2" AppendStringForAnimations="Stage2" />
|
|
||||||
<Stage ThresholdKilograms="12.5" DialoguePool="Stage3" AppendStringForAnimations="Stage3" />
|
|
||||||
<Stage ThresholdKilograms="17.5" DialoguePool="Stage4" AppendStringForAnimations="Stage4" />
|
|
||||||
<Stage ThresholdKilograms="27.5" DialoguePool="Stage5" AppendStringForAnimations="Stage5" />
|
|
||||||
<Stage ThresholdKilograms="40" DialoguePool="Stage6" AppendStringForAnimations="Stage6" />
|
|
||||||
<Stage ThresholdKilograms="55" DialoguePool="Stage7" AppendStringForAnimations="Stage7" />
|
|
||||||
<Stage ThresholdKilograms="75" DialoguePool="Stage8" AppendStringForAnimations="Stage8" />
|
|
||||||
<Stage ThresholdKilograms="100" DialoguePool="Stage9" AppendStringForAnimations="Stage9" />
|
|
||||||
<Stage ThresholdKilograms="130" DialoguePool="Stage10" AppendStringForAnimations="Stage10" />
|
|
||||||
</Stages>
|
|
||||||
<Animations>
|
|
||||||
<Start Animation="Appear" />
|
|
||||||
<Idle Animation="Neutral" />
|
|
||||||
<IdleFull Animation="NeutralFull" />
|
|
||||||
<StageUp Animation="StageUp" />
|
|
||||||
</Animations>
|
|
||||||
<AnimationOverrides>
|
|
||||||
<Talk LayerSource="Talk" LayerDestination="Mouth" />
|
|
||||||
<Blink LayerSource="Blink" LayerDestination="Head" />
|
|
||||||
</AnimationOverrides>
|
|
||||||
<EatAreas>
|
|
||||||
<EatArea Null="Mouth" Animation="Eat" EventToActivate="Eat" />
|
|
||||||
</EatAreas>
|
|
||||||
<Actions>
|
|
||||||
<Digest>
|
|
||||||
<Sound Path="digest1.ogg" />
|
|
||||||
<Sound Path="digest2.ogg" />
|
|
||||||
<Sound Path="digest3.ogg" />
|
|
||||||
</Digest>
|
|
||||||
<Gurgle>
|
|
||||||
<Sound Path="gurgle1.ogg" />
|
|
||||||
<Sound Path="gurgle2.ogg" />
|
|
||||||
<Sound Path="gurgle3.ogg" />
|
|
||||||
<Sound Path="gurgle4.ogg" />
|
|
||||||
<Sound Path="gurgle5.ogg" />
|
|
||||||
</Gurgle>
|
|
||||||
</Actions>
|
|
||||||
<ExpandAreas>
|
|
||||||
<ExpandArea Layer="Torso" Null="Belly" ScaleMultiplierMaximum="7.5" />
|
|
||||||
<ExpandArea Layer="Tail" Null="Tail" ScaleMultiplierMaximum="7.5" />
|
|
||||||
</ExpandAreas>
|
|
||||||
<InteractAreaTypes>
|
|
||||||
<InteractArea Type="Rub" GlyphFromIconFont="2" IsHold="true" AnimationCursorHover="Hover" AnimationCursorActive="Rub">
|
|
||||||
<Sound Path="rub.ogg" />
|
|
||||||
<Particle Type="Circle" />
|
|
||||||
</InteractArea>
|
|
||||||
<InteractArea Type="Smack" GlyphFromIconFont="3" AnimationCursorHover="SmackHover" AnimationCursorActive="Smack" TimeToActivateNewDialogueTicks="40" ScaleEffectAmplitude="5" ScaleEffectCycles="3">
|
|
||||||
<Sound Path="smack1.ogg" />
|
|
||||||
<Sound Path="smack2.ogg" />
|
|
||||||
<Sound Path="smack3.ogg" />
|
|
||||||
<Sound Path="smack4.ogg" />
|
|
||||||
<Sound Path="smack5.ogg" />
|
|
||||||
<Particle Type="Circle" />
|
|
||||||
</InteractArea>
|
|
||||||
<InteractArea Type="Kiss" GlyphFromIconFont="4" AnimationCursorHover="KissHover" AnimationCursorActive="Kiss">
|
|
||||||
<Sound Path="kiss1.ogg" />
|
|
||||||
<Sound Path="kiss2.ogg" />
|
|
||||||
<Sound Path="kiss3.ogg" />
|
|
||||||
<Sound Path="kiss4.ogg" />
|
|
||||||
<Sound Path="kiss5.ogg" />
|
|
||||||
<Particle Type="Circle" />
|
|
||||||
</InteractArea>
|
|
||||||
<InteractArea Type="Turn Around" GlyphFromIconFont="5">
|
|
||||||
<Particle Type="Circle" />
|
|
||||||
<Sound Path="turn.ogg" />
|
|
||||||
</InteractArea>
|
|
||||||
</InteractAreaTypes>
|
|
||||||
<InteractAreas>
|
|
||||||
<InteractArea Type="Rub" Null="Head" DialoguePool="HeadRub" DialoguePoolFull="HeadRubFull" />
|
|
||||||
<InteractArea Type="Rub" Null="HeadBack" DialoguePool="HeadRubBack" DialoguePoolFull="HeadRubBackFull" DefaultAnimationOnEnd="NeutralBack" />
|
|
||||||
<InteractArea Type="Rub" Null="Belly" AnimationCursorActive="Grab" DialoguePool="BellyRub" DigestionBonusOnHover="0.1" DialoguePoolFull="BellyRubFull" />
|
|
||||||
<InteractArea Type="Rub" Null="Tail" AnimationCursorActive="Grab" DialoguePool="TailRub" DialoguePoolFull="TailRubFull" />
|
|
||||||
<InteractArea Type="Rub" Null="TailBack" AnimationCursorActive="Grab" DialoguePool="TailRubBack" DialoguePoolFull="TailRubBackFull" DefaultAnimationOnEnd="NeutralBack" />
|
|
||||||
<InteractArea Type="Kiss" Null="Mouth" DialoguePool="MouthKiss" DialoguePoolFull="MouthKiss" />
|
|
||||||
<InteractArea Type="Kiss" Null="Belly" DialoguePool="BellyKiss" DialoguePoolFull="BellyKiss" />
|
|
||||||
<InteractArea Type="Kiss" Null="Tail" DialoguePool="TailKiss" DialoguePoolFull="TailKiss" />
|
|
||||||
<InteractArea Type="Smack" Null="Belly" DialoguePool="BellySmack" Layer="Torso" DigestionBonusOnClick="0.25" DialoguePoolFull="BellySmack" />
|
|
||||||
<InteractArea Type="Smack" Null="Tail" DialoguePool="TailSmack" Layer="Tail" DialoguePoolFull="TailSmack" />
|
|
||||||
<InteractArea Type="Smack" Null="TailBack" DialoguePool="TailSmackBack" Layer="Tail" DialoguePoolFull="TailSmackBack" DefaultAnimationOnEnd="NeutralBack" />
|
|
||||||
<InteractArea Type="Turn Around" Null="FullBody" AnimationCursorHover="TurnAround" Animation="NeutralBack" DefaultAnimationOnEnd="NeutralBack" />
|
|
||||||
<InteractArea Type="Turn Around" Null="FullBodyBack" AnimationCursorHover="TurnAroundBack" Animation="Neutral" DefaultAnimationOnEnd="Neutral" />
|
|
||||||
</InteractAreas>
|
|
||||||
</Character>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<Cursor TextureRootPath="gfx" SoundRootPath="sfx" Anm2="cursor/cursor.anm2">
|
|
||||||
<Actions>
|
|
||||||
<Idle Animation="Default" />
|
|
||||||
<Click Animation="Click" />
|
|
||||||
<Hover Animation="Hover" />
|
|
||||||
<Grab Animation="Grab">
|
|
||||||
<Sound Path="grab.ogg" />
|
|
||||||
<Particle Type="Circle" />
|
|
||||||
</Grab>
|
|
||||||
<Release>
|
|
||||||
<Sound Path="release.ogg" />
|
|
||||||
</Release>
|
|
||||||
<Pan Animation="Pan" />
|
|
||||||
<Zoom Animation="Zoom" />
|
|
||||||
<Dispose Animation="Dispose" />
|
|
||||||
<Throw>
|
|
||||||
<Sound Path="throw.ogg" />
|
|
||||||
<Sound Path="throw2.ogg" />
|
|
||||||
<Sound Path="throw3.ogg" />
|
|
||||||
</Throw>
|
|
||||||
</Actions>
|
|
||||||
</Cursor>
|
|
||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,76 +1,59 @@
|
|||||||
<AnimatedActor>
|
{
|
||||||
<Info CreatedBy="robot" CreatedOn="07/06/2026 09:29:37 PM" Fps="30" Version="0"/>
|
"anm2ed": 1,
|
||||||
<Content>
|
"info": {"createdOn": "2026-07-06T21:29:37"},
|
||||||
<Spritesheets>
|
"spritesheets": [
|
||||||
<Spritesheet Id="0" Path="area.png"/>
|
{
|
||||||
</Spritesheets>
|
"path": "area.png",
|
||||||
<Layers>
|
"regions": [
|
||||||
<Layer Id="0" Name="BG" SpritesheetId="0"/>
|
{
|
||||||
</Layers>
|
"name": "Main",
|
||||||
<Nulls>
|
"size": [700, 500],
|
||||||
<Null Id="0" Name="Bounds" ShowRect="true"/>
|
"pivot": [350, 400]
|
||||||
<Null Id="1" Name="ItemSpawn" ShowRect="true"/>
|
}
|
||||||
</Nulls>
|
]
|
||||||
<Events/>
|
}
|
||||||
</Content>
|
],
|
||||||
<Animations DefaultAnimation="Default">
|
"layers": [
|
||||||
<Animation Name="Default" FrameNum="1" Loop="true">
|
{"name": "BG"}
|
||||||
<RootAnimation>
|
],
|
||||||
<Frame XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
"nulls": [
|
||||||
</RootAnimation>
|
{"name": "Bounds", "showRect": true},
|
||||||
<LayerAnimations>
|
{"name": "ItemSpawn", "showRect": true}
|
||||||
<LayerAnimation LayerId="0" Visible="true">
|
],
|
||||||
<Frame XPivot="350" YPivot="400" XCrop="0" YCrop="0" Width="700" Height="500" XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
"defaultAnimation": "Default",
|
||||||
</LayerAnimation>
|
"animations": {
|
||||||
</LayerAnimations>
|
"Default": {
|
||||||
<NullAnimations>
|
"root": [
|
||||||
<NullAnimation NullId="0" Visible="true">
|
{}
|
||||||
<Frame XPosition="1" YPosition="-172" Delay="1" Visible="true" XScale="640" YScale="400" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
],
|
||||||
</NullAnimation>
|
"layers": [
|
||||||
<NullAnimation NullId="1" Visible="true">
|
{
|
||||||
<Frame XPosition="0" YPosition="-245" Delay="1" Visible="true" XScale="440" YScale="145" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
"layer": 0,
|
||||||
</NullAnimation>
|
"frames": [
|
||||||
</NullAnimations>
|
{"region": 0}
|
||||||
<Triggers/>
|
]
|
||||||
</Animation>
|
}
|
||||||
</Animations>
|
],
|
||||||
<SourceDocument>
|
"nulls": [
|
||||||
<Info CreatedBy="robot" CreatedOn="07/06/2026 09:29:37 PM" Fps="30" Version="0"/>
|
{
|
||||||
<Content>
|
"null": 0,
|
||||||
<Spritesheets>
|
"frames": [
|
||||||
<Spritesheet Id="0" Path="area.png">
|
{
|
||||||
<Region Id="0" Name="Main" XCrop="0" YCrop="0" Width="700" Height="500" XPivot="350" YPivot="400"/>
|
"pos": [1, -172],
|
||||||
</Spritesheet>
|
"scale": [640, 400]
|
||||||
</Spritesheets>
|
}
|
||||||
<Layers>
|
]
|
||||||
<Layer Id="0" Name="BG" SpritesheetId="0"/>
|
},
|
||||||
</Layers>
|
{
|
||||||
<Nulls>
|
"null": 1,
|
||||||
<Null Id="0" Name="Bounds" ShowRect="true"/>
|
"frames": [
|
||||||
<Null Id="1" Name="ItemSpawn" ShowRect="true"/>
|
{
|
||||||
</Nulls>
|
"pos": [0, -245],
|
||||||
<Events/>
|
"scale": [440, 145]
|
||||||
</Content>
|
}
|
||||||
<Animations DefaultAnimation="Default">
|
]
|
||||||
<Animation Name="Default" FrameNum="1" Loop="true">
|
}
|
||||||
<RootAnimation>
|
]
|
||||||
<Frame XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
}
|
||||||
</RootAnimation>
|
}
|
||||||
<LayerAnimations>
|
}
|
||||||
<LayerAnimation LayerId="0" Visible="true">
|
|
||||||
<Frame RegionId="0" XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
|
||||||
</LayerAnimation>
|
|
||||||
</LayerAnimations>
|
|
||||||
<NullAnimations>
|
|
||||||
<NullAnimation NullId="0" Visible="true">
|
|
||||||
<Frame XPosition="1" YPosition="-172" Delay="1" Visible="true" XScale="640" YScale="400" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
|
||||||
</NullAnimation>
|
|
||||||
<NullAnimation NullId="1" Visible="true">
|
|
||||||
<Frame XPosition="0" YPosition="-245" Delay="1" Visible="true" XScale="440" YScale="145" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
|
||||||
</NullAnimation>
|
|
||||||
</NullAnimations>
|
|
||||||
<Triggers/>
|
|
||||||
</Animation>
|
|
||||||
</Animations>
|
|
||||||
</SourceDocument>
|
|
||||||
</AnimatedActor>
|
|
||||||
+113128
-212789
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,29 +1,37 @@
|
|||||||
<AnimatedActor>
|
{
|
||||||
<Info CreatedBy="robot" CreatedOn="12/06/2025 11:49:27 PM" Fps="30" Version="0"/>
|
"anm2ed": 1,
|
||||||
<Content>
|
"info": {"createdOn": "2025-12-06T23:49:27"},
|
||||||
<Spritesheets>
|
"spritesheets": [
|
||||||
<Spritesheet Id="0" Path="item_001_poke_puff_basic_sweet.png">
|
{
|
||||||
<Region Id="0" Name="48x48" XCrop="0" YCrop="0" Width="48" Height="48" XPivot="24" YPivot="24"/>
|
"path": "item_001_poke_puff_basic_sweet.png",
|
||||||
</Spritesheet>
|
"regions": [
|
||||||
</Spritesheets>
|
{
|
||||||
<Layers>
|
"name": "48x48",
|
||||||
<Layer Id="0" Name="Main" SpritesheetId="0"/>
|
"size": [48, 48],
|
||||||
</Layers>
|
"pivot": [24, 24]
|
||||||
<Nulls/>
|
}
|
||||||
<Events/>
|
]
|
||||||
</Content>
|
}
|
||||||
<Animations DefaultAnimation="Default">
|
],
|
||||||
<Animation Name="Default" FrameNum="1" Loop="true">
|
"layers": [
|
||||||
<RootAnimation>
|
{"name": "Main"}
|
||||||
<Frame XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="125" YScale="125" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
],
|
||||||
</RootAnimation>
|
"defaultAnimation": "Default",
|
||||||
<LayerAnimations>
|
"animations": {
|
||||||
<LayerAnimation LayerId="0" Visible="true">
|
"Default": {
|
||||||
<Frame XPivot="24" YPivot="24" XCrop="0" YCrop="0" Width="48" Height="48" XPosition="0" YPosition="0" Delay="1" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
"root": [
|
||||||
</LayerAnimation>
|
{
|
||||||
</LayerAnimations>
|
"scale": [125, 125]
|
||||||
<NullAnimations/>
|
}
|
||||||
<Triggers/>
|
],
|
||||||
</Animation>
|
"layers": [
|
||||||
</Animations>
|
{
|
||||||
</AnimatedActor>
|
"layer": 0,
|
||||||
|
"frames": [
|
||||||
|
{"region": 0}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,48 @@
|
|||||||
<AnimatedActor>
|
{
|
||||||
<Info CreatedBy="robot" CreatedOn="" Fps="30" Version="0"/>
|
"anm2ed": 1,
|
||||||
<Content>
|
"info": {},
|
||||||
<Spritesheets>
|
"spritesheets": [
|
||||||
<Spritesheet Id="0" Path="circle.png">
|
{
|
||||||
<Region Id="0" Name="Main" XCrop="0" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"path": "circle.png",
|
||||||
</Spritesheet>
|
"regions": [
|
||||||
</Spritesheets>
|
{
|
||||||
<Layers>
|
"name": "Main",
|
||||||
<Layer Id="0" Name="Main" SpritesheetId="0"/>
|
"size": [128, 128],
|
||||||
</Layers>
|
"pivot": [64, 64],
|
||||||
<Nulls/>
|
"origin": "center"
|
||||||
<Events/>
|
}
|
||||||
</Content>
|
]
|
||||||
<Animations DefaultAnimation="Spawn">
|
}
|
||||||
<Animation Name="Spawn" FrameNum="11" Loop="false">
|
],
|
||||||
<RootAnimation>
|
"layers": [
|
||||||
<Frame XPosition="0" YPosition="0" Delay="11" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="false"/>
|
{"name": "Main"}
|
||||||
</RootAnimation>
|
],
|
||||||
<LayerAnimations>
|
"defaultAnimation": "Spawn",
|
||||||
<LayerAnimation LayerId="0" Visible="true">
|
"animations": {
|
||||||
<Frame RegionId="0" XPosition="0" YPosition="0" XScale="10" YScale="10" Delay="10" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"Spawn": {
|
||||||
<Frame RegionId="0" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="1" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="0" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"length": 11,
|
||||||
</LayerAnimation>
|
"loop": false,
|
||||||
</LayerAnimations>
|
"root": [
|
||||||
<NullAnimations/>
|
{"delay": 11}
|
||||||
<Triggers/>
|
],
|
||||||
</Animation>
|
"layers": [
|
||||||
</Animations>
|
{
|
||||||
</AnimatedActor>
|
"layer": 0,
|
||||||
|
"frames": [
|
||||||
|
{
|
||||||
|
"region": 0,
|
||||||
|
"scale": [10, 10],
|
||||||
|
"delay": 10,
|
||||||
|
"interpolation": "linear"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"region": 0,
|
||||||
|
"tint": [255, 255, 255, 0],
|
||||||
|
"interpolation": "linear"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,44 +1,99 @@
|
|||||||
<AnimatedActor>
|
{
|
||||||
<Info CreatedBy="robot" CreatedOn="" Fps="30" Version="0"/>
|
"anm2ed": 1,
|
||||||
<Content>
|
"info": {},
|
||||||
<Spritesheets>
|
"spritesheets": [
|
||||||
<Spritesheet Id="0" Path="puff.png">
|
{
|
||||||
<Region Id="0" Name="1" XCrop="0" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"path": "puff.png",
|
||||||
<Region Id="1" Name="2" XCrop="128" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"regions": [
|
||||||
<Region Id="2" Name="3" XCrop="256" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
{
|
||||||
<Region Id="3" Name="4" XCrop="384" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"name": "1",
|
||||||
<Region Id="4" Name="5" XCrop="512" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"size": [128, 128],
|
||||||
<Region Id="5" Name="6" XCrop="640" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"pivot": [64, 64],
|
||||||
<Region Id="6" Name="7" XCrop="768" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
"origin": "center"
|
||||||
<Region Id="7" Name="8" XCrop="896" YCrop="0" Width="128" Height="128" Origin="Center"/>
|
},
|
||||||
</Spritesheet>
|
{
|
||||||
</Spritesheets>
|
"name": "2",
|
||||||
<Layers>
|
"crop": [128, 0],
|
||||||
<Layer Id="0" Name="Main" SpritesheetId="0"/>
|
"size": [128, 128],
|
||||||
</Layers>
|
"pivot": [64, 64],
|
||||||
<Nulls/>
|
"origin": "center"
|
||||||
<Events/>
|
},
|
||||||
</Content>
|
{
|
||||||
<Animations DefaultAnimation="Main">
|
"name": "3",
|
||||||
<Animation Name="Main" FrameNum="16" Loop="false">
|
"crop": [256, 0],
|
||||||
<RootAnimation>
|
"size": [128, 128],
|
||||||
<Frame XPosition="0" YPosition="0" Delay="15" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"pivot": [64, 64],
|
||||||
<Frame XPosition="0" YPosition="-5" Delay="1" Visible="true" XScale="100" YScale="100" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="32" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"origin": "center"
|
||||||
</RootAnimation>
|
},
|
||||||
<LayerAnimations>
|
{
|
||||||
<LayerAnimation LayerId="0" Visible="true">
|
"name": "4",
|
||||||
<Frame RegionId="0" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"crop": [384, 0],
|
||||||
<Frame RegionId="1" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"size": [128, 128],
|
||||||
<Frame RegionId="2" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"pivot": [64, 64],
|
||||||
<Frame RegionId="3" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"origin": "center"
|
||||||
<Frame RegionId="4" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
},
|
||||||
<Frame RegionId="5" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
{
|
||||||
<Frame RegionId="6" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"name": "5",
|
||||||
<Frame RegionId="7" XPosition="0" YPosition="0" XScale="100" YScale="100" Delay="2" Visible="true" RedTint="255" GreenTint="255" BlueTint="255" AlphaTint="255" RedOffset="0" GreenOffset="0" BlueOffset="0" Rotation="0" Interpolated="true"/>
|
"crop": [512, 0],
|
||||||
</LayerAnimation>
|
"size": [128, 128],
|
||||||
</LayerAnimations>
|
"pivot": [64, 64],
|
||||||
<NullAnimations/>
|
"origin": "center"
|
||||||
<Triggers/>
|
},
|
||||||
</Animation>
|
{
|
||||||
</Animations>
|
"name": "6",
|
||||||
</AnimatedActor>
|
"crop": [640, 0],
|
||||||
|
"size": [128, 128],
|
||||||
|
"pivot": [64, 64],
|
||||||
|
"origin": "center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "7",
|
||||||
|
"crop": [768, 0],
|
||||||
|
"size": [128, 128],
|
||||||
|
"pivot": [64, 64],
|
||||||
|
"origin": "center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "8",
|
||||||
|
"crop": [896, 0],
|
||||||
|
"size": [128, 128],
|
||||||
|
"pivot": [64, 64],
|
||||||
|
"origin": "center"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"layers": [
|
||||||
|
{"name": "Main"}
|
||||||
|
],
|
||||||
|
"defaultAnimation": "Main",
|
||||||
|
"animations": {
|
||||||
|
"Main": {
|
||||||
|
"length": 16,
|
||||||
|
"loop": false,
|
||||||
|
"root": [
|
||||||
|
{"delay": 15, "interpolation": "linear"},
|
||||||
|
{
|
||||||
|
"pos": [0, -5],
|
||||||
|
"tint": [255, 255, 255, 32],
|
||||||
|
"interpolation": "linear"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"layers": [
|
||||||
|
{
|
||||||
|
"layer": 0,
|
||||||
|
"frames": [
|
||||||
|
{"region": 0, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 1, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 2, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 3, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 4, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 5, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 6, "delay": 2, "interpolation": "linear"},
|
||||||
|
{"region": 7, "delay": 2, "interpolation": "linear"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"anm2": "gfx/items/item.anm2",
|
||||||
|
"spritesheet": 0,
|
||||||
|
"spawnDelay": 600,
|
||||||
|
"spawnDelayMultiplier": 0.75,
|
||||||
|
"spawnLimit": 5,
|
||||||
|
"sounds": {
|
||||||
|
"bounce": ["sfx/bounce.ogg", "sfx/bounce2.ogg", "sfx/bounce3.ogg"],
|
||||||
|
"dispose": ["sfx/dispose.ogg"],
|
||||||
|
"summon": ["sfx/summon.ogg"],
|
||||||
|
"newItem": ["sfx/newItem.ogg"]
|
||||||
|
},
|
||||||
|
"particles": {
|
||||||
|
"dispose": ["Puff"],
|
||||||
|
"summon": ["Puff"]
|
||||||
|
},
|
||||||
|
"categories": {"Food": "edible", "Tool": "tool"},
|
||||||
|
"rarities": {
|
||||||
|
"Common": {"weight": 1.0, "sound": "sfx/common.ogg"},
|
||||||
|
"Uncommon": {"weight": 0.75, "sound": "sfx/uncommon.ogg"},
|
||||||
|
"Rare": {"weight": 0.5, "sound": "sfx/rare.ogg"},
|
||||||
|
"Epic": {"weight": 0.25, "sound": "sfx/epic.ogg"},
|
||||||
|
"Legendary": {"weight": 0.125, "sound": "sfx/legendary.ogg"},
|
||||||
|
"???": {"weight": 0.001, "sound": "sfx/impossible.ogg"},
|
||||||
|
"Special": {"weight": 0.0, "sound": "sfx/special.ogg"}
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{"name": "Poké Puff (Basic, Sweet)", "description": "A basic sweet Poké Puff.", "category": "Food", "rarity": "Common", "flavor": "Sweet", "texture": "gfx/items/item_001_poke_puff_basic_sweet.png", "calories": 100.0},
|
||||||
|
{"name": "Poké Puff (Basic, Mint)", "description": "A basic minty Poké Puff.", "category": "Food", "rarity": "Common", "flavor": "Mint", "texture": "gfx/items/item_002_poke_puff_basic_mint.png", "calories": 100.0},
|
||||||
|
{"name": "Poké Puff (Basic, Citrus)", "description": "A basic citrusy Poké Puff.", "category": "Food", "rarity": "Common", "flavor": "Citrus", "texture": "gfx/items/item_003_poke_puff_basic_citrus.png", "calories": 100.0},
|
||||||
|
{"name": "Poké Puff (Basic, Mocha)", "description": "A basic mocha Poké Puff.", "category": "Food", "rarity": "Common", "flavor": "Mocha", "texture": "gfx/items/item_004_poke_puff_basic_mocha.png", "calories": 100.0},
|
||||||
|
{"name": "Poké Puff (Basic, Spice)", "description": "A basic spice Poké Puff.", "category": "Food", "rarity": "Common", "flavor": "Spice", "texture": "gfx/items/item_005_poke_puff_basic_spice.png", "calories": 100.0},
|
||||||
|
{"name": "Poké Puff (Frosted, Sweet)", "description": "A frosted sweet Poké Puff.", "category": "Food", "rarity": "Uncommon", "flavor": "Sweet", "texture": "gfx/items/item_006_poke_puff_frosted_sweet.png", "calories": 250.0},
|
||||||
|
{"name": "Poké Puff (Frosted, Mint)", "description": "A frosted minty Poké Puff.", "category": "Food", "rarity": "Uncommon", "flavor": "Mint", "texture": "gfx/items/item_007_poke_puff_frosted_mint.png", "calories": 250.0},
|
||||||
|
{"name": "Poké Puff (Frosted, Citrus)", "description": "A frosted citrusy Poké Puff.", "category": "Food", "rarity": "Uncommon", "flavor": "Mint", "texture": "gfx/items/item_008_poke_puff_frosted_citrus.png", "calories": 250.0},
|
||||||
|
{"name": "Poké Puff (Frosted, Mocha)", "description": "A frosted mocha Poké Puff.", "category": "Food", "rarity": "Uncommon", "flavor": "Mocha", "texture": "gfx/items/item_009_poke_puff_frosted_mocha.png", "calories": 250.0},
|
||||||
|
{"name": "Poké Puff (Frosted, Spice)", "description": "A frosted spice Poké Puff.", "category": "Food", "rarity": "Uncommon", "flavor": "Spice", "texture": "gfx/items/item_010_poke_puff_frosted_spice.png", "calories": 250.0},
|
||||||
|
{"name": "Poké Puff (Fancy, Sweet)", "description": "A fancy sweet Poké Puff, adorned with a cherry.", "category": "Food", "rarity": "Rare", "flavor": "Sweet", "texture": "gfx/items/item_011_poke_puff_fancy_sweet.png", "calories": 500.0},
|
||||||
|
{"name": "Poké Puff (Fancy, Mint)", "description": "A fancy minty Poké Puff, adorned with a crescent moon-shaped candy.", "category": "Food", "rarity": "Rare", "flavor": "Mint", "texture": "gfx/items/item_012_poke_puff_fancy_mint.png", "calories": 500.0},
|
||||||
|
{"name": "Poké Puff (Fancy, Citrus)", "description": "A fancy citrus Poké Puff, adorned with an orange slice.", "category": "Food", "rarity": "Rare", "flavor": "Citrus", "texture": "gfx/items/item_013_poke_puff_fancy_citrus.png", "calories": 500.0},
|
||||||
|
{"name": "Poké Puff (Fancy, Mocha)", "description": "A fancy mocha Poké Puff, adorned with a morsel of white chocolate.", "category": "Food", "rarity": "Rare", "flavor": "Mocha", "texture": "gfx/items/item_014_poke_puff_fancy_mocha.png", "calories": 500.0},
|
||||||
|
{"name": "Poké Puff (Fancy, Spice)", "description": "A fancy spice Poké Puff, adorned with a morsel of dark chocolate.", "category": "Food", "rarity": "Rare", "flavor": "Spice", "texture": "gfx/items/item_015_poke_puff_fancy_spice.png", "calories": 500.0},
|
||||||
|
{"name": "Poké Puff (Deluxe, Sweet)", "description": "A deluxe sweet Poké Puff; frosted and adorned with a cherry.", "category": "Food", "rarity": "Epic", "flavor": "Sweet", "texture": "gfx/items/item_016_poke_puff_deluxe_sweet.png", "calories": 1000.0},
|
||||||
|
{"name": "Poké Puff (Deluxe, Mint)", "description": "A deluxe minty Poké Puff; frosted and adorned with a cinnamon stick.", "category": "Food", "rarity": "Epic", "flavor": "Mint", "texture": "gfx/items/item_017_poke_puff_deluxe_mint.png", "calories": 1000.0},
|
||||||
|
{"name": "Poké Puff (Deluxe, Citrus)", "description": "A deluxe citrusy Poké Puff; frosted and adorned with an orange slice.", "category": "Food", "rarity": "Epic", "flavor": "Citrus", "texture": "gfx/items/item_018_poke_puff_deluxe_citrus.png", "calories": 1000.0},
|
||||||
|
{"name": "Poké Puff (Deluxe, Mocha)", "description": "A deluxe mocha Poké Puff; frosted and adorned with a morsel of white chocolate.", "category": "Food", "rarity": "Epic", "flavor": "Mocha", "texture": "gfx/items/item_019_poke_puff_deluxe_mocha.png", "calories": 1000.0},
|
||||||
|
{"name": "Poké Puff (Deluxe, Spice)", "description": "A deluxe spice Poké Puff; frosted and adorned with a morsel of dark chocolate.", "category": "Food", "rarity": "Epic", "flavor": "Spice", "texture": "gfx/items/item_020_poke_puff_deluxe_spice.png", "calories": 1000.0},
|
||||||
|
{"name": "Poké Puff (Supreme Spring)", "description": "A supreme Poké Puff that tastes like the sweet cherry blossoms of spring.", "category": "Food", "rarity": "Legendary", "flavor": "Sweet", "texture": "gfx/items/item_021_poke_puff_supreme_spring.png", "calories": 2500.0},
|
||||||
|
{"name": "Poké Puff (Supreme Summer)", "description": "A supreme Poké Puff that tastes like a tropical summer vacation.", "category": "Food", "rarity": "Legendary", "flavor": "Citrus", "texture": "gfx/items/item_022_poke_puff_supreme_summer.png", "calories": 2500.0},
|
||||||
|
{"name": "Poké Puff (Supreme Autumn)", "description": "A supreme Poké Puff that tastes like a bountiful autumn harvest.", "category": "Food", "rarity": "Legendary", "flavor": "Citrus", "texture": "gfx/items/item_023_poke_puff_supreme_autumn.png", "calories": 2500.0},
|
||||||
|
{"name": "Poké Puff (Supreme Winter)", "description": "A supreme Poké Puff that tastes like a frosty winter wonderland.", "category": "Food", "rarity": "Legendary", "flavor": "Spice", "texture": "gfx/items/item_024_poke_puff_supreme_winter.png", "calories": 2500.0},
|
||||||
|
{"name": "Poké Puff (Supreme Wish)", "description": "A supreme Poké Puff that tastes like a cherished birthday celebration.\nIt also is the biggest calorie nuke ever.\nCan Snivy eat it!?\nAlso, statistically, it might be your birthday today, right?", "category": "Food", "rarity": "???", "flavor": "Spice", "texture": "gfx/items/item_025_poke_puff_supreme_wish.png", "calories": 10000.0},
|
||||||
|
{"name": "Poké Puff (Supreme Honor)", "description": "A supreme Poké Puff that tastes like a monumental victory.\nWith your luck, you've earned it...if Snivy can eat it!", "category": "Food", "rarity": "???", "flavor": "Mocha", "texture": "gfx/items/item_026_poke_puff_supreme_honor.png", "calories": 2500.0, "digestionBonus": 0.6},
|
||||||
|
{"name": "Cheri Berry", "description": "A spherical red berry that cures paralysis\n...oh, and it also excites one's stomach.", "category": "Food", "rarity": "Uncommon", "flavor": "Spicy", "texture": "gfx/items/item_027_cheri_berry.png", "calories": 25.0, "digestionBonus": 0.009},
|
||||||
|
{"name": "Tamato Berry", "description": "A spiny crimson berry that lowers speed and raises friendship.\n...oh, and it also greatly excites one's stomach.", "category": "Food", "rarity": "Rare", "flavor": "Spicy", "texture": "gfx/items/item_028_tamato_berry.png", "calories": 50.0, "digestionBonus": 0.018},
|
||||||
|
{"name": "Touga Berry", "description": "An obscure, searing-red berry from faraway lands.\nIt cures confusion...and it'll make one's stomach a burning inferno.", "category": "Food", "rarity": "Epic", "flavor": "Spicy", "texture": "gfx/items/item_029_touga_berry.png", "calories": 100.0, "digestionBonus": 0.0396},
|
||||||
|
{"name": "Pomeg Berry", "description": "cG9tZWcgYmVycnkgcG9tZWcgYmVycnkgcG9tZWcgYmVycnk=", "category": "Food", "rarity": "???", "flavor": "None", "texture": "gfx/items/item_030_pomeg_berry.png", "calories": -2500.0, "digestionBonus": 0.6, "gravity": 0.05},
|
||||||
|
{"name": "Shiny Charm", "description": "Can change a Pokémon's color scheme...", "category": "Tool", "rarity": "???", "texture": "gfx/items/item_031_shiny_charm.png", "isToggleSpritesheet": true}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
<ItemSchema BaseSpawnDelayTicks="600" ItemSpawnLimit="5" SpawnDelayStageMultiplier="0.75" TextureRootPath="gfx" SoundRootPath="sfx" BaseAnm2="items/item.anm2">
|
|
||||||
<Actions>
|
|
||||||
<Bounce>
|
|
||||||
<Sound Path="bounce.ogg" />
|
|
||||||
<Sound Path="bounce2.ogg" />
|
|
||||||
<Sound Path="bounce3.ogg" />
|
|
||||||
</Bounce>
|
|
||||||
<Dispose>
|
|
||||||
<Sound Path="dispose.ogg" />
|
|
||||||
<Particle Type="Puff" />
|
|
||||||
</Dispose>
|
|
||||||
<Summon>
|
|
||||||
<Sound Path="summon.ogg" />
|
|
||||||
<Particle Type="Puff" />
|
|
||||||
</Summon>
|
|
||||||
<NewItem>
|
|
||||||
<Sound Path="newItem.ogg" />
|
|
||||||
</NewItem>
|
|
||||||
<Return>
|
|
||||||
<Sound Path="return.ogg" />
|
|
||||||
<Particle Type="Puff" />
|
|
||||||
</Return>
|
|
||||||
</Actions>
|
|
||||||
<Categories>
|
|
||||||
<Category Name="Invalid" />
|
|
||||||
<Category Name="Food" UseMode="Edible" />
|
|
||||||
<Category Name="Tool" UseMode="Tool" />
|
|
||||||
</Categories>
|
|
||||||
<Flavors>
|
|
||||||
<Flavor Name="None" />
|
|
||||||
<Flavor Name="Sweet" />
|
|
||||||
<Flavor Name="Bitter" />
|
|
||||||
<Flavor Name="Spicy" />
|
|
||||||
<Flavor Name="Mint" />
|
|
||||||
<Flavor Name="Citrus" />
|
|
||||||
<Flavor Name="Mocha" />
|
|
||||||
<Flavor Name="Spice" />
|
|
||||||
</Flavors>
|
|
||||||
<Rarities>
|
|
||||||
<Rarity Name="Common" Weight="1.0" Sound="common.ogg" />
|
|
||||||
<Rarity Name="Uncommon" Weight="0.75" Sound="uncommon.ogg" />
|
|
||||||
<Rarity Name="Rare" Weight="0.5" Sound="rare.ogg" />
|
|
||||||
<Rarity Name="Epic" Weight="0.25" Sound="epic.ogg" />
|
|
||||||
<Rarity Name="Legendary" Weight="0.125" Sound="legendary.ogg" />
|
|
||||||
<Rarity Name="???" Weight="0.001" IsHiddenInCatalog="true" Sound="impossible.ogg" />
|
|
||||||
<Rarity Name="Special" Weight="0.0" IsHiddenInCatalog="true" Sound="special.ogg" />
|
|
||||||
</Rarities>
|
|
||||||
<Items TextureRootPath="gfx/items" SpritesheetID="0">
|
|
||||||
<Item Name="Poké Puff (Basic, Sweet)" Texture="item_001_poke_puff_basic_sweet.png" Description="A basic sweet Poké Puff." Category="Food" Flavor="Sweet" Rarity="Common" Calories="100.0" />
|
|
||||||
<Item Name="Poké Puff (Basic, Mint)" Texture="item_002_poke_puff_basic_mint.png" Description="A basic minty Poké Puff." Category="Food" Flavor="Mint" Rarity="Common" Calories="100.0" />
|
|
||||||
<Item Name="Poké Puff (Basic, Citrus)" Texture="item_003_poke_puff_basic_citrus.png" Description="A basic citrusy Poké Puff." Category="Food" Flavor="Citrus" Rarity="Common" Calories="100.0" />
|
|
||||||
<Item Name="Poké Puff (Basic, Mocha)" Texture="item_004_poke_puff_basic_mocha.png" Description="A basic mocha Poké Puff." Category="Food" Flavor="Mocha" Rarity="Common" Calories="100.0" />
|
|
||||||
<Item Name="Poké Puff (Basic, Spice)" Texture="item_005_poke_puff_basic_spice.png" Description="A basic spice Poké Puff." Category="Food" Flavor="Spice" Rarity="Common" Calories="100.0" />
|
|
||||||
<Item Name="Poké Puff (Frosted, Sweet)" Texture="item_006_poke_puff_frosted_sweet.png" Description="A frosted sweet Poké Puff." Category="Food" Flavor="Sweet" Rarity="Uncommon" Calories="250.0" />
|
|
||||||
<Item Name="Poké Puff (Frosted, Mint)" Texture="item_007_poke_puff_frosted_mint.png" Description="A frosted minty Poké Puff." Category="Food" Flavor="Mint" Rarity="Uncommon" Calories="250.0" />
|
|
||||||
<Item Name="Poké Puff (Frosted, Citrus)" Texture="item_008_poke_puff_frosted_citrus.png" Description="A frosted citrusy Poké Puff." Category="Food" Flavor="Mint" Rarity="Uncommon" Calories="250.0" />
|
|
||||||
<Item Name="Poké Puff (Frosted, Mocha)" Texture="item_009_poke_puff_frosted_mocha.png" Description="A frosted mocha Poké Puff." Category="Food" Flavor="Mocha" Rarity="Uncommon" Calories="250.0" />
|
|
||||||
<Item Name="Poké Puff (Frosted, Spice)" Texture="item_010_poke_puff_frosted_spice.png" Description="A frosted spice Poké Puff." Category="Food" Flavor="Spice" Rarity="Uncommon" Calories="250.0" />
|
|
||||||
<Item Name="Poké Puff (Fancy, Sweet)" Texture="item_011_poke_puff_fancy_sweet.png" Description="A fancy sweet Poké Puff, adorned with a cherry." Category="Food" Flavor="Sweet" Rarity="Rare" Calories="500.0" />
|
|
||||||
<Item Name="Poké Puff (Fancy, Mint)" Texture="item_012_poke_puff_fancy_mint.png" Description="A fancy minty Poké Puff, adorned with a crescent moon-shaped candy." Category="Food" Flavor="Mint" Rarity="Rare" Calories="500.0" />
|
|
||||||
<Item Name="Poké Puff (Fancy, Citrus)" Texture="item_013_poke_puff_fancy_citrus.png" Description="A fancy citrus Poké Puff, adorned with an orange slice." Category="Food" Flavor="Citrus" Rarity="Rare" Calories="500.0" />
|
|
||||||
<Item Name="Poké Puff (Fancy, Mocha)" Texture="item_014_poke_puff_fancy_mocha.png" Description="A fancy mocha Poké Puff, adorned with a morsel of white chocolate." Category="Food" Flavor="Mocha" Rarity="Rare" Calories="500.0" />
|
|
||||||
<Item Name="Poké Puff (Fancy, Spice)" Texture="item_015_poke_puff_fancy_spice.png" Description="A fancy spice Poké Puff, adorned with a morsel of dark chocolate." Category="Food" Flavor="Spice" Rarity="Rare" Calories="500.0" />
|
|
||||||
<Item Name="Poké Puff (Deluxe, Sweet)" Texture="item_016_poke_puff_deluxe_sweet.png" Description="A deluxe sweet Poké Puff; frosted and adorned with a cherry." Category="Food" Flavor="Sweet" Rarity="Epic" Calories="1000.0" />
|
|
||||||
<Item Name="Poké Puff (Deluxe, Mint)" Texture="item_017_poke_puff_deluxe_mint.png" Description="A deluxe minty Poké Puff; frosted and adorned with a cinnamon stick." Category="Food" Flavor="Mint" Rarity="Epic" Calories="1000.0" />
|
|
||||||
<Item Name="Poké Puff (Deluxe, Citrus)" Texture="item_018_poke_puff_deluxe_citrus.png" Description="A deluxe citrusy Poké Puff; frosted and adorned with an orange slice." Category="Food" Flavor="Citrus" Rarity="Epic" Calories="1000.0" />
|
|
||||||
<Item Name="Poké Puff (Deluxe, Mocha)" Texture="item_019_poke_puff_deluxe_mocha.png" Description="A deluxe mocha Poké Puff; frosted and adorned with a morsel of white chocolate." Category="Food" Flavor="Mocha" Rarity="Epic" Calories="1000.0" />
|
|
||||||
<Item Name="Poké Puff (Deluxe, Spice)" Texture="item_020_poke_puff_deluxe_spice.png" Description="A deluxe spice Poké Puff; frosted and adorned with a morsel of dark chocolate." Category="Food" Flavor="Spice" Rarity="Epic" Calories="1000.0" />
|
|
||||||
<Item Name="Poké Puff (Supreme Spring)" Texture="item_021_poke_puff_supreme_spring.png" Description="A supreme Poké Puff that tastes like the sweet cherry blossoms of spring." Category="Food" Flavor="Sweet" Rarity="Legendary" Calories="2500.0" />
|
|
||||||
<Item Name="Poké Puff (Supreme Summer)" Texture="item_022_poke_puff_supreme_summer.png" Description="A supreme Poké Puff that tastes like a tropical summer vacation." Category="Food" Flavor="Citrus" Rarity="Legendary" Calories="2500.0" />
|
|
||||||
<Item Name="Poké Puff (Supreme Autumn)" Texture="item_023_poke_puff_supreme_autumn.png" Description="A supreme Poké Puff that tastes like a bountiful autumn harvest." Category="Food" Flavor="Citrus" Rarity="Legendary" Calories="2500.0" />
|
|
||||||
<Item Name="Poké Puff (Supreme Winter)" Texture="item_024_poke_puff_supreme_winter.png" Description="A supreme Poké Puff that tastes like a frosty winter wonderland." Category="Food" Flavor="Spice" Rarity="Legendary" Calories="2500.0" />
|
|
||||||
<Item Name="Poké Puff (Supreme Wish)" Texture="item_025_poke_puff_supreme_wish.png" Description="A supreme Poké Puff that tastes like a cherished birthday celebration. It also is the biggest calorie nuke ever. Can Snivy eat it!? Also, statistically, it might be your birthday today, right?" Category="Food" Flavor="Spice" Rarity="???" Calories="10000.0" />
|
|
||||||
<Item Name="Poké Puff (Supreme Honor)" Texture="item_026_poke_puff_supreme_honor.png" Description="A supreme Poké Puff that tastes like a monumental victory. With your luck, you've earned it...if Snivy can eat it!" Category="Food" Flavor="Mocha" Rarity="???" Calories="2500.0" DigestionBonus="0.01" />
|
|
||||||
<Item Name="Cheri Berry" Texture="item_027_cheri_berry.png" Description="A spherical red berry that cures paralysis ...oh, and it also excites one's stomach." Category="Food" Flavor="Spicy" Rarity="Uncommon" Calories="25.0" DigestionBonus="0.00015" />
|
|
||||||
<Item Name="Tamato Berry" Texture="item_028_tamato_berry.png" Description="A spiny crimson berry that lowers speed and raises friendship. ...oh, and it also greatly excites one's stomach." Category="Food" Flavor="Spicy" Rarity="Rare" Calories="50.0" DigestionBonus="0.0003" />
|
|
||||||
<Item Name="Touga Berry" Texture="item_029_touga_berry.png" Description="An obscure, searing-red berry from faraway lands. It cures confusion...and it'll make one's stomach a burning inferno." Category="Food" Flavor="Spicy" Rarity="Epic" Calories="100.0" DigestionBonus="0.00066" />
|
|
||||||
<Item Name="Pomeg Berry" Texture="item_030_pomeg_berry.png" Description="cG9tZWcgYmVycnkgcG9tZWcgYmVycnkgcG9tZWcgYmVycnk=" Category="Food" Flavor="None" Rarity="???" Calories="-2500.0" DigestionBonus="0.01" Gravity="0.05" />
|
|
||||||
<Item Name="Shiny Charm" Texture="item_031_shiny_charm.png" Description="Can change a Pokémon's color scheme..." Category="Tool" Rarity="???" IsToggleSpritesheet="true" />
|
|
||||||
</Items>
|
|
||||||
</ItemSchema>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<Menu SoundRootPath="sfx" FontRootPath="font" Font="font.ttf" IconsFont="icons.ttf" ButtonRounding="10.0">
|
|
||||||
<Home GlyphFromIconFont="1" />
|
|
||||||
<Actions>
|
|
||||||
<Open>
|
|
||||||
<Sound Path="open.ogg" />
|
|
||||||
</Open>
|
|
||||||
<Close>
|
|
||||||
<Sound Path="close.ogg" />
|
|
||||||
</Close>
|
|
||||||
<Hover>
|
|
||||||
<Sound Path="hover.ogg" />
|
|
||||||
<Sound Path="hover2.ogg" />
|
|
||||||
<Sound Path="hover3.ogg" />
|
|
||||||
</Hover>
|
|
||||||
<Select>
|
|
||||||
<Sound Path="select.ogg" />
|
|
||||||
</Select>
|
|
||||||
<CheatsActivated>
|
|
||||||
<Sound Path="cheatsActivated.ogg" />
|
|
||||||
</CheatsActivated>
|
|
||||||
</Actions>
|
|
||||||
</Menu>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<Particles Anm2RootPath="gfx/particle">
|
|
||||||
<Particle Type="Circle" Anm2="circle.anm2" Animation="Main" />
|
|
||||||
<Particle Type="Puff" Anm2="puff.anm2" Animation="Main" />
|
|
||||||
</Particles>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<Strings>
|
|
||||||
</Strings>
|
|
||||||
+502
@@ -0,0 +1,502 @@
|
|||||||
|
#include "anm2.h"
|
||||||
|
#include "json.h"
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
constexpr float CHANNEL = 255;
|
||||||
|
constexpr Color LAYER_COLOR = {1, 0, 0, 1};
|
||||||
|
constexpr Color NULL_COLOR = {0, 0, 1, 1};
|
||||||
|
constexpr const char* EASING_NAMES[] = {"", "linear", "easeIn", "easeOut", "easeInOut"};
|
||||||
|
|
||||||
|
struct Region
|
||||||
|
{
|
||||||
|
Vec2 crop;
|
||||||
|
Vec2 pivot;
|
||||||
|
Vec2 size;
|
||||||
|
};
|
||||||
|
|
||||||
|
static Anm2Frame frame_parse(object source, const std::vector<Region>* regions)
|
||||||
|
{
|
||||||
|
Anm2Frame frame;
|
||||||
|
int region = json_int(source, "region", -1);
|
||||||
|
if (regions && region >= 0 && region < (int)regions->size())
|
||||||
|
{
|
||||||
|
frame.crop = (*regions)[region].crop;
|
||||||
|
frame.size = (*regions)[region].size;
|
||||||
|
frame.pivot = (*regions)[region].pivot;
|
||||||
|
}
|
||||||
|
frame.crop = json_vec2(source, "crop", frame.crop);
|
||||||
|
frame.size = json_vec2(source, "size", frame.size);
|
||||||
|
frame.pivot = json_vec2(source, "pivot", frame.pivot);
|
||||||
|
frame.position = json_vec2(source, "pos");
|
||||||
|
frame.scale = json_vec2(source, "scale", frame.scale);
|
||||||
|
frame.rotation = json_float(source, "rotation");
|
||||||
|
frame.tint = json_color(source, "tint", WHITE, CHANNEL);
|
||||||
|
frame.offset = json_color(source, "offset", CLEAR, CHANNEL);
|
||||||
|
frame.delay = std::max(1, json_int(source, "delay", 1));
|
||||||
|
frame.isVisible = json_is(source, "visible", true);
|
||||||
|
std::string easing = json_string(source, "interpolation");
|
||||||
|
for (int i = 0; i < (int)std::size(EASING_NAMES); i++)
|
||||||
|
if (easing == EASING_NAMES[i])
|
||||||
|
frame.easing = (Easing)i;
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Anm2Track track_parse(object source, const char* key, const std::vector<Region>* regions = nullptr)
|
||||||
|
{
|
||||||
|
Anm2Track track;
|
||||||
|
for (element entry : json_array(source, key))
|
||||||
|
{
|
||||||
|
object fields;
|
||||||
|
if (!entry.get(fields))
|
||||||
|
track.push_back(frame_parse(fields, regions));
|
||||||
|
}
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads a list of tracks and groups (each holding more), keyed by idKey, into items under parent.
|
||||||
|
static void items_parse(array list, const char* idKey, int parent, Anm2Animation& animation,
|
||||||
|
std::vector<Anm2Item>& items, const std::vector<std::vector<Region>>& sheetRegions,
|
||||||
|
const std::vector<int>& layerSheets)
|
||||||
|
{
|
||||||
|
for (element entry : list)
|
||||||
|
{
|
||||||
|
object fields;
|
||||||
|
if (entry.get(fields))
|
||||||
|
continue;
|
||||||
|
bool isVisible = json_is(fields, "visible", true);
|
||||||
|
std::string_view group;
|
||||||
|
if (!fields["group"].get(group))
|
||||||
|
{
|
||||||
|
animation.groups.push_back({track_parse(fields, "root"), parent, isVisible});
|
||||||
|
items_parse(json_array(fields, idKey == std::string("layer") ? "layers" : "nulls"), idKey,
|
||||||
|
(int)animation.groups.size() - 1, animation, items, sheetRegions, layerSheets);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int id = json_int(fields, idKey, -1);
|
||||||
|
int sheet = id >= 0 && id < (int)layerSheets.size() ? layerSheets[id] : -1;
|
||||||
|
bool isSheet = sheet >= 0 && sheet < (int)sheetRegions.size();
|
||||||
|
items.push_back({track_parse(fields, "frames", isSheet ? &sheetRegions[sheet] : nullptr), parent, id, isVisible});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads animations by name, along with those inside groups of them (which hold an "animations" object).
|
||||||
|
static void animations_parse(object list, std::vector<Anm2Animation>& animations,
|
||||||
|
const std::vector<std::vector<Region>>& sheetRegions, const std::vector<int>& layerSheets)
|
||||||
|
{
|
||||||
|
for (auto [key, value] : list)
|
||||||
|
{
|
||||||
|
object fields;
|
||||||
|
object group;
|
||||||
|
if (value.get(fields))
|
||||||
|
continue;
|
||||||
|
if (!fields["animations"].get(group))
|
||||||
|
{
|
||||||
|
animations_parse(group, animations, sheetRegions, layerSheets);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Anm2Animation& animation = animations.emplace_back();
|
||||||
|
animation.name = key;
|
||||||
|
animation.length = json_int(fields, "length", 1);
|
||||||
|
animation.isLoop = json_is(fields, "loop", true);
|
||||||
|
animation.root = track_parse(fields, "root");
|
||||||
|
items_parse(json_array(fields, "layers"), "layer", -1, animation, animation.layers, sheetRegions, layerSheets);
|
||||||
|
items_parse(json_array(fields, "nulls"), "null", -1, animation, animation.nulls, sheetRegions, layerSheets);
|
||||||
|
for (element entry : json_array(fields, "triggers"))
|
||||||
|
{
|
||||||
|
object trigger;
|
||||||
|
if (entry.get(trigger))
|
||||||
|
continue;
|
||||||
|
Anm2Trigger& added = animation.triggers.emplace_back();
|
||||||
|
added.at = json_int(trigger, "at");
|
||||||
|
for (element id : json_array(trigger, "sounds"))
|
||||||
|
{
|
||||||
|
int64_t value;
|
||||||
|
if (!id.get(value))
|
||||||
|
added.sounds.push_back((int)value);
|
||||||
|
}
|
||||||
|
int64_t event;
|
||||||
|
if (!json_array(trigger, "events").at(0).get(event))
|
||||||
|
added.event = (int)event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Anm2::load(const std::string& path, Renderer& renderer, Audio& audio)
|
||||||
|
{
|
||||||
|
Json json;
|
||||||
|
json.load(path);
|
||||||
|
if (!json.isValid)
|
||||||
|
return;
|
||||||
|
object root = json.root;
|
||||||
|
std::string folder = path.substr(0, path.find_last_of('/') + 1);
|
||||||
|
fps = json_float(json_object(root, "info"), "fps", fps);
|
||||||
|
|
||||||
|
std::vector<std::vector<Region>> sheetRegions;
|
||||||
|
for (element entry : json_array(root, "spritesheets"))
|
||||||
|
{
|
||||||
|
object fields;
|
||||||
|
if (entry.get(fields))
|
||||||
|
continue;
|
||||||
|
sheets.push_back(renderer.texture_get(folder + json_string(fields, "path")));
|
||||||
|
std::vector<Region>& regions = sheetRegions.emplace_back();
|
||||||
|
for (element regionEntry : json_array(fields, "regions"))
|
||||||
|
{
|
||||||
|
object region;
|
||||||
|
if (regionEntry.get(region))
|
||||||
|
continue;
|
||||||
|
Vec2 size = json_vec2(region, "size");
|
||||||
|
std::string origin = json_string(region, "origin");
|
||||||
|
Vec2 pivot = origin == "center" ? size / 2 : origin == "topLeft" ? Vec2{} : json_vec2(region, "pivot");
|
||||||
|
regions.push_back({json_vec2(region, "crop"), pivot, size});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto names_parse = [&](const char* key, std::vector<std::string>& names, std::vector<int>* sheetIds) {
|
||||||
|
for (element entry : json_array(root, key))
|
||||||
|
{
|
||||||
|
object fields;
|
||||||
|
std::string_view name;
|
||||||
|
if (!entry.get(name))
|
||||||
|
names.emplace_back(name);
|
||||||
|
else if (!entry.get(fields))
|
||||||
|
{
|
||||||
|
names.push_back(json_string(fields, "name"));
|
||||||
|
if (sheetIds)
|
||||||
|
sheetIds->push_back(json_int(fields, "sheet"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
names_parse("layers", layers, &layerSheets);
|
||||||
|
names_parse("nulls", nulls, nullptr);
|
||||||
|
names_parse("events", events, nullptr);
|
||||||
|
std::vector<std::string> soundPaths;
|
||||||
|
names_parse("sounds", soundPaths, nullptr);
|
||||||
|
sounds = audio.sounds_get(folder, soundPaths);
|
||||||
|
|
||||||
|
animations_parse(json_object(root, "animations"), animations, sheetRegions, layerSheets);
|
||||||
|
defaultAnimation = std::max(0, animation_get(json_string(root, "defaultAnimation")));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int name_get(const std::vector<std::string>& names, const std::string& name)
|
||||||
|
{
|
||||||
|
auto found = std::find(names.begin(), names.end(), name);
|
||||||
|
return found == names.end() ? -1 : (int)(found - names.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
int Anm2::animation_get(const std::string& name) const
|
||||||
|
{
|
||||||
|
for (int i = 0; i < (int)animations.size(); i++)
|
||||||
|
if (animations[i].name == name)
|
||||||
|
return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Anm2::event_get(const std::string& name) const
|
||||||
|
{
|
||||||
|
return name_get(events, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Anm2::layer_get(const std::string& name) const
|
||||||
|
{
|
||||||
|
return name_get(layers, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Anm2::null_get(const std::string& name) const
|
||||||
|
{
|
||||||
|
return name_get(nulls, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
int track_length_get(const Anm2Track& track)
|
||||||
|
{
|
||||||
|
int length = 0;
|
||||||
|
for (const Anm2Frame& frame : track)
|
||||||
|
length += frame.delay;
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float ease(float t, Easing easing)
|
||||||
|
{
|
||||||
|
t = std::clamp(t, 0.0f, 1.0f);
|
||||||
|
switch (easing)
|
||||||
|
{
|
||||||
|
case EASING_IN:
|
||||||
|
return t * t;
|
||||||
|
case EASING_OUT:
|
||||||
|
return 1 - (1 - t) * (1 - t);
|
||||||
|
case EASING_IN_OUT:
|
||||||
|
return t < 0.5f ? 2 * t * t : 1 - 2 * (1 - t) * (1 - t);
|
||||||
|
default:
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Actor::Actor(Anm2* anm2, Vec2 position, const std::string& name) : anm2(anm2), position(position)
|
||||||
|
{
|
||||||
|
if (!anm2)
|
||||||
|
return;
|
||||||
|
sheets = anm2->sheets;
|
||||||
|
int index = anm2->animation_get(name);
|
||||||
|
request_apply({anm2->animations[index >= 0 ? index : anm2->defaultAnimation].name, TRANSITION_FORCE});
|
||||||
|
}
|
||||||
|
|
||||||
|
Anm2Animation* Actor::animation_get(int index)
|
||||||
|
{
|
||||||
|
index = index < 0 ? animation : index;
|
||||||
|
return anm2 && index >= 0 && index < (int)anm2->animations.size() ? &anm2->animations[index] : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Actor::is_playing(const std::string& name)
|
||||||
|
{
|
||||||
|
Anm2Animation* current = animation_get();
|
||||||
|
return isPlaying && current && current->name == name;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Actor::is_finished()
|
||||||
|
{
|
||||||
|
Anm2Animation* current = animation_get();
|
||||||
|
return current && (current->isLoop || time > current->length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Actor::play(const std::string& name, Transition transition, Interrupt playInterrupt)
|
||||||
|
{
|
||||||
|
if (!isLocked)
|
||||||
|
pending = Request{name, transition, playInterrupt};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Actor::request_apply(const Request& request)
|
||||||
|
{
|
||||||
|
int index = anm2 ? anm2->animation_get(request.animation) : -1;
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
static std::unordered_set<std::string> missing;
|
||||||
|
if (anm2 && missing.insert(request.animation).second)
|
||||||
|
SDL_Log("Animation \"%s\" does not exist", request.animation.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Anm2Animation* current = animation_get();
|
||||||
|
bool isCurrentLoop = current && current->isLoop;
|
||||||
|
if (request.transition == TRANSITION_IF_IDLE && isPlaying && !isCurrentLoop)
|
||||||
|
return;
|
||||||
|
if (request.interrupt != INTERRUPT_ALWAYS && interrupt == INTERRUPT_NEVER)
|
||||||
|
return;
|
||||||
|
if (isPlaying && index == animation && request.transition != TRANSITION_FORCE)
|
||||||
|
return;
|
||||||
|
animation = index;
|
||||||
|
interrupt = request.interrupt;
|
||||||
|
time = 0;
|
||||||
|
event = -1;
|
||||||
|
isPlaying = !isStatic;
|
||||||
|
triggersPlayed.assign(anm2->animations[index].triggers.size(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Actor::update(Audio& audio)
|
||||||
|
{
|
||||||
|
if (pending)
|
||||||
|
request_apply(*pending);
|
||||||
|
pending.reset();
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)overrides.size(); i++)
|
||||||
|
{
|
||||||
|
Override& override = overrides[i];
|
||||||
|
if (override.timeStart <= 0)
|
||||||
|
continue;
|
||||||
|
float percent = std::clamp(override.time / override.timeStart, 0.0f, 1.0f);
|
||||||
|
float wave = std::cos((1 - percent) * 2 * PI * override.cycles) * percent;
|
||||||
|
override.scale = {std::abs(override.amplitude.x) * wave, std::abs(override.amplitude.y) * wave};
|
||||||
|
if (--override.time <= 0)
|
||||||
|
overrides.erase(overrides.begin() + i--);
|
||||||
|
}
|
||||||
|
|
||||||
|
Anm2Animation* current = animation_get();
|
||||||
|
if (!isPlaying || (current && current->isLoop))
|
||||||
|
interrupt = INTERRUPT_IF_ALLOWED;
|
||||||
|
if (!current || current->length <= 1 || !isPlaying)
|
||||||
|
return;
|
||||||
|
|
||||||
|
event = -1;
|
||||||
|
for (int i = 0; i < (int)current->triggers.size(); i++)
|
||||||
|
{
|
||||||
|
Anm2Trigger& trigger = current->triggers[i];
|
||||||
|
if (triggersPlayed[i] || time < trigger.at)
|
||||||
|
continue;
|
||||||
|
if (!trigger.sounds.empty())
|
||||||
|
{
|
||||||
|
int sound = trigger.sounds[SDL_rand((Sint32)trigger.sounds.size())];
|
||||||
|
if (sound >= 0 && sound < (int)anm2->sounds.size())
|
||||||
|
audio.play(anm2->sounds[sound]);
|
||||||
|
}
|
||||||
|
triggersPlayed[i] = true;
|
||||||
|
event = trigger.event;
|
||||||
|
}
|
||||||
|
|
||||||
|
time += anm2->fps / TICK_RATE;
|
||||||
|
if (time < current->length)
|
||||||
|
return;
|
||||||
|
if (current->isLoop)
|
||||||
|
time = 0;
|
||||||
|
else
|
||||||
|
isPlaying = false;
|
||||||
|
triggersPlayed.assign(triggersPlayed.size(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The track's keyframe at a time, blended toward the next one if it eases, then changed by any overrides. Past the end
|
||||||
|
// the last keyframe holds. An empty track gives an invisible frame.
|
||||||
|
Anm2Frame Actor::frame_get(const Anm2Track& track, float frameTime, OverrideTarget target, int id)
|
||||||
|
{
|
||||||
|
Anm2Frame frame;
|
||||||
|
if (track.empty())
|
||||||
|
{
|
||||||
|
frame.isVisible = false;
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
frameTime = std::max(frameTime, 0.0f);
|
||||||
|
float start = 0;
|
||||||
|
for (size_t i = 0; i < track.size(); i++)
|
||||||
|
{
|
||||||
|
frame = track[i];
|
||||||
|
if (frameTime >= start + frame.delay && i + 1 < track.size())
|
||||||
|
{
|
||||||
|
start += frame.delay;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (frame.easing != EASING_NONE && i + 1 < track.size() && frame.delay > 1)
|
||||||
|
{
|
||||||
|
const Anm2Frame& next = track[i + 1];
|
||||||
|
float t = ease((frameTime - start) / frame.delay, frame.easing);
|
||||||
|
frame.position = vec2_lerp(frame.position, next.position, t);
|
||||||
|
frame.scale = vec2_lerp(frame.scale, next.scale, t);
|
||||||
|
frame.rotation = std::lerp(frame.rotation, next.rotation, t);
|
||||||
|
frame.tint = color_lerp(frame.tint, next.tint, t);
|
||||||
|
frame.offset = color_lerp(frame.offset, next.offset, t);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (Override& override : overrides)
|
||||||
|
{
|
||||||
|
if (override.target != target || override.id != id)
|
||||||
|
continue;
|
||||||
|
if (override.isRegion)
|
||||||
|
{
|
||||||
|
frame.crop = override.crop;
|
||||||
|
frame.size = override.size;
|
||||||
|
frame.pivot = override.pivot;
|
||||||
|
}
|
||||||
|
if (override.isRotation)
|
||||||
|
frame.rotation = override.rotation;
|
||||||
|
frame.scale = frame.scale + override.scale;
|
||||||
|
}
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Affine root_affine_get(const Anm2Frame& frame, Vec2 position = {})
|
||||||
|
{
|
||||||
|
return affine_get(frame.position + position, frame.rotation, frame.scale / PERCENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The transform, tint and color offset a group (and its parents) give its items, or false if any is hidden.
|
||||||
|
bool Actor::is_group_visible(Anm2Animation& current, int group, float frameTime, Affine& model, Color& tint, Color& offset)
|
||||||
|
{
|
||||||
|
if (group < 0 || group >= (int)current.groups.size())
|
||||||
|
return true;
|
||||||
|
Anm2Group& entry = current.groups[group];
|
||||||
|
if (!is_group_visible(current, entry.parent, frameTime, model, tint, offset) || !entry.isVisible)
|
||||||
|
return false;
|
||||||
|
if (entry.root.empty())
|
||||||
|
return true;
|
||||||
|
Anm2Frame frame = frame_get(entry.root, frameTime);
|
||||||
|
model = model * root_affine_get(frame);
|
||||||
|
tint = tint * frame.tint;
|
||||||
|
offset = offset + frame.offset;
|
||||||
|
return frame.isVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Affine null_affine_get(const Anm2Frame& frame)
|
||||||
|
{
|
||||||
|
return affine_get(frame.position, frame.rotation, {1, 1}, frame.scale / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Actor::render(Renderer& renderer, Affine view)
|
||||||
|
{
|
||||||
|
Anm2Animation* current = animation_get();
|
||||||
|
if (!isVisible || !current)
|
||||||
|
return;
|
||||||
|
Anm2Frame root = frame_get(current->root, time);
|
||||||
|
Affine rootModel = view * root_affine_get(root, position);
|
||||||
|
for (Anm2Item& layer : current->layers)
|
||||||
|
{
|
||||||
|
Affine model = rootModel;
|
||||||
|
Color tint = root.tint;
|
||||||
|
Color offset = root.offset;
|
||||||
|
if (!layer.isVisible || !is_group_visible(*current, layer.group, time, model, tint, offset))
|
||||||
|
continue;
|
||||||
|
Anm2Frame frame = frame_get(layer.frames, time, TARGET_LAYER, layer.id);
|
||||||
|
int sheet = layer.id >= 0 && layer.id < (int)anm2->layerSheets.size() ? anm2->layerSheets[layer.id] : -1;
|
||||||
|
Texture* texture = sheet >= 0 && sheet < (int)sheets.size() ? sheets[sheet] : nullptr;
|
||||||
|
if (!frame.isVisible || !texture)
|
||||||
|
continue;
|
||||||
|
model = model * affine_get(frame.position, frame.rotation, frame.scale / PERCENT, frame.pivot);
|
||||||
|
renderer.quad_draw(*texture, {frame.crop.x, frame.crop.y, frame.size.x, frame.size.y}, model,
|
||||||
|
frame.tint * tint, frame.offset + offset);
|
||||||
|
if (isShowLayers)
|
||||||
|
renderer.outline_draw(model, frame.size.x, frame.size.y, LAYER_COLOR);
|
||||||
|
}
|
||||||
|
if (!isShowNulls)
|
||||||
|
return;
|
||||||
|
for (Anm2Item& null : current->nulls)
|
||||||
|
{
|
||||||
|
Affine model = rootModel;
|
||||||
|
Color tint;
|
||||||
|
Color offset;
|
||||||
|
Anm2Frame frame = frame_get(null.frames, time, TARGET_NULL, null.id);
|
||||||
|
if (null.isVisible && frame.isVisible && is_group_visible(*current, null.group, time, model, tint, offset))
|
||||||
|
renderer.outline_draw(model * null_affine_get(frame), frame.scale.x, frame.scale.y, NULL_COLOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where a null's box sits in the world, at a time in an animation (the current ones by default), or an invalid rect.
|
||||||
|
Rect Actor::null_rect_get(int null, int index, float frameTime)
|
||||||
|
{
|
||||||
|
Anm2Animation* current = animation_get(index);
|
||||||
|
if (!current || null < 0)
|
||||||
|
return {NAN, NAN, NAN, NAN};
|
||||||
|
frameTime = frameTime < 0 ? time : frameTime;
|
||||||
|
for (Anm2Item& item : current->nulls)
|
||||||
|
{
|
||||||
|
if (item.id != null)
|
||||||
|
continue;
|
||||||
|
Affine model = root_affine_get(frame_get(current->root, frameTime), position);
|
||||||
|
Color tint;
|
||||||
|
Color offset;
|
||||||
|
Anm2Frame frame = frame_get(item.frames, frameTime, TARGET_NULL, null);
|
||||||
|
if (!frame.isVisible || !is_group_visible(*current, item.group, frameTime, model, tint, offset))
|
||||||
|
break;
|
||||||
|
return affine_bounds(model * null_affine_get(frame), frame.scale.x, frame.scale.y);
|
||||||
|
}
|
||||||
|
return {NAN, NAN, NAN, NAN};
|
||||||
|
}
|
||||||
|
|
||||||
|
// The smallest rectangle holding every visible layer over the whole current animation.
|
||||||
|
Rect Actor::bounds_get()
|
||||||
|
{
|
||||||
|
Rect bounds = {NAN, NAN, NAN, NAN};
|
||||||
|
Anm2Animation* current = animation_get();
|
||||||
|
if (!current)
|
||||||
|
return bounds;
|
||||||
|
for (int frameTime = 0; frameTime < current->length; frameTime++)
|
||||||
|
{
|
||||||
|
Affine rootModel = root_affine_get(frame_get(current->root, (float)frameTime), position);
|
||||||
|
for (Anm2Item& layer : current->layers)
|
||||||
|
{
|
||||||
|
Affine model = rootModel;
|
||||||
|
Color tint;
|
||||||
|
Color offset;
|
||||||
|
Anm2Frame frame = frame_get(layer.frames, (float)frameTime, TARGET_LAYER, layer.id);
|
||||||
|
if (!layer.isVisible || !frame.isVisible || frame.size.x <= 0 ||
|
||||||
|
!is_group_visible(*current, layer.group, (float)frameTime, model, tint, offset))
|
||||||
|
continue;
|
||||||
|
model = model * affine_get(frame.position, frame.rotation, frame.scale / PERCENT, frame.pivot);
|
||||||
|
bounds = rect_merge(bounds, affine_bounds(model, frame.size.x, frame.size.y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
+182
@@ -0,0 +1,182 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "audio.h"
|
||||||
|
#include "renderer.h"
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
enum Easing
|
||||||
|
{
|
||||||
|
EASING_NONE,
|
||||||
|
EASING_LINEAR,
|
||||||
|
EASING_IN,
|
||||||
|
EASING_OUT,
|
||||||
|
EASING_IN_OUT
|
||||||
|
};
|
||||||
|
|
||||||
|
// One keyframe of a root, group, layer or null track. Crop, size and pivot are in spritesheet pixels, scale is in
|
||||||
|
// percent and rotation in degrees, clockwise.
|
||||||
|
struct Anm2Frame
|
||||||
|
{
|
||||||
|
Color offset = CLEAR;
|
||||||
|
Color tint = WHITE;
|
||||||
|
Vec2 crop;
|
||||||
|
Vec2 pivot;
|
||||||
|
Vec2 position;
|
||||||
|
Vec2 scale = {PERCENT, PERCENT};
|
||||||
|
Vec2 size;
|
||||||
|
float rotation = 0;
|
||||||
|
int delay = 1;
|
||||||
|
Easing easing = EASING_NONE;
|
||||||
|
bool isVisible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Anm2Track = std::vector<Anm2Frame>;
|
||||||
|
|
||||||
|
// A layer or null's keyframes within an animation, under one of its groups (or none, -1).
|
||||||
|
struct Anm2Item
|
||||||
|
{
|
||||||
|
Anm2Track frames;
|
||||||
|
int group = -1;
|
||||||
|
int id = -1;
|
||||||
|
bool isVisible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A transform its items take on after the animation's root, itself under a parent group (or none, -1).
|
||||||
|
struct Anm2Group
|
||||||
|
{
|
||||||
|
Anm2Track root;
|
||||||
|
int parent = -1;
|
||||||
|
bool isVisible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Anm2Trigger
|
||||||
|
{
|
||||||
|
std::vector<int> sounds;
|
||||||
|
int at = 0;
|
||||||
|
int event = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Layers draw in order, the first at the bottom.
|
||||||
|
struct Anm2Animation
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
Anm2Track root;
|
||||||
|
std::vector<Anm2Group> groups;
|
||||||
|
std::vector<Anm2Item> layers;
|
||||||
|
std::vector<Anm2Item> nulls;
|
||||||
|
std::vector<Anm2Trigger> triggers;
|
||||||
|
int length = 1;
|
||||||
|
bool isLoop = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// An anm2ed JSON document, with its spritesheets and sounds loaded.
|
||||||
|
class Anm2
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::vector<Anm2Animation> animations;
|
||||||
|
std::vector<std::string> events;
|
||||||
|
std::vector<std::string> layers;
|
||||||
|
std::vector<std::string> nulls;
|
||||||
|
std::vector<Texture*> sheets;
|
||||||
|
std::vector<int> layerSheets;
|
||||||
|
Sounds sounds;
|
||||||
|
float fps = 30;
|
||||||
|
int defaultAnimation = -1;
|
||||||
|
|
||||||
|
void load(const std::string& path, Renderer& renderer, Audio& audio);
|
||||||
|
int animation_get(const std::string& name) const;
|
||||||
|
int event_get(const std::string& name) const;
|
||||||
|
int layer_get(const std::string& name) const;
|
||||||
|
int null_get(const std::string& name) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
int track_length_get(const Anm2Track& track);
|
||||||
|
|
||||||
|
enum Transition
|
||||||
|
{
|
||||||
|
TRANSITION_NORMAL,
|
||||||
|
TRANSITION_FORCE,
|
||||||
|
TRANSITION_IF_IDLE
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Interrupt
|
||||||
|
{
|
||||||
|
INTERRUPT_IF_ALLOWED,
|
||||||
|
INTERRUPT_ALWAYS,
|
||||||
|
INTERRUPT_NEVER
|
||||||
|
};
|
||||||
|
|
||||||
|
enum OverrideTarget
|
||||||
|
{
|
||||||
|
TARGET_ROOT,
|
||||||
|
TARGET_LAYER,
|
||||||
|
TARGET_NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
// Changes one track's frames as an actor draws them: a swapped region, an added scale (oscillating out over time, when
|
||||||
|
// timed) or a set rotation.
|
||||||
|
struct Override
|
||||||
|
{
|
||||||
|
Vec2 amplitude;
|
||||||
|
Vec2 crop;
|
||||||
|
Vec2 pivot;
|
||||||
|
Vec2 scale;
|
||||||
|
Vec2 size;
|
||||||
|
float cycles = 0;
|
||||||
|
float rotation = 0;
|
||||||
|
float time = 0;
|
||||||
|
float timeStart = 0;
|
||||||
|
int id = -1;
|
||||||
|
OverrideTarget target = TARGET_LAYER;
|
||||||
|
bool isRegion = false;
|
||||||
|
bool isRotation = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Request
|
||||||
|
{
|
||||||
|
std::string animation;
|
||||||
|
Transition transition = TRANSITION_NORMAL;
|
||||||
|
Interrupt interrupt = INTERRUPT_IF_ALLOWED;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Plays an Anm2's animations by name at a position. A static actor holds its animation's first frame.
|
||||||
|
class Actor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static inline bool isShowLayers = false;
|
||||||
|
static inline bool isShowNulls = false;
|
||||||
|
|
||||||
|
Anm2* anm2 = nullptr;
|
||||||
|
std::optional<Request> pending;
|
||||||
|
std::vector<Override> overrides;
|
||||||
|
std::vector<Texture*> sheets;
|
||||||
|
std::vector<bool> triggersPlayed;
|
||||||
|
Vec2 position;
|
||||||
|
float time = 0;
|
||||||
|
int animation = -1;
|
||||||
|
int event = -1;
|
||||||
|
Interrupt interrupt = INTERRUPT_IF_ALLOWED;
|
||||||
|
bool isLocked = false;
|
||||||
|
bool isPlaying = false;
|
||||||
|
bool isStatic = false;
|
||||||
|
bool isVisible = true;
|
||||||
|
|
||||||
|
Actor() = default;
|
||||||
|
Actor(Anm2* anm2, Vec2 position = {}, const std::string& animation = "");
|
||||||
|
void play(const std::string& name, Transition transition = TRANSITION_NORMAL,
|
||||||
|
Interrupt interrupt = INTERRUPT_IF_ALLOWED);
|
||||||
|
void update(Audio& audio);
|
||||||
|
void render(Renderer& renderer, Affine view);
|
||||||
|
Anm2Animation* animation_get(int index = -1);
|
||||||
|
Anm2Frame frame_get(const Anm2Track& track, float frameTime, OverrideTarget target = TARGET_ROOT, int id = -1);
|
||||||
|
Rect null_rect_get(int null, int index = -1, float frameTime = -1);
|
||||||
|
Rect bounds_get();
|
||||||
|
bool is_playing(const std::string& name);
|
||||||
|
bool is_finished();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void request_apply(const Request& request);
|
||||||
|
bool is_group_visible(Anm2Animation& animation, int group, float frameTime, Affine& model, Color& tint, Color& offset);
|
||||||
|
};
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
#include "audio.h"
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <stb_vorbis.c>
|
||||||
|
|
||||||
|
void Audio::init()
|
||||||
|
{
|
||||||
|
if (SDL_InitSubSystem(SDL_INIT_AUDIO))
|
||||||
|
device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, nullptr);
|
||||||
|
if (!device)
|
||||||
|
{
|
||||||
|
SDL_Log("Failed to open audio: %s", SDL_GetError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SDL_AudioSpec spec = {};
|
||||||
|
SDL_GetAudioDeviceFormat(device, &spec, nullptr);
|
||||||
|
for (SDL_AudioStream*& voice : voices)
|
||||||
|
{
|
||||||
|
voice = SDL_CreateAudioStream(&spec, &spec);
|
||||||
|
SDL_BindAudioStream(device, voice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Sound* Audio::sound_get(const std::string& path)
|
||||||
|
{
|
||||||
|
Sound& sound = sounds[path];
|
||||||
|
sound.path = path;
|
||||||
|
return &sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sounds Audio::sounds_get(const std::string& folder, const std::vector<std::string>& paths)
|
||||||
|
{
|
||||||
|
Sounds list;
|
||||||
|
for (const std::string& path : paths)
|
||||||
|
list.push_back(sound_get(folder + path));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Audio::play(Sound* sound)
|
||||||
|
{
|
||||||
|
if (!device || !sound)
|
||||||
|
return;
|
||||||
|
if (sound->frames == 0)
|
||||||
|
{
|
||||||
|
sound->frames = stb_vorbis_decode_filename(sound->path.c_str(), &sound->channels, &sound->rate, &sound->samples);
|
||||||
|
if (sound->frames <= 0)
|
||||||
|
SDL_Log("Failed to load %s", sound->path.c_str());
|
||||||
|
sound->frames = sound->frames > 0 ? sound->frames : -1;
|
||||||
|
}
|
||||||
|
if (sound->frames < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int voice = sound->voice >= 0 && voiceSounds[sound->voice] == sound ? sound->voice : -1;
|
||||||
|
for (int i = 0; i < VOICE_COUNT && voice < 0; i++)
|
||||||
|
if (SDL_GetAudioStreamQueued(voices[i]) == 0)
|
||||||
|
voice = i;
|
||||||
|
if (voice < 0)
|
||||||
|
voice = voiceNext;
|
||||||
|
voiceNext = (voice + 1) % VOICE_COUNT;
|
||||||
|
if (voiceSounds[voice])
|
||||||
|
voiceSounds[voice]->voice = -1;
|
||||||
|
voiceSounds[voice] = sound;
|
||||||
|
sound->voice = voice;
|
||||||
|
|
||||||
|
SDL_AudioSpec spec = {SDL_AUDIO_S16, sound->channels, sound->rate};
|
||||||
|
SDL_ClearAudioStream(voices[voice]);
|
||||||
|
SDL_SetAudioStreamFormat(voices[voice], &spec, nullptr);
|
||||||
|
SDL_PutAudioStreamData(voices[voice], sound->samples, sound->frames * sound->channels * (int)sizeof(short));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Audio::play(const Sounds& list)
|
||||||
|
{
|
||||||
|
if (!list.empty())
|
||||||
|
play(list[SDL_rand((Sint32)list.size())]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the volume of everything played, from silent at 0 to full at 1.
|
||||||
|
void Audio::gain_set(float gain)
|
||||||
|
{
|
||||||
|
if (device)
|
||||||
|
SDL_SetAudioDeviceGain(device, gain);
|
||||||
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL_audio.h>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
constexpr int VOICE_COUNT = 16;
|
||||||
|
|
||||||
|
// A sound file, decoded the first time it plays.
|
||||||
|
struct Sound
|
||||||
|
{
|
||||||
|
std::string path;
|
||||||
|
short* samples = nullptr;
|
||||||
|
int channels = 0;
|
||||||
|
int frames = 0;
|
||||||
|
int rate = 0;
|
||||||
|
int voice = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Sounds = std::vector<Sound*>;
|
||||||
|
|
||||||
|
// Mixes sounds through SDL's audio streams, one voice each. A sound played again restarts on its voice.
|
||||||
|
class Audio
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void init();
|
||||||
|
Sound* sound_get(const std::string& path);
|
||||||
|
Sounds sounds_get(const std::string& folder, const std::vector<std::string>& paths);
|
||||||
|
void play(Sound* sound);
|
||||||
|
void play(const Sounds& sounds);
|
||||||
|
void gain_set(float gain);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, Sound> sounds;
|
||||||
|
Sound* voiceSounds[VOICE_COUNT] = {};
|
||||||
|
SDL_AudioStream* voices[VOICE_COUNT] = {};
|
||||||
|
SDL_AudioDeviceID device = 0;
|
||||||
|
int voiceNext = 0;
|
||||||
|
};
|
||||||
@@ -0,0 +1,463 @@
|
|||||||
|
#include "character.h"
|
||||||
|
#include "json.h"
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
constexpr float FONT_SIZE = 20;
|
||||||
|
constexpr const char* EFFECT_NAMES[] = {"animation", "big", "color", "delay", "oscillation", "scene",
|
||||||
|
"screenshake", "shake", "silent", "small", "sound"};
|
||||||
|
constexpr const char* USE_NAMES[] = {"", "edible", "tool"};
|
||||||
|
|
||||||
|
struct SoundSource
|
||||||
|
{
|
||||||
|
const char* key;
|
||||||
|
const char* section;
|
||||||
|
DataFile file;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr SoundSource SOUND_SOURCES[] = {
|
||||||
|
#define X(id, file, section, key) {key, section, file},
|
||||||
|
SOUND_IDS(X)
|
||||||
|
#undef X
|
||||||
|
};
|
||||||
|
|
||||||
|
static int utf8_length_get(const std::string& text)
|
||||||
|
{
|
||||||
|
int length = 0;
|
||||||
|
for (unsigned char byte : text)
|
||||||
|
length += (byte & 0xC0) != 0x80;
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry* Dialogue::entry_get(const std::string& label)
|
||||||
|
{
|
||||||
|
auto found = entries.find(label);
|
||||||
|
return found == entries.end() ? nullptr : &found->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry* Dialogue::pool_get(const std::string& pool)
|
||||||
|
{
|
||||||
|
auto found = pools.find(pool);
|
||||||
|
if (found == pools.end() || found->second.empty())
|
||||||
|
return nullptr;
|
||||||
|
return entry_get(found->second[SDL_rand((Sint32)found->second.size())]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry* Dialogue::hook_get(const std::string& hook)
|
||||||
|
{
|
||||||
|
auto found = hooks.find(hook);
|
||||||
|
if (found == hooks.end())
|
||||||
|
return nullptr;
|
||||||
|
Entry* entry = pool_get(found->second);
|
||||||
|
return entry ? entry : entry_get(found->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Character::preview_load(const std::string& path, Renderer& renderer)
|
||||||
|
{
|
||||||
|
Json json;
|
||||||
|
json.load(path + "character.json");
|
||||||
|
if (!(isValid = json.isValid))
|
||||||
|
return;
|
||||||
|
object root = json.root;
|
||||||
|
folder = path;
|
||||||
|
key = path.substr(0, path.size() - 1);
|
||||||
|
key = key.substr(key.find_last_of('/') + 1);
|
||||||
|
name = json_string(root, "name", key);
|
||||||
|
description = json_string(root, "description");
|
||||||
|
credits = json_string(root, "credits");
|
||||||
|
color = json_color(root, "color", color);
|
||||||
|
weight = json_float(root, "weight");
|
||||||
|
anm2Path = json_string(root, "anm2");
|
||||||
|
portrait = root["portrait"].error() ? nullptr : renderer.texture_get(folder + json_string(root, "portrait"));
|
||||||
|
render = root["render"].error() ? nullptr : renderer.texture_get(folder + json_string(root, "render"));
|
||||||
|
for (element entry : json_array(root, "stages"))
|
||||||
|
{
|
||||||
|
object fields;
|
||||||
|
if (!entry.get(fields))
|
||||||
|
stages.push_back({json_string(fields, "animationSuffix"), json_string(fields, "dialoguePool"),
|
||||||
|
json_float(fields, "weight"), json_int(fields, "area", -1)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Anm2* Character::anm2_get(const std::string& path, Renderer& renderer, Audio& audio)
|
||||||
|
{
|
||||||
|
if (path.empty())
|
||||||
|
return nullptr;
|
||||||
|
auto found = anm2s.find(path);
|
||||||
|
if (found != anm2s.end())
|
||||||
|
return found->second.animations.empty() ? nullptr : &found->second;
|
||||||
|
Anm2& loaded = anm2s[path];
|
||||||
|
loaded.load(folder + path, renderer, audio);
|
||||||
|
return loaded.animations.empty() ? nullptr : &loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets an interact area's fields from source, keeping what it doesn't name (its type's settings).
|
||||||
|
static void interact_parse(object source, InteractArea& area, Character& character, Audio& audio)
|
||||||
|
{
|
||||||
|
area.animation = json_string(source, "animation", area.animation);
|
||||||
|
area.cursorActive = json_string(source, "cursorActive", area.cursorActive);
|
||||||
|
area.cursorHover = json_string(source, "cursorHover", area.cursorHover);
|
||||||
|
area.dialoguePool = json_string(source, "dialoguePool", area.dialoguePool);
|
||||||
|
area.dialoguePoolFull = json_string(source, "dialoguePoolFull", area.dialoguePoolFull);
|
||||||
|
area.glyph = json_string(source, "glyph", area.glyph);
|
||||||
|
area.idleOnEnd = json_string(source, "idleOnEnd", area.idleOnEnd);
|
||||||
|
area.digestionClick = json_float(source, "digestionClick", area.digestionClick);
|
||||||
|
area.digestionHover = json_float(source, "digestionHover", area.digestionHover);
|
||||||
|
area.scaleAmplitude = json_float(source, "scaleAmplitude", area.scaleAmplitude);
|
||||||
|
area.scaleCycles = json_float(source, "scaleCycles", area.scaleCycles);
|
||||||
|
area.scaleTime = json_float(source, "scaleTime", area.scaleTime);
|
||||||
|
area.dialogueCooldown = json_int(source, "dialogueCooldown", area.dialogueCooldown);
|
||||||
|
area.isHold = json_is(source, "isHold", area.isHold);
|
||||||
|
if (!source["layer"].error())
|
||||||
|
area.layer = character.anm2 ? character.anm2->layer_get(json_string(source, "layer")) : -1;
|
||||||
|
if (!source["null"].error())
|
||||||
|
area.null = character.anm2 ? character.anm2->null_get(json_string(source, "null")) : -1;
|
||||||
|
if (!source["particles"].error())
|
||||||
|
area.particles = json_strings(source, "particles");
|
||||||
|
if (!source["sounds"].error())
|
||||||
|
area.sounds = audio.sounds_get(character.folder, json_strings(source, "sounds"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void character_parse(object root, Character& character, Renderer& renderer, Audio& audio)
|
||||||
|
{
|
||||||
|
character.anm2 = character.anm2_get(character.anm2Path, renderer, audio);
|
||||||
|
character.weightMax = json_float(root, "weightMax", character.weight);
|
||||||
|
character.capacity = json_float(root, "capacity");
|
||||||
|
character.capacityMin = json_float(root, "capacityMin");
|
||||||
|
character.capacityMax = json_float(root, "capacityMax", character.capacity);
|
||||||
|
character.overstuffedMultiplier = json_float(root, "overstuffedMultiplier", 1);
|
||||||
|
character.overstuffedDigestBonus = json_float(root, "overstuffedDigestBonus");
|
||||||
|
character.caloriesToKilogram = json_float(root, "caloriesToKilogram");
|
||||||
|
character.digestionRate = json_float(root, "digestionRate") / TICK_RATE;
|
||||||
|
character.digestionRateMin = json_float(root, "digestionRateMin") / TICK_RATE;
|
||||||
|
character.digestionRateMax = json_float(root, "digestionRateMax", character.digestionRate * TICK_RATE) / TICK_RATE;
|
||||||
|
character.digestionTime = json_int(root, "digestionTime");
|
||||||
|
character.blinkChance = json_float(root, "blinkChance");
|
||||||
|
character.gurgleChance = json_float(root, "gurgleChance");
|
||||||
|
character.gurgleCapacityMultiplier = json_float(root, "gurgleCapacityMultiplier");
|
||||||
|
character.dialoguePool = json_string(root, "dialoguePool");
|
||||||
|
|
||||||
|
object alternate = json_object(root, "alternate");
|
||||||
|
if (!alternate["texture"].error())
|
||||||
|
character.alternateTexture = renderer.texture_get(character.folder + json_string(alternate, "texture"));
|
||||||
|
if (!alternate["sound"].error())
|
||||||
|
character.alternateSound = audio.sound_get(character.folder + json_string(alternate, "sound"));
|
||||||
|
character.alternateChance = json_float(alternate, "chance");
|
||||||
|
character.alternateSheet = json_int(alternate, "spritesheet");
|
||||||
|
|
||||||
|
object animations = json_object(root, "animations");
|
||||||
|
character.animationStart = json_string(animations, "start");
|
||||||
|
character.animationIdle = json_string(animations, "idle");
|
||||||
|
character.animationIdleFull = json_string(animations, "idleFull");
|
||||||
|
character.animationStageUp = json_string(animations, "stageUp");
|
||||||
|
|
||||||
|
Anm2* anm2 = character.anm2;
|
||||||
|
auto layer_get = [&](object source, const char* key) {
|
||||||
|
return anm2 ? anm2->layer_get(json_string(source, key)) : -1;
|
||||||
|
};
|
||||||
|
auto null_get = [&](object source, const char* key) {
|
||||||
|
return anm2 ? anm2->null_get(json_string(source, key)) : -1;
|
||||||
|
};
|
||||||
|
character.talkSource = layer_get(json_object(root, "talk"), "source");
|
||||||
|
character.talkDestination = layer_get(json_object(root, "talk"), "destination");
|
||||||
|
character.blinkSource = layer_get(json_object(root, "blink"), "source");
|
||||||
|
character.blinkDestination = layer_get(json_object(root, "blink"), "destination");
|
||||||
|
|
||||||
|
for (element entry : json_array(root, "eatAreas"))
|
||||||
|
{
|
||||||
|
object fields;
|
||||||
|
if (!entry.get(fields))
|
||||||
|
character.eatAreas.push_back({json_string(fields, "animation"),
|
||||||
|
anm2 ? anm2->event_get(json_string(fields, "event")) : -1,
|
||||||
|
null_get(fields, "null")});
|
||||||
|
}
|
||||||
|
for (element entry : json_array(root, "expandAreas"))
|
||||||
|
{
|
||||||
|
object fields;
|
||||||
|
if (!entry.get(fields))
|
||||||
|
character.expandAreas.push_back(
|
||||||
|
{json_float(fields, "scaleMax"), layer_get(fields, "layer"), null_get(fields, "null")});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> typeNames;
|
||||||
|
for (auto [name, value] : json_object(root, "interactTypes"))
|
||||||
|
{
|
||||||
|
object fields;
|
||||||
|
if (value.get(fields))
|
||||||
|
continue;
|
||||||
|
InteractArea& type = character.interactTypes.emplace_back();
|
||||||
|
interact_parse(fields, type, character, audio);
|
||||||
|
type.name = name;
|
||||||
|
type.type = (int)typeNames.size();
|
||||||
|
typeNames.emplace_back(name);
|
||||||
|
}
|
||||||
|
for (element entry : json_array(root, "interactAreas"))
|
||||||
|
{
|
||||||
|
object fields;
|
||||||
|
if (entry.get(fields))
|
||||||
|
continue;
|
||||||
|
auto type = std::find(typeNames.begin(), typeNames.end(), json_string(fields, "type"));
|
||||||
|
InteractArea area = type == typeNames.end() ? InteractArea{} : character.interactTypes[type - typeNames.begin()];
|
||||||
|
interact_parse(fields, area, character, audio);
|
||||||
|
if (area.null >= 0)
|
||||||
|
character.interactAreas.push_back(area);
|
||||||
|
}
|
||||||
|
|
||||||
|
object areas = json_object(root, "areas");
|
||||||
|
Anm2* areaAnm2 = character.anm2_get(json_string(areas, "anm2"), renderer, audio);
|
||||||
|
std::string boundsNull = json_string(areas, "boundsNull", "Bounds");
|
||||||
|
std::string spawnNull = json_string(areas, "spawnNull", boundsNull);
|
||||||
|
for (element entry : json_array(areas, "list"))
|
||||||
|
{
|
||||||
|
object fields;
|
||||||
|
if (entry.get(fields))
|
||||||
|
continue;
|
||||||
|
Area& area = character.areas.emplace_back();
|
||||||
|
area.anm2 = fields["anm2"].error() ? areaAnm2 : character.anm2_get(json_string(fields, "anm2"), renderer, audio);
|
||||||
|
area.gravity = json_float(fields, "gravity", area.gravity);
|
||||||
|
area.friction = json_float(fields, "friction", area.friction);
|
||||||
|
area.airResistance = json_float(fields, "airResistance", area.airResistance);
|
||||||
|
if (!area.anm2)
|
||||||
|
continue;
|
||||||
|
Actor actor(area.anm2);
|
||||||
|
area.bounds = actor.null_rect_get(area.anm2->null_get(boundsNull), -1, 0);
|
||||||
|
area.spawn = actor.null_rect_get(area.anm2->null_get(spawnNull), -1, 0);
|
||||||
|
area.spawn = is_rect_valid(area.spawn) ? area.spawn : area.bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
object cursor = json_object(root, "cursor");
|
||||||
|
character.cursor = character.anm2_get(json_string(cursor, "anm2"), renderer, audio);
|
||||||
|
object cursorAnimations = json_object(cursor, "animations");
|
||||||
|
character.cursorIdle = json_string(cursorAnimations, "idle");
|
||||||
|
character.cursorClick = json_string(cursorAnimations, "click");
|
||||||
|
character.cursorHover = json_string(cursorAnimations, "hover");
|
||||||
|
character.cursorGrab = json_string(cursorAnimations, "grab");
|
||||||
|
character.cursorPan = json_string(cursorAnimations, "pan");
|
||||||
|
character.cursorZoom = json_string(cursorAnimations, "zoom");
|
||||||
|
character.cursorDispose = json_string(cursorAnimations, "dispose");
|
||||||
|
character.grabParticles = json_strings(json_object(cursor, "particles"), "grab");
|
||||||
|
|
||||||
|
object menu = json_object(root, "menu");
|
||||||
|
ImFontAtlas* fonts = ImGui::GetIO().Fonts;
|
||||||
|
if (!menu["font"].error())
|
||||||
|
character.font = fonts->AddFontFromFileTTF((character.folder + json_string(menu, "font")).c_str(), FONT_SIZE);
|
||||||
|
if (!menu["iconsFont"].error())
|
||||||
|
character.iconsFont =
|
||||||
|
fonts->AddFontFromFileTTF((character.folder + json_string(menu, "iconsFont")).c_str(), FONT_SIZE);
|
||||||
|
character.rounding = json_float(menu, "rounding");
|
||||||
|
character.homeGlyph = json_string(menu, "homeGlyph");
|
||||||
|
|
||||||
|
for (auto [name, value] : json_object(root, "particles"))
|
||||||
|
{
|
||||||
|
object fields;
|
||||||
|
if (!value.get(fields))
|
||||||
|
character.particles[std::string(name)] = {
|
||||||
|
json_string(fields, "animation"), character.anm2_get(json_string(fields, "anm2"), renderer, audio)};
|
||||||
|
}
|
||||||
|
|
||||||
|
object strings = json_object(root, "strings");
|
||||||
|
for (int i = 0; i < STRING_COUNT; i++)
|
||||||
|
character.strings[i] = json_string(strings, STRING_NAMES[i], STRING_DEFAULTS[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void items_parse(object root, Character& character, Renderer& renderer, Audio& audio)
|
||||||
|
{
|
||||||
|
Anm2* baseAnm2 = character.anm2_get(json_string(root, "anm2"), renderer, audio);
|
||||||
|
character.itemSheet = json_int(root, "spritesheet");
|
||||||
|
character.spawnDelay = json_int(root, "spawnDelay", character.spawnDelay);
|
||||||
|
character.spawnDelayMultiplier = json_float(root, "spawnDelayMultiplier", character.spawnDelayMultiplier);
|
||||||
|
character.spawnLimit = json_int(root, "spawnLimit", character.spawnLimit);
|
||||||
|
object particles = json_object(root, "particles");
|
||||||
|
character.disposeParticles = json_strings(particles, "dispose");
|
||||||
|
character.summonParticles = json_strings(particles, "summon");
|
||||||
|
|
||||||
|
object categories = json_object(root, "categories");
|
||||||
|
std::vector<std::string> rarityNames;
|
||||||
|
for (auto [name, value] : json_object(root, "rarities"))
|
||||||
|
{
|
||||||
|
object fields;
|
||||||
|
if (value.get(fields))
|
||||||
|
continue;
|
||||||
|
std::string sound = json_string(fields, "sound");
|
||||||
|
character.rarities.push_back({std::string(name), sound.empty() ? nullptr : audio.sound_get(character.folder + sound),
|
||||||
|
json_float(fields, "weight")});
|
||||||
|
rarityNames.emplace_back(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (element entry : json_array(root, "items"))
|
||||||
|
{
|
||||||
|
object fields;
|
||||||
|
if (entry.get(fields))
|
||||||
|
continue;
|
||||||
|
Item& item = character.items.emplace_back();
|
||||||
|
item.name = json_string(fields, "name", "???");
|
||||||
|
item.description = json_string(fields, "description", "???");
|
||||||
|
item.category = json_string(fields, "category");
|
||||||
|
item.flavor = json_string(fields, "flavor");
|
||||||
|
std::string use = json_string(categories, item.category.c_str());
|
||||||
|
for (int i = 0; i < (int)std::size(USE_NAMES); i++)
|
||||||
|
if (use == USE_NAMES[i])
|
||||||
|
item.use = (ItemUse)i;
|
||||||
|
auto rarity = std::find(rarityNames.begin(), rarityNames.end(), json_string(fields, "rarity"));
|
||||||
|
item.rarity = rarity == rarityNames.end() ? -1 : (int)(rarity - rarityNames.begin());
|
||||||
|
item.anm2 = fields["anm2"].error() ? baseAnm2 : character.anm2_get(json_string(fields, "anm2"), renderer, audio);
|
||||||
|
if (!fields["texture"].error())
|
||||||
|
item.texture = renderer.texture_get(character.folder + json_string(fields, "texture"));
|
||||||
|
item.color = json_color(fields, "color", CLEAR);
|
||||||
|
item.calories = json_float(fields, "calories");
|
||||||
|
item.capacityBonus = json_float(fields, "capacityBonus");
|
||||||
|
item.digestionBonus = json_float(fields, "digestionBonus") / TICK_RATE;
|
||||||
|
item.gravity = json_float(fields, "gravity");
|
||||||
|
item.isToggleSpritesheet = json_is(fields, "isToggleSpritesheet");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dialogue_parse(object root, Character& character, Audio& audio)
|
||||||
|
{
|
||||||
|
Dialogue& dialogue = character.dialogue;
|
||||||
|
dialogue.delay = std::max(1, json_int(root, "delay", dialogue.delay));
|
||||||
|
dialogue.blipDelay = json_int(root, "blipDelay");
|
||||||
|
dialogue.blip = audio.sounds_get(character.folder, json_strings(root, "blip"));
|
||||||
|
for (auto [letter, value] : json_object(root, "pauses"))
|
||||||
|
{
|
||||||
|
object fields;
|
||||||
|
if (!value.get(fields))
|
||||||
|
dialogue.pauses[std::string(letter)] = {json_int(fields, "delay"), json_is(fields, "isSilent")};
|
||||||
|
}
|
||||||
|
for (auto [hook, value] : json_object(root, "hooks"))
|
||||||
|
{
|
||||||
|
std::string_view target;
|
||||||
|
if (!value.get(target))
|
||||||
|
dialogue.hooks[std::string(hook)] = target;
|
||||||
|
}
|
||||||
|
for (auto [pool, value] : json_object(root, "pools"))
|
||||||
|
{
|
||||||
|
array list;
|
||||||
|
if (value.get(list))
|
||||||
|
continue;
|
||||||
|
std::vector<std::string>& labels = dialogue.pools[std::string(pool)];
|
||||||
|
for (element label : list)
|
||||||
|
{
|
||||||
|
std::string_view text;
|
||||||
|
if (!label.get(text))
|
||||||
|
labels.emplace_back(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto [label, value] : json_object(root, "entries"))
|
||||||
|
{
|
||||||
|
object fields;
|
||||||
|
if (value.get(fields))
|
||||||
|
continue;
|
||||||
|
Entry& entry = dialogue.entries[std::string(label)];
|
||||||
|
entry.text = json_string(fields, "text");
|
||||||
|
entry.animation = json_string(fields, "animation");
|
||||||
|
entry.next = json_string(fields, "next");
|
||||||
|
entry.length = utf8_length_get(entry.text);
|
||||||
|
for (element choiceEntry : json_array(fields, "choices"))
|
||||||
|
{
|
||||||
|
object choice;
|
||||||
|
if (!choiceEntry.get(choice))
|
||||||
|
entry.choices.push_back({json_string(choice, "next"), json_string(choice, "text", "<choice>")});
|
||||||
|
}
|
||||||
|
for (element effectEntry : json_array(fields, "effects"))
|
||||||
|
{
|
||||||
|
object source;
|
||||||
|
if (effectEntry.get(source))
|
||||||
|
continue;
|
||||||
|
Effect& effect = entry.effects.emplace_back();
|
||||||
|
std::string type = json_string(source, "type");
|
||||||
|
for (int i = 0; i < EFFECT_COUNT; i++)
|
||||||
|
if (type == EFFECT_NAMES[i])
|
||||||
|
effect.type = (EffectType)i;
|
||||||
|
effect.start = json_int(source, "start");
|
||||||
|
effect.end = json_int(source, "end");
|
||||||
|
effect.delay = json_int(source, "delay");
|
||||||
|
effect.color = json_color(source, "color");
|
||||||
|
effect.animation = json_string(source, "animation");
|
||||||
|
if (!source["sound"].error())
|
||||||
|
effect.sound = audio.sound_get(character.folder + json_string(source, "sound"));
|
||||||
|
entry.isScene |= effect.type == EFFECT_SCENE;
|
||||||
|
}
|
||||||
|
entry.isTerminal = entry.next.empty() && entry.choices.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sounds_parse(object root, DataFile file, Character& character, Audio& audio)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < SOUND_COUNT; i++)
|
||||||
|
{
|
||||||
|
const SoundSource& source = SOUND_SOURCES[i];
|
||||||
|
object section = *source.section ? json_object(root, source.section) : root;
|
||||||
|
if (source.file == file)
|
||||||
|
character.sounds[i] = audio.sounds_get(character.folder, json_strings(json_object(section, "sounds"), source.key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Character::load(Renderer& renderer, Audio& audio)
|
||||||
|
{
|
||||||
|
if (isLoaded)
|
||||||
|
return;
|
||||||
|
isLoaded = true;
|
||||||
|
Json json;
|
||||||
|
json.load(folder + "character.json");
|
||||||
|
if (json.isValid)
|
||||||
|
{
|
||||||
|
character_parse(json.root, *this, renderer, audio);
|
||||||
|
sounds_parse(json.root, FILE_CHARACTER, *this, audio);
|
||||||
|
}
|
||||||
|
json.load(folder + "items.json");
|
||||||
|
if (json.isValid)
|
||||||
|
{
|
||||||
|
items_parse(json.root, *this, renderer, audio);
|
||||||
|
sounds_parse(json.root, FILE_ITEMS, *this, audio);
|
||||||
|
}
|
||||||
|
json.load(folder + "dialogue.json");
|
||||||
|
if (json.isValid)
|
||||||
|
dialogue_parse(json.root, *this, audio);
|
||||||
|
}
|
||||||
|
|
||||||
|
// An actor for an item, showing its texture in place of its anm2's item spritesheet.
|
||||||
|
Actor Character::item_actor_get(int item)
|
||||||
|
{
|
||||||
|
Actor actor(items[item].anm2);
|
||||||
|
actor.isStatic = true;
|
||||||
|
if (items[item].texture && itemSheet >= 0 && itemSheet < (int)actor.sheets.size())
|
||||||
|
actor.sheets[itemSheet] = items[item].texture;
|
||||||
|
return actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The area a stage plays in: the last one a stage up to it names, or the first.
|
||||||
|
int Character::area_get(int stage)
|
||||||
|
{
|
||||||
|
if (areas.empty())
|
||||||
|
return -1;
|
||||||
|
int area = 0;
|
||||||
|
for (int i = 0; i < stage && i < (int)stages.size(); i++)
|
||||||
|
if (stages[i].area >= 0 && stages[i].area < (int)areas.size())
|
||||||
|
area = stages[i].area;
|
||||||
|
return area;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Character::save_path_get()
|
||||||
|
{
|
||||||
|
return pref_path_get("saves/" + key + ".txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every folder in resources/characters holding a character.json, in name order, with its preview loaded.
|
||||||
|
std::vector<Character> characters_load(Renderer& renderer)
|
||||||
|
{
|
||||||
|
std::string folder = path_get("characters/");
|
||||||
|
int count = 0;
|
||||||
|
char** names = SDL_GlobDirectory(folder.c_str(), "*", 0, &count);
|
||||||
|
std::vector<std::string> sorted(names, names + (names ? count : 0));
|
||||||
|
SDL_free(names);
|
||||||
|
std::sort(sorted.begin(), sorted.end());
|
||||||
|
std::vector<Character> characters;
|
||||||
|
characters.reserve(sorted.size());
|
||||||
|
for (const std::string& name : sorted)
|
||||||
|
{
|
||||||
|
characters.emplace_back().preview_load(folder + name + "/", renderer);
|
||||||
|
if (!characters.back().isValid)
|
||||||
|
characters.pop_back();
|
||||||
|
}
|
||||||
|
return characters;
|
||||||
|
}
|
||||||
+293
@@ -0,0 +1,293 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "anm2.h"
|
||||||
|
#include "strings.h"
|
||||||
|
#include <array>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
struct ImFont;
|
||||||
|
|
||||||
|
enum DataFile
|
||||||
|
{
|
||||||
|
FILE_CHARACTER,
|
||||||
|
FILE_ITEMS
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sounds a character plays, by the file, section (or the root, when empty) and key of the "sounds" holding them.
|
||||||
|
#define SOUND_IDS(X) \
|
||||||
|
X(DIGEST, FILE_CHARACTER, "", "digest") \
|
||||||
|
X(GURGLE, FILE_CHARACTER, "", "gurgle") \
|
||||||
|
X(GRAB, FILE_CHARACTER, "cursor", "grab") \
|
||||||
|
X(RELEASE, FILE_CHARACTER, "cursor", "release") \
|
||||||
|
X(THROW, FILE_CHARACTER, "cursor", "throw") \
|
||||||
|
X(OPEN, FILE_CHARACTER, "menu", "open") \
|
||||||
|
X(CLOSE, FILE_CHARACTER, "menu", "close") \
|
||||||
|
X(HOVER, FILE_CHARACTER, "menu", "hover") \
|
||||||
|
X(SELECT, FILE_CHARACTER, "menu", "select") \
|
||||||
|
X(CHEATS, FILE_CHARACTER, "menu", "cheats") \
|
||||||
|
X(BOUNCE, FILE_ITEMS, "", "bounce") \
|
||||||
|
X(DISPOSE, FILE_ITEMS, "", "dispose") \
|
||||||
|
X(SUMMON, FILE_ITEMS, "", "summon") \
|
||||||
|
X(NEW_ITEM, FILE_ITEMS, "", "newItem")
|
||||||
|
|
||||||
|
enum SoundId
|
||||||
|
{
|
||||||
|
#define X(id, file, section, key) SOUND_##id,
|
||||||
|
SOUND_IDS(X)
|
||||||
|
#undef X
|
||||||
|
SOUND_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Stage
|
||||||
|
{
|
||||||
|
std::string animationSuffix;
|
||||||
|
std::string dialoguePool;
|
||||||
|
float weight = 0;
|
||||||
|
int area = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A spot on the character the cursor acts on, starting from its type's settings. Types are kept the same way, for
|
||||||
|
// their tools and defaults.
|
||||||
|
struct InteractArea
|
||||||
|
{
|
||||||
|
std::string animation;
|
||||||
|
std::string cursorActive;
|
||||||
|
std::string cursorHover;
|
||||||
|
std::string dialoguePool;
|
||||||
|
std::string dialoguePoolFull;
|
||||||
|
std::string glyph;
|
||||||
|
std::string idleOnEnd;
|
||||||
|
std::string name;
|
||||||
|
std::vector<std::string> particles;
|
||||||
|
Sounds sounds;
|
||||||
|
float digestionClick = 0;
|
||||||
|
float digestionHover = 0;
|
||||||
|
float scaleAmplitude = 0;
|
||||||
|
float scaleCycles = 1;
|
||||||
|
float scaleTime = 20;
|
||||||
|
int dialogueCooldown = 0;
|
||||||
|
int layer = -1;
|
||||||
|
int null = -1;
|
||||||
|
int type = -1;
|
||||||
|
bool isHold = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EatArea
|
||||||
|
{
|
||||||
|
std::string animation;
|
||||||
|
int event = -1;
|
||||||
|
int null = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ExpandArea
|
||||||
|
{
|
||||||
|
float scaleMax = 0;
|
||||||
|
int layer = -1;
|
||||||
|
int null = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A background, with the world's bounds and where items spawn taken from its nulls.
|
||||||
|
struct Area
|
||||||
|
{
|
||||||
|
Anm2* anm2 = nullptr;
|
||||||
|
Rect bounds;
|
||||||
|
Rect spawn;
|
||||||
|
float airResistance = 0.975f;
|
||||||
|
float friction = 0.8f;
|
||||||
|
float gravity = 0.95f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Particle
|
||||||
|
{
|
||||||
|
std::string animation;
|
||||||
|
Anm2* anm2 = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ItemUse
|
||||||
|
{
|
||||||
|
USE_NONE,
|
||||||
|
USE_EDIBLE,
|
||||||
|
USE_TOOL
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Rarity
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
Sound* sound = nullptr;
|
||||||
|
float weight = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Item
|
||||||
|
{
|
||||||
|
std::string category;
|
||||||
|
std::string description;
|
||||||
|
std::string flavor;
|
||||||
|
std::string name;
|
||||||
|
Anm2* anm2 = nullptr;
|
||||||
|
Texture* texture = nullptr;
|
||||||
|
Color color = CLEAR;
|
||||||
|
float calories = 0;
|
||||||
|
float capacityBonus = 0;
|
||||||
|
float digestionBonus = 0;
|
||||||
|
float gravity = 0;
|
||||||
|
int rarity = -1;
|
||||||
|
ItemUse use = USE_NONE;
|
||||||
|
bool isToggleSpritesheet = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EffectType
|
||||||
|
{
|
||||||
|
EFFECT_ANIMATION,
|
||||||
|
EFFECT_BIG,
|
||||||
|
EFFECT_COLOR,
|
||||||
|
EFFECT_DELAY,
|
||||||
|
EFFECT_OSCILLATION,
|
||||||
|
EFFECT_SCENE,
|
||||||
|
EFFECT_SCREENSHAKE,
|
||||||
|
EFFECT_SHAKE,
|
||||||
|
EFFECT_SILENT,
|
||||||
|
EFFECT_SMALL,
|
||||||
|
EFFECT_SOUND,
|
||||||
|
EFFECT_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
// Something a dialogue line does over its characters, from start up to (not including) end.
|
||||||
|
struct Effect
|
||||||
|
{
|
||||||
|
std::string animation;
|
||||||
|
Color color;
|
||||||
|
Sound* sound = nullptr;
|
||||||
|
EffectType type = EFFECT_COUNT;
|
||||||
|
int delay = 0;
|
||||||
|
int end = 0;
|
||||||
|
int start = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Choice
|
||||||
|
{
|
||||||
|
std::string next;
|
||||||
|
std::string text;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Entry
|
||||||
|
{
|
||||||
|
std::string animation;
|
||||||
|
std::string next;
|
||||||
|
std::string text;
|
||||||
|
std::vector<Choice> choices;
|
||||||
|
std::vector<Effect> effects;
|
||||||
|
int length = 0;
|
||||||
|
bool isScene = false;
|
||||||
|
bool isTerminal = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Pause
|
||||||
|
{
|
||||||
|
int delay = 0;
|
||||||
|
bool isSilent = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dialogue entries by label, pools of them picked from at random, and hooks naming the pool (or else entry) the game
|
||||||
|
// plays on an event.
|
||||||
|
struct Dialogue
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, Entry> entries;
|
||||||
|
std::unordered_map<std::string, Pause> pauses;
|
||||||
|
std::unordered_map<std::string, std::string> hooks;
|
||||||
|
std::unordered_map<std::string, std::vector<std::string>> pools;
|
||||||
|
Sounds blip;
|
||||||
|
int blipDelay = 0;
|
||||||
|
int delay = 2;
|
||||||
|
|
||||||
|
Entry* entry_get(const std::string& label);
|
||||||
|
Entry* pool_get(const std::string& pool);
|
||||||
|
Entry* hook_get(const std::string& hook);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Everything a character folder holds. The preview (what the select screen shows) loads at startup, the rest when
|
||||||
|
// played.
|
||||||
|
class Character
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::array<Sounds, SOUND_COUNT> sounds;
|
||||||
|
std::array<std::string, STRING_COUNT> strings;
|
||||||
|
std::string animationIdle;
|
||||||
|
std::string animationIdleFull;
|
||||||
|
std::string animationStageUp;
|
||||||
|
std::string animationStart;
|
||||||
|
std::string anm2Path;
|
||||||
|
std::string credits;
|
||||||
|
std::string cursorClick;
|
||||||
|
std::string cursorDispose;
|
||||||
|
std::string cursorGrab;
|
||||||
|
std::string cursorHover;
|
||||||
|
std::string cursorIdle;
|
||||||
|
std::string cursorPan;
|
||||||
|
std::string cursorZoom;
|
||||||
|
std::string description;
|
||||||
|
std::string dialoguePool;
|
||||||
|
std::string folder;
|
||||||
|
std::string homeGlyph;
|
||||||
|
std::string key;
|
||||||
|
std::string name;
|
||||||
|
std::unordered_map<std::string, Anm2> anm2s;
|
||||||
|
std::unordered_map<std::string, Particle> particles;
|
||||||
|
std::vector<std::string> disposeParticles;
|
||||||
|
std::vector<std::string> grabParticles;
|
||||||
|
std::vector<std::string> summonParticles;
|
||||||
|
std::vector<Area> areas;
|
||||||
|
std::vector<EatArea> eatAreas;
|
||||||
|
std::vector<ExpandArea> expandAreas;
|
||||||
|
std::vector<InteractArea> interactAreas;
|
||||||
|
std::vector<InteractArea> interactTypes;
|
||||||
|
std::vector<Item> items;
|
||||||
|
std::vector<Rarity> rarities;
|
||||||
|
std::vector<Stage> stages;
|
||||||
|
Dialogue dialogue;
|
||||||
|
Anm2* anm2 = nullptr;
|
||||||
|
Anm2* cursor = nullptr;
|
||||||
|
ImFont* font = nullptr;
|
||||||
|
ImFont* iconsFont = nullptr;
|
||||||
|
Sound* alternateSound = nullptr;
|
||||||
|
Texture* alternateTexture = nullptr;
|
||||||
|
Texture* portrait = nullptr;
|
||||||
|
Texture* render = nullptr;
|
||||||
|
Color color = {0.5f, 0.5f, 0.5f, 1};
|
||||||
|
float alternateChance = 0;
|
||||||
|
float blinkChance = 0;
|
||||||
|
float caloriesToKilogram = 0;
|
||||||
|
float capacity = 0;
|
||||||
|
float capacityMax = 0;
|
||||||
|
float capacityMin = 0;
|
||||||
|
float digestionRate = 0;
|
||||||
|
float digestionRateMax = 0;
|
||||||
|
float digestionRateMin = 0;
|
||||||
|
float gurgleCapacityMultiplier = 0;
|
||||||
|
float gurgleChance = 0;
|
||||||
|
float overstuffedDigestBonus = 0;
|
||||||
|
float overstuffedMultiplier = 1;
|
||||||
|
float rounding = 0;
|
||||||
|
float spawnDelayMultiplier = 1;
|
||||||
|
float weight = 0;
|
||||||
|
float weightMax = 0;
|
||||||
|
int alternateSheet = 0;
|
||||||
|
int blinkDestination = -1;
|
||||||
|
int blinkSource = -1;
|
||||||
|
int digestionTime = 0;
|
||||||
|
int itemSheet = 0;
|
||||||
|
int spawnDelay = 60;
|
||||||
|
int spawnLimit = 100;
|
||||||
|
int talkDestination = -1;
|
||||||
|
int talkSource = -1;
|
||||||
|
bool isLoaded = false;
|
||||||
|
bool isValid = false;
|
||||||
|
|
||||||
|
void preview_load(const std::string& path, Renderer& renderer);
|
||||||
|
void load(Renderer& renderer, Audio& audio);
|
||||||
|
Anm2* anm2_get(const std::string& path, Renderer& renderer, Audio& audio);
|
||||||
|
Actor item_actor_get(int item);
|
||||||
|
int area_get(int stage);
|
||||||
|
std::string save_path_get();
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Character> characters_load(Renderer& renderer);
|
||||||
+200
@@ -0,0 +1,200 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL_filesystem.h>
|
||||||
|
#include <SDL3/SDL_log.h>
|
||||||
|
#include <SDL3/SDL_stdinc.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
constexpr const char* GAME_ID = "shweets-sim";
|
||||||
|
constexpr const char* GAME_NAME = "Shweet's Sim";
|
||||||
|
constexpr float DEGREES = 180;
|
||||||
|
constexpr float PERCENT = 100;
|
||||||
|
constexpr float PI = 3.14159265f;
|
||||||
|
constexpr float TICK_RATE = 60;
|
||||||
|
|
||||||
|
struct Vec2
|
||||||
|
{
|
||||||
|
float x = 0;
|
||||||
|
float y = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Rect
|
||||||
|
{
|
||||||
|
float x = 0;
|
||||||
|
float y = 0;
|
||||||
|
float w = 0;
|
||||||
|
float h = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Color
|
||||||
|
{
|
||||||
|
float r = 1;
|
||||||
|
float g = 1;
|
||||||
|
float b = 1;
|
||||||
|
float a = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr Color WHITE = {1, 1, 1, 1};
|
||||||
|
constexpr Color CLEAR = {0, 0, 0, 0};
|
||||||
|
constexpr Color GRAY = {0.5f, 0.5f, 0.5f, 1};
|
||||||
|
|
||||||
|
inline Vec2 operator+(Vec2 a, Vec2 b)
|
||||||
|
{
|
||||||
|
return {a.x + b.x, a.y + b.y};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vec2 operator-(Vec2 a, Vec2 b)
|
||||||
|
{
|
||||||
|
return {a.x - b.x, a.y - b.y};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vec2 operator*(Vec2 a, Vec2 b)
|
||||||
|
{
|
||||||
|
return {a.x * b.x, a.y * b.y};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vec2 operator*(Vec2 v, float scale)
|
||||||
|
{
|
||||||
|
return {v.x * scale, v.y * scale};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vec2 operator/(Vec2 v, float scale)
|
||||||
|
{
|
||||||
|
return {v.x / scale, v.y / scale};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Color operator*(Color a, Color b)
|
||||||
|
{
|
||||||
|
return {a.r * b.r, a.g * b.g, a.b * b.b, a.a * b.a};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Color operator+(Color a, Color b)
|
||||||
|
{
|
||||||
|
return {a.r + b.r, a.g + b.g, a.b + b.b, a.a + b.a};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float vec2_length(Vec2 v)
|
||||||
|
{
|
||||||
|
return std::sqrt(v.x * v.x + v.y * v.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vec2 vec2_lerp(Vec2 from, Vec2 to, float t)
|
||||||
|
{
|
||||||
|
return {std::lerp(from.x, to.x, t), std::lerp(from.y, to.y, t)};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Color color_lerp(Color from, Color to, float t)
|
||||||
|
{
|
||||||
|
return {std::lerp(from.r, to.r, t), std::lerp(from.g, to.g, t), std::lerp(from.b, to.b, t),
|
||||||
|
std::lerp(from.a, to.a, t)};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Rect rect_center(Rect rect, float padding)
|
||||||
|
{
|
||||||
|
return {rect.x - padding / 2, rect.y - padding / 2, rect.w + padding, rect.h + padding};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vec2 rect_middle(Rect rect)
|
||||||
|
{
|
||||||
|
return {rect.x + rect.w / 2, rect.y + rect.h / 2};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool is_rect_valid(Rect rect)
|
||||||
|
{
|
||||||
|
return std::isfinite(rect.x) && std::isfinite(rect.y) && std::isfinite(rect.w) && std::isfinite(rect.h) &&
|
||||||
|
rect.w > 0 && rect.h > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool is_point_in_rect(Rect rect, Vec2 point)
|
||||||
|
{
|
||||||
|
return point.x >= rect.x && point.y >= rect.y && point.x < rect.x + rect.w && point.y < rect.y + rect.h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A 2D affine transform taking x to (a·x + c·y + x0, b·x + d·y + y0).
|
||||||
|
struct Affine
|
||||||
|
{
|
||||||
|
float a = 1;
|
||||||
|
float b = 0;
|
||||||
|
float c = 0;
|
||||||
|
float d = 1;
|
||||||
|
float x0 = 0;
|
||||||
|
float y0 = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline Affine operator*(Affine m, Affine n)
|
||||||
|
{
|
||||||
|
return {m.a * n.a + m.c * n.b, m.b * n.a + m.d * n.b, m.a * n.c + m.c * n.d,
|
||||||
|
m.b * n.c + m.d * n.d, m.a * n.x0 + m.c * n.y0 + m.x0, m.b * n.x0 + m.d * n.y0 + m.y0};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vec2 operator*(Affine m, Vec2 v)
|
||||||
|
{
|
||||||
|
return {m.a * v.x + m.c * v.y + m.x0, m.b * v.x + m.d * v.y + m.y0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Moves to position, flips by the scale's signs, turns (clockwise, in degrees, the other way when one axis flips),
|
||||||
|
// scales by the scale's size (a fraction) and puts pivot at the origin.
|
||||||
|
inline Affine affine_get(Vec2 position, float rotation, Vec2 scale, Vec2 pivot = {})
|
||||||
|
{
|
||||||
|
float flipX = scale.x < 0 ? -1 : 1;
|
||||||
|
float flipY = scale.y < 0 ? -1 : 1;
|
||||||
|
float angle = rotation * PI / DEGREES * flipX * flipY;
|
||||||
|
float cosine = std::cos(angle);
|
||||||
|
float sine = std::sin(angle);
|
||||||
|
float scaleX = std::abs(scale.x);
|
||||||
|
float scaleY = std::abs(scale.y);
|
||||||
|
Affine m = {flipX * cosine * scaleX, flipY * sine * scaleX, -flipX * sine * scaleY, flipY * cosine * scaleY,
|
||||||
|
position.x, position.y};
|
||||||
|
return m * Affine{1, 0, 0, 1, -pivot.x, -pivot.y};
|
||||||
|
}
|
||||||
|
|
||||||
|
// The smallest rectangle holding a w by h box once transformed.
|
||||||
|
inline Rect affine_bounds(Affine m, float w, float h)
|
||||||
|
{
|
||||||
|
Vec2 corners[] = {m * Vec2{0, 0}, m * Vec2{w, 0}, m * Vec2{w, h}, m * Vec2{0, h}};
|
||||||
|
Vec2 low = corners[0];
|
||||||
|
Vec2 high = corners[0];
|
||||||
|
for (Vec2 corner : corners)
|
||||||
|
{
|
||||||
|
low = {std::min(low.x, corner.x), std::min(low.y, corner.y)};
|
||||||
|
high = {std::max(high.x, corner.x), std::max(high.y, corner.y)};
|
||||||
|
}
|
||||||
|
return {low.x, low.y, high.x - low.x, high.y - low.y};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Rect rect_merge(Rect a, Rect b)
|
||||||
|
{
|
||||||
|
if (!is_rect_valid(a))
|
||||||
|
return b;
|
||||||
|
float x = std::min(a.x, b.x);
|
||||||
|
float y = std::min(a.y, b.y);
|
||||||
|
return {x, y, std::max(a.x + a.w, b.x + b.w) - x, std::max(a.y + a.h, b.y + b.h) - y};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float random_get(float min, float max)
|
||||||
|
{
|
||||||
|
return min + SDL_randf() * (max - min);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether a roll succeeds, with a chance given in percent.
|
||||||
|
inline bool is_chance(float percent)
|
||||||
|
{
|
||||||
|
return SDL_randf() * PERCENT < percent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Absolute path to a file inside the resources folder next to the executable.
|
||||||
|
inline std::string path_get(const std::string& relative)
|
||||||
|
{
|
||||||
|
return std::string(SDL_GetBasePath()) + "resources/" + relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Absolute path to a file inside the user's save folder.
|
||||||
|
inline std::string pref_path_get(const std::string& relative)
|
||||||
|
{
|
||||||
|
char* folder = SDL_GetPrefPath("", GAME_ID);
|
||||||
|
std::string path = std::string(folder ? folder : "") + relative;
|
||||||
|
SDL_free(folder);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
+185
@@ -0,0 +1,185 @@
|
|||||||
|
#include "console.h"
|
||||||
|
#include "ui.h"
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
constexpr float CONSOLE_HEIGHT = 0.4f;
|
||||||
|
constexpr int INPUT_SIZE = 256;
|
||||||
|
|
||||||
|
struct Command
|
||||||
|
{
|
||||||
|
const char* name;
|
||||||
|
const char* usage;
|
||||||
|
bool isPlayOnly;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr Command COMMANDS[] = {
|
||||||
|
{"animations", "animations", true},
|
||||||
|
{"capacity", "capacity <calories>", true},
|
||||||
|
{"character", "character <name>", false},
|
||||||
|
{"characters", "characters", false},
|
||||||
|
{"dialogue", "dialogue", true},
|
||||||
|
{"digest", "digest", true},
|
||||||
|
{"digestion", "digestion <% per second>", true},
|
||||||
|
{"help", "help", false},
|
||||||
|
{"items", "items", true},
|
||||||
|
{"menu", "menu", true},
|
||||||
|
{"new_game", "new_game <name>", false},
|
||||||
|
{"play_animation", "play_animation <animation>", true},
|
||||||
|
{"play_dialogue", "play_dialogue <label>", true},
|
||||||
|
{"reload", "reload", false},
|
||||||
|
{"show_layers", "show_layers [true/false]", true},
|
||||||
|
{"show_nulls", "show_nulls [true/false]", true},
|
||||||
|
{"spawn", "spawn <item_id>", true},
|
||||||
|
{"stage", "stage <stage>", true},
|
||||||
|
{"weight", "weight <kilograms>", true},
|
||||||
|
};
|
||||||
|
|
||||||
|
static int history_callback(ImGuiInputTextCallbackData* data)
|
||||||
|
{
|
||||||
|
Console& console = *(Console*)data->UserData;
|
||||||
|
if (console.inputs.empty())
|
||||||
|
return 0;
|
||||||
|
int count = (int)console.inputs.size();
|
||||||
|
int index = console.inputIndex < 0 ? count : console.inputIndex;
|
||||||
|
index = std::clamp(index + (data->EventKey == ImGuiKey_UpArrow ? -1 : 1), 0, count);
|
||||||
|
console.inputIndex = index;
|
||||||
|
data->DeleteChars(0, data->BufTextLen);
|
||||||
|
data->InsertChars(0, index < count ? console.inputs[index].c_str() : "");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A toggle's new state: what its argument says, or the opposite of now without one.
|
||||||
|
static bool is_toggled(const std::string& argument, bool value)
|
||||||
|
{
|
||||||
|
return argument.empty() ? !value : argument == "true" || argument == "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
void Console::execute(Play* play, std::vector<Character>& characters)
|
||||||
|
{
|
||||||
|
std::istringstream words(input);
|
||||||
|
std::string name;
|
||||||
|
std::string argument;
|
||||||
|
words >> name >> argument;
|
||||||
|
if (name.empty())
|
||||||
|
return;
|
||||||
|
if (inputs.empty() || inputs.back() != input)
|
||||||
|
inputs.push_back(input);
|
||||||
|
inputIndex = -1;
|
||||||
|
history.push_back("> " + input);
|
||||||
|
auto command = std::find_if(std::begin(COMMANDS), std::end(COMMANDS), [&](const Command& entry) {
|
||||||
|
return name == entry.name;
|
||||||
|
});
|
||||||
|
if (command == std::end(COMMANDS))
|
||||||
|
return history.push_back("Unknown command: " + name);
|
||||||
|
if (command->isPlayOnly && !play)
|
||||||
|
return history.push_back("Command unavailable outside game: " + name);
|
||||||
|
float number = (float)SDL_atof(argument.c_str());
|
||||||
|
Progress* progress = play ? &play->progress : nullptr;
|
||||||
|
|
||||||
|
if (name == "animations")
|
||||||
|
{
|
||||||
|
Anm2* anm2 = play->actor.anm2;
|
||||||
|
for (int i = 0; anm2 && i < (int)anm2->animations.size(); i++)
|
||||||
|
history.push_back(" " + std::to_string(i) + " " + anm2->animations[i].name);
|
||||||
|
}
|
||||||
|
else if (name == "capacity")
|
||||||
|
progress->capacity = number;
|
||||||
|
else if (name == "character" || name == "new_game")
|
||||||
|
{
|
||||||
|
action = name == "character" ? ACTION_CHARACTER : ACTION_NEW_GAME;
|
||||||
|
target = argument;
|
||||||
|
}
|
||||||
|
else if (name == "characters")
|
||||||
|
{
|
||||||
|
for (Character& character : characters)
|
||||||
|
history.push_back(" " + character.key);
|
||||||
|
}
|
||||||
|
else if (name == "dialogue")
|
||||||
|
{
|
||||||
|
for (auto& [label, entry] : play->character->dialogue.entries)
|
||||||
|
history.push_back(" " + label);
|
||||||
|
}
|
||||||
|
else if (name == "digest")
|
||||||
|
{
|
||||||
|
progress->isDigesting = true;
|
||||||
|
progress->digestionTimer = 1;
|
||||||
|
}
|
||||||
|
else if (name == "digestion")
|
||||||
|
progress->digestionRate = number / TICK_RATE;
|
||||||
|
else if (name == "help")
|
||||||
|
{
|
||||||
|
for (const Command& entry : COMMANDS)
|
||||||
|
if (play || !entry.isPlayOnly)
|
||||||
|
history.push_back(std::string(" ") + entry.usage);
|
||||||
|
}
|
||||||
|
else if (name == "items")
|
||||||
|
{
|
||||||
|
for (int i = 0; i < (int)play->character->items.size(); i++)
|
||||||
|
history.push_back(" " + std::to_string(i) + " " + play->character->items[i].name);
|
||||||
|
}
|
||||||
|
else if (name == "menu")
|
||||||
|
action = ACTION_MENU;
|
||||||
|
else if (name == "play_animation")
|
||||||
|
play->actor.play(argument, TRANSITION_FORCE, INTERRUPT_ALWAYS);
|
||||||
|
else if (name == "play_dialogue")
|
||||||
|
play->text.set(*play, play->character->dialogue.entry_get(argument));
|
||||||
|
else if (name == "reload")
|
||||||
|
action = ACTION_RELOAD;
|
||||||
|
else if (name == "show_layers")
|
||||||
|
Actor::isShowLayers = is_toggled(argument, Actor::isShowLayers);
|
||||||
|
else if (name == "show_nulls")
|
||||||
|
Actor::isShowNulls = is_toggled(argument, Actor::isShowNulls);
|
||||||
|
else if (name == "spawn")
|
||||||
|
{
|
||||||
|
int id = SDL_atoi(argument.c_str());
|
||||||
|
if (argument.empty() || id < 0 || id >= (int)play->character->items.size())
|
||||||
|
history.push_back("Unable to spawn item " + argument + ".");
|
||||||
|
else
|
||||||
|
play->spawn_queue_add(id);
|
||||||
|
}
|
||||||
|
else if (name == "stage" || name == "weight")
|
||||||
|
{
|
||||||
|
play->stage = name == "stage" ? std::max(0, SDL_atoi(argument.c_str()) - 1) : play->stage_get(number);
|
||||||
|
progress->weight = name == "stage" ? play->stage_threshold_get(play->stage) : number;
|
||||||
|
play->idle_play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Console::update(Play* play, std::vector<Character>& characters)
|
||||||
|
{
|
||||||
|
action = ACTION_NONE;
|
||||||
|
if (!isOpen)
|
||||||
|
return;
|
||||||
|
ImVec2 screen = ImGui::GetIO().DisplaySize;
|
||||||
|
ImGui::SetNextWindowPos({0, 0}, ImGuiCond_FirstUseEver);
|
||||||
|
ImGui::SetNextWindowSize({screen.x, screen.y * CONSOLE_HEIGHT}, ImGuiCond_FirstUseEver);
|
||||||
|
if (ImGui::Begin("Console", &isOpen))
|
||||||
|
{
|
||||||
|
float inputHeight = ImGui::GetFrameHeightWithSpacing();
|
||||||
|
if (ImGui::BeginChild("##History", {0, -inputHeight}, ImGuiChildFlags_Borders))
|
||||||
|
{
|
||||||
|
for (const std::string& line : history)
|
||||||
|
ImGui::TextUnformatted(line.c_str());
|
||||||
|
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
|
||||||
|
ImGui::SetScrollHereY(1);
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
char text[INPUT_SIZE] = {};
|
||||||
|
SDL_strlcpy(text, input.c_str(), sizeof(text));
|
||||||
|
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||||
|
if (ImGui::IsWindowAppearing())
|
||||||
|
ImGui::SetKeyboardFocusHere();
|
||||||
|
if (ImGui::InputText("##Input", text, sizeof(text),
|
||||||
|
ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackHistory,
|
||||||
|
history_callback, this))
|
||||||
|
{
|
||||||
|
input = text;
|
||||||
|
execute(play, characters);
|
||||||
|
input.clear();
|
||||||
|
ImGui::SetKeyboardFocusHere(-1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
input = text;
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "play.h"
|
||||||
|
|
||||||
|
enum ConsoleAction
|
||||||
|
{
|
||||||
|
ACTION_NONE,
|
||||||
|
ACTION_CHARACTER,
|
||||||
|
ACTION_MENU,
|
||||||
|
ACTION_NEW_GAME,
|
||||||
|
ACTION_RELOAD
|
||||||
|
};
|
||||||
|
|
||||||
|
// A developer console (opened with the grave key when "console=1" is in settings.txt) for inspecting and changing a
|
||||||
|
// game. Commands that change characters are left as an action for the game to carry out.
|
||||||
|
class Console
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::vector<std::string> history;
|
||||||
|
std::vector<std::string> inputs;
|
||||||
|
std::string input;
|
||||||
|
std::string target;
|
||||||
|
ConsoleAction action = ACTION_NONE;
|
||||||
|
int inputIndex = -1;
|
||||||
|
bool isOpen = false;
|
||||||
|
|
||||||
|
void update(Play* play, std::vector<Character>& characters);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void execute(Play* play, std::vector<Character>& characters);
|
||||||
|
};
|
||||||
@@ -1,411 +0,0 @@
|
|||||||
#include "console.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <charconv>
|
|
||||||
#include <format>
|
|
||||||
#include <string_view>
|
|
||||||
#include <system_error>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <imgui.h>
|
|
||||||
#include <misc/cpp/imgui_stdlib.h>
|
|
||||||
|
|
||||||
#include "../resources.hpp"
|
|
||||||
#include "../state/play.hpp"
|
|
||||||
|
|
||||||
namespace game::developer
|
|
||||||
{
|
|
||||||
namespace console
|
|
||||||
{
|
|
||||||
#define GAME_CONSOLE_COMMANDS(X) \
|
|
||||||
X(ANIMATIONS, "animations", "animations", true) \
|
|
||||||
X(CAPACITY, "capacity", "capacity <calories>", true) \
|
|
||||||
X(CHARACTER, "character", "character <archive>", false) \
|
|
||||||
X(CHARACTERS, "characters", "characters", false) \
|
|
||||||
X(DIGEST, "digest", "digest", true) \
|
|
||||||
X(DIGESTION, "digestion", "digestion <% per second>", true) \
|
|
||||||
X(DIALOGUE, "dialogue", "dialogue", true) \
|
|
||||||
X(HELP, "help", "help", false) \
|
|
||||||
X(ITEMS, "items", "items", true) \
|
|
||||||
X(MENU, "menu", "menu", true) \
|
|
||||||
X(NEW_GAME, "new_game", "new_game <archive>", false) \
|
|
||||||
X(PLAY_ANIMATION, "play_animation", "play_animation <animation>", true) \
|
|
||||||
X(PLAY_DIALOGUE, "play_dialogue", "play_dialogue <label>", true) \
|
|
||||||
X(RELOAD, "reload", "reload", false) \
|
|
||||||
X(SHOW_LAYERS, "show_layers", "show_layers [true/false]", true) \
|
|
||||||
X(SHOW_NULLS, "show_nulls", "show_nulls [true/false]", true) \
|
|
||||||
X(SPAWN, "spawn", "spawn <item_id>", true) \
|
|
||||||
X(STAGE, "stage", "stage <stage>", true) \
|
|
||||||
X(WEIGHT, "weight", "weight <kilograms>", true)
|
|
||||||
|
|
||||||
enum class Command
|
|
||||||
{
|
|
||||||
#define GAME_CONSOLE_COMMAND_ENUM(SYMBOL, ...) SYMBOL,
|
|
||||||
GAME_CONSOLE_COMMANDS(GAME_CONSOLE_COMMAND_ENUM)
|
|
||||||
#undef GAME_CONSOLE_COMMAND_ENUM
|
|
||||||
UNKNOWN
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CommandInfo
|
|
||||||
{
|
|
||||||
Command command{};
|
|
||||||
std::string_view name{};
|
|
||||||
std::string_view usage{};
|
|
||||||
bool isGameOnly{};
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr CommandInfo COMMANDS[]{
|
|
||||||
#define GAME_CONSOLE_COMMAND_INFO(SYMBOL, NAME, USAGE, IS_GAME_ONLY) \
|
|
||||||
{.command = Command::SYMBOL, .name = NAME, .usage = USAGE, .isGameOnly = IS_GAME_ONLY},
|
|
||||||
GAME_CONSOLE_COMMANDS(GAME_CONSOLE_COMMAND_INFO)
|
|
||||||
#undef GAME_CONSOLE_COMMAND_INFO
|
|
||||||
};
|
|
||||||
|
|
||||||
#undef GAME_CONSOLE_COMMANDS
|
|
||||||
|
|
||||||
constexpr auto HISTORY_MIN_HEIGHT = 1.0f;
|
|
||||||
constexpr auto SCROLL_BOTTOM = 1.0f;
|
|
||||||
constexpr auto SCROLL_EPSILON = 1.0f;
|
|
||||||
constexpr auto INPUT_FLAGS = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackHistory;
|
|
||||||
constexpr auto ARG_VALUE = 1;
|
|
||||||
constexpr auto HISTORY_PREVIOUS = -1;
|
|
||||||
constexpr auto HISTORY_NEXT = 1;
|
|
||||||
|
|
||||||
const CommandInfo* command_info_find(std::string_view name)
|
|
||||||
{
|
|
||||||
for (auto& command : COMMANDS)
|
|
||||||
if (command.name == name) return &command;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
int int_parse(std::string_view value)
|
|
||||||
{
|
|
||||||
int result{};
|
|
||||||
if (value.empty()) return result;
|
|
||||||
std::from_chars(value.data(), value.data() + value.size(), result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
float float_parse(std::string_view value)
|
|
||||||
{
|
|
||||||
float result{};
|
|
||||||
if (value.empty()) return result;
|
|
||||||
std::from_chars(value.data(), value.data() + value.size(), result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool toggle_value_get(const std::vector<std::string_view>& args, bool isCurrent, bool& isValid)
|
|
||||||
{
|
|
||||||
isValid = true;
|
|
||||||
if (args.size() <= ARG_VALUE) return !isCurrent;
|
|
||||||
|
|
||||||
auto value = args[ARG_VALUE];
|
|
||||||
if (value == "true") return true;
|
|
||||||
if (value == "false") return false;
|
|
||||||
|
|
||||||
isValid = false;
|
|
||||||
return isCurrent;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string_view> args_get(const std::string& input)
|
|
||||||
{
|
|
||||||
std::vector<std::string_view> result{};
|
|
||||||
auto view = std::string_view{input};
|
|
||||||
while (!view.empty())
|
|
||||||
{
|
|
||||||
auto start = view.find_first_not_of(' ');
|
|
||||||
if (start == std::string_view::npos) break;
|
|
||||||
view.remove_prefix(start);
|
|
||||||
auto end = view.find(' ');
|
|
||||||
result.emplace_back(view.substr(0, end));
|
|
||||||
if (end == std::string_view::npos) break;
|
|
||||||
view.remove_prefix(end);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int input_callback(ImGuiInputTextCallbackData* data)
|
|
||||||
{
|
|
||||||
auto* console = static_cast<Console*>(data->UserData);
|
|
||||||
auto offset = data->EventKey == ImGuiKey_UpArrow ? HISTORY_PREVIOUS
|
|
||||||
: data->EventKey == ImGuiKey_DownArrow ? HISTORY_NEXT
|
|
||||||
: 0;
|
|
||||||
if (offset == 0) return 0;
|
|
||||||
auto& historyInput = console->command_history_offset(offset);
|
|
||||||
data->DeleteChars(0, data->BufTextLen);
|
|
||||||
data->InsertChars(0, historyInput.c_str());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
using namespace console;
|
|
||||||
|
|
||||||
void Console::focus_request() { isFocusRequested = true; }
|
|
||||||
|
|
||||||
void Console::history_push(std::string value) { history.emplace_back(std::move(value)); }
|
|
||||||
|
|
||||||
void Console::command_history_push()
|
|
||||||
{
|
|
||||||
if (input.empty()) return;
|
|
||||||
if (commandHistory.empty() || commandHistory.back() != input) commandHistory.emplace_back(input);
|
|
||||||
commandHistoryIndex = (int)commandHistory.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& Console::command_history_offset(int offset)
|
|
||||||
{
|
|
||||||
static const std::string EMPTY{};
|
|
||||||
if (commandHistory.empty()) return EMPTY;
|
|
||||||
if (commandHistoryIndex < 0) commandHistoryIndex = (int)commandHistory.size();
|
|
||||||
commandHistoryIndex = std::clamp(commandHistoryIndex + offset, 0, (int)commandHistory.size());
|
|
||||||
return commandHistoryIndex >= (int)commandHistory.size() ? EMPTY : commandHistory[commandHistoryIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
void Console::help_print(bool isGame)
|
|
||||||
{
|
|
||||||
history_push("Commands:");
|
|
||||||
for (auto& command : COMMANDS)
|
|
||||||
if (isGame || !command.isGameOnly) history_push(std::format(" {}", command.usage));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Console::command_execute(Resources& resources, state::Play* play)
|
|
||||||
{
|
|
||||||
action = {};
|
|
||||||
auto args = args_get(input);
|
|
||||||
if (args.empty()) return;
|
|
||||||
|
|
||||||
auto isGame = play != nullptr;
|
|
||||||
auto* info = command_info_find(args.front());
|
|
||||||
if (!info)
|
|
||||||
{
|
|
||||||
history_push(std::format("Unknown command: {}", args.front()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (info->isGameOnly && !isGame)
|
|
||||||
{
|
|
||||||
history_push(std::format("Command unavailable outside game: {}", info->name));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (info->command)
|
|
||||||
{
|
|
||||||
case Command::ANIMATIONS:
|
|
||||||
{
|
|
||||||
history_push("Animations:");
|
|
||||||
for (int i = 0; i < (int)play->character.animations.size(); ++i)
|
|
||||||
history_push(std::format(" {} {}", i, play->character.animations[i].name));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::CAPACITY:
|
|
||||||
{
|
|
||||||
play->character.capacity = float_parse(args.size() > ARG_VALUE ? args[ARG_VALUE] : std::string_view{});
|
|
||||||
history_push(std::format("Capacity set to {} kcal.", play->character.capacity));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::CHARACTER:
|
|
||||||
{
|
|
||||||
auto key = args.size() > ARG_VALUE ? std::string{args[ARG_VALUE]} : std::string{};
|
|
||||||
action.character = resources.character_index_get(key);
|
|
||||||
history_push(std::format("Character set to {}.", key));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::CHARACTERS:
|
|
||||||
{
|
|
||||||
history_push("Characters:");
|
|
||||||
for (int i = 0; i < (int)resources.characters.size(); ++i)
|
|
||||||
history_push(std::format(" {}", resources.character_key_get(i)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::DIGEST:
|
|
||||||
{
|
|
||||||
play->character.isDigesting = true;
|
|
||||||
play->character.digestionTimer = 1;
|
|
||||||
history_push("Digesting.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::DIGESTION:
|
|
||||||
{
|
|
||||||
auto digestion = float_parse(args.size() > ARG_VALUE ? args[ARG_VALUE] : std::string_view{});
|
|
||||||
play->character.digestionRate = digestion / Entity::UPDATE_RATE;
|
|
||||||
history_push(std::format("Digestion set to {}%/sec.", digestion));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::DIALOGUE:
|
|
||||||
{
|
|
||||||
history_push("Dialogue:");
|
|
||||||
for (auto& [label, index] : play->character.data.dialogue.entryIDMap)
|
|
||||||
history_push(std::format(" {}", label));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::PLAY_DIALOGUE:
|
|
||||||
{
|
|
||||||
auto label = args.size() > ARG_VALUE ? std::string{args[ARG_VALUE]} : std::string{};
|
|
||||||
auto* entry = play->character.data.dialogue.dialogue_entry_get(label);
|
|
||||||
play->text.set(entry, play->character);
|
|
||||||
history_push(std::format("Playing dialogue: {}", label));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::HELP:
|
|
||||||
help_print(isGame);
|
|
||||||
break;
|
|
||||||
case Command::ITEMS:
|
|
||||||
{
|
|
||||||
auto& items = play->character.data.itemSchema.items;
|
|
||||||
history_push("Items:");
|
|
||||||
for (int i = 0; i < (int)items.size(); ++i)
|
|
||||||
history_push(std::format(" {} {}", i, items[i].name));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::MENU:
|
|
||||||
{
|
|
||||||
action.isMenu = true;
|
|
||||||
history_push("Returning to menu.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::NEW_GAME:
|
|
||||||
{
|
|
||||||
auto key = args.size() > ARG_VALUE ? std::string{args[ARG_VALUE]} : std::string{};
|
|
||||||
if (key.empty())
|
|
||||||
{
|
|
||||||
history_push(std::format("Usage: {}", info->usage));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto characterIndex = resources.character_index_get(key);
|
|
||||||
if (characterIndex < 0)
|
|
||||||
{
|
|
||||||
history_push(std::format("Unknown character: {}", key));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
action.character = characterIndex;
|
|
||||||
action.isNewGame = true;
|
|
||||||
history_push(std::format("Starting new game: {}.", key));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::PLAY_ANIMATION:
|
|
||||||
{
|
|
||||||
auto animation = args.size() > ARG_VALUE ? std::string{args[ARG_VALUE]} : std::string{};
|
|
||||||
play->character.play(
|
|
||||||
{.animation = animation, .mode = Entity::PLAY_FORCE, .interrupt = Entity::Interrupt::ALWAYS});
|
|
||||||
history_push(std::format("Playing animation: {}", animation));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::RELOAD:
|
|
||||||
{
|
|
||||||
if (play)
|
|
||||||
{
|
|
||||||
auto key = resources.character_key_get(play->characterIndex);
|
|
||||||
play->save(resources);
|
|
||||||
resources.characters_refresh();
|
|
||||||
play->characterIndex = resources.character_index_get(key);
|
|
||||||
play->set(resources, play->characterIndex, state::Play::CONTINUE);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
resources.characters_refresh();
|
|
||||||
history_push(std::format("Reloaded character archives: {}", resources.characters.size()));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::SHOW_LAYERS:
|
|
||||||
{
|
|
||||||
auto isValid = true;
|
|
||||||
auto isShowLayers = toggle_value_get(args, play->isShowLayers, isValid);
|
|
||||||
if (!isValid)
|
|
||||||
{
|
|
||||||
history_push(std::format("Usage: {}", info->usage));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
play->isShowLayers = isShowLayers;
|
|
||||||
play->debug_flags_apply();
|
|
||||||
history_push(std::format("Show layers {}.", isShowLayers ? "enabled" : "disabled"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::SHOW_NULLS:
|
|
||||||
{
|
|
||||||
auto isValid = true;
|
|
||||||
auto isShowNulls = toggle_value_get(args, play->isShowNulls, isValid);
|
|
||||||
if (!isValid)
|
|
||||||
{
|
|
||||||
history_push(std::format("Usage: {}", info->usage));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
play->isShowNulls = isShowNulls;
|
|
||||||
play->debug_flags_apply();
|
|
||||||
history_push(std::format("Show nulls {}.", isShowNulls ? "enabled" : "disabled"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::SPAWN:
|
|
||||||
{
|
|
||||||
auto value = args.size() > ARG_VALUE ? args[ARG_VALUE] : std::string_view{};
|
|
||||||
int itemID{};
|
|
||||||
auto parsed = std::from_chars(value.data(), value.data() + value.size(), itemID);
|
|
||||||
auto isItemIDValid = !value.empty() && parsed.ec == std::errc{} && parsed.ptr == value.data() + value.size();
|
|
||||||
if (!isItemIDValid)
|
|
||||||
{
|
|
||||||
history_push(std::format("Usage: {}", info->usage));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!play->itemManager.spawn_queue(play->character, itemID, true))
|
|
||||||
{
|
|
||||||
history_push(std::format("Unable to spawn item {}.", itemID));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& item = play->character.data.itemSchema.items[itemID];
|
|
||||||
history_push(std::format("Spawned item {} {}.", itemID, item.name));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::STAGE:
|
|
||||||
{
|
|
||||||
auto stage = int_parse(args.size() > ARG_VALUE ? args[ARG_VALUE] : std::string_view{});
|
|
||||||
play->character.stage = stage - 1;
|
|
||||||
play->character.weight = play->character.stage_threshold_get(play->character.stage);
|
|
||||||
history_push(std::format("Stage set to {}.", stage));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::WEIGHT:
|
|
||||||
{
|
|
||||||
play->character.weight = float_parse(args.size() > ARG_VALUE ? args[ARG_VALUE] : std::string_view{});
|
|
||||||
history_push(std::format("Weight set to {} kg.", play->character.weight));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Command::UNKNOWN:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Console::update(Resources& resources, state::Play* play)
|
|
||||||
{
|
|
||||||
auto historySize = ImGui::GetContentRegionAvail();
|
|
||||||
historySize.y = std::max(HISTORY_MIN_HEIGHT,
|
|
||||||
historySize.y - ImGui::GetFrameHeight() - ImGui::GetStyle().ItemSpacing.y);
|
|
||||||
|
|
||||||
if (ImGui::BeginChild("##Console History", historySize, ImGuiChildFlags_Borders))
|
|
||||||
{
|
|
||||||
for (auto& entry : history)
|
|
||||||
ImGui::TextUnformatted(entry.c_str());
|
|
||||||
|
|
||||||
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY() - SCROLL_EPSILON)
|
|
||||||
ImGui::SetScrollHereY(SCROLL_BOTTOM);
|
|
||||||
}
|
|
||||||
ImGui::EndChild();
|
|
||||||
|
|
||||||
if (isFocusRequested)
|
|
||||||
{
|
|
||||||
ImGui::SetKeyboardFocusHere();
|
|
||||||
isFocusRequested = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
|
||||||
if (ImGui::InputText("##Console Input", &input, INPUT_FLAGS, input_callback, this) && !input.empty())
|
|
||||||
{
|
|
||||||
history_push(std::format("> {}", input));
|
|
||||||
command_history_push();
|
|
||||||
command_execute(resources, play);
|
|
||||||
input.clear();
|
|
||||||
isFocusRequested = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
struct ImGuiInputTextCallbackData;
|
|
||||||
|
|
||||||
namespace game
|
|
||||||
{
|
|
||||||
class Resources;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace game::state
|
|
||||||
{
|
|
||||||
class Play;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace game::developer
|
|
||||||
{
|
|
||||||
namespace console
|
|
||||||
{
|
|
||||||
int input_callback(ImGuiInputTextCallbackData*);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Console
|
|
||||||
{
|
|
||||||
friend int console::input_callback(ImGuiInputTextCallbackData*);
|
|
||||||
|
|
||||||
std::vector<std::string> history{};
|
|
||||||
std::vector<std::string> commandHistory{};
|
|
||||||
std::string input{};
|
|
||||||
int commandHistoryIndex{-1};
|
|
||||||
bool isFocusRequested{};
|
|
||||||
|
|
||||||
void history_push(std::string);
|
|
||||||
void command_history_push();
|
|
||||||
const std::string& command_history_offset(int offset);
|
|
||||||
void help_print(bool isGame);
|
|
||||||
void command_execute(Resources&, state::Play*);
|
|
||||||
|
|
||||||
public:
|
|
||||||
struct Action
|
|
||||||
{
|
|
||||||
int character{-1};
|
|
||||||
bool isMenu{};
|
|
||||||
bool isNewGame{};
|
|
||||||
};
|
|
||||||
|
|
||||||
Action action{};
|
|
||||||
|
|
||||||
void focus_request();
|
|
||||||
void update(Resources&, state::Play*);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
#include "document.hpp"
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <format>
|
|
||||||
|
|
||||||
namespace game::developer
|
|
||||||
{
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
using Schema = resource::xml::Schema;
|
|
||||||
using Element = Schema::Element;
|
|
||||||
|
|
||||||
static constexpr std::array elementTypeNames{
|
|
||||||
#define GAME_DEVELOPER_ELEMENT_TYPE_NAME(NAME, ENUM) std::pair{Element::ENUM, std::string_view{#NAME}},
|
|
||||||
GAME_SCHEMA_ELEMENT_TYPES(GAME_DEVELOPER_ELEMENT_TYPE_NAME)
|
|
||||||
#undef GAME_DEVELOPER_ELEMENT_TYPE_NAME
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string type_name_get(Element::Type type)
|
|
||||||
{
|
|
||||||
for (auto [mappedType, name] : elementTypeNames)
|
|
||||||
if (mappedType == type) return std::string{name};
|
|
||||||
return "Element";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string element_name_get(const Schema& schema, int index)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= (int)schema.elements.size()) return "element";
|
|
||||||
auto& element = schema.elements[index];
|
|
||||||
auto typeName = type_name_get(element.type);
|
|
||||||
if (!element.idString.empty()) return std::format("{} '{}'", typeName, element.idString);
|
|
||||||
if (!element.name.empty()) return std::format("{} '{}'", typeName, element.name);
|
|
||||||
return typeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
void snapshot_push(std::vector<Document::Snapshot>& snapshots, Document::Snapshot snapshot)
|
|
||||||
{
|
|
||||||
static constexpr auto MAX_SNAPSHOTS = 100;
|
|
||||||
snapshots.emplace_back(std::move(snapshot));
|
|
||||||
if ((int)snapshots.size() > MAX_SNAPSHOTS) snapshots.erase(snapshots.begin());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Document::SchemaFile* Document::schema_file_get(std::string_view filename)
|
|
||||||
{
|
|
||||||
for (auto& schema : schemas)
|
|
||||||
if (schema.filename == filename) return &schema;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Document::SchemaFile* Document::schema_file_get(std::string_view filename) const
|
|
||||||
{
|
|
||||||
for (auto& schema : schemas)
|
|
||||||
if (schema.filename == filename) return &schema;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Document::SchemaFile* Document::dialogue_schema_get() { return schema_file_get("dialogue.xml"); }
|
|
||||||
|
|
||||||
const Document::SchemaFile* Document::dialogue_schema_get() const { return schema_file_get("dialogue.xml"); }
|
|
||||||
|
|
||||||
bool Document::can_undo() const { return !undo.empty(); }
|
|
||||||
bool Document::can_redo() const { return !redo.empty(); }
|
|
||||||
|
|
||||||
void Document::edit_snapshot_begin()
|
|
||||||
{
|
|
||||||
auto* dialogueSchema = dialogue_schema_get();
|
|
||||||
if (!pendingEditSnapshot && dialogueSchema) pendingEditSnapshot = dialogueSchema->schema;
|
|
||||||
}
|
|
||||||
|
|
||||||
resource::xml::Schema Document::edit_snapshot_take_or(resource::xml::Schema fallback)
|
|
||||||
{
|
|
||||||
if (!pendingEditSnapshot) return fallback;
|
|
||||||
auto snapshot = std::move(*pendingEditSnapshot);
|
|
||||||
pendingEditSnapshot.reset();
|
|
||||||
return snapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Document::edit_snapshot_clear() { pendingEditSnapshot.reset(); }
|
|
||||||
|
|
||||||
void Document::snapshot_push(resource::xml::Schema snapshot, std::string message)
|
|
||||||
{
|
|
||||||
developer::snapshot_push(undo, {.schema = std::move(snapshot), .message = std::move(message)});
|
|
||||||
redo.clear();
|
|
||||||
isDirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Document::undo_apply()
|
|
||||||
{
|
|
||||||
if (undo.empty()) return {};
|
|
||||||
auto* dialogueSchema = dialogue_schema_get();
|
|
||||||
if (!dialogueSchema) return {};
|
|
||||||
|
|
||||||
auto snapshot = std::move(undo.back());
|
|
||||||
undo.pop_back();
|
|
||||||
developer::snapshot_push(redo, {.schema = dialogueSchema->schema, .message = snapshot.message});
|
|
||||||
dialogueSchema->schema = std::move(snapshot.schema);
|
|
||||||
isDirty = true;
|
|
||||||
return snapshot.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Document::redo_apply()
|
|
||||||
{
|
|
||||||
if (redo.empty()) return {};
|
|
||||||
auto* dialogueSchema = dialogue_schema_get();
|
|
||||||
if (!dialogueSchema) return {};
|
|
||||||
|
|
||||||
auto snapshot = std::move(redo.back());
|
|
||||||
redo.pop_back();
|
|
||||||
developer::snapshot_push(undo, {.schema = dialogueSchema->schema, .message = snapshot.message});
|
|
||||||
dialogueSchema->schema = std::move(snapshot.schema);
|
|
||||||
isDirty = true;
|
|
||||||
return snapshot.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Document::history_clear()
|
|
||||||
{
|
|
||||||
undo.clear();
|
|
||||||
redo.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Document::change_message_get(const Schema& before, const Schema& after)
|
|
||||||
{
|
|
||||||
if (after.elements.size() > before.elements.size())
|
|
||||||
{
|
|
||||||
for (int i = 0; i < (int)after.elements.size(); ++i)
|
|
||||||
{
|
|
||||||
auto serialized = after.element_serialize(i);
|
|
||||||
auto existed = false;
|
|
||||||
for (int beforeIndex = 0; beforeIndex < (int)before.elements.size(); ++beforeIndex)
|
|
||||||
{
|
|
||||||
if (before.element_serialize(beforeIndex) != serialized) continue;
|
|
||||||
existed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!existed) return std::format("Add {}", element_name_get(after, i));
|
|
||||||
}
|
|
||||||
return "Add element";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (after.elements.size() < before.elements.size())
|
|
||||||
{
|
|
||||||
for (int i = 0; i < (int)before.elements.size(); ++i)
|
|
||||||
{
|
|
||||||
auto serialized = before.element_serialize(i);
|
|
||||||
auto exists = false;
|
|
||||||
for (int afterIndex = 0; afterIndex < (int)after.elements.size(); ++afterIndex)
|
|
||||||
{
|
|
||||||
if (after.element_serialize(afterIndex) != serialized) continue;
|
|
||||||
exists = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!exists) return std::format("Delete {}", element_name_get(before, i));
|
|
||||||
}
|
|
||||||
return "Delete element";
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < (int)before.elements.size(); ++i)
|
|
||||||
{
|
|
||||||
if (before.element_serialize(i) == after.element_serialize(i)) continue;
|
|
||||||
if (before.elements[i].type != after.elements[i].type)
|
|
||||||
return std::format("Change {} to {}", element_name_get(before, i), element_name_get(after, i));
|
|
||||||
return std::format("Change {}", element_name_get(after, i));
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Change schema";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "../resource/schema.hpp"
|
|
||||||
#include "editor.hpp"
|
|
||||||
|
|
||||||
namespace game::developer
|
|
||||||
{
|
|
||||||
class Document
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
struct SchemaFile
|
|
||||||
{
|
|
||||||
std::string filename{};
|
|
||||||
std::string label{};
|
|
||||||
std::filesystem::path path{};
|
|
||||||
resource::xml::Schema::Element::Type type{resource::xml::Schema::Element::UNKNOWN};
|
|
||||||
resource::xml::Schema schema{};
|
|
||||||
bool isExists{};
|
|
||||||
bool isValid{};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Snapshot
|
|
||||||
{
|
|
||||||
resource::xml::Schema schema{};
|
|
||||||
std::string message{};
|
|
||||||
};
|
|
||||||
|
|
||||||
std::filesystem::path path{};
|
|
||||||
std::vector<SchemaFile> schemas{};
|
|
||||||
Editor editor{};
|
|
||||||
std::vector<Snapshot> undo{};
|
|
||||||
std::vector<Snapshot> redo{};
|
|
||||||
std::optional<resource::xml::Schema> pendingEditSnapshot{};
|
|
||||||
bool isDirty{};
|
|
||||||
|
|
||||||
SchemaFile* schema_file_get(std::string_view filename);
|
|
||||||
const SchemaFile* schema_file_get(std::string_view filename) const;
|
|
||||||
SchemaFile* dialogue_schema_get();
|
|
||||||
const SchemaFile* dialogue_schema_get() const;
|
|
||||||
bool can_undo() const;
|
|
||||||
bool can_redo() const;
|
|
||||||
void edit_snapshot_begin();
|
|
||||||
resource::xml::Schema edit_snapshot_take_or(resource::xml::Schema);
|
|
||||||
void edit_snapshot_clear();
|
|
||||||
void snapshot_push(resource::xml::Schema, std::string);
|
|
||||||
std::string undo_apply();
|
|
||||||
std::string redo_apply();
|
|
||||||
void history_clear();
|
|
||||||
|
|
||||||
static std::string change_message_get(const resource::xml::Schema& before, const resource::xml::Schema& after);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,399 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <functional>
|
|
||||||
#include <span>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
#include <imgui.h>
|
|
||||||
#include <imgui_internal.h>
|
|
||||||
|
|
||||||
#include "../resource/audio.hpp"
|
|
||||||
#include "../resource/character.hpp"
|
|
||||||
#include "../resource/font.hpp"
|
|
||||||
#include "../resource/schema.hpp"
|
|
||||||
#include "../entity.hpp"
|
|
||||||
#include "../resources.hpp"
|
|
||||||
#include "../util/clipboard.hpp"
|
|
||||||
#include "../util/dialog.hpp"
|
|
||||||
#include "../util/imgui/entity_canvas.hpp"
|
|
||||||
|
|
||||||
namespace game::developer
|
|
||||||
{
|
|
||||||
extern bool gIsDocumentUndoMenuRequested;
|
|
||||||
extern bool gIsDocumentRedoMenuRequested;
|
|
||||||
|
|
||||||
namespace editor_schema
|
|
||||||
{
|
|
||||||
using Schema = resource::xml::Schema;
|
|
||||||
using Element = Schema::Element;
|
|
||||||
|
|
||||||
struct ElementTypeInfo
|
|
||||||
{
|
|
||||||
std::string_view xmlName{};
|
|
||||||
Element::Type type{};
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr ElementTypeInfo ELEMENT_TYPES[]{
|
|
||||||
#define GAME_SCHEMA_DEVELOPER_ELEMENT_TYPE(NAME, ENUM) {#NAME, Element::ENUM},
|
|
||||||
GAME_SCHEMA_ELEMENT_TYPES(GAME_SCHEMA_DEVELOPER_ELEMENT_TYPE)
|
|
||||||
#undef GAME_SCHEMA_DEVELOPER_ELEMENT_TYPE
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ElementActionOptions
|
|
||||||
{
|
|
||||||
int parent{-1};
|
|
||||||
int target{-1};
|
|
||||||
int after{-1};
|
|
||||||
int pasteParent{-1};
|
|
||||||
int pasteAfter{-2};
|
|
||||||
Element::Type type{Element::UNKNOWN};
|
|
||||||
bool isInsertVisible{};
|
|
||||||
bool isInsertEnabled{};
|
|
||||||
bool isDuplicateEnabled{true};
|
|
||||||
bool isDeleteEnabled{true};
|
|
||||||
bool isClearVisible{};
|
|
||||||
bool isClearEnabled{};
|
|
||||||
bool isPasteEnabled{true};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ElementActionResult
|
|
||||||
{
|
|
||||||
bool isDirty{};
|
|
||||||
bool isStructureChanged{};
|
|
||||||
};
|
|
||||||
|
|
||||||
inline const ElementTypeInfo& element_type_info_get(Element::Type type)
|
|
||||||
{
|
|
||||||
for (auto& info : ELEMENT_TYPES)
|
|
||||||
if (info.type == type) return info;
|
|
||||||
return ELEMENT_TYPES[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool shortcut_is_pressed(ImGuiKeyChord chord)
|
|
||||||
{
|
|
||||||
auto& io = ImGui::GetIO();
|
|
||||||
return !io.WantTextInput && !ImGui::IsAnyItemActive() &&
|
|
||||||
ImGui::Shortcut(chord, ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void document_history_context_menu_draw()
|
|
||||||
{
|
|
||||||
ImGui::Separator();
|
|
||||||
if (ImGui::MenuItem("Undo", "Ctrl+Z")) gIsDocumentUndoMenuRequested = true;
|
|
||||||
if (ImGui::MenuItem("Redo", "Ctrl+Shift+Z")) gIsDocumentRedoMenuRequested = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool element_index_is_valid(const Schema& schema, int index)
|
|
||||||
{
|
|
||||||
return index >= 0 && index < (int)schema.elements.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void child_insert_after(Schema& schema, int parent, int child, int after)
|
|
||||||
{
|
|
||||||
if (!element_index_is_valid(schema, parent)) return;
|
|
||||||
auto& children = schema.elements[parent].children;
|
|
||||||
auto childIt = std::ranges::find(children, child);
|
|
||||||
if (childIt == children.end()) return;
|
|
||||||
auto value = *childIt;
|
|
||||||
children.erase(childIt);
|
|
||||||
auto afterIt = std::ranges::find(children, after);
|
|
||||||
if (afterIt == children.end())
|
|
||||||
children.emplace_back(value);
|
|
||||||
else
|
|
||||||
children.insert(afterIt + 1, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline int element_paste(util::Clipboard& clipboard, Schema& schema, int parent, int after,
|
|
||||||
Element::Type expected = Element::UNKNOWN)
|
|
||||||
{
|
|
||||||
auto index = schema.element_insert_xml(clipboard.get(), parent);
|
|
||||||
if (index < 0) return -1;
|
|
||||||
if (expected != Element::UNKNOWN && schema.elements[index].type != expected)
|
|
||||||
{
|
|
||||||
schema.element_erase(index);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
child_insert_after(schema, parent, index, after);
|
|
||||||
schema.indices_refresh();
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline int element_duplicate(Schema& schema, int parent, int source, int after,
|
|
||||||
Element::Type expected = Element::UNKNOWN)
|
|
||||||
{
|
|
||||||
if (!element_index_is_valid(schema, source)) return -1;
|
|
||||||
if (parent < 0) parent = schema.elements[source].parent;
|
|
||||||
if (!element_index_is_valid(schema, parent)) return -1;
|
|
||||||
auto index = schema.element_insert_xml(schema.element_serialize(source), parent);
|
|
||||||
if (index < 0) return -1;
|
|
||||||
if (expected != Element::UNKNOWN && schema.elements[index].type != expected)
|
|
||||||
{
|
|
||||||
schema.element_erase(index);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
child_insert_after(schema, parent, index, after);
|
|
||||||
schema.indices_refresh();
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void element_copy(util::Clipboard& clipboard, const Schema& schema, int index)
|
|
||||||
{
|
|
||||||
if (element_index_is_valid(schema, index)) clipboard.copy(schema.element_serialize(index));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool element_cut(util::Clipboard& clipboard, Schema& schema, int index)
|
|
||||||
{
|
|
||||||
if (!element_index_is_valid(schema, index)) return false;
|
|
||||||
clipboard.cut(schema.element_serialize(index));
|
|
||||||
return schema.element_erase(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool children_clear(Schema& schema, int parent, Element::Type type)
|
|
||||||
{
|
|
||||||
if (!element_index_is_valid(schema, parent)) return false;
|
|
||||||
std::vector<int> indices{};
|
|
||||||
for (auto child : schema.elements[parent].children)
|
|
||||||
{
|
|
||||||
if (!element_index_is_valid(schema, child)) continue;
|
|
||||||
if (schema.elements[child].type == type) indices.emplace_back(child);
|
|
||||||
}
|
|
||||||
if (indices.empty()) return false;
|
|
||||||
|
|
||||||
std::ranges::sort(indices, std::greater{});
|
|
||||||
auto isDirty = false;
|
|
||||||
for (auto index : indices)
|
|
||||||
if (schema.element_erase(index)) isDirty = true;
|
|
||||||
return isDirty;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Insert, typename Select, typename Clear>
|
|
||||||
ElementActionResult element_context_menu_draw(util::Clipboard& clipboard, Schema& schema,
|
|
||||||
const ElementActionOptions& options, int& selected, Insert insert,
|
|
||||||
Select select, Clear clear)
|
|
||||||
{
|
|
||||||
ElementActionResult result{};
|
|
||||||
auto isTargetValid = element_index_is_valid(schema, options.target);
|
|
||||||
auto pasteParent = options.pasteParent >= 0 ? options.pasteParent : options.parent;
|
|
||||||
auto pasteAfter = options.pasteAfter >= -1 ? options.pasteAfter : options.after;
|
|
||||||
|
|
||||||
if (options.isInsertVisible && ImGui::MenuItem("Insert", nullptr, false, options.isInsertEnabled))
|
|
||||||
{
|
|
||||||
selected = insert(options.after);
|
|
||||||
if (selected >= 0)
|
|
||||||
{
|
|
||||||
select(selected);
|
|
||||||
result.isDirty = true;
|
|
||||||
result.isStructureChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ImGui::MenuItem("Duplicate", nullptr, false, options.isDuplicateEnabled && isTargetValid))
|
|
||||||
{
|
|
||||||
selected = element_duplicate(schema, options.parent, options.target, options.after, options.type);
|
|
||||||
if (selected >= 0)
|
|
||||||
{
|
|
||||||
select(selected);
|
|
||||||
result.isDirty = true;
|
|
||||||
result.isStructureChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ImGui::MenuItem("Delete", "Del", false, options.isDeleteEnabled && isTargetValid))
|
|
||||||
{
|
|
||||||
if (schema.element_erase(options.target))
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
result.isDirty = true;
|
|
||||||
result.isStructureChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (options.isClearVisible && ImGui::MenuItem("Clear", nullptr, false, options.isClearEnabled))
|
|
||||||
{
|
|
||||||
if (children_clear(schema, options.parent, options.type))
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
result.isDirty = true;
|
|
||||||
result.isStructureChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::Separator();
|
|
||||||
if (ImGui::MenuItem("Cut", "Ctrl+X", false, options.isDeleteEnabled && isTargetValid))
|
|
||||||
{
|
|
||||||
if (element_cut(clipboard, schema, options.target))
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
result.isDirty = true;
|
|
||||||
result.isStructureChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ImGui::MenuItem("Copy", "Ctrl+C", false, isTargetValid)) element_copy(clipboard, schema, options.target);
|
|
||||||
if (ImGui::MenuItem("Paste", "Ctrl+V", false, options.isPasteEnabled && element_index_is_valid(schema, pasteParent)))
|
|
||||||
{
|
|
||||||
selected = element_paste(clipboard, schema, pasteParent, pasteAfter, options.type);
|
|
||||||
if (selected >= 0)
|
|
||||||
{
|
|
||||||
select(selected);
|
|
||||||
result.isDirty = true;
|
|
||||||
result.isStructureChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Select, typename Clear>
|
|
||||||
ElementActionResult element_shortcuts_apply(util::Clipboard& clipboard, Schema& schema,
|
|
||||||
const ElementActionOptions& options, int& selected, Select select,
|
|
||||||
Clear clear)
|
|
||||||
{
|
|
||||||
ElementActionResult result{};
|
|
||||||
auto isTargetValid = element_index_is_valid(schema, options.target);
|
|
||||||
auto pasteParent = options.pasteParent >= 0 ? options.pasteParent : options.parent;
|
|
||||||
auto pasteAfter = options.pasteAfter >= -1 ? options.pasteAfter : options.after;
|
|
||||||
|
|
||||||
if (options.isDeleteEnabled && isTargetValid && shortcut_is_pressed(ImGuiKey_Delete))
|
|
||||||
{
|
|
||||||
if (schema.element_erase(options.target))
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
result.isDirty = true;
|
|
||||||
result.isStructureChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (options.isDeleteEnabled && isTargetValid && shortcut_is_pressed(ImGuiMod_Ctrl | ImGuiKey_X))
|
|
||||||
{
|
|
||||||
if (element_cut(clipboard, schema, options.target))
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
result.isDirty = true;
|
|
||||||
result.isStructureChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (isTargetValid && shortcut_is_pressed(ImGuiMod_Ctrl | ImGuiKey_C))
|
|
||||||
{
|
|
||||||
element_copy(clipboard, schema, options.target);
|
|
||||||
}
|
|
||||||
else if (options.isPasteEnabled && element_index_is_valid(schema, pasteParent) &&
|
|
||||||
shortcut_is_pressed(ImGuiMod_Ctrl | ImGuiKey_V))
|
|
||||||
{
|
|
||||||
selected = element_paste(clipboard, schema, pasteParent, pasteAfter, options.type);
|
|
||||||
if (selected >= 0)
|
|
||||||
{
|
|
||||||
select(selected);
|
|
||||||
result.isDirty = true;
|
|
||||||
result.isStructureChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void attribute_order_ensure(Element& element, std::string_view symbol)
|
|
||||||
{
|
|
||||||
if (std::ranges::find(element.attributeOrder, symbol) == element.attributeOrder.end())
|
|
||||||
element.attributeOrder.emplace_back(symbol);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void attribute_order_remove(Element& element, std::string_view symbol)
|
|
||||||
{
|
|
||||||
element.attributeOrder.erase(std::remove(element.attributeOrder.begin(), element.attributeOrder.end(), symbol),
|
|
||||||
element.attributeOrder.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Insert, typename Clear, typename Paste>
|
|
||||||
bool attribute_context_menu_draw(util::Clipboard& clipboard, const std::string& assignments, Insert insert,
|
|
||||||
Clear clear, Paste paste)
|
|
||||||
{
|
|
||||||
auto isDirty = insert();
|
|
||||||
ImGui::BeginDisabled();
|
|
||||||
ImGui::MenuItem("Duplicate");
|
|
||||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
|
||||||
ImGui::SetTooltip("XML attributes are unique by name.");
|
|
||||||
ImGui::EndDisabled();
|
|
||||||
if (ImGui::MenuItem("Delete", "Del"))
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
isDirty = true;
|
|
||||||
}
|
|
||||||
ImGui::Separator();
|
|
||||||
if (ImGui::MenuItem("Cut", "Ctrl+X"))
|
|
||||||
{
|
|
||||||
clipboard.cut(assignments);
|
|
||||||
clear();
|
|
||||||
isDirty = true;
|
|
||||||
}
|
|
||||||
if (ImGui::MenuItem("Copy", "Ctrl+C")) clipboard.copy(assignments);
|
|
||||||
if (ImGui::MenuItem("Paste", "Ctrl+V") && paste(clipboard.get())) isDirty = true;
|
|
||||||
return isDirty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Editor
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
struct LoadedSchema
|
|
||||||
{
|
|
||||||
const std::filesystem::path* path{};
|
|
||||||
const resource::xml::Schema* schema{};
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Mode
|
|
||||||
{
|
|
||||||
ENTRY,
|
|
||||||
POOL
|
|
||||||
};
|
|
||||||
|
|
||||||
enum PendingSetting
|
|
||||||
{
|
|
||||||
PENDING_NONE,
|
|
||||||
PENDING_SOUND
|
|
||||||
};
|
|
||||||
|
|
||||||
Mode mode{ENTRY};
|
|
||||||
int selectedEntry{-1};
|
|
||||||
int selectedPool{-1};
|
|
||||||
std::vector<int> selectedEntries{};
|
|
||||||
std::vector<int> selectedPools{};
|
|
||||||
int selectedPoolEntry{-1};
|
|
||||||
int selectedChoice{-1};
|
|
||||||
int selectedEffect{-1};
|
|
||||||
int scrollToEntry{-1};
|
|
||||||
int scrollToPool{-1};
|
|
||||||
int scrollToPoolEntry{-1};
|
|
||||||
int scrollToChoice{-1};
|
|
||||||
int scrollToEffect{-1};
|
|
||||||
int textSelectionStart{};
|
|
||||||
int textSelectionEnd{};
|
|
||||||
int textSelectionStartByte{};
|
|
||||||
int textSelectionEndByte{};
|
|
||||||
int previewIndex{};
|
|
||||||
int pendingSoundEffect{-1};
|
|
||||||
bool isPlaying{};
|
|
||||||
bool previewNeedsAnimationPlay{};
|
|
||||||
bool isPreviewBlipQueued{};
|
|
||||||
float previewTimer{};
|
|
||||||
int previewBaseDelay{4};
|
|
||||||
int previewDelay{4};
|
|
||||||
int delay{4};
|
|
||||||
int blipDelay{4};
|
|
||||||
glm::vec3 nameColor{0.498039f, 0.498039f, 0.498039f};
|
|
||||||
glm::vec3 textColor{1.0f, 1.0f, 1.0f};
|
|
||||||
PendingSetting pendingSetting{PENDING_NONE};
|
|
||||||
resource::xml::Character previewCharacter{};
|
|
||||||
util::Clipboard clipboard{};
|
|
||||||
resource::Font previewFont{};
|
|
||||||
Entity previewEntity{};
|
|
||||||
util::imgui::widget::EntityCanvas previewCanvas{};
|
|
||||||
std::string animationAppendID{};
|
|
||||||
std::string fontPath{};
|
|
||||||
std::string fontRootPath{};
|
|
||||||
std::filesystem::path fontSchemaPath{};
|
|
||||||
std::string previewAnm2Path{};
|
|
||||||
std::string previewAnimation{};
|
|
||||||
std::string previewAnimationRequest{};
|
|
||||||
|
|
||||||
bool update(Resources&, resource::xml::Schema&, const std::filesystem::path&, util::Dialog&,
|
|
||||||
std::span<const LoadedSchema>);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,513 +0,0 @@
|
|||||||
#include "window.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <format>
|
|
||||||
#include <fstream>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <imgui.h>
|
|
||||||
#include <imgui_internal.h>
|
|
||||||
|
|
||||||
#include "../log.hpp"
|
|
||||||
#include "../util/physfs.hpp"
|
|
||||||
#include "../util/preferences.hpp"
|
|
||||||
|
|
||||||
namespace game::developer
|
|
||||||
{
|
|
||||||
bool gIsDocumentUndoMenuRequested{};
|
|
||||||
bool gIsDocumentRedoMenuRequested{};
|
|
||||||
|
|
||||||
namespace window_detail
|
|
||||||
{
|
|
||||||
constexpr auto RECENT_FILES_MAX = 10;
|
|
||||||
constexpr auto CENTER_RATIO = 0.5f;
|
|
||||||
constexpr auto DEVELOPER_WINDOW_SIZE_RATIO = 0.75f;
|
|
||||||
constexpr auto DEVELOPER_WINDOW_FLAGS = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings;
|
|
||||||
constexpr auto EDITOR_CHILD_FLAGS = ImGuiWindowFlags_MenuBar;
|
|
||||||
constexpr auto DIALOGUE_SCHEMA_FILENAME = "dialogue.xml";
|
|
||||||
|
|
||||||
using Schema = resource::xml::Schema;
|
|
||||||
using Element = Schema::Element;
|
|
||||||
|
|
||||||
#define GAME_DEVELOPER_SCHEMA_FILES(X) \
|
|
||||||
X(CHARACTER, "character.xml", "Character", Element::CHARACTER) \
|
|
||||||
X(DIALOGUE, "dialogue.xml", "Dialogue", Element::DIALOGUE) \
|
|
||||||
X(ITEMS, "items.xml", "Items", Element::ITEM_SCHEMA) \
|
|
||||||
X(AREAS, "areas.xml", "Areas", Element::AREAS) \
|
|
||||||
X(MENU, "menu.xml", "Menu", Element::MENU) \
|
|
||||||
X(CURSOR, "cursor.xml", "Cursor", Element::CURSOR) \
|
|
||||||
X(PARTICLES, "particles.xml", "Particles", Element::PARTICLES) \
|
|
||||||
X(STRINGS, "strings.xml", "Strings", Element::STRINGS)
|
|
||||||
|
|
||||||
struct SchemaInfo
|
|
||||||
{
|
|
||||||
std::string_view filename{};
|
|
||||||
std::string_view label{};
|
|
||||||
Element::Type type{};
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr SchemaInfo SCHEMA_INFOS[]{
|
|
||||||
#define GAME_DEVELOPER_SCHEMA_INFO(SYMBOL, FILENAME, LABEL, TYPE) {.filename = FILENAME, .label = LABEL, .type = TYPE},
|
|
||||||
GAME_DEVELOPER_SCHEMA_FILES(GAME_DEVELOPER_SCHEMA_INFO)
|
|
||||||
#undef GAME_DEVELOPER_SCHEMA_INFO
|
|
||||||
};
|
|
||||||
|
|
||||||
std::filesystem::path path_normalized_get(const std::filesystem::path& path)
|
|
||||||
{
|
|
||||||
std::error_code error{};
|
|
||||||
auto normalized = std::filesystem::weakly_canonical(path, error);
|
|
||||||
if (!error) return normalized;
|
|
||||||
return std::filesystem::absolute(path, error).lexically_normal();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path recent_file_path_get() { return util::preferences::path() / "recent.txt"; }
|
|
||||||
|
|
||||||
std::string document_path_label_get(const std::filesystem::path& path)
|
|
||||||
{
|
|
||||||
auto filename = path.filename();
|
|
||||||
if (filename.empty()) return path.string();
|
|
||||||
return filename.string();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool shortcut_is_pressed(ImGuiKeyChord chord)
|
|
||||||
{
|
|
||||||
auto& io = ImGui::GetIO();
|
|
||||||
return !io.WantTextInput && !ImGui::IsAnyItemActive() &&
|
|
||||||
ImGui::Shortcut(chord, ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused);
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef GAME_DEVELOPER_SCHEMA_FILES
|
|
||||||
}
|
|
||||||
|
|
||||||
using namespace window_detail;
|
|
||||||
|
|
||||||
void Window::recent_files_load()
|
|
||||||
{
|
|
||||||
if (isRecentFilesLoaded) return;
|
|
||||||
isRecentFilesLoaded = true;
|
|
||||||
recentFiles.clear();
|
|
||||||
|
|
||||||
std::ifstream file(recent_file_path_get());
|
|
||||||
if (!file.is_open()) return;
|
|
||||||
|
|
||||||
std::string line{};
|
|
||||||
while (std::getline(file, line))
|
|
||||||
{
|
|
||||||
if (line.empty()) continue;
|
|
||||||
recentFiles.push_back(path_normalized_get(line));
|
|
||||||
if ((int)recentFiles.size() >= RECENT_FILES_MAX) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::recent_files_save()
|
|
||||||
{
|
|
||||||
auto recentPath = recent_file_path_get();
|
|
||||||
if (recentPath.empty()) return;
|
|
||||||
std::error_code error{};
|
|
||||||
if (!recentPath.parent_path().empty()) std::filesystem::create_directories(recentPath.parent_path(), error);
|
|
||||||
|
|
||||||
std::ofstream file(recentPath, std::ios::trunc);
|
|
||||||
if (!file.is_open())
|
|
||||||
{
|
|
||||||
logger.warning(std::format("Failed to write {}", recentPath.string()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& path : recentFiles)
|
|
||||||
file << path.string() << '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::recent_file_push(const std::filesystem::path& path)
|
|
||||||
{
|
|
||||||
recent_files_load();
|
|
||||||
auto normalized = path_normalized_get(path);
|
|
||||||
if (normalized.empty()) return;
|
|
||||||
|
|
||||||
std::erase_if(recentFiles, [&](const auto& recent) { return recent == normalized; });
|
|
||||||
recentFiles.insert(recentFiles.begin(), normalized);
|
|
||||||
if ((int)recentFiles.size() > RECENT_FILES_MAX) recentFiles.resize(RECENT_FILES_MAX);
|
|
||||||
recent_files_save();
|
|
||||||
}
|
|
||||||
|
|
||||||
Document Window::document_load(const std::filesystem::path& path)
|
|
||||||
{
|
|
||||||
Document document{};
|
|
||||||
document.path = path_normalized_get(path);
|
|
||||||
|
|
||||||
for (auto& info : SCHEMA_INFOS)
|
|
||||||
{
|
|
||||||
Document::SchemaFile schemaFile{};
|
|
||||||
schemaFile.filename = info.filename;
|
|
||||||
schemaFile.label = info.label;
|
|
||||||
schemaFile.type = info.type;
|
|
||||||
schemaFile.path = document.path / schemaFile.filename;
|
|
||||||
schemaFile.isExists = std::filesystem::is_regular_file(schemaFile.path);
|
|
||||||
if (schemaFile.isExists)
|
|
||||||
{
|
|
||||||
schemaFile.isValid = schemaFile.schema.load(schemaFile.path);
|
|
||||||
auto* root = schemaFile.schema.root();
|
|
||||||
if (!root || root->type != schemaFile.type) schemaFile.isValid = false;
|
|
||||||
}
|
|
||||||
document.schemas.emplace_back(std::move(schemaFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::folder_open(const std::filesystem::path& path)
|
|
||||||
{
|
|
||||||
auto normalizedPath = path_normalized_get(path);
|
|
||||||
std::error_code error{};
|
|
||||||
if (!std::filesystem::is_directory(normalizedPath, error))
|
|
||||||
{
|
|
||||||
logger.warning(std::format("Ignored {}", normalizedPath.string()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto it = std::ranges::find_if(documents, [&](const auto& document) { return document.path == normalizedPath; });
|
|
||||||
if (it != documents.end())
|
|
||||||
{
|
|
||||||
*it = document_load(normalizedPath);
|
|
||||||
it->history_clear();
|
|
||||||
it->isDirty = false;
|
|
||||||
selectedDocument = (int)std::distance(documents.begin(), it);
|
|
||||||
logger.info(std::format("Opened {}", normalizedPath.string()));
|
|
||||||
toasts.push(std::format("Opened {}", normalizedPath.string()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
documents.push_back(document_load(normalizedPath));
|
|
||||||
selectedDocument = (int)documents.size() - 1;
|
|
||||||
logger.info(std::format("Opened {}", normalizedPath.string()));
|
|
||||||
toasts.push(std::format("Opened {}", normalizedPath.string()));
|
|
||||||
}
|
|
||||||
recent_file_push(normalizedPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::save(int index)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= (int)documents.size()) return;
|
|
||||||
auto& document = documents[index];
|
|
||||||
auto* dialogue = document.dialogue_schema_get();
|
|
||||||
if (!dialogue || !dialogue->isExists)
|
|
||||||
{
|
|
||||||
toasts.push("No dialogue.xml to save");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(std::format("Wrote to {}", dialogue->path.string()));
|
|
||||||
dialogue->schema.serialize(dialogue->path);
|
|
||||||
documents[index].isDirty = false;
|
|
||||||
toasts.push(std::format("Wrote to {}", dialogue->path.string()));
|
|
||||||
recent_file_push(documents[index].path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::export_to(int index, const std::filesystem::path& path)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= (int)documents.size()) return;
|
|
||||||
if (documents[index].isDirty) save(index);
|
|
||||||
auto destination = path_normalized_get(path);
|
|
||||||
if (util::physfs::archive_write(documents[index].path, destination))
|
|
||||||
toasts.push(std::format("Exported {}", destination.string()));
|
|
||||||
else
|
|
||||||
toasts.push(std::format("Failed to export {}", destination.string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::close(int index)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= (int)documents.size()) return;
|
|
||||||
auto label = document_path_label_get(documents[index].path);
|
|
||||||
documents.erase(documents.begin() + index);
|
|
||||||
selectedDocument = documents.empty() ? 0 : std::clamp(index, 0, (int)documents.size() - 1);
|
|
||||||
toasts.push(std::format("Closed {}", label));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::undo(int index)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= (int)documents.size()) return;
|
|
||||||
auto& document = documents[index];
|
|
||||||
if (!document.can_undo()) return;
|
|
||||||
|
|
||||||
auto message = document.undo_apply();
|
|
||||||
if (!message.empty()) toasts.push(std::format("Undo: {}", message));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::redo(int index)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= (int)documents.size()) return;
|
|
||||||
auto& document = documents[index];
|
|
||||||
if (!document.can_redo()) return;
|
|
||||||
|
|
||||||
auto message = document.redo_apply();
|
|
||||||
if (!message.empty()) toasts.push(std::format("Redo: {}", message));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::dropped_file_open(const char* path)
|
|
||||||
{
|
|
||||||
if (!path) return;
|
|
||||||
folder_open(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::editor_shortcuts_update()
|
|
||||||
{
|
|
||||||
if (shortcut_is_pressed(ImGuiMod_Ctrl | ImGuiKey_O)) dialog.folder_open(util::Dialog::SCHEMA_FOLDER_OPEN);
|
|
||||||
if (shortcut_is_pressed(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Z))
|
|
||||||
redo(selectedDocument);
|
|
||||||
else if (shortcut_is_pressed(ImGuiMod_Ctrl | ImGuiKey_Z))
|
|
||||||
undo(selectedDocument);
|
|
||||||
if (shortcut_is_pressed(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_S))
|
|
||||||
dialog.file_save(util::Dialog::DOCUMENT_EXPORT);
|
|
||||||
else if (shortcut_is_pressed(ImGuiMod_Ctrl | ImGuiKey_S))
|
|
||||||
save(selectedDocument);
|
|
||||||
if (shortcut_is_pressed(ImGuiMod_Ctrl | ImGuiKey_W)) close(selectedDocument);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::dialog_selection_update()
|
|
||||||
{
|
|
||||||
if (dialog.is_selected(util::Dialog::SCHEMA_FOLDER_OPEN))
|
|
||||||
{
|
|
||||||
folder_open(dialog.path);
|
|
||||||
dialog.reset();
|
|
||||||
}
|
|
||||||
else if (dialog.is_selected(util::Dialog::DOCUMENT_EXPORT))
|
|
||||||
{
|
|
||||||
export_to(selectedDocument, dialog.path);
|
|
||||||
dialog.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::editor_menu_update()
|
|
||||||
{
|
|
||||||
if (!ImGui::BeginMenuBar()) return;
|
|
||||||
|
|
||||||
if (ImGui::BeginMenu("File"))
|
|
||||||
{
|
|
||||||
if (ImGui::MenuItem("Open Folder", "Ctrl+O")) dialog.folder_open(util::Dialog::SCHEMA_FOLDER_OPEN);
|
|
||||||
if (ImGui::BeginMenu("Open Recent", !recentFiles.empty()))
|
|
||||||
{
|
|
||||||
for (int recentIndex = 0; recentIndex < (int)recentFiles.size(); ++recentIndex)
|
|
||||||
{
|
|
||||||
ImGui::PushID(recentIndex);
|
|
||||||
auto label = recentFiles[recentIndex].string();
|
|
||||||
if (ImGui::MenuItem(label.c_str())) folder_open(recentFiles[recentIndex]);
|
|
||||||
ImGui::PopID();
|
|
||||||
}
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
if (ImGui::MenuItem("Save", "Ctrl+S", false, selectedDocument >= 0 && selectedDocument < (int)documents.size()))
|
|
||||||
save(selectedDocument);
|
|
||||||
if (ImGui::MenuItem("Export", "Ctrl+Shift+S", false,
|
|
||||||
selectedDocument >= 0 && selectedDocument < (int)documents.size()))
|
|
||||||
dialog.file_save(util::Dialog::DOCUMENT_EXPORT);
|
|
||||||
if (ImGui::MenuItem("Close", "Ctrl+W", false, selectedDocument >= 0 && selectedDocument < (int)documents.size()))
|
|
||||||
close(selectedDocument);
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
if (ImGui::BeginMenu("Edit"))
|
|
||||||
{
|
|
||||||
auto isCanUndo =
|
|
||||||
selectedDocument >= 0 && selectedDocument < (int)documents.size() && documents[selectedDocument].can_undo();
|
|
||||||
auto isCanRedo =
|
|
||||||
selectedDocument >= 0 && selectedDocument < (int)documents.size() && documents[selectedDocument].can_redo();
|
|
||||||
if (ImGui::MenuItem("Undo", "Ctrl+Z", false, isCanUndo)) undo(selectedDocument);
|
|
||||||
if (ImGui::MenuItem("Redo", "Ctrl+Shift+Z", false, isCanRedo)) redo(selectedDocument);
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
ImGui::EndMenuBar();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::editor_documents_update(Resources& resources)
|
|
||||||
{
|
|
||||||
if (documents.empty() || !ImGui::BeginTabBar("schemas", ImGuiTabBarFlags_Reorderable)) return;
|
|
||||||
|
|
||||||
auto closeRequest = -1;
|
|
||||||
for (int i = 0; i < (int)documents.size(); ++i)
|
|
||||||
{
|
|
||||||
ImGui::PushID(i);
|
|
||||||
auto label = document_path_label_get(documents[i].path) + (documents[i].isDirty ? " *" : "");
|
|
||||||
label += std::format("###{}", documents[i].path.string());
|
|
||||||
|
|
||||||
auto isOpen = true;
|
|
||||||
auto isTabOpen = ImGui::BeginTabItem(label.c_str(), &isOpen);
|
|
||||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal))
|
|
||||||
ImGui::SetTooltip("%s", documents[i].path.string().c_str());
|
|
||||||
|
|
||||||
if (isTabOpen)
|
|
||||||
{
|
|
||||||
selectedDocument = i;
|
|
||||||
if (ImGui::BeginTabBar("##Document Tabs"))
|
|
||||||
{
|
|
||||||
if (ImGui::BeginTabItem("Dialogue"))
|
|
||||||
{
|
|
||||||
document_dialogue_update(resources, documents[i]);
|
|
||||||
ImGui::EndTabItem();
|
|
||||||
}
|
|
||||||
ImGui::EndTabBar();
|
|
||||||
}
|
|
||||||
ImGui::EndTabItem();
|
|
||||||
}
|
|
||||||
if (!isOpen) closeRequest = i;
|
|
||||||
ImGui::PopID();
|
|
||||||
}
|
|
||||||
ImGui::EndTabBar();
|
|
||||||
if (closeRequest >= 0) close(closeRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::document_dialogue_update(Resources& resources, Document& document)
|
|
||||||
{
|
|
||||||
auto* dialogue = document.dialogue_schema_get();
|
|
||||||
if (!dialogue || !dialogue->isExists)
|
|
||||||
{
|
|
||||||
ImGui::TextUnformatted("No dialogue.xml found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!dialogue->isValid)
|
|
||||||
{
|
|
||||||
ImGui::TextUnformatted("dialogue.xml is not valid.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::IsAnyItemActive()) document.edit_snapshot_begin();
|
|
||||||
std::vector<Editor::LoadedSchema> loadedSchemas{};
|
|
||||||
for (auto& schemaFile : document.schemas)
|
|
||||||
{
|
|
||||||
if (!schemaFile.isValid) continue;
|
|
||||||
loadedSchemas.push_back({.path = &schemaFile.path, .schema = &schemaFile.schema});
|
|
||||||
}
|
|
||||||
|
|
||||||
auto before = dialogue->schema;
|
|
||||||
if (document.editor.update(resources, dialogue->schema, dialogue->path, dialog, loadedSchemas))
|
|
||||||
{
|
|
||||||
before = document.edit_snapshot_take_or(std::move(before));
|
|
||||||
if (before.element_serialize(0) != dialogue->schema.element_serialize(0))
|
|
||||||
{
|
|
||||||
auto message = Document::change_message_get(before, dialogue->schema);
|
|
||||||
document.snapshot_push(std::move(before), message);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
document.isDirty = true;
|
|
||||||
}
|
|
||||||
else if (!ImGui::IsAnyItemActive())
|
|
||||||
document.edit_snapshot_clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::editor_update(Resources& resources)
|
|
||||||
{
|
|
||||||
if (ImGui::BeginChild("##Editor", ImVec2(), ImGuiChildFlags_None, EDITOR_CHILD_FLAGS))
|
|
||||||
{
|
|
||||||
editor_menu_update();
|
|
||||||
|
|
||||||
if (ImGui::BeginPopupContextWindow("developer-context",
|
|
||||||
ImGuiPopupFlags_MouseButtonRight | ImGuiPopupFlags_NoOpenOverItems))
|
|
||||||
{
|
|
||||||
auto isCanUndo =
|
|
||||||
selectedDocument >= 0 && selectedDocument < (int)documents.size() && documents[selectedDocument].can_undo();
|
|
||||||
auto isCanRedo =
|
|
||||||
selectedDocument >= 0 && selectedDocument < (int)documents.size() && documents[selectedDocument].can_redo();
|
|
||||||
if (ImGui::MenuItem("Undo", "Ctrl+Z", false, isCanUndo)) isContextUndoRequested = true;
|
|
||||||
if (ImGui::MenuItem("Redo", "Ctrl+Shift+Z", false, isCanRedo)) isContextRedoRequested = true;
|
|
||||||
ImGui::EndPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
editor_documents_update(resources);
|
|
||||||
}
|
|
||||||
ImGui::EndChild();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::update(Resources& resources, SDL_Window* window, state::Play* play)
|
|
||||||
{
|
|
||||||
dialog.window_set(window);
|
|
||||||
auto* settingsRoot = resources.settings.root();
|
|
||||||
|
|
||||||
if (!settingsRoot || !settingsRoot->isDeveloperConsoleEnabled)
|
|
||||||
{
|
|
||||||
resources.isDeveloper = false;
|
|
||||||
isDeveloperOpen = false;
|
|
||||||
action = {};
|
|
||||||
console.action = {};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!resources.isDeveloper && shortcut_is_pressed(ImGuiKey_GraveAccent))
|
|
||||||
{
|
|
||||||
resources.isDeveloper = true;
|
|
||||||
if (selectedTab == Tab::CONSOLE) console.focus_request();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!resources.isDeveloper)
|
|
||||||
{
|
|
||||||
isDeveloperOpen = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
recent_files_load();
|
|
||||||
|
|
||||||
auto* viewport = ImGui::GetMainViewport();
|
|
||||||
auto windowSize =
|
|
||||||
ImVec2(viewport->WorkSize.x * DEVELOPER_WINDOW_SIZE_RATIO, viewport->WorkSize.y * DEVELOPER_WINDOW_SIZE_RATIO);
|
|
||||||
auto windowPosition = ImVec2(viewport->WorkPos.x + (viewport->WorkSize.x * CENTER_RATIO),
|
|
||||||
viewport->WorkPos.y + (viewport->WorkSize.y * CENTER_RATIO));
|
|
||||||
ImGui::SetNextWindowPos(windowPosition, ImGuiCond_Appearing, ImVec2(CENTER_RATIO, CENTER_RATIO));
|
|
||||||
ImGui::SetNextWindowSize(windowSize, ImGuiCond_Appearing);
|
|
||||||
|
|
||||||
if (!isDeveloperOpen)
|
|
||||||
{
|
|
||||||
ImGui::SetNextWindowFocus();
|
|
||||||
if (selectedTab == Tab::CONSOLE) console.focus_request();
|
|
||||||
}
|
|
||||||
isDeveloperOpen = true;
|
|
||||||
|
|
||||||
isContextUndoRequested = false;
|
|
||||||
isContextRedoRequested = false;
|
|
||||||
action = {};
|
|
||||||
console.action = {};
|
|
||||||
gIsDocumentUndoMenuRequested = false;
|
|
||||||
gIsDocumentRedoMenuRequested = false;
|
|
||||||
|
|
||||||
if (selectedTab == Tab::EDITOR) editor_shortcuts_update();
|
|
||||||
dialog_selection_update();
|
|
||||||
|
|
||||||
auto gameStyle = ImGui::GetStyle();
|
|
||||||
ImGui::GetStyle() = ImGuiStyle{};
|
|
||||||
ImGui::StyleColorsDark();
|
|
||||||
ImGui::PushFont(resources.font.get(), resource::Font::SMALL);
|
|
||||||
|
|
||||||
if (ImGui::Begin("Developer", &resources.isDeveloper, DEVELOPER_WINDOW_FLAGS))
|
|
||||||
{
|
|
||||||
auto isFocused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
|
|
||||||
if (isFocused && (shortcut_is_pressed(ImGuiKey_GraveAccent) || shortcut_is_pressed(ImGuiKey_Escape)))
|
|
||||||
resources.isDeveloper = false;
|
|
||||||
|
|
||||||
if (ImGui::BeginTabBar("##Developer Tabs"))
|
|
||||||
{
|
|
||||||
auto isWasConsole = selectedTab == Tab::CONSOLE;
|
|
||||||
if (ImGui::BeginTabItem("Console"))
|
|
||||||
{
|
|
||||||
selectedTab = Tab::CONSOLE;
|
|
||||||
if (!isWasConsole) console.focus_request();
|
|
||||||
console.update(resources, play);
|
|
||||||
ImGui::EndTabItem();
|
|
||||||
}
|
|
||||||
if (ImGui::BeginTabItem("Editor"))
|
|
||||||
{
|
|
||||||
selectedTab = Tab::EDITOR;
|
|
||||||
editor_update(resources);
|
|
||||||
ImGui::EndTabItem();
|
|
||||||
}
|
|
||||||
ImGui::EndTabBar();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::End();
|
|
||||||
|
|
||||||
if (gIsDocumentUndoMenuRequested) isContextUndoRequested = true;
|
|
||||||
if (gIsDocumentRedoMenuRequested) isContextRedoRequested = true;
|
|
||||||
action = console.action;
|
|
||||||
if (isContextUndoRequested) undo(selectedDocument);
|
|
||||||
if (isContextRedoRequested) redo(selectedDocument);
|
|
||||||
|
|
||||||
toasts.update();
|
|
||||||
|
|
||||||
ImGui::PopFont();
|
|
||||||
ImGui::GetStyle() = gameStyle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "../resources.hpp"
|
|
||||||
#include "../state/play/toasts.hpp"
|
|
||||||
#include "../util/dialog.hpp"
|
|
||||||
#include "console.hpp"
|
|
||||||
#include "document.hpp"
|
|
||||||
|
|
||||||
namespace game::state
|
|
||||||
{
|
|
||||||
class Play;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace game::developer
|
|
||||||
{
|
|
||||||
class Window
|
|
||||||
{
|
|
||||||
enum class Tab
|
|
||||||
{
|
|
||||||
CONSOLE,
|
|
||||||
EDITOR
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<Document> documents{};
|
|
||||||
Console console{};
|
|
||||||
util::Dialog dialog{};
|
|
||||||
state::play::Toasts toasts{};
|
|
||||||
std::vector<std::filesystem::path> recentFiles{};
|
|
||||||
Tab selectedTab{Tab::CONSOLE};
|
|
||||||
int selectedDocument{};
|
|
||||||
bool isContextUndoRequested{};
|
|
||||||
bool isContextRedoRequested{};
|
|
||||||
bool isRecentFilesLoaded{};
|
|
||||||
bool isDeveloperOpen{};
|
|
||||||
|
|
||||||
void folder_open(const std::filesystem::path&);
|
|
||||||
void save(int index);
|
|
||||||
void export_to(int index, const std::filesystem::path&);
|
|
||||||
void close(int index);
|
|
||||||
void undo(int index);
|
|
||||||
void redo(int index);
|
|
||||||
Document document_load(const std::filesystem::path&);
|
|
||||||
void editor_update(Resources&);
|
|
||||||
void editor_shortcuts_update();
|
|
||||||
void editor_menu_update();
|
|
||||||
void editor_documents_update(Resources&);
|
|
||||||
void document_dialogue_update(Resources&, Document&);
|
|
||||||
void dialog_selection_update();
|
|
||||||
void recent_files_load();
|
|
||||||
void recent_files_save();
|
|
||||||
void recent_file_push(const std::filesystem::path&);
|
|
||||||
|
|
||||||
public:
|
|
||||||
Console::Action action{};
|
|
||||||
|
|
||||||
void dropped_file_open(const char*);
|
|
||||||
void update(Resources&, SDL_Window*, state::Play*);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,380 @@
|
|||||||
|
#include "dialogue.h"
|
||||||
|
#include "play.h"
|
||||||
|
#include "ui.h"
|
||||||
|
#include <cfloat>
|
||||||
|
#include <imgui_internal.h>
|
||||||
|
|
||||||
|
constexpr float LIFE_MAX = 10;
|
||||||
|
constexpr float OSCILLATION_AMPLITUDE = 2;
|
||||||
|
constexpr float OSCILLATION_FREQUENCY = 1;
|
||||||
|
constexpr float OSCILLATION_SPREAD = 0.16f;
|
||||||
|
constexpr float SCREENSHAKE_MAGNITUDE = 0.03f;
|
||||||
|
constexpr int SCREENSHAKE_TIME = 15;
|
||||||
|
constexpr float SHAKE_AMPLITUDE = 1;
|
||||||
|
constexpr float SHAKE_FREQUENCY = 30;
|
||||||
|
constexpr float TEXT_BIG = 1.25f;
|
||||||
|
constexpr float TEXT_SMALL = 0.75f;
|
||||||
|
constexpr float WIDTH_MULTIPLIER = 0.3f;
|
||||||
|
constexpr float HEIGHT_LINES = 6;
|
||||||
|
constexpr float INDICATOR_BOB = 0.12f;
|
||||||
|
constexpr float INDICATOR_SIZE = 0.35f;
|
||||||
|
constexpr float INDICATOR_SPEED = 2;
|
||||||
|
|
||||||
|
static bool is_effect_on(const Effect& effect, int at)
|
||||||
|
{
|
||||||
|
return effect.start <= at && at < effect.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_silent(const Entry& entry, int at)
|
||||||
|
{
|
||||||
|
for (const Effect& effect : entry.effects)
|
||||||
|
if (effect.type == EFFECT_SILENT && is_effect_on(effect, at))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float hash_unit(Uint32 value)
|
||||||
|
{
|
||||||
|
value ^= value >> 16;
|
||||||
|
value *= 0x7feb352du;
|
||||||
|
value ^= value >> 15;
|
||||||
|
value *= 0x846ca68bu;
|
||||||
|
value ^= value >> 16;
|
||||||
|
return (value & 0xffffu) / 65535.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The scale, color and offset the entry's effects give its character at an index.
|
||||||
|
static float letter_style_get(const Entry& entry, int at, float time, ImU32& color, ImVec2& offset)
|
||||||
|
{
|
||||||
|
float scale = 1;
|
||||||
|
color = ImGui::GetColorU32(ImGuiCol_Text);
|
||||||
|
offset = {};
|
||||||
|
for (const Effect& effect : entry.effects)
|
||||||
|
{
|
||||||
|
if (!is_effect_on(effect, at))
|
||||||
|
continue;
|
||||||
|
Uint32 seed = (Uint32)std::floor(time * SHAKE_FREQUENCY) * 747796405u + (Uint32)at * 2891336453u;
|
||||||
|
switch (effect.type)
|
||||||
|
{
|
||||||
|
case EFFECT_COLOR:
|
||||||
|
color = ImGui::GetColorU32(imvec4_get({effect.color.r, effect.color.g, effect.color.b, 1}));
|
||||||
|
break;
|
||||||
|
case EFFECT_BIG:
|
||||||
|
scale = TEXT_BIG;
|
||||||
|
break;
|
||||||
|
case EFFECT_SMALL:
|
||||||
|
scale = TEXT_SMALL;
|
||||||
|
break;
|
||||||
|
case EFFECT_OSCILLATION:
|
||||||
|
offset.y += std::sin((time * OSCILLATION_FREQUENCY + at * OSCILLATION_SPREAD) * 2 * PI) *
|
||||||
|
OSCILLATION_AMPLITUDE;
|
||||||
|
break;
|
||||||
|
case EFFECT_SHAKE:
|
||||||
|
offset.x += (hash_unit(seed) * 2 - 1) * SHAKE_AMPLITUDE;
|
||||||
|
offset.y += (hash_unit(seed ^ 0xa511e9b3u) * 2 - 1) * SHAKE_AMPLITUDE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draws the entry's first count characters with their effects, wrapping whole words to the window.
|
||||||
|
static void letters_draw(const Entry& entry, int count, float time)
|
||||||
|
{
|
||||||
|
ImFont* font = ImGui::GetFont();
|
||||||
|
float fontSize = ImGui::GetFontSize();
|
||||||
|
ImVec2 start = ImGui::GetCursorScreenPos();
|
||||||
|
float wrap = start.x + ImGui::GetContentRegionAvail().x;
|
||||||
|
ImVec2 cursor = start;
|
||||||
|
float lineHeight = fontSize;
|
||||||
|
const char* end = entry.text.data() + entry.text.size();
|
||||||
|
auto width_get = [&](const char* from, const char* to, int at) {
|
||||||
|
ImU32 color;
|
||||||
|
ImVec2 offset;
|
||||||
|
return font->CalcTextSizeA(fontSize * letter_style_get(entry, at, time, color, offset), FLT_MAX, 0, from, to).x;
|
||||||
|
};
|
||||||
|
auto line_break = [&]() {
|
||||||
|
cursor = {start.x, cursor.y + lineHeight};
|
||||||
|
lineHeight = fontSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
int at = 0;
|
||||||
|
for (const char* it = entry.text.data(); it < end && at < count; at++)
|
||||||
|
{
|
||||||
|
unsigned int codepoint = 0;
|
||||||
|
const char* next = it + ImTextCharFromUtf8(&codepoint, it, end);
|
||||||
|
if (codepoint == '\n')
|
||||||
|
{
|
||||||
|
line_break();
|
||||||
|
it = next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bool isSpace = codepoint == ' ';
|
||||||
|
if (!isSpace && cursor.x > start.x && it[-1] == ' ')
|
||||||
|
{
|
||||||
|
float wordWidth = 0;
|
||||||
|
int wordAt = at;
|
||||||
|
for (const char* letter = it; letter < end && *letter != ' ' && *letter != '\n'; wordAt++)
|
||||||
|
{
|
||||||
|
unsigned int wordCodepoint = 0;
|
||||||
|
const char* letterNext = letter + ImTextCharFromUtf8(&wordCodepoint, letter, end);
|
||||||
|
wordWidth += width_get(letter, letterNext, wordAt);
|
||||||
|
letter = letterNext;
|
||||||
|
}
|
||||||
|
if (cursor.x + wordWidth > wrap && wordWidth <= wrap - start.x)
|
||||||
|
line_break();
|
||||||
|
}
|
||||||
|
ImU32 color;
|
||||||
|
ImVec2 offset;
|
||||||
|
float scale = letter_style_get(entry, at, time, color, offset);
|
||||||
|
float width = width_get(it, next, at);
|
||||||
|
if (cursor.x + width > wrap)
|
||||||
|
{
|
||||||
|
line_break();
|
||||||
|
if (isSpace)
|
||||||
|
{
|
||||||
|
it = next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::GetWindowDrawList()->AddText(font, fontSize * scale, {cursor.x + offset.x, cursor.y + offset.y}, color,
|
||||||
|
it, next);
|
||||||
|
cursor.x += width;
|
||||||
|
lineHeight = std::max(lineHeight, fontSize * scale);
|
||||||
|
it = next;
|
||||||
|
}
|
||||||
|
ImGui::Dummy({wrap - start.x, cursor.y + lineHeight - start.y});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Text::set(Play& play, Entry* next, TextMode mode, const std::string& onEnd)
|
||||||
|
{
|
||||||
|
if (!next)
|
||||||
|
return;
|
||||||
|
entry = next;
|
||||||
|
if (mode != TEXT_SEQUENCE)
|
||||||
|
{
|
||||||
|
idleOnEnd = onEnd;
|
||||||
|
isScene = next->isScene;
|
||||||
|
}
|
||||||
|
isFinished = false;
|
||||||
|
isStarted = false;
|
||||||
|
isBlipQueued = false;
|
||||||
|
isAnimationStarted = false;
|
||||||
|
animationTimePrevious = 0;
|
||||||
|
index = 0;
|
||||||
|
tick = 0;
|
||||||
|
life = 0;
|
||||||
|
Interrupt interrupt = mode == TEXT_SEQUENCE ? INTERRUPT_ALWAYS
|
||||||
|
: mode == TEXT_INTERRUPTIBLE ? INTERRUPT_IF_ALLOWED
|
||||||
|
: INTERRUPT_NEVER;
|
||||||
|
if (!next->animation.empty())
|
||||||
|
play.body_play(next->animation, TRANSITION_NORMAL, interrupt);
|
||||||
|
delayBase = play.character->dialogue.delay;
|
||||||
|
delay = delayBase;
|
||||||
|
for (Effect& effect : next->effects)
|
||||||
|
{
|
||||||
|
if (effect.start != 0)
|
||||||
|
continue;
|
||||||
|
if (effect.type == EFFECT_DELAY)
|
||||||
|
delay = delayBase = std::max(1, effect.delay);
|
||||||
|
if (effect.type == EFFECT_ANIMATION && !effect.animation.empty())
|
||||||
|
play.body_play(effect.animation, TRANSITION_FORCE);
|
||||||
|
}
|
||||||
|
if (!next->text.empty())
|
||||||
|
play.isTalking = !is_silent(*next, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Applies an effect as its character comes up: a delay or animation, a screen shake or a sound.
|
||||||
|
static void effect_apply(Play& play, Text& text, const Effect& effect)
|
||||||
|
{
|
||||||
|
if (effect.type == EFFECT_DELAY)
|
||||||
|
text.delay = text.delayBase = std::max(1, effect.delay);
|
||||||
|
else if (effect.type == EFFECT_ANIMATION && !effect.animation.empty())
|
||||||
|
play.body_play(effect.animation, TRANSITION_FORCE);
|
||||||
|
else if (effect.type == EFFECT_SCREENSHAKE)
|
||||||
|
play.shake_add(SCREENSHAKE_MAGNITUDE, SCREENSHAKE_TIME);
|
||||||
|
else if (effect.type == EFFECT_SOUND)
|
||||||
|
play.audio->play(effect.sound);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Text::effects_apply(Play& play, int at)
|
||||||
|
{
|
||||||
|
for (Effect& effect : entry->effects)
|
||||||
|
if (effect.start == at)
|
||||||
|
effect_apply(play, *this, effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Text::clear(Play& play)
|
||||||
|
{
|
||||||
|
if (!idleOnEnd.empty())
|
||||||
|
{
|
||||||
|
play.idle_override_set(idleOnEnd);
|
||||||
|
play.idle_play();
|
||||||
|
idleOnEnd.clear();
|
||||||
|
}
|
||||||
|
entry = nullptr;
|
||||||
|
isScene = false;
|
||||||
|
play.isTalking = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Text::is_interruptible()
|
||||||
|
{
|
||||||
|
return !entry || entry->isTerminal;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Text::update(Play& play, Vec2 screenSize)
|
||||||
|
{
|
||||||
|
if (!entry)
|
||||||
|
return;
|
||||||
|
Dialogue& dialogue = play.character->dialogue;
|
||||||
|
if (!isFinished)
|
||||||
|
{
|
||||||
|
if (!isStarted)
|
||||||
|
{
|
||||||
|
isStarted = true;
|
||||||
|
for (Effect& effect : entry->effects)
|
||||||
|
if (effect.start == 0 && (effect.type == EFFECT_SCREENSHAKE || effect.type == EFFECT_SOUND))
|
||||||
|
effect_apply(play, *this, effect);
|
||||||
|
}
|
||||||
|
if (++tick >= delay)
|
||||||
|
{
|
||||||
|
tick = 0;
|
||||||
|
index++;
|
||||||
|
delay = delayBase;
|
||||||
|
effects_apply(play, index);
|
||||||
|
const char* letter = entry->text.c_str();
|
||||||
|
const char* end = letter + entry->text.size();
|
||||||
|
unsigned int codepoint = 0;
|
||||||
|
for (int i = 0; i < index - 1 && letter < end; i++)
|
||||||
|
letter += ImTextCharFromUtf8(&codepoint, letter, end);
|
||||||
|
int letterSize = letter < end ? ImTextCharFromUtf8(&codepoint, letter, end) : 0;
|
||||||
|
auto pause = dialogue.pauses.find(std::string(letter, letterSize));
|
||||||
|
Pause found = pause == dialogue.pauses.end() ? Pause{} : pause->second;
|
||||||
|
bool isSilentEffect = is_silent(*entry, index - 1);
|
||||||
|
delay += found.delay;
|
||||||
|
play.isTalking = !found.isSilent && !isSilentEffect;
|
||||||
|
bool isBlipStep = dialogue.blipDelay > 0 && index % dialogue.blipDelay == 0;
|
||||||
|
if (isSilentEffect)
|
||||||
|
isBlipQueued = false;
|
||||||
|
else if (found.isSilent)
|
||||||
|
isBlipQueued |= isBlipStep;
|
||||||
|
else if (isBlipStep || isBlipQueued)
|
||||||
|
{
|
||||||
|
play.audio->play(dialogue.blip);
|
||||||
|
isBlipQueued = false;
|
||||||
|
}
|
||||||
|
if (index >= entry->length)
|
||||||
|
{
|
||||||
|
isFinished = true;
|
||||||
|
isBlipQueued = false;
|
||||||
|
play.isTalking = false;
|
||||||
|
isScene &= !entry->isTerminal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry->text.empty())
|
||||||
|
{
|
||||||
|
Anm2Animation* animation = play.actor.animation_get();
|
||||||
|
bool isReady = entry->animation.empty() || !animation || !play.actor.isPlaying ||
|
||||||
|
(!isAnimationStarted && animation->length <= 1) ||
|
||||||
|
(animation->isLoop && isAnimationStarted && play.actor.time < animationTimePrevious);
|
||||||
|
isAnimationStarted = true;
|
||||||
|
animationTimePrevious = play.actor.time;
|
||||||
|
if (entry->next.empty())
|
||||||
|
clear(play);
|
||||||
|
else if (isReady)
|
||||||
|
set(play, dialogue.entry_get(entry->next), TEXT_SEQUENCE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiStyle& style = ImGui::GetStyle();
|
||||||
|
ImVec2 size = {screenSize.x * WIDTH_MULTIPLIER - style.WindowPadding.x * 2,
|
||||||
|
ImGui::GetTextLineHeightWithSpacing() * HEIGHT_LINES};
|
||||||
|
ImGui::SetNextWindowSize(size);
|
||||||
|
ImGui::SetNextWindowPos({screenSize.x / 2 - size.x / 2, screenSize.y - size.y - style.WindowPadding.y});
|
||||||
|
if (ImGui::Begin("##Text", nullptr,
|
||||||
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||||
|
ImGuiWindowFlags_NoMove))
|
||||||
|
{
|
||||||
|
bool isHovered = ImGui::IsWindowHovered();
|
||||||
|
bool isSpace = ImGui::IsKeyReleased(ImGuiKey_Space);
|
||||||
|
bool isAdvance = (isHovered && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) || isSpace;
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {style.ItemSpacing.x, 0});
|
||||||
|
if (ImGui::BeginTabBar("##Name"))
|
||||||
|
{
|
||||||
|
if (ImGui::BeginTabItem(play.character->name.c_str()))
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
ImGui::PushFont(nullptr, FONT_NORMAL);
|
||||||
|
letters_draw(*entry, index, (float)ImGui::GetTime());
|
||||||
|
|
||||||
|
ImVec2 windowPosition = ImGui::GetWindowPos();
|
||||||
|
ImVec2 contentMin = {windowPosition.x + style.WindowPadding.x, windowPosition.y + style.WindowPadding.y};
|
||||||
|
ImVec2 contentMax = {windowPosition.x + ImGui::GetWindowWidth() - style.WindowPadding.x,
|
||||||
|
windowPosition.y + ImGui::GetWindowHeight() - style.WindowPadding.y};
|
||||||
|
Entry* current = entry;
|
||||||
|
if (!isFinished && isAdvance)
|
||||||
|
{
|
||||||
|
for (int skipped = index + 1; skipped <= entry->length; skipped++)
|
||||||
|
effects_apply(play, skipped);
|
||||||
|
index = entry->length;
|
||||||
|
isFinished = true;
|
||||||
|
play.isTalking = false;
|
||||||
|
isScene &= !entry->isTerminal;
|
||||||
|
}
|
||||||
|
else if (isFinished && !current->choices.empty())
|
||||||
|
{
|
||||||
|
float buttonY = contentMax.y - ImGui::GetFrameHeight();
|
||||||
|
float separatorY = buttonY - style.ItemSpacing.y;
|
||||||
|
ImGui::GetWindowDrawList()->AddLine({contentMin.x, separatorY}, {contentMax.x, separatorY},
|
||||||
|
ImGui::GetColorU32(ImGuiCol_Separator));
|
||||||
|
ImGui::SetCursorScreenPos({contentMin.x, buttonY});
|
||||||
|
ImVec2 buttonSize = {row_width_get((int)current->choices.size()), 0};
|
||||||
|
for (int i = 0; i < (int)current->choices.size(); i++)
|
||||||
|
{
|
||||||
|
Choice& choice = current->choices[i];
|
||||||
|
ImGui::PushID(i);
|
||||||
|
if (is_fx(ImGui::Button(choice.text.c_str(), buttonSize)))
|
||||||
|
set(play, dialogue.entry_get(choice.next), TEXT_SEQUENCE);
|
||||||
|
ImGui::SetItemTooltip("%s", choice.text.c_str());
|
||||||
|
ImGui::PopID();
|
||||||
|
if (i + 1 < (int)current->choices.size())
|
||||||
|
ImGui::SameLine();
|
||||||
|
}
|
||||||
|
if (isHovered && isSpace)
|
||||||
|
{
|
||||||
|
play.audio->play(play.character->sounds[SOUND_SELECT]);
|
||||||
|
set(play, dialogue.entry_get(current->choices.front().next), TEXT_SEQUENCE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isFinished && !current->next.empty())
|
||||||
|
{
|
||||||
|
float side = ImGui::GetTextLineHeightWithSpacing();
|
||||||
|
ImVec2 center = {contentMax.x - side / 2 +
|
||||||
|
std::sin((float)ImGui::GetTime() * INDICATOR_SPEED) * side * INDICATOR_BOB,
|
||||||
|
contentMax.y - side / 2};
|
||||||
|
float half = side * INDICATOR_SIZE;
|
||||||
|
ImGui::GetWindowDrawList()->AddTriangleFilled({center.x + half, center.y},
|
||||||
|
{center.x - half, center.y - half},
|
||||||
|
{center.x - half, center.y + half},
|
||||||
|
ImGui::GetColorU32(ImGuiCol_Text));
|
||||||
|
if (isAdvance)
|
||||||
|
{
|
||||||
|
play.audio->play(play.character->sounds[SOUND_SELECT]);
|
||||||
|
set(play, dialogue.entry_get(current->next), TEXT_SEQUENCE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isFinished && isAdvance)
|
||||||
|
clear(play);
|
||||||
|
ImGui::PopFont();
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
|
||||||
|
if (entry && isFinished && entry->isTerminal && (life += ImGui::GetIO().DeltaTime) > LIFE_MAX)
|
||||||
|
clear(play);
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "character.h"
|
||||||
|
|
||||||
|
class Play;
|
||||||
|
|
||||||
|
enum TextMode
|
||||||
|
{
|
||||||
|
TEXT_INTERRUPTIBLE,
|
||||||
|
TEXT_UNINTERRUPTIBLE,
|
||||||
|
TEXT_SEQUENCE
|
||||||
|
};
|
||||||
|
|
||||||
|
// Plays out a dialogue entry a character at a time with its effects, then offers its choices or the entry it leads to.
|
||||||
|
// A scene holds the game still until its last entry finishes.
|
||||||
|
class Text
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string idleOnEnd;
|
||||||
|
Entry* entry = nullptr;
|
||||||
|
float animationTimePrevious = 0;
|
||||||
|
float life = 0;
|
||||||
|
int delay = 0;
|
||||||
|
int delayBase = 0;
|
||||||
|
int index = 0;
|
||||||
|
int tick = 0;
|
||||||
|
bool isAnimationStarted = false;
|
||||||
|
bool isBlipQueued = false;
|
||||||
|
bool isFinished = false;
|
||||||
|
bool isScene = false;
|
||||||
|
bool isStarted = false;
|
||||||
|
|
||||||
|
void set(Play& play, Entry* next, TextMode mode = TEXT_INTERRUPTIBLE, const std::string& onEnd = "");
|
||||||
|
void update(Play& play, Vec2 screenSize);
|
||||||
|
void clear(Play& play);
|
||||||
|
bool is_interruptible();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void effects_apply(Play& play, int at);
|
||||||
|
};
|
||||||
-1073
File diff suppressed because it is too large.
Load diff
-253
@@ -1,253 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
#include "render/canvas.hpp"
|
|
||||||
#include "resource/character.hpp"
|
|
||||||
#include "util/measurement.hpp"
|
|
||||||
|
|
||||||
namespace game
|
|
||||||
{
|
|
||||||
enum EntityType
|
|
||||||
{
|
|
||||||
ACTOR,
|
|
||||||
CHARACTER,
|
|
||||||
CURSOR,
|
|
||||||
AUTOCURSOR,
|
|
||||||
ITEM,
|
|
||||||
PARTICLE
|
|
||||||
};
|
|
||||||
|
|
||||||
class Entity
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using Anm2 = resource::xml::Anm2;
|
|
||||||
using FrameOptional = Anm2::FrameOptional;
|
|
||||||
|
|
||||||
static constexpr auto UPDATE_RATE = 60.0f;
|
|
||||||
static constexpr auto DIGESTION_MAX = 100.0f;
|
|
||||||
|
|
||||||
enum Mode
|
|
||||||
{
|
|
||||||
PLAY,
|
|
||||||
PLAY_FORCE,
|
|
||||||
SET,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum State
|
|
||||||
{
|
|
||||||
STOPPED,
|
|
||||||
PLAYING
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class Transition
|
|
||||||
{
|
|
||||||
NOW,
|
|
||||||
AFTER_CURRENT,
|
|
||||||
IF_IDLE
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class Interrupt
|
|
||||||
{
|
|
||||||
IF_ALLOWED,
|
|
||||||
ALWAYS,
|
|
||||||
NEVER
|
|
||||||
};
|
|
||||||
|
|
||||||
enum CursorState
|
|
||||||
{
|
|
||||||
DEFAULT,
|
|
||||||
HOVER,
|
|
||||||
ACTION
|
|
||||||
};
|
|
||||||
|
|
||||||
enum SpritesheetType
|
|
||||||
{
|
|
||||||
NORMAL,
|
|
||||||
ALTERNATE
|
|
||||||
};
|
|
||||||
|
|
||||||
class Override
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
enum Mode
|
|
||||||
{
|
|
||||||
SET,
|
|
||||||
ADD
|
|
||||||
};
|
|
||||||
|
|
||||||
using Function = void (*)(Override&);
|
|
||||||
|
|
||||||
int id{-1};
|
|
||||||
Anm2::Type type{Anm2::NONE};
|
|
||||||
Mode mode{SET};
|
|
||||||
FrameOptional frame{};
|
|
||||||
std::optional<float> time{};
|
|
||||||
Function function{nullptr};
|
|
||||||
|
|
||||||
FrameOptional frameBase{};
|
|
||||||
std::optional<float> timeStart{};
|
|
||||||
|
|
||||||
float cycles{};
|
|
||||||
|
|
||||||
Override() = default;
|
|
||||||
Override(int, Anm2::Type, Mode, FrameOptional = {}, std::optional<float> = std::nullopt, Function = nullptr,
|
|
||||||
float = 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AnimationRequest
|
|
||||||
{
|
|
||||||
std::string animation{};
|
|
||||||
std::string appendID{};
|
|
||||||
int index{-1};
|
|
||||||
float time{};
|
|
||||||
float speedMultiplier{1.0f};
|
|
||||||
Mode mode{PLAY};
|
|
||||||
Transition transition{Transition::NOW};
|
|
||||||
Interrupt interrupt{Interrupt::IF_ALLOWED};
|
|
||||||
|
|
||||||
inline bool empty() const { return animation.empty() && index == -1; };
|
|
||||||
};
|
|
||||||
|
|
||||||
EntityType entityType{ACTOR};
|
|
||||||
|
|
||||||
int fps{30};
|
|
||||||
std::map<int, Anm2::Spritesheet> spritesheets{};
|
|
||||||
std::map<int, Anm2::Layer> layers{};
|
|
||||||
std::map<int, Anm2::Null> nulls{};
|
|
||||||
std::map<int, Anm2::Event> events{};
|
|
||||||
std::map<int, Anm2::Sound> sounds{};
|
|
||||||
std::unordered_map<std::string, int> layerMap{};
|
|
||||||
std::unordered_map<std::string, int> nullMap{};
|
|
||||||
std::unordered_map<std::string, int> eventMap{};
|
|
||||||
std::string defaultAnimation{};
|
|
||||||
std::string defaultAnimationOverride{};
|
|
||||||
int defaultAnimationID{-1};
|
|
||||||
std::vector<Anm2::Animation> animations{};
|
|
||||||
std::unordered_map<std::string, int> animationMap{};
|
|
||||||
std::unordered_map<int, std::string> animationMapReverse{};
|
|
||||||
std::unordered_set<std::string> invalidAnimations{};
|
|
||||||
std::string path{};
|
|
||||||
bool isValid{};
|
|
||||||
Anm2::Flags flags{};
|
|
||||||
|
|
||||||
State state{STOPPED};
|
|
||||||
Mode mode{PLAY};
|
|
||||||
glm::vec2 position{};
|
|
||||||
float time{};
|
|
||||||
bool isShowLayers{};
|
|
||||||
bool isShowNulls{};
|
|
||||||
bool isVisible{true};
|
|
||||||
int animationIndex{-1};
|
|
||||||
int playedEventID{-1};
|
|
||||||
float startTime{};
|
|
||||||
float speedMultiplier{};
|
|
||||||
AnimationRequest pendingAnimation{};
|
|
||||||
AnimationRequest currentAnimation{};
|
|
||||||
AnimationRequest nextAnimation{};
|
|
||||||
std::unordered_set<int> playedTriggers{};
|
|
||||||
std::vector<Override> overrides{};
|
|
||||||
|
|
||||||
resource::xml::Character data;
|
|
||||||
float weight{};
|
|
||||||
int stage{0};
|
|
||||||
float calories{};
|
|
||||||
float capacity{};
|
|
||||||
float digestionProgress{};
|
|
||||||
float digestionRate{0.05f};
|
|
||||||
int digestionTimer{};
|
|
||||||
bool isDigesting{};
|
|
||||||
bool isJustDigested{};
|
|
||||||
float totalCaloriesConsumed{};
|
|
||||||
int totalItemsSpawned{};
|
|
||||||
int totalItemsConsumed{};
|
|
||||||
float totalPlaytimeSeconds{};
|
|
||||||
int talkOverrideID{};
|
|
||||||
float talkTimer{};
|
|
||||||
bool isTalking{};
|
|
||||||
std::vector<int> animationTalkDurations{};
|
|
||||||
int blinkOverrideID{};
|
|
||||||
float blinkTimer{};
|
|
||||||
bool isBlinking{};
|
|
||||||
std::vector<int> animationBlinkDurations{};
|
|
||||||
bool isStageUp{};
|
|
||||||
bool isStageUpDuring{};
|
|
||||||
bool isJustStageUp{};
|
|
||||||
bool isJustStageFinal{};
|
|
||||||
SpritesheetType spritesheetType{};
|
|
||||||
std::map<int, Override> interactAreaOverrides{};
|
|
||||||
std::unordered_map<int, int> expandAreaOverrideLayerIDs{};
|
|
||||||
std::unordered_map<int, int> expandAreaOverrideNullIDs{};
|
|
||||||
|
|
||||||
CursorState cursorState{DEFAULT};
|
|
||||||
int interactTypeID{-1};
|
|
||||||
std::optional<glm::vec2> positionOverride{};
|
|
||||||
|
|
||||||
bool isToBeDeleted{};
|
|
||||||
bool isRemoveOnAnimationEnd{};
|
|
||||||
bool isHeld{};
|
|
||||||
int schemaID{};
|
|
||||||
int rotationOverrideID{};
|
|
||||||
glm::vec2 velocity{};
|
|
||||||
float angularVelocity{};
|
|
||||||
float phase{};
|
|
||||||
float magnetTime{};
|
|
||||||
bool isCollected{};
|
|
||||||
bool isMagnetized{};
|
|
||||||
|
|
||||||
Entity() = default;
|
|
||||||
Entity(const Entity&);
|
|
||||||
Entity(Entity&&) noexcept;
|
|
||||||
Entity& operator=(const Entity&);
|
|
||||||
Entity& operator=(Entity&&) noexcept;
|
|
||||||
Entity(resource::xml::Anm2, glm::vec2 position = {}, Mode = PLAY, float time = 0.0f, int animationIndex = -1);
|
|
||||||
Entity(resource::xml::Character&, glm::ivec2);
|
|
||||||
Entity(resource::xml::Anm2, glm::ivec2 position, int id, glm::vec2 velocity = {}, float rotation = 0.0f);
|
|
||||||
|
|
||||||
bool is_playing(const std::string& name = {});
|
|
||||||
glm::vec4 null_frame_rect(int = -1, int animationIndex = -1, float time = -1.0f);
|
|
||||||
glm::vec4 rect();
|
|
||||||
int animation_index_get(const std::string&);
|
|
||||||
resource::xml::Anm2::Animation* animation_get(const std::string&);
|
|
||||||
resource::xml::Anm2::Animation* animation_get(int = -1);
|
|
||||||
resource::xml::Anm2::Frame frame_generate(resource::xml::Anm2::Item&, float, resource::xml::Anm2::Type,
|
|
||||||
int id = -1);
|
|
||||||
resource::xml::Anm2::Item* item_get(resource::xml::Anm2::Type, int = -1, int = -1);
|
|
||||||
void consume_played_event();
|
|
||||||
void play(AnimationRequest);
|
|
||||||
bool is_animation_request() const;
|
|
||||||
bool is_animation_finished();
|
|
||||||
void render(resource::Shader& textureShader, resource::Shader& rectShader, Canvas&);
|
|
||||||
|
|
||||||
float weight_get(util::measurement::System = util::measurement::METRIC);
|
|
||||||
float digestion_rate_get();
|
|
||||||
float capacity_percent_get() const;
|
|
||||||
float max_capacity() const;
|
|
||||||
bool is_over_capacity() const;
|
|
||||||
bool is_max_capacity() const;
|
|
||||||
int stage_get() const;
|
|
||||||
int stage_from_weight_get(float weight) const;
|
|
||||||
int stage_max_get() const;
|
|
||||||
int stage_count_get() const;
|
|
||||||
float stage_progress_get();
|
|
||||||
float stage_threshold_get(int stage = -1, util::measurement::System = util::measurement::METRIC) const;
|
|
||||||
float stage_threshold_next_get(util::measurement::System = util::measurement::METRIC) const;
|
|
||||||
void expand_areas_apply();
|
|
||||||
void spritesheet_set(SpritesheetType);
|
|
||||||
void default_animation_override_set(const std::string&);
|
|
||||||
std::string idle_animation_get() const;
|
|
||||||
std::string animation_append_id_get() const;
|
|
||||||
|
|
||||||
void update();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void anm2_set(const resource::xml::Anm2&);
|
|
||||||
void animation_start(int, const AnimationRequest&);
|
|
||||||
bool animation_request_apply(AnimationRequest&);
|
|
||||||
void pending_animation_apply();
|
|
||||||
void animation_update();
|
|
||||||
void character_values_clamp();
|
|
||||||
void character_update();
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
+594
@@ -0,0 +1,594 @@
|
|||||||
|
#include "play.h"
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
constexpr float AUTO_ACCELERATION = 0.45f;
|
||||||
|
constexpr float AUTO_ANGLE_STEP = 0.08f;
|
||||||
|
constexpr float AUTO_BRAKING = 2;
|
||||||
|
constexpr float AUTO_CALORIE_EPSILON = 0.001f;
|
||||||
|
constexpr float AUTO_DIGEST_ACCELERATION = 0.15f;
|
||||||
|
constexpr float AUTO_DIGEST_RADIUS = 0.25f;
|
||||||
|
constexpr float AUTO_DIGEST_SPEED = 3;
|
||||||
|
constexpr float AUTO_SCALE_MAX = 2;
|
||||||
|
constexpr float AUTO_SCALE_MIN = 0.5f;
|
||||||
|
constexpr float AUTO_SCALE_AREA = 512.0f * 512.0f;
|
||||||
|
constexpr float AUTO_SPEED = 9;
|
||||||
|
constexpr float AUTO_TARGET_EPSILON = 0.001f;
|
||||||
|
constexpr int AUTO_CLICK_COOLDOWN = 45;
|
||||||
|
constexpr float HOLD_SPIN = 0.1f;
|
||||||
|
constexpr float ROTATION_GROUND_DAMPING = 0.85f;
|
||||||
|
constexpr float ROTATION_MAX = 90;
|
||||||
|
constexpr float ROTATION_RETURN_SPEED = 120;
|
||||||
|
constexpr float THROW_THRESHOLD = 10;
|
||||||
|
|
||||||
|
void Play::item_add(int id, Vec2 position, Vec2 velocity, float rotation)
|
||||||
|
{
|
||||||
|
ItemEntity& entity = items.emplace_back(ItemEntity{character->item_actor_get(id), velocity, 0, id});
|
||||||
|
entity.actor.position = position;
|
||||||
|
entity.actor.overrides.push_back({.rotation = rotation, .target = TARGET_ROOT, .isRotation = true});
|
||||||
|
discovered[id] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Play::spawn_delay_get()
|
||||||
|
{
|
||||||
|
return std::max(1, (int)std::round(character->spawnDelay * std::pow(character->spawnDelayMultiplier, stage)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static float spawn_weight_get(Play& play, int id)
|
||||||
|
{
|
||||||
|
Item& item = play.character->items[id];
|
||||||
|
if (item.calories > play.capacity_max_get() || item.rarity < 0)
|
||||||
|
return 0;
|
||||||
|
return std::max(0.0f, play.character->rarities[item.rarity].weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Play::is_spawn_possible()
|
||||||
|
{
|
||||||
|
if ((int)items.size() >= character->spawnLimit)
|
||||||
|
return false;
|
||||||
|
for (int i = 0; i < (int)character->items.size(); i++)
|
||||||
|
if (spawn_weight_get(*this, i) > 0)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Play::spawn_queue_add(int id)
|
||||||
|
{
|
||||||
|
if (id < 0 || id >= (int)character->items.size())
|
||||||
|
return;
|
||||||
|
Item& item = character->items[id];
|
||||||
|
audio->play(character->sounds[SOUND_SUMMON]);
|
||||||
|
if (item.rarity >= 0)
|
||||||
|
audio->play(character->rarities[item.rarity].sound);
|
||||||
|
spawnQueue.push_back(id);
|
||||||
|
spawnDelay = spawn_delay_get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queues an item picked at random by rarity, once the spawn delay is up.
|
||||||
|
void Play::spawn_queue_add()
|
||||||
|
{
|
||||||
|
if (spawnDelay > 0 || !is_spawn_possible())
|
||||||
|
return;
|
||||||
|
float total = 0;
|
||||||
|
for (int i = 0; i < (int)character->items.size(); i++)
|
||||||
|
total += spawn_weight_get(*this, i);
|
||||||
|
float roll = random_get(0, total);
|
||||||
|
for (int i = 0; i < (int)character->items.size(); i++)
|
||||||
|
if (float weight = spawn_weight_get(*this, i); weight > 0 && (roll -= weight) < 0)
|
||||||
|
return spawn_queue_add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where an eat area takes food: its null now, or at the start of its animation when it isn't showing.
|
||||||
|
Rect Play::eat_rect_get(const EatArea& eatArea)
|
||||||
|
{
|
||||||
|
Rect rect = actor.null_rect_get(eatArea.null);
|
||||||
|
if (!is_rect_valid(rect))
|
||||||
|
rect = actor.null_rect_get(eatArea.null, actor.anm2->animation_get(eatArea.animation + suffix_get()), 0);
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forgets which item is being eaten, and lets go of the held one when asked.
|
||||||
|
void Play::item_drop(Hold hold)
|
||||||
|
{
|
||||||
|
heldItem = hold == HOLD_RELEASE ? -1 : heldItem;
|
||||||
|
eatAnimation = hold == HOLD_RELEASE ? -1 : eatAnimation;
|
||||||
|
eatItem = -1;
|
||||||
|
eatEvent = -1;
|
||||||
|
eatRect = {NAN, NAN, NAN, NAN};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Actor& pointer_actor_get(Play& play)
|
||||||
|
{
|
||||||
|
return play.isAutofeedActive ? play.autoCursor : play.cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Play::items_update(Pointer pointer)
|
||||||
|
{
|
||||||
|
Character& data = *character;
|
||||||
|
Actor& active = pointer_actor_get(*this);
|
||||||
|
int areaIndex = data.area_get(stage);
|
||||||
|
Area area = areaIndex >= 0 ? data.areas[areaIndex] : Area{};
|
||||||
|
bool isOver = is_over_capacity();
|
||||||
|
auto text_play = [&](const char* hook) {
|
||||||
|
if (!pointer.isAutomated)
|
||||||
|
text.set(*this, data.dialogue.hook_get(hook));
|
||||||
|
};
|
||||||
|
auto held_release = [&](bool isThrown) {
|
||||||
|
item_drop();
|
||||||
|
active.play(data.cursorIdle);
|
||||||
|
if (!isThrown && !actor.pending)
|
||||||
|
idle_play();
|
||||||
|
};
|
||||||
|
if (text.isScene && heldItem >= 0)
|
||||||
|
{
|
||||||
|
held_release(false);
|
||||||
|
isEatTextPending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEatTextPending && eatAnimation == actor.animation && (!actor.isPlaying || actor.is_finished()))
|
||||||
|
{
|
||||||
|
text.set(*this, data.dialogue.hook_get(isEatTextFull ? "EatFull" : "Eat"));
|
||||||
|
isEatTextPending = false;
|
||||||
|
eatAnimation = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wasItemHovered = isItemHovered;
|
||||||
|
isItemHovered = false;
|
||||||
|
tooltipItem = -1;
|
||||||
|
if (spawnDelay > 0)
|
||||||
|
spawnDelay--;
|
||||||
|
for (int id : spawnQueue)
|
||||||
|
{
|
||||||
|
Rect spawn = areaIndex >= 0 ? area.spawn : bounds;
|
||||||
|
Vec2 position = {spawn.x + random_get(0, spawn.w), spawn.y + random_get(0, spawn.h)};
|
||||||
|
if (!discovered[id])
|
||||||
|
{
|
||||||
|
toast_add(STRING_ToastNewItemFound);
|
||||||
|
audio->play(data.sounds[SOUND_NEW_ITEM]);
|
||||||
|
}
|
||||||
|
item_add(id, position);
|
||||||
|
progress.totalSpawned++;
|
||||||
|
particles_spawn(data.summonParticles, position);
|
||||||
|
}
|
||||||
|
spawnQueue.clear();
|
||||||
|
|
||||||
|
if (heldItem >= 0 && heldItem < (int)items.size())
|
||||||
|
{
|
||||||
|
ItemEntity& held = items[heldItem];
|
||||||
|
Item& item = data.items[held.id];
|
||||||
|
float& rotation = held.rotation_get();
|
||||||
|
Vec2 delta = itemPointerPrevious - pointer.position;
|
||||||
|
held.actor.position = pointer.position;
|
||||||
|
held.velocity = {};
|
||||||
|
held.angularVelocity = (held.angularVelocity - delta.x * HOLD_SPIN) * area.friction;
|
||||||
|
rotation = std::clamp(rotation * area.friction, -ROTATION_MAX, ROTATION_MAX);
|
||||||
|
int removed = -1;
|
||||||
|
|
||||||
|
if (item.use == USE_EDIBLE)
|
||||||
|
{
|
||||||
|
bool isEdible = item.calories > 0 && progress.calories + item.calories <= capacity_max_get();
|
||||||
|
if (isJustItemHeld)
|
||||||
|
text_play(isEdible ? (isOver ? "FeedFull" : "Feed")
|
||||||
|
: item.calories > progress.capacity ? "LowCapacity"
|
||||||
|
: "Full");
|
||||||
|
isJustItemHeld = false;
|
||||||
|
for (EatArea& eatArea : data.eatAreas)
|
||||||
|
{
|
||||||
|
Rect rect = eat_rect_get(eatArea);
|
||||||
|
bool isInside = is_point_in_rect(rect, held.actor.position);
|
||||||
|
bool isAreaActive = isEdible && isInside;
|
||||||
|
bool isPending = eatItem == heldItem && eatEvent == eatArea.event;
|
||||||
|
bool isPendingActive = isPending && is_point_in_rect(eatRect, held.actor.position);
|
||||||
|
std::string eatName = eatArea.animation + suffix_get();
|
||||||
|
if (isAreaActive || isPendingActive)
|
||||||
|
{
|
||||||
|
if (isPending && eatArea.event >= 0 && actor.event == eatArea.event)
|
||||||
|
{
|
||||||
|
actor.event = -1;
|
||||||
|
progress.calories += item.calories;
|
||||||
|
progress.totalCalories += item.calories;
|
||||||
|
progress.capacity = std::clamp(progress.capacity + item.capacityBonus, data.capacityMin,
|
||||||
|
data.capacityMax);
|
||||||
|
progress.digestionRate = std::clamp(progress.digestionRate + item.digestionBonus,
|
||||||
|
data.digestionRateMin, data.digestionRateMax);
|
||||||
|
progress.totalConsumed++;
|
||||||
|
removed = heldItem;
|
||||||
|
item_drop();
|
||||||
|
eatAnimation = actor.animation;
|
||||||
|
isEatTextPending = !pointer.isAutomated;
|
||||||
|
isEatTextFull = isOver;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
eatItem = heldItem;
|
||||||
|
eatEvent = eatArea.event;
|
||||||
|
if (isAreaActive)
|
||||||
|
eatRect = rect;
|
||||||
|
if (!actor.is_playing(eatName))
|
||||||
|
body_play(eatArea.animation, TRANSITION_NORMAL, INTERRUPT_ALWAYS);
|
||||||
|
}
|
||||||
|
else if (eatItem == heldItem)
|
||||||
|
item_drop(HOLD_KEEP);
|
||||||
|
if (actor.is_playing(eatName) && !isInside)
|
||||||
|
text_play(isOver ? "FoodTakenFull" : "FoodTaken");
|
||||||
|
if (isAreaActive)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.use == USE_TOOL && item.isToggleSpritesheet &&
|
||||||
|
is_point_in_rect(actor.bounds_get(), held.actor.position) && data.alternateTexture &&
|
||||||
|
data.alternateSheet < (int)actor.sheets.size())
|
||||||
|
{
|
||||||
|
progress.isAlternate = !progress.isAlternate;
|
||||||
|
actor.sheets[data.alternateSheet] =
|
||||||
|
progress.isAlternate ? data.alternateTexture : data.anm2->sheets[data.alternateSheet];
|
||||||
|
audio->play(data.alternateSound);
|
||||||
|
removed = heldItem;
|
||||||
|
item_drop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removed < 0 && pointer.isReleased)
|
||||||
|
{
|
||||||
|
bool isThrown = std::abs(delta.x) >= THROW_THRESHOLD || std::abs(delta.y) >= THROW_THRESHOLD;
|
||||||
|
audio->play(data.sounds[isThrown ? SOUND_THROW : SOUND_RELEASE]);
|
||||||
|
if (isThrown)
|
||||||
|
text_play("Throw");
|
||||||
|
held.velocity = held.velocity - delta;
|
||||||
|
held_release(isThrown);
|
||||||
|
}
|
||||||
|
if (removed >= 0)
|
||||||
|
items.erase(items.begin() + removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
int moved = -1;
|
||||||
|
bool isTargetLocked = pointer.target >= 0 && pointer.target < (int)items.size();
|
||||||
|
float right = bounds.x + bounds.w;
|
||||||
|
float bottom = bounds.y + bounds.h;
|
||||||
|
for (int i = 0; i < (int)items.size(); i++)
|
||||||
|
{
|
||||||
|
ItemEntity& entity = items[i];
|
||||||
|
Vec2& position = entity.actor.position;
|
||||||
|
Vec2& velocity = entity.velocity;
|
||||||
|
float& rotation = entity.rotation_get();
|
||||||
|
float gravity = data.items[entity.id].gravity != 0 ? data.items[entity.id].gravity : area.gravity;
|
||||||
|
entity.actor.update(*audio);
|
||||||
|
position = position + velocity;
|
||||||
|
rotation += entity.angularVelocity;
|
||||||
|
|
||||||
|
if (is_point_in_rect(entity.actor.bounds_get(), pointer.position) && !pointer.isCaptured &&
|
||||||
|
(!isTargetLocked || i == pointer.target))
|
||||||
|
{
|
||||||
|
isItemHovered = true;
|
||||||
|
tooltipItem = heldItem == i || pointer.isAutomated ? -1 : entity.id;
|
||||||
|
active.play(data.cursorHover, TRANSITION_IF_IDLE);
|
||||||
|
cursorState = CURSOR_HOVER;
|
||||||
|
if (pointer.isClicked)
|
||||||
|
{
|
||||||
|
audio->play(data.sounds[SOUND_GRAB]);
|
||||||
|
particles_spawn(data.grabParticles, pointer.position);
|
||||||
|
isJustItemHeld = true;
|
||||||
|
}
|
||||||
|
if (pointer.isDown)
|
||||||
|
{
|
||||||
|
active.play(data.cursorGrab);
|
||||||
|
cursorState = CURSOR_ACTION;
|
||||||
|
heldItem = moved = i;
|
||||||
|
}
|
||||||
|
if (pointer.isRightClicked)
|
||||||
|
{
|
||||||
|
audio->play(data.sounds[SOUND_DISPOSE]);
|
||||||
|
active.play(data.cursorDispose, TRANSITION_FORCE, INTERRUPT_NEVER);
|
||||||
|
particles_spawn(data.disposeParticles, position);
|
||||||
|
heldItem = heldItem == i ? -1 : heldItem > i ? heldItem - 1 : heldItem;
|
||||||
|
moved = moved == i ? -1 : moved > i ? moved - 1 : moved;
|
||||||
|
items.erase(items.begin() + i--);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i != heldItem)
|
||||||
|
{
|
||||||
|
if (position.x <= bounds.x || position.x >= right)
|
||||||
|
{
|
||||||
|
position.x = position.x <= bounds.x ? bounds.x + 1 : right - 1;
|
||||||
|
velocity.x = -velocity.x * area.friction;
|
||||||
|
entity.angularVelocity = -entity.angularVelocity * area.friction;
|
||||||
|
rotation = -rotation;
|
||||||
|
audio->play(data.sounds[SOUND_BOUNCE]);
|
||||||
|
}
|
||||||
|
if (position.y <= bounds.y || position.y >= bottom)
|
||||||
|
{
|
||||||
|
if (position.y >= bottom && velocity.y <= gravity)
|
||||||
|
{
|
||||||
|
position.y = bottom;
|
||||||
|
velocity = {velocity.x * area.friction, 0};
|
||||||
|
rotation = std::remainder(rotation, 360.0f);
|
||||||
|
float step = ROTATION_RETURN_SPEED * ImGui::GetIO().DeltaTime;
|
||||||
|
rotation = std::abs(rotation) <= step ? 0 : rotation - std::copysign(step, rotation);
|
||||||
|
entity.angularVelocity *= ROTATION_GROUND_DAMPING;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
velocity.y = -velocity.y;
|
||||||
|
entity.angularVelocity *= area.friction;
|
||||||
|
audio->play(data.sounds[SOUND_BOUNCE]);
|
||||||
|
}
|
||||||
|
velocity.y *= area.friction;
|
||||||
|
}
|
||||||
|
velocity.x *= area.airResistance;
|
||||||
|
velocity.y += gravity;
|
||||||
|
}
|
||||||
|
position = {std::clamp(position.x, bounds.x, right), std::clamp(position.y, bounds.y, bottom)};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moved >= 0 && moved < (int)items.size() - 1)
|
||||||
|
{
|
||||||
|
ItemEntity entity = std::move(items[moved]);
|
||||||
|
items.erase(items.begin() + moved);
|
||||||
|
items.push_back(std::move(entity));
|
||||||
|
heldItem = (int)items.size() - 1;
|
||||||
|
}
|
||||||
|
if (wasItemHovered && !isItemHovered)
|
||||||
|
active.play(data.cursorIdle);
|
||||||
|
itemPointerPrevious = pointer.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Play::interact_update(Pointer pointer)
|
||||||
|
{
|
||||||
|
Character& data = *character;
|
||||||
|
Actor& active = pointer_actor_get(*this);
|
||||||
|
if (text.isScene)
|
||||||
|
{
|
||||||
|
isInteracting = isHoldInteracting = isHovering = false;
|
||||||
|
interactArea = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool wasInteracting = isInteracting;
|
||||||
|
bool wasHolding = isHoldInteracting;
|
||||||
|
bool wasHovering = isHovering;
|
||||||
|
isHovering = false;
|
||||||
|
isHoldInteracting &= isInteracting;
|
||||||
|
dialogueCooldowns.resize(data.interactAreas.size());
|
||||||
|
for (int& cooldown : dialogueCooldowns)
|
||||||
|
cooldown = text.entry ? std::max(0, cooldown - 1) : 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)data.interactAreas.size(); i++)
|
||||||
|
{
|
||||||
|
InteractArea& area = data.interactAreas[i];
|
||||||
|
Rect rect = actor.null_rect_get(area.null);
|
||||||
|
bool isInside = is_point_in_rect(rect, pointer.position);
|
||||||
|
if (cursorState == CURSOR_DEFAULT && isInside && !pointer.isCaptured && area.type == pointer.type)
|
||||||
|
{
|
||||||
|
cursorState = CURSOR_HOVER;
|
||||||
|
active.play(area.cursorHover, TRANSITION_IF_IDLE);
|
||||||
|
isHovering = true;
|
||||||
|
interactArea = i;
|
||||||
|
bool isHoldStart = area.isHold && (pointer.isClicked || (pointer.isDown && !isInteracting));
|
||||||
|
if (isHoldStart || (!area.isHold && pointer.isClicked))
|
||||||
|
{
|
||||||
|
if (area.isHold)
|
||||||
|
isInteracting = isHoldInteracting = true;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cursorState = CURSOR_ACTION;
|
||||||
|
active.play(area.cursorActive, TRANSITION_FORCE, INTERRUPT_ALWAYS);
|
||||||
|
}
|
||||||
|
audio->play(area.sounds);
|
||||||
|
idle_override_set(area.idleOnEnd);
|
||||||
|
particles_spawn(area.particles, pointer.position);
|
||||||
|
if (area.digestionClick > 0 && progress.calories > 0 && !progress.isDigesting)
|
||||||
|
progress.digestionProgress += area.digestionClick;
|
||||||
|
if (area.layer >= 0)
|
||||||
|
actor.overrides.push_back({.amplitude = {area.scaleAmplitude, area.scaleAmplitude},
|
||||||
|
.cycles = area.scaleCycles,
|
||||||
|
.time = area.scaleTime,
|
||||||
|
.timeStart = area.scaleTime,
|
||||||
|
.id = area.layer});
|
||||||
|
bool isReady = dialogueCooldowns[i] <= 0;
|
||||||
|
dialogueCooldowns[i] = std::max(0, area.dialogueCooldown);
|
||||||
|
bool isFull = is_over_capacity() && data.dialogue.pools.contains(area.dialoguePoolFull);
|
||||||
|
const std::string& pool = isFull ? area.dialoguePoolFull : area.dialoguePool;
|
||||||
|
if (data.dialogue.pools.contains(pool))
|
||||||
|
{
|
||||||
|
if (text.is_interruptible() && isReady)
|
||||||
|
text.set(*this, data.dialogue.pool_get(pool), TEXT_INTERRUPTIBLE,
|
||||||
|
area.isHold ? "" : area.idleOnEnd);
|
||||||
|
}
|
||||||
|
else if (area.dialoguePool.empty() && !area.animation.empty())
|
||||||
|
body_play(area.animation, TRANSITION_FORCE);
|
||||||
|
}
|
||||||
|
if (isInteracting)
|
||||||
|
{
|
||||||
|
cursorState = CURSOR_ACTION;
|
||||||
|
active.play(area.cursorActive);
|
||||||
|
Vec2 moved = pointer.position - pointerPrevious;
|
||||||
|
if (area.digestionHover > 0 && progress.calories > 0 && !progress.isDigesting &&
|
||||||
|
(moved.x != 0 || moved.y != 0))
|
||||||
|
progress.digestionProgress += area.digestionHover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((i == interactArea && !isInside) || pointer.isReleased || pointer.isCaptured)
|
||||||
|
{
|
||||||
|
isInteracting = isHoldInteracting = false;
|
||||||
|
interactArea = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((wasInteracting && !isInteracting) || (wasHovering && !isHovering))
|
||||||
|
active.play(data.cursorIdle);
|
||||||
|
if (wasHolding && !isHoldInteracting && !actor.pending)
|
||||||
|
idle_play();
|
||||||
|
pointerPrevious = pointer.position;
|
||||||
|
if (isJustDigested && text.is_interruptible())
|
||||||
|
text.set(*this, data.dialogue.hook_get("Digest"));
|
||||||
|
if (isJustStageUp)
|
||||||
|
text.set(*this, data.dialogue.hook_get("StageUp"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Moves the cursor toward a target, braking to land on it. Returns whether it's there.
|
||||||
|
static bool is_target_reached(Vec2& position, Vec2& velocity, Vec2 target, float speed, float acceleration)
|
||||||
|
{
|
||||||
|
Vec2 delta = target - position;
|
||||||
|
float distance = vec2_length(delta);
|
||||||
|
if (!std::isfinite(distance))
|
||||||
|
return false;
|
||||||
|
if (distance <= AUTO_TARGET_EPSILON)
|
||||||
|
{
|
||||||
|
position = target;
|
||||||
|
velocity = {};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Vec2 desired = delta / distance * std::min(speed, std::sqrt(AUTO_BRAKING * acceleration * distance));
|
||||||
|
Vec2 change = desired - velocity;
|
||||||
|
float changeLength = vec2_length(change);
|
||||||
|
velocity = changeLength > acceleration ? velocity + change / changeLength * acceleration : desired;
|
||||||
|
if (vec2_length(velocity) >= distance)
|
||||||
|
{
|
||||||
|
position = target;
|
||||||
|
velocity = {};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
position = position + velocity;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_autofeed_food(const Item& item)
|
||||||
|
{
|
||||||
|
return item.use == USE_EDIBLE && item.capacityBonus >= 0 && item.digestionBonus >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_edible(Play& play, const Item& item)
|
||||||
|
{
|
||||||
|
return is_autofeed_food(item) && item.calories > 0 && play.progress.calories + item.calories <= play.capacity_max_get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The edible item in the world with the most calories, ties broken at random.
|
||||||
|
static int food_best_get(Play& play)
|
||||||
|
{
|
||||||
|
int best = -1;
|
||||||
|
int ties = 0;
|
||||||
|
for (int i = 0; i < (int)play.items.size(); i++)
|
||||||
|
{
|
||||||
|
Item& item = play.character->items[play.items[i].id];
|
||||||
|
if (!is_edible(play, item))
|
||||||
|
continue;
|
||||||
|
float bestCalories = best >= 0 ? play.character->items[play.items[best].id].calories : -1;
|
||||||
|
if (item.calories > bestCalories)
|
||||||
|
best = i, ties = 1;
|
||||||
|
else if (item.calories == bestCalories && SDL_rand(++ties) == 0)
|
||||||
|
best = i;
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plays for the player: spawns food, carries it to the mouth, and rubs or pats the belly along when nothing fits.
|
||||||
|
void Play::autofeed_update(Pointer& itemPointer, Pointer& interactPointer)
|
||||||
|
{
|
||||||
|
Character& data = *character;
|
||||||
|
isAutofeedActive = false;
|
||||||
|
autoCursor.isVisible = false;
|
||||||
|
auto reset = [&]() {
|
||||||
|
isAutoItemHeld = isAutoDigestHeld = false;
|
||||||
|
autoTarget = -1;
|
||||||
|
autoClickCooldown = 0;
|
||||||
|
autoVelocity = {};
|
||||||
|
};
|
||||||
|
bool isWorldFood = false;
|
||||||
|
bool isAnyEdible = false;
|
||||||
|
for (ItemEntity& entity : items)
|
||||||
|
{
|
||||||
|
isWorldFood |= is_autofeed_food(data.items[entity.id]);
|
||||||
|
isAnyEdible |= is_edible(*this, data.items[entity.id]);
|
||||||
|
}
|
||||||
|
if (!isAutofeed || isStageUp || isJustStageUp || isJustStageFinal)
|
||||||
|
return reset();
|
||||||
|
spawn_queue_add();
|
||||||
|
if (progress.calories <= AUTO_CALORIE_EPSILON && !isWorldFood)
|
||||||
|
return reset();
|
||||||
|
|
||||||
|
if (autoCursor.position.x == 0 && autoCursor.position.y == 0)
|
||||||
|
autoCursor.position = actor.position;
|
||||||
|
Rect bodyBounds = actor.bounds_get();
|
||||||
|
float speedScale = is_rect_valid(bodyBounds) ? std::clamp(std::sqrt(bodyBounds.w * bodyBounds.h / AUTO_SCALE_AREA),
|
||||||
|
AUTO_SCALE_MIN, AUTO_SCALE_MAX)
|
||||||
|
: 1;
|
||||||
|
Rect mouth = {NAN, NAN, NAN, NAN};
|
||||||
|
for (EatArea& eatArea : data.eatAreas)
|
||||||
|
if (!is_rect_valid(mouth))
|
||||||
|
mouth = eat_rect_get(eatArea);
|
||||||
|
bool isMouth = is_rect_valid(mouth);
|
||||||
|
auto move = [&](Vec2 target, float speed, float acceleration) {
|
||||||
|
isAutofeedActive = autoCursor.isVisible = true;
|
||||||
|
itemPointer = interactPointer = {.position = autoCursor.position, .isAutomated = true};
|
||||||
|
bool isThere = is_target_reached(autoCursor.position, autoVelocity, target, speed * speedScale,
|
||||||
|
acceleration * speedScale);
|
||||||
|
itemPointer.position = interactPointer.position = autoCursor.position;
|
||||||
|
return isThere;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (heldItem >= 0 && heldItem < (int)items.size())
|
||||||
|
{
|
||||||
|
if (isMouth && is_edible(*this, data.items[items[heldItem].id]))
|
||||||
|
{
|
||||||
|
move(rect_middle(mouth), AUTO_SPEED, AUTO_ACCELERATION);
|
||||||
|
itemPointer.isDown = isAutoItemHeld = true;
|
||||||
|
isAutoDigestHeld = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
move(autoCursor.position, AUTO_SPEED, AUTO_ACCELERATION);
|
||||||
|
itemPointer.isReleased = true;
|
||||||
|
isAutoItemHeld = false;
|
||||||
|
autoTarget = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isTargetValid = autoTarget >= 0 && autoTarget < (int)items.size() &&
|
||||||
|
is_edible(*this, data.items[items[autoTarget].id]);
|
||||||
|
if (!isMouth || !isTargetValid)
|
||||||
|
autoTarget = isMouth ? food_best_get(*this) : -1;
|
||||||
|
if (autoTarget >= 0)
|
||||||
|
{
|
||||||
|
Rect rect = items[autoTarget].actor.bounds_get();
|
||||||
|
if (!is_rect_valid(rect))
|
||||||
|
return reset();
|
||||||
|
move(rect_middle(rect), AUTO_SPEED, AUTO_ACCELERATION);
|
||||||
|
bool isInside = is_point_in_rect(rect, autoCursor.position);
|
||||||
|
itemPointer.target = autoTarget;
|
||||||
|
itemPointer.isClicked = isInside && !isAutoItemHeld;
|
||||||
|
itemPointer.isDown = isInside || isAutoItemHeld;
|
||||||
|
isAutoItemHeld |= isInside;
|
||||||
|
isAutoDigestHeld = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!(isWorldFood || !isAnyEdible))
|
||||||
|
return reset();
|
||||||
|
|
||||||
|
InteractArea* digestArea = nullptr;
|
||||||
|
for (InteractArea& area : data.interactAreas)
|
||||||
|
if (!digestArea && (area.digestionClick > 0 || area.digestionHover > 0))
|
||||||
|
digestArea = &area;
|
||||||
|
Rect rect = digestArea ? actor.null_rect_get(digestArea->null) : Rect{NAN, NAN, NAN, NAN};
|
||||||
|
if (!is_rect_valid(rect))
|
||||||
|
return reset();
|
||||||
|
autoAngle += AUTO_ANGLE_STEP;
|
||||||
|
float radius = std::max(1.0f, std::min(rect.w, rect.h) * AUTO_DIGEST_RADIUS);
|
||||||
|
Vec2 target = rect_middle(rect) + Vec2{std::cos(autoAngle), std::sin(autoAngle)} * radius;
|
||||||
|
bool isThere = move(target, AUTO_DIGEST_SPEED, AUTO_DIGEST_ACCELERATION);
|
||||||
|
interactPointer.type = digestArea->type;
|
||||||
|
if (digestArea->digestionHover > 0)
|
||||||
|
{
|
||||||
|
bool isInside = is_point_in_rect(rect, autoCursor.position);
|
||||||
|
interactPointer.isClicked = isInside && !isAutoDigestHeld;
|
||||||
|
interactPointer.isDown = isInside;
|
||||||
|
interactPointer.isReleased = isAutoDigestHeld && !isInside;
|
||||||
|
isAutoDigestHeld = isInside;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
autoClickCooldown = std::max(0, autoClickCooldown - 1);
|
||||||
|
interactPointer.isClicked = isThere && autoClickCooldown <= 0 && !isAutoDigestHeld;
|
||||||
|
interactPointer.isReleased = isAutoDigestHeld;
|
||||||
|
if (interactPointer.isClicked)
|
||||||
|
autoClickCooldown = AUTO_CLICK_COOLDOWN;
|
||||||
|
isAutoDigestHeld = interactPointer.isClicked;
|
||||||
|
}
|
||||||
|
isAutoItemHeld = false;
|
||||||
|
}
|
||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include <simdjson.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using simdjson::dom::array;
|
||||||
|
using simdjson::dom::element;
|
||||||
|
using simdjson::dom::object;
|
||||||
|
|
||||||
|
// A document holding an empty object and array, standing in for whatever's missing so lookups never fail.
|
||||||
|
inline element json_empty_get(const char* key)
|
||||||
|
{
|
||||||
|
static simdjson::padded_string text(std::string_view(R"({"o":{},"a":[]})"));
|
||||||
|
static simdjson::dom::parser parser;
|
||||||
|
static element document = parser.parse(text).value_unsafe();
|
||||||
|
return document[key].value_unsafe();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A parsed JSON file. Its objects stay readable while it lives.
|
||||||
|
struct Json
|
||||||
|
{
|
||||||
|
simdjson::dom::parser parser;
|
||||||
|
object root = json_empty_get("o").get_object().value_unsafe();
|
||||||
|
bool isValid = false;
|
||||||
|
|
||||||
|
void load(const std::string& path)
|
||||||
|
{
|
||||||
|
isValid = !parser.load(path).get(root);
|
||||||
|
if (!isValid)
|
||||||
|
SDL_Log("Failed to load %s", path.c_str());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline float json_float(object source, const char* key, float fallback = 0)
|
||||||
|
{
|
||||||
|
double value;
|
||||||
|
return source[key].get(value) ? fallback : (float)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int json_int(object source, const char* key, int fallback = 0)
|
||||||
|
{
|
||||||
|
return (int)json_float(source, key, (float)fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool json_is(object source, const char* key, bool fallback = false)
|
||||||
|
{
|
||||||
|
bool value;
|
||||||
|
return source[key].get(value) ? fallback : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string json_string(object source, const char* key, const std::string& fallback = "")
|
||||||
|
{
|
||||||
|
std::string_view value;
|
||||||
|
return source[key].get(value) ? fallback : std::string(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline object json_object(object source, const char* key)
|
||||||
|
{
|
||||||
|
object value;
|
||||||
|
return source[key].get(value) ? json_empty_get("o").get_object().value_unsafe() : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline array json_array(object source, const char* key)
|
||||||
|
{
|
||||||
|
array value;
|
||||||
|
return source[key].get(value) ? json_empty_get("a").get_array().value_unsafe() : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A list of numbers read into values, keeping each fallback where the list stops short.
|
||||||
|
inline void json_floats(object source, const char* key, float* values, int count)
|
||||||
|
{
|
||||||
|
array list = json_array(source, key);
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
double value;
|
||||||
|
if (!list.at(i).get(value))
|
||||||
|
values[i] = (float)value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vec2 json_vec2(object source, const char* key, Vec2 fallback = {})
|
||||||
|
{
|
||||||
|
json_floats(source, key, &fallback.x, 2);
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A color as a list of three or four numbers, scaled down by divisor.
|
||||||
|
inline Color json_color(object source, const char* key, Color fallback = WHITE, float divisor = 1)
|
||||||
|
{
|
||||||
|
Color value = {fallback.r * divisor, fallback.g * divisor, fallback.b * divisor, fallback.a * divisor};
|
||||||
|
json_floats(source, key, &value.r, 4);
|
||||||
|
return {value.r / divisor, value.g / divisor, value.b / divisor, value.a / divisor};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::vector<std::string> json_strings(object source, const char* key)
|
||||||
|
{
|
||||||
|
std::vector<std::string> values;
|
||||||
|
for (element entry : json_array(source, key))
|
||||||
|
{
|
||||||
|
std::string_view value;
|
||||||
|
if (!entry.get(value))
|
||||||
|
values.emplace_back(value);
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
Loaded 100 of 233 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user