Skip to content

Commit

Permalink
Merge pull request #2163 from KABoissonneault/feat/custom-enemy
Browse files Browse the repository at this point in the history
Modding new enemies
  • Loading branch information
Interkarma committed Jul 18, 2021
2 parents 5ec48d6 + 20b9aab commit 6730f5f
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 7 deletions.
Expand Up @@ -443,7 +443,7 @@ public static string Execute(params string[] args)
if (!int.TryParse(args[0], out id))
return "Invalid mobile ID.";

if (!Enum.IsDefined(typeof(MobileTypes), id))
if (!Enum.IsDefined(typeof(MobileTypes), id) && DaggerfallEntity.GetCustomCareerTemplate(id) == null)
return "Invalid mobile ID.";

int team = 0;
Expand Down
32 changes: 32 additions & 0 deletions Assets/Scripts/Game/Entities/DaggerfallEntity.cs
Expand Up @@ -929,6 +929,38 @@ public static DFCareer GetMonsterCareerTemplate(MonsterCareers career)
return monsterFile.GetMonsterClass((int)career);
}

/// <summary>
/// Allows mods to register a DFCareer template for IDs outside of the values in MobileTypes
/// </summary>
static readonly Dictionary<int, DFCareer> CustomCareerTemplates = new Dictionary<int, DFCareer>();

/// <summary>
/// Gets the career template for a custom (ie: mod-provided) enemy type
/// </summary>
/// <param name="enemyId">ID, as defined in EnemyBasics.Enemies</param>
/// <returns>The custom DFCareer template registered for this id, or null</returns>
public static DFCareer GetCustomCareerTemplate(int enemyId)
{
if (!CustomCareerTemplates.TryGetValue(enemyId, out DFCareer value))
{
return null;
}

return value;
}

/// <summary>
/// Sets the career template for a custom (ie: mod-provided) enemy type.
/// </summary>
/// <param name="enemyId">ID, as defined in EnemyBasics.Enemies</param>
/// <param name="career">The custom DFCareer template to register</param>
public static void RegisterCustomCareerTemplate(int enemyId, DFCareer career)
{
// Use indexer so that mods can overwrite previous values added by mods
// ex: mod 1 provides new enemies, mod 2 rebalances them
CustomCareerTemplates[enemyId] = career;
}

public static SoundClips GetRaceGenderAttackSound(Races race, Genders gender, bool isPlayerAttack = false)
{
// Check for racial override attack sound for player only
Expand Down
28 changes: 27 additions & 1 deletion Assets/Scripts/Game/Entities/EnemyEntity.cs
Expand Up @@ -246,7 +246,33 @@ public override void ClearConstantEffects()
/// </summary>
public void SetEnemyCareer(MobileEnemy mobileEnemy, EntityTypes entityType)
{
if (entityType == EntityTypes.EnemyMonster)
// 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);
Expand Down
17 changes: 17 additions & 0 deletions Assets/Scripts/Game/SetupDemoEnemy.cs
Expand Up @@ -167,6 +167,23 @@ public void ApplyEnemySettings(MobileGender gender)
entityBehaviour.EntityType = EntityTypes.EnemyClass;
entity.SetEnemyCareer(mobileEnemy, entityBehaviour.EntityType);
}
else if (DaggerfallEntity.GetCustomCareerTemplate(enemyIndex) != null)
{
// For custom enemies, we use the 7th bit to tell whether a class or monster was intended
// 0-127 is monster
// 128-255 is class
// 256-383 is monster again
// etc
if ((enemyIndex & 128) != 0)
{
entityBehaviour.EntityType = EntityTypes.EnemyClass;
}
else
{
entityBehaviour.EntityType = EntityTypes.EnemyMonster;
}
entity.SetEnemyCareer(mobileEnemy, entityBehaviour.EntityType);
}
else
{
entityBehaviour.EntityType = EntityTypes.None;
Expand Down
27 changes: 22 additions & 5 deletions Assets/Scripts/Game/TextManager.cs
Expand Up @@ -18,6 +18,7 @@
using UnityEngine.Localization.Settings;
using Wenzil.Console;
using DaggerfallWorkshop.Game.UserInterface;
using DaggerfallWorkshop.Game.Entity;
using UnityEngine.Localization.Tables;

namespace DaggerfallWorkshop.Game
Expand Down Expand Up @@ -331,15 +332,31 @@ public string[] GetLocalizedTextList(string key, TextCollections collection = Te
/// <summary>
/// Gets display name of an enemy from their ID.
/// </summary>
/// <param name="enemyID">ID of enemy. Valid IDs are 0-42 and 128-146.</param>
/// <param name="enemyID">ID of enemy. Valid IDs are 0-42 and 128-146, or values registered in Daggerfallentity.CustomCareerTemplates</param>
/// <returns>Name of enemy from localization if found, or exception if not found.</returns>
public string GetLocalizedEnemyName(int enemyID)
{
string[] enemyNames = GetLocalizedTextList("enemyNames", exception:true);
if (enemyID < 128)
return enemyNames[enemyID];
if (Enum.IsDefined(typeof(MobileTypes), (MobileTypes)enemyID))
{
string[] enemyNames = GetLocalizedTextList("enemyNames", exception: true);
if (enemyID < 128)
return enemyNames[enemyID];
else
return enemyNames[43 + enemyID - 128];
}
// Handle custom enemies
else
return enemyNames[43 + enemyID - 128];
{
// TODO: maybe go through TextProvider so mods can try to offer localization?
DaggerfallConnect.DFCareer career = DaggerfallEntity.GetCustomCareerTemplate(enemyID);
if (career == null)
{
Debug.LogError($"Enemy ID '{enemyID}' did not have a registered custom career template");
return "(invalid enemy)";
}

return career.Name;
}
}

#endregion
Expand Down

0 comments on commit 6730f5f

Please sign in to comment.