Example Implementation: MwSkinEvents.cs

Updated a week ago
using MwSkinAdditions;
using RoR2;
using UnityEngine;

namespace MioHuntress {
    public static class MwSkinEvents {
        public static VoiceGroup[] voiceGroups;

        public static VoiceArray blockDamage = new VoiceArray(
            new VoiceInfo[] {
                new VoiceInfo("Play_MioHuntress_Block_1", 1.1f),
                new VoiceInfo("Play_MioHuntress_Block_2", 1.7f),
            }
        );

        public static VoiceArray damage = new VoiceArray(
            new VoiceInfo[] {
                new VoiceInfo("Play_MioHuntress_Damage_1", 0.6f),
                new VoiceInfo("Play_MioHuntress_Damage_2", 0.8f),
                new VoiceInfo("Play_MioHuntress_Damage_3", 0.9f),
                new VoiceInfo("Play_MioHuntress_Damage_4", 0.6f),
                new VoiceInfo("Play_MioHuntress_Damage_5", 0.5f),
                new VoiceInfo("Play_MioHuntress_Damage_6", 0.5f),
                new VoiceInfo("Play_MioHuntress_Damage_7", 0.5f),
            }
        );

        public static VoiceArray death = new VoiceArray(
            new VoiceInfo[] {
                new VoiceInfo("Play_MioHuntress_Death_1", 2.7f),
                new VoiceInfo("Play_MioHuntress_Death_2", 2.1f),
                new VoiceInfo("Play_MioHuntress_Death_3", 1.0f),
            }
        );

        public static VoiceArray defeatBossGroup = new VoiceArray(
            new VoiceInfo[] {
                new VoiceInfo("Play_MioHuntress_Defeat_Boss_Group_1", 1.5f),
                new VoiceInfo("Play_MioHuntress_Defeat_Boss_Group_2", 1.3f),
                new VoiceInfo("Play_MioHuntress_Defeat_Boss_Group_3", 1.8f),
                new VoiceInfo("Play_MioHuntress_Defeat_Boss_Group_3", 1.3f),
            }
        );

        public static VoiceArray getItem = new VoiceArray(
            new VoiceInfo[] {
                new VoiceInfo("Play_MioHuntress_Get_Item_1", 1.0f),
                new VoiceInfo("Play_MioHuntress_Get_Item_2", 2.5f),
                new VoiceInfo("Play_MioHuntress_Get_Item_3", 1.4f),
                new VoiceInfo("Play_MioHuntress_Get_Item_4", 1.6f),
                new VoiceInfo("Play_MioHuntress_Get_Item_5", 1.6f),
            }
        );

        public static VoiceArray heal = new VoiceArray(
            new VoiceInfo[] {
                new VoiceInfo("Play_MioHuntress_Heal_1", 1.7f),
                new VoiceInfo("Play_MioHuntress_Heal_2", 1.5f),
            }
        );

        public static VoiceArray leavePod = new VoiceArray(
            new VoiceInfo[] {
                new VoiceInfo("Play_MioHuntress_Leave_Pod_1", 1.0f),
                new VoiceInfo("Play_MioHuntress_Leave_Pod_2", 1.1f),
            }
        );

        public static VoiceArray levelUp = new VoiceArray(
            new VoiceInfo[] {
                new VoiceInfo("Play_MioHuntress_Level_Up_1", 1.5f),
                new VoiceInfo("Play_MioHuntress_Level_Up_2", 1.3f),
                new VoiceInfo("Play_MioHuntress_Level_Up_2", 1.6f),
            }
        );

        public static VoiceArray lowHealth = new VoiceArray(
            new VoiceInfo[] {
                new VoiceInfo("Play_MioHuntress_Low_Health_1", 3.4f),
                new VoiceInfo("Play_MioHuntress_Low_Health_2", 1.6f),
            }
        );

        public static VoiceArray secondary = new VoiceArray(
            new VoiceInfo[] {
                new VoiceInfo("Play_MioHuntress_Secondary_1", 1.0f),
                new VoiceInfo("Play_MioHuntress_Secondary_2", 0.8f),
                new VoiceInfo("Play_MioHuntress_Secondary_3", 1.1f),
                new VoiceInfo("Play_MioHuntress_Secondary_4", 1.3f),
                new VoiceInfo("Play_MioHuntress_Secondary_5", 1.6f),
            }
        );

        public static VoiceArray shrineFailure = new VoiceArray(
            new VoiceInfo[] {
                new VoiceInfo("Play_MioHuntress_Shrine_Failure_1", 0.7f),
                new VoiceInfo("Play_MioHuntress_Shrine_Failure_2", 0.7f),
            }
        );

        public static VoiceArray shrineSuccess = new VoiceArray(
            new VoiceInfo[] {
                new VoiceInfo("Play_MioHuntress_Shrine_Success_1", 1.5f),
                new VoiceInfo("Play_MioHuntress_Shrine_Success_2", 0.8f),
                new VoiceInfo("Play_MioHuntress_Shrine_Success_3", 0.6f),
            }
        );

        public static VoiceArray special = new VoiceArray(
            new VoiceInfo[] {
                new VoiceInfo("Play_MioHuntress_Special_1", 1.8f),
                new VoiceInfo("Play_MioHuntress_Special_2", 1.2f),
                new VoiceInfo("Play_MioHuntress_Special_3", 1.2f),
                new VoiceInfo("Play_MioHuntress_Special_4", 1.3f),
                new VoiceInfo("Play_MioHuntress_Special_5", 1.0f),
            }
        );

        public static VoiceArray endTP = new VoiceArray(
            new VoiceInfo[] {
                new VoiceInfo("Play_MioHuntress_TP_End_1", 2.6f),
                new VoiceInfo("Play_MioHuntress_TP_End_2", 2.3f),
                new VoiceInfo("Play_MioHuntress_TP_End_3", 2.2f),
                new VoiceInfo("Play_MioHuntress_TP_End_4", 3.2f),
                new VoiceInfo("Play_MioHuntress_TP_End_5", 3.0f),
                new VoiceInfo("Play_MioHuntress_TP_End_6", 3.0f),
                new VoiceInfo("Play_MioHuntress_TP_End_7", 2.1f),
                new VoiceInfo("Play_MioHuntress_TP_End_8", 1.6f),
            }
        );

        public static VoiceArray startTP = new VoiceArray(
            new VoiceInfo[] {
                new VoiceInfo("Play_MioHuntress_TP_Start_1", 2.0f),
                new VoiceInfo("Play_MioHuntress_TP_Start_2", 2.2f),
                new VoiceInfo("Play_MioHuntress_TP_Start_3", 1.6f),
                new VoiceInfo("Play_MioHuntress_TP_Start_4", 3.4f),
                new VoiceInfo("Play_MioHuntress_TP_Start_5", 2.8f),
                new VoiceInfo("Play_MioHuntress_TP_Start_6", 3.0f),
                new VoiceInfo("Play_MioHuntress_TP_Start_7", 1.6f),
                new VoiceInfo("Play_MioHuntress_TP_Start_8", 1.9f),
                new VoiceInfo("Play_MioHuntress_TP_Start_9", 1.4f),
                new VoiceInfo("Play_MioHuntress_TP_Start_10", 2.7f),
            }
        );

        public static void Init(SkinDef skinDef) {
            EventSub eventSub = new EventSub(
                skinDef,
                new EventSubOptions() {
                    boneTransformations = new BoneTransformation[] {
                        new BoneTransformation("HuntressArmature/ROOT/base/stomach/chest/upper_arm.l", Vector3.one * 0.822f, Vector3.zero),
                        new BoneTransformation("HuntressArmature/ROOT/base/stomach/chest/upper_arm.r", Vector3.one * 0.822f, Vector3.zero),
                        new BoneTransformation("HuntressArmature/ROOT/base/BowRoot", Vector3.one * 0.822f, Vector3.zero),
                    },

                    useAnimations = true,

                    blinkAnimations = new BlendShapeAnimation[] {
                        new BlinkBlendShapeAnimation("HuntressMesh", "LowBlink")
                    },

                    voiceGroups = voiceGroups,

                    conditionalIdleAnimations = new IdleAnimation[] {
                        new IdleAnimation(
                            new BlendShapeAnimation[] {
                                new BlendShapeAnimation("HuntressMesh", "EyesTense", 0, -2, 0.3f, Mathf.Infinity, 0.3f),
                            },
                            InCombat,
                            true
                        ),
                        new IdleAnimation(
                            new BlendShapeAnimation[] {
                                new BlendShapeAnimation("HuntressMesh", "BrowHurt", 1, -1, 0.3f, Mathf.Infinity, 0.3f),
                            },
                            IsLowHealth,
                            true
                        )
                    }
                }
            );

            eventSub.TakeDamage += ExpressionOnHit;
            eventSub.Death += ExpressionOnDeath;
            eventSub.ShrineFailure += ExpressionOnShrineFailure;
            eventSub.LevelUp += ExpressionOnHoldoutOnLevelOnMithrix;
            eventSub.MithrixDefeat += ExpressionOnHoldoutOnLevelOnMithrix;
            eventSub.HoldoutZoneCharged += ExpressionOnHoldoutOnLevelOnMithrix;
            eventSub.GetItem += ExpressionOnItem;

            eventSub.TakeDamage += LowHealthLine;
            eventSub.Heal += HeavyHealLine;
            eventSub.TakeDamage += (GameObject body, DamageReport damageReport) => { body?.GetComponent<VoiceController>()?.TryPlayRandomUniqueSoundServer(damage, body, 0.4f); };
            eventSub.Death += (GameObject body) => { body?.GetComponent<VoiceController>()?.TryPlayRandomSoundServer(death, body); };
            eventSub.DefeatBossGroup += (GameObject body) => { body?.GetComponent<VoiceController>()?.TryPlayRandomUniqueSoundServer(defeatBossGroup, body); };
            eventSub.TeleporterStart += (GameObject body) => { body?.GetComponent<VoiceController>()?.TryPlayRandomUniqueSoundServer(startTP, body); };
            eventSub.TeleporterEnd += (GameObject body) => { body?.GetComponent<VoiceController>()?.TryPlayRandomUniqueSoundServer(endTP, body); };
            eventSub.LeavePod += (GameObject body) => { body?.GetComponent<VoiceController>()?.TryPlayRandomUniqueSoundServer(leavePod, body); };
            eventSub.GetItem += (GameObject body, ItemIndex itemIndex) => { body?.GetComponent<VoiceController>()?.TryPlayRandomUniqueSoundServer(getItem, body, 0.75f); };
            eventSub.ShrineSuccess += (GameObject body) => { body?.GetComponent<VoiceController>()?.TryPlayRandomUniqueSoundServer(shrineSuccess, body); };
            eventSub.ShrineFailure += (GameObject body) => { body?.GetComponent<VoiceController>()?.TryPlayRandomUniqueSoundServer(shrineFailure, body); };
            eventSub.LevelUp += (GameObject body) => { body?.GetComponent<VoiceController>()?.TryPlayRandomUniqueSoundServer(levelUp, body, 0.25f); };
            eventSub.BearDamageBlock += (GameObject body) => { body?.GetComponent<VoiceController>()?.TryPlayRandomUniqueSoundServer(blockDamage, body, 0.6f); };
            eventSub.UseSpecial += (GameObject body) => { body?.GetComponent<VoiceController>()?.TryPlayRandomUniqueSoundServer(special, body, 0.45f); };
            eventSub.UseSecondary += (GameObject body) => { body?.GetComponent<VoiceController>()?.TryPlayRandomUniqueSoundServer(secondary, body, 0.4f); };

            eventSub.Init();
        }

        private static bool InCombat(ExpressionController expressionController) {
            CharacterBody characterBody = expressionController.characterBody;
            if (characterBody != null) {
                return !characterBody.outOfCombat;
            }
            return false;
        }

        private static bool IsLowHealth(ExpressionController expressionController) {
            CharacterBody characterBody = expressionController.characterBody;
            if (characterBody != null) {
                return characterBody?.healthComponent?.combinedHealthFraction <= 0.25f;
            }
            return false;
        }

        private static void LowHealthLine(GameObject body, DamageReport damageReport) {
            if (damageReport?.victim?.GetComponent<HealthComponent>().combinedHealthFraction <= 0.25f) {
                body?.GetComponent<VoiceController>()?.TryPlayRandomUniqueSoundServer(lowHealth, body, 0.8f);
            }
        }

        private static void HeavyHealLine(GameObject body, float amount) {
            if (body?.GetComponent<VoiceController>() is VoiceController voiceController) {
                if (body?.GetComponent<CharacterBody>()?.healthComponent is HealthComponent healthComponent
                    && amount >= 0.5f * healthComponent.fullCombinedHealth
                    && healthComponent.combinedHealth < 0.5f * healthComponent.fullCombinedHealth) {
                    voiceController.TryPlayRandomSoundServer(heal, body);
                }
            }
        }

        private static void ExpressionOnHit(GameObject body, DamageReport damageReport) {
            if (damageReport?.victim?.GetComponent<HealthComponent>().combinedHealthFraction > 0.25f && body.GetComponent<ExpressionController>() is ExpressionController expressionController) {
                expressionController.TryPlayAnimation(new BlendShapeAnimation("HuntressMesh", "HighBlink", 0, 0, 0.1f, 0.1f, 0.1f, true));
                expressionController.TryPlayAnimation(new BlendShapeAnimation("HuntressMesh", "BrowHurt", 1, 0, 0.1f, 0.1f, 0.1f));
                expressionController.TryPlayAnimation(new BlendShapeAnimation("HuntressMesh", "MouthHurt", 2, 0, 0.1f, 0.1f, 0.1f));
            }
        }

        private static void ExpressionOnShrineFailure(GameObject body) {
            if (body.GetComponent<ExpressionController>() is ExpressionController expressionController) {
                expressionController.TryPlayAnimation(new BlendShapeAnimation("HuntressMesh", "EyesDisappointed", 0, 0, 0.2f, 0.4f, 0.2f, true));
                expressionController.TryPlayAnimation(new BlendShapeAnimation("HuntressMesh", "MouthFrown", 2, 0, 0.2f, 0.4f, 0.2f));
            }
        }

        private static void ExpressionOnHoldoutOnLevelOnMithrix(GameObject body) {
            if (body.GetComponent<ExpressionController>() is ExpressionController expressionController) {
                expressionController.TryPlayAnimation(new BlendShapeAnimation("HuntressMesh", "BrowHappy", 1, 0, 0.2f, 0.6f, 0.2f));
                expressionController.TryPlayAnimation(new BlendShapeAnimation("HuntressMesh", "mouth_smile_A", 2, 0, 0.2f, 0.6f, 0.2f));
            }
        }

        private static void ExpressionOnItem(GameObject body, ItemIndex itemIndex) {
            if (body.GetComponent<ExpressionController>() is ExpressionController expressionController) {
                expressionController.TryPlayAnimation(new BlendShapeAnimation("HuntressMesh", "HighBlink", 0, 0, 0.2f, 0.6f, 0.2f, true));
                expressionController.TryPlayAnimation(new BlendShapeAnimation("HuntressMesh", "BrowHappy", 1, 0, 0.2f, 0.6f, 0.2f));
                expressionController.TryPlayAnimation(new BlendShapeAnimation("HuntressMesh", "mouth_smile_A", 2, 0, 0.2f, 0.6f, 0.2f));
            }
        }

        private static void ExpressionOnDeath(GameObject body) {
            if (body.GetComponent<ExpressionController>() is ExpressionController expressionController) {
                expressionController.TryPlayAnimation(new BlendShapeAnimation("HuntressMesh", "HighBlink", 0, 2, 0.2f, Mathf.Infinity, 0.2f, true));
                expressionController.TryPlayAnimation(new BlendShapeAnimation("HuntressMesh", "BrowHurt", 1, 2, 0.2f, Mathf.Infinity, 0.2f));
                expressionController.TryPlayAnimation(new BlendShapeAnimation("HuntressMesh", "mouth_i_B", 2, 2, 0.2f, Mathf.Infinity, 0.2f));
            }
        }
        public static void CreateVoiceGroups() {
            RoR2Application.onLoadFinished += Options.OnLoadFinished;

            voiceGroups = new VoiceGroup[] {
                new VoiceGroup(
                    new VoiceArray[] { leavePod },
                    0.5f,
                    0.5f
                ),

                new VoiceGroup(
                    new VoiceArray[] { death },
                    5f,
                    5f
                ),

                new VoiceGroup(
                    new VoiceArray[] { getItem },
                    15f,
                    60f
                ),

                new VoiceGroup(
                    new VoiceArray[] { shrineSuccess, shrineFailure },
                    10f,
                    20f
                ),

                new VoiceGroup(
                    new VoiceArray[] { levelUp },
                    10f,
                    30f
                ),

                new VoiceGroup(
                    new VoiceArray[] { damage, secondary, special, blockDamage, heal, lowHealth },
                    0.5f,
                    8f
                ),

                new VoiceGroup(
                    new VoiceArray[] { startTP, defeatBossGroup, endTP },
                    5f,
                    10f
                ),
            };
        }
    }
}