a few puter fixes
This commit is contained in:
1 parent
0c400ef404
commit
3300fa8f2a
86 files changed
+4473
-193
No files matched your search
-15
@@ -76,11 +76,9 @@ public class OctopusBombEntity extends Monster implements GeoEntity {
|
|||||||
|
|
||||||
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
|
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
|
||||||
|
|
||||||
// Squid-style wander
|
|
||||||
private float wanderDx, wanderDy, wanderDz;
|
private float wanderDx, wanderDy, wanderDz;
|
||||||
private int wanderRetargetTicks;
|
private int wanderRetargetTicks;
|
||||||
|
|
||||||
// Combat
|
|
||||||
private int attackCooldown;
|
private int attackCooldown;
|
||||||
private int actionTicks;
|
private int actionTicks;
|
||||||
private int lastAttackState;
|
private int lastAttackState;
|
||||||
@@ -89,7 +87,6 @@ public class OctopusBombEntity extends Monster implements GeoEntity {
|
|||||||
private float orbitDirection = 1.0F;
|
private float orbitDirection = 1.0F;
|
||||||
private int inkSprayCooldown;
|
private int inkSprayCooldown;
|
||||||
|
|
||||||
// Kraken spawn flag
|
|
||||||
private boolean spawnedByKraken;
|
private boolean spawnedByKraken;
|
||||||
|
|
||||||
// Explosive impact (fired from the R.P.O. Launcher)
|
// Explosive impact (fired from the R.P.O. Launcher)
|
||||||
@@ -366,10 +363,6 @@ public class OctopusBombEntity extends Monster implements GeoEntity {
|
|||||||
this.hasImpulse = true;
|
this.hasImpulse = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Squid-style wander
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
private void tickWanderMovement() {
|
private void tickWanderMovement() {
|
||||||
if (--this.wanderRetargetTicks <= 0) {
|
if (--this.wanderRetargetTicks <= 0) {
|
||||||
this.wanderRetargetTicks = 40 + this.random.nextInt(40);
|
this.wanderRetargetTicks = 40 + this.random.nextInt(40);
|
||||||
@@ -388,10 +381,6 @@ public class OctopusBombEntity extends Monster implements GeoEntity {
|
|||||||
this.setDeltaMovement(this.getDeltaMovement().add(this.wanderDx, dy, this.wanderDz));
|
this.setDeltaMovement(this.getDeltaMovement().add(this.wanderDx, dy, this.wanderDz));
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Ink spray
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
private boolean hasHitExplosiveTarget() {
|
private boolean hasHitExplosiveTarget() {
|
||||||
AABB box = this.getBoundingBox();
|
AABB box = this.getBoundingBox();
|
||||||
return !this.level().getEntitiesOfClass(LivingEntity.class, box,
|
return !this.level().getEntitiesOfClass(LivingEntity.class, box,
|
||||||
@@ -447,10 +436,6 @@ public class OctopusBombEntity extends Monster implements GeoEntity {
|
|||||||
serverLevel.sendParticles(OCTOPUS_BOMB_INK_PARTICLE, cx, cy, cz, 30, 3.5D, 1.8D, 3.5D, 0.02D);
|
serverLevel.sendParticles(OCTOPUS_BOMB_INK_PARTICLE, cx, cy, cz, 30, 3.5D, 1.8D, 3.5D, 0.02D);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Combat
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
private void tickPursuitMovement(LivingEntity target) {
|
private void tickPursuitMovement(LivingEntity target) {
|
||||||
if (this.strafeRetargetTicks-- <= 0) {
|
if (this.strafeRetargetTicks-- <= 0) {
|
||||||
this.strafeRetargetTicks = 10 + this.random.nextInt(12);
|
this.strafeRetargetTicks = 10 + this.random.nextInt(12);
|
||||||
|
|||||||
@@ -157,7 +157,6 @@ public class BigBerthaItem extends SwordItem implements GeoItem, com.craisinlord
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toreterror jump tracking
|
|
||||||
if (isToreterrorJumping(stack) && player.getMainHandItem() == stack) {
|
if (isToreterrorJumping(stack) && player.getMainHandItem() == stack) {
|
||||||
player.fallDistance = 0.0F; // prevent fall damage during the crash-down
|
player.fallDistance = 0.0F; // prevent fall damage during the crash-down
|
||||||
boolean launched = readInt(stack, TORETERROR_LAUNCHED_TAG) == 1;
|
boolean launched = readInt(stack, TORETERROR_LAUNCHED_TAG) == 1;
|
||||||
@@ -178,7 +177,6 @@ public class BigBerthaItem extends SwordItem implements GeoItem, com.craisinlord
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tick client shake counter
|
|
||||||
if (level.isClientSide && clientShakeTicks > 0) {
|
if (level.isClientSide && clientShakeTicks > 0) {
|
||||||
clientShakeTicks--;
|
clientShakeTicks--;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.apache.logging.log4j.Logger;
|
|||||||
|
|
||||||
public class Antarchy {
|
public class Antarchy {
|
||||||
public static final String MODID = "antarchy";
|
public static final String MODID = "antarchy";
|
||||||
|
public static final String MOD_VERSION = "2.0.0";
|
||||||
public static final Logger LOGGER = LogManager.getLogger();
|
public static final Logger LOGGER = LogManager.getLogger();
|
||||||
|
|
||||||
public static void init() {
|
public static void init() {
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ public final class AntarchySoundEvents {
|
|||||||
public static Supplier<SoundEvent> QUEEN_ROAR = (Supplier<SoundEvent>) UNBOUND;
|
public static Supplier<SoundEvent> QUEEN_ROAR = (Supplier<SoundEvent>) UNBOUND;
|
||||||
public static Supplier<SoundEvent> KING_BEAM_SHOOT = (Supplier<SoundEvent>) UNBOUND;
|
public static Supplier<SoundEvent> KING_BEAM_SHOOT = (Supplier<SoundEvent>) UNBOUND;
|
||||||
public static Supplier<SoundEvent> KING_FIREBALL_SHOOT = (Supplier<SoundEvent>) UNBOUND;
|
public static Supplier<SoundEvent> KING_FIREBALL_SHOOT = (Supplier<SoundEvent>) UNBOUND;
|
||||||
|
public static Supplier<SoundEvent> KING_ICE_SPIKES = (Supplier<SoundEvent>) UNBOUND;
|
||||||
public static Supplier<SoundEvent> KING_DECREE_CAST = (Supplier<SoundEvent>) UNBOUND;
|
public static Supplier<SoundEvent> KING_DECREE_CAST = (Supplier<SoundEvent>) UNBOUND;
|
||||||
public static Supplier<SoundEvent> QUEEN_BEAM_SHOOT = (Supplier<SoundEvent>) UNBOUND;
|
public static Supplier<SoundEvent> QUEEN_BEAM_SHOOT = (Supplier<SoundEvent>) UNBOUND;
|
||||||
public static Supplier<SoundEvent> QUEEN_BEAM_START = (Supplier<SoundEvent>) UNBOUND;
|
public static Supplier<SoundEvent> QUEEN_BEAM_START = (Supplier<SoundEvent>) UNBOUND;
|
||||||
@@ -409,6 +410,7 @@ public final class AntarchySoundEvents {
|
|||||||
Supplier<SoundEvent> queenRoar,
|
Supplier<SoundEvent> queenRoar,
|
||||||
Supplier<SoundEvent> kingBeamShoot,
|
Supplier<SoundEvent> kingBeamShoot,
|
||||||
Supplier<SoundEvent> kingFireballShoot,
|
Supplier<SoundEvent> kingFireballShoot,
|
||||||
|
Supplier<SoundEvent> kingIceSpikes,
|
||||||
Supplier<SoundEvent> kingDecreeCast,
|
Supplier<SoundEvent> kingDecreeCast,
|
||||||
Supplier<SoundEvent> queenBeamShoot,
|
Supplier<SoundEvent> queenBeamShoot,
|
||||||
Supplier<SoundEvent> queenBeamStart,
|
Supplier<SoundEvent> queenBeamStart,
|
||||||
@@ -617,6 +619,7 @@ public final class AntarchySoundEvents {
|
|||||||
QUEEN_ROAR = queenRoar;
|
QUEEN_ROAR = queenRoar;
|
||||||
KING_BEAM_SHOOT = kingBeamShoot;
|
KING_BEAM_SHOOT = kingBeamShoot;
|
||||||
KING_FIREBALL_SHOOT = kingFireballShoot;
|
KING_FIREBALL_SHOOT = kingFireballShoot;
|
||||||
|
KING_ICE_SPIKES = kingIceSpikes;
|
||||||
KING_DECREE_CAST = kingDecreeCast;
|
KING_DECREE_CAST = kingDecreeCast;
|
||||||
QUEEN_BEAM_SHOOT = queenBeamShoot;
|
QUEEN_BEAM_SHOOT = queenBeamShoot;
|
||||||
QUEEN_BEAM_START = queenBeamStart;
|
QUEEN_BEAM_START = queenBeamStart;
|
||||||
|
|||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
package com.craisinlord.antarchy.content.antmail;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public record AntmailAddress(String username) {
|
||||||
|
public static final String DOMAIN = "antmail.com";
|
||||||
|
private static final Pattern USERNAME = Pattern.compile("[a-z0-9_]{3,16}");
|
||||||
|
|
||||||
|
public AntmailAddress {
|
||||||
|
username = normalizeUsername(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntmailAddress parse(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new IllegalArgumentException("Address is null");
|
||||||
|
}
|
||||||
|
String normalized = value.trim().toLowerCase(Locale.ROOT);
|
||||||
|
String suffix = "@" + DOMAIN;
|
||||||
|
if (!normalized.endsWith(suffix)) {
|
||||||
|
throw new IllegalArgumentException("Invalid Antmail domain");
|
||||||
|
}
|
||||||
|
return new AntmailAddress(normalized.substring(0, normalized.length() - suffix.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntmailAddress ofUsername(String username) {
|
||||||
|
return new AntmailAddress(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String fullAddress() {
|
||||||
|
return username + "@" + DOMAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String normalizeUsername(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new IllegalArgumentException("Username is null");
|
||||||
|
}
|
||||||
|
String normalized = value.trim().toLowerCase(Locale.ROOT);
|
||||||
|
if (!USERNAME.matcher(normalized).matches()) {
|
||||||
|
throw new IllegalArgumentException("Username must match [a-z0-9_]{3,16}");
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValidUsername(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return USERNAME.matcher(value.trim().toLowerCase(Locale.ROOT)).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValidAddress(String value) {
|
||||||
|
try {
|
||||||
|
parse(value);
|
||||||
|
return true;
|
||||||
|
} catch (IllegalArgumentException exception) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return fullAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object object) {
|
||||||
|
return object instanceof AntmailAddress other && username.equals(other.username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(username);
|
||||||
|
}
|
||||||
|
}
|
||||||
+102
@@ -0,0 +1,102 @@
|
|||||||
|
package com.craisinlord.antarchy.content.antmail;
|
||||||
|
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public sealed interface AntmailAttachment permits AntmailAttachment.TextFile, AntmailAttachment.PaintImage {
|
||||||
|
String TYPE_TAG = "Type";
|
||||||
|
String NAME_TAG = "Name";
|
||||||
|
|
||||||
|
String fileName();
|
||||||
|
|
||||||
|
int byteSize();
|
||||||
|
|
||||||
|
CompoundTag toTag();
|
||||||
|
|
||||||
|
static AntmailAttachment fromTag(CompoundTag tag) {
|
||||||
|
return switch (tag.getString(TYPE_TAG)) {
|
||||||
|
case "text_file" -> TextFile.fromTag(tag);
|
||||||
|
case "paint_image" -> PaintImage.fromTag(tag);
|
||||||
|
default -> throw new IllegalArgumentException("Unknown Antmail attachment type");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
record TextFile(String fileName, String contents) implements AntmailAttachment {
|
||||||
|
public TextFile {
|
||||||
|
fileName = validateFileName(fileName);
|
||||||
|
contents = Objects.requireNonNull(contents, "contents");
|
||||||
|
if (contents.length() > AntmailValidation.MAX_TEXT_ATTACHMENT_CHARACTERS) {
|
||||||
|
throw new IllegalArgumentException("Text attachment is too large");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int byteSize() {
|
||||||
|
return contents.getBytes(StandardCharsets.UTF_8).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompoundTag toTag() {
|
||||||
|
CompoundTag tag = new CompoundTag();
|
||||||
|
tag.putString(TYPE_TAG, "text_file");
|
||||||
|
tag.putString(NAME_TAG, fileName);
|
||||||
|
tag.putString("Contents", contents);
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TextFile fromTag(CompoundTag tag) {
|
||||||
|
return new TextFile(tag.getString(NAME_TAG), tag.getString("Contents"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record PaintImage(String fileName, int width, int height, byte[] pixels) implements AntmailAttachment {
|
||||||
|
public PaintImage {
|
||||||
|
fileName = validateFileName(fileName);
|
||||||
|
if (width != AntmailValidation.MAX_PAINT_WIDTH || height != AntmailValidation.MAX_PAINT_HEIGHT) {
|
||||||
|
throw new IllegalArgumentException("Invalid Paint image dimensions");
|
||||||
|
}
|
||||||
|
if (pixels == null || pixels.length != width * height) {
|
||||||
|
throw new IllegalArgumentException("Paint pixel data does not match dimensions");
|
||||||
|
}
|
||||||
|
for (byte pixel : pixels) {
|
||||||
|
if (pixel != 0 && pixel != 1) throw new IllegalArgumentException("Paint pixel data must be monochrome");
|
||||||
|
}
|
||||||
|
pixels = Arrays.copyOf(pixels, pixels.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] pixels() {
|
||||||
|
return Arrays.copyOf(pixels, pixels.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int byteSize() {
|
||||||
|
return pixels.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompoundTag toTag() {
|
||||||
|
CompoundTag tag = new CompoundTag();
|
||||||
|
tag.putString(TYPE_TAG, "paint_image");
|
||||||
|
tag.putString(NAME_TAG, fileName);
|
||||||
|
tag.putInt("Width", width);
|
||||||
|
tag.putInt("Height", height);
|
||||||
|
tag.putByteArray("Pixels", pixels);
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PaintImage fromTag(CompoundTag tag) {
|
||||||
|
return new PaintImage(tag.getString(NAME_TAG), tag.getInt("Width"), tag.getInt("Height"), tag.getByteArray("Pixels"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String validateFileName(String value) {
|
||||||
|
if (value == null || value.isBlank() || value.length() > AntmailValidation.MAX_FILE_NAME_LENGTH || value.contains("/") || value.contains("\\")) {
|
||||||
|
throw new IllegalArgumentException("Invalid attachment filename");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
+93
@@ -0,0 +1,93 @@
|
|||||||
|
package com.craisinlord.antarchy.content.antmail;
|
||||||
|
|
||||||
|
import com.craisinlord.antarchy.content.computer.ComputerFileSystem;
|
||||||
|
import com.craisinlord.antarchy.content.computer.paint.AntPaintCanvas;
|
||||||
|
import com.craisinlord.antarchy.content.computer.paint.AntPaintFile;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public final class AntmailAttachmentFiles {
|
||||||
|
private AntmailAttachmentFiles() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<AntmailAttachment> fromComputerFile(ComputerFileSystem.ComputerFile file) {
|
||||||
|
if (file == null || file.type() != ComputerFileSystem.ComputerFile.Type.TEXT) return Optional.empty();
|
||||||
|
try {
|
||||||
|
return Optional.of(new AntmailAttachment.TextFile(filename(file.path()), file.contents()));
|
||||||
|
} catch (IllegalArgumentException exception) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntmailAttachment.TextFile fromTextFile(String path, String contents) {
|
||||||
|
return new AntmailAttachment.TextFile(filename(path), contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntmailAttachment.PaintImage fromPaintFile(AntPaintFile file) {
|
||||||
|
if (file == null) throw new IllegalArgumentException("Paint file cannot be null");
|
||||||
|
AntPaintCanvas canvas = file.canvas();
|
||||||
|
if (canvas == null || canvas.copyPixels().length != AntPaintCanvas.PIXEL_COUNT) {
|
||||||
|
throw new IllegalArgumentException("Invalid AntPaint canvas");
|
||||||
|
}
|
||||||
|
boolean[] source = canvas.copyPixels();
|
||||||
|
byte[] pixels = new byte[source.length];
|
||||||
|
for (int index = 0; index < source.length; index++) pixels[index] = source[index] ? (byte) 1 : (byte) 0;
|
||||||
|
return new AntmailAttachment.PaintImage(file.filename(), AntPaintCanvas.WIDTH, AntPaintCanvas.HEIGHT, pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntPaintFile toPaintFile(AntmailAttachment.PaintImage image, String fileId, long createdTime, long modifiedTime) {
|
||||||
|
if (image == null) throw new IllegalArgumentException("Paint attachment cannot be null");
|
||||||
|
if (image.width() != AntPaintCanvas.WIDTH || image.height() != AntPaintCanvas.HEIGHT) {
|
||||||
|
throw new IllegalArgumentException("Unsupported AntPaint canvas size");
|
||||||
|
}
|
||||||
|
byte[] source = image.pixels();
|
||||||
|
boolean[] pixels = new boolean[source.length];
|
||||||
|
for (int index = 0; index < source.length; index++) {
|
||||||
|
if (source[index] != 0 && source[index] != 1) throw new IllegalArgumentException("Invalid AntPaint pixel");
|
||||||
|
pixels[index] = source[index] == 1;
|
||||||
|
}
|
||||||
|
AntPaintCanvas canvas = new AntPaintCanvas();
|
||||||
|
for (int index = 0; index < pixels.length; index++) {
|
||||||
|
if (pixels[index]) canvas.set(index % AntPaintCanvas.WIDTH, index / AntPaintCanvas.WIDTH, true);
|
||||||
|
}
|
||||||
|
return new AntPaintFile(fileId, image.fileName(), canvas, createdTime, modifiedTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ComputerFileSystem.ComputerFile toComputerTextFile(String path, AntmailAttachment.TextFile file) {
|
||||||
|
if (file == null) throw new IllegalArgumentException("Text attachment cannot be null");
|
||||||
|
String normalized = ComputerFileSystem.normalize(path);
|
||||||
|
if (normalized.isEmpty() || normalized.equals(ComputerFileSystem.ROOT)) throw new IllegalArgumentException("Invalid text file path");
|
||||||
|
return ComputerFileSystem.ComputerFile.text(normalized, file.contents());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ComputerFileSystem.ComputerFile> selectableTextFiles(Collection<ComputerFileSystem.ComputerFile> files) {
|
||||||
|
if (files == null) return List.of();
|
||||||
|
List<ComputerFileSystem.ComputerFile> result = new ArrayList<>();
|
||||||
|
for (ComputerFileSystem.ComputerFile file : files) {
|
||||||
|
if (fromComputerFile(file).isPresent()) result.add(file);
|
||||||
|
}
|
||||||
|
return List.copyOf(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<AntmailAttachment> selectablePaintFiles(Collection<AntPaintFile> files) {
|
||||||
|
if (files == null) return List.of();
|
||||||
|
List<AntmailAttachment> result = new ArrayList<>();
|
||||||
|
for (AntPaintFile file : files) {
|
||||||
|
try {
|
||||||
|
result.add(fromPaintFile(file));
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return List.copyOf(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String filename(String path) {
|
||||||
|
String normalized = ComputerFileSystem.normalize(path);
|
||||||
|
if (normalized.isEmpty() || normalized.equals(ComputerFileSystem.ROOT)) throw new IllegalArgumentException("Invalid file path");
|
||||||
|
int separator = normalized.lastIndexOf('/');
|
||||||
|
return normalized.substring(separator + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package com.craisinlord.antarchy.content.antmail;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record AntmailDeliveryResult(Status status, UUID messageId, AntmailAddress sender, AntmailAddress recipient, String detail) {
|
||||||
|
public enum Status {
|
||||||
|
DELIVERED,
|
||||||
|
QUEUED,
|
||||||
|
INVALID_ADDRESS,
|
||||||
|
ADDRESS_NOT_FOUND,
|
||||||
|
MAILBOX_FULL,
|
||||||
|
MESSAGE_INVALID,
|
||||||
|
DUPLICATE,
|
||||||
|
FAILED
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntmailDeliveryResult delivered(AntmailMessage message) {
|
||||||
|
return new AntmailDeliveryResult(Status.DELIVERED, message.id(), message.sender(), message.recipient(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntmailDeliveryResult queued(AntmailMessage message) {
|
||||||
|
return new AntmailDeliveryResult(Status.QUEUED, message.id(), message.sender(), message.recipient(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntmailDeliveryResult failed(Status status, AntmailMessage message, String detail) {
|
||||||
|
return new AntmailDeliveryResult(status, message == null ? null : message.id(), message == null ? null : message.sender(), message == null ? null : message.recipient(), detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
package com.craisinlord.antarchy.content.antmail;
|
||||||
|
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.ListTag;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record AntmailDraft(UUID id, AntmailAddress sender, AntmailAddress recipient, String subject, String body, List<AntmailAttachment> attachments) {
|
||||||
|
public AntmailDraft {
|
||||||
|
id = id == null ? UUID.randomUUID() : id;
|
||||||
|
subject = subject == null ? "" : subject;
|
||||||
|
body = body == null ? "" : body;
|
||||||
|
attachments = attachments == null ? List.of() : List.copyOf(attachments);
|
||||||
|
if (subject.length() > AntmailValidation.MAX_SUBJECT_LENGTH || body.length() > AntmailValidation.MAX_BODY_LENGTH || attachments.size() > AntmailValidation.MAX_ATTACHMENTS) {
|
||||||
|
throw new IllegalArgumentException("Draft exceeds Antmail limits");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompoundTag toTag() {
|
||||||
|
CompoundTag tag = new CompoundTag();
|
||||||
|
tag.putString("Id", id.toString());
|
||||||
|
if (sender != null) tag.putString("From", sender.fullAddress());
|
||||||
|
if (recipient != null) tag.putString("To", recipient.fullAddress());
|
||||||
|
tag.putString("Subject", subject);
|
||||||
|
tag.putString("Body", body);
|
||||||
|
ListTag attachmentTags = new ListTag();
|
||||||
|
for (AntmailAttachment attachment : attachments) attachmentTags.add(attachment.toTag());
|
||||||
|
tag.put("Attachments", attachmentTags);
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntmailDraft fromTag(CompoundTag tag) {
|
||||||
|
List<AntmailAttachment> attachments = new ArrayList<>();
|
||||||
|
ListTag attachmentTags = tag.getList("Attachments", 10);
|
||||||
|
for (int index = 0; index < attachmentTags.size(); index++) attachments.add(AntmailAttachment.fromTag(attachmentTags.getCompound(index)));
|
||||||
|
UUID id = UUID.fromString(tag.getString("Id"));
|
||||||
|
AntmailAddress sender = tag.contains("From") ? AntmailAddress.parse(tag.getString("From")) : null;
|
||||||
|
AntmailAddress recipient = tag.contains("To") ? AntmailAddress.parse(tag.getString("To")) : null;
|
||||||
|
return new AntmailDraft(id, sender, recipient, tag.getString("Subject"), tag.getString("Body"), attachments);
|
||||||
|
}
|
||||||
|
}
|
||||||
+93
@@ -0,0 +1,93 @@
|
|||||||
|
package com.craisinlord.antarchy.content.antmail;
|
||||||
|
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.ListTag;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public final class AntmailMailbox {
|
||||||
|
private final AntmailAddress address;
|
||||||
|
private final List<AntmailMessage> inbox = new ArrayList<>();
|
||||||
|
private final List<AntmailMessage> sent = new ArrayList<>();
|
||||||
|
private final List<AntmailDraft> drafts = new ArrayList<>();
|
||||||
|
|
||||||
|
public AntmailMailbox(AntmailAddress address) {
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AntmailAddress address() { return address; }
|
||||||
|
public List<AntmailMessage> inbox() { return List.copyOf(inbox); }
|
||||||
|
public List<AntmailMessage> sent() { return List.copyOf(sent); }
|
||||||
|
public List<AntmailDraft> drafts() { return List.copyOf(drafts); }
|
||||||
|
public int unreadCount() { return (int) inbox.stream().filter(message -> !message.read()).count(); }
|
||||||
|
|
||||||
|
public boolean addIncoming(AntmailMessage message) {
|
||||||
|
if (!address.equals(message.recipient()) || inbox.size() >= AntmailValidation.MAX_MAILBOX_MESSAGES || contains(inbox, message.id())) return false;
|
||||||
|
inbox.add(message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addSent(AntmailMessage message) {
|
||||||
|
if (!address.equals(message.sender()) || sent.size() >= AntmailValidation.MAX_MAILBOX_MESSAGES || contains(sent, message.id())) return false;
|
||||||
|
sent.add(message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean saveDraft(AntmailDraft draft) {
|
||||||
|
if (!address.equals(draft.sender()) || drafts.size() >= AntmailValidation.MAX_DRAFTS && findDraft(draft.id()) == null) return false;
|
||||||
|
drafts.removeIf(existing -> existing.id().equals(draft.id()));
|
||||||
|
drafts.add(draft);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeDraft(UUID id) { return drafts.removeIf(draft -> draft.id().equals(id)); }
|
||||||
|
public boolean removeInbox(UUID id) { return inbox.removeIf(message -> message.id().equals(id)); }
|
||||||
|
public boolean removeSent(UUID id) { return sent.removeIf(message -> message.id().equals(id)); }
|
||||||
|
|
||||||
|
public boolean markRead(UUID id) {
|
||||||
|
for (AntmailMessage message : inbox) if (message.id().equals(id)) { message.markRead(); return true; }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AntmailDraft findDraft(UUID id) { return drafts.stream().filter(draft -> draft.id().equals(id)).findFirst().orElse(null); }
|
||||||
|
|
||||||
|
public CompoundTag toTag() {
|
||||||
|
CompoundTag tag = new CompoundTag();
|
||||||
|
tag.putString("Address", address.fullAddress());
|
||||||
|
tag.put("Inbox", messagesToTag(inbox));
|
||||||
|
tag.put("Sent", messagesToTag(sent));
|
||||||
|
ListTag draftTags = new ListTag();
|
||||||
|
for (AntmailDraft draft : drafts) draftTags.add(draft.toTag());
|
||||||
|
tag.put("Drafts", draftTags);
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntmailMailbox fromTag(CompoundTag tag) {
|
||||||
|
AntmailMailbox mailbox = new AntmailMailbox(AntmailAddress.parse(tag.getString("Address")));
|
||||||
|
readMessages(tag.getList("Inbox", 10), mailbox.inbox);
|
||||||
|
readMessages(tag.getList("Sent", 10), mailbox.sent);
|
||||||
|
ListTag draftTags = tag.getList("Drafts", 10);
|
||||||
|
for (int index = 0; index < draftTags.size() && mailbox.drafts.size() < AntmailValidation.MAX_DRAFTS; index++) {
|
||||||
|
try { mailbox.drafts.add(AntmailDraft.fromTag(draftTags.getCompound(index))); } catch (RuntimeException ignored) { }
|
||||||
|
}
|
||||||
|
return mailbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ListTag messagesToTag(List<AntmailMessage> messages) {
|
||||||
|
ListTag tags = new ListTag();
|
||||||
|
for (AntmailMessage message : messages) tags.add(message.toTag());
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void readMessages(ListTag tags, List<AntmailMessage> target) {
|
||||||
|
for (int index = 0; index < tags.size() && target.size() < AntmailValidation.MAX_MAILBOX_MESSAGES; index++) {
|
||||||
|
try { target.add(AntmailMessage.fromTag(tags.getCompound(index))); } catch (RuntimeException ignored) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean contains(List<AntmailMessage> messages, UUID id) {
|
||||||
|
return messages.stream().anyMatch(message -> message.id().equals(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
package com.craisinlord.antarchy.content.antmail;
|
||||||
|
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.ListTag;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class AntmailMailboxStore {
|
||||||
|
private final Map<AntmailAddress, AntmailMailbox> mailboxes = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
public AntmailMailbox getOrCreate(AntmailAddress address) {
|
||||||
|
return mailboxes.computeIfAbsent(address, AntmailMailbox::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AntmailMailbox get(AntmailAddress address) {
|
||||||
|
return mailboxes.get(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(AntmailAddress address) {
|
||||||
|
return mailboxes.containsKey(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(AntmailMailbox mailbox) {
|
||||||
|
mailboxes.put(mailbox.address(), mailbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<AntmailMailbox> mailboxes() {
|
||||||
|
return List.copyOf(mailboxes.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompoundTag toTag() {
|
||||||
|
CompoundTag tag = new CompoundTag();
|
||||||
|
ListTag mailboxTags = new ListTag();
|
||||||
|
for (AntmailMailbox mailbox : mailboxes.values()) mailboxTags.add(mailbox.toTag());
|
||||||
|
tag.put("Mailboxes", mailboxTags);
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntmailMailboxStore fromTag(CompoundTag tag) {
|
||||||
|
AntmailMailboxStore store = new AntmailMailboxStore();
|
||||||
|
ListTag mailboxTags = tag.getList("Mailboxes", 10);
|
||||||
|
for (int index = 0; index < mailboxTags.size(); index++) {
|
||||||
|
try {
|
||||||
|
AntmailMailbox mailbox = AntmailMailbox.fromTag(mailboxTags.getCompound(index));
|
||||||
|
store.mailboxes.put(mailbox.address(), mailbox);
|
||||||
|
} catch (RuntimeException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
}
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
package com.craisinlord.antarchy.content.antmail;
|
||||||
|
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.ListTag;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public final class AntmailMessage {
|
||||||
|
private static final String ID_TAG = "Id";
|
||||||
|
private static final String FROM_TAG = "From";
|
||||||
|
private static final String TO_TAG = "To";
|
||||||
|
private static final String SUBJECT_TAG = "Subject";
|
||||||
|
private static final String BODY_TAG = "Body";
|
||||||
|
private static final String CREATED_TAG = "Created";
|
||||||
|
private static final String READ_TAG = "Read";
|
||||||
|
private static final String ATTACHMENTS_TAG = "Attachments";
|
||||||
|
|
||||||
|
private final UUID id;
|
||||||
|
private final AntmailAddress sender;
|
||||||
|
private final AntmailAddress recipient;
|
||||||
|
private final String subject;
|
||||||
|
private final String body;
|
||||||
|
private final long createdAt;
|
||||||
|
private final List<AntmailAttachment> attachments;
|
||||||
|
private boolean read;
|
||||||
|
|
||||||
|
public AntmailMessage(UUID id, AntmailAddress sender, AntmailAddress recipient, String subject, String body, long createdAt, List<AntmailAttachment> attachments, boolean read) {
|
||||||
|
AntmailValidation.Result result = AntmailValidation.validateMessage(sender, recipient, subject, body, attachments);
|
||||||
|
if (!result.valid()) {
|
||||||
|
throw new IllegalArgumentException(result.reason());
|
||||||
|
}
|
||||||
|
this.id = id == null ? UUID.randomUUID() : id;
|
||||||
|
this.sender = sender;
|
||||||
|
this.recipient = recipient;
|
||||||
|
this.subject = subject;
|
||||||
|
this.body = body;
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
this.attachments = List.copyOf(attachments);
|
||||||
|
this.read = read;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntmailMessage create(AntmailAddress sender, AntmailAddress recipient, String subject, String body, long createdAt, List<AntmailAttachment> attachments) {
|
||||||
|
return new AntmailMessage(UUID.randomUUID(), sender, recipient, subject, body, createdAt, attachments, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID id() { return id; }
|
||||||
|
public AntmailAddress sender() { return sender; }
|
||||||
|
public AntmailAddress recipient() { return recipient; }
|
||||||
|
public String subject() { return subject; }
|
||||||
|
public String body() { return body; }
|
||||||
|
public long createdAt() { return createdAt; }
|
||||||
|
public List<AntmailAttachment> attachments() { return attachments; }
|
||||||
|
public boolean read() { return read; }
|
||||||
|
public void markRead() { read = true; }
|
||||||
|
public void markUnread() { read = false; }
|
||||||
|
|
||||||
|
public CompoundTag toTag() {
|
||||||
|
CompoundTag tag = new CompoundTag();
|
||||||
|
tag.putString(ID_TAG, id.toString());
|
||||||
|
tag.putString(FROM_TAG, sender.fullAddress());
|
||||||
|
tag.putString(TO_TAG, recipient.fullAddress());
|
||||||
|
tag.putString(SUBJECT_TAG, subject);
|
||||||
|
tag.putString(BODY_TAG, body);
|
||||||
|
tag.putLong(CREATED_TAG, createdAt);
|
||||||
|
tag.putBoolean(READ_TAG, read);
|
||||||
|
ListTag attachmentTags = new ListTag();
|
||||||
|
for (AntmailAttachment attachment : attachments) attachmentTags.add(attachment.toTag());
|
||||||
|
tag.put(ATTACHMENTS_TAG, attachmentTags);
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntmailMessage fromTag(CompoundTag tag) {
|
||||||
|
List<AntmailAttachment> attachments = new ArrayList<>();
|
||||||
|
ListTag attachmentTags = tag.getList(ATTACHMENTS_TAG, 10);
|
||||||
|
for (int index = 0; index < attachmentTags.size(); index++) attachments.add(AntmailAttachment.fromTag(attachmentTags.getCompound(index)));
|
||||||
|
return new AntmailMessage(UUID.fromString(tag.getString(ID_TAG)), AntmailAddress.parse(tag.getString(FROM_TAG)), AntmailAddress.parse(tag.getString(TO_TAG)), tag.getString(SUBJECT_TAG), tag.getString(BODY_TAG), tag.getLong(CREATED_TAG), attachments, tag.getBoolean(READ_TAG));
|
||||||
|
}
|
||||||
|
}
|
||||||
+174
@@ -0,0 +1,174 @@
|
|||||||
|
package com.craisinlord.antarchy.content.antmail;
|
||||||
|
|
||||||
|
import net.minecraft.core.HolderLookup;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.ListTag;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.level.saveddata.SavedData;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class AntmailServerData extends SavedData {
|
||||||
|
private static final String ID = "antarchy_antmail";
|
||||||
|
private final Map<AntmailAddress, Endpoint> endpoints = new LinkedHashMap<>();
|
||||||
|
private final AntmailMailboxStore mailboxes = new AntmailMailboxStore();
|
||||||
|
private final List<PendingMessage> pending = new ArrayList<>();
|
||||||
|
|
||||||
|
public static AntmailServerData create() {
|
||||||
|
return new AntmailServerData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntmailServerData load(CompoundTag tag, HolderLookup.Provider registries) {
|
||||||
|
AntmailServerData data = new AntmailServerData();
|
||||||
|
ListTag endpointTags = tag.getList("Endpoints", 10);
|
||||||
|
for (int i = 0; i < endpointTags.size(); i++) {
|
||||||
|
try {
|
||||||
|
CompoundTag endpoint = endpointTags.getCompound(i);
|
||||||
|
AntmailAddress address = AntmailAddress.parse(endpoint.getString("Address"));
|
||||||
|
data.endpoints.put(address, new Endpoint(endpoint.getString("Dimension"), endpoint.getLong("Position")));
|
||||||
|
} catch (RuntimeException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AntmailServerData loaded = data;
|
||||||
|
AntmailMailboxStore restored = AntmailMailboxStore.fromTag(tag.getCompound("Mailboxes"));
|
||||||
|
for (AntmailMailbox mailbox : restored.mailboxes()) {
|
||||||
|
loaded.mailboxes.put(mailbox);
|
||||||
|
}
|
||||||
|
ListTag pendingTags = tag.getList("Pending", 10);
|
||||||
|
for (int i = 0; i < pendingTags.size(); i++) {
|
||||||
|
try {
|
||||||
|
CompoundTag pendingTag = pendingTags.getCompound(i);
|
||||||
|
loaded.pending.add(new PendingMessage(AntmailAddress.parse(pendingTag.getString("Recipient")), AntmailMessage.fromTag(pendingTag.getCompound("Message"))));
|
||||||
|
} catch (RuntimeException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) {
|
||||||
|
ListTag endpointTags = new ListTag();
|
||||||
|
for (Map.Entry<AntmailAddress, Endpoint> entry : endpoints.entrySet()) {
|
||||||
|
CompoundTag endpoint = new CompoundTag();
|
||||||
|
endpoint.putString("Address", entry.getKey().fullAddress());
|
||||||
|
endpoint.putString("Dimension", entry.getValue().dimension());
|
||||||
|
endpoint.putLong("Position", entry.getValue().position());
|
||||||
|
endpointTags.add(endpoint);
|
||||||
|
}
|
||||||
|
tag.put("Endpoints", endpointTags);
|
||||||
|
tag.put("Mailboxes", mailboxes.toTag());
|
||||||
|
ListTag pendingTags = new ListTag();
|
||||||
|
for (PendingMessage message : pending) {
|
||||||
|
CompoundTag pendingTag = new CompoundTag();
|
||||||
|
pendingTag.putString("Recipient", message.recipient().fullAddress());
|
||||||
|
pendingTag.put("Message", message.message().toTag());
|
||||||
|
pendingTags.add(pendingTag);
|
||||||
|
}
|
||||||
|
tag.put("Pending", pendingTags);
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AntmailServerData get(MinecraftServer server) {
|
||||||
|
return server.overworld().getDataStorage().computeIfAbsent(
|
||||||
|
new SavedData.Factory<>(AntmailServerData::create, AntmailServerData::load, null), ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntmailServerData access(MinecraftServer server) {
|
||||||
|
return get(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Registration register(MinecraftServer server, ServerLevel level, BlockPos position, String username) {
|
||||||
|
final AntmailAddress address;
|
||||||
|
try {
|
||||||
|
address = AntmailAddress.ofUsername(username);
|
||||||
|
} catch (IllegalArgumentException exception) {
|
||||||
|
return Registration.invalid("invalid_username");
|
||||||
|
}
|
||||||
|
AntmailAddress previous = addressAt(level.dimension().location(), position);
|
||||||
|
Endpoint existing = endpoints.get(address);
|
||||||
|
if (existing != null && (!existing.dimension().equals(level.dimension().location().toString()) || existing.position() != position.asLong())) {
|
||||||
|
return Registration.invalid("username_taken");
|
||||||
|
}
|
||||||
|
if (previous != null && !previous.equals(address)) {
|
||||||
|
endpoints.remove(previous);
|
||||||
|
}
|
||||||
|
endpoints.put(address, new Endpoint(level.dimension().location().toString(), position.asLong()));
|
||||||
|
get(server).drain(server, address);
|
||||||
|
setDirty();
|
||||||
|
return Registration.accepted(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized AntmailAddress addressAt(ResourceLocation dimension, BlockPos position) {
|
||||||
|
for (Map.Entry<AntmailAddress, Endpoint> entry : endpoints.entrySet()) {
|
||||||
|
if (entry.getValue().dimension().equals(dimension.toString()) && entry.getValue().position() == position.asLong()) return entry.getKey();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean unregister(ResourceLocation dimension, BlockPos position) {
|
||||||
|
AntmailAddress address = addressAt(dimension, position);
|
||||||
|
if (address == null) return false;
|
||||||
|
endpoints.remove(address);
|
||||||
|
setDirty();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Endpoint endpoint(AntmailAddress address) {
|
||||||
|
return endpoints.get(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized AntmailMailbox mailbox(AntmailAddress address) {
|
||||||
|
return mailboxes.get(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized AntmailMailbox mailboxOrCreate(AntmailAddress address) {
|
||||||
|
AntmailMailbox mailbox = mailboxes.getOrCreate(address);
|
||||||
|
setDirty();
|
||||||
|
return mailbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized AntmailDeliveryResult deliver(MinecraftServer server, AntmailMessage message) {
|
||||||
|
if (endpoint(message.recipient()) == null) return AntmailDeliveryResult.failed(AntmailDeliveryResult.Status.ADDRESS_NOT_FOUND, message, "address_not_found");
|
||||||
|
AntmailMailbox destination = mailboxes.getOrCreate(message.recipient());
|
||||||
|
if (destination.inbox().size() >= AntmailValidation.MAX_MAILBOX_MESSAGES) return AntmailDeliveryResult.failed(AntmailDeliveryResult.Status.MAILBOX_FULL, message, "mailbox_full");
|
||||||
|
Endpoint target = endpoint(message.recipient());
|
||||||
|
ServerLevel level = server.getLevel(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.DIMENSION, ResourceLocation.parse(target.dimension())));
|
||||||
|
if (level == null || !level.hasChunkAt(BlockPos.of(target.position()))) {
|
||||||
|
pending.add(new PendingMessage(message.recipient(), message));
|
||||||
|
mailboxes.getOrCreate(message.sender()).addSent(message);
|
||||||
|
setDirty();
|
||||||
|
return AntmailDeliveryResult.queued(message);
|
||||||
|
}
|
||||||
|
destination.addIncoming(message);
|
||||||
|
mailboxes.getOrCreate(message.sender()).addSent(message);
|
||||||
|
setDirty();
|
||||||
|
return AntmailDeliveryResult.delivered(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void drain(MinecraftServer server, AntmailAddress address) {
|
||||||
|
AntmailMailbox mailbox = mailboxes.getOrCreate(address);
|
||||||
|
pending.removeIf(pendingMessage -> {
|
||||||
|
if (!pendingMessage.recipient().equals(address) || mailbox.inbox().size() >= AntmailValidation.MAX_MAILBOX_MESSAGES) return false;
|
||||||
|
mailbox.addIncoming(pendingMessage.message());
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
setDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Endpoint(String dimension, long position) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Registration(boolean valid, AntmailAddress address, String reason) {
|
||||||
|
static Registration accepted(AntmailAddress address) { return new Registration(true, address, ""); }
|
||||||
|
static Registration invalid(String reason) { return new Registration(false, null, reason); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private record PendingMessage(AntmailAddress recipient, AntmailMessage message) {
|
||||||
|
}
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
package com.craisinlord.antarchy.content.antmail;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class AntmailValidation {
|
||||||
|
public static final int MAX_SUBJECT_LENGTH = 64;
|
||||||
|
public static final int MAX_BODY_LENGTH = 4096;
|
||||||
|
public static final int MAX_FILE_NAME_LENGTH = 32;
|
||||||
|
public static final int MAX_ATTACHMENTS = 8;
|
||||||
|
public static final int MAX_TEXT_ATTACHMENT_CHARACTERS = 16384;
|
||||||
|
public static final int MAX_PAINT_WIDTH = 32;
|
||||||
|
public static final int MAX_PAINT_HEIGHT = 24;
|
||||||
|
public static final int MAX_MESSAGE_BYTES = 65536;
|
||||||
|
public static final int MAX_ATTACHMENT_WIRE_CHARS = 65536;
|
||||||
|
public static final int MAX_MAILBOX_MESSAGES = 100;
|
||||||
|
public static final int MAX_DRAFTS = 25;
|
||||||
|
|
||||||
|
private AntmailValidation() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result validateMessage(AntmailAddress sender, AntmailAddress recipient, String subject, String body, List<AntmailAttachment> attachments) {
|
||||||
|
if (sender == null || recipient == null) {
|
||||||
|
return Result.invalid("missing address");
|
||||||
|
}
|
||||||
|
if (subject == null || subject.length() > MAX_SUBJECT_LENGTH) {
|
||||||
|
return Result.invalid("subject too long");
|
||||||
|
}
|
||||||
|
if (body == null || body.length() > MAX_BODY_LENGTH) {
|
||||||
|
return Result.invalid("body too long");
|
||||||
|
}
|
||||||
|
Result attachmentResult = validateAttachments(attachments);
|
||||||
|
if (!attachmentResult.valid()) return attachmentResult;
|
||||||
|
int bytes = subject.getBytes(StandardCharsets.UTF_8).length + body.getBytes(StandardCharsets.UTF_8).length;
|
||||||
|
for (AntmailAttachment attachment : attachments) bytes += attachment.byteSize();
|
||||||
|
if (bytes > MAX_MESSAGE_BYTES) {
|
||||||
|
return Result.invalid("message too large");
|
||||||
|
}
|
||||||
|
return Result.accepted();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result validateAttachments(List<AntmailAttachment> attachments) {
|
||||||
|
if (attachments == null || attachments.size() > MAX_ATTACHMENTS) return Result.invalid("too many attachments");
|
||||||
|
int textCharacters = 0;
|
||||||
|
for (AntmailAttachment attachment : attachments) {
|
||||||
|
if (attachment == null) return Result.invalid("null attachment");
|
||||||
|
if (attachment instanceof AntmailAttachment.TextFile textFile) {
|
||||||
|
if (textFile.contents().length() > MAX_TEXT_ATTACHMENT_CHARACTERS) return Result.invalid("text attachment too large");
|
||||||
|
textCharacters += textFile.contents().length();
|
||||||
|
} else if (attachment instanceof AntmailAttachment.PaintImage paintImage) {
|
||||||
|
if (paintImage.width() != MAX_PAINT_WIDTH || paintImage.height() != MAX_PAINT_HEIGHT) return Result.invalid("unsupported paint size");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (textCharacters > MAX_TEXT_ATTACHMENT_CHARACTERS) return Result.invalid("text attachments too large");
|
||||||
|
return Result.accepted();
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Result(boolean valid, String reason) {
|
||||||
|
public static Result accepted() {
|
||||||
|
return new Result(true, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result invalid(String reason) {
|
||||||
|
return new Result(false, reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.craisinlord.antarchy.content.antmail;
|
||||||
|
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.ListTag;
|
||||||
|
import net.minecraft.nbt.TagParser;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class AntmailWire {
|
||||||
|
private AntmailWire() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encodeAttachments(List<AntmailAttachment> attachments) {
|
||||||
|
AntmailValidation.Result validation = AntmailValidation.validateAttachments(attachments);
|
||||||
|
if (!validation.valid()) throw new IllegalArgumentException(validation.reason());
|
||||||
|
CompoundTag root = new CompoundTag();
|
||||||
|
ListTag list = new ListTag();
|
||||||
|
for (AntmailAttachment attachment : attachments) list.add(attachment.toTag());
|
||||||
|
root.put("Attachments", list);
|
||||||
|
String encoded = Base64.getEncoder().encodeToString(root.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
||||||
|
if (encoded.length() > AntmailValidation.MAX_ATTACHMENT_WIRE_CHARS) throw new IllegalArgumentException("Attachment payload is too large");
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encodeTag(CompoundTag tag) {
|
||||||
|
return Base64.getEncoder().encodeToString(tag.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CompoundTag decodeTag(String encoded) {
|
||||||
|
try {
|
||||||
|
return TagParser.parseTag(new String(Base64.getDecoder().decode(encoded), java.nio.charset.StandardCharsets.UTF_8));
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new IllegalArgumentException("Invalid Antmail data", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<AntmailAttachment> decodeAttachments(String encoded) {
|
||||||
|
if (encoded == null || encoded.isBlank()) return List.of();
|
||||||
|
if (encoded.length() > AntmailValidation.MAX_ATTACHMENT_WIRE_CHARS) throw new IllegalArgumentException("Attachment payload is too large");
|
||||||
|
try {
|
||||||
|
String snbt = new String(Base64.getDecoder().decode(encoded), java.nio.charset.StandardCharsets.UTF_8);
|
||||||
|
CompoundTag root = TagParser.parseTag(snbt);
|
||||||
|
ListTag list = root.getList("Attachments", 10);
|
||||||
|
List<AntmailAttachment> result = new ArrayList<>();
|
||||||
|
for (int i = 0; i < list.size(); i++) result.add(AntmailAttachment.fromTag(list.getCompound(i)));
|
||||||
|
AntmailValidation.Result validation = AntmailValidation.validateAttachments(result);
|
||||||
|
if (!validation.valid()) throw new IllegalArgumentException(validation.reason());
|
||||||
|
return List.copyOf(result);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new IllegalArgumentException("Invalid attachment data", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
-8
@@ -5,6 +5,7 @@ import com.craisinlord.antarchy.content.block.entity.ComputerBlockEntity;
|
|||||||
import com.craisinlord.antarchy.content.client.AntarchyClientHooks;
|
import com.craisinlord.antarchy.content.client.AntarchyClientHooks;
|
||||||
import com.mojang.serialization.MapCodec;
|
import com.mojang.serialization.MapCodec;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
import net.minecraft.world.InteractionHand;
|
import net.minecraft.world.InteractionHand;
|
||||||
import net.minecraft.world.ItemInteractionResult;
|
import net.minecraft.world.ItemInteractionResult;
|
||||||
import net.minecraft.world.entity.player.Player;
|
import net.minecraft.world.entity.player.Player;
|
||||||
@@ -13,7 +14,9 @@ import net.minecraft.world.level.BlockGetter;
|
|||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.block.BaseEntityBlock;
|
import net.minecraft.world.level.block.BaseEntityBlock;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.Mirror;
|
||||||
import net.minecraft.world.level.block.RenderShape;
|
import net.minecraft.world.level.block.RenderShape;
|
||||||
|
import net.minecraft.world.level.block.Rotation;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||||
@@ -21,6 +24,8 @@ import net.minecraft.world.level.block.state.BlockBehaviour;
|
|||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.level.block.state.StateDefinition;
|
import net.minecraft.world.level.block.state.StateDefinition;
|
||||||
import net.minecraft.world.level.block.state.properties.BooleanProperty;
|
import net.minecraft.world.level.block.state.properties.BooleanProperty;
|
||||||
|
import net.minecraft.world.level.block.state.properties.DirectionProperty;
|
||||||
|
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||||
import net.minecraft.world.phys.BlockHitResult;
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||||
@@ -28,11 +33,12 @@ import net.minecraft.world.phys.shapes.VoxelShape;
|
|||||||
public final class ComputerBlock extends BaseEntityBlock {
|
public final class ComputerBlock extends BaseEntityBlock {
|
||||||
public static final MapCodec<ComputerBlock> CODEC = Block.simpleCodec(ComputerBlock::new);
|
public static final MapCodec<ComputerBlock> CODEC = Block.simpleCodec(ComputerBlock::new);
|
||||||
public static final BooleanProperty ACTIVE = BooleanProperty.create("active");
|
public static final BooleanProperty ACTIVE = BooleanProperty.create("active");
|
||||||
|
public static final DirectionProperty FACING = net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING;
|
||||||
private static final VoxelShape SHAPE = Block.box(1.0D, 0.0D, 1.0D, 15.0D, 16.0D, 15.0D);
|
private static final VoxelShape SHAPE = Block.box(1.0D, 0.0D, 1.0D, 15.0D, 16.0D, 15.0D);
|
||||||
|
|
||||||
public ComputerBlock(BlockBehaviour.Properties properties) {
|
public ComputerBlock(BlockBehaviour.Properties properties) {
|
||||||
super(properties.lightLevel(state -> state.getValue(ACTIVE) ? 7 : 0));
|
super(properties.lightLevel(state -> state.getValue(ACTIVE) ? 7 : 0));
|
||||||
registerDefaultState(stateDefinition.any().setValue(ACTIVE, false));
|
registerDefaultState(stateDefinition.any().setValue(ACTIVE, false).setValue(FACING, Direction.NORTH));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -42,7 +48,22 @@ public final class ComputerBlock extends BaseEntityBlock {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
||||||
builder.add(ACTIVE);
|
builder.add(ACTIVE, FACING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockState getStateForPlacement(BlockPlaceContext context) {
|
||||||
|
return defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BlockState rotate(BlockState state, Rotation rotation) {
|
||||||
|
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BlockState mirror(BlockState state, Mirror mirror) {
|
||||||
|
return state.rotate(mirror.getRotation(state.getValue(FACING)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -55,6 +76,25 @@ public final class ComputerBlock extends BaseEntityBlock {
|
|||||||
return SHAPE;
|
return SHAPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSignalSource(BlockState state) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getSignal(BlockState state, BlockGetter level, BlockPos pos, Direction direction) {
|
||||||
|
return isAuthenticated(level, pos) ? 15 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getDirectSignal(BlockState state, BlockGetter level, BlockPos pos, Direction direction) {
|
||||||
|
return getSignal(state, level, pos, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAuthenticated(BlockGetter level, BlockPos pos) {
|
||||||
|
return level.getBlockEntity(pos) instanceof ComputerBlockEntity computer && computer.isAuthenticated();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
|
protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
|
||||||
if (!state.is(newState.getBlock()) && level.getBlockEntity(pos) instanceof ComputerBlockEntity computer) {
|
if (!state.is(newState.getBlock()) && level.getBlockEntity(pos) instanceof ComputerBlockEntity computer) {
|
||||||
@@ -92,12 +132,6 @@ public final class ComputerBlock extends BaseEntityBlock {
|
|||||||
protected net.minecraft.world.InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player,
|
protected net.minecraft.world.InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player,
|
||||||
BlockHitResult hitResult) {
|
BlockHitResult hitResult) {
|
||||||
if (level.getBlockEntity(pos) instanceof ComputerBlockEntity computer) {
|
if (level.getBlockEntity(pos) instanceof ComputerBlockEntity computer) {
|
||||||
if (player.isShiftKeyDown()) {
|
|
||||||
if (computer.ejectOne(player)) {
|
|
||||||
return net.minecraft.world.InteractionResult.SUCCESS;
|
|
||||||
}
|
|
||||||
return net.minecraft.world.InteractionResult.PASS;
|
|
||||||
}
|
|
||||||
computer.activate();
|
computer.activate();
|
||||||
if (level.isClientSide) {
|
if (level.isClientSide) {
|
||||||
AntarchyClientHooks.openComputer(pos);
|
AntarchyClientHooks.openComputer(pos);
|
||||||
|
|||||||
+203
-20
@@ -2,6 +2,8 @@ package com.craisinlord.antarchy.content.block.entity;
|
|||||||
|
|
||||||
import com.craisinlord.antarchy.content.AntarchyObjects;
|
import com.craisinlord.antarchy.content.AntarchyObjects;
|
||||||
import com.craisinlord.antarchy.content.block.ComputerBlock;
|
import com.craisinlord.antarchy.content.block.ComputerBlock;
|
||||||
|
import com.craisinlord.antarchy.content.computer.ComputerFileSystem;
|
||||||
|
import com.craisinlord.antarchy.content.computer.ComputerDesktopState;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.HolderLookup;
|
import net.minecraft.core.HolderLookup;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
@@ -10,12 +12,12 @@ import net.minecraft.nbt.StringTag;
|
|||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.Containers;
|
import net.minecraft.world.Containers;
|
||||||
import net.minecraft.world.entity.player.Player;
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.phys.Vec3;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
@@ -31,15 +33,27 @@ import software.bernie.geckolib.animation.PlayState;
|
|||||||
import software.bernie.geckolib.util.GeckoLibUtil;
|
import software.bernie.geckolib.util.GeckoLibUtil;
|
||||||
|
|
||||||
public final class ComputerBlockEntity extends BlockEntity implements GeoBlockEntity {
|
public final class ComputerBlockEntity extends BlockEntity implements GeoBlockEntity {
|
||||||
|
private static final ResourceLocation INTRODUCTION_DISK = ResourceLocation.fromNamespaceAndPath("antarchy", "introduction");
|
||||||
private static final String DISKS_TAG = "Disks";
|
private static final String DISKS_TAG = "Disks";
|
||||||
private static final String ACTIVE_UNTIL_TAG = "ActiveUntil";
|
private static final String ACTIVE_UNTIL_TAG = "ActiveUntil";
|
||||||
private final Set<ResourceLocation> diskIds = new LinkedHashSet<>();
|
private static final String PASSWORD_TAG = "Password";
|
||||||
|
private static final String AUTHENTICATED_TAG = "Authenticated";
|
||||||
|
private static final String AUTHENTICATED_UNTIL_TAG = "AuthenticatedUntil";
|
||||||
|
private static final String DESKTOP_TAG = "Desktop";
|
||||||
|
private static final long AUTHENTICATION_TIMEOUT = 6000L;
|
||||||
|
private final Set<ResourceLocation> physicalDiskIds = new LinkedHashSet<>();
|
||||||
|
private final ComputerFileSystem fileSystem = new ComputerFileSystem();
|
||||||
|
private final ComputerDesktopState desktopState = new ComputerDesktopState();
|
||||||
private final AnimatableInstanceCache animatableCache = GeckoLibUtil.createInstanceCache(this);
|
private final AnimatableInstanceCache animatableCache = GeckoLibUtil.createInstanceCache(this);
|
||||||
private static final RawAnimation ON = RawAnimation.begin().thenLoop("on_state");
|
private static final RawAnimation ON = RawAnimation.begin().thenLoop("on_state");
|
||||||
private static final RawAnimation OFF = RawAnimation.begin().thenLoop("off_state");
|
private static final RawAnimation OFF = RawAnimation.begin().thenLoop("off_state");
|
||||||
private static final RawAnimation TURN_ON = RawAnimation.begin().thenPlay("turn_on");
|
private static final RawAnimation TURN_ON = RawAnimation.begin().thenPlay("turn_on");
|
||||||
private static final RawAnimation TURN_OFF = RawAnimation.begin().thenPlay("turn_off");
|
private static final RawAnimation TURN_OFF = RawAnimation.begin().thenPlay("turn_off");
|
||||||
private long activeUntil;
|
private long activeUntil;
|
||||||
|
private String password = "";
|
||||||
|
private boolean authenticated;
|
||||||
|
private long authenticatedUntil;
|
||||||
|
private java.util.UUID activeUser;
|
||||||
private boolean lastActive;
|
private boolean lastActive;
|
||||||
|
|
||||||
public ComputerBlockEntity(BlockPos pos, BlockState state, Supplier<? extends BlockEntityType<ComputerBlockEntity>> type) {
|
public ComputerBlockEntity(BlockPos pos, BlockState state, Supplier<? extends BlockEntityType<ComputerBlockEntity>> type) {
|
||||||
@@ -60,6 +74,11 @@ public final class ComputerBlockEntity extends BlockEntity implements GeoBlockEn
|
|||||||
computer.setChanged();
|
computer.setChanged();
|
||||||
level.sendBlockUpdated(pos, state, state, 3);
|
level.sendBlockUpdated(pos, state, state, 3);
|
||||||
}
|
}
|
||||||
|
if (!level.isClientSide && computer.authenticated && (level.getGameTime() >= computer.authenticatedUntil || computer.activeUser == null ||
|
||||||
|
(level instanceof net.minecraft.server.level.ServerLevel serverLevel && serverLevel.getServer().getPlayerList().getPlayer(computer.activeUser) == null))) {
|
||||||
|
computer.clearAuthentication();
|
||||||
|
level.sendBlockUpdated(pos, state, state, 3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean insert(ItemStack stack, Player player) {
|
public boolean insert(ItemStack stack, Player player) {
|
||||||
@@ -67,12 +86,12 @@ public final class ComputerBlockEntity extends BlockEntity implements GeoBlockEn
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ResourceLocation diskId = stack.get(AntarchyObjects.FLOPPY_DISK_COMPONENT.get());
|
ResourceLocation diskId = stack.get(AntarchyObjects.FLOPPY_DISK_COMPONENT.get());
|
||||||
if (diskId == null || this.diskIds.contains(diskId)) {
|
if (diskId == null || this.physicalDiskIds.contains(diskId)) {
|
||||||
stack.shrink(1);
|
stack.shrink(1);
|
||||||
activate();
|
activate();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
this.diskIds.add(diskId);
|
this.physicalDiskIds.add(diskId);
|
||||||
stack.shrink(1);
|
stack.shrink(1);
|
||||||
activate();
|
activate();
|
||||||
setChanged();
|
setChanged();
|
||||||
@@ -94,12 +113,7 @@ public final class ComputerBlockEntity extends BlockEntity implements GeoBlockEn
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
return level != null && (level.hasNeighborSignal(worldPosition) || level.getGameTime() < activeUntil || hasNearbyPlayer());
|
return level != null && (level.hasNeighborSignal(worldPosition) || isAuthenticated());
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasNearbyPlayer() {
|
|
||||||
Vec3 center = Vec3.atCenterOf(worldPosition);
|
|
||||||
return level.players().stream().anyMatch(player -> player.distanceToSqr(center) <= 25.0D);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -116,15 +130,23 @@ public final class ComputerBlockEntity extends BlockEntity implements GeoBlockEn
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<ResourceLocation> diskIds() {
|
public List<ResourceLocation> diskIds() {
|
||||||
return List.copyOf(diskIds);
|
List<ResourceLocation> ids = new ArrayList<>(physicalDiskIds.size() + 1);
|
||||||
|
ids.add(INTRODUCTION_DISK);
|
||||||
|
ids.addAll(physicalDiskIds);
|
||||||
|
return List.copyOf(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean ejectOne(Player player) {
|
public boolean ejectOne(Player player) {
|
||||||
if (level == null || level.isClientSide || diskIds.isEmpty()) {
|
if (physicalDiskIds.isEmpty() || !(player instanceof ServerPlayer serverPlayer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ejectOne(serverPlayer, physicalDiskIds.iterator().next());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean ejectOne(ServerPlayer player, ResourceLocation diskId) {
|
||||||
|
if (level == null || level.isClientSide || diskId == null || !isAuthenticated() || !playerCanReach(player) || !physicalDiskIds.remove(diskId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ResourceLocation diskId = diskIds.iterator().next();
|
|
||||||
diskIds.remove(diskId);
|
|
||||||
ItemStack disk = diskStack(diskId);
|
ItemStack disk = diskStack(diskId);
|
||||||
if (!player.addItem(disk)) {
|
if (!player.addItem(disk)) {
|
||||||
player.drop(disk, false);
|
player.drop(disk, false);
|
||||||
@@ -138,10 +160,158 @@ public final class ComputerBlockEntity extends BlockEntity implements GeoBlockEn
|
|||||||
if (level.isClientSide) {
|
if (level.isClientSide) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (ResourceLocation diskId : diskIds) {
|
for (ResourceLocation diskId : physicalDiskIds) {
|
||||||
Containers.dropItemStack(level, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, diskStack(diskId));
|
Containers.dropItemStack(level, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, diskStack(diskId));
|
||||||
}
|
}
|
||||||
diskIds.clear();
|
physicalDiskIds.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPassword() {
|
||||||
|
return !password.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setPassword(ServerPlayer player, String newPassword) {
|
||||||
|
if (newPassword == null || newPassword.isBlank() || !isAuthenticated()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
password = newPassword;
|
||||||
|
setChanged();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean initializePassword(ServerPlayer player, String newPassword) {
|
||||||
|
if (hasPassword() || newPassword == null || newPassword.isBlank() || !claimUser(player)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
password = newPassword;
|
||||||
|
authenticated = true;
|
||||||
|
authenticatedUntil = level.getGameTime() + AUTHENTICATION_TIMEOUT;
|
||||||
|
setChanged();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean initializePassword(String newPassword) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean authenticate(ServerPlayer player, String attemptedPassword) {
|
||||||
|
if (!hasPassword() || !password.equals(attemptedPassword) || !claimUser(player)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
authenticated = true;
|
||||||
|
authenticatedUntil = level == null ? 0L : level.getGameTime() + AUTHENTICATION_TIMEOUT;
|
||||||
|
setChanged();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean authenticate(String attemptedPassword) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAuthenticated() {
|
||||||
|
if (!authenticated) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (level != null && level.getGameTime() >= authenticatedUntil) {
|
||||||
|
authenticated = false;
|
||||||
|
authenticatedUntil = 0L;
|
||||||
|
setChanged();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAuthenticatedBy(ServerPlayer player) {
|
||||||
|
return isAuthenticated() && activeUser != null && activeUser.equals(player.getUUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean claimUser(ServerPlayer player) {
|
||||||
|
if (level == null || level.isClientSide || !playerCanReach(player)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (activeUser != null && !activeUser.equals(player.getUUID()) && isAuthenticated()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
activeUser = player.getUUID();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean playerCanReach(ServerPlayer player) {
|
||||||
|
return player.level() == level && player.distanceToSqr(worldPosition.getX() + 0.5D, worldPosition.getY() + 0.5D, worldPosition.getZ() + 0.5D) <= 64.0D;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComputerFileSystem fileSystem() {
|
||||||
|
return fileSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComputerDesktopState desktopState() {
|
||||||
|
return desktopState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean unlockWallpaper(ResourceLocation id) {
|
||||||
|
return serverDesktopMutation(() -> desktopState.unlockWallpaper(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean installProgram(ResourceLocation id) {
|
||||||
|
return serverDesktopMutation(() -> desktopState.installProgram(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean uninstallProgram(ResourceLocation id) {
|
||||||
|
return serverDesktopMutation(() -> desktopState.uninstallProgram(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean selectWallpaper(ResourceLocation id) {
|
||||||
|
return serverDesktopMutation(() -> desktopState.selectWallpaper(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setIconPosition(ResourceLocation id, int x, int y) {
|
||||||
|
return serverDesktopMutation(() -> desktopState.setIconPosition(id, x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeIconPosition(ResourceLocation id) {
|
||||||
|
return serverDesktopMutation(() -> desktopState.removeIconPosition(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean serverDesktopMutation(java.util.function.BooleanSupplier mutation) {
|
||||||
|
if (level == null || level.isClientSide) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean changed = mutation.getAsBoolean();
|
||||||
|
if (changed) {
|
||||||
|
setChanged();
|
||||||
|
level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), 3);
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canUseFileSystem(ServerPlayer player) {
|
||||||
|
return level != null && !level.isClientSide && isAuthenticated() && playerCanReach(player) && (activeUser == null || activeUser.equals(player.getUUID()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void logout() {
|
||||||
|
clearAuthentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void releaseUser(ServerPlayer player) {
|
||||||
|
if (activeUser != null && activeUser.equals(player.getUUID())) {
|
||||||
|
activeUser = null;
|
||||||
|
setChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearAuthentication() {
|
||||||
|
authenticated = false;
|
||||||
|
authenticatedUntil = 0L;
|
||||||
|
activeUser = null;
|
||||||
|
setChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long authenticationTimeout() {
|
||||||
|
return AUTHENTICATION_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasActiveUser() {
|
||||||
|
return activeUser != null && isAuthenticated();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ItemStack diskStack(ResourceLocation diskId) {
|
private static ItemStack diskStack(ResourceLocation diskId) {
|
||||||
@@ -153,32 +323,45 @@ public final class ComputerBlockEntity extends BlockEntity implements GeoBlockEn
|
|||||||
@Override
|
@Override
|
||||||
protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
||||||
super.loadAdditional(tag, registries);
|
super.loadAdditional(tag, registries);
|
||||||
diskIds.clear();
|
physicalDiskIds.clear();
|
||||||
ListTag disks = tag.getList(DISKS_TAG, 8);
|
ListTag disks = tag.getList(DISKS_TAG, 8);
|
||||||
for (int index = 0; index < disks.size(); index++) {
|
for (int index = 0; index < disks.size(); index++) {
|
||||||
try {
|
try {
|
||||||
diskIds.add(ResourceLocation.parse(disks.getString(index)));
|
ResourceLocation diskId = ResourceLocation.parse(disks.getString(index));
|
||||||
|
if (!INTRODUCTION_DISK.equals(diskId)) {
|
||||||
|
physicalDiskIds.add(diskId);
|
||||||
|
}
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
activeUntil = tag.getLong(ACTIVE_UNTIL_TAG);
|
activeUntil = tag.getLong(ACTIVE_UNTIL_TAG);
|
||||||
|
password = tag.getString(PASSWORD_TAG);
|
||||||
|
fileSystem.load(tag, registries);
|
||||||
|
desktopState.load(tag.getCompound(DESKTOP_TAG));
|
||||||
|
authenticated = false;
|
||||||
|
authenticatedUntil = 0L;
|
||||||
|
activeUser = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
||||||
super.saveAdditional(tag, registries);
|
super.saveAdditional(tag, registries);
|
||||||
ListTag disks = new ListTag();
|
ListTag disks = new ListTag();
|
||||||
for (ResourceLocation diskId : diskIds) {
|
for (ResourceLocation diskId : physicalDiskIds) {
|
||||||
disks.add(StringTag.valueOf(diskId.toString()));
|
disks.add(StringTag.valueOf(diskId.toString()));
|
||||||
}
|
}
|
||||||
tag.put(DISKS_TAG, disks);
|
tag.put(DISKS_TAG, disks);
|
||||||
tag.putLong(ACTIVE_UNTIL_TAG, activeUntil);
|
tag.putLong(ACTIVE_UNTIL_TAG, activeUntil);
|
||||||
|
tag.putString(PASSWORD_TAG, password);
|
||||||
|
fileSystem.save(tag, registries);
|
||||||
|
tag.put(DESKTOP_TAG, desktopState.save());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
|
public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
|
||||||
CompoundTag tag = new CompoundTag();
|
CompoundTag tag = new CompoundTag();
|
||||||
saveAdditional(tag, registries);
|
tag.putBoolean("HasPassword", hasPassword());
|
||||||
|
tag.putBoolean(AUTHENTICATED_TAG, isAuthenticated());
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package com.craisinlord.antarchy.content.client;
|
||||||
|
|
||||||
|
import com.craisinlord.antarchy.content.network.AntmailResultPayload;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public final class AntmailClientState {
|
||||||
|
private static final Map<BlockPos, AntmailResultPayload> RESULTS = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private AntmailClientState() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void update(AntmailResultPayload payload) {
|
||||||
|
RESULTS.put(payload.pos(), payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntmailResultPayload get(BlockPos pos) {
|
||||||
|
return RESULTS.get(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package com.craisinlord.antarchy.content.client;
|
||||||
|
|
||||||
|
import com.craisinlord.antarchy.content.network.ComputerAccessResultPayload;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public final class ComputerAccessClientState {
|
||||||
|
private static final Map<BlockPos, ComputerAccessResultPayload> RESULTS = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private ComputerAccessClientState() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void update(ComputerAccessResultPayload result) {
|
||||||
|
RESULTS.put(result.pos(), result);
|
||||||
|
ComputerFileSystemClientState.update(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ComputerAccessResultPayload get(BlockPos pos) {
|
||||||
|
return RESULTS.get(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
package com.craisinlord.antarchy.content.client;
|
||||||
|
|
||||||
|
import com.craisinlord.antarchy.content.network.ComputerAccessPayload;
|
||||||
|
import com.craisinlord.antarchy.content.network.ComputerAccessResultPayload;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public final class ComputerFileSystemClientState {
|
||||||
|
private static final Map<BlockPos, State> STATES = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private ComputerFileSystemClientState() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void update(ComputerAccessResultPayload payload) {
|
||||||
|
if (payload.data().isEmpty()) return;
|
||||||
|
String[] envelope = payload.data().split("\u0000", 3);
|
||||||
|
if (envelope.length < 3) return;
|
||||||
|
int action;
|
||||||
|
try {
|
||||||
|
action = Integer.parseInt(envelope[0]);
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
State state = STATES.computeIfAbsent(payload.pos(), ignored -> new State());
|
||||||
|
state.success = payload.result() == ComputerAccessResultPayload.SUCCESS;
|
||||||
|
state.error = envelope[1];
|
||||||
|
if (action == ComputerAccessPayload.FILE_LIST && state.success) {
|
||||||
|
state.files = new ArrayList<>();
|
||||||
|
if (!envelope[2].isEmpty()) state.files.addAll(List.of(envelope[2].split("\\n")));
|
||||||
|
} else if (action == ComputerAccessPayload.FILE_OPEN && state.success) {
|
||||||
|
String[] file = envelope[2].split("\u0000", 2);
|
||||||
|
if (file.length == 2) {
|
||||||
|
state.openedPath = file[0];
|
||||||
|
state.openedContents = file[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static State get(BlockPos pos) {
|
||||||
|
return STATES.computeIfAbsent(pos, ignored -> new State());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class State {
|
||||||
|
private List<String> files = List.of();
|
||||||
|
private String openedPath = "";
|
||||||
|
private String openedContents = "";
|
||||||
|
private String error = "";
|
||||||
|
private boolean success;
|
||||||
|
|
||||||
|
public List<String> files() { return List.copyOf(files); }
|
||||||
|
public String openedPath() { return openedPath; }
|
||||||
|
public String openedContents() { return openedContents; }
|
||||||
|
public String error() { return error; }
|
||||||
|
public boolean success() { return success; }
|
||||||
|
}
|
||||||
|
}
|
||||||
+168
@@ -0,0 +1,168 @@
|
|||||||
|
package com.craisinlord.antarchy.content.client.game;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class AntFarmGame {
|
||||||
|
public enum Phase { READY, PLAYING, WON, LOST }
|
||||||
|
public enum Direction {
|
||||||
|
UP(0, -1), DOWN(0, 1), LEFT(-1, 0), RIGHT(1, 0), NONE(0, 0);
|
||||||
|
|
||||||
|
private final int dx;
|
||||||
|
private final int dy;
|
||||||
|
|
||||||
|
Direction(int dx, int dy) {
|
||||||
|
this.dx = dx;
|
||||||
|
this.dy = dy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String[] MAP = {
|
||||||
|
"###################",
|
||||||
|
"#.................#",
|
||||||
|
"#.###.#####.#####.#",
|
||||||
|
"#.................#",
|
||||||
|
"#.#####.###.#####.#",
|
||||||
|
"#.................#",
|
||||||
|
"#.#####.###.#####.#",
|
||||||
|
"#.................#",
|
||||||
|
"#.#####.###.#####.#",
|
||||||
|
"#.................#",
|
||||||
|
"###################"
|
||||||
|
};
|
||||||
|
private static final int TICKS_PER_TILE = 4;
|
||||||
|
private static final int PLAYER_START_X = 1;
|
||||||
|
private static final int PLAYER_START_Y = 1;
|
||||||
|
private static final int ENEMY_START_X = 17;
|
||||||
|
private static final int ENEMY_START_Y = 9;
|
||||||
|
|
||||||
|
private final boolean[][] pellets = new boolean[MAP.length][MAP[0].length()];
|
||||||
|
private Phase phase = Phase.READY;
|
||||||
|
private Direction playerDirection = Direction.NONE;
|
||||||
|
private Direction requestedDirection = Direction.NONE;
|
||||||
|
private Direction enemyDirection = Direction.LEFT;
|
||||||
|
private int playerX;
|
||||||
|
private int playerY;
|
||||||
|
private int enemyX;
|
||||||
|
private int enemyY;
|
||||||
|
private int movementTicks;
|
||||||
|
private int score;
|
||||||
|
private int remainingFood;
|
||||||
|
|
||||||
|
public AntFarmGame() {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
phase = Phase.READY;
|
||||||
|
playerDirection = Direction.NONE;
|
||||||
|
requestedDirection = Direction.NONE;
|
||||||
|
enemyDirection = Direction.LEFT;
|
||||||
|
playerX = PLAYER_START_X;
|
||||||
|
playerY = PLAYER_START_Y;
|
||||||
|
enemyX = ENEMY_START_X;
|
||||||
|
enemyY = ENEMY_START_Y;
|
||||||
|
movementTicks = 0;
|
||||||
|
score = 0;
|
||||||
|
remainingFood = 0;
|
||||||
|
for (int y = 0; y < MAP.length; y++) {
|
||||||
|
for (int x = 0; x < MAP[y].length(); x++) {
|
||||||
|
pellets[y][x] = MAP[y].charAt(x) == '.';
|
||||||
|
if (pellets[y][x]) remainingFood++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
if (phase == Phase.READY) phase = Phase.PLAYING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tick() {
|
||||||
|
if (phase != Phase.PLAYING) return;
|
||||||
|
movementTicks++;
|
||||||
|
if (movementTicks < TICKS_PER_TILE) return;
|
||||||
|
movementTicks = 0;
|
||||||
|
movePlayer();
|
||||||
|
if (phase != Phase.PLAYING) return;
|
||||||
|
moveEnemy();
|
||||||
|
if (playerX == enemyX && playerY == enemyY) phase = Phase.LOST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequestedDirection(Direction direction) {
|
||||||
|
if (direction != null && direction != Direction.NONE) requestedDirection = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Phase phase() { return phase; }
|
||||||
|
public int score() { return score; }
|
||||||
|
public int remainingFood() { return remainingFood; }
|
||||||
|
public int width() { return MAP[0].length(); }
|
||||||
|
public int height() { return MAP.length; }
|
||||||
|
public int playerX() { return playerX; }
|
||||||
|
public int playerY() { return playerY; }
|
||||||
|
public int enemyX() { return enemyX; }
|
||||||
|
public int enemyY() { return enemyY; }
|
||||||
|
public boolean hasPellet(int x, int y) { return pellets[y][x]; }
|
||||||
|
public boolean isWall(int x, int y) { return MAP[y].charAt(x) == '#'; }
|
||||||
|
|
||||||
|
private void movePlayer() {
|
||||||
|
if (canMove(playerX, playerY, requestedDirection)) playerDirection = requestedDirection;
|
||||||
|
if (canMove(playerX, playerY, playerDirection)) {
|
||||||
|
playerX += playerDirection.dx;
|
||||||
|
playerY += playerDirection.dy;
|
||||||
|
}
|
||||||
|
if (pellets[playerY][playerX]) {
|
||||||
|
pellets[playerY][playerX] = false;
|
||||||
|
remainingFood--;
|
||||||
|
score += 10;
|
||||||
|
if (remainingFood == 0) phase = Phase.WON;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveEnemy() {
|
||||||
|
List<Direction> directions = legalDirections(enemyX, enemyY);
|
||||||
|
if (directions.isEmpty()) return;
|
||||||
|
Direction reverse = opposite(enemyDirection);
|
||||||
|
if (directions.size() > 1) directions.remove(reverse);
|
||||||
|
Direction best = directions.get(0);
|
||||||
|
int bestDistance = distanceAfter(best, playerX, playerY, enemyX, enemyY);
|
||||||
|
for (Direction direction : directions) {
|
||||||
|
int distance = distanceAfter(direction, playerX, playerY, enemyX, enemyY);
|
||||||
|
if (distance < bestDistance) {
|
||||||
|
best = direction;
|
||||||
|
bestDistance = distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enemyDirection = best;
|
||||||
|
enemyX += best.dx;
|
||||||
|
enemyY += best.dy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int distanceAfter(Direction direction, int targetX, int targetY, int originX, int originY) {
|
||||||
|
return Math.abs(originX + direction.dx - targetX) + Math.abs(originY + direction.dy - targetY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Direction> legalDirections(int x, int y) {
|
||||||
|
List<Direction> result = new ArrayList<>(4);
|
||||||
|
for (Direction direction : new Direction[]{Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT}) {
|
||||||
|
if (canMove(x, y, direction)) result.add(direction);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean canMove(int x, int y, Direction direction) {
|
||||||
|
if (direction == Direction.NONE) return false;
|
||||||
|
int nextX = x + direction.dx;
|
||||||
|
int nextY = y + direction.dy;
|
||||||
|
return nextY >= 0 && nextY < MAP.length && nextX >= 0 && nextX < MAP[nextY].length() && !isWall(nextX, nextY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Direction opposite(Direction direction) {
|
||||||
|
return switch (direction) {
|
||||||
|
case UP -> Direction.DOWN;
|
||||||
|
case DOWN -> Direction.UP;
|
||||||
|
case LEFT -> Direction.RIGHT;
|
||||||
|
case RIGHT -> Direction.LEFT;
|
||||||
|
case NONE -> Direction.NONE;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package com.craisinlord.antarchy.content.client.game;
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
public final class AntFarmInput {
|
||||||
|
private AntFarmInput() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntFarmGame.Direction directionForKey(int keyCode) {
|
||||||
|
return switch (keyCode) {
|
||||||
|
case GLFW.GLFW_KEY_W, GLFW.GLFW_KEY_UP -> AntFarmGame.Direction.UP;
|
||||||
|
case GLFW.GLFW_KEY_S, GLFW.GLFW_KEY_DOWN -> AntFarmGame.Direction.DOWN;
|
||||||
|
case GLFW.GLFW_KEY_A, GLFW.GLFW_KEY_LEFT -> AntFarmGame.Direction.LEFT;
|
||||||
|
case GLFW.GLFW_KEY_D, GLFW.GLFW_KEY_RIGHT -> AntFarmGame.Direction.RIGHT;
|
||||||
|
default -> AntFarmGame.Direction.NONE;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isStartOrRetry(int keyCode) {
|
||||||
|
return keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER;
|
||||||
|
}
|
||||||
|
}
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
package com.craisinlord.antarchy.content.client.game;
|
||||||
|
|
||||||
|
import net.minecraft.client.gui.Font;
|
||||||
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
|
|
||||||
|
public final class AntFarmProgram {
|
||||||
|
private final AntFarmGame game = new AntFarmGame();
|
||||||
|
private boolean gameInstalled;
|
||||||
|
|
||||||
|
public AntFarmProgram(boolean gameInstalled) {
|
||||||
|
this.gameInstalled = gameInstalled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGameInstalled(boolean gameInstalled) {
|
||||||
|
this.gameInstalled = gameInstalled;
|
||||||
|
if (!gameInstalled) game.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGameInstalled() { return gameInstalled; }
|
||||||
|
public AntFarmGame game() { return game; }
|
||||||
|
|
||||||
|
public void tick() {
|
||||||
|
if (gameInstalled) game.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean keyPressed(int keyCode) {
|
||||||
|
if (!gameInstalled) return false;
|
||||||
|
AntFarmGame.Direction direction = AntFarmInput.directionForKey(keyCode);
|
||||||
|
if (direction != AntFarmGame.Direction.NONE) {
|
||||||
|
if (game.phase() == AntFarmGame.Phase.READY) game.start();
|
||||||
|
game.setRequestedDirection(direction);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (AntFarmInput.isStartOrRetry(keyCode)) {
|
||||||
|
if (game.phase() == AntFarmGame.Phase.READY) game.start();
|
||||||
|
else if (game.phase() == AntFarmGame.Phase.WON || game.phase() == AntFarmGame.Phase.LOST) {
|
||||||
|
game.reset();
|
||||||
|
game.start();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void render(GuiGraphics graphics, Font font, int x, int y, int width, int height) {
|
||||||
|
if (!gameInstalled) {
|
||||||
|
graphics.fill(x, y, x + width, y + height, 0xFF020602);
|
||||||
|
graphics.drawString(font, "ANT FARM NOT INSTALLED", x + 12, y + height / 2, 0xFF65FF65, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AntFarmRenderer.render(graphics, font, game, x, y, width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
package com.craisinlord.antarchy.content.client.game;
|
||||||
|
|
||||||
|
import net.minecraft.client.gui.Font;
|
||||||
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
|
public final class AntFarmRenderer {
|
||||||
|
private static final int BLACK = 0xFF020602;
|
||||||
|
private static final int DARK_GREEN = 0xFF123012;
|
||||||
|
private static final int GREEN = 0xFF65FF65;
|
||||||
|
private static final int PALE_GREEN = 0xFFB8FFB8;
|
||||||
|
|
||||||
|
private AntFarmRenderer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void render(GuiGraphics graphics, Font font, AntFarmGame game, int x, int y, int width, int height) {
|
||||||
|
graphics.fill(x, y, x + width, y + height, BLACK);
|
||||||
|
int hudHeight = 18;
|
||||||
|
int boardX = x + 5;
|
||||||
|
int boardY = y + hudHeight + 4;
|
||||||
|
int boardWidth = width - 10;
|
||||||
|
int boardHeight = height - hudHeight - 9;
|
||||||
|
int tileSize = Math.max(1, Math.min(boardWidth / game.width(), boardHeight / game.height()));
|
||||||
|
int mazeWidth = tileSize * game.width();
|
||||||
|
int mazeHeight = tileSize * game.height();
|
||||||
|
int mazeX = boardX + Math.max(0, (boardWidth - mazeWidth) / 2);
|
||||||
|
int mazeY = boardY + Math.max(0, (boardHeight - mazeHeight) / 2);
|
||||||
|
graphics.drawString(font, Component.literal("SCORE " + String.format("%06d", game.score())), x + 6, y + 4, GREEN, false);
|
||||||
|
graphics.drawString(font, Component.literal("FOOD " + String.format("%03d", game.remainingFood())), x + width - 76, y + 4, PALE_GREEN, false);
|
||||||
|
for (int gridY = 0; gridY < game.height(); gridY++) {
|
||||||
|
for (int gridX = 0; gridX < game.width(); gridX++) {
|
||||||
|
int cellX = mazeX + gridX * tileSize;
|
||||||
|
int cellY = mazeY + gridY * tileSize;
|
||||||
|
if (game.isWall(gridX, gridY)) {
|
||||||
|
graphics.fill(cellX, cellY, cellX + tileSize, cellY + tileSize, DARK_GREEN);
|
||||||
|
} else if (game.hasPellet(gridX, gridY)) {
|
||||||
|
int pelletSize = Math.max(1, tileSize / 4);
|
||||||
|
int offset = (tileSize - pelletSize) / 2;
|
||||||
|
graphics.fill(cellX + offset, cellY + offset, cellX + offset + pelletSize, cellY + offset + pelletSize, PALE_GREEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drawAnt(graphics, mazeX + game.playerX() * tileSize, mazeY + game.playerY() * tileSize, tileSize, GREEN);
|
||||||
|
drawAnt(graphics, mazeX + game.enemyX() * tileSize, mazeY + game.enemyY() * tileSize, tileSize, PALE_GREEN);
|
||||||
|
if (game.phase() != AntFarmGame.Phase.PLAYING) {
|
||||||
|
String message = switch (game.phase()) {
|
||||||
|
case READY -> "ANT FARM // PRESS ENTER TO START";
|
||||||
|
case WON -> "COLONY FED // ENTER TO PLAY AGAIN";
|
||||||
|
case LOST -> "COLONY COMPROMISED // ENTER TO RETRY";
|
||||||
|
case PLAYING -> "";
|
||||||
|
};
|
||||||
|
int messageWidth = font.width(message);
|
||||||
|
graphics.fill(x + 8, y + height / 2 - 12, x + width - 8, y + height / 2 + 12, BLACK);
|
||||||
|
graphics.drawString(font, Component.literal(message), x + Math.max(8, (width - messageWidth) / 2), y + height / 2 - 4, GREEN, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void drawAnt(GuiGraphics graphics, int x, int y, int tileSize, int color) {
|
||||||
|
int inset = Math.max(1, tileSize / 4);
|
||||||
|
int center = x + tileSize / 2;
|
||||||
|
graphics.fill(center - inset, y + inset, center + inset + 1, y + tileSize - inset, color);
|
||||||
|
graphics.fill(x + inset, y + tileSize / 2 - inset, x + tileSize - inset, y + tileSize / 2 + inset + 1, color);
|
||||||
|
if (tileSize >= 5) graphics.fill(center - inset, y + inset, center + inset + 1, y + inset + 1, BLACK);
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -2,10 +2,16 @@ package com.craisinlord.antarchy.content.client.renderer;
|
|||||||
|
|
||||||
import com.craisinlord.antarchy.content.block.entity.ComputerBlockEntity;
|
import com.craisinlord.antarchy.content.block.entity.ComputerBlockEntity;
|
||||||
import com.craisinlord.antarchy.content.client.model.ComputerModel;
|
import com.craisinlord.antarchy.content.client.model.ComputerModel;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
import software.bernie.geckolib.renderer.GeoBlockRenderer;
|
import software.bernie.geckolib.renderer.GeoBlockRenderer;
|
||||||
|
|
||||||
public final class ComputerRenderer extends GeoBlockRenderer<ComputerBlockEntity> {
|
public final class ComputerRenderer extends GeoBlockRenderer<ComputerBlockEntity> {
|
||||||
public ComputerRenderer(net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider.Context context) {
|
public ComputerRenderer(net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider.Context context) {
|
||||||
super(new ComputerModel());
|
super(new ComputerModel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Direction getFacing(ComputerBlockEntity computer) {
|
||||||
|
return computer.getBlockState().getValue(com.craisinlord.antarchy.content.block.ComputerBlock.FACING);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+999
-95
File diff suppressed because it is too large.
Load diff
+196
@@ -0,0 +1,196 @@
|
|||||||
|
package com.craisinlord.antarchy.content.computer;
|
||||||
|
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.ListTag;
|
||||||
|
import net.minecraft.nbt.StringTag;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public final class ComputerDesktopState {
|
||||||
|
public static final ResourceLocation DEFAULT_WALLPAPER = ResourceLocation.fromNamespaceAndPath("antarchy", "grid_ant");
|
||||||
|
public static final int MAX_UNLOCKED_WALLPAPERS = 64;
|
||||||
|
public static final int MAX_INSTALLED_PROGRAMS = 64;
|
||||||
|
public static final int MAX_ICON_POSITIONS = 64;
|
||||||
|
public static final int MAX_ID_LENGTH = 128;
|
||||||
|
public static final int MIN_ICON_COORDINATE = 0;
|
||||||
|
public static final int MAX_ICON_COORDINATE = 4096;
|
||||||
|
private static final String SELECTED_WALLPAPER_TAG = "SelectedWallpaper";
|
||||||
|
private static final String UNLOCKED_WALLPAPERS_TAG = "UnlockedWallpapers";
|
||||||
|
private static final String INSTALLED_PROGRAMS_TAG = "InstalledPrograms";
|
||||||
|
private static final String ICON_POSITIONS_TAG = "IconPositions";
|
||||||
|
private static final String ID_TAG = "Id";
|
||||||
|
private static final String X_TAG = "X";
|
||||||
|
private static final String Y_TAG = "Y";
|
||||||
|
private ResourceLocation selectedWallpaper;
|
||||||
|
private final Set<ResourceLocation> unlockedWallpaperIds = new LinkedHashSet<>();
|
||||||
|
private final Set<ResourceLocation> installedProgramIds = new LinkedHashSet<>();
|
||||||
|
private final Map<ResourceLocation, IconPosition> iconPositions = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
public ResourceLocation selectedWallpaper() {
|
||||||
|
return selectedWallpaper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<ResourceLocation> unlockedWallpaperIds() {
|
||||||
|
return Collections.unmodifiableSet(new LinkedHashSet<>(unlockedWallpaperIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<ResourceLocation> installedProgramIds() {
|
||||||
|
return Collections.unmodifiableSet(new LinkedHashSet<>(installedProgramIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<ResourceLocation, IconPosition> iconPositions() {
|
||||||
|
return Collections.unmodifiableMap(new LinkedHashMap<>(iconPositions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasWallpaper(ResourceLocation id) {
|
||||||
|
return id != null && unlockedWallpaperIds.contains(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasProgram(ResourceLocation id) {
|
||||||
|
return id != null && installedProgramIds.contains(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IconPosition iconPosition(ResourceLocation id) {
|
||||||
|
return iconPositions.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean unlockWallpaper(ResourceLocation id) {
|
||||||
|
if (!validId(id) || unlockedWallpaperIds.contains(id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (unlockedWallpaperIds.size() >= MAX_UNLOCKED_WALLPAPERS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return unlockedWallpaperIds.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean installProgram(ResourceLocation id) {
|
||||||
|
if (!validId(id) || installedProgramIds.size() >= MAX_INSTALLED_PROGRAMS && !installedProgramIds.contains(id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return installedProgramIds.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean uninstallProgram(ResourceLocation id) {
|
||||||
|
if (id == null || !installedProgramIds.remove(id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
iconPositions.remove(id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean selectWallpaper(ResourceLocation id) {
|
||||||
|
if (id != null && !unlockedWallpaperIds.contains(id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (id == null ? selectedWallpaper == null : id.equals(selectedWallpaper)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
selectedWallpaper = id;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setIconPosition(ResourceLocation id, int x, int y) {
|
||||||
|
if (!validId(id) || x < MIN_ICON_COORDINATE || x > MAX_ICON_COORDINATE || y < MIN_ICON_COORDINATE || y > MAX_ICON_COORDINATE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
IconPosition position = new IconPosition(x, y);
|
||||||
|
if (position.equals(iconPositions.get(id))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!iconPositions.containsKey(id) && iconPositions.size() >= MAX_ICON_POSITIONS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
iconPositions.put(id, position);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeIconPosition(ResourceLocation id) {
|
||||||
|
return id != null && iconPositions.remove(id) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
unlockedWallpaperIds.clear();
|
||||||
|
installedProgramIds.clear();
|
||||||
|
iconPositions.clear();
|
||||||
|
unlockedWallpaperIds.add(DEFAULT_WALLPAPER);
|
||||||
|
selectedWallpaper = DEFAULT_WALLPAPER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompoundTag save() {
|
||||||
|
CompoundTag tag = new CompoundTag();
|
||||||
|
if (selectedWallpaper != null) {
|
||||||
|
tag.putString(SELECTED_WALLPAPER_TAG, selectedWallpaper.toString());
|
||||||
|
}
|
||||||
|
tag.put(UNLOCKED_WALLPAPERS_TAG, saveIds(unlockedWallpaperIds));
|
||||||
|
tag.put(INSTALLED_PROGRAMS_TAG, saveIds(installedProgramIds));
|
||||||
|
ListTag positions = new ListTag();
|
||||||
|
for (Map.Entry<ResourceLocation, IconPosition> entry : iconPositions.entrySet()) {
|
||||||
|
CompoundTag position = new CompoundTag();
|
||||||
|
position.putString(ID_TAG, entry.getKey().toString());
|
||||||
|
position.putInt(X_TAG, entry.getValue().x());
|
||||||
|
position.putInt(Y_TAG, entry.getValue().y());
|
||||||
|
positions.add(position);
|
||||||
|
}
|
||||||
|
tag.put(ICON_POSITIONS_TAG, positions);
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(CompoundTag tag) {
|
||||||
|
clear();
|
||||||
|
selectedWallpaper = parseId(tag.getString(SELECTED_WALLPAPER_TAG));
|
||||||
|
loadIds(tag.getList(UNLOCKED_WALLPAPERS_TAG, 8), unlockedWallpaperIds, MAX_UNLOCKED_WALLPAPERS);
|
||||||
|
loadIds(tag.getList(INSTALLED_PROGRAMS_TAG, 8), installedProgramIds, MAX_INSTALLED_PROGRAMS);
|
||||||
|
ListTag positions = tag.getList(ICON_POSITIONS_TAG, 10);
|
||||||
|
for (int index = 0; index < positions.size() && iconPositions.size() < MAX_ICON_POSITIONS; index++) {
|
||||||
|
CompoundTag stored = positions.getCompound(index);
|
||||||
|
ResourceLocation id = parseId(stored.getString(ID_TAG));
|
||||||
|
int x = stored.getInt(X_TAG);
|
||||||
|
int y = stored.getInt(Y_TAG);
|
||||||
|
if (id != null && x >= MIN_ICON_COORDINATE && x <= MAX_ICON_COORDINATE && y >= MIN_ICON_COORDINATE && y <= MAX_ICON_COORDINATE) {
|
||||||
|
iconPositions.putIfAbsent(id, new IconPosition(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ListTag saveIds(Collection<ResourceLocation> ids) {
|
||||||
|
ListTag list = new ListTag();
|
||||||
|
for (ResourceLocation id : ids) {
|
||||||
|
list.add(StringTag.valueOf(id.toString()));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void loadIds(ListTag list, Set<ResourceLocation> destination, int limit) {
|
||||||
|
for (int index = 0; index < list.size() && destination.size() < limit; index++) {
|
||||||
|
ResourceLocation id = parseId(list.getString(index));
|
||||||
|
if (id != null) {
|
||||||
|
destination.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ResourceLocation parseId(String value) {
|
||||||
|
if (value == null || value.isBlank() || value.length() > MAX_ID_LENGTH) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return ResourceLocation.parse(value);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean validId(ResourceLocation id) {
|
||||||
|
return id != null && id.toString().length() <= MAX_ID_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record IconPosition(int x, int y) {
|
||||||
|
}
|
||||||
|
}
|
||||||
+174
@@ -0,0 +1,174 @@
|
|||||||
|
package com.craisinlord.antarchy.content.computer;
|
||||||
|
|
||||||
|
import net.minecraft.core.HolderLookup;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.ListTag;
|
||||||
|
import net.minecraft.nbt.StringTag;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class ComputerFileSystem {
|
||||||
|
public static final int MAX_FILE_SIZE = 16 * 1024;
|
||||||
|
public static final int MAX_TOTAL_SIZE = 64 * 1024;
|
||||||
|
public static final int MAX_NODES = 128;
|
||||||
|
public static final int MAX_PATH_LENGTH = 64;
|
||||||
|
public static final String ROOT = "/";
|
||||||
|
public static final String DOCUMENTS = "/documents";
|
||||||
|
public static final String PICTURES = "/pictures";
|
||||||
|
public static final String SYSTEM = "/system";
|
||||||
|
private static final String FILES_TAG = "Files";
|
||||||
|
private static final String PATH_TAG = "Path";
|
||||||
|
private static final String TYPE_TAG = "Type";
|
||||||
|
private static final String CONTENTS_TAG = "Contents";
|
||||||
|
private final Map<String, ComputerFile> nodes = new LinkedHashMap<>();
|
||||||
|
private int totalBytes;
|
||||||
|
|
||||||
|
public ComputerFileSystem() {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
nodes.clear();
|
||||||
|
nodes.put(ROOT, ComputerFile.directory(ROOT));
|
||||||
|
nodes.put(DOCUMENTS, ComputerFile.directory(DOCUMENTS));
|
||||||
|
nodes.put(PICTURES, ComputerFile.directory(PICTURES));
|
||||||
|
nodes.put(SYSTEM, ComputerFile.directory(SYSTEM));
|
||||||
|
totalBytes = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComputerFile get(String path) {
|
||||||
|
return nodes.get(normalize(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ComputerFile> list() {
|
||||||
|
return List.copyOf(nodes.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ComputerFile> listTextFiles() {
|
||||||
|
return nodes.values().stream().filter(file -> file.type() == ComputerFile.Type.TEXT).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result createTextFile(String path, String contents) {
|
||||||
|
String normalized = normalize(path);
|
||||||
|
Result validation = validateTextPath(normalized, contents);
|
||||||
|
if (!validation.successful()) return validation;
|
||||||
|
if (nodes.containsKey(normalized)) return Result.failure("already_exists");
|
||||||
|
if (nodes.size() >= MAX_NODES) return Result.failure("node_limit");
|
||||||
|
int size = contents.length();
|
||||||
|
if (totalBytes + size > MAX_TOTAL_SIZE) return Result.failure("storage_limit");
|
||||||
|
nodes.put(normalized, ComputerFile.text(normalized, contents));
|
||||||
|
totalBytes += size;
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result writeTextFile(String path, String contents) {
|
||||||
|
String normalized = normalize(path);
|
||||||
|
Result validation = validateTextPath(normalized, contents);
|
||||||
|
if (!validation.successful()) return validation;
|
||||||
|
ComputerFile current = nodes.get(normalized);
|
||||||
|
if (current == null) return Result.failure("not_found");
|
||||||
|
if (current.type() != ComputerFile.Type.TEXT) return Result.failure("not_text");
|
||||||
|
int replacement = totalBytes - current.contents().length() + contents.length();
|
||||||
|
if (replacement > MAX_TOTAL_SIZE) return Result.failure("storage_limit");
|
||||||
|
nodes.put(normalized, ComputerFile.text(normalized, contents));
|
||||||
|
totalBytes = replacement;
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result createOrWriteTextFile(String path, String contents) {
|
||||||
|
return nodes.containsKey(normalize(path)) ? writeTextFile(path, contents) : createTextFile(path, contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result delete(String path) {
|
||||||
|
String normalized = normalize(path);
|
||||||
|
if (isProtected(normalized)) return Result.failure("protected");
|
||||||
|
ComputerFile file = nodes.get(normalized);
|
||||||
|
if (file == null) return Result.failure("not_found");
|
||||||
|
if (file.type() == ComputerFile.Type.DIRECTORY && nodes.keySet().stream().anyMatch(key -> !key.equals(normalized) && key.startsWith(normalized + "/"))) {
|
||||||
|
return Result.failure("not_empty");
|
||||||
|
}
|
||||||
|
nodes.remove(normalized);
|
||||||
|
totalBytes -= file.contents().length();
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String normalize(String path) {
|
||||||
|
if (path == null || path.isBlank()) return "";
|
||||||
|
String value = path.trim().replace('\\', '/');
|
||||||
|
if (!value.startsWith("/")) value = "/" + value;
|
||||||
|
while (value.contains("//")) value = value.replace("//", "/");
|
||||||
|
if (value.length() > 1 && value.endsWith("/")) value = value.substring(0, value.length() - 1);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isProtected(String path) {
|
||||||
|
String normalized = normalize(path);
|
||||||
|
return normalized.equals(SYSTEM) || normalized.startsWith(SYSTEM + "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(CompoundTag tag, HolderLookup.Provider registries) {
|
||||||
|
ListTag files = new ListTag();
|
||||||
|
for (ComputerFile file : nodes.values()) {
|
||||||
|
if (ROOT.equals(file.path())) continue;
|
||||||
|
CompoundTag stored = new CompoundTag();
|
||||||
|
stored.putString(PATH_TAG, file.path());
|
||||||
|
stored.putString(TYPE_TAG, file.type().name());
|
||||||
|
if (file.type() == ComputerFile.Type.TEXT) stored.putString(CONTENTS_TAG, file.contents());
|
||||||
|
files.add(stored);
|
||||||
|
}
|
||||||
|
tag.put(FILES_TAG, files);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(CompoundTag tag, HolderLookup.Provider registries) {
|
||||||
|
reset();
|
||||||
|
ListTag files = tag.getList(FILES_TAG, 10);
|
||||||
|
for (int index = 0; index < files.size(); index++) {
|
||||||
|
CompoundTag stored = files.getCompound(index);
|
||||||
|
String path = normalize(stored.getString(PATH_TAG));
|
||||||
|
if (path.isEmpty() || nodes.containsKey(path) || path.length() > MAX_PATH_LENGTH || isProtected(path)) continue;
|
||||||
|
String type = stored.getString(TYPE_TAG);
|
||||||
|
if ("DIRECTORY".equals(type)) {
|
||||||
|
if (nodes.size() < MAX_NODES) nodes.put(path, ComputerFile.directory(path));
|
||||||
|
} else if ("TEXT".equals(type)) {
|
||||||
|
String contents = stored.getString(CONTENTS_TAG);
|
||||||
|
if (contents.length() <= MAX_FILE_SIZE && nodes.size() < MAX_NODES && totalBytes + contents.length() <= MAX_TOTAL_SIZE) {
|
||||||
|
nodes.put(path, ComputerFile.text(path, contents));
|
||||||
|
totalBytes += contents.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result validateTextPath(String path, String contents) {
|
||||||
|
if (path.isEmpty() || path.length() > MAX_PATH_LENGTH || ROOT.equals(path) || isProtected(path)) return Result.failure("invalid_path");
|
||||||
|
if (contents == null || contents.length() > MAX_FILE_SIZE) return Result.failure("file_limit");
|
||||||
|
if (path.contains("..") || path.indexOf('\0') >= 0 || path.indexOf('\n') >= 0 || path.indexOf('\r') >= 0) return Result.failure("invalid_path");
|
||||||
|
int slash = path.lastIndexOf('/');
|
||||||
|
if (slash > 0 && !nodesDirectory(path.substring(0, slash))) return Result.failure("missing_directory");
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean nodesDirectory(String path) {
|
||||||
|
ComputerFile parent = nodes.get(path);
|
||||||
|
return parent != null && parent.type() == ComputerFile.Type.DIRECTORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int totalBytes() {
|
||||||
|
return totalBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Result(boolean successful, String error) {
|
||||||
|
public static Result ok() { return new Result(true, ""); }
|
||||||
|
public static Result failure(String error) { return new Result(false, error); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ComputerFile(String path, Type type, String contents) {
|
||||||
|
public static ComputerFile directory(String path) { return new ComputerFile(path, Type.DIRECTORY, ""); }
|
||||||
|
public static ComputerFile text(String path, String contents) { return new ComputerFile(path, Type.TEXT, contents); }
|
||||||
|
public enum Type { DIRECTORY, TEXT, IMAGE }
|
||||||
|
}
|
||||||
|
}
|
||||||
+114
@@ -0,0 +1,114 @@
|
|||||||
|
package com.craisinlord.antarchy.content.computer.paint;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public final class AntPaintCanvas {
|
||||||
|
public static final int WIDTH = 32;
|
||||||
|
public static final int HEIGHT = 24;
|
||||||
|
public static final int PIXEL_COUNT = WIDTH * HEIGHT;
|
||||||
|
public static final int PACKED_LENGTH = (PIXEL_COUNT + 7) / 8;
|
||||||
|
|
||||||
|
private final boolean[] pixels;
|
||||||
|
|
||||||
|
public AntPaintCanvas() {
|
||||||
|
this.pixels = new boolean[PIXEL_COUNT];
|
||||||
|
}
|
||||||
|
|
||||||
|
private AntPaintCanvas(boolean[] pixels) {
|
||||||
|
this.pixels = pixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean get(int x, int y) {
|
||||||
|
return pixels[index(x, y)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(int x, int y, boolean value) {
|
||||||
|
pixels[index(x, y)] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean draw(int x, int y) {
|
||||||
|
return setIfChanged(x, y, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean erase(int x, int y) {
|
||||||
|
return setIfChanged(x, y, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
Arrays.fill(pixels, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int floodFill(int x, int y, boolean value) {
|
||||||
|
int start = index(x, y);
|
||||||
|
boolean original = pixels[start];
|
||||||
|
if (original == value) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ArrayDeque<Integer> queue = new ArrayDeque<>();
|
||||||
|
boolean[] visited = new boolean[PIXEL_COUNT];
|
||||||
|
queue.add(start);
|
||||||
|
int changed = 0;
|
||||||
|
while (!queue.isEmpty()) {
|
||||||
|
int current = queue.removeFirst();
|
||||||
|
if (visited[current] || pixels[current] != original) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
visited[current] = true;
|
||||||
|
pixels[current] = value;
|
||||||
|
changed++;
|
||||||
|
int px = current % WIDTH;
|
||||||
|
int py = current / WIDTH;
|
||||||
|
if (px > 0) queue.add(current - 1);
|
||||||
|
if (px + 1 < WIDTH) queue.add(current + 1);
|
||||||
|
if (py > 0) queue.add(current - WIDTH);
|
||||||
|
if (py + 1 < HEIGHT) queue.add(current + WIDTH);
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] pack() {
|
||||||
|
byte[] packed = new byte[PACKED_LENGTH];
|
||||||
|
for (int i = 0; i < PIXEL_COUNT; i++) {
|
||||||
|
if (pixels[i]) {
|
||||||
|
packed[i / 8] |= (byte) (1 << (i % 8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return packed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntPaintCanvas unpack(byte[] packed) {
|
||||||
|
if (packed == null || packed.length != PACKED_LENGTH) {
|
||||||
|
throw new IllegalArgumentException("Invalid AntPaint pixel data length");
|
||||||
|
}
|
||||||
|
boolean[] pixels = new boolean[PIXEL_COUNT];
|
||||||
|
for (int i = 0; i < PIXEL_COUNT; i++) {
|
||||||
|
pixels[i] = (packed[i / 8] & (1 << (i % 8))) != 0;
|
||||||
|
}
|
||||||
|
return new AntPaintCanvas(pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AntPaintCanvas copy() {
|
||||||
|
return new AntPaintCanvas(Arrays.copyOf(pixels, pixels.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean[] copyPixels() {
|
||||||
|
return Arrays.copyOf(pixels, pixels.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setIfChanged(int x, int y, boolean value) {
|
||||||
|
int index = index(x, y);
|
||||||
|
if (pixels[index] == value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pixels[index] = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int index(int x, int y) {
|
||||||
|
if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) {
|
||||||
|
throw new IndexOutOfBoundsException("AntPaint pixel outside canvas");
|
||||||
|
}
|
||||||
|
return y * WIDTH + x;
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
package com.craisinlord.antarchy.content.computer.paint;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record AntPaintFile(String fileId, String filename, AntPaintCanvas canvas, long createdTime, long modifiedTime) {
|
||||||
|
public AntPaintFile {
|
||||||
|
Objects.requireNonNull(fileId, "fileId");
|
||||||
|
filename = AntPaintFilename.normalize(filename);
|
||||||
|
Objects.requireNonNull(canvas, "canvas");
|
||||||
|
if (fileId.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("fileId cannot be blank");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AntPaintFile copy() {
|
||||||
|
return new AntPaintFile(fileId, filename, canvas.copy(), createdTime, modifiedTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AntPaintFile withCanvas(AntPaintCanvas value, long modified) {
|
||||||
|
return new AntPaintFile(fileId, filename, value, createdTime, modified);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AntPaintFile withFilename(String value, long modified) {
|
||||||
|
return new AntPaintFile(fileId, value, canvas, createdTime, modified);
|
||||||
|
}
|
||||||
|
}
|
||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
package com.craisinlord.antarchy.content.computer.paint;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public final class AntPaintFileCodec {
|
||||||
|
public static final int VERSION = 1;
|
||||||
|
public static final int MAX_ENCODED_BYTES = 1024;
|
||||||
|
private static final int MAGIC = 0x41505431;
|
||||||
|
|
||||||
|
private AntPaintFileCodec() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] encode(AntPaintFile file) {
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream(MAX_ENCODED_BYTES);
|
||||||
|
DataOutputStream data = new DataOutputStream(output);
|
||||||
|
data.writeInt(MAGIC);
|
||||||
|
data.writeByte(VERSION);
|
||||||
|
writeString(data, file.fileId());
|
||||||
|
writeString(data, file.filename());
|
||||||
|
data.writeLong(file.createdTime());
|
||||||
|
data.writeLong(file.modifiedTime());
|
||||||
|
data.writeByte(AntPaintCanvas.WIDTH);
|
||||||
|
data.writeByte(AntPaintCanvas.HEIGHT);
|
||||||
|
data.write(file.canvas().pack());
|
||||||
|
data.flush();
|
||||||
|
byte[] encoded = output.toByteArray();
|
||||||
|
if (encoded.length > MAX_ENCODED_BYTES) {
|
||||||
|
throw new IllegalArgumentException("AntPaint file exceeds encoded size limit");
|
||||||
|
}
|
||||||
|
return encoded;
|
||||||
|
} catch (IOException exception) {
|
||||||
|
throw new IllegalStateException("Unable to encode AntPaint file", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntPaintFile decode(byte[] encoded) {
|
||||||
|
if (encoded == null || encoded.length > MAX_ENCODED_BYTES) {
|
||||||
|
throw new IllegalArgumentException("Invalid AntPaint file size");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DataInputStream data = new DataInputStream(new ByteArrayInputStream(encoded));
|
||||||
|
if (data.readInt() != MAGIC || data.readUnsignedByte() != VERSION) {
|
||||||
|
throw new IllegalArgumentException("Unsupported AntPaint file format");
|
||||||
|
}
|
||||||
|
String fileId = readString(data, 128);
|
||||||
|
String filename = readString(data, AntPaintFilename.MAX_LENGTH + AntPaintFilename.EXTENSION.length());
|
||||||
|
long created = data.readLong();
|
||||||
|
long modified = data.readLong();
|
||||||
|
if (data.readUnsignedByte() != AntPaintCanvas.WIDTH || data.readUnsignedByte() != AntPaintCanvas.HEIGHT) {
|
||||||
|
throw new IllegalArgumentException("Unsupported AntPaint canvas size");
|
||||||
|
}
|
||||||
|
byte[] pixels = data.readNBytes(AntPaintCanvas.PACKED_LENGTH);
|
||||||
|
if (pixels.length != AntPaintCanvas.PACKED_LENGTH || data.available() != 0) {
|
||||||
|
throw new IllegalArgumentException("Invalid AntPaint pixel payload");
|
||||||
|
}
|
||||||
|
return new AntPaintFile(fileId, filename, AntPaintCanvas.unpack(pixels), created, modified);
|
||||||
|
} catch (IOException exception) {
|
||||||
|
throw new IllegalArgumentException("Invalid AntPaint file data", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeString(DataOutputStream data, String value) throws IOException {
|
||||||
|
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
|
||||||
|
if (bytes.length > 128) {
|
||||||
|
throw new IllegalArgumentException("AntPaint string exceeds size limit");
|
||||||
|
}
|
||||||
|
data.writeShort(bytes.length);
|
||||||
|
data.write(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readString(DataInputStream data, int maxBytes) throws IOException {
|
||||||
|
int length = data.readUnsignedShort();
|
||||||
|
if (length > maxBytes) {
|
||||||
|
throw new IllegalArgumentException("AntPaint string exceeds size limit");
|
||||||
|
}
|
||||||
|
byte[] bytes = new byte[length];
|
||||||
|
data.readFully(bytes);
|
||||||
|
return new String(bytes, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
package com.craisinlord.antarchy.content.computer.paint;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public final class AntPaintFilename {
|
||||||
|
public static final int MAX_LENGTH = 32;
|
||||||
|
public static final String EXTENSION = ".antpaint";
|
||||||
|
private static final Pattern SAFE_NAME = Pattern.compile("[A-Za-z0-9 _.-]+");
|
||||||
|
|
||||||
|
private AntPaintFilename() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValid(String filename) {
|
||||||
|
if (filename == null || filename.isBlank() || filename.length() > MAX_LENGTH) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (filename.equals(".") || filename.equals("..") || filename.contains("..")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return SAFE_NAME.matcher(filename).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String normalize(String filename) {
|
||||||
|
if (filename == null) {
|
||||||
|
throw new IllegalArgumentException("Filename cannot be null");
|
||||||
|
}
|
||||||
|
String value = filename.trim();
|
||||||
|
if (value.toLowerCase(Locale.ROOT).endsWith(EXTENSION)) {
|
||||||
|
value = value.substring(0, value.length() - EXTENSION.length());
|
||||||
|
}
|
||||||
|
value = value.trim();
|
||||||
|
if (!isValid(value)) {
|
||||||
|
throw new IllegalArgumentException("Invalid AntPaint filename");
|
||||||
|
}
|
||||||
|
return value + EXTENSION;
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package com.craisinlord.antarchy.content.computer.paint;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
|
||||||
|
public final class AntPaintHistory {
|
||||||
|
public static final int MAX_STATES = 32;
|
||||||
|
|
||||||
|
private final ArrayDeque<AntPaintCanvas> undo = new ArrayDeque<>();
|
||||||
|
private final ArrayDeque<AntPaintCanvas> redo = new ArrayDeque<>();
|
||||||
|
|
||||||
|
public void record(AntPaintCanvas canvas) {
|
||||||
|
undo.addLast(canvas.copy());
|
||||||
|
while (undo.size() > MAX_STATES) {
|
||||||
|
undo.removeFirst();
|
||||||
|
}
|
||||||
|
redo.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canUndo() {
|
||||||
|
return !undo.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canRedo() {
|
||||||
|
return !redo.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AntPaintCanvas undo(AntPaintCanvas current) {
|
||||||
|
if (undo.isEmpty()) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
redo.addLast(current.copy());
|
||||||
|
return undo.removeLast().copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AntPaintCanvas redo(AntPaintCanvas current) {
|
||||||
|
if (redo.isEmpty()) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
undo.addLast(current.copy());
|
||||||
|
return redo.removeLast().copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
undo.clear();
|
||||||
|
redo.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package com.craisinlord.antarchy.content.computer.paint;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public final class AntPaintRequests {
|
||||||
|
private AntPaintRequests() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Save(String computerId, AntPaintFile file) {
|
||||||
|
public Save {
|
||||||
|
Objects.requireNonNull(computerId, "computerId");
|
||||||
|
Objects.requireNonNull(file, "file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Load(String computerId, String fileId) {
|
||||||
|
public Load {
|
||||||
|
Objects.requireNonNull(computerId, "computerId");
|
||||||
|
Objects.requireNonNull(fileId, "fileId");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record List(String computerId) {
|
||||||
|
public List {
|
||||||
|
Objects.requireNonNull(computerId, "computerId");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Delete(String computerId, String fileId) {
|
||||||
|
public Delete {
|
||||||
|
Objects.requireNonNull(computerId, "computerId");
|
||||||
|
Objects.requireNonNull(fileId, "fileId");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
package com.craisinlord.antarchy.content.computer.paint;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class AntPaintResults {
|
||||||
|
private AntPaintResults() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Code {
|
||||||
|
SUCCESS,
|
||||||
|
UNAUTHENTICATED,
|
||||||
|
OUT_OF_RANGE,
|
||||||
|
INVALID_FILENAME,
|
||||||
|
INVALID_FILE,
|
||||||
|
FILE_NOT_FOUND,
|
||||||
|
DUPLICATE_FILE,
|
||||||
|
STORAGE_FULL,
|
||||||
|
PAYLOAD_TOO_LARGE,
|
||||||
|
BUSY,
|
||||||
|
UNKNOWN_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Save(Code code, String fileId) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Load(Code code, AntPaintFile file) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record List(Code code, java.util.List<AntPaintFileMetadata> files) {
|
||||||
|
public List {
|
||||||
|
files = files == null ? java.util.List.of() : java.util.List.copyOf(files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Delete(Code code, String fileId) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record AntPaintFileMetadata(String fileId, String filename, long modifiedTime) {
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package com.craisinlord.antarchy.content.computer.paint;
|
||||||
|
|
||||||
|
public enum AntPaintTool {
|
||||||
|
PENCIL,
|
||||||
|
ERASER,
|
||||||
|
FILL
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package com.craisinlord.antarchy.content.computer.terminal;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record TerminalCommand(String name, List<String> arguments, String rawInput) {
|
||||||
|
public TerminalCommand {
|
||||||
|
name = name.toLowerCase(java.util.Locale.ROOT);
|
||||||
|
arguments = List.copyOf(arguments);
|
||||||
|
rawInput = rawInput == null ? "" : rawInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
package com.craisinlord.antarchy.content.computer.terminal;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class TerminalCommandParser {
|
||||||
|
private static final int MAX_INPUT_LENGTH = 256;
|
||||||
|
|
||||||
|
private TerminalCommandParser() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseResult parse(String input) {
|
||||||
|
if (input == null) {
|
||||||
|
return ParseResult.error("ERROR: EMPTY INPUT");
|
||||||
|
}
|
||||||
|
if (input.length() > MAX_INPUT_LENGTH) {
|
||||||
|
return ParseResult.error("ERROR: COMMAND TOO LONG");
|
||||||
|
}
|
||||||
|
String trimmed = input.trim();
|
||||||
|
if (trimmed.isEmpty()) {
|
||||||
|
return ParseResult.error("ERROR: EMPTY INPUT");
|
||||||
|
}
|
||||||
|
List<String> tokens = new ArrayList<>();
|
||||||
|
StringBuilder token = new StringBuilder();
|
||||||
|
Character quote = null;
|
||||||
|
boolean escaped = false;
|
||||||
|
for (int index = 0; index < trimmed.length(); index++) {
|
||||||
|
char character = trimmed.charAt(index);
|
||||||
|
if (escaped) {
|
||||||
|
token.append(character);
|
||||||
|
escaped = false;
|
||||||
|
} else if (character == '\\') {
|
||||||
|
escaped = true;
|
||||||
|
} else if (quote != null) {
|
||||||
|
if (character == quote) {
|
||||||
|
quote = null;
|
||||||
|
} else {
|
||||||
|
token.append(character);
|
||||||
|
}
|
||||||
|
} else if (character == '\'' || character == '"') {
|
||||||
|
quote = character;
|
||||||
|
} else if (Character.isWhitespace(character)) {
|
||||||
|
if (!token.isEmpty()) {
|
||||||
|
tokens.add(token.toString());
|
||||||
|
token.setLength(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
token.append(character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (escaped) {
|
||||||
|
token.append('\\');
|
||||||
|
}
|
||||||
|
if (quote != null) {
|
||||||
|
return ParseResult.error("ERROR: UNTERMINATED QUOTE");
|
||||||
|
}
|
||||||
|
if (!token.isEmpty()) {
|
||||||
|
tokens.add(token.toString());
|
||||||
|
}
|
||||||
|
if (tokens.isEmpty()) {
|
||||||
|
return ParseResult.error("ERROR: EMPTY INPUT");
|
||||||
|
}
|
||||||
|
return ParseResult.success(new TerminalCommand(tokens.get(0), tokens.subList(1, tokens.size()), input));
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ParseResult(TerminalCommand command, String error) {
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return command != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseResult success(TerminalCommand command) {
|
||||||
|
return new ParseResult(command, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseResult error(String message) {
|
||||||
|
return new ParseResult(null, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+207
@@ -0,0 +1,207 @@
|
|||||||
|
package com.craisinlord.antarchy.content.computer.terminal;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public final class TerminalCommandService {
|
||||||
|
private static final int MAX_ARGUMENT_LENGTH = 128;
|
||||||
|
private static final List<String> COMMANDS = List.of(
|
||||||
|
"help", "clear", "pwd", "ls", "cd", "mkdir", "touch", "cat", "write", "rm", "rmdir", "mv", "open"
|
||||||
|
);
|
||||||
|
|
||||||
|
public TerminalResult execute(TerminalFileSystem fileSystem, String workingDirectory, String input) {
|
||||||
|
String currentDirectory = normalizeWorkingDirectory(fileSystem, workingDirectory);
|
||||||
|
TerminalCommandParser.ParseResult parsed = TerminalCommandParser.parse(input);
|
||||||
|
if (!parsed.isSuccess()) {
|
||||||
|
return TerminalResult.error(currentDirectory, parsed.error());
|
||||||
|
}
|
||||||
|
TerminalCommand command = parsed.command();
|
||||||
|
if (command.arguments().stream().anyMatch(argument -> argument.length() > MAX_ARGUMENT_LENGTH)) {
|
||||||
|
return TerminalResult.error(currentDirectory, "ERROR: ARGUMENT TOO LONG");
|
||||||
|
}
|
||||||
|
return switch (command.name()) {
|
||||||
|
case "help" -> help(currentDirectory, command);
|
||||||
|
case "clear" -> requireArguments(currentDirectory, command, 0, TerminalResult.clear(currentDirectory));
|
||||||
|
case "pwd" -> requireArguments(currentDirectory, command, 0, TerminalResult.success(currentDirectory, currentDirectory));
|
||||||
|
case "ls" -> list(fileSystem, currentDirectory, command);
|
||||||
|
case "cd" -> changeDirectory(fileSystem, currentDirectory, command);
|
||||||
|
case "mkdir" -> createDirectory(fileSystem, currentDirectory, command);
|
||||||
|
case "touch" -> createFile(fileSystem, currentDirectory, command);
|
||||||
|
case "cat" -> readFile(fileSystem, currentDirectory, command);
|
||||||
|
case "write" -> writeFile(fileSystem, currentDirectory, command);
|
||||||
|
case "rm" -> removeFile(fileSystem, currentDirectory, command);
|
||||||
|
case "rmdir" -> removeDirectory(fileSystem, currentDirectory, command);
|
||||||
|
case "mv" -> move(fileSystem, currentDirectory, command);
|
||||||
|
case "open" -> open(fileSystem, currentDirectory, command);
|
||||||
|
default -> TerminalResult.error(currentDirectory, "ERROR: INVALID COMMAND: " + command.name());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalResult help(String workingDirectory, TerminalCommand command) {
|
||||||
|
if (command.arguments().isEmpty()) {
|
||||||
|
return TerminalResult.success(workingDirectory,
|
||||||
|
"AVAILABLE COMMANDS",
|
||||||
|
"HELP CLEAR PWD LS CD MKDIR TOUCH CAT WRITE RM RMDIR MV OPEN");
|
||||||
|
}
|
||||||
|
if (command.arguments().size() != 1) {
|
||||||
|
return usage(workingDirectory, "help [command]");
|
||||||
|
}
|
||||||
|
return switch (command.arguments().get(0).toLowerCase(Locale.ROOT)) {
|
||||||
|
case "help" -> TerminalResult.success(workingDirectory, "HELP [COMMAND] // SHOW COMMAND HELP");
|
||||||
|
case "clear" -> TerminalResult.success(workingDirectory, "CLEAR // CLEAR TERMINAL OUTPUT");
|
||||||
|
case "pwd" -> TerminalResult.success(workingDirectory, "PWD // PRINT WORKING DIRECTORY");
|
||||||
|
case "ls" -> TerminalResult.success(workingDirectory, "LS [PATH] // LIST DIRECTORY CONTENTS");
|
||||||
|
case "cd" -> TerminalResult.success(workingDirectory, "CD [PATH] // CHANGE WORKING DIRECTORY");
|
||||||
|
case "mkdir" -> TerminalResult.success(workingDirectory, "MKDIR <PATH> // CREATE DIRECTORY");
|
||||||
|
case "touch" -> TerminalResult.success(workingDirectory, "TOUCH <PATH> // CREATE EMPTY TEXT FILE");
|
||||||
|
case "cat" -> TerminalResult.success(workingDirectory, "CAT <PATH> // PRINT TEXT FILE");
|
||||||
|
case "write" -> TerminalResult.success(workingDirectory, "WRITE <PATH> [TEXT] // REPLACE TEXT FILE CONTENTS");
|
||||||
|
case "rm" -> TerminalResult.success(workingDirectory, "RM <PATH> // DELETE TEXT FILE");
|
||||||
|
case "rmdir" -> TerminalResult.success(workingDirectory, "RMDIR <PATH> // DELETE EMPTY DIRECTORY");
|
||||||
|
case "mv" -> TerminalResult.success(workingDirectory, "MV <SOURCE> <DESTINATION> // MOVE OR RENAME");
|
||||||
|
case "open" -> TerminalResult.success(workingDirectory, "OPEN <PATH> // OPEN TEXT FILE IN ANTTEXT");
|
||||||
|
default -> TerminalResult.error(workingDirectory, "ERROR: NO HELP FOR " + command.arguments().get(0));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalResult list(TerminalFileSystem fileSystem, String workingDirectory, TerminalCommand command) {
|
||||||
|
if (command.arguments().size() > 1) {
|
||||||
|
return usage(workingDirectory, "ls [path]");
|
||||||
|
}
|
||||||
|
String path = command.arguments().isEmpty() ? workingDirectory : command.arguments().get(0);
|
||||||
|
Optional<TerminalFileSystem.Entry> entry = find(fileSystem, path, workingDirectory);
|
||||||
|
if (entry.isEmpty()) {
|
||||||
|
return missing(workingDirectory, path);
|
||||||
|
}
|
||||||
|
if (!entry.get().isDirectory()) {
|
||||||
|
return TerminalResult.success(workingDirectory, entry.get().name());
|
||||||
|
}
|
||||||
|
List<TerminalFileSystem.Entry> entries = new ArrayList<>(fileSystem.list(path, workingDirectory));
|
||||||
|
entries.sort(Comparator.comparing(TerminalFileSystem.Entry::isDirectory).reversed().thenComparing(TerminalFileSystem.Entry::name));
|
||||||
|
if (entries.isEmpty()) {
|
||||||
|
return TerminalResult.success(workingDirectory, "DIRECTORY EMPTY");
|
||||||
|
}
|
||||||
|
return new TerminalResult(TerminalResult.Status.SUCCESS, entries.stream()
|
||||||
|
.map(value -> (value.isDirectory() ? "[DIR] " : " ") + value.name()).toList(), workingDirectory, false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalResult changeDirectory(TerminalFileSystem fileSystem, String workingDirectory, TerminalCommand command) {
|
||||||
|
if (command.arguments().size() > 1) {
|
||||||
|
return usage(workingDirectory, "cd [path]");
|
||||||
|
}
|
||||||
|
String path = command.arguments().isEmpty() ? TerminalFileSystem.ROOT_PATH : command.arguments().get(0);
|
||||||
|
Optional<TerminalFileSystem.Entry> entry = find(fileSystem, path, workingDirectory);
|
||||||
|
if (entry.isEmpty() || !entry.get().isDirectory()) {
|
||||||
|
return missing(workingDirectory, path);
|
||||||
|
}
|
||||||
|
return TerminalResult.success(entry.get().path(), entry.get().path());
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalResult createDirectory(TerminalFileSystem fileSystem, String workingDirectory, TerminalCommand command) {
|
||||||
|
if (command.arguments().size() != 1) {
|
||||||
|
return usage(workingDirectory, "mkdir <path>");
|
||||||
|
}
|
||||||
|
return fileSystem.createDirectory(command.arguments().get(0), workingDirectory)
|
||||||
|
? TerminalResult.success(workingDirectory)
|
||||||
|
: TerminalResult.error(workingDirectory, "ERROR: CANNOT CREATE DIRECTORY");
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalResult createFile(TerminalFileSystem fileSystem, String workingDirectory, TerminalCommand command) {
|
||||||
|
if (command.arguments().size() != 1) {
|
||||||
|
return usage(workingDirectory, "touch <path>");
|
||||||
|
}
|
||||||
|
return fileSystem.createFile(command.arguments().get(0), workingDirectory)
|
||||||
|
? TerminalResult.success(workingDirectory)
|
||||||
|
: TerminalResult.error(workingDirectory, "ERROR: CANNOT CREATE FILE");
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalResult readFile(TerminalFileSystem fileSystem, String workingDirectory, TerminalCommand command) {
|
||||||
|
if (command.arguments().size() != 1) {
|
||||||
|
return usage(workingDirectory, "cat <path>");
|
||||||
|
}
|
||||||
|
Optional<String> contents = fileSystem.readText(command.arguments().get(0), workingDirectory);
|
||||||
|
return contents.map(value -> TerminalResult.success(workingDirectory, value.split("\\R", -1)))
|
||||||
|
.orElseGet(() -> missing(workingDirectory, command.arguments().get(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalResult writeFile(TerminalFileSystem fileSystem, String workingDirectory, TerminalCommand command) {
|
||||||
|
if (command.arguments().size() < 1 || command.arguments().size() > 2) {
|
||||||
|
return usage(workingDirectory, "write <path> [text]");
|
||||||
|
}
|
||||||
|
String contents = command.arguments().size() == 2 ? command.arguments().get(1) : "";
|
||||||
|
return fileSystem.writeText(command.arguments().get(0), workingDirectory, contents)
|
||||||
|
? TerminalResult.success(workingDirectory)
|
||||||
|
: TerminalResult.error(workingDirectory, "ERROR: CANNOT WRITE FILE");
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalResult removeFile(TerminalFileSystem fileSystem, String workingDirectory, TerminalCommand command) {
|
||||||
|
if (command.arguments().size() != 1) {
|
||||||
|
return usage(workingDirectory, "rm <path>");
|
||||||
|
}
|
||||||
|
return fileSystem.deleteFile(command.arguments().get(0), workingDirectory)
|
||||||
|
? TerminalResult.success(workingDirectory)
|
||||||
|
: TerminalResult.error(workingDirectory, "ERROR: CANNOT DELETE FILE");
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalResult removeDirectory(TerminalFileSystem fileSystem, String workingDirectory, TerminalCommand command) {
|
||||||
|
if (command.arguments().size() != 1) {
|
||||||
|
return usage(workingDirectory, "rmdir <path>");
|
||||||
|
}
|
||||||
|
return fileSystem.deleteEmptyDirectory(command.arguments().get(0), workingDirectory)
|
||||||
|
? TerminalResult.success(workingDirectory)
|
||||||
|
: TerminalResult.error(workingDirectory, "ERROR: DIRECTORY NOT EMPTY OR NOT FOUND");
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalResult move(TerminalFileSystem fileSystem, String workingDirectory, TerminalCommand command) {
|
||||||
|
if (command.arguments().size() != 2) {
|
||||||
|
return usage(workingDirectory, "mv <source> <destination>");
|
||||||
|
}
|
||||||
|
return fileSystem.move(command.arguments().get(0), command.arguments().get(1), workingDirectory)
|
||||||
|
? TerminalResult.success(workingDirectory)
|
||||||
|
: TerminalResult.error(workingDirectory, "ERROR: CANNOT MOVE PATH");
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalResult open(TerminalFileSystem fileSystem, String workingDirectory, TerminalCommand command) {
|
||||||
|
if (command.arguments().size() != 1) {
|
||||||
|
return usage(workingDirectory, "open <path>");
|
||||||
|
}
|
||||||
|
Optional<TerminalFileSystem.Entry> entry = find(fileSystem, command.arguments().get(0), workingDirectory);
|
||||||
|
if (entry.isEmpty() || entry.get().isDirectory()) {
|
||||||
|
return missing(workingDirectory, command.arguments().get(0));
|
||||||
|
}
|
||||||
|
return TerminalResult.open(workingDirectory, entry.get().path());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<TerminalFileSystem.Entry> find(TerminalFileSystem fileSystem, String path, String workingDirectory) {
|
||||||
|
try {
|
||||||
|
return fileSystem.find(path, workingDirectory);
|
||||||
|
} catch (RuntimeException ignored) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeWorkingDirectory(TerminalFileSystem fileSystem, String workingDirectory) {
|
||||||
|
try {
|
||||||
|
String normalized = fileSystem.normalize(workingDirectory, TerminalFileSystem.ROOT_PATH);
|
||||||
|
Optional<TerminalFileSystem.Entry> entry = fileSystem.find(normalized, TerminalFileSystem.ROOT_PATH);
|
||||||
|
return entry.filter(TerminalFileSystem.Entry::isDirectory).map(TerminalFileSystem.Entry::path).orElse(TerminalFileSystem.ROOT_PATH);
|
||||||
|
} catch (RuntimeException ignored) {
|
||||||
|
return TerminalFileSystem.ROOT_PATH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalResult missing(String workingDirectory, String path) {
|
||||||
|
return TerminalResult.error(workingDirectory, "ERROR: PATH NOT FOUND: " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalResult usage(String workingDirectory, String usage) {
|
||||||
|
return TerminalResult.error(workingDirectory, "USAGE: " + usage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalResult requireArguments(String workingDirectory, TerminalCommand command, int count, TerminalResult result) {
|
||||||
|
return command.arguments().size() == count ? result : usage(workingDirectory, command.name() + " requires " + count + " arguments");
|
||||||
|
}
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package com.craisinlord.antarchy.content.computer.terminal;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface TerminalFileSystem {
|
||||||
|
String ROOT_PATH = "/";
|
||||||
|
|
||||||
|
Optional<Entry> find(String path, String workingDirectory);
|
||||||
|
|
||||||
|
List<Entry> list(String path, String workingDirectory);
|
||||||
|
|
||||||
|
boolean createDirectory(String path, String workingDirectory);
|
||||||
|
|
||||||
|
boolean createFile(String path, String workingDirectory);
|
||||||
|
|
||||||
|
Optional<String> readText(String path, String workingDirectory);
|
||||||
|
|
||||||
|
boolean writeText(String path, String workingDirectory, String contents);
|
||||||
|
|
||||||
|
boolean deleteFile(String path, String workingDirectory);
|
||||||
|
|
||||||
|
boolean deleteEmptyDirectory(String path, String workingDirectory);
|
||||||
|
|
||||||
|
boolean move(String source, String destination, String workingDirectory);
|
||||||
|
|
||||||
|
String normalize(String path, String workingDirectory);
|
||||||
|
|
||||||
|
record Entry(String name, String path, EntryType type) {
|
||||||
|
public boolean isDirectory() {
|
||||||
|
return type == EntryType.DIRECTORY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EntryType {
|
||||||
|
FILE,
|
||||||
|
DIRECTORY
|
||||||
|
}
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package com.craisinlord.antarchy.content.computer.terminal;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record TerminalResult(Status status, List<String> lines, String workingDirectory, boolean clearOutput, String openPath) {
|
||||||
|
public TerminalResult {
|
||||||
|
status = status == null ? Status.ERROR : status;
|
||||||
|
lines = List.copyOf(lines);
|
||||||
|
workingDirectory = workingDirectory == null || workingDirectory.isBlank() ? TerminalFileSystem.ROOT_PATH : workingDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TerminalResult success(String workingDirectory, String... lines) {
|
||||||
|
return new TerminalResult(Status.SUCCESS, List.of(lines), workingDirectory, false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TerminalResult error(String workingDirectory, String... lines) {
|
||||||
|
return new TerminalResult(Status.ERROR, List.of(lines), workingDirectory, false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TerminalResult clear(String workingDirectory) {
|
||||||
|
return new TerminalResult(Status.SUCCESS, List.of(), workingDirectory, true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TerminalResult open(String workingDirectory, String path) {
|
||||||
|
return new TerminalResult(Status.OPEN, List.of("OPENING " + path), workingDirectory, false, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Status {
|
||||||
|
SUCCESS,
|
||||||
|
ERROR,
|
||||||
|
OPEN
|
||||||
|
}
|
||||||
|
}
|
||||||
-15
@@ -75,11 +75,9 @@ public class OctopusBombEntity extends Monster implements GeoEntity {
|
|||||||
|
|
||||||
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
|
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
|
||||||
|
|
||||||
// Squid-style wander
|
|
||||||
private float wanderDx, wanderDy, wanderDz;
|
private float wanderDx, wanderDy, wanderDz;
|
||||||
private int wanderRetargetTicks;
|
private int wanderRetargetTicks;
|
||||||
|
|
||||||
// Combat
|
|
||||||
private int attackCooldown;
|
private int attackCooldown;
|
||||||
private int actionTicks;
|
private int actionTicks;
|
||||||
private int lastAttackState;
|
private int lastAttackState;
|
||||||
@@ -88,7 +86,6 @@ public class OctopusBombEntity extends Monster implements GeoEntity {
|
|||||||
private float orbitDirection = 1.0F;
|
private float orbitDirection = 1.0F;
|
||||||
private int inkSprayCooldown;
|
private int inkSprayCooldown;
|
||||||
|
|
||||||
// Kraken spawn flag
|
|
||||||
private boolean spawnedByKraken;
|
private boolean spawnedByKraken;
|
||||||
|
|
||||||
// Explosive impact (fired from the R.P.O. Launcher)
|
// Explosive impact (fired from the R.P.O. Launcher)
|
||||||
@@ -364,10 +361,6 @@ public class OctopusBombEntity extends Monster implements GeoEntity {
|
|||||||
this.hasImpulse = true;
|
this.hasImpulse = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Squid-style wander
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
private void tickWanderMovement() {
|
private void tickWanderMovement() {
|
||||||
if (--this.wanderRetargetTicks <= 0) {
|
if (--this.wanderRetargetTicks <= 0) {
|
||||||
this.wanderRetargetTicks = 40 + this.random.nextInt(40);
|
this.wanderRetargetTicks = 40 + this.random.nextInt(40);
|
||||||
@@ -386,10 +379,6 @@ public class OctopusBombEntity extends Monster implements GeoEntity {
|
|||||||
this.setDeltaMovement(this.getDeltaMovement().add(this.wanderDx, dy, this.wanderDz));
|
this.setDeltaMovement(this.getDeltaMovement().add(this.wanderDx, dy, this.wanderDz));
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Ink spray
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
private boolean hasHitExplosiveTarget() {
|
private boolean hasHitExplosiveTarget() {
|
||||||
AABB box = this.getBoundingBox();
|
AABB box = this.getBoundingBox();
|
||||||
return !this.level().getEntitiesOfClass(LivingEntity.class, box,
|
return !this.level().getEntitiesOfClass(LivingEntity.class, box,
|
||||||
@@ -445,10 +434,6 @@ public class OctopusBombEntity extends Monster implements GeoEntity {
|
|||||||
serverLevel.sendParticles(OCTOPUS_BOMB_INK_PARTICLE, cx, cy, cz, 30, 3.5D, 1.8D, 3.5D, 0.02D);
|
serverLevel.sendParticles(OCTOPUS_BOMB_INK_PARTICLE, cx, cy, cz, 30, 3.5D, 1.8D, 3.5D, 0.02D);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Combat
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
private void tickPursuitMovement(LivingEntity target) {
|
private void tickPursuitMovement(LivingEntity target) {
|
||||||
if (this.strafeRetargetTicks-- <= 0) {
|
if (this.strafeRetargetTicks-- <= 0) {
|
||||||
this.strafeRetargetTicks = 10 + this.random.nextInt(12);
|
this.strafeRetargetTicks = 10 + this.random.nextInt(12);
|
||||||
|
|||||||
+31
-4
@@ -95,6 +95,7 @@ public abstract class BaseAntEntity extends Animal implements GeoEntity {
|
|||||||
private static final int NEST_SEARCH_INTERVAL_TICKS = 80;
|
private static final int NEST_SEARCH_INTERVAL_TICKS = 80;
|
||||||
private static final int FOOD_NEST_SEARCH_INTERVAL_TICKS = 20;
|
private static final int FOOD_NEST_SEARCH_INTERVAL_TICKS = 20;
|
||||||
private static final int CHEST_SEARCH_INTERVAL_TICKS = 20;
|
private static final int CHEST_SEARCH_INTERVAL_TICKS = 20;
|
||||||
|
private static final int CHEST_VALIDATION_INTERVAL_TICKS = 10;
|
||||||
private static final int SHARED_CHEST_MEMORY_TICKS = 120;
|
private static final int SHARED_CHEST_MEMORY_TICKS = 120;
|
||||||
private static final double FOOD_CHEST_SEARCH_RADIUS = 20.0D;
|
private static final double FOOD_CHEST_SEARCH_RADIUS = 20.0D;
|
||||||
private static final int GROUND_FOOD_CACHE_INTERVAL_TICKS = 20;
|
private static final int GROUND_FOOD_CACHE_INTERVAL_TICKS = 20;
|
||||||
@@ -136,6 +137,10 @@ public abstract class BaseAntEntity extends Animal implements GeoEntity {
|
|||||||
private BlockPos targetFoodChestPos;
|
private BlockPos targetFoodChestPos;
|
||||||
private int nextFoodChestSearchTick;
|
private int nextFoodChestSearchTick;
|
||||||
@Nullable
|
@Nullable
|
||||||
|
private BlockPos cachedFoodChestValidationPos;
|
||||||
|
private boolean cachedFoodChestValidationResult;
|
||||||
|
private int nextFoodChestValidationTick;
|
||||||
|
@Nullable
|
||||||
private BlockPos recentFoodChestPos;
|
private BlockPos recentFoodChestPos;
|
||||||
private int recentFoodChestExpireTick;
|
private int recentFoodChestExpireTick;
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -749,17 +754,36 @@ public abstract class BaseAntEntity extends Animal implements GeoEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (bestPos != null) {
|
||||||
|
this.cachedFoodChestValidationPos = bestPos;
|
||||||
|
this.cachedFoodChestValidationResult = true;
|
||||||
|
this.nextFoodChestValidationTick = this.tickCount + CHEST_VALIDATION_INTERVAL_TICKS;
|
||||||
|
}
|
||||||
return bestPos;
|
return bestPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidFoodChest(BlockPos chestPos) {
|
private boolean isValidFoodChest(BlockPos chestPos) {
|
||||||
|
if (this.cachedFoodChestValidationPos != null
|
||||||
|
&& this.cachedFoodChestValidationPos.equals(chestPos)
|
||||||
|
&& this.tickCount < this.nextFoodChestValidationTick) {
|
||||||
|
return this.cachedFoodChestValidationResult;
|
||||||
|
}
|
||||||
|
|
||||||
BlockEntity blockEntity = this.level().getBlockEntity(chestPos);
|
BlockEntity blockEntity = this.level().getBlockEntity(chestPos);
|
||||||
return blockEntity instanceof ChestBlockEntity chestBlockEntity && this.findStealableFoodSlot(chestBlockEntity) >= 0;
|
boolean valid = blockEntity instanceof ChestBlockEntity chestBlockEntity
|
||||||
|
&& this.findStealableFoodSlot(chestBlockEntity) >= 0;
|
||||||
|
this.cachedFoodChestValidationPos = chestPos.immutable();
|
||||||
|
this.cachedFoodChestValidationResult = valid;
|
||||||
|
this.nextFoodChestValidationTick = this.tickCount + CHEST_VALIDATION_INTERVAL_TICKS;
|
||||||
|
return valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearFoodChestTarget() {
|
private void clearFoodChestTarget() {
|
||||||
this.targetFoodChestPos = null;
|
this.targetFoodChestPos = null;
|
||||||
this.nextFoodChestSearchTick = 0;
|
this.nextFoodChestSearchTick = 0;
|
||||||
|
this.cachedFoodChestValidationPos = null;
|
||||||
|
this.cachedFoodChestValidationResult = false;
|
||||||
|
this.nextFoodChestValidationTick = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean stealFoodFromChest(BlockPos chestPos) {
|
private boolean stealFoodFromChest(BlockPos chestPos) {
|
||||||
@@ -1390,13 +1414,16 @@ public abstract class BaseAntEntity extends Animal implements GeoEntity {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (leader.targetFoodChestPos != null && this.isValidFoodChest(leader.targetFoodChestPos)) {
|
if (leader.targetFoodChestPos != null
|
||||||
|
&& leader.cachedFoodChestValidationPos != null
|
||||||
|
&& leader.cachedFoodChestValidationPos.equals(leader.targetFoodChestPos)
|
||||||
|
&& leader.cachedFoodChestValidationResult
|
||||||
|
&& leader.tickCount < leader.nextFoodChestValidationTick) {
|
||||||
return leader.targetFoodChestPos;
|
return leader.targetFoodChestPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (leader.recentFoodChestPos != null
|
if (leader.recentFoodChestPos != null
|
||||||
&& leader.tickCount <= leader.recentFoodChestExpireTick
|
&& leader.tickCount <= leader.recentFoodChestExpireTick) {
|
||||||
&& this.isValidFoodChest(leader.recentFoodChestPos)) {
|
|
||||||
return leader.recentFoodChestPos;
|
return leader.recentFoodChestPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -994,7 +994,7 @@ public class KingEntity extends RoyalBossEntity {
|
|||||||
}
|
}
|
||||||
@Override public void onActive(int elapsedTicks) {
|
@Override public void onActive(int elapsedTicks) {
|
||||||
KingEntity.this.raiseIceSpikes(level, target);
|
KingEntity.this.raiseIceSpikes(level, target);
|
||||||
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_BEAM_SHOOT.get(), 0.8F);
|
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_ICE_SPIKES.get(), 0.8F);
|
||||||
}
|
}
|
||||||
})) return;
|
})) return;
|
||||||
this.iceSpikeCooldownTicks = AntarchySettings.kingIceSpikeCooldownTicks();
|
this.iceSpikeCooldownTicks = AntarchySettings.kingIceSpikeCooldownTicks();
|
||||||
|
|||||||
+5
@@ -18,6 +18,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
public final class ComputerGuideData extends SimplePreparableReloadListener<ComputerGuideData.LoadedData> {
|
public final class ComputerGuideData extends SimplePreparableReloadListener<ComputerGuideData.LoadedData> {
|
||||||
private static final ComputerGuideData INSTANCE = new ComputerGuideData();
|
private static final ComputerGuideData INSTANCE = new ComputerGuideData();
|
||||||
|
private static final ResourceLocation INTRODUCTION = ResourceLocation.fromNamespaceAndPath("antarchy", "introduction");
|
||||||
private static volatile Map<ResourceLocation, Entry> entries = Map.of();
|
private static volatile Map<ResourceLocation, Entry> entries = Map.of();
|
||||||
private static volatile Map<ResourceLocation, Disk> disks = Map.of();
|
private static volatile Map<ResourceLocation, Disk> disks = Map.of();
|
||||||
|
|
||||||
@@ -39,6 +40,10 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
|
|||||||
public static List<Entry> entriesFor(List<ResourceLocation> diskIds) {
|
public static List<Entry> entriesFor(List<ResourceLocation> diskIds) {
|
||||||
Map<ResourceLocation, Entry> result = new LinkedHashMap<>();
|
Map<ResourceLocation, Entry> result = new LinkedHashMap<>();
|
||||||
for (ResourceLocation diskId : diskIds) {
|
for (ResourceLocation diskId : diskIds) {
|
||||||
|
if (INTRODUCTION.equals(diskId)) {
|
||||||
|
result.put(INTRODUCTION, new Entry(INTRODUCTION, "article", "general", "guide.antarchy.entry.introduction.title", "guide.antarchy.entry.introduction.subtitle", List.of("guide.antarchy.entry.introduction.description"), "", "", ""));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
Disk disk = disks.get(diskId);
|
Disk disk = disks.get(diskId);
|
||||||
if (disk == null) {
|
if (disk == null) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -176,7 +176,6 @@ public class BigBerthaItem extends SwordItem implements GeoItem {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toreterror jump tracking
|
|
||||||
if (isToreterrorJumping(stack) && player.getMainHandItem() == stack) {
|
if (isToreterrorJumping(stack) && player.getMainHandItem() == stack) {
|
||||||
player.fallDistance = 0.0F; // prevent fall damage during the crash-down
|
player.fallDistance = 0.0F; // prevent fall damage during the crash-down
|
||||||
boolean launched = readInt(stack, TORETERROR_LAUNCHED_TAG) == 1;
|
boolean launched = readInt(stack, TORETERROR_LAUNCHED_TAG) == 1;
|
||||||
@@ -197,7 +196,6 @@ public class BigBerthaItem extends SwordItem implements GeoItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tick client shake counter
|
|
||||||
if (level.isClientSide && clientShakeTicks > 0) {
|
if (level.isClientSide && clientShakeTicks > 0) {
|
||||||
clientShakeTicks--;
|
clientShakeTicks--;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import java.util.function.Consumer;
|
|||||||
|
|
||||||
public final class ComputerItem extends BlockItem implements GeoItem {
|
public final class ComputerItem extends BlockItem implements GeoItem {
|
||||||
private static final ResourceLocation MODEL = ResourceLocation.fromNamespaceAndPath("antarchy", "geo/computer.geo.json");
|
private static final ResourceLocation MODEL = ResourceLocation.fromNamespaceAndPath("antarchy", "geo/computer.geo.json");
|
||||||
private static final ResourceLocation TEXTURE = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/block/computer/computer.png");
|
private static final ResourceLocation TEXTURE = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/item/computer.png");
|
||||||
private static final ResourceLocation ANIMATION = ResourceLocation.fromNamespaceAndPath("antarchy", "animations/computer.animation.json");
|
private static final ResourceLocation ANIMATION = ResourceLocation.fromNamespaceAndPath("antarchy", "animations/computer.animation.json");
|
||||||
private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this);
|
private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this);
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ public final class ComputerItem extends BlockItem implements GeoItem {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
|
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
|
||||||
controllers.add(new AnimationController<>(this, "state", state -> state.setAndContinue(RawAnimation.begin().thenLoop("off_state"))));
|
controllers.add(new AnimationController<>(this, "state", state -> state.setAndContinue(RawAnimation.begin().thenLoop("on_state"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
package com.craisinlord.antarchy.content.network;
|
||||||
|
|
||||||
|
import com.craisinlord.antarchy.content.antmail.AntmailAttachment;
|
||||||
|
import com.craisinlord.antarchy.content.antmail.AntmailWire;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public final class AntmailNetworking {
|
||||||
|
private static Consumer<Object> sender = payload -> { };
|
||||||
|
|
||||||
|
private AntmailNetworking() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setSender(Consumer<Object> sender) {
|
||||||
|
AntmailNetworking.sender = sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setup(BlockPos pos, String username) {
|
||||||
|
sender.accept(new AntmailSetupPayload(pos, username));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void requestState(BlockPos pos) {
|
||||||
|
sender.accept(new AntmailStateRequestPayload(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void send(BlockPos pos, String recipient, String subject, String body, List<AntmailAttachment> attachments) {
|
||||||
|
sender.accept(new AntmailSendPayload(pos, recipient, subject, body, AntmailWire.encodeAttachments(attachments)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void markRead(BlockPos pos, UUID messageId) {
|
||||||
|
sender.accept(new AntmailReadPayload(pos, messageId.toString(), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void markUnread(BlockPos pos, UUID messageId) {
|
||||||
|
sender.accept(new AntmailReadPayload(pos, messageId.toString(), false));
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package com.craisinlord.antarchy.content.network;
|
||||||
|
|
||||||
|
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class AntmailPayloadTypes {
|
||||||
|
private AntmailPayloadTypes() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<CustomPacketPayload.Type<?>> all() {
|
||||||
|
return List.of(
|
||||||
|
AntmailSetupPayload.TYPE,
|
||||||
|
AntmailStateRequestPayload.TYPE,
|
||||||
|
AntmailSendPayload.TYPE,
|
||||||
|
AntmailReadPayload.TYPE,
|
||||||
|
AntmailResultPayload.TYPE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package com.craisinlord.antarchy.content.network;
|
||||||
|
|
||||||
|
import com.craisinlord.antarchy.Antarchy;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||||
|
import net.minecraft.network.codec.ByteBufCodecs;
|
||||||
|
import net.minecraft.network.codec.StreamCodec;
|
||||||
|
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
public record AntmailReadPayload(BlockPos pos, String messageId, boolean read) implements CustomPacketPayload {
|
||||||
|
public static final Type<AntmailReadPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "antmail_read"));
|
||||||
|
public static final StreamCodec<RegistryFriendlyByteBuf, AntmailReadPayload> STREAM_CODEC = StreamCodec.composite(BlockPos.STREAM_CODEC, AntmailReadPayload::pos, ByteBufCodecs.stringUtf8(64), AntmailReadPayload::messageId, ByteBufCodecs.BOOL, AntmailReadPayload::read, AntmailReadPayload::new);
|
||||||
|
@Override public Type<AntmailReadPayload> type() { return TYPE; }
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package com.craisinlord.antarchy.content.network;
|
||||||
|
|
||||||
|
import com.craisinlord.antarchy.Antarchy;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||||
|
import net.minecraft.network.codec.ByteBufCodecs;
|
||||||
|
import net.minecraft.network.codec.StreamCodec;
|
||||||
|
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
public record AntmailResultPayload(BlockPos pos, int status, String address, String messageId, String detail, String data) implements CustomPacketPayload {
|
||||||
|
public static final Type<AntmailResultPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "antmail_result"));
|
||||||
|
public static final StreamCodec<RegistryFriendlyByteBuf, AntmailResultPayload> STREAM_CODEC = StreamCodec.composite(BlockPos.STREAM_CODEC, AntmailResultPayload::pos, ByteBufCodecs.VAR_INT, AntmailResultPayload::status, ByteBufCodecs.stringUtf8(64), AntmailResultPayload::address, ByteBufCodecs.stringUtf8(64), AntmailResultPayload::messageId, ByteBufCodecs.stringUtf8(128), AntmailResultPayload::detail, ByteBufCodecs.stringUtf8(65536), AntmailResultPayload::data, AntmailResultPayload::new);
|
||||||
|
@Override public Type<AntmailResultPayload> type() { return TYPE; }
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package com.craisinlord.antarchy.content.network;
|
||||||
|
|
||||||
|
import com.craisinlord.antarchy.Antarchy;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||||
|
import net.minecraft.network.codec.ByteBufCodecs;
|
||||||
|
import net.minecraft.network.codec.StreamCodec;
|
||||||
|
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
public record AntmailSendPayload(BlockPos pos, String recipient, String subject, String body, String attachments) implements CustomPacketPayload {
|
||||||
|
public static final Type<AntmailSendPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "antmail_send"));
|
||||||
|
public static final StreamCodec<RegistryFriendlyByteBuf, AntmailSendPayload> STREAM_CODEC = StreamCodec.composite(BlockPos.STREAM_CODEC, AntmailSendPayload::pos, ByteBufCodecs.stringUtf8(64), AntmailSendPayload::recipient, ByteBufCodecs.stringUtf8(64), AntmailSendPayload::subject, ByteBufCodecs.stringUtf8(16384), AntmailSendPayload::body, ByteBufCodecs.stringUtf8(65536), AntmailSendPayload::attachments, AntmailSendPayload::new);
|
||||||
|
@Override public Type<AntmailSendPayload> type() { return TYPE; }
|
||||||
|
}
|
||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
package com.craisinlord.antarchy.content.network;
|
||||||
|
|
||||||
|
import com.craisinlord.antarchy.content.antmail.AntmailAddress;
|
||||||
|
import com.craisinlord.antarchy.content.antmail.AntmailAttachment;
|
||||||
|
import com.craisinlord.antarchy.content.antmail.AntmailDeliveryResult;
|
||||||
|
import com.craisinlord.antarchy.content.antmail.AntmailMessage;
|
||||||
|
import com.craisinlord.antarchy.content.antmail.AntmailMailbox;
|
||||||
|
import com.craisinlord.antarchy.content.antmail.AntmailServerData;
|
||||||
|
import com.craisinlord.antarchy.content.antmail.AntmailValidation;
|
||||||
|
import com.craisinlord.antarchy.content.antmail.AntmailWire;
|
||||||
|
import com.craisinlord.antarchy.content.block.entity.ComputerBlockEntity;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
public final class AntmailServerHandler {
|
||||||
|
private static BiConsumer<ServerPlayer, AntmailResultPayload> resultSender = (player, payload) -> { };
|
||||||
|
|
||||||
|
private AntmailServerHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setResultSender(BiConsumer<ServerPlayer, AntmailResultPayload> sender) {
|
||||||
|
resultSender = sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void handle(ServerPlayer player, AntmailSetupPayload payload) {
|
||||||
|
ComputerBlockEntity computer = computer(player, payload.pos());
|
||||||
|
if (computer == null || !computer.canUseFileSystem(player)) {
|
||||||
|
result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), "", "", "unauthorized", "");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AntmailServerData data = AntmailServerData.access(player.server);
|
||||||
|
AntmailServerData.Registration registration = data.register(player.server, player.serverLevel(), payload.pos(), payload.username());
|
||||||
|
result(player, payload.pos(), registration.valid() ? AntmailDeliveryResult.Status.DELIVERED.ordinal() : AntmailDeliveryResult.Status.FAILED.ordinal(), registration.valid() ? registration.address().fullAddress() : "", "", registration.reason(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void handle(ServerPlayer player, AntmailStateRequestPayload payload) {
|
||||||
|
ComputerBlockEntity computer = authorizedComputer(player, payload.pos());
|
||||||
|
if (computer == null) {
|
||||||
|
result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), "", "", "unauthorized", "");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AntmailServerData data = AntmailServerData.access(player.server);
|
||||||
|
AntmailAddress address = data.addressAt(player.serverLevel().dimension().location(), payload.pos());
|
||||||
|
if (address == null) {
|
||||||
|
result(player, payload.pos(), AntmailDeliveryResult.Status.ADDRESS_NOT_FOUND.ordinal(), "", "", "unconfigured", "");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AntmailMailbox mailbox = data.mailboxOrCreate(address);
|
||||||
|
result(player, payload.pos(), AntmailDeliveryResult.Status.DELIVERED.ordinal(), address.fullAddress(), "", "", AntmailWire.encodeTag(mailbox.toTag()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void handle(ServerPlayer player, AntmailSendPayload payload) {
|
||||||
|
ComputerBlockEntity computer = authorizedComputer(player, payload.pos());
|
||||||
|
if (computer == null) {
|
||||||
|
result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), "", "", "unauthorized", "");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AntmailServerData data = AntmailServerData.access(player.server);
|
||||||
|
AntmailAddress sender = data.addressAt(player.serverLevel().dimension().location(), payload.pos());
|
||||||
|
if (sender == null) {
|
||||||
|
result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), "", "", "unconfigured", "");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
AntmailAddress recipient = AntmailAddress.parse(payload.recipient());
|
||||||
|
List<AntmailAttachment> attachments = AntmailWire.decodeAttachments(payload.attachments());
|
||||||
|
AntmailMessage message = AntmailMessage.create(sender, recipient, payload.subject(), payload.body(), player.serverLevel().getGameTime(), attachments);
|
||||||
|
AntmailDeliveryResult delivery = data.deliver(player.server, message);
|
||||||
|
result(player, payload.pos(), delivery.status().ordinal(), recipient.fullAddress(), message.id().toString(), delivery.detail(), "");
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
result(player, payload.pos(), AntmailDeliveryResult.Status.MESSAGE_INVALID.ordinal(), "", "", "invalid_message", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void handle(ServerPlayer player, AntmailReadPayload payload) {
|
||||||
|
ComputerBlockEntity computer = authorizedComputer(player, payload.pos());
|
||||||
|
if (computer == null) {
|
||||||
|
result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), "", payload.messageId(), "unauthorized", "");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AntmailServerData data = AntmailServerData.access(player.server);
|
||||||
|
AntmailAddress address = data.addressAt(player.serverLevel().dimension().location(), payload.pos());
|
||||||
|
if (address == null) {
|
||||||
|
result(player, payload.pos(), AntmailDeliveryResult.Status.ADDRESS_NOT_FOUND.ordinal(), "", payload.messageId(), "unconfigured", "");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
UUID id = UUID.fromString(payload.messageId());
|
||||||
|
AntmailMailbox mailbox = data.mailboxOrCreate(address);
|
||||||
|
boolean changed = payload.read() ? mailbox.markRead(id) : markUnread(mailbox, id);
|
||||||
|
if (changed) data.setDirty();
|
||||||
|
result(player, payload.pos(), changed ? AntmailDeliveryResult.Status.DELIVERED.ordinal() : AntmailDeliveryResult.Status.FAILED.ordinal(), address.fullAddress(), payload.messageId(), changed ? "" : "not_found", "");
|
||||||
|
} catch (IllegalArgumentException exception) {
|
||||||
|
result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), address.fullAddress(), payload.messageId(), "invalid_id", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ComputerBlockEntity authorizedComputer(ServerPlayer player, BlockPos pos) {
|
||||||
|
ComputerBlockEntity computer = computer(player, pos);
|
||||||
|
return computer != null && computer.canUseFileSystem(player) ? computer : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean markUnread(AntmailMailbox mailbox, UUID id) {
|
||||||
|
for (com.craisinlord.antarchy.content.antmail.AntmailMessage message : mailbox.inbox()) {
|
||||||
|
if (message.id().equals(id)) {
|
||||||
|
message.markUnread();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ComputerBlockEntity computer(ServerPlayer player, BlockPos pos) {
|
||||||
|
if (!player.serverLevel().hasChunkAt(pos) || player.distanceToSqr(pos.getX() + .5D, pos.getY() + .5D, pos.getZ() + .5D) > 64D) return null;
|
||||||
|
BlockEntity blockEntity = player.serverLevel().getBlockEntity(pos);
|
||||||
|
return blockEntity instanceof ComputerBlockEntity computer ? computer : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void result(ServerPlayer player, BlockPos pos, int status, String address, String messageId, String detail, String data) {
|
||||||
|
resultSender.accept(player, new AntmailResultPayload(pos, status, address, messageId, detail, data));
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package com.craisinlord.antarchy.content.network;
|
||||||
|
|
||||||
|
import com.craisinlord.antarchy.Antarchy;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||||
|
import net.minecraft.network.codec.ByteBufCodecs;
|
||||||
|
import net.minecraft.network.codec.StreamCodec;
|
||||||
|
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
public record AntmailSetupPayload(BlockPos pos, String username) implements CustomPacketPayload {
|
||||||
|
public static final Type<AntmailSetupPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "antmail_setup"));
|
||||||
|
public static final StreamCodec<RegistryFriendlyByteBuf, AntmailSetupPayload> STREAM_CODEC = StreamCodec.composite(BlockPos.STREAM_CODEC, AntmailSetupPayload::pos, ByteBufCodecs.stringUtf8(32), AntmailSetupPayload::username, AntmailSetupPayload::new);
|
||||||
|
@Override public Type<AntmailSetupPayload> type() { return TYPE; }
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package com.craisinlord.antarchy.content.network;
|
||||||
|
|
||||||
|
import com.craisinlord.antarchy.Antarchy;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||||
|
import net.minecraft.network.codec.StreamCodec;
|
||||||
|
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
public record AntmailStateRequestPayload(BlockPos pos) implements CustomPacketPayload {
|
||||||
|
public static final Type<AntmailStateRequestPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "antmail_state_request"));
|
||||||
|
public static final StreamCodec<RegistryFriendlyByteBuf, AntmailStateRequestPayload> STREAM_CODEC = StreamCodec.composite(BlockPos.STREAM_CODEC, AntmailStateRequestPayload::pos, AntmailStateRequestPayload::new);
|
||||||
|
@Override public Type<AntmailStateRequestPayload> type() { return TYPE; }
|
||||||
|
}
|
||||||
+212
@@ -0,0 +1,212 @@
|
|||||||
|
package com.craisinlord.antarchy.content.network;
|
||||||
|
|
||||||
|
import com.craisinlord.antarchy.content.block.entity.ComputerBlockEntity;
|
||||||
|
import com.craisinlord.antarchy.content.computer.ComputerFileSystem;
|
||||||
|
import com.craisinlord.antarchy.content.antmail.AntmailWire;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
public final class ComputerAccessHandler {
|
||||||
|
private static BiConsumer<ServerPlayer, ComputerAccessResultPayload> resultSender = (player, result) -> {
|
||||||
|
};
|
||||||
|
|
||||||
|
private ComputerAccessHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setResultSender(BiConsumer<ServerPlayer, ComputerAccessResultPayload> sender) {
|
||||||
|
resultSender = sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void handle(ServerPlayer player, ComputerAccessPayload payload) {
|
||||||
|
if (payload.value().length() > 65536 || player.level().isClientSide || !player.serverLevel().hasChunkAt(payload.pos()) ||
|
||||||
|
player.distanceToSqr(payload.pos().getX() + 0.5D, payload.pos().getY() + 0.5D, payload.pos().getZ() + 0.5D) > 64.0D) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BlockEntity blockEntity = player.serverLevel().getBlockEntity(payload.pos());
|
||||||
|
if (!(blockEntity instanceof ComputerBlockEntity computer)) {
|
||||||
|
send(player, payload, ComputerAccessResultPayload.INVALID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (payload.action()) {
|
||||||
|
case ComputerAccessPayload.OPEN -> open(player, computer, payload);
|
||||||
|
case ComputerAccessPayload.SETUP -> setup(player, computer, payload);
|
||||||
|
case ComputerAccessPayload.LOGIN -> login(player, computer, payload);
|
||||||
|
case ComputerAccessPayload.LOGOUT -> {
|
||||||
|
computer.logout();
|
||||||
|
send(player, payload, ComputerAccessResultPayload.READY);
|
||||||
|
}
|
||||||
|
case ComputerAccessPayload.CLOSE -> {
|
||||||
|
computer.releaseUser(player);
|
||||||
|
send(player, payload, computer.isAuthenticated() ? ComputerAccessResultPayload.SUCCESS : ComputerAccessResultPayload.READY);
|
||||||
|
}
|
||||||
|
case ComputerAccessPayload.CHANGE_PASSWORD -> {
|
||||||
|
boolean changed = computer.setPassword(player, payload.value());
|
||||||
|
send(player, payload, changed ? ComputerAccessResultPayload.SUCCESS : ComputerAccessResultPayload.INVALID_PASSWORD);
|
||||||
|
}
|
||||||
|
case ComputerAccessPayload.EJECT -> eject(player, computer, payload);
|
||||||
|
case ComputerAccessPayload.FILE_LIST -> fileList(player, computer, payload);
|
||||||
|
case ComputerAccessPayload.FILE_OPEN -> fileOpen(player, computer, payload);
|
||||||
|
case ComputerAccessPayload.FILE_CREATE -> fileCreate(player, computer, payload);
|
||||||
|
case ComputerAccessPayload.FILE_SAVE -> fileSave(player, computer, payload);
|
||||||
|
case ComputerAccessPayload.DESKTOP_STATE -> desktopState(player, computer, payload);
|
||||||
|
case ComputerAccessPayload.DESKTOP_WALLPAPER -> desktopWallpaper(player, computer, payload);
|
||||||
|
default -> send(player, payload, ComputerAccessResultPayload.INVALID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void open(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
|
||||||
|
if (computer.hasActiveUser() && !computer.isAuthenticatedBy(player)) {
|
||||||
|
send(player, payload, ComputerAccessResultPayload.BUSY);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!computer.hasPassword()) {
|
||||||
|
computer.claimUser(player);
|
||||||
|
send(player, payload, ComputerAccessResultPayload.SETUP_REQUIRED);
|
||||||
|
} else if (computer.isAuthenticated()) {
|
||||||
|
computer.claimUser(player);
|
||||||
|
send(player, payload, ComputerAccessResultPayload.SUCCESS);
|
||||||
|
} else {
|
||||||
|
send(player, payload, ComputerAccessResultPayload.READY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setup(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
|
||||||
|
if (!computer.hasPassword() && computer.initializePassword(player, payload.value())) {
|
||||||
|
send(player, payload, ComputerAccessResultPayload.SUCCESS);
|
||||||
|
} else {
|
||||||
|
send(player, payload, computer.hasActiveUser() ? ComputerAccessResultPayload.BUSY : ComputerAccessResultPayload.INVALID_PASSWORD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void login(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
|
||||||
|
if (computer.hasActiveUser() && !computer.isAuthenticatedBy(player)) {
|
||||||
|
send(player, payload, ComputerAccessResultPayload.BUSY);
|
||||||
|
} else if (computer.authenticate(player, payload.value())) {
|
||||||
|
send(player, payload, ComputerAccessResultPayload.SUCCESS);
|
||||||
|
} else {
|
||||||
|
send(player, payload, ComputerAccessResultPayload.INVALID_PASSWORD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void eject(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
|
||||||
|
try {
|
||||||
|
ResourceLocation diskId = ResourceLocation.parse(payload.value());
|
||||||
|
boolean ejected = computer.ejectOne(player, diskId);
|
||||||
|
send(player, payload, ejected ? ComputerAccessResultPayload.SUCCESS : ComputerAccessResultPayload.INVALID);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
send(player, payload, ComputerAccessResultPayload.INVALID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void fileList(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
|
||||||
|
if (!computer.canUseFileSystem(player)) {
|
||||||
|
sendFile(player, payload, false, "", "unauthorized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StringBuilder data = new StringBuilder();
|
||||||
|
for (ComputerFileSystem.ComputerFile file : computer.fileSystem().list()) {
|
||||||
|
if (!data.isEmpty()) data.append('\n');
|
||||||
|
data.append(file.type().name()).append('\t').append(file.path()).append('\t').append(file.contents().length());
|
||||||
|
}
|
||||||
|
sendFile(player, payload, true, data.toString(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void fileOpen(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
|
||||||
|
if (!computer.canUseFileSystem(player)) {
|
||||||
|
sendFile(player, payload, false, "", "unauthorized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ComputerFileSystem.ComputerFile file = computer.fileSystem().get(payload.value());
|
||||||
|
if (file == null) {
|
||||||
|
sendFile(player, payload, false, "", "not_found");
|
||||||
|
} else if (file.type() != ComputerFileSystem.ComputerFile.Type.TEXT) {
|
||||||
|
sendFile(player, payload, false, "", "not_text");
|
||||||
|
} else {
|
||||||
|
sendFile(player, payload, true, file.path() + "\0" + file.contents(), "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void fileCreate(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
|
||||||
|
if (!computer.canUseFileSystem(player)) {
|
||||||
|
sendFile(player, payload, false, "", "unauthorized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String[] request = splitFileRequest(payload.value());
|
||||||
|
if (request == null) {
|
||||||
|
sendFile(player, payload, false, "", "invalid_request");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ComputerFileSystem.Result result = computer.fileSystem().createTextFile(request[0], request[1]);
|
||||||
|
if (result.successful()) computer.setChanged();
|
||||||
|
sendFile(player, payload, result.successful(), request[0], result.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void fileSave(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
|
||||||
|
if (!computer.canUseFileSystem(player)) {
|
||||||
|
sendFile(player, payload, false, "", "unauthorized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String[] request = splitFileRequest(payload.value());
|
||||||
|
if (request == null) {
|
||||||
|
sendFile(player, payload, false, "", "invalid_request");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ComputerFileSystem.Result result = computer.fileSystem().writeTextFile(request[0], request[1]);
|
||||||
|
if (result.successful()) computer.setChanged();
|
||||||
|
sendFile(player, payload, result.successful(), request[0], result.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void desktopState(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
|
||||||
|
if (!computer.canUseFileSystem(player)) {
|
||||||
|
sendDesktop(player, payload, false, "unauthorized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendDesktop(player, payload, true, AntmailWire.encodeTag(computer.desktopState().save()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void desktopWallpaper(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
|
||||||
|
if (!computer.canUseFileSystem(player)) {
|
||||||
|
sendDesktop(player, payload, false, "unauthorized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
boolean changed = computer.selectWallpaper(ResourceLocation.parse(payload.value()));
|
||||||
|
sendDesktop(player, payload, changed, changed ? "" : "wallpaper_locked");
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
sendDesktop(player, payload, false, "invalid_wallpaper");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sendDesktop(ServerPlayer player, ComputerAccessPayload payload, boolean success, String data) {
|
||||||
|
BlockEntity blockEntity = player.serverLevel().getBlockEntity(payload.pos());
|
||||||
|
boolean hasPassword = blockEntity instanceof ComputerBlockEntity computer && computer.hasPassword();
|
||||||
|
boolean authenticated = blockEntity instanceof ComputerBlockEntity computer && computer.isAuthenticated();
|
||||||
|
resultSender.accept(player, new ComputerAccessResultPayload(payload.pos(), success ? ComputerAccessResultPayload.SUCCESS : ComputerAccessResultPayload.INVALID,
|
||||||
|
hasPassword, authenticated, payload.action() + "\0\0" + data));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] splitFileRequest(String value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
int separator = value.indexOf('\0');
|
||||||
|
if (separator < 0) return null;
|
||||||
|
return new String[]{value.substring(0, separator), value.substring(separator + 1)};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void send(ServerPlayer player, ComputerAccessPayload payload, int result) {
|
||||||
|
BlockEntity blockEntity = player.serverLevel().getBlockEntity(payload.pos());
|
||||||
|
boolean hasPassword = blockEntity instanceof ComputerBlockEntity computer && computer.hasPassword();
|
||||||
|
boolean authenticated = blockEntity instanceof ComputerBlockEntity computer && computer.isAuthenticated();
|
||||||
|
resultSender.accept(player, new ComputerAccessResultPayload(payload.pos(), result, hasPassword, authenticated));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sendFile(ServerPlayer player, ComputerAccessPayload payload, boolean success, String data, String error) {
|
||||||
|
BlockEntity blockEntity = player.serverLevel().getBlockEntity(payload.pos());
|
||||||
|
boolean hasPassword = blockEntity instanceof ComputerBlockEntity computer && computer.hasPassword();
|
||||||
|
boolean authenticated = blockEntity instanceof ComputerBlockEntity computer && computer.isAuthenticated();
|
||||||
|
resultSender.accept(player, new ComputerAccessResultPayload(payload.pos(), success ? ComputerAccessResultPayload.SUCCESS : ComputerAccessResultPayload.INVALID,
|
||||||
|
hasPassword, authenticated, payload.action() + "\0" + error + "\0" + data));
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package com.craisinlord.antarchy.content.network;
|
||||||
|
|
||||||
|
import com.craisinlord.antarchy.Antarchy;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||||
|
import net.minecraft.network.codec.ByteBufCodecs;
|
||||||
|
import net.minecraft.network.codec.StreamCodec;
|
||||||
|
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
public record ComputerAccessPayload(BlockPos pos, int action, String value) implements CustomPacketPayload {
|
||||||
|
public static final int OPEN = 0;
|
||||||
|
public static final int SETUP = 1;
|
||||||
|
public static final int LOGIN = 2;
|
||||||
|
public static final int LOGOUT = 3;
|
||||||
|
public static final int CHANGE_PASSWORD = 4;
|
||||||
|
public static final int EJECT = 5;
|
||||||
|
public static final int CLOSE = 6;
|
||||||
|
public static final int FILE_LIST = 7;
|
||||||
|
public static final int FILE_OPEN = 8;
|
||||||
|
public static final int FILE_CREATE = 9;
|
||||||
|
public static final int FILE_SAVE = 10;
|
||||||
|
public static final int DESKTOP_STATE = 11;
|
||||||
|
public static final int DESKTOP_WALLPAPER = 12;
|
||||||
|
public static final Type<ComputerAccessPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "computer_access"));
|
||||||
|
public static final StreamCodec<RegistryFriendlyByteBuf, ComputerAccessPayload> STREAM_CODEC = StreamCodec.composite(
|
||||||
|
BlockPos.STREAM_CODEC, ComputerAccessPayload::pos,
|
||||||
|
ByteBufCodecs.VAR_INT, ComputerAccessPayload::action,
|
||||||
|
ByteBufCodecs.stringUtf8(65536), ComputerAccessPayload::value,
|
||||||
|
ComputerAccessPayload::new
|
||||||
|
);
|
||||||
|
|
||||||
|
public ComputerAccessPayload(BlockPos pos, int action) {
|
||||||
|
this(pos, action, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Type<ComputerAccessPayload> type() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
package com.craisinlord.antarchy.content.network;
|
||||||
|
|
||||||
|
import com.craisinlord.antarchy.Antarchy;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||||
|
import net.minecraft.network.codec.ByteBufCodecs;
|
||||||
|
import net.minecraft.network.codec.StreamCodec;
|
||||||
|
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
public record ComputerAccessResultPayload(BlockPos pos, int result, boolean hasPassword, boolean authenticated, String data) implements CustomPacketPayload {
|
||||||
|
public static final int SETUP_REQUIRED = 0;
|
||||||
|
public static final int READY = 1;
|
||||||
|
public static final int SUCCESS = 2;
|
||||||
|
public static final int INVALID_PASSWORD = 3;
|
||||||
|
public static final int BUSY = 4;
|
||||||
|
public static final int INVALID = 5;
|
||||||
|
public static final Type<ComputerAccessResultPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "computer_access_result"));
|
||||||
|
public static final StreamCodec<RegistryFriendlyByteBuf, ComputerAccessResultPayload> STREAM_CODEC = StreamCodec.composite(
|
||||||
|
BlockPos.STREAM_CODEC, ComputerAccessResultPayload::pos,
|
||||||
|
ByteBufCodecs.VAR_INT, ComputerAccessResultPayload::result,
|
||||||
|
ByteBufCodecs.BOOL, ComputerAccessResultPayload::hasPassword,
|
||||||
|
ByteBufCodecs.BOOL, ComputerAccessResultPayload::authenticated,
|
||||||
|
ByteBufCodecs.stringUtf8(65536), ComputerAccessResultPayload::data,
|
||||||
|
ComputerAccessResultPayload::new
|
||||||
|
);
|
||||||
|
|
||||||
|
public ComputerAccessResultPayload(BlockPos pos, int result, boolean hasPassword, boolean authenticated) {
|
||||||
|
this(pos, result, hasPassword, authenticated, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Type<ComputerAccessResultPayload> type() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
package com.craisinlord.antarchy.content.network;
|
||||||
|
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public final class ComputerNetworking {
|
||||||
|
private static Consumer<ComputerAccessPayload> sender = payload -> {
|
||||||
|
};
|
||||||
|
|
||||||
|
private ComputerNetworking() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setSender(Consumer<ComputerAccessPayload> sender) {
|
||||||
|
ComputerNetworking.sender = sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void open(BlockPos pos) {
|
||||||
|
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.OPEN));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setup(BlockPos pos, String password) {
|
||||||
|
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.SETUP, password));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void login(BlockPos pos, String password) {
|
||||||
|
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.LOGIN, password));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void logout(BlockPos pos) {
|
||||||
|
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.LOGOUT));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void close(BlockPos pos) {
|
||||||
|
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.CLOSE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void changePassword(BlockPos pos, String password) {
|
||||||
|
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.CHANGE_PASSWORD, password));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void eject(BlockPos pos, ResourceLocation diskId) {
|
||||||
|
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.EJECT, diskId.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void listFiles(BlockPos pos) {
|
||||||
|
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.FILE_LIST));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openFile(BlockPos pos, String path) {
|
||||||
|
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.FILE_OPEN, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void createFile(BlockPos pos, String path, String contents) {
|
||||||
|
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.FILE_CREATE, path + "\0" + contents));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void saveFile(BlockPos pos, String path, String contents) {
|
||||||
|
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.FILE_SAVE, path + "\0" + contents));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void requestDesktopState(BlockPos pos) {
|
||||||
|
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.DESKTOP_STATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void selectWallpaper(BlockPos pos, ResourceLocation id) {
|
||||||
|
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.DESKTOP_WALLPAPER, id.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"variants": {
|
"multipart": [
|
||||||
"": {
|
{
|
||||||
|
"apply": {
|
||||||
"model": "antarchy:block/computer"
|
"model": "antarchy:block/computer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
@@ -1053,6 +1053,7 @@
|
|||||||
"subtitles.antarchy.king_death": "The King falls",
|
"subtitles.antarchy.king_death": "The King falls",
|
||||||
"subtitles.antarchy.king_beam_shoot": "The King fires a beam",
|
"subtitles.antarchy.king_beam_shoot": "The King fires a beam",
|
||||||
"subtitles.antarchy.king_fireball_shoot": "The King breathes fire",
|
"subtitles.antarchy.king_fireball_shoot": "The King breathes fire",
|
||||||
|
"subtitles.antarchy.king_ice_spikes": "The King summons ice spikes",
|
||||||
"subtitles.antarchy.king_decree_cast": "The King pronounces a Decree",
|
"subtitles.antarchy.king_decree_cast": "The King pronounces a Decree",
|
||||||
"subtitles.antarchy.king_decree": "The King issues a Decree",
|
"subtitles.antarchy.king_decree": "The King issues a Decree",
|
||||||
"subtitles.antarchy.king_success": "The King acknowledges compliance",
|
"subtitles.antarchy.king_success": "The King acknowledges compliance",
|
||||||
@@ -1444,7 +1445,7 @@
|
|||||||
,"block.antarchy.computer": "Computer"
|
,"block.antarchy.computer": "Computer"
|
||||||
,"item.antarchy.computer": "Computer"
|
,"item.antarchy.computer": "Computer"
|
||||||
,"item.antarchy.floppy_disk": "Floppy Disk"
|
,"item.antarchy.floppy_disk": "Floppy Disk"
|
||||||
,"screen.antarchy.computer": "ANTARCHY ARCHIVE"
|
,"screen.antarchy.computer": "ANTINTOSH OS"
|
||||||
,"guide.antarchy.category.general": "GENERAL"
|
,"guide.antarchy.category.general": "GENERAL"
|
||||||
,"guide.antarchy.category.elythia": "ELYTHIA"
|
,"guide.antarchy.category.elythia": "ELYTHIA"
|
||||||
,"guide.antarchy.category.thoraxis": "THORAXIS"
|
,"guide.antarchy.category.thoraxis": "THORAXIS"
|
||||||
@@ -1467,4 +1468,21 @@
|
|||||||
,"guide.antarchy.entry.high_fructose_corn_syrup.title": "High Fructose Corn Syrup"
|
,"guide.antarchy.entry.high_fructose_corn_syrup.title": "High Fructose Corn Syrup"
|
||||||
,"guide.antarchy.entry.high_fructose_corn_syrup.subtitle": "Nutritional and operational notes"
|
,"guide.antarchy.entry.high_fructose_corn_syrup.subtitle": "Nutritional and operational notes"
|
||||||
,"guide.antarchy.entry.high_fructose_corn_syrup.description": "A sweet substance of uncertain strategic value. Doctoral enthusiasm surrounding it is statistically suspicious."
|
,"guide.antarchy.entry.high_fructose_corn_syrup.description": "A sweet substance of uncertain strategic value. Doctoral enthusiasm surrounding it is statistically suspicious."
|
||||||
|
,"screen.antarchy.computer.first_use.title": "INITIAL PASSWORD SETUP"
|
||||||
|
,"screen.antarchy.computer.first_use.prompt": "CREATE A PASSWORD TO CONTINUE"
|
||||||
|
,"screen.antarchy.computer.first_use.confirm": "CONFIRM PASSWORD"
|
||||||
|
,"screen.antarchy.computer.busy": "COMPUTER IN USE"
|
||||||
|
,"screen.antarchy.computer.eject_denied": "ACCESS DENIED: SETTINGS REQUIRED"
|
||||||
|
,"screen.antarchy.computer.ejected": "DISK EJECTED"
|
||||||
|
,"antintosh.antarchy.desktop.archive": "ARCHIVE"
|
||||||
|
,"antintosh.antarchy.desktop.settings": "SETTINGS"
|
||||||
|
,"antintosh.antarchy.desktop.terminal": "TERMINAL"
|
||||||
|
,"antintosh.antarchy.desktop.antpaint": "ANTPAINT"
|
||||||
|
,"antintosh.antarchy.desktop.antmail": "ANTMAIL"
|
||||||
|
,"antintosh.antarchy.desktop.text_editor": "TEXT EDITOR"
|
||||||
|
,"antintosh.antarchy.antmail.compose": "COMPOSE MESSAGE"
|
||||||
|
,"antintosh.antarchy.antmail.attach_text": "ATTACH TEXT FILE"
|
||||||
|
,"antintosh.antarchy.antmail.attach_image": "ATTACH ANTPAINT IMAGE"
|
||||||
|
,"antintosh.antarchy.antmail.save_attachment": "SAVE ATTACHMENT"
|
||||||
|
,"antintosh.antarchy.antmail.address": "COMPUTER ADDRESS"
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"parent": "antarchy:block/computer"
|
"parent": "builtin/entity"
|
||||||
}
|
}
|
||||||
@@ -2850,6 +2850,14 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"king_ice_spikes": {
|
||||||
|
"subtitle": "subtitles.antarchy.king_ice_spikes",
|
||||||
|
"sounds": [
|
||||||
|
{
|
||||||
|
"name": "antarchy:mob/king/ice_spikes"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"king_decree_cast": {
|
"king_decree_cast": {
|
||||||
"subtitle": "subtitles.antarchy.king_decree_cast",
|
"subtitle": "subtitles.antarchy.king_decree_cast",
|
||||||
"sounds": [
|
"sounds": [
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"category": "general",
|
"category": "thoraxis",
|
||||||
"title": "guide.antarchy.disk.chronosphere.title",
|
"title": "guide.antarchy.disk.chronosphere.title",
|
||||||
"entries": ["antarchy:chronosphere"]
|
"entries": ["antarchy:chronosphere"]
|
||||||
}
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"category": "general",
|
|
||||||
"title": "guide.antarchy.disk.introduction.title",
|
|
||||||
"entries": ["antarchy:introduction"]
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"type": "item",
|
"type": "item",
|
||||||
"category": "general",
|
"category": "thoraxis",
|
||||||
"title": "guide.antarchy.entry.chronosphere.title",
|
"title": "guide.antarchy.entry.chronosphere.title",
|
||||||
"subtitle": "guide.antarchy.entry.chronosphere.subtitle",
|
"subtitle": "guide.antarchy.entry.chronosphere.subtitle",
|
||||||
"description": ["guide.antarchy.entry.chronosphere.description"],
|
"description": ["guide.antarchy.entry.chronosphere.description"],
|
||||||
|
|||||||
@@ -60,8 +60,7 @@
|
|||||||
"antarchy:biowart_patch",
|
"antarchy:biowart_patch",
|
||||||
"antarchy:cavaryn_creepvine",
|
"antarchy:cavaryn_creepvine",
|
||||||
"antarchy:cavaryn_hanging_creeproots"
|
"antarchy:cavaryn_hanging_creeproots"
|
||||||
],
|
]
|
||||||
[]
|
|
||||||
],
|
],
|
||||||
"creature_spawn_probability": 0.98,
|
"creature_spawn_probability": 0.98,
|
||||||
"spawners": {
|
"spawners": {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Computer contract fixtures
|
||||||
|
|
||||||
|
These fixtures describe expected computer state transitions for validation tooling. They are not runtime datapack content and are not loaded by the game.
|
||||||
|
|
||||||
|
The repository currently has no computer-specific test harness. The fixtures cover first placement, duplicate disk consumption, protection of the built-in introduction module, authenticated ejection through Settings or Trash, rejection without authentication, and physical disk drops when the block is broken.
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"case": "built_in_introduction_protection",
|
||||||
|
"physical_disk_ids": ["antarchy:chronosphere"],
|
||||||
|
"built_in_modules": ["antarchy:introduction"],
|
||||||
|
"settings_ejection_candidates": ["antarchy:chronosphere"],
|
||||||
|
"expected_dropped_disk_ids": ["antarchy:chronosphere"],
|
||||||
|
"expected_ejectable_ids": ["antarchy:chronosphere"],
|
||||||
|
"expected_introduction_dropped": false,
|
||||||
|
"expected_introduction_ejectable": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"case": "duplicate_disk",
|
||||||
|
"initial_physical_disk_ids": ["antarchy:chronosphere"],
|
||||||
|
"inserted_disk_id": "antarchy:chronosphere",
|
||||||
|
"expected_physical_disk_ids": ["antarchy:chronosphere"],
|
||||||
|
"expected_item_consumed": true,
|
||||||
|
"expected_archive_change": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"case": "ejection_and_drop",
|
||||||
|
"physical_disk_ids": ["antarchy:chronosphere", "antarchy:toreterror_arena"],
|
||||||
|
"ejection": {"authenticated": true, "route": "settings_or_trash", "selected_disk_id": "antarchy:chronosphere", "expected_returned_item_id": "antarchy:floppy_disk", "expected_remaining_physical_disk_ids": ["antarchy:toreterror_arena"]},
|
||||||
|
"drop_after_break": {"expected_dropped_disk_ids": ["antarchy:toreterror_arena"], "expected_introduction_dropped": false},
|
||||||
|
"rejection": {"authenticated": false, "expected_ejection": false}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"case": "newly_placed",
|
||||||
|
"physical_disk_ids": [],
|
||||||
|
"built_in_modules": ["antarchy:introduction"],
|
||||||
|
"password": {"state": "unset"},
|
||||||
|
"session": {"authenticated": false, "screen_occupied": false},
|
||||||
|
"expectations": ["introduction_visible_before_login", "first_use_password_setup_required", "introduction_not_ejectable", "introduction_not_dropped"]
|
||||||
|
}
|
||||||
@@ -321,6 +321,7 @@ public final class AntarchyFabricContent {
|
|||||||
AntarchyFabricSounds.QUEEN_ROAR,
|
AntarchyFabricSounds.QUEEN_ROAR,
|
||||||
AntarchyFabricSounds.KING_BEAM_SHOOT,
|
AntarchyFabricSounds.KING_BEAM_SHOOT,
|
||||||
AntarchyFabricSounds.KING_FIREBALL_SHOOT,
|
AntarchyFabricSounds.KING_FIREBALL_SHOOT,
|
||||||
|
AntarchyFabricSounds.KING_ICE_SPIKES,
|
||||||
AntarchyFabricSounds.KING_DECREE_CAST,
|
AntarchyFabricSounds.KING_DECREE_CAST,
|
||||||
AntarchyFabricSounds.QUEEN_BEAM_SHOOT,
|
AntarchyFabricSounds.QUEEN_BEAM_SHOOT,
|
||||||
AntarchyFabricSounds.QUEEN_BEAM_START,
|
AntarchyFabricSounds.QUEEN_BEAM_START,
|
||||||
|
|||||||
-10
@@ -33,11 +33,6 @@ public final class AntarchyClothConfigScreenBuilder {
|
|||||||
ConfigBuilder builder = ConfigBuilder.create()
|
ConfigBuilder builder = ConfigBuilder.create()
|
||||||
.setParentScreen(parent)
|
.setParentScreen(parent)
|
||||||
.setTitle(Component.translatable("antarchy.config.title"));
|
.setTitle(Component.translatable("antarchy.config.title"));
|
||||||
// Entry save consumers (below) already update the live AntarchySettings fields the
|
|
||||||
// instant the player edits a widget, matching how the game normally behaves. This runs
|
|
||||||
// last, once, when the player clicks "Save" — it's what actually writes those changes
|
|
||||||
// back to the three JSON files, since AntarchyConfigModuleFabric only reads+writes them
|
|
||||||
// at startup otherwise.
|
|
||||||
builder.setSavingRunnable(AntarchyConfigModuleFabric::persist);
|
builder.setSavingRunnable(AntarchyConfigModuleFabric::persist);
|
||||||
|
|
||||||
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
|
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
|
||||||
@@ -74,11 +69,6 @@ public final class AntarchyClothConfigScreenBuilder {
|
|||||||
.setSaveConsumer(binding::setValue)
|
.setSaveConsumer(binding::setValue)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
// String-typed bindings back the ResourceKey<Level> dimension fields (see
|
|
||||||
// AntarchyConfigModuleFabric.isSupported) and are intentionally skipped here — they're
|
|
||||||
// structural dimension identity, not a tuning knob, and Cloth Config has no natural
|
|
||||||
// widget for them. Every other supported type is exactly what's already persisted to
|
|
||||||
// JSON today, so nothing shown here can drift from what the JSON files contain.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String displayName(ConfigSection section) {
|
private static String displayName(ConfigSection section) {
|
||||||
|
|||||||
+5
@@ -36,6 +36,11 @@ public final class AntarchyFabricClientNetworking {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void register() {
|
public static void register() {
|
||||||
|
ClientPlayNetworking.registerGlobalReceiver(AntmailResultPayload.TYPE, (payload, context) ->
|
||||||
|
context.client().execute(() -> com.craisinlord.antarchy.content.client.AntmailClientState.update(payload)));
|
||||||
|
ClientPlayNetworking.registerGlobalReceiver(ComputerAccessResultPayload.TYPE, (payload, context) ->
|
||||||
|
context.client().execute(() -> com.craisinlord.antarchy.content.client.ComputerAccessClientState.update(payload)));
|
||||||
|
com.craisinlord.antarchy.content.network.ComputerNetworking.setSender(ServerPlayNetworking::send);
|
||||||
ClientPlayNetworking.registerGlobalReceiver(GravityStatePayload.TYPE, (payload, context) ->
|
ClientPlayNetworking.registerGlobalReceiver(GravityStatePayload.TYPE, (payload, context) ->
|
||||||
context.client().execute(() -> AntarchyFabricNetworking.handleGravityState(context.player(), payload)));
|
context.client().execute(() -> AntarchyFabricNetworking.handleGravityState(context.player(), payload)));
|
||||||
ClientPlayNetworking.registerGlobalReceiver(BloodglassStatePayload.TYPE, (payload, context) ->
|
ClientPlayNetworking.registerGlobalReceiver(BloodglassStatePayload.TYPE, (payload, context) ->
|
||||||
|
|||||||
+20
@@ -49,6 +49,9 @@ public final class AntarchyFabricNetworking {
|
|||||||
public static void register() {
|
public static void register() {
|
||||||
registerPayloadTypes();
|
registerPayloadTypes();
|
||||||
registerServerReceivers();
|
registerServerReceivers();
|
||||||
|
com.craisinlord.antarchy.content.network.ComputerAccessHandler.setResultSender(ServerPlayNetworking::send);
|
||||||
|
com.craisinlord.antarchy.content.network.AntmailServerHandler.setResultSender(ServerPlayNetworking::send);
|
||||||
|
com.craisinlord.antarchy.content.network.ComputerNetworking.setSender(ServerPlayNetworking::send);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void bootstrapMultipartCommon() {
|
public static void bootstrapMultipartCommon() {
|
||||||
@@ -72,6 +75,8 @@ public final class AntarchyFabricNetworking {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void registerPayloadTypes() {
|
private static void registerPayloadTypes() {
|
||||||
|
PayloadTypeRegistry.playS2C().register(ComputerAccessResultPayload.TYPE, ComputerAccessResultPayload.STREAM_CODEC);
|
||||||
|
PayloadTypeRegistry.playS2C().register(AntmailResultPayload.TYPE, AntmailResultPayload.STREAM_CODEC);
|
||||||
PayloadTypeRegistry.playS2C().register(GravityStatePayload.TYPE, GravityStatePayload.STREAM_CODEC);
|
PayloadTypeRegistry.playS2C().register(GravityStatePayload.TYPE, GravityStatePayload.STREAM_CODEC);
|
||||||
PayloadTypeRegistry.playS2C().register(BloodglassStatePayload.TYPE, BloodglassStatePayload.STREAM_CODEC);
|
PayloadTypeRegistry.playS2C().register(BloodglassStatePayload.TYPE, BloodglassStatePayload.STREAM_CODEC);
|
||||||
PayloadTypeRegistry.playS2C().register(TigerEyeCamouflageStatePayload.TYPE, TigerEyeCamouflageStatePayload.STREAM_CODEC);
|
PayloadTypeRegistry.playS2C().register(TigerEyeCamouflageStatePayload.TYPE, TigerEyeCamouflageStatePayload.STREAM_CODEC);
|
||||||
@@ -104,9 +109,24 @@ public final class AntarchyFabricNetworking {
|
|||||||
PayloadTypeRegistry.playC2S().register(HerculesBeetleMountedChargePayload.TYPE, HerculesBeetleMountedChargePayload.STREAM_CODEC);
|
PayloadTypeRegistry.playC2S().register(HerculesBeetleMountedChargePayload.TYPE, HerculesBeetleMountedChargePayload.STREAM_CODEC);
|
||||||
PayloadTypeRegistry.playC2S().register(MultipartAttackPayload.TYPE, MultipartAttackPayload.STREAM_CODEC);
|
PayloadTypeRegistry.playC2S().register(MultipartAttackPayload.TYPE, MultipartAttackPayload.STREAM_CODEC);
|
||||||
PayloadTypeRegistry.playC2S().register(MultipartInteractPayload.TYPE, MultipartInteractPayload.STREAM_CODEC);
|
PayloadTypeRegistry.playC2S().register(MultipartInteractPayload.TYPE, MultipartInteractPayload.STREAM_CODEC);
|
||||||
|
PayloadTypeRegistry.playC2S().register(ComputerAccessPayload.TYPE, ComputerAccessPayload.STREAM_CODEC);
|
||||||
|
PayloadTypeRegistry.playC2S().register(AntmailSetupPayload.TYPE, AntmailSetupPayload.STREAM_CODEC);
|
||||||
|
PayloadTypeRegistry.playC2S().register(AntmailStateRequestPayload.TYPE, AntmailStateRequestPayload.STREAM_CODEC);
|
||||||
|
PayloadTypeRegistry.playC2S().register(AntmailSendPayload.TYPE, AntmailSendPayload.STREAM_CODEC);
|
||||||
|
PayloadTypeRegistry.playC2S().register(AntmailReadPayload.TYPE, AntmailReadPayload.STREAM_CODEC);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void registerServerReceivers() {
|
private static void registerServerReceivers() {
|
||||||
|
ServerPlayNetworking.registerGlobalReceiver(ComputerAccessPayload.TYPE, (payload, context) ->
|
||||||
|
context.server().execute(() -> ComputerAccessHandler.handle(context.player(), payload)));
|
||||||
|
ServerPlayNetworking.registerGlobalReceiver(AntmailSetupPayload.TYPE, (payload, context) ->
|
||||||
|
context.server().execute(() -> AntmailServerHandler.handle(context.player(), payload)));
|
||||||
|
ServerPlayNetworking.registerGlobalReceiver(AntmailStateRequestPayload.TYPE, (payload, context) ->
|
||||||
|
context.server().execute(() -> AntmailServerHandler.handle(context.player(), payload)));
|
||||||
|
ServerPlayNetworking.registerGlobalReceiver(AntmailSendPayload.TYPE, (payload, context) ->
|
||||||
|
context.server().execute(() -> AntmailServerHandler.handle(context.player(), payload)));
|
||||||
|
ServerPlayNetworking.registerGlobalReceiver(AntmailReadPayload.TYPE, (payload, context) ->
|
||||||
|
context.server().execute(() -> AntmailServerHandler.handle(context.player(), payload)));
|
||||||
ServerPlayNetworking.registerGlobalReceiver(GravityGunPrimaryPayload.TYPE, (payload, context) ->
|
ServerPlayNetworking.registerGlobalReceiver(GravityGunPrimaryPayload.TYPE, (payload, context) ->
|
||||||
context.server().execute(() -> handleGravityGunPrimary(context.player(), payload)));
|
context.server().execute(() -> handleGravityGunPrimary(context.player(), payload)));
|
||||||
ServerPlayNetworking.registerGlobalReceiver(PortalGunPrimaryPayload.TYPE, (payload, context) ->
|
ServerPlayNetworking.registerGlobalReceiver(PortalGunPrimaryPayload.TYPE, (payload, context) ->
|
||||||
|
|||||||
-3
@@ -1548,9 +1548,6 @@ public final class AntarchyFabricItems {
|
|||||||
() -> new EyeOfTheStormItem(new Item.Properties().stacksTo(1).durability(384).rarity(Rarity.EPIC)));
|
() -> new EyeOfTheStormItem(new Item.Properties().stacksTo(1).durability(384).rarity(Rarity.EPIC)));
|
||||||
|
|
||||||
|
|
||||||
// Temporarily disabled: keeping the code but not registering the item for now.
|
|
||||||
// public static final DeferredItem<MinersDreamItem> MINERS_DREAM = ITEMS.register("miners_dream",
|
|
||||||
// () -> new MinersDreamItem(new Item.Properties().stacksTo(1).rarity(Rarity.RARE)));
|
|
||||||
|
|
||||||
|
|
||||||
public static final DeferredItem<SquidzookaItem> SQUIDZOOKA = ITEMS.register("squidzooka",
|
public static final DeferredItem<SquidzookaItem> SQUIDZOOKA = ITEMS.register("squidzooka",
|
||||||
|
|||||||
+1
@@ -542,6 +542,7 @@ public final class AntarchyFabricSounds {
|
|||||||
public static final DeferredHolder<SoundEvent, SoundEvent> KING_DEATH = registerSoundEvent("king_death");
|
public static final DeferredHolder<SoundEvent, SoundEvent> KING_DEATH = registerSoundEvent("king_death");
|
||||||
public static final DeferredHolder<SoundEvent, SoundEvent> KING_BEAM_SHOOT = registerSoundEvent("king_beam_shoot");
|
public static final DeferredHolder<SoundEvent, SoundEvent> KING_BEAM_SHOOT = registerSoundEvent("king_beam_shoot");
|
||||||
public static final DeferredHolder<SoundEvent, SoundEvent> KING_FIREBALL_SHOOT = registerSoundEvent("king_fireball_shoot");
|
public static final DeferredHolder<SoundEvent, SoundEvent> KING_FIREBALL_SHOOT = registerSoundEvent("king_fireball_shoot");
|
||||||
|
public static final DeferredHolder<SoundEvent, SoundEvent> KING_ICE_SPIKES = registerSoundEvent("king_ice_spikes");
|
||||||
public static final DeferredHolder<SoundEvent, SoundEvent> KING_DECREE_CAST = registerSoundEvent("king_decree_cast");
|
public static final DeferredHolder<SoundEvent, SoundEvent> KING_DECREE_CAST = registerSoundEvent("king_decree_cast");
|
||||||
public static final DeferredHolder<SoundEvent, SoundEvent> KING_DECREE = registerSoundEvent("king_decree");
|
public static final DeferredHolder<SoundEvent, SoundEvent> KING_DECREE = registerSoundEvent("king_decree");
|
||||||
public static final DeferredHolder<SoundEvent, SoundEvent> KING_SUCCESS = registerSoundEvent("king_success");
|
public static final DeferredHolder<SoundEvent, SoundEvent> KING_SUCCESS = registerSoundEvent("king_success");
|
||||||
|
|||||||
@@ -216,6 +216,7 @@ public class AntarchyNeoforge {
|
|||||||
AntarchyNeoforgeSounds.QUEEN_ROAR,
|
AntarchyNeoforgeSounds.QUEEN_ROAR,
|
||||||
AntarchyNeoforgeSounds.KING_BEAM_SHOOT,
|
AntarchyNeoforgeSounds.KING_BEAM_SHOOT,
|
||||||
AntarchyNeoforgeSounds.KING_FIREBALL_SHOOT,
|
AntarchyNeoforgeSounds.KING_FIREBALL_SHOOT,
|
||||||
|
AntarchyNeoforgeSounds.KING_ICE_SPIKES,
|
||||||
AntarchyNeoforgeSounds.KING_DECREE_CAST,
|
AntarchyNeoforgeSounds.KING_DECREE_CAST,
|
||||||
AntarchyNeoforgeSounds.QUEEN_BEAM_SHOOT,
|
AntarchyNeoforgeSounds.QUEEN_BEAM_SHOOT,
|
||||||
AntarchyNeoforgeSounds.QUEEN_BEAM_START,
|
AntarchyNeoforgeSounds.QUEEN_BEAM_START,
|
||||||
@@ -287,6 +288,12 @@ public class AntarchyNeoforge {
|
|||||||
AntarchyNeoforgeCreativeModeTabs.register(modEventBus);
|
AntarchyNeoforgeCreativeModeTabs.register(modEventBus);
|
||||||
AntarchyNeoforgeEntityAttributes.register(modEventBus);
|
AntarchyNeoforgeEntityAttributes.register(modEventBus);
|
||||||
AntarchyNeoforgePayloadHandlers.register(modEventBus);
|
AntarchyNeoforgePayloadHandlers.register(modEventBus);
|
||||||
|
com.craisinlord.antarchy.content.network.ComputerNetworking.setSender(payload ->
|
||||||
|
net.neoforged.neoforge.network.PacketDistributor.sendToServer(payload));
|
||||||
|
com.craisinlord.antarchy.content.network.ComputerAccessHandler.setResultSender((player, payload) ->
|
||||||
|
net.neoforged.neoforge.network.PacketDistributor.sendToPlayer(player, payload));
|
||||||
|
com.craisinlord.antarchy.content.network.AntmailServerHandler.setResultSender((player, payload) ->
|
||||||
|
net.neoforged.neoforge.network.PacketDistributor.sendToPlayer(player, payload));
|
||||||
Antarchy.init();
|
Antarchy.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-3
@@ -1020,9 +1020,6 @@ public final class AntarchyNeoforgeItems {
|
|||||||
));
|
));
|
||||||
public static final DeferredItem<EyeOfTheStormItem> EYE_OF_THE_STORM = ITEMS.register("eye_of_the_storm",
|
public static final DeferredItem<EyeOfTheStormItem> EYE_OF_THE_STORM = ITEMS.register("eye_of_the_storm",
|
||||||
() -> new EyeOfTheStormItem(new Item.Properties().stacksTo(1).durability(384).rarity(Rarity.EPIC)));
|
() -> new EyeOfTheStormItem(new Item.Properties().stacksTo(1).durability(384).rarity(Rarity.EPIC)));
|
||||||
// Temporarily disabled: keeping the code but not registering the item for now.
|
|
||||||
// public static final DeferredItem<MinersDreamItem> MINERS_DREAM = ITEMS.register("miners_dream",
|
|
||||||
// () -> new MinersDreamItem(new Item.Properties().stacksTo(1).rarity(Rarity.RARE)));
|
|
||||||
public static final DeferredItem<SquidzookaItem> SQUIDZOOKA = ITEMS.register("squidzooka",
|
public static final DeferredItem<SquidzookaItem> SQUIDZOOKA = ITEMS.register("squidzooka",
|
||||||
() -> new SquidzookaItem(new Item.Properties().stacksTo(1).durability(384).rarity(Rarity.RARE)));
|
() -> new SquidzookaItem(new Item.Properties().stacksTo(1).durability(384).rarity(Rarity.RARE)));
|
||||||
public static final DeferredItem<RpoLauncherItem> RPO_LAUNCHER = ITEMS.register("rpo_launcher",
|
public static final DeferredItem<RpoLauncherItem> RPO_LAUNCHER = ITEMS.register("rpo_launcher",
|
||||||
|
|||||||
+42
@@ -1,7 +1,17 @@
|
|||||||
package com.craisinlord.antarchy.neoforge.registry;
|
package com.craisinlord.antarchy.neoforge.registry;
|
||||||
|
|
||||||
import com.craisinlord.antarchy.content.network.BloodCrystalKatanaTrailPayload;
|
import com.craisinlord.antarchy.content.network.BloodCrystalKatanaTrailPayload;
|
||||||
|
import com.craisinlord.antarchy.content.network.ComputerAccessHandler;
|
||||||
|
import com.craisinlord.antarchy.content.network.ComputerAccessPayload;
|
||||||
|
import com.craisinlord.antarchy.content.network.ComputerAccessResultPayload;
|
||||||
import com.craisinlord.antarchy.content.network.ToggleTigerEyeCamouflagePayload;
|
import com.craisinlord.antarchy.content.network.ToggleTigerEyeCamouflagePayload;
|
||||||
|
import com.craisinlord.antarchy.content.network.AntmailPayloadTypes;
|
||||||
|
import com.craisinlord.antarchy.content.network.AntmailSetupPayload;
|
||||||
|
import com.craisinlord.antarchy.content.network.AntmailStateRequestPayload;
|
||||||
|
import com.craisinlord.antarchy.content.network.AntmailSendPayload;
|
||||||
|
import com.craisinlord.antarchy.content.network.AntmailReadPayload;
|
||||||
|
import com.craisinlord.antarchy.content.network.AntmailResultPayload;
|
||||||
|
import com.craisinlord.antarchy.content.network.AntmailServerHandler;
|
||||||
import com.craisinlord.antarchy.content.item.TigerEyeArmorUtil;
|
import com.craisinlord.antarchy.content.item.TigerEyeArmorUtil;
|
||||||
import com.craisinlord.antarchy.content.tigereye.TigerEyeCamouflageController;
|
import com.craisinlord.antarchy.content.tigereye.TigerEyeCamouflageController;
|
||||||
import com.craisinlord.antarchy.neoforge.network.AntarchyGravityNetworking;
|
import com.craisinlord.antarchy.neoforge.network.AntarchyGravityNetworking;
|
||||||
@@ -23,6 +33,38 @@ public class AntarchyNeoforgePayloadHandlers {
|
|||||||
|
|
||||||
static void registerPayloadHandlers(RegisterPayloadHandlersEvent event) {
|
static void registerPayloadHandlers(RegisterPayloadHandlersEvent event) {
|
||||||
var registrar = event.registrar("1");
|
var registrar = event.registrar("1");
|
||||||
|
registrar.playToServer(
|
||||||
|
ComputerAccessPayload.TYPE,
|
||||||
|
ComputerAccessPayload.STREAM_CODEC,
|
||||||
|
(payload, ctx) -> ctx.enqueueWork(() -> {
|
||||||
|
if (ctx.player() instanceof net.minecraft.server.level.ServerPlayer player) {
|
||||||
|
ComputerAccessHandler.handle(player, payload);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
registrar.playToServer(AntmailSetupPayload.TYPE, AntmailSetupPayload.STREAM_CODEC,
|
||||||
|
(payload, ctx) -> ctx.enqueueWork(() -> {
|
||||||
|
if (ctx.player() instanceof net.minecraft.server.level.ServerPlayer player) AntmailServerHandler.handle(player, payload);
|
||||||
|
}));
|
||||||
|
registrar.playToServer(AntmailStateRequestPayload.TYPE, AntmailStateRequestPayload.STREAM_CODEC,
|
||||||
|
(payload, ctx) -> ctx.enqueueWork(() -> {
|
||||||
|
if (ctx.player() instanceof net.minecraft.server.level.ServerPlayer player) AntmailServerHandler.handle(player, payload);
|
||||||
|
}));
|
||||||
|
registrar.playToServer(AntmailSendPayload.TYPE, AntmailSendPayload.STREAM_CODEC,
|
||||||
|
(payload, ctx) -> ctx.enqueueWork(() -> {
|
||||||
|
if (ctx.player() instanceof net.minecraft.server.level.ServerPlayer player) AntmailServerHandler.handle(player, payload);
|
||||||
|
}));
|
||||||
|
registrar.playToServer(AntmailReadPayload.TYPE, AntmailReadPayload.STREAM_CODEC,
|
||||||
|
(payload, ctx) -> ctx.enqueueWork(() -> {
|
||||||
|
if (ctx.player() instanceof net.minecraft.server.level.ServerPlayer player) AntmailServerHandler.handle(player, payload);
|
||||||
|
}));
|
||||||
|
registrar.playToClient(AntmailResultPayload.TYPE, AntmailResultPayload.STREAM_CODEC,
|
||||||
|
(payload, ctx) -> ctx.enqueueWork(() -> com.craisinlord.antarchy.content.client.AntmailClientState.update(payload)));
|
||||||
|
registrar.playToClient(
|
||||||
|
ComputerAccessResultPayload.TYPE,
|
||||||
|
ComputerAccessResultPayload.STREAM_CODEC,
|
||||||
|
(payload, ctx) -> ctx.enqueueWork(() -> com.craisinlord.antarchy.content.client.ComputerAccessClientState.update(payload))
|
||||||
|
);
|
||||||
AntarchyGravityNetworking.register(registrar);
|
AntarchyGravityNetworking.register(registrar);
|
||||||
AntarchyTimeDilationNetworking.register(registrar);
|
AntarchyTimeDilationNetworking.register(registrar);
|
||||||
BrutalflyElytraNetworking.register(registrar);
|
BrutalflyElytraNetworking.register(registrar);
|
||||||
|
|||||||
+1
@@ -225,6 +225,7 @@ public final class AntarchyNeoforgeSounds {
|
|||||||
public static final DeferredHolder<SoundEvent, SoundEvent> KING_DEATH = registerSoundEvent("king_death");
|
public static final DeferredHolder<SoundEvent, SoundEvent> KING_DEATH = registerSoundEvent("king_death");
|
||||||
public static final DeferredHolder<SoundEvent, SoundEvent> KING_BEAM_SHOOT = registerSoundEvent("king_beam_shoot");
|
public static final DeferredHolder<SoundEvent, SoundEvent> KING_BEAM_SHOOT = registerSoundEvent("king_beam_shoot");
|
||||||
public static final DeferredHolder<SoundEvent, SoundEvent> KING_FIREBALL_SHOOT = registerSoundEvent("king_fireball_shoot");
|
public static final DeferredHolder<SoundEvent, SoundEvent> KING_FIREBALL_SHOOT = registerSoundEvent("king_fireball_shoot");
|
||||||
|
public static final DeferredHolder<SoundEvent, SoundEvent> KING_ICE_SPIKES = registerSoundEvent("king_ice_spikes");
|
||||||
public static final DeferredHolder<SoundEvent, SoundEvent> KING_DECREE_CAST = registerSoundEvent("king_decree_cast");
|
public static final DeferredHolder<SoundEvent, SoundEvent> KING_DECREE_CAST = registerSoundEvent("king_decree_cast");
|
||||||
public static final DeferredHolder<SoundEvent, SoundEvent> KING_DECREE = registerSoundEvent("king_decree");
|
public static final DeferredHolder<SoundEvent, SoundEvent> KING_DECREE = registerSoundEvent("king_decree");
|
||||||
public static final DeferredHolder<SoundEvent, SoundEvent> KING_SUCCESS = registerSoundEvent("king_success");
|
public static final DeferredHolder<SoundEvent, SoundEvent> KING_SUCCESS = registerSoundEvent("king_success");
|
||||||
|
|||||||
Reference in new issue
Block a user