DS-Pokemon-Rom-Editor/DS_Map/DocTool.cs

457 lines
20 KiB
C#

using DSPRE.Resources;
using DSPRE.ROMFiles;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Documents;
using System.Windows.Forms;
using static DSPRE.MoveData;
using static DSPRE.RomInfo;
namespace DSPRE
{
internal class DocTool
{
public static void ExportAll()
{
// Create the subfolder Docs in the executable directory and write the CSV files there
string executablePath = AppDomain.CurrentDomain.BaseDirectory;
string docsFolderPath = Path.Combine(executablePath, "Docs");
string pokePersonalDataPath = Path.Combine(docsFolderPath, "PokemonPersonalData.csv");
string learnsetDataPath = Path.Combine(docsFolderPath, "LearnsetData.csv");
string evolutionDataPath = Path.Combine(docsFolderPath, "EvolutionData.csv");
string trainerDataPath = Path.Combine(docsFolderPath, "TrainerData.txt");
string moveDataPath = Path.Combine(docsFolderPath, "MoveData.csv");
string TMHMDataPath = Path.Combine(docsFolderPath, "TMHMData.csv");
DSUtils.TryUnpackNarcs(new List<DirNames> { DirNames.personalPokeData, DirNames.learnsets, DirNames.evolutions, DirNames.trainerParty, DirNames.trainerProperties ,DirNames.moveData, DirNames.itemData});
string[] pokeNames = RomInfo.GetPokemonNames();
string[] itemNames = RomInfo.GetItemNames();
string[] abilityNames = RomInfo.GetAbilityNames();
string[] moveNames = RomInfo.GetAttackNames();
string[] trainerNames = RomInfo.GetSimpleTrainerNames();
string[] trainerClassNames = RomInfo.GetTrainerClassNames();
string[] typeNames = RomInfo.GetTypeNames();
// Handle Forms
int extraCount = RomInfo.GetPersonalFilesCount() - pokeNames.Length;
string[] extraNames = new string[extraCount];
for (int i = 0; i < extraCount; i++)
{
PokeDatabase.PersonalData.PersonalExtraFiles extraEntry = PokeDatabase.PersonalData.personalExtraFiles[i];
extraNames[i] = pokeNames[extraEntry.monId] + " - " + extraEntry.description;
}
pokeNames = pokeNames.Concat(extraNames).ToArray();
// Create the Docs folder if it doesn't exist
if (!Directory.Exists(docsFolderPath))
{
Directory.CreateDirectory(docsFolderPath);
}
ExportPersonalDataToCSV(pokePersonalDataPath, pokeNames, abilityNames, typeNames);
ExportLearnsetDataToCSV(learnsetDataPath, pokeNames, moveNames);
ExportEvolutionDataToCSV(evolutionDataPath, pokeNames, itemNames, moveNames);
ExportTrainersToText(trainerDataPath, trainerNames, trainerClassNames, pokeNames, itemNames, moveNames, abilityNames);
ExportMoveDataToCSV(moveDataPath, moveNames, typeNames);
ExportTMHMDataToCSV(TMHMDataPath, pokeNames);
MessageBox.Show($"CSV files exported successfully to path: {docsFolderPath}");
}
private static void ExportPersonalDataToCSV(string pokePersonalDataPath, string[] pokeNames, string[] abilityNames, string[] typeNames)
{
// Write the Pokemon Personal Data to the CSV file
PokemonPersonalData curPersonalData = null;
StreamWriter sw = new StreamWriter(pokePersonalDataPath);
sw.WriteLine("ID,Name,Type1,Type2,BaseHP,BaseAttack,BaseDefense,BaseSpecialAttack,BaseSpecialDefense,BaseSpeed," +
"Ability1,Ability2");
for (int i = 0; i < RomInfo.GetPersonalFilesCount(); i++)
{
curPersonalData = new PokemonPersonalData(i);
string type1String = (int) curPersonalData.type1 < typeNames.Length ? typeNames[(int)curPersonalData.type1] : "UnknownType_" + (int)curPersonalData.type1;
string type2String = (int) curPersonalData.type2 < typeNames.Length ? typeNames[(int)curPersonalData.type2] : "UnknownType_" + (int)curPersonalData.type2;
sw.WriteLine($"{i},{pokeNames[i]},{type1String},{type2String}," +
$"{curPersonalData.baseHP},{curPersonalData.baseAtk},{curPersonalData.baseDef}, " +
$"{curPersonalData.baseSpAtk},{curPersonalData.baseSpDef},{curPersonalData.baseSpeed}," +
$"{abilityNames[curPersonalData.firstAbility]},{abilityNames[curPersonalData.secondAbility]}");
}
sw.Close();
}
private static void ExportLearnsetDataToCSV(string learnsetDataPath, string[] pokeNames, string[] moveNames)
{
// Write the Learnset Data to the CSV file
LearnsetData curLearnsetData = null;
StreamWriter sw = new StreamWriter(learnsetDataPath);
sw.WriteLine("ID,Name,[Level,Move]");
for (int i = 0; i < RomInfo.GetLearnsetFilesCount(); i++)
{
curLearnsetData = new LearnsetData(i);
sw.Write($"{i},{pokeNames[i]}");
foreach (var entry in curLearnsetData.list)
{
sw.Write($",[{entry.level},{moveNames[entry.move]}]");
}
sw.WriteLine();
}
sw.Close();
}
private static void ExportEvolutionDataToCSV(string evolutionDataPath, string[] pokeNames, string[] itemNames, string[] moveNames)
{
// Write the Evolution Data to the CSV file
EvolutionFile curEvolutionFile = null;
StreamWriter sw = new StreamWriter(evolutionDataPath);
sw.WriteLine("ID,Name,[Method|Param|Target]");
for (int i = 0; i < RomInfo.GetEvolutionFilesCount(); i++)
{
curEvolutionFile = new EvolutionFile(i);
sw.Write($"{i},{pokeNames[i]}");
foreach (var entry in curEvolutionFile.data)
{
EvolutionParamMeaning meaning = EvolutionFile.evoDescriptions[entry.method];
string paramString = "";
switch (meaning)
{
case EvolutionParamMeaning.Ignored:
paramString = "Ignored";
break;
case EvolutionParamMeaning.FromLevel:
paramString = entry.param.ToString();
break;
case EvolutionParamMeaning.ItemName:
paramString = itemNames[entry.param];
break;
case EvolutionParamMeaning.MoveName:
paramString = moveNames[entry.param];
break;
case EvolutionParamMeaning.PokemonName:
paramString = pokeNames[entry.param];
break;
case EvolutionParamMeaning.BeautyValue:
paramString = entry.param.ToString();
break;
}
if (entry.target == 0)
{
break;
}
sw.Write($",[{entry.method}|{paramString}|{pokeNames[entry.target]}]");
}
sw.WriteLine();
}
sw.Close();
}
private static void ExportTrainersToText(string trainerDataPath, string[] trainerNames, string[] trainerClassNames, string[] pokeNames, string[] itemNames, string[] moveNames, string[] abilityNames)
{
// Write the Trainer Data to the Text file
TrainerFile curTrainerFile = null;
TrainerProperties curTrainerProperties = null;
FileStream curTrainerParty = null;
StreamWriter sw = new StreamWriter(trainerDataPath);
int trainerCount = Directory.GetFiles(RomInfo.gameDirs[DirNames.trainerProperties].unpackedDir).Length;
for (int i = 1; i < trainerCount; i++)
{
string suffix = "\\" + i.ToString("D4");
curTrainerProperties = new TrainerProperties((ushort)i,
new FileStream(RomInfo.gameDirs[DirNames.trainerProperties].unpackedDir + suffix, FileMode.Open));
curTrainerParty = new FileStream(RomInfo.gameDirs[DirNames.trainerParty].unpackedDir + suffix, FileMode.Open);
curTrainerFile = new TrainerFile(curTrainerProperties, curTrainerParty, trainerNames[i]);
string trainerName = trainerNames[i];
string trainerClass = trainerClassNames[curTrainerProperties.trainerClass];
string[] trainerItems = curTrainerProperties.trainerItems.Select(item => item != 0 ? itemNames[(int)item] : "None").ToArray();
// Create array of party pokemon
PartyPokemon[] partyPokemon = new PartyPokemon[curTrainerProperties.partyCount];
// Now that we have the party pokemons, we can declare the arrays to store the data
string[] monNames = new string[partyPokemon.Length];
PartyPokemon.GenderAndAbilityFlags[] monFlags = new PartyPokemon.GenderAndAbilityFlags[partyPokemon.Length];
string[] items = new string[partyPokemon.Length];
int[] levels = new int[partyPokemon.Length];
int[] ivs = new int[partyPokemon.Length];
string[][] moves = new string[partyPokemon.Length][];
for (int j = 0; j < partyPokemon.Length; j++)
{
// This assumes that the non-empty mons are at the beginning of the party array which they should be
// if there is some way for this not to be the case, the program will crash
partyPokemon[j] = curTrainerFile.party[j];
// Type cast can be done because CountNonEmptyMons() only returns non-empty mons i.e. mons with non-null pokeID
monNames[j] = pokeNames[(int)partyPokemon[j].pokeID];
monFlags[j] = partyPokemon[j].genderAndAbilityFlags;
// Need to account for the case where the mon has no held item
if (partyPokemon[j].heldItem != null)
{
items[j] = itemNames[(int)partyPokemon[j].heldItem];
}
else
{
items[j] = "None";
}
levels[j] = partyPokemon[j].level;
ivs[j] = partyPokemon[j].difficulty * 31 / 255;
// Need to account for the case where the mon has no moves
if (partyPokemon[j].moves == null)
{
LearnsetData learnset = new LearnsetData((int)partyPokemon[j].pokeID);
moves[j] = learnset.GetLearnsetAtLevel(levels[j]).Select(move => moveNames[move]).ToArray();
}
else
{
moves[j] = partyPokemon[j].moves.Select(move => moveNames[move]).ToArray();
}
}
string[] monGenders = new string[partyPokemon.Length];
string[] abilities = new string[partyPokemon.Length];
string[] natures = new string[partyPokemon.Length];
// This function sets the monGenders, abilities and natures arrays
// We hide this away in a function because it's a bit complex
// and we don't want to clutter the main function more than it already is
SetMonGendersAndAbilitiesAndNature(i, curTrainerProperties.trainerClass, partyPokemon, monFlags, ref abilityNames, ref monGenders, ref abilities, ref natures);
sw.Write(TrainerToDocFormat(i, trainerName, trainerClass, trainerItems, monNames, monGenders, items, abilities, levels, natures, ivs, moves));
}
sw.Close();
}
private static void ExportMoveDataToCSV(string moveDataPath, string[] moveNames, string[] typeNames)
{
StreamWriter sw = new StreamWriter(moveDataPath);
string[] moveFlags = Enum.GetNames(typeof(MoveData.MoveFlags));
string[] attackRange = Enum.GetNames(typeof(MoveData.AttackRange));
string[] battleSeqDesc = PokeDatabase.MoveData.battleSequenceDescriptions;
sw.WriteLine("Move ID,Move Name,Move Type,Move Split,Power,Accuracy,Priority,Side Effect Probability,PP,Ranges,Flags,Effect Description");
for (int i = 0; i < moveNames.Length; i++)
{
MoveData curMoveDataFile = new MoveData(i);
// Lambda magic to select the flags that are set, skipping the first enum entry (no flags)
string moveFlagsString = string.Join("|", moveFlags.Skip(1).Select((flag, index)
=> (curMoveDataFile.flagField & (1 << index)) != 0 ? flag : "").Where(flag => !string.IsNullOrEmpty(flag)));
string attackRangeString = string.Join("|", attackRange.Skip(1).Select((range, index)
=> (curMoveDataFile.target & (1 << index)) != 0 ? range : "").Where(range => !string.IsNullOrEmpty(range)));
string battleSeqDescString = curMoveDataFile.battleeffect < battleSeqDesc.Length ?
battleSeqDesc[curMoveDataFile.battleeffect] : "UnknownEffect_" + curMoveDataFile.battleeffect;
string typeString = (int)curMoveDataFile.movetype < typeNames.Length ?
typeNames[(int)curMoveDataFile.movetype] : "UnknownType_" + (int)curMoveDataFile.movetype;
sw.WriteLine($"{i},{moveNames[i]},{typeString},{curMoveDataFile.split}," +
$"{curMoveDataFile.damage},{curMoveDataFile.accuracy},{curMoveDataFile.priority}," +
$"{curMoveDataFile.sideEffectProbability},{curMoveDataFile.pp}," +
$"[{attackRangeString}],[{moveFlagsString}],{battleSeqDescString}");
}
sw.Close();
}
private static void ExportTMHMDataToCSV(string THHMDataPath, string[] pokeNames)
{
// Write the TM/HM Data to the CSV file
PokemonPersonalData curPersonalData = null;
StreamWriter sw = new StreamWriter(THHMDataPath);
sw.Write("ID,Name");
int totalTMs = PokemonPersonalData.tmsCount + PokemonPersonalData.hmsCount;
// Write Header (List of all TMs/HMs)
for (int count = 0; count < totalTMs; count++)
{
string currentItem = MachineNameFromIndex(count);
sw.Write($",{currentItem}");
}
sw.WriteLine();
for (int i = 0; i < RomInfo.GetPersonalFilesCount(); i++)
{
curPersonalData = new PokemonPersonalData(i);
sw.Write($"{i},{pokeNames[i]},[");
// Slight code duplication to PersonalDataEditor here
for (byte b = 0; b < totalTMs; b++)
{
sw.Write(b == 0 ? "" : ",");
sw.Write(curPersonalData.machines.Contains(b) ? "true" : "false");
}
sw.WriteLine("]");
}
sw.Close();
}
private static string TrainerToDocFormat(int index, string trainerName, string trainerClass, string[] trainerItems, string[] monNames, string[] monGenders, string[] items, string[] abilities,
int[] levels, string[] natures, int[] ivs, string[][] moves)
{
StringBuilder sb = new StringBuilder();
sb.Append($"[{index}] {trainerClass} {trainerName}");
// If trainer has at least one item then list all non-zero id items behind the trainer name
if (trainerItems.Length > 0 && trainerItems[0] != "None")
{
sb.Append(" @ (");
sb.Append(string.Join(", ", trainerItems.Where(item => item != "None")));
sb.Append(")");
}
sb.Append(":\n\n");
for (int i = 0; i < monNames.Length; i++)
{
sb.Append(MonToShowdownFormat(monNames[i], monGenders[i], items[i], abilities[i], levels[i], natures[i], ivs[i], moves[i]));
sb.Append("\n\n");
}
sb.Append("\n\n\n");
return sb.ToString();
}
private static string MonToShowdownFormat(string monName, string gender, string itemName, string ability,
int level, string nature, int ivs, string[] moves)
{
StringBuilder sb = new StringBuilder();
sb.Append($"{monName}");
if (gender != "random")
{
sb.Append($" ({gender})");
}
if (itemName != "None")
{
sb.Append($" @ {itemName}");
}
sb.Append("\nAbility: " + ability);
sb.Append("\nLevel: " + level);
sb.Append("\n"+ nature + " Nature");
sb.Append("\nIVs: " + string.Join(" / ", Enumerable.Repeat(ivs.ToString(), 6)));
moves = moves.Where(move => (move != "None" && move != "-")).ToArray();
sb.Append("\n- " + string.Join("\n- ", moves));
return sb.ToString();
}
// Slight code duplication to PersonalDataEditor here
private static string MachineNameFromIndex(int n)
{
//0-91 --> TMs
//>=92 --> HM
n += 1;
int diff = n - PokemonPersonalData.tmsCount;
string type = diff > 0 ? "HM" : "TM";
string item = $"{type}{(diff > 0 ? diff : n):D2}";
return item;
}
private static void SetMonGendersAndAbilitiesAndNature(int trainerID, int trainerClassID, PartyPokemon[] partyPokemon,
PartyPokemon.GenderAndAbilityFlags[] monFlags, ref string[] abilityNames,
ref string[] monGenders, ref string[] abilities, ref string[] natures)
{
bool trainerMale = false;
trainerMale = DVCalculator.TrainerClassGender.GetTrainerClassGender(trainerClassID);
DVCalculator.ResetGenderMod(trainerMale);
// Get Pokemon Genders and Abilities from flags
for (int j = 0; j < partyPokemon.Length; j++)
{
byte baseGenderRatio = new PokemonPersonalData((int)partyPokemon[j].pokeID).genderVec;
byte genderOverride = (byte)((byte) monFlags[j] & 0x0F); // Get the lower 4 bits
byte abilityOverride = (byte)((byte)monFlags[j] >> 4); // Get the upper 4 bits
uint PID = DVCalculator.generatePID((uint)trainerID, (uint)trainerClassID, (uint)partyPokemon[j].pokeID, (byte)partyPokemon[j].level, baseGenderRatio, genderOverride, abilityOverride, partyPokemon[j].difficulty);
natures[j] = DVCalculator.Natures[DVCalculator.getNatureFromPID(PID)].Split(':')[0];
switch (genderOverride)
{
case 0: // Random
monGenders[j] = "random";
break;
case 1: // Male
monGenders[j] = "M";
break;
case 2: // Female
monGenders[j] = "F";
break;
}
switch (PID % 2) // Lowest bit of PID determines the ability
{
case 0:
abilities[j] = abilityNames[new PokemonPersonalData((int)partyPokemon[j].pokeID).firstAbility];
break;
case 1:
abilities[j] = abilityNames[new PokemonPersonalData((int)partyPokemon[j].pokeID).secondAbility];
break;
}
}
}
}
}