| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074 |
- using UnityEngine;
- using System.Collections;
- using System.Collections.Generic;
- using System.Linq;
- /// <summary>
- /// New comprehensive travel system that handles movement costs, pathfinding, and visual planning.
- /// Integrates with SimpleTeamPlacement and MapMaker2 for seamless map-based travel.
- /// </summary>
- public class TeamTravelSystem : MonoBehaviour
- {
- [Header("Movement Costs")]
- [Range(0.1f, 5f)]
- public float plainsMovementCost = 1f;
- [Range(0.1f, 5f)]
- public float forestMovementCost = 2f;
- [Range(0.1f, 5f)]
- public float mountainMovementCost = 4f;
- [Range(0.1f, 2f)]
- public float roadMovementCost = 0.5f;
- [Range(0.1f, 2f)]
- public float townMovementCost = 0.1f; // Almost instant travel inside towns
- [Range(0.1f, 2f)]
- public float bridgeMovementCost = 0.6f;
- [Header("Special Travel Costs")]
- [Range(1, 100)]
- public int ferryBaseCost = 25; // Gold cost for ferry
- [Range(0.1f, 2f)]
- public float ferryMovementCost = 0.7f; // Faster than walking but costs money
- [Range(1, 50)]
- public int tunnelTorchCost = 10; // Torch cost for tunnel travel
- [Range(0.1f, 2f)]
- public float tunnelWithTorchCost = 0.8f; // Fast with torch
- [Range(1f, 5f)]
- public float tunnelWithoutTorchCost = 3f; // Slow and dangerous without torch
- [Range(1, 20)]
- public int bridgeTollCost = 5; // Gold cost for major bridge crossings
- [Range(1, 30)]
- public int mountainPassCost = 15; // Guide cost for dangerous mountain passes
- [Header("River Crossing")]
- [Range(2f, 10f)]
- public float riverCrossingCost = 2f; // Reduced from 6f - more reasonable river crossing
- [Range(1f, 8f)]
- public float lakeCrossingCost = 2.5f; // Reduced from 4f - more reasonable lake crossing
- [Range(5f, 15f)]
- public float oceanCrossingCost = 8f; // Reduced from 10f but still expensive
- [Header("Visual Path Settings")]
- public Material pathLineMaterial;
- public Color standardPathColor = Color.blue;
- public Color expensivePathColor = Color.red;
- public Color alternativePathColor = Color.yellow;
- [Range(0.1f, 5f)]
- public float pathLineWidth = 2.5f; // Increased for much better visibility
- [Range(-10f, 10f)]
- public float pathLineHeight = 1f; // Height above map tiles for path lines
- [Header("Debug")]
- public bool showDebugLogs = false;
- public bool showMovementCosts = false;
- // References
- private SimpleTeamPlacement teamPlacement;
- private MapMaker2 mapMaker;
- private Camera mapCamera;
- private TravelUI travelUI;
- // Travel state
- private Vector2Int? plannedDestination;
- private List<TravelRoute> availableRoutes = new List<TravelRoute>();
- private List<GameObject> currentPathVisualizations = new List<GameObject>(); // Store multiple route lines
- private bool isTraveling = false;
- private int currentRouteIndex = 0; // For cycling through route visualizations
- // Journey cancellation
- private bool shouldCancelJourney = false;
- private Coroutine currentTravelCoroutine = null;
- private TravelRoute currentRoute = null;
- // Time skip functionality
- [Header("Time Controls")]
- public float timeMultiplier = 1f; // Current time speed multiplier
- public float[] timeSpeedOptions = { 1f, 2f, 4f, 8f }; // Available speed options
- private int currentTimeSpeedIndex = 0;
- // Static instance
- public static TeamTravelSystem Instance { get; private set; }
- void Awake()
- {
- if (Instance == null)
- {
- Instance = this;
- }
- else
- {
- Debug.LogWarning("Multiple TeamTravelSystem instances found. Destroying this one.");
- Destroy(gameObject);
- return;
- }
- }
- void Start()
- {
- // Find required components
- teamPlacement = FindFirstObjectByType<SimpleTeamPlacement>();
- mapMaker = FindFirstObjectByType<MapMaker2>();
- mapCamera = Camera.main;
- travelUI = FindFirstObjectByType<TravelUI>();
- // Initialize pathLineMaterial if not assigned
- if (pathLineMaterial == null)
- {
- pathLineMaterial = CreateDefaultPathMaterial();
- }
- if (teamPlacement == null)
- {
- Debug.LogError("SimpleTeamPlacement not found! TeamTravelSystem requires it.");
- }
- if (mapMaker == null)
- {
- Debug.LogError("MapMaker2 not found! TeamTravelSystem requires it.");
- }
- if (travelUI == null)
- {
- Debug.LogWarning("TravelUI not found! UI features will be limited.");
- }
- }
- void Update()
- {
- HandleInput();
- }
- private void HandleInput()
- {
- // Time speed controls (available during travel)
- if (isTraveling)
- {
- if (Input.GetKeyDown(KeyCode.Period) || Input.GetKeyDown(KeyCode.RightArrow)) // > key or right arrow
- {
- IncreaseTimeSpeed();
- }
- else if (Input.GetKeyDown(KeyCode.Comma) || Input.GetKeyDown(KeyCode.LeftArrow)) // < key or left arrow
- {
- DecreaseTimeSpeed();
- }
- else if (Input.GetKeyDown(KeyCode.Space)) // Space to reset speed
- {
- ResetTimeSpeed();
- }
- else if (Input.GetKeyDown(KeyCode.Escape) || Input.GetKeyDown(KeyCode.C)) // ESC or C to cancel journey
- {
- CancelJourney();
- }
- }
- // Handle map clicking for travel planning (only if click is outside UI panel)
- if (Input.GetMouseButtonDown(0) && !isTraveling)
- {
- // Check if click is within the UI panel bounds
- if (travelUI != null && travelUI.IsVisible && travelUI.IsPointWithinUI(Input.mousePosition))
- {
- return; // Block map interaction if clicking within UI panel
- }
- Vector3 mouseWorldPos = GetMouseWorldPosition();
- if (mouseWorldPos != Vector3.zero)
- {
- Vector2Int clickedTile = WorldToTilePosition(mouseWorldPos);
- PlanTravelTo(clickedTile);
- }
- }
- // Check if TravelUI is blocking other input (non-map clicks)
- if (IsTravelUIBlocking())
- {
- return; // Block other inputs but allow map clicks above
- }
- // Cancel travel planning
- if (Input.GetKeyDown(KeyCode.Escape))
- {
- CancelTravelPlanning();
- }
- // Cycle through route visualizations
- if (Input.GetKeyDown(KeyCode.Tab) && availableRoutes.Count > 1)
- {
- currentRouteIndex = (currentRouteIndex + 1) % availableRoutes.Count;
- var selectedRoute = availableRoutes[currentRouteIndex];
- // Highlight the selected route without clearing others
- HighlightSpecificRoute(selectedRoute);
- // Update the UI to show the new selected route
- if (travelUI != null)
- {
- travelUI.UpdateUIForSelectedRoute(selectedRoute);
- }
- if (showDebugLogs)
- {
- Debug.Log($"Switched to {selectedRoute.routeType} route (Time: {selectedRoute.estimatedTime:F1}h)");
- }
- }
- // Debug key to show movement costs
- if (Input.GetKeyDown(KeyCode.C) && showMovementCosts)
- {
- ShowMovementCostInfo();
- }
- // Toggle debug logs
- if (Input.GetKeyDown(KeyCode.F8))
- {
- showDebugLogs = !showDebugLogs;
- Debug.Log($"Travel System Debug Logs: {(showDebugLogs ? "ENABLED" : "DISABLED")}");
- }
- }
- /// <summary>
- /// Plans travel to the specified destination and shows available routes
- /// </summary>
- public void PlanTravelTo(Vector2Int destination)
- {
- if (!teamPlacement.IsTeamPlaced())
- {
- Debug.LogWarning("❌ Cannot plan travel: Team not placed yet");
- return;
- }
- Vector2Int currentPosition = teamPlacement.GetTeamPosition();
- if (currentPosition == destination)
- {
- Debug.Log("Already at destination!");
- return;
- }
- plannedDestination = destination;
- currentRouteIndex = 0; // Reset route index for new destination
- if (showDebugLogs)
- {
- Debug.Log($"🗺️ Planning travel from {currentPosition} to {destination}");
- }
- // Calculate different route options
- CalculateRoutes(currentPosition, destination);
- // If UI is already visible, refresh it with new routes
- if (travelUI != null && travelUI.IsVisible)
- {
- Debug.Log("🔄 TeamTravelSystem: UI is visible, refreshing with new routes");
- travelUI.RefreshWithNewRoutes(availableRoutes);
- }
- // Show all available routes visually with different colors
- if (availableRoutes.Count > 0)
- {
- // Clear any existing visualizations first
- ClearPathVisualization();
- // Select the fastest route (shortest estimated time) by default
- var routesByTime = availableRoutes.OrderBy(r => r.estimatedTime).ToList();
- TravelRoute selectedRoute = routesByTime.First();
- // Update currentRouteIndex to match the selected route in the original list
- currentRouteIndex = availableRoutes.IndexOf(selectedRoute);
- // Visualize all routes with the fastest one highlighted on top
- VisualizeAllRoutes(availableRoutes);
- if (showDebugLogs)
- {
- Debug.Log($"🔧 About to show UI for {selectedRoute.routeType} route with {selectedRoute.path.Count} waypoints");
- }
- // Show travel UI if available
- ShowTravelUIIfAvailable(destination, availableRoutes);
- if (showDebugLogs)
- {
- Debug.Log($"Found {availableRoutes.Count} route options. Selected {selectedRoute.routeType} route: {selectedRoute.estimatedTime:F1}h time");
- Debug.Log("Use TAB to cycle through different route options");
- }
- }
- }
- /// <summary>
- /// Calculates different route options including fastest, shortest, and cheapest routes
- /// </summary>
- private void CalculateRoutes(Vector2Int start, Vector2Int destination)
- {
- availableRoutes.Clear();
- var mapData = mapMaker.GetMapData();
- if (mapData == null) return;
- if (showDebugLogs)
- {
- Debug.Log($"🔍 Calculating routes from {start} to {destination}");
- }
- // Fastest route (heavily prefers roads and fast terrain)
- var fastestRoute = FindPath(start, destination, RouteType.RoadPreferred);
- if (fastestRoute != null)
- {
- availableRoutes.Add(fastestRoute);
- if (showDebugLogs)
- {
- Debug.Log($"Fastest route: {fastestRoute.estimatedTime:F1}h time, {fastestRoute.path.Count} waypoints");
- }
- }
- // Shortest route (direct path, minimal waypoints)
- var shortestRoute = FindPath(start, destination, RouteType.Standard);
- if (shortestRoute != null)
- {
- bool similar = RoutesSimilar(fastestRoute, shortestRoute);
- if (showDebugLogs)
- {
- Debug.Log($"Shortest route: {shortestRoute.estimatedTime:F1}h time, {shortestRoute.path.Count} waypoints, similar to fastest: {similar}");
- }
- if (!similar)
- {
- availableRoutes.Add(shortestRoute);
- }
- }
- // Cheapest route (minimizes resource costs first, then travel time)
- var cheapestRoute = FindCheapestPath(start, destination);
- if (cheapestRoute != null)
- {
- bool similarToFastest = RoutesSimilar(fastestRoute, cheapestRoute);
- bool similarToShortest = RoutesSimilar(shortestRoute, cheapestRoute);
- if (showDebugLogs)
- {
- Debug.Log($"Cheapest route: {cheapestRoute.estimatedTime:F1}h time, {GetTotalResourceCost(cheapestRoute)} resource cost, {cheapestRoute.path.Count} waypoints");
- }
- if (!similarToFastest && !similarToShortest)
- {
- availableRoutes.Add(cheapestRoute);
- }
- }
- // If we ended up with only one route, try to generate alternatives with different parameters
- if (availableRoutes.Count == 1)
- {
- if (showDebugLogs)
- {
- Debug.Log("Only one unique route found, generating alternatives...");
- }
- // Try a more direct route that ignores some terrain penalties
- var directRoute = FindDirectPath(start, destination);
- if (directRoute != null && !RoutesSimilar(availableRoutes[0], directRoute))
- {
- availableRoutes.Add(directRoute);
- if (showDebugLogs)
- {
- Debug.Log($"Direct route added: {directRoute.estimatedTime:F1}h time");
- }
- }
- }
- if (showDebugLogs)
- {
- Debug.Log($"📊 Total unique routes calculated: {availableRoutes.Count}");
- }
- }
- /// <summary>
- /// Finds the best path between two points using A* pathfinding with different route preferences
- /// </summary>
- private TravelRoute FindPath(Vector2Int start, Vector2Int destination, RouteType routeType)
- {
- var mapData = mapMaker.GetMapData();
- Debug.Log($"🗺️ Finding {routeType} path from {start} to {destination}");
- var openSet = new List<PathNode>();
- var closedSet = new HashSet<Vector2Int>();
- var allNodes = new Dictionary<Vector2Int, PathNode>();
- var startNode = new PathNode
- {
- position = start,
- gCost = 0,
- hCost = CalculateHeuristic(start, destination, routeType),
- parent = null
- };
- openSet.Add(startNode);
- allNodes[start] = startNode;
- int iterations = 0;
- const int maxIterations = 10000; // Prevent infinite loops
- while (openSet.Count > 0 && iterations < maxIterations)
- {
- iterations++;
- // Find node with lowest fCost
- var currentNode = openSet.OrderBy(n => n.fCost).ThenBy(n => n.hCost).First();
- openSet.Remove(currentNode);
- closedSet.Add(currentNode.position);
- // Reached destination
- if (currentNode.position == destination)
- {
- var route = ReconstructRoute(currentNode, routeType);
- if (showDebugLogs)
- {
- Debug.Log($"{routeType} path found in {iterations} iterations, final cost: {currentNode.gCost:F1}");
- }
- // Analyze the route composition
- if (showDebugLogs)
- {
- AnalyzeRouteComposition(route);
- }
- return route;
- }
- // Check neighbors
- var neighbors = GetNeighbors(currentNode.position);
- foreach (var neighbor in neighbors)
- {
- if (closedSet.Contains(neighbor) || !mapData.IsValidPosition(neighbor.x, neighbor.y))
- continue;
- float movementCost = GetMovementCost(currentNode.position, neighbor, routeType);
- if (movementCost < 0) continue; // Impassable terrain
- float tentativeGCost = currentNode.gCost + movementCost;
- if (!allNodes.ContainsKey(neighbor))
- {
- allNodes[neighbor] = new PathNode
- {
- position = neighbor,
- gCost = float.MaxValue,
- hCost = CalculateHeuristic(neighbor, destination, routeType),
- parent = null
- };
- }
- var neighborNode = allNodes[neighbor];
- if (tentativeGCost < neighborNode.gCost)
- {
- neighborNode.parent = currentNode;
- neighborNode.gCost = tentativeGCost;
- if (!openSet.Contains(neighborNode))
- {
- openSet.Add(neighborNode);
- }
- }
- }
- }
- Debug.LogWarning($"❌ {routeType} path not found after {iterations} iterations (max: {maxIterations})");
- return null; // No path found
- }
- /// <summary>
- /// Gets movement cost for moving from one tile to another, considering route type preferences
- /// </summary>
- private float GetMovementCost(Vector2Int from, Vector2Int to, RouteType routeType)
- {
- var mapData = mapMaker.GetMapData();
- var toTile = mapData.GetTile(to.x, to.y);
- // Base movement cost by terrain
- float baseCost = GetTerrainMovementCost(toTile);
- // Feature modifications
- float featureCost = GetFeatureMovementCost(toTile, routeType);
- // Combine costs
- float totalCost = baseCost * featureCost;
- // Route type adjustments
- switch (routeType)
- {
- case RouteType.Standard:
- // Shortest route: minimize distance regardless of terrain difficulty
- // No terrain or road preferences - pure distance optimization
- break;
- case RouteType.RoadPreferred:
- // Fastest route: heavily favors roads and fast terrain, considers costly but fast options
- if (toTile.featureType == FeatureType.Road)
- {
- totalCost *= 0.3f; // Strong road preference (70% discount) - roads are much faster but realistic
- }
- else if (toTile.featureType == FeatureType.Tunnel)
- {
- // Tunnels are fast for fastest route (ignore resource cost for speed optimization)
- totalCost *= 0.4f; // Very fast travel through tunnels
- }
- else if (toTile.featureType == FeatureType.Ferry)
- {
- // Ferries are reasonably fast for fastest route
- totalCost *= 0.6f; // Fast water crossing
- }
- else
- {
- // Penalize non-road movement for road-preferred routes
- totalCost *= 1.5f; // 50% penalty for off-road movement
- }
- break;
- case RouteType.Special:
- // Cheapest route: avoid resource costs, but don't make travel time unrealistic
- // Moderate penalties for features that cost resources
- if (toTile.featureType == FeatureType.Ferry || toTile.featureType == FeatureType.Tunnel)
- totalCost *= 3.0f; // Significant penalty for resource-costing features (was 10x)
- // Moderate penalties for features that might trigger resource costs
- if (toTile.featureType == FeatureType.Bridge)
- totalCost *= 2.0f; // Penalty for bridges (potential tolls) (was 5x)
- // Light penalty for terrain that requires guides/special costs
- if (toTile.terrainType == TerrainType.Mountain)
- totalCost *= 2.0f; // Penalty for mountains (guide costs) (was 5x)
- // Still avoid slow terrain, but secondary to cost avoidance
- if (toTile.terrainType == TerrainType.River || toTile.terrainType == TerrainType.Lake)
- totalCost *= 1.5f; // Light penalty for water crossings (was 2x)
- // Prefer roads since they're free and fast
- if (toTile.featureType == FeatureType.Road)
- totalCost *= 0.5f; // Good road preference (50% discount)
- break;
- }
- return totalCost;
- }
- /// <summary>
- /// Gets base movement cost for different terrain types
- /// </summary>
- private float GetTerrainMovementCost(MapTile tile)
- {
- switch (tile.terrainType)
- {
- case TerrainType.Plains:
- return plainsMovementCost;
- case TerrainType.Forest:
- return forestMovementCost;
- case TerrainType.Mountain:
- return mountainMovementCost;
- case TerrainType.Ocean:
- return oceanCrossingCost; // Allow dangerous ocean crossing
- case TerrainType.Lake:
- return lakeCrossingCost; // Allow swimming across lakes
- case TerrainType.River:
- return riverCrossingCost; // Allow slow river crossing
- default:
- return plainsMovementCost;
- }
- }
- /// <summary>
- /// Gets movement cost modifications from features (roads, bridges, etc.)
- /// </summary>
- private float GetFeatureMovementCost(MapTile tile, RouteType routeType)
- {
- switch (tile.featureType)
- {
- case FeatureType.Road:
- return roadMovementCost; // Fast movement on roads
- case FeatureType.Bridge:
- return roadMovementCost; // Bridges are as fast as roads (crossing structures)
- case FeatureType.Town:
- case FeatureType.Village:
- return townMovementCost; // Very fast through settlements
- case FeatureType.Ferry:
- return ferryMovementCost; // Ferries are fast transportation (cost is in gold, not time)
- case FeatureType.Tunnel:
- return tunnelWithTorchCost; // Tunnels are fast with torch (cost is in torches, not time)
- default:
- return 1f; // No modification
- }
- }
- /// <summary>
- /// Calculates heuristic distance for A* pathfinding with route type considerations
- /// </summary>
- private float CalculateHeuristic(Vector2Int from, Vector2Int to, RouteType routeType = RouteType.Standard)
- {
- float baseHeuristic = Vector2Int.Distance(from, to) * plainsMovementCost;
- // For road-preferred routes, make the heuristic slightly more optimistic about finding roads
- if (routeType == RouteType.RoadPreferred)
- {
- // Assume we'll find roads for some of the journey
- baseHeuristic *= 0.8f; // Slightly more optimistic heuristic
- }
- return baseHeuristic;
- }
- /// <summary>
- /// Gets valid neighboring tiles for pathfinding
- /// </summary>
- private List<Vector2Int> GetNeighbors(Vector2Int position)
- {
- var neighbors = new List<Vector2Int>();
- // 8-directional movement
- for (int dx = -1; dx <= 1; dx++)
- {
- for (int dy = -1; dy <= 1; dy++)
- {
- if (dx == 0 && dy == 0) continue;
- Vector2Int neighbor = new Vector2Int(position.x + dx, position.y + dy);
- neighbors.Add(neighbor);
- }
- }
- return neighbors;
- }
- /// <summary>
- /// Reconstructs the final route from pathfinding results
- /// </summary>
- private TravelRoute ReconstructRoute(PathNode destinationNode, RouteType routeType)
- {
- var route = new TravelRoute
- {
- routeType = routeType,
- path = new List<Vector2Int>(),
- totalCost = destinationNode.gCost,
- totalDistance = 0f,
- estimatedTime = 0f,
- specialCosts = new Dictionary<string, int>()
- };
- var current = destinationNode;
- while (current != null)
- {
- route.path.Insert(0, current.position);
- current = current.parent;
- }
- // Calculate additional route information
- CalculateRouteDetails(route);
- return route;
- }
- /// <summary>
- /// Finds a more direct path that ignores some terrain penalties for route variety
- /// </summary>
- private TravelRoute FindDirectPath(Vector2Int start, Vector2Int destination)
- {
- var mapData = mapMaker.GetMapData();
- Debug.Log($"🎯 Finding direct path from {start} to {destination}");
- var openSet = new List<PathNode>();
- var closedSet = new HashSet<Vector2Int>();
- var allNodes = new Dictionary<Vector2Int, PathNode>();
- var startNode = new PathNode
- {
- position = start,
- gCost = 0,
- hCost = CalculateHeuristic(start, destination),
- parent = null
- };
- openSet.Add(startNode);
- allNodes[start] = startNode;
- int iterations = 0;
- const int maxIterations = 10000;
- while (openSet.Count > 0 && iterations < maxIterations)
- {
- iterations++;
- var currentNode = openSet.OrderBy(n => n.fCost).ThenBy(n => n.hCost).First();
- openSet.Remove(currentNode);
- closedSet.Add(currentNode.position);
- if (currentNode.position == destination)
- {
- var route = ReconstructRoute(currentNode, RouteType.Special);
- route.routeType = RouteType.Special; // Mark as direct route
- Debug.Log($"✅ Direct path found in {iterations} iterations, final cost: {currentNode.gCost:F1}");
- CalculateRouteDetails(route);
- return route;
- }
- var neighbors = GetNeighbors(currentNode.position);
- foreach (var neighbor in neighbors)
- {
- if (closedSet.Contains(neighbor) || !mapData.IsValidPosition(neighbor.x, neighbor.y))
- continue;
- // Use simplified movement cost for direct routes (less terrain penalty)
- float movementCost = GetDirectMovementCost(currentNode.position, neighbor);
- if (movementCost < 0) continue;
- float tentativeGCost = currentNode.gCost + movementCost;
- if (!allNodes.ContainsKey(neighbor))
- {
- allNodes[neighbor] = new PathNode
- {
- position = neighbor,
- gCost = float.MaxValue,
- hCost = CalculateHeuristic(neighbor, destination),
- parent = null
- };
- }
- var neighborNode = allNodes[neighbor];
- if (tentativeGCost < neighborNode.gCost)
- {
- neighborNode.parent = currentNode;
- neighborNode.gCost = tentativeGCost;
- if (!openSet.Contains(neighborNode))
- {
- openSet.Add(neighborNode);
- }
- }
- }
- }
- Debug.LogWarning($"❌ Direct path not found after {iterations} iterations");
- return null;
- }
- /// <summary>
- /// Simplified movement cost calculation for direct routes
- /// </summary>
- private float GetDirectMovementCost(Vector2Int from, Vector2Int to)
- {
- var mapData = mapMaker.GetMapData();
- var toTile = mapData.GetTile(to.x, to.y);
- // Simplified costs that emphasize directness over terrain optimization
- switch (toTile.terrainType)
- {
- case TerrainType.Plains:
- return 1.0f;
- case TerrainType.Forest:
- return 1.5f; // Less penalty than normal
- case TerrainType.Mountain:
- return 2.0f; // Much less penalty for directness
- case TerrainType.River:
- return 1.8f;
- case TerrainType.Lake:
- return 2.0f;
- case TerrainType.Ocean:
- return 4.0f; // Still expensive but possible
- default:
- return 1.0f;
- }
- }
- /// <summary>
- /// Finds the cheapest path that minimizes resource costs (gold) first, then travel time
- /// Uses the Special route type but should calculate actual travel time correctly
- /// </summary>
- private TravelRoute FindCheapestPath(Vector2Int start, Vector2Int destination)
- {
- // Use the standard A* pathfinding with Special route type (which avoids costly features)
- return FindPath(start, destination, RouteType.Special);
- }
- /// <summary>
- /// Gets the resource cost for entering a specific tile
- /// </summary>
- private float GetResourceCostForTile(Vector2Int position)
- {
- var mapData = mapMaker.GetMapData();
- var tile = mapData.GetTile(position.x, position.y);
- switch (tile.featureType)
- {
- case FeatureType.Ferry:
- return ferryBaseCost; // Direct gold cost
- case FeatureType.Tunnel:
- return tunnelTorchCost; // Torch cost (treated as gold equivalent)
- case FeatureType.Bridge:
- return 2.0f; // Potential bridge toll (small cost to prefer other routes)
- default:
- break;
- }
- // Terrain-based resource costs (for guide fees, etc.)
- switch (tile.terrainType)
- {
- case TerrainType.Mountain:
- return 3.0f; // Potential guide cost for dangerous areas
- default:
- return 0.0f; // No resource cost
- }
- }
- /// <summary>
- /// Calculates detailed information about the route (distance, time, special costs)
- /// </summary>
- private void CalculateRouteDetails(TravelRoute route)
- {
- var mapData = mapMaker.GetMapData();
- route.totalDistance = route.path.Count;
- route.estimatedTime = route.totalCost * 0.5f; // Rough time estimate
- // Check for special costs
- int bridgeCount = 0;
- int mountainTileCount = 0;
- foreach (var position in route.path)
- {
- var tile = mapData.GetTile(position.x, position.y);
- // Ferry costs
- if (tile.featureType == FeatureType.Ferry)
- {
- if (!route.specialCosts.ContainsKey("Ferry"))
- route.specialCosts["Ferry"] = ferryBaseCost;
- }
- // Tunnel costs
- else if (tile.featureType == FeatureType.Tunnel)
- {
- if (!route.specialCosts.ContainsKey("Tunnel Torch"))
- route.specialCosts["Tunnel Torch"] = tunnelTorchCost;
- }
- // Bridge tolls (for major bridges)
- else if (tile.featureType == FeatureType.Bridge)
- {
- bridgeCount++;
- }
- // Mountain passes (dangerous mountain areas need guides)
- else if (tile.terrainType == TerrainType.Mountain)
- {
- mountainTileCount++;
- }
- }
- // Add bridge toll costs (only for longer bridge crossings)
- if (bridgeCount >= 3) // Only charge for significant bridge crossings
- {
- route.specialCosts["Bridge Toll"] = bridgeTollCost;
- }
- // Add mountain guide costs (for extensive mountain travel)
- if (mountainTileCount >= 5) // Only charge for dangerous mountain passes
- {
- route.specialCosts["Mountain Guide"] = mountainPassCost;
- }
- }
- /// <summary>
- /// Calculates the total resource cost (gold equivalent) for a route
- /// </summary>
- private int GetTotalResourceCost(TravelRoute route)
- {
- if (route?.specialCosts == null) return 0;
- int totalCost = 0;
- foreach (var cost in route.specialCosts)
- {
- totalCost += cost.Value; // All costs are in gold or gold-equivalent
- }
- return totalCost;
- }
- /// <summary>
- /// Checks if two routes are essentially the same
- /// </summary>
- private bool RoutesSimilar(TravelRoute route1, TravelRoute route2)
- {
- if (route1 == null || route2 == null) return false;
- // Consider routes similar if they have >90% path overlap (more strict to allow more variations)
- var overlap = route1.path.Intersect(route2.path).Count();
- var maxLength = Mathf.Max(route1.path.Count, route2.path.Count);
- bool similar = (float)overlap / maxLength > 0.9f;
- Debug.Log($"🔍 Route similarity check: {overlap}/{maxLength} = {((float)overlap / maxLength):F2} > 0.9? {similar}");
- return similar;
- }
- /// <summary>
- /// Analyzes and logs the composition of a route for debugging
- /// </summary>
- private void AnalyzeRouteComposition(TravelRoute route)
- {
- if (route == null || route.path == null) return;
- var mapData = mapMaker.GetMapData();
- int plainsCount = 0, forestCount = 0, mountainCount = 0, roadCount = 0, riverCount = 0, lakeCount = 0;
- float totalMovementCost = 0;
- float roadBonusTotal = 0;
- foreach (var pos in route.path)
- {
- var tile = mapData.GetTile(pos.x, pos.y);
- float tileCost = GetMovementCost(pos, pos, route.routeType);
- totalMovementCost += tileCost;
- switch (tile.terrainType)
- {
- case TerrainType.Plains: plainsCount++; break;
- case TerrainType.Forest: forestCount++; break;
- case TerrainType.Mountain: mountainCount++; break;
- case TerrainType.River: riverCount++; break;
- case TerrainType.Lake: lakeCount++; break;
- }
- if (tile.featureType == FeatureType.Road)
- {
- roadCount++;
- // Calculate road bonus for this route type
- switch (route.routeType)
- {
- case RouteType.Standard:
- roadBonusTotal += GetTerrainMovementCost(tile) * (1f - 0.6f); // 40% discount
- break;
- case RouteType.RoadPreferred:
- roadBonusTotal += GetTerrainMovementCost(tile) * (1f - 0.2f); // 80% discount
- break;
- }
- }
- }
- float avgCostPerTile = totalMovementCost / route.path.Count;
- float roadPercentage = (float)roadCount / route.path.Count * 100f;
- Debug.Log($"📊 Route Analysis ({route.routeType}): {route.path.Count} tiles");
- Debug.Log($" Terrain: Plains:{plainsCount}, Forest:{forestCount}, Mountain:{mountainCount}, Rivers:{riverCount}, Lakes:{lakeCount}");
- Debug.Log($" Roads: {roadCount} tiles ({roadPercentage:F1}%), saved {roadBonusTotal:F1} cost");
- Debug.Log($" Average cost per tile: {avgCostPerTile:F2} (Total: {totalMovementCost:F1})");
- Debug.Log($" Time calculation: {route.estimatedTime:F1}h = {route.totalCost:F1} cost × 0.5 multiplier");
- // Explain why this route type might be different
- string explanation = "";
- switch (route.routeType)
- {
- case RouteType.Standard:
- explanation = "Shortest route minimizes distance and waypoints";
- break;
- case RouteType.RoadPreferred:
- explanation = $"Fastest route heavily favors roads (90% discount) - {roadPercentage:F1}% on roads";
- break;
- case RouteType.Special:
- explanation = "Cheapest/Direct route avoids special costs and takes direct paths";
- break;
- }
- Debug.Log($" Strategy: {explanation}");
- }
- /// <summary>
- /// Visualizes the selected route on the map
- /// </summary>
- private void VisualizeRoute(TravelRoute route)
- {
- if (showDebugLogs)
- {
- Debug.Log($"🎨 VisualizeRoute called with route: {route?.routeType} ({route?.path?.Count ?? 0} points)");
- }
- ClearPathVisualization();
- if (route == null || route.path.Count == 0)
- {
- if (showDebugLogs)
- {
- Debug.LogWarning("❌ Cannot visualize route: route is null or has no path points");
- }
- return;
- }
- // Create path visualization
- var pathObject = new GameObject("TravelPath");
- var lineRenderer = pathObject.AddComponent<LineRenderer>();
- // Configure line renderer with simple, reliable settings
- lineRenderer.material = pathLineMaterial ?? CreateDefaultPathMaterial();
- lineRenderer.material.color = GetRouteColor(route);
- // Force line width to 1.0 for consistent visibility
- lineRenderer.startWidth = 1.0f;
- lineRenderer.endWidth = 1.0f;
- lineRenderer.positionCount = route.path.Count;
- lineRenderer.useWorldSpace = true;
- // Simple LineRenderer settings for reliable visibility
- lineRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
- lineRenderer.receiveShadows = false;
- lineRenderer.alignment = LineAlignment.View; // Face camera
- lineRenderer.textureMode = LineTextureMode.Tile;
- lineRenderer.sortingLayerName = "Default";
- lineRenderer.sortingOrder = 50; // High sorting order but not excessive
- // Debug the width setting
- if (showDebugLogs)
- {
- Debug.Log($"🔧 Setting LineRenderer width to: {pathLineWidth} (startWidth: {lineRenderer.startWidth}, endWidth: {lineRenderer.endWidth})");
- }
- // Set path points
- for (int i = 0; i < route.path.Count; i++)
- {
- Vector3 worldPos = TileToWorldPosition(route.path[i]);
- worldPos.y = pathLineHeight; // Place above map tiles
- lineRenderer.SetPosition(i, worldPos);
- }
- currentPathVisualizations.Add(pathObject);
- if (showDebugLogs)
- {
- Debug.Log($"📍 Visualized {route.routeType} route with {route.path.Count} waypoints");
- Debug.Log($"🔧 LineRenderer config: startWidth={lineRenderer.startWidth}, endWidth={lineRenderer.endWidth}, material={lineRenderer.material?.name}, color={lineRenderer.material?.color}");
- Debug.Log($"🎯 GameObject created: {pathObject.name} at position {pathObject.transform.position}");
- }
- }
- /// <summary>
- /// Visualizes all available routes with different colors and proper layering
- /// </summary>
- private void VisualizeAllRoutes(List<TravelRoute> routes)
- {
- ClearPathVisualization();
- // Render all routes with the first one (fastest by time) selected by default
- var routesByTime = routes.OrderBy(r => r.estimatedTime).ToList();
- var defaultSelected = routesByTime.FirstOrDefault();
- for (int i = 0; i < routes.Count; i++)
- {
- var route = routes[i];
- if (route == null || route.path.Count == 0) continue;
- bool isSelected = (route == defaultSelected);
- RenderSingleRoute(route, i, isSelected);
- }
- if (showDebugLogs)
- {
- string selectedName = defaultSelected?.routeType == RouteType.RoadPreferred ? "Fastest" :
- defaultSelected?.routeType == RouteType.Standard ? "Shortest" : "Cheapest";
- Debug.Log($"🎨 Visualized {routes.Count} routes with {selectedName} as default selected (on top)");
- }
- } /// <summary>
- /// Gets the appropriate color for a route based on its type (GPS-style)
- /// </summary>
- private Color GetRouteColor(TravelRoute route)
- {
- // GPS-style color coding
- switch (route.routeType)
- {
- case RouteType.RoadPreferred: // Fastest route
- if (showDebugLogs) Debug.Log($"🎨 Route color: GREEN (fastest route)");
- return Color.green; // Green for fastest (like GPS)
- case RouteType.Standard: // Shortest route
- if (showDebugLogs) Debug.Log($"🎨 Route color: BLUE (shortest route)");
- return standardPathColor; // Blue for shortest
- case RouteType.Special: // Cheapest/Direct route
- if (showDebugLogs) Debug.Log($"🎨 Route color: YELLOW (cheapest/direct route)");
- return alternativePathColor; // Yellow for cheapest/alternative
- default:
- if (showDebugLogs) Debug.Log($"🎨 Route color: BLUE (default)");
- return standardPathColor;
- }
- }
- /// <summary>
- /// Creates a default material for path visualization
- /// </summary>
- private Material CreateDefaultPathMaterial()
- {
- // Use simple Sprites/Default shader for reliable rendering
- Shader shader = Shader.Find("Sprites/Default");
- if (shader == null)
- shader = Shader.Find("Legacy Shaders/Particles/Alpha Blended Premultiply");
- if (shader == null)
- shader = Shader.Find("Unlit/Color");
- if (shader == null)
- shader = Shader.Find("Standard");
- var material = new Material(shader ?? Shader.Find("Diffuse"));
- material.color = standardPathColor;
- if (showDebugLogs)
- {
- Debug.Log($"🎨 Created path material with shader: {shader?.name ?? "Unknown"}");
- }
- return material;
- }
- /// <summary>
- /// Clears any existing path visualization
- /// </summary>
- private void ClearPathVisualization()
- {
- foreach (var pathViz in currentPathVisualizations)
- {
- if (pathViz != null)
- {
- DestroyImmediate(pathViz);
- }
- }
- currentPathVisualizations.Clear();
- }
- /// <summary>
- /// Shows only the selected route with transparency for better marker visibility during travel
- /// </summary>
- private void ShowTravelRouteOnly(TravelRoute route)
- {
- if (route == null || route.path.Count == 0)
- {
- if (showDebugLogs)
- {
- Debug.LogWarning("❌ Cannot show travel route: route is null or has no path");
- }
- return;
- }
- // Clear all existing route visualizations
- ClearPathVisualization();
- if (showDebugLogs)
- {
- Debug.Log($"🎯 Showing only {route.routeType} route with transparency for travel");
- }
- // Create a semi-transparent visualization of the selected route
- var pathObject = new GameObject($"TravelPath_{route.routeType}_Traveling");
- var lineRenderer = pathObject.AddComponent<LineRenderer>();
- // Configure line renderer with semi-transparent material
- var baseMaterial = pathLineMaterial ?? CreateDefaultPathMaterial();
- var routeMaterial = new Material(baseMaterial);
- var routeColor = GetRouteColor(route);
- // Set to 50% transparency for better marker visibility
- routeColor.a = 0.5f;
- routeMaterial.color = routeColor;
- lineRenderer.material = routeMaterial;
- // Use a thinner line during travel to be less obtrusive
- lineRenderer.startWidth = 1.2f;
- lineRenderer.endWidth = 1.2f;
- lineRenderer.positionCount = route.path.Count;
- // Set sorting order to be below the team marker
- lineRenderer.sortingOrder = 45; // Lower than team marker
- // Set path points
- for (int i = 0; i < route.path.Count; i++)
- {
- Vector3 worldPos = TileToWorldPosition(route.path[i]);
- worldPos.y = pathLineHeight; // Keep consistent height
- lineRenderer.SetPosition(i, worldPos);
- }
- currentPathVisualizations.Add(pathObject);
- if (showDebugLogs)
- {
- Debug.Log($"✅ Travel route visualization created: {route.routeType} with 50% transparency");
- }
- }
- /// <summary>
- /// Cancels current travel planning
- /// </summary>
- public void CancelTravelPlanning()
- {
- plannedDestination = null;
- availableRoutes.Clear();
- ClearPathVisualization();
- HideTravelUI();
- if (showDebugLogs)
- {
- Debug.Log("❌ Travel planning cancelled");
- }
- }
- /// <summary>
- /// Starts travel using the specified route
- /// </summary>
- public void StartTravel(TravelRoute route)
- {
- if (route == null || isTraveling)
- {
- Debug.LogWarning("Cannot start travel: invalid route or already traveling");
- return;
- }
- // Synchronize coordinates before starting travel
- SynchronizeWithExplorationSystem();
- // Reset cancellation flag and store current route
- shouldCancelJourney = false;
- currentRoute = route;
- // Show only the selected route with transparency for better marker visibility
- ShowTravelRouteOnly(route);
- // Start the travel coroutine and store reference
- currentTravelCoroutine = StartCoroutine(ExecuteTravel(route));
- }
- /// <summary>
- /// Cancels the current journey if one is in progress
- /// </summary>
- public void CancelJourney()
- {
- if (!isTraveling)
- {
- if (showDebugLogs) Debug.Log("No journey to cancel");
- return;
- }
- if (showDebugLogs)
- {
- Debug.Log("Journey cancellation requested by player");
- }
- // Set the cancellation flag
- shouldCancelJourney = true;
- // The actual cleanup will happen in the ExecuteTravel coroutine
- }
- /// <summary>
- /// Executes the actual travel along the route
- /// </summary>
- private IEnumerator ExecuteTravel(TravelRoute route)
- {
- isTraveling = true;
- if (showDebugLogs)
- {
- Debug.Log($"🚶 Starting travel along {route.routeType} route");
- Debug.Log($"📍 Route has {route.path.Count} waypoints, estimated time: {route.estimatedTime:F1} hours");
- }
- // Ensure team marker is visible during travel (stop blinking)
- EnsureTeamMarkerVisible(true);
- // Debug: Check initial team marker state
- var initialMarker = teamPlacement.GetTeamMarker();
- if (initialMarker != null)
- {
- Debug.Log($"🔍 Initial team marker: Position={initialMarker.transform.position}, Active={initialMarker.activeInHierarchy}, WorldPos={initialMarker.transform.position}");
- var initialRenderer = initialMarker.GetComponent<Renderer>();
- if (initialRenderer != null)
- {
- Debug.Log($"🔍 Initial renderer: Enabled={initialRenderer.enabled}, Material={initialRenderer.material?.name}");
- }
- }
- else
- {
- Debug.LogError("❌ No team marker found at start of travel!");
- }
- // Deduct special costs before travel
- DeductTravelCosts(route);
- // Move team marker along the path with smooth animation
- for (int i = 1; i < route.path.Count; i++)
- {
- // Check for cancellation at the start of each step
- if (shouldCancelJourney)
- {
- if (showDebugLogs)
- {
- Debug.Log($"🛑 Journey cancelled at step {i}/{route.path.Count - 1}. Stopping at {route.path[i - 1]}");
- }
- break; // Exit the movement loop
- }
- Vector2Int nextPosition = route.path[i];
- Vector2Int currentPos = route.path[i - 1];
- if (showDebugLogs && i % 3 == 0) // Log every 3rd step
- {
- Debug.Log($"🚶 Traveling step {i}/{route.path.Count - 1} to {nextPosition} ({((float)i / (route.path.Count - 1) * 100):F0}% complete)");
- }
- // Get current and target world positions for smooth movement
- Vector3 currentWorldPos = TileToWorldPosition(currentPos);
- Vector3 targetWorldPos = TileToWorldPosition(nextPosition);
- // Animate the team marker moving smoothly between positions
- float moveTime = 0.8f; // Base time per step (slower movement)
- float stepCost = GetMovementCost(currentPos, nextPosition, route.routeType);
- // Adjust move time based on terrain difficulty
- moveTime *= Mathf.Clamp(stepCost * 0.3f, 0.5f, 3f); // Slower for difficult terrain
- // Smooth movement animation
- float elapsed = 0f;
- while (elapsed < moveTime)
- {
- // Check for cancellation during movement animation
- if (shouldCancelJourney)
- {
- if (showDebugLogs)
- {
- Debug.Log($"🛑 Journey cancelled during movement animation. Stopping at current position.");
- }
- break; // Exit the animation loop
- }
- elapsed += Time.deltaTime;
- float t = Mathf.SmoothStep(0f, 1f, elapsed / moveTime);
- // Interpolate between current and target position
- Vector3 lerpedWorldPos = Vector3.Lerp(currentWorldPos, targetWorldPos, t);
- Vector2Int lerpedTilePos = WorldToTilePosition(lerpedWorldPos);
- // Update team position smoothly (but only if it's a valid position)
- if (mapMaker.GetMapData().IsValidPosition(lerpedTilePos.x, lerpedTilePos.y))
- {
- teamPlacement.PlaceTeamAt(lerpedTilePos);
- // Ensure marker stays visible during travel
- var teamMarker = teamPlacement.GetTeamMarker();
- if (teamMarker != null)
- {
- teamMarker.SetActive(true);
- var renderer = teamMarker.GetComponent<Renderer>();
- if (renderer != null && !renderer.enabled)
- {
- renderer.enabled = true;
- if (showDebugLogs)
- {
- Debug.Log($"🔧 Re-enabled team marker renderer at {teamMarker.transform.position}");
- }
- }
- }
- }
- yield return null; // Wait one frame
- }
- // Break out of the inner animation loop if cancelled
- if (shouldCancelJourney)
- {
- break; // Exit the for loop as well
- }
- // Ensure we end up exactly at the target position (if not cancelled)
- if (!shouldCancelJourney)
- {
- teamPlacement.PlaceTeamAt(nextPosition);
- }
- }
- // Journey cleanup - whether completed or cancelled
- isTraveling = false;
- ResetTimeSpeed(); // Reset time scale to normal when travel ends
- EnsureTeamMarkerVisible(false); // Restore blinking
- ClearPathVisualization();
- HideTravelUI();
- // Synchronize coordinate systems after travel
- SynchronizeWithExplorationSystem();
- // Clear travel state
- currentTravelCoroutine = null;
- currentRoute = null;
- if (shouldCancelJourney)
- {
- if (showDebugLogs)
- {
- Debug.Log($"🛑 Journey cancelled! Team stopped at {teamPlacement.GetTeamPosition()}");
- }
- shouldCancelJourney = false; // Reset the flag
- }
- else
- {
- if (showDebugLogs)
- {
- Debug.Log($"✅ Travel completed! Arrived at {route.path.Last()}");
- }
- }
- }
- /// <summary>
- /// Synchronize travel system coordinates with exploration system
- /// </summary>
- private void SynchronizeWithExplorationSystem()
- {
- if (mapMaker != null && mapMaker.useExplorationSystem)
- {
- mapMaker.SyncTeamPosition();
- if (showDebugLogs)
- {
- Debug.Log("🔄 Synchronized team position with exploration system");
- }
- }
- }
- /// <summary>
- /// Deducts costs for travel (gold, items, etc.)
- /// </summary>
- private void DeductTravelCosts(TravelRoute route)
- {
- if (route.specialCosts.Count == 0) return;
- if (showDebugLogs)
- {
- Debug.Log($"💰 Deducting travel costs: {string.Join(", ", route.specialCosts.Select(kvp => $"{kvp.Key}: {kvp.Value}"))}");
- }
- // TODO: Implement actual resource deduction
- // This would integrate with your inventory/currency system
- foreach (var cost in route.specialCosts)
- {
- switch (cost.Key)
- {
- case "Ferry":
- // DeductGold(cost.Value);
- Debug.Log($"💰 Would deduct {cost.Value} gold for ferry");
- break;
- case "Tunnel Torch":
- // DeductTorches(cost.Value);
- Debug.Log($"🔥 Would deduct {cost.Value} torches for tunnel");
- break;
- }
- }
- }
- /// <summary>
- /// Ensures the team marker is visible during travel by controlling its blinking behavior
- /// </summary>
- private void EnsureTeamMarkerVisible(bool forceVisible)
- {
- if (teamPlacement == null)
- {
- Debug.LogWarning("❌ TeamPlacement is null in EnsureTeamMarkerVisible");
- return;
- }
- var teamMarker = teamPlacement.GetTeamMarker();
- if (teamMarker == null)
- {
- Debug.LogWarning("❌ Team marker is null - creating new marker");
- // Force creation of a new marker if it doesn't exist
- teamPlacement.PlaceTeamAt(teamPlacement.GetTeamPosition());
- teamMarker = teamPlacement.GetTeamMarker();
- if (teamMarker == null)
- {
- Debug.LogError("❌ Failed to create team marker!");
- return;
- }
- }
- var renderer = teamMarker.GetComponent<Renderer>();
- if (renderer == null)
- {
- Debug.LogWarning("❌ Team marker has no Renderer component");
- return;
- }
- Debug.Log($"🔧 Team marker status: Position={teamMarker.transform.position}, Active={teamMarker.activeInHierarchy}, RendererEnabled={renderer.enabled}");
- if (forceVisible)
- {
- // Force marker to be visible and stop blinking during travel
- teamMarker.SetActive(true);
- renderer.enabled = true;
- // Make the marker more visible by adjusting its properties
- teamMarker.transform.localScale = Vector3.one * 2f; // Make it bigger
- // Try to change material color to bright red for visibility
- var material = renderer.material;
- if (material != null)
- {
- material.color = Color.red;
- if (material.HasProperty("_BaseColor"))
- material.SetColor("_BaseColor", Color.red);
- if (material.HasProperty("_Color"))
- material.SetColor("_Color", Color.red);
- Debug.Log($"🔴 Set team marker to bright red for visibility");
- }
- // Force position to be visible above map (adjust Y for top-down view)
- var pos = teamMarker.transform.position;
- teamMarker.transform.position = new Vector3(pos.x, 2f, pos.z); // Raise above map for visibility
- // Stop any blinking coroutines in SimpleTeamPlacement using reflection
- try
- {
- var placementType = teamPlacement.GetType();
- var stopCooroutinesMethod = placementType.GetMethod("StopAllCoroutines");
- if (stopCooroutinesMethod != null)
- {
- stopCooroutinesMethod.Invoke(teamPlacement, null);
- Debug.Log("✅ Stopped blinking coroutines");
- }
- // Set isBlinking to false using reflection
- var isBlinkingField = placementType.GetField("isBlinking", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
- if (isBlinkingField != null)
- {
- isBlinkingField.SetValue(teamPlacement, false);
- Debug.Log("✅ Set isBlinking to false");
- }
- }
- catch (System.Exception e)
- {
- Debug.LogWarning($"⚠️ Reflection failed: {e.Message}");
- }
- if (showDebugLogs)
- {
- Debug.Log($"👁️ Team marker forced visible for travel at {teamMarker.transform.position}");
- }
- }
- else
- {
- // Restore normal blinking behavior after travel
- try
- {
- var placementType = teamPlacement.GetType();
- var enableBlinkingField = placementType.GetField("enableBlinking", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
- if (enableBlinkingField != null && (bool)enableBlinkingField.GetValue(teamPlacement))
- {
- // Restart blinking using reflection
- var startBlinkingMethod = placementType.GetMethod("StartBlinking", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
- if (startBlinkingMethod != null)
- {
- startBlinkingMethod.Invoke(teamPlacement, null);
- if (showDebugLogs)
- {
- Debug.Log("✨ Team marker blinking restored after travel");
- }
- }
- }
- }
- catch (System.Exception e)
- {
- Debug.LogWarning($"⚠️ Reflection failed during restore: {e.Message}");
- }
- }
- }
- /// <summary>
- /// Utility methods for coordinate conversion
- /// </summary>
- private Vector3 GetMouseWorldPosition()
- {
- if (mapCamera == null)
- {
- if (showDebugLogs) Debug.LogWarning("❌ Map camera is null, trying to find Camera.main");
- mapCamera = Camera.main;
- if (mapCamera == null) return Vector3.zero;
- }
- Ray ray = mapCamera.ScreenPointToRay(Input.mousePosition);
- if (showDebugLogs) Debug.Log($"📐 Raycast from camera: {ray.origin} direction: {ray.direction}");
- // Try raycast first (if there are colliders)
- if (Physics.Raycast(ray, out RaycastHit hit, 1000f))
- {
- if (showDebugLogs) Debug.Log($"🎯 Raycast hit: {hit.point} on {hit.collider.name}");
- return hit.point;
- }
- // Fallback: use camera plane projection at Y=0 (map level) for top-down view
- Plane mapPlane = new Plane(Vector3.up, Vector3.zero); // Y=0 plane facing up for top-down camera
- if (mapPlane.Raycast(ray, out float distance))
- {
- Vector3 point = ray.GetPoint(distance);
- if (showDebugLogs) Debug.Log($"📍 Plane intersection: {point}");
- return point;
- }
- if (showDebugLogs) Debug.LogWarning("❌ No raycast hit and no plane intersection");
- return Vector3.zero;
- }
- private Vector2Int WorldToTilePosition(Vector3 worldPos)
- {
- float tileSize = mapMaker?.mapVisualizer?.tileSize ?? 1f;
- return new Vector2Int(
- Mathf.RoundToInt(worldPos.x / tileSize),
- Mathf.RoundToInt(worldPos.z / tileSize) // Use Z coordinate for map Y in top-down view
- );
- }
- private Vector3 TileToWorldPosition(Vector2Int tilePos)
- {
- float tileSize = mapMaker?.mapVisualizer?.tileSize ?? 1f;
- return new Vector3(
- tilePos.x * tileSize,
- 0f, // Y=0 for tiles at ground level
- tilePos.y * tileSize // Map Y coordinate becomes world Z coordinate for top-down view
- );
- }
- /// <summary>
- /// Debug method to show movement cost information
- /// </summary>
- private void ShowMovementCostInfo()
- {
- Debug.Log("=== MOVEMENT COST INFO ===");
- Debug.Log("TERRAIN COSTS:");
- Debug.Log($" Plains: {plainsMovementCost}x");
- Debug.Log($" Forest: {forestMovementCost}x");
- Debug.Log($" Mountain: {mountainMovementCost}x");
- Debug.Log($" River Crossing: {riverCrossingCost}x (slow crossing)");
- Debug.Log($" Lake Swimming: {lakeCrossingCost}x (swimming)");
- Debug.Log($" Ocean Crossing: {oceanCrossingCost}x (very dangerous)");
- Debug.Log("FEATURE COSTS:");
- Debug.Log($" Road: {roadMovementCost}x");
- Debug.Log($" Town/Village: {townMovementCost}x");
- Debug.Log($" Bridge: {bridgeMovementCost}x");
- Debug.Log($" Ferry: {ferryMovementCost}x (+ {ferryBaseCost} gold)");
- Debug.Log($" Tunnel (with torch): {tunnelWithTorchCost}x (+ {tunnelTorchCost} torches)");
- Debug.Log($" Tunnel (without torch): {tunnelWithoutTorchCost}x");
- Debug.Log("CONTROLS:");
- Debug.Log(" TAB: Cycle through route options");
- Debug.Log(" ESC: Cancel travel planning (or cancel journey if traveling)");
- Debug.Log(" C: Cancel journey while traveling");
- Debug.Log(" F8: Toggle debug logs");
- Debug.Log("TRAVEL CONTROLS (while traveling):");
- Debug.Log(" >,→: Increase travel speed");
- Debug.Log(" <,←: Decrease travel speed");
- Debug.Log(" Space: Reset travel speed to normal");
- Debug.Log("========================");
- }
- /// <summary>
- /// Public getters for external systems
- /// </summary>
- public List<TravelRoute> GetAvailableRoutes() => availableRoutes;
- public Vector2Int? GetPlannedDestination() => plannedDestination;
- public bool IsTraveling() => isTraveling;
- public bool CanCancelJourney() => isTraveling && currentTravelCoroutine != null;
- public TravelRoute GetCurrentRoute() => currentRoute;
- /// <summary>
- /// Increases time speed for faster travel
- /// </summary>
- public void IncreaseTimeSpeed()
- {
- if (currentTimeSpeedIndex < timeSpeedOptions.Length - 1)
- {
- currentTimeSpeedIndex++;
- timeMultiplier = timeSpeedOptions[currentTimeSpeedIndex];
- Time.timeScale = timeMultiplier;
- Debug.Log($"⏩ Time speed increased to {timeMultiplier}x");
- }
- }
- /// <summary>
- /// Decreases time speed
- /// </summary>
- public void DecreaseTimeSpeed()
- {
- if (currentTimeSpeedIndex > 0)
- {
- currentTimeSpeedIndex--;
- timeMultiplier = timeSpeedOptions[currentTimeSpeedIndex];
- Time.timeScale = timeMultiplier;
- Debug.Log($"⏪ Time speed decreased to {timeMultiplier}x");
- }
- }
- /// <summary>
- /// Resets time speed to normal
- /// </summary>
- public void ResetTimeSpeed()
- {
- currentTimeSpeedIndex = 0;
- timeMultiplier = timeSpeedOptions[0];
- Time.timeScale = timeMultiplier;
- Debug.Log($"⏸️ Time speed reset to {timeMultiplier}x");
- }
- /// <summary>
- /// Gets current time speed multiplier
- /// </summary>
- public float GetTimeSpeed() => timeMultiplier;
- /// <summary>
- /// Methods for UI integration
- /// </summary>
- private bool IsTravelUIBlocking()
- {
- // Direct check instead of reflection since TravelUI is now available
- return travelUI != null && travelUI.IsUIBlocking();
- }
- private void ShowTravelUIIfAvailable(Vector2Int destination, List<TravelRoute> routes)
- {
- // Direct call instead of reflection since TravelUI is now available
- if (travelUI != null)
- {
- travelUI.ShowTravelUI(destination, routes);
- }
- else
- {
- Debug.LogWarning("⚠️ TravelUI not found, cannot show travel interface");
- }
- }
- public void PlanTravelWithPreferences(Vector2Int destination, bool avoidFerries, bool avoidMountainPasses, bool preferRoads)
- {
- // Store preferences and recalculate routes
- if (showDebugLogs)
- {
- Debug.Log($"🔄 Recalculating routes with preferences: AvoidFerries={avoidFerries}, AvoidMountains={avoidMountainPasses}, PreferRoads={preferRoads}");
- }
- // For now, just recalculate the same routes
- // In a full implementation, these preferences would modify the pathfinding algorithm
- PlanTravelTo(destination);
- }
- /// <summary>
- /// Forces recalculation of routes and refreshes the UI - useful for debugging or preference changes
- /// </summary>
- [ContextMenu("DEBUG: Recalculate Routes")]
- public void ForceRecalculateRoutes()
- {
- if (plannedDestination.HasValue)
- {
- Debug.Log($"🔄 TeamTravelSystem: Force recalculating routes to {plannedDestination.Value}");
- Vector2Int currentPosition = teamPlacement.GetTeamPosition();
- // Recalculate routes
- CalculateRoutes(currentPosition, plannedDestination.Value);
- // Refresh visualization
- if (availableRoutes.Count > 0)
- {
- VisualizeAllRoutes(availableRoutes);
- }
- // Refresh UI if visible
- if (travelUI != null && travelUI.IsVisible)
- {
- Debug.Log("🔄 TeamTravelSystem: Refreshing UI with recalculated routes");
- travelUI.RefreshWithNewRoutes(availableRoutes);
- }
- Debug.Log($"✅ TeamTravelSystem: Routes recalculated - {availableRoutes.Count} routes available");
- }
- else
- {
- Debug.LogWarning("⚠️ TeamTravelSystem: No planned destination to recalculate routes for");
- }
- }
- public void VisualizeSpecificRoute(TravelRoute route)
- {
- var callId = System.Guid.NewGuid().ToString("N")[..8]; // 8-character unique ID
- if (showDebugLogs)
- {
- Debug.Log($"🎯 [{callId}] VisualizeSpecificRoute called with route: {route?.routeType}, availableRoutes count: {availableRoutes?.Count ?? 0}");
- }
- if (route == null)
- {
- Debug.LogError($"❌ [{callId}] Cannot visualize null route!");
- return;
- }
- // If availableRoutes is empty, this might be because routes were cleared during UI interaction
- // In this case, we can still visualize the single route that was passed to us
- if (availableRoutes == null || availableRoutes.Count == 0)
- {
- Debug.LogWarning($"⚠️ [{callId}] availableRoutes is empty, visualizing single route {route.routeType}");
- // Temporarily add this route to the list so the visualization system can work
- if (availableRoutes == null) availableRoutes = new List<TravelRoute>();
- availableRoutes.Add(route);
- // Visualize just this route
- ClearPathVisualization();
- RenderSingleRoute(route, 0, true); // Render as selected
- if (showDebugLogs)
- {
- Debug.Log($"🎨 [{callId}] Visualized single {route.routeType} route");
- }
- return;
- }
- // Normal path when we have available routes
- // Verify the route is actually in our available routes
- bool routeExists = availableRoutes.Contains(route);
- if (!routeExists)
- {
- Debug.LogWarning($"⚠️ [{callId}] Selected route {route.routeType} is not in availableRoutes list!");
- // Try to find a matching route by type
- var matchingRoute = availableRoutes.FirstOrDefault(r => r?.routeType == route.routeType);
- if (matchingRoute != null)
- {
- Debug.Log($"🔄 [{callId}] Found matching route by type, using that instead");
- route = matchingRoute;
- }
- else
- {
- Debug.LogError($"❌ [{callId}] No matching route found, cannot visualize!");
- return;
- }
- }
- // Instead of clearing all routes, just highlight the selected one
- HighlightSpecificRoute(route);
- if (showDebugLogs)
- {
- Debug.Log($"🎨 [{callId}] Highlighted {route.routeType} route while keeping all routes visible");
- }
- }
- /// <summary>
- /// Highlights a specific route while keeping all routes visible (selected route on top)
- /// </summary>
- private void HighlightSpecificRoute(TravelRoute selectedRoute)
- {
- if (showDebugLogs)
- {
- Debug.Log($"🔍 HighlightSpecificRoute called with {selectedRoute?.routeType}, availableRoutes count: {availableRoutes.Count}");
- for (int i = 0; i < availableRoutes.Count; i++)
- {
- Debug.Log($" Route {i}: {availableRoutes[i]?.routeType} with {availableRoutes[i]?.path?.Count ?? 0} waypoints");
- }
- }
- // Validate that we have routes to work with
- if (availableRoutes == null || availableRoutes.Count == 0)
- {
- Debug.LogError("❌ HighlightSpecificRoute: No available routes to render!");
- return;
- }
- if (selectedRoute == null)
- {
- Debug.LogError("❌ HighlightSpecificRoute: Selected route is null!");
- return;
- }
- // Clear and re-render all routes with proper transparency and layering
- ClearPathVisualization();
- // Create a copy of routes to avoid any modification issues
- var routesToRender = new List<TravelRoute>(availableRoutes);
- if (showDebugLogs)
- {
- Debug.Log($"🎨 About to render {routesToRender.Count} routes");
- }
- // First render non-selected routes with transparency
- for (int i = 0; i < routesToRender.Count; i++)
- {
- var route = routesToRender[i];
- if (route == null)
- {
- Debug.LogWarning($"⚠️ Route {i} is null, skipping");
- continue;
- }
- if (route.path == null || route.path.Count == 0)
- {
- Debug.LogWarning($"⚠️ Route {i} ({route.routeType}) has no path, skipping");
- continue;
- }
- bool isSelected = (route == selectedRoute);
- if (showDebugLogs)
- {
- Debug.Log($"🎨 Rendering route {i}: {route.routeType}, selected: {isSelected}, pathCount: {route.path.Count}");
- }
- // All routes are rendered, but with different emphasis
- RenderSingleRoute(route, i, isSelected);
- }
- if (showDebugLogs)
- {
- string routeName = selectedRoute?.routeType == RouteType.RoadPreferred ? "Fastest" :
- selectedRoute?.routeType == RouteType.Standard ? "Shortest" : "Cheapest";
- Debug.Log($"🔝 Highlighted {routeName} route - rendered {currentPathVisualizations.Count} route visualizations");
- }
- }
- /// <summary>
- /// Renders a single route with specified layering
- /// </summary>
- private void RenderSingleRoute(TravelRoute route, int layerIndex, bool isSelected)
- {
- if (route == null || route.path.Count == 0)
- {
- if (showDebugLogs)
- {
- Debug.LogWarning($"⚠️ RenderSingleRoute: Cannot render route - route is null: {route == null}, path count: {route?.path?.Count ?? 0}");
- }
- return;
- }
- if (showDebugLogs)
- {
- Debug.Log($"🎨 RenderSingleRoute: Creating visualization for {route.routeType}, selected: {isSelected}, layerIndex: {layerIndex}");
- }
- // Create path visualization
- var pathObject = new GameObject($"TravelPath_{route.routeType}_{(isSelected ? "Selected" : "Normal")}");
- var lineRenderer = pathObject.AddComponent<LineRenderer>();
- if (showDebugLogs)
- {
- Debug.Log($"🔧 Created GameObject: {pathObject.name} with LineRenderer");
- }
- // Configure line renderer with a new material instance
- var baseMaterial = pathLineMaterial ?? CreateDefaultPathMaterial();
- var routeMaterial = new Material(baseMaterial);
- var routeColor = GetRouteColor(route);
- // Selected route gets full opacity, non-selected get good visibility
- if (isSelected)
- {
- routeColor.a = 1.0f; // Full opacity for selected
- }
- else
- {
- routeColor.a = 0.6f; // Good visibility for non-selected (was 0.8f)
- }
- routeMaterial.color = routeColor;
- lineRenderer.material = routeMaterial;
- // Selected route gets thicker line, non-selected get thinner
- float lineWidth = 1.0f;
- int sortingOrder = 50;
- switch (route.routeType)
- {
- case RouteType.RoadPreferred: // Fastest
- lineWidth = isSelected ? 2.2f : 1.4f; // More dramatic difference
- sortingOrder = isSelected ? 60 : 53;
- break;
- case RouteType.Standard: // Shortest
- lineWidth = isSelected ? 2.0f : 1.2f; // More dramatic difference
- sortingOrder = isSelected ? 59 : 52;
- break;
- case RouteType.Special: // Cheapest
- lineWidth = isSelected ? 1.8f : 1.0f; // More dramatic difference
- sortingOrder = isSelected ? 58 : 51;
- break;
- }
- lineRenderer.startWidth = lineWidth;
- lineRenderer.endWidth = lineWidth;
- lineRenderer.positionCount = route.path.Count;
- lineRenderer.useWorldSpace = true;
- // Enhanced settings with proper layering
- lineRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
- lineRenderer.receiveShadows = false;
- lineRenderer.alignment = LineAlignment.View;
- lineRenderer.sortingLayerName = "Default";
- lineRenderer.sortingOrder = sortingOrder;
- lineRenderer.textureMode = LineTextureMode.Tile;
- // Selected route gets priority Y-offset (higher above map)
- float zOffset = pathLineHeight + (isSelected ? 1.0f : layerIndex * 0.2f);
- // Set path points
- for (int j = 0; j < route.path.Count; j++)
- {
- // Convert map coordinates to world coordinates properly for top-down view
- Vector3 worldPos = TileToWorldPosition(route.path[j]);
- // Add slight offset for non-selected routes to prevent exact overlap
- if (!isSelected)
- {
- float offsetAmount = layerIndex * 0.02f;
- worldPos.x += Mathf.Sin(j + layerIndex) * offsetAmount;
- worldPos.z += Mathf.Cos(j + layerIndex) * offsetAmount; // Use Z for map Y offset
- }
- worldPos.y = zOffset; // Use Y for height/depth positioning
- lineRenderer.SetPosition(j, worldPos);
- }
- currentPathVisualizations.Add(pathObject);
- if (showDebugLogs)
- {
- string routeName = route.routeType == RouteType.RoadPreferred ? "Fastest" :
- route.routeType == RouteType.Standard ? "Shortest" : "Cheapest";
- Debug.Log($"✅ Successfully rendered {routeName} route: selected={isSelected}, width={lineWidth}, sorting={sortingOrder}, z-offset={zOffset}");
- Debug.Log($"📊 Total visualizations now: {currentPathVisualizations.Count}");
- }
- }
- public void HideTravelUI()
- {
- // Direct call instead of reflection since TravelUI is now available
- if (travelUI != null)
- {
- travelUI.HideTravelPanel();
- }
- }
- }
- /// <summary>
- /// Represents different types of routes with GPS-style optimization goals
- /// </summary>
- public enum RouteType
- {
- Standard, // Shortest path (minimal distance/waypoints)
- RoadPreferred, // Fastest path (heavily prefers roads and fast terrain)
- Special // Cheapest/Direct path (avoids special costs, takes more direct routes)
- }
- /// <summary>
- /// Represents a complete travel route with all associated costs and information
- /// </summary>
- [System.Serializable]
- public class TravelRoute
- {
- public RouteType routeType;
- public List<Vector2Int> path;
- public float totalCost;
- public float totalDistance;
- public float estimatedTime;
- public Dictionary<string, int> specialCosts; // Special costs like ferry fees, torch usage
- }
- /// <summary>
- /// Node used for A* pathfinding
- /// </summary>
- public class PathNode
- {
- public Vector2Int position;
- public float gCost; // Distance from starting node
- public float hCost; // Heuristic distance to target
- public float fCost => gCost + hCost;
- public PathNode parent;
- }
|