Compare commits

...

1 Commits

Author SHA1 Message Date
katboi01 f2909d2992 removed katworks api files
added postgresql database
2025-07-17 02:37:27 +02:00
32 changed files with 99 additions and 1900 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
__pycache__/
data/
test.py
database_config.txt

14
app.py
View File

@ -1,10 +1,9 @@
from flask import Flask
from waitress import serve
from flask_restful import Api
from modules.KF3.database import Database as KF3DB
from modules.Kingdom.database import Database as KFKDB
from modules.Archive.database import Database as KFADB
from modules.proxy import AgreementProxy, LaunchProxy, UpdateProxy
import logging
app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False
@ -13,11 +12,8 @@ app.databases = {}
api = Api(app)
KFADB(api)
api.add_resource(LaunchProxy, "/DMM/launch")
api.add_resource(UpdateProxy, "/DMM/filelist", "/DMM/update")
api.add_resource(AgreementProxy, "/DMM/agreement")
KF3DB(api)
KFKDB(api)
if __name__ == '__main__':
app.run(host='127.0.0.1', port=8080, debug=True)
#app.run(host='127.0.0.1', port=8081, debug=True)
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(message)s')
serve(app, listen='*:8081', threads=1)

View File

@ -1,6 +1,7 @@
import json
from flask import Flask
from modules.Archive.endpoints.tag_stats import TagStats
from .databaseController import DatabaseController
from .endpoints.query import Query
@ -23,108 +24,93 @@ class Database:
if "Archive" in self.app.databases:
del self.app.databases["Archive"]
self.reload_data("/home/pi/python/Katbots/JapariArchive/database.db")
self.reload_data()
self.app.databases["Archive"] = self
api.add_resource(Query, "/Archive/Query")
api.add_resource(NewQuery, "/Archive/NewQuery")
api.add_resource(Command, "/Archive/Command")
api.add_resource(Commands, "/Archive/Commands")
api.add_resource(GetPost, "/Archive/GetPost/<id>")
api.add_resource(GetPosts, "/Archive/GetPosts")
api.add_resource(GetPostsCount, "/Archive/GetPosts/Count")
api.add_resource(AccountStats, "/Archive/AccountStats")
api.add_resource(SetAction, "/Archive/SetAction")
api.add_resource(Query, "/Query")
api.add_resource(NewQuery, "/NewQuery")
api.add_resource(Command, "/Command")
api.add_resource(Commands, "/Commands")
api.add_resource(GetPost, "/GetPost/<id>")
api.add_resource(GetPosts, "/GetPosts")
api.add_resource(GetPostsCount, "/GetPosts/Count")
api.add_resource(AccountStats, "/AccountStats")
api.add_resource(TagStats, "/TagStats")
api.add_resource(SetAction, "/SetAction")
def get_accounts(self):
query = f'''
SELECT x_handle, id FROM accounts
SELECT x_handle, id FROM x_accounts
ORDER BY x_handle ASC'''
return self.db.run_query(query)
def get_account_stats(self):
query = '''SELECT x_handle,
SUM(IIF(x_posts.action_taken = 0, 1, 0)) as undecided,
SUM(IIF(x_posts.action_taken = 1, 1, 0)) as approved,
SUM(IIF(x_posts.action_taken = 2, 1, 0)) as deleted,
SUM(IIF(x_posts.action_taken = 3, 1, 0)) as hidden,
SUM(IIF(x_post_details.id is NULL, 1, 0)) as invalid
FROM accounts
LEFT JOIN x_posts on accounts.id = x_posts.account_id
LEFT JOIN x_post_details on x_post_details.id = x_posts.id
WHERE accounts.download_mode != 4
GROUP BY account_id
ORDER BY x_handle'''
query = 'SELECT * FROM "Artist Stats"'
return self.db.run_query(query)
def get_tag_stats(self):
query = 'SELECT * FROM "Tag Stats"'
return self.db.run_query(query)
def get_post(self, id):
query = f'''SELECT x_posts.id, cast(x_posts.id as TEXT) as id_str, x_posts.error_id, x_posts.action_taken, x_posts.is_saved, x_post_details.text, x_post_details.files, x_post_details.date, accounts.x_handle, accounts.rating from x_posts
LEFT JOIN x_post_details
ON x_posts.id = x_post_details.id
LEFT JOIN accounts
ON x_posts.account_id = accounts.id WHERE x_posts.id = {id}
query = f'''SELECT cast(x_posts.id as TEXT), x_posts.*, x_accounts.x_handle, x_accounts.rating from x_posts
LEFT JOIN x_accounts
ON x_posts.account_id = x_accounts.id WHERE x_posts.id = {id}
LIMIT 1'''
result = self.db.run_query(query)
if len(result) == 0:
return None
else:
#return most recent post
return result[-1]
return self.db.run_query(query)
def build_where_query(self, artist, actions_taken = [0, 1, 2, 3], last_id = -1, include_ratings = ["SFW", "NSFW"]):
where_query = "WHERE x_post_details.id NOT NULL AND x_posts.error_id = 0"
def build_where_query(self, artist, actions_taken = [0, 1, 2, 3], last_id = -1, include_ratings = ["SFW", "NSFW"], tags = []):
where_query = "WHERE x_posts.error_id = 0"
if last_id != -1:
where_query += f" AND x_posts.id < {last_id}"
if actions_taken != [0, 1, 2, 3]:
where_query += " AND (" + " OR ".join([f"x_posts.action_taken = {action}" for action in actions_taken]) + ")"
if include_ratings != ["SFW", "NSFW", "NSFL"]:
where_query += " AND (" + " OR ".join([f'accounts.rating = "{rating}"' for rating in include_ratings]) + ")"
where_query += " AND (" + " OR ".join([f'x_accounts.rating = \'{rating}\'' for rating in include_ratings]) + ")"
if artist is not None and artist != "":
where_query += f' AND accounts.x_handle = "{artist}"'
where_query += f' AND x_accounts.x_handle = \'{artist}\''
if len(tags) > 0:
tags = ", ".join(["'" + tag + "'" for tag in tags])
where_query += f' AND tags @> ARRAY[{tags}]'
return where_query
def get_posts_count(self, artist, actions_taken = [0, 1, 2, 3], last_id = -1, include_ratings = ["SFW", "NSFW"]):
where_query = self.build_where_query(artist, actions_taken, last_id, include_ratings)
def get_posts_count(self, artist, actions_taken = [0, 1, 2, 3], last_id = -1, include_ratings = ["SFW", "NSFW"], tags = []):
where_query = self.build_where_query(artist, actions_taken, last_id, include_ratings, tags)
query = f'''
SELECT count(*) as count FROM x_posts
LEFT JOIN x_post_details
ON x_posts.id = x_post_details.id
LEFT JOIN accounts
ON x_posts.account_id = accounts.id
LEFT JOIN x_accounts
ON x_posts.account_id = x_accounts.id
{where_query}'''
result = self.db.run_query(query)
_, result = self.db.run_query(query)
return result[0]["count"]
def get_posts(self, num_posts, artist, actions_taken = [0, 1, 2, 3], last_id = -1, offset = 0, include_ratings = ["SFW", "NSFW"], order = "DESC"):
def get_posts(self, num_posts, artist, actions_taken = [0, 1, 2, 3], last_id = -1, offset = 0, include_ratings = ["SFW", "NSFW"], tags = [], order = "DESC"):
num_posts = max(1, min(num_posts, 100))
order_by = "RANDOM()" if order == "RAND" else "x_posts.id ASC" if order == "ASC" else "x_posts.id DESC"
where_query = self.build_where_query(artist, actions_taken, last_id, include_ratings)
where_query = self.build_where_query(artist, actions_taken, last_id, include_ratings, tags)
query = f'''
SELECT x_posts.id, cast(x_posts.id as TEXT) as id_str, x_posts.action_taken, x_posts.is_saved, x_post_details.text, x_post_details.files, x_post_details.date, accounts.x_handle, accounts.rating FROM x_posts
LEFT JOIN x_post_details
ON x_posts.id = x_post_details.id
LEFT JOIN accounts
ON x_posts.account_id = accounts.id
SELECT x_posts.id, cast(x_posts.id as TEXT) as id_str, action_taken, saved_files != \'{{}}\' as is_saved, text, files, date, x_handle, x_accounts.rating FROM x_posts
LEFT JOIN x_accounts
ON x_posts.account_id = x_accounts.id
{where_query}
ORDER BY {order_by}
LIMIT {num_posts} OFFSET {offset}'''
return self.db.run_query(query)
def wrap_query_response(self, result, mode = "json"):
def wrap_query_response(self, result, mode = "json", error : str = ""):
if result is None:
response = self.app.response_class(status=400)
response = self.app.response_class(response=error, status=400)
else:
if mode == "json":
response = self.app.response_class(
response=json.dumps(result, ensure_ascii=False, indent=1),
response=json.dumps(result, ensure_ascii=False, indent=1, default=str),
status=200,
mimetype='application/json'
)
@ -138,5 +124,5 @@ ORDER BY x_handle'''
response.headers.add("Access-Control-Allow-Origin", "*")
return response
def reload_data(self, database_path):
self.db = DatabaseController(database_path)
def reload_data(self):
self.db = DatabaseController()

View File

@ -1,23 +1,29 @@
import os
import sqlite3
import psycopg
from psycopg.rows import dict_row
TABLE_ACCOUNTS = "accounts"
TABLE_ACCOUNTS = "x_accounts"
TABLE_X = "x_posts"
class DatabaseController:
def __init__(self, db_name):
self.conn = sqlite3.connect(db_name)
self.conn.row_factory = sqlite3.Row
def __init__(self):
if not os.path.exists("database_config.txt"):
raise Exception(f"Create 'database_config.txt' in {os.path.abspath(os.curdir)} with database connection string, example: 'dbname=japariarchive user=postgres password=password123'")
with open("database_config.txt", "rt") as file:
connection_string = file.read()
self.conn = psycopg.connect(connection_string, row_factory=dict_row)
self.cursor = self.conn.cursor()
def run_query(self, query):
try:
self.cursor.execute(query)
results = [dict(result) for result in self.cursor.fetchall()]
return results
results = self.cursor.fetchall()
self.conn.rollback()
return True, results
except Exception as e:
print(e)
return []
print(query, e)
self.conn.rollback()
return False, str(e)
def run_command(self, command):
try:

View File

@ -9,5 +9,5 @@ if TYPE_CHECKING:
class AccountStats(Resource):
def get(self):
db : Database = app.databases["Archive"]
result = db.get_account_stats()
_, result = db.get_account_stats()
return db.wrap_query_response(result)

View File

@ -12,6 +12,8 @@ class NewQuery(Resource):
db : Database = app.databases["Archive"]
result = db.db.run_query(query)
return db.wrap_query_response(result)
status, result = db.db.run_query(query)
if status:
return db.wrap_query_response(result)
else:
return db.wrap_query_response(None, error = result)

View File

@ -11,5 +11,5 @@ class GetPost(Resource):
id = int(id)
db : Database = app.databases["Archive"]
result = db.get_post(id)
_, result = db.get_post(id)
return db.wrap_query_response(result)

View File

@ -14,6 +14,7 @@ class GetPosts(Resource):
page = int(request.args["page"]) if "page" in request.args else 1
artist = request.args["artist"] if "artist" in request.args else None
last_id = int(request.args["last_id"]) if "last_id" in request.args else -1
tags = request.args["tags"].split() if "tags" in request.args else []
except:
response = app.response_class(status=400)
response.headers.add("Access-Control-Allow-Origin", "*")
@ -51,5 +52,8 @@ class GetPosts(Resource):
real_page = page-1
offset = real_page * count
result = db.get_posts(count, artist, actions_taken=actions, last_id= -1, offset= offset, include_ratings= ratings, order=order)
return db.wrap_query_response(result)
status, result = db.get_posts(count, artist, actions_taken=actions, last_id= -1, offset= offset, include_ratings= ratings, order=order, tags=tags)
if status:
return db.wrap_query_response(result)
else:
return db.wrap_query_response(None, error=result)

View File

@ -11,6 +11,7 @@ class GetPostsCount(Resource):
db : Database = app.databases["Archive"]
try:
artist = request.args["artist"] if "artist" in request.args else None
tags = request.args["tags"].split() if "tags" in request.args else []
except:
response = app.response_class(status=400)
response.headers.add("Access-Control-Allow-Origin", "*")
@ -38,5 +39,5 @@ class GetPostsCount(Resource):
if ratings == []:
ratings = ["SFW", "NSFW"]
result = db.get_posts_count(artist, actions_taken=actions, last_id= -1, include_ratings= ratings)
result = db.get_posts_count(artist, actions_taken=actions, last_id= -1, include_ratings= ratings, tags=tags)
return db.wrap_query_response(result, mode="text")

View File

@ -12,7 +12,9 @@ class Query(Resource):
db : Database = app.databases["Archive"]
result = db.db.run_query(query)
result = [list(d.values()) for d in result]
return db.wrap_query_response(result)
status, result = db.db.run_query(query)
if status:
result = [list(d.values()) for d in result]
return db.wrap_query_response(result)
else:
return db.wrap_query_response(None, error = result)

View File

@ -0,0 +1,13 @@
from __future__ import annotations
from flask_restful import Resource
from flask import current_app as app
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from modules.Archive.database import Database
class TagStats(Resource):
def get(self):
db : Database = app.databases["Archive"]
_, result = db.get_tag_stats()
return db.wrap_query_response(result)

View File

@ -1,562 +0,0 @@
import gzip
import json
import platform
import UnityPy
from datetime import datetime
from .statCalculation import get_all_stats, fill_miracle_numbers
from .endpoints.friend import KF3_Friend
from .endpoints.friends import KF3_Friends
from .endpoints.item import KF3_Item
from .endpoints.items import KF3_Items
from .endpoints.update import KF3_Update
class Database:
def __init__(self, api) -> None:
app = api.app
if "KF3" in app.databases:
del app.databases["KF3"]
self.reload_data()
app.databases["KF3"] = self
api.add_resource(KF3_Friend, "/KF3/Friend/<int:id>")
api.add_resource(KF3_Friends, "/KF3/Friends")
api.add_resource(KF3_Item, "/KF3/Item/<int:id>")
api.add_resource(KF3_Items, "/KF3/Items")
api.add_resource(KF3_Update, "/KF3/Update")
def unload_data(self):
self.charaData = {}
self.itemCommon = {}
self.promoteData = {}
self.charaClothesData = {}
self.promotePresetData = {}
self.limitlevel_rising_status = {}
self.paramArts = {}
self.paramAbilities = {}
self.paramAbilities1 = {}
self.paramAbilities2 = {}
self.paramAlphaBases = {}
self.paramWaitActions = {}
self.paramSpecialAttacks = {}
self.processed_friends = {}
def reload_data(self):
self.unload_data()
if platform.system() == "Windows":
path = "H:\\Apache\\Katworks\\KF\\assets\\KF3\\cache\\"
else:
path = "/var/www/html/Katworks/KF/assets/KF3/develop01/cache/"
with gzip.open(path + "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(path + "ITEM_COMMON.d", mode="rt", encoding="utf-8") as file:
for entry in json.loads(file.read()):
self.itemCommon[entry["id"]] = entry
with gzip.open(path + "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(path + "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(path + "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(path + "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.parse_parameter()
self.process_friends()
print("Data reload finished")
self.update_in_progress = False
def parse_parameter(self):
if platform.system() == "Windows":
path = "H:\\Apache\\Katworks\\KF\\assets\\KF3\\assets\\"
else:
path = "/var/www/html/Katworks/KF/assets/KF3/develop01/assets/Windows/"
paramAsset = UnityPy.load(path + "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):
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] if id in self.paramAlphaBases else None
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"]])
emptyPromote = {"promoteAtk": 0, "promoteDef": 0, "promoteHp": 0, "promoteAvoid": 0, "promoteActionDamageRatio": 0, "promoteBeatDamageRatio": 0, "promoteTryDamageRatio": 0}
promoteDatas = [[(self.promoteData[id] if id in self.promoteData else emptyPromote) 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["name"] = charaData["name"]
chara["nameEn"] = charaData["nameEn"]
chara["nickname"] = charaData["nickname"]
chara["rankLow"] = charaData["rankLow"]
chara["rankHigh"] = charaData["rankHigh"]
chara["castName"] = charaData["castName"]
chara["loginText"] = charaData["loginText"]
chara["flavorText"] = charaData["flavorText"]
chara["kizunaupText"] = charaData["kizunaupText"]
chara["greetingText"] = charaData["greetingText"]
chara["startTimeRaw"] = charaData["startTime"]
chara["startTime"] = datetime.fromtimestamp(charaData["startTime"]/1000.0).strftime('%A, %B %d, %Y')
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"]
if patternId not in self.limitlevel_rising_status:
level_curve = None
else:
level_curve = self.limitlevel_rising_status[patternId] if patternId != 0 else None
if alphaBase is None:
chara["stats_min"] = {"level" : 0,"status" : 0,"wr" : 0,"hp" : 0,"atk" : 0,"def" : 0,"evd" : 0,"beat" : 0,"act" : 0,"try" : 0}
chara["stats_max"] = {"level" : 0,"status" : 0,"wr" : 0,"hp" : 0,"atk" : 0,"def" : 0,"evd" : 0,"beat" : 0,"act" : 0,"try" : 0}
chara["plasmPoint"] = 0
chara["cards"] = {0,0,0,0,0}
else:
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
chara["costumes"] = self.get_costumes(id, chara["rankLow"])
return chara
def get_item(self, id : int):
if id in self.itemCommon:
return self.itemCommon[id]
else:
return None
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)
if chara is None:
return None
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']).zfill(4)}}}}}"
]
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={chara['startTime']} (App)")
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']}%")
lines.append("")
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("")
lines.append(f"|bondup={chara['kizunaupText']}")
lines.append(f"|profile={chara['flavorText']}")
lines.append(f"|greeting={chara['loginText']}")
lines.append(f"|selfintro={chara['greetingText']}")
lines.append(f"|enselfintro=")
cos_images = "|cos = " + ",".join([costume["image_name"] for costume in chara["costumes"]]) + ",end"
cos_obtain = "|cosobt = " + ",".join([costume["obtain"] for costume in chara["costumes"]])
cos_names = "|cosname = " + ",".join([costume["name"] for costume in chara["costumes"]])
lines.append("")
lines.append(cos_images)
lines.append(cos_names)
lines.append(cos_obtain)
lines.append("}}")
return "\n".join(lines)
def get_costumes(self, id : int, defaultRarity : int):
if id not in self.charaClothesData:
return []
output = []
data = self.charaClothesData[id]
for costume in data:
cos = {
"image_name" : f"icon dressup {costume['id']}.png",
"obtain" : "Limited"
}
cosId = costume['clothesId']
if cosId == 1:
cos["name"] = "Default"
cos["obtain"] = "Default"
elif cosId == 2:
cos["name"] = "Personal Fashion"
cos["obtain"] = f"Upgrade to {costume['getRank']} Stars" if defaultRarity < costume['getRank'] else "Default"
elif cosId == 3:
cos["name"] = "Tracksuit"
cos["obtain"] = f"Upgrade to {costume['getRank']} Stars" if defaultRarity < costume['getRank'] else "Default"
elif cosId == 4:
cos["name"] = "Park Staff"
cos["obtain"] = f"Upgrade to {costume['getRank']} Stars" if defaultRarity < costume['getRank'] else "Default"
elif cosId == 5:
cos["name"] = "Café Uniform"
cos["obtain"] = f"Upgrade to {costume['getRank']} Stars" if defaultRarity < costume['getRank'] else "Default"
elif cosId == 6:
cos["name"] = "Special Outfit 1"
elif cosId == 8:
cos["name"] = "Party Dress"
cos["obtain"] = "Increase Photo Pocket level to 12"
elif cosId == 9:
cos["name"] = "School Uniform"
elif cosId == 10:
cos["name"] = "Gym Clothes"
elif cosId == 11:
cos["name"] = "Raincoat"
elif cosId == 12:
cos["name"] = "Hanamaru Animal Swimsuit"
elif cosId == 13:
cos["name"] = "Cafe Uniform (Green)"
cos["obtain"] = "Dojo reward"
elif cosId == 14:
cos["name"] = "Cafe Uniform (Blue)"
cos["obtain"] = "Dojo reward"
elif cosId == 15:
cos["name"] = "Cafe Uniform (Pink)"
cos["obtain"] = "Dojo reward"
elif cosId == 16:
cos["name"] = "Orihime Costume"
elif cosId == 17:
cos["name"] = "Arle's Outfit"
elif cosId == 18:
cos["name"] = "Marine Nanoda! Swimsuit"
elif cosId == 19:
cos["name"] = "Batten Japari Dan Swimsuit"
elif cosId == 20:
cos["name"] = "Lifeguard Swimsuit"
elif cosId == 21:
cos["name"] = "Harvest Festival Outfit"
elif cosId == 22:
cos["name"] = "Halloween Costume"
elif cosId == 23:
cos["name"] = "Classic Maid Uniform"
elif cosId == 24:
cos["name"] = "Arle's Outfit"
elif cosId == 25:
cos["name"] = "Swimsuit"
elif cosId == 26:
cos["name"] = "Swimsuit"
elif cosId == 27:
cos["name"] = "Swimsuit"
elif cosId == 28:
cos["name"] = "Matching Clothes"
elif cosId == 29:
cos["name"] = "Frilly Frilly PPP Bikini"
elif cosId == 30:
cos["name"] = "Venus Flower Bikini"
elif cosId == 31:
cos["name"] = "Christmas Dress"
elif cosId == 32:
cos["name"] = "Chinese Shrine Maiden"
elif cosId == 33:
cos["name"] = "Chinese Dress"
elif cosId == 34:
cos["name"] = "Hanamaru Stage Costume"
elif cosId == 35:
cos["name"] = "Camo Outfit"
elif cosId == 36:
cos["name"] = "でびでび・ふりる"
elif cosId == 37:
cos["name"] = "夜に紛れし闇の衣"
elif cosId == 38:
cos["name"] = "Santa"
elif cosId == 39:
cos["name"] = "Fluffy Pajamas"
elif cosId == 40:
cos["name"] = "Japanese Armor"
elif cosId == 41:
cos["name"] = "Miko Outfit"
elif cosId == 42:
cos["name"] = "PPP Hoodie"
elif cosId == 43:
cos["name"] = "Blue Tracksuit"
elif cosId == 44:
cos["name"] = "Park Staff (beige)"
elif cosId == 45:
cos["name"] = "Sailor School Uniform"
elif cosId == 46:
cos["name"] = "Snow Knight Dress"
elif cosId == 47:
cos["name"] = "Sparkling Tree Dress"
elif cosId == 48:
cos["name"] = "Retro Detective"
elif cosId == 49:
cos["name"] = "Steampunk Detective"
elif cosId == 50:
cos["name"] = "鞠と花びらのはんなり和風服"
elif cosId == 51:
cos["name"] = "Numazu Deep Sea Aquarium Clothes"
elif cosId == 52:
cos["name"] = "たのしいことをお届け!"
elif cosId == 53:
cos["name"] = "かわいい勝負ですよ!ワンピース水着"
elif cosId == 54:
cos["name"] = "どきどき♡ずっきゅんビキニ"
elif cosId == 55:
cos["name"]= "のんほいパークの服"
elif cosId == 56:
cos["name"]= "なないろのはごろも"
elif cosId == 57:
cos["name"]= "4周年ハッピーラッキーTシャツ"
elif cosId == 58:
cos["name"]= "純情色のワンピース"
else:
print(f"{costume['clothesId']}\t{costume['id']}\t{costume['name']}")
continue
output.append(cos)
return output

View File

@ -1,276 +0,0 @@
import base64
import os
import gzip
import json
import asyncio
import hashlib
import platform
import aiohttp
from UnityPy import enums
from ..Shared.utility import divide_chunks
from ..Shared.downloading import download_bytes, download_text
from ..Shared.convert import convert, extract
servers = [
"https://parade-mobile-stg-app.kemono-friends-3.jp/",
"https://parade-mobile-develop01-app.kemono-friends-3.jp/",
#"https://parade-mobile-develop02-app.kemono-friends-3.jp/paradesv/",
#"https://parade-mobile-develop03-app.kemono-friends-3.jp/paradesv/",
#"https://parade-mobile-develop04-app.kemono-friends-3.jp/paradesv/",
]
def decode(data):
# cut off the md5 checksum at the end and the four bytes at the start
hash = bytearray.fromhex(data[:-(2*16)][2*4:])
key = hashlib.md5(bytearray.fromhex(data[:2*4])).digest() # md5 the key
# xor with the key
for i in range(0, len(hash)):
hash[i] = hash[i] ^ key[i % (len(key))]
return hash.decode("utf-8")
def encode(text):
hashed = hashlib.md5(text.encode()).digest()
checksum = hashlib.md5((text + "DARAPAB ").encode()).digest()
key = hashlib.md5(hashed[:4]).digest() # md5 the key
# xor the data with the key
text = bytearray(text.encode())
for i in range(0, len(text)):
text[i] = text[i] ^ key[i % (len(key))]
return hashed[:4].hex() + text.hex() + checksum.hex()
async def download_cache(server_name, server : str):
session = aiohttp.ClientSession()
downloaded_files = []
if platform.system() == "Windows":
path = f"D:\\Codebase\\KFKDecrypt\\assets\\KF3\\{server_name}\\cache\\"
else:
path = f"/var/www/html/Katworks/KF/assets/KF3/{server_name}/cache/"
os.makedirs(path, exist_ok=True)
new_mst_ver = {}
old_mst_ver = {}
file_path_mst = path + "mstVersion.txt"
if os.path.exists(file_path_mst):
with open(file_path_mst, "rt", encoding="utf-8") as file:
old_mst_ver = dict([(entry["type"], entry["version"]) for entry in json.load(file)])
param = encode(json.dumps({'dmm_viewer_id':0}))
request = await download_bytes(server + "paradesv/common/MstVersion.do?param=" + param, session)
result = gzip.decompress(request)
new_mst = json.loads(result)
new_mst_ver = dict([(entry["type"], entry["version"]) for entry in new_mst["mst_ver"]])
for key in new_mst_ver:
file_path_gzip = path + key + ".d"
file_path_json = path + key + ".json"
if os.path.exists(file_path_gzip) and os.path.exists(file_path_json) and key in old_mst_ver:
if new_mst_ver[key] == old_mst_ver[key]:
continue
downloaded_files.append(key)
param = encode(json.dumps({'type':key, 'dmm_viewer_id':0}))
request = await download_bytes(server + "paradesv/common/MstData.do?param=" + param, session)
result = gzip.decompress(request)
response = json.loads(result)
data = base64.b64decode(response["data"])
with open(file_path_gzip, "wb") as file:
file.write(data)
with open(file_path_json, "wt", encoding="utf-8") as out_file:
data = gzip.decompress(data)
data = json.loads(data)
# if key == "GACHA_DATA":
# download_banners(data, server_name)
json.dump(data, out_file, ensure_ascii=False, indent=1)
old_mst_ver[key] = new_mst_ver[key]
with open(file_path_mst, "wt", encoding="utf-8") as file:
new_json = [{"type":type, "version":version} for type, version in zip(old_mst_ver.keys(), old_mst_ver.values())]
json.dump(new_json, file, ensure_ascii=False)
await session.close()
return downloaded_files
async def download_banners(gacha_data, server_name, session):
path = f"/var/www/html/Katworks/KF/assets/KF3/{server_name}/banners/"
os.makedirs(path, exist_ok=True)
for entry in gacha_data:
banner_name = entry["banner"]
if banner_name == "" or banner_name == None:
continue
banner_name += ".png"
file_path = path + banner_name
if os.path.exists(file_path):
continue
file_url = "https://parade-mobile-prod-cdn.kemono-friends-3.jp/Texture2D/GachaTop/" + banner_name
file_url_alt = "https://parade-mobile-develop01-app.kemono-friends-3.jp/Texture2D/GachaTop/" + banner_name
status = 0
async with session.get(file_url) as resp:
response = await resp.read()
status = resp.status
if status != 200:
async with session.get(file_url_alt) as resp:
response = await resp.read()
status = resp.status
if status != 200: continue
with open(file_path, "wb") as file:
file.write(response)
async def download_files(server_name, asset_bundle_url, srv_platform : str):
def parse_ab_list(filecontent : str):
out = {}
lines = filecontent.replace("\r\n", "\n").split('\n')
for line in lines:
split = line.split('\t')
if len(split) > 1:
out[split[0]] = split[-2]
return out
async def download_file(url_assets : str, file_name : str, download_path : str, session : aiohttp.ClientSession):
data = await download_bytes(url_assets + file_name, session)
if data != None:
with open(download_path + file_name, "wb") as file:
file.write(data)
if server_name == "develop01":
convert_path = f"/var/www/html/Katworks/KF/assets/KF3/WebGL/assets/" + file_name
extract_path = f"/var/www/html/Katworks/KF/assets/KF3/extracted/"
try:
convert(data, convert_path, enums.BuildTarget.WebGL)
except:
with open(convert_path, "wb") as file:
file.write(data)
if file_name.endswith(".png"):
extract(data, extract_path)
session = aiohttp.ClientSession()
path = f"/var/www/html/Katworks/KF/assets/KF3/{server_name}/assets/{srv_platform}/"
os.makedirs(path, exist_ok=True)
files_to_download = []
url_base = asset_bundle_url + "/" + srv_platform + "/1.0.0/ja/"
url_list = url_base + "ab_list.txt"
url_env = url_base + "ab_env.txt"
url_assets = url_base + "assets/"
file_path_env = path + "ab_env.txt"
file_path_list = path + "ab_list.txt"
old_ab_env = ""
if os.path.exists(file_path_env):
with open(file_path_env, "rt") as file:
old_ab_env = file.read()
new_ab_env = await download_text(url_env, session)
if new_ab_env != old_ab_env:
old_ab_list = {}
if os.path.exists(file_path_list):
with open(file_path_list, "rt") as file:
old_ab_list = parse_ab_list(file.read())
new_ab_list_file = await download_text(url_list, session)
new_ab_list = parse_ab_list(new_ab_list_file)
for key in new_ab_list:
file_path = path + key
if os.path.exists(file_path) and key in old_ab_list:
if new_ab_list[key] == old_ab_list[key]:
continue
files_to_download.append(key)
chunked_files_to_download = list(divide_chunks(files_to_download, 5))
for chunk in chunked_files_to_download:
tasks = [asyncio.create_task(download_file(url_assets, file, path, session)) for file in chunk]
await asyncio.wait(tasks)
print(chunk)
print()
with open(file_path_env, "wt", encoding="utf-8") as file:
file.write(new_ab_env)
with open(file_path_list, "wt", encoding="utf-8") as file:
file.write(new_ab_list_file)
await session.close()
return files_to_download
async def convert_files():
directory = f"/var/www/html/Katworks/KF/assets/KF3/develop01/assets/Windows/"
with open("/var/www/html/Katworks/KF/assets/KF3/lastUpdate_dev_files.json", "rt", encoding="utf-8") as file:
files_to_convert = json.load(file)
for file_name in os.listdir(directory):
if file_name not in files_to_convert:
continue
f = os.path.join(directory, file_name)
if not os.path.isfile(f):
return
convert_path = f"/var/www/html/Katworks/KF/assets/KF3/WebGL/assets/" + file_name
try:
print(f)
convert(f, convert_path, enums.BuildTarget.WebGL)
except:
print("Conversion failed", f)
async def manual():
# session = aiohttp.ClientSession()
# path = f"/var/www/html/Katworks/KF/assets/KF3/{server_name}/cache/"
# await session.close()
# return
downloaded_cache = {}
downloaded_files = {}
async with aiohttp.ClientSession() as session:
param = encode(json.dumps({"version":"1.0.0","dmm_viewer_id":0,"platform":1}))
request = await download_bytes(servers[0] + "paradesv/common/GetUrl.do?param=" + param, session)
result = gzip.decompress(request)
response = json.loads(result)
asset_bundle_url = response["asset_bundle_url"]
urlName = asset_bundle_url.split("-")[2]
print("downloading from", servers[0])
downloaded_cache = await download_cache(urlName, servers[0])
downloaded_files = await download_files(urlName, asset_bundle_url, "Windows")
if downloaded_cache != [] and downloaded_cache != None:
with open("/var/www/html/Katworks/KF/assets/KF3/lastUpdate_prod_cache.json", "wt", encoding="utf-8") as file:
json.dump(downloaded_cache, file, ensure_ascii=False, indent=1)
if downloaded_files != [] and downloaded_files != None:
with open("/var/www/html/Katworks/KF/assets/KF3/lastUpdate_prod_files.json", "wt", encoding="utf-8") as file:
json.dump(downloaded_files, file, ensure_ascii=False, indent=1)
print("downloading from", servers[1])
asset_bundle_url = "https://parade-mobile-develop01-app.kemono-friends-3.jp/AssetBundles/0.0.0/latest"
urlName = asset_bundle_url.split("-")[2]
downloaded_cache = await download_cache(urlName, servers[1])
downloaded_files = await download_files(urlName, asset_bundle_url, "Windows")
if downloaded_cache != [] and downloaded_cache != None:
with open("/var/www/html/Katworks/KF/assets/KF3/lastUpdate_dev_cache.json", "wt", encoding="utf-8") as file:
json.dump(downloaded_cache, file, ensure_ascii=False, indent=1)
if downloaded_files != [] and downloaded_files != None:
with open("/var/www/html/Katworks/KF/assets/KF3/lastUpdate_dev_files.json", "wt", encoding="utf-8") as file:
json.dump(downloaded_files, file, ensure_ascii=False, indent=1)
if __name__ == "__main__":
asyncio.run(manual())
#asyncio.run(convert_files())

View File

@ -1,30 +0,0 @@
from flask_restful import Resource
from flask import current_app as app
from flask import request
import json
class KF3_Friend(Resource):
def get(self, id:int):
from ..database import Database
db : Database = app.databases["KF3"]
if "wiki" in request.args:
result = db.get_chara_wiki(id)
response = app.response_class(
response=result,
status=200,
mimetype='text/plain'
)
else:
result = db.get_chara(id)
result = json.dumps(result, ensure_ascii=False, indent=1)
response = app.response_class(
response=result,
status=200,
mimetype='application/json'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response

View File

@ -1,28 +0,0 @@
import json
from flask_restful import Resource
from flask import current_app as app
from flask import request
class KF3_Friends(Resource):
def get(self):
from ..database import Database
db : Database = app.databases["KF3"]
result = []
for value in db.processed_friends.values():
result.append({"id": value["id"], "name": value["nameEn"], "startTime" : value["startTime"], "startTimeRaw" : value["startTimeRaw"]})
sort_arg = request.args["sort"] if "sort" in request.args and request.args["sort"] in result[0] else "id"
if sort_arg == "startTime":
sort_arg = "startTimeRaw"
result = sorted(result, key=lambda f: f[sort_arg])
response = app.response_class(
response=json.dumps(result, ensure_ascii=False, indent=1),
status=200,
mimetype='application/json'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response

View File

@ -1,21 +0,0 @@
from flask_restful import Resource
from flask import current_app as app
from flask import request_tearing_down
import json
class KF3_Item(Resource):
def get(self, id:int):
from ..database import Database
db : Database = app.databases["KF3"]
result = db.get_item(id)
result = json.dumps(result, ensure_ascii=False, indent=1)
response = app.response_class(
response=result,
status=200,
mimetype='application/json'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response

View File

@ -1,21 +0,0 @@
import json
from flask_restful import Resource
from flask import current_app as app
class KF3_Items(Resource):
def get(self):
from ..database import Database
db : Database = app.databases["KF3"]
result = []
for value in db.itemCommon.values():
result.append({"id": value["id"], "name": value["name"], "iconName" : value["iconName"], "flavorText" : value["flavorText"]})
response = app.response_class(
response=json.dumps(result, ensure_ascii=False, indent=1),
status=200,
mimetype='application/json'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response

View File

@ -1,36 +0,0 @@
import asyncio
from flask_restful import Resource
from flask import current_app as app
from ..downloader import manual
class KF3_Update(Resource):
def post(self):
from ..database import Database
db : Database = app.databases["KF3"]
if db.update_in_progress:
response = app.response_class(
response="update in progress",
status=200,
mimetype='text/plain'
)
else:
self.perform_update()
response = app.response_class(
response="update started, reload the site in a while",
status=200,
mimetype='text/plain'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response
def get(self):
return self.post()
def perform_update(self):
from ..database import Database
db : Database = app.databases["KF3"]
print("Update")
#await manual()
db.reload_data()

View File

@ -1,133 +0,0 @@
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

View File

@ -1,298 +0,0 @@
import json
import platform
from .numeric import numeric
from .kemono import clean_skill_string, calculate_stats, fill_miracle, get_awaken_materials, get_skill_materials
from .endpoints.item import Kingdom_Item
from .endpoints.items import Kingdom_Items
from .endpoints.update import Kingdom_Update
from .endpoints.friend import Kingdom_Friend
from .endpoints.friends import Kingdom_Friends
#element 1-orange 2-blue 3-green
#showSkillType 1-control 2-guard 3-heal 4-support 5-assault 6-aoe
class Database:
processed_friends = {}
item_stages = {}
def __init__(self, api) -> None:
app = api.app
if "Kingdom" in app.databases:
del app.databases["Kingdom"]
self.reload_data()
app.databases["Kingdom"] = self
api.add_resource(Kingdom_Friend, "/Kingdom/Friend/<int:id>")
api.add_resource(Kingdom_Friends, "/Kingdom/Friends")
api.add_resource(Kingdom_Item, "/Kingdom/Item/<int:id>")
api.add_resource(Kingdom_Items, "/Kingdom/Items")
api.add_resource(Kingdom_Update, "/Kingdom/Update")
def reload_data(self):
if platform.system() == "Windows":
NUMERICPATH = "H:\\Apache\\Katworks\\KF\\assets\\Kingdom1\\NumericData"
else:
NUMERICPATH = "/var/www/html/Katworks/KF/assets/Kingdom1/NumericData"
self.kfk_drop = numeric(NUMERICPATH +"/Drop.num", 0)
self.kfk_combo = numeric(NUMERICPATH +"/Combo.num", 0)
self.kfk_skill = numeric(NUMERICPATH +"/Skill.num", 0)
self.kfk_equip = numeric(NUMERICPATH +"/Equip.num", 0)
self.kfk_kemono = numeric(NUMERICPATH +"/Kemono.num", 0)
self.kfk_stages = numeric(NUMERICPATH +"/Stage.num", 0)
self.kfk_drop_group = numeric(NUMERICPATH +"/DropGroup.num", 0)
self.kfk_trust_attri = numeric(NUMERICPATH +"/TrustAttri.num", 0)
self.kfk_kemono_skill = numeric(NUMERICPATH +"/KemonoSkill.num", 0)
self.kfk_kemono_power = numeric(NUMERICPATH +"/KemonoPower.num", 0)
self.kfk_kemono_waken = numeric(NUMERICPATH +"/KemonoWaken.num", 0)
self.kfk_en_item = numeric(NUMERICPATH + "/en/Item.ntxt", 1)
self.kfk_en_equip = numeric(NUMERICPATH + "/en/Equip.ntxt", 1)
self.kfk_en_skill = numeric(NUMERICPATH + "/en/Skill.ntxt", 1)
self.kfk_en_combo = numeric(NUMERICPATH + "/en/Combo.ntxt", 1)
self.kfk_en_kemono = numeric(NUMERICPATH + "/en/Kemono.ntxt", 1)
self.kfk_en_kemono_WkPaDc = numeric(NUMERICPATH + "/en/KemonoWkPaDc.ntxt", 1)
self.kfk_en_kemono_power = numeric(NUMERICPATH + "/en/KemonoPower.ntxt", 1)
self.kfk_en_kemono_waken = numeric(NUMERICPATH + "/en/KemonoWaken.ntxt", 1)
self.kfk_en_kemono_garden = numeric(NUMERICPATH + "/en/KemonoGarden.ntxt", 1)
self.kfk_en_str = numeric(NUMERICPATH + "/en/Str.ntxt", 1)
self.process_friends()
self.process_stages()
def process_friends(self):
self.processed_friends = {}
for friend_id in self.kfk_kemono.indexed_data:
if friend_id > 19000:
continue
friend = self.kfk_kemono.get(friend_id)
friend["comboSkill"] = ""
combo_friends = []
for entry in friend["combo"]:
combo = self.kfk_combo.get(entry)
if combo is not None:
combo_en = self.kfk_en_combo.get(combo["comboSkill"])
friend["comboSkill"] = {"name":combo_en["comboName"], "desc":combo_en["desc"]}
for combo_friend_id in combo["comboKemono"]:
combo_friend = self.kfk_en_kemono.get(combo_friend_id)
if combo_friend is not None and "name" in combo_friend:
combo_friends.append(combo_friend["name"])
else:
combo_friends.append(combo_friend_id)
friend["combo"] = combo_friends
limit_breaks = []
for bk_value in self.kfk_kemono_power.indexed_data.values():
if len(limit_breaks) == 5:
break
if(bk_value["kemonosn"] == friend_id):
if bk_value["effectType1"] == 0:
pass
if bk_value["effectType1"] == 1 or bk_value["effectType1"] == 3:
lb = self.kfk_en_kemono_power.get(friend_id * 100 + bk_value["powerlevel"])
if "name" in lb:
limit_breaks.append({"name": lb["name"], "desc": lb["describe"]})
else:
limit_breaks.append({"name": None, "desc": lb["describe"]})
if bk_value["effectType1"] == 2:
raise Exception()
if bk_value["effectType1"] == 4:
lb = self.kfk_en_kemono_WkPaDc.get(bk_value['effectParam1'][0] * 100)
if lb is None:
lb = {"name": None, "describe": None}
limit_breaks.append({"name": lb["name"], "desc": clean_skill_string(lb["describe"])})
habits = []
for habit in friend["habitSn"]:
hbt = self.kfk_en_kemono_WkPaDc.get(habit*100)
if hbt is None:
hbt = {"name": None, "describe": None}
habits.append({"name":hbt["name"], "desc":hbt["describe"]})
for i in range (3):
if len(habits) < i+1:
habits.append({"name":None, "desc":None})
awakens = []
max_levels = []
for i in range(3):
awaken_id = friend_id * 100 + i
awaken = self.kfk_kemono_waken.get(awaken_id)
awaken1 = self.kfk_en_kemono_waken.get(awaken_id)
if awaken1 == None:
awaken1 = {"describe":"missing", "brilliance":"missing"}
if awaken != None and awaken1 != None:
awaken["en"] = awaken1
awakens.append(awaken)
max_levels.append(awaken["levelmax"])
def validateValue(obj, valName):
if obj[valName] is None:
obj[valName] = {"name": "", "desc": ""}
if "desc" not in obj[valName]:
obj[valName]["desc"] = None
friend["name"] = self.kfk_en_kemono.get(friend_id)["name"]
ele = friend["element"]
friend["color"] = "Orange" if ele == 1 else "Green" if ele == 2 else "Blue" if ele == 3 else "Error"
ele = friend["showSkillType"]
friend["role"] = "Control" if ele == 1 else "Guard" if ele == 2 else "Healer" if ele == 3 else "Support" if ele == 4 else "Assault" if ele == 5 else "Assault (AOE)" if ele == 6 else "Error"
friend["maxLvl"] = max_levels
friend["awakenings"] = awakens
friend["maxAwaken"] = max_awaken = len(awakens)-1
friend["base_stats"] = calculate_stats(friend, 0, 1)
friend["max_stats"] = calculate_stats(friend, max_awaken, friend["maxLvl"][max_awaken], trust_percent=100, trustAttri= next((e for e in self.kfk_trust_attri.indexed_data.values() if e["kemonoSn"] == friend_id), None))
friend["max_miracle"] = 13 if friend["star"] == 6 or friend["star"] == 5 else 12 if friend["star"] == 4 else 11
friend["collisionSkill"] = self.kfk_en_skill.get(friend["collisionSkill"])
friend["gardenSpec1"] = self.kfk_en_kemono_garden.get(friend["gardenSpec1"])
friend["gardenSpec2"] = self.kfk_en_kemono_garden.get(friend["gardenSpec2"])
friend["gardenSpec3"] = self.kfk_en_kemono_garden.get(friend["gardenSpec3"])
friend["describe"] = self.kfk_en_str.get(friend["describe"])
friend["attack"] = self.kfk_en_skill.get(friend["attack"])
skill_id = friend["skill"]
friend["skill"] = self.kfk_en_skill.get(skill_id)
friend["skill1"] = self.kfk_en_skill.get(skill_id+1) if max_awaken >= 1 else None
friend["skill2"] = self.kfk_en_skill.get(skill_id+2) if max_awaken >= 2 else None
friend["spAttack"] = self.kfk_en_skill.get(friend["spAttack"])
friend["habitSn"] = habits
friend["limitBreaks"] = limit_breaks
validateValue(friend, "collisionSkill")
validateValue(friend, "attack")
validateValue(friend, "skill")
validateValue(friend, "skill1")
validateValue(friend, "skill2")
validateValue(friend, "spAttack")
self.processed_friends[friend_id] = friend
def process_stages(self):
self.item_stages = {}
for stage in self.kfk_stages.indexed_data.values():
merged_array = stage["fastReward2_star3"] + stage["fastReward3_star3"]
if len(merged_array) > 0:
for drop_sn in merged_array:
drop = self.kfk_drop.get(drop_sn)
for drop_group_sn in drop["groupSn"]:
drop_group = self.kfk_drop_group.get(drop_group_sn)
if drop_group["type"] == 2 or drop_group["type"] == 1:
#materials
item_id = drop_group["content"]
item = self.kfk_en_item.get(item_id)
if item_id not in self.item_stages:
self.item_stages[item_id] = []
if stage not in self.item_stages[item_id]:
self.item_stages[item_id].append(stage)
def get_chara(self, id : int):
#return next(lambda f: f["sn"] == id, None)
if id not in self.processed_friends:
return None
else:
friend = self.processed_friends[id]
data = {
"id": friend["sn"],
"name": friend["name"],
"element": friend["color"],
"role": friend["role"],
"star": friend["star"],
"combo": friend["combo"],
"comboSkill": friend["comboSkill"],
"describe": friend["describe"],
"awakenings": friend["awakenings"],
"max_miracle": friend["max_miracle"],
"stats": {
"base":friend["base_stats"],
"max":friend["max_stats"]
},
"collision": {"name": friend["collisionSkill"]["name"], "desc": clean_skill_string(friend["collisionSkill"]["desc"])},
"attack": {"name": friend["attack"]["name"], "desc": clean_skill_string(friend["attack"]["desc"])},
"skills":[
{"name": friend["skill"]["name"], "desc": fill_miracle(friend["skill"]["desc"], miracle_level=friend["max_miracle"], kemono_level=friend["maxLvl"][-1])},
{"name": friend["skill1"]["name"], "desc": fill_miracle(friend["skill1"]["desc"], miracle_level=friend["max_miracle"], kemono_level=friend["maxLvl"][-1])},
{"name": friend["skill2"]["name"], "desc": fill_miracle(friend["skill2"]["desc"], miracle_level=friend["max_miracle"], kemono_level=friend["maxLvl"][-1])},
],
"garden":[
friend["gardenSpec1"],
friend["gardenSpec2"],
friend["gardenSpec3"],
],
"habits":[
{"name": friend["habitSn"][0]["name"], "desc": clean_skill_string(friend["habitSn"][0]["desc"])},
{"name": friend["habitSn"][1]["name"], "desc": clean_skill_string(friend["habitSn"][1]["desc"])},
{"name": friend["habitSn"][2]["name"], "desc": clean_skill_string(friend["habitSn"][2]["desc"])}
],
"limitBreaks":[
friend["limitBreaks"][0],
friend["limitBreaks"][1],
friend["limitBreaks"][2],
friend["limitBreaks"][3],
friend["limitBreaks"][4]
],
"awakenMaterials": get_awaken_materials(friend, self.kfk_kemono_waken, self.kfk_en_item),
"skillMaterials": get_skill_materials(friend, self.kfk_kemono_skill, self.kfk_en_item)
}
return json.dumps(data, sort_keys=False, indent=1, ensure_ascii=False)
def get_chara_wiki(self, id : int):
friend = self.processed_friends[id]
lines = []
lines.append("|name=" + friend["name"])
#stats
lines.append("|maxLvls=" + ",".join([str(lvl) for lvl in friend["maxLvl"]]))
lines.append("|hp=" + str(friend["base_stats"]["hp"]))
lines.append("|maxhp=" + str(friend["max_stats"]["hp"]))
lines.append("|atk=" + str(friend["base_stats"]["patk"]))
lines.append("|maxatk=" + str(friend["max_stats"]["patk"]))
lines.append("|satk=" + str(friend["base_stats"]["satk"]))
lines.append("|maxsatk=" + str(friend["max_stats"]["satk"]))
lines.append("|pdef=" + str(friend["base_stats"]["pdef"]))
lines.append("|maxpdef=" + str(friend["max_stats"]["pdef"]))
lines.append("|sdef=" + str(friend["base_stats"]["sdef"]))
lines.append("|maxsdef=" + str(friend["max_stats"]["sdef"]))
lines.append("|speed=" + str(friend["base_stats"]["speed"]))
lines.append("|maxspeed=" + str(friend["max_stats"]["speed"]))
#skills
lines.append("|collisionSkill=" + friend["collisionSkill"]["name"])
lines.append("|collisionSkillEffect=" + clean_skill_string(friend["collisionSkill"]["desc"]))
lines.append("|attack=" + friend["attack"]["name"])
lines.append("|attackEffect=" + clean_skill_string(friend["attack"]["desc"]))
lines.append("|skill1=" + friend["skill"]["name"])
lines.append("|skill1Effect=" + clean_skill_string(friend["skill"]["desc"]))
lines.append("|skill2=" + friend["skill1"]["name"])
lines.append("|skill2Effect=" + clean_skill_string(friend["skill1"]["desc"]))
lines.append("|skill3=" + friend["skill2"]["name"])
lines.append("|skill3Effect=" + clean_skill_string(friend["skill2"]["desc"]))
lines.append("|spAttack=" + friend["spAttack"]["name"])
lines.append("|spAttackEffect=" + clean_skill_string(friend["spAttack"]["desc"]))
for i in range(0,5):
lb = friend['limitBreaks'][i]
if lb["name"] is None:
lines.append(f"|b{i+1}={friend['limitBreaks'][i]['desc']}")
else:
lines.append(f"|b{i+1}='''{friend['limitBreaks'][i]['name']}'''\n{friend['limitBreaks'][i]['desc']}")
return "\n".join(lines)
def get_item(self, id):
result = {"item": None, "stages": []}
item = self.kfk_en_item.get(id)
if item is None:
return result
result["item"] = item
if id in self.item_stages:
result["stages"] = self.item_stages[id]
return json.dumps(result, sort_keys=False, indent=1, ensure_ascii=False)

View File

@ -1,25 +0,0 @@
from flask_restful import Resource
from flask import current_app as app
from flask import request
class Kingdom_Friend(Resource):
def get(self, id:int):
if "wiki" in request.args:
result = app.databases["Kingdom"].get_chara_wiki(id)
response = app.response_class(
response=result,
status=200,
mimetype='text/plain'
)
else:
result = app.databases["Kingdom"].get_chara(id)
response = app.response_class(
response=result,
status=200,
mimetype='application/json'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response

View File

@ -1,21 +0,0 @@
import json
from flask_restful import Resource
from flask import current_app as app, jsonify
class Kingdom_Friends(Resource):
def get(self):
db = app.databases["Kingdom"]
result = []
for value in db.processed_friends.values():
result.append({"id": value["sn"], "name": value["name"], "describe": value["describe"]["content"] if "content" in value["describe"] else ""})
result = sorted(result, key=lambda f: f["id"])
response = app.response_class(
response=json.dumps(result, ensure_ascii=False, indent=1),
status=200,
mimetype='application/json'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response

View File

@ -1,16 +0,0 @@
from flask_restful import Resource
from flask import current_app as app
from flask import request
class Kingdom_Item(Resource):
def get(self, id:int):
result = app.databases["Kingdom"].get_item(id)
response = app.response_class(
response=result,
status=200,
mimetype='application/json'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response

View File

@ -1,23 +0,0 @@
import json
from flask_restful import Resource
from flask import current_app as app, jsonify
class Kingdom_Items(Resource):
def get(self):
db = app.databases["Kingdom"]
result = []
for key in db.kfk_en_item.indexed_data.keys():
new_value = db.kfk_en_item.indexed_data[key]
new_value["id"] = key
result.append(new_value)
result = sorted(result, key=lambda f: f["id"])
response = app.response_class(
response=json.dumps(result, ensure_ascii=False, indent=1),
status=200,
mimetype='application/json'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response

View File

@ -1,28 +0,0 @@
from flask_restful import Resource
from flask import current_app as app
from flask import request
import json
class Kingdom_Update(Resource):
def post(self):
from ..database import Database
db : Database = app.databases["Kingdom"]
try:
db.reload_data()
response = app.response_class(
response="update successful",
status=200,
mimetype='text/plain'
)
except:
response = app.response_class(
response="update failed",
status=500,
mimetype='text/plain'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response
def get(self):
return self.post()

View File

@ -1,161 +0,0 @@
import re
import math
def get_skill_materials(friend, kfk_kemono_skill, kfk_en_item):
items_total = []
kfk_skill = sorted(filter(lambda s: s["kemonosn"] == friend["sn"] and s["type"] == 2, kfk_kemono_skill.indexed_data.values()), key = lambda s: s["sn"])
for idx, s in enumerate(kfk_skill):
if len(s["item"]) == 0:
continue
items_total.append(
{
"lvl": s["skilllevel"],
"awakenReq": s["wakenlevel"],
"lvlReq": s["kemonolevel"],
"items": list([{"count": num, "name": kfk_en_item.get(item)["name"]} for item, num in zip(s["item"], s["itemnum"])])
}
)
return items_total
def get_awaken_materials(friend, kfk_kemono_waken, kfk_en_item):
items_total = []
kfk_waken = sorted(filter(lambda s: s["kemonosn"] == friend["sn"], kfk_kemono_waken.indexed_data.values()), key = lambda s: s["sn"])
for idx, s in enumerate(kfk_waken):
if len(s["item"]) == 0:
continue
items_total.append(
{
"lvl": idx+1,
"items": list([{"count": num, "name": kfk_en_item.get(item)["name"]} for item, num in zip(s["item"], s["itemnum"])])
}
)
return items_total
def calculate_stats(friend, awaken : int, level : int, trust_percent = 0, trustAttri = None):
stats_array = friend["attrScriptParam"]
max_levels = friend["maxLvl"]
max_awaken = friend["maxAwaken"]
awaken_step = max_awaken + 2
atk_ptr = 1 * awaken_step
satk_ptr = 2 * awaken_step
pdef_ptr = 3 * awaken_step
sdef_ptr = 4 * awaken_step
hp_ptr = 5 * awaken_step
speed_ptr = 6 * awaken_step
stats = {"patk":stats_array[atk_ptr], "satk":stats_array[satk_ptr], "pdef":stats_array[pdef_ptr], "sdef":stats_array[sdef_ptr], "hp":stats_array[hp_ptr], "speed":stats_array[speed_ptr]}
for i in range(awaken + 1):
if i == awaken:
max_level = level
else:
max_level = max_levels[i]
stats["patk"] += stats_array[atk_ptr + 1 + i] * (max_level-1)
stats["satk"] += stats_array[satk_ptr + 1 + i] * (max_level-1)
stats["pdef"] += stats_array[pdef_ptr + 1 + i] * (max_level-1)
stats["sdef"] += stats_array[sdef_ptr + 1 + i] * (max_level-1)
stats["hp"] += stats_array[hp_ptr + 1 + i] * (max_level-1)
stats["speed"] += stats_array[speed_ptr + 1 + i] * (max_level-1)
if trust_percent > 0 and trustAttri != None:
if trustAttri["attribute1"] == 1001:
stats["hp"] += trustAttri["attributeValue1"][trust_percent]
elif trustAttri["attribute1"] == 1021:
stats["patk"] += trustAttri["attributeValue1"][trust_percent]
elif trustAttri["attribute1"] == 1031:
stats["satk"] += trustAttri["attributeValue1"][trust_percent]
else:
print(trustAttri["attribute1"])
if trustAttri["attribute2"] == 1001:
stats["hp"] += trustAttri["attributeValue2"][trust_percent]
elif trustAttri["attribute2"] == 1021:
stats["patk"] += trustAttri["attributeValue2"][trust_percent]
elif trustAttri["attribute2"] == 1031:
stats["satk"] += trustAttri["attributeValue2"][trust_percent]
else:
print(trustAttri["attribute2"])
stats["patk"] = int(round(stats["patk"]))
stats["satk"] = int(round(stats["satk"]))
stats["pdef"] = int(round(stats["pdef"]))
stats["sdef"] = int(round(stats["sdef"]))
stats["hp"] = int(round(stats["hp"]))
stats["speed"] = int(round(stats["speed"]))
return stats
def get_stat_growth(friend, awaken : int):
stats_array = friend["attrScriptParam"]
max_awaken = friend["maxAwaken"]
awaken_step = max_awaken + 2
atk_ptr = 1 * awaken_step
satk_ptr = 2 * awaken_step
pdef_ptr = 3 * awaken_step
sdef_ptr = 4 * awaken_step
hp_ptr = 5 * awaken_step
speed_ptr = 6 * awaken_step
stats = {}
i = awaken
stats["patk"] += stats_array[atk_ptr + 1 + i]
stats["satk"] += stats_array[satk_ptr + 1 + i]
stats["pdef"] += stats_array[pdef_ptr + 1 + i]
stats["sdef"] += stats_array[sdef_ptr + 1 + i]
stats["hp"] += stats_array[hp_ptr + 1 + i]
stats["speed"] += stats_array[speed_ptr + 1 + i]
return stats
def clean_skill_string(input_string):
if input_string is None:
return None
color_tags_pattern = r"<color=#[A-Fa-f0-9]+>(.*?)<\/color>"
output_string = re.sub(color_tags_pattern, r"\1", input_string)
link_tags_pattern = r"<link=\".*?\">(.*?)<\/link>"
output_string = re.sub(link_tags_pattern, r"\1", output_string)
return output_string
def fill_miracle(input_string, miracle_level = 1, kemono_level = 0):
if input_string is None:
return ""
pattern = r"\{\{(.*?)\}\}"
def replace_match(match):
key : str = match.group(1).strip()
if key.startswith("skill | SkillDesc:"):
values = key.replace("skill | SkillDesc:", "").split(',')
values = [float(value) for value in values]
level_up_values = [
values[1],
values[1],
values[1],
values[2],
values[2],
values[2],
values[3],
values[3],
values[3],
values[4],
values[4],
values[4],
]
base_value = values[0]
level_up_value = sum(level_up_values[0:miracle_level-1]) + values[5]
level_bonus = (kemono_level - values[7]) * values[6]
return str(math.floor(base_value + level_up_value + level_bonus))
return "{{" + key + "}}"
output_string = re.sub(pattern, replace_match, input_string)
return clean_skill_string(output_string)

View File

@ -1,21 +0,0 @@
import json
class numeric:
def __init__(self, file_path:str, type:int):
self.indexed_data = {}
with open(file_path, "rt", encoding="utf-8") as f:
if type == 0:
values = json.load(f)["Data"]
for value in values:
self.indexed_data[value["sn"]] = value
if type == 1:
values = json.load(f)
for i in range(0, len(values["IDs"])):
id = values["IDs"][i]
self.indexed_data[id] = values["Data"][i]
def get(self, id):
if id not in self.indexed_data:
return None
return self.indexed_data[id]

View File

@ -1,7 +0,0 @@
class KingdomDB:
def __init__(self, app) -> None:
if "Nexon" in app.databases:
del app.databases["Nexon"]
app.databases["Nexon"] = self

View File

@ -1,45 +0,0 @@
import io
import os
import UnityPy
from UnityPy import enums
from UnityPy.files import ObjectReader
from PIL import Image
def convert(input_path : str, output_path : str, platform = enums.BuildTarget.WebGL, enable_read = False):
env = UnityPy.load(input_path)
for item in env.assets:
item._m_target_platform = int(platform)
for obj in env.objects:
if obj.type == enums.ClassIDType.Texture2D:
convert_texture(obj, platform)
if enable_read and obj.type == enums.ClassIDType.Mesh:
data = obj.read()
data.m_IsReadable = True
data.save()
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with open(output_path, "wb") as f:
f.write(env.file.save(packer="lz4"))
def extract(input_path : str, output_path : str):
env = UnityPy.load(input_path)
for obj in env.objects:
if obj.type == enums.ClassIDType.Texture2D:
os.makedirs(output_path, exist_ok=True)
data = obj.read()
data.image.save(output_path + data.name + ".png", format="PNG")
def convert_texture(obj : ObjectReader, target_platform : enums.BuildTarget):
if target_platform == enums.BuildTarget.WebGL:
data = obj.read()
with io.BytesIO() as output:
data.image.save(output, format="PNG")
if data.image.width % 4 == 0 and data.image.height % 4 == 0:
data.m_TextureFormat = enums.TextureFormat.DXT5
else:
data.m_TextureFormat = enums.TextureFormat.ARGB32
data.image = Image.open(output)
data.save()

View File

@ -1,9 +0,0 @@
import aiohttp
async def download_bytes(url, session : aiohttp.ClientSession):
async with session.get(url) as resp:
return await resp.read()
async def download_text(url, session : aiohttp.ClientSession):
async with session.get(url) as resp:
return await resp.text()

View File

@ -1,3 +0,0 @@
def divide_chunks(l, n):
for i in range(0, len(l), n):
yield l[i:i + n]

View File

@ -1,48 +0,0 @@
from flask_restful import Resource
from flask import current_app as app, request, Request
import requests
def relayRequest(user_request : Request, url, headers):
with requests.Session() as session:
requests.utils.add_dict_to_cookiejar(session.cookies, user_request.cookies)
response = session.post(url, headers=headers, data=request.data)
result = app.response_class(
response=response.text,
status=200,
mimetype='application/json'
)
result.headers.add("Access-Control-Allow-Origin", "*")
return result
class LaunchProxy(Resource):
def post(self):
url = "https://apidgp-gameplayer.games.dmm.com/v5/launch/cl"
headers = {"User-Agent": "DMMGamePlayer5-Win/5.3.12 Electron/32.1.0",
"Client-App": "DMMGamePlayer5",
"Client-version": "5.3.12",
"Content-Type": "application/json"}
return relayRequest(request, url, headers)
class UpdateProxy(Resource):
def post(self):
url = "https://apidgp-gameplayer.games.dmm.com/v5/r2/launch/cl"
headers = {"User-Agent": "DMMGamePlayer5-Win/5.3.12 Electron/32.1.0",
"Client-App": "DMMGamePlayer5",
"Client-version": "5.3.12",
"Content-Type": "application/json"}
return relayRequest(request, url, headers)
class AgreementProxy(Resource):
def post(self):
url = "https://apidgp-gameplayer.games.dmm.com/v5/agreement/confirm/client"
headers = {"User-Agent": "DMMGamePlayer5-Win/5.3.12 Electron/32.1.0",
"Client-App": "DMMGamePlayer5",
"Client-version": "5.3.12",
"Content-Type": "application/json"}
return relayRequest(request, url, headers)