コード例 #1
0
 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
 }
コード例 #2
0
            public void WhenHasTwoElements_AndCalledTwice_ThenRemove()
            {
                ICollection <int> sut = new List <int> {
                    1, 5
                };

                sut.RemoveRandom();
                sut.RemoveRandom();

                Assert.That(sut, Is.Empty);
            }
コード例 #3
0
    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());
        }
    }
コード例 #4
0
 /// <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());
     }
 }
コード例 #5
0
 IEnumerator GeneratePickups()
 {
     while (BMazeManager.Instance.CreatePickup(cellList.RemoveRandom()))
     {
         ;
     }
     yield return(null);
 }
コード例 #6
0
 void AddFoodsToList(List <GameObject> goList)
 {
     // Go through given list choose 5 different foods
     for (int i = 0; i < 5; i++)
     {
         foods.Add(goList.RemoveRandom());
     }
 }
コード例 #7
0
        public void TestListRemoveRandom()
        {
            List <int> list = new List <int> {
                1, 2, 3
            };
            int removed = list.RemoveRandom();

            Assert.IsTrue(!list.Contains(removed));
        }
コード例 #8
0
ファイル: GameManager.cs プロジェクト: TDSrock/GameJame2020
    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;
            }
        }
    }
コード例 #9
0
        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());
            }
        }
コード例 #10
0
            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));
            }
コード例 #11
0
            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);
            }
コード例 #12
0
 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);
     }
 }
コード例 #13
0
 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);
     }
 }
コード例 #14
0
ファイル: Utils.cs プロジェクト: takashicompany/utils
        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);
        }
コード例 #15
0
        /// <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);
        }
コード例 #16
0
    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;
        }
    }
コード例 #17
0
ファイル: MapScanner.cs プロジェクト: kesac/Loremaker
        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);
        }
コード例 #18
0
ファイル: MapGenerator.cs プロジェクト: AVAVT/BarcodeDungeon
  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;
  }
コード例 #19
0
        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);
        }
コード例 #20
0
    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);
    }
コード例 #21
0
 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;
                     }
                 }
             }
         }
     }
 }
コード例 #22
0
ファイル: Map.cs プロジェクト: ptrefall/ForaysIntoNorrendrin
 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());
     }
 }
コード例 #23
0
		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;
		}
コード例 #24
0
ファイル: City.cs プロジェクト: firebitsbr/Unity-Experiment
    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);
        }
    }
コード例 #25
0
        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);
        }
コード例 #26
0
ファイル: GameBoard.cs プロジェクト: jsnklpn/jatan
        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;
            }
        }
コード例 #27
0
ファイル: Map.cs プロジェクト: ptrefall/ForaysIntoNorrendrin
        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));
        }
コード例 #28
0
            public void WhenIsEmpty_ThenThrowException()
            {
                ICollection <int> sut = new List <int>();

                Assert.Throws <InvalidOperationException>(() => sut.RemoveRandom());
            }
コード例 #29
0
ファイル: Map.cs プロジェクト: ptrefall/ForaysIntoNorrendrin
 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;
 }
コード例 #30
0
ファイル: Game.cs プロジェクト: Cirrus-Game-Studios/gem-jam
        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);
            }
        }
コード例 #31
0
 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;
     }
 }
コード例 #32
0
ファイル: Map.cs プロジェクト: ptrefall/ForaysIntoNorrendrin
 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;
     }
 }
コード例 #33
0
 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;
     });
 }
コード例 #34
0
 void ChooseMonsterSpawn()
 {
     startingLocation = pointList.RemoveRandom();
 }
コード例 #35
0
 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);
     }
 }
コード例 #36
0
ファイル: Map.cs プロジェクト: ptrefall/ForaysIntoNorrendrin
 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;
         }
     }
 }
コード例 #37
0
ファイル: Item.cs プロジェクト: ptrefall/ForaysIntoNorrendrin
 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;
 }
コード例 #38
0
ファイル: Item.cs プロジェクト: ptrefall/ForaysIntoNorrendrin
 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;
 }
コード例 #39
0
ファイル: GameBoard.cs プロジェクト: jsnklpn/jatan
        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;
        }
コード例 #40
0
ファイル: Item.cs プロジェクト: ptrefall/ForaysIntoNorrendrin
 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;
                         }
                     }
                 }
             }
         }
     }
 }
コード例 #41
0
 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);
                         }
                     }
                 }
             }
         }
     }
 }
コード例 #42
0
 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];
         }
     }
 }
コード例 #43
0
 public virtual GameObject SelectRandomAndRemove()
 {
     return(pickupPrefabList.RemoveRandom());
 }
コード例 #44
0
 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;
                         }
                     }
                 }
             }
         }
     }
 }
コード例 #45
0
ファイル: Tile.cs プロジェクト: ptrefall/ForaysIntoNorrendrin
 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();
         }
     }
 }
コード例 #46
0
 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;
 }
コード例 #47
0
 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;
     }
 }
コード例 #48
0
 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;
         }
     }
 }
コード例 #49
0
ファイル: Program.cs プロジェクト: kesac/Loremaker
        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);
            }
        }
コード例 #50
0
        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;
        }