diff --git a/MODDING.md b/MODDING.md index e421f87..7128380 100644 --- a/MODDING.md +++ b/MODDING.md @@ -56,7 +56,7 @@ The file path of the area's texture (background). Gravity the area has; applies to items' velocities per tick. #### Friction (float) Friction of the area; applies to items' velocities when grounded or hitting walls. -### AirResistance (float) +#### AirResistance (float) Air resistance of the area; applies to items' velocities when airborne. ## character.xml @@ -416,6 +416,10 @@ The additional eat speed multiplier the item will give if eaten. The item's gravity; will use the default gravity if not available. #### ChewCount (int; optional) An item's custom chew count. +#### UpgradeID (string; optional) +The name of another item that this item will be able to be upgraded to. +#### UpgradeCount (int; optional) +The amount of this item it will take to upgrade to the upgrade item specified in UpgradeID. #### IsPlayReward (bool; optional) The item will be given out when the reward is hit in play (see play.xml) #### IsToggleSpritesheet (bool; optional) @@ -451,6 +455,10 @@ The sound that will play, based on SoundRootPath. Will play when clicking on a widget in a menu. #### Sound (path) The sound that will play, based on SoundRootPath. +### CheatsActivated +Sound that will play after entering a special code to activate cheats. +#### Sound (path) +The sound that will play, based on SoundRootPath. ## play.xml Determines behavior and appearance of the "Play" minigame. diff --git a/src/resource/xml/item.cpp b/src/resource/xml/item.cpp index 509d5de..51f393e 100644 --- a/src/resource/xml/item.cpp +++ b/src/resource/xml/item.cpp @@ -87,6 +87,22 @@ namespace game::resource::xml query_sound_entry_collection(element, "Dispose", archive, soundRootPath, sounds.dispose); query_sound_entry_collection(element, "Return", archive, soundRootPath, sounds.return_); query_sound_entry_collection(element, "Summon", archive, soundRootPath, sounds.summon); + query_sound_entry_collection(element, "Upgrade", archive, soundRootPath, sounds.upgrade); + query_sound_entry_collection(element, "UpgradeFail", archive, soundRootPath, sounds.upgradeFail); + } + + if (auto element = root->FirstChildElement("Items")) + { + int id{}; + + for (auto child = element->FirstChildElement("Item"); child; child = child->NextSiblingElement("Item")) + { + std::string name{}; + query_string_attribute(child, "Name", &name); + stringToIDMap[name] = id; + idToStringMap[id] = name; + id++; + } } if (auto element = root->FirstChildElement("Items")) @@ -108,7 +124,24 @@ namespace game::resource::xml query_float_optional_attribute(child, "DigestionBonus", item.digestionBonus); query_float_optional_attribute(child, "EatSpeedBonus", item.eatSpeedBonus); query_float_optional_attribute(child, "Gravity", item.gravity); + query_int_optional_attribute(child, "ChewCount", item.chewCount); + + if (child->FindAttribute("UpgradeID")) + { + std::string upgradeIDString{}; + query_string_attribute(child, "UpgradeID", &upgradeIDString); + + if (!upgradeIDString.empty() && stringToIDMap.contains(upgradeIDString)) + item.upgradeID = stringToIDMap[upgradeIDString]; + else if (upgradeIDString.empty()) + logger.warning(std::format("Empty UpgradeID ({})", item.name)); + else + logger.warning(std::format("Could not find item ID for UpgradeID: {} ({})", upgradeIDString, item.name)); + + query_int_optional_attribute(child, "UpgradeCount", item.upgradeCount); + } + query_bool_attribute(child, "IsPlayReward", &item.isPlayReward); query_bool_attribute(child, "IsToggleSpritesheet", &item.isToggleSpritesheet); diff --git a/src/resource/xml/item.hpp b/src/resource/xml/item.hpp index 1f8d752..a9168da 100644 --- a/src/resource/xml/item.hpp +++ b/src/resource/xml/item.hpp @@ -42,6 +42,8 @@ namespace game::resource::xml std::string description{UNDEFINED}; int categoryID{}; int rarityID{}; + std::optional upgradeCount{}; + std::optional upgradeID{}; std::optional flavorID; std::optional calories{}; std::optional eatSpeedBonus{}; @@ -63,11 +65,15 @@ namespace game::resource::xml SoundEntryCollection return_{}; SoundEntryCollection dispose{}; SoundEntryCollection summon{}; + SoundEntryCollection upgrade{}; + SoundEntryCollection upgradeFail{}; }; std::unordered_map categoryMap{}; std::unordered_map rarityMap{}; std::unordered_map flavorMap{}; + std::unordered_map stringToIDMap{}; + std::unordered_map idToStringMap{}; using Pool = std::vector; diff --git a/src/resource/xml/menu.cpp b/src/resource/xml/menu.cpp index 6ecd5c2..daae997 100644 --- a/src/resource/xml/menu.cpp +++ b/src/resource/xml/menu.cpp @@ -36,6 +36,7 @@ namespace game::resource::xml query_sound_entry_collection(element, "Close", archive, soundRootPath, sounds.close); query_sound_entry_collection(element, "Hover", archive, soundRootPath, sounds.hover); query_sound_entry_collection(element, "Select", archive, soundRootPath, sounds.select); + query_sound_entry_collection(element, "CheatsActivated", archive, soundRootPath, sounds.cheatsActivated); } } diff --git a/src/resource/xml/menu.hpp b/src/resource/xml/menu.hpp index 6f4712d..8194ccb 100644 --- a/src/resource/xml/menu.hpp +++ b/src/resource/xml/menu.hpp @@ -15,6 +15,7 @@ namespace game::resource::xml SoundEntryCollection close{}; SoundEntryCollection hover{}; SoundEntryCollection select{}; + SoundEntryCollection cheatsActivated{}; }; Sounds sounds{}; diff --git a/src/state/main.cpp b/src/state/main.cpp index 0615cd0..a56b6bb 100644 --- a/src/state/main.cpp +++ b/src/state/main.cpp @@ -1,5 +1,6 @@ #include "main.hpp" +#include #include #include #include @@ -34,6 +35,8 @@ namespace game::state auto& dialogue = data.dialogue; auto& menuSchema = data.menuSchema; this->characterIndex = selectedCharacterIndex; + konamiCodeIndex = 0; + konamiCodeStartTime = 0.0; character = entity::Character(data, vec2(World::BOUNDS.x + World::BOUNDS.z * 0.5f, World::BOUNDS.w - World::BOUNDS.y)); @@ -132,9 +135,55 @@ namespace game::state void Main::update(Resources& resources) { + static constexpr std::array KONAMI_CODE = { + ImGuiKey_UpArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow, ImGuiKey_DownArrow, ImGuiKey_LeftArrow, + ImGuiKey_RightArrow, ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_B, ImGuiKey_A}; + static constexpr std::array KONAMI_INPUT_KEYS = { + ImGuiKey_UpArrow, ImGuiKey_DownArrow, ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_B, ImGuiKey_A}; + static constexpr auto KONAMI_CODE_INPUT_TIME_SECONDS = 5.0; + auto focus = focus_get(); auto& dialogue = character.data.dialogue; + if (!menu.isCheats) + { + for (auto key : KONAMI_INPUT_KEYS) + { + if (!ImGui::IsKeyPressed(key, false)) continue; + + if (key == KONAMI_CODE[konamiCodeIndex]) + { + konamiCodeIndex++; + konamiCodeStartTime = ImGui::GetTime(); + } + else if (key == KONAMI_CODE[0]) + { + konamiCodeIndex = 1; + konamiCodeStartTime = ImGui::GetTime(); + } + else + { + konamiCodeIndex = 0; + konamiCodeStartTime = 0.0; + } + + if (konamiCodeIndex >= (int)KONAMI_CODE.size()) + { + menu.isCheats = true; + konamiCodeIndex = 0; + konamiCodeStartTime = 0.0; + toasts.push("Cheats unlocked!"); + character.data.menuSchema.sounds.cheatsActivated.play(); + } + } + + if (konamiCodeIndex > 0 && (ImGui::GetTime() - konamiCodeStartTime > KONAMI_CODE_INPUT_TIME_SECONDS)) + { + konamiCodeIndex = 0; + konamiCodeStartTime = 0.0; + } + } + if (isWindows) { menu.update(resources, itemManager, character, cursor, text, worldCanvas); diff --git a/src/state/main.hpp b/src/state/main.hpp index 3de806c..cadf6ef 100644 --- a/src/state/main.hpp +++ b/src/state/main.hpp @@ -42,6 +42,8 @@ namespace game::state int areaIndex{}; float autosaveTime{}; + int konamiCodeIndex{}; + double konamiCodeStartTime{}; bool isWindows{true}; diff --git a/src/state/main/inventory.cpp b/src/state/main/inventory.cpp index 01f6ed9..35189b3 100644 --- a/src/state/main/inventory.cpp +++ b/src/state/main/inventory.cpp @@ -76,6 +76,9 @@ namespace game::state::main auto canvasSize = ivec2(std::max(1.0f, previewSize.x), std::max(1.0f, previewSize.y)); if (!canvases.contains(i)) canvases.emplace((int)i, Canvas(canvasSize, Canvas::FLIP)); auto& canvas = canvases[i]; + bool isPossibleToUpgrade = item.upgradeID.has_value() && item.upgradeCount.has_value() && + schema.idToStringMap.contains(*item.upgradeID); + bool isAbleToUpgrade = isPossibleToUpgrade && quantity >= *item.upgradeCount; canvas.zoom = math::to_percent(previewScale); canvas.pan = vec2(rect.x, rect.y); canvas.bind(); @@ -90,7 +93,30 @@ namespace game::state::main quantity <= 0 ? ImVec4(0, 0, 0, 1) : ImVec4(1, 1, 1, 1))) && quantity > 0) { - if (category.isEdible) + if (ImGui::IsKeyDown(ImGuiMod_Shift)) + { + if (isAbleToUpgrade) + { + if (ImGui::IsKeyDown(ImGuiMod_Ctrl)) + { + while (quantity >= *item.upgradeCount) + { + values.at(*item.upgradeID)++; + quantity -= *item.upgradeCount; + } + } + else + { + values.at(*item.upgradeID)++; + quantity -= *item.upgradeCount; + } + + schema.sounds.upgrade.play(); + } + else + schema.sounds.upgradeFail.play(); + } + else if (category.isEdible) { if (itemManager.items.size() + 1 >= ItemManager::LIMIT) character.data.itemSchema.sounds.dispose.play(); @@ -143,6 +169,14 @@ namespace game::state::main else if (eatSpeedBonus < 0) ImGui::Text("Eat Speed Penalty: %0.2f%% / sec", *eatSpeedBonus); } + + if (isPossibleToUpgrade) + { + ImGui::Text("Upgrade: %ix -> %s", *item.upgradeCount, schema.idToStringMap.at(*item.upgradeID).c_str()); + if (isAbleToUpgrade) + ImGui::TextUnformatted("(Shift + Click -> Upgrade)\n(Shift + Ctrl + Click -> Upgrade All)"); + } + ImGui::PopStyleColor(); ImGui::Separator(); diff --git a/src/state/main/menu.hpp b/src/state/main/menu.hpp index 2766b80..e09222b 100644 --- a/src/state/main/menu.hpp +++ b/src/state/main/menu.hpp @@ -28,13 +28,13 @@ namespace game::state::main state::Configuration configuration; #if DEBUG - bool isCheats{true}; + bool isCheats{}; bool isDebug{true}; #else bool isCheats{}; bool isDebug{}; #endif - + bool isOpen{true}; bool isChat{true}; util::imgui::WindowSlide slide{}; diff --git a/src/state/main/world.cpp b/src/state/main/world.cpp index 4ee0158..4e8c01e 100644 --- a/src/state/main/world.cpp +++ b/src/state/main/world.cpp @@ -22,13 +22,14 @@ namespace game::state::main auto& pan = canvas.pan; auto& zoom = canvas.zoom; auto& io = ImGui::GetIO(); - bool isPan{true}; auto isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle); + auto isMouseLeftDown = ImGui::IsMouseDown(ImGuiMouseButton_Left); + auto isCtrlDown = ImGui::IsKeyDown(ImGuiMod_Ctrl); auto panMultiplier = ZOOM_BASE / zoom; if (!ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) && !ImGui::IsAnyItemActive()) { - if ((isMouseMiddleDown) && isPan) + if ((isMouseMiddleDown) || (isMouseLeftDown && isCtrlDown)) { cursor.queue_play({cursorSchema.animations.pan.get()}); pan -= imgui::to_vec2(io.MouseDelta) * panMultiplier;