feat(server): finish server part (1/2)
This commit is contained in:
13
README.md
13
README.md
@@ -6,14 +6,13 @@ The `LICENSE` file applies to all files unless listed specifically.
|
|||||||
|
|
||||||
`LICENSE_SPINE` file applies to following files including adapted code for this repo:
|
`LICENSE_SPINE` file applies to following files including adapted code for this repo:
|
||||||
|
|
||||||
- `template/build/spine-widget.js`
|
- `template/assets/spine-player.css`
|
||||||
- `template/new/build/spine-player.css`
|
- `template/assets/spine-player.js`
|
||||||
- `template/new/build/spine-player.js`
|
- `release/*/assets/spine-player.css`
|
||||||
- `release/*/build/spine-player.css`
|
- `release/*/assets/spine-player.js`
|
||||||
- `release/*/build/spine-player.js`
|
|
||||||
|
|
||||||
`Copyright © 2017 - 2021 Arknights/Hypergryph Co., Ltd` applies to following files:
|
`Copyright © 2017 - 2021 Arknights/Hypergryph Co., Ltd` applies to following files:
|
||||||
|
|
||||||
- all files under `operator` folder and its sub-folders
|
- all files under `operator` folder and its sub-folders
|
||||||
- all files under `release/*/assets/*` folder
|
- all files under `release/*/operator/*` folder
|
||||||
- `release/*/build/operator.js`
|
- `release/*/operator/operator.js`
|
||||||
95
aklive2d.py
Normal file
95
aklive2d.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from lib.config import Config
|
||||||
|
from lib.server import Server
|
||||||
|
from lib.builder import Builder
|
||||||
|
|
||||||
|
class AkLive2D:
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.config = Config().config
|
||||||
|
self.args = None
|
||||||
|
self.running = None
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="aklive2d",
|
||||||
|
description="Arknights Live 2D Wallpaper Builder",
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-d",
|
||||||
|
"--debug",
|
||||||
|
dest="debug",
|
||||||
|
action="store_true",
|
||||||
|
help="To debug the app"
|
||||||
|
)
|
||||||
|
|
||||||
|
subprasers = parser.add_subparsers(
|
||||||
|
title="Available commands",
|
||||||
|
dest="command",
|
||||||
|
required=True,
|
||||||
|
help="<Required> Select the command to run"
|
||||||
|
)
|
||||||
|
|
||||||
|
server = subprasers.add_parser(
|
||||||
|
"server",
|
||||||
|
help="Development Server",
|
||||||
|
aliases=['s'],
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||||
|
)
|
||||||
|
server.add_argument(
|
||||||
|
"-p",
|
||||||
|
"--port",
|
||||||
|
dest="port",
|
||||||
|
type=int,
|
||||||
|
default=8080,
|
||||||
|
help="Development server port"
|
||||||
|
)
|
||||||
|
server.add_argument(
|
||||||
|
"-o",
|
||||||
|
"--operator",
|
||||||
|
dest="operator_name",
|
||||||
|
type=str,
|
||||||
|
required=True,
|
||||||
|
help="<Required> Operatro to develop"
|
||||||
|
)
|
||||||
|
|
||||||
|
build = subprasers.add_parser(
|
||||||
|
"build",
|
||||||
|
help="Build releases",
|
||||||
|
aliases=['b'],
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||||
|
)
|
||||||
|
build.add_argument(
|
||||||
|
"-o",
|
||||||
|
"--operator",
|
||||||
|
dest="operator_names",
|
||||||
|
type=str,
|
||||||
|
default="all",
|
||||||
|
nargs='+',
|
||||||
|
help="Operatro to build",
|
||||||
|
choices=["all"] + [key for key in self.config["operators"]]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.args = parser.parse_args()
|
||||||
|
if self.args.command == "server" or self.args.command == "s":
|
||||||
|
self.running = Server(self.args.port, self.args.operator_name, self.config)
|
||||||
|
|
||||||
|
if self.args.command == "build" or self.args.command == "b":
|
||||||
|
self.running = Builder(self.args.operator_names, self.config["operators"])
|
||||||
|
|
||||||
|
self.running.start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.running.stop()
|
||||||
|
return
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
aklive2d = AkLive2D()
|
||||||
|
try:
|
||||||
|
aklive2d.start()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nInterrupted, exiting...")
|
||||||
|
sys.exit()
|
||||||
12
config.yaml
12
config.yaml
@@ -1,12 +1,10 @@
|
|||||||
operators:
|
operators:
|
||||||
skadi:
|
skadi:
|
||||||
common_name: dyn_illust_char_1012_skadi2
|
common_name: dyn_illust_char_1012_skadi2
|
||||||
source_folder: ./operator/skadi/extracted/
|
release_folder: ./release/{name}/
|
||||||
target_folder: ./operator/skadi/
|
source_folder: ./operator/{name}/extracted/
|
||||||
|
target_folder: ./operator/{name}/
|
||||||
server:
|
server:
|
||||||
asset_access_folder: /asset/
|
operator_folder: /operator/
|
||||||
css_access_folder: /build/
|
|
||||||
js_access_folder: /build/
|
|
||||||
release_folder: ./release/
|
release_folder: ./release/
|
||||||
template_folder: ./template/
|
template_folder: /template/
|
||||||
version_file: ./VERSION
|
|
||||||
|
|||||||
44
lib/base64_util.py
Normal file
44
lib/base64_util.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import base64
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
def encode_image(path, prefix=True):
|
||||||
|
with open(pathlib.Path.cwd().joinpath(path), "rb") as f:
|
||||||
|
bytes = f.read()
|
||||||
|
encoded_bytes = base64.b64encode(bytes)
|
||||||
|
humanreadable_data = encoded_bytes.decode("utf-8")
|
||||||
|
if prefix is True:
|
||||||
|
result = "data:image/png;base64," + humanreadable_data
|
||||||
|
else:
|
||||||
|
result = humanreadable_data
|
||||||
|
return result
|
||||||
|
|
||||||
|
def decode_image(data: str, path):
|
||||||
|
if data.strip().startswith("data:") is True:
|
||||||
|
data = data.split(",")[1]
|
||||||
|
encoded_bytes = data.encode("utf-8")
|
||||||
|
with open(pathlib.Path.cwd().joinpath(path), "wb") as f:
|
||||||
|
bytes = base64.decodebytes(encoded_bytes)
|
||||||
|
f.write(bytes)
|
||||||
|
|
||||||
|
def encode_string(data=None, type="text/plain", path=None, prefix=True, encoding="utf-8"):
|
||||||
|
if data is None and path is None:
|
||||||
|
return
|
||||||
|
if data is not None:
|
||||||
|
bytes = data.encode(encoding)
|
||||||
|
elif path is not None:
|
||||||
|
with open(pathlib.Path.cwd().joinpath(path), "r") as f:
|
||||||
|
bytes = f.read()
|
||||||
|
encoded_bytes = base64.b64encode(bytes)
|
||||||
|
humanreadable_data = encoded_bytes.decode(encoding)
|
||||||
|
if prefix is True:
|
||||||
|
result = "data:{};base64,".format(type) + humanreadable_data
|
||||||
|
else:
|
||||||
|
result = humanreadable_data
|
||||||
|
return result
|
||||||
|
|
||||||
|
def decode_string(data:str, encoding="utf-8"):
|
||||||
|
if data.strip().startswith("data:") is True:
|
||||||
|
data = data.split(",")[1]
|
||||||
|
encoded_bytes = data.encode(encoding)
|
||||||
|
bytes = base64.decodebytes(encoded_bytes)
|
||||||
|
return bytes.decode(encoding)
|
||||||
155
lib/builder.py
155
lib/builder.py
@@ -1,46 +1,153 @@
|
|||||||
import threading
|
import threading
|
||||||
|
import shutil
|
||||||
from lib.skeleton_binary import SkeletonBinary
|
from lib.skeleton_binary import SkeletonBinary
|
||||||
from lib.alpha_composite import AlphaComposite
|
from lib.alpha_composite import AlphaComposite
|
||||||
from lib.atlas_reader import AtlasReader
|
from lib.atlas_reader import AtlasReader
|
||||||
|
from lib.base64_util import *
|
||||||
|
|
||||||
class Builder:
|
class Builder:
|
||||||
|
|
||||||
def __init__(self, operator_name, config) -> None:
|
def __init__(self, operator_name, config) -> None:
|
||||||
self.operator_name = operator_name
|
self.operator_name = operator_name
|
||||||
self.config = config
|
self.config = config
|
||||||
|
self.source_path = config["operators"][operator_name]["source_folder"].format(name=operator_name)
|
||||||
|
self.target_path = config["operators"][operator_name]["target_folder"].format(name=operator_name)
|
||||||
|
self.common_name = config["operators"][operator_name]["common_name"]
|
||||||
|
self.file_paths = dict(
|
||||||
|
json=self.target_path + self.common_name + ".json",
|
||||||
|
atlas=self.target_path + self.common_name + ".atlas",
|
||||||
|
skel=self.target_path + self.common_name + ".skel",
|
||||||
|
)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.__set_version()
|
# self.__build_assets()
|
||||||
|
# self.__set_version()
|
||||||
|
# name = "skadi"
|
||||||
|
# print(self.config[name]["source_folder"].format(name=name))
|
||||||
|
source = "./operator/skadi/extracted/"
|
||||||
|
target = "./operator/skadi/"
|
||||||
|
name = "dyn_illust_char_1012_skadi2"
|
||||||
|
print("build")
|
||||||
return
|
return
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
return
|
return
|
||||||
|
|
||||||
def __build_assets(self):
|
def build_assets(self):
|
||||||
source = "./operator/skadi/extracted/"
|
operator_file = pathlib.Path.cwd().joinpath(self.target_path, "operator.js")
|
||||||
target = "./operator/skadi/"
|
if operator_file.exists() is False:
|
||||||
name = "dyn_illust_char_1012_skadi2"
|
print("Building operaotr data...")
|
||||||
ar = AtlasReader(source + name, target + name)
|
|
||||||
skeleton_binary = threading.Thread(
|
alpha_composite_threads = list()
|
||||||
target=SkeletonBinary,
|
png_to_base64_threads = list()
|
||||||
args=(source + name, target + name)
|
prefix = "window.operator = "
|
||||||
)
|
data = dict()
|
||||||
ar_thread = threading.Thread(
|
|
||||||
target=ar.get_images,
|
ar = AtlasReader(self.source_path + self.common_name, self.target_path + self.common_name)
|
||||||
args=()
|
|
||||||
)
|
skeleton_binary_thread = threading.Thread(
|
||||||
ar_thread.start()
|
target=SkeletonBinary,
|
||||||
skeleton_binary.start()
|
args=(self.source_path + self.common_name, self.target_path + self.common_name),
|
||||||
ar_thread.join()
|
daemon=True,
|
||||||
for item in ar.images:
|
)
|
||||||
threading.Thread(
|
ar_thread = threading.Thread(
|
||||||
target=AlphaComposite,
|
target=ar.get_images,
|
||||||
args=(source + item, target + item)
|
daemon=True,
|
||||||
).start()
|
)
|
||||||
|
atlas_base64_thread = threading.Thread(
|
||||||
|
target=self.__atlas_to_base64,
|
||||||
|
args=(
|
||||||
|
self.file_paths["atlas"],
|
||||||
|
data,
|
||||||
|
".{}".format(self.config["server"]["operator_folder"]) + self.common_name + ".atlas",
|
||||||
|
),
|
||||||
|
daemon=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
ar_thread.start()
|
||||||
|
skeleton_binary_thread.start()
|
||||||
|
ar_thread.join()
|
||||||
|
atlas_base64_thread.start()
|
||||||
|
|
||||||
|
# alpha composite
|
||||||
|
for item in ar.images:
|
||||||
|
alpha_composite_thread = threading.Thread(
|
||||||
|
target=AlphaComposite,
|
||||||
|
args=(self.source_path + item, self.target_path + item),
|
||||||
|
daemon=True,
|
||||||
|
)
|
||||||
|
alpha_composite_threads.append(alpha_composite_thread)
|
||||||
|
alpha_composite_thread.start()
|
||||||
|
|
||||||
|
for thread in alpha_composite_threads:
|
||||||
|
thread.join()
|
||||||
|
|
||||||
|
for item in ar.images:
|
||||||
|
png_to_base64_thread = threading.Thread(
|
||||||
|
target=self.__png_to_base64,
|
||||||
|
args=(
|
||||||
|
self.target_path + item,
|
||||||
|
data,
|
||||||
|
".{}".format(self.config["server"]["operator_folder"]) + item,
|
||||||
|
),
|
||||||
|
daemon=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
png_to_base64_threads.append(png_to_base64_thread)
|
||||||
|
png_to_base64_thread.start()
|
||||||
|
|
||||||
|
skeleton_binary_thread.join()
|
||||||
|
|
||||||
|
json_base64_thread =threading.Thread(
|
||||||
|
target=self.__json_to_base64,
|
||||||
|
args=(
|
||||||
|
self.file_paths["json"],
|
||||||
|
data,
|
||||||
|
".{}".format(self.config["server"]["operator_folder"]) + self.common_name + ".json",
|
||||||
|
),
|
||||||
|
daemon=True,
|
||||||
|
)
|
||||||
|
json_base64_thread.start()
|
||||||
|
json_base64_thread.join()
|
||||||
|
|
||||||
|
for thread in png_to_base64_threads:
|
||||||
|
thread.join()
|
||||||
|
|
||||||
|
atlas_base64_thread.join()
|
||||||
|
|
||||||
|
jsonContent = prefix + str(data)
|
||||||
|
with open(operator_file, "w") as f:
|
||||||
|
f.write(jsonContent)
|
||||||
|
|
||||||
|
print("Finished building operaotr data")
|
||||||
return
|
return
|
||||||
|
|
||||||
def __set_version(self):
|
def __set_version(self):
|
||||||
version = input("Enter build version: ")
|
version = input("Enter build version: ")
|
||||||
print(version)
|
print(version)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def __json_to_base64(self, path, dict=None, key=None):
|
||||||
|
with open(pathlib.Path.cwd().joinpath(path), "r") as f:
|
||||||
|
data = f.read()
|
||||||
|
result = encode_string(data, type="application/json")
|
||||||
|
if dict is not None and key is not None:
|
||||||
|
dict[key] = result
|
||||||
|
else:
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __atlas_to_base64(self, path, dict=None, key=None):
|
||||||
|
with open(pathlib.Path.cwd().joinpath(path), "r") as f:
|
||||||
|
data = f.read()
|
||||||
|
result = encode_string(data, type="application/octet-stream")
|
||||||
|
if dict is not None and key is not None:
|
||||||
|
dict[key] = result
|
||||||
|
else:
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __png_to_base64(self, path, dict=None, key=None):
|
||||||
|
result = encode_image(path)
|
||||||
|
if dict is not None and key is not None:
|
||||||
|
dict[key] = result
|
||||||
|
else:
|
||||||
|
return result
|
||||||
|
|||||||
@@ -4,6 +4,23 @@ class Config:
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.config_path = pathlib.Path.cwd().joinpath("config.yaml")
|
self.config_path = pathlib.Path.cwd().joinpath("config.yaml")
|
||||||
|
self.valid_keys = dict(
|
||||||
|
config=dict(
|
||||||
|
server=dict,
|
||||||
|
operators=dict
|
||||||
|
),
|
||||||
|
server=dict(
|
||||||
|
template_folder=str,
|
||||||
|
release_folder=str,
|
||||||
|
operator_folder=str
|
||||||
|
),
|
||||||
|
operators=dict(
|
||||||
|
source_folder=str,
|
||||||
|
target_folder=str,
|
||||||
|
common_name=str,
|
||||||
|
release_folder=str
|
||||||
|
)
|
||||||
|
)
|
||||||
self.__read_config()
|
self.__read_config()
|
||||||
|
|
||||||
def __read_config(self):
|
def __read_config(self):
|
||||||
@@ -15,29 +32,15 @@ class Config:
|
|||||||
self.__validate_config()
|
self.__validate_config()
|
||||||
|
|
||||||
def __validate_config(self):
|
def __validate_config(self):
|
||||||
config_keys = dict(
|
key = "config"
|
||||||
server=dict,
|
self.__config_check(key, self.config, self.valid_keys[key])
|
||||||
operators=dict
|
|
||||||
)
|
|
||||||
self.__config_check("config", self.config, config_keys)
|
|
||||||
|
|
||||||
server_keys = dict(
|
key = "server"
|
||||||
template_folder=str,
|
self.__config_check(key, self.config[key], self.valid_keys[key])
|
||||||
release_folder=str,
|
|
||||||
js_access_folder=str,
|
|
||||||
css_access_folder=str,
|
|
||||||
asset_access_folder=str,
|
|
||||||
version_file=str
|
|
||||||
)
|
|
||||||
self.__config_check("server", self.config["server"], server_keys)
|
|
||||||
|
|
||||||
operators_keys = dict(
|
key = "operators"
|
||||||
source_folder=str,
|
for operator_name, operator_content in self.config[key].items():
|
||||||
target_folder=str,
|
self.__config_check(operator_name, operator_content, self.valid_keys[key])
|
||||||
common_name=str
|
|
||||||
)
|
|
||||||
for operator_name, operator_content in self.config["operators"].items():
|
|
||||||
self.__config_check(operator_name, operator_content, operators_keys)
|
|
||||||
|
|
||||||
with open(self.config_path, 'w') as f:
|
with open(self.config_path, 'w') as f:
|
||||||
yaml.safe_dump(self.config, f, allow_unicode=True)
|
yaml.safe_dump(self.config, f, allow_unicode=True)
|
||||||
|
|||||||
@@ -1,12 +1,47 @@
|
|||||||
|
from http.server import SimpleHTTPRequestHandler
|
||||||
|
import pathlib
|
||||||
|
from socketserver import TCPServer
|
||||||
|
|
||||||
|
from lib.builder import Builder
|
||||||
class Server:
|
class Server:
|
||||||
|
|
||||||
def __init__(self, port, config) -> None:
|
def __init__(self, port, operator, config) -> None:
|
||||||
self.port = port
|
|
||||||
self.config = config
|
self.config = config
|
||||||
|
self.operator = operator
|
||||||
|
self.port = port
|
||||||
|
self.httpd = TCPServer(("", port), httpd(operator, config["server"], directory=str(pathlib.Path.cwd())))
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
# build assets first
|
||||||
|
Builder(self.operator, self.config).build_assets()
|
||||||
|
print("Server is up at 0.0.0.0:{port}".format(port=self.port))
|
||||||
|
self.httpd.serve_forever()
|
||||||
return
|
return
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
return
|
self.httpd.server_close()
|
||||||
|
return
|
||||||
|
|
||||||
|
class httpd(SimpleHTTPRequestHandler):
|
||||||
|
|
||||||
|
def __init__(self, operator, config, directory):
|
||||||
|
self.config = config
|
||||||
|
self.operator = operator
|
||||||
|
self.template_path = directory
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwds):
|
||||||
|
super().__init__(*args, directory=self.template_path, **kwds)
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
split_path = self.path.split("/")
|
||||||
|
access_path = "/{}/".format(split_path[1])
|
||||||
|
|
||||||
|
if self.path == "/":
|
||||||
|
self.path = self.config["template_folder"] + "index.html"
|
||||||
|
elif access_path == "/assets/":
|
||||||
|
# assets folder
|
||||||
|
self.path = self.config["template_folder"] + "assets/" + "/".join([i for i in split_path[2:]])
|
||||||
|
elif self.config["operator_folder"] == access_path:
|
||||||
|
# operator folder
|
||||||
|
self.path = self.config["operator_folder"] + "{}/".format(self.operator) + split_path[-1]
|
||||||
|
return SimpleHTTPRequestHandler.do_GET(self)
|
||||||
@@ -62,7 +62,7 @@ CURVE_BEZIER = 2
|
|||||||
|
|
||||||
class SkeletonBinary:
|
class SkeletonBinary:
|
||||||
|
|
||||||
def __init__(self, file_path, save_path, scale=1) -> None:
|
def __init__(self, file_path, save_path, scale=1):
|
||||||
if file_path.strip().endswith(".skel") is False:
|
if file_path.strip().endswith(".skel") is False:
|
||||||
file_path += ".skel"
|
file_path += ".skel"
|
||||||
if save_path.strip().endswith(".json") is False:
|
if save_path.strip().endswith(".json") is False:
|
||||||
@@ -78,7 +78,7 @@ class SkeletonBinary:
|
|||||||
self.readSkeletonData()
|
self.readSkeletonData()
|
||||||
|
|
||||||
with open(pathlib.Path.cwd().joinpath(save_path), "w") as f:
|
with open(pathlib.Path.cwd().joinpath(save_path), "w") as f:
|
||||||
json.dump(self.dict, f, indent=2, sort_keys=True)
|
json.dump(self.dict, f)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
@@ -743,10 +743,12 @@ class SkeletonBinary:
|
|||||||
if len(chars) < byteCount:
|
if len(chars) < byteCount:
|
||||||
chars = [None] * byteCount
|
chars = [None] * byteCount
|
||||||
charCount = 0
|
charCount = 0
|
||||||
for i in range(byteCount):
|
i = 0
|
||||||
|
while (i < byteCount):
|
||||||
b = self.read()
|
b = self.read()
|
||||||
shiftedB = b >> 4
|
shiftedB = b >> 4
|
||||||
if shiftedB == -1:
|
if shiftedB == -1:
|
||||||
|
# 0b11110000 -> 0b11111111 ?
|
||||||
raise ValueError("shiftedB is -1")
|
raise ValueError("shiftedB is -1")
|
||||||
elif shiftedB == 12 or shiftedB == 13:
|
elif shiftedB == 12 or shiftedB == 13:
|
||||||
chars[charCount] = chr((b & 0x1F) << 6 | self.read() & 0x3F)
|
chars[charCount] = chr((b & 0x1F) << 6 | self.read() & 0x3F)
|
||||||
|
|||||||
40
run.py
40
run.py
@@ -1,40 +0,0 @@
|
|||||||
import argparse
|
|
||||||
|
|
||||||
from lib.config import Config
|
|
||||||
from lib.server import Server
|
|
||||||
from lib.builder import Builder
|
|
||||||
|
|
||||||
class AkLive2D:
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.config = Config().config
|
|
||||||
self.args = None
|
|
||||||
self.server = None
|
|
||||||
self.builder = None
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
parser = argparse.ArgumentParser(description="Arknights Live 2D Wallpaper Builder")
|
|
||||||
arg_group = parser.add_mutually_exclusive_group(required=True)
|
|
||||||
arg_group.add_argument("-s", "--serve", dest="port", type=int, const=8080,nargs="?", help="Development server port (default: 8080)")
|
|
||||||
arg_group.add_argument("-b", "--build", dest="operator_name", type=str, const="all", nargs="?", help="Build wallpapers (default: all)")
|
|
||||||
|
|
||||||
self.args = parser.parse_args()
|
|
||||||
|
|
||||||
if self.args.port is not None:
|
|
||||||
self.server = Server(self.args.port, self.config["server"])
|
|
||||||
self.server.start()
|
|
||||||
|
|
||||||
if self.args.operator_name is not None:
|
|
||||||
self.builder = Builder(self.args.operator_name, self.config["operators"])
|
|
||||||
self.builder.start()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
aklive2d = AkLive2D()
|
|
||||||
try:
|
|
||||||
aklive2d.start()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("\nInterrupted, exiting...")
|
|
||||||
aklive2d.stop()
|
|
||||||
89
template/assets/runner.js
Normal file
89
template/assets/runner.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const RATIO = 0.618;
|
||||||
|
var fps = 60;
|
||||||
|
|
||||||
|
if (params.has("fps")) {
|
||||||
|
var tmp = parseInt(params.get("fps"));
|
||||||
|
if (!isNaN(tmp)) {
|
||||||
|
fps = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function supportsWebGL() {
|
||||||
|
try {
|
||||||
|
let canvas = document.createElement("canvas");
|
||||||
|
let ctx = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
||||||
|
return ctx != null;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateScale(width, height) {
|
||||||
|
let windowWidth = window.innerWidth;
|
||||||
|
let windowHeight = window.innerHeight;
|
||||||
|
let scaleX = windowWidth / width;
|
||||||
|
let scaleY = windowHeight / height;
|
||||||
|
return { x: scaleX, y: scaleY };
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!supportsWebGL()) {
|
||||||
|
alert('WebGL is unavailable. Fallback image will be used.');
|
||||||
|
var e = document.getElementById("container");
|
||||||
|
e.classList.add("fallback");
|
||||||
|
e.parentElement.classList.add("widget-wrapper");
|
||||||
|
|
||||||
|
function fallback() {
|
||||||
|
const scale = calculateScale(window.settings.fallbackImage.width, window.settings.fallbackImage.height);
|
||||||
|
if (scale.x > scale.y) {
|
||||||
|
e.style.width = window.settings.fallbackImage.width * scale.y + "px";
|
||||||
|
e.style.height = window.settings.fallbackImage.height * scale.y + "px";
|
||||||
|
} else if (scale.x < scale.y) {
|
||||||
|
e.style.width = window.settings.fallbackImage.width * scale.x + "px";
|
||||||
|
e.style.height = window.settings.fallbackImage.height * scale.x + "px";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', fallback, true);
|
||||||
|
fallback();
|
||||||
|
} else {
|
||||||
|
var e = document.getElementById("container");
|
||||||
|
var resetTime = window.performance.now();
|
||||||
|
|
||||||
|
var spinePlayer = new spine.SpinePlayer(e, {
|
||||||
|
jsonUrl: window.settings.jsonUrl,
|
||||||
|
atlasUrl: window.settings.atlasUrl,
|
||||||
|
animation: window.settings.animation,
|
||||||
|
rawDataURIs: window.operator,
|
||||||
|
premultipliedAlpha: true,
|
||||||
|
alpha: true,
|
||||||
|
backgroundColor: "#00000000",
|
||||||
|
fps: fps,
|
||||||
|
viewport: window.settings.viewport,
|
||||||
|
showControls: false,
|
||||||
|
defaultMix: window.settings.defaultMix,
|
||||||
|
success: window.settings.success,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function resizeLogo() {
|
||||||
|
document.getElementById("logo").width = window.innerWidth / 2 * RATIO
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', resizeLogo, true);
|
||||||
|
resizeLogo()
|
||||||
|
|
||||||
|
window.addEventListener("contextmenu", e => e.preventDefault());
|
||||||
|
document.addEventListener("gesturestart", e => e.preventDefault());
|
||||||
|
|
||||||
|
// wallpaper engine
|
||||||
|
window.wallpaperPropertyListener = {
|
||||||
|
applyGeneralProperties: function (properties) {
|
||||||
|
if (properties.fps) {
|
||||||
|
spinePlayer.setFps(properties.fps);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("All resources are extracted from Arknights.")
|
||||||
@@ -2089,11 +2089,15 @@ var spine;
|
|||||||
this.errors = {};
|
this.errors = {};
|
||||||
this.toLoad = 0;
|
this.toLoad = 0;
|
||||||
this.loaded = 0;
|
this.loaded = 0;
|
||||||
|
this.rawDataUris = {};
|
||||||
this.textureLoader = textureLoader;
|
this.textureLoader = textureLoader;
|
||||||
this.pathPrefix = pathPrefix;
|
this.pathPrefix = pathPrefix;
|
||||||
}
|
}
|
||||||
AssetManager.downloadText = function (url, success, error) {
|
AssetManager.prototype.downloadText = function (url, success, error) {
|
||||||
var request = new XMLHttpRequest();
|
var request = new XMLHttpRequest();
|
||||||
|
request.overrideMimeType("text/html");
|
||||||
|
if (this.rawDataUris[url])
|
||||||
|
url = this.rawDataUris[url];
|
||||||
request.open("GET", url, true);
|
request.open("GET", url, true);
|
||||||
request.onload = function () {
|
request.onload = function () {
|
||||||
if (request.status == 200) {
|
if (request.status == 200) {
|
||||||
@@ -2108,8 +2112,10 @@ var spine;
|
|||||||
};
|
};
|
||||||
request.send();
|
request.send();
|
||||||
};
|
};
|
||||||
AssetManager.downloadBinary = function (url, success, error) {
|
AssetManager.prototype.downloadBinary = function (url, success, error) {
|
||||||
var request = new XMLHttpRequest();
|
var request = new XMLHttpRequest();
|
||||||
|
if (this.rawDataUris[url])
|
||||||
|
url = this.rawDataUris[url];
|
||||||
request.open("GET", url, true);
|
request.open("GET", url, true);
|
||||||
request.responseType = "arraybuffer";
|
request.responseType = "arraybuffer";
|
||||||
request.onload = function () {
|
request.onload = function () {
|
||||||
@@ -2125,13 +2131,16 @@ var spine;
|
|||||||
};
|
};
|
||||||
request.send();
|
request.send();
|
||||||
};
|
};
|
||||||
|
AssetManager.prototype.setRawDataURI = function (path, data) {
|
||||||
|
this.rawDataUris[this.pathPrefix + path] = data;
|
||||||
|
};
|
||||||
AssetManager.prototype.loadText = function (path, success, error) {
|
AssetManager.prototype.loadText = function (path, success, error) {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
if (success === void 0) { success = null; }
|
if (success === void 0) { success = null; }
|
||||||
if (error === void 0) { error = null; }
|
if (error === void 0) { error = null; }
|
||||||
path = this.pathPrefix + path;
|
path = this.pathPrefix + path;
|
||||||
this.toLoad++;
|
this.toLoad++;
|
||||||
AssetManager.downloadText(path, function (data) {
|
this.downloadText(path, function (data) {
|
||||||
_this.assets[path] = data;
|
_this.assets[path] = data;
|
||||||
if (success)
|
if (success)
|
||||||
success(path, data);
|
success(path, data);
|
||||||
@@ -2150,12 +2159,13 @@ var spine;
|
|||||||
if (success === void 0) { success = null; }
|
if (success === void 0) { success = null; }
|
||||||
if (error === void 0) { error = null; }
|
if (error === void 0) { error = null; }
|
||||||
path = this.pathPrefix + path;
|
path = this.pathPrefix + path;
|
||||||
|
var storagePath = path;
|
||||||
this.toLoad++;
|
this.toLoad++;
|
||||||
var img = new Image();
|
var img = new Image();
|
||||||
img.crossOrigin = "anonymous";
|
img.crossOrigin = "anonymous";
|
||||||
img.onload = function (ev) {
|
img.onload = function (ev) {
|
||||||
var texture = _this.textureLoader(img);
|
var texture = _this.textureLoader(img);
|
||||||
_this.assets[path] = texture;
|
_this.assets[storagePath] = texture;
|
||||||
_this.toLoad--;
|
_this.toLoad--;
|
||||||
_this.loaded++;
|
_this.loaded++;
|
||||||
if (success)
|
if (success)
|
||||||
@@ -2168,6 +2178,8 @@ var spine;
|
|||||||
if (error)
|
if (error)
|
||||||
error(path, "Couldn't load image " + path);
|
error(path, "Couldn't load image " + path);
|
||||||
};
|
};
|
||||||
|
if (this.rawDataUris[path])
|
||||||
|
path = this.rawDataUris[path];
|
||||||
img.src = path;
|
img.src = path;
|
||||||
};
|
};
|
||||||
AssetManager.prototype.loadTextureData = function (path, data, success, error) {
|
AssetManager.prototype.loadTextureData = function (path, data, success, error) {
|
||||||
@@ -2201,7 +2213,7 @@ var spine;
|
|||||||
var parent = path.lastIndexOf("/") >= 0 ? path.substring(0, path.lastIndexOf("/")) : "";
|
var parent = path.lastIndexOf("/") >= 0 ? path.substring(0, path.lastIndexOf("/")) : "";
|
||||||
path = this.pathPrefix + path;
|
path = this.pathPrefix + path;
|
||||||
this.toLoad++;
|
this.toLoad++;
|
||||||
AssetManager.downloadText(path, function (atlasData) {
|
this.downloadText(path, function (atlasData) {
|
||||||
var pagesLoaded = { count: 0 };
|
var pagesLoaded = { count: 0 };
|
||||||
var atlasPages = new Array();
|
var atlasPages = new Array();
|
||||||
try {
|
try {
|
||||||
@@ -9726,6 +9738,12 @@ var spine;
|
|||||||
return dom;
|
return dom;
|
||||||
}
|
}
|
||||||
this.assetManager = new spine.webgl.AssetManager(this.context);
|
this.assetManager = new spine.webgl.AssetManager(this.context);
|
||||||
|
if (config.rawDataURIs) {
|
||||||
|
for (var path in config.rawDataURIs) {
|
||||||
|
var data = config.rawDataURIs[path];
|
||||||
|
this.assetManager.setRawDataURI(path, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
this.assetManager.loadText(config.jsonUrl);
|
this.assetManager.loadText(config.jsonUrl);
|
||||||
this.assetManager.loadTextureAtlas(config.atlasUrl);
|
this.assetManager.loadTextureAtlas(config.atlasUrl);
|
||||||
if (config.backgroundImage && config.backgroundImage.url)
|
if (config.backgroundImage && config.backgroundImage.url)
|
||||||
43
template/assets/style.css
Normal file
43
template/assets/style.css
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
html {
|
||||||
|
-ms-user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-image: url("../operator/operator_bg.png");
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
filter: invert(1);
|
||||||
|
opacity: 0.3;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fallback {
|
||||||
|
margin: auto;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-image: url("../operator/fallback.png");
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
const RATIO = 0.618;
|
|
||||||
var fps = 60;
|
|
||||||
|
|
||||||
if (params.has("fps")) {
|
|
||||||
var tmp = parseInt(params.get("fps"));
|
|
||||||
if (!isNaN(tmp)) {
|
|
||||||
fps = tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function supportsWebGL() {
|
|
||||||
try {
|
|
||||||
let canvas = document.createElement("canvas");
|
|
||||||
let ctx = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
||||||
return ctx != null;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateScale(width, height) {
|
|
||||||
let windowWidth = window.innerWidth;
|
|
||||||
let windowHeight = window.innerHeight;
|
|
||||||
let scaleX = windowWidth / width;
|
|
||||||
let scaleY = windowHeight / height;
|
|
||||||
return { x: scaleX, y: scaleY };
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!supportsWebGL()) {
|
|
||||||
alert('WebGL is unavailable. Fallback image will be used.');
|
|
||||||
var e = document.getElementById("spine-widget");
|
|
||||||
e.classList.add("fallback");
|
|
||||||
e.parentElement.classList.add("widget-wrapper");
|
|
||||||
document.getElementById("loader").style.display = "none";
|
|
||||||
|
|
||||||
function fallback() {
|
|
||||||
const scale = calculateScale(window.operator.fallbackImage.width, window.operator.fallbackImage.height);
|
|
||||||
if (scale.x > scale.y) {
|
|
||||||
e.style.width = window.operator.fallbackImage.width * scale.y;
|
|
||||||
e.style.height = window.operator.fallbackImage.height * scale.y;
|
|
||||||
} else if (scale.x < scale.y) {
|
|
||||||
e.style.width = window.operator.fallbackImage.width * scale.x;
|
|
||||||
e.style.height = window.operator.fallbackImage.height * scale.x;
|
|
||||||
}
|
|
||||||
document.getElementById("logo").width = window.innerWidth / 2 * RATIO
|
|
||||||
}
|
|
||||||
window.addEventListener('resize', fallback, true);
|
|
||||||
fallback();
|
|
||||||
} else {
|
|
||||||
var resetTime = window.performance.now();
|
|
||||||
var e = document.getElementById("spine-widget");
|
|
||||||
var spineWidget;
|
|
||||||
window.isLoaded = false;
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
let isPlayingInteract = false;
|
|
||||||
|
|
||||||
var scale = calculateScale(window.operator.spineSize.width, window.operator.spineSize.height);
|
|
||||||
|
|
||||||
spineWidget = new spine.SpineWidget(e, {
|
|
||||||
atlasContent: atob(window.operator.atlasContent),
|
|
||||||
jsonContent: window.operator.jsonContent,
|
|
||||||
atlasPages: window.operator.atlasPages,
|
|
||||||
atlasPagesContent: window.operator.atlasPagesContent,
|
|
||||||
animation: "Idle",
|
|
||||||
backgroundColor: "#00000000",
|
|
||||||
loop: true,
|
|
||||||
skin: "default",
|
|
||||||
fps: fps,
|
|
||||||
scale: Math.min(scale.x, scale.y),
|
|
||||||
x: window.innerWidth / 2,
|
|
||||||
y: calculateCenterY(scale.x, scale.y),
|
|
||||||
fitToCanvas: false,
|
|
||||||
premultipliedAlpha: true,
|
|
||||||
success: function (widget) {
|
|
||||||
widget.state.addListener({
|
|
||||||
end: (e) => {
|
|
||||||
if (e.animation.name == "Interact") {
|
|
||||||
isPlayingInteract = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
complete: (e) => {
|
|
||||||
if (window.performance.now() - resetTime >= 8 * 1000 && Math.random() < 0.3) {
|
|
||||||
resetTime = window.performance.now();
|
|
||||||
let entry = widget.state.setAnimation(0, "Special", true, 0);
|
|
||||||
entry.mixDuration = 0.2;
|
|
||||||
widget.state.addAnimation(0, "Idle", true, 0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
widget.canvas.onclick = function () {
|
|
||||||
if (isPlayingInteract) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isPlayingInteract = true;
|
|
||||||
let entry = widget.state.setAnimation(0, "Interact", true, 0);
|
|
||||||
entry.mixDuration = 0.2;
|
|
||||||
widget.state.addAnimation(0, "Idle", true, 0);
|
|
||||||
}
|
|
||||||
document.getElementById("loader").style.display = "none";
|
|
||||||
window.isLoaded = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("logo").width = window.innerWidth / 2 * RATIO
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateCenterY(scaleX, scaleY) {
|
|
||||||
var height = window.innerHeight;
|
|
||||||
var offset = Math.min(window.operator.spineSize.offset, height / window.operator.spineSize.correctionFactor);
|
|
||||||
if (scaleX < scaleY) {
|
|
||||||
// constrained by width
|
|
||||||
var scaledSpineHeight = window.operator.spineSize.height * scaleX;
|
|
||||||
return (height - scaledSpineHeight) / 2 + offset;
|
|
||||||
}
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
render();
|
|
||||||
optimizedResize.add(function() {
|
|
||||||
if (window.isLoaded) {
|
|
||||||
window.isLoaded = false;
|
|
||||||
document.getElementById("logo").width = window.innerWidth / 2 * RATIO
|
|
||||||
document.getElementById("loader").style.display = "inherit";
|
|
||||||
var scale = calculateScale(window.operator.spineSize.width, window.operator.spineSize.height);
|
|
||||||
spineWidget.reRender({
|
|
||||||
x: window.innerWidth / 2,
|
|
||||||
y: calculateCenterY(scale.x, scale.y),
|
|
||||||
scale: Math.min(scale.x, scale.y),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("contextmenu", e => e.preventDefault());
|
|
||||||
|
|
||||||
document.addEventListener("gesturestart", e => e.preventDefault());
|
|
||||||
|
|
||||||
// wallpaper engine
|
|
||||||
window.wallpaperPropertyListener = {
|
|
||||||
applyGeneralProperties: function (properties) {
|
|
||||||
if (properties.fps) {
|
|
||||||
spineWidget.setFps(properties.fps);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("All resources are extracted from Arknights.")
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,117 +0,0 @@
|
|||||||
html {
|
|
||||||
-ms-user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
background-image: url("../assets/operator_bg.png");
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
filter: invert(1);
|
|
||||||
opacity: 0.3;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fallback {
|
|
||||||
margin: auto;
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
background-image: url("../assets/fallback.png");
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader,
|
|
||||||
.loader:before,
|
|
||||||
.loader:after {
|
|
||||||
background: #546e7a;
|
|
||||||
-webkit-animation: load1 1s infinite ease-in-out;
|
|
||||||
animation: load1 1s infinite ease-in-out;
|
|
||||||
width: 1em;
|
|
||||||
height: 4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
color: #546e7a;
|
|
||||||
text-indent: -9999em;
|
|
||||||
margin: 88px auto;
|
|
||||||
position: relative;
|
|
||||||
font-size: 11px;
|
|
||||||
-webkit-transform: translateZ(0);
|
|
||||||
-ms-transform: translateZ(0);
|
|
||||||
transform: translateZ(0);
|
|
||||||
-webkit-animation-delay: -0.16s;
|
|
||||||
animation-delay: -0.16s;
|
|
||||||
position: fixed;
|
|
||||||
right: 88px;
|
|
||||||
top: 0;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader:before,
|
|
||||||
.loader:after {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader:before {
|
|
||||||
left: -1.5em;
|
|
||||||
-webkit-animation-delay: -0.32s;
|
|
||||||
animation-delay: -0.32s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader:after {
|
|
||||||
left: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes load1 {
|
|
||||||
0%,
|
|
||||||
80%,
|
|
||||||
100% {
|
|
||||||
box-shadow: 0 0;
|
|
||||||
height: 4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
40% {
|
|
||||||
box-shadow: 0 -2em;
|
|
||||||
height: 5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes load1 {
|
|
||||||
0%,
|
|
||||||
80%,
|
|
||||||
100% {
|
|
||||||
box-shadow: 0 0;
|
|
||||||
height: 4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
40% {
|
|
||||||
box-shadow: 0 -2em;
|
|
||||||
height: 5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<html lang="en">
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
@@ -6,19 +7,21 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
<meta name="renderer" content="webkit">
|
<meta name="renderer" content="webkit">
|
||||||
<title>Skadi the Corrupting Heart</title>
|
<title>Skadi the Corrupting Heart</title>
|
||||||
<link rel="stylesheet" type="text/css" href="./build/style.css?v1.6.1">
|
<link rel="stylesheet" href="./assets/style.css">
|
||||||
|
<link rel="stylesheet" href="./assets/spine-player.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="dev" style="position: fixed;left: 0;left: 0;background-color: white;"></div>
|
<div id="dev" style="position: fixed;left: 0;left: 0;background-color: white;"></div>
|
||||||
<div class="loader" id="loader">Loading...</div>
|
<image src="./operator/operator_logo.png" class="logo" id="logo" alt="operator logo" />
|
||||||
<image src="./assets/operator_logo.png" class="logo" id="logo" alt="operator logo" />
|
<div id="widget-wrapper" style="width: 100%; height: 100%;">
|
||||||
<div id="widget-wrapper">
|
<div id="container" style="width: 100%; height: 100%;"></div>
|
||||||
<div id="spine-widget"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
<script type="text/javascript" src="./build/operator.js?v1.6.1"></script>
|
<script src="./assets/spine-player.js"></script>
|
||||||
<script type="text/javascript" src="./build/spine-widget.js?v1.6.1"></script>
|
<script type="text/javascript" src="./operator/operator.js"></script>
|
||||||
<script type="text/javascript" src="./build/runner.js?v1.6.1"></script>
|
<script type="text/javascript" src="./operator/settings.js"></script>
|
||||||
|
<script type="text/javascript" src="./assets/runner.js"></script>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 1.9 MiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.0 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 33 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 31 KiB |
File diff suppressed because one or more lines are too long
@@ -1,148 +0,0 @@
|
|||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
const RATIO = 0.618;
|
|
||||||
var fps = 60;
|
|
||||||
|
|
||||||
if (params.has("fps")) {
|
|
||||||
var tmp = parseInt(params.get("fps"));
|
|
||||||
if (!isNaN(tmp)) {
|
|
||||||
fps = tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function supportsWebGL() {
|
|
||||||
try {
|
|
||||||
let canvas = document.createElement("canvas");
|
|
||||||
let ctx = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
||||||
return ctx != null;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateScale(width, height) {
|
|
||||||
let windowWidth = window.innerWidth;
|
|
||||||
let windowHeight = window.innerHeight;
|
|
||||||
let scaleX = windowWidth / width;
|
|
||||||
let scaleY = windowHeight / height;
|
|
||||||
return { x: scaleX, y: scaleY };
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!supportsWebGL()) {
|
|
||||||
alert('WebGL is unavailable. Fallback image will be used.');
|
|
||||||
var e = document.getElementById("spine-widget");
|
|
||||||
e.classList.add("fallback");
|
|
||||||
e.parentElement.classList.add("widget-wrapper");
|
|
||||||
document.getElementById("loader").style.display = "none";
|
|
||||||
|
|
||||||
function fallback() {
|
|
||||||
const scale = calculateScale(window.operator.fallbackImage.width, window.operator.fallbackImage.height);
|
|
||||||
if (scale.x > scale.y) {
|
|
||||||
e.style.width = window.operator.fallbackImage.width * scale.y;
|
|
||||||
e.style.height = window.operator.fallbackImage.height * scale.y;
|
|
||||||
} else if (scale.x < scale.y) {
|
|
||||||
e.style.width = window.operator.fallbackImage.width * scale.x;
|
|
||||||
e.style.height = window.operator.fallbackImage.height * scale.x;
|
|
||||||
}
|
|
||||||
document.getElementById("logo").width = window.innerWidth / 2 * RATIO
|
|
||||||
}
|
|
||||||
window.addEventListener('resize', fallback, true);
|
|
||||||
fallback();
|
|
||||||
} else {
|
|
||||||
var resetTime = window.performance.now();
|
|
||||||
var e = document.getElementById("spine-widget");
|
|
||||||
var spineWidget;
|
|
||||||
window.isLoaded = false;
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
let isPlayingInteract = false;
|
|
||||||
|
|
||||||
var scale = calculateScale(window.operator.spineSize.width, window.operator.spineSize.height);
|
|
||||||
|
|
||||||
spineWidget = new spine.SpineWidget(e, {
|
|
||||||
atlasContent: atob(window.operator.atlasContent),
|
|
||||||
json: "./assets/dyn_illust_char_1012_skadi2.json",
|
|
||||||
atlasPages: window.operator.atlasPages,
|
|
||||||
atlasPagesContent: window.operator.atlasPagesContent,
|
|
||||||
animation: "Idle",
|
|
||||||
backgroundColor: "#00000000",
|
|
||||||
loop: true,
|
|
||||||
skin: "default",
|
|
||||||
fps: fps,
|
|
||||||
scale: Math.min(scale.x, scale.y),
|
|
||||||
x: window.innerWidth / 2,
|
|
||||||
y: calculateCenterY(scale.x, scale.y),
|
|
||||||
fitToCanvas: false,
|
|
||||||
premultipliedAlpha: true,
|
|
||||||
success: function (widget) {
|
|
||||||
widget.state.addListener({
|
|
||||||
end: (e) => {
|
|
||||||
if (e.animation.name == "Interact") {
|
|
||||||
isPlayingInteract = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
complete: (e) => {
|
|
||||||
if (window.performance.now() - resetTime >= 8 * 1000 && Math.random() < 0.3) {
|
|
||||||
resetTime = window.performance.now();
|
|
||||||
let entry = widget.state.setAnimation(0, "Special", true, 0);
|
|
||||||
entry.mixDuration = 0.2;
|
|
||||||
widget.state.addAnimation(0, "Idle", true, 0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
widget.canvas.onclick = function () {
|
|
||||||
if (isPlayingInteract) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isPlayingInteract = true;
|
|
||||||
let entry = widget.state.setAnimation(0, "Interact", true, 0);
|
|
||||||
entry.mixDuration = 0.2;
|
|
||||||
widget.state.addAnimation(0, "Idle", true, 0);
|
|
||||||
}
|
|
||||||
document.getElementById("loader").style.display = "none";
|
|
||||||
window.isLoaded = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("logo").width = window.innerWidth / 2 * RATIO
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateCenterY(scaleX, scaleY) {
|
|
||||||
var height = window.innerHeight;
|
|
||||||
var offset = Math.min(window.operator.spineSize.offset, height / window.operator.spineSize.correctionFactor);
|
|
||||||
if (scaleX < scaleY) {
|
|
||||||
// constrained by width
|
|
||||||
var scaledSpineHeight = window.operator.spineSize.height * scaleX;
|
|
||||||
return (height - scaledSpineHeight) / 2 + offset;
|
|
||||||
}
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
render();
|
|
||||||
optimizedResize.add(function() {
|
|
||||||
if (window.isLoaded) {
|
|
||||||
window.isLoaded = false;
|
|
||||||
document.getElementById("logo").width = window.innerWidth / 2 * RATIO
|
|
||||||
document.getElementById("loader").style.display = "inherit";
|
|
||||||
var scale = calculateScale(window.operator.spineSize.width, window.operator.spineSize.height);
|
|
||||||
spineWidget.reRender({
|
|
||||||
x: window.innerWidth / 2,
|
|
||||||
y: calculateCenterY(scale.x, scale.y),
|
|
||||||
scale: Math.min(scale.x, scale.y),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("contextmenu", e => e.preventDefault());
|
|
||||||
|
|
||||||
document.addEventListener("gesturestart", e => e.preventDefault());
|
|
||||||
|
|
||||||
// wallpaper engine
|
|
||||||
window.wallpaperPropertyListener = {
|
|
||||||
applyGeneralProperties: function (properties) {
|
|
||||||
if (properties.fps) {
|
|
||||||
spineWidget.setFps(properties.fps);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("All resources are extracted from Arknights.")
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
html {
|
|
||||||
-ms-user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
background-image: url("../assets/operator_bg.png");
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
filter: invert(1);
|
|
||||||
opacity: 0.3;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fallback {
|
|
||||||
margin: auto;
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
background-image: url("../assets/fallback.png");
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader,
|
|
||||||
.loader:before,
|
|
||||||
.loader:after {
|
|
||||||
background: #546e7a;
|
|
||||||
-webkit-animation: load1 1s infinite ease-in-out;
|
|
||||||
animation: load1 1s infinite ease-in-out;
|
|
||||||
width: 1em;
|
|
||||||
height: 4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
color: #546e7a;
|
|
||||||
text-indent: -9999em;
|
|
||||||
margin: 88px auto;
|
|
||||||
position: relative;
|
|
||||||
font-size: 11px;
|
|
||||||
-webkit-transform: translateZ(0);
|
|
||||||
-ms-transform: translateZ(0);
|
|
||||||
transform: translateZ(0);
|
|
||||||
-webkit-animation-delay: -0.16s;
|
|
||||||
animation-delay: -0.16s;
|
|
||||||
position: fixed;
|
|
||||||
right: 88px;
|
|
||||||
top: 0;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader:before,
|
|
||||||
.loader:after {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader:before {
|
|
||||||
left: -1.5em;
|
|
||||||
-webkit-animation-delay: -0.32s;
|
|
||||||
animation-delay: -0.32s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader:after {
|
|
||||||
left: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes load1 {
|
|
||||||
0%,
|
|
||||||
80%,
|
|
||||||
100% {
|
|
||||||
box-shadow: 0 0;
|
|
||||||
height: 4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
40% {
|
|
||||||
box-shadow: 0 -2em;
|
|
||||||
height: 5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes load1 {
|
|
||||||
0%,
|
|
||||||
80%,
|
|
||||||
100% {
|
|
||||||
box-shadow: 0 0;
|
|
||||||
height: 4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
40% {
|
|
||||||
box-shadow: 0 -2em;
|
|
||||||
height: 5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<script src="./build/spine-player.js"></script>
|
|
||||||
<link rel="stylesheet" href="./build/style.css">
|
|
||||||
<link rel="stylesheet" href="./build/spine-player.css">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="dev" style="position: fixed;left: 0;left: 0;background-color: white;"></div>
|
|
||||||
<div id="container" style="width: 100%; height: 100%;"></div>
|
|
||||||
</body>
|
|
||||||
<script>
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
var fps = 60;
|
|
||||||
|
|
||||||
if (params.has("fps")) {
|
|
||||||
var tmp = parseInt(params.get("fps"));
|
|
||||||
if (!isNaN(tmp)) {
|
|
||||||
fps = tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var resetTime = window.performance.now();
|
|
||||||
let isPlayingInteract = false;
|
|
||||||
new spine.SpinePlayer("container", {
|
|
||||||
jsonUrl: "assets/dyn_illust_char_1012_skadi2.json",
|
|
||||||
atlasUrl: "assets/dyn_illust_char_1012_skadi2.atlas",
|
|
||||||
animation: "Idle",
|
|
||||||
premultipliedAlpha: true,
|
|
||||||
alpha: true,
|
|
||||||
backgroundColor: "#00000000",
|
|
||||||
fps: fps,
|
|
||||||
viewport: {
|
|
||||||
debugRender: false,
|
|
||||||
padLeft: "-5%",
|
|
||||||
padRight: "-10%",
|
|
||||||
padTop: "0%",
|
|
||||||
padBottom: "-12%",
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
},
|
|
||||||
showControls: false,
|
|
||||||
success: function (widget) {
|
|
||||||
widget.animationState.addListener({
|
|
||||||
end: (e) => {
|
|
||||||
if (e.animation.name == "Interact") {
|
|
||||||
isPlayingInteract = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
complete: (e) => {
|
|
||||||
if (window.performance.now() - resetTime >= 8 * 1000 && Math.random() < 0.3) {
|
|
||||||
resetTime = window.performance.now();
|
|
||||||
let entry = widget.animationState.setAnimation(0, "Special", true, 0);
|
|
||||||
entry.mixDuration = 0.2;
|
|
||||||
widget.animationState.addAnimation(0, "Idle", true, 0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
widget.canvas.onclick = function () {
|
|
||||||
if (isPlayingInteract) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isPlayingInteract = true;
|
|
||||||
let entry = widget.animationState.setAnimation(0, "Interact", true, 0);
|
|
||||||
entry.mixDuration = 0.2;
|
|
||||||
widget.animationState.addAnimation(0, "Idle", true, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user