[MOD] Meaner Monsters

A curated forum for compatible and maintained mods. Users are unable to create new topics in this forum but can reply to existing topics. Please message a moderator to have your mod moved into this forum area.
Post Reply
User avatar
Ralzar
Posts: 2211
Joined: Mon Oct 07, 2019 4:11 pm
Location: Norway

Re: [MOD] Meaner Monsters

Post by Ralzar »

And Meaner Monsters appears to finally work properly now:

https://youtu.be/lBt0uJcBIWk?t=521

:D

User avatar
Magicono43
Posts: 1139
Joined: Tue Nov 06, 2018 7:06 am

Re: [MOD] Meaner Monsters

Post by Magicono43 »

I am debating with myself if I should include this in Meaner Monsters or LevelUp Adjuster (which I'm thinking of renaming to just "Mechanics Adjuster"). Problem is, I'll balance Meaner Monsters for the modded mechanic, so having them seperate could be awkward.
Myself at least, I would be "balancing" the monster stats around the fact that the player is now less carried by the weapon material in terms of hit-chance, so I would think possibly making a second version of the mod could be a thing, or even as you said renaming it to something more encompassing. Cause no matter what, the monster stats will NEED to be adjusted, because the default ones are just way to junky and random to be difficult, even if they are harder to hit, like the damage and health values for even the moderately high level monsters just makes no sense. They seemed to think that 5-15 damage was some sort of sweet spot, but it's pretty pathetic when you look at even the more flimsy character builds at that level, same goes with most of the monster health values.

I want to take a crack at modding the formulas myself, but i'm pretty green when it comes to C#, so I mostly copy other modders work and use that as a base at this point. So i'll probably just have to play around a bit and see if I can get at least that formula changed without getting exceptions or breaking the game.

User avatar
Ralzar
Posts: 2211
Joined: Mon Oct 07, 2019 4:11 pm
Location: Norway

Re: [MOD] Meaner Monsters

Post by Ralzar »

I think what I'll do for now is add it to my LevelUp Adjuster (which I'll rename Mechanics Modifier).
I'll give it a bunch of mod settings so it's possible to easily play around with the numbers without having to recode stuff.
Then when we have more of a feel for it I'll consider which way to go with Meaner Monsters. Possibly I'll have a hardcoded version set in meaner monsters that you can override by using Mechanics Modifier.

User avatar
Hazelnut
Posts: 3015
Joined: Sat Aug 26, 2017 2:46 pm
Contact:

Re: [MOD] Meaner Monsters

Post by Hazelnut »

Wow guys... I didn't have time to respond before work today and wanted to mull it over anyway. In the meantime you seem to have made some progress/decisions?

Anyway I am troubled by the modifiers added for weapons, I can only assume this matches classic (seems to match UESP at least) but it seems like material to hit mods are a bit over the top. This is something I would consider rebalancing as a module to R&R to promote role playing. Prior to this, except for re-factoring the formula by request (UV IIRC) I've never looked too hard at this calculation.
See my mod code for examples of how to change various aspects of DFU: https://github.com/ajrb/dfunity-mods

User avatar
Ralzar
Posts: 2211
Joined: Mon Oct 07, 2019 4:11 pm
Location: Norway

Re: [MOD] Meaner Monsters

Post by Ralzar »

Hazelnut wrote: Mon Mar 02, 2020 8:54 pm Wow guys... I didn't have time to respond before work today and wanted to mull it over anyway. In the meantime you seem to have made some progress/decisions?

Anyway I am troubled by the modifiers added for weapons, I can only assume this matches classic (seems to match UESP at least) but it seems like material to hit mods are a bit over the top. This is something I would consider rebalancing as a module to R&R to promote role playing. Prior to this, except for re-factoring the formula by request (UV IIRC) I've never looked too hard at this calculation.
It's all still up in the air, but I'm thinking there should be at least one mod alternative to change how combat is calculated.

I have been spending this evening attempting to set up an override function for CalculateAttackDamage. This is for LevelUp Adjuster though, which was originally intended to be more generic (instead of specifically for level up mechanics) and is now moving back to that. It's not a mod in the usual sense of the word. It is more like a tool for manipulating mechanics so you can tailor the game to your own preferences. Which makes it a good tool for testing how you want to mod those formulas without having to re-code it over and over.

For something like Meaner Monsters or RP&R I would expect the override function to have hardcoded numbers, while what I'm building at the moment has a bunch of Mod Settings to manipulate the numbers.

I've just finished coding it (just not the mod settings), but I ran into a couple of problems:

1: It calls a bunch of other methods from FormulaHelper, many of them not available for the mod to call, so I had to make my own identical version of those methods. Which makes me basically move the whole Combat & Damage region out of FormulaHelper to make it work.

2: Because CalculateAttackDamage calls other methods that have the option to be overwritten, it will have unintuitive and far reaching consequences for what other mods it blocks from doing their own overrides. For example: the overrides you do for armor damage.

Possible solution: make more FormulaHelper methods public so it's possible to override one specific method and have it still call other methods that can be overridden.
Possible alternate solution: make some kind of conditional code that checks for other mods doing overrides and use their code instead for those methods.

Edit: And to be clear: this is mostly a coding experiment I'm doing at the moment. When there's an easy and controllable way to override this, that plays nice with other mods, I'm thinking it would be a good addition to RP&R (if you want it there) or Meaner Monsters.

User avatar
Magicono43
Posts: 1139
Joined: Tue Nov 06, 2018 7:06 am

Re: [MOD] Meaner Monsters

Post by Magicono43 »

Hazelnut wrote: Mon Mar 02, 2020 8:54 pm Wow guys... I didn't have time to respond before work today and wanted to mull it over anyway. In the meantime you seem to have made some progress/decisions?

Anyway I am troubled by the modifiers added for weapons, I can only assume this matches classic (seems to match UESP at least) but it seems like material to hit mods are a bit over the top. This is something I would consider rebalancing as a module to R&R to promote role playing. Prior to this, except for re-factoring the formula by request (UV IIRC) I've never looked too hard at this calculation.
Yeah, I was not really suggesting that the formula is inaccurate to what Classic had, but just from the perspective of a player, the weapon material modifier is just vastly more impactful to accuracy compared to other elements that, in my opinion, would make more sense, as well as make the game more difficult throughout the game, rather than just becoming a non-factor once you get closer to the max level. If anything, once the player gets a Daedric weapon, almost no matter what their skills are, they will be hitting the cap of 97% against most enemies. To me, this is just a bit crazy and explains why Daggerfall on DOS started to become so crazy easy once you got better weapons, you just stun-lock the dumb AI death from ALWAYS hitting them.

I was reading the Formulahelper script since my previous post and have been trying to understand how to use the "RegisterOverride" method which I think is the method a modder would use to actually override one of the formulas in the script with their own formula.

Code: Select all

#region Formula Overrides

        /// <summary>
        /// Registers an override for a formula using a generic `System.Func{T}` callback
        /// with the same signature as the method it overrides
        /// (i.e. `RegisterOverride{Func{int, int, float}}("FormulaName", (a, b) => (float)a / b);`).
        /// The invocation will fail if signature is not correct, meaning if the delegate
        /// is not one of the variation of `Func` with the expected arguments.
        /// </summary>
        /// <param name="provider">The mod that provides this override; used to enforce load order.</param>
        /// <param name="formulaName">The name of the method that provides the formula.</param>
        /// <param name="formula">A callback that implements the formula.</param>
        /// <typeparam name="TDelegate">One of the available generic Func delegates.</typeparam>
        /// <exception cref="ArgumentNullException">`formulaName` or `formula` is null.</exception>
        /// <exception cref="InvalidCastException">Type is not a delegate.</exception>
        public static void RegisterOverride<TDelegate>(Mod provider, string formulaName, TDelegate formula)
            where TDelegate : class
        {
            if (formulaName == null)
                throw new ArgumentNullException("formulaName");

            if (formula == null)
                throw new ArgumentNullException("formula");

            var del = formula as Delegate;
            if (del == null)
                throw new InvalidCastException("formula is not a delegate.");

            FormulaOverride formulaOverride;
            if (!overrides.TryGetValue(formulaName, out formulaOverride) || formulaOverride.Provider.LoadPriority < provider.LoadPriority)
                overrides[formulaName] = new FormulaOverride(del, provider);
        }

        /// <summary>
        /// Gets an override for a formula.
        /// </summary>
        /// <param name="formulaName">The name of the method that provides the formula.</param>
        /// <param name="formula">A callback that implements the formula.</param>
        /// <typeparam name="TDelegate">One of the available generic Func delegates.</typeparam>
        /// <returns>True if formula is found.</returns>
        private static bool TryGetOverride<TDelegate>(string formulaName, out TDelegate formula)
            where TDelegate : class
        {
            FormulaOverride formulaOverride;
            if (overrides.TryGetValue(formulaName, out formulaOverride))
            {
                if ((formula = formulaOverride.Formula as TDelegate) != null)
                    return true;

                const string errorMessage = "Removed override for formula {0} provided by {1} because signature doesn't match (expected {2} and got {3}).";
                Debug.LogErrorFormat(errorMessage, formulaName, formulaOverride.Provider.Title, typeof(TDelegate), formulaOverride.Formula.GetType());
                overrides.Remove(formulaName);
            }

            formula = default(TDelegate);
            return false;
        }

        #endregion
    }
I guess this would be called in the mod script with something like:

Code: Select all

FormulaHelper.RegisterOverride<TDelegate>(Mod_Name, string CalculateSuccessfulHit, TDelegate Modded_Formula){
"Modded Formula Code, mostly unchanged code from CalculateSuccessfulHit"
}
I might be off here, don't really understand what TDelegate is supposed to be, maybe just a type like int or float? That's how far I've gotten so far on figuring how to mod that at least.

User avatar
Hazelnut
Posts: 3015
Joined: Sat Aug 26, 2017 2:46 pm
Contact:

Re: [MOD] Meaner Monsters

Post by Hazelnut »

Ralzar, there's definitely more work to break down the combat formula to be more modular, but it can be done on a case by case basis. The focus was on reproducing classic and we need to be careful to preserve that effort while making it moddable. Overriding the big calc damage formula is really only appropriate for complete replacement mechanics which is why you found it so hard. It would be useful to me if you were to suggest logical blocks of formula for separation, however this is probably not the right place for that discussion.
See my mod code for examples of how to change various aspects of DFU: https://github.com/ajrb/dfunity-mods

User avatar
Ralzar
Posts: 2211
Joined: Mon Oct 07, 2019 4:11 pm
Location: Norway

Re: [MOD] Meaner Monsters

Post by Ralzar »

Hazelnut wrote: Mon Mar 02, 2020 9:33 pm Ralzar, there's definitely more work to break down the combat formula to be more modular, but it can be done on a case by case basis. The focus was on reproducing classic and we need to be careful to preserve that effort while making it moddable. Overriding the big calc damage formula is really only appropriate for complete replacement mechanics which is why you found it so hard. It would be useful to me if you were to suggest logical blocks of formula for separation, however this is probably not the right place for that discussion.
True.

This is written too much as an interconnected code at the moment. However, a lot of it is in seperate methods with their own override functions. They simply are not public, so you can't call them from other methods you override. Wherever possible I called the original methods, but that still left quite a few that I had to build my own exact copies of.
At the moment this is only designed to really work for a complete combat overhaul, not manipulation of particular pieces. I'll experiment a bit more and get back to you.

User avatar
Hazelnut
Posts: 3015
Joined: Sat Aug 26, 2017 2:46 pm
Contact:

Re: [MOD] Meaner Monsters

Post by Hazelnut »

Ralzar wrote: Mon Mar 02, 2020 9:56 pm At the moment this is only designed to really work for a complete combat overhaul, not manipulation of particular pieces. I'll experiment a bit more and get back to you.
It will work with either, but not both. I cannot recall if this was intentional or not, as this was mid 2018 I did this refactoring, but it seems sensible to me. I can already see several places where it needs refinement in its current form even before being broken down any more.
See my mod code for examples of how to change various aspects of DFU: https://github.com/ajrb/dfunity-mods

User avatar
Magicono43
Posts: 1139
Joined: Tue Nov 06, 2018 7:06 am

Re: [MOD] Meaner Monsters

Post by Magicono43 »

So I don't think I have any idea what i'm doing here, but maybe anyone following this thread would know better if i'm even close at all here.

This is the stitched together "mod" that would try to replace the formula that calculates the "chanceToHitMod" variable that ultimately leads to eventually being using in the "CalculateSuccessfulHit" method for FormulaHelper.cs.

From line 49: chanceToHitMod = attacker.Skills.GetLiveSkillValue(skillID);
to line 193: }
It's literally the code for the formula that has any changes that are made to "chanceToHitMod" in the formulahelper.

Code: Select all

// Project:         UnleveledMobs mod for Daggerfall Unity (http://www.dfworkshop.net)
// Copyright:       Copyright (C) 2019 JayH
// License:         MIT License (http://www.opensource.org/licenses/mit-license.php)
// Author:          JayH
// Modifier:		Kirk.O (Minor Tweeks Done to most values, 3/2/2020, 12:00 PM EST)

using DaggerfallConnect;
using DaggerfallWorkshop;
using DaggerfallWorkshop.Game;
using DaggerfallWorkshop.Game.Formulas;
using DaggerfallWorkshop.Game.Utility.ModSupport;
using DaggerfallWorkshop.Utility;
using UnityEngine;
using System.Collections;
using System.Collections.Generic

namespace WeaponModBalancer
{
    public class WeaponModBalancer : MonoBehaviour
    {
        static Mod mod;

        [Invoke(StateManager.StateTypes.Start, 0)]
        public static void Init(InitParams initParams)
        {
            mod = initParams.Mod;
            var go = new GameObject(mod.Title);
            go.AddComponent<WeaponModBalancer>();
        }

        void Awake()
        {
            InitMod();

            mod.IsReady = true;
        }

        private static void InitMod()
        {
            Debug.Log("Begin mod init: WeaponModBalancer");

			
			FormulaHelper.RegisterOverride<Func{DaggerfallEntity, DaggerfallEntity, int, int, DaggerfallUnityItem, int}>(WeaponModBalancer, AdjustWeaponHitChanceMod, ModdedHitChanceFormula);
			
			public static int ModdedHitChanceFormula(attacker, target, chanceToHitMod, weaponAnimTime, weapon)
			{
				int chanceToHitMod = 0;
				
				chanceToHitMod = attacker.Skills.GetLiveSkillValue(skillID);

				if (attacker == player)
				{
					// Apply swing modifiers.
					FPSWeapon onscreenWeapon = GameManager.Instance.WeaponManager.ScreenWeapon;

					if (onscreenWeapon != null)
					{
						// The Daggerfall manual groups diagonal slashes to the left and right as if they are the same, but they are different.
						// Classic does not apply swing modifiers to unarmed attacks.
						if (onscreenWeapon.WeaponState == WeaponStates.StrikeUp)
						{
							damageModifiers += -4;
							chanceToHitMod += 10;
						}
						if (onscreenWeapon.WeaponState == WeaponStates.StrikeDownRight)
						{
							damageModifiers += -2;
							chanceToHitMod += 5;
						}
						if (onscreenWeapon.WeaponState == WeaponStates.StrikeDownLeft)
						{
							damageModifiers += 2;
							chanceToHitMod += -5;
						}
						if (onscreenWeapon.WeaponState == WeaponStates.StrikeDown)
						{
							damageModifiers += 4;
							chanceToHitMod += -10;
						}
					}

					if (weapon != null)
					{
						// Apply weapon proficiency
						if (((int)attacker.Career.ExpertProficiencies & weapon.GetWeaponSkillUsed()) != 0)
						{
							damageModifiers += ((attacker.Level / 3) + 1);
							chanceToHitMod += attacker.Level;
						}
					}
					// Apply hand-to-hand proficiency. Hand-to-hand proficiency is not applied in classic.
					else if (((int)attacker.Career.ExpertProficiencies & (int)(DFCareer.ProficiencyFlags.HandToHand)) != 0)
					{
						damageModifiers += ((attacker.Level / 3) + 1);
						chanceToHitMod += attacker.Level;
					}

					// Apply racial bonuses
					if (weapon != null)
					{
						if (player.RaceTemplate.ID == (int)Races.DarkElf)
						{
							damageModifiers += (attacker.Level / 4);
							chanceToHitMod += (attacker.Level / 4);
						}
						else if (skillID == (short)DFCareer.Skills.Archery)
						{
							if (player.RaceTemplate.ID == (int)Races.WoodElf)
							{
								damageModifiers += (attacker.Level / 3);
								chanceToHitMod += (attacker.Level / 3);
							}
						}
						else if (player.RaceTemplate.ID == (int)Races.Redguard)
						{
							damageModifiers += (attacker.Level / 3);
							chanceToHitMod += (attacker.Level / 3);
						}
					}

					backstabChance = CalculateBackstabChance(player, null, enemyAnimStateRecord);
					chanceToHitMod += backstabChance;
				}
				
				// Choose struck body part
				int struckBodyPart = CalculateStruckBodyPart();

				// Get damage for weaponless attacks
				if (skillID == (short)DFCareer.Skills.HandToHand)
				{
					if (attacker == player || (AIAttacker != null && AIAttacker.EntityType == EntityTypes.EnemyClass))
					{
						if (CalculateSuccessfulHit(attacker, target, chanceToHitMod, struckBodyPart) > 0)
						{
							minBaseDamage = CalculateHandToHandMinDamage(attacker.Skills.GetLiveSkillValue(DFCareer.Skills.HandToHand));
							maxBaseDamage = CalculateHandToHandMaxDamage(attacker.Skills.GetLiveSkillValue(DFCareer.Skills.HandToHand));
							damage = UnityEngine.Random.Range(minBaseDamage, maxBaseDamage + 1);

							// Apply damage modifiers.
							damage += damageModifiers;
							// Apply strength modifier. It is not applied in classic despite what the in-game description for the Strength attribute says.
							if (attacker == player)
								damage += DamageModifier(attacker.Stats.LiveStrength);
							// Handle backstabbing
							damage = CalculateBackstabDamage(damage, backstabChance);
						}
					}
					else if (AIAttacker != null) // attacker is monster
					{
						// Handle multiple attacks by AI
						int attackNumber = 0;
						while (attackNumber < 3) // Classic supports up to 5 attacks but no monster has more than 3
						{
							if (attackNumber == 0)
							{
								minBaseDamage = AIAttacker.MobileEnemy.MinDamage;
								maxBaseDamage = AIAttacker.MobileEnemy.MaxDamage;
							}
							else if (attackNumber == 1)
							{
								minBaseDamage = AIAttacker.MobileEnemy.MinDamage2;
								maxBaseDamage = AIAttacker.MobileEnemy.MaxDamage2;
							}
							else if (attackNumber == 2)
							{
								minBaseDamage = AIAttacker.MobileEnemy.MinDamage3;
								maxBaseDamage = AIAttacker.MobileEnemy.MaxDamage3;
							}

							int reflexesChance = 50 - (10 * ((int)player.Reflexes - 2));

							if (DFRandom.rand() % 100 < reflexesChance && minBaseDamage > 0 && CalculateSuccessfulHit(attacker, target, chanceToHitMod, struckBodyPart) > 0)
							{
								int hitDamage = UnityEngine.Random.Range(minBaseDamage, maxBaseDamage + 1);
								// Apply special monster attack effects
								if (hitDamage > 0)
									OnMonsterHit(AIAttacker, target, hitDamage);

								damage += hitDamage;
							}
							++attackNumber;
						}
					}
				}
				// Handle weapon attacks
				else if (weapon != null)
				{
					// Apply weapon material modifier.
					if (weapon.GetWeaponMaterialModifier() > 0)
					{
						chanceToHitMod += (weapon.GetWeaponMaterialModifier() * 10);
					}	
				}
				return chanceToHitMod;
			}
            Debug.Log("Finished mod init: WeaponModBalancer");
        }
    }
}
The rest is what I added in an attempt to override the formula, as instructed by the "Formula Overrides" region of the script. Like I said, am mostly wondering if i'm close at all to what would be the "proper" way to override a formula with a mod script. I would have to assume that all of the parameters and such that came from the formulahelper original code would have to be redefined inside the mods method, otherwise the values would probably not be accessible as currently written, at least that's what I would assume.

Post Reply