#include "../gen/Def.h" #include "Gen.h" #include "Global.h" #include "Input.h" #include "Instance.h" #include "Level.h" #include "Log.h" #include "Puzzle.h" #include "bx/bx.h" #include "rendering/Rendering.h" #include "SDL3/SDL_mouse.h" #include "bgfx/bgfx.h" #include "bx/error.h" #include "bx/file.h" #include "bx/filepath.h" #include "bx/string.h" #include "imgui.h" #include #include #include #include using namespace Gen; namespace Game { void EntityRenderData::Render(const Model* models, const Material* materials, const Texture* textures) { if (models == nullptr || materials == nullptr || textures == nullptr) return; if (!Gen::IsValid(ModelH) || MaterialHandle >= EMaterial::EntryCount) return; if (!Visible) return; auto& rendering = GameRendering::Get(); UpdateMatrix(Transform); bgfx::setTransform(Transform.M.M); const Model& currentModel = models[ModelH.ModelIdx]; const Material& currentMaterial = materials[(uint16_t)MaterialHandle]; if (!isValid(currentModel.IndexBuffer) || !isValid(currentModel.VertexBuffer)) return; if (!isValid(currentMaterial.Shader)) return; bgfx::setVertexBuffer(0, currentModel.VertexBuffer); bgfx::setIndexBuffer(currentModel.IndexBuffer); bgfx::setState(currentMaterial.State); float timeValues[4]{0.0f}; timeValues[0] = GetInstance().Time.Now; float texInfo[4]{0.0f}; texInfo[0] = textures[0].Info.width; texInfo[1] = textures[0].Info.height; texInfo[2] = rendering.DitherTextures.DitherTexWH; texInfo[3] = rendering.DitherTextures.DitherTexDepth; bgfx::UniformHandle sampler = rendering.DefaultSampler; bgfx::TextureHandle tex = rendering.Textures[0].RenderHandle; if (IsValid(TextureHandle)) { sampler = textures[TextureHandle.TextureIdx].SamplerHandle; tex = textures[TextureHandle.TextureIdx].RenderHandle; texInfo[0] = textures[TextureHandle.TextureIdx].Info.width; texInfo[1] = textures[TextureHandle.TextureIdx].Info.height; } bgfx::setTexture(0, sampler, tex); bgfx::setTexture(1, rendering.DitherTextures.DitherSampler, rendering.DitherTextures.FinalTex); bgfx::setTexture(2, rendering.DitherTextures.RampSampler, rendering.DitherTextures.RampTex); bgfx::setUniform(currentMaterial.Uniforms[Material::UTime], timeValues); bgfx::setUniform(currentMaterial.Uniforms[Material::UDotColor], &DotColor.x); bgfx::setUniform(currentMaterial.Uniforms[Material::UTexInfo], texInfo); bgfx::setUniform(currentMaterial.Uniforms[Material::UBaseColor], &BaseColor.x); bgfx::submit(currentMaterial.ViewID, currentMaterial.Shader); } void EntityRenderData::LoadFromSaved(const Gen::SavedEntityRenderData& saved) { DotColor = saved.HighlightColor; BaseColor = saved.BaseColor; Transform = saved.TF; // TODO: fix handle indices MaterialHandle = saved.Material; TextureHandle = saved.Texture; ModelH = saved.Model; Visible = saved.Visible; } namespace { void UpdatePlayerInputMode() { bool IsGaming = GetInstance().Player.InputM == InputMode::Game; bool captureMouse = IsGaming && GetInstance().Player.InteractionM == InteractionMode::Walk; SDL_SetWindowRelativeMouseMode(GetShared().Window.SDLWindow, captureMouse); auto& rendering = GameRendering::Get(); if (rendering.SetupData.UseImgui) { auto& IO = ImGui::GetIO(); IO.ConfigFlags = FlagBool(IO.ConfigFlags, ImGuiConfigFlags_NoMouse | ImGuiConfigFlags_NoKeyboard, IsGaming); } rendering.UIVisible = IsGaming ? UIVisibilityState::Game : UIVisibilityState::Debug; } } // namespace void Level::Setup(GameData& data) { LOG("Level setup"); uint8_t* storagePtr = data.EntityArena.Base; bool needReset = false; needReset |= Cubes.Setup(storagePtr, needReset); needReset |= Tests.Setup(storagePtr, needReset); needReset |= PuzzleTiles.Setup(storagePtr, needReset); needReset |= PuzzleTileCovers.Setup(storagePtr, needReset); needReset |= UIQuads.Setup(storagePtr, needReset); needReset |= LevelEntities.Setup(storagePtr, needReset); Puzzle::Setup(); UIQuads.Count = 0; PuzzleTiles.Count = 0; bx::Error err; bx::DirectoryReader dirIter; bx::FileInfo info; bx::FilePath puzzleDirPath{Puzzle::PuzzleFileDir}; if (dirIter.open(puzzleDirPath, &err)) { while (true) { int32_t readCount = dirIter.read(&info, sizeof(info), &err); if (readCount == 0) break; if (err.isOk()) { if (info.type != bx::FileType::File) continue; bx::StringView pathEnd = info.filePath.getExt(); if (bx::strCmpI(pathEnd, ".pzl") != 0) continue; bx::FilePath fullPath = puzzleDirPath; fullPath.join(info.filePath); LOG("Loading %s", fullPath.getCPtr()); Gen::Deserializer ser; Gen::PuzzleData dataBuf; if (ser.Init(fullPath, "PZZL") && ser.ReadT(dataBuf)) { if (dataBuf.ID >= BX_COUNTOF(Puzzles)) { LOG_ERROR("Puzzle ID out of bounds: %u", dataBuf.ID); ser.Finish(); continue; } Puzzles[dataBuf.ID].Data = dataBuf; Puzzles[dataBuf.ID].Setup(); } else { LOG_WARN("Failed to load puzzle!"); } ser.Finish(); } else { LOG_ERROR("Failed parsing file name at %s:\n%s", puzzleDirPath.getCPtr(), err.getMessage()); break; } } } else { LOG_ERROR("Failed to open puzzle dir at %s:\n%s", puzzleDirPath.getCPtr(), err.getMessage().getCPtr()); } if (!IsValid(PlayerOutsideViewCube)) { PlayerOutsideViewCube = Cubes.New(); Cubes.Get(PlayerOutsideViewCube).Setup(); } UIQuads.Count = 0; PuzzleTiles.Count = 0; PuzzleTileCovers.Count = 0; for (int32_t i = 0; i < BX_COUNTOF(Puzzles); ++i) { if (Puzzles[i].Data.ID != UINT16_MAX) { Puzzles[i].Setup(); } } TabletHandle = UIQuads.New(); UpdatePlayerInputMode(); { Deserializer d; d.Init("game/data/static/uiconfig.dat", "UICO"); d.ReadT(GetInstance().Player.Config); d.Finish(); } } void Level::Update() { ZoneScopedN("Level update"); START_PERF(); PlayerData& player = GetInstance().Player; // Input float delta = GetInstance().Time.Delta; float moveSpeed = player.MovementSpeed; float rotSpeed = player.MouseSensitivity; float forwardInput = (GetKey(ScanCode::W) ? 1.0f : 0.0f) + (GetKey(ScanCode::S) ? -1.0f : 0.0f); float rightInput = (GetKey(ScanCode::D) ? 1.0f : 0.0f) + (GetKey(ScanCode::A) ? -1.0f : 0.0f); bx::Vec3 moveInput = bx::Vec3{rightInput, forwardInput, 0.0f}; moveInput = bx::normalize(moveInput); bx::Vec3 inputVec = {moveInput.x * delta * moveSpeed, 0.0f, moveInput.y * delta * moveSpeed}; Vec2 mouseMovement = GetMouseMovement(); bx::Vec3 rotInput = {mouseMovement.y * delta * -rotSpeed, mouseMovement.x * delta * -rotSpeed, 0.0f}; if (GetKeyPressedNow(ScanCode::F1)) { player.CameraM = player.CameraM == CameraMode::Walk ? CameraMode::Freefly : CameraMode::Walk; } if (GetKeyPressedNow(ScanCode::F2)) { if (player.InputM == InputMode::Game) { player.InputM = InputMode::UI; } else { player.InputM = InputMode::Game; } UpdatePlayerInputMode(); } if (player.CameraM == CameraMode::Freefly) { if (GetMouseButton(MouseButton::Left)) { player.FreeflyXRot += rotInput.x; player.FreeflyYRot += rotInput.y; bx::mtxRotateXYZ(player.FreeflyCamTransform.Rotation.M, player.FreeflyXRot, player.FreeflyYRot, 0.0f); } TranslateLocal(player.FreeflyCamTransform, {0.0f, 0.0f, inputVec.z}); TranslateLocal(player.FreeflyCamTransform, {inputVec.x, 0.0f, 0.0f}); } else if (player.CameraM == CameraMode::Walk) { TranslateLocal(player.PlayerCamTransform, {0.0f, 0.0f, inputVec.z}); TranslateLocal(player.PlayerCamTransform, {inputVec.x, 0.0f, 0.0f}); player.PlayerCamTransform.Position.y = 3.0f; player.WalkXRot += rotInput.x; player.WalkYRot += rotInput.y; bx::mtxRotateXYZ(player.PlayerCamTransform.Rotation.M, player.WalkXRot, player.WalkYRot, 0.0f); } if (GetKeyPressedNow(ScanCode::SPACE)) { player.InteractionM = player.InteractionM == InteractionMode::ReadTablet ? InteractionMode::Walk : InteractionMode::ReadTablet; UpdatePlayerInputMode(); } // UI Tablet if (IsValid(TabletHandle)) { auto& tablet = UIQuads.Get(TabletHandle); tablet.EData.LoadFromSaved(player.Config.TabletBackgroundRenderData); UpdateMatrix(player.PlayerCamTransform); tablet.EData.Transform.Rotation = player.PlayerCamTransform.Rotation; Rotate(tablet.EData.Transform, {0.5f * bx::kPi, 0.0f, 0.0f}); tablet.EData.Transform.Position = player.PlayerCamTransform.Position + AxisForward(player.PlayerCamTransform.M) * 1.0f; } // Cubes for (uint16_t i = 0; i < Cubes.Count; ++i) { Cubes.Get({i}).Update(); } // Puzzle tiles for (int32_t i = 0; i < BX_COUNTOF(Puzzles); ++i) { Puzzles[i].IsActive = GetInstance().DebugData.SelectedDebugLevel == i; Puzzles[i].Update(); } END_PERF(GetShared().Window.PerfCounters, PerfCounterType::GameLevelUpdate, GetShared().Window.FrameCounter); } void Level::Render(uint16_t viewId, const Model* models, const Material* materials, const Texture* textures) { ZoneScopedN("Level Render"); auto& shared = GetShared(); auto& player = GetInstance().Player; bx::mtxProj(&player.Projection.M[0], 75.0f, float(shared.Window.WindowWidth) / float(shared.Window.WindowHeight), 0.1f, 1000.0f, bgfx::getCaps()->homogeneousDepth); bx::mtxInverse(&player.ProjectionInverse.M[0], &player.Projection.M[0]); bool isFreefly = player.CameraM == CameraMode::Freefly; Cubes.Get(PlayerOutsideViewCube).EData.Visible = isFreefly; if (isFreefly) { UpdateMatrix(player.FreeflyCamTransform); bgfx::setViewTransform(viewId, player.FreeflyCamTransform.MI.M, &player.Projection.M[0]); } else { UpdateMatrix(player.PlayerCamTransform); bgfx::setViewTransform(viewId, player.PlayerCamTransform.MI.M, &player.Projection.M[0]); } bgfx::touch(viewId); Cubes.Render(models, materials, textures); Tests.Render(models, materials, textures); PuzzleTiles.Render(models, materials, textures); PuzzleTileCovers.Render(models, materials, textures); if (player.InteractionM == InteractionMode::ReadTablet) { UIQuads.Render(models, materials, textures); } } void Cube::Setup() { EData.MaterialHandle = EMaterial::UI; EData.ModelH = GameRendering::Get().GetModelHandleFromPath("models/cube.gltf"); } void Cube::Update() { EData.Transform.Position = GetInstance().Player.PlayerCamTransform.Position; EData.Transform.Rotation = GetInstance().Player.PlayerCamTransform.Rotation; EData.Transform.Scale = {0.2f, 0.2f, 0.2f}; } void TestEntity::Setup() { EData.MaterialHandle = EMaterial::Default; EData.ModelH = GameRendering::Get().GetModelHandleFromPath("models/cube.gltf"); } void WorldPuzzle::Setup() { auto& level = GetInstance().GameLevel; for (int32_t i = 0; i < Puzzle::Config::MaxCardsInPuzzle; ++i) { TileHandles[i] = level.PuzzleTiles.New(); auto& tile = level.PuzzleTiles.Get(TileHandles[i]); tile.EData.MaterialHandle = EMaterial::Default; for (int32_t j = 0; j < Puzzle::Config::MaxCoversInTile; ++j) { int32_t idx = i * Puzzle::Config::MaxCoversInTile + j; CoverHandles[idx] = level.PuzzleTileCovers.New(); auto& cover = level.PuzzleTileCovers.Get(CoverHandles[idx]); cover.EData.Visible = false; } UIPlacedCards[i] = level.UIQuads.New(); auto& quad = level.UIQuads.Get(UIPlacedCards[i]); quad.EData.ModelH = GameRendering::Get().GetModelHandleFromPath("models/plane.glb"); quad.EData.MaterialHandle = EMaterial::UI; } for (int32_t i = 0; i < Puzzle::Config::MaxAvailableStacks * WorldPuzzle::UIAvailableCardMaxStackPreview; ++i) { UIAvailableCards[i] = level.UIQuads.New(); auto& quad = level.UIQuads.Get(UIAvailableCards[i]); quad.EData.MaterialHandle = EMaterial::UI; quad.EData.ModelH = GameRendering::Get().GetModelHandleFromPath("models/plane.glb"); } SolvedQuad = level.UIQuads.New(); ResetQuad = level.UIQuads.New(); IsSetup = true; LOG("finished setup!"); } Vec3 GetMousePosWorld() { auto& window = GetShared().Window; Vec2 mousePos = GetMousePos(); mousePos.x = mousePos.x / window.WindowWidth; mousePos.y = mousePos.y / window.WindowHeight; mousePos *= 2.0f; mousePos -= 1.0f; Vec4 mousePosView = {mousePos.x, -mousePos.y, 0.0f, 1.0f}; Vec4 mousePosCam4 = Mul(GetInstance().Player.ProjectionInverse, mousePosView); Vec3 mousePosCam = Vec3{ mousePosCam4.x /= mousePosCam4.w, mousePosCam4.y /= mousePosCam4.w, mousePosCam4.z /= mousePosCam4.w, }; return LocalToGlobalPoint(GetInstance().Player.PlayerCamTransform, mousePosCam); } bool IsQuadHovered(Transform& quadTransform, Vec3 mousePosWorld, Vec3& outQuadPlaneIntersectPos) { Vec3 quadPosWorld = quadTransform.Position; Vec3 quadXWorld = LocalToGlobalPoint(quadTransform, {1, 0, 0}); Vec3 quadZWorld = LocalToGlobalPoint(quadTransform, {0, 0, 1}); if (RayPlaneIntersect(GetInstance().Player.PlayerCamTransform.Position, mousePosWorld, quadPosWorld, quadXWorld, quadZWorld, outQuadPlaneIntersectPos)) { Vec3 quadSpaceIntersect = GlobalToLocalPoint(quadTransform, outQuadPlaneIntersectPos); if (quadSpaceIntersect.x >= -1.0f && quadSpaceIntersect.x <= 1.0f && quadSpaceIntersect.z >= -1.0f && quadSpaceIntersect.z <= 1.0f) { return true; } } return false; } void WorldPuzzle::Update() { Level& level = GetInstance().GameLevel; auto& staticCards = Puzzle::GetStaticPuzzleData().Cards; auto& visuals = Puzzle::GetStaticPuzzleData().Visuals; Transform& camTransform = GetInstance().Player.PlayerCamTransform; UpdateMatrix(camTransform); Vec3 cameraPos = camTransform.Position; // TODO: disable warning & check if parentheses make sense like this Vec2 uiOffset = Vec2{static_cast(Data.WidthTiles / Puzzle::Config::CardSize) - 1, static_cast(Data.HeightTiles / Puzzle::Config::CardSize) - 1}; uiOffset *= -UICardOffset * 0.5f; Transform& boardTransform = level.UIQuads.Get(level.TabletHandle).EData.Transform; Transform tileOriginTransform = boardTransform; tileOriginTransform.Position += AxisForward(camTransform.M) * -0.01f; TranslateLocal(tileOriginTransform, Vec3{uiOffset.x, 0.0f, uiOffset.y} * 1.0f); UpdateMatrix(tileOriginTransform); Vec3 mousePosWorld = GetMousePosWorld(); Vec3 quadPlaneIntersectPos; Puzzle::PuzzleSolver solver; EntityRenderData solvedData; solvedData.LoadFromSaved(GetInstance().Player.Config.TabletStatusRenderData); auto& solvedQuad = level.UIQuads.Get(SolvedQuad); solvedQuad.EData = solvedData; solvedQuad.EData.Visible = IsActive; solvedQuad.EData.TextureHandle = solver.IsPuzzleSolved(Data) ? GetInstance().Player.Config.TabletStatusSolvedTexture : solvedData.TextureHandle; solvedQuad.EData.Transform.Position = tileOriginTransform.Position; solvedQuad.EData.Transform.Rotation = camTransform.Rotation; TranslateLocal(solvedQuad.EData.Transform, solvedData.Transform.Position); Rotate(solvedQuad.EData.Transform, Vec3{bx::kPi * 0.5f, 0.0f, (1.0f - 0 * 0.5f) * bx::kPi}); EntityRenderData resetData; resetData.LoadFromSaved(GetInstance().Player.Config.TabletResetRenderData); auto& resetQuad = level.UIQuads.Get(ResetQuad); resetQuad.EData = resetData; resetQuad.EData.Visible = IsActive; resetQuad.EData.Transform.Position = tileOriginTransform.Position; resetQuad.EData.Transform.Rotation = camTransform.Rotation; TranslateLocal(resetQuad.EData.Transform, resetData.Transform.Position); Rotate(resetQuad.EData.Transform, Vec3{bx::kPi * 0.5f, 0.0f, (1.0f - 0 * 0.5f) * bx::kPi}); if (GetMouseButtonPressedNow(MouseButton::Left) && IsQuadHovered(resetQuad.EData.Transform, mousePosWorld, quadPlaneIntersectPos)) { // TODO } // Available Cards for (int32_t i = 0; i < Puzzle::Config::MaxAvailableStacks; ++i) { auto& card = Data.AvailableCards[i]; for (int32_t j = 0; j < UIAvailableCardMaxStackPreview; j++) { auto& quad = level.UIQuads.Get(UIAvailableCards[i * UIAvailableCardMaxStackPreview + j]); int32_t remaining = (int32_t)card.MaxAvailableCount - (int32_t)card.UsedCount; if (i < Data.AvailableCardCount && j < remaining) { quad.EData.Visible = IsActive; quad.EData.TextureHandle = Puzzle::IsValid(Data.AvailableCards[i].RefCard) ? staticCards[Data.AvailableCards[i].RefCard.Idx].BoardTextureHandle : Gen::TextureHandle{}; quad.EData.Transform.Position = tileOriginTransform.Position; quad.EData.Transform.Rotation = camTransform.Rotation; TranslateLocal(quad.EData.Transform, Vec3{j * 0.05f + i * 1.2f, 6.0f + (j % 2 == 0 ? 0.02f : 0.0f), j * 0.001f} * UICardOffset * UICardScale); Rotate(quad.EData.Transform, Vec3{bx::kPi * 0.5f, 0.0f, (1.0f - 0 * 0.5f) * bx::kPi}); quad.EData.Transform.Scale = {UICardScale, UICardScale, UICardScale}; quad.EData.DotColor = {1.0f, 1.0f, 1.0f, 1.0f}; quad.EData.BaseColor = {1.0f, 1.0f, 1.0f, 1.0f}; if (j == 0) { if (IsQuadHovered(quad.EData.Transform, mousePosWorld, quadPlaneIntersectPos) && DraggedAvailableCardIdx == UINT16_MAX && DraggedCard.X == -1 && GetMouseButtonPressedNow(MouseButton::Left)) { DraggedAvailableCardIdx = i; } if (DraggedAvailableCardIdx == i) { Vec3 dragPos = quadPlaneIntersectPos; dragPos -= AxisForward(camTransform.M) * 0.01f; quad.EData.Transform.Position = dragPos; Vec3 boardPos = GlobalToLocalPoint(tileOriginTransform, quadPlaneIntersectPos); Vec3 boardTilePos = boardPos / UICardOffset; int32_t xPos = (int32_t)bx::round(boardTilePos.x); int32_t yPos = (int32_t)bx::round(boardTilePos.z); if (!GetMouseButton(MouseButton::Left)) { if (xPos >= 0 && xPos < Data.WidthTiles / Puzzle::Config::CardSize && yPos >= 0 && yPos < Data.HeightTiles / Puzzle::Config::CardSize) { Gen::PuzPos targetCardPos = {(int8_t)xPos, (int8_t)yPos}; Gen::PlacedPuzzleCard& targetCard = Data.PlacedCards[yPos * Puzzle::Config::MaxPuzzleSizeCards + xPos]; if (!Puzzle::IsValid(targetCard.RefCard) || targetCard.RefCard.Idx == 0) { Puzzle::DragAvailableCardTo(Data, targetCardPos, DraggedAvailableCardIdx, 0); } } DraggedAvailableCardIdx = UINT16_MAX; } } } } else { quad.EData.Visible = false; } } } // Board for (int8_t y = 0; y < Data.HeightTiles / Puzzle::Config::CardSize; ++y) { for (int8_t x = 0; x < Data.WidthTiles / Puzzle::Config::CardSize; ++x) { int32_t cardIdx = y * Puzzle::Config::MaxPuzzleSizeCards + x; Gen::PlacedPuzzleCard& card = Data.PlacedCards[cardIdx]; auto& tile = level.PuzzleTiles.Get(TileHandles[cardIdx]); auto& quad = level.UIQuads.Get(UIPlacedCards[cardIdx]); bool isValid = Puzzle::IsValid(card.RefCard); auto& staticCard = isValid ? staticCards[card.RefCard.Idx] : staticCards[0]; // World Tile tile.EData.Visible = IsActive; tile.EData.ModelH = staticCard.BaseModelHandle; tile.EData.DotColor = visuals.TileDotColor; tile.EData.BaseColor = visuals.TileBaseColor; Vec3 cardPos = { (float)card.Position.X * Puzzle::Config::CardScaleWorld, -5.0f, (float)card.Position.Y * Puzzle::Config::CardScaleWorld, }; if (!isValid) { cardPos = {x * Puzzle::Config::CardScaleWorld, -5.0f, y * Puzzle::Config::CardScaleWorld}; } tile.EData.Transform.Position = cardPos; bx::mtxRotateY(tile.EData.Transform.Rotation.M, card.Rotation * bx::kPi * 0.5f); // Covers if (IsValid(staticCard.BaseModelHandle)) { auto& model = GameRendering::Get().Models[staticCard.BaseModelHandle.ModelIdx]; for (int32_t i = 0; i < model.SocketCount; ++i) { auto& cover = level.PuzzleTileCovers.Get(CoverHandles[cardIdx * Puzzle::Config::MaxCoversInTile + i]); cover.EData.Visible = IsActive; cover.EData.ModelH = staticCard.Sockets[i].Model; cover.EData.Transform = tile.EData.Transform; Gen::TranslateLocal(cover.EData.Transform, model.Sockets[i].Pos); Gen::RotateLocal(cover.EData.Transform, Gen::EulerFromRotation(model.Sockets[i].Rot)); } } // UI Quad quad.EData.Visible = isValid && IsActive; quad.EData.TextureHandle = isValid ? staticCards[card.RefCard.Idx].BoardTextureHandle : Gen::TextureHandle{}; quad.EData.DotColor = card.IsLocked ? Puzzle::GetStaticPuzzleData().Visuals.DisabledCardTint : Vec4{1.0f, 1.0f, 1.0f, 1.0f}; quad.EData.Transform.Position = tileOriginTransform.Position; quad.EData.Transform.Rotation = camTransform.Rotation; TranslateLocal(quad.EData.Transform, Vec3{(float)card.Position.X, (float)card.Position.Y, 0.0f} * UICardOffset * UICardScale); Rotate(quad.EData.Transform, Vec3{bx::kPi * 0.5f, 0.0f, (1.0f - card.Rotation * 0.5f) * bx::kPi}); quad.EData.Transform.Scale = {UICardScale, UICardScale, UICardScale}; Vec3 quadPlaneIntersectPos; if (isValid && IsQuadHovered(quad.EData.Transform, mousePosWorld, quadPlaneIntersectPos)) { if (!card.IsLocked && DraggedCard.X == -1 && DraggedAvailableCardIdx == UINT16_MAX) { if (GetMouseButtonPressedNow(MouseButton::Left)) { DraggedCard.X = x; DraggedCard.Y = y; } if (GetMouseButtonPressedNow(MouseButton::Right)) { Puzzle::RotateCard(card); } } } if (DraggedCard.X == x && DraggedCard.Y == y) { Vec3 dragPos = quadPlaneIntersectPos; dragPos -= AxisForward(camTransform.M) * 0.01f; quad.EData.Transform.Position = dragPos; Vec3 boardPos = GlobalToLocalPoint(tileOriginTransform, quadPlaneIntersectPos); Vec3 boardTilePos = boardPos / UICardOffset; int32_t xPos = (int32_t)bx::round(boardTilePos.x); int32_t yPos = (int32_t)bx::round(boardTilePos.z); Gen::PuzPos srcCardPos = {(int8_t)DraggedCard.X, (int8_t)DraggedCard.Y}; Gen::PlacedPuzzleCard& srcCard = Data.PlacedCards[srcCardPos.Y * Puzzle::Config::MaxPuzzleSizeCards + srcCardPos.X]; if (GetMouseButtonPressedNow(MouseButton::Right)) { Puzzle::RotateCard(srcCard); } if (!GetMouseButton(MouseButton::Left)) { Gen::PuzPos targetCardPos = {(int8_t)xPos, (int8_t)yPos}; if (xPos >= 0 && xPos < Data.WidthTiles / Puzzle::Config::CardSize && yPos >= 0 && yPos < Data.HeightTiles / Puzzle::Config::CardSize) { PlacedPuzzleCard srcCardCopy = srcCard; Gen::PlacedPuzzleCard& targetCard = Data.PlacedCards[yPos * Puzzle::Config::MaxPuzzleSizeCards + xPos]; bool canBeReplaced = !Puzzle::IsValid(targetCard.RefCard) || targetCard.RefCard.Idx == 0; if (canBeReplaced && Puzzle::ReturnPlacedCard(Data, srcCardPos)) { int32_t foundIdx = -1; for (int32_t availCardIdx = 0; availCardIdx < Data.AvailableCardCount; ++availCardIdx) { LOG("CHECK: %u", Data.AvailableCards[availCardIdx].RefCard.Idx); if (Data.AvailableCards[availCardIdx].RefCard.Idx == srcCardCopy.RefCard.Idx) { foundIdx = availCardIdx; break; } } if (foundIdx >= 0) { Puzzle::DragAvailableCardTo(Data, targetCardPos, foundIdx, srcCard.Rotation); } else { LOG_ERROR("NOTFOUND: %u", srcCardCopy.RefCard.Idx); } } } else { Puzzle::ReturnPlacedCard(Data, srcCardPos); } DraggedCard.X = -1; DraggedCard.Y = -1; } } } } } } // namespace Game