1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-09-27 21:56:19 -04:00

Address Patch MPQ Issue (#52)

* Address patch mpq issue

Game can now load patch mpq files and will do so instead of a regular mpq version if a patch version exists.
Updated level details & char stats loading

* deleted screenshots

* merge fixes

* Fix weapon.cs

Code column moved over one in patch mpq

* Fixes: armor/misc/weapon column adjustment and mapgen autofac fix

* Fix SelectHeroClass to work with None herotype
This commit is contained in:
nicholasdechiara 2018-12-15 12:18:32 -05:00 committed by Tim Sarbin
parent 8699aad41b
commit be29a0017e
25 changed files with 1305 additions and 956 deletions

View File

@ -17,9 +17,13 @@ namespace OpenDiablo2.Common.UT
energy: 20, health: 50, mana: 40, stamina: 30, manaRegen: 20,
perlevelhealth: 3, perlevelmana: 1.5, perlevelstamina: 1,
pervitalityhealth: 2, pervitalitystamina: 1, perenergymana: 1.5,
perLevelStatPoints: 5,
baseatkrating: -30, basedefrating: -30, perdexterityatkrating: 5, perdexteritydefrating: 4,
walkVelocity: 6, runVelocity: 9, runDrain: 20, walkFrames: 8, runFrames: 8, swingFrames: 8,
spellFrames: 8, getHitFrames: 8, bowFrames: 8, startingSkill: 0, baseWeaponClass: "hth",
spellFrames: 8, getHitFrames: 8, bowFrames: 8, startingSkill: "",
startingSkills: new string[] { },
allSkillsBonusString: "", firstTabBonusString: "", secondTabBonusString: "",
thirdTabBonusString: "", classOnlyBonusString: "", baseWeaponClass: "hth",
initialEquipment: new List<InitialEquipment>());
LevelExperienceConfig expconfig = new LevelExperienceConfig(new List<long>()
{

View File

@ -15,13 +15,14 @@ namespace OpenDiablo2.Common.Interfaces
string MapName { get; }
Palette CurrentPalette { get; }
IEnumerable<PlayerInfo> PlayerInfos { get; }
eDifficulty Difficulty { get; }
ItemInstance SelectedItem { get; }
void SelectItem(ItemInstance item);
int CameraOffset { get; set; }
void Initialize(string characterName, eHero hero, eSessionType sessionType);
void Initialize(string characterName, eHero hero, eSessionType sessionType, eDifficulty difficulty);
void Update(long ms);
IEnumerable<MapCellInfo> GetMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType);
void UpdateMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType, IEnumerable<MapCellInfo> mapCellInfo);

View File

@ -28,6 +28,8 @@ namespace OpenDiablo2.Common.Interfaces.Mobs
double PerVitalityStamina { get; }
double PerEnergyMana { get; }
int PerLevelStatPoints { get; }
int BaseAttackRating { get; }
int PerDexterityAttackRating { get; }
@ -45,7 +47,14 @@ namespace OpenDiablo2.Common.Interfaces.Mobs
int GetHitFrames { get; }
int BowFrames { get; }
int StartingSkill { get; }
string StartingSkill { get; }
string[] StartingSkills { get; }
string AllSkillsBonusString { get; }
string FirstTabBonusString { get; }
string SecondTabBonusString { get; }
string ThirdTabBonusString { get; }
string ClassOnlyBonusString { get; }
string BaseWeaponClass { get; }
List<InitialEquipment> InitialEquipment { get; }

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Interfaces.Mobs
{
public interface IMissileTypeConfig
{
}
}

View File

@ -21,7 +21,7 @@ namespace OpenDiablo2.Common.Models
{
Name = row[0],
Code = row[17],
InvFile = row[33]
InvFile = row[34]
};
public static void Write(this BinaryWriter binaryWriter, Armor armor)

View File

@ -19,8 +19,8 @@ namespace OpenDiablo2.Common.Models
=> new Misc
{
Name = row[0],
Code = row[12],
InvFile = row[21]
Code = row[13],
InvFile = row[23]
};
public static void Write(this BinaryWriter binaryWriter, Misc misc)

View File

@ -19,9 +19,9 @@ namespace OpenDiablo2.Common.Models
=> new Weapon
{
Name = row[0],
Code = row[2],
WeaponClass = row[34],
InvFile = row[45]
Code = row[3],
WeaponClass = row[37],
InvFile = row[48]
};
public static void Write(this BinaryWriter binaryWriter, Weapon weapon)

View File

@ -1,4 +1,5 @@
using System;
using OpenDiablo2.Common.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -6,6 +7,31 @@ using System.Threading.Tasks;
namespace OpenDiablo2.Common.Models
{
public sealed class LevelDetailDifficulty
{
/// <summary>Horizontal size of the level</summary>
public int SizeX { get; internal set; }
/// <summary>Vertical size of the level</summary>
public int SizeY { get; internal set; }
/// <summary>Level (controls the item level of items that drop from chests etc)</summary>
public int MonLevel { get; internal set; }
/// <summary> MonLevel, but for expansion games (only use if non-null) </summary>
public int? MonLevelEx { get; internal set; }
/// <summary>The Density of Monsters</summary>
public int MonDen { get; internal set; }
/// <summary>Minimum Unique and Champion Monsters Spawned in this Level</summary>
public int MonUMin { get; internal set; }
/// <summary>Maximum Unique and Champion Monsters Spawned in this Level</summary>
public int MonUMax { get; internal set; }
/// <summary>Monster Species 1-25 (use ID from MonStats.txt)</summary>
public string[] M1_25 { get; internal set; }
}
public sealed class LevelDetail
{
/// <summary>Internal level name</summary>
@ -14,20 +40,26 @@ namespace OpenDiablo2.Common.Models
/// <summary>Level ID (Used in columns like VIS0-1)</summary>
public int Id { get; internal set; }
public Dictionary<eDifficulty, LevelDetailDifficulty> Difficulties = new Dictionary<eDifficulty, LevelDetailDifficulty>();
/// <summary>Palette</summary>
public int Pal { get; internal set; }
/// <summary>Act</summary>
public int Act { get; internal set; }
/// <summary>
/// Used in classic (non-expansion) games
/// If this is set, the character must have completed the quest with this id to take a portal here
/// </summary>
public int? QuestFlag { get; internal set; }
/// <summary> See above, but for expansion games </summary>
public int? QuestFlagEx { get; internal set; }
/// <summary>What layer the level is on (surface levels are always 0)</summary>
public int Layer { get; internal set; }
/// <summary>Horizontal size of the level</summary>
public int SizeX { get; internal set; }
/// <summary>Vertical size of the level</summary>
public int SizeY { get; internal set; }
/// <summary>Horizontal Placement Offset</summary>
public int OffsetX { get; internal set; }
@ -38,29 +70,34 @@ namespace OpenDiablo2.Common.Models
/// <summary>Special setting for levels that aren't random or preset (like Outer Cloister and Arcane Sancturary)</summary>
public int Depend { get; internal set; }
/// <summary> if false, teleport not allowed in this level </summary>
public bool Teleport { get; internal set; }
/// <summary> if true, cannot teleport through walls and objects in this level </summary>
public bool CantTeleportThroughWallsObjects { get; internal set; }
/// <summary>If true, it rains (or snows)</summary>
public int Rain { get; internal set; }
public bool Rain { get; internal set; }
/// <summary>Unused</summary>
public int Mud { get; internal set; }
public bool Mud { get; internal set; }
/// <summary>Perspective mode forced to off if set to 1</summary>
public int NoPer { get; internal set; }
public bool NoPer { get; internal set; }
/// <summary>Level of sight drawing</summary>
public int LOSDraw { get; internal set; }
public bool LOSDraw { get; internal set; }
/// <summary>Unknown</summary>
public int FloorFilter { get; internal set; }
public bool FloorFilter { get; internal set; }
/// <summary>Unknown</summary>
public int BlankScreen { get; internal set; }
public bool BlankScreen { get; internal set; }
/// <summary>For levels bordered with mountains or walls</summary>
public int DrawEdges { get; internal set; }
public bool DrawEdges { get; internal set; }
/// <summary>Set to 1 if this is underground or inside</summary>
public int IsInside { get; internal set; }
public bool IsInside { get; internal set; }
/// <summary> Setting for Level Generation: 1=Random Size, amount of rooms defined by LVLMAZE.TXT, 2=pre set map (example: catacombs lvl4) and 3=Random Area with preset size (wildernesses)</summary>
public int DrlgType { get; internal set; }
@ -99,13 +136,13 @@ namespace OpenDiablo2.Common.Models
public int Blue { get; internal set; }
/// <summary>Unknown</summary>
public int Portal { get; internal set; }
public bool Portal { get; internal set; }
/// <summary>Settings for preset levels</summary>
public int Position { get; internal set; }
public bool Position { get; internal set; }
/// <summary>If true, monster/creatures get saved/loaded instead of despawning.</summary>
public int SaveMonsters { get; internal set; }
public bool SaveMonsters { get; internal set; }
/// <summary>Quest flags</summary>
public int Quest { get; internal set; }
@ -113,23 +150,9 @@ namespace OpenDiablo2.Common.Models
/// <summary>Usually 2025, unknown</summary>
public int WarpDist { get; internal set; }
/// <summary>Level on Normal (controls the item level of items that drop from chests etc)</summary>
public int MonLvl1 { get; internal set; }
/// <summary>Level on Nightmare (controls the item level of items that drop from chests etc)</summary>
public int MonLvl2 { get; internal set; }
/// <summary> Level on Hell (controls the item level of items that drop from chests etc)</summary>
public int MonLvl3 { get; internal set; }
/// <summary>The Density of Monsters</summary>
public int MonDen { get; internal set; }
/// <summary>Minimum Unique and Champion Monsters Spawned in this Level</summary>
public int MonUMin { get; internal set; }
/// <summary>Maximum Unique and Champion Monsters Spawned in this Level</summary>
public int MonUMax { get; internal set; }
/// <summary></summary>
public int MonWndr { get; internal set; }
@ -140,26 +163,20 @@ namespace OpenDiablo2.Common.Models
/// <summary>How many different Species of Monsters can occur in this area (example: if you use M1-25 then set Mtot to 25 etc)</summary>
public int Mtot { get; internal set; }
/// <summary>Monster Species 1-25 (use ID from MonStats.txt)</summary>
public int[] M1_25 { get; internal set; }
/// <summary>Spawned Species 1-25 (use ID from MonStats for Monsters which have eSpawnCol set to 2, related to M1-25, eg: if M1 Spawns S1 will Spawn)</summary>
public int[] S1_25 { get; internal set; }
/// <summary>How many different Species of Monsters can spawn as Uniques and Champions in this Area (works like Mtot)</summary>
public int Utot { get; internal set; }
/// <summary> If true, ranged monsters have spawning preference </summary>
public bool RangedSpawnPreference { get; internal set; }
/// <summary> Unique Species 1-25 (same as M1-M25 just for Monsters that you want to appear as Unique/Champions)</summary>
public int[] U1_25 { get; internal set; }
public string[] U1_25 { get; internal set; }
/// <summary>Critter Species 1-5 (For monsters set to 1 in the IsCritter Column in MonStats.txt)</summary>
public int[] C1_5 { get; internal set; }
/// <summary>Critter Species 1-4 (For monsters set to 1 in the IsCritter Column in MonStats.txt)</summary>
public string[] C1_4 { get; internal set; }
/// <summary>Related to C1-5, eg: if you spawn a critter thru C1 then set this column to 30 etc. (function unknown)</summary>
public int[] CA1_5 { get; internal set; }
/// <summary>Related to C1-5, eg: if you spawn a critter thru C1 then set this column to 30 etc. Controls chance for critter to spawn.</summary>
public int[] CA1_4 { get; internal set; }
/// <summary>Unknown</summary>
public int[] CD1_5 { get; internal set; }
/// <summary>Unknown and unused</summary>
public int[] CD1_4 { get; internal set; }
/// <summary>unknown</summary>
public int Themes { get; internal set; }
@ -195,28 +212,60 @@ namespace OpenDiablo2.Common.Models
public static class LevelDetailHelper
{
private static int? IntOrEmpty(string s)
{
if (string.IsNullOrWhiteSpace(s))
{
return null;
}
return Convert.ToInt32(s);
}
private static int IntOrZero(string s)
{
if (string.IsNullOrWhiteSpace(s))
{
return 0;
}
return Convert.ToInt32(s);
}
public static LevelDetail ToLevelDetail(this string[] v, IEnumerable<LevelPreset> levelPresets, IEnumerable<LevelType> levelTypes)
{
var result = new LevelDetail();
result.Difficulties.Add(eDifficulty.NORMAL, new LevelDetailDifficulty());
result.Difficulties.Add(eDifficulty.NIGHTMARE, new LevelDetailDifficulty());
result.Difficulties.Add(eDifficulty.HELL, new LevelDetailDifficulty());
int i = 0;
result.Name = v[i++];
result.Id = Convert.ToInt32(v[i++]);
result.Pal = Convert.ToInt32(v[i++]);
result.Act = Convert.ToInt32(v[i++]);
result.QuestFlag = IntOrEmpty(v[i++]);
result.QuestFlagEx = IntOrEmpty(v[i++]);
result.Layer = Convert.ToInt32(v[i++]);
result.SizeX = Convert.ToInt32(v[i++]);
result.SizeY = Convert.ToInt32(v[i++]);
result.Difficulties[eDifficulty.NORMAL].SizeX = Convert.ToInt32(v[i++]);
result.Difficulties[eDifficulty.NORMAL].SizeY = Convert.ToInt32(v[i++]);
result.Difficulties[eDifficulty.NIGHTMARE].SizeX = Convert.ToInt32(v[i++]);
result.Difficulties[eDifficulty.NIGHTMARE].SizeY = Convert.ToInt32(v[i++]);
result.Difficulties[eDifficulty.HELL].SizeX = Convert.ToInt32(v[i++]);
result.Difficulties[eDifficulty.HELL].SizeY = Convert.ToInt32(v[i++]);
result.OffsetX = Convert.ToInt32(v[i++]);
result.OffsetY = Convert.ToInt32(v[i++]);
result.Depend = Convert.ToInt32(v[i++]);
result.Rain = Convert.ToInt32(v[i++]);
result.Mud = Convert.ToInt32(v[i++]);
result.NoPer = Convert.ToInt32(v[i++]);
result.LOSDraw = Convert.ToInt32(v[i++]);
result.FloorFilter = Convert.ToInt32(v[i++]);
result.BlankScreen = Convert.ToInt32(v[i++]);
result.DrawEdges = Convert.ToInt32(v[i++]);
result.IsInside = Convert.ToInt32(v[i++]);
result.Teleport = (v[i] == "1" || v[i] == "2");
result.CantTeleportThroughWallsObjects = (v[i] == "0" || v[i] == "2");
i++;
result.Rain = (v[i++] == "1");
result.Mud = (v[i++] == "1");
result.NoPer = (v[i++] == "1");
result.LOSDraw = (v[i++] == "1");
result.FloorFilter = (v[i++] == "1");
result.BlankScreen = (v[i++] == "1");
result.DrawEdges = (v[i++] == "1");
result.IsInside = (v[i++] == "1");
result.DrlgType = Convert.ToInt32(v[i++]);
result.LevelTypeId = Convert.ToInt32(v[i++]);
result.SubType = Convert.ToInt32(v[i++]);
@ -231,33 +280,52 @@ namespace OpenDiablo2.Common.Models
result.Red = Convert.ToInt32(v[i++]);
result.Green = Convert.ToInt32(v[i++]);
result.Blue = Convert.ToInt32(v[i++]);
result.Portal = Convert.ToInt32(v[i++]);
result.Position = Convert.ToInt32(v[i++]);
result.SaveMonsters = Convert.ToInt32(v[i++]);
result.Portal = (v[i++] == "1");
result.Position = (v[i++] == "1");
result.SaveMonsters = (v[i++] == "1");
result.Quest = Convert.ToInt32(v[i++]);
result.WarpDist = Convert.ToInt32(v[i++]);
result.MonLvl1 = Convert.ToInt32(v[i++]);
result.MonLvl2 = Convert.ToInt32(v[i++]);
result.MonLvl3 = Convert.ToInt32(v[i++]);
result.MonDen = Convert.ToInt32(v[i++]);
result.MonUMin = Convert.ToInt32(v[i++]);
result.MonUMax = Convert.ToInt32(v[i++]);
result.Difficulties[eDifficulty.NORMAL].MonLevel = Convert.ToInt32(v[i++]);
result.Difficulties[eDifficulty.NIGHTMARE].MonLevel = Convert.ToInt32(v[i++]);
result.Difficulties[eDifficulty.HELL].MonLevel = Convert.ToInt32(v[i++]);
result.Difficulties[eDifficulty.NORMAL].MonLevelEx = IntOrEmpty(v[i++]);
result.Difficulties[eDifficulty.NIGHTMARE].MonLevelEx = IntOrEmpty(v[i++]);
result.Difficulties[eDifficulty.HELL].MonLevelEx = IntOrEmpty(v[i++]);
result.Difficulties[eDifficulty.NORMAL].MonDen = Convert.ToInt32(v[i++]);
result.Difficulties[eDifficulty.NIGHTMARE].MonDen = Convert.ToInt32(v[i++]);
result.Difficulties[eDifficulty.HELL].MonDen = Convert.ToInt32(v[i++]);
result.Difficulties[eDifficulty.NORMAL].MonUMin = IntOrZero(v[i++]);
result.Difficulties[eDifficulty.NORMAL].MonUMax = IntOrZero(v[i++]);
result.Difficulties[eDifficulty.NIGHTMARE].MonUMin = IntOrZero(v[i++]);
result.Difficulties[eDifficulty.NIGHTMARE].MonUMax = IntOrZero(v[i++]);
result.Difficulties[eDifficulty.HELL].MonUMin = IntOrZero(v[i++]);
result.Difficulties[eDifficulty.HELL].MonUMax = IntOrZero(v[i++]);
result.MonWndr = Convert.ToInt32(v[i++]);
result.MonSpcWalk = Convert.ToInt32(v[i++]);
result.Mtot = Convert.ToInt32(v[i++]);
result.M1_25 = new int[25];
for (int j = 0; j < 25; j++) result.M1_25[j] = Convert.ToInt32(v[i++]);
result.S1_25 = new int[25];
for (int j = 0; j < 25; j++) result.S1_25[j] = Convert.ToInt32(v[i++]);
result.Utot = Convert.ToInt32(v[i++]);
result.U1_25 = new int[25];
for (int j = 0; j < 25; j++) result.U1_25[j] = Convert.ToInt32(v[i++]);
result.C1_5 = new int[5];
for (int j = 0; j < 5; j++) result.C1_5[j] = Convert.ToInt32(v[i++]);
result.CA1_5 = new int[5];
for (int j = 0; j < 5; j++) result.CA1_5[j] = Convert.ToInt32(v[i++]);
result.CD1_5 = new int[5];
for (int j = 0; j < 5; j++) result.CD1_5[j] = Convert.ToInt32(v[i++]);
result.Mtot = IntOrZero(v[i++]);
result.Difficulties[eDifficulty.NORMAL].M1_25 = new string[25]; // NOTE: the game will accept up to 25,
// but only the first 10 are present in the mpq provided table
// TODO: add check to see if the other 11-25 are present instead of only taking the first 10
for (int j = 0; j < 10; j++) result.Difficulties[eDifficulty.NORMAL].M1_25[j] = v[i++];
result.RangedSpawnPreference = (v[i++] == "1");
result.Difficulties[eDifficulty.NIGHTMARE].M1_25 = new string[25];
result.Difficulties[eDifficulty.HELL].M1_25 = new string[25];
// See above TODO
for (int j = 0; j < 10; j++)
{
result.Difficulties[eDifficulty.NIGHTMARE].M1_25[j] = v[i]; // note: currently these are the same
result.Difficulties[eDifficulty.HELL].M1_25[j] = v[i++];
}
result.U1_25 = new string[25];
// See above TODO
for (int j = 0; j < 10; j++) result.U1_25[j] = v[i++];
result.C1_4 = new string[4];
for (int j = 0; j < 4; j++) result.C1_4[j] = v[i++];
result.CA1_4 = new int[4];
for (int j = 0; j < 4; j++) result.CA1_4[j] = IntOrZero(v[i++]);
result.CD1_4 = new int[4];
for (int j = 0; j < 4; j++) result.CD1_4[j] = IntOrZero(v[i++]);
result.Themes = Convert.ToInt32(v[i++]);
result.SoundEnv = Convert.ToInt32(v[i++]);
result.Waypoint = Convert.ToInt32(v[i++]);

View File

@ -53,7 +53,9 @@ namespace OpenDiablo2.Common.Models
KeyAdjusted = 0x00020000, // The file's encryption key is adjusted by the block offset and file size (explained in detail in the File Data section). File must be encrypted.
IsEncrypted = 0x00010000, // File is encrypted.
IsCompressed = 0x00000200, // File is compressed. File cannot be imploded.
IsImploded = 0x00000100 // File is imploded. File cannot be compressed.
IsImploded = 0x00000100, // File is imploded. File cannot be compressed.
IsPatchFile = 0x00100000, // Marks as a mpq patch file
IsDeleteFile = 0x02000000, // Marks as a delete request
}
internal struct BlockRecord
@ -71,6 +73,8 @@ namespace OpenDiablo2.Common.Models
public bool IsEncrypted => (Flags & (UInt32)eBlockRecordBitflag.IsEncrypted) != 0;
public bool IsCompressed => (Flags & (UInt32)eBlockRecordBitflag.IsCompressed) != 0;
public bool IsImploded => (Flags & (UInt32)eBlockRecordBitflag.IsImploded) != 0;
public bool IsPatchFile => (Flags & (UInt32)eBlockRecordBitflag.IsPatchFile) != 0;
public bool IsDeleteFile => (Flags & (UInt32)eBlockRecordBitflag.IsDeleteFile) != 0;
}
internal struct HashRecord
@ -94,9 +98,15 @@ namespace OpenDiablo2.Common.Models
public string Path { get; private set; }
public eMPQFormatVersion FormatVersion => (eMPQFormatVersion)Header.FormatVersion;
public List<string> Files => GetFilePaths();
private List<string> FilesOverride = null;
private List<string> GetFilePaths()
{
if(FilesOverride != null)
{
return FilesOverride; // if we were passed in an existing listfile,
// don't query again; use the one we were given instead
}
using (var stream = OpenFile(LISTFILE_NAME))
{
if (stream == null)
@ -116,9 +126,10 @@ namespace OpenDiablo2.Common.Models
InitializeCryptTable();
}
public MPQ(string path)
public MPQ(string path, List<string> listfile = null)
{
this.Path = path;
this.FilesOverride = listfile;
// If you crash here, you may have Diablo2 open... can't do that :)
fileStream = new FileStream(path, FileMode.Open);
@ -369,6 +380,11 @@ namespace OpenDiablo2.Common.Models
return new MPQStream(this, block);
}
public bool HasFile(string filename)
{
return GetHashRecord(filename, out HashRecord hash);
}
public void Dispose()
{
fileStream?.Dispose();

View File

@ -27,7 +27,7 @@ namespace OpenDiablo2.Common.Models
this.blockRecord = blockRecord;
this.blockSize = 0x200 << mpq.Header.BlockSize;
if (blockRecord.IsCompressed && !blockRecord.SingleUnit)
if ((blockRecord.IsCompressed || blockRecord.IsImploded) && !blockRecord.SingleUnit)
LoadBlockOffsets();
}
@ -187,7 +187,7 @@ namespace OpenDiablo2.Common.Models
int toread;
uint encryptionseed;
if (blockRecord.IsCompressed)
if (blockRecord.IsCompressed || blockRecord.IsImploded)
{
offset = blockPositions[blockIndex];
toread = (int)(blockPositions[blockIndex + 1] - offset);
@ -226,6 +226,11 @@ namespace OpenDiablo2.Common.Models
data = PKDecompress(new MemoryStream(data), expectedLength);
}
if (blockRecord.IsImploded && (toread != expectedLength))
{
data = PKDecompress(new MemoryStream(data), expectedLength);
}
return data;
}

View File

@ -26,6 +26,8 @@ namespace OpenDiablo2.Common.Models.Mobs
public double PerVitalityStamina { get; protected set; }
public double PerEnergyMana { get; protected set; }
public int PerLevelStatPoints { get; protected set; }
public int BaseAttackRating { get; protected set; }
public int PerDexterityAttackRating { get; protected set; }
@ -43,7 +45,15 @@ namespace OpenDiablo2.Common.Models.Mobs
public int GetHitFrames { get; protected set; }
public int BowFrames { get; protected set; }
public int StartingSkill { get; protected set; }
public string StartingSkill { get; protected set; }
public string[] StartingSkills { get; protected set; }
public string AllSkillsBonusString { get; protected set; }
public string FirstTabBonusString { get; protected set; }
public string SecondTabBonusString { get; protected set; }
public string ThirdTabBonusString { get; protected set; }
public string ClassOnlyBonusString { get; protected set; }
public string BaseWeaponClass { get; protected set; }
public List<InitialEquipment> InitialEquipment { get; }
@ -52,10 +62,13 @@ namespace OpenDiablo2.Common.Models.Mobs
int health, int mana, int stamina, int manaRegen,
double perlevelhealth, double perlevelmana, double perlevelstamina,
double pervitalityhealth, double pervitalitystamina, double perenergymana,
int perLevelStatPoints,
int baseatkrating, int basedefrating, int perdexterityatkrating, int perdexteritydefrating,
int walkVelocity, int runVelocity, int runDrain,
int walkFrames, int runFrames, int swingFrames, int spellFrames, int getHitFrames, int bowFrames,
int startingSkill,
string startingSkill, string[] startingSkills,
string allSkillsBonusString, string firstTabBonusString, string secondTabBonusString,
string thirdTabBonusString, string classOnlyBonusString,
string baseWeaponClass,
List<InitialEquipment> initialEquipment)
{
@ -76,6 +89,8 @@ namespace OpenDiablo2.Common.Models.Mobs
PerVitalityStamina = pervitalitystamina;
PerEnergyMana = perenergymana;
PerLevelStatPoints = perLevelStatPoints;
BaseAttackRating = baseatkrating;
BaseDefenseRating = basedefrating;
PerDexterityAttackRating = perdexterityatkrating;
@ -93,6 +108,13 @@ namespace OpenDiablo2.Common.Models.Mobs
BowFrames = bowFrames;
StartingSkill = startingSkill;
StartingSkills = startingSkills;
AllSkillsBonusString = allSkillsBonusString;
FirstTabBonusString = firstTabBonusString;
SecondTabBonusString = secondTabBonusString;
ThirdTabBonusString = thirdTabBonusString;
ClassOnlyBonusString = classOnlyBonusString;
BaseWeaponClass = baseWeaponClass;
InitialEquipment = initialEquipment;
@ -122,29 +144,35 @@ namespace OpenDiablo2.Common.Models.Mobs
// RunDrain Comment LifePerLevel StaminaPerLevel ManaPerLevel
// 21 22 23
// LifePerVitality StaminaPerVitality ManaPerMagic
// 24 25 26 27 28 29
// 24
// StatPerLevel
// 25 26 27 28 29 30
// #walk #run #swing #spell #gethit #bow
// 30 31 32
// BlockFactor StartSkill baseWClass
// 33 34 35 36 37 38
// 31 32
// BlockFactor StartSkill
// 33 ... 42
// Skill1 ... Skill10
// 43 44 45 46 47
// StrAllSkills StrSkillTab1 StrSkillTab2 StrSkillTab3 StrClassOnly
// 48
// baseWClass
// 49 50 51 49+3 50+3 51+3
// item1 item1loc item1count item2 item2loc item2count
// 39 40 41 42 43 44
// item3 item3loc item3count item4 item4loc item4count
// 45 46 47 48 49 50
// item5 item5loc item5count item6 item6loc item6count
// 51 52 53 54 55 56
// item7 item7loc item7count item8 item8loc item8count
// 57 58 59 60 61 62
// item9 item9loc item9count item10 item10loc item10count
// itemn = 49 + (n-1)*3, itemnloc = 50 + (n-1)*3, itemncount = 51 + (n-1)*3
// NEW TODO: StatPerLevel Skill1 ... Skill10
// StrAllSkills StrSkillTab1 StrSkillTab2 StrSkillTab3 StrClassOnly
List<string> itemNames = new List<string>();
List<string> itemLocs = new List<string>();
List<int> itemCounts = new List<int>();
List<InitialEquipment> initialEquipment = new List<InitialEquipment>();
for(int i = 33; i <= 60; i+=3)
for(int i = 49; i <= 78; i+=3)
{
var item = new InitialEquipment();
item.name = row[i];
@ -169,21 +197,31 @@ namespace OpenDiablo2.Common.Models.Mobs
pervitalityhealth: Convert.ToInt32(row[21]) / 4.0,
pervitalitystamina: Convert.ToInt32(row[22]) / 4.0,
perenergymana: Convert.ToInt32(row[23]) / 4.0,
perLevelStatPoints: Convert.ToInt32(row[24]),
baseatkrating: Convert.ToInt32(row[13]),
basedefrating: Convert.ToInt32(row[30]),
basedefrating: Convert.ToInt32(row[31]),
perdexterityatkrating: 5,
perdexteritydefrating: 4,
walkVelocity: Convert.ToInt32(row[14]),
runVelocity: Convert.ToInt32(row[15]),
runDrain: Convert.ToInt32(row[16]),
walkFrames: Convert.ToInt32(row[24]),
runFrames: Convert.ToInt32(row[25]),
swingFrames: Convert.ToInt32(row[26]),
spellFrames: Convert.ToInt32(row[27]),
getHitFrames: Convert.ToInt32(row[28]),
bowFrames: Convert.ToInt32(row[29]),
startingSkill: Convert.ToInt32(row[31]),
baseWeaponClass: row[32],
walkFrames: Convert.ToInt32(row[25]),
runFrames: Convert.ToInt32(row[26]),
swingFrames: Convert.ToInt32(row[27]),
spellFrames: Convert.ToInt32(row[28]),
getHitFrames: Convert.ToInt32(row[29]),
bowFrames: Convert.ToInt32(row[30]),
startingSkill: row[32],
startingSkills: new string[]
{
row[33], row[34], row[35], row[36], row[37], row[38], row[39], row[40], row[41], row[42]
},
allSkillsBonusString: row[43],
firstTabBonusString: row[44],
secondTabBonusString: row[45],
thirdTabBonusString: row[46],
classOnlyBonusString: row[47],
baseWeaponClass: row[48],
initialEquipment: initialEquipment);
}
}

View File

@ -0,0 +1,131 @@
using OpenDiablo2.Common.Interfaces.Mobs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Models.Mobs
{
public class MissileTypeConfig : IMissileTypeConfig
{
public string Name { get; private set; }
public int Id { get; private set; }
public int BaseVelocity { get; private set; }
public int MaxVelocity { get; private set; }
public int Acceleration { get; private set; }
public int Range { get; private set; }
public int ExtraRangePerLevel { get; private set; }
public int LightRadius { get; private set; }
public bool LightFlicker { get; private set; }
public byte[] LightColor { get; private set; } // rgb
public int FramesBeforeVisible { get; private set; } // how many anim frames before it becomes visible
public int FramesBeforeActive { get; private set; } // anim frames before it can do anything, e.g. collide
public bool LoopAnimation { get; private set; } // true: repeat animation until missile hits range or is otherwise destroyed
// false: repeat animation once, then vanish (WARNING: only becomes invisible, not deleted)
public string CelFilePath { get; private set; }
public int AnimationRate { get; private set; } // does not seem to be used
public int AnimationLength { get; private set; } // length of animation for one direction
public int AnimationSpeed { get; private set; } // actually used, frames per second
public int StartingFrame { get; private set; } // 'randstart' is a misnomer, actually just starts at this frame
public bool AnimationHasSubLoop { get; private set; } // if true, will repeat in a certain range of frames
//instead of repeating the whole animation again
public int AnimationSubLoopStart { get; private set; } // what frame to start at if it has a subloop
public int AnimationSubLoopEnd { get; private set; } // what frame to end at in subloop
// when it hits this, goes back to subloop start or goes to end if missile is set to die (out of range)
public MissileTypeConfig(string Name, int Id,
int BaseVelocity, int MaxVelocity, int Acceleration, int Range, int ExtraRangePerLevel,
int LightRadius, bool LightFlicker, byte[] LightColor,
int FramesBeforeVisible, int FramesBeforeActive, bool LoopAnimation, string CelFilePath,
int AnimationRate, int AnimationLength, int AnimationSpeed, int StartingFrame,
bool AnimationHasSubLoop, int AnimationSubLoopStart, int AnimationSubLoopEnd)
{
this.Name = Name;
this.Id = Id;
this.BaseVelocity = BaseVelocity;
this.MaxVelocity = MaxVelocity;
this.Acceleration = Acceleration;
this.Range = Range;
this.ExtraRangePerLevel = ExtraRangePerLevel;
this.LightRadius = LightRadius;
this.LightFlicker = LightFlicker;
this.LightColor = LightColor;
this.FramesBeforeVisible = FramesBeforeVisible;
this.FramesBeforeActive = FramesBeforeActive;
this.LoopAnimation = LoopAnimation;
this.CelFilePath = CelFilePath;
this.AnimationRate = AnimationRate;
this.AnimationLength = AnimationLength;
this.AnimationSpeed = AnimationSpeed;
this.StartingFrame = StartingFrame;
this.AnimationHasSubLoop = AnimationHasSubLoop;
this.AnimationSubLoopStart = AnimationSubLoopStart;
this.AnimationSubLoopEnd = AnimationSubLoopEnd;
}
}
public static class MissileTypeConfigHelper
{
//Missile Id Vel MaxVel Accel Range LevRange
//0 1 2 3 4 5 6
//Light Flicker Red Green Blue
//7 8 9 10 11
//InitSteps Activate
//12 13
//LoopAnim CelFile AnimLen RandStart SubLoop SubStart SubStop
//14 15 16 17 18 19 20
//CollideType CollideKill CollideFriend LastCollide Collision
//21 22 23 24 25
//ClientSend NextHit NextDelay Size CanDestroy ToHit AlwaysExplode Explosion
//26 27 28 29 30 31 32 33
//NeverDup ReturnFire GetHit KnockBack Trans Qty Pierce
//34 35 36 37 38 39 40
//Param1 Param1 Comment Param2 Param2 Comment SpecialSetup
//41 42 43 44 45
//Open Beta Skill HitShift SrcDamage MinDamage MaxDamage LevDamage
//46 47 48 49 50 51 52 53
//EType EMin Emax ELevel ELen ELevelLen HitClass NumDirections LocalBlood
//54 55 56 57 58 59 60 61 62
public static IMissileTypeConfig ToMissileTypeConfig(this string[] row)
{
return new MissileTypeConfig(
Name: row[0],
Id: Convert.ToInt32(row[1]),
BaseVelocity: Convert.ToInt32(row[2]),
MaxVelocity: Convert.ToInt32(row[3]),
Acceleration: Convert.ToInt32(row[4]),
Range: Convert.ToInt32(row[5]),
ExtraRangePerLevel: Convert.ToInt32(row[6]),
LightRadius: Convert.ToInt32(row[7]),
LightFlicker: (row[8] == "1"),
LightColor: new byte[] {Convert.ToByte(row[9]), Convert.ToByte(row[10]), Convert.ToByte(row[11])},
FramesBeforeVisible: Convert.ToInt32(row[12]),
FramesBeforeActive: Convert.ToInt32(row[13]),
LoopAnimation: (row[14] == "1"),
CelFilePath: row[15],
// TODO: these rows are wrong! research why our missiles.txt has different columns thatn the one in the guide???
// TODO: UNFINISHED
AnimationRate: Convert.ToInt32(row[16]),
AnimationLength: Convert.ToInt32(row[17]),
AnimationSpeed: Convert.ToInt32(row[18]),
StartingFrame: Convert.ToInt32(row[19]),
AnimationHasSubLoop: (row[20] == "1"),
AnimationSubLoopStart: Convert.ToInt32(row[16]),
AnimationSubLoopEnd: Convert.ToInt32(row[16])
);
}
}
}

View File

@ -105,6 +105,7 @@
<Compile Include="Interfaces\Mobs\IEnemyTypeCombatConfig.cs" />
<Compile Include="Interfaces\Mobs\IEnemyTypeConfig.cs" />
<Compile Include="Interfaces\Mobs\IEnemyTypeDifficultyConfig.cs" />
<Compile Include="Interfaces\Mobs\IMissileTypeConfig.cs" />
<Compile Include="Interfaces\UI\ISkillsPanel.cs" />
<Compile Include="Models\Mobs\EnemyTypeAppearanceConfig.cs" />
<Compile Include="Models\Mobs\EnemyTypeCombatConfig.cs" />
@ -118,6 +119,7 @@
<Compile Include="Models\Item\ItemInstance.cs" />
<Compile Include="Models\Item\Misc.cs" />
<Compile Include="Models\Item\Weapon.cs" />
<Compile Include="Models\Mobs\MissileTypeConfig.cs" />
<Compile Include="Models\Mobs\PlayerEquipment.cs" />
<Compile Include="Models\MPQCOF.cs" />
<Compile Include="Models\MPQDCC.cs" />

View File

@ -244,6 +244,9 @@ namespace OpenDiablo2.Common
// --- Enemy Data ---
public static string MonStats = "data\\global\\excel\\monstats.txt";
// --- Skill Data ---
public static string Missiles = "data\\global\\excel\\missiles.txt";
public static string GeneratePathForItem(string spriteName)
{
return $@"data\global\items\{spriteName}.dc6";

View File

@ -29,6 +29,7 @@ namespace OpenDiablo2.Core
public ImmutableDictionary<eHero, ILevelExperienceConfig> ExperienceConfigs { get; internal set; }
public ImmutableDictionary<eHero, IHeroTypeConfig> HeroTypeConfigs { get; internal set; }
public ImmutableList<IEnemyTypeConfig> EnemyTypeConfigs { get; internal set; }
public ImmutableDictionary<int, IMissileTypeConfig> MissileTypeConfigs { get; internal set; }
public EngineDataManager(IMPQProvider mpqProvider)
{
@ -37,6 +38,7 @@ namespace OpenDiablo2.Core
LoadLevelDetails();
LoadCharacterData();
LoadEnemyData();
LoadSkillData();
Items = LoadItemData();
}
@ -50,7 +52,7 @@ namespace OpenDiablo2.Core
.Skip(1)
.Where(x => !String.IsNullOrWhiteSpace(x))
.Select(x => x.Split('\t'))
.Where(x => x.Count() == 36 && x[0] != "Expansion")
.Where(x => x.Count() >= 36 && x[0] != "Expansion")
.Select(x => x.ToLevelType())
.ToImmutableList();
@ -61,7 +63,7 @@ namespace OpenDiablo2.Core
.Skip(1)
.Where(x => !String.IsNullOrWhiteSpace(x))
.Select(x => x.Split('\t'))
.Where(x => x.Count() == 24 && x[0] != "Expansion")
.Where(x => x.Count() >= 24 && x[0] != "Expansion")
.Select(x => x.ToLevelPreset())
.ToImmutableList();
@ -170,7 +172,8 @@ namespace OpenDiablo2.Core
private void LoadEnemyData()
{
EnemyTypeConfigs = LoadEnemyTypeConfig();
//TODO: RE-ENABLE THIS once monstats is being loaded properly
//EnemyTypeConfigs = LoadEnemyTypeConfig();
}
private ImmutableList<IEnemyTypeConfig> LoadEnemyTypeConfig()
@ -183,5 +186,26 @@ namespace OpenDiablo2.Core
.ToArray()
.Select(x => x.ToEnemyTypeConfig())
.ToImmutableList();
private void LoadSkillData()
{
MissileTypeConfigs = LoadMissileTypeConfig();
}
private ImmutableDictionary<int, IMissileTypeConfig> LoadMissileTypeConfig()
{
var data = mpqProvider
.GetTextFile(ResourcePaths.Missiles)
.Where(x => !String.IsNullOrWhiteSpace(x));
var splitdata = data
.Select(x => x.Split('\t'))
.Where(x => x[0] != "Expansion" && x[0] != "unused")
.ToArray();
// TODO: UNFINISHED
return null;
}
}
}

View File

@ -35,6 +35,7 @@ namespace OpenDiablo2.Core.GameState_
public string MapName { get; private set; }
public Palette CurrentPalette => paletteProvider.PaletteTable[$"ACT{Act}"];
public List<PlayerInfo> PlayerInfos { get; private set; }
public eDifficulty Difficulty { get; private set; }
readonly private IMouseCursor originalMouseCursor;
@ -78,7 +79,7 @@ namespace OpenDiablo2.Core.GameState_
mapDataLookup = new List<MapCellInfo>();
}
public void Initialize(string characterName, eHero hero, eSessionType sessionType)
public void Initialize(string characterName, eHero hero, eSessionType sessionType, eDifficulty difficulty)
{
sessionManager = getSessionManager(sessionType);
sessionManager.Initialize();
@ -88,6 +89,8 @@ namespace OpenDiablo2.Core.GameState_
sessionManager.OnPlayerInfo += OnPlayerInfo;
sessionManager.OnFocusOnPlayer += OnFocusOnPlayer;
Difficulty = difficulty;
mapInfo = new List<IMapInfo>();
sceneManager.ChangeScene(eSceneType.Game);

View File

@ -42,7 +42,7 @@ namespace OpenDiablo2.Core
.EnumerateFiles(globalConfiguration.BaseDataPath, "*.mpq")
.Where(x => !(Path.GetFileName(x)?.StartsWith("patch") ?? false))
.Select(file => new MPQ(file))
.ToArray();
.ToList();
if (!_mpqs.Any())
@ -74,6 +74,32 @@ namespace OpenDiablo2.Core
foreach (var file in _mpqs[i].Files)
_mpqLookup[file.ToLower()] = i;
}
// Get the combined list file by joining all of the other mpqs
List<string> superListFile = _mpqs.SelectMany(x => x.Files).ToList();
var patchMPQ = Directory
.EnumerateFiles(globalConfiguration.BaseDataPath, "*.mpq")
.Where(x => Path.GetFileName(x).StartsWith("patch"))
.Select(file => new MPQ(file, superListFile))
.First();
_mpqs.Add(patchMPQ);
int patchMPQIndex = _mpqs.Count - 1;
// Replace existing mpqLookups with those from the patch, which take precedence
foreach (var file in patchMPQ.Files)
{
// unlike the other mpqs, we need to ensure that the files actually exist
// inside of the patch mpq instead of assuming that they do, because
// we can't trust the filelist
if (!patchMPQ.HasFile(file))
{
continue;
}
_mpqLookup[file.ToLower()] = patchMPQIndex;
}
}
public byte[] GetBytes(string fileName)

View File

@ -14,10 +14,12 @@ namespace OpenDiablo2.MapGenerators
private readonly IGameState gameState;
private readonly LevelDetail levelDetail;
private readonly eDifficulty difficulty;
public BloodMoor(IGameState gameState, IEngineDataManager dataManager)
{
this.gameState = gameState;
this.difficulty = gameState.Difficulty;
levelDetail = dataManager.Levels.First(x => x.LevelName == "Blood Moor");
}
@ -192,8 +194,8 @@ namespace OpenDiablo2.MapGenerators
// Generate the cave
while (true)
{
var rx = random.Next(8, levelDetail.SizeX - 16);
var ry = random.Next(8, levelDetail.SizeY - 16);
var rx = random.Next(8, levelDetail.Difficulties[difficulty].SizeX - 16);
var ry = random.Next(8, levelDetail.Difficulties[difficulty].SizeY - 16);
rx -= (rx % 8);
ry -= (ry % 8);
var caveX = rx + location.X;
@ -215,8 +217,8 @@ namespace OpenDiablo2.MapGenerators
var campsToGenerate = 3;
while (campsToGenerate > 0)
{
var rx = random.Next(8, levelDetail.SizeX - 16);
var ry = random.Next(8, levelDetail.SizeY - 16);
var rx = random.Next(8, levelDetail.Difficulties[difficulty].SizeX - 16);
var ry = random.Next(8, levelDetail.Difficulties[difficulty].SizeY - 16);
rx -= (rx % 8);
ry -= (ry % 8);
var campX = rx + location.X;
@ -243,8 +245,8 @@ namespace OpenDiablo2.MapGenerators
static int[] tileFillIds = new int[] { 29, 30, 31, 38, 39, 40, 41 };
private void FillCenterArea(Point location, IMapInfo parentMap)
{
var rightEdge = levelDetail.SizeX - 8;
var bottomEdge = levelDetail.SizeY - 8;
var rightEdge = levelDetail.Difficulties[difficulty].SizeX - 8;
var bottomEdge = levelDetail.Difficulties[difficulty].SizeY - 8;
for (var y = 8; y < bottomEdge; y += 8)
{

View File

@ -271,7 +271,8 @@ namespace OpenDiablo2.Scenes
StopSfx();
// TODO: Support other session types
gameState.Initialize(characterNameTextBox.Text, selectedHero.Value, eSessionType.Local);
// TODO: support other difficulty types
gameState.Initialize(characterNameTextBox.Text, selectedHero.Value, eSessionType.Local, eDifficulty.NORMAL);
}
private void OnExitClicked()
@ -321,7 +322,7 @@ namespace OpenDiablo2.Scenes
private void RenderHeros()
{
var heros = Enum.GetValues(typeof(eHero)).Cast<eHero>();
var heros = Enum.GetValues(typeof(eHero)).Cast<eHero>().Skip(1); // skip NONE
foreach (var hero in heros)
if (heroRenderInfo[hero].Stance == eHeroStance.Idle || heroRenderInfo[hero].Stance == eHeroStance.IdleSelected)
RenderHero(hero);
@ -419,6 +420,10 @@ namespace OpenDiablo2.Scenes
private void UpdateHeroSelectionHover(eHero hero, long ms, bool canSelect)
{
if(hero == eHero.None)
{
return;
}
var renderInfo = heroRenderInfo[hero];
if (renderInfo.Stance == eHeroStance.Approaching)
{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 521 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 728 KiB