429 lines
16 KiB
Python
429 lines
16 KiB
Python
import logging
|
|
|
|
import yaml
|
|
import os
|
|
import Utils
|
|
import zipfile
|
|
|
|
from .Items import item_dictionary_table
|
|
from .Locations import all_locations, SoraLevels, exclusion_table
|
|
from .XPValues import lvlStats, formExp, soraExp
|
|
from worlds.Files import APContainer
|
|
|
|
|
|
class KH2Container(APContainer):
|
|
game: str = 'Kingdom Hearts 2'
|
|
|
|
def __init__(self, patch_data: dict, base_path: str, output_directory: str,
|
|
player=None, player_name: str = "", server: str = ""):
|
|
self.patch_data = patch_data
|
|
self.file_path = base_path
|
|
container_path = os.path.join(output_directory, base_path + ".zip")
|
|
super().__init__(container_path, player, player_name, server)
|
|
|
|
def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None:
|
|
for filename, yml in self.patch_data.items():
|
|
opened_zipfile.writestr(filename, yml)
|
|
super().write_contents(opened_zipfile)
|
|
|
|
|
|
def patch_kh2(self, output_directory):
|
|
def increaseStat(i):
|
|
if lvlStats[i] == "str":
|
|
self.strength += 2
|
|
elif lvlStats[i] == "mag":
|
|
self.magic += 2
|
|
elif lvlStats[i] == "def":
|
|
self.defense += 1
|
|
elif lvlStats[i] == "ap":
|
|
self.ap += 3
|
|
|
|
self.formattedTrsr = {}
|
|
self.formattedBons = []
|
|
self.formattedLvup = {"Sora": {}}
|
|
self.formattedBons = {}
|
|
self.formattedFmlv = {}
|
|
self.formattedItem = {"Stats": []}
|
|
self.formattedPlrp = []
|
|
self.strength = 2
|
|
self.magic = 6
|
|
self.defense = 2
|
|
self.ap = 0
|
|
self.dblbonus = 0
|
|
formexp = None
|
|
formName = None
|
|
levelsetting = list()
|
|
|
|
if self.multiworld.Keyblade_Minimum[self.player].value > self.multiworld.Keyblade_Maximum[self.player].value:
|
|
logging.info(
|
|
f"{self.multiworld.get_file_safe_player_name(self.player)} has Keyblade Minimum greater than Keyblade Maximum")
|
|
keyblademin = self.multiworld.Keyblade_Maximum[self.player].value
|
|
keyblademax = self.multiworld.Keyblade_Minimum[self.player].value
|
|
else:
|
|
keyblademin = self.multiworld.Keyblade_Minimum[self.player].value
|
|
keyblademax = self.multiworld.Keyblade_Maximum[self.player].value
|
|
|
|
if self.multiworld.LevelDepth[self.player] == "level_50":
|
|
levelsetting.extend(exclusion_table["Level50"])
|
|
|
|
elif self.multiworld.LevelDepth[self.player] == "level_99":
|
|
levelsetting.extend(exclusion_table["Level99"])
|
|
|
|
elif self.multiworld.LevelDepth[self.player] != "level_1":
|
|
levelsetting.extend(exclusion_table["Level50Sanity"])
|
|
|
|
if self.multiworld.LevelDepth[self.player] == "level_99_sanity":
|
|
levelsetting.extend(exclusion_table["Level99Sanity"])
|
|
|
|
mod_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.get_file_safe_player_name(self.player)}"
|
|
all_valid_locations = {location for location, data in all_locations.items()}
|
|
for location in self.multiworld.get_filled_locations(self.player):
|
|
if location.name in all_valid_locations:
|
|
data = all_locations[location.name]
|
|
else:
|
|
continue
|
|
if location.item:
|
|
if location.item.player == self.player:
|
|
itemcode = item_dictionary_table[location.item.name].kh2id
|
|
else:
|
|
itemcode = 90 # castle map
|
|
else:
|
|
itemcode = 90
|
|
|
|
if data.yml == "Chest":
|
|
self.formattedTrsr[data.locid] = {"ItemId": itemcode}
|
|
|
|
elif data.yml in ["Get Bonus", "Double Get Bonus", "Second Get Bonus"]:
|
|
if data.yml == "Get Bonus":
|
|
self.dblbonus = 0
|
|
# if double bonus then addresses dbl bonus so the next check gets 2 items on it
|
|
if data.yml == "Double Get Bonus":
|
|
self.dblbonus = itemcode
|
|
continue
|
|
if data.locid not in self.formattedBons.keys():
|
|
self.formattedBons[data.locid] = {}
|
|
self.formattedBons[data.locid][data.charName] = {
|
|
"RewardId": data.locid,
|
|
"CharacterId": data.charNumber,
|
|
"HpIncrease": 0,
|
|
"MpIncrease": 0,
|
|
"DriveGaugeUpgrade": 0,
|
|
"ItemSlotUpgrade": 0,
|
|
"AccessorySlotUpgrade": 0,
|
|
"ArmorSlotUpgrade": 0,
|
|
"BonusItem1": itemcode,
|
|
"BonusItem2": self.dblbonus,
|
|
"Padding": 0
|
|
|
|
}
|
|
# putting dbl bonus at 0 again, so we don't have the same item placed multiple time
|
|
self.dblbonus = 0
|
|
elif data.yml == "Keyblade":
|
|
self.formattedItem["Stats"].append({
|
|
"Id": data.locid,
|
|
"Attack": self.random.randint(keyblademin, keyblademax),
|
|
"Magic": self.random.randint(keyblademin, keyblademax),
|
|
"Defense": 0,
|
|
"Ability": itemcode,
|
|
"AbilityPoints": 0,
|
|
"Unknown08": 100,
|
|
"FireResistance": 100,
|
|
"IceResistance": 100,
|
|
"LightningResistance": 100,
|
|
"DarkResistance": 100,
|
|
"Unknown0d": 100,
|
|
"GeneralResistance": 100,
|
|
"Unknown": 0
|
|
})
|
|
|
|
elif data.yml == "Forms":
|
|
# loc id is form lvl
|
|
# char name is the form name number :)
|
|
if data.locid == 2:
|
|
formDict = {1: "Valor", 2: "Wisdom", 3: "Limit", 4: "Master", 5: "Final"}
|
|
formDictExp = {
|
|
1: self.multiworld.Valor_Form_EXP[self.player].value,
|
|
2: self.multiworld.Wisdom_Form_EXP[self.player].value,
|
|
3: self.multiworld.Limit_Form_EXP[self.player].value,
|
|
4: self.multiworld.Master_Form_EXP[self.player].value,
|
|
5: self.multiworld.Final_Form_EXP[self.player].value
|
|
}
|
|
formexp = formDictExp[data.charName]
|
|
formName = formDict[data.charName]
|
|
self.formattedFmlv[formName] = []
|
|
self.formattedFmlv[formName].append({
|
|
"Ability": 1,
|
|
"Experience": int(formExp[data.charName][data.locid] / formexp),
|
|
"FormId": data.charName,
|
|
"FormLevel": 1,
|
|
"GrowthAbilityLevel": 0,
|
|
})
|
|
# row is form column is lvl
|
|
self.formattedFmlv[formName].append({
|
|
"Ability": itemcode,
|
|
"Experience": int(formExp[data.charName][data.locid] / formexp),
|
|
"FormId": data.charName,
|
|
"FormLevel": data.locid,
|
|
"GrowthAbilityLevel": 0,
|
|
})
|
|
|
|
# Summons have no actual locations so done down here.
|
|
self.formattedFmlv["Summon"] = []
|
|
for x in range(1, 7):
|
|
self.formattedFmlv["Summon"].append({
|
|
"Ability": 123,
|
|
"Experience": int(formExp[0][x] / self.multiworld.Summon_EXP[self.player].value),
|
|
"FormId": 0,
|
|
"FormLevel": x,
|
|
"GrowthAbilityLevel": 0,
|
|
})
|
|
# levels done down here because of optional settings that can take locations out of the pool.
|
|
self.i = 2
|
|
for location in SoraLevels:
|
|
increaseStat(self.random.randint(0, 3))
|
|
if location in levelsetting:
|
|
data = self.multiworld.get_location(location, self.player)
|
|
if data.item:
|
|
if data.item.player == self.player:
|
|
itemcode = item_dictionary_table[data.item.name].kh2id
|
|
else:
|
|
itemcode = 90 # castle map
|
|
else:
|
|
increaseStat(self.random.randint(0, 3))
|
|
itemcode = 0
|
|
self.formattedLvup["Sora"][self.i] = {
|
|
"Exp": int(soraExp[self.i] / self.multiworld.Sora_Level_EXP[self.player].value),
|
|
"Strength": self.strength,
|
|
"Magic": self.magic,
|
|
"Defense": self.defense,
|
|
"Ap": self.ap,
|
|
"SwordAbility": itemcode,
|
|
"ShieldAbility": itemcode,
|
|
"StaffAbility": itemcode,
|
|
"Padding": 0,
|
|
"Character": "Sora",
|
|
"Level": self.i
|
|
}
|
|
self.i += 1
|
|
# averaging stats for the struggle bats
|
|
for x in {122, 144, 145}:
|
|
self.formattedItem["Stats"].append({
|
|
"Id": x,
|
|
"Attack": int((keyblademin + keyblademax) / 2),
|
|
"Magic": int((keyblademin + keyblademax) / 2),
|
|
"Defense": 0,
|
|
"Ability": 405,
|
|
"AbilityPoints": 0,
|
|
"Unknown08": 100,
|
|
"FireResistance": 100,
|
|
"IceResistance": 100,
|
|
"LightningResistance": 100,
|
|
"DarkResistance": 100,
|
|
"Unknown0d": 100,
|
|
"GeneralResistance": 100,
|
|
"Unknown": 0
|
|
})
|
|
self.formattedLvup["Sora"][1] = {
|
|
"Exp": int(soraExp[1] / self.multiworld.Sora_Level_EXP[self.player].value),
|
|
"Strength": 2,
|
|
"Magic": 6,
|
|
"Defense": 2,
|
|
"Ap": 0,
|
|
"SwordAbility": 0,
|
|
"ShieldAbility": 0,
|
|
"StaffAbility": 0,
|
|
"Padding": 0,
|
|
"Character": "Sora",
|
|
"Level": 1
|
|
}
|
|
self.mod_yml = {
|
|
"assets": [
|
|
{
|
|
'method': 'binarc',
|
|
'name': '00battle.bin',
|
|
'source': [
|
|
{
|
|
'method': 'listpatch',
|
|
'name': 'fmlv',
|
|
'source': [
|
|
{
|
|
'name': 'FmlvList.yml',
|
|
'type': 'fmlv'
|
|
}
|
|
],
|
|
'type': 'List'
|
|
},
|
|
{
|
|
'method': 'listpatch',
|
|
'name': 'lvup',
|
|
'source': [
|
|
{
|
|
'name': 'LvupList.yml',
|
|
'type': 'lvup'
|
|
}
|
|
],
|
|
'type': 'List'
|
|
},
|
|
{
|
|
'method': 'listpatch',
|
|
'name': 'bons',
|
|
'source': [
|
|
{
|
|
'name': 'BonsList.yml',
|
|
'type': 'bons'
|
|
}
|
|
],
|
|
'type': 'List'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
'method': 'binarc',
|
|
'name': '03system.bin',
|
|
'source': [
|
|
{
|
|
'method': 'listpatch',
|
|
'name': 'trsr',
|
|
'source': [
|
|
{
|
|
'name': 'TrsrList.yml',
|
|
'type': 'trsr'
|
|
}
|
|
],
|
|
'type': 'List'
|
|
},
|
|
{
|
|
'method': 'listpatch',
|
|
'name': 'item',
|
|
'source': [
|
|
{
|
|
'name': 'ItemList.yml',
|
|
'type': 'item'
|
|
}
|
|
],
|
|
'type': 'List'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
'name': 'msg/us/po.bar',
|
|
'multi': [
|
|
{
|
|
'name': 'msg/fr/po.bar'
|
|
},
|
|
{
|
|
'name': 'msg/gr/po.bar'
|
|
},
|
|
{
|
|
'name': 'msg/it/po.bar'
|
|
},
|
|
{
|
|
'name': 'msg/sp/po.bar'
|
|
}
|
|
],
|
|
'method': 'binarc',
|
|
'source': [
|
|
{
|
|
'name': 'po',
|
|
'type': 'list',
|
|
'method': 'kh2msg',
|
|
'source': [
|
|
{
|
|
'name': 'po.yml',
|
|
'language': 'en'
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
'name': 'msg/us/sys.bar',
|
|
'multi': [
|
|
{
|
|
'name': 'msg/fr/sys.bar'
|
|
},
|
|
{
|
|
'name': 'msg/gr/sys.bar'
|
|
},
|
|
{
|
|
'name': 'msg/it/sys.bar'
|
|
},
|
|
{
|
|
'name': 'msg/sp/sys.bar'
|
|
}
|
|
],
|
|
'method': 'binarc',
|
|
'source': [
|
|
{
|
|
'name': 'sys',
|
|
'type': 'list',
|
|
'method': 'kh2msg',
|
|
'source': [
|
|
{
|
|
'name': 'sys.yml',
|
|
'language': 'en'
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
],
|
|
'title': 'Randomizer Seed'
|
|
}
|
|
|
|
goal_to_text = {
|
|
0: "Three Proofs",
|
|
1: "Lucky Emblem",
|
|
2: "Hitlist",
|
|
3: "Lucky Emblem and Hitlist",
|
|
}
|
|
lucky_emblem_text = {
|
|
0: "Your Goal is not Lucky Emblem. It is Hitlist or Three Proofs.",
|
|
1: f"Lucky Emblem Required: {self.multiworld.LuckyEmblemsRequired[self.player]} out of {self.multiworld.LuckyEmblemsAmount[self.player]}",
|
|
2: "Your Goal is not Lucky Emblem. It is Hitlist or Three Proofs.",
|
|
3: f"Lucky Emblem Required: {self.multiworld.LuckyEmblemsRequired[self.player]} out of {self.multiworld.LuckyEmblemsAmount[self.player]}"
|
|
}
|
|
hitlist_text = {
|
|
0: "Your Goal is not Hitlist. It is Lucky Emblem or Three Proofs",
|
|
1: "Your Goal is not Hitlist. It is Lucky Emblem or Three Proofs",
|
|
2: f"Bounties Required: {self.multiworld.BountyRequired[self.player]} out of {self.multiworld.BountyAmount[self.player]}",
|
|
3: f"Bounties Required: {self.multiworld.BountyRequired[self.player]} out of {self.multiworld.BountyAmount[self.player]}",
|
|
}
|
|
|
|
self.pooh_text = [
|
|
{
|
|
'id': 18326,
|
|
'en': f"Your goal is {goal_to_text[self.multiworld.Goal[self.player].value]}"
|
|
},
|
|
{
|
|
'id': 18327,
|
|
'en': lucky_emblem_text[self.multiworld.Goal[self.player].value]
|
|
},
|
|
{
|
|
'id': 18328,
|
|
'en': hitlist_text[self.multiworld.Goal[self.player].value]
|
|
}
|
|
]
|
|
self.level_depth_text = [
|
|
{
|
|
'id': 0x3BF1,
|
|
'en': f"Your Level Depth is {self.multiworld.LevelDepth[self.player].current_option_name}"
|
|
}
|
|
]
|
|
mod_dir = os.path.join(output_directory, mod_name + "_" + Utils.__version__)
|
|
|
|
openkhmod = {
|
|
"TrsrList.yml": yaml.dump(self.formattedTrsr, line_break="\n"),
|
|
"LvupList.yml": yaml.dump(self.formattedLvup, line_break="\n"),
|
|
"BonsList.yml": yaml.dump(self.formattedBons, line_break="\n"),
|
|
"ItemList.yml": yaml.dump(self.formattedItem, line_break="\n"),
|
|
"FmlvList.yml": yaml.dump(self.formattedFmlv, line_break="\n"),
|
|
"mod.yml": yaml.dump(self.mod_yml, line_break="\n"),
|
|
"po.yml": yaml.dump(self.pooh_text, line_break="\n"),
|
|
"sys.yml": yaml.dump(self.level_depth_text, line_break="\n"),
|
|
}
|
|
|
|
mod = KH2Container(openkhmod, mod_dir, output_directory, self.player,
|
|
self.multiworld.get_file_safe_player_name(self.player))
|
|
mod.write()
|