using System;
using System.Collections.Generic;
using System.Text;
namespace D2MonsterAuxParser
{
public class Program
{
public static string[] SplitAggregateData(string data)
{
data = data.Replace(" ", ""); // Remove spaces
data = data.Replace(",", ""); // Remove Commas (they exist for some pages and not others)
data = data.Trim(); // This may not be needed.
data = data.Replace("--", "0"); // Evidently this should just be ran on everything.
return data.Split('/');
}
public static string[] SplitTreasureClassData(string data)
{
data = data.Replace(" ", ""); // Remove spaces
data = data.Trim(); // This may not be needed.
data = data.Replace("--", "0"); // Evidently this should just be ran on everything.
string[] split = data.Split(',');
return new string[] {
split[0].Split('/')[0].Trim(),
split[1].Split('/')[0].Trim(),
split[2].Split('/')[0].Trim(),
};
}
public static string[] ParseBlockData(string data)
{
// D2 Blocking data is basically a cluster fuck.
data = data.Trim();
data = data.Replace("%", "");
data = data.Replace("--", "0"); // Evidently this should just be ran on everything.
if (data.Equals("0"))
{
return new string[] { "0", "0", "0" };
}
int count = 0;
foreach (char character in data)
{
if (character == '/')
{
count++;
}
}
if (count == 0)
{
// Blocking is universal for all difficulties
return new string[] { data, data, data };
}
if (count == 2)
{
// There are three separate blocking values; one for each difficulty
string[] split = data.Split("/");
return new string[] {
split[0],
split[1],
split[2] };
}
// Throw an error, because there is an edge case we're not prepared for!
throw new Exception("Blocking edge case detected!");
}
public static void Main(string[] args)
{
bool isDebugging = false;
bool filterNullData = false;
int resistanceTableLayoutCulture = 0;
Dictionary<string, string> auxData = new Dictionary<string, string>();
StringBuilder builder = new StringBuilder();
/**
* Get the monster base type from the user; this can be null
*/
Console.WriteLine("Enter the monster base type:");
Console.WriteLine("NOTE: For Unique Monsters, this should be left NULL");
Console.WriteLine("(Enter 'null' to skip)");
string input = Console.ReadLine();
if (!input.Equals("null"))
{
auxData.Add("ibasename", input.Trim());
}
else
{
auxData.Add("ibasename", "0");
}
/**
* Parse Statistics: Name, Level Experience, Hit Points, Speed, and Max TC
*/
Console.WriteLine("Input First Row 'Statistics' Data: ");
input = Console.ReadLine();
string[] firstRowSegments = input.Split('_');
// Name
auxData.Add("imvariant_name", firstRowSegments[0].Trim());
// Level
string[] splitCache = SplitAggregateData(firstRowSegments[1]);
auxData.Add("imlvl_norm", splitCache[0]);
auxData.Add("imlvl_night", splitCache[1]);
auxData.Add("imlvl_hell", splitCache[2]);
// Max TC
splitCache = SplitTreasureClassData(firstRowSegments[5]);
auxData.Add("imtrclass_norm", splitCache[0]);
auxData.Add("imtrclass_night", splitCache[1]);
auxData.Add("imtrclass_hell", splitCache[2]);
// Experience
splitCache = SplitAggregateData(firstRowSegments[2]);
auxData.Add("imxp_norm", splitCache[0]);
auxData.Add("imxp_night", splitCache[1]);
auxData.Add("imxp_hell", splitCache[2]);
// Hitpoints
splitCache = SplitAggregateData(firstRowSegments[3]);
auxData.Add("imhp_norm", splitCache[0]);
auxData.Add("imhp_night", splitCache[1]);
auxData.Add("imhp_hell", splitCache[2]);
// Speed (Unused for Diablo2.io
// We won't parse it because we don't need to
/**
* Parse Defenses: Defense, Blocking, Regen Rate, Drain Effectiveness, Chill Effectiveness
*/
Console.WriteLine("Input Second Row 'Defense' Data:");
input = Console.ReadLine();
string[] secondRowSegments = input.Split('_');
// Defense
splitCache = SplitAggregateData(secondRowSegments[0]);
auxData.Add("imdefense_norm", splitCache[0]);
auxData.Add("imdefense_night", splitCache[1]);
auxData.Add("imdefense_hell", splitCache[2]);
// Blocking
splitCache = ParseBlockData(secondRowSegments[1]);
auxData.Add("imblock_norm", splitCache[0]);
auxData.Add("imblock_night", splitCache[1]);
auxData.Add("imblock_hell", splitCache[2]);
// Regen Rate
// We won't parse it because we don't need to
// Drain Effectiveness
splitCache = SplitAggregateData(secondRowSegments[3]);
auxData.Add("imdrain_norm", splitCache[0]);
auxData.Add("imdrain_night", splitCache[1]);
auxData.Add("imdrain_hell", splitCache[2]);
// Chill Effectiveness
splitCache = SplitAggregateData(secondRowSegments[4]);
auxData.Add("imchill_norm", splitCache[0]);
auxData.Add("imchill_night", splitCache[1]);
auxData.Add("imchill_hell", splitCache[2]);
/**
* Parse Resistances: Physical, Fire, Cold, Lightning, Poison, Magic
*/
Console.WriteLine("Define the Resistance Table Layout:");
Console.WriteLine("Enter '1' for a layout of: 'Physical, Fire, Cold, Lightning, Poison, Magic'");
Console.WriteLine("Enter '2' for a layout of: 'Physical, Magic, Fire, Cold, Lightning, Poison'");
// Ask the user for the table layout culture of the monster
input = Console.ReadLine();
if (Int32.TryParse(input, out int tlcInput))
{
resistanceTableLayoutCulture = tlcInput;
}
else
{
throw new Exception("Table Layout must an integer between 1 and 2!");
}
// Configure Resistance Table Layout
int resPhysicalTableIndex = 0;
int resMagicTableIndex = 0;
int resFireTableIndex = 0;
int resColdTableIndex = 0;
int resLightTableIndex = 0;
int resPoisonTableIndex = 0;
switch (resistanceTableLayoutCulture)
{
case 1: // Layout: Physical, Fire, Cold, Lightning, Poison, Magic
resPhysicalTableIndex = 0;
resMagicTableIndex = 5;
resFireTableIndex = 1;
resColdTableIndex = 2;
resLightTableIndex = 3;
resPoisonTableIndex = 4;
break;
case 2: // Layout: Physical, Magic, Fire, Cold, Lightning, Poison
resPhysicalTableIndex = 0;
resMagicTableIndex = 1;
resFireTableIndex = 2;
resColdTableIndex = 3;
resLightTableIndex = 4;
resPoisonTableIndex = 5;
break;
default:
break;
}
Console.WriteLine("Input Third Row 'Resistances' Data:");
input = Console.ReadLine();
string[] thirdRowSegments = input.Split('_');
// Physical
splitCache = SplitAggregateData(thirdRowSegments[resPhysicalTableIndex]);
auxData.Add("imdmgres_norm", splitCache[0].Replace("%", ""));
auxData.Add("imdmgres_night", splitCache[1].Replace("%", ""));
auxData.Add("imdmgres_hell", splitCache[2].Replace("%", ""));
// Fire
splitCache = SplitAggregateData(thirdRowSegments[resFireTableIndex]);
auxData.Add("imfireres_norm", splitCache[0].Replace("%", ""));
auxData.Add("imfireres_night", splitCache[1].Replace("%", ""));
auxData.Add("imfireres_hell", splitCache[2].Replace("%", ""));
// Cold
splitCache = SplitAggregateData(thirdRowSegments[resColdTableIndex]);
auxData.Add("imcoldres_norm", splitCache[0].Replace("%", ""));
auxData.Add("imcoldres_night", splitCache[1].Replace("%", ""));
auxData.Add("imcoldres_hell", splitCache[2].Replace("%", ""));
// Lightning
splitCache = SplitAggregateData(thirdRowSegments[resLightTableIndex]);
auxData.Add("imlightres_norm", splitCache[0].Replace("%", ""));
auxData.Add("imlightres_night", splitCache[1].Replace("%", ""));
auxData.Add("imlightres_hell", splitCache[2].Replace("%", ""));
// Poison
splitCache = SplitAggregateData(thirdRowSegments[resPoisonTableIndex]);
auxData.Add("impoisres_norm", splitCache[0].Replace("%", ""));
auxData.Add("impoisres_night", splitCache[1].Replace("%", ""));
auxData.Add("impoisres_hell", splitCache[2].Replace("%", ""));
// Magic
splitCache = SplitAggregateData(thirdRowSegments[resMagicTableIndex]);
auxData.Add("immagicres_norm", splitCache[0].Replace("%", ""));
auxData.Add("immagicres_night", splitCache[1].Replace("%", ""));
auxData.Add("immagicres_hell", splitCache[2].Replace("%", ""));
/**
* We need to manually input attack1 and attack2 as well as attackrating1 and attackrating2
* because there are far too many ways of inputing this data, and it's nearly
* impossible, if not literally impossible to parse.
*/
Console.WriteLine("Input Attack1 Damage and Attack Rating (Enter 'null' to skip)");
Console.WriteLine("Format Hint: 'DamageNormal/DamageNightmare/DamageHell_ARNormal/ARNightmare/ARHell'");
Console.WriteLine("Format Example: '1-4/1-4/1-4_20/20/20'");
input = Console.ReadLine();
if (!input.Equals("null"))
{
string[] attack1DamAndARSegments = input.Split("_");
// Attack1 Damage
splitCache = SplitAggregateData(attack1DamAndARSegments[0]);
auxData.Add("imattack1_norm", splitCache[0].Replace(",", "").Trim());
auxData.Add("imattack1_night", splitCache[1].Replace(",", "").Trim());
auxData.Add("imattack1_hell", splitCache[2].Replace(",", "").Trim());
// Attack1 Attack Rating
splitCache = SplitAggregateData(attack1DamAndARSegments[1]);
auxData.Add("imrating1_norm", splitCache[0].Replace(",", "").Trim());
auxData.Add("imrating1_night", splitCache[1].Replace(",", "").Trim());
auxData.Add("imrating1_hell", splitCache[2].Replace(",", "").Trim());
}
else
{
// Fill in null data
auxData.Add("imattack1_norm", "0");
auxData.Add("imattack1_night", "0");
auxData.Add("imattack1_hell", "0");
auxData.Add("imrating1_norm", "0");
auxData.Add("imrating1_night", "0");
auxData.Add("imrating1_hell", "0");
}
Console.WriteLine("Input Attack2 Damage and Attack Rating (Enter 'null' to skip)");
Console.WriteLine("Format Hint: 'DamageNormal/DamageNightmare/DamageHell_ARNormal/ARNightmare/ARHell'");
Console.WriteLine("Format Example: '1-4/1-4/1-4_20/20/20'");
input = Console.ReadLine();
if (!input.Equals("null"))
{
string[] attack2DamAndARSegments = input.Split("_");
// Attack2 Damage
splitCache = SplitAggregateData(attack2DamAndARSegments[0]);
auxData.Add("imattack2_norm", splitCache[0].Replace(",", "").Trim());
auxData.Add("imattack2_night", splitCache[1].Replace(",", "").Trim());
auxData.Add("imattack2_hell", splitCache[2].Replace(",", "").Trim());
// Attack2 Attack Rating
splitCache = SplitAggregateData(attack2DamAndARSegments[1]);
auxData.Add("imrating2_norm", splitCache[0].Replace(",", "").Trim());
auxData.Add("imrating2_night", splitCache[1].Replace(",", "").Trim());
auxData.Add("imrating2_hell", splitCache[2].Replace(",", "").Trim());
}
else
{
// Fill in null data
auxData.Add("imattack2_norm", "0");
auxData.Add("imattack2_night", "0");
auxData.Add("imattack2_hell", "0");
auxData.Add("imrating2_norm", "0");
auxData.Add("imrating2_night", "0");
auxData.Add("imrating2_hell", "0");
}
/**
* Next, we need to parse for immunities using the data we already gained.
* If any resistance is greater than or equal to 100, we treat it as an immunity.
* This seems to be standard in all the tables I looked at.
*/
#region Automagically Detect Immunity Shenanigans
string normalImmunity = "0";
string nightmareImmunity = "0";
string hellImmunity1 = "0";
string hellImmunity2 = "0";
foreach (KeyValuePair<string, string> pair in auxData)
{
switch (pair.Key)
{
case "imdmgres_norm":
case "immagicres_norm":
case "imfireres_norm":
case "imcoldres_norm":
case "imlightres_norm":
case "impoisres_norm":
if (normalImmunity.Equals("0"))
{
if (pair.Value.StartsWith("-"))
{
// The resistance is negative
continue;
}
else
{
if (Int32.TryParse(pair.Value, out int normalResistance))
{
if (normalResistance >= 100)
{
switch (pair.Key)
{
case "imdmgres_norm":
normalImmunity = "Physical";
break;
case "immagicres_norm":
normalImmunity = "Magic";
break;
case "imfireres_norm":
normalImmunity = "Fire";
break;
case "imcoldres_norm":
normalImmunity = "Cold";
break;
case "imlightres_norm":
normalImmunity = "Lightning";
break;
case "impoisres_norm":
normalImmunity = "Poison";
break;
default:
break; // Should never happen.
}
}
}
}
}
break;
case "imdmgres_night":
case "immagicres_night":
case "imfireres_night":
case "imcoldres_night":
case "imlightres_night":
case "impoisres_night":
if (nightmareImmunity.Equals("0"))
{
if (pair.Value.StartsWith("-"))
{
// The resistance is negative
continue;
}
else
{
if (Int32.TryParse(pair.Value, out int nightmareResistance))
{
if (nightmareResistance >= 100)
{
switch (pair.Key)
{
case "imdmgres_night":
nightmareImmunity = "Physical";
break;
case "immagicres_night":
nightmareImmunity = "Magic";
break;
case "imfireres_night":
nightmareImmunity = "Fire";
break;
case "imcoldres_night":
nightmareImmunity = "Cold";
break;
case "imlightres_night":
nightmareImmunity = "Lightning";
break;
case "impoisres_night":
nightmareImmunity = "Poison";
break;
default:
break; // Should never happen.
}
}
}
}
}
break;
case "imdmgres_hell":
case "immagicres_hell":
case "imfireres_hell":
case "imcoldres_hell":
case "imlightres_hell":
case "impoisres_hell":
/**
* An inherent edge case exists here, because Hell difficulty
* is listed as having two possible immunities.
*/
if (pair.Value.StartsWith("-"))
{
// The resistance is negative
continue;
}
else
{
if (Int32.TryParse(pair.Value, out int hellResistance))
{
if (hellResistance >= 100)
{
switch (pair.Key)
{
case "imdmgres_hell":
if (hellImmunity1.Equals("0"))
{
hellImmunity1 = "Physical";
}
else
{
hellImmunity2 = "Physical";
}
break;
case "immagicres_hell":
if (hellImmunity1.Equals("0"))
{
hellImmunity1 = "Magic";
}
else
{
hellImmunity2 = "Magic";
}
break;
case "imfireres_hell":
if (hellImmunity1.Equals("0"))
{
hellImmunity1 = "Fire";
}
else
{
hellImmunity2 = "Fire";
}
break;
case "imcoldres_hell":
if (hellImmunity1.Equals("0"))
{
hellImmunity1 = "Cold";
}
else
{
hellImmunity2 = "Cold";
}
break;
case "imlightres_hell":
if (hellImmunity1.Equals("0"))
{
hellImmunity1 = "Lightning";
}
else
{
hellImmunity2 = "Lightning";
}
break;
case "impoisres_hell":
if (hellImmunity1.Equals("0"))
{
hellImmunity1 = "Poison";
}
else
{
hellImmunity2 = "Poison";
}
break;
default:
break; // Should never happen.
}
}
}
}
break;
default:
break;
}
}
// Add the new immunity data to the auxdata
auxData.Add("imimm_norm", normalImmunity);
auxData.Add("imimm_night", nightmareImmunity);
auxData.Add("imimm_hell", hellImmunity1);
auxData.Add("imimm_hell2", hellImmunity2);
#endregion
/**
* Continue to debugging and then finally generating
* and printing data out.
*/
if (isDebugging)
{
Console.WriteLine();
Console.WriteLine("Printing Debug Log...");
foreach (KeyValuePair<string, string> pair in auxData)
{
Console.WriteLine(pair.Key + " " + pair.Value);
}
Console.WriteLine("Debug Log Finished.");
}
/**
* Generate Diablo2.io Markup
*/
Console.WriteLine();
Console.WriteLine("Generating Diablo2.io Markup...");
builder.AppendLine("[zform][form][b]irarity[/b][/form][fbox]Monster (Auxiliary)[/fbox][/zform]");
builder.AppendLine("[zform][form][b]igraphic[/b][/form][att][attachment=0][/attachment][/att][/zform]");
foreach (KeyValuePair<string, string> pair in auxData)
{
if (filterNullData)
{
if (pair.Value.Equals("0"))
{
continue;
}
}
builder.AppendLine("[zform][form][b]" + pair.Key + "[/b][/form][fbox]" + pair.Value + "[/fbox][/zform]");
}
Console.WriteLine("Finished generating markup!");
Console.WriteLine("Printing Diablo2.io Aux Markup!");
Console.WriteLine(builder.ToString());
}
}
}