- added coin bank to HUD

- save coin status per level instead of just a total count of coins
- bug fix: removed flashing effect from all non-coin projectiles
- bug fix: don't collect coins when resting
This commit is contained in:
Cocktail Frank 2024-05-08 16:45:24 -04:00
parent 290e23b5da
commit bacf711cd7
13 changed files with 340 additions and 107 deletions

View File

@ -12,8 +12,8 @@
| | [TikTok (@dankd0tgame)](https://https://www.tiktok.com/@dankd0tgame) |
| | [Itch.io (@ohsqueezy)](https://ohsqueezy.itch.io) |
| | [Cakefoot on IGDB](https://www.igdb.com/games/cakefoot) |
| Genre | Action, Arcade, Rage, Minimalist, Single-button, Masocore |
| Platforms | Web, PC, Mac, Linux, [Coolmathgames](https://coolmathgames.com) |
| Genre | Action, Indie, Arcade, Rage, Minimalist, Single-button, Masocore |
| Platforms | Web, PC, Mac, Linux, [Coolmath](https://coolmathgames.com) |
| Release | 📅 1/1/2024 (Early Access) |
| | 📅 5/10/2024 (Web, PC, Mac, Linux) |
| | 📅 TBA (Android) |
@ -75,6 +75,11 @@ CAKE is just the beginning -- beat the game and collect all the coins to unlock
![](https://5.shampoo.ooo/video/Cakefoot_a_new_perspective.gif)
Keys
====
Free keys are available for the Steam version, and standalone builds for PC, MacOS, Linux, and Raspberry Pi are [available for download](https://cakefoot.dank.game/dist/protected). If you are interested in covering Cakefoot, streaming it, or otherwise need a key, send me a message at cocktail.frank at dank.game!
Screenshots
===========
@ -151,16 +156,11 @@ Past events
About
=====
The original version of _Cakefoot_ was a free downloadable game created in 2019 for [Ludum Dare 45](https://ldjam.com/events/ludum-dare/45/cakewalk) by [diskmem](https://ohsqueezy.itch.io) and [Azuria Sky](https://azuria-sky.bandcamp.com). Like the final version, the original was also a single-button arcade game with a unique, 2D physics on-rails movement mechanic. A small update after the jam added enemy sprites and a new level. The game remained available for free on itch.io. In 2023, after _Cakefoot_ was successfully pitched as a web game to [Coolmathgames](https://coolmathgames.com), diskmem revived the project and added many features, including introductory levels, coins, automatic saves, arcade mode, and unlockables.
The original version of _Cakefoot_ was a free downloadable game created in 2019 for [Ludum Dare 45](https://ldjam.com/events/ludum-dare/45/cakewalk) by [diskmem](https://ohsqueezy.itch.io) and [Azuria Sky](https://azuria-sky.bandcamp.com). Like the final version, the original was also a single-button arcade game with a unique, 2D physics on-rails movement mechanic. A small update after the jam added enemy sprites and a new level. The game remained available for free on itch.io. In 2023, after _Cakefoot_ was successfully pitched as a web game to [Coolmathgames](https://coolmathgames.com), diskmem revived the project and added new features, including introductory levels, coins, automatic saves, arcade mode, and unlockables.
An arcade mode was added when _Cakefoot_ was exhibited at [Derpy Con](https://derpycon.com) in 2023 as part of the Barnyardia pop-up arcade by diskmem and [snakesandrews](https://twitter.com/snakesandrews). Arcade mode gives the player limited time and records the farthest distance reached before time runs out. High scores are saved and displayed in the game. After the convention, the arcade cabinet returned with _Cakefoot_ to its owners at [Wonderville](https://wonderville.nyc) in Brooklyn, NY, where it is still available to play.
Arcade mode was added when _Cakefoot_ was exhibited at [Derpy Con](https://derpycon.com) in 2023 as part of the Barnyardia pop-up arcade by diskmem and [snakesandrews](https://twitter.com/snakesandrews). Arcade mode gives the player limited time and records the farthest distance reached before time runs out. High scores are saved and displayed in the game. After the convention, the arcade cabinet returned with _Cakefoot_ to its owners at [Wonderville](https://wonderville.nyc) in Brooklyn, NY, where it is still available to play.
A custom game engine called [SPACE🪐BOX](https://open.shampoo.ooo/shampoo/spacebox) for exporting games to multiple platforms was developed alongside the game. The engine was used to add support for PC, Mac, Linux, Raspberry Pi, and web browsers. _Cakefoot_ currently is available to play online at [💫dank.game💫](https://dank.game), a platform created for _Cakefoot_ and other upcoming games built with the engine.
Review Builds
=============
Free keys are available for the Steam version, and free downloads are available for PC, MacOS, and Linux. Send me a message at cocktail.frank at dank.game!
A custom game engine called [SPACE🪐BOX](https://open.shampoo.ooo/shampoo/spacebox) is being developed alongside the game. One of the main features of the engine is it supports exporting to many platforms, including PC, Mac, Linux, Raspberry Pi, and web browsers. _Cakefoot_ is playable at [💫dank.game💫](https://dank.game), a platform created for _Cakefoot_ and other upcoming games built with the engine.
Logo
====
@ -178,7 +178,7 @@ Permission is granted to stream the contents of _Cakefoot_ for any commercial or
About dank.game
===============
💫dank.game💫 is a portal for cross-platform ad-supported games primarily created for web browsers. The first game on the portal is _Cakefoot_, designed by dank.game and released in 2024. Further game development is supported by advertising, offering platform-specific builds, and subscription tiers, starting at free.
💫dank.game💫 is a portal for cross-platform ad-supported games primarily created for web browsers. The first game on the portal is _Cakefoot_, designed by dank.game and released in 2024. Further game development is supported by advertising, selling platform-specific builds, and offering subscription tiers, starting at 🆓
Contact
=======

View File

@ -20,11 +20,12 @@
"clock hud large scale": [0.65, 0.2],
"clock hud large translation": [0.0, 0.5],
"clock hud foreground": [255.0, 255.0, 255.0, 255.0],
"clock hud background": [0.0, 0.0, 0.0, 60.0],
"clock hud background": [30.0, 30.0, 30.0, 180.0],
"level hud scale": [0.11, 0.04],
"level hud translation": [-1.64, 0.92],
"level hud foreground": [255.0, 255.0, 255.0, 255.0],
"level hud background": [0.0, 0.0, 0.0, 60.0],
"level hud visible": false,
"hitbox": false,
"use play button": false,
"arcade only": false,
@ -97,9 +98,21 @@
"auto save scale": [0.325, 0.15],
"social media click": false,
"highlight saturation": 1.0,
"highlight value": 0.5
"highlight value": 0.5,
"loot offset": [0.0, 0.1]
},
"coin ui":
{
"scale": 0.024691358,
"translation": [-1.7, 0.92],
"spacing": 0.029,
"visible": true,
"nub": 0.01,
"collected text": "+",
"uncollected text": "-"
},
"shader":
{
"vertex": "src/shaders/gl/shader.vert",
@ -120,7 +133,7 @@
"video directory": "local/video",
"enabled": false,
"write mp4": true,
"video frame length": 0.016666666,
"video frame length": 0.033333,
"max video memory": 2000,
"mp4 pixel format": "yuv420p"
},
@ -192,10 +205,11 @@
"texture":
{
"coin": ["resource/coin/coin-0.png"],
"coin": "resource/coin/coin-0.png",
"flame": ["resource/flame/flame-1.png", "resource/flame/flame-2.png"],
"auto save": "resource/Autosave.png",
"demo message": "resource/Demo_message.png"
"demo message": "resource/Demo_message.png",
"coin missing": "resource/coin_missing.png"
},
"curve":

2
lib/sb

@ -1 +1 @@
Subproject commit a9665c7e3799636940c21c97d2e5ade7fcce6e5e
Subproject commit 0aafdb1ff0f942e0cc484d94611c9ae32f0075c6

BIN
resource/coin_missing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -1,8 +1,12 @@
/* _ _
* 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 |
*/
/*~~~~.
|\~~~~: [ C A K E F O O T ] presented by dank.game
|\\~~~|
|#\\~~: source code and license at https://open.shampoo.ooo/shampoo/cakefoot
\\#\\~|
\\#\\: created using the SPACE🪐BOX engine https://open.shampoo.ooo/shampoo/spacebox
\\#\'
\\#|
`-' */
#if defined(__ANDROID__) || defined(ANDROID)
#include <android/asset_manager_jni.h>
@ -44,7 +48,6 @@ Cakefoot::Cakefoot(std::initializer_list<std::string> configuration_merge) : Gam
{"quest checkpoint", 0.0f},
{"quest difficulty", 0},
{"quest time", 0.0f},
{"quest coin", false},
{"quest bank", 0},
{"quest best", 0.0},
{"arcade level", 1},
@ -52,7 +55,6 @@ Cakefoot::Cakefoot(std::initializer_list<std::string> configuration_merge) : Gam
{"arcade difficulty", 0},
{"arcade max distance", 0},
{"arcade time", 0.0f},
{"arcade coin", false},
{"arcade bank", 0},
{"jackpot", 0}
};
@ -215,6 +217,9 @@ Cakefoot::Cakefoot(std::initializer_list<std::string> configuration_merge) : Gam
set_up_buttons();
}
/* Bank HUD */
populate_bank_ui();
/* Switch sounds on by default */
button.at("volume").press();
button.at("bgm").press();
@ -492,7 +497,7 @@ void Cakefoot::set_up_buttons()
configuration()["progress"]["quest checkpoint"] = 0.0f;
configuration()["progress"]["quest difficulty"] = profile_index;
configuration()["progress"]["quest time"] = 0.0f;
configuration()["progress"]["quest bank"] = 0;
configuration()["progress"]["quest bank"] = bank_serialized(bank_init());
challenge_index = 0;
configuration()["progress"]["current challenge"] = challenge_index;
}
@ -503,7 +508,7 @@ void Cakefoot::set_up_buttons()
configuration()["progress"]["arcade difficulty"] = profile_index;
configuration()["progress"]["arcade max distance"] = 0;
configuration()["progress"]["arcade time"] = 0.0f;
configuration()["progress"]["arcade bank"] = 0;
configuration()["progress"]["arcade bank"] = bank_serialized(bank_init());
challenge_index = 3;
configuration()["progress"]["current challenge"] = challenge_index;
}
@ -1179,6 +1184,39 @@ void Cakefoot::load_vbo()
sb::Plane::uv->bind("vertex_uv", shader_program);
}
void Cakefoot::populate_bank_ui()
{
sb::Texture missing_texture {configuration()("texture", "coin missing").get<std::string>()};
missing_texture.filter(GL_LINEAR);
missing_texture.load();
sb::Texture collected_texture {configuration()("texture", "coin").get<std::string>()};
collected_texture.filter(GL_LINEAR);
collected_texture.load();
bank_ui.clear();
for (int ii = 0; static_cast<std::size_t>(ii) < bank_init().size(); ii++)
{
bool collected;
if (configuration()("challenge", challenge_index, "name") != "LEVEL SELECT")
{
collected = bank()[ii];
}
else
{
collected = ii + 1 == level_index && coin_collected;
}
const nlohmann::json& coin_ui = configuration()("coin ui");
bank_ui.emplace_back(collected ? collected_texture : missing_texture, coin_ui.at("scale"));
bank_ui.back().translate(
coin_ui.at("translation").get<glm::vec3>() +
glm::vec3{
ii * coin_ui.at("spacing").get<float>(),
ii + 1 == level_index ? coin_ui.at("nub").get<float>() : 0.0f,
0.0f
});
}
}
const Curve& Cakefoot::curve() const
{
return curves[curve_index % curves.size()];
@ -1231,6 +1269,9 @@ void Cakefoot::load_level(int index)
character.beginning(curve());
coin_collected = false;
/* Populate bank HUD so the proper coin will indicate the current level */
populate_bank_ui();
/* The wrap space of the field is necessary for flame enemy objects */
sb::Box field {-curve().aspect, -1.0f, 2.0f * curve().aspect, 2.0f};
@ -1388,15 +1429,14 @@ void Cakefoot::load_level(int index)
if (end_screen())
{
/* Load ending coins */
std::string coin_texture;
ending_coins.clear();
glm::vec2 coin_range = configuration()("ending", "coin range").get<glm::vec2>();
float coin_step = (coin_range.y - coin_range.x) / (bank() - 1);
for (std::size_t ii = 0; ii < bank(); ii++)
float coin_step = (coin_range.y - coin_range.x) / (bank_count() - 1);
for (std::size_t ii = 0; ii < bank_count(); 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()
0.0f, 0.0f, -1.0f, bank_count() < max_bank()
};
ending_coins.push_back(coin);
}
@ -1415,7 +1455,7 @@ void Cakefoot::load_level(int index)
}
/* Unlocks for getting all coins */
if (bank() >= max_bank())
if (bank_count() >= max_bank())
{
if (configuration()("progress", "max view").get<int>() < 1)
{
@ -1456,7 +1496,6 @@ void Cakefoot::load_level(int index)
configuration()["progress"]["arcade checkpoint"] = 0.0f;
configuration()["progress"]["arcade max distance"] = 0;
configuration()["progress"]["arcade time"] = 0.0f;
configuration()["progress"]["arcade bank"] = 0;
challenge_index = 4;
configuration()["progress"]["current challenge"] = challenge_index;
}
@ -1465,7 +1504,6 @@ void Cakefoot::load_level(int index)
configuration()["progress"]["quest level"] = 1;
configuration()["progress"]["quest checkpoint"] = 0.0f;
configuration()["progress"]["quest time"] = 0.0f;
configuration()["progress"]["quest bank"] = 0;
challenge_index = 1;
configuration()["progress"]["current challenge"] = challenge_index;
}
@ -1535,7 +1573,7 @@ void Cakefoot::load_level(int index)
if (configuration()("progress", "arcade coin"))
{
for (auto& enemy : enemies) enemy->take_coin();
collect_coin(false);
collect_coin();
}
}
else
@ -1555,10 +1593,10 @@ void Cakefoot::load_level(int index)
run_timer.elapsed(configuration()("progress", "quest time").get<float>());
character.checkpoint(configuration()("progress", "quest checkpoint").get<float>());
character.spawn(curve());
if (configuration()("progress", "quest coin"))
if (bank()[index])
{
for (auto& enemy : enemies) enemy->take_coin();
collect_coin(false);
collect_coin();
}
}
else
@ -1566,7 +1604,6 @@ void Cakefoot::load_level(int index)
configuration()["progress"]["quest level"] = index;
configuration()["progress"]["quest checkpoint"] = 0;
configuration()["progress"]["quest difficulty"] = profile_index;
configuration()["progress"]["quest coin"] = false;
}
}
@ -1796,8 +1833,7 @@ float Cakefoot::limit() const
} } }
/* Add bank bonus */
limit += configuration()("progress", "arcade bank").get<int>() * configuration()(
"challenge", challenge_index, "bank bonus").get<float>();
limit += bank_count() * configuration()("challenge", challenge_index, "bank bonus").get<float>();
}
return limit;
@ -1835,10 +1871,75 @@ bool Cakefoot::resuming() const
configuration()("challenge", challenge_index, "name") == "RESUME ARCADE";
}
std::size_t Cakefoot::bank() const
std::vector<bool> Cakefoot::bank() const
{
std::string mode = quest() ? "quest bank" : "arcade bank";
return configuration()("progress", mode).get<std::size_t>();
std::vector<bool> bank;
sb::Log::log("Bank serialized to " + bank_serialized(), sb::Log::DEBUG);
for (const char level : bank_serialized())
{
bank.push_back(level == configuration()("coin ui", "collected text").get<std::string>()[0] ? true : false);
}
std::ostringstream message;
message << bank;
sb::Log::log("Bank parsed as " + message.str(), sb::Log::DEBUG);
return bank;
}
std::string Cakefoot::bank_serialized(const std::vector<bool>& bank) const
{
if (bank.empty())
{
nlohmann::json bank = configuration()("progress", quest() ? "quest bank" : "arcade bank");
/* For backward compatibility, support the bank being saved as a number and convert it so that the first N characters
* of the string representation indicate a collected coin. Although this won't be correct in terms of which levels
* were collected, the count will be accurate. Replace the older number format with the newer string format in the progress
* when done. */
if (bank.is_number())
{
std::string bank_converted = "";
for (std::size_t ii = 0; ii < bank_init().size(); ii++)
{
bank_converted += configuration()("coin ui", ii < bank ? "collected text" : "uncollected text");
}
bank = bank_converted;
return bank_converted;
}
else
{
/* Get the already serialized data directly from the save file */
return bank.get<std::string>();
}
}
else
{
/* Serialize the input */
std::string bank_serialized;
for (const bool level : bank)
{
bank_serialized += configuration()("coin ui", (level ? "collected text" : "uncollected text"));
}
return bank_serialized;
}
}
std::vector<bool> Cakefoot::bank_init() const
{
std::vector<bool> bank;
for (std::size_t ii = 0; ii < max_bank(); ii++)
{
bank.push_back(false);
}
return bank;
}
std::size_t Cakefoot::bank_count() const
{
std::size_t count = 0;
for (const bool level : bank())
{
count += level;
}
return count;
}
std::size_t Cakefoot::max_bank() const
@ -1846,7 +1947,7 @@ std::size_t Cakefoot::max_bank() const
return configuration()("levels").size() - 2;
}
void Cakefoot::collect_coin(bool add_to_bank)
void Cakefoot::collect_coin()
{
if (!coin_collected)
{
@ -1856,21 +1957,16 @@ void Cakefoot::collect_coin(bool add_to_bank)
{
enemy->collect_coin();
coin_collected = true;
if (add_to_bank)
{
if (arcade())
{
configuration()["progress"]["arcade bank"].get_ref<nlohmann::json::number_integer_t&>()++;
configuration()["progress"]["arcade coin"] = true;
write_progress();
}
else if (quest())
{
configuration()["progress"]["quest bank"].get_ref<nlohmann::json::number_integer_t&>()++;
configuration()["progress"]["quest coin"] = true;
write_progress();
} } } } }
}
/* Get the bank out of the save file, convert to bool array, update the array, and re-serialize the bank
* in the save file. */
std::vector<bool> current_bank = bank();
current_bank[level_index - 1] = true;
configuration()["progress"][quest() ? "quest bank" : "arcade bank"] = bank_serialized(current_bank);
/* Update the HUD */
populate_bank_ui();
} } } }
void Cakefoot::end_game_over_display()
{
@ -2897,10 +2993,18 @@ void Cakefoot::update(float timestamp)
{
enemy_collision = true;
}
else if (enemy->collide_coin(character.box(), clip_upper, clip_lower))
else if (enemy->collide_coin(character.box(), clip_upper, clip_lower) && !character.resting())
{
audio.at("take").play();
enemy->take_coin();
}
/* Update coin position if the character is currently holding it */
if (!coin_collected && enemy->coin_taken())
{
glm::vec2 location = character.box().center() +
configuration()("display", "loot offset").get<glm::vec2>();
enemy->coin_translation(curve().wrap({location.x, location.y, 0.0f}));
} }
/* Collide with ending screen coins */
@ -3170,19 +3274,31 @@ void Cakefoot::update(float timestamp)
label.at("clock").enable();
glDrawArrays(GL_TRIANGLES, 0, label.at("clock").attributes("position")->count());
/* Draw the level indicator */
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;
label.at("level").content(level_indicator.str());
label.at("level").refresh();
label.at("level").texture(0).bind();
label_transformation = projection * view * label.at("level").transformation();
glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &label_transformation[0][0]);
label.at("level").enable();
glDrawArrays(GL_TRIANGLES, 0, label.at("level").attributes("position")->count());
/* Draw the original text level indicator */
if (configuration()("display", "level hud visible"))
{
std::stringstream level_indicator;
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();
label_transformation = projection * view * label.at("level").transformation();
glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &label_transformation[0][0]);
label.at("level").enable();
glDrawArrays(GL_TRIANGLES, 0, label.at("level").attributes("position")->count());
}
/* Draw the bank HUD */
if (configuration()("coin ui", "visible"))
{
for (const sb::Sprite& coin : bank_ui)
{
coin.draw(uniform.at("mvp"), view * rotation_matrix, projection, uniform.at("texture enabled"));
}
}
}
/* Draw game over text */

View File

@ -1,8 +1,12 @@
/* _ _
* 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
*/
/*~~~~.
|\~~~~: [ C A K E F O O T ] presented by dank.game
|\\~~~|
|#\\~~: source code and license at https://open.shampoo.ooo/shampoo/cakefoot
\\#\\~|
\\#\\: created using the SPACE🪐BOX engine https://open.shampoo.ooo/shampoo/spacebox
\\#\'
\\#|
`-' */
#pragma once
@ -265,7 +269,7 @@ private:
{"idle warning", sb::Text(font())}
};
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};
coin {configuration()("texture", "coin").get<std::string>(), 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;
@ -280,17 +284,20 @@ private:
ArcadeScores::Score arcade_score;
std::string name_entry;
sb::Text scoreboard {fonts.at("large")}, thanks {fonts.at("medium")};
std::vector<Flame> ending_coins;
sb::Color rotating_hue;
std::vector<Flame> ending_coins;
std::vector<sb::Text> ending_messages;
std::optional<std::string> selected;
std::shared_ptr<SDL_GameController> controller = nullptr;
std::optional<float> pre_ad_volume = std::nullopt;
std::vector<Splash> splash;
/* Vector of coin sprite objects to be drawn to the screen simultaneously */
std::vector<sb::Sprite> bank_ui;
/*!
* Load sound effects and music into objects that can be used by the SDL mixer library. Use chunk objects for background music instead
* of music objects so background music tracks can fade into each other.
* Load sound effects and music into objects that can be used by the SDL mixer library. Use chunk objects for background music
* instead of music objects so background music tracks can fade into each other.
*/
void load_audio();
@ -327,6 +334,11 @@ private:
*/
void load_vbo();
/*!
* Build a vector of coin sprites to display the player's bank in the UI.
*/
void populate_bank_ui();
/*!
* @return The current level's curve
*/
@ -347,16 +359,17 @@ private:
/*!
* Save the JSON in the `progress` field of Game::configuration to storage at Cakefoot::progress_file_path.
*
* For PC builds, the folder `storage/` will be created in the current working directory if it doesn't exist already. The user must have
* permission to create folders in the directory. The file `cakefoot_progress.json` will also be created if necessary.
* For PC builds, the folder `storage/` will be created in the current working directory if it doesn't exist already. The user
* must have permission to create folders in the directory. The file `cakefoot_progress.json` will also be created if necessary.
*
* For web builds, Emscripten is used to abstract this function so that writing to a browser's Indexed DB is done automatically. The folder
* `storage/` must have been mounted already using Emscripten's FS module.
* For web builds, Emscripten is used to abstract this function so that writing to a browser's Indexed DB is done automatically.
* The folder `storage/` must have been mounted already using Emscripten's FS module.
*/
void write_progress() const;
/*!
* Use the same procedure as Cakefoot::write_progress() to write arcade scores to a JSON file in `storage/cakefoot_arcade_scores.json`.
* Use the same procedure as Cakefoot::write_progress() to write arcade scores to a JSON file in
* `storage/cakefoot_arcade_scores.json`.
*
* @see Cakefoot::write_progress()
*/
@ -405,15 +418,40 @@ private:
bool resuming() const;
/*!
* @return Count of coins collected
* The player's coins represented as a vector. Each entry corresponds with a level, excluding the title screen and ending screen.
* The entry is true if the player collected the coin for the level, or false otherwise.
*
* @return The player's coin status as a vector
*/
std::size_t bank() const;
std::vector<bool> bank() const;
/*!
* Get the bank serialized as a string. If no bank is provided, read the serialized bank from the progress file.
*
* Each character in the string represents a level, excluding the title screen and ending screen. A "+" means the coin was
* collected, and a "-" means it is uncollected.
*
* @return The player's coin status serialized as a string
*/
std::string bank_serialized(const std::vector<bool>& bank = {}) const;
/*!
* @return An initialized (empty) bank vector with every entry false, one entry per game level excluding title and end screens
*/
std::vector<bool> bank_init() const;
/*!
* @return Count of number of coins the player has collected
*/
std::size_t bank_count() const;
/*!
* @return Maximum number of coins that can be collected
*/
std::size_t max_bank() const;
/* bank ui */
inline bool skip_resume_quest()
{
return configuration()("challenge", challenge_index, "name") == "RESUME QUEST" &&
@ -432,12 +470,12 @@ 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
* Remove coin from the enemy, the level, and the bankadd the coin to the
* appropriate bank.
*
* @param add_to_bank flag to either add to the bank or not
*/
void collect_coin(bool add_to_bank = true);
void collect_coin();
/*!
* Move the level index to the end level, causing the game over state to end.

View File

@ -52,11 +52,19 @@ void Enemy::collect_coin()
}
}
void Enemy::coin_translation(const glm::vec3& translation)
{
if (_coin.has_value())
{
_coin->translate({translation.x, translation.y, 0.0f});
}
}
void Enemy::draw(GLuint transformation_uniform, const glm::mat4& view, const glm::mat4& projection, GLuint texture_flag_uniform,
const sb::Color& rotating_hue, GLuint color_addition_uniform)
{
sprite.draw(transformation_uniform, view, projection, texture_flag_uniform);
if (_coin.has_value() && !coin_taken() && !coin_collected)
if (_coin.has_value() && !coin_collected)
{
glUniform4fv(color_addition_uniform, 1, &rotating_hue.normal()[0]);
_coin->draw(transformation_uniform, view, projection, texture_flag_uniform);
@ -217,7 +225,8 @@ void Fish::update(const sb::Timer& timer)
{
glm::vec3 coin_position;
/* Place coin either along the fish's circle or translated a vector away from the fish, depending on whether or not the radius is set. */
/* Place coin either along the fish's circle or translated a vector away from the fish, depending on whether or not the radius is
* set. */
if (coin_radius == 0.0f)
{
coin_position = center() + glm::vec3{sb::angle_to_vector(angle + coin_angle, radius), 0.0f};
@ -305,6 +314,7 @@ void Projectile::draw(GLuint transformation_uniform, const glm::mat4& view, cons
{
glUniform4fv(color_addition_uniform, 1, &rotating_hue.normal()[0]);
_coin->draw(transformation_uniform, view, projection, texture_flag_uniform);
glUniform4fv(color_addition_uniform, 1, &glm::vec4(0)[0]);
}
}
else
@ -442,7 +452,7 @@ void Projector::release()
/* Keep a count of how many projectiles have been fired */
count++;
/* Temporarily hard code the wrapping until figuring out how to reference the current curve in this function. */
/* Temporarily hard code the wrapping since the current curve is not currently accessible from this function. */
glm::vec3 point = sb::wrap_point(character.position, {-1.77777f, -1.0f, 0.0f}, {1.77777f, 1.0f, 1.0f});
projectiles.emplace_back(position, point, speed);
@ -462,6 +472,13 @@ void Projector::draw(GLuint transformation_uniform, const glm::mat4& view, const
{
projectile.draw(transformation_uniform, view, projection, texture_flag_uniform, rotating_hue, color_addition_uniform);
}
/* Draw the coin separately if it has been taken and isn't collected yet */
if (_coin.has_value() && coin_taken() && !coin_collected)
{
glUniform4fv(color_addition_uniform, 1, &rotating_hue.normal()[0]);
_coin->draw(transformation_uniform, view, projection, texture_flag_uniform);
}
}
Flame::Flame(const sb::Box& field, const glm::vec3& position, float speed, float angle, float mirror_interval, bool camo) :
@ -518,16 +535,19 @@ void Flame::update(const sb::Timer& timer)
if (_coin.has_value() && !coin_taken() && !coin_collected)
{
glm::vec3 coin_position = position + glm::vec3{sb::angle_to_vector(coin_angle, coin_radius), 0.0f};
_coin->translate(sb::wrap_point(coin_position, glm::vec3{field.left(), field.bottom(), -1.0f}, glm::vec3{field.right(), field.top(), 1.0f}));
_coin->translate(
sb::wrap_point(coin_position, glm::vec3{field.left(), field.bottom(), -1.0f}, glm::vec3{field.right(), field.top(), 1.0f}));
}
box.center(position);
sprite.translate(sb::wrap_point(position, glm::vec3{field.left(), field.bottom(), -1.0f}, glm::vec3{field.right(), field.top(), 1.0f}));
sprite.translate(
sb::wrap_point(position, glm::vec3{field.left(), field.bottom(), -1.0f}, glm::vec3{field.right(), field.top(), 1.0f}));
}
void Flame::mirror()
{
/* Reset the flame position and angle at the end of a cycle so overflow in time and movement don't begin to accumulate and desync the mirror cycle. */
/* Reset the flame position and angle at the end of a cycle so overflow in time and movement don't begin to accumulate and desync
* the mirror cycle. */
if (mirrored)
{
angle = initial_angle;

View File

@ -52,6 +52,13 @@ public:
*/
virtual void collect_coin();
/*!
* Set the position of the enemy's coin. If the enemy object has no coin, do nothing.
*
* @param translation coin location
*/
void coin_translation(const glm::vec3& translation);
/*!
* @param timer timer that has been updating every unpaused frame to be used to measure distance to move this frame
*/

View File

@ -1,12 +1,18 @@
/* _ _
* / `-' `-^~~-, | cakefoot presented by dank.game
* \__ _ _< |
* `v ) `~_/ | source code and license at https://open.shampoo.ooo/shampoo/cakefoot
*
* @file pre_js_collect.js
*
* Addition to Emscripten's Module object to be included in WASM builds that will collect user play data.
*/
/*~~~~.
|\~~~~: [ C A K E F O O T ] presented by dank.game
|\\~~~|
|#\\~~: source code and license at https://open.shampoo.ooo/shampoo/cakefoot
\\#\\~|
\\#\\: created using the SPACE🪐BOX engine https://open.shampoo.ooo/shampoo/spacebox
\\#\'
\\#|
`-'
@file pre_js_collect.js
Extend Emscripten's Module object to collect user play data in WASM builds.
*/
function collectData()
{

View File

@ -1,6 +1,14 @@
#version 150
/* https://open.shampoo.ooo/shampoo/cakefoot */
/*~~~~.
|\~~~~: [ C A K E F O O T ] presented by dank.game
|\\~~~|
|#\\~~: source code and license at https://open.shampoo.ooo/shampoo/cakefoot
\\#\\~|
\\#\\: created using the SPACE🪐BOX engine https://open.shampoo.ooo/shampoo/spacebox
\\#\'
\\#|
`-' */
/* The precision declaration is required by OpenGL ES */
precision mediump float;

View File

@ -1,6 +1,14 @@
#version 150
/* https://open.shampoo.ooo/shampoo/cakefoot */
/*~~~~.
|\~~~~: [ C A K E F O O T ] presented by dank.game
|\\~~~|
|#\\~~: source code and license at https://open.shampoo.ooo/shampoo/cakefoot
\\#\\~|
\\#\\: created using the SPACE🪐BOX engine https://open.shampoo.ooo/shampoo/spacebox
\\#\'
\\#|
`-' */
/* The precision declaration is required by OpenGL ES */
precision mediump float;

View File

@ -1,6 +1,14 @@
#version 300 es
/* https://open.shampoo.ooo/shampoo/cakefoot */
/*~~~~.
|\~~~~: [ C A K E F O O T ] presented by dank.game
|\\~~~|
|#\\~~: source code and license at https://open.shampoo.ooo/shampoo/cakefoot
\\#\\~|
\\#\\: created using the SPACE🪐BOX engine https://open.shampoo.ooo/shampoo/spacebox
\\#\'
\\#|
`-' */
/* The precision declaration is required by OpenGL ES */
precision mediump float;

View File

@ -1,6 +1,14 @@
#version 300 es
/* https://open.shampoo.ooo/shampoo/cakefoot */
/*~~~~.
|\~~~~: [ C A K E F O O T ] presented by dank.game
|\\~~~|
|#\\~~: source code and license at https://open.shampoo.ooo/shampoo/cakefoot
\\#\\~|
\\#\\: created using the SPACE🪐BOX engine https://open.shampoo.ooo/shampoo/spacebox
\\#\'
\\#|
`-' */
/* The precision declaration is required by OpenGL ES */
precision mediump float;