mirror of
https://github.com/AdAstra-LD/DS-Pokemon-Rom-Editor.git
synced 2026-05-10 14:11:04 -05:00
457 lines
20 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|