diff --git a/src/anm2/anm2.cpp b/src/anm2/anm2.cpp index 127a241..deb5a83 100644 --- a/src/anm2/anm2.cpp +++ b/src/anm2/anm2.cpp @@ -236,7 +236,6 @@ namespace anm2ed::anm2 for (auto& [sourceID, sourceEvent] : source.content.events) { auto event = sourceEvent; - event.soundID = remap_id(soundRemap, event.soundID); int destinationID = find_by_name(content.events, event.name); if (destinationID != -1) @@ -253,7 +252,8 @@ namespace anm2ed::anm2 { for (auto& frame : item.frames) { - frame.soundID = remap_id(soundRemap, frame.soundID); + for (auto& soundID : frame.soundIDs) + soundID = remap_id(soundRemap, soundID); frame.eventID = remap_id(eventRemap, frame.eventID); } }; diff --git a/src/anm2/anm2_sounds.cpp b/src/anm2/anm2_sounds.cpp index f2eb492..d9c4b0e 100644 --- a/src/anm2/anm2_sounds.cpp +++ b/src/anm2/anm2_sounds.cpp @@ -34,7 +34,8 @@ namespace anm2ed::anm2 std::set used; for (auto& animation : animations.items) for (auto& trigger : animation.triggers.frames) - if (content.sounds.contains(trigger.soundID)) used.insert(trigger.soundID); + for (auto& soundID : trigger.soundIDs) + if (content.sounds.contains(soundID)) used.insert(soundID); std::set unused; for (auto& [id, sound] : content.sounds) diff --git a/src/anm2/event.h b/src/anm2/event.h index 0cd9fe2..5569c78 100644 --- a/src/anm2/event.h +++ b/src/anm2/event.h @@ -9,7 +9,6 @@ namespace anm2ed::anm2 { public: std::string name{}; - int soundID{-1}; Event() = default; Event(tinyxml2::XMLElement*, int&); diff --git a/src/anm2/frame.cpp b/src/anm2/frame.cpp index 1f4c82a..a67de97 100644 --- a/src/anm2/frame.cpp +++ b/src/anm2/frame.cpp @@ -40,7 +40,17 @@ namespace anm2ed::anm2 else { element->QueryIntAttribute("EventId", &eventID); - element->QueryIntAttribute("SoundId", &soundID); + + int soundID{}; + // Backwards compatibility with old formats + if (element->QueryIntAttribute("SoundId", &soundID) == XML_SUCCESS) soundIDs.push_back(soundID); + + for (auto child = element->FirstChildElement("Sound"); child; child = child->NextSiblingElement("Sound")) + { + child->QueryIntAttribute("Id", &soundID); + soundIDs.push_back(soundID); + } + element->QueryIntAttribute("AtFrame", &atFrame); } } @@ -94,7 +104,13 @@ namespace anm2ed::anm2 break; case TRIGGER: element->SetAttribute("EventId", eventID); - element->SetAttribute("SoundId", soundID); + + for (auto& id : soundIDs) + { + auto soundChild = element->InsertNewChildElement("Sound"); + soundChild->SetAttribute("Id", id); + } + element->SetAttribute("AtFrame", atFrame); break; default: diff --git a/src/anm2/frame.h b/src/anm2/frame.h index c03676a..277f100 100644 --- a/src/anm2/frame.h +++ b/src/anm2/frame.h @@ -12,30 +12,23 @@ namespace anm2ed::anm2 constexpr auto FRAME_DURATION_MIN = 1; constexpr auto FRAME_DURATION_MAX = 1000000; -#define MEMBERS \ - X(isVisible, bool, true) \ - X(isInterpolated, bool, false) \ - X(rotation, float, 0.0f) \ - X(duration, int, FRAME_DURATION_MIN) \ - X(atFrame, int, -1) \ - X(eventID, int, -1) \ - X(soundID, int, -1) \ - X(pivot, glm::vec2, {}) \ - X(crop, glm::vec2, {}) \ - X(position, glm::vec2, {}) \ - X(size, glm::vec2, {}) \ - X(scale, glm::vec2, glm::vec2(100.0f)) \ - X(colorOffset, glm::vec3, glm::vec3()) \ - X(tint, glm::vec4, types::color::WHITE) - class Frame { public: -#define X(name, type, ...) type name = __VA_ARGS__; - MEMBERS -#undef X - -#undef MEMBERS + bool isVisible{true}; + bool isInterpolated{false}; + float rotation{}; + int duration{FRAME_DURATION_MIN}; + int atFrame{-1}; + int eventID{-1}; + std::vector soundIDs{}; + glm::vec2 pivot{}; + glm::vec2 crop{}; + glm::vec2 position{}; + glm::vec2 size{}; + glm::vec2 scale{}; + glm::vec3 colorOffset{}; + glm::vec4 tint{types::color::WHITE}; Frame() = default; Frame(tinyxml2::XMLElement*, Type); @@ -52,8 +45,6 @@ namespace anm2ed::anm2 std::optional isInterpolated{}; std::optional rotation{}; std::optional duration{}; - std::optional atFrame{}; - std::optional eventID{}; std::optional pivotX{}; std::optional pivotY{}; std::optional cropX{}; diff --git a/src/document.cpp b/src/document.cpp index 2be27d4..5ac84e3 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -194,10 +194,6 @@ namespace anm2ed { sound.unused = anm2.sounds_unused(); sound.labels_set(anm2.sound_labels_get()); - - for (auto& animation : anm2.animations.items) - for (auto& trigger : animation.triggers.frames) - if (!anm2.content.sounds.contains(trigger.soundID)) trigger.soundID = -1; }; switch (type) diff --git a/src/imgui/window/animation_preview.cpp b/src/imgui/window/animation_preview.cpp index 01f43f4..6cf53ad 100644 --- a/src/imgui/window/animation_preview.cpp +++ b/src/imgui/window/animation_preview.cpp @@ -204,8 +204,13 @@ namespace anm2ed::imgui animation && animation->triggers.isVisible && (!isOnlyShowLayers || manager.isRecording)) { if (auto trigger = animation->triggers.frame_generate(playback.time, anm2::TRIGGER); trigger.isVisible) - if (anm2.content.sounds.contains(trigger.soundID)) - anm2.content.sounds[trigger.soundID].audio.play(false, mixer); + { + auto soundID = trigger.soundIDs.size() > 1 + ? (int)trigger.soundIDs[math::random_in_range(0, trigger.soundIDs.size())] + : (int)trigger.soundIDs.front(); + + if (anm2.content.sounds.contains(soundID)) anm2.content.sounds[soundID].audio.play(false, mixer); + } } } diff --git a/src/imgui/window/frame_properties.cpp b/src/imgui/window/frame_properties.cpp index aabceb4..e46ed5f 100644 --- a/src/imgui/window/frame_properties.cpp +++ b/src/imgui/window/frame_properties.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "math_.h" #include "strings.h" @@ -38,14 +39,6 @@ namespace anm2ed::imgui frame->eventID = useFrame.eventID); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TRIGGER_EVENT)); - if (combo_negative_one_indexed(localize.get(BASIC_SOUND), - frame ? &useFrame.soundID : &dummy_value_negative(), - document.sound.labels) && - frame) - DOCUMENT_EDIT(document, localize.get(EDIT_TRIGGER_SOUND), Document::FRAMES, - frame->soundID = useFrame.soundID); - ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TRIGGER_SOUND)); - if (input_int_range(localize.get(BASIC_AT_FRAME), frame ? useFrame.atFrame : dummy_value(), 0, std::numeric_limits::max(), STEP, STEP_FAST, !frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0) && @@ -59,6 +52,45 @@ namespace anm2ed::imgui DOCUMENT_EDIT(document, localize.get(EDIT_TRIGGER_VISIBILITY), Document::FRAMES, frame->isVisible = useFrame.isVisible); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TRIGGER_VISIBILITY)); + + ImGui::SeparatorText(localize.get(LABEL_SOUNDS)); + + auto childSize = imgui::size_without_footer_get(); + + if (ImGui::BeginChild("##Sounds Child", childSize, ImGuiChildFlags_Borders)) + { + if (!useFrame.soundIDs.empty()) + { + for (auto [i, id] : std::views::enumerate(useFrame.soundIDs)) + { + ImGui::PushID(i); + if (combo_negative_one_indexed("##Sound", frame ? &id : &dummy_value_negative(), + document.sound.labels) && + frame) + DOCUMENT_EDIT(document, localize.get(EDIT_TRIGGER_SOUND), Document::FRAMES, + frame->soundIDs[i] = id); + ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TRIGGER_SOUND)); + ImGui::PopID(); + } + } + } + ImGui::EndChild(); + + auto widgetSize = imgui::widget_size_with_row_get(2); + + if (ImGui::Button(localize.get(BASIC_ADD), widgetSize) && frame) + DOCUMENT_EDIT(document, localize.get(EDIT_ADD_TRIGGER_SOUND), Document::FRAMES, + frame->soundIDs.push_back(-1)); + ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ADD_TRIGGER_SOUND)); + + ImGui::SameLine(); + + ImGui::BeginDisabled(useFrame.soundIDs.empty()); + if (ImGui::Button(localize.get(BASIC_REMOVE), widgetSize) && frame) + DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_TRIGGER_SOUND), Document::FRAMES, + frame->soundIDs.pop_back()); + ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REMOVE_TRIGGER_SOUND)); + ImGui::EndDisabled(); } else { diff --git a/src/loader.cpp b/src/loader.cpp index 81cbcc4..7050737 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -16,6 +16,8 @@ #include "snapshots.h" #include "socket.h" +#include "util/math_.h" + using namespace anm2ed::types; using namespace anm2ed::util; @@ -216,6 +218,8 @@ namespace anm2ed glDisable(GL_DEPTH_TEST); glDisable(GL_LINE_SMOOTH); + math::random_seed_set(); + IMGUI_CHECKVERSION(); if (!ImGui::CreateContext()) { diff --git a/src/resource/strings.h b/src/resource/strings.h index c5a731a..c9c0ce4 100644 --- a/src/resource/strings.h +++ b/src/resource/strings.h @@ -95,6 +95,8 @@ namespace anm2ed X(EDIT_ADD_NULL, "Add Null", "Añadir Null", "Добавить нуль", "添加Null", "Null 추가") \ X(EDIT_ADD_SPRITESHEET, "Add Spritesheet", "Añadir Spritesheet", "Добавить спрайт-лист", "添加图集", "스프라이트 시트 추가") \ X(EDIT_ADD_SOUND, "Add Sound", "Añadir Sonido", "Добавить звук", "添加声音", "사운드 추가") \ + X(EDIT_ADD_TRIGGER_SOUND, "Add Trigger Sound", "Añadir Sonido del Trigger", "Добавить звук триггера", "添加事件触发器声音", "트리거 사운드 추가") \ + X(EDIT_REMOVE_TRIGGER_SOUND, "Remove Trigger Sound", "Remover Sonido del Trigger", "Удалить звук триггера", "移除事件触发器声音", "트리거 사운드 제거") \ X(EDIT_ANIMATION_LENGTH, "Animation Length", "Duracion De Animacion", "Длина анимации", "动画时长", "애니메이션 길이") \ X(EDIT_AUTHOR, "Author", "Autor", "Автор", "制作者", "작성자") \ X(EDIT_BAKE_FRAMES, "Bake Frames", "Hacer Bake de Frames", "Запечь кадры", "烘培/提前渲染", "프레임 베이크") \ @@ -300,6 +302,7 @@ namespace anm2ed X(LABEL_SNAP, "Snap", "Ajustar", "Привязка", "吸附", "맞추기") \ X(LABEL_SNAPSHOTS, "Snapshots", "Snapshots", "Снапшоты", "快照", "스냅숏") \ X(LABEL_SOUND, "Sound", "Sonido", "Звук", "声音", "사운드") \ + X(LABEL_SOUNDS, "Sounds", "Sonidos", "Звук", "声音", "사운드") \ X(LABEL_SOUNDS_WINDOW, "Sounds###Sounds", "Sonidos###Sounds", "Звук###Sounds", "声音###Sounds", "사운드###Sounds") \ X(LABEL_SOURCE, "Source", "Fuente", "Източник", "动画层来源", "소스") \ X(LABEL_SPRITESHEET, "Spritesheet", "Spritesheet", "Спрайт-лист", "图集", "스프라이트 시트") \ @@ -579,6 +582,8 @@ namespace anm2ed X(TOOLTIP_TRIGGER_AT_FRAME, "Change the frame the trigger will be activated at.", "Cambia el Frame en el que el trigger sera activado.", "Изменить кадр, на котором будет активироваться триггер.", "更改此触发器的触发帧.", "트리거가 활성화될 프레임을 변경합니다.") \ X(TOOLTIP_TRIGGER_EVENT, "Change the event this trigger uses.", "Cambia el evento que usa este trigger.", "Изменить событие, которое использует этот триггер.", "更改此触发器使用的事件.", "이 트리거가 사용할 이벤트를 변경합니다.") \ X(TOOLTIP_TRIGGER_SOUND, "Change the sound this trigger uses.", "Cambia el sonido que usa este trigger.", "Изменить звук, который использует этот триггер.", "更改此触发器使用的声音.", "이 트리거가 사용할 사운드를 변경합니다.") \ + X(TOOLTIP_ADD_TRIGGER_SOUND, "Add a new sound to the trigger.\nIf multiple sounds exist, one will be chosen to play randomly.", "Añadir un nuevo sonido al trigger.\nSi existen múltiples sonidos, uno se elegirá al azar para reproducirse.", "Добавить новый звук к триггеру.\nЕсли существует несколько звуков, один будет выбран случайным образом для воспроизведения.", "为此事件触发器添加一个新声音.\n如果存在多个声音, 将随机选择一个进行播放.", "트리거에 새 사운드를 추가합니다.\n여러 사운드가 있으면 재생할 사운드가 무작위로 선택됩니다.") \ + X(TOOLTIP_REMOVE_TRIGGER_SOUND, "Remove the last trigger sound.", "Remover el último sonido del trigger.", "Удалить последний звук триггера.", "移除最后一个事件触发器声音.", "마지막 트리거 사운드를 제거합니다.") \ X(TOOLTIP_TRIGGER_VISIBILITY, "Toggle the trigger's visibility.", "Alterna la visibilidad del trigger.", "Переключить видимость триггера.", "切换触发器是否可见.", "트리거를 표시하거나 숨깁니다.") \ X(TOOLTIP_UI_SCALE, "Change the scale of the UI.", "Cambia la escala de la interfaz de usuario.", "Изменить масштаб пользовательского интерфейса.", "更改界面(UI)的缩放.", "UI 비율을 변경합니다.") \ X(TOOLTIP_UNUSED_ITEMS_HIDDEN, "Unused layers/nulls are hidden. Press to show them.", "Las capas/nulls no utilizados estan ocultos. Presiona para hacerlos visibles", "Неиспользуемые слои/нули скрыты. Нажмите, чтобы их показать.", "正在隐藏未使用的动画层/Null. 点击以显示它们.", "사용되지 않는 레이어/Null이 숨겨져 있습니다. 표시하려면 누르세요.") \ diff --git a/src/util/math_.cpp b/src/util/math_.cpp index 5a4f99f..f553fa9 100644 --- a/src/util/math_.cpp +++ b/src/util/math_.cpp @@ -1,5 +1,6 @@ #include "math.h" +#include #include #include @@ -76,4 +77,7 @@ namespace anm2ed::util::math return glm::translate(mat4(1.0f), vec3(position, 0.0f)) * local; } + float random() { return (float)rand() / RAND_MAX; } + float random_in_range(float min, float max) { return min + random() * (max - min); } + void random_seed_set() { srand(std::time(nullptr)); } } \ No newline at end of file diff --git a/src/util/math_.h b/src/util/math_.h index 5a1beac..59d3ef8 100644 --- a/src/util/math_.h +++ b/src/util/math_.h @@ -5,25 +5,13 @@ namespace anm2ed::util::math { - template constexpr T percent_to_unit(T value) - { - return value / 100.0f; - } + template constexpr T percent_to_unit(T value) { return value / 100.0f; } - template constexpr T unit_to_percent(T value) - { - return value * 100.0f; - } + template constexpr T unit_to_percent(T value) { return value * 100.0f; } - constexpr float uint8_to_float(int value) - { - return (float)(value / 255.0f); - } + constexpr float uint8_to_float(int value) { return (float)(value / 255.0f); } - constexpr int float_to_uint8(float value) - { - return (int)(value * 255); - } + constexpr int float_to_uint8(float value) { return (int)(value * 255); } constexpr std::array uv_vertices_get(glm::vec2 uvMin, glm::vec2 uvMax) { @@ -41,4 +29,8 @@ namespace anm2ed::util::math glm::mat4 quad_model_get(glm::vec2 = {}, glm::vec2 = {}, glm::vec2 = {}, glm::vec2 = glm::vec2(1.0f), float = {}); glm::mat4 quad_model_parent_get(glm::vec2 = {}, glm::vec2 = {}, glm::vec2 = glm::vec2(1.0f), float = {}); + + float random(); + float random_in_range(float min, float max); + void random_seed_set(); } \ No newline at end of file