BlueMap-Minecraft / BlueMapWiki

The wiki for BlueMap, located at https://bluemap.bluecolored.de/
https://bluemap.bluecolored.de/
MIT License
5 stars 57 forks source link

Community Guide for making BlueMap Addons #87

Open TechnicJelle opened 3 months ago

TechnicJelle commented 3 months ago

General document with some tips and tricks and common techniques and patterns I've collected over the years. And some explanations on how things work, and some things to look out for etc etc etc

BlueMap onEnable isn't being called

Make sure you're not shading the BlueMapAPI library! Mark it as provided (or for gradle, whatever that needs)

loading order

BlueMapAPI.onEnable({}) BlueMapAPI.onDisable({}) These can be called at any time during startup or during the running of the server. The /bluemap reload triggers a disable and then an enable again. So make sure your addom code handles that properly, by cleaning up after itself on disable, and re-adding everything it needs to on enable

When BlueMap (re)loads, it will have forgotten about everything you did the time before. So make sure it properly re-registers everything If you need to save stuff when BlueMap disables, so you can load it back in when BlueMap enables again, you can use the GSON object in the BlueMapAPI that is there at your convenience.

Bukkit/Spigot/Paper

When using one of these platforms for your addon, you may want to put the copying of files and registering of some things (TODO: list them) in the plugin's onLoad instead of the plugin's onEnable

Some things need to be registered very early in bluemap's startup process, and doing it in onLoad instead of onEnable makes sure of that.

Tile Filter setup

When using Tile Filters, you should set those in the BlueMapAPI.onEnable(onEnableListener); And that one should be in your plugin's onLoad() method. Not in the onEnable() method!

Full example:

public final class BlueMapDisabledTileFilter extends JavaPlugin {
    @Override
    public void onLoad() {
        BlueMapAPI.onEnable(onEnableListener);
    }

    private final Consumer<BlueMapAPI> onEnableListener = api -> {
        api.getMaps().forEach(map -> map.setTileFilter(vector2i -> false));
    };

    @Override
    public void onDisable() {
        BlueMapAPI.unregisterListener(onEnableListener);
    }
}

bluemap worlds and maps

In BlueMap, the concepts "worlds" and "maps" are very distinct. because you can have multiple maps per world. Or even a world with no maps

This is important to remember when developing BlueMap addons! Never assume every world has one map! Worlds can have 0 or a gazillion maps. Your code needs to handle that properly. I recommend making something for this in your test server. Make one world have a single map, one world with multiple maps, one world with zero maps. (for example: 1x Overworld, 2x Nether, 0x End)

when bluemap is first started, it creates one map for every world you have loaded you can then delete maps of worlds you don't want to have a bluemap of or you can create more maps of the same worlds (guide) For example, some people like having a separate map for their nether roof

My tip on how to handle creating, storing and accessing markersets throughout your addon code

You don't need to create a marker set for every map! You can share markersets between maps.

(Pseudocode typed on a phone: )

Class MyAddon {

  private HashMap<BlueMapWorld, MarkerSet> mySets;

  @override
  public onPlatformLoad() {
      BlueMapAPI.onEnable(api -> {
         For bluemapworld in api.getWorlds() {
           markerset = new()
           bluemapworld.foreach{map -> map.add(markerset)}; //share the same marker set for all a world's maps
           mySets.put(bluemapworld, markerset);
       });
       BlueMapAPI.onDisable(api -> {
          mySets.clear();
       });
     }
  }

  void someEvent(Event e) {
     BlueMapAPI api = BlueMapAPI.getInstance();
     PlatformWorld pw = e.getWorld();
     Optional<BlueMapWorld> obmw = api.getWorld(pw);
     BlueMapWorld bmw = obmw.get();
     MarkerSet ms = mySets.get(bmw);
     Marker m = new PoiMarker();
     ms.put(e.hashCode().toString(), m);
   }
}

I'll leave handling cases where a world doesn't have any maps as an exercise to the reader ;p

Storing assets

You can store assets in two ways:

  1. Globally, in the webapp, so the assets are accessible from anywhere.
    • This does require people who are using custom webserver setups to manually copy those assets over to their own webroot
  2. Per map, in the map's asset storage.
    • This stores the asset in the map's subfolder, or in the map's SQL database. This makes it so it'll automatically work for people with custom webserver setups. But it is less easy for them to customise the icons; to replace them with something else that they choose themselves.

Here's a veery simple addon (200 lines of code) that allows users to choose whether to save the icons per map or globally https://github.com/pop4959/BlueMap-Essentials/blob/master/src/main/java/org/popcraft/bluemapessentials/BlueMapEssentials.java The specific things to look at are loadImages(), the two copyResource() functions, and getMarkerURL() You can use this as an example for both ways

Note that improved versions of the copyResource() functions are available in my BMUtils library! :) Read more about that here: https://github.com/TechnicJelle/BMUtils?tab=readme-ov-file#copying-assets-docs

icon images sizes

BlueMap does not scale POI Icons! It just takes your image and puts it directly into an HTML tag and that's it. If the icons are too big or too small, you need to use an image editor to scale the image file itself. Or find a different icon that is a nice size. I generally recommend not going bigger than 48x48.

limiting situations with bridge addons

In some situations where you're making a bridge addons, you can't query all Things at any time you want, which means you can't add markers for all your Things in bluemaps onEnable And bluemap map or may not be enabled when the event fires. So we can use this pattern for such situations: We store the important data in an arraylist, so we can loop over the fired events later on, when we are ready for them.

@override
void onEvent(event) {
  If bmapi.isloadrd() {
     bmapi.addmarker()
  } else {
     todo.add(event.portal);
  }
}

Bmapi.onEnable(API -> {
   for(portal : todo) {
      api.addmarker(portal2marker(portal));
   }
   todo.clear();
});
TechnicJelle commented 2 weeks ago

Native Addons

Native addons are a way to make a BlueMap addon that does not rely on any server, and can be installed into BlueMap itself, so they basically automatically support every platform BlueMap itself supports. You can use this template to get started! https://github.com/TechnicJelle/BlueMapNativeAddonTemplate