Initial API (Kingdom + KF3)

This commit is contained in:
2023-08-24 22:02:10 +02:00
commit 12df873ead
21 changed files with 1150 additions and 0 deletions

133
loaders/KF3/charaData.py Normal file
View File

@@ -0,0 +1,133 @@
import json
import math
from collections import defaultdict
def GetName(charaData):
name = "no name"
if charaData is not None:
if charaData["nameEn"] != "":
name = charaData["nameEn"]
elif charaData["name"] != "":
name = charaData["name"]
if charaData["nickname"] != "":
name += " " + charaData["nickname"]
return name
def GetAttackBase(alphaBase, level):
if level <= alphaBase["atkLvMiddleNum"]:
t = (level - 1) / (alphaBase["atkLvMiddleNum"]-1)
return lerp(alphaBase["atkParamLv1"], alphaBase["atkParamLvMiddle"], t)
else:
t = (level - alphaBase["atkLvMiddleNum"]) / (99 - alphaBase["atkLvMiddleNum"])
return lerp(alphaBase["atkParamLvMiddle"], alphaBase["atkParamLv99"], t)
def GetDefenseBase(alphaBase, level):
if level <= alphaBase["defLvMiddleNum"]:
t = (level - 1) / (alphaBase["defLvMiddleNum"]-1)
return lerp(alphaBase["defParamLv1"], alphaBase["defParamLvMiddle"], t)
else:
t = (level - alphaBase["defLvMiddleNum"]) / (99 - alphaBase["defLvMiddleNum"])
return lerp(alphaBase["defParamLvMiddle"], alphaBase["defParamLv99"], t)
def GetHealthBase(alphaBase, level):
if level <= alphaBase["hpLvMiddleNum"]:
t = (level - 1) / (alphaBase["hpLvMiddleNum"]-1)
return lerp(alphaBase["hpParamLv1"], alphaBase["hpParamLvMiddle"], t)
else:
t = (level - alphaBase["hpLvMiddleNum"]) / (99 - alphaBase["hpLvMiddleNum"])
return lerp(alphaBase["hpParamLvMiddle"], alphaBase["hpParamLv99"], t)
def get_all_stats(chara, alphaBase, max_level: bool, rising_status_pattern):
level = 99 if max_level else 1
hp = GetHealthBase(alphaBase, level)
atk = GetAttackBase(alphaBase, level)
defe = GetDefenseBase(alphaBase, level)
if max_level:
starBoost = 1 + (chara["rankHigh"] - 1) * 0.02
hp = int(math.ceil((hp + chara["promoteBonus"]["hp"]) * starBoost) + chara["costumeBonus"]["hp"])
atk = int(math.ceil((atk + chara["promoteBonus"]["atk"]) * starBoost) + chara["costumeBonus"]["atk"])
defe = int(math.ceil((defe + chara["promoteBonus"]["def"]) * starBoost) + chara["costumeBonus"]["def"])
evd = 10 * alphaBase["avoidRatio"] + chara["promoteBonus"]["evd"]
beatBonus = chara["promoteBonus"]["beat"] / 10
actBonus = chara["promoteBonus"]["act"] / 10
tryBonus = chara["promoteBonus"]["try"] / 10
if rising_status_pattern is not None:
for i in range(51):
hp += rising_status_pattern["hp"]
atk += rising_status_pattern["atk"]
defe += rising_status_pattern["def"]
else:
starBoost = 1 + (chara["rankLow"] - 1) * 0.02
hp = int(math.ceil(hp * starBoost))
atk = int(math.ceil(atk * starBoost))
defe = int(math.ceil(defe * starBoost))
evd = 10 * alphaBase["avoidRatio"]
beatBonus = 0
actBonus = 0
tryBonus = 0
status = hp * 8 / 10
if hp * 8 % 10 > 0:
status += 1
status += atk * 3 + defe * 2
result = {
"level" : level,
"status" : int(status),
"wr" : chara["rankHigh"] if max_level else chara["rankLow"],
"hp" : hp,
"atk" : atk,
"def" : defe,
"evd" : evd,
"beat" : beatBonus,
"act" : actBonus,
"try" : tryBonus,
}
return result
def fill_miracle_numbers(chara):
output = []
damages = {}
buffs = defaultdict(list)
for i, damage_param in enumerate(chara["arts"]["damageList"]):
damages[f"[DAMAGE{i}]"] = damage_param
for i, buff_param in enumerate(chara["arts"]["buffList"]):
buffs[f"[BUFF{i}]"].append(buff_param)
buffs[f"[HEAL{i}]"].append(buff_param)
buffs[f"[INCREMENT{i}]"].append(buff_param)
for i in range(1, 7):
base_text = chara["arts"]["actionEffect"]
for damage_key, damage_value in damages.items():
new_value = str(int(damage_value["damageRate"] * (1 + damage_value["growthRate"] * (i - 1)) * 100 + 0.01))
base_text = base_text.replace(damage_key, new_value)
for buff_key, buff_value_list in buffs.items():
buff_value = buff_value_list[0]
if buff_key[1] == 'B':
new_value = str(int(abs(buff_value["coefficient"] * (1 + buff_value["growthRate"] * (i - 1)) - 1) * 100 + 0.01))
base_text = base_text.replace(buff_key, new_value)
elif buff_key[1] == 'H':
new_value = str(int(buff_value["coefficient"] * (1 + buff_value["growthRate"] * (i - 1)) * 100 + 0.01))
base_text = base_text.replace(buff_key, new_value)
elif buff_key[1] == 'I':
new_value = str(int(abs(buff_value["increment"]) * (1 + buff_value["growthRate"] * (i - 1)) + 0.01))
base_text = base_text.replace(buff_key, new_value)
output.append(base_text)
return output
def toJSON(chara):
return json.dumps(chara, default=lambda o: o.__dict__,
sort_keys=True, indent=1, ensure_ascii=False)
def lerp(a, b, t):
return (1 - t) * a + t * b

276
loaders/KF3/kf3db.py Normal file
View File

@@ -0,0 +1,276 @@
import gzip
import json
import UnityPy
from loaders.KF3.charaData import get_all_stats, fill_miracle_numbers
class KF3DB:
processed_friends = {}
paramAbilities = {}
paramAbilities1 = {}
paramAbilities2 = {}
paramAlphaBases = {}
paramArts = {}
paramSpecialAttacks = {}
paramWaitActions = {}
charaData = {}
promoteData = {}
promotePresetData = {}
charaClothesData = {}
limitlevel_rising_status = {}
def __init__(self, app) -> None:
if "KF3" in app.databases:
del app.databases["KF3"]
self.parse_parameter()
with gzip.open("data/KF3/CHARA_DATA.d", mode="rt", encoding="utf-8") as file:
for entry in json.loads(file.read()):
self.charaData[entry["id"]] = entry
with gzip.open("data/KF3/CHARA_PROMOTE_DATA.d", mode="rt", encoding="utf-8") as file:
for entry in json.loads(file.read()):
self.promoteData[entry["promoteId"]] = entry
with gzip.open("data/KF3/CHARA_PROMOTE_PRESET_DATA.d", mode="rt", encoding="utf-8") as file:
for entry in json.loads(file.read()):
if entry["promotePresetId"] not in self.promotePresetData:
self.promotePresetData[entry["promotePresetId"]] = []
self.promotePresetData[entry["promotePresetId"]].append(entry)
with gzip.open("data/KF3/CHARA_CLOTHES_DATA.d", mode="rt", encoding="utf-8") as file:
for entry in json.loads(file.read()):
if entry["clothesPresetId"] not in self.charaClothesData:
self.charaClothesData[entry["clothesPresetId"]] = []
self.charaClothesData[entry["clothesPresetId"]].append(entry)
with gzip.open("data/KF3/LIMITLEVEL_RISING_STATUS.d", mode="rt", encoding="utf-8") as file:
for entry in json.loads(file.read()):
self.limitlevel_rising_status[entry["patternId"]] = entry
self.process_friends()
app.databases["KF3"] = self
def parse_parameter(self):
paramAsset = UnityPy.load("data/KF3/assets/parameter.asset")
for obj in paramAsset.objects:
data = obj.read()
if data.name.split('_')[0] == "ParamAbility":
id = int(data.name.split('_')[1])
if data.name.endswith("_1"):
self.paramAbilities1[id] = obj.read_typetree()
elif data.name.endswith("_2"):
self.paramAbilities2[id] = obj.read_typetree()
else:
self.paramAbilities[id] = obj.read_typetree()
elif data.name.split('_')[0] == "ParamAlphaBase":
id = int(data.name.split('_')[1])
self.paramAlphaBases[id] = obj.read_typetree()
elif data.name.split('_')[0] == "ParamArts":
id = int(data.name.split('_')[1])
self.paramArts[id] = obj.read_typetree()
elif data.name.split('_')[0] == "ParamSpecialAttack":
id = int(data.name.split('_')[1])
self.paramSpecialAttacks[id] = obj.read_typetree()
elif data.name.split('_')[0] == "ParamWaitAction":
id = int(data.name.split('_')[1])
self.paramWaitActions[id] = obj.read_typetree()
def process_friends(self):
self.processed_friends = {}
for charaID in self.charaData:
self.processed_friends[charaID] = self.process_chara(charaID)
def process_chara(self, id : int):
chara = {}
charaData = self.charaData[id]
alphaBase = self.paramAlphaBases[id]
wrLocked = False
promoIds = []
promotePresetId = charaData["promotePresetId"]
promos = self.promotePresetData[promotePresetId] if promotePresetId in self.promotePresetData else []
for promo in promos:
if promo["promoteStepDatetime"] == 1917860400000:
wrLocked = True
continue
else:
promoIds.append([promo["promoteId00"], promo["promoteId01"], promo["promoteId02"], promo["promoteId03"], promo["promoteId04"], promo["promoteId05"]])
promoteDatas = [[self.promoteData[id] for id in promo] for promo in promoIds]
promote_bonus = {"atk" : 0, "def" : 0, "hp" : 0, "evd" : 0, "beat" : 0, "act" : 0, "try" : 0}
for promoTier in promoteDatas:
for promoteStep in promoTier:
promote_bonus["atk"] += promoteStep["promoteAtk"]
promote_bonus["def"] += promoteStep["promoteDef"]
promote_bonus["hp"] += promoteStep["promoteHp"]
promote_bonus["evd"] += promoteStep["promoteAvoid"]
promote_bonus["act"] += promoteStep["promoteActionDamageRatio"]
promote_bonus["beat"] += promoteStep["promoteBeatDamageRatio"]
promote_bonus["try"] += promoteStep["promoteTryDamageRatio"]
clothesPresetId = charaData["clothesPresetId"]
clothesDatas = self.charaClothesData[clothesPresetId] if clothesPresetId in self.charaClothesData else []
costume_bonus = {"atk" : 0, "def" : 0, "hp" : 0, "evd" : 0, "beat" : 0, "act" : 0, "try" : 0}
for clothesData in clothesDatas:
costume_bonus["atk"] += clothesData["atkBonus"]
costume_bonus["def"] += clothesData["defBonus"]
costume_bonus["hp"] += clothesData["hpBonus"]
chara["id"] = id
chara["promoteBonus"] = promote_bonus
chara["costumeBonus"] = costume_bonus
chara["attribute"] = charaData["attribute"]
chara["nameEn"] = charaData["nameEn"]
chara["rankLow"] = charaData["rankLow"]
chara["rankHigh"] = charaData["rankHigh"]
chara["castName"] = charaData["castName"]
chara["wr"] = 4 if wrLocked else 5
chara["is_wr5"] = chara["wr"] == 5
chara["has_party_dress"] = False
for clothesData in clothesDatas:
if clothesData["clothesId"] == 8:
chara["has_party_dress"] = True
break
chara["has_rainbow_trait"] = id in self.paramAbilities2
chara["risingStatusPatternId"] = patternId = charaData["risingStatusPatternId"]
level_curve = self.limitlevel_rising_status[patternId] if patternId != 0 else None
chara["stats_min"] = get_all_stats(chara, alphaBase, max_level = False, rising_status_pattern=level_curve)
chara["stats_max"] = get_all_stats(chara, alphaBase, max_level = True, rising_status_pattern=level_curve)
chara["plasmPoint"] = alphaBase["plasmPoint"]
chara["cards"] = [
{"type":alphaBase["orderCardType00"], "value":alphaBase["orderCardValue00"]},
{"type":alphaBase["orderCardType01"], "value":alphaBase["orderCardValue01"]},
{"type":alphaBase["orderCardType02"], "value":alphaBase["orderCardValue02"]},
{"type":alphaBase["orderCardType03"], "value":alphaBase["orderCardValue03"]},
{"type":alphaBase["orderCardType04"], "value":alphaBase["orderCardValue04"]}
]
chara["synergy_flag"] = self.paramArts[id]["authParam"]["SynergyFlag"] if id in self.paramArts else 0
chara["arts"] = self.paramArts[id] if id in self.paramArts else None
chara["ability"] = self.paramAbilities[id] if id in self.paramAbilities else None
chara["ability1"] = self.paramAbilities1[id] if id in self.paramAbilities1 else None
chara["ability2"] = self.paramAbilities2[id] if id in self.paramAbilities2 else None
chara["wait_action"] = self.paramWaitActions[id] if id in self.paramWaitActions else None
chara["special_attack"] = self.paramSpecialAttacks[id] if id in self.paramSpecialAttacks else None
return chara
def get_chara(self, id : int):
if id in self.processed_friends:
return self.processed_friends[id]
else:
return None
def get_chara_wiki(self, id : int):
chara = self.get_chara(id)
lines = []
stars_word = chara["rankLow"]
stars_word = "Two" if stars_word == 2 else "Three" if stars_word == 3 else "Four" if stars_word == 4 else "Five" if stars_word == 5 else "Unknown"
attribute_word = chara["attribute"]
attribute_word = "Funny" if attribute_word == 1 else "Friendly" if attribute_word == 2 else "Relaxed" if attribute_word == 3 else "Lovely" if attribute_word == 4 else "Active" if attribute_word == 5 else "Carefree" if attribute_word == 6 else "Unknown"
categories = [
f"[[Category:{attribute_word} KF3 Friends]]",
f"[[Category:{stars_word} Star KF3 Friends]]",
f"[[Category:Missing Content]]",
f"[[Category:Needs Audio]]",
f"{{{{#vardefine:id|{str(chara['id'])}}}}}"
]
if chara['has_party_dress']:
categories.append("[[Category:Party Dress KF3 Friends]]")
if chara['is_wr5']:
categories.append("[[Category:Wild Release 5 KF3 Friends]]")
if chara['has_rainbow_trait']:
categories.append("[[Category:Rainbow Trait KF3 Friends]]")
lines.append(" ".join(categories))
lines.append("")
lines.append("{{FriendBox/KF3")
lines.append(f"|name={chara['nameEn'].replace('_', ' ')}")
lines.append(f"|apppic={chara['nameEn'].replace('_', ' ')}KF3.png")
lines.append(f"|apprarity={{{{KF3{chara['rankLow']}Star}}}}")
lines.append(f"|seiyuu={chara['castName']}")
lines.append(f"|attribute={attribute_word} {{{{KF3{attribute_word}}}}}")
lines.append(f"|implemented={0}")
lines.append(f"|id={chara['id']}")
lines.append(f"|wr5={'Yes' if chara['is_wr5'] else 'No'}")
lines.append(f"|rainbowtrait={'Yes' if chara['has_rainbow_trait'] else 'No'}")
lines.append(f"|partydress={'Yes' if chara['has_party_dress'] else 'No'}")
lines.append("}}")
lines.append("")
lines.append("{{FriendBuilder/KF3")
lines.append(f"|introduction = '''{chara['nameEn'].replace('_', ' ')}''' is a Friend that appears in the app version of [[Kemono Friends 3]].")
lines.append(f"|status={chara['stats_min']['status']}")
lines.append(f"|hp={chara['stats_min']['hp']}")
lines.append(f"|atk={chara['stats_min']['atk']}")
lines.append(f"|def={chara['stats_min']['def']}")
lines.append(f"|evd={round(chara['stats_min']['evd']/10,1)}%")
lines.append(f"|beat={chara['stats_min']['beat']}%")
lines.append(f"|action={chara['stats_min']['act']}%")
lines.append(f"|try={chara['stats_min']['try']}%")
lines.append(f"|plasm={chara['plasmPoint']}")
lines.append(f"|maxstatus={chara['stats_max']['status']}")
lines.append(f"|maxhp={chara['stats_max']['hp']}")
lines.append(f"|maxatk={chara['stats_max']['atk']}")
lines.append(f"|maxdef={chara['stats_max']['def']}")
lines.append(f"|maxevd={round(chara['stats_max']['evd']/10,1)}%")
lines.append(f"|maxbeat={chara['stats_max']['beat']}%")
lines.append(f"|maxaction={chara['stats_max']['act']}%")
lines.append(f"|maxtry={chara['stats_max']['try']}%")
cards_str = "|flags="
for card in chara["cards"]:
cards_str += " {{"
if card["type"] == 1:
cards_str += "Beat}}"
elif card["type"] == 2:
cards_str += f"Action{card['value']}}}}}"
elif card["type"] == 3:
if card["value"] == 20:
cards_str += "TryLow}}"
if card["value"] == 30:
cards_str += "TryMiddle}}"
if card["value"] == 40:
cards_str += "TryHigh}}"
lines.append(cards_str)
cardType = "Beat" if chara["synergy_flag"] == 1 else "Action20" if chara["synergy_flag"] == 2 else "TryHigh" if chara["synergy_flag"] == 3 else ""
lines.append(f"|miracleplus={{{{{cardType}}}}}")
if chara["arts"] is not None:
miracles = fill_miracle_numbers(chara)
lines.append(f"|miracle={chara['arts']['actionName']}")
for idx,miracle in enumerate(miracles):
lines.append("|miracle" + str(idx + 1) + "=" + miracle.replace("\r\n", "\n").replace("\\n", "\n").replace("\\", " "))
if chara["special_attack"] is not None:
lines.append(f"|beatname={chara['special_attack']['actionName']}")
lines.append(f"|beatskill={chara['special_attack']['actionEffect']}")
if chara["wait_action"] is not None:
lines.append(f"|standby={chara['wait_action']['skillName']}")
lines.append(f"|standbyskill={chara['wait_action']['skillEffect']}")
if chara["ability"] is not None:
lines.append(f"|unique={chara['ability']['abilityName']}")
lines.append(f"|uniqueskill={chara['ability']['abilityEffect']}")
if chara["ability1"] is not None:
lines.append(f"|miracletrait={chara['ability1']['abilityName']}")
lines.append(f"|miracletraitskill={chara['ability1']['abilityEffect']}")
else:
lines.append(f"|miracletrait=N/A")
lines.append(f"|miracletraitskill=Not Implemented.")
if chara["ability2"] is not None:
lines.append(f"|rainbowtrait={chara['ability2']['abilityName']}")
lines.append(f"|rainbowtraitskill={chara['ability2']['abilityEffect']}")
else:
lines.append(f"|rainbowtrait=N/A")
lines.append(f"|rainbowtraitskill=Not Implemented.")
lines.append("}}")
return "\n".join(lines)