private EntityData generateBadelineEntityData(Level level, int badelineNumber)
        {
            EntityData entityData = ExtendedVariantsModule.GenerateBasicEntityData(level, badelineNumber);

            entityData.Values["canChangeMusic"] = false;
            return(entityData);
        }
        private void modLoadLevel(On.Celeste.Level.orig_LoadLevel orig, Level self, Player.IntroTypes playerIntro, bool isFromLoader)
        {
            orig(self, playerIntro, isFromLoader);

            // if we killed the slowdown earlier, stop now!
            killSeekerSlowdownToFixHeart = false;

            Level  level  = self;
            Player player = level.Tracker.GetEntity <Player>();

            if (player != null && Settings.AddSeekers != 0)
            {
                // make the seeker barriers temporarily collidable so that they are taken in account in Solid collide checks
                // and seekers can't spawn in them
                // (... yes, this is also what vanilla does in the seekers' Update method.)
                foreach (Entity entity in self.Tracker.GetEntities <SeekerBarrier>())
                {
                    entity.Collidable = true;
                }

                for (int seekerCount = 0; seekerCount < Settings.AddSeekers; seekerCount++)
                {
                    for (int i = 0; i < 100; i++)
                    {
                        // roll a seeker position in the room
                        int x = randomGenerator.Next(level.Bounds.Width) + level.Bounds.X;
                        int y = randomGenerator.Next(level.Bounds.Height) + level.Bounds.Y;

                        // should be at least 100 pixels from the player
                        double playerDistance = Math.Sqrt(Math.Pow(MathHelper.Distance(x, player.X), 2) + Math.Pow(MathHelper.Distance(y, player.Y), 2));

                        // also check if we are not spawning in a wall, that would be a shame
                        Rectangle collideRectangle = new Rectangle(x - 8, y - 8, 16, 16);
                        if (playerDistance > 100 && !level.CollideCheck <Solid>(collideRectangle) && !level.CollideCheck <Seeker>(collideRectangle))
                        {
                            // build a Seeker with a proper EntityID to make Speedrun Tool happy (this is useless in vanilla Celeste but the constructor call is intercepted by Speedrun Tool)
                            EntityData seekerData = ExtendedVariantsModule.GenerateBasicEntityData(level, 10 + seekerCount);
                            seekerData.Position = new Vector2(x, y);
                            Seeker seeker = new AutoDestroyingSeeker(seekerData, Vector2.Zero);
                            level.Add(seeker);
                            break;
                        }
                    }
                }

                foreach (Entity entity in self.Tracker.GetEntities <SeekerBarrier>())
                {
                    entity.Collidable = false;
                }

                if (playerIntro != Player.IntroTypes.Transition)
                {
                    level.Entities.UpdateLists();
                }
            }
        }
Esempio n. 3
0
        private void injectBadelineBosses(Level level)
        {
            Player player = level.Tracker.GetEntity <Player>();

            if (player != null)
            {
                for (int id = level.Tracker.CountEntities <FinalBoss>(); id < Settings.BadelineBossCount; id++)
                {
                    // let's add a boss

                    Vector2 bossPosition;
                    if (id == 0 && !Settings.FirstBadelineSpawnRandom)
                    {
                        // the first Badeline should spawn at the opposite of the room
                        bossPosition = computeBossPositionAtOppositeOfPlayer(level, player);
                    }
                    else
                    {
                        // all the others should spawn at random points
                        bossPosition = computeBossPositionAtRandom(level, player);
                    }

                    // position not found, abort!
                    if (bossPosition == Vector2.Zero)
                    {
                        break;
                    }

                    Vector2 penultimateNode = player.Position;
                    Vector2 lastNode        = bossPosition;

                    Vector2[] nodes = new Vector2[Settings.BadelineBossNodeCount];

                    for (int i = 0; i < Settings.BadelineBossNodeCount - 1; i++)
                    {
                        // randomize all nodes, except the last one.
                        nodes[i] = computeBossPositionAtRandom(level, player);

                        penultimateNode = lastNode;
                        lastNode        = nodes[i];

                        // position not found, don't process other nodes!
                        if (lastNode == Vector2.Zero)
                        {
                            break;
                        }
                    }

                    // position not found, stop adding bosses!
                    if (lastNode == Vector2.Zero)
                    {
                        break;
                    }

                    if (bossPosition != Vector2.Zero)
                    {
                        Vector2 bossPositionOffscreen = penultimateNode;

                        Vector2 lastMoveDirection = (lastNode - penultimateNode);

                        // extremely unlikely, but better safe than sorry
                        if (lastMoveDirection == Vector2.Zero)
                        {
                            lastMoveDirection = Vector2.One;
                        }

                        while (bossPositionOffscreen.X + 30 > level.Bounds.Left - 50 && bossPositionOffscreen.X < level.Bounds.Right + 50 &&
                               bossPositionOffscreen.Y + 30 > level.Bounds.Top - 50 && bossPositionOffscreen.Y < level.Bounds.Bottom + 50)
                        {
                            // push the position until it is offscreen, using the same direction as the last move (or the player => boss direction if there is no node).
                            bossPositionOffscreen += lastMoveDirection;
                        }

                        // the offscreen position has to be the last node.
                        nodes[nodes.Length - 1] = bossPositionOffscreen;

                        // build the boss
                        EntityData bossData = ExtendedVariantsModule.GenerateBasicEntityData(level, 15 + id); // 0 to 9 are Badeline chasers, 10 to 14 are seekers.
                        bossData.Position = bossPosition;
                        bossData.Values["canChangeMusic"] = false;
                        bossData.Values["cameraLockY"]    = false;
                        bossData.Values["patternIndex"]   = Settings.BadelineAttackPattern == 0 ? patternRandomizer.Next(1, 16) : Settings.BadelineAttackPattern;
                        bossData.Nodes = nodes;

                        // add it to the level!
                        level.Add(new AutoDestroyingBadelineBoss(bossData, Vector2.Zero));
                    }
                }

                level.Entities.UpdateLists();
            }
        }