- add options sub-menu to title screen: bgm, sfx, fullscreen, and exit

- hide UI when gamepad is in use, enable when mouse is in use
- indicate selected UI button using hue rotation animation
- support for gamepad hat
- support for disconnecting and reconnecting gamepads
- sanitize collected data in WASM build and write files per session
- add function for finding the closest UI button in a given direction
- bug fix: prevent character from moving when level loads or play is resumed from the pause menu
- bug fix: cancel character walking sfx when paused
This commit is contained in:
ohsqueezy 2024-05-01 20:35:15 -04:00 committed by Cocktail Frank
parent ba53c72b01
commit c5a8b9d885
12 changed files with 515 additions and 243 deletions

2
.gitignore vendored
View File

@ -15,7 +15,7 @@ ext/
local/
src/__pycache__/
storage/
Play_History.json
*Play_History.json
press.html
feed
test.html

View File

@ -116,8 +116,8 @@ $(addsuffix /Input.o, $(BUILD_DIRS)): $(addprefix $(SB_SRC_DIR)/, Input.cpp Inpu
Delegate.hpp) | $(BUILD_DIRS)
$(CXX) $(CXXFLAGS) $< -c -o $@
$(addsuffix /Configuration.o, $(BUILD_DIRS)): $(addprefix $(SB_SRC_DIR)/, Configuration.cpp Configuration.hpp Node.hpp Animation.hpp \
Log.hpp extension.hpp) | $(BUILD_DIRS)
$(addsuffix /Configuration.o, $(BUILD_DIRS)): $(addprefix $(SB_SRC_DIR)/, Configuration.cpp Configuration.hpp Animation.hpp Log.hpp \
extension.hpp) | $(BUILD_DIRS)
$(CXX) $(CXXFLAGS) $< -c -o $@
$(addsuffix /Delegate.o, $(BUILD_DIRS)): $(addprefix $(SB_SRC_DIR)/, Delegate.cpp Delegate.hpp Node.hpp Game.hpp Input.hpp) \
@ -265,7 +265,7 @@ cakefoot.js : CXXFLAGS = $(CFLAGS) --std=c++17
cakefoot.js : $(addprefix $(WASM_BUILD_DIR)/, SDL2_rotozoom.o SDL2_gfxPrimitives.o $(SB_O_FILES) $(SRC_O_FILES)) \
$(EMSCRIPTEN_GAME_CONFIGS)
$(CXX) $(filter-out $(EMSCRIPTEN_GAME_CONFIGS), $^) $(CXXFLAGS) $(EMSCRIPTEN_LFLAGS) $(EMSCRIPTEN_PRELOADS) \
--pre-js "src/pre_js_foam.js" -o $(WASM_BUILD_DIR)/$@
--pre-js "src/pre_js_dank.js" -o $(WASM_BUILD_DIR)/$@
cakefoot_debug.html : CC = $(EMSCRIPTENHOME)/emcc
cakefoot_debug.html : CXX = $(EMSCRIPTENHOME)/em++

View File

@ -28,7 +28,7 @@
"hitbox": false,
"use play button": false,
"arcade only": false,
"use arcade prompt": true,
"use arcade prompt": false,
"game over text": "GAME OVER",
"game over display time": 2.5,
"game over foreground": [255.0, 255.0, 255.0, 255.0],
@ -54,7 +54,7 @@
"scoreboard translation": [-0.4, 0.835],
"scoreboard scale": [1.35, 0.14],
"scoreboard wrap": 3000,
"qr display": true,
"qr display": false,
"qr background display": true,
"qr background texture": "resource/qr_background.png",
"qr texture": "resource/qr.png",
@ -95,7 +95,9 @@
"arcade warning color": [0.5, 0.0, 0.0, 1.0],
"auto save translation": [-1.45, -0.65],
"auto save scale": [0.325, 0.15],
"social media click": false
"social media click": false,
"highlight saturation": 1.0,
"highlight value": 0.5
},
"shader":
@ -116,7 +118,7 @@
{
"screenshot directory": "local/screenshots",
"video directory": "local/video",
"enabled": true,
"enabled": false,
"write mp4": true,
"video frame length": 0.016666666,
"max video memory": 2000,
@ -129,7 +131,8 @@
"any key ignore commands": ["left", "right", "up", "down", "pause"],
"gamepad pause button index": 9,
"gamepad axis cooldown": 0.2,
"gamepad reset button index": 8
"gamepad reset button index": 8,
"gamepad home button index": 10
},
"keys":
@ -267,16 +270,16 @@
"text dimensions": [275.0, 50.0],
"text scale": 0.71,
"text foreground": [200.0, 200.0, 200.0, 255.0],
"text background": [60.0, 60.0, 60.0, 190.0],
"text background": [60.0, 60.0, 60.0, 255.0],
"start text": "✶✶ PLAY ✶✶",
"start translation": [0.0, -0.4],
"start alt texture": "resource/press_button_to_start.png",
"start alt translation": [0.0, -0.5],
"start alt scale": [0.5787, 0.91],
"resume text": "RESUME",
"resume translation": [0.0, 0.25],
"resume translation": [0.0, 0.35],
"reset text": "SAVE & EXIT",
"reset translation": [0.0, -0.25],
"reset translation": [0.0, -0.025],
"level decrement translation": [-0.67, -0.71],
"level decrement text": "◀",
"level decrement dimensions": [40.0, 40.0],
@ -336,7 +339,22 @@
"fullscreen texture": "resource/fullscreen.png",
"fullscreen translation": [-1.45, -0.85],
"fullscreen scale": 0.07,
"fullscreen scale ratio": 0.75
"fullscreen scale ratio": 0.75,
"fullscreen text text": "FULLSCREEN",
"fullscreen text translation home": [0.0, -0.69],
"fullscreen text translation pause": [0.0, -0.29],
"fullscreen text dimensions": [850.0, 40.0],
"fullscreen text scale": 0.71,
"bgm text on": "BGM ON",
"bgm text off": "BGM OFF",
"bgm translation home": [0.0, -0.77],
"bgm translation pause": [0.0, -0.38],
"sfx text on": "SFX ON",
"sfx text off": "SFX OFF",
"sfx translation home": [0.0, -0.85],
"sfx translation pause": [0.0, -0.47],
"exit text": "EXIT GAME",
"exit translation": [0.0, -0.93]
},
"world": [
@ -387,6 +405,9 @@
"level addition advanced": 30.0,
"bank bonus": 5.0,
"advanced": 14
},
{
"name": "OPTIONS"
}
],
@ -456,7 +477,7 @@
"demo":
{
"active": true,
"active": false,
"idle timeout": 30.0,
"countdown display timeout": 10.0,
"countdown message": "IDLE RESET IN ",

View File

@ -252,7 +252,7 @@
var ticker_content = [
"Use ☝&#xFE0F;, &#x1F5B1;&#xFE0F;, &#x2328;&#xFE0F; or 🎮 to play",
"🗓&#xFE0F; Releasing on <a href='https://store.steampowered.com/app/2869020/Cakefoot/' target='_blank'>Steam</a>, " +
"<a href='https://ohsqueezy.itch.io/cakefoot' target='new'>itch.io</a>, Android & " +
"<a href='https://ohsqueezy.itch.io/cakefoot' target='new'>itch.io</a> & " +
"<a href='https://coolmathgames.com' target='_blank'>Coolmath</a> May 10th",
"<a href='https://youtu.be/xn-iNcUlIpo' target='_blank'>Watch the trailer</a>&#xFE0F; and " +
"<a href='https://store.steampowered.com/app/2869020/Cakefoot/'>wishlist on Steam</a>",

2
lib/sb

@ -1 +1 @@
Subproject commit 8498dfa00472431a6dd8a2ec588f0792a9e435dc
Subproject commit 102d1749a5325139723a887377523e8f5ff9e0b9

View File

@ -1,7 +1,7 @@
/* _ _
* c/a`k-e'f`o^o~t-, | a single-button action game | @dankd0tgame
* / _< | wow a living cake the sweet | https://cakefoot.dank.game
* > `~_/ | taste of victory | https://open.shampoo.ooo/shampoo/cakefoot
* c/a`k-e'f`o^o~t-, | a single-button action game | https://cakefoot.dank.game
* / _< | wow a living cake the sweet | https://open.shampoo.ooo/shampoo/cakefoot
* > `~_/ | taste of victory |
*/
#if defined(__ANDROID__) || defined(ANDROID)
@ -35,7 +35,7 @@ Cakefoot::Cakefoot(std::initializer_list<std::string> configuration_merge) : Gam
{"current difficulty", 0},
{"max difficulty", 0},
{"current challenge", 1},
{"max challenge", 4},
{"max challenge", 5},
{"current view", 0},
{"max view", 0},
{"deaths", 0},
@ -91,11 +91,11 @@ Cakefoot::Cakefoot(std::initializer_list<std::string> configuration_merge) : Gam
configuration()["progress"]["current level"] = 1;
}
/* Enforce max challenge to 4 */
configuration()["progress"]["max challenge"] = 4;
if (configuration()("progress", "current challenge") > 4)
/* Enforce max challenge to 5 */
configuration()["progress"]["max challenge"] = 5;
if (configuration()("progress", "current challenge") > 5)
{
configuration()["progress"]["current challenge"] = 4;
configuration()["progress"]["current challenge"] = 5;
}
/* Set the spinner values to what the player was last playing, unless demo mode is active, in which case leave
@ -111,15 +111,22 @@ Cakefoot::Cakefoot(std::initializer_list<std::string> configuration_merge) : Gam
/* Initialize name entry */
name_entry = configuration()("display", "default initials");
/* Initialize rotating hue highlight color */
rotating_hue = sb::Color(0.0f, 0.0f, 0.0f, 1.0f);
rotating_hue.hsv(0.0f, configuration()("display", "highlight saturation"), configuration()("display", "highlight value"));
/* Subscribe to events */
delegate().subscribe(&Cakefoot::respond, this, SDL_MOUSEMOTION);
delegate().subscribe(&Cakefoot::respond, this, SDL_MOUSEBUTTONDOWN);
delegate().subscribe(&Cakefoot::respond, this, SDL_MOUSEBUTTONUP);
delegate().subscribe(&Cakefoot::respond, this, SDL_MOUSEWHEEL);
delegate().subscribe(&Cakefoot::respond, this, SDL_JOYAXISMOTION);
delegate().subscribe(&Cakefoot::respond, this, SDL_JOYHATMOTION);
delegate().subscribe(&Cakefoot::respond, this, SDL_JOYBUTTONDOWN);
delegate().subscribe(&Cakefoot::respond, this, SDL_JOYBUTTONUP);
delegate().subscribe(&Cakefoot::respond, this, SDL_KEYDOWN);
delegate().subscribe(&Cakefoot::respond, this, SDL_JOYDEVICEADDED);
delegate().subscribe(&Cakefoot::respond, this, SDL_JOYDEVICEREMOVED);
/* Open a game controller if any are available at the beginning of the program */
open_game_controller();
@ -208,8 +215,10 @@ Cakefoot::Cakefoot(std::initializer_list<std::string> configuration_merge) : Gam
set_up_buttons();
}
/* Switch volume on */
/* Switch sounds on by default */
button.at("volume").press();
button.at("bgm").press();
button.at("sfx").press();
/* Track idle time */
idle_timer.on();
@ -231,34 +240,36 @@ Cakefoot::Cakefoot(std::initializer_list<std::string> configuration_merge) : Gam
void Cakefoot::open_game_controller()
{
bool found = false;
for (int ii = 0, jj = 0; ii < SDL_NumJoysticks(); ii++)
{
if (SDL_IsGameController(ii))
{
std::ostringstream message;
message << "Gamepad #" << ++jj << ": ";
message << "Gamepad #" << ++jj << ": ";
std::string name {SDL_GameControllerNameForIndex(ii)};
if (name == "")
{
name = "[unnamed]";
name = "unnamed";
}
message << name;
sb::Log::log(message);
if (controller.get() == nullptr)
{
controller = std::shared_ptr<SDL_GameController>(SDL_GameControllerOpen(ii), SDL_GameControllerClose);
std::ostringstream message;
if (controller.get() == nullptr)
{
message << "Could not open gamepad #" << jj;
sb::Log::sdl_error(message.str());
message << " [Could not open]";
sb::Log::sdl_error();
}
else
{
message << "Opened gamepad #" << jj;
found = true;
message << " [Using this gamepad]";
sb::Log::log(message);
break;
}
}
sb::Log::log(message);
}
else
{
@ -267,6 +278,10 @@ void Cakefoot::open_game_controller()
sb::Log::log(message);
}
}
if (!found)
{
sb::Log::log("No usable gamepad detected. Only mouse and touch controls will work.");
}
}
void Cakefoot::load_audio()
@ -399,17 +414,23 @@ void Cakefoot::set_up_buttons()
/* Set up text buttons */
for (const std::string& name : {
"start", "resume", "reset", "level increment", "level decrement", "profile increment", "profile decrement",
"challenge increment", "challenge decrement", "view increment", "view decrement"
"challenge increment", "challenge decrement", "view increment", "view decrement", "fullscreen text", "bgm", "sfx", "exit"
})
{
sb::Text text {name == "resume" || name == "reset" ? fonts.at("medium") : fonts.at("glyph")};
float scale;
glm::vec2 dimensions;
bool pressed = button.at(name).pressed();
if (name == "start" || name == "resume" || name == "reset")
{
dimensions = glm::vec2{configuration()("button", "text dimensions")};
scale = configuration()("button", "text scale");
}
else if (name == "fullscreen text" || name == "bgm" || name == "sfx" || name == "exit")
{
dimensions = glm::vec2{configuration()("button", "fullscreen text dimensions")};
scale = configuration()("button", "fullscreen text scale");
}
else
{
dimensions = glm::vec2{configuration()("button", "level decrement dimensions")};
@ -424,10 +445,30 @@ void Cakefoot::set_up_buttons()
}
text.foreground(configuration()("button", "text foreground").get<glm::vec4>());
text.background(configuration()("button", "text background").get<glm::vec4>());
text.content(configuration()("button", name + " text"));
std::string text_content;
if (name != "bgm" && name != "sfx")
{
text_content = name + " text";
}
else
{
text_content = name + " text " + (pressed ? "on" : "off");
}
text.content(configuration()("button", text_content));
text.dimensions(dimensions);
text.refresh();
button.at(name) = sb::Pad<>{text, configuration()("button", name + " translation"), scale, dimensions.y / dimensions.x};
std::string translation_entry;
if (name != "bgm" && name != "sfx" && name != "fullscreen text")
{
translation_entry = name + " translation";
}
else
{
/* If game is paused and it's not the title screen, pause menu must be active */
translation_entry = name + " translation " + (unpaused_timer || level_index == 0 ? "home" : "pause");
}
button.at(name) = sb::Pad<>{text, configuration()("button", translation_entry), scale, dimensions.y / dimensions.x};
button.at(name).state(pressed);
}
/* Replace start text texture with arcade prompt image if requested in config */
@ -478,8 +519,43 @@ void Cakefoot::set_up_buttons()
button.at("reset").on_state_change([&](bool state){
sb::Delegate::post(reset_command_name, false);
});
button.at("fullscreen text").on_state_change([&](bool state){
display.toggle_fullscreen();
});
button.at("bgm").on_state_change([&](bool state){
for (auto& [name, chunk] : audio)
{
if (name == "menu" || name == "main")
{
chunk.enabled(state);
}
}
if (state)
{
audio.at("menu").play();
}
else
{
audio.at("menu").stop();
}
set_up_buttons();
});
button.at("sfx").on_state_change([&](bool state){
for (auto& [name, chunk] : audio)
{
if (name != "menu" && name != "main")
{
chunk.enabled(state);
}
}
set_up_buttons();
});
button.at("exit").on_state_change([&](bool state){
flag_to_end();
});
/* Set up pause button */
bool visible = button.at("pause").visible();
sb::Texture pause_texture {configuration()("button", "pause texture").get<std::string>()};
pause_texture.load();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
@ -488,12 +564,14 @@ void Cakefoot::set_up_buttons()
pause_plane.texture(pause_texture);
button.at("pause") = sb::Pad<>{pause_plane, configuration()("button", "pause translation"),
configuration()("button", "pause scale"), 1.0f};
button.at("pause").visible(visible);
button.at("pause").on_state_change([&](bool state){
sb::Delegate::post("pause", false);
});
/* Set up volume button */
bool original_state = button.at("volume").pressed();
visible = button.at("volume").visible();
sb::Texture volume_off_texture {configuration()("button", "volume off texture").get<std::string>()};
volume_off_texture.load();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
@ -508,6 +586,7 @@ void Cakefoot::set_up_buttons()
button.at("volume") = sb::Pad<>{
volume_plane, configuration()("button", "volume translation"), configuration()("button", "volume scale"), 1.0f};
button.at("volume").state(original_state);
button.at("volume").visible(visible);
button.at("volume").on_state_change([&](bool state){
/* Mute or unmute (to full volume) depending on the state of the button */
if (Mix_QuerySpec(nullptr, nullptr, nullptr) != 0)
@ -621,14 +700,14 @@ void Cakefoot::set_up_buttons()
set_up_buttons();
});
button.at("level increment").on_state_change([&](bool state){
/* Only allow level select in level select mode */
if (configuration()("challenge", challenge_index, "name") == "LEVEL SELECT")
{
/* If the level is increased past the total number of levels or past the max level unlocked on the current difficulty,
* wrap the spinner back to 1. */
if (++level_select_index >= static_cast<int>(configuration()("levels").size() - 1) || (
profile_index == configuration()("progress", "max difficulty") && level_select_index > configuration()("progress", "max level")))
profile_index == configuration()("progress", "max difficulty") &&
level_select_index > configuration()("progress", "max level")))
{
level_select_index = 1;
}
@ -640,8 +719,8 @@ void Cakefoot::set_up_buttons()
set_up_buttons();
});
button.at("challenge decrement").on_state_change([&](bool state){
/* Only allow change if arcade-only mode is not active */
if (!configuration()("display", "arcade only"))
/* Only allow change when not in arcade-only or demo mode */
if (!configuration()("display", "arcade only") && !configuration()("demo", "active"))
{
if (--challenge_index < 0) challenge_index = configuration()("progress", "max challenge");
if (skip_resume_quest() || skip_resume_arcade() || skip_level_select())
@ -655,8 +734,8 @@ void Cakefoot::set_up_buttons()
}
});
button.at("challenge increment").on_state_change([&](bool state){
/* Only allow change if arcade-only mode is not active */
if (!configuration()("display", "arcade only"))
/* Only allow change when not in arcade-only or demo mode */
if (!configuration()("display", "arcade only") && !configuration()("demo", "active"))
{
if (++challenge_index > configuration()("progress", "max challenge")) challenge_index = 0;
if (skip_resume_quest() || skip_resume_arcade() || skip_level_select())
@ -721,7 +800,9 @@ void Cakefoot::set_up_buttons()
character.content(name_entry[std::stoi(character_index) - 1]);
character.refresh();
button.at("name " + character_index) = sb::Pad<>{
character, {configuration()("button", "name", "character " + character_index + " x"), configuration()("button", "name", "character y")},
character,
{configuration()("button", "name", "character " + character_index + " x"),
configuration()("button", "name", "character y")},
configuration()("button", "name", "character scale")[1], character_dimensions.y / character_dimensions.x};
sb::Texture increment_texture {configuration()("button", "name", "arrow increment texture").get<std::string>()};
increment_texture.load();
@ -731,7 +812,11 @@ void Cakefoot::set_up_buttons()
increment_plane.texture(increment_texture);
glm::vec2 arrow_dimensions {configuration()("button", "name", "arrow dimensions")};
button.at("name " + character_index + " increment") = sb::Pad<>{
increment_plane, {configuration()("button", "name", "character " + character_index + " x"), configuration()("button", "name", "arrow increment y")},
increment_plane,
{
configuration()("button", "name", "character " + character_index + " x"),
configuration()("button", "name", "arrow increment y")
},
configuration()("button", "name", "arrow scale")[1], arrow_dimensions.y / arrow_dimensions.x};
sb::Texture decrement_texture {configuration()("button", "name", "arrow decrement texture").get<std::string>()};
decrement_texture.load();
@ -740,7 +825,11 @@ void Cakefoot::set_up_buttons()
sb::Plane decrement_plane;
decrement_plane.texture(decrement_texture);
button.at("name " + character_index + " decrement") = sb::Pad<>{
decrement_plane, {configuration()("button", "name", "character " + character_index + " x"), configuration()("button", "name", "arrow decrement y")},
decrement_plane,
{
configuration()("button", "name", "character " + character_index + " x"),
configuration()("button", "name", "arrow decrement y")
},
configuration()("button", "name", "arrow scale")[1], arrow_dimensions.y / arrow_dimensions.x};
button.at("name " + character_index).on_state_change([&, character_index](bool state){
name_entry_index = std::stoi(character_index) - 1;
@ -774,14 +863,17 @@ void Cakefoot::set_up_buttons()
}
/* Set up fullscreen button */
visible = button.at("fullscreen").visible();
sb::Texture fullscreen_texture {configuration()("button", "fullscreen texture").get<std::string>()};
fullscreen_texture.load();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
sb::Plane fullscreen_plane;
fullscreen_plane.texture(fullscreen_texture);
button.at("fullscreen") = sb::Pad<>{fullscreen_plane, configuration()("button", "fullscreen translation"), configuration()("button", "fullscreen scale"),
configuration()("button", "fullscreen scale ratio")};
button.at("fullscreen") = sb::Pad<>{
fullscreen_plane, configuration()("button", "fullscreen translation"), configuration()("button", "fullscreen scale"),
configuration()("button", "fullscreen scale ratio")};
button.at("fullscreen").visible(visible);
button.at("fullscreen").on_state_change([&](bool state){
display.toggle_fullscreen();
});
@ -793,8 +885,9 @@ void Cakefoot::set_up_buttons()
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
sb::Plane diskmem_plane;
diskmem_plane.texture(diskmem_texture);
button.at("diskmem") = sb::Pad<>{diskmem_plane, configuration()("display", "social diskmem translation"), configuration()("display", "social single scale"),
configuration()("display", "social single ratio")};
button.at("diskmem") = sb::Pad<>{
diskmem_plane, configuration()("display", "social diskmem translation"), configuration()("display", "social single scale"),
configuration()("display", "social single ratio")};
button.at("diskmem").on_state_change([&](bool state){
#if defined(EMSCRIPTEN) && !defined(__COOLMATH__)
EM_ASM(
@ -833,7 +926,8 @@ void Cakefoot::set_up_buttons()
void Cakefoot::toggle_challenge()
{
/* In resume modes, set the level select and difficulty to the saved values. */
if (configuration()("challenge", challenge_index, "name") == "RESUME QUEST")
if (configuration()("challenge", challenge_index, "name") == "RESUME QUEST" ||
configuration()("challenge", challenge_index, "name") == "OPTIONS")
{
level_select_index = configuration()("progress", "quest level").get<int>();
profile_index = configuration()("progress", "quest difficulty").get<int>();
@ -1072,8 +1166,15 @@ Curve& Cakefoot::curve()
void Cakefoot::load_level(int index)
{
/* Cancel selection */
selected.reset();
/* Default gamepad selection resets every time a level loads */
if (index == 0)
{
selected = "start";
}
else
{
selected = "resume";
}
/* Wrap the index if it is out of range. */
index = glm::mod(index, static_cast<int>(configuration()("levels").size()));
@ -1658,7 +1759,8 @@ float Cakefoot::limit() const
{
limit += configuration()("challenge", challenge_index, level_addition).get<float>();
if (level->contains("checkpoints"))
limit += configuration()("challenge", challenge_index, checkpoint_addition).get<float>() * level->at("checkpoints").size();
limit += configuration()("challenge", challenge_index, checkpoint_addition).get<float>() *
level->at("checkpoints").size();
}
else if (level->contains("checkpoints"))
{
@ -1857,16 +1959,32 @@ void Cakefoot::respond(SDL_Event& event)
{
Game::respond(event);
/* Reopen gamepad if gamepad is detected as added or removed */
if (event.type == SDL_JOYDEVICEADDED || event.type == SDL_JOYDEVICEREMOVED)
{
sb::Log::log(std::string("Detected gamepad ") + (event.type == SDL_JOYDEVICEADDED ? "added" : "removed") +
". Reloading gamepad.");
controller.reset();
open_game_controller();
}
/* Reset the idle timer */
idle_timer.reset();
/* Track whether cursor should be visible or not */
bool joy_or_key_input_registered = event.type == SDL_KEYDOWN;
bool mouse_input_registered = (event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEBUTTONDOWN);
/* Translate gamepad input to commands */
if (event.type == SDL_JOYBUTTONDOWN)
{
/* Pause on either pause button or home button press */
if (level_index > 0 && static_cast<std::size_t>(level_index) <= configuration()("levels").size() - 2 &&
event.jbutton.button == configuration()("input", "gamepad pause button index"))
(event.jbutton.button == configuration()("input", "gamepad pause button index") ||
event.jbutton.button == configuration()("input", "gamepad home button index")))
{
sb::Delegate::post("pause");
joy_or_key_input_registered = true;
}
else if (configuration()("demo", "active") && level_index > 0 &&
static_cast<std::size_t>(level_index) <= configuration()("levels").size() - 2 &&
@ -1877,39 +1995,33 @@ void Cakefoot::respond(SDL_Event& event)
else if ((!use_play_button || button.at("play").pressed()) && !splash_animation.playing())
{
sb::Delegate::post("any");
joy_or_key_input_registered = true;
}
}
else if (event.type == SDL_JOYBUTTONUP)
{
sb::Delegate::post("any", true);
}
else if (event.type == SDL_JOYAXISMOTION && !cooldown_animation.playing())
else if ((event.type == SDL_JOYAXISMOTION || event.type == SDL_JOYHATMOTION) && !cooldown_animation.playing())
{
if (event.jaxis.axis == 1)
bool up = (event.type == SDL_JOYAXISMOTION && event.jaxis.axis == 1 && event.jaxis.value < -15000) ||
(event.type == SDL_JOYHATMOTION && event.jhat.value == SDL_HAT_UP);
bool right = (event.type == SDL_JOYAXISMOTION && event.jaxis.axis == 0 && event.jaxis.value > 15000) ||
(event.type == SDL_JOYHATMOTION && event.jhat.value == SDL_HAT_RIGHT);
bool down = (event.type == SDL_JOYAXISMOTION && event.jaxis.axis == 1 && event.jaxis.value > 15000) ||
(event.type == SDL_JOYHATMOTION && event.jhat.value == SDL_HAT_DOWN);
bool left = (event.type == SDL_JOYAXISMOTION && event.jaxis.axis == 0 && event.jaxis.value < -15000) ||
(event.type == SDL_JOYHATMOTION && event.jhat.value == SDL_HAT_LEFT);
if (up) sb::Delegate::post("up");
if (right) sb::Delegate::post("right");
if (down) sb::Delegate::post("down");
if (left) sb::Delegate::post("left");
if (up || right || down || left)
{
if (event.jaxis.value > 15000)
{
sb::Delegate::post("down");
cooldown_animation.play_once(configuration()("input", "gamepad axis cooldown"));
}
else if (event.jaxis.value < -15000)
{
sb::Delegate::post("up");
cooldown_animation.play_once(configuration()("input", "gamepad axis cooldown"));
}
}
else
{
if (event.jaxis.value > 15000)
{
sb::Delegate::post("right");
cooldown_animation.play_once(configuration()("input", "gamepad axis cooldown"));
}
else if (event.jaxis.value < -15000)
{
sb::Delegate::post("left");
cooldown_animation.play_once(configuration()("input", "gamepad axis cooldown"));
}
joy_or_key_input_registered = true;
cooldown_animation.play_once(configuration()("input", "gamepad axis cooldown"));
}
}
@ -1925,129 +2037,69 @@ void Cakefoot::respond(SDL_Event& event)
(1.0f - float(mouse_pixel.y) / window_box().height()) * 2.0f - 1.0f
};
/* Track whether cursor should display */
bool hovering = false;
/* Track whether pointer or default cursor should display. Only reset to default cursor if mouse motion was the event.
* Otherwise, set hovering to the current state of the cursor. */
bool hovering = event.type != SDL_MOUSEMOTION && SDL_GetCursor() == poke.get();
/* Build a list of title screen buttons that are currently active (when the title screen is displayed). */
std::vector<std::string> title_menu {"start"};
if (configuration()("challenge", challenge_index, "name") != "OPTIONS")
{
/* Check if the spinners are enabled before navigating to them. */
if (button.at("challenge decrement").enabled())
{
sb::extend(title_menu, {"challenge decrement", "challenge increment"});
}
if (button.at("level decrement").enabled())
{
sb::extend(title_menu, {"level decrement", "level increment"});
}
if (button.at("profile decrement").enabled())
{
sb::extend(title_menu, {"profile decrement", "profile increment"});
}
if (button.at("view decrement").enabled())
{
sb::extend(title_menu, {"view decrement", "view increment"});
}
}
else
{
sb::extend(title_menu, {"challenge decrement", "challenge increment", "fullscreen text", "bgm", "sfx", "exit"});
}
/* Ignore most events when play button or splash screen is active */
if ((!use_play_button || button.at("play").pressed()) && !splash_animation.playing())
{
/* Title screen and pause menu navigation is disabled when arcade-only or demo modes are active */
bool menu_active = !configuration()("display", "arcade only") && !configuration()("demo", "active");
/* Track whether a button has been pressed with this event */
bool button_pressed = false;
/* Custom keys for the title screen */
if (level_index == 0)
{
bool challenge_enabled = button.at("challenge decrement").enabled();
bool level_enabled = button.at("level decrement").enabled();
bool profile_enabled = button.at("profile decrement").enabled();
bool view_enabled = button.at("view decrement").enabled();
/* Prevent navigating into menus in demo and arcade-only modes */
if (sb::Delegate::compare(event, "down") && !configuration()("display", "arcade only") &&
!configuration()("demo", "active"))
if (menu_active)
{
if (selected == "start")
if (sb::Delegate::compare(event, {"up", "right", "down", "left"}))
{
if (challenge_enabled) selected = "challenge decrement";
else if (level_enabled) selected = "level decrement";
else if (profile_enabled) selected = "profile decrement";
else if (view_enabled) selected = "view decrement";
}
else if (selected == "challenge decrement")
{
selected = "challenge increment";
}
else if (selected == "challenge increment")
{
if (level_enabled) selected = "level decrement";
else if (profile_enabled) selected = "profile decrement";
else if (view_enabled) selected = "view decrement";
else selected = "start";
}
else if (selected == "level decrement")
{
selected = "level increment";
}
else if (selected == "level increment")
{
if (profile_enabled) selected = "profile decrement";
else if (view_enabled) selected = "view decrement";
else selected = "start";
}
else if (selected == "profile decrement")
{
selected = "profile increment";
}
else if (selected == "profile increment")
{
if (view_enabled) selected = "view decrement";
else selected = "start";
}
else if (selected == "view decrement")
{
selected = "view increment";
}
else
{
selected = "start";
}
}
/* Prevent navigating into menus in demo and arcade-only modes */
else if (sb::Delegate::compare(event, "up") && !configuration()("display", "arcade only") &&
!configuration()("demo", "active"))
{
if (selected == "start")
{
if (view_enabled) selected = "view increment";
else if (profile_enabled) selected = "profile increment";
else if (level_enabled) selected = "level increment";
else if (challenge_enabled) selected = "challenge increment";
}
else if (selected == "challenge increment")
{
selected = "challenge decrement";
}
else if (selected == "challenge decrement")
{
selected = "start";
}
else if (selected == "level increment")
{
selected = "level decrement";
}
else if (selected == "level decrement")
{
if (challenge_enabled) selected = "challenge increment";
else selected = "start";
}
else if (selected == "profile increment")
{
selected = "profile decrement";
}
else if (selected == "profile decrement")
{
if (level_enabled) selected = "level increment";
else if (challenge_enabled) selected = "challenge increment";
else selected = "start";
}
else if (selected == "view increment")
{
selected = "view decrement";
}
else if (selected == "view decrement")
{
if (profile_enabled) selected = "profile increment";
else if (level_enabled) selected = "level increment";
else if (challenge_enabled) selected = "challenge increment";
else selected = "start";
}
else
{
selected = "start";
if (selected.has_value())
{
selected = nearest_button(selected.value(), sb::Delegate::event_command(event), title_menu);
}
else
{
selected = "start";
}
}
}
/* Execute menu action */
else if (sb::Delegate::compare(event, "any"))
if (sb::Delegate::compare(event, "any"))
{
button_pressed = true;
if (!selected.has_value())
{
button.at("start").press();
@ -2086,9 +2138,10 @@ void Cakefoot::respond(SDL_Event& event)
{
if (sb::Delegate::compare(event, "up") || sb::Delegate::compare(event, "down"))
{
if (selected == "resume")
if (selected.has_value())
{
selected = "reset";
selected = nearest_button(selected.value(), sb::Delegate::event_command(event),
{"resume", "reset", "fullscreen text", "bgm", "sfx"});
}
else
{
@ -2128,23 +2181,20 @@ void Cakefoot::respond(SDL_Event& event)
else if (event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEBUTTONDOWN || sb::Delegate::compare(event, "any"))
{
/* Track whether a button has been pressed with this event */
bool button_pressed = false;
/* Collide with start button and spinners only on title screen */
/* Collide with start button, spinners, and options sub-menu only on title screen */
if (level_index == 0)
{
for (const std::string& name : {
"start", "level increment", "level decrement", "profile increment", "profile decrement",
"challenge increment", "challenge decrement", "view increment", "view decrement"})
for (const std::string& name : title_menu)
{
if (!configuration()("display", "arcade only") || name == "start")
{
if (button.at(name).enabled() && button.at(name).collide(mouse_ndc, view, projection))
{
selected = name;
hovering = true;
if (event.type == SDL_MOUSEBUTTONDOWN)
{
button_pressed = true;
button.at(name).press();
/* Cancel hover on the start button because the button will be removed from the screen after the
@ -2166,10 +2216,11 @@ void Cakefoot::respond(SDL_Event& event)
/* Check pause menu buttons */
else if (level_index > 0 && !unpaused_timer)
{
for (const std::string& button_name : {"resume", "reset"})
for (const std::string& button_name : {"resume", "reset", "fullscreen text", "bgm", "sfx"})
{
if (button.at(button_name).collide(mouse_ndc, view, projection))
{
selected = button_name;
if (event.type == SDL_MOUSEBUTTONDOWN)
{
button.at(button_name).press();
@ -2219,8 +2270,9 @@ void Cakefoot::respond(SDL_Event& event)
/* Start character acceleration */
bool acceleration_pressed = (
(event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) || sb::Delegate::compare(event, "any"));
if (!shift_pressed && !button_pressed && acceleration_pressed)
(event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) ||
sb::Delegate::compare(event, "any"));
if (!shift_pressed && !button_pressed && acceleration_pressed && unpaused_timer)
{
character.accelerating = true;
}
@ -2258,7 +2310,8 @@ void Cakefoot::respond(SDL_Event& event)
}
}
else if (sb::Delegate::compare(event, "pause") && level_index > 0 && static_cast<std::size_t>(level_index) <= configuration()("levels").size() - 2)
else if (sb::Delegate::compare(event, "pause") && level_index > 0 &&
static_cast<std::size_t>(level_index) <= configuration()("levels").size() - 2)
{
if (!unpaused_timer)
{
@ -2270,6 +2323,14 @@ void Cakefoot::respond(SDL_Event& event)
unpaused_timer.off();
run_timer.off();
/* UI */
selected = "resume";
set_up_buttons();
/* Cancel sfx */
audio.at("walk").stop();
audio.at("reverse").stop();
/* Transition between main theme and menu theme */
if (audio.at("main").playing())
{
@ -2325,7 +2386,8 @@ void Cakefoot::respond(SDL_Event& event)
message << "Pre-ad volume registered as " << pre_ad_volume.value();
sb::Log::log(message);
/* Mute without changing the state of the button to avoid losing the original state if this event is fired twice in a row. */
/* Mute without changing the state of the button to avoid losing the original state if this event is fired twice in
* a row. */
Mix_Volume(-1, 0);
if (level_index > 0 && static_cast<std::size_t>(level_index) <= configuration()("levels").size() - 2)
@ -2368,6 +2430,8 @@ void Cakefoot::respond(SDL_Event& event)
set_up_buttons();
set_up_hud();
load_audio();
rotating_hue.hsv(
0.0f, configuration()("display", "highlight saturation"), configuration()("display", "highlight value"));
}
else if (sb::Delegate::compare(event, "window resize"))
@ -2433,7 +2497,8 @@ void Cakefoot::respond(SDL_Event& event)
else if (use_play_button && !button.at("play").pressed())
{
/* Collide with play button */
if ((event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEMOTION) && button.at("play").collide(mouse_ndc, view, projection))
if ((event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEMOTION) &&
button.at("play").collide(mouse_ndc, view, projection))
{
if (event.type == SDL_MOUSEBUTTONDOWN)
{
@ -2480,6 +2545,16 @@ void Cakefoot::respond(SDL_Event& event)
{
SDL_SetCursor(SDL_GetDefaultCursor());
}
/* Display mouse or gamepad/keyboard UI. The UI changes state only if input was detected, and the state chosen is based on
* whether the detected input is mouse or joypad/keyboard input. */
if (mouse_input_registered || joy_or_key_input_registered)
{
SDL_ShowCursor(mouse_input_registered);
button.at("fullscreen").visible(mouse_input_registered);
button.at("volume").visible(mouse_input_registered);
button.at("pause").visible(mouse_input_registered);
}
}
bool Cakefoot::paused() const
@ -2487,6 +2562,93 @@ bool Cakefoot::paused() const
return !unpaused_timer;
}
std::string Cakefoot::nearest_button(
const std::string& subject, const std::string& direction, const std::map<std::string, sb::Pad<>>& pool) const
{
std::optional<std::string> nearest;
float closest_distance;
for (const auto& [name, pad] : pool)
{
/* Don't search against self */
if (name != subject)
{
/* Compare box positions of two buttons */
const sb::Box& box_a {button.at(subject).box()};
const sb::Box& box_b {button.at(name).box()};
/* Check distance based on direction */
if (direction == "up" || direction == "down")
{
float dy = direction == "up" ? box_a.top() - box_b.bottom() : box_a.bottom() - box_b.top();
if ((direction == "up" && dy < 0) || (direction == "down" && dy > 0))
{
if (!nearest.has_value() || std::abs(dy) < closest_distance)
{
nearest = name;
closest_distance = std::abs(dy);
}
else if (std::abs(dy) == closest_distance)
{
float box_b_dx = box_a.cx() - box_b.cx();
float current_dx = box_a.cx() - button.at(nearest.value()).box().cx();
if (std::abs(box_b_dx) < std::abs(current_dx))
{
nearest = name;
closest_distance = std::abs(dy);
}
}
}
}
else if (direction == "right" || direction == "left")
{
float dx = direction == "right" ? box_a.right() - box_b.left() : box_a.left() - box_b.right();
if ((direction == "right" && dx < 0) || (direction == "left" && dx > 0))
{
if (!nearest.has_value() || std::abs(dx) < closest_distance)
{
nearest = name;
closest_distance = std::abs(dx);
}
else if (std::abs(dx) == closest_distance)
{
float box_b_dy = box_a.cy() - box_b.cy();
float current_dy = box_a.cy() - button.at(nearest.value()).box().cy();
if (std::abs(box_b_dy) < std::abs(current_dy))
{
nearest = name;
closest_distance = std::abs(dx);
}
}
}
}
}
}
if (nearest.has_value())
{
return nearest.value();
}
else
{
return subject;
}
}
std::string Cakefoot::nearest_button(
const std::string& subject, const std::string& direction, const std::vector<std::string>& names) const
{
std::map<std::string, sb::Pad<>> subset;
for (const std::string& name : names)
{
subset[name] = button.at(name);
}
return nearest_button(subject, direction, subset);
}
std::string Cakefoot::nearest_button(const std::string& subject, const std::string& direction) const
{
return nearest_button(subject, direction, button);
}
void Cakefoot::run()
{
/* Start timers precisely when the game update loop starts */
@ -2594,7 +2756,8 @@ void Cakefoot::update(float timestamp)
idle_timer.update(timestamp);
/* In demo mode, reset game if idle timeout elapsed, or reset idle timer if character is accelerating */
if (level_index > 0 && configuration()("demo", "active") && idle_timer.elapsed() > configuration()("demo", "idle timeout"))
if (level_index > 0 && configuration()("demo", "active") && idle_timer.elapsed() >
configuration()("demo", "idle timeout"))
{
sb::Delegate::post("reset");
}
@ -2851,37 +3014,33 @@ void Cakefoot::update(float timestamp)
glm::mat4 label_transformation {0.0f};
if (level_index == 0)
{
/* Flash play button */
if (!configuration()("display", "use arcade prompt"))
/* Play button */
if (!configuration()("display", "use arcade prompt") && selected == "start")
{
glUniform4fv(uniform.at("color addition"), 1, &rotating_hue.normal()[0]);
}
if (selected != "start" || blinking_visible)
{
button.at("start").draw(uniform["mvp"], view, projection, uniform["texture enabled"]);
}
button.at("start").draw(uniform["mvp"], view, projection, uniform["texture enabled"]);
if (!flash_animation.playing())
{
glUniform4fv(uniform.at("color addition"), 1, &glm::vec4(0)[0]);
}
/* Disable spinners if arcade prompt displayed */
/* Disable spinners if arcade prompt displayed*/
if (!configuration()("display", "use arcade prompt"))
{
/* Draw spinner buttons */
for (const std::string& name : {
"level decrement", "level increment", "profile decrement", "profile increment", "challenge decrement",
"challenge increment", "view decrement", "view increment"
})
/* Always include challenge, but only include other spinners when options sub-menu is not active */
std::vector<std::string> names;
if (configuration()("challenge", challenge_index, "name") != "OPTIONS")
{
if (selected != name || blinking_visible)
{
button.at(name).draw(uniform.at("mvp"), view, projection, uniform.at("texture enabled"));
}
names = {"level select", "profile", "challenge", "view"};
}
else
{
names = {"challenge"};
}
/* Draw spinner labels */
for (const std::string& name : {"level select", "profile", "challenge", "view"})
for (const std::string& name : names)
{
if ((name != "profile" || profile_spinner_visible) && (name != "view" || view_spinner_enabled))
{
@ -2890,6 +3049,32 @@ void Cakefoot::update(float timestamp)
glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &label_transformation[0][0]);
label.at(name).enable();
glDrawArrays(GL_TRIANGLES, 0, label.at(name).attributes("position")->count());
} }
/* If options sub-menu is not active, draw spinner buttons. Otherwise, draw options sub-menu buttons. */
if (configuration()("challenge", challenge_index, "name") != "OPTIONS")
{
names = {
"level decrement", "level increment", "profile decrement", "profile increment", "challenge decrement",
"challenge increment", "view decrement", "view increment"
};
}
else
{
names = {"challenge decrement", "challenge increment", "fullscreen text", "bgm", "sfx", "exit"};
}
/* Draw buttons */
for (const std::string& name : names)
{
if (selected == name)
{
glUniform4fv(uniform.at("color addition"), 1, &rotating_hue.normal()[0]);
}
button.at(name).draw(uniform.at("mvp"), view, projection, uniform.at("texture enabled"));
if (!flash_animation.playing())
{
glUniform4fv(uniform.at("color addition"), 1, &glm::vec4(0)[0]);
} } } }
else
{
@ -2902,11 +3087,16 @@ void Cakefoot::update(float timestamp)
}
else
{
for (std::string name : {"resume", "reset"})
for (std::string name : {"resume", "reset", "fullscreen text", "bgm", "sfx"})
{
if (selected != name || blinking_visible)
if (selected == name)
{
button.at(name).draw(uniform["mvp"], view, projection, uniform["texture enabled"]);
glUniform4fv(uniform.at("color addition"), 1, &rotating_hue.normal()[0]);
}
button.at(name).draw(uniform["mvp"], view, projection, uniform["texture enabled"]);
if (!flash_animation.playing())
{
glUniform4fv(uniform.at("color addition"), 1, &glm::vec4(0)[0]);
}
}

View File

@ -236,7 +236,11 @@ private:
{"name 3 decrement", sb::Pad<>()},
{"fullscreen", sb::Pad<>()},
{"diskmem", sb::Pad<>()},
{"azuria sky", sb::Pad<>()}
{"azuria sky", sb::Pad<>()},
{"fullscreen text", sb::Pad<>()},
{"bgm", sb::Pad<>()},
{"sfx", sb::Pad<>()},
{"exit", sb::Pad<>()}
};
std::map<std::string, std::shared_ptr<TTF_Font>> fonts {
{"medium", font(configuration()("font", "medium", "path").get<std::string>(), configuration()("font", "medium", "size"))},
@ -277,7 +281,7 @@ private:
std::string name_entry;
sb::Text scoreboard {fonts.at("large")}, thanks {fonts.at("medium")};
std::vector<Flame> ending_coins;
sb::Color rotating_hue {128, 0, 0, 0};
sb::Color rotating_hue;
std::vector<sb::Text> ending_messages;
std::optional<std::string> selected;
std::shared_ptr<SDL_GameController> controller = nullptr;
@ -369,7 +373,8 @@ private:
int distance() const;
/*!
* @return The time limit of the current run. Combines the challenge mode's initial limit with added time for completed levels and checkpoints.
* @return The time limit of the current run. Combines the challenge mode's initial limit with added time for completed levels and
* checkpoints.
*/
float limit() const;
@ -411,14 +416,14 @@ private:
inline bool skip_resume_quest()
{
return configuration()("challenge", challenge_index, "name") == "RESUME QUEST" && configuration()("progress", "quest level") == 1 &&
configuration()("progress", "quest checkpoint") == 0.0f;
return configuration()("challenge", challenge_index, "name") == "RESUME QUEST" &&
configuration()("progress", "quest level") == 1 && configuration()("progress", "quest checkpoint") == 0.0f;
}
inline bool skip_resume_arcade()
{
return configuration()("challenge", challenge_index, "name") == "RESUME ARCADE" && configuration()("progress", "arcade level") == 1 &&
configuration()("progress", "arcade checkpoint") == 0.0f;
return configuration()("challenge", challenge_index, "name") == "RESUME ARCADE" &&
configuration()("progress", "arcade level") == 1 && configuration()("progress", "arcade checkpoint") == 0.0f;
}
inline bool skip_level_select()
@ -515,6 +520,30 @@ private:
*/
void refresh_scoreboard();
/*!
* Return the name of the button closest to a given button in Cakefoot::buttons in the specified direction ("up", "right", "down",
* or "left").
*
* @param subject The button to base the search on
* @param direction One of either "up", "right", "down", or "left"
* @param pool Buttons to search through. If omitted, all buttons will be searched.
* @return The nearest button in the specified direction or the subject if none is found
*/
std::string nearest_button(const std::string& subject, const std::string& direction,
const std::map<std::string, sb::Pad<>>& pool) const;
/*!
* @overload nearest_button(const std::string&, const std::string&, const std::map<std::string, sb::Pad<>>&)
*/
std::string nearest_button(const std::string& subject, const std::string& direction,
const std::vector<std::string>& names) const;
/*!
* @overload nearest_button(const std::string&, const std::string&, const std::map<std::string, sb::Pad<>>&)
*
* Searches every Cakefoot::button
*/
std::string nearest_button(const std::string& subject, const std::string& direction) const;
public:
/*!

View File

@ -14,7 +14,8 @@ void Character::profile(const std::string& name)
/* Reload the texture */
_sprite.clear_textures();
nlohmann::json frames = configuration("progress", "jackpot") == 777 ? configuration("character", "jackpot frames") : profile().at("animation frames");
nlohmann::json frames = configuration("progress", "jackpot") == 777 ? configuration("character", "jackpot frames") :
profile().at("animation frames");
for (const std::string path : frames)
{
_sprite.texture(path, GL_LINEAR);
@ -95,6 +96,12 @@ bool Character::resting() const
return _resting;
}
bool Character::resting(bool state)
{
_resting = state;
return _resting;
}
float Character::relative(const Curve& curve) const
{
return float(next_point_index) / curve.length();
@ -118,12 +125,14 @@ void Character::update(const Curve& curve, const sb::Timer& timer, bool muted, s
_resting = false;
/* Apply delta time to the speed increase. */
speed += timer.delta(profile()["speed increment"].get<float>()) + glm::abs(speed) * profile()["increment mod"].get<float>();
speed += timer.delta(profile()["speed increment"].get<float>()) +
glm::abs(speed) * profile()["increment mod"].get<float>();
}
else
{
/* Apply delta time to the speed decrease. */
speed -= timer.delta(profile()["speed decrement"].get<float>()) + glm::abs(speed) * profile()["decrement mod"].get<float>();
speed -= timer.delta(profile()["speed decrement"].get<float>()) +
glm::abs(speed) * profile()["decrement mod"].get<float>();
}
}

View File

@ -140,6 +140,12 @@ public:
*/
bool resting() const;
/*!
* @param state resting state
* @return resting state
*/
bool resting(bool state);
/*!
* @return character's relative position on the given curve
*/

11
src/config_local.json Normal file
View File

@ -0,0 +1,11 @@
{
"recording":
{
"enabled": true
},
"log":
{
"enabled": true
}
}

View File

@ -1,12 +1,13 @@
/* _ _
* c/a`k-e'f`o^o~t-, | a single-button action game | by @ooofoam
* / _< | wow a living cake the sweet | play online: https://foam.ooo/cakefoot
* > `~_/ | taste of victory | open source: https://open.shampoo.ooo/shampoo/cakefoot
* c/a`k-e'f`o^o~t-, | a single-button action game | play online: https://cakefoot.dank.game
* / _< | wow a living cake the sweet | source: https://open.shampoo.ooo/shampoo/cakefoot
* > `~_/ | taste of victory |
*
* Custom modifications to Emscripten's Module object and other JavaScript to be included in the WASM build specific to the FOAM site.
* Custom modifications to Emscripten's Module object and other JavaScript to be included in the WASM build specific
* to the dank.game site.
*/
/* Collect user play data when running on the FOAM site */
/* Collect user play data */
function collectData()
{
/* Open the database Emscripten's IDBFS library created. */

View File

@ -4,8 +4,9 @@
session_start();
/* The log of play history is stored in JSON format in a file in the same directory as this script. If this file doesn't exist yet, it
* will be created at write time. */
$history_path = "Play_History.json";
* will be created at write time. Each session ID is written to its own file to avoid race conditions between multiple sessions
* sharing a single file. */
$history_path = session_id() . "-Play_History.json";
/* Read JSON data from the history path. If the file doesn't exist, is not in JSON format, or any other exception occurs, just set the
* history to an empty array. */
@ -18,11 +19,15 @@ catch (Exception $e)
$history = array();
}
/* JSON data containing the user's play history is passed as a POST request from JavaScript */
$submitted_user_log = array(session_id() => json_decode(file_get_contents("php://input"), true)["progress"]);
/* JSON data containing the user's play history is passed as a POST request from JavaScript in pre_js_dank.js. Remove HTML and PHP
* special characters and limit length of input to 2048 characters to protect against injections. */
$submitted_user_log = array(session_id() => json_decode(file_get_contents("php://input", false, null, 0, 2048), true)["progress"]);
/* Merge the passed play history into the history array, overwriting any existing data at the current session ID, or adding a new section
* to the array if the session ID doesn't exist as a key in the array yet. Write the array to the history path. */
/* Add a timestamp to the log */
$submitted_user_log["timestamp"] = date("Y-m-d H:i:s");
/* Merge the passed play history into the history array, overwriting any existing data at the current session ID, or adding a new
* section to the array if the session ID doesn't exist as a key in the array yet. Write the array to the history path. */
file_put_contents(
$history_path,
json_encode(