Ejemplo n.º 1
0
        public virtual TextFile.Token[] GetRandomTokens(int id, bool dfRand = false)
        {
            TextFile.Token[] sourceTokens = GetRSCTokens(id);

            // Build a list of token subrecords
            List <TextFile.Token>   currentStream = new List <TextFile.Token>();
            List <TextFile.Token[]> tokenStreams  = new List <TextFile.Token[]>();

            for (int i = 0; i < sourceTokens.Length; i++)
            {
                // If we're at end of subrecord then start a new stream
                if (sourceTokens[i].formatting == TextFile.Formatting.SubrecordSeparator)
                {
                    tokenStreams.Add(currentStream.ToArray());
                    currentStream.Clear();
                    continue;
                }

                // Otherwise keep adding to current stream
                currentStream.Add(sourceTokens[i]);
            }

            // Complete final stream
            tokenStreams.Add(currentStream.ToArray());

            // Select a random token stream
            int index = dfRand ? (int)(DFRandom.rand() % tokenStreams.Count) : UnityEngine.Random.Range(0, tokenStreams.Count);

            // Select the next to last item from the array if the length of the last one is zero
            index = (tokenStreams[index].Length == 0 ? index - 1 : index);

            return(tokenStreams[index]);
        }
Ejemplo n.º 2
0
        void FixedUpdate()
        {
            // Unable to attack if AI disabled or paralyzed
            if (GameManager.Instance.DisableAI || entityBehaviour.Entity.IsParalyzed)
            {
                return;
            }

            // Countdown to next melee attack
            MeleeTimer -= Time.deltaTime;

            if (MeleeTimer < 0)
            {
                MeleeTimer = 0;
            }

            EnemyEntity entity = entityBehaviour.Entity as EnemyEntity;
            int         speed  = entity.Stats.LiveSpeed;

            // Note: Speed comparison here is reversed from classic. Classic's way makes fewer attack
            // attempts at higher speeds, so it seems backwards.
            if (GameManager.ClassicUpdate && (DFRandom.rand() % speed >= (speed >> 3) + 6 && MeleeTimer == 0))
            {
                if (!MeleeAnimation())
                {
                    return;
                }

                ResetMeleeTimer();
            }
        }
 public override string PaintingPrefix2()
 {                    // %pp2
     DFRandom.rand(); // Classic uses every other value.
     TextFile.Token[] tokens = DaggerfallUnity.Instance.TextProvider.GetRandomTokens(paintingPp2, true);
     MacroHelper.ExpandMacros(ref tokens);
     return((tokens.Length > 0) ? tokens[0].text : "%pp2[idxError]");
 }
Ejemplo n.º 4
0
            public override string ImperialName()
            {   // %imp
                string[] names = { "Pelagius", "Cephorus", "Uriel", "Cassynder", "Voragiel", "Trabbatus" };
                DFRandom.Seed = (uint)parent.GetHashCode();
                uint rand = DFRandom.rand() % 6;

                return(names[rand]);
            }
Ejemplo n.º 5
0
        void Update()
        {
            // If a melee attack has reached the damage frame we can run a melee attempt
            if (mobile.DoMeleeDamage)
            {
                MeleeDamage();
                mobile.DoMeleeDamage = false;
            }
            // If a bow attack has reached the shoot frame we can shoot an arrow
            else if (mobile.ShootArrow)
            {
                BowDamage(); // TODO: Shoot 3D projectile instead of doing an instant hit
                mobile.ShootArrow = false;

                DaggerfallAudioSource dfAudioSource = GetComponent <DaggerfallAudioSource>();
                if (dfAudioSource)
                {
                    dfAudioSource.PlayOneShot((int)SoundClips.ArrowShoot, 1, 1.0f);
                }
            }

            // Countdown to next melee attack
            meleeTimer -= Time.deltaTime;

            if (meleeTimer < 0)
            {
                meleeTimer = 0;
            }

            EnemyEntity entity = entityBehaviour.Entity as EnemyEntity;
            int         speed  = entity.Stats.LiveSpeed;

            // Note: Speed comparison here is reversed from classic. Classic's way makes fewer attack
            // attempts at higher speeds, so it seems backwards.
            if (classicUpdate && (DFRandom.rand() % speed >= (speed >> 3) + 6 && meleeTimer == 0))
            {
                MeleeAnimation();

                meleeTimer  = Random.Range(1500, 3001);
                meleeTimer -= 50 * (GameManager.Instance.PlayerEntity.Level - 10);

                // Note: In classic, what happens here is
                // meleeTimer += 450 * (enemydata[130] - 2);
                // Apparently this was meant to reference the game reflexes setting,
                // which is stored in playerentitydata[130].
                // Instead enemydata[130] seems to instead always be 0, the equivalent of
                // "very high" reflexes, regardless of what the game reflexes are.
                // Here, we use the reflexes data as was intended.
                meleeTimer += 450 * ((int)GameManager.Instance.PlayerEntity.Reflexes - 2);

                if (meleeTimer > 100000 || meleeTimer < 0)
                {
                    meleeTimer = 1500;
                }

                meleeTimer /= 980; // Approximates classic frame update
            }
        }
Ejemplo n.º 6
0
            public override string ImperialName()
            {   // %imp
                string[]      names  = { "Pelagius", "Cephorus", "Uriel", "Cassynder", "Voragiel", "Trabbatus" };
                System.Random random = new System.Random();
                DFRandom.Seed = (uint)random.Next();
                uint rand = DFRandom.rand() % 6;

                return(names[rand]);
            }
            public override string ArtistName()
            {                    // %an
                DFRandom.rand(); // Classic uses every other value.
                uint    rand   = DFRandom.rand() & 1;
                Genders gender = (Genders)rand;

                rand = DFRandom.rand();
                NameHelper.BankTypes race = (NameHelper.BankTypes)(rand & 7);
                return(DaggerfallUnity.Instance.NameHelper.FullName(race, gender));
            }
Ejemplo n.º 8
0
        public bool SetNewRulerData(int factionID)
        {
            if (factionDict.ContainsKey(factionID))
            {
                FactionFile.FactionData faction = factionDict[factionID];
                faction.rulerPowerBonus = DFRandom.random_range_inclusive(0, 50) + 20;
                uint random = DFRandom.rand() << 16;
                faction.rulerNameSeed = DFRandom.rand() | random;

                return(true);
            }

            return(false);
        }
Ejemplo n.º 9
0
        void FixedUpdate()
        {
            const int speedFloor = 8;

            // Unable to attack if AI disabled or paralyzed
            if (GameManager.Instance.DisableAI || entityBehaviour.Entity.IsParalyzed)
            {
                return;
            }

            // Unable to attack when playing certain oneshot anims
            if (mobile && mobile.IsPlayingOneShot() && mobile.OneShotPauseActionsWhilePlaying())
            {
                return;
            }

            // Countdown to next melee attack
            MeleeTimer -= Time.deltaTime;

            if (MeleeTimer < 0)
            {
                MeleeTimer = 0;
            }

            // Get entity speed and enforce a lower limit so Drain Speed does not prevent attack ever firing
            EnemyEntity entity = entityBehaviour.Entity as EnemyEntity;
            int         speed  = entity.Stats.LiveSpeed;

            if (speed < speedFloor)
            {
                speed = speedFloor;
            }

            // Slow down enemy frame rate based on floored speed value
            // If enemy is still at maximum speed then divisor is 1 and will experience no change to frame rate
            mobile.FrameSpeedDivisor = entity.Stats.PermanentSpeed / speed;

            // Note: Speed comparison here is reversed from classic. Classic's way makes fewer attack
            // attempts at higher speeds, so it seems backwards.
            if (GameManager.ClassicUpdate && (DFRandom.rand() % speed >= (speed >> 3) + 6 && MeleeTimer == 0))
            {
                if (!MeleeAnimation())
                {
                    return;
                }

                ResetMeleeTimer();
            }
        }
Ejemplo n.º 10
0
        void Update()
        {
            if (GameManager.Instance.DisableAI)
            {
                return;
            }

            // If a melee attack has reached the damage frame we can run a melee attempt
            if (mobile.DoMeleeDamage)
            {
                MeleeDamage();
                mobile.DoMeleeDamage = false;
            }
            // If a bow attack has reached the shoot frame we can shoot an arrow
            else if (mobile.ShootArrow)
            {
                ShootBow();
                mobile.ShootArrow = false;

                DaggerfallAudioSource dfAudioSource = GetComponent <DaggerfallAudioSource>();
                if (dfAudioSource)
                {
                    dfAudioSource.PlayOneShot((int)SoundClips.ArrowShoot, 1, 1.0f);
                }
            }

            // Countdown to next melee attack
            MeleeTimer -= Time.deltaTime;

            if (MeleeTimer < 0)
            {
                MeleeTimer = 0;
            }

            EnemyEntity entity = entityBehaviour.Entity as EnemyEntity;
            int         speed  = entity.Stats.LiveSpeed;

            // Note: Speed comparison here is reversed from classic. Classic's way makes fewer attack
            // attempts at higher speeds, so it seems backwards.
            if (GameManager.ClassicUpdate && (DFRandom.rand() % speed >= (speed >> 3) + 6 && MeleeTimer == 0))
            {
                if (!MeleeAnimation())
                {
                    return;
                }

                ResetMeleeTimer();
            }
        }
Ejemplo n.º 11
0
        // Gets random surname for Nord names that follow 0+1+"sen" pattern
        string GetRandomNordSurname(NameBank nameBank)
        {
            // Get set parts
            string[] partsA, partsB;
            partsA = nameBank.sets[0].parts;
            partsB = nameBank.sets[1].parts;

            // Generate strings
            uint   index   = DFRandom.rand() % (uint)partsA.Length;
            string stringA = partsA[index];

            index = DFRandom.rand() % (uint)partsB.Length;
            string stringB = partsB[index];

            return(stringA + stringB + "sen");
        }
Ejemplo n.º 12
0
        // Gets random Redguard name which follows 0+1+2+3(75%) (male), 0+1+2+4 (female) pattern
        string GetRandomRedguardName(NameBank nameBank, Genders gender)
        {
            // Get set parts
            string[] partsA, partsB, partsC, partsD;
            if (gender == Genders.Male)
            {
                partsA = nameBank.sets[0].parts;
                partsB = nameBank.sets[1].parts;
                partsC = nameBank.sets[2].parts;
                partsD = nameBank.sets[3].parts;
            }
            else
            {
                partsA = nameBank.sets[0].parts;
                partsB = nameBank.sets[1].parts;
                partsC = nameBank.sets[2].parts;
                partsD = nameBank.sets[4].parts;
            }

            // Generate strings
            uint   index   = DFRandom.rand() % (uint)partsA.Length;
            string stringA = partsA[index];

            index = DFRandom.rand() % (uint)partsB.Length;
            string stringB = partsB[index];

            index = DFRandom.rand() % (uint)partsC.Length;
            string stringC = partsC[index];

            string stringD = string.Empty;

            if (gender == Genders.Female || (DFRandom.rand() % 100 < 75))
            {
                index   = DFRandom.rand() % (uint)partsD.Length;
                stringD = partsD[index];
            }

            return(stringA + stringB + stringC + stringD);
        }
Ejemplo n.º 13
0
        // Gets random first name by gender for names that follow 0+1 (male), 2+3 (female) pattern
        string GetRandomFirstName(NameBank nameBank, Genders gender)
        {
            // Get set parts
            string[] partsA, partsB;
            if (gender == Genders.Male)
            {
                partsA = nameBank.sets[0].parts;
                partsB = nameBank.sets[1].parts;
            }
            else
            {
                partsA = nameBank.sets[2].parts;
                partsB = nameBank.sets[3].parts;
            }

            // Generate strings
            uint   index   = DFRandom.rand() % (uint)partsA.Length;
            string stringA = partsA[index];

            index = DFRandom.rand() % (uint)partsB.Length;
            string stringB = partsB[index];

            return(stringA + stringB);
        }
        public TextFile.Token[] InitPaintingInfo(int paintingTextId = 250)
        {
            GetMacroDataSource();
            if (ItemGroup == ItemGroups.Paintings && dataSource.paintingInfo == null)
            {
                DFRandom.srand(message);
                uint paintingIndex = DFRandom.rand() % 180;
                dataSource.paintingFileIdx = paintingIndex & 7;
                char paintingFileChar = (char)((paintingIndex >> 3) + 97);
                dataSource.paintingFilename = paintingFileChar + "paint.cif";

                byte[] paintingRecord = DaggerfallUnity.Instance.ContentReader.PaintFileReader.Read(paintingIndex);
                Debug.LogFormat("painting file: {0}, index: {1}, cif idx: {2}, record: {3} {4} {5}", dataSource.paintingFilename, paintingIndex, dataSource.paintingFileIdx, paintingRecord[0], paintingRecord[1], paintingRecord[2]);

                dataSource.paintingSub = GetPaintingRecordPart(paintingRecord, 0, 9) + 6100;   // for %sub macro
                dataSource.paintingAdj = GetPaintingRecordPart(paintingRecord, 10, 19) + 6200; // for %adj macro
                dataSource.paintingPp1 = GetPaintingRecordPart(paintingRecord, 20, 29) + 6300; // for %pp1 macro
                dataSource.paintingPp2 = GetPaintingRecordPart(paintingRecord, 30, 39) + 6400; // for %pp2 macro

                ITextProvider textProvider = DaggerfallUnity.Instance.TextProvider;
                dataSource.paintingInfo = textProvider.GetRandomTokens(paintingTextId, true);
            }
            return(dataSource.paintingInfo);
        }
Ejemplo n.º 15
0
        // Get random monster name.
        // Monster1: 0+(50% +1)+2
        // Monster2: 0+(50% +1)+2+(if female, +3)
        // Monster3: (if male, 25% +3 + " ")+0+1+2
        string GetRandomMonsterName(Genders gender)
        {
            BankTypes type     = (BankTypes)UnityEngine.Random.Range(8, 9 + 1); // Get random Monster1 or Monster2 for now.
            NameBank  nameBank = bankDict[type];

            // Get set parts
            string[] partsA, partsB, partsC, partsD;
            partsA = nameBank.sets[0].parts;
            partsB = nameBank.sets[1].parts;
            partsC = nameBank.sets[2].parts;
            partsD = null;

            string stringA = string.Empty;
            string stringB = string.Empty;
            string stringC = string.Empty;
            string stringD = string.Empty;

            uint index = 0;

            // Additional set for Monster2 and Monster3
            if (nameBank.sets.Length >= 4)
            {
                partsD = nameBank.sets[3].parts;
            }

            // Generate strings
            if (type != BankTypes.Monster3) // Monster1 or Monster2
            {
                index   = DFRandom.rand() % (uint)partsA.Length;
                stringA = partsA[index];

                stringB = string.Empty;
                if (DFRandom.rand() % 50 < 25)
                {
                    index   = DFRandom.rand() % (uint)partsB.Length;
                    stringB = partsB[index];
                }

                index   = DFRandom.rand() % (uint)partsC.Length;
                stringC = partsC[index];

                // Additional set for Monster2 female
                if (partsD != null && gender == Genders.Female)
                {
                    index   = DFRandom.rand() % (uint)partsD.Length;
                    stringD = partsD[index];
                }
            }
            else // Monster3
            {
                if (gender == Genders.Female || DFRandom.rand() % 100 >= 25)
                {
                    index   = DFRandom.rand() % (uint)partsA.Length;
                    stringA = partsA[index];
                }
                else
                {
                    index   = DFRandom.rand() % (uint)partsD.Length;
                    stringA = partsD[index] + " ";

                    index   = DFRandom.rand() % (uint)partsA.Length;
                    stringB = partsA[index];
                }

                index   = DFRandom.rand() % (uint)partsB.Length;
                stringC = partsB[index];

                index   = DFRandom.rand() % (uint)partsC.Length;
                stringD = partsC[index];
            }

            return(stringA + stringB + stringC + stringD);
        }
Ejemplo n.º 16
0
        public static int CalculateAttackDamage(Entity.DaggerfallEntity attacker, Entity.DaggerfallEntity target, int weaponEquipSlot, int enemyAnimStateRecord)
        {
            if (attacker == null || target == null)
            {
                return(0);
            }

            int minBaseDamage     = 0;
            int maxBaseDamage     = 0;
            int damageModifiers   = 0;
            int damage            = 0;
            int chanceToHitMod    = 0;
            int backstabbingLevel = 0;

            Entity.PlayerEntity       player = GameManager.Instance.PlayerEntity;
            Items.DaggerfallUnityItem weapon = attacker.ItemEquipTable.GetItem((Items.EquipSlots)weaponEquipSlot);
            short skillID = 0;

            // Choose whether weapon-wielding enemies use their weapons or weaponless attacks.
            // In classic, weapon-wielding enemies use the damage values of their weapons
            // instead of their weaponless values.
            // For some enemies this gives lower damage than similar-tier monsters
            // and the weaponless values seems more appropriate, so here
            // enemies will choose to use their weaponless attack if it is more damaging.
            Entity.EnemyEntity AIAttacker = attacker as Entity.EnemyEntity;
            if (AIAttacker != null && weapon != null)
            {
                int weaponAverage   = ((minBaseDamage + maxBaseDamage) / 2);
                int noWeaponAverage = ((AIAttacker.MobileEnemy.MinDamage + AIAttacker.MobileEnemy.MaxDamage) / 2);

                if (noWeaponAverage > weaponAverage)
                {
                    // Use hand-to-hand
                    weapon = null;
                }
            }

            if (weapon != null)
            {
                // If the attacker is using a weapon, check if the material is high enough to damage the target
                if (target.MinMetalToHit > (Items.WeaponMaterialTypes)weapon.NativeMaterialValue)
                {
                    if (attacker == player)
                    {
                        DaggerfallUI.Instance.PopupMessage(UserInterfaceWindows.HardStrings.materialIneffective);
                    }

                    return(0);
                }

                // Get weapon skill used
                skillID = weapon.GetWeaponSkillIDAsShort();
            }
            else
            {
                skillID = (short)DFCareer.Skills.HandToHand;
            }

            chanceToHitMod = attacker.Skills.GetLiveSkillValue(skillID);

            Entity.EnemyEntity AITarget = null;
            if (target != player)
            {
                AITarget = target as Entity.EnemyEntity;
            }

            if (attacker == player)
            {
                // Apply swing modifiers. Not applied to hand-to-hand in classic.
                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 hand-to-hand.
                    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)Entity.Races.DarkElf)
                    {
                        damageModifiers += (attacker.Level / 4);
                        chanceToHitMod  += (attacker.Level / 4);
                    }
                    else if (skillID == (short)DFCareer.Skills.Archery)
                    {
                        if (player.RaceTemplate.ID == (int)Entity.Races.WoodElf)
                        {
                            damageModifiers += (attacker.Level / 3);
                            chanceToHitMod  += (attacker.Level / 3);
                        }
                    }
                    else if (player.RaceTemplate.ID == (int)Entity.Races.Redguard)
                    {
                        damageModifiers += (attacker.Level / 3);
                        chanceToHitMod  += (attacker.Level / 3);
                    }
                }

                // Apply backstabbing
                if (enemyAnimStateRecord % 5 > 2) // Facing away from player
                {
                    chanceToHitMod += attacker.Skills.GetLiveSkillValue(DFCareer.Skills.Backstabbing);
                    attacker.TallySkill(DFCareer.Skills.Backstabbing, 1); // backstabbing
                    backstabbingLevel = attacker.Skills.GetLiveSkillValue(DFCareer.Skills.Backstabbing);
                }
            }

            // Choose struck body part
            int[] bodyParts      = { 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6 };
            int   struckBodyPart = bodyParts[UnityEngine.Random.Range(0, bodyParts.Length)];

            // Get damage for weaponless attacks
            if (skillID == (short)DFCareer.Skills.HandToHand)
            {
                if (attacker == player)
                {
                    if (CalculateSuccessfulHit(attacker, target, chanceToHitMod, null, struckBodyPart))
                    {
                        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.
                        damage += DamageModifier(attacker.Stats.LiveStrength);

                        // Handle backstabbing
                        if (backstabbingLevel > 0 && UnityEngine.Random.Range(1, 101) <= backstabbingLevel)
                        {
                            damage *= 3;
                            string backstabMessage = UserInterfaceWindows.HardStrings.successfulBackstab;
                            DaggerfallUI.Instance.PopupMessage(backstabMessage);
                        }
                    }
                }
                else // 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;
                        }

                        if (DFRandom.rand() % 100 < 50 && minBaseDamage > 0 && CalculateSuccessfulHit(attacker, target, chanceToHitMod, null, struckBodyPart))
                        {
                            damage += UnityEngine.Random.Range(minBaseDamage, maxBaseDamage + 1);
                        }
                        ++attackNumber;
                    }
                }
            }
            // Handle weapon attacks
            else
            {
                if (CalculateSuccessfulHit(attacker, target, chanceToHitMod, weapon, struckBodyPart))
                {
                    damage  = UnityEngine.Random.Range(weapon.GetBaseDamageMin(), weapon.GetBaseDamageMax() + 1);
                    damage += damageModifiers;

                    // Apply modifiers for Skeletal Warrior.
                    if (AITarget != null && AITarget.CareerIndex == (int)Entity.MonsterCareers.SkeletalWarrior)
                    {
                        if (weapon.NativeMaterialValue == (int)Items.WeaponMaterialTypes.Silver)
                        {
                            damage *= 2;
                        }
                        if ((weapon.flags & 0x10) == 0) // not a blunt weapon
                        {
                            damage /= 2;
                        }
                    }

                    // Apply strength modifier
                    damage += DamageModifier(attacker.Stats.LiveStrength);

                    // Apply material modifier.
                    // The in-game display in Daggerfall of weapon damages with material modifiers is incorrect. The material modifier is half of what the display suggests.
                    damage += weapon.GetWeaponMaterialModifier();

                    if (damage < 1)
                    {
                        damage = 0;
                    }

                    damage += GetBonusOrPenaltyByEnemyType(attacker, AITarget);

                    if (backstabbingLevel > 1 && UnityEngine.Random.Range(1, 100 + 1) <= backstabbingLevel)
                    {
                        damage *= 3;
                        string backstabMessage = UserInterfaceWindows.HardStrings.successfulBackstab;
                        DaggerfallUI.Instance.PopupMessage(backstabMessage);
                    }
                }
            }

            damage = Mathf.Max(0, damage);

            // If damage was done by a weapon, damage the weapon and armor of the hit body part.
            // In classic, shields are never damaged, only armor specific to the hitbody part is.
            // Here, if an equipped shield covers the hit body part, it takes damage instead.
            if (weapon != null && damage > 0)
            {
                weapon.DamageThroughPhysicalHit(damage, attacker);

                Items.DaggerfallUnityItem shield = target.ItemEquipTable.GetItem(Items.EquipSlots.LeftHand);
                bool shieldTakesDamage           = false;
                if (shield != null)
                {
                    Items.BodyParts[] protectedBodyParts = shield.GetShieldProtectedBodyParts();

                    for (int i = 0; (i < protectedBodyParts.Length) && !shieldTakesDamage; i++)
                    {
                        if (protectedBodyParts[i] == (Items.BodyParts)struckBodyPart)
                        {
                            shieldTakesDamage = true;
                        }
                    }
                }

                if (shieldTakesDamage)
                {
                    shield.DamageThroughPhysicalHit(damage, target);
                }
                else
                {
                    Items.EquipSlots          hitSlot = Items.DaggerfallUnityItem.GetEquipSlotForBodyPart((Items.BodyParts)struckBodyPart);
                    Items.DaggerfallUnityItem armor   = target.ItemEquipTable.GetItem(hitSlot);
                    if (armor != null)
                    {
                        armor.DamageThroughPhysicalHit(damage, target);
                    }
                }
            }

            return(damage);
        }
Ejemplo n.º 17
0
        public static string GetName(int seed, DFLocation.BuildingTypes type, int factionID, string locationName, string regionName)
        {
            const string firstNameTitleVar = "%ef";
            const string cityNameTitleVar  = "%cn";
            const string royalTitleVar     = "%rt";

            string a = string.Empty, b = string.Empty;
            string result = string.Empty;

            bool singleton = false;

            FactionFile.FactionData factionData;
            DFRandom.srand(seed);
            switch (type)
            {
            case DFLocation.BuildingTypes.HouseForSale:
                return("House for sale");

            case DFLocation.BuildingTypes.Tavern:
                b = TavernsB[DFRandom.random_range(0, TavernsB.Length)];
                a = TavernsA[DFRandom.random_range(0, TavernsA.Length)];
                break;

            case DFLocation.BuildingTypes.GeneralStore:
                b = GeneralStoresB[DFRandom.random_range(0, GeneralStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.WeaponSmith:
                b = WeaponStoresB[DFRandom.random_range(0, WeaponStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.Armorer:
                b = ArmorStoresB[DFRandom.random_range(0, ArmorStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.Bookseller:
                b = BookStoresB[DFRandom.random_range(0, BookStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.ClothingStore:
                b = ClothingStoresB[DFRandom.random_range(0, ClothingStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.Alchemist:
                b = AlchemyStoresB[DFRandom.random_range(0, AlchemyStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.GemStore:
                b = GemStoresB[DFRandom.random_range(0, GemStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.PawnShop:
                b = PawnStoresB[DFRandom.random_range(0, PawnStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.FurnitureStore:
                b = FurnitureStoresB[DFRandom.random_range(0, FurnitureStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.Library:
                b = LibraryStoresB[DFRandom.random_range(0, LibraryStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.Bank:
                // Banks always appear to be named "The Bank of RegionName"
                b = regionName;
                a = "The Bank of";
                break;

            case DFLocation.BuildingTypes.GuildHall:
                // Guild halls get the name from faction data
                if (DaggerfallUnity.Instance.ContentReader.FactionFileReader.GetFactionData(factionID, out factionData))
                {
                    a         = factionData.name;
                    singleton = true;
                }
                break;

            case DFLocation.BuildingTypes.Temple:
                // Temples get name from faction data - always seem to be first child of factionID
                if (DaggerfallUnity.Instance.ContentReader.FactionFileReader.GetFactionData(factionID, out factionData))
                {
                    if (factionData.children.Count > 0)
                    {
                        FactionFile.FactionData firstChild;
                        if (DaggerfallUnity.Instance.ContentReader.FactionFileReader.GetFactionData(factionData.children[0], out firstChild))
                        {
                            a         = firstChild.name;
                            singleton = true;
                        }
                    }
                }
                break;

            case DFLocation.BuildingTypes.Palace:
                // Main palace names come from TEXT.RSC (e.g. "Castle Daggerfall")
                // Other palaces are just named "Palace" (still need to confirm behaviour)
                int textId = 0;
                if (locationName == "Daggerfall")
                {
                    textId = 475;
                }
                else if (locationName == "Wayrest")
                {
                    textId = 476;
                }
                else if (locationName == "Sentinel")
                {
                    textId = 477;
                }

                if (textId > 0)
                {
                    TextFile.Token[] nameTokens = DaggerfallUnity.Instance.TextProvider.GetRSCTokens(textId);
                    foreach (TextFile.Token token in nameTokens)
                    {
                        if (token.formatting == TextFile.Formatting.Text)
                        {
                            a = token.text;
                            break;
                        }
                    }
                    a = a.TrimEnd('.');     // remove character '.' from castle text record entry if it is last character
                }
                else
                {
                    a = "Palace";
                }
                singleton = true;
                break;

            default:
                // Do nothing for unknown/unsupported building type
                // Houses can actually change names based on active quests
                return(string.Empty);
            }

            // Replace %cn
            a = a.Replace(cityNameTitleVar, locationName);

            // Replace %ef
            if (a.Contains(firstNameTitleVar))
            {
                // Need to burn a rand() for %ef roll to be correct.
                // Classic is always doing this when expanding a macro.
                DFRandom.rand();

                // In classic, the function expanding the %ef macro uses a global variable containing the current
                // region race. However, this variable is never updated when the character travels
                // and remains at 0. This explains why the Breton name bank is always used for shops.
                // This global variable is probably a leftover from Daggerfall early development as,
                // with the exception of %lp, which presents a similar issue and always returns
                // "High Rock", all naming functions use a global array of 62 fixed race values, one for each region.
                // As with %lp, we choose to fix the original bug in DFU and use this array, meaning that
                // all shops in Hammerfell now use Redguard names.
                NameHelper.BankTypes nameBank = (NameHelper.BankTypes)MapsFile.RegionRaces[GameManager.Instance.PlayerGPS.CurrentRegionIndex];
                string firstName = DaggerfallUnity.Instance.NameHelper.FirstName(nameBank, Game.Entity.Genders.Male);
                a = a.Replace(firstNameTitleVar, firstName);
            }

            // Replace %rt based on faction ruler
            if (a.Contains(royalTitleVar))
            {
                a = a.Replace(royalTitleVar, MacroHelper.RegentTitle(null));
            }

            // Final text is "{a} {b}" for two-part names or just "{a}" for singleton names
            if (!singleton)
            {
                result = string.Format("{0} {1}", a, b);
            }
            else
            {
                result = a;
            }

            return(result);
        }
Ejemplo n.º 18
0
        public static string GetName(int seed, DFLocation.BuildingTypes type, int factionID, string locationName, string regionName)
        {
            const string firstNameTitleVar = "%ef";
            const string cityNameTitleVar  = "%cn";
            const string royalTitleVar     = "%rt";

            string a = string.Empty, b = string.Empty;
            string result = string.Empty;

            bool singleton = false;

            FactionFile.FactionData factionData;
            DFRandom.srand(seed);
            switch (type)
            {
            case DFLocation.BuildingTypes.HouseForSale:
                return("House for sale");

            case DFLocation.BuildingTypes.Tavern:
                b = TavernsB[DFRandom.random_range(0, TavernsB.Length)];
                a = TavernsA[DFRandom.random_range(0, TavernsA.Length)];
                break;

            case DFLocation.BuildingTypes.GeneralStore:
                b = GeneralStoresB[DFRandom.random_range(0, GeneralStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.WeaponSmith:
                b = WeaponStoresB[DFRandom.random_range(0, WeaponStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.Armorer:
                b = ArmorStoresB[DFRandom.random_range(0, ArmorStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.Bookseller:
                b = BookStoresB[DFRandom.random_range(0, BookStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.ClothingStore:
                b = ClothingStoresB[DFRandom.random_range(0, ClothingStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.Alchemist:
                b = AlchemyStoresB[DFRandom.random_range(0, AlchemyStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.GemStore:
                b = GemStoresB[DFRandom.random_range(0, GemStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.PawnShop:
                b = PawnStoresB[DFRandom.random_range(0, PawnStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.FurnitureStore:
                b = FurnitureStoresB[DFRandom.random_range(0, FurnitureStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.Library:
                b = LibraryStoresB[DFRandom.random_range(0, LibraryStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.Bank:
                // Banks always appear to be named "The Bank of RegionName"
                b = regionName;
                a = "The Bank of";
                break;

            case DFLocation.BuildingTypes.GuildHall:
                // Guild halls get the name from faction data
                if (DaggerfallUnity.Instance.ContentReader.FactionFileReader.GetFactionData(factionID, out factionData))
                {
                    a         = factionData.name;
                    singleton = true;
                }
                break;

            case DFLocation.BuildingTypes.Temple:
                // Temples get name from faction data - always seem to be first child of factionID
                if (DaggerfallUnity.Instance.ContentReader.FactionFileReader.GetFactionData(factionID, out factionData))
                {
                    if (factionData.children.Count > 0)
                    {
                        FactionFile.FactionData firstChild;
                        if (DaggerfallUnity.Instance.ContentReader.FactionFileReader.GetFactionData(factionData.children[0], out firstChild))
                        {
                            a         = firstChild.name;
                            singleton = true;
                        }
                    }
                }
                break;

            case DFLocation.BuildingTypes.Palace:
                // Main palace names come from TEXT.RSC (e.g. "Castle Daggerfall")
                // Other palaces are just named "Palace" (still need to confirm behaviour)
                int textId = 0;
                if (locationName == "Daggerfall")
                {
                    textId = 475;
                }
                else if (locationName == "Wayrest")
                {
                    textId = 476;
                }
                else if (locationName == "Sentinel")
                {
                    textId = 477;
                }

                if (textId > 0)
                {
                    TextFile.Token[] nameTokens = DaggerfallUnity.Instance.TextProvider.GetRSCTokens(textId);
                    foreach (TextFile.Token token in nameTokens)
                    {
                        if (token.formatting == TextFile.Formatting.Text)
                        {
                            a = token.text;
                            break;
                        }
                    }
                    a = a.TrimEnd('.');     // remove character '.' from castle text record entry if it is last character
                }
                else
                {
                    a = "Palace";
                }
                singleton = true;
                break;

            default:
                // Do nothing for unknown/unsupported building type
                // Houses can actually change names based on active quests
                return(string.Empty);
            }

            // Replace %cn
            a = a.Replace(cityNameTitleVar, locationName);

            // Replace %ef
            if (a.Contains(firstNameTitleVar))
            {
                // Need to burn a rand() for %ef roll to be correct
                // What is Daggerfall rolling here?
                DFRandom.rand();

                // Observation finds nameplates only seem to use male Breton namebank
                string firstName = DaggerfallUnity.Instance.NameHelper.FirstName(NameHelper.BankTypes.Breton, Game.Entity.Genders.Male);
                a = a.Replace(firstNameTitleVar, firstName);
            }

            // Replace %rt based on faction ruler
            if (a.Contains(royalTitleVar))
            {
                a = a.Replace(royalTitleVar, MacroHelper.RegentTitle(null));
            }

            // Final text is "{a} {b}" for two-part names or just "{a}" for singleton names
            if (!singleton)
            {
                result = string.Format("{0} {1}", a, b);
            }
            else
            {
                result = a;
            }

            return(result);
        }
 public override string PaintingSubject()
 {                    // %sub
     DFRandom.rand(); // Classic uses every other value.
     TextFile.Token[] tokens = DaggerfallUnity.Instance.TextProvider.GetRandomTokens(paintingSub, true);
     return((tokens.Length > 0) ? tokens[0].text : "%sub[idxError]");
 }
Ejemplo n.º 20
0
        void Update()
        {
            // Change sound presets
            if (Presets != lastPresets)
            {
                // Clear settings
                lastPresets  = Presets;
                rainLoop     = null;
                cricketsLoop = null;

                // Stop playing any loops
                if (loopAudioSource.isPlaying)
                {
                    loopAudioSource.Stop();
                    loopAudioSource.clip = null;
                    loopAudioSource.loop = false;
                }

                ApplyPresets();
                StartWaiting();
            }

            // Start rain loop if not running
            if ((Presets == AmbientSoundPresets.Rain || Presets == AmbientSoundPresets.Storm) && rainLoop == null)
            {
                rainLoop = PlayLoop(SoundClips.AmbientRaining, 1f);
            }

            // Start crickets loop if not running
            if ((Presets == AmbientSoundPresets.ClearNight) && cricketsLoop == null)
            {
                cricketsLoop = PlayLoop(SoundClips.AmbientCrickets, 1f);
            }

            // Tick counters
            waitCounter      += Time.deltaTime;
            waterWaitCounter += Time.deltaTime;
            if (waitCounter > waitTime)
            {
                PlayEffects();
                StartWaiting();
            }

            // Play water sound effects. Timing based on classic.
            if (waterWaitCounter > GameManager.classicUpdateInterval)
            {
                if (playerEnterExit && playerEnterExit.blockWaterLevel != 10000)
                {
                    // Chance to play gentle water sound at water surface
                    if (DFRandom.rand() < 50)
                    {
                        Vector3 waterSoundPosition = playerBehaviour.transform.position;
                        waterSoundPosition.y  = playerEnterExit.blockWaterLevel * -1 * MeshReader.GlobalScale;
                        waterSoundPosition.x += Random.Range(-3f, 3f);
                        waterSoundPosition.z += Random.Range(-3f, 3f);
                        SpatializedPlayOneShot(SoundClips.WaterGentle, waterSoundPosition, 1f);
                    }

                    // Chance to play water bubbles sound if player is underwater
                    if (playerEnterExit.IsPlayerSubmerged && DFRandom.rand() < 100)
                    {
                        AmbientPlayOneShot(SoundClips.AmbientWaterBubbles, 1f);
                    }
                }
                waterWaitCounter = 0;
            }
        }
Ejemplo n.º 21
0
        void SelectCurrentSong()
        {
            if (currentPlaylist == null || currentPlaylist.Length == 0)
            {
                return;
            }

            int index = 0;

            // General MIDI song selection
            {
                uint gameMinutes = DaggerfallUnity.Instance.WorldTime.DaggerfallDateTime.ToClassicDaggerfallTime();
                DFRandom.srand(gameMinutes / 1440);
                uint random = DFRandom.rand();
                if (currentPlaylist == NightSongs)
                {
                    index = (int)(random % NightSongs.Length);
                }
                else if (currentPlaylist == SunnySongs)
                {
                    index = (int)(random % SunnySongs.Length);
                }
                else if (currentPlaylist == CloudySongs)
                {
                    index = (int)(random % CloudySongs.Length);
                }
                else if (currentPlaylist == OvercastSongs)
                {
                    index = (int)(random % OvercastSongs.Length);
                }
                else if (currentPlaylist == RainSongs)
                {
                    index = (int)(random % RainSongs.Length);
                }
                else if (currentPlaylist == SnowSongs)
                {
                    index = (int)(random % SnowSongs.Length);
                }
                else if (currentPlaylist == TempleSongs && playerEnterExit)
                {
                    byte[] templeFactions             = { 0x52, 0x54, 0x58, 0x5C, 0x5E, 0x62, 0x6A, 0x24 };
                    uint   factionOfPlayerEnvironment = playerEnterExit.FactionID;
                    index = Array.IndexOf(templeFactions, (byte)factionOfPlayerEnvironment);
                    if (index < 0)
                    {
                        byte[] godFactions = { 0x15, 0x16, 0x18, 0x1A, 0x1B, 0x1D, 0x21, 0x23 };
                        index = Array.IndexOf(godFactions, (byte)factionOfPlayerEnvironment);
                    }
                }
                else if (currentPlaylist == TavernSongs)
                {
                    index = (int)(gameMinutes / 1440 % TavernSongs.Length);
                }
                else if (currentPlaylist == DungeonInteriorSongs)
                {
                    PlayerGPS gps      = GameManager.Instance.PlayerGPS;
                    ushort    unknown2 = 0;
                    int       region   = 0;
                    if (gps.HasCurrentLocation)
                    {
                        unknown2 = (ushort)gps.CurrentLocation.Dungeon.RecordElement.Header.Unknown2;
                        region   = gps.CurrentRegionIndex;
                    }
                    DFRandom.srand(unknown2 ^ ((byte)region << 8));
                    random = DFRandom.rand();
                    index  = (int)(random % 15);
                }
                else if (currentPlaylist == SneakingSongs)
                {
                    index = UnityEngine.Random.Range(0, SneakingSongs.Length);
                }
            }
            currentSong      = currentPlaylist[index];
            currentSongIndex = index;
        }
        void Update()
        {
            // Change sound presets
            if (Presets != lastPresets)
            {
                // Clear settings
                lastPresets  = Presets;
                rainLoop     = null;
                cricketsLoop = null;

                // Stop playing any loops
                if (dfAudioSource.AudioSource.isPlaying)
                {
                    dfAudioSource.AudioSource.Stop();
                    dfAudioSource.AudioSource.clip = null;
                    dfAudioSource.AudioSource.loop = false;
                }

                ApplyPresets();
                StartWaiting();
            }

            // Start rain loop if not running
            if ((Presets == AmbientSoundPresets.Rain || Presets == AmbientSoundPresets.Storm) && rainLoop == null)
            {
                rainLoop = dfAudioSource.GetAudioClip((int)SoundClips.AmbientRaining);
                dfAudioSource.AudioSource.clip         = rainLoop;
                dfAudioSource.AudioSource.loop         = true;
                dfAudioSource.AudioSource.spatialBlend = 0;
                dfAudioSource.AudioSource.Play();
            }

            // Start crickets loop if not running
            if ((Presets == AmbientSoundPresets.ClearNight) && cricketsLoop == null)
            {
                cricketsLoop = dfAudioSource.GetAudioClip((int)SoundClips.AmbientCrickets);
                dfAudioSource.AudioSource.clip         = cricketsLoop;
                dfAudioSource.AudioSource.loop         = true;
                dfAudioSource.AudioSource.spatialBlend = 0;
                dfAudioSource.AudioSource.Play();
            }

            // Tick counters
            waitCounter      += Time.deltaTime;
            waterWaitCounter += Time.deltaTime;
            if (waitCounter > waitTime)
            {
                PlayEffects();
                StartWaiting();
            }

            // Play water sound effects. Timing and position based on classic behavior.
            // TODO: Make sound follow player's X and Z movement but play from Y coordinate of dungeon water, for a more dynamically 3d sound.
            // Currently the sound is a volume adjustment based on vertical distance from the water.
            if (waterWaitCounter > waterSoundWaitTime)
            {
                if (playerEnterExit == null)
                {
                    playerEnterExit = GameManager.Instance.PlayerEnterExit;
                }
                if (playerEnterExit)
                {
                    if (playerEnterExit.blockWaterLevel != 10000)
                    {
                        Entity.DaggerfallEntityBehaviour player = GameManager.Instance.PlayerEntityBehaviour;
                        // Chance to play gentle water sound based on vertical distance between player and water surface
                        if (DFRandom.rand() < 50)
                        {
                            float waterHeightInDFUnityUnits = (playerEnterExit.blockWaterLevel * -1 * MeshReader.GlobalScale);
                            float volumeScale = Mathf.Clamp(1 - (Mathf.Abs(player.transform.position.y - waterHeightInDFUnityUnits) / 9), 0, 1);
                            dfAudioSource.PlayOneShot((int)SoundClips.WaterGentle, 0, volumeScale);
                        }

                        // Chance to play underwater bubbling noise if player is underwater
                        if (playerEnterExit.IsPlayerSubmerged)
                        {
                            if (DFRandom.rand() < 100)
                            {
                                dfAudioSource.PlayOneShot((int)SoundClips.AmbientWaterBubbles, 0);
                            }
                        }
                    }
                }
                waterWaitCounter = 0;
            }
        }
Ejemplo n.º 23
0
        void ParseFactions(string txt)
        {
            // Unmodded faction.txt contains multiples of same id
            // This resolver counter is used to give a faction a unique id if needed
            int resolverId = 1000;

            // Clear existing dictionary
            factionDict.Clear();

            // First pass reads each faction text block in order
            List <string[]> factionBlocks = new List <string[]>();

            using (StringReader reader = new StringReader(txt))
            {
                List <string> currentblock = new List <string>();
                while (true)
                {
                    // Handle end of file
                    string line = reader.ReadLine();
                    if (line == null)
                    {
                        // Store final block
                        if (currentblock.Count > 0)
                        {
                            factionBlocks.Add(currentblock.ToArray());
                        }

                        break;
                    }

                    // Ignore comment lines and empty lines
                    if (line.StartsWith(";") || string.IsNullOrEmpty(line))
                    {
                        continue;
                    }

                    // All factions blocks start with a '#' character
                    if (line.Contains("#"))
                    {
                        // Store current block
                        if (currentblock.Count > 0)
                        {
                            factionBlocks.Add(currentblock.ToArray());
                        }

                        // Start new block
                        currentblock.Clear();
                    }

                    // Add line to current faction block
                    currentblock.Add(line);
                }
            }

            // Second pass parses the text block into FactionData
            int         lastPrecedingTabs = 0;
            FactionData previousFaction   = new FactionData();
            Stack <int> parentStack       = new Stack <int>();

            for (int i = 0; i < factionBlocks.Count; i++)
            {
                // Start a new faction
                FactionData faction = new FactionData();
                string[]    block   = factionBlocks[i];

                // Parent child relationship determined by preceding tabs
                int precedingTabs = CountPrecedingTabs(block[0]);
                if (precedingTabs > lastPrecedingTabs)
                {
                    parentStack.Push(previousFaction.id);
                }
                else if (precedingTabs < lastPrecedingTabs)
                {
                    while (parentStack.Count > precedingTabs)
                    {
                        parentStack.Pop();
                    }
                }
                lastPrecedingTabs = precedingTabs;

                // Set parent from top of stack
                if (parentStack.Count > 0)
                {
                    faction.parent = parentStack.Peek();
                }

                // Parse faction block
                ParseFactionData(ref block, ref faction);

                // Store faction just read
                if (!factionDict.ContainsKey(faction.id))
                {
                    factionDict.Add(faction.id, faction);
                }
                else
                {
                    // Duplicate id detected
                    faction.id = resolverId++;
                    factionDict.Add(faction.id, faction);
                }

                // Key faction name to faction id
                if (!factionNameToIDDict.ContainsKey(faction.name))
                {
                    factionNameToIDDict.Add(faction.name, faction.id);
                }
                else
                {
                    // Just ignoring duplicates for now
                    // Currently only using name to id lookup for to find region faction quickly
                    //UnityEngine.Debug.LogWarningFormat("Duplicate name detected " + faction.name);
                }

                // Calculate ruler name seed and ruler bonus in same manner as classic. These are not read from FACTION.TXT.
                uint random = DFRandom.rand() << 16;
                faction.rulerNameSeed   = DFRandom.rand() | random;
                faction.rulerPowerBonus = DFRandom.random_range_inclusive(0, 50) + 20;

                previousFaction = faction;
            }
        }
        public override void Update()
        {
            base.Update();

            // Close immediately if no crime assigned to player
            if (playerEntity.CrimeCommitted == Entity.PlayerEntity.Crimes.None)
            {
                CloseWindow();
                return;
            }

            if (state == 0) // Starting
            {
                regionIndex = GameManager.Instance.PlayerGPS.CurrentRegionIndex;

                int crimeType = (int)playerEntity.CrimeCommitted - 1;
                int legalRep  = playerEntity.RegionData[regionIndex].LegalRep;

                // Get whether punishment is normal (fine/prison) or a severe punishment (banishment/execution)
                int threshold1 = 0;
                int threshold2 = 0;

                if (legalRep < 0)
                {
                    threshold1 = -legalRep;
                    if (threshold1 > 75)
                    {
                        threshold1 = 75;
                    }
                    threshold2 = -legalRep / 2;
                    if (threshold2 > 75)
                    {
                        threshold2 = 75;
                    }
                }
                if (Dice100.FailedRoll(threshold2) &&
                    Dice100.FailedRoll(threshold1))
                {
                    punishmentType = 2; // fine/prison
                }
                else
                {
                    punishmentType = 0; // banishment or execution
                }
                // Calculate penalty amount
                int penaltyAmount = 0;

                if (legalRep >= 0)
                {
                    penaltyAmount = PenaltyPerLegalRepPoint[crimeType] * legalRep + BasePenaltyAmounts[crimeType];
                }
                else
                {
                    penaltyAmount = BasePenaltyAmounts[crimeType] - PenaltyPerLegalRepPoint[crimeType] * legalRep;
                }

                if (penaltyAmount > MaximumPenaltyAmounts[crimeType])
                {
                    penaltyAmount = MaximumPenaltyAmounts[crimeType];
                }
                else if (penaltyAmount < MinimumPenaltyAmounts[crimeType])
                {
                    penaltyAmount = MinimumPenaltyAmounts[crimeType];
                }

                penaltyAmount /= 40;

                // Calculate days of prison and fine
                daysInPrison = 0;
                fine         = 0;

                for (int i = 0; i < penaltyAmount; i++)
                {
                    if ((DFRandom.rand() & 1) != 0)
                    {
                        fine += 40;
                    }
                    else
                    {
                        daysInPrison += 3;
                    }
                }

                // If player can't pay fine, limit fine and add to prison sentence
                int playerGold = playerEntity.GetGoldAmount();
                if (playerGold < fine)
                {
                    daysInPrison += (fine - playerGold) / 40;
                    fine          = playerGold;
                }

                DaggerfallMessageBox messageBox;
                if (crimeType == 4 || crimeType == 3) // Assault or murder
                {
                    // If player is a member of the Dark Brotherhood, they may be rescued for a violent crime
                    Guilds.IGuild guild = GameManager.Instance.GuildManager.GetGuild((int)FactionFile.FactionIDs.The_Dark_Brotherhood);
                    if (guild.IsMember())
                    {
                        if (guild.Rank >= UnityEngine.Random.Range(0, 20))
                        {
                            messageBox = new DaggerfallMessageBox(uiManager, this, false, 149);
                            messageBox.SetTextTokens(DaggerfallUnity.Instance.TextProvider.GetRSCTokens(courtTextDB));
                            messageBox.ScreenDimColor = new Color32(0, 0, 0, 0);
                            messageBox.ParentPanel.VerticalAlignment = VerticalAlignment.Bottom;
                            messageBox.ClickAnywhereToClose          = true;
                            uiManager.PushWindow(messageBox);
                            playerEntity.FillVitalSigns();
                            playerEntity.RaiseReputationForDoingSentence();
                            state = 100;
                            return;
                        }
                    }
                }
                if (crimeType <= 2 || crimeType == 11) // Attempted breaking and entering, trespassing, breaking and entering, pickpocketing
                {
                    // If player is a member of the Thieves Guild, they may be rescued for a thieving crime
                    Guilds.IGuild guild = GameManager.Instance.GuildManager.GetGuild((int)FactionFile.FactionIDs.The_Thieves_Guild);
                    if (guild.IsMember())
                    {
                        if (guild.Rank >= UnityEngine.Random.Range(0, 20))
                        {
                            messageBox = new DaggerfallMessageBox(uiManager, this, false, 149);
                            messageBox.SetTextTokens(DaggerfallUnity.Instance.TextProvider.GetRSCTokens(courtTextTG));
                            messageBox.ScreenDimColor = new Color32(0, 0, 0, 0);
                            messageBox.ParentPanel.VerticalAlignment = VerticalAlignment.Bottom;
                            messageBox.ClickAnywhereToClose          = true;
                            messageBox.AllowCancel = false;
                            uiManager.PushWindow(messageBox);
                            playerEntity.FillVitalSigns();
                            playerEntity.RaiseReputationForDoingSentence();
                            state = 100;
                            return;
                        }
                    }
                }

                messageBox = new DaggerfallMessageBox(uiManager, this, false, 105);
                messageBox.SetTextTokens(DaggerfallUnity.Instance.TextProvider.GetRSCTokens(courtTextStart));
                messageBox.ScreenDimColor = new Color32(0, 0, 0, 0);
                messageBox.AddButton(DaggerfallMessageBox.MessageBoxButtons.Guilty);
                messageBox.AddButton(DaggerfallMessageBox.MessageBoxButtons.NotGuilty);
                messageBox.OnButtonClick += GuiltyNotGuilty_OnButtonClick;
                messageBox.ParentPanel.VerticalAlignment = VerticalAlignment.Bottom;
                messageBox.AllowCancel = false;
                uiManager.PushWindow(messageBox);
                state = 1;       // Done with initial message
            }
            else if (state == 2) // Found guilty
            {
                DaggerfallMessageBox messageBox = new DaggerfallMessageBox(uiManager, this, false, 149);
                messageBox.SetTextTokens(DaggerfallUnity.Instance.TextProvider.GetRSCTokens(courtTextFoundGuilty));
                messageBox.ScreenDimColor = new Color32(0, 0, 0, 0);
                messageBox.ParentPanel.VerticalAlignment = VerticalAlignment.Bottom;
                messageBox.ClickAnywhereToClose          = true;
                messageBox.AllowCancel = false;
                uiManager.PushWindow(messageBox);

                if (daysInPrison > 0)
                {
                    state = 3;
                }
                else
                {
                    // Give the reputation raise here if no prison time will be served.
                    playerEntity.RaiseReputationForDoingSentence();
                    repositionPlayer = true;
                    playerEntity.FillVitalSigns();
                    ReleaseFromPrison();
                    state = 100;
                }
            }
            else if (state == 3) // Serve prison sentence
            {
                playerEntity.InPrison = true;
                SwitchToPrisonScreen();
                daysInPrisonLeft = daysInPrison;
                playerEntity.RaiseReputationForDoingSentence();
                repositionPlayer = true;
                state            = 100;
            }
            else if (state == 4) // Banished
            {
                DaggerfallMessageBox messageBox = new DaggerfallMessageBox(uiManager, this, false, 149);
                messageBox.SetTextTokens(DaggerfallUnity.Instance.TextProvider.GetRSCTokens(courtTextBanished));
                messageBox.ScreenDimColor = new Color32(0, 0, 0, 0);
                messageBox.ParentPanel.VerticalAlignment = VerticalAlignment.Bottom;
                messageBox.ClickAnywhereToClose          = true;
                messageBox.AllowCancel = false;
                uiManager.PushWindow(messageBox);
                playerEntity.RegionData[regionIndex].SeverePunishmentFlags |= 1;
                repositionPlayer = true;

                // Refill player vitals after banishment, otherwise player left with 1HP outside city gates
                playerEntity.FillVitalSigns();
                state = 100;
            }
            // Note: Seems like an execution sentence can't be given in classic. It can't be given here, either.
            else if (state == 5) // Execution
            {
                DaggerfallMessageBox messageBox = new DaggerfallMessageBox(uiManager, this, false, 149);
                messageBox.SetTextTokens(DaggerfallUnity.Instance.TextProvider.GetRSCTokens(courtTextExecuted));
                messageBox.ScreenDimColor = new Color32(0, 0, 0, 0);
                messageBox.ParentPanel.VerticalAlignment = VerticalAlignment.Bottom;
                messageBox.ClickAnywhereToClose          = true;
                messageBox.AllowCancel = false;
                uiManager.PushWindow(messageBox);
                playerEntity.RegionData[regionIndex].SeverePunishmentFlags |= 2;
                state = 6;
            }
            else if (state == 6) // Reposition player at entrance
            {
                repositionPlayer = true;
                state            = 100;
            }
            else if (state == 100) // Done
            {
                if (playerEntity.InPrison)
                {
                    if (Input.GetKey(exitKey)) // Speed up prison day countdown. Not in classic.
                    {
                        prisonUpdateInterval = 0.001f;
                    }
                    else
                    {
                        prisonUpdateInterval = 0.3f;
                    }

                    if (prisonUpdateTimer == 0)
                    {
                        prisonUpdateTimer = Time.realtimeSinceStartup;
                    }

                    if (Time.realtimeSinceStartup < prisonUpdateTimer + prisonUpdateInterval)
                    {
                        return;
                    }
                    else
                    {
                        prisonUpdateTimer = Time.realtimeSinceStartup;
                        UpdatePrisonScreen();
                    }
                }
                else
                {
                    ReleaseFromPrison();
                }
            }
        }
Ejemplo n.º 25
0
        private void Move()
        {
            // Cancel movement and animations if paralyzed, but still allow gravity to take effect
            // This will have the (intentional for now) side-effect of making paralyzed flying enemies fall out of the air
            // Paralyzed swimming enemies will just freeze in place
            // Freezing anims also prevents the attack from triggering until paralysis cleared
            if (entityBehaviour.Entity.IsParalyzed)
            {
                mobile.FreezeAnims = true;

                if (swims)
                {
                    controller.Move(Vector3.zero);
                }
                else
                {
                    controller.SimpleMove(Vector3.zero);
                }

                return;
            }
            else
            {
                mobile.FreezeAnims = false;
            }

            // If hit, get knocked back
            if (knockBackSpeed > 0)
            {
                // Limit knockBackSpeed. This can be higher than what is actually used for the speed of motion,
                // making it last longer and do more damage if the enemy collides with something.
                if (knockBackSpeed > (40 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10)))
                {
                    knockBackSpeed = (40 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10));
                }

                if (knockBackSpeed > (5 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10)) &&
                    mobile.Summary.EnemyState != MobileStates.PrimaryAttack)
                {
                    mobile.ChangeEnemyState(MobileStates.Hurt);
                }

                // Actual speed of motion is limited
                Vector3 motion;
                if (knockBackSpeed <= (25 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10)))
                {
                    motion = knockBackDirection * knockBackSpeed;
                }
                else
                {
                    motion = knockBackDirection * (25 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10));
                }

                if (swims)
                {
                    WaterMove(motion);
                }
                else if (flies || isLevitating)
                {
                    controller.Move(motion * Time.deltaTime);
                }
                else
                {
                    controller.SimpleMove(motion);
                }

                if (classicUpdate)
                {
                    knockBackSpeed -= (5 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10));
                    if (knockBackSpeed <= (5 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10)) &&
                        mobile.Summary.EnemyState != MobileStates.PrimaryAttack)
                    {
                        mobile.ChangeEnemyState(MobileStates.Move);
                    }
                }

                return;
            }

            // Monster speed of movement follows the same formula as for when the player walks
            EnemyEntity entity    = entityBehaviour.Entity as EnemyEntity;
            float       moveSpeed = ((entity.Stats.LiveSpeed + PlayerSpeedChanger.dfWalkBase) / PlayerSpeedChanger.classicToUnitySpeedUnitRatio);

            // Reduced speed if playing a one-shot animation
            if (mobile.IsPlayingOneShot())
            {
                moveSpeed /= AttackSpeedDivisor;
            }

            // As long as the target is detected,
            // giveUpTimer is reset to full
            if (senses.DetectedTarget)
            {
                giveUpTimer = 200;
            }

            // GiveUpTimer value is from classic, so decrease at the speed of classic's update loop
            if (!senses.DetectedTarget &&
                giveUpTimer > 0 && classicUpdate)
            {
                giveUpTimer--;
            }

            // Enemy will keep moving towards last known target position
            targetPos = senses.LastKnownTargetPos;

            // Remain idle after finishing any attacks if no target or after giving up finding the target
            if (entityBehaviour.Target == null || giveUpTimer == 0 || targetPos == EnemySenses.ResetPlayerPos)
            {
                if (!mobile.IsPlayingOneShot())
                {
                    mobile.ChangeEnemyState(MobileStates.Idle);
                }

                return;
            }

            // Flying enemies and slaughterfish aim for target face
            if (flies || isLevitating || (swims && mobile.Summary.Enemy.ID == (int)MonsterCareers.Slaughterfish))
            {
                targetPos.y += 0.9f;
            }
            else
            {
                // Ground enemies target at their own height
                // This avoids short enemies from stepping on each other as they approach the target
                // Otherwise, their target vector aims up towards the target
                var playerController = GameManager.Instance.PlayerController;
                var deltaHeight      = (playerController.height - controller.height) / 2;
                targetPos.y -= deltaHeight;
            }

            // Get direction & distance.
            var   direction = targetPos - transform.position;
            float distance  = direction.magnitude;

            // If attacking, randomly follow target with attack.
            if (mobile.Summary.EnemyState == MobileStates.PrimaryAttack)
            {
                if (!isAttackFollowsPlayerSet)
                {
                    attackFollowsPlayer      = (Random.Range(0f, 1f) > 0.5f);
                    isAttackFollowsPlayerSet = true;
                }
            }
            else
            {
                isAttackFollowsPlayerSet = false;
            }

            if (attackFollowsPlayer)
            {
                transform.forward = direction.normalized;
            }

            // Bow attack for enemies that have the appropriate animation
            if (senses.TargetInSight && 360 * MeshReader.GlobalScale < distance && distance < 2048 * MeshReader.GlobalScale)
            {
                if (senses.TargetIsWithinYawAngle(22.5f))
                {
                    if (mobile.Summary.Enemy.HasRangedAttack1 && mobile.Summary.Enemy.ID > 129 && mobile.Summary.Enemy.ID != 132)
                    {
                        // Random chance to shoot bow
                        if (classicUpdate && DFRandom.rand() < 1000)
                        {
                            if (mobile.Summary.Enemy.HasRangedAttack1 && !mobile.Summary.Enemy.HasRangedAttack2 &&
                                mobile.Summary.EnemyState != MobileStates.RangedAttack1)
                            {
                                mobile.ChangeEnemyState(MobileStates.RangedAttack1);
                            }
                            else if (mobile.Summary.Enemy.HasRangedAttack2 && mobile.Summary.EnemyState != MobileStates.RangedAttack2)
                            {
                                mobile.ChangeEnemyState(MobileStates.RangedAttack2);
                            }
                        }
                        // Otherwise hold ground
                        else if (!mobile.IsPlayingOneShot())
                        {
                            mobile.ChangeEnemyState(MobileStates.Idle);
                        }
                    }
                    //else if (spellPoints > 0 && canCastRangeSpells && DFRandom.rand() % 40 == 0) TODO: Ranged spell shooting
                    //          CastRangedSpell();
                    //          Spell Cast Animation;
                    else
                    {
                        // If no ranged attack, move towards target
                        PursueTarget(direction, moveSpeed);
                    }
                }
                else
                {
                    if (!mobile.IsPlayingOneShot())
                    {
                        mobile.ChangeEnemyState(MobileStates.Move);
                    }
                    TurnToTarget(direction.normalized);
                    return;
                }
            }
            // Move towards target
            else if (distance > stopDistance)
            {
                PursueTarget(direction, moveSpeed);
            }
            else if (!senses.TargetIsWithinYawAngle(22.5f))
            {
                TurnToTarget(direction.normalized);
            }
            //else
            //{
            // TODO: Touch spells.
            //if (hasSpellPoints && attackCoolDownFinished && CanCastTouchSpells)
            //{
            //    Cast Touch Spell
            //    Spell Cast Animation
            //}
            //}
            else if (!senses.DetectedTarget && mobile.Summary.EnemyState == MobileStates.Move)
            {
                mobile.ChangeEnemyState(MobileStates.Idle);
            }
        }
Ejemplo n.º 26
0
        private void Move()
        {
            // If hit, get knocked back
            if (knockBackSpeed > 0)
            {
                // Limit knockBackSpeed. This can be higher than what is actually used for the speed of motion,
                // making it last longer and do more damage if the enemy collides with something.
                if (knockBackSpeed > (40 / (PlayerMotor.classicToUnitySpeedUnitRatio / 10)))
                {
                    knockBackSpeed = (40 / (PlayerMotor.classicToUnitySpeedUnitRatio / 10));
                }

                if (knockBackSpeed > (5 / (PlayerMotor.classicToUnitySpeedUnitRatio / 10)) &&
                    mobile.Summary.EnemyState != MobileStates.PrimaryAttack)
                {
                    mobile.ChangeEnemyState(MobileStates.Hurt);
                }

                // Actual speed of motion is limited
                Vector3 motion;
                if (knockBackSpeed <= (25 / (PlayerMotor.classicToUnitySpeedUnitRatio / 10)))
                {
                    motion = knockBackDirection * knockBackSpeed;
                }
                else
                {
                    motion = knockBackDirection * (25 / (PlayerMotor.classicToUnitySpeedUnitRatio / 10));
                }

                if (swims)
                {
                    WaterMove(motion);
                }
                else if (flies)
                {
                    controller.Move(motion * Time.deltaTime);
                }
                else
                {
                    controller.SimpleMove(motion);
                }

                if (classicUpdate)
                {
                    knockBackSpeed -= (5 / (PlayerMotor.classicToUnitySpeedUnitRatio / 10));
                    if (knockBackSpeed <= (5 / (PlayerMotor.classicToUnitySpeedUnitRatio / 10)) &&
                        mobile.Summary.EnemyState != MobileStates.PrimaryAttack)
                    {
                        mobile.ChangeEnemyState(MobileStates.Move);
                    }
                }

                return;
            }

            // Monster speed of movement follows the same formula as for when the player walks
            EnemyEntity entity    = entityBehaviour.Entity as EnemyEntity;
            float       moveSpeed = ((entity.Stats.LiveSpeed + PlayerMotor.dfWalkBase) / PlayerMotor.classicToUnitySpeedUnitRatio);

            // Reduced speed if playing a one-shot animation
            if (mobile.IsPlayingOneShot())
            {
                moveSpeed /= AttackSpeedDivisor;
            }

            // Remain idle when not hostile
            if (!isHostile)
            {
                mobile.ChangeEnemyState(MobileStates.Idle);
                return;
            }
            // If hostile but the enemy doesn't see the player, run the stealth check
            else if (senses.LastKnownPlayerPos == EnemySenses.ResetPlayerPos)
            {
                if (senses.StealthCheck())
                {
                    // Enemy noticed the player
                    senses.LastKnownPlayerPos = GameManager.Instance.PlayerObject.transform.position;
                    senses.DetectedPlayer     = true;
                }
                else
                {
                    // Enemy didn't notice the player
                    mobile.ChangeEnemyState(MobileStates.Idle);
                    senses.DetectedPlayer = false;
                    return;
                }
            }

            // As long as the player is directly seen/heard,
            // giveUpTimer is reset to full
            if (senses.PlayerInSight || senses.PlayerInEarshot)
            {
                giveUpTimer = 200;
            }
            else if (giveUpTimer == 0)
            {
                // Player lost for too long, or wasn't in sight/earshot to begin with. Time to give up
                senses.LastKnownPlayerPos = EnemySenses.ResetPlayerPos;
                return;
            }

            // GiveUpTimer value is from classic, so decrease at the speed of classic's update loop
            if (!senses.PlayerInSight && !senses.PlayerInEarshot &&
                giveUpTimer > 0 && classicUpdate)
            {
                giveUpTimer--;
            }

            // Enemy will keep moving towards last known player position
            targetPos = senses.LastKnownPlayerPos;

            // Flying enemies and slaughterfish aim for player face
            if (flies || (swims && mobile.Summary.Enemy.ID == (int)MonsterCareers.Slaughterfish))
            {
                targetPos.y += 0.9f;
            }
            else
            {
                // Ground enemies target at their own height
                // This avoids short enemies from stepping on each other as they approach the player
                // Otherwise, their target vector aims up towards the player
                var playerController = GameManager.Instance.PlayerController;
                var deltaHeight      = (playerController.height - controller.height) / 2;
                targetPos.y -= deltaHeight;
            }

            // Get direction & distance.
            var   direction = targetPos - transform.position;
            float distance  = direction.magnitude;

            // If attacking, randomly follow player with attack.
            if (mobile.Summary.EnemyState == MobileStates.PrimaryAttack)
            {
                if (!isAttackFollowsPlayerSet)
                {
                    attackFollowsPlayer      = (Random.Range(0f, 1f) > 0.5f);
                    isAttackFollowsPlayerSet = true;
                }
            }
            else
            {
                isAttackFollowsPlayerSet = false;
            }

            if (attackFollowsPlayer)
            {
                transform.forward = direction.normalized;
            }

            // Bow attack for enemies that have the appropriate animation
            if (senses.PlayerInSight && 360 * MeshReader.GlobalScale < distance && distance < 2048 * MeshReader.GlobalScale)
            {
                if (senses.TargetIsWithinYawAngle(22.5f))
                {
                    if (mobile.Summary.Enemy.HasRangedAttack1 && mobile.Summary.Enemy.ID > 129 && mobile.Summary.Enemy.ID != 132)
                    {
                        // Random chance to shoot bow
                        if (DFRandom.rand() < 1000)
                        {
                            if (mobile.Summary.Enemy.HasRangedAttack1 && !mobile.Summary.Enemy.HasRangedAttack2 &&
                                mobile.Summary.EnemyState != MobileStates.RangedAttack1)
                            {
                                mobile.ChangeEnemyState(MobileStates.RangedAttack1);
                            }
                            else if (mobile.Summary.Enemy.HasRangedAttack2 && mobile.Summary.EnemyState != MobileStates.RangedAttack2)
                            {
                                mobile.ChangeEnemyState(MobileStates.RangedAttack2);
                            }
                        }
                        // Otherwise hold ground
                        else if (!mobile.IsPlayingOneShot())
                        {
                            mobile.ChangeEnemyState(MobileStates.Idle);
                        }
                    }
                    //else if (spellPoints > 0 && canCastRangeSpells && DFRandom.rand() % 40 == 0) TODO: Ranged spell shooting
                    //          CastRangedSpell();
                    //          Spell Cast Animation;
                    else
                    {
                        // If no ranged attack, move towards target
                        PursueTarget(direction, moveSpeed);
                    }
                }
                else
                {
                    if (!mobile.IsPlayingOneShot())
                    {
                        mobile.ChangeEnemyState(MobileStates.Move);
                    }
                    TurnToTarget(direction.normalized);
                    return;
                }
            }
            // Move towards target
            else if (distance > stopDistance)
            {
                PursueTarget(direction, moveSpeed);
            }
            else if (!senses.TargetIsWithinYawAngle(22.5f))
            {
                TurnToTarget(direction.normalized);
            }
            //else
            //{
            // TODO: Touch spells.
            //if (hasSpellPoints && attackCoolDownFinished && CanCastTouchSpells)
            //{
            //    Cast Touch Spell
            //    Spell Cast Animation
            //}
            //}
            else if (!senses.PlayerInSight && !senses.PlayerInEarshot)
            {
                mobile.ChangeEnemyState(MobileStates.Idle);
            }
        }
Ejemplo n.º 27
0
        public static string GetName(int seed, DFLocation.BuildingTypes type, int factionID, string locationName, string regionName)
        {
            const string firstNameTitleVar = "%ef";
            const string cityNameTitleVar  = "%cn";
            const string royalTitleVar     = "%rt";

            string a = string.Empty, b = string.Empty;
            string result = string.Empty;

            bool singleton = false;

            FactionFile.FactionData factionData;
            DFRandom.srand(seed);
            switch (type)
            {
            case DFLocation.BuildingTypes.HouseForSale:
                return("House for sale");

            case DFLocation.BuildingTypes.Tavern:
                b = TavernsB[DFRandom.random_range(0, TavernsB.Length)];
                a = TavernsA[DFRandom.random_range(0, TavernsA.Length)];
                break;

            case DFLocation.BuildingTypes.GeneralStore:
                b = GeneralStoresB[DFRandom.random_range(0, GeneralStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.WeaponSmith:
                b = WeaponStoresB[DFRandom.random_range(0, WeaponStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.Armorer:
                b = ArmorStoresB[DFRandom.random_range(0, ArmorStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.Bookseller:
                b = BookStoresB[DFRandom.random_range(0, BookStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.ClothingStore:
                b = ClothingStoresB[DFRandom.random_range(0, ClothingStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.Alchemist:
                b = AlchemyStoresB[DFRandom.random_range(0, AlchemyStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.GemStore:
                b = GemStoresB[DFRandom.random_range(0, GemStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.PawnShop:
                b = PawnStoresB[DFRandom.random_range(0, PawnStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.FurnitureStore:
                b = FurnitureStoresB[DFRandom.random_range(0, FurnitureStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.Library:
                b = LibraryStoresB[DFRandom.random_range(0, LibraryStoresB.Length)];
                a = StoresA[DFRandom.random_range(0, StoresA.Length)];
                break;

            case DFLocation.BuildingTypes.Bank:
                // Banks always appear to be named "The Bank of RegionName"
                b = regionName;
                a = "The Bank of";
                break;

            case DFLocation.BuildingTypes.GuildHall:
                // Guild halls get the name from faction data
                if (DaggerfallUnity.Instance.ContentReader.FactionFileReader.GetFactionData(factionID, out factionData))
                {
                    a         = factionData.name;
                    singleton = true;
                }
                break;

            case DFLocation.BuildingTypes.Temple:
                // Temples get name from faction data - always seem to be first child of factionID
                if (DaggerfallUnity.Instance.ContentReader.FactionFileReader.GetFactionData(factionID, out factionData))
                {
                    if (factionData.children.Count > 0)
                    {
                        FactionFile.FactionData firstChild;
                        if (DaggerfallUnity.Instance.ContentReader.FactionFileReader.GetFactionData(factionData.children[0], out firstChild))
                        {
                            a         = firstChild.name;
                            singleton = true;
                        }
                    }
                }
                break;

            case DFLocation.BuildingTypes.Palace:
                // Main palace names (e.g. "Castle Daggerfall" appear to be hardcoded in FALL.EXE
                // Other palaces are just named "Palace"
                // Need to confirm behaviour before implementing
                // Just calling everything "Palace" for now.
                a         = "Palace";
                singleton = true;
                break;

            default:
                // Do nothing for unknown/unsupported building type
                // Houses can actually change names based on active quests
                return(string.Empty);
            }

            // Replace %cn
            a = a.Replace(cityNameTitleVar, locationName);

            // Replace %ef
            if (a.Contains(firstNameTitleVar))
            {
                // Need to burn a rand() for %ef roll to be correct
                // What is Daggerfall rolling here?
                DFRandom.rand();

                // Observation finds nameplates only seem to use male Breton namebank
                string firstName = DaggerfallUnity.Instance.NameHelper.FirstName(NameHelper.BankTypes.Breton, Game.Entity.Genders.Male);
                a = a.Replace(firstNameTitleVar, firstName);
            }

            // Replace %rt based on faction ruler
            if (a.Contains(royalTitleVar))
            {
                // Get factionID of this region
                FactionFile factionFile       = DaggerfallUnity.Instance.ContentReader.FactionFileReader;
                int         regionalFactionID = factionFile.GetFactionID(regionName);
                if (regionalFactionID != -1)
                {
                    // Get faction data
                    if (factionFile.GetFactionData(factionID, out factionData))
                    {
                        // Get ruler title for this region
                        string royalTile = RoyalTitles[factionData.ruler];
                        a = a.Replace(royalTitleVar, royalTile);
                    }
                }
            }

            // Final text is "{a} {b}" for two-part names or just "{a}" for singleton names
            if (!singleton)
            {
                result = string.Format("{0} {1}", a, b);
            }
            else
            {
                result = a;
            }

            return(result);
        }
Ejemplo n.º 28
0
        public override void Update()
        {
            base.Update();

            if (state == 0) // Starting
            {
                regionIndex = GameManager.Instance.PlayerGPS.CurrentRegionIndex;

                int crimeType = (int)playerEntity.CrimeCommitted - 1;
                int legalRep  = playerEntity.RegionData[regionIndex].LegalRep;

                // Get whether punishment is normal (fine/prison) or a severe punishment (banishment/execution)
                int threshold1 = 0;
                int threshold2 = 0;

                if (legalRep < 0)
                {
                    threshold1 = -legalRep;
                    if (threshold1 > 75)
                    {
                        threshold1 = 75;
                    }
                    threshold2 = -legalRep / 2;
                    if (threshold2 > 75)
                    {
                        threshold2 = 75;
                    }
                }
                if (UnityEngine.Random.Range(1, 101) > threshold2 &&
                    UnityEngine.Random.Range(1, 101) > threshold1)
                {
                    punishmentType = 2; // fine/prison
                }
                else
                {
                    punishmentType = 0; // banishment or execution
                }
                // Calculate penalty amount
                int penaltyAmount = 0;

                if (legalRep >= 0)
                {
                    penaltyAmount = PenaltyPerLegalRepPoint[crimeType] * legalRep
                                    + BasePenaltyAmounts[crimeType];
                }
                else
                {
                    penaltyAmount = BasePenaltyAmounts[crimeType]
                                    - PenaltyPerLegalRepPoint[crimeType] * legalRep;
                }

                if (penaltyAmount > MaximumPenaltyAmounts[crimeType])
                {
                    penaltyAmount = MaximumPenaltyAmounts[crimeType];
                }
                else if (penaltyAmount < MinimumPenaltyAmounts[crimeType])
                {
                    penaltyAmount = MinimumPenaltyAmounts[crimeType];
                }

                penaltyAmount /= 40;

                // Calculate days of prison and fine
                daysInPrison = 0;
                fine         = 0;

                for (int i = 0; i < penaltyAmount; i++)
                {
                    if ((DFRandom.rand() & 1) != 0)
                    {
                        fine += 40;
                    }
                    else
                    {
                        daysInPrison += 3;
                    }
                }

                // If player can't pay fine, limit fine and add to prison sentence
                int playerGold = playerEntity.GetGoldAmount();
                if (playerGold < fine)
                {
                    daysInPrison += (fine - playerGold) / 40;
                    fine          = playerGold;
                }

                DaggerfallMessageBox messageBox;
                if (crimeType == 4 || crimeType == 3)  // Assault or murder
                {
                    // If player is a member of the Dark Brotherhood, they may be rescued for a violent crime
                    Guilds.Guild guild = GameManager.Instance.GuildManager.GetGuild((int)FactionFile.FactionIDs.The_Dark_Brotherhood);
                    if (guild.IsMember())
                    {
                        if (guild.Rank >= UnityEngine.Random.Range(0, 20))
                        {
                            messageBox = new DaggerfallMessageBox(uiManager, this, false, 149);
                            messageBox.SetTextTokens(DaggerfallUnity.Instance.TextProvider.GetRSCTokens(courtTextDB));
                            messageBox.ScreenDimColor = new Color32(0, 0, 0, 0);
                            messageBox.ParentPanel.VerticalAlignment = VerticalAlignment.Bottom;
                            messageBox.ClickAnywhereToClose          = true;
                            uiManager.PushWindow(messageBox);
                            playerEntity.FillVitalSigns();
                            playerEntity.RaiseReputationForDoingSentence();
                            state = 100;
                            return;
                        }
                    }
                }
                if (crimeType <= 2 || crimeType == 11) // Attempted breaking and entering, trespassing, breaking and entering, pickpocketing
                {
                    // If player is a member of the Thieves Guild, they may be rescued for a thieving crime
                    Guilds.Guild guild = GameManager.Instance.GuildManager.GetGuild((int)FactionFile.FactionIDs.The_Thieves_Guild);
                    if (guild.IsMember())
                    {
                        if (guild.Rank >= UnityEngine.Random.Range(0, 20))
                        {
                            messageBox = new DaggerfallMessageBox(uiManager, this, false, 149);
                            messageBox.SetTextTokens(DaggerfallUnity.Instance.TextProvider.GetRSCTokens(courtTextTG));
                            messageBox.ScreenDimColor = new Color32(0, 0, 0, 0);
                            messageBox.ParentPanel.VerticalAlignment = VerticalAlignment.Bottom;
                            messageBox.ClickAnywhereToClose          = true;
                            uiManager.PushWindow(messageBox);
                            playerEntity.FillVitalSigns();
                            playerEntity.RaiseReputationForDoingSentence();
                            state = 100;
                            return;
                        }
                    }
                }

                messageBox = new DaggerfallMessageBox(uiManager, this, false, 105);
                messageBox.SetTextTokens(DaggerfallUnity.Instance.TextProvider.GetRSCTokens(courtTextStart));
                messageBox.ScreenDimColor = new Color32(0, 0, 0, 0);
                messageBox.AddButton(DaggerfallMessageBox.MessageBoxButtons.Guilty);
                messageBox.AddButton(DaggerfallMessageBox.MessageBoxButtons.NotGuilty);
                messageBox.OnButtonClick += GuiltyNotGuilty_OnButtonClick;
                messageBox.ParentPanel.VerticalAlignment = VerticalAlignment.Bottom;
                uiManager.PushWindow(messageBox);
                state = 1;       // Done with initial message
            }
            else if (state == 2) // Found guilty
            {
                DaggerfallMessageBox messageBox = new DaggerfallMessageBox(uiManager, this, false, 149);
                messageBox.SetTextTokens(DaggerfallUnity.Instance.TextProvider.GetRSCTokens(courtTextFoundGuilty));
                messageBox.ScreenDimColor = new Color32(0, 0, 0, 0);
                messageBox.ParentPanel.VerticalAlignment = VerticalAlignment.Bottom;
                messageBox.ClickAnywhereToClose          = true;
                uiManager.PushWindow(messageBox);
                state = 3;
            }
            else if (state == 3) // Serve prison sentence
            {
                PositionPlayerAtLocationEntrance();
                ServeTime(daysInPrison);
                playerEntity.RaiseReputationForDoingSentence();
                state = 100;
            }
            else if (state == 4) // Banished
            {
                DaggerfallMessageBox messageBox = new DaggerfallMessageBox(uiManager, this, false, 149);
                messageBox.SetTextTokens(DaggerfallUnity.Instance.TextProvider.GetRSCTokens(courtTextBanished));
                messageBox.ScreenDimColor = new Color32(0, 0, 0, 0);
                messageBox.ParentPanel.VerticalAlignment = VerticalAlignment.Bottom;
                messageBox.ClickAnywhereToClose          = true;
                uiManager.PushWindow(messageBox);
                playerEntity.RegionData[regionIndex].SeverePunishmentFlags |= 1;
                PositionPlayerAtLocationEntrance();
                state = 100;
            }
            // Note: Seems like an execution sentence can't be given in classic. It can't be given here, either.
            else if (state == 5) // Execution
            {
                DaggerfallMessageBox messageBox = new DaggerfallMessageBox(uiManager, this, false, 149);
                messageBox.SetTextTokens(DaggerfallUnity.Instance.TextProvider.GetRSCTokens(courtTextExecuted));
                messageBox.ScreenDimColor = new Color32(0, 0, 0, 0);
                messageBox.ParentPanel.VerticalAlignment = VerticalAlignment.Bottom;
                messageBox.ClickAnywhereToClose          = true;
                uiManager.PushWindow(messageBox);
                playerEntity.RegionData[regionIndex].SeverePunishmentFlags |= 2;
                state = 6;
            }
            else if (state == 6) // Reposition player at entrance
            {
                PositionPlayerAtLocationEntrance();
                state = 100;
            }
            else if (state == 100) // Done
            {
                ReleaseFromJail();
            }
        }
Ejemplo n.º 29
0
        /// <summary>
        /// Make decision about what movement action to take.
        /// </summary>
        void Move()
        {
            // Cancel movement and animations if paralyzed, but still allow gravity to take effect
            // This will have the (intentional for now) side-effect of making paralyzed flying enemies fall out of the air
            // Paralyzed swimming enemies will just freeze in place
            // Freezing anims also prevents the attack from triggering until paralysis cleared
            if (entityBehaviour.Entity.IsParalyzed)
            {
                mobile.FreezeAnims = true;

                if ((swims || flies) && !isLevitating)
                {
                    controller.Move(Vector3.zero);
                }
                else
                {
                    controller.SimpleMove(Vector3.zero);
                }

                return;
            }
            mobile.FreezeAnims = false;

            // If hit, get knocked back
            if (knockBackSpeed > 0)
            {
                // Limit knockBackSpeed. This can be higher than what is actually used for the speed of motion,
                // making it last longer and do more damage if the enemy collides with something (TODO).
                if (knockBackSpeed > (40 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10)))
                {
                    knockBackSpeed = (40 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10));
                }

                if (knockBackSpeed > (5 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10)) &&
                    mobile.Summary.EnemyState != MobileStates.PrimaryAttack)
                {
                    mobile.ChangeEnemyState(MobileStates.Hurt);
                }

                // Actual speed of motion is limited
                Vector3 motion;
                if (knockBackSpeed <= (25 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10)))
                {
                    motion = knockBackDirection * knockBackSpeed;
                }
                else
                {
                    motion = knockBackDirection * (25 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10));
                }

                // Move in direction of knockback
                if (swims)
                {
                    WaterMove(motion);
                }
                else if (flies || isLevitating)
                {
                    controller.Move(motion * Time.deltaTime);
                }
                else
                {
                    controller.SimpleMove(motion);
                }

                // Remove remaining knockback and restore animation
                if (classicUpdate)
                {
                    knockBackSpeed -= (5 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10));
                    if (knockBackSpeed <= (5 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10)) &&
                        mobile.Summary.EnemyState != MobileStates.PrimaryAttack)
                    {
                        mobile.ChangeEnemyState(MobileStates.Move);
                    }
                }

                // If a decent hit got in, reconsider whether to continue current tactic
                if (knockBackSpeed > (10 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10)))
                {
                    EvaluateMoveInForAttack();
                }

                return;
            }

            // Apply gravity
            if (!flies && !swims && !isLevitating && !controller.isGrounded)
            {
                controller.SimpleMove(Vector3.zero);

                // Only return if actually falling. Sometimes mobiles can get stuck where they are !isGrounded but SimpleMove(Vector3.zero) doesn't help.
                // Allowing them to continue and attempt a Move() in the code below frees them, but we don't want to allow that if we can avoid it so they aren't moving
                // while falling, which can also accelerate the fall due to anti-bounce downward movement in Move().
                if (lastPosition != transform.position)
                {
                    return;
                }
            }

            // Monster speed of movement follows the same formula as for when the player walks
            float moveSpeed = (entity.Stats.LiveSpeed + PlayerSpeedChanger.dfWalkBase) * MeshReader.GlobalScale;

            // Get isPlayingOneShot for use below
            bool isPlayingOneShot = mobile.IsPlayingOneShot();

            // Reduced speed if playing a one-shot animation with enhanced AI
            if (isPlayingOneShot && DaggerfallUnity.Settings.EnhancedCombatAI)
            {
                moveSpeed /= AttackSpeedDivisor;
            }

            // As long as the target is detected,
            // giveUpTimer is reset to full
            if (senses.DetectedTarget)
            {
                giveUpTimer = 200;
            }

            // GiveUpTimer value is from classic, so decrease at the speed of classic's update loop
            if (classicUpdate && !senses.DetectedTarget && giveUpTimer > 0)
            {
                giveUpTimer--;
            }

            // Change to idle animation if haven't moved or rotated
            if (!mobile.IsPlayingOneShot())
            {
                // Rotation is done at classic update rate, so check at classic update rate
                if (classicUpdate)
                {
                    Vector3 currentDirection = transform.forward;
                    currentDirection.y = 0;

                    if (lastPosition == transform.position && lastDirection == currentDirection)
                    {
                        mobile.ChangeEnemyState(MobileStates.Idle);
                        rotating = false;
                    }
                    else
                    {
                        mobile.ChangeEnemyState(MobileStates.Move);
                    }

                    lastDirection = currentDirection;
                }
                // Movement is done at regular update rate, so check at regular update rate
                else if (!rotating && lastPosition == transform.position)
                {
                    mobile.ChangeEnemyState(MobileStates.Idle);
                }
                else
                {
                    mobile.ChangeEnemyState(MobileStates.Move);
                }

                lastPosition = transform.position;
            }

            // Do nothing if no target or after giving up finding the target or if target position hasn't been acquired yet
            if (senses.Target == null || giveUpTimer == 0 || senses.PredictedTargetPos == EnemySenses.ResetPlayerPos)
            {
                SetChangeStateTimer();
                bashing = false;

                return;
            }

            if (bashing)
            {
                if (senses.TargetInSight || senses.LastKnownDoor == null || !senses.LastKnownDoor.IsLocked)
                {
                    bashing = false;
                }
                else
                {
                    int speed = entity.Stats.LiveSpeed;
                    if (classicUpdate && DFRandom.rand() % speed >= (speed >> 3) + 6 && attack.MeleeTimer == 0)
                    {
                        mobile.ChangeEnemyState(MobileStates.PrimaryAttack);
                        attack.ResetMeleeTimer();
                    }

                    return;
                }
            }

            bool targetPosIsEnemyPos = false;

            // Get location to move towards. Either the combat target's position or, if trying to avoid an obstacle or fall,
            // a location to try to detour around the obstacle/fall.
            if (avoidObstaclesTimer == 0 && (senses.PredictedTargetPos.y > transform.position.y || ClearPathToPosition(senses.PredictedTargetPos)))
            {
                targetPos = senses.PredictedTargetPos;
                // Flying enemies and slaughterfish aim for target face
                if (flies || isLevitating || (swims && mobile.Summary.Enemy.ID == (int)MonsterCareers.Slaughterfish))
                {
                    targetPos.y += 0.9f;
                }
                else
                {
                    // Ground enemies target at their own height
                    // This avoids short enemies from stepping on each other as they approach the target
                    // Otherwise, their target vector aims up towards the target
                    var targetController = senses.Target.GetComponent <CharacterController>();
                    var deltaHeight      = (targetController.height - controller.height) / 2;
                    targetPos.y -= deltaHeight;
                }
                tempMovePos         = targetPos;
                targetPosIsEnemyPos = true;
            }
            // If detouring, use the detour position
            else if (avoidObstaclesTimer > 0)
            {
                targetPos = tempMovePos;
            }
            // Otherwise, go straight
            else
            {
                tempMovePos = transform.position + transform.forward * 2;
                targetPos   = tempMovePos;
            }

            // Get direction & distance.
            var   direction = (targetPos - transform.position).normalized;
            float distance  = (targetPos - transform.position).magnitude;

            // Ranged attacks
            if (targetPosIsEnemyPos && senses.DetectedTarget && 360 * MeshReader.GlobalScale < senses.DistanceToTarget && senses.DistanceToTarget < 2048 * MeshReader.GlobalScale)
            {
                bool evaluateBow         = mobile.Summary.Enemy.HasRangedAttack1 && mobile.Summary.Enemy.ID > 129 && mobile.Summary.Enemy.ID != 132;
                bool evaluateRangedMagic = false;
                if (!evaluateBow)
                {
                    evaluateRangedMagic = CanCastRangedSpell();
                }

                if (evaluateBow || evaluateRangedMagic)
                {
                    if (classicUpdate && senses.TargetIsWithinYawAngle(22.5f, senses.LastKnownTargetPos))
                    {
                        if (!isPlayingOneShot)
                        {
                            if (evaluateBow)
                            {
                                // Random chance to shoot bow
                                if (DFRandom.rand() < 1000)
                                {
                                    if (mobile.Summary.Enemy.HasRangedAttack1 && !mobile.Summary.Enemy.HasRangedAttack2)
                                    {
                                        mobile.ChangeEnemyState(MobileStates.RangedAttack1);
                                    }
                                    else if (mobile.Summary.Enemy.HasRangedAttack2)
                                    {
                                        mobile.ChangeEnemyState(MobileStates.RangedAttack2);
                                    }
                                }
                            }
                            // Random chance to shoot spell
                            else if (DFRandom.rand() % 40 == 0 &&
                                     entityEffectManager.SetReadySpell(selectedSpell))
                            {
                                mobile.ChangeEnemyState(MobileStates.Spell);
                            }
                        }
                    }
                    else
                    {
                        TurnToTarget(direction);
                    }

                    return;
                }
            }

            // Touch spells
            if (targetPosIsEnemyPos && senses.TargetInSight && senses.DetectedTarget && attack.MeleeTimer == 0 && senses.DistanceToTarget <= attack.MeleeDistance +
                senses.TargetRateOfApproach && CanCastTouchSpell() && entityEffectManager.SetReadySpell(selectedSpell))
            {
                if (mobile.Summary.EnemyState != MobileStates.Spell)
                {
                    mobile.ChangeEnemyState(MobileStates.Spell);
                }

                attack.ResetMeleeTimer();
                return;
            }

            // Update advance/retreat decision
            if (moveInForAttackTimer <= 0 && avoidObstaclesTimer == 0)
            {
                EvaluateMoveInForAttack();
            }

            // Update timers
            if (moveInForAttackTimer > 0)
            {
                moveInForAttackTimer -= Time.deltaTime;
            }

            if (avoidObstaclesTimer > 0 && senses.TargetIsWithinYawAngle(5.625f, targetPos))
            {
                avoidObstaclesTimer -= Time.deltaTime;
            }
            if (avoidObstaclesTimer < 0)
            {
                avoidObstaclesTimer = 0;
            }

            if (checkingClockwiseTimer > 0)
            {
                checkingClockwiseTimer -= Time.deltaTime;
            }
            if (checkingClockwiseTimer < 0)
            {
                checkingClockwiseTimer = 0;
            }

            if (changeStateTimer > 0)
            {
                changeStateTimer -= Time.deltaTime;
            }

            // If detouring, attempt to move
            if (avoidObstaclesTimer > 0)
            {
                AttemptMove(direction, moveSpeed);
            }
            // Otherwise, if not still executing a retreat, approach target until close enough to be on-guard.
            // If decided to move in for attack, continue until within melee range. Classic always moves in for attack.
            else if ((!retreating && distance >= (stopDistance * 2.75)) ||
                     (distance > stopDistance && moveInForAttack))
            {
                // If state change timer is done, or we are continuing an already started combatMove, we can move immediately
                if (changeStateTimer <= 0 || pursuing)
                {
                    AttemptMove(direction, moveSpeed);
                }
                // Otherwise, look at target until timer finishes
                else if (!senses.TargetIsWithinYawAngle(22.5f, targetPos))
                {
                    TurnToTarget(direction);
                }
            }
            // Back away from combat target if right next to it, or if decided to retreat and enemy is too close.
            // Classic AI never backs awwy.
            else if (DaggerfallUnity.Settings.EnhancedCombatAI && senses.TargetInSight && (distance < stopDistance * .50 ||
                                                                                           (!moveInForAttack && distance < (stopDistance * retreatDistanceMultiplier))))
            {
                // If state change timer is done, or we are already executing a retreat, we can move immediately
                if (changeStateTimer <= 0 || retreating)
                {
                    AttemptMove(direction, moveSpeed / 2, true);
                }
                // Otherwise, look at target until timer finishes
                else if (!senses.TargetIsWithinYawAngle(22.5f, targetPos))
                {
                    TurnToTarget(direction.normalized);
                }
            }
            // Not moving, just look at target
            else if (!senses.TargetIsWithinYawAngle(22.5f, targetPos))
            {
                TurnToTarget(direction.normalized);
            }
            else // Not moving, and no need to turn
            {
                SetChangeStateTimer();
                pursuing            = false;
                retreating          = false;
                avoidObstaclesTimer = 0;
            }
        }
Ejemplo n.º 30
0
        /// <summary>
        /// Make decision about what movement action to take.
        /// </summary>
        void Move()
        {
            // Cancel movement and animations if paralyzed, but still allow gravity to take effect
            // This will have the (intentional for now) side-effect of making paralyzed flying enemies fall out of the air
            // Paralyzed swimming enemies will just freeze in place
            // Freezing anims also prevents the attack from triggering until paralysis cleared
            if (entityBehaviour.Entity.IsParalyzed)
            {
                mobile.FreezeAnims = true;

                if ((swims || flies) && !isLevitating)
                {
                    controller.Move(Vector3.zero);
                }
                else
                {
                    controller.SimpleMove(Vector3.zero);
                }

                return;
            }
            mobile.FreezeAnims = false;

            // Apply gravity to non-moving AI if active (has a combat target) or nearby
            if ((entityBehaviour.Target != null || senses.WouldBeSpawnedInClassic) && !flies && !swims)
            {
                controller.SimpleMove(Vector3.zero);
            }

            // If hit, get knocked back
            if (knockBackSpeed > 0)
            {
                // Limit knockBackSpeed. This can be higher than what is actually used for the speed of motion,
                // making it last longer and do more damage if the enemy collides with something (TODO).
                if (knockBackSpeed > (40 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10)))
                {
                    knockBackSpeed = (40 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10));
                }

                if (knockBackSpeed > (5 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10)) &&
                    mobile.Summary.EnemyState != MobileStates.PrimaryAttack)
                {
                    mobile.ChangeEnemyState(MobileStates.Hurt);
                }

                // Actual speed of motion is limited
                Vector3 motion;
                if (knockBackSpeed <= (25 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10)))
                {
                    motion = knockBackDirection * knockBackSpeed;
                }
                else
                {
                    motion = knockBackDirection * (25 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10));
                }

                // Move in direction of knockback
                if (swims)
                {
                    WaterMove(motion);
                }
                else if (flies || isLevitating)
                {
                    controller.Move(motion * Time.deltaTime);
                }
                else
                {
                    controller.SimpleMove(motion);
                }

                // Remove remaining knockback and restore animation
                if (classicUpdate)
                {
                    knockBackSpeed -= (5 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10));
                    if (knockBackSpeed <= (5 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10)) &&
                        mobile.Summary.EnemyState != MobileStates.PrimaryAttack)
                    {
                        mobile.ChangeEnemyState(MobileStates.Move);
                    }
                }

                // If a decent hit got in, reconsider whether to continue current tactic
                if (knockBackSpeed > (10 / (PlayerSpeedChanger.classicToUnitySpeedUnitRatio / 10)))
                {
                    EvaluateMoveInForAttack();
                }

                return;
            }

            // Monster speed of movement follows the same formula as for when the player walks
            float moveSpeed = (entity.Stats.LiveSpeed + PlayerSpeedChanger.dfWalkBase) * MeshReader.GlobalScale;

            // Reduced speed if playing a one-shot animation with enhanced AI
            if (mobile.IsPlayingOneShot() && DaggerfallUnity.Settings.EnhancedCombatAI)
            {
                moveSpeed /= AttackSpeedDivisor;
            }

            // As long as the target is detected,
            // giveUpTimer is reset to full
            if (senses.DetectedTarget)
            {
                giveUpTimer = 200;
            }

            // GiveUpTimer value is from classic, so decrease at the speed of classic's update loop
            if (!senses.DetectedTarget &&
                giveUpTimer > 0 && classicUpdate)
            {
                giveUpTimer--;
            }

            // Change to idle animation if haven't moved or rotated
            if (!mobile.IsPlayingOneShot())
            {
                // Rotation is done at classic update rate, so check at classic update rate
                if (classicUpdate)
                {
                    Vector3 currentDirection = transform.forward;
                    currentDirection.y = 0;

                    if (lastPosition == transform.position && lastDirection == currentDirection)
                    {
                        mobile.ChangeEnemyState(MobileStates.Idle);
                        rotating = false;
                    }
                    else
                    {
                        mobile.ChangeEnemyState(MobileStates.Move);
                    }

                    lastDirection = currentDirection;
                }
                // Movement is done at regular update rate, so check at regular update rate
                else if (!rotating && lastPosition == transform.position)
                {
                    mobile.ChangeEnemyState(MobileStates.Idle);
                }
                else
                {
                    mobile.ChangeEnemyState(MobileStates.Move);
                }

                lastPosition = transform.position;
            }

            // Do nothing if no target or after giving up finding the target
            if (entityBehaviour.Target == null || giveUpTimer == 0)
            {
                SetChangeStateTimer();

                return;
            }

            // Get predicted target position
            if (avoidObstaclesTimer == 0 && !lookingForDetour)
            {
                targetPos = senses.PredictedTargetPos;
                // Flying enemies and slaughterfish aim for target face
                if (flies || isLevitating || (swims && mobile.Summary.Enemy.ID == (int)MonsterCareers.Slaughterfish))
                {
                    targetPos.y += 0.9f;
                }
                else
                {
                    // Ground enemies target at their own height
                    // This avoids short enemies from stepping on each other as they approach the target
                    // Otherwise, their target vector aims up towards the target
                    var playerController = GameManager.Instance.PlayerController;
                    var deltaHeight      = (playerController.height - controller.height) / 2;
                    targetPos.y -= deltaHeight;
                }
                tempMovePos = targetPos;
            }
            else
            {
                targetPos = tempMovePos;
            }

            // Get direction & distance.
            var   direction = targetPos - transform.position;
            float distance  = (targetPos - transform.position).magnitude;

            // Ranged attacks
            if (senses.TargetInSight && 360 * MeshReader.GlobalScale < senses.DistanceToTarget && senses.DistanceToTarget < 2048 * MeshReader.GlobalScale)
            {
                bool evaluateBow         = mobile.Summary.Enemy.HasRangedAttack1 && mobile.Summary.Enemy.ID > 129 && mobile.Summary.Enemy.ID != 132;
                bool evaluateRangedMagic = false;
                if (!evaluateBow)
                {
                    evaluateRangedMagic = CanCastRangedSpell();
                }

                if (evaluateBow || evaluateRangedMagic)
                {
                    if (senses.TargetIsWithinYawAngle(22.5f, senses.LastKnownTargetPos))
                    {
                        if (!mobile.IsPlayingOneShot())
                        {
                            if (evaluateBow)
                            {
                                // Random chance to shoot bow
                                if (classicUpdate && DFRandom.rand() < 1000)
                                {
                                    if (mobile.Summary.Enemy.HasRangedAttack1 && !mobile.Summary.Enemy.HasRangedAttack2)
                                    {
                                        mobile.ChangeEnemyState(MobileStates.RangedAttack1);
                                    }
                                    else if (mobile.Summary.Enemy.HasRangedAttack2)
                                    {
                                        mobile.ChangeEnemyState(MobileStates.RangedAttack2);
                                    }
                                }
                            }
                            // Random chance to shoot spell
                            else if (classicUpdate && DFRandom.rand() % 40 == 0 &&
                                     entityEffectManager.SetReadySpell(selectedSpell))
                            {
                                mobile.ChangeEnemyState(MobileStates.Spell);
                            }
                        }
                    }
                    else
                    {
                        TurnToTarget(direction.normalized);
                    }

                    return;
                }
            }

            if (senses.TargetInSight && attack.MeleeTimer == 0 && senses.DistanceToTarget <= attack.MeleeDistance +
                senses.TargetRateOfApproach && CanCastTouchSpell() && entityEffectManager.SetReadySpell(selectedSpell))
            {
                if (mobile.Summary.EnemyState != MobileStates.Spell)
                {
                    mobile.ChangeEnemyState(MobileStates.Spell);
                }

                attack.ResetMeleeTimer();
                return;
            }

            // Update melee decision
            if (moveInForAttackTimer <= 0 && avoidObstaclesTimer == 0 && !lookingForDetour)
            {
                EvaluateMoveInForAttack();
            }
            if (moveInForAttackTimer > 0)
            {
                moveInForAttackTimer -= Time.deltaTime;
            }

            if (avoidObstaclesTimer > 0)
            {
                avoidObstaclesTimer -= Time.deltaTime;
            }
            if (avoidObstaclesTimer < 0)
            {
                avoidObstaclesTimer = 0;
            }

            if (changeStateTimer > 0)
            {
                changeStateTimer -= Time.deltaTime;
            }

            // Looking for detour
            if (lookingForDetour)
            {
                CombatMove(direction, moveSpeed);
            }
            // Approach target until we are close enough to be on-guard, or continue to melee range if attacking
            else if ((!retreating && distance >= (stopDistance * 2.75)) ||
                     (distance > stopDistance && moveInForAttack))
            {
                // If state change timer is done, or we are already pursuing, we can move
                if (changeStateTimer <= 0 || pursuing)
                {
                    CombatMove(direction, moveSpeed);
                }
                // Otherwise, just keep an eye on target until timer finishes
                else if (!senses.TargetIsWithinYawAngle(22.5f, targetPos))
                {
                    TurnToTarget(direction.normalized);
                }
            }
            // Back away if right next to target, if retreating, or if cooling down from attack
            // Classic AI never backs away
            else if (DaggerfallUnity.Settings.EnhancedCombatAI && (senses.TargetInSight && (distance < stopDistance * .50 ||
                                                                                            (!moveInForAttack && distance < (stopDistance * retreatDistanceMultiplier)))))
            {
                // If state change timer is done, or we are already retreating, we can move
                if (changeStateTimer <= 0 || retreating)
                {
                    CombatMove(direction, moveSpeed / 2, true);
                }
                // Otherwise, just keep an eye on target until timer finishes
                else if (!senses.TargetIsWithinYawAngle(22.5f, targetPos))
                {
                    TurnToTarget(direction.normalized);
                }
            }
            else if (!senses.TargetIsWithinYawAngle(22.5f, targetPos))
            {
                TurnToTarget(direction.normalized);
            }
            else if (avoidObstaclesTimer > 0 && distance > 0.1f)
            {
                CombatMove(direction, moveSpeed);
            }
            else // Next to target
            {
                SetChangeStateTimer();
                pursuing   = false;
                retreating = false;

                avoidObstaclesTimer = 0;
            }
        }