/// <summary> /// Generates usually one random Monster chosen appropriately for the given depth. May /// generate more than one if the Monster chosen has escorts or appears in groups. Adds /// them to the current dungeon. /// </summary> /// <param name="level">Dungeon level to generate at.</param> /// <returns>The new Monsters that were added to the Dungeon.</returns> public static IList <Monster> AddRandom(Dungeon dungeon, int level, Vec startPos) { Race race = Race.Random(dungeon, level, true); List <Monster> monsters = new List <Monster>(); // create the monster(s) Monster monster = new Monster(startPos, race); monsters.Add(monster); dungeon.Entities.Add(monster); // generate friends if (race.NumberInGroup > 1) { int numMonsters = Rng.TriangleInt(race.NumberInGroup, race.NumberInGroup / 3); int tries = 0; while ((monsters.Count < numMonsters) && (tries < 100)) { tries++; // pick a random spot next to one of the monsters in the group Vec pos; if (dungeon.TryFindOpenAdjacent(Rng.Item(monsters).Position, out pos)) { // found one, so put another there Monster buddy = new Monster(pos, race); monsters.Add(buddy); dungeon.Entities.Add(buddy); } } } return(monsters); }
/// <summary> /// Implementation of the "growing tree" algorithm from here: /// http://www.astrolog.org/labyrnth/algrithm.htm. /// </summary> /// <remarks> /// This is a general algorithm, capable of creating Mazes of different textures. It requires /// storage up to the size of the Maze. Each time you carve a cell, add that cell to a list. /// Proceed by picking a cell from the list, and carving into an unmade cell next to it. If /// there are no unmade cells next to the current cell, remove the current cell from the list. /// The Maze is done when the list becomes empty. The interesting part that allows many possible /// textures is how you pick a cell from the list. For example, if you always pick the most /// recent cell added to it, this algorithm turns into the recursive backtracker. If you always /// pick cells at random, this will behave similarly but not exactly to Prim's algorithm. If you /// always pick the oldest cells added to the list, this will create Mazes with about as low a /// "river" factor as possible, even lower than Prim's algorithm. If you usually pick the most /// recent cell, but occasionally pick a random cell, the Maze will have a high "river" factor /// but a short direct solution. If you randomly pick among the most recent cells, the Maze will /// have a low "river" factor but a long windy solution. /// </remarks> public void GrowTree() { List <Vec> cells = new List <Vec>(); // start with a random cell Vec pos = Rng.Vec(Bounds); Open(pos); cells.Add(pos); while (cells.Count > 0) { // weighting how the index is chosen here will affect the way the // maze looks. see the function description int index = Math.Abs(Rng.TriangleInt(0, cells.Count - 1)); Vec cell = cells[index]; // see which adjacent cells are open List <Direction> unmadeCells = new List <Direction>(); if (CanCarve(cell, Direction.N)) { unmadeCells.Add(Direction.N); } if (CanCarve(cell, Direction.S)) { unmadeCells.Add(Direction.S); } if (CanCarve(cell, Direction.E)) { unmadeCells.Add(Direction.E); } if (CanCarve(cell, Direction.W)) { unmadeCells.Add(Direction.W); } if (unmadeCells.Count > 0) { Direction direction = Rng.Item(unmadeCells); Carve(cell, direction); cells.Add(cell + direction); } else { // no adjacent uncarved cells cells.RemoveAt(index); } } }
public Stats() { // reset the stats and then foreach (Stat stat in this) { stat.Base = 5; } // randomly add points until a total of 15 points per stat have been distributed for (int i = 0; i < 10 * Count; i++) { Rng.Item(this).Base++; } }
public bool TryFindOpenTileWithin(Vec startPos, int minRadius, int maxRadius, out Vec pos) { // find all possible tiles List <Vec> positions = new List <Vec>(); Rect bounds = new Rect(startPos - (Vec.One * maxRadius), Vec.One * (maxRadius + maxRadius + 1)); foreach (Vec tryPos in bounds) { // skip if out of bounds if (!Bounds.Contains(tryPos)) { continue; } // skip if outside the valid radii int distanceSquared = (tryPos - startPos).LengthSquared; if ((distanceSquared < minRadius) || (distanceSquared > maxRadius)) { continue; } // skip if not open if (!Tiles[tryPos].IsPassable) { continue; } // skip if already an entity there if (mEntities.GetAt(tryPos) != null) { continue; } // if we got here, we found one positions.Add(tryPos); } // bail if there are none pos = startPos; if (positions.Count == 0) { return(false); } // choose one randomly pos = Rng.Item(positions); return(true); }
/* * /// Finds a nearby position for an <see cref="Item"/> starting at the given * /// position. Tries to spread items out (minimize floor stacking) but still * /// make sure that all spawn items are reachable from the starting location. * /// </summary> * /// <param name="startPos"></param> * public IList<Vec> FindItemPositions(Vec startPos, int numNeeded) * { * // get the possible positions * IDictionary<Vec, int> reachable = GetReachablePositions(startPos, numNeeded + 2); * * // weight by the number of items and jitter a little * IDictionary<Vec, int> weighted = new Dictionary<Vec, int>(); * * foreach (KeyValuePair<Vec, int> pair in reachable) * { * weighted[pair.Key] = ((pair.Value + Items.GetAllAt(pair.Key).Count) * 5) + Rng.Int(7); * } * * // sort by weight * List<Vec> positions = new List<Vec>(weighted.Keys); * positions.Sort((a, b) => weighted[a].CompareTo(weighted[b])); * * // add duplicates if we don't have enough * while (positions.Count < numNeeded) * { * positions.Add(Rng.Item(positions)); * } * * return positions; * } * * private IDictionary<Vec, int> GetReachablePositions(Vec startPos, int maxSteps) * { * // convert between flood coordinates and dungeon coordinates * Vec toDungeon = startPos - new Vec(maxSteps, maxSteps); * Vec toFlood = Vec.Zero - toDungeon; * * Array2D<int> flood = new Array2D<int>(maxSteps * 2 + 1, maxSteps * 2 + 1); * * // mark the invalid areas * foreach (Vec pos in flood.Bounds) * { * Vec dungeon = pos + toDungeon; * * if (!Bounds.Contains(dungeon) || !Tiles[dungeon].IsPassable) * { * flood[pos] = -1; * } * } * * // start the flood * flood[startPos + toFlood] = 1; * * // flood * bool changed = true; * * while (changed) * { * changed = false; * * foreach (Vec pos in flood.Bounds) * { * // if this tile has been reached * if (flood[pos] > 0) * { * // reach its neighbors * foreach (Direction direction in Direction.Clockwise) * { * Vec neighbor = pos + direction; * * // flood it * if (flood.Bounds.Contains(neighbor) && (flood[neighbor] == 0)) * { * flood[neighbor] = flood[pos] + 1; * changed = true; * } * } * } * } * } * * // collect the reached tiles * IDictionary<Vec, int> positions = new Dictionary<Vec, int>(); * * foreach (Vec pos in flood.Bounds) * { * if (flood[pos] > 0) * { * positions[pos + toDungeon] = flood[pos]; * } * } * * return positions; * } */ /// <summary> /// Attempts to find a tile adjacent to the given starting position /// that does not contain any Monsters. If there are multiple available /// adjacent tiles, one will be chosen randomly. /// </summary> /// <param name="startPos">The position to search around.</param> /// <param name="pos">The chosen open tile.</param> /// <returns><c>true</c> if one was found.</returns> public bool TryFindOpenAdjacent(Vec startPos, out Vec pos) { // find all possible ones List <Vec> positions = new List <Vec>(); foreach (Direction direction in Direction.Clockwise) { Vec tryPos = startPos + direction; // skip if out of bounds if (!Bounds.Contains(tryPos)) { continue; } // skip if not open if (!Tiles[tryPos].IsPassable) { continue; } // skip if already an entity there if (mEntities.GetAt(tryPos) != null) { continue; } // found a possible one positions.Add(tryPos); } // bail if there aren't any pos = startPos; if (positions.Count == 0) { return(false); } // choose one randomly pos = Rng.Item(positions); return(true); }
private Hero(string name, string race, Stats stats, bool cheatDeath) : base(Vec.Zero, Energy.NormalSpeed, 10) { mName = name; mRace = race; //### bob: temp. should be passed in mClass = new Warrior(); mStats = stats; mCheatDeath = cheatDeath; mInventory = new Inventory(); mEquipment = new Equipment(); mEquipment.ItemEquipped.Add(Equipment_ItemEquipped); mLevel = 1; SetBehavior(new OneShotBehavior(null)); mExperience.Changed += Experience_Changed; foreach (Stat stat in Stats) { stat.Changed += Stat_Changed; } // pick all of the random stat gains for levelling up. pick them now so that we // can unwind and rewind them as exp is drained and regained. mStatGains = new Stat[MaxLevel]; for (int i = 0; i < mStatGains.Length; i++) { mStatGains[i] = Rng.Item(Stats); } RefreshMaxHealth(); RefreshAutoHealTimer(); }
private bool MakePit(Connector connector) { // pits use room size right now int width = Rng.Int(mWriter.Options.RoomSizeMin, mWriter.Options.RoomSizeMax); int height = Rng.Int(mWriter.Options.RoomSizeMin, mWriter.Options.RoomSizeMax); Rect bounds = CreateRectRoom(connector, width, height); // bail if we failed if (bounds == Rect.Empty) { return(false); } // light it mWriter.LightRect(bounds, mDepth); // choose a group IList <Race> races = mWriter.Content.Races.AllInGroup(Rng.Item(mWriter.Content.Races.Groups)); // make sure we've got some races that aren't too out of depth races = new List <Race>(races.Where(race => race.Depth <= mDepth + 10)); if (races.Count == 0) { return(false); } // place the room foreach (Vec pos in bounds) { mWriter.SetTile(pos, TileType.Floor); } RoomDecoration.DecorateInnerRoom(bounds, new RoomDecorator(this, pos => mWriter.AddEntity(new Monster(pos, Rng.Item(races))))); return(true); }
public override Attack GetAttack(Entity defender) { // pick one randomly return(Rng.Item(Race.Attacks)); }
protected override ActionResult OnProcess() { if (Entity.StandsFirm(mHit)) { Log(LogType.Resist, "{subject} stand[s] firm against the waves."); return(ActionResult.Done); } // get a weighted list of the possible destinations List <Vec> positions = new List <Vec>(); Vec straight = Entity.Position + mHit.Direction; Vec left45 = Entity.Position + mHit.Direction.Previous; Vec right45 = Entity.Position + mHit.Direction.Next; Vec left90 = Entity.Position + mHit.Direction.RotateLeft90; Vec right90 = Entity.Position + mHit.Direction.RotateRight90; // directly away if (Entity.CanOccupy(straight)) { // more likely than other directions positions.Add(straight); positions.Add(straight); positions.Add(straight); positions.Add(straight); } // off to one side if (Entity.CanOccupy(left45)) { positions.Add(left45); positions.Add(left45); } // off to the other side if (Entity.CanOccupy(right45)) { positions.Add(right45); positions.Add(right45); } // off to one side if (Entity.CanOccupy(left90)) { positions.Add(left90); } // off to the other side if (Entity.CanOccupy(right90)) { positions.Add(right90); } // fail if nowhere to be pushed if (positions.Count == 0) { return(ActionResult.Fail); } // pick a random direction AddEffect(new Effect(Entity.Position, EffectType.Teleport, mHit.Attack.Element)); Entity.Position = Rng.Item(positions); AddEffect(new Effect(Entity.Position, EffectType.Teleport, mHit.Attack.Element)); Log(LogType.BadState, "{subject} [are|is] knocked back by the water!"); return(ActionResult.Done); }
private TileType ChooseInnerWall() { return(Rng.Item(new TileType[] { TileType.Wall, TileType.LowWall })); }