Combat Overhaul Alpha

Show off your mod creations or just a work in progress.
Post Reply
User avatar
Yagiza
Posts: 120
Joined: Wed Jul 31, 2019 5:16 pm

Re: Combat Overhaul Alpha

Post by Yagiza »

This mod looks promising.
Do you plan to add left-hand weapon mirroring and maybe two-handed fight?

daggerdude
Posts: 241
Joined: Sat May 23, 2015 2:22 pm

Re: Combat Overhaul Alpha

Post by daggerdude »

I think we are having a disconnect here. I just want to shoot you renders so you can do your thing and thought id add some of my own perspectives.

1.) I'm using .blendr and .jpegs. I can export into whatever is needed.

2.) I am NOT suggesting importing models for weapons at all. I'm talking about handing you renders to work with with your combat overhaul mod.

3.) I was stating my philosophy as to my vision for what combat might look like. I'll make the models all the same, but I'd like to make em better is all.

Just let me know what render format you need to work with the game engine and whatever else you need and I'll try my best as a hobby. I'm not very good with blender , but I'll try my best. To me this is just a hobby because I love this game and I like what you're trying to do so I just thought I'd try to lend you a hand not take over your project :lol:

daggerdude
Posts: 241
Joined: Sat May 23, 2015 2:22 pm

Re: Combat Overhaul Alpha

Post by daggerdude »

Now if you want me to shoot you the 3D file as well so you can play around with it and see how they animations working such I can do that too, I just was under the impression I was sending renders.

Did you want me to send you that file and try to like figure out the animation system for you?

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

Re: Combat Overhaul Alpha

Post by l3lessed »

Hazelnut wrote: Thu Oct 17, 2019 5:58 pm
l3lessed wrote: Thu Oct 10, 2019 4:53 pm At the end of the day, I plan on still releasing a base DFU branch of this mod that runs with the default DF sprites for traditionalist. However, I want a 3d model version for people like me, who want to update the games look and feel.
So, if I understand you correctly here, you're planning to release custom builds of DFU for this mod rather than a dfmod file that can be managed by mod system? I can understand why, but wondering how this could be avoided. I'm planning to make factories for UI and manager classes, which would mean you would be able to register a custom WeaponManager script from a mod. Would this be enough? I assume your code is not in a public github repo so I could look myself?
As of now, first release will consist of a separate DFU branch using the most updated DFU build; I'm still coding in the previous build version, but will move to the latest right before release. Just waiting to update to the newest, as another build might drop between now and my release.

I would prefer to have this as a stand alone mod, so players could use it with the main DFU build releases. However, I am positive most my animation tricks it can't be done right now because I'm having to hijack private routines and ienumerators to get UI to update weapon offsets between each frame render and ram in specific sprites on specific frames. When it comes to my custom weapon speed modifiers, that can easily be done as a stand alone mode. I'm not sure about my unique weapon ranges for the weapons, as I hijacked the itemhelper script to put in a whole new range var for all items.

I have the account setup; just need to link it to my current build and drop the link. Give me maybe a week, and I will get up a github for my current build, even if I haven't finished what I want; I would much rather work with the direction the main build is going than create a whole new DFU branch just for this mod. The code is also still semi-sloppy; I'm positive there are a few areas I can minimize cpu cycle demand with some better coding.

I'll post my fpsweapon script code below.

Code: Select all

// Project:         Daggerfall Tools For Unity
// Copyright:       Copyright (C) 2009-2019 Daggerfall Workshop
// Web Site:        http://www.dfworkshop.net
// License:         MIT License (http://www.opensource.org/licenses/mit-license.php)
// Source Code:     https://github.com/Interkarma/daggerfall-unity
// Original Author: Gavin Clayton (interkarma@dfworkshop.net)
// Contributors:    Hazelnut
// 
// Notes:
//

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using DaggerfallConnect;
using DaggerfallWorkshop.Game.Items;
using DaggerfallConnect.Utility;
using DaggerfallConnect.Arena2;
using DaggerfallWorkshop.Utility;
using DaggerfallWorkshop.Utility.AssetInjection;
using DaggerfallWorkshop.Game.Entity;
using DaggerfallWorkshop.Game.Serialization;
using Random = UnityEngine.Random;

namespace DaggerfallWorkshop.Game
{
    /// <summary>
    /// Renders first-person weapons and attack animations.
    /// Recommended for this component to be on its own game object.
    /// Cam modify pitch of audio source for different weapon speed effects.
    /// </summary>
    [RequireComponent(typeof(DaggerfallAudioSource))]
    public class FPSWeapon : MonoBehaviour
    {
        public bool ShowWeapon = true;
        public int AttackSpeed = 0;
        public bool FlipHorizontal = false;
        public WeaponTypes WeaponType = WeaponTypes.None;
        public MetalTypes MetalType = MetalTypes.None;
        public float Reach = 2.5f;
        public float AttackSpeedScale = 1.0f;
        public float Cooldown = 0.0f;
        public float time;
        public float minrange;
        public float maxrange;
        public SoundClips DrawWeaponSound = SoundClips.DrawWeapon;
        public SoundClips SwingWeaponSound = SoundClips.SwingMediumPitch;

        WeaponTypes currentWeaponType;
        MetalTypes currentMetalType;

        const int nativeScreenWidth = 320;
        const int nativeScreenHeight = 200;

        readonly byte[] leftUnarmedAnims = { 0, 1, 2, 3, 4, 2, 1, 0 };
        int leftUnarmedAnimIndex = 0;

        DaggerfallUnity dfUnity;
        CifRciFile cifFile;
        Texture2D weaponAtlas;
        Rect[] weaponRects;
        RecordIndex[] weaponIndices;
        Rect weaponPosition;
        float weaponScaleX;
        float weaponScaleY;

        DaggerfallAudioSource dfAudioSource;
        WeaponAnimation[] weaponAnims;
        WeaponStates weaponState = WeaponStates.Idle;
        int currentFrame = 0;
        int animTicks = 0;
        Rect curAnimRect;

        public bool hitObject;
        float timer = 0;
        float bob = 0;
        float posi = 0;
        bool posiswitch = false;
        bool bobSwitch;

        PlayerEntity playerEntity;

        readonly Dictionary<int, Texture2D> customTextures = new Dictionary<int, Texture2D>();
        Texture2D curCustomTexture;

        //bool usingRightHand = true;
        //DaggerfallUnityItem currentRightHandWeapon = null;
        //DaggerfallUnityItem currentLeftHandWeapon = null;

        public bool HitObject
        {
            get { return hitObject; }
            set { hitObject = value; }
        }

        #region Properties

        public WeaponStates WeaponState { get { return weaponState; } }

        #endregion

        void Start()
        {
            dfUnity = DaggerfallUnity.Instance;
            dfAudioSource = GetComponent<DaggerfallAudioSource>();
            StartCoroutine(AnimateWeapon());
        }

        void OnGUI()
        {
            GUI.depth = 1;

            // Must be ready and not loading the game
            if (!ReadyCheck() || WeaponType == WeaponTypes.None || GameManager.IsGamePaused || SaveLoadManager.Instance.LoadInProgress)
                return;

            // Must have current weapon texture atlas
            if (weaponAtlas == null || WeaponType != currentWeaponType || MetalType != currentMetalType)
            {
                LoadWeaponAtlas();
                if (weaponAtlas == null)
                    return;
            }
            UpdateWeapon();

            if (Event.current.type.Equals(EventType.Repaint) && ShowWeapon)
            {
                // Draw weapon texture behind other HUD elements
                GUI.DrawTextureWithTexCoords(weaponPosition, curCustomTexture ? curCustomTexture : weaponAtlas, curAnimRect);
            }
        }

        public void OnAttackDirection(WeaponManager.MouseDirections direction)
        {
            // Get state based on attack direction
            WeaponStates state;

                switch (direction)
                {
                    case WeaponManager.MouseDirections.Down:
                        state = WeaponStates.StrikeDown;
                        break;
                    case WeaponManager.MouseDirections.DownLeft:
                        state = WeaponStates.StrikeDownLeft;
                        break;
                    case WeaponManager.MouseDirections.Left:
                        state = WeaponStates.StrikeLeft;
                        break;
                    case WeaponManager.MouseDirections.Right:
                        state = WeaponStates.StrikeRight;
                        break;
                    case WeaponManager.MouseDirections.DownRight:
                        state = WeaponStates.StrikeDownRight;
                        break;
                    case WeaponManager.MouseDirections.Up:
                        state = WeaponStates.StrikeUp;
                        break;
                    default:
                        return;
                }

            // Do not change if already playing attack animation, unless releasing an arrow (bow & state=up->down)
            if (!IsPlayingOneShot() || (WeaponType == WeaponTypes.Bow && weaponState == WeaponStates.StrikeUp && state == WeaponStates.StrikeDown))
                ChangeWeaponState(state);
        }

        public void ChangeWeaponState(WeaponStates state)
        {
            weaponState = state;

            // Only reset frame to 0 for bows if idle state
            if (WeaponType != WeaponTypes.Bow || state == WeaponStates.Idle)
                currentFrame = animTicks = 0;

            UpdateWeapon();
        }

        public bool IsAttacking()
        {
            return IsPlayingOneShot();
        }

        public int GetHitFrame()
        {
            if (WeaponType == WeaponTypes.Bow)
                return 5;
            else
                return 2;
        }

        public int GetCurrentFrame()
        {
            return currentFrame;
        }

        public float GetAnimTime()
        {
            return animTicks * GetAnimTickTime();
        }

        public void PlayActivateSound()
        {
            if (dfAudioSource)
            {
                dfAudioSource.AudioSource.pitch = 1f;// *AttackSpeedScale;
                dfAudioSource.PlayOneShot(DrawWeaponSound, 0);
            }
        }

        public void PlaySwingSound()
        {
            if (dfAudioSource)
            {
                dfAudioSource.AudioSource.pitch = 1f * AttackSpeedScale;
                dfAudioSource.PlayOneShot(SwingWeaponSound, 0, 1.1f);
            }
        }

        public void PlayAttackVoice(SoundClips customSound = SoundClips.None)
        {
            if (dfAudioSource)
            {
                if (customSound == SoundClips.None)
                {
                    PlayerEntity playerEntity = GameManager.Instance.PlayerEntity;
                    SoundClips sound = DaggerfallEntity.GetRaceGenderAttackSound(playerEntity.Race, playerEntity.Gender, true);
                    float pitch = dfAudioSource.AudioSource.pitch;
                    dfAudioSource.AudioSource.pitch = pitch + UnityEngine.Random.Range(0, 0.3f);
                    dfAudioSource.PlayOneShot(sound, 0, 1f);
                    dfAudioSource.AudioSource.pitch = pitch;
                }
                else
                {
                    dfAudioSource.PlayOneShot(customSound, 0, 1f);
                }
            }
        }

        #region Private Methods

        private bool IsPlayingOneShot()
        {
            return (weaponState != WeaponStates.Idle);
        }

        private void UpdateWeapon()
        {

            // Do nothing if weapon not ready
            if (weaponAtlas == null || weaponAnims == null ||
                weaponRects == null || weaponIndices == null)
            {
                return;
            }

            // Reset state if weapon not visible
            if (!ShowWeapon || WeaponType == WeaponTypes.None)
            {
                
                currentFrame = 0;
            }

            // Handle bow with no arrows
            if (!GameManager.Instance.WeaponManager.Sheathed && WeaponType == WeaponTypes.Bow && GameManager.Instance.PlayerEntity.Items.GetItem(Items.ItemGroups.Weapons, (int)Items.Weapons.Arrow) == null)
            {
                GameManager.Instance.WeaponManager.SheathWeapons();
                DaggerfallUI.SetMidScreenText(UserInterfaceWindows.HardStrings.youHaveNoArrows);
            }

            // Store rect and anim
            int weaponAnimRecordIndex;
            if (WeaponType == WeaponTypes.Bow)
                weaponAnimRecordIndex = 0; // Bow has only 1 animation
            else
                weaponAnimRecordIndex = (int)weaponState;

            WeaponAnimation anim = weaponAnims[(int)weaponState];

            try
            {
                bool isImported = customTextures.TryGetValue(MaterialReader.MakeTextureKey(0, (byte)weaponAnimRecordIndex, (byte)currentFrame), out curCustomTexture);
                if (FlipHorizontal && (weaponState == WeaponStates.Idle || weaponState == WeaponStates.StrikeDown || weaponState == WeaponStates.StrikeUp))
                {
                    // Mirror weapon rect
                    if (isImported)
                    {
                        curAnimRect = new Rect(1, 0, -1, 1);
                    }
                    else
                    {
                        Rect rect = weaponRects[weaponIndices[weaponAnimRecordIndex].startIndex + currentFrame];
                        curAnimRect = new Rect(rect.xMax, rect.yMin, -rect.width, rect.height);
                    }
                }
                else
                {
                    if (weaponState == WeaponStates.Idle)
                    {
                        if ((InputManager.Instance.HasAction(InputManager.Actions.MoveRight) || InputManager.Instance.HasAction(InputManager.Actions.MoveLeft) || InputManager.Instance.HasAction(InputManager.Actions.MoveForwards) || InputManager.Instance.HasAction(InputManager.Actions.MoveBackwards)))
                        {
                            if (bob >= .10f && bobSwitch)
                                bobSwitch = false;
                            else if (bob <= 0 && !bobSwitch)
                                bobSwitch = true;

                            if (bobSwitch)
                                bob = bob + Random.Range(.0005f, .001f);
                            else
                                bob = bob - Random.Range(.0005f, .001f);
                        }

                        curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[0].startIndex];
                        weaponAnimRecordIndex = 0;
                        anim.Offset = (bob / 1.5f) - .07f;
                        anim.Offsety = (bob * 1.5f) - .15f;
                    }
                    else
                    {
                        curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[weaponAnimRecordIndex].startIndex + currentFrame];
                        if (weaponState == WeaponStates.StrikeLeft)
                        {
                            if (WeaponType == WeaponTypes.Flail || WeaponType == WeaponTypes.Flail_Magic)
                            {
                                if (GetCurrentFrame() == 1)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[3].startIndex + 3];
                                    weaponAnimRecordIndex = 3;
                                    anim.Offset = posi - .65f;
                                }
                                else if (GetCurrentFrame() == 2)
                                {
                                    posi = posi + .002f;
                                    Rect rect = weaponRects[weaponIndices[6].startIndex + 2];
                                    curAnimRect = new Rect(rect.xMax, rect.yMin, -rect.width, rect.height);
                                    weaponAnimRecordIndex = 6;
                                    anim.Offset = posi + .1f;                                
                                }
                                else
                                {
                                    anim.Offset = posi;
                                    anim.Offsety = (posi / 2) * -1;
                                }
                            }
                            else if (WeaponType == WeaponTypes.Dagger || WeaponType == WeaponTypes.Dagger_Magic)
                            {
                                if (GetCurrentFrame() == 1)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[2].startIndex + 2];
                                    weaponAnimRecordIndex = 2;
                                    anim.Offset = posi - .25f;
                                }
                                else
                                {
                                    anim.Offset = posi;
                                    anim.Offsety = (posi / 2) * -1;
                                }
                            }
                            else if (WeaponType == WeaponTypes.Battleaxe_Magic)
                            {
                                if (currentFrame == 1)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[4].startIndex + currentFrame];
                                    anim.Offset = posi;
                                    anim.Offsety = (posi / 6) * -1;
                                }
                                else if (currentFrame == 2)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[4].startIndex + currentFrame];
                                    anim.Offset = posi + .1f;
                                    anim.Offsety = (posi / 6) * -1;
                                }
                                else if (currentFrame == 3)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[4].startIndex + currentFrame];
                                    anim.Offset = posi + .2f;
                                    anim.Offsety = (posi / 6) * -1;
                                }
                                else if (currentFrame == 4)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[4].startIndex + currentFrame];
                                    anim.Offset = posi + .3f;
                                    anim.Offsety = (posi / 6) * -1;
                                }
                            }
                            else
                            {
                                anim.Offset = posi;
                                anim.Offsety = (posi / 2) * -1;
                            }

                        }
                        else if (weaponState == WeaponStates.StrikeRight)
                        {
                            if (WeaponType == WeaponTypes.Flail || WeaponType == WeaponTypes.Flail_Magic)
                            {
                                if (currentFrame == 1)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[4].startIndex + 3];
                                    weaponAnimRecordIndex = 4;
                                    anim.Offset = posi - .65f;
                                }
                                else if (currentFrame == 2)
                                {
                                    posi = posi + .003f;
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[6].startIndex + 2];
                                    weaponAnimRecordIndex = 6;
                                    anim.Offset = posi + .075f;
                                    anim.Offsety = (posi / 2) -.1f;
                                }
                                else
                                {
                                   anim.Offset = posi;
                                   anim.Offsety = posi / 2;
                                }
                            }
                            else if (WeaponType == WeaponTypes.Dagger || WeaponType == WeaponTypes.Dagger_Magic)
                            {
                                if (currentFrame == 1)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[5].startIndex + 2];
                                    weaponAnimRecordIndex = 5;
                                    anim.Offsety = posi + .25f;
                                    anim.Offset = posi - .25f;
                                }
                                else
                                {
                                    anim.Offset = posi;
                                    anim.Offsety = posi / 2;
                                }
                            }
                            else if (WeaponType == WeaponTypes.Battleaxe_Magic)
                            {
                                if (currentFrame == 1)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[5].startIndex + currentFrame];
                                    anim.Offset = posi;
                                    anim.Offsety = (posi / 4) * -1;
                                }
                                else if (currentFrame == 2)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[5].startIndex + currentFrame];
                                    anim.Offset = posi + .1f;
                                    anim.Offsety = (posi / 4) * -1;
                                }
                                else if (currentFrame == 3)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[5].startIndex + currentFrame];
                                    anim.Offset = posi + .2f;
                                    anim.Offsety = (posi / 4) * -1;
                                }
                                else if (currentFrame == 4)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[5].startIndex + currentFrame];
                                    anim.Offset = posi + .3f;
                                    anim.Offsety = (posi / 4) * -1;
                                }
                            }
                            else
                            {
                                anim.Offset = posi;
                                anim.Offsety = posi / 2;
                            }


                        }
                        else if (weaponState == WeaponStates.StrikeDown)
                        {
                            if (WeaponType == WeaponTypes.Flail || WeaponType == WeaponTypes.Flail_Magic)
                            {
                                if (currentFrame <= 1)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[2].startIndex + 1];
                                    weaponAnimRecordIndex = 2;
                                    anim.Offset = (posi / 4) - .27f;
                                    anim.Offsety = .21f - posi;
                                }
                                else if (currentFrame == 2)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[6].startIndex + 3];
                                    weaponAnimRecordIndex = 6;
                                    anim.Offset = (posi / 4) - .10f;
                                    anim.Offsety = .05f - (posi);
                                }
                                else if (currentFrame == 3)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[6].startIndex + 2];
                                    weaponAnimRecordIndex = 6;
                                    anim.Offset = posi / 4;
                                    anim.Offsety = ((posi) * -1) - .05f;
                                }
                                else
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[6].startIndex + 1];
                                    weaponAnimRecordIndex = 6;
                                    anim.Offset = posi / 4;
                                    anim.Offsety = ((posi) * -1) - .1f;
                                }
                            }
                            else if (WeaponType == WeaponTypes.Dagger || WeaponType == WeaponTypes.Dagger_Magic)
                            {
                                if (currentFrame <= 1)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[2].startIndex + 2];
                                    weaponAnimRecordIndex = 2;
                                    anim.Offset = (posi / 2) - .2f;
                                    anim.Offsety = ((posi) * -1) + .05f;
                                }
                                else
                                {
                                    anim.Offset = posi / 4;
                                    anim.Offsety = (posi) * -1;
                                }
                            }
                            else if (WeaponType == WeaponTypes.Battleaxe || WeaponType == WeaponTypes.Battleaxe_Magic)
                            {
                                if (currentFrame <= 1)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[6].startIndex + 3];
                                    weaponAnimRecordIndex = 1;
                                    anim.Offset = (posi / 4) - .025f;
                                    anim.Offsety = (posi * -1) + .1f;
                                }
                                else if (currentFrame == 2)
                                {
                                    posi = posi + .003f;
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[6].startIndex + 4];
                                    weaponAnimRecordIndex = 1;
                                    anim.Offset = (posi / 4) + .05f;
                                    anim.Offsety = (posi * -1) - .2f;
                                }
                                else if (currentFrame == 3)
                                {
                                    posi = posi + .003f;
                                    anim.Offset = (posi / 4) + .1f;
                                    anim.Offsety = ((posi) * -1) - .05f;
                                }
                                else
                                {
                                    posi = posi + .003f;
                                    anim.Offset = (posi / 4) + .1f;
                                    anim.Offsety = ((posi) * -1) - .15f;
                                }
                            }
                            else
                            {
                                if (currentFrame <= 1)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[6].startIndex + 3];
                                    weaponAnimRecordIndex = 1;
                                    anim.Offset = (posi / 4) - .125f;
                                    anim.Offsety = (posi * -1) + .05f;
                                }
                                else if (currentFrame == 2)
                                {
                                    posi = posi + .003f;
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[6].startIndex + 4];
                                    weaponAnimRecordIndex = 1;
                                    anim.Offset = (posi / 4) - .075f;
                                    anim.Offsety = (posi * -1) - .2f;
                                }
                                else if (currentFrame == 3)
                                {
                                    posi = posi + .003f;
                                    anim.Offset = (posi / 4) + .1f;
                                    anim.Offsety = ((posi) * -1) - .05f;
                                }
                                else
                                {
                                    posi = posi + .003f;
                                    anim.Offset = (posi / 4) + .1f;
                                    anim.Offsety = ((posi) * -1) - .15f;
                                }
                            }                 
                        }
                        else if (weaponState == WeaponStates.StrikeUp)
                        {
                            if ((WeaponType == WeaponTypes.Flail || WeaponType == WeaponTypes.Flail_Magic) && currentFrame < 4)
                            {
                                anim.Offsety = (posi / 2) - .22f;
                            }
                            else if ((WeaponType == WeaponTypes.Flail || WeaponType == WeaponTypes.Flail_Magic) && currentFrame == 4)
                            {
                                curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[6].startIndex + 3];
                                anim.Offsety = (posi / 2) - .11f;
                            }
                            else
                            {
                                if (currentFrame < 2)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[0].startIndex];
                                    weaponAnimRecordIndex = 0;
                                    anim.Offsety = (posi * -1) * 2.2f;
                                }
                                else if (currentFrame == 2)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[6].startIndex + currentFrame];
                                    weaponAnimRecordIndex = 6;
                                    posi = posi + .004f;
                                    anim.Offsety = posi - 1.125f;
                                }
                                else if (currentFrame == 3)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[6].startIndex + currentFrame];
                                    weaponAnimRecordIndex = 6;
                                    posi = posi + .004f;
                                    anim.Offsety = posi - .8f;
                                }
                                else if (currentFrame == 4)
                                {
                                    curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[6].startIndex + currentFrame];
                                    weaponAnimRecordIndex = 6;
                                    posi = posi + .004f;
                                    anim.Offsety = posi - .4f;
                                }
                            }

                        }
                    }
                }

                // Get weapon dimensions
                int width = weaponIndices[weaponAnimRecordIndex].width;
                int height = weaponIndices[weaponAnimRecordIndex].height;

                // Get weapon scale
                weaponScaleX = (float)Screen.width / (float)nativeScreenWidth;
                weaponScaleY = (float)Screen.height / (float)nativeScreenHeight;

                // Adjust scale to be slightly larger when not using point filtering
                // This reduces the effect of filter shrink at edge of display
                if (dfUnity.MaterialReader.MainFilterMode != FilterMode.Point)
                {
                    weaponScaleX *= 1.01f;
                    weaponScaleY *= 1.01f;
                }

                // Source weapon images are designed to overlay a fixed 320x200 display.
                // Some weapons need to align with both top, bottom, and right of display.
                // This means they might be a little stretched on widescreen displays.
                switch (anim.Alignment)
                {
                    case WeaponAlignment.Left:
                        AlignLeft(anim, width, height);
                        break;

                    case WeaponAlignment.Center:
                        AlignCenter(anim, width, height);
                        break;

                    case WeaponAlignment.Right:
                        AlignRight(anim, width, height);
                        break;
                }
            }
            catch (IndexOutOfRangeException)
            {
                DaggerfallUnity.LogMessage("Index out of range exception for weapon animation. Probably due to weapon breaking + being unequipped during animation.");
            }
        }

        private void AlignLeft(WeaponAnimation anim, int width, int height)
        {
            weaponPosition = new Rect(
                Screen.width * anim.Offset,
                Screen.height - height * weaponScaleY,
                width * weaponScaleX,
                height * weaponScaleY);
        }

        private void AlignCenter(WeaponAnimation anim, int width, int height)
        {
            weaponPosition = new Rect(
                Screen.width / 2f - (width * weaponScaleX) / 2f,
                Screen.height - height * weaponScaleY,
                width * weaponScaleX,
                height * weaponScaleY);
        }

        private void AlignRight(WeaponAnimation anim, int width, int height)
        {
            if (FlipHorizontal && (weaponState == WeaponStates.Idle || weaponState == WeaponStates.StrikeDown || weaponState == WeaponStates.StrikeUp))
            {
                // Flip alignment
                AlignLeft(anim, width, height);
                return;
            }
           
            weaponPosition = new Rect(
            (Screen.width * (1f - anim.Offset) - width * weaponScaleX),
            (Screen.height * (1f - anim.Offsety) - height * weaponScaleY),
            width * weaponScaleX,
            height * weaponScaleY);
        }

        private bool ReadyCheck()
        {
            // Do nothing if DaggerfallUnity not ready
            if (!dfUnity.IsReady)
            {
                DaggerfallUnity.LogMessage("FPSWeapon: DaggerfallUnity component is not ready. Have you set your Arena2 path?");
                return false;
            }

            // Ensure cif reader is ready
            if (cifFile == null)
            {
                cifFile = new CifRciFile();
                cifFile.Palette.Load(Path.Combine(dfUnity.Arena2Path, cifFile.PaletteName));
            }

            //// Must have weapon texture atlas
            //if (weaponAtlas == null ||
            //    WeaponType != lastWeaponType ||
            //    MetalType != lastMetalType)
            //{
            //    LoadWeaponAtlas();
            //    if (weaponAtlas == null)
            //        return false;
            //    UpdateWeapon();
            //}

            return true;
        }

        //runs the image animation system frame by frame. This is where all custom animation triggers will go, like collision recoil.
        IEnumerator AnimateWeapon()
        {

            while (true)
            {
               time = GetAnimTickTime();
               minrange = .0025f / time;
               maxrange = .005f / time;
               time = time / 12;

                if (posiswitch == false)
                    posi = 0;

                if (weaponAnims != null && ShowWeapon && !GameManager.Instance.WeaponManager.hitobject)
                {
                    int frameBeforeStepping = currentFrame;

                    // Special animation for unarmed attack to left
                    if ((WeaponType == WeaponTypes.Melee || WeaponType == WeaponTypes.Werecreature)
                        && WeaponState == WeaponStates.StrikeLeft)
                    {
                        // Step frame
                        currentFrame = leftUnarmedAnims[leftUnarmedAnimIndex];
                        leftUnarmedAnimIndex++;
                        if (leftUnarmedAnimIndex >= leftUnarmedAnims.Length)
                        {
                            ChangeWeaponState(WeaponStates.Idle);
                            leftUnarmedAnimIndex = 0;
                        }
                    }
                    else if (WeaponType == WeaponTypes.Bow && weaponState == WeaponStates.StrikeUp)
                    {
                        // Step each frame for drawing the bow until reach frame for ready to release arrow
                        if (currentFrame < weaponAnims[(int)weaponState].NumFrames - 1)
                            currentFrame++;
                        // Record animation ticks for drawing and then holding a bow
                        animTicks++;
                    }
                    else
                    {
                        // Step frame
                        currentFrame++;
                        if (currentFrame >= weaponAnims[(int)weaponState].NumFrames)
                        {
                            if (IsPlayingOneShot())
                            {
                                ChangeWeaponState(WeaponStates.Idle);   // If this is a one-shot anim go to queued weapon state
                                if (WeaponType == WeaponTypes.Bow)
                                    ShowWeapon = false;                 // Immediately hide bow so its idle frame doesn't show before it is hidden for its cooldown
                            }
                            else if (WeaponType == WeaponTypes.Bow && !DaggerfallUnity.Settings.BowDrawback)
                                currentFrame = 3;
                            else
                                currentFrame = 0;                       // Otherwise keep looping frames
                        }
                    }

                    // Only update if the frame actually changed
                    if (frameBeforeStepping != currentFrame)
                        UpdateWeapon();
                    else
                        posiswitch = false;
                }
                //start parry animation if hitobject detected. Begins reversing frames at an increased speed.
                //Goes to idle state at frame 0. This simulates the recoil of hitting an object with a large sword.
                else if (weaponAnims != null && ShowWeapon && GameManager.Instance.WeaponManager.hitobject)
                {
                    int frameBeforeStepping = currentFrame;

                    // Special animation for unarmed attack to left
                    if ((WeaponType == WeaponTypes.Melee || WeaponType == WeaponTypes.Werecreature)
                        && WeaponState == WeaponStates.StrikeLeft)
                    {
                        // Step frame
                        currentFrame = leftUnarmedAnims[leftUnarmedAnimIndex];
                        --leftUnarmedAnimIndex;
                        if (leftUnarmedAnimIndex >= leftUnarmedAnims.Length)
                        {
                            ChangeWeaponState(WeaponStates.Idle);
                            leftUnarmedAnimIndex = 0;
                        }
                    }
                    //reverse attack animation if not on 0/idle frame.
                    else if (currentFrame >= 1)
                    {
                        // Step frame
                        --currentFrame;
                        
                    }
                    //when recoil gets to frame 0, set weapon state to idle to stop attack looping.
                    else
                    {
                        currentFrame = 0;
                        posiswitch = false;
                        ChangeWeaponState(WeaponStates.Idle);   // If this is a one-shot anim go to queued weapon state
                    }

                    // Only update if the frame actually changed
                    if (frameBeforeStepping != currentFrame)
                        UpdateWeapon();
                }

                //put wait timer into if then trigger. If then ensures player is in an attack animation then
                //takes the attack time, divides it by 12, sets up 12 wait timers, and after each timer, offsets
                //the animations x position using the global float posi (which I added).
                if (WeaponState != WeaponStates.Idle && !GameManager.Instance.WeaponManager.hitobject && WeaponType != WeaponTypes.Bow)
                {
                    yield return new WaitForSeconds(time);
                    posi = posi + Random.Range(minrange, maxrange);

                    yield return new WaitForSeconds(time);
                    posi = posi + Random.Range(minrange, maxrange);

                    yield return new WaitForSeconds(time);
                    posi = posi + Random.Range(minrange, maxrange);

                    yield return new WaitForSeconds(time);
                    posi = posi + Random.Range(minrange, maxrange);

                    yield return new WaitForSeconds(time);
                    posi = posi + Random.Range(minrange, maxrange);

                    yield return new WaitForSeconds(time);
                    posi = posi + Random.Range(minrange, maxrange);

                    yield return new WaitForSeconds(time);
                    posi = posi + Random.Range(minrange, maxrange);

                    yield return new WaitForSeconds(time);
                    posi = posi + Random.Range(minrange, maxrange);

                    yield return new WaitForSeconds(time);
                    posi = posi + Random.Range(minrange, maxrange);

                    yield return new WaitForSeconds(time);
                    posi = posi + Random.Range(minrange, maxrange);

                    yield return new WaitForSeconds(time);
                    posi = posi + Random.Range(minrange, maxrange);

                    yield return new WaitForSeconds(time);
                    posi = posi + Random.Range(minrange, maxrange);
                }
                else
                {
                    yield return new WaitForSeconds(time);
                }
            }
        }

        private float GetAnimTickTime()
        {
            PlayerEntity player = GameManager.Instance.PlayerEntity;

            float speed = 0;

            if (WeaponType == WeaponTypes.Bow || player == null)
                return GameManager.classicUpdateInterval;
            else if (GameManager.Instance.WeaponManager.hitobject)
            {
                speed = 85;
                return (speed / 980); // Approximation of classic frame update
            }
                
            else
            {
                speed = (3 * ((115 - player.Stats.LiveSpeed) + AttackSpeed));
                return speed / 980; // Approximation of classic frame update
            }
        }

        private void LoadWeaponAtlas()
        {
            // Get weapon filename
            string filename = WeaponBasics.GetWeaponFilename(WeaponType);

            // Load the weapon texture atlas
            // Texture is dilated into a transparent coloured border to remove dark edges when filtered
            // Important to use returned UV rects when drawing to get right dimensions
            weaponAtlas = GetWeaponTextureAtlas(filename, MetalType, out weaponRects, out weaponIndices, 2, 2, true);
            weaponAtlas.filterMode = dfUnity.MaterialReader.MainFilterMode;

            // Get weapon anims
            weaponAnims = (WeaponAnimation[])WeaponBasics.GetWeaponAnims(WeaponType).Clone();

            // Store current weapon
            currentWeaponType = WeaponType;
            currentMetalType = MetalType;
        }

        #endregion

        #region Texture Loading

        private Texture2D GetWeaponTextureAtlas(
            string filename,
            MetalTypes metalType,
            out Rect[] rectsOut,
            out RecordIndex[] indicesOut,
            int padding,
            int border,
            bool dilate = false)
        {
            // Load texture file
            cifFile.Load(Path.Combine(dfUnity.Arena2Path, filename), FileUsage.UseMemory, true);

            // Read every image in archive
            Rect rect;
            List<Texture2D> textures = new List<Texture2D>();
            List<RecordIndex> indices = new List<RecordIndex>();
            customTextures.Clear();
            for (int record = 0; record < cifFile.RecordCount; record++)
            {
                int frames = cifFile.GetFrameCount(record);
                DFSize size = cifFile.GetSize(record);
                RecordIndex ri = new RecordIndex()
                {
                    startIndex = textures.Count,
                    frameCount = frames,
                    width = size.Width,
                    height = size.Height,
                };
                indices.Add(ri);
                for (int frame = 0; frame < frames; frame++)
                {
                    textures.Add(GetWeaponTexture2D(filename, record, frame, metalType, out rect, border, dilate));

                    Texture2D tex;
                    if (TextureReplacement.TryImportCifRci(filename, record, frame, metalType, true, out tex))
                    {
                        tex.filterMode = dfUnity.MaterialReader.MainFilterMode;
                        tex.wrapMode = TextureWrapMode.Mirror;
                        customTextures.Add(MaterialReader.MakeTextureKey(0, (byte)record, (byte)frame), tex);
                    }
                }
            }

            // Pack textures into atlas
            Texture2D atlas = new Texture2D(2048, 2048, TextureFormat.ARGB32, false);
            rectsOut = atlas.PackTextures(textures.ToArray(), padding, 2048);
            indicesOut = indices.ToArray();

            // Shrink UV rect to compensate for internal border
            float ru = 1f / atlas.width;
            float rv = 1f / atlas.height;
            for (int i = 0; i < rectsOut.Length; i++)
            {
                Rect rct = rectsOut[i];
                rct.xMin += border * ru;
                rct.xMax -= border * ru;
                rct.yMin += border * rv;
                rct.yMax -= border * rv;
                rectsOut[i] = rct;
            }

            return atlas;
        }

        private Texture2D GetWeaponTexture2D(
            string filename,
            int record,
            int frame,
            MetalTypes metalType,
            out Rect rectOut,
            int border = 0,
            bool dilate = false)
        {
            // Get source bitmap
            DFBitmap dfBitmap = cifFile.GetDFBitmap(record, frame);

            // Tint based on metal type
            // But not for steel as that is default colour in files
            if (metalType != MetalTypes.Steel && metalType != MetalTypes.None)
                dfBitmap = ImageProcessing.ChangeDye(dfBitmap, ImageProcessing.GetMetalDyeColor(metalType), DyeTargets.WeaponsAndArmor);

            // Get Color32 array
            DFSize sz;
            Color32[] colors = cifFile.GetColor32(dfBitmap, 0, border, out sz);

            // Dilate edges
            if (border > 0 && dilate)
                ImageProcessing.DilateColors(ref colors, sz);

            // Create Texture2D
            Texture2D texture = new Texture2D(sz.Width, sz.Height, TextureFormat.ARGB32, false);
            texture.SetPixels32(colors);
            texture.Apply(true);

            // Shrink UV rect to compensate for internal border
            float ru = 1f / sz.Width;
            float rv = 1f / sz.Height;
            rectOut = new Rect(border * ru, border * rv, (sz.Width - border * 2) * ru, (sz.Height - border * 2) * rv);

            return texture;
        }

        #endregion
    }
}
As you can see, there has to be a large number of switches to handle the differing weapons. Some weapons were easy, like the sword, because there isn't allot to the differing sprites themselves. Others took a number of tricks to get to look halfway decent, like the flail, because of so many differing parts to the weapon sprite.

It also lacks proper coding notations, as I am sloppy and tend to add it after I am done coding. I found myself to many times changing or removing notes between development cycles anytime I code.

lastly, I have not updated the left handed routine to properly render these animations. I do plan on getting to it either right before first release or right after. I am a right hander, so it doesn't affect me.

If you have any questions how this is working let me know, but the simplest explanation is I recoded the ienumerator routine, which is what is constantly updating the FPS weapon UI on screen. Before, it would run a cycle 5 times for every sprite animation frame. I merely added 12 co-routines that wait a set amount of time based on the weapon animation speed. After each coroutine update, it picks a random number in a range and updates the posi variable. The Posi variable is then passed on to the weapon position routines, which assign where the UI sprite is shown on the players screen. And thus, you get appearance of a full animation only using 5 sprites.

I've been busy with wife, work, and life stuff, and then just got strip throat and bronchitis this week. It's no fun coding when your sick and hopped up on meds.
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: Combat Overhaul Alpha

Post by l3lessed »

1.) I'm using .blendr and .jpegs. I can export into whatever is needed.

2.) I am NOT suggesting importing models for weapons at all. I'm talking about handing you renders to work with with your combat overhaul mod.
I see. JPGS would work fine; they could be recompiled using the daggerfall image asset manager to create the .cif file. Then it is just updating the weaponbasics script file.

However, you need to ensure the sprites match the same measurement dimensions as the original sprites or they will get really distorted and buggy. I believe they also need a transparent alpha layer for the background.

If you can, download the daggerfall image asset tool, open the cif files, go to the weapon sprites, and check their dimensions. As long as you match the original dimensions, then yes, I can take your set of images, recompile them into the cif so we have more frames, and then easily update the script files.

No problem on the disconnect. Happens all the time on online forums. You don't have all that extra social ques when speaking face to face.
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: Combat Overhaul Alpha

Post by l3lessed »

Do you plan to add left-hand weapon mirroring and maybe two-handed fight?
Two handed fighting was in the development cycle at one point and working, as I understand it, but they removed it. Not sure why. I imagine because it was to hard to balance. Also, two handed weapon fighting isn't a real thing for the most part. This is yet another pop culture myth. If a fighter had a second weapon, it was a smaller weapon, like a short sword or a wakazashi, and they were used as parry weapons to block attacks and allow a counter attack without exposing yourself as openly; They could also operate as a quick backup if your weapon got knocked out of your hand; or, every now and then, they could be used to get in a finished blow if the opponent opened themselves up to your offhand side. Point being, they never were used passed very specific instances.

Their are a few mentions in historical texts, but very, very few, and they involved things like daggers. Very small weapons that wouldn't cause balance issues when duel wielding. Try to duel wield a 20 pound sword and a 10 pound sword and keep a proper stance and balance during combat; it isn't worth the trade off, so no one really did it.

I do plan on adding a parry type option, where you can use an offhand weapon to do a short parry and block enemy attacks. I do not know about duel wielding though because of the reasons above, plus balance issues, not to mention figuring out the numerous coding issues to ensure the animations don't overlap and cause glitches.
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.

Ommamar
Posts: 541
Joined: Thu Jul 18, 2019 3:08 am

Re: Combat Overhaul Alpha

Post by Ommamar »

Deleted
Last edited by Ommamar on Sat Apr 18, 2020 12:30 am, edited 1 time in total.

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

Re: Combat Overhaul Alpha

Post by l3lessed »

Wonderful read. Seems to backup my point, that outside very specific settings that usually involved an audience, rules, and some form of an arena, fighters chose either sword + shield or two handed swords. It also points out duel wielding doesn't show up until pretty late in the texts and lacks any real in-depth discussion or backing.

Yeah, I want it more like what you describe. A parry weapon for positioning and controlling your enemies attacks/movements and a main attack weapon for actually landing the killing blow.

The one exception I was thinking of for long term development is allow certain short, light weight weapons to be duel wielded, like short swords, daggers, or the sort. Not sure though, but if it would happen, that is how I would do it.
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
Yagiza
Posts: 120
Joined: Wed Jul 31, 2019 5:16 pm

Re: Combat Overhaul Alpha

Post by Yagiza »

l3lessed wrote: Fri Oct 18, 2019 9:46 pm Two handed fighting was in the development cycle at one point and working, as I understand it, but they removed it. Not sure why. I imagine because it was to hard to balance. Also, two handed weapon fighting isn't a real thing for the most part. This is yet another pop culture myth. If a fighter had a second weapon, it was a smaller weapon, like a short sword or a wakazashi, and they were used as parry weapons to block attacks and allow a counter attack without exposing yourself as openly; They could also operate as a quick backup if your weapon got knocked out of your hand; or, every now and then, they could be used to get in a finished blow if the opponent opened themselves up to your offhand side. Point being, they never were used passed very specific instances.

Their are a few mentions in historical texts, but very, very few, and they involved things like daggers. Very small weapons that wouldn't cause balance issues when duel wielding. Try to duel wield a 20 pound sword and a 10 pound sword and keep a proper stance and balance during combat; it isn't worth the trade off, so no one really did it.

I do plan on adding a parry type option, where you can use an offhand weapon to do a short parry and block enemy attacks. I do not know about duel wielding though because of the reasons above, plus balance issues, not to mention figuring out the numerous coding issues to ensure the animations don't overlap and cause glitches.
The most important part was not two-handed fight, but mirroring of left-hand weapon.
And maybe shield display.

Ommamar
Posts: 541
Joined: Thu Jul 18, 2019 3:08 am

Re: Combat Overhaul Alpha

Post by Ommamar »

l3lessed wrote: Sat Oct 19, 2019 8:42 am Wonderful read. Seems to backup my point, that outside very specific settings that usually involved an audience, rules, and some form of an arena, fighters chose either sword + shield or two handed swords. It also points out duel wielding doesn't show up until pretty late in the texts and lacks any real in-depth discussion or backing.

Yeah, I want it more like what you describe. A parry weapon for positioning and controlling your enemies attacks/movements and a main attack weapon for actually landing the killing blow.

The one exception I was thinking of for long term development is allow certain short, light weight weapons to be duel wielded, like short swords, daggers, or the sort. Not sure though, but if it would happen, that is how I would do it.
Last edited by Ommamar on Sat Apr 18, 2020 12:31 am, edited 1 time in total.

Post Reply