Beispiel #1
0
        public static int GetWestBrosRevolverID(WestBros whichBro)
        {
            switch (whichBro)
            {
            case WestBros.Angel:
                return(WestBrosAngelGunID);

            case WestBros.Nome:
                return(WestBrosNomeGunID);

            case WestBros.Tuc:
                return(WestBrosTucGunID);

            default:
                throw new System.Exception("Invalid enum value");
            }
        }
Beispiel #2
0
        private static void BuildWestBrosHatPrefab(AssetBundle assetBundle, out GameObject outObject, WestBros whichBro, tk2dSpriteCollectionData spriteCollection, DebrisObject broDebris)
        {
            outObject = assetBundle.LoadAsset <GameObject>($"WestBrosHat_{whichBro}");

            string hatSpriteName = null;

            switch (whichBro)
            {
            case WestBros.Angel:
                hatSpriteName = "hat_angel";
                break;

            case WestBros.Nome:
                hatSpriteName = "hat_nome";
                break;

            case WestBros.Tuc:
                hatSpriteName = "hat_tuco";
                break;
            }

            tk2dSprite hatSprite = outObject.AddComponent <tk2dSprite>();

            hatSprite.SetSprite(spriteCollection, hatSpriteName);
            hatSprite.SortingOrder = 0;

            ExpandUtility.GenerateSpriteAnimator(outObject);

            DebrisObject debrisObject = outObject.AddComponent <DebrisObject>();

            // this is set seperately because we use DeclaredOnly for the reflection field copying and Priority is inherited from EphemeralObject
            debrisObject.Priority = broDebris.Priority;

            ExpandUtility.ReflectionShallowCopyFields(debrisObject, broDebris, (BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly));
        }
Beispiel #3
0
        private static void BuildWestBrosBossPrefab(AssetBundle assetBundle, out GameObject outObject, WestBros whichBro, bool isSmiley, tk2dSpriteCollectionData sourceSpriteCollection, tk2dSpriteAnimation sourceAnimations, bool keepIntroDoer = false)
        {
            GameObject prefab = ExpandCustomEnemyDatabase.GetOfficialEnemyByGuid(isSmiley
                ? "ea40fcc863d34b0088f490f4e57f8913"              // Smiley
                : "c00390483f394a849c36143eb878998f").gameObject; // Shades

            outObject = UnityEngine.Object.Instantiate(prefab, Vector3.zero, Quaternion.identity);

            try
            {
                string name = $"West Bros {whichBro}";

                outObject.SetActive(false);
                outObject.name = name;

                AIActor actor = outObject.GetComponent <AIActor>();

                actor.healthHaver.overrideBossName = "Western Bros";

                actor.EnemyId   = UnityEngine.Random.Range(100000, 999999);
                actor.ActorName = name;

                EncounterTrackable Encounterable = outObject.GetComponent <EncounterTrackable>();

                switch (whichBro)
                {
                case WestBros.Angel:
                    actor.EnemyGuid             = "275354563e244f558be87fcff4b07f9f";
                    Encounterable.EncounterGuid = "7d6e1faf682d4402b29535020313f383";
                    break;

                case WestBros.Nome:
                    actor.EnemyGuid             = "3a1a33a905bb4b669e7d798f20674c4c";
                    Encounterable.EncounterGuid = "78cb8889dc884dd9b3aafe64558d858e";
                    break;

                case WestBros.Tuc:
                    actor.EnemyGuid             = "d2e7ea9ea9a444cebadd3bafa0832cd1";
                    Encounterable.EncounterGuid = "1df53371ce084dafb46f6bcd5a6c1c5f";
                    break;
                }

                // TODO at some distant point in time
                Encounterable.journalData.PrimaryDisplayName           = name;
                Encounterable.journalData.NotificationPanelDescription = name;
                Encounterable.journalData.AmmonomiconFullEntry         = name;

                // x BroController
                // x BulletBroDeathController
                // x BulletBrosIntroDoer
                // x BulletBroSeekTargetBehavior // movement behaviour
                //   BulletBroRepositionBehavior // completely unused

                var oldBroController = outObject.GetComponent <BroController>();
                var newBroController = outObject.AddComponent <ExpandWesternBroController>();

                newBroController.enrageAnim          = oldBroController.enrageAnim;
                newBroController.enrageAnimTime      = oldBroController.enrageAnimTime;
                newBroController.enrageHealToPercent = oldBroController.enrageHealToPercent;
                newBroController.overheadVfx         = oldBroController.overheadVfx;
                newBroController.postEnrageMoveSpeed = oldBroController.postEnrageMoveSpeed;

                newBroController.whichBro = whichBro;
                newBroController.postSecondEnrageMoveSpeed = newBroController.postEnrageMoveSpeed;

                UnityEngine.Object.Destroy(oldBroController);

                UnityEngine.Object.Destroy(outObject.GetComponent <BulletBroDeathController>());
                outObject.AddComponent <ExpandWesternBroDeathController>();

                var newMovementBehavior = new ExpandWesternBroSeekTargetBehavior();
                var oldMovementBehavior = actor.behaviorSpeculator.MovementBehaviors.First() as BulletBroSeekTargetBehavior;

                newMovementBehavior.CustomRange     = oldMovementBehavior.CustomRange;
                newMovementBehavior.PathInterval    = oldMovementBehavior.PathInterval;
                newMovementBehavior.StopWhenInRange = oldMovementBehavior.StopWhenInRange;

                actor.behaviorSpeculator.MovementBehaviors = new List <MovementBehaviorBase>()
                {
                    newMovementBehavior
                };

                // only smiley has a bossIntroDoer, so the stuff after this would null reference if done with shade
                if (isSmiley)
                {
                    if (!keepIntroDoer)
                    {
                        UnityEngine.Object.Destroy(outObject.GetComponent <BulletBrosIntroDoer>());
                        UnityEngine.Object.Destroy(outObject.GetComponent <GenericIntroDoer>());
                    }
                    else
                    {
                        // BulletBrosIntroDoer is a SpecificIntroDoer; it does not inherent from GenericIntroDoer, it requires it to be present
                        BulletBrosIntroDoer bulletBrosIntroDoer = outObject.GetComponent <BulletBrosIntroDoer>();

                        // destroy it so we can add our own
                        UnityEngine.Object.Destroy(bulletBrosIntroDoer);

                        GenericIntroDoer genericIntroDoer = outObject.GetComponent <GenericIntroDoer>();

                        genericIntroDoer.portraitSlideSettings.bossNameString     = "Western Bros";
                        genericIntroDoer.portraitSlideSettings.bossSubtitleString = "Triple Tap";
                        genericIntroDoer.portraitSlideSettings.bossQuoteString    = string.Empty;

                        genericIntroDoer.portraitSlideSettings.bossArtSprite = assetBundle.LoadAsset <Texture2D>("WesternBrosBossCard");

                        genericIntroDoer.triggerType     = GenericIntroDoer.TriggerType.PlayerEnteredRoom;
                        genericIntroDoer.OnIntroFinished = () => { };

                        ExpandWesternBroIntroDoer specificIntroDoer = outObject.AddComponent <ExpandWesternBroIntroDoer>();
                    }
                }

                var animationsAsset = assetBundle.LoadAsset <GameObject>($"WestBrosAnimations_{whichBro}");

                List <tk2dSpriteAnimationClip> animationClips = new List <tk2dSpriteAnimationClip>();

                foreach (tk2dSpriteAnimationClip clip in sourceAnimations.clips)
                {
                    if (clip.name.StartsWith($"{whichBro.ToString().ToLower()}_"))
                    {
                        animationClips.Add(ExpandUtility.DuplicateAnimationClip(clip));
                    }
                }

                List <tk2dSpriteAnimationClip> clipsToAdd = new List <tk2dSpriteAnimationClip>();

                foreach (tk2dSpriteAnimationClip clip in animationClips)
                {
                    clip.name = clip.name.Replace($"{whichBro.ToString().ToLower()}_", string.Empty);

                    clip.name = clip.name.Replace("dash_front", "dash_forward");
                    clip.name = clip.name.Replace("move_front", "move_forward");

                    if (clip.name.EndsWith("_prime"))
                    {
                        clip.frames[5].eventAudio   = "Play_ENM_bullet_dash_01";
                        clip.frames[5].triggerEvent = true;
                    }
                    else if (clip.name.StartsWith("move_"))
                    {
                        clip.frames[2].eventAudio   = "PLay_FS_ENM";
                        clip.frames[2].triggerEvent = true;
                    }
                    else if (clip.name == "anger")
                    {
                        // we change the west bros anger animation from a loop to a loop section so the anger SFX only plays once.
                        // to do this we simply clone every frame once and make it loop at the first copied frame.

                        var angerFrames = clip.frames.ToList();

                        foreach (var frame in clip.frames)
                        {
                            tk2dSpriteAnimationFrame clonedFrame = new tk2dSpriteAnimationFrame();

                            ExpandUtility.ReflectionShallowCopyFields(clonedFrame, frame, BindingFlags.Public | BindingFlags.Instance);

                            angerFrames.Add(clonedFrame);
                        }

                        // it's important to do this before changing clip.frames
                        clip.loopStart              = clip.frames.Length;
                        clip.frames                 = angerFrames.ToArray();
                        clip.wrapMode               = tk2dSpriteAnimationClip.WrapMode.LoopSection;
                        clip.frames[0].eventAudio   = "Play_BOSS_bulletbros_anger_01";
                        clip.frames[0].triggerEvent = true;
                    }
                    else if (clip.name == "death_right")
                    {
                        clip.name = "die";

                        tk2dSpriteAnimationClip dieFrontRight = ExpandUtility.DuplicateAnimationClip(clip);
                        dieFrontRight.name = "die_front_right";
                        clipsToAdd.Add(dieFrontRight);
                    }
                    else if (clip.name == "death_left")
                    {
                        clip.name = "die_left";

                        tk2dSpriteAnimationClip dieFrontLeft = ExpandUtility.DuplicateAnimationClip(clip);
                        dieFrontLeft.name = "die_front_left";
                        clipsToAdd.Add(dieFrontLeft);
                    }
                    else if (clip.name == "idle_front")
                    {
                        clip.name = "idle";

                        tk2dSpriteAnimationClip bigShot = ExpandUtility.DuplicateAnimationClip(clip);
                        bigShot.wrapMode = tk2dSpriteAnimationClip.WrapMode.Once;
                        bigShot.name     = "big_shot";
                        clipsToAdd.Add(bigShot);

                        tk2dSpriteAnimationClip charge = ExpandUtility.DuplicateAnimationClip(clip);
                        charge.wrapMode = tk2dSpriteAnimationClip.WrapMode.Loop;
                        charge.name     = "charge";
                        clipsToAdd.Add(charge);

                        // not really necessary since it's nuked from the animator further down
                        tk2dSpriteAnimationClip appear = ExpandUtility.DuplicateAnimationClip(clip);
                        appear.wrapMode = tk2dSpriteAnimationClip.WrapMode.Once;
                        appear.name     = "appear";
                        clipsToAdd.Add(appear);

                        tk2dSpriteAnimationClip introGunToggle = ExpandUtility.DuplicateAnimationClip(clip);
                        introGunToggle.wrapMode = tk2dSpriteAnimationClip.WrapMode.Once;
                        introGunToggle.name     = "intro2";
                        clipsToAdd.Add(introGunToggle);

                        introGunToggle.frames[0].eventInfo    = "guntoggle";
                        introGunToggle.frames[0].triggerEvent = true;
                    }
                    else if (clip.name == "summon")
                    {
                        clip.name     = "whistle";
                        clip.wrapMode = tk2dSpriteAnimationClip.WrapMode.Once;
                        // the summon vfx don't look right
                        // clip.frames[0].eventVfx = "summon_vfx";
                        // clip.frames[0].triggerEvent = true;
                    }
                    else if (clip.name == "pound")
                    {
                        clip.name = "jump_attack";
                        // Uses modified sound that plays faster on the first half to account for west bros having faster animation.
                        clip.frames[0].eventAudio   = "Play_EX_BOSS_westbros_slam_01";
                        clip.frames[0].triggerEvent = true;
                    }
                    else if (clip.name == "intro")
                    {
                        clip.wrapMode = tk2dSpriteAnimationClip.WrapMode.Once;
                        // this is setup in case we want the intro to continue looping during the boss card instead of the idle animation
                        // requires to change the intro doer so it sets finished to true once it reaches the first loop

                        //if (whichBro == WestBros.Nome)
                        //{
                        //    // nome's intro animation wasn't looping at the end like the others
                        //    var list = clip.frames.ToList();

                        //    list.RemoveAt(20);
                        //    list.RemoveAt(20);

                        //    clip.frames = list.ToArray();

                        //    clip.loopStart = 23;

                        //    clip.frames[23] = clip.frames[0];
                        //    clip.frames[24] = clip.frames[1];
                        //    clip.frames[25] = clip.frames[2];
                        //    clip.frames[26] = clip.frames[3];
                        //}
                        //else if (whichBro == WestBros.Tuc)
                        //{
                        //    // tuc's intro animation was off by one frame compared to the others
                        //    var list = clip.frames.ToList();

                        //    list.RemoveAt(15);

                        //    clip.frames = list.ToArray();

                        //    clip.loopStart = 21;
                        //}
                    }
                }

                animationClips.AddRange(clipsToAdd);

                tk2dSpriteAnimation spriteAnimation = animationsAsset.AddComponent <tk2dSpriteAnimation>();

                spriteAnimation.clips = animationClips.ToArray();

                tk2dSpriteAnimator spriteAnimator = outObject.GetComponent <tk2dSpriteAnimator>();

                spriteAnimator.Library = spriteAnimation;

                tk2dSprite sprite = outObject.GetComponent <tk2dSprite>();
                sprite.SetSprite(sourceSpriteCollection, $"BB_{whichBro.ToString().ToLower()}_idle_front_001");
                sprite.PlaceAtPositionByAnchor(new Vector3(0f, 0f), tk2dBaseSprite.Anchor.LowerCenter);

                AIAnimator animator = outObject.GetComponent <AIAnimator>();
                // removes the 'appear' animation
                animator.OtherAnimations.RemoveAt(0);

                outObject.transform.Find("shadow").localPosition += new Vector3(1.52f, 0.02f, 0);

                var shooter = SetupAIShooter(outObject);
                shooter.handObject = WestBrosHandPrefab.GetComponent <PlayerHandController>();

                DebrisObject hatPrefab = null;

                switch (whichBro)
                {
                case WestBros.Angel:
                    shooter.gunAttachPoint.position = new Vector3(-1.05f, 0.5f);
                    WestBrosAngelGUID = actor.EnemyGuid;
                    hatPrefab         = WestBrosAngelHatPrefab.GetComponent <DebrisObject>();
                    break;

                case WestBros.Nome:
                    shooter.gunAttachPoint.position = new Vector3(-1.05f, 0.4f);
                    WestBrosNomeGUID = actor.EnemyGuid;
                    hatPrefab        = WestBrosNomeHatPrefab.GetComponent <DebrisObject>();
                    break;

                case WestBros.Tuc:
                    shooter.gunAttachPoint.position = new Vector3(-1.05f, 0.5f);
                    WestBrosTucGUID = actor.EnemyGuid;
                    hatPrefab       = WestBrosTucHatPrefab.GetComponent <DebrisObject>();
                    break;
                }

                shooter.equippedGunId = WestBrosRevolverGenerator.GetWestBrosRevolverID(whichBro);

                if (shooter.equippedGunId == -1)
                {
                    ETGModConsole.Log("The West Bros Gun ID should have been set at this point already, but it wasn't. Assigning fallback gun.");
                    shooter.equippedGunId = isSmiley ? 35 : 22;
                }

                var hatLauncher = outObject.GetComponentInChildren <ExplosionDebrisLauncher>();

                // basically changes smiley's debris launcher into shades'
                hatLauncher.specifyArcDegrees = false;
                hatLauncher.minShards         = 1;
                hatLauncher.maxShards         = 1;

                hatLauncher.debrisSources = new DebrisObject[] { hatPrefab };

                // move the ring of bullets spawned by jumping
                var shootPoint = outObject.transform.Find("shoot point");
                shootPoint.position += new Vector3(1.5f, 0);

                // move the actual pixel colliders
                var rigidbody = outObject.GetComponent <SpeculativeRigidbody>();

                foreach (var item in rigidbody.PixelColliders)
                {
                    item.ManualOffsetX = 32;
                    item.Regenerate(outObject.transform);
                }

                // TODO balance
                actor.healthHaver.ForceSetCurrentHealth(600);
                actor.healthHaver.SetHealthMaximum(600);

                actor.RegenerateCache();

                ExpandCustomEnemyDatabase.AddEnemyToDatabase(outObject, actor.EnemyGuid, true);
                FakePrefab.MarkAsFakePrefab(outObject);
                UnityEngine.Object.DontDestroyOnLoad(outObject);
            }
            catch (Exception e)
            {
                ETGModConsole.Log($"Error setting up the western bro {whichBro}: " + e.ToString());
            }
        }
Beispiel #4
0
        public static void Generate(WestBros whichBro)
        {
            string name      = whichBro.ToString();
            string lowerName = name.ToLower();

            Gun gun = ETGMod.Databases.Items.NewGun($"{name}'s Revolver", $"gr_{lowerName}_rev");

            Game.Items.Rename($"outdated_gun_mods:{lowerName}'s_revolver", $"ex:{lowerName}s_revolver");

            // shades
            var baseGun = PickupObjectDatabase.GetById(22) as Gun;

            //// smiley
            //var baseGun = PickupObjectDatabase.GetById(35) as Gun;

            //gun.gameObject.AddComponent<NomesRevolver>();
            gun.SetShortDescription("TODO");
            gun.SetLongDescription("TODO");

            gun.SetupSprite(null, $"gr_{lowerName}_rev_idle_001", 8);

            gun.SetAnimationFPS(gun.shootAnimation, 12);
            gun.SetAnimationFPS(gun.enemyPreFireAnimation, 8);
            gun.SetAnimationFPS(gun.reloadAnimation, 8);

            // enemyPreFireAnimation is set to Loop by default for some reason
            gun.spriteAnimator.GetClipByName(gun.enemyPreFireAnimation).wrapMode = tk2dSpriteAnimationClip.WrapMode.Once;

            //ETGModConsole.Log("Shadess revolver animations");
            //foreach (var item in baseGun.spriteAnimator.Library.clips)
            //{
            //    ETGModConsole.Log(item.name + " " + item.frames + " " + item.fps + " " + item.maxFidgetDuration + " " + item.minFidgetDuration + " " + item.wrapMode + " " + item.loopStart, true);
            //}
            //ETGModConsole.Log("West Bros revolver animations");
            //foreach (var item in gun.spriteAnimator.Library.clips)
            //{
            //    ETGModConsole.Log(item.name + " " + item.frames + " " + item.fps + " " + item.maxFidgetDuration + " " + item.minFidgetDuration + " " + item.wrapMode + " " + item.loopStart, true);
            //}

            // Every modded gun has base projectile it works with that is borrowed from other guns in the game.
            // The gun names are the names from the JSON dump! While most are the same, some guns named completely different things. If you need help finding gun names, ask a modder on the Gungeon discord.
            // which means its the ETGMod.Databases.Items / PickupObjectDatabase.Instance.InternalGetByName name, aka the pickupobject.name

            gun.AddProjectileModuleFrom(baseGun, true, false);

            gun.gunSwitchGroup     = baseGun.gunSwitchGroup;
            gun.muzzleFlashEffects = baseGun.muzzleFlashEffects;

            //gun.AddMuzzle();
            //gun.muzzleOffset.localPosition = new Vector3(0.9f, 0.3f, 0f);
            gun.barrelOffset.localPosition = new Vector3(1.1f, 0.3f, 0f);

            //gun.shellCasing = defaultGun.shellCasing;
            //gun.shellsToLaunchOnFire = 0;
            //gun.shellsToLaunchOnReload = defaultGun.shellsToLaunchOnReload;
            //gun.reloadShellLaunchFrame = defaultGun.reloadShellLaunchFrame;

            gun.DefaultModule.shootStyle          = ProjectileModule.ShootStyle.SemiAutomatic;
            gun.DefaultModule.sequenceStyle       = ProjectileModule.ProjectileSequenceStyle.Random;
            gun.DefaultModule.cooldownTime        = 0.07f;
            gun.DefaultModule.numberOfShotsInClip = 6;
            gun.DefaultModule.angleVariance       = 4;
            gun.DefaultModule.ammoCost            = 0;

            gun.reloadTime = 1f;
            gun.gunClass   = GunClass.PISTOL;
            gun.SetBaseMaxAmmo(350);
            gun.quality = PickupObject.ItemQuality.EXCLUDED;
            gun.encounterTrackable.EncounterGuid = $"this is {name}'s new revolver";

            Projectile projectile = Object.Instantiate <Projectile>(gun.DefaultModule.projectiles[0]);

            projectile.gameObject.SetActive(false);
            FakePrefab.MarkAsFakePrefab(projectile.gameObject);
            Object.DontDestroyOnLoad(projectile);

            gun.DefaultModule.projectiles[0] = projectile;

            projectile.baseData.damage  = 6f;
            projectile.transform.parent = gun.barrelOffset;

            var comp = projectile.gameObject.AddComponent <SkullRevolverBullet>();

            comp.jamsEnemies = false;

            ETGMod.Databases.Items.Add(gun, null, "ANY");

            switch (whichBro)
            {
            case WestBros.Angel:
                WestBrosAngelGunID = gun.PickupObjectId;
                break;

            case WestBros.Nome:
                WestBrosNomeGunID = gun.PickupObjectId;
                break;

            case WestBros.Tuc:
                WestBrosTucGunID = gun.PickupObjectId;
                break;
            }
        }