add demo mode

This commit is contained in:
ohsqueezy 2024-03-06 21:22:54 -05:00
parent 341bb50bef
commit e8ea466f51
8 changed files with 224 additions and 82 deletions

View File

@ -35,6 +35,10 @@
"game over background": [0.0, 0.0, 0.0, 255.0],
"game over translation": [0.0, 0.0],
"game over scale": [0.25, 0.05],
"idle warning foreground": [255.0, 255.0, 255.0, 255.0],
"idle warning background": [0.0, 0.0, 0.0, 255.0],
"idle warning translation": [0.0, 0.0],
"idle warning scale": [0.4, 0.05],
"arcade rank scale": [0.5, 0.15],
"arcade rank dimensions": [300.0, 80.0],
"arcade rank translation": [0.3, 0.6],
@ -50,7 +54,7 @@
"scoreboard translation": [-0.4, 0.835],
"scoreboard scale": [1.35, 0.14],
"scoreboard wrap": 3000,
"qr display": false,
"qr display": true,
"qr background display": true,
"qr background texture": "resource/qr_background.png",
"qr texture": "resource/qr.png",
@ -118,7 +122,8 @@
"suppress any key on mods": true,
"any key ignore commands": ["left", "right", "up", "down", "pause"],
"gamepad pause button index": 9,
"gamepad axis cooldown": 0.2
"gamepad axis cooldown": 0.2,
"gamepad reset button index": 8
},
"keys":
@ -180,7 +185,8 @@
{
"coin": ["resource/coin/coin-0.png"],
"flame": ["resource/flame/flame-1.png", "resource/flame/flame-2.png"],
"auto save": "resource/Autosave.png"
"auto save": "resource/Autosave.png",
"demo message": "resource/Demo_message.png"
},
"curve":
@ -440,5 +446,15 @@
"thanks translation": [0.3, -0.78],
"thanks scale": [1.58, 0.13],
"thanks wrap": 1080
},
"demo":
{
"active": true,
"idle timeout": 20.0,
"countdown display timeout": 11.0,
"countdown message": "IDLE RESET IN ",
"message translation": [0.0, 0.83],
"message scale": [1.05, 0.14]
}
}

View File

@ -88,9 +88,19 @@
<!--
<div id="message">
<div>email list
<div>hints (coins, controls, arcade cabinet, launch, game engine and source, merch, etc.)
<div>follow
<div>Join e-mail list
<div>Watch the trailer and wishlist on Steam
<div>Controls
<div>Hint: collect every coin for a different ending
<div>Hint: unlock BUFFALO BEEF CAKE to get a heavyweight character and faster times
<div>Hint: there are at least two other cakes, maybe three?
<div>Follow on Twitter, Tik Tok, YouTube, Steam and itch.io
<div>There is a press kit available
<div>Release dates and platforms
<div>Ad support and subscriptions coming soon
<div>Visit the arcade cabinet
<div>Cakefoot is powered by a custom game engine SPACE🪐BOX
<div>Buy a 5-pack of Cakefoot pins
-->
<div id="message">

2
lib/sb

@ -1 +1 @@
Subproject commit 0e4f06d77917d1f4349fd0f46f245eb7e63ebfca
Subproject commit 27ab95037f3f6d4f2a3674db99dd1946df07521e

BIN
resource/Demo_message.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -98,11 +98,15 @@ Cakefoot::Cakefoot(std::initializer_list<std::string> configuration_merge) : Gam
configuration()["progress"]["current challenge"] = 4;
}
/* Set the spinner values to what the player was last playing. */
level_select_index = configuration()("progress", "current level");
profile_index = configuration()("progress", "current difficulty");
challenge_index = configuration()("progress", "current challenge");
view_index = configuration()("progress", "current view");
/* Set the spinner values to what the player was last playing, unless demo mode is active, in which case leave
* the values at the defaults. */
if (!configuration()("demo", "active"))
{
level_select_index = configuration()("progress", "current level");
profile_index = configuration()("progress", "current difficulty");
challenge_index = configuration()("progress", "current challenge");
view_index = configuration()("progress", "current view");
}
/* Initialize name entry */
name_entry = configuration()("display", "default initials");
@ -207,6 +211,9 @@ Cakefoot::Cakefoot(std::initializer_list<std::string> configuration_merge) : Gam
/* Switch volume on */
button.at("volume").press();
/* Track idle time */
idle_timer.on();
#if defined(EMSCRIPTEN)
/* Pause the game when the browser tab is hidden */
if (emscripten_set_visibilitychange_callback(this, false, &respond_to_visibility_change) < 0)
@ -479,7 +486,8 @@ void Cakefoot::set_up_buttons()
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
sb::Plane pause_plane;
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") = sb::Pad<>{pause_plane, configuration()("button", "pause translation"),
configuration()("button", "pause scale"), 1.0f};
button.at("pause").on_state_change([&](bool state){
sb::Delegate::post("pause", false);
});
@ -497,7 +505,8 @@ void Cakefoot::set_up_buttons()
sb::Plane volume_plane;
volume_plane.texture(volume_off_texture);
volume_plane.texture(volume_on_texture);
button.at("volume") = sb::Pad<>{volume_plane, configuration()("button", "volume translation"), configuration()("button", "volume scale"), 1.0f};
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").on_state_change([&](bool state){
/* Mute or unmute (to full volume) depending on the state of the button */
@ -588,12 +597,11 @@ void Cakefoot::set_up_buttons()
}
});
button.at("level decrement").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 decreased below 1, wrap to the last level if the current difficulty is complete, otherwise wrap to the max level
* unlocked. */
/* If the level is decreased below 1, wrap to the last level if the current difficulty is complete, otherwise wrap to
* the max level unlocked. */
if (--level_select_index < 1)
{
if (profile_index < configuration()("progress", "max difficulty"))
@ -841,7 +849,8 @@ void Cakefoot::toggle_challenge()
}
/* In new game modes, set the level select to 1 and leave the difficulty unchanged. */
else if (configuration()("challenge", challenge_index, "name") == "ARCADE" || configuration()("challenge", challenge_index, "name") == "NEW QUEST")
else if (configuration()("challenge", challenge_index, "name") == "ARCADE" ||
configuration()("challenge", challenge_index, "name") == "NEW QUEST")
{
level_select_index = 1;
}
@ -974,7 +983,8 @@ void Cakefoot::set_up_hud()
social.scale(configuration()("display", "social scale"));
/* Set up auto save icon */
auto_save = sb::Sprite {configuration()("texture", "auto save").get<std::string>(), configuration()("display", "auto save scale").get<glm::vec2>(), GL_LINEAR};
auto_save = sb::Sprite {configuration()("texture", "auto save").get<std::string>(),
configuration()("display", "auto save scale").get<glm::vec2>(), GL_LINEAR};
auto_save.translate(configuration()("display", "auto save translation"));
/* Style the quest best time indicator */
@ -999,6 +1009,24 @@ void Cakefoot::set_up_hud()
thanks.translate(configuration()("ending", "thanks translation"));
thanks.scale(configuration()("ending", "thanks scale"));
thanks.refresh();
/* Style the idle warning */
label.at("idle warning").content(configuration()("demo", "countdown message"));
label.at("idle warning").foreground(configuration()("display", "idle warning foreground").get<glm::vec4>());
label.at("idle warning").background(configuration()("display", "idle warning background").get<glm::vec4>());
label.at("idle warning").untransform();
label.at("idle warning").translate(configuration()("display", "idle warning translation"));
label.at("idle warning").scale(configuration()("display", "idle warning scale"));
label.at("idle warning").refresh();
/* Style the demo message */
sb::Texture demo_message_texture {configuration()("texture", "demo message").get<std::string>()};
demo_message_texture.load();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
demo_message.texture(demo_message_texture);
demo_message.translate(configuration()("demo", "message translation"));
demo_message.scale(configuration()("demo", "message scale"));
}
void Cakefoot::load_vbo()
@ -1090,11 +1118,11 @@ void Cakefoot::load_level(int index)
submit_score_animation.reset();
}
/* Reset enemies list to empty. Open configuration for the current level. Repopulate list of enemies one by one using the list of enemies in the
* configuration. For each enemy, add a challenge coin if the config specifies the coin parameters.
/* Reset enemies list to empty. Open configuration for the current level. Repopulate list of enemies one by one using the list
* of enemies in the configuration. For each enemy, add a challenge coin if the config specifies the coin parameters.
*
* Values read from the config are in some cases converted from old 25fps hard-coded per-frame values to per-second values, and hard-coded
* 864px by 486px pixel space to relative NDC space.
* Values read from the config are in some cases converted from old 25fps hard-coded per-frame values to per-second values,
* and hard-coded 864px by 486px pixel space to relative NDC space.
*/
this->enemies.clear();
if (configuration()("levels", index).contains("enemies"))
@ -1107,7 +1135,8 @@ void Cakefoot::load_level(int index)
if (type == "slicer")
{
std::shared_ptr<Slicer> slicer = std::make_shared<Slicer>(
curve(), enemy[1].get<float>(), 2.0f * 25.0f * enemy[2].get<float>() / 486.0f, 2.0f * enemy[3].get<float>() / 486.0f);
curve(), enemy[1].get<float>(), 2.0f * 25.0f * enemy[2].get<float>() / 486.0f,
2.0f * enemy[3].get<float>() / 486.0f);
/* Add coin to slicer */
if (enemy.size() > 4)
@ -1176,7 +1205,8 @@ void Cakefoot::load_level(int index)
float x = field.left() + shift * margin.x / 2.0f;
while (x < field.right())
{
std::shared_ptr<Flame> flame = std::make_shared<Flame>(field, glm::vec3{x, y, 0.0f}, 0.41152263f, glm::quarter_pi<float>());
std::shared_ptr<Flame> flame = std::make_shared<Flame>(
field, glm::vec3{x, y, 0.0f}, 0.41152263f, glm::quarter_pi<float>());
/* Add a challenge coin */
if (++count == 15)
@ -1214,7 +1244,8 @@ void Cakefoot::load_level(int index)
for (std::size_t count = 0; x < range.y; ++count)
{
y = amplitude * glm::sin(period * x) + shift;
std::shared_ptr<Flame> flame = std::make_shared<Flame>(field, glm::vec3{x, y, 0.0f}, speed, 3.0f * glm::half_pi<float>(), mirror);
std::shared_ptr<Flame> flame = std::make_shared<Flame>(
field, glm::vec3{x, y, 0.0f}, speed, 3.0f * glm::half_pi<float>(), mirror);
if (enemy.size() > 8 && enemy[8].get<std::size_t>() == count)
{
flame->coin(coin, enemy[9].get<float>(), enemy[10].get<float>());
@ -1226,8 +1257,8 @@ void Cakefoot::load_level(int index)
}
}
/* If the level is the end screen, reset the player's current level to the beginning and load ending screen coin list. Unlock any new difficulty
* or view. Set a list of messages to be displayed on the end screen. */
/* If the level is the end screen, reset the player's current level to the beginning and load ending screen coin list. Unlock
* any new difficulty or view. Set a list of messages to be displayed on the end screen. */
if (end_screen())
{
/* Load ending coins */
@ -1238,7 +1269,8 @@ void Cakefoot::load_level(int index)
for (std::size_t ii = 0; ii < bank(); ii++)
{
Flame coin {
field, glm::vec3{coin_range.x + coin_step * ii, configuration()("ending", "coin y").get<float>(), 0.0f}, 0.0f, 0.0f, -1.0f, bank() < max_bank()
field, glm::vec3{coin_range.x + coin_step * ii, configuration()("ending", "coin y").get<float>(), 0.0f},
0.0f, 0.0f, -1.0f, bank() < max_bank()
};
ending_coins.push_back(coin);
}
@ -1249,7 +1281,8 @@ void Cakefoot::load_level(int index)
/* Show the end for any run that beats all the levels */
if (quest() || (arcade() && configuration()("progress", "arcade level") >= configuration()("levels").size() - 2))
{
sb::Text message {fonts.at("glyph large"), configuration()("ending", "end text"), configuration()("ending", "messages foreground").get<glm::vec4>()};
sb::Text message {fonts.at("glyph large"), configuration()("ending", "end text"),
configuration()("ending", "messages foreground").get<glm::vec4>()};
message.dimensions(configuration()("ending", "messages dimensions"));
message.refresh();
ending_messages.push_back(message);
@ -1261,7 +1294,8 @@ void Cakefoot::load_level(int index)
if (configuration()("progress", "max view").get<int>() < 1)
{
configuration()["progress"]["max view"] = 1;
sb::Text message {fonts.at("glyph"), configuration()("ending", "unlock mirror"), configuration()("ending", "messages foreground").get<glm::vec4>()};
sb::Text message {fonts.at("glyph"), configuration()("ending", "unlock mirror"),
configuration()("ending", "messages foreground").get<glm::vec4>()};
message.dimensions(configuration()("ending", "messages dimensions"));
message.refresh();
ending_messages.push_back(message);
@ -1269,7 +1303,8 @@ void Cakefoot::load_level(int index)
if (configuration()("progress", "max view").get<int>() < 2 && profile_index >= 1)
{
configuration()["progress"]["max view"] = 2;
sb::Text message {fonts.at("glyph"), configuration()("ending", "unlock warped"), configuration()("ending", "messages foreground").get<glm::vec4>()};
sb::Text message {fonts.at("glyph"), configuration()("ending", "unlock warped"),
configuration()("ending", "messages foreground").get<glm::vec4>()};
message.dimensions(configuration()("ending", "messages dimensions"));
message.refresh();
ending_messages.push_back(message);
@ -1278,7 +1313,8 @@ void Cakefoot::load_level(int index)
{
configuration()["progress"]["jackpot"] = 777;
character.profile(configuration()("character", "profile", profile_index, "name"));
sb::Text message {fonts.at("glyph"), configuration()("ending", "unlock jackpot"), configuration()("ending", "messages foreground").get<glm::vec4>()};
sb::Text message {fonts.at("glyph"), configuration()("ending", "unlock jackpot"),
configuration()("ending", "messages foreground").get<glm::vec4>()};
message.dimensions(configuration()("ending", "messages dimensions"));
message.refresh();
ending_messages.push_back(message);
@ -1308,9 +1344,9 @@ void Cakefoot::load_level(int index)
configuration()["progress"]["current challenge"] = challenge_index;
}
/* In quest mode, unlock higher difficulty at the end of the game: increase difficulty if there is a higher difficulty than the current one.
* Then reset the max level because it will now refer to max level of the next difficulty. Save the time if it is better than the existing
* record. */
/* In quest mode, unlock higher difficulty at the end of the game: increase difficulty if there is a higher difficulty
* than the current one. Then reset the max level because it will now refer to max level of the next difficulty. Save
* the time if it is better than the existing record. */
if (quest())
{
if (profile_index < static_cast<int>(configuration()("character", "profile").size()) - 1)
@ -1319,7 +1355,8 @@ void Cakefoot::load_level(int index)
{
sb::Text message {fonts.at("glyph")};
message.foreground(configuration()("ending", "messages foreground").get<glm::vec4>());
profile_index == 1 ? message.content(configuration()("ending", "unlock beef")) : message.content(configuration()("ending", "unlock buffalo"));
profile_index == 1 ? message.content(configuration()("ending", "unlock beef")) :
message.content(configuration()("ending", "unlock buffalo"));
message.dimensions(configuration()("ending", "messages dimensions"));
message.refresh();
ending_messages.push_back(message);
@ -1334,9 +1371,11 @@ void Cakefoot::load_level(int index)
if (best <= 0.0f || run_timer.elapsed() < best)
{
configuration()["progress"]["quest best"] = run_timer.elapsed();
label.at("quest best").content(configuration()("display", "quest best text").get<std::string>() + " " + format_clock(run_timer.elapsed()));
label.at("quest best").content(
configuration()("display", "quest best text").get<std::string>() + " " + format_clock(run_timer.elapsed()));
label.at("quest best").refresh();
sb::Text message {fonts.at("glyph"), configuration()("ending", "new best"), configuration()("ending", "messages foreground").get<glm::vec4>()};
sb::Text message {fonts.at("glyph"), configuration()("ending", "new best"),
configuration()("ending", "messages foreground").get<glm::vec4>()};
message.dimensions(configuration()("ending", "messages dimensions"));
message.refresh();
ending_messages.push_back(message);
@ -1347,12 +1386,13 @@ void Cakefoot::load_level(int index)
level_select_index = 1;
}
/* Otherwise, if the level is not the title screen, save it as the current level in the player's progress. Also save the newly assigned current level
* in the level select index. */
/* Otherwise, if the level is not the title screen, save it as the current level in the player's progress. Also save the newly
* assigned current level in the level select index. */
else if (index > 0)
{
/* Unlock the level if it is a newly reached level */
if (configuration()("progress", "max difficulty") == profile_index && configuration()("progress", "max level").get<int>() < index)
if (configuration()("progress", "max difficulty") == profile_index &&
configuration()("progress", "max level").get<int>() < index)
{
configuration()["progress"]["max level"] = index;
}
@ -1431,6 +1471,13 @@ void Cakefoot::load_level(int index)
run_timer.on();
}
/* In demo mode, reset the challenge to new quest every time the title is loaded */
if (configuration()("demo", "active") && index == 0)
{
challenge_index = 1;
level_select_index = 1;
}
/* Refresh HUD elements */
set_up_hud();
@ -1622,7 +1669,8 @@ float Cakefoot::limit() const
} } }
/* Add bank bonus */
limit += configuration()("progress", "arcade bank").get<int>() * configuration()("challenge", challenge_index, "bank bonus").get<float>();
limit += configuration()("progress", "arcade bank").get<int>() * configuration()(
"challenge", challenge_index, "bank bonus").get<float>();
}
return limit;
@ -1656,7 +1704,8 @@ bool Cakefoot::end_screen(std::optional<std::size_t> index) const
bool Cakefoot::resuming() const
{
return configuration()("challenge", challenge_index, "name") == "RESUME QUEST" || configuration()("challenge", challenge_index, "name") == "RESUME ARCADE";
return configuration()("challenge", challenge_index, "name") == "RESUME QUEST" ||
configuration()("challenge", challenge_index, "name") == "RESUME ARCADE";
}
std::size_t Cakefoot::bank() const
@ -1808,6 +1857,9 @@ void Cakefoot::respond(SDL_Event& event)
{
Game::respond(event);
/* Reset the idle timer */
idle_timer.reset();
/* Translate gamepad input to commands */
if (event.type == SDL_JOYBUTTONDOWN)
{
@ -1816,6 +1868,12 @@ void Cakefoot::respond(SDL_Event& event)
{
sb::Delegate::post("pause");
}
else if (configuration()("demo", "active") && level_index > 0 &&
static_cast<std::size_t>(level_index) <= configuration()("levels").size() - 2 &&
event.jbutton.button == configuration()("input", "gamepad reset button index"))
{
sb::Delegate::post("reset");
}
else if ((!use_play_button || button.at("play").pressed()) && !splash_animation.playing())
{
sb::Delegate::post("any");
@ -1863,7 +1921,8 @@ void Cakefoot::respond(SDL_Event& event)
glm::vec2 mouse_pixel = event.type == SDL_MOUSEBUTTONDOWN ? glm::vec2{event.button.x, event.button.y} :
glm::vec2{event.motion.x, event.motion.y};
glm::vec2 mouse_ndc {
float(mouse_pixel.x) / window_box().width() * 2.0f - 1.0f, (1.0f - float(mouse_pixel.y) / window_box().height()) * 2.0f - 1.0f
float(mouse_pixel.x) / window_box().width() * 2.0f - 1.0f,
(1.0f - float(mouse_pixel.y) / window_box().height()) * 2.0f - 1.0f
};
/* Track whether cursor should display */
@ -1879,7 +1938,10 @@ void Cakefoot::respond(SDL_Event& event)
bool level_enabled = button.at("level decrement").enabled();
bool profile_enabled = button.at("profile decrement").enabled();
bool view_enabled = button.at("view decrement").enabled();
if (sb::Delegate::compare(event, "down"))
/* Prevent navigating into menus in demo and arcade-only modes */
if (sb::Delegate::compare(event, "down") && !configuration()("display", "arcade only") &&
!configuration()("demo", "active"))
{
if (selected == "start")
{
@ -1927,7 +1989,10 @@ void Cakefoot::respond(SDL_Event& event)
selected = "start";
}
}
else if (sb::Delegate::compare(event, "up"))
/* 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")
{
@ -1979,6 +2044,8 @@ void Cakefoot::respond(SDL_Event& event)
selected = "start";
}
}
/* Execute menu action */
else if (sb::Delegate::compare(event, "any"))
{
if (!selected.has_value())
@ -1993,7 +2060,8 @@ void Cakefoot::respond(SDL_Event& event)
}
/* Custom keys for name entry. */
else if (static_cast<std::size_t>(level_index) == configuration()("levels").size() - 1 && arcade() && configuration()("display", "name entry enabled"))
else if (static_cast<std::size_t>(level_index) == configuration()("levels").size() - 1 && arcade() &&
configuration()("display", "name entry enabled"))
{
if (sb::Delegate::compare(event, "up"))
{
@ -2051,7 +2119,8 @@ void Cakefoot::respond(SDL_Event& event)
else if (event.type == SDL_MOUSEBUTTONUP || sb::Delegate::compare_cancel(event, "any"))
{
/* End character acceleration */
if (sb::Delegate::compare_cancel(event, "any") || (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT))
if (sb::Delegate::compare_cancel(event, "any") ||
(event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT))
{
character.accelerating = false;
}
@ -2066,8 +2135,8 @@ void Cakefoot::respond(SDL_Event& event)
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"})
"start", "level increment", "level decrement", "profile increment", "profile decrement",
"challenge increment", "challenge decrement", "view increment", "view decrement"})
{
if (!configuration()("display", "arcade only") || name == "start")
{
@ -2078,7 +2147,8 @@ void Cakefoot::respond(SDL_Event& event)
{
button.at(name).press();
/* Cancel hover on the start button because the button will be removed from the screen after the press. */
/* Cancel hover on the start button because the button will be removed from the screen after the
* press. */
if (name == "start") hovering = false;
} } } } }
@ -2109,7 +2179,8 @@ void Cakefoot::respond(SDL_Event& event)
} } }
/* Collide with name entry in arcade mode on end screen */
else if (static_cast<std::size_t>(level_index) == configuration()("levels").size() - 1 && arcade() && configuration()("display", "name entry enabled"))
else if (static_cast<std::size_t>(level_index) == configuration()("levels").size() - 1 &&
arcade() && configuration()("display", "name entry enabled"))
{
for (const std::string& button_name : {std::string("name 1"), std::string("name 2"), std::string("name 3"),
"name " + std::to_string(name_entry_index + 1) + " increment",
@ -2459,7 +2530,8 @@ void Cakefoot::update(float timestamp)
}
else
{
fov = 2.0f * glm::atan(((1.0f / (window_box().width() * (9.0f / 16.0f))) * window_box().height()) / camera_position.z) + zoom;
fov = 2.0f * glm::atan(((1.0f / (window_box().width() * (9.0f / 16.0f))) * window_box().height()) / camera_position.z) +
zoom;
}
projection = glm::perspective(fov, window_box().aspect(), 0.1f, 100.0f);
@ -2518,13 +2590,22 @@ void Cakefoot::update(float timestamp)
configuration()["progress"]["quest time"].get_ref<nlohmann::json::number_float_t&>() += run_timer.frame();
}
unpaused_timer.update(timestamp);
idle_timer.update(timestamp);
/* In demo mode, reset if playing and idle timeout elapsed */
if (level_index > 0 && configuration()("demo", "active") &&
idle_timer.elapsed() > configuration()("demo", "idle timeout"))
{
sb::Delegate::post("reset");
}
/* Arcade scoring */
auto& maximum_distance = configuration()["progress"]["arcade max distance"].get_ref<nlohmann::json::number_integer_t&>();
float extended_limit = limit();
if (arcade())
{
/* Check if maximum distance increased. Using auto as the type handles differences between integer types in different compilers. */
/* Check if maximum distance increased. Using auto as the type handles differences between integer types in
* different compilers. */
if (distance() > maximum_distance)
{
maximum_distance = distance();
@ -2532,7 +2613,8 @@ void Cakefoot::update(float timestamp)
/* End run if there is a time limit and the time limit is passed. Queue end level to load after a delay. */
bool game_over_active = arcade() && level_index > 0 &&
run_timer.elapsed() > extended_limit && static_cast<std::size_t>(level_index) < configuration()("levels").size() - 1;
run_timer.elapsed() > extended_limit &&
static_cast<std::size_t>(level_index) < configuration()("levels").size() - 1;
if (game_over_active && !game_over_animation.playing())
{
run_timer.off();
@ -2549,10 +2631,11 @@ void Cakefoot::update(float timestamp)
/* Freeze screen while game over display is active. */
if (!game_over_animation.playing())
{
/* Update character, along the curve, using the timer to determine movement since last frame, and update enemies. Check for collison
* as enemies are updated. */
character.update(curve(), unpaused_timer, !button.at("volume").pressed(),
level_index ? std::nullopt : std::optional<float>(configuration()("character", "idle speed").get<float>()));
/* Update character, along the curve, using the timer to determine movement since last frame, and update enemies.
* Check for collison as enemies are updated. */
character.update(
curve(), unpaused_timer, !button.at("volume").pressed(),
level_index ? std::nullopt : std::optional<float>(configuration()("character", "idle speed").get<float>()));
if (character.at_end(curve()))
{
/* On the ending screen, submit the score and name entry. */
@ -2591,7 +2674,8 @@ void Cakefoot::update(float timestamp)
{
for (nlohmann::json checkpoint : configuration()("levels", level_index, "checkpoints"))
{
if (character.relative(curve()) >= checkpoint["position"].get<float>() && character.checkpoint() < checkpoint["position"].get<float>())
if (character.relative(curve()) >= checkpoint["position"].get<float>() &&
character.checkpoint() < checkpoint["position"].get<float>())
{
audio.at("checkpoint").play();
character.checkpoint(checkpoint["position"].get<float>());
@ -2706,7 +2790,8 @@ void Cakefoot::update(float timestamp)
sprite = &checkpoint_on;
}
glm::vec3 position = curve().relative(checkpoint["position"].get<float>());
glm::vec2 delta = sb::angle_to_vector(checkpoint["angle"].get<float>(), configuration()("display", "checkpoint distance"));
glm::vec2 delta = sb::angle_to_vector(
checkpoint["angle"].get<float>(), configuration()("display", "checkpoint distance"));
position += glm::vec3{delta.x, delta.y, 0.0f};
sprite->translate(curve().wrap(position));
sprite->draw(uniform.at("mvp"), view * rotation_matrix, projection, uniform.at("texture enabled"));
@ -2716,7 +2801,8 @@ void Cakefoot::update(float timestamp)
/* Draw enemies */
for (auto& enemy : enemies)
{
enemy->draw(uniform.at("mvp"), view * rotation_matrix, projection, uniform.at("texture enabled"), rotating_hue, uniform.at("color addition"));
enemy->draw(uniform.at("mvp"), view * rotation_matrix, projection, uniform.at("texture enabled"), rotating_hue,
uniform.at("color addition"));
if (!flash_animation.playing())
{
glUniform4fv(uniform.at("color addition"), 1, &glm::vec4(0)[0]);
@ -2731,7 +2817,8 @@ void Cakefoot::update(float timestamp)
{
for (Flame& coin : ending_coins)
{
coin.draw(uniform.at("mvp"), view * rotation_matrix, projection, uniform.at("texture enabled"), rotating_hue, uniform.at("color addition"));
coin.draw(uniform.at("mvp"), view * rotation_matrix, projection, uniform.at("texture enabled"), rotating_hue,
uniform.at("color addition"));
if (!flash_animation.playing())
{
glUniform4fv(uniform.at("color addition"), 1, &glm::vec4(0)[0]);
@ -2779,8 +2866,8 @@ void Cakefoot::update(float timestamp)
{
/* Draw spinner buttons */
for (const std::string& name : {
"level decrement", "level increment", "profile decrement", "profile increment", "challenge decrement", "challenge increment",
"view decrement", "view increment"
"level decrement", "level increment", "profile decrement", "profile increment", "challenge decrement",
"challenge increment", "view decrement", "view increment"
})
{
if (selected != name || blinking_visible)
@ -2868,7 +2955,8 @@ void Cakefoot::update(float timestamp)
if (level_index > 0 && static_cast<std::size_t>(level_index) < _configuration("levels").size() - 1)
{
std::stringstream level_indicator;
level_indicator << std::setw(2) << std::setfill('0') << level_index << "/" << std::setw(2) << _configuration("levels").size() - 2;
level_indicator << std::setw(2) << std::setfill('0') << level_index << "/" << std::setw(2) <<
_configuration("levels").size() - 2;
label.at("level").content(level_indicator.str());
label.at("level").refresh();
label.at("level").texture(0).bind();
@ -2888,6 +2976,28 @@ void Cakefoot::update(float timestamp)
glDrawArrays(GL_TRIANGLES, 0, label.at("game over").attributes("position")->count());
}
/* Draw idle warning */
if (level_index > 0 && configuration()("demo", "active") &&
idle_timer.elapsed() > configuration()("demo", "countdown display timeout"))
{
std::stringstream idle_warning_message;
int remaining = std::ceil(configuration()("demo", "idle timeout").get<float>() - idle_timer.elapsed());
idle_warning_message << configuration()("demo", "countdown message").get<std::string>() << remaining;
label.at("idle warning").content(idle_warning_message.str());
label.at("idle warning").refresh();
label.at("idle warning").texture(0).bind();
label_transformation = projection * view * label.at("idle warning").transformation();
glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &label_transformation[0][0]);
label.at("idle warning").enable();
glDrawArrays(GL_TRIANGLES, 0, label.at("idle warning").attributes("position")->count());
}
/* Draw demo message */
if (level_index == 0 && configuration()("demo", "active"))
{
demo_message.draw(uniform.at("mvp"), view, projection, uniform.at("texture enabled"));
}
/* Draw arcade results */
if (static_cast<std::size_t>(level_index) == configuration()("levels").size() - 1 && arcade())
{
@ -2946,7 +3056,8 @@ void Cakefoot::update(float timestamp)
}
/* Draw end screen messages */
if (static_cast<std::size_t>(level_index) == configuration()("levels").size() - 1 && !configuration()("display", "arcade only"))
if (static_cast<std::size_t>(level_index) == configuration()("levels").size() - 1 &&
!configuration()("display", "arcade only"))
{
float y = configuration()("ending", "messages y").get<float>();
for (std::size_t message_ii = 0; message_ii < ending_messages.size(); message_ii++)

View File

@ -1,6 +1,6 @@
/* _ _
* c/a`k-e'f`o^o~t-, | a single-button action game | by @ooofoam
* / _< | wow a living cake the sweet | play online: https://shampoo.ooo/cakefoot
* c/a`k-e'f`o^o~t-, | a single-button action game | by @dankd0tgame
* / _< | wow a living cake the sweet | play online: https://cakefoot.dank.game
* _> `~_/ | taste of victory | open source: https://open.shampoo.ooo/shampoo/cakefoot
*/
@ -201,8 +201,8 @@ private:
/* Member vars */
std::shared_ptr<SDL_Cursor> poke, grab;
int previous_frames_per_second = 0, curve_index = 0, curve_byte_count = 0, level_index = 0, level_select_index = 1, profile_index = 0,
challenge_index = 0, view_index = 0, name_entry_index = 0, splash_index = 0;
int previous_frames_per_second = 0, curve_index = 0, curve_byte_count = 0, level_index = 0, level_select_index = 1,
profile_index = 0, challenge_index = 0, view_index = 0, name_entry_index = 0, splash_index = 0;
std::map<std::string, GLuint> uniform;
GLuint shader_program;
glm::mat4 view {1.0f}, projection {1.0f};
@ -241,7 +241,8 @@ private:
{"small", font(configuration()("font", "small", "path").get<std::string>(), configuration()("font", "small", "size"))},
{"large", font(configuration()("font", "large", "path").get<std::string>(), configuration()("font", "large", "size"))},
{"glyph", font(configuration()("font", "glyph", "path").get<std::string>(), configuration()("font", "glyph", "size"))},
{"glyph large", font(configuration()("font", "glyph large", "path").get<std::string>(), configuration()("font", "glyph large", "size"))}
{"glyph large", font(configuration()("font", "glyph large", "path").get<std::string>(),
configuration()("font", "glyph large", "size"))}
};
std::map<std::string, sb::Text> label = {
{"fps", sb::Text(font())},
@ -254,11 +255,12 @@ private:
{"game over", sb::Text(font())},
{"arcade rank", sb::Text(fonts.at("large"))},
{"arcade distance", sb::Text(fonts.at("large"))},
{"quest best", sb::Text(fonts.at("glyph"))}
{"quest best", sb::Text(fonts.at("glyph"))},
{"idle warning", sb::Text(font())}
};
sb::Sprite playing_field, checkpoint_on, checkpoint_off, coin {"resource/coin/coin-0.png", glm::vec2{12.0f / 486.0f}, GL_LINEAR}, qr_code, qr_code_bg, social,
auto_save;
sb::Timer on_timer, run_timer, unpaused_timer;
sb::Sprite playing_field, checkpoint_on, checkpoint_off, qr_code, qr_code_bg, social, auto_save, demo_message,
coin {"resource/coin/coin-0.png", glm::vec2{12.0f / 486.0f}, GL_LINEAR};
sb::Timer on_timer, run_timer, unpaused_timer, idle_timer;
glm::vec3 camera_position {0.0f, 0.0f, 2.0f}, subject_position {0.0f, 0.0f, 0.0f};
float zoom = 0.0f;
glm::vec2 rotation = {0.0f, 0.0f};
@ -293,7 +295,8 @@ private:
/*!
* Compile and attach shaders, store locations of uniforms, initialize some GL properties. This must be done after the GL context
* is created (currently the context is created in the Game constructor, so it will have been created already when this is called).
* is created (currently the context is created in the Game constructor, so it will have been created already when this is
* called).
*/
void initialize_gl();
@ -422,7 +425,8 @@ private:
}
/*!
* Remove coin from the enemy and the level. If quest or arcade mode is active and flag is not set, add the coin to the appropriate bank.
* Remove coin from the enemy and the level. If quest or arcade mode is active and flag is not set, add the coin to the
* appropriate bank.
*
* @param add_to_bank flag to either add to the bank or not
*/
@ -433,8 +437,8 @@ private:
*/
void end_game_over_display();
/* This animation can be used to end the game over state after the time limit is reached. Play once with a delay to let the game over screen
* display temporarily before being ended by this animation. */
/* This animation can be used to end the game over state after the time limit is reached. Play once with a delay to let the
* game over screen display temporarily before being ended by this animation. */
Animation game_over_animation {std::bind(&Cakefoot::end_game_over_display, this)};
/*!
@ -480,7 +484,8 @@ private:
Animation splash_animation {std::bind(&Cakefoot::next_splash, this)};
/*!
* Set arcade time limit warning state at Cakefoot::arcade_limit_warning based on mode, time remaining, and whether the blinking frame is on or off.
* Set arcade time limit warning state at Cakefoot::arcade_limit_warning based on mode, time remaining, and whether the
* blinking frame is on or off.
*/
void flash_warning();