/// <summary> /// Determines the absolute position of this position for a formation facing this direction. /// For instance, for a facing of west, the front right position would be located in the northwest. /// </summary> /// <param name="facing"></param> /// <returns></returns> public AbsolutePosition AsAbsolute(Direction facing) { if (facing == Direction.North) { return(AbsolutePosition.FromCoordinates(Column, Row)); } if (facing == Direction.South) { return(AbsolutePosition.FromCoordinates(2 - Column, 2 - Row)); } if (facing == Direction.East) { return(AbsolutePosition.FromCoordinates(2 - Row, Column)); } if (facing == Direction.West) { return(AbsolutePosition.FromCoordinates(Row, 2 - Column)); } throw new ArgumentException("Invalid facing specified, not north/south/east/west"); }
private void Fight(Tile attackerTile, Tile defenderTile) { var attackers = attackerTile.Formation; var defenders = defenderTile.Formation; if (attackers == null || defenders == null) { return; } int tileSize = 3; foreach (var kvp in attackers.CreaturePositions) { // who is attacking now? var attacker = attackers.CreaturePositions[kvp.Key]; // is attacker blocked behind another attacker? then he can't attack bool blocked = false; RelativePosition pos = kvp.Key; while (true) { var apos = pos.AsAbsolute(attackers.Facing); apos = AbsolutePosition.FromCoordinates(apos.Column + attackers.Facing.DeltaX, apos.Row + attackers.Facing.DeltaY); if (apos == null) { break; } pos = apos.RelativeTo(attackers.Facing); if (pos != kvp.Key && attackers.CreaturePositions.ContainsKey(pos)) { Log.Append("The " + attacker.Name + " is blocked by the " + attackers.CreaturePositions[pos].Name + "."); blocked = true; break; } } if (blocked) { continue; } // find nearest target Creature defender = null; int distance; var absPos = kvp.Key.AsAbsolute(attackers.Facing); AbsolutePosition targetPos; if (attackers.Facing == Direction.North) { distance = absPos.Row; targetPos = AbsolutePosition.FromCoordinates(absPos.Column, tileSize - 1); } else if (attackers.Facing == Direction.South) { distance = tileSize - 1 - absPos.Row; targetPos = AbsolutePosition.FromCoordinates(absPos.Column, 0); } else if (attackers.Facing == Direction.East) { distance = tileSize - 1 - absPos.Column; targetPos = AbsolutePosition.FromCoordinates(0, absPos.Column); } else if (attackers.Facing == Direction.West) { distance = absPos.Column; targetPos = AbsolutePosition.FromCoordinates(tileSize - 1, absPos.Column); } else { throw new Exception("Invalid formation facing, not north/south/east/west"); } foreach (var distanceGroup in AbsolutePosition.All.GroupBy(pos2 => targetPos.DistanceTo(pos2))) { var matches = distanceGroup.Where(pos2 => { var relpos = pos2.RelativeTo(defenders.Facing); return(defenders.CreaturePositions.ContainsKey(relpos) && defenders.CreaturePositions[relpos] != null); }); if (matches.Any()) { // multiple targets at same distance? pick one at random! defender = defenders.CreaturePositions[matches.Pick().RelativeTo(defenders.Facing)]; distance += distanceGroup.Key; break; } } // attaaaaack! if (defender == null) { Log.Append("The " + attacker.Name + " has no one to attack."); // everyone's already dead! } else { var attack = attacker.Attack; var defense = defender.Defense; var atkBody = attacker.Body; var atkMind = attacker.Mind; var defBody = defender.Body; var defMind = defender.Mind; int atkStat, defStat, range; string atkText; if (atkBody - defBody >= atkMind - defMind) { // use physical attack atkStat = atkBody; defStat = defBody; atkText = attacker.PhysicalAttackText; range = attacker.PhysicalAttackRange; } else { // use mental attack atkStat = atkMind; defStat = defMind; atkText = attacker.MentalAttackText; range = attacker.MentalAttackRange; } var color = defenders == Heroes ? Color.Yellow : Color.White; // accuracy penalty: 1/3 chance to miss for each subtile out of range bool miss = false; for (int i = 0; i < distance - range; i++) { if (Dice.Range(1, 3) == 1) { miss = true; break; } } string msg; if (miss) { color = Color.Gray; msg = "The " + attacker.Name + " " + atkText + " the " + defender.Name + ", but misses (attack range " + range + ", target distance " + distance + ")."; } else { var atkRoll = Dice.Roll(attack, atkStat); var defRoll = Dice.Roll(defense, defStat); var damage = Math.Max(0, atkRoll - defRoll); msg = "The " + attacker.Name + " " + atkText + " the " + defender.Name + " (" + attack + "d" + atkStat + " vs. " + defense + "d" + defStat + ") for " + damage + " damage."; defender.Health -= damage; if (defender.Health <= 0) { msg += " The " + defender.Name + " is slain!"; if (defenders == Heroes) { color = Color.Red; } foreach (var p in RelativePosition.All) { if (defenders.CreaturePositions.ContainsKey(p) && defenders.CreaturePositions[p] == defender) { KillCreature(defenderTile, p); // he's dead, Jim... } } if (!defenders.CreaturePositions.Any()) { defenderTile.Formation = null; // they're all dead, Dave... } } } Log.Append(msg, color); } } }