- 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:
parent
ba53c72b01
commit
2eae10da98
|
@ -15,7 +15,7 @@ ext/
|
|||
local/
|
||||
src/__pycache__/
|
||||
storage/
|
||||
Play_History.json
|
||||
*Play_History.json
|
||||
press.html
|
||||
feed
|
||||
test.html
|
||||
|
|
6
Makefile
6
Makefile
|
@ -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_collect.js" -o $(WASM_BUILD_DIR)/$@
|
||||
|
||||
cakefoot_debug.html : CC = $(EMSCRIPTENHOME)/emcc
|
||||
cakefoot_debug.html : CXX = $(EMSCRIPTENHOME)/em++
|
||||
|
|
41
config.json
41
config.json
|
@ -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 ",
|
||||
|
|
|
@ -252,7 +252,7 @@
|
|||
var ticker_content = [
|
||||
"Use ☝️, 🖱️, ⌨️ or 🎮 to play",
|
||||
"🗓️ 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> ▶️ and " +
|
||||
"<a href='https://store.steampowered.com/app/2869020/Cakefoot/'>wishlist on Steam</a>",
|
||||
|
|
2
lib/sb
2
lib/sb
|
@ -1 +1 @@
|
|||
Subproject commit 8498dfa00472431a6dd8a2ec588f0792a9e435dc
|
||||
Subproject commit 102d1749a5325139723a887377523e8f5ff9e0b9
|
602
src/Cakefoot.cpp
602
src/Cakefoot.cpp
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
/*!
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"recording":
|
||||
{
|
||||
"enabled": true
|
||||
},
|
||||
|
||||
"log":
|
||||
{
|
||||
"enabled": true
|
||||
}
|
||||
}
|
|
@ -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
|
||||
/* _ _
|
||||
* / `-' `-^~~-, | cakefoot presented by dank.game
|
||||
* \__ _ _< |
|
||||
* `v ) `~_/ | source code and license at https://open.shampoo.ooo/shampoo/cakefoot
|
||||
*
|
||||
* Custom modifications to Emscripten's Module object and other JavaScript to be included in the WASM build specific to the FOAM site.
|
||||
* @file pre_js_collect.js
|
||||
*
|
||||
* Addition to Emscripten's Module object to be included in WASM builds that will collect user play data.
|
||||
*/
|
||||
|
||||
/* Collect user play data when running on the FOAM site */
|
||||
function collectData()
|
||||
{
|
||||
/* Open the database Emscripten's IDBFS library created. */
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue