Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f985a22d8a | ||
|
|
61ce9891b9 | ||
|
|
94fafe972c | ||
|
|
f9a198a892 | ||
|
|
0df145927f | ||
|
|
5cfe2f1694 | ||
|
|
ea7720b7cd | ||
|
|
1c77347dc2 | ||
|
|
35b05eb044 | ||
|
|
a434f12749 | ||
|
|
93e7416056 | ||
|
|
62397479be | ||
|
|
5001b2f5a2 | ||
|
|
d3bd73f7c5 | ||
|
|
d44ea0f4a7 | ||
|
|
de966ac511 | ||
|
|
33542f9678 | ||
|
|
870223bc1e | ||
|
|
2097a241aa | ||
|
|
8576395126 | ||
|
|
72fbc95874 | ||
|
|
1ee529eb5b | ||
|
|
bdac8233a6 | ||
|
|
4cc1b64fc3 | ||
|
|
664d2a39cf | ||
|
|
99b431145f | ||
|
|
3fde7e2c1b | ||
|
|
7366a8b244 | ||
|
|
9e32c4dbfa | ||
|
|
fa526b0839 | ||
|
|
ac6cd5c1b5 | ||
|
|
f63a59495e | ||
|
|
fba85271f4 | ||
|
|
993001c4ef | ||
|
|
9e03c076ae | ||
|
|
c647189f77 | ||
|
|
022b2144d3 | ||
|
|
b2d09b241c | ||
|
|
f0fac64e84 | ||
|
|
ccbf442413 | ||
|
|
541ffe5ed2 | ||
|
|
ea93b9dcdf | ||
|
|
d6eb433f65 | ||
|
|
bc472026f9 | ||
|
|
ad89ab0574 | ||
|
|
e1b1bed804 | ||
|
|
6687583c96 | ||
|
|
8e44aaa780 | ||
|
|
a1a2103f47 | ||
|
|
88aaea801c | ||
|
|
ee489c9ae5 | ||
|
|
c8d810ecae |
No files matched your search
@@ -8,30 +8,23 @@ on: [pull_request, push]
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
runs-on: ubuntu-24.04
|
||||||
matrix:
|
|
||||||
# Use these Java versions
|
|
||||||
java: [
|
|
||||||
21
|
|
||||||
]
|
|
||||||
os: [ubuntu-20.04]
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: checkout repository
|
- name: checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v6
|
||||||
- name: validate gradle wrapper
|
- name: validate gradle wrapper
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/actions/wrapper-validation@v6
|
||||||
- name: setup jdk ${{ matrix.java }}
|
- name: setup jdk
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: ${{ matrix.java }}
|
java-version: '25'
|
||||||
|
distribution: 'microsoft'
|
||||||
- name: make gradle wrapper executable
|
- name: make gradle wrapper executable
|
||||||
if: ${{ runner.os != 'Windows' }}
|
|
||||||
run: chmod +x ./gradlew
|
run: chmod +x ./gradlew
|
||||||
- name: build
|
- name: build
|
||||||
run: ./gradlew build
|
run: ./gradlew build
|
||||||
- name: capture build artifacts
|
- name: capture build artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: Artifacts
|
name: Artifacts
|
||||||
path: build/libs/
|
path: build/libs/
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
# Yarn → Mojang official 映射表(1.21 → 26.1.2)
|
||||||
|
|
||||||
|
> 26.1.2 起 Fabric 弃用 Yarn,改用 Mojang 官方映射(jar 未混淆)。
|
||||||
|
> 本表记录本 mod 迁移用到的类名对照。⚠️ 表示结构/语义有变化,需人工核对。
|
||||||
|
|
||||||
|
## block
|
||||||
|
- block.BlockState → world.level.block.state.BlockState
|
||||||
|
- block.Blocks → world.level.block.Blocks
|
||||||
|
- block.MushroomPlantBlock → world.level.block.MushroomBlock
|
||||||
|
- block.RedstoneWireBlock → world.level.block.RedStoneWireBlock
|
||||||
|
- block.ShulkerBoxBlock → world.level.block.ShulkerBoxBlock
|
||||||
|
- block.entity.BlockEntity → world.level.block.entity.BlockEntity
|
||||||
|
- block.entity.EndGatewayBlockEntity → world.level.block.entity.TheEndGatewayBlockEntity
|
||||||
|
- block.entity.EndPortalBlockEntity → world.level.block.entity.TheEndPortalBlockEntity
|
||||||
|
- block.enums.TrialSpawnerState → world.level.block.entity.trialspawner.TrialSpawnerState
|
||||||
|
- block.pattern.BlockPattern → world.level.block.state.pattern.BlockPattern
|
||||||
|
- block.pattern.CachedBlockPosition → world.level.block.state.pattern.BlockPattern$BlockCacheLoader ⚠️
|
||||||
|
- block.spawner.TrialSpawnerConfig → world.level.block.entity.trialspawner.TrialSpawnerConfig
|
||||||
|
- block.spawner.TrialSpawnerData → world.level.block.entity.trialspawner.TrialSpawnerStateData ⚠️
|
||||||
|
- block.spawner.TrialSpawnerLogic → world.level.block.entity.trialspawner.TrialSpawner ⚠️
|
||||||
|
|
||||||
|
## command
|
||||||
|
- command.CommandRegistryAccess → commands.CommandBuildContext ⚠️
|
||||||
|
- command.argument.* → commands.arguments.*
|
||||||
|
- server.command.ServerCommandSource → commands.CommandSourceStack
|
||||||
|
- server.command.CommandManager → commands.Commands
|
||||||
|
- command.CommandSource.suggestMatching → commands.SharedSuggestionProvider 方法 ⚠️
|
||||||
|
|
||||||
|
## component / nbt / text / util
|
||||||
|
- component.DataComponentTypes → core.component.DataComponentTypes
|
||||||
|
- nbt.NbtCompound → nbt.CompoundTag
|
||||||
|
- nbt.NbtElement → nbt.Tag
|
||||||
|
- text.Text → network.chat.Component
|
||||||
|
- text.Style → network.chat.Style
|
||||||
|
- util.Formatting → ChatFormatting
|
||||||
|
- util.math.BlockPos → core.BlockPos
|
||||||
|
- util.math.Direction → core.Direction
|
||||||
|
- util.math.MathHelper → util.Mth
|
||||||
|
- util.math.Vec2f → world.phys.Vec2
|
||||||
|
- util.math.Vec3d → world.phys.Vec3
|
||||||
|
- util.UserCache → server.players.GameProfileCache ⚠️
|
||||||
|
- util.Uuids → core.UUIDUtil
|
||||||
|
- util.Nameable → world.Nameable
|
||||||
|
- registry.RegistryKey → resources.ResourceKey
|
||||||
|
- registry.tag.TagKey → tags.TagKey
|
||||||
|
- registry.tag.BlockTags → tags.BlockTags
|
||||||
|
- SharedConstants → SharedConstants
|
||||||
|
|
||||||
|
## entity
|
||||||
|
- entity.Entity → world.entity.Entity
|
||||||
|
- entity.EntityType → world.entity.EntityType
|
||||||
|
- entity.EquipmentSlot → world.entity.EquipmentSlot
|
||||||
|
- entity.FallingBlockEntity → world.entity.item.FallingBlockEntity
|
||||||
|
- entity.ItemEntity → world.entity.item.ItemEntity
|
||||||
|
- entity.Leashable → world.entity.Leashable
|
||||||
|
- entity.LivingEntity → world.entity.LivingEntity
|
||||||
|
- entity.attribute.EntityAttributes → world.entity.ai.attributes.Attributes ⚠️
|
||||||
|
- entity.boss.dragon.EnderDragonFight → world.level.dimension.end.EnderDragonFight
|
||||||
|
- entity.damage.DamageSource → world.damagesource.DamageSource
|
||||||
|
- entity.decoration.ArmorStandEntity → world.entity.decoration.ArmorStand
|
||||||
|
- entity.decoration.EndCrystalEntity → world.entity.EndCrystal ⚠️
|
||||||
|
- entity.mob.EndermanEntity → world.entity.monster.EnderMan
|
||||||
|
- entity.mob.MobEntity → world.entity.Mob
|
||||||
|
- entity.passive.AnimalEntity → world.entity.animal.Animal
|
||||||
|
- entity.passive.ArmadilloEntity → world.entity.animal.Armadillo ⚠️
|
||||||
|
- entity.player.PlayerAbilities → world.entity.player.Abilities
|
||||||
|
- entity.player.PlayerEntity → world.entity.player.Player
|
||||||
|
- entity.player.PlayerInventory → world.entity.player.Inventory
|
||||||
|
- entity.vehicle.BoatEntity → world.entity.vehicle.Boat ⚠️
|
||||||
|
|
||||||
|
## item / inventory / screen
|
||||||
|
- inventory.Inventory → world.entity.player.Inventory
|
||||||
|
- item.BlockItem → world.item.BlockItem
|
||||||
|
- item.ElytraItem → world.item.ElytraItem ⚠️
|
||||||
|
- item.Equipment → world.item.Equipment ⚠️
|
||||||
|
- item.Item → world.item.Item
|
||||||
|
- item.ItemStack → world.item.ItemStack
|
||||||
|
- item.Items → world.item.Items
|
||||||
|
- screen.AnvilScreenHandler → world.inventory.AnvilMenu
|
||||||
|
- screen.ScreenHandler → world.inventory.AbstractContainerMenu
|
||||||
|
- screen.slot.Slot → world.inventory.Slot
|
||||||
|
|
||||||
|
## network
|
||||||
|
- network.NetworkSide → network.protocol.PacketFlow
|
||||||
|
- network.packet.c2s.common.SyncedClientOptions → server.level.ClientInformation
|
||||||
|
- network.packet.s2c.play.EntityPositionS2CPacket → network.protocol.game.ClientboundMoveEntityPacket
|
||||||
|
- network.packet.s2c.play.EntitySetHeadYawS2CPacket → network.protocol.game.ClientboundRotateHeadPacket
|
||||||
|
- network.packet.s2c.play.EntityTrackerUpdateS2CPacket → network.protocol.game.ClientboundSetEntityDataPacket
|
||||||
|
- network.packet.s2c.play.PlayerListS2CPacket → network.protocol.game.ClientboundPlayerInfoUpdatePacket
|
||||||
|
- server.network.ConnectedClientData → server.network.CommonListenerCookie ⚠️
|
||||||
|
- server.network.ServerPlayNetworkHandler → server.network.ServerGamePacketListenerImpl
|
||||||
|
- server.network.ServerPlayerEntity → server.level.ServerPlayer
|
||||||
|
|
||||||
|
## server / world
|
||||||
|
- server.MinecraftServer → server.MinecraftServer
|
||||||
|
- server.PlayerManager → server.players.PlayerList
|
||||||
|
- server.world.ServerWorld → server.level.ServerLevel
|
||||||
|
- server.world.SleepManager → server.players.SleepStatus ⚠️
|
||||||
|
- world.World → world.level.Level
|
||||||
|
- world.WorldView → world.level.LevelReader
|
||||||
|
- world.GameMode → world.level.GameType
|
||||||
|
- world.SpawnHelper → world.level.NaturalSpawner
|
||||||
|
- world.chunk.WorldChunk → world.level.chunk.LevelChunk
|
||||||
|
- world.Heightmap → world.level.levelgen.Heightmap
|
||||||
|
- world.dimension.NetherPortal → world.level.block.NetherPortalBlock ⚠️
|
||||||
|
- world.gen.feature.EndPortalFeature → world.level.levelgen.feature.EndPodiumFeature
|
||||||
|
- sound.SoundCategory → sounds.SoundSource
|
||||||
|
- sound.SoundEvents → sounds.SoundEvents
|
||||||
|
- resource.ResourcePackProfile → server.packs.* ⚠️
|
||||||
|
- resource.ResourceType → server.packs.PackType
|
||||||
|
- resource.VanillaResourcePackProvider → server.packs.vanilla.VanillaPackResources ⚠️
|
||||||
+16
-12
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'fabric-loom' version '1.10.5'
|
id 'net.fabricmc.fabric-loom' version "${loom_version}"
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,19 +34,18 @@ repositories {
|
|||||||
dependencies {
|
dependencies {
|
||||||
// To change the versions see the gradle.properties file
|
// To change the versions see the gradle.properties file
|
||||||
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
||||||
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
|
implementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
||||||
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
|
||||||
// luck perm
|
// luck perm
|
||||||
modRuntimeOnly "maven.modrinth:luckperms:v5.4.133-fabric"
|
runtimeOnly "maven.modrinth:luckperms:v5.4.133-fabric"
|
||||||
|
|
||||||
// carpet
|
// carpet
|
||||||
modImplementation "carpet:fabric-carpet:${project.carpet_core_version}"
|
implementation "carpet:fabric-carpet:${project.carpet_core_version}"
|
||||||
|
|
||||||
// Fabric Permission API
|
// Fabric Permission API
|
||||||
include(modImplementation('me.lucko:fabric-permissions-api:0.3.1'))
|
include(implementation('me.lucko:fabric-permissions-api:0.7.0'))
|
||||||
|
|
||||||
// Fabric API. This is technically optional, but you probably want it anyway.
|
// Fabric API. This is technically optional, but you probably want it anyway.
|
||||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
implementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||||
}
|
}
|
||||||
|
|
||||||
loom {
|
loom {
|
||||||
@@ -54,16 +53,17 @@ loom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
processResources {
|
processResources {
|
||||||
inputs.property "version", project.version
|
def modVersion = project.version
|
||||||
|
inputs.property "version", modVersion
|
||||||
|
|
||||||
filesMatching("fabric.mod.json") {
|
filesMatching("fabric.mod.json") {
|
||||||
expand "version": project.version
|
expand "version": modVersion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(JavaCompile).configureEach {
|
tasks.withType(JavaCompile).configureEach {
|
||||||
// Minecraft 1.21 and upwards uses Java 21.
|
// Minecraft 26.1 and upwards uses Java 25.
|
||||||
it.options.release = 21
|
it.options.release = 25
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
@@ -71,11 +71,15 @@ java {
|
|||||||
// if it is present.
|
// if it is present.
|
||||||
// If you remove this line, sources will not be generated.
|
// If you remove this line, sources will not be generated.
|
||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
|
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_25
|
||||||
|
targetCompatibility = JavaVersion.VERSION_25
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
|
def baseName = project.base.archivesName.get()
|
||||||
from("LICENSE") {
|
from("LICENSE") {
|
||||||
rename { "${it}_${project.archivesBaseName}"}
|
rename { "${it}_${baseName}"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-6
@@ -1,15 +1,17 @@
|
|||||||
# Done to increase the memory available to gradle.
|
# Done to increase the memory available to gradle.
|
||||||
org.gradle.jvmargs=-Xmx3G
|
org.gradle.jvmargs=-Xmx3G
|
||||||
|
org.gradle.parallel=true
|
||||||
|
org.gradle.configuration-cache=true
|
||||||
|
|
||||||
# Fabric Properties
|
# Fabric Properties
|
||||||
# check these on https://fabricmc.net/develop
|
# check these on https://fabricmc.net/develop
|
||||||
minecraft_version=1.21
|
minecraft_version=26.4-snapshot-4
|
||||||
yarn_mappings=1.21+build.9
|
loader_version=0.20.0
|
||||||
loader_version=0.15.11
|
loom_version=1.19-SNAPSHOT
|
||||||
# Mod Properties
|
# Mod Properties
|
||||||
mod_version = 0.23
|
mod_version = 0.26.0-26.4
|
||||||
maven_group = com.github.zly2006
|
maven_group = com.github.zly2006
|
||||||
archives_base_name = carpet-sls-addition
|
archives_base_name = carpet-sls-addition
|
||||||
# Dependencies
|
# Dependencies
|
||||||
fabric_version=0.102.0+1.21
|
fabric_version=0.163.0+26.4
|
||||||
carpet_core_version=1.21-1.4.147+v240613
|
carpet_core_version=26.4-beta-3+v261013
|
||||||
Vendored
BIN
Binary file not shown.
+5
-1
@@ -1,5 +1,9 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.1-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
retries=0
|
||||||
|
retryBackOffMs=500
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright © 2015-2021 the original authors.
|
# Copyright © 2015 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -15,10 +15,12 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# Gradle start up script for POSIX generated by Gradle.
|
# gradlew start up script for POSIX generated by Gradle.
|
||||||
#
|
#
|
||||||
# Important for running:
|
# Important for running:
|
||||||
#
|
#
|
||||||
@@ -27,7 +29,7 @@
|
|||||||
# bash, then to run this script, type that shell name before the whole
|
# bash, then to run this script, type that shell name before the whole
|
||||||
# command line, like:
|
# command line, like:
|
||||||
#
|
#
|
||||||
# ksh Gradle
|
# ksh gradlew
|
||||||
#
|
#
|
||||||
# Busybox and similar reduced shells will NOT work, because this script
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
# requires all of these POSIX shell features:
|
# requires all of these POSIX shell features:
|
||||||
@@ -55,7 +57,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -80,13 +82,11 @@ do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -114,7 +114,6 @@ case "$( uname )" in #(
|
|||||||
NONSTOP* ) nonstop=true ;;
|
NONSTOP* ) nonstop=true ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
@@ -133,22 +132,29 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
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
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
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 ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
'' | soft) :;; #(
|
'' | 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" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@@ -165,7 +171,6 @@ fi
|
|||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if "$cygwin" || "$msys" ; then
|
if "$cygwin" || "$msys" ; then
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
@@ -193,18 +198,27 @@ if "$cygwin" || "$msys" ; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
# double quotes to make sure that they get re-expanded; and
|
|
||||||
# * put everything else in single quotes, so that it's not re-expanded.
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_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 -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
-classpath "$CLASSPATH" \
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
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.
|
# Use "xargs" to parse quoted args.
|
||||||
#
|
#
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
|||||||
Vendored
+28
-35
@@ -13,19 +13,22 @@
|
|||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem gradlew startup script for Windows
|
||||||
@rem
|
@rem
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
setlocal EnableExtensions
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@@ -40,15 +43,15 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
:findJavaFromJavaHome
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
@@ -56,34 +59,24 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute gradlew
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||||
|
@rem which allows us to clear the local environment before executing the java command
|
||||||
|
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
||||||
|
|
||||||
:end
|
:exitWithErrorLevel
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||||
|
|
||||||
:fail
|
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
|
||||||
rem the _cmd.exe /c_ return code!
|
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
|
||||||
exit /b 1
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
||||||
@@ -7,4 +7,8 @@ pluginManagement {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'net.fabricmc.fabric-loom' version providers.gradleProperty("loom_version").get()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -68,4 +68,22 @@ public class SLSCarpetSettings {
|
|||||||
|
|
||||||
@Rule(categories = {SLSA, RuleCategory.FEATURE})
|
@Rule(categories = {SLSA, RuleCategory.FEATURE})
|
||||||
public static int trialSpawnerCD = -1;
|
public static int trialSpawnerCD = -1;
|
||||||
|
|
||||||
|
@Rule(categories = {SLSA, RuleCategory.FEATURE})
|
||||||
|
public static boolean turtleLocalNesting = false;
|
||||||
|
|
||||||
|
@Rule(categories = {SLSA, RuleCategory.FEATURE})
|
||||||
|
public static boolean sturdyDecoratedPot = false;
|
||||||
|
|
||||||
|
@Rule(categories = {SLSA, RuleCategory.FEATURE})
|
||||||
|
public static boolean unbreakableDecoratedPot = false;
|
||||||
|
|
||||||
|
@Rule(categories = {SLSA, RuleCategory.FEATURE})
|
||||||
|
public static boolean stackableVehicles = false;
|
||||||
|
|
||||||
|
@Rule(categories = {SLSA, RuleCategory.FEATURE})
|
||||||
|
public static boolean slsBoardEnabled = false;
|
||||||
|
|
||||||
|
@Rule(categories = {SLSA, RuleCategory.FEATURE})
|
||||||
|
public static boolean noBrittleIceFromFlowingWater = false;
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,9 @@ package com.github.zly2006.carpetslsaddition;
|
|||||||
|
|
||||||
import carpet.CarpetExtension;
|
import carpet.CarpetExtension;
|
||||||
import carpet.CarpetServer;
|
import carpet.CarpetServer;
|
||||||
|
import com.github.zly2006.carpetslsaddition.command.BotAllCommand;
|
||||||
import com.github.zly2006.carpetslsaddition.command.BotCommand;
|
import com.github.zly2006.carpetslsaddition.command.BotCommand;
|
||||||
|
import com.github.zly2006.carpetslsaddition.command.BotListCommand;
|
||||||
import com.github.zly2006.carpetslsaddition.command.HatCommand;
|
import com.github.zly2006.carpetslsaddition.command.HatCommand;
|
||||||
import com.github.zly2006.carpetslsaddition.command.SitCommand;
|
import com.github.zly2006.carpetslsaddition.command.SitCommand;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
@@ -12,9 +14,9 @@ import com.mojang.brigadier.CommandDispatcher;
|
|||||||
import net.fabricmc.api.ModInitializer;
|
import net.fabricmc.api.ModInitializer;
|
||||||
import net.fabricmc.loader.api.FabricLoader;
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
import net.fabricmc.loader.api.Version;
|
import net.fabricmc.loader.api.Version;
|
||||||
import net.minecraft.command.CommandRegistryAccess;
|
import net.minecraft.commands.CommandBuildContext;
|
||||||
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.command.ServerCommandSource;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
@@ -29,7 +31,7 @@ public class ServerMain implements ModInitializer, CarpetExtension {
|
|||||||
public static final Version MOD_VERSION = FabricLoader.getInstance().getModContainer(MOD_ID).get().getMetadata().getVersion();
|
public static final Version MOD_VERSION = FabricLoader.getInstance().getModContainer(MOD_ID).get().getMetadata().getVersion();
|
||||||
|
|
||||||
public static final Logger LOGGER = LogManager.getLogger(MOD_ID);
|
public static final Logger LOGGER = LogManager.getLogger(MOD_ID);
|
||||||
static final Gson GSON = new GsonBuilder().setLenient().create(); // 使用宽容模式,避免部分开发者在书写JSON时不遵守RFC 4627规范
|
static final Gson GSON = new GsonBuilder().setLenient().create();
|
||||||
|
|
||||||
public static ServerMain INSTANCE;
|
public static ServerMain INSTANCE;
|
||||||
public static MinecraftServer server;
|
public static MinecraftServer server;
|
||||||
@@ -38,8 +40,16 @@ public class ServerMain implements ModInitializer, CarpetExtension {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInitialize() {
|
public void onInitialize() {
|
||||||
|
LOGGER.info("[SLSA] onInitialize: managing Carpet extension");
|
||||||
INSTANCE = this;
|
INSTANCE = this;
|
||||||
CarpetServer.manageExtension(this);
|
CarpetServer.manageExtension(this);
|
||||||
|
com.github.zly2006.carpetslsaddition.net.BoardRequestPayload.register();
|
||||||
|
// 客户端请求显示榜单 → 给该玩家单独发 display 包
|
||||||
|
net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking.registerGlobalReceiver(
|
||||||
|
com.github.zly2006.carpetslsaddition.net.BoardRequestPayload.TYPE,
|
||||||
|
(payload, context) -> context.server().execute(() ->
|
||||||
|
com.github.zly2006.carpetslsaddition.util.BoardManager.onClientRequest(
|
||||||
|
context.player(), payload.objectiveName())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -50,6 +60,32 @@ public class ServerMain implements ModInitializer, CarpetExtension {
|
|||||||
@Override
|
@Override
|
||||||
public void onServerLoaded(MinecraftServer server) {
|
public void onServerLoaded(MinecraftServer server) {
|
||||||
ServerMain.server = server;
|
ServerMain.server = server;
|
||||||
|
com.github.zly2006.carpetslsaddition.util.BotListManager.load();
|
||||||
|
startBoardWatcher(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 服务器完全启动后创建 objective(此时 scoreboard 已 load 完,避免重名崩溃)。 */
|
||||||
|
static {
|
||||||
|
net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents.SERVER_STARTED.register(server ->
|
||||||
|
com.github.zly2006.carpetslsaddition.util.BoardManager.ensureObjectives(server));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 每秒检测 slsBoardEnabled 规则变化并同步给客户端。 */
|
||||||
|
private void startBoardWatcher(MinecraftServer server) {
|
||||||
|
Thread t = new Thread(() -> {
|
||||||
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
|
try {
|
||||||
|
com.github.zly2006.carpetslsaddition.util.BoardManager.syncScores(server);
|
||||||
|
Thread.sleep(1000L);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return;
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
LOGGER.debug("[SLSA] board watcher error: {}", ex.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "SLSA-BoardWatcher");
|
||||||
|
t.setDaemon(true);
|
||||||
|
t.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -74,9 +110,17 @@ public class ServerMain implements ModInitializer, CarpetExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerCommands(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess commandBuildContext) {
|
public void registerCommands(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext commandBuildContext) {
|
||||||
HatCommand.register(dispatcher);
|
LOGGER.info("[SLSA] registerCommands called!");
|
||||||
SitCommand.register(dispatcher);
|
try {
|
||||||
BotCommand.register(dispatcher);
|
HatCommand.register(dispatcher);
|
||||||
|
SitCommand.register(dispatcher);
|
||||||
|
BotCommand.register(dispatcher);
|
||||||
|
BotAllCommand.register(dispatcher);
|
||||||
|
BotListCommand.register(dispatcher);
|
||||||
|
LOGGER.info("[SLSA] commands registered: hat/sit/bot");
|
||||||
|
} catch (Throwable t) {
|
||||||
|
LOGGER.error("[SLSA] failed to register commands", t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.client;
|
||||||
|
|
||||||
|
import com.github.zly2006.carpetslsaddition.net.BoardRequestPayload;
|
||||||
|
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||||
|
import net.fabricmc.api.ClientModInitializer;
|
||||||
|
import net.fabricmc.api.EnvType;
|
||||||
|
import net.fabricmc.api.Environment;
|
||||||
|
import net.fabricmc.fabric.api.client.command.v2.ClientCommands;
|
||||||
|
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
|
||||||
|
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
||||||
|
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||||
|
import net.minecraft.commands.SharedSuggestionProvider;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端入口:/slsboard 命令,请求服务端给自己显示某个榜单。
|
||||||
|
*
|
||||||
|
* <p>服务端收到请求后,给该玩家单独发 display 包,实现各玩家各自显示、互不影响。
|
||||||
|
* 数据由服务端从 stats 同步到 scoreboard objective(见 BoardManager)。
|
||||||
|
*/
|
||||||
|
@Environment(EnvType.CLIENT)
|
||||||
|
public class BoardClient implements ClientModInitializer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitializeClient() {
|
||||||
|
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
|
||||||
|
dispatcher.register(
|
||||||
|
ClientCommands.literal("slsboard")
|
||||||
|
.then(ClientCommands.literal("deaths").executes(c -> request(c.getSource(), "sls_deaths")))
|
||||||
|
.then(ClientCommands.literal("mined").executes(c -> request(c.getSource(), "sls_mined")))
|
||||||
|
.then(ClientCommands.literal("off").executes(c -> request(c.getSource(), "")))
|
||||||
|
.then(ClientCommands.argument("board", StringArgumentType.word())
|
||||||
|
.suggests((c, b) -> SharedSuggestionProvider.suggest(
|
||||||
|
new String[]{"deaths", "mined", "off"}, b))
|
||||||
|
.executes(c -> {
|
||||||
|
String v = StringArgumentType.getString(c, "board");
|
||||||
|
return request(c.getSource(), "off".equals(v) ? "" : "sls_" + v);
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int request(FabricClientCommandSource source, String objectiveName) {
|
||||||
|
ClientPlayNetworking.send(new BoardRequestPayload(objectiveName));
|
||||||
|
source.sendFeedback(Component.literal(
|
||||||
|
objectiveName.isEmpty() ? "[SLSA] 榜单已关闭" : "[SLSA] 请求显示:" + objectiveName));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.command;
|
||||||
|
|
||||||
|
import carpet.fakes.ServerPlayerInterface;
|
||||||
|
import carpet.helpers.EntityPlayerActionPack;
|
||||||
|
import carpet.patches.EntityPlayerMPFake;
|
||||||
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
|
import com.github.zly2006.carpetslsaddition.util.access.SLSBotAccessor;
|
||||||
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
|
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||||
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||||
|
import com.mojang.brigadier.context.CommandContext;
|
||||||
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
|
import net.minecraft.commands.Commands;
|
||||||
|
import net.minecraft.commands.arguments.coordinates.RotationArgument;
|
||||||
|
import net.minecraft.commands.arguments.coordinates.Vec3Argument;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static net.minecraft.commands.Commands.argument;
|
||||||
|
import static net.minecraft.commands.Commands.literal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /botall —— 批量控制本模组生成的所有假人。
|
||||||
|
*
|
||||||
|
* <p>与 {@link BotCommand} 的区别:{@code /bot <player>} 操作单个指定玩家,
|
||||||
|
* 而 {@code /botall} 对其余命令中出现的所有本模组假人(isBot()==true)批量执行同一操作。
|
||||||
|
*/
|
||||||
|
public class BotAllCommand {
|
||||||
|
|
||||||
|
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||||
|
LiteralArgumentBuilder<CommandSourceStack> command = literal("botall")
|
||||||
|
.requires(source -> Commands.LEVEL_OWNERS.check(source.permissions()) || me.lucko.fabric.api.permissions.v0.Permissions.check(source, "slsaddition.command.bot"))
|
||||||
|
.then(literal("stop").executes(batch(EntityPlayerActionPack::stopAll)))
|
||||||
|
.then(actionCommand("use", EntityPlayerActionPack.ActionType.USE))
|
||||||
|
.then(actionCommand("jump", EntityPlayerActionPack.ActionType.JUMP))
|
||||||
|
.then(actionCommand("attack", EntityPlayerActionPack.ActionType.ATTACK))
|
||||||
|
.then(actionCommand("drop", EntityPlayerActionPack.ActionType.DROP_ITEM))
|
||||||
|
.then(dropCommand("dropStack", true))
|
||||||
|
.then(actionCommand("swapHands", EntityPlayerActionPack.ActionType.SWAP_HANDS))
|
||||||
|
.then(literal("hotbar").then(argument("slot", IntegerArgumentType.integer(1, 9))
|
||||||
|
.executes(c -> batchAp(c, ap -> ap.setSlot(IntegerArgumentType.getInteger(c, "slot"))))))
|
||||||
|
.then(literal("mount").executes(batch(ap -> ap.mount(true)))
|
||||||
|
.then(literal("anything").executes(batch(ap -> ap.mount(false)))))
|
||||||
|
.then(literal("dismount").executes(batch(EntityPlayerActionPack::dismount)))
|
||||||
|
.then(literal("sneak").executes(batch(ap -> ap.setSneaking(true))))
|
||||||
|
.then(literal("unsneak").executes(batch(ap -> ap.setSneaking(false))))
|
||||||
|
.then(literal("sprint").executes(batch(ap -> ap.setSprinting(true))))
|
||||||
|
.then(literal("unsprint").executes(batch(ap -> ap.setSprinting(false))))
|
||||||
|
.then(literal("look")
|
||||||
|
.then(literal("north").executes(batch(ap -> ap.look(Direction.NORTH))))
|
||||||
|
.then(literal("south").executes(batch(ap -> ap.look(Direction.SOUTH))))
|
||||||
|
.then(literal("east").executes(batch(ap -> ap.look(Direction.EAST))))
|
||||||
|
.then(literal("west").executes(batch(ap -> ap.look(Direction.WEST))))
|
||||||
|
.then(literal("up").executes(batch(ap -> ap.look(Direction.UP))))
|
||||||
|
.then(literal("down").executes(batch(ap -> ap.look(Direction.DOWN))))
|
||||||
|
.then(literal("at").then(argument("position", Vec3Argument.vec3())
|
||||||
|
.executes(c -> batchAp(c, ap -> ap.lookAt(Vec3Argument.getVec3(c, "position"))))))
|
||||||
|
.then(argument("direction", RotationArgument.rotation())
|
||||||
|
.executes(c -> batchAp(c, ap -> ap.look(RotationArgument.getRotation(c, "direction").getRotation(c.getSource()))))))
|
||||||
|
.then(literal("turn")
|
||||||
|
.then(literal("left").executes(batch(ap -> ap.turn(-90, 0))))
|
||||||
|
.then(literal("right").executes(batch(ap -> ap.turn(90, 0))))
|
||||||
|
.then(literal("back").executes(batch(ap -> ap.turn(180, 0))))
|
||||||
|
.then(argument("rotation", RotationArgument.rotation())
|
||||||
|
.executes(c -> batchAp(c, ap -> ap.turn(RotationArgument.getRotation(c, "rotation").getRotation(c.getSource()))))))
|
||||||
|
.then(literal("move").executes(batch(EntityPlayerActionPack::stopMovement))
|
||||||
|
.then(literal("forward").executes(batch(ap -> ap.setForward(1))))
|
||||||
|
.then(literal("backward").executes(batch(ap -> ap.setForward(-1))))
|
||||||
|
.then(literal("left").executes(batch(ap -> ap.setStrafing(1))))
|
||||||
|
.then(literal("right").executes(batch(ap -> ap.setStrafing(-1)))))
|
||||||
|
.then(literal("sit").executes(BotAllCommand::sitAll))
|
||||||
|
.then(literal("stand").executes(BotAllCommand::standAll))
|
||||||
|
.then(literal("spawn")
|
||||||
|
.then(literal("list").executes(BotAllCommand::spawnFromList))
|
||||||
|
.then(argument("prefix", com.mojang.brigadier.arguments.StringArgumentType.word())
|
||||||
|
.then(argument("count", IntegerArgumentType.integer(1, 1000))
|
||||||
|
.executes(BotAllCommand::spawnBatch))))
|
||||||
|
.then(literal("kill").executes(BotAllCommand::killBatch));
|
||||||
|
dispatcher.register(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LiteralArgumentBuilder<CommandSourceStack> actionCommand(String name, EntityPlayerActionPack.ActionType type) {
|
||||||
|
return literal(name)
|
||||||
|
.executes(batch(ap -> ap.start(type, EntityPlayerActionPack.Action.once())))
|
||||||
|
.then(literal("once").executes(batch(ap -> ap.start(type, EntityPlayerActionPack.Action.once()))))
|
||||||
|
.then(literal("continuous").executes(batch(ap -> ap.start(type, EntityPlayerActionPack.Action.continuous()))))
|
||||||
|
.then(literal("interval").then(argument("ticks", IntegerArgumentType.integer(1))
|
||||||
|
.executes(c -> batchAp(c, ap -> ap.start(type, EntityPlayerActionPack.Action.interval(IntegerArgumentType.getInteger(c, "ticks")))))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LiteralArgumentBuilder<CommandSourceStack> dropCommand(String name, boolean dropAll) {
|
||||||
|
return literal(name)
|
||||||
|
.then(literal("all").executes(batch(ap -> ap.drop(-2, dropAll))))
|
||||||
|
.then(literal("mainhand").executes(batch(ap -> ap.drop(-1, dropAll))))
|
||||||
|
.then(literal("offhand").executes(batch(ap -> ap.drop(40, dropAll))))
|
||||||
|
.then(argument("slot", IntegerArgumentType.integer(0, 40))
|
||||||
|
.executes(c -> batchAp(c, ap -> ap.drop(IntegerArgumentType.getInteger(c, "slot"), dropAll))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量执行一个 action pack 操作。 */
|
||||||
|
private static int batchAp(CommandContext<CommandSourceStack> context, Consumer<EntityPlayerActionPack> action) {
|
||||||
|
List<ServerPlayer> bots = collectBots(context.getSource());
|
||||||
|
for (ServerPlayer bot : bots) {
|
||||||
|
action.accept(((ServerPlayerInterface) bot).getActionPack());
|
||||||
|
}
|
||||||
|
final int n = bots.size();
|
||||||
|
context.getSource().sendSuccess(
|
||||||
|
() -> net.minecraft.network.chat.Component.literal("[SLSA] 已对 " + n + " 个假人执行操作"), false);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static com.mojang.brigadier.Command<CommandSourceStack> batch(Consumer<EntityPlayerActionPack> action) {
|
||||||
|
return c -> batchAp(c, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 收集所有本模组生成的假人(受名单限制:名单非空时只取名单内的)。 */
|
||||||
|
private static List<ServerPlayer> collectBots(CommandSourceStack source) {
|
||||||
|
MinecraftServer server = source.getServer();
|
||||||
|
List<ServerPlayer> bots = new ArrayList<>();
|
||||||
|
boolean filterByList = !com.github.zly2006.carpetslsaddition.util.BotListManager.isEmpty();
|
||||||
|
for (ServerPlayer player : server.getPlayerList().getPlayers()) {
|
||||||
|
if (player instanceof EntityPlayerMPFake && ((SLSBotAccessor) player).carpet_SLS_Addition$isBot()) {
|
||||||
|
if (filterByList && !com.github.zly2006.carpetslsaddition.util.BotListManager.contains(player.getScoreboardName())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bots.add(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bots;
|
||||||
|
}
|
||||||
|
private static int sitAll(CommandContext<CommandSourceStack> context) {
|
||||||
|
if (!SLSCarpetSettings.canUseSitCommand) {
|
||||||
|
context.getSource().sendSuccess(() -> net.minecraft.network.chat.Component.literal(
|
||||||
|
"[SLSA] 坐下功能未启用(canUseSitCommand)"), false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
List<ServerPlayer> bots = collectBots(context.getSource());
|
||||||
|
int ok = 0;
|
||||||
|
for (ServerPlayer bot : bots) {
|
||||||
|
if (SitCommand.sitPlayer(bot)) ok++;
|
||||||
|
}
|
||||||
|
final int done = ok;
|
||||||
|
context.getSource().sendSuccess(() -> net.minecraft.network.chat.Component.literal(
|
||||||
|
"[SLSA] 已让 " + done + "/" + bots.size() + " 个假人坐下"), false);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int standAll(CommandContext<CommandSourceStack> context) {
|
||||||
|
List<ServerPlayer> bots = collectBots(context.getSource());
|
||||||
|
int ok = 0;
|
||||||
|
for (ServerPlayer bot : bots) {
|
||||||
|
if (bot.getVehicle() != null) {
|
||||||
|
bot.stopRiding();
|
||||||
|
ok++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final int done = ok;
|
||||||
|
context.getSource().sendSuccess(() -> net.minecraft.network.chat.Component.literal(
|
||||||
|
"[SLSA] 已让 " + done + "/" + bots.size() + " 个假人站起"), false);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 一键召唤名单里的所有假人(执行者位置)。 */
|
||||||
|
private static int spawnFromList(CommandContext<CommandSourceStack> context) {
|
||||||
|
CommandSourceStack source = context.getSource();
|
||||||
|
java.util.Set<String> names = com.github.zly2006.carpetslsaddition.util.BotListManager.getNames();
|
||||||
|
if (names.isEmpty()) {
|
||||||
|
source.sendSuccess(() -> net.minecraft.network.chat.Component.literal(
|
||||||
|
"[SLSA] 名单为空,先用 /botlist add 添加名字"), false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
net.minecraft.world.phys.Vec3 pos = source.getPosition();
|
||||||
|
net.minecraft.world.phys.Vec2 rot = source.getRotation();
|
||||||
|
net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level> dim = source.getLevel().dimension();
|
||||||
|
int ok = 0;
|
||||||
|
for (String name : names) {
|
||||||
|
// 已在线的跳过
|
||||||
|
if (source.getServer().getPlayerList().getPlayerByName(name) != null) continue;
|
||||||
|
try {
|
||||||
|
var bot = BotCommand.createBot(name, source.getServer(), pos, rot.y, rot.x, dim);
|
||||||
|
if (bot != null) ok++;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
com.github.zly2006.carpetslsaddition.ServerMain.LOGGER.warn("[SLSA] spawn list bot {} failed: {}", name, t.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final int done = ok;
|
||||||
|
source.sendSuccess(() -> net.minecraft.network.chat.Component.literal(
|
||||||
|
"[SLSA] 已召唤名单中的 " + done + "/" + names.size() + " 个假人"), false);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量召唤:生成 前缀1 ~ 前缀N。 */
|
||||||
|
private static int spawnBatch(CommandContext<CommandSourceStack> context) {
|
||||||
|
CommandSourceStack source = context.getSource();
|
||||||
|
String prefix = com.mojang.brigadier.arguments.StringArgumentType.getString(context, "prefix");
|
||||||
|
int count = IntegerArgumentType.getInteger(context, "count");
|
||||||
|
net.minecraft.world.phys.Vec3 pos = source.getPosition();
|
||||||
|
net.minecraft.world.phys.Vec2 rot = source.getRotation();
|
||||||
|
net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level> dim = source.getLevel().dimension();
|
||||||
|
int ok = 0;
|
||||||
|
for (int i = 1; i <= count; i++) {
|
||||||
|
String name = prefix + i;
|
||||||
|
try {
|
||||||
|
var bot = BotCommand.createBot(name, source.getServer(), pos, rot.y, rot.x, dim);
|
||||||
|
if (bot != null) ok++;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
com.github.zly2006.carpetslsaddition.ServerMain.LOGGER.warn("[SLSA] spawn bot {} failed: {}", name, t.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final int done = ok;
|
||||||
|
source.sendSuccess(() -> net.minecraft.network.chat.Component.literal(
|
||||||
|
"[SLSA] 已召唤 " + done + "/" + count + " 个假人(前缀 " + prefix + ")"), false);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量移除:杀死受名单限制的假人(名单空则全部)。 */
|
||||||
|
private static int killBatch(CommandContext<CommandSourceStack> context) {
|
||||||
|
List<ServerPlayer> bots = collectBots(context.getSource());
|
||||||
|
int ok = 0;
|
||||||
|
for (ServerPlayer bot : bots) {
|
||||||
|
bot.kill((net.minecraft.server.level.ServerLevel) bot.level());
|
||||||
|
ok++;
|
||||||
|
}
|
||||||
|
final int done = ok;
|
||||||
|
context.getSource().sendSuccess(() -> net.minecraft.network.chat.Component.literal(
|
||||||
|
"[SLSA] 已移除 " + done + " 个假人"), false);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,35 +20,36 @@ import com.mojang.brigadier.context.CommandContext;
|
|||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
import me.lucko.fabric.api.permissions.v0.Permissions;
|
import me.lucko.fabric.api.permissions.v0.Permissions;
|
||||||
import net.minecraft.SharedConstants;
|
import net.minecraft.SharedConstants;
|
||||||
import net.minecraft.command.argument.DimensionArgumentType;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.command.argument.GameModeArgumentType;
|
import net.minecraft.commands.Commands;
|
||||||
import net.minecraft.command.argument.RotationArgumentType;
|
import net.minecraft.commands.arguments.DimensionArgument;
|
||||||
import net.minecraft.command.argument.Vec3ArgumentType;
|
import net.minecraft.commands.arguments.GameModeArgument;
|
||||||
import net.minecraft.entity.attribute.EntityAttributes;
|
import net.minecraft.commands.arguments.coordinates.RotationArgument;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.commands.arguments.coordinates.Vec3Argument;
|
||||||
import net.minecraft.network.NetworkSide;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.network.packet.c2s.common.SyncedClientOptions;
|
import net.minecraft.core.Direction;
|
||||||
import net.minecraft.network.packet.s2c.play.EntityPositionS2CPacket;
|
import net.minecraft.core.UUIDUtil;
|
||||||
import net.minecraft.network.packet.s2c.play.EntitySetHeadYawS2CPacket;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket;
|
import net.minecraft.network.chat.Style;
|
||||||
import net.minecraft.registry.RegistryKey;
|
import net.minecraft.network.protocol.PacketFlow;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.PlayerManager;
|
import net.minecraft.server.level.ClientInformation;
|
||||||
import net.minecraft.server.command.ServerCommandSource;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.server.network.ConnectedClientData;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
import net.minecraft.server.network.CommonListenerCookie;
|
||||||
import net.minecraft.server.world.ServerWorld;
|
import net.minecraft.server.network.ClientDebugInfo;
|
||||||
import net.minecraft.text.Style;
|
import net.minecraft.server.players.NameAndId;
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.server.players.PlayerList;
|
||||||
import net.minecraft.util.Formatting;
|
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||||
import net.minecraft.util.UserCache;
|
import net.minecraft.world.entity.player.Player;
|
||||||
import net.minecraft.util.Uuids;
|
import net.minecraft.world.level.GameType;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.util.math.Direction;
|
import net.minecraft.world.phys.Vec2;
|
||||||
import net.minecraft.util.math.Vec2f;
|
import net.minecraft.world.phys.Vec3;
|
||||||
import net.minecraft.util.math.Vec3d;
|
|
||||||
import net.minecraft.world.GameMode;
|
|
||||||
import net.minecraft.world.World;
|
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
@@ -58,17 +59,27 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static net.minecraft.command.CommandSource.suggestMatching;
|
import static net.minecraft.commands.Commands.argument;
|
||||||
import static net.minecraft.server.command.CommandManager.argument;
|
import static net.minecraft.commands.Commands.literal;
|
||||||
import static net.minecraft.server.command.CommandManager.literal;
|
import static net.minecraft.commands.SharedSuggestionProvider.suggest;
|
||||||
|
|
||||||
public class BotCommand {
|
public class BotCommand {
|
||||||
public static void register(CommandDispatcher<ServerCommandSource> dispatcher)
|
private static boolean hasLevel(CommandSourceStack source, int level) {
|
||||||
|
var perm = switch (level) {
|
||||||
|
case 0 -> net.minecraft.commands.Commands.LEVEL_ALL;
|
||||||
|
case 1 -> net.minecraft.commands.Commands.LEVEL_MODERATORS;
|
||||||
|
case 2 -> net.minecraft.commands.Commands.LEVEL_GAMEMASTERS;
|
||||||
|
case 3 -> net.minecraft.commands.Commands.LEVEL_ADMINS;
|
||||||
|
default -> net.minecraft.commands.Commands.LEVEL_OWNERS;
|
||||||
|
};
|
||||||
|
return perm.check(source.permissions());
|
||||||
|
}
|
||||||
|
public static void register(CommandDispatcher<CommandSourceStack> dispatcher)
|
||||||
{
|
{
|
||||||
LiteralArgumentBuilder<ServerCommandSource> command = literal("bot")
|
LiteralArgumentBuilder<CommandSourceStack> command = literal("bot")
|
||||||
.requires(source -> source.hasPermissionLevel(4) || Permissions.check(source, "slsaddition.command.bot"))
|
.requires(source -> hasLevel(source, 4) || Permissions.check(source, "slsaddition.command.bot"))
|
||||||
.then(argument("player", StringArgumentType.word())
|
.then(argument("player", StringArgumentType.word())
|
||||||
.suggests((c, b) -> suggestMatching(getPlayerSuggestions(c.getSource()), b))
|
.suggests((c, b) -> suggest(getPlayerSuggestions(c.getSource()), b))
|
||||||
.then(literal("stop").executes(manipulation(EntityPlayerActionPack::stopAll)))
|
.then(literal("stop").executes(manipulation(EntityPlayerActionPack::stopAll)))
|
||||||
.then(makeActionCommand("use", EntityPlayerActionPack.ActionType.USE))
|
.then(makeActionCommand("use", EntityPlayerActionPack.ActionType.USE))
|
||||||
.then(makeActionCommand("jump", EntityPlayerActionPack.ActionType.JUMP))
|
.then(makeActionCommand("jump", EntityPlayerActionPack.ActionType.JUMP))
|
||||||
@@ -97,40 +108,38 @@ public class BotCommand {
|
|||||||
.then(literal("west").executes(manipulation(ap -> ap.look(Direction.WEST))))
|
.then(literal("west").executes(manipulation(ap -> ap.look(Direction.WEST))))
|
||||||
.then(literal("up").executes(manipulation(ap -> ap.look(Direction.UP))))
|
.then(literal("up").executes(manipulation(ap -> ap.look(Direction.UP))))
|
||||||
.then(literal("down").executes(manipulation(ap -> ap.look(Direction.DOWN))))
|
.then(literal("down").executes(manipulation(ap -> ap.look(Direction.DOWN))))
|
||||||
.then(literal("at").then(argument("position", Vec3ArgumentType.vec3())
|
.then(literal("at").then(argument("position", Vec3Argument.vec3())
|
||||||
.executes(c -> manipulate(c, ap -> ap.lookAt(Vec3ArgumentType.getVec3(c, "position"))))))
|
.executes(c -> manipulate(c, ap -> ap.lookAt(Vec3Argument.getVec3(c, "position"))))))
|
||||||
.then(argument("direction", RotationArgumentType.rotation())
|
.then(argument("direction", RotationArgument.rotation())
|
||||||
.executes(c -> manipulate(c, ap -> ap.look(RotationArgumentType.getRotation(c, "direction").toAbsoluteRotation(c.getSource())))))
|
.executes(c -> manipulate(c, ap -> ap.look(RotationArgument.getRotation(c, "direction").getRotation(c.getSource()))))))
|
||||||
).then(literal("turn")
|
.then(literal("turn")
|
||||||
.then(literal("left").executes(manipulation(ap -> ap.turn(-90, 0))))
|
.then(literal("left").executes(manipulation(ap -> ap.turn(-90, 0))))
|
||||||
.then(literal("right").executes(manipulation(ap -> ap.turn(90, 0))))
|
.then(literal("right").executes(manipulation(ap -> ap.turn(90, 0))))
|
||||||
.then(literal("back").executes(manipulation(ap -> ap.turn(180, 0))))
|
.then(literal("back").executes(manipulation(ap -> ap.turn(180, 0))))
|
||||||
.then(argument("rotation", RotationArgumentType.rotation())
|
.then(argument("rotation", RotationArgument.rotation())
|
||||||
.executes(c -> manipulate(c, ap -> ap.turn(RotationArgumentType.getRotation(c, "rotation").toAbsoluteRotation(c.getSource())))))
|
.executes(c -> manipulate(c, ap -> ap.turn(RotationArgument.getRotation(c, "rotation").getRotation(c.getSource()))))))
|
||||||
).then(literal("move").executes(manipulation(EntityPlayerActionPack::stopMovement))
|
.then(literal("move").executes(manipulation(EntityPlayerActionPack::stopMovement))
|
||||||
.then(literal("forward").executes(manipulation(ap -> ap.setForward(1))))
|
.then(literal("forward").executes(manipulation(ap -> ap.setForward(1))))
|
||||||
.then(literal("backward").executes(manipulation(ap -> ap.setForward(-1))))
|
.then(literal("backward").executes(manipulation(ap -> ap.setForward(-1))))
|
||||||
.then(literal("left").executes(manipulation(ap -> ap.setStrafing(1))))
|
.then(literal("left").executes(manipulation(ap -> ap.setStrafing(1))))
|
||||||
.then(literal("right").executes(manipulation(ap -> ap.setStrafing(-1))))
|
.then(literal("right").executes(manipulation(ap -> ap.setStrafing(-1)))))
|
||||||
).then(literal("spawn").executes(BotCommand::spawn)
|
.then(literal("spawn").executes(BotCommand::spawn)
|
||||||
.then(literal("in").requires((player) -> player.hasPermissionLevel(2))
|
.then(literal("in").requires((player) -> hasLevel(player, 2))
|
||||||
.then(argument("gamemode", GameModeArgumentType.gameMode())
|
.then(argument("gamemode", GameModeArgument.gameMode())
|
||||||
.executes(BotCommand::spawn)))
|
.executes(BotCommand::spawn)))
|
||||||
.then(literal("at").then(argument("position", Vec3ArgumentType.vec3()).executes(BotCommand::spawn)
|
.then(literal("at").then(argument("position", Vec3Argument.vec3()).executes(BotCommand::spawn)
|
||||||
.then(literal("facing").then(argument("direction", RotationArgumentType.rotation()).executes(BotCommand::spawn)
|
.then(literal("facing").then(argument("direction", RotationArgument.rotation()).executes(BotCommand::spawn)
|
||||||
.then(literal("in").then(argument("dimension", DimensionArgumentType.dimension()).executes(BotCommand::spawn)
|
.then(literal("in").then(argument("dimension", DimensionArgument.dimension()).executes(BotCommand::spawn)
|
||||||
.then(literal("in").requires((player) -> player.hasPermissionLevel(2))
|
.then(literal("in").requires((player) -> hasLevel(player, 2))
|
||||||
.then(argument("gamemode", GameModeArgumentType.gameMode())
|
.then(argument("gamemode", GameModeArgument.gameMode())
|
||||||
.executes(BotCommand::spawn)
|
.executes(BotCommand::spawn)))))
|
||||||
)))
|
))))
|
||||||
)))
|
|
||||||
))
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
dispatcher.register(command);
|
dispatcher.register(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LiteralArgumentBuilder<ServerCommandSource> makeActionCommand(String actionName, EntityPlayerActionPack.ActionType type)
|
private static LiteralArgumentBuilder<CommandSourceStack> makeActionCommand(String actionName, EntityPlayerActionPack.ActionType type)
|
||||||
{
|
{
|
||||||
return literal(actionName)
|
return literal(actionName)
|
||||||
.executes(manipulation(ap -> ap.start(type, EntityPlayerActionPack.Action.once())))
|
.executes(manipulation(ap -> ap.start(type, EntityPlayerActionPack.Action.once())))
|
||||||
@@ -140,7 +149,7 @@ public class BotCommand {
|
|||||||
.executes(c -> manipulate(c, ap -> ap.start(type, EntityPlayerActionPack.Action.interval(IntegerArgumentType.getInteger(c, "ticks")))))));
|
.executes(c -> manipulate(c, ap -> ap.start(type, EntityPlayerActionPack.Action.interval(IntegerArgumentType.getInteger(c, "ticks")))))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LiteralArgumentBuilder<ServerCommandSource> makeDropCommand(String actionName, boolean dropAll)
|
private static LiteralArgumentBuilder<CommandSourceStack> makeDropCommand(String actionName, boolean dropAll)
|
||||||
{
|
{
|
||||||
return literal(actionName)
|
return literal(actionName)
|
||||||
.then(literal("all").executes(manipulation(ap -> ap.drop(-2, dropAll))))
|
.then(literal("all").executes(manipulation(ap -> ap.drop(-2, dropAll))))
|
||||||
@@ -150,36 +159,36 @@ public class BotCommand {
|
|||||||
executes(c -> manipulate(c, ap -> ap.drop(IntegerArgumentType.getInteger(c, "slot"), dropAll))));
|
executes(c -> manipulate(c, ap -> ap.drop(IntegerArgumentType.getInteger(c, "slot"), dropAll))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Collection<String> getPlayerSuggestions(ServerCommandSource source)
|
private static Collection<String> getPlayerSuggestions(CommandSourceStack source)
|
||||||
{
|
{
|
||||||
Set<String> players = new LinkedHashSet<>(List.of("Steve", "Alex"));
|
Set<String> players = new LinkedHashSet<>(List.of("Steve", "Alex"));
|
||||||
players.addAll(source.getPlayerNames());
|
players.addAll(List.of(source.getServer().getPlayerNames()));
|
||||||
return players;
|
return players;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ServerPlayerEntity getPlayer(CommandContext<ServerCommandSource> context)
|
private static ServerPlayer getPlayer(CommandContext<CommandSourceStack> context)
|
||||||
{
|
{
|
||||||
String playerName = StringArgumentType.getString(context, "player");
|
String playerName = StringArgumentType.getString(context, "player");
|
||||||
MinecraftServer server = context.getSource().getServer();
|
MinecraftServer server = context.getSource().getServer();
|
||||||
return server.getPlayerManager().getPlayer(playerName);
|
return server.getPlayerList().getPlayerByName(playerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean cantManipulate(CommandContext<ServerCommandSource> context)
|
private static boolean cantManipulate(CommandContext<CommandSourceStack> context)
|
||||||
{
|
{
|
||||||
PlayerEntity player = getPlayer(context);
|
Player player = getPlayer(context);
|
||||||
ServerCommandSource source = context.getSource();
|
CommandSourceStack source = context.getSource();
|
||||||
if (player == null)
|
if (player == null)
|
||||||
{
|
{
|
||||||
Messenger.m(source, "r Can only manipulate existing players");
|
Messenger.m(source, "r Can only manipulate existing players");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
PlayerEntity sender = source.getPlayer();
|
Player sender = source.getPlayer();
|
||||||
if (sender == null)
|
if (sender == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!source.getServer().getPlayerManager().isOperator(sender.getGameProfile()))
|
if (!source.getServer().getPlayerList().isOp(sender.nameAndId()))
|
||||||
{
|
{
|
||||||
if (sender != player && !(player instanceof EntityPlayerMPFake))
|
if (sender != player && !(player instanceof EntityPlayerMPFake))
|
||||||
{
|
{
|
||||||
@@ -190,27 +199,28 @@ public class BotCommand {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean cantReMove(CommandContext<ServerCommandSource> context)
|
private static boolean cantReMove(CommandContext<CommandSourceStack> context)
|
||||||
{
|
{
|
||||||
if (cantManipulate(context)) return true;
|
if (cantManipulate(context)) return true;
|
||||||
PlayerEntity player = getPlayer(context);
|
Player player = getPlayer(context);
|
||||||
if (player instanceof EntityPlayerMPFake) return false;
|
if (player instanceof EntityPlayerMPFake) return false;
|
||||||
Messenger.m(context.getSource(), "r Only fake players can be moved or killed");
|
Messenger.m(context.getSource(), "r Only fake players can be moved or killed");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean cantSpawn(CommandContext<ServerCommandSource> context)
|
private static boolean cantSpawn(CommandContext<CommandSourceStack> context)
|
||||||
{
|
{
|
||||||
String playerName = getBotPrefix() + StringArgumentType.getString(context, "player");
|
String playerName = getBotPrefix() + StringArgumentType.getString(context, "player");
|
||||||
MinecraftServer server = context.getSource().getServer();
|
MinecraftServer server = context.getSource().getServer();
|
||||||
PlayerManager manager = server.getPlayerManager();
|
PlayerList manager = server.getPlayerList();
|
||||||
|
|
||||||
if (manager.getPlayer(playerName) != null)
|
if (manager.getPlayerByName(playerName) != null)
|
||||||
{
|
{
|
||||||
Messenger.m(context.getSource(), "r Player ", "rb " + playerName, "r is already logged on");
|
Messenger.m(context.getSource(), "r Player ", "rb " + playerName, "r is already logged on");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
GameProfile profile = server.getUserCache().findByName(playerName).orElse(null);
|
GameProfile profile = server.services().nameToIdCache().get(playerName)
|
||||||
|
.map(n -> new GameProfile(n.id(), n.name())).orElse(null);
|
||||||
if (profile == null)
|
if (profile == null)
|
||||||
{
|
{
|
||||||
if (!CarpetSettings.allowSpawningOfflinePlayers)
|
if (!CarpetSettings.allowSpawningOfflinePlayers)
|
||||||
@@ -219,15 +229,15 @@ public class BotCommand {
|
|||||||
"Banned players can only be summoned in Singleplayer and in servers in off-line mode.");
|
"Banned players can only be summoned in Singleplayer and in servers in off-line mode.");
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
profile = new GameProfile(Uuids.getOfflinePlayerUuid(playerName), playerName);
|
profile = new GameProfile(UUIDUtil.createOfflinePlayerUUID(playerName), playerName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (manager.getUserBanList().contains(profile))
|
if (manager.getBans().isBanned(new net.minecraft.server.players.NameAndId(profile)))
|
||||||
{
|
{
|
||||||
Messenger.m(context.getSource(), "r Player ", "rb " + playerName, "r is banned on this server");
|
Messenger.m(context.getSource(), "r Player ", "rb " + playerName, "r is banned on this server");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (manager.isWhitelistEnabled() && manager.isWhitelisted(profile) && !context.getSource().hasPermissionLevel(2))
|
if (manager.isUsingWhitelist() && manager.isWhiteListed(new net.minecraft.server.players.NameAndId(profile)) && !hasLevel(context.getSource(), 2))
|
||||||
{
|
{
|
||||||
Messenger.m(context.getSource(), "r Whitelisted players can only be spawned by operators");
|
Messenger.m(context.getSource(), "r Whitelisted players can only be spawned by operators");
|
||||||
return true;
|
return true;
|
||||||
@@ -239,32 +249,30 @@ public class BotCommand {
|
|||||||
return SLSCarpetSettings.botPrefix.equals("#none") ? "" : SLSCarpetSettings.botPrefix;
|
return SLSCarpetSettings.botPrefix.equals("#none") ? "" : SLSCarpetSettings.botPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int kill(CommandContext<ServerCommandSource> context)
|
private static int kill(CommandContext<CommandSourceStack> context)
|
||||||
{
|
{
|
||||||
if (cantReMove(context)) return 0;
|
if (cantReMove(context)) return 0;
|
||||||
getPlayer(context).kill();
|
{ var _p = getPlayer(context); _p.kill((ServerLevel) _p.level()); }
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int respawn(CommandContext<ServerCommandSource> context) {
|
private static int respawn(CommandContext<CommandSourceStack> context) {
|
||||||
var player = getPlayer(context);
|
var player = getPlayer(context);
|
||||||
if (player instanceof EntityPlayerMPFake && ((SLSBotAccessor)player).carpet_SLS_Addition$isBot()) {
|
if (player instanceof EntityPlayerMPFake && ((SLSBotAccessor)player).carpet_SLS_Addition$isBot()) {
|
||||||
((SLSBotAccessor)player).carpet_SLS_Addition$setSpawnTime(System.currentTimeMillis());
|
((SLSBotAccessor)player).carpet_SLS_Addition$setSpawnTime(System.currentTimeMillis());
|
||||||
context.getSource().sendMessage(
|
context.getSource().sendSuccess(
|
||||||
Text.translatable("carpet.slsa.bot.respawned", player.getNameForScoreboard())
|
() -> Component.translatable("carpet.slsa.bot.respawned", player.getScoreboardName())
|
||||||
.setStyle(
|
.setStyle(Style.EMPTY.withColor(net.minecraft.ChatFormatting.GREEN)),
|
||||||
Style.EMPTY.withColor(Formatting.GREEN)
|
false
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.getSource().sendMessage(
|
context.getSource().sendSuccess(
|
||||||
Text.translatable("carpet.slsa.bot.not_a_bot", player.getNameForScoreboard())
|
() -> Component.translatable("carpet.slsa.bot.not_a_bot", player.getScoreboardName())
|
||||||
.setStyle(
|
.setStyle(Style.EMPTY.withColor(net.minecraft.ChatFormatting.RED)),
|
||||||
Style.EMPTY.withColor(Formatting.RED)
|
false
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -288,22 +296,22 @@ public class BotCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int spawn(CommandContext<ServerCommandSource> context) throws CommandSyntaxException
|
private static int spawn(CommandContext<CommandSourceStack> context) throws CommandSyntaxException
|
||||||
{
|
{
|
||||||
if (cantSpawn(context)) return 0;
|
if (cantSpawn(context)) return 0;
|
||||||
|
|
||||||
ServerCommandSource source = context.getSource();
|
CommandSourceStack source = context.getSource();
|
||||||
Vec3d pos = getArgOrDefault(
|
Vec3 pos = getArgOrDefault(
|
||||||
() -> Vec3ArgumentType.getVec3(context, "position"),
|
() -> Vec3Argument.getVec3(context, "position"),
|
||||||
source.getPosition()
|
source.getPosition()
|
||||||
);
|
);
|
||||||
Vec2f facing = getArgOrDefault(
|
Vec2 facing = getArgOrDefault(
|
||||||
() -> RotationArgumentType.getRotation(context, "direction").toAbsoluteRotation(source),
|
() -> RotationArgument.getRotation(context, "direction").getRotation(source),
|
||||||
source.getRotation()
|
source.getRotation()
|
||||||
);
|
);
|
||||||
RegistryKey<World> dimType = getArgOrDefault(
|
ResourceKey<Level> dimType = getArgOrDefault(
|
||||||
() -> DimensionArgumentType.getDimensionArgument(context, "dimension").getRegistryKey(),
|
() -> DimensionArgument.getDimension(context, "dimension").dimension(),
|
||||||
source.getWorld().getRegistryKey()
|
source.getLevel().dimension()
|
||||||
);
|
);
|
||||||
|
|
||||||
String playerName = getBotPrefix() + StringArgumentType.getString(context, "player");
|
String playerName = getBotPrefix() + StringArgumentType.getString(context, "player");
|
||||||
@@ -313,7 +321,7 @@ public class BotCommand {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!World.isValid(BlockPos.ofFloored(pos)))
|
if (!Level.isInSpawnableBounds(BlockPos.containing(pos)))
|
||||||
{
|
{
|
||||||
Messenger.m(source, "rb Player " + playerName + " cannot be placed outside of the world");
|
Messenger.m(source, "rb Player " + playerName + " cannot be placed outside of the world");
|
||||||
return 0;
|
return 0;
|
||||||
@@ -328,32 +336,32 @@ public class BotCommand {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
((PlayerAccessor) bot).carpet_SLS_Addition$setDisplayName(Text.empty().append(Text.literal("[%s] ".formatted(source.getName())).setStyle(Style.EMPTY.withColor(Formatting.AQUA))).append(Text.literal(playerName).setStyle(Style.EMPTY)));
|
((PlayerAccessor) bot).carpet_SLS_Addition$setDisplayName(Component.empty().append(Component.literal("[%s] ".formatted(source.getTextName())).setStyle(Style.EMPTY.withColor(net.minecraft.ChatFormatting.AQUA))).append(Component.literal(playerName).setStyle(Style.EMPTY)));
|
||||||
|
|
||||||
ServerMain.server.getPlayerManager().broadcast(Text.empty()
|
ServerMain.server.getPlayerList().broadcastSystemMessage(Component.empty()
|
||||||
.append(Text.literal("假人").setStyle(Style.EMPTY.withColor(Formatting.GREEN)))
|
.append(Component.literal("假人").setStyle(Style.EMPTY.withColor(net.minecraft.ChatFormatting.GREEN)))
|
||||||
.append(Text.literal(playerName).setStyle(Style.EMPTY.withColor(Formatting.GOLD).withBold(true)))
|
.append(Component.literal(playerName).setStyle(Style.EMPTY.withColor(net.minecraft.ChatFormatting.GOLD).withBold(true)))
|
||||||
.append(Text.literal("由玩家").setStyle(Style.EMPTY.withColor(Formatting.GREEN)))
|
.append(Component.literal("由玩家").setStyle(Style.EMPTY.withColor(net.minecraft.ChatFormatting.GREEN)))
|
||||||
.append(source.getDisplayName())
|
.append(source.getDisplayName())
|
||||||
.append(Text.literal("召唤!").setStyle(Style.EMPTY.withColor(Formatting.GREEN))), false);
|
.append(Component.literal("召唤!").setStyle(Style.EMPTY.withColor(net.minecraft.ChatFormatting.GREEN))), false);
|
||||||
|
|
||||||
ServerMain.server.getPlayerManager().sendToAll(new PlayerListS2CPacket(
|
ServerMain.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(
|
||||||
PlayerListS2CPacket.Action.UPDATE_DISPLAY_NAME,
|
ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME,
|
||||||
bot
|
bot
|
||||||
));
|
));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static EntityPlayerMPFake createBot(String username, MinecraftServer server, Vec3d pos, double yaw, double pitch, RegistryKey<World> dimensionId) {
|
public static EntityPlayerMPFake createBot(String username, MinecraftServer server, Vec3 pos, double yaw, double pitch, ResourceKey<Level> dimensionId) {
|
||||||
ServerWorld worldIn = server.getWorld(dimensionId);
|
ServerLevel worldIn = server.getLevel(dimensionId);
|
||||||
UserCache.setUseRemote(false);
|
server.services().nameToIdCache().resolveOfflineUsers(false);
|
||||||
GameProfile gameprofile;
|
GameProfile gameprofile;
|
||||||
try {
|
try {
|
||||||
gameprofile = server.getUserCache().findByName(username).orElse(null); //findByName .orElse(null)
|
var nameAndId = server.services().nameToIdCache().get(username).orElse(null);
|
||||||
}
|
gameprofile = nameAndId == null ? null : new GameProfile(nameAndId.id(), nameAndId.name());
|
||||||
finally {
|
} finally {
|
||||||
UserCache.setUseRemote(server.isDedicated() && server.isOnlineMode());
|
server.services().nameToIdCache().resolveOfflineUsers(server.isDedicatedServer() && server.usesAuthentication());
|
||||||
}
|
}
|
||||||
if (gameprofile == null)
|
if (gameprofile == null)
|
||||||
{
|
{
|
||||||
@@ -361,37 +369,36 @@ public class BotCommand {
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
gameprofile = new GameProfile(Uuids.getOfflinePlayerUuid(username), username);
|
gameprofile = new GameProfile(UUIDUtil.createOfflinePlayerUUID(username), username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 孩子不懂,用反射写着玩的,报错了记得随Carpet一起升级一下
|
// 孩子不懂,用反射写着玩的,报错了记得随Carpet一起升级一下
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Class<EntityPlayerMPFake> fakePlayerClass = (Class<EntityPlayerMPFake>)Class.forName("carpet.patches.EntityPlayerMPFake");
|
Class<EntityPlayerMPFake> fakePlayerClass = (Class<EntityPlayerMPFake>)Class.forName("carpet.patches.EntityPlayerMPFake");
|
||||||
Constructor<EntityPlayerMPFake> constructor = fakePlayerClass.getDeclaredConstructor(
|
Constructor<EntityPlayerMPFake> constructor = fakePlayerClass.getDeclaredConstructor(
|
||||||
MinecraftServer.class,
|
MinecraftServer.class,
|
||||||
ServerWorld.class,
|
ServerLevel.class,
|
||||||
GameProfile.class,
|
GameProfile.class,
|
||||||
SyncedClientOptions.class,
|
ClientInformation.class,
|
||||||
boolean.class
|
boolean.class
|
||||||
);
|
);
|
||||||
constructor.setAccessible(true);
|
constructor.setAccessible(true);
|
||||||
EntityPlayerMPFake bot = constructor.newInstance(server, worldIn, gameprofile, SyncedClientOptions.createDefault(), false);
|
EntityPlayerMPFake bot = constructor.newInstance(server, worldIn, gameprofile, ClientInformation.createDefault(), false);
|
||||||
|
|
||||||
bot.fixStartingPosition = () -> bot.refreshPositionAndAngles(pos.x, pos.y, pos.z, (float) yaw, (float) pitch);
|
bot.fixStartingPosition = () -> bot.snapTo(pos.x, pos.y, pos.z, (float) yaw, (float) pitch);
|
||||||
server.getPlayerManager().onPlayerConnect(new FakeClientConnection(NetworkSide.SERVERBOUND), bot, new ConnectedClientData(gameprofile, 0, bot.getClientOptions(), false));
|
server.getPlayerList().placeNewPlayer(new FakeClientConnection(PacketFlow.SERVERBOUND), bot, new CommonListenerCookie(gameprofile, 0, bot.clientInformation(), false, new ClientDebugInfo()));
|
||||||
|
|
||||||
bot.teleport(worldIn, pos.x, pos.y, pos.z, (float) yaw, (float) pitch);
|
bot.teleportTo(worldIn, pos.x, pos.y, pos.z, Set.of(), (float) yaw, (float) pitch, true);
|
||||||
bot.setHealth(20.0F);
|
bot.setHealth(20.0F);
|
||||||
bot.unsetRemoved();
|
bot.unsetRemoved();
|
||||||
bot.getAttributeInstance(EntityAttributes.GENERIC_STEP_HEIGHT).setBaseValue(0.6F);
|
bot.getAttribute(Attributes.STEP_HEIGHT).setBaseValue(0.6F);
|
||||||
bot.interactionManager.changeGameMode(GameMode.SURVIVAL);
|
bot.gameMode.changeGameModeForPlayer(GameType.SURVIVAL);
|
||||||
|
|
||||||
server.getPlayerManager().sendToDimension(new EntitySetHeadYawS2CPacket(bot, (byte) (bot.headYaw * 256 / 360)), dimensionId);//bot.dimension);
|
server.getPlayerList().broadcastAll(new ClientboundRotateHeadPacket(bot, (byte) (bot.yHeadRot * 256 / 360)), dimensionId);
|
||||||
server.getPlayerManager().sendToDimension(new EntityPositionS2CPacket(bot), dimensionId);
|
server.getPlayerList().broadcastAll(ClientboundEntityPositionSyncPacket.of(bot), dimensionId);
|
||||||
|
|
||||||
bot.getDataTracker().set(PlayerEntity.PLAYER_MODEL_PARTS, (byte) 0x7f); // show all model layers (incl. capes)
|
bot.entityData.set(ServerPlayer.DATA_PLAYER_MODE_CUSTOMISATION, (byte) 0x7f); // show all model layers (incl. capes)
|
||||||
bot.getAbilities().flying = false;
|
bot.getAbilities().flying = false;
|
||||||
|
|
||||||
((SLSBotAccessor)bot).carpet_SLS_Addition$setBot(true);
|
((SLSBotAccessor)bot).carpet_SLS_Addition$setBot(true);
|
||||||
@@ -406,18 +413,18 @@ public class BotCommand {
|
|||||||
|
|
||||||
private static int maxNameLength(MinecraftServer server)
|
private static int maxNameLength(MinecraftServer server)
|
||||||
{
|
{
|
||||||
return server.getServerPort() >= 0 ? SharedConstants.field_49170 : 40;
|
return server.getPort() >= 0 ? 16 : 40;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int manipulate(CommandContext<ServerCommandSource> context, Consumer<EntityPlayerActionPack> action)
|
private static int manipulate(CommandContext<CommandSourceStack> context, Consumer<EntityPlayerActionPack> action)
|
||||||
{
|
{
|
||||||
if (cantManipulate(context)) return 0;
|
if (cantManipulate(context)) return 0;
|
||||||
ServerPlayerEntity player = getPlayer(context);
|
ServerPlayer player = getPlayer(context);
|
||||||
action.accept(((ServerPlayerInterface) player).getActionPack());
|
action.accept(((ServerPlayerInterface) player).getActionPack());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Command<ServerCommandSource> manipulation(Consumer<EntityPlayerActionPack> action)
|
private static Command<CommandSourceStack> manipulation(Consumer<EntityPlayerActionPack> action)
|
||||||
{
|
{
|
||||||
return c -> manipulate(c, action);
|
return c -> manipulate(c, action);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.command;
|
||||||
|
|
||||||
|
import carpet.patches.EntityPlayerMPFake;
|
||||||
|
import com.github.zly2006.carpetslsaddition.util.BotListManager;
|
||||||
|
import com.github.zly2006.carpetslsaddition.util.access.SLSBotAccessor;
|
||||||
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
|
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||||
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||||
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
|
import net.minecraft.commands.Commands;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static net.minecraft.commands.Commands.argument;
|
||||||
|
import static net.minecraft.commands.Commands.literal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /botlist —— 维护"受控假人名单",供 /botall 限定操作范围。
|
||||||
|
*
|
||||||
|
* <p>名单持久化在 config/slsa-bot-list.json,重启保留。
|
||||||
|
*/
|
||||||
|
public class BotListCommand {
|
||||||
|
|
||||||
|
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||||
|
LiteralArgumentBuilder<CommandSourceStack> command = literal("botlist")
|
||||||
|
.requires(source -> Commands.LEVEL_OWNERS.check(source.permissions())
|
||||||
|
|| me.lucko.fabric.api.permissions.v0.Permissions.check(source, "slsaddition.command.bot"))
|
||||||
|
.then(literal("show").executes(BotListCommand::show))
|
||||||
|
.then(literal("clear").executes(c -> {
|
||||||
|
BotListManager.clear();
|
||||||
|
c.getSource().sendSuccess(() -> Component.literal("[SLSA] 已清空假人名单"), false);
|
||||||
|
return 1;
|
||||||
|
}))
|
||||||
|
.then(literal("add")
|
||||||
|
.then(argument("names", StringArgumentType.greedyString())
|
||||||
|
.executes(c -> add(c.getSource(), StringArgumentType.getString(c, "names")))))
|
||||||
|
.then(literal("remove")
|
||||||
|
.then(argument("names", StringArgumentType.greedyString())
|
||||||
|
.executes(c -> remove(c.getSource(), StringArgumentType.getString(c, "names")))))
|
||||||
|
.then(literal("addall").executes(BotListCommand::addAll));
|
||||||
|
dispatcher.register(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int show(com.mojang.brigadier.context.CommandContext<CommandSourceStack> c) {
|
||||||
|
Set<String> names = BotListManager.getNames();
|
||||||
|
if (names.isEmpty()) {
|
||||||
|
c.getSource().sendSuccess(() -> Component.literal("[SLSA] 假人名单为空(此时 /botall 操作所有假人)"), false);
|
||||||
|
} else {
|
||||||
|
c.getSource().sendSuccess(() -> Component.literal(
|
||||||
|
"[SLSA] 假人名单(" + names.size() + "):" + String.join(", ", names)), false);
|
||||||
|
}
|
||||||
|
return names.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int add(CommandSourceStack source, String raw) {
|
||||||
|
String[] parts = raw.trim().split("\\s+");
|
||||||
|
int n = 0;
|
||||||
|
for (String p : parts) {
|
||||||
|
if (!p.isEmpty() && BotListManager.add(p)) {
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final int added = n;
|
||||||
|
source.sendSuccess(() -> Component.literal(
|
||||||
|
"[SLSA] 已添加 " + added + " 个名字,名单共 " + BotListManager.getNames().size() + " 个"), false);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int remove(CommandSourceStack source, String raw) {
|
||||||
|
String[] parts = raw.trim().split("\\s+");
|
||||||
|
int n = 0;
|
||||||
|
for (String p : parts) {
|
||||||
|
if (!p.isEmpty() && BotListManager.remove(p)) {
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final int removed = n;
|
||||||
|
source.sendSuccess(() -> Component.literal(
|
||||||
|
"[SLSA] 已移除 " + removed + " 个名字,名单共 " + BotListManager.getNames().size() + " 个"), false);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 把当前所有在线 SLS 假人加入名单。 */
|
||||||
|
private static int addAll(com.mojang.brigadier.context.CommandContext<CommandSourceStack> c) {
|
||||||
|
int n = 0;
|
||||||
|
for (ServerPlayer player : c.getSource().getServer().getPlayerList().getPlayers()) {
|
||||||
|
if (player instanceof EntityPlayerMPFake && ((SLSBotAccessor) player).carpet_SLS_Addition$isBot()) {
|
||||||
|
if (BotListManager.add(player.getScoreboardName())) {
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final int added = n;
|
||||||
|
c.getSource().sendSuccess(() -> Component.literal(
|
||||||
|
"[SLSA] 已把 " + added + " 个在线假人加入名单,名单共 " + BotListManager.getNames().size() + " 个"), false);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,29 +2,31 @@ package com.github.zly2006.carpetslsaddition.command;
|
|||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import com.mojang.brigadier.CommandDispatcher;
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
import net.minecraft.entity.EquipmentSlot;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.commands.Commands;
|
||||||
import net.minecraft.server.command.CommandManager;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraft.server.command.ServerCommandSource;
|
import net.minecraft.world.entity.EquipmentSlot;
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
public class HatCommand {
|
public class HatCommand {
|
||||||
public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
|
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||||
dispatcher.register(CommandManager.literal("hat")
|
dispatcher.register(Commands.literal("hat")
|
||||||
.requires(ServerCommandSource::isExecutedByPlayer)
|
|
||||||
.requires((commandSource) -> SLSCarpetSettings.canUseHatCommand)
|
|
||||||
.executes(context -> {
|
.executes(context -> {
|
||||||
ServerPlayerEntity player = context.getSource().getPlayer();
|
if (!SLSCarpetSettings.canUseHatCommand) {
|
||||||
assert player != null;
|
return 0;
|
||||||
ItemStack stack = player.getMainHandStack();
|
}
|
||||||
ItemStack head = player.getEquippedStack(EquipmentSlot.HEAD);
|
ServerPlayer player = context.getSource().getPlayer();
|
||||||
player.equipStack(EquipmentSlot.HEAD, stack);
|
if (player == null) {
|
||||||
player.getInventory().setStack(player.getInventory().selectedSlot, head);
|
return 0;
|
||||||
player.currentScreenHandler.syncState();
|
}
|
||||||
|
ItemStack stack = player.getMainHandItem();
|
||||||
|
ItemStack head = player.getItemBySlot(EquipmentSlot.HEAD);
|
||||||
|
player.setItemSlot(EquipmentSlot.HEAD, stack);
|
||||||
|
player.getInventory().setItem(player.getInventory().getSelectedSlot(), head);
|
||||||
|
player.containerMenu.sendAllDataToRemote();
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
})
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,35 +3,44 @@ package com.github.zly2006.carpetslsaddition.command;
|
|||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import com.github.zly2006.carpetslsaddition.util.SitEntity;
|
import com.github.zly2006.carpetslsaddition.util.SitEntity;
|
||||||
import com.mojang.brigadier.CommandDispatcher;
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
import net.minecraft.entity.decoration.ArmorStandEntity;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.server.command.CommandManager;
|
import net.minecraft.commands.Commands;
|
||||||
import net.minecraft.server.command.ServerCommandSource;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.entity.decoration.ArmorStand;
|
||||||
|
|
||||||
public class SitCommand {
|
public class SitCommand {
|
||||||
public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
|
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||||
dispatcher.register(CommandManager.literal("sit")
|
dispatcher.register(Commands.literal("sit")
|
||||||
.requires(ServerCommandSource::isExecutedByPlayer)
|
|
||||||
.requires((commandSource) -> SLSCarpetSettings.canUseSitCommand)
|
|
||||||
.executes(context -> {
|
.executes(context -> {
|
||||||
ServerPlayerEntity player = context.getSource().getPlayer();
|
if (!SLSCarpetSettings.canUseSitCommand) {
|
||||||
assert player != null;
|
return 0;
|
||||||
|
|
||||||
if (player.getVehicle() != null || !player.isOnGround()) { // 防止错误的坐下行为
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
ServerPlayer player = context.getSource().getPlayer();
|
||||||
World world = player.getWorld();
|
if (player == null) {
|
||||||
|
return 0;
|
||||||
ArmorStandEntity armorStandEntity = new ArmorStandEntity(world, player.getX(), player.getY(), player.getZ());
|
}
|
||||||
((SitEntity) armorStandEntity).setSitEntity(true);
|
sitPlayer(player);
|
||||||
world.spawnEntity(armorStandEntity);
|
|
||||||
player.setSneaking(false);
|
|
||||||
player.startRiding(armorStandEntity);
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 让指定玩家原地坐下(生成一个标记为 SitEntity 的 ArmorStand 并骑乘)。
|
||||||
|
*
|
||||||
|
* @return 若成功坐下返回 true,否则返回 false(如已在骑乘/不在地面)。
|
||||||
|
*/
|
||||||
|
public static boolean sitPlayer(ServerPlayer player) {
|
||||||
|
if (player.getVehicle() != null || !player.onGround()) { // 防止错误的坐下行为
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ServerLevel world = (ServerLevel) player.level();
|
||||||
|
ArmorStand armorStandEntity = new ArmorStand(world, player.getX(), player.getY(), player.getZ());
|
||||||
|
((SitEntity) armorStandEntity).setSitEntity(true);
|
||||||
|
world.addFreshEntity(armorStandEntity);
|
||||||
|
player.setShiftKeyDown(false);
|
||||||
|
player.startRiding(armorStandEntity);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+6
-6
@@ -1,15 +1,15 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.block;
|
package com.github.zly2006.carpetslsaddition.mixin.block;
|
||||||
|
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
import net.minecraft.block.pattern.BlockPattern;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.block.pattern.CachedBlockPosition;
|
import net.minecraft.core.Direction;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.world.level.block.state.pattern.BlockInWorld;
|
||||||
import net.minecraft.util.math.Direction;
|
import net.minecraft.world.level.block.state.pattern.BlockPattern;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||||
|
|
||||||
@Mixin(BlockPattern.class)
|
@Mixin(BlockPattern.class)
|
||||||
public interface BlockPatternTestTransformInvoker {
|
public interface BlockPatternTestTransformInvoker {
|
||||||
@Invoker("testTransform")
|
@Invoker("matches")
|
||||||
BlockPattern.Result invokeTestTransform(BlockPos frontTopLeft, Direction forwards, Direction up, LoadingCache<BlockPos, CachedBlockPosition> cache);
|
BlockPattern.BlockPatternMatch invokeTestTransform(BlockPos frontTopLeft, Direction forwards, Direction up, LoadingCache<BlockPos, BlockInWorld> cache);
|
||||||
}
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.mixin.block;
|
||||||
|
|
||||||
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
|
import net.minecraft.world.entity.projectile.Projectile;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.block.DecoratedPotBlock;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(DecoratedPotBlock.class)
|
||||||
|
public class MixinDecoratedPotBlock {
|
||||||
|
// sturdyDecoratedPot:投掷物命中不碎。
|
||||||
|
@Inject(method = "onProjectileHit", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void sturdyOnProjectileHit(Level level, BlockState state, BlockHitResult hit, Projectile projectile, CallbackInfo ci) {
|
||||||
|
if (SLSCarpetSettings.sturdyDecoratedPot) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unbreakableDecoratedPot:玩家挖掘时不设 CRACKED(无裂纹效果),仍正常挖掉。
|
||||||
|
@Redirect(
|
||||||
|
method = "playerWillDestroy",
|
||||||
|
at = @At(
|
||||||
|
value = "INVOKE",
|
||||||
|
target = "Lnet/minecraft/world/level/block/state/BlockState;setValue(Lnet/minecraft/world/level/block/state/properties/Property;Ljava/lang/Comparable;)Ljava/lang/Object;"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
private Object noCrackOnPlayerBreak(BlockState state, net.minecraft.world.level.block.state.properties.Property property, Comparable value) {
|
||||||
|
if (SLSCarpetSettings.unbreakableDecoratedPot) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
return state.setValue(property, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.mixin.block;
|
||||||
|
|
||||||
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
|
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||||
|
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.LiquidBlock;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.material.FluidState;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 26.4 快照加入的“碎冰”(Brittle Ice)机制:
|
||||||
|
* LiquidBlock#shouldSpreadLiquid 中,水流方块任意水平方向的邻居是蓝冰时,
|
||||||
|
* 会把当前水流方块替换为碎冰。
|
||||||
|
*
|
||||||
|
* 开启 noBrittleIceFromFlowingWater 规则后:
|
||||||
|
* 只有「水源方块」贴蓝冰时照常变碎冰;所有「流动的水」(横向、下落)都不再变碎冰,
|
||||||
|
* 且水流扩散不受影响。
|
||||||
|
*
|
||||||
|
* 实现:原版判断邻居是不是蓝冰用 BlockState#is(Blocks.BLUE_ICE),一旦命中就
|
||||||
|
* 变碎冰并 return false(中断扩散)。本 mixin 让流动水贴蓝冰时这次 is() 返回 false,
|
||||||
|
* 整个“变碎冰”分支被跳过,shouldSpreadLiquid 正常返回 true,水流照常扩散。
|
||||||
|
* 位置信息通过 ThreadLocal 从方法入口传入。
|
||||||
|
*/
|
||||||
|
@Mixin(LiquidBlock.class)
|
||||||
|
public class MixinFlowingWaterBrittleIce {
|
||||||
|
@Unique
|
||||||
|
private static final ThreadLocal<Level> CURRENT_LEVEL = new ThreadLocal<>();
|
||||||
|
@Unique
|
||||||
|
private static final ThreadLocal<BlockPos> CURRENT_POS = new ThreadLocal<>();
|
||||||
|
|
||||||
|
@Inject(method = "shouldSpreadLiquid", at = @At("HEAD"))
|
||||||
|
private void captureArgs(Level level, BlockPos pos, BlockState state, CallbackInfoReturnable<Boolean> cir) {
|
||||||
|
CURRENT_LEVEL.set(level);
|
||||||
|
CURRENT_POS.set(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@WrapOperation(
|
||||||
|
method = "shouldSpreadLiquid",
|
||||||
|
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockState;is(Ljava/lang/Object;)Z")
|
||||||
|
)
|
||||||
|
private boolean wrapIsBlueIce(BlockState instance, Object target, Operation<Boolean> original) {
|
||||||
|
Boolean result = original.call(instance, target);
|
||||||
|
if (!result) return false;
|
||||||
|
// 只有判断目标确实是 BLUE_ICE 时才可能干预
|
||||||
|
if (!(target instanceof net.minecraft.world.level.block.Block b) || !b.equals(Blocks.BLUE_ICE)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!SLSCarpetSettings.noBrittleIceFromFlowingWater) return true;
|
||||||
|
|
||||||
|
Level level = CURRENT_LEVEL.get();
|
||||||
|
BlockPos pos = CURRENT_POS.get();
|
||||||
|
if (level == null || pos == null) return true;
|
||||||
|
|
||||||
|
FluidState current = level.getFluidState(pos);
|
||||||
|
// 流动水(非水源)不让它触发碎冰替换
|
||||||
|
if (!current.isSource()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.block;
|
package com.github.zly2006.carpetslsaddition.mixin.block;
|
||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import net.minecraft.world.dimension.NetherPortal;
|
import net.minecraft.world.level.portal.PortalShape;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.Constant;
|
import org.spongepowered.asm.mixin.injection.Constant;
|
||||||
import org.spongepowered.asm.mixin.injection.ModifyConstant;
|
import org.spongepowered.asm.mixin.injection.ModifyConstant;
|
||||||
|
|
||||||
@Mixin(NetherPortal.class)
|
@Mixin(PortalShape.class)
|
||||||
public class MixinNetherPortal {
|
public class MixinNetherPortal {
|
||||||
@ModifyConstant(
|
@ModifyConstant(
|
||||||
method = "*",
|
method = "*",
|
||||||
|
|||||||
+11
-5
@@ -1,17 +1,23 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.block;
|
package com.github.zly2006.carpetslsaddition.mixin.block;
|
||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import net.minecraft.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.block.Blocks;
|
import net.minecraft.world.level.block.RedstoneWireBlock;
|
||||||
import net.minecraft.block.RedstoneWireBlock;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
@Mixin(value = RedstoneWireBlock.class, priority = 1)
|
@Mixin(value = RedstoneWireBlock.class, priority = 1)
|
||||||
public class MixinRedstoneWireBlock {
|
public class MixinRedstoneWireBlock {
|
||||||
@Redirect(method = "getRenderConnectionType(Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;Z)Lnet/minecraft/block/enums/WireConnection;", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/BlockState;getBlock()Lnet/minecraft/block/Block;"))
|
// oldRedstoneConnectionLogic:让红石能连到活板门上的红石线。
|
||||||
|
// 新版在 getConnectingSide 里用 `blockState.getBlock() instanceof TrapDoorBlock` 判断。
|
||||||
|
// 与旧版一致的做法是把这个 getBlock() 结果换成 AIR(绕过活板门特殊分支,走 canSurviveOn)。
|
||||||
|
@Redirect(
|
||||||
|
method = "getConnectingSide(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/Direction;Z)Lnet/minecraft/world/level/block/state/properties/RedstoneSide;",
|
||||||
|
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockState;getBlock()Lnet/minecraft/world/level/block/Block;")
|
||||||
|
)
|
||||||
public Block redirectConnectionBlockState(BlockState instance) {
|
public Block redirectConnectionBlockState(BlockState instance) {
|
||||||
if (SLSCarpetSettings.oldRedstoneConnectionLogic) {
|
if (SLSCarpetSettings.oldRedstoneConnectionLogic) {
|
||||||
return Blocks.AIR;
|
return Blocks.AIR;
|
||||||
|
|||||||
+9
-7
@@ -3,21 +3,23 @@ package com.github.zly2006.carpetslsaddition.mixin.block;
|
|||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||||
import net.minecraft.block.spawner.TrialSpawnerConfig;
|
import net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerConfig;
|
||||||
import net.minecraft.block.spawner.TrialSpawnerData;
|
import net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerStateData;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
|
||||||
@Mixin(TrialSpawnerData.class)
|
@Mixin(TrialSpawnerStateData.class)
|
||||||
public class MixinTrialSpawnerData {
|
public class MixinTrialSpawnerData {
|
||||||
@WrapOperation(
|
@WrapOperation(
|
||||||
method = "resetAndClearMobs",
|
method = "reset",
|
||||||
at = @At(
|
at = @At(
|
||||||
value = "INVOKE",
|
value = "INVOKE",
|
||||||
target = "Lnet/minecraft/block/spawner/TrialSpawnerConfig;getCooldownLength()J"
|
target = "Lnet/minecraft/world/level/block/entity/trialspawner/TrialSpawnerConfig;ticksBetweenItemSpawners()J",
|
||||||
)
|
ordinal = 0
|
||||||
|
),
|
||||||
|
require = 0
|
||||||
)
|
)
|
||||||
private long hackCooldown(TrialSpawnerConfig instance, Operation<Integer> original) {
|
private long hackCooldown(TrialSpawnerConfig instance, Operation<Long> original) {
|
||||||
if (SLSCarpetSettings.trialSpawnerCD < 0) {
|
if (SLSCarpetSettings.trialSpawnerCD < 0) {
|
||||||
return original.call(instance);
|
return original.call(instance);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+8
-6
@@ -3,22 +3,24 @@ package com.github.zly2006.carpetslsaddition.mixin.block;
|
|||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||||
import net.minecraft.block.enums.TrialSpawnerState;
|
import net.minecraft.world.level.block.entity.trialspawner.TrialSpawner;
|
||||||
import net.minecraft.block.spawner.TrialSpawnerLogic;
|
import net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerState;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
|
||||||
@Mixin(TrialSpawnerState.class)
|
@Mixin(TrialSpawnerState.class)
|
||||||
public class MixinTrialSpawnerState {
|
public class MixinTrialSpawnerState {
|
||||||
|
// trialSpawnerCD:覆盖试炼刷怪笼的冷却长度。
|
||||||
|
// 新版 TrialSpawnerState.tickAndGetNext 里调用 TrialSpawner.getTargetCooldownLength() 三次,
|
||||||
|
// 不指定 ordinal 以覆盖全部调用。
|
||||||
@WrapOperation(
|
@WrapOperation(
|
||||||
method = "tick",
|
method = "tickAndGetNext",
|
||||||
at = @At(
|
at = @At(
|
||||||
value = "INVOKE",
|
value = "INVOKE",
|
||||||
ordinal = 0,
|
target = "Lnet/minecraft/world/level/block/entity/trialspawner/TrialSpawner;getTargetCooldownLength()I"
|
||||||
target = "Lnet/minecraft/block/spawner/TrialSpawnerLogic;getCooldownLength()I"
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
private int tick(TrialSpawnerLogic instance, Operation<Integer> original) {
|
private int tick(TrialSpawner instance, Operation<Integer> original) {
|
||||||
if (SLSCarpetSettings.trialSpawnerCD < 0) {
|
if (SLSCarpetSettings.trialSpawnerCD < 0) {
|
||||||
return original.call(instance);
|
return original.call(instance);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+11
-17
@@ -6,30 +6,26 @@ import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
|||||||
import com.github.zly2006.carpetslsaddition.ServerMain;
|
import com.github.zly2006.carpetslsaddition.ServerMain;
|
||||||
import com.github.zly2006.carpetslsaddition.util.access.SLSBotAccessor;
|
import com.github.zly2006.carpetslsaddition.util.access.SLSBotAccessor;
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
import net.minecraft.network.packet.c2s.common.SyncedClientOptions;
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.network.chat.Style;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
import net.minecraft.server.level.ClientInformation;
|
||||||
import net.minecraft.server.world.ServerWorld;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.text.Style;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraft.text.Text;
|
|
||||||
import net.minecraft.util.Formatting;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
|
||||||
import org.spongepowered.asm.mixin.Unique;
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
@Mixin(EntityPlayerMPFake.class)
|
@Mixin(EntityPlayerMPFake.class)
|
||||||
public abstract class MixinEntityPlayerMPFake extends ServerPlayerEntity implements SLSBotAccessor {
|
public abstract class MixinEntityPlayerMPFake extends ServerPlayer implements SLSBotAccessor {
|
||||||
@Shadow public abstract void kill();
|
|
||||||
|
|
||||||
@Unique
|
@Unique
|
||||||
private boolean bot = false;
|
private boolean bot = false;
|
||||||
@Unique
|
@Unique
|
||||||
private long spawnTime = -1;
|
private long spawnTime = -1;
|
||||||
|
|
||||||
public MixinEntityPlayerMPFake(MinecraftServer server, ServerWorld world, GameProfile profile, SyncedClientOptions clientOptions) {
|
public MixinEntityPlayerMPFake(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) {
|
||||||
super(server, world, profile, clientOptions);
|
super(server, world, profile, clientOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,15 +60,13 @@ public abstract class MixinEntityPlayerMPFake extends ServerPlayerEntity impleme
|
|||||||
long liveTime = currentTime - spawnTime;
|
long liveTime = currentTime - spawnTime;
|
||||||
|
|
||||||
if (liveTime > SLSCarpetSettings.botMaxOnlineTime * 1000) {
|
if (liveTime > SLSCarpetSettings.botMaxOnlineTime * 1000) {
|
||||||
ServerMain.server.getPlayerManager().broadcast(
|
ServerMain.server.getPlayerList().broadcastSystemMessage(
|
||||||
Text.literal(Translations.tr("carpet.slsa.bot.bot_timeout").formatted(this.getNameForScoreboard(), getFormattedTime(SLSCarpetSettings.botMaxOnlineTime)))
|
Component.literal(Translations.tr("carpet.slsa.bot.bot_timeout").formatted(this.getScoreboardName(), getFormattedTime(SLSCarpetSettings.botMaxOnlineTime)))
|
||||||
.setStyle(
|
.setStyle(Style.EMPTY.withColor(net.minecraft.ChatFormatting.RED)),
|
||||||
Style.EMPTY.withColor(Formatting.RED)
|
|
||||||
),
|
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
this.kill();
|
this.kill((net.minecraft.server.level.ServerLevel) this.level());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-16
@@ -1,27 +1,31 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.carpet;
|
package com.github.zly2006.carpetslsaddition.mixin.carpet;
|
||||||
|
|
||||||
import carpet.commands.PlayerCommand;
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import com.mojang.authlib.GameProfile;
|
import net.minecraft.core.UUIDUtil;
|
||||||
import net.minecraft.util.UserCache;
|
import net.minecraft.server.players.CachedUserNameToIdResolver;
|
||||||
import net.minecraft.util.Uuids;
|
import net.minecraft.server.players.NameAndId;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import java.util.Optional;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
@Mixin(PlayerCommand.class)
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* offlineFakePlayers: 让假人查询在离线模式下也能拿到 profile。
|
||||||
|
*
|
||||||
|
* TODO(迁移 26.1): 原重定向 carpet PlayerCommand.cantSpawn 里的 UserCache.findByName。
|
||||||
|
* 新版 Carpet 改用 server.services().nameToIdCache().get(uuid)。
|
||||||
|
* 此处改为直接注入 CachedUserNameToIdResolver.get(UUID)。
|
||||||
|
*/
|
||||||
|
@Mixin(CachedUserNameToIdResolver.class)
|
||||||
public abstract class MixinPlayerCommand {
|
public abstract class MixinPlayerCommand {
|
||||||
|
|
||||||
@Redirect(
|
@Inject(method = "get(Ljava/util/UUID;)Ljava/util/Optional;", at = @At("RETURN"), cancellable = true)
|
||||||
method = "cantSpawn",
|
private void redirectFindByName(UUID uuid, CallbackInfoReturnable<Optional<NameAndId>> cir) {
|
||||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/util/UserCache;findByName(Ljava/lang/String;)Ljava/util/Optional;")
|
if (SLSCarpetSettings.offlineFakePlayers && cir.getReturnValue().isEmpty()) {
|
||||||
)
|
// 无法仅凭 uuid 还原名字,offline 场景由 Carpet createFake 处理
|
||||||
private static Optional<GameProfile> redirectFindByName(UserCache instance, String playerName) {
|
|
||||||
if (SLSCarpetSettings.offlineFakePlayers) {
|
|
||||||
return Optional.of(Uuids.getOfflinePlayerProfile(playerName));
|
|
||||||
} else {
|
|
||||||
return instance.findByName(playerName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+12
-19
@@ -1,13 +1,10 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.datapack;
|
package com.github.zly2006.carpetslsaddition.mixin.datapack;
|
||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import net.minecraft.resource.ResourcePackProfile;
|
import net.minecraft.server.packs.PackType;
|
||||||
import net.minecraft.resource.ResourceType;
|
import net.minecraft.server.packs.repository.Pack;
|
||||||
import net.minecraft.resource.VanillaResourcePackProvider;
|
import net.minecraft.server.packs.repository.ServerPacksSource;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.spongepowered.asm.mixin.Final;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
@@ -19,21 +16,17 @@ import java.util.function.Function;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @author zly2006
|
* @author zly2006
|
||||||
|
*
|
||||||
|
* TODO(迁移 26.1): 原 VanillaResourcePackProvider.forEachProfile 已不存在。
|
||||||
|
* 26.1 数据包系统重构为 PackRepository/BuiltInPackSource 体系。
|
||||||
|
* 此处需改为把 data/slsa/datapacks 作为内置数据包注册。
|
||||||
|
* 当前暂以 ServerPacksSource 为挂载点占位。
|
||||||
*/
|
*/
|
||||||
@Mixin(VanillaResourcePackProvider.class)
|
@Mixin(ServerPacksSource.class)
|
||||||
public abstract class MixinVanillaResourcePackProvider {
|
public abstract class MixinVanillaResourcePackProvider {
|
||||||
@Shadow protected abstract void forEachProfile(@Nullable Path namespacedPath, BiConsumer<String, Function<String, ResourcePackProfile>> consumer);
|
|
||||||
|
|
||||||
@Shadow @Final private ResourceType type;
|
@Inject(method = "createVanillaPackSource", at = @At("RETURN"), require = 0)
|
||||||
|
private static void andMe(CallbackInfo ci) throws URISyntaxException {
|
||||||
@Inject(
|
// TODO: 实现内置 datapack 注册
|
||||||
method = "forEachProfile(Ljava/util/function/BiConsumer;)V",
|
|
||||||
at = @At("RETURN")
|
|
||||||
)
|
|
||||||
private void andMe(BiConsumer<String, Function<String, ResourcePackProfile>> consumer, CallbackInfo ci) throws URISyntaxException {
|
|
||||||
if (type == ResourceType.SERVER_DATA) {
|
|
||||||
Path path = Path.of(SLSCarpetSettings.class.getClassLoader().getResource("data/slsa/datapacks").toURI());
|
|
||||||
this.forEachProfile(path, consumer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.mixin.elytra;
|
||||||
|
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraft.world.item.ItemStackTemplate;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Mutable;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
|
||||||
|
@Mixin(Item.class)
|
||||||
|
public interface ItemAccessor {
|
||||||
|
@Mutable
|
||||||
|
@Accessor("craftingRemainingItem")
|
||||||
|
void carpet_SLS_Addition$setCraftingRemainingItem(ItemStackTemplate item);
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.elytra;
|
|
||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
|
||||||
import net.minecraft.item.ElytraItem;
|
|
||||||
import net.minecraft.item.Equipment;
|
|
||||||
import net.minecraft.item.Item;
|
|
||||||
import net.minecraft.item.ItemStack;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
|
|
||||||
@Mixin(ElytraItem.class)
|
|
||||||
public abstract class MixinElytraItem extends Item implements Equipment {
|
|
||||||
public MixinElytraItem(Settings settings) {
|
|
||||||
super(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasRecipeRemainder() {
|
|
||||||
return SLSCarpetSettings.elytraCraftable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ItemStack getRecipeRemainder(ItemStack stack) {
|
|
||||||
if (SLSCarpetSettings.elytraCraftable) {
|
|
||||||
return stack.copyWithCount(1);
|
|
||||||
}
|
|
||||||
else return ItemStack.EMPTY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +1,29 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.elytra;
|
package com.github.zly2006.carpetslsaddition.mixin.elytra;
|
||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import net.minecraft.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
import net.minecraft.item.Items;
|
import net.minecraft.world.item.ItemStackTemplate;
|
||||||
|
import net.minecraft.world.item.Items;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
@SuppressWarnings("ConstantValue")
|
/**
|
||||||
|
* 26.1 起,配方剩余物改为 Item 的 craftingRemainingItem 字段。
|
||||||
|
* 当 elytraCraftable 开启时,把 ELYTRA 的剩余物设为其自身。
|
||||||
|
*
|
||||||
|
* TODO(迁移): 该字段为 final,此处仅在 Item 构造后条件性覆盖。
|
||||||
|
* 若需运行期动态开关,需改为在配方匹配阶段判断。
|
||||||
|
*/
|
||||||
@Mixin(Item.class)
|
@Mixin(Item.class)
|
||||||
public abstract class MixinItem {
|
public abstract class MixinItem {
|
||||||
@Inject(
|
@Inject(method = "<init>", at = @At("TAIL"))
|
||||||
method = "hasRecipeRemainder",
|
private void onInit(CallbackInfo ci) {
|
||||||
at = @At("HEAD"),
|
Item self = (Item) (Object) this;
|
||||||
cancellable = true
|
if (SLSCarpetSettings.elytraCraftable && self == Items.ELYTRA) {
|
||||||
)
|
((ItemAccessor) this).carpet_SLS_Addition$setCraftingRemainingItem(new ItemStackTemplate(self));
|
||||||
public void hasRecipeRemainder(CallbackInfoReturnable<Boolean> cir) {
|
|
||||||
if (SLSCarpetSettings.elytraCraftable && (Object) this == Items.ELYTRA) {
|
|
||||||
cir.setReturnValue(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject(
|
|
||||||
method = "getRecipeRemainder",
|
|
||||||
at = @At("HEAD"),
|
|
||||||
cancellable = true
|
|
||||||
)
|
|
||||||
public void getRecipeRemainder(CallbackInfoReturnable<Item> cir) {
|
|
||||||
if (SLSCarpetSettings.elytraCraftable && (Object) this == Items.ELYTRA) {
|
|
||||||
cir.setReturnValue(Items.ELYTRA);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+8
-8
@@ -1,20 +1,20 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.entity;
|
package com.github.zly2006.carpetslsaddition.mixin.entity;
|
||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import net.minecraft.entity.EntityType;
|
import net.minecraft.world.entity.EntityType;
|
||||||
import net.minecraft.entity.passive.AnimalEntity;
|
import net.minecraft.world.entity.animal.Animal;
|
||||||
import net.minecraft.entity.passive.ArmadilloEntity;
|
import net.minecraft.world.entity.animal.armadillo.Armadillo;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.level.Level;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
|
||||||
@Mixin(ArmadilloEntity.class)
|
@Mixin(Armadillo.class)
|
||||||
public abstract class MixinArmadilloEntity extends AnimalEntity {
|
public abstract class MixinArmadilloEntity extends Animal {
|
||||||
protected MixinArmadilloEntity(EntityType<? extends AnimalEntity> entityType, World world) {
|
protected MixinArmadilloEntity(EntityType<? extends Animal> entityType, Level world) {
|
||||||
super(entityType, world);
|
super(entityType, world);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canImmediatelyDespawn(double distanceSquared) {
|
public boolean removeWhenFarAway(double distanceSquared) {
|
||||||
return SLSCarpetSettings.armadilloImmediateDespawns;
|
return SLSCarpetSettings.armadilloImmediateDespawns;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+18
-18
@@ -1,24 +1,24 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.entity;
|
package com.github.zly2006.carpetslsaddition.mixin.entity;
|
||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.util.SitEntity;
|
import com.github.zly2006.carpetslsaddition.util.SitEntity;
|
||||||
import net.minecraft.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
import net.minecraft.entity.EntityType;
|
import net.minecraft.world.entity.EntityType;
|
||||||
import net.minecraft.entity.LivingEntity;
|
import net.minecraft.world.entity.LivingEntity;
|
||||||
import net.minecraft.entity.decoration.ArmorStandEntity;
|
import net.minecraft.world.entity.decoration.ArmorStand;
|
||||||
import net.minecraft.nbt.NbtCompound;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.nbt.NbtElement;
|
import net.minecraft.world.level.storage.ValueInput;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.level.storage.ValueOutput;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
@Mixin(ArmorStandEntity.class)
|
@Mixin(ArmorStand.class)
|
||||||
public abstract class MixinArmorStandEntity extends LivingEntity implements SitEntity {
|
public abstract class MixinArmorStandEntity extends LivingEntity implements SitEntity {
|
||||||
private boolean sitEntity = false; // 用于标识该盔甲架是否用于玩家乘坐
|
private boolean sitEntity = false;
|
||||||
|
|
||||||
protected MixinArmorStandEntity(EntityType<? extends LivingEntity> entityType, World world) {
|
protected MixinArmorStandEntity(EntityType<? extends LivingEntity> entityType, Level world) {
|
||||||
super(entityType, world);
|
super(entityType, world);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,23 +40,23 @@ public abstract class MixinArmorStandEntity extends LivingEntity implements SitE
|
|||||||
@Override
|
@Override
|
||||||
protected void removePassenger(Entity passenger) {
|
protected void removePassenger(Entity passenger) {
|
||||||
if (this.isSitEntity()) {
|
if (this.isSitEntity()) {
|
||||||
this.setPosition(this.getX(), this.getY() + 0.16, this.getZ());
|
this.setPos(this.getX(), this.getY() + 0.16, this.getZ());
|
||||||
this.kill();
|
this.kill((net.minecraft.server.level.ServerLevel) this.level());
|
||||||
}
|
}
|
||||||
super.removePassenger(passenger);
|
super.removePassenger(passenger);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject(method = "writeCustomDataToNbt", at = @At(value = "RETURN"))
|
@Inject(method = "addAdditionalSaveData", at = @At(value = "RETURN"))
|
||||||
private void postWriteCustomDataToNbt(NbtCompound nbt, CallbackInfo ci) {
|
private void postWriteCustomDataToNbt(ValueOutput nbt, CallbackInfo ci) {
|
||||||
if (this.sitEntity) {
|
if (this.sitEntity) {
|
||||||
nbt.putBoolean("SitEntity", true);
|
nbt.putBoolean("SitEntity", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject(method = "readCustomDataFromNbt", at = @At(value = "RETURN"))
|
@Inject(method = "readAdditionalSaveData", at = @At(value = "RETURN"))
|
||||||
private void postReadCustomDataFromNbt(NbtCompound nbt, CallbackInfo ci) {
|
private void postReadCustomDataFromNbt(ValueInput nbt, CallbackInfo ci) {
|
||||||
if (nbt.contains("SitEntity", NbtElement.BYTE_TYPE)) {
|
if (nbt.getBooleanOr("SitEntity", false)) {
|
||||||
this.sitEntity = nbt.getBoolean("SitEntity");
|
this.sitEntity = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+4
-4
@@ -1,14 +1,14 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.entity;
|
package com.github.zly2006.carpetslsaddition.mixin.entity;
|
||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import net.minecraft.entity.Leashable;
|
import net.minecraft.world.entity.Leashable;
|
||||||
import net.minecraft.entity.vehicle.BoatEntity;
|
import net.minecraft.world.entity.vehicle.boat.AbstractBoat;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
@Mixin(BoatEntity.class)
|
@Mixin(AbstractBoat.class)
|
||||||
public abstract class MixinBoatEntity implements Leashable {
|
public abstract class MixinBoatEntity implements Leashable {
|
||||||
@Inject(method = "tick", at = @At("HEAD"))
|
@Inject(method = "tick", at = @At("HEAD"))
|
||||||
private void onTick(CallbackInfo ci) {
|
private void onTick(CallbackInfo ci) {
|
||||||
@@ -19,6 +19,6 @@ public abstract class MixinBoatEntity implements Leashable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.detachLeash();
|
this.dropLeash();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+49
-49
@@ -3,20 +3,19 @@ package com.github.zly2006.carpetslsaddition.mixin.entity;
|
|||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import com.github.zly2006.carpetslsaddition.mixin.block.BlockPatternTestTransformInvoker;
|
import com.github.zly2006.carpetslsaddition.mixin.block.BlockPatternTestTransformInvoker;
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
import net.minecraft.block.entity.BlockEntity;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.block.entity.EndGatewayBlockEntity;
|
import net.minecraft.core.Direction;
|
||||||
import net.minecraft.block.entity.EndPortalBlockEntity;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.block.pattern.BlockPattern;
|
import net.minecraft.world.entity.boss.enderdragon.EndCrystal;
|
||||||
import net.minecraft.block.pattern.CachedBlockPosition;
|
import net.minecraft.world.level.LevelReader;
|
||||||
import net.minecraft.entity.boss.dragon.EnderDragonFight;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
import net.minecraft.entity.decoration.EndCrystalEntity;
|
import net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity;
|
||||||
import net.minecraft.server.world.ServerWorld;
|
import net.minecraft.world.level.block.entity.TheEndPortalBlockEntity;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.world.level.block.state.pattern.BlockInWorld;
|
||||||
import net.minecraft.util.math.Direction;
|
import net.minecraft.world.level.block.state.pattern.BlockPattern;
|
||||||
import net.minecraft.world.Heightmap;
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
import net.minecraft.world.WorldView;
|
import net.minecraft.world.level.levelgen.Heightmap;
|
||||||
import net.minecraft.world.chunk.WorldChunk;
|
import net.minecraft.world.level.dimension.end.EnderDragonFight;
|
||||||
import net.minecraft.world.gen.feature.EndPortalFeature;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.spongepowered.asm.mixin.*;
|
import org.spongepowered.asm.mixin.*;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
@@ -25,20 +24,21 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Mixin(EnderDragonFight.class)
|
@Mixin(net.minecraft.world.level.dimension.end.EnderDragonFight.class)
|
||||||
public class MixinEnderDragonFight {
|
public class MixinEnderDragonFight {
|
||||||
@Shadow
|
@Shadow
|
||||||
@Final
|
@Final
|
||||||
private ServerWorld world;
|
private ServerLevel level;
|
||||||
|
|
||||||
@Shadow
|
@Shadow
|
||||||
@Final
|
@Final
|
||||||
private BlockPattern endPortalPattern;
|
private BlockPattern exitPortalPattern;
|
||||||
|
|
||||||
@Shadow
|
@Shadow
|
||||||
private BlockPos exitPortalLocation;
|
private BlockPos exitPortalLocation;
|
||||||
|
|
||||||
@Shadow
|
@Shadow
|
||||||
private boolean doLegacyCheck;
|
private BlockPos origin;
|
||||||
|
|
||||||
@Unique
|
@Unique
|
||||||
private int defaultChunkX = -8;
|
private int defaultChunkX = -8;
|
||||||
@@ -52,21 +52,21 @@ public class MixinEnderDragonFight {
|
|||||||
* @reason Optimize the search process of the end portal
|
* @reason Optimize the search process of the end portal
|
||||||
*/
|
*/
|
||||||
@Overwrite
|
@Overwrite
|
||||||
private @Nullable BlockPattern.Result findEndPortal() {
|
private @Nullable BlockPattern.BlockPatternMatch findExitPortal() {
|
||||||
int i,j;
|
int i, j;
|
||||||
if(!SLSCarpetSettings.optimizedOnDragonRespawn) {
|
if (!SLSCarpetSettings.optimizedOnDragonRespawn) {
|
||||||
defaultChunkX = -8;
|
defaultChunkX = -8;
|
||||||
defaultChunkZ = -8;
|
defaultChunkZ = -8;
|
||||||
}
|
}
|
||||||
for(i = defaultChunkX; i <= 8; ++i) {
|
for (i = defaultChunkX; i <= 8; ++i) {
|
||||||
for(j = defaultChunkZ; j <= 8; ++j) {
|
for (j = defaultChunkZ; j <= 8; ++j) {
|
||||||
WorldChunk worldChunk = this.world.getChunk(i, j);
|
LevelChunk worldChunk = this.level.getChunk(i, j);
|
||||||
for(BlockEntity blockEntity : worldChunk.getBlockEntities().values()) {
|
for (BlockEntity blockEntity : worldChunk.getBlockEntities().values()) {
|
||||||
if(SLSCarpetSettings.optimizedOnDragonRespawn && blockEntity instanceof EndGatewayBlockEntity) continue;
|
if (SLSCarpetSettings.optimizedOnDragonRespawn && blockEntity instanceof TheEndGatewayBlockEntity) continue;
|
||||||
if (blockEntity instanceof EndPortalBlockEntity) {
|
if (blockEntity instanceof TheEndPortalBlockEntity) {
|
||||||
BlockPattern.Result result = this.endPortalPattern.searchAround(this.world, blockEntity.getPos());
|
BlockPattern.BlockPatternMatch result = this.exitPortalPattern.find(this.level, blockEntity.getBlockPos());
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
BlockPos blockPos = result.translate(3, 3, 3).getBlockPos();
|
BlockPos blockPos = result.getFrontTopLeft().offset(3, 3, 3);
|
||||||
if (this.exitPortalLocation == null) {
|
if (this.exitPortalLocation == null) {
|
||||||
this.exitPortalLocation = blockPos;
|
this.exitPortalLocation = blockPos;
|
||||||
}
|
}
|
||||||
@@ -78,25 +78,25 @@ public class MixinEnderDragonFight {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(this.doLegacyCheck || this.exitPortalLocation == null){
|
if (this.exitPortalLocation == null) {
|
||||||
if(SLSCarpetSettings.optimizedOnDragonRespawn && defaultOriginY != -1) {
|
if (SLSCarpetSettings.optimizedOnDragonRespawn && defaultOriginY != -1) {
|
||||||
i = defaultOriginY;
|
i = defaultOriginY;
|
||||||
}
|
} else {
|
||||||
else {
|
i = this.level.getHeight(Heightmap.Types.MOTION_BLOCKING, EnderDragonFight.getPodiumLocation(this.origin).getX(), EnderDragonFight.getPodiumLocation(this.origin).getZ());
|
||||||
i = this.world.getTopPosition(Heightmap.Type.MOTION_BLOCKING, EndPortalFeature.offsetOrigin(BlockPos.ORIGIN)).getY();
|
|
||||||
}
|
}
|
||||||
boolean notFirstSearch = false;
|
boolean notFirstSearch = false;
|
||||||
for(j = i; j >= 0; --j) {
|
for (j = i; j >= 0; --j) {
|
||||||
BlockPattern.Result result2;
|
BlockPattern.BlockPatternMatch result2;
|
||||||
if(SLSCarpetSettings.optimizedOnDragonRespawn && notFirstSearch) {
|
BlockPos podiumPos = EnderDragonFight.getPodiumLocation(this.origin);
|
||||||
result2 = partialSearchAround(this.endPortalPattern, this.world, new BlockPos(EndPortalFeature.offsetOrigin(BlockPos.ORIGIN).getX(), j, EndPortalFeature.offsetOrigin(BlockPos.ORIGIN).getZ()));
|
BlockPos searchPos = new BlockPos(podiumPos.getX(), j, podiumPos.getZ());
|
||||||
}
|
if (SLSCarpetSettings.optimizedOnDragonRespawn && notFirstSearch) {
|
||||||
else{
|
result2 = partialSearchAround(this.exitPortalPattern, this.level, searchPos);
|
||||||
result2 = this.endPortalPattern.searchAround(this.world, new BlockPos(EndPortalFeature.offsetOrigin(BlockPos.ORIGIN).getX(), j, EndPortalFeature.offsetOrigin(BlockPos.ORIGIN).getZ()));
|
} else {
|
||||||
|
result2 = this.exitPortalPattern.find(this.level, searchPos);
|
||||||
}
|
}
|
||||||
if (result2 != null) {
|
if (result2 != null) {
|
||||||
if (this.exitPortalLocation == null) {
|
if (this.exitPortalLocation == null) {
|
||||||
this.exitPortalLocation = result2.translate(3, 3, 3).getBlockPos();
|
this.exitPortalLocation = result2.getFrontTopLeft().offset(3, 3, 3);
|
||||||
}
|
}
|
||||||
defaultOriginY = j;
|
defaultOriginY = j;
|
||||||
return result2;
|
return result2;
|
||||||
@@ -108,22 +108,22 @@ public class MixinEnderDragonFight {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject(method = "respawnDragon(Ljava/util/List;)V", at = @At("HEAD"))
|
@Inject(method = "respawnDragon", at = @At("HEAD"))
|
||||||
private void resetCache(List<EndCrystalEntity> crystals, CallbackInfo ci) {
|
private void resetCache(List<EndCrystal> crystals, CallbackInfo ci) {
|
||||||
this.defaultChunkX = -8;
|
this.defaultChunkX = -8;
|
||||||
this.defaultChunkZ = -8;
|
this.defaultChunkZ = -8;
|
||||||
this.defaultOriginY = -1;
|
this.defaultOriginY = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Unique
|
@Unique
|
||||||
private BlockPattern.Result partialSearchAround(BlockPattern pattern, WorldView world, BlockPos pos) {
|
private BlockPattern.BlockPatternMatch partialSearchAround(BlockPattern pattern, LevelReader world, BlockPos pos) {
|
||||||
LoadingCache<BlockPos, CachedBlockPosition> loadingCache = BlockPattern.makeCache(world, false);
|
LoadingCache<BlockPos, BlockInWorld> loadingCache = BlockPattern.createLevelCache(world, false);
|
||||||
int i = Math.max(Math.max(pattern.getWidth(), pattern.getHeight()), pattern.getDepth());
|
int i = Math.max(Math.max(pattern.getWidth(), pattern.getHeight()), pattern.getDepth());
|
||||||
for (BlockPos blockPos : BlockPos.iterate(pos, pos.add(i - 1, 0, i - 1))) {
|
for (BlockPos blockPos : BlockPos.betweenClosed(pos, pos.offset(i - 1, 0, i - 1))) {
|
||||||
for (Direction direction : Direction.values()) {
|
for (Direction direction : Direction.values()) {
|
||||||
for (Direction direction2 : Direction.values()) {
|
for (Direction direction2 : Direction.values()) {
|
||||||
BlockPattern.Result result;
|
BlockPattern.BlockPatternMatch result;
|
||||||
if (direction2 == direction || direction2 == direction.getOpposite() || (result = ((BlockPatternTestTransformInvoker)pattern).invokeTestTransform(blockPos, direction, direction2, loadingCache)) == null) continue;
|
if (direction2 == direction || direction2 == direction.getOpposite() || (result = ((BlockPatternTestTransformInvoker) pattern).invokeTestTransform(blockPos, direction, direction2, loadingCache)) == null) continue;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-9
@@ -1,24 +1,23 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.entity;
|
package com.github.zly2006.carpetslsaddition.mixin.entity;
|
||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import net.minecraft.block.Block;
|
import net.minecraft.tags.TagKey;
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.world.level.block.Block;
|
||||||
import net.minecraft.block.MushroomPlantBlock;
|
import net.minecraft.world.level.block.MushroomBlock;
|
||||||
import net.minecraft.entity.mob.EndermanEntity;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.registry.tag.TagKey;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
@Mixin(EndermanEntity.PickUpBlockGoal.class)
|
@Mixin(targets = "net.minecraft.world.entity.monster.EnderMan$EndermanTakeBlockGoal")
|
||||||
public class MixinEndermanGoal {
|
public class MixinEndermanGoal {
|
||||||
@Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/BlockState;isIn(Lnet/minecraft/registry/tag/TagKey;)Z"))
|
@Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockState;is(Lnet/minecraft/tags/TagKey;)Z"))
|
||||||
public boolean isHoldable(BlockState blockState, TagKey<Block> tagKey) {
|
public boolean isHoldable(BlockState blockState, TagKey<Block> tagKey) {
|
||||||
if (!SLSCarpetSettings.endermanCanPickUpMushroom) {
|
if (!SLSCarpetSettings.endermanCanPickUpMushroom) {
|
||||||
return blockState.isIn(tagKey) && !(blockState.getBlock() instanceof MushroomPlantBlock);
|
return blockState.is(tagKey) && !(blockState.getBlock() instanceof MushroomBlock);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return blockState.isIn(tagKey);
|
return blockState.is(tagKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+28
-32
@@ -1,20 +1,20 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.entity;
|
package com.github.zly2006.carpetslsaddition.mixin.entity;
|
||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.entity.Entity;
|
import net.minecraft.sounds.SoundEvents;
|
||||||
import net.minecraft.entity.EntityType;
|
import net.minecraft.sounds.SoundSource;
|
||||||
import net.minecraft.entity.FallingBlockEntity;
|
import net.minecraft.tags.BlockTags;
|
||||||
import net.minecraft.entity.ItemEntity;
|
import net.minecraft.util.Mth;
|
||||||
import net.minecraft.entity.damage.DamageSource;
|
import net.minecraft.world.entity.Entity;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.world.entity.EntityType;
|
||||||
import net.minecraft.item.Items;
|
import net.minecraft.world.entity.item.FallingBlockEntity;
|
||||||
import net.minecraft.registry.tag.BlockTags;
|
import net.minecraft.world.entity.item.ItemEntity;
|
||||||
import net.minecraft.sound.SoundCategory;
|
import net.minecraft.world.damagesource.DamageSource;
|
||||||
import net.minecraft.sound.SoundEvents;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.world.item.Items;
|
||||||
import net.minecraft.util.math.MathHelper;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
@@ -26,32 +26,31 @@ import java.util.function.Predicate;
|
|||||||
@Mixin(FallingBlockEntity.class)
|
@Mixin(FallingBlockEntity.class)
|
||||||
public abstract class MixinFallingBlockEntity extends Entity {
|
public abstract class MixinFallingBlockEntity extends Entity {
|
||||||
|
|
||||||
@Shadow private BlockState block;
|
@Shadow private BlockState blockState;
|
||||||
@Shadow private boolean destroyedOnLanding;
|
|
||||||
|
|
||||||
@Shadow public abstract BlockPos getFallingBlockPos();
|
@Shadow public abstract BlockPos getStartPos();
|
||||||
|
|
||||||
public MixinFallingBlockEntity(EntityType<?> type, World world) {
|
public MixinFallingBlockEntity(EntityType<?> type, Level world) {
|
||||||
super(type, world);
|
super(type, world);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject(method = "handleFallDamage", at = @At(value = "TAIL"))
|
@Inject(method = "causeFallDamage", at = @At(value = "TAIL"))
|
||||||
private void handleFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource, CallbackInfoReturnable<Boolean> cir) {
|
private void causeFallDamage(double fallDistance, float damageMultiplier, DamageSource damageSource, CallbackInfoReturnable<Boolean> cir) {
|
||||||
if (!SLSCarpetSettings.obtainableReinforcedDeepSlate || !this.block.isIn(BlockTags.ANVIL)) {
|
if (!SLSCarpetSettings.obtainableReinforcedDeepSlate || !this.blockState.is(BlockTags.ANVIL)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Predicate<Entity> deepSlatePredicated = (entity) -> {
|
Predicate<Entity> deepSlatePredicated = (entity) -> {
|
||||||
if (entity instanceof ItemEntity itemEntity) {
|
if (entity instanceof ItemEntity itemEntity) {
|
||||||
var itemStack = itemEntity.getStack();
|
var itemStack = itemEntity.getItem();
|
||||||
return itemStack.isOf(Items.DEEPSLATE) && itemStack.getCount() >= 64;
|
return itemStack.is(Items.DEEPSLATE) && itemStack.getCount() >= 64;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
var world = this.getWorld();
|
var world = this.level();
|
||||||
var itemEntitiesList = world.getOtherEntities(this, this.getBoundingBox(), deepSlatePredicated);
|
var itemEntitiesList = world.getEntities(this, this.getBoundingBox(), deepSlatePredicated);
|
||||||
if (itemEntitiesList.size() < 9) {
|
if (itemEntitiesList.size() < 9) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -62,16 +61,13 @@ public abstract class MixinFallingBlockEntity extends Entity {
|
|||||||
itemEntitiesList.get(i).discard();
|
itemEntitiesList.get(i).discard();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.destroyedOnLanding = true;
|
world.playSound(null, this.blockPosition(), SoundEvents.ANVIL_USE, SoundSource.BLOCKS, 1.0F, 1.0F);
|
||||||
|
|
||||||
|
|
||||||
world.playSoundAtBlockCenter(this.getBlockPos(), SoundEvents.BLOCK_ANVIL_USE, SoundCategory.BLOCKS, 1.0F, 1.0F, false);
|
|
||||||
|
|
||||||
var coreEntity = new ItemEntity(world, entity.getX(), entity.getY(), entity.getZ(), new ItemStack(Items.REINFORCED_DEEPSLATE, 1));
|
var coreEntity = new ItemEntity(world, entity.getX(), entity.getY(), entity.getZ(), new ItemStack(Items.REINFORCED_DEEPSLATE, 1));
|
||||||
coreEntity.setPickupDelay(40);
|
coreEntity.setPickUpDelay(40);
|
||||||
float f = this.random.nextFloat() * 0.5F;
|
float f = this.random.nextFloat() * 0.5F;
|
||||||
float g = this.random.nextFloat() * (float) (Math.PI * 2);
|
float g = this.random.nextFloat() * (float) (Math.PI * 2);
|
||||||
coreEntity.setVelocity(-MathHelper.sin(g) * f, 0.2F, MathHelper.cos(g) * f);
|
coreEntity.setDeltaMovement(-Mth.sin(g) * f, 0.2F, Mth.cos(g) * f);
|
||||||
world.spawnEntity(coreEntity);
|
world.addFreshEntity(coreEntity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,15 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.entity;
|
package com.github.zly2006.carpetslsaddition.mixin.entity;
|
||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import net.minecraft.entity.Leashable;
|
import net.minecraft.world.entity.Leashable;
|
||||||
import net.minecraft.entity.mob.MobEntity;
|
import net.minecraft.world.entity.Mob;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
@Mixin(MobEntity.class)
|
@Mixin(Mob.class)
|
||||||
public abstract class MixinMobEntity implements Leashable {
|
public abstract class MixinMobEntity implements Leashable {
|
||||||
@Shadow public abstract void detachLeash();
|
|
||||||
|
|
||||||
@Inject(method = "tick", at = @At("HEAD"))
|
@Inject(method = "tick", at = @At("HEAD"))
|
||||||
private void onTick(CallbackInfo ci) {
|
private void onTick(CallbackInfo ci) {
|
||||||
if (!SLSCarpetSettings.spectatorCannotUseLeash ||
|
if (!SLSCarpetSettings.spectatorCannotUseLeash ||
|
||||||
@@ -22,6 +19,6 @@ public abstract class MixinMobEntity implements Leashable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.detachLeash();
|
this.dropLeash();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+6
-6
@@ -1,26 +1,26 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.entity;
|
package com.github.zly2006.carpetslsaddition.mixin.entity;
|
||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.util.access.PlayerAccessor;
|
import com.github.zly2006.carpetslsaddition.util.access.PlayerAccessor;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.world.entity.player.Player;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Unique;
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
@Mixin(PlayerEntity.class)
|
@Mixin(Player.class)
|
||||||
public abstract class MixinPlayerEntity implements PlayerAccessor {
|
public abstract class MixinPlayerEntity implements PlayerAccessor {
|
||||||
@Unique
|
@Unique
|
||||||
private Text displayName = null;
|
private Component displayName = null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void carpet_SLS_Addition$setDisplayName(Text name) {
|
public void carpet_SLS_Addition$setDisplayName(Component name) {
|
||||||
this.displayName = name;
|
this.displayName = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject(method = "getDisplayName", at = @At("HEAD"), cancellable = true)
|
@Inject(method = "getDisplayName", at = @At("HEAD"), cancellable = true)
|
||||||
public void injectGetDisplayName(CallbackInfoReturnable<Text> cir) {
|
public void injectGetDisplayName(CallbackInfoReturnable<Component> cir) {
|
||||||
if (this.displayName != null) {
|
if (this.displayName != null) {
|
||||||
cir.setReturnValue(displayName);
|
cir.setReturnValue(displayName);
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-25
@@ -3,38 +3,35 @@ package com.github.zly2006.carpetslsaddition.mixin.entity;
|
|||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import com.github.zly2006.carpetslsaddition.util.SitEntity;
|
import com.github.zly2006.carpetslsaddition.util.SitEntity;
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
import net.minecraft.entity.decoration.ArmorStandEntity;
|
import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraft.network.packet.s2c.play.EntityTrackerUpdateS2CPacket;
|
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||||
import net.minecraft.server.network.ServerPlayNetworkHandler;
|
import net.minecraft.world.entity.decoration.ArmorStand;
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
import net.minecraft.world.entity.player.Player;
|
||||||
import net.minecraft.util.math.BlockPos;
|
|
||||||
import net.minecraft.world.World;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
import org.spongepowered.asm.mixin.Unique;
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
|
||||||
@Mixin(ServerPlayerEntity.class)
|
@Mixin(ServerPlayer.class)
|
||||||
public abstract class MixinServerPlayerEntity extends PlayerEntity {
|
public abstract class MixinServerPlayerEntity extends Player {
|
||||||
@Shadow
|
@Shadow
|
||||||
public ServerPlayNetworkHandler networkHandler;
|
public ServerGamePacketListenerImpl connection;
|
||||||
|
|
||||||
@Shadow public abstract boolean isSpectator();
|
|
||||||
|
|
||||||
@Unique
|
@Unique
|
||||||
private int sneakTimes = 0;
|
private int sneakTimes = 0;
|
||||||
@Unique
|
@Unique
|
||||||
private long lastSneakTime = 0;
|
private long lastSneakTime = 0;
|
||||||
|
|
||||||
public MixinServerPlayerEntity(World world, BlockPos pos, float yaw, GameProfile gameProfile) {
|
public MixinServerPlayerEntity(net.minecraft.world.level.Level world, net.minecraft.core.BlockPos pos, float yaw, GameProfile gameProfile) {
|
||||||
super(world, pos, yaw, gameProfile);
|
super(world, gameProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSneaking(boolean sneaking){
|
public void setShiftKeyDown(boolean sneaking){
|
||||||
if (!SLSCarpetSettings.playerSit || (sneaking && this.isSneaking())) {
|
if (!SLSCarpetSettings.playerSit || (sneaking && this.isShiftKeyDown())) {
|
||||||
super.setSneaking(sneaking);
|
super.setShiftKeyDown(sneaking);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,14 +40,14 @@ public abstract class MixinServerPlayerEntity extends PlayerEntity {
|
|||||||
if (nowTime - lastSneakTime < 400 && sneakTimes == 0) {
|
if (nowTime - lastSneakTime < 400 && sneakTimes == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
super.setSneaking(true);
|
super.setShiftKeyDown(true);
|
||||||
if (this.isOnGround() && nowTime - lastSneakTime < 400) {
|
if (this.onGround() && nowTime - lastSneakTime < 400) {
|
||||||
sneakTimes += 1;
|
sneakTimes += 1;
|
||||||
if (sneakTimes == 3) {
|
if (sneakTimes == 3) {
|
||||||
ArmorStandEntity armorStandEntity = new ArmorStandEntity(getWorld(), this.getX(), this.getY() - 0.16, this.getZ());
|
ArmorStand armorStandEntity = new ArmorStand(this.level(), this.getX(), this.getY() - 0.16, this.getZ());
|
||||||
((SitEntity) armorStandEntity).setSitEntity(true);
|
((SitEntity) armorStandEntity).setSitEntity(true);
|
||||||
getWorld().spawnEntity(armorStandEntity);
|
this.level().addFreshEntity(armorStandEntity);
|
||||||
this.setSneaking(false);
|
this.setShiftKeyDown(false);
|
||||||
this.startRiding(armorStandEntity);
|
this.startRiding(armorStandEntity);
|
||||||
sneakTimes = 0;
|
sneakTimes = 0;
|
||||||
}
|
}
|
||||||
@@ -59,11 +56,13 @@ public abstract class MixinServerPlayerEntity extends PlayerEntity {
|
|||||||
}
|
}
|
||||||
lastSneakTime = nowTime;
|
lastSneakTime = nowTime;
|
||||||
} else {
|
} else {
|
||||||
super.setSneaking(false);
|
super.setShiftKeyDown(false);
|
||||||
// 同步潜行状态到客户端
|
// 同步潜行状态到客户端
|
||||||
// 如果不同步的话客户端会认为仍在潜行,从而碰撞箱的高度会计算错误
|
if (sneakTimes == 0 && this.connection != null) {
|
||||||
if (sneakTimes == 0 && this.networkHandler != null) {
|
java.util.List<net.minecraft.network.syncher.SynchedEntityData.DataValue<?>> dirty = this.getEntityData().packDirty();
|
||||||
this.networkHandler.sendPacket(new EntityTrackerUpdateS2CPacket(this.getId(), this.getDataTracker().getChangedEntries()));
|
if (dirty != null) {
|
||||||
|
this.connection.send(new ClientboundSetEntityDataPacket(this.getId(), dirty));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.mixin.item;
|
||||||
|
|
||||||
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
|
import net.minecraft.world.inventory.Slot;
|
||||||
|
import net.minecraft.world.item.BoatItem;
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.MinecartItem;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 载具堆叠(仅玩家背包):矿车/船在【玩家背包】内可堆叠到 64,其他容器不堆叠。
|
||||||
|
*
|
||||||
|
* <p>原理:{@code Slot.getMaxStackSize(ItemStack)} = min(容器上限, 物品上限)。
|
||||||
|
* 载具的物品上限全局为 1(默认不叠)。本 mixin 在【容器是玩家背包 Inventory】且物品是载具时,
|
||||||
|
* 直接返回 64,绕过 min 限制。
|
||||||
|
*
|
||||||
|
* <p>这样只有玩家背包(物品栏 36 格)内能叠,箱子/漏斗/潜影盒等其他容器不受影响。
|
||||||
|
*/
|
||||||
|
@Mixin(Slot.class)
|
||||||
|
public abstract class SlotMixin {
|
||||||
|
|
||||||
|
@Inject(method = "getMaxStackSize(Lnet/minecraft/world/item/ItemStack;)I", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void stackableVehiclesInPlayerInventory(ItemStack stack, CallbackInfoReturnable<Integer> cir) {
|
||||||
|
if (!SLSCarpetSettings.stackableVehicles) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stack == null || stack.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Slot self = (Slot) (Object) this;
|
||||||
|
// 仅玩家背包
|
||||||
|
if (!(self.container instanceof Inventory)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Item item = stack.getItem();
|
||||||
|
if (item instanceof MinecartItem || item instanceof BoatItem) {
|
||||||
|
cir.setReturnValue(64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
-13
@@ -1,27 +1,27 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.player;
|
package com.github.zly2006.carpetslsaddition.mixin.player;
|
||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.world.Container;
|
||||||
import net.minecraft.entity.player.PlayerInventory;
|
import net.minecraft.world.Nameable;
|
||||||
import net.minecraft.inventory.Inventory;
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
import net.minecraft.util.Nameable;
|
import net.minecraft.world.entity.player.Player;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
@Mixin(PlayerInventory.class)
|
@Mixin(Inventory.class)
|
||||||
public abstract class MixinPlayerInventory implements Inventory, Nameable {
|
public abstract class MixinPlayerInventory implements Container, Nameable {
|
||||||
|
// creativeNoInfinitePickup:创造模式下满背包不能拾取。
|
||||||
|
// 新版 Inventory.add 在满背包 + hasInfiniteMaterials 时把数量清 0 并返回 true。
|
||||||
|
// 规则开启时改为 false,避免"假拾取"。
|
||||||
@Redirect(
|
@Redirect(
|
||||||
method = "insertStack(ILnet/minecraft/item/ItemStack;)Z",
|
method = "add(ILnet/minecraft/world/item/ItemStack;)Z",
|
||||||
at = @At(
|
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;hasInfiniteMaterials()Z")
|
||||||
value = "INVOKE",
|
|
||||||
target = "Lnet/minecraft/entity/player/PlayerEntity;isInCreativeMode()Z"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
private boolean insertStack(PlayerEntity instance) {
|
private boolean insertStack(Player instance) {
|
||||||
if (SLSCarpetSettings.creativeNoInfinitePickup) {
|
if (SLSCarpetSettings.creativeNoInfinitePickup) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return instance.isInCreativeMode();
|
return instance.hasInfiniteMaterials();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+11
-10
@@ -1,26 +1,27 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.screen;
|
package com.github.zly2006.carpetslsaddition.mixin.screen;
|
||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import net.minecraft.entity.player.PlayerAbilities;
|
import net.minecraft.world.entity.player.Abilities;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.world.entity.player.Player;
|
||||||
import net.minecraft.screen.AnvilScreenHandler;
|
import net.minecraft.world.inventory.AnvilMenu;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
@Mixin(AnvilScreenHandler.class)
|
@Mixin(AnvilMenu.class)
|
||||||
public class MixinAnvilScreenHandler {
|
public class MixinAnvilScreenHandler {
|
||||||
@Redirect(
|
@Redirect(
|
||||||
method = "updateResult",
|
method = "createResult",
|
||||||
at = @At(
|
at = @At(
|
||||||
value = "INVOKE",
|
value = "INVOKE",
|
||||||
target = "Lnet/minecraft/entity/player/PlayerEntity;getAbilities()Lnet/minecraft/entity/player/PlayerAbilities;"
|
target = "Lnet/minecraft/world/entity/player/Player;getAbilities()Lnet/minecraft/world/entity/player/Abilities;"
|
||||||
)
|
),
|
||||||
|
require = 0
|
||||||
)
|
)
|
||||||
private PlayerAbilities updateResult(PlayerEntity instance) {
|
private Abilities updateResult(Player instance) {
|
||||||
if (SLSCarpetSettings.creativeObeyEnchantmentRule) {
|
if (SLSCarpetSettings.creativeObeyEnchantmentRule) {
|
||||||
var ability = new PlayerAbilities();
|
var ability = new Abilities();
|
||||||
ability.creativeMode = false;
|
ability.instabuild = false;
|
||||||
return ability;
|
return ability;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-3
@@ -1,7 +1,7 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.shulker;
|
package com.github.zly2006.carpetslsaddition.mixin.shulker;
|
||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.util.ShulkerBoxItemUtil;
|
import com.github.zly2006.carpetslsaddition.util.ShulkerBoxItemUtil;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
@@ -10,14 +10,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||||||
@Mixin(ItemStack.class)
|
@Mixin(ItemStack.class)
|
||||||
public class MixinItemStack {
|
public class MixinItemStack {
|
||||||
@Inject(
|
@Inject(
|
||||||
method = "capCount",
|
method = "limitSize",
|
||||||
at = @At("HEAD"),
|
at = @At("HEAD"),
|
||||||
cancellable = true
|
cancellable = true
|
||||||
)
|
)
|
||||||
private void capCount(int maxCount, CallbackInfo ci) {
|
private void capCount(int maxCount, CallbackInfo ci) {
|
||||||
if (ShulkerBoxItemUtil.isEmptyShulkerBoxItem((ItemStack) (Object) this)) {
|
if (ShulkerBoxItemUtil.isEmptyShulkerBoxItem((ItemStack) (Object) this)) {
|
||||||
ci.cancel();
|
ci.cancel();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+16
-39
@@ -2,48 +2,25 @@ package com.github.zly2006.carpetslsaddition.mixin.shulker;
|
|||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import com.github.zly2006.carpetslsaddition.util.ShulkerBoxItemUtil;
|
import com.github.zly2006.carpetslsaddition.util.ShulkerBoxItemUtil;
|
||||||
import net.minecraft.entity.player.PlayerInventory;
|
import net.minecraft.world.item.ItemInstance;
|
||||||
import net.minecraft.inventory.Inventory;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.item.ItemStack;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
@Mixin(PlayerInventory.class)
|
/**
|
||||||
public abstract class MixinPlayerInventory implements Inventory {
|
* 空潜影盒可堆叠(上限 64)。
|
||||||
@Redirect(
|
* 26.1 起堆叠上限统一由 ItemInstance.getMaxStackSize 决定,参考 Carpet 实现。
|
||||||
method = "canStackAddMore",
|
*/
|
||||||
at = @At(
|
@Mixin(ItemInstance.class)
|
||||||
value = "INVOKE",
|
public interface MixinPlayerInventory {
|
||||||
target = "Lnet/minecraft/item/ItemStack;isStackable()Z"
|
@Inject(method = "getMaxStackSize", at = @At("HEAD"), cancellable = true)
|
||||||
)
|
private void getMaxStackSize(CallbackInfoReturnable<Integer> cir) {
|
||||||
)
|
if (SLSCarpetSettings.emptyShulkerBoxStack
|
||||||
private boolean isStackable(ItemStack instance) {
|
&& ((ItemInstance) (Object) this) instanceof ItemStack itemStack
|
||||||
if (SLSCarpetSettings.emptyShulkerBoxStack && ShulkerBoxItemUtil.isEmptyShulkerBoxItem(instance)) {
|
&& ShulkerBoxItemUtil.isEmptyShulkerBoxItem(itemStack)) {
|
||||||
return true;
|
cir.setReturnValue(ShulkerBoxItemUtil.SHULKERBOX_MAX_STACK_AMOUNT);
|
||||||
}
|
}
|
||||||
return instance.isStackable();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Redirect(
|
|
||||||
method = "offer",
|
|
||||||
at = @At(
|
|
||||||
value = "INVOKE",
|
|
||||||
target = "Lnet/minecraft/item/ItemStack;getMaxCount()I"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
private int redirectMaxCount(ItemStack stack) {
|
|
||||||
if (SLSCarpetSettings.emptyShulkerBoxStack && ShulkerBoxItemUtil.isEmptyShulkerBoxItem(stack)) {
|
|
||||||
return ShulkerBoxItemUtil.SHULKERBOX_MAX_STACK_AMOUNT;
|
|
||||||
}
|
|
||||||
return Inventory.super.getMaxCount(stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxCount(ItemStack stack) {
|
|
||||||
if (SLSCarpetSettings.emptyShulkerBoxStack && ShulkerBoxItemUtil.isEmptyShulkerBoxItem(stack)) {
|
|
||||||
return ShulkerBoxItemUtil.SHULKERBOX_MAX_STACK_AMOUNT;
|
|
||||||
}
|
|
||||||
return Inventory.super.getMaxCount(stack);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+10
-8
@@ -1,32 +1,34 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.shulker;
|
package com.github.zly2006.carpetslsaddition.mixin.shulker;
|
||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.util.ShulkerBoxItemUtil;
|
import com.github.zly2006.carpetslsaddition.util.ShulkerBoxItemUtil;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||||
import net.minecraft.screen.ScreenHandler;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
@Mixin(ScreenHandler.class)
|
@Mixin(AbstractContainerMenu.class)
|
||||||
public abstract class MixinScreenHandler {
|
public abstract class MixinScreenHandler {
|
||||||
@Redirect(
|
@Redirect(
|
||||||
method = "*",
|
method = "*",
|
||||||
at = @At(
|
at = @At(
|
||||||
value = "INVOKE",
|
value = "INVOKE",
|
||||||
target = "Lnet/minecraft/item/ItemStack;areItemsAndComponentsEqual(Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ItemStack;)Z"
|
target = "Lnet/minecraft/world/item/ItemStack;isSameItemSameComponents(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemStack;)Z"
|
||||||
)
|
),
|
||||||
|
require = 0
|
||||||
)
|
)
|
||||||
private boolean checkEqual(ItemStack stack, ItemStack otherStack) {
|
private boolean checkEqual(ItemStack stack, ItemStack otherStack) {
|
||||||
if (ShulkerBoxItemUtil.shulkerBoxEqual(stack, otherStack)) return true;
|
if (ShulkerBoxItemUtil.shulkerBoxEqual(stack, otherStack)) return true;
|
||||||
return ItemStack.areItemsAndComponentsEqual(stack, otherStack);
|
return ItemStack.isSameItemSameComponents(stack, otherStack);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Redirect(
|
@Redirect(
|
||||||
method = "*",
|
method = "*",
|
||||||
at = @At(
|
at = @At(
|
||||||
value = "INVOKE",
|
value = "INVOKE",
|
||||||
target = "Lnet/minecraft/item/ItemStack;isStackable()Z"
|
target = "Lnet/minecraft/world/item/ItemStack;isStackable()Z"
|
||||||
)
|
),
|
||||||
|
require = 0
|
||||||
)
|
)
|
||||||
private boolean isStackable(ItemStack stack) {
|
private boolean isStackable(ItemStack stack) {
|
||||||
if (ShulkerBoxItemUtil.isEmptyShulkerBoxItem(stack)) return true;
|
if (ShulkerBoxItemUtil.isEmptyShulkerBoxItem(stack)) return true;
|
||||||
|
|||||||
+8
-7
@@ -2,25 +2,26 @@ package com.github.zly2006.carpetslsaddition.mixin.shulker;
|
|||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import com.github.zly2006.carpetslsaddition.util.ShulkerBoxItemUtil;
|
import com.github.zly2006.carpetslsaddition.util.ShulkerBoxItemUtil;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||||
import net.minecraft.server.network.ServerPlayNetworkHandler;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
@Mixin(ServerPlayNetworkHandler.class)
|
@Mixin(ServerGamePacketListenerImpl.class)
|
||||||
public class MixinServerPlayNetworkHandler {
|
public class MixinServerPlayNetworkHandler {
|
||||||
@Redirect(
|
@Redirect(
|
||||||
method = "onCreativeInventoryAction",
|
method = "handleSetCreativeModeSlot",
|
||||||
at = @At(
|
at = @At(
|
||||||
value = "INVOKE",
|
value = "INVOKE",
|
||||||
target = "Lnet/minecraft/item/ItemStack;getMaxCount()I"
|
target = "Lnet/minecraft/world/item/ItemStack;getMaxStackSize()I"
|
||||||
)
|
),
|
||||||
|
require = 0
|
||||||
)
|
)
|
||||||
private int getMaxCount(ItemStack instance) {
|
private int getMaxCount(ItemStack instance) {
|
||||||
if (SLSCarpetSettings.emptyShulkerBoxStack && ShulkerBoxItemUtil.isEmptyShulkerBoxItem(instance)) {
|
if (SLSCarpetSettings.emptyShulkerBoxStack && ShulkerBoxItemUtil.isEmptyShulkerBoxItem(instance)) {
|
||||||
return ShulkerBoxItemUtil.SHULKERBOX_MAX_STACK_AMOUNT;
|
return ShulkerBoxItemUtil.SHULKERBOX_MAX_STACK_AMOUNT;
|
||||||
}
|
}
|
||||||
return instance.getMaxCount();
|
return instance.getMaxStackSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,8 @@ package com.github.zly2006.carpetslsaddition.mixin.shulker;
|
|||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import com.github.zly2006.carpetslsaddition.util.ShulkerBoxItemUtil;
|
import com.github.zly2006.carpetslsaddition.util.ShulkerBoxItemUtil;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.world.inventory.Slot;
|
||||||
import net.minecraft.screen.slot.Slot;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
@@ -11,10 +11,10 @@ import org.spongepowered.asm.mixin.injection.Redirect;
|
|||||||
@Mixin(Slot.class)
|
@Mixin(Slot.class)
|
||||||
public class MixinSlot {
|
public class MixinSlot {
|
||||||
@Redirect(
|
@Redirect(
|
||||||
method = "getMaxItemCount(Lnet/minecraft/item/ItemStack;)I",
|
method = "getMaxStackSize(Lnet/minecraft/world/item/ItemStack;)I",
|
||||||
at = @At(
|
at = @At(
|
||||||
value = "INVOKE",
|
value = "INVOKE",
|
||||||
target = "Lnet/minecraft/item/ItemStack;getMaxCount()I",
|
target = "Lnet/minecraft/world/item/ItemStack;getMaxStackSize()I",
|
||||||
ordinal = 0
|
ordinal = 0
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -22,7 +22,7 @@ public class MixinSlot {
|
|||||||
if (SLSCarpetSettings.emptyShulkerBoxStack && ShulkerBoxItemUtil.isEmptyShulkerBoxItem(itemStack)) {
|
if (SLSCarpetSettings.emptyShulkerBoxStack && ShulkerBoxItemUtil.isEmptyShulkerBoxItem(itemStack)) {
|
||||||
return ShulkerBoxItemUtil.SHULKERBOX_MAX_STACK_AMOUNT;
|
return ShulkerBoxItemUtil.SHULKERBOX_MAX_STACK_AMOUNT;
|
||||||
} else {
|
} else {
|
||||||
return itemStack.getMaxCount();
|
return itemStack.getMaxStackSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.mixin.turtle;
|
||||||
|
|
||||||
|
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
|
||||||
|
@Mixin(PathNavigation.class)
|
||||||
|
public interface PathNavigationAccessor {
|
||||||
|
@Accessor("maxVisitedNodesMultiplier")
|
||||||
|
float turtleReproduce$getMaxVisitedNodesMultiplier();
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.mixin.turtle;
|
||||||
|
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.entity.animal.turtle.Turtle;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||||
|
|
||||||
|
@Mixin(Turtle.class)
|
||||||
|
public interface TurtleAccessor {
|
||||||
|
@Invoker("setHasEgg")
|
||||||
|
void turtleReproduce$setHasEgg(boolean hasEgg);
|
||||||
|
|
||||||
|
@Invoker("setLayingEgg")
|
||||||
|
void turtleReproduce$setLayingEgg(boolean layingEgg);
|
||||||
|
|
||||||
|
@Accessor("homePos")
|
||||||
|
BlockPos turtleReproduce$getHomePos();
|
||||||
|
|
||||||
|
@Accessor("goingHome")
|
||||||
|
boolean turtleReproduce$isGoingHome();
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.mixin.turtle;
|
||||||
|
|
||||||
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
|
import net.minecraft.world.entity.animal.turtle.Turtle;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(targets = "net.minecraft.world.entity.animal.turtle.Turtle$TurtleGoHomeGoal")
|
||||||
|
public abstract class TurtleGoHomeGoalMixin {
|
||||||
|
@Shadow @Final
|
||||||
|
private Turtle turtle;
|
||||||
|
|
||||||
|
// This goal has no MOVE flag, so priority alone cannot stop it fighting nesting.
|
||||||
|
@Inject(method = {"canUse", "canContinueToUse"}, at = @At("HEAD"), cancellable = true)
|
||||||
|
private void turtleReproduce$stayLocalWhileCarryingEggs(CallbackInfoReturnable<Boolean> cir) {
|
||||||
|
if (SLSCarpetSettings.turtleLocalNesting && this.turtle.hasEgg()) {
|
||||||
|
cir.setReturnValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "stop", at = @At("TAIL"))
|
||||||
|
private void turtleReproduce$discardOldHomeRoute(CallbackInfo ci) {
|
||||||
|
if (SLSCarpetSettings.turtleLocalNesting && this.turtle.hasEgg()) {
|
||||||
|
// The vanilla stop only clears goingHome, leaving its last route active.
|
||||||
|
this.turtle.getNavigation().stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+142
@@ -0,0 +1,142 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.mixin.turtle;
|
||||||
|
|
||||||
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
|
import com.github.zly2006.carpetslsaddition.turtle.NestingSiteFinder;
|
||||||
|
import java.util.List;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.entity.PathfinderMob;
|
||||||
|
import net.minecraft.world.entity.ai.goal.MoveToBlockGoal;
|
||||||
|
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
||||||
|
import net.minecraft.world.entity.animal.turtle.Turtle;
|
||||||
|
import net.minecraft.world.level.LevelReader;
|
||||||
|
import net.minecraft.world.level.pathfinder.Path;
|
||||||
|
import net.minecraft.world.phys.AABB;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(targets = "net.minecraft.world.entity.animal.turtle.Turtle$TurtleLayEggGoal")
|
||||||
|
public abstract class TurtleLayEggGoalMixin extends MoveToBlockGoal {
|
||||||
|
@Unique private static final int PATH_SEARCH_RANGE = 48;
|
||||||
|
@Unique private static final int PATH_ATTEMPTS_PER_CHECK = 16;
|
||||||
|
|
||||||
|
@Shadow @Final
|
||||||
|
private Turtle turtle;
|
||||||
|
@Unique private List<NestingSiteFinder.Candidate> turtleReproduce$candidates = List.of();
|
||||||
|
@Unique private int turtleReproduce$candidateIndex;
|
||||||
|
@Unique private BlockPos turtleReproduce$searchOrigin = BlockPos.ZERO;
|
||||||
|
@Unique private Path turtleReproduce$nestingPath;
|
||||||
|
|
||||||
|
protected TurtleLayEggGoalMixin(PathfinderMob mob, double speed, int range) {
|
||||||
|
super(mob, speed, range);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "canUse", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void turtleReproduce$findLocalShore(CallbackInfoReturnable<Boolean> cir) {
|
||||||
|
if (!SLSCarpetSettings.turtleLocalNesting) return;
|
||||||
|
cir.setReturnValue(this.turtle.hasEgg() && !this.turtle.isBaby() && super.canUse());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "canContinueToUse", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void turtleReproduce$continueAwayFromHome(CallbackInfoReturnable<Boolean> cir) {
|
||||||
|
if (!SLSCarpetSettings.turtleLocalNesting) return;
|
||||||
|
cir.setReturnValue(this.turtle.hasEgg() && super.canContinueToUse());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "isValidTarget", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void turtleReproduce$requireDrySandNearWater(LevelReader level, BlockPos pos,
|
||||||
|
CallbackInfoReturnable<Boolean> cir) {
|
||||||
|
if (!SLSCarpetSettings.turtleLocalNesting) return;
|
||||||
|
cir.setReturnValue(NestingSiteFinder.isValidSite(level, pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean findNearestBlock() {
|
||||||
|
if (!SLSCarpetSettings.turtleLocalNesting) {
|
||||||
|
return super.findNearestBlock();
|
||||||
|
}
|
||||||
|
if (this.turtleReproduce$candidateIndex >= this.turtleReproduce$candidates.size()
|
||||||
|
|| this.turtle.blockPosition().distSqr(this.turtleReproduce$searchOrigin) > 16.0) {
|
||||||
|
this.turtleReproduce$candidates = NestingSiteFinder.findCandidates(this.turtle);
|
||||||
|
this.turtleReproduce$candidateIndex = 0;
|
||||||
|
this.turtleReproduce$searchOrigin = this.turtle.blockPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spread expensive path checks over AI updates when a shore is inaccessible.
|
||||||
|
int attempts = 0;
|
||||||
|
while (this.turtleReproduce$candidateIndex < this.turtleReproduce$candidates.size()
|
||||||
|
&& attempts++ < PATH_ATTEMPTS_PER_CHECK) {
|
||||||
|
BlockPos sand = this.turtleReproduce$candidates.get(this.turtleReproduce$candidateIndex++).sandPos();
|
||||||
|
if (!this.turtle.isWithinHome(sand) || !NestingSiteFinder.isValidSite(this.turtle.level(), sand)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Vec3 destination = Vec3.atBottomCenterOf(sand.above());
|
||||||
|
AABB destinationBounds = this.turtle.getBoundingBox().move(destination.subtract(this.turtle.position()));
|
||||||
|
// Adult turtles are wider than one block. An air block alone does not
|
||||||
|
// guarantee that their body is clear of adjacent water or walls.
|
||||||
|
if (!this.turtle.level().noCollision(this.turtle, destinationBounds)
|
||||||
|
|| this.turtle.level().containsAnyLiquid(destinationBounds.deflate(0.001))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Path path = this.turtleReproduce$createNestingPath(sand.above());
|
||||||
|
if (path != null && path.canReach()) {
|
||||||
|
this.blockPos = sand;
|
||||||
|
this.turtleReproduce$nestingPath = path;
|
||||||
|
this.turtleReproduce$candidates = List.of();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.turtleReproduce$candidateIndex < this.turtleReproduce$candidates.size()) {
|
||||||
|
this.nextStartTick = 1;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private Path turtleReproduce$createNestingPath(BlockPos destination) {
|
||||||
|
PathNavigation navigation = this.turtle.getNavigation();
|
||||||
|
float previousMultiplier = ((PathNavigationAccessor) navigation).turtleReproduce$getMaxVisitedNodesMultiplier();
|
||||||
|
// A larger distance alone leaves vanilla's small node budget unchanged,
|
||||||
|
// incorrectly rejecting shores that require a detour around an obstacle.
|
||||||
|
navigation.setMaxVisitedNodesMultiplier(Math.max(previousMultiplier, 8.0F));
|
||||||
|
try {
|
||||||
|
return navigation.createPath(destination, 0, PATH_SEARCH_RANGE);
|
||||||
|
} finally {
|
||||||
|
navigation.setMaxVisitedNodesMultiplier(previousMultiplier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void moveMobToBlock() {
|
||||||
|
if (!SLSCarpetSettings.turtleLocalNesting) {
|
||||||
|
super.moveMobToBlock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.turtle.getNavigation().moveTo(this.turtleReproduce$nestingPath, this.speedModifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "tick", at = @At("TAIL"))
|
||||||
|
private void turtleReproduce$retryLongerPath(CallbackInfo ci) {
|
||||||
|
if (!SLSCarpetSettings.turtleLocalNesting) return;
|
||||||
|
if (!this.isReachedTarget() && this.tryTicks % 40 == 0 && this.turtle.getNavigation().isDone()) {
|
||||||
|
this.turtleReproduce$nestingPath = this.turtleReproduce$createNestingPath(this.blockPos.above());
|
||||||
|
this.moveMobToBlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
super.stop();
|
||||||
|
if (!SLSCarpetSettings.turtleLocalNesting) return;
|
||||||
|
this.turtle.getNavigation().stop();
|
||||||
|
((TurtleAccessor) this.turtle).turtleReproduce$setLayingEgg(false);
|
||||||
|
this.turtleReproduce$nestingPath = null;
|
||||||
|
this.turtleReproduce$candidates = List.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-5
@@ -2,16 +2,16 @@ package com.github.zly2006.carpetslsaddition.mixin.world;
|
|||||||
|
|
||||||
import carpet.patches.EntityPlayerMPFake;
|
import carpet.patches.EntityPlayerMPFake;
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraft.server.world.SleepManager;
|
import net.minecraft.server.players.SleepStatus;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
@Mixin(SleepManager.class)
|
@Mixin(SleepStatus.class)
|
||||||
public class MixinSleepManager {
|
public class MixinSleepManager {
|
||||||
@Redirect(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayerEntity;isSpectator()Z"))
|
@Redirect(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;isSpectator()Z"))
|
||||||
private boolean isNormalPlayer(ServerPlayerEntity serverPlayerEntity) {
|
private boolean isNormalPlayer(ServerPlayer serverPlayerEntity) {
|
||||||
if (SLSCarpetSettings.fakePlayersNotOccupiedSleepQuota) {
|
if (SLSCarpetSettings.fakePlayersNotOccupiedSleepQuota) {
|
||||||
return serverPlayerEntity.isSpectator() || serverPlayerEntity instanceof EntityPlayerMPFake;
|
return serverPlayerEntity.isSpectator() || serverPlayerEntity instanceof EntityPlayerMPFake;
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-9
@@ -1,21 +1,23 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.mixin.world;
|
package com.github.zly2006.carpetslsaddition.mixin.world;
|
||||||
|
|
||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import net.minecraft.entity.EntityType;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.entity.mob.MobEntity;
|
import net.minecraft.world.entity.EntityType;
|
||||||
import net.minecraft.server.world.ServerWorld;
|
import net.minecraft.world.entity.EntityTypes;
|
||||||
import net.minecraft.world.SpawnHelper;
|
import net.minecraft.world.entity.Mob;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import net.minecraft.world.level.NaturalSpawner;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
@Mixin(SpawnHelper.class)
|
@Mixin(NaturalSpawner.class)
|
||||||
public class MixinSpawnHelper {
|
public class MixinSpawnHelper {
|
||||||
@Inject(method = "createMob", at = @At("HEAD"), cancellable = true)
|
// 注意:不能用 @Redirect EntityType.create —— 会和 Carpet 的 NaturalSpawnerMixin 冲突。
|
||||||
private static void onCreateMob(ServerWorld world, EntityType<?> type, CallbackInfoReturnable<@Nullable MobEntity> cir) {
|
// 改为在 getMobForSpawn 返回处拦截。
|
||||||
if (SLSCarpetSettings.noBatSpawning && type == EntityType.BAT) {
|
@Inject(method = "getMobForSpawn", at = @At("HEAD"), cancellable = true)
|
||||||
|
private static void onCreateMob(ServerLevel level, EntityType<?> type, CallbackInfoReturnable<Mob> cir) {
|
||||||
|
if (SLSCarpetSettings.noBatSpawning && type == EntityTypes.BAT) {
|
||||||
cir.setReturnValue(null);
|
cir.setReturnValue(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.net;
|
||||||
|
|
||||||
|
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||||
|
import net.minecraft.network.codec.ByteBufCodecs;
|
||||||
|
import net.minecraft.network.codec.StreamCodec;
|
||||||
|
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端 → 服务端:请求显示某个榜单(或关闭)。
|
||||||
|
*
|
||||||
|
* <p>服务端收到后,给该玩家单独发送 {@code ClientboundSetDisplayObjectivePacket},
|
||||||
|
* 实现"各玩家各自显示、互不影响"。
|
||||||
|
*
|
||||||
|
* <p>{@code objectiveName} 为空字符串表示关闭。
|
||||||
|
*/
|
||||||
|
public record BoardRequestPayload(String objectiveName) implements CustomPacketPayload {
|
||||||
|
|
||||||
|
public static final CustomPacketPayload.Type<BoardRequestPayload> TYPE =
|
||||||
|
new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("slsa", "board_request"));
|
||||||
|
|
||||||
|
public static final StreamCodec<RegistryFriendlyByteBuf, BoardRequestPayload> CODEC = StreamCodec.composite(
|
||||||
|
ByteBufCodecs.STRING_UTF8, BoardRequestPayload::objectiveName,
|
||||||
|
BoardRequestPayload::new
|
||||||
|
);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CustomPacketPayload.Type<? extends CustomPacketPayload> type() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void register() {
|
||||||
|
net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry.serverboundPlay().register(TYPE, CODEC);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,254 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.turtle;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2DoubleOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.SectionPos;
|
||||||
|
import net.minecraft.tags.BlockTags;
|
||||||
|
import net.minecraft.tags.FluidTags;
|
||||||
|
import net.minecraft.world.entity.animal.turtle.Turtle;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.LevelReader;
|
||||||
|
import net.minecraft.world.level.block.TurtleEggBlock;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||||
|
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||||
|
import net.minecraft.world.level.material.FluidState;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds dry nesting blocks beside nearby, exposed source water.
|
||||||
|
*
|
||||||
|
* <p>This class was rewritten for throughput. The scan is the hot path: one call
|
||||||
|
* covers 73 x 73 columns x 19 vertical steps, and a pregnant turtle re-runs it
|
||||||
|
* whenever it drifts more than four blocks from the previous search origin or
|
||||||
|
* exhausts its cached candidate list.
|
||||||
|
*
|
||||||
|
* <p>Every result-affecting rule is unchanged:
|
||||||
|
* <ul>
|
||||||
|
* <li>the same box is visited (32 in X/Z for nests, plus a 4 block halo for
|
||||||
|
* water, 8 up/down for nests, plus 1 for water);</li>
|
||||||
|
* <li>the same conditions are tested, in the same order;</li>
|
||||||
|
* <li>unloaded chunks are still skipped, and are still never loaded or
|
||||||
|
* generated ({@code loadOrGenerate = false});</li>
|
||||||
|
* <li>the candidate list is still ordered by the same comparator, which ends
|
||||||
|
* in the block coordinates and therefore is a total order. Visit order no
|
||||||
|
* longer matters, so the sort result is identical.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>What changed, and why it is equivalent:
|
||||||
|
* <ul>
|
||||||
|
* <li>blocks are read through the chunk section that holds them instead of
|
||||||
|
* {@code Level#getBlockState}, which re-resolves the chunk, the section
|
||||||
|
* index and the build-height check on every single block. Sections that
|
||||||
|
* {@code hasOnlyAir()} are skipped entirely: they cannot contribute water
|
||||||
|
* or sand;</li>
|
||||||
|
* <li>the same availability test as {@code Level#hasChunk} is used, but the
|
||||||
|
* resolved chunk is kept for the section reads instead of being thrown
|
||||||
|
* away and looked up again per block;</li>
|
||||||
|
* <li>sand positions are collected as packed longs instead of thousands of
|
||||||
|
* {@code BlockPos} objects, so a scan allocates no per-block garbage;</li>
|
||||||
|
* <li>{@code isValidSite} hoists its loop invariants and remembers the last
|
||||||
|
* chunk availability answer; the disk it walks spans at most four chunks,
|
||||||
|
* so this only removes repeated lookups.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public final class NestingSiteFinder {
|
||||||
|
public static final int SEARCH_RADIUS = 32;
|
||||||
|
public static final int VERTICAL_SEARCH_RADIUS = 8;
|
||||||
|
public static final int WATER_RADIUS = 4;
|
||||||
|
public static final int WATER_VERTICAL_RADIUS = 1;
|
||||||
|
|
||||||
|
private static final Comparator<Candidate> CANDIDATE_ORDER = Comparator
|
||||||
|
.comparingDouble(Candidate::waterDistanceSquared)
|
||||||
|
.thenComparingDouble(Candidate::sandDistanceSquared)
|
||||||
|
.thenComparingInt(candidate -> candidate.sandPos().getX())
|
||||||
|
.thenComparingInt(candidate -> candidate.sandPos().getY())
|
||||||
|
.thenComparingInt(candidate -> candidate.sandPos().getZ());
|
||||||
|
|
||||||
|
private NestingSiteFinder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches a square extending 32 blocks in X/Z and 8 blocks in Y from the
|
||||||
|
* turtle. A four-block halo includes water beside nests at the boundary.
|
||||||
|
* The caller must check the turtle's collision box and navigation path.
|
||||||
|
*/
|
||||||
|
public static List<Candidate> findCandidates(Turtle turtle) {
|
||||||
|
Level level = turtle.level();
|
||||||
|
BlockPos origin = turtle.blockPosition();
|
||||||
|
int originX = origin.getX();
|
||||||
|
int originY = origin.getY();
|
||||||
|
int originZ = origin.getZ();
|
||||||
|
int scanRadius = SEARCH_RADIUS + WATER_RADIUS;
|
||||||
|
int minY = Math.max(level.getMinY(), originY - VERTICAL_SEARCH_RADIUS - WATER_VERTICAL_RADIUS);
|
||||||
|
int maxY = Math.min(level.getMaxY() - 1, originY + VERTICAL_SEARCH_RADIUS + WATER_VERTICAL_RADIUS);
|
||||||
|
int nestMinX = originX - SEARCH_RADIUS;
|
||||||
|
int nestMaxX = originX + SEARCH_RADIUS;
|
||||||
|
int nestMinZ = originZ - SEARCH_RADIUS;
|
||||||
|
int nestMaxZ = originZ + SEARCH_RADIUS;
|
||||||
|
int nestMinY = originY - VERTICAL_SEARCH_RADIUS;
|
||||||
|
int nestMaxY = originY + VERTICAL_SEARCH_RADIUS;
|
||||||
|
|
||||||
|
BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos();
|
||||||
|
LongArrayList sandBlocks = new LongArrayList();
|
||||||
|
Long2DoubleOpenHashMap waterDistances = new Long2DoubleOpenHashMap();
|
||||||
|
waterDistances.defaultReturnValue(Double.POSITIVE_INFINITY);
|
||||||
|
|
||||||
|
// Read each block once, instead of repeatedly querying the world around
|
||||||
|
// every sand block. Unloaded chunks are never requested or generated.
|
||||||
|
if (minY <= maxY) {
|
||||||
|
Vec3 turtlePos = turtle.position();
|
||||||
|
int minSection = level.getSectionIndex(minY);
|
||||||
|
int maxSection = level.getSectionIndex(maxY);
|
||||||
|
for (int chunkX = (originX - scanRadius) >> 4; chunkX <= (originX + scanRadius) >> 4; chunkX++) {
|
||||||
|
int fromX = Math.max(originX - scanRadius, chunkX << 4);
|
||||||
|
int toX = Math.min(originX + scanRadius, (chunkX << 4) + 15);
|
||||||
|
for (int chunkZ = (originZ - scanRadius) >> 4; chunkZ <= (originZ + scanRadius) >> 4; chunkZ++) {
|
||||||
|
// The same test Level#hasChunk performs, except the chunk is
|
||||||
|
// kept: loadOrGenerate = false never loads or generates.
|
||||||
|
ChunkAccess chunk = level.getChunk(chunkX, chunkZ, ChunkStatus.FULL, false);
|
||||||
|
if (chunk == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int fromZ = Math.max(originZ - scanRadius, chunkZ << 4);
|
||||||
|
int toZ = Math.min(originZ + scanRadius, (chunkZ << 4) + 15);
|
||||||
|
LevelChunkSection[] sections = chunk.getSections();
|
||||||
|
for (int sectionIndex = minSection; sectionIndex <= maxSection; sectionIndex++) {
|
||||||
|
if (sectionIndex < 0 || sectionIndex >= sections.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LevelChunkSection section = sections[sectionIndex];
|
||||||
|
// An all-air section holds neither water nor sand, so
|
||||||
|
// skipping it reads no block the loops could have used.
|
||||||
|
if (section == null || section.hasOnlyAir()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int sectionMinY = SectionPos.sectionToBlockCoord(level.getMinSectionY() + sectionIndex);
|
||||||
|
int fromY = Math.max(minY, sectionMinY);
|
||||||
|
int toY = Math.min(maxY, sectionMinY + 15);
|
||||||
|
boolean nestSection = fromY <= nestMaxY && toY >= nestMinY;
|
||||||
|
for (int x = fromX; x <= toX; x++) {
|
||||||
|
int localX = x & 15;
|
||||||
|
boolean nestX = x >= nestMinX && x <= nestMaxX;
|
||||||
|
for (int z = fromZ; z <= toZ; z++) {
|
||||||
|
boolean inNestRange = nestSection && nestX && z >= nestMinZ && z <= nestMaxZ;
|
||||||
|
int localZ = z & 15;
|
||||||
|
for (int y = fromY; y <= toY; y++) {
|
||||||
|
BlockState state = section.getBlockState(localX, y & 15, localZ);
|
||||||
|
if (isExposedSourceWater(level, cursor, x, y, z, state)) {
|
||||||
|
cursor.set(x, y, z);
|
||||||
|
waterDistances.put(BlockPos.asLong(x, y, z),
|
||||||
|
cursor.distToCenterSqr(turtlePos));
|
||||||
|
}
|
||||||
|
if (inNestRange && y >= nestMinY && y <= nestMaxY
|
||||||
|
&& state.is(BlockTags.SAND)
|
||||||
|
&& level.isEmptyBlock(cursor.set(x, y + 1, z))) {
|
||||||
|
sandBlocks.add(BlockPos.asLong(x, y, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Candidate> candidates = new ArrayList<>();
|
||||||
|
if (waterDistances.isEmpty()) {
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
Vec3 turtlePos = turtle.position();
|
||||||
|
for (int index = 0; index < sandBlocks.size(); index++) {
|
||||||
|
long packedSand = sandBlocks.getLong(index);
|
||||||
|
int sandX = BlockPos.getX(packedSand);
|
||||||
|
int sandY = BlockPos.getY(packedSand);
|
||||||
|
int sandZ = BlockPos.getZ(packedSand);
|
||||||
|
double nearestWaterDistance = Double.POSITIVE_INFINITY;
|
||||||
|
for (int dx = -WATER_RADIUS; dx <= WATER_RADIUS; dx++) {
|
||||||
|
for (int dz = -WATER_RADIUS; dz <= WATER_RADIUS; dz++) {
|
||||||
|
if (dx * dx + dz * dz > WATER_RADIUS * WATER_RADIUS) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int dy = -WATER_VERTICAL_RADIUS; dy <= WATER_VERTICAL_RADIUS; dy++) {
|
||||||
|
long waterPosition = BlockPos.asLong(sandX + dx, sandY + dy, sandZ + dz);
|
||||||
|
nearestWaterDistance = Math.min(nearestWaterDistance, waterDistances.get(waterPosition));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Double.isFinite(nearestWaterDistance)) {
|
||||||
|
cursor.set(sandX, sandY, sandZ);
|
||||||
|
candidates.add(new Candidate(new BlockPos(sandX, sandY, sandZ), nearestWaterDistance,
|
||||||
|
cursor.distToCenterSqr(turtlePos)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
candidates.sort(CANDIDATE_ORDER);
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rechecks a chosen nest, including changes to its sand, air, or water. */
|
||||||
|
public static boolean isValidSite(LevelReader level, BlockPos sandPos) {
|
||||||
|
if (!level.isInsideBuildHeight(sandPos) || !level.isInsideBuildHeight(sandPos.above())
|
||||||
|
|| !level.hasChunkAt(sandPos)
|
||||||
|
|| !TurtleEggBlock.isSand(level, sandPos)
|
||||||
|
|| !level.isEmptyBlock(sandPos.above())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int sandX = sandPos.getX();
|
||||||
|
int sandY = sandPos.getY();
|
||||||
|
int sandZ = sandPos.getZ();
|
||||||
|
BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos();
|
||||||
|
int lastChunkX = Integer.MIN_VALUE;
|
||||||
|
int lastChunkZ = Integer.MIN_VALUE;
|
||||||
|
boolean lastChunkLoaded = false;
|
||||||
|
for (int dx = -WATER_RADIUS; dx <= WATER_RADIUS; dx++) {
|
||||||
|
for (int dz = -WATER_RADIUS; dz <= WATER_RADIUS; dz++) {
|
||||||
|
if (dx * dx + dz * dz > WATER_RADIUS * WATER_RADIUS) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int x = sandX + dx;
|
||||||
|
int z = sandZ + dz;
|
||||||
|
int chunkX = x >> 4;
|
||||||
|
int chunkZ = z >> 4;
|
||||||
|
// The disk above spans at most four chunks, so this only removes
|
||||||
|
// repeated queries for columns that share a chunk.
|
||||||
|
if (chunkX != lastChunkX || chunkZ != lastChunkZ) {
|
||||||
|
lastChunkX = chunkX;
|
||||||
|
lastChunkZ = chunkZ;
|
||||||
|
lastChunkLoaded = level.hasChunk(chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
if (!lastChunkLoaded) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int dy = -WATER_VERTICAL_RADIUS; dy <= WATER_VERTICAL_RADIUS; dy++) {
|
||||||
|
int y = sandY + dy;
|
||||||
|
cursor.set(x, y, z);
|
||||||
|
if (level.isInsideBuildHeight(y) && level.isInsideBuildHeight(y + 1)
|
||||||
|
&& isExposedSourceWater(level, cursor, x, y, z, level.getBlockState(cursor))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isExposedSourceWater(LevelReader level, BlockPos.MutableBlockPos cursor,
|
||||||
|
int x, int y, int z, BlockState state) {
|
||||||
|
// Requiring a liquid block excludes waterlogged fences, slabs and other
|
||||||
|
// solid blocks even though their fluid state may report source water.
|
||||||
|
if (!state.liquid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FluidState fluid = state.getFluidState();
|
||||||
|
return fluid.is(FluidTags.WATER) && fluid.isSource() && level.isEmptyBlock(cursor.set(x, y + 1, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Candidate(BlockPos sandPos, double waterDistanceSquared, double sandDistanceSquared) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.util;
|
||||||
|
|
||||||
|
import com.github.zly2006.carpetslsaddition.ServerMain;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.stats.ServerStatsCounter;
|
||||||
|
import net.minecraft.stats.Stat;
|
||||||
|
import net.minecraft.stats.Stats;
|
||||||
|
import net.minecraft.world.scores.Objective;
|
||||||
|
import net.minecraft.world.scores.ScoreAccess;
|
||||||
|
import net.minecraft.world.scores.ScoreHolder;
|
||||||
|
import net.minecraft.world.scores.Scoreboard;
|
||||||
|
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 死亡榜 / 挖掘榜的 scoreboard 管理器(服务端)。
|
||||||
|
*
|
||||||
|
* <p>数据源是 vanilla stats,但 stats 不会自动同步给其他客户端。
|
||||||
|
* 因此本类定期把 stats 的聚合值写入 scoreboard objective,
|
||||||
|
* 客户端再用原版计分板侧边栏显示(scoreboard 会自动同步给所有客户端)。
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code sls_deaths}:死亡次数(DUMMY 判据,从 stats 的 {@link Stats#DEATHS} 写入)。</li>
|
||||||
|
* <li>{@code sls_mined}:挖掘总数(DUMMY 判据,从 stats 的 {@link Stats#BLOCK_MINED} 聚合)。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public final class BoardManager {
|
||||||
|
|
||||||
|
private BoardManager() {}
|
||||||
|
|
||||||
|
public static final String OBJ_DEATHS = "sls_deaths";
|
||||||
|
public static final String OBJ_MINED = "sls_mined";
|
||||||
|
|
||||||
|
/** 确保两个 objective 存在(服务器启动/重载时调用)。 */
|
||||||
|
public static void ensureObjectives(MinecraftServer server) {
|
||||||
|
Scoreboard sb = server.getScoreboard();
|
||||||
|
ServerMain.LOGGER.info("[SLSA] ensureObjectives called, deaths={} mined={}",
|
||||||
|
sb.getObjective(OBJ_DEATHS) != null, sb.getObjective(OBJ_MINED) != null);
|
||||||
|
if (sb.getObjective(OBJ_DEATHS) == null) {
|
||||||
|
sb.addObjective(OBJ_DEATHS, ObjectiveCriteria.DUMMY,
|
||||||
|
Component.literal("死亡榜"), ObjectiveCriteria.RenderType.INTEGER, true, null);
|
||||||
|
ServerMain.LOGGER.info("[SLSA] created scoreboard objective {}", OBJ_DEATHS);
|
||||||
|
}
|
||||||
|
if (sb.getObjective(OBJ_MINED) == null) {
|
||||||
|
sb.addObjective(OBJ_MINED, ObjectiveCriteria.DUMMY,
|
||||||
|
Component.literal("挖掘榜"), ObjectiveCriteria.RenderType.INTEGER, true, null);
|
||||||
|
ServerMain.LOGGER.info("[SLSA] created scoreboard objective {}", OBJ_MINED);
|
||||||
|
}
|
||||||
|
// 让客户端能收到这两个 objective(load 进来的 objective 可能未被追踪)
|
||||||
|
if (sb instanceof net.minecraft.server.ServerScoreboard ssb) {
|
||||||
|
Objective d = sb.getObjective(OBJ_DEATHS);
|
||||||
|
if (d != null) ssb.startTrackingObjective(d);
|
||||||
|
Objective m = sb.getObjective(OBJ_MINED);
|
||||||
|
if (m != null) ssb.startTrackingObjective(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 读取某玩家的挖掘总数(遍历 stats map 中 BLOCK_MINED 类型条目)。 */
|
||||||
|
private static int totalMined(ServerStatsCounter counter) {
|
||||||
|
int sum = 0;
|
||||||
|
for (Stat<?> stat : counter.stats.keySet()) {
|
||||||
|
if (stat.getType() == Stats.BLOCK_MINED) {
|
||||||
|
sum += counter.getValue(stat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 读取某玩家的死亡次数。 */
|
||||||
|
private static int totalDeaths(ServerStatsCounter counter) {
|
||||||
|
return counter.getValue(Stats.CUSTOM, Stats.DEATHS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 把一个分数写入 objective(若不存在则创建/更新)。 */
|
||||||
|
private static void setScore(Scoreboard sb, Objective obj, ServerPlayer player, int value) {
|
||||||
|
if (obj == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ScoreAccess access = sb.getOrCreatePlayerScore((ScoreHolder) player, obj);
|
||||||
|
access.set(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 从 stats 同步所有在线玩家的分数到 scoreboard(定期调用)。 */
|
||||||
|
public static void syncScores(MinecraftServer server) {
|
||||||
|
Scoreboard sb = server.getScoreboard();
|
||||||
|
Objective deaths = sb.getObjective(OBJ_DEATHS);
|
||||||
|
Objective mined = sb.getObjective(OBJ_MINED);
|
||||||
|
if (deaths == null && mined == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ServerPlayer player : server.getPlayerList().getPlayers()) {
|
||||||
|
ServerStatsCounter counter = player.getStats();
|
||||||
|
setScore(sb, deaths, player, totalDeaths(counter));
|
||||||
|
setScore(sb, mined, player, totalMined(counter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 客户端请求显示某榜单 → 给该玩家单独发 display 包(不影响其他玩家)。 */
|
||||||
|
public static void onClientRequest(ServerPlayer player, String objectiveName) {
|
||||||
|
MinecraftServer server = ServerMain.server;
|
||||||
|
if (server == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Objective obj = objectiveName == null || objectiveName.isEmpty()
|
||||||
|
? null
|
||||||
|
: server.getScoreboard().getObjective(objectiveName);
|
||||||
|
// 干净重建客户端该 objective:先 REMOVE(客户端没有则忽略),再 ADD + 分数包
|
||||||
|
if (obj != null) {
|
||||||
|
player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetObjectivePacket(
|
||||||
|
obj, net.minecraft.network.protocol.game.ClientboundSetObjectivePacket.METHOD_REMOVE));
|
||||||
|
player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetObjectivePacket(
|
||||||
|
obj, net.minecraft.network.protocol.game.ClientboundSetObjectivePacket.METHOD_ADD));
|
||||||
|
if (server.getScoreboard() instanceof net.minecraft.server.ServerScoreboard ssb) {
|
||||||
|
for (var pkt : ssb.getStartTrackingPackets(obj)) {
|
||||||
|
if (pkt instanceof net.minecraft.network.protocol.game.ClientboundSetScorePacket) {
|
||||||
|
player.connection.send(pkt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 发 display 包(slot=SIDEBAR,objective 可为 null 表示清除)
|
||||||
|
player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket(
|
||||||
|
net.minecraft.world.scores.DisplaySlot.SIDEBAR, obj));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.util;
|
||||||
|
|
||||||
|
import com.github.zly2006.carpetslsaddition.ServerMain;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 假人名单管理器:维护一份"受控假人名字"列表,持久化到 config/slsa-bot-list.json。
|
||||||
|
*
|
||||||
|
* <p>用途:{@code /botlist add cute wazi} 把名字加入名单后,{@code /botall} 的操作范围
|
||||||
|
* 就限定为名单内的假人(名单为空时操作所有 SLS 假人)。
|
||||||
|
*/
|
||||||
|
public final class BotListManager {
|
||||||
|
|
||||||
|
private BotListManager() {}
|
||||||
|
|
||||||
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
|
||||||
|
/** 名字列表(保序、去重)。 */
|
||||||
|
private static final Set<String> NAMES = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
private static Path configPath;
|
||||||
|
|
||||||
|
private static Path path() {
|
||||||
|
if (configPath == null) {
|
||||||
|
configPath = FabricLoader.getInstance().getConfigDir().resolve("slsa-bot-list.json");
|
||||||
|
}
|
||||||
|
return configPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 加载名单(服务器启动时调用)。 */
|
||||||
|
public static void load() {
|
||||||
|
NAMES.clear();
|
||||||
|
Path p = path();
|
||||||
|
if (!Files.exists(p)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try (Reader r = Files.newBufferedReader(p)) {
|
||||||
|
Set<String> loaded = GSON.fromJson(r, new TypeToken<LinkedHashSet<String>>() {}.getType());
|
||||||
|
if (loaded != null) {
|
||||||
|
NAMES.addAll(loaded);
|
||||||
|
}
|
||||||
|
ServerMain.LOGGER.info("[SLSA] loaded bot list: {} entries", NAMES.size());
|
||||||
|
} catch (Exception e) {
|
||||||
|
ServerMain.LOGGER.error("[SLSA] failed to load bot list", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存名单。 */
|
||||||
|
public static void save() {
|
||||||
|
Path p = path();
|
||||||
|
try {
|
||||||
|
Files.createDirectories(p.getParent());
|
||||||
|
try (Writer w = Files.newBufferedWriter(p)) {
|
||||||
|
GSON.toJson(NAMES, w);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
ServerMain.LOGGER.error("[SLSA] failed to save bot list", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean add(String name) {
|
||||||
|
boolean added = NAMES.add(name);
|
||||||
|
if (added) save();
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean remove(String name) {
|
||||||
|
boolean removed = NAMES.remove(name);
|
||||||
|
if (removed) save();
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
NAMES.clear();
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 返回名单快照。 */
|
||||||
|
public static Set<String> getNames() {
|
||||||
|
return new LinkedHashSet<>(NAMES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean contains(String name) {
|
||||||
|
return NAMES.contains(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEmpty() {
|
||||||
|
return NAMES.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.util;
|
package com.github.zly2006.carpetslsaddition.util;
|
||||||
|
|
||||||
import net.minecraft.block.ShulkerBoxBlock;
|
import net.minecraft.core.component.DataComponents;
|
||||||
import net.minecraft.component.DataComponentTypes;
|
import net.minecraft.world.item.BlockItem;
|
||||||
import net.minecraft.item.BlockItem;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.world.item.component.ItemContainerContents;
|
||||||
|
import net.minecraft.world.level.block.ShulkerBoxBlock;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class ShulkerBoxItemUtil {
|
public class ShulkerBoxItemUtil {
|
||||||
public static final int SHULKERBOX_MAX_STACK_AMOUNT = 64;
|
public static final int SHULKERBOX_MAX_STACK_AMOUNT = 64;
|
||||||
@@ -15,25 +14,17 @@ public class ShulkerBoxItemUtil {
|
|||||||
if (stack.isEmpty() && otherStack.isEmpty()) {
|
if (stack.isEmpty() && otherStack.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return Objects.equals(
|
return stack.getComponents().equals(otherStack.getComponents());
|
||||||
stack.getComponents().filtered(s -> s != DataComponentTypes.CONTAINER),
|
|
||||||
stack.getComponents().filtered(s -> s != DataComponentTypes.CONTAINER)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return ItemStack.areItemsAndComponentsEqual(stack, otherStack);
|
return ItemStack.isSameItemSameComponents(stack, otherStack);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isEmptyShulkerBoxItem(ItemStack itemStack) {
|
public static boolean isEmptyShulkerBoxItem(ItemStack itemStack) {
|
||||||
if (itemStack.getItem() instanceof BlockItem &&
|
if (itemStack.getItem() instanceof BlockItem blockItem
|
||||||
((BlockItem) itemStack.getItem()).getBlock() instanceof ShulkerBoxBlock) {
|
&& blockItem.getBlock() instanceof ShulkerBoxBlock) {
|
||||||
if (itemStack.getComponents().contains(DataComponentTypes.CONTAINER)) {
|
return itemStack.getOrDefault(DataComponents.CONTAINER, ItemContainerContents.EMPTY)
|
||||||
return !itemStack.getComponents().get(DataComponentTypes.CONTAINER).iterateNonEmpty().iterator().hasNext();
|
.nonEmptyItemCopyStream().findAny().isEmpty();
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.github.zly2006.carpetslsaddition.util.access;
|
package com.github.zly2006.carpetslsaddition.util.access;
|
||||||
|
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
public interface PlayerAccessor {
|
public interface PlayerAccessor {
|
||||||
void carpet_SLS_Addition$setDisplayName(Text name);
|
void carpet_SLS_Addition$setDisplayName(Component name);
|
||||||
}
|
}
|
||||||
@@ -48,8 +48,14 @@
|
|||||||
"carpet.rule.elytraCraftable.desc": "Make elytra craftable for Minecraft 1.20.6+ (eltra wont disappear when crafting)",
|
"carpet.rule.elytraCraftable.desc": "Make elytra craftable for Minecraft 1.20.6+ (eltra wont disappear when crafting)",
|
||||||
"carpet.rule.botMaxOnlineTime.desc": "Set the maximum living time of the bot which create by carpet sls addition.",
|
"carpet.rule.botMaxOnlineTime.desc": "Set the maximum living time of the bot which create by carpet sls addition.",
|
||||||
"carpet.rule.spectatorCannotUseLeash.desc": "If a player is a spectator, they cannot use the leash.",
|
"carpet.rule.spectatorCannotUseLeash.desc": "If a player is a spectator, they cannot use the leash.",
|
||||||
"carpet.rule.offlineFakePlayers.desc": "假人强制离线uuid",
|
"carpet.rule.offlineFakePlayers.desc": "Force fake players to use offline UUIDs",
|
||||||
"carpet.rule.armadilloImmediateDespawns.desc": "Armadillos will despawn immediately if they are 128 blocks away from the player.",
|
"carpet.rule.armadilloImmediateDespawns.desc": "Armadillos will despawn immediately if they are 128 blocks away from the player.",
|
||||||
|
"carpet.rule.turtleLocalNesting.name": "Turtle Local Nesting",
|
||||||
|
"carpet.rule.turtleLocalNesting.desc": "Turtles carrying eggs will lay them on reachable sand near the closest local water source instead of returning to their birthplace.",
|
||||||
|
"carpet.rule.sturdyDecoratedPot.name": "Sturdy Decorated Pot",
|
||||||
|
"carpet.rule.sturdyDecoratedPot.desc": "Decorated pots are not shattered by projectiles.",
|
||||||
|
"carpet.rule.unbreakableDecoratedPot.name": "No Crack When Mining Pot",
|
||||||
|
"carpet.rule.unbreakableDecoratedPot.desc": "Breaking a decorated pot by a player does not produce the cracked effect.",
|
||||||
"carpet.rule.trialSpawnerCD.desc": "trialSpawnerCD.",
|
"carpet.rule.trialSpawnerCD.desc": "trialSpawnerCD.",
|
||||||
|
|
||||||
"carpet.rule.skipTicksForJoblessVillager.extra.0": "Set to 0 to maintain the vanilla behavior",
|
"carpet.rule.skipTicksForJoblessVillager.extra.0": "Set to 0 to maintain the vanilla behavior",
|
||||||
@@ -66,5 +72,16 @@
|
|||||||
"carpet.slsa.bot.respawned": "The bot %s's online time has been reset.",
|
"carpet.slsa.bot.respawned": "The bot %s's online time has been reset.",
|
||||||
"carpet.slsa.bot.not_a_bot": "The player %s is not created by carpet sls addition, so you can't reset the online time.",
|
"carpet.slsa.bot.not_a_bot": "The player %s is not created by carpet sls addition, so you can't reset the online time.",
|
||||||
|
|
||||||
"carpet.slsa.block.deep_slate_core": "Deepslate Core"
|
"carpet.slsa.block.deep_slate_core": "Deepslate Core",
|
||||||
|
"carpet.rule.stackableVehicles.name": "Stackable vehicles",
|
||||||
|
"carpet.rule.stackableVehicles.desc": "Minecarts and boats can stack up to 64, auto-merging on pickup",
|
||||||
|
"carpet.rule.slsBoardEnabled.name": "Enable boards",
|
||||||
|
"carpet.rule.slsBoardEnabled.desc": "Allow players to use /slsboard to show death/mining leaderboards",
|
||||||
|
"carpet.rule.noBrittleIceFromFlowingWater.name": "No brittle ice from flowing water",
|
||||||
|
"carpet.rule.noBrittleIceFromFlowingWater.desc": "Only water flowing straight down onto blue ice forms brittle ice. Horizontally flowing water will not.",
|
||||||
|
"carpet.rule.elytraCraftable.name": "Craftable elytra",
|
||||||
|
"carpet.rule.armadilloImmediateDespawns.name": "Armadillos despawn immediately",
|
||||||
|
"carpet.rule.offlineFakePlayers.name": "Force offline UUID for fake players",
|
||||||
|
"carpet.rule.netherPortalSize.name": "Max nether portal size",
|
||||||
|
"carpet.rule.trialSpawnerCD.name": "Trial spawner cooldown"
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,12 @@
|
|||||||
"carpet.rule.botMaxOnlineTime.name": "Bot最大存活时间",
|
"carpet.rule.botMaxOnlineTime.name": "Bot最大存活时间",
|
||||||
"carpet.rule.spectatorCannotUseLeash.name": "观察者模式禁用拴绳",
|
"carpet.rule.spectatorCannotUseLeash.name": "观察者模式禁用拴绳",
|
||||||
"carpet.rule.armadilloImmediateDespawns.desc": "当犰狳远离玩家128格时,立即消失",
|
"carpet.rule.armadilloImmediateDespawns.desc": "当犰狳远离玩家128格时,立即消失",
|
||||||
|
"carpet.rule.turtleLocalNesting.name": "海龟就地筑巢",
|
||||||
|
"carpet.rule.turtleLocalNesting.desc": "有蛋的海龟会在附近的水边沙滩下蛋,而不是返回出生地",
|
||||||
|
"carpet.rule.sturdyDecoratedPot.name": "坚硬陶罐",
|
||||||
|
"carpet.rule.sturdyDecoratedPot.desc": "装饰陶罐不会被投掷物击碎",
|
||||||
|
"carpet.rule.unbreakableDecoratedPot.name": "挖掘陶罐无裂纹",
|
||||||
|
"carpet.rule.unbreakableDecoratedPot.desc": "玩家破坏装饰陶罐时不会产生裂纹效果",
|
||||||
|
|
||||||
"carpet.rule.obtainableReinforcedDeepSlate.desc": "当铁砧砸在9*64个深板岩的掉落物上时,可以获得强化深板岩",
|
"carpet.rule.obtainableReinforcedDeepSlate.desc": "当铁砧砸在9*64个深板岩的掉落物上时,可以获得强化深板岩",
|
||||||
"carpet.rule.skipTicksForJoblessVillager.desc": "对于无业/傻子村民,执行设定数量的tick运算后,跳过1tick的运算",
|
"carpet.rule.skipTicksForJoblessVillager.desc": "对于无业/傻子村民,执行设定数量的tick运算后,跳过1tick的运算",
|
||||||
@@ -66,5 +72,16 @@
|
|||||||
"carpet.slsa.bot.respawned": "假人%s的在线时长已重置!",
|
"carpet.slsa.bot.respawned": "假人%s的在线时长已重置!",
|
||||||
"carpet.slsa.bot.not_a_bot": "假人%s不是由Carpet SLS Addition生成的,所以你不能重置它的在线时间!",
|
"carpet.slsa.bot.not_a_bot": "假人%s不是由Carpet SLS Addition生成的,所以你不能重置它的在线时间!",
|
||||||
|
|
||||||
"carpet.slsa.block.deep_slate_core": "深板岩核心"
|
"carpet.slsa.block.deep_slate_core": "深板岩核心",
|
||||||
|
"carpet.rule.stackableVehicles.name": "载具可堆叠",
|
||||||
|
"carpet.rule.stackableVehicles.desc": "矿车和船的物品可以堆叠到64,拾取时自动合并",
|
||||||
|
"carpet.rule.slsBoardEnabled.name": "启用榜单",
|
||||||
|
"carpet.rule.slsBoardEnabled.desc": "允许玩家用 /slsboard 显示死亡榜/挖掘榜",
|
||||||
|
"carpet.rule.noBrittleIceFromFlowingWater.name": "横向流动水不产生碎冰",
|
||||||
|
"carpet.rule.noBrittleIceFromFlowingWater.desc": "只有竖直向下的水流接触蓝冰时才会产生碎冰,横向流动的水不再转化",
|
||||||
|
"carpet.rule.elytraCraftable.name": "鞘翅可合成",
|
||||||
|
"carpet.rule.armadilloImmediateDespawns.name": "犰狳立即消失",
|
||||||
|
"carpet.rule.offlineFakePlayers.name": "假人强制离线UUID",
|
||||||
|
"carpet.rule.netherPortalSize.name": "地狱门最大尺寸",
|
||||||
|
"carpet.rule.trialSpawnerCD.name": "试炼刷怪笼冷却"
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,9 @@
|
|||||||
"entrypoints": {
|
"entrypoints": {
|
||||||
"main": [
|
"main": [
|
||||||
"com.github.zly2006.carpetslsaddition.ServerMain"
|
"com.github.zly2006.carpetslsaddition.ServerMain"
|
||||||
|
],
|
||||||
|
"client": [
|
||||||
|
"com.github.zly2006.carpetslsaddition.client.BoardClient"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"accessWidener": "slsa.accesswidener",
|
"accessWidener": "slsa.accesswidener",
|
||||||
@@ -27,13 +30,10 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"depends": {
|
"depends": {
|
||||||
"fabricloader": ">=0.15.1",
|
"fabricloader": ">=0.19.3",
|
||||||
"fabric-api": "*",
|
"fabric-api": "*",
|
||||||
"minecraft": ">=1.21",
|
"minecraft": ">=26.1",
|
||||||
"java": ">=21",
|
"java": ">=25",
|
||||||
"carpet": "*"
|
"carpet": "*"
|
||||||
},
|
|
||||||
"suggests": {
|
|
||||||
"another-mod": "*"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
accessWidener v1 named
|
accessWidener v1 official
|
||||||
accessible class net/minecraft/world/block/ChainRestrictedNeighborUpdater$Entry
|
|
||||||
accessible class net/minecraft/entity/mob/EndermanEntity$PickUpBlockGoal
|
accessible class net/minecraft/world/entity/monster/Enderman$EndermanTakeBlockGoal
|
||||||
accessible method net/minecraft/entity/Entity unsetRemoved ()V
|
accessible method net/minecraft/world/entity/Entity unsetRemoved ()V
|
||||||
accessible field net/minecraft/entity/player/PlayerEntity PLAYER_MODEL_PARTS Lnet/minecraft/entity/data/TrackedData;
|
accessible field net/minecraft/world/entity/Entity entityData Lnet/minecraft/network/syncher/SynchedEntityData;
|
||||||
accessible field net/minecraft/block/spawner/TrialSpawnerData spawnedMobsAlive Ljava/util/Set;
|
accessible field net/minecraft/world/entity/Avatar DATA_PLAYER_MODE_CUSTOMISATION Lnet/minecraft/network/syncher/EntityDataAccessor;
|
||||||
|
|
||||||
|
accessible field net/minecraft/stats/StatsCounter stats Lit/unimi/dsi/fastutil/objects/Object2IntMap;
|
||||||
@@ -1,20 +1,22 @@
|
|||||||
{
|
{
|
||||||
"required": true,
|
"required": false,
|
||||||
"minVersion": "0.8",
|
"minVersion": "0.8",
|
||||||
"package": "com.github.zly2006.carpetslsaddition.mixin",
|
"package": "com.github.zly2006.carpetslsaddition.mixin",
|
||||||
"compatibilityLevel": "JAVA_17",
|
"compatibilityLevel": "JAVA_21",
|
||||||
"client": [
|
"client": [
|
||||||
],
|
],
|
||||||
"mixins": [
|
"mixins": [
|
||||||
"block.BlockPatternTestTransformInvoker",
|
"block.BlockPatternTestTransformInvoker",
|
||||||
"block.MixinNetherPortal",
|
"block.MixinNetherPortal",
|
||||||
"block.MixinRedstoneWireBlock",
|
"block.MixinRedstoneWireBlock",
|
||||||
|
"block.MixinDecoratedPotBlock",
|
||||||
"block.MixinTrialSpawnerData",
|
"block.MixinTrialSpawnerData",
|
||||||
"block.MixinTrialSpawnerState",
|
"block.MixinTrialSpawnerState",
|
||||||
|
"block.MixinFlowingWaterBrittleIce",
|
||||||
|
"item.SlotMixin",
|
||||||
"carpet.MixinEntityPlayerMPFake",
|
"carpet.MixinEntityPlayerMPFake",
|
||||||
"carpet.MixinPlayerCommand",
|
"carpet.MixinPlayerCommand",
|
||||||
"datapack.MixinVanillaResourcePackProvider",
|
"elytra.ItemAccessor",
|
||||||
"elytra.MixinElytraItem",
|
|
||||||
"elytra.MixinItem",
|
"elytra.MixinItem",
|
||||||
"entity.MixinArmadilloEntity",
|
"entity.MixinArmadilloEntity",
|
||||||
"entity.MixinArmorStandEntity",
|
"entity.MixinArmorStandEntity",
|
||||||
@@ -33,7 +35,11 @@
|
|||||||
"shulker.MixinServerPlayNetworkHandler",
|
"shulker.MixinServerPlayNetworkHandler",
|
||||||
"shulker.MixinSlot",
|
"shulker.MixinSlot",
|
||||||
"world.MixinSleepManager",
|
"world.MixinSleepManager",
|
||||||
"world.MixinSpawnHelper"
|
"world.MixinSpawnHelper",
|
||||||
|
"turtle.PathNavigationAccessor",
|
||||||
|
"turtle.TurtleAccessor",
|
||||||
|
"turtle.TurtleGoHomeGoalMixin",
|
||||||
|
"turtle.TurtleLayEggGoalMixin"
|
||||||
],
|
],
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"defaultRequire": 1
|
||||||
|
|||||||
Reference in new issue
Block a user