[全流程完成]
@@ -18,3 +18,5 @@ progress.md
|
||||
/.reasonix/
|
||||
/.tmp_*/
|
||||
__pycache__/
|
||||
/Issue-改善物品方块扫描要素.md
|
||||
/路径.md
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Project
|
||||
mod_version=2.0.1
|
||||
mod_version=2.1.1
|
||||
mod_group=thaumicenergistics
|
||||
|
||||
# Mod
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
@@ -0,0 +1,94 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -63,6 +63,8 @@ public final class ThaumicEnergistics {
|
||||
|
||||
thaumicenergistics.common.network.ThENetworking.init(modBus);
|
||||
|
||||
thaumicenergistics.common.blocks.ThEWrenchHandler.register();
|
||||
|
||||
modBus.addListener(this::registerCapabilities);
|
||||
|
||||
PartModels.registerModels(
|
||||
@@ -147,7 +149,14 @@ public final class ThaumicEnergistics {
|
||||
event.register(ModMenuTypes.ESSENTIA_CELL_WORKBENCH.get(), GuiEssentiaCellWorkbench::new);
|
||||
event.register(ModMenuTypes.EVC.get(), GuiEssentiaVibrationChamber::new);
|
||||
event.register(ModMenuTypes.ARCANE_ASSEMBLER.get(), GuiArcaneAssembler::new);
|
||||
event.register(ModMenuTypes.ESSENTIA_TERMINAL.get(), GuiEssentiaCellTerminal::new);
|
||||
event.register(ModMenuTypes.ESSENTIA_TERMINAL.get(),
|
||||
(EssentiaTerminalMenu menu, Inventory inv, Component title)
|
||||
-> new GuiEssentiaTerminal(menu, inv, title,
|
||||
StyleManager.loadStyleDoc("/screens/terminals/terminal.json")));
|
||||
event.register(ModMenuTypes.WIRELESS_ESSENTIA_TERMINAL.get(),
|
||||
(EssentiaTerminalMenu menu, Inventory inv, Component title)
|
||||
-> new GuiEssentiaTerminal(menu, inv, title,
|
||||
StyleManager.loadStyleDoc("/screens/terminals/wireless_terminal.json")));
|
||||
event.register(ModMenuTypes.KNOWLEDGE_INSCRIBER.get(), GuiKnowledgeInscriber::new);
|
||||
event.register(ModMenuTypes.DISTILLATION_ENCODER.get(), GuiDistillationPatternEncoder::new);
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package thaumicenergistics.common.blocks;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.ItemInteractionResult;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
@@ -14,10 +16,8 @@ import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.DirectionProperty;
|
||||
import net.minecraft.world.level.storage.loot.LootParams;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import thaumcraft.api.aspects.Aspect;
|
||||
import thaumcraft.api.aspects.AspectList;
|
||||
import thaumcraft.api.aspects.IEssentiaContainerItem;
|
||||
import thaumicenergistics.common.tiles.ThETileBase;
|
||||
import thaumicenergistics.common.tiles.TileEssentiaVibrationChamber;
|
||||
|
||||
@@ -43,20 +43,16 @@ public abstract class OrientableEntityBlock extends Block implements EntityBlock
|
||||
@Override
|
||||
public abstract BlockEntity newBlockEntity(BlockPos pos, BlockState state);
|
||||
|
||||
/**
|
||||
* 谐振仓:通配吸力模式,不再需要安瓿瓶切换燃料类型。
|
||||
* 放行默认交互(若后续添加 GUI 则走 getMenuProvider)。
|
||||
*/
|
||||
@Override
|
||||
protected ItemInteractionResult useItemOn(ItemStack stack, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
|
||||
if (level.isClientSide()) return ItemInteractionResult.SUCCESS;
|
||||
BlockEntity be = level.getBlockEntity(pos);
|
||||
if (!(be instanceof TileEssentiaVibrationChamber chamber)) return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
|
||||
// 检查手持物品是否为源质安瓿瓶
|
||||
if (!(stack.getItem() instanceof IEssentiaContainerItem container)) return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
|
||||
AspectList aspects = container.getAspects(stack);
|
||||
if (aspects == null || aspects.size() == 0) return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
|
||||
Aspect aspect = aspects.getAspects()[0];
|
||||
if (aspect == Aspect.FIRE || aspect == Aspect.ENERGY) {
|
||||
chamber.setSuctionType(aspect);
|
||||
return ItemInteractionResult.SUCCESS;
|
||||
}
|
||||
// 通配吸力:无需切换,直接放行
|
||||
return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
|
||||
}
|
||||
|
||||
@@ -68,4 +64,12 @@ public abstract class OrientableEntityBlock extends Block implements EntityBlock
|
||||
}
|
||||
super.onRemove(state, level, pos, newState, moved);
|
||||
}
|
||||
|
||||
/**
|
||||
* 挖掘掉落方块自身(不导出方块实体 NBT)。
|
||||
*/
|
||||
@Override
|
||||
public List<ItemStack> getDrops(BlockState state, LootParams.Builder builder) {
|
||||
return List.of(new ItemStack(this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
package thaumicenergistics.common.blocks;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.ItemInteractionResult;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
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.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.storage.loot.LootParams;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import thaumicenergistics.common.tiles.TileEssentiaProvider;
|
||||
import thaumicenergistics.common.tiles.TileEssentiaVibrationChamber;
|
||||
import thaumicenergistics.common.tiles.TileInfusionProvider;
|
||||
@@ -36,6 +44,24 @@ public abstract class ThEBaseEntityBlock extends Block implements EntityBlock {
|
||||
return (BlockEntityTicker<T>) ticker;
|
||||
}
|
||||
|
||||
/**
|
||||
* AE2 扳手拆卸。WrenchHook 只处理 AEBaseBlockEntity,ThE 方块 BE 不走该路径,故在此自行拆卸并掉落。
|
||||
*/
|
||||
@Override
|
||||
protected ItemInteractionResult useItemOn(ItemStack stack, BlockState state, Level level, BlockPos pos,
|
||||
Player player, InteractionHand hand, BlockHitResult hit) {
|
||||
// 扳手拆卸由 ThEWrenchHandler(RightClickBlock 事件)处理,这里不重复
|
||||
return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
|
||||
}
|
||||
|
||||
/**
|
||||
* 挖掘掉落方块自身(不导出方块实体 NBT)。
|
||||
*/
|
||||
@Override
|
||||
public List<ItemStack> getDrops(BlockState state, LootParams.Builder builder) {
|
||||
return List.of(new ItemStack(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean moved) {
|
||||
if (!state.is(newState.getBlock())) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import net.neoforged.neoforge.items.SlotItemHandler;
|
||||
import thaumcraft.api.IVisDiscountGear;
|
||||
import thaumcraft.api.IWarpingGear;
|
||||
import thaumcraft.api.aspects.Aspect;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
import thaumicenergistics.common.container.slot.ReadOnlySlot;
|
||||
import thaumicenergistics.common.tiles.TileArcaneAssembler;
|
||||
import thaumicenergistics.init.ModMenuTypes;
|
||||
|
||||
@@ -160,16 +160,12 @@ public class ContainerArcaneAssembler extends AbstractContainerMenu {
|
||||
}
|
||||
});
|
||||
|
||||
// ===== 4. 3×7 配方槽(只读展示) =====
|
||||
// ===== 4. 3×7 配方槽(只读展示,AE2 式防整理) =====
|
||||
for (int r = 0; r < PATTERN_ROWS; r++)
|
||||
for (int c = 0; c < PATTERN_COLS; c++) {
|
||||
int invIdx = TileArcaneAssembler.PATTERN_SLOT_START + r * PATTERN_COLS + c;
|
||||
addSlot(new SlotItemHandler(machineHandler, invIdx,
|
||||
PATTERN_X + c * 18, PATTERN_Y + r * 18) {
|
||||
@Override public boolean mayPlace(ItemStack s) { return false; }
|
||||
@Override public boolean mayPickup(Player p) { return false; }
|
||||
@Override public int getMaxStackSize() { return 1; }
|
||||
});
|
||||
addSlot(new ReadOnlySlot(machineHandler, invIdx,
|
||||
PATTERN_X + c * 18, PATTERN_Y + r * 18));
|
||||
}
|
||||
|
||||
// ===== 5. 4张AE2速度卡 =====
|
||||
@@ -182,11 +178,8 @@ public class ContainerArcaneAssembler extends AbstractContainerMenu {
|
||||
});
|
||||
}
|
||||
|
||||
// ===== 6. 目标产物输出槽(只读展示) =====
|
||||
addSlot(new SlotItemHandler(machineHandler, TileArcaneAssembler.TARGET_SLOT, TARGET_X, TARGET_Y) {
|
||||
@Override public boolean mayPlace(ItemStack s) { return false; }
|
||||
@Override public boolean mayPickup(Player p) { return false; }
|
||||
});
|
||||
// ===== 6. 目标产物输出槽(只读展示,AE2 式防整理) =====
|
||||
addSlot(new ReadOnlySlot(machineHandler, TileArcaneAssembler.TARGET_SLOT, TARGET_X, TARGET_Y));
|
||||
|
||||
// ===== 7. 4个护甲折扣槽 =====
|
||||
for (int i = 0; i < ARMOR_COUNT; i++) {
|
||||
@@ -202,9 +195,9 @@ public class ContainerArcaneAssembler extends AbstractContainerMenu {
|
||||
@Override public boolean mayPlace(ItemStack s) {
|
||||
if (s.getItem() instanceof IVisDiscountGear) return true;
|
||||
if (s.getItem() instanceof IWarpingGear) return true;
|
||||
if (s.getItem() instanceof net.minecraft.world.item.Equipable eqItem
|
||||
&& eqItem.getEquipmentSlot() == eq) return true;
|
||||
return s.canEquip(eq, null);
|
||||
if (s.getItem() instanceof net.minecraft.world.item.Equipable eqItem)
|
||||
return eqItem.getEquipmentSlot() == eq;
|
||||
return false;
|
||||
}
|
||||
@Override public void setChanged() {
|
||||
super.setChanged();
|
||||
@@ -220,13 +213,9 @@ public class ContainerArcaneAssembler extends AbstractContainerMenu {
|
||||
this(id, inv, resolveTile(inv, buf));
|
||||
}
|
||||
|
||||
/** 临时诊断:确认客户端能否拿到 tile(定位后删除) */
|
||||
private static TileArcaneAssembler resolveTile(Inventory inv, RegistryFriendlyByteBuf buf) {
|
||||
var pos = buf.readBlockPos();
|
||||
var be = inv.player.level().getBlockEntity(pos);
|
||||
ThaumicEnergistics.LOG.info("[AA-DEBUG] client ctor: pos={}, be={}",
|
||||
pos, be == null ? "null" : be.getClass().getSimpleName());
|
||||
return (TileArcaneAssembler) be;
|
||||
return (TileArcaneAssembler) inv.player.level().getBlockEntity(pos);
|
||||
}
|
||||
|
||||
private ItemStackHandler wrapMachineInventory() {
|
||||
|
||||
@@ -33,9 +33,11 @@ import appeng.me.storage.LinkStatusRespectingInventory;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import thaumcraft.api.aspects.Aspect;
|
||||
import thaumcraft.api.aspects.AspectList;
|
||||
import thaumcraft.api.wands.WandCap;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import thaumcraft.common.items.TCFunctionalItems;
|
||||
import thaumcraft.common.lib.crafting.ThaumcraftCraftingManager;
|
||||
import thaumcraft.common.vis.TCVisHelper;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
import thaumicenergistics.common.container.slot.ArcaneCraftingResultSlot;
|
||||
import thaumicenergistics.common.parts.ArcaneCraftingTerminalPart;
|
||||
@@ -55,6 +57,34 @@ public class ContainerArcaneCraftingTerminal extends MEStorageMenu implements IC
|
||||
*/
|
||||
private boolean isCraftingResultBeingTaken = false;
|
||||
|
||||
/** vis 恢复检测节流:每 10 tick(0.5 秒)检查一次,避免持续遍历配方。 */
|
||||
private static final int VIS_RECOVERY_CHECK_INTERVAL = 10;
|
||||
private int visRecoveryTickCounter = 0;
|
||||
|
||||
@Override
|
||||
public void broadcastChanges() {
|
||||
super.broadcastChanges();
|
||||
// vis 充能恢复后自动重算结果槽:结果槽空 + 奥术配方有效 + vis 已足够 → 重新显示产物。
|
||||
// 节流:仅"待恢复"状态每 10 tick 检查一次(findMatchingArcaneRecipe 只在满足时执行)
|
||||
var player = getPlayerInventory().player;
|
||||
if (player.level().isClientSide() || !hasValidRecipe || !resultSlot.getItem().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (++visRecoveryTickCounter < VIS_RECOVERY_CHECK_INTERVAL) {
|
||||
return;
|
||||
}
|
||||
visRecoveryTickCounter = 0;
|
||||
var craftContainer = this.craftingInventoryHost.getSubInventory(ArcaneCraftingTerminalPart.INV_CRAFTING).toContainer();
|
||||
ItemStack arcaneResult = ThaumcraftCraftingManager.findMatchingArcaneRecipe(craftContainer, player);
|
||||
if (!arcaneResult.isEmpty()) {
|
||||
AspectList aspects = ThaumcraftCraftingManager.findMatchingArcaneRecipeAspects(craftContainer, player);
|
||||
if (aspects != null && hasEnoughVis(aspects)) {
|
||||
resultSlot.setRequiredAspects(aspects);
|
||||
resultSlot.setDisplayedCraftingOutput(arcaneResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final ISegmentedInventory craftingInventoryHost;
|
||||
private final CraftingMatrixSlot[] craftingSlots = new CraftingMatrixSlot[9];
|
||||
private final ArcaneCraftingResultSlot resultSlot;
|
||||
@@ -76,9 +106,9 @@ public class ContainerArcaneCraftingTerminal extends MEStorageMenu implements IC
|
||||
}
|
||||
|
||||
var linkStatusInventory = new LinkStatusRespectingInventory(host.getInventory(), this::getLinkStatus);
|
||||
this.resultSlot = new ArcaneCraftingResultSlot(getPlayerInventory().player, craftingGridInv);
|
||||
this.resultSlot.setRefreshCallback(() -> updateCurrentRecipeAndOutput(false));
|
||||
this.resultSlot.setContainer(this);
|
||||
this.resultSlot = new ArcaneCraftingResultSlot(
|
||||
getPlayerInventory().player, this, craftingGridInv, craftingGridInv,
|
||||
linkStatusInventory, this.getEnergySource(), this.getActionSource());
|
||||
this.addSlot(this.resultSlot, SlotSemantics.CRAFTING_RESULT);
|
||||
|
||||
var wandInv = this.craftingInventoryHost.getSubInventory(ArcaneCraftingTerminalPart.INV_WAND);
|
||||
@@ -88,7 +118,6 @@ public class ContainerArcaneCraftingTerminal extends MEStorageMenu implements IC
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean mayPlace(ItemStack stack) {
|
||||
//只允许 TCFunctionalItems.WandCastingItem 类型的物品放入
|
||||
@@ -145,6 +174,26 @@ public class ContainerArcaneCraftingTerminal extends MEStorageMenu implements IC
|
||||
|
||||
private static final int CENTIVIS = 100;
|
||||
|
||||
/**
|
||||
* 复刻 TC port {@code WandCastingItem.consumptionModifier}:法杖 cap 折扣 − 玩家装备/饰品折扣 − 法杖折扣。
|
||||
* 仅用于显示(GUI 所需 vis),实际扣费由 consumeVisCost 内部同公式处理。
|
||||
*/
|
||||
private float getConsumptionModifier(Aspect aspect, boolean crafting) {
|
||||
var wandInv = this.craftingInventoryHost.getSubInventory(ArcaneCraftingTerminalPart.INV_WAND);
|
||||
var wandStack = wandInv.getStackInSlot(0);
|
||||
if (wandStack.isEmpty() || !(wandStack.getItem() instanceof TCFunctionalItems.WandCastingItem wand)) {
|
||||
return 1.0f;
|
||||
}
|
||||
float modifier = wand.consumptionModifier(wandStack, getPlayerInventory().player, aspect, crafting);
|
||||
return modifier;
|
||||
}
|
||||
|
||||
/** 折扣后的 vis 成本(GUI 显示用)。 */
|
||||
private int getDiscountedVisCost(Aspect aspect, int rawCost) {
|
||||
if (rawCost <= 0 || aspect == null) return 0;
|
||||
return Math.max(0, Math.round(rawCost * getConsumptionModifier(aspect, true)));
|
||||
}
|
||||
|
||||
private boolean hasEnoughVis(AspectList cost) {
|
||||
ThaumicEnergistics.LOG.debug("[V] hasEnoughVis: cost={}", cost);
|
||||
if (cost == null || cost.isEmpty()) {
|
||||
@@ -172,7 +221,6 @@ public class ContainerArcaneCraftingTerminal extends MEStorageMenu implements IC
|
||||
}
|
||||
|
||||
private void updateCurrentRecipeAndOutput(boolean forceUpdate) {
|
||||
ThaumicEnergistics.LOG.debug("[R] === updateCurrentRecipeAndOutput forceUpdate={} ===", forceUpdate);
|
||||
var player = getPlayerInventory().player;
|
||||
var level = player.level();
|
||||
|
||||
@@ -183,27 +231,20 @@ public class ContainerArcaneCraftingTerminal extends MEStorageMenu implements IC
|
||||
var craftContainer = this.craftingInventoryHost.getSubInventory(ArcaneCraftingTerminalPart.INV_CRAFTING).toContainer();
|
||||
|
||||
ItemStack arcaneResult = ThaumcraftCraftingManager.findMatchingArcaneRecipe(craftContainer, player);
|
||||
ThaumicEnergistics.LOG.debug("[R] findMatchingArcaneRecipe result={}", arcaneResult);
|
||||
|
||||
if (!arcaneResult.isEmpty()) {
|
||||
requiredAspects = ThaumcraftCraftingManager.findMatchingArcaneRecipeAspects(craftContainer, player);
|
||||
ThaumicEnergistics.LOG.debug("[R] ARCANE MATCH! aspects={}, result={}", requiredAspects, arcaneResult);
|
||||
foundArcane = true;
|
||||
|
||||
if (hasEnoughVis(requiredAspects)) {
|
||||
ThaumicEnergistics.LOG.debug("[R] Vis OK → showing result");
|
||||
resultSlot.setDisplayedCraftingOutput(arcaneResult);
|
||||
} else {
|
||||
ThaumicEnergistics.LOG.debug("[R] Vis INSUFFICIENT → result empty, but recipe recognized");
|
||||
resultSlot.setDisplayedCraftingOutput(ItemStack.EMPTY);
|
||||
}
|
||||
} else {
|
||||
ThaumicEnergistics.LOG.debug("[R] No arcane recipe match");
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundArcane) {
|
||||
ThaumicEnergistics.LOG.debug("[R] No arcane match, trying vanilla...");
|
||||
var testItems = new ArrayList<ItemStack>(this.craftingSlots.length);
|
||||
for (var craftingSlot : this.craftingSlots) {
|
||||
testItems.add(craftingSlot.getItem().copy());
|
||||
@@ -212,10 +253,8 @@ public class ContainerArcaneCraftingTerminal extends MEStorageMenu implements IC
|
||||
var recipeHolder = level.getRecipeManager().getRecipeFor(RecipeType.CRAFTING, testInput, level);
|
||||
if (recipeHolder.isPresent()) {
|
||||
ItemStack result = recipeHolder.get().value().assemble(testInput, level.registryAccess());
|
||||
ThaumicEnergistics.LOG.debug("[R] Vanilla match: {}, setting result slot", result);
|
||||
resultSlot.setDisplayedCraftingOutput(result);
|
||||
} else {
|
||||
ThaumicEnergistics.LOG.debug("[R] No vanilla match either, clearing result slot");
|
||||
// 客户端不清空产物槽,@GuiSync 会同步服务端的正确状态
|
||||
if (!level.isClientSide()) {
|
||||
resultSlot.setDisplayedCraftingOutput(ItemStack.EMPTY);
|
||||
@@ -225,9 +264,7 @@ public class ContainerArcaneCraftingTerminal extends MEStorageMenu implements IC
|
||||
|
||||
if (foundArcane) {
|
||||
// 服务端设置 @GuiSync 字段;客户端不应覆盖来自服务端的同步值
|
||||
ThaumicEnergistics.LOG.debug("[R] foundArcane branch: isClientSide={}", level.isClientSide());
|
||||
if (level.isClientSide()) {
|
||||
ThaumicEnergistics.LOG.debug("[R] Client: keeping server-synced values, not overriding");
|
||||
return;
|
||||
}
|
||||
this.hasValidRecipe = true;
|
||||
@@ -235,32 +272,27 @@ public class ContainerArcaneCraftingTerminal extends MEStorageMenu implements IC
|
||||
var wandInv = this.craftingInventoryHost.getSubInventory(ArcaneCraftingTerminalPart.INV_WAND);
|
||||
var ws = wandInv.getStackInSlot(0);
|
||||
resultSlot.setWandStack(ws);
|
||||
ThaumicEnergistics.LOG.debug("[R] SET state: hasValidRecipe=true, wandStack={}", ws);
|
||||
|
||||
if (requiredAspects != null && requiredAspects.size() > 0) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Map.Entry<Aspect, Integer> entry : requiredAspects.aspects.entrySet()) {
|
||||
if (!sb.isEmpty()) sb.append(",");
|
||||
sb.append(entry.getKey().getTag()).append("=").append(entry.getValue());
|
||||
// 存折扣后数值(GUI 显示实际所需 vis,含法杖 + 装备 + 饰品减免)
|
||||
sb.append(entry.getKey().getTag()).append("=")
|
||||
.append(getDiscountedVisCost(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
this.requiredAspectsData = sb.toString();
|
||||
ThaumicEnergistics.LOG.debug("[R] aspectsData={}", this.requiredAspectsData);
|
||||
} else {
|
||||
this.requiredAspectsData = "";
|
||||
ThaumicEnergistics.LOG.debug("[R] aspectsData=empty (no aspects)");
|
||||
}
|
||||
} else {
|
||||
ThaumicEnergistics.LOG.debug("[R] else branch: isClientSide={}, levelClass={}",
|
||||
level.isClientSide(), level.getClass().getSimpleName());
|
||||
if (level.isClientSide()) {
|
||||
ThaumicEnergistics.LOG.debug("[R] Client: not overriding synced values (no arcane recipe on client)");
|
||||
return;
|
||||
}
|
||||
this.requiredAspectsData = "";
|
||||
this.hasValidRecipe = false;
|
||||
resultSlot.setRequiredAspects(null);
|
||||
resultSlot.setWandStack(ItemStack.EMPTY);
|
||||
ThaumicEnergistics.LOG.debug("[R] SET state: hasValidRecipe=false (no arcane recipe)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import thaumcraft.api.aspects.AspectList;
|
||||
import thaumcraft.common.research.TCResearchManager;
|
||||
import thaumcraft.common.research.ThaumometerScanManager;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
import thaumicenergistics.common.container.slot.ReadOnlySlot;
|
||||
import thaumicenergistics.common.integration.appeng.AEssentiaKey;
|
||||
import thaumicenergistics.common.integration.tc.TCReflection;
|
||||
import thaumicenergistics.common.network.DistillationEncoderSetSourceC2SPacket;
|
||||
@@ -98,20 +99,10 @@ public class ContainerDistillationPatternEncoder extends AbstractContainerMenu {
|
||||
|
||||
for (int i = 0; i < SLOT_ASPECTS_COUNT; i++) {
|
||||
int y = SLOT_ASPECTS_Y + i * 18;
|
||||
addSlot(new SlotItemHandler(aspectInventory, i, SLOT_ASPECTS_X, y) {
|
||||
@Override
|
||||
public boolean mayPlace(ItemStack s) { return false; }
|
||||
@Override
|
||||
public boolean mayPickup(Player p) { return false; }
|
||||
});
|
||||
addSlot(new ReadOnlySlot(aspectInventory, i, SLOT_ASPECTS_X, y));
|
||||
}
|
||||
|
||||
addSlot(new SlotItemHandler(aspectInventory, SLOT_ASPECTS_COUNT, SLOT_SELECTED_X, SLOT_SELECTED_Y) {
|
||||
@Override
|
||||
public boolean mayPlace(ItemStack s) { return false; }
|
||||
@Override
|
||||
public boolean mayPickup(Player p) { return false; }
|
||||
});
|
||||
addSlot(new ReadOnlySlot(aspectInventory, SLOT_ASPECTS_COUNT, SLOT_SELECTED_X, SLOT_SELECTED_Y));
|
||||
|
||||
if (encoder != null) {
|
||||
// 源物品槽:ghost 模板(背包拖入不消耗——实际物品还在手里;空手点击清空;JEI 拖拽设置模板)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package thaumicenergistics.common.container;
|
||||
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.neoforged.neoforge.items.ItemStackHandler;
|
||||
import net.neoforged.neoforge.items.SlotItemHandler;
|
||||
import thaumicenergistics.init.ModMenuTypes;
|
||||
|
||||
/** 源质元件终端的容器。基于网格的源质视图 + 元件槽。 */
|
||||
public class ContainerEssentiaCellTerminal extends ThEContainerBase {
|
||||
final ItemStackHandler cellSlot = new ItemStackHandler(1);
|
||||
final ItemStackHandler viewSlot = new ItemStackHandler(1);
|
||||
|
||||
public ContainerEssentiaCellTerminal(int id, Inventory inv) {
|
||||
super(ModMenuTypes.ESSENTIA_TERMINAL.get(), id, inv, 2);
|
||||
addSlot(new SlotItemHandler(cellSlot, 0, 26, 16));
|
||||
addSlot(new SlotItemHandler(viewSlot, 0, 134, 16));
|
||||
}
|
||||
public ContainerEssentiaCellTerminal(int id, Inventory inv, RegistryFriendlyByteBuf buf) { this(id, inv); }
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import thaumcraft.api.crafting.ShapedArcaneRecipe;
|
||||
import thaumcraft.api.crafting.ShapelessArcaneRecipe;
|
||||
import thaumcraft.common.research.TCResearchManager;
|
||||
import thaumicenergistics.common.container.slot.GhostSlot;
|
||||
import thaumicenergistics.common.container.slot.ReadOnlySlot;
|
||||
import thaumicenergistics.common.integration.tc.ArcaneCraftingPattern;
|
||||
import thaumicenergistics.common.inventory.HandlerKnowledgeCore;
|
||||
import thaumicenergistics.common.network.KnowledgeInscriberGhostSlotPacket;
|
||||
@@ -121,12 +122,7 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu {
|
||||
initPatternSlots();
|
||||
initCraftingSlots();
|
||||
|
||||
addSlot(new SlotItemHandler(resultInventory, 0, RESULT_SLOT_X, RESULT_SLOT_Y) {
|
||||
@Override
|
||||
public boolean mayPlace(ItemStack s) { return false; }
|
||||
@Override
|
||||
public boolean mayPickup(Player p) { return false; }
|
||||
});
|
||||
addSlot(new ReadOnlySlot(resultInventory, 0, RESULT_SLOT_X, RESULT_SLOT_Y));
|
||||
|
||||
saveStateSlot = addDataSlot(DataSlot.standalone());
|
||||
saveStateSlot.set(0);
|
||||
|
||||
@@ -7,9 +7,10 @@ import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.ContainerData;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
import org.anti_ad.mc.ipn.api.IPNIgnore;
|
||||
|
||||
/**
|
||||
* 奥术装配器 GUI。
|
||||
* 奥术装配器 GUI。标 @IPNIgnore:IPN 一键整理完全跳过此界面(不渲染排序按钮、不执行整理)。
|
||||
*
|
||||
* 贴图 arcane_assembler.png = 256×256 画布,包含4块不连续区域:
|
||||
* 第1块 主矩形:(0,0) → 176×197
|
||||
@@ -20,6 +21,7 @@ import thaumicenergistics.ThaumicEnergistics;
|
||||
* 仿1.7.10纹理裁剪方式:GUI分块blit,bar区域不随纹理整体绘制,
|
||||
* 而是先画暗色背景,再从纹理Y=202处裁剪满bar按百分比叠加。
|
||||
*/
|
||||
@IPNIgnore
|
||||
public class GuiArcaneAssembler extends AbstractContainerScreen<ContainerArcaneAssembler> {
|
||||
|
||||
static final ResourceLocation TEX = ResourceLocation.fromNamespaceAndPath(
|
||||
|
||||
@@ -11,17 +11,13 @@ import appeng.client.gui.style.ScreenStyle;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class GuiArcaneCraftingTerminal extends MEStorageScreen<ContainerArcaneCraftingTerminal> {
|
||||
import org.anti_ad.mc.ipn.api.IPNIgnore;
|
||||
|
||||
/** 6 个要素图标位置(左上角坐标) */
|
||||
private static final int[][] ASPECT_POSITIONS = {
|
||||
{10, 95}, // 左列 第1行
|
||||
{10, 112}, // 左列 第2行
|
||||
{10, 128}, // 左列 第3行
|
||||
{80, 95}, // 右列 第1行
|
||||
{80, 112}, // 右列 第2行
|
||||
{80, 128}, // 右列 第3行
|
||||
};
|
||||
/**
|
||||
* 奥术合成终端 GUI。标 @IPNIgnore:IPN 一键整理完全跳过此界面(不渲染排序按钮、不执行整理)。
|
||||
*/
|
||||
@IPNIgnore
|
||||
public class GuiArcaneCraftingTerminal extends MEStorageScreen<ContainerArcaneCraftingTerminal> {
|
||||
|
||||
private static final int ICON_SIZE = 14;
|
||||
|
||||
@@ -30,9 +26,6 @@ public class GuiArcaneCraftingTerminal extends MEStorageScreen<ContainerArcaneCr
|
||||
super(menu, playerInventory, title, style);
|
||||
}
|
||||
|
||||
/** 合成结果槽锚点 Y(首次计算缓存,避免每帧遍历 slots)。 */
|
||||
private int cachedAnchorY = -1;
|
||||
|
||||
@Override
|
||||
public void drawFG(GuiGraphics guiGraphics, int offsetX, int offsetY, int mouseX, int mouseY) {
|
||||
super.drawFG(guiGraphics, offsetX, offsetY, mouseX, mouseY);
|
||||
@@ -42,22 +35,17 @@ public class GuiArcaneCraftingTerminal extends MEStorageScreen<ContainerArcaneCr
|
||||
Map<String, Integer> aspectMap = this.menu.getParsedAspects();
|
||||
if (aspectMap.isEmpty()) return;
|
||||
|
||||
// 获取合成结果槽的 Y 位置作为锚点(槽位置不变,首次计算后缓存,避免每帧遍历)
|
||||
int anchorY;
|
||||
if (cachedAnchorY < 0) {
|
||||
// 锚点取合成结果槽的 Y:圆环紧贴结果槽左侧,窗口缩放时随槽位一起移动
|
||||
int anchorY = -1;
|
||||
for (var slot : this.menu.slots) {
|
||||
// CRAFTING_RESULT 槽在 style JSON 中 bottom:140
|
||||
if (slot instanceof appeng.menu.slot.AppEngCraftingSlot) {
|
||||
cachedAnchorY = slot.y;
|
||||
anchorY = slot.y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 若找不到锚点则回退到硬编码值
|
||||
if (cachedAnchorY < 0) cachedAnchorY = 95;
|
||||
}
|
||||
anchorY = cachedAnchorY;
|
||||
if (anchorY < 0) anchorY = 95;
|
||||
|
||||
// 要素圆圈 Y = 锚点 + 偏移(-16/1/17,对应纹理 95/112/128)
|
||||
// 两列三行:左列 x=10,右列 x=80;行偏移对应三行圆环
|
||||
int[] aspectOffsets = { -16, 1, 17 };
|
||||
|
||||
int index = 0;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package thaumicenergistics.common.container;
|
||||
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
|
||||
public class GuiEssentiaCellTerminal extends AbstractContainerScreen<ContainerEssentiaCellTerminal> {
|
||||
static final ResourceLocation TEX = ResourceLocation.fromNamespaceAndPath(ThaumicEnergistics.MODID,"textures/gui/essentia_terminal.png");
|
||||
public GuiEssentiaCellTerminal(ContainerEssentiaCellTerminal m, Inventory inv, Component t) { super(m,inv,t); imageWidth=176; imageHeight=166; inventoryLabelY=imageHeight-94; }
|
||||
@Override protected void renderBg(GuiGraphics g, float pt, int mx, int my) { int x=(width-imageWidth)/2,y=(height-imageHeight)/2; g.blit(TEX,x,y,0,0,imageWidth,imageHeight); }
|
||||
@Override public void render(GuiGraphics g, int mx, int my, float pt) { super.render(g,mx,my,pt); renderTooltip(g,mx,my); }
|
||||
}
|
||||
@@ -4,37 +4,44 @@ import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import appeng.api.inventories.InternalInventory;
|
||||
import appeng.menu.slot.AppEngCraftingSlot;
|
||||
import appeng.api.networking.energy.IEnergySource;
|
||||
import appeng.api.networking.security.IActionSource;
|
||||
import appeng.api.storage.MEStorage;
|
||||
import appeng.helpers.ICraftingGridMenu;
|
||||
import appeng.helpers.InventoryAction;
|
||||
import appeng.menu.slot.CraftingTermSlot;
|
||||
import appeng.util.inv.PlayerInternalInventory;
|
||||
import thaumcraft.api.aspects.Aspect;
|
||||
import thaumcraft.api.aspects.AspectList;
|
||||
import thaumcraft.common.items.TCFunctionalItems;
|
||||
import thaumcraft.common.lib.crafting.ThaumcraftCraftingManager;
|
||||
import thaumicenergistics.common.container.ContainerArcaneCraftingTerminal;
|
||||
|
||||
/**
|
||||
* 奥术合成结果槽:在取走合成结果时消耗法杖要素。
|
||||
* 参考 TC4 1.21.1 ArcaneWorkbenchMenu.onResultTaken + consumeVis。
|
||||
* 奥术合成结果槽:继承 AE2 CraftingTermSlot 使客户端识别(shift/右键/space 合成自动生效),
|
||||
* 并保留 vis 消耗检查(mayPickup)与取走时 vis 扣除 + 自动补货(onTake)。
|
||||
*/
|
||||
public class ArcaneCraftingResultSlot extends AppEngCraftingSlot {
|
||||
public class ArcaneCraftingResultSlot extends CraftingTermSlot {
|
||||
|
||||
/** TC4 内部使用 centivis,1 vis = 100 centivis */
|
||||
private static final int CENTIVIS = 100;
|
||||
|
||||
private AspectList requiredAspects = null;
|
||||
private ItemStack wandStack = ItemStack.EMPTY;
|
||||
private Runnable refreshCallback = null;
|
||||
private ContainerArcaneCraftingTerminal container = null;
|
||||
private final MEStorage storage;
|
||||
private final IEnergySource energySrc;
|
||||
private final IActionSource mySrc;
|
||||
private final InternalInventory craftingGrid;
|
||||
|
||||
public ArcaneCraftingResultSlot(Player player, InternalInventory craftingGridInv) {
|
||||
super(player, craftingGridInv);
|
||||
}
|
||||
|
||||
public void setContainer(ContainerArcaneCraftingTerminal container) {
|
||||
public ArcaneCraftingResultSlot(Player player, ContainerArcaneCraftingTerminal container,
|
||||
InternalInventory craftingGridInv, InternalInventory secondMatrix,
|
||||
MEStorage storage, IEnergySource energySrc, IActionSource mySrc) {
|
||||
super(player, mySrc, energySrc, storage, craftingGridInv, secondMatrix, (ICraftingGridMenu) container);
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
public void setRefreshCallback(Runnable callback) {
|
||||
this.refreshCallback = callback;
|
||||
this.storage = storage;
|
||||
this.energySrc = energySrc;
|
||||
this.mySrc = mySrc;
|
||||
this.craftingGrid = craftingGridInv;
|
||||
}
|
||||
|
||||
public void setRequiredAspects(AspectList aspects) {
|
||||
@@ -47,40 +54,177 @@ public class ArcaneCraftingResultSlot extends AppEngCraftingSlot {
|
||||
|
||||
@Override
|
||||
public boolean mayPickup(Player player) {
|
||||
if (requiredAspects == null || requiredAspects.isEmpty()) {
|
||||
return super.mayPickup(player);
|
||||
}
|
||||
|
||||
if (wandStack.isEmpty()) return false;
|
||||
if (!(wandStack.getItem() instanceof TCFunctionalItems.WandCastingItem wand)) {
|
||||
// AE2 原版:结果槽不可 vanilla 点按取走,所有取走走 doClick(含 vis 检查)
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doClick(InventoryAction action, Player who) {
|
||||
// vis 检查:奥术配方需法杖有足够 vis,否则不合成
|
||||
if (requiredAspects != null && !requiredAspects.isEmpty()) {
|
||||
if (wandStack.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!(wandStack.getItem() instanceof TCFunctionalItems.WandCastingItem wand)) {
|
||||
return;
|
||||
}
|
||||
AspectList centivisCost = new AspectList();
|
||||
for (Aspect aspect : requiredAspects.aspects()) {
|
||||
centivisCost.add(aspect, requiredAspects.amount(aspect) * CENTIVIS);
|
||||
}
|
||||
return wand.consumeVisCost(wandStack, player, centivisCost, false, true);
|
||||
if (!wand.consumeVisCost(wandStack, who, centivisCost, false, true)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 奥术配方(非 vanilla CRAFTING)不走 AE2 craftItem(findRecipe 只认 vanilla)
|
||||
// 走自定义合成:从合成格读配方 → 产物 + 消耗材料 + 从网络补货
|
||||
if (container != null && container.hasValidRecipe) {
|
||||
doArcaneCraft(action, who);
|
||||
return;
|
||||
}
|
||||
|
||||
// vanilla 配方:严格 AE2(从网络提取材料填合成格)
|
||||
super.doClick(action, who);
|
||||
// 消耗 vis(实际扣除)
|
||||
if (requiredAspects != null && !requiredAspects.isEmpty() && !wandStack.isEmpty()) {
|
||||
if (wandStack.getItem() instanceof TCFunctionalItems.WandCastingItem wand) {
|
||||
AspectList centivisCost = new AspectList();
|
||||
for (Aspect aspect : requiredAspects.aspects()) {
|
||||
centivisCost.add(aspect, requiredAspects.amount(aspect) * CENTIVIS);
|
||||
}
|
||||
wand.consumeVisCost(wandStack, who, centivisCost, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 奥术配方合成:从合成格读 Thaumcraft 奥术配方 → 产物 + 消耗材料 + 从网络补货。 */
|
||||
private void doArcaneCraft(InventoryAction action, Player who) {
|
||||
if (this.getItem().isEmpty() || isRemote()) {
|
||||
return;
|
||||
}
|
||||
var grid = craftingGrid;
|
||||
var level = who.level();
|
||||
if (level.isClientSide()) return;
|
||||
|
||||
// 用 Thaumcraft 奥术配方匹配(取配方对象以支持 retainIngredient 保留原料)
|
||||
var craftContainer = grid.toContainer();
|
||||
thaumcraft.api.crafting.IArcaneRecipe arcaneRecipe = thaumcraft.common.lib.crafting.ThaumcraftCraftingManager
|
||||
.findMatchingArcaneRecipeEntry(craftContainer, who);
|
||||
if (arcaneRecipe == null) {
|
||||
return;
|
||||
}
|
||||
ItemStack arcaneResult = arcaneRecipe.getCraftingResult(craftContainer);
|
||||
if (arcaneResult.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 保留原料(retainIngredient,如元始珍珠):getRemainingItems 返回非空栈的槽位不消耗。
|
||||
// 对应 TC 奥术工作台行为——珍珠作为"催化"留在合成格,仅消耗构材与虚空金属锭。
|
||||
net.minecraft.core.NonNullList<net.minecraft.world.item.ItemStack> remainders =
|
||||
arcaneRecipe.getRemainingItems(craftContainer);
|
||||
|
||||
|
||||
// shift=整组到背包、普通=1 个到手
|
||||
int maxTimesToCraft;
|
||||
InternalInventory target;
|
||||
if (action == InventoryAction.CRAFT_SHIFT || action == InventoryAction.CRAFT_ALL) {
|
||||
target = new appeng.util.inv.PlayerInternalInventory(who.getInventory());
|
||||
maxTimesToCraft = (int) Math.floor((double) arcaneResult.getMaxStackSize() / (double) arcaneResult.getCount());
|
||||
if (action == InventoryAction.CRAFT_ALL) {
|
||||
maxTimesToCraft *= net.minecraft.world.entity.player.Inventory.INVENTORY_SIZE;
|
||||
}
|
||||
} else {
|
||||
target = new appeng.util.inv.CarriedItemInventory(getMenu());
|
||||
maxTimesToCraft = 1;
|
||||
}
|
||||
|
||||
int crafted = 0;
|
||||
for (int x = 0; x < maxTimesToCraft; x++) {
|
||||
// 每轮 vis 检查 + 实际扣减(consume=true):法杖 vis 不足则停
|
||||
if (requiredAspects != null && !requiredAspects.isEmpty()) {
|
||||
if (wandStack.isEmpty()) break;
|
||||
if (!(wandStack.getItem() instanceof TCFunctionalItems.WandCastingItem wand)) break;
|
||||
AspectList centivisCost = new AspectList();
|
||||
for (Aspect aspect : requiredAspects.aspects()) {
|
||||
centivisCost.add(aspect, requiredAspects.amount(aspect) * CENTIVIS);
|
||||
}
|
||||
boolean ok = wand.consumeVisCost(wandStack, who, centivisCost, true, true);
|
||||
if (!ok) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 目标满或配方失效则停
|
||||
if (!target.simulateAdd(arcaneResult).isEmpty()) {
|
||||
break;
|
||||
}
|
||||
// 合成前检查奥术配方仍匹配
|
||||
ItemStack currentResult = thaumcraft.common.lib.crafting.ThaumcraftCraftingManager
|
||||
.findMatchingArcaneRecipe(grid.toContainer(), who);
|
||||
if (currentResult.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 记录消耗前合成格(首次记录,后续每轮消耗后补货)
|
||||
var beforeItems = new java.util.HashMap<Integer, ItemStack>();
|
||||
for (int i = 0; i < grid.size(); i++) {
|
||||
var s = grid.getStackInSlot(i);
|
||||
if (!s.isEmpty()) beforeItems.put(i, s.copy());
|
||||
}
|
||||
// 消耗合成格材料(每格 1 个);保留原料槽(remainders 非空,如元始珍珠)不消耗
|
||||
for (int i = 0; i < grid.size(); i++) {
|
||||
if (!grid.getStackInSlot(i).isEmpty() && remainders.get(i).isEmpty()) {
|
||||
grid.extractItem(i, 1, false);
|
||||
}
|
||||
}
|
||||
// 从网络补货(对齐 AE2 终端)
|
||||
if (storage != null && energySrc != null && mySrc != null) {
|
||||
for (var entry : beforeItems.entrySet()) {
|
||||
int slot = entry.getKey();
|
||||
var beforeItem = entry.getValue();
|
||||
var currentItem = grid.getStackInSlot(slot);
|
||||
if (currentItem.getCount() < beforeItem.getCount()) {
|
||||
long needed = beforeItem.getCount() - currentItem.getCount();
|
||||
var key = appeng.api.stacks.AEItemKey.of(beforeItem);
|
||||
if (key != null) {
|
||||
long extracted = appeng.api.storage.StorageHelper.poweredExtraction(
|
||||
energySrc, storage, key, needed, mySrc);
|
||||
if (extracted > 0) {
|
||||
grid.setItemDirect(slot, beforeItem.copyWithCount(
|
||||
(int) (currentItem.getCount() + extracted)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 产物入目标
|
||||
var extra = target.addItems(arcaneResult.copy());
|
||||
if (!extra.isEmpty()) {
|
||||
appeng.util.Platform.spawnDrops(who.level(), who.blockPosition(), java.util.List.of(extra));
|
||||
break;
|
||||
}
|
||||
crafted++;
|
||||
}
|
||||
|
||||
// 触发容器刷新(重算结果槽)
|
||||
container.slotsChanged(grid.toContainer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTake(Player player, ItemStack stack) {
|
||||
// 记录消耗前各合成格中的物品类型,用于消耗后从 ME 网络补充
|
||||
java.util.Map<Integer, ItemStack> beforeItems = new java.util.HashMap<>();
|
||||
if (container != null) {
|
||||
var grid = container.getCraftingMatrix();
|
||||
for (int i = 0; i < grid.size(); i++) {
|
||||
var s = grid.getStackInSlot(i);
|
||||
if (!s.isEmpty()) {
|
||||
beforeItems.put(i, s.copy());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (container != null) {
|
||||
container.setCraftingResultBeingTaken(true);
|
||||
}
|
||||
try {
|
||||
// 记录消耗前各合成格物品,取走后从 ME 存储自动补回(AE2 终端行为)
|
||||
java.util.Map<Integer, ItemStack> beforeItems = new java.util.HashMap<>();
|
||||
var grid = craftingGrid;
|
||||
for (int i = 0; i < grid.size(); i++) {
|
||||
var s = grid.getStackInSlot(i);
|
||||
if (!s.isEmpty()) beforeItems.put(i, s.copy());
|
||||
}
|
||||
|
||||
super.onTake(player, stack);
|
||||
|
||||
if (requiredAspects != null && !requiredAspects.isEmpty() && !wandStack.isEmpty()) {
|
||||
@@ -93,28 +237,22 @@ public class ArcaneCraftingResultSlot extends AppEngCraftingSlot {
|
||||
}
|
||||
}
|
||||
|
||||
// 从 ME 网络自动补充消耗掉的合成材料
|
||||
if (container != null && !beforeItems.isEmpty()) {
|
||||
var grid = container.getCraftingMatrix();
|
||||
var node = container.getGridNode();
|
||||
if (node != null && node.getGrid() != null) {
|
||||
var storage = node.getGrid().getStorageService().getInventory();
|
||||
var energy = container.getEnergySource();
|
||||
var actionSource = container.getActionSource();
|
||||
// 自动补货:合成格材料被消耗后从 ME 存储补回
|
||||
if (!beforeItems.isEmpty()) {
|
||||
if (storage != null && energySrc != null && mySrc != null) {
|
||||
for (var entry : beforeItems.entrySet()) {
|
||||
int slot = entry.getKey();
|
||||
var beforeItem = entry.getValue();
|
||||
var currentItem = grid.getStackInSlot(slot);
|
||||
// 如果该格物品变少了(被消耗了),从网络提取补充
|
||||
if (currentItem.getCount() < beforeItem.getCount()) {
|
||||
long needed = beforeItem.getCount() - currentItem.getCount();
|
||||
var key = appeng.api.stacks.AEItemKey.of(beforeItem);
|
||||
if (key != null) {
|
||||
long extracted = appeng.api.storage.StorageHelper.poweredExtraction(
|
||||
energy, storage, key, needed, actionSource);
|
||||
energySrc, storage, key, needed, mySrc);
|
||||
if (extracted > 0) {
|
||||
var toPlace = beforeItem.copyWithCount((int) (currentItem.getCount() + extracted));
|
||||
grid.setItemDirect(slot, toPlace);
|
||||
grid.setItemDirect(slot, beforeItem.copyWithCount(
|
||||
(int) (currentItem.getCount() + extracted)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,8 +264,6 @@ public class ArcaneCraftingResultSlot extends AppEngCraftingSlot {
|
||||
container.setCraftingResultBeingTaken(false);
|
||||
}
|
||||
}
|
||||
if (refreshCallback != null) {
|
||||
refreshCallback.run();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ public final class RecipeRegistration {
|
||||
"lBl",
|
||||
"QPQ",
|
||||
'Q', ae("quartz_glass"),
|
||||
'l', new ItemStack(Items.LAPIS_LAZULI, 4),
|
||||
'l', new ItemStack(Items.LAPIS_LAZULI, 1),
|
||||
'B', tc("zombie_brain"),
|
||||
'P', ae("calculation_processor"));
|
||||
|
||||
@@ -138,7 +138,7 @@ public final class RecipeRegistration {
|
||||
"qWq",
|
||||
"FqA",
|
||||
'A', tc("air_shard"),
|
||||
'q', new ItemStack(Items.QUARTZ, 4),
|
||||
'q', new ItemStack(Items.QUARTZ, 1),
|
||||
'F', tc("fire_shard"),
|
||||
'W', ae("certus_quartz_wrench"));
|
||||
|
||||
@@ -156,22 +156,23 @@ public final class RecipeRegistration {
|
||||
|
||||
ESSENTIA_IMPORT_BUS = ThaumcraftApi.addArcaneCraftingRecipe("ESSENTIABUSES", stack(ModItems.ESSENTIA_IMPORT_BUS),
|
||||
new AspectList().add(Aspect.FIRE, 2).add(Aspect.EARTH, 2).add(Aspect.WATER, 1),
|
||||
"JDJ",
|
||||
// 注意:TC ShapedArcaneRecipe 要求模式为矩形(各行等长),删 J 后必须补空格占位
|
||||
"JD ",
|
||||
"IFI",
|
||||
" ",
|
||||
'J', tc("warded_jar"),
|
||||
'J', tc("jar_of_essentia"),
|
||||
'D', stack(ModItems.DIFFUSION_CORE),
|
||||
'I', new ItemStack(Items.IRON_INGOT, 2),
|
||||
'I', new ItemStack(Items.IRON_INGOT, 1),
|
||||
'F', tc("essentia_filter"));
|
||||
|
||||
ESSENTIA_EXPORT_BUS = ThaumcraftApi.addArcaneCraftingRecipe("ESSENTIABUSES", stack(ModItems.ESSENTIA_EXPORT_BUS),
|
||||
new AspectList().add(Aspect.FIRE, 2).add(Aspect.EARTH, 2).add(Aspect.WATER, 1),
|
||||
"JCJ",
|
||||
"JC ",
|
||||
"IFI",
|
||||
" ",
|
||||
'J', tc("warded_jar"),
|
||||
'J', tc("jar_of_essentia"),
|
||||
'C', stack(ModItems.COALESCENCE_CORE),
|
||||
'I', new ItemStack(Items.IRON_INGOT, 2),
|
||||
'I', new ItemStack(Items.IRON_INGOT, 1),
|
||||
'F', tc("essentia_filter"));
|
||||
|
||||
ESSENTIA_STORAGE_BUS = ThaumcraftApi.addArcaneCraftingRecipe("ESSENTIABUSES", stack(ModItems.ESSENTIA_STORAGE_BUS),
|
||||
@@ -182,7 +183,7 @@ public final class RecipeRegistration {
|
||||
'D', stack(ModItems.DIFFUSION_CORE),
|
||||
'C', stack(ModItems.COALESCENCE_CORE),
|
||||
'F', tc("essentia_filter"),
|
||||
'I', new ItemStack(Items.IRON_INGOT, 2),
|
||||
'I', new ItemStack(Items.IRON_INGOT, 1),
|
||||
'G', ae("quartz_glass"));
|
||||
|
||||
KNOWLEDGE_INSCRIBER_BLOCK = ThaumcraftApi.addArcaneCraftingRecipe("KNOWLEDGEINSCRIBER", stack(ModBlocks.KNOWLEDGE_INSCRIBER),
|
||||
@@ -190,7 +191,7 @@ public final class RecipeRegistration {
|
||||
"ILI",
|
||||
" T ",
|
||||
"IPI",
|
||||
'I', new ItemStack(Items.IRON_INGOT, 4),
|
||||
'I', new ItemStack(Items.IRON_INGOT, 1),
|
||||
'L', MONITORS,
|
||||
'T', tc("thaumonomicon"),
|
||||
'P', ae("logic_processor"));
|
||||
@@ -200,7 +201,7 @@ public final class RecipeRegistration {
|
||||
"ILI",
|
||||
" G ",
|
||||
"IPI",
|
||||
'I', new ItemStack(Items.IRON_INGOT, 4),
|
||||
'I', new ItemStack(Items.IRON_INGOT, 1),
|
||||
'L', MONITORS,
|
||||
'G', tc("thaumometer"),
|
||||
'P', ae("engineering_processor"));
|
||||
@@ -252,7 +253,7 @@ public final class RecipeRegistration {
|
||||
// 6. 源流接口
|
||||
VIS_INTERFACE = ThaumcraftApi.addShapelessArcaneCraftingRecipe("VISINTERFACE", stack(ModItems.VIS_INTERFACE),
|
||||
new AspectList().add(Aspect.AIR, 2).add(Aspect.EARTH, 2).add(Aspect.FIRE, 2).add(Aspect.WATER, 2).add(Aspect.ORDER, 2).add(Aspect.ENTROPY, 2),
|
||||
tc("balance_shard"),
|
||||
tc("balanced_shard"),
|
||||
ae("me_p2p_tunnel"));
|
||||
|
||||
// 7. 原质振荡室
|
||||
@@ -293,27 +294,38 @@ public final class RecipeRegistration {
|
||||
tc("salis_mundus"));
|
||||
|
||||
// 3. 奥术装配器
|
||||
// 权杖要求:6 个原质要素值全满(满充能)。createFullVisScepter() 生成"满充能"预期栈,
|
||||
// TC port 的 InfusionRecipe.hasExpectedComponents 会逐键比对 custom_data——
|
||||
// 预期键必须全部存在于实物且数值相等 → 未满充能的权杖无法匹配。
|
||||
// 组件数:8 个(1.7.10 原版一致)。实测 9 组件(含世界盐)注魔祭坛无法启动——
|
||||
// TC 自身所有注魔配方组件数上限就是 8。
|
||||
ARCANE_ASSEMBLER = ThaumcraftApi.addInfusionCraftingRecipe("ARCANEASSEMBLER", stack(ModBlocks.ARCANE_ASSEMBLER),
|
||||
5, new AspectList().add(Aspect.CRAFT, 64).add(Aspect.EXCHANGE, 32).add(Aspect.AURA, 16).add(Aspect.MAGIC, 16).add(Aspect.METAL, 8).add(Aspect.CRYSTAL, 8),
|
||||
ae("molecular_assembler"),
|
||||
tc("fire_shard"),
|
||||
tc("entropy_shard"),
|
||||
tc("order_shard"),
|
||||
tc("balance_shard"),
|
||||
tc("balanced_shard"),
|
||||
tc("water_shard"),
|
||||
tc("earth_shard"),
|
||||
tc("air_shard"),
|
||||
tc("salis_mundus"),
|
||||
createFullVisScepter());
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成"满充能"银木神秘权杖作为注魔配方的预期组件。
|
||||
*
|
||||
* ⚠ 关键:不要调用 TCVisHelper.setWandParts()!玩家实际合成的权杖(ArcaneSceptreRecipe →
|
||||
* WandCastingItem.createWandFor)是纯物品,custom_data 只有充能时写入的 tc_vis_* 键,
|
||||
* 没有 tc_wand_rod/tc_wand_cap。若在预期侧写入这些键,hasExpectedComponents 逐键比对时
|
||||
* 会因实物缺少而永不匹配——满充能权杖也放不进去。
|
||||
*
|
||||
* getWandMaxVis 用 fallbackVariant "silverwood_thaumium_scepter" 解析即可得 15000
|
||||
* (银木杆 10000 × 权杖 1.5),无需依赖栈上的部件数据。
|
||||
*/
|
||||
private static ItemStack createFullVisScepter() {
|
||||
ItemStack scepter = tc("silverwood_thaumium_scepter");
|
||||
if (scepter.isEmpty()) return scepter;
|
||||
TCVisHelper.setWandParts(scepter, "silverwood", "thaumium", true);
|
||||
int max = TCVisHelper.getWandMaxVis(scepter, "silverwood_thaumium_scepter");
|
||||
for (Aspect a : Aspect.getPrimalAspects()) {
|
||||
TCVisHelper.storeVis(scepter, a, max, max);
|
||||
|
||||
@@ -7,13 +7,13 @@ import java.util.Map;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import appeng.api.config.Actionable;
|
||||
import appeng.api.networking.IGrid;
|
||||
import appeng.api.networking.IGridServiceProvider;
|
||||
import appeng.api.networking.security.IActionSource;
|
||||
import appeng.api.networking.storage.IStorageService;
|
||||
import appeng.api.storage.MEStorage;
|
||||
import appeng.api.storage.StorageHelper;
|
||||
import appeng.api.networking.security.IActionSource;
|
||||
import thaumicenergistics.api.grid.IEssentiaGrid;
|
||||
import thaumicenergistics.api.storage.AspectStack;
|
||||
import thaumicenergistics.api.storage.IAspectStack;
|
||||
@@ -101,11 +101,28 @@ public class EssentiaGridService implements IEssentiaGrid, IGridServiceProvider
|
||||
@Override
|
||||
public long injectEssentia(ResourceLocation aspectId, long amount, boolean simulate) {
|
||||
if (amount <= 0) return 0;
|
||||
// 写入 AE2 原生存储(源质存储元件/存储总线可见、持久化)
|
||||
var nativeInjected = injectNative(aspectId, amount, simulate);
|
||||
if (nativeInjected < amount) {
|
||||
// 原生存储空间不足时剩余部分退回内存 monitor(供注魔供应器等内部读取)
|
||||
long remaining = amount - nativeInjected;
|
||||
if (!simulate) {
|
||||
long current = monitor.get(aspectId);
|
||||
monitor.set(aspectId, current + amount);
|
||||
monitor.set(aspectId, current + remaining);
|
||||
}
|
||||
return 0;
|
||||
return amount - nativeInjected - remaining; // 全部接受(monitor 兜底)
|
||||
}
|
||||
return 0; // 全部写入原生存储
|
||||
}
|
||||
|
||||
private long injectNative(ResourceLocation aspectId, long amount, boolean simulate) {
|
||||
var storage = getNativeStorage();
|
||||
if (storage == null) return 0;
|
||||
var key = AEssentiaKey.of(aspectId);
|
||||
if (key == null) return 0;
|
||||
return StorageHelper.poweredInsert(
|
||||
grid.getEnergyService(), storage, key, amount,
|
||||
IActionSource.empty(), simulate ? Actionable.SIMULATE : Actionable.MODULATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -34,11 +34,11 @@ public enum VibrationChamberProvider implements IBlockComponentProvider, IServer
|
||||
CompoundTag data = accessor.getServerData();
|
||||
if (data == null || data.isEmpty()) return;
|
||||
|
||||
// 吸力类型
|
||||
// 当前仓内源质类型(通配吸力模式下显示仓内实际燃料)
|
||||
if (data.contains(TAG_ASPECT)) {
|
||||
String aspect = data.getString(TAG_ASPECT);
|
||||
String aspectKey = "thaumicenergistics.aspect." + aspect;
|
||||
tooltip.add(Component.translatable("thaumicenergistics.jade.suction_type",
|
||||
tooltip.add(Component.translatable("thaumicenergistics.jade.current_aspect",
|
||||
Component.translatable(aspectKey)));
|
||||
}
|
||||
|
||||
@@ -93,8 +93,8 @@ public enum VibrationChamberProvider implements IBlockComponentProvider, IServer
|
||||
data.putDouble(TAG_ENERGY_MAX, chamber.getMaxEnergyStorage());
|
||||
data.putDouble(TAG_ENERGY_OUTPUT, chamber.getMaxOutputPerTick());
|
||||
data.putBoolean(TAG_GENERATING, chamber.isActive());
|
||||
if (chamber.getSuctionType(null) != null) {
|
||||
data.putString(TAG_ASPECT, chamber.getSuctionType(null).getTag());
|
||||
if (chamber.getEssentiaType(null) != null) {
|
||||
data.putString(TAG_ASPECT, chamber.getEssentiaType(null).getTag());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import net.minecraft.world.phys.BlockHitResult;
|
||||
import appeng.helpers.WirelessTerminalMenuHost;
|
||||
import appeng.items.tools.powered.WirelessTerminalItem;
|
||||
import appeng.menu.locator.ItemMenuHostLocator;
|
||||
import appeng.menu.me.common.MEStorageMenu;
|
||||
|
||||
/**
|
||||
* 无线源质终端物品。继承 AE2 的WirelessTerminalItem
|
||||
@@ -25,7 +24,7 @@ public class WirelessEssentiaTerminalItem extends WirelessTerminalItem {
|
||||
|
||||
@Override
|
||||
public MenuType<?> getMenuType() {
|
||||
return MEStorageMenu.WIRELESS_TYPE;
|
||||
return thaumicenergistics.init.ModMenuTypes.WIRELESS_ESSENTIA_TERMINAL.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -50,6 +50,20 @@ public final class ThENetworking {
|
||||
(pkt, ctx) -> pkt.handleOnClient(ctx)
|
||||
);
|
||||
|
||||
// EssentiaTerminalFillC2SPacket — C2S(客户端→服务端,源质终端右键装罐)
|
||||
registrar.playToServer(
|
||||
EssentiaTerminalFillC2SPacket.TYPE,
|
||||
EssentiaTerminalFillC2SPacket.STREAM_CODEC,
|
||||
(pkt, ctx) -> pkt.handleOnServer(ctx)
|
||||
);
|
||||
|
||||
// EssentiaTerminalDepositC2SPacket — C2S(客户端→服务端,手持源质容器右键存入网络)
|
||||
registrar.playToServer(
|
||||
EssentiaTerminalDepositC2SPacket.TYPE,
|
||||
EssentiaTerminalDepositC2SPacket.STREAM_CODEC,
|
||||
(pkt, ctx) -> pkt.handleOnServer(ctx)
|
||||
);
|
||||
|
||||
// ResearchGateErrorPacket — S2C(服务端→客户端,研究未解锁拦截提示,携带研究键)
|
||||
registrar.playToClient(
|
||||
ResearchGateErrorPacket.TYPE,
|
||||
|
||||
@@ -60,7 +60,6 @@ public class EssentiaExportBusPart extends IOBusPart
|
||||
|
||||
private static final double IDLE_POWER = 0.5;
|
||||
private static final double TRANSFER_POWER = EssentiaConversionHelper.AE_PER_ESSENTIA;
|
||||
private static final int TRANSFER_RATE = 8;
|
||||
|
||||
private boolean isWorkingFlag = false;
|
||||
|
||||
@@ -70,6 +69,14 @@ public class EssentiaExportBusPart extends IOBusPart
|
||||
|
||||
private long storedEnergy = 0;
|
||||
|
||||
/**
|
||||
* 单次导出量 = 基础速率 8 × 每 tick 操作次数(受加速卡影响)。
|
||||
* AE2 加速卡语义:0卡=1、1卡=8、2卡=32、3卡=64、4卡=96(getOperationsPerTick)。
|
||||
*/
|
||||
private long getExportRate() {
|
||||
return 8L * Math.max(1, getOperationsPerTick());
|
||||
}
|
||||
|
||||
public EssentiaExportBusPart(IPartItem<?> partItem) {
|
||||
super(TickRates.ExportBus, Set.of(AEssentiaKeyType.INSTANCE), partItem);
|
||||
this.getMainNode().setIdlePowerUsage(IDLE_POWER);
|
||||
@@ -122,7 +129,7 @@ public class EssentiaExportBusPart extends IOBusPart
|
||||
if (key == null) return false;
|
||||
|
||||
long extracted = grid.getStorageService().getInventory().extract(
|
||||
key, TRANSFER_RATE, Actionable.MODULATE, IActionSource.ofMachine(this));
|
||||
key, getExportRate(), Actionable.MODULATE, IActionSource.ofMachine(this));
|
||||
if (extracted <= 0) return false;
|
||||
|
||||
// 尝试注入到相邻方块:先 IAspectContainer,再回退到 IEssentiaTransport
|
||||
|
||||
@@ -62,6 +62,14 @@ public class EssentiaImportBusPart extends IOBusPart
|
||||
|
||||
private boolean isWorkingFlag = false;
|
||||
|
||||
/**
|
||||
* 单次传输量 = 基础速率 × 每 tick 操作次数(受加速卡影响)。
|
||||
* AE2 加速卡语义:0卡=1、1卡=8、2卡=32、3卡=64、4卡=96(getOperationsPerTick)。
|
||||
*/
|
||||
private int getTransferRate() {
|
||||
return TRANSFER_RATE * Math.max(1, getOperationsPerTick());
|
||||
}
|
||||
|
||||
private final KeyTypeSelection keyTypeSelection = new KeyTypeSelection(
|
||||
selection -> { },
|
||||
keyType -> keyType == AEssentiaKeyType.INSTANCE);
|
||||
@@ -104,7 +112,7 @@ public class EssentiaImportBusPart extends IOBusPart
|
||||
BlockEntity adjacentBE = level.getBlockEntity(adjacentPos);
|
||||
if (adjacentBE == null) return false;
|
||||
|
||||
AspectStack extracted = TCReflection.extractEssentia(adjacentBE, TRANSFER_RATE);
|
||||
AspectStack extracted = TCReflection.extractEssentia(adjacentBE, getTransferRate());
|
||||
if (extracted == null || extracted.isEmpty()) return false;
|
||||
|
||||
long injected = this.injectToNetwork(grid, extracted);
|
||||
|
||||
@@ -10,7 +10,6 @@ import appeng.api.parts.IPartItem;
|
||||
import appeng.api.parts.IPartModel;
|
||||
import appeng.api.util.KeyTypeSelection;
|
||||
import appeng.items.parts.PartModels;
|
||||
import appeng.menu.me.common.MEStorageMenu;
|
||||
import appeng.parts.PartModel;
|
||||
import appeng.parts.reporting.AbstractTerminalPart;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
@@ -54,7 +53,7 @@ public class EssentiaTerminalPart extends AbstractTerminalPart {
|
||||
|
||||
@Override
|
||||
public MenuType<?> getMenuType(Player player) {
|
||||
return MEStorageMenu.TYPE;
|
||||
return thaumicenergistics.init.ModMenuTypes.ESSENTIA_TERMINAL.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -53,6 +53,9 @@ public class VisInterfacePart extends P2PTunnelPart<VisInterfacePart> implements
|
||||
/** 抽取颜色在没有新抽取后保持点亮的时间(ms),超时清除。 */
|
||||
private static final long TIME_TO_CLEAR = 500L;
|
||||
|
||||
/** 懒 tick 周期:固定 30 tick(vis 传输为低频操作,无需实时轮询)。 */
|
||||
private static final int TICK_RATE = 30;
|
||||
|
||||
private static final String NBT_KEY_UID = "uid";
|
||||
private static final String NBT_KEY_IS_PROVIDER = "isProvider";
|
||||
|
||||
@@ -290,7 +293,7 @@ public class VisInterfacePart extends P2PTunnelPart<VisInterfacePart> implements
|
||||
|
||||
@Override
|
||||
public TickingRequest getTickingRequest(IGridNode node) {
|
||||
return new TickingRequest(30, 30, false);
|
||||
return new TickingRequest(TICK_RATE, TICK_RATE, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -51,7 +51,6 @@ import thaumcraft.api.aspects.AspectList;
|
||||
import thaumcraft.api.crafting.IArcaneRecipe;
|
||||
|
||||
import thaumicenergistics.api.grid.IEssentiaGrid;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
import thaumicenergistics.common.integration.tc.ArcaneCraftingPattern;
|
||||
import thaumicenergistics.common.integration.tc.TCReflection;
|
||||
import thaumicenergistics.common.inventory.HandlerKnowledgeCore;
|
||||
@@ -314,7 +313,6 @@ public class TileArcaneAssembler extends ThETileBase
|
||||
|
||||
boolean enoughVis = hasEnoughVisForCraft();
|
||||
if (!enoughVis) {
|
||||
ThaumicEnergistics.LOG.debug("[AA] craftingTick: vis 不足,等待补充 (storedVis cvis={})", storedVis.visSize());
|
||||
return TickRateModulation.SAME;
|
||||
}
|
||||
|
||||
@@ -331,7 +329,6 @@ public class TileArcaneAssembler extends ThETileBase
|
||||
long inserted = storage.getInventory().insert(
|
||||
outputKey, output.getCount(), Actionable.SIMULATE, actionSource);
|
||||
if (inserted < output.getCount()) {
|
||||
ThaumicEnergistics.LOG.debug("[AA] craftingTick: 产物无法插入网络(模拟 inserted={}/{}),等待", inserted, output.getCount());
|
||||
return TickRateModulation.SAME;
|
||||
}
|
||||
|
||||
@@ -339,7 +336,6 @@ public class TileArcaneAssembler extends ThETileBase
|
||||
|
||||
storage.getInventory().insert(
|
||||
outputKey, output.getCount(), Actionable.MODULATE, actionSource);
|
||||
ThaumicEnergistics.LOG.debug("[AA] craftingTick: 完成!插入产物 {}x{} 到网络", outputKey, output.getCount());
|
||||
|
||||
isCrafting = false;
|
||||
currentRecipe = null;
|
||||
@@ -359,11 +355,8 @@ public class TileArcaneAssembler extends ThETileBase
|
||||
double extracted = energy.extractAEPower(need, Actionable.MODULATE, PowerMultiplier.CONFIG);
|
||||
if (extracted >= need * 0.9) {
|
||||
craftTickCounter += ticksSinceLast;
|
||||
ThaumicEnergistics.LOG.debug("[AA] craftingTick: 推进 {}/{} (抽电 {}/{})", craftTickCounter, ticksPerCraft, extracted, need);
|
||||
// 节流同步合成进度到客户端(GUI 第7格进度条动画)
|
||||
maybeMarkVisDirty();
|
||||
} else {
|
||||
ThaumicEnergistics.LOG.debug("[AA] craftingTick: 能量不足 (抽到 {}/{}),不推进", extracted, need);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,19 +439,13 @@ public class TileArcaneAssembler extends ThETileBase
|
||||
ArcaneAssemblerPattern ap = buildArcanePattern(acp);
|
||||
if (ap != null) {
|
||||
result.add(ap);
|
||||
ThaumicEnergistics.LOG.debug("[AA] getAvailablePatterns: pattern primaryOutput={}, definition={}",
|
||||
ap.getPrimaryOutput(), ap.getDefinition());
|
||||
}
|
||||
}
|
||||
ThaumicEnergistics.LOG.debug("[AA] getAvailablePatterns: kCore valid={}, handler patterns={}, returned={}",
|
||||
!kCore.isEmpty(), patterns.size(), result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pushPattern(IPatternDetails patternDetails, KeyCounter[] inputHolder) {
|
||||
ThaumicEnergistics.LOG.info("[AA] pushPattern(provider) called, pattern={}, isArcanePattern={}",
|
||||
patternDetails, patternDetails instanceof ArcaneAssemblerPattern);
|
||||
if (patternDetails instanceof ArcaneAssemblerPattern arcanePattern) {
|
||||
return beginCrafting(arcanePattern);
|
||||
}
|
||||
@@ -490,9 +477,6 @@ public class TileArcaneAssembler extends ThETileBase
|
||||
private boolean beginCrafting(ArcaneAssemblerPattern arcanePattern) {
|
||||
AspectList aspects = arcanePattern.getAspects();
|
||||
ItemStack result = arcanePattern.getResultStack();
|
||||
ThaumicEnergistics.LOG.info("[AA] beginCrafting: isCrafting={}, active={}, aspectsEmpty={}, resultEmpty={}",
|
||||
isCrafting, mainNode.isActive(),
|
||||
aspects == null || aspects.isEmpty(), result.isEmpty());
|
||||
if (isCrafting || !mainNode.isActive()) return false;
|
||||
if (aspects == null || aspects.isEmpty() || result.isEmpty()) return false;
|
||||
|
||||
@@ -504,7 +488,6 @@ public class TileArcaneAssembler extends ThETileBase
|
||||
this.internalInventory.setItem(TARGET_SLOT, result.copy());
|
||||
setChanged();
|
||||
markForUpdate();
|
||||
ThaumicEnergistics.LOG.info("[AA] beginCrafting: STARTED, result={}, aspects={}", result, aspects.visSize());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -593,7 +576,6 @@ public class TileArcaneAssembler extends ThETileBase
|
||||
// 守卫 getGrid()!=null:装配器未连 ME 网络(无相邻 cable)时 getGrid() 为 null,
|
||||
// 此时 requestUpdate 内部 node.getGrid().getCraftingService() 会 NPE,导致 kCore 插入事务回滚 → "无法绑定"。
|
||||
if (level != null && !level.isClientSide() && mainNode.getGrid() != null) {
|
||||
ThaumicEnergistics.LOG.info("[AA] kCore slot changed → requestUpdate");
|
||||
ICraftingProvider.requestUpdate(mainNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,12 +40,16 @@ public class TileEssentiaVibrationChamber extends ThETileBase implements IInWorl
|
||||
|
||||
private static final int MAX_ESSENTIA = 64;
|
||||
private static final int BASE_BURN_TICKS = 800;
|
||||
private static final double BASE_AE_PER_TICK = 20.0;
|
||||
private static final double BASE_AE_PER_TICK = 200.0;
|
||||
private static final int IDLE_POWER = 1;
|
||||
|
||||
// 能量槽配置:8KFE = 8000 AE,对外最大输出 400 AE/t
|
||||
public static final double MAX_ENERGY_STORAGE = 8000.0;
|
||||
public static final double MAX_OUTPUT_PER_TICK = 400.0;
|
||||
// 能量槽配置:16KFE = 16000 AE,对外最大输出 1000 AE/t
|
||||
public static final double MAX_ENERGY_STORAGE = 16000.0;
|
||||
public static final double MAX_OUTPUT_PER_TICK = 2000.0;
|
||||
|
||||
// 懒 tick 周期:燃烧中 10 tick、空闲 40 tick(能量生产无需实时响应,低频即可)
|
||||
private static final int TICK_RATE_BURNING = 10;
|
||||
private static final int TICK_RATE_IDLE = 40;
|
||||
|
||||
protected final IManagedGridNode mainNode;
|
||||
private final IGridNodeListener<TileEssentiaVibrationChamber> nodeListener = new IGridNodeListener<>() {
|
||||
@@ -62,7 +66,6 @@ public class TileEssentiaVibrationChamber extends ThETileBase implements IInWorl
|
||||
private double aePerTick;
|
||||
private int storedEssentia;
|
||||
private Aspect currentAspect;
|
||||
private Aspect suctionAspect = Aspect.ENERGY;
|
||||
|
||||
private double storedEnergy;
|
||||
|
||||
@@ -97,7 +100,7 @@ public class TileEssentiaVibrationChamber extends ThETileBase implements IInWorl
|
||||
|
||||
@Override
|
||||
public TickingRequest getTickingRequest(IGridNode node) {
|
||||
return new TickingRequest(10, 40, false);
|
||||
return new TickingRequest(TICK_RATE_BURNING, TICK_RATE_IDLE, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -177,8 +180,7 @@ public class TileEssentiaVibrationChamber extends ThETileBase implements IInWorl
|
||||
int srcAmt = source.getEssentiaAmount(sourceSide);
|
||||
if (!canOut) continue;
|
||||
if (srcType == null) continue;
|
||||
String srcTag = srcType.getTag();
|
||||
if (!srcTag.equals("ignis") && !srcTag.equals("potentia") && !srcTag.equals("energy")) continue;
|
||||
// 所有要素均可作为燃料(火/能量效率更高,其他要素 1/2)
|
||||
|
||||
if (space <= 0) return;
|
||||
|
||||
@@ -187,7 +189,7 @@ public class TileEssentiaVibrationChamber extends ThETileBase implements IInWorl
|
||||
if (taken > 0) {
|
||||
storedEssentia += taken;
|
||||
currentAspect = srcType;
|
||||
suctionAspect = srcType;
|
||||
// suctionAspect 固定为 null(通配吸力),不锁定单一类型
|
||||
setChanged();
|
||||
return;
|
||||
}
|
||||
@@ -240,9 +242,25 @@ public class TileEssentiaVibrationChamber extends ThETileBase implements IInWorl
|
||||
private boolean startBurning() {
|
||||
if (currentAspect == null) currentAspect = Aspect.FIRE;
|
||||
|
||||
boolean isPotentia = currentAspect != null && (currentAspect == Aspect.ENERGY || currentAspect.getTag().equals("potentia") || currentAspect.getTag().equals("energy"));
|
||||
int burnTime = isPotentia ? (int)(BASE_BURN_TICKS / 1.6f) : BASE_BURN_TICKS / 2;
|
||||
double power = isPotentia ? BASE_AE_PER_TICK * 1.6 : BASE_AE_PER_TICK;
|
||||
String tag = currentAspect.getTag();
|
||||
boolean isPotentia = currentAspect == Aspect.ENERGY || tag.equals("potentia") || tag.equals("energy");
|
||||
boolean isOther = !tag.equals("ignis") && !isPotentia;
|
||||
|
||||
int burnTime;
|
||||
double power;
|
||||
if (isPotentia) {
|
||||
// 能量:1.6 倍功率,燃烧更久
|
||||
burnTime = (int)(BASE_BURN_TICKS / 1.6f);
|
||||
power = BASE_AE_PER_TICK * 1.6;
|
||||
} else if (isOther) {
|
||||
// 其他所有要素:基础效率的 1/2
|
||||
burnTime = BASE_BURN_TICKS / 2;
|
||||
power = BASE_AE_PER_TICK / 2.0;
|
||||
} else {
|
||||
// 火:基础效率
|
||||
burnTime = BASE_BURN_TICKS / 2;
|
||||
power = BASE_AE_PER_TICK;
|
||||
}
|
||||
|
||||
burnTicksRemaining = burnTime;
|
||||
totalBurnTicks = burnTime;
|
||||
@@ -254,17 +272,16 @@ public class TileEssentiaVibrationChamber extends ThETileBase implements IInWorl
|
||||
@Override public boolean canInputFrom(Direction side) { return true; }
|
||||
@Override public boolean canOutputTo(Direction side) { return true; }
|
||||
@Override public void setSuction(Aspect aspect, int amount) {
|
||||
this.suctionAspect = aspect;
|
||||
// 通配吸力:始终吸所有源质类型,忽略外部设置
|
||||
}
|
||||
@Override public Aspect getSuctionType(Direction side) {
|
||||
return suctionAspect;
|
||||
return null; // null = 通配吸力:管道可输送任意类型源质
|
||||
}
|
||||
@Override public int getSuctionAmount(Direction side) { return 128; }
|
||||
|
||||
private boolean isFuelAspect(Aspect aspect) {
|
||||
if (aspect == null) return false;
|
||||
String tag = aspect.getTag();
|
||||
return tag.equals("ignis") || tag.equals("potentia") || tag.equals("energy");
|
||||
// 所有要素均可作为燃料(火/能量效率更高,其他要素 1/2)
|
||||
return aspect != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -298,11 +315,7 @@ public class TileEssentiaVibrationChamber extends ThETileBase implements IInWorl
|
||||
@Override public boolean renderExtendedTube() { return false; }
|
||||
|
||||
public void setSuctionType(Aspect aspect) {
|
||||
if (aspect == Aspect.FIRE || aspect == Aspect.ENERGY) {
|
||||
suctionAspect = aspect;
|
||||
setChanged();
|
||||
markForUpdate();
|
||||
}
|
||||
// 通配吸力模式下无需设置类型;保留方法避免编译错误
|
||||
}
|
||||
|
||||
public int getStoredEssentia() { return storedEssentia; }
|
||||
@@ -338,7 +351,7 @@ public class TileEssentiaVibrationChamber extends ThETileBase implements IInWorl
|
||||
storedEnergy = tag.getDouble("StoredEnergy");
|
||||
if (storedEnergy > MAX_ENERGY_STORAGE) storedEnergy = MAX_ENERGY_STORAGE;
|
||||
if (tag.contains("AspectTag")) currentAspect = Aspect.getAspect(tag.getString("AspectTag"));
|
||||
if (tag.contains("SuctionTag")) suctionAspect = Aspect.getAspect(tag.getString("SuctionTag"));
|
||||
// suctionAspect 固定为 null(通配吸力),不读取 SuctionTag
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -352,6 +365,6 @@ public class TileEssentiaVibrationChamber extends ThETileBase implements IInWorl
|
||||
tag.putInt("StoredEssentia", storedEssentia);
|
||||
tag.putDouble("StoredEnergy", storedEnergy);
|
||||
if (currentAspect != null) tag.putString("AspectTag", currentAspect.getTag());
|
||||
if (suctionAspect != null) tag.putString("SuctionTag", suctionAspect.getTag());
|
||||
// suctionAspect 固定为 null(通配吸力),不写入 SuctionTag
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package thaumicenergistics.init;
|
||||
|
||||
import appeng.api.implementations.menuobjects.IPortableTerminal;
|
||||
import appeng.api.storage.ITerminalHost;
|
||||
import appeng.menu.implementations.MenuTypeBuilder;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
@@ -27,8 +28,16 @@ public final class ModMenuTypes {
|
||||
REGISTRY.register("essentia_vibration_chamber", () -> IMenuTypeExtension.create(ContainerEssentiaVibrationChamber::new));
|
||||
public static final Supplier<MenuType<ContainerArcaneAssembler>> ARCANE_ASSEMBLER =
|
||||
REGISTRY.register("arcane_assembler", () -> IMenuTypeExtension.create(ContainerArcaneAssembler::new));
|
||||
public static final Supplier<MenuType<ContainerEssentiaCellTerminal>> ESSENTIA_TERMINAL =
|
||||
REGISTRY.register("essentia_terminal", () -> IMenuTypeExtension.create(ContainerEssentiaCellTerminal::new));
|
||||
/** 源质终端菜单:有线段(挂在线缆上的终端部件)。 */
|
||||
public static final Supplier<MenuType<EssentiaTerminalMenu>> ESSENTIA_TERMINAL =
|
||||
REGISTRY.register("essentia_terminal", () ->
|
||||
MenuTypeBuilder.create(EssentiaTerminalMenu::new, ITerminalHost.class)
|
||||
.buildUnregistered(ThaumicEnergistics.id("essentia_terminal")));
|
||||
/** 源质终端菜单:无线段(手持无线源质终端)。 */
|
||||
public static final Supplier<MenuType<EssentiaTerminalMenu>> WIRELESS_ESSENTIA_TERMINAL =
|
||||
REGISTRY.register("wireless_essentia_terminal", () ->
|
||||
MenuTypeBuilder.create(EssentiaTerminalMenu::new, IPortableTerminal.class)
|
||||
.buildUnregistered(ThaumicEnergistics.id("wireless_essentia_terminal")));
|
||||
public static final Supplier<MenuType<ContainerKnowledgeInscriber>> KNOWLEDGE_INSCRIBER =
|
||||
REGISTRY.register("knowledge_inscriber", () -> IMenuTypeExtension.create(ContainerKnowledgeInscriber::new));
|
||||
public static final Supplier<MenuType<ContainerDistillationPatternEncoder>> DISTILLATION_ENCODER =
|
||||
@@ -38,28 +47,28 @@ public final class ModMenuTypes {
|
||||
REGISTRY.register("essentia_import_bus", () ->
|
||||
MenuTypeBuilder.create(ContainerEssentiaImportBus::new, EssentiaImportBusPart.class)
|
||||
.withMenuTitle(host -> host.getName() != null ? host.getName() : Component.empty())
|
||||
.build("essentia_import_bus"));
|
||||
.buildUnregistered(ThaumicEnergistics.id("essentia_import_bus")));
|
||||
|
||||
public static final Supplier<MenuType<ContainerEssentiaExportBus>> ESSENTIA_EXPORT_BUS =
|
||||
REGISTRY.register("essentia_export_bus", () ->
|
||||
MenuTypeBuilder.create(ContainerEssentiaExportBus::new, EssentiaExportBusPart.class)
|
||||
.withMenuTitle(host -> host.getName() != null ? host.getName() : Component.empty())
|
||||
.build("essentia_export_bus"));
|
||||
.buildUnregistered(ThaumicEnergistics.id("essentia_export_bus")));
|
||||
|
||||
public static final Supplier<MenuType<ContainerEssentiaStorageBus>> ESSENTIA_STORAGE_BUS =
|
||||
REGISTRY.register("essentia_storage_bus", () ->
|
||||
MenuTypeBuilder.create(ContainerEssentiaStorageBus::new, EssentiaStorageBusPart.class)
|
||||
.withMenuTitle(host -> host.getName() != null ? host.getName() : Component.empty())
|
||||
.build("essentia_storage_bus"));
|
||||
.buildUnregistered(ThaumicEnergistics.id("essentia_storage_bus")));
|
||||
|
||||
public static final Supplier<MenuType<ContainerArcaneCraftingTerminal>> ARCANE_CRAFTING_TERMINAL =
|
||||
REGISTRY.register("arcane_crafting_terminal", () ->
|
||||
MenuTypeBuilder.create(ContainerArcaneCraftingTerminal::new, ITerminalHost.class)
|
||||
.build("arcane_crafting_terminal"));
|
||||
.buildUnregistered(ThaumicEnergistics.id("arcane_crafting_terminal")));
|
||||
|
||||
public static final Supplier<MenuType<ContainerEssentiaLevelEmitter>> ESSENTIA_LEVEL_EMITTER =
|
||||
REGISTRY.register("essentia_level_emitter", () ->
|
||||
MenuTypeBuilder.create(ContainerEssentiaLevelEmitter::new, EssentiaLevelEmitterPart.class)
|
||||
.withMenuTitle(host -> host.getName() != null ? host.getName() : Component.empty())
|
||||
.build("essentia_level_emitter"));
|
||||
.buildUnregistered(ThaumicEnergistics.id("essentia_level_emitter")));
|
||||
}
|
||||
|
||||
@@ -49,9 +49,8 @@ public class ThEJeiPlugin implements IModPlugin {
|
||||
Class.forName("tamaized.ae2jeiintegration.api.integrations.jei.IngredientConverters");
|
||||
Class.forName("thaumicenergistics.integration.jei.Ae2JeiCompat")
|
||||
.getMethod("register").invoke(null);
|
||||
ThaumicEnergistics.LOG.info("[JEI] AE2-JEI-Integration detected, essentia drag-conversion registered");
|
||||
} catch (ClassNotFoundException e) {
|
||||
ThaumicEnergistics.LOG.info("[JEI] AE2-JEI-Integration not present, essentia drag-conversion unavailable (optional)");
|
||||
// AE2-JEI-Integration 未安装:跳过源质拖拽转换(可选功能,不影响其他 JEI 功能)
|
||||
} catch (ReflectiveOperationException e) {
|
||||
ThaumicEnergistics.LOG.warn("[JEI] AE2-JEI-Integration registration failed: {}", e.toString());
|
||||
}
|
||||
@@ -83,7 +82,6 @@ public class ThEJeiPlugin implements IModPlugin {
|
||||
|
||||
@Override
|
||||
public void registerRecipeTransferHandlers(IRecipeTransferRegistration registration) {
|
||||
ThaumicEnergistics.LOG.info("[JEI] Registering ThEUseCraftingRecipeTransfer for ArcaneCraftingTerminal");
|
||||
// 注册 IRecipeTransferInfo 让 JEI 显示 "+" 按钮
|
||||
registration.addRecipeTransferHandler(new ArcaneCraftingTransferInfo());
|
||||
registration.addRecipeTransferHandler(new ArcaneRecipeTransferInfo());
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package thaumicenergistics.integration.jei;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -24,10 +26,14 @@ import net.neoforged.neoforge.network.PacketDistributor;
|
||||
|
||||
import thaumcraft.integration.jei.JeiArcaneRecipe;
|
||||
import thaumcraft.integration.jei.ThaumcraftJeiPlugin;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
import thaumicenergistics.common.container.ContainerArcaneCraftingTerminal;
|
||||
import thaumicenergistics.init.ModMenuTypes;
|
||||
|
||||
import static appeng.integration.modules.itemlists.TransferHelper.BLUE_PLUS_BUTTON_COLOR;
|
||||
import static appeng.integration.modules.itemlists.TransferHelper.BLUE_SLOT_HIGHLIGHT_COLOR;
|
||||
import static appeng.integration.modules.itemlists.TransferHelper.ORANGE_PLUS_BUTTON_COLOR;
|
||||
import static appeng.integration.modules.itemlists.TransferHelper.RED_SLOT_HIGHLIGHT_COLOR;
|
||||
|
||||
/**
|
||||
* 奥术合成终端专用的 JEI 奥术配方转移处理器。
|
||||
* 点击 "+" 时发送 FillCraftingGridFromRecipePacket 从 ME 网络填充材料。
|
||||
@@ -65,13 +71,31 @@ public class ThEUseArcaneRecipeTransfer
|
||||
boolean maxTransfer,
|
||||
boolean doTransfer) {
|
||||
|
||||
ThaumicEnergistics.LOG.info("[JEI-Arcane] transferRecipe called, recipeId={}, doTransfer={}",
|
||||
recipe.id(), doTransfer);
|
||||
|
||||
boolean craftMissing = AbstractContainerScreen.hasControlDown();
|
||||
|
||||
// 预览:始终 null(绿色按钮),由服务端处理实际缺失
|
||||
// 构建 3x3 合成格 slot→ingredient 映射,检测材料缺失(与普通配方一致)
|
||||
var slotToIngredientMap = buildSlotToIngredientMap(recipe);
|
||||
var missingSlots = menu.findMissingIngredients(slotToIngredientMap);
|
||||
|
||||
// 全部缺失 → 红色,无法点击
|
||||
if (missingSlots.missingSlots().size() == slotToIngredientMap.size()) {
|
||||
var inputSlots = display.getSlotViews(RecipeIngredientRole.INPUT);
|
||||
var missingSlotViews = missingSlots.missingSlots().stream()
|
||||
.map(idx -> idx < inputSlots.size() ? inputSlots.get(idx) : null)
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
return helper.createUserErrorForMissingSlots(
|
||||
appeng.core.localization.ItemModText.NO_ITEMS.text(), missingSlotViews);
|
||||
}
|
||||
|
||||
// 预览:检查缺失情况,返回对应的错误渲染
|
||||
if (!doTransfer) {
|
||||
if (missingSlots.totalSize() != 0) {
|
||||
int color = missingSlots.anyMissing() ? ORANGE_PLUS_BUTTON_COLOR : BLUE_PLUS_BUTTON_COLOR;
|
||||
// 有缺料 → Type.ERROR 阻止转移(按钮不可点),有料也不允许部分填充
|
||||
return new ThEUseCraftingRecipeTransfer.MissingSlotsError(
|
||||
missingSlots, craftMissing, color, missingSlots.anyMissing(), menu);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -88,10 +112,23 @@ public class ThEUseArcaneRecipeTransfer
|
||||
// 奥术配方不在 vanilla 配方管理器中,recipeId 传 null
|
||||
// 服务端会用 Ingredient.of(templateItems) 重建,然后从 ME 网络提取
|
||||
var packet = new FillCraftingGridFromRecipePacket(null, templateItems, craftMissing);
|
||||
ThaumicEnergistics.LOG.info("[JEI-Arcane] Sending FillCraftingGridFromRecipePacket, templateCount={}",
|
||||
templateItems.stream().filter(s -> !s.isEmpty()).count());
|
||||
PacketDistributor.sendToServer(packet);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据奥术配方 inputs(9 槽 List<List<ItemStack>>)构建 3x3 合成格 slot → ingredient 映射。
|
||||
*/
|
||||
private static Map<Integer, Ingredient> buildSlotToIngredientMap(JeiArcaneRecipe recipe) {
|
||||
Map<Integer, Ingredient> map = new HashMap<>();
|
||||
var inputs = recipe.inputs();
|
||||
for (int i = 0; i < inputs.size() && i < 9; i++) {
|
||||
var variants = inputs.get(i);
|
||||
if (variants != null && !variants.isEmpty()) {
|
||||
map.put(i, Ingredient.of(variants.stream()));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +82,6 @@ public class ThEUseCraftingRecipeTransfer
|
||||
boolean doTransfer) {
|
||||
|
||||
var recipe = recipeHolder.value();
|
||||
ThaumicEnergistics.LOG.info("[JEI] transferRecipe called, recipeId={}, doTransfer={}, player={}",
|
||||
recipeHolder.id(), doTransfer, player.getName().getString());
|
||||
if (recipe.getType() != RecipeType.CRAFTING) {
|
||||
ThaumicEnergistics.LOG.warn("[JEI] Not a crafting recipe, type={}", recipe.getType());
|
||||
return helper.createInternalError();
|
||||
@@ -96,16 +94,14 @@ public class ThEUseCraftingRecipeTransfer
|
||||
}
|
||||
|
||||
boolean craftMissing = AbstractContainerScreen.hasControlDown();
|
||||
// 自行构建 slot→ingredient 映射(0-8 对应 3x3 合成格),
|
||||
// 不依赖 helper.getGuiSlotIndexToIngredientMap(需要 IRecipeTransferInfo 才能正确工作)
|
||||
var slotToIngredientMap = buildSlotToIngredientMap(recipe);
|
||||
// 用 JEI 官方映射 helper.getGuiSlotIndexToIngredientMap:配方居中摆入 3x3 网格,
|
||||
// 键空间与 JEI 输入槽视图完全一致。之前用 ensure3by3CraftingMatrix(左上对齐填充),
|
||||
// 对非 3x3 配方(如 2x2 笔与墨)索引错位,导致缺失高亮画到错误的格子上。
|
||||
var slotToIngredientMap = helper.getGuiSlotIndexToIngredientMap(recipeHolder);
|
||||
var missingSlots = menu.findMissingIngredients(slotToIngredientMap);
|
||||
ThaumicEnergistics.LOG.info("[JEI] missingSlots: missing={}, craftable={}, total={}",
|
||||
missingSlots.missingSlots().size(), missingSlots.craftableSlots().size(), missingSlots.totalSize());
|
||||
|
||||
// 全部缺失 → 红色,无法点击
|
||||
if (missingSlots.missingSlots().size() == slotToIngredientMap.size()) {
|
||||
ThaumicEnergistics.LOG.info("[JEI] All items missing, returning NO_ITEMS error");
|
||||
var inputSlots = display.getSlotViews(RecipeIngredientRole.INPUT);
|
||||
var missingSlotViews = missingSlots.missingSlots().stream()
|
||||
.map(idx -> idx < inputSlots.size() ? inputSlots.get(idx) : null)
|
||||
@@ -119,7 +115,8 @@ public class ThEUseCraftingRecipeTransfer
|
||||
if (!doTransfer) {
|
||||
if (missingSlots.totalSize() != 0) {
|
||||
int color = missingSlots.anyMissing() ? ORANGE_PLUS_BUTTON_COLOR : BLUE_PLUS_BUTTON_COLOR;
|
||||
return new MissingSlotsError(missingSlots, craftMissing, color);
|
||||
// 有缺料 → USER_FACING 阻止转移(按钮不可点);仅可合成(craftable)不阻止(可触发自动合成)
|
||||
return new MissingSlotsError(missingSlots, craftMissing, color, missingSlots.anyMissing(), menu);
|
||||
}
|
||||
// 全部物品都有 → null = 绿色按钮
|
||||
return null;
|
||||
@@ -145,42 +142,28 @@ public class ThEUseCraftingRecipeTransfer
|
||||
}
|
||||
|
||||
var packet = new FillCraftingGridFromRecipePacket(recipeId, templateItems, craftMissing);
|
||||
ThaumicEnergistics.LOG.info("[JEI] Sending FillCraftingGridFromRecipePacket, recipeId={}, templateCount={}, craftMissing={}",
|
||||
recipeId, templateItems.stream().filter(s -> !s.isEmpty()).count(), craftMissing);
|
||||
PacketDistributor.sendToServer(packet);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配方构建 3x3 合成格的 slot index → ingredient 映射。
|
||||
* 使用 CraftingRecipeUtil.ensure3by3CraftingMatrix 确保 9 格对齐,
|
||||
* 与实际转移时的索引体系完全一致。
|
||||
*/
|
||||
private static Map<Integer, Ingredient> buildSlotToIngredientMap(CraftingRecipe recipe) {
|
||||
var ingredients = CraftingRecipeUtil.ensure3by3CraftingMatrix(recipe);
|
||||
Map<Integer, Ingredient> map = new HashMap<>();
|
||||
for (int i = 0; i < ingredients.size() && i < 9; i++) {
|
||||
var ing = ingredients.get(i);
|
||||
if (!ing.isEmpty()) {
|
||||
map.put(i, ing);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缺失/可合成物品的预览错误渲染器。
|
||||
* 参考 AE2-JEI-Integration.UseCraftingRecipeTransfer.ErrorRenderer。
|
||||
* 供奥术配方转移(ThEUseArcaneRecipeTransfer)复用。
|
||||
* blockTransfer=true 时返回 ERROR(阻止转移,按钮灰/橙不可点)。
|
||||
*/
|
||||
private record MissingSlotsError(
|
||||
public record MissingSlotsError(
|
||||
ContainerArcaneCraftingTerminal.MissingIngredientSlots indices,
|
||||
boolean craftMissing,
|
||||
int color) implements IRecipeTransferError {
|
||||
int color,
|
||||
boolean blockTransfer,
|
||||
ContainerArcaneCraftingTerminal menu) implements IRecipeTransferError {
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.COSMETIC;
|
||||
// USER_FACING 阻止转移(allowsTransfer=false)且显示错误;COSMETIC 仅预览
|
||||
return blockTransfer ? Type.USER_FACING : Type.COSMETIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -195,16 +178,20 @@ public class ThEUseCraftingRecipeTransfer
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(recipeX, recipeY, 0);
|
||||
|
||||
var slotViews = slots.getSlotViews(RecipeIngredientRole.INPUT);
|
||||
for (int i = 0; i < slotViews.size(); i++) {
|
||||
var slotView = slotViews.get(i);
|
||||
// 用 JEI 槽视图 drawHighlight 画高亮(AE2-JEI-Integration 官方做法)。
|
||||
// 不能用 menu.slots 的容器坐标:奥术终端槽位顺序与配方格不对应,
|
||||
// 且 translate(recipeX,recipeY) 后再用相对坐标会双重偏移 → 红框画到屏幕外。
|
||||
var inputSlots = slots.getSlotViews(RecipeIngredientRole.INPUT);
|
||||
for (int i = 0; i < 9; i++) {
|
||||
boolean missing = indices.missingSlots().contains(i);
|
||||
boolean craftable = indices.craftableSlots().contains(i);
|
||||
if (missing || craftable) {
|
||||
slotView.drawHighlight(
|
||||
guiGraphics,
|
||||
if (!missing && !craftable) continue;
|
||||
if (i >= inputSlots.size()) continue;
|
||||
var slotView = inputSlots.get(i);
|
||||
slotView.drawHighlight(guiGraphics,
|
||||
missing ? RED_SLOT_HIGHLIGHT_COLOR : BLUE_SLOT_HIGHLIGHT_COLOR);
|
||||
}
|
||||
ThaumicEnergistics.LOG.info("[JEI-Transfer] showError slot={} missing={} craftable={}",
|
||||
i, missing, craftable);
|
||||
}
|
||||
|
||||
poseStack.popPose();
|
||||
@@ -221,15 +208,15 @@ public class ThEUseCraftingRecipeTransfer
|
||||
int craftable = indices.craftableSlots().size();
|
||||
if (missing > 0) {
|
||||
tooltip.add(net.minecraft.network.chat.Component.translatable(
|
||||
"jei.tooltip.transfer.missing", missing));
|
||||
"thaumicenergistics.jei.transfer.missing", missing));
|
||||
}
|
||||
if (craftable > 0) {
|
||||
tooltip.add(net.minecraft.network.chat.Component.translatable(
|
||||
"jei.tooltip.transfer.craftable", craftable));
|
||||
"thaumicenergistics.jei.transfer.craftable", craftable));
|
||||
}
|
||||
if (craftMissing) {
|
||||
tooltip.add(net.minecraft.network.chat.Component.translatable(
|
||||
"jei.tooltip.transfer.ctrl.click"));
|
||||
"thaumicenergistics.jei.transfer.ctrl_click"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ side="BOTH"
|
||||
[[dependencies.thaumicenergistics]]
|
||||
modId="thaumcraft"
|
||||
type="required"
|
||||
versionRange="[0,)"
|
||||
versionRange="[0.2.2.95,)"
|
||||
ordering="AFTER"
|
||||
side="BOTH"
|
||||
|
||||
|
||||
@@ -32,7 +32,16 @@
|
||||
},
|
||||
"position": {
|
||||
"left": 8,
|
||||
"bottom": 177
|
||||
"bottom": 179
|
||||
}
|
||||
},
|
||||
"player_inventory_title": {
|
||||
"text": {
|
||||
"translate": "container.inventory"
|
||||
},
|
||||
"position": {
|
||||
"left": 8,
|
||||
"bottom": 94
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -111,5 +111,8 @@
|
||||
"thaumicenergistics.jade.energy_stored": "Stored: %s / %s",
|
||||
"thaumicenergistics.jade.energy_output": "Output Limit: %s AE/tick",
|
||||
"thaumicenergistics.jade.tank_full": "Tank full, generation stopped",
|
||||
"thaumicenergistics.gui.distillation_encoder.not_scanned": "Not scanned"
|
||||
"thaumicenergistics.gui.distillation_encoder.not_scanned": "Not scanned",
|
||||
"thaumicenergistics.jei.transfer.missing": "Missing: %d",
|
||||
"thaumicenergistics.jei.transfer.craftable": "Craftable: %d",
|
||||
"thaumicenergistics.jei.transfer.ctrl_click": "Use Ctrl to craft missing items"
|
||||
}
|
||||
@@ -121,7 +121,7 @@
|
||||
"tc.research_name.PSEUDO_SCEPTRE": "工艺权杖",
|
||||
"tc.research_name.PSEUDO_COREGATHER": "傀儡核心:聚集",
|
||||
|
||||
"thaumicenergistics.jade.suction_type": "吸力: %s",
|
||||
"thaumicenergistics.jade.current_aspect": "燃料: %s",
|
||||
"thaumicenergistics.jade.essentia_stored": "源质: %d / %d",
|
||||
"thaumicenergistics.jade.burning": "燃烧 %d%%, %.1f AE/tick",
|
||||
"thaumicenergistics.aspect.ignis": "火",
|
||||
@@ -130,5 +130,8 @@
|
||||
"thaumicenergistics.jade.energy_stored": "已存储: %s / %s",
|
||||
"thaumicenergistics.jade.energy_output": "输出上限: %s AE/tick",
|
||||
"thaumicenergistics.jade.tank_full": "能量槽已满,停止发电",
|
||||
"thaumicenergistics.gui.distillation_encoder.not_scanned": "未扫描"
|
||||
"thaumicenergistics.gui.distillation_encoder.not_scanned": "未扫描",
|
||||
"thaumicenergistics.jei.transfer.missing": "缺少: %d",
|
||||
"thaumicenergistics.jei.transfer.craftable": "可合成: %d",
|
||||
"thaumicenergistics.jei.transfer.ctrl_click": "按住 Ctrl 合成缺失物品"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -8,11 +8,13 @@
|
||||
"ae2.MixinCraftingTreeProcess",
|
||||
"ae2.MixinAbstractTerminalPart",
|
||||
"ae2.MixinConfigMenuInventory",
|
||||
"ae2.MixinMEStorageMenuAccessors",
|
||||
"tc.MixinAspect",
|
||||
"tc.MixinVisRelayBlock"
|
||||
],
|
||||
"client": [
|
||||
"ae2.MixinCraftConfirmScreen",
|
||||
"ae2.MixinMEStorageScreen",
|
||||
"tc.ResearchPageIndexMixin",
|
||||
"tc.MixinThaumonomiconScreen"
|
||||
],
|
||||
|
||||