protected override List <IUnitController> CheckHit(List <HexEntry> recentPath) { if (recentPath.Count < 2) { return(new List <IUnitController>()); } HexEntry current = recentPath[recentPath.Count - 1]; HexEntry last = recentPath[recentPath.Count - 2]; Vector2 displacement = current.BoardPos - last.BoardPos; Vector2 targetPos1 = current.BoardPos + HexVectorUtil.RotateClockwise(displacement); Vector2 targetPos2 = current.BoardPos + HexVectorUtil.RotateCounterclockwise(displacement); List <IUnitController> results = new List <IUnitController>(); if (scenarioLoader.HexGrid.ContainsKey(targetPos1)) { results.Add(scenarioLoader.HexGrid[targetPos1].SimOccupant); } if (scenarioLoader.HexGrid.ContainsKey(targetPos2)) { results.Add(scenarioLoader.HexGrid[targetPos2].SimOccupant); } return(results); }
public static List<Outcome> ChooseBestPath(List<List<HexEntry>> allPaths, HexEntry dest, IUnitController unitController, Func<List<HexEntry>, List<Outcome>> SimulatePath) { // Make sure that there actually is at least one path. var paths = allPaths.Where(x => x.Last() == dest); if (!paths.Any()) { return new List<Outcome>(); } // Get the outcomes, then preferentially order them: // 1. descending # of enemies attacked // 2. ascending # of enemies retaliating // 3. ascending ZOC cost var bestOutcomes = paths .Select(x => SimulatePath(x)) .OrderByDescending(x => (x.Last().position == dest) ? 1 : 0) // Does the unit reach the destination without dying or otherwise being prevented .ThenBy(x => x.Aggregate(0, (acc, y) => acc - y.combat .Where(z => (z.attackType == AttackType.Melee || z.attackType == AttackType.Push) && z.target.PlayerOwner != z.source.PlayerOwner).ToList().Count // Maximize the number of times the unit attacks enemies + y.combat.Where(z => (z.attackType == AttackType.Melee || z.attackType == AttackType.Push) && z.target.PlayerOwner == z.source.PlayerOwner).ToList().Count)) // Maximize the number of times the unit attacks allies .ThenBy(x => x.Aggregate(0, (acc, y) => acc + y.combat .Where(z => z.attackType == AttackType.Retal).ToList().Count)) // Minimize the number of times the unit receives retal .ThenBy(x => zoneOfControlExpense(x)) // Try not to consume the unit's movement by getting ZoCed .ThenBy(x => movePointExpense(x, unitController)); // Try to minimize mp consumption from terrain // The best one is now at the top return bestOutcomes.First(); }
// First-strike property is defined within EmptyWeapon protected override List <IUnitController> CheckHit(List <HexEntry> recentPath) { if (recentPath.Count < 2) { return(new List <IUnitController>()); } HexEntry current = recentPath[recentPath.Count - 1]; HexEntry last = recentPath[recentPath.Count - 2]; Vector2 displacement = current.BoardPos - last.BoardPos; Vector2 targetPos = displacement + current.BoardPos; if (!scenarioLoader.HexGrid.ContainsKey(targetPos)) { return(new List <IUnitController>()); } else { return(new List <IUnitController>() { scenarioLoader.HexGrid[targetPos].SimOccupant }); } }
private void MapDistancesToGoal() { HexEntry goalHex = null; foreach (HexEntry hex in scenarioLoader.HexGrid.Values) { if (hex.Terrain == Terrain.Goal) { goalHex = hex; } hex.aiDistanceToGoal = int.MaxValue; } if (goalHex == null) { Debug.Log("No goal"); return; } foreach (HexEntry hex in scenarioLoader.HexGrid.Values) { if (hex != goalHex) { hex.aiDistanceToGoal = (int)HexVectorUtil.AxialDistance(goalHex.BoardPos, hex.BoardPos); } else { hex.aiDistanceToGoal = -10; // Used because the distance is a direct weighting factor } } }
public void TravelToHex(HexEntry hex) { if (_unitSelected != null) { if (hex.Occupant == null && selectionReachableHexes.Contains(hex)) { List <Outcome> outcomes = outcomeCache[hex]; outcomeExecutor.ExecuteMoves(outcomes, _unitSelected); // Immediately update all internal values to reflect the outcomes of the move boardStateHistory.Add(CreateBoardState(outcomes)); outcomeAnimator.Interpret(outcomes); // Launch coroutine to animate move outcomes sequentially if (_unitSelected.HP <= 0) { UnitSelected = null; } else { UnitSelected = _unitSelected; } } else { UnitSelected = null; } } else { Debug.Log("No unit selected"); } }
public void HidePathToHex(HexEntry hex) { if (_unitSelected != null) { outcomeVisualizer.Revert(); } }
public void ShowPathToHex(HexEntry hex) { if (_unitSelected != null) { List <Outcome> outcomes; if (outcomeCache.ContainsKey(hex)) { outcomes = outcomeCache[hex]; } else { outcomes = _unitSelected.Mover.MoveOutcomes(hex); outcomeCache.Add(hex, outcomes); } if (outcomes.Count > 0 && outcomes[outcomes.Count - 1].position == hex) { _unitSelected.SpriteManager.ShowExtraInfo(UnitSpriteManager.UnitInfoDisplaySource.Selection, true, hex); } else { _unitSelected.SpriteManager.ShowExtraInfo(UnitSpriteManager.UnitInfoDisplaySource.Selection, true);// Show at true unit location } outcomeVisualizer.Interpret(outcomes, _unitSelected.Position); } }
public List <Outcome> SimulatePush(IUnitController target, Vector2 pushDirection, int pushAmount) { List <Outcome> outcomes = new List <Outcome>(); //outcomes.Add(new Outcome(target, target.SimPosition)); // Add current hex for interpreter convenience if (pushAmount > 0) { for (int i = 1; i <= pushAmount; i++) { if (!scenarioLoader.HexGrid.ContainsKey(pushDirection + target.SimPosition.BoardPos)) { break; } HexEntry nextHex = scenarioLoader.HexGrid[pushDirection + target.SimPosition.BoardPos]; if (nextHex.SimOccupant != null) { break; } if (UnitBaseStats.TerrainCost(target.UnitType, nextHex.Terrain) == 0) { break; } target.SimPosition = nextHex; List <AttackResult> combat = target.Weapon.CombatResults(target.SimRecentPath); outcomes.Add(new Outcome(target, nextHex, false, combat)); } } return(outcomes); }
private void LoadGrid() { string mapName; if (Vaults.mapName != null) { mapName = Vaults.mapName; } else { mapName = Config.defaultMapName; } TextAsset map = (TextAsset)Resources.Load(Config.mapLocation + mapName, typeof(TextAsset)); string[] hexDescriptions = map.text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); foreach (string description in hexDescriptions) { try { //Parse file input string desc; string q = description.Substring(0, description.IndexOf(',')); desc = description.Substring(description.IndexOf(',') + 1, description.Length - description.IndexOf(',') - 1); string r = desc.Substring(0, desc.IndexOf(',')); desc = desc.Substring(desc.IndexOf(',') + 1, desc.Length - desc.IndexOf(',') - 1); string t = desc; int qVal; int rVal; qVal = int.Parse(q); rVal = int.Parse(r); Terrain terrain = (Terrain)Enum.Parse(typeof(Terrain), t); //Instantiate in-game hexes Vector2 hexCoords = new Vector2(qVal, rVal); Vector2 worldCoords = HexVectorUtil.worldPositionOfHexCoord(hexCoords); GameObject hex = Instantiate(basicHex, new Vector3(worldCoords.x, worldCoords.y, 1), Quaternion.identity); hex.transform.SetParent(hexContainer); HexEntry newHex = new HexEntry(hex, hexCoords, terrain); HexGrid.Add(hexCoords, newHex); hexesByUniqueID.Add(newHex); //Text stuff: GameObject hexText = Instantiate(textPrefab, Vector2.Scale(worldCoords, Config.worldToCanvasCoordScaler), Quaternion.identity); hexText.GetComponent <UnityEngine.UI.Text>().text = hexCoords.ToString(); //hexText.GetComponent<UnityEngine.UI.Text>().text = "[_ _]"; hexText.transform.SetParent(worldCanvas.transform, false); newHex.debugText = hexText.GetComponent <UnityEngine.UI.Text>(); if (terrain == Terrain.Goal) { turnsRemainingText.transform.position = new Vector3(worldCoords.x, worldCoords.y + 0.23f, 0); turnsRemainingText.gameObject.SetActive(true); } } catch { } } }
//ZoC utility function public static bool IsEnemyNeighbor(HexEntry hex, int owner) { foreach (HexEntry neighborHex in hex.Neighbors.Values) { if (neighborHex.Occupant != null && neighborHex.Occupant.PlayerOwner != owner) return true; } return false; }
private void OrientArrow(HexEntry start, HexEntry dest, GameObject arrow) { Vector2 startWorldPos = HexVectorUtil.worldPositionOfHexCoord(start.BoardPos); Vector2 destWorldPos = HexVectorUtil.worldPositionOfHexCoord(dest.BoardPos); arrow.transform.position = new Vector3(startWorldPos.x, startWorldPos.y, -3) + (Config.hitArrowPosScaler) * Vector3.Normalize(destWorldPos - startWorldPos); arrow.transform.rotation = Quaternion.FromToRotation(Vector2.right, destWorldPos - startWorldPos); }
private void OrientSegment(HexEntry start, HexEntry dest, GameObject segment) { Vector2 startWorldPos = HexVectorUtil.worldPositionOfHexCoord(start.BoardPos); Vector2 destWorldPos = HexVectorUtil.worldPositionOfHexCoord(dest.BoardPos); segment.transform.position = new Vector3(startWorldPos.x, startWorldPos.y, 2); segment.transform.rotation = Quaternion.FromToRotation(Vector2.right, destWorldPos - startWorldPos); }
public void CreateFriendlyFireWarning(HexEntry hex) { GameObject newFFWarning = Instantiate(friendlyFireWarning, HexVectorUtil.worldPositionOfHexCoord(hex.BoardPos), Quaternion.identity); newFFWarning.GetComponent <SpriteRenderer>().color = Config.Palette.attack; newFFWarning.transform.SetParent(hitArrowContainer.transform); ffWarnings.Add(newFFWarning); }
public void CreateHitArrow(HexEntry start, HexEntry dest) { GameObject newArrow = Instantiate(hitArrow, Vector3.zero, Quaternion.identity); newArrow.GetComponent <SpriteRenderer>().color = Config.Palette.pathArrow; newArrow.transform.SetParent(hitArrowContainer.transform); hitArrows.Add(newArrow); OrientArrow(start, dest, newArrow); }
public UnitState(IUnitController unitController, HexEntry pos, List <HexEntry> recentPath, int movesRemaining, int zoCMovesRemaining, int hp, int shots) { this._unitController = unitController; this.pos = pos; this.recentPath = recentPath; this.movesRemaining = movesRemaining; this.zoCMovesRemaining = zoCMovesRemaining; this.hp = hp; this.shots = shots; }
public void HexMouseEnter(HexEntry hex) { hoveredHex = hex; if (inputEnabled.hexHover) { hex.HexManager.FaintBackground(); selectionManager.ShowPathToHex(hex); } }
public AttackResult(IUnitController target, IUnitController source, int healthRemaining, HexEntry sourceHex, HexEntry targetHex, AttackType attackType, List <Outcome> pushMoves = null) { this.target = target; this.source = source; this.healthRemaining = healthRemaining; this.sourceHex = sourceHex; this.targetHex = targetHex; this.attackType = attackType; this.pushMoves = pushMoves; }
public Outcome(SelectionManager selectionManager, ScenarioLoader scenarioLoader, SZOutcome toCopy) { activeUnit = selectionManager.GetUnitByID(toCopy.activeUnit); position = scenarioLoader.GetHexByID(toCopy.position); spendingMoves = toCopy.spendingMoves; combat = new List <AttackResult>(); foreach (SZAttackResult attackResult in toCopy.combat) { combat.Add(new AttackResult(selectionManager, scenarioLoader, attackResult)); } }
public static List <List <HexEntry> > FindAllPaths(HexEntry current, int movesRemaining, IUnitController controller) { // We will fill this with paths we find List <List <HexEntry> > allPaths = new List <List <HexEntry> >(); // This will be used after every run of the outer loop to process the most recently found paths List <List <HexEntry> > pathsFound = new List <List <HexEntry> >(); List <List <HexEntry> > newPathsFound = new List <List <HexEntry> >(); // Used to keep track of hexes we don't want our new paths to go back to. HashSet <HexEntry> closed = new HashSet <HexEntry>(); // Initially, we already know how to get to the origin List <HexEntry> originPath = new List <HexEntry>(); originPath.Add(current); // Add the origin path and update allPaths pathsFound.Add(originPath); allPaths = allPaths.Concat(pathsFound).ToList(); // We've considered all paths that go through the origin: close it. closed.Add(current); // At each step, consider paths of length i (not including the origin--that would be i + 1) for (var i = 1; i <= movesRemaining; i++) { // This is a set of the hexes that surround the perimeter of closed. HashSet <HexEntry> borderHexes = FindBorderHexes(pathsFound); // Now we add them to closed. foreach (HexEntry hex in borderHexes) { closed.Add(hex); } // After each call to AugmentPath, newPathsFound will have a new path in it. for (var j = 0; j < pathsFound.Count; j++) { List <HexEntry> path = pathsFound[j]; AugmentPath(path, newPathsFound, closed, controller, movesRemaining); } // For the next round, make the new paths found the "old" paths found, and make a new newPathsFound allPaths = new List <List <HexEntry> >(allPaths.Concat(newPathsFound)); pathsFound = newPathsFound; newPathsFound = new List <List <HexEntry> >(); } return(allPaths); }
public void HexMouseExit(HexEntry hex) { if (hoveredHex == hex) { hoveredHex = null; } hex.HexManager.FullBackground(); if (inputEnabled.hexHover) { selectionManager.HidePathToHex(hex); } }
//Returns null if not neighbors public static Bearing?NeighborsToBearing(HexEntry start, HexEntry end) { foreach (Bearing b in Enum.GetValues(typeof(Bearing))) { if (start.Neighbors.ContainsKey(b)) { if (start.Neighbors[b] == end) { return(b); } } } return(null); }
public Outcome(IUnitController activeUnit, HexEntry position, bool spendingMoves, List <AttackResult> combat = null) { this.activeUnit = activeUnit; this.position = position; this.spendingMoves = spendingMoves; if (combat == null) { this.combat = new List <AttackResult>(); } else { this.combat = combat; } }
public void SetPath(List <Outcome> pathInput, HexEntry currentHex) { for (int i = 0; i < pathInput.Count; i++) { GameObject newSegment = Instantiate(pathSegment, Vector3.zero, Quaternion.identity); newSegment.GetComponent <SpriteRenderer>().color = Config.Palette.pathFootstep; newSegment.transform.SetParent(pathSegmentContainer.transform); pathSegments.Add(newSegment); if (i == 0) { OrientSegment(currentHex, pathInput[i].position, newSegment); } else { OrientSegment(pathInput[i - 1].position, pathInput[i].position, newSegment); } } }
private void MarkEdge(IUnitController unit, HexEntry hex, Bearing b) { Vector2 cornerSpot = new Vector2(); Vector2 rotationVec = new Vector2(); switch (b) { case Bearing.E: cornerSpot = FindCorner(hex, -30); rotationVec = Vector2.down; break; case Bearing.NNE: cornerSpot = FindCorner(hex, -90); rotationVec = new Vector2(1.732f, -1); break; case Bearing.NNW: cornerSpot = FindCorner(hex, -150); rotationVec = new Vector2(1.732f, 1); break; case Bearing.W: cornerSpot = FindCorner(hex, -210); rotationVec = Vector2.up; break; case Bearing.SSW: cornerSpot = FindCorner(hex, -270); rotationVec = new Vector2(-1.732f, 1); break; case Bearing.SSE: cornerSpot = FindCorner(hex, -330); rotationVec = new Vector2(-1.732f, -1); break; } GameObject segment = Instantiate(rangeIndicator, new Vector3(cornerSpot.x, cornerSpot.y, 3), Quaternion.FromToRotation(Vector2.right, rotationVec)); segment.transform.SetParent(rangeIndicatorContainer.transform); segment.GetComponent <SpriteRenderer>().color = Config.Palette.PlayerColor(unit.PlayerOwner); rangeIndicatorSegments[unit].Add(segment); }
// Tell each HexEntry about the HexEntries that are immediately adjacent public void ConnectNeighbors() { foreach (KeyValuePair <Vector2, HexEntry> entry in HexGrid) { Vector2 loc = entry.Key; HexEntry hexEntry = entry.Value; IDictionary <Bearing, HexEntry> neighbors = new Dictionary <Bearing, HexEntry>(); foreach (Bearing b in Enum.GetValues(typeof(Bearing))) { Vector2 neighborLoc = loc + HexVectorUtil.NeighborOffsetFromBearing(b); if (HexGrid.ContainsKey(neighborLoc)) { neighbors.Add(b, HexGrid[neighborLoc]); } } hexEntry.SetNeighbors(neighbors); } }
public override int CheckRetal(List <HexEntry> recentPath, IUnitController enemy) { if (recentPath.Count < 2 || enemy.PlayerOwner == unitController.PlayerOwner) { return(0); } HexEntry current = recentPath[recentPath.Count - 1]; HexEntry last = recentPath[recentPath.Count - 2]; if (HexVectorUtil.AxialDistance(current.BoardPos, unitController.SimPosition.BoardPos) == 3) { if (HexVectorUtil.AxialDistance(last.BoardPos, unitController.SimPosition.BoardPos) > 3) { return(unitController.DamageOutput); } } return(0); }
public void ShowRangeIndicator(IUnitController unit, HexEntry location, int range) { if (location == null) { return; } if (!rangeIndicatorSegments.ContainsKey(unit)) { rangeIndicatorSegments[unit] = new List <GameObject>(); } List <HexEntry> hexesInRange = new List <HexEntry>(); for (int dx = -range; dx <= range; dx++) { for (int dy = Mathf.Max(-range, -dx - range); dy <= Mathf.Min(range, -dx + range); dy++) { Vector2 hexPosition = new Vector2((int)location.BoardPos.x + dx, (int)location.BoardPos.y - dx - dy); if (scenarioLoader.HexGrid.ContainsKey(hexPosition)) { hexesInRange.Add(scenarioLoader.HexGrid[hexPosition]); } } } foreach (HexEntry h in hexesInRange) { foreach (Bearing b in Enum.GetValues(typeof(Bearing))) { if (!h.Neighbors.ContainsKey(b)) { MarkEdge(unit, h, b); } else if (!hexesInRange.Contains(h.Neighbors[b])) { MarkEdge(unit, h, b); } } } }
public void Interpret(List <Outcome> outcomes, HexEntry footstepStart = null) { if (footstepStart != null) { pathVisualizer.SetPath(outcomes, footstepStart); } foreach (Outcome outcome in outcomes) { foreach (AttackResult attackResult in outcome.combat) { if (attackResult.attackType != AttackType.Push) { attackResult.targetHex.HexManager.BorderColor = Config.Palette.attack; } else { attackResult.targetHex.HexManager.BorderColor = Config.Palette.pushAttack; attackResult.targetHex.HexManager.gameObject.transform.position = new Vector3(attackResult.targetHex.HexManager.gameObject.transform.position.x, attackResult.targetHex.HexManager.gameObject.transform.position.y, 0); // Make sure push attack borders go behind damaging attack borders } redHexes.Add(attackResult.targetHex); pathVisualizer.CreateHitArrow(attackResult.sourceHex, attackResult.targetHex); attackResult.source.SpriteManager.ShowExtraInfo(UnitSpriteManager.UnitInfoDisplaySource.CombatVisualization, true); rangeIndicators.Add(attackResult.source); if (attackResult.source.PlayerOwner == attackResult.target.PlayerOwner) { pathVisualizer.CreateFriendlyFireWarning(outcome.position); } if (attackResult.pushMoves != null) { Interpret(attackResult.pushMoves); } } } }
public void Initialize(IMover mover, OutcomeAnimator outcomeAnimator, UnitSpriteManager unitSpriteManager, HexEntry startPosition) { this._mover = mover; this._outcomeAnimator = outcomeAnimator; this._unitSpriteManager = unitSpriteManager; RecentPath = new List <HexEntry>(); Position = startPosition; unitSpriteManager.VisiblePosition = Position; HP = UnitBaseStats.HP(UnitType); SpriteManager.MaxHealthDisplay = HP; // Instantiate the health bar sizing for the unit, maxHP must be first SpriteManager.CurrentHealthDisplay = HP; TurnEndUpkeep(); TurnStartUpkeep(); UnitType = _unitType; // Gives spritemanager unit-type data WeaponType = _weaponType; PlayerOwner = _playerOwner; }
public AttackResult(SelectionManager selectionManager, ScenarioLoader scenarioLoader, SZAttackResult toCopy) { target = selectionManager.GetUnitByID(toCopy.target); source = selectionManager.GetUnitByID(toCopy.source); healthRemaining = toCopy.healthRemaining; sourceHex = scenarioLoader.GetHexByID(toCopy.sourceHex); targetHex = scenarioLoader.GetHexByID(toCopy.targetHex); attackType = toCopy.attackType; if (toCopy.pushMoves == null) { pushMoves = null; } else { pushMoves = new List <Outcome>(); foreach (SZOutcome outcome in toCopy.pushMoves) { pushMoves.Add(new Outcome(selectionManager, scenarioLoader, outcome)); } } }