using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.InputSystem; namespace MedievalShopkeeper { public enum BuildMode { None, Build, Move, Sell, Repair } public class BuildManager : MonoBehaviour { public static BuildManager Instance { get; private set; } [Header("Settings")] [Tooltip("Size of the grid for snapping floor placements.")] [SerializeField] private float gridSize = 1f; [Tooltip("Layers representing obstacles that block placement.")] [SerializeField] private LayerMask obstacleLayers; [Tooltip("Layer representing the floor.")] [SerializeField] private LayerMask floorLayer; [Tooltip("Layer representing the walls.")] [SerializeField] private LayerMask wallLayer; [Header("Materials")] [Tooltip("Material shown when placement is valid.")] [SerializeField] private Material validMaterial; [Tooltip("Material shown when placement is invalid.")] [SerializeField] private Material invalidMaterial; // Current state private BuildMode currentMode = BuildMode.None; private PlaceableData currentData; private GameObject ghostObject; private GameObject objectBeingMoved; private List ghostRenderers = new List(); private bool isValidPlacement; private Camera mainCam; public bool IsBuilding => currentMode != BuildMode.None; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; mainCam = Camera.main; if (validMaterial == null) CreateDefaultMaterials(); } private void CreateDefaultMaterials() { validMaterial = new Material(Shader.Find("Standard")); validMaterial.SetFloat("_Mode", 2); // Fade validMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); validMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); validMaterial.SetInt("_ZWrite", 0); validMaterial.DisableKeyword("_ALPHATEST_ON"); validMaterial.EnableKeyword("_ALPHABLEND_ON"); validMaterial.DisableKeyword("_ALPHAPREMULTIPLY_ON"); validMaterial.renderQueue = 3000; validMaterial.color = new Color(0f, 1f, 0f, 0.4f); invalidMaterial = new Material(validMaterial); invalidMaterial.color = new Color(1f, 0f, 0f, 0.4f); } public void StartBuilding(PlaceableData data) { if (data == null || data.prefab == null) return; CancelBuilding(); currentData = data; currentMode = BuildMode.Build; if (UIManager.Instance != null) UIManager.Instance.CloseAllMenus(); ghostObject = Instantiate(data.prefab); ghostObject.name = "Ghost_" + data.objectName; PrepareGhostObject(ghostObject); } public void StartMoveMode() { CancelBuilding(); currentMode = BuildMode.Move; Debug.Log("[BuildManager] Move Mode Active."); } public void StartSellMode() { CancelBuilding(); currentMode = BuildMode.Sell; Debug.Log("[BuildManager] Sell Mode Active."); } public void StartRepairMode() { CancelBuilding(); currentMode = BuildMode.Repair; Debug.Log("[BuildManager] Repair Mode Active."); } private void PrepareGhostObject(GameObject obj) { foreach (var col in obj.GetComponentsInChildren()) Destroy(col); foreach (var comp in obj.GetComponentsInChildren()) Destroy(comp); var navObstacle = obj.GetComponent(); if (navObstacle != null) Destroy(navObstacle); ghostRenderers.Clear(); ghostRenderers.AddRange(obj.GetComponentsInChildren()); UpdateGhostMaterial(true); } public void CancelBuilding() { currentMode = BuildMode.None; currentData = null; if (ghostObject != null) { Destroy(ghostObject); } if (objectBeingMoved != null) { objectBeingMoved.SetActive(true); objectBeingMoved = null; } ghostRenderers.Clear(); } private void Update() { if (currentMode == BuildMode.None) return; bool rightClick = Mouse.current != null && Mouse.current.rightButton.wasPressedThisFrame; bool escapeKey = Keyboard.current != null && Keyboard.current.escapeKey.wasPressedThisFrame; bool leftClick = Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame; if (rightClick || escapeKey) { CancelBuilding(); return; } Vector2 pointerPos = Mouse.current != null ? Mouse.current.position.ReadValue() : Vector2.zero; Ray ray = mainCam.ScreenPointToRay(pointerPos); if (currentMode == BuildMode.Move || currentMode == BuildMode.Sell || currentMode == BuildMode.Repair) { if (leftClick && !EventSystem.current.IsPointerOverGameObject()) { if (Physics.Raycast(ray, out RaycastHit selectHit, 100f, obstacleLayers)) { GameObject clickedObj = selectHit.collider.transform.root.gameObject; if (clickedObj.GetComponentInChildren() != null) { HandleExistingObjectClick(clickedObj); } } } return; } if (ghostObject == null || currentData == null && objectBeingMoved == null) return; LayerMask targetMask = floorLayer; if (currentData != null && currentData.placementType == PlacementType.Wall) targetMask = wallLayer; else if (objectBeingMoved != null && objectBeingMoved.CompareTag("WallMount")) targetMask = wallLayer; // Generic fallback if (Physics.Raycast(ray, out RaycastHit hit, 100f, targetMask)) { ghostObject.SetActive(true); Vector3 targetPos; Quaternion targetRot = ghostObject.transform.rotation; if (targetMask == floorLayer) { float x = Mathf.Round(hit.point.x / gridSize) * gridSize; float z = Mathf.Round(hit.point.z / gridSize) * gridSize; targetPos = new Vector3(x, hit.point.y, z); bool rKey = Keyboard.current != null && Keyboard.current.rKey.wasPressedThisFrame; if (rKey) { ghostObject.transform.Rotate(0, 90f, 0); } targetRot = ghostObject.transform.rotation; } else { targetRot = Quaternion.LookRotation(hit.normal); float snappedY = Mathf.Abs(hit.point.y - 1.0f) < Mathf.Abs(hit.point.y - 1.5f) ? 1.0f : 1.5f; targetPos = new Vector3(hit.point.x, snappedY, hit.point.z); } ghostObject.transform.position = targetPos; ghostObject.transform.rotation = targetRot; CheckPlacementValidity(targetPos, targetRot); if (leftClick && !EventSystem.current.IsPointerOverGameObject()) { TryPlaceObject(); } } else { ghostObject.SetActive(false); } } private void HandleExistingObjectClick(GameObject obj) { if (currentMode == BuildMode.Sell) { if (GameManager.Instance != null) GameManager.Instance.AddMoney(50); Debug.Log($"[BuildManager] Sold {obj.name} for 50G."); if (AudioManager.Instance != null) AudioManager.Instance.PlayDestroy(); Destroy(obj); currentMode = BuildMode.Sell; } else if (currentMode == BuildMode.Repair) { Shelf shelf = obj.GetComponent(); if (shelf != null && shelf.isBroken) { shelf.RepairShelf(); if (GameManager.Instance != null && !GameManager.Instance.TrySpendMoney(10)) { Debug.LogWarning("[BuildManager] Not enough money to repair (costs 10G)!"); shelf.BreakShelf(); } else { if (AudioManager.Instance != null) AudioManager.Instance.PlayRepair(); Debug.Log($"[BuildManager] Repaired {obj.name} for 10G."); } } } else if (currentMode == BuildMode.Move) { objectBeingMoved = obj; objectBeingMoved.SetActive(false); // Hide real one if (ghostObject != null) Destroy(ghostObject); ghostObject = GameObject.CreatePrimitive(PrimitiveType.Cube); Destroy(ghostObject.GetComponent()); ghostObject.transform.localScale = new Vector3(1f, 1f, 1f); PrepareGhostObject(ghostObject); currentMode = BuildMode.Build; Debug.Log($"[BuildManager] Picked up {obj.name} to move."); } } private void CheckPlacementValidity(Vector3 pos, Quaternion rot) { Vector3 extents = currentData != null ? currentData.overlapBoxExtents : new Vector3(0.45f, 0.45f, 0.45f); Vector3 boxCenter = pos + rot * new Vector3(0, extents.y, 0); Collider[] colliders = Physics.OverlapBox(boxCenter, extents, rot, obstacleLayers); isValidPlacement = (colliders.Length == 0); if (objectBeingMoved == null && currentData != null) { if (GameManager.Instance != null && GameManager.Instance.CurrentMoney < currentData.purchaseCost) { isValidPlacement = false; } } UpdateGhostMaterial(isValidPlacement); } private void UpdateGhostMaterial(bool isValid) { Material targetMat = isValid ? validMaterial : invalidMaterial; foreach (var r in ghostRenderers) { if (r == null) continue; Material[] mats = new Material[r.materials.Length]; for (int i = 0; i < mats.Length; i++) { mats[i] = targetMat; } r.materials = mats; } } private void TryPlaceObject() { if (!isValidPlacement) { Debug.LogWarning("[BuildManager] Cannot place object here (Blocked or insufficient funds)."); return; } if (objectBeingMoved != null) { objectBeingMoved.transform.position = ghostObject.transform.position; objectBeingMoved.transform.rotation = ghostObject.transform.rotation; objectBeingMoved.SetActive(true); if (AudioManager.Instance != null) AudioManager.Instance.PlayBuild(); Debug.Log($"[BuildManager] Moved {objectBeingMoved.name}."); CancelBuilding(); } else if (currentData != null) { if (GameManager.Instance != null && !GameManager.Instance.TrySpendMoney(currentData.purchaseCost)) { return; } GameObject newObj = Instantiate(currentData.prefab, ghostObject.transform.position, ghostObject.transform.rotation); newObj.name = currentData.objectName; if (AudioManager.Instance != null) AudioManager.Instance.PlayBuild(); Debug.Log($"[BuildManager] Placed {currentData.objectName} for {currentData.purchaseCost} G."); CheckPlacementValidity(ghostObject.transform.position, ghostObject.transform.rotation); } } } }