#include "world/GameWorld.h" #include "character/CharacterObject.h" #include "map/GameDataLoader.h" #include "scene/GameDebugUIScene.h" #include #include namespace frostbite2D { namespace { constexpr int kDefaultTownId = 1; Vec2 ComputeMoveAreaCenter(const game::MoveAreaBounds& bounds) { return Vec2((bounds.leftTop.x + bounds.rightBottom.x) * 0.5f, (bounds.leftTop.y + bounds.rightBottom.y) * 0.5f); } void SetActorGroundPosition(RefPtr actor, const Vec2& position) { if (!actor) { return; } if (auto* character = dynamic_cast(actor.Get())) { character->SetCharacterPosition(position); return; } actor->SetPosition(position); } } // namespace GameWorld::GameWorld() = default; void GameWorld::onEnter() { Scene::onEnter(); EnsureDebugScene(); if (!initialized_) { initialized_ = InitWorld(); } RefreshDebugContext(); } void GameWorld::onExit() { pendingCharacterMove_.reset(); ClearSuppressedMoveArea(); if (debugScene_) { debugScene_->ClearDebugContext(); SceneManager::get().RemoveUIScene(debugScene_.Get()); } Scene::onExit(); } void GameWorld::Update(float deltaTime) { Scene::Update(deltaTime); ProcessPendingCharacterMove(); } bool GameWorld::InitWorld() { RemoveAllChildren(); townPathMap_.clear(); townMap_.clear(); mainActor_.Reset(); pendingCharacterMove_.reset(); ClearSuppressedMoveArea(); curTown_ = -1; townPathMap_ = game::loadTownList(); if (townPathMap_.empty()) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: no town entries found"); return false; } for (const auto& [townId, townPath] : townPathMap_) { auto town = MakePtr(); if (!town->Init(townId, townPath)) { continue; } townMap_[townId] = town; } if (townMap_.empty()) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: failed to create any town"); return false; } int defaultTownId = kDefaultTownId; if (townMap_.find(defaultTownId) == townMap_.end()) { defaultTownId = townMap_.begin()->first; SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: default town %d missing, fallback to town %d", kDefaultTownId, defaultTownId); } if (!InitMainCharacter(defaultTownId)) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: falling back to empty town %d without main character", defaultTownId); AddCharacter(nullptr, defaultTownId); } return true; } bool GameWorld::InitMainCharacter(int townId) { auto mainCharacter = MakePtr(); if (!mainCharacter->Construction(0)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: failed to construct default main character"); return false; } mainCharacter->EnableEventReceive(); mainCharacter->SetEventPriority(-100); mainCharacter->SetInputEnabled(true); AddCharacter(mainCharacter, townId); return true; } void GameWorld::AddCharacter(RefPtr actor, int townId) { auto it = townMap_.find(townId); if (it == townMap_.end()) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: town %d not found", townId); return; } mainActor_ = actor; curTown_ = townId; it->second->AddCharacter(actor); AddChild(it->second); RefreshDebugContext(); } void GameWorld::MoveCharacter(RefPtr actor, int townId, int area) { auto townIt = townMap_.find(townId); if (townIt == townMap_.end()) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: target town %d missing", townId); return; } ClearSuppressedMoveArea(); int sourceTownId = curTown_; int sourceAreaId = -1; if (curTown_ != -1) { auto currentTown = townMap_.find(curTown_); if (currentTown != townMap_.end()) { sourceAreaId = currentTown->second->GetCurAreaIndex(); } } RefPtr targetMap = townIt->second->GetArea(area); if (actor && targetMap && sourceTownId != -1 && sourceAreaId != -1) { bool foundTransitionEntry = false; const auto& moveAreaInfo = targetMap->GetMoveAreaInfo(); for (size_t i = 0; i < moveAreaInfo.size(); ++i) { if (moveAreaInfo[i].town != sourceTownId || moveAreaInfo[i].area != sourceAreaId) { continue; } game::MoveAreaBounds bounds = targetMap->GetMovablePositionBounds(i); SetActorGroundPosition(actor, ComputeMoveAreaCenter(bounds)); SetSuppressedMoveArea(targetMap.Get(), i); foundTransitionEntry = true; break; } if (!foundTransitionEntry) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: no matching move area in town %d area %d for source town %d area %d", townId, area, sourceTownId, sourceAreaId); } } else if (actor && !targetMap) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: target area %d missing in town %d, use town fallback spawn", area, townId); } if (curTown_ != -1) { auto currentTown = townMap_.find(curTown_); if (currentTown != townMap_.end()) { RemoveChild(currentTown->second); } } curTown_ = townId; mainActor_ = actor; townIt->second->AddCharacter(actor, area); AddChild(townIt->second); RefreshDebugContext(); } void GameWorld::RequestMoveCharacter(RefPtr actor, int townId, int area) { if (!actor) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: ignore move request without actor"); return; } pendingCharacterMove_ = PendingCharacterMove{actor, townId, area}; } void GameWorld::UpdateMoveAreaSuppression(GameMap* map, size_t moveAreaIndex) { if (!suppressedMoveArea_) { return; } if (suppressedMoveArea_->map != map || suppressedMoveArea_->moveAreaIndex != moveAreaIndex) { ClearSuppressedMoveArea(); } } bool GameWorld::ShouldSuppressMoveArea(GameMap* map, size_t moveAreaIndex) const { return suppressedMoveArea_ && suppressedMoveArea_->map == map && suppressedMoveArea_->moveAreaIndex == moveAreaIndex; } void GameWorld::ProcessPendingCharacterMove() { if (!pendingCharacterMove_) { return; } PendingCharacterMove move = *pendingCharacterMove_; pendingCharacterMove_.reset(); MoveCharacter(move.actor, move.townId, move.area); } void GameWorld::EnsureDebugScene() { if (!debugScene_) { debugScene_ = MakePtr(); } SceneManager::get().RemoveUIScene(debugScene_.Get()); SceneManager::get().PushUIScene(debugScene_); } void GameWorld::RefreshDebugContext() { if (!debugScene_) { return; } debugScene_->SetDebugContext(GetCurrentMap(), GetMainCharacter()); } void GameWorld::SetSuppressedMoveArea(GameMap* map, size_t moveAreaIndex) { if (!map || moveAreaIndex == GameMap::kInvalidMoveAreaIndex) { ClearSuppressedMoveArea(); return; } suppressedMoveArea_ = SuppressedMoveArea{map, moveAreaIndex}; } void GameWorld::ClearSuppressedMoveArea() { suppressedMoveArea_.reset(); } Actor* GameWorld::GetMainActor() const { return mainActor_.Get(); } CharacterObject* GameWorld::GetMainCharacter() const { return dynamic_cast(mainActor_.Get()); } GameTown* GameWorld::GetCurrentTown() const { auto it = townMap_.find(curTown_); if (it == townMap_.end()) { return nullptr; } return it->second.Get(); } GameMap* GameWorld::GetCurrentMap() const { GameTown* town = GetCurrentTown(); if (!town) { return nullptr; } RefPtr area = town->GetCurrentArea(); return area.Get(); } GameWorld* GameWorld::GetWorld() { return dynamic_cast(SceneManager::get().GetCurrentScene()); } } // namespace frostbite2D