Skip to content

Commit

Permalink
Minor combat improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
csinkers committed Sep 30, 2024
1 parent 764a5c8 commit b9009e7
Show file tree
Hide file tree
Showing 13 changed files with 248 additions and 62 deletions.
13 changes: 10 additions & 3 deletions src/Api/Eventing/EventPartParsers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ MethodInfo GetParseMethod(Type type)

if (type.IsEnum)
{
var methodName = ApiUtil.IsFlagsEnum(type) ? "ParseFlags" : "ParseEnum";
var methodName = ApiUtil.IsFlagsEnum(type) ? nameof(ParseFlags) : nameof(ParseEnum);
var method = GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static);
if (method == null)
throw new InvalidOperationException("Method ParseEnum could not be found");
throw new InvalidOperationException($"Method {nameof(ParseEnum)} could not be found");

parser = method.MakeGenericMethod(type);
}
Expand All @@ -79,7 +79,14 @@ MethodInfo GetParseMethod(Type type)
// Methods called via reflection
// ReSharper disable UnusedMember.Local
#pragma warning disable IDE0051 // Remove unused private members
static T ParseEnum<T>(string s) => (T)Enum.Parse(typeof(T), s, true);
static T ParseEnum<T>(string s)
{
if (!Enum.TryParse(typeof(T), s, true, out var result))
throw new FormatException("No value supplied for required parameter");

return (T)result;
}

static T? ParseNullableEnum<T>(string s) where T : struct, Enum => string.IsNullOrEmpty(s) ? null : (T?)Enum.Parse(typeof(T), s, true);
static T ParseFlags<T>(string s) where T : unmanaged, Enum
{
Expand Down
4 changes: 2 additions & 2 deletions src/Editor/CombatAttributeEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ public override void Render()
UInt16Slider(nameof(_combat.LifePoints ), _combat.LifePoints.Current, 0, _combat.LifePoints.Max);
UInt16Slider(nameof(_combat.LifePoints.Max ), _combat.LifePoints.Max , 0, ushort.MaxValue);
UInt8Slider(nameof(_combat.ActionPoints ), _combat.ActionPoints , 0, byte.MaxValue);
UInt16Slider(nameof(_combat.UnknownD6 ), _combat.UnknownD6 , 0, 100);
UInt16Slider(nameof(_combat.UnknownD8 ), _combat.UnknownD8 , 0, 100);
UInt16Slider(nameof(_combat.BaseDefense ), _combat.BaseDefense , 0, 100);
UInt16Slider(nameof(_combat.BonusDefense ), _combat.BonusDefense , 0, 100);
EnumCheckboxes(nameof(_combat.Conditions), _combat.Conditions);
}
}
33 changes: 18 additions & 15 deletions src/Formats/Assets/CharacterSheet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,14 @@ public string GetName(string language)

// Pending further reversing
// ReSharper disable InconsistentNaming
public byte Unknown6 { get; set; }
public byte Unknown7 { get; set; }
public byte NumberOfOccupiedHands { get; set; }
public byte NumberOfOccupiedFingers { get; set; }
public byte UnkownC { get; set; }
public byte UnkownD { get; set; }
public byte UnknownE { get; set; }
public ushort Unknown1C { get; set; }
public ushort Unknown22 { get; set; }
public byte[] UnusedBlock { get; set; } // Only non-zero for the NPC "Konny"
public ushort UnknownDA { get; set; }
public ushort UnknownDC { get; set; }
public ushort UnknownDE { get; set; }
public ushort UnknownE0 { get; set; }
public ushort UnknownE8 { get; set; }
Expand Down Expand Up @@ -189,9 +187,12 @@ public static CharacterSheet Serdes(SheetId id, CharacterSheet sheet, AssetMappi
sheet.PlayerClass = s.EnumU8(nameof(sheet.PlayerClass), sheet.PlayerClass); // 3
sheet.Magic.SpellClasses = s.EnumU8(nameof(sheet.Magic.SpellClasses), sheet.Magic.SpellClasses); // 4
sheet.Level = s.UInt8(nameof(sheet.Level), sheet.Level); // 5
sheet.Unknown6 = s.UInt8(nameof(sheet.Unknown6), sheet.Unknown6); // 6 takes values [0..2] except Rainer, with 255. All other party members except Siobhan are 1. Monsters are mix of 1 and 2.
sheet.Unknown7 = s.UInt8(nameof(sheet.Unknown7), sheet.Unknown7); // 7 (always 0)
sheet.Languages = s.EnumU8(nameof(sheet.Languages), sheet.Languages); //8

// Takes values [0..2] except Rainer, with 255. All other party members except Siobhan are 1. Monsters are mix of 1 and 2.
// Based on Ambermoon, this is probably 'Number of occupied hands'
sheet.NumberOfOccupiedHands = s.UInt8(nameof(sheet.NumberOfOccupiedHands), sheet.NumberOfOccupiedHands);
sheet.NumberOfOccupiedFingers = s.UInt8(nameof(sheet.NumberOfOccupiedFingers), sheet.NumberOfOccupiedFingers); // 7 (always 0) Number of occupied fingers? (ref Ambermoon)
sheet.Languages = s.EnumU8(nameof(sheet.Languages), sheet.Languages); // 8

sheet.SpriteId = sheet.Type switch // 9
{
Expand All @@ -206,16 +207,18 @@ public static CharacterSheet Serdes(SheetId id, CharacterSheet sheet, AssetMappi
// Only monster graphics if monster, means something else for party members (matches PartyMemberId). Never set for NPCs.
sheet.MonsterGfxId = SpriteId.SerdesU8(nameof(sheet.MonsterGfxId), sheet.MonsterGfxId, AssetType.MonsterGfx, mapping, s); // B

sheet.UnkownC = s.UInt8(nameof(sheet.UnkownC), sheet.UnkownC); // C takes values [0..9], only non-zero for monsters & Drirr. Distribution of non-zero values: 42, 3, 4, 1, 1, 1, 4, 3, 1
sheet.UnkownC = s.UInt8(nameof(sheet.UnkownC), sheet.UnkownC); // C takes values [0..9], only non-zero for monsters & Drirr. Distribution of non-zero values: 42, 3, 4, 1, 1, 1, 4, 3, 1
sheet.UnkownD = s.UInt8(nameof(sheet.UnkownD), sheet.UnkownD); // D takes values [0..4], only non-zero for monsters & Drirr. Distribution of non-zero values: 46, 3, 4, 3

// E takes values 1,2,10,20,130,138,186. Only non-zero for monsters & party members. All party members use 2. Distr: 23, 15, 3, 3, 1, 2, 12. Flags?
// Basic mobs=1. AiBody1,Argim=2. all demons=20. human/iskai mobs=10. Ai,Ai2,AiBody22=130. Beastmaster,Nodd,Kontos=138. Kamulos=186.
// Maybe 'magic bonus to hit'? (ref Ambermoon)
sheet.UnknownE = s.UInt8(nameof(sheet.UnknownE), sheet.UnknownE);

sheet.Morale = s.UInt8(nameof(sheet.Morale), sheet.Morale); // F [0..100]
sheet.SpellTypeImmunities = s.EnumU8(nameof(sheet.SpellTypeImmunities), sheet.SpellTypeImmunities); // 10 spell type immunities? Always 0
sheet.Combat.ActionPoints = s.UInt8(nameof(sheet.Combat.ActionPoints), sheet.Combat.ActionPoints); // 11

sheet.EventSetId = EventSetId.SerdesU16(nameof(sheet.EventSetId), sheet.EventSetId, mapping, s); // 12
sheet.WordSetId = EventSetId.SerdesU16(nameof(sheet.WordSetId), sheet.WordSetId, mapping, s); // 14
sheet.Combat.TrainingPoints = s.UInt16(nameof(sheet.Combat.TrainingPoints), sheet.Combat.TrainingPoints); // 16
Expand Down Expand Up @@ -260,18 +263,18 @@ public static CharacterSheet Serdes(SheetId id, CharacterSheet sheet, AssetMappi
sheet.Magic.SpellPoints = CharacterAttribute.Serdes(nameof(sheet.Magic.SpellPoints), sheet.Magic.SpellPoints, s, false); // D0

// Expect variable protection, base protection, variable attack, base attack
sheet.Combat.UnknownD6 = s.UInt16(nameof(sheet.Combat.UnknownD6), sheet.Combat.UnknownD6); // D6
sheet.Combat.UnknownD8 = s.UInt16(nameof(sheet.Combat.UnknownD8), sheet.Combat.UnknownD8); // D8
sheet.UnknownDA = s.UInt16(nameof(sheet.UnknownDA), sheet.UnknownDA); // DA
sheet.UnknownDC = s.UInt16(nameof(sheet.UnknownDC), sheet.UnknownDC); // DC
sheet.UnknownDE = s.UInt16(nameof(sheet.UnknownDE), sheet.UnknownDE); // DE always 0 in initial data
sheet.UnknownE0 = s.UInt16(nameof(sheet.UnknownE0), sheet.UnknownE0); // E0 always 0 in initial data
sheet.Combat.BaseDefense = s.UInt16(nameof(sheet.Combat.BaseDefense), sheet.Combat.BaseDefense); // D6
sheet.Combat.BonusDefense = s.UInt16(nameof(sheet.Combat.BonusDefense), sheet.Combat.BonusDefense); // D8
sheet.Combat.BaseAttack = s.UInt16(nameof(sheet.Combat.BaseAttack), sheet.Combat.BaseAttack); // DA
sheet.Combat.BonusAttack = s.UInt16(nameof(sheet.Combat.BonusAttack), sheet.Combat.BonusAttack); // DC
sheet.UnknownDE = s.UInt16(nameof(sheet.UnknownDE), sheet.UnknownDE); // DE always 0 in initial data. Magic attack level? (ref Ambermoon)
sheet.UnknownE0 = s.UInt16(nameof(sheet.UnknownE0), sheet.UnknownE0); // E0 always 0 in initial data. Magic defense level? (ref Ambermoon)
sheet.LevelsPerActionPoint = s.UInt16(nameof(sheet.LevelsPerActionPoint), sheet.LevelsPerActionPoint); // E2
sheet.LifePointsPerLevel = s.UInt16(nameof(sheet.LifePointsPerLevel), sheet.LifePointsPerLevel); // E4
sheet.SpellPointsPerLevel = s.UInt16(nameof(sheet.SpellPointsPerLevel), sheet.SpellPointsPerLevel); // E6
sheet.UnknownE8 = s.UInt16(nameof(sheet.UnknownE8), sheet.UnknownE8); // E8 Spell learning points? Only set for some Iskai monsters
sheet.TrainingPointsPerLevel = s.UInt16(nameof(sheet.TrainingPointsPerLevel), sheet.TrainingPointsPerLevel); // EA
sheet.UnknownEC = s.UInt16(nameof(sheet.UnknownEC), sheet.UnknownEC); // EC
sheet.UnknownEC = s.UInt16(nameof(sheet.UnknownEC), sheet.UnknownEC); // EC, ref Ambermoon: Text index for looking at character

sheet.Combat.ExperiencePoints = s.Int32(nameof(sheet.Combat.ExperiencePoints), sheet.Combat.ExperiencePoints); // EE
// e.g. 98406 = 0x18066 => 6680 0100 in file
Expand Down
4 changes: 2 additions & 2 deletions src/Formats/Assets/CombatAnimationId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ public enum CombatAnimationId // From Ambermoon, still need to check/confirm aga
Move = 0,
Attack = 1,
CastSpell = 2,
Unk3 = 3,
Unk3 = 3, // Ranged attack?
TakeDamage = 4,
Unk5 = 5,
Unk5 = 5, // Death?
Initial = 6, // Played at start of combat
Unk7 = 7
}
12 changes: 7 additions & 5 deletions src/Formats/Assets/CombatAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ public class CombatAttributes : ICombatAttributes

[DiagEdit(Style = DiagEditStyle.NumericInput, Min = 0)]
public byte ActionPoints { get; set; }
public ushort UnknownD6 { get; set; }
public ushort UnknownD8 { get; set; }
public ushort BaseDefense { get; set; } // Intrinsic defense (monsters only?)
public ushort BonusDefense { get; set; } // Due to equipment
public ushort BaseAttack { get; set; } // Intrinsic damage (monsters only?)
public ushort BonusAttack { get; set; } // Due to equipment

[DiagEdit(Style = DiagEditStyle.Checkboxes)]
public PlayerConditions Conditions { get; set; }
Expand All @@ -32,11 +34,11 @@ public CombatAttributes CopyFrom(CombatAttributes other)
TrainingPoints = other.TrainingPoints;
LifePoints = other.LifePoints.DeepClone();
ActionPoints = other.ActionPoints;
UnknownD6 = other.UnknownD6;
UnknownD8 = other.UnknownD8;
BaseDefense = other.BaseDefense;
BonusDefense = other.BonusDefense;
Conditions = other.Conditions;
return this;
}

public override string ToString() => $"XP:{ExperiencePoints} TP:{TrainingPoints} LP:{LifePoints} AP:{ActionPoints} D:{UnknownD8} P:{UnknownD6} Cond:{Conditions}";
public override string ToString() => $"XP:{ExperiencePoints} TP:{TrainingPoints} LP:{LifePoints} AP:{ActionPoints} D:{BonusDefense} P:{BaseDefense} Cond:{Conditions}";
}
4 changes: 2 additions & 2 deletions src/Formats/Assets/ICombatAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public interface ICombatAttributes
ushort TrainingPoints { get; }
ICharacterAttribute LifePoints { get; }
byte ActionPoints { get; }
ushort UnknownD6 { get; }
ushort UnknownD8 { get; }
ushort BaseDefense { get; }
ushort BonusDefense { get; }
PlayerConditions Conditions { get; }
}
184 changes: 177 additions & 7 deletions src/Formats/Assets/MonsterData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,130 @@ public class MonsterData
// Hardcoded offset of 12. This is pretty nasty, but it's how it was done in the original.
// TODO: Make this data driven if modding ever requires it.
public SpriteId TacticalGraphics => new(AssetType.TacticalGfx, MonsterGraphics.Id + 12);
public byte Unk1 { get; set; }
public byte[] Unk2 { get; set; }

public byte Unk1 { get; set; }
public byte[] Unk2 { get; set; }
public ushort Unk34 { get; set; } // Override MonsterGfx??
public ushort Unk36 { get; set; }
public uint Unk38 { get; set; }
public uint Unk3c { get; set; }
public byte[] Unk40 { get; set; }
public uint Unk58 { get; set; }
public uint Unk5c { get; set; }
public uint Unk60 { get; set; }
public uint Unk64 { get; set; }
public uint Unk68 { get; set; }
public uint Unk6c { get; set; }
public uint Unk70 { get; set; }
public uint Unk74 { get; set; }
public uint Unk78 { get; set; }
public uint Unk7c { get; set; }
public uint Unk80 { get; set; }
public uint Unk84 { get; set; }
public uint Unk88 { get; set; }
public byte[] Unk8c { get; set; }
public uint Unk98 { get; set; }
public uint Unk9c { get; set; }
public uint Unka0 { get; set; }
public uint Unka4 { get; set; }
public uint Unka8 { get; set; }
public uint Unkac { get; set; }
public uint Unkb0 { get; set; }
public uint Unkb4 { get; set; }
public uint Unkb8 { get; set; }
public uint Unkbc { get; set; }
public byte[] Unkc0 { get; set; }
public uint Unkd8 { get; set; }
public uint Unkdc { get; set; }
public uint Unke0 { get; set; }
public uint Unke4 { get; set; }
public uint Unke8 { get; set; }
public uint Unkec { get; set; }
public ulong Unkf0 { get; set; }
public uint Unkf8 { get; set; }
public uint Unkfc { get; set; }
public uint Unk100 { get; set; }
public uint Unk104 { get; set; }
public uint Unk108 { get; set; }
public byte[] Unk10c { get; set; }
public uint Unk118 { get; set; }
public uint Unk11c { get; set; }
public uint Unk120 { get; set; }
public uint Unk124 { get; set; }
public uint Unk128 { get; set; }
public byte[] Unk12c { get; set; }
public uint Unk138 { get; set; }
public uint Unk13c { get; set; }
public uint Unk140 { get; set; }
public uint Unk144 { get; set; }

public static MonsterData Serdes(MonsterData m, AssetMapping mapping, ISerializer s)
{
m ??= new MonsterData();
var initial = s.Offset;
m.MonsterGraphics = SpriteId.SerdesU8(nameof(MonsterGraphics), m.MonsterGraphics, AssetType.MonsterGfx, mapping, s);
m.Unk1 = s.UInt8(nameof(Unk1), m.Unk1);
m.Unk2 = s.Bytes(nameof(Unk2), m.Unk2, 326);

// 0x148 total length
m.Unk1 = s.UInt8(nameof(Unk1), m.Unk1);
m.Unk2 = s.Bytes(nameof(Unk2), m.Unk2, 0x32);
m.Unk34 = s.UInt16(nameof(Unk34), m.Unk34);
m.Unk36 = s.UInt16(nameof(Unk36), m.Unk36);
m.Unk38 = s.UInt32(nameof(Unk38), m.Unk38);
m.Unk3c = s.UInt32(nameof(Unk3c), m.Unk3c);
m.Unk40 = s.Bytes(nameof(Unk40), m.Unk40, 0x18);
m.Unk58 = s.UInt32(nameof(Unk58), m.Unk58);
m.Unk5c = s.UInt32(nameof(Unk5c), m.Unk5c);
m.Unk60 = s.UInt32(nameof(Unk60), m.Unk60);
m.Unk64 = s.UInt32(nameof(Unk64), m.Unk64);
m.Unk68 = s.UInt32(nameof(Unk68), m.Unk68);
m.Unk6c = s.UInt32(nameof(Unk6c), m.Unk6c);
m.Unk70 = s.UInt32(nameof(Unk70), m.Unk70);
m.Unk74 = s.UInt32(nameof(Unk74), m.Unk74);
m.Unk78 = s.UInt32(nameof(Unk78), m.Unk78);
m.Unk7c = s.UInt32(nameof(Unk7c), m.Unk7c);
m.Unk80 = s.UInt32(nameof(Unk80), m.Unk80);
m.Unk84 = s.UInt32(nameof(Unk84), m.Unk84);
m.Unk88 = s.UInt32(nameof(Unk88), m.Unk88);
m.Unk8c = s.Bytes(nameof(Unk8c), m.Unk8c, 0xc);
m.Unk98 = s.UInt32(nameof(Unk98), m.Unk98);
m.Unk9c = s.UInt32(nameof(Unk9c), m.Unk9c);
m.Unka0 = s.UInt32(nameof(Unka0), m.Unka0);
m.Unka4 = s.UInt32(nameof(Unka4), m.Unka4);
m.Unka8 = s.UInt32(nameof(Unka8), m.Unka8);
m.Unkac = s.UInt32(nameof(Unkac), m.Unkac);
m.Unkb0 = s.UInt32(nameof(Unkb0), m.Unkb0);
m.Unkb4 = s.UInt32(nameof(Unkb4), m.Unkb4);
m.Unkb8 = s.UInt32(nameof(Unkb8), m.Unkb8);
m.Unkbc = s.UInt32(nameof(Unkbc), m.Unkbc);
m.Unkc0 = s.Bytes(nameof(Unkc0), m.Unkc0, 0x18);
m.Unkd8 = s.UInt32(nameof(Unkd8), m.Unkd8);
m.Unkdc = s.UInt32(nameof(Unkdc), m.Unkdc);
m.Unke0 = s.UInt32(nameof(Unke0), m.Unke0);
m.Unke4 = s.UInt32(nameof(Unke4), m.Unke4);
m.Unke8 = s.UInt32(nameof(Unke8), m.Unke8);
m.Unkec = s.UInt32(nameof(Unkec), m.Unkec);
m.Unkf0 = s.UInt64(nameof(Unkf0), m.Unkf0);
m.Unkf8 = s.UInt32(nameof(Unkf8), m.Unkf8);
m.Unkfc = s.UInt32(nameof(Unkfc), m.Unkfc);
m.Unk100 = s.UInt32(nameof(Unk100), m.Unk100);
m.Unk104 = s.UInt32(nameof(Unk104), m.Unk104);
m.Unk108 = s.UInt32(nameof(Unk108), m.Unk108);
m.Unk10c = s.Bytes(nameof(Unk10c), m.Unk10c, 0xc);
m.Unk118 = s.UInt32(nameof(Unk118), m.Unk118);
m.Unk11c = s.UInt32(nameof(Unk11c), m.Unk11c);
m.Unk120 = s.UInt32(nameof(Unk120), m.Unk120);
m.Unk124 = s.UInt32(nameof(Unk124), m.Unk124);
m.Unk128 = s.UInt32(nameof(Unk128), m.Unk128);
m.Unk12c = s.Bytes(nameof(Unk12c), m.Unk12c, 0xc);
m.Unk138 = s.UInt32(nameof(Unk138), m.Unk138);
m.Unk13c = s.UInt32(nameof(Unk13c), m.Unk13c);
m.Unk140 = s.UInt32(nameof(Unk140), m.Unk140);
m.Unk144 = s.UInt32(nameof(Unk144), m.Unk144);

var totalLength = s.Offset - initial;
if (totalLength != 0x148)
throw new FormatException($"Expected to process 0x148 bytes, but actually got {totalLength}");

return m;
}

Expand All @@ -42,8 +157,63 @@ public MonsterData CopyFrom(MonsterData other)
ArgumentNullException.ThrowIfNull(other);

MonsterGraphics = other.MonsterGraphics;
Unk1 = other.Unk1;
Unk2 = other.Unk2.ToArray();
Unk1 = other.Unk1;
Unk2 = other.Unk2.ToArray();
Unk1 = other.Unk1;
Unk2 = other.Unk2.ToArray();
Unk34 = other.Unk34;
Unk36 = other.Unk36;
Unk38 = other.Unk38;
Unk3c = other.Unk3c;
Unk40 = other.Unk40.ToArray();
Unk58 = other.Unk58;
Unk5c = other.Unk5c;
Unk60 = other.Unk60;
Unk64 = other.Unk64;
Unk68 = other.Unk68;
Unk6c = other.Unk6c;
Unk70 = other.Unk70;
Unk74 = other.Unk74;
Unk78 = other.Unk78;
Unk7c = other.Unk7c;
Unk80 = other.Unk80;
Unk84 = other.Unk84;
Unk88 = other.Unk88;
Unk8c = other.Unk8c.ToArray();
Unk98 = other.Unk98;
Unk9c = other.Unk9c;
Unka0 = other.Unka0;
Unka4 = other.Unka4;
Unka8 = other.Unka8;
Unkac = other.Unkac;
Unkb0 = other.Unkb0;
Unkb4 = other.Unkb4;
Unkb8 = other.Unkb8;
Unkbc = other.Unkbc;
Unkc0 = other.Unkc0.ToArray();
Unkd8 = other.Unkd8;
Unkdc = other.Unkdc;
Unke0 = other.Unke0;
Unke4 = other.Unke4;
Unke8 = other.Unke8;
Unkec = other.Unkec;
Unkf0 = other.Unkf0;
Unkf8 = other.Unkf8;
Unkfc = other.Unkfc;
Unk100 = other.Unk100;
Unk104 = other.Unk104;
Unk108 = other.Unk108;
Unk10c = other.Unk10c.ToArray();
Unk118 = other.Unk118;
Unk11c = other.Unk11c;
Unk120 = other.Unk120;
Unk124 = other.Unk124;
Unk128 = other.Unk128;
Unk12c = other.Unk12c.ToArray();
Unk138 = other.Unk138;
Unk13c = other.Unk13c;
Unk140 = other.Unk140;
Unk144 = other.Unk144;
return this;
}
}
}
Loading

0 comments on commit b9009e7

Please sign in to comment.