Initial commit
build / build (push) Successful in 2m24s

This commit is contained in:
ciomek committed 2026-03-12 19:48:20 +01:00
commit 7c9c5b8ab1
32 files changed
+1488

No files matched your search

@@ -0,0 +1,21 @@
package ciomek.quetzal;
import ciomek.quetzal.teleport.TeleportRequest;
import ciomek.quetzal.tick.TickTimer;
import net.minecraft.core.entity.player.Player;
import net.minecraft.core.world.World;
import java.util.List;
import java.util.function.Consumer;
public interface IWorldUtils {
void addTickCallback(int delayTicks, Consumer<World> callback);
void addRequestToTeleportTo(Player sender, Player receiver);
void addRequestToTeleportFrom(Player sender, Player receiver);
List<TeleportRequest> getPendingRequests(Player receiver);
TeleportRequest getNewestRequest(Player receiver);
TickTimer getTickTimer();
}
+39
View File
@@ -0,0 +1,39 @@
package ciomek.quetzal;
import ciomek.quetzal.commands.HomeCommand;
import ciomek.quetzal.commands.TeleportAcceptCommand;
import ciomek.quetzal.commands.TeleportCommand;
import ciomek.quetzal.commands.TeleportDeclineCommand;
import net.fabricmc.api.ModInitializer;
import net.minecraft.core.net.command.CommandManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import turniplabs.halplibe.util.GameStartEntrypoint;
import turniplabs.halplibe.util.RecipeEntrypoint;
public class Quetzal implements ModInitializer, RecipeEntrypoint, GameStartEntrypoint {
public static final String MOD_ID = "quetzal";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
@Override
public void onInitialize() {
LOGGER.info("quetzal initialized.");
CommandManager.registerCommand(new TeleportCommand());
CommandManager.registerCommand(new TeleportAcceptCommand());
CommandManager.registerCommand(new TeleportDeclineCommand());
CommandManager.registerCommand(new HomeCommand());
}
@Override
public void onRecipesReady() {}
@Override
public void initNamespaces() {}
@Override
public void beforeGameStart() {}
@Override
public void afterGameStart() {}
}
+32
View File
@@ -0,0 +1,32 @@
package ciomek.quetzal;
import ciomek.quetzal.tick.TickTimer;
import net.minecraft.core.entity.player.Player;
import net.minecraft.server.entity.player.PlayerServer;
public class Utils {
public static void TeleportPlayer(Player fromPlayer, Player toPlayer, TickTimer timer)
{
double startX = round1(fromPlayer.x);
double startY = round1(fromPlayer.y);
double startZ = round1(fromPlayer.z);
fromPlayer.sendMessage("Teleporting home. Don't move for 5 seconds...");
timer.addTickTask(100, (world) -> {
if (round1(fromPlayer.x) != startX || round1(fromPlayer.y) != startY || round1(fromPlayer.z) != startZ) {
fromPlayer.sendMessage("Teleport cancelled because you moved.");
return;
}
((PlayerServer)fromPlayer).playerNetServerHandler.teleportAndRotate(toPlayer.x, toPlayer.y, toPlayer.z, toPlayer.yRot, toPlayer.xRot);
fromPlayer.sendMessage("Teleported to " + toPlayer.username + "!");
toPlayer.sendMessage(fromPlayer.username + " teleported to you!");
});
}
private static double round1(double value) {
return Math.round(value * 10.0) / 10.0;
}
}
@@ -0,0 +1,117 @@
package ciomek.quetzal.commands;
import ciomek.quetzal.IWorldUtils;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.ArgumentBuilderLiteral;
import net.minecraft.core.entity.player.Player;
import net.minecraft.core.net.command.CommandManager;
import net.minecraft.core.net.command.CommandSource;
import net.fabricmc.api.ModInitializer;
import net.minecraft.server.entity.player.PlayerServer;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
public class HomeCommand implements CommandManager.CommandRegistry, ModInitializer {
private static final Map<String, double[]> homes = new HashMap<>();
private static final Gson gson = new Gson();
private static final File homesFile = new File("homes.json");
@Override
public void onInitialize() {
loadHomes();
}
private void loadHomes() {
if (!homesFile.exists()) return;
try (FileReader reader = new FileReader(homesFile)) {
Type type = new TypeToken<Map<String, double[]>>(){}.getType();
Map<String, double[]> loaded = gson.fromJson(reader, type);
if (loaded != null) homes.putAll(loaded);
} catch (IOException e) {
System.err.println("Failed to load homes: " + e.getMessage());
}
}
private void saveHomes() {
try (FileWriter writer = new FileWriter(homesFile)) {
gson.toJson(homes, writer);
} catch (IOException e) {
System.err.println("Failed to save homes: " + e.getMessage());
}
}
@Override
public void register(CommandDispatcher<CommandSource> dispatcher) {
dispatcher.register(
ArgumentBuilderLiteral.<CommandSource>literal("sethome")
.executes(context -> {
CommandSource source = context.getSource();
Player player = source.getSender();
if (player == null) {
source.sendMessage("This command can only be run by a player!");
return 0;
}
homes.put(player.username, new double[]{player.x, player.y, player.z});
saveHomes();
player.sendMessage("Home set.");
return 1;
})
);
dispatcher.register(
ArgumentBuilderLiteral.<CommandSource>literal("home")
.executes(context -> {
CommandSource source = context.getSource();
Player player = source.getSender();
IWorldUtils worldUtils = (IWorldUtils) source.getWorld();
if (player == null) {
source.sendMessage("This command can only be run by a player!");
return 0;
}
if (!homes.containsKey(player.username)) {
homes.put(player.username, new double[]{player.x, player.y, player.z});
saveHomes();
player.sendMessage("Home was not set. Setting it to your current location.");
return 1;
}
double[] home = homes.get(player.username);
double startX = round1(player.x);
double startY = round1(player.y);
double startZ = round1(player.z);
player.sendMessage("Teleporting home. Don't move for 5 seconds...");
worldUtils.addTickCallback(100, (w) -> {
if (round1(player.x) != startX || round1(player.y) != startY || round1(player.z) != startZ) {
player.sendMessage("Teleport cancelled because you moved.");
return;
}
((PlayerServer)player).playerNetServerHandler.teleport(home[0], home[1], home[2]);
player.sendMessage("Teleported home.");
});
return 1;
})
);
}
private double round1(double value) {
return Math.round(value * 10.0) / 10.0;
}
}
@@ -0,0 +1,34 @@
package ciomek.quetzal.commands;
import ciomek.quetzal.IWorldUtils;
import ciomek.quetzal.teleport.TeleportRequest;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.ArgumentBuilderLiteral;
import net.minecraft.core.entity.player.Player;
import net.minecraft.core.net.command.CommandManager;
import net.minecraft.core.net.command.CommandSource;
import static com.mojang.brigadier.builder.ArgumentBuilderLiteral.literal;
public class TeleportAcceptCommand implements CommandManager.CommandRegistry {
@SuppressWarnings("unchecked")
@Override
public void register(CommandDispatcher<CommandSource> commandDispatcher) {
commandDispatcher
.register(
(ArgumentBuilderLiteral<CommandSource>) (Object) literal("tpa")
.executes(context -> {
CommandSource source = (CommandSource) context.getSource();
Player player = source.getSender();
IWorldUtils worldUtils = (IWorldUtils) source.getWorld();
TeleportRequest tpr = worldUtils.getNewestRequest(player);
tpr.accept(worldUtils.getTickTimer());
return 1;
})
);
}
}
@@ -0,0 +1,92 @@
package ciomek.quetzal.commands;
import ciomek.quetzal.IWorldUtils;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.ArgumentBuilderLiteral;
import com.mojang.brigadier.builder.ArgumentBuilderRequired;
import net.minecraft.core.entity.Entity;
import net.minecraft.core.entity.player.Player;
import net.minecraft.core.net.command.CommandManager;
import net.minecraft.core.net.command.CommandSource;
import net.minecraft.core.net.command.arguments.ArgumentTypeEntity;
import net.minecraft.core.net.command.helpers.EntitySelector;
import static com.mojang.brigadier.builder.ArgumentBuilderLiteral.literal;
public class TeleportCommand implements CommandManager.CommandRegistry {
@SuppressWarnings("unchecked")
@Override
public void register(CommandDispatcher<CommandSource> commandDispatcher) {
commandDispatcher
.register(
(ArgumentBuilderLiteral<CommandSource>) (Object) literal("tpr")
.then(
ArgumentBuilderRequired.argument("player", ArgumentTypeEntity.username())
.executes(context -> {
CommandSource source = (CommandSource) context.getSource();
EntitySelector entitySelector = context.getArgument("player", EntitySelector.class);
if (entitySelector.get(source).isEmpty()) {
source.sendMessage("None selected!");
return 0;
}
Entity entity = entitySelector.get(source).get(0);
Player target = (Player) entity;
Player player = source.getSender();
IWorldUtils worldUtils = (IWorldUtils) source.getWorld();
if (player == null) {
source.sendMessage("This command can only be run by a player!");
return 0;
}
worldUtils.addRequestToTeleportTo(player, target);
player.sendMessage("Sent request to " + target.username + ".");
target.sendMessage("Do you want " + target.username + " to teleport to you?");
return 1;
})
)
);
commandDispatcher
.register(
(ArgumentBuilderLiteral<CommandSource>) (Object) literal("tpfr")
.then(
ArgumentBuilderRequired.argument("player", ArgumentTypeEntity.username())
.executes(context -> {
CommandSource source = (CommandSource) context.getSource();
EntitySelector entitySelector = context.getArgument("player", EntitySelector.class);
if (entitySelector.get(source).isEmpty()) {
source.sendMessage("None selected!");
return 0;
}
Entity entity = entitySelector.get(source).get(0);
Player target = (Player) entity;
Player player = source.getSender();
IWorldUtils worldUtils = (IWorldUtils) source.getWorld();
if (player == null) {
source.sendMessage("This command can only be run by a player!");
return 0;
}
worldUtils.addRequestToTeleportFrom(player, target);
player.sendMessage("Sent request to " + target.username + ".");
target.sendMessage("Do you want to teleport to " + target.username + "?");
return 1;
})
)
);
}
}
@@ -0,0 +1,34 @@
package ciomek.quetzal.commands;
import ciomek.quetzal.IWorldUtils;
import ciomek.quetzal.teleport.TeleportRequest;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.ArgumentBuilderLiteral;
import net.minecraft.core.entity.player.Player;
import net.minecraft.core.net.command.CommandManager;
import net.minecraft.core.net.command.CommandSource;
import static com.mojang.brigadier.builder.ArgumentBuilderLiteral.literal;
public class TeleportDeclineCommand implements CommandManager.CommandRegistry {
@SuppressWarnings("unchecked")
@Override
public void register(CommandDispatcher<CommandSource> commandDispatcher) {
commandDispatcher
.register(
(ArgumentBuilderLiteral<CommandSource>) (Object) literal("tpd")
.executes(context -> {
CommandSource source = (CommandSource) context.getSource();
Player player = source.getSender();
IWorldUtils worldUtils = (IWorldUtils) source.getWorld();
TeleportRequest tpr = worldUtils.getNewestRequest(player);
tpr.decline();
return 1;
})
);
}
}
@@ -0,0 +1,76 @@
package ciomek.quetzal.mixin;
import ciomek.quetzal.IWorldUtils;
import ciomek.quetzal.teleport.TeleportRequest;
import ciomek.quetzal.teleport.TeleportToRequest;
import ciomek.quetzal.tick.TickTimer;
import net.minecraft.core.entity.player.Player;
import net.minecraft.core.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@Mixin(value = World.class, remap = false)
public class WorldOverwriteMixin implements IWorldUtils {
@Unique
private final TickTimer tickTimer = new TickTimer();
@Unique
private final List<TeleportRequest> teleportRequests = new ArrayList<>();
@Override
public TickTimer getTickTimer()
{
return tickTimer;
}
@Override
public void addTickCallback(int delayTicks, Consumer<World> callback) {
tickTimer.addTickTask(delayTicks, callback);
}
@Inject(method = "tick", at = @At(value = "TAIL"))
public void tick(CallbackInfo ci) {
tickTimer.tick((World) (Object) this);
}
@Override
public void addRequestToTeleportTo(Player sender, Player receiver) {
TeleportRequest tpr = new TeleportToRequest(sender, receiver, LocalDate.now());
teleportRequests.add(tpr);
}
@Override
public void addRequestToTeleportFrom(Player sender, Player receiver) {
TeleportRequest tpr = new TeleportToRequest(sender, receiver, LocalDate.now());
teleportRequests.add(tpr);
}
@Override
public TeleportRequest getNewestRequest(Player receiver)
{
return teleportRequests.stream()
.filter(tr -> Objects.equals(tr.receiver, receiver))
.max(Comparator.comparing(tr -> tr.sentAt))
.orElse(null);
}
@Override
public List<TeleportRequest> getPendingRequests(Player receiver) {
return teleportRequests.stream()
.filter(tr -> Objects.equals(tr.receiver, receiver))
.collect(Collectors.toList());
}
}
@@ -0,0 +1,24 @@
package ciomek.quetzal.teleport;
import ciomek.quetzal.Utils;
import ciomek.quetzal.tick.TickTimer;
import net.minecraft.core.entity.player.Player;
import java.time.LocalDate;
public class TeleportFromRequest extends TeleportRequest {
public TeleportFromRequest(Player sender, Player receiver, LocalDate sentAt) {
super(sender, receiver, sentAt);
}
public void accept(TickTimer timer)
{
sender.sendMessage(receiver.username + " has accepted the teleportation request. ");
Utils.TeleportPlayer(receiver, sender, timer);
}
public void decline()
{
sender.sendMessage(receiver.username + " has declined the teleportation request. ");
}
}
@@ -0,0 +1,24 @@
package ciomek.quetzal.teleport;
import ciomek.quetzal.tick.TickTimer;
import net.minecraft.core.entity.player.Player;
import java.time.LocalDate;
public abstract class TeleportRequest {
public Player sender;
public Player receiver;
public LocalDate sentAt;
public TeleportRequest(Player sender, Player receiver, LocalDate sentAt) {
this.sender = sender;
this.receiver = receiver;
this.sentAt = sentAt;
}
public abstract void accept(TickTimer timer);
public abstract void decline();
}
@@ -0,0 +1,24 @@
package ciomek.quetzal.teleport;
import ciomek.quetzal.Utils;
import ciomek.quetzal.tick.TickTimer;
import net.minecraft.core.entity.player.Player;
import java.time.LocalDate;
public class TeleportToRequest extends TeleportRequest {
public TeleportToRequest(Player sender, Player receiver, LocalDate sentAt) {
super(sender, receiver, sentAt);
}
public void accept(TickTimer timer)
{
sender.sendMessage(receiver.username + " has accepted the teleportation request. ");
Utils.TeleportPlayer(sender, receiver, timer);
}
public void decline()
{
sender.sendMessage(receiver.username + " has declined the teleportation request. ");
}
}
@@ -0,0 +1,15 @@
package ciomek.quetzal.tick;
import net.minecraft.core.world.World;
import java.util.function.Consumer;
public class TickTask {
public int ticksLeft;
public Consumer<World> callback;
public TickTask(int ticksLeft, Consumer<World> callback) {
this.ticksLeft = ticksLeft;
this.callback = callback;
}
}
@@ -0,0 +1,32 @@
package ciomek.quetzal.tick;
import net.minecraft.core.world.World;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
public class TickTimer {
private final List<TickTask> tickTasks = new ArrayList<>();
public void addTickTask(int delayTicks, Consumer<World> callback) {
tickTasks.add(new TickTask(delayTicks, callback));
}
public void tick(World world) {
Iterator<TickTask> iterator = tickTasks.iterator();
while (iterator.hasNext()) {
TickTask task = iterator.next();
task.ticksLeft--;
if (task.ticksLeft <= 0) {
task.callback.accept(world);
iterator.remove();
}
}
}
}
+43
View File
@@ -0,0 +1,43 @@
{
"schemaVersion": 1,
"id": "quetzal",
"version": "${version}",
"name": "Quetzal",
"description": "Basic stuff for servers.",
"authors": [
"ciomek"
],
"contact": {
"homepage": "",
"sources": ""
},
"icon": "icon.png",
"license": "CC0-1.0",
"environment": "*",
"entrypoints": {
"main": [
"ciomek.quetzal.Quetzal"
],
"beforeGameStart": [
"ciomek.quetzal.Quetzal"
],
"afterGameStart": [
"ciomek.quetzal.Quetzal"
],
"recipesReady": [
"ciomek.quetzal.Quetzal"
]
},
"mixins": [
"quetzal.mixins.json"
],
"depends": {
"fabricloader": ">=${fabricloader}",
"halplibe": ">=${halplibe}",
"minecraft": "*",
"java": ">=${java}"
},
"suggests": {
"modmenu": ">=${modmenu}"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Whitespace-only changes.
+12
View File
@@ -0,0 +1,12 @@
{
"required": true,
"package": "ciomek.quetzal.mixin",
"compatibilityLevel": "JAVA_${java}",
"injectors": {
"defaultRequire": 1
},
"mixins": [
"WorldOverwriteMixin"
],
"client": []
}