#include "character/CharacterAnimation.h" #include "character/CharacterObject.h" #include #include #include namespace frostbite2D { namespace { const std::array kAvatarParts = { "weapon_avatar", "aurora_avatar", "hair_avatar", "hat_avatar", "face_avatar", "breast_avatar", "coat_avatar", "skin_avatar", "waist_avatar", "pants_avatar", "shoes_avatar", "weapon"}; std::string truncatePath(const std::string& path) { size_t slashPos = path.find_last_of('/'); if (slashPos == std::string::npos) { return {}; } return path.substr(0, slashPos); } std::string actionTail(const std::string& path) { size_t slashPos = path.find_last_of('/'); if (slashPos == std::string::npos) { return path; } return path.substr(slashPos); } } // namespace bool CharacterAnimation::Init(CharacterObject* parent, const character::CharacterConfig& config, const CharacterEquipmentManager& equipmentManager) { parent_ = parent; actionAnimations_.clear(); currentActionTag_.clear(); direction_ = 1; for (const auto& [actionName, actionPath] : config.animationPath) { for (const char* slotName : kAvatarParts) { CreateAnimationBySlot(actionName, slotName, actionPath, config, equipmentManager); } } if (actionAnimations_.empty()) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "CharacterAnimation: no usable action animations for job %d", config.jobId); return false; } if (actionAnimations_.count("rest") > 0) { SetAction("rest"); } else { SetAction(actionAnimations_.begin()->first); } return true; } std::string CharacterAnimation::FormatImgPath(std::string path, Animation::ReplaceData data) { size_t pos = path.find("%04d"); if (pos != std::string::npos) { path.replace(pos, 4, "%02d%02d"); } char buffer[512] = {}; std::snprintf(buffer, sizeof(buffer), path.c_str(), data.param1, data.param2); return std::string(buffer); } void CharacterAnimation::CreateAnimationBySlot( const std::string& actionName, const std::string& slotName, const std::string& actionPath, const character::CharacterConfig& config, const CharacterEquipmentManager& equipmentManager) { if (slotName == std::string("skin_avatar")) { Animation::ReplaceData replaceData(0, 0); if (const auto* equip = equipmentManager.GetEquip(slotName)) { auto it = equip->jobAnimations.find(config.jobId); if (it != equip->jobAnimations.end() && !it->second.empty()) { replaceData.param1 = it->second.front().imgFormat[0]; replaceData.param2 = it->second.front().imgFormat[1]; } } auto animation = MakePtr(actionPath, FormatImgPath, replaceData); if (!animation || !animation->IsUsable()) { return; } animation->SetVisible(false); AddChild(animation); actionAnimations_[actionName].push_back(animation); return; } const auto* equip = equipmentManager.GetEquip(slotName); if (!equip) { return; } auto jobAniIt = equip->jobAnimations.find(config.jobId); if (jobAniIt == equip->jobAnimations.end()) { return; } std::string equipDir = truncatePath(equip->path); std::string actionPathTail = actionTail(actionPath); for (const auto& variation : jobAniIt->second) { if (variation.animationGroup.empty()) { continue; } std::string aniPath = equipDir + "/" + variation.animationGroup + actionPathTail; auto animation = MakePtr( aniPath, FormatImgPath, Animation::ReplaceData(variation.imgFormat[0], variation.imgFormat[1])); if (!animation || !animation->IsUsable()) { continue; } animation->SetVisible(false); animation->SetZOrder(variation.layer); AddChild(animation); actionAnimations_[actionName].push_back(animation); } } void CharacterAnimation::SetAction(const std::string& actionName) { auto nextIt = actionAnimations_.find(actionName); if (nextIt == actionAnimations_.end()) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "CharacterAnimation: action %s missing, keep %s", actionName.c_str(), currentActionTag_.c_str()); return; } if (!currentActionTag_.empty()) { auto currentIt = actionAnimations_.find(currentActionTag_); if (currentIt != actionAnimations_.end()) { for (auto& animation : currentIt->second) { animation->Reset(); animation->SetVisible(false); } } } for (auto& animation : nextIt->second) { animation->Reset(); animation->SetVisible(true); } currentActionTag_ = actionName; SetDirection(direction_); } void CharacterAnimation::SetDirection(int direction) { direction_ = direction >= 0 ? 1 : -1; auto currentIt = actionAnimations_.find(currentActionTag_); if (currentIt == actionAnimations_.end()) { return; } for (auto& animation : currentIt->second) { if (animation) { animation->SetDirection(direction_); } } } } // namespace frostbite2D