private void ChooseActiveEmotion() { EmotionData.EmotionStruct selectedEmoStruct = poolEmotions.RemoveRandom(); // Randomly select and remove an emotion from pool poolEmotions.Add(currentTargetEmotionStruct); // Put last emotion back into deck currentTargetEmotionStruct = selectedEmoStruct; // Select emotion chosen by random activeEmotions.Add(selectedEmoStruct); // Add to active emotions deck }
public void WhenHasTwoElements_AndCalledTwice_ThenRemove() { ICollection <int> sut = new List <int> { 1, 5 }; sut.RemoveRandom(); sut.RemoveRandom(); Assert.That(sut, Is.Empty); }
void SpawnPickups() { BMazeManager.Instance.SetFactoryScale(factoryScale); List <GameObject> prefabList = new List <GameObject> (); prefabList.AddRange(BMazeManager.Instance.GetFactoryList()); int listCount = prefabList.Count; if (numOfPickupsToSpawn < listCount) { listCount = numOfPickupsToSpawn; } for (int i = 0; i < listCount; i++) { BMazeManager.Instance.OrderPickupFromFactory(prefabList.RemoveRandom(), pointList.RemoveRandom()); numOfPickupsToSpawn--; } prefabList.AddRange(BMazeManager.Instance.GetFactoryList()); for (int i = 0; i < numOfPickupsToSpawn; i++) { BMazeManager.Instance.OrderPickupFromFactory(prefabList.GetRandomItem(), pointList.RemoveRandom()); } }
/// <summary> /// Select a random card in this zone and move it to targetZone /// </summary> /// <param name="targetZone"></param> public void MoveRandom(CardZone targetZone) { if (cards.Count > 0) { targetZone.Add(cards.RemoveRandom()); } }
IEnumerator GeneratePickups() { while (BMazeManager.Instance.CreatePickup(cellList.RemoveRandom())) { ; } yield return(null); }
void AddFoodsToList(List <GameObject> goList) { // Go through given list choose 5 different foods for (int i = 0; i < 5; i++) { foods.Add(goList.RemoveRandom()); } }
public void TestListRemoveRandom() { List <int> list = new List <int> { 1, 2, 3 }; int removed = list.RemoveRandom(); Assert.IsTrue(!list.Contains(removed)); }
void SetupWorld() { FindAllLootObjects(); //decide who is corrupt var copy = new List <President>(presidents); foreach (var pres in presidents) { pres.isClean = false; } presidents.RandomItem().isClean = true; for (int i = 0; i < presidentNotesDuplicates; i++) { foreach (President pres in presidents) { if (pres.isClean) { foreach (var doc in pres.whenCleanNotes) { LootableDocumentHolder place = lootables.RemoveRandom(); place.document = doc; } } else { foreach (var doc in pres.whenCorruptNotes) { LootableDocumentHolder place = lootables.RemoveRandom(); place.document = doc; } } } } for (int i = 0; i < fillerDuplicates; i++) { foreach (var doc in filler) { LootableDocumentHolder place = lootables.RemoveRandom(); place.document = doc; } } }
public static void Randomize <T>(this List <T> l) { List <T> temp = new List <T>(l); l.Clear(); while (temp.Count > 0) { l.Add(temp.RemoveRandom()); } }
public void WhenHasThreeElements_ThenReturnRandomElement() { ICollection <int> sut = new List <int> { 1, 5, 10 }; var result = sut.RemoveRandom(); Assert.That(result, Is.EqualTo(1).Or.EqualTo(5).Or.EqualTo(10)); Assert.That(sut.Count, Is.EqualTo(2)); }
public void WhenHasSingleElement_ThenReturnElmentAndRemove() { ICollection <int> sut = new List <int> { 1 }; var result = sut.RemoveRandom(); Assert.That(result, Is.EqualTo(1)); Assert.That(sut, Is.Empty); }
void CreateFoodInDishes() { for (int i = 0; i < numberOfDishes; ++i) { GameObject newChosenFood = foodList.RemoveRandom(); DishObject dishComponent = dishes[i].GetComponent <DishObject> (); GameObject newFoodObject = SpawnFood(newChosenFood, dishComponent.lid.transform, dishComponent.dish.transform, foodScale, true); dishComponent.SetFood(newFoodObject); activeFoodList.Add(newFoodObject); } }
public static void AnimateStorm(pos origin, int radius, int num_frames, int num_per_frame, colorchar ch) { for (int i = 0; i < num_frames; ++i) { List <pos> cells = new List <pos>(); List <pos> nearby = origin.PositionsWithinDistance(radius); for (int j = 0; j < num_per_frame; ++j) { cells.Add(nearby.RemoveRandom()); } Screen.AnimateMapCells(cells, ch); } }
public static List <T> GetRandomSorted <T>(this IList <T> self) { var myList = new List <T>(self); var list = new List <T>(); while (myList.Count > 0) { list.Add(myList.RemoveRandom()); } return(list); }
/// <summary> /// Find a random path from "start". Tries to go in a random direction. /// </summary> /// <param name="start">Start position.</param> /// <param name="minSteps">Minimal number of steps. Please note that this might not be feasible.</param> /// <param name="maxSteps">Maximum number of steps. Please note that this might not be feasible.</param> /// <returns> /// List of nodes to walk in order to go from "start". /// Might be empty if it's impossible to go anywhere from the node (i.e a node with no neighbours). /// Will be null if "start" node cannot be found. /// </returns> public List <Node> FindRandomWalk(Vector3 start, int minSteps, int maxSteps) { if (minSteps > maxSteps) { throw new ArgumentException($"{nameof(minSteps)} must be lower or equal to {nameof(maxSteps)}"); } var startNode = navigationMesh.Find(start); if (startNode == null) { return(null); } var path = new List <Node>(); var pathOptions = new List <Node>(); var nbSteps = random.Next(minSteps, maxSteps); var currentNode = startNode; var sqrDistanceFromStart = 0f; for (var i = 0; i < nbSteps && currentNode != null; i++) { pathOptions.AddRange(currentNode.Neighbours); while (pathOptions.Count > 0) { currentNode = pathOptions.RemoveRandom(random); var sqDistanceToCurrentNode = startNode.Position.SqrDistanceTo(currentNode.Position); if (sqDistanceToCurrentNode > sqrDistanceFromStart) { path.Add(currentNode); sqrDistanceFromStart = sqDistanceToCurrentNode; break; } //No more options are available to go farther. if (pathOptions.Count == 0) { currentNode = null; break; } } pathOptions.Clear(); } return(path); }
public void ChooseFoodToMatch() { if (currentFoodToMatch) { Destroy(currentFoodToMatch); } selectedFood = activeFoodList.RemoveRandom(); if (activeFoodList.Count >= 0) { currentFoodToMatch = SpawnFood(selectedFood, foodToMatchSpawnPos, foodToMatchSpawnPos, foodToMatchScale, false); currentFoodToMatch.GetComponent <SpriteRenderer> ().sortingLayerName = "UI"; currentFoodToMatch.GetComponent <SpriteRenderer> ().sortingOrder = 5; } }
public List <Landmass> FindLandmasses() { var result = new List <Landmass>(); var unprocessed = new List <MapCell>(); unprocessed.AddRange(this.Map.MapCells.Values.Where(x => x.IsLand)); uint landmassId = 0; while (unprocessed.Count > 0) { var root = unprocessed.RemoveRandom <MapCell>(); var scanned = new List <MapCell>() { root }; var adjacencies = new List <MapCell>(); adjacencies.AddRange(root.AdjacentMapCells.Where(x => x.IsLand)); while (adjacencies.Count > 0) { var landcell = adjacencies[adjacencies.Count - 1]; scanned.Add(landcell); adjacencies.Remove(landcell); unprocessed.Remove(landcell); adjacencies.AddRange(landcell.AdjacentMapCells.Where(x => x.IsLand && !scanned.Contains(x))); } scanned.AddRange(adjacencies); var landmass = new Landmass() { MapCells = scanned, MapCellIds = scanned.Select(x => x.Id).ToList(), Id = landmassId++ }; landmass.X = (int)landmass.MapCells.Average(cell => cell.X); landmass.Y = (int)landmass.MapCells.Average(cell => cell.Y); result.Add(landmass); } return(result); }
private static List<BoundsInt> GenerateRooms(Vector2Int mapSizes, System.Random rng) { int maxWidth = mapSizes.x % 2 == 1 ? mapSizes.x / 2 : mapSizes.x / 2 - 1; int maxHeight = mapSizes.y % 2 == 1 ? mapSizes.y / 2 : mapSizes.y / 2 - 1; List<int> slots = Enumerable.Range(0, (mapSizes.x - 1) * (mapSizes.y - 1)).ToList(); List<BoundsInt> rooms = Enumerable.Range(0, rng.Next(8, 15)).Select(i => { int slot = slots.RemoveRandom(rng); return new BoundsInt( slot % (mapSizes.x - 1), slot / (mapSizes.x - 1), 0, 0, 0, 0 ); }).ToList(); bool allowContact = true; // allow contacting rooms only once for (int i = rooms.Count - 1; i >= 0; i--) { BoundsInt room = rooms[i]; int maxPotential = GetRoomMaxPotential(mapSizes, rooms, room, 1); if (!allowContact) maxPotential -= 1; if (maxPotential < 1) rooms.Remove(room); else { int sizeX = rng.Next(1, Mathf.Min(maxWidth, maxPotential)); int sizeY = rng.Next(1, Mathf.Min(maxHeight, maxPotential)); // TODO not guaranteed contact here if (sizeX == maxPotential || sizeY == maxPotential) allowContact = false; rooms[i] = new BoundsInt( room.position, new Vector3Int(sizeX, sizeY, 0) ); } }; return rooms; }
public List <Node> FindRandomWalk(Vector3 start, int maxSteps, float maxDistanceToStart = float.MaxValue) { var startNode = navigationMesh.Find(start, maxDistanceToStart); if (startNode == null) { return(null); } var path = new List <Node>(); var pathOptions = new List <Node>(); var nbSteps = random.Next(1, maxSteps); var currentNode = startNode; var sqrDistanceFromStart = 0f; for (var i = 0; i < nbSteps && currentNode != null; i++) { pathOptions.AddRange(currentNode.Neighbours); while (pathOptions.Count > 0) { currentNode = pathOptions.RemoveRandom(random); var sqDistanceToCurrentNode = startNode.Position2D.SqrDistanceTo(currentNode.Position2D); if (sqDistanceToCurrentNode > sqrDistanceFromStart) { path.Add(currentNode); sqrDistanceFromStart = sqDistanceToCurrentNode; break; } //No more options are available to go farther. if (pathOptions.Count == 0) { currentNode = null; break; } } pathOptions.Clear(); } return(path); }
public static List <Vector2Int> GeneratePoisson(int width, int height, float minDist, int numPoints, Func <Vector2Int, float> weightFunction = null) { var processList = new List <Vector2Int>(); var samplePoints = new List <Vector2Int>(); int randX = (int)(UnityEngine.Random.value * width - width / 2f); int randY = (int)(UnityEngine.Random.value * height - height / 2f); Vector2Int firstPoint = new Vector2Int(randX, randY); processList.Add(firstPoint); samplePoints.Add(firstPoint); //generate other points from points in queue. while (processList.Count > 0) { Vector2Int point = processList.RemoveRandom(); for (int i = 0; i < numPoints; i++) { Vector2Int newPoint = weightFunction != null ? GenerateWeightedPoint(point, minDist, weightFunction) : GenerateRandomPointAround(point, minDist); //check that the point is in the image region //and no points exists in the point's neighbourhood if (InNeighbourhood(samplePoints, newPoint, minDist) == false) { //update containers processList.Add(newPoint); samplePoints.Add(newPoint); } } } return(samplePoints); }
public void AddTrees(int number_of_trees,int minimum_distance_from_walls,int minimum_distance_from_other_trees) { List<pos> locations = new List<pos>(); //if number_of_trees is negative, keep going until no more can be placed. while(true){ bool[,] valid = new bool[H,W]; for(int i=0;i<H;++i){ for(int j=0;j<W;++j){ valid[i,j] = true; } } for(int i=0;i<H;++i){ for(int j=0;j<W;++j){ if(map[i,j].IsWall()){ for(int s=i-minimum_distance_from_walls;s<=i+minimum_distance_from_walls;++s){ for(int t=j-minimum_distance_from_walls;t<=j+minimum_distance_from_walls;++t){ if(BoundsCheck(s,t)){ valid[s,t] = false; } } } } else{ if(map[i,j].Is(CellType.Tree)){ for(int s=i-minimum_distance_from_other_trees;s<=i+minimum_distance_from_other_trees;++s){ for(int t=j-minimum_distance_from_other_trees;t<=j+minimum_distance_from_other_trees;++t){ if(BoundsCheck(s,t)){ valid[s,t] = false; } } } } } } } locations.Clear(); for(int i=0;i<H;++i){ for(int j=0;j<W;++j){ if(valid[i,j] && map[i,j].IsFloor()){ locations.Add(new pos(i,j)); } } } if(locations.Count == 0){ return; //nothing left to do } for(int tries_left = Math.Min(50,locations.Count);tries_left > 0;--tries_left){ pos p = locations.RemoveRandom(); int i = p.row; int j = p.col; bool good = true; for(int s=i-minimum_distance_from_walls;s<=i+minimum_distance_from_walls;++s){ for(int t=j-minimum_distance_from_walls;t<=j+minimum_distance_from_walls;++t){ if(BoundsCheck(s,t) && map[s,t].IsWall()){ good = false; } } } for(int s=i-minimum_distance_from_other_trees;s<=i+minimum_distance_from_other_trees;++s){ for(int t=j-minimum_distance_from_other_trees;t<=j+minimum_distance_from_other_trees;++t){ if(BoundsCheck(s,t) && map[s,t].Is(CellType.Tree)){ good = false; } } } if(good){ map[p] = CellType.Tree; ++tries_left; if(number_of_trees > 0){ --number_of_trees; if(number_of_trees == 0){ return; } } } } } }
public void GenerateLevel() { if(current_level < 20){ ++current_level; } InitializeNewLevel(); PosArray<CellType> map = GenerateMap(level_types[current_level-1]); List<pos> interesting_tiles = new List<pos>(); for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(map[i,j] == CellType.InterestingLocation){ interesting_tiles.Add(new pos(i,j)); } } } int attempts = 0; List<CellType> shrines = null; if(current_level % 2 == 1){ shrines = new List<CellType>{CellType.SpecialFeature1,CellType.SpecialFeature2,CellType.SpecialFeature3,CellType.SpecialFeature4,CellType.SpecialFeature5}; shrines.Randomize(); // after this initialization step, shrines are only removed from the front of the list, to enable prediction of pairs. nextLevelShrines = new List<CellType>(); int n = R.Between(0,5); for(int i=0;i<n;++i){ nextLevelShrines.Add(shrines.RemoveLast()); } } else{ shrines = nextLevelShrines; nextLevelShrines = null; } while(shrines.Count > 0){ attempts = 0; for(bool done=false;!done;++attempts){ int rr = R.Roll(ROWS-4) + 1; int rc = R.Roll(COLS-4) + 1; if(interesting_tiles.Count > 0){ pos p = interesting_tiles.RemoveRandom(); rr = p.row; rc = p.col; map[p] = CellType.RoomInterior; } pos temp = new pos(rr,rc); if(shrines.Count > 1){ if(map[rr,rc].IsFloor()){ if(attempts > 1000){ bool good = true; foreach(pos p in temp.PositionsWithinDistance(4,map)){ CellType ch = map[p]; if(ch.Is(CellType.SpecialFeature1,CellType.SpecialFeature2,CellType.SpecialFeature3,CellType.SpecialFeature4,CellType.SpecialFeature5)){ good = false; } } if(good){ List<pos> dist2 = new List<pos>(); foreach(pos p2 in temp.PositionsAtDistance(2,map)){ if(map[p2].IsFloor()){ dist2.Add(p2); } } if(dist2.Count > 0){ map[rr,rc] = shrines.RemoveFirst(); pos p2 = dist2.Random(); map[p2.row,p2.col] = shrines.RemoveFirst(); done = true; break; } } else{ interesting_tiles.Remove(temp); } } bool floors = true; foreach(pos p in temp.PositionsAtDistance(1,map)){ if(!map[p.row,p.col].IsFloor()){ floors = false; } } foreach(pos p in temp.PositionsWithinDistance(3,map)){ CellType ch = map[p]; if(ch.Is(CellType.SpecialFeature1,CellType.SpecialFeature2,CellType.SpecialFeature3,CellType.SpecialFeature4,CellType.SpecialFeature5)){ floors = false; } } if(floors){ if(R.CoinFlip()){ map[rr-1,rc] = shrines.RemoveFirst(); map[rr+1,rc] = shrines.RemoveFirst(); } else{ map[rr,rc-1] = shrines.RemoveFirst(); map[rr,rc+1] = shrines.RemoveFirst(); } CellType center = CellType.Wall; while(center == CellType.Wall){ switch(R.Roll(5)){ case 1: if(level_types[current_level-1] != LevelType.Hive && level_types[current_level-1] != LevelType.Garden){ center = CellType.Pillar; } break; case 2: center = CellType.Statue; break; case 3: if(level_types[current_level-1] != LevelType.Garden){ center = CellType.FirePit; } break; case 4: center = CellType.ShallowWater; break; case 5: if(level_types[current_level-1] != LevelType.Hive){ center = CellType.Torch; } break; } } map[rr,rc] = center; interesting_tiles.Remove(temp); done = true; break; } } } else{ if(map[rr,rc].IsFloor()){ bool good = true; foreach(pos p in temp.PositionsWithinDistance(2,map)){ CellType ch = map[p]; if(ch.Is(CellType.SpecialFeature1,CellType.SpecialFeature2,CellType.SpecialFeature3,CellType.SpecialFeature4,CellType.SpecialFeature5)){ good = false; } } if(good){ if(attempts > 1000){ map[rr,rc] = shrines.RemoveFirst(); interesting_tiles.Remove(temp); done = true; break; } else{ bool floors = true; foreach(pos p in temp.PositionsAtDistance(1,map)){ if(!map[p.row,p.col].IsFloor()){ floors = false; } } if(floors){ map[rr,rc] = shrines.RemoveFirst(); interesting_tiles.Remove(temp); done = true; break; } } } } if(map[rr,rc].IsWall()){ if(!map[rr+1,rc].IsFloor() && !map[rr-1,rc].IsFloor() && !map[rr,rc-1].IsFloor() && !map[rr,rc+1].IsFloor()){ continue; //no floors? retry. } bool no_good = false; foreach(pos p in temp.PositionsWithinDistance(2,map)){ CellType ch = map[p]; if(ch.Is(CellType.SpecialFeature1,CellType.SpecialFeature2,CellType.SpecialFeature3,CellType.SpecialFeature4,CellType.SpecialFeature5)){ no_good = true; } } if(no_good){ continue; } int walls = 0; foreach(pos p in temp.PositionsAtDistance(1,map)){ if(map[p].IsWall()){ ++walls; } } if(walls >= 5){ int successive_walls = 0; CellType[] rotated = new CellType[8]; for(int i=0;i<8;++i){ pos temp2; temp2 = temp.PosInDir(8.RotateDir(true,i)); rotated[i] = map[temp2.row,temp2.col]; } for(int i=0;i<15;++i){ if(rotated[i%8].IsWall()){ ++successive_walls; } else{ successive_walls = 0; } if(successive_walls == 5){ done = true; map[rr,rc] = shrines.RemoveFirst(); interesting_tiles.Remove(temp); break; } } } } } } } int num_chests = R.Between(0,1); if(level_types[current_level-1] == LevelType.Crypt){ num_chests -= map.PositionsWhere(x=>map[x] == CellType.Chest).Count; } for(int i=0;i<num_chests;++i){ int tries = 0; for(bool done=false;!done;++tries){ int rr = R.Roll(ROWS-4) + 1; int rc = R.Roll(COLS-4) + 1; if(interesting_tiles.Count > 0){ pos p = interesting_tiles.RemoveRandom(); rr = p.row; rc = p.col; map[rr,rc] = CellType.RoomInterior; } if(map[rr,rc].IsFloor()){ bool floors = true; pos temp = new pos(rr,rc); foreach(pos p in temp.PositionsAtDistance(1,map)){ if(!map[p.row,p.col].IsFloor()){ floors = false; } } if(floors || tries > 1000){ //after 1000 tries, place it anywhere map[rr,rc] = CellType.Chest; done = true; } } } } attempts = 0; for(bool done=false;!done;++attempts){ int rr = R.Roll(ROWS-4) + 1; int rc = R.Roll(COLS-4) + 1; if(interesting_tiles.Count > 0){ pos p = interesting_tiles.RemoveRandom(); rr = p.row; rc = p.col; map[p] = CellType.RoomInterior; } if(map[rr,rc].IsFloor()){ bool floors = true; pos temp = new pos(rr,rc); foreach(pos p in temp.PositionsAtDistance(1,map)){ if(!map[p.row,p.col].IsFloor()){ floors = false; } } if(floors || attempts > 1000){ map[rr,rc] = CellType.Stairs; done = true; } } } if(level_types[current_level-1] != LevelType.Garden){ GenerateFloorTypes(map); GenerateFeatures(map,interesting_tiles); } int num_traps = R.Roll(2,3); for(int i=0;i<num_traps;++i){ int tries = 0; for(bool done=false;!done && tries < 100;++tries){ int rr = R.Roll(ROWS-2); int rc = R.Roll(COLS-2); if(map[rr,rc].IsFloor() && map[rr,rc] != CellType.ShallowWater){ map[rr,rc] = CellType.Trap; done = true; } } } List<Tile> hidden = new List<Tile>(); Event grave_dirt_event = null; Event poppy_event = null; Tile stairs = null; for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ //Screen.WriteMapChar(i,j,map[i,j]); switch(map[i,j]){ case CellType.Wall: case CellType.Pillar: Tile.Create(TileType.WALL,i,j); break; case CellType.Door: if(R.OneIn(120)){ if(R.CoinFlip()){ Tile.Create(TileType.STONE_SLAB,i,j); Q.Add(new Event(tile[i,j],new List<Tile>{tile[i,j]},100,EventType.STONE_SLAB)); } else{ Tile.Create(TileType.HIDDEN_DOOR,i,j); hidden.Add(tile[i,j]); } } else{ Tile.Create(TileType.DOOR_C,i,j); } break; case CellType.Stairs: if(current_level < 20){ Tile.Create(TileType.STAIRS,i,j); stairs = tile[i,j]; } else{ if(current_level == 20){ Tile.Create(TileType.STAIRS,i,j); tile[i,j].color = Color.Red; tile[i,j].SetName("scorched stairway"); stairs = tile[i,j]; } else{ Tile.Create(TileType.FLOOR,i,j); } } break; case CellType.Statue: Tile.Create(TileType.STATUE,i,j); break; case CellType.Rubble: Tile.Create(TileType.RUBBLE,i,j); break; case CellType.FirePit: Tile.Create(TileType.FIREPIT,i,j); break; case CellType.Pool: Tile.Create(TileType.POOL_OF_RESTORATION,i,j); break; case CellType.BlastFungus: Tile.Create(TileType.BLAST_FUNGUS,i,j); break; case CellType.CrackedWall: Tile.Create(TileType.CRACKED_WALL,i,j); break; case CellType.Chest: Tile.Create(TileType.CHEST,i,j); break; case CellType.Trap: { TileType type = Tile.RandomTrap(); Tile.Create(type,i,j); if(tile[i,j].IsTrap()){ tile[i,j].name = "floor"; tile[i,j].the_name = "the floor"; tile[i,j].a_name = "a floor"; tile[i,j].symbol = '.'; tile[i,j].color = Color.White; hidden.Add(tile[i,j]); } break; } case CellType.Geyser: Tile.Create(TileType.FIRE_GEYSER,i,j); int frequency = R.Roll(31) + 8; //9-39 int variance = R.Roll(10) - 1; //0-9 int variance_amount = (frequency * variance) / 10; int number_of_values = variance_amount*2 + 1; int minimum_value = frequency - variance_amount; if(minimum_value < 5){ int diff = 5 - minimum_value; number_of_values -= diff; minimum_value = 5; } int delay = ((minimum_value - 1) + R.Roll(number_of_values)) * 100; Q.Add(new Event(tile[i,j],delay + 200,EventType.FIRE_GEYSER,(frequency*10)+variance)); //notice the hacky way the value is stored Q.Add(new Event(tile[i,j],delay,EventType.FIRE_GEYSER_ERUPTION,2)); break; case CellType.FogVent: Tile.Create(TileType.FOG_VENT,i,j); Q.Add(new Event(tile[i,j],100,EventType.FOG_VENT)); break; case CellType.PoisonVent: Tile.Create(TileType.POISON_GAS_VENT,i,j); Q.Add(new Event(tile[i,j],100,EventType.POISON_GAS_VENT)); break; case CellType.Tombstone: { Tile t = Tile.Create(TileType.TOMBSTONE,i,j); if(R.OneIn(8)){ Q.Add(new Event(null,new List<Tile>{t},100,EventType.TOMBSTONE_GHOST)); } break; } case CellType.HiddenDoor: Tile.Create(TileType.HIDDEN_DOOR,i,j); hidden.Add(tile[i,j]); break; case CellType.SpecialFeature1: Tile.Create(TileType.COMBAT_SHRINE,i,j); break; case CellType.SpecialFeature2: Tile.Create(TileType.DEFENSE_SHRINE,i,j); break; case CellType.SpecialFeature3: Tile.Create(TileType.MAGIC_SHRINE,i,j); break; case CellType.SpecialFeature4: Tile.Create(TileType.SPIRIT_SHRINE,i,j); break; case CellType.SpecialFeature5: Tile.Create(TileType.STEALTH_SHRINE,i,j); break; case CellType.DeepWater: case CellType.ShallowWater: Tile.Create(TileType.WATER,i,j); break; case CellType.Barrel: Tile.Create(TileType.BARREL,i,j); break; case CellType.Brush: Tile.Create(TileType.BRUSH,i,j); break; case CellType.GlowingFungus: Tile.Create(TileType.GLOWING_FUNGUS,i,j); break; case CellType.GraveDirt: Tile.Create(TileType.GRAVE_DIRT,i,j); if(grave_dirt_event == null){ grave_dirt_event = new Event(new List<Tile>{tile[i,j]},100,EventType.GRAVE_DIRT); Q.Add(grave_dirt_event); } else{ grave_dirt_event.area.Add(tile[i,j]); } break; case CellType.Gravel: Tile.Create(TileType.GRAVEL,i,j); break; case CellType.Ice: Tile.Create(TileType.ICE,i,j); break; case CellType.Oil: Tile.Create(TileType.FLOOR,i,j); tile[i,j].AddFeature(FeatureType.OIL); break; case CellType.PoisonBulb: Tile.Create(TileType.POISON_BULB,i,j); break; case CellType.Poppies: Tile.Create(TileType.POPPY_FIELD,i,j); if(poppy_event == null){ poppy_event = new Event(new List<Tile>{tile[i,j]},100,EventType.POPPIES); Q.Add(poppy_event); } else{ poppy_event.area.Add(tile[i,j]); } break; case CellType.Slime: Tile.Create(TileType.FLOOR,i,j); tile[i,j].AddFeature(FeatureType.SLIME); break; case CellType.Torch: Tile.Create(TileType.STANDING_TORCH,i,j); break; case CellType.Vine: Tile.Create(TileType.VINE,i,j); break; case CellType.Webs: Tile.Create(TileType.FLOOR,i,j); tile[i,j].AddFeature(FeatureType.WEB); break; default: Tile.Create(TileType.FLOOR,i,j); break; } //alltiles.Add(tile[i,j]); tile[i,j].solid_rock = true; } } //Input.ReadKey(); player.ResetForNewLevel(); foreach(Tile t in AllTiles()){ if(t.light_radius > 0){ t.UpdateRadius(0,t.light_radius); } } int num_items = R.Between(0,2); for(int i=num_items;i>0;--i){ SpawnItem(); } bool poltergeist_spawned = false; //i'm not sure this is the right call, but for now bool mimic_spawned = false; // i'm limiting these guys, to avoid "empty" levels bool marble_horror_spawned = false; int num_monsters = R.Roll(2,2) + 4; if(num_monsters == 6){ num_monsters += R.Roll(3); //this works out to 7/12 seven, 4/12 eight, and 1/12 nine. } for(int i=num_monsters;i>0;--i){ ActorType type = MobType(); if(type == ActorType.POLTERGEIST){ if(!poltergeist_spawned){ SpawnMob(type); poltergeist_spawned = true; } else{ ++i; //try again.. } } else{ if(type == ActorType.MIMIC){ if(!mimic_spawned){ SpawnMob(type); mimic_spawned = true; } else{ ++i; } } else{ if(type == ActorType.MARBLE_HORROR){ Tile statue = AllTiles().Where(t=>t.type == TileType.STATUE).RandomOrDefault(); if(!marble_horror_spawned && statue != null){ SpawnMob(type); marble_horror_spawned = true; } else{ ++i; } } else{ if(type == ActorType.ENTRANCER){ if(i >= 2){ //need 2 slots here Actor entrancer = SpawnMob(type); entrancer.attrs[AttrType.WANDERING]++; List<Tile> tiles = new List<Tile>(); int dist = 1; while(tiles.Count == 0 && dist < 100){ foreach(Tile t in entrancer.TilesAtDistance(dist)){ if(t.passable && !t.IsTrap() && t.actor() == null){ tiles.Add(t); } } ++dist; } if(tiles.Count > 0){ ActorType thralltype = ActorType.SPECIAL; bool done = false; while(!done){ thralltype = MobType(); switch(thralltype){ //not on the list: group/stealth/ranged/rare/immobile/hazardous monsters, plus carrion crawler, mechanical knight, mud elemental, and luminous avenger case ActorType.ROBED_ZEALOT: case ActorType.WILD_BOAR: case ActorType.TROLL: case ActorType.DERANGED_ASCETIC: case ActorType.ALASI_BATTLEMAGE: case ActorType.ALASI_SOLDIER: case ActorType.SKITTERMOSS: case ActorType.STONE_GOLEM: case ActorType.OGRE_BARBARIAN: case ActorType.SNEAK_THIEF: case ActorType.CRUSADING_KNIGHT: case ActorType.CORROSIVE_OOZE: case ActorType.ALASI_SENTINEL: case ActorType.CYCLOPEAN_TITAN: done = true; break; } } Tile t = tiles.Random(); Actor thrall = Actor.Create(thralltype,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent); if(entrancer.group == null){ entrancer.group = new List<Actor>{entrancer}; } entrancer.group.Add(thrall); thrall.group = entrancer.group; --i; } } else{ ++i; } } else{ Actor a = SpawnMob(type); if(type == ActorType.WARG){ if(a.group != null){ foreach(Actor a2 in a.group){ a2.attrs[AttrType.WANDERING]++; } } } else{ if(a.AlwaysWanders() || (R.PercentChance(40) && a.CanWanderAtLevelGen())){ a.attrs[AttrType.WANDERING]++; } } } } } } } for(int i=(current_level-3)/4;i>0;--i){ //yes, this is all copied and pasted for a one-line change. i'll try to fix it later. if(R.CoinFlip()){ //generate some shallow monsters ActorType type = ShallowMobType(); if(type == ActorType.POLTERGEIST){ if(!poltergeist_spawned){ SpawnMob(type); poltergeist_spawned = true; } else{ ++i; //try again.. } } else{ if(type == ActorType.MIMIC){ if(!mimic_spawned){ SpawnMob(type); mimic_spawned = true; } else{ ++i; } } else{ if(type == ActorType.MARBLE_HORROR){ Tile statue = AllTiles().Where(t=>t.type == TileType.STATUE).RandomOrDefault(); if(!marble_horror_spawned && statue != null){ SpawnMob(type); marble_horror_spawned = true; } else{ ++i; } } else{ if(type == ActorType.ENTRANCER){ if(i >= 2){ //need 2 slots here Actor entrancer = SpawnMob(type); entrancer.attrs[AttrType.WANDERING]++; List<Tile> tiles = new List<Tile>(); int dist = 1; while(tiles.Count == 0 && dist < 100){ foreach(Tile t in entrancer.TilesAtDistance(dist)){ if(t.passable && !t.IsTrap() && t.actor() == null){ tiles.Add(t); } } ++dist; } if(tiles.Count > 0){ ActorType thralltype = ActorType.SPECIAL; bool done = false; while(!done){ thralltype = MobType(); switch(thralltype){ case ActorType.ROBED_ZEALOT: case ActorType.DERANGED_ASCETIC: case ActorType.BERSERKER: case ActorType.TROLL: case ActorType.CRUSADING_KNIGHT: case ActorType.SKITTERMOSS: case ActorType.OGRE_BARBARIAN: case ActorType.SNEAK_THIEF: case ActorType.STONE_GOLEM: case ActorType.LUMINOUS_AVENGER: case ActorType.WILD_BOAR: case ActorType.ALASI_SOLDIER: case ActorType.CORROSIVE_OOZE: case ActorType.ALASI_SENTINEL: done = true; break; } } Tile t = tiles.Random(); Actor thrall = Actor.Create(thralltype,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent); if(entrancer.group == null){ entrancer.group = new List<Actor>{entrancer}; } entrancer.group.Add(thrall); thrall.group = entrancer.group; --i; } } else{ ++i; } } else{ Actor a = SpawnMob(type); if(type == ActorType.WARG){ if(a.group != null){ foreach(Actor a2 in a.group){ a2.attrs[AttrType.WANDERING]++; } } } else{ if(a.AlwaysWanders() || (R.PercentChance(40) && a.CanWanderAtLevelGen())){ a.attrs[AttrType.WANDERING]++; } } } } } } } } int minimum_distance_from_stairs = 0; PosArray<int> distance_from_stairs = null; if(stairs != null){ distance_from_stairs = tile.GetDijkstraMap(new List<pos>{stairs.p},x=>tile[x].BlocksConnectivityOfMap()); minimum_distance_from_stairs = distance_from_stairs[distance_from_stairs.PositionsWhere(x=>distance_from_stairs[x].IsValidDijkstraValue()).WhereGreatest(x=>distance_from_stairs[x]).Random()] / 2; } bool[,] good_location = new bool[ROWS,COLS]; for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(tile[i,j].Is(TileType.FLOOR) && !tile[i,j].Is(FeatureType.WEB) && (stairs == null || distance_from_stairs[i,j] >= minimum_distance_from_stairs)){ good_location[i,j] = true; } else{ good_location[i,j] = false; } } } foreach(Actor a in AllActors()){ if(a != player){ good_location[a.row,a.col] = false; for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(good_location[i,j] && a.HasLOS(i,j)){ good_location[i,j] = false; } } } } } bool at_least_one_good = false; for(int i=0;i<ROWS && !at_least_one_good;++i){ for(int j=0;j<COLS && !at_least_one_good;++j){ if(good_location[i,j]){ at_least_one_good = true; } } } if(!at_least_one_good){ foreach(Actor a in AllActors()){ if(a != player){ good_location[a.row,a.col] = false; for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(good_location[i,j] && a.CanSee(i,j)){ //checking CanSee this time good_location[i,j] = false; } } } } } } List<Tile> goodtiles = new List<Tile>(); for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(good_location[i,j]){ goodtiles.Add(tile[i,j]); } } } if(goodtiles.Count > 0){ Tile t = goodtiles.Random(); int light = player.light_radius; int burning = player.attrs[AttrType.BURNING]; int shining = player.attrs[AttrType.SHINING]; player.light_radius = 0; player.attrs[AttrType.BURNING] = 0; player.attrs[AttrType.SHINING] = 0; player.Move(t.row,t.col); player.light_radius = light; player.attrs[AttrType.BURNING] = burning; player.attrs[AttrType.SHINING] = shining; player.UpdateRadius(0,player.LightRadius()); } else{ for(bool done=false;!done;){ int rr = R.Roll(ROWS-2); int rc = R.Roll(COLS-2); bool good = true; foreach(Tile t in tile[rr,rc].TilesWithinDistance(1)){ if(t.IsTrap()){ good = false; } } if(good && tile[rr,rc].passable && actor[rr,rc] == null){ int light = player.light_radius; int burning = player.attrs[AttrType.BURNING]; int shining = player.attrs[AttrType.SHINING]; player.light_radius = 0; player.attrs[AttrType.BURNING] = 0; player.attrs[AttrType.SHINING] = 0; player.Move(rr,rc); player.light_radius = light; player.attrs[AttrType.BURNING] = burning; player.attrs[AttrType.SHINING] = shining; player.UpdateRadius(0,player.LightRadius()); done = true; } } } actor[player.row,player.col] = player; //this line fixes a bug that occurs when the player ends up in the same position on a new level Screen.screen_center_col = player.col; if(R.CoinFlip()){ //todo: copied and pasted below bool done = false; for(int tries=0;!done && tries<500;++tries){ int rr = R.Roll(ROWS-4) + 1; int rc = R.Roll(COLS-4) + 1; bool good = true; foreach(Tile t in tile[rr,rc].TilesWithinDistance(2)){ if(t.type != TileType.WALL){ good = false; break; } } if(good){ List<int> dirs = new List<int>(); bool long_corridor = false; int connections = 0; for(int i=2;i<=8;i+=2){ Tile t = tile[rr,rc].TileInDirection(i).TileInDirection(i); bool good_dir = true; int distance = -1; while(good_dir && t != null && t.type == TileType.WALL){ if(t.TileInDirection(i.RotateDir(false,2)).type != TileType.WALL){ good_dir = false; } if(t.TileInDirection(i.RotateDir(true,2)).type != TileType.WALL){ good_dir = false; } t = t.TileInDirection(i); if(t != null && t.type == TileType.STATUE){ good_dir = false; } ++distance; } if(good_dir && t != null){ dirs.Add(i); ++connections; if(distance >= 4){ long_corridor = true; } } } if(dirs.Count > 0){ List<TileType> all_possible_traps = new List<TileType>{TileType.GRENADE_TRAP,TileType.POISON_GAS_TRAP,TileType.PHANTOM_TRAP,TileType.FIRE_TRAP,TileType.SHOCK_TRAP,TileType.SCALDING_OIL_TRAP,TileType.FLING_TRAP,TileType.STONE_RAIN_TRAP}; List<TileType> possible_traps = new List<TileType>(); //int trap_roll = R.Roll(7); int num_types = R.Between(2,3); /*if(trap_roll == 1){ num_types = 1; } else{ if(trap_roll <= 4){ num_types = 2; } else{ num_types = 3; } }*/ for(int i=0;i<num_types;++i){ TileType tt = all_possible_traps.Random(); if(possible_traps.Contains(tt)){ --i; } else{ possible_traps.Add(tt); } } bool stone_slabs = false; //(instead of hidden doors) if(R.OneIn(4)){ stone_slabs = true; } foreach(int i in dirs){ Tile t = tile[rr,rc].TileInDirection(i); int distance = -2; //distance of the corridor between traps and secret door while(t.type == TileType.WALL){ ++distance; t = t.TileInDirection(i); } if(long_corridor && distance < 4){ continue; } t = tile[rr,rc].TileInDirection(i); while(t.type == TileType.WALL){ if(distance >= 4){ TileType tt = TileType.FLOOR; if(R.Roll(3) >= 2){ tt = possible_traps.Random(); hidden.Add(t); } t.TransformTo(tt); t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; if(t.DistanceFrom(tile[rr,rc]) < distance+2){ Tile neighbor = t.TileInDirection(i.RotateDir(false,2)); if(neighbor.TileInDirection(i.RotateDir(false,1)).type == TileType.WALL && neighbor.TileInDirection(i.RotateDir(false,2)).type == TileType.WALL && neighbor.TileInDirection(i.RotateDir(false,3)).type == TileType.WALL){ tt = TileType.FLOOR; if(R.Roll(3) >= 2){ tt = possible_traps.Random(); } neighbor.TransformTo(tt); if(possible_traps.Contains(tt)){ neighbor.name = "floor"; neighbor.the_name = "the floor"; neighbor.a_name = "a floor"; neighbor.symbol = '.'; neighbor.color = Color.White; hidden.Add(neighbor); } } neighbor = t.TileInDirection(i.RotateDir(true,2)); if(neighbor.TileInDirection(i.RotateDir(true,1)).type == TileType.WALL && neighbor.TileInDirection(i.RotateDir(true,2)).type == TileType.WALL && neighbor.TileInDirection(i.RotateDir(true,3)).type == TileType.WALL){ tt = TileType.FLOOR; if(R.Roll(3) >= 2){ tt = possible_traps.Random(); } neighbor.TransformTo(tt); if(possible_traps.Contains(tt)){ neighbor.name = "floor"; neighbor.the_name = "the floor"; neighbor.a_name = "a floor"; neighbor.symbol = '.'; neighbor.color = Color.White; hidden.Add(neighbor); } } } } else{ TileType tt = TileType.FLOOR; if(R.CoinFlip()){ tt = Tile.RandomTrap(); hidden.Add(t); } t.TransformTo(tt); if(tt != TileType.FLOOR){ t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; } } t = t.TileInDirection(i); } t = t.TileInDirection(i.RotateDir(true,4)); if(stone_slabs){ t.TransformTo(TileType.STONE_SLAB); Q.Add(new Event(t,new List<Tile>{t.TileInDirection(i.RotateDir(true,4))},100,EventType.STONE_SLAB)); } else{ t.TransformTo(TileType.HIDDEN_DOOR); hidden.AddUnique(t); } t = t.TileInDirection(i.RotateDir(true,4)); if(R.CoinFlip()){ if(t.IsTrap()){ t.type = TileType.ALARM_TRAP; } else{ t.TransformTo(TileType.ALARM_TRAP); t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; hidden.AddUnique(t); } } } if(long_corridor && connections == 1){ foreach(Tile t in tile[rr,rc].TilesWithinDistance(1)){ t.TransformTo(possible_traps.Random()); t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; hidden.Add(t); } tile[rr,rc].TileInDirection(dirs[0].RotateDir(true,4)).TransformTo(TileType.CHEST); tile[rr,rc].TileInDirection(dirs[0].RotateDir(true,4)).color = Color.Yellow; } else{ foreach(Tile t in tile[rr,rc].TilesAtDistance(1)){ t.TransformTo(Tile.RandomTrap()); t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; hidden.Add(t); } tile[rr,rc].TransformTo(TileType.CHEST); tile[rr,rc].color = Color.Yellow; } done = true; } } } } /*if(R.CoinFlip()){ bool done = false; for(int tries=0;!done && tries<500;++tries){ int rr = R.Roll(ROWS-4) + 1; int rc = R.Roll(COLS-4) + 1; bool good = true; foreach(Tile t in tile[rr,rc].TilesWithinDistance(2)){ if(t.type != TileType.WALL){ good = false; break; } } if(good){ List<int> dirs = new List<int>(); bool long_corridor = false; int connections = 0; for(int i=2;i<=8;i+=2){ Tile t = tile[rr,rc].TileInDirection(i).TileInDirection(i); bool good_dir = true; int distance = -1; while(good_dir && t != null && t.type == TileType.WALL){ if(t.TileInDirection(i.RotateDir(false,2)).type != TileType.WALL){ good_dir = false; } if(t.TileInDirection(i.RotateDir(true,2)).type != TileType.WALL){ good_dir = false; } t = t.TileInDirection(i); if(t != null && t.type == TileType.STATUE){ good_dir = false; } ++distance; } if(good_dir && t != null){ dirs.Add(i); ++connections; if(distance >= 4){ long_corridor = true; } } } if(dirs.Count > 0){ List<TileType> possible_traps = new List<TileType>(); int trap_roll = R.Roll(7); if(trap_roll == 1 || trap_roll == 4 || trap_roll == 5 || trap_roll == 7){ possible_traps.Add(TileType.GRENADE_TRAP); } if(trap_roll == 2 || trap_roll == 4 || trap_roll == 6 || trap_roll == 7){ possible_traps.Add(TileType.POISON_GAS_TRAP); } if(trap_roll == 3 || trap_roll == 5 || trap_roll == 6 || trap_roll == 7){ possible_traps.Add(TileType.PHANTOM_TRAP); } bool stone_slabs = false; //(instead of hidden doors) if(R.OneIn(4)){ stone_slabs = true; } foreach(int i in dirs){ Tile t = tile[rr,rc].TileInDirection(i); int distance = -2; //distance of the corridor between traps and secret door while(t.type == TileType.WALL){ ++distance; t = t.TileInDirection(i); } if(long_corridor && distance < 4){ continue; } t = tile[rr,rc].TileInDirection(i); while(t.type == TileType.WALL){ if(distance >= 4){ TileType tt = TileType.FLOOR; if(R.Roll(3) >= 2){ tt = possible_traps.Random(); hidden.Add(t); } t.TransformTo(tt); t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; if(t.DistanceFrom(tile[rr,rc]) < distance+2){ Tile neighbor = t.TileInDirection(i.RotateDir(false,2)); if(neighbor.TileInDirection(i.RotateDir(false,1)).type == TileType.WALL && neighbor.TileInDirection(i.RotateDir(false,2)).type == TileType.WALL && neighbor.TileInDirection(i.RotateDir(false,3)).type == TileType.WALL){ tt = TileType.FLOOR; if(R.Roll(3) >= 2){ tt = possible_traps.Random(); } neighbor.TransformTo(tt); if(possible_traps.Contains(tt)){ neighbor.name = "floor"; neighbor.the_name = "the floor"; neighbor.a_name = "a floor"; neighbor.symbol = '.'; neighbor.color = Color.White; hidden.Add(neighbor); } } neighbor = t.TileInDirection(i.RotateDir(true,2)); if(neighbor.TileInDirection(i.RotateDir(true,1)).type == TileType.WALL && neighbor.TileInDirection(i.RotateDir(true,2)).type == TileType.WALL && neighbor.TileInDirection(i.RotateDir(true,3)).type == TileType.WALL){ tt = TileType.FLOOR; if(R.Roll(3) >= 2){ tt = possible_traps.Random(); } neighbor.TransformTo(tt); if(possible_traps.Contains(tt)){ neighbor.name = "floor"; neighbor.the_name = "the floor"; neighbor.a_name = "a floor"; neighbor.symbol = '.'; neighbor.color = Color.White; hidden.Add(neighbor); } } } } else{ TileType tt = TileType.FLOOR; if(R.CoinFlip()){ tt = Tile.RandomTrap(); hidden.Add(t); } t.TransformTo(tt); if(tt != TileType.FLOOR){ t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; } } t = t.TileInDirection(i); } t = t.TileInDirection(i.RotateDir(true,4)); if(stone_slabs){ t.TransformTo(TileType.STONE_SLAB); Q.Add(new Event(t,new List<Tile>{t.TileInDirection(i.RotateDir(true,4))},100,EventType.STONE_SLAB)); } else{ t.TransformTo(TileType.HIDDEN_DOOR); hidden.AddUnique(t); } t = t.TileInDirection(i.RotateDir(true,4)); if(R.CoinFlip()){ if(t.IsTrap()){ t.type = TileType.ALARM_TRAP; } else{ t.TransformTo(TileType.ALARM_TRAP); t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; hidden.AddUnique(t); } } } if(long_corridor && connections == 1){ foreach(Tile t in tile[rr,rc].TilesWithinDistance(1)){ t.TransformTo(possible_traps.Random()); t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; hidden.Add(t); } tile[rr,rc].TileInDirection(dirs[0].RotateDir(true,4)).TransformTo(TileType.CHEST); tile[rr,rc].TileInDirection(dirs[0].RotateDir(true,4)).color = Color.Yellow; } else{ foreach(Tile t in tile[rr,rc].TilesAtDistance(1)){ t.TransformTo(Tile.RandomTrap()); t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; hidden.Add(t); } tile[rr,rc].TransformTo(TileType.CHEST); tile[rr,rc].color = Color.Yellow; } done = true; } } } }*/ foreach(Tile t in AllTiles()){ if(t.type != TileType.WALL){ foreach(Tile neighbor in t.TilesAtDistance(1)){ neighbor.solid_rock = false; } } if(t.type == TileType.GLOWING_FUNGUS){ foreach(Tile neighbor in t.TilesAtDistance(1)){ if(neighbor.type == TileType.WALL){ neighbor.color = Color.RandomGlowingFungus; } } } } if(level_types[current_level-1] == LevelType.Hive){ var dijkstra = tile.GetDijkstraMap(x=>tile[x].type != TileType.WALL,x=>false); for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if((dijkstra[i,j] == 1 && !R.OneIn(20)) || (dijkstra[i,j] == 2 && R.CoinFlip())){ if(i == 0 || j == 0 || i == ROWS-1 || j == COLS-1){ tile[i,j].color = Color.DarkYellow; tile[i,j].SetName("waxy wall"); //borders become waxy but don't burn } else{ tile[i,j].Toggle(null,TileType.WAX_WALL); } } } } } if(level_types[current_level-1] == LevelType.Fortress){ foreach(pos p in tile.PositionsWhere(x=>tile[x].Is(TileType.WALL,TileType.HIDDEN_DOOR))){ tile[p].color = Color.TerrainDarkGray; } } string desc = GetDungeonDescription(); for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ //todo fix this! if(!tile.BoundsCheck(i,j,false)) continue; pos p = new pos(i,j); if(SchismExtensionMethods.Extensions.ConsecutiveAdjacent(p,x=>!tile[x].BlocksConnectivityOfMap()) >= 3){ //todo, fix check! dungeon_description[i,j] = desc; } else{ dungeon_description[i,j] = "You pass through a hallway of hewn stone."; //todo, fix message! } } } if(poppy_event != null){ CalculatePoppyDistanceMap(); } if(hidden.Count > 0){ Event e = new Event(hidden,100,EventType.CHECK_FOR_HIDDEN); e.tiebreaker = 0; Q.Add(e); } { Event e = new Event(10000,EventType.RELATIVELY_SAFE); e.tiebreaker = 0; Q.Add(e); } { Event e = new Event(R.Between(400,450)*100,EventType.SPAWN_WANDERING_MONSTER); e.tiebreaker = 0; Q.Add(e); } if(current_level == 1){ B.Add("In the mountain pass where travelers vanish, a stone staircase leads downward... Welcome, " + Actor.player_name + "! "); } else{ B.Add(LevelMessage()); } }
public async Task<bool> Use(Actor user,List<Tile> line){ bool used = true; switch(itype){ case ConsumableType.HEALING: await user.TakeDamage(DamageType.HEAL,DamageClass.NO_TYPE,50,null); //was Roll(8,6) B.Add("A blue glow surrounds " + user.the_name + ". ",new PhysicalObject[]{user}); break; case ConsumableType.TOXIN_IMMUNITY: if(!user.HasAttr(AttrType.IMMUNE_TOXINS)){ if(user.HasAttr(AttrType.POISONED)){ user.attrs[AttrType.POISONED] = 0; B.Add(user.YouFeel() + " relieved. ",user); } user.GainAttr(AttrType.IMMUNE_TOXINS,5100,user.YouAre() + " no longer immune to toxins. ",new PhysicalObject[]{user}); } else{ B.Add("Nothing happens. ",user); } break; case ConsumableType.REGENERATION: { user.attrs[AttrType.REGENERATING]++; if(user.name == "you"){ B.Add("Your blood tingles. ",user); } else{ B.Add(user.the_name + " looks energized. ",user); } int duration = 60; //was Roll(10)+20 Q.Add(new Event(user,duration*100,AttrType.REGENERATING)); break; } /*case ConsumableType.RESISTANCE: { user.attrs[AttrType.RESIST_FIRE]++; user.attrs[AttrType.RESIST_COLD]++; user.attrs[AttrType.RESIST_ELECTRICITY]++; B.Add(user.YouFeel() + " insulated. ",user); int duration = Global.Roll(2,10)+5; Q.Add(new Event(user,duration*100,AttrType.RESIST_FIRE)); Q.Add(new Event(user,duration*100,AttrType.RESIST_COLD)); Q.Add(new Event(user,duration*100,AttrType.RESIST_ELECTRICITY,user.YouFeel() + " less insulated. ",user)); if(user.HasAttr(AttrType.ON_FIRE) || user.HasAttr(AttrType.CATCHING_FIRE) || user.HasAttr(AttrType.STARTED_CATCHING_FIRE_THIS_TURN)){ B.Add(user.YouAre() + " no longer on fire. ",user); int oldradius = user.LightRadius(); user.attrs[AttrType.ON_FIRE] = 0; user.attrs[AttrType.CATCHING_FIRE] = 0; user.attrs[AttrType.STARTED_CATCHING_FIRE_THIS_TURN] = 0; if(oldradius != user.LightRadius()){ user.UpdateRadius(oldradius,user.LightRadius()); } } break; }*/ case ConsumableType.CLARITY: user.ResetSpells(); if(user.name == "you"){ B.Add("Your mind clears. "); } else{ B.Add(user.the_name + " seems focused. ",user); } break; case ConsumableType.CLOAKING: if(user.tile().IsLit()){ B.Add("You would feel at home in the shadows. "); } else{ B.Add("You fade away in the darkness. "); } user.GainAttrRefreshDuration(AttrType.SHADOW_CLOAK,(Global.Roll(41)+29)*100,"You are no longer cloaked. ",user); break; case ConsumableType.BLINKING: for(int i=0;i<9999;++i){ int rr = Global.Roll(1,17) - 9; int rc = Global.Roll(1,17) - 9; if(Math.Abs(rr) + Math.Abs(rc) >= 6){ rr += user.row; rc += user.col; if(M.BoundsCheck(rr,rc) && M.tile[rr,rc].passable && M.actor[rr,rc] == null){ B.Add(user.You("step") + " through a rip in reality. ",new PhysicalObject[]{M.tile[user.row,user.col],M.tile[rr,rc]}); user.AnimateStorm(2,3,4,"*",Color.DarkMagenta); await user.Move(rr, rc); M.Draw(); user.AnimateStorm(2,3,4,"*",Color.DarkMagenta); break; } } } break; case ConsumableType.TELEPORTATION: for(int i=0;i<9999;++i){ int rr = Global.Roll(1,Global.ROWS-2); int rc = Global.Roll(1,Global.COLS-2); if(Math.Abs(rr-user.row) >= 10 || Math.Abs(rc-user.col) >= 10 || (Math.Abs(rr-user.row) >= 7 && Math.Abs(rc-user.col) >= 7)){ if(M.BoundsCheck(rr,rc) && M.tile[rr,rc].passable && M.actor[rr,rc] == null){ B.Add(user.You("jump") + " through a rift in reality. ", new PhysicalObject[] { M.tile[user.row, user.col], M.tile[rr, rc] }); user.AnimateStorm(3,3,10,"*",Color.Green); await user.Move(rr, rc); M.Draw(); user.AnimateStorm(3,3,10,"*",Color.Green); break; } } } break; case ConsumableType.PASSAGE: { int i = user.DirectionOfOnlyUnblocked(TileType.WALL,true); if(i == 0){ B.Add("This item requires an adjacent wall. "); used = false; break; } else{ i = await user.GetDirection(true,false); Tile t = user.TileInDirection(i); if(t != null){ if(t.ttype == TileType.WALL){ Game.Console.CursorVisible = false; colorchar ch = new colorchar(Color.Cyan,"!"); switch(user.DirectionOf(t)){ case 8: case 2: ch.c = "|"; break; case 4: case 6: ch.c = "-"; break; } List<Tile> tiles = new List<Tile>(); List<colorchar> memlist = new List<colorchar>(); while(!t.passable){ if(t.row == 0 || t.row == Global.ROWS-1 || t.col == 0 || t.col == Global.COLS-1){ break; } tiles.Add(t); memlist.Add(Screen.MapChar(t.row,t.col)); Screen.WriteMapChar(t.row,t.col,ch); await Task.Delay(35); t = t.TileInDirection(i); } if(t.passable && M.actor[t.row,t.col] == null){ if(M.tile[user.row,user.col].inv != null){ Screen.WriteMapChar(user.row,user.col,new colorchar(user.tile().inv.color,user.tile().inv.symbol)); } else{ Screen.WriteMapChar(user.row,user.col,new colorchar(user.tile().color,user.tile().symbol)); } Screen.WriteMapChar(t.row,t.col,new colorchar(user.color,user.symbol)); int j = 0; foreach(Tile tile in tiles){ Screen.WriteMapChar(tile.row,tile.col,memlist[j++]); await Task.Delay(35); } B.Add(user.You("travel") + " through the passage. ",user,t); await user.Move(t.row, t.col); } else{ int j = 0; foreach(Tile tile in tiles){ Screen.WriteMapChar(tile.row,tile.col,memlist[j++]); await Task.Delay(35); } B.Add("The passage is blocked. ",user); } } else{ B.Add("This item requires an adjacent wall. "); used = false; break; } } else{ used = false; } } break; } case ConsumableType.TIME: B.Add("Time stops for a moment. "); Q.turn -= 200; break; case ConsumableType.DETECT_MONSTERS: { //user.attrs[AttrType.DETECTING_MONSTERS]++; B.Add("The scroll reveals " + user.Your() + " foes. ",user); int duration = Global.Roll(20)+30; //Q.Add(new Event(user,duration*100,AttrType.DETECTING_MONSTERS,user.Your() + " foes are no longer revealed. ",user)); user.GainAttrRefreshDuration(AttrType.DETECTING_MONSTERS,duration*100,user.Your() + " foes are no longer revealed. ",user); break; } case ConsumableType.MAGIC_MAP: { B.Add("The scroll reveals the layout of this level. "); Event hiddencheck = null; foreach(Event e in Q.list){ if(!e.dead && e.evtype == EventType.CHECK_FOR_HIDDEN){ hiddencheck = e; break; } } foreach(Tile t in M.AllTiles()){ if(t.ttype != TileType.FLOOR){ bool good = false; foreach(Tile neighbor in t.TilesAtDistance(1)){ if(neighbor.ttype != TileType.WALL){ good = true; } } if(good){ t.seen = true; if(t.IsTrapOrVent() || t.Is(TileType.HIDDEN_DOOR)){ if(hiddencheck != null){ hiddencheck.area.Remove(t); } } if(t.IsTrapOrVent()){ t.name = Tile.Prototype(t.ttype).name; t.a_name = Tile.Prototype(t.ttype).a_name; t.the_name = Tile.Prototype(t.ttype).the_name; t.symbol = Tile.Prototype(t.ttype).symbol; t.color = Tile.Prototype(t.ttype).color; } if(t.Is(TileType.HIDDEN_DOOR)){ t.Toggle(null); } } } } break; } case ConsumableType.SUNLIGHT: if(!M.wiz_lite){ M.wiz_lite = true; M.wiz_dark = false; B.Add("The air itself seems to shine. "); } else{ B.Add("Nothing happens. "); } break; case ConsumableType.DARKNESS: if(!M.wiz_dark){ M.wiz_dark = true; M.wiz_lite = false; B.Add("The air itself grows dark. "); } else{ B.Add("Nothing happens. "); } break; case ConsumableType.PRISMATIC: { if(line == null){ line = await user.GetTarget(12,1); } if(line != null){ Tile t = line.Last(); Tile prev = line.LastBeforeSolidTile(); Actor first = user.FirstActorInLine(line); //todo - consider allowing thrown items to pass over actors, because they fly in an arc B.Add(user.You("throw") + " the prismatic orb. ",user); if(first != null){ t = first.tile(); B.Add("It shatters on " + first.the_name + "! ",first); } else{ B.Add("It shatters on " + t.the_name + "! ",t); } user.AnimateProjectile(line.ToFirstObstruction(),"*",Color.RandomPrismatic); List<DamageType> dmg = new List<DamageType>(); dmg.Add(DamageType.FIRE); dmg.Add(DamageType.COLD); dmg.Add(DamageType.ELECTRIC); while(dmg.Count > 0){ DamageType damtype = dmg.Random(); colorchar ch = new colorchar(Color.Black,"*"); switch(damtype){ case DamageType.FIRE: ch.color = Color.RandomFire; break; case DamageType.COLD: ch.color = Color.RandomIce; break; case DamageType.ELECTRIC: ch.color = Color.RandomLightning; break; } B.DisplayNow(); Screen.AnimateExplosion(t,1,ch,100); if(t.passable){ foreach(Tile t2 in t.TilesWithinDistance(1)){ if(t2.actor() != null){ await t2.actor().TakeDamage(damtype,DamageClass.MAGICAL,Global.Roll(2,6),user,"a prismatic orb"); } if(damtype == DamageType.FIRE && t2.Is(FeatureType.TROLL_CORPSE)){ t2.features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ",t2); } if(damtype == DamageType.FIRE && t2.Is(FeatureType.TROLL_SEER_CORPSE)){ t2.features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ",t2); } } } else{ foreach(Tile t2 in t.TilesWithinDistance(1)){ if(prev != null && prev.HasBresenhamLine(t2.row,t2.col)){ if(t2.actor() != null){ await t2.actor().TakeDamage(damtype,DamageClass.MAGICAL,Global.Roll(2,6),user,"a prismatic orb"); } if(damtype == DamageType.FIRE && t2.Is(FeatureType.TROLL_CORPSE)){ t2.features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ",t2); } if(damtype == DamageType.FIRE && t2.Is(FeatureType.TROLL_SEER_CORPSE)){ t2.features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ",t2); } } } } dmg.Remove(damtype); } } else{ used = false; } break; } case ConsumableType.FREEZING: { if(line == null){ line = await user.GetTarget(12,3); } if(line != null){ Tile t = line.Last(); Tile prev = line.LastBeforeSolidTile(); Actor first = user.FirstActorInLine(line); B.Add(user.You("throw") + " the freezing orb. ",user); if(first != null){ t = first.tile(); B.Add("It shatters on " + first.the_name + "! ",first); } else{ B.Add("It shatters on " + t.the_name + "! ",t); } user.AnimateProjectile(line.ToFirstObstruction(),"*",Color.RandomIce); user.AnimateExplosion(t,3,"*",Color.Cyan); List<Actor> targets = new List<Actor>(); if(t.passable){ foreach(Actor ac in t.ActorsWithinDistance(3)){ if(t.HasLOE(ac)){ targets.Add(ac); } } } else{ foreach(Actor ac in t.ActorsWithinDistance(3)){ if(prev != null && prev.HasLOE(ac)){ targets.Add(ac); } } } while(targets.Count > 0){ Actor ac = targets.RemoveRandom(); B.Add(ac.YouAre() + " encased in ice. ",ac); ac.attrs[Forays.AttrType.FROZEN] = 25; } } else{ used = false; } break; } case ConsumableType.QUICKFIRE: { if(line == null){ line = await user.GetTarget(12,-1); } if(line != null){ Tile t = line.Last(); Tile prev = line.LastBeforeSolidTile(); Actor first = user.FirstActorInLine(line); B.Add(user.You("throw") + " the orb of quickfire. ",user); if(first != null){ t = first.tile(); B.Add("It shatters on " + first.the_name + "! ",first); } else{ B.Add("It shatters on " + t.the_name + "! ",t); } user.AnimateProjectile(line.ToFirstObstruction(),"*",Color.RandomFire); if(t.passable){ t.features.Add(FeatureType.QUICKFIRE); Q.Add(new Event(t,new List<Tile>{t},100,EventType.QUICKFIRE,AttrType.NO_ATTR,3,"")); } else{ prev.features.Add(FeatureType.QUICKFIRE); Q.Add(new Event(prev,new List<Tile>{prev},100,EventType.QUICKFIRE,AttrType.NO_ATTR,3,"")); } } else{ used = false; } break; } case ConsumableType.FOG: { if(line == null){ line = await user.GetTarget(12,-3); } if(line != null){ Tile t = line.Last(); Tile prev = line.LastBeforeSolidTile(); Actor first = user.FirstActorInLine(line); B.Add(user.You("throw") + " the orb of fog. ",user); if(first != null){ t = first.tile(); B.Add("It shatters on " + first.the_name + "! ",first); } else{ B.Add("It shatters on " + t.the_name + "! ",t); } user.AnimateProjectile(line.ToFirstObstruction(),"*",Color.Gray); List<Tile> area = new List<Tile>(); List<pos> cells = new List<pos>(); if(t.passable){ foreach(Tile tile in t.TilesWithinDistance(3)){ if(tile.passable && t.HasLOE(tile)){ tile.AddOpaqueFeature(FeatureType.FOG); area.Add(tile); cells.Add(tile.p); } } } else{ foreach(Tile tile in t.TilesWithinDistance(3)){ if(prev != null && tile.passable && prev.HasLOE(tile)){ tile.AddOpaqueFeature(FeatureType.FOG); area.Add(tile); cells.Add(tile.p); } } } Screen.AnimateMapCells(cells,new colorchar("*",Color.Gray)); Q.Add(new Event(area,400,EventType.FOG)); } else{ used = false; } break; } case ConsumableType.BANDAGE: await user.TakeDamage(DamageType.HEAL,DamageClass.NO_TYPE,1,null); if(user.HasAttr(AttrType.MAGICAL_BLOOD)){ user.recover_time = Q.turn + 200; } else{ user.recover_time = Q.turn + 500; } if(user.name == "you"){ B.Add("You apply a bandage. "); } else{ B.Add(user.the_name + " applies a bandage. ",user); } break; default: used = false; break; } if(used){ if(quantity > 1){ --quantity; } else{ if(user != null){ user.inv.Remove(this); } } } return used; }
public void EachSecond() { List <Character> reproduceables = new List <Character>(); List <Character> marriableMales = new List <Character>(); List <Character> marriableFemales = new List <Character>(); List <Character> willBeDeads = new List <Character>(); foreach (Character character in population) { if (character.CanReproduce) { reproduceables.Add(character); } if (character.IsMale && character.CanMarry) { marriableMales.Add(character); } if (character.IsFemale && character.CanMarry) { marriableFemales.Add(character); } if (character.IsDead) { willBeDeads.Add(character); } } int matchableCount = Mathf.Min(marriableMales.Count, marriableFemales.Count); matchableCount = (int)Mathf.Ceil(UnityEngine.Random.Range(0f, matchableCount) / 2f); for (int i = 0; i < matchableCount; i++) { Character male = marriableMales.RemoveRandom(); Character female = marriableFemales.RemoveRandom(); if (male == null || female == null) { break; } if (male.CanMarryWith(female)) { male.MarryWith(female); Debug.LogFormat("結婚確定: {0} + {1}", male.name, female.name); } } foreach (Character reproduceable in reproduceables) { float percentage = PregnancyCalculator.GetPercentage(reproduceable); if (UnityEngine.Random.Range(0f, 100f) <= percentage) { Character child = reproduceable.Impregenate(characterFactory); population.Add(child); charactersPlaceholder.Append(child); Debug.LogFormat("妊娠確定: {0} + {1} = {2} ({3}%)", child.Father.name, child.Mother.name, child.name, percentage); } } foreach (Character willbeDead in willBeDeads) { willbeDead.Die(); population.Remove(willbeDead); } }
private Territory[] Subdivide(Territory original, int divisions = 2) { if (original.MapCells.Count < divisions) { throw new InvalidOperationException("The number of divisions cannot exceed the number of cells in the territory."); } var unclaimed = new List <uint>(); // For RemoveRandom() unclaimed.AddRange(original.MapCellIds); var territories = Enumerable.Range(0, divisions).Select(_ => new Territory()).ToArray(); var adjacencies = Enumerable.Range(0, divisions).Select(_ => new List <uint>()).ToArray(); for (int i = 0; i < divisions; i++) { var rootId = unclaimed.RemoveRandom(); var cell = this.World.Map.MapCells[rootId]; territories[i].MapCells.Add(cell); territories[i].MapCellIds.Add(cell.Id); adjacencies[i].AddRange(cell.AdjacentMapCellIds.Where(id => unclaimed.Contains(id))); } while (unclaimed.Count > 0) { bool expanded = false; for (int i = 0; i < divisions && unclaimed.Count > 0; i++) { if (Chance.Roll(0.5)) { continue; } if (adjacencies[i].Count > 0) { var newAdjacencies = new List <uint>(); foreach (var adjacentId in adjacencies[i]) { if (unclaimed.Contains(adjacentId)) { unclaimed.Remove(adjacentId); var adjacentCell = this.World.Map.MapCells[adjacentId]; territories[i].MapCells.Add(adjacentCell); territories[i].MapCellIds.Add(adjacentId); expanded = true; foreach (var id in adjacentCell.AdjacentMapCellIds) { if (unclaimed.Contains(id)) { newAdjacencies.Add(id); } } } } adjacencies[i] = newAdjacencies; } } if (!expanded) { // break; } } return(territories); }
private void SetupPorts() { _ports.Clear(); // There are 4 three-to-one ports, and one two-to-one for each resource type. // Sort the water edges into a ring, so we can walk around the outside var waterRing = new List<HexEdge>(); var remainingWaterEdges = new List<HexEdge>(_borderEdges); waterRing.Add(remainingWaterEdges.RemoveRandom()); while (remainingWaterEdges.Count > 0) { var currentEdge = waterRing.Last(); var nextEdge = remainingWaterEdges.First(e => e.IsTouching(currentEdge)); remainingWaterEdges.Remove(nextEdge); waterRing.Add(nextEdge); } var portsToAdd = new List<ResourceTypes>(); portsToAdd.Add(ResourceTypes.None, 4); portsToAdd.Add(ResourceTypes.Brick); portsToAdd.Add(ResourceTypes.Ore); portsToAdd.Add(ResourceTypes.Sheep); portsToAdd.Add(ResourceTypes.Wheat); portsToAdd.Add(ResourceTypes.Wood); int ringIndex = 0; for (int i = 0; portsToAdd.Any(); i++) { var resource = portsToAdd.RemoveRandom(); _ports[waterRing[ringIndex]] = new Port(resource); // Every third add, skip 4 edges instead of 3. ringIndex += (i % 3 == 0) ? 4 : 3; } }
public void GenerateFinalLevel() { final_level_cultist_count = new int[5]; final_level_demon_count = 0; final_level_clock = 0; current_level = 21; InitializeNewLevel(); string[] final_map = FinalLevelLayout(); PosArray<CellType> map = new PosArray<CellType>(ROWS,COLS); PosArray<bool> doors = new PosArray<bool>(ROWS,COLS); List<List<pos>> door_sets = new List<List<pos>>(); for(int i=0;i<ROWS;++i){ string s = final_map[i]; for(int j=0;j<COLS;++j){ switch(s[j]){ case '#': map[i,j] = CellType.Wall; break; case '.': map[i,j] = CellType.RoomInterior; break; case '2': map[i,j] = CellType.RoomFeature1; break; case '&': map[i,j] = CellType.RoomFeature2; break; case '*': map[i,j] = CellType.RoomFeature3; break; case 'X': map[i,j] = CellType.RoomFeature4; break; case '+': map[i,j] = CellType.Wall; if(!doors[i,j]){ doors[i,j] = true; pos p = new pos(i,j); List<pos> door_set = new List<pos>{p}; foreach(int dir in new int[]{2,6}){ p = new pos(i,j); while(true){ p = p.PosInDir(dir); if(p.BoundsCheck(tile) && final_map[p.row][p.col] == '+'){ doors[p] = true; door_set.Add(p); } else{ break; } } } door_sets.Add(door_set); } break; } } } Dungeon d = new Dungeon(ROWS,COLS); d.map = map; while(!d.IsFullyConnected() && door_sets.Count > 0){ List<pos> door_set = door_sets.RemoveRandom(); d.map[door_set.Random()] = CellType.RoomInterior; } List<Tile> flames = new List<Tile>(); for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ switch(map[i,j]){ case CellType.Wall: Tile.Create(TileType.WALL,i,j); break; case CellType.RoomFeature1: Tile.Create(TileType.DEMONIC_IDOL,i,j); break; case CellType.RoomFeature2: Tile.Create(TileType.FLOOR,i,j); flames.Add(tile[i,j]); break; case CellType.RoomFeature3: Tile.Create(TileType.FLOOR,i,j); tile[i,j].color = Color.RandomDoom; break; case CellType.RoomFeature4: Tile.Create(TileType.FIRE_RIFT,i,j); break; default: Tile.Create(TileType.FLOOR,i,j); break; } tile[i,j].solid_rock = true; } } //todo! add dungeon descriptions for final level. //todo: ^^^ or else it crashes ^^^ player.ResetForNewLevel(); foreach(Tile t in AllTiles()){ if(t.light_radius > 0){ t.UpdateRadius(0,t.light_radius); } } foreach(Tile t in flames){ t.AddFeature(FeatureType.FIRE); } int light = player.light_radius; int fire = player.attrs[AttrType.BURNING]; player.light_radius = 0; player.attrs[AttrType.BURNING] = 0; player.Move(6,7); player.UpdateRadius(0,Math.Max(light,fire),true); player.light_radius = light; player.attrs[AttrType.BURNING] = fire; foreach(Tile t in AllTiles()){ if(t.type != TileType.WALL){ foreach(Tile neighbor in t.TilesAtDistance(1)){ neighbor.solid_rock = false; } } } for(int i=0;i<3;++i){ Actor a = SpawnMob(ActorType.CULTIST); List<Actor> group = new List<Actor>(a.group); a.group.Clear(); if(a != null && group != null){ int ii = 0; foreach(Actor a2 in group){ ++ii; pos circle = FinalLevelSummoningCircle(ii); a2.FindPath(circle.row,circle.col); a2.attrs[AttrType.COOLDOWN_2] = ii; a2.type = ActorType.FINAL_LEVEL_CULTIST; a2.group = null; if(!R.OneIn(20)){ a2.attrs[AttrType.NO_ITEM] = 1; } } } } Q.Add(new Event(500,EventType.FINAL_LEVEL_SPAWN_CULTISTS)); }
public void WhenIsEmpty_ThenThrowException() { ICollection <int> sut = new List <int>(); Assert.Throws <InvalidOperationException>(() => sut.RemoveRandom()); }
public PosArray<CellType> GenerateMap(LevelType type) { PosArray<CellType> result = new PosArray<CellType>(ROWS,COLS); Dungeon d = new Dungeon(ROWS,COLS); switch(type){ case LevelType.Standard: while(true){ d.CreateBasicMap(); d.ConnectDiagonals(); d.RemoveUnconnectedAreas(); d.RemoveDeadEndCorridors(); d.AddDoors(25); d.AlterRooms(5,2,2,1,0); d.MarkInterestingLocations(); d.RemoveUnconnectedAreas(); if(d.NumberOfFloors() < 320 || d.HasLargeUnusedSpaces(300)){ d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } case LevelType.Cave: { int roll = R.Roll(2); if(R.OneIn(20)){ roll = 3; } switch(roll){ //three different algorithms case 1: { while(true){ d.FillWithRandomWalls(25); d.ApplyCellularAutomataXYRule(3); d.ConnectDiagonals(); d.ImproveMapEdges(5); d.RemoveDeadEndCorridors(); d.RemoveUnconnectedAreas(); d.MarkInterestingLocationsNonRectangular(); if(d.NumberOfFloors() < 320 || d.HasLargeUnusedSpaces(300)){ d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } } case 2: { while(true){ d.CreateTwistyCave(true,40); U.DefaultMetric = DistanceMetric.Manhattan; var dijk = d.map.GetDijkstraMap(x=>!d.map[x].IsWall(),x=>false); for(int i=1;i<ROWS-1;++i){ for(int j=1;j<COLS-1;++j){ U.DefaultMetric = DistanceMetric.Manhattan; if(dijk[i,j] == 1){ pos p = new pos(i,j); List<pos> floors = null; foreach(int dir in U.FourDirections){ pos n = p.PosInDir(dir); if(dijk[n] == 1){ if(floors == null){ floors = p.PositionsAtDistance(1,dijk).Where(x=>dijk[x] == 0); } List<pos> floors2 = new List<pos>(); foreach(pos n2 in n.PositionsAtDistance(1,dijk)){ if(dijk[n2] == 0 && !floors.Contains(n2)){ floors2.Add(n2); } } if(floors2.Count > 0 && R.OneIn(5)){ //IIRC this checks each pair twice, so that affects the chance here pos f1 = floors.Random(); pos f2 = floors2.Random(); U.DefaultMetric = DistanceMetric.Chebyshev; int dist = d.map.PathingDistanceFrom(f1,f2,x=>!d.map[x].IsPassable() && d.map[x] != CellType.Door); if(dist > 22 || (dist > 8 && R.OneIn(4))){ CellType rubble = R.OneIn(8)? CellType.Rubble : CellType.CorridorIntersection; d[p] = R.OneIn(3)? rubble : CellType.CorridorIntersection; d[n] = R.OneIn(3)? rubble : CellType.CorridorIntersection; List<pos> neighbors = new List<pos>(); foreach(pos nearby in p.PositionsAtDistance(1)){ if(nearby.BoundsCheck(d.map,false) && nearby.DistanceFrom(n) == 1){ neighbors.Add(nearby); } } while(neighbors.Count > 0){ pos neighbor = neighbors.RemoveRandom(); if(R.OneIn(neighbors.Count + 3) && !d.SeparatesMultipleAreas(neighbor)){ d[neighbor] = R.OneIn(2)? CellType.Rubble : CellType.CorridorIntersection; } } } } break; } } } } } /*List<pos> thin_walls = d.map.AllPositions().Where(x=>d.map[x].IsWall() && x.HasOppositePairWhere(true,y=>y.BoundsCheck() && d.map[y].IsFloor())); while(thin_walls.Count > 0){ pos p = thin_walls.Random(); foreach(int dir in new int[]{8,4}){ if(d.map[p.PosInDir(dir)] != CellType.Wall && d.map[p.PosInDir(dir.RotateDir(true,4))] != CellType.Wall){ var dijkstra = d.map.GetDijkstraMap(x=>d[x] == CellType.Wall,new List<pos>{p.PosInDir(dir)}); //todo: this would be better as "get distance" if(Math.Abs(dijkstra[p.PosInDir(dir)] - dijkstra[p.PosInDir(dir.RotateDir(true,4))]) > 30){ d.map[p] = CellType.CorridorIntersection; break; } } } thin_walls.Remove(p); //todo: move thin-wall-removal to schism }*/ d.ConnectDiagonals(); d.RemoveUnconnectedAreas(); d.ImproveMapEdges(5); d.SmoothCorners(60); d.RemoveDeadEndCorridors(); d.MarkInterestingLocationsNonRectangular(); if(d.NumberOfFloors() < 320 || d.HasLargeUnusedSpaces(300)){ d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } U.DefaultMetric = DistanceMetric.Chebyshev; return result; } } } case 3: { d.RoomHeightMax = 3; d.RoomWidthMax = 3; while(true){ int successes = 0; int consecutive_failures = 0; while(successes < 13){ if(d.CreateRoom()){ ++successes; consecutive_failures = 0; } else{ if(consecutive_failures++ >= 50){ d.Clear(); successes = 0; consecutive_failures = 0; } } } d.CaveWidenRooms(100,50); d.AddRockFormations(40,2); List<pos> thin_walls = d.map.AllPositions().Where(x=>d.map[x].IsWall() && x.HasOppositePairWhere(true,y=>y.BoundsCheck(tile) && d.map[y].IsFloor())); while(!d.IsFullyConnected() && thin_walls.Count > 0){ pos p = thin_walls.Random(); d.map[p] = CellType.CorridorIntersection; foreach(pos neighbor in p.PositionsWithinDistance(1,d.map)){ thin_walls.Remove(neighbor); } } d.ConnectDiagonals(); d.RemoveDeadEndCorridors(); d.RemoveUnconnectedAreas(); d.MarkInterestingLocationsNonRectangular(); if(d.NumberOfFloors() < 320 || d.HasLargeUnusedSpaces(300)){ //todo: add 'proper coverage' check here - make sure it stretches across enough of the map. d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } } } break; } case LevelType.Hive: { d.RoomHeightMax = 3; d.RoomWidthMax = 3; while(true){ int successes = 0; int consecutive_failures = 0; while(successes < 35){ if(d.CreateRoom()){ ++successes; consecutive_failures = 0; } else{ if(consecutive_failures++ >= 40){ d.Clear(); successes = 0; consecutive_failures = 0; } } } d.CaveWidenRooms(100,10); d.CaveWidenRooms(3,20); List<pos> thin_walls = d.map.AllPositions().Where(x=>d.map[x].IsWall() && x.HasOppositePairWhere(true,y=>y.BoundsCheck(tile) && d.map[y].IsFloor())); while(!d.IsFullyConnected() && thin_walls.Count > 0){ pos p = thin_walls.Random(); d.map[p] = CellType.CorridorIntersection; foreach(pos neighbor in p.PositionsWithinDistance(2,d.map)){ thin_walls.Remove(neighbor); } } d.ConnectDiagonals(); d.RemoveDeadEndCorridors(); d.RemoveUnconnectedAreas(); d.MarkInterestingLocations(); //to find rooms big enough for stuff in the center: //var dijkstra = d.map.GetDijkstraMap(x=>d.map[x].IsWall(),d.map.AllPositions().Where(x=>d.map[x].IsWall() && x.HasAdjacentWhere(y=>d.map.BoundsCheck(y) && !d.map[y].IsWall()))); if(d.NumberOfFloors() < 340 || d.HasLargeUnusedSpaces(300)){ //todo: add 'proper coverage' check here - make sure it stretches across enough of the map. d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } } case LevelType.Mine: { d.CorridorExtraLengthChance = 0; d.CorridorChainSizeMax = 10; while(true){ d.RoomHeightMin = 8; d.RoomWidthMin = 8; d.RoomHeightMax = 8; d.RoomWidthMax = 10; d.MinimumSpaceBetweenCorridors = 3; d.CorridorLengthMin = 3; d.CorridorLengthMax = 5; while(!d.CreateRoom()){} d.RoomHeightMin = 5; d.RoomWidthMin = 5; d.RoomHeightMax = 5; d.RoomWidthMax = 5; while(!d.CreateRoom()){} while(!d.CreateRoom()){} /*for(int i=0;i<10;++i){ d.CreateRoom(); } d.AddRockFormations(100,2);*/ d.MinimumSpaceBetweenCorridors = 5; d.CorridorLengthMin = 4; d.CorridorLengthMax = 12; for(int i=0;i<70;++i){ d.CreateCorridor(); } d.CorridorLengthMin = 3; d.CorridorLengthMax = 5; d.MinimumSpaceBetweenCorridors = 3; for(int i=0;i<350;++i){ d.CreateCorridor(); } d.RemoveUnconnectedAreas(); d.ConnectDiagonals(true); d.RemoveUnconnectedAreas(); d.MarkInterestingLocations(); if(d.NumberOfFloors() < 250 || d.HasLargeUnusedSpaces(300)){ d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } } case LevelType.Fortress: while(true){ int H = ROWS; int W = COLS; for(int i=H/2-1;i<H/2+1;++i){ for(int j=1;j<W-1;++j){ if(j==1 || j==W-2){ d.map[i,j] = CellType.RoomCorner; } else{ d.map[i,j] = CellType.RoomEdge; } } } for(int i=0;i<700;++i){ if(R.OneIn(5)){ d.CreateCorridor(); } else{ d.CreateRoom(); } } bool reflect_features = R.PercentChance(80); if(reflect_features){ d.AddDoors(25); d.AddPillars(30); } d.Reflect(true,false); d.ConnectDiagonals(); d.RemoveDeadEndCorridors(); d.RemoveUnconnectedAreas(); if(!reflect_features){ d.AddDoors(25); d.AddPillars(30); } bool door_right = false; bool door_left = false; int rightmost_door = 0; int leftmost_door = 999; for(int j=0;j<22;++j){ if(d[H/2-2,j].IsCorridorType()){ door_left = true; if(leftmost_door == 999){ leftmost_door = j; } } if(d[H/2-2,W-1-j].IsCorridorType()){ door_right = true; if(rightmost_door == 0){ rightmost_door = W-1-j; } } } if(!door_left || !door_right){ d.Clear(); continue; } for(int j=1;j<leftmost_door-6;++j){ d[H/2-1,j] = CellType.Wall; d[H/2,j] = CellType.Wall; } for(int j=W-2;j>rightmost_door+6;--j){ d[H/2-1,j] = CellType.Wall; d[H/2,j] = CellType.Wall; } for(int j=1;j<W-1;++j){ if(d[H/2-1,j].IsFloor()){ d[H/2-1,j] = CellType.Statue; d[H/2,j] = CellType.Statue; break; } else{ if(d[H/2-1,j] == CellType.Statue){ break; } } } for(int j=W-2;j>0;--j){ if(d[H/2-1,j].IsFloor()){ d[H/2-1,j] = CellType.Statue; d[H/2,j] = CellType.Statue; break; } else{ if(d[H/2-1,j] == CellType.Statue){ break; } } } for(int i=H/2-1;i<H/2+1;++i){ for(int j=1;j<W-1;++j){ if(d[i,j] == CellType.RoomCorner || d[i,j] == CellType.RoomEdge){ d[i,j] = CellType.CorridorIntersection; } } } d.MarkInterestingLocations(); if(d.NumberOfFloors() < 420 || d.HasLargeUnusedSpaces(300)){ d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } case LevelType.Slime: while(true){ /*for(int i=1;i<ROWS-1;++i){ for(int j=1;j<COLS-1;++j){ if(d[i,j].IsWall()){ if(!d[i+1,j+1].IsWall()){ d[i,j] = d[i+1,j+1]; } else{ if(!d[i+1,j].IsWall()){ d[i,j] = d[i+1,j]; } else{ if(!d[i,j+1].IsWall()){ d[i,j] = d[i,j+1]; } } } } } }*/ d.CreateBasicMap(); d.ConnectDiagonals(); d.RemoveUnconnectedAreas(); d.AddDoors(25); d.CaveWidenRooms(30,30); d.RemoveDeadEndCorridors(); d.AddPillars(30); d.MarkInterestingLocations(); if(d.NumberOfFloors() < 120 || d.HasLargeUnusedSpaces(300)){ d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } case LevelType.Garden: { d.RoomHeightMin = 4; d.RoomHeightMax = 10; d.RoomWidthMin = 4; d.RoomWidthMax = 10; while(true){ d.CreateBasicMap(); d.ConnectDiagonals(); d.RemoveUnconnectedAreas(); d.RemoveDeadEndCorridors(); var dijkstra = d.map.GetDijkstraMap(x=>d[x].IsPassable(),x=>false); List<pos> possible_room_centers = d.map.PositionsWhere(x=>dijkstra[x] == 3 && x.row > 1 && x.row < ROWS-2 && x.col > 1 && x.col < COLS-2); int rooms = 0; while(rooms < 6 && possible_room_centers.Count > 0){ pos p = possible_room_centers.RemoveRandom(); List<int> valid_dirs = new List<int>(); foreach(int dir in U.FourDirections){ pos p2 = p.PosInDir(dir).PosInDir(dir).PosInDir(dir); if(p2.BoundsCheck(d.map) && d[p2].IsPassable() && d[p2] != CellType.RoomCorner){ valid_dirs.Add(dir); } } if(valid_dirs.Count > 0){ foreach(pos neighbor in p.PositionsWithinDistance(1,d.map)){ d[neighbor] = CellType.RoomInterior; } possible_room_centers.RemoveWhere(x=>p.DistanceFrom(x) <= 3); foreach(int dir in valid_dirs){ d[p.PosInDir(dir).PosInDir(dir)] = CellType.CorridorIntersection; } ++rooms; } } CellType water_type = CellType.ShallowWater; if(R.OneIn(8)){ water_type = CellType.Ice; } d.ForEachRectangularRoom((start_r,start_c,end_r,end_c)=>{ int room_height = (end_r - start_r) + 1; int room_width = (end_c - start_c) + 1; if(room_height <= 4 && room_width <= 4){ if(room_height == 3 && room_width == 3){ return true; } List<pos> water = new List<pos>(); if(!new pos(start_r+1,start_c).PositionsAtDistance(1,d.map).Any(x=>d[x].IsCorridorType())){ water.Add(new pos(start_r+1,start_c)); water.Add(new pos(start_r+2,start_c)); } if(!new pos(start_r,start_c+1).PositionsAtDistance(1,d.map).Any(x=>d[x].IsCorridorType())){ water.Add(new pos(start_r,start_c+1)); water.Add(new pos(start_r,start_c+2)); } if(!new pos(end_r-1,end_c).PositionsAtDistance(1,d.map).Any(x=>d[x].IsCorridorType())){ water.Add(new pos(end_r-1,end_c)); water.Add(new pos(end_r-2,end_c)); } if(!new pos(end_r,end_c-1).PositionsAtDistance(1,d.map).Any(x=>d[x].IsCorridorType())){ water.Add(new pos(end_r,end_c-1)); water.Add(new pos(end_r,end_c-2)); } foreach(pos p in water){ d[p] = water_type; } d[start_r,start_c] = CellType.Statue; d[start_r,end_c] = CellType.Statue; d[end_r,start_c] = CellType.Statue; d[end_r,end_c] = CellType.Statue; } else{ CellType center_type = CellType.RoomFeature1; switch(R.Roll(3)){ case 1: center_type = water_type; break; case 2: center_type = CellType.Poppies; break; case 3: center_type = CellType.Brush; break; } bool statues = R.CoinFlip(); CellType statue_type = CellType.Statue; if(room_height <= 8 && room_width <= 8 && R.OneIn(8)){ statue_type = CellType.Torch; } CellType edge_type = CellType.ShallowWater; if(center_type != water_type && !R.OneIn(4)){ edge_type = CellType.ShallowWater; } else{ int vine_chance = 50; if(!statues){ vine_chance = 80; } if(R.PercentChance(vine_chance)){ edge_type = CellType.Vine; } else{ edge_type = CellType.Gravel; } if(R.OneIn(32)){ if(R.CoinFlip()){ edge_type = CellType.Statue; } else{ edge_type = CellType.GlowingFungus; } } } bool gravel = R.OneIn(16); bool edges = R.CoinFlip(); if(room_height < 6 || room_width < 6){ edges = false; } if(room_height >= 8 && room_width >= 8){ edges = !R.OneIn(4); } if(edges){ for(int i=start_r;i<=end_r;++i){ for(int j=start_c;j<=end_c;++j){ if(i == start_r || i == end_r || j == start_c || j == end_c){ //edges if(statues && (i == start_r || i == end_r) && (j == start_c || j == end_c)){ //corners d[i,j] = statue_type; } else{ pos p = new pos(i,j); if(!p.CardinalAdjacentPositions().Any(x=>d[x].IsCorridorType())){ d[i,j] = edge_type; } } } else{ if(i == start_r+1 || i == end_r-1 || j == start_c+1 || j == end_c-1){ //the path if(gravel){ d[i,j] = CellType.Gravel; } } else{ d[i,j] = center_type; } } } } } else{ for(int i=start_r;i<=end_r;++i){ for(int j=start_c;j<=end_c;++j){ if(i == start_r || i == end_r || j == start_c || j == end_c){ if(gravel){ d[i,j] = CellType.Gravel; } } else{ d[i,j] = center_type; } } } } if(center_type == water_type && room_height % 2 == 1 && room_width % 2 == 1){ statue_type = CellType.Statue; if(room_height <= 7 && room_width <= 7 && R.OneIn(12)){ statue_type = CellType.Torch; } d[(start_r+end_r)/2,(start_c+end_c)/2] = statue_type; } } return true; }); d.ConnectDiagonals(); d.RemoveUnconnectedAreas(); d.AddDoors(10); d.RemoveDeadEndCorridors(); d.MarkInterestingLocations(); if(d.NumberOfFloors() < 320 || d.HasLargeUnusedSpaces(300)){ d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } } case LevelType.Crypt: { while(true){ pos room1origin = new pos(ROWS/2,R.Roll(COLS/8 - 1) + COLS/8 - 1); pos room2origin = new pos(ROWS/2,R.Roll(COLS/8 - 1) + (COLS*6 / 8) - 1); while(!d.CreateRoom(room1origin.row,room1origin.col)){} //left half while(!d.CreateRoom(room2origin.row,room2origin.col)){} //right half d.CaveWidenRooms(100,150); d.MoveRoom(room1origin,4); d.MoveRoom(room2origin,6); var dijkstra = d.map.GetDijkstraMap(x=>d.map[x] == CellType.Wall,x=>false); //todo: among these Map dijkstra maps I have, like, 3 different ways of testing for walls. are these all correct? int distance_from_walls = 3; List<pos> central_room = d.map.PositionsWhere(x=>dijkstra[x] > distance_from_walls); int required_consecutive = 3; for(int i=0;i<ROWS;++i){ //first, check each row... for(int j=0;j<COLS;++j){ List<pos> this_row = new List<pos>(); while(j < COLS && dijkstra[i,j] > distance_from_walls){ this_row.Add(new pos(i,j)); ++j; } if(this_row.Count < required_consecutive){ foreach(pos p in this_row){ central_room.Remove(p); } } } } for(int j=0;j<COLS;++j){ //...then each column for(int i=0;i<ROWS;++i){ List<pos> this_col = new List<pos>(); while(i < ROWS && dijkstra[i,j] > distance_from_walls){ this_col.Add(new pos(i,j)); ++i; } if(this_col.Count < required_consecutive){ foreach(pos p in this_col){ central_room.Remove(p); } } } } central_room = d.map.GetFloodFillPositions(central_room.Where(x=>x.PositionsWithinDistance(1).All(y=>central_room.Contains(y))),false,x=>central_room.Contains(x)); List<pos> walls = new List<pos>(); foreach(pos p in central_room){ d.map[p] = CellType.InterestingLocation; foreach(pos neighbor in p.PositionsAtDistance(1,d.map)){ if(!central_room.Contains(neighbor)){ d.map[neighbor] = CellType.Wall; walls.Add(neighbor); } } } while(true){ List<pos> potential_doors = new List<pos>(); foreach(pos p in walls){ foreach(int dir in U.FourDirections){ if(d.map[p.PosInDir(dir)] == CellType.InterestingLocation && d.map[p.PosInDir(dir.RotateDir(true,4))].IsRoomType() && d.map[p.PosInDir(dir.RotateDir(true,4))] != CellType.InterestingLocation){ potential_doors.Add(p); break; } } } if(potential_doors.Count > 0){ pos p = potential_doors.Random(); d.map[p] = CellType.Door; List<pos> room = d.map.GetFloodFillPositions(p,true,x=>d.map[x] == CellType.InterestingLocation); foreach(pos p2 in room){ d.map[p2] = CellType.RoomInterior; } } else{ break; } } dijkstra = d.map.GetDijkstraMap(x=>d.map[x] == CellType.Wall,x=>false); int num_chests = 0; d.ForEachRoom(list=>{ if(central_room.Contains(list[0])){ if(num_chests++ < 2){ d[list.Random()] = CellType.Chest; } return true; } List<pos> room = list.Where(x=>dijkstra[x] > 1); int start_r = room.WhereLeast(x=>x.row)[0].row; int end_r = room.WhereGreatest(x=>x.row)[0].row; int start_c = room.WhereLeast(x=>x.col)[0].col; int end_c = room.WhereGreatest(x=>x.col)[0].col; List<List<pos>> offsets = new List<List<pos>>(); for(int i=0;i<4;++i){ offsets.Add(new List<pos>()); } for(int i=start_r;i<=end_r;i+=2){ for(int j=start_c;j<=end_c;j+=2){ if(room.Contains(new pos(i,j))){ offsets[0].Add(new pos(i,j)); } if(i+1 <= end_r && room.Contains(new pos(i+1,j))){ offsets[1].Add(new pos(i+1,j)); } if(j+1 <= end_c && room.Contains(new pos(i,j+1))){ offsets[2].Add(new pos(i,j+1)); } if(i+1 <= end_r && j+1 <= end_c && room.Contains(new pos(i+1,j+1))){ offsets[3].Add(new pos(i+1,j+1)); } } } List<pos> tombstones = offsets.WhereGreatest(x=>x.Count).RandomOrDefault(); if(tombstones != null){ foreach(pos p in tombstones){ d.map[p] = CellType.Tombstone; } } return true; }); for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(d[i,j] == CellType.Door){ pos p = new pos(i,j); List<pos> potential_statues = p.PositionsAtDistance(1,d.map).Where(x=>!d[x].IsWall() && !central_room.Contains(x) && p.DirectionOf(x) % 2 != 0 && !x.PositionsAtDistance(1,d.map).Any(y=>d[y].Is(CellType.Tombstone))); if(potential_statues.Count == 2){ d[potential_statues[0]] = CellType.Statue; d[potential_statues[1]] = CellType.Statue; } } } } List<pos> room_one = null; List<pos> room_two = null; for(int j=0;j<COLS && room_one == null;++j){ for(int i=0;i<ROWS;++i){ if(d[i,j] != CellType.Wall){ room_one = d.map.GetFloodFillPositions(new pos(i,j),false,x=>!d[x].IsWall()); break; } } } for(int j=COLS-1;j>=0 && room_two == null;--j){ for(int i=0;i<ROWS;++i){ if(d[i,j] != CellType.Wall){ room_two = d.map.GetFloodFillPositions(new pos(i,j),false,x=>!d[x].IsWall()); break; } } } if(room_one.WhereGreatest(x=>x.col).Random().DistanceFrom(room_two.WhereLeast(x=>x.col).Random()) < 12){ d.Clear(); continue; } Dungeon d2 = new Dungeon(ROWS,COLS); int tries = 0; while(tries < 10){ d2.CreateBasicMap(); d2.ConnectDiagonals(); for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(d[i,j] != CellType.Wall){ pos p = new pos(i,j); foreach(pos neighbor in p.PositionsAtDistance(1,d2.map)){ d2[neighbor] = CellType.Wall; } } } } d2.RemoveUnconnectedAreas(); List<pos> room_one_walls = new List<pos>(); List<pos> room_two_walls = new List<pos>(); for(int i=0;i<ROWS;++i){ for(int j=COLS-1;j>=0;--j){ pos p = new pos(i,j); if(room_one.Contains(p)){ room_one_walls.Add(p); break; } } for(int j=0;j<COLS;++j){ pos p = new pos(i,j); if(room_two.Contains(p)){ room_two_walls.Add(p); break; } } } List<pos> room_one_valid_connections = new List<pos>(); List<pos> room_two_valid_connections = new List<pos>(); foreach(pos p in room_one_walls){ pos next = p.PosInDir(6); while(BoundsCheck(next) && p.DistanceFrom(next) < 7){ if(d2[next] != CellType.Wall){ room_one_valid_connections.Add(p.PosInDir(6)); break; } next = next.PosInDir(6); } } foreach(pos p in room_two_walls){ pos next = p.PosInDir(4); while(BoundsCheck(next) && p.DistanceFrom(next) < 7){ if(d2[next] != CellType.Wall){ room_two_valid_connections.Add(p.PosInDir(4)); break; } next = next.PosInDir(4); } } if(room_one_valid_connections.Count > 0 && room_two_valid_connections.Count > 0){ pos one = room_one_valid_connections.Random(); while(true){ if(d2[one] == CellType.Wall){ d2[one] = CellType.CorridorHorizontal; } else{ break; } one = one.PosInDir(6); } pos two = room_two_valid_connections.Random(); while(true){ if(d2[two] == CellType.Wall){ d2[two] = CellType.CorridorHorizontal; } else{ break; } two = two.PosInDir(4); } break; } else{ d2.Clear(); } ++tries; } if(tries == 10){ d.Clear(); continue; } for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(d2[i,j] != CellType.Wall){ d[i,j] = d2[i,j]; } } } //d.CaveWidenRooms(100,20); //d.MakeCavesMoreRectangular(4); //d.RemoveDeadEndCorridors(); //d.MakeCavesMoreRectangular(1 + num++ / 10); //d.Clear(); //continue; d.ConnectDiagonals(); d.RemoveUnconnectedAreas(); d.RemoveDeadEndCorridors(); d.MarkInterestingLocations(); d.RemoveUnconnectedAreas(); if(d.NumberOfFloors() < 340 || d.HasLargeUnusedSpaces(350)){ d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } } } return null; }
protected bool TryFinishChangeState(State target, params object[] args) { switch (target) { case State.Menu: _state = target; return(true); case State.CharacterSelection: OnCharacterSelectHandler?.Invoke(true); _state = target; return(true); case State.Begin: _podium.Clear(); foreach (var c in _controllers) { if (c == null) { continue; } _podium.Add(c, c._characterResource); } _podium.gameObject.SetActive(false); OnLevelSelectHandler.Invoke(false); foreach (World.Level level in _levels) { if (level == null) { continue; } if (level == _selectedLevel) { continue; } level.gameObject.SetActive(false); } _selectedLevel.gameObject.SetActive(false); _state = target; return(TryChangeState(State.Round, _roundTime)); case State.Transition: _transition = (State)args[0]; _transitionTimer.Start(); _transitionEffect.Perform(); _state = target; return(true); case State.Podium: _podium.gameObject.SetActive(true); OnPodiumHandler?.Invoke(); _state = target; return(true); case State.FinalPodium: _podium.gameObject.SetActive(true); OnFinalPodiumHandler?.Invoke(); _state = target; return(true); case State.LevelSelection: _state = target; foreach (World.Level lv in _levels) { lv.gameObject.SetActive(true); lv.OnLevelSelect(); } OnLevelSelect(); OnLevelSelected(0); return(true); case State.Round: // TODO enable _selectedLevel.TargetPosition = Vector3.zero; _selectedLevel.transform.position = Vector3.zero; _selectedLevel.gameObject.SetActive(true); _currentLevel = Instantiate( _selectedLevel.gameObject, Vector3.zero, Quaternion.identity, gameObject.transform).GetComponent <World.Level>(); _selectedLevel.gameObject.SetActive(false); _currentLevel.OnScoreValueAddedHandler += OnScoreValueAdded; _currentLevel.OnLevelCompletedHandler += OnLevelCompleted; List <Placeholder> placeholders = new List <Placeholder>(); placeholders.AddRange(_currentLevel._characterPlaceholders); int i = 0; while (!placeholders.IsEmpty()) { Placeholder placeholder = placeholders.RemoveRandom(); _controllers[i]._character = _controllers[i] ._characterResource.Create( _currentLevel.GridToWorld(placeholder._gridPosition), _currentLevel.transform); _controllers[i]._character.Number = _controllers[i].Number; _controllers[i]._character.Color = _controllers[i].Color; _controllers[i]._character._level = _currentLevel; _controllers[i]._character.TryChangeState(Character.State.Disabled); _controllers[i].Score = 0; _controllers[i]._assignedNumber = placeholder.Number; i++; Destroy(placeholder.gameObject); } _round = new Round( _countDown, _roundTime, _countDownTime, _intermissionTime, _roundIndex); StartCoroutine(NewRoundCoroutine()); _state = target; return(true); case State.Score: _state = target; return(true); case State.WaitingNextRound: //Lobby.Characters.Clear(); //Lobby.Characters.AddRange(_selectedLevel.Characters); foreach (Controller ctrl in Lobby.Controllers) { if (ctrl == null) { continue; } //ctrl.Character = null; } _state = target; OnWaiting(); return(true); default: return(false); } }
public async Task ActiveAI() { if (path.Count > 0) { path.Clear(); } switch (atype) { case ActorType.LARGE_BAT: case ActorType.PHANTOM_BLIGHTWING: if (DistanceFrom(target) == 1) { int idx = Global.Roll(1, 2) - 1; await Attack(idx, target); if (Global.CoinFlip()) { //chance of retreating await AI_Step(target, true); } } else { if (Global.CoinFlip()) { await AI_Step(target); QS(); } else { await AI_Step(TileInDirection(Global.RandomDirection())); //could also have RandomGoodDirection, but it QS(); //would be part of Actor or Map } } break; /*case ActorType.SHAMBLING_SCARECROW: if(DistanceFrom(target) == 1){ if(curhp < maxhp || Global.CoinFlip()){ if(HasAttr(AttrType.ON_FIRE)){ attrs[AttrType.FIRE_HIT]++; } Attack(0,target); if(HasAttr(AttrType.ON_FIRE)){ attrs[AttrType.FIRE_HIT]--; } } else{ B.Add(the_name + " stares at you silently. ",this); Q1(); } } else{ if(speed == 90){ if(curhp < maxhp){ AI_Step(target); QS(); } else{ if(Global.CoinFlip()){ AI_Step(TileInDirection(Global.RandomDirection())); } else{ if(Global.Roll(1,3) == 3 && DistanceFrom(player) <= 6){ if(player.CanSee(this)){ B.Add(the_name + " emits an eerie whistling sound. "); } else{ B.Add("You hear an eerie whistling sound. "); } } } Q1(); //note that the scarecrow doesn't move quickly until it is disturbed. } } else{ AI_Step(target); QS(); } } break;*/ case ActorType.BLOOD_MOTH: { PhysicalObject brightest = null; if (!M.wiz_lite && !M.wiz_dark) { List<PhysicalObject> current_brightest = new List<PhysicalObject>(); foreach (Tile t in M.AllTiles()) { int pos_radius = t.light_radius; PhysicalObject pos_obj = t; if (t.inv != null && t.inv.light_radius > pos_radius) { pos_radius = t.inv.light_radius; pos_obj = t.inv; } if (t.actor() != null && t.actor().LightRadius() > pos_radius) { pos_radius = t.actor().LightRadius(); pos_obj = t.actor(); } if (pos_radius > 0) { if (current_brightest.Count == 0 && CanSee(t)) { current_brightest.Add(pos_obj); } else { foreach (PhysicalObject o in current_brightest) { if (pos_radius > o.light_radius) { if (CanSee(t)) { current_brightest.Clear(); current_brightest.Add(pos_obj); break; } } else { if (pos_radius == o.light_radius && DistanceFrom(t) < DistanceFrom(o)) { if (CanSee(t)) { current_brightest.Clear(); current_brightest.Add(pos_obj); break; } } else { if (pos_radius == o.light_radius && DistanceFrom(t) == DistanceFrom(o) && pos_obj == player) { if (CanSee(t)) { current_brightest.Clear(); current_brightest.Add(pos_obj); break; } } } } } } } } if (current_brightest.Count > 0) { brightest = current_brightest.Random(); } } if (brightest != null) { if (DistanceFrom(brightest) <= 1) { if (brightest == target) { await Attack(0, target); if (target == player && player.curhp > 0) { await Help.TutorialTip(TutorialTopic.Torch); } } else { List<Tile> open = new List<Tile>(); foreach (Tile t in TilesAtDistance(1)) { if (t.DistanceFrom(brightest) <= 1 && t.passable && t.actor() == null) { open.Add(t); } } if (open.Count > 0) { await AI_Step(open.Random()); } QS(); } } else { await AI_Step(brightest); QS(); } } else { int dir = Global.RandomDirection(); if (TilesAtDistance(1).Where(t => !t.passable).Count > 4 && !TileInDirection(dir).passable) { dir = Global.RandomDirection(); } if (TileInDirection(dir).passable && ActorInDirection(dir) == null) { await AI_Step(TileInDirection(dir)); QS(); } else { if (curhp < maxhp && ActorInDirection(dir) == target) { await Attack(0, target); } else { if (player.HasLOS(TileInDirection(dir))) { if (!TileInDirection(dir).passable) { B.Add(the_name + " brushes up against " + TileInDirection(dir).the_name + ". ", this); } else { if (ActorInDirection(dir) != null) { B.Add(the_name + " brushes up against " + ActorInDirection(dir).TheVisible() + ". ", this); } } } QS(); } } } break; } case ActorType.SWORDSMAN: case ActorType.PHANTOM_SWORDMASTER: if (DistanceFrom(target) == 1) { await Attack(0, target); if (!HasAttr(AttrType.COOLDOWN_1)) { B.Add(You("adopt") + " a more aggressive stance. ", this); attrs[AttrType.BONUS_COMBAT] += 5; } } else { await AI_Step(target); QS(); } break; case ActorType.DARKNESS_DWELLER: if (HasAttr(AttrType.COOLDOWN_1)) { int dir = Global.RandomDirection(); if (!TileInDirection(dir).passable) { B.Add(You("stagger") + " into " + TileInDirection(dir).the_name + ". ", this); } else { if (ActorInDirection(dir) != null) { B.Add(YouVisible("stagger") + " into " + ActorInDirection(dir).TheVisible() + ". ", new PhysicalObject[] { this, ActorInDirection(dir) }); } else { if (GrabPreventsMovement(TileInDirection(dir))) { B.Add(the_name + " staggers and almost falls over. ", this); } else { B.Add(You("stagger") + ". ", this); await Move(TileInDirection(dir).row, TileInDirection(dir).col); } } } QS(); } else { if (DistanceFrom(target) == 1) { await Attack(0, target); } else { await AI_Step(target); QS(); } } break; case ActorType.CARNIVOROUS_BRAMBLE: case ActorType.MUD_TENTACLE: if (DistanceFrom(target) == 1) { await Attack(0, target); if (target == player && player.curhp > 0) { await Help.TutorialTip(TutorialTopic.RangedAttacks); } } else { QS(); } break; case ActorType.FROSTLING: if (DistanceFrom(target) == 1) { if (!HasAttr(AttrType.COOLDOWN_2)) { //burst attack cooldown attrs[AttrType.COOLDOWN_2]++; int cooldown = 100 * (Global.Roll(1, 3) + 8); Q.Add(new Event(this, cooldown, AttrType.COOLDOWN_2)); AnimateExplosion(this, 1, Color.RandomIce, "*"); await Attack(2, target); } else { if (Global.CoinFlip()) { await Attack(0, target); } else { if (await AI_Step(target, true)) { QS(); } else { await Attack(0, target); } } } } else { if (FirstActorInLine(target) == target && !HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 6) { int cooldown = Global.Roll(1, 4); if (cooldown != 1) { attrs[AttrType.COOLDOWN_1]++; cooldown *= 100; Q.Add(new Event(this, cooldown, AttrType.COOLDOWN_1)); } AnimateBoltProjectile(target, Color.RandomIce); await Attack(1, target); } else { if (!HasAttr(AttrType.COOLDOWN_2)) { await AI_Step(target); } else { await AI_Sidestep(target); //message for this? hmm. } QS(); } } break; case ActorType.DREAM_WARRIOR: if (DistanceFrom(target) == 1) { if (curhp <= 18 && !HasAttr(AttrType.COOLDOWN_1)) { attrs[AttrType.COOLDOWN_1]++; List<Tile> openspaces = new List<Tile>(); foreach (Tile t in target.TilesAtDistance(1)) { if (t.passable && t.actor() == null) { openspaces.Add(t); } } foreach (Tile t in openspaces) { if (group == null) { group = new List<Actor> { this }; } Create(ActorType.DREAM_CLONE, t.row, t.col, true, true); t.actor().player_visibility_duration = -1; group.Add(M.actor[t.row, t.col]); M.actor[t.row, t.col].group = group; group.Randomize(); } openspaces.Add(tile()); Tile newtile = openspaces[Global.Roll(openspaces.Count) - 1]; if (newtile != tile()) { await Move(newtile.row, newtile.col, false); } if (openspaces.Count > 1) { B.Add(the_name + " is suddenly standing all around " + target.the_name + ". "); Q1(); } else { await Attack(0, target); } } else { await Attack(0, target); } } else { await AI_Step(target); QS(); } break; case ActorType.CULTIST: if (curhp <= 10 && !HasAttr(AttrType.COOLDOWN_1)) { attrs[AttrType.COOLDOWN_1]++; string invocation; switch (Global.Roll(4)) { case 1: invocation = "ae vatra kersai"; break; case 2: invocation = "kersai dzaggath"; break; case 3: invocation = "od fir od bahgal"; break; case 4: invocation = "denei kersai nammat"; break; default: invocation = "gubed gubed gubed"; break; } if (Global.CoinFlip()) { B.Add(You("whisper") + " '" + invocation + "'. ", this); } else { B.Add(You("scream") + " '" + invocation.ToUpper() + "'. ", this); } B.Add("Flames erupt from " + the_name + ". ", this); if (LightRadius() < 2) { UpdateRadius(LightRadius(), 2); } attrs[AttrType.ON_FIRE] = Math.Max(attrs[AttrType.ON_FIRE], 2); foreach (Actor a in ActorsAtDistance(1)) { if (!a.HasAttr(AttrType.RESIST_FIRE) && !a.HasAttr(AttrType.IMMUNE_FIRE) && !a.HasAttr(AttrType.ON_FIRE) && !a.HasAttr(AttrType.CATCHING_FIRE) && !a.HasAttr(AttrType.STARTED_CATCHING_FIRE_THIS_TURN)) { if (a.name == "you") { B.Add("You start to catch fire! "); } else { B.Add(a.the_name + " starts to catch fire. ", a); } a.attrs[AttrType.CATCHING_FIRE] = 1; } } Q1(); } else { if (DistanceFrom(target) == 1) { await Attack(0, target); } else { await AI_Step(target); QS(); } } break; case ActorType.GOBLIN_ARCHER: case ActorType.PHANTOM_ARCHER: switch (DistanceFrom(target)) { case 1: if (target.EnemiesAdjacent() > 1) { await Attack(0, target); } else { if (await AI_Step(target, true)) { QS(); } else { await Attack(0, target); } } break; case 2: if (FirstActorInLine(target) == target) { await FireArrow(target); } else { if (await AI_Step(target, true)) { QS(); } else { if (await AI_Sidestep(target)) { B.Add(the_name + " tries to line up a shot. ", this); } QS(); } } break; case 3: case 4: case 5: case 6: case 7: case 8: if (FirstActorInLine(target) == target) { await FireArrow(target); } else { if (await AI_Sidestep(target)) { B.Add(the_name + " tries to line up a shot. ", this); } QS(); } break; default: await AI_Step(target); QS(); break; } break; case ActorType.GOBLIN_SHAMAN: { foreach (Actor a in ActorsWithinDistance(2)) { if (a.HasAttr(AttrType.SPELL_DISRUPTION) && a.HasLOE(this)) { if (DistanceFrom(target) == 1) { await Attack(0, target); } else { await AI_Step(target); QS(); } return; } } List<SpellType> valid_spells = new List<SpellType>(); valid_spells.Add(SpellType.FORCE_PALM); valid_spells.Add(SpellType.IMMOLATE); if (target.HasAttr(AttrType.ON_FIRE) || target.HasAttr(AttrType.CATCHING_FIRE)) { valid_spells.Remove(SpellType.IMMOLATE); } SpellType[] close_spells = valid_spells.ToArray(); valid_spells.Add(SpellType.SCORCH); //SpellType[] all_spells = valid_spells.ToArray(); valid_spells.Remove(SpellType.FORCE_PALM); SpellType[] ranged_spells = valid_spells.ToArray(); switch (DistanceFrom(target)) { case 1: if (target.EnemiesAdjacent() > 1 || Global.CoinFlip()) { await CastRandomSpell(target, close_spells); } else { if (await AI_Step(target, true)) { QS(); } else { await CastRandomSpell(target, close_spells); } } break; case 2: if (Global.CoinFlip()) { if (await AI_Step(target, true)) { QS(); } else { if (FirstActorInLine(target) == target) { await CastRandomSpell(target, ranged_spells); } else { await AI_Sidestep(target); QS(); } } } else { if (FirstActorInLine(target) == target) { await CastRandomSpell(target, ranged_spells); } else { if (await AI_Step(target, true)) { QS(); } else { await AI_Sidestep(target); QS(); } } } break; case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: if (FirstActorInLine(target) == target) { await CastRandomSpell(target, ranged_spells); } else { await AI_Sidestep(target); QS(); } break; default: await AI_Step(target); QS(); break; } break; } case ActorType.SKULKING_KILLER: if (!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 3) { attrs[AttrType.COOLDOWN_1]++; AnimateProjectile(target, Color.DarkYellow, "%"); if (target.CanSee(this)) { B.Add(the_name + " throws a bola at " + target.the_name + ". ", this, target); } else { B.Add("A bola whirls toward " + target.the_name + ". ", this, target); } attrs[AttrType.TURNS_VISIBLE] = -1; target.attrs[AttrType.SLOWED]++; target.speed += 100; Q.Add(new Event(target, (Global.Roll(3) + 5) * 100, AttrType.SLOWED, target.YouAre() + " no longer slowed. ", new PhysicalObject[] { target })); B.Add(target.YouAre() + " slowed by the bola. ", target); Q1(); } else { if (DistanceFrom(target) == 1) { await Attack(0, target); } else { await AI_Step(target); QS(); } } break; case ActorType.ZOMBIE: case ActorType.PHANTOM_ZOMBIE: if (DistanceFrom(target) == 1) { await Attack(1, target); } else { await AI_Step(target); if (DistanceFrom(target) == 1) { await Attack(0, target); } else { QS(); } } break; case ActorType.ROBED_ZEALOT: foreach (Actor a in ActorsWithinDistance(2)) { if (a.HasAttr(AttrType.SPELL_DISRUPTION) && a.HasLOE(this)) { if (DistanceFrom(target) == 1) { await Attack(0, target); } else { await AI_Step(target); QS(); } return; } } switch (DistanceFrom(target)) { case 1: if (HasAttr(AttrType.BLESSED)) { await Attack(0, target); } else { if (curhp <= 13) { await CastSpell(SpellType.MINOR_HEAL); } else { if (curhp < maxhp) { if (HasAttr(AttrType.HOLY_SHIELDED)) { await CastSpell(SpellType.BLESS); } else { await CastRandomSpell(null, new SpellType[] { SpellType.HOLY_SHIELD, SpellType.BLESS }); } } else { await CastSpell(SpellType.BLESS); } } } break; case 2: if (curhp <= 20) { await CastSpell(SpellType.MINOR_HEAL); } else { if (HasAttr(AttrType.BLESSED)) { if (await AI_Step(target)) { QS(); } else { await AI_Sidestep(target); QS(); } } else { if (Global.Roll(1, 3) == 3) { await CastSpell(SpellType.BLESS); } else { if (await AI_Step(target)) { QS(); } else { if (await AI_Sidestep(target)) { QS(); } else { await CastSpell(SpellType.BLESS); } } } } } break; default: if (curhp <= 26) { await CastSpell(SpellType.MINOR_HEAL); } else { if (curhp < maxhp) { if (HasAttr(AttrType.HOLY_SHIELDED)) { if (await AI_Step(target)) { QS(); } else { if (await AI_Sidestep(target)) { QS(); } else { await CastSpell(SpellType.BLESS); } } } else { if (Global.CoinFlip()) { await CastSpell(SpellType.HOLY_SHIELD); } else { if (await AI_Step(target)) { QS(); } else { if (await AI_Sidestep(target)) { QS(); } else { await CastSpell(SpellType.BLESS); } } } } } else { if (await AI_Step(target)) { QS(); } else { if (await AI_Sidestep(target)) { QS(); } else { await CastSpell(SpellType.BLESS); } } } } break; } break; case ActorType.BANSHEE: if (!HasAttr(AttrType.COOLDOWN_1)) { attrs[AttrType.COOLDOWN_1]++; Q.Add(new Event(this, (Global.Roll(5) + 5) * 100, AttrType.COOLDOWN_1)); if (player.CanSee(this)) { B.Add(You("scream") + ". ", this); } else { if (DistanceFrom(player) <= 12) { B.Add("You hear a scream! "); } else { B.Add("You hear a distant scream! "); } } int i = 1; Actor a; List<Actor> targets = new List<Actor>(); for (bool done = false; !done; ++i) { a = FirstActorInLine(target, i); if (a != null && !a.HasAttr(AttrType.UNDEAD) && !a.HasAttr(AttrType.CONSTRUCT) && !a.HasAttr(AttrType.PLANTLIKE)) { targets.Add(a); } if (a == target) { done = true; } if (i > 100) { B.Add(target.You("resist") + " the scream. ", target); Q1(); return; } } foreach (Actor actor in targets) { if (await actor.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(6), this, "a banshee's scream")) { actor.attrs[AttrType.AFRAID]++; Q.Add(new Event(actor, actor.DurationOfMagicalEffect((Global.Roll(3) + 2)) * 100, AttrType.AFRAID)); } } Q1(); } else { if (DistanceFrom(target) == 1) { await Attack(0, target); } else { await AI_Step(target); QS(); } } break; case ActorType.PHASE_SPIDER: { int action = 0; if (DistanceFrom(target) == 1) { if (Global.CoinFlip()) { action = 2; //disappear } else { if (Global.CoinFlip()) { await Attack(0, target); } else { action = 1; //blink } } } else { if (Global.CoinFlip()) { //teleport next to target and attack List<Tile> tilelist = new List<Tile>(); for (int dir = 1; dir <= 9; ++dir) { if (dir != 5) { if (target.TileInDirection(dir).passable && target.ActorInDirection(dir) == null) { tilelist.Add(target.TileInDirection(dir)); } } } if (tilelist.Count > 0) { Tile t = tilelist[Global.Roll(1, tilelist.Count) - 1]; await Move(t.row, t.col); await Attack(0, target); } else { action = 2; //disappear } } else { if (Global.CoinFlip()) { action = 1; //blink } else { action = 2; //disappear } } } switch (action) { case 1: //blink for (int i = 0; i < 9999; ++i) { int a = Global.Roll(1, 17) - 9; //-8 to 8 int b = Global.Roll(1, 17) - 9; if (Math.Abs(a) + Math.Abs(b) >= 6) { a += row; b += col; if (M.BoundsCheck(a, b)) { if (M.tile[a, b].passable && M.actor[a, b] == null) { await Move(a, b); break; } } } } QS(); break; case 2: //disappear from target's sight bool[,] valid_tiles = new bool[ROWS, COLS]; for (int i = 0; i < ROWS; ++i) { for (int j = 0; j < COLS; ++j) { if (M.tile[i, j].passable && M.actor[i, j] == null && !target.CanSee(i, j)) { valid_tiles[i, j] = true; } else { valid_tiles[i, j] = false; } } } List<Tile> tilelist = new List<Tile>(); bool found = false; for (int distance = 1; distance < COLS && !found; ++distance) { for (int i = row - distance; i <= row + distance; ++i) { for (int j = col - distance; j <= col + distance; ++j) { if (M.BoundsCheck(i, j) && valid_tiles[i, j] && DistanceFrom(i, j) == distance) { found = true; tilelist.Add(M.tile[i, j]); } } } } if (found) { Tile t = tilelist[Global.Roll(1, tilelist.Count) - 1]; await Move(t.row, t.col); } QS(); break; default: break; } break; } case ActorType.DERANGED_ASCETIC: if (DistanceFrom(target) == 1) { await Attack(Global.Roll(3) - 1, target); } else { await AI_Step(target); QS(); } break; case ActorType.POLTERGEIST: if (inv.Count == 0) { if (DistanceFrom(target) == 1) { int target_r = target.row; int target_c = target.col; if (await Attack(0, target) && M.actor[target_r, target_c] != null && target.inv.Any(i => !i.do_not_stack)) { Item item = target.inv.Where(i => !i.do_not_stack).Random(); if (item.quantity > 1) { inv.Add(new Item(item, -1, -1)); item.quantity--; } else { inv.Add(item); target.inv.Remove(item); } B.Add(YouVisible("steal") + " " + target.YourVisible() + " " + item.Name() + "! ", this, target); } } else { await AI_Step(target); QS(); } } else { List<Tile> line = target.GetBestExtendedLineOfEffect(this); Tile next = null; bool found = false; foreach (Tile t in line) { if (found) { next = t; break; } else { if (t.actor() == this) { found = true; } } } if (next != null) { if (next.passable && next.actor() == null && await AI_Step(next)) { QS(); } else { if (!next.passable) { B.Add(the_name + " disappears into " + next.the_name + ". ", this); foreach (Tile t in TilesWithinDistance(1)) { if (t.DistanceFrom(next) == 1 && t.name == "floor") { t.features.Add(FeatureType.SLIME); } } Event e = null; foreach (Event e2 in Q.list) { if (e2.target == this && e2.evtype == EventType.POLTERGEIST) { e = e2; break; } } e.target = inv[0]; Actor.tiebreakers[e.tiebreaker] = null; inv.Clear(); await TakeDamage(DamageType.NORMAL, DamageClass.NO_TYPE, 9999, null); } else { if (next.actor() != null) { if (!next.actor().HasAttr(AttrType.NEVER_MOVES)) { await Move(next.row, next.col); QS(); } else { if (next.actor().HasAttr(AttrType.NEVER_MOVES)) { if (await AI_Step(next)) { QS(); } else { if (DistanceFrom(target) == 1) { await Attack(1, target); } else { QS(); } } } } } else { QS(); } } } } } break; case ActorType.CAVERN_HAG: if (curhp < maxhp && !HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12) { B.Add(the_name + " curses you! "); switch (Global.Roll(4)) { case 1: //light allergy B.Add("You become allergic to light! "); target.GainAttrRefreshDuration(AttrType.LIGHT_ALLERGY, 10000, "You are no longer allergic to light. "); break; case 2: //magical drowsiness B.Add("The floor suddenly looks like a wonderful spot for a nap. "); target.GainAttrRefreshDuration(AttrType.MAGICAL_DROWSINESS, 10000, "You are no longer quite so drowsy. "); break; case 3: //aggravate monsters B.Add("Every sound you make becomes amplified and echoes across the dungeon. "); target.GainAttrRefreshDuration(AttrType.AGGRAVATING, 10000, "Your sounds are no longer amplified. "); break; case 4: //cursed weapon B.Add("Your " + Weapon.Name(target.weapons[0]) + " becomes stuck to your hand! "); target.GainAttrRefreshDuration(AttrType.CURSED_WEAPON, 10000, "Your " + Weapon.Name(target.weapons[0]) + " is no longer stuck to your hand. "); break; } attrs[Forays.AttrType.COOLDOWN_1]++; Q1(); } else { if (DistanceFrom(target) == 1) { await Attack(0, target); } else { await AI_Step(target); QS(); } } break; case ActorType.COMPY: if (DistanceFrom(target) == 1) { pos target_pos = target.p; if (await Attack(0, target) && M.actor[target_pos] != null && target == player && !target.HasAttr(AttrType.INVULNERABLE) && !target.HasAttr(AttrType.ARCANE_SHIELDED) && !target.HasAttr(AttrType.IMMUNE_TOXINS)) { bool first_bite = !target.HasAttr(AttrType.COMPY_POISON_COUNTER); target.GainAttrRefreshDuration(AttrType.COMPY_POISON_COUNTER, 5000, "You no longer feel the effects of the poison. "); if (target.attrs[Forays.AttrType.COMPY_POISON_COUNTER] >= target.curhp) { if (!target.HasAttr(AttrType.COMPY_POISON_LETHAL)) { B.Add("The poison is overwhelming you! "); B.Add("You're falling asleep. "); B.Add("You'll surely be eaten... "); await B.PrintAll(); target.attrs[Forays.AttrType.COMPY_POISON_LETHAL]++; } } else { if (target.attrs[Forays.AttrType.COMPY_POISON_COUNTER] >= target.curhp / 2 && !target.HasAttr(AttrType.COMPY_POISON_WARNING)) { target.GainAttrRefreshDuration(AttrType.COMPY_POISON_WARNING, 5000); B.Add("You feel the subtle poison starting to take effect. "); B.Add("Your injuries make it hard to stay awake. "); await B.PrintAll(); } else { if (first_bite) { B.Add("The compy's bite makes you momentarily fatigued. "); B.Add("You shake off the effects. "); } } } } } else { await AI_Step(target); QS(); } break; case ActorType.NOXIOUS_WORM: if (!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12) { B.Add(TheVisible() + " breathes poisonous gas. "); List<Tile> area = new List<Tile>(); foreach (Tile t in target.TilesWithinDistance(1)) { if (t.passable && target.HasLOE(t) && !t.Is(FeatureType.POISON_GAS)) { t.features.Add(FeatureType.POISON_GAS); area.Add(t); } } Tile current = target.tile(); int num = 8; for (int i = 0; i < num; ++i) { //i should make this gas placement bit into a method if (!current.Is(FeatureType.POISON_GAS)) { current.features.Add(FeatureType.POISON_GAS); area.Add(current); } else { for (int tries = 0; tries < 50; ++tries) { List<Tile> open = new List<Tile>(); foreach (Tile t in current.TilesAtDistance(1)) { if (t.passable) { open.Add(t); } } if (open.Count > 0) { Tile possible = open.Random(); if (!possible.Is(FeatureType.POISON_GAS)) { possible.features.Add(FeatureType.POISON_GAS); area.Add(possible); break; } else { current = possible; } } else { break; } } } } Q.Add(new Event(area, 600, EventType.POISON_GAS)); GainAttr(AttrType.COOLDOWN_1, (Global.Roll(6) + 18) * 100); Q1(); } else { if (DistanceFrom(target) == 1) { await Attack(0, target); } else { await AI_Step(target); QS(); } } break; case ActorType.BERSERKER: if (HasAttr(AttrType.COOLDOWN_2)) { int dir = attrs[AttrType.COOLDOWN_2]; bool cw = Global.CoinFlip(); if (TileInDirection(dir).passable && ActorInDirection(dir) == null && !GrabPreventsMovement(TileInDirection(dir))) { B.Add(the_name + " leaps forward swinging his axe! ", this); await Move(TileInDirection(dir).row, TileInDirection(dir).col); Actor a = ActorInDirection(RotateDirection(dir, cw)); if (a != null) { B.Add(Your() + " axe hits " + a.the_name + ". ", this, a); await a.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(3, 6), this, "a berserker's axe"); } a = ActorInDirection(dir); if (a != null) { B.Add(Your() + " axe hits " + a.the_name + ". ", this, a); await a.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(3, 6), this, "a berserker's axe"); } a = ActorInDirection(RotateDirection(dir, !cw)); if (a != null) { B.Add(Your() + " axe hits " + a.the_name + ". ", this, a); await a.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(3, 6), this, "a berserker's axe"); } Q1(); } else { if (ActorInDirection(dir) != null || GrabPreventsMovement(TileInDirection(dir))) { B.Add(the_name + " swings his axe furiously! ", this); Actor a = ActorInDirection(RotateDirection(dir, cw)); if (a != null) { B.Add(Your() + " axe hits " + a.the_name + ". ", this, a); await a.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(3, 6), this, "a berserker's axe"); } a = ActorInDirection(dir); if (a != null) { B.Add(Your() + " axe hits " + a.the_name + ". ", this, a); await a.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(3, 6), this, "a berserker's axe"); } a = ActorInDirection(RotateDirection(dir, !cw)); if (a != null) { B.Add(Your() + " axe hits " + a.the_name + ". ", this, a); await a.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(3, 6), this, "a berserker's axe"); } Q1(); } else { B.Add(the_name + " turns to face " + target.the_name + ". ", this, target); attrs[AttrType.COOLDOWN_2] = DirectionOf(target); Q1(); } } } else { if (DistanceFrom(target) == 1) { await Attack(0, target); if (target != null && Global.Roll(3) == 3) { B.Add(the_name + " screams with fury! ", this); attrs[AttrType.COOLDOWN_2] = DirectionOf(target); Q.Add(new Event(this, 350, AttrType.COOLDOWN_2, Your() + " rage diminishes. ", new PhysicalObject[] { this })); } } else { await AI_Step(target); QS(); } } break; case ActorType.VAMPIRE: if (DistanceFrom(target) == 1) { await Attack(0, target); } else { if (DistanceFrom(target) <= 12) { if (tile().IsLit() && !HasAttr(AttrType.COOLDOWN_1)) { attrs[Forays.AttrType.COOLDOWN_1]++; B.Add(the_name + " gestures. ", this); List<Tile> tiles = new List<Tile>(); foreach (Tile t in target.TilesWithinDistance(6)) { if (t.passable && t.actor() == null && DistanceFrom(t) >= DistanceFrom(target) && target.HasLOS(t) && target.HasLOE(t)) { tiles.Add(t); } } if (tiles.Count == 0) { foreach (Tile t in target.TilesWithinDistance(6)) { //same, but with no distance requirement if (t.passable && t.actor() == null && target.HasLOS(t) && target.HasLOE(t)) { tiles.Add(t); } } } if (tiles.Count == 0) { B.Add("Nothing happens. ", this); } else { if (tiles.Count == 1) { B.Add("A blood moth appears! "); } else { B.Add("Blood moths appear! "); } for (int i = 0; i < 2; ++i) { if (tiles.Count > 0) { Tile t = tiles.RemoveRandom(); Create(Forays.ActorType.BLOOD_MOTH, t.row, t.col, true, true); M.actor[t.row, t.col].player_visibility_duration = -1; } } } Q1(); } else { await AI_Step(target); QS(); } } else { await AI_Step(target); QS(); } } break; case ActorType.MUD_ELEMENTAL: { int count = 0; int walls = 0; foreach (Tile t in target.TilesAtDistance(1)) { if (t.ttype == TileType.WALL) { ++walls; if (t.actor() == null) { ++count; } } } if (DistanceFrom(target) <= 12 && count >= 2 || (count == 1 && walls == 1)) { foreach (Tile t in target.TilesAtDistance(1)) { if (t.ttype == TileType.WALL && t.actor() == null) { Create(ActorType.MUD_TENTACLE, t.row, t.col, true, true); M.actor[t.p].player_visibility_duration = -1; M.actor[t.p].attrs[Forays.AttrType.COOLDOWN_1] = 20; } } if (count >= 2) { B.Add("Mud tentacles emerge from the walls! "); } else { B.Add("A mud tentacle emerges from the wall! "); } Q1(); } else { if (DistanceFrom(target) == 1) { await Attack(0, target); } else { await AI_Step(target); QS(); } } break; } case ActorType.ENTRANCER: if (group == null) { if (await AI_Step(target, true)) { QS(); } else { if (DistanceFrom(target) == 1) { await Attack(0, target); } else { QS(); } } } else { Actor thrall = group[1]; if (CanSee(thrall)) { //cooldown 1 is teleport. cooldown 2 is shield. if (DistanceFrom(target) < thrall.DistanceFrom(target) && DistanceFrom(thrall) == 1) { await Move(thrall.row, thrall.col); QS(); } else { if (DistanceFrom(target) == 1 && curhp < maxhp) { List<Tile> safe = TilesAtDistance(1).Where(t => t.passable && t.actor() == null && target.GetBestExtendedLineOfEffect(thrall).Contains(t)); if (DistanceFrom(thrall) == 1 && safe.Count > 0) { await AI_Step(safe.Random()); QS(); } else { if (await AI_Step(target, true)) { QS(); } else { await Attack(0, target); } } } else { if (!HasAttr(AttrType.COOLDOWN_1) && (thrall.DistanceFrom(target) > 1 || !target.GetBestExtendedLineOfEffect(thrall).Any(t => t.actor() == this))) { //the entrancer tries to be smart about placing the thrall in a position that blocks ranged attacks List<Tile> closest = new List<Tile>(); int dist = 99; foreach (Tile t in thrall.TilesWithinDistance(2).Where(x => x.passable && (x.actor() == null || x.actor() == thrall))) { if (t.DistanceFrom(target) < dist) { closest.Clear(); closest.Add(t); dist = t.DistanceFrom(target); } else { if (t.DistanceFrom(target) == dist) { closest.Add(t); } } } List<Tile> in_line = new List<Tile>(); foreach (Tile t in closest) { if (target.GetBestExtendedLineOfEffect(t).Any(x => x.actor() == this)) { in_line.Add(t); } } Tile tile = null; if (in_line.Count > 0) { tile = in_line.Random(); } else { if (closest.Count > 0) { tile = closest.Random(); } } if (tile != null && tile.actor() != thrall) { GainAttr(AttrType.COOLDOWN_1, 400); B.Add(TheVisible() + " teleports " + thrall.TheVisible() + ". ", this, thrall); M.Draw(); await thrall.Move(tile.row, tile.col); B.DisplayNow(); Screen.AnimateStorm(tile.p, 1, 1, 4, thrall.symbol, thrall.color); foreach (Tile t2 in thrall.GetBestLineOfEffect(tile)) { Screen.AnimateStorm(t2.p, 1, 1, 4, thrall.symbol, thrall.color); } Q1(); } else { List<Tile> safe = target.GetBestExtendedLineOfEffect(thrall).Where(t => t.passable && t.actor() == null && t.DistanceFrom(target) > thrall.DistanceFrom(target)).WhereLeast(t => DistanceFrom(t)); if (safe.Any(t => t.DistanceFrom(target) > 2)) { await AI_Step(safe.Where(t => t.DistanceFrom(target) > 2).Random()); } else { await AI_Step(safe.Random()); } QS(); } } else { if (!HasAttr(AttrType.COOLDOWN_2) && !thrall.HasAttr(AttrType.ARCANE_SHIELDED)) { GainAttr(AttrType.COOLDOWN_2, 1500); B.Add(TheVisible() + " shields " + thrall.TheVisible() + ". ", this, thrall); B.DisplayNow(); Screen.AnimateStorm(thrall.p, 1, 2, 5, "*", Color.White); thrall.attrs[Forays.AttrType.ARCANE_SHIELDED] = 25; Q.Add(new Event(thrall, 2000, AttrType.ARCANE_SHIELDED, thrall.Your() + " arcane shield dissolves. ", new PhysicalObject[] { thrall })); Q1(); } else { List<Tile> safe = target.GetBestExtendedLineOfEffect(thrall).Where(t => t.passable && t.actor() == null).WhereLeast(t => DistanceFrom(t)); if (safe.Any(t => t.DistanceFrom(target) > 2)) { await AI_Step(safe.Where(t => t.DistanceFrom(target) > 2).Random()); } else { await AI_Step(safe.Random()); } QS(); } } } } } else { group[1].FindPath(this); //call for help if (await AI_Step(target, true)) { QS(); } else { if (DistanceFrom(target) == 1) { await Attack(0, target); } else { QS(); } } } } break; case ActorType.MARBLE_HORROR_STATUE: QS(); break; /*case ActorType.MARBLE_HORROR: break;//todo : anything here?*/ case ActorType.ORC_GRENADIER: if (!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 8) { attrs[AttrType.COOLDOWN_1]++; Q.Add(new Event(this, (Global.Roll(2) * 100) + 150, AttrType.COOLDOWN_1)); B.Add(the_name + " tosses a grenade toward " + target.the_name + ". ", this, target); List<Tile> tiles = new List<Tile>(); foreach (Tile tile in target.TilesWithinDistance(1)) { if (tile.passable) { tiles.Add(tile); } } Tile t = tiles[Global.Roll(tiles.Count) - 1]; if (t.actor() != null) { if (t.actor() == player) { B.Add("It lands under you! "); } else { B.Add("It lands under " + t.actor().the_name + ". ", t.actor()); } } else { if (t.inv != null) { B.Add("It lands under " + t.inv.TheName() + ". ", t); } } t.features.Add(FeatureType.GRENADE); Q.Add(new Event(t, 100, EventType.GRENADE)); Q1(); } else { if (curhp <= 18) { if (await AI_Step(target, true)) { B.Add(the_name + " backs away. ", this); QS(); } else { if (DistanceFrom(target) == 1) { await Attack(0, target); } else { QS(); } } } else { if (DistanceFrom(target) == 1) { await Attack(0, target); } else { await AI_Step(target); QS(); } } } break; case ActorType.SHADOWVEIL_DUELIST: if (DistanceFrom(target) == 1) { await Attack(0, target); if (target != null) { List<Tile> valid_dirs = new List<Tile>(); foreach (Tile t in target.TilesAtDistance(1)) { if (t.passable && t.actor() == null && DistanceFrom(t) == 1) { valid_dirs.Add(t); } } if (valid_dirs.Count > 0) { await AI_Step(valid_dirs.Random()); } } } else { await AI_Step(target); QS(); } break; case ActorType.CARRION_CRAWLER: if (DistanceFrom(target) == 1) { if (target.HasAttr(AttrType.PARALYZED)) { await Attack(0, target); } else { await Attack(Global.Roll(1, 2) - 1, target); } } else { await AI_Step(target); QS(); } break; case ActorType.SPELLMUDDLE_PIXIE: if (DistanceFrom(target) == 1) { await Attack(0, target); if (Global.CoinFlip()) { await AI_Step(target, true); } } else { await AI_Step(target); QS(); } break; case ActorType.PYREN_ARCHER: switch (DistanceFrom(target)) { case 1: if (target.EnemiesAdjacent() > 1) { await Attack(0, target); } else { if (await AI_Step(target, true)) { QS(); } else { await Attack(0, target); } } break; case 2: if (FirstActorInLine(target) == target) { await FireArrow(target); } else { if (await AI_Step(target, true)) { QS(); } else { if (await AI_Sidestep(target)) { B.Add(the_name + " tries to line up a shot. ", this); } QS(); } } break; case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: if (FirstActorInLine(target) == target) { await FireArrow(target); } else { if (await AI_Sidestep(target)) { B.Add(the_name + " tries to line up a shot. ", this); } QS(); } break; default: await AI_Step(target); QS(); break; } break; case ActorType.TROLL_SEER: if (curhp <= 10 && !HasAttr(AttrType.COOLDOWN_1)) { for (int i = 0; i < 9999; ++i) { int rr = Global.Roll(1, Global.ROWS - 2); int rc = Global.Roll(1, Global.COLS - 2); if (Math.Abs(rr - row) >= 10 || Math.Abs(rc - col) >= 10 || (Math.Abs(rr - row) >= 7 && Math.Abs(rc - col) >= 7)) { if (M.BoundsCheck(rr, rc) && M.tile[rr, rc].passable && M.actor[rr, rc] == null && !HasLOS(rr, rc)) { B.Add(TheVisible() + " slashes at the air, sending a swirling vortex toward " + target.the_name + ". ", target); AnimateBeam(target, "*", Color.Green); target.AnimateStorm(3, 3, 10, "*", Color.Green); await target.Move(rr, rc); M.Draw(); target.AnimateStorm(3, 3, 10, "*", Color.Green); B.Add(target.YouAre() + " transported elsewhere. "); attrs[Forays.AttrType.COOLDOWN_1]++; break; } } } QS(); } else { foreach (Actor a in ActorsWithinDistance(2)) { if (a.HasAttr(AttrType.SPELL_DISRUPTION) && a.HasLOE(this)) { if (DistanceFrom(target) == 1) { await Attack(0, target); } else { await AI_Step(target); QS(); } return; } } if (DistanceFrom(target) == 1) { await Attack(0, target); } else { if (DistanceFrom(target) <= 12 && FirstActorInLine(target) == target) { await CastRandomSpell(target, new SpellType[] { SpellType.GLACIAL_BLAST, SpellType.SONIC_BOOM }); } else { await AI_Step(target); QS(); } } } break; case ActorType.MECHANICAL_KNIGHT: if (DistanceFrom(target) == 1) { if (HasAttr(AttrType.COOLDOWN_1)) { //no arms await Attack(1, target); } else { if (true != await Attack(0, target)) { B.Add(the_name + " is off balance! ", this); attrs[Forays.AttrType.MECHANICAL_SHIELD] = 0; } } } else { if (!HasAttr(AttrType.COOLDOWN_2)) { //no legs await AI_Step(target); } QS(); } break; case ActorType.ORC_WARMAGE: { foreach (Actor a in ActorsWithinDistance(2)) { if (a.HasAttr(AttrType.SPELL_DISRUPTION) && a.HasLOE(this)) { if (DistanceFrom(target) == 1) { await Attack(0, target); } else { await AI_Step(target); QS(); } return; } } if (curhp <= 15 && HasLOS(target)) { Tile wall = null; int wall_distance_to_center = 9999; pos center = new pos(ROWS / 2, COLS / 2); for (int i = 2; i <= 8; i += 2) { if (TileInDirection(i).ttype == TileType.WALL) { if (TileInDirection(i).EstimatedEuclideanDistanceFromX10(center) < wall_distance_to_center) { wall = TileInDirection(i); wall_distance_to_center = TileInDirection(i).EstimatedEuclideanDistanceFromX10(center); } } } if (wall != null) { await CastSpell(Forays.SpellType.PASSAGE, wall); break; } } List<SpellType> valid_spells = new List<SpellType>(); valid_spells.Add(SpellType.FORCE_BEAM); valid_spells.Add(SpellType.IMMOLATE); valid_spells.Add(SpellType.GLACIAL_BLAST); valid_spells.Add(SpellType.GLACIAL_BLAST); if (target.HasAttr(AttrType.ON_FIRE) || target.HasAttr(AttrType.CATCHING_FIRE)) { valid_spells.Remove(Forays.SpellType.IMMOLATE); } SpellType[] ranged_spells = valid_spells.ToArray(); switch (DistanceFrom(target)) { case 1: if (target.EnemiesAdjacent() > 1 || Global.CoinFlip()) { await CastRandomSpell(target, new SpellType[] { SpellType.MAGIC_HAMMER, SpellType.MAGIC_HAMMER, SpellType.FORCE_BEAM }); } else { if (await AI_Step(target, true)) { QS(); } else { await CastRandomSpell(target, new SpellType[] { SpellType.MAGIC_HAMMER, SpellType.MAGIC_HAMMER, SpellType.FORCE_BEAM }); } } break; case 2: if (HasLOE(target) && FirstActorInLine(target) != target) { await CastSpell(SpellType.VOLTAIC_SURGE); break; } if (Global.CoinFlip()) { if (await AI_Step(target, true)) { QS(); } else { if (FirstActorInLine(target) == target) { await CastRandomSpell(target, new SpellType[] { SpellType.IMMOLATE, SpellType.FORCE_BEAM, SpellType.GLACIAL_BLAST }); } else { await AI_Sidestep(target); QS(); } } } else { if (FirstActorInLine(target) == target) { await CastRandomSpell(target, new SpellType[] { SpellType.IMMOLATE, SpellType.FORCE_BEAM, SpellType.GLACIAL_BLAST }); } else { if (await AI_Step(target, true)) { QS(); } else { await AI_Sidestep(target); QS(); } } } break; case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: if (FirstActorInLine(target) == target) { await CastRandomSpell(target, ranged_spells); } else { await AI_Sidestep(target); QS(); } break; default: await AI_Step(target); QS(); break; } break; } case ActorType.LASHER_FUNGUS: if (DistanceFrom(target) <= 12) { if (DistanceFrom(target) == 1) { await Attack(0, target); } else { if (FirstActorInLine(target) == target) { List<Tile> line = GetBestLine(target.row, target.col); line.Remove(line[line.Count - 1]); AnimateBoltBeam(line, Color.DarkGreen); if (Global.Roll(1, 4) == 4) { await Attack(0, target); } else { int target_r = target.row; int target_c = target.col; if (await Attack(1, target) && M.actor[target_r, target_c] != null) { if (target.HasAttr(AttrType.FROZEN)) { if (target.name == "you") { B.Add("You don't move far. "); } else { B.Add(target.the_name + " doesn't move far. ", target); } } else { int rowchange = 0; int colchange = 0; if (target.row < row) { rowchange = 1; } if (target.row > row) { rowchange = -1; } if (target.col < col) { colchange = 1; } if (target.col > col) { colchange = -1; } if (true != await target.AI_MoveOrOpen(target.row + rowchange, target.col + colchange)) { if (Math.Abs(target.row - row) > Math.Abs(target.col - col)) { await target.AI_Step(M.tile[row, target.col]); } else { if (Math.Abs(target.row - row) < Math.Abs(target.col - col)) { await target.AI_Step(M.tile[target.row, col]); } else { await target.AI_Step(this); } } } } } } } else { Q1(); } } } else { Q1(); } break; case ActorType.NECROMANCER: if (!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12) { attrs[AttrType.COOLDOWN_1]++; Q.Add(new Event(this, (Global.Roll(4) + 8) * 100, AttrType.COOLDOWN_1)); B.Add(the_name + " calls out to the dead. ", this); ActorType summon = Global.CoinFlip() ? ActorType.SKELETON : ActorType.ZOMBIE; List<Tile> tiles = new List<Tile>(); foreach (Tile tile in TilesWithinDistance(2)) { if (tile.passable && tile.actor() == null && DirectionOf(tile) == DirectionOf(target)) { tiles.Add(tile); } } if (tiles.Count == 0) { foreach (Tile tile in TilesWithinDistance(2)) { if (tile.passable && tile.actor() == null) { tiles.Add(tile); } } } if (tiles.Count == 0 || (group != null && group.Count > 3)) { B.Add("Nothing happens. ", this); } else { Tile t = tiles.Random(); B.Add(Prototype(summon).a_name + " digs through the floor! "); Create(summon, t.row, t.col, true, true); M.actor[t.row, t.col].player_visibility_duration = -1; if (group == null) { group = new List<Actor> { this }; } group.Add(M.actor[t.row, t.col]); M.actor[t.row, t.col].group = group; } Q1(); } else { bool blast = false; switch (DistanceFrom(target)) { case 1: if (await AI_Step(target, true)) { QS(); } else { await Attack(0, target); } break; case 2: if (Global.CoinFlip() && FirstActorInLine(target) == target) { blast = true; } else { if (await AI_Step(target, true)) { QS(); } else { blast = true; } } break; case 3: case 4: case 5: case 6: if (FirstActorInLine(target) == target) { blast = true; } else { await AI_Sidestep(target); QS(); } break; default: await AI_Step(target); QS(); break; } if (blast) { B.Add(the_name + " fires dark energy at " + target.the_name + ". ", this, target); AnimateBoltProjectile(target, Color.DarkBlue); await target.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(6), this, "*blasted by a necromancer"); Q1(); } } break; case ActorType.LUMINOUS_AVENGER: if (curhp <= 10 && !M.wiz_dark) { if (player.CanSee(this)) { B.Add(the_name + " absorbs the light from the air. "); } else { B.Add("Something drains the light from the air. "); } B.Add(the_name + " is restored. ", this); curhp = maxhp; M.wiz_dark = true; M.wiz_lite = false; Q1(); } else { if (DistanceFrom(target) == 1) { await Attack(0, target); } else { await AI_Step(target); QS(); } } break; case ActorType.FIRE_DRAKE: if (player.magic_items.Contains(MagicItemType.RING_OF_RESISTANCE) && DistanceFrom(player) <= 12 && CanSee(player)) { B.Add(the_name + " exhales an orange mist toward you. "); foreach (Tile t in GetBestLine(player)) { Screen.AnimateStorm(t.p, 1, 2, 3, "*", Color.Red); } B.Add("Your ring of resistance melts and drips onto the floor! "); player.magic_items.Remove(MagicItemType.RING_OF_RESISTANCE); Q.Add(new Event(this, 100, EventType.MOVE)); } else { if (player.armors[0] == ArmorType.FULL_PLATE_OF_RESISTANCE && DistanceFrom(player) <= 12 && CanSee(player)) { B.Add(the_name + " exhales an orange mist toward you. "); foreach (Tile t in GetBestLine(player)) { Screen.AnimateStorm(t.p, 1, 2, 3, "*", Color.Red); } B.Add("The runes drip from your full plate of resistance! "); player.armors[0] = ArmorType.FULL_PLATE; player.UpdateOnEquip(ArmorType.FULL_PLATE_OF_RESISTANCE, ArmorType.FULL_PLATE); Q.Add(new Event(this, 100, EventType.MOVE)); } else { if (!HasAttr(AttrType.COOLDOWN_1)) { if (DistanceFrom(target) <= 12) { attrs[AttrType.COOLDOWN_1]++; int cooldown = (Global.Roll(1, 4) + 1) * 100; Q.Add(new Event(this, cooldown, AttrType.COOLDOWN_1)); AnimateBeam(target, Color.RandomFire, "*"); await Attack(2, target); if (target != null && !target.HasAttr(AttrType.ON_FIRE) && !target.HasAttr(AttrType.CATCHING_FIRE)) { target.attrs[Forays.AttrType.CATCHING_FIRE] = 1; B.Add(target.You("start") + " catching fire! ", target); } } else { await AI_Step(target); QS(); } } else { if (DistanceFrom(target) == 1) { await Attack(Global.Roll(1, 2) - 1, target); } else { await AI_Step(target); QS(); } } } } break; default: if (DistanceFrom(target) == 1) { await Attack(0, target); } else { await AI_Step(target); QS(); } break; } }
public IEnumerable<Tile> ReachableTilesByDistance(int origin_row,int origin_col,bool return_reachable_walls,params TileType[] tiles_considered_passable) { int[,] values = new int[ROWS,COLS]; //note that this method never returns the map borders. it'd need to check bounds if i wanted that. for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ bool passable = tile[i,j].passable; foreach(TileType tt in tiles_considered_passable){ if(tile[i,j].type == tt){ passable = true; break; } } if(return_reachable_walls && !tile[i,j].solid_rock){ passable = true; } if(passable){ values[i,j] = 0; } else{ values[i,j] = -1; } } } int minrow = 1; int maxrow = ROWS-2; int mincol = 1; //todo: make it start at 1 radius and go out from there until it hits these limits. int maxcol = COLS-2; values[origin_row,origin_col] = 1; int val = 1; bool done = false; List<Tile> just_added = new List<Tile>{tile[origin_row,origin_col]}; while(!done){ done = true; while(just_added.Count > 0){ yield return just_added.RemoveRandom(); } for(int i=minrow;i<=maxrow;++i){ for(int j=mincol;j<=maxcol;++j){ if(values[i,j] == val){ for(int s=i-1;s<=i+1;++s){ for(int t=j-1;t<=j+1;++t){ if(values[s,t] == 0){ values[s,t] = val + 1; done = false; just_added.Add(tile[s,t]); } } } } } } ++val; } }
public void AlterRooms(int no_change_freq,int add_pillars_freq,int cross_room_freq,int cave_widen_freq,int cave_fill_freq) { List<int> modification = new List<int>(); for(int i=0;i<no_change_freq;++i){ modification.Add(0); } for(int i=0;i<add_pillars_freq;++i){ modification.Add(1); } for(int i=0;i<cross_room_freq;++i){ modification.Add(2); } for(int i=0;i<cave_widen_freq;++i){ modification.Add(3); } for(int i=0;i<cave_fill_freq;++i){ modification.Add(4); } if(modification.Count == 0){ return; } ForEachRectangularRoom((start_r,start_c,end_r,end_c) => { int mod = modification.Random(); switch(mod){ case 0: return true; case 1: { int height = end_r - start_r + 1; int width = end_c - start_c + 1; if(height > 3 || width > 3){ List<PillarArrangement> layouts = new List<PillarArrangement>(); if(height % 2 == 1 && width % 2 == 1){ layouts.Add(PillarArrangement.Single); } if((height % 2 == 1 || width % 2 == 1) && height != 4 && width != 4){ layouts.Add(PillarArrangement.Row); } if(height >= 5 && width >= 5){ layouts.Add(PillarArrangement.Corners); } if(height > 2 && width > 2 && height != 4 && width != 4){ layouts.Add(PillarArrangement.Full); } if((width % 2 == 1 && width >= 5) || (height % 2 == 1 && height >= 5)){ layouts.Add(PillarArrangement.StatueEdges); } if(layouts.Count == 0 || CoinFlip()){ //otherwise they're too common layouts.Add(PillarArrangement.StatueCorners); } if(layouts.Count > 0){ CellType pillar = CellType.Pillar; switch(layouts.Random()){ case PillarArrangement.Single: map[(start_r + end_r)/2,(start_c + end_c)/2] = pillar; break; case PillarArrangement.Row: { bool vertical; if(width % 2 == 1 && height % 2 == 0){ vertical = true; } else{ if(height % 2 == 1 && width % 2 == 0){ vertical = false; } else{ vertical = CoinFlip(); } } if(vertical){ if(height % 2 == 1){ for(int i=start_r+1;i<=end_r-1;i+=2){ map[i,(start_c + end_c)/2] = pillar; } } else{ int offset = 0; if(height % 4 == 0){ offset = Roll(2) - 1; } for(int i=start_r+1+offset;i<(start_r + end_r)/2;i+=2){ map[i,(start_c + end_c)/2] = pillar; } for(int i=end_r-1-offset;i>(start_r + end_r)/2+1;i-=2){ map[i,(start_c + end_c)/2] = pillar; } } } else{ if(width % 2 == 1){ for(int i=start_c+1;i<=end_c-1;i+=2){ map[(start_r + end_r)/2,i] = pillar; } } else{ int offset = 0; if(width % 4 == 0){ offset = Roll(2) - 1; } for(int i=start_c+1+offset;i<(start_c + end_c)/2;i+=2){ map[(start_r + end_r)/2,i] = pillar; } for(int i=end_c-1-offset;i>(start_c + end_c)/2+1;i-=2){ map[(start_r + end_r)/2,i] = pillar; } } } break; } case PillarArrangement.Corners: { int v_offset = 0; int h_offset = 0; if(height % 4 == 0){ v_offset = Roll(2) - 1; } if(width % 4 == 0){ h_offset = Roll(2) - 1; } map[start_r + 1 + v_offset,start_c + 1 + h_offset] = pillar; map[start_r + 1 + v_offset,end_c - 1 - h_offset] = pillar; map[end_r - 1 - v_offset,start_c + 1 + h_offset] = pillar; map[end_r - 1 - v_offset,end_c - 1 - h_offset] = pillar; break; } case PillarArrangement.Full: { int v_offset = 0; int h_offset = 0; if(height % 4 == 0){ v_offset = Roll(2) - 1; } if(width % 4 == 0){ h_offset = Roll(2) - 1; } int half_r = (start_r + end_r)/2; int half_c = (start_c + end_c)/2; int half_r_offset = (start_r + end_r + 1)/2; int half_c_offset = (start_c + end_c + 1)/2; for(int i=start_r+1+v_offset;i<half_r;i+=2){ for(int j=start_c+1+h_offset;j<half_c;j+=2){ map[i,j] = pillar; } } for(int i=start_r+1+v_offset;i<half_r;i+=2){ for(int j=end_c-1-h_offset;j>half_c_offset;j-=2){ map[i,j] = pillar; } } for(int i=end_r-1-v_offset;i>half_r_offset;i-=2){ for(int j=start_c+1+h_offset;j<half_c;j+=2){ map[i,j] = pillar; } } for(int i=end_r-1-v_offset;i>half_r_offset;i-=2){ for(int j=end_c-1-h_offset;j>half_c_offset;j-=2){ map[i,j] = pillar; } } if((width+1) % 4 == 0){ if(height % 2 == 1){ for(int i=start_r+1;i<=end_r-1;i+=2){ map[i,half_c] = pillar; } } else{ int offset = 0; if(height % 4 == 0){ offset = Roll(2) - 1; } for(int i=start_r+1+offset;i<half_r;i+=2){ map[i,half_c] = pillar; } for(int i=end_r-1-offset;i>half_r_offset;i-=2){ map[i,half_c] = pillar; } } } if((height+1) % 4 == 0){ if(width % 2 == 1){ for(int i=start_c+1;i<=end_c-1;i+=2){ map[half_r,i] = pillar; } } else{ int offset = 0; if(width % 4 == 0){ offset = Roll(2) - 1; } for(int i=start_c+1+offset;i<half_c;i+=2){ map[half_r,i] = pillar; } for(int i=end_c-1-offset;i>half_c_offset;i-=2){ map[half_r,i] = pillar; } } } break; } case PillarArrangement.StatueCorners: map[start_r,start_c] = CellType.Statue; map[start_r,end_c] = CellType.Statue; map[end_r,start_c] = CellType.Statue; map[end_r,end_c] = CellType.Statue; break; case PillarArrangement.StatueEdges: { map[start_r,start_c] = CellType.Statue; map[start_r,end_c] = CellType.Statue; map[end_r,start_c] = CellType.Statue; map[end_r,end_c] = CellType.Statue; if(width % 2 == 1 && width > 3){ int half_c = (start_c + end_c)/2; int corridors = new pos(start_r,half_c).CardinalAdjacentPositions().Where(x => BoundsCheck(x) && map[x].IsCorridorType()).Count; if(corridors == 0){ map[start_r,half_c] = CellType.Statue; } corridors = new pos(end_r,half_c).CardinalAdjacentPositions().Where(x => BoundsCheck(x) && map[x].IsCorridorType()).Count; if(corridors == 0){ map[end_r,half_c] = CellType.Statue; } } if(height % 2 == 1 && height > 3){ int half_r = (start_r + end_r)/2; int corridors = new pos(half_r,start_c).CardinalAdjacentPositions().Where(x => BoundsCheck(x) && map[x].IsCorridorType()).Count; if(corridors == 0){ map[half_r,start_c] = CellType.Statue; } corridors = new pos(half_r,end_c).CardinalAdjacentPositions().Where(x => BoundsCheck(x) && map[x].IsCorridorType()).Count; if(corridors == 0){ map[half_r,end_c] = CellType.Statue; } } break; } default: break; } } } return true; } case 2: { int height = end_r - start_r + 1; int width = end_c - start_c + 1; if(height < 4 || width < 4){ //nothing happens until we get above 4x4 return true; } int rows_to_convert = Roll((height/2)-1); int cols_to_convert = Roll((width/2)-1); if(rows_to_convert == 1 && cols_to_convert == 1){ return true; } List<pos> blocked = new List<pos>(); for(int i=start_r;i<=end_r;++i){ for(int j=start_c;j<=end_c;++j){ if((i < start_r + rows_to_convert || i > end_r - rows_to_convert) && (j < start_c + cols_to_convert || j > end_c - cols_to_convert)){ pos p = new pos(i,j); foreach(pos neighbor in p.CardinalAdjacentPositions()){ if(map[neighbor].IsCorridorType()){ blocked.Add(p); } } map[i,j] = CellType.Wall; } } } blocked.Randomize(); foreach(pos p in blocked){ bool done = false; foreach(pos neighbor in p.CardinalAdjacentPositions()){ if(map[neighbor].IsRoomType()){ map[p] = CellType.RoomInterior; done = true; break; } } if(!done){ List<int> valid_dirs = new List<int>(); foreach(int dir in U.FourDirections){ pos next = p.PosInDir(dir); while(next.row >= start_r && next.row <= end_r && next.col >= start_c && next.col <= end_c){ if(next.CardinalAdjacentPositions().Any(x=>map[x].IsRoomType())){ valid_dirs.Add(dir); break; } next = next.PosInDir(dir); } } int valid_dir = valid_dirs.RandomOrDefault(); pos next2 = p.PosInDir(valid_dir); List<pos> new_corridor = new List<pos>{p}; while(true){ new_corridor.Add(next2); if(next2.CardinalAdjacentPositions().Any(x=>map[x].IsRoomType())){ break; } next2 = next2.PosInDir(valid_dir); } foreach(pos p2 in new_corridor){ map[p2] = CellType.RoomInterior; } } } return true; } case 3: { List<pos> list = map.PositionsWhere(x=>x.row >= start_r && x.row <= end_r && x.col >= start_c && x.col <= end_c); PosArray<CellType> old_map = new PosArray<CellType>(H,W); foreach(pos p in list){ old_map[p] = map[p]; map[p] = CellType.Wall; } PosArray<bool> rock = new PosArray<bool>(H,W); for(int i=0;i<H;++i){ for(int j=0;j<W;++j){ pos p = new pos(i,j); rock[p] = true; if(BoundsCheck(i,j,false)){ foreach(pos neighbor in p.AdjacentPositionsClockwise()){ if(map[neighbor] != CellType.Wall){ rock[p] = false; break; } } } } } foreach(pos p in list){ map[p] = CellType.RoomInterior; //todo: might this step be extraneous? } List<pos> frontier = new List<pos>(); { PosArray<bool> in_list = new PosArray<bool>(H,W); foreach(pos p in list){ in_list[p] = true; } for(int i=0;i<H;++i){ for(int j=0;j<W;++j){ pos p = new pos(i,j); if(in_list[p]){ foreach(pos neighbor in p.PositionsAtDistance(1,in_list)){ if(!in_list[neighbor]){ frontier.Add(p); break; } } } } } } int fail_counter = 0; int num_added = 0; bool finished = false; while(!finished){ if(frontier.Count == 0 || num_added >= 30){ //todo check this value finished = true; break; } pos f = frontier.RemoveRandom(); foreach(pos neighbor in f.CardinalAdjacentPositions()){ if(!BoundsCheck(neighbor,false) || !rock[neighbor.row,neighbor.col]){ ++fail_counter; //this might now be unreachable if(!BoundsCheck(neighbor,false)){ fail_counter += 25; //fail quicker when against the edge of the map to prevent ugliness } //however, this doesn't actually fail as quickly as it should - i've overlooked something. if(fail_counter >= 50){ finished = true; break; } } else{ if(map[neighbor] != CellType.RoomInterior){ map[neighbor] = CellType.RoomInterior; ++num_added; bool add_neighbor = true; foreach(pos n2 in neighbor.CardinalAdjacentPositions()){ if(!BoundsCheck(n2,false) || !rock[n2.row,n2.col]){ add_neighbor = false; ++fail_counter; //this might now be unreachable if(!BoundsCheck(neighbor,false)){ fail_counter += 25; //fail quicker when against the edge of the map to prevent ugliness } //however, this doesn't actually fail as quickly as it should - i've overlooked something. if(fail_counter >= 50){ finished = true; } break; } } if(finished){ break; } if(add_neighbor){ frontier.Add(neighbor); } } } } } foreach(pos p in list){ map[p] = old_map[p]; } return true; } case 4: { List<pos> list = map.PositionsWhere(x=>x.row >= start_r && x.row <= end_r && x.col >= start_c && x.col <= end_c); Dungeon room = new Dungeon((end_r - start_r) + 3,(end_c - start_c) + 3); //includes borders List<pos> map_exits = list.Where(x=>x.CardinalAdjacentPositions().Any(y=>map[y].IsCorridorType())); //grab the positions from list that have any adjacent corridor-type cells if(map_exits.Count < 2){ return true; } List<pos> room_exits = new List<pos>(); foreach(pos exit in map_exits){ room_exits.Add(new pos(exit.row-start_r+1,exit.col-start_c+1)); } int tries = 0; while(true){ room.FillWithRandomWalls(25); room.ApplyCellularAutomataXYRule(3); room.ConnectDiagonals(); room.RemoveDeadEndCorridors(); room.RemoveUnconnectedAreas(); bool exits_open = true; foreach(pos p in room_exits){ if(!room[p].IsPassable()){ exits_open = false; } } if(exits_open){ for(int i=start_r;i<=end_r;++i){ for(int j=start_c;j<=end_c;++j){ if(list.Contains(new pos(i,j))){ map[i,j] = room[(i-start_r)+1,(j-start_c)+1]; } } } break; } ++tries; if(tries > 50){ return false; } } return true; } default: break; } return true; }); }
void ChooseMonsterSpawn() { startingLocation = pointList.RemoveRandom(); }
public void IncreaseSkill(SkillType skill) { List<string> learned = new List<string>(); skills[skill]++; bool active_feat_learned = false; B.Add("You feel a rush of power. "); B.PrintAll(); ConsoleKeyInfo command; bool gain_feat = false; if(!M.feat_gained_this_level){ if(Feat.NumberOfFeats(skill,feats_in_order) < 4){ List<List<SkillType>> skill_groups = new List<List<SkillType>>{new List<SkillType>{tile().type.GetAssociatedSkill()}}; for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(M.tile[i,j].IsShrine() && M.tile[i,j].type != TileType.SPELL_EXCHANGE_SHRINE && this.DistanceFrom(i,j) > 2){ foreach(var list in skill_groups){ foreach(SkillType s in list){ if(s == M.tile[i,j].type.GetAssociatedSkill()){ goto NoAdd; } } } List<SkillType> newlist = new List<SkillType>(); foreach(Tile t in M.tile[i,j].TilesWithinDistance(2)){ if(t.IsShrine() && t.type != TileType.SPELL_EXCHANGE_SHRINE){ SkillType newskill = t.type.GetAssociatedSkill(); if(Feat.NumberOfFeats(newskill,feats_in_order) >= 4){ goto NoAdd; } newlist.Add(newskill); } } skill_groups.Add(newlist); NoAdd: continue; } } } if(M.nextLevelShrines?.Count > 0){ List<SchismDungeonGenerator.CellType> nextShrines = new List<SchismDungeonGenerator.CellType>(M.nextLevelShrines); while(nextShrines.Count > 0){ List<SkillType> newlist = new List<SkillType>(); newlist.Add(nextShrines.RemoveFirst().GetAssociatedSkill()); if(nextShrines.Count > 0){ newlist.Add(nextShrines.RemoveFirst().GetAssociatedSkill()); } foreach(SkillType newskill in newlist){ if(Feat.NumberOfFeats(newskill,feats_in_order) >= 4){ goto NoAdd; } } skill_groups.Add(newlist); NoAdd: continue; } } if(skill_groups.Random().Contains(skill)){ gain_feat = true; } } } if(gain_feat){ M.feat_gained_this_level = true; FeatType feat_chosen = FeatType.NO_FEAT; bool done = false; MouseUI.PushButtonMap(); for(int i=0;i<4;++i){ MouseUI.CreateMapButton((ConsoleKey)(ConsoleKey.A + i),false,1 + i*5,5); } MouseUI.CreateButton(ConsoleKey.Oem2,true,Global.MAP_OFFSET_ROWS + ROWS-1,Global.MAP_OFFSET_COLS + 32,1,12); while(!done){ Screen.ResetColors(); Screen.WriteMapString(0,0,"".PadRight(COLS,'-')); for(int i=0;i<4;++i){ FeatType ft = Feat.OfSkill(skill,i); Color featcolor = (feat_chosen == ft)? Color.Green : Color.Gray; Color lettercolor = Color.Cyan; if(HasFeat(ft)){ featcolor = Color.Magenta; lettercolor = Color.DarkRed; } Screen.WriteMapString(1+i*5,0,("["+(char)(i+97)+"] ")); Screen.WriteMapChar(1+i*5,1,(char)(i+97),lettercolor); Screen.WriteMapString(1+i*5,4,Feat.Name(ft).PadRight(30),featcolor); if(Feat.IsActivated(ft)){ Screen.WriteMapString(1+i*5,30," Active".PadToMapSize(),featcolor); } else{ Screen.WriteMapString(1+i*5,30," Passive".PadToMapSize(),featcolor); } List<string> desc = Feat.Description(ft); for(int j=0;j<4;++j){ if(desc.Count > j){ Screen.WriteMapString(2+j+i*5,0," " + desc[j].PadRight(64),featcolor); } else{ Screen.WriteMapString(2+j+i*5,0,"".PadRight(66)); } } } if(feat_chosen != FeatType.NO_FEAT){ Screen.WriteMapString(21,0,"--Type [a-d] to choose a feat---[?] for help---[Enter] to accept--"); Screen.WriteMapChar(21,8,new colorchar(Color.Cyan,'a')); Screen.WriteMapChar(21,10,new colorchar(Color.Cyan,'d')); Screen.WriteMapChar(21,33,new colorchar(Color.Cyan,'?')); Screen.WriteMapString(21,48,new cstr(Color.Magenta,"Enter")); MouseUI.CreateButton(ConsoleKey.Enter,false,Global.MAP_OFFSET_ROWS + ROWS-1,Global.MAP_OFFSET_COLS + 47,1,17); } else{ Screen.WriteMapString(21,0,"--Type [a-d] to choose a feat---[?] for help----------------------"); Screen.WriteMapChar(21,8,new colorchar(Color.Cyan,'a')); Screen.WriteMapChar(21,10,new colorchar(Color.Cyan,'d')); Screen.WriteMapChar(21,33,new colorchar(Color.Cyan,'?')); MouseUI.RemoveButton(Global.MAP_OFFSET_ROWS + ROWS-1,Global.MAP_OFFSET_COLS + 47); } B.DisplayNow("Your " + Skill.Name(skill) + " skill increases to " + skills[skill] + ". Choose a feat: "); if(!Help.displayed[TutorialTopic.Feats]){ Help.TutorialTip(TutorialTopic.Feats,true); B.DisplayNow("Your " + Skill.Name(skill) + " skill increases to " + skills[skill] + ". Choose a feat: "); } Screen.CursorVisible = true; command = Input.ReadKey(); Screen.CursorVisible = false; char ch = command.GetCommandChar(); switch(ch){ case 'a': case 'b': case 'c': case 'd': { FeatType ft = Feat.OfSkill(skill,(int)(ch-97)); int i = (int)(ch - 'a'); if(feat_chosen == ft){ feat_chosen = FeatType.NO_FEAT; MouseUI.RemoveButton(Global.MAP_OFFSET_ROWS + 1 + i*5,60); MouseUI.CreateMapButton((ConsoleKey)(ConsoleKey.A + i),false,1 + i*5,5); } else{ if(!HasFeat(ft)){ if(feat_chosen != FeatType.NO_FEAT){ int num = (int)feat_chosen % 4; MouseUI.RemoveButton(Global.MAP_OFFSET_ROWS + 1 + num*5,60); MouseUI.CreateMapButton((ConsoleKey)(ConsoleKey.A + num),false,1 + num*5,5); } feat_chosen = ft; MouseUI.RemoveButton(Global.MAP_OFFSET_ROWS + 1 + i*5,60); MouseUI.CreateMapButton(ConsoleKey.Enter,false,1 + i*5,5); } } break; } case '?': Help.DisplayHelp(HelpTopic.Feats); UI.DisplayStats(); break; case (char)13: if(feat_chosen != FeatType.NO_FEAT){ done = true; } break; default: break; } } feats[feat_chosen] = true; feats_in_order.Add(feat_chosen); learned.Add("You master the " + Feat.Name(feat_chosen) + " feat. "); if(Feat.IsActivated(feat_chosen)){ active_feat_learned = true; } MouseUI.PopButtonMap(); } else{ learned.Add("Your " + Skill.Name(skill) + " skill increases to " + skills[skill] + ". "); } if(skill == SkillType.MAGIC){ maxmp += 5; curmp += 5; List<SpellType> unknown = new List<SpellType>(); List<colorstring> unknownstr = new List<colorstring>(); List<SpellType> random_spell_list = new List<SpellType>(); foreach(SpellType spell in Enum.GetValues(typeof(SpellType))){ random_spell_list.Add(spell); } while(unknown.Count < 5 && random_spell_list.Count > 0){ SpellType spell = random_spell_list.RemoveRandom(); if(!HasSpell(spell) && spell != SpellType.NO_SPELL && spell != SpellType.NUM_SPELLS){ unknown.Add(spell); } } unknown.Sort((sp1,sp2)=>Spell.Tier(sp1).CompareTo(Spell.Tier(sp2))); foreach(SpellType spell in unknown){ colorstring cs = new colorstring(); cs.strings.Add(new cstr(Spell.Name(spell).PadRight(17) + Spell.Tier(spell).ToString().PadLeft(3),Color.Gray)); cs.strings.Add(new cstr("".PadRight(5),Color.Gray)); unknownstr.Add(cs + Spell.Description(spell)); } M.Draw(); /*for(int i=unknown.Count+2;i<ROWS;++i){ Screen.WriteMapString(i,0,"".PadRight(COLS)); }*/ Help.TutorialTip(TutorialTopic.SpellTiers); Screen.WriteMapString(unknown.Count+2,0,"".PadRight(COLS)); colorstring topborder = new colorstring("---------------------Tier-----------------Description-------------",Color.Gray); int selection = Select("Learn which spell? ",topborder,new colorstring("".PadRight(25,'-') + "[",Color.Gray,"?",Color.Cyan,"] for help".PadRight(COLS,'-'),Color.Gray),unknownstr,false,true,false,true,HelpTopic.Spells); spells[unknown[selection]] = true; learned.Add("You learn " + Spell.Name(unknown[selection]) + ". "); spells_in_order.Add(unknown[selection]); } if(learned.Count > 0){ foreach(string s in learned){ B.Add(s); } } if(active_feat_learned){ M.Draw(); Help.TutorialTip(TutorialTopic.ActiveFeats); } }
public void GenerateFeatures(PosArray<CellType> map,List<pos> interesting_tiles) { List<DungeonFeature> features = new List<DungeonFeature>(); foreach(DungeonFeature df in Enum.GetValues(typeof(DungeonFeature))){ features.Add(df); } int[] rarity = null; switch(level_types[current_level-1]){ case LevelType.Standard: rarity = new int[]{30,40,15,30, 25,6,8,15,15,3,3,4,4,4}; break; case LevelType.Cave: rarity = new int[]{30,15,10,15, 15,100,8,10,30,5,25,6,3,4}; break; case LevelType.Mine: rarity = new int[]{30,20,5,16, 18,6,7,15,10,3,5,30,1,1}; break; case LevelType.Hive: rarity = new int[]{30,15,100,50, 50,100,4,10,10,100,100,15,25,0}; break; case LevelType.Fortress: rarity = new int[]{30,100,100,100, 100,1,8,25,8,6,1,100,20,15}; break; case LevelType.Garden: rarity = new int[]{20,50,50,0, 20,30,20,20,20,20,5,5,8,15}; break; case LevelType.Crypt: rarity = new int[]{30,50,50,25, 25,30,8,10,15,20,30,5,8,8}; break; case LevelType.Slime: //todo default: rarity = new int[]{30,20,15,12, 10,4,8,10,7,3,3,3,4,10}; break; } /*int[] rarity = new int[]{30,20,15,12, 10,4,8,10,7,3,3,3,4}; int[] frequency = new int[]{1,1,2,2,3,3,3, 4,4,4,4,2,2,5,5,5,6,5,5,8};*/ int[] removal_chance = new int[]{95,20,10,60, 30,25,70,50,60,35,12,10,10,20}; /*List<DungeonFeature> feature_pool = new List<DungeonFeature>(); for(int i=0;i<20;++i){ for(int j=frequency[i];j>0;--j){ feature_pool.Add(features[i]); } }*/ List<DungeonFeature> feature_pool = new List<DungeonFeature>(); while(feature_pool.Count < 3){ feature_pool.Clear(); for(int i=0;i<14;++i){ if(rarity[i] > 0 && R.OneIn(rarity[i])){ feature_pool.Add(features[i]); } } } List<DungeonFeature> selected_features = new List<DungeonFeature>(); for(int i=0;i<5 && feature_pool.Count > 0;++i){ selected_features.Add(feature_pool.RemoveRandom()); } List<DungeonFeature> result = new List<DungeonFeature>(); for(int count=5;count>0 && selected_features.Count > 0;--count){ DungeonFeature df = selected_features.Random(); if(R.PercentChance(removal_chance[(int)df])){ selected_features.Remove(df); } result.Add(df); } List<pos> thin_walls = null; if(result.Contains(DungeonFeature.CRACKED_WALL)){ thin_walls = map.AllPositions().Where(x=>map[x].IsWall() && x.HasOppositePairWhere(true,y=>y.BoundsCheck(tile) && map[y].IsFloor())); } while(result.Count > 0){ DungeonFeature df = result.RemoveRandom(); switch(df){ case DungeonFeature.POOL_OF_RESTORATION: case DungeonFeature.FIRE_PIT: { for(int i=0;i<50;++i){ int rr = R.Roll(ROWS-4)+1; int rc = R.Roll(COLS-4)+1; if(interesting_tiles.Count > 0){ pos p = interesting_tiles.RemoveRandom(); rr = p.row; rc = p.col; map[p] = CellType.RoomInterior; } if(map[rr,rc].IsFloor()){ bool floors = true; foreach(pos p in new pos(rr,rc).PositionsAtDistance(1,map)){ if(!map[p].IsFloor()){ floors = false; break; } } if(floors){ if(df == DungeonFeature.POOL_OF_RESTORATION){ map[rr,rc] = CellType.Pool; } if(df == DungeonFeature.FIRE_PIT){ map[rr,rc] = CellType.FirePit; } break; } } } break; } case DungeonFeature.BARREL: case DungeonFeature.TORCH: for(int i=0;i<50;++i){ int rr = R.Roll(ROWS-2); int rc = R.Roll(COLS-2); if(map[rr,rc].IsRoomType() && map[rr,rc].IsFloor()){ if(df == DungeonFeature.BARREL){ map[rr,rc] = CellType.Barrel; } if(df == DungeonFeature.TORCH){ map[rr,rc] = CellType.Torch; } break; } } break; case DungeonFeature.WEBS: case DungeonFeature.RUBBLE: { for(int i=0;i<50;++i){ int rr = R.Roll(ROWS-2); int rc = R.Roll(COLS-2); if(map[rr,rc].IsRoomType()){ CellType cell = CellType.Webs; int max_radius = 2; switch(df){ case DungeonFeature.WEBS: cell = CellType.Webs; max_radius = 3; break; case DungeonFeature.RUBBLE: cell = CellType.Rubble; max_radius = 2; break; } map[rr,rc] = cell; for(int j=1;j<=max_radius;++j){ List<pos> added = new List<pos>(); foreach(pos p in new pos(rr,rc).PositionsWithinDistance(j,map)){ if(map[p] == cell){ foreach(pos neighbor in p.CardinalAdjacentPositions()){ if(map[neighbor].IsFloor() && R.CoinFlip()){ added.AddUnique(neighbor); } } } } foreach(pos p in added){ /*if(df == DungeonFeature.RUBBLE){ foreach(pos neighbor in p.CardinalAdjacentPositions()){ if(!added.Contains(neighbor) && map[neighbor].IsFloor() && R.OneIn(3)){ map[neighbor] = CellType.Gravel; } } }*/ map[p] = cell; } } break; } } break; } case DungeonFeature.SLIME: case DungeonFeature.OIL: { for(int i=0;i<50;++i){ int rr = R.Roll(ROWS-2); int rc = R.Roll(COLS-2); if(map[rr,rc].IsFloor()){ CellType cell = CellType.Wall; int max_radius = 2; switch(df){ case DungeonFeature.SLIME: cell = CellType.Slime; max_radius = 3; break; case DungeonFeature.OIL: cell = CellType.Oil; max_radius = 3; break; } map[rr,rc] = cell; for(int j=1;j<=max_radius;++j){ List<pos> added = new List<pos>(); foreach(pos p in new pos(rr,rc).PositionsWithinDistance(j,map)){ if(map[p] == cell){ foreach(pos neighbor in p.CardinalAdjacentPositions()){ if(map[neighbor].IsFloor() && R.CoinFlip()){ added.AddUnique(neighbor); } } } } foreach(pos p in added){ map[p] = cell; } } break; } } break; } case DungeonFeature.FIRE_GEYSER: { for(int i=0;i<50;++i){ int rr = R.Roll(ROWS-4)+1; int rc = R.Roll(COLS-4)+1; if(map[rr,rc].IsFloor()){ bool floors = true; foreach(pos p in new pos(rr,rc).PositionsAtDistance(1,map)){ if(!map[p].IsFloor()){ floors = false; break; } } if(floors){ map[rr,rc] = CellType.Geyser; break; } } } break; } case DungeonFeature.VINES: { for(int i=0;i<500;++i){ int rr = R.Roll(ROWS-2); int rc = R.Roll(COLS-2); pos p = new pos(rr,rc); if(map[p].IsRoomType() && p.HasAdjacentWhere(x=>map.BoundsCheck(x) && map[x].IsWall())){ PosArray<bool> vine = map.GetFloodFillArray(p,false,x=>map[x].IsRoomType() && x.HasAdjacentWhere(y=>map.BoundsCheck(y) && map[y].IsWall()) && !R.OneIn(3)); //changed from one in 6 so vines won't fill caves so often rr = R.Roll(ROWS-2); rc = R.Roll(COLS-2); pos p2 = new pos(rr,rc); PosArray<bool> new_vine = new PosArray<bool>(ROWS,COLS); int max = Math.Max(ROWS,COLS); for(int dist=0;dist<max;++dist){ bool found = false; foreach(pos possible_vine in p2.PositionsAtDistance(dist)){ if(possible_vine.BoundsCheck(new_vine,false)){ found = true; if(vine[possible_vine] && possible_vine.PositionsAtDistance(1,new_vine).Where(x=>new_vine[x] || map[x] == CellType.Vine).Count < 3){ new_vine[possible_vine] = true; } } } if(!found){ break; } } List<pos> added = new List<pos>(); for(int s=1;s<ROWS-1;++s){ for(int t=1;t<COLS-1;++t){ if(new_vine[s,t]){ pos neighbor = new pos(s,t); foreach(int dir in U.FourDirections){ if(R.OneIn(6) && map[neighbor.PosInDir(dir)].IsFloor()){ added.AddUnique(neighbor.PosInDir(dir)); } } } } } foreach(pos neighbor in added){ new_vine[neighbor] = true; } for(int s=1;s<ROWS-1;++s){ for(int t=1;t<COLS-1;++t){ if(new_vine[s,t]){ if(R.OneIn(35)){ map[s,t] = CellType.PoisonBulb; } else{ map[s,t] = CellType.Vine; } } } } break; } } break; } case DungeonFeature.BLAST_FUNGUS: case DungeonFeature.FOG_VENT: case DungeonFeature.POISON_VENT: for(int i=0;i<50;++i){ int rr = R.Roll(ROWS-2); int rc = R.Roll(COLS-2); if(map[rr,rc].IsFloor()){ if(df == DungeonFeature.BLAST_FUNGUS){ map[rr,rc] = CellType.BlastFungus; } if(df == DungeonFeature.FOG_VENT){ map[rr,rc] = CellType.FogVent; } if(df == DungeonFeature.POISON_VENT){ map[rr,rc] = CellType.PoisonVent; } break; } } break; case DungeonFeature.CRACKED_WALL: for(int i=R.Between(2,4);i>0;--i){ if(thin_walls.Count > 0){ map[thin_walls.RemoveRandom()] = CellType.CrackedWall; } } break; } } }
public bool Use(Actor user,List<Tile> line) { bool used = true; bool IDed = true; switch(type){ case ConsumableType.HEALING: user.curhp = user.maxhp; B.Add(user.Your() + " wounds are healed completely. ",user); break; case ConsumableType.REGENERATION: { if(user == player){ B.Add("Your blood tingles. "); } else{ B.Add(user.the_name + " looks energized. ",user); } user.attrs[AttrType.REGENERATING]++; int duration = 100; Q.Add(new Event(user,duration*100,AttrType.REGENERATING)); break; } case ConsumableType.STONEFORM: { B.Add(user.You("transform") + " into a being of animated stone. ",user); int duration = R.Roll(2,20) + 20; List<AttrType> attributes = new List<AttrType>{AttrType.REGENERATING,AttrType.BRUTISH_STRENGTH,AttrType.VIGOR,AttrType.SILENCE_AURA,AttrType.SHADOW_CLOAK,AttrType.CAN_DODGE,AttrType.MENTAL_IMMUNITY,AttrType.DETECTING_MONSTERS,AttrType.MYSTIC_MIND}; foreach(AttrType at in attributes){ //in the rare case where a monster drinks this potion, it can lose these natural statuses permanently. this might eventually be fixed. if(user.HasAttr(at)){ user.attrs[at] = 0; Q.KillEvents(user,at); switch(at){ case AttrType.REGENERATING: B.Add(user.You("no longer regenerate") + ". ",user); break; case AttrType.BRUTISH_STRENGTH: B.Add(user.Your() + " brutish strength fades. ",user); break; case AttrType.VIGOR: B.Add(user.Your() + " extraordinary speed fades. ",user); break; case AttrType.SILENCED: B.Add(user.You("no longer radiate") + " an aura of silence. ",user); break; case AttrType.SHADOW_CLOAK: B.Add(user.YouAre() + " no longer cloaked. ",user); break; case AttrType.MYSTIC_MIND: B.Add(user.Your() + " consciousness returns to normal. ",user); break; } } } if(user.HasAttr(AttrType.PSEUDO_VAMPIRIC)){ user.attrs[AttrType.LIGHT_SENSITIVE] = 0; user.attrs[AttrType.FLYING] = 0; user.attrs[AttrType.PSEUDO_VAMPIRIC] = 0; Q.KillEvents(user,AttrType.LIGHT_SENSITIVE); Q.KillEvents(user,AttrType.FLYING); Q.KillEvents(user,AttrType.PSEUDO_VAMPIRIC); B.Add(user.YouAre() + " no longer vampiric. ",user); } if(user.HasAttr(AttrType.ROOTS)){ foreach(Event e in Q.list){ if(e.target == user && !e.dead){ if(e.attr == AttrType.IMMOBILE && e.msg.Contains("rooted to the ground")){ e.dead = true; user.attrs[AttrType.IMMOBILE]--; B.Add(user.YouAre() + " no longer rooted to the ground. ",user); } else{ if(e.attr == AttrType.BONUS_DEFENSE && e.value == 10){ e.dead = true; //this would break if there were other timed effects that gave the same amount of defense user.attrs[AttrType.BONUS_DEFENSE] -= 10; } else{ if(e.attr == AttrType.ROOTS){ e.dead = true; user.attrs[AttrType.ROOTS]--; } } } } } } if(user.HasAttr(AttrType.BURNING)){ user.RefreshDuration(AttrType.BURNING,0); } user.attrs[AttrType.IMMUNE_BURNING]++; Q.Add(new Event(user,duration*100,AttrType.IMMUNE_BURNING)); user.attrs[AttrType.DAMAGE_RESISTANCE]++; Q.Add(new Event(user,duration*100,AttrType.DAMAGE_RESISTANCE)); user.attrs[AttrType.NONLIVING]++; Q.Add(new Event(user,duration*100,AttrType.NONLIVING)); user.RefreshDuration(AttrType.STONEFORM,duration*100,user.Your() + " rocky form reverts to flesh. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.Stoneform); } break; } case ConsumableType.VAMPIRISM: { B.Add(user.You("become") + " vampiric. ",user); B.Add(user.You("rise") + " into the air. ",user); int duration = R.Roll(2,20) + 20; user.RefreshDuration(AttrType.LIGHT_SENSITIVE,duration*100); user.RefreshDuration(AttrType.FLYING,duration*100); user.attrs[AttrType.DESCENDING] = 0; user.RefreshDuration(AttrType.PSEUDO_VAMPIRIC,duration*100,user.YouAre() + " no longer vampiric. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.Vampirism); } break; } case ConsumableType.BRUTISH_STRENGTH: { if(user == player){ B.Add("You feel a surge of strength. "); } else{ B.Add(user.Your() + " muscles ripple. ",user); } user.RefreshDuration(AttrType.BRUTISH_STRENGTH,(R.Roll(3,6)+16)*100,user.Your() + " incredible strength wears off. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.BrutishStrength); } break; } case ConsumableType.ROOTS: { if(user.HasAttr(AttrType.ROOTS)){ foreach(Event e in Q.list){ if(e.target == user && !e.dead){ if(e.attr == AttrType.IMMOBILE && e.msg.Contains("rooted to the ground")){ e.dead = true; user.attrs[AttrType.IMMOBILE]--; } else{ if(e.attr == AttrType.BONUS_DEFENSE && e.value == 10){ e.dead = true; //this would break if there were other timed effects that gave 5 defense user.attrs[AttrType.BONUS_DEFENSE] -= 10; } else{ if(e.attr == AttrType.ROOTS){ e.dead = true; user.attrs[AttrType.ROOTS]--; } } } } } B.Add(user.Your() + " roots extend deeper into the ground. ",user); } else{ B.Add(user.You("grow") + " roots and a hard shell of bark. ",user); } int duration = R.Roll(20) + 20; user.RefreshDuration(AttrType.ROOTS,duration*100); user.attrs[AttrType.BONUS_DEFENSE] += 10; Q.Add(new Event(user,duration*100,AttrType.BONUS_DEFENSE,10)); user.attrs[AttrType.IMMOBILE]++; Q.Add(new Event(user,duration*100,AttrType.IMMOBILE,user.YouAre() + " no longer rooted to the ground. ",user)); if(user == player){ Help.TutorialTip(TutorialTopic.Roots); } if(user.HasAttr(AttrType.FLYING) && user.tile().IsTrap()){ user.tile().TriggerTrap(); } break; } case ConsumableType.HASTE: { B.Add(user.You("start") + " moving with extraordinary speed. ",user); int duration = (R.Roll(2,10) + 10) * 100; user.RefreshDuration(AttrType.CAN_DODGE,duration); //todo: dodging tip goes here user.RefreshDuration(AttrType.VIGOR,duration,user.Your() + " extraordinary speed fades. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.IncreasedSpeed); } break; } case ConsumableType.SILENCE: { B.Add("A hush falls around " + user.the_name + ". ",user); user.RefreshDuration(AttrType.SILENCE_AURA,(R.Roll(2,20)+20)*100,user.You("no longer radiate") + " an aura of silence. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.Silenced); } break; } case ConsumableType.CLOAKING: if(user.tile().IsLit()){ if(user == player){ B.Add("You would feel at home in the shadows. "); } else{ B.Add("A shadow moves across " + user.the_name + ". ",user); } } else{ B.Add(user.You("fade") + " away in the darkness. ",user); } user.RefreshDuration(AttrType.SHADOW_CLOAK,(R.Roll(2,20)+30)*100,user.YouAre() + " no longer cloaked. ",user); break; case ConsumableType.MYSTIC_MIND: { B.Add(user.Your() + " mind expands. ",user); int duration = R.Roll(2,20)+60; user.attrs[AttrType.ASLEEP] = 0; //user.RefreshDuration(AttrType.MAGICAL_DROWSINESS,0); user.RefreshDuration(AttrType.CONFUSED,0); user.RefreshDuration(AttrType.STUNNED,0); user.RefreshDuration(AttrType.ENRAGED,0); user.RefreshDuration(AttrType.MENTAL_IMMUNITY,duration*100); user.RefreshDuration(AttrType.DETECTING_MONSTERS,duration*100); user.RefreshDuration(AttrType.MYSTIC_MIND,duration*100,user.Your() + " consciousness returns to normal. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.MysticMind); } break; } case ConsumableType.BLINKING: { List<Tile> tiles = user.TilesWithinDistance(8).Where(x => x.passable && x.actor() == null && user.ApproximateEuclideanDistanceFromX10(x) >= 45); if(tiles.Count > 0 && !user.HasAttr(AttrType.IMMOBILE)){ Tile t = tiles.Random(); B.Add(user.You("step") + " through a rip in reality. ",M.tile[user.p],t); user.AnimateStorm(2,3,4,'*',Color.DarkMagenta); user.Move(t.row,t.col); M.Draw(); user.AnimateStorm(2,3,4,'*',Color.DarkMagenta); } else{ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.PASSAGE: { if(user.HasAttr(AttrType.IMMOBILE)){ B.Add("Nothing happens. ",user); IDed = false; break; } List<int> valid_dirs = new List<int>(); foreach(int dir in U.FourDirections){ Tile t = user.TileInDirection(dir); if(t != null && t.Is(TileType.WALL,TileType.CRACKED_WALL,TileType.WAX_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR,TileType.STONE_SLAB)){ while(!t.passable){ if(t.row == 0 || t.row == Global.ROWS-1 || t.col == 0 || t.col == Global.COLS-1){ break; } t = t.TileInDirection(dir); } if(t.passable){ valid_dirs.Add(dir); } } } if(valid_dirs.Count > 0){ int dir = valid_dirs.Random(); Tile t = user.TileInDirection(dir); colorchar ch = new colorchar(Color.Cyan,'!'); switch(user.DirectionOf(t)){ case 8: case 2: ch.c = '|'; break; case 4: case 6: ch.c = '-'; break; } List<Tile> tiles = new List<Tile>(); List<colorchar> memlist = new List<colorchar>(); Screen.CursorVisible = false; Tile last_wall = null; while(!t.passable){ tiles.Add(t); memlist.Add(Screen.MapChar(t.row,t.col)); Screen.WriteMapChar(t.row,t.col,ch); Game.GLUpdate(); Thread.Sleep(35); last_wall = t; t = t.TileInDirection(dir); } Input.FlushInput(); if(t.actor() == null){ int r = user.row; int c = user.col; user.Move(t.row,t.col); Screen.WriteMapChar(r,c,M.VisibleColorChar(r,c)); Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col)); int idx = 0; foreach(Tile tile in tiles){ Screen.WriteMapChar(tile.row,tile.col,memlist[idx++]); Game.GLUpdate(); Thread.Sleep(35); } Input.FlushInput(); B.Add(user.You("travel") + " through the passage. ",user,t); } else{ Tile destination = null; List<Tile> adjacent = t.TilesAtDistance(1).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(last_wall) == 1); if(adjacent.Count > 0){ destination = adjacent.Random(); } else{ foreach(Tile tile in M.ReachableTilesByDistance(t.row,t.col,false)){ if(tile.actor() == null){ destination = tile; break; } } } if(destination != null){ int r = user.row; int c = user.col; user.Move(destination.row,destination.col); Screen.WriteMapChar(r,c,M.VisibleColorChar(r,c)); Screen.WriteMapChar(destination.row,destination.col,M.VisibleColorChar(destination.row,destination.col)); int idx = 0; foreach(Tile tile in tiles){ Screen.WriteMapChar(tile.row,tile.col,memlist[idx++]); Game.GLUpdate(); Thread.Sleep(35); } Input.FlushInput(); B.Add(user.You("travel") + " through the passage. ",user,destination); } else{ B.Add("Something blocks " + user.Your() + " movement through the passage. ",user); } } } else{ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.TIME: if(user == player){ B.Add("Time stops for a moment. ",user); } else{ B.Add("Time warps around " + user.the_name + "! ",user); B.PrintAll(); } if(Fire.fire_event == null){ //this prevents fire from updating while time is frozen Fire.fire_event = new Event(0,EventType.FIRE); Fire.fire_event.tiebreaker = 0; Q.Add(Fire.fire_event); } Q.turn -= 200; break; case ConsumableType.KNOWLEDGE: { if(user == player){ B.Add("Knowledge fills your mind. "); Event hiddencheck = null; foreach(Event e in Q.list){ if(!e.dead && e.type == EventType.CHECK_FOR_HIDDEN){ hiddencheck = e; break; } } int max_dist = 0; List<Tile> last_tiles = new List<Tile>(); foreach(Tile t in M.ReachableTilesByDistance(user.row,user.col,true,TileType.STONE_SLAB,TileType.DOOR_C,TileType.STALAGMITE,TileType.RUBBLE,TileType.HIDDEN_DOOR)){ if(t.type != TileType.FLOOR){ t.seen = true; if(t.type != TileType.WALL){ t.revealed_by_light = true; } if(t.IsTrap() || t.Is(TileType.HIDDEN_DOOR)){ if(hiddencheck != null){ hiddencheck.area.Remove(t); } } if(t.IsTrap()){ t.name = Tile.Prototype(t.type).name; t.a_name = Tile.Prototype(t.type).a_name; t.the_name = Tile.Prototype(t.type).the_name; t.symbol = Tile.Prototype(t.type).symbol; t.color = Tile.Prototype(t.type).color; } if(t.Is(TileType.HIDDEN_DOOR)){ t.Toggle(null); } colorchar ch2 = Screen.BlankChar(); if(t.inv != null){ t.inv.revealed_by_light = true; ch2.c = t.inv.symbol; ch2.color = t.inv.color; M.last_seen[t.row,t.col] = ch2; } else{ if(t.features.Count > 0){ ch2 = t.FeatureVisual(); M.last_seen[t.row,t.col] = ch2; } else{ ch2.c = t.symbol; ch2.color = t.color; if(ch2.c == '#' && ch2.color == Color.RandomGlowingFungus){ ch2.color = Color.Gray; } M.last_seen[t.row,t.col] = ch2; } } Screen.WriteMapChar(t.row,t.col,t.symbol,Color.RandomRainbow); //Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col)); if(user.DistanceFrom(t) > max_dist){ max_dist = user.DistanceFrom(t); Game.GLUpdate(); Thread.Sleep(10); while(last_tiles.Count > 0){ Tile t2 = last_tiles.RemoveRandom(); Screen.WriteMapChar(t2.row,t2.col,M.last_seen[t2.row,t2.col]); //Screen.WriteMapChar(t2.row,t2.col,M.VisibleColorChar(t2.row,t2.col)); } } last_tiles.Add(t); } } if(user.inv.Count > 0){ foreach(Item i in user.inv){ identified[i.type] = true; if(i.NameOfItemType() == "wand"){ i.other_data = -1; } } } } else{ B.Add(user.the_name + " looks more knowledgeable. ",user); } break; } case ConsumableType.SUNLIGHT: if(M.wiz_lite == false){ B.Add("The air itself seems to shine. "); M.wiz_lite = true; M.wiz_dark = false; Q.KillEvents(null,EventType.NORMAL_LIGHTING); Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING)); } else{ B.Add("The air grows even brighter for a moment. "); Q.KillEvents(null,EventType.NORMAL_LIGHTING); Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING)); } break; case ConsumableType.DARKNESS: if(M.wiz_dark == false){ B.Add("The air itself grows dark. "); if(player.light_radius > 0){ B.Add("Your light is extinguished! "); } M.wiz_dark = true; M.wiz_lite = false; Q.KillEvents(null,EventType.NORMAL_LIGHTING); Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING)); } else{ B.Add("The air grows even darker for a moment. "); Q.KillEvents(null,EventType.NORMAL_LIGHTING); Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING)); } break; case ConsumableType.RENEWAL: { B.Add("A glow envelops " + user.the_name + ". ",user); //B.Add("A glow envelops " + user.Your() + " equipment. ",user); bool repaired = false; foreach(EquipmentStatus eqstatus in Enum.GetValues(typeof(EquipmentStatus))){ foreach(Weapon w in user.weapons){ if(w.status[eqstatus]){ repaired = true; w.status[eqstatus] = false; } } foreach(Armor a in user.armors){ if(a.status[eqstatus]){ repaired = true; a.status[eqstatus] = false; } } } if(repaired){ B.Add(user.Your() + " equipment looks as good as new! ",user); } if(user.HasAttr(AttrType.SLIMED)){ B.Add(user.YouAre() + " no longer covered in slime. ",user); user.attrs[AttrType.SLIMED] = 0; } if(user.HasAttr(AttrType.OIL_COVERED)){ B.Add(user.YouAre() + " no longer covered in oil. ",user); user.attrs[AttrType.OIL_COVERED] = 0; } int recharged = 0; foreach(Item i in user.inv){ if(i.NameOfItemType() == "wand"){ i.charges++; recharged++; } } if(recharged > 0){ if(recharged == 1){ B.Add("The glow charges " + user.Your() + " wand. ",user); } else{ B.Add("The glow charges " + user.Your() + " wands. ",user); } } break; } case ConsumableType.CALLING: { bool found = false; if(user == player){ for(int dist = 1;dist < Math.Max(Global.ROWS,Global.COLS);++dist){ List<Tile> tiles = user.TilesAtDistance(dist).Where(x=>x.actor() != null && !x.actor().HasAttr(AttrType.IMMOBILE)); if(tiles.Count > 0){ Actor a = tiles.Random().actor(); Tile t2 = user.TileInDirection(user.DirectionOf(a)); if(t2.passable && t2.actor() == null){ B.Add("The scroll calls " + a.a_name + " to you. "); a.Move(t2.row,t2.col); found = true; break; } foreach(Tile t in M.ReachableTilesByDistance(user.row,user.col,false)){ if(t.actor() == null){ B.Add("The scroll calls " + a.a_name + " to you. "); a.Move(t.row,t.col); found = true; break; } } if(found){ break; } } } } else{ if(!player.HasAttr(AttrType.IMMOBILE) && user.DistanceFrom(player) > 1){ Tile t2 = user.TileInDirection(user.DirectionOf(player)); if(t2.passable && t2.actor() == null){ B.Add("The scroll calls you to " + user.TheName(true) + ". "); player.Move(t2.row,t2.col); found = true; } if(!found){ foreach(Tile t in M.ReachableTilesByDistance(user.row,user.col,false)){ if(t.actor() == null){ B.Add("The scroll calls you to " + user.TheName(true) + ". "); player.Move(t.row,t.col); found = true; break; } } } } } if(!found){ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.TRAP_CLEARING: { List<Tile> traps = new List<Tile>(); { List<Tile>[] traparray = new List<Tile>[5]; for(int i=0;i<5;++i){ traparray[i] = new List<Tile>(); } for(int i=0;i<=12;++i){ foreach(Tile t in user.TilesAtDistance(i)){ //all this ensures that the traps go off in the best order switch(t.type){ case TileType.ALARM_TRAP: case TileType.TELEPORT_TRAP: case TileType.ICE_TRAP: case TileType.BLINDING_TRAP: case TileType.SHOCK_TRAP: case TileType.FIRE_TRAP: case TileType.SCALDING_OIL_TRAP: traparray[0].Add(t); break; case TileType.POISON_GAS_TRAP: case TileType.GRENADE_TRAP: traparray[1].Add(t); break; case TileType.SLIDING_WALL_TRAP: case TileType.PHANTOM_TRAP: traparray[2].Add(t); break; case TileType.LIGHT_TRAP: case TileType.DARKNESS_TRAP: traparray[3].Add(t); break; case TileType.FLING_TRAP: case TileType.STONE_RAIN_TRAP: traparray[4].Add(t); break; } } } for(int i=0;i<5;++i){ foreach(Tile t in traparray[i]){ traps.Add(t); } } } if(traps.Count > 0){ B.Add("*CLICK*. "); foreach(Tile t in traps){ t.TriggerTrap(false); } } else{ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.ENCHANTMENT: { if(user == player){ EnchantmentType ench = (EnchantmentType)R.Between(0,4); while(ench == user.EquippedWeapon.enchantment){ ench = (EnchantmentType)R.Between(0,4); } B.Add("Your " + user.EquippedWeapon.NameWithEnchantment() + " glows brightly! "); user.EquippedWeapon.enchantment = ench; B.Add("Your " + user.EquippedWeapon.NameWithoutEnchantment() + " is now a " + user.EquippedWeapon.NameWithEnchantment() + "! "); } else{ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.THUNDERCLAP: { B.Add("Thunder crashes! ",user); var scr = Screen.GetCurrentMap(); List<Tile>[] printed = new List<Tile>[13]; Color leading_edge_color = Color.White; Color trail_color = Color.DarkCyan; if(Global.LINUX && !Screen.GLMode){ leading_edge_color = Color.Gray; } for(int dist=0;dist<=12;++dist){ printed[dist] = new List<Tile>(); foreach(Tile t in user.TilesAtDistance(dist)){ if(t.seen && user.HasLOE(t)){ printed[dist].Add(t); } } foreach(Tile t in printed[dist]){ colorchar cch = M.VisibleColorChar(t.row,t.col); cch.bgcolor = leading_edge_color; if(cch.color == leading_edge_color){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); } if(dist > 0){ foreach(Tile t in printed[dist-1]){ colorchar cch = M.VisibleColorChar(t.row,t.col); cch.bgcolor = trail_color; if(cch.color == trail_color){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); } if(dist > 4){ foreach(Tile t in printed[dist-5]){ Screen.WriteMapChar(t.row,t.col,scr[t.row,t.col]); } } } Game.GLUpdate(); Thread.Sleep(10); } List<Actor> actors = new List<Actor>(); for(int dist=0;dist<=12;++dist){ foreach(Tile t in user.TilesAtDistance(dist).Randomize()){ if(user.HasLOE(t)){ if(t.actor() != null && t.actor() != user){ actors.Add(t.actor()); } t.BreakFragileFeatures(); } } } foreach(Actor a in actors){ if(a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(4,6),user,"a scroll of thunderclap")){ a.ApplyStatus(AttrType.STUNNED,R.Between(5,10)*100); } } user.MakeNoise(12); break; } case ConsumableType.FIRE_RING: { List<pos> cells = new List<pos>(); List<Tile> valid = new List<Tile>(); foreach(Tile t in user.TilesWithinDistance(3)){ if(t.passable && user.DistanceFrom(t) > 1 && user.HasLOE(t) && user.ApproximateEuclideanDistanceFromX10(t) < 45){ valid.Add(t); cells.Add(t.p); } } if(valid.Count > 0){ if(player.CanSee(user)){ B.Add("A ring of fire surrounds " + user.the_name + ". "); } else{ B.Add("A ring of fire appears! ",user.tile()); } valid.Randomize(); foreach(Tile t in valid){ t.AddFeature(FeatureType.FIRE); } Screen.AnimateMapCells(cells,new colorchar('&',Color.RandomFire)); } else{ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.RAGE: { B.Add("A murderous red glow cascades outward. ",user); List<Tile>[] printed = new List<Tile>[13]; Color leading_edge_color = Color.Red; Color trail_color = Color.DarkRed; if(Global.LINUX && !Screen.GLMode){ leading_edge_color = Color.DarkRed; } for(int dist=0;dist<=12;++dist){ printed[dist] = new List<Tile>(); foreach(Tile t in user.TilesAtDistance(dist)){ if(t.seen && user.HasLOS(t)){ printed[dist].Add(t); } } foreach(Tile t in printed[dist]){ colorchar cch = M.VisibleColorChar(t.row,t.col); cch.bgcolor = leading_edge_color; if(cch.color == leading_edge_color){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); } if(dist > 0){ foreach(Tile t in printed[dist-1]){ colorchar cch = M.VisibleColorChar(t.row,t.col); cch.bgcolor = trail_color; if(cch.color == trail_color){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); } } Game.GLUpdate(); Thread.Sleep(5); } int actors_affected = 0; string name_is = ""; foreach(Actor a in M.AllActors()){ if(a != user && user.DistanceFrom(a) <= 12 && user.HasLOS(a)){ a.ApplyStatus(AttrType.ENRAGED,R.Between(10,17)*100,false,"",a.You("calm") + " down. ",a.You("resist") + "! "); actors_affected++; if(player.CanSee(a)){ name_is = a.YouAre(); } } } if(actors_affected > 0){ if(actors_affected == 1){ B.Add(name_is + " enraged! "); } else{ B.Add("Bloodlust fills the air. "); } } break; } case ConsumableType.FREEZING: { ItemUseResult orb_result = UseOrb(2,false,user,line,(t,LOE_tile,results)=>{ Screen.AnimateExplosion(t,2,new colorchar('*',Color.RandomIce)); List<Tile> targets = new List<Tile>(); foreach(Tile t2 in t.TilesWithinDistance(2)){ if(LOE_tile.HasLOE(t2)){ targets.Add(t2); } } while(targets.Count > 0){ Tile t2 = targets.RemoveRandom(); t2.ApplyEffect(DamageType.COLD); Actor ac = t2.actor(); if(ac != null){ ac.ApplyFreezing(); } } }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.FLAMES: { ItemUseResult orb_result = UseOrb(2,false,user,line,(t,LOE_tile,results)=>{ List<Tile> area = new List<Tile>(); List<pos> cells = new List<pos>(); foreach(Tile tile in t.TilesWithinDistance(2)){ if(LOE_tile.HasLOE(tile)){ if(tile.passable){ tile.AddFeature(FeatureType.FIRE); } else{ tile.ApplyEffect(DamageType.FIRE); } if(tile.Is(FeatureType.FIRE)){ area.Add(tile); } cells.Add(tile.p); } } Screen.AnimateMapCells(cells,new colorchar('&',Color.RandomFire)); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.FOG: { ItemUseResult orb_result = UseOrb(3,false,user,line,(t,LOE_tile,results)=>{ List<Tile> area = new List<Tile>(); List<pos> cells = new List<pos>(); colorchar cch = new colorchar('*',Color.Gray); for(int i=0;i<=3;++i){ foreach(Tile tile in t.TilesAtDistance(i)){ if(tile.passable && LOE_tile.HasLOE(tile)){ tile.AddFeature(FeatureType.FOG); area.Add(tile); cells.Add(tile.p); if(tile.seen){ M.last_seen[tile.row,tile.col] = cch; } } } Screen.AnimateMapCells(cells,cch,40); } Q.RemoveTilesFromEventAreas(area,EventType.REMOVE_GAS); Event.RemoveGas(area,800,FeatureType.FOG,25); //Q.Add(new Event(area,600,EventType.FOG,25)); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.DETONATION: { ItemUseResult orb_result = UseOrb(3,false,user,line,(t,LOE_tile,results)=>{ LOE_tile.ApplyExplosion(3,user,"an orb of detonation"); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.BREACHING: { ItemUseResult orb_result = UseOrb(5,false,user,line,(t,LOE_tile,results)=>{ int max_dist = -1; foreach(Tile t2 in M.TilesByDistance(t.row,t.col,false,true)){ if(t.DistanceFrom(t2) > 5){ break; } if(t2.Is(TileType.WALL,TileType.WAX_WALL,TileType.STALAGMITE,TileType.CRACKED_WALL,TileType.DOOR_C)){ Screen.WriteMapChar(t2.row,t2.col,t2.symbol,Color.RandomBreached); if(t.DistanceFrom(t2) > max_dist){ max_dist = t.DistanceFrom(t2); Game.GLUpdate(); //todo: stalagmites - if I add them to caves, they should no longer always vanish. check for an event, maybe? Thread.Sleep(50); } } } List<Tile> area = new List<Tile>(); foreach(Tile tile in t.TilesWithinDistance(5)){ if(tile.Is(TileType.WALL,TileType.WAX_WALL,TileType.STALAGMITE,TileType.CRACKED_WALL,TileType.DOOR_C) && tile.p.BoundsCheck(M.tile,false)){ TileType prev_type = tile.type; if(tile.Is(TileType.STALAGMITE)){ tile.Toggle(null,TileType.FLOOR); } else{ tile.Toggle(null,TileType.BREACHED_WALL); tile.toggles_into = prev_type; area.Add(tile); } foreach(Tile neighbor in tile.TilesWithinDistance(1)){ neighbor.solid_rock = false; } } } if(area.Count > 0){ Q.Add(new Event(t,area,500,EventType.BREACH)); } }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.SHIELDING: { ItemUseResult orb_result = UseOrb(1,true,user,line,(t,LOE_tile,results)=>{ List<Tile> area = new List<Tile>(); List<pos> cells = new List<pos>(); List<colorchar> symbols = new List<colorchar>(); foreach(Tile tile in t.TilesWithinDistance(1)){ if(tile.passable && LOE_tile.HasLOE(tile)){ colorchar cch = tile.visual; if(tile.actor() != null){ if(!tile.actor().HasAttr(AttrType.SHIELDED)){ tile.actor().attrs[AttrType.SHIELDED] = 1; B.Add(tile.actor().YouAre() + " shielded. ",tile.actor()); } if(player.CanSee(tile.actor())){ cch = tile.actor().visual; } } cch.bgcolor = Color.Blue; if(Global.LINUX && !Screen.GLMode){ cch.bgcolor = Color.DarkBlue; } if(cch.color == cch.bgcolor){ cch.color = Color.Black; } if(cch.c == '.'){ cch.c = '+'; } symbols.Add(cch); cells.Add(tile.p); area.Add(tile); } } Screen.AnimateMapCells(cells,symbols,150); foreach(Tile tile in area){ if(player.CanSee(tile)){ B.Add("A zone of protection is created. "); break; } } Q.Add(new Event(area,100,EventType.SHIELDING,R.Roll(2,6)+6)); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.TELEPORTAL: { ItemUseResult orb_result = UseOrb(0,false,user,line,(t,LOE_tile,results)=>{ LOE_tile.AddFeature(FeatureType.TELEPORTAL); if(LOE_tile.Is(FeatureType.TELEPORTAL)){ Q.Add(new Event(LOE_tile,0,EventType.TELEPORTAL,100)); } }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.PAIN: { ItemUseResult orb_result = UseOrb(5,false,user,line,(t,LOE_tile,results)=>{ List<pos> cells = new List<pos>(); List<colorchar> symbols = new List<colorchar>(); foreach(Tile tile in t.TilesWithinDistance(5)){ if(LOE_tile.HasLOE(tile)){ Actor a = tile.actor(); if(a != null){ if(a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(2,6),user,"an orb of pain")){ a.ApplyStatus(AttrType.VULNERABLE,(R.Roll(2,6)+6)*100); if(a == player){ Help.TutorialTip(TutorialTopic.Vulnerable); } } } symbols.Add(new colorchar('*',Color.RandomDoom)); /*if(tile.DistanceFrom(t) % 2 == 0){ symbols.Add(new colorchar('*',Color.DarkMagenta)); } else{ symbols.Add(new colorchar('*',Color.DarkRed)); }*/ cells.Add(tile.p); } } player.AnimateVisibleMapCells(cells,symbols,80); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.CONFUSION: { ItemUseResult orb_result = UseOrb(2,false,user,line,(t,LOE_tile,results)=>{ List<Tile> area = new List<Tile>(); List<pos> cells = new List<pos>(); colorchar cch = new colorchar('*',Color.RandomConfusion); for(int i=0;i<=2;++i){ foreach(Tile tile in t.TilesAtDistance(i)){ if(tile.passable && LOE_tile.HasLOE(tile)){ tile.AddFeature(FeatureType.CONFUSION_GAS); area.Add(tile); cells.Add(tile.p); if(tile.seen){ M.last_seen[tile.row,tile.col] = cch; } } } Screen.AnimateMapCells(cells,cch,40); } Q.RemoveTilesFromEventAreas(area,EventType.REMOVE_GAS); Event.RemoveGas(area,R.Between(7,9)*100,FeatureType.CONFUSION_GAS,20); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.BLADES: { ItemUseResult orb_result = UseOrb(1,false,user,line,(t,LOE_tile,results)=>{ List<Tile> targets = new List<Tile>(); foreach(Tile t2 in t.TilesWithinDistance(1)){ if(t2.passable && t2.actor() == null && LOE_tile.HasLOE(t2)){ targets.Add(t2); } } targets.Randomize(); foreach(Tile t2 in targets){ Actor a = Actor.Create(ActorType.BLADE,t2.row,t2.col); if(a != null){ a.speed = 50; } } }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.DUST_STORM: { ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{ List<Tile> area = new List<Tile>(); List<pos> cells = new List<pos>(); foreach(Tile neighbor in LOE_tile.TilesWithinDistance(1)){ if(neighbor.passable){ area.Add(neighbor); } } List<Tile> added = new List<Tile>(); foreach(Tile n1 in area){ foreach(int dir in U.FourDirections){ if(R.CoinFlip() && n1.TileInDirection(dir).passable){ added.Add(n1.TileInDirection(dir)); } } } foreach(Tile n1 in added){ area.AddUnique(n1); } colorchar cch = new colorchar('*',Color.TerrainDarkGray); foreach(Tile t2 in area){ t2.AddFeature(FeatureType.THICK_DUST); cells.Add(t2.p); if(t2.seen){ M.last_seen[t2.row,t2.col] = cch; } Actor a = t2.actor(); if(a != null && t2.Is(FeatureType.THICK_DUST)){ if(!a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE,AttrType.BLINDSIGHT)){ if(a == player){ B.Add("Thick dust fills the air! "); } a.ApplyStatus(AttrType.BLIND,R.Between(1,3)*100); } } } Screen.AnimateMapCells(cells,cch,80); Q.RemoveTilesFromEventAreas(area,EventType.REMOVE_GAS); Event.RemoveGas(area,R.Between(20,25)*100,FeatureType.THICK_DUST,8); }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.FLESH_TO_FIRE: { ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{ Actor a = targeting.targeted.actor(); if(a != null){ B.Add("Jets of flame erupt from " + a.TheName(true) + ". ",a,targeting.targeted); Screen.AnimateMapCell(a.row,a.col,new colorchar('&',Color.RandomFire)); int dmg = (a.curhp+1)/2; if(a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,dmg,user,"a wand of flesh to fire")){ a.ApplyBurning(); } } else{ if(targeting.targeted.Is(FeatureType.TROLL_CORPSE)){ B.Add("Jets of flame erupt from the troll corpse. ",a,targeting.targeted); targeting.targeted.ApplyEffect(DamageType.FIRE); if(targeting.targeted.Is(FeatureType.TROLL_CORPSE)){ //if it's still there because of thick gas, it still gets destroyed. targeting.targeted.RemoveFeature(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ",targeting.targeted); } } else{ if(targeting.targeted.Is(FeatureType.TROLL_BLOODWITCH_CORPSE)){ B.Add("Jets of flame erupt from the troll bloodwitch corpse. ",a,targeting.targeted); targeting.targeted.ApplyEffect(DamageType.FIRE); if(targeting.targeted.Is(FeatureType.TROLL_BLOODWITCH_CORPSE)){ //if it's still there because of thick gas, it still gets destroyed. targeting.targeted.RemoveFeature(FeatureType.TROLL_BLOODWITCH_CORPSE); B.Add("The troll bloodwitch corpse burns to ashes! ",targeting.targeted); } } else{ B.Add("Nothing happens. ",user); results.IDed = false; } } } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.INVISIBILITY: { ItemUseResult wand_result = UseWand(false,false,user,line,(LOE_tile,targeting,results)=>{ Actor a = targeting.targeted.actor(); if(a != null){ B.Add(a.You("vanish",true) + " from view. ",a); if(a.light_radius > 0 && !M.wiz_dark && !M.wiz_lite){ B.Add(a.Your() + " light still reveals " + a.Your() + " location. ",a); } a.RefreshDuration(AttrType.INVISIBLE,(R.Between(2,20)+30)*100,a.YouAre() + " no longer invisible. ",a); } else{ B.Add("Nothing happens. ",user); results.IDed = false; } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.REACH: { ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{ Actor a = targeting.targeted.actor(); if(a != null && a != user){ user.Attack(0,a,true); } else{ B.Add("Nothing happens. ",user); results.IDed = false; } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.SLUMBER: { ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{ Actor a = targeting.targeted.actor(); if(a != null){ if(a.HasAttr(AttrType.MENTAL_IMMUNITY)){ if(a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE)){ B.Add(a.You("resist") + " becoming dormant. ",a); } else{ B.Add(a.You("resist") + " falling asleep. ",a); } } else{ if(a.ResistedBySpirit()){ if(player.HasLOS(a)){ if(a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE)){ B.Add(a.You("resist") + " becoming dormant. ",a); } else{ B.Add(a.You("almost fall") + " asleep. ",a); } } } else{ if(player.HasLOS(a)){ if(a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE)){ B.Add(a.You("become") + " dormant. ",a); } else{ B.Add(a.You("fall") + " asleep. ",a); } } a.attrs[AttrType.ASLEEP] = 6 + R.Roll(4,6); } } } else{ B.Add("Nothing happens. ",user); results.IDed = false; } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.TELEKINESIS: { ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{ if(!SharedEffect.Telekinesis(false,user,targeting.targeted)){ results.used = false; } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.WEBS: { ItemUseResult wand_result = UseWand(true,true,user,line,(LOE_tile,targeting,results)=>{ if(targeting.targeted == user.tile()){ B.Add("Nothing happens. ",user); results.IDed = false; } else{ Screen.CursorVisible = false; foreach(Tile t in targeting.line_to_targeted){ if(t.passable && t != user.tile()){ t.AddFeature(FeatureType.WEB); if(t.seen){ Screen.WriteMapChar(t.row,t.col,';',Color.White); Game.GLUpdate(); Thread.Sleep(15); } } } M.Draw(); } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.BLAST_FUNGUS: { if(line == null){ line = user.GetTargetTile(12,0,false,true); } if(line != null){ revealed_by_light = true; ignored = true; Tile t = line.LastBeforeSolidTile(); Actor first = user.FirstActorInLine(line); B.Add(user.You("fling") + " " + TheName() + ". "); if(first != null && first != user){ t = first.tile(); B.Add("It hits " + first.the_name + ". ",first); } line = line.ToFirstSolidTileOrActor(); if(line.Count > 0){ line.RemoveAt(line.Count - 1); } int idx = 0; foreach(Tile tile2 in line){ if(tile2.seen){ ++idx; } else{ line = line.To(tile2); if(line.Count > 0){ line.RemoveAt(line.Count - 1); } break; } } if(line.Count > 0){ user.AnimateProjectile(line,symbol,color); } t.GetItem(this); //inv.Remove(i); t.MakeNoise(2); if(first != null && first != user){ first.player_visibility_duration = -1; first.attrs[AttrType.PLAYER_NOTICED]++; } else{ if(t.IsTrap()){ t.TriggerTrap(); } } } else{ used = false; } break; } case ConsumableType.BANDAGES: if(!user.HasAttr(AttrType.BANDAGED)){ user.attrs[AttrType.BANDAGED] = 20; //user.recover_time = Q.turn + 100; B.Add(user.You("apply",false,true) + " a bandage. ",user); } else{ B.Add(user.the_name + " can't apply another bandage yet. ",user); used = false; } break; case ConsumableType.FLINT_AND_STEEL: { int dir = -1; if(user == player){ dir = user.GetDirection("Which direction? ",false,true); } else{ dir = user.DirectionOf(player); } if(dir != -1){ Tile t = user.TileInDirection(dir); B.Add(user.You("use") + " your flint & steel. ",user); if(t.actor() != null && t.actor().HasAttr(AttrType.OIL_COVERED) && !t.Is(FeatureType.POISON_GAS,FeatureType.THICK_DUST)){ t.actor().ApplyBurning(); } if(!t.Is(TileType.WAX_WALL)){ t.ApplyEffect(DamageType.FIRE); } } else{ used = false; } break; } default: used = false; break; } if(used){ if(IDed){ bool seen = true; //i'll try letting orbs always be IDed. keep an eye on this. /*bool seen = (user == player); if(user != player){ if(player.CanSee(line[0])){ //fix this line - or at least check for null/empty seen = true; } if(user != null && player.CanSee(user)){ //heck, I could even check to see whose turn it is, if I really wanted to be hacky. seen = true; } }*/ if(!identified[type] && seen){ identified[type] = true; B.Add("(It was " + SingularName(true) + "!) "); } } else{ if(!unIDed_name[type].Contains("{tried}")){ unIDed_name[type] = unIDed_name[type] + " {tried}"; } } if(quantity > 1){ --quantity; } else{ if(type == ConsumableType.BANDAGES){ --other_data; if(user != null && other_data == 0){ B.Add(user.You("use") + " your last bandage. ",user); user.inv.Remove(this); } } else{ if(type == ConsumableType.FLINT_AND_STEEL){ if(R.OneIn(3)){ --other_data; if(user != null){ if(other_data == 2){ B.Add("Your flint & steel shows signs of wear. ",user); } if(other_data == 1){ B.Add("Your flint & steel is almost depleted. ",user); } if(other_data == 0){ B.Add("Your flint & steel is used up. ",user); user.inv.Remove(this); } } } } else{ if(NameOfItemType() == "wand"){ if(charges > 0){ --charges; if(other_data >= 0){ ++other_data; } } else{ other_data = -1; } } else{ if(user != null){ user.inv.Remove(this); } } } } } CheckForMimic(); } return used; }
public static string GenerateScrollName() { //List<string> vowel = new List<string>{"a","e","i","o","u"}; //List<string> consonant = new List<string>{"k","s","t","n","h","m","y","r","w","g","d","p","b"}; //Japanese-inspired - used AEIOU, 4 syllables max, and 3-9 total //List<string> consonant = new List<string>{"h","k","l","n","m","p","w"}; //Hawaiian-inspired //List<string> vowel = new List<string>{"y","i","e","u","ae"}; //some kinda Gaelic-inspired //List<string> consonant = new List<string>{"r","t","s","rr","m","n","w","b","c","d","f","g","l","ss","v"}; //some kinda Gaelic-inspired List<string> vowel = new List<string>{"a","e","i","o","u","ea","ei","io","a","e","i","o","u","a","e","i","o","u","a","e","i","o","oo","ee","a","e","o"}; //the result of a bunch of tweaking List<string> consonant = new List<string>{"k","s","t","n","h","m","y","r","w","g","d","p","b","f","l","v","z","ch","br","cr","dr","fr","gr","kr","pr","tr","th","sc","sh","sk","sl","sm","sn","sp","st","k","s","t","n","m","r","g","d","p","b","l","k","s","t","n","m","r","d","p","b","l",}; int syllables = 0; List<int> syllable_count = null; do{ syllables = R.Roll(4) + 2; syllable_count = new List<int>(); while(syllables > 0){ if(syllable_count.Count == 2){ syllable_count.Add(syllables); syllables = 0; break; } int R2 = Math.Min(syllables,3); int M = 0; if(syllable_count.Count == 0){ //sorry, magic numbers here M = 6; } if(syllable_count.Count == 1){ M = 5; } int D = 0; if(syllable_count.Count == 0){ D = Math.Max(0,syllables - M); } int s = R.Roll(R2 - D) + D; syllable_count.Add(s); syllables -= s; } } while(!syllable_count.Any(x => x!=1)); // if every word has only 1 syllable, try again string result = ""; while(syllable_count.Count > 0){ string word = ""; if(R.OneIn(5)){ word = word + vowel.Random(); } for(int count = syllable_count.RemoveRandom();count > 0;--count){ word = word + consonant.Random() + vowel.Random(); /*if(R.OneIn(20)){ //used for the Japanese-inspired one word = word + "n"; }*/ } if(result == ""){ result = result + word; } else{ result = result + " " + word; } } return result; }
private void SetupResourceTiles() { _resourceTiles.Clear(); var tiles = new List<ResourceTypes>(); tiles.Add(ResourceTypes.Sheep, 4); tiles.Add(ResourceTypes.Wood, 4); tiles.Add(ResourceTypes.Wheat, 4); tiles.Add(ResourceTypes.Ore, 3); tiles.Add(ResourceTypes.Brick, 3); tiles.Add(ResourceTypes.None, 1); int numberTokenIndex = 0; int innerIndex = _random.Next(5); int outerIndex = innerIndex * 2; // Populate outer ring for (int i = 0; i < OuterHexagons.Length; i++) { var tile = tiles.RemoveRandom(); var resourceTile = (tile == ResourceTypes.None) ? ResourceTile.DesertTile : new ResourceTile(tile, NumberTokens[numberTokenIndex++]); _resourceTiles[OuterHexagons[outerIndex]] = resourceTile; // Wrap back to start of array if needed outerIndex++; if (outerIndex == OuterHexagons.Length) outerIndex = 0; } // Populate inner ring for (int i = 0; i < InnerHexagons.Length; i++) { var tile = tiles.RemoveRandom(); var resourceTile = (tile == ResourceTypes.None) ? ResourceTile.DesertTile : new ResourceTile(tile, NumberTokens[numberTokenIndex++]); _resourceTiles[InnerHexagons[innerIndex]] = resourceTile; // Wrap back to start of array if needed innerIndex++; if (innerIndex == InnerHexagons.Length) innerIndex = 0; } // Populate center var lastTile = tiles[0]; var lastResourceTile = (lastTile == ResourceTypes.None) ? ResourceTile.DesertTile : new ResourceTile(lastTile, NumberTokens[numberTokenIndex]); _resourceTiles[Hexagon.Zero] = lastResourceTile; }
public static void GenerateUnIDedNames() { identified = new Dict<ConsumableType,bool>(); List<string> potion_flavors = new List<string>{"vermilion","cerulean","emerald","fuchsia","aquamarine","goldenrod","violet","silver","indigo","crimson"}; List<Color> potion_colors = new List<Color>{Color.Red,Color.Blue,Color.Green,Color.Magenta,Color.Cyan,Color.Yellow,Color.DarkMagenta,Color.Gray,Color.DarkBlue,Color.DarkRed}; List<pos> potion_sprites = new List<pos>(); for(int i=0;i<10;++i){ potion_sprites.Add(new pos(0,48+i)); } List<string> orb_flavors = new List<string>{"flickering","iridescent","sparkling","chromatic","psychedelic","scintillating","glittering","glimmering","shimmering","kaleidoscopic"}; List<Color> orb_colors = new List<Color>{Color.RandomRGB,Color.RandomCMY,Color.RandomDRGB,Color.RandomDCMY,Color.RandomRGBW,Color.RandomCMYW,Color.RandomRainbow,Color.RandomBright,Color.RandomDark,Color.RandomAny}; List<string> wand_flavors = new List<string>{"runed","bone","crystal","brittle","twisted","slender","bent","serpentine","carved","tapered"}; //...etched sturdy smooth flexible inscribed banded polished thick gilded foreach(ConsumableType type in Enum.GetValues(typeof(ConsumableType))){ string type_name = NameOfItemType(type); if(type_name == "potion"){ int num = R.Roll(potion_flavors.Count) - 1; unIDed_name[type] = potion_flavors[num] + " potion~"; proto[type].color = potion_colors[num]; proto[type].sprite_offset = potion_sprites[num]; potion_flavors.RemoveAt(num); potion_colors.RemoveAt(num); potion_sprites.RemoveAt(num); } else{ if(type_name == "scroll"){ unIDed_name[type] = "scroll~ labeled '" + GenerateScrollName() + "'"; proto[type].sprite_offset = new pos(2,48); } else{ if(type_name == "orb"){ unIDed_name[type] = orb_flavors.RemoveRandom() + " orb~"; int color_num = R.Roll(orb_colors.Count) - 1; proto[type].color = orb_colors[color_num]; //note that color isn't tied to name for orbs. they're all random. orb_colors.RemoveAt(color_num); proto[type].sprite_offset = new pos(3,48+color_num); if(type == ConsumableType.TELEPORTAL){ Tile.Feature(FeatureType.TELEPORTAL).color = proto[type].color; } } else{ if(type_name == "wand"){ unIDed_name[type] = wand_flavors.RemoveRandom() + " wand~"; } else{ identified[type] = true; //bandages, trap, blast fungus... switch(type){ case ConsumableType.BANDAGES: proto[type].sprite_offset = new pos(5,48); break; case ConsumableType.FLINT_AND_STEEL: proto[type].sprite_offset = new pos(5,49); break; case ConsumableType.BLAST_FUNGUS: proto[type].sprite_offset = new pos(5,50); break; } } } } } } }
private void WhirlwindMove(int row,int col,bool trigger_traps,List<Actor> excluded) { List<Actor> previously_adjacent = new List<Actor>(); foreach(Actor a in ActorsAtDistance(1)){ if(!a.IsHiddenFrom(this) && (excluded == null || !excluded.Contains(a))){ previously_adjacent.Add(a); } } Move(row,col,trigger_traps); if(previously_adjacent.Count > 0){ List<Actor> still_adjacent = new List<Actor>(); foreach(Actor a in ActorsAtDistance(1)){ if(previously_adjacent.Contains(a)){ still_adjacent.Add(a); } } if(still_adjacent.Count > 0){ if(HasAttr(AttrType.STUNNED) && R.OneIn(3)){ B.Add(You("stagger") + ". ",this); } else{ if(exhaustion == 100 && R.CoinFlip()){ B.Add(You("fumble") + " from exhaustion. ",this); } else{ bool possessed = false; if(EquippedWeapon.status[EquipmentStatus.POSSESSED] && R.CoinFlip()){ List<Actor> actors = ActorsWithinDistance(1); Actor chosen = actors.RandomOrDefault(); if(chosen == this){ possessed = true; B.Add(Your() + " possessed " + EquippedWeapon.NameWithEnchantment() + " tries to attack " + the_name + "! ",this); B.Add(You("fight") + " it off! ",this); } } if(!possessed){ while(still_adjacent.Count > 0){ Actor a = still_adjacent.RemoveRandom(); Attack(0,a,true); } } } } } } }
public void CaveWidenRooms(int percent_chance_per_room,int number_of_tiles_to_add) { List<List<pos>> roomlist = new List<List<pos>>(); ForEachRoom(list=>{ if(PercentChance(percent_chance_per_room)){ roomlist.Add(list); } return true; }); while(roomlist.Count > 0){ List<pos> list = roomlist.RemoveRandom(); PosArray<CellType> old_map = new PosArray<CellType>(H,W); foreach(pos p in list){ old_map[p] = map[p]; map[p] = CellType.Wall; } PosArray<bool> rock = new PosArray<bool>(H,W); for(int i=0;i<H;++i){ for(int j=0;j<W;++j){ pos p = new pos(i,j); rock[p] = true; if(BoundsCheck(i,j,false)){ foreach(pos neighbor in p.AdjacentPositionsClockwise()){ if(map[neighbor] != CellType.Wall){ rock[p] = false; break; } } } } } foreach(pos p in list){ map[p] = CellType.RoomInterior; //todo: might this step be extraneous? } List<pos> frontier = new List<pos>(); { PosArray<bool> in_list = new PosArray<bool>(H,W); foreach(pos p in list){ in_list[p] = true; } for(int i=0;i<H;++i){ for(int j=0;j<W;++j){ pos p = new pos(i,j); if(in_list[p]){ foreach(pos neighbor in p.PositionsAtDistance(1,in_list)){ if(!in_list[neighbor]){ frontier.Add(p); break; } } } } } } int fail_counter = 0; int num_added = 0; bool finished = false; while(!finished){ if(frontier.Count == 0 || num_added >= number_of_tiles_to_add){ finished = true; break; } pos f = frontier.RemoveRandom(); foreach(pos neighbor in f.CardinalAdjacentPositions()){ if(!BoundsCheck(neighbor,false) || !rock[neighbor.row,neighbor.col]){ ++fail_counter; //this might now be unreachable if(!BoundsCheck(neighbor,false)){ fail_counter += 25; //fail quicker when against the edge of the map to prevent ugliness } //however, this doesn't actually fail as quickly as it should - i've overlooked something. if(fail_counter >= 50){ finished = true; break; } } else{ if(map[neighbor] != CellType.RoomInterior){ map[neighbor] = CellType.RoomInterior; ++num_added; bool add_neighbor = true; foreach(pos n2 in neighbor.CardinalAdjacentPositions()){ if(!BoundsCheck(n2,false) || !rock[n2.row,n2.col]){ add_neighbor = false; ++fail_counter; //this might now be unreachable if(!BoundsCheck(neighbor,false)){ fail_counter += 25; //fail quicker when against the edge of the map to prevent ugliness } //however, this doesn't actually fail as quickly as it should - i've overlooked something. if(fail_counter >= 50){ finished = true; } break; } } if(finished){ break; } if(add_neighbor){ frontier.Add(neighbor); } } } } } foreach(pos p in list){ map[p] = old_map[p]; } } }
public virtual GameObject SelectRandomAndRemove() { return(pickupPrefabList.RemoveRandom()); }
public void ConnectDiagonals(bool force_connection) { List<pos> walls = new List<pos>(); for(int i=1;i<H-2;++i){ for(int j=1;j<W-2;++j){ if(map[i,j].IsPassable() && map[i,j+1].IsWall()){ if(map[i+1,j].IsWall() && map[i+1,j+1].IsPassable()){ walls.Add(new pos(i,j+1)); walls.Add(new pos(i+1,j)); } } else{ if(map[i,j].IsWall() && map[i,j+1].IsPassable()){ if(map[i+1,j].IsPassable() && map[i+1,j+1].IsWall()){ walls.Add(new pos(i,j)); walls.Add(new pos(i+1,j+1)); } } } if(walls.Count > 0){ pos wall0 = walls[0]; pos wall1 = walls[1]; while(walls.Count > 0){ pos p = walls.RemoveRandom(); int direction_of_other_wall = 0; for(int ii=0;ii<8;++ii){ pos other_wall = new pos(-1,-1); if(p.row == wall0.row && p.col == wall0.col){ other_wall = wall1; } else{ other_wall = wall0; } if(p.PosInDir(N.RotateDir(true,ii)).row == other_wall.row && p.PosInDir(N.RotateDir(true,ii)).col == other_wall.col){ direction_of_other_wall = N.RotateDir(true,ii); } } bool good = true; for(int ii=3;ii<=5;++ii){ if(map[p.PosInDir(direction_of_other_wall.RotateDir(true,ii))].IsWall() == false){ good = false; break; } } int consecutive_walls = p.ConsecutiveAdjacent(x => map[x].IsWall()); if(consecutive_walls >= 4 || good || force_connection){ map[p] = CellType.CorridorIntersection; if(IsLegal(p) || force_connection){ walls.Clear(); } else{ map[p] = CellType.Wall; } } } } } } }
public void OpenChest() { if(type == TileType.CHEST){ if(spellbooks_generated < 5 && R.OneIn(50)){ //keep an eye on this value ++spellbooks_generated; SpellType spell = SpellType.NO_SPELL; List<SpellType> random_spell_list = new List<SpellType>(); foreach(SpellType sp in Enum.GetValues(typeof(SpellType))){ random_spell_list.Add(sp); } while(spell == SpellType.NO_SPELL && random_spell_list.Count > 0){ SpellType sp = random_spell_list.RemoveRandom(); if(!player.HasSpell(sp) && sp != SpellType.NO_SPELL && sp != SpellType.NUM_SPELLS){ spell = sp; } } if(spell != SpellType.NO_SPELL){ B.Add("You find a spellbook! "); B.Add("You learn " + Spell.Name(spell) + ". "); player.spells[spell] = true; Actor.spells_in_order.Add(spell); } else{ B.Add("The chest is empty! "); } } else{ ConsumableType item = Item.RandomChestItem(); if(item == ConsumableType.MAGIC_TRINKET){ List<MagicTrinketType> valid = new List<MagicTrinketType>(); foreach(MagicTrinketType trinket in Enum.GetValues(typeof(MagicTrinketType))){ if(trinket != MagicTrinketType.NO_MAGIC_TRINKET && trinket != MagicTrinketType.NUM_MAGIC_TRINKETS && !player.magic_trinkets.Contains(trinket)){ valid.Add(trinket); } } if(valid.Count > 0){ MagicTrinketType trinket = valid.Random(); if(trinket == MagicTrinketType.BRACERS_OF_ARROW_DEFLECTION || trinket == MagicTrinketType.BOOTS_OF_GRIPPING){ B.Add("You find " + MagicTrinket.Name(trinket) + "! "); } else{ B.Add("You find a " + MagicTrinket.Name(trinket) + "! "); } player.magic_trinkets.Add(trinket); Help.TutorialTip(TutorialTopic.MagicTrinkets); } else{ B.Add("The chest is empty! "); } } else{ bool no_room = false; if(player.InventoryCount() >= Global.MAX_INVENTORY_SIZE){ no_room = true; } Item i = Item.Create(Item.RandomItem(),player); if(i != null){ i.revealed_by_light = true; B.Add("You find " + Item.Prototype(i.type).AName() + ". "); if(no_room){ B.Add("Your pack is too full to pick it up. "); i.ignored = true; } } } } if(color == Color.Yellow){ B.Add("There's something else in the chest! "); color = Color.DarkYellow; } else{ TurnToFloor(); } } }
public bool CreateTwistyCave(bool two_walls_between_corridors,int percent_coverage,int density_threshold,DensityUpdateDelegate update_density) { int target_number_of_floors = (H * W * percent_coverage) / 100; List<pos> frontier = new List<pos>(); PosArray<int> density = new PosArray<int>(H,W); pos origin = new pos(R.Between(2,H-3),R.Between(2,W-3)); frontier.Add(origin); map[origin] = CellType.RoomInterior; int count = 1; bool pick_random = false; while(frontier.Count > 0 && count < target_number_of_floors){ pos p; if(pick_random || R.PercentChance(5)){ p = frontier.RemoveRandom(); pick_random = false; } else{ p = frontier.RemoveLast(); } if(density_threshold > 0 && density[p] >= density_threshold){ continue; } List<int> valid_dirs = new List<int>(); foreach(int dir in U.FourDirections){ pos neighbor = p.PosInDir(dir); if(BoundsCheck(neighbor,false) && map[neighbor].IsWall()){ bool valid = true; if(two_walls_between_corridors){ int idx = 0; foreach(int dir2 in dir.GetArc(1)){ if(!map[neighbor.PosInDir(dir2)].IsWall()){ valid = false; break; } else{ if(idx == 1){ pos n = neighbor.PosInDir(dir2).PosInDir(dir2); if(n.BoundsCheck(map) && !map[n].IsWall()){ valid = false; break; } } else{ foreach(int dir3 in dir2.GetArc(1)){ pos n = neighbor.PosInDir(dir2).PosInDir(dir3); if(n.BoundsCheck(map) && !map[n].IsWall()){ valid = false; break; } } } } ++idx; } } else{ foreach(int dir2 in dir.GetArc(1)){ if(!map[neighbor.PosInDir(dir2)].IsWall()){ valid = false; break; } } } if(valid){ valid_dirs.Add(dir); } } } if(valid_dirs.Count == 0){ pick_random = true; } else{ if(valid_dirs.Count == 1 && R.CoinFlip()){ pick_random = true; } else{ valid_dirs.Randomize(); foreach(int i in valid_dirs){ pos neighbor = p.PosInDir(i); map[neighbor] = CellType.RoomInterior; ++count; update_density(neighbor,density,map); if(!pick_random){ //todo: should this check be removed? it can lead to abandoned paths that don't ever get filled frontier.Add(neighbor); } } } } } foreach(pos p in frontier){ if(BoundsCheck(p,false)){ map[p] = CellType.RoomInterior; } } return true; }
public void ActiveAI() { if(path.Count > 0){ path.Clear(); } if(!HasAttr(AttrType.AGGRESSION_MESSAGE_PRINTED)){ PrintAggressionMessage(); } switch(type){ case ActorType.GIANT_BAT: case ActorType.PHANTOM_BLIGHTWING: if(DistanceFrom(target) == 1){ int idx = R.Roll(1,2) - 1; Attack(idx,target); if(target != null && R.CoinFlip()){ //chance of retreating AI_Step(target,true); } } else{ if(R.CoinFlip()){ AI_Step(target); QS(); } else{ AI_Step(TileInDirection(Global.RandomDirection())); QS(); } } break; case ActorType.BLOOD_MOTH: { Tile brightest = null; if(!M.wiz_dark && !M.wiz_lite && !HasAttr(AttrType.BLIND)){ List<Tile> valid = M.AllTiles().Where(x=>x.light_value > 0 && CanSee(x)); valid = valid.WhereGreatest(x=>{ int result = x.light_radius; if(x.Is(FeatureType.FIRE) && result == 0){ result = 1; } if(x.inv != null && x.inv.light_radius > result){ result = x.inv.light_radius; } if(x.actor() != null && x.actor().LightRadius() > result){ result = x.actor().LightRadius(); } return result; }); valid = valid.WhereLeast(x=>DistanceFrom(x)); if(valid.Count > 0){ brightest = valid.RandomOrDefault(); } } if(brightest != null){ if(DistanceFrom(brightest) <= 1){ if(target != null && brightest == target.tile()){ Attack(0,target); if(target == player && player.curhp > 0){ Help.TutorialTip(TutorialTopic.Torch); } } else{ List<Tile> open = new List<Tile>(); foreach(Tile t in TilesAtDistance(1)){ if(t.DistanceFrom(brightest) <= 1 && t.passable && t.actor() == null){ open.Add(t); } } if(open.Count > 0){ AI_Step(open.Random()); } QS(); } } else{ List<Tile> tiles = new List<Tile>(); if(brightest.row == row || brightest.col == col){ int targetdir = DirectionOf(brightest); for(int i=-1;i<=1;++i){ pos adj = p.PosInDir(targetdir.RotateDir(true,i)); if(M.tile[adj].passable && M.actor[adj] == null){ tiles.Add(M.tile[adj]); } } } if(tiles.Count > 0){ AI_Step(tiles.Random()); } else{ AI_Step(brightest); } QS(); } } else{ int dir = Global.RandomDirection(); if(!TileInDirection(dir).passable && TilesAtDistance(1).Where(t => !t.passable).Count > 4){ dir = Global.RandomDirection(); } if(TileInDirection(dir).passable && ActorInDirection(dir) == null){ AI_Step(TileInDirection(dir)); QS(); } else{ if(curhp < maxhp && target != null && ActorInDirection(dir) == target){ Attack(0,target); } else{ if(player.HasLOS(TileInDirection(dir)) && player.HasLOS(this)){ if(!TileInDirection(dir).passable){ B.Add(the_name + " brushes up against " + TileInDirection(dir).the_name + ". ",this); } else{ if(ActorInDirection(dir) != null){ B.Add(the_name + " brushes up against " + ActorInDirection(dir).TheName(true) + ". ",this); } } } QS(); } } } /*PhysicalObject brightest = null; if(!M.wiz_lite && !M.wiz_dark){ List<PhysicalObject> current_brightest = new List<PhysicalObject>(); foreach(Tile t in M.AllTiles()){ int pos_radius = t.light_radius; PhysicalObject pos_obj = t; if(t.Is(FeatureType.FIRE) && pos_radius == 0){ pos_radius = 1; } if(t.inv != null && t.inv.light_radius > pos_radius){ pos_radius = t.inv.light_radius; pos_obj = t.inv; } if(t.actor() != null && t.actor().LightRadius() > pos_radius){ pos_radius = t.actor().LightRadius(); pos_obj = t.actor(); } if(pos_radius > 0){ if(current_brightest.Count == 0 && CanSee(t)){ current_brightest.Add(pos_obj); } else{ foreach(PhysicalObject o in current_brightest){ int object_radius = o.light_radius; if(o is Actor){ object_radius = (o as Actor).LightRadius(); } if(object_radius == 0 && o is Tile && (o as Tile).Is(FeatureType.FIRE)){ object_radius = 1; } if(pos_radius > object_radius){ if(CanSee(t)){ current_brightest.Clear(); current_brightest.Add(pos_obj); break; } } else{ if(pos_radius == object_radius && DistanceFrom(t) < DistanceFrom(o)){ if(CanSee(t)){ current_brightest.Clear(); current_brightest.Add(pos_obj); break; } } else{ if(pos_radius == object_radius && DistanceFrom(t) == DistanceFrom(o) && pos_obj == player){ if(CanSee(t)){ current_brightest.Clear(); current_brightest.Add(pos_obj); break; } } } } } } } } if(current_brightest.Count > 0){ brightest = current_brightest.Random(); } } if(brightest != null){ if(DistanceFrom(brightest) <= 1){ if(brightest == target){ Attack(0,target); if(target == player && player.curhp > 0){ Help.TutorialTip(TutorialTopic.Torch); } } else{ List<Tile> open = new List<Tile>(); foreach(Tile t in TilesAtDistance(1)){ if(t.DistanceFrom(brightest) <= 1 && t.passable && t.actor() == null){ open.Add(t); } } if(open.Count > 0){ AI_Step(open.Random()); } QS(); } } else{ AI_Step(brightest); QS(); } } else{ int dir = Global.RandomDirection(); if(TilesAtDistance(1).Where(t => !t.passable).Count > 4 && !TileInDirection(dir).passable){ dir = Global.RandomDirection(); } if(TileInDirection(dir).passable && ActorInDirection(dir) == null){ AI_Step(TileInDirection(dir)); QS(); } else{ if(curhp < maxhp && target != null && ActorInDirection(dir) == target){ Attack(0,target); } else{ if(player.HasLOS(TileInDirection(dir)) && player.HasLOS(this)){ if(!TileInDirection(dir).passable){ B.Add(the_name + " brushes up against " + TileInDirection(dir).the_name + ". ",this); } else{ if(ActorInDirection(dir) != null){ B.Add(the_name + " brushes up against " + ActorInDirection(dir).TheName(true) + ". ",this); } } } QS(); } } }*/ break; } case ActorType.CARNIVOROUS_BRAMBLE: case ActorType.MUD_TENTACLE: if(DistanceFrom(target) == 1){ Attack(0,target); if(target == player && player.curhp > 0){ Help.TutorialTip(TutorialTopic.RangedAttacks); } } else{ QS(); } break; case ActorType.FROSTLING: { if(DistanceFrom(target) == 1){ if(R.CoinFlip()){ Attack(0,target); } else{ if(AI_Step(target,true)){ QS(); } else{ Attack(0,target); } } } else{ if(FirstActorInLine(target) == target && !HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 6){ int cooldown = R.Roll(1,4); if(cooldown != 1){ RefreshDuration(AttrType.COOLDOWN_1,cooldown*100); } AnimateBoltProjectile(target,Color.RandomIce); if(R.CoinFlip()){ B.Add(TheName(true) + " hits " + target.the_name + " with a blast of cold. ",target); target.TakeDamage(DamageType.COLD,DamageClass.PHYSICAL,R.Roll(2,6),this,"a frostling"); } else{ B.Add(TheName(true) + " misses " + target.the_name + " with a blast of cold. ",target); } foreach(Tile t in GetBestLineOfEffect(target)){ t.ApplyEffect(DamageType.COLD); } Q1(); } else{ if(!HasAttr(AttrType.COOLDOWN_2)){ AI_Step(target); } else{ AI_Sidestep(target); //message for this? hmm. } QS(); } } break; } case ActorType.SWORDSMAN: case ActorType.PHANTOM_SWORDMASTER: if(DistanceFrom(target) == 1){ pos target_pos = target.p; Attack(0,target); if(target != null && target.p.Equals(target_pos)){ List<Tile> valid_dirs = new List<Tile>(); foreach(Tile t in target.TilesAtDistance(1)){ if(t.passable && t.actor() == null && DistanceFrom(t) == 1){ valid_dirs.Add(t); } } if(valid_dirs.Count > 0){ AI_Step(valid_dirs.Random()); } } } else{ attrs[AttrType.COMBO_ATTACK] = 0; AI_Step(target); QS(); } break; case ActorType.DREAM_WARRIOR: if(DistanceFrom(target) == 1){ if(curhp <= 10 && !HasAttr(AttrType.COOLDOWN_1)){ //todo: changed to 20hp and a 10hp threshold...better? attrs[AttrType.COOLDOWN_1]++; List<Tile> openspaces = new List<Tile>(); foreach(Tile t in target.TilesAtDistance(1)){ if(t.passable && t.actor() == null){ openspaces.Add(t); } } foreach(Tile t in openspaces){ if(group == null){ group = new List<Actor>{this}; } Create(ActorType.DREAM_WARRIOR_CLONE,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent); t.actor().player_visibility_duration = -1; t.actor().attrs[AttrType.NO_ITEM]++; group.Add(M.actor[t.row,t.col]); M.actor[t.row,t.col].group = group; group.Randomize(); } openspaces.Add(tile()); Tile newtile = openspaces[R.Roll(openspaces.Count)-1]; if(newtile != tile()){ Move(newtile.row,newtile.col,false); } if(openspaces.Count > 1){ B.Add(the_name + " is suddenly standing all around " + target.the_name + ". ",this,target); Q1(); } else{ Attack(0,target); } } else{ Attack(0,target); } } else{ AI_Step(target); QS(); } break; case ActorType.SPITTING_COBRA: if(DistanceFrom(target) <= 3 && !HasAttr(AttrType.COOLDOWN_1) && FirstActorInLine(target) == target){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(50,75)*100); B.Add(TheName(true) + " spits poison in " + target.YourVisible() + " eyes! ",this,target); AnimateBoltProjectile(target,Color.DarkGreen); if(!target.HasAttr(AttrType.NONLIVING)){ target.ApplyStatus(AttrType.BLIND,R.Between(5,8)*100); /*B.Add(target.YouAre() + " blind! ",target); target.RefreshDuration(AttrType.BLIND,R.Between(5,8)*100,target.YouAre() + " no longer blinded. ",target);*/ } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ List<Tile> tiles = new List<Tile>(); if(target.row == row || target.col == col){ int targetdir = DirectionOf(target); for(int i=-1;i<=1;++i){ pos adj = p.PosInDir(targetdir.RotateDir(true,i)); if(M.tile[adj].passable && M.actor[adj] == null){ tiles.Add(M.tile[adj]); } } } if(tiles.Count > 0){ AI_Step(tiles.Random()); } else{ AI_Step(target); } QS(); } } break; case ActorType.KOBOLD: if(!HasAttr(AttrType.COOLDOWN_1)){ if(DistanceFrom(target) > 12){ AI_Step(target); QS(); } else{ if(FirstActorInLine(target) != target){ AI_Sidestep(target); QS(); } else{ attrs[AttrType.COOLDOWN_1]++; AnimateBoltProjectile(target,Color.DarkCyan,30); if(player.CanSee(this)){ B.Add(the_name + " fires a dart at " + target.the_name + ". ",this,target); } else{ B.Add("A dart hits " + target.the_name + "! ",target); if(player.CanSee(tile()) && !IsInvisibleHere()){ attrs[AttrType.TURNS_VISIBLE] = -1; attrs[AttrType.NOTICED] = 1; B.Add("You spot " + the_name + " that fired it. ",this); //B.Add("You notice " + a_name + ". ",tile()); } } if(target.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(6),this,"a kobold's dart")){ target.ApplyStatus(AttrType.VULNERABLE,R.Between(2,4)*100); /*if(!target.HasAttr(AttrType.VULNERABLE)){ B.Add(target.YouFeel() + " vulnerable. ",target); } target.RefreshDuration(AttrType.VULNERABLE,R.Between(2,4)*100,target.YouFeel() + " less vulnerable. ",target);*/ if(target == player){ Help.TutorialTip(TutorialTopic.Vulnerable); } } Q1(); } } } else{ if(DistanceFrom(target) <= 2){ AI_Flee(); QS(); } else{ B.Add(the_name + " starts reloading. ",this); attrs[AttrType.COOLDOWN_1] = 0; Q1(); RefreshDuration(AttrType.COOLDOWN_2,R.Between(5,6)*100 - 50); //Q.Add(new Event(this,R.Between(5,6)*100,EventType.MOVE)); } } break; case ActorType.SPORE_POD: if(DistanceFrom(target) == 1){ TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,100,null); } else{ AI_Step(target); QS(); } break; case ActorType.FORASECT: { bool burrow = false; if((curhp * 2 <= maxhp || DistanceFrom(target) > 6) && R.CoinFlip()){ burrow = true; } if(DistanceFrom(target) <= 6 && DistanceFrom(target) > 1){ if(R.OneIn(10)){ burrow = true; } } if(burrow && !HasAttr(AttrType.COOLDOWN_1)){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(8,11)*100); if(curhp * 2 <= maxhp){ Burrow(TilesWithinDistance(6)); } else{ Burrow(GetCone(DirectionOf(target),6,true)); } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; } case ActorType.POLTERGEIST: if(inv.Count == 0){ if(DistanceFrom(target) == 1){ pos target_p = target.p; if(Attack(0,target) && M.actor[target_p] != null && M.actor[target_p].inv.Any(i=>!i.do_not_stack)){ target = M.actor[target_p]; Item item = target.inv.Where(i=>!i.do_not_stack).Random(); if(item.quantity > 1){ inv.Add(new Item(item,-1,-1)); item.quantity--; B.Add(YouVisible("steal") + " " + target.YourVisible() + " " + inv[0].Name() + "! ",this,target); } else{ inv.Add(item); target.inv.Remove(item); B.Add(YouVisible("steal") + " " + target.YourVisible() + " " + item.Name() + "! ",this,target); } } } else{ AI_Step(target); QS(); } } else{ attrs[AttrType.KEEPS_DISTANCE] = 1; List<Tile> line = target.GetBestExtendedLineOfEffect(this); Tile next = null; bool found = false; foreach(Tile t in line){ if(found){ next = t; break; } else{ if(t.actor() == this){ found = true; } } } if(next != null){ if(next.passable && next.actor() == null && AI_Step(next)){ QS(); } else{ if(!next.passable){ B.Add(the_name + " disappears into " + next.the_name + ". ",this); foreach(Tile t in TilesWithinDistance(1)){ if(t.DistanceFrom(next) == 1 && t.name == "floor"){ t.AddFeature(FeatureType.SLIME); } } Event e = null; foreach(Event e2 in Q.list){ if(e2.target == this && e2.type == EventType.POLTERGEIST){ e = e2; break; } } if(e != null){ e.target = inv[0]; Actor.tiebreakers[e.tiebreaker] = null; } inv.Clear(); Kill(); } else{ if(next.actor() != null){ if(!next.actor().HasAttr(AttrType.IMMOBILE)){ Move(next.row,next.col); QS(); } else{ if(next.actor().HasAttr(AttrType.IMMOBILE)){ if(AI_Step(next)){ QS(); } else{ if(DistanceFrom(target) == 1){ Attack(1,target); } else{ QS(); } } } } } else{ QS(); } } } } } break; case ActorType.CULTIST: case ActorType.FINAL_LEVEL_CULTIST: if(curhp <= 10 && !HasAttr(AttrType.COOLDOWN_1)){ attrs[AttrType.COOLDOWN_1]++; string invocation; switch(R.Roll(4)){ case 1: invocation = "ae vatra kersai"; break; case 2: invocation = "kersai dzaggath"; break; case 3: invocation = "od fir od bahgal"; break; case 4: invocation = "denei kersai nammat"; break; default: invocation = "denommus pilgni"; break; } if(R.CoinFlip()){ B.Add(You("whisper") + " '" + invocation + "'. ",this); } else{ B.Add(You("scream") + " '" + invocation.ToUpper() + "'. ",this); } if(HasAttr(AttrType.SLIMED)){ B.Add("Nothing happens. ",this); } else{ B.Add("Flames erupt from " + the_name + ". ",this); AnimateExplosion(this,1,Color.RandomFire,'*'); ApplyBurning(); foreach(Tile t in TilesWithinDistance(1)){ t.ApplyEffect(DamageType.FIRE); if(t.actor() != null){ t.actor().ApplyBurning(); } } } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; case ActorType.GOBLIN_ARCHER: case ActorType.PHANTOM_ARCHER: switch(DistanceFrom(target)){ case 1: /*if(target.EnemiesAdjacent() > 1){ Attack(0,target); } else{*/ if(AI_Flee()){ QS(); } else{ Attack(0,target); } //} break; case 2: if(FirstActorInLine(target) == target){ FireArrow(target); } else{ if(AI_Flee()){ QS(); } else{ if(AI_Sidestep(target)){ B.Add(the_name + " tries to line up a shot. ",this); } QS(); } } break; case 3: case 4: case 5: case 6: case 7: case 8: if(FirstActorInLine(target) == target){ FireArrow(target); } else{ if(AI_Sidestep(target)){ B.Add(the_name + " tries to line up a shot. ",this); } QS(); } break; default: AI_Step(target); QS(); break; } break; case ActorType.GOBLIN_SHAMAN: { if(SilencedThisTurn()){ return; } if(DistanceFrom(target) == 1){ if(exhaustion > 50){ Attack(0,target); } else{ CastCloseRangeSpellOrAttack(target); } } else{ if(DistanceFrom(target) > 12){ AI_Step(target); QS(); } else{ if(FirstActorInLine(target) != target || R.CoinFlip()){ AI_Step(target); QS(); } else{ CastRangedSpellOrMove(target); } } } break; } case ActorType.PHASE_SPIDER: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ Tile t = target.TilesAtDistance(DistanceFrom(target)-1).Where(x=>x.passable && x.actor() == null).RandomOrDefault(); if(t != null){ Move(t.row,t.col); } QS(); } break; case ActorType.ZOMBIE: case ActorType.PHANTOM_ZOMBIE: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); if(DistanceFrom(target) == 1){ Attack(1,target); } else{ QS(); } } break; case ActorType.ROBED_ZEALOT: if(HasAttr(AttrType.COOLDOWN_3)){ if(DistanceFrom(target) <= 12 && HasLOS(target)){ target.AnimateExplosion(target,1,Color.Yellow,'*'); B.Add(YouVisible("smite") + " " + target.the_name + "! ",target); int amount = target.curhp / 10; bool still_alive = target.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,Math.Max(amount,1),this,"a zealot's wrath"); attrs[AttrType.COOLDOWN_3]--; attrs[AttrType.DETECTING_MONSTERS]--; if(!HasAttr(AttrType.COOLDOWN_3)){ B.Add(YouVisible("stop") + " praying. "); if(still_alive && target.EquippedWeapon.type != WeaponType.NO_WEAPON && !target.EquippedWeapon.status[EquipmentStatus.MERCIFUL]){ target.EquippedWeapon.status[EquipmentStatus.MERCIFUL] = true; B.Add(target.You("feel") + " a strange power enter " + target.Your() + " " + target.EquippedWeapon.NameWithoutEnchantment() + "! ",target); B.PrintAll(); Help.TutorialTip(TutorialTopic.Merciful); } } } else{ attrs[AttrType.COOLDOWN_3]--; attrs[AttrType.DETECTING_MONSTERS]--; } Q1(); } else{ if(!HasAttr(AttrType.COOLDOWN_1)){ attrs[AttrType.COOLDOWN_1] = maxhp; //initialize this value here instead of complicating the spawning code } if(DistanceFrom(target) <= 12 && !HasAttr(AttrType.COOLDOWN_2) && curhp < attrs[AttrType.COOLDOWN_1]){ //if the ability is ready and additional damage has been taken... RefreshDuration(AttrType.COOLDOWN_2,R.Between(11,13)*100); attrs[AttrType.COOLDOWN_1] = curhp; attrs[AttrType.COOLDOWN_3] = 4; attrs[AttrType.DETECTING_MONSTERS] = 4; B.Add(YouVisible("start") + " praying. "); B.Add(the_name + " points directly at you. ",this); Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } } /*if(HasAttr(AttrType.COOLDOWN_2)){ attrs[AttrType.COOLDOWN_2] = 0; B.Add(the_name + " finishes the prayer. ",this); if(DistanceFrom(target) == 1 && target.EquippedWeapon.type != WeaponType.NO_WEAPON){ target.EquippedWeapon.status[EquipmentStatus.MERCIFUL] = true; B.Add("You feel a strange power enter " + target.Your() + " " + target.EquippedWeapon.NameWithoutEnchantment() + "! ",target); B.PrintAll(); Help.TutorialTip(TutorialTopic.Merciful); } Q1(); } else{ if((maxhp / 5) * 4 > curhp && !HasAttr(AttrType.COOLDOWN_1)){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(14,16)*100); attrs[AttrType.COOLDOWN_2]++; B.Add(the_name + " starts praying. ",this); B.Add("A fiery halo appears above " + the_name + ". ",this); RefreshDuration(AttrType.RADIANT_HALO,R.Between(8,10)*100,Your() + " halo fades. ",this); Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } }*/ break; case ActorType.GIANT_SLUG: { if(DistanceFrom(target) == 1){ Attack(R.Between(0,1),target); } else{ if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12 && FirstActorInLine(target) == target){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(11,14)*100); B.Add(TheName(true) + " spits slime at " + target.the_name + ". ",target); List<Tile> slimed = GetBestLineOfEffect(target); List<Tile> added = new List<Tile>(); foreach(Tile t in slimed){ foreach(int dir in U.FourDirections){ Tile neighbor = t.TileInDirection(dir); if(R.OneIn(3) && neighbor.passable && !slimed.Contains(neighbor)){ added.AddUnique(neighbor); } } } slimed.AddRange(added); List<pos> cells = new List<pos>(); List<Actor> slimed_actors = new List<Actor>(); for(int i=0;slimed.Count > 0;++i){ List<Tile> removed = new List<Tile>(); foreach(Tile t in slimed){ if(DistanceFrom(t) == i){ t.AddFeature(FeatureType.SLIME); if(t.actor() != null && t.actor() != this && !t.actor().HasAttr(AttrType.SLIMED,AttrType.FROZEN)){ slimed_actors.Add(t.actor()); } removed.Add(t); if(DistanceFrom(t) > 0){ cells.Add(t.p); } } } foreach(Tile t in removed){ slimed.Remove(t); } if(cells.Count > 0){ Screen.AnimateMapCells(cells,new colorchar(',',Color.Green),20); } } M.Draw(); slimed_actors.AddUnique(target); foreach(Actor a in slimed_actors){ a.attrs[AttrType.SLIMED] = 1; a.attrs[AttrType.OIL_COVERED] = 0; a.RefreshDuration(AttrType.BURNING,0); B.Add(a.YouAre() + " covered in slime. ",a); } Q1(); } else{ AI_Step(target); if(tile().Is(FeatureType.SLIME)){ speed = 50; QS(); //normal speed is 150 speed = 150; } else{ QS(); } } } break; } case ActorType.BANSHEE: { if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(13,15)*100); if(player.CanSee(this)){ if(player.IsSilencedHere()){ B.Add(You("seem") + " to scream. ",this); } else{ B.Add(You("scream") + ". ",this); } } else{ if(!player.IsSilencedHere()){ B.Add("You hear a scream! "); } } if(!target.IsSilencedHere()){ if(target.ResistedBySpirit() || target.HasAttr(AttrType.MENTAL_IMMUNITY)){ B.Add(target.You("remain") + " courageous. ",target); } else{ B.Add(target.YouAre() + " terrified! ",target); RefreshDuration(AttrType.TERRIFYING,R.Between(5,8)*100,target.YouAre() + " no longer afraid. ",target); Help.TutorialTip(TutorialTopic.Afraid); } } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; } case ActorType.CAVERN_HAG: if(curhp < maxhp && HasAttr(AttrType.COOLDOWN_2) && !HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12){ B.Add(TheName(true) + " curses you! "); if(target.ResistedBySpirit()){ B.Add("You resist the curse. "); } else{ switch(R.Roll(4)){ case 1: //light allergy B.Add("You become allergic to light! "); target.RefreshDuration(AttrType.LIGHT_SENSITIVE,(R.Roll(2,20) + 70) * 100,"You are no longer allergic to light. "); break; case 2: //aggravate monsters B.Add("Every sound you make becomes amplified and echoes across the dungeon. "); target.RefreshDuration(AttrType.AGGRAVATING,(R.Roll(2,20) + 70) * 100,"Your sounds are no longer amplified. "); break; case 3: //cursed weapon B.Add("Your " + target.EquippedWeapon + " becomes stuck to your hand! "); target.EquippedWeapon.status[EquipmentStatus.STUCK] = true; Help.TutorialTip(TutorialTopic.Stuck); break; case 4: //heavy weapon B.Add("Your " + target.EquippedWeapon + " suddenly feels much heavier. "); target.EquippedWeapon.status[EquipmentStatus.HEAVY] = true; Help.TutorialTip(TutorialTopic.Heavy); break; } } attrs[AttrType.COOLDOWN_1]++; Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; case ActorType.BERSERKER: { if(HasAttr(AttrType.COOLDOWN_2)){ int dir = attrs[AttrType.COOLDOWN_2]; bool cw = R.CoinFlip(); if(TileInDirection(dir).passable && ActorInDirection(dir) == null && !MovementPrevented(TileInDirection(dir))){ B.Add(the_name + " leaps forward swinging his axe! ",this); Move(TileInDirection(dir).row,TileInDirection(dir).col); M.Draw(); for(int i=-1;i<=1;++i){ Screen.AnimateBoltProjectile(new List<Tile>{tile(),TileInDirection(dir.RotateDir(cw,i))},Color.Red,30); } for(int i=-1;i<=1;++i){ Actor a = ActorInDirection(dir.RotateDir(cw,i)); if(a != null){ B.Add(YourVisible() + " axe hits " + a.TheName(true) + ". ",this,a); a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(4,6),this,"a berserker's axe"); } TileInDirection(dir.RotateDir(cw,i)).Bump(dir.RotateDir(cw,i)); } Q1(); } else{ if(ActorInDirection(dir) != null || MovementPrevented(TileInDirection(dir)) || TileInDirection(dir).Is(TileType.STANDING_TORCH,TileType.BARREL,TileType.POISON_BULB)){ B.Add(the_name + " swings his axe furiously! ",this); for(int i=-1;i<=1;++i){ Screen.AnimateBoltProjectile(new List<Tile>{tile(),TileInDirection(dir.RotateDir(cw,i))},Color.Red,30); } for(int i=-1;i<=1;++i){ Actor a = ActorInDirection(dir.RotateDir(cw,i)); if(a != null){ B.Add(YourVisible() + " axe hits " + a.TheName(true) + ". ",this,a); a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(4,6),this,"a berserker's axe"); } TileInDirection(dir.RotateDir(cw,i)).Bump(dir.RotateDir(cw,i)); } Q1(); } else{ if(target != null && HasLOS(target)){ B.Add(the_name + " turns to face " + target.the_name + ". ",this); attrs[AttrType.COOLDOWN_2] = DirectionOf(target); Q1(); } } } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); if(target != null && R.Roll(3) == 3){ B.Add(the_name + " screams with fury! ",this); attrs[AttrType.COOLDOWN_2] = DirectionOf(target); Q.Add(new Event(this,350,AttrType.COOLDOWN_2,Your() + " rage diminishes. ",this)); } } else{ AI_Step(target); QS(); } } break; } case ActorType.DIRE_RAT: { bool slip_past = false; if(DistanceFrom(target) == 1){ foreach(Actor a in ActorsAtDistance(1)){ if(a.type == ActorType.DIRE_RAT && a.DistanceFrom(target) > this.DistanceFrom(target)){ bool can_walk = false; foreach(Tile t in a.TilesAtDistance(1)){ if(t.DistanceFrom(target) < a.DistanceFrom(target) && t.passable && t.actor() == null){ can_walk = true; break; } } if(!can_walk){ //there's a rat that would benefit from a space opening up - now check to see whether a move is possible foreach(Tile t in target.TilesAtDistance(1)){ if(t.passable && t.actor() == null){ slip_past = true; break; } } break; } } } } if(slip_past){ bool moved = false; foreach(Tile t in TilesAtDistance(1)){ if(t.DistanceFrom(target) == 1 && t.passable && t.actor() == null){ AI_Step(t); QS(); moved = true; break; } } if(!moved){ Tile t = target.TilesAtDistance(1).Where(x=>x.passable && x.actor() == null).RandomOrDefault(); if(t != null){ B.Add(TheName(true) + " slips past " + target.TheName(true) + ". ",this,target); Move(t.row,t.col); Q.Add(new Event(this,Speed() + 100,EventType.MOVE)); } else{ QS(); } } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; } case ActorType.SKULKING_KILLER: { if(HasAttr(AttrType.KEEPS_DISTANCE)){ bool try_to_hide = false; if(AI_Flee()){ try_to_hide = true; QS(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ //give up on fleeing, just attack attrs[AttrType.COOLDOWN_2] = 0; attrs[AttrType.KEEPS_DISTANCE] = 0; AI_Step(target); QS(); } } if(try_to_hide){ bool visible = player.CanSee(this); if(!R.OneIn(5) && (!player.HasLOE(this) || !visible || DistanceFrom(player) > 12)){ //just to add some uncertainty attrs[AttrType.COOLDOWN_2]++; if(attrs[AttrType.COOLDOWN_2] >= 3){ attrs[AttrType.KEEPS_DISTANCE] = 0; attrs[AttrType.COOLDOWN_2] = 0; if(!visible){ attrs[AttrType.TURNS_VISIBLE] = 0; } } } } } else{ if(DistanceFrom(target) == 1){ if(Attack(0,target)){ attrs[AttrType.KEEPS_DISTANCE] = 1; } } else{ AI_Step(target); QS(); } } /*if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 3 && R.OneIn(3) && HasLOE(target)){ attrs[AttrType.COOLDOWN_1]++; AnimateProjectile(target,Color.DarkYellow,'%'); Input.FlushInput(); if(target.CanSee(this)){ B.Add(the_name + " throws a bola at " + target.the_name + ". ",this,target); } else{ B.Add("A bola whirls toward " + target.the_name + ". ",this,target); } attrs[AttrType.TURNS_VISIBLE] = -1; target.RefreshDuration(AttrType.SLOWED,(R.Roll(3)+6)*100,target.YouAre() + " no longer slowed. ",target); B.Add(target.YouAre() + " slowed by the bola. ",target); Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } }*/ break; } case ActorType.WILD_BOAR: if(DistanceFrom(target) == 1){ Attack(0,target); if(HasAttr(AttrType.JUST_FLUNG)){ //if it just flung its target... attrs[AttrType.JUST_FLUNG] = 0; attrs[AttrType.COOLDOWN_1] = 0; } else{ //...otherwise it might prepare to fling again if(!HasAttr(AttrType.COOLDOWN_1)){ if(!HasAttr(AttrType.COOLDOWN_2) || R.OneIn(5)){ attrs[AttrType.COOLDOWN_2]++; B.Add(the_name + " lowers its head. ",this); attrs[AttrType.COOLDOWN_1]++; } } } } else{ AI_Step(target); if(!HasAttr(AttrType.COOLDOWN_2)){ attrs[AttrType.COOLDOWN_2]++; B.Add(the_name + " lowers its head. ",this); attrs[AttrType.COOLDOWN_1]++; } QS(); } break; case ActorType.DREAM_SPRITE: if(!HasAttr(AttrType.COOLDOWN_1)){ if(DistanceFrom(target) <= 12 && FirstActorInLine(target) == target){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(3,4)*100); bool visible = false; List<List<Tile>> lines = new List<List<Tile>>{GetBestLineOfEffect(target)}; if(group != null && group.Count > 0){ foreach(Actor a in group){ if(target == player && player.CanSee(a)){ visible = true; } if(a.type == ActorType.DREAM_SPRITE_CLONE){ a.attrs[AttrType.COOLDOWN_1]++; //for them, it means 'skip next turn' if(a.FirstActorInLine(target) == target){ lines.Add(a.GetBestLineOfEffect(target)); } } } } foreach(List<Tile> line in lines){ if(line.Count > 0){ line.RemoveAt(0); } } if(visible){ B.Add(the_name + " hits " + target.the_name + " with stinging magic. ",target); } else{ B.Add(TheName(true) + " hits " + target.the_name + " with stinging magic. ",target); } int max = lines.WhereGreatest(x=>x.Count)[0].Count; for(int i=0;i<max;++i){ List<pos> cells = new List<pos>(); foreach(List<Tile> line in lines){ if(line.Count > i){ cells.Add(line[i].p); } } Screen.AnimateMapCells(cells,new colorchar('*',Color.RandomRainbow)); } target.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(2,6),this,"a blast of fairy magic"); Q1(); } else{ if(DistanceFrom(target) > 12){ AI_Step(target); } else{ AI_Sidestep(target); } QS(); } } else{ if(DistanceFrom(target) > 5){ AI_Step(target); } else{ if(DistanceFrom(target) < 3){ AI_Flee(); } else{ Tile t = TilesAtDistance(1).Where(x=>x.passable && x.actor() == null).RandomOrDefault(); if(t != null){ AI_Step(t); } } } QS(); } break; case ActorType.DREAM_SPRITE_CLONE: if(HasAttr(AttrType.COOLDOWN_1)){ attrs[AttrType.COOLDOWN_1] = 0; Q1(); } else{ if(DistanceFrom(target) > 5){ AI_Step(target); } else{ if(DistanceFrom(target) < 3){ AI_Flee(); } else{ Tile t = TilesAtDistance(1).Where(x=>x.passable && x.actor() == null).RandomOrDefault(); if(t != null){ AI_Step(t); } } } QS(); } break; case ActorType.CLOUD_ELEMENTAL: { List<pos> cloud = M.tile.GetFloodFillPositions(p,false,x=>M.tile[x].features.Contains(FeatureType.FOG)); PhysicalObject[] objs = new PhysicalObject[cloud.Count + 1]; int idx = 0; foreach(pos p2 in cloud){ objs[idx++] = M.tile[p2]; } objs[idx] = this; List<colorchar> chars = new List<colorchar>(); colorchar cch = new colorchar('*',Color.RandomLightning); if(cloud.Contains(target.p)){ B.Add(the_name + " electrifies the cloud! ",objs); foreach(pos p2 in cloud){ if(M.actor[p2] != null && M.actor[p2] != this){ M.actor[p2].TakeDamage(DamageType.ELECTRIC,DamageClass.PHYSICAL,R.Roll(3,6),this,"*electrocuted by a cloud elemental"); } if(M.actor[p2] == this){ chars.Add(visual); } else{ chars.Add(cch); } } Screen.AnimateMapCells(cloud,chars,50); Q1(); } else{ if(DistanceFrom(target) == 1){ Tile t = TilesAtDistance(1).Where(x=>x.actor() == null && x.passable).RandomOrDefault(); if(t != null){ AI_Step(t); } QS(); } else{ if(R.OneIn(4)){ Tile t = TilesAtDistance(1).Where(x=>x.actor() == null && x.passable).RandomOrDefault(); if(t != null){ AI_Step(t); } QS(); } else{ AI_Step(target); QS(); } } } break; } case ActorType.DERANGED_ASCETIC: if(DistanceFrom(target) == 1){ Attack(R.Roll(3)-1,target); } else{ AI_Step(target); QS(); } break; case ActorType.SNEAK_THIEF: { if(DistanceFrom(target) <= 12 && !R.OneIn(3) && AI_UseRandomItem()){ Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); if(target != null){ List<Tile> valid_dirs = new List<Tile>(); foreach(Tile t in target.TilesAtDistance(1)){ if(t.passable && t.actor() == null && DistanceFrom(t) == 1){ valid_dirs.Add(t); } } if(valid_dirs.Count > 0){ AI_Step(valid_dirs.Random()); } } } else{ AI_Step(target); QS(); } } break; } case ActorType.WARG: { bool howl = false; if(DistanceFrom(target) == 1){ if(R.CoinFlip() || group == null || group.Count < 2 || HasAttr(AttrType.COOLDOWN_1)){ Attack(0,target); } else{ howl = true; } } else{ if(group == null || group.Count < 2 || HasAttr(AttrType.COOLDOWN_1)){ if(AI_Step(target)){ QS(); } else{ howl = true; } } else{ howl = true; } } if(howl){ if(group == null || group.Count < 2){ Q1(); break; } B.Add(TheName(true) + " howls. "); PosArray<int> paths = new PosArray<int>(ROWS,COLS); foreach(Actor packmate in group){ packmate.RefreshDuration(AttrType.COOLDOWN_1,2000); if(packmate != this){ var dijkstra = M.tile.GetDijkstraMap(new List<pos>{target.p},x=>!M.tile[x].passable,y=>M.actor[y] != null? 5 : paths[y]+1); if(!dijkstra[packmate.p].IsValidDijkstraValue()){ continue; } List<pos> new_path = new List<pos>(); pos p = packmate.p; while(!p.Equals(target.p)){ p = p.PositionsAtDistance(1,dijkstra).Where(x=>dijkstra[x].IsValidDijkstraValue()).WhereLeast(x=>dijkstra[x]).Random(); new_path.Add(p); paths[p]++; } packmate.path = new_path; } } Q1(); } break; } case ActorType.RUNIC_TRANSCENDENT: { if(SilencedThisTurn()){ return; } if(!HasSpell(SpellType.MERCURIAL_SPHERE)){ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } return; } if(curmp < 2){ B.Add(the_name + " absorbs mana from the universe. ",this); curmp = maxmp; Q1(); } else{ if(M.safetymap == null){ M.UpdateSafetyMap(player); } Tile t = TilesAtDistance(1).Where(x=>x.DistanceFrom(target) == 3 && x.passable && x.actor() == null).WhereLeast(x=>M.safetymap[x.p]).RandomOrDefault(); if(t != null){ //check safety map. if there's a safer spot at distance 3, step there. AI_Step(t); } else{ if(DistanceFrom(target) > 3){ AI_Step(target); } else{ if(DistanceFrom(target) < 3){ AI_Flee(); } } } if(DistanceFrom(target) <= 12 && FirstActorInLine(target) != null && FirstActorInLine(target).DistanceFrom(target) <= 3){ CastSpell(SpellType.MERCURIAL_SPHERE,target); } else{ QS(); } } break; } case ActorType.CARRION_CRAWLER: if(DistanceFrom(target) == 1){ if(!target.HasAttr(AttrType.PARALYZED)){ Attack(0,target); } else{ Attack(1,target); } } else{ AI_Step(target); QS(); } break; case ActorType.MECHANICAL_KNIGHT: if(attrs[AttrType.COOLDOWN_1] == 3){ //no head int dir = Global.RandomDirection(); if(R.CoinFlip()){ Actor a = ActorInDirection(dir); if(a != null){ if(!Attack(0,a)){ B.Add(the_name + " drops its guard! ",this); attrs[AttrType.MECHANICAL_SHIELD] = 0; } } else{ B.Add(the_name + " attacks empty space. ",this); TileInDirection(dir).Bump(dir); B.Add(the_name + " drops its guard! ",this); attrs[AttrType.MECHANICAL_SHIELD] = 0; Q1(); } } else{ Tile t = TileInDirection(dir); if(t.passable){ if(t.actor() == null){ AI_Step(t); QS(); } else{ B.Add(the_name + " bumps into " + t.actor().TheName(true) + ". ",this); QS(); } } else{ B.Add(the_name + " bumps into " + t.TheName(true) + ". ",this); t.Bump(DirectionOf(t)); QS(); } } } else{ if(DistanceFrom(target) == 1){ if(attrs[AttrType.COOLDOWN_1] == 1){ //no arms Attack(1,target); } else{ if(!Attack(0,target)){ B.Add(the_name + " drops its guard! ",this); attrs[AttrType.MECHANICAL_SHIELD] = 0; } } } else{ if(attrs[AttrType.COOLDOWN_1] != 2){ //no legs AI_Step(target); } QS(); } } break; case ActorType.ALASI_BATTLEMAGE: if(SilencedThisTurn()){ return; } if(DistanceFrom(target) > 12){ AI_Step(target); QS(); } else{ if(DistanceFrom(target) == 1){ if(exhaustion < 50){ CastCloseRangeSpellOrAttack(null,target,true); } else{ Attack(0,target); } } else{ CastRangedSpellOrMove(target); } } break; case ActorType.ALASI_SOLDIER: if(DistanceFrom(target) > 2){ AI_Step(target); QS(); attrs[AttrType.COMBO_ATTACK] = 0; } else{ if(FirstActorInLine(target) != null && !FirstActorInLine(target).name.Contains("alasi")){ //I had planned to make this attack possibly hit multiple targets, but not yet. Attack(0,target); } else{ if(AI_Step(target)){ QS(); } else{ AI_Sidestep(target); QS(); } attrs[AttrType.COMBO_ATTACK] = 0; } } break; case ActorType.SKITTERMOSS: if(DistanceFrom(target) == 1){ Attack(0,target); if(target != null && R.CoinFlip()){ //chance of retreating AI_Step(target,true); } } else{ if(R.CoinFlip()){ AI_Step(target); QS(); } else{ AI_Step(TileInDirection(Global.RandomDirection())); QS(); } } break; case ActorType.ALASI_SCOUT: { if(DistanceFrom(target) == 1){ Attack(0,target); } else{ if(curhp == maxhp){ if(FirstActorInLine(target) == target){ Attack(1,target); } else{ AI_Sidestep(target); QS(); } } else{ AI_Step(target); QS(); } } break; } case ActorType.MUD_ELEMENTAL: { int count = 0; int walls = 0; foreach(Tile t in target.TilesAtDistance(1)){ if(t.p.BoundsCheck(M.tile,false) && t.type == TileType.WALL){ ++walls; if(t.actor() == null){ ++count; } } } if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12 && count >= 2 || (count == 1 && walls == 1)){ RefreshDuration(AttrType.COOLDOWN_1,150); foreach(Tile t in target.TilesAtDistance(1)){ if(t.p.BoundsCheck(M.tile,false) && t.type == TileType.WALL && t.actor() == null){ Create(ActorType.MUD_TENTACLE,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent); M.actor[t.p].player_visibility_duration = -1; } } if(count >= 2){ if(player.CanSee(this)){ B.Add(the_name + " calls mud tentacles from the walls! "); } else{ B.Add("Mud tentacles emerge from the walls! "); } } else{ if(player.CanSee(this)){ B.Add(the_name + " calls a mud tentacle from the wall! "); } else{ B.Add("A mud tentacle emerges from the wall! "); } } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; } case ActorType.FLAMETONGUE_TOAD: { bool burrow = false; if((curhp * 3 <= maxhp || DistanceFrom(target) > 6) && R.CoinFlip()){ burrow = true; } if(DistanceFrom(target) <= 6 && DistanceFrom(target) > 1){ if(R.OneIn(20)){ burrow = true; } } if(burrow && !HasAttr(AttrType.COOLDOWN_1)){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(12,16)*100); if(curhp * 3 <= maxhp){ Burrow(TilesWithinDistance(6)); } else{ Burrow(GetCone(DirectionOf(target),6,true)); } } else{ if(!HasAttr(AttrType.COOLDOWN_2) && FirstActorInLine(target) != null && FirstActorInLine(target).DistanceFrom(target) <= 1){ RefreshDuration(AttrType.COOLDOWN_2,R.Between(10,14)*100); Actor first = FirstActorInLine(target); B.Add(TheName(true) + " breathes fire! ",this,first); AnimateProjectile(first,'*',Color.RandomFire); AnimateExplosion(first,1,'*',Color.RandomFire); foreach(Tile t in GetBestLineOfEffect(first)){ t.ApplyEffect(DamageType.FIRE); } foreach(Tile t in first.TilesWithinDistance(1)){ t.ApplyEffect(DamageType.FIRE); if(t.actor() != null){ t.actor().ApplyBurning(); } } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } } break; } case ActorType.ENTRANCER: if(group == null){ if(AI_Flee()){ QS(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ QS(); } } } else{ Actor thrall = group[1]; if(CanSee(thrall) && HasLOE(thrall)){ //cooldown 1 is teleport. cooldown 2 is shield. //if the thrall is visible and you have LOE, the next goal is for the entrancer to be somewhere on the line that starts at the target and extends through the thrall. List<Tile> line_from_target = target.GetBestExtendedLineOfEffect(thrall); bool on_line = line_from_target.Contains(tile()); bool space_near_target = line_from_target.Count > 1 && line_from_target[1].passable && line_from_target[1].actor() == null; if(on_line && DistanceFrom(target) > thrall.DistanceFrom(target)){ if(!HasAttr(AttrType.COOLDOWN_2) && thrall.curhp <= thrall.maxhp / 2){ //check whether you can shield it, if the thrall is low on health. RefreshDuration(AttrType.COOLDOWN_2,1500); B.Add(TheName(true) + " shields " + thrall.TheName(true) + ". ",this,thrall); B.DisplayNow(); Screen.AnimateStorm(thrall.p,1,2,5,'*',Color.White); thrall.attrs[AttrType.SHIELDED] = 1; Q1(); } else{ //check whether you can teleport the thrall closer. if(!HasAttr(AttrType.COOLDOWN_1) && thrall.DistanceFrom(target) > 1 && space_near_target){ Tile dest = line_from_target[1]; RefreshDuration(AttrType.COOLDOWN_1,400); B.Add(TheName(true) + " teleports " + thrall.TheName(true) + ". ",this,thrall); M.Draw(); thrall.Move(dest.row,dest.col); B.DisplayNow(); Screen.AnimateStorm(dest.p,1,1,4,thrall.symbol,thrall.color); foreach(Tile t2 in thrall.GetBestLineOfEffect(dest)){ Screen.AnimateStorm(t2.p,1,1,4,thrall.symbol,thrall.color); } Q1(); } else{ //check whether you can shield it, if the thrall isn't low on health. if(!HasAttr(AttrType.COOLDOWN_2)){ RefreshDuration(AttrType.COOLDOWN_2,1500); B.Add(TheName(true) + " shields " + thrall.TheName(true) + ". ",this,thrall); B.DisplayNow(); Screen.AnimateStorm(thrall.p,1,2,5,'*',Color.White); thrall.attrs[AttrType.SHIELDED] = 1; Q1(); } else{ //check whether you are adjacent to thrall and can step away while remaining on line. List<Tile> valid = line_from_target.Where(x=>DistanceFrom(x) == 1 && x.actor() == null && x.passable); if(DistanceFrom(thrall) == 1 && valid.Count > 0){ AI_Step(valid.Random()); } QS(); } } } } else{ if(on_line){ //if on the line but not behind the thrall, we might be able to swap places or teleport if(DistanceFrom(thrall) == 1){ Move(thrall.row,thrall.col); QS(); } else{ Tile dest = null; foreach(Tile t in line_from_target){ if(t.passable && t.actor() == null){ dest = t; break; } } if(dest != null){ RefreshDuration(AttrType.COOLDOWN_1,400); B.Add(TheName(true) + " teleports " + thrall.TheName(true) + ". ",this,thrall); M.Draw(); thrall.Move(dest.row,dest.col); B.DisplayNow(); Screen.AnimateStorm(dest.p,1,1,4,thrall.symbol,thrall.color); foreach(Tile t2 in thrall.GetBestLineOfEffect(dest)){ Screen.AnimateStorm(t2.p,1,1,4,thrall.symbol,thrall.color); } } Q1(); } } else{ //if there's a free adjacent space on the line and behind the thrall, step there. List<Tile> valid = line_from_target.From(thrall).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(this) == 1); if(valid.Count > 0){ AI_Step(valid.Random()); QS(); } else{ //if you can teleport and there's a free tile on the line between you and the target, teleport the thrall there. List<Tile> valid_between = GetBestLineOfEffect(target).Where(x=>x.passable && x.actor() == null && thrall.HasLOE(x)); if(!HasAttr(AttrType.COOLDOWN_1) && valid_between.Count > 0){ Tile dest = valid_between.Random(); RefreshDuration(AttrType.COOLDOWN_1,400); B.Add(TheName(true) + " teleports " + thrall.TheName(true) + ". ",this,thrall); M.Draw(); thrall.Move(dest.row,dest.col); B.DisplayNow(); Screen.AnimateStorm(dest.p,1,1,4,thrall.symbol,thrall.color); foreach(Tile t2 in thrall.GetBestLineOfEffect(dest)){ Screen.AnimateStorm(t2.p,1,1,4,thrall.symbol,thrall.color); } Q1(); } else{ //step toward a tile on the line (and behind the thrall) List<Tile> valid_behind_thrall = line_from_target.From(thrall).Where(x=>x.passable && x.actor() == null); if(valid_behind_thrall.Count > 0){ AI_Step(valid_behind_thrall.Random()); } QS(); } } } } //the old code: /*if(DistanceFrom(target) < thrall.DistanceFrom(target) && DistanceFrom(thrall) == 1){ Move(thrall.row,thrall.col); QS(); } else{ if(DistanceFrom(target) == 1 && curhp < maxhp){ List<Tile> safe = TilesAtDistance(1).Where(t=>t.passable && t.actor() == null && target.GetBestExtendedLineOfEffect(thrall).Contains(t)); if(DistanceFrom(thrall) == 1 && safe.Count > 0){ AI_Step(safe.Random()); QS(); } else{ if(AI_Flee()){ QS(); } else{ Attack(0,target); } } } else{ if(!HasAttr(AttrType.COOLDOWN_1) && (thrall.DistanceFrom(target) > 1 || !target.GetBestExtendedLineOfEffect(thrall).Any(t=>t.actor()==this))){ //the entrancer tries to be smart about placing the thrall in a position that blocks ranged attacks List<Tile> closest = new List<Tile>(); int dist = 99; foreach(Tile t in thrall.TilesWithinDistance(2).Where(x=>x.passable && (x.actor()==null || x.actor()==thrall))){ if(t.DistanceFrom(target) < dist){ closest.Clear(); closest.Add(t); dist = t.DistanceFrom(target); } else{ if(t.DistanceFrom(target) == dist){ closest.Add(t); } } } List<Tile> in_line = new List<Tile>(); foreach(Tile t in closest){ if(target.GetBestExtendedLineOfEffect(t).Any(x=>x.actor()==this)){ in_line.Add(t); } } Tile tile2 = null; if(in_line.Count > 0){ tile2 = in_line.Random(); } else{ if(closest.Count > 0){ tile2 = closest.Random(); } } if(tile2 != null && tile2.actor() != thrall){ GainAttr(AttrType.COOLDOWN_1,400); B.Add(TheName(true) + " teleports " + thrall.TheName(true) + ". ",this,thrall); M.Draw(); thrall.Move(tile2.row,tile2.col); B.DisplayNow(); Screen.AnimateStorm(tile2.p,1,1,4,thrall.symbol,thrall.color); foreach(Tile t2 in thrall.GetBestLineOfEffect(tile2)){ Screen.AnimateStorm(t2.p,1,1,4,thrall.symbol,thrall.color); } Q1(); } else{ List<Tile> safe = target.GetBestExtendedLineOfEffect(thrall).Where(t=>t.passable && t.actor() == null && t.DistanceFrom(target) > thrall.DistanceFrom(target)).WhereLeast(t=>DistanceFrom(t)); if(safe.Count > 0){ if(safe.Any(t=>t.DistanceFrom(target) > 2)){ AI_Step(safe.Where(t=>t.DistanceFrom(target) > 2).Random()); } else{ AI_Step(safe.Random()); } } QS(); } } else{ if(!HasAttr(AttrType.COOLDOWN_2) && thrall.attrs[AttrType.ARCANE_SHIELDED] < 25){ GainAttr(AttrType.COOLDOWN_2,1500); B.Add(TheName(true) + " shields " + thrall.TheName(true) + ". ",this,thrall); B.DisplayNow(); Screen.AnimateStorm(thrall.p,1,2,5,'*',Color.White); thrall.attrs[AttrType.ARCANE_SHIELDED] = 25; Q1(); } else{ List<Tile> safe = target.GetBestExtendedLineOfEffect(thrall).Where(t=>t.passable && t.actor() == null).WhereLeast(t=>DistanceFrom(t)); if(safe.Count > 0){ if(safe.Any(t=>t.DistanceFrom(target) > 2)){ AI_Step(safe.Where(t=>t.DistanceFrom(target) > 2).Random()); } else{ AI_Step(safe.Random()); } } QS(); } } } }*/ } else{ group[1].FindPath(this); //call for help if(AI_Flee()){ QS(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ QS(); } } } } break; case ActorType.ORC_GRENADIER: if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 8){ attrs[AttrType.COOLDOWN_1]++; Q.Add(new Event(this,(R.Roll(2)*100)+150,AttrType.COOLDOWN_1)); B.Add(TheName(true) + " tosses a grenade toward " + target.the_name + ". ",target); List<Tile> tiles = new List<Tile>(); foreach(Tile tile in target.TilesWithinDistance(1)){ if(tile.passable && !tile.Is(FeatureType.GRENADE)){ tiles.Add(tile); } } Tile t = tiles[R.Roll(tiles.Count)-1]; if(t.actor() != null){ if(t.actor() == player){ B.Add("It lands under you! "); } else{ B.Add("It lands under " + t.actor().the_name + ". ",t.actor()); } } else{ if(t.inv != null){ B.Add("It lands under " + t.inv.TheName() + ". ",t); } } t.features.Add(FeatureType.GRENADE); Q.Add(new Event(t,100,EventType.GRENADE)); Q1(); } else{ if(curhp <= 18){ if(AI_Step(target,true)){ QS(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ QS(); } } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } } break; case ActorType.MARBLE_HORROR: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } break; case ActorType.SPELLMUDDLE_PIXIE: if(DistanceFrom(target) == 1){ Attack(0,target); if(target != null && R.CoinFlip()){ AI_Step(target,true); } } else{ AI_Step(target); QS(); } break; case ActorType.OGRE_BARBARIAN: //if has grabbed target, check for open spaces near the opposite side. //if one is found, slam target into that tile, then do the attack. //otherwise, slam target into a solid tile (target doesn't move), then attack. //if nothing is grabbed yet, just keep attacking. if(DistanceFrom(target) == 1){ if(target.HasAttr(AttrType.GRABBED) && attrs[AttrType.GRABBING] == DirectionOf(target) && !target.MovementPrevented(tile())){ Tile t = null; Tile opposite = TileInDirection(DirectionOf(target).RotateDir(true,4)); if(opposite.passable && opposite.actor() == null){ t = opposite; } if(t == null){ List<Tile> near_opposite = new List<Tile>(); foreach(int i in new int[]{-1,1}){ Tile near = TileInDirection(DirectionOf(target).RotateDir(true,4+i)); if(near.passable && near.actor() == null){ near_opposite.Add(near); } } if(near_opposite.Count > 0){ t = near_opposite.Random(); } } if(t != null){ target.attrs[AttrType.TURN_INTO_CORPSE]++; Attack(1,target); target.Move(t.row,t.col); target.CollideWith(target.tile()); target.CorpseCleanup(); } else{ target.attrs[AttrType.TURN_INTO_CORPSE]++; Attack(1,target); target.CollideWith(target.tile()); target.CorpseCleanup(); } } else{ Attack(0,target); } } else{ if(speed == 100){ speed = 50; } if(!HasAttr(AttrType.COOLDOWN_1) && target == player && player.CanSee(this)){ B.Add(the_name + " charges! "); attrs[AttrType.COOLDOWN_1] = 1; } AI_Step(target); if(!HasAttr(AttrType.COOLDOWN_1) && target == player && player.CanSee(this)){ //check twice so the message appears ASAP B.Add(the_name + " charges! "); attrs[AttrType.COOLDOWN_1] = 1; } QS(); } break; case ActorType.MARBLE_HORROR_STATUE: QS(); break; case ActorType.PYREN_ARCHER: //still considering some sort of fire trail movement ability for this guy switch(DistanceFrom(target)){ case 1: if(target.EnemiesAdjacent() > 1){ Attack(0,target); } else{ if(AI_Flee()){ QS(); } else{ Attack(0,target); } } break; case 2: if(FirstActorInLine(target) == target){ FireArrow(target); } else{ if(AI_Flee()){ QS(); } else{ if(AI_Sidestep(target)){ B.Add(the_name + " tries to line up a shot. ",this); } QS(); } } break; case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: if(FirstActorInLine(target) == target){ FireArrow(target); } else{ if(AI_Sidestep(target)){ B.Add(the_name + " tries to line up a shot. ",this); } QS(); } break; default: AI_Step(target); QS(); break; } break; case ActorType.CYCLOPEAN_TITAN: { if(DistanceFrom(target) == 1){ Attack(0,target); } else{ if(DistanceFrom(target) > 2 && DistanceFrom(target) <= 12 && R.OneIn(15) && FirstActorInLine(target) == target){ B.Add(TheName(true) + " lobs a huge rock! ",this,target); AnimateProjectile(target,'*',Color.Gray); pos tp = target.p; int plus_to_hit = -target.TotalSkill(SkillType.DEFENSE)*3; if(target.IsHit(plus_to_hit)){ B.Add("It hits " + target.the_name + "! ",target); if(target.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(4,6),this,"a cyclopean titan's rock")){ if(R.OneIn(8)){ target.ApplyStatus(AttrType.STUNNED,R.Between(3,4)*100); } } } else{ int armor_value = target.TotalProtectionFromArmor(); if(target != player){ armor_value = target.TotalSkill(SkillType.DEFENSE); //if monsters have Defense skill, it's from armor } int roll = R.Roll(25 - plus_to_hit); if(roll <= armor_value * 3){ B.Add(target.Your() + " armor blocks it! ",target); } else{ if(target.HasAttr(AttrType.ROOTS) && roll <= (armor_value + 10) * 3){ //potion of roots gives 10 defense B.Add(target.Your() + " root shell blocks it! ",target); } else{ B.Add(target.You("avoid") + " it! ",target); } } } foreach(pos neighbor in tp.PositionsWithinDistance(1,M.tile)){ Tile t = M.tile[neighbor]; if(t.Is(TileType.FLOOR) && R.OneIn(4)){ t.Toggle(null,TileType.GRAVEL); } } Q1(); } else{ bool smashed = false; if(DistanceFrom(target) == 2 && !HasLOE(target)){ Tile t = FirstSolidTileInLine(target); if(t != null && !t.passable){ smashed = true; B.Add(You("smash",true) + " through " + t.TheName(true) + "! ",t); foreach(int dir in DirectionOf(t).GetArc(1)){ TileInDirection(dir).Smash(dir); } Move(t.row,t.col); QS(); } } if(!smashed){ AI_Step(target); QS(); } } } break; } case ActorType.ALASI_SENTINEL: if(DistanceFrom(target) == 1){ Attack(0,target); if(HasAttr(AttrType.JUST_FLUNG)){ attrs[AttrType.JUST_FLUNG] = 0; } else{ if(target != null){ List<Tile> valid_dirs = new List<Tile>(); foreach(Tile t in target.TilesAtDistance(1)){ if(t.passable && t.actor() == null && DistanceFrom(t) == 1){ valid_dirs.Add(t); } } if(valid_dirs.Count > 0){ AI_Step(valid_dirs.Random()); } } } } else{ AI_Step(target); QS(); } break; case ActorType.NOXIOUS_WORM: if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12 && HasLOE(target)){ B.Add(TheName(true) + " breathes poisonous gas. "); List<Tile> area = new List<Tile>(); foreach(Tile t in target.TilesWithinDistance(1)){ if(t.passable && target.HasLOE(t)){ t.AddFeature(FeatureType.POISON_GAS); area.Add(t); } } List<Tile> area2 = target.tile().AddGaseousFeature(FeatureType.POISON_GAS,8); area.AddRange(area2); Event.RemoveGas(area,600,FeatureType.POISON_GAS,18); RefreshDuration(AttrType.COOLDOWN_1,(R.Roll(6) + 18) * 100); Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; case ActorType.LASHER_FUNGUS: { if(DistanceFrom(target) <= 12){ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ if(FirstActorInLine(target) == target){ List<Tile> line = GetBestLineOfEffect(target.row,target.col); line.Remove(line[line.Count-1]); AnimateBoltBeam(line,Color.DarkGreen); pos target_p = target.p; if(Attack(1,target) && M.actor[target_p] != null){ target = M.actor[target_p]; int rowchange = 0; int colchange = 0; if(target.row < row){ rowchange = 1; } if(target.row > row){ rowchange = -1; } if(target.col < col){ colchange = 1; } if(target.col > col){ colchange = -1; } if(!target.AI_MoveOrOpen(target.row+rowchange,target.col+colchange)){ bool moved = false; if(Math.Abs(target.row - row) > Math.Abs(target.col - col)){ if(target.AI_Step(M.tile[row,target.col])){ moved = true; } } else{ if(Math.Abs(target.row - row) < Math.Abs(target.col - col)){ if(target.AI_Step(M.tile[target.row,col])){ moved = true; } } else{ if(target.AI_Step(this)){ moved = true; } } } if(!moved){ //todo: this still isn't ideal. maybe I need an AI_Step that only considers 3 directions - right now, it'll make you move even if it isn't closer. B.Add(target.You("do",true) + "n't move far. ",target); } } } } else{ Q1(); } } } else{ Q1(); } break; } case ActorType.LUMINOUS_AVENGER: { if(DistanceFrom(target) <= 3){ List<Tile> ext = GetBestExtendedLineOfEffect(target); int max_count = Math.Min(5,ext.Count); //look 4 spaces away unless the line is even shorter than that. List<Actor> targets = new List<Actor>(); Tile destination = null; for(int i=0;i<max_count;++i){ Tile t = ext[i]; if(t.passable){ if(t.actor() == null){ if(targets.Contains(target)){ destination = t; } } else{ if(t.actor() != this){ targets.Add(t.actor()); } } } else{ break; } } if(destination != null){ Move(destination.row,destination.col); foreach(Tile t in ext.To(destination)){ colorchar cch = M.VisibleColorChar(t.row,t.col); cch.bgcolor = Color.Yellow; if(Global.LINUX && !Screen.GLMode){ cch.bgcolor = Color.DarkYellow; } if(cch.color == cch.bgcolor){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); Game.GLUpdate(); Thread.Sleep(15); } foreach(Actor a in targets){ Attack(0,a,true); } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } } else{ AI_Step(target); QS(); } break; } case ActorType.VAMPIRE: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ if(DistanceFrom(target) <= 12){ if(tile().IsLit() && !HasAttr(AttrType.COOLDOWN_1)){ attrs[AttrType.COOLDOWN_1]++; B.Add(the_name + " gestures. ",this); List<Tile> tiles = new List<Tile>(); foreach(Tile t in target.TilesWithinDistance(6)){ if(t.passable && t.actor() == null && DistanceFrom(t) >= DistanceFrom(target) && target.HasLOS(t) && target.HasLOE(t)){ tiles.Add(t); } } if(tiles.Count == 0){ foreach(Tile t in target.TilesWithinDistance(6)){ //same, but with no distance requirement if(t.passable && t.actor() == null && target.HasLOS(t) && target.HasLOE(t)){ tiles.Add(t); } } } if(tiles.Count == 0){ B.Add("Nothing happens. ",this); } else{ if(tiles.Count == 1){ B.Add("A blood moth appears! "); } else{ B.Add("Blood moths appear! "); } for(int i=0;i<2;++i){ if(tiles.Count > 0){ Tile t = tiles.RemoveRandom(); Create(ActorType.BLOOD_MOTH,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent); M.actor[t.row,t.col].player_visibility_duration = -1; } } } Q1(); } else{ AI_Step(target); QS(); } } else{ AI_Step(target); QS(); } } break; case ActorType.ORC_WARMAGE: { if(SilencedThisTurn()){ return; } switch(DistanceFrom(target)){ case 1: { List<SpellType> close_range = new List<SpellType>(); close_range.Add(SpellType.MAGIC_HAMMER); close_range.Add(SpellType.MAGIC_HAMMER); close_range.Add(SpellType.BLINK); if(target.EnemiesAdjacent() > 1 || R.CoinFlip()){ CastCloseRangeSpellOrAttack(close_range,target,false); } else{ if(AI_Step(target,true)){ QS(); } else{ CastCloseRangeSpellOrAttack(close_range,target,false); } } break; } case 2: if(R.CoinFlip()){ if(AI_Step(target,true)){ QS(); } else{ if(FirstActorInLine(target) == target){ CastRangedSpellOrMove(target); } else{ AI_Sidestep(target); QS(); } } } else{ if(FirstActorInLine(target) == target){ CastRangedSpellOrMove(target); } else{ if(AI_Step(target,true)){ QS(); } else{ AI_Sidestep(target); QS(); } } } break; case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: if(FirstActorInLine(target) == target){ CastRangedSpellOrMove(target); } else{ AI_Sidestep(target); QS(); } break; default: AI_Step(target); QS(); break; } break; } case ActorType.NECROMANCER: { if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12){ attrs[AttrType.COOLDOWN_1]++; Q.Add(new Event(this,(R.Roll(4)+8)*100,AttrType.COOLDOWN_1)); B.Add(the_name + " calls out to the dead. ",this); ActorType summon = R.CoinFlip()? ActorType.SKELETON : ActorType.ZOMBIE; List<Tile> tiles = new List<Tile>(); foreach(Tile tile in TilesWithinDistance(2)){ if(tile.passable && tile.actor() == null && DirectionOf(tile) == DirectionOf(target)){ tiles.Add(tile); } } if(tiles.Count == 0){ foreach(Tile tile in TilesWithinDistance(2)){ if(tile.passable && tile.actor() == null){ tiles.Add(tile); } } } if(tiles.Count == 0 || (group != null && group.Count > 3)){ B.Add("Nothing happens. ",this); } else{ Tile t = tiles.Random(); B.Add(Prototype(summon).a_name + " digs through the floor! "); Create(summon,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent); M.actor[t.row,t.col].player_visibility_duration = -1; if(group == null){ group = new List<Actor>{this}; } group.Add(M.actor[t.row,t.col]); M.actor[t.row,t.col].group = group; } Q1(); } else{ bool blast = false; switch(DistanceFrom(target)){ case 1: if(AI_Step(target,true)){ QS(); } else{ Attack(0,target); } break; case 2: if(R.CoinFlip() && FirstActorInLine(target) == target){ blast = true; } else{ if(AI_Step(target,true)){ QS(); } else{ blast = true; } } break; case 3: case 4: case 5: case 6: if(FirstActorInLine(target) == target){ blast = true; } else{ AI_Sidestep(target); QS(); } break; default: AI_Step(target); QS(); break; } if(blast){ B.Add(TheName(true) + " fires dark energy at " + target.TheName(true) + ". ",this,target); AnimateBoltProjectile(target,Color.DarkBlue); if(target.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(6),this,"*blasted by a necromancer")){ target.IncreaseExhaustion(R.Roll(3)); } Q1(); } } break; } case ActorType.STALKING_WEBSTRIDER: { bool burrow = false; if(DistanceFrom(target) >= 2 && DistanceFrom(target) <= 6){ if(R.CoinFlip() && !target.tile().Is(FeatureType.WEB)){ burrow = true; } } if((DistanceFrom(target) > 6 || target.HasAttr(AttrType.POISONED))){ burrow = true; } if(burrow && !HasAttr(AttrType.COOLDOWN_1)){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(5,8)*100); if(DistanceFrom(target) <= 2){ Burrow(TilesWithinDistance(6)); } else{ Burrow(GetCone(DirectionOf(target),6,true)); } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; } case ActorType.ORC_ASSASSIN: if(DistanceFrom(target) > 2 && attrs[AttrType.TURNS_VISIBLE] < 0){ Tile t = TilesAtDistance(1).Where(x=>x.passable && x.actor() == null && target.DistanceFrom(x) == target.DistanceFrom(this)-1 && !target.CanSee(x)).RandomOrDefault(); if(t != null){ AI_Step(t); FindPath(target); //so it won't forget where the target is... QS(); } else{ AI_Step(target); QS(); } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); if(DistanceFrom(target) == 1){ Attack(1,target); } else{ QS(); } } } break; case ActorType.MACHINE_OF_WAR: { if(attrs[AttrType.COOLDOWN_1] % 2 == 0){ //the machine of war moves on even turns and fires on odd turns. AI_Step(target); QS(); } else{ if(DistanceFrom(target) <= 12 && FirstActorInLine(target) == target){ B.Add(TheName(true) + " fires a stream of scalding oil at " + target.the_name + ". ",target); List<Tile> covered_in_oil = GetBestLineOfEffect(target); List<Tile> added = new List<Tile>(); foreach(Tile t in covered_in_oil){ foreach(int dir in U.FourDirections){ Tile neighbor = t.TileInDirection(dir); if(R.OneIn(3) && neighbor.passable && !covered_in_oil.Contains(neighbor)){ added.AddUnique(neighbor); } } } covered_in_oil.AddRange(added); List<pos> cells = new List<pos>(); List<Actor> oiled_actors = new List<Actor>(); for(int i=0;covered_in_oil.Count > 0;++i){ List<Tile> removed = new List<Tile>(); foreach(Tile t in covered_in_oil){ if(DistanceFrom(t) == i){ t.AddFeature(FeatureType.OIL); if(t.actor() != null && t.actor() != this){ oiled_actors.Add(t.actor()); } removed.Add(t); if(DistanceFrom(t) > 0){ cells.Add(t.p); } } } foreach(Tile t in removed){ covered_in_oil.Remove(t); } if(cells.Count > 0){ Screen.AnimateMapCells(cells,new colorchar(',',Color.DarkYellow),20); } } oiled_actors.AddUnique(target); M.Draw(); foreach(Actor a in oiled_actors){ if(a.TakeDamage(DamageType.FIRE,DamageClass.PHYSICAL,R.Roll(4,6),this,"a stream of scalding oil")){ if(a.IsBurning()){ a.ApplyBurning(); } else{ if(!a.HasAttr(AttrType.SLIMED,AttrType.FROZEN)){ a.attrs[AttrType.OIL_COVERED]++; B.Add(a.YouAre() + " covered in oil. ",a); } } } } Q1(); } else{ Q1(); } } break; } case ActorType.IMPOSSIBLE_NIGHTMARE: { if(DistanceFrom(target) == 1){ Attack(0,target); } else{ Tile t = target.TilesAtDistance(DistanceFrom(target)-1).Where(x=>x.passable && x.actor() == null).RandomOrDefault(); if(t != null){ Move(t.row,t.col); //todo: fear effect? } QS(); } break; } case ActorType.FIRE_DRAKE: /*if(player.magic_trinkets.Contains(MagicTrinketType.RING_OF_RESISTANCE) && DistanceFrom(player) <= 12 && CanSee(player)){ B.Add(the_name + " exhales an orange mist toward you. "); foreach(Tile t in GetBestLineOfEffect(player)){ Screen.AnimateStorm(t.p,1,2,3,'*',Color.Red); } B.Add("Your ring of resistance melts and drips onto the floor! "); player.magic_trinkets.Remove(MagicTrinketType.RING_OF_RESISTANCE); Q.Add(new Event(this,100,EventType.MOVE)); } else{ if(player.EquippedArmor == ArmorType.FULL_PLATE_OF_RESISTANCE && DistanceFrom(player) <= 12 && CanSee(player)){ B.Add(the_name + " exhales an orange mist toward you. "); foreach(Tile t in GetBestLine(player)){ Screen.AnimateStorm(t.p,1,2,3,'*',Color.Red); } B.Add("The runes drip from your full plate of resistance! "); player.EquippedArmor = ArmorType.FULL_PLATE; player.UpdateOnEquip(ArmorType.FULL_PLATE_OF_RESISTANCE,ArmorType.FULL_PLATE); Q.Add(new Event(this,100,EventType.MOVE)); } else{*/ if(!HasAttr(AttrType.COOLDOWN_1)){ if(DistanceFrom(target) <= 12){ attrs[AttrType.COOLDOWN_1]++; int cooldown = (R.Roll(1,4)+1) * 100; Q.Add(new Event(this,cooldown,AttrType.COOLDOWN_1)); AnimateBeam(target,Color.RandomFire,'*'); B.Add(TheName(true) + " breathes fire. ",target); target.TakeDamage(DamageType.FIRE,DamageClass.PHYSICAL,R.Roll(6,6),this,"*roasted by fire breath"); target.ApplyBurning(); Q.Add(new Event(this,200,EventType.MOVE)); } else{ AI_Step(target); QS(); } } else{ if(DistanceFrom(target) == 1){ Attack(R.Roll(1,2)-1,target); } else{ AI_Step(target); QS(); } } //} //} break; case ActorType.GHOST: { attrs[AttrType.AGGRESSION_MESSAGE_PRINTED] = 1; bool tombstone = false; foreach(Tile t in TilesWithinDistance(1)){ if(t.type == TileType.TOMBSTONE){ tombstone = true; } } if(!tombstone){ B.Add("The ghost vanishes. ",this); Kill(); return; } if(target == null || DistanceFrom(target) > 2){ List<Tile> valid = TilesAtDistance(1).Where(x=>x.TilesWithinDistance(1).Any(y=>y.type == TileType.TOMBSTONE)); if(valid.Count > 0){ AI_Step(valid.Random()); } QS(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ List<Tile> valid = tile().NeighborsBetween(target.row,target.col).Where(x=>x.passable && x.actor() == null && x.TilesWithinDistance(1).Any(y=>y.type == TileType.TOMBSTONE)); if(valid.Count == 0){ valid = TilesAtDistance(1).Where(x=>x.TilesWithinDistance(1).Any(y=>y.type == TileType.TOMBSTONE)); } if(valid.Count > 0){ AI_Step(valid.Random()); } QS(); } } break; } case ActorType.BLADE: { attrs[AttrType.AGGRESSION_MESSAGE_PRINTED] = 1; List<Actor> valid_targets = new List<Actor>(); //this is based on EnragedMove(), with an exception for other blades int max_dist = Math.Max(Math.Max(row,col),Math.Max(ROWS-row,COLS-col)); //this should find the farthest edge of the map for(int i=1;i<max_dist && valid_targets.Count == 0;++i){ foreach(Actor a in ActorsAtDistance(i)){ if(a.type != ActorType.BLADE && CanSee(a) && HasLOE(a)){ valid_targets.Add(a); } } } if(valid_targets.Count > 0){ if(target == null || !valid_targets.Contains(target)){ //keep old target if possible target = valid_targets.Random(); } if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } else{ if(target != null){ SeekAI(); } else{ QS(); } } break; } case ActorType.PHANTOM_CONSTRICTOR: case ActorType.PHANTOM_WASP: { if(DistanceFrom(target) == 1){ Attack(0,target); } else{ List<Tile> tiles = new List<Tile>(); //i should turn this "slither" movement into a standardized attribute or something if(target.row == row || target.col == col){ int targetdir = DirectionOf(target); for(int i=-1;i<=1;++i){ pos adj = p.PosInDir(targetdir.RotateDir(true,i)); if(M.tile[adj].passable && M.actor[adj] == null){ tiles.Add(M.tile[adj]); } } } if(tiles.Count > 0){ AI_Step(tiles.Random()); } else{ AI_Step(target); } QS(); } break; } case ActorType.MINOR_DEMON: case ActorType.FROST_DEMON: case ActorType.BEAST_DEMON: case ActorType.DEMON_LORD: { int damage_threshold = 1; if(type == ActorType.BEAST_DEMON){ damage_threshold = 0; } if(target == player && attrs[AttrType.COOLDOWN_2] > damage_threshold && CanSee(target)){ switch(type){ case ActorType.MINOR_DEMON: case ActorType.BEAST_DEMON: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } break; case ActorType.FROST_DEMON: if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12 && FirstActorInLine(target) == target){ attrs[AttrType.COOLDOWN_1] = 1; AnimateProjectile(target,'*',Color.RandomIce); foreach(Tile t in GetBestLineOfEffect(target)){ t.ApplyEffect(DamageType.COLD); } B.Add(TheName(true) + " fires a chilling sphere. ",target); if(target.TakeDamage(DamageType.COLD,DamageClass.PHYSICAL,R.Roll(3,6),this,"a frost demon")){ target.ApplyStatus(AttrType.SLOWED,R.Between(4,7)*100); //target.RefreshDuration(AttrType.SLOWED,R.Between(4,7)*100,target.YouAre() + " no longer slowed. ",target); } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; case ActorType.DEMON_LORD: if(DistanceFrom(target) > 2){ AI_Step(target); QS(); } else{ if(FirstActorInLine(target) != null){ Attack(0,target); } else{ if(AI_Step(target)){ QS(); } else{ AI_Sidestep(target); QS(); } } } break; } } else{ if(row >= 7 && row <= 12 && col >= 30 && col <= 35){ //near the center foreach(Actor a in ActorsAtDistance(1)){ if(a.IsFinalLevelDemon()){ List<Tile> dist2 = new List<Tile>(); foreach(Tile t in TilesWithinDistance(5)){ if(t.TilesAtDistance(2).Any(x=>x.type == TileType.FIRE_RIFT) && !t.TilesAtDistance(1).Any(x=>x.type == TileType.FIRE_RIFT)){ dist2.Add(t); } } //if there's another distance 2 (from the center) tile with no adjacent demons, move there //List<Tile> valid = dist2.Where(x=>DistanceFrom(x) == 1 && x.actor() == null && !x.TilesAtDistance(1).Any(y=>y.actor() != null && y.actor().Is(ActorType.MINOR_DEMON,ActorType.FROST_DEMON,ActorType.BEAST_DEMON,ActorType.DEMON_LORD))); List<Tile> valid = dist2.Where(x=>DistanceFrom(x) == 1); valid = valid.Where(x=>x.actor() == null && !x.TilesAtDistance(1).Any(y=>y.actor() != null && y.actor() != this && y.actor().IsFinalLevelDemon())); if(valid.Count > 0){ AI_Step(valid.Random()); } break; } } if(player.HasLOS(this)){ B.Add(TheName(true) + " chants. ",this); } M.IncrementClock(); Q1(); } else{ if(path != null && path.Count > 0){ if(!PathStep()){ QS(); } } else{ FindPath(9+R.Between(0,1),32+R.Between(0,1)); if(!PathStep()){ QS(); } } } } break; } default: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } break; } }
public void SmoothCorners(int percent_chance) { List<pos> corners = new List<pos>(); for(int i=1;i<H-1;++i){ for(int j=1;j<W-1;++j){ if(IsCornerFloor(i,j)){ corners.Add(new pos(i,j)); } } } while(corners.Count > 0){ pos p = corners.RemoveRandom(); corners.RemoveWhere(x=>x.DistanceFrom(p) <= 1); if(R.PercentChance(percent_chance)){ map[p] = CellType.Wall; } } }
private static void DrawWorld(World world) { var map = world.Map; var output = "cellmap.png"; var e = map.MapCells.Values.ElementAt(10); var highlight = new List <uint>() { e.Id }; highlight.AddRange(e.AdjacentMapCellIds); using (var image = new Image <Rgba32>(map.Width, map.Height)) { // Draw edges first to ensure any pixel gaps between polygons // will be filled in foreach (var cell in map.MapCells.Values) { if (cell.MapPoints.Count > 2) { var color = GetColor(cell, map.LandThreshold); image.Mutate(x => x .DrawLines(new Pen(color, 3f), cell.MapPoints.Select(p => new PointF(p.X, p.Y)).ToArray()) ); } } // Now draw map cell polygons foreach (var cell in map.MapCells.Values) { if (cell.MapPoints.Count > 2) { var color = GetColor(cell, map.LandThreshold); image.Mutate(x => x .FillPolygon(color, cell.MapPoints.Select(p => new PointF(p.X, p.Y)).ToArray()) ); } } var colors = new List <Color>() { Color.Red, Color.Orange, Color.Yellow, Color.GreenYellow, Color.Green, Color.DarkGreen, Color.DarkBlue, Color.Blue, Color.BlueViolet, Color.Violet, Color.PaleVioletRed }; colors.AddRange(Color.WebSafePalette.ToArray()); // Regions foreach (var territory in world.Territories.Values) { var color = colors.RemoveRandom(); if (color == null) { color = Color.White; } /* * foreach (var cell in territory.MapCells) * { * if (cell.MapPoints.Count > 2) * { * var centerX = cell.X; * var centerY = cell.Y; * * image.Mutate(x => x * .FillPolygon(color, cell.MapPoints.Select(p => new PointF(p.X, p.Y)).ToArray())); * } * }/**/ /* * image.Mutate(x => x * .DrawLines(new Pen(color, 2f), territory.ContainingMapPointIds.Select(id => new PointF(world.Map.MapPoints[id].X, world.Map.MapPoints[id].Y)).ToArray())); */ } // Now draw population centers foreach (var pop in world.PopulationCenters.Values) { image.Mutate(x => x .DrawLines(new Pen(Color.LightGray, 10f), new PointF(pop.MapCell.X, pop.MapCell.Y), new PointF(pop.MapCell.X, pop.MapCell.Y)) .DrawText(pop.Name, SmallFont, Color.White, new PointF(pop.MapCell.X + 10, pop.MapCell.Y + 10)) ); } // Show names of landmasses var textOptions = new TextOptions() { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, }; var drawingOptions = new DrawingOptions() { TextOptions = textOptions }; foreach (var landmass in world.Landmasses.Values.Where(x => x.MapCells.Count > 2)) { if (landmass.MapCells.Count > 100) { image.Mutate(x => x .DrawText(drawingOptions, landmass.Name, BigFont, Brushes.Solid(Color.White), Pens.Solid(Color.Black, 2.5f), new PointF(landmass.X, landmass.Y)) ); } else if (landmass.MapCells.Count < 20) { /* * drawingOptions.TextOptions.VerticalAlignment = VerticalAlignment.Bottom; * * image.Mutate(x => x * .DrawText(drawingOptions, landmass.Name, SmallFont, Brushes.Solid(Color.Black), Pens.Solid(Color.Black, 1), new PointF(landmass.Center.X, landmass.Center.Y)) * ); * /**/ } else { image.Mutate(x => x .DrawText(drawingOptions, landmass.Name, NormalFont, Brushes.Solid(Color.Black), Pens.Solid(Color.Black, 0.5f), new PointF(landmass.X, landmass.Y)) ); } } using (var filestream = new FileStream(output, FileMode.Create)) { image.SaveAsPng(filestream); } } try { var process = new ProcessStartInfo(output) { UseShellExecute = true, Verb = "open" }; Process.Start(process); } catch (Exception ex) { Console.WriteLine(ex.Message); } }
public async Task<bool> CastSpell(SpellType spell, PhysicalObject obj, bool force_of_will) { //returns false if targeting is canceled. if ((await StunnedThisTurn()) && !force_of_will) { //eventually this will be moved to the last possible second return true; //returns true because turn was used up. } if (!HasSpell(spell)) { return false; } foreach (Actor a in ActorsWithinDistance(2)) { if (a.HasAttr(AttrType.SPELL_DISRUPTION) && a.HasLOE(this)) { if (this == player) { if (CanSee(a)) { B.Add(a.Your() + " presence disrupts your spell! "); } else { B.Add("Something disrupts your spell! "); } } return false; } } Tile t = null; List<Tile> line = null; if (obj != null) { t = M.tile[obj.row, obj.col]; if (spell == SpellType.FORCE_BEAM) { //force beam requires a line for proper knockback line = GetBestExtendedLine(t); } else { line = GetBestLine(t); } } int bonus = 0; //used for bonus damage on spells - currently, only Master's Edge adds bonus damage. if (FailRate(spell) > 0) { int fail = FailRate(spell); if (force_of_will) { fail = magic_penalty * 5; fail -= skills[SkillType.SPIRIT] * 2; if (fail < 0) { fail = 0; } } if (Global.Roll(1, 100) - fail <= 0) { if (player.CanSee(this)) { B.Add("Sparks fly from " + Your() + " fingers. ", this); } else { if (player.DistanceFrom(this) <= 4 || (player.DistanceFrom(this) <= 12 && player.HasLOS(row, col))) { B.Add("You hear words of magic, but nothing happens. "); } } Q1(); return true; } } if (HasFeat(FeatType.MASTERS_EDGE)) { foreach (SpellType s in spells_in_order) { if (Spell.IsDamaging(s)) { if (s == spell) { bonus = 1; } break; } } } switch (spell) { case SpellType.SHINE: if (!HasAttr(AttrType.ENHANCED_TORCH)) { B.Add("You cast shine. "); if (!M.wiz_dark) { B.Add("Your torch begins to shine brightly. "); } attrs[AttrType.ENHANCED_TORCH]++; if (light_radius > 0) { UpdateRadius(LightRadius(), Global.MAX_LIGHT_RADIUS - attrs[AttrType.DIM_LIGHT] * 2, true); } Q.Add(new Event(9500, "Your torch begins to flicker a bit. ")); Q.Add(new Event(this, 10000, AttrType.ENHANCED_TORCH, "Your torch no longer shines as brightly. ")); } else { B.Add("Your torch is already shining brightly! "); return false; } break; /* case SpellType.MAGIC_MISSILE: if(t == null){ t = await GetTarget(); } if(t != null){ B.Add(You("cast") + " magic missile. ",this); Actor a = FirstActorInLine(t); if(a != null){ AnimateBoltProjectile(a,Color.Magenta); B.Add("The missile hits " + a.the_name + ". ",a); a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,Global.Roll(1+bonus,6),this); } else{ AnimateBoltProjectile(t,Color.Magenta); if(t.IsLit()){ B.Add("The missile hits " + t.the_name + ". "); } else{ B.Add("You attack the darkness. "); } } } else{ return false; } break; case SpellType.DETECT_MONSTERS: if(!HasAttr(AttrType.DETECTING_MONSTERS)){ B.Add(You("cast") + " detect monsters. ",this); if(type == ActorType.PLAYER){ B.Add("You can sense beings around you. "); Q.Add(new Event(this,2100,AttrType.DETECTING_MONSTERS,"You can no longer sense beings around you. ")); } else{ Q.Add(new Event(this,2100,AttrType.DETECTING_MONSTERS)); } attrs[AttrType.DETECTING_MONSTERS]++; } else{ B.Add("You are already detecting monsters! "); return false; } break;*/ case SpellType.IMMOLATE: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " immolate. ", this); Actor a = FirstActorInLine(line); if (a != null) { AnimateBeam(line.ToFirstObstruction(), "*", Color.RandomFire); if (!a.HasAttr(AttrType.RESIST_FIRE) && !a.HasAttr(AttrType.CATCHING_FIRE) && !a.HasAttr(AttrType.ON_FIRE)) { if (a.name == "you") { B.Add("You start to catch fire! "); } else { B.Add(a.the_name + " starts to catch fire. ", a); } a.attrs[AttrType.CATCHING_FIRE]++; } else { B.Add(a.You("shrug") + " off the flames. ", a); } } else { foreach (Tile t2 in line) { if (t2.Is(FeatureType.TROLL_CORPSE) || t2.Is(FeatureType.TROLL_SEER_CORPSE)) { line = line.To(t2); } } AnimateBeam(line, "*", Color.RandomFire); B.Add(You("throw") + " flames. ", this); if (line.Last().Is(FeatureType.TROLL_CORPSE)) { line.Last().features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ", line.Last()); } if (line.Last().Is(FeatureType.TROLL_SEER_CORPSE)) { line.Last().features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ", line.Last()); } } } else { return false; } break; case SpellType.FORCE_PALM: if (t == null) { t = TileInDirection(await GetDirection()); } if (t != null) { Actor a = M.actor[t.row, t.col]; B.Add(You("cast") + " force palm. ", this); //AnimateMapCell(t,Color.DarkCyan,"*"); B.DisplayNow(); Screen.AnimateMapCell(t.row, t.col, new colorchar("*", Color.Blue), 100); if (a != null) { B.Add(You("strike") + " " + a.TheVisible() + ". ", new PhysicalObject[] { this, a }); string s = a.the_name; string s2 = a.a_name; List<Tile> line2 = GetBestExtendedLine(a.row, a.col); int idx = line2.IndexOf(M.tile[a.row, a.col]); Tile next = line2[idx + 1]; await a.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(1 + bonus, 6), this, a_name); if (Global.Roll(1, 10) <= 7) { if (M.actor[t.row, t.col] != null) { await a.GetKnockedBack(this); } else { if (!next.passable) { B.Add(s + "'s corpse is knocked into " + next.the_name + ". ", new PhysicalObject[] { t, next }); } else { if (M.actor[next.row, next.col] != null) { B.Add(s + "'s corpse is knocked into " + M.actor[next.row, next.col].the_name + ". ", new PhysicalObject[] { t, M.actor[next.row, next.col] }); await M.actor[next.row, next.col].TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(1, 6), this, s2 + "'s falling corpse"); } } } } } else { if (t.passable) { B.Add("You strike at empty space. "); } else { B.Add("You strike " + t.the_name + " with your palm. "); if (t.ttype == TileType.DOOR_C) { //heh, why not? B.Add("It flies open! "); t.Toggle(this); } if (t.ttype == TileType.HIDDEN_DOOR) { //and this one gives it an actual use B.Add("A hidden door flies open! "); t.Toggle(this); t.Toggle(this); } } } } else { return false; } break; case SpellType.FREEZE: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " freeze. ", this); Actor a = FirstActorInLine(line); if (a != null) { AnimateBoltBeam(line.ToFirstObstruction(), Color.Cyan); if (!a.HasAttr(AttrType.FROZEN) && !a.HasAttr(AttrType.UNFROZEN)) { B.Add(a.YouAre() + " encased in ice. ", a); a.attrs[AttrType.FROZEN] = 25; } else { B.Add("The beam dissipates on the remaining ice. ", a); } } else { AnimateBoltBeam(line, Color.Cyan); B.Add("A bit of ice forms on " + t.the_name + ". ", t); } } else { return false; } break; case SpellType.BLINK: for (int i = 0; i < 9999; ++i) { int a = Global.Roll(1, 17) - 9; //-8 to 8 int b = Global.Roll(1, 17) - 9; if (Math.Abs(a) + Math.Abs(b) >= 6) { a += row; b += col; if (M.BoundsCheck(a, b) && M.tile[a, b].passable && M.actor[a, b] == null) { B.Add(You("cast") + " blink. ", this); B.Add(You("step") + " through a rip in reality. ", this); AnimateStorm(2, 3, 4, "*", Color.DarkMagenta); await Move(a, b); M.Draw(); AnimateStorm(2, 3, 4, "*", Color.DarkMagenta); break; } } } break; case SpellType.SCORCH: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " scorch. ", this); Actor a = FirstActorInLine(line); if (a != null) { AnimateProjectile(line.ToFirstObstruction(), "*", Color.RandomFire); B.Add("The scorching bolt hits " + a.the_name + ". ", a); await a.TakeDamage(DamageType.FIRE, DamageClass.MAGICAL, Global.Roll(2 + bonus, 6), this, a_name); } else { foreach (Tile t2 in line) { if (t2.Is(FeatureType.TROLL_CORPSE) || t2.Is(FeatureType.TROLL_SEER_CORPSE)) { line = line.To(t2); } } AnimateProjectile(line, "*", Color.RandomFire); B.Add("The scorching bolt hits " + t.the_name + ". ", t); if (line.Last().Is(FeatureType.TROLL_CORPSE)) { line.Last().features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ", line.Last()); } if (line.Last().Is(FeatureType.TROLL_SEER_CORPSE)) { line.Last().features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ", line.Last()); } } } else { return false; } break; case SpellType.BLOODSCENT: if (!HasAttr(AttrType.BLOODSCENT)) { B.Add(You("cast") + " bloodscent. ", this); attrs[Forays.AttrType.BLOODSCENT]++; if (atype == ActorType.PLAYER) { B.Add("You smell fear. "); Q.Add(new Event(this, 10000, Forays.AttrType.BLOODSCENT, "You lose the scent. ")); } else { Q.Add(new Event(this, 10000, Forays.AttrType.BLOODSCENT)); } } else { B.Add("You can already smell the blood of your enemies. "); return false; } break; case SpellType.LIGHTNING_BOLT: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " lightning bolt. ", this); PhysicalObject bolt_target = null; List<Actor> damage_targets = new List<Actor>(); foreach (Tile t2 in line) { if (t2.actor() != null && t2.actor() != this) { bolt_target = t2.actor(); damage_targets.Add(t2.actor()); break; } else { if (t2.ConductsElectricity()) { bolt_target = t2; break; } } } if (bolt_target != null) { Dict<PhysicalObject, List<PhysicalObject>> chain = new Dict<PhysicalObject, List<PhysicalObject>>(); chain[this] = new List<PhysicalObject> { bolt_target }; List<PhysicalObject> last_added = new List<PhysicalObject> { bolt_target }; for (bool done = false; !done; ) { done = true; List<PhysicalObject> new_last_added = new List<PhysicalObject>(); foreach (PhysicalObject added in last_added) { List<PhysicalObject> sort_list = new List<PhysicalObject>(); foreach (Tile nearby in added.TilesWithinDistance(3, true)) { if (nearby.actor() != null || nearby.ConductsElectricity()) { if (added.HasLOE(nearby)) { if (nearby.actor() != null) { bolt_target = nearby.actor(); } else { bolt_target = nearby; } bool contains_value = false; foreach (PhysicalObject k in chain.d.Keys) { List<PhysicalObject> list = chain.d[k]; foreach (PhysicalObject o in list) { if (o == bolt_target) { contains_value = true; break; } } if (contains_value) { break; } } if (!chain.d.ContainsKey(bolt_target) && !contains_value) { if (bolt_target as Actor != null) { damage_targets.AddUnique(bolt_target as Actor); } done = false; if (sort_list.Count == 0) { sort_list.Add(bolt_target); } else { int idx = 0; foreach (PhysicalObject o in sort_list) { if (bolt_target.DistanceFrom(added) < o.DistanceFrom(added)) { sort_list.Insert(idx, bolt_target); break; } ++idx; } if (idx == sort_list.Count) { sort_list.Add(bolt_target); } } if (chain[added] == null) { chain[added] = new List<PhysicalObject> { bolt_target }; } else { chain[added].Add(bolt_target); } } } } } foreach (PhysicalObject o in sort_list) { new_last_added.Add(o); } } if (!done) { last_added = new_last_added; } } //whew. the tree structure is complete. start at chain[this] and go from there... Dict<int, List<pos>> frames = new Dict<int, List<pos>>(); Dict<PhysicalObject, int> line_length = new Dict<PhysicalObject, int>(); line_length[this] = 0; List<PhysicalObject> current = new List<PhysicalObject> { this }; List<PhysicalObject> next = new List<PhysicalObject>(); while (current.Count > 0) { foreach (PhysicalObject o in current) { if (chain[o] != null) { foreach (PhysicalObject o2 in chain[o]) { List<Tile> bres = o.GetBestLine(o2); bres.RemoveAt(0); line_length[o2] = bres.Count + line_length[o]; int idx = 0; foreach (Tile t2 in bres) { if (frames[idx + line_length[o]] != null) { frames[idx + line_length[o]].Add(new pos(t2.row, t2.col)); } else { frames[idx + line_length[o]] = new List<pos> { new pos(t2.row, t2.col) }; } ++idx; } next.Add(o2); } } } current = next; next = new List<PhysicalObject>(); } List<pos> frame = frames[0]; for (int i = 0; frame != null; ++i) { foreach (pos p in frame) { Screen.WriteMapChar(p.row, p.col, "*", Color.RandomLightning); } await Task.Delay(50); frame = frames[i]; } foreach (Actor ac in damage_targets) { B.Add("The bolt hits " + ac.the_name + ". ", ac); await ac.TakeDamage(DamageType.ELECTRIC, DamageClass.MAGICAL, Global.Roll(2 + bonus, 6), this, a_name); } } else { AnimateBeam(line, "*", Color.RandomLightning); B.Add("The bolt hits " + t.the_name + ". ", t); } } else { return false; } break; case SpellType.SHADOWSIGHT: if (!HasAttr(AttrType.SHADOWSIGHT)) { B.Add("You cast shadowsight. "); B.Add("Your eyes pierce the darkness. "); int duration = 10001; GainAttr(AttrType.SHADOWSIGHT, duration, "You no longer see as well in darkness. "); GainAttr(AttrType.LOW_LIGHT_VISION, duration); } else { B.Add("Your eyes are already attuned to darkness. "); return false; } break; /*case SpellType.BURNING_HANDS: if(t == null){ t = TileInDirection(GetDirection()); } if(t != null){ B.Add(You("cast") + " burning hands. ",this); AnimateMapCell(t,Color.DarkRed,'*'); Actor a = M.actor[t.row,t.col]; if(a != null){ B.Add(You("project") + " flames onto " + a.the_name + ". ",this,a); a.TakeDamage(DamageType.FIRE,DamageClass.MAGICAL,Global.Roll(3+bonus,6),this); if(M.actor[t.row,t.col] != null && Global.Roll(1,10) <= 2){ B.Add(a.You("start") + " to catch fire! ",a); a.attrs[AttrType.CATCHING_FIRE]++; } } else{ B.Add("You project flames from your hands. "); } } else{ return false; } break; case SpellType.NIMBUS: { if(HasAttr(AttrType.NIMBUS_ON)){ B.Add("You're already surrounded by a nimbus. "); return false; } else{ B.Add(You("cast") + " nimbus. ",this); B.Add("An electric glow surrounds " + the_name + ". ",this); attrs[AttrType.NIMBUS_ON]++; int duration = (Global.Roll(5)+5)*100; Q.Add(new Event(this,duration,AttrType.NIMBUS_ON,"The electric glow fades from " + the_name + ". ",this)); } break; }*/ case SpellType.VOLTAIC_SURGE: { List<Actor> targets = new List<Actor>(); foreach (Actor a in ActorsWithinDistance(2, true)) { if (HasLOE(a)) { targets.Add(a); } } B.Add(You("cast") + " voltaic surge. ", this); AnimateExplosion(this, 2, Color.RandomLightning, "*"); if (targets.Count == 0) { B.Add("The air around " + the_name + " crackles. ", this); } else { while (targets.Count > 0) { Actor a = targets.Random(); targets.Remove(a); B.Add("Electricity blasts " + a.the_name + ". ", a); await a.TakeDamage(DamageType.ELECTRIC, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name); } } break; } case SpellType.MAGIC_HAMMER: if (t == null) { t = TileInDirection(await GetDirection()); } if (t != null) { Actor a = t.actor(); B.Add(You("cast") + " magic hammer. ", this); B.DisplayNow(); Screen.AnimateMapCell(t.row, t.col, new colorchar("*", Color.Magenta), 100); if (a != null) { B.Add(You("smash", true) + " " + a.TheVisible() + ". ", this, a); if (await a.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(4 + bonus, 6), this, a_name)) { a.GainAttrRefreshDuration(AttrType.STUNNED, 201, a.YouAre() + " no longer stunned. ", a); B.Add(a.YouAre() + " stunned. ", a); } } else { B.Add("You smash " + t.the_name + ". "); } } else { return false; } break; case SpellType.RETREAT: //this is a player-only spell for now because it uses target_location to track position B.Add("You cast retreat. "); if (target_location == null) { target_location = M.tile[row, col]; B.Add("You create a rune of transport on " + M.tile[row, col].the_name + ". "); target_location.features.Add(FeatureType.RUNE_OF_RETREAT); } else { if (M.actor[target_location.row, target_location.col] == null && target_location.passable) { B.Add("You activate your rune of transport. "); await Move(target_location.row, target_location.col); target_location.features.Remove(FeatureType.RUNE_OF_RETREAT); target_location = null; } else { B.Add("Something blocks your transport. "); } } break; case SpellType.GLACIAL_BLAST: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " glacial blast. ", this); Actor a = FirstActorInLine(line); if (a != null) { AnimateProjectile(line.ToFirstObstruction(), "*", Color.RandomIce); B.Add("The glacial blast hits " + a.the_name + ". ", a); await a.TakeDamage(DamageType.COLD, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name); } else { AnimateProjectile(line, "*", Color.RandomIce); B.Add("The glacial blast hits " + t.the_name + ". ", t); } } else { return false; } break; case SpellType.PASSAGE: { int i = DirectionOfOnlyUnblocked(TileType.WALL, true); if (i == 0) { B.Add("There's no wall here. ", this); return false; } else { if (t == null) { i = await GetDirection(true, false); t = TileInDirection(i); } else { i = DirectionOf(t); } if (t != null) { if (t.ttype == TileType.WALL) { B.Add(You("cast") + " passage. ", this); colorchar ch = new colorchar(Color.Cyan, "!"); if (this == player) { Game.Console.CursorVisible = false; switch (DirectionOf(t)) { case 8: case 2: ch.c = "|"; break; case 4: case 6: ch.c = "-"; break; } } List<Tile> tiles = new List<Tile>(); List<colorchar> memlist = new List<colorchar>(); while (!t.passable) { if (t.row == 0 || t.row == ROWS - 1 || t.col == 0 || t.col == COLS - 1) { break; } if (this == player) { tiles.Add(t); memlist.Add(Screen.MapChar(t.row, t.col)); Screen.WriteMapChar(t.row, t.col, ch); await Task.Delay(35); // Thread.Sleep(35); } t = t.TileInDirection(i); } if (t.passable && M.actor[t.row, t.col] == null) { if (this == player) { if (M.tile[row, col].inv != null) { Screen.WriteMapChar(row, col, new colorchar(tile().inv.color, tile().inv.symbol)); } else { Screen.WriteMapChar(row, col, new colorchar(tile().color, tile().symbol)); } Screen.WriteMapChar(t.row, t.col, new colorchar(color, symbol)); int j = 0; foreach (Tile tile in tiles) { Screen.WriteMapChar(tile.row, tile.col, memlist[j++]); await Task.Delay(35); //Thread.Sleep(35); } } await Move(t.row, t.col); M.Draw(); B.Add(You("travel") + " through the passage. ", this); } else { if (this == player) { int j = 0; foreach (Tile tile in tiles) { Screen.WriteMapChar(tile.row, tile.col, memlist[j++]); await Task.Delay(35); //Thread.Sleep(35); } B.Add("The passage is blocked. "); } } } else { if (this == player) { B.Add("There's no wall here. ", this); } return false; } } else { return false; } } break; } case SpellType.FLASHFIRE: if (t == null) { line = await GetTarget(12, 2); if (line != null) { t = line.Last(); } } if (t != null) { Actor a = FirstActorInLine(line); if (a != null) { t = a.tile(); } B.Add(You("cast") + " flashfire. ", this); AnimateBoltProjectile(line.ToFirstObstruction(), Color.Red); AnimateExplosion(t, 2, "*", Color.RandomFire); B.Add("Fwoosh! ", new PhysicalObject[] { this, t }); List<Actor> targets = new List<Actor>(); Tile prev = line.ToFirstObstruction()[line.ToFirstObstruction().Count - 2]; foreach (Actor ac in t.ActorsWithinDistance(2)) { if (t.passable) { if (t.HasBresenhamLine(ac.row, ac.col)) { targets.Add(ac); } } else { if (prev.HasBresenhamLine(ac.row, ac.col)) { targets.Add(ac); } } } foreach (Tile t2 in t.TilesWithinDistance(2)) { if (t.passable) { if (t.HasBresenhamLine(t2.row, t2.col)) { if (t2.actor() != null) { targets.Add(t2.actor()); } if (t2.Is(FeatureType.TROLL_CORPSE)) { t2.features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ", t2); } if (t2.Is(FeatureType.TROLL_SEER_CORPSE)) { t2.features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ", t2); } } } else { if (prev.HasBresenhamLine(t2.row, t2.col)) { if (t2.actor() != null) { targets.Add(t2.actor()); } if (t2.Is(FeatureType.TROLL_CORPSE)) { t2.features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ", t2); } if (t2.Is(FeatureType.TROLL_SEER_CORPSE)) { t2.features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ", t2); } } } } while (targets.Count > 0) { Actor ac = targets.RemoveRandom(); B.Add("The explosion hits " + ac.the_name + ". ", ac); await ac.TakeDamage(DamageType.FIRE, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name); } } else { return false; } break; case SpellType.SONIC_BOOM: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " sonic boom. ", this); Actor a = FirstActorInLine(line); if (a != null) { AnimateProjectile(line.ToFirstObstruction(), "~", Color.Yellow); B.Add("A wave of sound hits " + a.the_name + ". ", a); int r = a.row; int c = a.col; await a.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name); if (Global.Roll(1, 10) <= 5 && M.actor[r, c] != null && !M.actor[r, c].HasAttr(AttrType.STUNNED)) { B.Add(a.YouAre() + " stunned. ", a); a.attrs[AttrType.STUNNED]++; int duration = DurationOfMagicalEffect((Global.Roll(1, 4) + 2)) * 100; Q.Add(new Event(a, duration, AttrType.STUNNED, a.YouAre() + " no longer stunned. ", new PhysicalObject[] { a })); } } else { AnimateProjectile(line, "~", Color.Yellow); B.Add("Sonic boom! "); } } else { return false; } break; case SpellType.COLLAPSE: if (t == null) { line = await GetTarget(12, -1); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " collapse. ", this); B.DisplayNow(); for (int dist = 2; dist > 0; --dist) { List<pos> cells = new List<pos>(); List<colorchar> chars = new List<colorchar>(); pos p2 = new pos(t.row - dist, t.col - dist); if (p2.BoundsCheck()) { cells.Add(p2); chars.Add(new colorchar("\\", Color.DarkGreen)); } p2 = new pos(t.row - dist, t.col + dist); if (p2.BoundsCheck()) { cells.Add(p2); chars.Add(new colorchar("/", Color.DarkGreen)); } p2 = new pos(t.row + dist, t.col - dist); if (p2.BoundsCheck()) { cells.Add(p2); chars.Add(new colorchar("/", Color.DarkGreen)); } p2 = new pos(t.row + dist, t.col + dist); if (p2.BoundsCheck()) { cells.Add(p2); chars.Add(new colorchar("\\", Color.DarkGreen)); } Screen.AnimateMapCells(cells, chars); } Screen.AnimateMapCell(t.row, t.col, new colorchar("X", Color.DarkGreen)); Actor a = t.actor(); if (a != null) { B.Add("Part of the ceiling falls onto " + a.the_name + ". ", a); await a.TakeDamage(DamageType.BASHING, DamageClass.PHYSICAL, Global.Roll(4 + bonus, 6), this, a_name); } else { if (t.row == 0 || t.col == 0 || t.row == ROWS - 1 || t.col == COLS - 1) { B.Add("The wall resists. "); } else { if (t.ttype == TileType.WALL || t.ttype == TileType.HIDDEN_DOOR) { B.Add("The wall crashes down! "); t.TurnToFloor(); foreach (Tile neighbor in t.TilesAtDistance(1)) { if (neighbor.solid_rock) { neighbor.solid_rock = false; } } } } } List<Tile> open_spaces = new List<Tile>(); foreach (Tile neighbor in t.TilesWithinDistance(1)) { if (neighbor.passable) { if (a == null || neighbor != t) { //don't hit the same guy again open_spaces.Add(neighbor); } } } int count = 4; if (open_spaces.Count < 4) { count = open_spaces.Count; } for (; count > 0; --count) { Tile chosen = open_spaces.Random(); open_spaces.Remove(chosen); if (chosen.actor() != null) { B.Add("A rock falls onto " + chosen.actor().the_name + ". ", chosen.actor()); await chosen.actor().TakeDamage(DamageType.BASHING, Forays.DamageClass.PHYSICAL, Global.Roll(2, 6), this, a_name); } else { TileType prev = chosen.ttype; chosen.TransformTo(TileType.RUBBLE); chosen.toggles_into = prev; } } } else { return false; } break; case SpellType.FORCE_BEAM: if (t == null) { line = await GetTarget(); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " force beam. ", this); B.DisplayNow(); //List<Tile> line2 = GetBestExtendedLine(t.row,t.col); List<Tile> full_line = new List<Tile>(line); line = line.GetRange(0, Math.Min(13, line.Count)); for (int i = 0; i < 3; ++i) { //hits thrice Actor firstactor = null; Actor nextactor = null; Tile firsttile = null; Tile nexttile = null; foreach (Tile tile in line) { if (!tile.passable) { firsttile = tile; break; } if (M.actor[tile.row, tile.col] != null && M.actor[tile.row, tile.col] != this) { int idx = full_line.IndexOf(tile); firsttile = tile; firstactor = M.actor[tile.row, tile.col]; nexttile = full_line[idx + 1]; nextactor = M.actor[nexttile.row, nexttile.col]; break; } } AnimateBoltBeam(line.ToFirstObstruction(), Color.Cyan); if (firstactor != null) { string s = firstactor.TheVisible(); string s2 = firstactor.a_name; await firstactor.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(1 + bonus, 6), this, a_name); if (M.actor[firsttile.row, firsttile.col] != null) { await firstactor.GetKnockedBack(full_line); } else { if (!nexttile.passable) { B.Add(s + "'s corpse is knocked into " + nexttile.the_name + ". ", new PhysicalObject[] { firsttile, nexttile }); } else { if (nextactor != null) { B.Add(s + "'s corpse is knocked into " + nextactor.TheVisible() + ". ", new PhysicalObject[] { firsttile, nextactor }); await nextactor.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(1, 6), this, s2 + "'s falling corpse"); } } } } } } else { return false; } break; /*case SpellType.DISINTEGRATE: if(t == null){ t = await GetTarget(); } if(t != null){ B.Add(You("cast") + " disintegrate. ",this); Actor a = FirstActorInLine(t); if(a != null){ AnimateBoltBeam(a,Color.DarkGreen); B.Add(You("direct") + " destructive energies toward " + a.the_name + ". ",this,a); a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,Global.Roll(8+bonus,6),this); } else{ AnimateBoltBeam(t,Color.DarkGreen); if(t.type == TileType.WALL || t.type == TileType.DOOR_C || t.type == TileType.DOOR_O || t.type == TileType.CHEST){ B.Add(You("direct") + " destructive energies toward " + t.the_name + ". ",this,t); B.Add(t.the_name + " turns to dust. ",t); t.TurnToFloor(); } } } else{ return false; } break;*/ case SpellType.AMNESIA: if (t == null) { t = TileInDirection(await GetDirection()); } if (t != null) { Actor a = t.actor(); if (a != null) { B.Add(You("cast") + " amnesia. ", this); /*for(int i=0;i<4;++i){ List<pos> cells = new List<pos>(); List<colorchar> chars = new List<colorchar>(); List<pos> nearby = a.p.PositionsWithinDistance(2); for(int j=0;j<4;++j){ cells.Add(nearby.RemoveRandom()); chars.Add(new colorchar('*',Color.RandomPrismatic)); } Screen.AnimateMapCells(cells,chars); }*/ a.AnimateStorm(2, 4, 4, "*", Color.RandomPrismatic); B.Add("You fade from " + a.TheVisible() + "'s awareness. "); a.player_visibility_duration = 0; a.target = null; a.target_location = null; a.attrs[Forays.AttrType.AMNESIA_STUN]++; } else { B.Add("There's nothing to target there. "); return false; } } else { return false; } break; case SpellType.BLIZZARD: { List<Actor> targets = ActorsWithinDistance(5, true); B.Add(You("cast") + " blizzard. ", this); AnimateStorm(5, 8, 24, "*", Color.RandomIce); B.Add("A massive ice storm surrounds " + the_name + ". ", this); while (targets.Count > 0) { int idx = Global.Roll(1, targets.Count) - 1; Actor a = targets[idx]; targets.Remove(a); B.Add("The blizzard hits " + a.the_name + ". ", a); int r = a.row; int c = a.col; await a.TakeDamage(DamageType.COLD, DamageClass.MAGICAL, Global.Roll(5 + bonus, 6), this, a_name); if (M.actor[r, c] != null && Global.Roll(1, 10) <= 8) { B.Add(a.the_name + " is encased in ice. ", a); a.attrs[AttrType.FROZEN] = 25; } } break; } case SpellType.BLESS: if (!HasAttr(AttrType.BLESSED)) { B.Add(You("cast") + " bless. ", this); B.Add(You("shine") + " briefly with inner light. ", this); attrs[AttrType.BLESSED]++; Q.Add(new Event(this, 400, AttrType.BLESSED)); } else { B.Add(YouAre() + " already blessed! ", this); return false; } break; case SpellType.MINOR_HEAL: B.Add(You("cast") + " minor heal. ", this); B.Add("A bluish glow surrounds " + the_name + ". ", this); await TakeDamage(DamageType.HEAL, DamageClass.NO_TYPE, Global.Roll(4, 6), null); break; case SpellType.HOLY_SHIELD: if (!HasAttr(AttrType.HOLY_SHIELDED)) { B.Add(You("cast") + " holy shield. ", this); B.Add("A fiery halo appears above " + the_name + ". ", this); attrs[AttrType.HOLY_SHIELDED]++; int duration = (Global.Roll(3, 2) + 1) * 100; Q.Add(new Event(this, duration, AttrType.HOLY_SHIELDED, the_name + "'s halo fades. ", new PhysicalObject[] { this })); } else { B.Add(Your() + " holy shield is already active. ", this); return false; } break; } if (atype == ActorType.PLAYER && spell != SpellType.AMNESIA) { MakeNoise(); } if (!force_of_will) { if (Spell.Level(spell) - TotalSkill(SkillType.MAGIC) > 0) { if (HasFeat(FeatType.STUDENTS_LUCK)) { if (Global.CoinFlip()) { magic_penalty++; B.Add(YouFeel() + " drained. ", this); } else { if (atype == ActorType.PLAYER) { B.Add("You feel lucky. "); //punk } } } else { magic_penalty++; B.Add(YouFeel() + " drained. ", this); } } } else { magic_penalty += 5; if (magic_penalty > 20) { magic_penalty = 20; } B.Add("You drain your magic reserves. "); } Q1(); return true; }