Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to specify different mc-versions & resourcepacks per map #502

Open
jotalanusse opened this issue Jan 22, 2024 · 3 comments
Open

Comments

@jotalanusse
Copy link

jotalanusse commented Jan 22, 2024

Is your feature request related to a problem? Please describe.
I’m trying to create an archive of my multiple minecraft worlds that I've accumulated over the years. The problem is that some maps are very old and some are very recent, so I'm having trouble with some textures in the old maps.

Describe the solution you'd like
I was wondering if it could be possible to add a new supported optional setting in the .conf files that allows each render to have it’s own minecraft version defined.

I’m aware that there is a -v or --mc-version option in the BlueMap CLI, but that means that I have to create an additional script to individually execute 40+ files.

EDIT:

If there is such an option that allows to specify a minecraft version per render as described above, please let me know, I have not found one up until now.

@TBlueF
Copy link
Member

TBlueF commented Jan 22, 2024

So, let me try to explain why this is a problem to implement:

All the --mc-version option does is it changes the base-resources that are (downloaded from mojang and then) used to render the map. So what you are asking for is essentially a way to define a different resourcepack per map.
Also: while it looks like BlueMap is rendering the maps sequentially, it actually needs to keep all resources needed to render a certain map loaded at the same time to support things like updating maps when they change (-u)..
For 40+ maps this would in the worst case mean 40 different resourcepacks with all its textures and models loaded at once in memory. Which is not gonna work :D

I have thought about this feature myself a couple of times, but this is why it has not been added yet. I still have some ideas how it could be implemented, so i haven't discarded it yet. But it won't be a quick thing to implement.

I'll add this issue to the TODO list, but .. for the next time please stick to the Contributing Guidelines and use discord for feature-requests. :)

@github-project-automation github-project-automation bot moved this to TODO (Unordered) in TODO / Idea board Jan 22, 2024
@TBlueF TBlueF changed the title Feature request: Allow render configurations to have a minecraft version defined Ability to specify different mc-versions & resourcepacks per map Jan 22, 2024
@TBlueF TBlueF moved this from TODO (Unordered) to TODO in TODO / Idea board Jan 22, 2024
@TBlueF TBlueF moved this from TODO to TODO (Unordered) in TODO / Idea board Mar 13, 2024
@dali99
Copy link

dali99 commented Jun 18, 2024

You can do this manually (at least in 3.21) with the cli by creating multiple different configs and then merging them and generating the webui afterwards:

bluemap -c AboveAndBeyondOverworld-config -r
bluemap -c CreativeOverworld-config -r
bluemap -c VanillaEnd-config -r
bluemap -c VanillaNether-config -r
bluemap -c VanillaOverworld-config -r
bluemap -c bluemap-merged-config -gs

I generate these using an external configuration-management language and have the script above running on a systemd-timer

The only issue seems to be that bluemap does update the webapp even without passing -gs, so while a map is rendering only that one map is available in the ui until the last step updates the webapp to make them all available again.

I have not tested this with the new 5.2 version though

Actual NixOS implementation
Modified nixos module
{ config, lib, pkgs, ... }:
let
  cfg = config.services.bluemap;
  format = pkgs.formats.hocon { };

  coreConfig = format.generate "core.conf" cfg.coreSettings;
  webappConfig = format.generate "webapp.conf" cfg.webappSettings;
  webserverConfig = format.generate "webserver.conf" cfg.webserverSettings;

  storageFolder = pkgs.linkFarm "storage"
    (lib.attrsets.mapAttrs' (name: value:
      lib.nameValuePair "${name}.conf"
        (format.generate "${name}.conf" value))
      cfg.storage);

  mapsFolder = pkgs.linkFarm "maps"
    (lib.attrsets.mapAttrs' (name: value:
      lib.nameValuePair "${name}.conf"
        (format.generate "${name}.conf" value.settings))
      cfg.maps);

  webappConfigFolder = pkgs.linkFarm "bluemap-config" {
    "maps" = mapsFolder;
    "storages" = storageFolder;
    "core.conf" = coreConfig;
    "webapp.conf" = webappConfig;
    "webserver.conf" = webserverConfig;
    "resourcepacks" = cfg.resourcepacks;
  };

  renderConfigFolder = name: value: pkgs.linkFarm "bluemap-${name}-config" {
    "maps" = pkgs.linkFarm "maps" {
      "${name}.conf" = (format.generate "${name}.conf" value.settings);
    };
    "storages" = storageFolder;
    "core.conf" = coreConfig;
    "webapp.conf" = webappConfig;
    "webserver.conf" = webserverConfig;
    "resourcepacks" = value.resourcepacks;
  };

  inherit (lib) mkOption;
in {
  options.services.bluemap = {
    enable = lib.mkEnableOption "bluemap";

    eula = mkOption {
      type = lib.types.bool;
      description = ''
        By changing this option to true you confirm that you own a copy of minecraft Java Edition,
        and that you agree to minecrafts EULA.
      '';
      default = false;
    };

    defaultWorld = mkOption {
      type = lib.types.path;
      description = ''
        The world used by the default map ruleset.
        If you configure your own maps you do not need to set this.
      '';
      example = lib.literalExpression "\${config.services.minecraft.dataDir}/world";
    };

    enableRender = mkOption {
      type = lib.types.bool;
      description = "Enable rendering";
      default = true;
    };

    webRoot = mkOption {
      type = lib.types.path;
      default = "/var/lib/bluemap/web";
      description = "The directory for saving and serving the webapp and the maps";
    };

    enableNginx = mkOption {
      type = lib.types.bool;
      default = true;
      description = "Enable configuring a virtualHost for serving the bluemap webapp";
    };

    host = mkOption {
      type = lib.types.str;
      default = "bluemap.${config.networking.domain}";
      defaultText = lib.literalExpression "bluemap.\${config.networking.domain}";
      description = "Domain to configure nginx for";
    };

    onCalendar = mkOption {
      type = lib.types.str;
      description = ''
        How often to trigger rendering the map,
        in the format of a systemd timer onCalendar configuration.
        See {manpage}`systemd.timer(5)`.
      '';
      default = "*-*-* 03:10:00";
    };

    coreSettings = mkOption {
      type = lib.types.submodule {
        freeformType = format.type;
        options = {
          data = mkOption {
            type = lib.types.path;
            description = "Folder for where bluemap stores its data";
            default = "/var/lib/bluemap";
          };
          metrics = lib.mkEnableOption "Sending usage metrics containing the version of bluemap in use";
        };
      };
      description = "Settings for the core.conf file, [see upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/core.conf).";
    };

    webappSettings = mkOption {
      type = lib.types.submodule {
        freeformType = format.type;
      };
      default = {
        enabled = true;
        webroot = cfg.webRoot;
      };
      defaultText = lib.literalExpression ''
        {
          enabled = true;
          webroot = config.services.bluemap.webRoot;
        }
      '';
      description = "Settings for the webapp.conf file, see [upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/webapp.conf).";
    };

    webserverSettings = mkOption {
      type = lib.types.submodule {
        freeformType = format.type;
        options = {
          enabled = mkOption {
            type = lib.types.bool;
            description = ''
              Enable bluemap's built-in webserver.
              Disabled by default in nixos for use of nginx directly.
            '';
            default = false;
          };
        };
      };
      default = { };
      description = ''
        Settings for the webserver.conf file, usually not required.
        [See upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/webserver.conf).
      '';
    };

    maps = mkOption {
      type = lib.types.attrsOf (lib.types.submodule {
        options = {
          resourcepacks = mkOption {
            type = lib.types.path;
            default = cfg.resourcepacks;
            defaultText = lib.literalExpression "config.services.bluemap.resourcepacks";
            description = "A set of resourcepacks/mods to extract models from loaded in alphabetical order";
          };
          settings = mkOption {
            type = (lib.types.submodule {
              freeformType = format.type;
              options = {
                world = mkOption {
                  type = lib.types.path;
                  description = "Path to world folder containing the dimension to render";
                };
              };
            });
            description = ''
              Settings for files in `maps/`.
              See the default for an example with good options for the different world types.
              For valid values [consult upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/maps/map.conf).
            '';
          };
        };
      });
      default = {
        "overworld".settings = {
          world = "${cfg.defaultWorld}";
          ambient-light = 0.1;
          cave-detection-ocean-floor = -5;
        };

        "nether".settings = {
          world = "${cfg.defaultWorld}/DIM-1";
          sorting = 100;
          sky-color = "#290000";
          void-color = "#150000";
          ambient-light = 0.6;
          world-sky-light = 0;
          remove-caves-below-y = -10000;
          cave-detection-ocean-floor = -5;
          cave-detection-uses-block-light = true;
          max-y = 90;
        };

        "end".settings = {
          world = "${cfg.defaultWorld}/DIM1";
          sorting = 200;
          sky-color = "#080010";
          void-color = "#080010";
          ambient-light = 0.6;
          world-sky-light = 0;
          remove-caves-below-y = -10000;
          cave-detection-ocean-floor = -5;
        };
      };
      defaultText = lib.literalExpression ''
        {
          "overworld".settings = {
            world = "''${cfg.defaultWorld}";
            ambient-light = 0.1;
            cave-detection-ocean-floor = -5;
          };

          "nether".settings = {
            world = "''${cfg.defaultWorld}/DIM-1";
            sorting = 100;
            sky-color = "#290000";
            void-color = "#150000";
            ambient-light = 0.6;
            world-sky-light = 0;
            remove-caves-below-y = -10000;
            cave-detection-ocean-floor = -5;
            cave-detection-uses-block-light = true;
            max-y = 90;
          };

          "end".settings = {
            world = "''${cfg.defaultWorld}/DIM1";
            sorting = 200;
            sky-color = "#080010";
            void-color = "#080010";
            ambient-light = 0.6;
            world-sky-light = 0;
            remove-caves-below-y = -10000;
            cave-detection-ocean-floor = -5;
          };
        };
      '';
      description = ''
        map-specific configuration.
        These correspond to views in the webapp and are usually
        different dimension of a world or different render settings of the same dimension.
        If you set anything in this option you must configure all dimensions yourself!
      '';
    };

    storage = mkOption {
      type = lib.types.attrsOf (lib.types.submodule {
        freeformType = format.type;
        options = {
          storage-type = mkOption {
            type = lib.types.enum [ "FILE" "SQL" ];
            description = "Type of storage config";
            default = "FILE";
          };
        };
      });
      description = ''
        Where the rendered map will be stored.
        Unless you are doing something advanced you should probably leave this alone and configure webRoot instead.
        [See upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/tree/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages)
      '';
      default = {
        "file" = {
          root = "${cfg.webRoot}/maps";
        };
      };
      defaultText = lib.literalExpression ''
        {
          "file" = {
            root = "''${config.services.bluemap.webRoot}/maps";
          };
        }
      '';
    };

    resourcepacks = mkOption {
      type = lib.types.path;
      default = pkgs.linkFarm "resourcepacks" { };
      description = ''
        A set of resourcepacks/mods to extract models from loaded in alphabetical order.
        Can be overriden on a per-map basis with `services.bluemap.maps.<name>.resourcepacks`.
      '';
    };
  };


  config = lib.mkIf cfg.enable {
    assertions =
      [ { assertion = config.services.bluemap.eula;
          message = ''
            You have enabled bluemap but have not accepted minecraft's EULA.
            You can achieve this through setting `services.bluemap.eula = true`
          '';
        }
      ];

    services.bluemap.coreSettings.accept-download = cfg.eula;

    systemd.services."render-bluemap-maps" = lib.mkIf cfg.enableRender {
      serviceConfig = {
        Type = "oneshot";
        Group = "nginx";
        UMask = "026";
      };
      script = lib.strings.concatStringsSep "\n" ((lib.attrsets.mapAttrsToList
        (name: value: "${lib.getExe pkgs.bluemap} -c ${renderConfigFolder name value} -r")
        cfg.maps) ++ [ "${lib.getExe pkgs.bluemap} -c ${webappConfigFolder} -gs" ]);
    };

    systemd.timers."render-bluemap-maps" = lib.mkIf cfg.enableRender {
      wantedBy = [ "timers.target" ];
      timerConfig = {
        OnCalendar = cfg.onCalendar;
        Persistent = true;
        Unit = "render-bluemap-maps.service";
      };
    };

    services.nginx.virtualHosts = lib.mkIf cfg.enableNginx {
      "${cfg.host}" = {
        root = config.services.bluemap.webRoot;
        locations = {
          "~* ^/maps/[^/]*/tiles/[^/]*.json$".extraConfig = ''
            error_page 404 =200 /assets/emptyTile.json;
            gzip_static always;
          '';
          "~* ^/maps/[^/]*/tiles/[^/]*.png$".tryFiles = "$uri =204";
        };
      };
    };
  };

  meta = {
    maintainers = with lib.maintainers; [ dandellion h7x4 ];
  };
}

Usage

{ config, lib, pkgs, ... }:
let
  vanilla = "/path/to/vanilla/world";
  creative = "/path/to/creative/world";
  above-and-beyond = "/path/to/modded/world";
in
{
  imports = [ ./bluemap-module.nix ];
  disabledModules = [ "services/web-servers/bluemap.nix" ];

  services.bluemap = {
    enable = true;
    eula = true;

    host = "mc.example.com";
    webRoot = "/var/lib/bluemap/web";

    maps = {
      "VanillaOverworld" = {
        settings = {
          world = vanilla;
          sorting = 0;
          ambient-light = 0.1;
          cave-detection-ocean-floor = -5;
        };
      };

      "VanillaNether" = {
        settings = {
          world = "${vanilla}/DIM-1";
          sorting = 100;
          sky-color = "#290000";
          void-color = "#150000";
          ambient-light = 0.6;
          world-sky-light = 0;
          remove-caves-below-y = -10000;
          cave-detection-ocean-floor = -5;
          cave-detection-uses-block-light = true;
          max-y = 90;
        };
      };

      "VanillaEnd" = {
        settings = {
          world = "${vanilla}/DIM1";
          sorting = 200;
          sky-color = "#080010";
          void-color = "#080010";
          ambient-light = 0.6;
          world-sky-light = 0;
          remove-caves-below-y = -10000;
          cave-detection-ocean-floor = -5;
        };
      };

      "CreativeOverworld" = {
        settings = {
          world = creative;
          sorting = 1000;
          ambient-light = 0.1;
          cave-detection-ocean-floor = -5;
        };
      };

      "AboveAndBeyondOverworld" = {
        resourcepacks = "/path/to/modded/mods-folder";
        settings = {
          world = above-and-beyond;
          sorting = 2000;
          ambient-light = 0.1;
          cave-detection-ocean-floor = -5;
        };
      };
    };
  };


  services.nginx.virtualHosts."mc.example.com" = {
    enableACME = true;
    forceSSL = true;
  };
}

@TBlueF
Copy link
Member

TBlueF commented Jun 18, 2024

The only issue seems to be that bluemap does update the webapp even without passing -gs

@dali99 you can prevent that by setting update-settings-file: false in the webapp.conf of all the runs except the combined one :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Planned (unordered)
Development

No branches or pull requests

3 participants