public static DaggerfallUnityItem CreateRandomlyFilledSoulTrap() { // Create a trapped soul type and filter invalid creatures MobileTypes soul = MobileTypes.None; while (soul == MobileTypes.None) { MobileTypes randomSoul = (MobileTypes)UnityEngine.Random.Range((int)MobileTypes.Rat, (int)MobileTypes.Lamia + 1); if (randomSoul == MobileTypes.Horse_Invalid || randomSoul == MobileTypes.Dragonling) // NOTE: Dragonling (34) is soulless, only soul of Dragonling_Alternate (40) from B0B70Y16 has a soul { continue; } else { soul = randomSoul; } } // Generate item DaggerfallUnityItem newItem = CreateItem(ItemGroups.MiscItems, (int)MiscItems.Soul_trap); newItem.TrappedSoulType = soul; MobileEnemy mobileEnemy = GameObjectHelper.EnemyDict[(int)soul]; newItem.value = 5000 + mobileEnemy.SoulPts; return(newItem); }
private void MeleeDamage() { if (entityBehaviour) { EnemyEntity entity = entityBehaviour.Entity as EnemyEntity; MobileEnemy enemy = entity.MobileEnemy; // Are we still in range and facing player? Then apply melee damage. if (senses.DistanceToPlayer < MeleeDistance && senses.PlayerInSight) { // Calculate damage // TODO: Implement enemy use of weapons. Play hit or miss sounds. int damage = Game.Formulas.FormulaHelper.CalculateWeaponDamage(entity, GameManager.Instance.PlayerEntity, null); if (damage > 0) { GameManager.Instance.PlayerObject.SendMessage("RemoveHealth", damage); } else if (sounds) { sounds.PlayMissSound(); } // Tally player's dodging skill GameManager.Instance.PlayerEntity.TallySkill((short)Skills.Dodging, 1); } } }
private void MeleeDamage() { if (entityBehaviour) { EnemyEntity entity = entityBehaviour.Entity as EnemyEntity; MobileEnemy enemy = entity.MobileEnemy; int damage = 0; // Are we still in range and facing player? Then apply melee damage. if (senses.DistanceToPlayer < MeleeDistance && senses.PlayerInSight) { // Calculate damage damage = Game.Formulas.FormulaHelper.CalculateWeaponDamage(entity, GameManager.Instance.PlayerEntity, null); if (damage > 0) { GameManager.Instance.PlayerObject.SendMessage("RemoveHealth", damage); } // Tally player's dodging skill GameManager.Instance.PlayerEntity.TallySkill(DFCareer.Skills.Dodging, 1); } if (sounds) { Items.DaggerfallUnityItem weapon = entity.ItemEquipTable.GetItem(Items.EquipSlots.RightHand); if (weapon == null) { weapon = entity.ItemEquipTable.GetItem(Items.EquipSlots.LeftHand); } if (damage > 0) { // TODO: Play hit and parry sounds on other AI characters once attacks against other AI are possible DaggerfallAudioSource dfAudioSource = GetComponent <DaggerfallAudioSource>(); if (dfAudioSource) { if (weapon == null) { dfAudioSource.PlayOneShot((int)SoundClips.Hit1 + UnityEngine.Random.Range(2, 4), 0, 1.1f); } else { dfAudioSource.PlayOneShot((int)SoundClips.Hit1 + UnityEngine.Random.Range(0, 5), 0, 1.1f); } } } else { sounds.PlayMissSound(weapon); } } } }
ItemCollection GetMerchantMagicItems() { if (merchantItems == null) { PlayerEntity playerEntity = GameManager.Instance.PlayerEntity; ItemCollection items = new ItemCollection(); int numOfItems = (buildingDiscoveryData.quality / 2) + 1; for (int i = 0; i <= numOfItems; i++) { DaggerfallUnityItem magicItem = ItemBuilder.CreateRandomMagicItem(playerEntity.Level, playerEntity.Gender, playerEntity.Race); // Item is already identified magicItem.flags |= 0x20; items.AddItem(magicItem); } items.AddItem(ItemBuilder.CreateItem(ItemGroups.MiscItems, (int)MiscItems.Spellbook)); if (guild.Rank >= 4) { for (int i = 0; i <= numOfItems; i++) { DaggerfallUnityItem magicItem = ItemBuilder.CreateItem(ItemGroups.MiscItems, (int)MiscItems.Soul_trap); magicItem.value = 5000; if (UnityEngine.Random.Range(1, 101) >= 25) { magicItem.TrappedSoulType = MobileTypes.None; } else { int id = UnityEngine.Random.Range(0, 43); magicItem.TrappedSoulType = (MobileTypes)id; MobileEnemy mobileEnemy = GameObjectHelper.EnemyDict[id]; magicItem.value += mobileEnemy.SoulPts; } items.AddItem(magicItem); } } merchantItems = items; } return(merchantItems); }
private void SetEnemy() { // Get random enemy int index = Random.Range(0, GameObjectHelper.EnemyDict.Count); MobileEnemy mobileEnemy = GameObjectHelper.EnemyDict.ElementAt(index).Value; // Get random texture int archive = Random.value < 0.5f ? mobileEnemy.MaleTexture : mobileEnemy.FemaleTexture; string fileName = string.Format("TEXTURE.{0}", archive); TextureFile textureFile = new TextureFile(Path.Combine(DaggerfallUnity.Instance.Arena2Path, fileName), FileUsage.UseMemory, true); int record = Random.Range(0, textureFile.RecordCount); int frame = Random.Range(0, textureFile.GetFrameCount(record)); // Set fields enemyTexture = ImageReader.GetTexture(fileName, record, frame, true); enemyName = TextManager.Instance.GetLocalizedEnemyName(mobileEnemy.ID); }
private void MeleeDamage() { int minDamage = 0, maxDamage = 0; if (entityBehaviour) { EnemyEntity entity = entityBehaviour.Entity as EnemyEntity; MobileEnemy enemy = entity.MobileEnemy; minDamage = enemy.MinDamage; maxDamage = enemy.MaxDamage; } // Are we still in range and facing player? Then apply melee damage. if (senses.DistanceToPlayer < MeleeDistance && senses.PlayerInSight) { int damage = Random.Range(minDamage, maxDamage + 1); senses.Player.SendMessage("RemoveHealth", damage); } }
/// <summary> /// Sets enemy career and prepares entity settings. /// </summary> public void SetEnemyCareer(MobileEnemy mobileEnemy, EntityTypes entityType) { if (entityType == EntityTypes.EnemyMonster) { careerIndex = (int)mobileEnemy.ID; career = GetMonsterCareerTemplate((MonsterCareers)careerIndex); stats.SetFromCareer(career); // Enemy monster has predefined level and health level = career.HitPointsPerLevelOrMonsterLevel; maxHealth = UnityEngine.Random.Range(mobileEnemy.MinHealth, mobileEnemy.MaxHealth + 1); } else if (entityType == EntityTypes.EnemyClass) { careerIndex = (int)mobileEnemy.ID - 128; career = GetClassCareerTemplate((ClassCareers)careerIndex); stats.SetFromCareer(career); // Enemy class is levelled to player and uses same health rules level = GameManager.Instance.PlayerEntity.Level; maxHealth = FormulaHelper.RollMaxHealth(level, stats.Endurance, career.HitPointsPerLevelOrMonsterLevel); // Enemy class damage is temporarily set by a fudged level multiplier // This will change once full entity setup and items are available const float damageMultiplier = 4f; mobileEnemy.MinDamage = (int)(level * damageMultiplier); mobileEnemy.MaxDamage = (int)((level + 2) * damageMultiplier); } else { career = new DFCareer(); careerIndex = -1; return; } this.mobileEnemy = mobileEnemy; this.entityType = entityType; name = career.Name; FillVitalSigns(); }
// TODO: Classic seems deterministic so when re-visiting each mages guildhall, player sees same stuff. // Does it change with level? Is it always generated but uses a consistent seed? How should DFU do this? ItemCollection GetMerchantMagicItems() { PlayerEntity playerEntity = GameManager.Instance.PlayerEntity; ItemCollection items = new ItemCollection(); int numOfItems = (buildingDiscoveryData.quality / 2) + 1; for (int i = 0; i <= numOfItems; i++) { // Create magic item which is already identified DaggerfallUnityItem magicItem = ItemBuilder.CreateRandomMagicItem(playerEntity.Level, playerEntity.Gender, playerEntity.Race); magicItem.IdentifyItem(); items.AddItem(magicItem); } items.AddItem(ItemBuilder.CreateItem(ItemGroups.MiscItems, (int)MiscItems.Spellbook)); if (guild.CanAccessService(GuildServices.BuySoulgems)) { for (int i = 0; i <= numOfItems; i++) { DaggerfallUnityItem magicItem = ItemBuilder.CreateItem(ItemGroups.MiscItems, (int)MiscItems.Soul_trap); magicItem.value = 5000; if (Dice100.FailedRoll(25)) { magicItem.TrappedSoulType = MobileTypes.None; } else { int id = UnityEngine.Random.Range(0, 43); magicItem.TrappedSoulType = (MobileTypes)id; MobileEnemy mobileEnemy = GameObjectHelper.EnemyDict[id]; magicItem.value += mobileEnemy.SoulPts; } items.AddItem(magicItem); } } return(items); }
void Update() { if (mainCamera == null) { return; } // Change activate mode if (InputManager.Instance.ActionStarted(InputManager.Actions.StealMode)) { ChangeInteractionMode(PlayerActivateModes.Steal); } else if (InputManager.Instance.ActionStarted(InputManager.Actions.GrabMode)) { ChangeInteractionMode(PlayerActivateModes.Grab); } else if (InputManager.Instance.ActionStarted(InputManager.Actions.InfoMode)) { ChangeInteractionMode(PlayerActivateModes.Info); } else if (InputManager.Instance.ActionStarted(InputManager.Actions.TalkMode)) { ChangeInteractionMode(PlayerActivateModes.Talk); } // Fire ray into scene if (InputManager.Instance.ActionStarted(InputManager.Actions.ActivateCenterObject)) { // TODO: Clean all this up // Ray origin is slightly below camera height to ensure it originates inside player's own collider // This prevents ray from intersecting with player's own collider and blocking looting or low triggers Ray ray = new Ray(transform.position + Vector3.up * 0.7f, mainCamera.transform.forward); RaycastHit hit; RayDistance = 75f; // Approximates classic at full view distance (default setting). Classic seems to do raycasts for as far as it can render objects. bool hitSomething = Physics.Raycast(ray, out hit, RayDistance); if (hitSomething) { bool hitBuilding = false; bool buildingUnlocked = false; DFLocation.BuildingTypes buildingType = DFLocation.BuildingTypes.AllValid; StaticBuilding building = new StaticBuilding(); #region Hit Checks // Trigger quest resource behaviour click on anything but NPCs QuestResourceBehaviour questResourceBehaviour; if (QuestResourceBehaviourCheck(hit, out questResourceBehaviour)) { if (!(questResourceBehaviour.TargetResource is Person)) { if (hit.distance > (DefaultActivationDistance * MeshReader.GlobalScale)) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); return; } // Only trigger click when not in info mode if (currentMode != PlayerActivateModes.Info) { TriggerQuestResourceBehaviourClick(questResourceBehaviour); } } } // Check for a static building hit Transform buildingOwner; DaggerfallStaticBuildings buildings = GetBuildings(hit.transform, out buildingOwner); if (buildings) { if (buildings.HasHit(hit.point, out building)) { hitBuilding = true; // Get building directory for location BuildingDirectory buildingDirectory = GameManager.Instance.StreamingWorld.GetCurrentBuildingDirectory(); if (!buildingDirectory) { return; } // Get detailed building data from directory BuildingSummary buildingSummary; if (!buildingDirectory.GetBuildingSummary(building.buildingKey, out buildingSummary)) { return; } // Check if door is unlocked buildingUnlocked = BuildingIsUnlocked(buildingSummary); // Store building type buildingType = buildingSummary.BuildingType; if (currentMode == PlayerActivateModes.Info) { // Discover building GameManager.Instance.PlayerGPS.DiscoverBuilding(building.buildingKey); // Get discovered building PlayerGPS.DiscoveredBuilding db; if (GameManager.Instance.PlayerGPS.GetDiscoveredBuilding(building.buildingKey, out db)) { // TODO: Check against quest system for an overriding quest-assigned display name for this building DaggerfallUI.AddHUDText(db.displayName); if (!buildingUnlocked && buildingType < DFLocation.BuildingTypes.Temple && buildingType != DFLocation.BuildingTypes.HouseForSale) { string storeClosedMessage = HardStrings.storeClosed; storeClosedMessage = storeClosedMessage.Replace("%d1", openHours[(int)buildingType].ToString()); storeClosedMessage = storeClosedMessage.Replace("%d2", closeHours[(int)buildingType].ToString()); DaggerfallUI.Instance.PopupMessage(storeClosedMessage); } } } } } // Check for a static door hit Transform doorOwner; DaggerfallStaticDoors doors = GetDoors(hit.transform, out doorOwner); if (doors && playerEnterExit) { StaticDoor door; if (doors.HasHit(hit.point, out door)) { // Check if close enough to activate if (hit.distance > (DoorActivationDistance * MeshReader.GlobalScale)) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); return; } if (door.doorType == DoorTypes.Building && !playerEnterExit.IsPlayerInside) { // Discover building GameManager.Instance.PlayerGPS.DiscoverBuilding(building.buildingKey); // TODO: Implement lockpicking and door bashing for exterior doors // For now, any locked building door can be entered by using steal mode if (!buildingUnlocked) { if (currentMode != PlayerActivateModes.Steal) { string Locked = "Locked."; DaggerfallUI.Instance.PopupMessage(Locked); return; } else // Breaking into building { PlayerEntity player = GameManager.Instance.PlayerEntity; //player.TallyCrimeGuildRequirements(true, 1); } } // If entering a shop let player know the quality level // If entering an open home, show greeting if (hitBuilding) { const int houseGreetingsTextId = 256; DaggerfallMessageBox mb; if (buildingUnlocked && buildingType >= DFLocation.BuildingTypes.House1 && buildingType <= DFLocation.BuildingTypes.House4) { string greetingText = DaggerfallUnity.Instance.TextProvider.GetRandomText(houseGreetingsTextId); mb = DaggerfallUI.MessageBox(greetingText); } else { mb = PresentShopQuality(building); } if (mb != null) { // Defer transition to interior to after user closes messagebox deferredInteriorDoorOwner = doorOwner; deferredInteriorDoor = door; mb.OnClose += Popup_OnClose; return; } } // Hit door while outside, transition inside TransitionInterior(doorOwner, door, true); return; } else if (door.doorType == DoorTypes.Building && playerEnterExit.IsPlayerInside) { // Hit door while inside, transition outside playerEnterExit.TransitionExterior(true); return; } else if (door.doorType == DoorTypes.DungeonEntrance && !playerEnterExit.IsPlayerInside) { if (playerGPS) { // Hit dungeon door while outside, transition inside playerEnterExit.TransitionDungeonInterior(doorOwner, door, playerGPS.CurrentLocation, true); return; } } else if (door.doorType == DoorTypes.DungeonExit && playerEnterExit.IsPlayerInside) { // Hit dungeon exit while inside, ask if access wagon or transition outside if (GameManager.Instance.PlayerEntity.Items.Contains(ItemGroups.Transportation, (int)Transportation.Small_cart)) { DaggerfallMessageBox messageBox = new DaggerfallMessageBox(DaggerfallUI.UIManager, DaggerfallMessageBox.CommonMessageBoxButtons.YesNo, 38, DaggerfallUI.UIManager.TopWindow); messageBox.OnButtonClick += DungeonWagonAccess_OnButtonClick; DaggerfallUI.UIManager.PushWindow(messageBox); return; } else { playerEnterExit.TransitionDungeonExterior(true); } } } } // Check for an action door hit DaggerfallActionDoor actionDoor; if (ActionDoorCheck(hit, out actionDoor)) { // Check if close enough to activate if (hit.distance > (DoorActivationDistance * MeshReader.GlobalScale)) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); return; } if (currentMode == PlayerActivateModes.Steal && actionDoor.IsLocked && !actionDoor.IsOpen) { actionDoor.AttemptLockpicking(); } else { actionDoor.ToggleDoor(true); } } // Check for action record hit DaggerfallAction action; if (ActionCheck(hit, out action)) { if (hit.distance <= (DefaultActivationDistance * MeshReader.GlobalScale)) { action.Receive(this.gameObject, DaggerfallAction.TriggerTypes.Direct); } } // Check for lootable object hit DaggerfallLoot loot; if (LootCheck(hit, out loot)) { switch (currentMode) { case PlayerActivateModes.Info: if (loot.ContainerType == LootContainerTypes.CorpseMarker && !string.IsNullOrEmpty(loot.entityName)) { string message = string.Empty; if (loot.isEnemyClass) { message = HardStrings.youSeeADeadPerson; } else { message = HardStrings.youSeeADead; message = message.Replace("%s", loot.entityName); } DaggerfallUI.Instance.PopupMessage(message); } break; case PlayerActivateModes.Grab: case PlayerActivateModes.Talk: case PlayerActivateModes.Steal: // Check if close enough to activate if (loot.ContainerType == LootContainerTypes.CorpseMarker) { if (hit.distance > CorpseActivationDistance * MeshReader.GlobalScale) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); break; } } else if (hit.distance > TreasureActivationDistance * MeshReader.GlobalScale) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); break; } // For bodies, check has treasure first if (loot.ContainerType == LootContainerTypes.CorpseMarker && loot.Items.Count == 0) { DaggerfallUI.AddHUDText(HardStrings.theBodyHasNoTreasure); break; } // Open inventory window with loot as remote target DaggerfallUI.Instance.InventoryWindow.LootTarget = loot; DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenInventoryWindow); break; } } // Check for static NPC hit StaticNPC npc; if (NPCCheck(hit, out npc)) { switch (currentMode) { case PlayerActivateModes.Info: PresentNPCInfo(npc); break; case PlayerActivateModes.Grab: case PlayerActivateModes.Talk: case PlayerActivateModes.Steal: if (hit.distance > (StaticNPCActivationDistance * MeshReader.GlobalScale)) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); break; } StaticNPCClick(npc); break; } } // Check for mobile NPC hit MobilePersonNPC mobileNpc = null; if (MobilePersonMotorCheck(hit, out mobileNpc)) { switch (currentMode) { case PlayerActivateModes.Info: case PlayerActivateModes.Grab: case PlayerActivateModes.Talk: if (hit.distance > (MobileNPCActivationDistance * MeshReader.GlobalScale)) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); break; } GameManager.Instance.TalkManager.TalkToMobileNPC(mobileNpc); break; case PlayerActivateModes.Steal: if (!mobileNpc.PickpocketByPlayerAttempted) { if (hit.distance > (PickpocketDistance * MeshReader.GlobalScale)) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); break; } mobileNpc.PickpocketByPlayerAttempted = true; Pickpocket(); } break; } } // Check for mobile enemy hit DaggerfallEntityBehaviour mobileEnemyBehaviour; if (MobileEnemyCheck(hit, out mobileEnemyBehaviour)) { EnemyEntity enemyEntity = mobileEnemyBehaviour.Entity as EnemyEntity; switch (currentMode) { case PlayerActivateModes.Info: case PlayerActivateModes.Grab: case PlayerActivateModes.Talk: if (enemyEntity != null) { MobileEnemy mobileEnemy = enemyEntity.MobileEnemy; bool startsWithVowel = "aeiouAEIOU".Contains(mobileEnemy.Name[0].ToString()); string message; if (startsWithVowel) { message = HardStrings.youSeeAn; } else { message = HardStrings.youSeeA; } message = message.Replace("%s", mobileEnemy.Name); DaggerfallUI.Instance.PopupMessage(message); } break; case PlayerActivateModes.Steal: // Classic allows pickpocketing of NPC mobiles and enemy mobiles. // In early versions the only enemy mobiles that can be pickpocketed are classes, // but patch 1.07.212 allows pickpocketing of creatures. // For now, the only enemy mobiles being allowed by DF Unity are classes. if (mobileEnemyBehaviour && (mobileEnemyBehaviour.EntityType != EntityTypes.EnemyClass)) { break; } // Classic doesn't set any flag when pickpocketing enemy mobiles, so infinite attempts are possible if (enemyEntity != null && !enemyEntity.PickpocketByPlayerAttempted) { if (hit.distance > (PickpocketDistance * MeshReader.GlobalScale)) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); break; } enemyEntity.PickpocketByPlayerAttempted = true; Pickpocket(mobileEnemyBehaviour); } break; } } // Trigger ladder hit DaggerfallLadder ladder = hit.transform.GetComponent <DaggerfallLadder>(); if (ladder) { if (hit.distance > (DefaultActivationDistance * MeshReader.GlobalScale)) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); return; } ladder.ClimbLadder(); } #endregion } } }
/// <summary> /// Sets enemy career and prepares entity settings. /// </summary> public void SetEnemyCareer(MobileEnemy mobileEnemy, EntityTypes entityType) { if (entityType == EntityTypes.EnemyMonster) { careerIndex = mobileEnemy.ID; career = GetMonsterCareerTemplate((MonsterCareers)careerIndex); stats.SetPermanentFromCareer(career); // Enemy monster has predefined level, health and armor values. // Armor values can be modified below by equipment. level = mobileEnemy.Level; maxHealth = UnityEngine.Random.Range(mobileEnemy.MinHealth, mobileEnemy.MaxHealth + 1); for (int i = 0; i < ArmorValues.Length; i++) { ArmorValues[i] = (sbyte)(mobileEnemy.ArmorValue * 5); } } else if (entityType == EntityTypes.EnemyClass) { careerIndex = mobileEnemy.ID - 128; career = GetClassCareerTemplate((ClassCareers)careerIndex); stats.SetPermanentFromCareer(career); // I may have a better way to alter the attributes of individual enemy entities, this seems to be where it originates from, will possibly alter later on. // Enemy class is levelled to player and uses similar health rules // City guards are 3 to 6 levels above the player //level = GameManager.Instance.PlayerEntity.Level; // Definitely going to want to mess with this a lot eventually, this is apparently what makes the human enemies equal to the player level, will alter that. level = UnityEngine.Random.Range(1, 31); if (careerIndex == (int)MobileTypes.Knight_CityWatch) { level += UnityEngine.Random.Range(3, 7); } maxHealth = FormulaHelper.RollEnemyClassMaxHealth(level, career.HitPointsPerLevel); } else { career = new DFCareer(); careerIndex = -1; return; } this.mobileEnemy = mobileEnemy; this.entityType = entityType; name = career.Name; minMetalToHit = mobileEnemy.MinMetalToHit; team = mobileEnemy.Team; short skillsLevel = (short)((level * 5) + 30); if (skillsLevel > 100) { skillsLevel = 100; } for (int i = 0; i <= DaggerfallSkills.Count; i++) { skills.SetPermanentSkillValue(i, skillsLevel); } int[] personalityTraits = DaggerfallWorkshop.Utility.EnemyBasics.EnemyPersonalityTraitGenerator(this); // May put the method for the "context based" inventory modifiers here, but first i'll have to figure out how i'm going to do that exactly first. DaggerfallLoot.GenerateEnemyItems(items, personalityTraits, this); // Enemy classes and some monsters use equipment if (EquipmentUser()) { SetEnemyEquipment(personalityTraits); } // Assign spell lists if (entityType == EntityTypes.EnemyMonster) { if (careerIndex == (int)MonsterCareers.Imp) { SetEnemySpells(ImpSpells); } else if (careerIndex == (int)MonsterCareers.Ghost) { SetEnemySpells(GhostSpells); } else if (careerIndex == (int)MonsterCareers.OrcShaman) { SetEnemySpells(OrcShamanSpells); } else if (careerIndex == (int)MonsterCareers.Wraith) { SetEnemySpells(WraithSpells); } else if (careerIndex == (int)MonsterCareers.FrostDaedra) { SetEnemySpells(FrostDaedraSpells); } else if (careerIndex == (int)MonsterCareers.FireDaedra) { SetEnemySpells(FireDaedraSpells); } else if (careerIndex == (int)MonsterCareers.Daedroth) { SetEnemySpells(DaedrothSpells); } else if (careerIndex == (int)MonsterCareers.Vampire) { SetEnemySpells(VampireSpells); } else if (careerIndex == (int)MonsterCareers.DaedraSeducer) { SetEnemySpells(SeducerSpells); } else if (careerIndex == (int)MonsterCareers.VampireAncient) { SetEnemySpells(VampireAncientSpells); } else if (careerIndex == (int)MonsterCareers.DaedraLord) { SetEnemySpells(DaedraLordSpells); } else if (careerIndex == (int)MonsterCareers.Lich) { SetEnemySpells(LichSpells); } else if (careerIndex == (int)MonsterCareers.AncientLich) { SetEnemySpells(AncientLichSpells); } } else if (entityType == EntityTypes.EnemyClass && (mobileEnemy.CastsMagic)) { int spellListLevel = level / 3; if (spellListLevel > 6) { spellListLevel = 6; } SetEnemySpells(EnemyClassSpells[spellListLevel]); } FillVitalSigns(); // Could use this to set enemies health and other vitals at a lower level when they first spawn, to simulate them being already wounded or something. }
/// <summary> /// Sets enemy career and prepares entity settings. /// </summary> public void SetEnemyCareer(MobileEnemy mobileEnemy, EntityTypes entityType) { if (entityType == EntityTypes.EnemyMonster) { careerIndex = (int)mobileEnemy.ID; career = GetMonsterCareerTemplate((MonsterCareers)careerIndex); stats.SetFromCareer(career); // Enemy monster has predefined level, health and armor values. // Armor values can be modified below by equipment. level = mobileEnemy.Level; maxHealth = UnityEngine.Random.Range(mobileEnemy.MinHealth, mobileEnemy.MaxHealth + 1); for (int i = 0; i < ArmorValues.Length; i++) { ArmorValues[i] = (sbyte)(mobileEnemy.ArmorValue * 5); } } else if (entityType == EntityTypes.EnemyClass) { careerIndex = (int)mobileEnemy.ID - 128; career = GetClassCareerTemplate((ClassCareers)careerIndex); stats.SetFromCareer(career); // Enemy class is levelled to player and uses similar health rules level = GameManager.Instance.PlayerEntity.Level; maxHealth = FormulaHelper.RollEnemyClassMaxHealth(level, career.HitPointsPerLevel); } else { career = new DFCareer(); careerIndex = -1; return; } this.mobileEnemy = mobileEnemy; this.entityType = entityType; name = career.Name; minMetalToHit = mobileEnemy.MinMetalToHit; short skillsLevel = (short)((level * 5) + 30); if (skillsLevel > 100) { skillsLevel = 100; } for (int i = 0; i <= DaggerfallSkills.Count; i++) { skills.SetSkillValue(i, skillsLevel); } // Enemy classes and some monsters use equipment if (careerIndex == (int)MonsterCareers.Orc || careerIndex == (int)MonsterCareers.OrcShaman) { SetEnemyEquipment(0); } else if (careerIndex == (int)MonsterCareers.Centaur || careerIndex == (int)MonsterCareers.OrcSergeant) { SetEnemyEquipment(1); } else if (careerIndex == (int)MonsterCareers.OrcWarlord) { SetEnemyEquipment(2); } else if (entityType == EntityTypes.EnemyClass) { SetEnemyEquipment(UnityEngine.Random.Range(0, 2)); // 0 or 1 } FillVitalSigns(); }
/// <summary> /// Gets enemy definition based on name. /// Runs a brute force search for ID, so use sparingly. /// </summary> /// <param name="name">Enemy name to extract definition.</param> /// <param name="mobileEnemyOut">Receives details of enemy type if found.</param> /// <returns>True if successful.</returns> public static bool GetEnemy(string name, out MobileEnemy mobileEnemyOut) { for (int i = 0; i < Enemies.Length; i++) { if (0 == string.Compare(Enemies[i].Name, name, StringComparison.InvariantCultureIgnoreCase)) { mobileEnemyOut = Enemies[i]; return true; } } // No match found, just return an empty definition mobileEnemyOut = new MobileEnemy(); return false; }
/// <summary> /// Gets enemy definition based on type. /// Runs a brute force search for ID, so use sparingly. /// Store a dictionary from GetEnemyDict() for faster lookups. /// </summary> /// <param name="enemyType">Enemy type to extract definition.</param> /// <param name="mobileEnemyOut">Receives details of enemy type.</param> /// <returns>True if successful.</returns> public static bool GetEnemy(MobileTypes enemyType, out MobileEnemy mobileEnemyOut) { // Cast type enum to ID. // You can add additional IDs to enum to create new enemies. int id = (int)enemyType; // Search for matching definition in enemy list. // Don't forget to add new enemy IDs to Enemies definition array. for (int i = 0; i < Enemies.Length; i++) { if (Enemies[i].ID == id) { mobileEnemyOut = Enemies[i]; return true; } } // No match found, just return an empty definition mobileEnemyOut = new MobileEnemy(); return false; }
/// <summary> /// Sets up enemy based on current settings. /// </summary> public void ApplyEnemySettings(MobileGender gender) { DaggerfallUnity dfUnity = DaggerfallUnity.Instance; Dictionary <int, MobileEnemy> enemyDict = GameObjectHelper.EnemyDict; MobileEnemy mobileEnemy = enemyDict[(int)EnemyType]; // Find mobile unit in children DaggerfallMobileUnit dfMobile = GetMobileBillboardChild(); if (dfMobile != null) { // Setup mobile billboard Vector2 size = Vector2.one; mobileEnemy.Gender = gender; dfMobile.SetEnemy(dfUnity, mobileEnemy, EnemyReaction); // Setup controller CharacterController controller = GetComponent <CharacterController>(); if (controller) { // Set base height from sprite size = dfMobile.Summary.RecordSizes[0]; controller.height = size.y; // Reduce height of flying creatures as their wing animation makes them taller than desired // This helps them get through doors while aiming for player eye height if (dfMobile.Summary.Enemy.Behaviour == MobileBehaviour.Flying) { controller.height /= 2f; } // Uncomment below lines to limit maximum controller height // Some particularly tall sprites (e.g. giants) require this hack to get through doors // However they will appear sunken into ground as a result //if (controller.height > 1.9f) // controller.height = 1.9f; controller.gameObject.layer = LayerMask.NameToLayer("Enemies"); } // Setup sounds EnemySounds enemySounds = GetComponent <Game.EnemySounds>(); if (enemySounds) { enemySounds.MoveSound = (SoundClips)dfMobile.Summary.Enemy.MoveSound; enemySounds.BarkSound = (SoundClips)dfMobile.Summary.Enemy.BarkSound; enemySounds.AttackSound = (SoundClips)dfMobile.Summary.Enemy.AttackSound; } // Setup entity if (entityBehaviour) { EnemyEntity entity = new EnemyEntity(); entityBehaviour.Entity = entity; int enemyIndex = (int)EnemyType; if (enemyIndex >= 0 && enemyIndex <= 42) { entityBehaviour.EntityType = EntityTypes.EnemyMonster; entity.SetEnemyCareer(mobileEnemy, entityBehaviour.EntityType); } else if (enemyIndex >= 128 && enemyIndex <= 146) { entityBehaviour.EntityType = EntityTypes.EnemyClass; entity.SetEnemyCareer(mobileEnemy, entityBehaviour.EntityType); } else { entityBehaviour.EntityType = EntityTypes.None; } } } }
/// <summary> /// Sets up enemy based on current settings. /// </summary> public void ApplyEnemySettings(MobileGender gender) { DaggerfallUnity dfUnity = DaggerfallUnity.Instance; Dictionary <int, MobileEnemy> enemyDict = GameObjectHelper.EnemyDict; MobileEnemy mobileEnemy = enemyDict[(int)EnemyType]; if (AlliedToPlayer) { mobileEnemy.Team = MobileTeams.PlayerAlly; } // Find mobile unit in children MobileUnit dfMobile = GetMobileBillboardChild(); if (dfMobile != null) { // Setup mobile billboard Vector2 size = Vector2.one; mobileEnemy.Gender = gender; mobileEnemy.Reactions = EnemyReaction; dfMobile.SetEnemy(dfUnity, mobileEnemy, EnemyReaction, ClassicSpawnDistanceType); // Setup controller CharacterController controller = GetComponent <CharacterController>(); if (controller) { // Set base height from sprite size = dfMobile.GetSize(); controller.height = size.y; // Reduce height of flying creatures as their wing animation makes them taller than desired // This helps them get through doors while aiming for player eye height if (dfMobile.Enemy.Behaviour == MobileBehaviour.Flying) { // (in frame 0 wings are in high position, assume body is the lower half) AdjustControllerHeight(controller, controller.height / 2, ControllerJustification.BOTTOM); } // Limit minimum controller height // Stops very short characters like rats from being walked upon if (controller.height < 1.6f) { AdjustControllerHeight(controller, 1.6f, ControllerJustification.BOTTOM); } controller.gameObject.layer = LayerMask.NameToLayer("Enemies"); } // Setup sounds EnemySounds enemySounds = GetComponent <Game.EnemySounds>(); if (enemySounds) { enemySounds.MoveSound = (SoundClips)dfMobile.Enemy.MoveSound; enemySounds.BarkSound = (SoundClips)dfMobile.Enemy.BarkSound; enemySounds.AttackSound = (SoundClips)dfMobile.Enemy.AttackSound; } MeshRenderer meshRenderer = dfMobile.GetComponent <MeshRenderer>(); if (meshRenderer) { if (dfMobile.Enemy.Behaviour == MobileBehaviour.Spectral) { meshRenderer.material.shader = Shader.Find(MaterialReader._DaggerfallGhostShaderName); meshRenderer.material.SetFloat("_Cutoff", 0.1f); } if (dfMobile.Enemy.NoShadow) { meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; } if (dfMobile.Enemy.GlowColor != null) { meshRenderer.receiveShadows = false; GameObject enemyLightGameObject = Instantiate(LightAura); enemyLightGameObject.transform.parent = dfMobile.transform; enemyLightGameObject.transform.localPosition = new Vector3(0, 0.3f, 0.2f); Light enemyLight = enemyLightGameObject.GetComponent <Light>(); enemyLight.color = (Color)dfMobile.Enemy.GlowColor; enemyLight.shadows = DaggerfallUnity.Settings.DungeonLightShadows ? LightShadows.Soft : LightShadows.None; } } // Setup entity if (entityBehaviour) { EnemyEntity entity = new EnemyEntity(entityBehaviour); entityBehaviour.Entity = entity; // Enemies are initially added to same world context as player entity.WorldContext = GameManager.Instance.PlayerEnterExit.WorldContext; int enemyIndex = (int)EnemyType; if (enemyIndex >= 0 && enemyIndex <= 42) { entityBehaviour.EntityType = EntityTypes.EnemyMonster; entity.SetEnemyCareer(mobileEnemy, entityBehaviour.EntityType); } else if (enemyIndex >= 128 && enemyIndex <= 146) { entityBehaviour.EntityType = EntityTypes.EnemyClass; entity.SetEnemyCareer(mobileEnemy, entityBehaviour.EntityType); } else { entityBehaviour.EntityType = EntityTypes.None; } } // Add special behaviour for Daedra Seducer mobiles if (dfMobile.Enemy.ID == (int)MobileTypes.DaedraSeducer) { dfMobile.gameObject.AddComponent <DaedraSeducerMobileBehaviour>(); } } }
/// <summary> /// Sets enemy career and prepares entity settings. /// </summary> public void SetEnemyCareer(MobileEnemy mobileEnemy, EntityTypes entityType) { // Try custom career first career = GetCustomCareerTemplate(mobileEnemy.ID); if (career != null) { // Custom enemy careerIndex = mobileEnemy.ID; stats.SetPermanentFromCareer(career); if (entityType == EntityTypes.EnemyMonster) { // Default like a monster level = mobileEnemy.Level; maxHealth = Random.Range(mobileEnemy.MinHealth, mobileEnemy.MaxHealth + 1); for (int i = 0; i < ArmorValues.Length; i++) { ArmorValues[i] = (sbyte)(mobileEnemy.ArmorValue * 5); } } else { // Default like a class enemy level = GameManager.Instance.PlayerEntity.Level; maxHealth = FormulaHelper.RollEnemyClassMaxHealth(level, career.HitPointsPerLevel); } } else if (entityType == EntityTypes.EnemyMonster) { careerIndex = mobileEnemy.ID; career = GetMonsterCareerTemplate((MonsterCareers)careerIndex); stats.SetPermanentFromCareer(career); // Enemy monster has predefined level, health and armor values. // Armor values can be modified below by equipment. level = mobileEnemy.Level; maxHealth = UnityEngine.Random.Range(mobileEnemy.MinHealth, mobileEnemy.MaxHealth + 1); for (int i = 0; i < ArmorValues.Length; i++) { ArmorValues[i] = (sbyte)(mobileEnemy.ArmorValue * 5); } } else if (entityType == EntityTypes.EnemyClass) { careerIndex = mobileEnemy.ID - 128; career = GetClassCareerTemplate((ClassCareers)careerIndex); stats.SetPermanentFromCareer(career); // Enemy class is levelled to player and uses similar health rules // City guards are 3 to 6 levels above the player level = GameManager.Instance.PlayerEntity.Level; if (careerIndex == (int)MobileTypes.Knight_CityWatch - 128) { level += UnityEngine.Random.Range(3, 7); } maxHealth = FormulaHelper.RollEnemyClassMaxHealth(level, career.HitPointsPerLevel); } else { career = new DFCareer(); careerIndex = -1; return; } this.mobileEnemy = mobileEnemy; this.entityType = entityType; name = career.Name; minMetalToHit = mobileEnemy.MinMetalToHit; team = mobileEnemy.Team; short skillsLevel = (short)((level * 5) + 30); if (skillsLevel > 100) { skillsLevel = 100; } for (int i = 0; i <= DaggerfallSkills.Count; i++) { skills.SetPermanentSkillValue(i, skillsLevel); } // Generate loot table items DaggerfallLoot.GenerateItems(mobileEnemy.LootTableKey, items); // Enemy classes and some monsters use equipment if (careerIndex == (int)MonsterCareers.Orc || careerIndex == (int)MonsterCareers.OrcShaman) { SetEnemyEquipment(0); } else if (careerIndex == (int)MonsterCareers.Centaur || careerIndex == (int)MonsterCareers.OrcSergeant) { SetEnemyEquipment(1); } else if (careerIndex == (int)MonsterCareers.OrcWarlord) { SetEnemyEquipment(2); } else if (entityType == EntityTypes.EnemyClass) { SetEnemyEquipment(UnityEngine.Random.Range(0, 2)); // 0 or 1 } // Assign spell lists if (entityType == EntityTypes.EnemyMonster) { if (careerIndex == (int)MonsterCareers.Imp) { SetEnemySpells(ImpSpells); } else if (careerIndex == (int)MonsterCareers.Ghost) { SetEnemySpells(GhostSpells); } else if (careerIndex == (int)MonsterCareers.OrcShaman) { SetEnemySpells(OrcShamanSpells); } else if (careerIndex == (int)MonsterCareers.Wraith) { SetEnemySpells(WraithSpells); } else if (careerIndex == (int)MonsterCareers.FrostDaedra) { SetEnemySpells(FrostDaedraSpells); } else if (careerIndex == (int)MonsterCareers.FireDaedra) { SetEnemySpells(FireDaedraSpells); } else if (careerIndex == (int)MonsterCareers.Daedroth) { SetEnemySpells(DaedrothSpells); } else if (careerIndex == (int)MonsterCareers.Vampire) { SetEnemySpells(VampireSpells); } else if (careerIndex == (int)MonsterCareers.DaedraSeducer) { SetEnemySpells(SeducerSpells); } else if (careerIndex == (int)MonsterCareers.VampireAncient) { SetEnemySpells(VampireAncientSpells); } else if (careerIndex == (int)MonsterCareers.DaedraLord) { SetEnemySpells(DaedraLordSpells); } else if (careerIndex == (int)MonsterCareers.Lich) { SetEnemySpells(LichSpells); } else if (careerIndex == (int)MonsterCareers.AncientLich) { SetEnemySpells(AncientLichSpells); } } else if (entityType == EntityTypes.EnemyClass && (mobileEnemy.CastsMagic)) { int spellListLevel = level / 3; if (spellListLevel > 6) { spellListLevel = 6; } SetEnemySpells(EnemyClassSpells[spellListLevel]); } // Chance of adding map DaggerfallLoot.RandomlyAddMap(mobileEnemy.MapChance, items); if (!string.IsNullOrEmpty(mobileEnemy.LootTableKey)) { // Chance of adding potion DaggerfallLoot.RandomlyAddPotion(3, items); // Chance of adding potion recipe DaggerfallLoot.RandomlyAddPotionRecipe(2, items); } OnLootSpawned?.Invoke(this, new EnemyLootSpawnedEventArgs { MobileEnemy = mobileEnemy, EnemyCareer = career, Items = items }); FillVitalSigns(); }
/// <summary> /// Sets up enemy based on current settings. /// </summary> public void ApplyEnemySettings(MobileGender gender) { DaggerfallUnity dfUnity = DaggerfallUnity.Instance; Dictionary <int, MobileEnemy> enemyDict = GameObjectHelper.EnemyDict; MobileEnemy mobileEnemy = enemyDict[(int)EnemyType]; // Find mobile unit in children DaggerfallMobileUnit dfMobile = GetMobileBillboardChild(); if (dfMobile != null) { // Setup mobile billboard Vector2 size = Vector2.one; mobileEnemy.Gender = gender; dfMobile.SetEnemy(dfUnity, mobileEnemy, EnemyReaction, ClassicSpawnDistanceType); // Setup controller CharacterController controller = GetComponent <CharacterController>(); if (controller) { // Set base height from sprite size = dfMobile.Summary.RecordSizes[0]; controller.height = size.y; // Reduce height of flying creatures as their wing animation makes them taller than desired // This helps them get through doors while aiming for player eye height if (dfMobile.Summary.Enemy.Behaviour == MobileBehaviour.Flying) { controller.height /= 2f; } // Limit maximum controller height // Some particularly tall sprites (e.g. giants) require this hack to get through doors if (controller.height > 1.78f) { // Adjust center so that sprite doesn't sink into the ground Vector3 newCenter = controller.center; newCenter.y += (1.78f - controller.height) / 2; controller.center = newCenter; controller.height = 1.78f; } controller.gameObject.layer = LayerMask.NameToLayer("Enemies"); } // Setup sounds EnemySounds enemySounds = GetComponent <Game.EnemySounds>(); if (enemySounds) { enemySounds.MoveSound = (SoundClips)dfMobile.Summary.Enemy.MoveSound; enemySounds.BarkSound = (SoundClips)dfMobile.Summary.Enemy.BarkSound; enemySounds.AttackSound = (SoundClips)dfMobile.Summary.Enemy.AttackSound; } // Setup entity if (entityBehaviour) { EnemyEntity entity = new EnemyEntity(entityBehaviour); entityBehaviour.Entity = entity; // Enemies are initially added to same world context as player entity.WorldContext = GameManager.Instance.PlayerEnterExit.WorldContext; int enemyIndex = (int)EnemyType; if (enemyIndex >= 0 && enemyIndex <= 42) { entityBehaviour.EntityType = EntityTypes.EnemyMonster; entity.SetEnemyCareer(mobileEnemy, entityBehaviour.EntityType); } else if (enemyIndex >= 128 && enemyIndex <= 146) { entityBehaviour.EntityType = EntityTypes.EnemyClass; entity.SetEnemyCareer(mobileEnemy, entityBehaviour.EntityType); } else { entityBehaviour.EntityType = EntityTypes.None; } } } }
/// <summary> /// Create a regular non-artifact magic item. /// </summary> /// <param name="chosenItem">An integer index of the item to create, or -1 for a random one.</param> /// <param name="playerLevel">The player level to create an item for.</param> /// <param name="gender">The gender to create an item for.</param> /// <param name="race">The race to create an item for.</param> /// <returns>DaggerfallUnityItem</returns> /// <exception cref="Exception">When a base item cannot be created.</exception> public static DaggerfallUnityItem CreateRegularMagicItem(int chosenItem, int playerLevel, Genders gender, Races race) { byte[] itemGroups0 = { 2, 3, 6, 10, 12, 14, 25 }; byte[] itemGroups1 = { 2, 3, 6, 12, 25 }; DaggerfallUnityItem newItem = null; // Get the list of magic item templates read from MAGIC.DEF MagicItemsFile magicItemsFile = new MagicItemsFile(Path.Combine(DaggerfallUnity.Instance.Arena2Path, "MAGIC.DEF")); List <MagicItemTemplate> magicItems = magicItemsFile.MagicItemsList; // Reduce the list to only the regular magic items. MagicItemTemplate[] regularMagicItems = magicItems.Where(template => template.type == MagicItemTypes.RegularMagicItem).ToArray(); if (chosenItem > regularMagicItems.Length) { throw new Exception(string.Format("Magic item subclass {0} does not exist", chosenItem)); } // Pick a random one if needed. if (chosenItem == chooseAtRandom) { chosenItem = UnityEngine.Random.Range(0, regularMagicItems.Length); } // Get the chosen template MagicItemTemplate magicItem = regularMagicItems[chosenItem]; // Get the item group. The possible groups are determined by the 33rd byte (magicItem.group) of the MAGIC.DEF template being used. ItemGroups group = 0; if (magicItem.group == 0) { group = (ItemGroups)itemGroups0[UnityEngine.Random.Range(0, 7)]; } else if (magicItem.group == 1) { group = (ItemGroups)itemGroups1[UnityEngine.Random.Range(0, 5)]; } else if (magicItem.group == 2) { group = ItemGroups.Weapons; } // Create the base item if (group == ItemGroups.Weapons) { newItem = CreateRandomWeapon(playerLevel); // No arrows as enchanted items while (newItem.GroupIndex == 18) { newItem = CreateRandomWeapon(playerLevel); } } else if (group == ItemGroups.Armor) { newItem = CreateRandomArmor(playerLevel, gender, race); } else if (group == ItemGroups.MensClothing || group == ItemGroups.WomensClothing) { newItem = CreateRandomClothing(gender, race); } else if (group == ItemGroups.ReligiousItems) { newItem = CreateRandomReligiousItem(); } else if (group == ItemGroups.Gems) { newItem = CreateRandomGem(); } else // Only other possibility is jewellery { newItem = CreateRandomJewellery(); } if (newItem == null) { throw new Exception("CreateRegularMagicItem() failed to create an item."); } // Replace the regular item name with the magic item name newItem.shortName = magicItem.name; // Add the enchantments newItem.legacyMagic = new DaggerfallEnchantment[magicItem.enchantments.Length]; for (int i = 0; i < magicItem.enchantments.Length; ++i) { newItem.legacyMagic[i] = magicItem.enchantments[i]; } // Set the condition/magic uses newItem.maxCondition = magicItem.uses; newItem.currentCondition = magicItem.uses; // Set the value of the item. This is determined by the enchantment point cost/spell-casting cost // of the enchantments on the item. int value = 0; for (int i = 0; i < magicItem.enchantments.Length; ++i) { if (magicItem.enchantments[i].type != EnchantmentTypes.None && magicItem.enchantments[i].type < EnchantmentTypes.ItemDeteriorates) { switch (magicItem.enchantments[i].type) { case EnchantmentTypes.CastWhenUsed: case EnchantmentTypes.CastWhenHeld: case EnchantmentTypes.CastWhenStrikes: // Enchantments that cast a spell. The parameter is the spell index in SPELLS.STD. value += Formulas.FormulaHelper.GetSpellEnchantPtCost(magicItem.enchantments[i].param); break; case EnchantmentTypes.RepairsObjects: case EnchantmentTypes.AbsorbsSpells: case EnchantmentTypes.EnhancesSkill: case EnchantmentTypes.FeatherWeight: case EnchantmentTypes.StrengthensArmor: // Enchantments that provide an effect that has no parameters value += enchantmentPointCostsForNonParamTypes[(int)magicItem.enchantments[i].type]; break; case EnchantmentTypes.SoulBound: // Bound soul MobileEnemy mobileEnemy = GameObjectHelper.EnemyDict[magicItem.enchantments[i].param]; value += mobileEnemy.SoulPts; // TODO: Not sure about this. Should be negative? Needs to be tested. break; default: // Enchantments that provide a non-spell effect with a parameter (parameter = when effect applies, what enemies are affected, etc.) value += enchantmentPtsForItemPowerArrays[(int)magicItem.enchantments[i].type][magicItem.enchantments[i].param]; break; } } } newItem.value = value; return(newItem); }
/// <summary> /// Sets enemy career and prepares entity settings. /// </summary> public void SetEnemyCareer(MobileEnemy mobileEnemy, EntityTypes entityType) { if (entityType == EntityTypes.EnemyMonster) { careerIndex = (int)mobileEnemy.ID; career = GetMonsterCareerTemplate((MonsterCareers)careerIndex); stats.SetFromCareer(career); // Enemy monster has predefined level, health and armor values level = mobileEnemy.Level; maxHealth = UnityEngine.Random.Range(mobileEnemy.MinHealth, mobileEnemy.MaxHealth + 1); // Monsters have the same armor value for all body parts for (int i = 0; i < ArmorValues.Length; i++) { ArmorValues[i] = (sbyte)(mobileEnemy.ArmorValue * 5); } } else if (entityType == EntityTypes.EnemyClass) { careerIndex = (int)mobileEnemy.ID - 128; career = GetClassCareerTemplate((ClassCareers)careerIndex); stats.SetFromCareer(career); // Enemy class is levelled to player and uses similar health rules level = GameManager.Instance.PlayerEntity.Level; maxHealth = FormulaHelper.RollEnemyClassMaxHealth(level, career.HitPointsPerLevel); // Enemy classes may be able to equip armor. Not sure yet how classic does this. // For now, using fudge value of 60. for (int i = 0; i < ArmorValues.Length; i++) { ArmorValues[i] = 60; } // Enemy class damage is temporarily set by a fudged level multiplier // This will change once full entity setup and items are available const float damageMultiplier = 4f; mobileEnemy.MinDamage = (int)(level * damageMultiplier); mobileEnemy.MaxDamage = (int)((level + 2) * damageMultiplier); } else { career = new DFCareer(); careerIndex = -1; return; } this.mobileEnemy = mobileEnemy; this.entityType = entityType; name = career.Name; short skillsLevel = (short)((level * 5) + 30); if (skillsLevel > 100) { skillsLevel = 100; } for (int i = 0; i <= DaggerfallSkills.Count; i++) { skills.SetSkillValue(i, skillsLevel); } FillVitalSigns(); }