SettlementInteractionManager.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  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. // Initialize team marker position tracking if teamMarker is found
  118. if (teamMarker != null)
  119. {
  120. lastTeamMarkerPosition = teamMarker.position;
  121. }
  122. else
  123. {
  124. LogDebug("WARNING: TeamMarker is null - position tracking will not work until teamMarker is assigned");
  125. }
  126. }
  127. /// <summary>
  128. /// Check if TeamMarker is near any settlement
  129. /// </summary>
  130. private void CheckForSettlementInteraction()
  131. {
  132. // Check if teamMarker is assigned
  133. if (teamMarker == null)
  134. {
  135. return; // Skip interaction check if no team marker
  136. }
  137. Vector2 teamWorldPos = new Vector2(teamMarker.position.x, teamMarker.position.z);
  138. // Convert world position to map coordinates
  139. Vector2Int mapPos = WorldToMapCoordinates(teamWorldPos);
  140. // Find nearby settlement
  141. Settlement nearbySettlement = FindNearestSettlement(mapPos);
  142. // Handle settlement detection changes
  143. if (nearbySettlement != currentNearbySettlement)
  144. {
  145. if (currentNearbySettlement != null)
  146. {
  147. OnSettlementLeft?.Invoke();
  148. LogDebug($"Left settlement: {currentNearbySettlement.name}");
  149. }
  150. currentNearbySettlement = nearbySettlement;
  151. if (currentNearbySettlement != null)
  152. {
  153. OnSettlementDetected?.Invoke(currentNearbySettlement);
  154. LogDebug($"Near settlement: {currentNearbySettlement.name} ({currentNearbySettlement.Type})");
  155. }
  156. UpdateUI();
  157. }
  158. }
  159. /// <summary>
  160. /// Convert world coordinates to map tile coordinates
  161. /// </summary>
  162. private Vector2Int WorldToMapCoordinates(Vector2 worldPos)
  163. {
  164. // Get tile size from MapVisualizer if available
  165. float tileSize = 1f;
  166. var mapVisualizer = FindFirstObjectByType<MapVisualizer>();
  167. if (mapVisualizer != null && mapVisualizer.tileSize > 0)
  168. {
  169. tileSize = mapVisualizer.tileSize;
  170. }
  171. return new Vector2Int(
  172. Mathf.RoundToInt(worldPos.x / tileSize),
  173. Mathf.RoundToInt(worldPos.y / tileSize)
  174. );
  175. }
  176. /// <summary>
  177. /// Find the nearest settlement to the given map position
  178. /// </summary>
  179. private Settlement FindNearestSettlement(Vector2Int mapPos)
  180. {
  181. if (mapData == null) return null;
  182. // Only detect settlements when directly on the settlement tile (exact match)
  183. Settlement exactMatch = mapData.GetSettlementAt(mapPos);
  184. if (exactMatch != null)
  185. {
  186. // Check exit cooldown
  187. if (exactMatch.name == lastExitedSettlement && Time.time - lastExitTime < exitCooldown)
  188. {
  189. float remainingCooldown = exitCooldown - (Time.time - lastExitTime);
  190. LogDebug($"Settlement {exactMatch.name} on cooldown for {remainingCooldown:F1}s more");
  191. return null;
  192. }
  193. LogDebug($"Direct settlement match: {exactMatch.name} at {mapPos}");
  194. return exactMatch;
  195. }
  196. // No nearby search - only exact tile matches
  197. return null;
  198. }
  199. /// <summary>
  200. /// Handle input for settlement interaction
  201. /// </summary>
  202. private void HandleInput()
  203. {
  204. if (currentNearbySettlement != null && Input.GetKeyDown(enterSettlementKey))
  205. {
  206. EnterSettlement(currentNearbySettlement);
  207. }
  208. }
  209. /// <summary>
  210. /// Handle automatic settlement entry when stationary
  211. /// </summary>
  212. private void HandleAutoEnter()
  213. {
  214. if (!autoEnterOnStop || currentNearbySettlement == null) return;
  215. // Check if team marker has moved
  216. Vector3 currentPosition = teamMarker.position;
  217. float movementDistance = Vector3.Distance(currentPosition, lastTeamMarkerPosition);
  218. if (movementDistance < 0.1f) // Very small threshold for "stationary"
  219. {
  220. timeStationary += Time.deltaTime;
  221. if (timeStationary >= autoEnterDelay)
  222. {
  223. EnterSettlement(currentNearbySettlement);
  224. timeStationary = 0f; // Reset to prevent multiple entries
  225. }
  226. }
  227. else
  228. {
  229. timeStationary = 0f; // Reset timer if moving
  230. }
  231. lastTeamMarkerPosition = currentPosition;
  232. }
  233. /// <summary>
  234. /// Enter the specified settlement - setup context and load town scene
  235. /// </summary>
  236. public void EnterSettlement(Settlement settlement)
  237. {
  238. if (settlement == null) return;
  239. LogDebug($"Entering settlement: {settlement.name} ({settlement.Type})");
  240. // Determine if settlement has harbor (check if near water)
  241. bool hasHarbor = DetermineHarborPresence(settlement);
  242. // Set up SettlementContext
  243. SetupSettlementContext(settlement, hasHarbor);
  244. // Trigger event
  245. OnSettlementEntered?.Invoke(settlement);
  246. // Load town scene
  247. LoadTownScene();
  248. }
  249. /// <summary>
  250. /// Determine if settlement should have a harbor based on nearby terrain
  251. /// </summary>
  252. private bool DetermineHarborPresence(Settlement settlement)
  253. {
  254. if (mapData == null) return false;
  255. // Check nearby tiles for water
  256. for (int dx = -2; dx <= 2; dx++)
  257. {
  258. for (int dy = -2; dy <= 2; dy++)
  259. {
  260. int checkX = settlement.position.x + dx;
  261. int checkY = settlement.position.y + dy;
  262. var tile = mapData.GetTile(checkX, checkY);
  263. if (tile != null && (tile.terrainType == TerrainType.Ocean || tile.terrainType == TerrainType.Lake || tile.terrainType == TerrainType.River))
  264. {
  265. return true;
  266. }
  267. }
  268. }
  269. return false;
  270. }
  271. /// <summary>
  272. /// Setup SettlementContext for scene transition
  273. /// </summary>
  274. private void SetupSettlementContext(Settlement settlement, bool hasHarbor)
  275. {
  276. // Find or create SettlementContext
  277. var existingContext = FindFirstObjectByType<SettlementContext>();
  278. SettlementContext context;
  279. if (existingContext != null)
  280. {
  281. context = existingContext;
  282. }
  283. else
  284. {
  285. var contextObject = new GameObject("SettlementContext");
  286. context = contextObject.AddComponent<SettlementContext>();
  287. DontDestroyOnLoad(contextObject);
  288. }
  289. // Set settlement data
  290. context.SetSettlement(settlement.name, settlement.Type, settlement.position, hasHarbor);
  291. LogDebug($"SettlementContext configured: {settlement.name} ({settlement.Type}) - Harbor: {hasHarbor}");
  292. }
  293. /// <summary>
  294. /// Load the town scene
  295. /// </summary>
  296. private void LoadTownScene()
  297. {
  298. LogDebug($"Loading town scene: {townSceneName}");
  299. SceneManager.LoadScene(townSceneName);
  300. }
  301. /// <summary>
  302. /// Update UI based on current settlement state
  303. /// </summary>
  304. private void UpdateUI()
  305. {
  306. if (interactionPrompt == null) return;
  307. if (currentNearbySettlement != null)
  308. {
  309. interactionPrompt.SetActive(true);
  310. // You could update text here to show settlement name and interaction key
  311. }
  312. else
  313. {
  314. interactionPrompt.SetActive(false);
  315. }
  316. }
  317. /// <summary>
  318. /// Log debug message if debug logging is enabled
  319. /// </summary>
  320. private void LogDebug(string message)
  321. {
  322. if (enableDebugLogs)
  323. {
  324. Debug.Log($"[SettlementInteraction] {message}");
  325. }
  326. }
  327. /// <summary>
  328. /// Force enter a settlement by name (for testing)
  329. /// </summary>
  330. [ContextMenu("Test Enter Current Settlement")]
  331. public void TestEnterCurrentSettlement()
  332. {
  333. if (currentNearbySettlement != null)
  334. {
  335. EnterSettlement(currentNearbySettlement);
  336. }
  337. else
  338. {
  339. LogDebug("No settlement nearby to enter");
  340. }
  341. }
  342. /// <summary>
  343. /// Get current nearby settlement (for external scripts)
  344. /// </summary>
  345. public Settlement GetCurrentNearbySettlement()
  346. {
  347. return currentNearbySettlement;
  348. }
  349. /// <summary>
  350. /// Manually set team marker reference (for external scripts)
  351. /// </summary>
  352. public void SetTeamMarker(Transform marker)
  353. {
  354. teamMarker = marker;
  355. if (marker != null)
  356. {
  357. lastTeamMarkerPosition = marker.position;
  358. LogDebug("Team marker reference updated");
  359. }
  360. }
  361. }