#include "actor.hpp" #include "../util/map.hpp" #include "../util/math.hpp" #include "../util/unordered_map.hpp" #include "../util/vector.hpp" #include #include #include "../log.hpp" #include using namespace glm; using namespace game::util; using namespace game::resource::xml; namespace game::entity { Actor::Override::Override(int _id, Anm2::Type _type, Actor::Override::Mode _mode, FrameOptional _frame, std::optional _time, Actor::Override::Function _function, float _cycles) : id(_id), type(_type), mode(_mode), frame(_frame), time(_time), function(_function), cycles(_cycles) { frameBase = _frame; timeStart = _time; } Actor::Actor(const Actor&) = default; Actor::Actor(Actor&&) noexcept = default; Actor& Actor::operator=(const Actor&) = default; Actor& Actor::operator=(Actor&&) noexcept = default; Actor::Actor(Anm2 _anm2, vec2 _position, Mode playMode, float startAtTime, int startAnimationIndex) : Anm2(_anm2), position(_position) { this->mode = playMode; this->startTime = startAtTime; if (startAnimationIndex != -1) play(startAnimationIndex, playMode, startAtTime); else play_default_animation(playMode, startAtTime); } Anm2::Animation* Actor::animation_get(int index) { if (index == -1) index = animationIndex; if (animationMapReverse.contains(index)) return &animations[index]; return nullptr; } Anm2::Animation* Actor::animation_get(const std::string& name) { if (animationMap.contains(name)) return &animations[animationMap[name]]; return nullptr; } bool Actor::is_playing(const std::string& name) { if (name.empty()) return state == PLAYING; else return state == PLAYING && animationMap[name] == animationIndex; } int Actor::animation_index_get(const std::string& name) { if (animationMap.contains(name)) return animationMap[name]; return -1; } Anm2::Item* Actor::item_get(Anm2::Type type, int id, int checkAnimationIndex) { if (checkAnimationIndex == -1) checkAnimationIndex = this->animationIndex; if (auto animation = animation_get(checkAnimationIndex)) { switch (type) { case Anm2::ROOT: return &animation->rootAnimation; break; case Anm2::LAYER: return unordered_map::find(animation->layerAnimations, id); case Anm2::NULL_: return map::find(animation->nullAnimations, id); break; case Anm2::TRIGGER: return &animation->triggers; default: return nullptr; } } return nullptr; } int Actor::item_length(Anm2::Item* item) { if (!item) return -1; int duration{}; for (auto& frame : item->frames) duration += frame.duration; return duration; } Anm2::Frame Actor::frame_generate(Anm2::Item& item, float frameTime, Anm2::Type type, int id) { Anm2::Frame frame{}; frame.isVisible = false; if (item.frames.empty()) return frame; frameTime = frameTime < 0.0f ? 0.0f : frameTime; Anm2::Frame* frameNext = nullptr; Anm2::Frame frameNextCopy{}; int durationCurrent = 0; int durationNext = 0; for (int i = 0; i < (int)item.frames.size(); i++) { Anm2::Frame& checkFrame = item.frames[i]; frame = checkFrame; durationNext += frame.duration; if (frameTime >= durationCurrent && frameTime < durationNext) { if (i + 1 < (int)item.frames.size()) { frameNext = &item.frames[i + 1]; frameNextCopy = *frameNext; } else frameNext = nullptr; break; } durationCurrent += frame.duration; } auto override_handle = [&](Anm2::Frame& overrideFrame) { for (auto& override : overrides) { if (override.type != type) continue; if (override.id != id) continue; auto& source = override.frame; switch (override.mode) { case Override::SET: if (source.position.has_value()) overrideFrame.position = *source.position; if (source.pivot.has_value()) overrideFrame.pivot = *source.pivot; if (source.size.has_value()) overrideFrame.size = *source.size; if (source.scale.has_value()) overrideFrame.scale = *source.scale; if (source.crop.has_value()) overrideFrame.crop = *source.crop; if (source.rotation.has_value()) overrideFrame.rotation = *source.rotation; if (source.tint.has_value()) overrideFrame.tint = *source.tint; if (source.colorOffset.has_value()) overrideFrame.colorOffset = *source.colorOffset; if (source.isInterpolated.has_value()) overrideFrame.isInterpolated = *source.isInterpolated; if (source.isVisible.has_value()) overrideFrame.isVisible = *source.isVisible; break; case Override::ADD: if (source.scale.has_value()) overrideFrame.scale += *source.scale; break; default: break; } } }; override_handle(frame); if (frameNext) override_handle(frameNextCopy); if (frame.isInterpolated && frameNext && frame.duration > 1) { auto interpolation = (frameTime - durationCurrent) / (durationNext - durationCurrent); frame.rotation = glm::mix(frame.rotation, frameNextCopy.rotation, interpolation); frame.position = glm::mix(frame.position, frameNextCopy.position, interpolation); frame.scale = glm::mix(frame.scale, frameNextCopy.scale, interpolation); frame.colorOffset = glm::mix(frame.colorOffset, frameNextCopy.colorOffset, interpolation); frame.tint = glm::mix(frame.tint, frameNextCopy.tint, interpolation); } return frame; } void Actor::play(int index, Mode playMode, float startAtTime, float speedMultiplierValue) { if (!vector::in_bounds(animations, index)) return; if (playMode != PLAY_FORCE && index == animationIndex) return; this->playedEventID = -1; this->playedTriggers.clear(); this->speedMultiplier = speedMultiplierValue; this->animationIndex = index; this->time = startAtTime; if (playMode == PLAY) state = PLAYING; } void Actor::queue_play(QueuedPlay newQueuedPlay) { queuedPlay = newQueuedPlay; } void Actor::queue_default_animation() { queue_play({defaultAnimation}); } void Actor::play(const std::string& name, Mode playMode, float startAtTime, float speedMultiplierValue) { if (animationMap.contains(name)) play(animationMap.at(name), playMode, startAtTime, speedMultiplierValue); else logger.error(std::string("Animation \"" + name + "\" does not exist! Unable to play!")); } void Actor::play_default_animation(Mode playMode, float startAtTime, float speedMultiplierValue) { play(defaultAnimationID, playMode, startAtTime, speedMultiplierValue); } void Actor::tick() { if (state == Actor::STOPPED) { if (!nextQueuedPlay.empty()) { queuedPlay = nextQueuedPlay; queuedPlay.isPlayAfterAnimation = false; nextQueuedPlay = QueuedPlay{}; } currentQueuedPlay = QueuedPlay{}; } if (auto animation = animation_get(); animation && animation->isLoop) currentQueuedPlay = QueuedPlay{}; if (!queuedPlay.empty()) { auto& index = animationMap.at(queuedPlay.animation); if (queuedPlay.isPlayAfterAnimation) nextQueuedPlay = queuedPlay; else if (index != animationIndex && currentQueuedPlay.isInterruptible) { play(queuedPlay.animation, queuedPlay.mode, queuedPlay.time, queuedPlay.speedMultiplier); currentQueuedPlay = queuedPlay; } queuedPlay = QueuedPlay{}; } auto animation = animation_get(); if (!animation || animation->frameNum == 1 || mode == SET || state == STOPPED) return; playedEventID = -1; for (auto& trigger : animation->triggers.frames) { if (!playedTriggers.contains(trigger.atFrame) && time >= trigger.atFrame) { auto id = trigger.soundIDs[(int)math::random_max((float)trigger.soundIDs.size())]; if (auto sound = map::find(sounds, id)) sound->audio.play(); playedTriggers.insert((int)trigger.atFrame); playedEventID = trigger.eventID; } } auto increment = (fps / TICK_RATE) * speedMultiplier; time += increment; if (time >= animation->frameNum) { if (animation->isLoop) time = 0.0f; else state = STOPPED; playedTriggers.clear(); } for (int i = 0; i < (int)overrides.size();) { auto& override_ = overrides[i]; if (override_.function) override_.function(override_); if (override_.time.has_value()) { *override_.time -= 1.0f; if (*override_.time <= 0.0f) { overrides.erase(overrides.begin() + i); continue; } } i++; } } glm::vec4 Actor::null_frame_rect(int nullID) { constexpr ivec2 CORNERS[4] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; if (nullID == -1) return glm::vec4(NAN); auto item = item_get(Anm2::NULL_, nullID); if (!item) return glm::vec4(NAN); auto animation = animation_get(); if (!animation) return glm::vec4(NAN); auto root = frame_generate(animation->rootAnimation, time, Anm2::ROOT); auto frame = frame_generate(*item, time, Anm2::NULL_, nullID); if (!frame.isVisible) return glm::vec4(NAN); auto rootModel = math::quad_model_no_size_get(root.position + position, root.pivot, math::to_unit(root.scale), root.rotation); auto frameModel = math::quad_model_get(frame.scale, frame.position, frame.scale * 0.5f, vec2(1.0f), frame.rotation); auto model = rootModel * frameModel; float minX = std::numeric_limits::infinity(); float minY = std::numeric_limits::infinity(); float maxX = -std::numeric_limits::infinity(); float maxY = -std::numeric_limits::infinity(); for (auto& corner : CORNERS) { vec4 world = model * vec4(corner, 0.0f, 1.0f); minX = std::min(minX, world.x); minY = std::min(minY, world.y); maxX = std::max(maxX, world.x); maxY = std::max(maxY, world.y); } return glm::vec4(minX, minY, maxX - minX, maxY - minY); } void Actor::render(resource::Shader& textureShader, resource::Shader& rectShader, Canvas& canvas) { auto animation = animation_get(); if (!animation) return; auto root = frame_generate(animation->rootAnimation, time, Anm2::ROOT); auto rootModel = math::quad_model_no_size_get(root.position + position, root.pivot, math::to_unit(root.scale), root.rotation); for (auto& i : animation->layerOrder) { auto& layerAnimation = animation->layerAnimations[i]; if (!layerAnimation.isVisible) continue; auto layer = map::find(layers, i); if (!layer) continue; auto spritesheet = map::find(spritesheets, layer->spritesheetID); if (!spritesheet) continue; auto frame = frame_generate(layerAnimation, time, Anm2::LAYER, i); if (!frame.isVisible) continue; auto model = math::quad_model_get(frame.size, frame.position, frame.pivot, math::to_unit(frame.scale), frame.rotation); model = rootModel * model; auto& texture = spritesheet->texture; if (!texture.is_valid()) return; auto tint = frame.tint * root.tint; auto colorOffset = frame.colorOffset + root.colorOffset; auto inset = vec2(0); auto uvMin = (frame.crop + inset) / vec2(texture.size); auto uvMax = (frame.crop + frame.size - inset) / vec2(texture.size); auto uvVertices = math::uv_vertices_get(uvMin, uvMax); canvas.texture_render(textureShader, texture.id, model, tint, colorOffset, uvVertices.data()); } if (isShowNulls) { for (int i = 0; i < (int)animation->nullAnimations.size(); i++) { auto& nullAnimation = animation->nullAnimations[i]; if (!nullAnimation.isVisible) continue; auto frame = frame_generate(nullAnimation, time, Anm2::NULL_, i); if (!frame.isVisible) continue; auto model = math::quad_model_get(frame.scale, frame.position, frame.scale * 0.5f, vec2(1.0f), frame.rotation); model = rootModel * model; canvas.rect_render(rectShader, model); } } } vec4 Actor::rect() { constexpr ivec2 CORNERS[4] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; auto animation = animation_get(); float minX = std::numeric_limits::infinity(); float minY = std::numeric_limits::infinity(); float maxX = -std::numeric_limits::infinity(); float maxY = -std::numeric_limits::infinity(); bool any = false; if (!animation) return vec4(-NAN); for (float t = 0.0f; t < (float)animation->frameNum; t += 1.0f) { mat4 transform(1.0f); auto root = frame_generate(animation->rootAnimation, t, Anm2::ROOT); transform *= math::quad_model_no_size_get(root.position + position, root.pivot, math::to_unit(root.scale), root.rotation); for (auto& [id, layerAnimation] : animation->layerAnimations) { if (!layerAnimation.isVisible) continue; auto frame = frame_generate(layerAnimation, t, Anm2::LAYER, id); if (frame.size == vec2() || !frame.isVisible) continue; auto layerTransform = transform * math::quad_model_get(frame.size, frame.position, frame.pivot, math::to_unit(frame.scale), frame.rotation); for (auto& corner : CORNERS) { vec4 world = layerTransform * vec4(corner, 0.0f, 1.0f); minX = std::min(minX, world.x); minY = std::min(minY, world.y); maxX = std::max(maxX, world.x); maxY = std::max(maxY, world.y); any = true; } } } if (!any) return vec4(-NAN); return {minX, minY, maxX - minX, maxY - minY}; } bool Actor::is_animation_finished() { if (auto animation = animation_get()) { if (animation->isLoop) return true; if (time > animation->frameNum) return true; } return false; } void Actor::consume_played_event() { playedEventID = -1; } };