#include "Runtime/World/CScriptMazeNode.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/Character/CModelData.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/World/CActorParameters.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { std::array sMazeSeeds; std::array sDebugCellPos; CScriptMazeNode::CScriptMazeNode(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, bool active, s32 col, s32 row, s32 side, const zeus::CVector3f& actorPos, const zeus::CVector3f& triggerPos, const zeus::CVector3f& effectPos) : CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(), CActorParameters::None(), kInvalidUniqueId) , xe8_col(col) , xec_row(row) , xf0_side(static_cast(side)) , x100_actorPos(actorPos) , x110_triggerPos(triggerPos) , x120_effectPos(effectPos) {} void CScriptMazeNode::Accept(IVisitor& visitor) { visitor.Visit(this); } void CScriptMazeNode::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) { if (GetActive()) { if (msg == EScriptObjectMessage::Action) { if (auto* maze = mgr.GetCurrentMaze()) { bool shouldGenObjs = false; auto& cell = maze->GetCell(xe8_col, xec_row); if (xf0_side == ESide::Top && cell.x0_24_openTop) { if (cell.x0_28_gateTop) { shouldGenObjs = true; x13c_25_hasGate = true; } } else if (xf0_side == ESide::Right && cell.x0_25_openRight) { if (cell.x0_29_gateRight) { shouldGenObjs = true; x13c_25_hasGate = true; } } else { shouldGenObjs = true; } if (shouldGenObjs) { GenerateObjects(mgr); } if (xf0_side == ESide::Right && cell.x1_24_puddle) { x13c_24_hasPuddle = true; } if (x13c_25_hasGate) { const auto origin = GetTranslation(); for (const auto& conn : GetConnectionList()) { if (conn.x0_state != EScriptObjectState::Modify || conn.x4_msg != EScriptObjectMessage::Activate) { continue; } bool wasGeneratingObject = mgr.GetIsGeneratingObject(); mgr.SetIsGeneratingObject(true); const auto genObj = mgr.GenerateObject(conn.x8_objId); mgr.SetIsGeneratingObject(wasGeneratingObject); xf4_gateEffectId = genObj.second; if (TCastToPtr actor = mgr.ObjectById(genObj.second)) { actor->SetTranslation(origin + x120_effectPos); mgr.SendScriptMsg(actor, GetUniqueId(), EScriptObjectMessage::Activate); } break; } } if (x13c_24_hasPuddle) { size_t count = 0; for (const auto& conn : GetConnectionList()) { if ((conn.x0_state == EScriptObjectState::Closed || conn.x0_state == EScriptObjectState::DeactivateState) && conn.x4_msg == EScriptObjectMessage::Activate) { count++; } } x12c_puddleObjectIds.reserve(count); for (const auto& conn : GetConnectionList()) { if ((conn.x0_state == EScriptObjectState::Closed || conn.x0_state == EScriptObjectState::DeactivateState) && conn.x4_msg == EScriptObjectMessage::Activate) { bool wasGeneratingObject = mgr.GetIsGeneratingObject(); mgr.SetIsGeneratingObject(true); const auto genObj = mgr.GenerateObject(conn.x8_objId); mgr.SetIsGeneratingObject(wasGeneratingObject); x12c_puddleObjectIds.push_back(genObj.second); if (TCastToPtr actor = mgr.ObjectById(genObj.second)) { actor->SetTransform(GetTransform()); if (conn.x0_state == EScriptObjectState::Closed) { mgr.SendScriptMsg(actor, GetUniqueId(), EScriptObjectMessage::Activate); } } } } } } } else if (msg == EScriptObjectMessage::SetToZero) { auto* maze = mgr.GetCurrentMaze(); if (x13c_24_hasPuddle && maze != nullptr && std::any_of(x12c_puddleObjectIds.cbegin(), x12c_puddleObjectIds.cend(), [=](auto v) { return v == uid; })) { for (const auto& id : x12c_puddleObjectIds) { if (auto* ent = mgr.ObjectById(id)) { if (!ent->GetActive()) { mgr.SendScriptMsg(ent, GetUniqueId(), EScriptObjectMessage::Activate); } else { mgr.FreeScriptObject(ent->GetUniqueId()); } } } for (const auto ent : mgr.GetAllObjectList()) { if (TCastToPtr node = ent) { if (node->xe8_col == xe8_col - 1 && node->xec_row == xec_row && node->xf0_side == ESide::Right) { auto& cell = maze->GetCell(xe8_col - 1, xec_row); if (!cell.x0_25_openRight) { cell.x0_25_openRight = true; node->Reset(mgr); node->x13c_25_hasGate = false; } } if (node->xe8_col == xe8_col && node->xec_row == xec_row && node->xf0_side == ESide::Right) { auto& cell = maze->GetCell(xe8_col, xec_row); if (!cell.x0_25_openRight) { cell.x0_25_openRight = true; node->Reset(mgr); node->x13c_25_hasGate = false; } } if (node->xe8_col == xe8_col && node->xec_row == xec_row && node->xf0_side == ESide::Top) { auto& cell = maze->GetCell(xe8_col, xec_row); if (!cell.x0_24_openTop) { cell.x0_24_openTop = true; node->Reset(mgr); node->x13c_25_hasGate = false; } } if (node->xe8_col == xe8_col && node->xec_row == xec_row + 1 && node->xf0_side == ESide::Top) { auto& cell = maze->GetCell(xe8_col, xec_row + 1); if (!cell.x0_24_openTop) { cell.x0_24_openTop = true; node->Reset(mgr); node->x13c_25_hasGate = false; } } } } } } else if (msg == EScriptObjectMessage::Deactivate) { Reset(mgr); } else if (msg == EScriptObjectMessage::InitializedInArea) { if (mgr.GetCurrentMaze() == nullptr) { auto maze = std::make_unique(skEnterCol, skEnterRow, skTargetCol, skTargetRow); maze->Reset(sMazeSeeds[mgr.GetActiveRandom()->Next() % sMazeSeeds.size()]); maze->Initialize(); maze->GenerateObstacles(); mgr.SetCurrentMaze(std::move(maze)); } if (xf0_side == ESide::Right) { sDebugCellPos[xe8_col + xec_row * skMazeCols] = GetTranslation(); } else if (xe8_col == skMazeCols - 1) { // Last column does not have right nodes, but we can infer the position sDebugCellPos[xe8_col + xec_row * skMazeCols] = GetTranslation() - zeus::CVector3f{1.1875f, -0.1215f, 1.2187f}; } } } // URDE change: used to be in the above if branch if (msg == EScriptObjectMessage::Deleted) { mgr.ClearCurrentMaze(); Reset(mgr); } CActor::AcceptScriptMsg(msg, uid, mgr); } void CScriptMazeNode::Think(float dt, CStateManager& mgr) { if (!GetActive() || !x13c_25_hasGate) { return; } xf8_msgTimer -= dt; if (xf8_msgTimer <= 0.f) { xf8_msgTimer = 1.f; if (x13c_26_gateActive) { x13c_26_gateActive = false; SendScriptMsgs(mgr, EScriptObjectMessage::Deactivate); } else { x13c_26_gateActive = true; SendScriptMsgs(mgr, EScriptObjectMessage::Activate); } } } void CScriptMazeNode::LoadMazeSeeds() { const SObjectTag* tag = g_ResFactory->GetResourceIdByName("DUMB_MazeSeeds"); const u32 resSize = g_ResFactory->ResourceSize(*tag); const std::unique_ptr buf = g_ResFactory->LoadResourceSync(*tag); CMemoryInStream in(buf.get(), resSize); for (auto& seed : sMazeSeeds) { seed = in.ReadInt32(); } } void CScriptMazeNode::GenerateObjects(CStateManager& mgr) { for (const auto& conn : GetConnectionList()) { if (conn.x0_state != EScriptObjectState::MaxReached || conn.x4_msg != EScriptObjectMessage::Activate) { continue; } const auto* ent = mgr.GetObjectById(mgr.GetIdForScript(conn.x8_objId)); TCastToConstPtr scriptEffect{ent}; TCastToConstPtr scriptActor{ent}; TCastToConstPtr scriptTrigger{ent}; if ((scriptEffect || scriptActor || scriptTrigger) && (!scriptEffect || !x13c_25_hasGate)) { bool wasGeneratingObject = mgr.GetIsGeneratingObject(); mgr.SetIsGeneratingObject(true); const auto genObj = mgr.GenerateObject(conn.x8_objId); mgr.SetIsGeneratingObject(wasGeneratingObject); if (auto* actor = static_cast(mgr.ObjectById(genObj.second))) { mgr.SendScriptMsg(actor, GetUniqueId(), EScriptObjectMessage::Activate); if (scriptEffect) { actor->SetTranslation(GetTranslation() + x120_effectPos); x11c_effectId = genObj.second; } if (scriptActor) { actor->SetTranslation(GetTranslation() + x100_actorPos); xfc_actorId = genObj.second; } if (scriptTrigger) { actor->SetTranslation(GetTranslation() + x110_triggerPos); x10c_triggerId = genObj.second; } } } } } void CScriptMazeNode::Reset(CStateManager& mgr) { mgr.FreeScriptObject(x11c_effectId); mgr.FreeScriptObject(xfc_actorId); mgr.FreeScriptObject(x10c_triggerId); mgr.FreeScriptObject(xf4_gateEffectId); xf4_gateEffectId = kInvalidUniqueId; xfc_actorId = kInvalidUniqueId; x10c_triggerId = kInvalidUniqueId; x11c_effectId = kInvalidUniqueId; } void CScriptMazeNode::SendScriptMsgs(CStateManager& mgr, EScriptObjectMessage msg) { mgr.SendScriptMsg(x11c_effectId, GetUniqueId(), msg); mgr.SendScriptMsg(xfc_actorId, GetUniqueId(), msg); mgr.SendScriptMsg(x10c_triggerId, GetUniqueId(), msg); mgr.SendScriptMsg(xf4_gateEffectId, GetUniqueId(), msg); } void CMazeState::Reset(s32 seed) { x0_rand.SetSeed(seed); x94_24_initialized = false; x4_cells.fill({}); std::array sides{}; s32 cellIdx = 0; for (u32 i = skMazeCols * skMazeRows - 1; i != 0;) { u32 acc = 0; if (cellIdx - skMazeCols > 0 && !GetCell(cellIdx - skMazeCols).IsOpen()) { sides[acc++] = ESide::Top; } if (cellIdx < x4_cells.size() - 2 && (cellIdx + 1) % skMazeCols != 0 && !GetCell(cellIdx + 1).IsOpen()) { sides[acc++] = ESide::Right; } if (cellIdx + skMazeCols < x4_cells.size() && !GetCell(cellIdx + skMazeCols).IsOpen()) { sides[acc++] = ESide::Bottom; } if (cellIdx > 0 && cellIdx % skMazeCols != 0 && !GetCell(cellIdx - 1).IsOpen()) { sides[acc++] = ESide::Left; } if (acc == 0) { do { cellIdx++; if (cellIdx > x4_cells.size() - 1) { cellIdx = 0; } } while (!GetCell(cellIdx).IsOpen()); continue; } i--; ESide side = sides[x0_rand.Next() % acc]; if (side == ESide::Bottom) { GetCell(cellIdx).x0_26_openBottom = true; GetCell(cellIdx + skMazeCols).x0_24_openTop = true; cellIdx += skMazeCols; } else if (side == ESide::Top) { GetCell(cellIdx).x0_24_openTop = true; GetCell(cellIdx - skMazeCols).x0_26_openBottom = true; cellIdx -= skMazeCols; } else if (side == ESide::Right) { GetCell(cellIdx).x0_25_openRight = true; GetCell(cellIdx + 1).x0_27_openLeft = true; cellIdx++; } else if (side == ESide::Left) { GetCell(cellIdx).x0_27_openLeft = true; GetCell(cellIdx - 1).x0_25_openRight = true; cellIdx--; } } } void CMazeState::Initialize() { std::array path{}; path[0] = x84_enterCol + x88_enterRow * skMazeCols; GetCell(path[0]).x1_26_checked = true; s32 pathLength = 1; while (path[0] != x8c_targetCol + x90_targetRow * skMazeCols) { if (GetCell(path[0]).x0_24_openTop) { if (!GetCell(path[0] - skMazeCols).x1_26_checked) { path[pathLength] = path[0] - skMazeCols; pathLength++; } } if (GetCell(path[0]).x0_25_openRight) { if (!GetCell(path[0] + 1).x1_26_checked) { path[pathLength] = path[0] + 1; pathLength++; } } if (GetCell(path[0]).x0_26_openBottom) { if (!GetCell(path[0] + skMazeCols).x1_26_checked) { path[pathLength] = path[0] + skMazeCols; pathLength++; } } if (GetCell(path[0]).x0_27_openLeft) { if (!GetCell(path[0] - 1).x1_26_checked) { path[pathLength] = path[0] - 1; pathLength++; } } if (path[0] == path[pathLength - 1]) { pathLength--; } path[0] = path[pathLength - 1]; GetCell(path[0]).x1_26_checked = true; } s32* idx = &path[pathLength]; while (pathLength != 0) { pathLength--; idx--; auto& cell = GetCell(*idx); if (cell.x1_26_checked) { cell.x1_25_onPath = true; if (pathLength > 0) { m_path.push_back(*idx); } } } x94_24_initialized = true; } void CMazeState::GenerateObstacles() { if (!x94_24_initialized) { Initialize(); } auto GetRandom = [this](s32 offset) { s32 tmp = x0_rand.Next(); return tmp + ((tmp / 5) * -5) + offset; }; s32 gate1Idx = GetRandom(9); s32 gate2Idx = GetRandom(21); s32 gate3Idx = GetRandom(33); s32 puddle1Idx = GetRandom(13); s32 puddle2Idx = GetRandom(29); ESide side = ESide::Invalid; s32 idx = 0; s32 prevCol = x84_enterCol; s32 prevRow = x88_enterRow; s32 col = x84_enterCol; s32 row = x88_enterRow; while (col != x8c_targetCol || row != x90_targetRow) { if (idx == gate1Idx || idx == gate2Idx || idx == gate3Idx) { if (side == ESide::Bottom) { GetCell(col, row).x0_28_gateTop = true; GetCell(prevCol, prevRow).x0_30_gateBottom = true; } else if (side == ESide::Top) { GetCell(col, row).x0_30_gateBottom = true; GetCell(prevCol, prevRow).x0_28_gateTop = true; } else if (side == ESide::Right) { GetCell(col, row).x0_31_gateLeft = true; GetCell(prevCol, prevRow).x0_29_gateRight = true; } else if (side == ESide::Left) { GetCell(col, row).x0_29_gateRight = true; GetCell(prevCol, prevRow).x0_31_gateLeft = true; } } s32 nextCol = col; s32 nextRow = -1; if (row < 1 || side == ESide::Bottom || !GetCell(col, row).x0_24_openTop || !GetCell(col, row - 1).x1_25_onPath) { if (row < skMazeRows - 1 && side != ESide::Top && GetCell(col, row).x0_26_openBottom && GetCell(col, row + 1).x1_25_onPath) { side = ESide::Bottom; nextRow = row + 1; } else { nextRow = row; if (col < 1 || side == ESide::Right || !GetCell(col, row).x0_27_openLeft || !GetCell(col - 1, row).x1_25_onPath) { if (col > skMazeRows || side == ESide::Left || !GetCell(col, row).x0_25_openRight || !GetCell(col + 1, row).x1_25_onPath) { return; } side = ESide::Right; nextCol = col + 1; } else { side = ESide::Left; nextCol = col - 1; } } } else { side = ESide::Top; nextRow = row - 1; } if (idx == puddle1Idx || idx == puddle2Idx) { if (col == 0 || row == 0 || col == skMazeCols - 1 || row == skMazeRows - 1) { if (idx == puddle1Idx) { puddle1Idx++; } else { puddle2Idx++; } } else { auto& cell = GetCell(col, row); cell.x1_24_puddle = true; if (side == ESide::Bottom) { GetCell(nextCol, nextRow).x0_24_openTop = false; cell.x0_26_openBottom = false; } else if (side == ESide::Top) { GetCell(nextCol, nextRow).x0_26_openBottom = false; cell.x0_24_openTop = false; } else if (side == ESide::Right) { GetCell(nextCol, nextRow).x0_27_openLeft = false; cell.x0_25_openRight = false; } else if (side == ESide::Left) { GetCell(nextCol, nextRow).x0_25_openRight = false; cell.x0_27_openLeft = false; } } } idx++; prevCol = col; prevRow = row; col = nextCol; row = nextRow; }; } void CMazeState::DebugRender() { m_renderer.Reset(); m_renderer.AddVertex(sDebugCellPos[skEnterCol + skEnterRow * skMazeCols], zeus::skBlue, 2.f); for (s32 i = m_path.size() - 1; i >= 0; --i) { s32 idx = m_path[i]; zeus::CVector3f pos; if (idx == skMazeCols - 1) { // 8,0 has no node, infer from 8,1 pos = sDebugCellPos[idx + skMazeCols] + zeus::CVector3f{4.f, 0.f, 0.f}; } else { pos = sDebugCellPos[idx]; } m_renderer.AddVertex(pos, zeus::skBlue, 2.f); } m_renderer.Render(); } } // namespace metaforce