/// <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 StatsBox FindStatsBox(AbsolutePosition pos) { if (pos == AbsolutePosition.Northwest) { return(sbNW); } if (pos == AbsolutePosition.North) { return(sbN); } if (pos == AbsolutePosition.Northeast) { return(sbNE); } if (pos == AbsolutePosition.West) { return(sbW); } if (pos == AbsolutePosition.Center) { return(sbC); } if (pos == AbsolutePosition.East) { return(sbE); } if (pos == AbsolutePosition.Southwest) { return(sbSW); } if (pos == AbsolutePosition.South) { return(sbS); } if (pos == AbsolutePosition.Southeast) { return(sbSE); } throw new Exception("Invalid position specified for FindStatsBox"); }
public Color GetColor(AbsolutePosition pos) { if (Formation == null) { if (Items.ContainsKey(pos)) { return(Items[pos].Color); } return(Terrain.Color); } var creature = Formation.GetCreature(pos); if (creature == null) { if (Items.ContainsKey(pos)) { return(Items[pos].Color); } return(Terrain.Color); } return(creature.Color); }
public char GetSymbol(AbsolutePosition pos) { if (Formation == null) { if (Items.ContainsKey(pos)) { return(Items[pos].Symbol); } return(Terrain.Symbol); } var creature = Formation.GetCreature(pos); if (creature == null) { if (Items.ContainsKey(pos)) { return(Items[pos].Symbol); } return(Terrain.Symbol); } return(creature.Symbol); }
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); } } }
/// <summary> /// Computes Manhattan distance from this position to the target. /// </summary> /// <param name="target"></param> /// <returns></returns> public int DistanceTo(AbsolutePosition target) { return(Math.Abs(Column - target.Column) + Math.Abs(Row - target.Row)); }
public Creature GetCreature(AbsolutePosition pos) { return(GetCreature(pos.RelativeTo(Facing))); }
private void DrawSubtile(Tile tile, Graphics g, Font font, int border, int x, int y, AbsolutePosition pos) { var charSize = font.Size; g.DrawString(tile.GetSymbol(pos).ToString(), font, GetBrush(tile.GetColor(pos)), new PointF(x + charSize * (pos.Column + border), y + charSize * (pos.Row + border))); }
private void MainForm_KeyDown(object sender, KeyEventArgs e) { if (heroesDead) { return; // no doing stuff when you're dead! } if (heroBeingPlaced >= 0) { if (e.KeyCode == Keys.F) { heroBeingPlaced = -1; // cancel Log.Append("Never mind, then."); } else { // place the hero var pos = AbsolutePosition.FromKey(e.KeyCode); if (pos == null) { Log.Append("Use the numeric keypad to place the " + map.Heroes.CreaturePositions.Values.ElementAt(heroBeingPlaced).Name + " or press F to cancel."); } else { if (newHeroPositions.Keys.Contains(pos)) { Log.Append("The " + newHeroPositions[pos].Name + " is already in the " + pos.Name + ". Try again or press F to cancel."); } else { // set placement newHeroPositions[pos] = map.Heroes.CreaturePositions.Values.ElementAt(heroBeingPlaced); // move on to next hero, or be done if last hero was placed heroBeingPlaced++; if (heroBeingPlaced >= map.Heroes.CreaturePositions.Values.Count) { map.Heroes.CreaturePositions.Clear(); foreach (var kvp in newHeroPositions) { map.Heroes.CreaturePositions.Add(kvp.Key.RelativeTo(map.Heroes.Facing), kvp.Value); } heroBeingPlaced = -1; Log.Append("The heroes rearrange their formation."); map.Heroes.Act(map.Tiles[map.HeroX, map.HeroY].Terrain.MovementCost); map.LetMonstersAct(); } else { Log.Append("Please choose a position for the " + map.Heroes.CreaturePositions.Values.ElementAt(heroBeingPlaced).Name + "."); } } } } } else if (mode == 's') { if (heroDoingStuff != null) { if (e.KeyCode == Keys.S) { // cancel mode = ' '; heroDoingStuff = null; Log.Append("Never mind, then."); } else { // pick skill var skillIdx = GetNumberFromKey(e.KeyCode) - 1; var skillToUse = heroDoingStuff.Skills.ElementAtOrDefault(skillIdx); if (skillToUse == null) { // prompt for skill to use Log.Append("What skill will the " + heroDoingStuff.Name + " use? (press S again to cancel)"); int skillNum = 0; foreach (var skill in heroDoingStuff.Skills) { skillNum++; Log.Append(skillNum + ": " + skill.Name + " (" + skill.ManaCost + " mana) - " + skill.Description); } } else if (skillToUse.ManaCost > heroDoingStuff.Mana) { // not enough mana, cancel Log.Append("The " + heroDoingStuff.Name + " lacks the mana for " + skillToUse.Name + "!"); mode = ' '; heroDoingStuff = null; Log.Append("Never mind, then."); } else { // use skill Log.Append("The " + heroDoingStuff.Name + " " + skillToUse.Verb + " " + skillToUse.Name + "!"); int x = map.HeroX + map.Heroes.Facing.DeltaX; int y = map.HeroY + map.Heroes.Facing.DeltaY; Tile target = null; if (map.CoordsInBounds(x, y)) { target = map.Tiles[x, y]; } skillToUse.Use(heroDoingStuff, map.Heroes, target, map); // spend mana heroDoingStuff.Mana -= skillToUse.ManaCost; // use time map.Heroes.Act(map.Tiles[map.HeroX, map.HeroY].Terrain.MovementCost); map.LetMonstersAct(); // done using skill mode = ' '; heroDoingStuff = null; } } } else { if (e.KeyCode == Keys.S) { // cancel mode = ' '; heroDoingStuff = null; Log.Append("Never mind, then."); } else { // pick hero var heroIdx = GetNumberFromKey(e.KeyCode) - 1; heroDoingStuff = map.Heroes.CreaturePositions.Values.ElementAtOrDefault(heroIdx); if (heroDoingStuff == null) { // didn't pick a hero Log.Append("Who will use a skill? (press S again to cancel)"); int heroNum = 0; foreach (var hero in map.Heroes.CreaturePositions.Values) { heroNum++; Log.Append(heroNum + ": " + hero.Name, hero.Color); } } else { // prompt for skill to use Log.Append("What skill will the " + heroDoingStuff.Name + " use? (press S again to cancel)"); int skillNum = 0; foreach (var skill in heroDoingStuff.Skills) { skillNum++; Log.Append(skillNum + ": " + skill.Name + " (" + skill.ManaCost + " mana) - " + skill.Description); } } } } } else if (mode == 'w') { if (heroDoingStuff != null) { if (e.KeyCode == Keys.W) { // cancel mode = ' '; heroDoingStuff = null; Log.Append("Never mind, then."); } else { // pick weapon var idx = GetNumberFromKey(e.KeyCode) - 1; var available = Weapon.All.Where(w => w.Hero == heroDoingStuff && w.HasBeenFound); var weapon = available.ElementAtOrDefault(idx); if (weapon == null) { if (!available.Any()) { // nothing to equip Log.Append("The " + heroDoingStuff.Name + " doesn't have any weapons to equip!"); mode = ' '; heroDoingStuff = null; } else { // prompt for weapon to equip Log.Append("What weapon will the " + heroDoingStuff.Name + " equip? (press W again to cancel)"); int num = 0; foreach (var w in available) { num++; Log.Append(num + ": " + w.Name + " (" + w.Description + ")"); } } } else { if (heroDoingStuff.Weapon == weapon) { // nothing to do Log.Append("But the " + heroDoingStuff.Name + " has already equipped the " + weapon.Name + "!"); mode = ' '; heroDoingStuff = null; } else { // equip weapon Log.Append("The " + heroDoingStuff.Name + " equips the " + weapon.Name + "."); heroDoingStuff.Weapon = weapon; // use time map.Heroes.Act(map.Tiles[map.HeroX, map.HeroY].Terrain.MovementCost); map.LetMonstersAct(); // done equipping mode = ' '; heroDoingStuff = null; } } } } else { if (e.KeyCode == Keys.W) { // cancel mode = ' '; heroDoingStuff = null; Log.Append("Never mind, then."); } else { // pick hero var heroIdx = GetNumberFromKey(e.KeyCode) - 1; heroDoingStuff = map.Heroes.CreaturePositions.Values.ElementAtOrDefault(heroIdx); if (heroDoingStuff == null) { // didn't pick a hero Log.Append("Who will equip a weapon? (press W again to cancel)"); int heroNum = 0; foreach (var hero in map.Heroes.CreaturePositions.Values) { heroNum++; Log.Append(heroNum + ": " + hero.Name, hero.Color); } } else { var available = Weapon.All.Where(w => w.Hero == heroDoingStuff && w.HasBeenFound); if (!available.Any()) { // nothing to equip Log.Append("The " + heroDoingStuff.Name + " doesn't have any weapons to equip!"); mode = ' '; heroDoingStuff = null; } else { // prompt for weapon to equip Log.Append("What weapon will the " + heroDoingStuff.Name + " equip? (press W again to cancel)"); int num = 0; foreach (var w in available) { num++; Log.Append(num + ": " + w.Name + " (" + w.Description + ")"); } } } } } } else if (mode == 'a') { if (heroDoingStuff != null) { if (e.KeyCode == Keys.A) { // cancel mode = ' '; heroDoingStuff = null; Log.Append("Never mind, then."); } else { // pick armor var idx = GetNumberFromKey(e.KeyCode) - 1; var available = Armor.All.Where(a => a.Hero == heroDoingStuff && a.HasBeenFound); var armor = available.ElementAtOrDefault(idx); if (armor == null) { if (!available.Any()) { // nothing to equip Log.Append("The " + heroDoingStuff.Name + " doesn't have any armor to equip!"); mode = ' '; heroDoingStuff = null; } else { // prompt for armor to equip Log.Append("What armor will the " + heroDoingStuff.Name + " equip? (press A again to cancel)"); int num = 0; foreach (var a in available) { num++; Log.Append(num + ": " + a.Name + " (" + a.Description + ")"); } } } else { if (heroDoingStuff.Armor == armor) { // nothing to do Log.Append("But the " + heroDoingStuff.Name + " has already equipped the " + armor.Name + "!"); mode = ' '; heroDoingStuff = null; } else { // equip armor Log.Append("The " + heroDoingStuff.Name + " equips the " + armor.Name + "."); heroDoingStuff.Armor = armor; // use time map.Heroes.Act(map.Tiles[map.HeroX, map.HeroY].Terrain.MovementCost); map.LetMonstersAct(); // done equipping mode = ' '; heroDoingStuff = null; } } } } else { if (e.KeyCode == Keys.A) { // cancel mode = ' '; heroDoingStuff = null; Log.Append("Never mind, then."); } else { // pick hero var heroIdx = GetNumberFromKey(e.KeyCode) - 1; heroDoingStuff = map.Heroes.CreaturePositions.Values.ElementAtOrDefault(heroIdx); if (heroDoingStuff == null) { // didn't pick a hero Log.Append("Who will equip armor? (press A again to cancel)"); int heroNum = 0; foreach (var hero in map.Heroes.CreaturePositions.Values) { heroNum++; Log.Append(heroNum + ": " + hero.Name, hero.Color); } } else { var available = Armor.All.Where(a => a.Hero == heroDoingStuff && a.HasBeenFound); if (!available.Any()) { // nothing to equip Log.Append("The " + heroDoingStuff.Name + " doesn't have any armor to equip!"); mode = ' '; heroDoingStuff = null; } else { // prompt for armor to equip Log.Append("What armor will the " + heroDoingStuff.Name + " equip? (press A again to cancel)"); int num = 0; foreach (var a in available) { num++; Log.Append(num + ": " + a.Name + " (" + a.Description + ")"); } } } } } } else { if (e.KeyCode == Keys.F) { // F: set formation heroBeingPlaced = 0; newHeroPositions = new Dictionary <AbsolutePosition, Creature>(); Log.Append("Please choose a position for the " + map.Heroes.CreaturePositions.Values.ElementAt(heroBeingPlaced).Name + "."); } else if (e.KeyCode == Keys.S) { // S: use skill mode = 's'; Log.Append("Who will use a skill? (press S again to cancel)"); int heroNum = 0; foreach (var hero in map.Heroes.CreaturePositions.Values) { heroNum++; Log.Append(heroNum + ": " + hero.Name, hero.Color); } } else if (e.KeyCode == Keys.W) { // W: change weapon mode = 'w'; Log.Append("Who will change his weapon? (press W again to cancel)"); int heroNum = 0; foreach (var hero in map.Heroes.CreaturePositions.Values) { heroNum++; Log.Append(heroNum + ": " + hero.Name, hero.Color); } } else if (e.KeyCode == Keys.A) { // W: change weapon mode = 'a'; Log.Append("Who will change his armor? (press A again to cancel)"); int heroNum = 0; foreach (var hero in map.Heroes.CreaturePositions.Values) { heroNum++; Log.Append(heroNum + ": " + hero.Name, hero.Color); } } else if (e.KeyCode == Keys.D5 || e.KeyCode == Keys.NumPad5 || e.KeyCode == Keys.Clear) { // wait a turn map.Heroes.Act(map.Tiles[map.HeroX, map.HeroY].Terrain.MovementCost); map.LetMonstersAct(); } else { var dir = Direction.FromKey(e.KeyCode); if (dir != null) { // movement if (ModifierKeys.HasFlag(Keys.Shift)) { // shift-arrow moves without rotating (strafe move) map.Move(map.Heroes, dir); } else { // just plain arrow attempts to rotate instead of move if not facing direction of movement map.MoveOrTurn(map.Heroes, dir); } map.LetMonstersAct(); if (!map.CoordsInBounds(map.HeroX, map.HeroY)) { heroesDead = true; Log.Append("Oh no! The heroes have fallen...", Color.Red); } } } } // see if final boss is dead if (!winner && !map.Tiles.Cast <Tile>().Any(t => t.Formation != null && t.Formation.CreaturePositions.Values.Any(c => c.Name == MonsterTemplate.ChaosLord.Archetype.Name))) { Log.Append("Congratulations! The Chaos Lord has been vanquished! The brave heroes can now retire to a life of fame... or they can continue slaying the foul minions that still plague the land!", Color.Magenta); winner = true; Text = "TriQuest - *WINNER*"; } // see if all monsters are dead if (map.Tiles.Cast <Tile>().All(t => t.Formation == null || t.Formation == map.Heroes)) { Log.Append("Wow! You're really dedicated! You actually managed to slay every last monster! Double congratulations!!! Now go take a well-deserved rest!", Color.Magenta); omnicide = true; Text = "TriQuest - +*OMNICIDE*+"; } // refresh screen picMap.Invalidate(); picMinimap.Invalidate(); BindStatsBoxes(map.Heroes); RefreshLog(); }