diff --git a/Frostbite2D/include/frostbite2D/2d/actor.h b/Frostbite2D/include/frostbite2D/2d/actor.h index 0015eb4..7b67190 100644 --- a/Frostbite2D/include/frostbite2D/2d/actor.h +++ b/Frostbite2D/include/frostbite2D/2d/actor.h @@ -33,6 +33,8 @@ public: virtual void Update(float deltaTime); virtual void Render(); + virtual void OnUpdate(float deltaTime) {} + virtual void OnAdded(Actor* parent) {} void AddChild(RefPtr child); void RemoveChild(RefPtr child); diff --git a/Frostbite2D/include/frostbite2D/animation/animation.h b/Frostbite2D/include/frostbite2D/animation/animation.h index 003c512..350fd45 100644 --- a/Frostbite2D/include/frostbite2D/animation/animation.h +++ b/Frostbite2D/include/frostbite2D/animation/animation.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -30,8 +31,8 @@ public: void Init(const std::string& aniPath); - void Update(float deltaTime) override; - void OnAdded(Actor* parent); + void OnUpdate(float deltaTime) override; + void OnAdded(Actor* parent) override; void SetVisible(bool visible); public: diff --git a/Frostbite2D/include/frostbite2D/graphics/camera.h b/Frostbite2D/include/frostbite2D/graphics/camera.h index 70c7484..15a1b7e 100644 --- a/Frostbite2D/include/frostbite2D/graphics/camera.h +++ b/Frostbite2D/include/frostbite2D/graphics/camera.h @@ -15,6 +15,8 @@ public: const Vec2& getPosition() const { return position_; } float getZoom() const { return zoom_; } + int getViewportWidth() const { return viewportWidth_; } + int getViewportHeight() const { return viewportHeight_; } void lookAt(const Vec2& target); void move(const Vec2& delta); @@ -31,4 +33,4 @@ public: bool flipY_ = false; // 调试用:Y轴翻转开关 }; -} \ No newline at end of file +} diff --git a/Frostbite2D/src/frostbite2D/2d/actor.cpp b/Frostbite2D/src/frostbite2D/2d/actor.cpp index 952c296..8e79844 100644 --- a/Frostbite2D/src/frostbite2D/2d/actor.cpp +++ b/Frostbite2D/src/frostbite2D/2d/actor.cpp @@ -142,6 +142,7 @@ Actor::~Actor() { void Actor::Update(float deltaTime) { executeUpdateListeners(UpdatePhase::BeforeChildren, deltaTime); + OnUpdate(deltaTime); UpdateChildren(deltaTime); executeUpdateListeners(UpdatePhase::AfterChildren, deltaTime); } @@ -188,6 +189,7 @@ void Actor::AddChild(Ptr child) { child->SetParent(this); child->SetScene(scene_); insertChildByZOrder(child); + child->OnAdded(this); } void Actor::SetZOrder(int zOrder) { diff --git a/Frostbite2D/src/frostbite2D/animation/animation.cpp b/Frostbite2D/src/frostbite2D/animation/animation.cpp index 0b6bb1e..3f9412d 100644 --- a/Frostbite2D/src/frostbite2D/animation/animation.cpp +++ b/Frostbite2D/src/frostbite2D/animation/animation.cpp @@ -91,7 +91,14 @@ void Animation::Init(const std::string& aniPath) { std::string dir = (lastSlash != std::string::npos) ? aniPath.substr(0, lastSlash + 1) : ""; for (auto& ani : alsInfo->aniList) { - auto subAni = MakePtr(dir + ani.second.path); + std::string subAniPath = dir + ani.second.path; + auto subAni = MakePtr(subAniPath); + if (!subAni || !subAni->IsUsable()) { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "Animation: failed to load ALS child animation %s", + subAniPath.c_str()); + continue; + } if (ani.second.layer.size() >= 2) { subAni->SetZOrder(ani.second.layer[1]); } @@ -103,32 +110,44 @@ void Animation::Init(const std::string& aniPath) { FlushFrame(0); } -void Animation::Update(float deltaTime) { - if (usable_ && IsVisible()) { - float dtMs = deltaTime * 1000.0f; - currentFrameTime_ += dtMs; - - while (currentFrameTime_ >= nextFrameDelay_) { - currentFrameTime_ -= nextFrameDelay_; - - if (currentFrameIndex_ < (totalFrameCount_ - 1)) { - FlushFrame(currentFrameIndex_ + 1); - } else { - if (isLooping_) { - FlushFrame(0); - } else { - usable_ = false; - if (endCallback_) { - endCallback_(); - } - break; - } - } - } - } else { +void Animation::OnUpdate(float deltaTime) { + if (!IsVisible()) { for (auto& sp : spriteFrames_) { sp->SetVisible(false); } + return; + } + + if (!usable_) { + if (currentFrame_) { + currentFrame_->SetVisible(true); + } + return; + } + + float dtMs = deltaTime * 1000.0f; + currentFrameTime_ += dtMs; + + while (currentFrameTime_ >= nextFrameDelay_) { + currentFrameTime_ -= nextFrameDelay_; + + if (currentFrameIndex_ < (totalFrameCount_ - 1)) { + FlushFrame(currentFrameIndex_ + 1); + } else { + if (isLooping_) { + FlushFrame(0); + } else { + usable_ = false; + currentFrameTime_ = 0.0f; + if (currentFrame_) { + currentFrame_->SetVisible(true); + } + if (endCallback_) { + endCallback_(); + } + break; + } + } } } diff --git a/Frostbite2D/src/frostbite2D/animation/animation_data.cpp b/Frostbite2D/src/frostbite2D/animation/animation_data.cpp index c8b15a3..fd7d44e 100644 --- a/Frostbite2D/src/frostbite2D/animation/animation_data.cpp +++ b/Frostbite2D/src/frostbite2D/animation/animation_data.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -368,12 +369,46 @@ std::optional parseAlsFromPvf(const std::string& path) { return std::nullopt; } - auto content = pvf.getFileContent(path); - if (!content) { + auto rawData = pvf.getFileRawData(path); + if (!rawData) { return std::nullopt; } - return parseAlsInfo(*content); + ScriptParser parser(*rawData, path); + if (!parser.isValid()) { + return std::nullopt; + } + + AlsInfo info; + while (!parser.isEnd()) { + auto segmentValue = parser.next(); + if (!segmentValue || segmentValue->type != ScriptValueType::String) { + continue; + } + + std::string segment = toLowerCase(segmentValue->toString()); + if (segment == "[use animation]") { + auto aniPath = parser.next(); + auto aniKey = parser.next(); + if (!aniPath || !aniKey) { + break; + } + info.aniList[aniKey->toString()].path = toLowerCase(aniPath->toString()); + } else if (segment == "[none effect add]" || segment == "[add]") { + auto layer1 = parser.next(); + auto layer2 = parser.next(); + auto aniKey = parser.next(); + if (!layer1 || !layer2 || !aniKey) { + break; + } + + info.aniList[aniKey->toString()].layer = { + std::stoi(layer1->toString()), + std::stoi(layer2->toString())}; + } + } + + return info; } } // namespace animation diff --git a/Frostbite2D/src/frostbite2D/resource/script_parser.cpp b/Frostbite2D/src/frostbite2D/resource/script_parser.cpp index ddc32d1..7b396f9 100644 --- a/Frostbite2D/src/frostbite2D/resource/script_parser.cpp +++ b/Frostbite2D/src/frostbite2D/resource/script_parser.cpp @@ -6,6 +6,25 @@ namespace frostbite2D { +namespace { + +std::string resolveScriptString(PvfArchive& archive, const std::string& fileType, int32 key) { + auto binStr = archive.getBinString(key); + if (!binStr || binStr->empty()) { + return ""; + } + + if (auto loadStr = archive.getLoadString(fileType, *binStr)) { + if (!loadStr->empty()) { + return *loadStr; + } + } + + return *binStr; +} + +} // namespace + std::string ScriptValue::toString() const { switch (type) { case ScriptValueType::Integer: @@ -174,9 +193,7 @@ std::optional ScriptParser::parseValueAt(size_t offset) const { case ScriptOpcode::StringRef7: case ScriptOpcode::StringRef8: { result.type = ScriptValueType::String; - if (auto str = archive.getBinString(data)) { - result.stringValue = *str; - } + result.stringValue = resolveScriptString(archive, fileType_, data); break; } @@ -186,27 +203,14 @@ std::optional ScriptParser::parseValueAt(size_t offset) const { uint8 newOpcode = readByte(offset + 5); (void)newOpcode; int32 newData = readInt32(offset + 6); - - if (auto binStr = archive.getBinString(newData)) { - if (!binStr->empty()) { - if (auto loadStr = archive.getLoadString(fileType_, *binStr)) { - result.stringValue = *loadStr; - } - } - } + result.stringValue = resolveScriptString(archive, fileType_, newData); } break; } case ScriptOpcode::ExtendedString10: { result.type = ScriptValueType::String; - if (auto binStr = archive.getBinString(data)) { - if (!binStr->empty()) { - if (auto loadStr = archive.getLoadString(fileType_, *binStr)) { - result.stringValue = *loadStr; - } - } - } + result.stringValue = resolveScriptString(archive, fileType_, data); break; } diff --git a/Game/assets/ImagePacks2/!HUD_CW.NPK b/Game/assets/ImagePacks2/!HUD_CW.NPK deleted file mode 100644 index b93b362..0000000 Binary files a/Game/assets/ImagePacks2/!HUD_CW.NPK and /dev/null differ diff --git a/Game/assets/SoundPacks/sounds_ui.npk b/Game/assets/SoundPacks/sounds_ui.npk index 7104221..47d9c4d 100644 Binary files a/Game/assets/SoundPacks/sounds_ui.npk and b/Game/assets/SoundPacks/sounds_ui.npk differ diff --git a/Game/assets/audio.xml b/Game/assets/audio.xml index 5e2ce72..39a3cb6 100644 --- a/Game/assets/audio.xml +++ b/Game/assets/audio.xml @@ -2,19185 +2,19842 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + - - + + - - - + + + - - - - + + + + - - - - + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - - + + + + - - - - + + + + - - + + - - - + + + - - + + - - + + - - - - + + + + - - + + - - + + - - + + - - - - + + + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - + + - - - + + + - - - + + + - - - + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - + + + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - - + + + - - + + - - + + - - - - + + + + - + - - - + + + - - + + - - - + + + - + - + - + - + - + - + - + - + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - + + + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - - + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - - - - + + + + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - - + + + + - - - - + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - - - + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - - - + + + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - + - - + + - - + + - + - - + + - - + + - - - - + + + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - - + + + - - + + - - + + - - + + - - + + - + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - + + + + - - + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - + + + - + - - + + - + - + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - - - + + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - + + - - - - + + + + - - - - - - - + + + + + + + - - - - - - - - + + + + + + + + - + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - + + + - - - - + + + + - - - - + + + + - - - - - + + + + + - - - - - + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - + + + + + - - - - + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - + + + + - - - - - - + + + + + + - - - - + + + + - - - - - + + + + + - - - - + + + + - - - - + + + + - - - + + + - - - - + + + + - - - - + + + + - - - - - + + + + + - - + + - - - - - + + + + + - - - - + + + + - - - + + + - - - + + + - - + + - - - + + + - - - - + + + + - - - + + + - - - + + + - - - - + + + + - - - - + + + - - - - + + + + + - - - - + + + + - - - + + + - - - - + + + + + - - - - + + + - - - + + + - - - - + + + + - - - - + + + + - - - + + + - - - - + + + + + - - + + - - + + - - - - - + + + + + + - - - - + + + + - - - - + + + + - - - - - + + + + + + - - - - + + + + - - - - + + + + - - - - - - - + + + + + + + + - - - - + + + + - - - - + + + + - - - - - + + + + + + - - - + + + - - - + + + - - - - - + + + + + + - - - - + + + + - - - - + + + + - - - - - + + + + + + - - - + + + - - - - + + + + - - - - - + + + + + + - - - + + + - - - - + + + + - - - - - + + + + + - - - - + + + - - - - + + + - - - - - - + + + + + + + - - - + + + - - - - + + + + - - - - - + + + + + + - - - + + + - - - - + + + + - - - - - + + + + + + - - - - - + + + + - - - - - + + + + - - - - - - - - - - + + + + + - - + + - - - + + + - - - - + + + + + - - - + + + - - - - + + + + - - - - + + + + + - - - + + + - - - + + + - - - - + + + + + - - + + - - - + + + - - - - + + + + + - - - + + + - - - - - + + + + + - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - - + + + + + - - - + + + - - + + - - - - + + + + + - - - + + + - - - + + + - - - - - - + + + + + + + - - - - + + + + - - - - - + + + + + - - - - - - + + + + + + + - - - - + + + + - - - - + + + + - - - - - - + + + + + + + - - - + + + - - - + + + - - - - - - + + + + + + + - - - + + + - - - + + + - - - + + + - - - - - - - + + + - - + + - - - - - - + + + + + + - - - + + + - - - + + + - - - - - - + + + + + + - - - + + + - - - - + + + + - - - - - - + + + + + + - - - + + + - - - + + + - - - - + + + + - - - + + + - - - + + + - - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + - - - - - + + + + + - - - - + + + + - - - - + + + + - - - - - + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - - - - + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - - - + + + + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - + + + - - + + - - + + - - - + + + - - - - + + + + - - + + - - + + - - - - + + + + - - - - + + + + - - - + + + - - - + + + - - - - - - + + + + + + - - - - - - - - - + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - + + + + + - - - + + + - - - + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - - - + + + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - + + + + + - - - + + + - - + + - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - + + + + - - + + - - + + - - - + + + - - - + + + - - + + - - + + - - + + - - - - + + + + - - + + - - - - + + + + - - - + + + - - - + + + - - + + - - - - + + + + - - + + - - + + - - + + - - + + - - - - + + + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - + + - - - + + + - - - - - + + + + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - - + + + - - - + + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - - - - + + + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - - - + + + + + - - + + - - - - - + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - + + - - - - + + + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - + + + + + - - - + + + - - + + - - - - - - - + + + + + + + - - - + + + - - - - + + + + - - - - + + + + - - - - + + + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - + - - + + - - - + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + - - + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - + - - + + - - + + - + - - + + - - + + - - + + - + - - + + - - + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - + + + - - + + - - - + + + - - + + - - + + - - - - + + + + - - + + - - + + - - - + + + - - + + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - + + + + + - + - - + + - - + + - - - + + + - - + + - - + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - - - + + + + - - + + - - + + - - - - + + + + - - + + - - - - + + + + - - - + + + - - + + - - + + - - - + + + - - - - - + + + + + - - - + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - + + - - - - - + + + + + - - - - + + + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - + + - - + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - + + + + - - + + - - + + - - - - - - + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - - - + + + + - - + + - - - + + + - - + + - - + + - - + + - - - + + + - - - + + + - - + + - - + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + - - - - + + + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - + + + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - - - - + + + + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - + + + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - + + - - - + + + - - - + + + - + - - - + + + - - + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - + + - - - + + + - - - + + + - - - - + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - + + - - + + - - + + - - - - + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - - + + + - - + + - - - - + + + + - - + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - + + - - + + - - - + + + - - + + - - - + + + - - + + - - + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - + + - - - - + + + + - - + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - + + - - + + - - - + + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - + + + - - + + - - + + - - + + - - - + + + - - - + + + - - + + - - + + - - - + + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - - - + + + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - + + + + + + + + - - - - - - + + + + + + - - - - - - - + + + + + + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - + + - - - - + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - - + + + + - - - - - + + + + + - - + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - - - - + + + + + + - - + + - - - - + + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + diff --git a/Game/include/Actor/GameDataLoader.h b/Game/include/Actor/GameDataLoader.h index 5d5fa09..fbfb54b 100644 --- a/Game/include/Actor/GameDataLoader.h +++ b/Game/include/Actor/GameDataLoader.h @@ -44,7 +44,6 @@ struct MoveAreaTarget { struct TileInfo { std::string spritePath; int spriteIndex = 0; - int imgPos = 0; }; struct MapConfig { @@ -52,6 +51,7 @@ struct MapConfig { std::string mapPath; std::string mapDir; int backgroundPos = 0; + Vec2 tilePos = Vec2::Zero(); int farSightScroll = 100; bool hasCameraLimit = false; int cameraLimitMinX = -1; diff --git a/Game/include/Actor/GameMap.h b/Game/include/Actor/GameMap.h index 8b11742..bb18290 100644 --- a/Game/include/Actor/GameMap.h +++ b/Game/include/Actor/GameMap.h @@ -8,6 +8,15 @@ namespace frostbite2D { +/** + * @brief 地图运行时容器,负责把 map 配置组织成可显示/可交互的场景结构 + * + * 这里不直接解析底层资源格式,而是消费 `GameDataLoader` 已经整理好的 + * `MapConfig/TileInfo`。GameMap 主要负责三件事: + * 1. 按固定图层顺序挂载地板、背景动画、场景动画和动态对象 + * 2. 根据 camera limit 和可移动区域维护摄像机关注点 + * 3. 提供移动判定和切图区域查询给上层角色/场景逻辑使用 + */ class GameMap : public Actor { public: using MapMoveArea = game::MoveAreaTarget; @@ -15,40 +24,65 @@ public: GameMap(); ~GameMap() override = default; + /// 读取地图配置并重建当前地图内容。重复调用时会先清空旧地图运行态。 bool LoadMap(const std::string& mapName); + /// 地图进入场景后的初始化入口,目前主要负责音乐播放。 void Enter(); void Update(float deltaTime) override; + /// 将运行时对象挂到 normal 层,并按 y 值设置基础排序。 void AddObject(RefPtr object); + /// 返回地图推荐的默认相机关注点。 + Vec2 GetDefaultCameraFocus() const; + /// 根据 camera limit 和缩放约束修正相机关注点。 + Vec2 ClampCameraFocus(const Vec2& focus, float zoom) const; + /// 将各层转换到屏幕空间;远景层在这里做视差滚动。 + void ApplyCameraFocus(const Vec2& focus); + + /// 根据虚拟可行走区域限制位移,返回修正后的目标坐标。 Vec3 CheckIsItMovable(const Vec3& curPos, const Vec3& posOffset) const; + /// 检查当前位置是否进入 town move area,用于切图/传送判定。 MapMoveArea CheckIsItMoveArea(const Vec3& curPos) const; const std::vector& GetMoveAreaInfo() const; Rect GetMovablePositionArea(size_t index) const; - int GetMapLength() const { return mapLength_; } - int GetMapHeight() const { return mapHeight_; } + int GetBackgroundRepeatWidth() const { return backgroundRepeatWidth_; } private: + /// 初始化固定图层。图层名字与 DNF 地图层概念保持一致。 void initLayers(); + /// 清理当前地图的运行态节点和缓存数据,但保留 GameMap 自身及图层骨架。 void clearLayerChildren(); + /// 创建普通 tile 和扩展 tile,并推导背景平铺需要的横向覆盖宽度。 void InitTile(); + /// 初始化背景动画层,必要时按横向覆盖宽度做横向平铺。 void InitBackgroundAnimation(); + /// 初始化地图对象动画(门、特效、场景摆件等)。 void InitMapAnimation(); + /// 初始化角色移动用的可行走区域。 void InitVirtualMovableArea(); + /// 初始化切图/传送区域。 void InitMoveArea(); - void updateCamera(); - void updateLayerPositions(const Vec2& cameraPos); + /// 将各层转换到屏幕空间;远景层在这里做视差滚动。 + void updateLayerPositions(const Vec2& cameraFocus); + /// 原始地图配置,作为运行时装配地图内容的输入。 game::MapConfig mapConfig_; + /// 地图的固定图层集合,key 为地图层名。 std::unordered_map> layerMap_; + /// bottom 层里的专用地板容器,固定放在该层最底部,避免地图动画被地板压住。 + RefPtr tileRoot_; + /// 角色可行走矩形区域。 std::vector movableArea_; + /// 进入后会触发切图/传送的矩形区域。 std::vector moveArea_; - int mapLength_ = 0; - int mapHeight_ = 0; + /// 地板铺设后推导出的横向覆盖宽度,用于背景动画横向平铺。 + int backgroundRepeatWidth_ = 0; + /// 地图配置里的整体 Y 偏移;既影响层位置,也影响地板校准。 int mapOffsetY_ = 0; bool debugMode_ = false; - Vec2 cameraFocus_ = Vec2::Zero(); + /// 当前地图正在播放的背景音乐。 Ptr currentMusic_; }; diff --git a/Game/include/Actor/GameMapTestScene.h b/Game/include/Actor/GameMapTestScene.h new file mode 100644 index 0000000..d71ebbf --- /dev/null +++ b/Game/include/Actor/GameMapTestScene.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Camera/GameCameraController.h" +#include "GameMap.h" +#include + +namespace frostbite2D { + +class GameMapTestScene : public Scene { +public: + GameMapTestScene(); + ~GameMapTestScene() override = default; + + void onEnter() override; + void onExit() override; + void Update(float deltaTime) override; + +private: + GameCameraController cameraController_; + bool initialized_ = false; + RefPtr map_; +}; + +} // namespace frostbite2D diff --git a/Game/include/Actor/GameTown.h b/Game/include/Actor/GameTown.h index 5e312a3..18d5afa 100644 --- a/Game/include/Actor/GameTown.h +++ b/Game/include/Actor/GameTown.h @@ -1,5 +1,6 @@ #pragma once +#include "Camera/GameCameraController.h" #include "GameDataLoader.h" #include "GameMap.h" #include @@ -38,6 +39,7 @@ private: Vec2 sariaRoomPos_ = Vec2(-1.0f, -1.0f); std::vector mapList_; int curMapIndex_ = -1; + GameCameraController cameraController_; }; } // namespace frostbite2D diff --git a/Game/include/Camera/GameCameraController.h b/Game/include/Camera/GameCameraController.h new file mode 100644 index 0000000..fbee82d --- /dev/null +++ b/Game/include/Camera/GameCameraController.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +struct _SDL_GameController; +typedef struct _SDL_GameController SDL_GameController; + +namespace frostbite2D { + +class GameMap; + +class GameCameraController { +public: + GameCameraController() = default; + ~GameCameraController(); + + void SetMap(GameMap* map); + void SetTarget(Actor* target); + void ClearTarget(); + + void SetDebugEnabled(bool enabled); + bool IsDebugEnabled() const { return debugEnabled_; } + + void SetZoom(float zoom); + float GetZoom() const { return zoom_; } + + void SetFocus(const Vec2& focus); + const Vec2& GetFocus() const { return focus_; } + + void SnapToDefaultFocus(); + void Update(float deltaTime); + +private: + void openDebugController(); + void closeDebugController(); + void updateDebugInput(float deltaTime); + void applyFocus() const; + + GameMap* map_ = nullptr; + Actor* target_ = nullptr; + SDL_GameController* debugController_ = nullptr; + Vec2 focus_ = Vec2::Zero(); + + bool debugEnabled_ = false; + bool initialized_ = false; + + float zoom_ = 1.2f; + float followLerpSpeed_ = 8.0f; + float debugMoveSpeed_ = 800.0f; +}; + +} // namespace frostbite2D diff --git a/Game/src/Actor/GameDataLoader.cpp b/Game/src/Actor/GameDataLoader.cpp index 2f98142..2d620d2 100644 --- a/Game/src/Actor/GameDataLoader.cpp +++ b/Game/src/Actor/GameDataLoader.cpp @@ -80,6 +80,12 @@ public: return tokens_[index_++]; } + void back() { + if (index_ > 0) { + --index_; + } + } + private: std::string path_; std::vector tokens_; @@ -176,6 +182,9 @@ bool loadMapConfig(const std::string& mapPath, MapConfig& outConfig) { std::string segment = stream.get(); if (segment == "[background pos]") { outConfig.backgroundPos = toInt(stream.get()); + } else if (segment == "[tile pos]") { + outConfig.tilePos.x = static_cast(toInt(stream.get(), 0)); + outConfig.tilePos.y = static_cast(toInt(stream.get(), 0)); } else if (segment == "[map name]") { outConfig.name = stream.get(); } else if (segment == "[limit map camera move]") { @@ -273,6 +282,17 @@ bool loadMapConfig(const std::string& mapPath, MapConfig& outConfig) { } } + if (!outConfig.hasCameraLimit) { + outConfig.hasCameraLimit = true; + outConfig.cameraLimitMinX = 0; + outConfig.cameraLimitMaxX = 0; + outConfig.cameraLimitMinY = 0; + outConfig.cameraLimitMaxY = 0; + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "GameDataLoader: map %s missing [limit map camera move], fallback to 0 0 0 0", + outConfig.mapPath.c_str()); + } + return true; } @@ -283,15 +303,17 @@ bool loadTileInfo(const std::string& path, TileInfo& outInfo) { } outInfo = TileInfo(); - size_t slashPos = path.find_last_of('/'); - std::string baseDir = slashPos == std::string::npos ? "" : path.substr(0, slashPos + 1); while (!stream.isEnd()) { - std::string segment = stream.get(); + std::string segment = toLowerCase(stream.get()); if (segment == "[image]") { - outInfo.spritePath = resolveSpritePath(baseDir, stream.get()); + std::string rawImagePath = stream.get(); + outInfo.spritePath = normalizeWithPrefix("sprite", rawImagePath); outInfo.spriteIndex = toInt(stream.get()); - } else if (segment == "[img pos]") { - outInfo.imgPos = toInt(stream.get()); + if (outInfo.spritePath.empty()) { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "GameDataLoader: tile image path resolved empty for %s, raw=%s", + path.c_str(), rawImagePath.c_str()); + } } } diff --git a/Game/src/Actor/GameMap.cpp b/Game/src/Actor/GameMap.cpp index 4813e9b..a479a5a 100644 --- a/Game/src/Actor/GameMap.cpp +++ b/Game/src/Actor/GameMap.cpp @@ -12,6 +12,7 @@ namespace frostbite2D { namespace { +// 图层名和层级顺序与原始地图资源的层语义保持一致,方便直接按 layer 字段挂载。 static const char* kLayerNames[] = { "contact", "distantback", "middleback", "bottom", "closeback", "normal", "close", "cover", "max"}; @@ -19,11 +20,11 @@ static const char* kLayerNames[] = { static const int kLayerOrders[] = { 10000, 50000, 100000, 150000, 200000, 250000, 300000, 350000, 400000}; -constexpr float kScreenWidth = 1280.0f; -constexpr float kScreenHeight = 720.0f; -constexpr float kTileSpacing = 224.0f; +constexpr int kTileRootZOrder = -1000000; constexpr float kExtendedTileStepY = 120.0f; +// 地图里的 tile/img 统一走这里创建,失败时返回空 Sprite +// 占位,避免整张地图中断。 Ptr createMapSprite(const std::string& path, int index) { auto sprite = Sprite::createFromNpk(path, static_cast(index)); if (!sprite) { @@ -56,41 +57,37 @@ void GameMap::clearLayerChildren() { layer->RemoveAllChildren(); } } + tileRoot_.Reset(); movableArea_.clear(); moveArea_.clear(); currentMusic_.Reset(); - mapLength_ = 0; - mapHeight_ = 0; + backgroundRepeatWidth_ = 0; } -bool GameMap::LoadMap(const std::string& mapName) { +bool GameMap::LoadMap(const std::string &mapName) { + // 清空所有图层子节点。 clearLayerChildren(); - + // 读取PVF地图配置。 if (!game::loadMapConfig(mapName, mapConfig_)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "GameMap: failed to load map %s", mapName.c_str()); return false; } + // backgroundPos 会影响地图整体的视觉基线,所以地板和各显示层都会参考它。 mapOffsetY_ = mapConfig_.backgroundPos; + // 加载顺序基本就是地图的组装顺序:先地板,再背景,再对象,再可行走数据。 InitTile(); InitBackgroundAnimation(); InitMapAnimation(); - InitVirtualMovableArea(); - InitMoveArea(); - - if (cameraFocus_ == Vec2::Zero()) { - if (!moveArea_.empty()) { - cameraFocus_ = moveArea_.front().center(); - } else if (mapLength_ > 0 || mapHeight_ > 0) { - cameraFocus_ = Vec2(mapLength_ * 0.5f, mapHeight_ * 0.5f); - } - } + // InitVirtualMovableArea(); + // InitMoveArea(); return true; } void GameMap::Enter() { + // 地图进入时尝试找到第一个可播放的 BGM 并循环播放。 if (!mapConfig_.soundIds.empty()) { auto& audioDatabase = AudioDatabase::get(); for (const auto& soundId : mapConfig_.soundIds) { @@ -107,25 +104,30 @@ void GameMap::Enter() { } } } - - updateCamera(); } -void GameMap::Update(float deltaTime) { - Actor::Update(deltaTime); - (void)deltaTime; - updateCamera(); -} +void GameMap::Update(float deltaTime) { Actor::Update(deltaTime); } void GameMap::InitTile() { if (mapConfig_.tilePaths.empty() && mapConfig_.extendedTilePaths.empty()) { return; } + auto bottomIt = layerMap_.find("bottom"); + if (bottomIt == layerMap_.end() || !bottomIt->second) { + return; + } + + tileRoot_ = MakePtr(); + tileRoot_->SetName("tileRoot"); + tileRoot_->SetZOrder(kTileRootZOrder); + bottomIt->second->AddChild(tileRoot_); + float maxBaseHeight = 0.0f; - float maxTotalBottom = 0.0f; - float maxOffset = 0.0f; + float maxOffset = mapConfig_.tilePos.y; + int normalTileCount = static_cast(mapConfig_.tilePaths.size()); + float nextNormalX = mapConfig_.tilePos.x; for (size_t i = 0; i < mapConfig_.tilePaths.size(); ++i) { game::TileInfo info; @@ -133,43 +135,45 @@ void GameMap::InitTile() { continue; } - auto sprite = createMapSprite(info.spritePath.empty() ? "sprite/character/common/circlecooltime.img" - : info.spritePath, - info.spriteIndex); - sprite->SetPosition(static_cast(i) * kTileSpacing, static_cast(info.imgPos)); - layerMap_["bottom"]->AddChild(sprite); + std::string spritePath = info.spritePath.empty() + ? "sprite/character/common/circlecooltime.img" + : info.spritePath; + auto sprite = createMapSprite(spritePath, info.spriteIndex); + float posX = nextNormalX; + float posY = mapConfig_.tilePos.y; + sprite->SetPosition(posX, posY); + tileRoot_->AddChild(sprite); Vec2 size = sprite->GetSize(); - maxOffset = std::max(maxOffset, static_cast(info.imgPos)); + maxOffset = std::max(maxOffset, posY); maxBaseHeight = std::max(maxBaseHeight, size.y); - mapLength_ = std::max(mapLength_, static_cast(i * kTileSpacing + size.x)); - maxTotalBottom = std::max(maxTotalBottom, info.imgPos + size.y); - } - - if (normalTileCount > 0 && mapLength_ == 0) { - mapLength_ = static_cast(normalTileCount * kTileSpacing); + backgroundRepeatWidth_ = + std::max(backgroundRepeatWidth_, static_cast(posX + size.x)); + nextNormalX += size.x; } + float nextExtendedX = mapConfig_.tilePos.x; for (size_t i = 0; i < mapConfig_.extendedTilePaths.size(); ++i) { game::TileInfo info; if (!game::loadTileInfo(mapConfig_.extendedTilePaths[i], info)) { continue; } - auto sprite = createMapSprite(info.spritePath.empty() ? "sprite/character/common/circlecooltime.img" - : info.spritePath, - info.spriteIndex); + std::string spritePath = info.spritePath.empty() + ? "sprite/character/common/circlecooltime.img" + : info.spritePath; + auto sprite = createMapSprite(spritePath, info.spriteIndex); int row = normalTileCount > 0 ? static_cast(i) / normalTileCount : 0; + float posX = nextExtendedX; float posY = maxOffset + maxBaseHeight + row * kExtendedTileStepY; - sprite->SetPosition(static_cast(i) * kTileSpacing, posY); - layerMap_["bottom"]->AddChild(sprite); + sprite->SetPosition(posX, posY); + tileRoot_->AddChild(sprite); Vec2 size = sprite->GetSize(); - mapLength_ = std::max(mapLength_, static_cast(i * kTileSpacing + size.x)); - maxTotalBottom = std::max(maxTotalBottom, posY + size.y); + backgroundRepeatWidth_ = + std::max(backgroundRepeatWidth_, static_cast(posX + size.x)); + nextExtendedX += size.x; } - - mapHeight_ = std::max(mapHeight_, static_cast(maxTotalBottom)); } void GameMap::InitBackgroundAnimation() { @@ -179,11 +183,15 @@ void GameMap::InitBackgroundAnimation() { continue; } + // 远景背景通常需要横向重复铺满地图;滚动倍率越大,需要铺的份数也越多。 int repeatCount = 1; Vec2 animationSize = animation->GetSize(); float rate = std::max(1.0f, mapConfig_.farSightScroll / 100.0f); - if (animationSize.x > 0.0f && mapLength_ > 0) { - repeatCount = std::max(1, static_cast((mapLength_ * rate) / animationSize.x) + 1); + if (animationSize.x > 0.0f && backgroundRepeatWidth_ > 0) { + repeatCount = + std::max(1, static_cast((backgroundRepeatWidth_ * rate) / + animationSize.x) + + 1); } auto layerIt = layerMap_.find(ani.layer); @@ -196,6 +204,7 @@ void GameMap::InitBackgroundAnimation() { if (!backgroundAni || !backgroundAni->IsUsable()) { continue; } + // 背景动画在各自层内从左到右平铺,zOrder 放到极低,避免压住该层其他内容。 backgroundAni->SetPosition(i * animationSize.x, -120.0f); backgroundAni->SetZOrder(-1000000); layerIt->second->AddChild(backgroundAni); @@ -210,8 +219,10 @@ void GameMap::InitMapAnimation() { continue; } + // 地图对象的显示 y 会扣掉 zPos,等价于把“离地高度”折算回屏幕坐标。 animation->SetPosition(static_cast(ani.xPos), static_cast(ani.yPos - ani.zPos)); + // 同层内继续按 y 排序,保证越靠下的物件越靠前。 animation->SetZOrder(ani.yPos); auto layerIt = layerMap_.find(ani.layer); @@ -223,6 +234,7 @@ void GameMap::InitMapAnimation() { void GameMap::InitVirtualMovableArea() { movableArea_ = mapConfig_.virtualMovableAreas; + // debugMode 打开时,把可行走区域可视化到最高层,方便校验地图配置。 if (!debugMode_) { return; } @@ -234,6 +246,7 @@ void GameMap::InitVirtualMovableArea() { void GameMap::InitMoveArea() { moveArea_ = mapConfig_.townMovableAreas; + // move area 和普通 movable area 分开展示,方便区分“能走”与“会触发切图”。 if (!debugMode_) { return; } @@ -243,55 +256,83 @@ void GameMap::InitMoveArea() { } } -void GameMap::updateCamera() { +Vec2 GameMap::GetDefaultCameraFocus() const { + float centerX = static_cast(mapConfig_.cameraLimitMinX + + mapConfig_.cameraLimitMaxX) * + 0.5f; + float centerY = static_cast(mapConfig_.cameraLimitMinY + + mapConfig_.cameraLimitMaxY) * + 0.5f; + return Vec2(centerX, centerY); +} + +Vec2 GameMap::ClampCameraFocus(const Vec2 &focus, float zoom) const { Camera* camera = Renderer::get().getCamera(); + float targetX = focus.x; + float targetY = focus.y; + float halfWidth = 0.0f; + float halfHeight = 0.0f; + + if (camera) { + float safeZoom = std::max(zoom, 0.01f); + halfWidth = + static_cast(camera->getViewportWidth()) * 0.5f / safeZoom; + halfHeight = + static_cast(camera->getViewportHeight()) * 0.5f / safeZoom; + } + + float minFocusX = static_cast(mapConfig_.cameraLimitMinX) + halfWidth; + float maxFocusX = static_cast(mapConfig_.cameraLimitMaxX) - halfWidth; + float minFocusY = static_cast(mapConfig_.cameraLimitMinY) + halfHeight; + float maxFocusY = static_cast(mapConfig_.cameraLimitMaxY) - halfHeight; + + if (minFocusX > maxFocusX) { + targetX = (static_cast(mapConfig_.cameraLimitMinX) + + static_cast(mapConfig_.cameraLimitMaxX)) * + 0.5f; + } else { + targetX = std::clamp(targetX, minFocusX, maxFocusX); + } + + if (minFocusY > maxFocusY) { + targetY = (static_cast(mapConfig_.cameraLimitMinY) + + static_cast(mapConfig_.cameraLimitMaxY)) * + 0.5f; + } else { + targetY = std::clamp(targetY, minFocusY, maxFocusY); + } + + return Vec2(targetX, targetY); +} + +void GameMap::ApplyCameraFocus(const Vec2 &focus) { + updateLayerPositions(focus); +} + +void GameMap::updateLayerPositions(const Vec2 &cameraFocus) { + (void)cameraFocus; + + Camera *camera = Renderer::get().getCamera(); if (!camera) { return; } - float halfWidth = kScreenWidth * 0.5f; - float halfHeight = kScreenHeight * 0.5f; - float targetX = cameraFocus_.x; - float targetY = cameraFocus_.y; - - if (mapLength_ > 0) { - targetX = std::clamp(targetX, halfWidth, std::max(halfWidth, static_cast(mapLength_) - halfWidth)); - } - if (mapHeight_ > 0) { - targetY = std::clamp(targetY, halfHeight, std::max(halfHeight, static_cast(mapHeight_) - halfHeight)); - } - - if (mapConfig_.hasCameraLimit) { - if (mapConfig_.cameraLimitMinX != -1) { - targetX = std::max(targetX, static_cast(mapConfig_.cameraLimitMinX)); - } - if (mapConfig_.cameraLimitMaxX != -1) { - targetX = std::min(targetX, static_cast(mapConfig_.cameraLimitMaxX)); - } - if (mapConfig_.cameraLimitMinY != -1) { - targetY = std::max(targetY, static_cast(mapConfig_.cameraLimitMinY)); - } - if (mapConfig_.cameraLimitMaxY != -1) { - targetY = std::min(targetY, static_cast(mapConfig_.cameraLimitMaxY)); - } - } - - Vec2 cameraPos(targetX - halfWidth, targetY - halfHeight); - camera->setPosition(cameraPos); - updateLayerPositions(Vec2(targetX, targetY)); -} - -void GameMap::updateLayerPositions(const Vec2& cameraTarget) { - float halfWidth = kScreenWidth * 0.5f; - float halfHeight = kScreenHeight * 0.5f; + Vec2 cameraPos = camera->getPosition(); + // 主视图平移完全交给底层 + // Camera,这里只保留地图层自己的静态校准和少量视差偏移。 for (auto& entry : layerMap_) { const std::string& name = entry.first; RefPtr& layer = entry.second; - float posX = -cameraTarget.x + halfWidth; - float posY = -cameraTarget.y + halfHeight + static_cast(mapOffsetY_); + float posX = 0.0f; + float posY = static_cast(mapOffsetY_); + + // 远景层只做部分水平跟随;主 Camera + // 已经负责完整位移,这里补上“少动一些”的差值。 if (name == "distantback") { - posX *= static_cast(mapConfig_.farSightScroll) / 100.0f; + float scrollFactor = + static_cast(mapConfig_.farSightScroll) / 100.0f; + posX = cameraPos.x * (1.0f - scrollFactor); } layer->SetPosition(posX, posY); } @@ -301,6 +342,7 @@ void GameMap::AddObject(RefPtr object) { if (!object) { return; } + // 动态对象默认进 normal 层,并沿用 2D 地图里常见的“按 y 值排序”规则。 object->SetZOrder(static_cast(object->GetPosition().y)); layerMap_["normal"]->AddObject(object); } @@ -315,6 +357,8 @@ Vec3 GameMap::CheckIsItMovable(const Vec3& curPos, const Vec3& posOffset) const float targetX = curPos.x + posOffset.x; float targetY = curPos.y + posOffset.y; + // X/Y + // 分开判断,这样贴着边移动时不会因为一个方向越界而把另一个方向也一起锁死。 bool isXValid = false; for (const auto& area : movableArea_) { if (area.containsPoint(Vec2(targetX, curPos.y))) { @@ -342,6 +386,7 @@ Vec3 GameMap::CheckIsItMovable(const Vec3& curPos, const Vec3& posOffset) const } GameMap::MapMoveArea GameMap::CheckIsItMoveArea(const Vec3& curPos) const { + // moveArea_ 和配置里的 target 是一一对应的,命中后直接返回同索引的目标信息。 for (size_t i = 0; i < moveArea_.size() && i < mapConfig_.townMovableAreaTargets.size(); ++i) { if (moveArea_[i].containsPoint(Vec2(curPos.x, curPos.y))) { return mapConfig_.townMovableAreaTargets[i]; diff --git a/Game/src/Actor/GameMapTestScene.cpp b/Game/src/Actor/GameMapTestScene.cpp new file mode 100644 index 0000000..777c56d --- /dev/null +++ b/Game/src/Actor/GameMapTestScene.cpp @@ -0,0 +1,48 @@ +#include "Actor/GameMapTestScene.h" +#include +#include +namespace frostbite2D { + +namespace { + +constexpr char kTestMapPath[] = "map/elvengard/elvengard.map"; + +} // namespace + +GameMapTestScene::GameMapTestScene() = default; + +void GameMapTestScene::onEnter() { + Scene::onEnter(); + if (initialized_) { + return; + } + + map_ = MakePtr(); + if (!map_->LoadMap(kTestMapPath)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "GameMapTestScene: failed to load map %s", kTestMapPath); + map_.Reset(); + return; + } + AddChild(map_); + + cameraController_.SetMap(map_.Get()); + cameraController_.SetZoom(1.2f); + cameraController_.ClearTarget(); + cameraController_.SetDebugEnabled(true); + cameraController_.SnapToDefaultFocus(); + map_->Enter(); + initialized_ = true; + +} + +void GameMapTestScene::onExit() { + Scene::onExit(); +} + +void GameMapTestScene::Update(float deltaTime) { + Scene::Update(deltaTime); + cameraController_.Update(deltaTime); +} + +} // namespace frostbite2D diff --git a/Game/src/Actor/GameTown.cpp b/Game/src/Actor/GameTown.cpp index e94b8f1..9719012 100644 --- a/Game/src/Actor/GameTown.cpp +++ b/Game/src/Actor/GameTown.cpp @@ -88,20 +88,30 @@ void GameTown::AddCharacter(RefPtr actor, int areaIndex) { } AddChild(mapIt->map); - mapIt->map->Enter(); + cameraController_.SetMap(mapIt->map.Get()); + cameraController_.SetZoom(1.2f); + cameraController_.SetTarget(actor.Get()); + cameraController_.SnapToDefaultFocus(); if (actor) { + cameraController_.SetDebugEnabled(false); if (areaIndex == -2 && sariaRoomPos_.x >= 0.0f && sariaRoomPos_.y >= 0.0f) { actor->SetPosition(sariaRoomPos_); } mapIt->map->AddObject(actor); + cameraController_.SetFocus(actor->GetPosition()); + } else { + cameraController_.SetDebugEnabled(true); } + mapIt->map->Enter(); + curMapIndex_ = mapIt->areaId; } void GameTown::Update(float deltaTime) { Actor::Update(deltaTime); + cameraController_.Update(deltaTime); } } // namespace frostbite2D diff --git a/Game/src/Actor/GameWorld.cpp b/Game/src/Actor/GameWorld.cpp index f22d80d..92bd6d8 100644 --- a/Game/src/Actor/GameWorld.cpp +++ b/Game/src/Actor/GameWorld.cpp @@ -13,7 +13,6 @@ void GameWorld::onEnter() { return; } - SetScale(1.2f); initialized_ = InitWorld(); } diff --git a/Game/src/Camera/GameCameraController.cpp b/Game/src/Camera/GameCameraController.cpp new file mode 100644 index 0000000..f5d3a5a --- /dev/null +++ b/Game/src/Camera/GameCameraController.cpp @@ -0,0 +1,193 @@ +#include "Camera/GameCameraController.h" +#include "Actor/GameMap.h" +#include +#include +#include +#include +#include + +namespace frostbite2D { + +namespace { + +constexpr float kScreenWidth = 1280.0f; +constexpr float kScreenHeight = 720.0f; +constexpr int kDebugStickDeadzone = 8000; + +float normalizeControllerAxis(int16 value) { + float magnitude = static_cast(value); + if (std::abs(magnitude) <= static_cast(kDebugStickDeadzone)) { + return 0.0f; + } + + float sign = magnitude < 0.0f ? -1.0f : 1.0f; + float normalized = + (std::abs(magnitude) - static_cast(kDebugStickDeadzone)) / + (32767.0f - static_cast(kDebugStickDeadzone)); + return std::clamp(normalized, 0.0f, 1.0f) * sign; +} + +} + +GameCameraController::~GameCameraController() { + closeDebugController(); +} + +void GameCameraController::SetMap(GameMap* map) { + map_ = map; + initialized_ = false; +} + +void GameCameraController::SetTarget(Actor* target) { + target_ = target; +} + +void GameCameraController::ClearTarget() { + target_ = nullptr; +} + +void GameCameraController::SetDebugEnabled(bool enabled) { + debugEnabled_ = enabled; + if (debugEnabled_) { + openDebugController(); + } else { + closeDebugController(); + } +} + +void GameCameraController::SetZoom(float zoom) { + zoom_ = std::max(zoom, 0.01f); + if (map_) { + focus_ = map_->ClampCameraFocus(focus_, zoom_); + } + applyFocus(); +} + +void GameCameraController::SetFocus(const Vec2& focus) { + focus_ = map_ ? map_->ClampCameraFocus(focus, zoom_) : focus; + initialized_ = true; + applyFocus(); +} + +void GameCameraController::SnapToDefaultFocus() { + if (!map_) { + return; + } + + focus_ = map_->ClampCameraFocus(map_->GetDefaultCameraFocus(), zoom_); + initialized_ = true; + applyFocus(); +} + +void GameCameraController::Update(float deltaTime) { + if (!map_) { + return; + } + + if (!initialized_) { + SnapToDefaultFocus(); + } + + if (target_) { + Vec2 targetFocus = target_->GetPosition(); + float t = std::clamp(deltaTime * followLerpSpeed_, 0.0f, 1.0f); + focus_ = focus_ + (targetFocus - focus_) * t; + } else if (debugEnabled_) { + updateDebugInput(deltaTime); + } + + focus_ = map_->ClampCameraFocus(focus_, zoom_); + applyFocus(); +} + +void GameCameraController::openDebugController() { + if (debugController_ && SDL_GameControllerGetAttached(debugController_)) { + return; + } + + closeDebugController(); + + int joystickCount = SDL_NumJoysticks(); + for (int i = 0; i < joystickCount; ++i) { + if (!SDL_IsGameController(i)) { + continue; + } + + debugController_ = SDL_GameControllerOpen(i); + if (debugController_) { + return; + } + } +} + +void GameCameraController::closeDebugController() { + if (!debugController_) { + return; + } + + SDL_GameControllerClose(debugController_); + debugController_ = nullptr; +} + +void GameCameraController::updateDebugInput(float deltaTime) { + const uint8* keyboardState = SDL_GetKeyboardState(nullptr); + float moveX = 0.0f; + float moveY = 0.0f; + + if (keyboardState) { + if (keyboardState[SDL_SCANCODE_A]) { + moveX -= 1.0f; + } + if (keyboardState[SDL_SCANCODE_D]) { + moveX += 1.0f; + } + if (keyboardState[SDL_SCANCODE_W]) { + moveY -= 1.0f; + } + if (keyboardState[SDL_SCANCODE_S]) { + moveY += 1.0f; + } + } + + if (debugEnabled_ && !debugController_) { + openDebugController(); + } + + if (debugController_ && !SDL_GameControllerGetAttached(debugController_)) { + closeDebugController(); + openDebugController(); + } + + if (debugController_ && SDL_GameControllerGetAttached(debugController_)) { + moveX += normalizeControllerAxis( + SDL_GameControllerGetAxis(debugController_, SDL_CONTROLLER_AXIS_LEFTX)); + moveY += normalizeControllerAxis( + SDL_GameControllerGetAxis(debugController_, SDL_CONTROLLER_AXIS_LEFTY)); + } + + moveX = std::clamp(moveX, -1.0f, 1.0f); + moveY = std::clamp(moveY, -1.0f, 1.0f); + + if (moveX == 0.0f && moveY == 0.0f) { + return; + } + + focus_.x += moveX * debugMoveSpeed_ * deltaTime; + focus_.y += moveY * debugMoveSpeed_ * deltaTime; +} + +void GameCameraController::applyFocus() const { + Camera* camera = Renderer::get().getCamera(); + if (!camera || !map_) { + return; + } + + float halfWidth = kScreenWidth * 0.5f / zoom_; + float halfHeight = kScreenHeight * 0.5f / zoom_; + Vec2 cameraPos(focus_.x - halfWidth, focus_.y - halfHeight); + camera->setZoom(zoom_); + camera->setPosition(cameraPos); + map_->ApplyCameraFocus(focus_); +} + +} // namespace frostbite2D diff --git a/Game/src/main.cpp b/Game/src/main.cpp index 9e894b7..21fb7ad 100644 --- a/Game/src/main.cpp +++ b/Game/src/main.cpp @@ -19,6 +19,7 @@ #include #include #include +#include "Actor/GameMapTestScene.h" #include "Actor/GameWorld.h" using namespace frostbite2D; @@ -106,8 +107,8 @@ int main(int argc, char **argv) { [](std::string message) mutable { SDL_Log("后台资源加载成功"); - auto gameWorld = MakePtr(); - SceneManager::get().ReplaceScene(gameWorld); + auto testMapScene = MakePtr(); + SceneManager::get().ReplaceScene(testMapScene); }, [](std::exception_ptr error) { try { diff --git a/modules/build_helpers/assets.lua b/modules/build_helpers/assets.lua new file mode 100644 index 0000000..d7fb77f --- /dev/null +++ b/modules/build_helpers/assets.lua @@ -0,0 +1,49 @@ +function syncAssets(source_dir, target_dir) + if not os.isdir(source_dir) then + return + end + + local copied = 0 + local skipped = 0 + local removed = 0 + local source_files = {} + + for _, source_file in ipairs(os.files(path.join(source_dir, "**"))) do + local relative_path = path.relative(source_file, source_dir) + local target_file = path.join(target_dir, relative_path) + local target_subdir = path.directory(target_file) + local should_copy = not os.isfile(target_file) + + source_files[relative_path] = true + + if not should_copy then + local source_mtime = os.mtime(source_file) + local target_mtime = os.mtime(target_file) + local source_size = os.filesize(source_file) + local target_size = os.filesize(target_file) + should_copy = source_mtime > target_mtime or source_size ~= target_size + end + + if should_copy then + if target_subdir ~= "." and not os.isdir(target_subdir) then + os.mkdir(target_subdir) + end + os.cp(source_file, target_file) + copied = copied + 1 + else + skipped = skipped + 1 + end + end + + if os.isdir(target_dir) then + for _, target_file in ipairs(os.files(path.join(target_dir, "**"))) do + local relative_path = path.relative(target_file, target_dir) + if not source_files[relative_path] then + os.rm(target_file) + removed = removed + 1 + end + end + end + + print(string.format("Sync assets: %d copied, %d skipped, %d removed", copied, skipped, removed)) +end diff --git a/platform/linux.lua b/platform/linux.lua index 7734bc7..7a25dd1 100644 --- a/platform/linux.lua +++ b/platform/linux.lua @@ -24,15 +24,13 @@ target("Frostbite2D") -- 复制着色器文件到输出目录 after_build(function (target) + local assetHelper = import("build_helpers.assets") + -- 复制 assets 目录 local assets_dir = path.join(os.projectdir(), "Game/assets") local output_dir = target:targetdir() local target_assets_dir = path.join(output_dir, "assets") - if os.isdir(assets_dir) then - os.rm(target_assets_dir) - os.cp(assets_dir, output_dir) - print("Copy assets directory: " .. assets_dir .. " -> " .. target_assets_dir) - end + assetHelper.syncAssets(assets_dir, target_assets_dir) end) target_end() diff --git a/platform/mingw.lua b/platform/mingw.lua index ecb686e..fce0613 100644 --- a/platform/mingw.lua +++ b/platform/mingw.lua @@ -28,16 +28,14 @@ target("Frostbite2D") -- 复制 assets 目录到输出目录 after_build(function (target) + local assetHelper = import("build_helpers.assets") + -- 复制 assets 目录 local assets_dir = path.join(os.projectdir(), "Game/assets") local output_dir = target:targetdir() local target_assets_dir = path.join(output_dir, "assets") - if os.isdir(assets_dir) then - os.rm(target_assets_dir) - os.cp(assets_dir, output_dir) - print("Copy assets directory: " .. assets_dir .. " -> " .. target_assets_dir) - end + assetHelper.syncAssets(assets_dir, target_assets_dir) -- 复制所有依赖的 DLL (Windows 平台) if is_plat("mingw") or is_plat("windows") then diff --git a/platform/switch.lua b/platform/switch.lua index 91fb688..9d9f537 100644 --- a/platform/switch.lua +++ b/platform/switch.lua @@ -47,6 +47,8 @@ target("Frostbite2D") -- 构建后生成 NRO 文件 after_build(function (target) + local assetHelper = import("build_helpers.assets") + local elf_file = target:targetfile() local output_dir = path.directory(elf_file) local nacp_file = path.join(output_dir, "hello_world.nacp") @@ -72,11 +74,7 @@ target("Frostbite2D") local output_dir = target:targetdir() local target_assets_dir = path.join(output_dir, "assets") - if os.isdir(assets_dir) then - os.rm(target_assets_dir) - os.cp(assets_dir, output_dir) - print("Copy assets directory: " .. assets_dir .. " -> " .. target_assets_dir) - end + assetHelper.syncAssets(assets_dir, target_assets_dir) end) target_end() diff --git a/xmake.lua b/xmake.lua index 7f6e96b..8eb2593 100644 --- a/xmake.lua +++ b/xmake.lua @@ -7,6 +7,7 @@ set_languages("c++17") set_encodings("utf-8") add_rules("mode.debug", "mode.release") +add_moduledirs("modules") local host_plat = "mingw" local target_plat = get_config("plat") or host_plat