public void FieldOfViewUpdate() { //UInt16 range = _statsDerivative[(sbyte)StatDerivative.SightRange]; UInt16 range = GetSightRange(); //Map currentMap = this.InhabitedMap; if (range < 0) { return; } //REMOVE REDUNDANCY HERE BitArray[] update = new BitArray[_map.BoundX]; for (int i = 0; i < _map.BoundX; ++i) { update[i] = new BitArray(_map.BoundY); } for (Int32 i = -range; i <= range; i++) { for (Int32 j = -range; j <= range; j++) { Coords current = new Coords(CoordsType.Tile, _positionTile.X + i, _positionTile.Y + j); if ( !this._map.CheckInBounds(current) || (StaticMathFunctions.DistanceBetweenTwoCoordsHex(this._positionTile, current) > range) ) { continue; } bool val = _myVisibilityTracker.RayTracerVisibilityCheckTile(this._positionTile, current, true, range); update[current.X][current.Y] = val; } } // determine values that were changed for (int i = 0; i < _map.BoundX; ++i) { update[i] = update[i].Xor(_fieldOfView[i]); } // update changes for (int i = 0; i < _map.BoundX; ++i) { for (int j = 0; j < _map.BoundY; ++j) { if (update[i][j]) { bool val = _fieldOfView[i][j]; _fieldOfView[i][j] = !val; //_inhabitedMap.GetTile(i, j).VisibilityUpdate(this, !val); _myVisibilityTracker.VisibilityUpdate(new Coords(CoordsType.Tile, i, j), this, !val); } } } }
public override bool Execute(Coords?_target) { if (_target == null) { throw new Exception("Basic Attack no target hex passed."); } _range = _agent.GetAttackRange(); int distance = StaticMathFunctions.DistanceBetweenTwoCoordsHex(_agent.PositionGet(), _target.Value); if (distance > _range) { // target not in range; do nothing return(false); } Creature quarry = _map.TenancyMap[_target.Value.X, _target.Value.Y]; if (quarry == null) { // no one to hit: do nothing. return(false); } Item weapon = _agent.InventoryEquipped.GetItem((sbyte)InventoryType.HandWeapon); if (weapon == null || (weapon.ItemFunctions[ItemProperty.Range] <= 1)) { // weapon not of proper type; do nothing. return(false); } // determine damage int damage = (UInt16)Math.Max(_agent.GetAttackDamage() - quarry.GetArmor(), 1); // substract HP // quarry.AddToStatBasic(Creature.StatBasic.HP, -damage); quarry.EffectRegister(new EffectChangeStatBasic(_agent, 0, quarry, Creature.StatBasic.HP, -damage)); // NOTE: AP substraction done at ActionUseSpell level. // Check if target is dead. //if (quarry.Dead) //{ // _agent.AddToStatBasic(Creature.StatBasic.XP, StaticMathFunctions.XPFormula(quarry)); //} // Ranged attack anim // for now use arrow by default; later associate particle sprites to the various ranged weapons (slings?) _drawer.Animations.Add(new AnimProjectile(Constants.AnimProjectileArrowBaseSpeed * distance, _drawer.Particles[(sbyte)SpriteParticle.ParticleArrow], _interface.HexPosition(_agent.PositionGet()) + new Vector2(Constants.TileSize / 2, Constants.TileSize / 2), _interface.HexPosition(_target.Value) + new Vector2(Constants.TileSize / 2, Constants.TileSize / 2))); // Floating message addition _drawer.FloatingMessages.Add(new FloatingMessage(Constants.FloatingTextDefaultTimer, "-" + damage.ToString() + "HP", _interface.HexPosition(_target.Value) + new Vector2(Constants.TileSize / 2, Constants.TileSize / 2))); return(true); }
public List<Direction> RetrieveRoute(Coords goal) { if (_owner.PositionGet() != _currentOrigin) { Update(); } if (!_currentMap.CheckInBounds(goal) || !_rangeMap[goal.X][goal.Y]) { return null; } List<Direction> returnList = new List<Direction>(); Coords trackbackCoords = goal; while (trackbackCoords != _currentOrigin) { Direction newDirection = _directionMap[trackbackCoords.X, trackbackCoords.Y].Value; returnList.Add(newDirection); trackbackCoords = StaticMathFunctions.CoordsNeighboringInDirection(new Coords(CoordsType.Tile, trackbackCoords), StaticMathFunctions.OppositeDirection(newDirection)); } return returnList; }
/// <summary> /// Returns list of possible moves, sorted by /// 1) amount of increase, 2) distance to influence map source /// THIS METHOD SHOULD BE IMPROVED /// </summary> public List <Direction> PossibleMoves(Coords currentPosition) { List <Direction> dirList = new List <Direction>(); Tile currentTile = this._currentMap.GetTile(currentPosition); for (byte i = 1; i <= 8; i++) { Direction currentDir = (Direction)i; if (currentTile.AllowedMovesCheckInDirection(currentDir)) { dirList.Add(currentDir); } } dirList.Sort( delegate(Direction d1, Direction d2) { Coords c1 = StaticMathFunctions.CoordsNeighboringInDirection(currentPosition, d1); Coords c2 = StaticMathFunctions.CoordsNeighboringInDirection(currentPosition, d2); Int32 returnVal = (this._influenceMap[c1.X, c1.Y]).CompareTo(this._influenceMap[c2.X, c2.Y]); if (returnVal == 0) { returnVal = (StaticMathFunctions.DistanceBetweenTwoCoordsEucledean(c1, currentPosition)).CompareTo (StaticMathFunctions.DistanceBetweenTwoCoordsEucledean(c2, currentPosition)); } return(returnVal); } ); return(dirList); }
public override bool Execute(Coords?_target) { if (_target == null) { throw new Exception("Basic Attack no target hex passed."); } int distance = StaticMathFunctions.DistanceBetweenTwoCoordsHex(_agent.PositionGet(), _target.Value); if (distance > 1) { // target not in range; do nothing return(false); } Creature quarry = _map.TenancyMap[_target.Value.X, _target.Value.Y]; if (quarry == null) { // no one to hit: do nothing. return(false); } Item weapon = _agent.InventoryEquipped.GetItem((sbyte)InventoryType.HandWeapon); if (weapon != null && (weapon.MyType != ItemType.Weapon || weapon.ItemFunctions[ItemProperty.Range] > 1)) { // weapon not of proper type; do nothing. return(false); } // determine damage int damage = (UInt16)Math.Max(_agent.GetAttackDamage() - quarry.GetArmor(), 1); // substract HP //quarry.AddToStatBasic(Creature.StatBasic.HP, -damage); quarry.EffectRegister(new EffectChangeStatBasic(_agent, 0, quarry, Creature.StatBasic.HP, -damage)); // Check if target is dead. //if (quarry.Dead) //{ // _agent.AddToStatBasic(Creature.StatBasic.XP, StaticMathFunctions.XPFormula(quarry)); //} // NOTE: AP substraction done at ActionUseSpell level. // Melee slash anim if (weapon != null) { _drawer.Animations.Add(new AnimWeaponSlash(Constants.AnimWeaponSlashBaseDuration, _drawer.Items[(sbyte)weapon.ItemBitmap], _interface.HexPosition(_target.Value) + new Vector2(Constants.TileSize / 2, Constants.TileSize / 2))); } // Floating message addition _drawer.FloatingMessages.Add(new FloatingMessage(Constants.FloatingTextDefaultTimer, "-" + damage.ToString() + "HP", _interface.HexPosition(_target.Value) + new Vector2(Constants.TileSize / 2, Constants.TileSize / 2))); return(true); }
private void CheckEnemyVanquished() { if (_target.Dead) { // target vanquished; apply XP gain to effect author (if appropriate). _author.AddToStatBasic(Creature.StatBasic.XP, StaticMathFunctions.XPFormula(_target)); } }
private void DrawCreatures(SpriteBatch spriteBatch) { int tilesize = Constants.TileSize; int offset = tilesize / 4; // CREATURES foreach (KeyValuePair <UInt32, Creature> kvp in _currentMap.Menagerie) { Creature tenant = kvp.Value; Texture2D tenantBitmap = _creatures[(sbyte)tenant.MyBitmap]; Int32 i = tenant.PositionGet().X; Int32 j = tenant.PositionGet().Y; Int16 visibilityTracker = _currentMap.MyVisibilityTracker.VisibilityCheck(tenant.PositionGet(), _myGame.PlayerTeam); Vector2 pos = new Vector2(i * tilesize + (j % 2) * (tilesize / 2), j * tilesize - j * offset) + _screenAnchor; Rectangle rectHex = ZoomTransform(new Rectangle((int)pos.X, (int)pos.Y, tilesize, tilesize)); Rectangle rectCreature = ZoomTransform(new Rectangle((int)pos.X, (int)pos.Y, tenantBitmap.Width, tenantBitmap.Height)); if (tenant != null && (tenant.Team == _myGame.PlayerTeam || visibilityTracker > 0)) { // if the tenant is selected, draw selection box if (_myInterface.SelectedCreature == tenant) { spriteBatch.Draw(_tiles[(sbyte)SpriteTile.HexRed], rectHex, Color.White); } // if the tenant is an enemy and within range of a friendly selected creature, draw an indication if (_myInterface.SelectedCreature != null && _myInterface.SelectedCreature.Team == _myGame.PlayerTeam && tenant.Team != _myGame.PlayerTeam && _myInterface.SelectedCreature.GetAttackRange() >= StaticMathFunctions.DistanceBetweenTwoCoordsHex(_myInterface.SelectedCreature.PositionGet(), tenant.PositionGet())) { spriteBatch.Draw(_tiles[(sbyte)SpriteTile.HexRed], rectHex, Color.Red); } // If there is an active animation for the creature, draw it. if (_creatureAnimations.ContainsKey(tenant.UniqueID)) { List <AnimUnitMove> currentStack = _creatureAnimations[tenant.UniqueID]; AnimUnitMove current = currentStack.Last(); current.Draw(spriteBatch, _screenAnchor, Color.White, _zoom); } // Otherwise, draw static sprite of the creature. else { spriteBatch.Draw(_creatures[(sbyte)tenant.MyBitmap], rectCreature, Color.White); //HP bar: float hpRatio = (float)tenant.GetHP() / (float)tenant.GetHPMax(); Vector2 barLocation = pos; barLocation.X += 3 * Constants.TileSize / 4; barLocation.Y += (1 - hpRatio) * Constants.TileSize; Color drawColor = Color.Lerp(Color.Red, Color.Green, hpRatio); drawColor.A = 64; spriteBatch.Draw(_particles[(sbyte)SpriteParticle.ParticlePixel], ZoomTransform(new Rectangle((int)barLocation.X, (int)barLocation.Y, Constants.TileSize / 12, (int)(hpRatio * Constants.TileSize))), drawColor); } } } }
public AnimProjectile(Int32 durationMax, Texture2D sprite, Vector2 origin, Vector2 goal) : base(durationMax, new Texture2D[] { sprite }, origin) { _goal = goal; _delta = (goal - origin); _delta.X = _delta.X / durationMax; _delta.Y = _delta.Y / durationMax; _angle = StaticMathFunctions.VectorToAngle(_delta); }
/// <summary> /// Analyzes and remembers tile accessibility. Starts at northwest corner and goes through the array, /// checking east / southeast / south / southwest on the current tile and in case of accessibility /// recording the result in both directions. /// </summary> public void AnalyzeTileAccessibility() { Tile currentTile; for (UInt16 i = 0; i < this._xMax; i++) { for (UInt16 j = 0; j < this._yMax; j++) { Tile east, southEast, southWest; currentTile = this._tiles[i, j]; if (Constants.APMoveCostsStandard[(sbyte)currentTile.MyTerrainType] == 0) { continue; } //_vacancyMap[i][j] = true; _visibilityMap[i, j] = currentTile.VisibilityCoefficient; // Sort of wasteful, hopefully compiler does this smartly if (i < _xMax - 1) { east = this.GetTile(StaticMathFunctions.CoordsNeighboringInDirection(new Coords(CoordsType.Tile, i, j), Direction.East)); if (east.IsPassable()) { currentTile.AllowedMovesSet(Direction.East, true); east.AllowedMovesSet(Direction.West, true); } } if ((i < _xMax - 1) & (j < _yMax - 1)) { southEast = this.GetTile(StaticMathFunctions.CoordsNeighboringInDirection(new Coords(CoordsType.Tile, i, j), Direction.Southeast)); if (southEast.IsPassable()) { currentTile.AllowedMovesSet(Direction.Southeast, true); southEast.AllowedMovesSet(Direction.Northwest, true); } } if ((i > 0) & (j < _yMax - 1)) { southWest = this.GetTile(StaticMathFunctions.CoordsNeighboringInDirection(new Coords(CoordsType.Tile, i, j), Direction.Southwest)); if (southWest.IsPassable()) { currentTile.AllowedMovesSet(Direction.Southwest, true); southWest.AllowedMovesSet(Direction.Northeast, true); } } } } }
public Int32 DistanceTo(Coords c) { return(StaticMathFunctions.DistanceBetweenTwoCoordsHex(this, c)); }
private List<Direction> _PathfinderAStar(Coords start, Coords endTopLeft, Coords endBottomRight, BitArray[] _passabilityMap, hFunction h) { // NOTE: Should later implemented a collision predictor mechanic to work in tandem // with the path-finder to provide better agent behavior. // NOTE: Consider returning the number of tiles scanned in case no path is found. // This will alert a boxed-in creature of its predicament. // NOTE: Introduce a flag for a straight-line initial check(for outdoors environmens and // for when the goal is near). Int32 rangeX = _passabilityMap.Length; Int32 rangeY = _passabilityMap[0].Count; NodeAStar?[,] nodeArray = new NodeAStar?[rangeX, rangeY]; NodeAStar startNode = new NodeAStar(); startNode.costSoFar = 0; startNode.estimatedTotalCost = h(start); nodeArray[start.X, start.Y] = startNode; List<Coords> ListOpen = new List<Coords>(); ListOpen.Add(start); while (ListOpen.Count > 0) { // I have to use this bool the way I've implemented the algo. Consider rewriting. bool resortList = false; Coords currentCoords = ListOpen.First(); // Check to see if goal is reached. //if (currentCoords.Equals(endTopLeft)) if (StaticMathFunctions.CoordinateIsInBox(currentCoords, endTopLeft, endBottomRight)) { break; } NodeAStar currentNode = nodeArray[currentCoords.X, currentCoords.Y].Value; for (byte i = 0; i <= 5; ++i) { Direction currentDir = (Direction)(i); //Coords dirCoords = StaticMathFunctions.DirectionToCoords(currentDir); Coords potential = StaticMathFunctions.CoordsNeighboringInDirection(currentCoords, currentDir); // check if move in dir is allowed if (potential.X >= 0 && potential.X < rangeX && potential.Y >= 0 && potential.Y < rangeY // bounds check && _passabilityMap[potential.X][potential.Y]) // passability check { // Using the simplest cost function possible. Can be easily updated // once tile walkability coefficients are added. //Coords newNodePosition = new Coords(CoordsType.General, currentCoords.X + dirCoords.X, currentCoords.Y + dirCoords.Y); Coords newNodePosition = potential; float accruedCost = currentNode.costSoFar + 1; // Straight line correction if (currentDir == nodeArray[currentCoords.X, currentCoords.Y].Value.connection) { accruedCost -= Constants.PathfinderStraightPathCorrection; } // Check to see if the node under examination is in the closed list. //NodeAStar? oldNode = nodeArray[newNodePosition.X, newNodePosition.Y]; if (nodeArray[newNodePosition.X, newNodePosition.Y] != null) { // If node is in closed list, see if it needs updating. if (nodeArray[newNodePosition.X, newNodePosition.Y].Value.costSoFar > accruedCost) { float expectedAdditionalCost = nodeArray[newNodePosition.X, newNodePosition.Y].Value.estimatedTotalCost - nodeArray[newNodePosition.X, newNodePosition.Y].Value.costSoFar; NodeAStar nodeToAdd = new NodeAStar(currentDir, accruedCost, accruedCost + expectedAdditionalCost); nodeArray[newNodePosition.X, newNodePosition.Y] = nodeToAdd; ListOpen.Add(newNodePosition); resortList = true; } } // Node is in open list. Process it. else { float expectedAdditionalCost = h(newNodePosition); NodeAStar nodeToAdd = new NodeAStar(currentDir, accruedCost, accruedCost + expectedAdditionalCost); nodeArray[newNodePosition.X, newNodePosition.Y] = nodeToAdd; ListOpen.Add(newNodePosition); resortList = true; } } } ListOpen.RemoveAt(0); if (resortList) { ListOpen.Sort( delegate(Coords c1, Coords c2) { float difference = nodeArray[c1.X, c1.Y].Value.estimatedTotalCost - nodeArray[c2.X, c2.Y].Value.estimatedTotalCost; Int32 returnValue = 0; if (difference > 0) { returnValue = 1; } else if (difference < 0) { returnValue = -1; } return returnValue; } ); } } List<Direction> ListRoute = new List<Direction>(); // Return empty route if the open list is empty, i.e. there is no path to the target // Ideally, the game logic should be fixed so that the search isn't even attempted // if there is no path between the two points. if (ListOpen.Count == 0) { return ListRoute; } Coords trackbackCoords = endTopLeft; while (trackbackCoords != start) { Direction newDirection = nodeArray[trackbackCoords.X, trackbackCoords.Y].Value.connection; ListRoute.Add(newDirection); trackbackCoords = StaticMathFunctions.CoordsNeighboringInDirection(new Coords(CoordsType.Tile, trackbackCoords), StaticMathFunctions.OppositeDirection(newDirection)); } // Might be faster without reversing //ListRoute.Reverse(); // We skip the reversal, so pick directions from the END of the list. return ListRoute; }
private void CalculateMoveRange(Coords origin, UInt16[] moveCosts, UInt16 availableAP) { _currentOrigin = origin; for (int i = 0; i < _currentMap.BoundX; ++i) { for (int j = 0; j < _currentMap.BoundY; ++j) { _APCount[i, j] = -1; _directionMap[i, j] = null; } } _APCount[origin.X, origin.Y] = availableAP; // WARNING: Code repetition with influence maps / A* Queue<Coords> currentQueue = new Queue<Coords>(); Queue<Coords> nextQueue = new Queue<Coords>(); currentQueue.Enqueue(origin); UInt32 currentDistance = 0; // main loop // Stopping conditions: the two queues are exhausted, OR InfluenceMapMaxDistance is reached while ( ((currentQueue.Count > 0) & (nextQueue.Count > 0)) | (currentDistance < Constants.InfluenceMapMaxDistance) ) { // Checks if it's time to start the next pass if (currentQueue.Count == 0) { currentQueue = nextQueue; nextQueue = new Queue<Coords>(); currentDistance++; continue; } Coords currentCoords = currentQueue.Peek(); //Coords delta1 = currentCoords - origin; Tile currentTile = _currentMap.GetTile(currentCoords); // Analyzes the neighbors of the current Tile for possible additions to nextQueue for (byte i = 0; i < 6; i++) { Direction currentDir = (Direction)i; Coords toCheck = StaticMathFunctions.CoordsNeighboringInDirection(currentCoords, currentDir); if (_currentMap.CheckInBounds(toCheck)) { Tile targetTile = _currentMap.GetTile(toCheck); UInt16 cost = moveCosts[(sbyte)targetTile.MyTerrainType]; if (cost > 0 && _currentMap.TenancyMap[toCheck.X, toCheck.Y] == null) // ignore impassable terrain and ignore occupied tiles { // checks if this approach is cheaper than the best approach so far Int32 currentAPleft = _APCount[toCheck.X, toCheck.Y]; Int32 potentialAPleft = _APCount[currentCoords.X, currentCoords.Y] - cost; if (currentAPleft < potentialAPleft) { _APCount[toCheck.X, toCheck.Y] = (UInt16)potentialAPleft; _directionMap[toCheck.X, toCheck.Y] = currentDir; nextQueue.Enqueue(toCheck); } } } } currentQueue.Dequeue(); } for (int i = 0; i < _currentMap.BoundX; ++i) { for (int j = 0; j < _currentMap.BoundY; ++j) { _rangeMap[i][j] = _APCount[i, j] >= 0; } } //return rangeMap; }
/// <summary> /// Tile-level (coarse) A* pathfinding. /// </summary> /// <param name="start"> Start Coords </param> /// <param name="endTopLeft"> Goal-box TopLeft Coords </param> /// <param name="endBottomRight"> Goal-box BottomRight Coords </param> /// <param name="h"> Heuristic function </param> /// <returns> Route to goal, as a list of Directions </returns> public List<Direction> PathfinderAStarCoarse(Coords start, Coords endTopLeft, Coords endBottomRight, HeuristicFunction h) { return this._PathfinderAStar(new Coords(CoordsType.General, start), new Coords(CoordsType.General, endTopLeft), new Coords(CoordsType.General, endBottomRight), this._passabilityMap, delegate(Coords c) { return h(c, StaticMathFunctions.CoordsAverage(endTopLeft, endBottomRight)); }); }
/// Generates the influence map. /// Uses a silly recursive algorithm. /// Stopping conditions: Let's use two, to avoid stupid infinite loops. /// One is a distance threshold check. // Second is a min influence threshold check. /// <summary> /// Generates the influence map. /// Uses a silly recursive algorithm. /// Stopping conditions: Let's use two, to avoid stupid infinite loops. /// One is a distance threshold check. /// Second is a min influence threshold check. /// </summary> public void GenerateInfluenceMap() { // boolean array to keep note of which tiles have been processed //BitArray[,] takenCareOf = new BitArray[_currentMap.BoundX, _currentMap.BoundY]; BitArray[] takenCareOf = new BitArray[_currentMap.BoundX]; for (int i = 0; i < _currentMap.BoundX; ++i) { takenCareOf[i] = new BitArray(_currentMap.BoundY); } takenCareOf[Source.X][Source.Y] = true; // sets up two queues - one for the current pass, one for the next one // distance increments by one at each pass // if too slow, the process should be broken up so it does a number of passes each tick Queue <Coords> currentQueue = new Queue <Coords>(); Queue <Coords> nextQueue = new Queue <Coords>(); currentQueue.Enqueue(_source); UInt32 currentDistance = 0; // main loop // Stopping conditions: the two queues are exhausted, OR InfluenceMapMaxDistance is reached while ( ((currentQueue.Count > 0) & (nextQueue.Count > 0)) | (currentDistance < Constants.InfluenceMapMaxDistance) ) { // Checks if it's time to start the next pass if (currentQueue.Count == 0) { currentQueue = nextQueue; nextQueue = new Queue <Coords>(); currentDistance++; continue; } Coords currentCoords = currentQueue.Peek(); Tile currentTile = CurrentMap.GetTile(currentCoords); // Analyzes the neighbors of the current Tile for possible additions to nextQueue for (byte i = 0; i < 6; i++) { Direction currentDir = (Direction)i; if (currentTile.AllowedMovesCheckInDirection(currentDir)) { Coords toCheck = StaticMathFunctions.CoordsNeighboringInDirection(currentCoords, currentDir); if (!takenCareOf[toCheck.X][toCheck.Y]) { nextQueue.Enqueue(toCheck); takenCareOf[toCheck.X][toCheck.Y] = true; } } } float newVal = _f(currentDistance); // Check to avert infnite / excessively deep loop if (newVal > _lowTreshold) { this.SetMapValue(currentCoords, newVal); } currentQueue.Dequeue(); } }