Feature Request: Method to categorize Deeds for evaluation during rumor processing

Announcements, support questions, and discussion for Love/Hate.
FrostedLink
Posts: 12
Joined: Fri Jan 07, 2022 7:43 pm

Feature Request: Method to categorize Deeds for evaluation during rumor processing

Post by FrostedLink »

I'm working on a project where I think having different tiers of deeds would be helpful to designers.
I'd like to branch logic into either the DefaultEvaluateRumor or a delegate based on these categories.
Currently, the only way I see to do this is with string manipulation. That doesn't seem very performant, or designer friendly.

For a concrete example, consider 'Normal' and 'Romantic' deeds.

Normal - Be helpful, Steal, Give Gift.
Romantic - Flirt, Embrace

Set up 2 different DeedTemplateLibraries. Then in the EvaluateRumorDelagate for the NPC, branch to either:
DefaultEvaluateRumor = Normal
RomanticEvaluateRumor = Romantic

I envision this as a 1:1 relationship between DeedTemplateLibrary and the branching logic.

I've mocked up a sample using the scriptableObject as Enum pattern, but I think this can be improved. Passing around the whole library, probably isn't the best idea. But it does add the least amount of code.

Possible code changes:
Deed.cs new property:
public DeedTemplateLibrary library;

Deed.cs new GetDeed signature:
public static Deed GetNew(DeedTemplate deedTemplate, int actorFactionID, int targetFactionID, float actorPowerLevel, DeedTemplateLibrary library)

Rumor.cs new property:
public DeedTemplateLibrary library;

Add supporting assignments in Rumor.cs > Clear(), AssignDeed(Deed), AssignRumor(Rumor)

Then in my delegate I can just call:
[SerializeField] private DeedTemplateLibrary romanticLibrary;
private Rumor MyEvaluateRumor(Rumor rumor, FactionMember source){
if (rumor.library == romanticLibrary)
{
//branch to romantic deed logic
}else{
//branch to default logic
}
}

If you're familiar with a better way to handle this natively, I'm all ears.
User avatar
Tony Li
Posts: 22112
Joined: Thu Jul 18, 2013 1:27 pm

Re: Feature Request: Method to categorize Deeds for evaluation during rumor processing

Post by Tony Li »

I'll give this some thought.

In the meantime, Rumors already have a customData field:

Code: Select all

public object customData = null;
For a very generalized solution, we could add a customData field to Deeds, too. When creating a Rumor object from a Deed, the Rumor object could set its customData to the same value as the Deed's.

In this case, it might be useful to ensure that the Deed Template Library inspector script can be overridden to allow designers to assign something to the customData field.

---

Alternatively, it might be simpler to define a DeedCategory ScriptableObject asset type, and add a DeedCategory field to Deeds (and Rumors). The Deed Template Library inspector would then allow you to assign a DeedCategory to each deed. Or, if you're creating deeds manually, you could pass the constructor a DeedCategory.
FrostedLink
Posts: 12
Joined: Fri Jan 07, 2022 7:43 pm

Re: Feature Request: Method to categorize Deeds for evaluation during rumor processing

Post by FrostedLink »

Yes, I saw that customData field in Rumors. Though I wasn't sure why it was there. I didn't see any linkages and without a matching copy in Deeds, it is ambiguous.
Alternatively, it might be simpler to define a DeedCategory ScriptableObject asset type, and add a DeedCategory field to Deeds (and Rumors). The Deed Template Library inspector would then allow you to assign a DeedCategory to each deed. Or, if you're creating deeds manually, you could pass the constructor a DeedCategory.
This is exactly how I envisioned a production ready example to look. I've been toying with the Unity Atoms framework, and when I saw the gap for categories I thought a StringAtom would fit great here.

I didn't build it into my prototype simply because I've found messing with the source of a paid asset tends to cause headaches or version locking yourself.

A possible addition to this would be a CategoriesDB ScriptableObject. Then nest the DeedCategory inside it. The DeedTemplateLibraryEditor could then build dropdowns based on the DeedCategory ScriptableObject definitions. Leave 'None' or 'Null' as the default, and it won't break production code.

I don't know how useful this next part could be, but hierarchical categories could nest inside DeedCategory "Romantic/Aggressive".

By the way, is there a reason the Deed.cs > GetDeed signature takes most of the parameters of a DeedTemplate instead of just passing the entire DeedTemplate?
I was thinking maybe it was a decoupling approach, but it is so specialized I couldn't think of a reason to not overload it and make DeedReporter's call a bit simpler.
User avatar
Tony Li
Posts: 22112
Joined: Thu Jul 18, 2013 1:27 pm

Re: Feature Request: Method to categorize Deeds for evaluation during rumor processing

Post by Tony Li »

FrostedLink wrote: Sat Jan 08, 2022 9:36 amYes, I saw that customData field in Rumors. Though I wasn't sure why it was there. I didn't see any linkages and without a matching copy in Deeds, it is ambiguous.
It was added by request for devs who wanted to add additional annotations to rumors. Since rumors are stored in a faction member's memory, it can be used to remember additional information related to the rumor.
FrostedLink wrote: Sat Jan 08, 2022 9:36 amThis is exactly how I envisioned a production ready example to look. I've been toying with the Unity Atoms framework, and when I saw the gap for categories I thought a StringAtom would fit great here.
Love/Hate does have a StringAsset type that's essentially a StringAtom. But I think a specific DeedCategory type will be better in this case because then you don't need a database. Unity's default object selection popup window can just show all DeedCategory assets in the project. The DeedTemplateLibraryEditor could also populate a dropdown this way.
FrostedLink wrote: Sat Jan 08, 2022 9:36 amI don't know how useful this next part could be, but hierarchical categories could nest inside DeedCategory "Romantic/Aggressive".
I may make sure that dropdowns group DeedCategories into hierarchical subgroups using forward slashes. So if there's a DeedCategory "Romantic/Aggressive" and a DeedCategory "Romantic/Passive", the dropdown will show both under the submenu "Romantic".
FrostedLink wrote: Sat Jan 08, 2022 9:36 amBy the way, is there a reason the Deed.cs > GetDeed signature takes most of the parameters of a DeedTemplate instead of just passing the entire DeedTemplate?
I was thinking maybe it was a decoupling approach, but it is so specialized I couldn't think of a reason to not overload it and make DeedReporter's call a bit simpler.
Mostly decoupling. The scripts in Core don't have any dependencies on the scripts in Interaction. But it's really a false distinction. They're all part of the same asset, so there's no need to keep a decoupling wall between Core and Script if it's more convenient for the end programmers to have a version of GetDeed that accepts a DeedTemplate.
FrostedLink
Posts: 12
Joined: Fri Jan 07, 2022 7:43 pm

Re: Feature Request: Method to categorize Deeds for evaluation during rumor processing

Post by FrostedLink »

I wanted to add a bit to the community here. Jealousy is a complex issue. Using what I built from the feature request above, here is one approach.
This is heavily based off of the approach listed here:
https://www.pixelcrushers.com/phpbb/vie ... f666#p7980

2 DeedTemplateLibraries - One 'normal' one 'romantic'
Actors: Player, character 1, character 2, character 3
Action:
Player flirts with character1.
Reaction:
character 2 dislikes Player(Afinity), reduces happiness, pleasure, dominance. Increases arousal (they are irritated Player flirted)
character 3 likes Player (Afinity), increases PAD
Flirt deed is configured in 'romantic' DeedTemplateLibrary. Functionaly similar to a flatter, or give deed in the example scene
New Relationship Trait 'PreferCurrentRomanticRelationships' = "The trait amongst monogomous romantic relationships or encourage new romances"
Assign Character 1 'PreferCurrentRomanticRelationships' to Character 2 = 0; Character 1 doesn't care if they are in one or many relationships;
Assign Character 2 'PreferCurrentRomanticRelationships' to Character 1 = 65; Character 2 prefers a monogomous relationship with Character 1;
Assign Character 3 'PreferCurrentRomanticRelationships' to Character 1 = -75; Character 3 believes Character 2 is no good for Character 1; Wishes Character 1 would start a new relationship;

In the monobehavior holding the EvaluateRumor delegate:

Code: Select all

private int _romanticPreferCurrentRelationshipTraitID;
private AnimationCurve _jealousyCurve;
private AnimationCurve _encourageNewRomanceCurve;

Code: Select all

Start(){
_romanticPreferCurrentRelationshipTraitID = _mFactionMember.factionDatabase.GetRelationshipTraitID("PreferCurrentRomanticRelationships");
_jealousyCurve = AnimationCurve.EaseInOut(0,-1,100,-2); //this curve is for positive values of PreferCurrentRomanticRelationships; multiplies the effectiveness and inverts the deed, b/c Character is jealous
_encourageNewRomanceCurve = AnimationCurve.EaseInOut(-100,2,0,1); //effective inverse of jealousy curve
}
In the EvaluateRumor delagate:

Code: Select all

if (rumor.library == romanticLibrary) {
	//check jealousy
	//is this a romantic gesture and do we have a relationship to the target?
	if (_mFactionMember.factionDatabase.FindPersonalRelationship(_mFactionMember.factionID,
			rumor.targetFactionID, out var relationshipToTarget)) {
		//do we want to be monogamous to the target or encourage a new relationship?
		var curveToUse = (relationshipToTarget.traits[_romanticPreferCurrentRelationshipTraitID] > 0)
			? _jealousyCurve
			: _encourageNewRomanceCurve;
		var curveAdjuster =
			curveToUse.Evaluate(relationshipToTarget.traits[_romanticPreferCurrentRelationshipTraitID]);
		rumor.impact *= curveAdjuster;
		rumor.aggression *= curveAdjuster;
	}
FrostedLink
Posts: 12
Joined: Fri Jan 07, 2022 7:43 pm

Re: Feature Request: Method to categorize Deeds for evaluation during rumor processing

Post by FrostedLink »

After writing this, I realized Jealousy isn't the only relationship trait that has a weird inversion property. I abstracted this concept into a Power Dynamics trait.
For jealousy, Character 2 wants to maintain the Power Dynamic of monogomy with Character 1 and Power is threatened by Player.

With Deed Catorigization in mind, I thought:
What if we could assign a Relationship Trait at design time to a Category. Faction Members could evaluate the rumor based on the category and it's corresponding relationship trait.
This function should be overridable, and probably explicitly called in an EvaluateRumor delagate instead of the Core.

For another example, imagine NPC1 wants to continue manipulating NPC2. Player does some deed that helps NPC2, this weakens NPC1's power over NPC2;
NPC1 should be irritated
NPC2 should be happy.

So I took the ScriptableObject approach, but didn't make it a full SO for this simple test.

Code: Select all

public class DeedCategoryWithCurves
{
    public string Name; //used only for an example comparison 
    public int TargetRelationshipTrait;  //would correspond to the RelationshipDefinition array
    public AnimationCurve NegativeSide; //visual editor
    public AnimationCurve PositiveSide;

    /// <summary>
    /// Applies a Category to Relationship modifier based on the curves
    /// Only the TargetRelationshipTrait is inspected 
    /// </summary>
    /// <param name="rumor">Main rumor under evaluation</param>
    /// <param name="relationship">Relationship to target</param>
    /// <returns>Updated Rumor</returns>
    public Rumor AffectRumor(Rumor rumor, Relationship relationship){
        
        if (rumor == null || relationship == null) return rumor;
        var curveToUse = (relationship.traits[TargetRelationshipTrait] > 0)
            ? PositiveSide
            : NegativeSide;
        var curveAdjuster =
            curveToUse.Evaluate(relationship.traits[TargetRelationshipTrait]);
        rumor.impact *= curveAdjuster;
        rumor.aggression *= curveAdjuster;
        return rumor;
    }
}
Vastly simplifies the check in the delgate, and allows for new types of power dynamics:

Code: Select all

private Rumor MyEvaluateRumor(Rumor rumor, FactionMember source){
	//simple existence check that a Deed Category was assigned.
	if (rumor.library.DeedCategory.Name != null) {

		//if any relationship exists between the actors, try to modify the rumor based on Power Dynamics
		if (_mFactionMember.factionDatabase.FindPersonalRelationship(_mFactionMember.factionID,
				rumor.targetFactionID, out relationshipToTarget)) {
			rumor = rumor.library.DeedCategory.AffectRumor(rumor, relationshipToTarget);
		}
	}
	return _mFactionMember.DefaultEvaluateRumor(rumor, source);
}
Basic assignment in Start() to populate the data from my last post:

Code: Select all

        romanticLibrary.DeedCategory.Name = "Romantic";
        romanticLibrary.DeedCategory.NegativeSide = _encourageNewRomanceCurve;
        romanticLibrary.DeedCategory.PositiveSide = _jealousyCurve;
        romanticLibrary.DeedCategory.TargetRelationshipTrait = _romanticPreferCurrentRelationshipTraitID;
        
User avatar
Tony Li
Posts: 22112
Joined: Thu Jul 18, 2013 1:27 pm

Re: Feature Request: Method to categorize Deeds for evaluation during rumor processing

Post by Tony Li »

How is your implementation working out? Are you getting the numbers that you want?

Do you anticipate any issues if, in the next Love/Hate update, deeds point to DeedCategory assets instead of libraries?
FrostedLink
Posts: 12
Joined: Fri Jan 07, 2022 7:43 pm

Re: Feature Request: Method to categorize Deeds for evaluation during rumor processing

Post by FrostedLink »

I use the DefaultEvaluateRumor everywhere right now. I use the Deed Categories to adjust the impact and aggression attributes.

Actually, as long as there is a way to inherit from Deed Categories ScriptableObject, and a method to extend the property drawer easily. I can adapt to an update quickly.

My last post about the AnimationCurves and Power Dynamics may be something to consider for Core Love/Hate. The post I linked about Jealousy looks like a gap in the current implementation that can be solved with this approach.
User avatar
Tony Li
Posts: 22112
Joined: Thu Jul 18, 2013 1:27 pm

Re: Feature Request: Method to categorize Deeds for evaluation during rumor processing

Post by Tony Li »

I think I'll leave the AnimationCurves and Power Dynamics out of the core asset to keep it simpler for the majority of devs who don't use that, but Deed Categories will be in the next release. They and their editor code will be extensible.
FrostedLink
Posts: 12
Joined: Fri Jan 07, 2022 7:43 pm

Re: Feature Request: Method to categorize Deeds for evaluation during rumor processing

Post by FrostedLink »

I see that DeedCategories made it into the latest update. However, I have a few questions.

Are Rumors not supposed to have the DeedCategories reference? If not, what is the proposed method to determine a category during EvaluateRumor delegate?

I like the idea that every Deed has it's own category. My initial thought was each library has one, then the designer just creates a new database for each category. Your way makes more sense I think.

I'm not great at Editor code, and handling SerializedProperty objects. I'm testing out an extension to the DeedTemplateLibraryEditor and I ran into a roadblock.

Something like:
var category = m_deedTemplate.FindPropertyRelative("category")
var name = category.FindPropertyRelative("m_categoryName")
EditorGUI.LabelField(rect, "Category Name: " + name);


I would expect this to return the DeedCategory's > m_categoryName property. Instead I get a null reference. My google-fu didn't really pull up responses that fit this field > object relationship. Do you have a suggested direction here? Even something within Love/Hate source I can reference to learn off of would be fine.
Post Reply