diff --git a/.gitignore b/.gitignore index 38706c1..4fd2842 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ __pycache__/ /.venv/ *.egg-info/ -build/ \ No newline at end of file +build/ + +# Nix +/result diff --git a/factorio_sat/assets/fetch.py b/factorio_sat/assets/fetch.py index 5220546..0e256c4 100644 --- a/factorio_sat/assets/fetch.py +++ b/factorio_sat/assets/fetch.py @@ -3,8 +3,12 @@ import json import shutil import sys +import os + from os import path +ASSETS_DIR = path.join(os.getenv("XDG_DATA_HOME"), "factorio-sat/assets") + try: from luaparser import ast except ModuleNotFoundError: @@ -12,8 +16,8 @@ ast = None -def fetch_tilemaps(base_directory: str): - graphics_directory = path.join(base_directory, 'graphics', 'entity') +def copy_game_tilemaps(base_dir: str, assets_dir: str): + graphics_dir = path.join(base_dir, 'graphics', 'entity') files = [ ('assembling-machine-1', 'hr-assembling-machine-1.png'), @@ -46,8 +50,9 @@ def fetch_tilemaps(base_directory: str): ] for file in files: - source = path.join(graphics_directory, *file) - destination = path.join(path.dirname(__file__), file[-1]) + source = path.join(graphics_dir, *file) + + destination = path.join(assets_dir, file[-1]) print('Copying: {} -> {}'.format(source, destination)) shutil.copyfile(source, destination) @@ -130,9 +135,9 @@ def get_recipes_for_variant(data, variant): return recipes -def fetch_recipes(base_directory): +def copy_game_recipes(base_dir, assets_dir): data = [] - for file in glob.glob(path.join(base_directory, 'prototypes', 'recipe', '*.lua')): + for file in glob.glob(path.join(base_dir, 'prototypes', 'recipe', '*.lua')): with open(file) as f: text = f.read() @@ -142,7 +147,7 @@ def fetch_recipes(base_directory): data += entry for variant in ('normal', 'expensive'): - with open(path.join(path.dirname(__file__), f'{variant}-recipes.json'), 'w') as f: + with open(path.join(assets_dir, f'{variant}-recipes.json'), 'w') as f: json.dump(get_recipes_for_variant(data, variant), f) @@ -170,11 +175,12 @@ def main(): if not path.exists(game_directory): raise RuntimeError('Factorio not found at: {}'.format(game_directory)) - base_directory = path.join(game_directory, 'data', 'base') + game_base_dir = path.join(game_directory, 'data', 'base') - fetch_tilemaps(base_directory) + os.makedirs(ASSETS_DIR, exist_ok=True) + copy_game_tilemaps(game_base_dir, ASSETS_DIR) if ast is not None: - fetch_recipes(base_directory) + copy_game_recipes(game_base_dir, ASSETS_DIR) if __name__ == '__main__': diff --git a/factorio_sat/tilemaps.py b/factorio_sat/tilemaps.py index 8b4d1f3..55dc3a3 100644 --- a/factorio_sat/tilemaps.py +++ b/factorio_sat/tilemaps.py @@ -1,9 +1,12 @@ +import os + from typing import * from os import path from OpenGL.GL import * from PIL import Image +ASSETS_DIR = os.path.join(os.getenv("XDG_DATA_HOME"), "factorio-sat/assets") def get_texture_size(texture: int) -> Tuple[int, int]: glBindTexture(GL_TEXTURE_2D, texture) @@ -88,29 +91,28 @@ def render(self, x, y, lower=(0, 0), upper=(1, 1)): def init(): global BELT, UNDERGROUND, SPLITTER_EAST, SPLITTER_WEST, SPLITTER_NORTH, SPLITTER_SOUTH, INSERTER_PLATFORM, INSERTER_HAND_BASE, INSERTER_HAND_OPEN, INSERTER_HAND_CLOSED, ASSEMBLING_MACHINE - base_path = path.join(path.dirname(__file__), 'assets') - BELT = Tilemap(load_image(path.join(base_path, 'hr-transport-belt.png')), (128, 128), PIXELS_PER_UNIT) - UNDERGROUND = Tilemap(load_image(path.join(base_path, 'hr-underground-belt-structure.png')), (192, 192), PIXELS_PER_UNIT) + BELT = Tilemap(load_image(path.join(ASSETS_DIR, 'hr-transport-belt.png')), (128, 128), PIXELS_PER_UNIT) + UNDERGROUND = Tilemap(load_image(path.join(ASSETS_DIR, 'hr-underground-belt-structure.png')), (192, 192), PIXELS_PER_UNIT) SPLITTER_EAST = [ - Tilemap(load_image(path.join(base_path, 'hr-splitter-east.png')), (90, 84), PIXELS_PER_UNIT), - Tilemap(load_image(path.join(base_path, 'hr-splitter-east-top_patch.png')), (90, 104), PIXELS_PER_UNIT), + Tilemap(load_image(path.join(ASSETS_DIR, 'hr-splitter-east.png')), (90, 84), PIXELS_PER_UNIT), + Tilemap(load_image(path.join(ASSETS_DIR, 'hr-splitter-east-top_patch.png')), (90, 104), PIXELS_PER_UNIT), ] SPLITTER_WEST = [ - Tilemap(load_image(path.join(base_path, 'hr-splitter-west.png')), (90, 86), PIXELS_PER_UNIT), - Tilemap(load_image(path.join(base_path, 'hr-splitter-west-top_patch.png')), (90, 96), PIXELS_PER_UNIT), + Tilemap(load_image(path.join(ASSETS_DIR, 'hr-splitter-west.png')), (90, 86), PIXELS_PER_UNIT), + Tilemap(load_image(path.join(ASSETS_DIR, 'hr-splitter-west-top_patch.png')), (90, 96), PIXELS_PER_UNIT), ] - SPLITTER_SOUTH = Tilemap(load_image(path.join(base_path, 'hr-splitter-south.png')), (164, 64), PIXELS_PER_UNIT) - SPLITTER_NORTH = Tilemap(load_image(path.join(base_path, 'hr-splitter-north.png')), (160, 70), PIXELS_PER_UNIT) + SPLITTER_SOUTH = Tilemap(load_image(path.join(ASSETS_DIR, 'hr-splitter-south.png')), (164, 64), PIXELS_PER_UNIT) + SPLITTER_NORTH = Tilemap(load_image(path.join(ASSETS_DIR, 'hr-splitter-north.png')), (160, 70), PIXELS_PER_UNIT) - INSERTER_PLATFORM = Tilemap(load_image(path.join(base_path, 'hr-inserter-platform.png')), (105, 79), - PIXELS_PER_UNIT), Tilemap(load_image(path.join(base_path, 'hr-long-handed-inserter-platform.png')), (105, 79), PIXELS_PER_UNIT) + INSERTER_PLATFORM = Tilemap(load_image(path.join(ASSETS_DIR, 'hr-inserter-platform.png')), (105, 79), + PIXELS_PER_UNIT), Tilemap(load_image(path.join(ASSETS_DIR, 'hr-long-handed-inserter-platform.png')), (105, 79), PIXELS_PER_UNIT) - INSERTER_HAND_BASE = load_image(path.join(base_path, 'hr-inserter-hand-base.png')), load_image(path.join(base_path, 'hr-long-handed-inserter-hand-base.png')) - INSERTER_HAND_OPEN = load_image(path.join(base_path, 'hr-inserter-hand-open.png')), load_image(path.join(base_path, 'hr-long-handed-inserter-hand-open.png')) - INSERTER_HAND_CLOSED = load_image(path.join(base_path, 'hr-inserter-hand-closed.png')), load_image(path.join(base_path, 'hr-long-handed-inserter-hand-closed.png')) + INSERTER_HAND_BASE = load_image(path.join(ASSETS_DIR, 'hr-inserter-hand-base.png')), load_image(path.join(ASSETS_DIR, 'hr-long-handed-inserter-hand-base.png')) + INSERTER_HAND_OPEN = load_image(path.join(ASSETS_DIR, 'hr-inserter-hand-open.png')), load_image(path.join(ASSETS_DIR, 'hr-long-handed-inserter-hand-open.png')) + INSERTER_HAND_CLOSED = load_image(path.join(ASSETS_DIR, 'hr-inserter-hand-closed.png')), load_image(path.join(ASSETS_DIR, 'hr-long-handed-inserter-hand-closed.png')) - ASSEMBLING_MACHINE = Tilemap(load_image(path.join(base_path, 'hr-assembling-machine-1.png')), (214, 226), PIXELS_PER_UNIT) + ASSEMBLING_MACHINE = Tilemap(load_image(path.join(ASSETS_DIR, 'hr-assembling-machine-1.png')), (214, 226), PIXELS_PER_UNIT) PIXELS_PER_UNIT = 64 diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..00626e3 --- /dev/null +++ b/flake.lock @@ -0,0 +1,42 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1724011724, + "narHash": "sha256-YMMETBa2U31DJHuQkOjFTrdn89KEz1WfyG4HCUQzcXU=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "757247cb45d828a765660149e687a610a31ab622", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "systems": "systems" + } + }, + "systems": { + "locked": { + "lastModified": 1689347949, + "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", + "owner": "nix-systems", + "repo": "default-linux", + "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default-linux", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..f759f59 --- /dev/null +++ b/flake.nix @@ -0,0 +1,43 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs"; + systems.url = "github:nix-systems/default-linux"; + }; + + outputs = { self, nixpkgs, systems }: + let + inherit (nixpkgs) lib; + + eachSystem = lib.genAttrs (import systems); + pkgsFor = eachSystem (system: + import nixpkgs { + localSystem.system = system; + overlays = [ self.overlays.default ]; + }); + in { + apps = lib.mapAttrs (system: pkgs: { + default = self.apps.${system}.cli; + cli = { + type = "app"; + program = lib.getExe pkgs.factorio-sat-cli; + }; + }) pkgsFor; + + devShells = lib.mapAttrs (system: pkgs: { + default = pkgs.mkShell { + inputsFrom = with pkgs; [ factorio-sat ]; + packages = with pkgs; [ factorio-sat factorio-sat-cli ]; + }; + }) pkgsFor; + + overlays = { default = import ./nix/overlay.nix { inherit self; }; }; + + packages = lib.mapAttrs (system: pkgs: { + inherit (pkgs) factorio-sat factorio-sat-cli; + default = self.packages.${system}.factorio-sat; + }) pkgsFor; + + formatter = + eachSystem (system: nixpkgs.legacyPackages.${system}.nixfmt-classic); + }; +} diff --git a/nix/antlr/default.nix b/nix/antlr/default.nix new file mode 100644 index 0000000..8bd2ea6 --- /dev/null +++ b/nix/antlr/default.nix @@ -0,0 +1,27 @@ +{ lib, stdenv, callPackage, python3, libuuid }: +let + # This import is a builder extracted from Nixpkgs. + mkAntlr = callPackage ./mk-antlr.nix { }; + + versions."4.7.2" = { + + antlr = (mkAntlr { + version = "4.7.2"; + sourceSha256 = "sha256-kta+K/c6cUdOuW6jWMpX4tA22GXsaDBwtalzw4z+gN4="; + jarSha256 = "sha256-aFI4bXl17/KRcdrgAswiMlFRDTXyka4neUjzgaezgLQ="; + extraCppBuildInputs = lib.optional stdenv.isLinux libuuid; + extraCppCmakeFlags = [ "-DANTLR4_INSTALL=ON" ]; + }).antlr; + + pythonOverrides = pypkgs: pypkgs0: { + antlr4-python3-runtime = pypkgs.toPythonModule + ((pypkgs0.antlr4-python3-runtime.override { + antlr4 = versions."4.7.2".antlr; + }).overridePythonAttrs { + postPatch = ""; + doCheck = false; + }); + }; + + }; +in versions diff --git a/nix/antlr/mk-antlr.nix b/nix/antlr/mk-antlr.nix new file mode 100644 index 0000000..c5e56f5 --- /dev/null +++ b/nix/antlr/mk-antlr.nix @@ -0,0 +1,98 @@ +# Copied from Nixpkgs +# + +{ lib, stdenv, fetchurl, jre, fetchFromGitHub, cmake, ninja, pkg-config + +# darwin only +, CoreFoundation ? null + + # ANTLR 4.8 & 4.9 +, libuuid + +# ANTLR 4.9 +, utf8cpp }: +{ version, sourceSha256, jarSha256, extraCppBuildInputs ? [ ] +, extraCppCmakeFlags ? [ ] }: rec { + source = fetchFromGitHub { + owner = "antlr"; + repo = "antlr4"; + rev = version; + sha256 = sourceSha256; + }; + + antlr = stdenv.mkDerivation { + pname = "antlr"; + inherit version; + + src = fetchurl { + url = "https://www.antlr.org/download/antlr-${version}-complete.jar"; + sha256 = jarSha256; + }; + + dontUnpack = true; + + installPhase = '' + mkdir -p "$out"/{share/java,bin} + cp "$src" "$out/share/java/antlr-${version}-complete.jar" + + echo "#! ${stdenv.shell}" >> "$out/bin/antlr" + echo "'${jre}/bin/java' -cp '$out/share/java/antlr-${version}-complete.jar:$CLASSPATH' -Xmx500M org.antlr.v4.Tool \"\$@\"" >> "$out/bin/antlr" + + echo "#! ${stdenv.shell}" >> "$out/bin/antlr-parse" + echo "'${jre}/bin/java' -cp '$out/share/java/antlr-${version}-complete.jar:$CLASSPATH' -Xmx500M org.antlr.v4.gui.Interpreter \"\$@\"" >> "$out/bin/antlr-parse" + + echo "#! ${stdenv.shell}" >> "$out/bin/grun" + echo "'${jre}/bin/java' -cp '$out/share/java/antlr-${version}-complete.jar:$CLASSPATH' org.antlr.v4.gui.TestRig \"\$@\"" >> "$out/bin/grun" + + chmod a+x "$out/bin/antlr" "$out/bin/antlr-parse" "$out/bin/grun" + ln -s "$out/bin/antlr"{,4} + ln -s "$out/bin/antlr"{,4}-parse + ''; + + inherit jre; + + passthru = { + inherit runtime; + jarLocation = "${antlr}/share/java/antlr-${version}-complete.jar"; + }; + + meta = with lib; { + description = "Powerful parser generator"; + longDescription = '' + ANTLR (ANother Tool for Language Recognition) is a powerful parser + generator for reading, processing, executing, or translating structured + text or binary files. It's widely used to build languages, tools, and + frameworks. From a grammar, ANTLR generates a parser that can build and + walk parse trees. + ''; + homepage = "https://www.antlr.org/"; + sourceProvenance = with sourceTypes; [ binaryBytecode ]; + license = licenses.bsd3; + platforms = platforms.unix; + }; + }; + + runtime = { + cpp = stdenv.mkDerivation { + pname = "antlr-runtime-cpp"; + inherit version; + src = source; + sourceRoot = "${source.name}/runtime/Cpp"; + + outputs = [ "out" "dev" "doc" ]; + + nativeBuildInputs = [ cmake ninja pkg-config ]; + buildInputs = lib.optional stdenv.isDarwin CoreFoundation + ++ extraCppBuildInputs; + + cmakeFlags = extraCppCmakeFlags; + + meta = with lib; { + description = "C++ target for ANTLR 4"; + homepage = "https://www.antlr.org/"; + license = licenses.bsd3; + platforms = platforms.unix; + }; + }; + }; +} diff --git a/nix/cli.nix b/nix/cli.nix new file mode 100644 index 0000000..f56d296 --- /dev/null +++ b/nix/cli.nix @@ -0,0 +1,39 @@ +{ lib, writeShellApplication, factorio-sat, }: +let + exes = lib.pipe (builtins.readDir "${factorio-sat}/bin") [ + (lib.filterAttrs + (name: kind: kind == "regular" && !(lib.hasPrefix "." name))) + (lib.mapAttrsToList (name: _: name)) + ]; +in writeShellApplication { + name = "factorio-sat"; + text = '' + show_cmds() { + echo '${lib.concatStringsSep ", " exes}' + } + + if [[ $# -eq 0 ]]; then + echo 'Need subcommand.' + echo + show_cmds + exit 1 + fi + + exe="$1" + shift 1 + case "$exe" in + ${ + lib.concatMapStrings (exe: '' + ${exe}) + '${lib.getExe' factorio-sat exe}' "$@" + ;; + '') exes + } + *) + echo "No such subcommand: $exe" + echo + show_cmds + exit 1 + esac + ''; +} diff --git a/nix/factorio-sat.nix b/nix/factorio-sat.nix new file mode 100644 index 0000000..56e9456 --- /dev/null +++ b/nix/factorio-sat.nix @@ -0,0 +1,47 @@ +{ sourceRoot ? ../., versionSuffix ? null, lib, buildPythonApplication +, makeWrapper, setuptools, pygame, pillow, pyopengl, graphviz, ffmpeg-python +, numpy, python-sat, luaparser, }: +let + pyproject = lib.importTOML "${sourceRoot}/pyproject.toml"; + version = if versionSuffix == null then + pyproject.project.version + else + pyproject.project.version + versionSuffix; + + self = buildPythonApplication { + pname = pyproject.project.name; + inherit version; + + src = sourceRoot; + pyproject = true; + + nativeBuildInputs = [ setuptools makeWrapper ]; + + dependencies = [ + numpy + python-sat + luaparser + (pygame.overrideAttrs { + # evidently broken at the moment + installCheckPhase = ""; + }) + pillow + pyopengl + graphviz + ffmpeg-python + ]; + + preBuild = '' + sed -i 's/python-sat/python-sat==${python-sat.version}/g' pyproject.toml + ''; + + meta = { + inherit (pyproject.project.urls) homepage; + inherit (pyproject.project) description; + license = lib.licenses.gpl3Plus; + platforms = lib.platforms.all; + # mainProgram = there is none, use the factorio-sat-cli wrapper + maintainers = [ lib.maintainers.spikespaz ]; + }; + }; +in self diff --git a/nix/luaparser.nix b/nix/luaparser.nix new file mode 100644 index 0000000..1a7fd16 --- /dev/null +++ b/nix/luaparser.nix @@ -0,0 +1,14 @@ +{ lib, buildPythonPackage, fetchPypi, setuptools-scm, setuptools, six +, multimethod, antlr4, antlr4-python3-runtime }: +buildPythonPackage rec { + pname = "luaparser"; + version = "3.2.1"; + pyproject = true; + src = fetchPypi { + inherit pname version; + hash = "sha256-7oF6Wc5C/fqRmXoludubGkGRruVIwjeeNqNI4ukCUmk="; + }; + # I don't know how python dependencies work but this does + build-system = [ setuptools-scm ]; + dependencies = [ setuptools six antlr4 antlr4-python3-runtime multimethod ]; +} diff --git a/nix/overlay.nix b/nix/overlay.nix new file mode 100644 index 0000000..7080a00 --- /dev/null +++ b/nix/overlay.nix @@ -0,0 +1,32 @@ +{ self }: +final: _: +let pkgs = final; +in let + inherit (pkgs) lib; + + antlr4_7_2 = (pkgs.callPackage ./antlr { })."4.7.2"; + python = pkgs.python3.override { + self = python; + packageOverrides = lib.composeManyExtensions [ + antlr4_7_2.pythonOverrides + (pypkgs: _: { + luaparser = + pypkgs.toPythonModule (pypkgs.callPackage ./luaparser.nix { }); + }) + ]; + }; + + mkDate = longDate: + (lib.concatStringsSep "-" [ + (builtins.substring 0 4 longDate) + (builtins.substring 4 2 longDate) + (builtins.substring 6 2 longDate) + ]); + date = mkDate (self.lastModifiedDate or "19700101"); + rev = self.shortRev or "dirty"; +in { + factorio-sat = python.pkgs.callPackage ./factorio-sat.nix { + versionSuffix = "+date=${date}_${rev}"; + }; + factorio-sat-cli = pkgs.callPackage ./cli.nix { }; +}