SettlementInteractionManager.cs 12 KB

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