// Recursive algorithm that paints the tiles the character may move to. BLUE public void PaintPath(BattleTile current, int row, int col, int moves) { // Limits path range based on number of moves taken if (moves < 0) { return; } // Maximum range in the grid from anywhere in the grid. // Without this limit, the algorithm would continue to run needlessly if (moves > 9) { moves = 9; } // If the current tile is passed, paint it red so characters can skip turn. if (row == 0 && col == 0) { Grid[current.Column][current.Row].Highlight = "Blue"; } // Updates the new position current.Row += row; current.Column += col; // Tries to continue the path on all directions PaintPath(new BattleTile(current), 1, 0, moves - 1); PaintPath(new BattleTile(current), 0, 1, moves - 1); PaintPath(new BattleTile(current), -1, 0, moves - 1); PaintPath(new BattleTile(current), 0, -1, moves - 1); // Ensures path is within bounds if (current.Row >= 0 && current.Row < Grid[0].Count && current.Column >= 0 && current.Column < Grid.Count) { // Cannot pass by monsters but may pass by tiles and characters if (Grid[current.Column][current.Row].Type == "Monster") { return; } //Mark path if (Grid[current.Column][current.Row].Type == "Tile") { Grid[current.Column][current.Row].Highlight = "Blue"; } } }
// Recursive algorithm that paints the grid in red to indicate range to attack. // Exclusively used by Characters to indicate their range. public void PaintWarPath(BattleTile current, int row, int col, int range) { // Limits path range based on number of moves taken if (range < 0) { return; } // Maximum range in the grid from anywhere in the grid. // Without this limit, the algorithm would continue to run needlessly if (range > 9) { range = 9; } // If the current tile is passed, paint it red so characters can skip turn. if (row == 0 && col == 0) { Grid[current.Column][current.Row].Highlight = "Red"; } // Updates the new position current.Row += row; current.Column += col; // Tries to continue the path on all directions PaintWarPath(new BattleTile(current), 1, 0, range - 1); PaintWarPath(new BattleTile(current), 0, 1, range - 1); PaintWarPath(new BattleTile(current), -1, 0, range - 1); PaintWarPath(new BattleTile(current), 0, -1, range - 1); // Ensures path is within bounds if (current.Row >= 0 && current.Row < Grid[0].Count && current.Column >= 0 && current.Column < Grid.Count) { // No friendly fire if (Grid[current.Column][current.Row].Type == "Character") { return; } // Mark empty tiles to skip attack and Monsters to direct attack. if (Grid[current.Column][current.Row].Type == "Tile" || Grid[current.Column][current.Row].Type == "Monster") { Grid[current.Column][current.Row].Highlight = "Red"; } } }
// Algorithm that tries to move the monster closer to the target. public BattleTile MoveCloser(BattleTile target, BattleTile current) { TargetList = new List <BattleTile>(); BattleTile newLocation = new BattleTile(current); MoveCloser(target, new BattleTile(current), 0, 0, current.GetMove()); foreach (BattleTile t in TargetList) { // Manhattand Distance is used to select the closest tile to the target whether or not it is within range. if (ManhattanDistance(t.Row, t.Column, target.Row, target.Column) < ManhattanDistance(newLocation.Row, newLocation.Column, target.Row, target.Column)) { newLocation = t; } } // Returns the new monster location closest to the target. Might be the current location in which case the monster did not move. return(Grid[newLocation.Column][newLocation.Row]); }
// Constructor to make a new instance of a tile public BattleTile(BattleTile bt) { Type = bt.Type; if (bt.Type == "Monster") { Enemy = bt.Enemy; } if (bt.Type == "Character") { Hero = bt.Hero; } Row = bt.Row; Column = bt.Column; Highlight = bt.Highlight; Image = bt.Image; Life = bt.Life; Name = bt.Name; Settings(); }
// Recursive algorithm for pathfinding. public void MoveCloser(BattleTile target, BattleTile current, int row, int col, int moves) { // Limits path range based on number of moves taken if (moves < 0) { return; } // Moves to the next avaialable tile current.Row += row; current.Column += col; MoveCloser(target, new BattleTile(current), 1, 0, moves - 1); MoveCloser(target, new BattleTile(current), 0, 1, moves - 1); MoveCloser(target, new BattleTile(current), -1, 0, moves - 1); MoveCloser(target, new BattleTile(current), 0, -1, moves - 1); // Adds the target to the taget list for comparison after the recursion. if (current.Row >= 0 && current.Row < Grid[0].Count && current.Column >= 0 && current.Column < Grid.Count && Grid[current.Column][current.Row].Type == "Tile") { TargetList.Add(Grid[current.Column][current.Row]); } }
// Generates a turnlist by grabbing all monsters and characters in the grid then arranging them by speed. public List <BattleTile> ComputeTurnList() { // Visit every tile in the Grid to identify all potential fighters List <BattleTile> turnList = new List <BattleTile>(); for (int i = 0; i < Grid.Count; i++) { for (int j = 0; j < Grid[i].Count; j++) { // If this tile is a fighter BattleTile tempTile = Grid[i][j]; if (tempTile.Type != "Tile") { // Identify where in the turnlist this fighter will be inserted. bool insterted = false; for (var k = 0; k < turnList.Count && !insterted; k++) { // If the fighter from the grid is faster than the current fighter in the turnlist, insert the fighter if (tempTile.GetSpeed() > turnList[k].GetSpeed()) { insterted = true; turnList.Insert(k, tempTile); } // If the fighter from the grid is of equal speed as the fighter in the turnlist, compare levels else if (tempTile.GetSpeed() == turnList[k].GetSpeed()) { // If the fighter from the grid is of a higher level than the current fighter in the turnlist, insert the fighter if (tempTile.GetLevel() > turnList[k].GetLevel()) { insterted = true; turnList.Insert(k, tempTile); } // If the fighter from the grid is of equal speed as the fighter in the turnlist, compare experience else if (tempTile.GetLevel() == turnList[k].GetLevel()) { // If the fighter from the grid is of a higher experience than the current fighter in the turnlist, insert the fighter // Experience for characters is their current XP and the experience for monsters is their current ExpToGive. if (tempTile.GetExperience() > turnList[k].GetExperience()) { insterted = true; turnList.Insert(k, tempTile); } // If the fighter from the grid is of equal speed as the fighter in the turnlist, compare type else if (tempTile.GetExperience() == turnList[k].GetExperience()) { // If the fighter from the grid is a Character and the current fighter in the turnlist is a Monster, insert the fighter if (tempTile.Type == "Character" && turnList[k].Type == "Monster") { insterted = true; turnList.Insert(k, tempTile); } // If the fighter from the grid is of equal type as the fighter in the turnlist, compare name else if (tempTile.Type == turnList[k].Type) { // If the fighter from the grid does not have the same name as the current fighter in the turnlist, check alphabetical order if (tempTile.Name != turnList[k].Name) { List <string> judge = new List <string> { tempTile.Name, turnList[k].Name }; judge.Sort(); // If the fighter from the grid is first based on alphabetical order for both names, insert the fighter if (tempTile.Name == judge[0]) { insterted = true; turnList.Insert(k, tempTile); } // If the fighter from the grid is second based on alphabetical order for both names, check the next fighter. } // If they are equal then check the next fighter because this fighter came first in the list order. } } } } } // If the fighter is the slowest among the fighters in the turnlist if (!insterted) { // Insert the fighter to the end of the list. turnList.Add(tempTile); } } } } return(turnList); }
// AI for a Monster to make an attack. // Includes identifying the closest character // Moving closer to the character. Teleporting more like // Then attacking the target if within range. public string EnemyTakeTurn(int row, int column) { // Find target in the grid BattleTile target = new BattleTile(); for (int i = 0; i < Grid.Count; i++) { for (int j = 0; j < Grid[i].Count; j++) { // If a Character occupies the current grid access to determine if it is closer than current target. if (Grid[i][j].Type == "Character") { // If the current target is just a tile or empty then assign the new target. if (target.Type == "Tile") { target = Grid[i][j]; } else { // If the current target and locations are both Characters, use ManhattanDistance to determine the closest one. if (ManhattanDistance(target.Row, target.Column, row, column) > ManhattanDistance(j, i, row, column)) { target = Grid[i][j]; } } } } } string battleText = ""; // Move to target in the grid BattleTile current = new BattleTile(Grid[column][row]); BattleTile newLocation = new BattleTile(MoveCloser(target, current)); Grid[column][row] = new BattleTile(row, column); current.Row = newLocation.Row; current.Column = newLocation.Column; Grid[newLocation.Column][newLocation.Row] = current; // Cannot move any closer to the target so the Monster stays put if (row == newLocation.Row && column == newLocation.Column) { battleText += current.Name + " does not move.\n"; } // Has successfully moved closer to the target. else { battleText += current.Name + " moves to Row " + newLocation.Row + " and Column " + newLocation.Column + ".\n"; } // Try to attack target. if (ManhattanDistance(target.Row, target.Column, newLocation.Row, newLocation.Column) <= current.GetRange()) { // Damage is attempted against the Character target. int damage = target.DealtDamage(current); string status = target.DamageStatus; // Monster misses the attack. if (status == "Miss") { battleText += current.Name + " misses attack against " + target.Name + ".\n"; } // Monster hits the target. else if (status == "Hit") { battleText += current.Name + " deals " + damage + " damage to " + target.Name + ".\n"; } // Monster critically misses. else if (status == "Critical Miss") { battleText += current.Name + " CRITICALLY misses attack against " + target.Name + ".\n"; DamageStatus = "Critical Miss"; // Monster Criticaly hits. } else if (status == "Critical Hit") { battleText += current.Name + " deals " + damage + " CRITICAL damage to " + target.Name + ".\n"; DamageStatus = "Critical Hit"; } target.DamageStatus = ""; // If the target dies if (target.GetHealthCurr() <= 0) { // Automatically resurect if the MostlyDead option is turned on. if (BattleSystemViewModel.Instance.MostlyDead && target.MostlyDead) { target.MostlyDead = false; battleText += "Miracle Max pops out of a drainage pipe steps on " + target.Name + " and brings him back to life.\n"; target.Hero.HealthCurr = target.Hero.Health; target.Life = "life" + (int)(((double)(target.Hero.HealthCurr + 1) / (double)(target.Hero.Health + 1)) * 10) + ".png"; } // Otherwise the character dies and the state is saved. else { battleText += target.Name + " dies from the damage.\n"; Grid[target.Column][target.Row] = new BattleTile(target.Row, target.Column); recentlyDeceased = target; } } } // Monster is not within range of its target. else { battleText += current.Name + " does not attack. \n"; } // Returns the messages saved during combat. return(battleText); }
// Damage dealing function that works with either monster or character public int DealtDamage(BattleTile attacker) { BattleSystemViewModel.Instance.AddTxt(new StringWrapper("Damage Calculation Begins")); // Roll 20 for attack type. Random rng = new Random(); int roll = 1 + rng.Next(20); BattleSystemViewModel.Instance.AddTxt(new StringWrapper("Rolled: " + roll)); // Applies to hit Rule if (ToHit > 0) { roll = ToHit; BattleSystemViewModel.Instance.AddTxt(new StringWrapper("ToHit Applied: " + ToHit)); } // Applies Miss at roll/ToHit 1 Rule if (Miss1 && roll == 1) { BattleSystemViewModel.Instance.AddTxt(new StringWrapper("Missed")); DamageStatus = "Miss"; return(0); // Applies Critical Miss at 1 rule } else if (CriticalMiss1 && roll == 1) { DamageStatus = "Critical Miss"; BattleSystemViewModel.Instance.AddTxt(new StringWrapper("Critical Miss")); return(0); } // Applies Hit at roll/ToHit 20 Rule bool hit = false; bool criticalHit = false; bool focusedAttack = false; if (Hit20 && roll == 20) { BattleSystemViewModel.Instance.AddTxt(new StringWrapper("Hit20")); DamageStatus = "Hit"; hit = true; // Applies critical hit rule at 20 } else if (Critical20 && roll == 20) { BattleSystemViewModel.Instance.AddTxt(new StringWrapper("Critical Hit")); DamageStatus = "Critical Hit"; criticalHit = true; // Applies focused attack rule } else if (BattleSystemViewModel.Instance.FocusedAttack && BattleSystemViewModel.Instance.UseFocusedAttack) { if (attacker.Type == "Character" && attacker.Hero.UnequipAllItems().Count > 0) { BattleSystemViewModel.Instance.AddTxt(new StringWrapper("Focused Attack Applied")); DamageStatus = "Focused Attack"; FocusedAttack = true; } } BattleSystemViewModel.Instance.AddTxt(new StringWrapper("Attacker Attack: " + attacker.GetAttack())); BattleSystemViewModel.Instance.AddTxt(new StringWrapper("Attacker Level: " + attacker.GetLevel())); BattleSystemViewModel.Instance.AddTxt(new StringWrapper("Target Defense: " + GetDefense())); BattleSystemViewModel.Instance.AddTxt(new StringWrapper("Target Level: " + GetLevel())); // Hit Designation if (roll + attacker.GetLevel() + attacker.GetAttack() > GetDefense() + GetLevel() || hit || criticalHit || focusedAttack) { int damage = 0; BattleSystemViewModel.Instance.AddTxt(new StringWrapper("Hit Success")); // Level Damage int lvlDamage = (int)(attacker.GetLevel() * .25) + 1; BattleSystemViewModel.Instance.AddTxt(new StringWrapper("Level Damage: " + lvlDamage)); BattleSystemViewModel.Instance.AddTxt(new StringWrapper("Weapon Damage: " + attacker.GetDamage())); // Applies Disable RNG Rule if (DisableRNG) { damage = attacker.GetDamage() + lvlDamage + attacker.GetAttack(); BattleSystemViewModel.Instance.AddTxt(new StringWrapper("RNG Disabled damage: " + damage)); } // RNG enabled rule else { Random weaponDamage = new Random(); damage = (1 + weaponDamage.Next(attacker.GetDamage() + attacker.GetAttack())) + lvlDamage; BattleSystemViewModel.Instance.AddTxt(new StringWrapper("Resulting Damage: " + damage)); } // Critical hit doubles the damage dealt if (criticalHit) { damage = 2 * damage; BattleSystemViewModel.Instance.AddTxt(new StringWrapper("Critical Applied damage: " + damage)); } // Focused attack deals 10 times the damage dealt if (focusedAttack) { damage = 10 * damage; BattleSystemViewModel.Instance.AddTxt(new StringWrapper("Focus Attack Applied damage: " + damage)); } // Update life bar of this Combatant UpdateLifeBar(damage); BattleSystemViewModel.Instance.AddTxt(new StringWrapper("End Damage")); // Return damage delt for recording return(damage); } // Missed return(0); }