[1.5] HugsLib (11.0.3) Lightweight modding library

Started by UnlimitedHugs, December 15, 2016, 02:20:14 PM

Previous topic - Next topic

DaemonDeathAngel

Now that I know what this system is capable of, it is a life saver. One feature I really like is the gist upload feature for error logs.

System.Linq

Would you kindly detour the FoodUtility code to make it so that more than one ingestThought can be put on a food item? I'd do it myself but it's so tangential to my mod I thought it might be better as part of the library.

UnlimitedHugs

Quote from: Psychology on December 26, 2016, 10:45:02 AM
Would you kindly detour the FoodUtility code to make it so that more than one ingestThought can be put on a food item? I'd do it myself but it's so tangential to my mod I thought it might be better as part of the library.

No can do. I'd like to avoid making detours in the library itself, if I can help it. Also, this is unnecessary bloat that I would have to support from now on.
HugsLib - AllowTool - Remote Tech - Map Reroll - Defensive Positions: Github, Steam


System.Linq

How do you recommend handling compatibility for two mods that detour the same methods? With CCL it seemed like you could just load one after the other and it would overwrite that mod's detour with the new one.

UnlimitedHugs

Quote from: Wishmaster on December 28, 2016, 01:02:01 PM
I used the detour lib of HugsLibs. It's amazing, convenient and safer.

Thank you. I'm glad this makes your modding experience easier.

Quote from: Psychology on December 28, 2016, 02:51:38 PM
How do you recommend handling compatibility for two mods that detour the same methods? With CCL it seemed like you could just load one after the other and it would overwrite that mod's detour with the new one.

Looks like you're right, CCL allows detours to be overwritten. I will add that to the library.
Of course, the way of properly resolving the situation is slightly more complex. I see three options:

  • Looking for an alternative approach. Object replacement and comps come to mind, but that is not always viable.
  • Finding a method that is closely related. In some situations there is a method that can be detoured higher or lower on the call chain, which would sidestep the conflict.
  • Getting in touch with the other mod author. If there is a specific mod that is conflicting with your own, you could contact the author and ask him to add an event or callback for that detour. In your own mod you could then access that using reflection to avoid incurring a dependency. When the reflection fails, it means the other mod is not loaded and you can apply your own detour.
HugsLib - AllowTool - Remote Tech - Map Reroll - Defensive Positions: Github, Steam

System.Linq

#36
What I ended up doing was removing the DetourMethodAttributes and instead detouring the methods using TryCompatibleDetour() on DefsLoaded(), and disabling certain parts of the mod if those detours could not be made.

The only downside is the new annoying yellow warning message about a dangerous detour on Pawn_RelationsTracker, which shows up with TryCompatibleDetour() but not with a DetourMethodAttribute.

I think that not allowing overwriting by default is probably the superior solution, since it will draw attention immediately to conflicts which might otherwise be subtle and insidious. You just need to document how to handle it.

Or perhaps the best solution is to overwrite it by default, and log a warning message about it, since that's what most mod authors will end up doing anyway.

UnlimitedHugs

Quote from: Psychology on December 28, 2016, 08:08:46 PM
What I ended up doing was removing the DetourMethodAttributes and instead detouring the methods using TryCompatibleDetour() on DefsLoaded()

At the moment that's a good way to run fallback code when a detour fails.

Quote from: Psychology on December 28, 2016, 08:08:46 PM
The only downside is the new annoying yellow warning message about a dangerous detour on Pawn_RelationsTracker, which shows up with TryCompatibleDetour() but not with a DetourMethodAttribute.

That warning should appear in both cases- it is meant to highlight a problem with the target method signature or its parent type. Could you show me your implemetation of the detour? I'd like to replicate the discrepancy.

As for overwriting by default, it doesn't really matter which mod gets their detour in when there's a conflict. But there should be a way for the mod to detect the collision and run some fallback code. Perhaps adding a callback method to the attribute is the way to go.

HugsLib - AllowTool - Remote Tech - Map Reroll - Defensive Positions: Github, Steam

System.Linq

Quote from: UnlimitedHugs on December 28, 2016, 08:29:46 PM
That warning should appear in both cases- it is meant to highlight a problem with the target method signature or its parent type. Could you show me your implemetation of the detour? I'd like to replicate the discrepancy.


namespace Psychology.Detour
{
    internal static class _Pawn_RelationsTracker
    {
        internal static FieldInfo _pawn;

        internal static Pawn GetPawn(this Pawn_RelationsTracker _this)
        {
            if (_Pawn_RelationsTracker._pawn == null)
            {
                _Pawn_RelationsTracker._pawn = typeof(Pawn_RelationsTracker).GetField("pawn", BindingFlags.Instance | BindingFlags.NonPublic);
                if (_Pawn_RelationsTracker._pawn == null)
                {
                    Log.ErrorOnce("Unable to reflect Pawn_RelationsTracker.pawn!", 305432421);
                }
            }
            return (Pawn)_Pawn_RelationsTracker._pawn.GetValue(_this);
        }

        [DetourMethod(typeof(Pawn_RelationsTracker),"Notify_RescuedBy")]
        internal static void _Notify_RescuedBy(this Pawn_RelationsTracker t, Pawn rescuer)
        {

            if (rescuer.RaceProps.Humanlike && t.canGetRescuedThought)
            {
                t.GetPawn().needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.RescuedMe, rescuer);
                t.canGetRescuedThought = false;
                rescuer.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOfPsychology.RescuedBleedingHeart, t.GetPawn());
            }
        }
       
        internal static float _SecondaryRomanceChanceFactor(this Pawn_RelationsTracker t, Pawn otherPawn)
        {
            Pawn pawn = t.GetPawn();
            if (pawn.def != otherPawn.def || pawn == otherPawn)
            {
                return 0f;
            }
            Rand.PushSeed();
            Rand.Seed = pawn.HashOffset();
            bool flag = Rand.Value < 0.015f;
            Rand.PopSeed();
            float num = 1f;
            float num2 = 1f;
            float ageBiologicalYearsFloat = pawn.ageTracker.AgeBiologicalYearsFloat;
            float ageBiologicalYearsFloat2 = otherPawn.ageTracker.AgeBiologicalYearsFloat;
            PsychologyPawn realPawn = pawn as PsychologyPawn;
            if (PsychologyBase.ActivateKinsey() && realPawn != null)
            {
                flag = true;
                float kinsey = 3 - realPawn.sexuality.kinseyRating;
                float homo = (pawn.gender == otherPawn.gender) ? 1f : -1f;
                num2 = Mathf.InverseLerp(3f, 0f, kinsey * homo);
            }
            if (pawn.gender == Gender.Male)
            {
                if (!flag)
                {
                    if (pawn.RaceProps.Humanlike && pawn.story.traits.HasTrait(TraitDefOf.Gay))
                    {
                        if (otherPawn.gender == Gender.Female)
                        {
                            return 0f;
                        }
                    }
                    else if (otherPawn.gender == Gender.Male)
                    {
                        return 0f;
                    }
                }
                num2 = GenMath.FlatHill(0f, 16f, 20f, ageBiologicalYearsFloat, ageBiologicalYearsFloat + 15f, 0.07f, ageBiologicalYearsFloat2);
                if (pawn.RaceProps.Humanlike && pawn.story.traits.HasTrait(TraitDefOfPsychology.OpenMinded))
                {
                    num2 = 1f;
                }
            }
            else if (pawn.gender == Gender.Female)
            {
                if (!flag)
                {
                    if (pawn.RaceProps.Humanlike && pawn.story.traits.HasTrait(TraitDefOf.Gay))
                    {
                        if (otherPawn.gender == Gender.Male)
                        {
                            return 0f;
                        }
                    }
                    else if (otherPawn.gender == Gender.Female)
                    {
                        num = 0f;
                    }
                }
                if ((ageBiologicalYearsFloat2 < ageBiologicalYearsFloat - 10f) && (pawn.story.traits.HasTrait(TraitDefOfPsychology.OpenMinded)))
                {
                    return 0f;
                }
                if (ageBiologicalYearsFloat2 < ageBiologicalYearsFloat - 3f)
                {
                    num2 = Mathf.InverseLerp(ageBiologicalYearsFloat - 10f, ageBiologicalYearsFloat - 3f, ageBiologicalYearsFloat2) * 0.2f;
                }
                else
                {
                    num2 = GenMath.FlatHill(0.2f, ageBiologicalYearsFloat - 3f, ageBiologicalYearsFloat, ageBiologicalYearsFloat + 10f, ageBiologicalYearsFloat + 30f, 0.1f, ageBiologicalYearsFloat2);
                }
                if (pawn.RaceProps.Humanlike && pawn.story.traits.HasTrait(TraitDefOfPsychology.OpenMinded))
                {
                    num2 = 1f;
                }
            }
            float num3 = 1f;
            num3 *= Mathf.Lerp(0.2f, 1f, otherPawn.health.capacities.GetEfficiency(PawnCapacityDefOf.Talking));
            num3 *= Mathf.Lerp(0.2f, 1f, otherPawn.health.capacities.GetEfficiency(PawnCapacityDefOf.Manipulation));
            num3 *= Mathf.Lerp(0.2f, 1f, otherPawn.health.capacities.GetEfficiency(PawnCapacityDefOf.Moving));
            if (pawn.RaceProps.Humanlike && pawn.story.traits.HasTrait(TraitDefOfPsychology.OpenMinded))
            {
                num3 = 1f;
            }
            float num4 = 1f;
            foreach (PawnRelationDef current in pawn.GetRelations(otherPawn))
            {
                num4 *= current.attractionFactor;
            }
            int num5 = 0;
            if (otherPawn.RaceProps.Humanlike)
            {
                num5 = otherPawn.story.traits.DegreeOfTrait(TraitDefOf.Beauty);
            }
            if (pawn.RaceProps.Humanlike && pawn.story.traits.HasTrait(TraitDefOfPsychology.OpenMinded))
            {
                num5 = 0;
            }
            float num6 = 1f;
            if (num5 < 0)
            {
                num6 = 0.3f;
            }
            else if (num5 > 0)
            {
                num6 = 2.3f;
            }
            float num7 = Mathf.InverseLerp(15f, 18f, ageBiologicalYearsFloat);
            float num8 = Mathf.InverseLerp(15f, 18f, ageBiologicalYearsFloat2);
            return num * num2 * num3 * num4 * num7 * num8 * num6;
        }
    }
}


Quote from: UnlimitedHugs on December 28, 2016, 08:29:46 PM
As for overwriting by default, it doesn't really matter which mod gets their detour in when there's a conflict. But there should be a way for the mod to detect the collision and run some fallback code. Perhaps adding a callback method to the attribute is the way to go.

As long as you document how to use it!

RemingtonRyder

I used HugsLib for a simple ThingDef stat value changer. It's super effective. :)

UnlimitedHugs

Updated to 2.2.0

I have implemented a new attribute: DetourFallback.
It complements the existing detour by attribute system by allowing a method to be designated as a callback for when a detour fails. This can happen due to another mod being the first to detour the same method, or the method in question becoming unavailable.
You can read up on the new system on the wiki.

@Psychology
I wasn't able to replicate the discrepancy with the warnings, but then again I didn't have your manual detour implementation.
@MarvinKosh
I'm glad it worked out for you :)
HugsLib - AllowTool - Remote Tech - Map Reroll - Defensive Positions: Github, Steam

System.Linq

#41
Would you kindly make Logger internal and not protected? It's unclear how you're supposed to use it outside ModBase.

e: I guess I don't have to in this case.

e2: DetourFallback works perfectly. Good work!

UnlimitedHugs

Quote from: Psychology on December 29, 2016, 06:59:35 AM
Would you kindly make Logger internal and not protected? It's unclear how you're supposed to use it outside ModBase.

This is how I expose the logger in my own mods:

internal new ModLogger Logger {
    get { return base.Logger; }
}
HugsLib - AllowTool - Remote Tech - Map Reroll - Defensive Positions: Github, Steam

scuba156

I'm currently using HugsLib and HugsLibChecker in my wip mod, detours work great. I've tried to link back to HugsLib where appropriate.

Any chance of getting pastebin support? Possibly default to pastebin if an apikey has been set in modbase?

UnlimitedHugs

Quote from: scuba156 on December 29, 2016, 08:35:50 AM
Any chance of getting pastebin support? Possibly default to pastebin if an apikey has been set in modbase?

What would be the advantage of using Pastebin over the GitHub gists?
HugsLib - AllowTool - Remote Tech - Map Reroll - Defensive Positions: Github, Steam