Add Minecraft 26.x support and version Java artifacts
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
package dev.minegeo.fabric;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import com.mojang.blaze3d.pipeline.*;
|
||||
import net.minecraft.client.CameraType;
|
||||
import net.minecraft.client.Screenshot;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import java.nio.file.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/** Changes the render camera only: no movement or rotation packets are sent to a server. */
|
||||
public final class Capture {
|
||||
public static final int SIZE=1024;
|
||||
public static final String[] FACES={"px","nx","py","ny","pz","nz"};
|
||||
private static final float[] YAW={-90,90,0,0,0,180},PITCH={0,0,-90,90,0,0};
|
||||
private static boolean active;private static RenderTarget buffer;private static Path output;
|
||||
private static Vec3 position;private static Object world;private static int face,settle;
|
||||
private static boolean hud,bobbing;private static CameraType perspective;
|
||||
private static final AtomicBoolean pending=new AtomicBoolean();
|
||||
public static boolean active(){return active;}public static RenderTarget framebuffer(){return buffer;}
|
||||
public static float yaw(){return YAW[Math.min(face,5)];}public static float pitch(){return PITCH[Math.min(face,5)];}
|
||||
public static void start(Path directory)throws Exception {
|
||||
var c=Minecraft.getInstance();if(active)throw new IllegalStateException("Capture already running");
|
||||
if(c.player==null||c.level==null||!c.level.dimension().identifier().toString().equals("minecraft:overworld"))throw new IllegalStateException("Join an Overworld first");
|
||||
if(c.player.isUnderWater())throw new IllegalStateException("Capture from a dry surface location");
|
||||
output=directory;Files.createDirectories(output);position=new Vec3(c.player.getX(),c.player.getY(),c.player.getZ());world=c.level;
|
||||
hud=ClientCompat.hudHidden(c);bobbing=c.options.bobView().get();perspective=c.options.getCameraType();
|
||||
try {buffer=ClientCompat.target();face=0;settle=0;pending.set(false);active=true;ClientCompat.hudHidden(c,true);c.options.bobView().set(false);c.options.setCameraType(CameraType.FIRST_PERSON);c.gameRenderer.resize(SIZE,SIZE);}
|
||||
catch(Throwable e){cancel("Capture setup failed: "+e.getMessage());throw e;}
|
||||
}
|
||||
public static void beforeFrame(){if(!active)return;var c=Minecraft.getInstance();if(c.player==null||c.level!=world||ClientCompat.screen(c)!=null||new Vec3(c.player.getX(),c.player.getY(),c.player.getZ()).distanceToSqr(position)>.0004)cancel("Capture cancelled. Stand still until all six views are saved.");}
|
||||
public static void afterFrame(){if(!active||pending.get()||++settle<4)return;pending.set(true);final int index=face;final Path directory=output;
|
||||
try{Screenshot.takeScreenshot(buffer,image->{try(image){if(active&&output.equals(directory)){image.writeToFile(directory.resolve(FACES[index]+".png"));var c=Minecraft.getInstance();c.execute(()->{if(!active||!output.equals(directory))return;face++;settle=0;pending.set(false);if(face==6){restore();MineGeoClient.INSTANCE.savedCapture(directory);}});}}catch(Exception e){Minecraft.getInstance().execute(()->cancel("Capture failed: "+e.getMessage()));}});}
|
||||
catch(Exception e){cancel("Capture failed: "+e.getMessage());}
|
||||
}
|
||||
public static void cancel(String reason){if(active||buffer!=null){restore();MineGeoClient.INSTANCE.message(reason);}}
|
||||
private static void restore(){var c=Minecraft.getInstance();active=false;pending.set(false);ClientCompat.hudHidden(c,hud);c.options.bobView().set(bobbing);if(perspective!=null)c.options.setCameraType(perspective);c.gameRenderer.resize(c.getWindow().getWidth(),c.getWindow().getHeight());if(buffer!=null){buffer.destroyBuffers();buffer=null;}}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package dev.minegeo.fabric;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.*;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public final class CaptureScreen extends Screen {
|
||||
private EditBox name,target;
|
||||
public CaptureScreen(){super(Component.literal("MineGeo Capture"));}
|
||||
@Override protected void init(){var mod=MineGeoClient.INSTANCE;int x=width/2-130;
|
||||
name=new EditBox(font,x,92,260,20,Component.literal("Map name"));name.setMaxLength(80);name.setValue(mod.dataset==null?"My Minecraft map":mod.dataset.name);addRenderableWidget(name);
|
||||
target=new EditBox(font,x,126,260,20,Component.literal("Add to map ID (optional)"));target.setMaxLength(36);target.setValue(mod.targetMap);addRenderableWidget(target);
|
||||
addRenderableWidget(Button.builder(Component.literal("Capture panorama"),b->{mod.targetMap=target.getValue().trim();mod.capture();}).bounds(x,154,126,20).build());
|
||||
addRenderableWidget(Button.builder(Component.literal("Preview files"),b->{if(mod.dataset!=null)net.minecraft.util.Util.getPlatform().openPath(mod.dataset.directory);}).bounds(x+134,154,126,20).build());
|
||||
addRenderableWidget(Button.builder(Component.literal("Link account"),b->{mod.targetMap=target.getValue().trim();mod.pair();}).bounds(x,182,126,20).build());
|
||||
addRenderableWidget(Button.builder(Component.literal("Upload / resume"),b->{mod.targetMap=target.getValue().trim();mod.upload(name.getValue());}).bounds(x+134,182,126,20).build());
|
||||
addRenderableWidget(Button.builder(Component.literal("New map at this location"),b->{mod.newMap();resize(width,height);}).bounds(x,210,260,20).build());
|
||||
addRenderableWidget(Button.builder(Component.literal("Close"),b->onClose()).bounds(x,238,260,20).build());
|
||||
}
|
||||
@Override public void extractRenderState(GuiGraphicsExtractor context,int mouseX,int mouseY,float delta){super.extractRenderState(context,mouseX,mouseY,delta);int x=width/2-130;context.centeredText(font,title,width/2,20,0xff8cdf6b);context.text(font,"MineGeo service",x,38,0xffffffff);context.text(font,MineGeoClient.website,x,54,0xff8cdf6b);context.text(font,"Map name",x,80,0xffffffff);context.text(font,"Add to map ID (blank = new map)",x,114,0xffffffff);var mod=MineGeoClient.INSTANCE;String counts=mod.dataset==null?"Join an Overworld to begin":mod.dataset.points.size()+" panoramas · "+mod.dataset.coverage.size()+" explored chunks";context.centeredText(font,Component.literal(counts),width/2,270,0xffffffff);int y=292;for(var line:font.split(Component.literal(mod.status),260)){context.text(font,line,x,y,0xffd5d5d5);y+=10;}}
|
||||
@Override public boolean isPauseScreen(){return false;}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package dev.minegeo.fabric;
|
||||
import dev.minegeo.plugin.UploadClient;
|
||||
import com.google.gson.*;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.keymapping.v1.KeyMappingHelper;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import java.nio.file.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public final class MineGeoClient implements ClientModInitializer {
|
||||
public static MineGeoClient INSTANCE;
|
||||
public final ExecutorService background=Executors.newSingleThreadExecutor(r->{var t=new Thread(r,"MineGeo upload");t.setDaemon(true);return t;});
|
||||
public final Gson gson=new GsonBuilder().setPrettyPrinting().create();
|
||||
public static final String website=UploadClient.WEBSITE;
|
||||
public volatile String token="",status="Explore normally to collect surface coverage.";
|
||||
public volatile String targetMap="";
|
||||
public volatile boolean uploading=false;public SurfaceDataset dataset;private SurfaceDataset.Point pending;
|
||||
private Path config;
|
||||
@Override public void onInitializeClient(){INSTANCE=this;config=Minecraft.getInstance().gameDirectory.toPath().resolve("config/minegeo.json");try{if(Files.exists(config)){var j=JsonParser.parseString(Files.readString(config)).getAsJsonObject();token=j.get("token").getAsString();if(j.has("targetMap"))targetMap=j.get("targetMap").getAsString();}}catch(Exception e){message("Could not read MineGeo configuration");}
|
||||
var key=KeyMappingHelper.registerKeyMapping(new KeyMapping("key.minegeo.open",InputConstants.Type.KEYSYM,GLFW.GLFW_KEY_G,KeyMapping.Category.register(Identifier.fromNamespaceAndPath("minegeo","capture"))));
|
||||
ClientTickEvents.END_CLIENT_TICK.register(c->{while(key.consumeClick()){if(Capture.active())Capture.cancel("Capture cancelled");else ClientCompat.screen(c,new CaptureScreen());}if(c.level!=null&&c.player!=null&&c.level.dimension().identifier().toString().equals("minecraft:overworld")){try{if(dataset==null||!dataset.server.equals(server()))dataset=SurfaceDataset.open(server());if(!Capture.active()&&!uploading)dataset.scan(c);}catch(Exception e){message("Map collection: "+e.getMessage());}}});
|
||||
}
|
||||
public String server(){var c=Minecraft.getInstance();return c.getCurrentServer()!=null?c.getCurrentServer().ip:"Singleplayer";}
|
||||
public void saveConfig()throws Exception{Files.createDirectories(config.getParent());var j=new JsonObject();j.addProperty("token",token);j.addProperty("targetMap",targetMap);Files.writeString(config,gson.toJson(j));}
|
||||
public void message(String text){status=text;var c=Minecraft.getInstance();if(c.player!=null)ClientCompat.message(c,Component.literal("[MineGeo] "+text));}
|
||||
public void capture(){try{if(dataset==null)throw new IllegalStateException("Join an Overworld and explore first");var c=Minecraft.getInstance();if(!dataset.contains(c.player.getX(),c.player.getZ()))throw new IllegalStateException("Outside this map's 512-block boundary. Start a new map here.");int limit=24;if(dataset.points.size()>=limit)throw new IllegalStateException("This capture already contains "+limit+" locations");pending=new SurfaceDataset.Point("p"+java.util.UUID.randomUUID().toString().replace("-","").substring(0,12),c.player.getX(),c.player.getEyeY(),c.player.getZ());ClientCompat.screen(c,null);Capture.start(dataset.directory.resolve(pending.id()));message("Capturing six views. Stand still; press G to cancel.");}catch(Exception e){message(e.getMessage());}}
|
||||
public void savedCapture(Path path){try{if(pending==null||!path.getFileName().toString().equals(pending.id()))return;dataset.points.add(pending);dataset.save();message("Panorama saved locally ("+dataset.points.size()+" total). Press G for preview or upload.");}catch(Exception e){message("Saving capture failed: "+e.getMessage());}finally{pending=null;}}
|
||||
public void newMap(){try{if(uploading||Capture.active())throw new IllegalStateException("Finish the current operation first");dataset=SurfaceDataset.create(server());message("New 512 × 512 map started. Older maps remain in .minecraft/minegeo.");}catch(Exception e){message(e.getMessage());}}
|
||||
public void pair(){background.execute(()->{try{saveConfig();var uploader=new UploadClient("");var pair=uploader.beginPairing();String code=pair.get("deviceCode").getAsString();String link=pair.get("verificationUri").getAsString();Minecraft.getInstance().execute(()->{message("Approve code "+pair.get("userCode").getAsString()+" at "+link);net.minecraft.util.Util.getPlatform().openUri(link);});for(int i=0;i<120;i++){Thread.sleep(5000);var response=uploader.pollPairing(code);if(response.get("status").getAsString().equals("approved")){token=response.get("token").getAsString();saveConfig();Minecraft.getInstance().execute(()->message("Account linked. You can upload maps now."));return;}}throw new IllegalStateException("Pairing expired. Try linking again.");}catch(Exception e){Minecraft.getInstance().execute(()->message("Pairing: "+e.getMessage()));}});}
|
||||
public void upload(String name){if(uploading)return;final SurfaceDataset selected=dataset;final String target=targetMap.trim();if(!target.isBlank()&&(Minecraft.getInstance().getCurrentServer()==null||selected==null||!java.util.Objects.equals(dev.minegeo.plugin.ServerAddress.normalize(server()),dev.minegeo.plugin.ServerAddress.normalize(selected.server)))){message("Join the server this capture belongs to before contributing.");return;}int minimum=target.isBlank()?5:1;if(selected==null||selected.points.size()<minimum){message("Capture at least "+minimum+" locations before uploading.");return;}uploading=true;background.execute(()->{try{selected.name=name;selected.save();saveConfig();status="Uploading assets. Interrupted transfers resume when you retry.";new UploadClient(token).upload(selected.directory,target.isBlank()?null:target, progress -> status=progress, () -> false);Minecraft.getInstance().execute(()->message("Upload received. Preview your selection and submit it in your dashboard."));}catch(Exception e){Minecraft.getInstance().execute(()->message("Upload paused: "+e.getMessage()));}finally{uploading=false;}});}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package dev.minegeo.fabric;
|
||||
import com.google.gson.*;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import java.nio.file.*;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
/** Reads only chunks already delivered to the client; never requests or generates chunks. */
|
||||
public final class SurfaceDataset {
|
||||
public record Point(String id,double x,double y,double z){}
|
||||
public final Path directory;public final String server;public String name="My Minecraft map";
|
||||
public final List<Point> points=new ArrayList<>();public final Set<String> coverage=new HashSet<>();
|
||||
private final BufferedImage image=new BufferedImage(512,512,BufferedImage.TYPE_INT_ARGB);
|
||||
private int minX,minZ,cursor;private String scanning;private int chunkX,chunkZ;
|
||||
private SurfaceDataset(Path directory,String server,int x,int z){this.directory=directory;this.server=server;minX=x;minZ=z;for(int iz=0;iz<512;iz++)for(int ix=0;ix<512;ix++)image.setRGB(ix,iz,((ix/16+iz/16)%2==0)?0xff4c493d:0xff3b3932);}
|
||||
public static SurfaceDataset create(String server)throws Exception{var c=Minecraft.getInstance();var root=c.gameDirectory.toPath().resolve("minegeo");Files.createDirectories(root);var dataset=new SurfaceDataset(root.resolve(UUID.randomUUID().toString()),server,(Math.floorDiv(c.player.getBlockX(),16)-16)*16,(Math.floorDiv(c.player.getBlockZ(),16)-16)*16);Files.createDirectories(dataset.directory);dataset.saveLocal();return dataset;}
|
||||
public static SurfaceDataset open(String server)throws Exception{Path root=Minecraft.getInstance().gameDirectory.toPath().resolve("minegeo");if(Files.exists(root)){try(var dirs=Files.list(root)){for(Path path:dirs.sorted(Comparator.reverseOrder()).toList()){Path state=path.resolve("local.json");if(!Files.exists(state))continue;var j=JsonParser.parseString(Files.readString(state)).getAsJsonObject();if(!j.get("server").getAsString().equals(server))continue;var d=new SurfaceDataset(path,server,j.get("minX").getAsInt(),j.get("minZ").getAsInt());d.name=j.get("name").getAsString();for(var p:j.getAsJsonArray("viewpoints")){var v=p.getAsJsonObject();d.points.add(new Point(v.get("id").getAsString(),v.get("x").getAsDouble(),v.get("y").getAsDouble(),v.get("z").getAsDouble()));}for(var k:j.getAsJsonArray("coverage"))d.coverage.add(k.getAsString());if(Files.exists(path.resolve("map.png"))){var saved=ImageIO.read(path.resolve("map.png").toFile());d.image.getGraphics().drawImage(saved,0,0,null);}return d;}}}return create(server);}
|
||||
public boolean contains(double x,double z){return x>=minX&&x<minX+512&&z>=minZ&&z<minZ+512;}
|
||||
public void scan(Minecraft c){
|
||||
if(scanning==null){int px=Math.floorDiv(c.player.getBlockX(),16),pz=Math.floorDiv(c.player.getBlockZ(),16);outer:for(int radius=0;radius<=12;radius++)for(int dz=-radius;dz<=radius;dz++)for(int dx=-radius;dx<=radius;dx++){if(Math.max(Math.abs(dx),Math.abs(dz))!=radius)continue;int cx=px+dx,cz=pz+dz;String key=cx+","+cz;if(coverage.contains(key)||!contains(cx*16,cz*16))continue;if(c.level.getChunk(cx,cz,ChunkStatus.FULL,false)==null)continue;scanning=key;chunkX=cx;chunkZ=cz;cursor=0;break outer;}}
|
||||
if(scanning==null)return;if(c.level.getChunk(chunkX,chunkZ,ChunkStatus.FULL,false)==null){scanning=null;return;}
|
||||
var pos=new BlockPos.MutableBlockPos();for(int n=0;n<64&&cursor<256;n++,cursor++){int x=chunkX*16+(cursor%16),z=chunkZ*16+(cursor/16),y=c.level.getHeight(Heightmap.Types.WORLD_SURFACE,x,z)-1;pos.set(x,y,z);int color=c.level.getBlockState(pos).getMapColor(c.level,pos).col;image.setRGB(x-minX,z-minZ,0xff000000|color);}
|
||||
if(cursor==256){coverage.add(scanning);scanning=null;}
|
||||
}
|
||||
private JsonObject state(){var g=MineGeoClient.INSTANCE.gson;var j=new JsonObject();j.addProperty("server",server);j.addProperty("name",name);j.addProperty("minX",minX);j.addProperty("minZ",minZ);j.add("viewpoints",g.toJsonTree(points));j.add("coverage",g.toJsonTree(coverage));return j;}
|
||||
private void saveLocal()throws Exception{Files.createDirectories(directory);Files.writeString(directory.resolve("local.json"),MineGeoClient.INSTANCE.gson.toJson(state()));ImageIO.write(image,"png",directory.resolve("map.png").toFile());}
|
||||
public void save()throws Exception{saveLocal();var j=new JsonObject();j.addProperty("format","minegeo-dataset/v1");j.addProperty("minecraftVersion",net.fabricmc.loader.api.FabricLoader.getInstance().getModContainer("minecraft").orElseThrow().getMetadata().getVersion().getFriendlyString());j.addProperty("dimension","minecraft:overworld");j.addProperty("north","-z");j.addProperty("source","fabric");j.addProperty("name",name);j.addProperty("description","Player-captured surface map. Checkerboard areas have not been explored.");j.addProperty("serverName",server);if(!server.equals("Singleplayer"))j.addProperty("serverAddress",dev.minegeo.plugin.ServerAddress.normalize(server));var b=new JsonObject();b.addProperty("minX",minX);b.addProperty("maxX",minX+512);b.addProperty("minZ",minZ);b.addProperty("maxZ",minZ+512);j.add("bounds",b);j.add("viewpoints",MineGeoClient.INSTANCE.gson.toJsonTree(points));var chunks=new JsonArray();for(String key:coverage){var v=new JsonArray();for(String number:key.split(","))v.add(Integer.parseInt(number));chunks.add(v);}j.add("coverage",chunks);var assets=new JsonArray();addAsset(assets,"map.png");for(Point p:points)for(String face:Capture.FACES)addAsset(assets,p.id()+"/"+face+".png");j.add("assets",assets);Files.writeString(directory.resolve("manifest.json"),MineGeoClient.INSTANCE.gson.toJson(j));}
|
||||
private void addAsset(JsonArray array,String file)throws Exception{Path p=directory.resolve(file);var j=new JsonObject();j.addProperty("path",file);j.addProperty("bytes",Files.size(p));j.addProperty("sha256",HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(Files.readAllBytes(p))));array.add(j);}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package dev.minegeo.fabric.mixin;
|
||||
import dev.minegeo.fabric.Capture;
|
||||
import net.minecraft.client.Camera;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.*;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
@Mixin(Camera.class)
|
||||
public abstract class CameraMixin {
|
||||
@Shadow protected abstract void setRotation(float yaw,float pitch);
|
||||
@Inject(method="update",at=@At("TAIL")) private void direction(CallbackInfo ci){if(Capture.active())setRotation(Capture.yaw(),Capture.pitch());}
|
||||
@Inject(method="getFov",at=@At("HEAD"),cancellable=true) private void fov(CallbackInfoReturnable<Float> ci){if(Capture.active())ci.setReturnValue(90f);}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package dev.minegeo.fabric.mixin;
|
||||
import dev.minegeo.fabric.Capture;
|
||||
import net.minecraft.client.renderer.GameRenderer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
@Mixin(GameRenderer.class)
|
||||
public class GameRendererMixin {
|
||||
@Inject(method="update",at=@At("HEAD")) private void before(CallbackInfo ci){Capture.beforeFrame();}
|
||||
@Inject(method="render",at=@At("RETURN")) private void after(CallbackInfo ci){Capture.afterFrame();}
|
||||
@Inject(method={"renderItemInHand","bobView","bobHurt"},at=@At("HEAD"),cancellable=true) private void clean(CallbackInfo ci){if(Capture.active())ci.cancel();}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package dev.minegeo.fabric.mixin;
|
||||
import dev.minegeo.fabric.Capture;
|
||||
import com.mojang.blaze3d.platform.Window;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.*;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
@Mixin(Window.class)
|
||||
public class WindowMixin {
|
||||
@Inject(method={"getWidth","getHeight"},at=@At("HEAD"),cancellable=true) private void square(CallbackInfoReturnable<Integer> ci){if(Capture.active())ci.setReturnValue(Capture.SIZE);}
|
||||
}
|
||||
11
minecraft/fabric-modern/src/main/resources/fabric.mod.json
Normal file
11
minecraft/fabric-modern/src/main/resources/fabric.mod.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "minegeo",
|
||||
"version": "${version}",
|
||||
"name": "MineGeo Capture",
|
||||
"description": "Capture six-face Minecraft panoramas and surface maps, then upload them to MineGeo.",
|
||||
"environment": "client",
|
||||
"entrypoints": {"client": ["dev.minegeo.fabric.MineGeoClient"]},
|
||||
"mixins": ["minegeo.mixins.json"],
|
||||
"depends": {"fabricloader": ">=0.19.5", "minecraft": "~${minecraftVersion}", "java": ">=25", "fabric-api": "*"}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"required":true,"package":"dev.minegeo.fabric.mixin","compatibilityLevel":"JAVA_25","client":["GameRendererMixin","WindowMixin","${targetMixin}","CameraMixin"],"injectors":{"defaultRequire":1}}
|
||||
Reference in New Issue
Block a user