SettlementInteractionManager.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. using UnityEngine;
  2. using UnityEngine.SceneManagement;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. /// <summary>
  6. /// Manages settlement interaction on the map - detects when TeamMarker is over a settlement
  7. /// and allows entering the town scene with proper context setup
  8. /// </summary>
  9. public class SettlementInteractionManager : MonoBehaviour
  10. {
  11. [Header("Settlement Detection")]
  12. [Tooltip("The TeamMarker GameObject to track")]
  13. public Transform teamMarker;
  14. [Tooltip("How close the team marker needs to be to settlement center to interact")]
  15. public float interactionDistance = 2f;
  16. [Tooltip("Key to press to enter settlement")]
  17. public KeyCode enterSettlementKey = KeyCode.E;
  18. [Tooltip("Automatically enter settlement when TeamMarker stops moving")]
  19. public bool autoEnterOnStop = false;
  20. [Tooltip("Time to wait before auto-entering (if enabled)")]
  21. public float autoEnterDelay = 1f;
  22. [Header("UI")]
  23. [Tooltip("UI element to show when near settlement (optional)")]
  24. public GameObject interactionPrompt;
  25. [Header("Scene Loading")]
  26. [Tooltip("Name of the town scene to load")]
  27. public string townSceneName = "TownSceen";
  28. [Header("Debug")]
  29. public bool enableDebugLogs = true;
  30. [Header("Exit Protection")]
  31. [Tooltip("Cooldown period after exiting a settlement before allowing re-entry")]
  32. public float exitCooldown = 3f;
  33. // Private fields
  34. private MapMaker2 mapMaker;
  35. private MapData mapData;
  36. private Settlement currentNearbySettlement;
  37. private Vector3 lastTeamMarkerPosition;
  38. private float timeStationary = 0f;
  39. private float lastExitTime = -999f; // Time when last exited a settlement
  40. private string lastExitedSettlement = ""; // Name of last exited settlement
  41. // Events
  42. public System.Action<Settlement> OnSettlementDetected;
  43. public System.Action OnSettlementLeft;
  44. public System.Action<Settlement> OnSettlementEntered;
  45. void Awake()
  46. {
  47. // Check if we're returning from a town scene
  48. var settlementContext = FindFirstObjectByType<SettlementContext>();
  49. if (settlementContext != null && !string.IsNullOrEmpty(settlementContext.settlementName))
  50. {
  51. // We just returned from a settlement - set cooldown
  52. lastExitTime = Time.time;
  53. lastExitedSettlement = settlementContext.settlementName;
  54. LogDebug($"COOLDOWN: Detected return from settlement: {settlementContext.settlementName} - applying {exitCooldown}s cooldown");
  55. }
  56. else
  57. {
  58. LogDebug("COOLDOWN: No settlement context found - normal startup");
  59. }
  60. }
  61. void Start()
  62. {
  63. InitializeComponents();
  64. }
  65. void Update()
  66. {
  67. // Retry MapData connection if it failed initially
  68. if (mapData == null && mapMaker != null)
  69. {
  70. mapData = mapMaker.GetMapData();
  71. if (mapData != null)
  72. {
  73. LogDebug($"MapData connection retry successful - Map size: {mapData.Width}x{mapData.Height}");
  74. }
  75. }
  76. if (teamMarker == null || mapData == null) return;
  77. CheckForSettlementInteraction();
  78. HandleInput();
  79. // Remove auto-enter functionality - only manual E-key entry
  80. // HandleAutoEnter();
  81. }
  82. /// <summary>
  83. /// Initialize required components and references
  84. /// </summary>
  85. private void InitializeComponents()
  86. {
  87. // Find TeamMarker if not assigned
  88. if (teamMarker == null)
  89. {
  90. GameObject teamMarkerObj = GameObject.Find("TeamMarker");
  91. if (teamMarkerObj != null)
  92. {
  93. teamMarker = teamMarkerObj.transform;
  94. LogDebug("Found TeamMarker automatically");
  95. }
  96. else
  97. {
  98. LogDebug("TeamMarker not found - assign manually or create GameObject named 'TeamMarker'");
  99. // Don't return - continue to find MapMaker2 so we can retry later
  100. }
  101. }
  102. // Find MapMaker2
  103. mapMaker = FindFirstObjectByType<MapMaker2>();
  104. if (mapMaker != null)
  105. {
  106. mapData = mapMaker.GetMapData();
  107. LogDebug($"Connected to MapMaker2 - Map size: {mapData?.Width}x{mapData?.Height}");
  108. }
  109. else
  110. {
  111. LogDebug("MapMaker2 not found - settlement detection will not work");
  112. }
  113. // Initialize UI
  114. if (interactionPrompt != null)
  115. {
  116. interactionPrompt.SetActive(false);
  117. }
  118. lastTeamMarkerPosition = teamMarker.position;
  119. }
  120. /// <summary>
  121. /// Check if TeamMarker is near any settlement
  122. /// </summary>
  123. private void CheckForSettlementInteraction()
  124. {
  125. Vector2 teamWorldPos = new Vector2(teamMarker.position.x, teamMarker.position.z);
  126. // Convert world position to map coordinates
  127. Vector2Int mapPos = WorldToMapCoordinates(teamWorldPos);
  128. // Find nearby settlement
  129. Settlement nearbySettlement = FindNearestSettlement(mapPos);
  130. // Handle settlement detection changes
  131. if (nearbySettlement != currentNearbySettlement)
  132. {
  133. if (currentNearbySettlement != null)
  134. {
  135. OnSettlementLeft?.Invoke();
  136. LogDebug($"Left settlement: {currentNearbySettlement.name}");
  137. }
  138. currentNearbySettlement = nearbySettlement;
  139. if (currentNearbySettlement != null)
  140. {
  141. OnSettlementDetected?.Invoke(currentNearbySettlement);
  142. LogDebug($"Near settlement: {currentNearbySettlement.name} ({currentNearbySettlement.Type})");
  143. }
  144. UpdateUI();
  145. }
  146. }
  147. /// <summary>
  148. /// Convert world coordinates to map tile coordinates
  149. /// </summary>
  150. private Vector2Int WorldToMapCoordinates(Vector2 worldPos)
  151. {
  152. // Get tile size from MapVisualizer if available
  153. float tileSize = 1f;
  154. var mapVisualizer = FindFirstObjectByType<MapVisualizer>();
  155. if (mapVisualizer != null && mapVisualizer.tileSize > 0)
  156. {
  157. tileSize = mapVisualizer.tileSize;
  158. }
  159. return new Vector2Int(
  160. Mathf.RoundToInt(worldPos.x / tileSize),
  161. Mathf.RoundToInt(worldPos.y / tileSize)
  162. );
  163. }
  164. /// <summary>
  165. /// Find the nearest settlement to the given map position
  166. /// </summary>
  167. private Settlement FindNearestSettlement(Vector2Int mapPos)
  168. {
  169. if (mapData == null) return null;
  170. // Only detect settlements when directly on the settlement tile (exact match)
  171. Settlement exactMatch = mapData.GetSettlementAt(mapPos);
  172. if (exactMatch != null)
  173. {
  174. // Check exit cooldown
  175. if (exactMatch.name == lastExitedSettlement && Time.time - lastExitTime < exitCooldown)
  176. {
  177. float remainingCooldown = exitCooldown - (Time.time - lastExitTime);
  178. LogDebug($"Settlement {exactMatch.name} on cooldown for {remainingCooldown:F1}s more");
  179. return null;
  180. }
  181. LogDebug($"Direct settlement match: {exactMatch.name} at {mapPos}");
  182. return exactMatch;
  183. }
  184. // No nearby search - only exact tile matches
  185. return null;
  186. }
  187. /// <summary>
  188. /// Handle input for settlement interaction
  189. /// </summary>
  190. private void HandleInput()
  191. {
  192. if (currentNearbySettlement != null && Input.GetKeyDown(enterSettlementKey))
  193. {
  194. EnterSettlement(currentNearbySettlement);
  195. }
  196. }
  197. /// <summary>
  198. /// Handle automatic settlement entry when stationary
  199. /// </summary>
  200. private void HandleAutoEnter()
  201. {
  202. if (!autoEnterOnStop || currentNearbySettlement == null) return;
  203. // Check if team marker has moved
  204. Vector3 currentPosition = teamMarker.position;
  205. float movementDistance = Vector3.Distance(currentPosition, lastTeamMarkerPosition);
  206. if (movementDistance < 0.1f) // Very small threshold for "stationary"
  207. {
  208. timeStationary += Time.deltaTime;
  209. if (timeStationary >= autoEnterDelay)
  210. {
  211. EnterSettlement(currentNearbySettlement);
  212. timeStationary = 0f; // Reset to prevent multiple entries
  213. }
  214. }
  215. else
  216. {
  217. timeStationary = 0f; // Reset timer if moving
  218. }
  219. lastTeamMarkerPosition = currentPosition;
  220. }
  221. /// <summary>
  222. /// Enter the specified settlement - setup context and load town scene
  223. /// </summary>
  224. public void EnterSettlement(Settlement settlement)
  225. {
  226. if (settlement == null) return;
  227. LogDebug($"Entering settlement: {settlement.name} ({settlement.Type})");
  228. // Determine if settlement has harbor (check if near water)
  229. bool hasHarbor = DetermineHarborPresence(settlement);
  230. // Set up SettlementContext
  231. SetupSettlementContext(settlement, hasHarbor);
  232. // Trigger event
  233. OnSettlementEntered?.Invoke(settlement);
  234. // Load town scene
  235. LoadTownScene();
  236. }
  237. /// <summary>
  238. /// Determine if settlement should have a harbor based on nearby terrain
  239. /// </summary>
  240. private bool DetermineHarborPresence(Settlement settlement)
  241. {
  242. if (mapData == null) return false;
  243. // Check nearby tiles for water
  244. for (int dx = -2; dx <= 2; dx++)
  245. {
  246. for (int dy = -2; dy <= 2; dy++)
  247. {
  248. int checkX = settlement.position.x + dx;
  249. int checkY = settlement.position.y + dy;
  250. var tile = mapData.GetTile(checkX, checkY);
  251. if (tile != null && (tile.terrainType == TerrainType.Ocean || tile.terrainType == TerrainType.Lake || tile.terrainType == TerrainType.River))
  252. {
  253. return true;
  254. }
  255. }
  256. }
  257. return false;
  258. }
  259. /// <summary>
  260. /// Setup SettlementContext for scene transition
  261. /// </summary>
  262. private void SetupSettlementContext(Settlement settlement, bool hasHarbor)
  263. {
  264. // Find or create SettlementContext
  265. var existingContext = FindFirstObjectByType<SettlementContext>();
  266. SettlementContext context;
  267. if (existingContext != null)
  268. {
  269. context = existingContext;
  270. }
  271. else
  272. {
  273. var contextObject = new GameObject("SettlementContext");
  274. context = contextObject.AddComponent<SettlementContext>();
  275. DontDestroyOnLoad(contextObject);
  276. }
  277. // Set settlement data
  278. context.SetSettlement(settlement.name, settlement.Type, settlement.position, hasHarbor);
  279. LogDebug($"SettlementContext configured: {settlement.name} ({settlement.Type}) - Harbor: {hasHarbor}");
  280. }
  281. /// <summary>
  282. /// Load the town scene
  283. /// </summary>
  284. private void LoadTownScene()
  285. {
  286. LogDebug($"Loading town scene: {townSceneName}");
  287. SceneManager.LoadScene(townSceneName);
  288. }
  289. /// <summary>
  290. /// Update UI based on current settlement state
  291. /// </summary>
  292. private void UpdateUI()
  293. {
  294. if (interactionPrompt == null) return;
  295. if (currentNearbySettlement != null)
  296. {
  297. interactionPrompt.SetActive(true);
  298. // You could update text here to show settlement name and interaction key
  299. }
  300. else
  301. {
  302. interactionPrompt.SetActive(false);
  303. }
  304. }
  305. /// <summary>
  306. /// Log debug message if debug logging is enabled
  307. /// </summary>
  308. private void LogDebug(string message)
  309. {
  310. if (enableDebugLogs)
  311. {
  312. Debug.Log($"[SettlementInteraction] {message}");
  313. }
  314. }
  315. /// <summary>
  316. /// Force enter a settlement by name (for testing)
  317. /// </summary>
  318. [ContextMenu("Test Enter Current Settlement")]
  319. public void TestEnterCurrentSettlement()
  320. {
  321. if (currentNearbySettlement != null)
  322. {
  323. EnterSettlement(currentNearbySettlement);
  324. }
  325. else
  326. {
  327. LogDebug("No settlement nearby to enter");
  328. }
  329. }
  330. /// <summary>
  331. /// Get current nearby settlement (for external scripts)
  332. /// </summary>
  333. public Settlement GetCurrentNearbySettlement()
  334. {
  335. return currentNearbySettlement;
  336. }
  337. /// <summary>
  338. /// Manually set team marker reference (for external scripts)
  339. /// </summary>
  340. public void SetTeamMarker(Transform marker)
  341. {
  342. teamMarker = marker;
  343. if (marker != null)
  344. {
  345. lastTeamMarkerPosition = marker.position;
  346. LogDebug("Team marker reference updated");
  347. }
  348. }
  349. }