/// <summary> /// Does the square contain a player or creature? /// </summary> /// <param name="level"></param> /// <param name="location"></param> /// <returns></returns> public SquareContents MapSquareContents(int level, Point location) { SquareContents contents = new SquareContents(); //Check creature that be blocking foreach (Monster creature in monsters) { if (creature.LocationLevel == level && creature.LocationMap.x == location.x && creature.LocationMap.y == location.y) { contents.monster = creature; break; } } //Check for PC blocking if (player.LocationMap.x == location.x && player.LocationMap.y == location.y) { contents.player = player; } if (contents.monster == null && contents.player == null) { contents.empty = true; } return(contents); }
internal bool PCMove(int x, int y) { Point newPCLocation = new Point(Player.LocationMap.x + x, Player.LocationMap.y + y); if (newPCLocation.x < 0 || newPCLocation.x >= levels[player.LocationLevel].width) { return(false); } if (newPCLocation.y < 0 || newPCLocation.y >= levels[player.LocationLevel].height) { return(false); } //If this is not a valid square, return false if (!MapSquareCanBeEntered(player.LocationLevel, newPCLocation)) { return(false); } //Check for monsters in the square SquareContents contents = MapSquareContents(player.LocationLevel, newPCLocation); bool okToMoveIntoSquare = false; //If it's empty, it's OK if (contents.empty) { okToMoveIntoSquare = true; } //Monster - attack it if (contents.monster != null) { CombatResults results = player.AttackMonster(contents.monster); if (results == CombatResults.DefenderDied) { okToMoveIntoSquare = true; } } if (okToMoveIntoSquare) { player.LocationMap = newPCLocation; } return(true); }
public bool AddMonster(Monster creature, int level, Point location) { //Try to add a creature at the requested location //This may fail due to something else being there or being non-walkable try { Map creatureLevel = levels[level]; //Check square is accessable if (!MapSquareCanBeEntered(level, location)) { LogFile.Log.LogEntryDebug("AddMonster failure: Square not enterable", LogDebugLevel.Low); return(false); } //Check square has nothing else on it SquareContents contents = MapSquareContents(level, location); if (contents.monster != null) { LogFile.Log.LogEntryDebug("AddMonster failure: Monster at this square", LogDebugLevel.Low); return(false); } if (contents.player != null) { LogFile.Log.LogEntryDebug("AddMonster failure: Player at this square", LogDebugLevel.Low); return(false); } //Otherwise OK creature.LocationLevel = level; creature.LocationMap = location; monsters.Add(creature); return(true); } catch (Exception ex) { LogFile.Log.LogEntry(String.Format("AddCreature: ") + ex.Message); return(false); } }
/// <summary> /// Override the following code from the simple throwing AI to include backing away /// </summary> /// <param name="newTarget"></param> protected override void FollowAndAttack(Creature newTarget) { double range = Utility.GetDistanceBetween(this, newTarget); CreatureFOV currentFOV = Game.Dungeon.CalculateCreatureFOV(this); CreatureFOV playerFOV = Game.Dungeon.CalculateCreatureFOV(Game.Dungeon.Player); bool backAwayFromTarget = false; //Back away if we are too close & can see the target //If we can't see the target, don't back away if (range < GetMissileRange() / 2.0 && currentFOV.CheckTileFOV(newTarget.LocationMap.x, newTarget.LocationMap.y)) { //Enforce a symmetry FOV if (newTarget != Game.Dungeon.Player || (newTarget == Game.Dungeon.Player && playerFOV.CheckTileFOV(this.LocationMap))) { //Check our chance to back away. For a hard/clever creature this is 100. For a stupid creature it is 0. if (Game.Random.Next(100) < GetChanceToBackAway()) { backAwayFromTarget = true; } } } if (backAwayFromTarget && CanMove() && WillPursue()) { //Target is too close, so back away before firing int deltaX = newTarget.LocationMap.x - this.LocationMap.x; int deltaY = newTarget.LocationMap.y - this.LocationMap.y; //Find a point in the dungeon to flee to int fleeX = 0; int fleeY = 0; int counter = 0; bool relaxDirection = false; bool goodPath = false; Point nextStep = new Point(0, 0); int totalFleeLoops = GetTotalFleeLoops(); int relaxDirectionAt = RelaxDirectionAt(); do { //This performs badly when there are few escape options and you are close to the edge of the map fleeX = Game.Random.Next(Game.Dungeon.Levels[this.LocationLevel].width); fleeY = Game.Random.Next(Game.Dungeon.Levels[this.LocationLevel].height); //Relax conditions if we are having a hard time if (counter > relaxDirectionAt) { relaxDirection = true; } //Find the square to move to Pathing.PathingPermission permission = Pathing.PathingPermission.Normal; if (CanOpenDoors()) { permission = Pathing.PathingPermission.IgnoreDoors; } nextStep = Game.Dungeon.Pathing.GetPathToPoint(this.LocationLevel, this.LocationMap, new Point(fleeX, fleeY), PathingType(), permission).MonsterFinalLocation; //Check the square is pathable to if (nextStep.x == LocationMap.x && nextStep.y == LocationMap.y) { counter++; //LogFile.Log.LogEntryDebug("MonsterThrowAndRunAI: Back away fail unpathable", LogDebugLevel.Low); continue; } //Check that the next square is in a direction away from the attacker int deltaFleeX = nextStep.x - this.LocationMap.x; int deltaFleeY = nextStep.y - this.LocationMap.y; if (!relaxDirection) { if (deltaFleeX > 0 && deltaX > 0) { counter++; //LogFile.Log.LogEntryDebug("MonsterThrowAndRunAI: Back away fail direction", LogDebugLevel.Low); continue; } if (deltaFleeX < 0 && deltaX < 0) { counter++; //LogFile.Log.LogEntryDebug("MonsterThrowAndRunAI: Back away fail direction", LogDebugLevel.Low); continue; } if (deltaFleeY > 0 && deltaY > 0) { counter++; //LogFile.Log.LogEntryDebug("MonsterThrowAndRunAI: Back away fail direction", LogDebugLevel.Low); continue; } if (deltaFleeY < 0 && deltaY < 0) { counter++; //LogFile.Log.LogEntryDebug("MonsterThrowAndRunAI: Back away fail direction", LogDebugLevel.Low); continue; } } //Check the square is empty bool isEnterable = Game.Dungeon.MapSquareIsWalkable(this.LocationLevel, new Point(fleeX, fleeY)); if (!isEnterable) { counter++; //LogFile.Log.LogEntryDebug("MonsterThrowAndRunAI: Back away fail enterable", LogDebugLevel.Low); continue; } //Check the square is empty of creatures SquareContents contents = Game.Dungeon.MapSquareContents(this.LocationLevel, new Point(fleeX, fleeY)); if (contents.monster != null) { counter++; //LogFile.Log.LogEntryDebug("MonsterThrowAndRunAI: Back away fail blocked", LogDebugLevel.Low); continue; } //Check that the target is visible from the square we are fleeing to //This may prove to be too expensive CreatureFOV projectedFOV = Game.Dungeon.CalculateCreatureFOV(this, nextStep); if (!projectedFOV.CheckTileFOV(newTarget.LocationMap.x, newTarget.LocationMap.y)) { counter++; //LogFile.Log.LogEntryDebug("MonsterThrowAndRunAI: Back away fail fov", LogDebugLevel.Low); continue; } //Otherwise we found it goodPath = true; break; } while (counter < totalFleeLoops); LogFile.Log.LogEntryDebug("Back away results. Count: " + counter + " Direction at: " + relaxDirectionAt + " Total: " + totalFleeLoops, LogDebugLevel.Low); //If we found a good path, walk it if (goodPath) { MoveIntoSquare(nextStep); SetHeadingToTarget(newTarget); } else if (WillAttack()) { //If not, don't back away and attack //(target in FOV) CombatResults result; //Set heading to target SetHeadingToTarget(newTarget); if (newTarget == Game.Dungeon.Player) { result = AttackPlayer(newTarget as Player); } else { //It's a normal creature result = AttackMonster(newTarget as Monster); } //Missile animation Screen.Instance.DrawMissileAttack(this, newTarget, result, GetWeaponColor()); } } //Close enough to fire. Not backing away (either far enough away or chose not to) else if (Utility.TestRange(this, newTarget, GetMissileRange()) && WillAttack()) { //In range //Check FOV. If not in FOV, chase the player. if (!currentFOV.CheckTileFOV(newTarget.LocationMap.x, newTarget.LocationMap.y)) { ContinueChasing(newTarget); return; } //Enforce a symmetry FOV if (newTarget == Game.Dungeon.Player && !playerFOV.CheckTileFOV(this.LocationMap)) { ContinueChasing(newTarget); return; } //In preference they will use their special ability rather than fighting //If they don't have a special ability or choose not to use it will return false //Special abilities are defined in derived classes bool usingSpecial = UseSpecialAbility(); if (!usingSpecial) { //In FOV - fire at the player CombatResults result; //Set heading to target (only if we are a Pursuing creature, capable of adapting our heading) if (WillPursue()) { SetHeadingToTarget(newTarget); } if (newTarget == Game.Dungeon.Player) { result = AttackPlayer(newTarget as Player); } else { //It's a normal creature result = AttackMonster(newTarget as Monster); } //Missile animation Screen.Instance.DrawMissileAttack(this, newTarget, result, GetWeaponColor()); } } //Not in range, chase the target else { ContinueChasing(newTarget); } }
/// <summary> /// Does the square contain a player or creature? /// </summary> /// <param name="level"></param> /// <param name="location"></param> /// <returns></returns> public SquareContents MapSquareContents(int level, Point location) { SquareContents contents = new SquareContents(); //Check creature that be blocking foreach (Monster creature in monsters) { if (creature.LocationLevel == level && creature.LocationMap.x == location.x && creature.LocationMap.y == location.y) { contents.monster = creature; break; } } //Check for PC blocking if (player.LocationMap.x == location.x && player.LocationMap.y == location.y) { contents.player = player; } if (contents.monster == null && contents.player == null) contents.empty = true; return contents; }
protected override bool UseSpecialAbility() { //Check if they are going to use their special at all if (Game.Random.Next(100) > GetUseSpecialChance()) { return(false); } if (GetSpecialAIType() == SpecialAIType.Healer) { //Look for injured creatures within range List <Monster> targetsInRange = new List <Monster>(); foreach (Monster monster in Game.Dungeon.Monsters) { if (this.LocationLevel != monster.LocationLevel) { continue; } //Can't heal yourself if (monster == this) { continue; } //Don't healed charmed monsters either if (Utility.GetDistanceBetween(this, monster) < GetMissileRange() + 0.005 && !monster.Charmed) { targetsInRange.Add(monster); } } //See if any of them are injured List <Monster> injuredTargets = targetsInRange.FindAll(x => x.Hitpoints < x.MaxHitpoints); if (injuredTargets.Count == 0) { return(false); } //Pick a random monster Monster actualTarget = injuredTargets[Game.Random.Next(injuredTargets.Count)]; //Heal this monster int oldHP = actualTarget.Hitpoints; actualTarget.Hitpoints += (int)(Game.Random.Next(actualTarget.MaxHitpoints - actualTarget.Hitpoints) / 3.0); //Update msg Game.MessageQueue.AddMessage("The " + this.SingleDescription + " heals the " + actualTarget.SingleDescription); LogFile.Log.LogEntryDebug(actualTarget.SingleDescription + " hp: " + oldHP + " -> " + actualTarget.Hitpoints, LogDebugLevel.Medium); //We used this ability return(true); } else if (GetSpecialAIType() == SpecialAIType.Raiser) { //Look for a nearby corpse //Look for injured creatures within range List <Feature> corpseInRange = new List <Feature>(); foreach (Feature feature in Game.Dungeon.Features) { if (this.LocationLevel != feature.LocationLevel) { continue; } if (Utility.GetDistanceBetween(this, feature) < GetMissileRange() + 0.005) { if (feature is Features.Corpse) { corpseInRange.Add(feature); } } } if (corpseInRange.Count == 0) { return(false); } //Pick a corpse at random Feature actualCorpse = corpseInRange[Game.Random.Next(corpseInRange.Count)]; //Check this square is empty int corpseLevel = actualCorpse.LocationLevel; Point corpseMap = actualCorpse.LocationMap; SquareContents contents = Game.Dungeon.MapSquareContents(corpseLevel, corpseMap); if (!contents.empty) { return(false); } //Raise a creature here //For now just raise skeletons I think we might need to make a separate AI for each raisey creature Game.Dungeon.Features.Remove(actualCorpse); //should have a helper for this really //Spawn a skelly bool raisedSuccess = RaiseCorpse(actualCorpse.LocationLevel, actualCorpse.LocationMap); if (raisedSuccess) { Game.MessageQueue.AddMessage("The " + this.SingleDescription + " tries to raise a corpse!"); LogFile.Log.LogEntryDebug(this.SingleDescription + " raises corpse", LogDebugLevel.Medium); } return(raisedSuccess); } //Effect on player. Know we are in range if this was called else if (GetSpecialAIType() == SpecialAIType.PlayerEffecter) { //Shouldn't happen if charmed LogFile.Log.LogEntryDebug(this.SingleDescription + " attempting player effect attack", LogDebugLevel.Medium); //Player already has this effect Player player = Game.Dungeon.Player; PlayerEffect effectToUse = GetSpecialAIEffect(); if (effectToUse == null) { LogFile.Log.LogEntryDebug(this.SingleDescription + " error getting effect", LogDebugLevel.High); return(false); } //Don't do it twice if (player.IsEffectActive(effectToUse.GetType())) { return(false); } string attackStr = EffectAttackString(); Game.MessageQueue.AddMessage("The " + this.SingleDescription + " tries to " + attackStr + " you!"); //Player resistance bool playerResistance = DoPlayerResistance(); if (playerResistance == true) { Game.MessageQueue.AddMessage("You resist the attack."); return(true); } //If failed, we add our effect player.AddEffect(effectToUse); return(true); } //Spellkon player. Know we are in range if this was called else if (GetSpecialAIType() == SpecialAIType.PlayerCaster) { //Shouldn't happen if charmed LogFile.Log.LogEntryDebug(this.SingleDescription + " attempting player spell attack", LogDebugLevel.Medium); //Player already has this effect Player player = Game.Dungeon.Player; Spell effectToUse = GetSpecialAISpell(); if (effectToUse == null) { LogFile.Log.LogEntryDebug(this.SingleDescription + " error getting spell", LogDebugLevel.High); return(false); } string attackStr = EffectAttackString(); Game.MessageQueue.AddMessage("The " + this.SingleDescription + " tries to " + attackStr + " you!"); //Player resistance bool playerResistance = DoPlayerResistance(); if (playerResistance == true) { Game.MessageQueue.AddMessage("You resist the attack."); return(true); } //If failed, we add our effect effectToUse.DoSpell(player.LocationMap); return(true); } //Dragon can do a variety of things else if (GetSpecialAIType() == SpecialAIType.Dragon) { Player player = Game.Dungeon.Player; //Are we injured? If so, try to heal ourselves if (this.Hitpoints < (int)Math.Floor(this.MaxHitpoints / 2.0)) { int oldHP = this.Hitpoints; this.Hitpoints += (int)(Game.Random.Next(this.MaxHitpoints - this.Hitpoints) / 5.0); //Update msg Game.MessageQueue.AddMessage("The Dragon heals itself!"); LogFile.Log.LogEntryDebug(this.SingleDescription + " hp: " + oldHP + " -> " + this.Hitpoints, LogDebugLevel.Medium); return(true); } //If not, screw around with the player a bit //50% chance we will just attack if (Game.Random.Next(100) < 50) { return(false); } //Otherwise decide what we're going to do int taskNo = Game.Random.Next(3); if (taskNo == 0) { //Player already has this effect /* * Spell effectToUse = new Spells.Blink(); * * if (effectToUse == null) * { * LogFile.Log.LogEntryDebug(this.SingleDescription + " error getting spell", LogDebugLevel.High); * return false; * } * * Game.MessageQueue.AddMessage("The Dragon tries to teleport you!"); * * //Player resistance * bool playerResistance = DoPlayerResistance(); * * if (playerResistance == true) * { * Game.MessageQueue.AddMessage("You resist the attack."); * return true; * } * * //If failed, we add our effect * effectToUse.DoSpell(player.LocationMap); * * return true;*/ return(false); } else if (taskNo == 1) { int duration = 2 * Creature.turnTicks + Game.Random.Next(5 * Creature.turnTicks); PlayerEffects.SpeedDown speedDownEff = new RogueBasin.PlayerEffects.SpeedDown(duration, 30); if (speedDownEff == null) { LogFile.Log.LogEntryDebug(this.SingleDescription + " error getting effect", LogDebugLevel.High); return(false); } //Don't do it twice if (player.IsEffectActive(typeof(PlayerEffects.SpeedDown))) { return(false); } Game.MessageQueue.AddMessage("The Dragon tries to slow you!"); //Player resistance bool playerResistance = DoPlayerResistance(); if (playerResistance == true) { Game.MessageQueue.AddMessage("You resist the attack."); return(true); } //If failed, we add our effect player.AddEffect(speedDownEff); } else { int duration = 3 * Creature.turnTicks + Game.Random.Next(5 * Creature.turnTicks); int playerSight = Game.Dungeon.Player.SightRadius; int sightDown = playerSight - 1; PlayerEffects.SightRadiusDown sightDownEff = new RogueBasin.PlayerEffects.SightRadiusDown(duration, sightDown); if (sightDownEff == null) { LogFile.Log.LogEntryDebug(this.SingleDescription + " error getting effect", LogDebugLevel.High); return(false); } //Don't do it twice if (player.IsEffectActive(typeof(PlayerEffects.SightRadiusDown))) { return(false); } Game.MessageQueue.AddMessage("The Dragon tries to blind you!"); //Player resistance bool playerResistance = DoPlayerResistance(); if (playerResistance == true) { Game.MessageQueue.AddMessage("You resist the attack."); return(true); } //If failed, we add our effect player.AddEffect(sightDownEff); } return(true); } else { //Summoner not implemented yet return(false); } }
/// <summary> /// Run the Simple AI actions /// </summary> public override void ProcessTurn() { //If in pursuit state, continue to pursue enemy until it is dead (or creature itself is killed) [no FOV used after initial target selected] //If in randomWalk state, look for new enemies in FOV. //Closest enemy becomes new target //If no targets, move randomly Random rand = Game.Random; if (AIState == SimpleAIStates.Pursuit) { //Pursuit state, continue chasing and attacking target //Is target yet living? if (currentTarget.Alive == false) { //If not, go to non-chase state AIState = SimpleAIStates.RandomWalk; } //Is target on another level (i.e. has escaped down the stairs) else if (currentTarget.LocationLevel != this.LocationLevel) { AIState = SimpleAIStates.RandomWalk; } else { //Otherwise continue to chase ChaseCreature(currentTarget); } } if (AIState == SimpleAIStates.RandomWalk) { //RandomWalk state //Search an area of sightRadius on either side for creatures and check they are in the FOV Map currentMap = Game.Dungeon.Levels[LocationLevel]; //Get the FOV from Dungeon (this also updates the map creature FOV state) TCODFov currentFOV = Game.Dungeon.CalculateCreatureFOV(this); //currentFOV.CalculateFOV(LocationMap.x, LocationMap.y, SightRadius); //Check for other creatures within this creature's FOV int xl = LocationMap.x - SightRadius; int xr = LocationMap.x + SightRadius; int yt = LocationMap.y - SightRadius; int yb = LocationMap.y + SightRadius; //If sight is infinite, check all the map if (SightRadius == 0) { xl = 0; xr = currentMap.width; yt = 0; yb = currentMap.height; } if (xl < 0) { xl = 0; } if (xr >= currentMap.width) { xr = currentMap.width - 1; } if (yt < 0) { yt = 0; } if (yb >= currentMap.height) { yb = currentMap.height - 1; } //List will contain monsters & player List <Creature> creaturesInFOV = new List <Creature>(); foreach (Monster monster in Game.Dungeon.Monsters) { //Same monster if (monster == this) { continue; } //Not on the same level if (monster.LocationLevel != this.LocationLevel) { continue; } //Not in FOV if (!currentFOV.CheckTileFOV(monster.LocationMap.x, monster.LocationMap.y)) { continue; } //Otherwise add to list of possible targets creaturesInFOV.Add(monster); LogFile.Log.LogEntryDebug(this.Representation + " spots " + monster.Representation, LogDebugLevel.Medium); } //Check PC if (Game.Dungeon.Player.LocationLevel == this.LocationLevel) { if (currentFOV.CheckTileFOV(Game.Dungeon.Player.LocationMap.x, Game.Dungeon.Player.LocationMap.y)) { creaturesInFOV.Add(Game.Dungeon.Player); LogFile.Log.LogEntryDebug(this.Representation + " spots " + Game.Dungeon.Player.Representation, LogDebugLevel.Medium); } } //If there are possible targets, find the closest and chase it //Otherwise continue to move randomly if (creaturesInFOV.Count > 0) { //Find the closest creature Creature closestCreature = null; double closestDistance = Double.MaxValue; //a long way foreach (Creature creature in creaturesInFOV) { double distanceSq = Math.Pow(creature.LocationMap.x - this.LocationMap.x, 2) + Math.Pow(creature.LocationMap.y - this.LocationMap.y, 2); double distance = Math.Sqrt(distanceSq); if (distance < closestDistance) { closestDistance = distance; closestCreature = creature; } } //Start chasing this creature LogFile.Log.LogEntryDebug(this.Representation + " chases " + closestCreature.Representation, LogDebugLevel.Medium); ChaseCreature(closestCreature); } else { //Move randomly. If we walk into something attack it, but it does not become a new target int direction = rand.Next(9); int moveX = 0; int moveY = 0; moveX = direction / 3 - 1; moveY = direction % 3 - 1; //If we're not moving quit at this point, otherwise the target square will be the one we're in if (moveX == 0 && moveY == 0) { return; } //Check this is a valid move bool validMove = false; Point newLocation = new Point(LocationMap.x + moveX, LocationMap.y + moveY); validMove = Game.Dungeon.MapSquareCanBeEntered(LocationLevel, newLocation); //Give up if this is not a valid move if (!validMove) { return; } //Check if the square is occupied by a PC or monster SquareContents contents = Game.Dungeon.MapSquareContents(LocationLevel, newLocation); bool okToMoveIntoSquare = false; if (contents.empty) { okToMoveIntoSquare = true; } if (contents.player != null) { //Attack the player CombatResults result = AttackPlayer(contents.player); if (result == CombatResults.DefenderDied) { //Bad news for the player here! okToMoveIntoSquare = true; } } if (contents.monster != null) { //Attack the monster CombatResults result = AttackMonster(contents.monster); if (result == CombatResults.DefenderDied) { okToMoveIntoSquare = true; } } //Move if allowed if (okToMoveIntoSquare) { LocationMap = newLocation; } } } }