Cracking Open Combat

Discuss modding questions and implementation details.
Post Reply
User avatar
mikeprichard
Posts: 1037
Joined: Sun Feb 19, 2017 6:49 pm

Re: Cracking Open Combat

Post by mikeprichard »

Hazelnut wrote: Fri Dec 13, 2019 5:00 pm Sorry I've not got time to read through this whole thread and correct/debate all of this in detail. If I am way off though lack of analysis please tell me.

All I can say is having skimmed, I disagree with the analysis that run speed is used as the base speed with walk being /2 and sneak = /4.

Walk speed is base speed, with run_speed = baseSpeed * (1.25f + (Skills_Running / 200f))

If sneak is pressed, the speed is halved. I think (not checked) that if speed is less or equal to half base speed then sneak checks occur.
Thanks for this input/clarification, Hazelnut - it would also make intuitive sense that the default walking speed, not running speed, is the "base" speed. This would mean that both "sneak" and "crouch" movement (if "crouch-walking" is in fact the same speed as "sneak-walking"?) would be "half base speed", thus allowing continued stealth checks after initial detection by an enemy. This would also resolve the question of the usefulness/relevance of "sneak" from a gameplay perspective. As long as nobody disagrees/can disprove that "crouch" movement can also fulfill this function - only "crouch" and not "sneak" is even available in the control scheme when the default "mouse cursor" view is checked in classic DF - I'll have resolved my main points of confusion. My resulting almost-final guess as to what would be an accurate overview for the revised wiki page:

Whenever you approach a creature and enter its detection range, your Stealth skill is automatically checked to determine whether the creature successfully detects you and (if hostile) engages you in active combat. In addition to your Stealth skill value, other factors affect your chance of successfully avoiding the creature's detection, including the creature's Stealth skill, your distance from and location in relation to the creature, and your movement speed. If the creature has initially seen and/or heard you, leaving the creature's sight and hearing range and then moving more slowly - i.e. by crouching or sneaking instead of walking or running - will enable you to continue to attempt to avoid detection, while walking or running will result in detection by the creature. On the other hand, if the creature has not yet seen or heard you, you may continue to attempt to avoid detection while moving at any speed.

l3lessed
Posts: 1403
Joined: Mon Aug 12, 2019 4:32 pm
Contact:

Re: Cracking Open Combat

Post by l3lessed »

Okay, I think you all are correct. Thanks for the extra info on how movement and sneak work.

That would make sense, there is no sneak mode. It all based on movement speed and stealth skill. Within the enemy sense script and the stealth check routine, there is no boolean to indicate a stealth mode is being activated or not. This means it is running as long as you're in range of enemy senses.

As for the base movement speed needing to be at minimum equal to half base speed, yes it is an equal or greater check (>=).

Here is the base speed code. As hazel says, it is not a clear cut /2 and /4. Instead, it is a more complicated formula, but I did figure out the ratios for it:

Code: Select all

        public float GetBaseSpeed()
        {
            Entity.PlayerEntity player = GameManager.Instance.PlayerEntity;
            float baseSpeed = 0;
            float playerSpeed = player.Stats.LiveSpeed;
            if (playerMotor == null) // fixes null reference bug.
                playerMotor = GameManager.Instance.PlayerMotor;
            // crouching speed penalty doesn't apply if swimming.
            if (playerMotor.IsCrouching && !levitateMotor.IsSwimming)
                baseSpeed = (playerSpeed + dfCrouchBase) / classicToUnitySpeedUnitRatio;
            else if (playerMotor.IsRiding)
            {
                float rideSpeed = (GameManager.Instance.TransportManager.TransportMode == TransportModes.Cart) ? dfCartBase : dfRideBase;
                baseSpeed = (playerSpeed + rideSpeed) / classicToUnitySpeedUnitRatio;
            }
            else
                baseSpeed = GetWalkSpeed(player);
            return baseSpeed;
        }
When walk is activated, base speed takes the players speed stat, adds 150 to it, and then divides that by 39.5. So, walk speed is actually, ((SpeedState + 150)/39.5). This gives you the base speed for walking.

Crouch seems it takes the players speed stat, adds 50 to it, and then divides that by 39.5. So, crouch speed is actually, ((SpeedState + 50)/39.5). However, it seems the way the math works out, according to the code, crouch speed will always end up being 77% slower than base walk speed (it's a third of walk speeds 150) and since it is lower the 50% (aka /2) crouch will always allow stealth to remain activated, if not encountered within the enemy senses routine.

The interesting thing, is it seems basespeed returns the walk speed, which would mean when even walking, you are going faster than half the base speed. As a result, I think I made a mistake, only crouching or standing still will keep you stealthed once detected. Unless there are other movement mods, outside of walk, run, and crouch, I don't know about. Maybe climbing? Climbing takes current movement speed divides it by three and then doubles the speed if you have the adept climbing modifier from character creation. So you can stay hidden in climbing mode too, if I am understanding the playerspeedchanger script correctly.

Code: Select all

        public float GetClimbingSpeed()
        {
            // Climbing effect states "target can climb twice as well" - doubling climbing speed
            Entity.PlayerEntity player = GameManager.Instance.PlayerEntity;
            float climbingBoost = player.IsEnhancedClimbing ? 2f : 1f;
            return (playerMotor.Speed / 3) * climbingBoost;
        }
Here is Running. This looks off if you do the math for the equation, but that is because it uses the base speed after its calculated to return the run speed. Think of it has a multiplier for the current base speed.

Code: Select all

        public bool IsRunning
        {
            get { return speed == speedChanger.GetRunSpeed(speedChanger.GetBaseSpeed()); }
        }

Code: Select all

        /// <summary>
        /// Get LiveSpeed adjusted for running
        /// </summary>
        /// <param name="baseSpeed"></param>
        /// <returns></returns>
        public float GetRunSpeed(float baseSpeed)
        {
            if (useRunSpeedOverride)
                return runSpeedOverride;
            Entity.PlayerEntity player = GameManager.Instance.PlayerEntity;
            return baseSpeed * (1.25f + (player.Skills.GetLiveSkillValue(DFCareer.Skills.Running) / 200f));
        }
Here is walk:

Code: Select all

        /// <summary>
        /// Get LiveSpeed adjusted for walking
        /// </summary>
        /// <param name="player">the PlayerEntity to use</param>
        /// <returns></returns>
        public float GetWalkSpeed(Entity.PlayerEntity player)
        {
            if (useWalkSpeedOverride)
                return walkSpeedOverride;
            else
                return (player.Stats.LiveSpeed + dfWalkBase) / classicToUnitySpeedUnitRatio;
        }
Not sure how I overlooked this at the front of the script. According to the code, sneak mode does have a use. It will take whatever speed you're at, half it, and subtract 1. According to this, you should be able to use sneak mode to sneak within whatever movement mode you want.

Code: Select all

        /// <summary>
        /// Determines how speed should be changed based on player's input
        /// </summary>
        /// <param name="speed"></param>
        public void HandleInputSpeedAdjustment(ref float speed)
        {
            if (playerMotor.IsGrounded)
            {
                if (InputManager.Instance.HasAction(InputManager.Actions.Run) && CanRun())
                {
                    try
                    {
                        // If running isn't on a toggle, then use the appropriate speed depending on whether the run button is down
                        if (!toggleRun)
                            speed = GetRunSpeed(speed);
                        else
                            speed = (speed == GetBaseSpeed() ? GetRunSpeed(speed) : GetBaseSpeed());
                    }
                    catch
                    {
                        speed = GetRunSpeed(speed);
                    }
                }
                // Handle sneak key. Reduces movement speed to half, then subtracts 1 in classic speed units
                else if (InputManager.Instance.HasAction(InputManager.Actions.Sneak))
                {
                    speed /= 2;
                    speed -= (1 / classicToUnitySpeedUnitRatio);
                }
            }
        }
Here is the code that decides if you are moving half speed or not. And yes, crouch is always half base speed and will ensure stealth checks can happen within the StealthCheck Routine while crouched and moving.

Code: Select all

 public bool IsMovingLessThanHalfSpeed
        {
            get
            {
                if (IsStandingStill)
                {
                    return true;
                }
                if (isCrouching)
                {
                    return (speedChanger.GetWalkSpeed(GameManager.Instance.PlayerEntity) / 2) >= speed;
                }
                return (speedChanger.GetBaseSpeed() / 2) >= speed;
            }
Last edited by l3lessed on Fri Dec 13, 2019 8:43 pm, edited 3 times in total.
My Daggerfall Mod Github: l3lessed DFU Mod Github

My Beth Mods: l3lessed Nexus Page

Daggerfall Unity mods: Combat Overhaul Mod

Enjoy the free work I'm doing? Consider lending your support.

l3lessed
Posts: 1403
Joined: Mon Aug 12, 2019 4:32 pm
Contact:

Re: Cracking Open Combat

Post by l3lessed »

So, the long of the short it, if sneak mode is engaged, you can always do a stealth check, as it forces the players movement to be halved and then reduced by 1 unit to ensure you are under the movement penalty.

So, if not detected, you can move at whatever speed you want and still have stealth checks work.

Once encountered, you can never move more than half speed without being detected. However, as long as you have sneak mode engaged, the engine should force your movement to be below half speed no matter what, so stealth checks will always happen.

This makes sense to me from a development point of view. They probably thought the same thing, which is this is extremely confusing from a mechanics point, so let's just put in a mode that can be activated and ensures a player is always under proper movement speed for stealth checks, no matter what.
Last edited by l3lessed on Fri Dec 13, 2019 8:03 pm, edited 1 time in total.
My Daggerfall Mod Github: l3lessed DFU Mod Github

My Beth Mods: l3lessed Nexus Page

Daggerfall Unity mods: Combat Overhaul Mod

Enjoy the free work I'm doing? Consider lending your support.

User avatar
mikeprichard
Posts: 1037
Joined: Sun Feb 19, 2017 6:49 pm

Re: Cracking Open Combat

Post by mikeprichard »

l3lessed wrote: Fri Dec 13, 2019 7:16 pm Okay, I think you all are correct. Thanks for the extra info on how movement and sneak work.
Thank you and everyone here for all the detailed research and explanations - I've now input the below revised text at the wiki at https://en.uesp.net/wiki/Daggerfall:Stealth with credit to l3lessed and everyone who posted and a link to this discussion in the page history notes. I appreciate all the help with this!

Whenever you approach a creature and enter its detection range, your Stealth skill is automatically checked to determine whether the creature successfully detects you and (if hostile) engages you in active combat. In addition to your Stealth skill value, other factors affect your chance of successfully avoiding the creature's detection, including the creature's Stealth skill, your distance from and location in relation to the creature, and your movement speed. If the creature has initially seen and/or heard you, leaving the creature's sight and hearing range and then moving more slowly - i.e. by crouching or sneaking instead of walking or running - will enable you to continue to attempt to avoid detection, while walking or running will result in detection by the creature. On the other hand, if the creature has not yet seen or heard you, you may continue to attempt to avoid detection while moving at any speed.

Now, if I could switch back to the other issue I briefly raised earlier re: shields' actual armor ratings (which I think will be a much simpler/more limited discussion than the above re: stealth mechanics), I see that the reason I wasn't sure as to shields' "actual" armor ratings in the game calculations is because shields in classic Daggerfall consistently display the same +1 through +4 ratings in both the inventory paper doll display and the "info" tooltips. This is different from non-shield armor pieces in classic, for which these two displays were inconsistent, and which it's already been proven that the "actual" armor ratings used are 5 times the inventory paper doll display values. So with shields, given also that they're unique in protecting several body parts, the question I have for potential wiki editing purposes is whether the armor ratings used in game calculations are a) those displayed in the UI (+1, +2, +3, or +4, depending on the shield category), the probably likely b) 5 times those displayed in the UI (as is the case with other armor pieces), or the probably unlikely c) 10 times those displayed in the UI (as was the case in classic Daggerfall for "info" tooltip displayed values for other armor pieces, a bug fixed by DFU). Any code research l3lessed or others may eventually report on this here would again be welcome! From l3lessed's earlier post in this topic, one relevant code section:

Code: Select all

		    // Shield armor values in classic are unaffected by their material type.
                    int[] values = { 0, 0, 0, 0, 0, 0, 0 }; // shield's effect on the 7 armor values
                    int armorBonus = armor.GetShieldArmorValue();
                    BodyParts[] protectedBodyParts = armor.GetShieldProtectedBodyParts();

                    foreach (var BodyPart in protectedBodyParts)
                    {
                        values[(int)BodyPart] = armorBonus;
                    }

                    for (int i = 0; i < armorValues.Length; i++)
                    {
                        if (equipping)
                        {
                            armorValues[i] -= (sbyte)(values[i] * 5);
                        }
                        else
                        {
                            armorValues[i] += (sbyte)(values[i] * 5);
                        }

l3lessed
Posts: 1403
Joined: Mon Aug 12, 2019 4:32 pm
Contact:

Re: Cracking Open Combat

Post by l3lessed »

Yeah sorry, I am saying stealth mode, which probably is confusing sense later games have a clear stealth mode. However, I just mean the sneak movement mode, when I am saying that. Sorry for the confusing phrasing.

One small note I want to make, but isn't huge, is the enemy also needs to be hostile for stealth checks to go off.

However, the enemy automatically goes hostile the moment is senses a player, but you may want to add a line or phrase in the description about enemy being hostile before anything else will start. Or, is it so self-evident it isn't needed?

Code: Select all

public void MakeEnemyHostileToAttacker(DaggerfallEntityBehaviour attacker)
        {
            if (!senses)
                senses = GetComponent<EnemySenses>();
            if (!entityBehaviour)
                entityBehaviour = GetComponent<DaggerfallEntityBehaviour>();

            // Assign target if don't already have target, or original target isn't seen or adjacent
            if (attacker && senses && (senses.Target == null || !senses.TargetInSight || senses.DistanceToTarget > 2f))
            {
                senses.Target = attacker;
                senses.SecondaryTarget = senses.Target;
                senses.OldLastKnownTargetPos = attacker.transform.position;
                senses.LastKnownTargetPos = attacker.transform.position;
                senses.PredictedTargetPos = attacker.transform.position;
                GiveUpTimer = 200;
            }

            if (attacker == GameManager.Instance.PlayerEntityBehaviour)
            {
                IsHostile = true;
                // Reset former ally's team
                if (entityBehaviour.Entity.Team == MobileTeams.PlayerAlly)
                {
                    int id = (entityBehaviour.Entity as EnemyEntity).MobileEnemy.ID;
                    entityBehaviour.Entity.Team = EnemyBasics.Enemies[id].Team;
                }
            }
        }
And now on to the shield questions.

Also, semester just ended yesterday, finished state and department audit at work, and wife is calming down some, so I think I'll have time to finish the outfit manager and begin trying to wrap up first version of this combat overhaul mod over next few weeks.
My Daggerfall Mod Github: l3lessed DFU Mod Github

My Beth Mods: l3lessed Nexus Page

Daggerfall Unity mods: Combat Overhaul Mod

Enjoy the free work I'm doing? Consider lending your support.

User avatar
mikeprichard
Posts: 1037
Joined: Sun Feb 19, 2017 6:49 pm

Re: Cracking Open Combat

Post by mikeprichard »

Hmm, I think I'll leave that small point alone for the wiki, as I still don't fully understand the chronology of these various sensing/detection stages even after all your explanations, and I don't think I want to get us into another round of digging! (E.g. going back to your flow chart summary three pages ago, you noted "Checks player is in detectable range -> checks if enemy can hear or see target/player -> false -> checks to see if you succeed stealth check -> True -> Enemy stays in detection mode looking for you using last sensed location until it sees or hears you again." This was really confusing me earlier, as I read it as saying the enemy can't "hear or see" you (false for first check), but still keeps looking for you until it "sees or hears you" "again".) But thank you for making time for this research (including the hopefully much less involved shield question) with everything else on your plate.

l3lessed
Posts: 1403
Joined: Mon Aug 12, 2019 4:32 pm
Contact:

Re: Cracking Open Combat

Post by l3lessed »

the question I have for potential wiki editing purposes is whether the armor ratings used in game calculations are a) those displayed in the UI (+1, +2, +3, or +4, depending on the shield category), the probably likely b) 5 times those displayed in the UI (as is the case with other armor pieces), or the probably unlikely c) 10 times those displayed in the UI (as was the case in classic Daggerfall for "info" tooltip displayed values for other armor pieces, a bug fixed by DFU).
It takes each body part, throws it into an array, and then loops through the array for each body part the shield can possibly protect and multiplies the armor value by five. Different shields cover different body parts.

Also, when you're hit, it will randomly choose a body part hit, and only use the armor value from that body part. As a result, shields are actually a massive boost to enemies missing you because the total armor value is not all armor values put together, like in more modern games (Skyrim). So, even the difference between shields is huge too.

As an example, a buckler will only protect hands and your left arm, and it will only add 5 points (1 * 5). Compare this to a tower shield, which protects your head, leftarm, hands, and both legs, and adds 20 points to each slot (4*5).

The UI should be fixed to show the proper shield values being added. I imagine in the original it didn't because they don't compute that value into after everything else is done, and it would of been a hassle for them to update the numbers for each armor slot covered, add it to the current armor in that slot, and force it into the UI versus just using the default 1,2,3,4. Or, they just overlooked it, as can happen in development.

Slot protection code for shield:

Code: Select all

 public BodyParts[] GetShieldProtectedBodyParts()
        {
            switch (TemplateIndex)
            {
                case (int)Armor.Buckler:
                    return new BodyParts[] { BodyParts.LeftArm, BodyParts.Hands };
                case (int)Armor.Round_Shield:
                case (int)Armor.Kite_Shield:
                    return new BodyParts[] { BodyParts.LeftArm, BodyParts.Hands, BodyParts.Legs };
                case (int)Armor.Tower_Shield:
                    return new BodyParts[] { BodyParts.Head, BodyParts.LeftArm, BodyParts.Hands, BodyParts.Legs };

                default:
                    return new BodyParts[] { };
            }
Armor value code for shields:

Code: Select all

        public int GetShieldArmorValue()
        {
            switch (TemplateIndex)
            {
                case (int)Armor.Buckler:
                    return 1;
                case (int)Armor.Round_Shield:
                    return 2;
                case (int)Armor.Kite_Shield:
                    return 3;
                case (int)Armor.Tower_Shield:
                    return 4;

                default:
                    return 0;
            }
        }
Then the update armor value code uses those two routines to do what I explain above:

Code: Select all

// Shield armor values in classic are unaffected by their material type.
                    int[] values = { 0, 0, 0, 0, 0, 0, 0 }; // shield's effect on the 7 armor values
                    int armorBonus = armor.GetShieldArmorValue();
                    BodyParts[] protectedBodyParts = armor.GetShieldProtectedBodyParts();

                    foreach (var BodyPart in protectedBodyParts)
                    {
                        values[(int)BodyPart] = armorBonus;
                    }

                    for (int i = 0; i < armorValues.Length; i++)
                    {
                        if (equipping)
                        {
                            armorValues[i] -= (sbyte)(values[i] * 5);
                        }
                        else
                        {
                            armorValues[i] += (sbyte)(values[i] * 5);
                        }
                    }
Here is how hit chance is calculated. Here you can see it only checks the armor slot hit. The key is you want a higher number because the attacker has to roll a higher number out of a 100 to get a successful hit. If not, it will not register as a hit, even if the ray registered you aimed correctly when you swung.
Last edited by l3lessed on Fri Dec 13, 2019 8:43 pm, edited 3 times in total.
My Daggerfall Mod Github: l3lessed DFU Mod Github

My Beth Mods: l3lessed Nexus Page

Daggerfall Unity mods: Combat Overhaul Mod

Enjoy the free work I'm doing? Consider lending your support.

l3lessed
Posts: 1403
Joined: Mon Aug 12, 2019 4:32 pm
Contact:

Re: Cracking Open Combat

Post by l3lessed »

"Checks player is in detectable range -> checks if enemy can hear or see target/player -> false -> checks to see if you succeed stealth check -> True -> Enemy stays in detection mode looking for you using last sensed location until it sees or hears you again." This was really confusing me earlier, as I read it as saying the enemy can't "hear or see" you (false for first check), but still keeps looking for you until it "sees or hears you" "again".)
Actually, that's exactly how it works. If the routine, on update, does not see or hear you, you are no longer detected. The enemy will still be searching for you, he just doesn't know where you are. Think about when you played hide & seek as a kid, or even today. when you see or hear something, you have detected the person and begin searching for them in that area. They then run away and hide silently; you no longer can detect them and have to go back to either searching the old area more or wandering further about trying to detect the person again.

None of the above really matters for your description, as you are pretty spot on. I'm just at this point being a nit picky composition person, as that is my actual training and masters degree.
My Daggerfall Mod Github: l3lessed DFU Mod Github

My Beth Mods: l3lessed Nexus Page

Daggerfall Unity mods: Combat Overhaul Mod

Enjoy the free work I'm doing? Consider lending your support.

User avatar
mikeprichard
Posts: 1037
Joined: Sun Feb 19, 2017 6:49 pm

Re: Cracking Open Combat

Post by mikeprichard »

l3lessed, thanks for the quick and detailed reply; other than assuming you meant 4*5 = 20 and not 40, this is perfectly clear to me on the first try! I just updated the https://en.uesp.net/wiki/Daggerfall:Armor page with the shield armor rating value info; the remaining info you posted was already correctly reflected in the wiki page (though this is a great summary for reference). Thanks very much again for your help.

User avatar
mikeprichard
Posts: 1037
Joined: Sun Feb 19, 2017 6:49 pm

Re: Cracking Open Combat

Post by mikeprichard »

l3lessed wrote: Fri Dec 13, 2019 8:28 pm I'm just at this point being a nit picky composition person, as that is my actual training and masters degree.
In that case, will you allow me to be a complete jerk and point out that in some of your earlier posts, "would of" should be "would have", and "line of site" should be "line of sight"? If you're studying composition, I thought this might be relevant; otherwise, feel free to ignore my obsessive nitpicks.

Let me take a look at this one more time and see if I can get it through my skull. :lol:

Post Reply