feat: 移植1.20的实体加速器

This commit is contained in:
C-H716 2025-09-21 01:57:28 +08:00
parent 3968b26217
commit 10afb16acb
49 changed files with 1668 additions and 35 deletions

View File

@ -159,7 +159,10 @@ dependencies {
runtimeOnly "de.mari_023:ae2wtlib:19.2.1"
runtimeOnly "curse.maven:jade-324717:5427817"
runtimeOnly "curse.maven:mega-cells-622112:6005043"
// runtimeOnly "mekanism:Mekanism:1.21.1-10.7.0.55"
runtimeOnly "curse.maven:mekanism-268560:6895130"
runtimeOnly "curse.maven:applied-mekanistics-574300:5978711"
runtimeOnly "curse.maven:ex-pattern-provider-892005:6863556"
//aea

View File

@ -1,10 +1,12 @@
package com.extendedae_plus;
import appeng.api.parts.IPart;
import appeng.api.parts.PartModels;
import appeng.block.AEBaseEntityBlock;
import appeng.blockentity.crafting.CraftingBlockEntity;
import appeng.items.parts.PartModelsHelper;
import com.extendedae_plus.config.ModConfigs;
import com.extendedae_plus.init.*;
import com.extendedae_plus.network.ModNetwork;
import com.mojang.logging.LogUtils;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
@ -69,6 +71,16 @@ public class ExtendedAEPlus {
// 绑定 AE2 CraftingBlockEntity 到本模组的自定义加速器方块避免 AEBaseEntityBlock.blockEntityType 为空
event.enqueueWork(() -> {
try {
// 注册升级卡
new UpgradeCards(event);
// PartItem 注册 AE2 部件模型
PartModels.registerModels(
PartModelsHelper.createModels(
ModItems.ENTITY_TICKER_PART_ITEM.get().getPartClass().asSubclass(IPart.class)
)
);
// 注册自定义 AE2 MenuLocator用于 Curios 槽位打开菜单
try {
appeng.menu.locator.MenuLocators.register(

View File

@ -0,0 +1,80 @@
package com.extendedae_plus.ae.definitions.upgrades;
import appeng.items.materials.UpgradeCardItem;
import com.extendedae_plus.init.ModItems;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.component.CustomData;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* 单一的实体加速卡 Item通过 ItemStack NBT 存储 exponent0/1/2/3来区分等级
*/
public class EntitySpeedCardItem extends UpgradeCardItem {
public static final String NBT_MULTIPLIER = "EAS:mult";
public EntitySpeedCardItem(Properties props) {
super(props);
}
public static ItemStack withMultiplier(int multiplier) {
ItemStack stack = new ItemStack(ModItems.ENTITY_SPEED_CARD.get());
CompoundTag tag = new CompoundTag();
tag.putInt(NBT_MULTIPLIER, multiplier);
stack.set(DataComponents.CUSTOM_DATA, CustomData.of(tag));
return stack;
}
public static int readMultiplier(ItemStack stack) {
if (stack == null || stack.isEmpty()) return 1;
CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
if (customData == null || !customData.copyTag().contains(NBT_MULTIPLIER)) return 1;
return customData.copyTag().getInt(NBT_MULTIPLIER);
}
@Override
public @NotNull Component getName(@NotNull ItemStack stack) {
int mult = readMultiplier(stack);
String key;
switch (mult) {
case 2 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x2";
case 4 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x4";
case 8 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x8";
case 16 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x16";
default -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x1";
}
return Component.translatable(key);
}
public List<Component> getTooltipLines(ItemStack stack) {
int mult = readMultiplier(stack);
long cap = 1L;
switch (mult) {
case 16 -> cap = 1024L;
case 8 -> cap = 256L;
case 4 -> cap = 64L;
case 2 -> cap = 8L;
}
MutableComponent line1 = Component.translatable("tooltip." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.multiplier", "x" + mult);
MutableComponent line2 = Component.translatable("tooltip." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.max", cap);
return List.of(line1, line2);
}
@OnlyIn(Dist.CLIENT)
@Override
public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> lines, TooltipFlag advancedTooltips) {
super.appendHoverText(stack, context, lines, advancedTooltips);
lines.addAll(this.getTooltipLines(stack));
}
}

View File

@ -0,0 +1,25 @@
package com.extendedae_plus.ae.items;
import appeng.items.parts.PartItem;
import com.extendedae_plus.ae.parts.EntitySpeedTickerPart;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import java.util.List;
public class EntitySpeedTickerPartItem extends PartItem<EntitySpeedTickerPart> {
public EntitySpeedTickerPartItem(Properties properties) {
super(properties, EntitySpeedTickerPart.class, EntitySpeedTickerPart::new);
}
@Override
public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> tooltip, TooltipFlag tooltipFlag) {
super.appendHoverText(stack, context, tooltip, tooltipFlag);
tooltip.add(Component.translatable("item.extendedae_plus.entity_speed_ticker.tip.requirement", "需要放入实体加速卡以启用加速"));
tooltip.add(Component.translatable("item.extendedae_plus.entity_speed_ticker.tip.max", "最高可达 1024x 加速"));
tooltip.add(Component.translatable("item.extendedae_plus.entity_speed_ticker.tip.energy", "加速将消耗 AE 网络能量,网络能量不足时无法加速"));
}
}

View File

@ -0,0 +1,151 @@
package com.extendedae_plus.ae.menu;
import appeng.core.definitions.AEItems;
import appeng.menu.guisync.GuiSync;
import appeng.menu.implementations.UpgradeableMenu;
import appeng.menu.slot.OptionalFakeSlot;
import com.extendedae_plus.ae.parts.EntitySpeedTickerPart;
import com.extendedae_plus.ae.screen.EntitySpeedTickerScreen;
import com.extendedae_plus.config.ModConfigs;
import com.extendedae_plus.init.ModItems;
import com.extendedae_plus.init.ModMenuTypes;
import com.extendedae_plus.util.ConfigParsingUtils;
import com.extendedae_plus.util.PowerUtils;
import it.unimi.dsi.fastutil.shorts.ShortSet;
import net.minecraft.client.Minecraft;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
// 实体加速器菜单负责与客户端界面同步数据
public class EntitySpeedTickerMenu extends UpgradeableMenu<EntitySpeedTickerPart> {
@GuiSync(716)
public boolean accelerateEnabled = true;
// 已安装的实体加速卡数量用于能耗计算
@GuiSync(717)
public int entitySpeedCardCount;
// 已安装的能量卡数量
@GuiSync(718)
public int energyCardCount;
// 当前生效的配置倍率从配置中读取并同步
// 当前计算出的生效速度product of multipliers同步给客户端用于显示
@GuiSync(719)
public int effectiveSpeed = 1;
@GuiSync(720)
public double multiplier = 1.0;
@GuiSync(721)
public boolean targetBlacklisted = false;
// 构造方法初始化菜单并与部件绑定
public EntitySpeedTickerMenu(int id, Inventory ip, EntitySpeedTickerPart host) {
super(ModMenuTypes.ENTITY_TICKER_MENU.get(), id, ip, host);
// 让部件持有当前菜单实例便于通信
getHost().menu = this;
// 初始同步部件上的开关状态到菜单服务器端构造时保证一致
try {
this.accelerateEnabled = getHost().getAccelerateEnabled();
} catch (Exception ignored) {
}
}
public boolean getAccelerateEnabled() {
return this.accelerateEnabled;
}
public void setAccelerateEnabled(boolean enabled) {
this.accelerateEnabled = enabled;
}
@Override
public void onServerDataSync(ShortSet updatedFields) {
super.onServerDataSync(updatedFields);
// 重新统计实体加速卡和能量卡数量
this.entitySpeedCardCount = this.getUpgrades().getInstalledUpgrades(ModItems.ENTITY_SPEED_CARD.get());
this.energyCardCount = this.getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD);
// 计算当前面向方块的倍率服务器端并同步给客户端
double mult = 1.0;
try {
BlockEntity target = getHost().getLevel().getBlockEntity(
getHost().getBlockEntity().getBlockPos().relative(getHost().getSide())
);
if (target != null) {
String blockId = BuiltInRegistries.BLOCK.getKey(target.getBlockState().getBlock()).toString();
for (ConfigParsingUtils.MultiplierEntry me :
ConfigParsingUtils.getCachedMultiplierEntries(ModConfigs.ENTITY_TICKER_MULTIPLIERS.get())) {
if (me.pattern.matcher(blockId).matches()) {
mult = Math.max(mult, me.multiplier);
}
}
}
} catch (Exception ignored) {}
this.multiplier = mult;
// 检查目标是否在黑名单中如果是则标记并将生效速度设为 0服务器端计算
boolean blacklisted = false;
try {
BlockEntity target = getHost().getLevel().getBlockEntity(
getHost().getBlockEntity().getBlockPos().relative(getHost().getSide())
);
if (target != null) {
Block block = target.getBlockState().getBlock();
String blockId = BuiltInRegistries.BLOCK.getKey(block).toString(); // 直接拿到 "minecraft:stone"
for (java.util.regex.Pattern p : ConfigParsingUtils.getCachedBlacklist(
ModConfigs.ENTITY_TICKER_BLACK_LIST.get())) {
if (p.matcher(blockId).matches()) {
blacklisted = true;
break;
}
}
}
} catch (Exception ignored) {
}
this.targetBlacklisted = blacklisted;
// 计算生效速度如果被黑名单则为 0否则进行正常计算使用工具类从菜单直接计算 product with cap最多 8
if (this.targetBlacklisted) {
this.effectiveSpeed = 0;
} else {
this.effectiveSpeed = (int) PowerUtils.computeProductWithCapFromMenu(this, 8);
}
// 如果在客户端刷新界面
if (isClientSide()) {
if (Minecraft.getInstance().screen instanceof EntitySpeedTickerScreen screen) {
screen.refreshGui();
}
}
}
// 当任意槽位发生变化时调用
@Override
public void onSlotChange(net.minecraft.world.inventory.Slot slot) {
super.onSlotChange(slot);
// 客户端重新统计卡数量并刷新界面
if (isClientSide()) {
this.entitySpeedCardCount = this.getUpgrades().getInstalledUpgrades(ModItems.ENTITY_SPEED_CARD.get());
this.energyCardCount = this.getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD);
// 立即在客户端计算生效速度以便界面即时反馈使用与服务端相同的工具方法最多 8 张卡
this.effectiveSpeed = (int) PowerUtils.computeProductWithCapFromMenu(this, 8);
if (Minecraft.getInstance().screen instanceof EntitySpeedTickerScreen screen) {
screen.refreshGui();
}
}
}
@Override
public void broadcastChanges() {
// 遍历所有槽位清理未启用但有物品显示的 OptionalFakeSlot
for (Object o : this.slots) {
if (o instanceof OptionalFakeSlot fs) {
if (!fs.isSlotEnabled() && !fs.getDisplayStack().isEmpty()) {
fs.clearStack();
}
}
}
// 调用标准的同步方法通知监听者数据已更新
this.standardDetectAndSendChanges();
}
}

View File

@ -0,0 +1,309 @@
package com.extendedae_plus.ae.parts;
import appeng.api.config.Actionable;
import appeng.api.config.PowerMultiplier;
import appeng.api.networking.GridFlags;
import appeng.api.networking.IGridNode;
import appeng.api.networking.ticking.IGridTickable;
import appeng.api.networking.ticking.TickRateModulation;
import appeng.api.networking.ticking.TickingRequest;
import appeng.api.parts.IPartCollisionHelper;
import appeng.api.parts.IPartItem;
import appeng.api.parts.IPartModel;
import appeng.api.upgrades.IUpgradeableObject;
import appeng.core.definitions.AEItems;
import appeng.items.parts.PartModels;
import appeng.menu.MenuOpener;
import appeng.menu.locator.MenuLocators;
import appeng.parts.PartModel;
import appeng.parts.automation.UpgradeablePart;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
import com.extendedae_plus.config.ModConfigs;
import com.extendedae_plus.init.ModItems;
import com.extendedae_plus.init.ModMenuTypes;
import com.extendedae_plus.util.ConfigParsingUtils;
import com.extendedae_plus.util.PowerUtils;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.regex.Pattern;
/**
* EntitySpeedTickerPart 是一个可升级的 AE2 部件<p>
* 该部件可以加速目标方块实体的 tick 速率消耗 AE 网络能量并支持加速卡升级<p>
* 功能受<a href="https://github.com/GilbertzRivi/crazyae2addons">Crazy AE2 Addons</a>启发
*/
public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTickable, MenuProvider, IUpgradeableObject {
public static final ResourceLocation MODEL_BASE = ResourceLocation.fromNamespaceAndPath(
ExtendedAEPlus.MODID, "part/entity_speed_ticker_part");
@PartModels
public static final PartModel MODELS_OFF;
@PartModels
public static final PartModel MODELS_ON;
@PartModels
public static final PartModel MODELS_HAS_CHANNEL;
static {
MODELS_OFF = new PartModel(MODEL_BASE, ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "part/entity_speed_ticker_off"));
MODELS_ON = new PartModel(MODEL_BASE, ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "part/entity_speed_ticker_on"));
MODELS_HAS_CHANNEL = new PartModel(MODEL_BASE, ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "part/entity_speed_ticker_has_channel"));
}
// 当前打开的菜单实例如果有
public EntitySpeedTickerMenu menu;
// 控制是否启用加速默认启用
private boolean accelerateEnabled = true;
/**
* 构造函数初始化部件并设置网络节点属性
*
* @param partItem 部件物品
*/
public EntitySpeedTickerPart(IPartItem<?> partItem) {
super(partItem);
// 设置网络节点属性需要通道空闲功耗为1并注册为 IGridTickable 服务
this.getMainNode()
.setFlags(GridFlags.REQUIRE_CHANNEL)
.setIdlePowerUsage(1)
.addService(IGridTickable.class, this);
}
public boolean getAccelerateEnabled() {
return this.accelerateEnabled;
}
public void setAccelerateEnabled(boolean accelerateEnabled) {
this.accelerateEnabled = accelerateEnabled;
}
/**
* 获取当前状态下的静态模型用于渲染
*
* @return 当前状态的模型
*/
public IPartModel getStaticModels() {
if (this.isActive() && this.isPowered()) {
return MODELS_HAS_CHANNEL;
} else if (this.isPowered()) {
return MODELS_ON;
} else {
return MODELS_OFF;
}
}
/**
* 当玩家激活部件右键时调用打开自定义菜单
*
* @param player 玩家
* @param pos 点击位置
* @return 总是返回 true表示激活成功
*/
@Override
public final boolean onUseWithoutItem(Player player, Vec3 pos) {
if (!isClientSide()) {
MenuOpener.open(ModMenuTypes.ENTITY_TICKER_MENU.get(), player, MenuLocators.forPart(this));
}
return true;
}
/**
* 定义部件的碰撞箱用于物理碰撞和渲染
*
* @param bch 碰撞辅助器
*/
@Override
public void getBoxes(IPartCollisionHelper bch) {
bch.addBox(2, 2, 14, 14, 14, 16);
bch.addBox(5, 5, 12, 11, 11, 14);
}
/**
* 获取定时请求决定本部件多久 tick 一次
*
* @param iGridNode 网络节点
* @return TickingRequest 对象
*/
@Override
public TickingRequest getTickingRequest(IGridNode iGridNode) {
// 1 tick 执行一次
return new TickingRequest(1, 1, false);
}
/**
* 当升级卡数量发生变化时调用通知菜单更新
*/
@Override
public void upgradesChanged() {
if (this.menu != null) {
// 使用 AE2 风格当升级发生变化时让菜单广播变化/数据会被同步客户端会基于槽内容重新计算并刷新界面
this.menu.broadcastChanges();
}
}
/**
* 网络定时回调每次 tick 时调用
*
* @param iGridNode 网络节点
* @param ticksSinceLastCall 距离上次调用经过的 tick
* @return TickRateModulation.IDLE 表示继续保持当前 tick 速率
*/
@Override
public TickRateModulation tickingRequest(IGridNode iGridNode, int ticksSinceLastCall) {
// 如果部件的加速开关被关闭则不进行加速提前返回
if (!this.getAccelerateEnabled()) {
return TickRateModulation.IDLE;
}
// 获取目标方块实体本部件朝向的方块
BlockEntity target = getLevel().getBlockEntity(getBlockEntity().getBlockPos().relative(getSide()));
// 仅在目标存在且部件处于激活状态时执行加速
if (target != null && isActive()) {
ticker(target);
}
return TickRateModulation.IDLE;
}
/**
* 以指定速度对目标方块实体进行 tick 操作
*
* @param blockEntity 需要被 tick 的方块实体
* @param <T> 方块实体类型
*/
private <T extends BlockEntity> void ticker(@NotNull T blockEntity) {
if (this.getGridNode() == null
|| this.getMainNode() == null
|| this.getMainNode().getGrid() == null) {
return;
}
// 获取方块实体的位置
BlockPos pos = blockEntity.getBlockPos();
if (blockEntity.getLevel() == null) return;
// 检查黑名单支持通配符/正则
String blockId = BuiltInRegistries.BLOCK.getKey(blockEntity.getBlockState().getBlock()).toString();
// 使用工具类的缓存接口工具类内部负责懒加载/线程安全
List<Pattern> compiledBlacklist = ConfigParsingUtils.getCachedBlacklist(ModConfigs.ENTITY_TICKER_BLACK_LIST.get());
for (Pattern p : compiledBlacklist) {
if (p.matcher(blockId).matches()) return;
}
// 获取该方块实体的 Ticker
@SuppressWarnings("unchecked")
BlockEntityTicker<T> blockEntityTicker = this.getLevel()
.getBlockState(pos)
.getTicker(this.getLevel(), (BlockEntityType<T>) blockEntity.getType());
if (blockEntityTicker == null) return;
// 使用集中定义的 CardDef 列表支持以后添加等级或改倍率而无需修改此逻辑
int energyCardCount = getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD);
// 使用已注册的单一 Item 计算已安装卡数量总计用于能耗计算
int entitySpeedCardCount = getUpgrades().getInstalledUpgrades(ModItems.ENTITY_SPEED_CARD.get());
// 使用工具方法从槽位直接计算乘积并应用 cap最多 8 张卡
long product = PowerUtils.computeProductWithCapFromStacks(this.getUpgrades(), 8);
// 如果没有任何实体加速卡则不进行加速且不消耗额外能量只保留部件的被动功耗
if (entitySpeedCardCount <= 0) return;
// 计算本次 tick 所需能量使用工具类根据 product 计算最终能耗
double requiredPower = PowerUtils.computeFinalPowerForProduct(product, energyCardCount);
int speed = (int) product;
double multiplier = 1.0;
for (ConfigParsingUtils.MultiplierEntry me : ConfigParsingUtils.getCachedMultiplierEntries(ModConfigs.ENTITY_TICKER_MULTIPLIERS.get())) {
if (me.pattern.matcher(blockId).matches()) {
multiplier = Math.max(multiplier, me.multiplier);
}
}
requiredPower *= multiplier;
// 先模拟提取以检查网络中是否有足够能量再真正抽取
double simulated = getMainNode().getGrid().getEnergyService()
.extractAEPower(requiredPower, Actionable.SIMULATE, PowerMultiplier.CONFIG);
if (simulated < requiredPower) return;
double extractedPower = getMainNode().getGrid().getEnergyService()
.extractAEPower(requiredPower, Actionable.MODULATE, PowerMultiplier.CONFIG);
if (extractedPower < requiredPower) return;
// 计算加速倍数基于 2 的次方并把 8 张映射到最大 1024x2^10
// 已由 product 计算得到 speed上面已在没有卡时提前返回
// 执行 tick 操作
for (int i = 0; i < speed - 1; i++) {
blockEntityTicker.tick(
blockEntity.getLevel(),
blockEntity.getBlockPos(),
blockEntity.getBlockState(),
blockEntity
);
}
}
/**
* 判断部件是否有自定义名称
*
* @return 是否有自定义名称
*/
@Override
public boolean hasCustomName() {
return super.hasCustomName();
}
/**
* 获取部件的显示名称
*
* @return 显示名称
*/
@Override
public @NotNull Component getDisplayName() {
return super.getDisplayName();
}
/**
* 创建自定义菜单GUI
*
* @param containerId 容器ID
* @param playerInventory 玩家背包
* @param player 玩家
* @return 菜单实例
*/
@Override
public @Nullable AbstractContainerMenu createMenu(int containerId,
@NotNull Inventory playerInventory,
@NotNull Player player) {
return new EntitySpeedTickerMenu(containerId, playerInventory, this);
}
/**
* 获取可用的升级卡槽数量
*
* @return 升级卡槽数量
*/
@Override
protected int getUpgradeSlots() {
return 8;
}
}

View File

@ -0,0 +1,136 @@
package com.extendedae_plus.ae.screen;
import appeng.api.config.Settings;
import appeng.api.config.YesNo;
import appeng.client.gui.Icon;
import appeng.client.gui.implementations.UpgradeableScreen;
import appeng.client.gui.style.ScreenStyle;
import appeng.client.gui.widgets.CommonButtons;
import appeng.client.gui.widgets.SettingToggleButton;
import appeng.util.Platform;
import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
import com.extendedae_plus.network.ToggleEntityTickerC2SPacket;
import com.extendedae_plus.util.PowerUtils;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory;
import java.util.List;
public class EntitySpeedTickerScreen<C extends EntitySpeedTickerMenu> extends UpgradeableScreen<C> {
private boolean eap$entitySpeedTickerEnabled = false;
private SettingToggleButton<YesNo> eap$entitySpeedTickerToggle;
public EntitySpeedTickerScreen(
EntitySpeedTickerMenu menu, Inventory playerInventory, Component title, ScreenStyle style) {
super((C) menu, playerInventory, title, style);
this.addToLeftToolbar(CommonButtons.togglePowerUnit());
try{
this.eap$entitySpeedTickerEnabled = menu.getAccelerateEnabled();
}catch (Exception ignored){}
// 使用 SettingToggleButton<YesNo> 的外观原版图标但自定义悬停描述为智能阻挡
// 不做本地切换点击仅发送自定义C2S显示由@GuiSync回传
eap$entitySpeedTickerToggle = new SettingToggleButton<>(
Settings.BLOCKING_MODE,
this.eap$entitySpeedTickerEnabled ? YesNo.YES : YesNo.NO,
(btn, backwards) -> {
// 不做本地切换点击仅发送自定义C2S显示由@GuiSync回传
var conn = Minecraft.getInstance().getConnection();
if (conn != null) conn.send(ToggleEntityTickerC2SPacket.INSTANCE);
}
) {
@Override
public List<Component> getTooltipMessage() {
// 如果目标在黑名单中直接显示已禁用的提示
try {
if (menu != null && menu.targetBlacklisted) {
var title = Component.literal("实体加速");
var stateLine = Component.literal("已禁用(目标在黑名单)");
return List.of(title, stateLine);
}
} catch (Exception ignored) {}
boolean enabled = eap$entitySpeedTickerEnabled;
var title = Component.literal("实体加速");
var stateLine = enabled
? Component.literal("已启用: 将加速目标方块实体的tick")
: Component.literal("已关闭: 不会对目标方块实体进行加速");
return List.of(title, stateLine);
}
@Override
protected Icon getIcon() {
try {
if (menu != null && menu.targetBlacklisted) {
// 黑名单时显示禁用图标
return Icon.INVALID;
}
} catch (Exception ignored) {}
// 根据当前值显示不同图标可按需替换 Icon 常量
if (this.getCurrentValue() == YesNo.YES) {
return Icon.VALID;
} else {
return Icon.INVALID;
}
}
};
// 初始化后立刻对齐当前@GuiSync状态避免首帧显示不一致
eap$entitySpeedTickerToggle.set(this.eap$entitySpeedTickerEnabled ? YesNo.YES : YesNo.NO);
this.addToLeftToolbar(eap$entitySpeedTickerToggle);
}
@Override
protected void updateBeforeRender() {
super.updateBeforeRender();
if (this.eap$entitySpeedTickerToggle != null) {
boolean desired = this.eap$entitySpeedTickerEnabled;
if (this.menu != null) {
desired = this.menu.getAccelerateEnabled();
}
this.eap$entitySpeedTickerEnabled = desired;
// 如果目标在黑名单中禁用切换并强制显示为关闭
if (this.menu != null && this.menu.targetBlacklisted) {
this.eap$entitySpeedTickerToggle.set(YesNo.NO);
this.eap$entitySpeedTickerToggle.active = false;
} else {
this.eap$entitySpeedTickerToggle.set(desired ? YesNo.YES : YesNo.NO);
this.eap$entitySpeedTickerToggle.active = true;
}
}
textData();
}
public void refreshGui() {
textData();
}
private void textData() {
// 如果目标被黑名单禁止则显示禁用状态并把数值显示为 0
if (getMenu().targetBlacklisted) {
setTextContent("enable", Component.translatable("screen.extendedae_plus.entity_speed_ticker.enable"));
setTextContent("speed", Component.translatable("screen.extendedae_plus.entity_speed_ticker.speed", 0));
setTextContent("energy", Component.translatable("screen.extendedae_plus.entity_speed_ticker.energy", Platform.formatPower(0.0, false)));
setTextContent("power_ratio", Component.translatable("screen.extendedae_plus.entity_speed_ticker.power_ratio", PowerUtils.formatPercentage(0.0)));
setTextContent("multiplier", Component.translatable("screen.extendedae_plus.entity_speed_ticker.multiplier", String.format("%.2fx", 0.0)));
return;
}
int energyCardCount = getMenu().energyCardCount;
double multiplier = getMenu().multiplier;
int effectiveSpeed = getMenu().effectiveSpeed;
double finalPower = PowerUtils.computeFinalPowerForProduct(effectiveSpeed, energyCardCount);
double remainingRatio = PowerUtils.getRemainingRatio(energyCardCount);
setTextContent("speed", Component.translatable("screen.extendedae_plus.entity_speed_ticker.speed", effectiveSpeed));
setTextContent("energy", Component.translatable("screen.extendedae_plus.entity_speed_ticker.energy", Platform.formatPower(finalPower, false)));
setTextContent("power_ratio", Component.translatable("screen.extendedae_plus.entity_speed_ticker.power_ratio", PowerUtils.formatPercentage(remainingRatio)));
setTextContent("multiplier", Component.translatable("screen.extendedae_plus.entity_speed_ticker.multiplier", String.format("%.2fx", multiplier)));
}
}

View File

@ -1,18 +1,21 @@
package com.extendedae_plus.client;
import appeng.client.render.crafting.CraftingCubeModel;
import appeng.init.client.InitScreens;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem;
import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
import com.extendedae_plus.ae.screen.EntitySpeedTickerScreen;
import com.extendedae_plus.client.render.crafting.EPlusCraftingCubeModelProvider;
import com.extendedae_plus.client.screen.GlobalProviderModesScreen;
import com.extendedae_plus.init.ModMenuTypes;
import com.extendedae_plus.content.crafting.EPlusCraftingUnitType;
import com.extendedae_plus.hooks.BuiltInModelHooks;
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraft.client.gui.screens.MenuScreens;
import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import com.extendedae_plus.init.ModItems;
import com.extendedae_plus.init.ModMenuTypes;
import net.minecraft.client.renderer.item.ItemProperties;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent;
/**
* 客户端模型注册 formed 模型注册为内置模型
@ -26,6 +29,10 @@ public final class ClientProxy {
public static void init() {
if (REGISTERED) return;
REGISTERED = true;
// 注册 Item property用于根据 ItemStack NBT exponent 切换模型
ItemProperties.register(ModItems.ENTITY_SPEED_CARD.get(), ExtendedAEPlus.id("mult"),
(stack, world, entity, seed) -> (float) EntitySpeedCardItem.readMultiplier(stack));
// 注册四种形成态模型为内置模型
BuiltInModelHooks.addBuiltInModel(
ExtendedAEPlus.id("block/crafting/4x_accelerator_formed_v2"),
@ -46,16 +53,7 @@ public final class ClientProxy {
BuiltInModelHooks.addBuiltInModel(
ExtendedAEPlus.id("block/crafting/1024x_accelerator_formed_v2"),
new CraftingCubeModel(new EPlusCraftingCubeModelProvider(EPlusCraftingUnitType.ACCELERATOR_1024x)));
}
/**
* 客户端设置阶段延迟执行需要访问注册对象的客户端注册
*/
public static void onClientSetup(final FMLClientSetupEvent event) {
event.enqueueWork(() -> {
// 确保在首次资源加载前完成内置模型注册REGISTERED 保护避免重复
init();
});
}
@SubscribeEvent
@ -75,5 +73,10 @@ public final class ClientProxy {
}
}
);
/**
* 注册由 AE2 InitScreens 所需的屏幕资源映射用于内置 JSON 屏幕注册
*/
InitScreens.register(event, ModMenuTypes.ENTITY_TICKER_MENU.get(), EntitySpeedTickerScreen<EntitySpeedTickerMenu>::new, "/screens/entity_speed_ticker.json");
}
}

View File

@ -1,6 +1,5 @@
package com.extendedae_plus.client.ui;
import com.extendedae_plus.network.ModNetwork;
import com.extendedae_plus.network.UploadEncodedPatternToProviderC2SPacket;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.Button;

View File

@ -2,6 +2,8 @@ package com.extendedae_plus.config;
import net.neoforged.neoforge.common.ModConfigSpec;
import java.util.List;
public final class ModConfigs {
public static final ModConfigSpec COMMON_SPEC;
public static final ModConfigSpec.IntValue PAGE_MULTIPLIER;
@ -12,7 +14,9 @@ public final class ModConfigs {
public static final ModConfigSpec.BooleanValue PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT;
public static final ModConfigSpec.IntValue SMART_SCALING_MAX_MULTIPLIER;
public static final ModConfigSpec.IntValue CRAFTING_PAUSE_THRESHOLD;
public static final ModConfigSpec.IntValue ENTITY_TICKER_COST;
public static final ModConfigSpec.ConfigValue<List<? extends String>> ENTITY_TICKER_BLACK_LIST;
public static final ModConfigSpec.ConfigValue<List<? extends String>> ENTITY_TICKER_MULTIPLIERS;
static {
ModConfigSpec.Builder builder = new ModConfigSpec.Builder();
@ -22,7 +26,8 @@ public final class ModConfigs {
.comment(
"扩展样板供应器总槽位容量的倍率",
"基础为36每页仍显示36格倍率会增加总页数/总容量",
"建议范围 1-16")
"建议范围 1-16"
)
.defineInRange("pageMultiplier", 1, 1, 64);
// 是否显示样板编码玩家通用
@ -59,7 +64,8 @@ public final class ModConfigs {
"智能倍增时是否对样板供应器轮询分配",
"仅多个供应器有相同样板时生效,开启后请求会均分到所有可用供应器,关闭则全部分配给单一供应器",
"注意:所有相关供应器需开启智能倍增,否则可能失效",
"默认: true")
"默认: true"
)
.define("providerRoundRobinEnable", true);
// 智能倍增的最大倍数以单次样板产出为单位
@ -67,7 +73,8 @@ public final class ModConfigs {
SMART_SCALING_MAX_MULTIPLIER = builder
.comment(
"智能倍增的最大倍数0 表示不限制)",
"此倍数是针对单次样板产出的放大倍数上限,用于限制一次推送中按倍增缩放的规模")
"此倍数是针对单次样板产出的放大倍数上限,用于限制一次推送中按倍增缩放的规模"
)
.defineInRange("smartScalingMaxMultiplier", 0, 0, 1048576);
builder.pop(); // pop smart
@ -79,19 +86,55 @@ public final class ModConfigs {
WIRELESS_MAX_RANGE = builder
.comment(
"无线收发器最大连接距离(单位:方块)",
"从端与主端的直线距离需小于等于该值才会建立连接。")
"从端与主端的直线距离需小于等于该值才会建立连接。"
)
.defineInRange("wirelessMaxRange", 256.0D, 1.0D, 4096.0D);
// 是否允许跨维度连接忽略维度差异进行频道传输
WIRELESS_CROSS_DIM_ENABLE = builder
.comment(
"是否允许无线收发器跨维度建立连接",
"开启后,从端可连接到不同维度的主端(忽略距离限制)")
"开启后,从端可连接到不同维度的主端(忽略距离限制)"
)
.define("wirelessCrossDimEnable", true);
builder.pop(); // pop wireless
// builder.pop(); // pop extendedae_plus
builder.push("entitySpeedTicker");
ENTITY_TICKER_COST = builder
.comment(
"实体加速器能量消耗基础值"
)
.defineInRange("entityTickerCost", 512, 0 , Integer.MAX_VALUE);
ENTITY_TICKER_BLACK_LIST = builder
.comment(
"实体加速器黑名单:匹配的方块将不会被加速。支持通配符/正则例如minecraft:*",
"格式:全名或通配符/正则字符串,例如 'minecraft:chest'、'minecraft:*'、'modid:.*_fluid'"
)
.defineListAllowEmpty(
List.of("entityTickerBlackList"), // 路径
List::of, // 默认值
() -> "", // 新元素默认值空字符串供配置编辑器使用
obj -> obj instanceof String // 验证每个元素是字符串
);
ENTITY_TICKER_MULTIPLIERS = builder
.comment(
"额外消耗倍率配置:为某些方块设置额外能量倍率,格式 'modid:blockid multiplier',例如 'minecraft:chest 2x'",
"支持通配符/正则匹配(例如 'minecraft:* 2x' 会对整个命名空间生效)。"
)
.defineListAllowEmpty(
List.of("entityTickerMultipliers"), // 路径
List::of, // 默认值
() -> "", // 新元素默认值空字符串供配置编辑器使用
obj -> obj instanceof String // 验证每个元素是字符串
);
builder.pop();
COMMON_SPEC = builder.build();
}

View File

@ -26,8 +26,12 @@ public final class ModCreativeTabs {
output.accept(ModItems.ACCELERATOR_64x.get());
output.accept(ModItems.ACCELERATOR_256x.get());
output.accept(ModItems.ACCELERATOR_1024x.get());
// output.accept(ModItems.INFINITY_BIGINTEGER_CELL_ITEM.get());
output.accept(ModItems.ENTITY_TICKER_PART_ITEM.get());
// 放入四个预设的 stacksx2,x4,x8,x16使用 ModItems 工厂创建
output.accept(ModItems.createEntitySpeedCardStack(2));
output.accept(ModItems.createEntitySpeedCardStack(4));
output.accept(ModItems.createEntitySpeedCardStack(8));
output.accept(ModItems.createEntitySpeedCardStack(16));
})
.build());
}

View File

@ -1,8 +1,14 @@
package com.extendedae_plus.init;
import appeng.api.parts.IPart;
import appeng.api.parts.PartModels;
import appeng.items.parts.PartModelsHelper;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem;
import com.extendedae_plus.ae.items.EntitySpeedTickerPartItem;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.neoforged.neoforge.registries.DeferredItem;
import net.neoforged.neoforge.registries.DeferredRegister;
@ -47,8 +53,34 @@ public final class ModItems {
() -> new BlockItem(ModBlocks.ACCELERATOR_1024x.get(), new Item.Properties())
);
// public static final DeferredItem<Item> INFINITY_BIGINTEGER_CELL_ITEM = ITEMS.register(
// "infinity_biginteger_cell", InfinityBigIntegerCellItem::new
// );
public static final DeferredItem<EntitySpeedTickerPartItem> ENTITY_TICKER_PART_ITEM = ITEMS.register(
"entity_speed_ticker",
() -> new EntitySpeedTickerPartItem(new Item.Properties())
);
// AE Upgrade Cards: 实体加速卡四个等级x2,x4,x8,x16
// 单一实体加速卡 Item不同等级由 ItemStack.nbt 存储
public static final DeferredItem<EntitySpeedCardItem> ENTITY_SPEED_CARD = ITEMS.register(
"entity_speed_card",
() -> new EntitySpeedCardItem(new Item.Properties())
);
/**
* PartItem 注册 AE2 部件模型
* 在客户端进行模型/几何体注册时调用
*/
public static void registerPartModels() {
PartModels.registerModels(
PartModelsHelper.createModels(
ENTITY_TICKER_PART_ITEM.get().getPartClass().asSubclass(IPart.class)
)
);
}
/**
* 工厂创建带 multiplier 的实体加速卡 ItemStack2/4/8/16
*/
public static ItemStack createEntitySpeedCardStack(int multiplier) {
return EntitySpeedCardItem.withMultiplier(multiplier);
}
}

View File

@ -1,12 +1,15 @@
package com.extendedae_plus.init;
import appeng.menu.implementations.MenuTypeBuilder;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
import com.extendedae_plus.ae.parts.EntitySpeedTickerPart;
import com.extendedae_plus.menu.NetworkPatternControllerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.inventory.MenuType;
import net.neoforged.neoforge.common.extensions.IMenuTypeExtension;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
import net.neoforged.neoforge.common.extensions.IMenuTypeExtension;
public final class ModMenuTypes {
private ModMenuTypes() {}
@ -16,5 +19,11 @@ public final class ModMenuTypes {
public static final DeferredHolder<MenuType<?>, MenuType<NetworkPatternControllerMenu>> NETWORK_PATTERN_CONTROLLER =
MENUS.register("network_pattern_controller",
() -> IMenuTypeExtension.create((id, inv, buf) -> new NetworkPatternControllerMenu(id, inv, buf)));
() -> IMenuTypeExtension.create(NetworkPatternControllerMenu::new));
public static final DeferredHolder<MenuType<?>, MenuType<EntitySpeedTickerMenu>> ENTITY_TICKER_MENU =
MENUS.register("entity_speed_ticker",
() -> MenuTypeBuilder
.create(EntitySpeedTickerMenu::new, EntitySpeedTickerPart.class)
.build("entity_speed_ticker"));
}

View File

@ -1,12 +1,14 @@
package com.extendedae_plus.network;
package com.extendedae_plus.init;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.network.*;
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
public class ModNetwork {
// Mod 构造中通过 modEventBus.addListener(ModNetwork::registerPayloadHandlers) 注册
public static void registerPayloadHandlers(final RegisterPayloadHandlersEvent event) {
var registrar = event.registrar(ExtendedAEPlus.MODID);
registrar.playToServer(ToggleEntityTickerC2SPacket.TYPE, ToggleEntityTickerC2SPacket.STREAM_CODEC, ToggleEntityTickerC2SPacket::handle);
registrar.playToServer(ToggleAdvancedBlockingC2SPacket.TYPE, ToggleAdvancedBlockingC2SPacket.STREAM_CODEC, ToggleAdvancedBlockingC2SPacket::handle);
registrar.playToServer(ToggleSmartDoublingC2SPacket.TYPE, ToggleSmartDoublingC2SPacket.STREAM_CODEC, ToggleSmartDoublingC2SPacket::handle);
registrar.playToServer(ScalePatternsC2SPacket.TYPE, ScalePatternsC2SPacket.STREAM_CODEC, ScalePatternsC2SPacket::handle);

View File

@ -0,0 +1,17 @@
package com.extendedae_plus.init;
import appeng.api.upgrades.Upgrades;
import appeng.core.definitions.AEItems;
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
public class UpgradeCards {
public UpgradeCards(final FMLCommonSetupEvent event) {
event.enqueueWork(() -> {
// 现有 Entity Ticker 的部件注册为处理 SPEED/ENERGY 卡的宿主
Upgrades.add(AEItems.ENERGY_CARD, ModItems.ENTITY_TICKER_PART_ITEM.get(), 8, "group.entity_ticker.name");
// 使用单一的 UpgradeCard Item 作为注册键总共允许安装 4 不同等级由 ItemStack NBT 区分
Upgrades.add(ModItems.ENTITY_SPEED_CARD.get(), ModItems.ENTITY_TICKER_PART_ITEM.get(), 4, "group.entity_ticker.name");
});
}
}

View File

@ -1,10 +1,17 @@
package com.extendedae_plus.integration.jei;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem;
import mezz.jei.api.IModPlugin;
import mezz.jei.api.JeiPlugin;
import mezz.jei.api.constants.VanillaTypes;
import mezz.jei.api.ingredients.subtypes.ISubtypeInterpreter;
import mezz.jei.api.ingredients.subtypes.UidContext;
import mezz.jei.api.registration.ISubtypeRegistration;
import mezz.jei.api.runtime.IJeiRuntime;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
@JeiPlugin
public class ExtendedAEJeiPlugin implements IModPlugin {
@ -19,4 +26,28 @@ public class ExtendedAEJeiPlugin implements IModPlugin {
public void onRuntimeAvailable(IJeiRuntime jeiRuntime) {
JeiRuntimeProxy.setRuntime(jeiRuntime);
}
@Override
public void registerItemSubtypes(ISubtypeRegistration registration) {
// Register NBT-based subtype interpreter so JEI treats different multipliers as distinct items
registration.registerSubtypeInterpreter(
VanillaTypes.ITEM_STACK,
com.extendedae_plus.init.ModItems.ENTITY_SPEED_CARD.get(),
new ISubtypeInterpreter<ItemStack>() {
@Override
public @NotNull Object getSubtypeData(@NotNull ItemStack ingredient, @NotNull UidContext context) {
// 返回你想让 JEI 区分子类型的数据这里用 multiplier
return EntitySpeedCardItem.readMultiplier(ingredient);
}
@Override
public @NotNull String getLegacyStringSubtypeInfo(@NotNull ItemStack ingredient, @NotNull UidContext context) {
// 返回同样的值给旧接口兼容
return String.valueOf(EntitySpeedCardItem.readMultiplier(ingredient));
}
}
);
}
}

View File

@ -17,7 +17,6 @@ import com.extendedae_plus.api.ExPatternPageAccessor;
import com.extendedae_plus.content.ClientPatternHighlightStore;
import com.extendedae_plus.network.CraftingMonitorJumpC2SPacket;
import com.extendedae_plus.network.CraftingMonitorOpenProviderC2SPacket;
import com.extendedae_plus.network.ModNetwork;
import com.extendedae_plus.util.GuiUtil;
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider;
import com.mojang.logging.LogUtils;

View File

@ -0,0 +1,50 @@
package com.extendedae_plus.network;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
import com.extendedae_plus.ae.parts.EntitySpeedTickerPart;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.neoforge.network.handling.IPayloadContext;
/**
* C2S: Toggle the accelerateEnabled flag on the EntitySpeedTickerPart bound to the open menu.
*/
public class ToggleEntityTickerC2SPacket implements CustomPacketPayload {
public static final CustomPacketPayload.Type<ToggleEntityTickerC2SPacket> TYPE = new CustomPacketPayload.Type<>(
ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "toggle_entity_ticker"));
public static final ToggleEntityTickerC2SPacket INSTANCE = new ToggleEntityTickerC2SPacket();
public static final StreamCodec<FriendlyByteBuf, ToggleEntityTickerC2SPacket> STREAM_CODEC =
StreamCodec.unit(INSTANCE);
private ToggleEntityTickerC2SPacket() {}
@Override
public CustomPacketPayload.Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static void handle(final ToggleEntityTickerC2SPacket msg, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
if (!(ctx.player() instanceof ServerPlayer player)) return;
if (!(player.containerMenu instanceof EntitySpeedTickerMenu menu)) return;
EntitySpeedTickerPart part = menu.getHost();
if (part == null) return;
// 切换部件上的状态并把新状态同步到菜单字段随后广播以通知客户端
boolean current = part.getAccelerateEnabled();
boolean next = !current;
part.setAccelerateEnabled(next);
// 确保菜单上的字段也被更新这样 @GuiSync 会把状态发回客户端
menu.setAccelerateEnabled(next);
menu.broadcastChanges();
});
}
}

View File

@ -0,0 +1,172 @@
package com.extendedae_plus.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
* 配置解析工具类用于解析黑名单与倍率配置的字符串
*/
public final class ConfigParsingUtils {
private ConfigParsingUtils() {}
public static final class MultiplierEntry {
public final Pattern pattern;
public final double multiplier;
public MultiplierEntry(Pattern pattern, double multiplier) {
this.pattern = pattern;
this.multiplier = multiplier;
}
}
/**
* 编译用户提供的匹配串支持简单的 glob 语法'*' '?'以及完整的正则表达式
* 规则
* - 如果字符串包含 ".*" 或其他正则元字符优先尝试按正则编译若失败则回退到 glob 转换
* - 否则若包含 '*' '?'将按 glob 语法转换为正则
* - 否则按字面量匹配处理
*/
public static Pattern compilePattern(String raw) {
if (raw == null) throw new IllegalArgumentException("pattern is null");
raw = raw.trim();
if (raw.isEmpty()) throw new IllegalArgumentException("pattern is empty");
// If it looks like regex (contains '.*' or regex metachar), try regex first
if (raw.contains(".*") || raw.matches(".*[\\[\\(\\+\\{\\\\].*")) {
try {
return Pattern.compile("^" + raw + "$");
} catch (PatternSyntaxException ignored) {
// fallback to glob below
}
}
// If contains glob chars, convert to regex
if (raw.contains("*") || raw.contains("?")) {
StringBuilder sb = new StringBuilder();
sb.append('^');
for (char c : raw.toCharArray()) {
switch (c) {
case '*': sb.append(".*"); break;
case '?': sb.append('.'); break;
default:
// escape regex special chars
if (".\\+[]{}()^$|".indexOf(c) >= 0) {
sb.append('\\');
}
sb.append(c);
}
}
sb.append('$');
return Pattern.compile(sb.toString());
}
// Otherwise treat as a literal (match exact)
return Pattern.compile("^" + Pattern.quote(raw) + "$");
}
/**
* Parse multiplier entries like 'modid:block 2x' into MultiplierEntry objects.
* Accepts values with optional trailing 'x' (case-insensitive).
*/
public static MultiplierEntry parseMultiplierEntry(String entry) {
if (entry == null) return null;
String[] parts = entry.trim().split("\\s+");
if (parts.length < 2) return null;
String key = parts[0];
String val = parts[1].toLowerCase();
if (val.endsWith("x")) val = val.substring(0, val.length() - 1);
double m;
try {
m = Double.parseDouble(val);
} catch (NumberFormatException ex) {
return null;
}
try {
Pattern p = compilePattern(key);
return new MultiplierEntry(p, m);
} catch (IllegalArgumentException e) {
return null;
}
}
public static List<Pattern> compilePatterns(List<? extends String> raw) {
List<Pattern> out = new ArrayList<>();
if (raw == null) return out;
for (String s : raw) {
if (s == null || s.isBlank()) continue;
try { out.add(compilePattern(s)); } catch (IllegalArgumentException ignored) {}
}
return out;
}
public static List<MultiplierEntry> parseMultiplierList(List<? extends String> raw) {
List<MultiplierEntry> out = new ArrayList<>();
if (raw == null) return out;
for (String s : raw) {
MultiplierEntry me = parseMultiplierEntry(s);
if (me != null) out.add(me);
}
return out;
}
// ------------------ 全局缓存与接口 ------------------
private static volatile List<Pattern> cachedBlacklist = null;
private static volatile List<MultiplierEntry> cachedMultiplierEntries = null;
private static final Object CACHE_LOCK = new Object();
/**
* 获取已解析并缓存的黑名单线程安全懒加载
*/
public static List<Pattern> getCachedBlacklist(List<? extends String> source) {
if (cachedBlacklist == null) {
synchronized (CACHE_LOCK) {
if (cachedBlacklist == null) {
cachedBlacklist = compilePatterns(source);
}
}
} else {
synchronized (CACHE_LOCK) {
List<Pattern> newCachedBlackList = compilePatterns(source);
if (!cachedBlacklist.equals(newCachedBlackList)) {
cachedBlacklist = newCachedBlackList;
}
}
}
return Collections.unmodifiableList(cachedBlacklist);
}
/**
* 获取已解析并缓存的倍率列表线程安全懒加载
*/
public static List<MultiplierEntry> getCachedMultiplierEntries(List<? extends String> source) {
if (cachedMultiplierEntries == null) {
synchronized (CACHE_LOCK) {
if (cachedMultiplierEntries == null) {
cachedMultiplierEntries = parseMultiplierList(source);
}
}
}else {
synchronized (CACHE_LOCK) {
List<MultiplierEntry> newCachedMultiplierEntries = parseMultiplierList(source);
if (!cachedMultiplierEntries.equals(newCachedMultiplierEntries)) {
cachedMultiplierEntries = newCachedMultiplierEntries;
}
}
}
return Collections.unmodifiableList(cachedMultiplierEntries);
}
/**
* 清空缓存下一次获取时将重新从提供的源解析或调用方可以重新调用 getter
*/
public static void reload() {
synchronized (CACHE_LOCK) {
cachedBlacklist = null;
cachedMultiplierEntries = null;
}
}
}

View File

@ -0,0 +1,162 @@
package com.extendedae_plus.util;
import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem;
import com.extendedae_plus.config.ModConfigs;
/**
* 用于计算实体加速器的能耗与加速倍率的工具类
*/
public final class PowerUtils {
private PowerUtils() {}
// ---- 重构后的 API ----
/**
* card multipliers按插槽序计算乘积并应用 cap 规则 capForHighestMultiplier
* @param multipliers iterable of per-card multipliers
* @param maxCards 最多计入的卡数
* @return cap 约束后的乘积
*/
public static long computeProductWithCap(Iterable<Integer> multipliers, int maxCards) {
long product = 1L;
int considered = 0;
int highest = 1;
for (Integer m : multipliers) {
if (m == null) continue;
if (considered >= maxCards) break;
int mult = m.intValue();
if (mult <= 0) mult = 1;
product *= mult;
highest = Math.max(highest, mult);
considered++;
}
long cap = capForHighestMultiplier(highest);
return Math.min(product, cap);
}
/**
* 根据最高单卡 multiplier 返回 cap
*/
public static long capForHighestMultiplier(int highestMultiplier) {
if (highestMultiplier >= 16) return 1024L;
if (highestMultiplier >= 8) return 256L;
if (highestMultiplier >= 4) return 64L;
if (highestMultiplier >= 2) return 8L;
return 1L;
}
/**
* 从菜单对象读取前 maxCards 个加速卡的 multiplier 并计算 product with cap
*/
public static long computeProductWithCapFromMenu(appeng.menu.implementations.UpgradeableMenu<?> menu, int maxCards) {
java.util.List<Integer> list = new java.util.ArrayList<>();
int considered = 0;
for (var stack : menu.getUpgrades()) {
if (considered >= maxCards) break;
if (stack != null && !stack.isEmpty() && stack.getItem() instanceof EntitySpeedCardItem) {
int multVal = EntitySpeedCardItem.readMultiplier(stack);
int count = Math.min(stack.getCount(), maxCards - considered);
for (int i = 0; i < count; i++) {
list.add(multVal);
considered++;
if (considered >= maxCards) break;
}
}
}
return computeProductWithCap(list, maxCards);
}
/**
* 从一组 ItemStack升级槽直接计算 product with cap最多 maxCards
*/
public static long computeProductWithCapFromStacks(Iterable<net.minecraft.world.item.ItemStack> stacks, int maxCards) {
java.util.List<Integer> list = new java.util.ArrayList<>();
int considered = 0;
for (var stack : stacks) {
if (considered >= maxCards) break;
if (stack != null && !stack.isEmpty() && stack.getItem() instanceof EntitySpeedCardItem) {
int multVal = EntitySpeedCardItem.readMultiplier(stack);
int count = Math.min(stack.getCount(), maxCards - considered);
for (int i = 0; i < count; i++) {
list.add(multVal);
considered++;
if (considered >= maxCards) break;
}
}
}
return computeProductWithCap(list, maxCards);
}
/**
* 计算最终消耗 product 转换为等效卡数log2并调用 getFinalPower
*/
public static double computeFinalPowerForProduct(long product, int energyCardCount) {
if (product <= 1L) return 0.0;
double base = ModConfigs.ENTITY_TICKER_COST.getAsInt();
// 计算以2为底的对数用于分档与公式
double log2 = Math.log(product) / Math.log(2.0);
// 分档product==2 为一档4..256 为中档512..1024 为高档
double raw;
if (product == 2L) {
// 轻量档线性小幅增长
raw = base * 4;
} else if (product <= 256L) {
// 中档增长放缓使用 1.5 * log2
raw = base * Math.pow(2.0, 1.5 * log2) * 2;
} else {
// 高档增长较快使用 2.5 * log2
raw = base * Math.pow(2.0, 2.5 * log2);
}
double reduction = getReductionPercent(energyCardCount);
return raw * (1.0 - reduction) / 8.0;
}
/* ----------------- legacy helpers (restored) ----------------- */
public static double getGrowthFactor(int speedCardCount) {
if (speedCardCount <= 0) return 1.0;
if (speedCardCount == 1) return 2.0;
if (speedCardCount <= 6) return Math.pow(2.0, 2.0 * speedCardCount);
return Math.pow(2.0, 3.0 * speedCardCount);
}
public static double getRawPower(int speedCardCount) {
double base = ModConfigs.ENTITY_TICKER_COST.getAsInt();
return base * getGrowthFactor(speedCardCount);
}
public static double getReductionPercent(int energyCardCount) {
if (energyCardCount <= 0) return 0.0;
if (energyCardCount == 1) return 0.1;
if (energyCardCount >= 8) return 0.5;
return 0.5 * (1.0 - Math.pow(0.7, energyCardCount));
}
public static double getFinalPower(int speedCardCount, int energyCardCount) {
double raw = getRawPower(speedCardCount);
double reduction = getReductionPercent(energyCardCount);
return raw * (1.0 - reduction) / 4.0;
}
/**
* 返回能源卡减免后剩余的功耗比率例如 1 张能源卡 -> 0.9
* @param energyCardCount 能源卡数量
* @return 剩余功耗比率
*/
public static double getRemainingRatio(int energyCardCount) {
return 1.0 - getReductionPercent(energyCardCount);
}
/**
* 将剩余功耗比率格式化为百分比字符串例如 0.9 -> "90%"
*/
public static String formatPercentage(double ratio) {
double pct = ratio * 100.0;
// 如果为整数则无小数
if (Math.abs(pct - Math.round(pct)) < 1e-9) {
return String.format("%d%%", Math.round(pct));
}
return String.format("%.2f%%", pct);
}
}

View File

@ -0,0 +1,78 @@
{
"$schema": "schema.json",
"includes": [
"common/common.json",
"common/player_inventory.json"
],
"background": {
"texture": "guis/blank_background.png",
"srcRect": [
0,
0,
176,
205
]
},
"text": {
"dialog_title": {
"text": {
"translate": "item.extendedae_plus.entity_speed_ticker"
},
"position": {
"left": 8,
"top": 6
}
},
"enable": {
"position": {
"top": 20,
"left": 88
},
"align": "CENTER"
},
"speed": {
"position": {
"top": 35,
"left": 88
},
"align": "CENTER"
},
"energy": {
"position": {
"top": 50,
"left": 88
},
"align": "CENTER"
},
"power_ratio": {
"position": {
"top": 65,
"left": 88
},
"align": "CENTER"
}
,
"multiplier": {
"position": {
"top": 80,
"left": 88
},
"align": "CENTER"
}
},
"widgets": {
"toolbox": {
"right": -2,
"bottom": 50,
"width": 68,
"height": 68
}
},
"slots": {
"TOOLBOX": {
"bottom": 42,
"right": -10,
"grid": "BREAK_AFTER_3COLS"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -28,6 +28,25 @@
"item.extendedae_plus.64x_crafting_accelerator": "64x并行处理单元",
"item.extendedae_plus.256x_crafting_accelerator": "256x并行处理单元",
"item.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元",
"item.extendedae_plus.entity_speed_ticker": "实体加速器",
"screen.extendedae_plus.entity_speed_ticker.enable": "§c§l机器已被禁用",
"screen.extendedae_plus.entity_speed_ticker.energy": "能耗: %s FE/t",
"screen.extendedae_plus.entity_speed_ticker.power_ratio": "功耗比例: %s",
"screen.extendedae_plus.entity_speed_ticker.speed": "当前加速倍率: %d",
"screen.extendedae_plus.entity_speed_ticker.multiplier": "额外消耗倍率: %s",
"item.extendedae_plus.entity_speed_ticker.tip.requirement": "需要放入实体加速卡以启用加速",
"item.extendedae_plus.entity_speed_ticker.tip.max": "最高可达 1024x 加速",
"item.extendedae_plus.entity_speed_ticker.tip.energy": "加速将消耗 AE 网络能量,网络能量不足时无法加速",
"item.extendedae_plus.entity_speed_card": "实体加速卡",
"item.extendedae_plus.entity_speed_card.x2": "实体加速卡 (x2)",
"item.extendedae_plus.entity_speed_card.x4": "实体加速卡 (x4)",
"item.extendedae_plus.entity_speed_card.x8": "实体加速卡 (x8)",
"item.extendedae_plus.entity_speed_card.x16": "实体加速卡 (x16)",
"tooltip.extendedae_plus.entity_speed_card.multiplier": "乘数: %s",
"tooltip.extendedae_plus.entity_speed_card.max": "最大生效: %s 倍",
"item.extendedae_plus.infinity_biginteger_cell": "§4吞§c噬§6万§e籁§a的§b寂§d静",
"tooltip.extendedae_plus.infinity_biginteger_cell.summon1": "§6九重献祭, 终得虚空回响§r——觐见§8虚空之主Iava§r, 赐汝此物",
"tooltip.extendedae_plus.infinity_biginteger_cell.summon2": "§b——§4方§d寸§c之§e间§a, §6自§b有§5千§9寰",
@ -62,6 +81,12 @@
"extendedae_plus.configuration.wirelessMaxRange_with_range": "无线最大距离 (1-4096)",
"extendedae_plus.configuration.wirelessCrossDimEnable": "无线收发器允许跨维度连接",
"extendedae_plus.configuration.entitySpeedTicker": "实体加速器",
"extendedae_plus.configuration.entityTickerCost": "实体加速器能量消耗基础值",
"extendedae_plus.configuration.entityTickerBlackList": "实体加速器黑名单",
"extendedae_plus.configuration.entityTickerMultipliers": "实体加速器额外消耗倍率",
"extendedae_pluscraftingPauseThreshold": "AE合成计算暂停检查阈值",
"extendedae_plus.configuration.state_on": "开",
"extendedae_plus.configuration.state_off": "关",
"block.extendedae_plus.network_pattern_controller": "样板供应器状态控制器",

View File

@ -0,0 +1,14 @@
{
"parent": "item/generated",
"overrides": [
{ "predicate": { "extendedae_plus:mult": 2.0 }, "model": "extendedae_plus:item/entity_speed_card_x2" },
{ "predicate": { "extendedae_plus:mult": 4.0 }, "model": "extendedae_plus:item/entity_speed_card_x4" },
{ "predicate": { "extendedae_plus:mult": 8.0 }, "model": "extendedae_plus:item/entity_speed_card_x8" },
{ "predicate": { "extendedae_plus:mult": 16.0 }, "model": "extendedae_plus:item/entity_speed_card_x16" }
],
"textures": {
"layer0": "extendedae_plus:item/entity_speed_card_x2"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "item/generated",
"textures": { "layer0": "extendedae_plus:item/entity_speed_card_x16" }
}

View File

@ -0,0 +1,6 @@
{
"parent": "item/generated",
"textures": { "layer0": "extendedae_plus:item/entity_speed_card_x2" }
}

View File

@ -0,0 +1,6 @@
{
"parent": "item/generated",
"textures": { "layer0": "extendedae_plus:item/entity_speed_card_x4" }
}

View File

@ -0,0 +1,6 @@
{
"parent": "item/generated",
"textures": { "layer0": "extendedae_plus:item/entity_speed_card_x8" }
}

View File

@ -0,0 +1,7 @@
{
"parent": "ae2:item/cable_interface",
"textures": {
"front": "extendedae_plus:part/entity_speed_ticker_font",
"back": "extendedae_plus:part/entity_speed_ticker_back"
}
}

View File

@ -0,0 +1,3 @@
{
"parent": "ae2:part/interface_has_channel"
}

View File

@ -0,0 +1,3 @@
{
"parent": "ae2:part/interface_off"
}

View File

@ -0,0 +1,3 @@
{
"parent": "ae2:part/interface_on"
}

View File

@ -0,0 +1,9 @@
{
"parent": "ae2:part/interface_base",
"textures": {
"front": "extendedae_plus:part/entity_speed_ticker_font",
"sides": "extendedae_plus:part/entity_speed_ticker_sides",
"back": "extendedae_plus:part/entity_speed_ticker_back",
"particle": "extendedae_plus:part/entity_speed_ticker_back"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,15 @@
{
"animation": {
"interpolate": true,
"frames": [
{
"index": 0,
"time": 32
},
{
"index": 1,
"time": 16
}
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,15 @@
{
"animation": {
"interpolate": true,
"frames": [
{
"index": 0,
"time": 32
},
{
"index": 1,
"time": 16
}
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,15 @@
{
"animation": {
"interpolate": true,
"frames": [
{
"index": 0,
"time": 32
},
{
"index": 1,
"time": 16
}
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,15 @@
{
"animation": {
"interpolate": true,
"frames": [
{
"index": 0,
"time": 32
},
{
"index": 1,
"time": 16
}
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,37 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"SAS",
"QXQ",
"SBS"
],
"key": {
"S": {
"type": "forge:partial_nbt",
"item": "extendedae_plus:entity_speed_card",
"nbt": {
"EAS:mult": 8
}
},
"A": {
"item": "minecraft:nether_star"
},
"B": {
"item": "minecraft:beacon"
},
"Q": {
"item": "ae2:spatial_cell_component_128"
},
"X": {
"item": "minecraft:dragon_egg"
}
},
"result": {
"type": "forge:partial_nbt",
"item": "extendedae_plus:entity_speed_card",
"count": 1,
"nbt": {
"EAS:mult": 16
}
}
}

View File

@ -0,0 +1,30 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"SBS",
"QXQ",
"SBS"
],
"key": {
"S": {
"item": "ae2:speed_card"
},
"B": {
"item": "extendedae_plus:64x_crafting_accelerator"
},
"Q": {
"item": "ae2:spatial_cell_component_2"
},
"X": {
"item": "ae2:cell_component_256k"
}
},
"result": {
"type": "forge:partial_nbt",
"item": "extendedae_plus:entity_speed_card",
"count": 1,
"nbt": {
"EAS:mult": 2
}
}
}

View File

@ -0,0 +1,34 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"SBS",
"QXQ",
"SBS"
],
"key": {
"S": {
"type": "forge:partial_nbt",
"item": "extendedae_plus:entity_speed_card",
"nbt": {
"EAS:mult": 2
}
},
"B": {
"item": "extendedae_plus:256x_crafting_accelerator"
},
"Q": {
"item": "ae2:spatial_cell_component_16"
},
"X": {
"item": "ae2:dense_energy_cell"
}
},
"result": {
"type": "forge:partial_nbt",
"item": "extendedae_plus:entity_speed_card",
"count": 1,
"nbt": {
"EAS:mult": 4
}
}
}

View File

@ -0,0 +1,34 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"SBS",
"QXQ",
"SBS"
],
"key": {
"S": {
"type": "forge:partial_nbt",
"item": "extendedae_plus:entity_speed_card",
"nbt": {
"EAS:mult": 4
}
},
"B": {
"item": "extendedae_plus:1024x_crafting_accelerator"
},
"Q": {
"item": "ae2:spatial_cell_component_128"
},
"X": {
"item": "minecraft:nether_star"
}
},
"result": {
"type": "forge:partial_nbt",
"item": "extendedae_plus:entity_speed_card",
"count": 1,
"nbt": {
"EAS:mult": 8
}
}
}

View File

@ -0,0 +1,33 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"SZS",
"QXQ",
"SIS"
],
"key": {
"S": {
"type": "forge:partial_nbt",
"item": "extendedae_plus:entity_speed_card",
"nbt": {
"EAS:mult": 2
}
},
"Z": {
"item": "ae2:dense_energy_cell"
},
"Q": {
"item": "ae2:singularity"
},
"X": {
"item": "minecraft:nether_star"
},
"I": {
"item": "expatternprovider:ex_io_port"
}
},
"result": {
"item": "extendedae_plus:entity_speed_ticker",
"count": 1
}
}