/// <summary> /// Returns the points in a triangular target (i.e. shotgun weapon) from origin to target. /// Only returns points within FOV. Moral: If you can see it, you can shoot it. /// fovAngle = spread of target /// </summary> /// <param name="location"></param> /// <param name="size"></param> /// <returns></returns> public List <Point> GetPointsForTriangularTargetInFOV(Point origin, Point target, Map mapLevel, int range, double fovAngle) { if (origin == target) { return(new List <Point>()); } List <Point> triangularPoints = new List <Point>(); double angle = DirectionUtil.AngleFromOriginToTarget(origin, target); for (int i = origin.x - range; i < origin.x + range; i++) { for (int j = origin.y - range; j < origin.y + range; j++) { if (new Point(i, j) == origin) { continue; } //Check for creature's FOV //If OK, check to see if it falls within a TriangularFOV (blast radius) if (i >= 0 && i < mapLevel.width && j >= 0 && j < mapLevel.height) { if (CheckTileFOV(i, j) && CreatureFOV.TriangularFOV(origin, angle, range, i, j, fovAngle)) { triangularPoints.Add(new Point(i, j)); } } } } return(triangularPoints); }
/// <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> /// Gets a target from the player. false showed an escape. otherwise target is the target selected. /// </summary> /// <param name="?"></param> /// <returns></returns> private bool GetTargetFromPlayer(Point start, out Point target, TargettingType type, int range, double spreadAngle, char confirmChar, CreatureFOV currentFOV) { //Turn targetting mode on the screen Screen.Instance.TargettingModeOn(); Screen.Instance.Target = start; Screen.Instance.TargetType = type; Screen.Instance.TargetRange = range; Screen.Instance.TargetPermissiveAngle = spreadAngle; if ((range == -1 && currentFOV.CheckTileFOV(start.x, start.y)) || Utility.TestRangeFOVForWeapon(Game.Dungeon.Player, start, range, currentFOV)) { Screen.Instance.SetTargetInRange = true; SquareContents sqC = SetViewPanelToTargetAtSquare(start); } else { Screen.Instance.SetTargetInRange = false; } Game.MessageQueue.AddMessage("Find a target. " + confirmChar + " to confirm. ESC to exit."); Screen.Instance.Update(); bool keepLooping = true; bool validFire = false; target = start; do { //Get direction from the user or 'z' to fire KeyPress userKey = Keyboard.WaitForKeyPress(true); Point direction = new Point(); KeyModifier mod = KeyModifier.Arrow; bool validDirection = false; if (GetDirectionFromKeypress(userKey, out direction, out mod)) { //Valid direction validDirection = true; } else { //Look for firing if (userKey.KeyCode == KeyCode.TCODK_CHAR) { char keyCode = (char)userKey.Character; if(keyCode == confirmChar) { validFire = true; keepLooping = false; } } if (userKey.KeyCode == KeyCode.TCODK_ESCAPE) { keepLooping = false; } } //If direction, update the location and redraw if (validDirection) { Point newPoint = new Point(target.x + direction.x, target.y + direction.y); int level = Game.Dungeon.Player.LocationLevel; if (newPoint.x < 0 || newPoint.x >= Game.Dungeon.Levels[level].width || newPoint.y < 0 || newPoint.y >= Game.Dungeon.Levels[level].height) continue; //Otherwise OK target = newPoint; if ((range == -1 && currentFOV.CheckTileFOV(newPoint.x, newPoint.y)) || Utility.TestRangeFOVForWeapon(Game.Dungeon.Player, newPoint, range, currentFOV)) { Screen.Instance.SetTargetInRange = true; SquareContents sqC = SetViewPanelToTargetAtSquare(target); } else Screen.Instance.SetTargetInRange = false; //Update screen Screen.Instance.Target = newPoint; Game.MessageQueue.AddMessage("Find a target. " + confirmChar + " to confirm. ESC to exit."); Screen.Instance.Update(); } } while (keepLooping); //Turn targetting mode off Screen.Instance.TargettingModeOff(); Screen.Instance.Update(); return validFire; }
private void CheckTargetInPlayerFOV(CreatureFOV playerFOV) { var targetCreature = Screen.Instance.CreatureToView; var targetItem = Screen.Instance.ItemToView; var targetFeature = Screen.Instance.FeatureToView; if (targetCreature == null && targetItem == null && targetFeature == null) return; if(targetCreature != null) { if(targetCreature.LocationLevel != Game.Dungeon.Player.LocationLevel) { ResetViewPanel(); return; } if (!playerFOV.CheckTileFOV(targetCreature.LocationMap)) { ResetViewPanel(); return; } } if (targetItem != null) { if (targetItem.LocationLevel != Game.Dungeon.Player.LocationLevel) { ResetViewPanel(); return; } if (!playerFOV.CheckTileFOV(targetItem.LocationMap)) { ResetViewPanel(); return; } } if (targetFeature != null) { if (targetFeature.LocationLevel != Game.Dungeon.Player.LocationLevel) { ResetViewPanel(); return; } if (!playerFOV.CheckTileFOV(targetFeature.LocationMap)) { ResetViewPanel(); return; } } }
private void TargetItemsCloseToPlayer(double range, CreatureFOV currentFOV) { Player player = Game.Dungeon.Player; Point start = player.LocationMap; var candidates = Game.Dungeon.GetNearbyCreaturesInOrderOfRange(range, currentFOV, player.LocationLevel, player.LocationMap); var candidatesInFOV = candidates.Where(c => currentFOV.CheckTileFOV(c.Item2.LocationMap)); if (candidatesInFOV.Count() == 0) { ResetViewPanel(); return; } var hostiles = candidatesInFOV.Where(c => c.Item2.InPursuit()); if(hostiles.Count() > 0) SetViewPanelToTargetAtSquare(hostiles.First().Item2.LocationMap); else SetViewPanelToTargetAtSquare(candidatesInFOV.First().Item2.LocationMap); }
/// <summary> /// Let the user target something /// </summary> /// <returns></returns> private bool TargetAttack(out Point target, int range, TargettingType targetType, double spreadAngle, char confirmChar, CreatureFOV currentFOV) { Player player = Game.Dungeon.Player; //Start on the nearest creature Creature closeCreature = Game.Dungeon.FindClosestHostileCreatureInFOV(player); if (closeCreature == null) { var allCreatures = Game.Dungeon.FindClosestCreaturesInPlayerFOV(); if (allCreatures.Any()) closeCreature = allCreatures.First(); } //If no nearby creatures, start on the player if (closeCreature == null) closeCreature = Game.Dungeon.Player; Point startPoint; if (Utility.TestRange(Game.Dungeon.Player, closeCreature, range) || range == -1) { startPoint = new Point(closeCreature.LocationMap.x, closeCreature.LocationMap.y); } else { startPoint = new Point(player.LocationMap.x, player.LocationMap.y); } /* //Get the FOV from Dungeon (this also updates the map creature FOV state) TCODFov currentFOV = Game.Dungeon.CalculateCreatureFOV(player); Point startPoint; //Is that creature in FOV if (currentFOV.CheckTileFOV(closeCreature.LocationMap.x, closeCreature.LocationMap.y)) { //If so, target startPoint = new Point(closeCreature.LocationMap.x, closeCreature.LocationMap.y); } else { //If not, target the PC startPoint = new Point(player.LocationMap.x, player.LocationMap.y); } */ //Get the desired target from the player return GetTargetFromPlayer(startPoint, out target, targetType, range, spreadAngle, confirmChar, currentFOV); }
private bool TargetAttack(out Point target, TargettingType targetType, double spreadAngle, char confirmChar, CreatureFOV currentFOV) { return TargetAttack(out target, -1, targetType, spreadAngle, confirmChar, currentFOV); }