lfs, current build

This commit is contained in:
shweet committed 2026-08-08 15:20:37 -04:00
1 parent 2efed2a443
commit a5904b917b
397 files changed
+215918 -2458

No files matched your search

+91 -17
View File
@@ -4,6 +4,7 @@
#include <charconv>
#include <format>
#include <string_view>
#include <system_error>
#include <utility>
#include <imgui.h>
@@ -24,14 +25,16 @@ namespace game::developer
X(DIGEST, "digest", "digest", true) \
X(DIGESTION, "digestion", "digestion <% per second>", true) \
X(DIALOGUE, "dialogue", "dialogue", true) \
X(EAT_SPEED, "eat_speed", "eat_speed <multiplier>", true) \
X(GIVE, "give", "give <item_id> <quantity>", true) \
X(HELP, "help", "help", false) \
X(ITEMS, "items", "items", true) \
X(MENU, "menu", "menu", true) \
X(NEW_GAME, "new_game", "new_game <archive>", false) \
X(PLAY_ANIMATION, "play_animation", "play_animation <animation>", true) \
X(PLAY_DIALOGUE, "play_dialogue", "play_dialogue <label>", true) \
X(RELOAD, "reload", "reload", false) \
X(SHOW_LAYERS, "show_layers", "show_layers [true/false]", true) \
X(SHOW_NULLS, "show_nulls", "show_nulls [true/false]", true) \
X(SPAWN, "spawn", "spawn <item_id>", true) \
X(STAGE, "stage", "stage <stage>", true) \
X(WEIGHT, "weight", "weight <kilograms>", true)
@@ -65,7 +68,6 @@ namespace game::developer
constexpr auto SCROLL_EPSILON = 1.0f;
constexpr auto INPUT_FLAGS = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackHistory;
constexpr auto ARG_VALUE = 1;
constexpr auto ARG_QUANTITY = ARG_VALUE + 1;
constexpr auto HISTORY_PREVIOUS = -1;
constexpr auto HISTORY_NEXT = 1;
@@ -92,6 +94,19 @@ namespace game::developer
return result;
}
bool toggle_value_get(const std::vector<std::string_view>& args, bool isCurrent, bool& isValid)
{
isValid = true;
if (args.size() <= ARG_VALUE) return !isCurrent;
auto value = args[ARG_VALUE];
if (value == "true") return true;
if (value == "false") return false;
isValid = false;
return isCurrent;
}
std::vector<std::string_view> args_get(const std::string& input)
{
std::vector<std::string_view> result{};
@@ -229,20 +244,6 @@ namespace game::developer
history_push(std::format("Playing dialogue: {}", label));
break;
}
case Command::EAT_SPEED:
{
play->character.eatSpeed = float_parse(args.size() > ARG_VALUE ? args[ARG_VALUE] : std::string_view{});
history_push(std::format("Eat speed set to {}x.", play->character.eatSpeed));
break;
}
case Command::GIVE:
{
auto itemID = int_parse(args.size() > ARG_VALUE ? args[ARG_VALUE] : std::string_view{});
auto quantity = int_parse(args.size() > ARG_QUANTITY ? args[ARG_QUANTITY] : std::string_view{});
play->menu.inventory.values[itemID] += quantity;
history_push(std::format("Gave item {} x{}.", itemID, quantity));
break;
}
case Command::HELP:
help_print(isGame);
break;
@@ -260,6 +261,27 @@ namespace game::developer
history_push("Returning to menu.");
break;
}
case Command::NEW_GAME:
{
auto key = args.size() > ARG_VALUE ? std::string{args[ARG_VALUE]} : std::string{};
if (key.empty())
{
history_push(std::format("Usage: {}", info->usage));
break;
}
auto characterIndex = resources.character_index_get(key);
if (characterIndex < 0)
{
history_push(std::format("Unknown character: {}", key));
break;
}
action.character = characterIndex;
action.isNewGame = true;
history_push(std::format("Starting new game: {}.", key));
break;
}
case Command::PLAY_ANIMATION:
{
auto animation = args.size() > ARG_VALUE ? std::string{args[ARG_VALUE]} : std::string{};
@@ -283,6 +305,58 @@ namespace game::developer
history_push(std::format("Reloaded character archives: {}", resources.characters.size()));
break;
}
case Command::SHOW_LAYERS:
{
auto isValid = true;
auto isShowLayers = toggle_value_get(args, play->isShowLayers, isValid);
if (!isValid)
{
history_push(std::format("Usage: {}", info->usage));
break;
}
play->isShowLayers = isShowLayers;
play->debug_flags_apply();
history_push(std::format("Show layers {}.", isShowLayers ? "enabled" : "disabled"));
break;
}
case Command::SHOW_NULLS:
{
auto isValid = true;
auto isShowNulls = toggle_value_get(args, play->isShowNulls, isValid);
if (!isValid)
{
history_push(std::format("Usage: {}", info->usage));
break;
}
play->isShowNulls = isShowNulls;
play->debug_flags_apply();
history_push(std::format("Show nulls {}.", isShowNulls ? "enabled" : "disabled"));
break;
}
case Command::SPAWN:
{
auto value = args.size() > ARG_VALUE ? args[ARG_VALUE] : std::string_view{};
int itemID{};
auto parsed = std::from_chars(value.data(), value.data() + value.size(), itemID);
auto isItemIDValid = !value.empty() && parsed.ec == std::errc{} && parsed.ptr == value.data() + value.size();
if (!isItemIDValid)
{
history_push(std::format("Usage: {}", info->usage));
break;
}
if (!play->itemManager.spawn_queue(play->character, itemID, true))
{
history_push(std::format("Unable to spawn item {}.", itemID));
break;
}
auto& item = play->character.data.itemSchema.items[itemID];
history_push(std::format("Spawned item {} {}.", itemID, item.name));
break;
}
case Command::STAGE:
{
auto stage = int_parse(args.size() > ARG_VALUE ? args[ARG_VALUE] : std::string_view{});
+1
View File
@@ -43,6 +43,7 @@ namespace game::developer
{
int character{-1};
bool isMenu{};
bool isNewGame{};
};
Action action{};
+296 -66
View File
@@ -37,6 +37,7 @@ namespace game::developer
X(OSCILLATION, "Oscillation", true) \
X(SCREEN_SHAKE, "Screenshake", false) \
X(SHAKE, "Shake", true) \
X(SILENT, "Silent", true) \
X(SMALL, "Small", true) \
X(SOUND, "Sound", false)
@@ -154,6 +155,7 @@ namespace game::developer
case DialogueEffectType::OSCILLATION:
break;
case DialogueEffectType::SHAKE:
case DialogueEffectType::SILENT:
break;
case DialogueEffectType::SCREEN_SHAKE:
break;
@@ -180,7 +182,6 @@ namespace game::developer
std::string fontPath{};
glm::vec3 color{1.0f, 1.0f, 1.0f};
bool isHasColor{};
const resource::Audio* blipAudio{};
};
std::filesystem::path resource_absolute_path_get(const std::filesystem::path& schemaPath,
@@ -311,7 +312,6 @@ namespace game::developer
info.blip = element.path;
else if (element.soundEntry.is_valid())
info.blip = element.soundEntry.path;
if (element.soundEntry.is_valid()) info.blipAudio = &element.soundEntry.sound;
}
}
@@ -342,11 +342,7 @@ namespace game::developer
}
if (info.blip.empty())
{
if (auto* blip = root->soundBlip.get())
{
info.blip = "<cached blip>";
info.blipAudio = blip;
}
if (root->soundBlip.get()) info.blip = "<cached blip>";
}
}
@@ -357,6 +353,16 @@ namespace game::developer
return info;
}
const resource::Audio* preview_blip_get(const Schema& schema, const ReferenceInfo* reference)
{
auto* root = schema.root();
auto* blip = root ? root->soundBlip.get() : nullptr;
if (blip) return blip;
root = reference && reference->schema ? reference->schema->root() : nullptr;
return root ? root->soundBlip.get() : nullptr;
}
int container_index_get(const Schema& schema, Element::Type type)
{
auto it = schema.elementsByType.find(type);
@@ -416,14 +422,21 @@ namespace game::developer
void element_selection_range_set(Schema& schema, int& selected, std::vector<int>& selectedElements, int index)
{
if (!editor_schema::element_index_is_valid(schema, index))
{
element_selection_set(selected, selectedElements, -1);
return;
}
auto parent = schema.elements[index].parent;
if (selected < 0 || selected >= (int)schema.elements.size() ||
schema.elements[selected].parent != schema.elements[index].parent)
schema.elements[selected].parent != parent || !editor_schema::element_index_is_valid(schema, parent))
{
element_selection_set(selected, selectedElements, index);
return;
}
auto& siblings = schema.elements[schema.elements[index].parent].children;
auto& siblings = schema.elements[parent].children;
auto begin = std::ranges::find(siblings, selected);
auto end = std::ranges::find(siblings, index);
if (begin == siblings.end() || end == siblings.end())
@@ -521,10 +534,12 @@ namespace game::developer
return true;
}
int elements_paste(util::Clipboard& clipboard, Schema& schema, int parent, int beforeSibling)
int elements_paste(util::Clipboard& clipboard, Schema& schema, int parent, int beforeSibling,
Element::Type expected = Element::UNKNOWN)
{
auto* text = clipboard.get();
if (!text || !*text) return -1;
if (!editor_schema::element_index_is_valid(schema, parent)) return -1;
tinyxml2::XMLDocument document{};
if (document.Parse(text) == tinyxml2::XML_SUCCESS && document.RootElement() &&
@@ -534,12 +549,17 @@ namespace game::developer
for (auto* child = document.RootElement()->FirstChildElement(); child; child = child->NextSiblingElement())
{
auto inserted = schema.element_insert_xml(xml_element_text_get(child), parent, beforeSibling);
if (inserted >= 0 && expected != Element::UNKNOWN && schema.elements[inserted].type != expected)
{
schema.element_erase(inserted);
continue;
}
if (inserted >= 0) selected = inserted;
}
return selected;
}
return editor_schema::element_paste(clipboard, schema, parent, beforeSibling);
return editor_schema::element_paste(clipboard, schema, parent, beforeSibling, expected);
}
bool selected_elements_duplicate(Schema& schema, int& selected, std::vector<int>& selectedElements,
@@ -566,10 +586,12 @@ namespace game::developer
bool elements_move(Schema& schema, const std::vector<int>& selectedElements, int parent, int beforeSibling)
{
if (selectedElements.empty()) return false;
if (!editor_schema::element_index_is_valid(schema, parent)) return false;
auto isDirty = false;
for (auto index : selectedElements)
{
if (index == beforeSibling) continue;
if (!editor_schema::element_index_is_valid(schema, index)) continue;
if (schema.element_move(index, parent, beforeSibling)) isDirty = true;
}
return isDirty;
@@ -588,6 +610,7 @@ namespace game::developer
const std::vector<int>* selectedElements = nullptr)
{
auto isDirty = false;
if (!editor_schema::element_index_is_valid(schema, index)) return isDirty;
if (ImGui::BeginDragDropSource())
{
ImGui::SetDragDropPayload(payloadName, &index, sizeof(index));
@@ -783,6 +806,18 @@ namespace game::developer
return result;
}
bool dialogue_silent_effect_get(const Schema& schema, const Element& entry, int index)
{
for (auto effectIndex : dialogue_effect_indices_get(schema, entry))
{
auto& effect = schema.elements[effectIndex];
if (dialogue_effect_type_get(effect.typeString) == DialogueEffectType::SILENT && effect.start <= index &&
index < effect.end)
return true;
}
return false;
}
int dialogue_effect_insert(Schema& schema, int entryIndex, int after, std::string_view type, int start, int end)
{
if (entryIndex < 0 || entryIndex >= (int)schema.elements.size()) return -1;
@@ -1099,9 +1134,147 @@ namespace game::developer
return isDirty;
}
void animation_append_id_combo_draw(const Schema& schema, std::string& value)
{
auto preview = value.empty() ? "<none>" : value.c_str();
if (ImGui::BeginCombo("AppendStringForAnimations", preview))
{
if (ImGui::Selectable("<none>", value.empty())) value.clear();
std::vector<std::string> options{};
if (auto* root = schema.root())
if (auto* stages = schema.child_get(*root, Element::STAGES))
for (auto* stage : schema.children_get(*stages, Element::STAGE))
{
if (!stage || stage->animationAppendID.empty()) continue;
if (std::ranges::find(options, stage->animationAppendID) != options.end()) continue;
options.emplace_back(stage->animationAppendID);
}
for (auto& option : options)
if (ImGui::Selectable(option.c_str(), value == option)) value = option;
ImGui::EndCombo();
}
}
std::vector<std::string> animation_append_ids_get(const Schema& schema)
{
std::vector<std::string> result{};
if (auto* root = schema.root())
if (auto* stages = schema.child_get(*root, Element::STAGES))
for (auto* stage : schema.children_get(*stages, Element::STAGE))
{
if (!stage || stage->animationAppendID.empty()) continue;
if (std::ranges::find(result, stage->animationAppendID) != result.end()) continue;
result.emplace_back(stage->animationAppendID);
}
return result;
}
bool animation_name_appended_get(std::string_view name, const std::vector<std::string>& appendIDs)
{
for (auto& appendID : appendIDs)
if (name.find(appendID) != std::string_view::npos) return true;
return false;
}
std::vector<std::string> dialogue_animations_get(const ReferenceInfo* reference)
{
std::vector<std::string> result{};
if (!reference || !reference->schema) return result;
auto* root = reference->schema->root();
if (!root || !root->anm2Entry.is_valid()) return result;
auto appendIDs = animation_append_ids_get(*reference->schema);
for (auto& animation : root->anm2Entry.anm2.animations)
{
if (animation.name.empty() || animation_name_appended_get(animation.name, appendIDs)) continue;
if (std::ranges::find(result, animation.name) != result.end()) continue;
result.emplace_back(animation.name);
}
return result;
}
bool animation_combo_draw(const char* label, std::string& value, const std::vector<std::string>& animations)
{
auto isDirty = false;
auto preview = value.empty() ? "<none>" : value.c_str();
if (ImGui::BeginCombo(label, preview))
{
if (ImGui::Selectable("<none>", value.empty()))
{
value.clear();
isDirty = true;
}
for (auto& animation : animations)
{
auto isSelected = value == animation;
if (ImGui::Selectable(animation.c_str(), isSelected))
{
value = animation;
isDirty = true;
}
}
ImGui::EndCombo();
}
return isDirty;
}
std::vector<std::string> sound_files_get(const std::filesystem::path& schemaPath, const std::string& rootPath)
{
std::vector<std::string> result{};
auto root = resource_absolute_path_get(schemaPath, rootPath, {});
std::error_code error{};
if (!std::filesystem::is_directory(root, error)) return result;
for (auto it = std::filesystem::recursive_directory_iterator(root, error);
!error && it != std::filesystem::recursive_directory_iterator{}; it.increment(error))
{
if (!it->is_regular_file(error)) continue;
auto extension = it->path().extension().string();
std::ranges::transform(extension, extension.begin(), [](unsigned char character)
{ return (char)std::tolower(character); });
if (extension != ".wav" && extension != ".ogg" && extension != ".mp3") continue;
auto relative = std::filesystem::relative(it->path(), root, error);
if (!error && !relative.empty()) result.emplace_back(relative.generic_string());
}
std::ranges::sort(result);
return result;
}
bool sound_combo_draw(const char* label, const std::filesystem::path& schemaPath, const std::string& rootPath,
Element& effect)
{
auto isDirty = false;
auto files = sound_files_get(schemaPath, rootPath);
auto preview = effect.sound.empty() ? "<none>" : effect.sound.c_str();
if (ImGui::BeginCombo(label, preview))
{
if (ImGui::Selectable("<none>", effect.sound.empty()))
{
effect.sound.clear();
effect.soundEntry = {};
isDirty = true;
}
for (auto& file : files)
{
auto isSelected = effect.sound == file;
if (ImGui::Selectable(file.c_str(), isSelected))
{
effect.sound = file;
sound_effect_entry_rebuild(schemaPath, rootPath, effect);
isDirty = true;
}
}
ImGui::EndCombo();
}
return isDirty;
}
bool pool_entry_delete(Schema& schema, int poolIndex, int poolEntryIndex)
{
if (poolIndex < 0 || poolIndex >= (int)schema.elements.size()) return false;
if (!editor_schema::element_index_is_valid(schema, poolEntryIndex)) return false;
auto& children = schema.elements[poolIndex].children;
auto it = std::ranges::find(children, poolEntryIndex);
if (it == children.end()) return false;
@@ -1183,9 +1356,10 @@ namespace game::developer
}
auto delayCharacter =
dialogue_delay_character_get(schema, entry.text, editor.previewIndex - 1, editor.previewDelay);
auto isSilent = dialogue_silent_effect_get(schema, entry, editor.previewIndex - 1);
editor.previewEntity.isTalking = editor.isPlaying &&
editor.previewIndex < util::imgui::utf8_char_count(entry.text) &&
!delayCharacter.isDisableTalk;
!delayCharacter.isDisableTalk && !isSilent;
editor.previewEntity.update();
auto& canvas = editor.previewCanvas.render(resources, editor.previewEntity, size);
@@ -1231,9 +1405,8 @@ namespace game::developer
auto referenceInfo = reference_info_get(loaded_schema_get(loadedSchemas, Element::CHARACTER));
auto* reference = referenceInfo ? &*referenceInfo : nullptr;
auto dialogueAnimations = dialogue_animations_get(reference);
auto menuInfo = reference_info_get(loaded_schema_get(loadedSchemas, Element::MENU));
previewBlip = root->soundBlip.get();
if (!previewBlip && reference && reference->blipAudio) previewBlip = reference->blipAudio;
nameColor = reference && reference->isHasColor ? reference->color : glm::vec3{NAME_COLOR_DEFAULT};
if (menuInfo && menuInfo->path && !menuInfo->font.empty() &&
@@ -1278,6 +1451,7 @@ namespace game::developer
auto available = ImGui::GetContentRegionAvail();
auto leftWidth = std::max(LEFT_PANEL_MIN_WIDTH, available.x * LEFT_PANEL_WIDTH_RATIO);
auto rightWidth = std::max(WIDGET_MIN_SIZE, available.x - leftWidth - ImGui::GetStyle().ItemSpacing.x);
auto isDialogueStructureChanged = false;
ImGui::BeginChild("dialogue-left", ImVec2(leftWidth, 0.0f));
auto settingsHeight = ImGui::GetFrameHeightWithSpacing();
@@ -1285,7 +1459,7 @@ namespace game::developer
if (ImGui::BeginChild("dialogue-settings", ImVec2(0.0f, settingsHeight), ImGuiChildFlags_None,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse))
{
util::imgui::input_string("AppendStringForAnimations", animationAppendID);
animation_append_id_combo_draw(reference && reference->schema ? *reference->schema : schema, animationAppendID);
}
ImGui::EndChild();
@@ -1308,6 +1482,43 @@ namespace game::developer
mode = ENTRY;
scrollToEntry = selectedEntry;
};
auto entryNavigate = [&](int offset)
{
if (entries.empty()) return -1;
auto it = std::ranges::find(entries, selectedEntry);
if (it == entries.end()) return offset > 0 ? entries.front() : entries.back();
auto index = (int)std::distance(entries.begin(), it);
index = std::clamp(index + offset, 0, (int)entries.size() - 1);
return entries[index];
};
auto entrySelectionApply = [&](int index, bool isToggle, bool isRange)
{
if (!element_is_type(schema, index, Element::ENTRY)) return;
mode = ENTRY;
if (isToggle)
element_selection_toggle(selectedEntry, selectedEntries, index);
else if (isRange)
element_selection_range_set(schema, selectedEntry, selectedEntries, index);
else
element_selection_set(selectedEntry, selectedEntries, index);
selectedPoolEntry = -1;
selectedChoice = -1;
selectedEffect = -1;
textSelectionStart = 0;
textSelectionEnd = 0;
textSelectionStartByte = 0;
textSelectionEndByte = 0;
previewIndex = 0;
previewTimer = 0.0f;
previewBaseDelay = std::max(DELAY_MIN, delay);
previewDelay = previewBaseDelay;
isPreviewBlipQueued = false;
auto& entry = schema.elements[index];
isPlaying = util::imgui::utf8_char_count(entry.text) > 0;
previewNeedsAnimationPlay = true;
scrollToEntry = index;
if (isPlaying) dialogue_preview_start_effects_apply(*this, schema, entry, previewIndex);
};
auto entryMenuDraw = [&](int target)
{
auto actionDirty = false;
@@ -1335,8 +1546,8 @@ namespace game::developer
selected_elements_copy(clipboard, schema, target, selectedEntries, Element::ENTRY);
if (ImGui::MenuItem("Paste", "Ctrl+V", false, entriesParent >= 0))
{
auto pasted =
elements_paste(clipboard, schema, entriesParent, sibling_after_get(schema, entriesParent, target));
auto pasted = elements_paste(clipboard, schema, entriesParent,
sibling_after_get(schema, entriesParent, target), Element::ENTRY);
if (pasted >= 0)
{
element_selection_set(selectedEntry, selectedEntries, pasted);
@@ -1353,6 +1564,7 @@ namespace game::developer
{
isDirty = true;
entriesListChanged = true;
isDialogueStructureChanged = true;
}
document_history_context_menu_draw();
ImGui::EndPopup();
@@ -1362,35 +1574,15 @@ namespace game::developer
if (entriesListChanged) break;
auto& entry = schema.elements[index];
auto isSelected = mode == ENTRY && element_selected_has(selectedEntry, selectedEntries, index);
ImGui::SetNextItemAllowOverlap();
auto renameResult =
util::imgui::selectable_renamable(std::format("entry-{}", index).c_str(), entry.idString, "<entry>",
isSelected, ImGuiSelectableFlags_SelectOnRelease);
isSelected);
if (renameResult.selected && !ImGui::IsDragDropActive() &&
!ImGui::IsMouseDragPastThreshold(ImGuiMouseButton_Left))
{
mode = ENTRY;
auto& io = ImGui::GetIO();
if (io.KeyCtrl)
element_selection_toggle(selectedEntry, selectedEntries, index);
else if (io.KeyShift)
element_selection_range_set(schema, selectedEntry, selectedEntries, index);
else
element_selection_set(selectedEntry, selectedEntries, index);
selectedPoolEntry = -1;
selectedChoice = -1;
selectedEffect = -1;
textSelectionStart = 0;
textSelectionEnd = 0;
textSelectionStartByte = 0;
textSelectionEndByte = 0;
previewIndex = 0;
previewTimer = 0.0f;
previewBaseDelay = std::max(DELAY_MIN, delay);
previewDelay = previewBaseDelay;
isPreviewBlipQueued = false;
isPlaying = util::imgui::utf8_char_count(entry.text) > 0;
previewNeedsAnimationPlay = true;
if (isPlaying) dialogue_preview_start_effects_apply(*this, schema, entry, previewIndex);
entrySelectionApply(index, io.KeyCtrl, io.KeyShift);
}
if (renameResult.renamed)
{
@@ -1413,6 +1605,7 @@ namespace game::developer
{
isDirty = true;
entriesListChanged = true;
isDialogueStructureChanged = true;
}
document_history_context_menu_draw();
ImGui::EndPopup();
@@ -1425,7 +1618,17 @@ namespace game::developer
auto target = hoveredEntry >= 0 ? hoveredEntry : selectedEntry;
if (element_is_type(schema, target, Element::ENTRY))
{
if (editor_schema::shortcut_is_pressed(ImGuiKey_Delete))
if (editor_schema::shortcut_is_pressed(ImGuiKey_DownArrow))
{
auto next = entryNavigate(1);
if (next >= 0) entrySelectionApply(next, ImGui::GetIO().KeyCtrl, ImGui::GetIO().KeyShift);
}
else if (editor_schema::shortcut_is_pressed(ImGuiKey_UpArrow))
{
auto previous = entryNavigate(-1);
if (previous >= 0) entrySelectionApply(previous, ImGui::GetIO().KeyCtrl, ImGui::GetIO().KeyShift);
}
else if (editor_schema::shortcut_is_pressed(ImGuiKey_Delete))
{
if (!element_selected_has(selectedEntry, selectedEntries, target))
element_selection_set(selectedEntry, selectedEntries, target);
@@ -1447,7 +1650,7 @@ namespace game::developer
else if (editor_schema::shortcut_is_pressed(ImGuiMod_Ctrl | ImGuiKey_V))
{
auto pasted = elements_paste(clipboard, schema, entriesParent,
sibling_after_get(schema, entriesParent, target));
sibling_after_get(schema, entriesParent, target), Element::ENTRY);
if (pasted >= 0)
{
element_selection_set(selectedEntry, selectedEntries, pasted);
@@ -1455,7 +1658,11 @@ namespace game::developer
entriesListChanged = true;
}
}
if (entriesListChanged) isDirty = true;
if (entriesListChanged)
{
isDirty = true;
isDialogueStructureChanged = true;
}
}
}
}
@@ -1505,8 +1712,8 @@ namespace game::developer
selected_elements_copy(clipboard, schema, target, selectedPools, Element::POOL);
if (ImGui::MenuItem("Paste", "Ctrl+V", false, poolsParent >= 0))
{
auto pasted =
elements_paste(clipboard, schema, poolsParent, sibling_after_get(schema, poolsParent, target));
auto pasted = elements_paste(clipboard, schema, poolsParent,
sibling_after_get(schema, poolsParent, target), Element::POOL);
if (pasted >= 0)
{
element_selection_set(selectedPool, selectedPools, pasted);
@@ -1523,6 +1730,8 @@ namespace game::developer
{
isDirty = true;
poolsListChanged = true;
selectedPoolEntry = -1;
isDialogueStructureChanged = true;
}
document_history_context_menu_draw();
ImGui::EndPopup();
@@ -1532,9 +1741,10 @@ namespace game::developer
if (poolsListChanged) break;
auto& pool = schema.elements[index];
auto isSelected = mode == POOL && element_selected_has(selectedPool, selectedPools, index);
ImGui::SetNextItemAllowOverlap();
auto renameResult =
util::imgui::selectable_renamable(std::format("pool-{}", index).c_str(), pool.idString, "<pool>",
isSelected, ImGuiSelectableFlags_SelectOnRelease);
isSelected);
if (renameResult.selected && !ImGui::IsDragDropActive() &&
!ImGui::IsMouseDragPastThreshold(ImGuiMouseButton_Left))
{
@@ -1568,6 +1778,8 @@ namespace game::developer
{
isDirty = true;
poolsListChanged = true;
selectedPoolEntry = -1;
isDialogueStructureChanged = true;
}
document_history_context_menu_draw();
ImGui::EndPopup();
@@ -1600,8 +1812,8 @@ namespace game::developer
}
else if (editor_schema::shortcut_is_pressed(ImGuiMod_Ctrl | ImGuiKey_V))
{
auto pasted =
elements_paste(clipboard, schema, poolsParent, sibling_after_get(schema, poolsParent, target));
auto pasted = elements_paste(clipboard, schema, poolsParent,
sibling_after_get(schema, poolsParent, target), Element::POOL);
if (pasted >= 0)
{
element_selection_set(selectedPool, selectedPools, pasted);
@@ -1609,7 +1821,12 @@ namespace game::developer
poolsListChanged = true;
}
}
if (poolsListChanged) isDirty = true;
if (poolsListChanged)
{
isDirty = true;
selectedPoolEntry = -1;
isDialogueStructureChanged = true;
}
}
}
}
@@ -1626,7 +1843,11 @@ namespace game::developer
ImGui::PushID("dialogue-right");
ImGui::BeginGroup();
if (mode == ENTRY)
if (isDialogueStructureChanged)
{
ImGui::TextUnformatted("Selection changed.");
}
else if (mode == ENTRY)
{
auto* entry = selected_get(schema, selectedEntry);
if (!entry)
@@ -1781,6 +2002,8 @@ namespace game::developer
auto textEditorRectMax = ImGui::GetItemRectMax();
auto textEditorRightClicked = ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right);
auto isTextEditorContextOpen =
ImGui::IsPopupOpen("text-editor-context") || ImGui::IsPopupOpen("text-editor-item-context");
auto textEditorScroll = ImVec2{};
if (auto* inputState = ImGui::GetInputTextState(ImGui::GetItemID()))
{
@@ -1795,13 +2018,14 @@ namespace game::developer
textSelectionStart = util::imgui::utf8_byte_to_char_index(entry->text, selectionStart);
textSelectionEnd = util::imgui::utf8_byte_to_char_index(entry->text, selectionEnd);
}
else if (textEditorRightClicked && textSelectionStartByte != textSelectionEndByte)
else if ((textEditorRightClicked || isTextEditorContextOpen) &&
textSelectionStartByte != textSelectionEndByte)
{
inputState->WantReloadUserBuf = true;
inputState->ReloadSelectionStart = textSelectionStartByte;
inputState->ReloadSelectionEnd = textSelectionEndByte;
}
else if (!textEditorRightClicked)
else if (!textEditorRightClicked && !isTextEditorContextOpen)
{
textSelectionStartByte = selectionStart;
textSelectionEndByte = selectionEnd;
@@ -1809,9 +2033,7 @@ namespace game::developer
textSelectionEnd = util::imgui::utf8_byte_to_char_index(entry->text, selectionEnd);
}
}
if ((textEditorRightClicked || ImGui::IsPopupOpen("text-editor-context") ||
ImGui::IsPopupOpen("text-editor-item-context")) &&
textSelectionStartByte != textSelectionEndByte)
if ((textEditorRightClicked || isTextEditorContextOpen) && textSelectionStartByte != textSelectionEndByte)
text_selection_overlay_draw(entry->text, textSelectionStartByte, textSelectionEndByte, textEditorRectMin,
textEditorRectMax, textEditorScroll);
@@ -1878,7 +2100,8 @@ namespace game::developer
if (entry_combo_draw("Next", schema, entry->next)) isDirty = true;
ImGui::EndDisabled();
ImGui::Separator();
if (util::imgui::input_string("Animation", entry->animation))
ImGui::SetNextItemWidth(itemWidth);
if (animation_combo_draw("Animation", entry->animation, dialogueAnimations))
{
isDirty = true;
}
@@ -1995,7 +2218,7 @@ namespace game::developer
{
case DialogueEffectType::ANIMATION:
ImGui::SetNextItemWidth(-FLT_MIN);
if (util::imgui::input_string("##Value", child.animation)) isEffectsChanged = true;
if (animation_combo_draw("##Value", child.animation, dialogueAnimations)) isEffectsChanged = true;
break;
case DialogueEffectType::DELAY:
ImGui::SetNextItemWidth(-FLT_MIN);
@@ -2003,6 +2226,7 @@ namespace game::developer
break;
case DialogueEffectType::OSCILLATION:
case DialogueEffectType::SHAKE:
case DialogueEffectType::SILENT:
case DialogueEffectType::BIG:
case DialogueEffectType::SMALL:
break;
@@ -2014,16 +2238,11 @@ namespace game::developer
auto rootPath = root ? root->soundRootPath : std::string{};
if (!child.sound.empty() && child.soundEntry.path != child.sound)
sound_effect_entry_rebuild(schemaPath, rootPath, child);
auto displayPath = resource_display_path_get(schemaPath, rootPath, child.sound);
auto suffixWidth = SOUND_WIDGET_WIDTH;
auto pathWidth = std::max(WIDGET_MIN_SIZE, ImGui::GetContentRegionAvail().x - suffixWidth -
ImGui::GetStyle().ItemSpacing.x);
if (util::imgui::path_widget("##Sound Value", child.sound, displayPath, ImVec2(pathWidth, 0.0f)))
{
pendingSetting = PENDING_SOUND;
pendingSoundEffect = childIndex;
dialog.file_open(util::Dialog::SCHEMA_SOUND_OPEN);
}
ImGui::SetNextItemWidth(pathWidth);
if (sound_combo_draw("##Sound Value", schemaPath, rootPath, child)) isEffectsChanged = true;
ImGui::SameLine();
auto isMissing = !child.sound.empty() && !child.soundEntry.sound.is_valid();
if (util::imgui::sound_widget("##Sound Play", isMissing))
@@ -2141,6 +2360,7 @@ namespace game::developer
case DialogueEffectType::OSCILLATION:
case DialogueEffectType::SHAKE:
case DialogueEffectType::SCREEN_SHAKE:
case DialogueEffectType::SILENT:
case DialogueEffectType::BIG:
case DialogueEffectType::SMALL:
break;
@@ -2188,14 +2408,18 @@ namespace game::developer
previewDelay = previewBaseDelay;
dialogue_preview_start_effects_apply(*this, schema, *entry, previewIndex);
auto delayCharacter = dialogue_delay_character_get(schema, entry->text, previewIndex - 1, previewDelay);
auto isSilent = dialogue_silent_effect_get(schema, *entry, previewIndex - 1);
previewDelay = delayCharacter.delay;
auto isBlipStep = blipDelay > 0 && previewIndex % blipDelay == 0;
if (delayCharacter.isDisableTalk)
if (isSilent)
isPreviewBlipQueued = false;
else if (delayCharacter.isDisableTalk)
{
if (isBlipStep) isPreviewBlipQueued = true;
}
else if (isBlipStep || isPreviewBlipQueued)
{
auto* previewBlip = preview_blip_get(schema, reference);
if (previewBlip && previewBlip->is_valid()) previewBlip->play();
isPreviewBlipQueued = false;
}
@@ -2229,6 +2453,7 @@ namespace game::developer
}
else if (ImGui::BeginChild("pool-entries", ImVec2(rightWidth, 0.0f), ImGuiChildFlags_Borders))
{
auto isPoolEntriesChanged = false;
auto poolEntriesShortcutScope = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) ||
ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows);
auto hoveredPoolEntry = -1;
@@ -2249,6 +2474,7 @@ namespace game::developer
if (pool_entry_delete(schema, selectedPool, selectedPoolEntry))
{
selectedPoolEntry = -1;
isPoolEntriesChanged = true;
isDirty = true;
}
}
@@ -2258,10 +2484,12 @@ namespace game::developer
auto children = pool->children;
for (auto childIndex : children)
{
if (isPoolEntriesChanged) break;
if (childIndex < 0 || childIndex >= (int)schema.elements.size()) continue;
auto& poolEntry = schema.elements[childIndex];
if (poolEntry.type != Element::POOL_ENTRY) continue;
auto label = poolEntry.idString.empty() ? "<entry>" : poolEntry.idString.c_str();
ImGui::SetNextItemAllowOverlap();
if (ImGui::Selectable(label, selectedPoolEntry == childIndex)) selectedPoolEntry = childIndex;
scroll_to_item_if_requested(scrollToPoolEntry, childIndex);
if (ImGui::IsItemHovered()) hoveredPoolEntry = childIndex;
@@ -2280,15 +2508,17 @@ namespace game::developer
if (pool_entry_delete(schema, selectedPool, childIndex))
{
selectedPoolEntry = -1;
isPoolEntriesChanged = true;
isDirty = true;
}
}
document_history_context_menu_draw();
ImGui::EndPopup();
}
if (isPoolEntriesChanged) break;
if (auto* entry = schema.dialogue_entry_get(poolEntry.idString)) entry_tooltip_draw(schema, *entry);
}
if (poolEntriesShortcutScope && editor_schema::shortcut_is_pressed(ImGuiKey_Delete))
if (!isPoolEntriesChanged && poolEntriesShortcutScope && editor_schema::shortcut_is_pressed(ImGuiKey_Delete))
{
auto target = hoveredPoolEntry >= 0 ? hoveredPoolEntry : selectedPoolEntry;
if (target >= 0 && pool_entry_delete(schema, selectedPool, target))
-1
View File
@@ -383,7 +383,6 @@ namespace game::developer
resource::xml::Character previewCharacter{};
util::Clipboard clipboard{};
resource::Font previewFont{};
const resource::Audio* previewBlip{};
Entity previewEntity{};
util::imgui::widget::EntityCanvas previewCanvas{};
std::string animationAppendID{};
+10 -33
View File
@@ -22,7 +22,6 @@ namespace game::developer
constexpr auto RECENT_FILES_MAX = 10;
constexpr auto CENTER_RATIO = 0.5f;
constexpr auto DEVELOPER_WINDOW_SIZE_RATIO = 0.75f;
constexpr auto INFO_SCHEMA_TABLE_COLUMNS = 4;
constexpr auto DEVELOPER_WINDOW_FLAGS = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings;
constexpr auto EDITOR_CHILD_FLAGS = ImGuiWindowFlags_MenuBar;
constexpr auto DIALOGUE_SCHEMA_FILENAME = "dialogue.xml";
@@ -338,11 +337,6 @@ namespace game::developer
selectedDocument = i;
if (ImGui::BeginTabBar("##Document Tabs"))
{
if (ImGui::BeginTabItem("Info"))
{
document_info_update(documents[i]);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Dialogue"))
{
document_dialogue_update(resources, documents[i]);
@@ -359,33 +353,6 @@ namespace game::developer
if (closeRequest >= 0) close(closeRequest);
}
void Window::document_info_update(const Document& document)
{
if (!ImGui::BeginTable("##Schema Info", INFO_SCHEMA_TABLE_COLUMNS, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
return;
ImGui::TableSetupColumn("Schema");
ImGui::TableSetupColumn("File");
ImGui::TableSetupColumn("Exists");
ImGui::TableSetupColumn("Valid");
ImGui::TableHeadersRow();
for (auto& schema : document.schemas)
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted(schema.label.c_str());
ImGui::TableNextColumn();
ImGui::TextUnformatted(schema.filename.c_str());
ImGui::TableNextColumn();
ImGui::TextUnformatted(schema.isExists ? "Yes" : "No");
ImGui::TableNextColumn();
ImGui::TextUnformatted(schema.isValid ? "Yes" : "No");
}
ImGui::EndTable();
}
void Window::document_dialogue_update(Resources& resources, Document& document)
{
auto* dialogue = document.dialogue_schema_get();
@@ -450,6 +417,16 @@ namespace game::developer
void Window::update(Resources& resources, SDL_Window* window, state::Play* play)
{
dialog.window_set(window);
auto* settingsRoot = resources.settings.root();
if (!settingsRoot || !settingsRoot->isDeveloperConsoleEnabled)
{
resources.isDeveloper = false;
isDeveloperOpen = false;
action = {};
console.action = {};
return;
}
if (!resources.isDeveloper && shortcut_is_pressed(ImGuiKey_GraveAccent))
{
-1
View File
@@ -48,7 +48,6 @@ namespace game::developer
void editor_shortcuts_update();
void editor_menu_update();
void editor_documents_update(Resources&);
void document_info_update(const Document&);
void document_dialogue_update(Resources&, Document&);
void dialog_selection_update();
void recent_files_load();
+123 -42
View File
@@ -18,6 +18,19 @@
namespace game
{
constexpr auto CHARACTER_VALUE_ZERO = 0.0f;
constexpr auto MODEL_IDENTITY = 1.0f;
constexpr auto DEBUG_COLOR_MIN = 0.0f;
constexpr auto DEBUG_COLOR_MAX = 1.0f;
constexpr auto DEBUG_LAYER_COLOR = glm::vec4(DEBUG_COLOR_MAX, DEBUG_COLOR_MIN, DEBUG_COLOR_MIN, DEBUG_COLOR_MAX);
constexpr auto DEBUG_NULL_COLOR = glm::vec4(DEBUG_COLOR_MIN, DEBUG_COLOR_MIN, DEBUG_COLOR_MAX, DEBUG_COLOR_MAX);
struct Anm2RenderState
{
glm::mat4 model{MODEL_IDENTITY};
glm::vec4 tint{MODEL_IDENTITY};
glm::vec3 colorOffset{};
bool isVisible{true};
};
float value_clamp(float value, float minimum, float maximum)
{
@@ -34,6 +47,41 @@ namespace game
return std::clamp(value, rangeMin, rangeMax);
}
static glm::mat4 anm2_root_model_get(const Entity::Anm2::Frame& frame, glm::vec2 position = {})
{
return util::math::quad_model_no_size_get(frame.position + position, frame.pivot,
util::math::to_unit(frame.scale), frame.rotation);
}
static Anm2RenderState anm2_group_state_get(Entity& entity, Entity::Anm2::Animation& animation, int groupID,
float frameTime)
{
Anm2RenderState state{};
if (!util::vector::in_bounds(animation.groups, groupID)) return state;
auto& group = animation.groups[groupID];
state = anm2_group_state_get(entity, animation, group.parent, frameTime);
if (!state.isVisible) return state;
if (!group.isVisible)
{
state.isVisible = false;
return state;
}
if (group.rootAnimation.frames.empty()) return state;
auto frame = entity.frame_generate(group.rootAnimation, frameTime, Entity::Anm2::GROUP, groupID);
if (!frame.isVisible)
{
state.isVisible = false;
return state;
}
state.model *= anm2_root_model_get(frame);
state.tint *= frame.tint;
state.colorOffset += frame.colorOffset;
return state;
}
Entity::Override::Override(int _id, Entity::Anm2::Type _type, Entity::Override::Mode _mode,
Entity::FrameOptional _frame, std::optional<float> _time,
Entity::Override::Function _function, float _cycles)
@@ -86,28 +134,27 @@ namespace game
Entity::Anm2::Animation* Entity::animation_get(int index)
{
if (index == -1) index = animationIndex;
if (animationMapReverse.contains(index)) return &animations[index];
if (index >= 0 && index < (int)animations.size() && animationMapReverse.contains(index)) return &animations[index];
return nullptr;
}
Entity::Anm2::Animation* Entity::animation_get(const std::string& name)
{
if (animationMap.contains(name)) return &animations[animationMap[name]];
return nullptr;
auto it = animationMap.find(name);
return it == animationMap.end() ? nullptr : animation_get(it->second);
}
bool Entity::is_playing(const std::string& name)
{
if (name.empty())
return state == PLAYING;
else
return state == PLAYING && animationMap[name] == animationIndex;
if (name.empty()) return state == PLAYING;
auto it = animationMap.find(name);
return it != animationMap.end() && state == PLAYING && it->second == animationIndex;
}
int Entity::animation_index_get(const std::string& name)
{
if (animationMap.contains(name)) return animationMap[name];
return -1;
auto it = animationMap.find(name);
return it == animationMap.end() ? -1 : it->second;
}
Entity::Anm2::Item* Entity::item_get(Entity::Anm2::Type type, int id, int checkAnimationIndex)
@@ -374,11 +421,13 @@ namespace game
auto frame = frame_generate(*item, checkTime, Entity::Anm2::NULL_, nullID);
if (!frame.isVisible) return glm::vec4(NAN);
auto rootModel = util::math::quad_model_no_size_get(root.position + position, root.pivot,
util::math::to_unit(root.scale), root.rotation);
auto groupState = anm2_group_state_get(*this, *animation, item->groupID, checkTime);
if (!groupState.isVisible) return glm::vec4(NAN);
auto rootModel = anm2_root_model_get(root, position);
auto frameModel =
util::math::quad_model_get(frame.scale, frame.position, frame.scale * 0.5f, glm::vec2(1.0f), frame.rotation);
auto model = rootModel * frameModel;
auto model = rootModel * groupState.model * frameModel;
float minX = std::numeric_limits<float>::infinity();
float minY = std::numeric_limits<float>::infinity();
@@ -406,14 +455,16 @@ namespace game
auto root = frame_generate(animation->rootAnimation, time, Entity::Anm2::ROOT);
auto rootModel = util::math::quad_model_no_size_get(root.position + position, root.pivot,
util::math::to_unit(root.scale), root.rotation);
auto rootModel = anm2_root_model_get(root, position);
for (auto& i : animation->layerOrder)
{
auto& layerAnimation = animation->layerAnimations[i];
if (!layerAnimation.isVisible) continue;
auto groupState = anm2_group_state_get(*this, *animation, layerAnimation.groupID, time);
if (!groupState.isVisible) continue;
auto layer = util::map::find(layers, i);
if (!layer) continue;
@@ -425,13 +476,13 @@ namespace game
auto model = util::math::quad_model_get(frame.size, frame.position, frame.pivot, util::math::to_unit(frame.scale),
frame.rotation);
model = rootModel * model;
model = rootModel * groupState.model * model;
auto& texture = spritesheet->texture;
if (!texture.is_valid()) return;
auto tint = frame.tint * root.tint;
auto colorOffset = frame.colorOffset + root.colorOffset;
auto tint = frame.tint * groupState.tint * root.tint;
auto colorOffset = frame.colorOffset + groupState.colorOffset + root.colorOffset;
auto inset = glm::vec2(0);
auto uvMin = (frame.crop + inset) / glm::vec2(texture.size);
@@ -439,23 +490,37 @@ namespace game
auto uvVertices = util::math::uv_vertices_get(uvMin, uvMax);
canvas.texture_render(textureShader, texture.id, model, tint, colorOffset, uvVertices.data());
if (isShowLayers) canvas.rect_render(rectShader, model, DEBUG_LAYER_COLOR);
}
if (isShowNulls)
{
for (int i = 0; i < (int)animation->nullAnimations.size(); i++)
auto null_render = [&](int i, Entity::Anm2::Item& nullAnimation)
{
auto& nullAnimation = animation->nullAnimations[i];
if (!nullAnimation.isVisible) continue;
if (!nullAnimation.isVisible) return;
auto groupState = anm2_group_state_get(*this, *animation, nullAnimation.groupID, time);
if (!groupState.isVisible) return;
auto frame = frame_generate(nullAnimation, time, Entity::Anm2::NULL_, i);
if (!frame.isVisible) continue;
if (!frame.isVisible) return;
auto model = util::math::quad_model_get(frame.scale, frame.position, frame.scale * 0.5f, glm::vec2(1.0f),
frame.rotation);
model = rootModel * model;
model = rootModel * groupState.model * model;
canvas.rect_render(rectShader, model);
canvas.rect_render(rectShader, model, DEBUG_NULL_COLOR);
};
if (!animation->nullOrder.empty())
{
for (auto& i : animation->nullOrder)
if (auto nullAnimation = util::map::find(animation->nullAnimations, i)) null_render(i, *nullAnimation);
}
else
{
for (auto& [i, nullAnimation] : animation->nullAnimations)
null_render(i, nullAnimation);
}
}
}
@@ -479,19 +544,22 @@ namespace game
glm::mat4 transform(1.0f);
auto root = frame_generate(animation->rootAnimation, t, Entity::Anm2::ROOT);
transform *= util::math::quad_model_no_size_get(root.position + position, root.pivot,
util::math::to_unit(root.scale), root.rotation);
transform *= anm2_root_model_get(root, position);
for (auto& [id, layerAnimation] : animation->layerAnimations)
{
if (!layerAnimation.isVisible) continue;
auto groupState = anm2_group_state_get(*this, *animation, layerAnimation.groupID, t);
if (!groupState.isVisible) continue;
auto frame = frame_generate(layerAnimation, t, Entity::Anm2::LAYER, id);
if (frame.size == glm::vec2() || !frame.isVisible) continue;
auto layerTransform = transform * util::math::quad_model_get(frame.size, frame.position, frame.pivot,
util::math::to_unit(frame.scale), frame.rotation);
auto layerTransform = transform * groupState.model *
util::math::quad_model_get(frame.size, frame.position, frame.pivot,
util::math::to_unit(frame.scale), frame.rotation);
for (auto& corner : CORNERS)
{
glm::vec4 world = layerTransform * glm::vec4(corner, 0.0f, 1.0f);
@@ -575,7 +643,6 @@ namespace game
capacity = saveCharacter ? saveCharacter->capacityCalories : (double)root->capacityCalories;
weight = saveCharacter ? saveCharacter->weightKilograms : (double)root->weightKilograms;
digestionRate = saveCharacter ? saveCharacter->digestionRate : (double)root->digestionRate;
eatSpeed = saveCharacter ? saveCharacter->eatSpeedMultiplier : (double)root->eatSpeedMultiplier;
calories = saveCharacter ? saveCharacter->calories : 0;
@@ -713,12 +780,13 @@ namespace game
weight = value_clamp(weight, root->weightKilograms, root->weightMaxKilograms);
capacity = value_clamp(capacity, root->capacityMinCalories, root->capacityMaxCalories);
digestionRate = value_clamp(digestionRate, root->digestionRateMin, root->digestionRateMax);
eatSpeed = value_clamp(eatSpeed, root->eatSpeedMinMultiplier, root->eatSpeedMaxMultiplier);
calories = value_clamp(calories, CHARACTER_VALUE_ZERO, std::max(CHARACTER_VALUE_ZERO, max_capacity()));
digestionProgress = value_clamp(digestionProgress, CHARACTER_VALUE_ZERO, DIGESTION_MAX);
digestionTimer = value_clamp(digestionTimer, 0, std::max(0, root->digestionTimerMax));
totalCaloriesConsumed = std::max(CHARACTER_VALUE_ZERO, totalCaloriesConsumed);
totalFoodItemsEaten = std::max(0, totalFoodItemsEaten);
totalItemsSpawned = std::max(0, totalItemsSpawned);
totalItemsConsumed = std::max(0, totalItemsConsumed);
totalPlaytimeSeconds = std::max(CHARACTER_VALUE_ZERO, totalPlaytimeSeconds);
stage = value_clamp(stage_get(), 0, stage_max_get());
}
@@ -801,7 +869,6 @@ namespace game
(double)data.root()->capacityMaxCalories);
}
totalCaloriesConsumed += calories;
calories = 0;
if (auto nextStage = stage_from_weight_get((double)nextWeight); nextStage > stage_from_weight_get(weight))
@@ -844,7 +911,28 @@ namespace game
stage = stage_get();
expand_areas_apply();
if (talkOverrideID < 0 || talkOverrideID >= (int)overrides.size() || blinkOverrideID < 0 ||
blinkOverrideID >= (int)overrides.size())
return;
auto* animation = animation_get();
auto isAnimationStateValid = animation && animationIndex >= 0 &&
animationIndex < (int)animationTalkDurations.size() &&
animationIndex < (int)animationBlinkDurations.size();
auto& talkOverride = overrides[talkOverrideID];
auto& blinkOverride = overrides[blinkOverrideID];
if (!isAnimationStateValid)
{
isTalking = false;
isBlinking = false;
talkTimer = 0.0f;
blinkTimer = 0.0f;
talkOverride.frame = {};
blinkOverride.frame = {};
return;
}
if (isTalking)
{
@@ -858,7 +946,7 @@ namespace game
auto* talk = data.overrides_root() ? data.child_get(*data.overrides_root(), resource::xml::Schema::Element::TALK)
: nullptr;
auto id = talk ? data.layer_id_get(talk->layerSource) : -1;
auto& layerAnimations = animation_get()->layerAnimations;
auto& layerAnimations = animation->layerAnimations;
if (layerAnimations.contains(id) && animationTalkDurations.at(animationIndex) > -1)
{
@@ -885,8 +973,6 @@ namespace game
else
talkOverride.frame = {};
auto& blinkOverride = overrides[blinkOverrideID];
if (auto blinkDuration = animationBlinkDurations[animationIndex]; blinkDuration != 1)
{
if (util::math::random_percent_roll(data.root()->blinkChance)) isBlinking = true;
@@ -904,7 +990,7 @@ namespace game
? data.child_get(*data.overrides_root(), resource::xml::Schema::Element::BLINK)
: nullptr;
auto id = blink ? data.layer_id_get(blink->layerSource) : -1;
auto& layerAnimations = animation_get()->layerAnimations;
auto& layerAnimations = animation->layerAnimations;
if (layerAnimations.contains(id))
{
@@ -958,21 +1044,16 @@ namespace game
spritesheetType = type;
}
Entity::Entity(Entity::Anm2 _anm2, glm::ivec2 _position, int _schemaID, int _durability, int _animationIndex,
glm::vec2 _velocity, float _rotation)
Entity::Entity(Entity::Anm2 _anm2, glm::ivec2 _position, int _schemaID, glm::vec2 _velocity, float _rotation)
{
anm2_set(_anm2);
position = _position;
schemaID = _schemaID;
durability = _durability;
velocity = _velocity;
entityType = ITEM;
mode = SET;
startTime = 0.0f;
if (_animationIndex != -1)
play({.index = _animationIndex, .mode = SET});
else
play({.index = defaultAnimationID, .mode = SET});
play({.index = defaultAnimationID, .mode = SET});
pending_animation_apply();
rotationOverrideID = util::vector::push_index(
+5 -5
View File
@@ -134,6 +134,7 @@ namespace game
Mode mode{PLAY};
glm::vec2 position{};
float time{};
bool isShowLayers{};
bool isShowNulls{};
bool isVisible{true};
int animationIndex{-1};
@@ -156,9 +157,10 @@ namespace game
int digestionTimer{};
bool isDigesting{};
bool isJustDigested{};
float eatSpeed{1.0f};
float totalCaloriesConsumed{};
int totalFoodItemsEaten{};
int totalItemsSpawned{};
int totalItemsConsumed{};
float totalPlaytimeSeconds{};
int talkOverrideID{};
float talkTimer{};
bool isTalking{};
@@ -185,7 +187,6 @@ namespace game
bool isHeld{};
int schemaID{};
int rotationOverrideID{};
int durability{};
glm::vec2 velocity{};
float angularVelocity{};
float phase{};
@@ -200,8 +201,7 @@ namespace game
Entity& operator=(Entity&&) noexcept;
Entity(resource::xml::Anm2, glm::vec2 position = {}, Mode = PLAY, float time = 0.0f, int animationIndex = -1);
Entity(resource::xml::Character&, glm::ivec2);
Entity(resource::xml::Anm2, glm::ivec2 position, int id, int durability = 0, int animationIndex = -1,
glm::vec2 velocity = {}, float rotation = 0.0f);
Entity(resource::xml::Anm2, glm::ivec2 position, int id, glm::vec2 velocity = {}, float rotation = 0.0f);
bool is_playing(const std::string& name = {});
glm::vec4 null_frame_rect(int = -1, int animationIndex = -1, float time = -1.0f);
+107 -39
View File
@@ -6,6 +6,7 @@
#include "../log.hpp"
#include <array>
#include <cmath>
#include <cstring>
#include <format>
#include <ranges>
@@ -17,6 +18,11 @@ namespace game::resource::xml
{
namespace
{
constexpr std::string_view SOURCE_DOCUMENT_TAG = "SourceDocument";
constexpr auto ANM2_COLOR_MAX = 255.0f;
constexpr auto ANM2_OVERLAY_DRAW_ORDER_BELOW = 0;
constexpr auto ANM2_OVERLAY_DRAW_ORDER_ABOVE = 1;
XMLError document_load(const physfs::Path& path, XMLDocument& document)
{
if (!path.is_valid())
@@ -51,10 +57,21 @@ namespace game::resource::xml
{
int temp{};
auto result = element->QueryIntAttribute(attribute, &temp);
if (result == XML_SUCCESS && value) *value = (temp / 255.0f);
if (result == XML_SUCCESS && value) *value = (temp / ANM2_COLOR_MAX);
return result;
}
XMLElement* source_document_get(XMLElement* rootElement)
{
if (!rootElement) return nullptr;
if (rootElement->Name() && SOURCE_DOCUMENT_TAG == rootElement->Name()) return rootElement;
for (auto child = rootElement->FirstChildElement(); child; child = child->NextSiblingElement())
if (child->Name() && SOURCE_DOCUMENT_TAG == child->Name()) return child;
return nullptr;
}
Anm2::Frame::Interpolation interpolation_get(XMLElement* element)
{
if (!element) return Anm2::Frame::INTERPOLATION_NONE;
@@ -79,6 +96,10 @@ namespace game::resource::xml
query_string_attribute(element, "DefaultAnimation", &parsed.defaultAnimation);
query_string_attribute(element, "Name", &parsed.name);
query_string_attribute(element, "Path", &parsed.path);
query_string_attribute(element, "Vertex", &parsed.vertex);
query_string_attribute(element, "Fragment", &parsed.fragment);
query_string_attribute(element, "Binding", &parsed.binding);
query_string_attribute(element, "Value", &parsed.value);
element->QueryIntAttribute("Id", &parsed.id);
element->QueryIntAttribute("LayerId", &parsed.layerID);
@@ -87,6 +108,8 @@ namespace game::resource::xml
element->QueryIntAttribute("EventId", &parsed.eventID);
element->QueryIntAttribute("RegionId", &parsed.regionID);
element->QueryIntAttribute("GroupId", &parsed.groupID);
element->QueryIntAttribute("SoundId", &parsed.soundID);
element->QueryIntAttribute("Index", &parsed.index);
element->QueryIntAttribute("AtFrame", &parsed.atFrame);
element->QueryIntAttribute("FrameNum", &parsed.frameNum);
element->QueryIntAttribute("Fps", &parsed.fps);
@@ -97,6 +120,8 @@ namespace game::resource::xml
element->QueryFloatAttribute("YCrop", &parsed.crop.y);
element->QueryFloatAttribute("XPosition", &parsed.position.x);
element->QueryFloatAttribute("YPosition", &parsed.position.y);
element->QueryFloatAttribute("XOffset", &parsed.position.x);
element->QueryFloatAttribute("YOffset", &parsed.position.y);
element->QueryFloatAttribute("XPivot", &parsed.pivot.x);
element->QueryFloatAttribute("YPivot", &parsed.pivot.y);
element->QueryFloatAttribute("Width", &parsed.size.x);
@@ -112,11 +137,18 @@ namespace game::resource::xml
query_color_attribute(element, "RedOffset", &parsed.colorOffset.r);
query_color_attribute(element, "GreenOffset", &parsed.colorOffset.g);
query_color_attribute(element, "BlueOffset", &parsed.colorOffset.b);
query_color_attribute(element, "Alpha", &parsed.tint.a);
element->QueryBoolAttribute("Visible", &parsed.isVisible);
element->QueryBoolAttribute("ShowRect", &parsed.isShowRect);
element->QueryBoolAttribute("Loop", &parsed.isLoop);
element->QueryBoolAttribute("Expanded", &parsed.isExpanded);
element->QueryBoolAttribute("IsExpanded", &parsed.isExpanded);
element->QueryBoolAttribute("Enabled", &parsed.isEnabled);
if (auto drawOrder = element->Attribute("DrawOrder"))
parsed.index = std::strcmp(drawOrder, "Below") == 0 ? ANM2_OVERLAY_DRAW_ORDER_BELOW
: ANM2_OVERLAY_DRAW_ORDER_ABOVE;
}
Anm2::Element element_parse(XMLElement* element)
@@ -139,32 +171,12 @@ namespace game::resource::xml
Anm2::Element::Type Anm2::Element::type_get(std::string_view tag)
{
static constexpr std::array<std::pair<Type, std::string_view>, TYPE_COUNT - 1> TAGS{{
{ACTOR, "AnimatedActor"},
{INFO, "Info"},
{CONTENT, "Content"},
{SPRITESHEETS, "Spritesheets"},
{SPRITESHEET, "Spritesheet"},
{REGION, "Region"},
{LAYERS, "Layers"},
{LAYER_ELEMENT, "Layer"},
{NULLS, "Nulls"},
{NULL_ELEMENT, "Null"},
{EVENTS, "Events"},
{EVENT, "Event"},
{SOUNDS, "Sounds"},
{SOUND_ELEMENT, "Sound"},
{ANIMATIONS, "Animations"},
{ANIMATION, "Animation"},
{ROOT_ANIMATION, "RootAnimation"},
{LAYER_ANIMATIONS, "LayerAnimations"},
{LAYER_ANIMATION, "LayerAnimation"},
{NULL_ANIMATIONS, "NullAnimations"},
{NULL_ANIMATION, "NullAnimation"},
{GROUP, "Group"},
{TRIGGERS, "Triggers"},
{TRIGGER_ELEMENT, "Trigger"},
{FRAME, "Frame"},
if (tag == SOURCE_DOCUMENT_TAG) return ACTOR;
static constexpr std::array<std::pair<Type, std::string_view>, TYPE_COUNT> TAGS{{
#define X(symbol, elementTag) {symbol, elementTag},
GAME_ANM2_ELEMENT_TYPES(X)
#undef X
}};
for (auto [type, typeTag] : TAGS)
@@ -215,8 +227,21 @@ namespace game::resource::xml
auto element = document.RootElement();
if (!element) return;
if (auto sourceElement = source_document_get(element)) element = sourceElement;
root = element_parse(element);
auto tagged_children_parse = [](auto&& self, XMLElement* parent, const char* tag, auto&& callback) -> void
{
for (auto child = parent->FirstChildElement(); child; child = child->NextSiblingElement())
{
auto name = child->Name();
if (name && std::strcmp(name, tag) == 0)
callback(child);
else if (name && std::strcmp(name, "Group") == 0)
self(self, child, tag, callback);
}
};
auto parse_frame = [&](XMLElement* element, Type type, int spritesheetID = -1)
{
Frame frame{};
@@ -288,11 +313,12 @@ namespace game::resource::xml
return frame;
};
auto parse_item = [&](XMLElement* element, Type type, int& id)
auto parse_item = [&](XMLElement* element, Type type, int& id, int groupID = -1)
{
Item item{};
if (!element) return item;
item.groupID = groupID;
if (type == LAYER) element->QueryIntAttribute("LayerId", &id);
if (type == NULL_) element->QueryIntAttribute("NullId", &id);
@@ -323,23 +349,41 @@ namespace game::resource::xml
if (auto rootAnimationElement = element->FirstChildElement("RootAnimation"))
animation.rootAnimation = parse_item(rootAnimationElement, ROOT, id);
auto track_elements_parse = [](auto&& self, XMLElement* parent, const char* tag, auto&& callback) -> void
auto track_children_parse = [&](auto&& self, XMLElement* parent, const char* tag, int parentGroupID,
auto&& callback) -> void
{
for (auto child = parent->FirstChildElement(); child; child = child->NextSiblingElement())
{
auto name = child->Name();
if (name && std::strcmp(name, tag) == 0)
callback(child);
{
callback(child, parentGroupID);
}
else if (name && std::strcmp(name, "Group") == 0)
self(self, child, tag, callback);
{
Group group{};
int groupRootID{-1};
group.parent = parentGroupID;
child->QueryBoolAttribute("Visible", &group.isVisible);
if (auto rootAnimationElement = child->FirstChildElement("RootAnimation"))
group.rootAnimation = parse_item(rootAnimationElement, GROUP, groupRootID);
auto groupID = (int)animation.groups.size();
animation.groups.emplace_back(std::move(group));
self(self, child, tag, groupID, callback);
}
else if (name && (std::strcmp(name, "LayerAnimationGroups") == 0 ||
std::strcmp(name, "NullAnimationGroups") == 0))
self(self, child, tag, parentGroupID, callback);
}
};
if (auto layerAnimationsElement = element->FirstChildElement("LayerAnimations"))
{
track_elements_parse(track_elements_parse, layerAnimationsElement, "LayerAnimation", [&](XMLElement* child)
track_children_parse(track_children_parse, layerAnimationsElement, "LayerAnimation", -1,
[&](XMLElement* child, int groupID)
{
auto layerAnimation = parse_item(child, LAYER, id);
id = -1;
auto layerAnimation = parse_item(child, LAYER, id, groupID);
animation.layerOrder.push_back(id);
animation.layerAnimations.emplace(id, std::move(layerAnimation));
});
@@ -347,9 +391,12 @@ namespace game::resource::xml
if (auto nullAnimationsElement = element->FirstChildElement("NullAnimations"))
{
track_elements_parse(track_elements_parse, nullAnimationsElement, "NullAnimation", [&](XMLElement* child)
track_children_parse(track_children_parse, nullAnimationsElement, "NullAnimation", -1,
[&](XMLElement* child, int groupID)
{
auto nullAnimation = parse_item(child, NULL_, id);
id = -1;
auto nullAnimation = parse_item(child, NULL_, id, groupID);
animation.nullOrder.push_back(id);
animation.nullAnimations.emplace(id, std::move(nullAnimation));
});
}
@@ -480,9 +527,11 @@ namespace game::resource::xml
{
query_string_attribute(animationsElement, "DefaultAnimation", &defaultAnimation);
for (auto child = animationsElement->FirstChildElement("Animation"); child;
child = child->NextSiblingElement("Animation"))
bool isDefaultAnimationParsed{};
tagged_children_parse(tagged_children_parse, animationsElement, "Animation", [&](XMLElement* child)
{
if (isDefaultAnimationParsed) return;
if ((this->flags & DEFAULT_ANIMATION_ONLY) != 0)
{
std::string name{};
@@ -490,12 +539,23 @@ namespace game::resource::xml
if (name == defaultAnimation)
{
animations.emplace_back(parse_animation(child));
break;
isDefaultAnimationParsed = true;
return;
}
}
else
animations.emplace_back(parse_animation(child));
}
});
if ((this->flags & DEFAULT_ANIMATION_ONLY) != 0 && animations.empty())
tagged_children_parse(tagged_children_parse, animationsElement, "Animation", [&](XMLElement* child)
{
if (!isDefaultAnimationParsed)
{
animations.emplace_back(parse_animation(child));
isDefaultAnimationParsed = true;
}
});
for (int i = 0; i < (int)animations.size(); i++)
{
@@ -552,6 +612,14 @@ namespace game::resource::xml
bool Anm2::is_valid() const { return isValid; }
bool Anm2::is_rect_valid(glm::vec4 rect)
{
static constexpr auto ZERO_FLOAT = 0.0f;
return std::isfinite(rect.x) && std::isfinite(rect.y) && std::isfinite(rect.z) && std::isfinite(rect.w) &&
rect.z > ZERO_FLOAT && rect.w > ZERO_FLOAT;
}
int Anm2::item_length(const Item* item)
{
if (!item) return -1;
+56 -26
View File
@@ -18,6 +18,40 @@
#include "../util/physfs.hpp"
#define GAME_ANM2_ELEMENT_TYPES(X) \
X(UNKNOWN, "") \
X(ACTOR, "AnimatedActor") \
X(INFO, "Info") \
X(CONTENT, "Content") \
X(SPRITESHEETS, "Spritesheets") \
X(SPRITESHEET, "Spritesheet") \
X(OVERLAY, "Overlay") \
X(SHADER, "Shader") \
X(UNIFORM, "Uniform") \
X(COMPONENT, "Component") \
X(REGION, "Region") \
X(LAYERS, "Layers") \
X(LAYER_ELEMENT, "Layer") \
X(NULLS, "Nulls") \
X(NULL_ELEMENT, "Null") \
X(EVENTS, "Events") \
X(EVENT, "Event") \
X(SOUNDS, "Sounds") \
X(SOUND_ELEMENT, "Sound") \
X(ANIMATIONS, "Animations") \
X(ANIMATION, "Animation") \
X(ROOT_ANIMATION, "RootAnimation") \
X(LAYER_ANIMATIONS, "LayerAnimations") \
X(LAYER_ANIMATION_GROUPS, "LayerAnimationGroups") \
X(LAYER_ANIMATION, "LayerAnimation") \
X(NULL_ANIMATIONS, "NullAnimations") \
X(NULL_ANIMATION_GROUPS, "NullAnimationGroups") \
X(NULL_ANIMATION, "NullAnimation") \
X(GROUP, "Group") \
X(TRIGGERS, "Triggers") \
X(TRIGGER_ELEMENT, "Trigger") \
X(FRAME, "Frame")
namespace game::resource::xml
{
class Anm2
@@ -27,6 +61,7 @@ namespace game::resource::xml
{
NONE,
ROOT,
GROUP,
LAYER,
NULL_,
TRIGGER
@@ -45,32 +80,9 @@ namespace game::resource::xml
{
enum Type
{
UNKNOWN,
ACTOR,
INFO,
CONTENT,
SPRITESHEETS,
SPRITESHEET,
REGION,
LAYERS,
LAYER_ELEMENT,
NULLS,
NULL_ELEMENT,
EVENTS,
EVENT,
SOUNDS,
SOUND_ELEMENT,
ANIMATIONS,
ANIMATION,
ROOT_ANIMATION,
LAYER_ANIMATIONS,
LAYER_ANIMATION,
NULL_ANIMATIONS,
NULL_ANIMATION,
GROUP,
TRIGGERS,
TRIGGER_ELEMENT,
FRAME,
#define X(symbol, tag) symbol,
GAME_ANM2_ELEMENT_TYPES(X)
#undef X
TYPE_COUNT
};
@@ -82,6 +94,10 @@ namespace game::resource::xml
std::string defaultAnimation{};
std::string name{};
std::string path{};
std::string vertex{};
std::string fragment{};
std::string binding{};
std::string value{};
int id{-1};
int layerID{-1};
@@ -90,6 +106,8 @@ namespace game::resource::xml
int eventID{-1};
int regionID{-1};
int groupID{-1};
int soundID{-1};
int index{-1};
int atFrame{-1};
int frameNum{};
int fps{30};
@@ -109,6 +127,7 @@ namespace game::resource::xml
bool isShowRect{};
bool isLoop{};
bool isExpanded{true};
bool isEnabled{true};
Element* child_get(Type, std::size_t index = 0);
const Element* child_get(Type, std::size_t index = 0) const;
@@ -264,9 +283,17 @@ namespace game::resource::xml
struct Item
{
std::vector<Frame> frames{};
int groupID{-1};
bool isVisible{};
};
struct Group
{
Item rootAnimation{};
int parent{-1};
bool isVisible{true};
};
struct Animation
{
std::string name{"New Animation"};
@@ -274,9 +301,11 @@ namespace game::resource::xml
bool isLoop{};
Item rootAnimation{};
std::vector<Group> groups{};
std::unordered_map<int, Item> layerAnimations{};
std::vector<int> layerOrder{};
std::map<int, Item> nullAnimations{};
std::vector<int> nullOrder{};
Item triggers{};
};
@@ -311,6 +340,7 @@ namespace game::resource::xml
Anm2(const util::physfs::Path&, Flags = 0);
bool is_valid() const;
static bool is_rect_valid(glm::vec4);
static int item_length(const Item*);
static float interpolation_apply(Frame::Interpolation, float);
static Frame frame_generate(Item&, float, Type = NONE, int id = -1,
+7 -5
View File
@@ -166,14 +166,16 @@ namespace game::resource
void Audio::play(bool isLoop) const
{
if (!internal) return;
auto audio = internal;
if (!audio) return;
auto mixer = mixer_get();
if (!mixer) return;
if (track && MIX_GetTrackMixer(track) != mixer)
if (!isLoop)
{
MIX_DestroyTrack(track);
track = nullptr;
MIX_PlayAudio(mixer, audio.get());
return;
}
if (!track)
@@ -182,7 +184,7 @@ namespace game::resource
if (!track) return;
}
MIX_SetTrackAudio(track, internal.get());
if (!MIX_SetTrackAudio(track, audio.get())) return;
SDL_PropertiesID options = 0;
+1
View File
@@ -19,6 +19,7 @@ namespace game::resource
static constexpr auto HEADER_1 = 40;
static constexpr auto HEADER_2 = 30;
static constexpr auto HEADER_3 = 24;
static constexpr auto HUGE = 80;
Font() = default;
Font(const std::filesystem::path&, float = NORMAL);
+132 -70
View File
@@ -17,15 +17,16 @@ namespace game::resource::xml
std::string* Schema::AnimationEntryCollection::get()
{
if (empty()) return nullptr;
return &at(util::vector::random_index_weighted(*this, [](const auto& entry) { return entry.weight; })).animation;
auto index = util::vector::random_index_weighted(*this, [](const auto& entry) { return entry.weight; });
return index < 0 || index >= (int)size() ? nullptr : &at(index).animation;
}
const Audio* Schema::SoundEntryCollection::get() const
{
if (empty()) return nullptr;
auto index =
util::vector::random_index_weighted(*this, [](const auto* entry) { return entry ? entry->weight : 0.0f; });
return index < 0 || !at(index) ? nullptr : &at(index)->sound;
util::vector::random_index_weighted(*this, [](const auto& entry) { return entry.weight; });
return index < 0 || index >= (int)size() ? nullptr : &at(index).sound;
}
void Schema::SoundEntryCollection::play() const
@@ -34,8 +35,8 @@ namespace game::resource::xml
auto audio = get();
if (!audio) return;
for (auto* entry : *this)
if (entry && &entry->sound != audio) entry->sound.stop();
for (auto& entry : *this)
if (&entry.sound != audio) entry.sound.stop();
audio->play();
}
@@ -67,6 +68,11 @@ namespace game::resource::xml
return result;
}
bool element_index_is_valid(const Schema& schema, int index)
{
return index >= 0 && index < (int)schema.elements.size();
}
XMLError query_color_attribute(XMLElement* element, const char* attribute, float* value)
{
int temp{};
@@ -137,8 +143,10 @@ namespace game::resource::xml
X(Color) \
X(Delay) \
X(Oscillation) \
X(Scene) \
X(Screenshake) \
X(Shake) \
X(Silent) \
X(Small) \
X(Sound)
@@ -231,6 +239,7 @@ namespace game::resource::xml
Schema::Anm2Entry baseAnm2{};
int itemSpritesheetID{};
bool isDialogue{};
bool isSave{};
};
std::string resource_path_get(const Context& context, const std::string& rootPath, const std::string& path)
@@ -380,7 +389,13 @@ namespace game::resource::xml
#undef GAME_SCHEMA_QUERY_ATTRIBUTE_STRING_TARGET
#undef GAME_SCHEMA_QUERY_ATTRIBUTE_STRING
if (element.type == Schema::Element::POOL_ENTRY) query_string(xml, "Entry", element.idString);
if (element.type == Schema::Element::POOL_ENTRY)
{
query_string(xml, "Label", element.idString);
if (element.idString.empty()) query_string(xml, "Entry", element.idString);
}
else if (element.type == Schema::Element::HOOK)
query_string(xml, "Entry", element.idString);
if (!element.textureRootPath.empty()) resourceContext.textureRootPath = element.textureRootPath;
if (!element.anm2RootPath.empty())
@@ -520,10 +535,20 @@ namespace game::resource::xml
if (symbol == "typeString" && element.type == Schema::Element::EFFECT)
return true;
if (symbol == "idString" && element.type == Schema::Element::POOL_ENTRY)
{
set_string("Label", element.idString);
return true;
}
if (symbol == "idString" && element.type == Schema::Element::HOOK)
{
set_string("Entry", element.idString);
return true;
}
if (symbol == "id" && context.isSave && element.type == Schema::Element::ITEM)
{
xml->SetAttribute("ID", element.id);
return true;
}
#define GAME_SCHEMA_WRITE_ATTRIBUTE_STRING(KEY, KEY_2, KEY_3, MEMBER) \
set_string(KEY, resource_string_get(#MEMBER, element.MEMBER));
#define GAME_SCHEMA_WRITE_ATTRIBUTE_STRING_TARGET(KEY, KEY_2, KEY_3, MEMBER) \
@@ -603,10 +628,14 @@ namespace game::resource::xml
if (!source.anm2RootPath.empty()) context.anm2RootPath = source.anm2RootPath;
if (!source.soundRootPath.empty()) context.soundRootPath = source.soundRootPath;
if (!source.fontRootPath.empty()) context.fontRootPath = source.fontRootPath;
if (source.type == Schema::Element::SAVE) context.isSave = true;
element_attributes_write(document, xml, source, context);
for (auto child : source.children)
{
if (!element_index_is_valid(schema, child)) continue;
xml->InsertEndChild(element_write(document, schema, child, context));
}
return xml;
}
@@ -619,14 +648,15 @@ namespace game::resource::xml
void sound_entry_add(Schema::Element& element, Schema::SoundEntryCollection& collection)
{
if (!element.soundEntry.is_valid()) return;
element.soundEntry.weight = element.weight == 0.0f ? 1.0f : element.weight;
collection.push_back(&element.soundEntry);
auto entry = element.soundEntry;
entry.weight = element.weight == 0.0f ? 1.0f : element.weight;
collection.push_back(std::move(entry));
}
void sound_entries_add(const Schema::SoundEntryCollection& source, Schema::SoundEntryCollection& collection)
{
for (auto* entry : source)
if (entry) collection.push_back(entry);
for (auto& entry : source)
if (entry.is_valid()) collection.push_back(entry);
}
bool element_attribute_has(const Schema::Element& element, std::string_view attribute)
@@ -704,10 +734,16 @@ namespace game::resource::xml
X(RELEASE, soundRelease) \
X(SELECT, soundSelect) \
X(SUMMON, soundSummon) \
X(THROW, soundThrow) \
X(RETURN_, soundReturn) \
X(UPGRADE, soundUpgrade) \
X(UPGRADE_FAIL, soundUpgradeFail)
X(THROW, soundThrow)
#define GAME_SCHEMA_ACTION_ANIMATIONS(X) \
X(CLICK, animationClick) \
X(DISPOSE, animationDispose) \
X(GRAB, animationGrab) \
X(HOVER, animationHover) \
X(IDLE, animationIdle) \
X(PAN, animationPan) \
X(ZOOM, animationZoom)
void cache_refresh(Schema& schema)
{
@@ -725,8 +761,6 @@ namespace game::resource::xml
schema.baseAnm2 = {};
schema.font = {};
schema.iconsFont = {};
schema.durability = 2;
schema.quantityMax = 99;
schema.rounding = 0.0f;
auto* root = schema.root();
@@ -755,7 +789,7 @@ namespace game::resource::xml
root->animationIdle = {};
root->animationClick = {};
root->animationPan = {};
root->animationReturn = {};
root->animationDispose = {};
root->animationZoom = {};
root->soundBlip = {};
root->soundBounce = {};
@@ -772,20 +806,17 @@ namespace game::resource::xml
root->soundLevelUpReady = {};
root->soundOpen = {};
root->soundRelease = {};
root->soundReturn = {};
root->soundSelect = {};
root->soundSummon = {};
root->soundThrow = {};
root->soundUpgrade = {};
root->soundUpgradeFail = {};
root->animationChew = {};
for (auto& element : schema.elements)
{
element.soundEntries = {};
for (auto childID : element.children)
{
auto& child = schema.elements.at(childID);
if (!element_index_is_valid(schema, childID)) continue;
auto& child = schema.elements[childID];
if (child.type == Schema::Element::SOUND) sound_entry_add(child, element.soundEntries);
}
}
@@ -810,10 +841,8 @@ namespace game::resource::xml
animation_entry_add(*element, root->animationPan);
if (auto* element = schema.child_get(*animations, Schema::Element::ZOOM))
animation_entry_add(*element, root->animationZoom);
if (auto* element = schema.child_get(*animations, Schema::Element::RETURN_))
animation_entry_add(*element, root->animationReturn);
if (auto* element = schema.child_get(*animations, Schema::Element::CHEW))
root->animationChew = element->animation;
if (auto* element = schema.child_get(*animations, Schema::Element::DISPOSE))
animation_entry_add(*element, root->animationDispose);
}
if (auto* sounds = schema.child_get(*root, Schema::Element::SOUNDS))
@@ -848,21 +877,21 @@ namespace game::resource::xml
sound_entry_add(*element, root->soundOpen);
for (auto* element : schema.children_get(*sounds, Schema::Element::RELEASE))
sound_entry_add(*element, root->soundRelease);
for (auto* element : schema.children_get(*sounds, Schema::Element::RETURN_))
sound_entry_add(*element, root->soundReturn);
for (auto* element : schema.children_get(*sounds, Schema::Element::SELECT))
sound_entry_add(*element, root->soundSelect);
for (auto* element : schema.children_get(*sounds, Schema::Element::SUMMON))
sound_entry_add(*element, root->soundSummon);
for (auto* element : schema.children_get(*sounds, Schema::Element::THROW))
sound_entry_add(*element, root->soundThrow);
for (auto* element : schema.children_get(*sounds, Schema::Element::UPGRADE))
sound_entry_add(*element, root->soundUpgrade);
for (auto* element : schema.children_get(*sounds, Schema::Element::UPGRADE_FAIL))
sound_entry_add(*element, root->soundUpgradeFail);
}
if (auto* actions = schema.child_get(*root, Schema::Element::ACTIONS))
{
#define GAME_SCHEMA_ACTION_ANIMATION_CACHE(TYPE, MEMBER) \
if (auto* action = schema.child_get(*actions, Schema::Element::TYPE)) \
animation_entry_add(*action, root->MEMBER);
GAME_SCHEMA_ACTION_ANIMATIONS(GAME_SCHEMA_ACTION_ANIMATION_CACHE)
#undef GAME_SCHEMA_ACTION_ANIMATION_CACHE
#define GAME_SCHEMA_ACTION_SOUND_CACHE(TYPE, MEMBER) \
for (auto* action : schema.children_get(*actions, Schema::Element::TYPE)) \
for (auto* element : schema.children_get(*action, Schema::Element::SOUND)) \
@@ -871,6 +900,7 @@ namespace game::resource::xml
#undef GAME_SCHEMA_ACTION_SOUND_CACHE
}
#undef GAME_SCHEMA_ACTION_SOUNDS
#undef GAME_SCHEMA_ACTION_ANIMATIONS
if (auto* textPlayback = schema.child_get(*root, Schema::Element::TEXT_PLAYBACK))
if (auto* blip = schema.child_get(*textPlayback, Schema::Element::BLIP))
sound_entries_add(blip->soundEntries, root->soundBlip);
@@ -879,37 +909,34 @@ namespace game::resource::xml
for (auto* element : schema.get_all(Schema::Element::AREA))
schema.areas.push_back({.texture = element->textureEntry.texture,
.anm2 = element->anm2Entry.anm2,
.anm2 = element->anm2Entry.is_valid() ? element->anm2Entry.anm2 : schema.baseAnm2,
.gravity = element->gravity == 0.0f ? 0.95f : element->gravity,
.friction = element->friction == 0.0f ? 0.80f : element->friction,
.airResistance = element->airResistance == 0.0f ? 0.975f : element->airResistance});
if (root->type == Schema::Element::AREAS && schema.areas.empty()) schema.areas.emplace_back();
if (root->type == Schema::Element::AREAS && schema.areas.empty())
schema.areas.emplace_back(Schema::AreaEntry{.anm2 = schema.baseAnm2});
for (auto* element : schema.get_all(Schema::Element::CATEGORY))
schema.categories.push_back({.name = element->name, .isEdible = element->isEdible});
schema.categories.push_back({.name = element->name, .useMode = element->useMode});
for (auto* element : schema.get_all(Schema::Element::RARITY))
{
schema.rarityIDs.push_back((int)schema.rarities.size());
schema.rarities.push_back({.name = element->name.empty() ? "???" : element->name,
.weight = element->weight,
.isHidden = element->isHidden,
.isHiddenInCatalog = element->isHiddenInCatalog,
.sound = element->soundEntry.sound});
}
for (auto* element : schema.get_all(Schema::Element::FLAVOR))
schema.flavors.push_back({.name = element->name.empty() ? "???" : element->name});
if (auto* itemsRoot = schema.get(Schema::Element::ITEMS))
{
if (itemsRoot->durability != 0) schema.durability = itemsRoot->durability;
if (itemsRoot->quantityMax != 0) schema.quantityMax = itemsRoot->quantityMax;
}
auto itemElements = schema.get_all(Schema::Element::ITEM);
for (auto* element : itemElements)
{
if (element->parent < 0 || schema.elements[element->parent].type != Schema::Element::ITEMS) continue;
if (!element_index_is_valid(schema, element->parent) ||
schema.elements[element->parent].type != Schema::Element::ITEMS)
continue;
auto itemID = (int)schema.stringToIDMap.size();
schema.stringToIDMap[element->name] = itemID;
schema.idToStringMap[itemID] = element->name;
@@ -917,7 +944,9 @@ namespace game::resource::xml
for (auto* element : itemElements)
{
if (element->parent < 0 || schema.elements[element->parent].type != Schema::Element::ITEMS) continue;
if (!element_index_is_valid(schema, element->parent) ||
schema.elements[element->parent].type != Schema::Element::ITEMS)
continue;
Schema::ItemEntry item{};
item.name = element->name.empty() ? "???" : element->name;
item.description = element->description.empty() ? "???" : element->description;
@@ -931,21 +960,10 @@ namespace game::resource::xml
item.capacityBonus = element->capacityBonus;
item.isDigestionBonus = element->digestionBonus != 0.0f;
item.digestionBonus = element->digestionBonus;
item.isEatSpeedBonus = element->eatSpeedBonus != 0.0f;
item.eatSpeedBonus = element->eatSpeedBonus;
item.isGravity = element->gravity != 0.0f;
item.gravity = element->gravity;
item.isDurability = element->durability != 0;
item.durability = element->durability;
item.isColor = element->color != glm::vec3{};
item.color = element->color;
if (!element->upgradeID.empty() && schema.stringToIDMap.contains(element->upgradeID))
{
item.isUpgradeID = true;
item.upgradeID = schema.stringToIDMap[element->upgradeID];
}
item.isUpgradeCount = element->upgradeCount != 0;
item.upgradeCount = element->upgradeCount;
item.isToggleSpritesheet = element->isToggleSpritesheet;
schema.pools[item.rarityID].push_back((int)schema.items.size());
schema.anm2s.push_back(element->anm2Entry.anm2);
@@ -1072,6 +1090,7 @@ namespace game::resource::xml
root.measurementSystem = "Metric";
root.volume = 50;
root.isAudioMuted = false;
root.isDeveloperConsoleEnabled = false;
root.color = {0.120f, 0.515f, 0.115f};
root.windowSize = {1600, 900};
root.isUseCharacterColor = true;
@@ -1108,7 +1127,10 @@ namespace game::resource::xml
auto index = (int)elements.size();
elements.emplace_back(std::move(element));
elementsByType[type].push_back(index);
if (parent >= 0) elements.at(parent).children.emplace_back(index);
if (parent >= 0 && parent < index)
elements[parent].children.emplace_back(index);
else
elements[index].parent = -1;
isValid = true;
return elements.back();
}
@@ -1139,7 +1161,8 @@ namespace game::resource::xml
for (auto& element : kept)
{
if (element.parent >= 0) element.parent = remap[element.parent];
if (element.parent >= 0)
element.parent = element.parent < (int)remap.size() ? remap[element.parent] : -1;
std::vector<int> children{};
children.reserve(element.children.size());
@@ -1192,11 +1215,11 @@ namespace game::resource::xml
if (parent < 0 || parent >= (int)elements.size()) return false;
if (index == parent || index == beforeSibling) return false;
for (auto ancestor = parent; ancestor >= 0; ancestor = elements[ancestor].parent)
for (auto ancestor = parent; element_index_is_valid(*this, ancestor); ancestor = elements[ancestor].parent)
if (ancestor == index) return false;
auto oldParent = elements[index].parent;
if (oldParent >= 0)
if (element_index_is_valid(*this, oldParent))
{
auto& oldSiblings = elements[oldParent].children;
oldSiblings.erase(std::remove(oldSiblings.begin(), oldSiblings.end(), index), oldSiblings.end());
@@ -1228,14 +1251,16 @@ namespace game::resource::xml
{
auto it = elementsByType.find(type);
if (it == elementsByType.end() || index < 0 || index >= (int)it->second.size()) return nullptr;
return &elements.at(it->second.at(index));
auto elementIndex = it->second[index];
return element_index_is_valid(*this, elementIndex) ? &elements[elementIndex] : nullptr;
}
const Schema::Element* Schema::get(Element::Type type, int index) const
{
auto it = elementsByType.find(type);
if (it == elementsByType.end() || index < 0 || index >= (int)it->second.size()) return nullptr;
return &elements.at(it->second.at(index));
auto elementIndex = it->second[index];
return element_index_is_valid(*this, elementIndex) ? &elements[elementIndex] : nullptr;
}
std::vector<Schema::Element*> Schema::get_all(Element::Type type)
@@ -1245,7 +1270,7 @@ namespace game::resource::xml
if (it == elementsByType.end()) return result;
result.reserve(it->second.size());
for (auto index : it->second)
result.emplace_back(&elements.at(index));
if (element_index_is_valid(*this, index)) result.emplace_back(&elements[index]);
return result;
}
@@ -1256,7 +1281,7 @@ namespace game::resource::xml
if (it == elementsByType.end()) return result;
result.reserve(it->second.size());
for (auto index : it->second)
result.emplace_back(&elements.at(index));
if (element_index_is_valid(*this, index)) result.emplace_back(&elements[index]);
return result;
}
@@ -1264,7 +1289,8 @@ namespace game::resource::xml
{
for (auto child : element.children)
{
auto& candidate = elements.at(child);
if (!element_index_is_valid(*this, child)) continue;
auto& candidate = elements[child];
if (candidate.type != type) continue;
if (index-- == 0) return &candidate;
}
@@ -1275,7 +1301,8 @@ namespace game::resource::xml
{
for (auto child : element.children)
{
auto& candidate = elements.at(child);
if (!element_index_is_valid(*this, child)) continue;
auto& candidate = elements[child];
if (candidate.type != type) continue;
if (index-- == 0) return &candidate;
}
@@ -1286,7 +1313,10 @@ namespace game::resource::xml
{
std::vector<Element*> result{};
for (auto child : element.children)
if (auto& candidate = elements.at(child); candidate.type == type) result.emplace_back(&candidate);
{
if (!element_index_is_valid(*this, child)) continue;
if (auto& candidate = elements[child]; candidate.type == type) result.emplace_back(&candidate);
}
return result;
}
@@ -1294,7 +1324,10 @@ namespace game::resource::xml
{
std::vector<const Element*> result{};
for (auto child : element.children)
if (auto& candidate = elements.at(child); candidate.type == type) result.emplace_back(&candidate);
{
if (!element_index_is_valid(*this, child)) continue;
if (auto& candidate = elements[child]; candidate.type == type) result.emplace_back(&candidate);
}
return result;
}
@@ -1308,7 +1341,7 @@ namespace game::resource::xml
Schema::Element* Schema::dialogue_entry_get(const std::string& id)
{
auto it = entryIDMap.find(id);
return it == entryIDMap.end() ? nullptr : &elements.at(it->second);
return it == entryIDMap.end() || !element_index_is_valid(*this, it->second) ? nullptr : &elements[it->second];
}
Schema::Element* Schema::dialogue_entry_get(Element& reference)
@@ -1330,8 +1363,9 @@ namespace game::resource::xml
{
auto poolIt = poolMap.find(poolID);
if (poolIt == poolMap.end()) return nullptr;
if (!element_index_is_valid(*this, poolIt->second)) return nullptr;
auto& pool = elements.at(poolIt->second);
auto& pool = elements[poolIt->second];
auto entries = children_get(pool, Element::POOL_ENTRY);
if (entries.empty()) return nullptr;
@@ -1346,6 +1380,34 @@ namespace game::resource::xml
return dialogue_pool_entry_get(reference.poolID);
}
Schema::Element* Schema::dialogue_hook_get(Element::Type type, int value)
{
auto* rootElement = root();
auto* hooks = rootElement ? child_get(*rootElement, Element::HOOKS) : nullptr;
if (!hooks) return nullptr;
Element* entryHook{};
Element* poolHook{};
for (auto* hook : children_get(*hooks, Element::HOOK))
{
if (!hook || type_get(hook->typeString.c_str()) != type) continue;
if (value != 0 && hook->value == value && (!hook->idString.empty() || !hook->poolID.empty())) return hook;
if (hook->value != 0) continue;
if (!entryHook && !hook->idString.empty()) entryHook = hook;
if (!poolHook && !hook->poolID.empty()) poolHook = hook;
}
return entryHook ? entryHook : poolHook;
}
Schema::Element* Schema::dialogue_hook_entry_get(Element::Type type, int value)
{
auto* hook = dialogue_hook_get(type, value);
if (!hook) return nullptr;
if (!hook->idString.empty()) return dialogue_entry_get(hook->idString);
return dialogue_pool_entry_get(hook->poolID);
}
int Schema::dialogue_entry_id_get(const std::string& id) const
{
auto it = entryIDMap.find(id);
@@ -1367,7 +1429,7 @@ namespace game::resource::xml
Schema::Element* Schema::item_get(const std::string& id)
{
auto it = itemIDMap.find(id);
return it == itemIDMap.end() ? nullptr : &elements.at(it->second);
return it == itemIDMap.end() || !element_index_is_valid(*this, it->second) ? nullptr : &elements[it->second];
}
int Schema::item_id_get(const std::string& id) const
+27 -46
View File
@@ -31,7 +31,6 @@
X(Category, CATEGORY) \
X(Character, CHARACTER) \
X(CheatsActivated, CHEATS_ACTIVATED) \
X(Chew, CHEW) \
X(Choice, CHOICE) \
X(Click, CLICK) \
X(Close, CLOSE) \
@@ -44,6 +43,7 @@
X(DelayCharacters, DELAY_CHARACTERS) \
X(Dialogue, DIALOGUE) \
X(Digest, DIGEST) \
X(Discovered, DISCOVERED) \
X(Dispose, DISPOSE) \
X(Eat, EAT) \
X(EatArea, EAT_AREA) \
@@ -74,6 +74,8 @@
X(Help, HELP) \
X(Home, HOME) \
X(Hover, HOVER) \
X(Hook, HOOK) \
X(Hooks, HOOKS) \
X(Hurt, HURT) \
X(Idle, IDLE) \
X(IdleFull, IDLE_FULL) \
@@ -137,8 +139,6 @@
X(Trigger, TRIGGER) \
X(Triggers, TRIGGERS) \
X(Unknown, UNKNOWN) \
X(Upgrade, UPGRADE) \
X(UpgradeFail, UPGRADE_FAIL) \
X(Warning, WARNING) \
X(Zoom, ZOOM)
#define GAME_SCHEMA_ELEMENT_FIELDS(X, DATA) \
@@ -163,6 +163,8 @@
"", "", atFrame) \
X(DATA, MEMBER_ATTRIBUTE, baseAnm2, std::string, "Base ANM2", \
STRING, "BaseAnm2", "", "", baseAnm2) \
X(DATA, MEMBER_ATTRIBUTE, baseSpawnDelayTicks, int, "Base Spawn Delay Ticks", INT_RW, \
"BaseSpawnDelayTicks", "", "", baseSpawnDelayTicks) \
X(DATA, MEMBER_ATTRIBUTE, blinkChance, float, "Blink Chance", FLOAT, \
"BlinkChance", "", "", blinkChance) \
X(DATA, MEMBER_ATTRIBUTE, blipDelayTicks, int, "Blip Delay Ticks", INT_RW, "BlipDelayTicks", "", "", \
@@ -222,14 +224,6 @@
"DigestionTimer", "", "", digestionTimer) \
X(DATA, MEMBER_ATTRIBUTE, digestionTimerMax, int, "Digestion Timer Max", INT_RW, \
"DigestionTimerMax", "", "", digestionTimerMax) \
X(DATA, MEMBER_ATTRIBUTE, durability, int, "Durability", INT_RW, "Durability", \
"", "", durability) \
X(DATA, MEMBER_ATTRIBUTE, eatSpeedMultiplier, float, "Eat Speed Multiplier", \
FLOAT, "EatSpeedMultiplier", "", "", eatSpeedMultiplier) \
X(DATA, MEMBER_ATTRIBUTE, eatSpeedBonus, float, "Eat Speed Bonus", \
FLOAT, "EatSpeedBonus", "", "", eatSpeedBonus) \
X(DATA, MEMBER_ATTRIBUTE, eatSpeedMaxMultiplier, float, "Eat Speed Max Multiplier", FLOAT, "EatSpeedMaxMultiplier", "", "", eatSpeedMaxMultiplier) \
X(DATA, MEMBER_ATTRIBUTE, eatSpeedMinMultiplier, float, "Eat Speed Min Multiplier", FLOAT, "EatSpeedMinMultiplier", "", "", eatSpeedMinMultiplier) \
X(DATA, MEMBER_ATTRIBUTE, end, int, "End", INT_RW, "End", \
"", "", end) \
X(DATA, MEMBER_ATTRIBUTE, event, std::string, "Event To Activate", STRING, \
@@ -270,16 +264,15 @@
"", "", idleString) \
X(DATA, MEMBER_ATTRIBUTE, interpolated, bool, "Interpolated", BOOL, \
"Interpolated", "", "", interpolated) \
X(DATA, MEMBER_ATTRIBUTE, itemSpawnLimit, int, "Item Spawn Limit", INT_RW, "ItemSpawnLimit", "", "", itemSpawnLimit) \
X(DATA, MEMBER_ATTRIBUTE, isAlternateSpritesheet, bool, "Is Alternate Spritesheet", BOOL, "IsAlternateSpritesheet", "", "", \
isAlternateSpritesheet) \
X(DATA, MEMBER_ATTRIBUTE, isDigesting, bool, "Is Digesting", \
BOOL, "IsDigesting", "", "", isDigesting) \
X(DATA, MEMBER_ATTRIBUTE, isDisableTalk, bool, "Is Disable Talk", BOOL, "IsDisableTalk", "", \
"", isDisableTalk) \
X(DATA, MEMBER_ATTRIBUTE, isEdible, bool, "Is Edible", BOOL, "IsEdible", \
"", "", isEdible) \
X(DATA, MEMBER_ATTRIBUTE, isHidden, bool, "Is Hidden", \
BOOL, "IsHidden", "", "", isHidden) \
X(DATA, MEMBER_ATTRIBUTE, isHiddenInCatalog, bool, "Is Hidden In Catalog", BOOL, "IsHiddenInCatalog", "", "", \
isHiddenInCatalog) \
X(DATA, MEMBER_ATTRIBUTE, isHold, bool, "Is Hold", BOOL, "IsHold", \
"", "", isHold) \
X(DATA, MEMBER_ATTRIBUTE, isPostgame, bool, "Is Postgame", \
@@ -287,6 +280,8 @@
X(DATA, MEMBER_ATTRIBUTE, isToggleSpritesheet, bool, "Is Toggle Spritesheet", BOOL, "IsToggleSpritesheet", "", "", isToggleSpritesheet) \
X(DATA, MEMBER_ATTRIBUTE, isAudioMuted, bool, "Is Audio Muted", BOOL, \
"IsAudioMuted", "", "", isAudioMuted) \
X(DATA, MEMBER_ATTRIBUTE, isDeveloperConsoleEnabled, bool, "Is Developer Console Enabled", BOOL, \
"IsDeveloperConsoleEnabled", "", "", isDeveloperConsoleEnabled) \
X(DATA, MEMBER_ATTRIBUTE, isUseCharacterColor, bool, "Is Use Character Color", BOOL, "IsUseCharacterColor", "", "", isUseCharacterColor) \
X(DATA, MEMBER_ATTRIBUTE, layer, std::string, "Layer", STRING, \
"Layer", "", "", layer) \
@@ -319,10 +314,6 @@
STRING_TARGET, "Portrait", "", "", portrait.path) \
X(DATA, MEMBER_ATTRIBUTE, position, glm::vec2, "Position", VEC2, "PositionX", \
"PositionY", "", position) \
X(DATA, MEMBER_ATTRIBUTE, quantity, int, "Quantity", INT_RW, "Quantity", "", "", \
quantity) \
X(DATA, MEMBER_ATTRIBUTE, quantityMax, int, "Quantity Max", INT_RW, \
"QuantityMax", "", "", quantityMax) \
X(DATA, MEMBER_ATTRIBUTE, rarity, std::string, "Rarity", STRING, "Rarity", \
"", "", rarity) \
X(DATA, ATTRIBUTE, colorOffset.r, void, "Red Offset", \
@@ -357,6 +348,9 @@
X(DATA, MEMBER_ATTRIBUTE, spawnChanceBase, float, "Spawn Chance Base", \
FLOAT, "SpawnChanceBase", "", "", spawnChanceBase) \
X(DATA, MEMBER_ATTRIBUTE, spawnChanceScoreBonus, float, "Spawn Chance Score Bonus", FLOAT, "SpawnChanceScoreBonus", "", "", spawnChanceScoreBonus) \
X(DATA, MEMBER_ATTRIBUTE, spawnDelayTicks, int, "Spawn Delay Ticks", INT_RW, "SpawnDelayTicks", "", "", spawnDelayTicks) \
X(DATA, MEMBER_ATTRIBUTE, spawnDelayStageMultiplier, float, "Spawn Delay Stage Multiplier", FLOAT, \
"SpawnDelayStageMultiplier", "", "", spawnDelayStageMultiplier) \
X(DATA, MEMBER_ATTRIBUTE, spawnPadding, float, "Spawn Padding", FLOAT, \
"SpawnPadding", "", "", spawnPadding) \
X(DATA, MEMBER_ATTRIBUTE, speed, float, "Speed", FLOAT, "Speed", "", "", speed) \
@@ -391,14 +385,14 @@
X(DATA, MEMBER_ATTRIBUTE, title, std::string, "Title", STRING, "Title", \
"", "", title) \
X(DATA, MEMBER_ATTRIBUTE, totalCaloriesConsumed, float, "Total Calories Consumed", FLOAT, "TotalCaloriesConsumed", "", "", totalCaloriesConsumed) \
X(DATA, MEMBER_ATTRIBUTE, totalFoodItemsEaten, int, "Total Food Items Eaten", \
INT_RW, "TotalFoodItemsEaten", "", "", totalFoodItemsEaten) \
X(DATA, MEMBER_ATTRIBUTE, totalItemsConsumed, int, "Total Items Consumed", INT_RW, "TotalItemsConsumed", "", "", \
totalItemsConsumed) \
X(DATA, MEMBER_ATTRIBUTE, totalItemsSpawned, int, "Total Items Spawned", INT_RW, "TotalItemsSpawned", "", "", \
totalItemsSpawned) \
X(DATA, MEMBER_ATTRIBUTE, totalPlaytimeSeconds, float, "Total Playtime Seconds", FLOAT, "TotalPlaytimeSeconds", "", "", totalPlaytimeSeconds) \
X(DATA, MEMBER_ATTRIBUTE, typeString, std::string, "Type", STRING, "Type", "", \
"", typeString) \
X(DATA, MEMBER_ATTRIBUTE, upgradeCount, int, "Upgrade Count", INT_RW, \
"UpgradeCount", "", "", upgradeCount) \
X(DATA, MEMBER_ATTRIBUTE, upgradeID, std::string, "Upgrade ID", \
STRING, "UpgradeID", "", "", upgradeID) \
X(DATA, MEMBER_ATTRIBUTE, useMode, std::string, "Use Mode", STRING, "UseMode", "", "", useMode) \
X(DATA, MEMBER_ATTRIBUTE, value, int, "Value", INT_RW, "Value", "", "", value) \
X(DATA, MEMBER_ATTRIBUTE, velocity, glm::vec2, "Velocity", VEC2, "VelocityX", \
"VelocityY", "", velocity) \
@@ -416,20 +410,20 @@
"", windowSize) \
X(DATA, MEMBER_ATTRIBUTE, windowPosition, glm::vec2, "Window Position", VEC2, "WindowX", \
"WindowY", "", windowPosition) \
X(DATA, MEMBER_ATTRIBUTE, worldBoundsNull, std::string, "World Bounds Null", STRING, "WorldBoundsNull", "", "", worldBoundsNull) \
X(DATA, MEMBER_ATTRIBUTE, itemSpawnNull, std::string, "Item Spawn Null", STRING, "ItemSpawnNull", "", "", itemSpawnNull) \
X(DATA, MEMBER_ATTRIBUTE, crop, glm::vec2, "Crop", VEC2, "XCrop", "YCrop", "", crop) \
X(DATA, MEMBER_ATTRIBUTE, pivot, glm::vec2, "Pivot", VEC2, "XPivot", "YPivot", "", pivot) \
X(DATA, MEMBER_ATTRIBUTE, framePosition, glm::vec2, "Frame Position", VEC2, \
"XPosition", "YPosition", "", framePosition) \
X(DATA, MEMBER_ATTRIBUTE, scale, glm::vec2, "Scale", VEC2, "XScale", "YScale", "", scale) \
X(DATA, MEMBER_ATTRIBUTE, zoom, float, "Zoom", FLOAT, "Zoom", "", "", zoom) \
X(DATA, MEMBER, animationChew, std::string, "Animation Chew", NONE, "", \
"", "", animationChew) \
X(DATA, MEMBER, animationClick, AnimationEntryCollection, "Animation Click", NONE, "", "", "", animationClick) \
X(DATA, MEMBER, animationGrab, AnimationEntryCollection, "Animation Grab", NONE, "", "", "", animationGrab) \
X(DATA, MEMBER, animationHover, AnimationEntryCollection, "Animation Hover", NONE, "", "", "", animationHover) \
X(DATA, MEMBER, animationIdle, AnimationEntryCollection, "Animation Idle", NONE, "", "", "", animationIdle) \
X(DATA, MEMBER, animationPan, AnimationEntryCollection, "Animation Pan", NONE, "", "", "", animationPan) \
X(DATA, MEMBER, animationReturn, AnimationEntryCollection, "Animation Return", NONE, "", "", "", animationReturn) \
X(DATA, MEMBER, animationDispose, AnimationEntryCollection, "Animation Dispose", NONE, "", "", "", animationDispose) \
X(DATA, MEMBER, animationZoom, AnimationEntryCollection, "Animation Zoom", NONE, "", "", "", animationZoom) \
X(DATA, MEMBER, anm2Entry, Anm2Entry, "ANM2 Entry", NONE, "", "", "", \
anm2Entry) \
@@ -478,17 +472,12 @@
soundOpen) \
X(DATA, MEMBER, soundRelease, SoundEntryCollection, "Sound Release", NONE, \
"", "", "", soundRelease) \
X(DATA, MEMBER, soundReturn, SoundEntryCollection, "Sound Return", NONE, "", \
"", "", soundReturn) \
X(DATA, MEMBER, soundSelect, SoundEntryCollection, "Sound Select", NONE, "", \
"", "", soundSelect) \
X(DATA, MEMBER, soundSummon, SoundEntryCollection, "Sound Summon", NONE, "", \
"", "", soundSummon) \
X(DATA, MEMBER, soundThrow, SoundEntryCollection, "Sound Throw", NONE, "", "", \
"", soundThrow) \
X(DATA, MEMBER, soundUpgrade, SoundEntryCollection, "Sound Upgrade", NONE, \
"", "", "", soundUpgrade) \
X(DATA, MEMBER, soundUpgradeFail, SoundEntryCollection, "Sound Upgrade Fail", NONE, "", "", "", soundUpgradeFail) \
X(DATA, MEMBER, textureEntry, TextureEntry, "Texture Entry", NONE, "", \
"", "", textureEntry) \
X(DATA, MEMBER, tint, glm::vec4, "Tint", NONE, "", "", "", tint) \
@@ -562,7 +551,7 @@ namespace game::resource::xml
std::string* get();
};
struct SoundEntryCollection : std::vector<SoundEntry*>
struct SoundEntryCollection : std::vector<SoundEntry>
{
const Audio* get() const;
void play() const;
@@ -580,14 +569,14 @@ namespace game::resource::xml
struct Category
{
std::string name{};
bool isEdible{};
std::string useMode{};
};
struct Rarity
{
std::string name{};
float weight{};
bool isHidden{};
bool isHiddenInCatalog{};
Audio sound{};
};
@@ -607,22 +596,14 @@ namespace game::resource::xml
bool isCalories{};
bool isCapacityBonus{};
bool isDigestionBonus{};
bool isEatSpeedBonus{};
bool isGravity{};
bool isDurability{};
bool isColor{};
bool isUpgradeID{};
bool isUpgradeCount{};
bool isToggleSpritesheet{};
float calories{};
float capacityBonus{};
float digestionBonus{};
float eatSpeedBonus{};
float gravity{};
int durability{};
glm::vec3 color{};
int upgradeID{-1};
int upgradeCount{};
};
struct Element
@@ -693,8 +674,6 @@ namespace game::resource::xml
Anm2 baseAnm2{};
Font font{};
Font iconsFont{};
int durability{2};
int quantityMax{99};
float rounding{};
Schema() = default;
@@ -732,6 +711,8 @@ namespace game::resource::xml
Element* dialogue_pool_entry_get(int poolID);
Element* dialogue_pool_entry_get(const std::string& poolID);
Element* dialogue_pool_entry_get(Element& reference);
Element* dialogue_hook_get(Element::Type type, int value = 0);
Element* dialogue_hook_entry_get(Element::Type type, int value = 0);
int dialogue_entry_id_get(const std::string& id) const;
int dialogue_pool_id_get(const std::string& id) const;
Element* item_get(int id);
+8 -24
View File
@@ -9,24 +9,24 @@ namespace game::resource::xml
{
#define GAME_XML_STRING_LIST(X) \
X(MenuTabInteract, "TextMenuTabInteract", "Interact") \
X(MenuTabGather, "TextMenuTabGather", "Gather") \
X(MenuTabInventory, "TextMenuTabInventory", "Inventory") \
X(MenuTabInventory, "TextMenuTabInventory", "Catalog") \
X(MenuTabSettings, "TextMenuTabSettings", "Settings") \
X(MenuTabCheats, "TextMenuTabCheats", "Cheats") \
X(MenuOpenTooltip, "TextMenuOpenTooltip", "Open Main Menu") \
X(MenuOpenFullscreenTooltip, "TextMenuOpenFullscreenTooltip", "Open Fullscreen Main Menu") \
X(MenuRestoreTooltip, "TextMenuRestoreTooltip", "Restore Main Menu") \
X(MenuCloseTooltip, "TextMenuCloseTooltip", "Close Main Menu") \
X(InteractChatButton, "TextInteractChatButton", "Let's chat!") \
X(InteractHelpButton, "TextInteractHelpButton", "Help") \
X(InteractFeelingButton, "TextInteractFeelingButton", "How are you feeling?") \
X(InteractSpawnItemButton, "TextInteractSpawnItemButton", "Spawn Item") \
X(InteractSpawnItemCooldownFormat, "TextInteractSpawnItemCooldownFormat", "Next item in %0.2fs") \
X(InteractStats, "TextInteractStats", "Stats") \
X(InteractWeightFormat, "TextInteractWeightFormat", "Weight: %0.2f %s (Stage: %i)") \
X(InteractCapacityFormat, "TextInteractCapacityFormat", "Capacity: %0.0f kcal (Max: %0.0f kcal)") \
X(InteractDigestionRateFormat, "TextInteractDigestionRateFormat", "Digestion Rate: %0.2f%%/sec") \
X(InteractEatingSpeedFormat, "TextInteractEatingSpeedFormat", "Eating Speed: %0.2fx") \
X(InteractTotalCaloriesFormat, "TextInteractTotalCaloriesFormat", "Total Calories Consumed: %0.0f kcal") \
X(InteractTotalFoodItemsFormat, "TextInteractTotalFoodItemsFormat", "Total Food Items Eaten: %i") \
X(InteractTotalItemsSpawnedFormat, "TextInteractTotalItemsSpawnedFormat", "Total Items Spawned: %i") \
X(InteractTotalItemsConsumedFormat, "TextInteractTotalItemsConsumedFormat", "Total Items Consumed: %i") \
X(InteractTotalPlaytimeFormat, "TextInteractTotalPlaytimeFormat", "Total Playtime: %s") \
X(SettingsWindow, "TextSettingsWindow", "Window") \
X(SettingsWindowMode, "TextSettingsWindowMode", "Mode") \
X(SettingsWindowModeTooltip, "TextSettingsWindowModeTooltip", \
@@ -61,32 +61,20 @@ namespace game::resource::xml
X(ToolsHomeTooltip, "TextToolsHomeTooltip", "Reset camera view.\n(Shortcut: Home)") \
X(ToolsOpenTooltip, "TextToolsOpenTooltip", "Open Tools") \
X(ToolsCloseTooltip, "TextToolsCloseTooltip", "Close Tools") \
X(InventoryEmptyHint, "TextInventoryEmptyHint", "No items yet.") \
X(InventoryEmptyHint, "TextInventoryEmptyHint", "No items.") \
X(InventoryFlavorFormat, "TextInventoryFlavorFormat", "Flavor: %s") \
X(InventoryCaloriesFormat, "TextInventoryCaloriesFormat", "%0.0f kcal") \
X(InventoryDurabilityFormat, "TextInventoryDurabilityFormat", "Durability: %i") \
X(InventoryCapacityBonusFormat, "TextInventoryCapacityBonusFormat", "Capacity Bonus: +%0.0f kcal") \
X(InventoryDigestionRateBonusFormat, "TextInventoryDigestionRateBonusFormat", \
"Digestion Rate Bonus: +%0.2f%% / sec") \
X(InventoryDigestionRatePenaltyFormat, "TextInventoryDigestionRatePenaltyFormat", \
"Digestion Rate Penalty: %0.2f%% / sec") \
X(InventoryEatSpeedBonusFormat, "TextInventoryEatSpeedBonusFormat", "Eat Speed Bonus: +%0.2f%% / sec") \
X(InventoryEatSpeedPenaltyFormat, "TextInventoryEatSpeedPenaltyFormat", "Eat Speed Penalty: %0.2f%% / sec") \
X(InventoryUpgradePreviewFormat, "TextInventoryUpgradePreviewFormat", "Upgrade: %ix -> %s") \
X(InventoryUnknown, "TextInventoryUnknown", "???") \
X(InventorySpawnButton, "TextInventorySpawnButton", "Spawn") \
X(InventoryEnableAutofeedButton, "TextInventoryEnableAutofeedButton", "Enable Autofeed") \
X(InventoryDisableAutofeedButton, "TextInventoryDisableAutofeedButton", "Disable Autofeed") \
X(InventoryEnableAutofeedTooltip, "TextInventoryEnableAutofeedTooltip", \
"Serve the character food and assist in digestion automatically.") \
X(InventoryDisableAutofeedTooltip, "TextInventoryDisableAutofeedTooltip", \
"Return to manual interaction.") \
X(InventoryUpgradeButton, "TextInventoryUpgradeButton", "Upgrade") \
X(InventoryUpgradeAllButton, "TextInventoryUpgradeAllButton", "Upgrade All") \
X(InventoryUpgradeNoPath, "TextInventoryUpgradeNoPath", "This item cannot be upgraded.") \
X(InventoryUpgradeNeedsTemplate, "TextInventoryUpgradeNeedsTemplate", "Needs {}x to upgrade into {}!") \
X(InventoryUpgradeOneTemplate, "TextInventoryUpgradeOneTemplate", "Use {}x to upgrade into 1x {}.") \
X(InventoryUpgradeAllTemplate, "TextInventoryUpgradeAllTemplate", "Use {}x to upgrade into {}x {}.") \
X(InfoProgressMax, "TextInfoProgressMax", "MAX") \
X(InfoProgressToNextStage, "TextInfoProgressToNextStage", "To Next Stage") \
X(InfoStageProgressFormat, "TextInfoStageProgressFormat", "Stage: %i/%i (%0.1f%%)") \
@@ -99,7 +87,6 @@ namespace game::resource::xml
X(InfoDigestionInProgress, "TextInfoDigestionInProgress", "Digestion in progress...") \
X(InfoGiveFoodToStartDigesting, "TextInfoGiveFoodToStartDigesting", "Give food to start digesting!") \
X(InfoDigestionRateFormat, "TextInfoDigestionRateFormat", "Rate: %0.2f%% / sec") \
X(InfoEatingSpeedFormat, "TextInfoEatingSpeedFormat", "Eating Speed: %0.2fx") \
X(CheatsCalories, "TextCheatsCalories", "Calories") \
X(CheatsCapacity, "TextCheatsCapacity", "Capacity") \
X(CheatsWeight, "TextCheatsWeight", "Weight") \
@@ -107,10 +94,7 @@ namespace game::resource::xml
X(CheatsStage, "TextCheatsStage", "Stage") \
X(CheatsDigestionRate, "TextCheatsDigestionRate", "Digestion Rate") \
X(CheatsDigestionRateFormat, "TextCheatsDigestionRateFormat", "%0.2f%% / sec") \
X(CheatsEatSpeed, "TextCheatsEatSpeed", "Eat Speed") \
X(CheatsEatSpeedFormat, "TextCheatsEatSpeedFormat", "%0.2fx") \
X(CheatsDigestButton, "TextCheatsDigestButton", "Digest") \
X(CheatsInventory, "TextCheatsInventory", "Inventory")
X(CheatsDigestButton, "TextCheatsDigestButton", "Digest")
class Strings
{
+7 -1
View File
@@ -39,7 +39,7 @@ namespace game
}
std::vector<std::filesystem::path> archives{};
for (auto& entry : std::filesystem::recursive_directory_iterator(CHARACTERS_DIRECTORY, ec))
for (auto& entry : std::filesystem::directory_iterator(CHARACTERS_DIRECTORY, ec))
if (!ec && entry.is_regular_file(ec) && entry.path().extension() == ".zip") archives.emplace_back(entry.path());
std::ranges::sort(archives);
@@ -47,6 +47,12 @@ namespace game
{
auto& preview = characterPreviews.emplace_back();
preview.load_archive(path, "character.xml");
if (!preview.root())
{
characterPreviews.pop_back();
logger.warning(std::format("Skipping invalid character archive: {}", path.string()));
continue;
}
auto& character = characters.emplace_back(path);
characterSaves.emplace_back(character.save_path_get());
+21 -20
View File
@@ -65,13 +65,14 @@ namespace game
resources.audio_mute_set(settingsRoot->isAudioMuted);
}
}
#if DEBUG
else if (event.type == SDL_EVENT_DROP_FILE)
{
resources.isDeveloper = true;
developerWindow.dropped_file_open(event.drop.data);
if (auto* settingsRoot = resources.settings.root(); settingsRoot && settingsRoot->isDeveloperConsoleEnabled)
{
resources.isDeveloper = true;
developerWindow.dropped_file_open(event.drop.data);
}
}
#endif
if (!isRunning) return;
}
ImGui_ImplOpenGL3_NewFrame();
@@ -110,22 +111,22 @@ namespace game
break;
}
#if DEBUG
developerWindow.update(resources, window, type == PLAY ? &play : nullptr);
if (developerWindow.action.isMenu && type == PLAY)
{
play.exit(resources);
type = SELECT;
resources.last_character_clear();
}
if (developerWindow.action.character >= 0)
{
if (type == PLAY) play.exit(resources);
select.characterIndex = developerWindow.action.character;
play.set(resources, select.characterIndex, state::Play::CONTINUE);
type = PLAY;
}
#endif
developerWindow.update(resources, window, type == PLAY ? &play : nullptr);
if (developerWindow.action.isMenu && type == PLAY)
{
play.exit(resources);
type = SELECT;
resources.last_character_clear();
}
if (developerWindow.action.character >= 0)
{
if (type == PLAY) play.exit(resources);
select.characterIndex = developerWindow.action.character;
if (developerWindow.action.isNewGame) resources.character_save_clear(select.characterIndex);
play.set(resources, select.characterIndex,
developerWindow.action.isNewGame ? state::Play::NEW_GAME : state::Play::CONTINUE);
type = PLAY;
}
auto isHideCursor = type == PLAY && !resources.isDeveloper && play.cursor_enabled_get();
if (isHideCursor != isCursorHidden)
-4
View File
@@ -5,9 +5,7 @@
#include "render/canvas.hpp"
#include "resources.hpp"
#if DEBUG
#include "developer/window.hpp"
#endif
#include "state/play.hpp"
#include "state/select.hpp"
@@ -35,9 +33,7 @@ namespace game
state::Play play;
state::Select select;
#if DEBUG
developer::Window developerWindow;
#endif
void update();
void render();
+100 -142
View File
@@ -19,23 +19,24 @@ using namespace glm;
namespace game::state
{
namespace
void entity_debug_flags_apply(Entity& entity, bool isShowLayers, bool isShowNulls)
{
int durability_animation_index_get(const resource::xml::Schema& schema, const resource::xml::Anm2& anm2,
int durability, int durabilityMax)
{
if (durability >= durabilityMax) return -1;
entity.isShowLayers = isShowLayers;
entity.isShowNulls = isShowNulls;
}
auto animationName = schema.root()->animationChew + std::to_string(std::max(0, durability));
return anm2.animationMap.contains(animationName) ? anm2.animationMap.at(animationName) : -1;
}
int saved_item_id_get(const Schema& itemSchema, const Schema::Element& item)
{
if (item.id >= 0 && item.id < (int)itemSchema.items.size() && item.id < (int)itemSchema.anm2s.size())
return item.id;
return -1;
}
World::Focus Play::focus_get()
{
if (!isWindows) return World::CENTER;
if (text.is_scene_active()) return World::CENTER;
auto isToolsOpen = tools.isOpen && !menu.is_fullscreen_visible_get();
auto isToolsOpen = tools.isOpen;
return menu.isOpen && isToolsOpen ? World::MENU_TOOLS
: menu.isOpen ? World::MENU
: isToolsOpen ? World::TOOLS
@@ -48,34 +49,16 @@ namespace game::state
return root && root->type == Schema::Element::CURSOR;
}
void Play::start_sequence_begin()
void Play::debug_flags_apply()
{
auto& dialogue = character.data.dialogue;
auto* start = dialogue.get(Schema::Element::START);
if (!start) return;
character.play({.animation = start->animation,
.appendID = character.animation_append_id_get(),
.interrupt = Entity::Interrupt::NEVER});
character.update();
text.entry = nullptr;
text.isEnabled = false;
isWindows = false;
isStart = true;
isStartBegin = false;
isStartEnd = false;
}
void Play::end_sequence_begin()
{
auto& dialogue = character.data.dialogue;
if (!dialogue.get(Schema::Element::END)) return;
text.entry = nullptr;
text.isEnabled = false;
isEnd = true;
isEndBegin = false;
isEndEnd = false;
entity_debug_flags_apply(character, isShowLayers, isShowNulls);
entity_debug_flags_apply(cursor, isShowLayers, isShowNulls);
entity_debug_flags_apply(autofeedCursor, isShowLayers, isShowNulls);
areaManager.debug_flags_set(isShowLayers, isShowNulls);
for (auto& item : itemManager.items)
entity_debug_flags_apply(item, isShowLayers, isShowNulls);
for (auto& particle : particleManager.particles)
entity_debug_flags_apply(particle, isShowLayers, isShowNulls);
}
void Play::set(Resources& resources, int selectedCharacterIndex, enum Game game)
@@ -84,8 +67,8 @@ namespace game::state
auto* saveRoot = data.save.root();
auto isSaveValid = saveRoot && saveRoot->type == Schema::Element::SAVE;
auto* saveCharacter = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::CHARACTER) : nullptr;
auto* saveInventory = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::INVENTORY) : nullptr;
auto* saveItems = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::ITEMS) : nullptr;
auto* saveDiscovered = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::DISCOVERED) : nullptr;
auto* savePan = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::PAN) : nullptr;
auto* saveZoom = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::ZOOM) : nullptr;
auto& itemSchema = data.itemSchema;
@@ -96,12 +79,11 @@ namespace game::state
resources.last_character_save(selectedCharacterIndex);
cheatCodeIndex = 0;
cheatCodeStartTime = 0.0;
world = play::World{};
character = Entity(data, vec2(World::BOUNDS.x + World::BOUNDS.z * 0.5f, World::BOUNDS.w - World::BOUNDS.y));
character = Entity(data, vec2{});
character.digestionRate =
glm::clamp(character.digestionRate, characterRoot->digestionRateMin, characterRoot->digestionRateMax);
character.eatSpeed =
glm::clamp(character.eatSpeed, characterRoot->eatSpeedMinMultiplier, characterRoot->eatSpeedMaxMultiplier);
character.capacity =
glm::clamp(character.capacity, characterRoot->capacityMinCalories, characterRoot->capacityMaxCalories);
@@ -116,11 +98,16 @@ namespace game::state
}
character.totalCaloriesConsumed = saveCharacter ? saveCharacter->totalCaloriesConsumed : 0;
character.totalFoodItemsEaten = saveCharacter ? saveCharacter->totalFoodItemsEaten : 0;
character.totalItemsSpawned = saveCharacter ? saveCharacter->totalItemsSpawned : 0;
character.totalItemsConsumed = saveCharacter ? saveCharacter->totalItemsConsumed : 0;
character.totalPlaytimeSeconds = saveCharacter ? saveCharacter->totalPlaytimeSeconds : 0;
autofeed = play::Autofeed{};
characterManager = CharacterManager{};
particleManager = ParticleManager{};
areaManager = AreaManager{};
areaManager.set(character);
world.bounds_set(areaManager.bounds_get(character, World::BOUNDS));
character.position = {};
cursor = Entity{};
cursor.entityType = CURSOR;
@@ -143,24 +130,24 @@ namespace game::state
}
menu.inventory = play::menu::Inventory{};
for (auto* item : saveInventory ? data.save.children_get(*saveInventory, Schema::Element::ITEM)
: std::vector<Schema::Element*>{})
{
if (item->quantity == 0) continue;
menu.inventory.values[item->id] = item->quantity;
}
itemManager = ItemManager{};
itemManager.spawnDelayTicks = saveRoot ? saveRoot->spawnDelayTicks : 0;
itemManager.discoveredItemIDs.resize(itemSchema.items.size());
for (auto* item :
saveDiscovered ? data.save.children_get(*saveDiscovered, Schema::Element::ITEM) : std::vector<Schema::Element*>{})
{
auto itemID = saved_item_id_get(itemSchema, *item);
if (itemID != -1) itemManager.discoveredItemIDs[itemID] = true;
}
for (auto* item :
saveItems ? data.save.children_get(*saveItems, Schema::Element::ITEM) : std::vector<Schema::Element*>{})
{
auto& anm2 = itemSchema.anm2s.at(item->id);
auto& schemaItem = itemSchema.items.at(item->id);
auto durabilityMax = schemaItem.isDurability ? schemaItem.durability : itemSchema.durability;
auto animationIndex = durability_animation_index_get(itemSchema, anm2, item->durability, durabilityMax);
auto& saveItem = itemSchema.anm2s.at(item->id);
itemManager.items.emplace_back(saveItem, item->position, item->id, item->durability, animationIndex,
item->velocity, item->rotation);
auto itemID = saved_item_id_get(itemSchema, *item);
if (itemID == -1) continue;
auto& saveItem = itemSchema.anm2s[itemID];
itemManager.items.emplace_back(saveItem, item->position, itemID, item->velocity, item->rotation);
itemManager.discoveredItemIDs[itemID] = true;
}
imgui::style::widget_set(menuSchema.rounding);
@@ -169,6 +156,7 @@ namespace game::state
text.entry = nullptr;
text.isEnabled = false;
isPostgamePending = false;
#if DEBUG
menu.isCheats = true;
@@ -180,32 +168,29 @@ namespace game::state
if (character.stage_get() >= character.stage_max_get()) isPostgame = true;
if (isPostgame) menu.isCheats = true;
if (game == NEW_GAME) isWindows = false;
if (auto font = character.data.menuSchema.font.get()) ImGui::GetIO().FontDefault = font;
character.play({.animation = character.idle_animation_get(), .appendID = character.animation_append_id_get()});
character.update();
areaManager.set(character);
worldCanvas.size_set(imgui::to_vec2(ImGui::GetMainViewport()->Size));
world.set(character, worldCanvas, focus_get());
if (savePan) worldCanvas.pan = savePan->position;
if (saveZoom && saveZoom->zoom >= World::ZOOM_MIN)
{
worldCanvas.zoom = glm::clamp(World::ZOOM_MIN, saveZoom->zoom, World::ZOOM_MAX);
world.zoom_index_set(worldCanvas);
}
if (game == NEW_GAME && dialogue.get(Schema::Element::START)) start_sequence_begin();
if (game == NEW_GAME)
text.set(dialogue.dialogue_hook_entry_get(Schema::Element::START), character, false);
if (isPostgame)
{
isEnd = true;
isEndBegin = true;
isEndEnd = true;
isPostgamePending = false;
}
else
{
isEnd = false;
isEndBegin = false;
isEndEnd = false;
isPostgamePending = false;
}
}
@@ -231,6 +216,7 @@ namespace game::state
auto focus = focus_get();
auto& dialogue = character.data.dialogue;
auto isCursorEnabled = cursor_enabled_get();
character.totalPlaytimeSeconds += ImGui::GetIO().DeltaTime;
if (isCursorEnabled)
{
cursor.isVisible = true;
@@ -276,87 +262,55 @@ namespace game::state
}
}
if (isWindows)
auto isSceneActive = text.is_scene_active();
if (!isSceneActive)
{
auto isToolsDisabled = menu.is_fullscreen_visible_get();
auto uiAlpha = UI_ALPHA_MAX - menu.fullscreen_alpha_get();
tools.update(character, cursor, world, focus, worldCanvas, isToolsDisabled, uiAlpha);
info.update(resources, character, uiAlpha);
tools.update(character, cursor, world, focus, worldCanvas, false, UI_ALPHA_MAX);
menu.update(resources, itemManager, character, text, autofeed);
toasts.update();
}
auto isEndSequenceActive = isEndBegin && !isEndEnd;
itemManager.isDisabled = isEndSequenceActive;
characterManager.isDisabled = isEndSequenceActive;
itemManager.isDisabled = isSceneActive;
characterManager.isDisabled = isSceneActive;
if (text.isEnabled) text.update(character, worldCanvas);
if (isStart)
if (character.isJustStageFinal && !isPostgame && !isPostgamePending)
{
if (!isStartBegin)
auto* end = dialogue.dialogue_hook_entry_get(Schema::Element::END);
if (end)
{
if (auto animation = character.animation_get())
{
if (animation->isLoop || character.state == Entity::STOPPED)
{
auto* start = dialogue.get(Schema::Element::START);
text.set(start ? dialogue.dialogue_entry_get(*start) : nullptr, character);
isStartBegin = true;
}
}
text.set(end, character, false);
tools.isOpen = false;
menu.isOpen = false;
character.calories = 0;
character.digestionProgress = 0;
itemManager.heldItemIndex = -1;
isPostgamePending = true;
world.character_focus(character, worldCanvas, focus_get());
}
else if (!isStartEnd)
else
{
if (text.entry && text.is_terminal() && text.is_finished())
{
isWindows = true;
isStartEnd = true;
isStart = false;
world.character_focus(character, worldCanvas, focus_get());
}
isPostgame = true;
menu.isCheats = true;
}
}
if (character.isJustStageFinal && !isEnd && !isPostgame) isEnd = true;
if (isEnd)
if (isPostgamePending && text.is_terminal() && text.is_finished() && !text.is_scene_active())
{
if (!isEndBegin)
{
if (character.is_animation_finished())
{
auto* end = dialogue.get(Schema::Element::END);
text.set(end ? dialogue.dialogue_entry_get(*end) : nullptr, character);
isEndBegin = true;
isWindows = false;
tools.isOpen = false;
menu.isOpen = false;
character.calories = 0;
character.digestionProgress = 0;
itemManager.heldItemIndex = -1;
world.character_focus(character, worldCanvas, focus_get());
}
}
else if (!isEndEnd)
{
if (text.entry && text.is_terminal() && text.is_finished())
{
menu.isOpen = true;
isWindows = true;
isEndEnd = true;
isEnd = false;
isPostgame = true;
menu.isCheats = true;
world.character_focus(character, worldCanvas, focus_get());
}
}
menu.isOpen = true;
isPostgamePending = false;
isPostgame = true;
menu.isCheats = true;
world.character_focus(character, worldCanvas, focus_get());
}
autofeed.update(resources, character, autofeedCursor, menu.inventory, itemManager, characterManager, worldCanvas);
if (!text.is_scene_active())
autofeed.update(resources, character, autofeedCursor, menu.inventory, itemManager, characterManager, worldCanvas);
auto& interactionCursor = autofeed.active_get() ? autofeedCursor : cursor;
itemManager.isAutomation = autofeed.active_get();
itemManager.update(character, interactionCursor, areaManager, text, World::BOUNDS, worldCanvas);
itemManager.update(character, interactionCursor, areaManager, text, world.bounds, worldCanvas);
characterManager.update(character, interactionCursor, text, worldCanvas);
for (auto& particle : itemManager.particles)
particleManager.spawn(character.data, particle.label, particle.position);
@@ -371,6 +325,7 @@ namespace game::state
character.update();
areaManager.update(character);
world.bounds_set(areaManager.bounds_get(character, world.bounds));
particleManager.update();
cursor.update();
if (autofeed.active_get()) autofeedCursor.update();
@@ -396,13 +351,15 @@ namespace game::state
worldCanvas.bind();
worldCanvas.size_set(size);
worldCanvas.clear();
debug_flags_apply();
areaManager.render(character, textureShader, rectShader, worldCanvas);
character.render(textureShader, rectShader, worldCanvas);
for (auto& item : itemManager.items)
item.render(textureShader, rectShader, worldCanvas);
if (!text.is_scene_active())
for (auto& item : itemManager.items)
item.render(textureShader, rectShader, worldCanvas);
auto isCursorEnabled = cursor_enabled_get();
if (isCursorEnabled && autofeed.active_get()) autofeedCursor.render(textureShader, rectShader, worldCanvas);
@@ -423,9 +380,11 @@ namespace game::state
void Play::save(Resources& resources)
{
resource::xml::Schema save;
auto& itemSchema = character.data.itemSchema;
auto& root = save.element_add(resource::xml::Schema::Element::SAVE);
root.isPostgame = isPostgame;
root.isAlternateSpritesheet = character.spritesheetType == Entity::ALTERNATE;
root.spawnDelayTicks = itemManager.spawnDelayTicks;
auto& panElement = save.element_add(resource::xml::Schema::Element::PAN, 0);
panElement.position = worldCanvas.pan;
@@ -438,36 +397,35 @@ namespace game::state
characterElement.calories = character.calories;
characterElement.capacityCalories = character.capacity;
characterElement.digestionRate = character.digestionRate;
characterElement.eatSpeedMultiplier = character.eatSpeed;
characterElement.digestionProgress = character.digestionProgress;
characterElement.isDigesting = character.isDigesting;
characterElement.digestionTimer = character.digestionTimer;
characterElement.totalCaloriesConsumed = character.totalCaloriesConsumed;
characterElement.totalFoodItemsEaten = character.totalFoodItemsEaten;
auto inventoryIndex = (int)save.elements.size();
save.element_add(resource::xml::Schema::Element::INVENTORY, 0);
for (auto& [id, quantity] : menu.inventory.values)
{
if (quantity == 0) continue;
auto& item = save.element_add(resource::xml::Schema::Element::ITEM, inventoryIndex);
item.id = id;
item.quantity = quantity;
}
characterElement.totalItemsSpawned = character.totalItemsSpawned;
characterElement.totalItemsConsumed = character.totalItemsConsumed;
characterElement.totalPlaytimeSeconds = character.totalPlaytimeSeconds;
auto itemsIndex = (int)save.elements.size();
save.element_add(resource::xml::Schema::Element::ITEMS, 0);
for (auto& item : itemManager.items)
{
if (item.schemaID < 0 || item.schemaID >= (int)itemSchema.items.size()) continue;
auto& itemElement = save.element_add(resource::xml::Schema::Element::ITEM, itemsIndex);
itemElement.id = item.schemaID;
itemElement.durability = item.durability;
itemElement.position = item.position;
itemElement.velocity = item.velocity;
itemElement.rotation = *item.overrides[item.rotationOverrideID].frame.rotation;
}
auto discoveredIndex = (int)save.elements.size();
save.element_add(resource::xml::Schema::Element::DISCOVERED, 0);
for (int i = 0; i < (int)itemManager.discoveredItemIDs.size(); i++)
{
if (!itemManager.discoveredItemIDs[i]) continue;
auto& itemElement = save.element_add(resource::xml::Schema::Element::ITEM, discoveredIndex);
itemElement.id = i;
}
resources.character_save_set(characterIndex, save);
save.serialize(character.data.save_path_get());
+4 -14
View File
@@ -5,7 +5,6 @@
#include "play/area_manager.hpp"
#include "play/autofeed.hpp"
#include "play/character_manager.hpp"
#include "play/info.hpp"
#include "play/item_manager.hpp"
#include "play/menu.hpp"
#include "play/particle_manager.hpp"
@@ -31,7 +30,6 @@ namespace game::state
Entity cursor;
Entity autofeedCursor;
play::Info info;
play::Menu menu;
play::Tools tools;
play::Text text;
@@ -50,17 +48,10 @@ namespace game::state
int cheatCodeIndex{};
double cheatCodeStartTime{};
bool isWindows{true};
bool isStartBegin{};
bool isStart{};
bool isStartEnd{};
bool isEndBegin{};
bool isEnd{};
bool isEndEnd{};
bool isPostgame{};
bool isPostgamePending{};
bool isShowLayers{};
bool isShowNulls{};
Canvas worldCanvas{play::World::SIZE};
@@ -71,8 +62,7 @@ namespace game::state
void update(Resources&);
void render(Resources&, Canvas&);
void save(Resources&);
void debug_flags_apply();
play::World::Focus focus_get();
void start_sequence_begin();
void end_sequence_begin();
};
};
+75 -14
View File
@@ -1,12 +1,34 @@
#include "area_manager.hpp"
#include "../../util/math.hpp"
#include <cmath>
using namespace game::resource;
using namespace game::util;
namespace game::state::play
{
namespace
{
constexpr auto DEFAULT_WORLD_BOUNDS_NULL = "Bounds";
constexpr auto ZERO_FLOAT = 0.0f;
bool rect_is_valid(glm::vec4 rect)
{
return std::isfinite(rect.x) && std::isfinite(rect.y) && std::isfinite(rect.z) && std::isfinite(rect.w) &&
rect.z > ZERO_FLOAT && rect.w > ZERO_FLOAT;
}
glm::vec4 bounds_from_rect_get(glm::vec4 rect)
{
return {rect.x, rect.y, rect.x + rect.z, rect.y + rect.w};
}
glm::vec4 rect_from_bounds_get(glm::vec4 bounds)
{
return {bounds.x, bounds.y, bounds.z - bounds.x, bounds.w - bounds.y};
}
}
int AreaManager::index_get(Entity& character)
{
auto& data = character.data;
@@ -32,14 +54,61 @@ namespace game::state::play
return &character.data.areaSchema.areas[index];
}
glm::vec4 AreaManager::bounds_get(Entity& character, const glm::vec4& fallback)
{
auto index = index_get(character);
if (index == -1 || index >= (int)bounds.size()) return fallback;
auto result = bounds[index];
return rect_is_valid(rect_from_bounds_get(result)) ? result : fallback;
}
glm::vec4 AreaManager::item_spawn_rect_get(Entity& character, const glm::vec4& fallbackBounds)
{
auto index = index_get(character);
if (index == -1 || index >= (int)itemSpawnRects.size()) return rect_from_bounds_get(fallbackBounds);
auto result = itemSpawnRects[index];
return rect_is_valid(result) ? result : rect_from_bounds_get(bounds_get(character, fallbackBounds));
}
void AreaManager::debug_flags_set(bool isShowLayers, bool isShowNulls)
{
this->isShowLayers = isShowLayers;
this->isShowNulls = isShowNulls;
for (auto& entity : entities)
{
entity.isShowLayers = isShowLayers;
entity.isShowNulls = isShowNulls;
}
}
void AreaManager::set(Entity& character)
{
entities.clear();
bounds.clear();
itemSpawnRects.clear();
auto& areas = character.data.areaSchema.areas;
entities.reserve(areas.size());
bounds.reserve(areas.size());
itemSpawnRects.reserve(areas.size());
auto* root = character.data.areaSchema.root();
auto worldBoundsNull = root && !root->worldBoundsNull.empty() ? root->worldBoundsNull : DEFAULT_WORLD_BOUNDS_NULL;
auto itemSpawnNull = root && !root->itemSpawnNull.empty() ? root->itemSpawnNull : worldBoundsNull;
for (auto& area : areas)
entities.emplace_back(area.anm2.is_valid() ? Entity(area.anm2) : Entity{});
{
auto entity = area.anm2.is_valid() ? Entity(area.anm2) : Entity{};
entity.position = {};
entity.isShowLayers = isShowLayers;
entity.isShowNulls = isShowNulls;
auto worldBoundsNullID = entity.nullMap.contains(worldBoundsNull) ? entity.nullMap.at(worldBoundsNull) : -1;
auto itemSpawnNullID = entity.nullMap.contains(itemSpawnNull) ? entity.nullMap.at(itemSpawnNull) : worldBoundsNullID;
auto rect = entity.null_frame_rect(worldBoundsNullID, entity.defaultAnimationID, ZERO_FLOAT);
auto itemSpawnRect = entity.null_frame_rect(itemSpawnNullID, entity.defaultAnimationID, ZERO_FLOAT);
if (!rect_is_valid(itemSpawnRect)) itemSpawnRect = rect;
entities.emplace_back(std::move(entity));
bounds.emplace_back(rect_is_valid(rect) ? bounds_from_rect_get(rect) : glm::vec4(NAN));
itemSpawnRects.emplace_back(rect_is_valid(itemSpawnRect) ? itemSpawnRect : glm::vec4(NAN));
}
}
void AreaManager::update(Entity& character)
@@ -48,6 +117,7 @@ namespace game::state::play
auto index = index_get(character);
if (index == -1 || index >= (int)entities.size()) return;
entities[index].position = {};
entities[index].update();
}
@@ -56,18 +126,9 @@ namespace game::state::play
if (entities.size() != character.data.areaSchema.areas.size()) set(character);
auto index = index_get(character);
if (index == -1) return;
if (index == -1 || index >= (int)entities.size()) return;
auto& area = character.data.areaSchema.areas[index];
if (index < (int)entities.size() && area.anm2.is_valid())
{
entities[index].render(textureShader, rectShader, canvas);
return;
}
if (!area.texture.is_valid()) return;
auto worldModel = math::quad_model_get(area.texture.size);
canvas.texture_render(textureShader, area.texture.id, worldModel);
entities[index].position = {};
entities[index].render(textureShader, rectShader, canvas);
}
}
+7
View File
@@ -11,9 +11,16 @@ namespace game::state::play
{
public:
std::vector<Entity> entities{};
std::vector<glm::vec4> bounds{};
std::vector<glm::vec4> itemSpawnRects{};
bool isShowLayers{};
bool isShowNulls{};
int index_get(Entity&);
game::resource::xml::Schema::AreaEntry* get(Entity&);
glm::vec4 bounds_get(Entity&, const glm::vec4& fallback);
glm::vec4 item_spawn_rect_get(Entity&, const glm::vec4& fallbackBounds);
void debug_flags_set(bool isShowLayers, bool isShowNulls);
void set(Entity&);
void update(Entity&);
void render(Entity&, game::resource::Shader&, game::resource::Shader&, Canvas&);
+78 -118
View File
@@ -19,6 +19,7 @@ namespace game::state::play
constexpr float CURSOR_SPEED_SCALE_MIN = 0.5f;
constexpr float CURSOR_SPEED_SCALE_MAX = 2.0f;
constexpr float CALORIE_EPSILON = 0.001f;
constexpr float FOOD_TIE_PICK_WEIGHT = 1.0f;
glm::vec2 rect_center(glm::vec4 rect) { return {rect.x + rect.z * 0.5f, rect.y + rect.w * 0.5f}; }
@@ -62,13 +63,12 @@ namespace game::state::play
bool is_food(const Schema& schema, const Schema::ItemEntry& item)
{
return item.categoryID >= 0 && item.categoryID < (int)schema.categories.size() &&
schema.categories[item.categoryID].isEdible;
schema.categories[item.categoryID].useMode == "Edible";
}
bool is_negative_effect(const Schema::ItemEntry& item)
{
return (item.isCapacityBonus && item.capacityBonus < 0.0f) ||
(item.isEatSpeedBonus && item.eatSpeedBonus < 0.0f) ||
(item.isDigestionBonus && item.digestionBonus < 0.0f);
}
@@ -77,45 +77,24 @@ namespace game::state::play
return is_food(schema, item) && !is_negative_effect(item);
}
int durability_max_get(const Schema& schema, const Schema::ItemEntry& item)
{
return item.isDurability ? item.durability : schema.durability;
}
float calories_per_bite_get(const Schema& schema, const Schema::ItemEntry& item)
{
auto durabilityMax = durability_max_get(schema, item);
return item.isCalories && durabilityMax > 0 ? item.calories / (float)durabilityMax : 0.0f;
}
bool can_eat(const Entity& character, const Schema& schema, const Schema::ItemEntry& item)
{
auto caloriesPerBite = calories_per_bite_get(schema, item);
return caloriesPerBite > 0.0f && character.calories + caloriesPerBite <= character.max_capacity();
return is_autofeed_food(schema, item) && item.isCalories && item.calories > 0.0f &&
character.calories + item.calories <= character.max_capacity();
}
struct FoodScore
{
float calories{};
int bites{};
float caloriesPerBite{};
float value{};
bool isValid{};
};
FoodScore food_score_get(const Entity& character, const Schema& schema, const Schema::ItemEntry& item,
int durability)
FoodScore food_score_get(const Entity& character, const Schema& schema, const Schema::ItemEntry& item)
{
if (!is_autofeed_food(schema, item)) return {};
auto caloriesPerBite = calories_per_bite_get(schema, item);
auto remainingCapacity = character.max_capacity() - character.calories;
auto durabilityMax = durability_max_get(schema, item);
auto remainingBites = glm::max(0, durabilityMax - durability);
if (caloriesPerBite <= 0.0f || remainingCapacity < caloriesPerBite || remainingBites <= 0) return {};
auto bites = glm::clamp((int)std::floor(remainingCapacity / caloriesPerBite), 0, remainingBites);
auto calories = (float)bites * caloriesPerBite;
return {.calories = calories, .bites = bites, .caloriesPerBite = caloriesPerBite, .isValid = bites > 0};
if (!can_eat(character, schema, item)) return {};
return {.calories = item.calories, .bites = 1, .value = item.calories, .isValid = true};
}
bool is_better_food_score(const FoodScore& score, const FoodScore& best)
@@ -124,10 +103,31 @@ namespace game::state::play
if (!best.isValid) return true;
if (score.calories != best.calories) return score.calories > best.calories;
if (score.bites != best.bites) return score.bites < best.bites;
return score.caloriesPerBite > best.caloriesPerBite;
return score.value > best.value;
}
bool can_eat_anything(Entity& character, ItemManager& itemManager, menu::Inventory& inventory)
bool is_same_food_score(const FoodScore& score, const FoodScore& best)
{
return score.isValid && best.isValid && score.calories == best.calories && score.bites == best.bites &&
score.value == best.value;
}
bool food_score_select(FoodScore& best, int& count, const FoodScore& score)
{
if (is_better_food_score(score, best))
{
best = score;
count = 1;
return true;
}
if (!is_same_food_score(score, best)) return false;
count++;
return math::random_max((float)count) < FOOD_TIE_PICK_WEIGHT;
}
bool can_eat_anything(Entity& character, ItemManager& itemManager)
{
auto& schema = character.data.itemSchema;
for (auto& entity : itemManager.items)
@@ -137,47 +137,6 @@ namespace game::state::play
if (is_autofeed_food(schema, item) && can_eat(character, schema, item)) return true;
}
for (auto& [id, quantity] : inventory.values)
{
if (quantity <= 0 || id < 0 || id >= (int)schema.items.size()) continue;
auto& item = schema.items[id];
if (is_autofeed_food(schema, item) && can_eat(character, schema, item)) return true;
}
return false;
}
int best_inventory_food_get(Entity& character, menu::Inventory& inventory)
{
auto& schema = character.data.itemSchema;
auto bestID = -1;
auto bestScore = FoodScore{};
for (auto& [id, quantity] : inventory.values)
{
if (quantity <= 0 || id < 0 || id >= (int)schema.items.size()) continue;
auto& item = schema.items[id];
auto score = food_score_get(character, schema, item, 0);
if (is_better_food_score(score, bestScore))
{
bestScore = score;
bestID = id;
}
}
return bestID;
}
bool is_inventory_food(Entity& character, menu::Inventory& inventory)
{
auto& schema = character.data.itemSchema;
for (auto& [id, quantity] : inventory.values)
{
if (quantity <= 0 || id < 0 || id >= (int)schema.items.size()) continue;
if (is_autofeed_food(schema, schema.items[id])) return true;
}
return false;
}
@@ -193,10 +152,9 @@ namespace game::state::play
return false;
}
bool is_food(Entity& character, menu::Inventory& inventory, ItemManager& itemManager)
bool is_food(Entity& character, ItemManager& itemManager)
{
return character.calories > CALORIE_EPSILON || is_inventory_food(character, inventory) ||
is_world_food(character, itemManager);
return character.calories > CALORIE_EPSILON || is_world_food(character, itemManager);
}
int best_world_food_get(Entity& character, ItemManager& itemManager, bool requireCanEat)
@@ -204,6 +162,7 @@ namespace game::state::play
auto& schema = character.data.itemSchema;
auto bestIndex = -1;
auto bestScore = FoodScore{};
auto bestCount = 0;
for (int i = 0; i < (int)itemManager.items.size(); i++)
{
@@ -214,17 +173,25 @@ namespace game::state::play
if (!is_autofeed_food(schema, item)) continue;
if (requireCanEat && !can_eat(character, schema, item)) continue;
auto score = food_score_get(character, schema, item, entity.durability);
if (is_better_food_score(score, bestScore))
{
bestScore = score;
bestIndex = i;
}
auto score = food_score_get(character, schema, item);
if (food_score_select(bestScore, bestCount, score)) bestIndex = i;
}
return bestIndex;
}
bool world_food_target_is_valid(Entity& character, ItemManager& itemManager, int index)
{
auto& schema = character.data.itemSchema;
if (index < 0 || index >= (int)itemManager.items.size()) return false;
auto& entity = itemManager.items[index];
if (entity.schemaID < 0 || entity.schemaID >= (int)schema.items.size()) return false;
auto& item = schema.items[entity.schemaID];
return can_eat(character, schema, item) && is_finite(entity.rect());
}
bool eat_rect_get(Entity& character, glm::vec4& rect)
{
for (auto* eatArea : character.data.eat_areas_get())
@@ -257,16 +224,18 @@ namespace game::state::play
return nullptr;
}
void state_reset(Entity& cursor, bool& isItemMouseHeld, bool& isDigestMouseHeld, int& digestClickCooldown)
void state_reset(Entity& cursor, bool& isItemMouseHeld, bool& isDigestMouseHeld, int& targetItemIndex,
int& digestClickCooldown)
{
isItemMouseHeld = false;
isDigestMouseHeld = false;
targetItemIndex = -1;
digestClickCooldown = 0;
if (!is_finite(cursor.position)) cursor.position = {};
}
}
void Autofeed::update(Resources& resources, Entity& character, Entity& cursor, menu::Inventory& inventory,
void Autofeed::update(Resources& resources, Entity& character, Entity& cursor, menu::Inventory&,
ItemManager& itemManager, CharacterManager& characterManager, Canvas& canvas)
{
isActive = false;
@@ -274,30 +243,27 @@ namespace game::state::play
if (!isEnabled)
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
return;
}
if (!is_food(character, inventory, itemManager))
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
return;
}
if (character.isStageUp || character.isJustStageUp || character.isJustStageFinal)
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
return;
}
itemManager.spawn_queue(character);
if (!is_food(character, itemManager))
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
return;
}
if (cursor.position == glm::vec2{} && is_finite(character.position)) cursor.position = character.position;
auto& schema = character.data.itemSchema;
if (inventory.upgrade_all_possible(schema))
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
return;
}
auto itemInput = ItemManager::Input{};
itemInput.isAutomation = true;
@@ -315,7 +281,7 @@ namespace game::state::play
{
isActive = false;
cursor.isVisible = false;
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
return;
}
auto& schemaItem = schema.items[heldItem.schemaID];
@@ -338,10 +304,14 @@ namespace game::state::play
isActive = true;
cursor.isVisible = true;
isItemMouseHeld = false;
targetItemIndex = -1;
return;
}
auto bestWorldFood = isEatTarget ? best_world_food_get(character, itemManager, true) : -1;
if (!isEatTarget || !world_food_target_is_valid(character, itemManager, targetItemIndex))
targetItemIndex = isEatTarget ? best_world_food_get(character, itemManager, true) : -1;
auto bestWorldFood = targetItemIndex;
if (bestWorldFood != -1)
{
@@ -349,7 +319,7 @@ namespace game::state::play
auto rect = item.rect();
if (!is_finite(rect))
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
return;
}
@@ -359,6 +329,7 @@ namespace game::state::play
auto isInItemRect = math::is_point_in_rectf(rect, cursor.position);
itemInput.targetItemIndex = bestWorldFood;
itemInput.isMouseLeftClicked = isInItemRect && !isItemMouseHeld;
itemInput.isMouseLeftDown = isInItemRect || isItemMouseHeld;
itemManager.inputOverride = itemInput;
@@ -367,37 +338,26 @@ namespace game::state::play
return;
}
auto isInventoryFood = is_inventory_food(character, inventory);
auto isWorldFood = is_world_food(character, itemManager);
if (character.calories <= CALORIE_EPSILON && !isInventoryFood && !isWorldFood)
if (character.calories <= CALORIE_EPSILON && !isWorldFood)
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
return;
}
auto bestInventoryFood = isEatTarget ? best_inventory_food_get(character, inventory) : -1;
if (bestInventoryFood != -1 && !isWorldFood && (int)itemManager.items.size() < ItemManager::LIMIT)
{
itemManager.queuedItemIDs.emplace_back(bestInventoryFood);
inventory.values[bestInventoryFood]--;
schema.root()->soundSummon.play();
cursor.isVisible = false;
return;
}
if ((isWorldFood && bestWorldFood == -1) || !can_eat_anything(character, itemManager, inventory))
if ((isWorldFood && bestWorldFood == -1) || !can_eat_anything(character, itemManager))
{
auto* interactArea = digestion_area_get(character);
if (!interactArea)
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
return;
}
auto rect = character.null_frame_rect(character.data.null_id_get(interactArea->null));
if (!is_finite(rect))
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
return;
}
auto radius = glm::max(1.0f, glm::min(rect.z, rect.w) * 0.25f);
@@ -434,14 +394,14 @@ namespace game::state::play
return;
}
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
}
void Autofeed::toggle() { isEnabled = !isEnabled; }
void Autofeed::disable() { isEnabled = false; }
bool Autofeed::available_get(Entity& character, menu::Inventory& inventory, ItemManager& itemManager) const
bool Autofeed::available_get(Entity& character, ItemManager& itemManager) const
{
return is_food(character, inventory, itemManager);
return is_food(character, itemManager) || itemManager.spawn_possible_get(character);
}
bool Autofeed::enabled_get() const { return isEnabled; }
bool Autofeed::active_get() const { return isActive; }
+2 -1
View File
@@ -11,6 +11,7 @@ namespace game::state::play
{
bool isItemMouseHeld{};
bool isDigestMouseHeld{};
int targetItemIndex{-1};
int digestClickCooldown{};
float digestAngle{};
bool isEnabled{};
@@ -21,7 +22,7 @@ namespace game::state::play
CharacterManager&, Canvas&);
void toggle();
void disable();
bool available_get(Entity& character, menu::Inventory&, ItemManager&) const;
bool available_get(Entity& character, ItemManager&) const;
bool enabled_get() const;
bool active_get() const;
};
+6 -8
View File
@@ -2,8 +2,8 @@
#include "../../util/math.hpp"
#include <imgui.h>
#include <algorithm>
#include <imgui.h>
#include <optional>
using namespace game::resource::xml;
@@ -139,10 +139,10 @@ namespace game::state::play
if (layerID != -1)
{
auto scaleEffectTimeTicks = interactArea->timeTicks == ZERO_FLOAT ? SCALE_EFFECT_TIME_TICKS_DEFAULT
: interactArea->timeTicks;
auto scaleEffectTimeTicks =
interactArea->timeTicks == ZERO_FLOAT ? SCALE_EFFECT_TIME_TICKS_DEFAULT : interactArea->timeTicks;
auto scaleEffectCycles = interactArea->scaleEffectCycles == ZERO_FLOAT ? SCALE_EFFECT_CYCLES_DEFAULT
: interactArea->scaleEffectCycles;
: interactArea->scaleEffectCycles;
character.overrides.emplace_back(Entity::Override(
layerID, Anm2::LAYER, Entity::Override::ADD, {.scale = glm::vec2(interactArea->scaleEffectAmplitude)},
std::optional<float>(scaleEffectTimeTicks), interact_area_override_tick, scaleEffectCycles));
@@ -204,10 +204,8 @@ namespace game::state::play
cursorWorldPositionPrevious = cursorWorldPosition;
if (character.isJustDigested && text.is_interruptible())
if (auto* digest = dialogue.get(Schema::Element::DIGEST))
text.set(dialogue.dialogue_pool_entry_get(*digest), character);
text.set(dialogue.dialogue_hook_entry_get(Schema::Element::DIGEST), character);
if (character.isJustStageUp)
if (auto* stageUp = dialogue.get(Schema::Element::STAGE_UP))
text.set(dialogue.dialogue_pool_entry_get(*stageUp), character);
text.set(dialogue.dialogue_hook_entry_get(Schema::Element::STAGE_UP, character.stage_get() + 1), character);
}
}
+1 -25
View File
@@ -3,7 +3,6 @@
#include <algorithm>
#include <ranges>
#include "../../util/imgui/input_int_ex.hpp"
#include "../../util/imgui/widget.hpp"
using namespace game::util::imgui;
@@ -12,7 +11,7 @@ using namespace game::resource::xml;
namespace game::state::play
{
void Cheats::update(Resources&, Entity& character, menu::Inventory& inventory)
void Cheats::update(Resources&, Entity& character)
{
auto& strings = character.data.strings;
@@ -27,8 +26,6 @@ namespace game::state::play
auto digestionRateMin = (float)character.data.root()->digestionRateMin * Entity::UPDATE_RATE;
auto digestionRateMax = (float)character.data.root()->digestionRateMax * Entity::UPDATE_RATE;
auto digestionRate = character.digestion_rate_get();
auto eatSpeedMin = (float)character.data.root()->eatSpeedMinMultiplier;
auto eatSpeedMax = (float)character.data.root()->eatSpeedMaxMultiplier;
auto weight_update = [&]()
{
@@ -59,31 +56,10 @@ namespace game::state::play
digestionRateMin, digestionRateMax,
strings.get(Strings::CheatsDigestionRateFormat).c_str())))
character.digestionRate = digestionRate / Entity::UPDATE_RATE;
WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsEatSpeed).c_str(), &character.eatSpeed, eatSpeedMin,
eatSpeedMax,
strings.get(Strings::CheatsEatSpeedFormat).c_str()));
if (WIDGET_FX(ImGui::Button(strings.get(Strings::CheatsDigestButton).c_str())))
character.digestionProgress = Entity::DIGESTION_MAX;
ImGui::SeparatorText(strings.get(Strings::CheatsInventory).c_str());
if (ImGui::BeginChild("##Inventory", ImGui::GetContentRegionAvail(), ImGuiChildFlags_Borders))
{
auto& schema = character.data.itemSchema;
ImGui::PushItemWidth(100);
for (int i = 0; i < (int)schema.items.size(); i++)
{
auto& item = schema.items[i];
ImGui::PushID(i);
WIDGET_FX(input_int_range(item.name.c_str(), &inventory.values[i], 0, schema.quantityMax, 1, 5));
ImGui::SetItemTooltip("%s", item.name.c_str());
ImGui::PopID();
}
ImGui::PopItemWidth();
}
ImGui::EndChild();
}
ImGui::EndChild();
}
+1 -2
View File
@@ -1,6 +1,5 @@
#pragma once
#include "menu/inventory.hpp"
#include "text.hpp"
#include <imgui.h>
@@ -10,6 +9,6 @@ namespace game::state::play
class Cheats
{
public:
void update(Resources&, Entity&, menu::Inventory&);
void update(Resources&, Entity&);
};
}
-149
View File
@@ -1,149 +0,0 @@
#include "info.hpp"
#include "../../util/color.hpp"
#include "../../util/imgui.hpp"
#include "../../util/math.hpp"
#include "../../util/string.hpp"
#include <algorithm>
#include <format>
using namespace game::resource;
using namespace game::resource::xml;
using namespace game::util;
namespace game::state::play
{
float info_height_get()
{
static constexpr auto HEIGHT_MULTIPLIER = 4.0f;
return ImGui::GetTextLineHeightWithSpacing() * HEIGHT_MULTIPLIER;
}
void info_content_draw(Resources& resources, Entity& character, ImVec2 size)
{
static constexpr auto HALF_MULTIPLIER = 0.5f;
static constexpr auto ZERO_FLOAT = 0.0f;
auto& strings = character.data.strings;
auto& style = ImGui::GetStyle();
auto childSize = ImVec2((size.x - style.ItemSpacing.x) * HALF_MULTIPLIER, size.y);
if (ImGui::BeginChild("##Weight", childSize))
{
auto* settings = resources.settings.root();
auto system = settings->measurementSystem == "Imperial" ? measurement::IMPERIAL : measurement::METRIC;
auto weight = character.weight_get(system);
auto stage = character.stage_get();
auto stageMax = character.stage_max_get();
auto stageCount = character.stage_count_get();
auto stageWeight = character.stage_threshold_get(stage, system);
auto stageNextWeight = character.stage_threshold_next_get(system);
auto unitString = (system == measurement::IMPERIAL ? "lbs" : "kg");
auto weightString = util::string::format_commas(weight, 2) + " " + unitString;
imgui::text_unformatted_fit_draw(weightString.c_str(), ImVec2(ImGui::GetContentRegionAvail().x, Font::HEADER_2),
Font::HEADER_2);
ImGui::SetItemTooltip("%s", weightString.c_str());
auto stageProgress = character.stage_progress_get();
ImGui::ProgressBar(stageProgress, ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT),
strings.get(stage >= stageMax ? Strings::InfoProgressMax : Strings::InfoProgressToNextStage)
.c_str());
if (ImGui::BeginItemTooltip())
{
ImGui::Text(strings.get(Strings::InfoStageProgressFormat).c_str(), stage + 1, stageCount,
math::to_percent(stageProgress));
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, imgui::to_imvec4(color::GRAY));
if (stage >= stageMax)
ImGui::TextUnformatted(strings.get(Strings::InfoMaxedOut).c_str());
else
{
ImGui::Text(strings.get(Strings::InfoStageStartFormat).c_str(), stageWeight, unitString);
ImGui::Text(strings.get(Strings::InfoStageCurrentFormat).c_str(), weight, unitString);
ImGui::Text(strings.get(Strings::InfoStageNextFormat).c_str(), stageNextWeight, unitString);
}
ImGui::PopStyleColor();
ImGui::EndTooltip();
}
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("##Calories and Capacity", childSize))
{
auto& calories = character.calories;
auto& capacity = character.capacity;
auto overstuffedPercent = std::max(ZERO_FLOAT, (calories - capacity) / (character.max_capacity() - capacity));
auto caloriesColor = ImVec4(1.0f, 1.0f - overstuffedPercent, 1.0f - overstuffedPercent, 1.0f);
ImGui::PushStyleColor(ImGuiCol_Text, caloriesColor);
auto caloriesString = std::format("{:.0f} kcal / {:.0f} kcal", calories,
character.is_over_capacity() ? character.max_capacity() : character.capacity);
imgui::text_unformatted_fit_draw(caloriesString.c_str(),
ImVec2(ImGui::GetContentRegionAvail().x, Font::HEADER_2), Font::HEADER_2);
ImGui::SetItemTooltip("%s", caloriesString.c_str());
ImGui::PopStyleColor();
auto digestionProgress = character.isDigesting
? (float)character.digestionTimer / character.data.root()->digestionTimerMax
: character.digestionProgress / Entity::DIGESTION_MAX;
ImGui::ProgressBar(digestionProgress, ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT),
strings.get(character.isDigesting ? Strings::InfoDigesting : Strings::InfoDigestion).c_str());
if (ImGui::BeginItemTooltip())
{
if (character.isDigesting)
ImGui::TextUnformatted(strings.get(Strings::InfoDigestionInProgress).c_str());
else if (digestionProgress <= ZERO_FLOAT)
ImGui::TextUnformatted(strings.get(Strings::InfoGiveFoodToStartDigesting).c_str());
else
ImGui::Text("%0.2f%%", math::to_percent(digestionProgress));
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
ImGui::Text(strings.get(Strings::InfoDigestionRateFormat).c_str(), character.digestion_rate_get());
ImGui::Text(strings.get(Strings::InfoEatingSpeedFormat).c_str(), character.eatSpeed);
ImGui::PopStyleColor();
ImGui::EndTooltip();
}
}
ImGui::EndChild();
}
void Info::update(Resources& resources, Entity& character, float fadeAlpha)
{
static constexpr auto WIDTH_MULTIPLIER = 0.30f;
static constexpr auto ALPHA_MIN = 0.0f;
static constexpr auto ALPHA_MAX = 1.0f;
static constexpr auto STYLE_VAR_COUNT = 1;
if (fadeAlpha <= ALPHA_MIN) return;
auto& style = ImGui::GetStyle();
auto windowSize = imgui::to_ivec2(ImGui::GetMainViewport()->Size);
auto size = ImVec2(windowSize.x * WIDTH_MULTIPLIER - (style.WindowPadding.x * 2.0f),
info_height_get());
auto pos = ImVec2((windowSize.x * 0.5f) - (size.x * 0.5f), style.WindowPadding.y);
ImGui::SetNextWindowSize(size);
ImGui::SetNextWindowPos(pos);
auto flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove;
if (fadeAlpha < ALPHA_MAX) flags |= ImGuiWindowFlags_NoInputs;
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, style.Alpha * fadeAlpha);
if (ImGui::Begin("##Info", nullptr, flags))
info_content_draw(resources, character, ImGui::GetContentRegionAvail());
ImGui::End();
ImGui::PopStyleVar(STYLE_VAR_COUNT);
}
}
-18
View File
@@ -1,18 +0,0 @@
#pragma once
#include "../../entity.hpp"
#include "../../resources.hpp"
#include <imgui.h>
namespace game::state::play
{
float info_height_get();
void info_content_draw(Resources&, Entity&, ImVec2);
class Info
{
public:
void update(Resources&, Entity&, float fadeAlpha);
};
}
+62
View File
@@ -0,0 +1,62 @@
#include "item_display.hpp"
#include "../../resource/font.hpp"
#include "../../util/color.hpp"
#include "../../util/imgui.hpp"
#include <imgui.h>
namespace game::state::play
{
constexpr auto ZERO_FLOAT = 0.0f;
void item_header_draw(const resource::xml::Schema::ItemEntry& item)
{
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
ImGui::TextWrapped("%s", item.name.c_str());
ImGui::PopFont();
}
void item_summary_draw(Entity& character, resource::xml::Schema& schema, const resource::xml::Schema::ItemEntry& item)
{
using Strings = resource::xml::Strings;
auto& strings = character.data.strings;
auto isCategory = item.categoryID >= 0 && item.categoryID < (int)schema.categories.size();
auto isRarity = item.rarityID >= 0 && item.rarityID < (int)schema.rarities.size();
auto isFlavor = item.flavorID >= 0 && item.flavorID < (int)schema.flavors.size();
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(util::imgui::to_imvec4(util::color::GRAY)));
if (isCategory && isRarity)
ImGui::TextWrapped("-- %s (%s) --", schema.categories[item.categoryID].name.c_str(),
schema.rarities[item.rarityID].name.c_str());
else if (isCategory)
ImGui::TextWrapped("-- %s --", schema.categories[item.categoryID].name.c_str());
if (item.isFlavor && isFlavor)
ImGui::TextWrapped(strings.get(Strings::InventoryFlavorFormat).c_str(), schema.flavors[item.flavorID].name.c_str());
if (item.isCalories) ImGui::TextWrapped(strings.get(Strings::InventoryCaloriesFormat).c_str(), item.calories);
if (item.isCapacityBonus)
ImGui::TextWrapped(strings.get(Strings::InventoryCapacityBonusFormat).c_str(), item.capacityBonus);
if (item.isDigestionBonus)
{
if (item.digestionBonus > ZERO_FLOAT)
ImGui::TextWrapped(strings.get(Strings::InventoryDigestionRateBonusFormat).c_str(),
item.digestionBonus * Entity::UPDATE_RATE);
else if (item.digestionBonus < ZERO_FLOAT)
ImGui::TextWrapped(strings.get(Strings::InventoryDigestionRatePenaltyFormat).c_str(),
item.digestionBonus * Entity::UPDATE_RATE);
}
ImGui::PopStyleColor();
}
void item_tooltip_draw(Entity& character, resource::xml::Schema& schema, const resource::xml::Schema::ItemEntry& item)
{
ImGui::PushTextWrapPos(ImGui::GetFontSize() * ITEM_TOOLTIP_WRAP_FONT_MULTIPLIER);
item_header_draw(item);
ImGui::Separator();
item_summary_draw(character, schema, item);
ImGui::Separator();
ImGui::TextWrapped("%s", item.description.c_str());
ImGui::PopTextWrapPos();
}
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "../../entity.hpp"
namespace game::state::play
{
inline constexpr auto ITEM_TOOLTIP_WRAP_FONT_MULTIPLIER = 24.0f;
void item_header_draw(const resource::xml::Schema::ItemEntry&);
void item_summary_draw(Entity&, resource::xml::Schema&, const resource::xml::Schema::ItemEntry&);
void item_tooltip_draw(Entity&, resource::xml::Schema&, const resource::xml::Schema::ItemEntry&);
}
+230 -123
View File
@@ -1,4 +1,5 @@
#include "item_manager.hpp"
#include "item_display.hpp"
#include <cmath>
#include <string>
@@ -15,44 +16,100 @@ using namespace glm;
namespace game::state::play
{
namespace
static inline float item_spawn_weight_get(resource::xml::Schema& itemSchema, Entity& character, int id)
{
int durability_animation_index_get(const resource::xml::Schema& schema, const resource::xml::Anm2& anm2,
int durability, int durabilityMax)
{
if (durability >= durabilityMax) return -1;
static constexpr auto ZERO_FLOAT = 0.0f;
auto animationName = schema.root()->animationChew + std::to_string(std::max(0, durability));
return anm2.animationMap.contains(animationName) ? anm2.animationMap.at(animationName) : -1;
if (id < 0 || id >= (int)itemSchema.items.size()) return ZERO_FLOAT;
auto& item = itemSchema.items[id];
if (item.isCalories && item.calories > character.max_capacity()) return ZERO_FLOAT;
if (item.rarityID < 0 || item.rarityID >= (int)itemSchema.rarities.size()) return ZERO_FLOAT;
return glm::max(ZERO_FLOAT, itemSchema.rarities[item.rarityID].weight);
}
int ItemManager::spawn_delay_get(Entity& character)
{
static constexpr auto ZERO_FLOAT = 0.0f;
static constexpr auto ONE_INT = 1;
static constexpr auto SPAWN_DELAY_TICKS_DEFAULT = 60;
static constexpr auto SPAWN_DELAY_STAGE_MULTIPLIER_DEFAULT = 1.0f;
auto* itemRoot = character.data.itemSchema.root();
auto stage = glm::clamp(0, character.stage_get(), character.stage_max_get());
auto baseSpawnDelay =
itemRoot && itemRoot->baseSpawnDelayTicks > 0 ? itemRoot->baseSpawnDelayTicks : SPAWN_DELAY_TICKS_DEFAULT;
auto spawnDelayMultiplier = itemRoot && itemRoot->spawnDelayStageMultiplier > ZERO_FLOAT
? itemRoot->spawnDelayStageMultiplier
: SPAWN_DELAY_STAGE_MULTIPLIER_DEFAULT;
return glm::max(ONE_INT, (int)std::round((float)baseSpawnDelay * std::pow(spawnDelayMultiplier, stage)));
}
bool ItemManager::spawn_possible_get(Entity& character)
{
static constexpr auto ZERO_FLOAT = 0.0f;
auto& itemSchema = character.data.itemSchema;
auto* itemRoot = itemSchema.root();
auto spawnLimit = itemRoot && itemRoot->itemSpawnLimit > 0 ? itemRoot->itemSpawnLimit : LIMIT;
if (!itemRoot || (int)items.size() >= spawnLimit || itemSchema.items.empty()) return false;
for (int i = 0; i < (int)itemSchema.items.size(); i++)
if (item_spawn_weight_get(itemSchema, character, i) > ZERO_FLOAT) return true;
return false;
}
bool ItemManager::spawn_available_get(Entity& character)
{
return spawnDelayTicks <= 0 && spawn_possible_get(character);
}
bool ItemManager::spawn_queue(Entity& character, int itemID, bool isLimitIgnored)
{
auto& itemSchema = character.data.itemSchema;
auto* itemRoot = itemSchema.root();
auto spawnLimit = itemRoot && itemRoot->itemSpawnLimit > 0 ? itemRoot->itemSpawnLimit : LIMIT;
if (!itemRoot || itemID < 0 || itemID >= (int)itemSchema.items.size() ||
(!isLimitIgnored && (int)items.size() >= spawnLimit))
return false;
auto& item = itemSchema.items[itemID];
itemRoot->soundSummon.play();
if (item.rarityID >= 0 && item.rarityID < (int)itemSchema.rarities.size())
itemSchema.rarities[item.rarityID].sound.play();
queuedItemIDs.emplace_back(itemID);
spawnDelayTicks = spawn_delay_get(character);
return true;
}
bool ItemManager::spawn_queue(Entity& character)
{
static constexpr auto ZERO_FLOAT = 0.0f;
auto& itemSchema = character.data.itemSchema;
if (!spawn_available_get(character)) return false;
auto total = ZERO_FLOAT;
for (int i = 0; i < (int)itemSchema.items.size(); i++)
total += item_spawn_weight_get(itemSchema, character, i);
if (total <= ZERO_FLOAT) return false;
auto roll = math::random_max(total);
auto accumulator = ZERO_FLOAT;
auto itemID = -1;
for (int i = 0; i < (int)itemSchema.items.size(); i++)
{
auto weight = item_spawn_weight_get(itemSchema, character, i);
if (weight <= ZERO_FLOAT) continue;
itemID = i;
accumulator += weight;
if (roll < accumulator) break;
}
int durability_animation_index_get(const resource::xml::Schema& schema, const Entity& entity, int durability,
int durabilityMax)
{
if (durability >= durabilityMax) return -1;
auto animationName = schema.root()->animationChew + std::to_string(std::max(0, durability));
return entity.animationMap.contains(animationName) ? entity.animationMap.at(animationName) : -1;
}
bool is_finite(glm::vec4 value)
{
return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z) && std::isfinite(value.w) &&
value.z > 0.0f && value.w > 0.0f;
}
glm::vec2 item_spawn_position_get(Entity& character, const glm::vec4& bounds)
{
auto rect = character.rect();
if (!is_finite(rect)) rect = bounds;
auto center = glm::vec2(rect.x + rect.z * 0.5f, rect.y + rect.w);
auto radius = rect.w * 0.5f;
auto angle = glm::pi<float>() + math::random_max(glm::pi<float>());
auto position = center + glm::vec2(std::cos(angle), std::sin(angle)) * radius;
return {glm::clamp(position.x, bounds.x, bounds.x + bounds.z),
glm::clamp(position.y, bounds.y, bounds.y + bounds.w)};
}
if (itemID == -1) return false;
return spawn_queue(character, itemID);
}
void ItemManager::update(Entity& character, Entity& cursor, AreaManager& areaManager, Text& text,
@@ -74,11 +131,8 @@ namespace game::state::play
auto& airResistance = area.airResistance;
auto& dialogue = character.data.dialogue;
auto isOverCapacity = character.is_over_capacity();
auto dialogue_pool_entry_get = [&](resource::xml::Schema::Element::Type type)
{
auto* reference = dialogue.get(type);
return reference ? dialogue.dialogue_pool_entry_get(*reference) : nullptr;
};
auto dialogue_hook_entry_get = [&](resource::xml::Schema::Element::Type type)
{ return dialogue.dialogue_hook_entry_get(type); };
auto particles_queue =
[&](resource::xml::Schema& particleSchema, resource::xml::Schema::Element::Type type, glm::vec2 position)
{
@@ -101,8 +155,8 @@ namespace game::state::play
auto isMouseLeftDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
auto isMouseLeftReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
auto isMouseRightClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
auto isMouseRightDown = ImGui::IsMouseDown(ImGuiMouseButton_Right);
auto isAutomation = this->isAutomation;
auto targetItemIndex = -1;
auto& io = ImGui::GetIO();
@@ -113,7 +167,7 @@ namespace game::state::play
isMouseLeftDown = inputOverride->isMouseLeftDown;
isMouseLeftReleased = inputOverride->isMouseLeftReleased;
isMouseRightClicked = inputOverride->isMouseRightClicked;
isMouseRightDown = inputOverride->isMouseRightDown;
targetItemIndex = inputOverride->targetItemIndex;
isAutomation = isAutomation || inputOverride->isAutomation;
inputOverride.reset();
}
@@ -123,6 +177,11 @@ namespace game::state::play
if (heldItemIndex != -1)
{
heldItemIndex = -1;
pendingEatItemIndex = -1;
pendingEatEventID = -1;
pendingEatAnimationIndex = -1;
isPendingEatRect = false;
isEatTextPending = false;
isJustItemHeldStopped = true;
}
isItemHovered = false;
@@ -132,10 +191,20 @@ namespace game::state::play
isMouseLeftDown = false;
isMouseLeftReleased = false;
isMouseRightClicked = false;
isMouseRightDown = false;
isImguiCaptureMouse = true;
}
auto isEatAnimationFinished = pendingEatAnimationIndex == character.animationIndex &&
(character.state == Entity::STOPPED || character.is_animation_finished());
if (isEatTextPending && isEatAnimationFinished)
{
text.set(dialogue_hook_entry_get(isEatTextOverCapacity ? resource::xml::Schema::Element::EAT_FULL
: resource::xml::Schema::Element::EAT),
character);
isEatTextPending = false;
pendingEatAnimationIndex = -1;
}
if (isJustItemHoveredStopped)
{
cursor.play({cursor.defaultAnimation});
@@ -155,27 +224,24 @@ namespace game::state::play
isItemHovered = false;
particles.clear();
if (isItemHovered != isItemHoveredPrevious && !isItemHovered) isJustItemHoveredStopped = true;
if (spawnDelayTicks > 0) spawnDelayTicks--;
if (discoveredItemIDs.size() != schema.items.size()) discoveredItemIDs.resize(schema.items.size());
for (auto& id : queuedItemIDs)
{
auto position = item_spawn_position_get(character, bounds);
auto spawnRect = areaManager.item_spawn_rect_get(character, bounds);
auto position = glm::vec2(spawnRect.x + math::random_max(spawnRect.z),
spawnRect.y + math::random_max(spawnRect.w));
auto& itemSchema = character.data.itemSchema;
auto& item = itemSchema.items.at(id);
auto& anm2 = itemSchema.anm2s.at(id);
auto durabilityMax = item.isDurability ? item.durability : itemSchema.durability;
auto animationIndex = durability_animation_index_get(itemSchema, anm2, 0, durabilityMax);
items.emplace_back(anm2, position, id, 0, animationIndex);
items.emplace_back(anm2, position, id);
character.totalItemsSpawned++;
if (id >= 0 && id < (int)discoveredItemIDs.size()) discoveredItemIDs[id] = true;
particles_queue(itemSchema, resource::xml::Schema::Element::SUMMON, position);
}
queuedItemIDs.clear();
if (isMouseRightDown)
{
if (cursorRoot)
if (auto animation = cursorRoot->animationReturn.get()) cursor.play({*animation});
}
if (auto heldItem = vector::find(items, heldItemIndex))
{
auto& item = schema.items[heldItem->schemaID];
@@ -191,24 +257,24 @@ namespace game::state::play
rotation *= friction;
rotation = glm::clamp(-ROTATION_MAX, rotation, ROTATION_MAX);
if (schema.categories[item.categoryID].isEdible)
auto& category = schema.categories[item.categoryID];
if (category.useMode == "Edible")
{
auto durabilityMax = item.isDurability ? item.durability : schema.durability;
auto caloriesPerBite = item.isCalories && durabilityMax > 0 ? item.calories / durabilityMax : 0;
auto isCanEat = character.calories + caloriesPerBite <= character.max_capacity();
auto calories = item.isCalories ? item.calories : 0.0f;
auto isCanEat = calories > 0.0f && character.calories + calories <= character.max_capacity();
if (isJustItemHeld)
{
if (!isAutomation)
{
if (isCanEat)
text.set(dialogue_pool_entry_get(isOverCapacity ? resource::xml::Schema::Element::FEED_FULL
text.set(dialogue_hook_entry_get(isOverCapacity ? resource::xml::Schema::Element::FEED_FULL
: resource::xml::Schema::Element::FEED),
character);
else if (caloriesPerBite > character.capacity)
text.set(dialogue_pool_entry_get(resource::xml::Schema::Element::LOW_CAPACITY), character);
else if (calories > character.capacity)
text.set(dialogue_hook_entry_get(resource::xml::Schema::Element::LOW_CAPACITY), character);
else
text.set(dialogue_pool_entry_get(resource::xml::Schema::Element::FULL), character);
text.set(dialogue_hook_entry_get(resource::xml::Schema::Element::FULL), character);
}
isJustItemHeld = false;
}
@@ -220,77 +286,78 @@ namespace game::state::play
auto nullID = character.data.null_id_get(eatArea->null);
auto rect = character.null_frame_rect(nullID);
if (!is_finite(rect))
if (!resource::xml::Anm2::is_rect_valid(rect))
{
auto animationIndex =
character.animation_index_get(eatArea->animation + character.animation_append_id_get());
rect = character.null_frame_rect(nullID, animationIndex, 0.0f);
}
if (isCanEat && math::is_point_in_rectf(rect, heldItem->position))
{
character.play({.animation = eatArea->animation,
.appendID = character.animation_append_id_get(),
.speedMultiplier = character.eatSpeed});
auto eventID = eatArea->event.empty() ? eatArea->eventID : character.data.event_id_get(eatArea->event);
auto isEatAreaActive = isCanEat && math::is_point_in_rectf(rect, heldItem->position);
auto isPendingEatItem = pendingEatItemIndex == heldItemIndex && pendingEatEventID == eventID;
auto isPendingEatAreaActive =
isPendingEatItem && isPendingEatRect && math::is_point_in_rectf(pendingEatRect, heldItem->position);
auto isEatEventActivated = isPendingEatItem && eventID != -1 && character.playedEventID == eventID;
if (character.playedEventID == character.data.event_id_get(eatArea->event))
if (isEatAreaActive || isPendingEatAreaActive)
{
if (isEatEventActivated)
{
heldItem->durability++;
character.consume_played_event();
character.calories += caloriesPerBite;
character.totalCaloriesConsumed += caloriesPerBite;
character.calories += calories;
character.totalCaloriesConsumed += calories;
if (item.isCapacityBonus)
{
character.capacity += item.capacityBonus / durabilityMax;
character.capacity += item.capacityBonus;
character.capacity = glm::clamp(character.capacity, (float)character.data.root()->capacityMinCalories,
(float)character.data.root()->capacityMaxCalories);
}
if (item.isEatSpeedBonus)
{
character.eatSpeed += item.eatSpeedBonus / durabilityMax;
character.eatSpeed = glm::clamp(character.eatSpeed, (float)character.data.root()->eatSpeedMinMultiplier,
(float)character.data.root()->eatSpeedMaxMultiplier);
}
if (item.isDigestionBonus)
{
character.digestionRate += item.digestionBonus / durabilityMax;
character.digestionRate += item.digestionBonus;
character.digestionRate =
glm::clamp(character.digestionRate, (float)character.data.root()->digestionRateMin,
(float)character.data.root()->digestionRateMax);
}
if (heldItem->durability >= durabilityMax)
character.totalItemsConsumed++;
queuedRemoveItemIndex = heldItemIndex;
heldItemIndex = -1;
pendingEatItemIndex = -1;
pendingEatEventID = -1;
pendingEatAnimationIndex = character.animationIndex;
isPendingEatRect = false;
if (!isAutomation)
{
isQueueFinishFood = true;
character.totalFoodItemsEaten++;
queuedRemoveItemIndex = heldItemIndex;
heldItemIndex = -1;
isEatTextPending = true;
isEatTextOverCapacity = isOverCapacity;
}
else
break;
}
else
{
pendingEatItemIndex = heldItemIndex;
pendingEatEventID = eventID;
if (isEatAreaActive)
{
auto animationIndex =
durability_animation_index_get(schema, *heldItem, heldItem->durability, durabilityMax);
heldItem->play({.index = animationIndex, .mode = Entity::SET});
pendingEatRect = rect;
isPendingEatRect = true;
}
if (auto animation = character.animation_get(eatArea->animation + character.animation_append_id_get());
!animation || !character.is_playing(animation->name))
character.play({.animation = eatArea->animation,
.appendID = character.animation_append_id_get(),
.interrupt = Entity::Interrupt::ALWAYS});
}
}
if (isMouseLeftReleased)
else if (pendingEatItemIndex == heldItemIndex)
{
if (fabs(delta.x) >= THROW_THRESHOLD || fabs(delta.y) >= THROW_THRESHOLD)
{
if (cursorRoot) cursorRoot->soundThrow.play();
if (!isAutomation) text.set(dialogue_pool_entry_get(resource::xml::Schema::Element::THROW), character);
isJustItemThrown = true;
}
else if (cursorRoot)
cursorRoot->soundRelease.play();
heldItem->velocity -= delta;
heldItemIndex = -1;
isJustItemHeldStopped = true;
pendingEatItemIndex = -1;
pendingEatEventID = -1;
isPendingEatRect = false;
}
// Food stolen
@@ -299,30 +366,65 @@ namespace game::state::play
{
if (!math::is_point_in_rectf(rect, heldItem->position))
if (!isAutomation)
text.set(dialogue_pool_entry_get(isOverCapacity ? resource::xml::Schema::Element::FOOD_TAKEN_FULL
text.set(dialogue_hook_entry_get(isOverCapacity ? resource::xml::Schema::Element::FOOD_TAKEN_FULL
: resource::xml::Schema::Element::FOOD_TAKEN),
character);
}
if (isEatAreaActive) break;
}
}
else if (category.useMode == "Tool")
{
auto rect = character.rect();
if (item.isToggleSpritesheet && resource::xml::Anm2::is_rect_valid(rect) &&
math::is_point_in_rectf(rect, heldItem->position))
{
character.spritesheet_set(character.spritesheetType == Entity::NORMAL ? Entity::ALTERNATE : Entity::NORMAL);
if (auto* alternate = character.data.alternate_spritesheet()) alternate->soundEntry.sound.play();
queuedRemoveItemIndex = heldItemIndex;
heldItemIndex = -1;
pendingEatItemIndex = -1;
pendingEatEventID = -1;
pendingEatAnimationIndex = -1;
isPendingEatRect = false;
}
}
}
if (auto animation = character.animation_get(); character.time >= animation->frameNum && isQueueFinishFood)
{
if (!isAutomation)
text.set(dialogue_pool_entry_get(isOverCapacity ? resource::xml::Schema::Element::EAT_FULL
: resource::xml::Schema::Element::EAT),
character);
isQueueFinishFood = false;
heldItem = vector::find(items, heldItemIndex);
if (heldItem && isMouseLeftReleased)
{
if (fabs(delta.x) >= THROW_THRESHOLD || fabs(delta.y) >= THROW_THRESHOLD)
{
if (cursorRoot) cursorRoot->soundThrow.play();
if (!isAutomation) text.set(dialogue_hook_entry_get(resource::xml::Schema::Element::THROW), character);
isJustItemThrown = true;
}
else if (cursorRoot)
cursorRoot->soundRelease.play();
heldItem->velocity -= delta;
heldItemIndex = -1;
pendingEatItemIndex = -1;
pendingEatEventID = -1;
pendingEatAnimationIndex = -1;
isPendingEatRect = false;
isJustItemHeldStopped = true;
}
}
if (queuedRemoveItemIndex > -1)
{
items.erase(items.begin() + queuedRemoveItemIndex);
queuedRemoveItemIndex = -1;
pendingEatItemIndex = -1;
pendingEatEventID = -1;
isPendingEatRect = false;
}
int heldItemMoveIndex = -1;
int hoveredTooltipItemID = -1;
bool isHoveredTooltipItemHeld = false;
for (int i = 0; i < (int)items.size(); i++)
{
auto& item = items[i];
@@ -330,12 +432,16 @@ namespace game::state::play
auto& rotationOverride = item.overrides[item.rotationOverrideID];
auto& rotation = *rotationOverride.frame.rotation;
auto& gravity = schemaItem.isGravity ? schemaItem.gravity : area.gravity;
auto isTargetItemLocked = targetItemIndex >= 0 && targetItemIndex < (int)items.size();
item.update();
if (math::is_point_in_rectf(item.rect(), cursorPosition) && !isImguiCaptureMouse)
if (math::is_point_in_rectf(item.rect(), cursorPosition) && !isImguiCaptureMouse &&
(!isTargetItemLocked || i == targetItemIndex))
{
isItemHovered = true;
hoveredTooltipItemID = item.schemaID;
isHoveredTooltipItemHeld = heldItemIndex == i;
if (cursorRoot)
if (auto animation = cursorRoot->animationHover.get())
cursor.play({.animation = *animation, .transition = Entity::Transition::IF_IDLE});
@@ -360,17 +466,11 @@ namespace game::state::play
if (isMouseRightClicked)
{
if (item.durability > 0)
{
schema.root()->soundDispose.play();
particles_queue(schema, resource::xml::Schema::Element::DISPOSE, item.position);
}
else
{
schema.root()->soundReturn.play();
particles_queue(schema, resource::xml::Schema::Element::RETURN_, item.position);
returnItemIDs.emplace_back(item.schemaID);
}
schema.root()->soundDispose.play();
if (cursorRoot)
if (auto animation = cursorRoot->animationDispose.get())
cursor.play({.animation = *animation, .mode = Entity::PLAY_FORCE, .interrupt = Entity::Interrupt::NEVER});
particles_queue(schema, resource::xml::Schema::Element::DISPOSE, item.position);
if (heldItemIndex == i) heldItemIndex = -1;
if (heldItemMoveIndex == i) heldItemMoveIndex = -1;
@@ -436,6 +536,13 @@ namespace game::state::play
item.position.y = glm::clamp(bounds.y, item.position.y, bounds.w);
}
if (cursor.entityType == CURSOR && hoveredTooltipItemID != -1 && !isHoveredTooltipItemHeld &&
ImGui::BeginTooltip())
{
item_tooltip_draw(character, schema, schema.items[hoveredTooltipItemID]);
ImGui::EndTooltip();
}
if (heldItemMoveIndex != -1 && heldItemMoveIndex < (int)items.size() - 1)
{
auto heldItem = std::move(items[heldItemMoveIndex]);
+15 -5
View File
@@ -19,6 +19,13 @@ namespace game::state::play
std::vector<Entity> items{};
int heldItemIndex{-1};
int queuedRemoveItemIndex{-1};
int pendingEatItemIndex{-1};
int pendingEatEventID{-1};
int pendingEatAnimationIndex{-1};
glm::vec4 pendingEatRect{};
bool isPendingEatRect{};
bool isEatTextPending{};
bool isEatTextOverCapacity{};
bool isItemHovered{};
bool isItemHoveredPrevious{};
@@ -30,16 +37,14 @@ namespace game::state::play
bool isJustItemHeld{};
bool isJustItemThrown{};
bool isQueueFinishFood{};
bool isItemFinished{};
glm::vec2 cursorPositionPrevious{};
glm::vec2 cursorDeltaPrevious{};
std::vector<int> queuedItemIDs{};
std::vector<int> returnItemIDs{};
std::vector<bool> discoveredItemIDs{};
int spawnDelayTicks{};
struct Particle
{
std::string label{};
@@ -53,13 +58,18 @@ namespace game::state::play
bool isMouseLeftDown{};
bool isMouseLeftReleased{};
bool isMouseRightClicked{};
bool isMouseRightDown{};
bool isAutomation{};
int targetItemIndex{-1};
};
std::optional<Input> inputOverride{};
std::vector<Particle> particles{};
int spawn_delay_get(Entity&);
bool spawn_possible_get(Entity&);
bool spawn_available_get(Entity&);
bool spawn_queue(Entity&, int itemID, bool isLimitIgnored = false);
bool spawn_queue(Entity&);
void update(Entity&, Entity&, AreaManager&, Text&, const glm::vec4& bounds, Canvas&);
};
}
+12 -72
View File
@@ -14,25 +14,19 @@ namespace game::state::play
void Menu::update(Resources& resources, ItemManager& itemManager, Entity& character, Text& text, Autofeed& autofeed)
{
static constexpr auto WIDTH_MULTIPLIER = 0.30f;
static constexpr auto HALF_MULTIPLIER = 0.5f;
static constexpr auto BAR_DIVIDER_THICKNESS = 1.0f;
static constexpr auto PADDING_SIZE_MULTIPLIER = 2.0f;
static constexpr auto ZERO_FLOAT = 0.0f;
static constexpr auto ONE_FLOAT = 1.0f;
static constexpr auto MENU_STYLE_VAR_COUNT = 2;
static constexpr auto MENU_BAR_STYLE_VAR_COUNT = 4;
static constexpr auto MENU_BAR_FLAGS = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
auto& schema = character.data.menuSchema;
auto& strings = character.data.strings;
auto style = ImGui::GetStyle();
auto& io = ImGui::GetIO();
slide.update(isOpen, io.DeltaTime);
fullscreenSlide.update(isOpen && isFullscreen, io.DeltaTime);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
@@ -40,21 +34,12 @@ namespace game::state::play
auto windowSize = imgui::to_ivec2(ImGui::GetMainViewport()->Size);
auto barSize =
ImVec2(imgui::side_bar_width_get(), windowSize.y - style.WindowPadding.y * PADDING_SIZE_MULTIPLIER);
auto fullscreenOffset = barSize.x;
auto normalSize =
ImVec2(windowSize.x * WIDTH_MULTIPLIER, windowSize.y - style.WindowPadding.y * PADDING_SIZE_MULTIPLIER);
auto fullscreenSize =
ImVec2(windowSize.x - fullscreenOffset, windowSize.y - style.WindowPadding.y * PADDING_SIZE_MULTIPLIER);
auto fullscreenT = fullscreenSlide.eased_get();
auto size = ImVec2(normalSize.x + (fullscreenSize.x - normalSize.x) * fullscreenT,
normalSize.y + (fullscreenSize.y - normalSize.y) * fullscreenT);
auto size = normalSize;
auto targetX = windowSize.x - size.x;
auto t = slide.value_get();
auto eased = slide.eased_get();
auto isFullscreenVisible = fullscreenSlide.value_get() > ZERO_FLOAT;
auto isFullscreenResizeSettled =
isFullscreen ? fullscreenSlide.value_get() >= ONE_FLOAT : fullscreenSlide.value_get() <= ZERO_FLOAT;
auto posX = windowSize.x + (targetX - windowSize.x) * eased;
auto pos = ImVec2(posX, style.WindowPadding.y);
@@ -74,19 +59,13 @@ namespace game::state::play
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabInteract).c_str())))
{
interact.update(resources, text, character, isFullscreenVisible);
ImGui::EndTabItem();
}
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabGather).c_str())))
{
gather.update(isFullscreen, isFullscreenResizeSettled);
interact.update(resources, itemManager, text, character, autofeed, false);
ImGui::EndTabItem();
}
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabInventory).c_str())))
{
inventory.update(resources, itemManager, character, autofeed);
inventory.update(resources, itemManager, character);
ImGui::EndTabItem();
}
@@ -99,7 +78,7 @@ namespace game::state::play
if (isCheats && WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabCheats).c_str())))
{
cheats.update(resources, character, inventory);
cheats.update(resources, character);
ImGui::EndTabItem();
}
}
@@ -119,8 +98,6 @@ namespace game::state::play
{
auto buttonSize = ImGui::GetContentRegionAvail();
auto cursorPos = ImGui::GetCursorScreenPos();
auto halfButtonSize = ImVec2(buttonSize.x, buttonSize.y * HALF_MULTIPLIER);
auto activeButtonSize = isOpen && !isFullscreenVisible ? halfButtonSize : buttonSize;
auto tooltip_set = [&](const std::string& tooltip)
{
@@ -129,65 +106,28 @@ namespace game::state::play
ImGui::PopStyleVar();
};
auto isMainResult = WIDGET_FX(ImGui::Button("##MenuToggleNormal", activeButtonSize));
auto isMainResult = WIDGET_FX(ImGui::Button("##MenuToggleNormal", buttonSize));
if (t <= 0.0f || t >= 1.0f)
{
tooltip_set(strings.get(isFullscreenVisible ? Strings::MenuRestoreTooltip
: isOpen ? Strings::MenuCloseTooltip
: Strings::MenuOpenTooltip));
tooltip_set(strings.get(isOpen ? Strings::MenuCloseTooltip : Strings::MenuOpenTooltip));
if (isMainResult)
{
if (isFullscreenVisible)
{
if (isFullscreen) schema.root()->soundClose.play();
isFullscreen = false;
}
isOpen = !isOpen;
if (isOpen)
character.data.menuSchema.root()->soundOpen.play();
else
{
isOpen = !isOpen;
isFullscreen = false;
if (isOpen)
schema.root()->soundOpen.play();
else
schema.root()->soundClose.play();
}
character.data.menuSchema.root()->soundClose.play();
}
if (!isOpen && !isMainResult && ImGui::IsItemHovered())
{
isOpen = true;
isFullscreen = false;
schema.root()->soundOpen.play();
}
}
auto fullscreenCursorPos = ImGui::GetCursorScreenPos();
auto isFullscreenResult = false;
if (isOpen && !isFullscreenVisible)
{
isFullscreenResult = WIDGET_FX(ImGui::Button("##MenuToggleFullscreen", halfButtonSize));
if (t <= 0.0f || t >= 1.0f)
{
tooltip_set(strings.get(Strings::MenuOpenFullscreenTooltip));
if (isFullscreenResult)
{
isFullscreen = true;
schema.root()->soundOpen.play();
}
character.data.menuSchema.root()->soundOpen.play();
}
}
auto direction = !isOpen ? imgui::TriangleDirection::LEFT : imgui::TriangleDirection::RIGHT;
imgui::triangle_draw(*ImGui::GetWindowDrawList(), cursorPos, activeButtonSize, direction,
imgui::triangle_draw(*ImGui::GetWindowDrawList(), cursorPos, buttonSize, direction,
ImGui::GetColorU32(ImGuiCol_Text));
if (isOpen && !isFullscreenVisible)
{
auto separatorY = cursorPos.y + halfButtonSize.y;
ImGui::GetWindowDrawList()->AddLine(ImVec2(cursorPos.x, separatorY),
ImVec2(cursorPos.x + buttonSize.x, separatorY),
ImGui::GetColorU32(ImGuiCol_Separator), BAR_DIVIDER_THICKNESS);
imgui::triangle_draw(*ImGui::GetWindowDrawList(), fullscreenCursorPos, halfButtonSize,
imgui::TriangleDirection::LEFT, ImGui::GetColorU32(ImGuiCol_Text));
}
}
ImGui::End();
ImGui::PopStyleVar(MENU_BAR_STYLE_VAR_COUNT);
-7
View File
@@ -5,7 +5,6 @@
#include "../settings_menu.hpp"
#include "cheats.hpp"
#include "menu/gather.hpp"
#include "menu/interact.hpp"
#include "menu/inventory.hpp"
#include "text.hpp"
@@ -18,7 +17,6 @@ namespace game::state::play
{
public:
menu::Interact interact;
menu::Gather gather;
Cheats cheats;
menu::Inventory inventory;
state::SettingsMenu settingsMenu;
@@ -30,14 +28,9 @@ namespace game::state::play
#endif
bool isOpen{true};
bool isFullscreen{};
static constexpr auto SLIDE_DURATION = 0.125f;
static constexpr auto SLIDE_CLOSED = 0.0f;
util::imgui::WindowSlide slide{};
util::imgui::WindowSlide fullscreenSlide{SLIDE_DURATION, SLIDE_CLOSED};
bool is_fullscreen_visible_get() const { return isOpen && (isFullscreen || fullscreenSlide.is_visible()); }
float fullscreen_alpha_get() const { return fullscreenSlide.eased_get(); }
void update(Resources&, ItemManager&, Entity&, Text&, Autofeed&);
};
}
-619
View File
@@ -1,619 +0,0 @@
#include "gather.hpp"
#include "../../../resource/font.hpp"
#include "../../../util/imgui.hpp"
#include "../../../util/math.hpp"
#include "../../../util/time.hpp"
#include <algorithm>
#include <cmath>
#include <format>
#include <imgui.h>
namespace game::state::play::menu
{
constexpr auto CANVAS_MIN_SIZE = 1.0f;
constexpr auto CENTER_RATIO = 0.5f;
constexpr auto START_TEXT_Y_RATIO = 0.75f;
constexpr auto PLAYER_ACCELERATION = 3.0f;
constexpr auto PLAYER_FRICTION_PER_SECOND = 0.72f;
constexpr auto PLAYER_SIZE = 32.0f;
constexpr auto PLAYER_VELOCITY_MAX = 720.0f;
constexpr auto PLAYER_VELOCITY_STOP = 0.0f;
constexpr auto PLAYER_BOOST_VELOCITY_GAIN = 320.0f;
constexpr auto PLAYER_BOOST_VELOCITY_MIN = 540.0f;
constexpr auto PLAYER_BOOST_DURATION = 0.35f;
constexpr auto BOOST_DIRECTION_EPSILON = 0.001f;
constexpr auto BOOST_DIRECTION_UNIT = 1.0f;
constexpr auto GAME_TEXT_FONT_SIZE = resource::Font::HEADER_1;
constexpr auto STAMINA_MIN = 0.0f;
constexpr auto STAMINA_MAX = 1.0f;
constexpr auto STAMINA_REGEN_PER_TICK = 0.002f;
constexpr auto STAMINA_BOOST_COST = 0.25f;
constexpr auto STAMINA_MAGNET_COST = 0.25f;
constexpr auto STAMINA_BAR_WIDTH = 120.0f;
constexpr auto STAMINA_BAR_HEIGHT = GAME_TEXT_FONT_SIZE;
constexpr auto STAMINA_BAR_PADDING = 8.0f;
constexpr auto STAMINA_BAR_PADDING_MULTIPLIER = 2.0f;
constexpr auto STAMINA_BAR_ROUNDING = 2.0f;
constexpr auto COLLECTIBLE_SIZE = 20.0f;
constexpr auto COLLECTIBLE_OSCILLATION_SPEED = 6.0f;
constexpr auto COLLECTIBLE_OSCILLATION_AMOUNT = 3.0f;
constexpr auto COLLECTIBLE_MAGNET_RADIUS = 72.0f;
constexpr auto COLLECTIBLE_MAGNET_RADIUS_EXTENDED = 240.0f;
constexpr auto COLLECTIBLE_MAGNET_RADIUS_EXTEND_SECONDS = 1.0f;
constexpr auto COLLECTIBLE_MAGNET_RADIUS_WINDOWED_MULTIPLIER = 0.5f;
constexpr auto COLLECTIBLE_MAGNET_ACCELERATION = 5200.0f;
constexpr auto COLLECTIBLE_MAGNET_ACCELERATION_GAIN = 26000.0f;
constexpr auto COLLECTIBLE_MAGNET_ACCELERATION_MAX = 26000.0f;
constexpr auto COLLECTIBLE_MAGNET_VELOCITY_MIN = 900.0f;
constexpr auto COLLECTIBLE_MAGNET_SPEED_MAX = 1800.0f;
constexpr auto COLLECTIBLE_MAGNET_FRICTION_PER_SECOND = 0.98f;
constexpr auto COLLECTIBLE_MAGNET_SIDE_VELOCITY_KEEP = 0.35f;
constexpr auto COLLECTIBLE_SPAWN_MARGIN_MULTIPLIER = 2.0f;
constexpr auto COLLECTIBLE_STRUCTURE_SPACING = 34.0f;
constexpr auto COLLECTIBLE_STRUCTURE_COUNT_MIN = 3;
constexpr auto COLLECTIBLE_POLYGON_RADIUS_BASE = 28.0f;
constexpr auto COLLECTIBLE_POLYGON_RADIUS_PER_COLLECTIBLE = 4.5f;
constexpr auto STAMINA_PICKUP_COUNT = 1;
constexpr auto COLLECTIBLE_TWO_PI = 6.283185f;
constexpr auto COLLECTIBLE_POLYGON_CHANCE = 0.20f;
constexpr auto COLLECTIBLE_LINE_CHANCE = 0.55f;
constexpr auto SCORE_TEXT_PADDING = 8.0f;
constexpr auto SCORE_TEXT_LINE_MULTIPLIER = 1.15f;
constexpr auto GAME_TIME_START_SECONDS = 30.0f;
constexpr auto GAME_TIME_START_TICKS = GAME_TIME_START_SECONDS * Entity::UPDATE_RATE;
constexpr auto GAME_TIME_GAIN_SECONDS = 5.0f;
constexpr auto GAME_TIME_GAIN_TICKS = GAME_TIME_GAIN_SECONDS * Entity::UPDATE_RATE;
constexpr auto WAVE_RESET_FADE_SECONDS = 0.125f;
constexpr auto TIME_GAIN_ALPHA_MAX = 1.0f;
constexpr auto TIME_GAIN_FADE_SECONDS = 1.25f;
constexpr auto TIME_TEXT_PADDING = 8.0f;
constexpr auto COLOR_CHANNEL_MAX = 255;
constexpr auto COLOR_GREEN_DIM = 64;
constexpr auto COLOR_GREEN = 220;
constexpr auto COLOR_COLLECTIBLE_BLUE = 255;
constexpr auto COLOR_TIME = 255;
constexpr auto COLOR_MAGNET_BLUE = 255;
constexpr auto COLOR_MAGNET_ALPHA = 70;
constexpr auto MAGNET_CIRCLE_SEGMENTS = 96;
constexpr auto MAGNET_CIRCLE_THICKNESS = 2.0f;
constexpr auto COLOR_COLLECTIBLE_ALPHA = 255;
constexpr auto COLOR_COLLECTIBLE_NEXT_ALPHA = 35;
constexpr auto STYLE_VAR_COUNT = 1;
constexpr auto CANVAS_FLAGS = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
float Gather::random_range_get(float min, float max)
{
if (min >= max) return min;
return util::math::random_in_range(min, max);
}
glm::vec2 Gather::random_position_get(float minX, float minY, float maxX, float maxY)
{
return {random_range_get(minX, maxX), random_range_get(minY, maxY)};
}
Entity Gather::collectible_make(glm::vec2 position)
{
Entity collectible{};
collectible.position = position;
collectible.phase = util::math::random_max(COLLECTIBLE_TWO_PI);
return collectible;
}
Gather::Structure Gather::structure_get(int remaining)
{
auto roll = util::math::random();
if (remaining < COLLECTIBLE_STRUCTURE_COUNT_MIN) return Structure::SINGLE;
if (roll < COLLECTIBLE_POLYGON_CHANCE) return Structure::POLYGON;
if (roll < COLLECTIBLE_LINE_CHANCE)
return util::math::random_bool() ? Structure::LINE_HORIZONTAL : Structure::LINE_VERTICAL;
return Structure::SINGLE;
}
int Gather::structure_count_get(int remaining)
{
auto countRange = (remaining - COLLECTIBLE_STRUCTURE_COUNT_MIN) + 1;
auto countRoll = std::min(countRange - 1, (int)util::math::random_max((float)countRange));
return COLLECTIBLE_STRUCTURE_COUNT_MIN + countRoll;
}
int Gather::wave_collectible_count_get(int wave) { return std::max(1, wave); }
void Gather::collectible_add(std::vector<Entity>& wave, glm::vec2 position, int targetCount)
{
if ((int)wave.size() >= targetCount) return;
wave.emplace_back(collectible_make(position));
}
void Gather::single_spawn(std::vector<Entity>& wave, ImVec2 canvasSize, float margin, int targetCount)
{
auto position = random_position_get(margin, margin, canvasSize.x - margin, canvasSize.y - margin);
collectible_add(wave, position, targetCount);
}
void Gather::line_spawn(std::vector<Entity>& wave, ImVec2 canvasSize, float margin, bool isHorizontal,
int structureCount, int targetCount)
{
auto structureHalf = COLLECTIBLE_STRUCTURE_SPACING * ((float)structureCount - 1) * CENTER_RATIO;
auto clearance = structureHalf + (COLLECTIBLE_SIZE * CENTER_RATIO);
auto center =
isHorizontal
? random_position_get(margin + clearance, margin, canvasSize.x - margin - clearance, canvasSize.y - margin)
: random_position_get(margin, margin + clearance, canvasSize.x - margin, canvasSize.y - margin - clearance);
for (auto i = 0; i < structureCount; ++i)
{
auto offset = ((float)i - (((float)structureCount - 1) * CENTER_RATIO)) * COLLECTIBLE_STRUCTURE_SPACING;
auto position = isHorizontal ? glm::vec2(center.x + offset, center.y) : glm::vec2(center.x, center.y + offset);
collectible_add(wave, position, targetCount);
}
}
float Gather::polygon_radius_get(int structureCount)
{
return COLLECTIBLE_POLYGON_RADIUS_BASE + ((float)structureCount * COLLECTIBLE_POLYGON_RADIUS_PER_COLLECTIBLE);
}
void Gather::polygon_spawn(std::vector<Entity>& wave, ImVec2 canvasSize, float margin, int structureCount,
int targetCount)
{
auto radius = polygon_radius_get(structureCount);
auto clearance = radius + (COLLECTIBLE_SIZE * CENTER_RATIO);
auto center = random_position_get(margin + clearance, margin + clearance, canvasSize.x - margin - clearance,
canvasSize.y - margin - clearance);
for (auto i = 0; i < structureCount; ++i)
{
auto angle = COLLECTIBLE_TWO_PI * ((float)i / (float)structureCount);
auto position = center + glm::vec2(std::cos(angle), std::sin(angle)) * radius;
collectible_add(wave, position, targetCount);
}
}
void Gather::wave_spawn(std::vector<Entity>& wave, ImVec2 canvasSize, int waveNumber)
{
wave.clear();
auto targetCount = wave_collectible_count_get(waveNumber);
auto margin = PLAYER_SIZE * COLLECTIBLE_SPAWN_MARGIN_MULTIPLIER;
while ((int)wave.size() < targetCount)
{
auto remaining = targetCount - (int)wave.size();
switch (structure_get(remaining))
{
case Structure::LINE_HORIZONTAL:
line_spawn(wave, canvasSize, margin, true, structure_count_get(remaining), targetCount);
break;
case Structure::LINE_VERTICAL:
line_spawn(wave, canvasSize, margin, false, structure_count_get(remaining), targetCount);
break;
case Structure::POLYGON:
polygon_spawn(wave, canvasSize, margin, structure_count_get(remaining), targetCount);
break;
case Structure::SINGLE:
default:
single_spawn(wave, canvasSize, margin, targetCount);
break;
}
}
}
int Gather::stamina_pickup_count_get(int) { return STAMINA_PICKUP_COUNT; }
void Gather::stamina_pickups_spawn(std::vector<Entity>& pickups, ImVec2 canvasSize, int waveNumber)
{
pickups.clear();
auto targetCount = stamina_pickup_count_get(waveNumber);
auto margin = PLAYER_SIZE * COLLECTIBLE_SPAWN_MARGIN_MULTIPLIER;
while ((int)pickups.size() < targetCount)
single_spawn(pickups, canvasSize, margin, targetCount);
}
float Gather::wave_time_gain_ticks_get() { return GAME_TIME_GAIN_TICKS; }
bool Gather::collectible_hit_get(const Entity& collectible, const Entity& player)
{
auto playerHalf = PLAYER_SIZE * CENTER_RATIO;
auto collectibleHalf = COLLECTIBLE_SIZE * CENTER_RATIO;
auto distance = glm::abs(player.position - collectible.position);
return distance.x <= playerHalf + collectibleHalf && distance.y <= playerHalf + collectibleHalf;
}
float Gather::magnet_radius_get(float magnetRadiusTime, bool isMenuFullscreen)
{
auto multiplier = isMenuFullscreen ? STAMINA_MAX : COLLECTIBLE_MAGNET_RADIUS_WINDOWED_MULTIPLIER;
auto baseRadius = COLLECTIBLE_MAGNET_RADIUS * multiplier;
auto extendedRadius = COLLECTIBLE_MAGNET_RADIUS_EXTENDED * multiplier;
auto ratio = std::clamp(magnetRadiusTime / COLLECTIBLE_MAGNET_RADIUS_EXTEND_SECONDS, STAMINA_MIN, STAMINA_MAX);
return baseRadius + ((extendedRadius - baseRadius) * ratio);
}
int Gather::collectibles_collect(std::vector<Entity>& wave, const Entity& player)
{
auto collected = 0;
for (auto& collectible : wave)
{
if (collectible.isCollected || !collectible_hit_get(collectible, player)) continue;
collectible.isCollected = true;
++collected;
}
return collected;
}
void Gather::collectibles_magnet_update(std::vector<Entity>& wave, const Entity& player, ImVec2 canvasSize,
float magnetRadius, float dt)
{
auto radiusSquared = magnetRadius * magnetRadius;
auto collectibleHalf = COLLECTIBLE_SIZE * CENTER_RATIO;
auto minX = collectibleHalf;
auto minY = collectibleHalf;
auto maxX = std::max(minX, canvasSize.x - collectibleHalf);
auto maxY = std::max(minY, canvasSize.y - collectibleHalf);
for (auto& collectible : wave)
{
if (collectible.isCollected) continue;
auto direction = player.position - collectible.position;
auto distanceSquared = (direction.x * direction.x) + (direction.y * direction.y);
if (distanceSquared <= radiusSquared) collectible.isMagnetized = true;
if (collectible.isMagnetized && distanceSquared > BOOST_DIRECTION_EPSILON)
{
collectible.magnetTime += dt;
auto magnetAcceleration =
std::min(COLLECTIBLE_MAGNET_ACCELERATION_MAX,
COLLECTIBLE_MAGNET_ACCELERATION + (COLLECTIBLE_MAGNET_ACCELERATION_GAIN * collectible.magnetTime));
auto distance = std::sqrt(distanceSquared);
auto directionUnit = direction * (BOOST_DIRECTION_UNIT / distance);
auto inwardVelocity = (collectible.velocity.x * directionUnit.x) + (collectible.velocity.y * directionUnit.y);
auto inward = directionUnit * inwardVelocity;
auto sideways = collectible.velocity - inward;
collectible.velocity = inward + (sideways * COLLECTIBLE_MAGNET_SIDE_VELOCITY_KEEP);
collectible.velocity += directionUnit * magnetAcceleration * dt;
inwardVelocity = (collectible.velocity.x * directionUnit.x) + (collectible.velocity.y * directionUnit.y);
if (inwardVelocity < COLLECTIBLE_MAGNET_VELOCITY_MIN)
collectible.velocity += directionUnit * (COLLECTIBLE_MAGNET_VELOCITY_MIN - inwardVelocity);
}
auto velocityLength = std::sqrt((collectible.velocity.x * collectible.velocity.x) +
(collectible.velocity.y * collectible.velocity.y));
if (velocityLength > COLLECTIBLE_MAGNET_SPEED_MAX)
{
auto velocityScale = COLLECTIBLE_MAGNET_SPEED_MAX / velocityLength;
collectible.velocity *= velocityScale;
}
auto velocityRetention = std::pow(COLLECTIBLE_MAGNET_FRICTION_PER_SECOND, dt);
collectible.velocity *= velocityRetention;
collectible.position += collectible.velocity * dt;
if (collectible.position.x < minX || collectible.position.x > maxX) collectible.velocity.x = PLAYER_VELOCITY_STOP;
if (collectible.position.y < minY || collectible.position.y > maxY) collectible.velocity.y = PLAYER_VELOCITY_STOP;
collectible.position.x = std::clamp(collectible.position.x, minX, maxX);
collectible.position.y = std::clamp(collectible.position.y, minY, maxY);
}
}
bool Gather::wave_complete_get(const std::vector<Entity>& wave)
{
if (wave.empty()) return false;
for (auto& collectible : wave)
if (!collectible.isCollected) return false;
return true;
}
void Gather::collectibles_draw(const std::vector<Entity>& wave, ImDrawList* drawList, ImVec2 canvasMin, float time,
ImU32 color)
{
for (auto& collectible : wave)
{
if (collectible.isCollected) continue;
auto size = COLLECTIBLE_SIZE + (std::sin((time * COLLECTIBLE_OSCILLATION_SPEED) + collectible.phase) *
COLLECTIBLE_OSCILLATION_AMOUNT);
auto half = size * CENTER_RATIO;
auto min = ImVec2(canvasMin.x + collectible.position.x - half, canvasMin.y + collectible.position.y - half);
auto max = ImVec2(min.x + size, min.y + size);
drawList->AddRectFilled(min, max, color);
}
}
void Gather::magnet_radius_draw(ImDrawList* drawList, ImVec2 canvasMin, const Entity& player, float radius)
{
auto center = ImVec2(canvasMin.x + player.position.x, canvasMin.y + player.position.y);
drawList->AddCircle(center, radius, IM_COL32(0, 0, COLOR_MAGNET_BLUE, COLOR_MAGNET_ALPHA), MAGNET_CIRCLE_SEGMENTS,
MAGNET_CIRCLE_THICKNESS);
}
int Gather::alpha_get(int alpha, float multiplier)
{
return (int)((float)alpha * std::clamp(multiplier, STAMINA_MIN, STAMINA_MAX));
}
void Gather::progress_bar_draw(ImDrawList* drawList, ImVec2 min, ImVec2 max, float progress, ImU32 backgroundColor,
ImU32 fillColor, const std::string& text)
{
auto fillMax = ImVec2(min.x + ((max.x - min.x) * std::clamp(progress, STAMINA_MIN, STAMINA_MAX)), max.y);
drawList->AddRectFilled(min, max, backgroundColor, STAMINA_BAR_ROUNDING);
drawList->AddRectFilled(min, fillMax, fillColor, STAMINA_BAR_ROUNDING);
if (text.empty()) return;
auto textSize = util::imgui::draw_list_text_size_get(text, GAME_TEXT_FONT_SIZE);
auto textPos = ImVec2(min.x + ((max.x - min.x - textSize.x) * CENTER_RATIO),
min.y + ((max.y - min.y - textSize.y) * CENTER_RATIO));
util::imgui::draw_list_text_draw(
*drawList, textPos, text, GAME_TEXT_FONT_SIZE,
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
}
void Gather::reset()
{
player = {};
collectibles.clear();
nextCollectibles.clear();
staminaPickups.clear();
nextStaminaPickups.clear();
targetPosition = {};
stamina = STAMINA_MAX;
boostTime = STAMINA_MIN;
magnetRadiusTime = STAMINA_MIN;
timeTicks = GAME_TIME_START_TICKS;
timeGainTicks = STAMINA_MIN;
timeGainAlpha = STAMINA_MIN;
score = 0;
wave = 1;
isStarted = false;
isPaused = false;
isMenuFullscreenKnown = false;
isMenuFullscreenLast = false;
isWaveResetPending = false;
waveResetAlpha = STAMINA_MAX;
}
void Gather::start(ImVec2 canvasSize)
{
reset();
player.position = glm::vec2(canvasSize.x * CENTER_RATIO, canvasSize.y * CENTER_RATIO);
targetPosition = player.position;
isStarted = true;
wave_reset(canvasSize);
}
void Gather::wave_reset(ImVec2 canvasSize)
{
wave_spawn(collectibles, canvasSize, wave);
wave_spawn(nextCollectibles, canvasSize, wave + 1);
stamina_pickups_spawn(staminaPickups, canvasSize, wave);
stamina_pickups_spawn(nextStaminaPickups, canvasSize, wave + 1);
magnetRadiusTime = STAMINA_MIN;
isWaveResetPending = false;
waveResetAlpha = STAMINA_MAX;
}
void Gather::wave_reset_request()
{
isWaveResetPending = true;
waveResetAlpha = STAMINA_MAX;
magnetRadiusTime = STAMINA_MIN;
}
void Gather::score_draw(ImDrawList* drawList, ImVec2 canvasMin, int score, int wave)
{
auto scoreText = std::format("{}", score);
auto position = ImVec2(canvasMin.x + SCORE_TEXT_PADDING, canvasMin.y + SCORE_TEXT_PADDING);
util::imgui::draw_list_text_draw(
*drawList, position, scoreText, GAME_TEXT_FONT_SIZE,
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
auto waveText = std::format("Wave #{}", wave);
auto wavePosition = ImVec2(position.x, position.y + (GAME_TEXT_FONT_SIZE * SCORE_TEXT_LINE_MULTIPLIER));
util::imgui::draw_list_text_draw(
*drawList, wavePosition, waveText, GAME_TEXT_FONT_SIZE,
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
}
void Gather::time_draw(ImDrawList* drawList, ImVec2 canvasMin, ImVec2 canvasMax, float timeTicks, float timeGainTicks,
float timeGainAlpha)
{
auto timeText = util::time::ticks_to_minutes_seconds_centiseconds((int)std::ceil(timeTicks));
auto textSize = util::imgui::draw_list_text_size_get(timeText, GAME_TEXT_FONT_SIZE);
auto textWidth = util::imgui::draw_list_digit_fixed_width_get(timeText, GAME_TEXT_FONT_SIZE);
auto textPos = ImVec2(canvasMax.x - textWidth - TIME_TEXT_PADDING, canvasMin.y + TIME_TEXT_PADDING);
util::imgui::draw_list_digit_fixed_width_draw(*drawList, textPos, timeText, GAME_TEXT_FONT_SIZE,
IM_COL32(COLOR_TIME, COLOR_TIME, COLOR_TIME, COLOR_CHANNEL_MAX));
if (timeGainAlpha <= STAMINA_MIN) return;
auto gainText =
std::format("+{}", util::time::ticks_to_minutes_seconds_centiseconds((int)std::ceil(timeGainTicks)));
auto alpha = (int)(COLOR_CHANNEL_MAX * std::clamp(timeGainAlpha, STAMINA_MIN, STAMINA_MAX));
auto gainTextWidth = util::imgui::draw_list_digit_fixed_width_get(gainText, GAME_TEXT_FONT_SIZE);
auto gainPos = ImVec2(canvasMax.x - gainTextWidth - TIME_TEXT_PADDING, textPos.y + textSize.y + TIME_TEXT_PADDING);
util::imgui::draw_list_digit_fixed_width_draw(*drawList, gainPos, gainText, GAME_TEXT_FONT_SIZE,
IM_COL32(COLOR_TIME, COLOR_TIME, COLOR_TIME, alpha));
}
void Gather::update(bool isMenuFullscreen, bool isMenuResizeSettled)
{
auto canvasSize = ImGui::GetContentRegionAvail();
if (canvasSize.x <= CANVAS_MIN_SIZE || canvasSize.y <= CANVAS_MIN_SIZE) return;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
if (ImGui::BeginChild("##Gather Canvas Child", canvasSize, ImGuiChildFlags_None, CANVAS_FLAGS))
{
canvasSize = ImGui::GetContentRegionAvail();
auto canvasMin = ImGui::GetCursorScreenPos();
auto canvasMax = ImVec2(canvasMin.x + canvasSize.x, canvasMin.y + canvasSize.y);
auto drawList = ImGui::GetWindowDrawList();
auto dt = ImGui::GetIO().DeltaTime;
auto isCanvasFocused = ImGui::IsWindowFocused();
ImGui::InvisibleButton("##Gather Canvas", canvasSize);
auto isCanvasHovered = ImGui::IsItemHovered();
drawList->AddRectFilled(canvasMin, canvasMax, IM_COL32(0, 0, 0, COLOR_CHANNEL_MAX / 2));
if (!isMenuFullscreenKnown)
{
isMenuFullscreenKnown = true;
isMenuFullscreenLast = isMenuFullscreen;
}
auto isMenuFullscreenChanged = isMenuFullscreenLast != isMenuFullscreen;
isMenuFullscreenLast = isMenuFullscreen;
if (isStarted && isMenuFullscreenChanged) wave_reset_request();
if (isStarted && isWaveResetPending)
{
waveResetAlpha = std::max(STAMINA_MIN, waveResetAlpha - (dt / WAVE_RESET_FADE_SECONDS));
if (isMenuResizeSettled) wave_reset(canvasSize);
}
if (!isStarted && isCanvasHovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) start(canvasSize);
if (isStarted)
{
if (isCanvasFocused && ImGui::IsKeyPressed(ImGuiKey_P, false)) isPaused = !isPaused;
auto playerHalf = PLAYER_SIZE * CENTER_RATIO;
auto minX = std::min(playerHalf, canvasSize.x * CENTER_RATIO);
auto minY = std::min(playerHalf, canvasSize.y * CENTER_RATIO);
auto maxX = std::max(minX, canvasSize.x - playerHalf);
auto maxY = std::max(minY, canvasSize.y - playerHalf);
player.position.x = std::clamp(player.position.x, minX, maxX);
player.position.y = std::clamp(player.position.y, minY, maxY);
targetPosition.x = std::clamp(targetPosition.x, minX, maxX);
targetPosition.y = std::clamp(targetPosition.y, minY, maxY);
if (!isPaused)
{
if (isCanvasHovered)
{
auto mouse = ImGui::GetIO().MousePos;
targetPosition =
glm::vec2(std::clamp(mouse.x - canvasMin.x, minX, maxX), std::clamp(mouse.y - canvasMin.y, minY, maxY));
}
timeTicks = std::max(STAMINA_MIN, timeTicks - (Entity::UPDATE_RATE * dt));
if (timeTicks <= STAMINA_MIN) reset();
if (isStarted)
{
timeGainAlpha = std::max(STAMINA_MIN, timeGainAlpha - (dt / TIME_GAIN_FADE_SECONDS));
stamina =
std::clamp(stamina + (STAMINA_REGEN_PER_TICK * Entity::UPDATE_RATE * dt), STAMINA_MIN, STAMINA_MAX);
if (isCanvasHovered && ImGui::IsMouseClicked(ImGuiMouseButton_Middle) && stamina >= STAMINA_MAGNET_COST)
{
magnetRadiusTime = COLLECTIBLE_MAGNET_RADIUS_EXTEND_SECONDS;
stamina = std::max(STAMINA_MIN, stamina - STAMINA_MAGNET_COST);
}
if (isCanvasHovered && ImGui::IsMouseClicked(ImGuiMouseButton_Right) && stamina >= STAMINA_BOOST_COST)
{
auto boostDirection = targetPosition - player.position;
auto boostDirectionLength =
std::sqrt((boostDirection.x * boostDirection.x) + (boostDirection.y * boostDirection.y));
if (boostDirectionLength > BOOST_DIRECTION_EPSILON)
{
auto boostDirectionScale = BOOST_DIRECTION_UNIT / boostDirectionLength;
auto boostDirectionUnit = boostDirection * boostDirectionScale;
player.velocity.x += boostDirectionUnit.x * PLAYER_BOOST_VELOCITY_GAIN;
player.velocity.y += boostDirectionUnit.y * PLAYER_BOOST_VELOCITY_GAIN;
auto boostVelocity =
(player.velocity.x * boostDirectionUnit.x) + (player.velocity.y * boostDirectionUnit.y);
if (boostVelocity < PLAYER_BOOST_VELOCITY_MIN)
{
auto boostVelocityAdd = PLAYER_BOOST_VELOCITY_MIN - boostVelocity;
player.velocity.x += boostDirectionUnit.x * boostVelocityAdd;
player.velocity.y += boostDirectionUnit.y * boostVelocityAdd;
}
boostTime = PLAYER_BOOST_DURATION;
stamina = std::max(STAMINA_MIN, stamina - STAMINA_BOOST_COST);
}
}
player.velocity.x += (targetPosition.x - player.position.x) * PLAYER_ACCELERATION * dt;
player.velocity.y += (targetPosition.y - player.position.y) * PLAYER_ACCELERATION * dt;
auto velocityRetention = std::pow(PLAYER_FRICTION_PER_SECOND, dt);
player.velocity.x *= velocityRetention;
player.velocity.y *= velocityRetention;
auto velocityLength =
std::sqrt((player.velocity.x * player.velocity.x) + (player.velocity.y * player.velocity.y));
auto isBoostActive = boostTime > STAMINA_MIN;
if (!isBoostActive && velocityLength > PLAYER_VELOCITY_MAX)
{
auto velocityScale = PLAYER_VELOCITY_MAX / velocityLength;
player.velocity.x *= velocityScale;
player.velocity.y *= velocityScale;
}
boostTime = std::max(STAMINA_MIN, boostTime - dt);
player.position.x += player.velocity.x * dt;
player.position.y += player.velocity.y * dt;
if (player.position.x < minX || player.position.x > maxX) player.velocity.x = PLAYER_VELOCITY_STOP;
if (player.position.y < minY || player.position.y > maxY) player.velocity.y = PLAYER_VELOCITY_STOP;
player.position.x = std::clamp(player.position.x, minX, maxX);
player.position.y = std::clamp(player.position.y, minY, maxY);
if (!isWaveResetPending)
{
auto magnetRadius = magnet_radius_get(magnetRadiusTime, isMenuFullscreen);
collectibles_magnet_update(collectibles, player, canvasSize, magnetRadius, dt);
magnetRadiusTime = std::max(STAMINA_MIN, magnetRadiusTime - dt);
score += collectibles_collect(collectibles, player);
if (collectibles_collect(staminaPickups, player) > 0) stamina = STAMINA_MAX;
if (wave_complete_get(collectibles))
{
timeGainTicks = wave_time_gain_ticks_get();
timeTicks += timeGainTicks;
timeGainAlpha = TIME_GAIN_ALPHA_MAX;
++wave;
collectibles = nextCollectibles;
staminaPickups = nextStaminaPickups;
wave_spawn(nextCollectibles, canvasSize, wave + 1);
stamina_pickups_spawn(nextStaminaPickups, canvasSize, wave + 1);
}
}
}
}
}
if (isStarted)
{
auto playerHalf = PLAYER_SIZE * CENTER_RATIO;
auto collectibleAlpha = alpha_get(COLOR_COLLECTIBLE_ALPHA, waveResetAlpha);
auto nextCollectibleAlpha = alpha_get(COLOR_COLLECTIBLE_NEXT_ALPHA, waveResetAlpha);
collectibles_draw(nextCollectibles, drawList, canvasMin, (float)ImGui::GetTime(),
IM_COL32(0, 0, COLOR_COLLECTIBLE_BLUE, nextCollectibleAlpha));
collectibles_draw(nextStaminaPickups, drawList, canvasMin, (float)ImGui::GetTime(),
IM_COL32(0, COLOR_GREEN, 0, nextCollectibleAlpha));
collectibles_draw(collectibles, drawList, canvasMin, (float)ImGui::GetTime(),
IM_COL32(0, 0, COLOR_COLLECTIBLE_BLUE, collectibleAlpha));
collectibles_draw(staminaPickups, drawList, canvasMin, (float)ImGui::GetTime(),
IM_COL32(0, COLOR_GREEN, 0, collectibleAlpha));
magnet_radius_draw(drawList, canvasMin, player, magnet_radius_get(magnetRadiusTime, isMenuFullscreen));
auto playerMin =
ImVec2(canvasMin.x + player.position.x - playerHalf, canvasMin.y + player.position.y - playerHalf);
auto playerMax = ImVec2(playerMin.x + PLAYER_SIZE, playerMin.y + PLAYER_SIZE);
drawList->AddRectFilled(playerMin, playerMax,
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
auto staminaBarWidth =
std::min(STAMINA_BAR_WIDTH,
std::max(CANVAS_MIN_SIZE, canvasSize.x - (STAMINA_BAR_PADDING * STAMINA_BAR_PADDING_MULTIPLIER)));
auto staminaBarMin = ImVec2(canvasMax.x - staminaBarWidth - STAMINA_BAR_PADDING,
canvasMax.y - STAMINA_BAR_HEIGHT - STAMINA_BAR_PADDING);
auto staminaBarMax = ImVec2(staminaBarMin.x + staminaBarWidth, staminaBarMin.y + STAMINA_BAR_HEIGHT);
progress_bar_draw(drawList, staminaBarMin, staminaBarMax, stamina,
IM_COL32(0, COLOR_GREEN_DIM, 0, COLOR_CHANNEL_MAX),
IM_COL32(0, COLOR_GREEN, 0, COLOR_CHANNEL_MAX));
score_draw(drawList, canvasMin, score, wave);
time_draw(drawList, canvasMin, canvasMax, timeTicks, timeGainTicks, timeGainAlpha);
if (isPaused)
util::imgui::draw_list_text_center_draw(
*drawList, canvasMin, canvasSize, "PAUSED", GAME_TEXT_FONT_SIZE, CENTER_RATIO, CENTER_RATIO,
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
}
else
util::imgui::draw_list_text_center_draw(
*drawList, canvasMin, canvasSize, "Click to start!", GAME_TEXT_FONT_SIZE, CENTER_RATIO, START_TEXT_Y_RATIO,
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
}
ImGui::EndChild();
ImGui::PopStyleVar(STYLE_VAR_COUNT);
}
}
-77
View File
@@ -1,77 +0,0 @@
#pragma once
#include "../../../entity.hpp"
#include <imgui.h>
#include <string>
#include <vector>
namespace game::state::play::menu
{
class Gather
{
public:
Entity player{};
std::vector<Entity> collectibles{};
std::vector<Entity> nextCollectibles{};
std::vector<Entity> staminaPickups{};
std::vector<Entity> nextStaminaPickups{};
glm::vec2 targetPosition{};
float stamina{};
float boostTime{};
float magnetRadiusTime{};
float timeTicks{};
float timeGainTicks{};
float timeGainAlpha{};
int score{};
int wave{};
bool isStarted{};
bool isPaused{};
bool isMenuFullscreenKnown{};
bool isMenuFullscreenLast{};
bool isWaveResetPending{};
float waveResetAlpha{};
void update(bool isMenuFullscreen, bool isMenuResizeSettled);
private:
enum class Structure
{
SINGLE,
LINE_HORIZONTAL,
LINE_VERTICAL,
POLYGON
};
static float random_range_get(float, float);
static glm::vec2 random_position_get(float, float, float, float);
static Entity collectible_make(glm::vec2);
static Structure structure_get(int);
static int structure_count_get(int);
static int wave_collectible_count_get(int);
static void collectible_add(std::vector<Entity>&, glm::vec2, int);
static void single_spawn(std::vector<Entity>&, ImVec2, float, int);
static void line_spawn(std::vector<Entity>&, ImVec2, float, bool, int, int);
static float polygon_radius_get(int);
static void polygon_spawn(std::vector<Entity>&, ImVec2, float, int, int);
static void wave_spawn(std::vector<Entity>&, ImVec2, int);
static int stamina_pickup_count_get(int);
static void stamina_pickups_spawn(std::vector<Entity>&, ImVec2, int);
static float wave_time_gain_ticks_get();
static bool collectible_hit_get(const Entity&, const Entity&);
static float magnet_radius_get(float, bool isMenuFullscreen);
static int collectibles_collect(std::vector<Entity>&, const Entity&);
static void collectibles_magnet_update(std::vector<Entity>&, const Entity&, ImVec2, float, float);
static bool wave_complete_get(const std::vector<Entity>&);
static void collectibles_draw(const std::vector<Entity>&, ImDrawList*, ImVec2, float, ImU32);
static void magnet_radius_draw(ImDrawList*, ImVec2, const Entity&, float);
static int alpha_get(int, float);
static void progress_bar_draw(ImDrawList*, ImVec2, ImVec2, float, ImU32, ImU32, const std::string& = {});
static void score_draw(ImDrawList*, ImVec2, int, int);
static void time_draw(ImDrawList*, ImVec2, ImVec2, float, float, float);
void reset();
void start(ImVec2);
void wave_reset(ImVec2);
void wave_reset_request();
};
}
+202 -8
View File
@@ -1,8 +1,16 @@
#include "interact.hpp"
#include "../info.hpp"
#include "../../../util/color.hpp"
#include "../../../util/imgui.hpp"
#include "../../../util/imgui/widget.hpp"
#include "../../../util/math.hpp"
#include "../../../util/string.hpp"
#include <array>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <format>
using namespace game::resource;
using namespace game::resource::xml;
@@ -11,25 +19,172 @@ using namespace game::util::imgui;
namespace game::state::play::menu
{
void Interact::update(Resources& resources, Text& text, Entity& character, bool)
std::string playtime_format(float totalPlaytimeSeconds)
{
static constexpr auto PLAYTIME_SECONDS_PER_MINUTE = 60;
static constexpr auto PLAYTIME_MINUTES_PER_HOUR = 60;
static constexpr auto PLAYTIME_SECONDS_PER_HOUR = PLAYTIME_SECONDS_PER_MINUTE * PLAYTIME_MINUTES_PER_HOUR;
auto totalSeconds = std::max(0, (int)std::floor(totalPlaytimeSeconds));
auto seconds = totalSeconds % PLAYTIME_SECONDS_PER_MINUTE;
if (totalSeconds < PLAYTIME_SECONDS_PER_MINUTE) return std::format("{:02}", seconds);
auto minutes = totalSeconds / PLAYTIME_SECONDS_PER_MINUTE % PLAYTIME_MINUTES_PER_HOUR;
if (totalSeconds < PLAYTIME_SECONDS_PER_HOUR) return std::format("{:02}:{:02}", minutes, seconds);
auto hours = totalSeconds / PLAYTIME_SECONDS_PER_HOUR;
return std::format("{:02}:{:02}:{:02}", hours, minutes, seconds);
}
float interact_info_height_get()
{
return Font::HEADER_2 + ImGui::GetFrameHeightWithSpacing();
}
void interact_info_content_draw(Resources& resources, Entity& character, ImVec2 size)
{
static constexpr auto HALF_MULTIPLIER = 0.5f;
static constexpr auto ZERO_FLOAT = 0.0f;
auto& strings = character.data.strings;
auto& style = ImGui::GetStyle();
auto childSize = ImVec2((size.x - style.ItemSpacing.x) * HALF_MULTIPLIER, size.y);
if (ImGui::BeginChild("##Weight", childSize))
{
auto* settings = resources.settings.root();
auto system = settings->measurementSystem == "Imperial" ? measurement::IMPERIAL : measurement::METRIC;
auto weight = character.weight_get(system);
auto stage = character.stage_get();
auto stageMax = character.stage_max_get();
auto stageCount = character.stage_count_get();
auto stageWeight = character.stage_threshold_get(stage, system);
auto stageNextWeight = character.stage_threshold_next_get(system);
auto unitString = (system == measurement::IMPERIAL ? "lbs" : "kg");
auto weightString = util::string::format_commas(weight, 2) + " " + unitString;
imgui::text_unformatted_fit_draw(weightString.c_str(), ImVec2(ImGui::GetContentRegionAvail().x, Font::HEADER_2),
Font::HEADER_2);
ImGui::SetItemTooltip("%s", weightString.c_str());
auto stageProgress = character.stage_progress_get();
ImGui::ProgressBar(stageProgress, ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT),
strings.get(stage >= stageMax ? Strings::InfoProgressMax : Strings::InfoProgressToNextStage)
.c_str());
if (ImGui::BeginItemTooltip())
{
ImGui::Text(strings.get(Strings::InfoStageProgressFormat).c_str(), stage + 1, stageCount,
math::to_percent(stageProgress));
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, imgui::to_imvec4(color::GRAY));
if (stage >= stageMax)
ImGui::TextUnformatted(strings.get(Strings::InfoMaxedOut).c_str());
else
{
ImGui::Text(strings.get(Strings::InfoStageStartFormat).c_str(), stageWeight, unitString);
ImGui::Text(strings.get(Strings::InfoStageCurrentFormat).c_str(), weight, unitString);
ImGui::Text(strings.get(Strings::InfoStageNextFormat).c_str(), stageNextWeight, unitString);
}
ImGui::PopStyleColor();
ImGui::EndTooltip();
}
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("##Calories and Capacity", childSize))
{
auto& calories = character.calories;
auto& capacity = character.capacity;
auto overstuffedPercent = std::max(ZERO_FLOAT, (calories - capacity) / (character.max_capacity() - capacity));
auto caloriesColor = ImVec4(1.0f, 1.0f - overstuffedPercent, 1.0f - overstuffedPercent, 1.0f);
ImGui::PushStyleColor(ImGuiCol_Text, caloriesColor);
auto caloriesString = std::format("{:.0f} kcal / {:.0f} kcal", calories,
character.is_over_capacity() ? character.max_capacity() : character.capacity);
imgui::text_unformatted_fit_draw(caloriesString.c_str(),
ImVec2(ImGui::GetContentRegionAvail().x, Font::HEADER_2), Font::HEADER_2);
ImGui::SetItemTooltip("%s", caloriesString.c_str());
ImGui::PopStyleColor();
auto digestionProgress = character.isDigesting
? (float)character.digestionTimer / character.data.root()->digestionTimerMax
: character.digestionProgress / Entity::DIGESTION_MAX;
ImGui::ProgressBar(digestionProgress, ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT),
strings.get(character.isDigesting ? Strings::InfoDigesting : Strings::InfoDigestion).c_str());
if (ImGui::BeginItemTooltip())
{
if (character.isDigesting)
ImGui::TextUnformatted(strings.get(Strings::InfoDigestionInProgress).c_str());
else if (digestionProgress <= ZERO_FLOAT)
ImGui::TextUnformatted(strings.get(Strings::InfoGiveFoodToStartDigesting).c_str());
else
ImGui::Text("%0.2f%%", math::to_percent(digestionProgress));
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
ImGui::Text(strings.get(Strings::InfoDigestionRateFormat).c_str(), character.digestion_rate_get());
ImGui::PopStyleColor();
ImGui::EndTooltip();
}
}
ImGui::EndChild();
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
ImGui::SeparatorText(strings.get(Strings::InteractStats).c_str());
ImGui::PopFont();
ImGui::Text(strings.get(Strings::InteractTotalCaloriesFormat).c_str(), character.totalCaloriesConsumed);
ImGui::Text(strings.get(Strings::InteractTotalItemsSpawnedFormat).c_str(), character.totalItemsSpawned);
ImGui::Text(strings.get(Strings::InteractTotalItemsConsumedFormat).c_str(), character.totalItemsConsumed);
auto totalPlaytime = playtime_format(character.totalPlaytimeSeconds);
ImGui::Text(strings.get(Strings::InteractTotalPlaytimeFormat).c_str(), totalPlaytime.c_str());
}
void Interact::update(Resources& resources, ItemManager& itemManager, Text& text, Entity& character, Autofeed& autofeed,
bool)
{
static constexpr auto ZERO_FLOAT = 0.0f;
static constexpr auto ONE_FLOAT = 1.0f;
static constexpr auto TOOLTIP_BUFFER_SIZE = 128;
auto& dialogue = character.data.dialogue;
auto& strings = character.data.strings;
auto* random = dialogue.get(Schema::Element::RANDOM);
auto* help = dialogue.get(Schema::Element::HELP);
auto* random = dialogue.dialogue_hook_get(Schema::Element::RANDOM);
auto* help = dialogue.dialogue_hook_get(Schema::Element::HELP);
auto stage = glm::clamp(0, character.stage_get(), character.stage_max_get());
auto poolID = character.data.stage_pool_id_get(stage);
auto isFeelingAvailable = poolID != -1;
auto isDialogueAvailable = random || help || isFeelingAvailable;
auto spawnDelay = itemManager.spawn_delay_get(character);
auto isSpawnReady = itemManager.spawnDelayTicks <= 0;
auto isSpawnAvailable = itemManager.spawn_available_get(character);
auto normal_tooltip_set = [&](const char* tooltip)
{
ImGui::PushFont(ImGui::GetFont(), resource::Font::NORMAL);
ImGui::SetItemTooltip("%s", tooltip);
ImGui::PopFont();
};
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
ImGui::SeparatorText(character.data.root()->name.c_str());
ImGui::PopFont();
info_content_draw(resources, character, ImVec2(ImGui::GetContentRegionAvail().x, info_height_get()));
interact_info_content_draw(resources, character, ImVec2(ImGui::GetContentRegionAvail().x, interact_info_height_get()));
auto buttonHeight = ZERO_FLOAT;
ImGui::PushFont(ImGui::GetFont(), resource::Font::HUGE);
buttonHeight += ImGui::GetFrameHeightWithSpacing();
ImGui::PopFont();
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
buttonHeight += ImGui::GetFrameHeightWithSpacing();
ImGui::PopFont();
if (isDialogueAvailable) buttonHeight += ImGui::GetStyle().ItemSpacing.y + ONE_FLOAT;
if (random)
{
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
@@ -52,12 +207,51 @@ namespace game::state::play::menu
auto fillerHeight = ImGui::GetContentRegionAvail().y - buttonHeight;
if (fillerHeight > ZERO_FLOAT) ImGui::Dummy(ImVec2(ImGui::GetContentRegionAvail().x, fillerHeight));
ImGui::PushFont(ImGui::GetFont(), resource::Font::HUGE);
if (isSpawnReady)
{
ImGui::BeginDisabled(!isSpawnAvailable);
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InteractSpawnItemButton).c_str(),
ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT))))
itemManager.spawn_queue(character);
ImGui::EndDisabled();
}
else
{
auto progress = ONE_FLOAT - glm::clamp((float)itemManager.spawnDelayTicks / (float)spawnDelay, ZERO_FLOAT, ONE_FLOAT);
ImGui::BeginDisabled();
ImGui::ProgressBar(progress, ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetFrameHeight()),
strings.get(Strings::InteractSpawnItemButton).c_str());
ImGui::EndDisabled();
std::array<char, TOOLTIP_BUFFER_SIZE> tooltip{};
std::snprintf(tooltip.data(), tooltip.size(), strings.get(Strings::InteractSpawnItemCooldownFormat).c_str(),
(float)itemManager.spawnDelayTicks / Entity::UPDATE_RATE);
normal_tooltip_set(tooltip.data());
}
ImGui::PopFont();
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
auto autofeedLabel = autofeed.enabled_get() ? strings.get(Strings::InventoryDisableAutofeedButton)
: strings.get(Strings::InventoryEnableAutofeedButton);
auto isAutofeedAvailable = autofeed.available_get(character, itemManager);
ImGui::BeginDisabled(!isAutofeedAvailable);
if (WIDGET_FX(ImGui::Button(autofeedLabel.c_str(), ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT))))
autofeed.toggle();
ImGui::EndDisabled();
normal_tooltip_set(strings
.get(autofeed.enabled_get() ? Strings::InventoryDisableAutofeedTooltip
: Strings::InventoryEnableAutofeedTooltip)
.c_str());
ImGui::PopFont();
if (isDialogueAvailable) ImGui::Separator();
if (random)
{
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InteractChatButton).c_str(),
ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT))))
text.set(dialogue.dialogue_pool_entry_get(*random), character);
text.set(dialogue.dialogue_hook_entry_get(Schema::Element::RANDOM), character);
ImGui::PopFont();
}
@@ -66,7 +260,7 @@ namespace game::state::play::menu
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InteractHelpButton).c_str(),
ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT))))
text.set(dialogue.dialogue_entry_get(*help), character);
text.set(dialogue.dialogue_hook_entry_get(Schema::Element::HELP), character);
ImGui::PopFont();
}
+3 -1
View File
@@ -1,6 +1,8 @@
#pragma once
#include "../text.hpp"
#include "../autofeed.hpp"
#include "../item_manager.hpp"
#include <imgui.h>
@@ -9,6 +11,6 @@ namespace game::state::play::menu
class Interact
{
public:
void update(Resources&, Text&, Entity&, bool isMenuFullscreen);
void update(Resources&, ItemManager&, Text&, Entity&, Autofeed&, bool isMenuFullscreen);
};
}
+150 -434
View File
@@ -1,213 +1,107 @@
#include "inventory.hpp"
#include "../autofeed.hpp"
#include "../item_display.hpp"
#include "../style.hpp"
#include <cmath>
#include <format>
#include <ranges>
#include <string_view>
#include <tuple>
#include "../../../resource/font.hpp"
#include "../../../util/color.hpp"
#include "../../../util/imgui.hpp"
#include "../../../util/imgui/style.hpp"
#include "../../../util/imgui/widget.hpp"
#include "../../../util/math.hpp"
#include <algorithm>
#include <array>
#include <cstdio>
#include <string>
#include <string_view>
using namespace game::util;
using namespace game::util::imgui;
using namespace game::resource;
using namespace glm;
namespace game::state::play::menu
{
using Strings = resource::xml::Strings;
namespace
{
int& quantity_get(std::map<int, int>& values, const resource::xml::Schema& schema, int itemID)
{
auto& quantity = values[itemID];
quantity = glm::clamp(0, quantity, schema.quantityMax);
return quantity;
}
constexpr auto INFO_CHILD_HEIGHT_MAX_MULTIPLIER = 0.5f;
constexpr auto DETAIL_CHILD_HEIGHT_MULTIPLIER = 0.5f;
constexpr auto WINDOW_PADDING_MULTIPLIER = 2.0f;
constexpr auto ZERO_FLOAT = 0.0f;
constexpr auto WIDGET_MIN_SIZE = 1.0f;
constexpr auto SELECTED_ITEM_STYLE_COLOR_COUNT = 3;
constexpr auto TEXT_FORMAT_BUFFER_SIZE = 512;
constexpr auto UNDISCOVERED_ITEM_TINT = 0.0f;
constexpr auto UNDISCOVERED_ITEM_ALPHA = 0.5f;
constexpr auto UNKNOWN_TEXT = "???";
bool is_possible_to_upgrade_get(const resource::xml::Schema& schema, const resource::xml::Schema::ItemEntry& item)
{
return item.isUpgradeID && item.isUpgradeCount && schema.idToStringMap.contains(item.upgradeID);
}
template <typename... Args> std::string formatted_text_get(const std::string& format, Args... args)
{
std::array<char, TEXT_FORMAT_BUFFER_SIZE> buffer{};
auto size = std::snprintf(buffer.data(), buffer.size(), format.c_str(), args...);
if (size < 0) return {};
auto length = std::min(static_cast<std::size_t>(size), buffer.size() - 1);
return std::string(buffer.data(), length);
}
bool Inventory::can_upgrade(const resource::xml::Schema& schema, int itemID)
float wrapped_text_height_get(std::string_view text, float wrapWidth)
{
if (itemID < 0 || itemID >= (int)schema.items.size()) return false;
auto& item = schema.items[itemID];
auto& quantity = quantity_get(values, schema, itemID);
return is_possible_to_upgrade_get(schema, item) && quantity >= item.upgradeCount;
auto safeWrapWidth = std::max(WIDGET_MIN_SIZE, wrapWidth);
return ImGui::CalcTextSize(text.data(), text.data() + text.size(), false, safeWrapWidth).y;
}
bool Inventory::upgrade(resource::xml::Schema& schema, int itemID, bool isAll)
{
if (itemID < 0 || itemID >= (int)schema.items.size()) return false;
auto& item = schema.items[itemID];
auto& quantity = quantity_get(values, schema, itemID);
if (!is_possible_to_upgrade_get(schema, item) || quantity < item.upgradeCount)
{
schema.root()->soundUpgradeFail.play();
return false;
}
if (isAll)
{
while (quantity >= item.upgradeCount)
{
values[item.upgradeID]++;
quantity -= item.upgradeCount;
}
}
else
{
values[item.upgradeID]++;
quantity -= item.upgradeCount;
}
schema.root()->soundUpgrade.play();
if (quantity < item.upgradeCount && selectedItemID == itemID) selectedItemID = item.upgradeID;
return true;
}
bool Inventory::upgrade_all_possible(resource::xml::Schema& schema)
{
auto isUpgraded = false;
for (auto isPassUpgraded = true; isPassUpgraded;)
{
isPassUpgraded = false;
for (int itemID = 0; itemID < (int)schema.items.size(); itemID++)
{
if (!can_upgrade(schema, itemID)) continue;
isPassUpgraded = upgrade(schema, itemID, true) || isPassUpgraded;
isUpgraded = isPassUpgraded || isUpgraded;
}
}
return isUpgraded;
}
void Inventory::update(Resources& resources, ItemManager& itemManager, Entity& character, Autofeed& autofeed)
void Inventory::update(Resources& resources, ItemManager& itemManager, Entity& character)
{
for (auto& [i, actor] : actors)
actor.update();
static constexpr auto INFO_CHILD_HEIGHT_MAX_MULTIPLIER = 0.5f;
bool isSelectedItemPressed{};
int pressedItemQuantity{-1};
auto& schema = character.data.itemSchema;
auto& strings = character.data.strings;
auto isSelectedItemPressed = false;
auto inventory_quantity_get = [&](int itemID) -> int& { return quantity_get(values, schema, itemID); };
auto is_possible_to_upgrade_get = [&](const resource::xml::Schema::ItemEntry& item)
{ return game::state::play::menu::is_possible_to_upgrade_get(schema, item); };
auto is_able_to_upgrade_get = [&](const resource::xml::Schema::ItemEntry& item, int quantity)
{ return is_possible_to_upgrade_get(item) && quantity >= item.upgradeCount; };
auto item_header_draw = [&](const resource::xml::Schema::ItemEntry& item, int quantity)
{
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
ImGui::TextWrapped("%s (x%i)", item.name.c_str(), quantity);
ImGui::PopFont();
};
auto item_summary_draw = [&](const resource::xml::Schema::ItemEntry& item)
{
auto& category = schema.categories[item.categoryID];
auto& rarity = schema.rarities[item.rarityID];
auto durability = item.isDurability ? item.durability : schema.durability;
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
ImGui::TextWrapped("-- %s (%s) --", category.name.c_str(), rarity.name.c_str());
if (item.isFlavor)
ImGui::TextWrapped(strings.get(Strings::InventoryFlavorFormat).c_str(),
schema.flavors[item.flavorID].name.c_str());
if (item.isCalories) ImGui::TextWrapped(strings.get(Strings::InventoryCaloriesFormat).c_str(), item.calories);
ImGui::TextWrapped(strings.get(Strings::InventoryDurabilityFormat).c_str(), durability);
if (item.isCapacityBonus)
ImGui::TextWrapped(strings.get(Strings::InventoryCapacityBonusFormat).c_str(), item.capacityBonus);
if (item.isDigestionBonus)
{
if (item.digestionBonus > 0)
ImGui::TextWrapped(strings.get(Strings::InventoryDigestionRateBonusFormat).c_str(),
item.digestionBonus * 60.0f);
else if (item.digestionBonus < 0)
ImGui::TextWrapped(strings.get(Strings::InventoryDigestionRatePenaltyFormat).c_str(),
item.digestionBonus * 60.0f);
}
if (item.isEatSpeedBonus)
{
if (item.eatSpeedBonus > 0)
ImGui::TextWrapped(strings.get(Strings::InventoryEatSpeedBonusFormat).c_str(), item.eatSpeedBonus);
else if (item.eatSpeedBonus < 0)
ImGui::TextWrapped(strings.get(Strings::InventoryEatSpeedPenaltyFormat).c_str(), item.eatSpeedBonus);
}
if (is_possible_to_upgrade_get(item))
ImGui::TextWrapped(strings.get(Strings::InventoryUpgradePreviewFormat).c_str(), item.upgradeCount,
schema.idToStringMap.at(item.upgradeID).c_str());
ImGui::PopStyleColor();
};
auto info_section_separator_height_get = [&]() { return ImGui::GetStyle().ItemSpacing.y + 1.0f; };
auto info_section_separator_height_get = []() { return ImGui::GetStyle().ItemSpacing.y + WIDGET_MIN_SIZE; };
auto info_section_separator_draw = [&]()
{
auto separatorHeight = info_section_separator_height_get();
auto cursorScreenPos = ImGui::GetCursorScreenPos();
auto width = ImGui::GetContentRegionAvail().x;
auto y = cursorScreenPos.y + separatorHeight * 0.5f;
auto y = cursorScreenPos.y + separatorHeight * DETAIL_CHILD_HEIGHT_MULTIPLIER;
ImGui::GetWindowDrawList()->AddLine(ImVec2(cursorScreenPos.x, y), ImVec2(cursorScreenPos.x + width, y),
ImGui::GetColorU32(ImGuiCol_Separator));
ImGui::Dummy(ImVec2(0.0f, separatorHeight));
ImGui::Dummy(ImVec2(ZERO_FLOAT, separatorHeight));
};
auto item_tooltip_draw = [&](const resource::xml::Schema::ItemEntry& item, int quantity)
auto item_unknown_tooltip_draw = [&]()
{
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f);
item_header_draw(item, quantity);
ImGui::PushTextWrapPos(ImGui::GetFontSize() * ITEM_TOOLTIP_WRAP_FONT_MULTIPLIER);
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
ImGui::TextWrapped("%s", UNKNOWN_TEXT);
ImGui::PopFont();
ImGui::Separator();
item_summary_draw(item);
ImGui::Separator();
ImGui::TextWrapped("%s", item.description.c_str());
ImGui::TextWrapped("%s", UNKNOWN_TEXT);
ImGui::PopTextWrapPos();
};
auto item_unknown_draw = [&]()
{
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f);
ImGui::TextWrapped("%s", strings.get(Strings::InventoryUnknown).c_str());
ImGui::PopTextWrapPos();
ImGui::TextWrapped("%s", strings.get(Strings::InventoryEmptyHint).c_str());
ImGui::PopFont();
};
auto wrapped_text_height_get = [](std::string_view text, float wrapWidth)
auto item_unknown_detail_draw = [&]()
{
auto safeWrapWidth = std::max(1.0f, wrapWidth);
return ImGui::CalcTextSize(text.data(), text.data() + text.size(), false, safeWrapWidth).y;
};
auto item_header_height_get = [&](const resource::xml::Schema::ItemEntry& item, int quantity, float width)
{
float height{};
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
height += wrapped_text_height_get(std::format("{} (x{})", item.name, quantity), width);
ImGui::TextWrapped("%s", UNKNOWN_TEXT);
ImGui::PopFont();
info_section_separator_draw();
ImGui::TextWrapped("%s", UNKNOWN_TEXT);
};
auto item_header_height_get = [&](const resource::xml::Schema::ItemEntry& item, float width)
{
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
auto height = wrapped_text_height_get(item.name, width);
ImGui::PopFont();
return height;
};
@@ -215,10 +109,9 @@ namespace game::state::play::menu
{
auto& category = schema.categories[item.categoryID];
auto& rarity = schema.rarities[item.rarityID];
auto durability = item.isDurability ? item.durability : schema.durability;
auto itemSpacing = ImGui::GetStyle().ItemSpacing.y;
float height{};
int lineCount{};
auto height = ZERO_FLOAT;
auto lineCount = 0;
auto add_line_height = [&](std::string_view text)
{
@@ -227,75 +120,63 @@ namespace game::state::play::menu
lineCount++;
};
add_line_height(std::format("-- {} ({}) --", category.name, rarity.name));
add_line_height(formatted_text_get("-- %s (%s) --", category.name.c_str(), rarity.name.c_str()));
if (item.isFlavor)
add_line_height(std::vformat(strings.get(Strings::InventoryFlavorFormat),
std::make_format_args(schema.flavors[item.flavorID].name)));
add_line_height(formatted_text_get(strings.get(Strings::InventoryFlavorFormat),
schema.flavors[item.flavorID].name.c_str()));
if (item.isCalories)
add_line_height(
std::vformat(strings.get(Strings::InventoryCaloriesFormat), std::make_format_args(item.calories)));
add_line_height(std::vformat(strings.get(Strings::InventoryDurabilityFormat), std::make_format_args(durability)));
add_line_height(formatted_text_get(strings.get(Strings::InventoryCaloriesFormat), item.calories));
if (item.isCapacityBonus)
add_line_height(std::vformat(strings.get(Strings::InventoryCapacityBonusFormat),
std::make_format_args(item.capacityBonus)));
add_line_height(formatted_text_get(strings.get(Strings::InventoryCapacityBonusFormat), item.capacityBonus));
if (item.isDigestionBonus)
{
if (item.digestionBonus > 0)
{
auto digestionRateBonus = item.digestionBonus * 60.0f;
add_line_height(std::vformat(strings.get(Strings::InventoryDigestionRateBonusFormat),
std::make_format_args(digestionRateBonus)));
}
else if (item.digestionBonus < 0)
{
auto digestionRatePenalty = item.digestionBonus * 60.0f;
add_line_height(std::vformat(strings.get(Strings::InventoryDigestionRatePenaltyFormat),
std::make_format_args(digestionRatePenalty)));
}
if (item.digestionBonus > ZERO_FLOAT)
add_line_height(formatted_text_get(strings.get(Strings::InventoryDigestionRateBonusFormat),
item.digestionBonus * Entity::UPDATE_RATE));
else if (item.digestionBonus < ZERO_FLOAT)
add_line_height(formatted_text_get(strings.get(Strings::InventoryDigestionRatePenaltyFormat),
item.digestionBonus * Entity::UPDATE_RATE));
}
if (item.isEatSpeedBonus)
{
if (item.eatSpeedBonus > 0)
add_line_height(std::vformat(strings.get(Strings::InventoryEatSpeedBonusFormat),
std::make_format_args(item.eatSpeedBonus)));
else if (item.eatSpeedBonus < 0)
add_line_height(std::vformat(strings.get(Strings::InventoryEatSpeedPenaltyFormat),
std::make_format_args(item.eatSpeedBonus)));
}
if (is_possible_to_upgrade_get(item))
add_line_height(
std::vformat(strings.get(Strings::InventoryUpgradePreviewFormat),
std::make_format_args(item.upgradeCount, schema.idToStringMap.at(item.upgradeID))));
return height;
};
auto item_details_height_get = [&](const resource::xml::Schema::ItemEntry& item, int quantity, float width)
auto item_details_height_get = [&](const resource::xml::Schema::ItemEntry& item, float width)
{
auto separatorHeight = info_section_separator_height_get();
auto detailBodyHeight =
std::max(item_summary_height_get(item, width), wrapped_text_height_get(item.description, width));
return item_header_height_get(item, quantity, width) + separatorHeight + detailBodyHeight * 2.0f +
return item_header_height_get(item, width) + separatorHeight + detailBodyHeight * WINDOW_PADDING_MULTIPLIER +
separatorHeight;
};
auto item_unknown_height_get = [&](float width)
auto item_unknown_details_height_get = [&](float width)
{
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
auto height = wrapped_text_height_get(strings.get(Strings::InventoryUnknown), width);
auto headerHeight = wrapped_text_height_get(UNKNOWN_TEXT, width);
ImGui::PopFont();
return height;
auto separatorHeight = info_section_separator_height_get();
return headerHeight + separatorHeight + wrapped_text_height_get(UNKNOWN_TEXT, width);
};
auto info_child_height_get = [&](ImVec2 available, bool isItemSelected, int selectedQuantity)
auto item_discovered_get = [&](int itemID)
{
auto isInfoVisible = isItemSelected || count() == 0;
if (!isInfoVisible) return 0.0f;
return itemID >= 0 && itemID < (int)itemManager.discoveredItemIDs.size() && itemManager.discoveredItemIDs[itemID];
};
auto infoWidth = std::max(1.0f, available.x - ImGui::GetStyle().WindowPadding.x * 2.0f);
auto infoPadding = ImGui::GetStyle().WindowPadding.y * 2.0f;
auto item_catalog_visible_get = [&](int itemID)
{
return itemID >= 0 && itemID < (int)schema.items.size();
};
auto info_child_height_get = [&](ImVec2 available, bool isItemSelected)
{
if (!isItemSelected && !schema.items.empty()) return ZERO_FLOAT;
auto infoWidth =
std::max(WIDGET_MIN_SIZE, available.x - ImGui::GetStyle().WindowPadding.x * WINDOW_PADDING_MULTIPLIER);
auto infoPadding = ImGui::GetStyle().WindowPadding.y * WINDOW_PADDING_MULTIPLIER;
auto separatorHeight = info_section_separator_height_get();
auto measuredBodyHeight = 0.0f;
auto isSelectedItemKnown = isItemSelected && selectedQuantity > 0;
auto measuredBodyHeight = ZERO_FLOAT;
if (!isItemSelected)
{
@@ -306,112 +187,57 @@ namespace game::state::play::menu
else
{
auto& item = schema.items[selectedItemID];
measuredBodyHeight = isSelectedItemKnown ? item_details_height_get(item, selectedQuantity, infoWidth)
: item_unknown_height_get(infoWidth);
measuredBodyHeight = item_discovered_get(selectedItemID) ? item_details_height_get(item, infoWidth)
: item_unknown_details_height_get(infoWidth);
}
auto buttonChildHeight = 0.0f;
if (selectedQuantity > 0)
{
ImGui::PushFont(resources.font.get(), Font::HEADER_2);
auto buttonRowHeight = ImGui::GetFrameHeight();
buttonChildHeight = buttonRowHeight * 2.0f + ImGui::GetStyle().ItemSpacing.y * 5.0f;
ImGui::PopFont();
}
auto desiredInfoChildHeight = infoPadding + separatorHeight + measuredBodyHeight + buttonChildHeight +
(buttonChildHeight > 0.0f ? separatorHeight : 0.0f);
auto desiredInfoChildHeight = infoPadding + separatorHeight + measuredBodyHeight;
auto maxInfoChildHeight = available.y * INFO_CHILD_HEIGHT_MAX_MULTIPLIER;
return glm::clamp(desiredInfoChildHeight, 0.0f, maxInfoChildHeight);
};
auto item_use = [&](int itemID)
{
auto& item = schema.items[itemID];
auto& category = schema.categories[item.categoryID];
auto& quantity = inventory_quantity_get(itemID);
if (quantity <= 0) return;
if (category.isEdible)
{
if (itemManager.items.size() + 1 >= ItemManager::LIMIT)
character.data.itemSchema.root()->soundDispose.play();
else
{
character.data.itemSchema.root()->soundSummon.play();
itemManager.queuedItemIDs.emplace_back(itemID);
quantity--;
if (quantity <= 0) selectedItemID = -1;
}
}
else if (item.isToggleSpritesheet)
{
character.spritesheet_set(character.spritesheetType == Entity::NORMAL ? Entity::ALTERNATE : Entity::NORMAL);
if (auto* alternate = character.data.alternate_spritesheet()) alternate->soundEntry.sound.play();
quantity--;
}
return std::clamp(desiredInfoChildHeight, ZERO_FLOAT, maxInfoChildHeight);
};
auto item_actor_get = [&](int itemID) -> Entity&
{
if (!actors.contains(itemID)) actors[itemID] = Entity(schema.anm2s[itemID], {}, Entity::SET);
return actors[itemID];
};
if (!itemManager.returnItemIDs.empty())
{
for (auto& id : itemManager.returnItemIDs)
values[id]++;
itemManager.returnItemIDs.clear();
}
if (ImGui::BeginChild("##Inventory Child", ImGui::GetContentRegionAvail(), ImGuiChildFlags_None,
ImGuiWindowFlags_NoScrollbar))
{
auto inventoryCount = count();
auto available = ImGui::GetContentRegionAvail();
auto isItemSelected = selectedItemID >= 0 && selectedItemID < (int)schema.items.size();
auto isInfoVisible = isItemSelected || inventoryCount == 0;
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
auto toggleButtonHeight = ImGui::GetFrameHeight();
ImGui::PopFont();
auto toggleChildHeight = toggleButtonHeight + ImGui::GetStyle().WindowPadding.y * 2.0f;
auto isItemSelected =
selectedItemID >= 0 && selectedItemID < (int)schema.items.size() && item_catalog_visible_get(selectedItemID);
auto isInfoVisible = isItemSelected || schema.items.empty();
auto toggleSpacing = ImGui::GetStyle().ItemSpacing.y;
auto infoChildHeight =
info_child_height_get(available, isItemSelected, isItemSelected ? inventory_quantity_get(selectedItemID) : 0);
auto infoChildHeight = info_child_height_get(available, isItemSelected);
auto inventoryChildHeight = std::max(0.0f, available.y - infoChildHeight - toggleChildHeight -
(isInfoVisible ? toggleSpacing * 2.0f : toggleSpacing));
auto inventoryChildHeight =
std::max(ZERO_FLOAT, available.y - infoChildHeight -
(isInfoVisible ? toggleSpacing * WINDOW_PADDING_MULTIPLIER : toggleSpacing));
auto childSize = ImVec2(available.x, inventoryChildHeight);
auto toggleChildSize = ImVec2(available.x, toggleChildHeight);
auto infoChildSize = ImVec2(available.x, infoChildHeight);
if (ImGui::BeginChild("##Inventory List Child", childSize))
{
auto cursorPos = ImGui::GetCursorPos();
auto cursorStartX = ImGui::GetCursorPosX();
bool isAnyInventoryItemHovered{};
auto isAnyInventoryItemHovered = false;
auto size = ImVec2(SIZE, SIZE);
for (int i = 0; i < (int)schema.items.size(); i++)
{
auto& item = schema.items[i];
auto& quantity = inventory_quantity_get(i);
auto& rarity = schema.rarities[item.rarityID];
if (!item_catalog_visible_get(i)) continue;
auto isItemColor = item.isColor;
if (rarity.isHidden && quantity <= 0) continue;
ImGui::PushID(i);
ImGui::SetCursorPos(cursorPos);
auto cursorScreenPos = ImGui::GetCursorScreenPos();
auto& actor = item_actor_get(i);
auto isSelected = selectedItemID == i;
if (isItemColor) imgui::style::color_set(item.color);
auto isDiscovered = item_discovered_get(i);
if (isItemColor && isDiscovered) imgui::style::color_set(item.color);
if (isSelected)
{
@@ -421,35 +247,26 @@ namespace game::state::play::menu
ImGui::PushStyleColor(ImGuiCol_ButtonActive, selectedColor);
}
auto isPressed = itemButtons[i].button(resources, actor, "##Image Button", size,
quantity <= 0 ? ImVec4(0, 0, 0, 0.5f) : ImVec4(1, 1, 1, 1));
if (isSelected) ImGui::PopStyleColor(3);
auto tint = isDiscovered ? ImVec4(1, 1, 1, 1)
: ImVec4(UNDISCOVERED_ITEM_TINT, UNDISCOVERED_ITEM_TINT, UNDISCOVERED_ITEM_TINT,
UNDISCOVERED_ITEM_ALPHA);
auto isPressed = itemButtons[i].button(resources, actor, "##Image Button", size, tint);
if (isSelected) ImGui::PopStyleColor(SELECTED_ITEM_STYLE_COLOR_COUNT);
isAnyInventoryItemHovered = isAnyInventoryItemHovered || ImGui::IsItemHovered();
if (isPressed)
{
isSelectedItemPressed = selectedItemID != i;
selectedItemID = i;
pressedItemQuantity = quantity;
}
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && quantity > 0) item_use(i);
if (!isSelected && ImGui::BeginItemTooltip())
{
if (quantity > 0)
item_tooltip_draw(item, quantity);
if (isDiscovered)
item_tooltip_draw(character, schema, item);
else
item_unknown_draw();
item_unknown_tooltip_draw();
ImGui::EndTooltip();
}
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
auto text = std::format("x{}", quantity);
auto textPos = ImVec2(cursorScreenPos.x + size.x - ImGui::CalcTextSize(text.c_str()).x,
cursorScreenPos.y + size.y - ImGui::GetTextLineHeightWithSpacing());
ImGui::GetWindowDrawList()->AddText(textPos, ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_Text)),
text.c_str());
ImGui::PopFont();
if (isItemColor) style::color_set(resources, character);
if (isItemColor && isDiscovered) style::color_set(resources, character);
auto increment = ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x;
cursorPos.x += increment;
@@ -468,177 +285,76 @@ namespace game::state::play::menu
}
ImGui::EndChild();
if (ImGui::BeginChild("##Autofeed Toggle Child", toggleChildSize, ImGuiChildFlags_None,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse))
{
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
auto isAutofeedAvailable = autofeed.available_get(character, *this, itemManager);
auto label = autofeed.enabled_get() ? strings.get(Strings::InventoryDisableAutofeedButton)
: strings.get(Strings::InventoryEnableAutofeedButton);
ImGui::BeginDisabled(!isAutofeedAvailable);
if (WIDGET_FX(ImGui::Button(label.c_str(), {ImGui::GetContentRegionAvail().x, 0}))) autofeed.toggle();
ImGui::EndDisabled();
ImGui::PopFont();
ImGui::SetItemTooltip("%s", strings
.get(autofeed.enabled_get() ? Strings::InventoryDisableAutofeedTooltip
: Strings::InventoryEnableAutofeedTooltip)
.c_str());
}
ImGui::EndChild();
isItemSelected = selectedItemID >= 0 && selectedItemID < (int)schema.items.size();
isInfoVisible = isItemSelected || inventoryCount == 0;
auto selectedQuantity =
isItemSelected ? (isSelectedItemPressed && pressedItemQuantity >= 0 ? pressedItemQuantity
: inventory_quantity_get(selectedItemID))
: 0;
infoChildHeight = info_child_height_get(available, isItemSelected, selectedQuantity);
isItemSelected =
selectedItemID >= 0 && selectedItemID < (int)schema.items.size() && item_catalog_visible_get(selectedItemID);
isInfoVisible = isItemSelected || schema.items.empty();
infoChildHeight = info_child_height_get(available, isItemSelected);
infoChildSize = ImVec2(available.x, infoChildHeight);
auto isSelectedItemKnown = isItemSelected && selectedQuantity > 0;
auto selectedItemHasColor = isItemSelected && schema.items[selectedItemID].isColor;
auto isSelectedItemDiscovered = isItemSelected && item_discovered_get(selectedItemID);
auto selectedItemHasColor = isSelectedItemDiscovered && schema.items[selectedItemID].isColor;
if (isInfoVisible && ImGui::BeginChild("##Info Child", infoChildSize, ImGuiChildFlags_None,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse))
{
if (selectedItemHasColor) imgui::style::color_set(schema.items[selectedItemID].color);
info_section_separator_draw();
auto isButtonChildVisible = selectedQuantity > 0;
ImGui::PushFont(resources.font.get(), Font::HEADER_2);
auto buttonRowHeight = ImGui::GetFrameHeight();
auto buttonChildHeight =
isButtonChildVisible ? buttonRowHeight * 2.0f + ImGui::GetStyle().ItemSpacing.y * 5.0f : 0.0f;
ImGui::PopFont();
auto separatorHeight = info_section_separator_height_get();
if (!isItemSelected)
{
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
ImGui::TextWrapped("%s", strings.get(Strings::InventoryEmptyHint).c_str());
ImGui::PopFont();
item_unknown_draw();
}
else if (isSelectedItemKnown)
else
{
auto& selectedItem = schema.items[selectedItemID];
auto contentWidth = std::max(1.0f, ImGui::GetContentRegionAvail().x);
auto statsHeight = item_summary_height_get(selectedItem, contentWidth);
auto descriptionHeight = wrapped_text_height_get(selectedItem.description, contentWidth);
auto sharedDetailHeight = std::max(statsHeight, descriptionHeight);
auto headerHeight = item_header_height_get(selectedItem, selectedQuantity, contentWidth);
auto desiredInfoContentHeight = headerHeight + separatorHeight + sharedDetailHeight * 2.0f + separatorHeight;
auto availableInfoContentHeight =
std::max(0.0f, ImGui::GetContentRegionAvail().y -
(isButtonChildVisible ? separatorHeight + buttonChildHeight : 0.0f));
auto infoContentHeight = std::min(desiredInfoContentHeight, availableInfoContentHeight);
item_header_draw(selectedItem, selectedQuantity);
info_section_separator_draw();
auto detailChildHeight = sharedDetailHeight;
if (desiredInfoContentHeight > availableInfoContentHeight)
detailChildHeight =
std::max(0.0f, (infoContentHeight - headerHeight - separatorHeight - separatorHeight) * 0.5f);
auto detailChildSize = ImVec2(ImGui::GetContentRegionAvail().x, detailChildHeight);
if (ImGui::BeginChild("##Info Stats Child", detailChildSize))
if (!isSelectedItemDiscovered)
{
if (isSelectedItemPressed) ImGui::SetScrollY(0.0f);
item_summary_draw(selectedItem);
item_unknown_detail_draw();
}
ImGui::EndChild();
info_section_separator_draw();
if (ImGui::BeginChild("##Info Description Child", detailChildSize))
else
{
if (isSelectedItemPressed) ImGui::SetScrollY(0.0f);
ImGui::TextWrapped("%s", selectedItem.description.c_str());
}
ImGui::EndChild();
auto contentWidth = std::max(WIDGET_MIN_SIZE, ImGui::GetContentRegionAvail().x);
auto statsHeight = item_summary_height_get(selectedItem, contentWidth);
auto descriptionHeight = wrapped_text_height_get(selectedItem.description, contentWidth);
auto sharedDetailHeight = std::max(statsHeight, descriptionHeight);
auto headerHeight = item_header_height_get(selectedItem, contentWidth);
auto separatorHeight = info_section_separator_height_get();
auto desiredInfoContentHeight =
headerHeight + separatorHeight + sharedDetailHeight * WINDOW_PADDING_MULTIPLIER + separatorHeight;
auto availableInfoContentHeight = std::max(ZERO_FLOAT, ImGui::GetContentRegionAvail().y);
auto infoContentHeight = std::min(desiredInfoContentHeight, availableInfoContentHeight);
if (isButtonChildVisible)
{
item_header_draw(selectedItem);
info_section_separator_draw();
if (ImGui::BeginChild("##Info Actions Child", ImVec2(ImGui::GetContentRegionAvail().x, buttonChildHeight),
ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar))
auto detailChildHeight = sharedDetailHeight;
if (desiredInfoContentHeight > availableInfoContentHeight)
detailChildHeight =
std::max(ZERO_FLOAT, (infoContentHeight - headerHeight - separatorHeight - separatorHeight) *
DETAIL_CHILD_HEIGHT_MULTIPLIER);
auto detailChildSize = ImVec2(ImGui::GetContentRegionAvail().x, detailChildHeight);
if (ImGui::BeginChild("##Info Stats Child", detailChildSize))
{
auto canUseSelectedItem = true;
auto canUpgradeSelectedItem = is_able_to_upgrade_get(selectedItem, selectedQuantity);
auto rowTwoButtonSize = row_widget_size_get(2);
if (isSelectedItemPressed) ImGui::SetScrollY(ZERO_FLOAT);
item_summary_draw(character, schema, selectedItem);
}
ImGui::EndChild();
auto upgrade_item_name_get = [&]() -> std::string
{
if (!selectedItem.isUpgradeID) return {};
return schema.items.at(selectedItem.upgradeID).name;
};
info_section_separator_draw();
auto upgrade_tooltip_get = [&](bool isAll)
{
if (!is_possible_to_upgrade_get(selectedItem)) return strings.get(Strings::InventoryUpgradeNoPath);
auto upgradeItemName = upgrade_item_name_get();
auto upgradeCount = selectedItem.upgradeCount;
if (!canUpgradeSelectedItem)
return std::vformat(strings.get(Strings::InventoryUpgradeNeedsTemplate),
std::make_format_args(upgradeCount, upgradeItemName));
if (!isAll)
return std::vformat(strings.get(Strings::InventoryUpgradeOneTemplate),
std::make_format_args(upgradeCount, upgradeItemName));
auto upgradedCount = selectedQuantity / upgradeCount;
return std::vformat(strings.get(Strings::InventoryUpgradeAllTemplate),
std::make_format_args(upgradeCount, upgradedCount, upgradeItemName));
};
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
ImGui::BeginDisabled(!canUseSelectedItem);
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InventorySpawnButton).c_str(),
{ImGui::GetContentRegionAvail().x, 0})))
item_use(selectedItemID);
ImGui::EndDisabled();
ImGui::BeginDisabled(!canUpgradeSelectedItem);
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InventoryUpgradeButton).c_str(), rowTwoButtonSize)))
upgrade(schema, selectedItemID, false);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
{
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
ImGui::SetItemTooltip("%s", upgrade_tooltip_get(false).c_str());
ImGui::PopFont();
}
ImGui::SameLine();
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InventoryUpgradeAllButton).c_str(), rowTwoButtonSize)))
upgrade(schema, selectedItemID, true);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
{
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
ImGui::SetItemTooltip("%s", upgrade_tooltip_get(true).c_str());
ImGui::PopFont();
}
ImGui::EndDisabled();
ImGui::PopFont();
if (ImGui::BeginChild("##Info Description Child", detailChildSize))
{
if (isSelectedItemPressed) ImGui::SetScrollY(ZERO_FLOAT);
ImGui::TextWrapped("%s", selectedItem.description.c_str());
}
ImGui::EndChild();
}
}
else
item_unknown_draw();
if (selectedItemHasColor) style::color_set(resources, character);
}
if (isInfoVisible) ImGui::EndChild();
}
ImGui::EndChild();
}
int Inventory::count()
{
int count{};
for (auto& [type, quantity] : values)
count += quantity;
return count;
}
}
+1 -6
View File
@@ -22,15 +22,10 @@ namespace game::state::play::menu
public:
static constexpr auto SIZE = 96.0f;
std::map<int, int> values{};
std::unordered_map<int, Entity> actors{};
std::unordered_map<int, util::imgui::widget::EntityButton> itemButtons{};
int selectedItemID{-1};
bool can_upgrade(const resource::xml::Schema&, int itemID);
bool upgrade(resource::xml::Schema&, int itemID, bool isAll);
bool upgrade_all_possible(resource::xml::Schema&);
void update(Resources&, ItemManager&, Entity&, Autofeed&);
int count();
void update(Resources&, ItemManager&, Entity&);
};
}
+74 -12
View File
@@ -24,6 +24,30 @@ namespace game::state::play
return element.type == resource::xml::Schema::Element::EFFECT;
}
bool dialogue_scene_effect_get(const resource::xml::Schema& dialogue, const resource::xml::Schema::Element& entry)
{
for (auto child : entry.children)
{
if (child < 0 || child >= (int)dialogue.elements.size()) continue;
auto& effect = dialogue.elements[child];
if (dialogue_effect_is(effect) && effect.typeString == "Scene") return true;
}
return false;
}
bool dialogue_silent_effect_get(const resource::xml::Schema& dialogue, const resource::xml::Schema::Element& entry,
int index)
{
for (auto child : entry.children)
{
if (child < 0 || child >= (int)dialogue.elements.size()) continue;
auto& effect = dialogue.elements[child];
if (dialogue_effect_is(effect) && effect.typeString == "Silent" && effect.start <= index && index < effect.end)
return true;
}
return false;
}
constexpr auto OSCILLATION_AMPLITUDE_DEFAULT = 2.0f;
constexpr auto OSCILLATION_FREQUENCY_DEFAULT = 1.0f;
constexpr auto SCREEN_SHAKE_MAGNITUDE_DEFAULT = 0.03f;
@@ -95,11 +119,15 @@ namespace game::state::play
}
}
void Text::set(resource::xml::Schema::Element* dialogueEntry, Entity& character, bool isInterruptible)
void Text::set(resource::xml::Schema::Element* dialogueEntry, Entity& character, bool isInterruptible,
bool isSequenceAdvance)
{
if (!dialogueEntry) return;
this->entry = dialogueEntry;
if (!isSequenceAdvance) isSceneActive = false;
if (!isSceneActive && !isSequenceAdvance)
isSceneActive = dialogue_scene_effect_get(character.data.dialogue, *dialogueEntry);
isFinished = false;
isTerminal = dialogue_entry_terminal_get(character.data.dialogue, *dialogueEntry);
index = 0;
@@ -108,13 +136,19 @@ namespace game::state::play
currentDelay = 0;
isStartEffectsApplied = false;
isBlipQueued = false;
isAnimationEntryStarted = false;
animationEntryTimePrevious = 0.0f;
time = 0.0f;
isEnabled = true;
if (!dialogueEntry->animation.empty())
{
auto interrupt = isSequenceAdvance ? Entity::Interrupt::ALWAYS
: isInterruptible ? Entity::Interrupt::IF_ALLOWED
: Entity::Interrupt::NEVER;
character.play({.animation = dialogueEntry->animation,
.appendID = character.animation_append_id_get(),
.interrupt = isInterruptible ? Entity::Interrupt::IF_ALLOWED
: Entity::Interrupt::NEVER});
.appendID = character.animation_append_id_get(),
.interrupt = interrupt});
}
auto* dialogueRoot = character.data.dialogue.root();
baseDelay = dialogueRoot && dialogueRoot->delayTicks > 0 ? dialogueRoot->delayTicks : 2;
currentDelay = baseDelay;
@@ -135,10 +169,10 @@ namespace game::state::play
.mode = Entity::PLAY_FORCE});
}
}
if (dialogueEntry->text.empty())
if (dialogueEntry->text.empty() && dialogueEntry->next.empty())
isEnabled = false;
else
character.isTalking = true;
else if (!dialogueEntry->text.empty())
character.isTalking = !dialogue_silent_effect_get(character.data.dialogue, *dialogueEntry, 0);
}
void Text::update(Entity& character, Canvas& canvas)
@@ -191,11 +225,14 @@ namespace game::state::play
}
}
auto delayCharacter = dialogue_delay_character_get(character.data.dialogue, entry->text, index - 1, currentDelay);
auto isSilent = dialogue_silent_effect_get(character.data.dialogue, *entry, index - 1);
currentDelay = delayCharacter.delay;
character.isTalking = !delayCharacter.isDisableTalk;
character.isTalking = !delayCharacter.isDisableTalk && !isSilent;
auto blipDelay = dialogueRoot ? dialogueRoot->blipDelayTicks : 0;
auto isBlipStep = blipDelay > 0 && index % blipDelay == 0;
if (delayCharacter.isDisableTalk)
if (isSilent)
isBlipQueued = false;
else if (delayCharacter.isDisableTalk)
{
if (isBlipStep) isBlipQueued = true;
}
@@ -210,10 +247,31 @@ namespace game::state::play
isFinished = true;
character.isTalking = false;
isBlipQueued = false;
if (isTerminal) isSceneActive = false;
}
}
}
if (entry && entry->text.empty())
{
auto* animation = character.animation_get();
auto isAnimationEntryReady = entry->animation.empty() || !animation || character.state == Entity::STOPPED ||
(!isAnimationEntryStarted && animation->frameNum <= 1) ||
(animation->isLoop && isAnimationEntryStarted &&
character.time < animationEntryTimePrevious);
isAnimationEntryStarted = true;
animationEntryTimePrevious = character.time;
if (!entry->next.empty() && isAnimationEntryReady)
set(character.data.dialogue.dialogue_entry_get(*entry), character, true, true);
else if (entry->next.empty())
{
isFinished = true;
isEnabled = false;
if (isTerminal) isSceneActive = false;
}
return;
}
auto& dialogue = character.data.dialogue;
auto& menuSchema = character.data.menuSchema;
@@ -326,7 +384,7 @@ namespace game::state::play
auto label = branch->text.empty() ? "<choice>" : branch->text.c_str();
ImGui::PushID(index);
if (WIDGET_FX(ImGui::Button(label, buttonSize)))
set(dialogue.dialogue_entry_get(*branch), character);
set(dialogue.dialogue_entry_get(*branch), character, true, true);
ImGui::SetItemTooltip("%s", label);
ImGui::PopID();
@@ -337,7 +395,7 @@ namespace game::state::play
{
auto* next = dialogue.dialogue_entry_get(*choices.front());
if (next) menuSchema.root()->soundSelect.play();
set(next, character);
set(next, character, true, true);
}
}
else
@@ -364,13 +422,14 @@ namespace game::state::play
{
auto* next = dialogue.dialogue_entry_get(*entry);
if (next) menuSchema.root()->soundSelect.play();
set(next, character);
set(next, character, true, true);
}
}
else if (isAdvance)
{
isEnabled = false;
entry = nullptr;
isSceneActive = false;
}
}
}
@@ -406,6 +465,7 @@ namespace game::state::play
index = target;
isFinished = true;
character.isTalking = false;
if (isTerminal) isSceneActive = false;
}
}
}
@@ -421,6 +481,7 @@ namespace game::state::play
{
isEnabled = false;
entry = nullptr;
isSceneActive = false;
}
}
}
@@ -428,4 +489,5 @@ namespace game::state::play
bool Text::is_interruptible() const { return !entry || isTerminal; }
bool Text::is_finished() const { return isFinished; }
bool Text::is_terminal() const { return isTerminal; }
bool Text::is_scene_active() const { return isSceneActive; }
}
+5 -1
View File
@@ -19,6 +19,9 @@ namespace game::state::play
bool isTerminal{};
bool isStartEffectsApplied{};
bool isBlipQueued{};
bool isSceneActive{};
bool isAnimationEntryStarted{};
float animationEntryTimePrevious{};
public:
static constexpr auto LIFETIME = 10.0f;
@@ -28,10 +31,11 @@ namespace game::state::play
bool isEnabled{true};
float time{};
void set(resource::xml::Schema::Element*, Entity&, bool isInterruptible = true);
void set(resource::xml::Schema::Element*, Entity&, bool isInterruptible = true, bool isSequenceAdvance = false);
void update(Entity&, Canvas&);
bool is_interruptible() const;
bool is_finished() const;
bool is_terminal() const;
bool is_scene_active() const;
};
}
+35 -38
View File
@@ -10,37 +10,40 @@ using namespace game::util;
namespace game::state::play
{
constexpr auto ZOOM_LADDER_EPSILON = 0.01f;
constexpr auto ZOOM_WHEEL_ZERO = 0.0f;
constexpr auto ZERO_FLOAT = 0.0f;
constexpr auto ONE_INT = 1;
float zoom_ladder_level_get(int index)
int zoom_ladder_index_get(float zoom)
{
auto clampedIndex = std::clamp(index, 0, World::ZOOM_LADDER_LEVEL_COUNT - 1);
auto exponent = (float)clampedIndex / (float)(World::ZOOM_LADDER_LEVEL_COUNT - 1);
return World::ZOOM_MIN * (float)std::pow(World::ZOOM_MAX / World::ZOOM_MIN, exponent);
}
int zoom_ladder_next_index_get(float zoom, int direction)
{
if (direction > 0)
auto result = 0;
auto distance = std::abs(zoom - World::ZOOM_LADDER.front());
for (int index = 1; index < (int)World::ZOOM_LADDER.size(); index++)
{
for (int index = 0; index < World::ZOOM_LADDER_LEVEL_COUNT; ++index)
if (zoom < zoom_ladder_level_get(index) - ZOOM_LADDER_EPSILON) return index;
return World::ZOOM_LADDER_LEVEL_COUNT - 1;
auto nextDistance = std::abs(zoom - World::ZOOM_LADDER[index]);
if (nextDistance >= distance) continue;
distance = nextDistance;
result = index;
}
for (int index = World::ZOOM_LADDER_LEVEL_COUNT - 1; index >= 0; --index)
if (zoom > zoom_ladder_level_get(index) + ZOOM_LADDER_EPSILON) return index;
return 0;
return result;
}
float zoom_ladder_zoom_get(float zoom, float wheel)
bool bounds_is_valid(glm::vec4 value)
{
auto direction = wheel > ZOOM_WHEEL_ZERO ? 1 : -1;
auto stepCount = std::max(1, (int)std::ceil(std::abs(wheel)));
auto index = zoom_ladder_next_index_get(zoom, direction);
index = std::clamp(index + ((stepCount - 1) * direction), 0, World::ZOOM_LADDER_LEVEL_COUNT - 1);
return zoom_ladder_level_get(index);
return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z) && std::isfinite(value.w) &&
value.z > value.x && value.w > value.y;
}
void World::bounds_set(const glm::vec4& newBounds)
{
bounds = bounds_is_valid(newBounds) ? newBounds : BOUNDS;
size = {bounds.z - bounds.x, bounds.w - bounds.y};
}
void World::zoom_index_set(Canvas& canvas)
{
zoomIndex = zoom_ladder_index_get(canvas.zoom);
canvas.zoom = ZOOM_LADDER[zoomIndex];
}
void World::set(Entity& character, Canvas& canvas, Focus focus)
@@ -82,14 +85,17 @@ namespace game::state::play
if (cursorRoot)
if (auto animation = cursorRoot->animationZoom.get()) cursor.play({*animation});
zoom = zoom_ladder_zoom_get(zoom, io.MouseWheel);
auto direction = io.MouseWheel > ZOOM_WHEEL_ZERO ? ONE_INT : -ONE_INT;
zoomIndex = std::clamp(zoomIndex + direction, 0, (int)ZOOM_LADDER.size() - ONE_INT);
zoom = ZOOM_LADDER[zoomIndex];
auto zoomFactorAfter = math::to_unit(zoom);
pan = cursorWorld - (cursorPos / zoomFactorAfter);
}
}
zoom = glm::clamp(ZOOM_MIN, zoom, ZOOM_MAX);
zoomIndex = zoom_ladder_index_get(zoom);
zoom = ZOOM_LADDER[zoomIndex];
if (ImGui::IsKeyPressed(ImGuiKey_Home)) character_focus(character, canvas, focus);
}
@@ -98,37 +104,28 @@ namespace game::state::play
{
static constexpr float MENU_WIDTH_MULTIPLIER = 0.30f;
static constexpr float TOOLS_WIDTH_MULTIPLIER = 0.10f;
static constexpr float INFO_HEIGHT_MULTIPLIER = 4.0f;
static constexpr float PADDING = 100.0f;
auto rect = character.rect();
if (!std::isfinite(rect.x) || !std::isfinite(rect.y) || !std::isfinite(rect.z) || !std::isfinite(rect.w) ||
rect.z <= 0.0f || rect.w <= 0.0f)
rect.z <= ZERO_FLOAT || rect.w <= ZERO_FLOAT)
return;
rect = {rect.x - PADDING * 0.5f, rect.y - PADDING * 0.5f, rect.z + PADDING, rect.w + PADDING};
auto infoHeightPixels =
ImGui::GetTextLineHeightWithSpacing() * INFO_HEIGHT_MULTIPLIER + ImGui::GetStyle().WindowPadding.y * 2.0f;
auto usableHeightPixels = std::max(1.0f, (float)canvas.size.y - infoHeightPixels);
auto zoomFactor = std::min((float)canvas.size.x / rect.z, usableHeightPixels / rect.w);
auto zoomFactor = std::min((float)canvas.size.x / rect.z, (float)canvas.size.y / rect.w);
canvas.zoom = glm::clamp(ZOOM_MIN, math::to_percent(zoomFactor), ZOOM_MAX);
zoom_index_set(canvas);
zoomFactor = math::to_unit(canvas.zoom);
auto rectCenter = glm::vec2(rect.x + rect.z * 0.5f, rect.y + rect.w * 0.5f);
auto viewSizeWorld = glm::vec2(canvas.size) / zoomFactor;
auto infoHeightWorld = infoHeightPixels / zoomFactor;
canvas.pan = rectCenter - vec2(viewSizeWorld.x * 0.5f, (viewSizeWorld.y + infoHeightWorld) * 0.5f);
canvas.pan = rectCenter - viewSizeWorld * 0.5f;
auto menuWidthWorld = (canvas.size.x * MENU_WIDTH_MULTIPLIER) / zoomFactor;
auto toolsWidthWorld = (canvas.size.x * TOOLS_WIDTH_MULTIPLIER) / zoomFactor;
if (focus == Focus::MENU || focus == Focus::MENU_TOOLS) canvas.pan.x += menuWidthWorld * 0.5f;
if (focus == Focus::TOOLS || focus == Focus::MENU_TOOLS) canvas.pan.x -= toolsWidthWorld * 0.5f;
auto panMin = glm::vec2(0.0f, 0.0f);
auto panMax = glm::max(glm::vec2(0.0f), SIZE - viewSizeWorld);
canvas.pan = glm::clamp(panMin, canvas.pan, panMax);
}
}
+13 -5
View File
@@ -1,7 +1,9 @@
#pragma once
#include "../../render/canvas.hpp"
#include <array>
#include "../../entity.hpp"
#include "../../render/canvas.hpp"
#include "character_manager.hpp"
#include "item_manager.hpp"
@@ -11,13 +13,13 @@ namespace game::state::play
class World
{
public:
static constexpr auto ZOOM_MIN = 50.0f;
static constexpr auto ZOOM_MIN = 100.0f;
static constexpr auto ZOOM_BASE = 100.0f;
static constexpr auto ZOOM_LADDER_LEVEL_COUNT = 10;
static constexpr auto ZOOM_MAX = 400.0f;
static constexpr auto SIZE = glm::vec2{1920, 1080};
static constexpr auto BOUNDS =
glm::vec4(SIZE.x * 0.05, SIZE.y * 0.05, SIZE.x - (SIZE.x * 0.05f), SIZE.y - (SIZE.y * 0.05f));
static constexpr auto BOUNDS = glm::vec4{96, 54, 1824, 1026};
static constexpr std::array<float, 10> ZOOM_LADDER{100.0f, 115.0f, 130.0f, 150.0f, 175.0f,
200.0f, 235.0f, 275.0f, 325.0f, 400.0f};
enum Focus
{
@@ -27,6 +29,12 @@ namespace game::state::play
TOOLS
};
glm::vec2 size{SIZE};
glm::vec4 bounds{BOUNDS};
int zoomIndex{};
void bounds_set(const glm::vec4& newBounds);
void zoom_index_set(Canvas& canvas);
void update(Entity& character, Entity& cursor, Canvas& canvas, Focus = CENTER);
void character_focus(Entity& character, Canvas& canvas, Focus = CENTER);
void set(Entity& character, Canvas& canvas, Focus = CENTER);
+2 -2
View File
@@ -62,7 +62,7 @@ namespace game::state::select
{
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(game::util::color::GRAY)));
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
ImGui::TextWrapped("%s", character->description.c_str());
ImGui::PopFont();
@@ -86,7 +86,7 @@ namespace game::state::select
if (ImGui::BeginTabItem("Credits"))
{
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(game::util::color::GRAY)));
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
if (!character->credits.empty())
ImGui::TextWrapped("%s", character->credits.c_str());
+1 -1
View File
@@ -13,4 +13,4 @@ namespace game::state::select
void update(Resources&, int characterIndex);
};
}
}
+5 -4
View File
@@ -68,13 +68,14 @@ namespace game::util::imgui::widget
{
if (!is_rect_valid(rect)) rect = entity.rect();
auto rectSize = glm::vec2(rect.z, rect.w);
scale = (size.x <= 0.0f || size.y <= 0.0f || !is_rect_valid(rect))
? 0.0f
: std::min(size.x / rectSize.x, size.y / rectSize.y);
auto isRectValid = is_rect_valid(rect);
auto rectSize = isRectValid ? glm::vec2(rect.z, rect.w) : glm::vec2(1.0f);
scale = (size.x <= 0.0f || size.y <= 0.0f || !isRectValid) ? 0.0f
: std::min(size.x / rectSize.x, size.y / rectSize.y);
if (maxScale > 0.0f) scale = std::min(maxScale, scale);
auto previewSize = rectSize * scale;
if (!std::isfinite(previewSize.x) || !std::isfinite(previewSize.y)) previewSize = {};
imageSize = ImVec2(previewSize.x, previewSize.y);
auto canvasSize = glm::ivec2(std::max(1.0f, previewSize.x), std::max(1.0f, previewSize.y));
+88 -32
View File
@@ -6,6 +6,7 @@
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <limits>
#include <string_view>
#include <vector>
@@ -36,7 +37,9 @@ namespace game::util::physfs
constexpr auto CRC_INITIAL = 0xffffffffu;
constexpr auto CRC_POLYNOMIAL = 0xedb88320u;
constexpr auto CRC_XOR = 0xffffffffu;
constexpr auto MOUNT_EXPORT = "__developer_export__";
constexpr auto ZIP_TEMP_EXTENSION = ".tmp";
constexpr auto ZIP_SIZE_LIMIT = std::numeric_limits<uint32_t>::max();
constexpr auto ZIP_ENTRY_LIMIT = std::numeric_limits<uint16_t>::max();
struct ZipEntry
{
@@ -44,6 +47,7 @@ namespace game::util::physfs
std::vector<uint8_t> data{};
uint32_t crc{};
uint32_t offset{};
uint32_t uncompressedSize{};
};
uint32_t crc32_get(const std::vector<uint8_t>& data)
@@ -83,7 +87,7 @@ namespace game::util::physfs
uint16_write(stream, ZIP_DATE_NONE);
uint32_write(stream, entry.crc);
uint32_write(stream, (uint32_t)entry.data.size());
uint32_write(stream, (uint32_t)entry.data.size());
uint32_write(stream, entry.uncompressedSize);
uint16_write(stream, (uint16_t)entry.path.size());
uint16_write(stream, ZIP_EXTRA_LENGTH_NONE);
stream.write(entry.path.data(), (std::streamsize)entry.path.size());
@@ -100,7 +104,7 @@ namespace game::util::physfs
uint16_write(stream, ZIP_DATE_NONE);
uint32_write(stream, entry.crc);
uint32_write(stream, (uint32_t)entry.data.size());
uint32_write(stream, (uint32_t)entry.data.size());
uint32_write(stream, entry.uncompressedSize);
uint16_write(stream, (uint16_t)entry.path.size());
uint16_write(stream, ZIP_EXTRA_LENGTH_NONE);
uint16_write(stream, ZIP_COMMENT_LENGTH_NONE);
@@ -254,46 +258,98 @@ namespace game::util::physfs
auto output = destination;
if (output.extension().empty()) output += ".zip";
if (!output.parent_path().empty()) std::filesystem::create_directories(output.parent_path(), error);
Archive archive(source, MOUNT_EXPORT);
if (!archive.is_valid()) return false;
std::vector<std::filesystem::path> paths{};
for (auto& entry : std::filesystem::recursive_directory_iterator(source, error))
if (!error && entry.is_regular_file(error)) paths.emplace_back(entry.path());
std::ranges::sort(paths);
auto outputTemp = output;
outputTemp += ZIP_TEMP_EXTENSION;
std::vector<ZipEntry> entries{};
entries.reserve(paths.size());
for (auto& path : paths)
auto outputAbsolute = std::filesystem::absolute(output, error).lexically_normal();
if (error) return false;
auto outputTempAbsolute = std::filesystem::absolute(outputTemp, error).lexically_normal();
if (error) return false;
auto iterator = std::filesystem::recursive_directory_iterator(source, error);
if (error) return false;
for (auto& file : iterator)
{
auto relative = std::filesystem::relative(path, source, error).generic_string();
if (relative.empty() || error) continue;
auto physfsPath = Path(std::string(archive) + "/" + relative);
auto data = physfsPath.read();
auto size = std::filesystem::file_size(path, error);
if (!error && size > 0 && data.empty()) return false;
entries.push_back({.path = relative, .data = std::move(data)});
entries.back().crc = crc32_get(entries.back().data);
if (error) return false;
if (!file.is_regular_file(error)) continue;
auto fileAbsolute = std::filesystem::absolute(file.path(), error).lexically_normal();
if (fileAbsolute == outputAbsolute || fileAbsolute == outputTempAbsolute) continue;
auto relative = std::filesystem::relative(file.path(), source, error);
if (error) return false;
auto relativeString = relative.generic_string();
if (relativeString.size() > ZIP_ENTRY_LIMIT) return false;
auto size = std::filesystem::file_size(file.path(), error);
if (error || size > ZIP_SIZE_LIMIT) return false;
std::vector<uint8_t> data((size_t)size);
std::ifstream input(file.path(), std::ios::binary);
if (!input) return false;
if (!data.empty()) input.read((char*)data.data(), (std::streamsize)data.size());
if (!input) return false;
entries.emplace_back(std::move(relativeString), std::move(data), crc32_get(data), ZIP_ATTRIBUTE_NONE,
(uint32_t)data.size());
}
std::ofstream stream(output, std::ios::binary | std::ios::trunc);
if (!stream.is_open()) return false;
if (entries.size() > ZIP_ENTRY_LIMIT) return false;
std::ranges::sort(entries, {}, &ZipEntry::path);
auto parent = output.parent_path();
if (!parent.empty())
{
std::filesystem::create_directories(parent, error);
if (error) return false;
}
std::filesystem::remove(outputTemp, error);
error.clear();
std::ofstream stream(outputTemp, std::ios::binary | std::ios::trunc);
if (!stream) return false;
for (auto& entry : entries)
{
entry.offset = (uint32_t)stream.tellp();
auto offset = stream.tellp();
if (offset < 0 || (uint64_t)offset > ZIP_SIZE_LIMIT) return false;
entry.offset = (uint32_t)offset;
local_header_write(stream, entry);
if (!entry.data.empty()) stream.write((const char*)entry.data.data(), (std::streamsize)entry.data.size());
if (!entry.data.empty()) stream.write((char*)entry.data.data(), (std::streamsize)entry.data.size());
}
auto directoryOffset = (uint32_t)stream.tellp();
for (auto& entry : entries)
central_header_write(stream, entry);
auto directorySize = (uint32_t)stream.tellp() - directoryOffset;
end_header_write(stream, (uint16_t)entries.size(), directorySize, directoryOffset);
auto directoryOffset = stream.tellp();
if (directoryOffset < 0 || (uint64_t)directoryOffset > ZIP_SIZE_LIMIT) return false;
return stream.good();
for (const auto& entry : entries)
central_header_write(stream, entry);
auto directoryEnd = stream.tellp();
if (directoryEnd < 0 || (uint64_t)directoryEnd > ZIP_SIZE_LIMIT) return false;
auto directorySize = (uint64_t)directoryEnd - (uint64_t)directoryOffset;
if (directorySize > ZIP_SIZE_LIMIT) return false;
end_header_write(stream, (uint16_t)entries.size(), (uint32_t)directorySize, (uint32_t)directoryOffset);
stream.close();
if (!stream)
{
std::filesystem::remove(outputTemp, error);
return false;
}
std::filesystem::rename(outputTemp, output, error);
if (error)
{
error.clear();
std::filesystem::remove(output, error);
if (error) return false;
std::filesystem::rename(outputTemp, output, error);
}
if (error) return false;
return std::filesystem::is_regular_file(output, error);
}
}