1.20 progres

king and queen balancing
Antmail
Blockle
This commit is contained in:
craisinlord committed 2026-09-15 19:10:28 -05:00
1 parent c1d60d735d
commit fc5028a15b
430 files changed
+30569 -1684

No files matched your search

@@ -1,6 +1,7 @@
package com.craisinlord.antarchy;
import com.craisinlord.antarchy.content.util.AttributeRangeInit;
import com.craisinlord.antarchy.content.worldgen.VillagePoolAdditions;
import com.craisinlord.integrated_api.events.lifecycle.ServerGoingToStartEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -16,5 +17,6 @@ public class Antarchy {
ServerGoingToStartEvent.EVENT.addListener(Antarchy::serverAboutToStart);
}
private static void serverAboutToStart(final ServerGoingToStartEvent event) {
VillagePoolAdditions.apply(event.getServer());
}
}
@@ -247,8 +247,8 @@ public final class AntarchySettings {
private static double princessArmor = 8.0D;
private static double princessKnockbackResistance = 0.5D;
private static double princessFollowRange = 48.0D;
private static double kingHealth = 6000.0D;
private static double queenHealth = 6000.0D;
private static double kingHealth = 3400.0D;
private static double queenHealth = 4300.0D;
private static double kingAttackDamage = 50.0D;
private static double queenAttackDamage = 45.0D;
private static double royalBossSoundVolume = 4.5D;
@@ -256,10 +256,6 @@ public final class AntarchySettings {
private static int queenBlackHoleActiveTicks = 120;
private static double queenBlackHolePullStrength = 0.16D;
private static int queenBlackHoleBlockSuctionCap = 160;
private static double queenFinalTimeFieldRadius = 20.0D;
private static double queenFinalTimeFieldRate = 0.05D;
private static int queenFinalTimeFieldDurationTicks = 45;
private static int queenFinalTimeFieldCooldownTicks = 80;
private static double kingFireballDamage = 32.0D;
private static double kingFireballRadius = 4.5D;
private static int kingFireballCooldownTicks = 150;
@@ -270,6 +266,7 @@ public final class AntarchySettings {
private static int kingIceSpikeCooldownTicks = 240;
private static int kingElementalTerrainCap = 256;
private static double royalBossArmor = 20.0D;
private static double kingArmor = 30.0D;
private static double royalBossFollowRange = 128.0D;
private static double royalBossMovementSpeed = 0.22D;
private static double royalBossKnockbackResistance = 1.0D;
@@ -278,6 +275,13 @@ public final class AntarchySettings {
private static double royalBossBiteReach = 12.0D;
private static double royalBossBiteDamageMultiplier = 1.6D;
private static int royalBossBiteCooldownTicks = 45;
private static boolean royalBossMultiplayerScalingEnabled = true;
private static int royalBossScalingMaxPlayers = 4;
private static double royalBossHealthPerAdditionalPlayer = 0.35D;
private static double royalBossDamagePerAdditionalPlayer = 0.10D;
private static double royalBoundaryRadius = 192.0D;
private static double royalBoundaryWarningRadius = 160.0D;
private static int royalBoundaryGraceTicks = 60;
private static boolean basiliskPetrifyingGazeEnabled = true;
private static int basiliskSpawnMaxLightLevel = 5;
private static double basiliskHealth = 320.0D;
@@ -546,7 +550,7 @@ public final class AntarchySettings {
private static double manticoreHealth = 40.0D;
private static double manticoreAttackDamage = 7.0D;
private static int manticoreStingPoisonTicks = 100;
private static int queenManticoreCap = 6;
private static int queenManticoreCap = 15;
private static int queenManticoreSummonCooldownTicks = 800;
private static int queenManticoreSummonCount = 2;
private static double queenManticoreSummonRange = 24.0D;
@@ -2067,14 +2071,6 @@ public final class AntarchySettings {
public static void setQueenBlackHolePullStrength(double v) { queenBlackHolePullStrength = v; }
public static int queenBlackHoleBlockSuctionCap() { return queenBlackHoleBlockSuctionCap; }
public static void setQueenBlackHoleBlockSuctionCap(int v) { queenBlackHoleBlockSuctionCap = v; }
public static double queenFinalTimeFieldRadius() { return queenFinalTimeFieldRadius; }
public static void setQueenFinalTimeFieldRadius(double v) { queenFinalTimeFieldRadius = v; }
public static double queenFinalTimeFieldRate() { return queenFinalTimeFieldRate; }
public static void setQueenFinalTimeFieldRate(double v) { queenFinalTimeFieldRate = v; }
public static int queenFinalTimeFieldDurationTicks() { return queenFinalTimeFieldDurationTicks; }
public static void setQueenFinalTimeFieldDurationTicks(int v) { queenFinalTimeFieldDurationTicks = v; }
public static int queenFinalTimeFieldCooldownTicks() { return queenFinalTimeFieldCooldownTicks; }
public static void setQueenFinalTimeFieldCooldownTicks(int v) { queenFinalTimeFieldCooldownTicks = v; }
public static double kingFireballDamage() { return kingFireballDamage; }
public static void setKingFireballDamage(double v) { kingFireballDamage = v; }
public static double kingFireballRadius() { return kingFireballRadius; }
@@ -2096,6 +2092,8 @@ public final class AntarchySettings {
public static double royalBossArmor() { return royalBossArmor; }
public static void setRoyalBossArmor(double v) { royalBossArmor = v; }
public static double kingArmor() { return kingArmor; }
public static void setKingArmor(double v) { kingArmor = v; }
public static double royalBossFollowRange() { return royalBossFollowRange; }
public static void setRoyalBossFollowRange(double v) { royalBossFollowRange = v; }
@@ -2120,6 +2118,20 @@ public final class AntarchySettings {
public static int royalBossBiteCooldownTicks() { return royalBossBiteCooldownTicks; }
public static void setRoyalBossBiteCooldownTicks(int v) { royalBossBiteCooldownTicks = v; }
public static boolean royalBossMultiplayerScalingEnabled() { return royalBossMultiplayerScalingEnabled; }
public static void setRoyalBossMultiplayerScalingEnabled(boolean v) { royalBossMultiplayerScalingEnabled = v; }
public static int royalBossScalingMaxPlayers() { return royalBossScalingMaxPlayers; }
public static void setRoyalBossScalingMaxPlayers(int v) { royalBossScalingMaxPlayers = v; }
public static double royalBossHealthPerAdditionalPlayer() { return royalBossHealthPerAdditionalPlayer; }
public static void setRoyalBossHealthPerAdditionalPlayer(double v) { royalBossHealthPerAdditionalPlayer = v; }
public static double royalBossDamagePerAdditionalPlayer() { return royalBossDamagePerAdditionalPlayer; }
public static void setRoyalBossDamagePerAdditionalPlayer(double v) { royalBossDamagePerAdditionalPlayer = v; }
public static double royalBoundaryRadius() { return royalBoundaryRadius; }
public static void setRoyalBoundaryRadius(double v) { royalBoundaryRadius = v; }
public static double royalBoundaryWarningRadius() { return royalBoundaryWarningRadius; }
public static void setRoyalBoundaryWarningRadius(double v) { royalBoundaryWarningRadius = v; }
public static int royalBoundaryGraceTicks() { return royalBoundaryGraceTicks; }
public static void setRoyalBoundaryGraceTicks(int v) { royalBoundaryGraceTicks = v; }
public static double nightmareSwordBaseDamage() { return nightmareSwordBaseDamage; }
public static void setNightmareSwordBaseDamage(double v) { nightmareSwordBaseDamage = v; }
@@ -0,0 +1,102 @@
package com.craisinlord.antarchy.content.antmail;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.advancements.AdvancementHolder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public final class AntmailEventData extends SimplePreparableReloadListener<Map<ResourceLocation, AntmailEventData.Definition>> {
private static final AntmailEventData INSTANCE = new AntmailEventData();
private static volatile Map<ResourceLocation, Definition> definitions = Map.of();
private AntmailEventData() {
}
public static AntmailEventData instance() {
return INSTANCE;
}
public static void onTick(MinecraftServer server) {
long dayTime = server.overworld().getDayTime();
long day = dayTime / 24000L;
if (dayTime % 24000L >= 20L) return;
AntmailServerData data = AntmailServerData.access(server);
if (!data.beginRandomMailDay(day)) return;
for (AntmailAddress address : data.registeredAddresses()) {
for (Map.Entry<ResourceLocation, Definition> entry : definitions.entrySet()) {
Definition definition = entry.getValue();
if (definition.trigger().type().equals("random") && server.overworld().random.nextDouble() < definition.trigger().chance()) deliver(server, address, entry.getKey(), definition, false);
}
}
}
public static void onAdvancement(ServerPlayer player, AdvancementHolder advancement) {
AntmailServerData data = AntmailServerData.access(player.server);
AntmailAddress address = data.lastUsedAddress(player);
if (address == null) return;
for (Map.Entry<ResourceLocation, Definition> entry : definitions.entrySet()) {
Trigger trigger = entry.getValue().trigger();
if (trigger.type().equals("advancement") && trigger.value().equals(advancement.id().toString())) deliver(player.server, address, entry.getKey(), entry.getValue(), true);
}
}
private static void deliver(MinecraftServer server, AntmailAddress recipient, ResourceLocation id, Definition definition, boolean permanentClaim) {
AntmailServerData data = AntmailServerData.access(server);
try {
AntmailAddress sender = AntmailAddress.parse(definition.sender());
AntmailMessage message = AntmailMessage.create(sender, recipient, definition.subject(), definition.body(), server.overworld().getGameTime(), List.of());
AntmailDeliveryResult result = data.deliver(server, message);
if (permanentClaim && result.status() != AntmailDeliveryResult.Status.MAILBOX_FULL
&& result.status() != AntmailDeliveryResult.Status.FAILED
&& result.status() != AntmailDeliveryResult.Status.ADDRESS_NOT_FOUND) {
data.claimTrigger(recipient, id);
}
} catch (IllegalArgumentException ignored) {
}
}
@Override
protected Map<ResourceLocation, Definition> prepare(ResourceManager manager, ProfilerFiller profiler) {
Map<ResourceLocation, Definition> loaded = new HashMap<>();
for (Map.Entry<ResourceLocation, net.minecraft.server.packs.resources.Resource> resource : manager.listResources("antmail/message", path -> path.getPath().endsWith(".json")).entrySet()) {
try {
JsonElement element = JsonParser.parseReader(resource.getValue().openAsReader());
if (!element.isJsonObject()) continue;
var object = element.getAsJsonObject();
var trigger = object.getAsJsonObject("trigger");
String type = trigger.get("type").getAsString();
String value = trigger.has("value") ? trigger.get("value").getAsString() : String.valueOf(trigger.get("day").getAsInt());
ResourceLocation id = ResourceLocation.fromNamespaceAndPath(resource.getKey().getNamespace(), resource.getKey().getPath().substring("antmail/message/".length(), resource.getKey().getPath().length() - 5));
loaded.put(id, new Definition(object.get("sender").getAsString(), object.get("subject").getAsString(), object.get("body").getAsString(), new Trigger(type, value)));
} catch (Exception ignored) {
}
}
return Map.copyOf(loaded);
}
@Override
protected void apply(Map<ResourceLocation, Definition> loaded, ResourceManager manager, ProfilerFiller profiler) {
definitions = loaded;
}
public record Definition(String sender, String subject, String body, Trigger trigger) {
}
public record Trigger(String type, String value) {
double chance() {
try {
return Math.max(0.0D, Math.min(1.0D, Double.parseDouble(value)));
} catch (NumberFormatException ignored) {
return 0.0D;
}
}
}
}
@@ -8,10 +8,12 @@ import java.util.List;
import java.util.UUID;
public final class AntmailMailbox {
public static final int PAGE_SIZE = 20;
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<>();
private int reportedUnreadCount = -1;
public AntmailMailbox(AntmailAddress address) {
this.address = address;
@@ -21,7 +23,7 @@ public final class AntmailMailbox {
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 int unreadCount() { return reportedUnreadCount >= 0 ? reportedUnreadCount : (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;
@@ -45,6 +47,8 @@ public final class AntmailMailbox {
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 AntmailMessage findSent(UUID id) { return sent.stream().filter(message -> message.id().equals(id)).findFirst().orElse(null); }
public AntmailMessage findInbox(UUID id) { return inbox.stream().filter(message -> message.id().equals(id)).findFirst().orElse(null); }
public boolean markRead(UUID id) {
for (AntmailMessage message : inbox) if (message.id().equals(id)) { message.markRead(); return true; }
@@ -54,18 +58,47 @@ public final class AntmailMailbox {
public AntmailDraft findDraft(UUID id) { return drafts.stream().filter(draft -> draft.id().equals(id)).findFirst().orElse(null); }
public CompoundTag toTag() {
return toTag(true);
}
public CompoundTag toTag(boolean includeMessageAttachments) {
CompoundTag tag = new CompoundTag();
tag.putString("Address", address.fullAddress());
tag.put("Inbox", messagesToTag(inbox));
tag.put("Sent", messagesToTag(sent));
tag.put("Inbox", messagesToTag(inbox, includeMessageAttachments));
tag.put("Sent", messagesToTag(sent, includeMessageAttachments));
ListTag draftTags = new ListTag();
for (AntmailDraft draft : drafts) draftTags.add(draft.toTag());
tag.put("Drafts", draftTags);
return tag;
}
public CompoundTag toPageTag(int folder, int page) {
int safePage = Math.max(0, page);
int start = safePage * PAGE_SIZE;
CompoundTag tag = new CompoundTag();
tag.putString("Address", address.fullAddress());
tag.putInt("Folder", folder);
tag.putInt("Page", safePage);
tag.putInt("PageSize", PAGE_SIZE);
tag.putInt("TotalInbox", inbox.size());
tag.putInt("TotalSent", sent.size());
tag.putInt("UnreadCount", (int) inbox.stream().filter(message -> !message.read()).count());
tag.put("Inbox", folder == 0 ? messagesToTag(page(inbox, start), false) : new ListTag());
tag.put("Sent", folder == 1 ? messagesToTag(page(sent, start), false) : new ListTag());
ListTag draftTags = new ListTag();
for (AntmailDraft draft : drafts) draftTags.add(draft.toTag());
tag.put("Drafts", draftTags);
return tag;
}
private static <T> List<T> page(List<T> values, int start) {
if (start >= values.size()) return List.of();
return values.subList(start, Math.min(start + PAGE_SIZE, values.size()));
}
public static AntmailMailbox fromTag(CompoundTag tag) {
AntmailMailbox mailbox = new AntmailMailbox(AntmailAddress.parse(tag.getString("Address")));
if (tag.contains("UnreadCount")) mailbox.reportedUnreadCount = Math.max(0, tag.getInt("UnreadCount"));
readMessages(tag.getList("Inbox", 10), mailbox.inbox);
readMessages(tag.getList("Sent", 10), mailbox.sent);
ListTag draftTags = tag.getList("Drafts", 10);
@@ -75,9 +108,9 @@ public final class AntmailMailbox {
return mailbox;
}
private static ListTag messagesToTag(List<AntmailMessage> messages) {
private static ListTag messagesToTag(List<AntmailMessage> messages, boolean includeMessageAttachments) {
ListTag tags = new ListTag();
for (AntmailMessage message : messages) tags.add(message.toTag());
for (AntmailMessage message : messages) tags.add(message.toTag(includeMessageAttachments));
return tags;
}
@@ -16,6 +16,7 @@ public final class AntmailMessage {
private static final String CREATED_TAG = "Created";
private static final String READ_TAG = "Read";
private static final String ATTACHMENTS_TAG = "Attachments";
private static final String DELIVERY_STATUS_TAG = "DeliveryStatus";
private final UUID id;
private final AntmailAddress sender;
@@ -25,6 +26,7 @@ public final class AntmailMessage {
private final long createdAt;
private final List<AntmailAttachment> attachments;
private boolean read;
private String deliveryStatus = "DELIVERED";
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);
@@ -53,10 +55,16 @@ public final class AntmailMessage {
public long createdAt() { return createdAt; }
public List<AntmailAttachment> attachments() { return attachments; }
public boolean read() { return read; }
public String deliveryStatus() { return deliveryStatus; }
public void setDeliveryStatus(String status) { deliveryStatus = status == null || status.isBlank() ? "DELIVERED" : status; }
public void markRead() { read = true; }
public void markUnread() { read = false; }
public CompoundTag toTag() {
return toTag(true);
}
public CompoundTag toTag(boolean includeAttachments) {
CompoundTag tag = new CompoundTag();
tag.putString(ID_TAG, id.toString());
tag.putString(FROM_TAG, sender.fullAddress());
@@ -65,9 +73,12 @@ public final class AntmailMessage {
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);
tag.putString(DELIVERY_STATUS_TAG, deliveryStatus);
if (includeAttachments) {
ListTag attachmentTags = new ListTag();
for (AntmailAttachment attachment : attachments) attachmentTags.add(attachment.toTag());
tag.put(ATTACHMENTS_TAG, attachmentTags);
}
return tag;
}
@@ -75,6 +86,8 @@ public final class AntmailMessage {
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));
AntmailMessage message = 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));
message.setDeliveryStatus(tag.getString(DELIVERY_STATUS_TAG));
return message;
}
}
@@ -7,18 +7,27 @@ import net.minecraft.nbt.ListTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import com.craisinlord.antarchy.content.block.entity.ComputerBlockEntity;
import net.minecraft.world.level.saveddata.SavedData;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public final class AntmailServerData extends SavedData {
private static final String ID = "antarchy_antmail";
private static final int MAX_PENDING_MESSAGES = AntmailValidation.MAX_MAILBOX_MESSAGES;
private final Map<AntmailAddress, Endpoint> endpoints = new LinkedHashMap<>();
private final AntmailMailboxStore mailboxes = new AntmailMailboxStore();
private final List<PendingMessage> pending = new ArrayList<>();
private final Map<AntmailAddress, java.util.Set<ResourceLocation>> triggered = new LinkedHashMap<>();
private final Map<AntmailAddress, Long> mailboxVersions = new LinkedHashMap<>();
private final Map<UUID, AntmailAddress> lastUsedAddresses = new LinkedHashMap<>();
private long randomMailDay = Long.MIN_VALUE;
private int retryTicks;
public static AntmailServerData create() {
return new AntmailServerData();
@@ -48,6 +57,35 @@ public final class AntmailServerData extends SavedData {
} catch (RuntimeException ignored) {
}
}
ListTag triggeredTags = tag.getList("Triggered", 10);
for (int i = 0; i < triggeredTags.size(); i++) {
CompoundTag triggeredTag = triggeredTags.getCompound(i);
try {
AntmailAddress address = AntmailAddress.parse(triggeredTag.getString("Address"));
java.util.Set<ResourceLocation> ids = new java.util.LinkedHashSet<>();
ListTag idsTag = triggeredTag.getList("Ids", 8);
for (int j = 0; j < idsTag.size(); j++) ids.add(ResourceLocation.parse(idsTag.getString(j)));
data.triggered.put(address, ids);
} catch (RuntimeException ignored) {
}
}
ListTag lastUsedTags = tag.getList("LastUsedAddresses", 10);
for (int i = 0; i < lastUsedTags.size(); i++) {
try {
CompoundTag lastUsedTag = lastUsedTags.getCompound(i);
data.lastUsedAddresses.put(UUID.fromString(lastUsedTag.getString("Player")), AntmailAddress.parse(lastUsedTag.getString("Address")));
} catch (RuntimeException ignored) {
}
}
data.randomMailDay = tag.contains("RandomMailDay") ? tag.getLong("RandomMailDay") : Long.MIN_VALUE;
ListTag versionTags = tag.getList("MailboxVersions", 10);
for (int i = 0; i < versionTags.size(); i++) {
try {
CompoundTag versionTag = versionTags.getCompound(i);
data.mailboxVersions.put(AntmailAddress.parse(versionTag.getString("Address")), Math.max(1L, versionTag.getLong("Version")));
} catch (RuntimeException ignored) {
}
}
return loaded;
}
@@ -71,6 +109,33 @@ public final class AntmailServerData extends SavedData {
pendingTags.add(pendingTag);
}
tag.put("Pending", pendingTags);
ListTag triggeredTags = new ListTag();
for (Map.Entry<AntmailAddress, java.util.Set<ResourceLocation>> entry : triggered.entrySet()) {
CompoundTag triggeredTag = new CompoundTag();
triggeredTag.putString("Address", entry.getKey().fullAddress());
ListTag idsTag = new ListTag();
for (ResourceLocation id : entry.getValue()) idsTag.add(net.minecraft.nbt.StringTag.valueOf(id.toString()));
triggeredTag.put("Ids", idsTag);
triggeredTags.add(triggeredTag);
}
tag.put("Triggered", triggeredTags);
ListTag lastUsedTags = new ListTag();
for (Map.Entry<UUID, AntmailAddress> entry : lastUsedAddresses.entrySet()) {
CompoundTag lastUsedTag = new CompoundTag();
lastUsedTag.putString("Player", entry.getKey().toString());
lastUsedTag.putString("Address", entry.getValue().fullAddress());
lastUsedTags.add(lastUsedTag);
}
tag.put("LastUsedAddresses", lastUsedTags);
tag.putLong("RandomMailDay", randomMailDay);
ListTag versionTags = new ListTag();
for (Map.Entry<AntmailAddress, Long> entry : mailboxVersions.entrySet()) {
CompoundTag versionTag = new CompoundTag();
versionTag.putString("Address", entry.getKey().fullAddress());
versionTag.putLong("Version", entry.getValue());
versionTags.add(versionTag);
}
tag.put("MailboxVersions", versionTags);
return tag;
}
@@ -123,6 +188,40 @@ public final class AntmailServerData extends SavedData {
return endpoints.get(address);
}
public synchronized List<AntmailAddress> registeredAddresses() {
return List.copyOf(endpoints.keySet());
}
public synchronized AntmailAddress addressFor(ServerPlayer player) {
AntmailAddress address = lastUsedAddresses.get(player.getUUID());
return address != null && endpoints.containsKey(address) ? address : null;
}
public synchronized AntmailAddress lastUsedAddress(ServerPlayer player) {
AntmailAddress address = lastUsedAddresses.get(player.getUUID());
return address != null && endpoints.containsKey(address) ? address : null;
}
public synchronized void setLastUsedAddress(ServerPlayer player, AntmailAddress address) {
if (address == null) return;
lastUsedAddresses.put(player.getUUID(), address);
setDirty();
}
public synchronized boolean claimTrigger(AntmailAddress address, ResourceLocation triggerId) {
java.util.Set<ResourceLocation> ids = triggered.computeIfAbsent(address, ignored -> new java.util.LinkedHashSet<>());
if (!ids.add(triggerId)) return false;
setDirty();
return true;
}
public synchronized boolean beginRandomMailDay(long day) {
if (randomMailDay == day) return false;
randomMailDay = day;
setDirty();
return true;
}
public synchronized AntmailMailbox mailbox(AntmailAddress address) {
return mailboxes.get(address);
}
@@ -133,34 +232,93 @@ public final class AntmailServerData extends SavedData {
return mailbox;
}
public synchronized long mailboxVersion(AntmailAddress address) {
return mailboxVersions.getOrDefault(address, 1L);
}
public synchronized void touchMailbox(AntmailAddress address) {
if (address == null) return;
mailboxVersions.put(address, mailboxVersion(address) + 1L);
setDirty();
}
public synchronized void drainAddress(MinecraftServer server, ServerLevel level, BlockPos position) {
AntmailAddress address = addressAt(level.dimension().location(), position);
if (address != null) drain(server, address);
}
public synchronized void drainAvailable(MinecraftServer server) {
if (pending.isEmpty() || ++retryTicks < 20) return;
retryTicks = 0;
List<AntmailAddress> available = new ArrayList<>();
for (Map.Entry<AntmailAddress, Endpoint> entry : endpoints.entrySet()) {
Endpoint endpoint = entry.getValue();
ServerLevel level = server.getLevel(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.DIMENSION, ResourceLocation.parse(endpoint.dimension())));
if (level != null && level.hasChunkAt(BlockPos.of(endpoint.position()))) available.add(entry.getKey());
}
for (AntmailAddress address : available) drain(server, address);
}
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");
if (message == null) return AntmailDeliveryResult.failed(AntmailDeliveryResult.Status.MESSAGE_INVALID, null, "missing_message");
if (endpoint(message.recipient()) == null) return failedAndRecorded(message, AntmailDeliveryResult.Status.ADDRESS_NOT_FOUND, "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");
if (destination.inbox().size() >= AntmailValidation.MAX_MAILBOX_MESSAGES) return failedAndRecorded(message, AntmailDeliveryResult.Status.MAILBOX_FULL, "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()))) {
if (pending.stream().anyMatch(item -> item.message().id().equals(message.id()))) {
return AntmailDeliveryResult.failed(AntmailDeliveryResult.Status.DUPLICATE, message, "already_pending");
}
if (pending.size() >= MAX_PENDING_MESSAGES) {
return failedAndRecorded(message, AntmailDeliveryResult.Status.FAILED, "pending_queue_full");
}
message.setDeliveryStatus("QUEUED");
pending.add(new PendingMessage(message.recipient(), message));
mailboxes.getOrCreate(message.sender()).addSent(message);
touchMailbox(message.recipient());
touchMailbox(message.sender());
setDirty();
return AntmailDeliveryResult.queued(message);
}
message.setDeliveryStatus("DELIVERED");
destination.addIncoming(message);
mailboxes.getOrCreate(message.sender()).addSent(message);
touchMailbox(message.recipient());
touchMailbox(message.sender());
setDirty();
return AntmailDeliveryResult.delivered(message);
}
private AntmailDeliveryResult failedAndRecorded(AntmailMessage message, AntmailDeliveryResult.Status status, String detail) {
message.setDeliveryStatus("FAILED // " + detail.toUpperCase());
mailboxes.getOrCreate(message.sender()).addSent(message);
touchMailbox(message.sender());
setDirty();
return AntmailDeliveryResult.failed(status, message, detail);
}
public synchronized void drain(MinecraftServer server, AntmailAddress address) {
AntmailMailbox mailbox = mailboxes.getOrCreate(address);
final boolean[] delivered = {false};
pending.removeIf(pendingMessage -> {
if (!pendingMessage.recipient().equals(address) || mailbox.inbox().size() >= AntmailValidation.MAX_MAILBOX_MESSAGES) return false;
mailbox.addIncoming(pendingMessage.message());
if (!mailbox.addIncoming(pendingMessage.message())) return false;
pendingMessage.message().setDeliveryStatus("DELIVERED");
delivered[0] = true;
return true;
});
if (delivered[0]) touchMailbox(address);
setDirty();
}
public synchronized AntmailDeliveryResult retry(MinecraftServer server, AntmailAddress sender, UUID messageId) {
AntmailMessage message = mailboxes.getOrCreate(sender).findSent(messageId);
if (message == null) return null;
pending.removeIf(item -> item.message().id().equals(messageId));
return deliver(server, message);
}
public record Endpoint(String dimension, long position) {
}
@@ -2,6 +2,7 @@ package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.block.entity.ComputerBlockEntity;
import com.craisinlord.antarchy.content.antmail.AntmailServerData;
import com.craisinlord.antarchy.content.client.AntarchyClientHooks;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
@@ -99,6 +100,9 @@ public final class ComputerBlock extends BaseEntityBlock {
protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
if (!state.is(newState.getBlock()) && level.getBlockEntity(pos) instanceof ComputerBlockEntity computer) {
computer.dropDisks(level, pos);
if (level instanceof net.minecraft.server.level.ServerLevel serverLevel) {
AntmailServerData.access(serverLevel.getServer()).unregister(serverLevel.dimension().location(), pos);
}
}
super.onRemove(state, level, pos, newState, movedByPiston);
}
@@ -41,6 +41,12 @@ public final class ComputerBlockEntity extends BlockEntity implements GeoBlockEn
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 String BASILISK_SCORE_TAG = "BasiliskScore";
private static final String BASILISK_HIGH_SCORE_TAG = "BasiliskHighScore";
private static final String ANTMAN_SCORE_TAG = "AntmanScore";
private static final String ANTMAN_HIGH_SCORE_TAG = "AntmanHighScore";
private static final String BLOCKLE_DAY_TAG = "BlockleDay";
private static final String BLOCKLE_GUESSES_TAG = "BlockleGuesses";
private static final long AUTHENTICATION_TIMEOUT = 6000L;
private final Set<ResourceLocation> physicalDiskIds = new LinkedHashSet<>();
private final ComputerFileSystem fileSystem = new ComputerFileSystem();
@@ -56,6 +62,12 @@ public final class ComputerBlockEntity extends BlockEntity implements GeoBlockEn
private long authenticatedUntil;
private java.util.UUID activeUser;
private boolean lastActive;
private int basiliskScore;
private int basiliskHighScore;
private int antmanScore;
private int antmanHighScore;
private long blockleDay = -1L;
private final List<String> blockleGuesses = new ArrayList<>();
public ComputerBlockEntity(BlockPos pos, BlockState state, Supplier<? extends BlockEntityType<ComputerBlockEntity>> type) {
super(type.get(), pos, state);
@@ -256,6 +268,50 @@ public final class ComputerBlockEntity extends BlockEntity implements GeoBlockEn
return desktopState;
}
public int basiliskScore() {
return basiliskScore;
}
public int basiliskHighScore() {
return basiliskHighScore;
}
public void setBasiliskScore(int score) {
basiliskScore = Math.max(0, score);
basiliskHighScore = Math.max(basiliskHighScore, basiliskScore);
setChanged();
}
public int antmanScore() { return antmanScore; }
public int antmanHighScore() { return antmanHighScore; }
public void setAntmanScore(int score) {
antmanScore = Math.max(0, score);
antmanHighScore = Math.max(antmanHighScore, antmanScore);
setChanged();
}
public long blockleDay() {
return blockleDay;
}
public List<String> blockleGuesses() {
return List.copyOf(blockleGuesses);
}
public void resetBlockle(long day) {
if (blockleDay == day) return;
blockleDay = day;
blockleGuesses.clear();
setChanged();
}
public boolean addBlockleGuess(String guess) {
if (blockleGuesses.size() >= 6) return false;
blockleGuesses.add(guess);
setChanged();
return true;
}
public boolean unlockWallpaper(ResourceLocation id) {
return serverDesktopMutation(() -> desktopState.unlockWallpaper(id));
}
@@ -346,6 +402,17 @@ public final class ComputerBlockEntity extends BlockEntity implements GeoBlockEn
password = tag.getString(PASSWORD_TAG);
fileSystem.load(tag, registries);
desktopState.load(tag.getCompound(DESKTOP_TAG));
basiliskScore = tag.getInt(BASILISK_SCORE_TAG);
basiliskHighScore = tag.getInt(BASILISK_HIGH_SCORE_TAG);
antmanScore = tag.getInt(ANTMAN_SCORE_TAG);
antmanHighScore = tag.getInt(ANTMAN_HIGH_SCORE_TAG);
blockleDay = tag.contains(BLOCKLE_DAY_TAG) ? tag.getLong(BLOCKLE_DAY_TAG) : -1L;
blockleGuesses.clear();
ListTag blockle = tag.getList(BLOCKLE_GUESSES_TAG, 8);
for (int index = 0; index < blockle.size(); index++) {
String guess = blockle.getString(index);
if (guess.length() == 5 && !blockleGuesses.contains(guess) && blockleGuesses.size() < 6) blockleGuesses.add(guess);
}
authenticated = false;
authenticatedUntil = 0L;
activeUser = null;
@@ -363,6 +430,14 @@ public final class ComputerBlockEntity extends BlockEntity implements GeoBlockEn
tag.putString(PASSWORD_TAG, password);
fileSystem.save(tag, registries);
tag.put(DESKTOP_TAG, desktopState.save());
tag.putInt(BASILISK_SCORE_TAG, basiliskScore);
tag.putInt(BASILISK_HIGH_SCORE_TAG, basiliskHighScore);
tag.putInt(ANTMAN_SCORE_TAG, antmanScore);
tag.putInt(ANTMAN_HIGH_SCORE_TAG, antmanHighScore);
tag.putLong(BLOCKLE_DAY_TAG, blockleDay);
ListTag blockle = new ListTag();
for (String guess : blockleGuesses) blockle.add(StringTag.valueOf(guess));
tag.put(BLOCKLE_GUESSES_TAG, blockle);
}
@Override
@@ -370,6 +445,9 @@ public final class ComputerBlockEntity extends BlockEntity implements GeoBlockEn
CompoundTag tag = new CompoundTag();
tag.putBoolean("HasPassword", hasPassword());
tag.putBoolean(AUTHENTICATED_TAG, isAuthenticated());
ListTag disks = new ListTag();
for (ResourceLocation diskId : physicalDiskIds) disks.add(StringTag.valueOf(diskId.toString()));
tag.put(DISKS_TAG, disks);
return tag;
}
@@ -1,6 +1,7 @@
package com.craisinlord.antarchy.content.block.entity;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.block.UnderVaultBlock;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.particles.SimpleParticleType;
@@ -22,6 +23,7 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.AABB;
import java.util.Optional;
import java.util.function.Supplier;
@@ -44,6 +46,14 @@ public final class UnderVaultBlockEntity extends BlockEntity {
public static void serverTick(ServerLevel level, BlockPos pos, BlockState state, UnderVaultBlockEntity blockEntity) {
blockEntity.vaultData.setLevel(level);
// The vanilla vault detector can miss this ceiling-mounted wrapper because its
// block state has a separate render-facing property. Keep the state synchronized
// with the same four-block activation range before running vanilla's state machine.
if (state.getValue(UnderVaultBlock.STATE) == VaultState.INACTIVE
&& !level.getEntitiesOfClass(Player.class, new AABB(pos).inflate(4.0D), player -> !player.isSpectator()).isEmpty()) {
state = state.setValue(UnderVaultBlock.STATE, VaultState.ACTIVE);
level.setBlockAndUpdate(pos, state);
}
VaultBlockEntity.Server.tick(level, pos, state, blockEntity.vaultData.getConfig(), blockEntity.vaultData.getServerData(), blockEntity.vaultData.getSharedData());
}
@@ -40,6 +40,7 @@ import java.util.function.Supplier;
public final class UndertrialSpawnerBlockEntity extends BlockEntity implements TrialSpawner.StateAccessor {
private static final String SPAWNER_TAG = "trial_spawner";
private static final int DEFAULT_TARGET_COOLDOWN_LENGTH = 36000;
private static final int EFFECT_DURATION = 3600;
private TrialSpawner trialSpawner;
private TrialSpawnerState previousClientState;
@@ -194,12 +195,17 @@ public final class UndertrialSpawnerBlockEntity extends BlockEntity implements T
private TrialSpawner createRewardlessSpawner(TrialSpawner source) {
TrialSpawner rewardless = new TrialSpawner(withoutLootTables(source.getNormalConfig()), withoutLootTables(source.getOminousConfig()),
source.getData(), source.getRequiredPlayerRange(), source.getTargetCooldownLength(), this,
source.getData(), source.getRequiredPlayerRange(), targetCooldownLength(source), this,
source.getPlayerDetector(), source.getEntitySelector());
rewardless.overridePeacefulAndMobSpawnRule();
return rewardless;
}
private static int targetCooldownLength(TrialSpawner source) {
int targetCooldownLength = source.getTargetCooldownLength();
return targetCooldownLength > 0 ? targetCooldownLength : DEFAULT_TARGET_COOLDOWN_LENGTH;
}
private static TrialSpawnerConfig withoutLootTables(TrialSpawnerConfig config) {
return new TrialSpawnerConfig(config.spawnRange(), config.totalMobs(), config.simultaneousMobs(),
config.totalMobsAddedPerPlayer(), config.simultaneousMobsAddedPerPlayer(), config.ticksBetweenSpawn(),
@@ -1,22 +1,70 @@
package com.craisinlord.antarchy.content.client;
import com.craisinlord.antarchy.content.network.AntmailResultPayload;
import com.craisinlord.antarchy.content.antmail.AntmailMessage;
import net.minecraft.core.BlockPos;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public final class AntmailClientState {
private static final int MAX_CACHED_DETAILS_PER_MAILBOX = 8;
private static final Map<BlockPos, AntmailResultPayload> RESULTS = new ConcurrentHashMap<>();
private static final Map<BlockPos, AntmailResultPayload> MAILBOXES = new ConcurrentHashMap<>();
private static final Map<BlockPos, Long> VERSIONS = new ConcurrentHashMap<>();
private static final Map<BlockPos, Map<java.util.UUID, AntmailMessage>> DETAILS = new ConcurrentHashMap<>();
private AntmailClientState() {
}
public static void update(AntmailResultPayload payload) {
RESULTS.put(payload.pos(), payload);
if ("unchanged".equals(payload.detail())) {
VERSIONS.put(payload.pos(), payload.version());
return;
} else if ("message_detail".equals(payload.detail()) && !payload.data().isBlank()) {
try {
AntmailMessage message = AntmailMessage.fromTag(com.craisinlord.antarchy.content.antmail.AntmailWire.decodeTag(payload.data()));
Map<java.util.UUID, AntmailMessage> details = DETAILS.computeIfAbsent(payload.pos(), ignored -> new ConcurrentHashMap<>());
details.put(message.id(), message);
while (details.size() > MAX_CACHED_DETAILS_PER_MAILBOX) {
java.util.UUID oldest = details.keySet().iterator().next();
details.remove(oldest);
}
} catch (RuntimeException ignored) {
}
} else if (!payload.data().isBlank()) {
MAILBOXES.put(payload.pos(), payload);
VERSIONS.put(payload.pos(), payload.version());
} else {
// A failed or unconfigured response is authoritative; never show a
// mailbox from a previous address/computer at this position.
MAILBOXES.remove(payload.pos());
VERSIONS.remove(payload.pos());
}
}
public static void clear(BlockPos pos) {
RESULTS.remove(pos);
MAILBOXES.remove(pos);
VERSIONS.remove(pos);
DETAILS.remove(pos);
}
public static AntmailResultPayload get(BlockPos pos) {
return RESULTS.get(pos);
}
public static AntmailResultPayload getMailbox(BlockPos pos) {
return MAILBOXES.get(pos);
}
public static long getVersion(BlockPos pos) {
return VERSIONS.getOrDefault(pos, 0L);
}
public static AntmailMessage getMessage(BlockPos pos, java.util.UUID id) {
Map<java.util.UUID, AntmailMessage> details = DETAILS.get(pos);
return details == null ? null : details.get(id);
}
}
@@ -0,0 +1,26 @@
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 BlockleClientState {
private static final Map<BlockPos, ComputerAccessResultPayload> RESULTS = new ConcurrentHashMap<>();
private BlockleClientState() {
}
public static void update(ComputerAccessResultPayload result) {
RESULTS.put(result.pos(), result);
}
public static ComputerAccessResultPayload get(BlockPos pos) {
return RESULTS.get(pos);
}
public static void clear(BlockPos pos) {
RESULTS.remove(pos);
}
}
@@ -13,10 +13,19 @@ public final class ComputerAccessClientState {
}
public static void update(ComputerAccessResultPayload result) {
RESULTS.put(result.pos(), result);
if (result.data().startsWith("18\u0000") || result.data().startsWith("19\u0000")) {
BlockleClientState.update(result);
}
// File, terminal, desktop, and wallpaper responses carry their own operation
// status. They must not replace the last authoritative login state.
if (result.data().isBlank()) RESULTS.put(result.pos(), result);
ComputerFileSystemClientState.update(result);
}
public static void clear(BlockPos pos) {
RESULTS.remove(pos);
}
public static ComputerAccessResultPayload get(BlockPos pos) {
return RESULTS.get(pos);
}
@@ -29,6 +29,13 @@ public final class ComputerFileSystemClientState {
state.success = payload.result() == ComputerAccessResultPayload.SUCCESS;
state.error = envelope[1];
if (state.success) state.error = "";
if (action == ComputerAccessPayload.FILE_CREATE || action == ComputerAccessPayload.FILE_SAVE
|| action == ComputerAccessPayload.FILE_DELETE || action == ComputerAccessPayload.FILE_MOVE) {
state.lastMutationAction = action;
state.lastMutationPath = envelope[2];
state.lastMutationSuccess = state.success;
state.lastMutationError = envelope[1];
}
if (action == ComputerAccessPayload.FILE_LIST) {
state.files = new ArrayList<>();
if (state.success && !envelope[2].isEmpty()) state.files.addAll(List.of(envelope[2].split("\\n")));
@@ -59,11 +66,19 @@ public final class ComputerFileSystemClientState {
private String openedContents = "";
private String error = "";
private boolean success;
private int lastMutationAction = -1;
private String lastMutationPath = "";
private boolean lastMutationSuccess;
private String lastMutationError = "";
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; }
public int lastMutationAction() { return lastMutationAction; }
public String lastMutationPath() { return lastMutationPath; }
public boolean lastMutationSuccess() { return lastMutationSuccess; }
public String lastMutationError() { return lastMutationError; }
}
}
@@ -1,168 +0,0 @@
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;
};
}
}
@@ -1,22 +0,0 @@
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;
}
}
@@ -1,53 +0,0 @@
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);
}
}
@@ -1,65 +0,0 @@
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);
}
}
@@ -0,0 +1,233 @@
package com.craisinlord.antarchy.content.client.game;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public final class AntmanGame {
public enum Phase { READY, PLAYING, LOST }
public enum Direction { UP(0, -1), DOWN(0, 1), LEFT(-1, 0), RIGHT(1, 0);
final int dx;
final int dy;
Direction(int dx, int dy) { this.dx = dx; this.dy = dy; }
Direction opposite() { return switch (this) { case UP -> DOWN; case DOWN -> UP; case LEFT -> RIGHT; case RIGHT -> LEFT; }; }
}
public enum EnemyKind { CREEPER, ZOMBIE }
public record Enemy(EnemyKind kind, double x, double y, Direction direction) { }
private static final int WIDTH = 28;
private static final int HEIGHT = 20;
private static final double SPEED = 0.075D;
private final Random random = new Random();
private final boolean[][] walls = new boolean[HEIGHT][WIDTH];
private final List<Enemy> enemies = new ArrayList<>();
private Direction direction = Direction.RIGHT;
private Direction requested = Direction.RIGHT;
private Phase phase = Phase.READY;
private double playerX;
private double playerY;
private int diamondX;
private int diamondY;
private int cornDogX;
private int cornDogY;
private int cornDogTicks;
private int cornDogSpawnTicks;
private int score;
private int highScore;
private int lives = 3;
private int ticks;
private boolean collectedDiamond;
public AntmanGame(int highScore, int score) {
this.highScore = Math.max(0, highScore);
reset(score);
}
public void reset() { reset(0); }
public void reset(int currentScore) {
generateMaze();
playerX = 1.5D;
playerY = 1.5D;
direction = Direction.RIGHT;
requested = Direction.RIGHT;
phase = Phase.READY;
score = Math.max(0, currentScore);
lives = 3;
ticks = 0;
cornDogTicks = 0;
cornDogSpawnTicks = 0;
placeDiamond();
cornDogX = -1;
cornDogY = -1;
enemies.clear();
enemies.add(new Enemy(EnemyKind.CREEPER, WIDTH - 2.5D, HEIGHT - 2.5D, Direction.LEFT));
enemies.add(new Enemy(EnemyKind.ZOMBIE, WIDTH - 2.5D, 1.5D, Direction.DOWN));
}
public void start() { if (phase == Phase.READY) phase = Phase.PLAYING; }
public boolean tick() {
collectedDiamond = false;
if (phase != Phase.PLAYING) return false;
ticks++;
if (cornDogTicks > 0) cornDogTicks--;
if (cornDogX < 0 && ++cornDogSpawnTicks >= 240) {
placeCornDog();
cornDogSpawnTicks = 0;
}
updatePlayer();
updateEnemies();
if (near(playerX, playerY, diamondX + 0.5D, diamondY + 0.5D)) {
score += 10;
highScore = Math.max(highScore, score);
collectedDiamond = true;
placeDiamond();
}
if (cornDogX >= 0 && near(playerX, playerY, cornDogX + 0.5D, cornDogY + 0.5D)) {
cornDogTicks = 200;
cornDogX = -1;
cornDogY = -1;
cornDogSpawnTicks = 0;
}
for (int index = 0; index < enemies.size(); index++) {
Enemy enemy = enemies.get(index);
if (!near(playerX, playerY, enemy.x(), enemy.y())) continue;
if (cornDogTicks > 0) {
score += 100;
highScore = Math.max(highScore, score);
enemies.set(index, new Enemy(enemy.kind(), WIDTH - 2.5D, enemy.kind() == EnemyKind.CREEPER ? HEIGHT - 2.5D : 1.5D, Direction.LEFT));
} else {
lives--;
if (lives <= 0) phase = Phase.LOST;
else {
playerX = 1.5D;
playerY = 1.5D;
direction = Direction.RIGHT;
requested = Direction.RIGHT;
}
}
}
return true;
}
public void setDirection(Direction direction) { if (direction != null) requested = direction; }
public Phase phase() { return phase; }
public int score() { return score; }
public int highScore() { return highScore; }
public int lives() { return lives; }
public int width() { return WIDTH; }
public int height() { return HEIGHT; }
public double playerX() { return playerX; }
public double playerY() { return playerY; }
public int diamondX() { return diamondX; }
public int diamondY() { return diamondY; }
public int cornDogX() { return cornDogX; }
public int cornDogY() { return cornDogY; }
public int cornDogTicks() { return cornDogTicks; }
public int animationFrame() { return (ticks / 5) % 2; }
public Direction direction() { return direction; }
public List<Enemy> enemies() { return Collections.unmodifiableList(enemies); }
public boolean collectedDiamond() { return collectedDiamond; }
public boolean isWall(int x, int y) { return x < 0 || y < 0 || x >= WIDTH || y >= HEIGHT || walls[y][x]; }
private void updatePlayer() {
if (atCenter(playerX) && atCenter(playerY) && canMove(playerX, playerY, requested)) {
playerX = Math.round(playerX - 0.5D) + 0.5D;
playerY = Math.round(playerY - 0.5D) + 0.5D;
direction = requested;
}
double nextX = playerX + direction.dx * SPEED;
double nextY = playerY + direction.dy * SPEED;
if (canMove(nextX, nextY, direction)) { playerX = nextX; playerY = nextY; }
else { playerX = Math.round(playerX - 0.5D) + 0.5D; playerY = Math.round(playerY - 0.5D) + 0.5D; }
}
private void updateEnemies() {
for (int index = 0; index < enemies.size(); index++) {
Enemy enemy = enemies.get(index);
Direction next = enemy.direction();
if (atCenter(enemy.x()) && atCenter(enemy.y())) next = enemy.kind() == EnemyKind.CREEPER ? chaseDirection(enemy.x(), enemy.y(), next) : zombieDirection(enemy.x(), enemy.y(), next);
double nextX = enemy.x() + next.dx * (enemy.kind() == EnemyKind.CREEPER ? 0.06D : 0.05D);
double nextY = enemy.y() + next.dy * (enemy.kind() == EnemyKind.CREEPER ? 0.06D : 0.05D);
if (!canMove(nextX, nextY, next)) { next = randomDirection(enemy.x(), enemy.y(), next); nextX = enemy.x() + next.dx * 0.05D; nextY = enemy.y() + next.dy * 0.05D; }
if (canMove(nextX, nextY, next)) enemies.set(index, new Enemy(enemy.kind(), nextX, nextY, next));
}
}
private Direction chaseDirection(double x, double y, Direction current) {
Direction best = current;
double distance = Double.MAX_VALUE;
for (Direction direction : Direction.values()) {
if (isOpposite(direction, current) || !canMove(x, y, direction)) continue;
double nx = x + direction.dx;
double ny = y + direction.dy;
double value = Math.abs(playerX - nx) + Math.abs(playerY - ny);
if (value < distance) { distance = value; best = direction; }
}
if (distance == Double.MAX_VALUE && canMove(x, y, current.opposite())) best = current.opposite();
return best;
}
private Direction zombieDirection(double x, double y, Direction current) {
if (random.nextInt(4) != 0 && canMove(x, y, current)) return current;
return randomDirection(x, y, current);
}
private Direction randomDirection(double x, double y, Direction current) {
List<Direction> options = new ArrayList<>();
for (Direction direction : Direction.values()) if (!isOpposite(direction, current) && canMove(x, y, direction)) options.add(direction);
if (options.isEmpty() && canMove(x, y, current.opposite())) return current.opposite();
if (options.isEmpty()) return current;
return options.get(random.nextInt(options.size()));
}
private boolean canMove(double x, double y, Direction direction) {
double nx = x + direction.dx * SPEED;
double ny = y + direction.dy * SPEED;
return !isWall((int) Math.floor(nx), (int) Math.floor(ny));
}
private boolean atCenter(double value) { return Math.abs((value - 0.5D) - Math.rint(value - 0.5D)) < 0.07D; }
private boolean near(double firstX, double firstY, double secondX, double secondY) { return Math.abs(firstX - secondX) < 0.45D && Math.abs(firstY - secondY) < 0.45D; }
private boolean isOpposite(Direction first, Direction second) { return first.dx + second.dx == 0 && first.dy + second.dy == 0; }
private void generateMaze() {
for (int y = 0; y < HEIGHT; y++) for (int x = 0; x < WIDTH; x++) walls[y][x] = true;
carve(1, 1);
}
private void carve(int x, int y) {
walls[y][x] = false;
List<Direction> directions = new ArrayList<>(List.of(Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT));
Collections.shuffle(directions, random);
for (Direction direction : directions) {
int nx = x + direction.dx * 2;
int ny = y + direction.dy * 2;
if (nx > 0 && ny > 0 && nx < WIDTH - 1 && ny < HEIGHT - 1 && walls[ny][nx]) {
walls[y + direction.dy][x + direction.dx] = false;
carve(nx, ny);
}
}
}
private void placeDiamond() {
int[] cell = randomOpenCell();
diamondX = cell[0];
diamondY = cell[1];
}
private void placeCornDog() {
int[] cell = randomOpenCell();
cornDogX = cell[0];
cornDogY = cell[1];
}
private int[] randomOpenCell() {
int x;
int y;
do { x = random.nextInt(WIDTH); y = random.nextInt(HEIGHT); } while (isWall(x, y) || near(playerX, playerY, x + 0.5D, y + 0.5D));
return new int[]{x, y};
}
}
@@ -0,0 +1,48 @@
package com.craisinlord.antarchy.content.client.game;
import com.craisinlord.antarchy.content.AntarchyObjects;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.sounds.SoundEvents;
import java.util.function.IntConsumer;
public final class AntmanProgram {
private final AntmanGame game;
private final IntConsumer scoreSaver;
private boolean installed;
private int savedScore;
private int savedHighScore;
public AntmanProgram(boolean installed, int highScore, int score, IntConsumer scoreSaver) {
this.installed = installed;
this.savedScore = score;
this.savedHighScore = highScore;
this.game = new AntmanGame(highScore, score);
this.scoreSaver = scoreSaver;
}
public void setInstalled(boolean installed) { this.installed = installed; if (!installed) game.reset(); }
public boolean installed() { return installed; }
public AntmanGame game() { return game; }
public void setStoredScores(int score, int highScore) { savedScore = score; savedHighScore = highScore; game.reset(score); }
public void tick() {
if (!game.tick()) return;
if (game.collectedDiamond() && Minecraft.getInstance().player != null) Minecraft.getInstance().player.playSound(SoundEvents.AMETHYST_BLOCK_CHIME, 0.6F, 1.4F);
if (game.score() != savedScore || game.highScore() != savedHighScore) { savedScore = game.score(); savedHighScore = game.highScore(); scoreSaver.accept(savedScore); }
}
public boolean keyPressed(int keyCode) {
if (!installed) return false;
AntmanGame.Direction direction = switch (keyCode) {
case 265, 87 -> AntmanGame.Direction.UP;
case 264, 83 -> AntmanGame.Direction.DOWN;
case 263, 65 -> AntmanGame.Direction.LEFT;
case 262, 68 -> AntmanGame.Direction.RIGHT;
default -> null;
};
if (direction != null) { game.start(); game.setDirection(direction); return true; }
if (keyCode == 257 || keyCode == 335) { if (game.phase() != AntmanGame.Phase.PLAYING) { game.reset(); game.start(); savedScore = 0; scoreSaver.accept(0); } return true; }
return false;
}
public void render(GuiGraphics graphics, Font font, int x, int y, int width, int height) { AntmanRenderer.render(graphics, font, game, x, y, width, height); }
}
@@ -0,0 +1,100 @@
package com.craisinlord.antarchy.content.client.game;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
public final class AntmanRenderer {
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 static final int ANT_BODY = 0xFFC98A3D;
private static final int GHOST_RED = 0xFFFF4A4A;
private static final int GHOST_PINK = 0xFFFF8FD2;
private static final int GHOST_FRIGHTENED = 0xFF4A8CFF;
private static final int GHOST_EYE = 0xFFF8FFFF;
private AntmanRenderer() { }
public static void render(GuiGraphics graphics, Font font, AntmanGame game, int x, int y, int width, int height) {
graphics.fill(x, y, x + width, y + height, BLACK);
graphics.drawString(font, "SCORE " + String.format("%04d", game.score()), x + 5, y + 4, GREEN, false);
graphics.drawString(font, "HI " + String.format("%04d", game.highScore()), x + width - 55, y + 4, PALE_GREEN, false);
graphics.drawString(font, "LIVES " + game.lives(), x + 5, y + height - 13, PALE_GREEN, false);
int tile = Math.max(1, Math.min((width - 8) / game.width(), (height - 34) / game.height()));
int boardWidth = tile * game.width();
int boardHeight = tile * game.height();
int boardX = x + (width - boardWidth) / 2;
int boardY = y + 20 + (height - 34 - boardHeight) / 2;
for (int cellY = 0; cellY < game.height(); cellY++) for (int cellX = 0; cellX < game.width(); cellX++) if (game.isWall(cellX, cellY)) graphics.fill(boardX + cellX * tile, boardY + cellY * tile, boardX + (cellX + 1) * tile, boardY + (cellY + 1) * tile, DARK_GREEN);
renderItem(graphics, new ItemStack(Items.DIAMOND), boardX + game.diamondX() * tile, boardY + game.diamondY() * tile, tile);
if (game.cornDogX() >= 0) renderItem(graphics, new ItemStack(AntarchyObjects.COOKED_CORNDOG.get()), boardX + game.cornDogX() * tile, boardY + game.cornDogY() * tile, tile);
int playerX = boardX + (int) (game.playerX() * tile);
int playerY = boardY + (int) (game.playerY() * tile);
drawAnt(graphics, playerX, playerY, tile, game.animationFrame(), ANT_BODY, game.direction());
for (AntmanGame.Enemy enemy : game.enemies()) {
int enemyX = boardX + (int) (enemy.x() * tile);
int enemyY = boardY + (int) (enemy.y() * tile);
int color = game.cornDogTicks() > 0 ? GHOST_FRIGHTENED : (enemy.kind() == AntmanGame.EnemyKind.CREEPER ? GHOST_RED : GHOST_PINK);
drawGhost(graphics, enemyX, enemyY, tile, game.animationFrame(), color);
}
if (game.phase() != AntmanGame.Phase.PLAYING) {
String title = game.phase() == AntmanGame.Phase.READY ? "ANTMAN.EXE" : "GAME OVER";
String prompt = game.phase() == AntmanGame.Phase.READY ? "ENTER TO START" : "ENTER TO RETRY";
int centerY = y + height / 2;
graphics.fill(x + 4, centerY - 16, x + width - 4, centerY + 16, BLACK);
graphics.drawString(font, title, x + (width - font.width(title)) / 2, centerY - 12, GREEN, false);
graphics.drawString(font, prompt, x + (width - font.width(prompt)) / 2, centerY + 2, PALE_GREEN, false);
}
}
private static void drawAnt(GuiGraphics graphics, int centerX, int centerY, int tile, int frame, int color, AntmanGame.Direction direction) {
int size = Math.max(4, tile - 1);
int left = centerX - size / 2;
int top = centerY - size / 2;
int segment = Math.max(1, size / 4);
graphics.fill(left + segment * 2, top + segment, left + segment * 3, top + segment * 3, color);
graphics.fill(left + segment, top + segment * 2, left + segment * 4, top + segment * 4, color);
graphics.fill(left + segment * 3, top + segment * 2, left + segment * 5, top + segment * 4, color);
int legOffset = frame == 0 ? 1 : -1;
graphics.fill(left + segment, top + segment * 3, left + segment * 2, top + segment * 4 + legOffset, color);
graphics.fill(left + segment * 3, top + segment * 3, left + segment * 4, top + segment * 4 - legOffset, color);
if (direction == AntmanGame.Direction.LEFT || direction == AntmanGame.Direction.RIGHT) {
int headX = direction == AntmanGame.Direction.LEFT ? left : left + segment * 4;
graphics.fill(headX, top + segment, headX + segment, top + segment * 2, BLACK);
}
}
private static void drawGhost(GuiGraphics graphics, int centerX, int centerY, int tile, int frame, int color) {
int size = Math.max(4, tile - 1);
int left = centerX - size / 2;
int top = centerY - size / 2;
int eyeWidth = Math.max(1, size / 5);
int bodyBottom = top + size - 1;
graphics.fill(left + 1, top, left + size - 1, bodyBottom - 1, color);
graphics.fill(left, top + 2, left + size, bodyBottom - 2, color);
int wave = frame == 0 ? 0 : 1;
graphics.fill(left, bodyBottom - 1 - wave, left + size / 3, bodyBottom + 1, color);
graphics.fill(left + size / 3, bodyBottom - wave, left + size * 2 / 3, bodyBottom + 1, color);
graphics.fill(left + size * 2 / 3, bodyBottom - 1 - wave, left + size, bodyBottom + 1, color);
int eyeY = top + Math.max(1, size / 3);
graphics.fill(left + size / 3 - eyeWidth / 2, eyeY, left + size / 3 + eyeWidth, eyeY + eyeWidth + 1, GHOST_EYE);
graphics.fill(left + size * 2 / 3 - eyeWidth / 2, eyeY, left + size * 2 / 3 + eyeWidth, eyeY + eyeWidth + 1, GHOST_EYE);
graphics.fill(left + size / 3, eyeY + 1, left + size / 3 + 1, eyeY + eyeWidth + 1, BLACK);
graphics.fill(left + size * 2 / 3, eyeY + 1, left + size * 2 / 3 + 1, eyeY + eyeWidth + 1, BLACK);
}
private static void renderItem(GuiGraphics graphics, ItemStack stack, int x, int y, int tile) {
float scale = Math.min(1.0F, Math.max(0.45F, tile / 16.0F));
graphics.pose().pushPose();
graphics.pose().translate(x + tile / 2.0F, y + tile / 2.0F, 0.0F);
graphics.pose().scale(scale, scale, 1.0F);
RenderSystem.setShaderColor(0.38F, 1.0F, 0.38F, 1.0F);
graphics.renderItem(stack, -8, -8);
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
graphics.pose().popPose();
}
}
@@ -0,0 +1,94 @@
package com.craisinlord.antarchy.content.client.game;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Random;
public final class BasiliskGame {
public enum Phase { READY, PLAYING, LOST }
public enum Direction { UP(0, -1), DOWN(0, 1), LEFT(-1, 0), RIGHT(1, 0);
final int dx;
final int dy;
Direction(int dx, int dy) { this.dx = dx; this.dy = dy; }
}
private final Deque<Part> body = new ArrayDeque<>();
private final Random random = new Random();
private Direction direction = Direction.RIGHT;
private Direction requested = Direction.RIGHT;
private Phase phase = Phase.READY;
private int foodX;
private int foodY;
private int score;
private int highScore;
private int ticks;
public BasiliskGame(int highScore, int score) {
this.highScore = Math.max(0, highScore);
reset(score);
}
public void reset() {
reset(0);
}
public void reset(int currentScore) {
body.clear();
body.addFirst(new Part(9, 7));
body.addLast(new Part(8, 7));
body.addLast(new Part(7, 7));
direction = Direction.RIGHT;
requested = Direction.RIGHT;
phase = Phase.READY;
score = Math.max(0, currentScore);
ticks = 0;
placeFood();
}
public void start() { if (phase == Phase.READY) phase = Phase.PLAYING; }
public boolean tick() {
if (phase != Phase.PLAYING || ++ticks < 5) return false;
ticks = 0;
if (!isOpposite(requested, direction)) direction = requested;
Part head = body.peekFirst();
int nextX = head.x() + direction.dx;
int nextY = head.y() + direction.dy;
if (nextX < 0 || nextX >= 20 || nextY < 0 || nextY >= 14 || body.contains(new Part(nextX, nextY))) {
phase = Phase.LOST;
return true;
}
body.addFirst(new Part(nextX, nextY));
if (nextX == foodX && nextY == foodY) {
score += 10;
highScore = Math.max(highScore, score);
placeFood();
} else {
body.removeLast();
}
return true;
}
public void setDirection(Direction direction) { if (direction != null && !isOpposite(direction, this.direction)) requested = direction; }
public Phase phase() { return phase; }
public int score() { return score; }
public int highScore() { return highScore; }
public int width() { return 20; }
public int height() { return 14; }
public int foodX() { return foodX; }
public int foodY() { return foodY; }
public Deque<Part> body() { return body; }
private void placeFood() {
do {
foodX = random.nextInt(width());
foodY = random.nextInt(height());
} while (body.contains(new Part(foodX, foodY)));
}
private boolean isOpposite(Direction first, Direction second) {
return first.dx + second.dx == 0 && first.dy + second.dy == 0;
}
public record Part(int x, int y) { }
}
@@ -0,0 +1,43 @@
package com.craisinlord.antarchy.content.client.game;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import java.util.function.IntConsumer;
public final class BasiliskProgram {
private final BasiliskGame game;
private boolean installed;
private int savedScore;
private int savedHighScore;
private final IntConsumer scoreSaver;
public BasiliskProgram(boolean installed, int highScore, int score, IntConsumer scoreSaver) {
this.installed = installed;
this.savedHighScore = highScore;
this.savedScore = score;
this.game = new BasiliskGame(highScore, score);
this.scoreSaver = scoreSaver;
}
public void setInstalled(boolean installed) { this.installed = installed; if (!installed) game.reset(); }
public boolean installed() { return installed; }
public BasiliskGame game() { return game; }
public void setStoredScores(int score, int highScore) { savedScore = score; savedHighScore = highScore; game.reset(score); }
public void tick() { if (game.tick() && (game.score() != savedScore || game.highScore() != savedHighScore)) { if (game.score() > savedScore) Minecraft.getInstance().player.playSound(AntarchySoundEvents.BASILISK_BITE.get(), 0.5F, 1.2F); savedScore = game.score(); savedHighScore = game.highScore(); scoreSaver.accept(savedScore); } }
public boolean keyPressed(int keyCode) {
if (!installed) return false;
BasiliskGame.Direction direction = switch (keyCode) {
case 265, 87 -> BasiliskGame.Direction.UP;
case 264, 83 -> BasiliskGame.Direction.DOWN;
case 263, 65 -> BasiliskGame.Direction.LEFT;
case 262, 68 -> BasiliskGame.Direction.RIGHT;
default -> null;
};
if (direction != null) { game.start(); game.setDirection(direction); return true; }
if (keyCode == 257 || keyCode == 335) { if (game.phase() != BasiliskGame.Phase.PLAYING) { game.reset(); game.start(); savedScore = 0; scoreSaver.accept(0); } return true; }
return false;
}
public void render(GuiGraphics graphics, Font font, int x, int y, int width, int height) { BasiliskRenderer.render(graphics, font, game, x, y, width, height); }
}
@@ -0,0 +1,39 @@
package com.craisinlord.antarchy.content.client.game;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
public final class BasiliskRenderer {
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 BasiliskRenderer() { }
public static void render(GuiGraphics graphics, Font font, BasiliskGame game, int x, int y, int width, int height) {
graphics.fill(x, y, x + width, y + height, BLACK);
graphics.drawString(font, "SCORE " + String.format("%04d", game.score()), x + 6, y + 4, GREEN, false);
graphics.drawString(font, "HI " + String.format("%04d", game.highScore()), x + width - 56, y + 4, PALE_GREEN, false);
int tile = Math.max(1, Math.min((width - 10) / game.width(), (height - 28) / game.height()));
int boardWidth = tile * game.width();
int boardHeight = tile * game.height();
int boardX = x + (width - boardWidth) / 2;
int boardY = y + 22 + (height - 22 - boardHeight) / 2;
graphics.fill(boardX, boardY, boardX + boardWidth, boardY + boardHeight, DARK_GREEN);
graphics.fill(boardX + 1, boardY + 1, boardX + boardWidth - 1, boardY + boardHeight - 1, BLACK);
int appleX = boardX + game.foodX() * tile;
int appleY = boardY + game.foodY() * tile;
int appleInset = Math.max(1, tile / 4);
graphics.fill(appleX + appleInset, appleY + appleInset + 1, appleX + tile - appleInset, appleY + tile - appleInset, PALE_GREEN);
if (tile >= 5) graphics.fill(appleX + tile / 2, appleY + 1, appleX + tile / 2 + 1, appleY + tile / 3 + 1, GREEN);
for (BasiliskGame.Part part : game.body()) graphics.fill(boardX + part.x() * tile + 1, boardY + part.y() * tile + 1, boardX + (part.x() + 1) * tile - 1, boardY + (part.y() + 1) * tile - 1, GREEN);
if (game.phase() != BasiliskGame.Phase.PLAYING) {
String title = game.phase() == BasiliskGame.Phase.READY ? "BASILISK" : "GAME OVER";
String prompt = game.phase() == BasiliskGame.Phase.READY ? "ENTER TO START" : "ENTER TO RETRY";
int centerY = y + height / 2;
graphics.fill(x + 4, centerY - 16, x + width - 4, centerY + 16, BLACK);
graphics.drawString(font, title, x + (width - font.width(title)) / 2, centerY - 12, GREEN, false);
graphics.drawString(font, prompt, x + (width - font.width(prompt)) / 2, centerY + 2, PALE_GREEN, false);
}
}
}
@@ -0,0 +1,141 @@
package com.craisinlord.antarchy.content.client.game;
import com.craisinlord.antarchy.content.client.BlockleClientState;
import com.craisinlord.antarchy.content.network.ComputerAccessResultPayload;
import com.craisinlord.antarchy.content.network.ComputerNetworking;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import java.util.ArrayList;
import java.util.List;
public final class BlockleProgram {
private final BlockPos position;
private final List<String> guesses = new ArrayList<>();
private final List<String> colors = new ArrayList<>();
private String input = "";
private String status = "LOADING...";
private String phase = "PLAYING";
private String answer = "";
private ResourceLocation answerId;
private long day = -1L;
private boolean installed;
private boolean pending;
private boolean received;
private String lastResponse = "";
public BlockleProgram(BlockPos position) {
this.position = position;
}
public void setInstalled(boolean installed) {
this.installed = installed;
if (!installed) clear();
}
public boolean installed() {
return installed;
}
public void requestState() {
if (installed) ComputerNetworking.requestBlockleState(position);
}
public void tick() {
ComputerAccessResultPayload result = BlockleClientState.get(position);
if (result == null) return;
if (result.data().equals(lastResponse)) return;
lastResponse = result.data();
String[] fields = result.data().split("\u0000", 3);
if (fields.length < 2 || (!fields[0].equals("18") && !fields[0].equals("19"))) return;
if (!fields[1].isBlank()) {
status = fields[1].toUpperCase();
pending = false;
return;
}
if (fields.length == 3) {
parse(fields[2]);
pending = false;
input = "";
received = true;
}
}
public boolean keyPressed(int keyCode) {
if (!installed) return false;
if (keyCode == 259) {
if (!input.isEmpty()) input = input.substring(0, input.length() - 1);
return true;
}
if (keyCode == 257 || keyCode == 335) {
if (!received) return true;
if (input.length() == 5 && phase.equals("PLAYING") && !pending) {
ComputerNetworking.submitBlockleGuess(position, input);
pending = true;
status = "SUBMITTING";
}
return true;
}
return false;
}
public boolean charTyped(char codePoint) {
if (!installed || !received || phase.equals("SOLVED") || phase.equals("FAILED")) return false;
if (codePoint >= 'A' && codePoint <= 'Z') codePoint = Character.toLowerCase(codePoint);
if (codePoint >= 'a' && codePoint <= 'z' && input.length() < 5) {
input += codePoint;
status = "";
return true;
}
return false;
}
public void render(GuiGraphics graphics, Font font, int x, int y, int width, int height) {
BlockleRenderer.render(graphics, font, x, y, width, height, guesses, colors, input, status, phase, answerId);
}
private void parse(String encoded) {
String[] values = encoded.split("\\|");
if (values.length == 0) return;
try { day = Long.parseLong(values[0]); } catch (NumberFormatException ignored) { return; }
guesses.clear();
colors.clear();
phase = "PLAYING";
answer = "";
for (int index = 1; index < values.length; index++) {
String value = values[index];
if (value.equals("PLAYING") || value.equals("SOLVED") || value.equals("FAILED")) {
phase = value;
if (index + 1 < values.length && (phase.equals("SOLVED") || phase.equals("FAILED"))) {
answer = values[index + 1];
if (index + 2 < values.length) {
try { answerId = ResourceLocation.parse(values[index + 2]); } catch (IllegalArgumentException ignored) { answerId = null; }
}
}
break;
}
String[] guess = value.split(",", 2);
if (guess.length == 2 && guess[0].length() == 5 && guess[1].length() == 5) {
guesses.add(guess[0]);
colors.add(guess[1]);
}
}
status = phase.equals("FAILED") && !answer.isBlank() ? "ANSWER: " + answer.toUpperCase() : phase.equals("PLAYING") ? "" : phase;
}
private void clear() {
guesses.clear();
colors.clear();
input = "";
status = "LOADING...";
phase = "PLAYING";
answer = "";
answerId = null;
day = -1L;
pending = false;
received = false;
lastResponse = "";
}
}
@@ -0,0 +1,61 @@
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;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.ItemStack;
import net.minecraft.resources.ResourceLocation;
import java.util.List;
public final class BlockleRenderer {
private static final int EMPTY = 0xFF102010;
private static final int GREY = 0xFF3A403A;
private static final int YELLOW = 0xFFB59F3B;
private static final int GREEN = 0xFF538D4E;
private static final int PALE = 0xFFB8FFB8;
private BlockleRenderer() {
}
public static void render(GuiGraphics graphics, Font font, int x, int y, int width, int height,
List<String> guesses, List<String> colors, String input, String status, String phase,
ResourceLocation answerId) {
graphics.drawString(font, Component.literal("BLOCKLE"), x, y, 0xFF65FF65, false);
int cell = 18;
int gap = 3;
int boardX = x + Math.max(0, (width - cell * 5 - gap * 4) / 2);
int boardY = y + 18;
for (int row = 0; row < 6; row++) {
String guess = row < guesses.size() ? guesses.get(row) : row == guesses.size() ? input : "";
String rowColors = row < colors.size() ? colors.get(row) : "";
for (int column = 0; column < 5; column++) {
int left = boardX + column * (cell + gap);
int top = boardY + row * (cell + gap);
int fill = EMPTY;
if (column < rowColors.length()) fill = switch (rowColors.charAt(column)) {
case 'G' -> GREEN;
case 'Y' -> YELLOW;
default -> GREY;
};
graphics.fill(left, top, left + cell, top + cell, fill);
if (column < guess.length()) {
String letter = Character.toString(Character.toUpperCase(guess.charAt(column)));
graphics.drawCenteredString(font, letter, left + cell / 2, top + 5, PALE);
}
}
}
String message = status.isBlank() ? "ENTER TO SUBMIT" : status;
if (phase.equals("PLAYING") && input.length() < 5) message = input.length() + "/5 LETTERS";
graphics.drawString(font, Component.literal(message), x, y + 145, PALE, false);
if (!phase.equals("PLAYING") && answerId != null) {
ItemStack answerStack = BuiltInRegistries.ITEM.getOptional(answerId).map(ItemStack::new).orElse(ItemStack.EMPTY);
if (!answerStack.isEmpty()) {
graphics.setColor(0.35F, 1.0F, 0.35F, 1.0F);
graphics.renderItem(answerStack, x + width - 28, y + 137);
graphics.setColor(1.0F, 1.0F, 1.0F, 1.0F);
}
}
}
}
@@ -80,7 +80,7 @@ public final class ComputerFileSystem {
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");
if (current.type() != ComputerFile.Type.TEXT && current.type() != ComputerFile.Type.IMAGE) return Result.failure("not_file");
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));
@@ -102,8 +102,29 @@ public final class ComputerFileSystem {
int slash = to.lastIndexOf('/');
if (slash > 0 && !nodesDirectory(to.substring(0, slash))) return Result.failure("missing_directory");
if (file.type() == ComputerFile.Type.DIRECTORY && to.startsWith(from + "/")) return Result.failure("invalid_path");
nodes.remove(from);
nodes.put(to, file.type() == ComputerFile.Type.DIRECTORY ? ComputerFile.directory(to) : ComputerFile.text(to, file.contents()));
if (file.type() == ComputerFile.Type.DIRECTORY) {
List<String> descendants = nodes.keySet().stream()
.filter(path -> path.startsWith(from + "/"))
.toList();
for (String descendant : descendants) {
String movedPath = to + descendant.substring(from.length());
if (nodes.containsKey(movedPath)) return Result.failure("already_exists");
}
Map<String, ComputerFile> moved = new LinkedHashMap<>();
moved.put(to, ComputerFile.directory(to));
for (String descendant : descendants) {
ComputerFile child = nodes.get(descendant);
String movedPath = to + descendant.substring(from.length());
moved.put(movedPath, child.type() == ComputerFile.Type.DIRECTORY
? ComputerFile.directory(movedPath)
: ComputerFile.text(movedPath, child.contents()));
}
nodes.keySet().removeIf(path -> path.equals(from) || path.startsWith(from + "/"));
nodes.putAll(moved);
} else {
nodes.remove(from);
nodes.put(to, ComputerFile.text(to, file.contents()));
}
return Result.ok();
}
@@ -141,7 +162,7 @@ public final class ComputerFileSystem {
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());
if (file.type() == ComputerFile.Type.TEXT || file.type() == ComputerFile.Type.IMAGE) stored.putString(CONTENTS_TAG, file.contents());
files.add(stored);
}
tag.put(FILES_TAG, files);
@@ -157,10 +178,10 @@ public final class ComputerFileSystem {
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)) {
} else if ("TEXT".equals(type) || "IMAGE".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));
nodes.put(path, "IMAGE".equals(type) ? ComputerFile.image(path, contents) : ComputerFile.text(path, contents));
totalBytes += contents.length();
}
}
@@ -192,7 +213,8 @@ public final class ComputerFileSystem {
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 static ComputerFile text(String path, String contents) { return path.endsWith(".antpaint") ? image(path, contents) : new ComputerFile(path, Type.TEXT, contents); }
public static ComputerFile image(String path, String contents) { return new ComputerFile(path, Type.IMAGE, contents); }
public enum Type { DIRECTORY, TEXT, IMAGE }
}
}
@@ -0,0 +1,80 @@
package com.craisinlord.antarchy.content.computer.blockle;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.resources.ResourceLocation;
import java.util.List;
public final class BlockleAnswers {
public static final int VERSION = 1;
private static final int SALT = 0x4A17;
private static final List<Answer> ANSWERS = List.of(
new Answer("anvil", id("anvil")),
new Answer("apple", id("apple")),
new Answer("arrow", id("arrow")),
new Answer("bread", id("bread")),
new Answer("brick", id("brick")),
new Answer("chain", id("chain")),
new Answer("chest", id("chest")),
new Answer("clock", id("clock")),
new Answer("cocoa", id("cocoa")),
new Answer("flint", id("flint")),
new Answer("glass", id("glass")),
new Answer("melon", id("melon")),
new Answer("paper", id("paper")),
new Answer("poppy", id("poppy")),
new Answer("sculk", id("sculk")),
new Answer("stone", id("stone")),
new Answer("stick", id("stick")),
new Answer("sugar", id("sugar")),
new Answer("torch", id("torch")),
new Answer("vault", id("vault")),
new Answer("wheat", id("wheat")),
new Answer("allay", id("allay_spawn_egg")),
new Answer("blaze", id("blaze_spawn_egg")),
new Answer("camel", id("camel_spawn_egg")),
new Answer("drown", id("drowned_spawn_egg")),
new Answer("ghast", id("ghast_spawn_egg")),
new Answer("horse", id("horse_spawn_egg")),
new Answer("llama", id("llama_spawn_egg")),
new Answer("panda", id("panda_spawn_egg")),
new Answer("sheep", id("sheep_spawn_egg")),
new Answer("slime", id("slime_spawn_egg")),
new Answer("squid", id("squid_spawn_egg")),
new Answer("stray", id("stray_spawn_egg")),
new Answer("witch", id("witch_spawn_egg")),
new Answer("lever", id("lever")),
new Answer("sword", id("iron_sword")),
new Answer("cheep", antarchy("cheep_spawn_egg")),
new Answer("jerry", antarchy("jerry_spawn_egg")),
new Answer("lucid", antarchy("lucid_spawn_egg")),
new Answer("queen", antarchy("queen_spawn_egg")),
new Answer("ichor", antarchy("ichor")),
new Answer("lotus", antarchy("lotus")),
new Answer("mucus", antarchy("mucus")),
new Answer("peach", antarchy("peach"))
);
private BlockleAnswers() {
}
public static Answer answerForDay(long day) {
int index = (int) Math.floorMod(day + SALT, ANSWERS.size());
return ANSWERS.get(index);
}
public static int size() {
return ANSWERS.size();
}
public record Answer(String word, ResourceLocation itemId) {
}
private static ResourceLocation id(String path) {
return ResourceLocation.withDefaultNamespace(path);
}
private static ResourceLocation antarchy(String path) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, path);
}
}
@@ -0,0 +1,38 @@
package com.craisinlord.antarchy.content.computer.blockle;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
public final class BlockleDictionary {
private static final String[] CHUNKS = {
"rossa,jetty,wizzo,cuppa,cohoe,gurks,squad,beisa,shrug,fossa,fluyt,camus,speed,mamil,array,polio,barns,panes,souts,limas,fetch,queck,twink,graze,crock,almud,oohed,colog,wisht,beard,samel,ahind,brung,barca,mahal,jambe,plush,bruja,howre,middy,kabab,zeals,mazel,sputa,goory,pails,scogs,snool,poboy,doest,orixa,quipo,burbs,goads,blaud,flocs,harim,mamey,primp,lathy,lunes,staps,salad,agony,gusli,damps,tocks,hoick,marry,amply,glisk,pulis,apter,shell,capas,saunf,kutis,teggs,halsh,gamma,tardy,tease,towts,abuts,allee,chins,rolly,situs,bards,phyma,torrs,chace,bikky,stoor,quink,wedgy,vises,swore,upsee,gyves,heapy,gilas,gairs,yonic,wagon,rance,swail,laxed,panne,tabus,anomy,swarm,sword,pharm,penes,snoek,razet,nahal,kapus,lokey,gobos,jails,afars,nerka,cundy,snark,intra,bundh,tanga,baaed,solus,senor,umami,wared,sharp,pewed,tuktu,knowd,dawds,flurr,inert,taxus,ruana,sulci,jaded,othyl,redig,manky,ovism,matlo,cease,perae,sixes,pubco,yappy,famed,hexed,bawrs,decry,iddah,dixit,cased,rupia,toidy,hodad,yukes,gaffe,annoy,leugh,howay,pixel,linga,bayou,lurgy,lears,scrat,prize,homes,scroo,egest,mopus,verbs,maple,barfs,judge,salle,shine,zineb,flexo,gismo,rears,khads,begum,glade,imams,strad,venae,skene,moten,rugby,goels,khazi,belit,smolt,morin,ofays,sural,janny,short,swish,haets,owned,musky,gimps,nodes,lefty,furca,exome,dribs,gouks,smews,munts,chiro,learn,mocus,slime,mokis,nevis,arcus,prigs,rente,yfere,hiker,crame,irked,naira,stire,nagas,peisy,miser,vivda,dolie,weave,umbel,barmy,louns,didos,viold,fowth,guqin,kayos,finca,taths,eliad,dorts,cooey,dicty,bobak,lowse,tuile,laked,tarts,reefy,agama,thing,shore,nicks,nunny,akoya,snaws,writs,doubt,alive,beaut,ibrik,hemps,towed,crout,mogul,halid,pecky,eched,musit,couth,acron,dynes,gothy,bundy,seeps,deeps,plene,kukus,petre,putto,darls,warps,shoji,emoji,hiked,omits,rainy,fills,zines,bores,algor,oliva,midge,vizor,hafta,mulla,paled,kneed,fibre,ptish,redan,exode,zorro,droll,niece,segno,sceat,tunes,mompe,radon,akkas,jowly,gandy,medii,knelt,evoke,lowts,teaks,bucko,groks,leafy,meses,synes,laths,zedas,blank,japed,voled,toxic,reach,nazzy,pubsy,pedro,cucks,baith,magas,gompa,bilby,popes,hents,auras,giddy,tacos,onned,bowed,tsars,slogs,grind,morel,asada,weeke,winna,amman,sabji,runts,slops,tread,loofa,fever,glove,trist,creps,pheon,beres,corso,sofar,reeze,pisos,lames,treck,boabs,apply,grata,daals,soyas,amour,gerne,dudes,nifle,again,slurp,ulnar,ettin,noles,gobar,bohos,kawas,radgy,coati,scail,helot,motel,tides,coven,jasey,flown,urubu,kiddo,nrtya,pinna,curbs,botas,cycle,yugas,ugali,opter,laver,ousts,ootid,easel,lewis,renos,paned,cunit,muils,jehus,ebike,cotch,upper,theor,unais,idiot,manes,sawer,gazoo,edged,pikey,meech,adyts,boite,ixias,alios,padle,lotah,ragas,pudus,meare,graip,quack,noise,livre,pheme,chest,lined,azlon,waves,achoo,valve,mawks,hydro,muxed,etude,smith,mends,pisco,cilia,algos,nenes,karma,neant,visie,volar,burro,doeth,ecrus,dekes,seepy,facts,pours,gambo,mensa,swept,oafos,shock,roams,potts,piker,tings,fount,daunt,battu,licit,cider,fords,palms,wheep,copes,whelm,usnea,staid,dosai,patka,gamas,drome,licks,aldea,snowy,quods,waker,syphs,stumm,chore,silts,topee,zeins,likin,topic,waved,worse,anear,rayah,clipe,teloi,thesp,dusts,pooch,lunts,vinny,purda,zonda,widen,jonty,mosed,hussy,escar,appay,tonic,tavah,budos,cures,retia,maile,undee,quois,vangs,kytes,rends,taiga,abets,licht,potae,funks,repas,sicht,mobes,ceorl,torte,snarl,might,genas,washy,etats,boink,perog,coden,crown,moity,helos,murky,gaitt,dance,grail,deads,mivvy,fugio,ursid,burin,mimsy,lotus,frons,durst,devis,parae,mands,sarin,ethic,whirs,bribe,rorty,nymph,billy,gighe,kalif,breds,cutey,mincy,modoc,allin,yeses,dwalm,balms,yucca,cyder,loped,kagus,smeek,ylems,peags,comps,nobly,cocoa,pedon,hithe,etrog,dumps,magic,bendy,seely,spawn,oxids,hakus,grads,abacs,focus,hides,hayed,stony,slims,tonka,ident,tansu,whirr,glike,votes,sigri,cades,layed,snail,fykes,ousia,nevus,wings,toile,junta,chair,regia,lavvy,fatty,bippy,hoody,bunco,zupan,spend,phubs,sluse,dwine,piety,pyrus,mogue,glows,kutch,ngana,piton,spalt,qipao,pakka,redly,furzy,arhat,fumer,addin,cardy,zooea,clegs,dhals,deshi,meany,lenos,cines,sneap,busti,belts,weans,globe,decad,tinks,bally,yokes,onsen,lover,iddat,terry,pogey,planh,lotte,clies,femes,deedy,acnes,rubli,lolos,rapes,manse,wiped,sabha,gamer,afear,adraw,outta,slaps,tarka,goyle,tomia,misdo,sayee,popsy,watch,riped,baths,tails,dinna,conin,bunya,skool,khors,hulks,lacet,lazes,seams,slyly,joins,doats,proke,kingy,ranny,raker,swoun,gular,sedum,opera,perns,redia,tekke,appel,waist,bolds,steer,podgy,humfs,stagy,opgaf,skeos,swerf,rhyta,pians,ulans,diver,penny,nanua,onlap,loyal,bohea,lirks,senti,pleon,daman,syned,mools,baked,rungs,brace,melba,sotto,dealt,bikes,fecit,flyer,sidhe,rengs,alkyd,veney,strag,apode,aigas,torts,sooth,stean,algin,gummy,nduja,ratan,durum,acies,unios,punts,filos,blype,molal,tiara,ratal,lowen,dawts,iddut,indew,surfs,brims,abaci,troke,mimed,rosha,elate,tilak,cycas,ornis,jives,mercy,dimes,cries,affix,ought,ansas,besti,romer,gisms,which,logon,weels,leftLine truncated
"puttu,anlas,torch,homed,serow,blogs,agist,tanka,prima,vobla,flosh,tamas,raver,vegas,roves,ommel,godet,sujee,cerne,halon,axils,eaten,dorms,chiva,sonne,kings,royst,album,groof,buddy,dinic,erhus,quoit,vitta,coxib,tithi,bemad,gouts,chiru,wodgy,gucks,ricin,liman,owner,spoil,trews,gawds,ownio,grams,vaxes,trigs,leets,queme,laxly,dhoni,sider,tabls,spuer,cored,merit,whews,treif,elfed,corgi,newsy,idyls,prays,wight,garri,chado,nonno,glazy,tenge,paves,plink,naffs,nonas,pipid,maiks,smees,sudds,plump,cered,moons,tarse,bewet,abaca,saved,scrab,byded,malic,sadhu,karos,trans,pokie,douce,elegy,conto,obeys,nilas,merry,fixer,escot,poise,soyuz,raise,pashm,rorts,bongo,atuas,asker,azuki,falsy,cubeb,aquas,wolfs,annul,luged,smugs,draft,lited,pichi,weils,grate,whins,sieve,boked,pollo,blunk,esker,eyres,padas,ileac,lawny,cymas,gutsy,bombs,zawns,crier,acers,guppy,lusty,dated,ranke,rusty,omdeh,tamal,remap,wersh,torcs,proas,dholl,enarm,loled,whear,cunei,coset,rafty,javel,fifes,grice,apish,idant,locie,hillo,slaws,cents,plain,volta,gundy,rines,keros,condo,asana,spink,psion,yawns,coyer,thews,carls,odyle,chals,darks,fates,pongs,hoyed,halal,sorgo,icons,bafta,blate,pares,rowel,helps,vakas,thali,llano,wamus,dozes,moble,myxos,yearn,ragga,lilty,unpay,niger,slank,bifid,gaums,laces,bekah,fient,nowds,vitro,shyly,helve,murrs,firry,mairs,lindy,tutti,karee,mobos,labis,pluke,insee,basin,winch,about,tizes,genes,probe,umiac,spaer,moots,sewen,grued,shist,perks,tolus,afire,pomas,mapou,gulag,ariki,jupes,false,nelly,gites,burry,sends,canes,bever,knawe,wonga,limen,pungo,staig,twerk,thumb,resid,copay,strew,oomph,saggy,kaids,ruers,naves,virls,sever,pupus,ettle,tipup,ollas,wally,bully,swang,doser,urent,pocan,spile,occur,risus,makaf,sabot,yamun,matai,durzi,vouge,macka,grrrl,avels,sorts,glide,menes,crane,azine,nosed,xoana,uteri,obeli,trots,simba,wirer,party,potai,reges,gombo,grume,hawed,wagyu,upbow,mares,nused,synod,hemic,twerp,tryma,teeny,uveal,crept,hyphy,perst,lords,began,thrum,nocks,ornee,krais,iglus,sloes,apols,cyclo,backy,richt,moist,droid,stand,bonie,fetus,teras,blare,napoo,diced,malts,shend,boars,charm,mayos,suets,antsy,breve,pinas,sprug,recit,halts,dater,mogos,afoam,ploce,inula,setae,raddy,lawns,genip,xrays,raked,maxim,reeaf,swale,wills,midis,scire,stonn,kinks,zeros,vatas,dogie,serry,pepsi,viand,acene,aloft,beals,kaneh,yirks,piled,bajra,lesos,renin,bonus,wauff,crink,lyses,knack,recon,canns,devel,spayd,throb,gaper,arise,moder,bluid,berko,agile,kamas,assai,slits,matte,spads,stool,ashet,uraei,pulls,firms,moule,rober,bydes,modii,ealed,tache,bebop,swapt,guava,curly,solon,argue,covet,skoal,mosso,haded,shorl,doups,hives,bulls,truce,katal,thugs,arles,drown,skags,babel,revet,novel,alays,codas,rache,punji,douks,potsy,diols,amnic,evict,bauds,hosta,dowie,wizen,micks,showy,zoeae,toxin,noded,bleat,kiaat,toyed,azurn,pupae,grees,oopak,yahoo,wists,ovoli,pines,foams,neves,puris,serve,lovie,ysame,womas,relos,sepia,trade,butes,micra,weise,recut,riser,mbari,dower,sakai,octas,danny,divot,cooly,scapi,salsa,exuls,scudi,leges,visit,farle,crest,faced,bukes,mouth,basse,basts,alkin,mausy,forks,dosha,reely,junks,cubed,plots,drent,curfs,goban,snide,taros,funts,niffy,vairs,noggy,toges,lever,egers,rokos,inrun,platy,antre,sabal,cocco,pouce,fugis,ranes,obang,thrip,thoft,baste,stats,momes,ocote,gloop,admin,oozle,slips,boord,hilus,smarm,aloha,natak,knubs,avert,dowak,bruck,bison,onkus,libor,gives,hardy,greet,craft,narra,tetri,ample,awave,thane,hales,acara,braks,mowra,lassy,kerma,shlep,words,raths,paans,aftos,along,fresh,negro,nuked,gated,manis,exons,poeps,joual,milko,azide,poake,mises,culet,pized,dykon,crunk,whose,bogus,moved,gyppy,oscar,lisps,danda,misch,ngwee,ryoti,swayl,snoke,awork,sopra,alley,kideo,crema,spaed,mired,idees,cuffs,baned,styre,poire,lacky,akita,clime,curst,wades,savvy,musks,stell,satis,wussy,zings,ernes,cluck,susus,beach,saola,rifte,tabun,crons,infer,sypes,build,unfed,pomps,mohur,hoser,autos,unset,neath,macte,whist,acrid,ecads,mamba,chocs,smush,cytes,thanx,rings,vinos,woful,awash,bayer,boody,alans,birzz,campy,retch,motus,taish,scots,panim,ethyl,knurs,coots,dacks,munge,ungag,eland,equip,elite,unlay,nonyl,rouky,varve,milts,soare,gecko,filly,jills,sakti,scrob,trope,seral,atocs,sarvo,nonic,masse,picra,dhaba,sensi,fiber,darns,bouge,rayls,tints,phuts,choil,vivre,osone,kemps,holds,bends,datil,hoten,slums,amrit,gouge,pikis,eensy,clans,dough,scads,blame,ghyll,bliss,tumps,budes,senza,gyres,verte,fluff,dhaks,huzzy,omber,piony,leams,amove,unbar,porus,ooaas,janty,leear,trull,droil,nitta,savey,qaids,caphs,brawn,wokka,mumph,ratas,hexer,lucid,oobit,pleat,holks,youks,dashi,leaps,kills,skein,shoat,hurra,juves,strow,shalt,lollo,duxes,chias,tined,plead,pricy,vanes,hazes,remen,rushy,abyss,vares,gally,dizen,wedge,kanga,haham,nuque,spurn,until,laree,midst,naker,mened,karsy,masks,pucer,goors,knoop,ticky,sargo,split,skeen,stood,togae,slush,prise,eales,eater,glaur,prexy,raffs,wekas,snout,court,ileus,palsy,sevir,rynds,ducal,ovels,juice,plets,woofy,panty,talak,vinas,bapus,weber,olein,gamed,tolaLine truncated
"built,mawrs,lagar,marid,grebo,spare,pieta,noxal,abase,kinky,priss,hacek,vicar,marka,temse,serir,blaze,ngaka,heeds,belar,qophs,noted,brews,hafts,ratti,firma,carbo,wrawl,adrip,mvuli,voici,fetor,recks,perne,dowls,mutic,fetts,wetly,exine,kauru,bubby,stope,haram,herry,dacha,saice,theow,awing,frugs,swoln,odium,parti,gaily,dwelt,money,laned,murry,skits,rowme,tatou,frere,ukase,dolci,rhomb,tynes,corer,nerdy,peats,fondu,okole,close,afoul,swole,scena,talaq,paces,senas,faiks,pneus,rubai,jibba,would,patly,fused,crena,doley,mesca,eches,serai,tinge,saran,glout,solei,tinny,noter,upful,venue,natch,panic,curia,yomps,diode,binks,stear,bring,pekau,peace,humps,ixnay,maunt,balot,cosec,purao,armed,liars,hifis,docks,wuxia,phooo,russe,gutty,urena,aidas,vials,posse,chaos,admen,pilum,gunks,wowed,inurn,trugo,skulk,tutee,wrier,pyxes,sonce,tsubo,floss,algid,allen,nolos,stoss,weamb,aspro,mucho,snebs,talcs,pekes,waxen,marls,clavi,titin,swing,uncut,entia,hwyls,vanga,sayne,based,knurr,raiah,pings,vegan,mound,menta,plowt,gaids,pipal,deets,caaed,sayed,hates,debes,masts,resus,unarm,chays,rowns,babus,lapse,alter,bruin,furol,loord,swits,majoe,faves,albas,lysis,shark,peach,vades,owlet,brods,lyams,rater,moves,mynas,arris,hiems,quags,roans,reamy,tanti,heady,pirns,casts,dress,enews,amido,kinin,adits,puffa,pucks,shuts,mural,mered,whens,gowls,darer,helix,mirly,womyn,raggs,wilja,monie,neram,nyams,slams,dirke,tehrs,skens,tavas,table,horal,oupas,navar,hippy,rawly,diets,surgy,lysol,seths,panny,arums,mulls,mazut,drony,virga,ratha,lunet,rowet,dancy,defat,splat,roger,nucha,plout,mungs,sloan,usque,sakia,guido,hissy,jigot,tyran,drapy,tight,puses,droop,pepla,deter,gleby,primo,haves,pinda,rieve,zabra,lieus,parky,fanum,yoyos,styte,faros,heald,gases,samas,scapa,teary,ginge,lossy,embed,genal,burps,fitly,mythy,purge,bauks,kwink,bawty,ferox,bazar,almah,hains,peaky,plops,noris,brink,kirns,sordo,boomy,dufus,nards,galea,corps,speil,deary,flyby,kerky,unlid,drums,undos,cools,conge,dodos,pavis,patas,pands,tipis,puree,statu,rotte,beads,evert,teals,vined,hamba,gopik,crans,wiled,weedy,pacha,parge,perdu,vaunt,pequi,piccy,leany,marly,whiff,neive,homie,rests,aware,riads,somas,lytta,dorps,idyll,mulsh,pence,vatus,zoril,semes,argot,drouk,linac,cloke,nimbi,anger,mojos,bozos,choof,avale,viewy,mazas,canon,abune,south,slobs,moler,ships,souls,boded,waspy,nowls,trice,nisse,teats,caned,biner,tronc,cutty,truss,diyas,merse,waffs,koala,agate,ezine,nudes,rigmo,evhoe,slick,wadas,olate,oleic,woons,typey,oncer,gumma,sweet,heavy,psych,khyal,cates,brens,socle,katis,frisk,withy,sauba,heyed,rosti,feuds,niced,kyles,rawns,gadje,jabot,crise,tacet,lines,esses,grope,swash,ngati,wised,garda,locks,gelid,horst,attas,sycon,telia,algal,frais,fango,yetis,marcs,addio,anils,bunce,wirra,gnarr,ludes,glary,coxed,beaky,ylids,added,patta,veles,curie,unces,ledum,sansa,adust,aurei,spitz,logie,unget,thema,yowls,coifs,surge,softa,gyoza,putas,blaes,blaws,celly,butte,stamp,grain,bursa,ligge,sagos,sheal,atrip,emeer,degus,stush,kynde,cured,pfftt,mills,minxy,tykes,atmos,suite,ramis,merks,coyau,twats,bussu,mooth,terfs,fests,enmew,nould,hoosh,nylon,alefs,pikas,zoons,busks,qapik,remou,cawed,pally,muntu,teugh,slimy,bassi,almes,outgo,batta,ranks,lanks,roguy,zippy,foids,welks,rowan,psias,yowes,allow,rants,faker,dells,rumbo,titir,combi,winds,rubby,deify,quena,crape,jokey,wootz,wites,koses,nizam,pratt,pukka,fixes,bobby,ruffs,magot,argal,ammas,scoog,aweel,gilia,payer,touns,rupee,marge,colle,pates,doles,rappe,lakhs,carve,grana,wytes,macks,nerve,mitry,crips,bewig,melic,andic,bands,mugil,quoin,pools,omiai,drips,eared,yippy,kexes,carol,lores,bises,obias,thurl,tajes,emmew,chiko,orbit,jimmy,vexer,begob,askos,liwaa,burgs,demob,imbed,lahal,chats,laves,cohos,jowar,nurdy,iodic,whoso,broth,soops,tried,crins,jaune,coarb,cocas,mugho,smoko,scaup,feart,skets,hebes,doums,korat,briki,soars,oaths,abeys,brome,appro,tarot,lymes,quich,amend,doers,duded,hushy,grogs,cleik,audit,kutai,skyre,milky,peens,cooze,rusks,meiko,boost,booze,jeans,neeps,kobos,dazes,bayes,spims,pegma,maddy,bwazi,xylic,agaty,rawdy,soree,dinge,assam,stoic,nyaff,rowed,ledes,polls,olive,strim,taras,piner,steno,seems,notum,cripe,stoai,nsima,buyer,gluey,poohs,hovea,jocky,kinas,fangs,yawed,tungs,ephas,povos,scram,camos,ympes,spacy,nucin,looed,ambit,stile,palay,roche,rices,diota,gatch,vasty,otium,flory,crues,petri,sycee,nippy,ludic,sayid,welke,links,uptie,clone,ploat,tiyns,humic,atopy,basan,boney,drill,oaten,mucro,seals,cavil,rimer,odyls,naked,maqam,kreng,sards,simis,geoid,butoh,aboma,oyers,sheas,laich,imine,ficus,ducky,mobey,eaves,warez,hiper,laund,punim,jouks,route,roods,wushu,mokos,ovens,ebons,paths,eidos,glute,abies,tiles,unsew,bidon,teams,muffs,reave,vigil,dutch,japes,spear,dules,caneh,lakin,arnut,fados,tonus,flied,serge,rives,scorn,orbat,aline,genre,hiant,quorl,cream,pebas,coala,prodd,lamps,wroth,shill,penal,spine,citee,milds,choir,bilat,betty,culpa,dilly,perfs,malik,kondo,surds,slice,ablet,omasa,hanch,vilde,fayne,tense,stare,disas,dowds,braza,dooks,verts,intro,mirah,karks,noneLine truncated
"pahus,goldy,unbox,dowts,copse,snuck,haars,bello,nebek,slipt,tokes,hewgh,alose,bucks,paips,sewar,mieux,madal,ahint,plish,nudgy,tuque,inane,galas,neeld,urdee,amyls,keyed,laccy,slide,adoon,kaugh,naice,vamps,twier,glebe,borms,krunk,buaze,imari,sepad,vertu,giant,repos,courd,resin,flubs,unbid,felty,decaf,razoo,hibas,pouts,kliks,eyras,fogey,aleem,papey,fiers,motet,tulip,steek,scaff,queen,tango,reput,rorie,monas,ponks,deros,venal,piped,alaps,fitts,besee,fauld,moley,apods,circs,plook,asyla,waite,wound,laddu,tices,bleys,tuffe,kains,kunds,seeds,alala,mawns,neddy,mengs,ewhow,doucs,tools,wacks,kypes,bunjy,braid,spifs,wyled,choli,quint,panto,clown,cokey,syver,coths,yokul,vogue,olden,vardo,vibed,cires,fonds,adaws,amuse,sisal,phone,kwaai,bruhs,runty,wonks,psoas,alowe,knows,azido,raike,sidey,sades,tared,culch,poted,afoot,kerel,kakis,devos,metol,avize,saids,kilts,punas,copal,prial,angry,sooty,slane,quest,goopy,plore,dubby,himbo,dogal,appal,dorrs,cando,rewan,kagos,cloop,adays,gulet,porte,clept,haiks,miyas,yeard,mifty,oater,liner,manos,rower,shura,quirl,jafas,clack,pikel,menad,fluky,reans,enate,mates,abray,thans,kuyas,songy,incog,gauds,hokum,dogma,blend,areca,dirge,sarge,ryijy,latus,gambe,shack,renal,reuse,slish,pagan,stirp,kylie,calif,hands,kirks,docus,uveas,shaps,gonzo,mummy,thawt,actin,hoard,howls,bundt,blush,supes,crogs,seron,solve,chert,totty,aborn,vinho,agree,snush,danks,proud,racon,shaly,paged,pulse,shank,onset,spain,regex,ureas,orlop,mushy,cronk,latch,blive,honds,gamay,heath,arnis,purau,tofus,hinds,mewed,cowks,birls,goyim,stend,alant,nolle,fulth,tasar,luvvy,dives,lunge,snick,derth,cosey,disci,uncos,yukos,tansy,metal,barde,saucy,smear,frore,kneel,grama,crios,chedi,endue,bafts,ralli,smore,tenor,pawns,lodge,resol,caron,tiare,poove,undam,kohas,vapes,river,caffs,blahs,vapor,brach,retax,molls,beans,kempy,ceric,scody,ditty,porth,tarte,faqir,jagra,ogive,bezel,malae,tapis,acryl,dully,trave,clads,rhime,skirr,snoot,prowk,satai,embus,colza,affly,dashy,bugan,wahay,shent,hodja,valli,dauby,oracy,beano,corms,hukou,corni,laxes,noisy,shtum,canny,outer,flaff,fuels,sieur,rehab,gleet,disco,mammy,aevum,hiped,moors,agley,ebene,coady,rerun,tynde,fetwa,garba,troys,kapai,calix,dites,ments,praus,loofs,troth,abore,rimae,early,carrs,salet,miens,joeys,quirt,embar,coked,leirs,cozey,flask,benis,guise,patsy,kirri,brava,ridge,jotas,reaps,varus,twits,freak,chads,plaud,oskin,oiran,manks,adopt,triol,logan,decos,glams,sadhe,cirls,theme,siver,quats,mbube,drole,mhorr,garbs,quyte,kells,mitch,adage,ozeki,vrils,mohos,leeps,skivy,plaps,clied,saith,saiga,swain,klutz,varan,ahing,louse,clink,peery,burke,scold,kunas,forze,syboe,pagod,wicca,infos,guimp,kails,evils,hosed,vaned,charr,muras,matzo,draff,range,buffo,alist,epoch,roral,yucko,crack,filet,pouff,biggy,festa,moyle,ramee,bajan,margs,gored,copha,dangs,bardo,micas,hiree,vegie,beers,aalii,yoghs,navel,heare,fease,jorum,exeme,upped,nepit,cover,smack,poney,preif,cauls,gecks,burka,dates,bonce,ruffy,layer,arbah,mirkn,train,enact,fremd,drave,feare,erect,sweal,pring,batts,thees,nudge,antas,doved,pelsh,pigmy,muiry,flora,bonne,oping,nauch,pawaw,golly,muirs,minae,agloo,faked,jamon,bowne,souly,funsy,claro,whids,hubba,flong,snath,howes,lomas,quass,aboil,nabis,shako,shoos,pooks,huger,totes,glial,atigi,mirth,loans,fagin,phpht,china,toter,fleek,bocce,henge,sunis,nenta,booky,miles,steez,pions,rapin,wolve,popup,unode,gymps,stane,naids,pleas,orgia,dames,abjad,peece,lurgi,frill,gitch,feued,lowps,stong,price,holed,eaved,humas,dered,swags,imide,hydel,annus,trons,moppy,rello,bents,bubal,dinlo,kiwis,urine,namer,feens,idler,lanes,scrog,mased,hider,ruffe,fakey,zaris,perts,satyr,suped,ganch,tibia,ceded,rabbi,stuns,stoop,abysm,aulic,carpe,moste,signs,steak,mixes,panir,grids,saddy,chode,viola,gigue,xerox,mirid,manor,strut,curet,bonze,winks,morts,geals,amlas,stubs,kembo,subah,daled,hells,apres,lifer,jhils,kerry,gauss,dayal,films,kophs,suede,unlap,valid,wayed,mechs,picot,halva,funic,zexes,pitas,nondo,waddy,lobar,rowth,threw,efits,nosir,iller,dobes,kabar,toffs,rehem,reign,lenti,hucks,knock,lovey,lants,skies,grouf,smuts,rifts,mauve,retro,tikia,chomp,nazar,clonk,reget,ovoid,sechs,mukim,kyars,amaze,valor,woald,fisks,giber,cames,round,leese,pubic,barbs,issue,razor,myths,kranz,baaps,poxed,vadas,jowls,twill,frita,naiad,ureal,gusla,lipid,sojas,nempt,huers,puler,whoop,obied,litho,debus,panax,befog,kilig,nites,carex,logic,swage,frati,edict,dalle,iliad,boots,scour,bacon,tares,sexer,masha,monal,pappi,flume,abord,chivy,parol,neons,mucko,event,yapon,fusks,sownd,frows,paxes,whaur,skean,owing,gilet,coops,abbed,onlay,akees,zouks,biers,mooni,kafir,lysin,xviii,rusas,reeds,yohay,salon,hones,urupa,smirs,shirr,meter,agrin,paved,lipas,ensky,trust,gippy,enjoy,appuy,fitna,moues,waxes,koiwi,lungi,bunch,goier,frape,keirs,hertz,nairu,promo,sunup,refis,scudo,olent,bromo,soole,conch,tepid,menge,aught,yecch,burns,bogie,carpi,arete,braky,naled,boned,talon,gonia,deray,ceres,inker,ranga,panga,reune,slomo,trema,ycled,heder,narco,quake,mixie,belle,genro,hurty,smeke,gossLine truncated
"ooids,gully,ramen,suber,sutra,preak,drere,bowel,hedgy,deles,refer,tofts,milty,frosh,manas,octan,kerfs,enorm,rebel,guaco,nisei,pelma,nasis,goeth,oared,segar,aflow,skims,wited,sough,hokku,pardy,teems,michi,decks,twonk,fales,ideas,dirls,snive,clows,leses,wakas,wahoo,memic,eniac,sexor,kimbo,oases,derms,pulka,sutor,botch,unaus,vughs,resto,kimet,malwa,rioty,enemy,ruder,ahent,yoker,shmoe,grame,lytic,potty,odism,acini,mules,soler,antae,lound,nicky,brags,doeks,ulnas,dagga,cadre,coypu,cotts,galis,lowes,bezzy,skran,iliac,pases,halve,flesh,runer,wefts,rebbe,isnae,siroc,gules,clomb,amaut,newts,ladle,gibes,taggy,haply,kalpa,bilks,blown,yomim,attap,educt,ingan,linos,gooey,fibro,maise,ngoma,adzed,flawy,toran,jades,offal,twilt,wager,booby,koban,yules,irone,azygy,tryke,sweys,neist,nunks,rogue,riato,mulai,obeah,emule,mocos,girns,sorta,cooky,causa,ayelp,mitta,emote,pelau,thang,appui,momus,asity,taunt,damme,ulzie,suids,cymes,muzzy,anata,spyre,roate,boppy,kelep,pilaf,caver,pheer,derat,peeps,tania,murva,surer,yatra,kapur,udons,coate,thelf,robbo,chynd,noups,brize,texes,bosky,aargh,alert,shews,lirot,lofty,jolly,palea,geode,suety,ponga,fores,voila,beech,cumin,polyp,murid,laufs,alarm,tyros,ragee,berry,ytost,welts,tinct,cacks,snuff,ecigs,plein,reded,dived,kests,mucky,grays,berth,adore,raksi,whift,pirks,roven,syncs,nomad,medal,cerge,gruff,macho,rooty,yabby,linty,delft,dawah,resow,blink,salty,sowff,lweis,tufts,gipon,veale,tigon,jumps,crick,pacos,porae,doter,rewax,doofs,sarus,vales,douse,cheka,gores,crusy,weals,pause,cours,kakas,nixer,scran,cumec,metes,wifie,eleet,heart,coste,duped,aloin,drags,nouny,vacas,riffs,locos,botty,catch,burnt,belli,swack,thole,stimy,holos,tapet,recti,fasts,musty,keech,moult,hella,tends,rugas,reede,spyal,blitz,octad,fiery,using,robot,unsaw,lipes,methy,gived,faces,touse,bikie,stulm,palla,toled,whies,herls,write,kanat,perch,shirk,roofy,skeps,beamy,rully,swops,hauns,horse,giust,minis,sized,zizit,crump,hulas,haika,verve,mooch,velum,techy,fouds,manty,mesal,alkie,roons,capos,grump,nooit,afanc,packs,mores,ambry,rugal,parly,alifs,legge,wawls,nadir,tenny,erned,solde,yerks,eagly,abeer,reply,gybes,cabre,vifda,vivid,gyver,psoai,pacas,piums,emits,touts,jotun,regal,sheen,tesla,unmix,grace,peasy,sohur,ganja,pylon,sente,gumbo,gruel,tanty,edile,rhumb,tilde,slyer,lasts,carry,kaifs,leche,bunks,moats,woose,mists,nihil,buffy,cabby,sarod,quoys,sered,livid,attar,craig,chave,macro,blech,steik,umiaq,donee,yonks,nopal,tells,gluts,bdays,flics,metas,eagre,spoon,hogos,pinol,fezzy,doody,alaap,rugae,upsey,chyme,raile,urbia,booth,manga,preen,hilum,luffs,baits,raven,preux,gauze,galed,pungi,hedge,heirs,reive,indue,wyted,torsk,fuffy,filmy,carks,neosa,lownd,quaky,lofts,hemin,vodun,pared,kawed,gurly,horas,gelly,foyle,shawm,faine,ulcer,podex,ariot,krays,towny,frags,humid,mints,sifts,merer,inerm,exude,selva,aspis,cinqs,yogic,auris,zests,bazoo,agios,rotal,binal,sella,astir,wacke,owler,saick,kylin,coxae,zokor,vying,ameer,taken,yests,haver,mugga,axiom,primi,pikau,scout,nears,sozin,pooly,roped,sezes,lavas,pilis,tabis,lazar,smote,feyly,japer,inros,griot,alurk,duvet,sepoy,rusts,curse,hahas,kerve,kyaks,longa,degum,dauts,ungum,walds,hollo,forty,howfs,nazis,enlit,gilpy,odeon,wheal,flobs,forby,lumen,opted,neebs,gaged,sulfa,toyon,clous,apart,merde,sukhs,abmho,rheid,bumfs,cogon,larks,parki,rotos,movie,nines,sambo,saint,fungo,snafu,alkos,staun,agros,bawks,takhi,snell,banks,inlet,rumen,shape,pesky,bacco,warst,dukes,typps,jauks,napoh,niton,khula,prese,tranq,gazes,majos,tuism,bacca,lasso,askew,anigh,heaps,sages,brise,kaims,tikes,bulge,gemma,tafia,annat,casks,taxol,nitty,miter,riqqs,cecum,avant,smurs,stray,yesty,boyly,skate,rangy,dosed,edify,gride,burds,agone,coxal,djinn,fadge,abele,skail,apeek,toons,spaes,payor,legit,gapes,takin,cuits,aisle,amici,ceroc,warms,soldi,spaws,naans,dolor,arnas,image,mouly,axels,codec,faffy,slopy,peppy,laari,oligo,prior,lupus,chare,rindy,proto,haick,leash,xysts,mufti,dandy,oxter,fries,hylic,musse,advts,musta,farad,ontal,above,punto,nicad,coirs,knosp,miros,samfu,doggo,oiler,mowie,mirza,fecal,rouet,yikes,jorts,denay,fulla,liart,sojus,ajupa,yumps,limos,gaped,miggs,haint,micos,weids,rorid,logos,mopey,axled,prove,lapje,ramps,oxlip,petal,aphis,fauch,goods,chiao,lazed,opahs,titch,lento,cahow,hutia,mochi,atlas,jolts,prats,omees,nipas,danio,dhoti,vents,allel,loure,erses,dumka,salop,zante,douma,agush,girsh,scyes,apism,ceaze,dicta,miffs,blady,feebs,tyiyn,arear,ruote,chewy,bonks,stogs,fuzil,suhur,oxeye,swamp,proyn,coped,grape,tichy,aarti,rudas,inbye,mopsy,tasca,amiga,gland,beeps,metis,bogey,glatt,nutty,dearn,ranse,aural,sleek,acton,talea,ovine,saist,rivet,leapt,wiper,alums,skimo,skimp,rouse,delay,gazal,appts,blert,cajun,anion,soxes,flips,tipsy,cuspy,trays,amate,yente,ictal,acoel,tumpy,scrim,scums,boing,roils,coffs,shily,tronk,tours,brood,dumas,cokes,dictu,think,teend,haugh,kenaf,lousy,wreak,maqui,radio,pasmo,grens,skint,mneme,liker,bukos,cisco,clang,cycad,micht,desex,naevi,salue,binge,abask,cager,orals,fryer,photo,grins,nurrs,meils,gilly,prorLine truncated
"flair,goofs,laiks,karas,amity,gawsy,moggy,lordy,samba,alone,eatin,muset,poker,trins,grist,paver,istle,plaid,groat,blond,bortz,galop,aside,calpa,augur,limes,yrivd,quail,mongo,pagle,yechs,unwit,aback,pilot,acids,ewked,beray,prian,rypin,folic,plied,okies,gushy,banda,worms,divis,pakki,muxes,cudda,conga,dined,tiffs,moxas,terce,thine,douar,gemel,dreks,pedal,towai,thigs,taits,morat,nappe,novia,wembs,smaze,talik,oncet,pecks,pruny,blays,dwaal,luaus,urger,manna,xenon,spags,biter,bunty,sinky,otary,euked,apays,scala,ostia,nasty,oopsy,dauds,beget,harry,meath,voxel,jelly,hotch,sties,creme,rules,dobra,kloof,unmad,podus,yowsa,aiyah,folia,lowry,acedy,alecs,prowl,prune,elude,xylem,lumme,nisin,ruble,tulps,yopos,duits,lulus,necks,tapir,lezzo,leaze,evegs,bumps,odder,grips,prone,fugal,hullo,morne,owies,lobby,yarns,teade,smirk,yandy,cusks,jures,psyop,rhino,kanji,traik,areic,butle,spina,letup,khets,loess,mason,orpin,ureid,nurls,suona,bingo,namak,bassy,hasps,linky,molar,hoghs,walls,comma,titas,mache,trace,crine,coles,spard,hoked,eisel,pooay,rider,haldi,algas,tempi,absey,ancho,fetas,gojis,cleve,lohan,tasks,sedes,chang,pikul,roins,vatic,reast,catty,rafee,laika,soums,whiss,mayas,peple,kyack,pujah,percs,nexal,lochy,fordo,boyey,crumb,glums,demit,wanle,quate,amins,stuff,booty,spook,shool,sluit,manoa,antar,karat,nalla,chimp,frowy,buoys,kissy,furos,tenia,zloty,blear,kotow,sweed,oinks,midgy,unsub,golpe,lades,graph,minor,agbas,celom,wands,gonif,grime,mbuga,dozen,clunk,firth,kurus,meane,fenny,jaspe,golds,latex,diazo,brash,ahigh,pinot,pioye,swipe,flexi,buats,pamby,salut,humus,oleyl,kohen,deems,padou,colin,lepra,yorga,rokey,onces,weets,caput,pilae,yenta,ancon,belly,ripen,raake,palis,treed,boygs,slart,scuzz,tenno,aggie,kooks,petti,weeks,aided,hypos,imago,ameba,pager,award,scute,cippi,asdic,brins,meson,sunny,pyots,babby,ditts,spaul,patel,ching,loams,styed,nante,slipe,grese,wythe,heles,nummy,ketch,hoast,chump,dixie,idles,vodka,fermi,maize,aimed,azons,kamme,moits,exeem,goral,melch,shuck,taluk,pawls,gajos,ghaut,guile,gleys,jukus,gluon,manly,walty,yiked,corby,zaire,chary,arrah,droog,subby,ragde,ponds,oseys,grass,gurdy,aixes,marma,crimp,gassy,bronc,coopt,glibs,toman,dimps,vaids,liken,blowy,sware,print,yourt,unite,tadah,coria,whamo,teene,gormy,urvas,izzat,gyans,syens,fidos,strap,rebar,chile,rifer,urase,hings,major,rabid,tauon,ontic,garms,bombe,firni,germy,tunny,baboo,tulle,rendu,brule,oidia,dorse,apian,dabba,jumby,zobos,akoja,mated,waney,maaed,frank,flute,dryad,jupon,gyals,rurps,lovat,extol,sheol,wrate,afizz,septs,brede,chemo,vieux,sores,noxas,trock,nerts,misto,local,yourn,klong,missa,sukuk,peine,cabas,vardy,issei,boric,aleph,husky,hoven,aiery,apses,wharf,addas,assay,plast,succi,tales,datum,ulmin,eusol,grief,prong,whaps,vocal,vower,nides,rebus,brows,scath,scabs,rozes,blunt,adbot,myons,rally,eyers,fille,frist,zimbs,aymag,leeks,judas,flirs,prunt,inorb,fyked,dicto,chili,spial,lisks,earth,scone,bemud,tosas,tribe,daynt,cohab,preop,oware,atilt,shrow,glass,pavvy,raved,mindi,ochre,poley,deave,umpie,ranch,clair,fugus,bunny,tarps,doomy,rexes,vroom,lauan,aster,meets,blini,pavan,stull,flimp,mirks,shola,passe,skart,gazar,trefa,teind,widdy,dinos,cline,toked,cubic,touch,steys,lazos,samey,chino,rajah,lycee,popia,shaws,sates,other,duchy,moove,orcas,point,beaus,vigor,scaud,girls,gigas,folly,rumpy,gaols,motis,sauch,salud,pents,vozhd,obbos,hokas,kombi,bokeh,laics,namaz,slain,teets,force,phies,genic,inver,tachi,argus,prick,users,bluff,hecks,bewdy,silds,vivat,knowe,besaw,muons,noils,buiks,krait,green,dooky,yeeds,gings,kalis,chugs,soras,buyin,silks,serer,mercs,arses,limbo,moufs,carta,venin,crudo,stoit,cynic,rhabd,kaphs,arsed,noois,flier,ylkes,firie,thowl,virtu,tules,silva,trest,krill,houri,lezes,infra,honda,ansae,nicol,unkey,stims,trapt,abbot,pelch,greed,fault,jolie,axial,estoc,misls,norma,pleep,moire,kudzu,goose,flamy,gayal,gamba,urped,sybil,goxes,wives,muruk,often,welsh,poste,bisom,walie,tuber,doura,reerd,baons,rubin,relic,towzy,tinea,after,volva,egger,dungy,shone,haugo,zitty,yummo,caups,margo,lanas,tinds,fabbo,blanc,quilt,pekoe,napas,chaft,fique,milly,sigma,shmek,rumly,umbra,ovary,foley,nitid,nyong,ogled,tewed,bytes,litie,auger,cimar,seiza,tamps,bruit,tough,kopek,algae,rioja,deare,plips,rheas,proot,lolog,denet,hames,drubs,saute,swoon,fogle,tidal,deals,haler,aglet,segas,seric,neifs,dynel,oldie,feoff,ustad,hance,pixie,netop,raupo,keets,fusty,biked,buroo,mozes,paolo,phoca,dykey,ksars,rejon,dinks,egmas,nital,kilns,malas,hopak,pacai,kreef,lunch,kilps,losed,etwee,tenne,texta,wench,pects,mazey,spork,hatha,cotes,poncy,while,ozone,bantu,salol,remit,lippy,cocus,urbex,purls,saeta,linch,plock,levis,slant,murti,fades,found,hakam,buses,gogga,fouet,silky,bigha,begem,lease,spire,binds,sampi,pyres,newel,baghs,rudis,niefs,donne,howto,yucky,ummed,domal,essay,nujol,limba,blain,kbars,boets,lotta,diker,aloes,felch,aggry,kutus,amuck,smeik,herns,witch,yaird,pryse,munch,finos,molas,dolee,rubor,zonae,boyed,novas,kheth,fudge,wodge,aflap,guyle,arepa,hault,stobs,faxed,cento,avail,usnic,songLine truncated
};
private static final Set<String> WORDS = load();
private BlockleDictionary() {
}
public static boolean contains(String word) {
return word != null && WORDS.contains(word.toLowerCase(Locale.ROOT));
}
public static int size() {
return WORDS.size();
}
private static Set<String> load() {
Set<String> words = new HashSet<>();
for (String chunk : CHUNKS) {
Collections.addAll(words, chunk.split(","));
}
return Set.copyOf(words);
}
}
@@ -0,0 +1,30 @@
package com.craisinlord.antarchy.content.computer.blockle;
public final class BlockleGame {
private BlockleGame() {
}
public static String evaluate(String answer, String guess) {
if (answer == null || guess == null || answer.length() != 5 || guess.length() != 5) return "XXXXX";
char[] result = {'X', 'X', 'X', 'X', 'X'};
int[] remaining = new int[26];
for (int index = 0; index < 5; index++) {
if (answer.charAt(index) == guess.charAt(index)) {
result[index] = 'G';
} else {
remaining[answer.charAt(index) - 'a']++;
}
}
for (int index = 0; index < 5; index++) {
if (result[index] == 'G') {
continue;
}
int letter = guess.charAt(index) - 'a';
if (letter >= 0 && letter < remaining.length && remaining[letter] > 0) {
result[index] = 'Y';
remaining[letter]--;
}
}
return new String(result);
}
}
@@ -640,7 +640,7 @@ public final class CreativeTabContents {
acceptPotionFamily(output, potionPath);
}
for (String diskId : List.of("antarchy:introduction", "antarchy:chronosphere", "antarchy:toreterror_arena", "antarchy:high_fructose_corn_syrup",
"antarchy:elythia", "antarchy:cavaryn", "antarchy:thoraxis", "antarchy:temporal_fields", "antarchy:bloodglass", "antarchy:vortex")) {
"antarchy:elythia", "antarchy:flying_squirrel", "antarchy:cavaryn", "antarchy:thoraxis", "antarchy:temporal_fields", "antarchy:bloodglass", "antarchy:vortex")) {
ItemStack disk = new ItemStack(AntarchyObjects.FLOPPY_DISK.get());
disk.set(AntarchyObjects.FLOPPY_DISK_COMPONENT.get(), ResourceLocation.parse(diskId));
output.accept(disk);
@@ -32,6 +32,7 @@ import net.minecraft.world.Difficulty;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
@@ -117,7 +118,7 @@ public class DimensionalTearEntity extends Entity implements GeoEntity {
int lifetimeTicks, UUID queenId, int count) {
DimensionalTearEntity tear = create(level, pos, yaw, lifetimeTicks);
tear.queenOwnerId = queenId;
tear.queenManticoreCount = Math.max(1, count);
tear.queenManticoreCount = Math.max(0, count);
QUEEN_TEAR_IDS.computeIfAbsent(queenId, ignored -> new HashSet<>()).add(tear.getUUID());
return tear;
}
@@ -472,6 +473,16 @@ public class DimensionalTearEntity extends Entity implements GeoEntity {
return true;
}
@Override
public boolean hurt(DamageSource source, float amount) {
if (this.queenOwnerId != null && source.getDirectEntity() instanceof Projectile
&& !this.level().isClientSide) {
this.removeLinkedPair();
return true;
}
return false;
}
@Override
public boolean fireImmune() {
return true;
@@ -55,10 +55,8 @@ import com.craisinlord.antarchy.content.entity.royal.decree.ComeNoCloserDecree;
import com.craisinlord.antarchy.content.entity.royal.decree.DoNotRunDecree;
import com.craisinlord.antarchy.content.entity.royal.decree.FightMeCowardDecree;
import com.craisinlord.antarchy.content.entity.royal.decree.HandsOffTheCrownDecree;
import com.craisinlord.antarchy.content.entity.royal.decree.KeepYourDistanceDecree;
import com.craisinlord.antarchy.content.entity.royal.decree.KneelDecree;
import com.craisinlord.antarchy.content.entity.royal.decree.NoRespiteDecree;
import com.craisinlord.antarchy.content.entity.royal.decree.NoRetreatDecree;
import com.craisinlord.antarchy.content.entity.royal.decree.RoyalDecree;
import com.craisinlord.antarchy.content.entity.royal.decree.ShowNoMercyDecree;
import com.craisinlord.antarchy.content.entity.royal.decree.SkyIsMineDecree;
@@ -89,8 +87,6 @@ public class KingEntity extends RoyalBossEntity {
private static final int BEHAVIOR_SCORE_CAP = 12;
/** Keep the tree-bound patrol safely inside the authored tree footprint. */
private static final double TREE_ORBIT_RADIUS = 128.0D;
private static final double TREE_RETURN_RADIUS = 192.0D;
private static final double TREE_RETURN_RELEASE_RADIUS = 144.0D;
private static final double TREE_ORBIT_ANGLE_STEP = 0.014D;
private static final int TREE_ORBIT_PATH_SAMPLES = 16;
private static final double KING_FOLLOW_RANGE = 192.0D;
@@ -108,7 +104,6 @@ public class KingEntity extends RoyalBossEntity {
private double treePatrolMaximumY;
private double treePatrolAngle;
private boolean treePatrolBound;
private boolean returningToTree;
private int patrolCooldownTicks;
private int decreeCooldownTicks;
private int activeDecreeTicks;
@@ -118,6 +113,7 @@ public class KingEntity extends RoyalBossEntity {
private int iceballCooldownTicks = 120;
private int iceSpikeCooldownTicks = 160;
private boolean decreeRetreatPressure;
private int royalBoundaryGraceTicks;
@Nullable
private ServerPlayer exileTarget;
private int exileTicks;
@@ -151,7 +147,7 @@ public class KingEntity extends RoyalBossEntity {
}
public static AttributeSupplier.Builder createAttributes() {
return createBaseAttributes(AntarchySettings.kingHealth(), AntarchySettings.kingAttackDamage())
return createBaseAttributes(AntarchySettings.kingHealth(), AntarchySettings.kingAttackDamage(), AntarchySettings.kingArmor())
.add(net.minecraft.world.entity.ai.attributes.Attributes.FOLLOW_RANGE, KING_FOLLOW_RANGE);
}
@@ -373,14 +369,11 @@ public class KingEntity extends RoyalBossEntity {
@Override
public void tick() {
if (!this.level().isClientSide && this.shouldReturnToTree()) {
this.returningToTree = true;
this.setTarget(null);
}
super.tick();
if (this.level().isClientSide) {
return;
}
this.tickRoyalBoundary((ServerLevel) this.level());
this.tickComeNoCloserIndicator();
this.tickRoyalPunishment();
this.tickPendingExileAnimation();
@@ -390,21 +383,6 @@ public class KingEntity extends RoyalBossEntity {
ServerLevel serverLevel = (ServerLevel) this.level();
this.iceBuildup.entrySet().removeIf(entry -> serverLevel.getEntity(entry.getKey()) == null);
if (this.iceBuildup.size() > 16) this.iceBuildup.clear();
if (this.returningToTree) {
this.setTarget(null);
this.decreeRetreatPressure = false;
if (this.treePatrolBound && this.treePatrolCenter != null) {
this.setRoyalFlying(true);
if (this.isWithinTreeReturnReleaseRadius()) {
this.returningToTree = false;
} else {
this.tickTreePatrol();
return;
}
} else {
this.returningToTree = false;
}
}
LivingEntity target = this.getTarget();
if (target != null && !this.isDeadOrDying() && this.level() instanceof ServerLevel level) {
this.trackBehavior(target);
@@ -474,14 +452,44 @@ public class KingEntity extends RoyalBossEntity {
}
}
private boolean shouldReturnToTree() {
return this.treePatrolBound && this.treePatrolCenter != null
&& this.horizontalDistanceToTreeSqr() > TREE_RETURN_RADIUS * TREE_RETURN_RADIUS;
}
private boolean isWithinTreeReturnReleaseRadius() {
return this.treePatrolCenter != null
&& this.horizontalDistanceToTreeSqr() <= TREE_RETURN_RELEASE_RADIUS * TREE_RETURN_RELEASE_RADIUS;
private void tickRoyalBoundary(ServerLevel level) {
if (!this.treePatrolBound || this.treePatrolCenter == null) {
return;
}
double boundaryRadius = Math.max(32.0D, AntarchySettings.royalBoundaryRadius());
double distance = Math.sqrt(this.horizontalDistanceToTreeSqr());
if (distance > boundaryRadius) {
Vec3 towardCenter = this.treePatrolCenter.subtract(this.position()).multiply(1.0D, 0.0D, 1.0D);
if (towardCenter.lengthSqr() > 0.001D) {
towardCenter = towardCenter.normalize();
this.getMoveControl().setWantedPosition(this.getX() + towardCenter.x * 12.0D,
this.groundYBelow(this.getX() + towardCenter.x * 12.0D,
this.getZ() + towardCenter.z * 12.0D) + FLYING_PREFERRED_HOVER,
this.getZ() + towardCenter.z * 12.0D, 1.4D);
}
}
LivingEntity target = this.getTarget();
if (target == null || !target.isAlive()) {
this.royalBoundaryGraceTicks = 0;
return;
}
double warningRadius = Math.min(boundaryRadius, Math.max(16.0D, AntarchySettings.royalBoundaryWarningRadius()));
double targetDistance = Math.sqrt(target.position().subtract(this.treePatrolCenter).multiply(1.0D, 0.0D, 1.0D).lengthSqr());
if (targetDistance > warningRadius) {
if (this.tickCount % 5 == 0) {
level.sendParticles(ParticleTypes.END_ROD, target.getX(), target.getY() + 1.0D, target.getZ(),
4, 0.3D, 0.5D, 0.3D, 0.02D);
}
if (targetDistance > boundaryRadius) {
this.royalBoundaryGraceTicks++;
if (this.royalBoundaryGraceTicks > Math.max(0, AntarchySettings.royalBoundaryGraceTicks())
&& this.tickCount % 20 == 0) {
this.invokeJudgment(target);
}
}
} else {
this.royalBoundaryGraceTicks = 0;
}
}
private double horizontalDistanceToTreeSqr() {
@@ -824,10 +832,13 @@ public class KingEntity extends RoyalBossEntity {
}
private void tickKingWholeBody(ServerLevel level, LivingEntity target) {
if (this.isRoyalRecoveryActive()) {
return;
}
boolean skyIsMine = this.activeDecree instanceof SkyIsMineDecree;
boolean forceFar = this.decreeRetreatPressure
|| this.activeDecree instanceof ComeNoCloserDecree
|| this.activeDecree instanceof KeepYourDistanceDecree;
;
{
double distance = target.distanceTo(this);
@@ -1013,7 +1024,7 @@ public class KingEntity extends RoyalBossEntity {
for (int lane = -1; lane <= 1; lane++) {
Vec3 point = start.add(line.scale(distance)).add(side.scale(lane * (i % 2 == 0 ? 2.0D : 0.0D)));
double y = this.groundYBelow(point.x, point.z) + 0.1D;
RoyalIceSpikeEntity spike = RoyalIceSpikeEntity.create(level, new Vec3(point.x, y, point.z));
RoyalIceSpikeEntity spike = RoyalIceSpikeEntity.create(level, new Vec3(point.x, y, point.z), this);
level.addFreshEntity(spike);
}
}
@@ -1053,12 +1064,15 @@ public class KingEntity extends RoyalBossEntity {
Vec3 push = toTarget.normalize().scale(3.2D);
living.setDeltaMovement(living.getDeltaMovement().add(push.x, 0.5D, push.z));
living.hasImpulse = true;
living.hurt(this.damageSources().mobAttack(this), 6.0F);
living.hurt(this.damageSources().mobAttack(this), this.scaleRoyalDamage(6.0D));
}
level.sendParticles(ParticleTypes.SWEEP_ATTACK, this.getX() + forward.x * 6.0D, this.getY() + 4.0D, this.getZ() + forward.z * 6.0D, 12, 4.0D, 2.0D, 4.0D, 0.0D);
}
private void tickDecree(ServerLevel level, LivingEntity target) {
if (this.isRoyalRecoveryActive() && this.activeDecree == null) {
return;
}
if (!(target instanceof ServerPlayer player) || !target.isAlive() || target.level() != level) {
if (this.activeDecree != null) {
this.endDecree(target);
@@ -1137,6 +1151,7 @@ public class KingEntity extends RoyalBossEntity {
this.activeDecreeTicks = 0;
this.decreeRetreatPressure = false;
this.decreeCooldownTicks = AntarchySettings.royalDecreeCooldownTicks();
this.startRoyalRecovery(40);
}
public void clearActiveDecree() {
@@ -1175,7 +1190,7 @@ public class KingEntity extends RoyalBossEntity {
if (this.judgmentCooldowns.getOrDefault(target.getUUID(), 0L) <= now) {
this.judgmentCooldowns.put(target.getUUID(), now + 20L);
this.projectRoyalSound(AntarchySoundEvents.KING_JUDGEMENT.get(), 4.0F, 1.0F, target);
target.hurt(this.damageSources().magic(), 6.0F);
target.hurt(this.damageSources().magic(), this.scaleRoyalDamage(6.0D));
}
}
@@ -252,6 +252,7 @@ public class QueenEntity extends RoyalBossEntity {
}
private void tickDreamFireballs() {
if (this.isRoyalRecoveryActive()) return;
if (!(this.level() instanceof ServerLevel level)) return;
LivingEntity target = this.getTarget();
if (target == null || !target.isAlive() || !this.canAttack(target)) return;
@@ -360,6 +361,7 @@ public class QueenEntity extends RoyalBossEntity {
}
private void tickManticoreSummon() {
if (this.isRoyalRecoveryActive()) return;
if (this.manticoreSummonCooldownTicks > 0) {
this.manticoreSummonCooldownTicks--;
return;
@@ -386,6 +388,7 @@ public class QueenEntity extends RoyalBossEntity {
? this.cooldown(Math.max(20, AntarchySettings.queenManticoreSummonCooldownTicks()))
: FAILED_SUMMON_RETRY_TICKS;
if (spawned > 0) {
this.startRoyalRecovery(45);
this.manticoreAnimationPending = true;
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.8F + this.random.nextFloat() * 0.12F);
serverLevel.sendParticles(ParticleTypes.PORTAL, this.getX(), this.getY() + 2.0D, this.getZ(),
@@ -413,6 +416,7 @@ public class QueenEntity extends RoyalBossEntity {
private int spawnManticoreTears(ServerLevel level, LivingEntity target, int count) {
int pairs = Math.max(1, Math.min(3, (count + 1) / 2));
int created = 0;
int remaining = count;
Vec3 towardTarget = target.position().subtract(this.position()).multiply(1.0D, 0.0D, 1.0D);
if (towardTarget.lengthSqr() < 1.0E-4D) {
towardTarget = this.getViewVector(1.0F).multiply(1.0D, 0.0D, 1.0D);
@@ -436,20 +440,24 @@ public class QueenEntity extends RoyalBossEntity {
double pairSeparation = 3.5D + this.random.nextDouble() * 3.0D;
Vec3 firstPos = pairCenter.add(pairDirection.scale(pairSeparation));
Vec3 secondPos = pairCenter.add(pairDirection.scale(-pairSeparation));
int firstCount = Math.min(3, (remaining + 1) / 2);
int secondCount = Math.min(3, remaining / 2);
DimensionalTearEntity first = DimensionalTearEntity.createQueenManticoreTear(level, firstPos,
tearYaw, 240, this.getUUID(), Math.min(3, count));
tearYaw, 240, this.getUUID(), firstCount);
DimensionalTearEntity second = DimensionalTearEntity.createQueenManticoreTear(level, secondPos,
tearYaw, 240, this.getUUID(), Math.min(3, count));
tearYaw, 240, this.getUUID(), secondCount);
first.linkTo(second);
second.linkTo(first);
level.addFreshEntity(first);
level.addFreshEntity(second);
created++;
remaining -= firstCount + secondCount;
}
return created;
}
private void tickQueenAbilities() {
if (this.isRoyalRecoveryActive()) return;
if (this.gravityStompCooldownTicks > 0) this.gravityStompCooldownTicks--;
if (this.momentumLockCooldownTicks > 0) this.momentumLockCooldownTicks--;
if (this.crushingGravityCooldownTicks > 0) this.crushingGravityCooldownTicks--;
@@ -523,7 +531,7 @@ public class QueenEntity extends RoyalBossEntity {
case "crushing_gravity" -> {
this.crushingGravityCooldownTicks = cooldown;
this.royalEffects.start("crushing_gravity", CRUSHING_GRAVITY_DURATION,
() -> {}, this::tickCrushingGravity, () -> {});
() -> {}, this::tickCrushingGravity, () -> this.startRoyalRecovery(35));
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.6F);
}
case "acceleration" -> { this.accelerationCooldownTicks = cooldown; this.startRoyalAcceleration(); }
@@ -565,7 +573,7 @@ public class QueenEntity extends RoyalBossEntity {
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class,
this.getBoundingBox().inflate(18.0D), this::canDamageWithRoyalAttack)) {
double distance = Math.max(1.0D, living.distanceTo(this));
living.hurt(source, (float) (12.0D * Math.max(0.25D, 1.0D - distance / 24.0D)));
living.hurt(source, this.scaleRoyalDamage(12.0D * Math.max(0.25D, 1.0D - distance / 24.0D)));
Vec3 push = living.position().subtract(this.position()).normalize().scale(1.3D);
living.setDeltaMovement(living.getDeltaMovement().add(push.x, 0.65D, push.z));
living.hasImpulse = true;
@@ -611,6 +619,7 @@ public class QueenEntity extends RoyalBossEntity {
}
}
this.frozenVelocities.clear();
this.startRoyalRecovery(35);
});
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.5F);
}
@@ -669,6 +678,7 @@ public class QueenEntity extends RoyalBossEntity {
this.setRoyalAccelerated(false);
removeAccelModifier(this.getAttribute(Attributes.MOVEMENT_SPEED), ACCEL_SPEED_ID);
removeAccelModifier(this.getAttribute(Attributes.FLYING_SPEED), ACCEL_FLY_ID);
this.startRoyalRecovery(30);
});
}
@@ -355,7 +355,9 @@ public class RoyalBlackHoleEntity extends Entity implements GeoEntity {
}
double distance = Math.max(1.0D, living.position().distanceTo(center));
float falloff = (float) Math.max(0.25D, 1.0D - distance / radius);
living.hurt(source, 26.0F * falloff);
double damage = 26.0D * falloff;
living.hurt(source, this.ownerEntity() instanceof RoyalBossEntity royalBoss
? royalBoss.scaleRoyalDamage(damage) : (float) damage);
Vec3 launch = living.position().subtract(center).normalize().scale(2.6D * falloff);
living.setDeltaMovement(launch.x, 1.1D * falloff + 0.4D, launch.z);
living.hasImpulse = true;
@@ -14,7 +14,11 @@ import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.network.chat.Component;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
@@ -37,6 +41,8 @@ import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.control.FlyingMoveControl;
import net.minecraft.world.entity.ai.control.MoveControl;
import net.minecraft.world.entity.ai.goal.FloatGoal;
@@ -100,6 +106,8 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
private static final int CRUSH_MAX_BLOCKS = 24;
private static final double CRUSH_MAX_RESISTANCE = 60.0D;
private static final float CRUSH_DROP_CHANCE = 0.1F;
private static final int RECOVERY_AFTER_BEAM_TICKS = 30;
private static final ResourceLocation MULTIPLAYER_DAMAGE_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "royal_multiplayer_damage");
private static final int OBSTRUCTION_CLEAR_INTERVAL_TICKS = 5;
private static final double OBSTRUCTION_CLEAR_RADIUS = 3.5D;
private static final int OBSTRUCTION_CLEAR_MAX_BLOCKS = 24;
@@ -177,6 +185,10 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
@Nullable
private Vec3 aerialCombatAnchor;
private int aerialCombatAnchorTicks;
private int recoveryWindowTicks;
private boolean multiplayerScalingInitialized;
private final Set<UUID> encounterParticipants = new HashSet<>();
private double royalDamageMultiplier = 1.0D;
@Nullable
private Entity[] multipartParts;
@@ -249,10 +261,14 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
}
public static AttributeSupplier.Builder createBaseAttributes(double health, double attackDamage) {
return createBaseAttributes(health, attackDamage, AntarchySettings.royalBossArmor());
}
protected static AttributeSupplier.Builder createBaseAttributes(double health, double attackDamage, double armor) {
return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, health)
.add(Attributes.ATTACK_DAMAGE, attackDamage)
.add(Attributes.ARMOR, AntarchySettings.royalBossArmor())
.add(Attributes.ARMOR, armor)
.add(Attributes.FOLLOW_RANGE, AntarchySettings.royalBossFollowRange())
.add(Attributes.MOVEMENT_SPEED, AntarchySettings.royalBossMovementSpeed())
.add(Attributes.FLYING_SPEED, AntarchySettings.royalBossMovementSpeed())
@@ -447,6 +463,9 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth());
this.attackScheduler.tick();
if (this.recoveryWindowTicks > 0) {
this.recoveryWindowTicks--;
}
if (this.isRoyalFlying() && this.tickCount % 40 == 0 && this.royalFlyLoopSound() != null) {
this.playRoyalSound(this.royalFlyLoopSound(), 0.92F + this.random.nextFloat() * 0.12F);
@@ -464,6 +483,8 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
return;
}
this.initializeMultiplayerScaling(primaryTarget);
this.tickCombatLocomotionMode(primaryTarget);
boolean aeriallyStabilized = this.steerTowardTarget(primaryTarget);
if (!aeriallyStabilized) {
@@ -475,6 +496,9 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
Phase phase = this.phase();
int activeHeadAttacks = 0;
if (this.recoveryWindowTicks > 0) {
return;
}
for (RoyalHead head : this.heads) {
if (this.attackScheduler.laneBusy(this.headLane(head))) {
activeHeadAttacks++;
@@ -503,6 +527,9 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
}
private void tickRoyalBeam(LivingEntity primaryTarget) {
if (this.recoveryWindowTicks > 0) {
return;
}
Phase phase = this.phase();
int activeBeams = 0;
for (RoyalHead head : this.heads) {
@@ -588,6 +615,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
@Override
public void onComplete() {
RoyalBossEntity.this.stopRoyalBeam(head);
RoyalBossEntity.this.startRoyalRecovery(RECOVERY_AFTER_BEAM_TICKS);
}
})) {
continue;
@@ -601,6 +629,47 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
return phase.maxConcurrentHeadAttacks();
}
protected boolean isRoyalRecoveryActive() {
return this.recoveryWindowTicks > 0;
}
protected void startRoyalRecovery(int ticks) {
this.recoveryWindowTicks = Math.max(this.recoveryWindowTicks, ticks);
}
private void initializeMultiplayerScaling(LivingEntity target) {
if (this.multiplayerScalingInitialized || !(this.level() instanceof ServerLevel level)) {
return;
}
this.multiplayerScalingInitialized = true;
if (!AntarchySettings.royalBossMultiplayerScalingEnabled()) {
return;
}
int maxPlayers = Math.max(1, AntarchySettings.royalBossScalingMaxPlayers());
List<ServerPlayer> players = level.getPlayers(player -> player.isAlive()
&& !player.isSpectator()
&& player.distanceToSqr(this) <= AntarchySettings.royalBossFollowRange() * AntarchySettings.royalBossFollowRange());
if (target instanceof ServerPlayer player && player.isAlive() && !players.contains(player)) {
players.add(player);
}
int participantCount = Math.min(maxPlayers, Math.max(1, players.size()));
for (ServerPlayer player : players) {
if (this.encounterParticipants.size() >= participantCount) {
break;
}
this.encounterParticipants.add(player.getUUID());
}
double healthMultiplier = 1.0D + Math.max(0, participantCount - 1)
* Math.max(0.0D, AntarchySettings.royalBossHealthPerAdditionalPlayer());
double baseHealth = this.getAttributeBaseValue(Attributes.MAX_HEALTH);
this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(baseHealth * healthMultiplier);
this.setHealth(this.getMaxHealth());
double damageMultiplier = Math.max(0.0D, participantCount - 1)
* Math.max(0.0D, AntarchySettings.royalBossDamagePerAdditionalPlayer());
this.royalDamageMultiplier = 1.0D + damageMultiplier;
this.applyRoyalDamageModifier();
}
private void stopRoyalBeam(RoyalHead head) {
int index = head.slot().ordinal();
head.setBeamActive(false);
@@ -1211,12 +1280,38 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putBoolean("RoyalFlying", this.isRoyalFlying());
tag.putBoolean("RoyalScalingInitialized", this.multiplayerScalingInitialized);
tag.putInt("RoyalScalingParticipants", this.encounterParticipants.size());
tag.putDouble("RoyalDamageMultiplier", this.royalDamageMultiplier);
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.setRoyalFlying(!tag.contains("RoyalFlying") || tag.getBoolean("RoyalFlying"));
this.multiplayerScalingInitialized = tag.getBoolean("RoyalScalingInitialized");
this.royalDamageMultiplier = Math.max(1.0D, tag.getDouble("RoyalDamageMultiplier"));
if (tag.contains("RoyalScalingParticipants")) {
int count = Math.max(1, tag.getInt("RoyalScalingParticipants"));
for (int i = 0; i < count; i++) {
this.encounterParticipants.add(new UUID(0L, i + 1L));
}
}
this.applyRoyalDamageModifier();
}
public float scaleRoyalDamage(double damage) {
return (float) (damage * this.royalDamageMultiplier);
}
private void applyRoyalDamageModifier() {
AttributeInstance attackDamage = this.getAttribute(Attributes.ATTACK_DAMAGE);
if (attackDamage == null || this.royalDamageMultiplier <= 1.0D
|| attackDamage.getModifier(MULTIPLAYER_DAMAGE_ID) != null) {
return;
}
attackDamage.addPermanentModifier(new AttributeModifier(MULTIPLAYER_DAMAGE_ID,
this.royalDamageMultiplier - 1.0D, AttributeModifier.Operation.ADD_MULTIPLIED_BASE));
}
@Override
@@ -136,7 +136,9 @@ public class RoyalElementalProjectileEntity extends ThrowableProjectile implemen
this::canDamageWithImpact)) {
double distance = Math.max(0.5D, living.position().distanceTo(center));
float falloff = (float) Math.max(0.25D, 1.0D - distance / 5.0D);
living.hurt(source, (float) (AntarchySettings.kingFireballDamage() * falloff));
double damage = AntarchySettings.kingFireballDamage() * falloff;
living.hurt(source, this.getOwner() instanceof RoyalBossEntity royalBoss
? royalBoss.scaleRoyalDamage(damage) : (float) damage);
living.setRemainingFireTicks(Math.max(living.getRemainingFireTicks(), 120));
}
igniteImpact(level, center, this.isDreamFireball());
@@ -170,7 +172,9 @@ public class RoyalElementalProjectileEntity extends ThrowableProjectile implemen
DamageSource source = this.damageSources().mobProjectile(this, this.getOwner() instanceof LivingEntity living ? living : null);
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class, new AABB(center, center).inflate(5.0D),
this::canDamageWithImpact)) {
living.hurt(source, (float) AntarchySettings.kingIceballDamage());
double damage = AntarchySettings.kingIceballDamage();
living.hurt(source, this.getOwner() instanceof RoyalBossEntity royalBoss
? royalBoss.scaleRoyalDamage(damage) : (float) damage);
living.setTicksFrozen(Math.min(living.getTicksRequiredToFreeze() + 80, living.getTicksFrozen() + 180));
living.addEffect(new net.minecraft.world.effect.MobEffectInstance(net.minecraft.world.effect.MobEffects.MOVEMENT_SLOWDOWN, 120, 3));
}
@@ -12,6 +12,8 @@ import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import java.util.UUID;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.animation.AnimatableManager;
@@ -26,6 +28,8 @@ public class RoyalIceSpikeEntity extends Entity implements GeoEntity {
private static final String ANIMATION_CONTROLLER = "ice_spike";
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private boolean impacted;
@Nullable
private UUID ownerId;
public RoyalIceSpikeEntity(EntityType<? extends RoyalIceSpikeEntity> type, Level level) {
super(type, level);
@@ -39,6 +43,12 @@ public class RoyalIceSpikeEntity extends Entity implements GeoEntity {
return spike;
}
public static RoyalIceSpikeEntity create(ServerLevel level, Vec3 position, LivingEntity owner) {
RoyalIceSpikeEntity spike = create(level, position);
spike.ownerId = owner.getUUID();
return spike;
}
@Override
protected void defineSynchedData(net.minecraft.network.syncher.SynchedEntityData.Builder builder) {
}
@@ -65,7 +75,10 @@ public class RoyalIceSpikeEntity extends Entity implements GeoEntity {
DamageSource source = this.damageSources().magic();
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class,
new AABB(this.blockPosition()).inflate(2.2D), entity -> entity.isAlive())) {
living.hurt(source, (float) AntarchySettings.kingIceSpikeDamage());
Entity owner = this.ownerId == null ? null : level.getEntity(this.ownerId);
living.hurt(source, owner instanceof RoyalBossEntity royalBoss
? royalBoss.scaleRoyalDamage(AntarchySettings.kingIceSpikeDamage())
: (float) AntarchySettings.kingIceSpikeDamage());
living.setTicksFrozen(Math.min(living.getTicksRequiredToFreeze() + 60, living.getTicksFrozen() + 100));
living.setDeltaMovement(living.getDeltaMovement().add(0.0D, 0.35D, 0.0D));
living.hasImpulse = true;
@@ -76,11 +89,17 @@ public class RoyalIceSpikeEntity extends Entity implements GeoEntity {
@Override
protected void readAdditionalSaveData(CompoundTag tag) {
this.impacted = tag.getBoolean("Impacted");
if (tag.hasUUID("Owner")) {
this.ownerId = tag.getUUID("Owner");
}
}
@Override
protected void addAdditionalSaveData(CompoundTag tag) {
tag.putBoolean("Impacted", this.impacted);
if (this.ownerId != null) {
tag.putUUID("Owner", this.ownerId);
}
}
@Override
@@ -154,13 +154,13 @@ public final class RoyalBeamController {
boolean pathMutates = terrainMode == RoyalBeamTerrainMode.DESTROY;
for (double walked = settings.pathStep(); walked < Math.min(distance, settings.range()); walked += settings.pathStep()) {
Vec3 sample = shootFrom.add(direction.scale(walked));
hurtEntitiesAround(sample, settings.pathDamageRadius(), settings.damage(), settings.knockback(), damageSource, settings.requireLineOfSightForDamage(), this.damagedThisTick);
hurtEntitiesAround(sample, settings.pathDamageRadius(), this.scaledDamage(settings.damage()), settings.knockback(), damageSource, settings.requireLineOfSightForDamage(), this.damagedThisTick);
if (pathMutates && shouldMutateTerrain(settings, walked)) {
mutateTerrainAround(sample, settings.pathTerrainRadius(), settings, terrainMode);
}
}
hurtEntitiesAround(beamEnd, settings.impactDamageRadius(), settings.damage(), settings.knockback(), damageSource, settings.requireLineOfSightForDamage(), this.damagedThisTick);
hurtEntitiesAround(beamEnd, settings.impactDamageRadius(), this.scaledDamage(settings.damage()), settings.knockback(), damageSource, settings.requireLineOfSightForDamage(), this.damagedThisTick);
if (!shouldMutateTerrain(settings, distance)) {
return;
@@ -174,6 +174,10 @@ public final class RoyalBeamController {
}
}
private float scaledDamage(float damage) {
return this.owner instanceof RoyalBossEntity royalBoss ? royalBoss.scaleRoyalDamage(damage) : damage;
}
private boolean shouldMutateTerrain(RoyalBeamSettings settings, double distanceFromOwner) {
return settings.terrainEnabled()
&& settings.maxTerrainMutationsPerTick() > 0
@@ -1,16 +0,0 @@
package com.craisinlord.antarchy.content.entity.royal.decree;
import com.craisinlord.antarchy.content.entity.royal.KingEntity;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
public final class AdvanceDecree implements RoyalDecree {
private static final double REQUIRED_RANGE = 14.0D;
public String translationKey() { return "decree.antarchy.advance"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) { king.setDecreeRetreatPressure(true); }
@Override public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
return target.distanceToSqr(king) > REQUIRED_RANGE * REQUIRED_RANGE ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
}
@Override
public int contextWeight(KingEntity king, LivingEntity target) {
return 4 + king.behaviorScore(KingEntity.Behavior.KEEPING_FAR);
}
}
@@ -1,16 +0,0 @@
package com.craisinlord.antarchy.content.entity.royal.decree;
import com.craisinlord.antarchy.content.entity.royal.KingEntity;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
public final class KeepYourDistanceDecree implements RoyalDecree {
private static final double INNER_RADIUS = 16.0D;
public String translationKey() { return "decree.antarchy.keep_your_distance"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) { }
@Override public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
return target.distanceToSqr(king) <= INNER_RADIUS * INNER_RADIUS ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
}
@Override
public int contextWeight(KingEntity king, LivingEntity target) {
return 4 + king.behaviorScore(KingEntity.Behavior.HUGGING);
}
}
@@ -1,17 +0,0 @@
package com.craisinlord.antarchy.content.entity.royal.decree;
import com.craisinlord.antarchy.content.entity.royal.KingEntity;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
public final class NoRetreatDecree implements RoyalDecree {
private static final double ALLOWED_RANGE = 26.0D;
public String translationKey() { return "decree.antarchy.no_retreat"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) {
if (target.distanceToSqr(king) > ALLOWED_RANGE * ALLOWED_RANGE) {
king.invokeJudgment(target);
}
}
@Override
public int contextWeight(KingEntity king, LivingEntity target) {
return 4 + king.behaviorScore(KingEntity.Behavior.RETREATING);
}
}
@@ -0,0 +1,133 @@
package com.craisinlord.antarchy.content.entity.trades;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.entity.npc.VillagerTrades;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.trading.ItemCost;
import net.minecraft.world.item.trading.MerchantOffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/** Loads Computer Scientist offers from data/computer_scientist_trades/*.json. */
public final class ComputerScientistTradeManager extends SimpleJsonResourceReloadListener {
private static final Gson GSON = new Gson();
private static final String DIRECTORY = "computer_scientist_trades";
private static final List<ResourceLocation> GENERAL_DISKS = List.of(
id("introduction"), id("chronosphere"), id("high_fructose_corn_syrup"),
id("elythia"), id("flying_squirrel"), id("cavaryn"), id("thoraxis"),
id("temporal_fields"), id("bloodglass"), id("vortex"));
private static volatile List<TradeEntry> loadedTrades = List.of();
public ComputerScientistTradeManager() {
super(GSON, DIRECTORY);
}
public static int[] slotsPerLevel() {
return new int[] {4, 2, 2, 3, 4};
}
public static VillagerTrades.ItemListing listing(int level, int index) {
return (trader, random) -> {
TradeEntry trade = loadedTrades.stream()
.filter(entry -> entry.level == level && entry.index == index)
.findFirst()
.orElse(null);
return trade == null ? null : trade.createOffer(random);
};
}
@Override
protected void apply(Map<ResourceLocation, JsonElement> object, ResourceManager resourceManager, ProfilerFiller profiler) {
List<TradeEntry> parsed = new ArrayList<>();
for (JsonElement file : object.values()) {
if (!file.isJsonObject()) continue;
JsonObject root = file.getAsJsonObject();
if (root.has("trades") && root.get("trades").isJsonArray()) {
for (JsonElement element : root.getAsJsonArray("trades")) {
if (element.isJsonObject()) parsed.add(parse(element.getAsJsonObject()));
}
} else {
parsed.add(parse(root));
}
}
loadedTrades = List.copyOf(parsed);
}
private static TradeEntry parse(JsonObject json) {
return new TradeEntry(
integer(json, "level", 1), integer(json, "index", 0),
stack(json.getAsJsonObject("buy")),
json.has("second_buy") ? stack(json.getAsJsonObject("second_buy")) : null,
stack(json.getAsJsonObject("sell")),
json.has("sell_disk") ? json.get("sell_disk").getAsString() : null,
integer(json, "max_uses", 12), integer(json, "villager_xp", 5),
json.has("price_multiplier") ? json.get("price_multiplier").getAsFloat() : 0.05F,
json.has("chance") ? json.get("chance").getAsFloat() : 1.0F);
}
private record TradeEntry(int level, int index, StackRange buy, StackRange secondBuy,
StackRange sell, String sellDisk, int maxUses, int villagerXp,
float priceMultiplier, float chance) {
private MerchantOffer createOffer(net.minecraft.util.RandomSource random) {
if (random.nextFloat() > chance) return null;
ItemCost buyCost = buy.toCost(random);
java.util.Optional<ItemCost> secondCost = secondBuy == null
? java.util.Optional.empty() : java.util.Optional.of(secondBuy.toCost(random));
ItemStack result = sellDisk == null ? sell.toStack(random) : diskStack(sellDisk, random, sell.toStack(random).getCount());
return new MerchantOffer(buyCost, secondCost, result, maxUses, villagerXp, priceMultiplier);
}
}
private record StackRange(ResourceLocation item, int min, int max) {
private Item itemValue() {
return BuiltInRegistries.ITEM.get(item);
}
private int count(net.minecraft.util.RandomSource random) {
return min == max ? min : min + random.nextInt(max - min + 1);
}
private ItemCost toCost(net.minecraft.util.RandomSource random) {
return new ItemCost(itemValue(), count(random));
}
private ItemStack toStack(net.minecraft.util.RandomSource random) {
return new ItemStack(itemValue(), count(random));
}
}
private static StackRange stack(JsonObject json) {
int min = integer(json, "min", integer(json, "count", 1));
int max = integer(json, "max", min);
return new StackRange(ResourceLocation.parse(json.get("item").getAsString()), min, max);
}
private static ItemStack diskStack(String diskType, net.minecraft.util.RandomSource random, int count) {
ResourceLocation diskId = diskType.equals("random_general")
? GENERAL_DISKS.get(random.nextInt(GENERAL_DISKS.size()))
: ResourceLocation.parse(diskType);
ItemStack disk = new ItemStack(AntarchyObjects.FLOPPY_DISK.get(), count);
disk.set(AntarchyObjects.FLOPPY_DISK_COMPONENT.get(), diskId);
return disk;
}
private static int integer(JsonObject json, String key, int fallback) {
return json.has(key) ? json.get(key).getAsInt() : fallback;
}
private static ResourceLocation id(String path) {
return ResourceLocation.fromNamespaceAndPath("antarchy", path);
}
}
@@ -2,6 +2,8 @@ package com.craisinlord.antarchy.content.entity.vortex;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
@@ -200,7 +202,10 @@ public class VortexEntity extends Monster implements GeoEntity {
private void tickCombatMovement(LivingEntity target) {
this.faceTowards(target.position().add(0.0D, target.getBbHeight() * 0.5D, 0.0D), 12.0F, 8.0F);
this.getMoveControl().setWantedPosition(target.getX(), target.getY() + 2.5D, target.getZ(), 0.38D);
Vec3 gravityUp = Vec3.atLowerCornerOf(
AntarchyGravityRotationUtil.getGravityDownDirection(this).getOpposite().getNormal());
Vec3 hoverPosition = target.position().add(gravityUp.scale(2.5D));
this.getMoveControl().setWantedPosition(hoverPosition.x, hoverPosition.y, hoverPosition.z, 0.38D);
if (this.attackCooldown <= 0
&& this.distanceToSqr(target) <= ATTACK_RANGE * ATTACK_RANGE
@@ -251,6 +256,7 @@ public class VortexEntity extends Monster implements GeoEntity {
double distance = direction.length();
Vec3 drift = distance > 1.0E-6D ? direction.scale(0.24D / distance) : this.getViewVector(1.0F).scale(0.24D);
WindVortexEntity vortex = WindVortexEntity.create(serverLevel, windVortexTypeSupplier.get(), origin, Vec3.ZERO, this, true);
vortex.setAxis(AntarchyGravityRotationUtil.getGravityDownDirection(this).getOpposite());
vortex.setTravel(drift);
vortex.setTravelDuration(Mth.clamp((int) Math.ceil(distance / drift.length()), 8, 36));
vortex.setHoming(false);
@@ -2,6 +2,8 @@ package com.craisinlord.antarchy.content.entity.vortex;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyTags;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
@@ -208,12 +210,10 @@ public class WindVortexEntity extends Entity {
}
} else if (!lensVortex) {
Vec3 current = this.getDeltaMovement();
Vec3 axis = this.getAxis();
double downwardSpeed = Math.min(MAX_FALL_SPEED, Math.max(MINIMUM_DOWNWARD_DRIFT,
-current.y + FALL_ACCELERATION));
Vec3 drift = new Vec3(
current.x * DRIFT_FRICTION,
-downwardSpeed,
current.z * DRIFT_FRICTION);
-current.dot(axis) + FALL_ACCELERATION));
Vec3 drift = current.scale(DRIFT_FRICTION).add(axis.scale(-downwardSpeed));
Vec3 from = this.position();
Vec3 to = from.add(drift);
HitResult blockHit = this.level().clip(
@@ -323,20 +323,17 @@ public class WindVortexEntity extends Entity {
: this.getDeltaMovement();
double speed = Mth.clamp(incoming.length(), 0.2D, 0.9D);
Vec3 flow = incoming;
if (normal.y > 0.5D) {
flow = new Vec3(incoming.x, 0.0D, incoming.z);
} else if (normal.y < -0.5D) {
flow = new Vec3(incoming.x, 0.0D, incoming.z).add(0.0D, -0.08D, 0.0D);
} else {
Vec3 tangent = incoming.subtract(normal.scale(incoming.dot(normal)));
Vec3 tangent = incoming.subtract(normal.scale(incoming.dot(normal)));
if (tangent.lengthSqr() < 1.0E-6D) {
tangent = this.getAxis().cross(normal);
if (tangent.lengthSqr() < 1.0E-6D) {
tangent = new Vec3(-normal.z, 0.0D, normal.x);
if (tangent.lengthSqr() < 1.0E-6D) {
tangent = new Vec3(0.0D, 0.0D, 1.0D);
}
tangent = new Vec3(0.0D, 1.0D, 0.0D).cross(normal);
}
if (tangent.lengthSqr() < 1.0E-6D) {
tangent = new Vec3(1.0D, 0.0D, 0.0D);
}
flow = tangent.normalize().scale(speed);
}
flow = tangent.normalize().scale(speed);
if (flow.lengthSqr() < 1.0E-6D) {
this.travelVelocity = Vec3.ZERO;
this.setDeltaMovement(Vec3.ZERO);
@@ -511,24 +508,20 @@ public class WindVortexEntity extends Entity {
.add(tangent.scale(spin))
.add(basis.axis.scale(axialSpeed))
.scale(ENTITY_FORCE_SPEED_SCALE);
Vec3 current = entity.getDeltaMovement();
Vec3 updatedMovement = new Vec3(
Mth.lerp(0.72D, current.x, wanted.x),
Mth.lerp(0.58D, current.y, wanted.y),
Mth.lerp(0.72D, current.z, wanted.z)
);
Vec3 current = AntarchyGravityApi.getWorldVelocity(entity);
Vec3 updatedMovement = current.lerp(wanted, 0.72D);
if (this.travelling) {
updatedMovement = updatedMovement.add(
this.travelVelocity.x * 0.8D * ENTITY_FORCE_SPEED_SCALE,
0.0D,
this.travelVelocity.z * 0.8D * ENTITY_FORCE_SPEED_SCALE);
updatedMovement = updatedMovement.add(this.travelVelocity.scale(0.8D * ENTITY_FORCE_SPEED_SCALE));
}
if (mode == VortexMode.UPWARD && entity.onGround()) {
double minRise = (entity instanceof Player ? 0.38D : 0.3D) * ENTITY_FORCE_SPEED_SCALE;
updatedMovement = new Vec3(updatedMovement.x, Math.max(updatedMovement.y, minRise), updatedMovement.z);
Vec3 gravityUp = Vec3.atLowerCornerOf(
AntarchyGravityRotationUtil.getGravityDownDirection(entity).getOpposite().getNormal());
double minimumAxialSpeed = Math.max(updatedMovement.dot(gravityUp), minRise);
updatedMovement = updatedMovement.add(gravityUp.scale(minimumAxialSpeed - updatedMovement.dot(gravityUp)));
entity.setOnGround(false);
}
entity.setDeltaMovement(updatedMovement);
AntarchyGravityApi.setWorldVelocity(entity, updatedMovement);
if (entity instanceof Player) {
entity.setOnGround(false);
}
@@ -542,7 +535,7 @@ public class WindVortexEntity extends Entity {
Vec3 radial = radialVector.lengthSqr() < 1.0E-6D ? basis.sideA : radialVector.scale(1.0D / horizontal);
Vec3 tangent = basis.axis.cross(radial).normalize();
double scale = this.launchStrength * Mth.clamp(this.getVortexHeight() / 5.0D, 0.5D, 2.5D);
entity.setDeltaMovement(tangent.scale(0.72D * scale * ENTITY_FORCE_SPEED_SCALE)
this.setWorldVelocity(entity, tangent.scale(0.72D * scale * ENTITY_FORCE_SPEED_SCALE)
.add(radial.scale(0.34D * scale * ENTITY_FORCE_SPEED_SCALE))
.add(basis.axis.scale((0.78D + progress * 0.28D) * scale * ENTITY_FORCE_SPEED_SCALE)));
entity.hasImpulse = true;
@@ -578,7 +571,9 @@ public class WindVortexEntity extends Entity {
Vec3 direction = distance < 1.0E-4D ? new Vec3(0.0D, 1.0D, 0.0D) : toOwner.scale(1.0D / distance);
double arc = Mth.clamp(distance * 0.06D, 0.25D, 1.1D) * ENTITY_FORCE_SPEED_SCALE;
double power = this.launchStrength * Mth.clamp(distance / 6.0D, 0.6D, 2.0D) * ENTITY_FORCE_SPEED_SCALE;
entity.setDeltaMovement(direction.scale(power).add(0.0D, arc, 0.0D));
Vec3 gravityUp = Vec3.atLowerCornerOf(
AntarchyGravityRotationUtil.getGravityDownDirection(entity).getOpposite().getNormal());
this.setWorldVelocity(entity, direction.scale(power).add(gravityUp.scale(arc)));
entity.fallDistance = 0.0F;
entity.hasImpulse = true;
entity.hurtMarked = true;
@@ -596,6 +591,10 @@ public class WindVortexEntity extends Entity {
}
}
private void setWorldVelocity(Entity entity, Vec3 velocity) {
AntarchyGravityApi.setWorldVelocity(entity, velocity);
}
private DamageSource damageSource() {
Entity owner = this.getOwnerEntity();
if (owner instanceof LivingEntity living) {
@@ -41,14 +41,14 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
public static List<Entry> entriesFor(List<ResourceLocation> diskIds) {
if (AntarchySettings.unlockAllArchives()) {
Map<ResourceLocation, Entry> result = new LinkedHashMap<>();
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"), "", "", "", ""));
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"), "", "", "", "", ""));
result.putAll(entries);
return result.values().stream().sorted(Comparator.comparing(entry -> entry.titleKey().toString())).toList();
}
Map<ResourceLocation, Entry> result = new LinkedHashMap<>();
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"), "", "", "", ""));
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);
@@ -115,9 +115,10 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
List<String> description = strings(object.get("description"));
String item = string(object, "item", "");
String entity = string(object, "entity", "");
String recipe = string(object, "recipe", "");
String structure = string(object, "structure", "");
String dimension = string(object, "dimension", "");
target.put(id, new Entry(id, type, category, title, subtitle, description, item, entity, structure, dimension));
target.put(id, new Entry(id, type, category, title, subtitle, description, item, entity, recipe, structure, dimension));
}
private static void parseDisk(ResourceLocation id, JsonElement element, Map<ResourceLocation, Disk> target) {
@@ -163,13 +164,21 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
for (JsonElement value : array) {
if (value.isJsonPrimitive()) {
values.add(value.getAsString());
} else if (value.isJsonObject()) {
JsonObject render = value.getAsJsonObject();
String item = string(render, "item", "");
String entity = string(render, "entity", "");
String recipe = string(render, "recipe", "");
if (!item.isBlank()) values.add("@item:" + item);
else if (!entity.isBlank()) values.add("@entity:" + entity);
else if (!recipe.isBlank()) values.add("@recipe:" + recipe);
}
}
return List.copyOf(values);
}
public record Entry(ResourceLocation id, String type, String category, String titleKey, String subtitleKey,
List<String> descriptionKeys, String itemId, String entityId, String structureId, String dimensionId) {
List<String> descriptionKeys, String itemId, String entityId, String recipeId, String structureId, String dimensionId) {
}
public record Disk(ResourceLocation id, String category, String titleKey, List<ResourceLocation> entries, String wallpaper) {
@@ -1,54 +1,10 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.content.client.model.ResourceBackedGeoItemModel;
import com.craisinlord.antarchy.content.client.renderer.AnimatedHeldItemRenderer;
import com.craisinlord.antarchy.content.block.ComputerBlock;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.BlockItem;
import software.bernie.geckolib.animatable.GeoItem;
import software.bernie.geckolib.animatable.client.GeoRenderProvider;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.animation.AnimatableManager;
import software.bernie.geckolib.animation.AnimationController;
import software.bernie.geckolib.animation.PlayState;
import software.bernie.geckolib.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
import java.util.function.Consumer;
public final class ComputerItem extends BlockItem implements GeoItem {
private static final ResourceLocation MODEL = ResourceLocation.fromNamespaceAndPath("antarchy", "geo/computer.geo.json");
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 final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this);
public final class ComputerItem extends BlockItem {
public ComputerItem(ComputerBlock block, Properties properties) {
super(block, properties);
GeoItem.registerSyncedAnimatable(this);
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "state", state -> state.setAndContinue(RawAnimation.begin().thenLoop("on_state"))));
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return cache;
}
@Override
public void createGeoRenderer(Consumer<GeoRenderProvider> consumer) {
consumer.accept(new GeoRenderProvider() {
private AnimatedHeldItemRenderer<ComputerItem> renderer;
@Override
public net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer getGeoItemRenderer() {
if (renderer == null) {
renderer = new AnimatedHeldItemRenderer<>(new ResourceBackedGeoItemModel<>(MODEL, TEXTURE, ANIMATION));
}
return renderer;
}
});
}
}
@@ -5,6 +5,7 @@ import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.client.model.ResourceBackedGeoItemModel;
import com.craisinlord.antarchy.content.client.renderer.AnimatedHeldItemRenderer;
import com.craisinlord.antarchy.content.entity.vortex.WindVortexEntity;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import java.util.List;
import java.util.function.Consumer;
import net.minecraft.ChatFormatting;
@@ -76,12 +77,15 @@ public class EyeOfTheStormItem extends Item implements GeoItem {
false
);
vortex.setMode(WindVortexEntity.VortexMode.UPWARD);
vortex.setAxis(Direction.UP);
vortex.setAxis(AntarchyGravityApi.isGravityInverted(player) ? Direction.DOWN : Direction.UP);
vortex.setVortexSize((float) AntarchySettings.eyeOfTheStormUpdraftHeight(), (float) AntarchySettings.eyeOfTheStormUpdraftRadius());
vortex.setVortexDurationTicks(AntarchySettings.eyeOfTheStormUpdraftDurationTicks());
serverLevel.addFreshEntity(vortex);
player.setDeltaMovement(player.getDeltaMovement().add(0.0D, AntarchySettings.eyeOfTheStormUpdraftLaunchStrength(), 0.0D));
Vec3 gravityUp = Vec3.atLowerCornerOf(
(AntarchyGravityApi.isGravityInverted(player) ? Direction.DOWN : Direction.UP).getNormal());
AntarchyGravityApi.setWorldVelocity(player,
AntarchyGravityApi.getWorldVelocity(player).add(gravityUp.scale(AntarchySettings.eyeOfTheStormUpdraftLaunchStrength())));
player.hasImpulse = true;
player.resetFallDistance();
player.getCooldowns().addCooldown(this, AntarchySettings.eyeOfTheStormUpdraftCooldownTicks());
@@ -129,7 +133,7 @@ public class EyeOfTheStormItem extends Item implements GeoItem {
true
);
vortex.setMode(WindVortexEntity.VortexMode.UPWARD);
vortex.setAxis(Direction.UP);
vortex.setAxis(owner != null && AntarchyGravityApi.isGravityInverted(owner) ? Direction.DOWN : Direction.UP);
vortex.setVortexSize((float) AntarchySettings.eyeOfTheStormSurgeHeight(), (float) AntarchySettings.eyeOfTheStormSurgeRadius());
vortex.setVortexDurationTicks(AntarchySettings.eyeOfTheStormSurgeDurationTicks());
vortex.setVortexStrengths(AntarchySettings.eyeOfTheStormSurgePullStrength(), AntarchySettings.eyeOfTheStormSurgeReturnStrength());
@@ -14,6 +14,19 @@ public final class FloppyDiskItem extends Item {
super(properties.stacksTo(1));
}
public static float categoryModelProperty(ItemStack stack) {
ResourceLocation id = stack.get(AntarchyObjects.FLOPPY_DISK_COMPONENT.get());
if (id == null) return 0.0F;
var disk = com.craisinlord.antarchy.content.guide.ComputerGuideData.disk(id);
if (disk == null) return 0.0F;
return switch (disk.category()) {
case "elythia" -> 1.0F;
case "cavaryn" -> 2.0F;
case "thoraxis" -> 3.0F;
default -> 0.0F;
};
}
@Override
public Component getName(ItemStack stack) {
ResourceLocation id = stack.get(AntarchyObjects.FLOPPY_DISK_COMPONENT.get());
@@ -28,6 +41,7 @@ public final class FloppyDiskItem extends Item {
@Override
public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> tooltip, TooltipFlag flag) {
tooltip.add(Component.translatable("item.antarchy.floppy_disk.tooltip"));
ResourceLocation id = stack.get(AntarchyObjects.FLOPPY_DISK_COMPONENT.get());
if (id != null) {
var disk = com.craisinlord.antarchy.content.guide.ComputerGuideData.disk(id);
@@ -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 AntmailDeletePayload(BlockPos pos, String messageId, boolean sent) implements CustomPacketPayload {
public static final Type<AntmailDeletePayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "antmail_delete"));
public static final StreamCodec<RegistryFriendlyByteBuf, AntmailDeletePayload> STREAM_CODEC = StreamCodec.composite(BlockPos.STREAM_CODEC, AntmailDeletePayload::pos, ByteBufCodecs.stringUtf8(64), AntmailDeletePayload::messageId, ByteBufCodecs.BOOL, AntmailDeletePayload::sent, AntmailDeletePayload::new);
@Override public Type<AntmailDeletePayload> type() { return TYPE; }
}
@@ -0,0 +1,17 @@
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 AntmailDraftPayload(BlockPos pos, int action, String data) implements CustomPacketPayload {
public static final int SAVE = 0;
public static final int DELETE = 1;
public static final Type<AntmailDraftPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "antmail_draft"));
public static final StreamCodec<RegistryFriendlyByteBuf, AntmailDraftPayload> STREAM_CODEC = StreamCodec.composite(BlockPos.STREAM_CODEC, AntmailDraftPayload::pos, ByteBufCodecs.VAR_INT, AntmailDraftPayload::action, ByteBufCodecs.stringUtf8(65536), AntmailDraftPayload::data, AntmailDraftPayload::new);
@Override public Type<AntmailDraftPayload> type() { return TYPE; }
}
@@ -0,0 +1,23 @@
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 AntmailMessageRequestPayload(BlockPos pos, String messageId) implements CustomPacketPayload {
public static final Type<AntmailMessageRequestPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "antmail_message_request"));
public static final StreamCodec<RegistryFriendlyByteBuf, AntmailMessageRequestPayload> STREAM_CODEC = StreamCodec.composite(
BlockPos.STREAM_CODEC, AntmailMessageRequestPayload::pos,
ByteBufCodecs.stringUtf8(64), AntmailMessageRequestPayload::messageId,
AntmailMessageRequestPayload::new
);
@Override
public Type<AntmailMessageRequestPayload> type() {
return TYPE;
}
}
@@ -1,6 +1,7 @@
package com.craisinlord.antarchy.content.network;
import com.craisinlord.antarchy.content.antmail.AntmailAttachment;
import com.craisinlord.antarchy.content.antmail.AntmailDraft;
import com.craisinlord.antarchy.content.antmail.AntmailWire;
import net.minecraft.core.BlockPos;
@@ -23,7 +24,19 @@ public final class AntmailNetworking {
}
public static void requestState(BlockPos pos) {
sender.accept(new AntmailStateRequestPayload(pos));
requestState(pos, AntmailStateRequestPayload.INBOX, 0, 0L);
}
public static void requestState(BlockPos pos, int folder, int page) {
requestState(pos, folder, page, 0L);
}
public static void requestState(BlockPos pos, int folder, int page, long knownVersion) {
sender.accept(new AntmailStateRequestPayload(pos, folder, page, knownVersion));
}
public static void requestMessage(BlockPos pos, UUID messageId) {
sender.accept(new AntmailMessageRequestPayload(pos, messageId.toString()));
}
public static void send(BlockPos pos, String recipient, String subject, String body, List<AntmailAttachment> attachments) {
@@ -37,4 +50,20 @@ public final class AntmailNetworking {
public static void markUnread(BlockPos pos, UUID messageId) {
sender.accept(new AntmailReadPayload(pos, messageId.toString(), false));
}
public static void delete(BlockPos pos, UUID messageId, boolean sent) {
sender.accept(new AntmailDeletePayload(pos, messageId.toString(), sent));
}
public static void saveDraft(BlockPos pos, AntmailDraft draft) {
sender.accept(new AntmailDraftPayload(pos, AntmailDraftPayload.SAVE, AntmailWire.encodeTag(draft.toTag())));
}
public static void deleteDraft(BlockPos pos, UUID draftId) {
sender.accept(new AntmailDraftPayload(pos, AntmailDraftPayload.DELETE, draftId.toString()));
}
public static void retry(BlockPos pos, UUID messageId) {
sender.accept(new AntmailRetryPayload(pos, messageId.toString()));
}
}
@@ -12,8 +12,12 @@ public final class AntmailPayloadTypes {
return List.of(
AntmailSetupPayload.TYPE,
AntmailStateRequestPayload.TYPE,
AntmailMessageRequestPayload.TYPE,
AntmailSendPayload.TYPE,
AntmailReadPayload.TYPE,
AntmailDeletePayload.TYPE,
AntmailDraftPayload.TYPE,
AntmailRetryPayload.TYPE,
AntmailResultPayload.TYPE
);
}
@@ -8,8 +8,26 @@ 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 record AntmailResultPayload(BlockPos pos, int status, String address, String messageId, String detail, String data, long version) 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);
public static final StreamCodec<RegistryFriendlyByteBuf, AntmailResultPayload> STREAM_CODEC = StreamCodec.of(
(buf, payload) -> {
BlockPos.STREAM_CODEC.encode(buf, payload.pos());
ByteBufCodecs.VAR_INT.encode(buf, payload.status());
ByteBufCodecs.stringUtf8(64).encode(buf, payload.address());
ByteBufCodecs.stringUtf8(64).encode(buf, payload.messageId());
ByteBufCodecs.stringUtf8(128).encode(buf, payload.detail());
ByteBufCodecs.stringUtf8(65536).encode(buf, payload.data());
ByteBufCodecs.VAR_LONG.encode(buf, payload.version());
},
buf -> new AntmailResultPayload(
BlockPos.STREAM_CODEC.decode(buf),
ByteBufCodecs.VAR_INT.decode(buf),
ByteBufCodecs.stringUtf8(64).decode(buf),
ByteBufCodecs.stringUtf8(64).decode(buf),
ByteBufCodecs.stringUtf8(128).decode(buf),
ByteBufCodecs.stringUtf8(65536).decode(buf),
ByteBufCodecs.VAR_LONG.decode(buf)
));
@Override public Type<AntmailResultPayload> type() { return TYPE; }
}
@@ -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 AntmailRetryPayload(BlockPos pos, String messageId) implements CustomPacketPayload {
public static final Type<AntmailRetryPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "antmail_retry"));
public static final StreamCodec<RegistryFriendlyByteBuf, AntmailRetryPayload> STREAM_CODEC = StreamCodec.composite(BlockPos.STREAM_CODEC, AntmailRetryPayload::pos, ByteBufCodecs.stringUtf8(64), AntmailRetryPayload::messageId, AntmailRetryPayload::new);
@Override public Type<AntmailRetryPayload> type() { return TYPE; }
}
@@ -3,6 +3,7 @@ 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.AntmailDraft;
import com.craisinlord.antarchy.content.antmail.AntmailMessage;
import com.craisinlord.antarchy.content.antmail.AntmailMailbox;
import com.craisinlord.antarchy.content.antmail.AntmailServerData;
@@ -35,7 +36,8 @@ public final class AntmailServerHandler {
}
AntmailServerData data = AntmailServerData.access(player.server);
AntmailServerData.Registration registration = data.register(player.server, player.serverLevel(), payload.pos(), payload.username());
String mailbox = registration.valid() ? AntmailWire.encodeTag(data.mailboxOrCreate(registration.address()).toTag()) : "";
if (registration.valid()) data.setLastUsedAddress(player, registration.address());
String mailbox = registration.valid() ? AntmailWire.encodeTag(data.mailboxOrCreate(registration.address()).toTag(false)) : "";
String detail = registration.valid() ? "registration_success" : "registration_failed:" + registration.reason();
result(player, payload.pos(), registration.valid() ? AntmailDeliveryResult.Status.DELIVERED.ordinal() : AntmailDeliveryResult.Status.FAILED.ordinal(), registration.valid() ? registration.address().fullAddress() : "", "", detail, mailbox);
}
@@ -47,13 +49,48 @@ public final class AntmailServerHandler {
return;
}
AntmailServerData data = AntmailServerData.access(player.server);
data.drainAddress(player.server, player.serverLevel(), payload.pos());
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()));
if (payload.knownVersion() == data.mailboxVersion(address)) {
result(player, payload.pos(), AntmailDeliveryResult.Status.DELIVERED.ordinal(), address.fullAddress(), "", "unchanged", "");
return;
}
int folder = payload.folder() < AntmailStateRequestPayload.INBOX || payload.folder() > AntmailStateRequestPayload.DRAFTS
? AntmailStateRequestPayload.INBOX : payload.folder();
int page = Math.max(0, Math.min(payload.page(), AntmailValidation.MAX_MAILBOX_MESSAGES / AntmailMailbox.PAGE_SIZE));
result(player, payload.pos(), AntmailDeliveryResult.Status.DELIVERED.ordinal(), address.fullAddress(), "", "", AntmailWire.encodeTag(mailbox.toPageTag(folder, page)));
}
public static void handle(ServerPlayer player, AntmailMessageRequestPayload 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);
AntmailMessage message = mailbox.findInbox(id);
if (message == null) message = mailbox.findSent(id);
if (message == null) {
result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), address.fullAddress(), payload.messageId(), "not_found", "");
return;
}
result(player, payload.pos(), AntmailDeliveryResult.Status.DELIVERED.ordinal(), address.fullAddress(), payload.messageId(), "message_detail", AntmailWire.encodeTag(message.toTag()));
} catch (IllegalArgumentException exception) {
result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), address.fullAddress(), payload.messageId(), "invalid_id", "");
}
}
public static void handle(ServerPlayer player, AntmailSendPayload payload) {
@@ -73,7 +110,7 @@ public final class AntmailServerHandler {
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(), sender.fullAddress(), message.id().toString(), delivery.detail(), AntmailWire.encodeTag(data.mailboxOrCreate(sender).toTag()));
result(player, payload.pos(), delivery.status().ordinal(), sender.fullAddress(), message.id().toString(), delivery.detail(), AntmailWire.encodeTag(data.mailboxOrCreate(sender).toTag(false)));
} catch (RuntimeException exception) {
result(player, payload.pos(), AntmailDeliveryResult.Status.MESSAGE_INVALID.ordinal(), "", "", "invalid_message", "");
}
@@ -95,13 +132,87 @@ public final class AntmailServerHandler {
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", AntmailWire.encodeTag(mailbox.toTag()));
if (changed) data.touchMailbox(address);
result(player, payload.pos(), changed ? AntmailDeliveryResult.Status.DELIVERED.ordinal() : AntmailDeliveryResult.Status.FAILED.ordinal(), address.fullAddress(), payload.messageId(), changed ? "" : "not_found", AntmailWire.encodeTag(mailbox.toTag(false)));
} catch (IllegalArgumentException exception) {
result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), address.fullAddress(), payload.messageId(), "invalid_id", "");
}
}
public static void handle(ServerPlayer player, AntmailDeletePayload 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.sent() ? mailbox.removeSent(id) : mailbox.removeInbox(id);
if (changed) data.touchMailbox(address);
result(player, payload.pos(), changed ? AntmailDeliveryResult.Status.DELIVERED.ordinal() : AntmailDeliveryResult.Status.FAILED.ordinal(), address.fullAddress(), payload.messageId(), changed ? "" : "not_found", AntmailWire.encodeTag(mailbox.toTag(false)));
} catch (IllegalArgumentException exception) {
result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), address.fullAddress(), payload.messageId(), "invalid_id", "");
}
}
public static void handle(ServerPlayer player, AntmailDraftPayload 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);
try {
if (payload.action() == AntmailDraftPayload.SAVE) {
AntmailDraft draft = AntmailDraft.fromTag(AntmailWire.decodeTag(payload.data()));
AntmailDraft stored = new AntmailDraft(draft.id(), address, draft.recipient(), draft.subject(), draft.body(), draft.attachments());
if (!mailbox.saveDraft(stored)) throw new IllegalArgumentException("draft_limit");
} else if (payload.action() == AntmailDraftPayload.DELETE) {
if (!mailbox.removeDraft(UUID.fromString(payload.data()))) throw new IllegalArgumentException("not_found");
} else {
throw new IllegalArgumentException("invalid_action");
}
data.touchMailbox(address);
result(player, payload.pos(), AntmailDeliveryResult.Status.DELIVERED.ordinal(), address.fullAddress(), "", "draft_saved", AntmailWire.encodeTag(mailbox.toTag(false)));
} catch (RuntimeException exception) {
result(player, payload.pos(), AntmailDeliveryResult.Status.MESSAGE_INVALID.ordinal(), address.fullAddress(), "", "draft_failed", AntmailWire.encodeTag(mailbox.toTag(false)));
}
}
public static void handle(ServerPlayer player, AntmailRetryPayload 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 sender = data.addressAt(player.serverLevel().dimension().location(), payload.pos());
if (sender == null) {
result(player, payload.pos(), AntmailDeliveryResult.Status.ADDRESS_NOT_FOUND.ordinal(), "", payload.messageId(), "unconfigured", "");
return;
}
try {
AntmailDeliveryResult delivery = data.retry(player.server, sender, UUID.fromString(payload.messageId()));
AntmailMailbox mailbox = data.mailboxOrCreate(sender);
result(player, payload.pos(), delivery == null ? AntmailDeliveryResult.Status.MESSAGE_INVALID.ordinal() : delivery.status().ordinal(), sender.fullAddress(), payload.messageId(), delivery == null ? "not_found" : delivery.detail(), AntmailWire.encodeTag(mailbox.toTag(false)));
} catch (IllegalArgumentException exception) {
result(player, payload.pos(), AntmailDeliveryResult.Status.MESSAGE_INVALID.ordinal(), sender.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;
@@ -124,6 +235,11 @@ public final class AntmailServerHandler {
}
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));
long version = 0L;
try {
if (!address.isBlank()) version = AntmailServerData.access(player.server).mailboxVersion(AntmailAddress.parse(address));
} catch (IllegalArgumentException ignored) {
}
resultSender.accept(player, new AntmailResultPayload(pos, status, address, messageId, detail, data, version));
}
}
@@ -3,12 +3,21 @@ 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 AntmailStateRequestPayload(BlockPos pos) implements CustomPacketPayload {
public record AntmailStateRequestPayload(BlockPos pos, int folder, int page, long knownVersion) implements CustomPacketPayload {
public static final int INBOX = 0;
public static final int SENT = 1;
public static final int DRAFTS = 2;
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);
public static final StreamCodec<RegistryFriendlyByteBuf, AntmailStateRequestPayload> STREAM_CODEC = StreamCodec.composite(
BlockPos.STREAM_CODEC, AntmailStateRequestPayload::pos,
ByteBufCodecs.VAR_INT, AntmailStateRequestPayload::folder,
ByteBufCodecs.VAR_INT, AntmailStateRequestPayload::page,
ByteBufCodecs.VAR_LONG, AntmailStateRequestPayload::knownVersion,
AntmailStateRequestPayload::new);
@Override public Type<AntmailStateRequestPayload> type() { return TYPE; }
}
@@ -6,7 +6,12 @@ import com.craisinlord.antarchy.content.computer.terminal.TerminalCommandService
import com.craisinlord.antarchy.content.computer.terminal.TerminalFileSystem;
import com.craisinlord.antarchy.content.computer.terminal.TerminalResult;
import com.craisinlord.antarchy.content.antmail.AntmailWire;
import com.craisinlord.antarchy.content.computer.blockle.BlockleAnswers;
import com.craisinlord.antarchy.content.computer.blockle.BlockleDictionary;
import com.craisinlord.antarchy.content.computer.blockle.BlockleGame;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.BlockEntity;
@@ -59,6 +64,10 @@ public final class ComputerAccessHandler {
case ComputerAccessPayload.TERMINAL_COMMAND -> terminalCommand(player, computer, payload);
case ComputerAccessPayload.DESKTOP_STATE -> desktopState(player, computer, payload);
case ComputerAccessPayload.DESKTOP_WALLPAPER -> desktopWallpaper(player, computer, payload);
case ComputerAccessPayload.BASILISK_STATE -> basiliskState(player, computer, payload);
case ComputerAccessPayload.ANTMAN_STATE -> antmanState(player, computer, payload);
case ComputerAccessPayload.BLOCKLE_STATE -> blockleState(player, computer, payload, false);
case ComputerAccessPayload.BLOCKLE_GUESS -> blockleState(player, computer, payload, true);
default -> send(player, payload, ComputerAccessResultPayload.INVALID);
}
}
@@ -91,6 +100,10 @@ public final class ComputerAccessHandler {
if (computer.hasActiveUser() && !computer.isAuthenticatedBy(player)) {
send(player, payload, ComputerAccessResultPayload.BUSY);
} else if (computer.authenticate(player, payload.value())) {
com.craisinlord.antarchy.content.antmail.AntmailServerData data = com.craisinlord.antarchy.content.antmail.AntmailServerData.access(player.server);
net.minecraft.resources.ResourceLocation dimension = player.serverLevel().dimension().location();
com.craisinlord.antarchy.content.antmail.AntmailAddress address = data.addressAt(dimension, computer.getBlockPos());
if (address != null) data.setLastUsedAddress(player, address);
send(player, payload, ComputerAccessResultPayload.SUCCESS);
} else {
send(player, payload, ComputerAccessResultPayload.INVALID_PASSWORD);
@@ -128,8 +141,8 @@ public final class ComputerAccessHandler {
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 if (file.type() != ComputerFileSystem.ComputerFile.Type.TEXT && file.type() != ComputerFileSystem.ComputerFile.Type.IMAGE) {
sendFile(player, payload, false, "", "not_file");
} else {
sendFile(player, payload, true, file.path() + "\0" + file.contents(), "");
}
@@ -269,6 +282,91 @@ public final class ComputerAccessHandler {
}
}
private static void basiliskState(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
if (!computer.canUseFileSystem(player)) {
sendFile(player, payload, false, "", "unauthorized");
return;
}
if (!payload.value().isBlank()) {
try {
computer.setBasiliskScore(Integer.parseInt(payload.value()));
} catch (NumberFormatException ignored) {
sendFile(player, payload, false, "", "invalid_score");
return;
}
}
sendFile(player, payload, true, computer.basiliskScore() + "\0" + computer.basiliskHighScore(), "");
}
private static void antmanState(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
if (!computer.canUseFileSystem(player)) {
sendFile(player, payload, false, "", "unauthorized");
return;
}
if (!payload.value().isBlank()) {
try {
computer.setAntmanScore(Integer.parseInt(payload.value()));
} catch (NumberFormatException ignored) {
sendFile(player, payload, false, "", "invalid_score");
return;
}
}
sendFile(player, payload, true, computer.antmanScore() + "\0" + computer.antmanHighScore(), "");
}
private static void blockleState(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload, boolean submit) {
if (!computer.canUseFileSystem(player)) {
sendFile(player, payload, false, "", "unauthorized");
return;
}
ResourceLocation disk = ResourceLocation.fromNamespaceAndPath("antarchy", "blockle_game");
if (!computer.diskIds().contains(disk)) {
sendFile(player, payload, false, "", "not_installed");
return;
}
ServerLevel overworld = player.server.getLevel(Level.OVERWORLD);
if (overworld == null) {
sendFile(player, payload, false, "", "overworld_unavailable");
return;
}
long day = overworld.getDayTime() / 24000L;
computer.resetBlockle(day);
BlockleAnswers.Answer answer = BlockleAnswers.answerForDay(day);
if (submit) {
String guess = payload.value() == null ? "" : payload.value().toLowerCase(java.util.Locale.ROOT);
if (guess.length() != 5 || !guess.chars().allMatch(value -> value >= 'a' && value <= 'z')) {
sendFile(player, payload, false, "", "invalid_word");
return;
}
if (!BlockleDictionary.contains(guess)) {
sendFile(player, payload, false, "", "invalid_word");
return;
}
if (computer.blockleGuesses().size() >= 6 || computer.blockleGuesses().stream().anyMatch(value -> value.equals(answer.word()))) {
sendFile(player, payload, false, "", "game_over");
return;
}
computer.addBlockleGuess(guess);
}
sendFile(player, payload, true, encodeBlockle(day, answer, computer.blockleGuesses()), "");
}
private static String encodeBlockle(long day, BlockleAnswers.Answer answer, java.util.List<String> guesses) {
StringBuilder result = new StringBuilder(Long.toString(day));
boolean solved = false;
for (String guess : guesses) {
String colors = BlockleGame.evaluate(answer.word(), guess);
if (guess.equals(answer.word())) solved = true;
result.append('|').append(guess).append(',').append(colors);
}
if (solved || guesses.size() >= 6) {
result.append('|').append(solved ? "SOLVED" : "FAILED").append('|').append(answer.word()).append('|').append(answer.itemId());
} else {
result.append('|').append("PLAYING");
}
return result.toString();
}
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();
@@ -25,6 +25,10 @@ public record ComputerAccessPayload(BlockPos pos, int action, String value) impl
public static final int FILE_DELETE = 13;
public static final int FILE_MOVE = 14;
public static final int TERMINAL_COMMAND = 15;
public static final int BASILISK_STATE = 16;
public static final int ANTMAN_STATE = 17;
public static final int BLOCKLE_STATE = 18;
public static final int BLOCKLE_GUESS = 19;
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,
@@ -79,4 +79,28 @@ public final class ComputerNetworking {
public static void selectWallpaper(BlockPos pos, ResourceLocation id) {
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.DESKTOP_WALLPAPER, id.toString()));
}
public static void requestBasiliskState(BlockPos pos) {
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.BASILISK_STATE));
}
public static void saveBasiliskScore(BlockPos pos, int score) {
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.BASILISK_STATE, Integer.toString(Math.max(0, score))));
}
public static void requestAntmanState(BlockPos pos) {
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.ANTMAN_STATE));
}
public static void saveAntmanScore(BlockPos pos, int score) {
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.ANTMAN_STATE, Integer.toString(Math.max(0, score))));
}
public static void requestBlockleState(BlockPos pos) {
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.BLOCKLE_STATE));
}
public static void submitBlockleGuess(BlockPos pos, String guess) {
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.BLOCKLE_GUESS, guess));
}
}
@@ -0,0 +1,125 @@
package com.craisinlord.antarchy.content.worldgen;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.mixins.PoiTypesAccessor;
import com.craisinlord.integrated_api.mixins.structures.StructurePoolAccessor;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.core.registries.BuiltInRegistries;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/** Applies Antarchy-owned additions to existing vanilla jigsaw pools without replacing them. */
public final class VillagePoolAdditions {
private static final Gson GSON = new Gson();
private static final String DIRECTORY = "structure_pool_additions";
private VillagePoolAdditions() {
}
public static void apply(MinecraftServer server) {
registerComputerPoiStates(server);
Registry<StructureTemplatePool> pools = server.registryAccess().registryOrThrow(Registries.TEMPLATE_POOL);
Registry<StructureProcessorList> processors = server.registryAccess().registryOrThrow(Registries.PROCESSOR_LIST);
for (Map.Entry<ResourceLocation, Resource> entry : server.getResourceManager()
.listResources(DIRECTORY, path -> path.getPath().endsWith(".json")).entrySet()) {
try (java.io.Reader reader = entry.getValue().openAsReader()) {
JsonObject definition = GSON.fromJson(reader, JsonObject.class);
addToPool(pools, processors, definition, entry.getKey());
} catch (IOException | RuntimeException exception) {
Antarchy.LOGGER.error("Failed to apply structure pool additions from {}", entry.getKey(), exception);
}
}
}
private static void registerComputerPoiStates(MinecraftServer server) {
ResourceLocation poiId = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "computer");
Registry<net.minecraft.world.entity.ai.village.poi.PoiType> poiRegistry =
server.registryAccess().registryOrThrow(Registries.POINT_OF_INTEREST_TYPE);
Holder<net.minecraft.world.entity.ai.village.poi.PoiType> poi = poiRegistry.getHolderOrThrow(
ResourceKey.create(Registries.POINT_OF_INTEREST_TYPE, poiId));
Map<BlockState, Holder<net.minecraft.world.entity.ai.village.poi.PoiType>> stateMap =
PoiTypesAccessor.antarchy$getTypeByState();
for (BlockState state : AntarchyObjects.COMPUTER.get().getStateDefinition().getPossibleStates()) {
stateMap.put(state, poi);
}
}
private static void addToPool(
Registry<StructureTemplatePool> pools,
Registry<StructureProcessorList> processors,
JsonObject definition,
ResourceLocation source
) {
ResourceLocation poolId = ResourceLocation.parse(definition.get("pool").getAsString());
StructureTemplatePool pool = pools.get(poolId);
if (pool == null) {
Antarchy.LOGGER.warn("Skipping structure pool addition {} because pool {} does not exist", source, poolId);
return;
}
JsonObject elementDefinition = definition.getAsJsonObject("element");
ResourceLocation location = ResourceLocation.parse(elementDefinition.get("location").getAsString());
ResourceLocation processorId = ResourceLocation.parse(stringValue(
elementDefinition, "processors", "minecraft:empty"));
Holder<StructureProcessorList> processor = processors.getHolderOrThrow(
ResourceKey.create(Registries.PROCESSOR_LIST, processorId));
StructureTemplatePool.Projection projection = projection(stringValue(
elementDefinition, "projection", "rigid"));
int weight = definition.has("weight") ? definition.get("weight").getAsInt() : 1;
if (weight < 1) {
throw new IllegalArgumentException("weight must be positive");
}
StructurePoolElement poolElement = StructurePoolElement
.legacy(location.toString(), processor)
.apply(projection);
StructurePoolAccessor accessor = (StructurePoolAccessor) pool;
List<Pair<StructurePoolElement, Integer>> templates = new ArrayList<>(accessor.integratedapi_getRawTemplates());
templates.add(Pair.of(poolElement, weight));
accessor.integratedapi_setRawTemplates(templates);
// StructureTemplatePool caches its weighted list in addition to the raw entries.
// Keep both views synchronized so the new house is immediately eligible.
ObjectArrayList<StructurePoolElement> expandedTemplates = new ObjectArrayList<>(accessor.integratedapi_getTemplates());
for (int i = 0; i < weight; i++) {
expandedTemplates.add(poolElement);
}
accessor.integratedapi_setTemplates(expandedTemplates);
}
private static StructureTemplatePool.Projection projection(String value) {
return switch (value) {
case "rigid" -> StructureTemplatePool.Projection.RIGID;
case "terrain_matching" -> StructureTemplatePool.Projection.TERRAIN_MATCHING;
default -> throw new IllegalArgumentException("Unknown structure pool projection: " + value);
};
}
private static JsonElement primitive(String value) {
return new com.google.gson.JsonPrimitive(value);
}
private static String stringValue(JsonObject object, String key, String fallback) {
return object.has(key) ? object.get(key).getAsString() : fallback;
}
}
@@ -0,0 +1,22 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.antmail.AntmailEventData;
import net.minecraft.advancements.AdvancementHolder;
import net.minecraft.server.PlayerAdvancements;
import net.minecraft.server.level.ServerPlayer;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(PlayerAdvancements.class)
public abstract class PlayerAdvancementsMixin {
@Shadow @Final private ServerPlayer player;
@Inject(method = "award", at = @At("RETURN"))
private void antarchy$onAward(AdvancementHolder advancement, String criterion, CallbackInfoReturnable<Boolean> callback) {
if (callback.getReturnValueZ() && player.getAdvancements().getOrStartProgress(advancement).isDone()) AntmailEventData.onAdvancement(player, advancement);
}
}
@@ -0,0 +1,17 @@
package com.craisinlord.antarchy.mixins;
import net.minecraft.core.Holder;
import net.minecraft.world.entity.ai.village.poi.PoiType;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.Map;
@Mixin(net.minecraft.world.entity.ai.village.poi.PoiTypes.class)
public interface PoiTypesAccessor {
@Accessor("TYPE_BY_STATE")
static Map<BlockState, Holder<PoiType>> antarchy$getTypeByState() {
throw new AssertionError();
}
}
@@ -0,0 +1,38 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.ai.village.poi.PoiType;
import net.minecraft.world.entity.ai.village.poi.PoiTypes;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Optional;
@Mixin(PoiTypes.class)
public abstract class PoiTypesMixin {
@Inject(method = "forState", at = @At("HEAD"), cancellable = true)
private static void antarchy$computerPoi(BlockState state,
CallbackInfoReturnable<Optional<Holder<PoiType>>> callback) {
try {
if (!state.is(AntarchyObjects.COMPUTER.get())) {
return;
}
ResourceKey<PoiType> key = ResourceKey.create(
Registries.POINT_OF_INTEREST_TYPE,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "computer"));
BuiltInRegistries.POINT_OF_INTEREST_TYPE.getHolder(key)
.ifPresent(holder -> callback.setReturnValue(Optional.of(holder)));
} catch (IllegalStateException ignored) {
// Registration can be queried during bootstrap before loader bindings finish.
}
}
}
@@ -4,7 +4,7 @@ import com.craisinlord.antarchy.content.fluid.AntarchyFluidChecks;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import com.craisinlord.antarchy.content.portalgun.PortalGunCollisionHelper;
// import com.craisinlord.antarchy.content.portalgun.PortalGunCollisionHelper;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import java.util.List;
@@ -349,12 +349,9 @@ public abstract class EntityMoveMixin {
localMovement, cir.getReturnValue(), this.antarchy$collideWorldMovement, worldResult,
entity.onGround(), entity.maxUpStep(), entity.getBoundingBox());
}
cir.setReturnValue(PortalGunCollisionHelper.resolveCollision(
entity,
entity.getBoundingBox(),
this.antarchy$collideWorldMovement,
worldResult
));
// Portal-gun collision restoration is temporarily disabled. It can
// recurse through Sable's collision redirect and hang the server.
cir.setReturnValue(worldResult);
}
@Unique
@@ -42,13 +42,14 @@
"NightmareSwordDamageMixin",
"NightmareArmorEffectImmunityMixin",
"PotionBrewingMixin",
"PortalGunEntityCollisionMixin",
"PortalGunEntityInWallMixin",
"PoiTypesAccessor",
"PoiTypesMixin",
"RedStoneWireBlockMixin",
"StinkyCrouchMixin",
"VillagerTradesMixin",
"WaterlilyLumenMixin",
"ServerPlayerGameModeMixin",
"PlayerAdvancementsMixin",
"SpongeAntiwaterMixin",
"SquidzookaEnchantMixin",
"SquidzookaMultishotMixin",
@@ -1425,26 +1425,22 @@
,"decree.antarchy.fight_me_coward.subtitle": "Approach the King."
,"decree.antarchy.no_respite": "NO RESPITE"
,"decree.antarchy.no_respite.subtitle": "Healing is sealed."
,"decree.antarchy.no_retreat": "NO RETREAT"
,"decree.antarchy.no_retreat.subtitle": "Do not flee the King's presence."
,"decree.antarchy.keep_your_distance": "KEEP YOUR DISTANCE"
,"decree.antarchy.keep_your_distance.subtitle": "Stay out of the King's reach."
,"decree.antarchy.stand_tall": "STAND TALL"
,"decree.antarchy.stand_tall.subtitle": "Do not crouch before the King."
,"decree.antarchy.stand_your_ground": "STAND YOUR GROUND"
,"decree.antarchy.stand_your_ground.subtitle": "Do not stray from where you stand."
,"decree.antarchy.hands_off_the_crown": "HANDS OFF THE CROWN"
,"decree.antarchy.hands_off_the_crown.subtitle": "Strike the King and be judged."
,"decree.antarchy.advance": "ADVANCE"
,"decree.antarchy.advance.subtitle": "Close on the King or be judged."
,"decree.antarchy.show_no_mercy": "SHOW NO MERCY"
,"decree.antarchy.show_no_mercy.subtitle": "Keep dealing damage."
,"menu.antarchy.royal_judgment": "Royal Judgment"
,"enchantment.antarchy.feather_rising": "Feather Rising"
,"enchantment.antarchy.chronosphere": "Chronosphere"
,"block.antarchy.computer": "Computer"
,"entity.minecraft.villager.antarchy.computer_scientist": "Game Developer"
,"item.antarchy.computer": "Computer"
,"item.antarchy.floppy_disk": "Floppy Disk"
,"item.antarchy.floppy_disk.tooltip": "Put in a computer to find out more"
,"screen.antarchy.computer": "ANTINTOSH OS"
,"guide.antarchy.category.general": "GENERAL"
,"guide.antarchy.category.elythia": "ELYTHIA"
@@ -1457,6 +1453,10 @@
,"guide.antarchy.disk.toreterror_arena.title": "TORETERROR ARENA DATA"
,"guide.antarchy.disk.high_fructose_corn_syrup.title": "CORN SYRUP DATA"
,"guide.antarchy.disk.elythia.title": "ELYTHIA ACCESS RECORD"
,"guide.antarchy.disk.flying_squirrel.title": "FLYING SQUIRREL FIELD NOTES"
,"guide.antarchy.disk.basilisk_game.title": "BASILISK.EXE"
,"guide.antarchy.disk.antman_game.title": "ANTMAN.EXE"
,"guide.antarchy.disk.blockle_game.title": "BLOCKLE"
,"guide.antarchy.disk.cavaryn.title": "CAVARYN ACCESS RECORD"
,"guide.antarchy.disk.thoraxis.title": "THORAXIS ACCESS RECORD"
,"guide.antarchy.disk.temporal_fields.title": "TEMPORAL FIELDS MANUAL"
@@ -1474,6 +1474,9 @@
,"guide.antarchy.entry.elythia.title": "Elythia"
,"guide.antarchy.entry.elythia.subtitle": "Dimension and portal access record"
,"guide.antarchy.entry.elythia.description": "Elythia is reached through an Elythia portal made with a valid frame. A sacrificed Brown Ant activates the portal. The dimension contains oceans, meadows, forests, coral spikes, and other surface biomes."
,"guide.antarchy.entry.flying_squirrel.title": "Flying Squirrel"
,"guide.antarchy.entry.flying_squirrel.subtitle": "Elythia companion record"
,"guide.antarchy.entry.flying_squirrel.description": "You wanna get nuts? Let’s get nuts. Flying squirrels like nuts, trees, and making a dramatic exit. Feed one to befriend it, then give it plenty of canopy to climb through. If you tell it to, it will perch on top of your head. If it falls, do not panic. It may be gliding. If you fall, you have my permission to panic."
,"guide.antarchy.entry.cavaryn.title": "Cavaryn"
,"guide.antarchy.entry.cavaryn.subtitle": "Dimension and portal access record"
,"guide.antarchy.entry.cavaryn.description": "Cavaryn is reached through a Cavaryn portal made with a valid frame. A sacrificed Termite activates the portal. The dimension is a deep network of caves, including Glowcap, Biowart, Moleworm, Wormy, and Brooding Tunnel regions."
@@ -1,7 +1,7 @@
{
"parent": "minecraft:block/cube_bottom_top",
"textures": {
"bottom": "antarchy:block/nyxite",
"bottom": "antarchy:block/typhonite",
"side": "antarchy:block/verdant_nyxium",
"top": "antarchy:block/verdant_nyxium_top"
}
@@ -1,3 +1,232 @@
{
"parent": "builtin/entity"
"format_version": "1.21.11",
"credit": "Made with Blockbench",
"texture_size": [128, 128],
"textures": {
"0": "antarchy/item/computer",
"particle": "antarchy/item/computer"
},
"elements": [
{
"from": [1, 0, 1],
"to": [15, 4, 15],
"rotation": {"angle": 0, "axis": "y", "origin": [8, -1, 9]},
"faces": {
"north": {"uv": [1.75, 1.75, 3.5, 2.25], "texture": "#0"},
"east": {"uv": [0, 1.75, 1.75, 2.25], "texture": "#0"},
"south": {"uv": [5.25, 1.75, 7, 2.25], "texture": "#0"},
"west": {"uv": [3.5, 1.75, 5.25, 2.25], "texture": "#0"},
"up": {"uv": [3.5, 1.75, 1.75, 0], "texture": "#0"},
"down": {"uv": [5.25, 0, 3.5, 1.75], "texture": "#0"}
}
},
{
"from": [11, 2, 0.95],
"to": [14, 3, 0.95],
"rotation": {"angle": 0, "axis": "y", "origin": [8, -1, 8.95]},
"faces": {
"north": {"uv": [12.125, 1.75, 12.5, 1.875], "texture": "#0"},
"east": {"uv": [10.375, 1.75, 10.375, 1.875], "texture": "#0"},
"south": {"uv": [15.625, 1.75, 16, 1.875], "texture": "#0"},
"west": {"uv": [13.875, 1.75, 13.875, 1.875], "texture": "#0"},
"up": {"uv": [12.5, 0, 12.125, 0], "texture": "#0"},
"down": {"uv": [14.25, 0, 13.875, 0], "texture": "#0"}
}
},
{
"from": [10, 1, 13],
"to": [10, 9, 16],
"rotation": {"angle": 0, "axis": "y", "origin": [13, 3, 9]},
"faces": {
"north": {"uv": [1.625, 8.25, 1.625, 9.25], "texture": "#0"},
"east": {"uv": [0, 8.25, 0.375, 9.25], "texture": "#0"},
"south": {"uv": [3.25, 8.25, 3.25, 9.25], "texture": "#0"},
"west": {"uv": [1.625, 8.25, 3.25, 9.25], "texture": "#0"},
"up": {"uv": [1.625, 8.25, 1.625, 6.625], "texture": "#0"},
"down": {"uv": [1.625, 6.625, 1.625, 8.25], "texture": "#0"}
}
},
{
"from": [6, 1, 13],
"to": [6, 9, 16],
"rotation": {"angle": 0, "axis": "y", "origin": [9, 3, 9]},
"faces": {
"north": {"uv": [5.625, 6.375, 5.625, 7.375], "texture": "#0"},
"east": {"uv": [4, 6.375, 4.375, 7.375], "texture": "#0"},
"south": {"uv": [7.25, 6.375, 7.25, 7.375], "texture": "#0"},
"west": {"uv": [5.625, 6.375, 7.25, 7.375], "texture": "#0"},
"up": {"uv": [5.625, 6.375, 5.625, 4.75], "texture": "#0"},
"down": {"uv": [5.625, 4.75, 5.625, 6.375], "texture": "#0"}
}
},
{
"from": [2, 4, 2],
"to": [14, 15, 6],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 3, 8]},
"faces": {
"north": {"uv": [0.5, 5.25, 2, 6.625], "texture": "#0"},
"east": {"uv": [0, 5.25, 0.5, 6.625], "texture": "#0"},
"south": {"uv": [2.5, 5.25, 4, 6.625], "texture": "#0"},
"west": {"uv": [2, 5.25, 2.5, 6.625], "texture": "#0"},
"up": {"uv": [2, 5.25, 0.5, 4.75], "texture": "#0"}
}
},
{
"from": [3, 4, 3],
"to": [13, 14, 13],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 3, 9]},
"faces": {
"north": {"uv": [1.25, 3.5, 2.5, 4.75], "texture": "#0"},
"east": {"uv": [0, 3.5, 1.25, 4.75], "texture": "#0"},
"south": {"uv": [3.75, 3.5, 5, 4.75], "texture": "#0"},
"west": {"uv": [2.5, 3.5, 3.75, 4.75], "texture": "#0"},
"up": {"uv": [2.5, 3.5, 1.25, 2.25], "texture": "#0"},
"down": {"uv": [3.75, 2.25, 2.5, 3.5], "texture": "#0"}
}
},
{
"from": [2.5, 3.9, 2.4],
"to": [13.5, 14.9, 2.4],
"rotation": {"angle": 0, "axis": "y", "origin": [7.5, 2.9, 8.4]},
"faces": {
"north": {"uv": [9.625, 4.125, 11.75, 6.25], "texture": "#0"},
"east": {"uv": [5, 2.25, 5, 3.625], "texture": "#0"},
"south": {"uv": [6.375, 2.25, 7.75, 3.625], "texture": "#0"},
"west": {"uv": [6.375, 2.25, 6.375, 3.625], "texture": "#0"},
"up": {"uv": [6.375, 2.25, 5, 2.25], "texture": "#0"},
"down": {"uv": [7.75, 2.25, 6.375, 2.25], "texture": "#0"}
}
},
{
"from": [2.5, 3.9, 2.35],
"to": [13.5, 14.9, 2.35],
"rotation": {"angle": 0, "axis": "y", "origin": [7.5, 2.9, 8.35]},
"faces": {
"north": {"uv": [12.25, 3.9375, 14.375, 6.0625], "texture": "#0"},
"east": {"uv": [5, 2.25, 5, 3.625], "texture": "#0"},
"south": {"uv": [6.375, 2.25, 7.75, 3.625], "texture": "#0"},
"west": {"uv": [6.375, 2.25, 6.375, 3.625], "texture": "#0"},
"up": {"uv": [6.375, 2.25, 5, 2.25], "texture": "#0"},
"down": {"uv": [7.75, 2.25, 6.375, 2.25], "texture": "#0"}
}
},
{
"from": [2.5, 3.9, 2.6],
"to": [13.5, 14.9, 2.6],
"rotation": {"angle": 0, "axis": "y", "origin": [7.5, 2.9, 8.6]},
"faces": {
"north": {"uv": [9.625, 6.25, 11.75, 8.375], "texture": "#0"},
"east": {"uv": [7.25, 3.625, 7.25, 5], "texture": "#0"},
"south": {"uv": [7.75, 5.75, 9.125, 7.125], "texture": "#0"},
"west": {"uv": [8.625, 3.625, 8.625, 5], "texture": "#0"},
"up": {"uv": [8.625, 3.625, 7.25, 3.625], "texture": "#0"},
"down": {"uv": [10, 3.625, 8.625, 3.625], "texture": "#0"}
}
},
{
"from": [2.5, 3.9, 2.45],
"to": [13.5, 14.9, 2.45],
"rotation": {"angle": 0, "axis": "y", "origin": [7.5, 2.9, 8.45]},
"faces": {
"north": {"uv": [7, 0, 8.375, 1.375], "texture": "#0"},
"east": {"uv": [7, 0, 7, 1.375], "texture": "#0"},
"south": {"uv": [8.375, 0, 9.75, 1.375], "texture": "#0"},
"west": {"uv": [8.375, 0, 8.375, 1.375], "texture": "#0"},
"up": {"uv": [8.375, 0, 7, 0], "texture": "#0"},
"down": {"uv": [9.75, 0, 8.375, 0], "texture": "#0"}
}
}
],
"groups": [
{
"name": "computer",
"origin": [13, 3, 9],
"scope": 0,
"color": 0,
"children": [
0,
{
"name": "computer_on",
"origin": [8, -1, 8.95],
"scope": 0,
"color": 0,
"children": [1]
},
{
"name": "cables",
"origin": [13, 3, 9],
"scope": 0,
"color": 0,
"children": [2, 3]
},
{
"name": "monitor",
"origin": [8, 3, 8],
"scope": 0,
"color": 0,
"children": [
4,
5,
{
"name": "screens",
"origin": [7.5, 2.9, 8.5],
"scope": 0,
"color": 0,
"children": [
{
"name": "on",
"origin": [8, 9.4, 2.375],
"scope": 0,
"color": 0,
"children": [6, 7]
},
{
"name": "off",
"origin": [8, 9.4, 2.6],
"scope": 0,
"color": 0,
"children": [8]
},
{
"name": "white_flash",
"origin": [8, 9.4, 2.45],
"scope": 0,
"color": 0,
"children": [9]
}
]
}
]
}
]
}
],
"display": {
"thirdperson_righthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"thirdperson_lefthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"firstperson_righthand": {
"rotation": [0, 45, 0],
"scale": [0.4, 0.4, 0.4]
},
"firstperson_lefthand": {
"rotation": [0, 225, 0],
"scale": [0.4, 0.4, 0.4]
},
"gui": {
"rotation": [30, 225, 0],
"scale": [0.625, 0.625, 0.625]
},
"fixed": {
"rotation": [0, 180, 0],
"scale": [0.5, 0.5, 0.5]
}
}
}
@@ -2,5 +2,19 @@
"parent": "minecraft:item/generated",
"textures": {
"layer0": "antarchy:item/floppy_disk/floppy_disk"
}
},
"overrides": [
{
"predicate": {"antarchy:disk_category": 1.0},
"model": "antarchy:item/floppy_disk/elythia_floppy_disk"
},
{
"predicate": {"antarchy:disk_category": 2.0},
"model": "antarchy:item/floppy_disk/cavaryn_floppy_disk"
},
{
"predicate": {"antarchy:disk_category": 3.0},
"model": "antarchy:item/floppy_disk/thoraxis_floppy_disk"
}
]
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "antarchy:item/floppy_disk/cavaryn_floppy_disk"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "antarchy:item/floppy_disk/elythia_floppy_disk"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "antarchy:item/floppy_disk/thoraxis_floppy_disk"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 861 B

@@ -0,0 +1,5 @@
{
"villager": {
"hat": "full"
}
}
@@ -0,0 +1,9 @@
{
"sender": "bloxiclean@antmail.com",
"subject": "IS YOUR HIVE QUEEN DUSTY?",
"body": "Bloxiclean now offers premium royal-area cleaning. We remove ash, ichor, bloodglass dust, and other difficult stains. Queen not included. Prices may vary by dimension.",
"trigger": {
"type": "random",
"value": 0.01
}
}
@@ -0,0 +1,9 @@
{
"sender": "bloxiclean@antmail.com",
"subject": "BLOXICLEAN: WE MAKE BLOCKS SPARKLE",
"body": "Cobwebs? Sculk? Suspicious residue? Bloxiclean removes nearly anything from nearly anywhere. Book your complimentary pressure-wash before the stains become lore.",
"trigger": {
"type": "random",
"value": 0.01
}
}
@@ -0,0 +1,9 @@
{
"sender": "coldcraft@antmail.com",
"subject": "COLDCRAFT CITY: YOUR NEXT DESTINATION",
"body": "Visit ColdCraft City! See Big Ben, go shopping, and ride the Ferris wheel. Bring a coat, some spending money, and a plan for getting home.",
"trigger": {
"type": "random",
"value": 0.01
}
}
@@ -0,0 +1,9 @@
{
"sender": "dicaprios@antmail.com",
"subject": "A TASTE OF ITALY AWAITS",
"body": "DiCaprio's is now serving handmade pasta, suspiciously expensive bread, and a tiramisu that may know your secrets. Reserve a table today. Formal armor encouraged.",
"trigger": {
"type": "random",
"value": 0.01
}
}
@@ -0,0 +1,9 @@
{
"sender": "dr_traeoris@antmail.com",
"subject": "WELCOME TO ANTMAIL",
"body": "Your address has been registered. If this message arrived late, the network probably had ants in it.",
"trigger": {
"type": "advancement",
"value": "minecraft:story/root"
}
}
@@ -0,0 +1,4 @@
{
"title": "guide.antarchy.disk.antman_game.title",
"entries": []
}
@@ -0,0 +1,5 @@
{
"category": "elythia",
"title": "guide.antarchy.disk.basilisk_game.title",
"entries": []
}
@@ -0,0 +1,4 @@
{
"title": "guide.antarchy.disk.blockle_game.title",
"entries": []
}
@@ -0,0 +1,5 @@
{
"category": "elythia",
"title": "guide.antarchy.disk.flying_squirrel.title",
"entries": ["antarchy:flying_squirrel"]
}
@@ -0,0 +1,8 @@
{
"type": "mob",
"category": "elythia",
"title": "guide.antarchy.entry.flying_squirrel.title",
"subtitle": "guide.antarchy.entry.flying_squirrel.subtitle",
"description": ["guide.antarchy.entry.flying_squirrel.description"],
"entity": "antarchy:flying_squirrel"
}
@@ -0,0 +1,20 @@
{
"trades": [
{"level": 1, "index": 0, "buy": {"item": "minecraft:redstone", "min": 12, "max": 16}, "sell": {"item": "minecraft:emerald", "min": 1, "max": 2}},
{"level": 1, "index": 1, "buy": {"item": "antarchy:bluestone", "min": 6, "max": 10}, "sell": {"item": "minecraft:emerald", "min": 1, "max": 2}},
{"level": 1, "index": 2, "buy": {"item": "minecraft:emerald", "min": 2, "max": 4}, "sell": {"item": "antarchy:duct_tape", "min": 1, "max": 2}},
{"level": 1, "index": 3, "buy": {"item": "antarchy:floppy_disk", "min": 1, "max": 1}, "sell": {"item": "minecraft:emerald", "min": 4, "max": 8}},
{"level": 2, "index": 0, "buy": {"item": "minecraft:emerald", "min": 4, "max": 8}, "sell": {"item": "antarchy:floppy_disk", "min": 1, "max": 1}, "sell_disk": "random_general"},
{"level": 2, "index": 1, "buy": {"item": "antarchy:floppy_disk", "min": 12, "max": 20}, "sell": {"item": "antarchy:floppy_disk", "min": 1, "max": 1}, "sell_disk": "antarchy:toreterror_arena"},
{"level": 3, "index": 0, "buy": {"item": "antarchy:vortex_charge", "min": 1, "max": 1}, "sell": {"item": "minecraft:emerald", "min": 10, "max": 10}},
{"level": 3, "index": 1, "buy": {"item": "antarchy:flytrap_goo", "min": 1, "max": 1}, "sell": {"item": "minecraft:emerald", "min": 10, "max": 10}},
{"level": 4, "index": 0, "buy": {"item": "minecraft:emerald", "min": 8, "max": 16}, "sell": {"item": "minecraft:comparator", "min": 1, "max": 2}},
{"level": 4, "index": 1, "buy": {"item": "minecraft:emerald", "min": 12, "max": 20}, "sell": {"item": "antarchy:bluestone_comparator", "min": 1, "max": 2}},
{"level": 4, "index": 2, "buy": {"item": "antarchy:antimetal", "min": 1, "max": 2}, "sell": {"item": "minecraft:emerald", "min": 8, "max": 16}},
{"level": 5, "index": 0, "buy": {"item": "minecraft:emerald", "min": 32, "max": 64}, "sell": {"item": "antarchy:uranium_nugget", "min": 1, "max": 1}},
{"level": 5, "index": 1, "buy": {"item": "minecraft:emerald", "min": 32, "max": 64}, "sell": {"item": "antarchy:titanium_nugget", "min": 1, "max": 1}},
{"level": 5, "index": 2, "buy": {"item": "minecraft:emerald", "min": 16, "max": 32}, "sell": {"item": "antarchy:rainbow_sugar", "min": 1, "max": 2}},
{"level": 5, "index": 3, "buy": {"item": "minecraft:emerald", "min": 24, "max": 40}, "sell": {"item": "antarchy:floppy_disk", "min": 1, "max": 1}, "sell_disk": "antarchy:antman_game", "max_uses": 1, "chance": 0.35},
{"level": 5, "index": 4, "buy": {"item": "minecraft:emerald", "min": 24, "max": 40}, "sell": {"item": "antarchy:floppy_disk", "min": 1, "max": 1}, "sell_disk": "antarchy:blockle_game", "max_uses": 1, "chance": 0.35}
]
}
@@ -34,6 +34,19 @@
}
]
},
{
"type": "minecraft:item",
"name": "antarchy:floppy_disk",
"weight": 2,
"functions": [
{
"function": "minecraft:set_components",
"components": {
"antarchy:floppy_disk": "antarchy:basilisk_game"
}
}
]
},
{
"type": "minecraft:item",
"name": "minecraft:diamond",
@@ -30,6 +30,19 @@
}
]
},
{
"type": "minecraft:item",
"name": "antarchy:floppy_disk",
"weight": 4,
"functions": [
{
"function": "minecraft:set_components",
"components": {
"antarchy:floppy_disk": "antarchy:flying_squirrel"
}
}
]
},
{
"type": "minecraft:item",
"name": "antarchy:floppy_disk",
@@ -42,6 +55,19 @@
}
}
]
},
{
"type": "minecraft:item",
"name": "antarchy:floppy_disk",
"weight": 1,
"functions": [
{
"function": "minecraft:set_components",
"components": {
"antarchy:floppy_disk": "antarchy:basilisk_game"
}
}
]
}
]
}
@@ -0,0 +1,9 @@
{
"pool": "minecraft:village/plains/houses",
"element": {
"location": "antarchy:village/computer_scientist_house",
"processors": "minecraft:empty",
"projection": "rigid"
},
"weight": 1
}
@@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"antarchy:computer"
]
}