package random.gba.loader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.stream.Collectors; import fedata.gba.GBAFECharacterData; import fedata.gba.GBAFEClassData; import fedata.gba.GBAFEItemData; import fedata.gba.GBAFESpellAnimationCollection; import fedata.gba.general.GBAFEClass; import fedata.gba.general.GBAFEItem; import fedata.gba.general.GBAFEItemProvider; import fedata.gba.general.GBAFEItemProvider.WeaponRanks; import fedata.gba.general.GBAFEPromotionItem; import fedata.gba.general.WeaponRank; import fedata.gba.general.WeaponType; import io.FileHandler; import util.Diff; import util.DiffCompiler; import util.FileReadHelper; import util.FreeSpaceManager; import util.WhyDoesJavaNotHaveThese; import util.recordkeeper.RecordKeeper; public class ItemDataLoader { private GBAFEItemProvider provider; public enum AdditionalData { STR_MAG_BOOST("STRMAG"), SKL_BOOST("SKL"), SPD_BOOST("SPD"), DEF_BOOST("DEF"), RES_BOOST("RES"), LCK_BOOST("LCK"), KNIGHTCAV_EFFECT("EFF_KNIGHT_CAV"), KNIGHT_EFFECT("EFF_KNIGHT"), DRAGON_EFFECT("EFF_DRAGON"), CAVALRY_EFFECT("EFF_CAVALRY"), MYRMIDON_EFFECT("EFF_MYRMIDON"), FLIERS_EFFECT("EFF_FLIER"), MONSTER_EFFECT("EFF_MONSTER"); String key; private AdditionalData(String key) { this.key = key; } } private Map<Integer, GBAFEItemData> itemMap = new HashMap<Integer, GBAFEItemData>(); // TODO: Put this somewhere else. public GBAFESpellAnimationCollection spellAnimations; private FreeSpaceManager freeSpace; private Map<AdditionalData, Long> offsetsForAdditionalData; private Map<String, Long> promotionItemAddressPointers; public static final String RecordKeeperCategoryWeaponKey = "Weapons"; public ItemDataLoader(GBAFEItemProvider provider, FileHandler handler, FreeSpaceManager freeSpace) { super(); this.freeSpace = freeSpace; this.provider = provider; long baseAddress = FileReadHelper.readAddress(handler, provider.itemTablePointer()); for (GBAFEItem item : provider.allItems()) { if (item.getID() == 0) { continue; } long offset = baseAddress + (provider.bytesPerItem() * item.getID()); byte[] itemData = handler.readBytesAtOffset(offset, provider.bytesPerItem()); itemMap.put(item.getID(), provider.itemDataWithData(itemData, offset, item.getID())); } long spellAnimationBaseAddress = FileReadHelper.readAddress(handler, provider.spellAnimationTablePointer()); byte[] spellAnimationData = handler.readBytesAtOffset(spellAnimationBaseAddress, provider.numberOfAnimations() * provider.bytesPerAnimation()); spellAnimations = provider.spellAnimationCollectionAtAddress(spellAnimationData, spellAnimationBaseAddress); offsetsForAdditionalData = new HashMap<AdditionalData, Long>(); // Set up effectiveness. registerAdditionalData(AdditionalData.KNIGHTCAV_EFFECT, classByteArrayFromClassList(provider.knightCavEffectivenessClasses())); registerAdditionalData(AdditionalData.KNIGHT_EFFECT, classByteArrayFromClassList(provider.knightEffectivenessClasses())); registerAdditionalData(AdditionalData.CAVALRY_EFFECT, classByteArrayFromClassList(provider.cavalryEffectivenessClasses())); registerAdditionalData(AdditionalData.DRAGON_EFFECT, classByteArrayFromClassList(provider.dragonEffectivenessClasses())); registerAdditionalData(AdditionalData.MYRMIDON_EFFECT, classByteArrayFromClassList(provider.myrmidonEffectivenessClasses())); registerAdditionalData(AdditionalData.FLIERS_EFFECT, classByteArrayFromClassList(provider.flierEffectivenessClasses())); registerAdditionalData(AdditionalData.MONSTER_EFFECT, classByteArrayFromClassList(provider.monsterEffectivenessClasses())); // Set up stat boosts. long offset = freeSpace.setValue(new byte[] {0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, AdditionalData.STR_MAG_BOOST.key); offsetsForAdditionalData.put(AdditionalData.STR_MAG_BOOST, offset); offset = freeSpace.setValue(new byte[] {0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}, AdditionalData.SKL_BOOST.key); offsetsForAdditionalData.put(AdditionalData.SKL_BOOST, offset); offset = freeSpace.setValue(new byte[] {0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00}, AdditionalData.SPD_BOOST.key); offsetsForAdditionalData.put(AdditionalData.SPD_BOOST, offset); offset = freeSpace.setValue(new byte[] {0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00}, AdditionalData.DEF_BOOST.key); offsetsForAdditionalData.put(AdditionalData.DEF_BOOST, offset); offset = freeSpace.setValue(new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00}, AdditionalData.RES_BOOST.key); offsetsForAdditionalData.put(AdditionalData.RES_BOOST, offset); offset = freeSpace.setValue(new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00}, AdditionalData.LCK_BOOST.key); offsetsForAdditionalData.put(AdditionalData.LCK_BOOST, offset); // Set up promotion items. promotionItemAddressPointers = new HashMap<String, Long>(); for (GBAFEPromotionItem promotionItem : provider.allPromotionItems()) { long promotionItemOffset = promotionItem.getListAddress(); if (promotionItem.isIndirected()) { promotionItemOffset = FileReadHelper.readAddress(handler, promotionItemOffset) + 4; } List<Byte> idList = new ArrayList<Byte>(); byte currentByte = 0x0; long currentOffset = FileReadHelper.readAddress(handler, promotionItemOffset); // One more jump here. do { currentByte = handler.readBytesAtOffset(currentOffset++, 1)[0]; if (currentByte != 0) { idList.add(currentByte); } } while (currentByte != 0); List<GBAFEClass> additionalClasses = provider.additionalClassesForPromotionItem(promotionItem, idList); if (additionalClasses != null && !additionalClasses.isEmpty()) { for (int i = 0; i < additionalClasses.size(); i++) { GBAFEClass additionalClass = additionalClasses.get(i); if (!idList.contains((byte)additionalClass.getID())) { idList.add((byte)additionalClass.getID()); } } idList.add((byte)0x0); byte[] byteArray = new byte[idList.size()]; for (int i = 0; i < idList.size(); i++) { byteArray[i] = idList.get(i); } offset = freeSpace.setValue(byteArray, promotionItem.itemName()); promotionItemAddressPointers.put(promotionItem.itemName(), promotionItemOffset); } } } private void registerAdditionalData(AdditionalData dataName, byte[] byteArray) { if (byteArray.length > 0) { long offset = freeSpace.setValue(byteArray, dataName.key); offsetsForAdditionalData.put(dataName, offset); } } private byte[] classByteArrayFromClassList(List<GBAFEClass> classList) { if (classList == null || classList.size() == 0) { return new byte[] {}; } Boolean addTerminal = false; if (classList.get(classList.size() - 1).getID() != 0) { addTerminal = true; } byte[] byteArray = new byte[classList.size() + (addTerminal ? 1 : 0)]; for (int i = 0; i < classList.size(); i++) { byteArray[i] = (byte)(classList.get(i).getID() & 0xFF); } if (addTerminal) { byteArray[byteArray.length - 1] = 0x0; } return byteArray; } public GBAFEItemData itemWithID(int itemID) { return itemMap.get(itemID); } public int getHighestWeaponRank() { return provider.getHighestWeaponRankValue(); } public WeaponRank rankForValue(int value) { return provider.rankWithValue(value); } public WeaponRanks ranksForCharacter(GBAFECharacterData character, GBAFEClassData charClass) { return new WeaponRanks(character, charClass, provider); } public WeaponRanks ranksForClass(GBAFEClassData charClass) { return new WeaponRanks(charClass, provider); } public GBAFEItemData[] getAllWeapons() { return feItemsFromItemSet(provider.allWeapons()); } public long[] possibleStatBoostAddresses() { return new long[] { offsetsForAdditionalData.get(AdditionalData.STR_MAG_BOOST), offsetsForAdditionalData.get(AdditionalData.SKL_BOOST), offsetsForAdditionalData.get(AdditionalData.SPD_BOOST), offsetsForAdditionalData.get(AdditionalData.DEF_BOOST), offsetsForAdditionalData.get(AdditionalData.RES_BOOST), offsetsForAdditionalData.get(AdditionalData.LCK_BOOST)}; } public String descriptionStringForAddress(long address, Boolean isMagic, Boolean shortForm) { if (offsetsForAdditionalData.get(AdditionalData.STR_MAG_BOOST) == address) { return isMagic ? "+5 Magic" : "+5 Strength"; } if (offsetsForAdditionalData.get(AdditionalData.SKL_BOOST) == address) { return "+5 Skill"; } if (offsetsForAdditionalData.get(AdditionalData.SPD_BOOST) == address) { return "+5 Speed"; } if (offsetsForAdditionalData.get(AdditionalData.LCK_BOOST) == address) { return "+5 Luck"; } if (offsetsForAdditionalData.get(AdditionalData.DEF_BOOST) == address) { return "+5 Defense"; } if (offsetsForAdditionalData.get(AdditionalData.RES_BOOST) == address) { return "+5 Resistance"; } if (offsetsForAdditionalData.get(AdditionalData.KNIGHT_EFFECT) == address) { return shortForm ? "Eff. Knights" : "Effective against knights"; } if (offsetsForAdditionalData.get(AdditionalData.KNIGHTCAV_EFFECT) == address) { return shortForm ? "Eff. Infantry" : "Effective against infantry"; } if (offsetsForAdditionalData.get(AdditionalData.CAVALRY_EFFECT) == address) { return shortForm ? "Eff. Cavalry" : "Effective against cavalry"; } if (offsetsForAdditionalData.get(AdditionalData.FLIERS_EFFECT) == address) { return shortForm ? "Eff. Fliers" : "Effective against fliers"; } if (offsetsForAdditionalData.get(AdditionalData.MYRMIDON_EFFECT) != null && address == offsetsForAdditionalData.get(AdditionalData.MYRMIDON_EFFECT)) { return shortForm ? "Eff. Swordfighters" : "Effective against swordfighters"; } if (offsetsForAdditionalData.get(AdditionalData.DRAGON_EFFECT) == address) { return shortForm ? "Eff. Dragons" : "Effective against dragons"; } for (GBAFEItem weapon : provider.weaponsWithStatBoosts()) { if (address == itemMap.get(weapon.getID()).getStatBonusPointer()) { return provider.statBoostStringForWeapon(weapon); } } for (GBAFEItem weapon : provider.weaponsWithEffectiveness()) { if (address == itemMap.get(weapon.getID()).getEffectivenessPointer()) { return provider.effectivenessStringForWeapon(weapon, shortForm); } } return null; } public long flierEffectPointer() { if (offsetsForAdditionalData.containsKey(AdditionalData.FLIERS_EFFECT)) { return offsetsForAdditionalData.get(AdditionalData.FLIERS_EFFECT); } return 0; } public long[] possibleEffectivenessAddresses() { List<Long> registeredEffectivenessPointers = new ArrayList<Long>(); if (offsetsForAdditionalData.containsKey(AdditionalData.KNIGHTCAV_EFFECT)) { registeredEffectivenessPointers.add(offsetsForAdditionalData.get(AdditionalData.KNIGHTCAV_EFFECT)); } if (offsetsForAdditionalData.containsKey(AdditionalData.KNIGHT_EFFECT)) { registeredEffectivenessPointers.add(offsetsForAdditionalData.get(AdditionalData.KNIGHT_EFFECT)); } if (offsetsForAdditionalData.containsKey(AdditionalData.CAVALRY_EFFECT)) { registeredEffectivenessPointers.add(offsetsForAdditionalData.get(AdditionalData.CAVALRY_EFFECT)); } if (offsetsForAdditionalData.containsKey(AdditionalData.DRAGON_EFFECT)) { registeredEffectivenessPointers.add(offsetsForAdditionalData.get(AdditionalData.DRAGON_EFFECT)); } if (offsetsForAdditionalData.containsKey(AdditionalData.FLIERS_EFFECT)) { registeredEffectivenessPointers.add(offsetsForAdditionalData.get(AdditionalData.FLIERS_EFFECT)); } if (offsetsForAdditionalData.containsKey(AdditionalData.MYRMIDON_EFFECT)) { registeredEffectivenessPointers.add(offsetsForAdditionalData.get(AdditionalData.MYRMIDON_EFFECT)); } if (offsetsForAdditionalData.containsKey(AdditionalData.MONSTER_EFFECT)) { registeredEffectivenessPointers.add(offsetsForAdditionalData.get(AdditionalData.MONSTER_EFFECT)); } long[] result = new long[registeredEffectivenessPointers.size()]; for (int i = 0; i < registeredEffectivenessPointers.size(); i++) { result[i] = registeredEffectivenessPointers.get(i); } return result; } public Boolean isWeapon(GBAFEItemData item) { if (item == null) { return false; } return provider.itemWithID(item.getID()).isWeapon(); } public Boolean isBasicWeapon(int itemID) { return provider.itemWithID(itemID).isBasicWeapon(); } public GBAFEItemData basicItemOfType(WeaponType type) { GBAFEItem basicItem = provider.basicWeaponOfType(type); if (basicItem != null) { return itemMap.get(basicItem.getID()); } return null; } public GBAFEItemData[] itemsOfTypeAndBelowRankValue(WeaponType type, int rankValue, Boolean rangedOnly, Boolean requiresMelee) { return itemsOfTypeAndBelowRank(type, provider.rankWithValue(rankValue), rangedOnly, requiresMelee); } public GBAFEItemData[] itemsOfTypeAndBelowRank(WeaponType type, WeaponRank rank, Boolean rangedOnly, Boolean requiresMelee) { return feItemsFromItemSet(provider.weaponsOfTypeUpToRank(type, rank, rangedOnly, requiresMelee)); } public GBAFEItemData[] itemsOfTypeAndEqualRankValue(WeaponType type, int rankValue, Boolean rangedOnly, Boolean requiresMelee, Boolean allowLower) { return itemsOfTypeAndEqualRank(type, provider.rankWithValue(rankValue), rangedOnly, requiresMelee, allowLower); } public int weaponRankValueForRank(WeaponRank rank) { return provider.rankValueForRank(rank); } public GBAFEItemData[] itemsOfTypeAndEqualRank(WeaponType type, WeaponRank rank, Boolean rangedOnly, Boolean requiresMelee, Boolean allowLower) { return feItemsFromItemSet(provider.weaponsOfTypeAndEqualRank(type, rank, rangedOnly, requiresMelee, allowLower)); } public GBAFEItemData[] prfWeaponsForClass(int classID) { return feItemsFromItemSet(provider.prfWeaponsForClassID(classID)); } public GBAFEItemData[] getChestRewards() { return feItemsFromItemSet(provider.allPotentialChestRewards()); } public GBAFEItemData[] relatedItems(int itemID) { return feItemsFromItemSet(provider.relatedItemsToItem(itemMap.get(itemID))); } public GBAFEItemData[] lockedWeaponsToClass(int classID) { return feItemsFromItemSet(provider.weaponsLockedToClass(classID)); } public boolean isPlayerOnly(int itemID) { return provider.playerOnlyWeapons().stream().anyMatch(item -> item.getID() == itemID); } public Boolean isHealingStaff(int itemID) { return provider.itemWithID(itemID).isHealingStaff(); } public WeaponRank weaponRankFromValue(int rankValue) { return provider.rankWithValue(rankValue); } public GBAFEItemData getRandomHealingStaff(WeaponRank maxRank, Random rng) { Set<GBAFEItem> healingStaves = provider.weaponsOfTypeUpToRank(WeaponType.STAFF, maxRank, false, false); GBAFEItem[] staves = healingStaves.toArray(new GBAFEItem[healingStaves.size()]); return itemMap.get(staves[rng.nextInt(staves.length)].getID()); } public GBAFEItemData getBasicWeaponForCharacter(GBAFECharacterData character, Boolean ranged, Boolean mustAttack, Random rng) { int classID = character.getClassID(); Set<GBAFEItem> weapons = provider.basicWeaponsForClass(classID); GBAFEItem[] weaponArray = weapons.toArray(new GBAFEItem[weapons.size()]); if (weapons.size() == 1) { return itemMap.get(weaponArray[0].getID()); } else if (weapons.isEmpty()) { return null; } return itemMap.get(weaponArray[rng.nextInt(weapons.size())].getID()); } public GBAFEItemData getSidegradeWeapon(GBAFEClassData targetClass, GBAFEItemData originalWeapon, boolean strict, Random rng) { if (!isWeapon(originalWeapon) && originalWeapon.getType() != WeaponType.STAFF) { return null; } Set<GBAFEItem> potentialItems = provider.comparableWeaponsForClass(targetClass.getID(), new WeaponRanks(targetClass, provider), originalWeapon, strict); if (potentialItems.isEmpty()) { potentialItems = provider.basicWeaponsForClass(targetClass.getID()); if (potentialItems.isEmpty()) { return null; } } // No minion should be getting any player only weapons. potentialItems.removeAll(provider.playerOnlyWeapons()); List<GBAFEItem> itemList = potentialItems.stream().sorted(GBAFEItem.defaultComparator()).collect(Collectors.toList()); return itemMap.get(itemList.get(rng.nextInt(itemList.size())).getID()); } public GBAFEItemData getSidegradeWeapon(GBAFECharacterData character, GBAFEClassData charClass, GBAFEItemData originalWeapon, boolean isEnemy, boolean strict, Random rng) { if (!isWeapon(originalWeapon) && originalWeapon.getType() != WeaponType.STAFF) { return null; } Set<GBAFEItem> potentialItems = provider.comparableWeaponsForClass(character.getClassID(), new WeaponRanks(character, charClass, provider), originalWeapon, strict); if (potentialItems.isEmpty()) { potentialItems = provider.basicWeaponsForClass(character.getClassID()); if (potentialItems.isEmpty()) { return null; } } if (isEnemy) { potentialItems.removeAll(provider.playerOnlyWeapons()); } if (potentialItems.isEmpty()) { return null; } List<GBAFEItem> itemList = potentialItems.stream().sorted(GBAFEItem.defaultComparator()).collect(Collectors.toList()); return itemMap.get(itemList.get(rng.nextInt(itemList.size())).getID()); } public GBAFEItemData getPrfWeaponForClass(int classID) { Set<GBAFEItem> prfs = provider.prfWeaponsForClassID(classID); GBAFEItem item = prfs.stream().min(new Comparator<GBAFEItem>() { @Override public int compare(GBAFEItem arg0, GBAFEItem arg1) { return Integer.compare(arg0.getID(), arg1.getID()); } }).orElse(null); return item != null ? itemWithID(item.getID()) : null; } public GBAFEItemData getRandomWeaponForCharacter(GBAFECharacterData character, Boolean ranged, Boolean melee, boolean isEnemy, Random rng) { GBAFEItemData[] potentialItems = usableWeaponsForCharacter(character, ranged, melee, isEnemy); if (potentialItems == null || potentialItems.length < 1) { // Check class specific weapons (e.g. FE8 monsters) potentialItems = feItemsFromItemSet(provider.weaponsForClass(character.getClassID())); if (potentialItems == null || potentialItems.length < 1) { return null; } } int index = rng.nextInt(potentialItems.length); return potentialItems[index]; } private GBAFEItemData[] usableWeaponsForCharacter(GBAFECharacterData character, Boolean ranged, Boolean melee, boolean isEnemy) { ArrayList<GBAFEItemData> items = new ArrayList<GBAFEItemData>(); if (character.getSwordRank() > 0) { items.addAll(Arrays.asList(itemsOfTypeAndBelowRankValue(WeaponType.SWORD, character.getSwordRank(), ranged, melee))); } if (character.getLanceRank() > 0) { items.addAll(Arrays.asList(itemsOfTypeAndBelowRankValue(WeaponType.LANCE, character.getLanceRank(), ranged, melee))); } if (character.getAxeRank() > 0) { items.addAll(Arrays.asList(itemsOfTypeAndBelowRankValue(WeaponType.AXE, character.getAxeRank(), ranged, melee))); } if (character.getBowRank() > 0) { items.addAll(Arrays.asList(itemsOfTypeAndBelowRankValue(WeaponType.BOW, character.getBowRank(), ranged, melee))); } if (character.getAnimaRank() > 0) { items.addAll(Arrays.asList(itemsOfTypeAndBelowRankValue(WeaponType.ANIMA, character.getAnimaRank(), ranged, melee))); } if (character.getLightRank() > 0) { items.addAll(Arrays.asList(itemsOfTypeAndBelowRankValue(WeaponType.LIGHT, character.getLightRank(), ranged, melee))); } if (character.getDarkRank() > 0) { items.addAll(Arrays.asList(itemsOfTypeAndBelowRankValue(WeaponType.DARK, character.getDarkRank(), ranged, melee))); } if (character.getStaffRank() > 0) { items.addAll(Arrays.asList(itemsOfTypeAndBelowRankValue(WeaponType.STAFF, character.getStaffRank(), ranged, melee))); } Set<GBAFEItem> prfs = provider.prfWeaponsForClassID(character.getClassID()); items.addAll(Arrays.asList(feItemsFromItemSet(prfs))); if (isEnemy) { items.removeIf(item -> provider.playerOnlyWeapons().contains(provider.itemWithID(item.getID()))); } return items.toArray(new GBAFEItemData[items.size()]); } public GBAFEItemData[] formerThiefInventory() { return feItemsFromItemSet(provider.formerThiefInventory()); } public GBAFEItemData[] thiefItemsToRemove() { return feItemsFromItemSet(provider.thiefItemsToRemove()); } public GBAFEItemData[] specialItemsToRetain() { return feItemsFromItemSet(provider.specialItemsToRetain()); } public GBAFEItemData[] specialInventoryForClass(int classID, Random rng) { return feItemsFromItemSet(provider.itemKitForSpecialClass(classID, rng)); } public void commit() { for (GBAFEItemData item : itemMap.values()) { item.commitChanges(); } spellAnimations.commit(); } public void compileDiffs(DiffCompiler compiler) { for (GBAFEItemData item : itemMap.values()) { item.commitChanges(); if (item.hasCommittedChanges()) { Diff charDiff = new Diff(item.getAddressOffset(), item.getData().length, item.getData(), null); compiler.addDiff(charDiff); } } spellAnimations.compileDiffs(compiler); for (String promotionItemName : promotionItemAddressPointers.keySet()) { if (freeSpace.hasOffsetForKey(promotionItemName)) { long offset = freeSpace.getOffsetForKey(promotionItemName); byte[] addressByteArray = WhyDoesJavaNotHaveThese.bytesFromAddress(offset); long targetOffset = promotionItemAddressPointers.get(promotionItemName); compiler.addDiff(new Diff(targetOffset, addressByteArray.length, addressByteArray, null)); } } } private GBAFEItemData[] feItemsFromItemSet(Set<GBAFEItem> itemSet) { if (itemSet == null) { return new GBAFEItemData[] {}; } List<GBAFEItem> itemList = new ArrayList<GBAFEItem>(itemSet); Collections.sort(itemList, GBAFEItem.defaultComparator()); GBAFEItemData[] items = new GBAFEItemData[itemList.size()]; for (int i = 0; i < itemList.size(); i++) { items[i] = itemWithID(itemList.get(i).getID()); } return items; } public void recordWeapons(RecordKeeper rk, Boolean isInitial, ClassDataLoader classData, TextLoader textData, FileHandler handler) { for (GBAFEItemData item : getAllWeapons()) { recordWeapon(rk, item, isInitial, classData, textData, handler); } } private void recordWeapon(RecordKeeper rk, GBAFEItemData item, Boolean isInitial, ClassDataLoader classData, TextLoader textData, FileHandler handler) { int nameIndex = item.getNameIndex(); String name = textData.getStringAtIndex(nameIndex, true).trim(); int descriptionIndex = item.getDescriptionIndex(); String description = textData.getStringAtIndex(descriptionIndex, true).trim(); if (isInitial) { rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Description", description); rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Power (MT)", String.format("%d", item.getMight())); rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Accuracy (Hit)", String.format("%d", item.getHit())); rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Weight (WT)", String.format("%d", item.getWeight())); rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Durability", String.format("%d", item.getDurability())); rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Critical", String.format("%d", item.getCritical())); long statPointerAddress = item.getStatBonusPointer(); if (statPointerAddress != 0) { statPointerAddress -= 0x8000000; if (handler != null) { byte[] bonuses = handler.readBytesAtOffset(statPointerAddress, 7); List<String> bonusStrings = new ArrayList<String>(); if (bonuses[0] > 0) { bonusStrings.add("+" + bonuses[0] + " HP"); } if (bonuses[1] > 0) { bonusStrings.add("+" + bonuses[1] + ((item.getType() == WeaponType.ANIMA || item.getType() == WeaponType.LIGHT || item.getType() == WeaponType.DARK) ? " Magic" : " Strength")); } if (bonuses[2] > 0) { bonusStrings.add("+" + bonuses[2] + " Skill"); } if (bonuses[3] > 0) { bonusStrings.add("+" + bonuses[3] + " Speed"); } if (bonuses[4] > 0) { bonusStrings.add("+" + bonuses[4] + " Defense"); } if (bonuses[5] > 0) { bonusStrings.add("+" + bonuses[5] + " Resistance"); } if (bonuses[6] > 0) { bonusStrings.add("+" + bonuses[6] + " Luck"); } rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Stat Bonus", String.join("<br>", bonusStrings)); } else { rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Stat Bonus", "No input handler."); } } else { rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Stat Bonus", "None"); } long effectiveClasses = item.getEffectivenessPointer(); if (effectiveClasses != 0) { effectiveClasses -= 0x8000000; if (handler != null) { handler.setNextReadOffset(effectiveClasses); byte[] classes = handler.continueReadingBytesUpToNextTerminator(effectiveClasses + 100); List<String> classList = new ArrayList<String>(); for (byte classID : classes) { if (classID == 0) { break; } GBAFEClassData classObject = classData.classForID(classID); if (classObject == null) { classList.add("Unknown (0x" + Integer.toHexString(classID).toUpperCase() + ")"); } else { if (classData.isFemale(classID)) { classList.add(textData.getStringAtIndex(classObject.getNameIndex(), true).trim() + " (F)"); } else { classList.add(textData.getStringAtIndex(classObject.getNameIndex(), true).trim()); } } } rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Effectiveness", String.join("<br>", classList)); } else { rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Effectiveness", "No input handler."); } } else { rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Effectiveness", "None"); } if (item.hasAbility1()) { rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Ability 1", item.getAbility1Description("<br>")); } if (item.hasAbility2()) { rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Ability 2", item.getAbility2Description("<br>")); } if (item.hasAbility3()) { rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Ability 3", item.getAbility3Description("<br>")); } if (item.hasAbility4()) { rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Ability 4", item.getAbility4Description("<br>")); } if (item.hasWeaponEffect()) { rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Effect", item.getWeaponEffectDescription()); } rk.recordOriginalEntry(RecordKeeperCategoryWeaponKey, name, "Range", String.format("%d ~ %d", item.getMinRange(), item.getMaxRange())); } else { rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Description", description); rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Power (MT)", String.format("%d", item.getMight())); rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Accuracy (Hit)", String.format("%d", item.getHit())); rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Weight (WT)", String.format("%d", item.getWeight())); rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Durability", String.format("%d", item.getDurability())); rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Critical", String.format("%d", item.getCritical())); long statPointerAddress = item.getStatBonusPointer(); if (statPointerAddress != 0) { statPointerAddress -= 0x8000000; if (handler != null) { byte[] bonuses = handler.readBytesAtOffset(statPointerAddress, 7); List<String> bonusStrings = new ArrayList<String>(); if (bonuses[0] > 0) { bonusStrings.add("+" + bonuses[0] + " HP"); } if (bonuses[1] > 0) { bonusStrings.add("+" + bonuses[1] + ((item.getType() == WeaponType.ANIMA || item.getType() == WeaponType.LIGHT || item.getType() == WeaponType.DARK) ? " Magic" : " Strength")); } if (bonuses[2] > 0) { bonusStrings.add("+" + bonuses[2] + " Skill"); } if (bonuses[3] > 0) { bonusStrings.add("+" + bonuses[3] + " Speed"); } if (bonuses[4] > 0) { bonusStrings.add("+" + bonuses[4] + " Defense"); } if (bonuses[5] > 0) { bonusStrings.add("+" + bonuses[5] + " Resistance"); } if (bonuses[6] > 0) { bonusStrings.add("+" + bonuses[6] + " Luck"); } rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Stat Bonus", String.join("<br>", bonusStrings)); } else { rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Stat Bonus", "No output handler."); } } else { rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Stat Bonus", "None"); } long effectiveClasses = item.getEffectivenessPointer(); if (effectiveClasses != 0) { effectiveClasses -= 0x8000000; if (handler != null) { handler.setNextReadOffset(effectiveClasses); byte[] classes = handler.continueReadingBytesUpToNextTerminator(effectiveClasses + 100); List<String> classList = new ArrayList<String>(); for (byte classID : classes) { if (classID == 0) { break; } GBAFEClassData classObject = classData.classForID(classID); if (classObject == null) { classList.add("Unknown (0x" + Integer.toHexString(classID).toUpperCase() + ")"); } else { if (classData.isFemale(classID)) { classList.add(textData.getStringAtIndex(classObject.getNameIndex(), true).trim() + " (F)"); } else { classList.add(textData.getStringAtIndex(classObject.getNameIndex(), true).trim()); } } } rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Effectiveness", String.join("<br>", classList)); } else { rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Effectiveness", "No output handler."); } } else { rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Effectiveness", "None"); } if (item.hasAbility1()) { rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Ability 1", item.getAbility1Description("<br>")); } if (item.hasAbility2()) { rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Ability 2", item.getAbility2Description("<br>")); } if (item.hasAbility3()) { rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Ability 3", item.getAbility3Description("<br>")); } if (item.hasAbility4()) { rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Ability 4", item.getAbility4Description("<br>")); } if (item.hasWeaponEffect()) { rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Effect", item.getWeaponEffectDescription()); } rk.recordUpdatedEntry(RecordKeeperCategoryWeaponKey, name, "Range", String.format("%d ~ %d", item.getMinRange(), item.getMaxRange())); } } }