/// <summary> /// This uses an overriden location for the creature /// </summary> /// <param name="creature"></param> /// <param name="fov"></param> /// <param name="creatureFOVType"></param> /// <param name="locationX"></param> /// <param name="locationY"></param> public CreatureFOV(Creature creature, WrappedFOV fov, CreatureFOVType creatureFOVType, Point overrideLocation) { this.fov = fov; this.type = creatureFOVType; this.creature = creature; this.overrideLocation = overrideLocation; }
internal PathingResult GetPathToCreature(Creature originCreature, Creature destCreature, PathingType type, PathingPermission permission) { //If on different levels it's an error if (originCreature.LocationLevel != destCreature.LocationLevel) { string msg = originCreature.Representation + " not on the same level as " + destCreature.Representation; LogFile.Log.LogEntry(msg); throw new ApplicationException(msg); } return GetPathToPoint(originCreature.LocationLevel, originCreature.LocationMap, destCreature.LocationMap, type, permission); }
/// <summary> /// Cast a spell. Returns if time passes. /// </summary> /// <returns></returns> private bool SelectAndCastSpell() { Dungeon dungeon = Game.Dungeon; Player player = Game.Dungeon.Player; //No casting in town or wilderness /* if (player.LocationLevel < 2) { Game.MessageQueue.AddMessage("You want to save your spells for the dungeons."); LogFile.Log.LogEntryDebug("Attempted to cast spell outside of dungeon", LogDebugLevel.Low); return false; }*/ //Get the user's selection Spell toCast = SelectSpell(); //User exited if (toCast == null) return false; //Get a target if needed Point target = new Point(); bool targettingSuccess = true; if (toCast.NeedsTarget()) { //Find spell range int range = toCast.GetRange(); TargettingType targetType = toCast.TargetType(); //Calculate FOV CreatureFOV currentFOV = Game.Dungeon.CalculateCreatureFOV(Game.Dungeon.Player); targettingSuccess = TargetAttack(out target, range, targetType, 0, 'z', currentFOV); } //User exited if (!targettingSuccess) return false; bool success = Game.Dungeon.Player.CastSpell(toCast, target); //Store details for a recast //If we successfully cast, store the target if (success) { //Only do this for certain spells if (toCast.GetType() != typeof(Spells.MagicMissile) && toCast.GetType() != typeof(Spells.FireLance) && toCast.GetType() != typeof(Spells.FireBall) && toCast.GetType() != typeof(Spells.EnergyBlast)) return success; lastSpell = toCast; //Spell target is the creature (monster or PC) SquareContents squareContents = dungeon.MapSquareContents(player.LocationLevel, target); //Is there a creature here? If so, store if (squareContents.monster != null) lastSpellTarget = squareContents.monster; if (squareContents.player != null) lastSpellTarget = squareContents.player; } else { //Failure store the spell anyway lastSpell = toCast; } //Time only goes past if successfully cast return success; }
/// <summary> /// Returns the direction to go in (+-xy) for the next step towards the target /// If there's no route at all, return -1, -1. Right now we throw an exception for this, since it shouldn't happen in a connected dungeon /// If there's a route but its blocked by a creature return the originCreature's coords /// </summary> /// <param name="originCreature"></param> /// <param name="destCreature"></param> /// <returns></returns> internal Point GetPathTo(Creature originCreature, Creature destCreature) { //If on different levels it's an error if (originCreature.LocationLevel != destCreature.LocationLevel) { string msg = originCreature.Representation + " not on the same level as " + destCreature.Representation; LogFile.Log.LogEntry(msg); throw new ApplicationException(msg); } //Destination square needs to be walkable for the path finding algorithm. However it isn't walkable at the moment since there is the target creature on it //Temporarily make it walkable, keeping transparency the same //levelTCODMaps[destCreature.LocationLevel].SetCell(destCreature.LocationMap.x, destCreature.LocationMap.y, // !levels[destCreature.LocationLevel].mapSquares[destCreature.LocationMap.x, destCreature.LocationMap.y].BlocksLight, true); //Try to walk the path //If we fail, check if this square occupied by a creature //If so, make that square temporarily unwalkable and try to re-route List<Point> blockedSquares = new List<Point>(); bool goodPath = false; bool pathBlockedByCreature = false; Point nextStep = new Point(-1, -1); do { //Generate path object TCODPathFinding path = new TCODPathFinding(levelTCODMaps[originCreature.LocationLevel], 1.0); path.ComputePath(originCreature.LocationMap.x, originCreature.LocationMap.y, destCreature.LocationMap.x, destCreature.LocationMap.y); //Find the first step. We need to load x and y with the origin of the path int x, y; int xOrigin, yOrigin; path.GetPathOrigin(out x, out y); xOrigin = x; yOrigin = y; path.WalkPath(ref x, ref y, false); //If the x and y of the next step it means the path is blocked if (x == xOrigin && y == yOrigin) { //If there was no blocking creature then there is no possible route (hopefully impossible in a fully connected dungeon) if (!pathBlockedByCreature) { throw new ApplicationException("Path blocked in connected dungeon!"); /* nextStep = new Point(x, y); bool trans; bool walkable; levelTCODMaps[0].GetCell(originCreature.LocationMap.x, originCreature.LocationMap.y, out trans, out walkable); levelTCODMaps[0].GetCell(destCreature.LocationMap.x, destCreature.LocationMap.y, out trans, out walkable); */ //Uncomment this if you want to return -1, -1 //nextStep = new Point(-1, -1); //goodPath = true; //continue; } else { //Blocking creature but no path nextStep = new Point(x, y); goodPath = true; continue; } } //Check if that square is occupied Creature blockingCreature = null; foreach (Monster creature in monsters) { if (creature.LocationLevel != originCreature.LocationLevel) continue; //Is it the source creature itself? if (creature == originCreature) continue; //Is it the target creature? if (creature == destCreature) continue; //Another creature is blocking if (creature.LocationMap.x == x && creature.LocationMap.y == y) { blockingCreature = creature; } } //Do the same for the player (if the creature is chasing another creature around the player) if (destCreature != Player) { if (Player.LocationLevel == originCreature.LocationLevel && Player.LocationMap.x == x && Player.LocationMap.y == y) { blockingCreature = Player; } } //If no blocking creature, the path is good if (blockingCreature == null) { goodPath = true; nextStep = new Point(x, y); path.Dispose(); } else { //Otherwise, there's a blocking creature. Make his square unwalkable temporarily and try to reroute pathBlockedByCreature = true; int blockingLevel = blockingCreature.LocationLevel; int blockingX = blockingCreature.LocationMap.x; int blockingY = blockingCreature.LocationMap.y; levelTCODMaps[blockingLevel].SetCell(blockingX, blockingY, !levels[blockingLevel].mapSquares[blockingX, blockingY].BlocksLight, false); //Add this square to a list of squares to put back blockedSquares.Add(new Point(blockingX, blockingY)); //Dispose the old path path.Dispose(); //We will try again } } while (!goodPath); //Put back any squares we made unwalkable foreach (Point sq in blockedSquares) { levelTCODMaps[originCreature.LocationLevel].SetCell(sq.x, sq.y, !levels[originCreature.LocationLevel].mapSquares[sq.x, sq.y].BlocksLight, true); } //path.WalkPath(ref x, ref y, false); //path.GetPointOnPath(0, out x, out y); //crashes for some reason //Dispose of path (bit wasteful seeming!) //path.Dispose(); //Set the destination square as unwalkable again //levelTCODMaps[destCreature.LocationLevel].SetCell(destCreature.LocationMap.x, destCreature.LocationMap.y, // !levels[destCreature.LocationLevel].mapSquares[destCreature.LocationMap.x, destCreature.LocationMap.y].BlocksLight, false); //Point nextStep = new Point(x, y); return nextStep; }
/// <summary> /// Calculates the FOV for a creature /// </summary> /// <param name="creature"></param> public TCODFov CalculateCreatureFOV(Creature creature) { Map currentMap = levels[creature.LocationLevel]; TCODFov tcodFOV = levelTCODMaps[creature.LocationLevel]; //Update FOV tcodFOV.CalculateFOV(creature.LocationMap.x, creature.LocationMap.y, creature.SightRadius); return tcodFOV; }
public virtual bool OnDrop(Creature droppingCreature) { return false; }
public override bool OnPickup(Creature pickupCreature) { return Use(pickupCreature); }
/// <summary> /// Add cohort monsters close to a unique. Spawns new copies of the cohort monster type passed in (does not use the passed object) /// </summary> /// <param name="monsterType"></param> /// <param name="noMonsters"></param> /// <param name="masterMonser"></param> /// <param name="minDistance"></param> /// <param name="levelNo"></param> /// <returns></returns> private bool AddMonstersCloseToMaster(Monster monsterType, int noMonsters, Creature masterMonser, int minDistance, int levelNo) { Point location; int outerLoopCount = 0; for (int i = 0; i < noMonsters; i++) { do { int loopCount = 0; do { location = Game.Dungeon.RandomWalkablePointInLevel(i); loopCount++; } while (Utility.GetDistanceBetween(masterMonser, location) > minDistance && loopCount < maxLoopCount); outerLoopCount++; } while (!Game.Dungeon.AddMonster(NewMonsterOfType(monsterType), levelNo, location) && outerLoopCount < 50); } //Failed to add monster if (outerLoopCount == 50) { LogFile.Log.LogEntryDebug("Failed to place a monster near master", LogDebugLevel.Medium); return false; } return true; }
/// <summary> /// We have been hit by creature /// </summary> /// <param name="creature"></param> public abstract void NotifyHitByCreature(Creature creature, int damage);
/// <summary> /// On death, null our currentTarget. This is a good idea anyway, but looks like it was added for an important reason 'circular references' /// </summary> public override void NotifyMonsterDeath() { currentTarget = null; currentTargetID = -1; }
/// <summary> /// We've been hit. Have a chance of recovering if we are fleeing /// </summary> /// <param name="creature"></param> /// <param name="damage"></param> public override void NotifyHitByCreature(Creature creature, int damage) { RecoverOnBeingHit(); }
/// <summary> /// A creature has attacked us (possibly from out of our view range). Don't just sit there passively /// </summary> public override void NotifyAttackByCreature(Creature creature) { AIState = SimpleAIStates.Pursuit; currentTarget = creature; currentTargetID = creature.UniqueID; }
double GetDistance(Creature creature1, Creature creature2) { double distanceSq = Math.Pow(creature1.LocationMap.x - creature2.LocationMap.x, 2) + Math.Pow(creature1.LocationMap.y - creature2.LocationMap.y, 2); double distance = Math.Sqrt(distanceSq); return distance; }
private void ChaseCreature(Creature newTarget) { //Confirm this as current target currentTarget = newTarget; currentTargetID = newTarget.UniqueID; //Go into pursuit mode //AIState = SimpleAIStates.Pursuit; //If the creature is badly damaged they may flee int maxHitPointsWillFlee = GetMaxHPWillFlee(); int chanceToRecover = GetChanceToRecover(); // out of 100 int chanceToFlee = GetChanceToFlee(); // out of 100 //Are we fleeing already if (AIState == SimpleAIStates.Fleeing) { //Do we recover? if (Game.Random.Next(100) < chanceToRecover) { AIState = SimpleAIStates.Pursuit; LogFile.Log.LogEntryDebug(this.Representation + " recovered", LogDebugLevel.Medium); } } else { //Only not-charmed creatures will flee if (!Charmed) { //Check if we want to flee. Only recheck after we've been injured again if (Hitpoints <= maxHitPointsWillFlee && Hitpoints < lastHitpoints) { if (Game.Random.Next(100) < chanceToFlee) { AIState = SimpleAIStates.Fleeing; LogFile.Log.LogEntryDebug(this.Representation + " fleeing", LogDebugLevel.Medium); } } } } lastHitpoints = Hitpoints; if (AIState == SimpleAIStates.Fleeing) { //Flee code, same as ThrowAndRunAI 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; Pathing.PathingResult pathingResult; Point nextStep = LocationMap; int totalFleeLoops = GetTotalFleeLoops(); int relaxDirectionAt = RelaxDirectionAt(); do { 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; //Check these are in the direction away from the attacker int deltaFleeX = fleeX - this.LocationMap.x; int deltaFleeY = fleeY - this.LocationMap.y; if (!relaxDirection) { if (deltaFleeX > 0 && deltaX > 0) { counter++; continue; } if (deltaFleeX < 0 && deltaX < 0) { counter++; continue; } if (deltaFleeY > 0 && deltaY > 0) { counter++; continue; } if (deltaFleeY < 0 && deltaY < 0) { counter++; continue; } } //Check the square is empty bool isEnterable = Game.Dungeon.MapSquareIsWalkable(this.LocationLevel, new Point(fleeX, fleeY)); if (!isEnterable) { counter++; continue; } //Check the square is empty of creatures SquareContents contents = Game.Dungeon.MapSquareContents(this.LocationLevel, new Point(fleeX, fleeY)); if (contents.monster != null) { counter++; continue; } //Check the square is pathable to Pathing.PathingPermission permission = Pathing.PathingPermission.Normal; if(CanOpenDoors()) permission = Pathing.PathingPermission.IgnoreDoors; pathingResult = Game.Dungeon.Pathing.GetPathToPoint(this.LocationLevel, this.LocationMap, new Point(fleeX, fleeY), PathingType(), permission); if (pathingResult.TerminallyBlocked) { counter++; continue; } //Otherwise we found it goodPath = true; nextStep = pathingResult.MonsterFinalLocation; break; } while (counter < totalFleeLoops); //If we found a good path, walk it if (goodPath) { SetHeadingToMapSquare(nextStep); MoveIntoSquare(nextStep); } else { //No good place to flee, attack instead FollowAndAttack(newTarget); } } //Not fleeing else { //If charmed creatures get too far away chasing they will come back if (Charmed) { //Calculate distance between PC and creature if (Game.Dungeon.Player.LocationLevel == this.LocationLevel) { double distanceSq = Math.Pow(Game.Dungeon.Player.LocationMap.x - this.LocationMap.x, 2) + Math.Pow(Game.Dungeon.Player.LocationMap.y - this.LocationMap.y, 2); double distance = Math.Sqrt(distanceSq); if (distance > maxChaseDistance) { LogFile.Log.LogEntryDebug(this.SingleDescription + " returns to PC", LogDebugLevel.Low); AIState = SimpleAIStates.Returning; FollowPC(); } } } //Not charmed - pursue and attack FollowAndAttack(newTarget); } }
/// <summary> /// Call after updating LocationMap. Aim at the target /// </summary> protected void SetHeadingToTarget(Creature newTarget) { Heading = DirectionUtil.AngleFromOriginToTarget(this.LocationMap, newTarget.LocationMap); }
/// <summary> /// Fire weapon. Returns if time passes. /// </summary> /// <returns></returns> private bool FireWeapon() { Dungeon dungeon = Game.Dungeon; Player player = Game.Dungeon.Player; //Check we have a fireable weapon IEquippableItem weapon = player.GetEquippedWeapon(); Item weaponI = player.GetEquippedWeaponAsItem(); if (weapon == null || !weapon.HasFireAction()) { Game.MessageQueue.AddMessage("Need a weapon that can fire."); return false; } Point target = new Point(); bool targettingSuccess = true; //Find weapon range int range = weapon.RangeFire(); TargettingType targetType = weapon.TargetTypeFire(); double spreadAngle = weapon.ShotgunSpreadAngle(); //Calculate FOV CreatureFOV currentFOV = Game.Dungeon.CalculateCreatureFOV(Game.Dungeon.Player); targettingSuccess = TargetAttack(out target, range, targetType, spreadAngle, 'f', currentFOV); //User exited if (!targettingSuccess) return false; if (target.x == player.LocationMap.x && target.y == player.LocationMap.y) { Game.MessageQueue.AddMessage("Can't target self with " + weaponI.SingleItemDescription + "."); LogFile.Log.LogEntryDebug("Can't target self with " + weaponI.SingleItemDescription, LogDebugLevel.Medium); return false; } //Check ammo if (weapon.RemainingAmmo() < 1) { Game.MessageQueue.AddMessage("Not enough ammo for " + weaponI.SingleItemDescription); LogFile.Log.LogEntryDebug("Not enough ammo for " + weaponI.SingleItemDescription, LogDebugLevel.Medium); return false; } //Check we are in range of target (not done above) if (!Utility.TestRangeFOVForWeapon(Game.Dungeon.Player, target, range, currentFOV)) { Game.MessageQueue.AddMessage("Out of range!"); LogFile.Log.LogEntryDebug("Out of range for " + weaponI.SingleItemDescription, LogDebugLevel.Medium); return false; } //Actually do firing action bool success = weapon.FireItem(target); if (success) { RemoveEffectsDueToFiringWeapon(player); } //Store details for a recast //If we successfully cast, store the target if (success) { //Spell target is the creature (monster or PC) SquareContents squareContents = dungeon.MapSquareContents(player.LocationLevel, target); //Is there a creature here? If so, store if (squareContents.monster != null) lastSpellTarget = squareContents.monster; if (squareContents.player != null) lastSpellTarget = squareContents.player; } //Time only goes past if successfully cast return success; }
/// <summary> /// We have be attacked (but not necessarily damaged by) creature /// </summary> /// <param name="creature"></param> public abstract void NotifyAttackByCreature(Creature creature);
/// <summary> /// Main loop called on each turn when we move /// </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] //TODO: add forget mode? Random rand = Game.Random; Point startOfTurnLocation = LocationMap; //RESTORE STATE AFTER SAVE //Creature references may be circular, and will crash serialization, so an index is used instead //Restore currentTarget reference from ID, in case we have reloaded if (currentTargetID == -1) { currentTarget = null; } else { currentTarget = Game.Dungeon.GetCreatureByUniqueID(currentTargetID); } //Restore lastAttackedByFromID if (LastAttackedByID == -1) { LastAttackedBy = null; } else { LastAttackedBy = Game.Dungeon.GetCreatureByUniqueID(LastAttackedByID); } //Restore sound if (CurrentSoundID == -1) { currentSound = null; } else { currentSound = Game.Dungeon.GetSoundByID(CurrentSoundID); if (currentSound == null) { ResetFollowingSound(); AIState = SimpleAIStates.Patrol; LogFile.Log.LogEntryDebug("Error restoring sound, resetting", LogDebugLevel.High); } } //Stunned creatures miss turns if (StunnedTurns > 0) { StunnedTurns--; LogFile.Log.LogEntryDebug(this.Representation + " is stunned for " + StunnedTurns + " more turns", LogDebugLevel.Low); ResetTurnsMoving(); return; } //TEST SLEEPING CREATURES //Sleeping is a Creature state that is used like an AI state //This is OK since we exit immediately //Creatures which sleep until seen (i.e. for ease of processing, not game effects) if (Sleeping && WakesOnBeingSeen()) { //Check to see if we should wake by looking for woken creatures in POV //(when we drop through currentFOV may be unnecessarily recalculated) CreatureFOV currentFOV = Game.Dungeon.CalculateCreatureFOV(Game.Dungeon.Player); //Player sees monster, wake up if (currentFOV.CheckTileFOV(LocationMap.x, LocationMap.y)) { Sleeping = false; AIState = SimpleAIStates.Patrol; LogFile.Log.LogEntryDebug(this.Representation + " spotted by player so wakes", LogDebugLevel.Low); } } //Sleeping creatures don't react until they see a woken creature if (Sleeping && WakesOnSight()) { //Check to see if we should wake by looking for woken creatures in POV //(when we drop through currentFOV may be unnecessarily recalculated) CreatureFOV currentFOV = Game.Dungeon.CalculateCreatureFOV(this); 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 in FOV //Check if it's awake. If so, wake up and stop if (!monster.Sleeping) { Sleeping = false; AIState = SimpleAIStates.Patrol; LogFile.Log.LogEntryDebug(this.Representation + " spots awake " + monster.Representation + " and wakes", LogDebugLevel.Low); break; } } //Check if we can see the player if (Game.Dungeon.Player.LocationLevel == this.LocationLevel && currentFOV.CheckTileFOV(Game.Dungeon.Player.LocationMap.x, Game.Dungeon.Player.LocationMap.y) && !Game.Dungeon.Player.isStealthed()) { //In FOV wake Sleeping = false; AIState = SimpleAIStates.Patrol; LogFile.Log.LogEntryDebug(this.Representation + " spots player and wakes", LogDebugLevel.Low); } } //If we're still sleeping then skip this go if (Sleeping) { ResetTurnsMoving(); return; } //RETURNING - used when a charmed creature gets a long way from the PC if (AIState == SimpleAIStates.Returning) { //Don't stop on an attack otherwise charmed creatures will be frozen in front of missile troops //We have been attacked by someone new //if (LastAttackedBy != null && LastAttackedBy.Alive) //{ //Reset the AI, will drop through and chase the nearest target // AIState = SimpleAIStates.RandomWalk; //} //else { //Are we close enough to the PC? double distance = GetDistance(this, Game.Dungeon.Player); if (distance <= recoverDistance) { //Reset AI and fall through AIState = SimpleAIStates.Patrol; LogFile.Log.LogEntryDebug(this.Representation + " close enough to PC", LogDebugLevel.Low); } //Otherwise follow the PC back FollowPC(); //} } //PURSUIT MODES - Pursuit [active] and Fleeing [temporarily fleeing, will return to target] if (AIState == SimpleAIStates.Fleeing || AIState == SimpleAIStates.Pursuit) { Monster targetMonster = currentTarget as Monster; //Fleeing //Check we have a valid target (may not after reload) //still required? if (currentTarget == null) { AIState = SimpleAIStates.Patrol; } //Is target yet living? else if (currentTarget.Alive == false) { //If not, go to non-chase state AIState = SimpleAIStates.Patrol; } //Charmed creatures should not attack other charmed creatures else if (Charmed && targetMonster != null && targetMonster.Charmed) { //Go to non-chase state AIState = SimpleAIStates.Patrol; } //Is target on another level (i.e. has escaped down the stairs) else if (currentTarget.LocationLevel != this.LocationLevel) { AIState = SimpleAIStates.Patrol; } //Have we just become charmed? Reset AI (stop chasing player) else if (currentTarget == Game.Dungeon.Player && Charmed) { AIState = SimpleAIStates.Patrol; } //Have we just become passive? Reset AI (stop chasing player) else if (currentTarget == Game.Dungeon.Player && Passive) { AIState = SimpleAIStates.Patrol; } //Has the player stealthed? else if (currentTarget == Game.Dungeon.Player && Game.Dungeon.Player.isStealthed()) { LogFile.Log.LogEntryDebug(this.Representation + " stop chasing. Player went stealthed", LogDebugLevel.Medium); AIState = SimpleAIStates.Patrol; } //Have we just been attacked by a new enemy? else if (LastAttackedBy != null && LastAttackedBy.Alive && LastAttackedBy != currentTarget) { //Reset the AI for now AIState = SimpleAIStates.Patrol; } else { //Otherwise continue to pursue or flee ChaseCreature(currentTarget); } } //PATROL STATE OR INVESTIGATE STATE //Check states which override patrol or investigate (e.g being attacked, charmed, seeing the PC) if(AIState == SimpleAIStates.Patrol || AIState == SimpleAIStates.InvestigateSound) { Map currentMap = Game.Dungeon.Levels[LocationLevel]; //AI branches here depending on if we are charmed or passive if (this.Charmed) { //Charmed - will fight for the PC //Won't attack passive creatures (otherwise will de-passify them and it would be annoying) //Look for creatures in FOV CreatureFOV currentFOV = Game.Dungeon.CalculateCreatureFOV(this); //List will contain monsters & player List<Monster> monstersInFOV = new List<Monster>(); 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 monstersInFOV.Add(monster); LogFile.Log.LogEntryDebug(this.Representation + " spots " + monster.Representation, LogDebugLevel.Low); } //Look for creatures which aren't passive or charmed List<Monster> notPassiveTargets = monstersInFOV.FindAll(x => !x.Passive); List<Monster> notCharmedOrPassiveTargets = notPassiveTargets.FindAll(x => !x.Charmed); //Go chase a not-passive, not-charmed creature if (notCharmedOrPassiveTargets.Count > 0) { //Find the closest creature Monster closestCreature = null; double closestDistance = Double.MaxValue; //a long way foreach (Monster creature in notCharmedOrPassiveTargets) { 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 + " charm chases " + closestCreature.Representation, LogDebugLevel.Low); AIState = SimpleAIStates.Pursuit; ChaseCreature(closestCreature); } else { //No creature to chase, go find PC FollowPC(); } } else if(!Passive) { //Normal fighting behaviour //Optional: check next move and open any doors if possible. This gives us a chance to shoot lurking PCs //Removed this while closing doors is not possible - avoids repeated abuse /* if (CanOpenDoors() && (AIState == SimpleAIStates.Patrol || AIState == SimpleAIStates.InvestigateSound)) { //Very simple version, monsters open any doors they are facing List<Point> directedAhead = DirectionUtil.SurroundingPointsFromDirection(Heading, LocationMap, 3); foreach (Point p in directedAhead) { MapTerrain doorTerrain = Game.Dungeon.GetTerrainAtPoint(this.LocationLevel, p); if (doorTerrain == MapTerrain.ClosedDoor) { LogFile.Log.LogEntryDebug(this.Representation + " : door detected ahead, opening", LogDebugLevel.Medium); Game.Dungeon.OpenDoor(this.LocationLevel, p); } } }*/ //Find creatures & PC in FOV CreatureFOV currentFOV = Game.Dungeon.CalculateCreatureFOV(this); List<Creature> monstersInFOV = new List<Creature>(); foreach (Creature 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 monstersInFOV.Add(monster); LogFile.Log.LogEntryDebug(this.Representation + " spots " + monster.Representation, LogDebugLevel.Low); } if (Game.Dungeon.Player.LocationLevel == this.LocationLevel) { if (currentFOV.CheckTileFOV(Game.Dungeon.Player.LocationMap.x, Game.Dungeon.Player.LocationMap.y) && !Game.Dungeon.Player.isStealthed()) { monstersInFOV.Add(Game.Dungeon.Player); LogFile.Log.LogEntryDebug(this.Representation + " spots " + Game.Dungeon.Player.Representation, LogDebugLevel.Low); } } //Have we just been attacked by a new enemy? If so, respond to them if (LastAttackedBy != null && LastAttackedBy.Alive && LastAttackedBy != currentTarget) { //Is this target within FOV? If so, attack it if (monstersInFOV.Contains(LastAttackedBy)) { LogFile.Log.LogEntryDebug(this.Representation + " changes target to " + LastAttackedBy.Representation, LogDebugLevel.Medium); AIState = SimpleAIStates.Pursuit; ChaseCreature(LastAttackedBy); } else { //Continue chasing whoever it was we were chasing last if (currentTarget != null) { AIState = SimpleAIStates.Pursuit; ChaseCreature(currentTarget); } } } //Check if we can see the PC and pursue them //If we are not currently pursuing anything and we see the PC, pursue if seen //Technically, go into pursuit mode, which may not involve actual movement if ((AIState == SimpleAIStates.Patrol || AIState == SimpleAIStates.InvestigateSound) && monstersInFOV.Contains(Game.Dungeon.Player) && !Game.Dungeon.Player.isStealthed()) { Creature closestCreature = Game.Dungeon.Player; //Start chasing this creature LogFile.Log.LogEntryDebug(this.Representation + " chases " + closestCreature.Representation, LogDebugLevel.Medium); AIState = SimpleAIStates.Pursuit; ChaseCreature(closestCreature); } } //This is so we don't have to instrument each state if (LocationMap == startOfTurnLocation) { ResetTurnsMoving(); AddTurnsInactive(); } else { ResetTurnsInactive(); AddTurnsMoving(); } } //INVESTIGATE SOUNDS //If a new sound has happened, calculate interest //If high interest, make this our target //Continue to investigate the sound //If we've reached the target, return to Patrol [sightings are handled above] //Monster that don't pursue still have a chance to direct their FOVs at sounds bool moveFollowingSound = false; if ((AIState == SimpleAIStates.Patrol || AIState == SimpleAIStates.InvestigateSound) && WillInvestigateSounds() ) { double currentSoundInterest; if (CurrentSoundID == -1) { currentSoundInterest = 0.0; } else { //Interest in the last interesting sound will decay over time currentSoundInterest = currentSound.DecayedInterest(CurrentSoundInterestScore, Game.Dungeon.WorldClock); } //Get sounds which have happened since we last looked (and update) //TODO: reset this when we leave a pursuit state - could look at very old sounds then?? List<SoundEffect> newSounds = Game.Dungeon.GetSoundsAfterTime(LastCheckedSounds); LastCheckedSounds = Game.Dungeon.WorldClock; SoundEffect newSoundToFollow = null; int newSoundToFollowID = -1; double newSoundInterest = currentSoundInterest; foreach(SoundEffect soundEvent in newSounds) { SoundEffect sEffect = soundEvent; double newSoundScore = sEffect.DecayedMagnitude(this.LocationLevel, this.LocationMap); if (newSoundScore > newSoundInterest) { newSoundToFollowID = sEffect.ID; newSoundToFollow = sEffect; newSoundInterest = newSoundScore; } } //Have we found a new more interesting sound? //If so, follow it if (newSoundToFollowID != -1) { LogFile.Log.LogEntryDebug(this.Representation + " new sound target: " + newSoundToFollow + "[ int: " + newSoundInterest + "] (old: " + currentSound + " [ int: " + currentSoundInterest + "])", LogDebugLevel.Medium); //Change sound //A sound we choose gets a boost in interest to give us a bit of hystersis SetSoundToFollow(newSoundToFollow, newSoundInterest * 1.5); AIState = SimpleAIStates.InvestigateSound; } else { if (currentSoundInterest < 0.01) { //Sound has decayed so much it's not interesting, or we never had an interesting sound ResetFollowingSound(); if(AIState == SimpleAIStates.InvestigateSound) LogFile.Log.LogEntryDebug(this.Representation + " sound " + currentSound + " is old, resetting", LogDebugLevel.Low); AIState = SimpleAIStates.Patrol; } } //For a new or existing sound, pursue it if (AIState == SimpleAIStates.InvestigateSound) { moveFollowingSound = InvestigateSound(); } } //If nothing else happened, do the Patrol action //Don't if we moved in response to a sound if ((AIState == SimpleAIStates.Patrol && !moveFollowingSound) || (WillAlwaysPatrol() && !headingSetToSound)) { //We haven't got anything to do and we can't see the PC //Do normal movement DoPatrol(); } //Reset the skip-patrol if we looked at sound flag headingSetToSound = false; }
protected virtual int AttackCreatureWithModifiers(Creature creature, int hitMod, int damBase, int damMod, int ACmod) { //Just do damage base return damageBase; /* int attackToHit = hitModifier + hitMod; int attackDamageMod = damageModifier + damMod; int attackDamageBase; if (damBase > damageBase) attackDamageBase = damBase; else attackDamageBase = damageBase; int targetAC = creature.ArmourClass() + ACmod; toHitRoll = Utility.d20() + attackToHit; if (toHitRoll >= targetAC) { //Hit - calculate damage int totalDamage = Utility.DamageRoll(attackDamageBase) + attackDamageMod; return totalDamage; } //Miss return 0;*/ }
public MonsterFightAndRunAI() { AIState = SimpleAIStates.Patrol; currentTarget = null; CurrentSoundID = -1; lastHitpoints = MaxHitpoints; }
protected virtual void FollowAndAttack(Creature newTarget) { //Find location of next step on the path towards target Pathing.PathingResult pathingResult; Pathing.PathingPermission permission = Pathing.PathingPermission.Normal; if(CanOpenDoors()) permission = Pathing.PathingPermission.IgnoreDoors; pathingResult = Game.Dungeon.Pathing.GetPathToCreature(this, newTarget, PathingType(), permission); Point nextStep = pathingResult.MonsterFinalLocation; //We are adjacent and can attack if (pathingResult.MoveIsInteractionWithTarget) { //If we can attack, attack the monster or PC if(WillAttack()) { CombatResults result; if (newTarget == Game.Dungeon.Player) { result = AttackPlayer(newTarget as Player); } else { //It's a normal creature result = AttackMonster(newTarget as Monster); } Screen.Instance.DrawMeleeAttack(this, newTarget, result); //If we killed it, move into its square if (result == CombatResults.DefenderDied && !(newTarget == Game.Dungeon.Player && Game.Dungeon.PlayerImmortal)) { nextStep = currentTarget.LocationMap; } } } //If we are permanently blocked, return to patrol state if (pathingResult.TerminallyBlocked) { LogFile.Log.LogEntryDebug(this.Representation + " permanently blocked (door), returning to patrol ", LogDebugLevel.Medium); AIState = SimpleAIStates.Patrol; return; } //Otherwise (or if the creature died), move towards it (or its corpse) if(WillPursue()) { //If we want to pursue, move towards the creature if (CanMove()) { MoveIntoSquare(nextStep); SetHeadingToTarget(newTarget); } } else { //If we don't we continue our normal Patrol route //(we are set to Pursuit in the AI though) DoPatrol(); } }
/// <summary> /// Called every click. If the event duration is over, call OnEnd() and mark as ended /// </summary> public abstract void IncrementTime(Creature target);
/// <summary> /// Applies the effect of the object /// </summary> /// <param name="user"></param> /// <returns>True if the object could be used</returns> public abstract bool Use(Creature user);
/// <summary> /// Carries out the end effects on the target /// </summary> public abstract void OnEnd(Creature target);
public virtual bool OnPickup(Creature pickupCreature) { return false; }
/// <summary> /// Carries out the start effects on the target. /// </summary> public abstract void OnStart(Creature target);
/// <summary> /// Displays the creature FOV on the map. Note that this clobbers the FOV map /// </summary> /// <param name="creature"></param> public void ShowCreatureFOVOnMap(Creature creature) { //Only do this if the creature is on a visible level if(creature.LocationLevel != Player.LocationLevel) return; Map currentMap = levels[creature.LocationLevel]; TCODFov tcodFOV = levelTCODMaps[creature.LocationLevel]; //Calculate FOV tcodFOV.CalculateFOV(creature.LocationMap.x, creature.LocationMap.y, creature.SightRadius); //Only check sightRadius around the creature int xl = creature.LocationMap.x - creature.SightRadius; int xr = creature.LocationMap.x + creature.SightRadius; int yt = creature.LocationMap.y - creature.SightRadius; int yb = creature.LocationMap.y + creature.SightRadius; //If sight is infinite, check all the map if (creature.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; for (int i = xl; i <= xr; i++) { for (int j = yt; j <= yb; j++) { MapSquare thisSquare = currentMap.mapSquares[i, j]; bool inFOV = tcodFOV.CheckTileFOV(i, j); if(inFOV) thisSquare.InMonsterFOV = true; } } }
public CreatureFOV(Creature creature, WrappedFOV fov, CreatureFOVType creatureFOVType) { this.fov = fov; this.type = creatureFOVType; this.creature = creature; }
public MonsterSimpleAI() { AIState = SimpleAIStates.Patrol; currentTarget = null; }
private bool RecastSpellCastAtCreature(Spell spell, Creature target) { //Convert the stored Creature last target into a square Point spellTargetSq = new Point(target.LocationMap.x, target.LocationMap.y); return Game.Dungeon.Player.CastSpell(spell, spellTargetSq); }