Merge branch 'factorio_gui_client' into Archipelago_Main
This commit is contained in:
commit
a3d2df7c45
|
@ -65,6 +65,7 @@ class FactorioContext(CommonContext):
|
||||||
super(FactorioContext, self).__init__(*args, **kwargs)
|
super(FactorioContext, self).__init__(*args, **kwargs)
|
||||||
self.send_index = 0
|
self.send_index = 0
|
||||||
self.rcon_client = None
|
self.rcon_client = None
|
||||||
|
self.awaiting_bridge = False
|
||||||
self.raw_json_text_parser = RawJSONtoTextParser(self)
|
self.raw_json_text_parser = RawJSONtoTextParser(self)
|
||||||
|
|
||||||
async def server_auth(self, password_requested):
|
async def server_auth(self, password_requested):
|
||||||
|
@ -86,10 +87,10 @@ class FactorioContext(CommonContext):
|
||||||
def on_print_json(self, args: dict):
|
def on_print_json(self, args: dict):
|
||||||
if not self.found_items and args.get("type", None) == "ItemSend" and args["receiving"] == args["sending"]:
|
if not self.found_items and args.get("type", None) == "ItemSend" and args["receiving"] == args["sending"]:
|
||||||
pass # don't want info on other player's local pickups.
|
pass # don't want info on other player's local pickups.
|
||||||
copy_data = copy.deepcopy(args["data"]) # jsontotextparser is destructive currently
|
text = self.raw_json_text_parser(args["data"])
|
||||||
logger.info(self.jsontotextparser(args["data"]))
|
logger.info(text)
|
||||||
if self.rcon_client:
|
if self.rcon_client:
|
||||||
cleaned_text = self.raw_json_text_parser(copy_data).replace('"', '')
|
cleaned_text = text.replace('"', '')
|
||||||
self.rcon_client.send_command(f"/sc game.print(\"Archipelago: {cleaned_text}\")")
|
self.rcon_client.send_command(f"/sc game.print(\"Archipelago: {cleaned_text}\")")
|
||||||
|
|
||||||
async def game_watcher(ctx: FactorioContext, bridge_file: str):
|
async def game_watcher(ctx: FactorioContext, bridge_file: str):
|
||||||
|
@ -97,27 +98,29 @@ async def game_watcher(ctx: FactorioContext, bridge_file: str):
|
||||||
from worlds.factorio.Technologies import lookup_id_to_name
|
from worlds.factorio.Technologies import lookup_id_to_name
|
||||||
bridge_counter = 0
|
bridge_counter = 0
|
||||||
try:
|
try:
|
||||||
while 1:
|
while not ctx.exit_event.is_set():
|
||||||
if os.path.exists(bridge_file):
|
if os.path.exists(bridge_file):
|
||||||
bridge_logger.info("Found Factorio Bridge file.")
|
bridge_logger.info("Found Factorio Bridge file.")
|
||||||
while 1:
|
while not ctx.exit_event.is_set():
|
||||||
with open(bridge_file) as f:
|
if ctx.awaiting_bridge:
|
||||||
data = json.load(f)
|
ctx.awaiting_bridge = False
|
||||||
research_data = data["research_done"]
|
with open(bridge_file) as f:
|
||||||
research_data = {int(tech_name.split("-")[1]) for tech_name in research_data}
|
data = json.load(f)
|
||||||
victory = data["victory"]
|
research_data = data["research_done"]
|
||||||
ctx.auth = data["slot_name"]
|
research_data = {int(tech_name.split("-")[1]) for tech_name in research_data}
|
||||||
ctx.seed_name = data["seed_name"]
|
victory = data["victory"]
|
||||||
|
ctx.auth = data["slot_name"]
|
||||||
|
ctx.seed_name = data["seed_name"]
|
||||||
|
|
||||||
if not ctx.finished_game and victory:
|
if not ctx.finished_game and victory:
|
||||||
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
|
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
|
||||||
ctx.finished_game = True
|
ctx.finished_game = True
|
||||||
|
|
||||||
if ctx.locations_checked != research_data:
|
if ctx.locations_checked != research_data:
|
||||||
bridge_logger.info(f"New researches done: "
|
bridge_logger.info(f"New researches done: "
|
||||||
f"{[lookup_id_to_name[rid] for rid in research_data - ctx.locations_checked]}")
|
f"{[lookup_id_to_name[rid] for rid in research_data - ctx.locations_checked]}")
|
||||||
ctx.locations_checked = research_data
|
ctx.locations_checked = research_data
|
||||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}])
|
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}])
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
else:
|
else:
|
||||||
bridge_counter += 1
|
bridge_counter += 1
|
||||||
|
@ -160,8 +163,9 @@ async def factorio_server_watcher(ctx: FactorioContext):
|
||||||
stream_factorio_output(factorio_process.stdout, factorio_queue)
|
stream_factorio_output(factorio_process.stdout, factorio_queue)
|
||||||
stream_factorio_output(factorio_process.stderr, factorio_queue)
|
stream_factorio_output(factorio_process.stderr, factorio_queue)
|
||||||
script_folder = None
|
script_folder = None
|
||||||
|
progression_watcher = None
|
||||||
try:
|
try:
|
||||||
while 1:
|
while not ctx.exit_event.is_set():
|
||||||
while not factorio_queue.empty():
|
while not factorio_queue.empty():
|
||||||
msg = factorio_queue.get()
|
msg = factorio_queue.get()
|
||||||
factorio_server_logger.info(msg)
|
factorio_server_logger.info(msg)
|
||||||
|
@ -177,7 +181,10 @@ async def factorio_server_watcher(ctx: FactorioContext):
|
||||||
if os.path.exists(bridge_file):
|
if os.path.exists(bridge_file):
|
||||||
os.remove(bridge_file)
|
os.remove(bridge_file)
|
||||||
logging.info(f"Bridge File Path: {bridge_file}")
|
logging.info(f"Bridge File Path: {bridge_file}")
|
||||||
asyncio.create_task(game_watcher(ctx, bridge_file), name="FactorioProgressionWatcher")
|
progression_watcher= asyncio.create_task(
|
||||||
|
game_watcher(ctx, bridge_file), name="FactorioProgressionWatcher")
|
||||||
|
if not ctx.awaiting_bridge and "Archipelago Bridge File written for game tick " in msg:
|
||||||
|
ctx.awaiting_bridge = True
|
||||||
if ctx.rcon_client:
|
if ctx.rcon_client:
|
||||||
while ctx.send_index < len(ctx.items_received):
|
while ctx.send_index < len(ctx.items_received):
|
||||||
transfer_item: NetworkItem = ctx.items_received[ctx.send_index]
|
transfer_item: NetworkItem = ctx.items_received[ctx.send_index]
|
||||||
|
@ -192,10 +199,16 @@ async def factorio_server_watcher(ctx: FactorioContext):
|
||||||
ctx.send_index += 1
|
ctx.send_index += 1
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception(e)
|
logging.exception(e)
|
||||||
logging.error("Aborted Factorio Server Bridge")
|
logging.error("Aborted Factorio Server Bridge")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
factorio_process.terminate()
|
||||||
|
if progression_watcher:
|
||||||
|
await progression_watcher
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
ctx = FactorioContext(None, None, True)
|
ctx = FactorioContext(None, None, True)
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
os.makedirs("logs", exist_ok=True)
|
||||||
|
logging.basicConfig(format='[%(name)s]: %(message)s', level=logging.INFO)
|
||||||
|
logging.getLogger().addHandler(logging.FileHandler(os.path.join("logs", "FactorioClient.txt"), "w"))
|
||||||
|
os.environ["KIVY_NO_CONSOLELOG"] = "1"
|
||||||
|
os.environ["KIVY_NO_FILELOG"] = "1"
|
||||||
|
os.environ["KIVY_NO_ARGS"] = "1"
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from CommonClient import server_loop, logger
|
||||||
|
from FactorioClient import FactorioContext, factorio_server_watcher
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
ctx = FactorioContext(None, None, True)
|
||||||
|
|
||||||
|
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
|
||||||
|
factorio_server_task = asyncio.create_task(factorio_server_watcher(ctx), name="FactorioServer")
|
||||||
|
ui_app = FactorioManager(ctx)
|
||||||
|
ui_task = asyncio.create_task(ui_app.async_run(), name="UI")
|
||||||
|
|
||||||
|
await ctx.exit_event.wait() # wait for signal to exit application
|
||||||
|
ui_app.stop()
|
||||||
|
ctx.server_address = None
|
||||||
|
ctx.snes_reconnect_address = None
|
||||||
|
# allow tasks to quit
|
||||||
|
await ui_task
|
||||||
|
await factorio_server_task
|
||||||
|
await ctx.server_task
|
||||||
|
|
||||||
|
if ctx.server is not None and not ctx.server.socket.closed:
|
||||||
|
await ctx.server.socket.close()
|
||||||
|
if ctx.server_task is not None:
|
||||||
|
await ctx.server_task
|
||||||
|
|
||||||
|
while ctx.input_requests > 0: # clear queue for shutdown
|
||||||
|
ctx.input_queue.put_nowait(None)
|
||||||
|
ctx.input_requests -= 1
|
||||||
|
|
||||||
|
|
||||||
|
from kivy.app import App
|
||||||
|
from kivy.uix.label import Label
|
||||||
|
from kivy.uix.gridlayout import GridLayout
|
||||||
|
from kivy.uix.textinput import TextInput
|
||||||
|
from kivy.uix.recycleview import RecycleView
|
||||||
|
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelHeader
|
||||||
|
from kivy.lang import Builder
|
||||||
|
|
||||||
|
|
||||||
|
class FactorioManager(App):
|
||||||
|
def __init__(self, ctx):
|
||||||
|
super(FactorioManager, self).__init__()
|
||||||
|
self.ctx = ctx
|
||||||
|
self.commandprocessor = ctx.command_processor(ctx)
|
||||||
|
self.icon = "data/icon.png"
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
self.grid = GridLayout()
|
||||||
|
self.grid.cols = 1
|
||||||
|
self.tabs = TabbedPanel()
|
||||||
|
self.tabs.default_tab_text = "All"
|
||||||
|
self.title = "Archipelago Factorio Client"
|
||||||
|
pairs = [
|
||||||
|
("Client", "Archipelago"),
|
||||||
|
("FactorioServer", "Factorio Server Log"),
|
||||||
|
("FactorioWatcher", "Bridge File Log"),
|
||||||
|
]
|
||||||
|
self.tabs.default_tab_content = UILog(*(logging.getLogger(logger_name) for logger_name, name in pairs))
|
||||||
|
for logger_name, display_name in pairs:
|
||||||
|
bridge_logger = logging.getLogger(logger_name)
|
||||||
|
panel = TabbedPanelHeader(text=display_name)
|
||||||
|
self.tabs.add_widget(panel)
|
||||||
|
panel.content = UILog(bridge_logger)
|
||||||
|
self.grid.add_widget(self.tabs)
|
||||||
|
textinput = TextInput(size_hint_y=None, height=30, multiline=False)
|
||||||
|
textinput.bind(on_text_validate=self.on_message)
|
||||||
|
self.grid.add_widget(textinput)
|
||||||
|
self.commandprocessor("/help")
|
||||||
|
return self.grid
|
||||||
|
|
||||||
|
def on_stop(self):
|
||||||
|
self.ctx.exit_event.set()
|
||||||
|
|
||||||
|
def on_message(self, textinput: TextInput):
|
||||||
|
try:
|
||||||
|
input_text = textinput.text.strip()
|
||||||
|
textinput.text = ""
|
||||||
|
|
||||||
|
if self.ctx.input_requests > 0:
|
||||||
|
self.ctx.input_requests -= 1
|
||||||
|
self.ctx.input_queue.put_nowait(input_text)
|
||||||
|
elif input_text:
|
||||||
|
self.commandprocessor(input_text)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(e)
|
||||||
|
|
||||||
|
|
||||||
|
class LogtoUI(logging.Handler):
|
||||||
|
def __init__(self, on_log):
|
||||||
|
super(LogtoUI, self).__init__(logging.DEBUG)
|
||||||
|
self.on_log = on_log
|
||||||
|
|
||||||
|
def handle(self, record: logging.LogRecord) -> None:
|
||||||
|
self.on_log(record)
|
||||||
|
|
||||||
|
|
||||||
|
class UILog(RecycleView):
|
||||||
|
cols = 1
|
||||||
|
|
||||||
|
def __init__(self, *loggers_to_handle, **kwargs):
|
||||||
|
super(UILog, self).__init__(**kwargs)
|
||||||
|
self.data = []
|
||||||
|
for logger in loggers_to_handle:
|
||||||
|
logger.addHandler(LogtoUI(self.on_log))
|
||||||
|
|
||||||
|
def on_log(self, record: logging.LogRecord) -> None:
|
||||||
|
self.data.append({"text": record.getMessage()})
|
||||||
|
|
||||||
|
def update_text_width(self, *_):
|
||||||
|
self.message.text_size = (self.message.width * 0.9, None)
|
||||||
|
|
||||||
|
|
||||||
|
Builder.load_string('''
|
||||||
|
<TabbedPanel>
|
||||||
|
tab_width: 200
|
||||||
|
<Row@Label>:
|
||||||
|
canvas.before:
|
||||||
|
Color:
|
||||||
|
rgba: 0.2, 0.2, 0.2, 1
|
||||||
|
Rectangle:
|
||||||
|
size: self.size
|
||||||
|
pos: self.pos
|
||||||
|
text_size: self.width, None
|
||||||
|
size_hint_y: None
|
||||||
|
height: self.texture_size[1]
|
||||||
|
font_size: dp(20)
|
||||||
|
<UILog>:
|
||||||
|
viewclass: 'Row'
|
||||||
|
scroll_y: 0
|
||||||
|
effect_cls: "ScrollEffect"
|
||||||
|
RecycleBoxLayout:
|
||||||
|
default_size: None, dp(20)
|
||||||
|
default_size_hint: 1, None
|
||||||
|
size_hint_y: None
|
||||||
|
height: self.minimum_height
|
||||||
|
orientation: 'vertical'
|
||||||
|
spacing: dp(3)
|
||||||
|
''')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(main())
|
||||||
|
loop.close()
|
5
Main.py
5
Main.py
|
@ -502,7 +502,10 @@ def main(args, seed=None):
|
||||||
minimum_versions = {"server": (0, 1, 1), "clients": client_versions}
|
minimum_versions = {"server": (0, 1, 1), "clients": client_versions}
|
||||||
games = {}
|
games = {}
|
||||||
for slot in world.player_ids:
|
for slot in world.player_ids:
|
||||||
client_versions[slot] = (0, 0, 3)
|
if world.game[slot] == "Factorio":
|
||||||
|
client_versions[slot] = (0, 1, 2)
|
||||||
|
else:
|
||||||
|
client_versions[slot] = (0, 0, 3)
|
||||||
games[slot] = world.game[slot]
|
games[slot] = world.game[slot]
|
||||||
connect_names = {base64.b64encode(rom_name).decode(): (team, slot) for
|
connect_names = {base64.b64encode(rom_name).decode(): (team, slot) for
|
||||||
slot, team, rom_name in rom_names}
|
slot, team, rom_name in rom_names}
|
||||||
|
|
2
Utils.py
2
Utils.py
|
@ -12,7 +12,7 @@ class Version(typing.NamedTuple):
|
||||||
minor: int
|
minor: int
|
||||||
build: int
|
build: int
|
||||||
|
|
||||||
__version__ = "0.1.1"
|
__version__ = "0.1.2"
|
||||||
_version_tuple = tuplize_version(__version__)
|
_version_tuple = tuplize_version(__version__)
|
||||||
|
|
||||||
import builtins
|
import builtins
|
||||||
|
|
|
@ -133,16 +133,18 @@ script.on_init(function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- for testing
|
-- for testing
|
||||||
script.on_event(defines.events.on_tick, function(event)
|
-- script.on_event(defines.events.on_tick, function(event)
|
||||||
if event.tick%600 == 300 then
|
-- if event.tick%3600 == 300 then
|
||||||
dumpInfo(game.forces["player"])
|
-- dumpInfo(game.forces["player"])
|
||||||
end
|
-- end
|
||||||
end)
|
-- end)
|
||||||
|
|
||||||
-- hook into researches done
|
-- hook into researches done
|
||||||
script.on_event(defines.events.on_research_finished, function(event)
|
script.on_event(defines.events.on_research_finished, function(event)
|
||||||
local technology = event.research
|
local technology = event.research
|
||||||
dumpInfo(technology.force)
|
if technology.researched and string.find(technology.name, "ap%-") == 1 then
|
||||||
|
dumpInfo(technology.force) --is sendable
|
||||||
|
end
|
||||||
if FREE_SAMPLES == 0 then
|
if FREE_SAMPLES == 0 then
|
||||||
return -- Nothing else to do
|
return -- Nothing else to do
|
||||||
end
|
end
|
||||||
|
@ -186,6 +188,7 @@ function dumpInfo(force)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
game.write_file("ap_bridge.json", game.table_to_json(data_collection), false, 0)
|
game.write_file("ap_bridge.json", game.table_to_json(data_collection), false, 0)
|
||||||
|
log("Archipelago Bridge File written for game tick ".. game.tick .. ".")
|
||||||
-- game.write_file("research_done.json", game.table_to_json(data_collection), false, 0)
|
-- game.write_file("research_done.json", game.table_to_json(data_collection), false, 0)
|
||||||
-- game.print("Sent progress to Archipelago.")
|
-- game.print("Sent progress to Archipelago.")
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
#define sourcepath "build_factorio\exe.win-amd64-3.8\"
|
||||||
|
#define MyAppName "Archipelago Factorio Client"
|
||||||
|
#define MyAppExeName "ArchipelagoGraphicalFactorioClient.exe"
|
||||||
|
#define MyAppIcon "icon.ico"
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
; NOTE: The value of AppId uniquely identifies this application.
|
||||||
|
; Do not use the same AppId value in installers for other applications.
|
||||||
|
AppId={{D13CEBD0-F1D5-4435-A4A6-5243F934613F}}
|
||||||
|
AppName={#MyAppName}
|
||||||
|
AppVerName={#MyAppName}
|
||||||
|
DefaultDirName={commonappdata}\{#MyAppName}
|
||||||
|
DisableProgramGroupPage=yes
|
||||||
|
DefaultGroupName=Archipelago
|
||||||
|
OutputDir=setups
|
||||||
|
OutputBaseFilename=Setup {#MyAppName}
|
||||||
|
Compression=lzma2
|
||||||
|
SolidCompression=yes
|
||||||
|
LZMANumBlockThreads=8
|
||||||
|
ArchitecturesInstallIn64BitMode=x64
|
||||||
|
ChangesAssociations=yes
|
||||||
|
ArchitecturesAllowed=x64
|
||||||
|
AllowNoIcons=yes
|
||||||
|
SetupIconFile={#MyAppIcon}
|
||||||
|
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||||
|
SignTool= signtool
|
||||||
|
LicenseFile= LICENSE
|
||||||
|
WizardStyle= modern
|
||||||
|
SetupLogging=yes
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|
||||||
|
[Tasks]
|
||||||
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}";
|
||||||
|
|
||||||
|
|
||||||
|
[Dirs]
|
||||||
|
NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify;
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall
|
||||||
|
Source: "{#sourcepath}*"; Excludes: "*.sfc, *.log, data\sprites\alttpr"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{group}\{#MyAppName} Folder"; Filename: "{app}";
|
||||||
|
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}";
|
||||||
|
Name: "{commondesktop}\{#MyAppName} Folder"; Filename: "{app}"; Tasks: desktopicon
|
||||||
|
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..."
|
||||||
|
|
||||||
|
[UninstallDelete]
|
||||||
|
Type: dirifempty; Name: "{app}"
|
||||||
|
|
||||||
|
|
||||||
|
[Code]
|
||||||
|
// See: https://stackoverflow.com/a/51614652/2287576
|
||||||
|
function IsVCRedist64BitNeeded(): boolean;
|
||||||
|
var
|
||||||
|
strVersion: string;
|
||||||
|
begin
|
||||||
|
if (RegQueryStringValue(HKEY_LOCAL_MACHINE,
|
||||||
|
'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64', 'Version', strVersion)) then
|
||||||
|
begin
|
||||||
|
// Is the installed version at least the packaged one ?
|
||||||
|
Log('VC Redist x64 Version : found ' + strVersion);
|
||||||
|
Result := (CompareStr(strVersion, 'v14.28.29325') < 0);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
// Not even an old version installed
|
||||||
|
Log('VC Redist x64 is not already installed');
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
85
setup.py
85
setup.py
|
@ -38,15 +38,15 @@ def _threaded_hash(filepath):
|
||||||
os.makedirs(buildfolder, exist_ok=True)
|
os.makedirs(buildfolder, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
def manifest_creation():
|
def manifest_creation(folder):
|
||||||
hashes = {}
|
hashes = {}
|
||||||
manifestpath = os.path.join(buildfolder, "manifest.json")
|
manifestpath = os.path.join(folder, "manifest.json")
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
pool = ThreadPoolExecutor()
|
pool = ThreadPoolExecutor()
|
||||||
for dirpath, dirnames, filenames in os.walk(buildfolder):
|
for dirpath, dirnames, filenames in os.walk(folder):
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
path = os.path.join(dirpath, filename)
|
path = os.path.join(dirpath, filename)
|
||||||
hashes[os.path.relpath(path, start=buildfolder)] = pool.submit(_threaded_hash, path)
|
hashes[os.path.relpath(path, start=folder)] = pool.submit(_threaded_hash, path)
|
||||||
import json
|
import json
|
||||||
from Utils import _version_tuple
|
from Utils import _version_tuple
|
||||||
manifest = {"buildtime": buildtime.isoformat(sep=" ", timespec="seconds"),
|
manifest = {"buildtime": buildtime.isoformat(sep=" ", timespec="seconds"),
|
||||||
|
@ -161,4 +161,79 @@ for file in os.listdir(alttpr_sprites_folder):
|
||||||
if file != ".gitignore":
|
if file != ".gitignore":
|
||||||
os.remove(alttpr_sprites_folder / file)
|
os.remove(alttpr_sprites_folder / file)
|
||||||
|
|
||||||
manifest_creation()
|
manifest_creation(buildfolder)
|
||||||
|
|
||||||
|
buildfolder = Path("build_factorio", folder)
|
||||||
|
sbuildfolder = str(buildfolder)
|
||||||
|
libfolder = Path(buildfolder, "lib")
|
||||||
|
library = Path(libfolder, "library.zip")
|
||||||
|
print("Outputting Factorio Client to: " + sbuildfolder)
|
||||||
|
|
||||||
|
os.makedirs(buildfolder, exist_ok=True)
|
||||||
|
|
||||||
|
scripts = {"FactorioClient.py": "ArchipelagoConsoleFactorioClient"}
|
||||||
|
|
||||||
|
exes = []
|
||||||
|
|
||||||
|
for script, scriptname in scripts.items():
|
||||||
|
exes.append(cx_Freeze.Executable(
|
||||||
|
script=script,
|
||||||
|
target_name=scriptname + ("" if sys.platform == "linux" else ".exe"),
|
||||||
|
icon=icon,
|
||||||
|
))
|
||||||
|
exes.append(cx_Freeze.Executable(
|
||||||
|
script="FactorioClientGUI.py",
|
||||||
|
target_name="ArchipelagoGraphicalFactorioClient" + ("" if sys.platform == "linux" else ".exe"),
|
||||||
|
icon=icon,
|
||||||
|
base="Win32GUI"
|
||||||
|
))
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
buildtime = datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
cx_Freeze.setup(
|
||||||
|
name="Archipelago Factorio Client",
|
||||||
|
version=f"{buildtime.year}.{buildtime.month}.{buildtime.day}.{buildtime.hour}",
|
||||||
|
description="Archipelago Factorio Client",
|
||||||
|
executables=exes,
|
||||||
|
options={
|
||||||
|
"build_exe": {
|
||||||
|
"packages": ["websockets", "kivy"],
|
||||||
|
"includes": [],
|
||||||
|
"excludes": ["numpy", "Cython", "PySide2", "PIL",
|
||||||
|
"pandas"],
|
||||||
|
"zip_include_packages": ["*"],
|
||||||
|
"zip_exclude_packages": ["kivy"],
|
||||||
|
"include_files": [],
|
||||||
|
"include_msvcr": True,
|
||||||
|
"replace_paths": [("*", "")],
|
||||||
|
"optimize": 2,
|
||||||
|
"build_exe": buildfolder
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
extra_data = ["LICENSE", "data", "host.yaml", "meta.yaml"]
|
||||||
|
from kivy_deps import sdl2, glew
|
||||||
|
for folder in sdl2.dep_bins+glew.dep_bins:
|
||||||
|
shutil.copytree(folder, buildfolder, dirs_exist_ok=True)
|
||||||
|
for data in extra_data:
|
||||||
|
installfile(Path(data))
|
||||||
|
|
||||||
|
|
||||||
|
os.makedirs(buildfolder / "Players", exist_ok=True)
|
||||||
|
shutil.copyfile("playerSettings.yaml", buildfolder / "Players" / "weightedSettings.yaml")
|
||||||
|
|
||||||
|
if signtool:
|
||||||
|
for exe in exes:
|
||||||
|
print(f"Signing {exe.target_name}")
|
||||||
|
os.system(signtool + os.path.join(buildfolder, exe.target_name))
|
||||||
|
|
||||||
|
alttpr_sprites_folder = buildfolder / "data" / "sprites" / "alttpr"
|
||||||
|
for file in os.listdir(alttpr_sprites_folder):
|
||||||
|
if file != ".gitignore":
|
||||||
|
os.remove(alttpr_sprites_folder / file)
|
||||||
|
|
||||||
|
manifest_creation(buildfolder)
|
||||||
|
|
|
@ -241,25 +241,13 @@ def generate_itempool(world, player: int):
|
||||||
else:
|
else:
|
||||||
world.push_item(world.get_location('Ganon', player), ItemFactory('Triforce', player), False)
|
world.push_item(world.get_location('Ganon', player), ItemFactory('Triforce', player), False)
|
||||||
|
|
||||||
if world.goal[player] in ['triforcehunt', 'localtriforcehunt']:
|
|
||||||
region = world.get_region('Light World', player)
|
|
||||||
|
|
||||||
loc = ALttPLocation(player, "Murahdahla", parent=region)
|
|
||||||
loc.access_rule = lambda state: state.has_triforce_pieces(state.world.treasure_hunt_count[player], player)
|
|
||||||
|
|
||||||
region.locations.append(loc)
|
|
||||||
world.dynamic_locations.append(loc)
|
|
||||||
|
|
||||||
world.clear_location_cache()
|
|
||||||
|
|
||||||
world.push_item(loc, ItemFactory('Triforce', player), False)
|
|
||||||
loc.event = True
|
|
||||||
loc.locked = True
|
|
||||||
|
|
||||||
if world.goal[player] == 'icerodhunt':
|
if world.goal[player] == 'icerodhunt':
|
||||||
world.progression_balancing[player] = False
|
world.progression_balancing[player] = False
|
||||||
loc = world.get_location('Turtle Rock - Boss', player)
|
loc = world.get_location('Turtle Rock - Boss', player)
|
||||||
world.push_item(loc, ItemFactory('Triforce', player), False)
|
world.push_item(loc, ItemFactory('Triforce Piece', player), False)
|
||||||
|
world.treasure_hunt_count[player] = 1
|
||||||
if world.boss_shuffle[player] != 'none':
|
if world.boss_shuffle[player] != 'none':
|
||||||
if 'turtle rock-' not in world.boss_shuffle[player]:
|
if 'turtle rock-' not in world.boss_shuffle[player]:
|
||||||
world.boss_shuffle[player] = f'Turtle Rock-Trinexx;{world.boss_shuffle[player]}'
|
world.boss_shuffle[player] = f'Turtle Rock-Trinexx;{world.boss_shuffle[player]}'
|
||||||
|
@ -295,6 +283,19 @@ def generate_itempool(world, player: int):
|
||||||
for item in itempool:
|
for item in itempool:
|
||||||
world.push_precollected(ItemFactory(item, player))
|
world.push_precollected(ItemFactory(item, player))
|
||||||
|
|
||||||
|
if world.goal[player] in ['triforcehunt', 'localtriforcehunt', 'icerodhunt']:
|
||||||
|
region = world.get_region('Light World', player)
|
||||||
|
|
||||||
|
loc = ALttPLocation(player, "Murahdahla", parent=region)
|
||||||
|
loc.access_rule = lambda state: state.has_triforce_pieces(state.world.treasure_hunt_count[player], player)
|
||||||
|
|
||||||
|
region.locations.append(loc)
|
||||||
|
world.dynamic_locations.append(loc)
|
||||||
|
world.clear_location_cache()
|
||||||
|
|
||||||
|
world.push_item(loc, ItemFactory('Triforce', player), False)
|
||||||
|
loc.event = True
|
||||||
|
loc.locked = True
|
||||||
|
|
||||||
world.get_location('Ganon', player).event = True
|
world.get_location('Ganon', player).event = True
|
||||||
world.get_location('Ganon', player).locked = True
|
world.get_location('Ganon', player).locked = True
|
||||||
|
|
|
@ -2337,9 +2337,13 @@ def write_strings(rom, world, player, team):
|
||||||
tt['sign_ganon'] = f'You need {world.crystals_needed_for_ganon[player]} crystals to beat Ganon and ' \
|
tt['sign_ganon'] = f'You need {world.crystals_needed_for_ganon[player]} crystals to beat Ganon and ' \
|
||||||
f'have beaten Agahnim atop Ganons Tower'
|
f'have beaten Agahnim atop Ganons Tower'
|
||||||
elif world.goal[player] == "icerodhunt":
|
elif world.goal[player] == "icerodhunt":
|
||||||
tt['sign_ganon'] = 'Go find the Ice Rod and Kill Trinexx... Ganon is invincible!'
|
tt['sign_ganon'] = 'Go find the Ice Rod and Kill Trinexx, then talk to Murahdahla... Ganon is invincible!'
|
||||||
tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Go kill Trinexx instead.'
|
tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Go kill Trinexx instead.'
|
||||||
tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
|
tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
|
||||||
|
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\n" \
|
||||||
|
"invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \
|
||||||
|
"hidden in a hollow tree. " \
|
||||||
|
"If you bring me the Triforce piece from Turtle Rock, I can reassemble it."
|
||||||
else:
|
else:
|
||||||
if world.crystals_needed_for_ganon[player] == 1:
|
if world.crystals_needed_for_ganon[player] == 1:
|
||||||
tt['sign_ganon'] = 'You need a crystal to beat Ganon.'
|
tt['sign_ganon'] = 'You need a crystal to beat Ganon.'
|
||||||
|
@ -2354,7 +2358,7 @@ def write_strings(rom, world, player, team):
|
||||||
tt['sahasrahla_quest_have_master_sword'] = Sahasrahla2_texts[local_random.randint(0, len(Sahasrahla2_texts) - 1)]
|
tt['sahasrahla_quest_have_master_sword'] = Sahasrahla2_texts[local_random.randint(0, len(Sahasrahla2_texts) - 1)]
|
||||||
tt['blind_by_the_light'] = Blind_texts[local_random.randint(0, len(Blind_texts) - 1)]
|
tt['blind_by_the_light'] = Blind_texts[local_random.randint(0, len(Blind_texts) - 1)]
|
||||||
|
|
||||||
if world.goal[player] in ['triforcehunt', 'localtriforcehunt']:
|
if world.goal[player] in ['triforcehunt', 'localtriforcehunt', 'icerodhunt']:
|
||||||
tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Get the Triforce Pieces.'
|
tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Get the Triforce Pieces.'
|
||||||
tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
|
tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
|
||||||
if world.goal[player] == 'triforcehunt' and world.players > 1:
|
if world.goal[player] == 'triforcehunt' and world.players > 1:
|
||||||
|
@ -2364,12 +2368,12 @@ def write_strings(rom, world, player, team):
|
||||||
if world.treasure_hunt_count[player] > 1:
|
if world.treasure_hunt_count[player] > 1:
|
||||||
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\n" \
|
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\n" \
|
||||||
"invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \
|
"invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \
|
||||||
"hidden in a hollow tree. If you bring\n%d triforce pieces out of %d, I can reassemble it." % \
|
"hidden in a hollow tree. If you bring\n%d Triforce pieces out of %d, I can reassemble it." % \
|
||||||
(world.treasure_hunt_count[player], world.triforce_pieces_available[player])
|
(world.treasure_hunt_count[player], world.triforce_pieces_available[player])
|
||||||
else:
|
else:
|
||||||
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\n" \
|
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\n" \
|
||||||
"invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \
|
"invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \
|
||||||
"hidden in a hollow tree. If you bring\n%d triforce piece out of %d, I can reassemble it." % \
|
"hidden in a hollow tree. If you bring\n%d Triforce piece out of %d, I can reassemble it." % \
|
||||||
(world.treasure_hunt_count[player], world.triforce_pieces_available[player])
|
(world.treasure_hunt_count[player], world.triforce_pieces_available[player])
|
||||||
elif world.goal[player] in ['pedestal']:
|
elif world.goal[player] in ['pedestal']:
|
||||||
tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Your goal is at the pedestal.'
|
tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Your goal is at the pedestal.'
|
||||||
|
|
Loading…
Reference in New Issue