mirror of
https://github.com/PaperMC/Paper.git
synced 2026-11-04 17:00:09 -05:00
Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4d6dfd159 | ||
|
|
91693c3ec1 | ||
|
|
3e24be0380 | ||
|
|
58e6e45d67 | ||
|
|
19ebc4a49b | ||
|
|
e8c8a68254 | ||
|
|
a3d63e8d81 | ||
|
|
9240f586a4 | ||
|
|
5749698297 | ||
|
|
ad9a0347f5 | ||
|
|
c646fafccf | ||
|
|
eb5b0dee24 | ||
|
|
22ca6c7de3 | ||
|
|
e5fe71723e | ||
|
|
50018799f2 | ||
|
|
a3b2cb162a | ||
|
|
a2a42c5b12 | ||
|
|
1797fbccf1 | ||
|
|
bb09b431d2 | ||
|
|
5d4f9bd0e4 | ||
|
|
bd0c804a19 | ||
|
|
27af5dde55 | ||
|
|
37dc5450d9 | ||
|
|
de518f79b5 | ||
|
|
0bdc80db32 | ||
|
|
c9e894d3cc |
No files matched your search
+1
-1
@@ -18,7 +18,7 @@ indent_style = tab
|
||||
[*.bat]
|
||||
end_of_line = crlf
|
||||
|
||||
[*.yml]
|
||||
[{*.yaml,*.yml}]
|
||||
indent_size = 2
|
||||
|
||||
[{*.patch,*.md}]
|
||||
|
||||
@@ -5,14 +5,17 @@
|
||||
# of the PR relating to this action.
|
||||
|
||||
name: Build Paper
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- labeled
|
||||
push:
|
||||
branches-ignore:
|
||||
- ver/26.2
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -107,7 +110,7 @@ jobs:
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: Test Results
|
||||
name: test-results
|
||||
path: |
|
||||
**/build/test-results/test/TEST-*.xml
|
||||
|
||||
@@ -137,12 +140,12 @@ jobs:
|
||||
publishing-task: ":paper-api:publishAllPublicationsTo_githubPackages_PRsRepository publishDevBundlePublicationTo_githubPackages_PRsRepository -PpublishDevBundle"
|
||||
event_file:
|
||||
name: "Event File"
|
||||
# Only run on PRs if the source branch is on someone else's repo
|
||||
if: ${{ github.event_name != 'pull_request' || github.repository != github.event.pull_request.head.repo.full_name }}
|
||||
needs: build
|
||||
if: ${{ !cancelled() && needs.build.result != 'skipped' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: Event File
|
||||
name: event-data
|
||||
path: ${{ github.event_path }}
|
||||
@@ -0,0 +1,56 @@
|
||||
name: Release 26.2
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- ver/26.2
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
cancel-in-progress: false
|
||||
group: release-26.2
|
||||
|
||||
jobs:
|
||||
release:
|
||||
# guard for manual dispatch
|
||||
if: ${{ github.ref == 'refs/heads/ver/26.2' }}
|
||||
runs-on: namespace-profile-linux-default
|
||||
timeout-minutes: 30
|
||||
#env:
|
||||
# BUILD_NUMBER: ${{ github.run_number }}
|
||||
steps:
|
||||
- name: "environment"
|
||||
run: |
|
||||
echo "BUILD_NUMBER=$((134 + GITHUB_RUN_NUMBER))" >> "$GITHUB_ENV"
|
||||
echo "BUILD_STARTED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: "git / checkout"
|
||||
uses: namespacelabs/nscloud-checkout-action@66f2dc6f6c42a8ac6c4e53473c4840006822831e # v9.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: papermc/ci/setup-gradle@main
|
||||
|
||||
- uses: papermc/ci/build-paper@main
|
||||
|
||||
- if: ${{ !cancelled() }}
|
||||
uses: papermc/ci/upload-event-data@main
|
||||
with:
|
||||
path: ${{ github.event_path }}
|
||||
|
||||
- if: ${{ !cancelled() }}
|
||||
uses: papermc/ci/upload-test-results@main
|
||||
|
||||
- uses: papermc/ci/publish-fill@main
|
||||
with:
|
||||
fill_api_url: ${{ secrets.FILL_API_URL }}
|
||||
fill_api_key: ${{ secrets.FILL_API_KEY }}
|
||||
|
||||
- uses: papermc/ci/publish-maven@main
|
||||
with:
|
||||
maven_username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
maven_password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
maven_tasks: -PpublishDevBundle :paper-api:publish publishDevBundlePublicationToPaperReleasesRepository
|
||||
@@ -0,0 +1,24 @@
|
||||
name: Test Results
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [ "Build Paper" ]
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
test-results:
|
||||
name: Test Results
|
||||
if: github.event.workflow_run.conclusion != 'skipped'
|
||||
runs-on: namespace-profile-linux-small
|
||||
steps:
|
||||
- uses: papermc/ci/publish-test-results@main
|
||||
with:
|
||||
event: ${{ github.event.workflow_run.event }}
|
||||
commit: ${{ github.event.workflow_run.head_sha }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
@@ -1,34 +0,0 @@
|
||||
name: Test Results
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [ "Build Paper" ]
|
||||
types:
|
||||
- completed
|
||||
permissions: { }
|
||||
|
||||
jobs:
|
||||
test-results:
|
||||
name: Test Results
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.workflow_run.conclusion != 'skipped'
|
||||
permissions:
|
||||
checks: write
|
||||
# for downloading test result artifacts
|
||||
actions: read
|
||||
steps:
|
||||
- name: Download and Extract Artifacts
|
||||
uses: dawidd6/action-download-artifact@v21
|
||||
with:
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
path: test_artifacts
|
||||
name: "Test Results|Event File"
|
||||
name_is_regexp: true
|
||||
- name: Publish Test Results
|
||||
uses: EnricoMi/publish-unit-test-result-action@v2
|
||||
with:
|
||||
commit: ${{ github.event.workflow_run.head_sha }}
|
||||
event_file: test_artifacts/Event File/event.json
|
||||
event_name: ${{ github.event.workflow_run.event }}
|
||||
files: "test_artifacts/**/*.xml"
|
||||
comment_mode: off
|
||||
@@ -1,4 +1,4 @@
|
||||
Paper [](https://papermc.io/downloads/paper)
|
||||
[](https://github.com/PaperMC/Paper/actions)
|
||||
[](https://discord.gg/papermc)
|
||||
|
||||
+9
-9
@@ -1,12 +1,12 @@
|
||||
package io.papermc.paper.event.entity;
|
||||
|
||||
import java.util.List;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.util.List;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
/**
|
||||
* Fired when two entities collide with each other.
|
||||
@@ -15,6 +15,7 @@ import java.util.List;
|
||||
* Note that even if cancelled, the client may still run its own collision unless
|
||||
* disabled via player teams.
|
||||
*/
|
||||
@NullMarked
|
||||
public class EntityCollideWithEntityEvent extends Event implements Cancellable {
|
||||
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
@@ -22,8 +23,8 @@ public class EntityCollideWithEntityEvent extends Event implements Cancellable {
|
||||
private final List<Entity> entities;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public EntityCollideWithEntityEvent(@NotNull Entity entity1, @NotNull Entity entity2) {
|
||||
entities = List.of(entity1, entity2);
|
||||
public EntityCollideWithEntityEvent(final Entity entity1, final Entity entity2) {
|
||||
this.entities = List.of(entity1, entity2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,23 +32,22 @@ public class EntityCollideWithEntityEvent extends Event implements Cancellable {
|
||||
*
|
||||
* @return entities that are involved in this event
|
||||
*/
|
||||
public @NotNull List<Entity> getEntities() {
|
||||
return entities;
|
||||
public List<Entity> getEntities() {
|
||||
return this.entities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull HandlerList getHandlers() {
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.function.Predicate;
|
||||
import org.bukkit.Keyed;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.ItemType;
|
||||
import org.bukkit.inventory.RecipeChoice;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -41,10 +42,12 @@ public final class PotionMix implements Keyed {
|
||||
*
|
||||
* @param stackPredicate a predicate for an itemstack.
|
||||
* @return a new RecipeChoice
|
||||
* @deprecated use {@link RecipeChoice#predicateChoice(Predicate, ItemStack)}
|
||||
*/
|
||||
@Contract(value = "_ -> new", pure = true)
|
||||
@Deprecated(since = "26.2")
|
||||
public static RecipeChoice createPredicateChoice(final Predicate<? super ItemStack> stackPredicate) {
|
||||
return new PredicateRecipeChoice(stackPredicate);
|
||||
return RecipeChoice.predicateChoice(stackPredicate, ItemType.STONE.createItemStack());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1367,11 +1367,7 @@ public final class Bukkit {
|
||||
* Sets the radius, in blocks, around each worlds spawn point to protect.
|
||||
*
|
||||
* @param value new spawn radius, or 0 if none
|
||||
* @deprecated has not functioned for a long time as the spawn radius is defined by the server.properties file.
|
||||
* There is no API replacement for this method. It is generally recommended to implement "protection"-like behaviour
|
||||
* via events or third-party plugin APIs.
|
||||
*/
|
||||
@Deprecated(since = "1.21.4", forRemoval = true)
|
||||
public static void setSpawnRadius(int value) {
|
||||
server.setSpawnRadius(value);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package org.bukkit.entity;
|
||||
|
||||
import com.destroystokyo.paper.event.entity.EntityAddToWorldEvent;
|
||||
import io.papermc.paper.entity.Bucketable;
|
||||
import io.papermc.paper.entity.Shearable;
|
||||
import io.papermc.paper.event.entity.EntityEquipmentChangedEvent;
|
||||
import io.papermc.paper.registry.RegistryAccess;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
import io.papermc.paper.registry.keys.tags.ItemTypeTagKeys;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.key.KeyPattern;
|
||||
import org.bukkit.Keyed;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
/**
|
||||
@@ -73,6 +78,58 @@ public interface SulfurCube extends AbstractCubeMob, Shearable, Bucketable, Agea
|
||||
return this.ignite(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes this sulfur cube swallow the provided item, following any Vanilla logic.
|
||||
* <p>
|
||||
* This method will:
|
||||
* <ul>
|
||||
* <li>not equip the item to a baby sulfur cube,</li>
|
||||
* <li>if present, drop a previously swallowed item, and</li>
|
||||
* <li>play the swallow sound.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* If the currently swallowed item is changed, a {@link EntityEquipmentChangedEvent} is called.
|
||||
* May also call a {@link EntityAddToWorldEvent} for the newly dropped {@link Item} entity.
|
||||
*
|
||||
* @param itemStack the item to swallow. Use {@link ItemStack#empty()} to unset the item.
|
||||
* Items not in the {@link ItemTypeTagKeys#SULFUR_CUBE_SWALLOWABLE} tag
|
||||
* will not be properly rendered inside the sulfur cube
|
||||
* @return whether the sulfur cube's absorbed item was updated
|
||||
* @see #setEquipped(ItemStack) set the swallowed item, skipping any Vanilla swallow logic
|
||||
*/
|
||||
boolean swallow(ItemStack itemStack);
|
||||
|
||||
/**
|
||||
* Sets the swallowed item stack for this sulfur cube.
|
||||
* <p>
|
||||
* This method acts as a simple utility method to set the {@link EquipmentSlot#BODY}
|
||||
* equipment slot of this sulfur cube, which holds the sulfur cube's swallowed item.
|
||||
* <p>
|
||||
* Different to {@link #swallow(ItemStack)}, this method does not play a sound or
|
||||
* drop the previously equipped item on the ground.
|
||||
* <p>
|
||||
* This method will call a {@link EntityEquipmentChangedEvent}.
|
||||
*
|
||||
* @param itemStack the item stack to be equipped, use {@link ItemStack#empty()} to unset the item
|
||||
* @see #swallow(ItemStack) set the swallowed item, following any Vanilla swallow logic
|
||||
*/
|
||||
default void setEquipped(final ItemStack itemStack) {
|
||||
this.getEquipment().setItem(EquipmentSlot.BODY, itemStack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item stack currently swallowed by this sulfur cube.
|
||||
* <p>
|
||||
* This method acts as a simple utility method to get the {@link EquipmentSlot#BODY}
|
||||
* equipment slot of this sulfur cube, which holds the sulfur cube's swallowed item.
|
||||
*
|
||||
* @return the item stack in the {@link EquipmentSlot#BODY} equipment slot, returns {@link ItemStack#empty()}
|
||||
* if no item is present
|
||||
*/
|
||||
default ItemStack getEquipped() {
|
||||
return this.getEquipment().getItem(EquipmentSlot.BODY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the archetype of a sulfur cube
|
||||
* which define a lot of possible behavior and interaction
|
||||
|
||||
@@ -15,7 +15,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
*/
|
||||
public abstract class Event {
|
||||
|
||||
private String name;
|
||||
private final boolean isAsync;
|
||||
|
||||
/**
|
||||
@@ -60,10 +59,7 @@ public abstract class Event {
|
||||
*/
|
||||
@NotNull
|
||||
public String getEventName() {
|
||||
if (this.name == null) {
|
||||
this.name = this.getClass().getSimpleName();
|
||||
}
|
||||
return this.name;
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.bukkit.inventory;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.util.function.Predicate;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
record PredicateRecipeChoiceImpl(Predicate<? super ItemStack> stackPredicate, ItemStack exampleStack) implements RecipeChoice.PredicateChoice {
|
||||
|
||||
public PredicateRecipeChoiceImpl {
|
||||
Preconditions.checkArgument(stackPredicate != null, "The item predicate cannot be null");
|
||||
Preconditions.checkArgument(exampleStack != null, "The example stack cannot be null");
|
||||
Preconditions.checkArgument(!exampleStack.isEmpty(), "Cannot have empty/air example stack");
|
||||
|
||||
exampleStack = exampleStack.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getItemStack() {
|
||||
return this.exampleStack.clone();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"MethodDoesntCallSuperMethod", "FunctionalExpressionCanBeFolded"})
|
||||
@Override
|
||||
public PredicateRecipeChoiceImpl clone() {
|
||||
return new PredicateRecipeChoiceImpl(this.stackPredicate::test, this.exampleStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(final ItemStack itemStack) {
|
||||
return this.stackPredicate.test(itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeChoice validate(final boolean allowEmptyRecipes) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,53 @@ public interface RecipeChoice extends Predicate<ItemStack>, Cloneable {
|
||||
return new ItemTypeRecipeChoiceImpl(itemTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a choice that will be valid only if one of the stacks is
|
||||
* exactly matched (aside from stack size).
|
||||
*
|
||||
* @param first an ItemStack to match against.
|
||||
* Cannot be null or empty/air.
|
||||
* @param others additional ItemStacks to match against.
|
||||
* @return a new ExactChoice
|
||||
*/
|
||||
@Contract(value = "_, _ -> new", pure = true)
|
||||
static ExactChoice exactChoice(ItemStack first, ItemStack... others) {
|
||||
List<ItemStack> stacks = new ArrayList<>(others.length + 1);
|
||||
stacks.add(first);
|
||||
Collections.addAll(stacks, others);
|
||||
return new ExactChoice(stacks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a choice that will be valid only if one of the stacks is
|
||||
* exactly matched (aside from stack size).
|
||||
*
|
||||
* @param stacks the ItemStacks to match against.
|
||||
* Cannot be empty or contain empty/air stacks.
|
||||
* @return a new ExactChoice
|
||||
*/
|
||||
@Contract(value = "_ -> new", pure = true)
|
||||
static ExactChoice exactChoice(List<ItemStack> stacks) {
|
||||
return new ExactChoice(stacks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a recipe choice that will be valid only if an item matches the
|
||||
* given predicate.
|
||||
* <p>
|
||||
* <b>Note:</b> Mutating the {@link ItemStack} within the predicate is not
|
||||
* supported.
|
||||
*
|
||||
* @param stackPredicate the predicate to match against.
|
||||
* @param exampleStack an example {@link ItemStack} to be shown in the
|
||||
* recipe book. Cannot be empty or air.
|
||||
* @return a new PredicateChoice
|
||||
*/
|
||||
@Contract(value = "_, _ -> new", pure = true)
|
||||
static PredicateChoice predicateChoice(Predicate<? super ItemStack> stackPredicate, ItemStack exampleStack) {
|
||||
return new PredicateRecipeChoiceImpl(stackPredicate, exampleStack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a single item stack representative of this stack choice.
|
||||
*
|
||||
@@ -228,14 +275,26 @@ public interface RecipeChoice extends Predicate<ItemStack>, Cloneable {
|
||||
|
||||
private List<ItemStack> choices;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link RecipeChoice#exactChoice(ItemStack, ItemStack...)} instead
|
||||
*/
|
||||
@Deprecated(since = "26.2", forRemoval = true)
|
||||
public ExactChoice(ItemStack stack) {
|
||||
this(Arrays.asList(stack));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link RecipeChoice#exactChoice(ItemStack, ItemStack...)} instead
|
||||
*/
|
||||
@Deprecated(since = "26.2", forRemoval = true)
|
||||
public ExactChoice(ItemStack... stacks) {
|
||||
this(Arrays.asList(stacks));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link RecipeChoice#exactChoice(List)} instead
|
||||
*/
|
||||
@Deprecated(since = "26.2", forRemoval = true)
|
||||
public ExactChoice(List<ItemStack> choices) {
|
||||
Preconditions.checkArgument(choices != null, "choices");
|
||||
Preconditions.checkArgument(!choices.isEmpty(), "Must have at least one choice");
|
||||
@@ -341,4 +400,13 @@ public interface RecipeChoice extends Predicate<ItemStack>, Cloneable {
|
||||
*/
|
||||
RegistryKeySet<ItemType> itemTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a choice that will be valid only if an item matches the
|
||||
* given predicate.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
interface PredicateChoice extends RecipeChoice {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -180,7 +180,7 @@ public class ShapedRecipe extends CraftingRecipe {
|
||||
@NotNull
|
||||
public ShapedRecipe setIngredient(char key, @NotNull ItemStack item) {
|
||||
Preconditions.checkArgument(!item.getType().isAir(), "Item cannot be air"); // Paper
|
||||
return setIngredient(key, new RecipeChoice.ExactChoice(item.clone())); // Paper
|
||||
return setIngredient(key, RecipeChoice.exactChoice(item.clone())); // Paper
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -143,7 +143,7 @@ public class ShapelessRecipe extends CraftingRecipe {
|
||||
Preconditions.checkArgument(!item.getType().isAir(), "Item cannot be air"); // Paper
|
||||
item = item.clone(); // Paper
|
||||
while (count-- > 0) {
|
||||
this.ingredients.add(new RecipeChoice.ExactChoice(item));
|
||||
this.ingredients.add(RecipeChoice.exactChoice(item));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ plugins {
|
||||
`maven-publish`
|
||||
idea
|
||||
id("io.papermc.paperweight.core")
|
||||
id("io.papermc.fill.gradle") version "1.0.12"
|
||||
id("io.papermc.fill.gradle") version "1.0.14"
|
||||
}
|
||||
|
||||
val paperMavenPublicUrl = "https://repo.papermc.io/repository/maven-public/"
|
||||
@@ -110,7 +110,7 @@ abstract class MockitoAgentProvider : CommandLineArgumentProvider {
|
||||
|
||||
dependencies {
|
||||
implementation(project(":paper-api"))
|
||||
implementation("ca.spottedleaf:leafpile:1.0.0")
|
||||
implementation("ca.spottedleaf:leafpile:1.2.0")
|
||||
implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+
|
||||
implementation("org.jline:jline-terminal-jni:3.27.1") // fall back to jni on java 21
|
||||
implementation("net.minecrell:terminalconsoleappender:1.3.0")
|
||||
@@ -127,7 +127,7 @@ dependencies {
|
||||
runtimeOnly(log4jPlugins.output)
|
||||
alsoShade(log4jPlugins.output)
|
||||
|
||||
implementation("com.velocitypowered:velocity-native:3.4.0-SNAPSHOT") {
|
||||
implementation("com.velocitypowered:velocity-native:4.1.0") {
|
||||
isTransitive = false
|
||||
}
|
||||
implementation("io.netty:netty-codec-haproxy:4.2.15.Final") // Add support for proxy protocol
|
||||
@@ -154,12 +154,9 @@ dependencies {
|
||||
testImplementation("org.ow2.asm:asm-tree:9.9.1")
|
||||
testImplementation("org.junit-pioneer:junit-pioneer:2.3.0") // CartesianTest
|
||||
|
||||
// Bytecode framework
|
||||
implementation("io.papermc:asm-utils:0.0.4-SNAPSHOT")
|
||||
|
||||
// Spark
|
||||
implementation("me.lucko:spark-api:0.1-20240720.200737-2")
|
||||
implementation("me.lucko:spark-paper:1.10.172")
|
||||
implementation("me.lucko:spark-paper:1.10.180")
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
|
||||
@@ -15996,10 +15996,10 @@ index 0000000000000000000000000000000000000000..1414f25ef770b0fe73ea618a450ade2f
|
||||
+}
|
||||
diff --git a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..15568f598b8c61cf92563b6a7f8cae526b84484e
|
||||
index 0000000000000000000000000000000000000000..54b2492c3ab6bf9ff52cec53dc4635f725f9eb4f
|
||||
--- /dev/null
|
||||
+++ b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
|
||||
@@ -0,0 +1,2199 @@
|
||||
@@ -0,0 +1,2200 @@
|
||||
+package ca.spottedleaf.moonrise.patches.collisions;
|
||||
+
|
||||
+import ca.spottedleaf.moonrise.common.util.WorldUtil;
|
||||
@@ -18048,6 +18048,7 @@ index 0000000000000000000000000000000000000000..15568f598b8c61cf92563b6a7f8cae52
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ // TODO perform intersection check using moved aabb instead of moved blockCollision so we can avoid the call?
|
||||
+ final VoxelShape blockCollisionOffset = blockCollision.move((double)blockX, (double)blockY, (double)blockZ);
|
||||
+
|
||||
+ if (!voxelShapeIntersectNoEmpty(blockCollisionOffset, aabb)) {
|
||||
@@ -27402,7 +27403,7 @@ index f11c5ea1ee9381e7bf65305b959bd7da67423ece..26822e1211194b411dd8ea75ce523133
|
||||
List<Entity> passengers = this.entity.getPassengers();
|
||||
if (!passengers.equals(this.lastPassengers)) {
|
||||
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
|
||||
index 473feb1aa6ccf0f03ce0bc895de367b3e554e1bc..e5328f08d6a323aa1307ed791a88916b4588939c 100644
|
||||
index 473feb1aa6ccf0f03ce0bc895de367b3e554e1bc..4fce291cb7cd6eb2f4e47237fe247a5d666da0ad 100644
|
||||
--- a/net/minecraft/server/level/ServerLevel.java
|
||||
+++ b/net/minecraft/server/level/ServerLevel.java
|
||||
@@ -182,7 +182,7 @@ import net.minecraft.world.waypoints.WaypointTransmitter;
|
||||
@@ -27625,7 +27626,13 @@ index 473feb1aa6ccf0f03ce0bc895de367b3e554e1bc..e5328f08d6a323aa1307ed791a88916b
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -340,16 +476,135 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
|
||||
@@ -335,21 +471,140 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
|
||||
for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
|
||||
for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
|
||||
ca.spottedleaf.moonrise.common.PlatformHooks.get().scheduleChunkLoad(
|
||||
- this, cx, cz, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true, priority, consumer
|
||||
+ this, cx, cz, chunkStatus, true, priority, consumer
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28885,7 +28892,7 @@ index 3271c83bd103bea9b9ffcd6882b52896ea4da770..f6601bc30fa37a309923ea9883b04233
|
||||
PrepareSpawnTask.this.loadListener.updateFocus(this.spawnLevel.dimension(), spawnChunk);
|
||||
}
|
||||
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
|
||||
index b4845731ba1eaae582afece7e09e2af427b29d6a..1dbb1d49c797be89f3739c2fa226aef25f68ee87 100644
|
||||
index 7e311382fb265f49b1f079a3d9ffa7f7e6fde67c..ea16325d61c8bf8c8dba161c16207551bffc2aaf 100644
|
||||
--- a/net/minecraft/server/players/PlayerList.java
|
||||
+++ b/net/minecraft/server/players/PlayerList.java
|
||||
@@ -982,8 +982,8 @@ public abstract class PlayerList {
|
||||
@@ -28899,7 +28906,7 @@ index b4845731ba1eaae582afece7e09e2af427b29d6a..1dbb1d49c797be89f3739c2fa226aef2
|
||||
// Paper end - view distances
|
||||
if (level.isRaining()) {
|
||||
// CraftBukkit start - handle player weather
|
||||
@@ -1200,7 +1200,7 @@ public abstract class PlayerList {
|
||||
@@ -1203,7 +1203,7 @@ public abstract class PlayerList {
|
||||
|
||||
public void setViewDistance(final int viewDistance) {
|
||||
this.viewDistance = viewDistance;
|
||||
@@ -28908,7 +28915,7 @@ index b4845731ba1eaae582afece7e09e2af427b29d6a..1dbb1d49c797be89f3739c2fa226aef2
|
||||
|
||||
for (ServerLevel level : this.server.getAllLevels()) {
|
||||
level.getChunkSource().setViewDistance(viewDistance);
|
||||
@@ -1209,7 +1209,7 @@ public abstract class PlayerList {
|
||||
@@ -1212,7 +1212,7 @@ public abstract class PlayerList {
|
||||
|
||||
public void setSimulationDistance(final int simulationDistance) {
|
||||
this.simulationDistance = simulationDistance;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+7
-7
@@ -28,7 +28,7 @@ and then catch exceptions and close if they fire.
|
||||
Part of this commit was authored by: Spottedleaf, sandtechnology
|
||||
|
||||
diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
|
||||
index 709f2c6b47f78920cd9432da4c75e0d1ab2bffbc..3f10ea62d2ef70e7e4f01c83942cd9051249954c 100644
|
||||
index f46c55fd212ea68386342bc9ce71c04108758a8e..0b65c06e43fd22b2c30d9b8c5ef5b5e99301cec1 100644
|
||||
--- a/net/minecraft/network/Connection.java
|
||||
+++ b/net/minecraft/network/Connection.java
|
||||
@@ -65,7 +65,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
@@ -111,7 +111,7 @@ index 709f2c6b47f78920cd9432da4c75e0d1ab2bffbc..3f10ea62d2ef70e7e4f01c83942cd905
|
||||
this.flushQueue();
|
||||
}
|
||||
}
|
||||
@@ -444,21 +475,42 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
@@ -461,21 +492,42 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
}
|
||||
|
||||
private void doSendPacket(final Packet<?> packet, final @Nullable ChannelFutureListener listener, final boolean flush) {
|
||||
@@ -158,7 +158,7 @@ index 709f2c6b47f78920cd9432da4c75e0d1ab2bffbc..3f10ea62d2ef70e7e4f01c83942cd905
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,16 +522,57 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
@@ -487,16 +539,57 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ index 709f2c6b47f78920cd9432da4c75e0d1ab2bffbc..3f10ea62d2ef70e7e4f01c83942cd905
|
||||
|
||||
private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world
|
||||
private static int joinAttemptsThisTick; // Paper - Buffer joins to world
|
||||
@@ -549,6 +642,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
@@ -566,6 +659,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
|
||||
public void disconnect(final DisconnectionDetails details) {
|
||||
this.preparing = false; // Spigot
|
||||
@@ -229,7 +229,7 @@ index 709f2c6b47f78920cd9432da4c75e0d1ab2bffbc..3f10ea62d2ef70e7e4f01c83942cd905
|
||||
if (this.channel == null) {
|
||||
this.delayedDisconnect = details;
|
||||
}
|
||||
@@ -738,7 +832,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
@@ -755,7 +849,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
public void handleDisconnection() {
|
||||
if (this.channel != null && !this.channel.isOpen()) {
|
||||
if (this.disconnectionHandled) {
|
||||
@@ -238,7 +238,7 @@ index 709f2c6b47f78920cd9432da4c75e0d1ab2bffbc..3f10ea62d2ef70e7e4f01c83942cd905
|
||||
} else {
|
||||
this.disconnectionHandled = true;
|
||||
PacketListener packetListener = this.getPacketListener();
|
||||
@@ -749,7 +843,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
@@ -766,7 +860,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
);
|
||||
disconnectListener.onDisconnect(details);
|
||||
}
|
||||
@@ -247,7 +247,7 @@ index 709f2c6b47f78920cd9432da4c75e0d1ab2bffbc..3f10ea62d2ef70e7e4f01c83942cd905
|
||||
// Paper start - Add PlayerConnectionCloseEvent
|
||||
if (packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl commonPacketListener) {
|
||||
/* Player was logged in, either game listener or configuration listener */
|
||||
@@ -792,4 +886,96 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
@@ -809,4 +903,96 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
public @Nullable UUID getIntendedProfileId() {
|
||||
return this.intendedProfileId;
|
||||
}
|
||||
|
||||
+6
-6
@@ -267,10 +267,10 @@ index 595db570b0ade9cad7de77d2ab7b10d9e68699df..ac095f59d9f17d8239d6493d1c1e3942
|
||||
+ // Paper end - Use Velocity cipher
|
||||
}
|
||||
diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
|
||||
index 3f10ea62d2ef70e7e4f01c83942cd9051249954c..9a04309584a5b9b55d0d383619113835190ad8f9 100644
|
||||
index 0b65c06e43fd22b2c30d9b8c5ef5b5e99301cec1..9fbc7e93712672dbab67f931e2aee447d803607b 100644
|
||||
--- a/net/minecraft/network/Connection.java
|
||||
+++ b/net/minecraft/network/Connection.java
|
||||
@@ -768,10 +768,24 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
@@ -785,10 +785,24 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
return connection;
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ index 3f10ea62d2ef70e7e4f01c83942cd9051249954c..9a04309584a5b9b55d0d383619113835
|
||||
|
||||
public boolean isConnected() {
|
||||
return this.channel != null && this.channel.isOpen();
|
||||
@@ -805,16 +819,17 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
@@ -822,16 +836,17 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
|
||||
public void setupCompression(final int threshold, final boolean validateDecompressed) {
|
||||
if (threshold >= 0) {
|
||||
@@ -320,7 +320,7 @@ index 3f10ea62d2ef70e7e4f01c83942cd9051249954c..9a04309584a5b9b55d0d383619113835
|
||||
this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - Add Channel initialization listeners
|
||||
} else {
|
||||
diff --git a/net/minecraft/server/network/ServerConnectionListener.java b/net/minecraft/server/network/ServerConnectionListener.java
|
||||
index 1a60071f32a0c92c69f60c75035e5324aa144fb4..e84e243f35b09691d03e9a9cf3de0c093b1d5cfb 100644
|
||||
index fa2467ff885e039f861bb9b1ae076a5a64b891ed..8a2f1ebcf06bb0c1e8b352652de69b6c1d74daf3 100644
|
||||
--- a/net/minecraft/server/network/ServerConnectionListener.java
|
||||
+++ b/net/minecraft/server/network/ServerConnectionListener.java
|
||||
@@ -80,6 +80,10 @@ public class ServerConnectionListener {
|
||||
@@ -335,10 +335,10 @@ index 1a60071f32a0c92c69f60c75035e5324aa144fb4..e84e243f35b09691d03e9a9cf3de0c09
|
||||
.add(
|
||||
new ServerBootstrap()
|
||||
diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
|
||||
index d6b6c6e7dce618c3f4aecc0969b7423771eab3ba..b94e01aa54b932b29370d1742b00ed73254fe4dc 100644
|
||||
index 24bcd1067db20fc09b87b6e300615e3fa590049a..1d892a8c905496af9d56cec86941e82ec48505db 100644
|
||||
--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
|
||||
+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
|
||||
@@ -257,9 +257,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
|
||||
@@ -259,9 +259,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
|
||||
SecretKey secretKey = packet.getSecretKey(serverPrivateKey);
|
||||
digest = new BigInteger(Crypt.digestData("", this.server.getKeyPair().getPublic(), secretKey)).toString(16);
|
||||
this.state = ServerLoginPacketListenerImpl.State.AUTHENTICATING;
|
||||
|
||||
@@ -78,10 +78,10 @@ index 0000000000000000000000000000000000000000..9e621873454cc2edc2e59e6c7554d811
|
||||
+}
|
||||
diff --git a/io/papermc/paper/inventory/recipe/StackedContentsExtrasMap.java b/io/papermc/paper/inventory/recipe/StackedContentsExtrasMap.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..f47c12e9dd6cfa857ca07a764edc22de372e25b6
|
||||
index 0000000000000000000000000000000000000000..a9dcccefb19e6610076e1153547d2f9156042313
|
||||
--- /dev/null
|
||||
+++ b/io/papermc/paper/inventory/recipe/StackedContentsExtrasMap.java
|
||||
@@ -0,0 +1,68 @@
|
||||
@@ -0,0 +1,81 @@
|
||||
+package io.papermc.paper.inventory.recipe;
|
||||
+
|
||||
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
@@ -94,12 +94,16 @@ index 0000000000000000000000000000000000000000..f47c12e9dd6cfa857ca07a764edc22de
|
||||
+import net.minecraft.world.item.crafting.CraftingInput;
|
||||
+import net.minecraft.world.item.crafting.Ingredient;
|
||||
+import net.minecraft.world.item.crafting.Recipe;
|
||||
+import java.util.ArrayList;
|
||||
+import java.util.List;
|
||||
+import java.util.function.Predicate;
|
||||
+
|
||||
+public final class StackedContentsExtrasMap {
|
||||
+
|
||||
+ private final StackedContents<ItemOrExact> contents;
|
||||
+ public Object2IntMap<ItemOrExact.Item> regularRemoved = new Object2IntOpenHashMap<>(); // needed for re-using the regular contents (for ShapelessRecipe)
|
||||
+ public final ObjectSet<ItemStack> exactIngredients = new ObjectOpenCustomHashSet<>(ItemStackLinkedSet.TYPE_AND_TAG);
|
||||
+ public final List<Predicate<ItemStack>> predicateIngredients = new ArrayList<>();
|
||||
+
|
||||
+ public StackedContentsExtrasMap(final StackedContents<ItemOrExact> contents) {
|
||||
+ this.contents = contents;
|
||||
@@ -107,8 +111,11 @@ index 0000000000000000000000000000000000000000..f47c12e9dd6cfa857ca07a764edc22de
|
||||
+
|
||||
+ public void initialize(final Recipe<?> recipe) {
|
||||
+ this.exactIngredients.clear();
|
||||
+ this.predicateIngredients.clear();
|
||||
+ for (final Ingredient ingredient : recipe.placementInfo().ingredients()) {
|
||||
+ if (ingredient.isExact()) {
|
||||
+ if (ingredient.stackPredicate != null) {
|
||||
+ this.predicateIngredients.add(ingredient.stackPredicate);
|
||||
+ } else if (ingredient.isExact()) {
|
||||
+ this.exactIngredients.addAll(ingredient.itemStacks());
|
||||
+ }
|
||||
+ }
|
||||
@@ -138,11 +145,17 @@ index 0000000000000000000000000000000000000000..f47c12e9dd6cfa857ca07a764edc22de
|
||||
+ for (final Object2IntMap.Entry<ItemOrExact.Item> entry : this.regularRemoved.object2IntEntrySet()) {
|
||||
+ this.contents.amounts.addTo(entry.getKey(), entry.getIntValue());
|
||||
+ }
|
||||
+ this.predicateIngredients.clear();
|
||||
+ this.exactIngredients.clear();
|
||||
+ this.regularRemoved.clear();
|
||||
+ }
|
||||
+
|
||||
+ public boolean accountStack(final ItemStack stack, final int count) {
|
||||
+ for (Predicate<ItemStack> stackPredicate : this.predicateIngredients) {
|
||||
+ if (!stackPredicate.test(stack)) continue;
|
||||
+ this.contents.account(new ItemOrExact.Exact(stack), count);
|
||||
+ return true;
|
||||
+ }
|
||||
+ if (this.exactIngredients.contains(stack)) {
|
||||
+ this.contents.account(new ItemOrExact.Exact(stack), count);
|
||||
+ return true;
|
||||
@@ -344,7 +357,7 @@ index ffaa8e450a11a01060b6847d1b66a76323f9d8ba..1ace17d8fc7e7b8b4722a1889d49233c
|
||||
}
|
||||
|
||||
diff --git a/net/minecraft/world/item/crafting/Ingredient.java b/net/minecraft/world/item/crafting/Ingredient.java
|
||||
index 1a6870fd5dd02022d6b0df88e8f55f8ba9cf07c9..4f3697573fbfec3865751037de00a562ad81cddb 100644
|
||||
index 218d56fa066513fd8d9761af36bcc2873a34406b..778b38911b0d244738fd3db3b275b0c78a17339f 100644
|
||||
--- a/net/minecraft/world/item/crafting/Ingredient.java
|
||||
+++ b/net/minecraft/world/item/crafting/Ingredient.java
|
||||
@@ -22,7 +22,7 @@ import net.minecraft.world.item.Items;
|
||||
@@ -356,9 +369,9 @@ index 1a6870fd5dd02022d6b0df88e8f55f8ba9cf07c9..4f3697573fbfec3865751037de00a562
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, Ingredient> CONTENTS_STREAM_CODEC = ByteBufCodecs.holderSet(Registries.ITEM)
|
||||
.map(Ingredient::new, i -> i.values);
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, Optional<Ingredient>> OPTIONAL_CONTENTS_STREAM_CODEC = ByteBufCodecs.holderSet(Registries.ITEM)
|
||||
@@ -34,19 +34,23 @@ public final class Ingredient implements Predicate<ItemStack>, StackedContents.I
|
||||
public static final Codec<Ingredient> CODEC = ExtraCodecs.nonEmptyHolderSet(NON_AIR_HOLDER_SET_CODEC).xmap(Ingredient::new, i -> i.values);
|
||||
@@ -35,19 +35,23 @@ public final class Ingredient implements Predicate<ItemStack>, StackedContents.I
|
||||
public final HolderSet<Item> values;
|
||||
public java.util.function.@org.jspecify.annotations.Nullable Predicate<ItemStack> stackPredicate; // Paper - add PredicateChoice
|
||||
// CraftBukkit start
|
||||
- private java.util.@org.jspecify.annotations.Nullable List<ItemStack> itemStacks;
|
||||
+ private java.util.@org.jspecify.annotations.Nullable Set<ItemStack> itemStacks; // Paper - Improve exact choice recipe ingredients
|
||||
@@ -383,8 +396,8 @@ index 1a6870fd5dd02022d6b0df88e8f55f8ba9cf07c9..4f3697573fbfec3865751037de00a562
|
||||
return ingredient;
|
||||
}
|
||||
// CraftBukkit end
|
||||
@@ -81,21 +85,22 @@ public final class Ingredient implements Predicate<ItemStack>, StackedContents.I
|
||||
public boolean test(final ItemStack input) {
|
||||
@@ -87,21 +91,32 @@ public final class Ingredient implements Predicate<ItemStack>, StackedContents.I
|
||||
// Paper end - add PredicateChoice
|
||||
// CraftBukkit start
|
||||
if (this.isExact()) {
|
||||
- for (ItemStack item : this.itemStacks()) {
|
||||
@@ -408,14 +421,24 @@ index 1a6870fd5dd02022d6b0df88e8f55f8ba9cf07c9..4f3697573fbfec3865751037de00a562
|
||||
+ return switch (itemOrExact) {
|
||||
+ case io.papermc.paper.inventory.recipe.ItemOrExact.Item(final Holder<Item> item) ->
|
||||
+ !this.isExact() && this.values.contains(item);
|
||||
+ case io.papermc.paper.inventory.recipe.ItemOrExact.Exact(final ItemStack exact) ->
|
||||
+ this.isExact() && this.itemStacks.contains(exact);
|
||||
+ case io.papermc.paper.inventory.recipe.ItemOrExact.Exact(final ItemStack exact) -> {
|
||||
+ if (this.stackPredicate != null) {
|
||||
+ yield this.stackPredicate.test(exact);
|
||||
+ }
|
||||
+ if (this.isExact()) {
|
||||
+ yield this.itemStacks.contains(exact);
|
||||
+ }
|
||||
+ // A plain, usable item can be routed to the Exact pool because it also matched a
|
||||
+ // predicate/exact ingredient in the same recipe; a regular ingredient must still be
|
||||
+ // able to draw from that shared pool (the picker's per-key count keeps it double-count free).
|
||||
+ yield net.minecraft.world.entity.player.Inventory.isUsableForCrafting(exact) && exact.is(this.values);
|
||||
+ }
|
||||
+ };
|
||||
+ // Paper end - Improve exact choice recipe ingredients
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,6 +130,11 @@ public final class Ingredient implements Predicate<ItemStack>, StackedContents.I
|
||||
@@ -131,6 +146,11 @@ public final class Ingredient implements Predicate<ItemStack>, StackedContents.I
|
||||
}
|
||||
|
||||
public SlotDisplay display() {
|
||||
|
||||
@@ -4,43 +4,6 @@ Date: Sat, 7 Jun 2025 15:05:05 -0400
|
||||
Subject: [PATCH] DataConverter, Moonrise & co fixes
|
||||
|
||||
|
||||
diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractOldAttributesRename.java b/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractOldAttributesRename.java
|
||||
index 1a6d02df13f3461402fed429f7fbdb5acee877c4..712cca21a6b4736a15d49dceaf7e6d641096b0a9 100644
|
||||
--- a/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractOldAttributesRename.java
|
||||
+++ b/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractOldAttributesRename.java
|
||||
@@ -38,7 +38,13 @@ public final class ConverterAbstractOldAttributesRename {
|
||||
MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(version, versionStep) {
|
||||
@Override
|
||||
public MapType convert(final MapType data, final long sourceVersion, final long toVersion) {
|
||||
- final ListType attributes = data.getList("AttributeModifiers", ObjectType.MAP);
|
||||
+ final MapType tag = data.getMap("tag");
|
||||
+
|
||||
+ if (tag == null) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ final ListType attributes = tag.getList("AttributeModifiers", ObjectType.MAP);
|
||||
|
||||
if (attributes == null) {
|
||||
return null;
|
||||
diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4307.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4307.java
|
||||
index d2877c20f389d0131e1dd208b464f590671e5d82..27bdc70d861ca39487ad16cb3afb89d604b462c8 100644
|
||||
--- a/ca/spottedleaf/dataconverter/minecraft/versions/V4307.java
|
||||
+++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4307.java
|
||||
@@ -87,10 +87,10 @@ public final class V4307 {
|
||||
|
||||
@Override
|
||||
public MapType convert(final MapType root, final long sourceVersion, final long toVersion) {
|
||||
- final Set<String> hiddenComponents = new LinkedHashSet<>();
|
||||
-
|
||||
- unwrapBlockPredicates(root, "minecraft:can_break", hiddenComponents);
|
||||
+ // Don't use a linked hash set, and ensure that it is added in the same (undefined) order as the vanilla datafixer
|
||||
+ final Set<String> hiddenComponents = new java.util.HashSet<>();
|
||||
unwrapBlockPredicates(root, "minecraft:can_place_on", hiddenComponents);
|
||||
+ unwrapBlockPredicates(root, "minecraft:can_break", hiddenComponents);
|
||||
|
||||
updateComponent(root, "minecraft:trim", hiddenComponents);
|
||||
updateComponent(root, "minecraft:unbreakable", hiddenComponents);
|
||||
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
|
||||
index 3b28b39ebf252d8de46f7d0ce27037cdb041f1b5..0b6d7f99edde802cb464baa5a4b2b4a4dcce684a 100644
|
||||
--- a/net/minecraft/server/dedicated/DedicatedServer.java
|
||||
|
||||
+3
-3
@@ -5,16 +5,16 @@ Subject: [PATCH] Call AsyncServerDataFixerRemoveBlockEntityEvent from DC
|
||||
|
||||
|
||||
diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRemove.java b/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRemove.java
|
||||
index a74821562154c99ee3a8571ff8dbf8fe5eacf416..8d47addc3cf6d0c551809463992da8551c2835f6 100644
|
||||
index 6aef3311ca59220e8d7c97a4a5c1f14e96d08117..e70408061c8a495af319c810ee93d51a2644e465 100644
|
||||
--- a/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRemove.java
|
||||
+++ b/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRemove.java
|
||||
@@ -61,8 +61,43 @@ public final class ConverterAbstractTileEntityRemove {
|
||||
@@ -63,8 +63,43 @@ public final class ConverterAbstractTileEntityRemove {
|
||||
return null;
|
||||
}
|
||||
|
||||
+ // Paper start - call event on block entity removal
|
||||
+ final java.util.function.Supplier<net.kyori.adventure.key.Key> worldKeySupplier = com.google.common.base.Suppliers.memoize(() -> {
|
||||
+ final ca.spottedleaf.dataconverter.types.MapType context = data.getMap(net.minecraft.util.datafix.fixes.ChunkHeightAndBiomeFix.DATAFIXER_CONTEXT_TAG);
|
||||
+ final MapType context = data.getMap(net.minecraft.util.datafix.fixes.ChunkHeightAndBiomeFix.DATAFIXER_CONTEXT_TAG);
|
||||
+ if (context == null) return null;
|
||||
+
|
||||
+ final @org.intellij.lang.annotations.Subst("minecraft:overworld") String levelIdentifier = context.getString("level_identifier");
|
||||
|
||||
+4
-2
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java
|
||||
+++ b/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java
|
||||
@@ -27,17 +_,36 @@
|
||||
@@ -27,17 +_,38 @@
|
||||
ServerLevel level = source.level();
|
||||
Direction direction = source.state().getValue(DispenserBlock.FACING);
|
||||
Position position = this.dispenseConfig.positionFunction().getDispensePosition(source, direction);
|
||||
@@ -40,8 +40,10 @@
|
||||
+
|
||||
+ // SPIGOT-7923: Avoid create projectiles with empty item
|
||||
+ if (!singleItemStack.isEmpty()) {
|
||||
+ Projectile projectile = Projectile.spawnProjectileUsingShoot(this.projectileItem.asProjectile(level, position, org.bukkit.craftbukkit.inventory.CraftItemStack.unwrap(event.getItem()), direction), level, singleItemStack, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), this.dispenseConfig.power(), this.dispenseConfig.uncertainty()); // Paper - track changed items in the dispense event; unwrap is safe here because all uses of the stack make their own copies
|
||||
+ Projectile projectile = this.projectileItem.asProjectile(level, position, org.bukkit.craftbukkit.inventory.CraftItemStack.unwrap(event.getItem()), direction); // Paper - track changed items in the dispense event; unwrap is safe here because all uses of the stack make their own copies
|
||||
+ Projectile.Delayed<Projectile> delayedProjectile = Projectile.spawnProjectileUsingShootDelayed(projectile, level, singleItemStack, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), this.dispenseConfig.power(), this.dispenseConfig.uncertainty());
|
||||
+ projectile.projectileSource = new org.bukkit.craftbukkit.projectiles.CraftBlockProjectileSource(source.blockEntity());
|
||||
+ delayedProjectile.spawn();
|
||||
+ }
|
||||
+ if (shrink) dispensed.shrink(1);
|
||||
+ // CraftBukkit end
|
||||
|
||||
@@ -182,7 +182,7 @@
|
||||
} catch (RejectedExecutionException ignored) {
|
||||
this.disconnect(Component.translatable("multiplayer.disconnect.server_shutdown"));
|
||||
} catch (ClassCastException exception) {
|
||||
@@ -297,6 +_,30 @@
|
||||
@@ -297,6 +_,47 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
+ *
|
||||
+ * @see <a href="https://mojira.dev/MC-308621">MC-308621</a>
|
||||
+ */
|
||||
+ public void scheduleNettyTask(final Runnable task) {
|
||||
+ private void scheduleNettyTask(final Runnable task) {
|
||||
+ final boolean connected = this.isConnected();
|
||||
+ if (!connected && !this.preparing) {
|
||||
+ return;
|
||||
@@ -208,6 +208,23 @@
|
||||
+ this.flushQueue();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Schedules a task on the channel's event loop after all currently queued packets have been submitted for writing.
|
||||
+ * This allows the compression packet to be written and compression to be enabled in the same event-loop task.
|
||||
+ * Vanilla enables compression from a write-future listener, which may run after another packet has already been
|
||||
+ * queued without compression.
|
||||
+ *
|
||||
+ * @see <a href="https://mojira.dev/MC-308621">MC-308621</a>
|
||||
+ */
|
||||
+ public void sendPacketAndScheduleNettyTask(final Packet<?> packet, final boolean flush, final Runnable after) {
|
||||
+ this.scheduleNettyTask(() -> {
|
||||
+ Connection.this.sendPacket(packet, null, flush);
|
||||
+ if (after != null) {
|
||||
+ after.run();
|
||||
+ }
|
||||
+ });
|
||||
+ }
|
||||
+ // Paper end - Add netty task scheduling helper
|
||||
+
|
||||
private void sendPacket(final Packet<?> packet, final @Nullable ChannelFutureListener listener, final boolean flush) {
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
--- a/net/minecraft/network/protocol/game/GameProtocols.java
|
||||
+++ b/net/minecraft/network/protocol/game/GameProtocols.java
|
||||
@@ -37,8 +_,29 @@
|
||||
import net.minecraft.network.protocol.ping.ServerboundPingRequestPacket;
|
||||
|
||||
public class GameProtocols {
|
||||
+ // Paper start - check if game master
|
||||
+ public static final CodecModifier<RegistryFriendlyByteBuf, ServerboundTestInstanceBlockActionPacket, GameProtocols.Context> IS_GAME_MASTER = (original, context) -> new StreamCodec<>(
|
||||
+ ) {
|
||||
+ @Override
|
||||
+ public ServerboundTestInstanceBlockActionPacket decode(final RegistryFriendlyByteBuf input) {
|
||||
+ if (!context.canUseCommandBlocks()) {
|
||||
+ throw new SkipPacketDecoderException("Not a game master");
|
||||
+ } else {
|
||||
+ return original.decode(input);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void encode(final RegistryFriendlyByteBuf output, final ServerboundTestInstanceBlockActionPacket value) {
|
||||
+ if (!context.canUseCommandBlocks()) {
|
||||
+ throw new SkipPacketEncoderException("Not a game master");
|
||||
+ }
|
||||
+ original.encode(output, value);
|
||||
+ }
|
||||
+ };
|
||||
+ // Paper end - check if game master
|
||||
public static final CodecModifier<RegistryFriendlyByteBuf, ServerboundSetCreativeModeSlotPacket, GameProtocols.Context> HAS_INFINITE_MATERIALS = (original, context) -> new StreamCodec<RegistryFriendlyByteBuf, ServerboundSetCreativeModeSlotPacket>(
|
||||
-
|
||||
+
|
||||
) {
|
||||
@Override
|
||||
public ServerboundSetCreativeModeSlotPacket decode(final RegistryFriendlyByteBuf input) {
|
||||
@@ -125,7 +_,7 @@
|
||||
.addPacket(GamePacketTypes.SERVERBOUND_SPECTATOR_ACTION, ServerboundSpectatorActionPacket.STREAM_CODEC)
|
||||
.addPacket(GamePacketTypes.SERVERBOUND_SWING, ServerboundSwingPacket.STREAM_CODEC)
|
||||
.addPacket(GamePacketTypes.SERVERBOUND_TELEPORT_TO_ENTITY, ServerboundTeleportToEntityPacket.STREAM_CODEC)
|
||||
- .addPacket(GamePacketTypes.SERVERBOUND_TEST_INSTANCE_BLOCK_ACTION, ServerboundTestInstanceBlockActionPacket.STREAM_CODEC)
|
||||
+ .addPacket(GamePacketTypes.SERVERBOUND_TEST_INSTANCE_BLOCK_ACTION, ServerboundTestInstanceBlockActionPacket.STREAM_CODEC, IS_GAME_MASTER) // Paper - check if game master
|
||||
.addPacket(GamePacketTypes.SERVERBOUND_USE_ITEM_ON, ServerboundUseItemOnPacket.STREAM_CODEC)
|
||||
.addPacket(GamePacketTypes.SERVERBOUND_USE_ITEM, ServerboundUseItemPacket.STREAM_CODEC)
|
||||
.addPacket(CommonPacketTypes.SERVERBOUND_CUSTOM_CLICK_ACTION, ServerboundCustomClickActionPacket.STREAM_CODEC)
|
||||
@@ -277,5 +_,6 @@
|
||||
|
||||
public interface Context {
|
||||
boolean hasInfiniteMaterials();
|
||||
+ boolean canUseCommandBlocks(); // Paper - check if game master
|
||||
}
|
||||
}
|
||||
+13
@@ -2584,6 +2584,19 @@
|
||||
if (!this.receivedMovementThisTick) {
|
||||
this.player.setKnownMovement(Vec3.ZERO);
|
||||
}
|
||||
@@ -2220,6 +_,12 @@
|
||||
public boolean hasInfiniteMaterials() {
|
||||
return this.player.hasInfiniteMaterials();
|
||||
}
|
||||
+ // Paper start - check if game master
|
||||
+ @Override
|
||||
+ public boolean canUseCommandBlocks() {
|
||||
+ return this.player.canUseGameMasterBlocks();
|
||||
+ }
|
||||
+ // Paper end - check if game master
|
||||
|
||||
@Override
|
||||
public ServerPlayer getPlayer() {
|
||||
@@ -2231,12 +_,23 @@
|
||||
}
|
||||
|
||||
|
||||
+7
-5
@@ -143,7 +143,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,21 +_,23 @@
|
||||
@@ -139,21 +_,25 @@
|
||||
|
||||
private void verifyLoginAndFinishConnectionSetup(final GameProfile profile) {
|
||||
PlayerList playerList = this.server.getPlayerList();
|
||||
@@ -164,10 +164,12 @@
|
||||
- );
|
||||
+ // Paper start - Keep compression packet write and setup in the same Netty task
|
||||
+ final int compressionThreshold = this.server.getCompressionThreshold();
|
||||
+ this.connection.scheduleNettyTask(() -> {
|
||||
+ this.connection.send(new ClientboundLoginCompressionPacket(compressionThreshold));
|
||||
+ this.connection.setupCompression(compressionThreshold, true);
|
||||
+ });
|
||||
+ this.connection.sendPacketAndScheduleNettyTask(
|
||||
+ new ClientboundLoginCompressionPacket(compressionThreshold), true,
|
||||
+ () -> {
|
||||
+ this.connection.setupCompression(compressionThreshold, true);
|
||||
+ }
|
||||
+ );
|
||||
+ // Paper end - Keep compression packet write and setup in the same Netty task
|
||||
}
|
||||
|
||||
|
||||
@@ -737,7 +737,7 @@
|
||||
}
|
||||
|
||||
public UserWhiteList getWhiteList() {
|
||||
@@ -654,12 +_,20 @@
|
||||
@@ -654,12 +_,23 @@
|
||||
public void sendLevelInfo(final ServerPlayer player, final ServerLevel level) {
|
||||
WorldBorder worldBorder = level.getWorldBorder();
|
||||
player.connection.send(new ClientboundInitializeBorderPacket(worldBorder));
|
||||
@@ -758,6 +758,9 @@
|
||||
+ // player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, level.getThunderLevel(1.0F)));
|
||||
+ player.setPlayerWeather(org.bukkit.WeatherType.DOWNFALL, false);
|
||||
+ player.updateWeather(-level.rainLevel, level.rainLevel, -level.thunderLevel, level.thunderLevel);
|
||||
+ } else {
|
||||
+ player.setPlayerWeather(org.bukkit.WeatherType.CLEAR, false);
|
||||
+ player.updateWeather(-1.0F, level.rainLevel, -1.0F, level.thunderLevel);
|
||||
+ // CraftBukkit end
|
||||
}
|
||||
|
||||
|
||||
@@ -172,6 +172,15 @@
|
||||
double dropChance = this.dropChances.byEquipment(slot);
|
||||
if (!current.isEmpty() && Math.max(this.random.nextFloat() - 0.1F, 0.0F) < dropChance) {
|
||||
this.spawnAtLocation(level, current);
|
||||
@@ -566,7 +_,7 @@
|
||||
}
|
||||
|
||||
protected boolean canShearEquipment(final Player player) {
|
||||
- return !this.isVehicle();
|
||||
+ return !this.isVehicle() && !(this instanceof net.minecraft.world.entity.npc.villager.Villager); // Paper - Prevent shearing villager display items
|
||||
}
|
||||
|
||||
protected boolean attemptToShearEquipment(final Player player, final InteractionHand hand, final ItemStack heldItem) {
|
||||
@@ -688,22 +_,29 @@
|
||||
@Override
|
||||
public void checkDespawn() {
|
||||
|
||||
+20
@@ -12,3 +12,23 @@
|
||||
return isValidTarget ? transportItemTarget : null;
|
||||
}
|
||||
|
||||
@@ -377,8 +_,17 @@
|
||||
}
|
||||
|
||||
private boolean targetHasNotChanged(final Level level, final TransportItemsBetweenContainers.TransportItemTarget target) {
|
||||
- return target.blockEntity.equals(level.getBlockEntity(target.pos));
|
||||
- }
|
||||
+ // Paper start
|
||||
+ return target.blockEntity.equals(level.getBlockEntity(target.pos)) && !isContainerRemoved(target.container);
|
||||
+ }
|
||||
+
|
||||
+ private static boolean isContainerRemoved(final Container container) {
|
||||
+ if (container instanceof net.minecraft.world.CompoundContainer compoundContainer) {
|
||||
+ return isContainerRemoved(compoundContainer.container1) || isContainerRemoved(compoundContainer.container2);
|
||||
+ }
|
||||
+ return container instanceof BlockEntity blockEntity && blockEntity.isRemoved();
|
||||
+ }
|
||||
+ // Paper end
|
||||
|
||||
private Stream<TransportItemsBetweenContainers.TransportItemTarget> getConnectedTargets(
|
||||
final TransportItemsBetweenContainers.TransportItemTarget target, final Level level
|
||||
+2
-1
@@ -1,11 +1,12 @@
|
||||
--- a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
|
||||
+++ b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
|
||||
@@ -101,6 +_,11 @@
|
||||
@@ -101,6 +_,12 @@
|
||||
}
|
||||
|
||||
if (this.ticksSinceReachedGoal > 60) {
|
||||
+ // CraftBukkit start - Step on eggs
|
||||
+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityInteractEvent(this.removerMob, org.bukkit.craftbukkit.block.CraftBlock.at(level, eatPos))) {
|
||||
+ this.ticksSinceReachedGoal++;
|
||||
+ return;
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
|
||||
+2
-1
@@ -60,7 +60,8 @@
|
||||
+ this.inventory.getItems().stream().filter(item -> !item.isEmpty()).forEach(stack -> this.spawnAtLocation(level, stack)); // Paper - delay removal post event - taken from removeAllItems
|
||||
+ this.postDeathEventTasks.add(this.inventory::clearContent); // Paper - only clear after death event
|
||||
ItemStack itemStack = this.getItemBySlot(EquipmentSlot.MAINHAND);
|
||||
if (!itemStack.isEmpty() && !EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) {
|
||||
- if (!itemStack.isEmpty() && !EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) {
|
||||
+ if (!itemStack.isEmpty() && !EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP) && !this.droppedEquipmentSlots.contains(EquipmentSlot.MAINHAND)) { // Paper
|
||||
this.spawnAtLocation(level, itemStack);
|
||||
- this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
|
||||
+ this.postDeathEventTasks.add(() -> this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY)); // Paper
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
protected void alertOther(final Mob other, final LivingEntity hurtByMob) {
|
||||
if (other instanceof Bee && this.mob.hasLineOfSight(hurtByMob)) {
|
||||
- other.setTarget(hurtByMob);
|
||||
+ other.setTarget(hurtByMob, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_ENTITY); // CraftBukkit - reason
|
||||
+ other.setTarget(hurtByMob, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY); // CraftBukkit - reason
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@
|
||||
protected void alertOther(final Mob other, final LivingEntity hurtByMob) {
|
||||
if (other instanceof Panda && other.isAggressive()) {
|
||||
- other.setTarget(hurtByMob);
|
||||
+ other.setTarget(hurtByMob, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_ENTITY); // CraftBukkit
|
||||
+ other.setTarget(hurtByMob, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY); // CraftBukkit
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-2
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/world/entity/monster/cubemob/SulfurCube.java
|
||||
+++ b/net/minecraft/world/entity/monster/cubemob/SulfurCube.java
|
||||
@@ -295,13 +_,34 @@
|
||||
@@ -295,13 +_,35 @@
|
||||
|
||||
if (!this.explosionData.isEmpty()) {
|
||||
if (this.fuse == 0) {
|
||||
@@ -30,6 +30,7 @@
|
||||
+ if (primeEvent.isCancelled()) {
|
||||
+ this.setFuse(PrimedTnt.NO_FUSE);
|
||||
+ this.entityData.set(MAX_FUSE, PrimedTnt.NO_FUSE);
|
||||
+ this.setInvulnerable(false);
|
||||
+ return;
|
||||
+ }
|
||||
+ afterEvent.run();
|
||||
@@ -37,7 +38,7 @@
|
||||
level.explode(
|
||||
this,
|
||||
Explosion.getDefaultDamageSource(this.level(), this),
|
||||
@@ -309,16 +_,20 @@
|
||||
@@ -309,17 +_,27 @@
|
||||
this.getX(),
|
||||
this.getY(0.0625),
|
||||
this.getZ(),
|
||||
@@ -59,8 +60,15 @@
|
||||
- this.discard();
|
||||
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause
|
||||
}
|
||||
+ // Paper start - properly reset fuse if explosion data was unset by game
|
||||
+ } else if (this.tickCount > 1 && this.isPrimed()) {
|
||||
+ this.setFuse(PrimedTnt.NO_FUSE);
|
||||
+ this.entityData.set(MAX_FUSE, PrimedTnt.NO_FUSE);
|
||||
+ this.setInvulnerable(false);
|
||||
+ // Paper end - properly reset fuse if explosion data was unset by game
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,6 +_,12 @@
|
||||
&& !this.isPrimed()) {
|
||||
int fuse = this.explosionData.get().fuse();
|
||||
@@ -156,6 +164,17 @@
|
||||
this.playSound(this.getEjectSound());
|
||||
this.pickupTimer = 100;
|
||||
}
|
||||
@@ -748,6 +_,10 @@
|
||||
double sulfurCubeTopPosition = sulfurCubeBottomPosition + this.getBbHeight();
|
||||
double pusherTopPosition = pusherFeetPosition + pusher.getBbHeight();
|
||||
if (cubeToPusher.horizontalDistance() < 1.3F && pusherFeetPosition <= sulfurCubeTopPosition && pusherTopPosition > sulfurCubeBottomPosition) {
|
||||
+ // Paper start - Add EntityCollideWithEntity Event
|
||||
+ if (io.papermc.paper.event.entity.EntityCollideWithEntityEvent.getHandlerList().getRegisteredListeners().length != 0 &&
|
||||
+ !new io.papermc.paper.event.entity.EntityCollideWithEntityEvent(this.getBukkitEntity(), pusher.getBukkitEntity()).callEvent()) return;
|
||||
+ // Paper end - Add EntityCollideWithEntity Event
|
||||
double knockback = Math.max(0.0, 1.0 - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE));
|
||||
Vec3 pushDirection = cubeToPusher.horizontal().normalize().scale(knockback);
|
||||
float pushSpeedScale = player.isPassenger() ? 0.16F : 0.3F;
|
||||
@@ -827,7 +_,7 @@
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -80,7 +80,7 @@
|
||||
public void startUnderWaterConversion(final int time) {
|
||||
this.conversionTime = time;
|
||||
this.getEntityData().set(DATA_DROWNED_CONVERSION_ID, true);
|
||||
@@ -246,17 +_,28 @@
|
||||
@@ -246,17 +_,35 @@
|
||||
}
|
||||
|
||||
protected void convertToZombieType(final ServerLevel level, final EntityType<? extends Zombie> zombieType) {
|
||||
@@ -110,6 +110,13 @@
|
||||
+
|
||||
+ public static @Nullable ZombieVillager convertVillagerToZombieVillager(final ServerLevel level, final Villager villager, final net.minecraft.core.BlockPos blockPosition, boolean silent, org.bukkit.event.entity.EntityTransformEvent.TransformReason transformReason, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason creatureSpawnReason) {
|
||||
+ // CraftBukkit end
|
||||
+ // Paper start - Clear display items before conversion
|
||||
+ for (net.minecraft.world.entity.ai.behavior.BehaviorControl<? super Villager> behavior : villager.getBrain().getRunningBehaviors()) {
|
||||
+ if (behavior instanceof net.minecraft.world.entity.ai.behavior.ShowTradesToPlayer) {
|
||||
+ behavior.doStop(level, villager, level.getGameTime());
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - Clear display items before conversion
|
||||
ZombieVillager zombieVillager = villager.convertTo(
|
||||
EntityTypes.ZOMBIE_VILLAGER,
|
||||
ConversionParams.single(villager, true, true),
|
||||
|
||||
+8
-2
@@ -1,9 +1,10 @@
|
||||
--- a/net/minecraft/world/item/crafting/Ingredient.java
|
||||
+++ b/net/minecraft/world/item/crafting/Ingredient.java
|
||||
@@ -33,6 +_,23 @@
|
||||
@@ -33,6 +_,24 @@
|
||||
public static final Codec<HolderSet<Item>> NON_AIR_HOLDER_SET_CODEC = HolderSetCodec.create(Registries.ITEM, Item.CODEC, false);
|
||||
public static final Codec<Ingredient> CODEC = ExtraCodecs.nonEmptyHolderSet(NON_AIR_HOLDER_SET_CODEC).xmap(Ingredient::new, i -> i.values);
|
||||
public final HolderSet<Item> values;
|
||||
+ public java.util.function.@org.jspecify.annotations.Nullable Predicate<ItemStack> stackPredicate; // Paper - add PredicateChoice
|
||||
+ // CraftBukkit start
|
||||
+ private java.util.@org.jspecify.annotations.Nullable List<ItemStack> itemStacks;
|
||||
+
|
||||
@@ -24,10 +25,15 @@
|
||||
|
||||
private Ingredient(final HolderSet<Item> values) {
|
||||
values.unwrap().ifRight(directValues -> {
|
||||
@@ -62,6 +_,17 @@
|
||||
@@ -62,6 +_,22 @@
|
||||
|
||||
@Override
|
||||
public boolean test(final ItemStack input) {
|
||||
+ // Paper start - add PredicateChoice
|
||||
+ if (this.stackPredicate != null) {
|
||||
+ return this.stackPredicate.test(input);
|
||||
+ }
|
||||
+ // Paper end - add PredicateChoice
|
||||
+ // CraftBukkit start
|
||||
+ if (this.isExact()) {
|
||||
+ for (ItemStack item : this.itemStacks()) {
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
package io.papermc.paper.plugin;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.AnnotatedType;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Predicate;
|
||||
import org.bukkit.craftbukkit.util.Versioning;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.spongepowered.configurate.serialize.ScalarSerializer;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
@NullMarked
|
||||
public sealed interface ApiVersion extends io.papermc.asm.versioned.ApiVersion<ApiVersion> permits ApiVersion.Legacy, ApiVersion.Modern, ApiVersion.None {
|
||||
|
||||
Serializer SERIALIZER = new Serializer();
|
||||
|
||||
ApiVersion CURRENT = getOrCreateVersion(Versioning.getCurrentApiVersion());
|
||||
ApiVersion FLATTENING = getOrCreateVersion("1.13");
|
||||
ApiVersion FIELD_NAME_PARITY = getOrCreateVersion("1.20.5");
|
||||
ApiVersion ABSTRACT_COW = getOrCreateVersion("1.21.5");
|
||||
ApiVersion ABSTRACT_CUBE_MOB = getOrCreateVersion("26.2");
|
||||
ApiVersion NONE = getOrCreateVersion("none");
|
||||
|
||||
String getVersionString();
|
||||
|
||||
@Override
|
||||
default int compareTo(final ApiVersion o) {
|
||||
return switch (o) {
|
||||
case final Legacy oLegacy -> switch (this) {
|
||||
case final Legacy thisLegacy -> thisLegacy.compareSameType(oLegacy);
|
||||
case final Modern ignored -> 1;
|
||||
case final None ignored -> -1;
|
||||
};
|
||||
case final Modern oModern -> switch (this) {
|
||||
case final Legacy ignored -> -1;
|
||||
case final Modern thisModern -> thisModern.compareSameType(oModern);
|
||||
case final None ignored -> -1;
|
||||
};
|
||||
case final None ignored -> switch (this) {
|
||||
case final Legacy ignored2 -> 1;
|
||||
case final Modern ignored2 -> 1;
|
||||
case final None ignored2 -> 0;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
record Legacy(int major, int minor, int patch) implements ApiVersion {
|
||||
|
||||
private static final Comparator<Legacy> COMPARATOR = Comparator
|
||||
.comparingInt(Legacy::major)
|
||||
.thenComparingInt(Legacy::minor)
|
||||
.thenComparingInt(Legacy::patch);
|
||||
|
||||
int compareSameType(final Legacy other) {
|
||||
return COMPARATOR.compare(this, other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersionString() {
|
||||
return this.major() + "." + this.minor() + "." + this.patch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getVersionString();
|
||||
}
|
||||
}
|
||||
|
||||
record Modern(int year, int drop, int hotfix) implements ApiVersion {
|
||||
|
||||
private static final Comparator<Modern> COMPARATOR = Comparator
|
||||
.comparingInt(Modern::year)
|
||||
.thenComparingInt(Modern::drop)
|
||||
.thenComparingInt(Modern::hotfix);
|
||||
|
||||
int compareSameType(final Modern other) {
|
||||
return COMPARATOR.compare(this, other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersionString() {
|
||||
return this.year() + "." + this.drop() + "." + this.hotfix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getVersionString();
|
||||
}
|
||||
}
|
||||
|
||||
record None() implements ApiVersion {
|
||||
|
||||
private static final None INSTANCE = new None();
|
||||
|
||||
@Override
|
||||
public String getVersionString() {
|
||||
return "none";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getVersionString();
|
||||
}
|
||||
}
|
||||
|
||||
final class Serializer extends ScalarSerializer.Annotated<ApiVersion> {
|
||||
|
||||
Serializer() {
|
||||
super(ApiVersion.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiVersion deserialize(final AnnotatedType type, final Object obj) throws SerializationException {
|
||||
try {
|
||||
final ApiVersion version = getOrCreateVersion(obj.toString());
|
||||
final Minimum min = type.getAnnotation(Minimum.class);
|
||||
if (min != null) {
|
||||
final ApiVersion minVersion = getOrCreateVersion(min.value());
|
||||
if (version.isOlderThan(minVersion)) {
|
||||
throw new SerializationException(ApiVersion.class, version + " is too old for a paper plugin!");
|
||||
}
|
||||
}
|
||||
return version;
|
||||
} catch (final IllegalArgumentException ex) {
|
||||
throw new SerializationException(ApiVersion.class, "Could not parse version string", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object serialize(final AnnotatedType type, final ApiVersion item, final Predicate<Class<?>> typeSupported) {
|
||||
return item.getVersionString();
|
||||
}
|
||||
}
|
||||
|
||||
static ApiVersion getOrCreateVersion(final @Nullable String versionString) {
|
||||
class Holder {
|
||||
private static final Map<String, ApiVersion> cache = new ConcurrentHashMap<>();
|
||||
}
|
||||
if (versionString == null || versionString.isBlank() || versionString.equalsIgnoreCase("none")) {
|
||||
return None.INSTANCE;
|
||||
}
|
||||
final ApiVersion parsed = parse(versionString);
|
||||
Holder.cache.putIfAbsent(parsed.getVersionString(), parsed);
|
||||
return Holder.cache.get(parsed.getVersionString());
|
||||
}
|
||||
|
||||
private static ApiVersion parse(final String versionString) {
|
||||
final String[] parts = versionString.split("\\.");
|
||||
|
||||
if (parts.length < 2 || parts.length > 3) {
|
||||
throw new IllegalArgumentException("Version string must have 2 or 3 numbers");
|
||||
}
|
||||
|
||||
final int first = Integer.parseInt(parts[0]);
|
||||
final int second = Integer.parseInt(parts[1]);
|
||||
final int third = parts.length == 3 ? Integer.parseInt(parts[2]) : 0;
|
||||
|
||||
if (first == 1) {
|
||||
if (second > 21) {
|
||||
throw new IllegalArgumentException("Legacy version string must be 1.21.xx or lower");
|
||||
}
|
||||
return new Legacy(first, second, third);
|
||||
} else {
|
||||
if (first < 26) {
|
||||
throw new IllegalArgumentException("Modern version string must be 26.x.x or higher");
|
||||
}
|
||||
return new Modern(first, second, third);
|
||||
}
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@interface Minimum {
|
||||
String value();
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package io.papermc.paper.plugin.entrypoint.classloader;
|
||||
|
||||
import io.papermc.paper.plugin.configuration.PluginMeta;
|
||||
|
||||
// Stub, implement in future.
|
||||
public class PaperClassloaderBytecodeModifier implements ClassloaderBytecodeModifier {
|
||||
|
||||
@Override
|
||||
public byte[] modify(PluginMeta configuration, byte[] bytecode) {
|
||||
return bytecode;
|
||||
}
|
||||
}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
package io.papermc.paper.plugin.entrypoint.classloader.bytecode;
|
||||
|
||||
import com.google.common.collect.Iterators;
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import io.papermc.paper.plugin.configuration.PluginMeta;
|
||||
import io.papermc.paper.plugin.entrypoint.classloader.ClassloaderBytecodeModifier;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import net.minecraft.util.Util;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
public class PaperClassloaderBytecodeModifier implements ClassloaderBytecodeModifier {
|
||||
|
||||
private static final Map<ApiVersion, List<ModifierFactory>> MODIFIERS = Util.make(new LinkedHashMap<>(), map -> {
|
||||
});
|
||||
|
||||
private final Map<ApiVersion, List<VersionedClassloaderBytecodeModifier>> constructedModifiers = MODIFIERS.entrySet().stream()
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, entry -> {
|
||||
return entry.getValue().stream().map(factory -> factory.create(Opcodes.ASM9)).toList();
|
||||
}));
|
||||
|
||||
@Override
|
||||
public byte[] modify(final PluginMeta configuration, byte[] bytecode) {
|
||||
int start = -1;
|
||||
if (configuration.getAPIVersion() != null) {
|
||||
int i = 0;
|
||||
for (final Map.Entry<ApiVersion, List<VersionedClassloaderBytecodeModifier>> entry : this.constructedModifiers.entrySet()) {
|
||||
final ApiVersion apiVersion = ApiVersion.getOrCreateVersion(configuration.getAPIVersion());
|
||||
final ApiVersion modifierApiVersion = entry.getKey();
|
||||
if (apiVersion.isOlderThanOrSameAs(modifierApiVersion)) {
|
||||
start = i;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
start = 0;
|
||||
}
|
||||
if (start == -1) {
|
||||
return bytecode; // no modification needed. The plugin version is newer than all versioned modifiers
|
||||
}
|
||||
|
||||
final Iterator<Map.Entry<ApiVersion, List<VersionedClassloaderBytecodeModifier>>> it = this.constructedModifiers.entrySet().iterator();
|
||||
Iterators.advance(it, start);
|
||||
while (it.hasNext()) {
|
||||
for (final VersionedClassloaderBytecodeModifier modifier : it.next().getValue()) {
|
||||
bytecode = modifier.modify(configuration, bytecode);
|
||||
}
|
||||
}
|
||||
return bytecode;
|
||||
}
|
||||
|
||||
private interface ModifierFactory {
|
||||
|
||||
VersionedClassloaderBytecodeModifier create(int api);
|
||||
}
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
package io.papermc.paper.plugin.entrypoint.classloader.bytecode;
|
||||
|
||||
import io.papermc.asm.AbstractRewriteRuleVisitorFactory;
|
||||
import io.papermc.asm.ClassInfoProvider;
|
||||
import io.papermc.asm.rules.builder.RuleFactoryConfiguration;
|
||||
import io.papermc.paper.plugin.configuration.PluginMeta;
|
||||
import io.papermc.paper.plugin.entrypoint.classloader.ClassloaderBytecodeModifier;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
|
||||
import static io.papermc.asm.util.DescriptorUtils.desc;
|
||||
|
||||
public abstract class VersionedClassloaderBytecodeModifier extends AbstractRewriteRuleVisitorFactory implements ClassloaderBytecodeModifier, RuleFactoryConfiguration.Holder {
|
||||
|
||||
protected VersionedClassloaderBytecodeModifier(final int api) {
|
||||
super(api, ClassInfoProvider.basic());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final byte[] modify(final PluginMeta config, final byte[] bytecode) {
|
||||
final ClassReader cr = new ClassReader(bytecode);
|
||||
final ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS); // need to compute maxs because of instruction removal in ctor rewriting
|
||||
|
||||
cr.accept(this.createVisitor(cw), 0);
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final RuleFactoryConfiguration configuration() {
|
||||
return RuleFactoryConfiguration.create(desc(this.getClass()));
|
||||
}
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
@NullMarked
|
||||
package io.papermc.paper.plugin.entrypoint.classloader.bytecode;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
+26
-3
@@ -5,13 +5,15 @@ import com.google.common.collect.ImmutableList;
|
||||
import io.papermc.paper.configuration.constraint.Constraint;
|
||||
import io.papermc.paper.configuration.serializer.ComponentSerializer;
|
||||
import io.papermc.paper.configuration.serializer.EnumValueSerializer;
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import io.papermc.paper.plugin.configuration.PluginMeta;
|
||||
import io.papermc.paper.plugin.provider.configuration.serializer.PermissionConfigurationSerializer;
|
||||
import io.papermc.paper.plugin.provider.configuration.serializer.constraints.PluginConfigConstraints;
|
||||
import io.papermc.paper.plugin.provider.configuration.type.DependencyConfiguration;
|
||||
import io.papermc.paper.plugin.provider.configuration.type.PermissionConfiguration;
|
||||
import io.papermc.paper.plugin.provider.configuration.type.PluginDependencyLifeCycle;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.function.Predicate;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
import org.bukkit.plugin.PluginLoadOrder;
|
||||
@@ -24,6 +26,8 @@ import org.spongepowered.configurate.loader.HeaderMode;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.ObjectMapper;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Required;
|
||||
import org.spongepowered.configurate.serialize.ScalarSerializer;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
import org.spongepowered.configurate.yaml.NodeStyle;
|
||||
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
||||
|
||||
@@ -59,7 +63,6 @@ public class PaperPluginMeta implements PluginMeta {
|
||||
@FlattenedResolver
|
||||
private PermissionConfiguration permissionConfiguration = new PermissionConfiguration(PermissionDefault.OP, List.of());
|
||||
@Required
|
||||
@ApiVersion.Minimum("1.19")
|
||||
private ApiVersion apiVersion;
|
||||
|
||||
private Map<PluginDependencyLifeCycle, Map<String, DependencyConfiguration>> dependencies = new EnumMap<>(PluginDependencyLifeCycle.class);
|
||||
@@ -67,6 +70,7 @@ public class PaperPluginMeta implements PluginMeta {
|
||||
public PaperPluginMeta() {
|
||||
}
|
||||
|
||||
static final ApiVersion MINIMUM = ApiVersion.getOrCreateVersion("1.19");
|
||||
public static PaperPluginMeta create(BufferedReader reader) throws ConfigurateException {
|
||||
YamlConfigurationLoader loader = YamlConfigurationLoader.builder()
|
||||
.indent(2)
|
||||
@@ -74,9 +78,28 @@ public class PaperPluginMeta implements PluginMeta {
|
||||
.headerMode(HeaderMode.NONE)
|
||||
.source(() -> reader)
|
||||
.defaultOptions((options) -> {
|
||||
|
||||
return options.serializers((serializers) -> {
|
||||
serializers
|
||||
.register(ApiVersion.SERIALIZER)
|
||||
.register(new ScalarSerializer<>(ApiVersion.class) {
|
||||
@Override
|
||||
public ApiVersion deserialize(final Type type, final Object obj) throws SerializationException {
|
||||
try {
|
||||
final ApiVersion version = ApiVersion.getOrCreateVersion(obj.toString());
|
||||
if (version.isOlderThan(MINIMUM)) {
|
||||
throw new SerializationException(version + " is too old for a paper plugin!");
|
||||
}
|
||||
return version;
|
||||
} catch (final IllegalArgumentException e) {
|
||||
throw new SerializationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object serialize(final ApiVersion item, final Predicate<Class<?>> typeSupported) {
|
||||
return item.getVersionString();
|
||||
}
|
||||
})
|
||||
.register(new EnumValueSerializer())
|
||||
.register(PermissionConfiguration.class, PermissionConfigurationSerializer.SERIALIZER)
|
||||
.register(new ComponentSerializer())
|
||||
|
||||
@@ -4,18 +4,12 @@ import java.util.function.Predicate;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.inventory.CraftRecipe;
|
||||
import org.bukkit.inventory.RecipeChoice;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public record PaperPotionMix(ItemStack result, Predicate<ItemStack> input, Predicate<ItemStack> ingredient) {
|
||||
|
||||
public PaperPotionMix(PotionMix potionMix) {
|
||||
this(CraftItemStack.asNMSCopy(potionMix.getResult()), convert(potionMix.getInput()), convert(potionMix.getIngredient()));
|
||||
}
|
||||
|
||||
static Predicate<ItemStack> convert(final RecipeChoice choice) {
|
||||
if (choice instanceof PredicateRecipeChoice predicateRecipeChoice) {
|
||||
return stack -> predicateRecipeChoice.test(CraftItemStack.asBukkitCopy(stack));
|
||||
}
|
||||
return CraftRecipe.toIngredient(choice, true);
|
||||
public PaperPotionMix(final PotionMix potionMix) {
|
||||
this(CraftItemStack.asNMSCopy(potionMix.getResult()), CraftRecipe.toIngredient(potionMix.getInput(), true), CraftRecipe.toIngredient(potionMix.getIngredient(), true));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,5 @@
|
||||
package io.papermc.paper.registry.entry;
|
||||
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import io.papermc.paper.registry.PaperRegistryBuilder;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
import java.util.function.BiFunction;
|
||||
@@ -11,6 +10,7 @@ import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import org.bukkit.Keyed;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
|
||||
import static io.papermc.paper.registry.entry.RegistryEntryMeta.RegistryModificationApiSupport.ADDABLE;
|
||||
import static io.papermc.paper.registry.entry.RegistryEntryMeta.RegistryModificationApiSupport.MODIFIABLE;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.papermc.paper.registry.entry;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import io.papermc.paper.registry.PaperRegistryBuilder;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
import io.papermc.paper.registry.TypedKey;
|
||||
@@ -17,6 +16,7 @@ import net.minecraft.resources.ResourceKey;
|
||||
import org.bukkit.Keyed;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.craftbukkit.CraftRegistry;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
|
||||
public sealed interface RegistryEntryMeta<M, A extends Keyed> permits RegistryEntryMeta.ApiOnly, RegistryEntryMeta.ServerSide { // TODO remove Keyed
|
||||
|
||||
|
||||
+4
-2
@@ -32,7 +32,8 @@ public final class OversizedItemComponentSanitizer {
|
||||
public static final Codec<ChargedProjectiles> CHARGED_PROJECTILES = codec(ChargedProjectiles.CODEC, OversizedItemComponentSanitizer::sanitizeChargedProjectiles);
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, ItemContainerContents> CONTAINER_STREAM = codec(ItemContainerContents.STREAM_CODEC, OversizedItemComponentSanitizer::sanitizeItemContainerContents);
|
||||
public static final Codec<ItemContainerContents> CONTAINER = codec(ItemContainerContents.CODEC, OversizedItemComponentSanitizer::sanitizeItemContainerContents);
|
||||
public static final Codec<BundleContents> BUNDLE_CONTENTS = new Codec<>() {
|
||||
public static final Codec<BundleContents> BUNDLE_CONTENTS = new BundleContentsCodec();
|
||||
static class BundleContentsCodec implements Codec<BundleContents>, ObfuscationDependantCodec {
|
||||
|
||||
@Override
|
||||
public <T> DataResult<Pair<BundleContents, T>> decode(final DynamicOps<T> ops, final T input) {
|
||||
@@ -51,7 +52,8 @@ public final class OversizedItemComponentSanitizer {
|
||||
}
|
||||
}
|
||||
};
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, BundleContents> BUNDLE_CONTENTS_STREAM = new StreamCodec<>() {
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, BundleContents> BUNDLE_CONTENTS_STREAM = new BundleContentsStreamCodec();
|
||||
static class BundleContentsStreamCodec implements StreamCodec<RegistryFriendlyByteBuf, BundleContents>, ObfuscationDependantCodec {
|
||||
@Override
|
||||
public BundleContents decode(final RegistryFriendlyByteBuf buffer) {
|
||||
return BundleContents.STREAM_CODEC.decode(buffer);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.bukkit.craftbukkit;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import io.papermc.paper.registry.PaperRegistries;
|
||||
import io.papermc.paper.registry.RegistryAccess;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
@@ -9,6 +8,7 @@ import io.papermc.paper.registry.entry.RegistryEntryMeta;
|
||||
import io.papermc.paper.registry.set.NamedRegistryKeySetImpl;
|
||||
import io.papermc.paper.registry.tag.Tag;
|
||||
import io.papermc.paper.util.Holderable;
|
||||
import io.papermc.paper.util.MCUtil;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
@@ -24,6 +24,7 @@ import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.craftbukkit.legacy.FieldRename;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
|
||||
import org.bukkit.craftbukkit.util.Handleable;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
@@ -13,7 +13,6 @@ import com.mojang.serialization.JsonOps;
|
||||
import io.papermc.paper.configuration.GlobalConfiguration;
|
||||
import io.papermc.paper.configuration.PaperServerConfiguration;
|
||||
import io.papermc.paper.configuration.ServerConfiguration;
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import io.papermc.paper.util.MCUtil;
|
||||
import io.papermc.paper.world.PaperWorldLoader;
|
||||
import io.papermc.paper.world.migration.WorldFolderMigration;
|
||||
@@ -154,6 +153,7 @@ import org.bukkit.craftbukkit.ban.CraftIpBanList;
|
||||
import org.bukkit.craftbukkit.ban.CraftProfileBanList;
|
||||
import org.bukkit.craftbukkit.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.boss.CraftBossBar;
|
||||
import org.bukkit.craftbukkit.boss.CraftKeyedBossbar;
|
||||
import org.bukkit.craftbukkit.command.CraftCommandMap;
|
||||
import org.bukkit.craftbukkit.command.VanillaCommandWrapper;
|
||||
import org.bukkit.craftbukkit.entity.CraftEntityFactory;
|
||||
@@ -197,6 +197,7 @@ import org.bukkit.craftbukkit.tag.CraftEntityTag;
|
||||
import org.bukkit.craftbukkit.tag.CraftFluidTag;
|
||||
import org.bukkit.craftbukkit.tag.CraftGameEventTag;
|
||||
import org.bukkit.craftbukkit.tag.CraftItemTag;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
import org.bukkit.craftbukkit.util.CraftChatMessage;
|
||||
import org.bukkit.craftbukkit.util.CraftIconCache;
|
||||
import org.bukkit.craftbukkit.util.CraftLocation;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.bukkit.craftbukkit.attribute;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
import io.papermc.paper.util.OldEnumHolderable;
|
||||
import java.util.Locale;
|
||||
@@ -11,6 +10,7 @@ import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.craftbukkit.CraftRegistry;
|
||||
import org.bukkit.craftbukkit.legacy.FieldRename;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
|
||||
public class CraftAttribute extends OldEnumHolderable<Attribute, net.minecraft.world.entity.ai.attributes.Attribute> implements Attribute {
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,6 @@
|
||||
package org.bukkit.craftbukkit.enchantments;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
import io.papermc.paper.util.Holderable;
|
||||
import java.util.Locale;
|
||||
@@ -13,6 +12,7 @@ import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.craftbukkit.CraftRegistry;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.legacy.FieldRename;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.enchantments.EnchantmentTarget;
|
||||
import org.bukkit.enchantments.EnchantmentWrapper;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.bukkit.craftbukkit.entity;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
import java.util.Locale;
|
||||
import net.minecraft.core.Holder;
|
||||
@@ -10,6 +9,7 @@ import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.craftbukkit.CraftRegistry;
|
||||
import org.bukkit.craftbukkit.legacy.FieldRename;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
|
||||
@@ -1693,8 +1693,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa
|
||||
final Optional<net.minecraft.world.item.enchantment.EnchantedItemInUse> selected = net.minecraft.world.item.enchantment.EnchantmentHelper
|
||||
.getRandomItemWith(net.minecraft.world.item.enchantment.EnchantmentEffectComponents.REPAIR_WITH_XP, handle, net.minecraft.world.item.ItemStack::isDamaged);
|
||||
final net.minecraft.world.item.ItemStack itemstack = selected.map(net.minecraft.world.item.enchantment.EnchantedItemInUse::itemStack).orElse(net.minecraft.world.item.ItemStack.EMPTY);
|
||||
if (!itemstack.isEmpty() && itemstack.getItem().components().has(net.minecraft.core.component.DataComponents.MAX_DAMAGE)) {
|
||||
net.minecraft.world.entity.ExperienceOrb orb = net.minecraft.world.entity.EntityTypes.EXPERIENCE_ORB.create(handle.level(), net.minecraft.world.entity.EntitySpawnReason.COMMAND);
|
||||
if (!itemstack.isEmpty()) {
|
||||
final net.minecraft.world.entity.ExperienceOrb orb = net.minecraft.world.entity.EntityTypes.EXPERIENCE_ORB.create(handle.level(), net.minecraft.world.entity.EntitySpawnReason.COMMAND);
|
||||
if (orb == null) {
|
||||
return amount;
|
||||
}
|
||||
orb.setValue(amount);
|
||||
orb.spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM;
|
||||
orb.setPosRaw(handle.getX(), handle.getY(), handle.getZ());
|
||||
|
||||
@@ -10,7 +10,9 @@ import net.minecraft.world.entity.SulfurCubeArchetype;
|
||||
import net.minecraft.world.entity.item.PrimedTnt;
|
||||
import org.bukkit.craftbukkit.CraftRegistry;
|
||||
import org.bukkit.craftbukkit.CraftServer;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.SulfurCube;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public class CraftSulfurCube extends CraftAbstractCubeMob implements SulfurCube, PaperShearable, PaperBucketable {
|
||||
public CraftSulfurCube(final CraftServer server, final net.minecraft.world.entity.monster.cubemob.SulfurCube entity) {
|
||||
@@ -44,6 +46,11 @@ public class CraftSulfurCube extends CraftAbstractCubeMob implements SulfurCube,
|
||||
return this.getHandle().primeTime(imminent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean swallow(final ItemStack itemStack) {
|
||||
return this.getHandle().equipItem(CraftItemStack.asNMSCopy(itemStack));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAge() {
|
||||
return this.getHandle().getAge();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.bukkit.craftbukkit.inventory;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import org.bukkit.craftbukkit.legacy.FieldRename;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
|
||||
public class CraftItemFlag {
|
||||
|
||||
@@ -370,7 +370,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta, WritableBo
|
||||
|
||||
@Override
|
||||
public @NotNull Book asBook() {
|
||||
return Book.book(net.kyori.adventure.text.Component.empty(), net.kyori.adventure.text.Component.empty(), this.getPages().stream().map(net.kyori.adventure.text.Component::text).collect(Collectors.toList()));
|
||||
return Book.book(pages());
|
||||
}
|
||||
|
||||
// Spigot start
|
||||
|
||||
+1
-1
@@ -365,7 +365,7 @@ public class CraftMetaBookSigned extends CraftMetaItem implements BookMeta {
|
||||
|
||||
@Override
|
||||
public @NotNull Book asBook() {
|
||||
return Book.book(net.kyori.adventure.text.Component.empty(), net.kyori.adventure.text.Component.empty(), this.getPages().stream().map(net.kyori.adventure.text.Component::text).collect(Collectors.toList()));
|
||||
return Book.book(Objects.requireNonNullElse(title(), net.kyori.adventure.text.Component.empty()), Objects.requireNonNullElse(author(), net.kyori.adventure.text.Component.empty()), pages());
|
||||
}
|
||||
|
||||
// Spigot start
|
||||
|
||||
@@ -6,8 +6,10 @@ import io.papermc.paper.registry.data.util.Conversions;
|
||||
import io.papermc.paper.registry.set.PaperRegistrySets;
|
||||
import io.papermc.paper.registry.set.RegistryKeySet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import org.bukkit.inventory.ItemType;
|
||||
@@ -32,6 +34,9 @@ public interface CraftRecipe extends Recipe {
|
||||
stack = Ingredient.of();
|
||||
} else if (bukkit instanceof final RecipeChoice.ItemTypeChoice itemTypeChoice) {
|
||||
stack = Ingredient.of(PaperRegistrySets.convertToNms(Registries.ITEM, Conversions.global().lookup(), itemTypeChoice.itemTypes()));
|
||||
} else if (bukkit instanceof final RecipeChoice.PredicateChoice predicateChoice) {
|
||||
stack = Ingredient.ofStacks(Collections.singletonList(CraftItemStack.asNMSCopy(predicateChoice.getItemStack())));
|
||||
stack.stackPredicate = nmsStack -> predicateChoice.test(CraftItemStack.asBukkitCopy(nmsStack));
|
||||
} else if (bukkit instanceof RecipeChoice.MaterialChoice) {
|
||||
stack = Ingredient.of(((RecipeChoice.MaterialChoice) bukkit).getChoices().stream().map(CraftItemType::bukkitToMinecraft));
|
||||
} else if (bukkit instanceof RecipeChoice.ExactChoice) {
|
||||
@@ -63,13 +68,19 @@ public interface CraftRecipe extends Recipe {
|
||||
return RecipeChoice.empty(); // Paper - null breaks API contracts
|
||||
}
|
||||
|
||||
if (ingredient.stackPredicate != null) {
|
||||
net.minecraft.world.item.ItemStack stack = ingredient.itemStacks().iterator().next();
|
||||
Predicate<org.bukkit.inventory.ItemStack> predicate = bukkitStack -> ingredient.stackPredicate.test(CraftItemStack.asNMSCopy(bukkitStack));
|
||||
return RecipeChoice.predicateChoice(predicate, CraftItemStack.asBukkitCopy(stack));
|
||||
}
|
||||
|
||||
if (ingredient.isExact()) {
|
||||
List<org.bukkit.inventory.ItemStack> choices = new ArrayList<>(ingredient.itemStacks().size());
|
||||
for (net.minecraft.world.item.ItemStack i : ingredient.itemStacks()) {
|
||||
choices.add(CraftItemStack.asBukkitCopy(i));
|
||||
}
|
||||
|
||||
return new RecipeChoice.ExactChoice(choices);
|
||||
return RecipeChoice.exactChoice(choices);
|
||||
} else {
|
||||
final RegistryKeySet<ItemType> itemTypes = PaperRegistrySets.convertToApi(RegistryKey.ITEM, ingredient.values);
|
||||
return RecipeChoice.itemType(itemTypes);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.bukkit.craftbukkit.legacy;
|
||||
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import java.util.function.BiFunction;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Particle;
|
||||
@@ -13,6 +12,7 @@ import org.bukkit.craftbukkit.legacy.reroute.DoNotReroute;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.InjectPluginVersion;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.RerouteMethodName;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.RerouteStatic;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.bukkit.craftbukkit.legacy;
|
||||
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
@@ -30,6 +29,7 @@ import org.bukkit.craftbukkit.legacy.reroute.InjectPluginVersion;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.RerouteStatic;
|
||||
import org.bukkit.craftbukkit.tag.CraftBlockTag;
|
||||
import org.bukkit.craftbukkit.tag.CraftItemTag;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
|
||||
import org.bukkit.enchantments.EnchantmentTarget;
|
||||
import org.bukkit.entity.Animals;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.bukkit.craftbukkit.legacy.enums;
|
||||
|
||||
/**
|
||||
* A crash dummy to use, instead of the old enums which matured to Abstracthood or Interfacehood and the baby enums which are still growing.
|
||||
*/
|
||||
public enum DummyEnum {
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package org.bukkit.craftbukkit.legacy.enums;
|
||||
|
||||
import com.google.common.base.Converter;
|
||||
import com.google.common.base.Enums;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Lists;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
import org.bukkit.Art;
|
||||
import org.bukkit.Fluid;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.banner.PatternType;
|
||||
import org.bukkit.craftbukkit.legacy.FieldRename;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.DoNotReroute;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.InjectPluginVersion;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.NotInBukkit;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.RequireCompatibility;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.RequirePluginVersion;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.RerouteArgumentType;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.RerouteReturnType;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.RerouteStatic;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
import org.bukkit.craftbukkit.util.ClassTraverser;
|
||||
import org.bukkit.entity.Cat;
|
||||
import org.bukkit.entity.Frog;
|
||||
import org.bukkit.entity.Villager;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.util.OldEnum;
|
||||
|
||||
@Deprecated
|
||||
@NotInBukkit
|
||||
@RequireCompatibility("enum-compatibility-mode")
|
||||
@RequirePluginVersion(maxInclusive = "1.20.6")
|
||||
public class EnumEvil {
|
||||
|
||||
private static final Map<Class<?>, LegacyRegistryData> REGISTRIES = new HashMap<>();
|
||||
|
||||
static {
|
||||
// Add Classes which got changed here
|
||||
REGISTRIES.put(Art.class, new LegacyRegistryData(Registry.ART, Art::valueOf));
|
||||
REGISTRIES.put(Attribute.class, new LegacyRegistryData(Registry.ATTRIBUTE, Attribute::valueOf));
|
||||
REGISTRIES.put(Biome.class, new LegacyRegistryData(Registry.BIOME, Biome::valueOf));
|
||||
REGISTRIES.put(Fluid.class, new LegacyRegistryData(Registry.FLUID, Fluid::valueOf));
|
||||
REGISTRIES.put(Villager.Type.class, new LegacyRegistryData(Registry.VILLAGER_TYPE, Villager.Type::valueOf));
|
||||
REGISTRIES.put(Villager.Profession.class, new LegacyRegistryData(Registry.VILLAGER_PROFESSION, Villager.Profession::valueOf));
|
||||
REGISTRIES.put(Sound.class, new LegacyRegistryData(Registry.SOUNDS, Sound::valueOf));
|
||||
REGISTRIES.put(Frog.Variant.class, new LegacyRegistryData(Registry.FROG_VARIANT, Frog.Variant::valueOf));
|
||||
REGISTRIES.put(Cat.Type.class, new LegacyRegistryData(Registry.CAT_VARIANT, Cat.Type::valueOf));
|
||||
REGISTRIES.put(MapCursor.Type.class, new LegacyRegistryData(Registry.MAP_DECORATION_TYPE, MapCursor.Type::valueOf));
|
||||
REGISTRIES.put(PatternType.class, new LegacyRegistryData(Registry.BANNER_PATTERN, PatternType::valueOf));
|
||||
}
|
||||
|
||||
public static LegacyRegistryData getRegistryData(Class<?> clazz) {
|
||||
ClassTraverser it = new ClassTraverser(clazz);
|
||||
LegacyRegistryData registryData;
|
||||
while (it.hasNext()) {
|
||||
registryData = EnumEvil.REGISTRIES.get(it.next());
|
||||
if (registryData != null) {
|
||||
return registryData;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@DoNotReroute
|
||||
public static Registry<?> getRegistry(Class<?> clazz) {
|
||||
LegacyRegistryData registryData = EnumEvil.getRegistryData(clazz);
|
||||
|
||||
if (registryData != null) {
|
||||
return registryData.registry();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/collect/Maps")
|
||||
@RerouteReturnType("java/util/EnumSet")
|
||||
public static ImposterEnumMap newEnumMap(Class<?> objectClass) {
|
||||
return new ImposterEnumMap(objectClass);
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/collect/Maps")
|
||||
@RerouteReturnType("java/util/EnumSet")
|
||||
public static ImposterEnumMap newEnumMap(Map map) {
|
||||
return new ImposterEnumMap(map);
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/collect/Sets")
|
||||
public static Collector<?, ?, ?> toImmutableEnumSet() {
|
||||
return Collectors.toUnmodifiableSet();
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/collect/Sets")
|
||||
@RerouteReturnType("java/util/EnumSet")
|
||||
public static ImposterEnumSet newEnumSet(Iterable<?> iterable, Class<?> clazz) {
|
||||
ImposterEnumSet set = ImposterEnumSet.noneOf(clazz);
|
||||
|
||||
for (Object some : iterable) {
|
||||
set.add(some);
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/collect/Sets")
|
||||
public static ImmutableSet<?> immutableEnumSet(Iterable<?> iterable) {
|
||||
return ImmutableSet.of(iterable);
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/collect/Sets")
|
||||
public static ImmutableSet<?> immutableEnumSet(@RerouteArgumentType("java/lang/Enum") Object first, @RerouteArgumentType("[java/lang/Enum") Object... rest) {
|
||||
return ImmutableSet.of(first, rest);
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/base/Enums")
|
||||
public static Field getField(@RerouteArgumentType("java/lang/Enum") Object value) {
|
||||
if (value instanceof Enum eValue) {
|
||||
return Enums.getField(eValue);
|
||||
}
|
||||
|
||||
try {
|
||||
return value.getClass().getField(((OldEnum) value).name());
|
||||
} catch (NoSuchFieldException impossible) {
|
||||
throw new AssertionError(impossible);
|
||||
}
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/base/Enums")
|
||||
public static com.google.common.base.Optional getIfPresent(Class clazz, String name, @InjectPluginVersion ApiVersion apiVersion) {
|
||||
if (clazz.isEnum()) {
|
||||
return Enums.getIfPresent(clazz, name);
|
||||
}
|
||||
|
||||
Registry registry = EnumEvil.getRegistry(clazz);
|
||||
if (registry == null) {
|
||||
return com.google.common.base.Optional.absent();
|
||||
}
|
||||
|
||||
name = FieldRename.rename(apiVersion, clazz.getName().replace('.', '/'), name);
|
||||
return com.google.common.base.Optional.fromNullable(registry.get(NamespacedKey.fromString(name.toLowerCase(Locale.ROOT))));
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/base/Enums")
|
||||
public static Converter stringConverter(Class clazz, @InjectPluginVersion ApiVersion apiVersion) {
|
||||
if (clazz.isEnum()) {
|
||||
return Enums.stringConverter(clazz);
|
||||
}
|
||||
|
||||
return new StringConverter(apiVersion, clazz);
|
||||
}
|
||||
|
||||
public static Object[] getEnumConstants(Class<?> clazz) {
|
||||
if (clazz.isEnum()) {
|
||||
return clazz.getEnumConstants();
|
||||
}
|
||||
|
||||
Registry<?> registry = EnumEvil.getRegistry(clazz);
|
||||
|
||||
if (registry == null) {
|
||||
return clazz.getEnumConstants();
|
||||
}
|
||||
|
||||
// Need to do this in such away to avoid ClassCastException
|
||||
List<?> values = Lists.newArrayList(registry);
|
||||
Object array = Array.newInstance(clazz, values.size());
|
||||
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
Array.set(array, i, values.get(i));
|
||||
}
|
||||
|
||||
return (Object[]) array;
|
||||
}
|
||||
|
||||
public static String name(@RerouteArgumentType("java/lang/Enum") Object object) {
|
||||
if (object instanceof OldEnum<?>) {
|
||||
return ((OldEnum<?>) object).name();
|
||||
}
|
||||
|
||||
return ((Enum<?>) object).name();
|
||||
}
|
||||
|
||||
public static int compareTo(@RerouteArgumentType("java/lang/Enum") Object object, @RerouteArgumentType("java/lang/Enum") Object other) {
|
||||
if (object instanceof OldEnum<?>) {
|
||||
return ((OldEnum) object).compareTo((OldEnum) other);
|
||||
}
|
||||
|
||||
return ((Enum) object).compareTo((Enum) other);
|
||||
}
|
||||
|
||||
public static Class<?> getDeclaringClass(@RerouteArgumentType("java/lang/Enum") Object object) {
|
||||
Class<?> clazz = object.getClass();
|
||||
Class<?> zuper = clazz.getSuperclass();
|
||||
return (zuper == Enum.class) ? clazz : zuper;
|
||||
}
|
||||
|
||||
public static Optional<Enum.EnumDesc> describeConstable(@RerouteArgumentType("java/lang/Enum") Object object) {
|
||||
return EnumEvil.getDeclaringClass(object)
|
||||
.describeConstable()
|
||||
.map(c -> Enum.EnumDesc.of(c, EnumEvil.name(object)));
|
||||
}
|
||||
|
||||
@RerouteStatic("java/lang/Enum")
|
||||
@RerouteReturnType("java/lang/Enum")
|
||||
public static Object valueOf(Class enumClass, String name, @InjectPluginVersion ApiVersion apiVersion) {
|
||||
name = FieldRename.rename(apiVersion, enumClass.getName().replace('.', '/'), name);
|
||||
LegacyRegistryData registryData = EnumEvil.getRegistryData(enumClass);
|
||||
if (registryData != null) {
|
||||
return registryData.function().apply(name);
|
||||
}
|
||||
|
||||
return Enum.valueOf(enumClass, name);
|
||||
}
|
||||
|
||||
public static String toString(@RerouteArgumentType("java/lang/Enum") Object object) {
|
||||
return object.toString();
|
||||
}
|
||||
|
||||
public static int ordinal(@RerouteArgumentType("java/lang/Enum") Object object) {
|
||||
if (object instanceof OldEnum<?>) {
|
||||
return ((OldEnum<?>) object).ordinal();
|
||||
}
|
||||
|
||||
return ((Enum<?>) object).ordinal();
|
||||
}
|
||||
|
||||
public record LegacyRegistryData(Registry<?> registry, Function<String, ?> function) {
|
||||
}
|
||||
|
||||
private static final class StringConverter<T extends OldEnum<T>> extends Converter<String, T> implements Serializable {
|
||||
|
||||
private final ApiVersion apiVersion;
|
||||
private final Class<T> clazz;
|
||||
private transient LegacyRegistryData registryData;
|
||||
|
||||
StringConverter(ApiVersion apiVersion, Class<T> clazz) {
|
||||
this.apiVersion = apiVersion;
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T doForward(String value) {
|
||||
if (this.registryData == null) {
|
||||
this.registryData = EnumEvil.getRegistryData(this.clazz);
|
||||
}
|
||||
value = FieldRename.rename(this.apiVersion, this.clazz.getName().replace('.', '/'), value);
|
||||
return (T) this.registryData.function().apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doBackward(T enumValue) {
|
||||
return enumValue.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (object instanceof StringConverter<?> that) {
|
||||
return this.clazz.equals(that.clazz);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.clazz.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Enums.stringConverter(" + this.clazz.getName() + ".class)";
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package org.bukkit.craftbukkit.legacy.enums;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* The "I can't believe it works" map.
|
||||
* It replaces every EnumMap with the ImposterEnumMap and uses a HashMap instead of an object array.
|
||||
* Used so that plugins which use an EnumMap still work.
|
||||
*/
|
||||
public class ImposterEnumMap extends AbstractMap<Object, Object> {
|
||||
|
||||
private final Class<?> objectClass;
|
||||
private final Map map;
|
||||
|
||||
public ImposterEnumMap(Class<?> objectClass) {
|
||||
this.objectClass = objectClass;
|
||||
this.map = ImposterEnumMap.getMap(objectClass);
|
||||
}
|
||||
|
||||
public ImposterEnumMap(EnumMap enumMap) {
|
||||
this.objectClass = DummyEnum.class;
|
||||
this.map = enumMap.clone();
|
||||
}
|
||||
|
||||
public ImposterEnumMap(Map map) {
|
||||
if (map instanceof ImposterEnumMap) {
|
||||
this.objectClass = ((ImposterEnumMap) map).objectClass;
|
||||
this.map = ImposterEnumMap.getMap(this.objectClass);
|
||||
} else {
|
||||
this.objectClass = DummyEnum.class;
|
||||
this.map = new TreeMap();
|
||||
}
|
||||
|
||||
this.map.putAll(map);
|
||||
}
|
||||
|
||||
private static Map getMap(Class<?> objectClass) {
|
||||
// Since we replace every enum map we might also replace some maps which are for real enums.
|
||||
// If this is the case use a EnumMap instead of a HashMap
|
||||
if (objectClass.isEnum()) {
|
||||
return new EnumMap(objectClass);
|
||||
} else {
|
||||
return new HashMap();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.map.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return this.map.containsValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return this.map.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(Object key) {
|
||||
return this.map.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object put(Object key, Object value) {
|
||||
this.typeCheck(key);
|
||||
return this.map.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object remove(Object key) {
|
||||
return this.map.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends Object, ?> m) {
|
||||
if (this.map instanceof EnumMap<?, ?>) {
|
||||
this.map.putAll(m);
|
||||
}
|
||||
|
||||
super.putAll(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.map.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Object> keySet() {
|
||||
return this.map.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Object> values() {
|
||||
return this.map.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<Object, Object>> entrySet() {
|
||||
return this.map.entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this.map.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.map.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImposterEnumMap clone() {
|
||||
ImposterEnumMap enumMap = new ImposterEnumMap(this.objectClass);
|
||||
enumMap.putAll(this.map);
|
||||
return enumMap;
|
||||
}
|
||||
|
||||
private void typeCheck(Object object) {
|
||||
if (this.objectClass != DummyEnum.class) {
|
||||
if (!this.objectClass.isAssignableFrom(object.getClass())) {
|
||||
throw new ClassCastException(object.getClass() + " != " + this.objectClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
package org.bukkit.craftbukkit.legacy.enums;
|
||||
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.util.OldEnum;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ImposterEnumSet extends AbstractSet<Object> {
|
||||
|
||||
private final Class<?> objectClass;
|
||||
private final Set set;
|
||||
|
||||
private static Set createSet(Class<?> clazz) {
|
||||
if (clazz.isEnum()) {
|
||||
return EnumSet.noneOf((Class<Enum>) clazz);
|
||||
} else {
|
||||
return new TreeSet();
|
||||
}
|
||||
}
|
||||
|
||||
public static ImposterEnumSet noneOf(Class<?> clazz) {
|
||||
Set set = ImposterEnumSet.createSet(clazz);
|
||||
return new ImposterEnumSet(set, clazz);
|
||||
}
|
||||
|
||||
public static ImposterEnumSet allOf(Class<?> clazz) {
|
||||
Set set;
|
||||
if (clazz.isEnum()) {
|
||||
set = EnumSet.allOf((Class<Enum>) clazz);
|
||||
} else {
|
||||
set = new HashSet();
|
||||
Registry registry = EnumEvil.getRegistry(clazz);
|
||||
if (registry == null) {
|
||||
throw new IllegalArgumentException("Class " + clazz + " is not an Enum nor an OldEnum");
|
||||
}
|
||||
|
||||
for (Object object : registry) {
|
||||
set.add(object);
|
||||
}
|
||||
}
|
||||
return new ImposterEnumSet(set, clazz);
|
||||
}
|
||||
|
||||
public static ImposterEnumSet copyOf(Set set) {
|
||||
Class<?> clazz;
|
||||
if (set instanceof ImposterEnumSet imposter) {
|
||||
set = imposter.set;
|
||||
clazz = imposter.objectClass;
|
||||
} else {
|
||||
if (!set.isEmpty()) {
|
||||
clazz = (Class<?>) set.stream()
|
||||
.filter(val -> val != null)
|
||||
.map(val -> val.getClass())
|
||||
.findAny()
|
||||
.orElse(Object.class);
|
||||
} else {
|
||||
clazz = Object.class;
|
||||
}
|
||||
}
|
||||
|
||||
Set newSet = ImposterEnumSet.createSet(clazz);
|
||||
newSet.addAll(set);
|
||||
|
||||
return new ImposterEnumSet(newSet, clazz);
|
||||
}
|
||||
|
||||
public static ImposterEnumSet copyOf(Collection collection) {
|
||||
Class<?> clazz;
|
||||
if (collection instanceof ImposterEnumSet imposter) {
|
||||
collection = imposter.set;
|
||||
clazz = imposter.objectClass;
|
||||
} else {
|
||||
if (!collection.isEmpty()) {
|
||||
clazz = (Class<?>) collection.stream()
|
||||
.filter(val -> val != null)
|
||||
.map(val -> val.getClass())
|
||||
.findAny()
|
||||
.orElse(Object.class);
|
||||
} else {
|
||||
clazz = Object.class;
|
||||
}
|
||||
}
|
||||
|
||||
Set newSet = ImposterEnumSet.createSet(clazz);
|
||||
newSet.addAll(collection);
|
||||
|
||||
return new ImposterEnumSet(newSet, clazz);
|
||||
}
|
||||
|
||||
public static ImposterEnumSet complementOf(Set set) {
|
||||
Class<?> clazz = null;
|
||||
if (set instanceof ImposterEnumSet imposter) {
|
||||
set = imposter.set;
|
||||
clazz = imposter.objectClass;
|
||||
}
|
||||
|
||||
if (set instanceof EnumSet<?> enumSet) {
|
||||
enumSet = EnumSet.complementOf(enumSet);
|
||||
|
||||
if (clazz != null) {
|
||||
return new ImposterEnumSet(enumSet, clazz);
|
||||
}
|
||||
|
||||
if (!set.isEmpty()) {
|
||||
clazz = (Class<?>) set.stream()
|
||||
.filter(val -> val != null)
|
||||
.map(val -> val.getClass())
|
||||
.findAny()
|
||||
.orElse(Object.class);
|
||||
} else {
|
||||
clazz = (Class<?>) enumSet.stream()
|
||||
.filter(val -> val != null)
|
||||
.map(val -> val.getClass())
|
||||
.map(val -> (Class) val)
|
||||
.findAny()
|
||||
.orElse(Object.class);
|
||||
}
|
||||
|
||||
return new ImposterEnumSet(enumSet, clazz);
|
||||
}
|
||||
|
||||
if (set.isEmpty() && clazz == null) {
|
||||
throw new IllegalStateException("Class is null and set is empty, cannot get class!");
|
||||
}
|
||||
|
||||
if (clazz == null) {
|
||||
clazz = (Class<?>) set.stream()
|
||||
.filter(val -> val != null)
|
||||
.map(val -> val.getClass())
|
||||
.findAny()
|
||||
.orElse(Object.class);
|
||||
}
|
||||
|
||||
Registry registry = EnumEvil.getRegistry(clazz);
|
||||
Set newSet = new HashSet();
|
||||
|
||||
for (Object value : registry) {
|
||||
if (set.contains(value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
newSet.add(value);
|
||||
}
|
||||
|
||||
return new ImposterEnumSet(newSet, clazz);
|
||||
}
|
||||
|
||||
public static ImposterEnumSet of(Object e) {
|
||||
Set set = ImposterEnumSet.createSet(e.getClass());
|
||||
set.add(e);
|
||||
|
||||
return new ImposterEnumSet(set, e.getClass());
|
||||
}
|
||||
|
||||
public static ImposterEnumSet of(Object e1, Object e2) {
|
||||
Set set = ImposterEnumSet.createSet(e1.getClass());
|
||||
set.add(e1);
|
||||
set.add(e2);
|
||||
|
||||
return new ImposterEnumSet(set, e1.getClass());
|
||||
}
|
||||
|
||||
public static ImposterEnumSet of(Object e1, Object e2, Object e3) {
|
||||
Set set = ImposterEnumSet.createSet(e1.getClass());
|
||||
set.add(e1);
|
||||
set.add(e2);
|
||||
set.add(e3);
|
||||
|
||||
return new ImposterEnumSet(set, e1.getClass());
|
||||
}
|
||||
|
||||
|
||||
public static ImposterEnumSet of(Object e1, Object e2, Object e3, Object e4) {
|
||||
Set set = ImposterEnumSet.createSet(e1.getClass());
|
||||
set.add(e1);
|
||||
set.add(e2);
|
||||
set.add(e3);
|
||||
set.add(e4);
|
||||
|
||||
return new ImposterEnumSet(set, e1.getClass());
|
||||
}
|
||||
|
||||
|
||||
public static ImposterEnumSet of(Object e1, Object e2, Object e3, Object e4, Object e5) {
|
||||
Set set = ImposterEnumSet.createSet(e1.getClass());
|
||||
set.add(e1);
|
||||
set.add(e2);
|
||||
set.add(e3);
|
||||
set.add(e4);
|
||||
set.add(e5);
|
||||
|
||||
return new ImposterEnumSet(set, e1.getClass());
|
||||
}
|
||||
|
||||
|
||||
public static ImposterEnumSet of(Object e, Object... rest) {
|
||||
Set set = ImposterEnumSet.createSet(e.getClass());
|
||||
set.add(e);
|
||||
|
||||
Collections.addAll(set, rest);
|
||||
|
||||
return new ImposterEnumSet(set, e.getClass());
|
||||
}
|
||||
|
||||
public static ImposterEnumSet range(Object from, Object to) {
|
||||
Set set;
|
||||
if (from.getClass().isEnum()) {
|
||||
set = EnumSet.range((Enum) from, (Enum) to);
|
||||
} else {
|
||||
set = new HashSet();
|
||||
Registry registry = EnumEvil.getRegistry(from.getClass());
|
||||
for (Object o : registry) {
|
||||
if (((OldEnum) o).ordinal() < ((OldEnum) from).ordinal()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (((OldEnum) o).ordinal() > ((OldEnum) to).ordinal()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
set.add(o);
|
||||
}
|
||||
}
|
||||
|
||||
return new ImposterEnumSet(set, from.getClass());
|
||||
}
|
||||
|
||||
private ImposterEnumSet(Set set, Class<?> objectClass) {
|
||||
this.set = set;
|
||||
this.objectClass = objectClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Object> iterator() {
|
||||
return this.set.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.set.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this.set.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.set.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
return this.set.removeAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.set.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return this.set.contains(o);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return this.set.toArray();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public <T> T[] toArray(@NotNull T[] a) {
|
||||
return (T[]) this.set.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Object o) {
|
||||
this.typeCheck(o);
|
||||
return this.set.add(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return this.set.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(@NotNull Collection<?> c) {
|
||||
return this.set.containsAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(@NotNull Collection<?> c) {
|
||||
if (this.set instanceof EnumSet<?>) {
|
||||
this.set.addAll(c);
|
||||
}
|
||||
|
||||
return super.addAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(@NotNull Collection<?> c) {
|
||||
return this.set.retainAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.set.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.set.toString();
|
||||
}
|
||||
|
||||
public ImposterEnumSet clone() {
|
||||
Set newSet;
|
||||
if (this.set instanceof EnumSet<?> enumSet) {
|
||||
newSet = enumSet.clone();
|
||||
} else {
|
||||
newSet = new HashSet();
|
||||
newSet.addAll(this.set);
|
||||
}
|
||||
|
||||
return new ImposterEnumSet(newSet, this.objectClass);
|
||||
}
|
||||
|
||||
private void typeCheck(Object object) {
|
||||
if (this.objectClass != DummyEnum.class) {
|
||||
if (!this.objectClass.isAssignableFrom(object.getClass())) {
|
||||
throw new ClassCastException(object.getClass() + " != " + this.objectClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,12 +1,12 @@
|
||||
package org.bukkit.craftbukkit.legacy.fieldrename;
|
||||
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.TreeMap;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
|
||||
public record FieldRenameData(RenameData<String> renameData, RenameData<NamespacedKey> keyRenameData) {
|
||||
|
||||
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
package org.bukkit.craftbukkit.legacy.reroute;
|
||||
|
||||
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
|
||||
public record RequirePluginVersionData(ApiVersion minInclusive, ApiVersion maxInclusive) {
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.bukkit.craftbukkit.legacy.reroute;
|
||||
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
import org.bukkit.craftbukkit.util.ClassTraverser;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
+1
-1
@@ -1,7 +1,6 @@
|
||||
package org.bukkit.craftbukkit.legacy.reroute;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -11,6 +10,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
public class RerouteBuilder {
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.bukkit.craftbukkit.potion;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Suppliers;
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -14,6 +13,7 @@ import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.craftbukkit.CraftRegistry;
|
||||
import org.bukkit.craftbukkit.legacy.FieldRename;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package org.bukkit.craftbukkit.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class ApiVersion implements Comparable<ApiVersion>, Serializable {
|
||||
|
||||
public static final ApiVersion CURRENT;
|
||||
public static final ApiVersion FLATTENING;
|
||||
public static final ApiVersion FIELD_NAME_PARITY;
|
||||
public static final ApiVersion ABSTRACT_COW;
|
||||
public static final ApiVersion ABSTRACT_CUBE_MOB;
|
||||
public static final ApiVersion NONE;
|
||||
|
||||
private static final Map<String, ApiVersion> versions;
|
||||
|
||||
static {
|
||||
versions = new HashMap<>();
|
||||
CURRENT = getOrCreateVersion(Versioning.getCurrentApiVersion());
|
||||
FLATTENING = getOrCreateVersion("1.13");
|
||||
FIELD_NAME_PARITY = getOrCreateVersion("1.20.5");
|
||||
ABSTRACT_COW = getOrCreateVersion("1.21.5");
|
||||
ABSTRACT_CUBE_MOB = getOrCreateVersion("26.2");
|
||||
NONE = getOrCreateVersion("none");
|
||||
}
|
||||
|
||||
private final boolean none;
|
||||
private final int major;
|
||||
private final int minor;
|
||||
private final int patch;
|
||||
|
||||
private ApiVersion() {
|
||||
this.none = true;
|
||||
this.major = Integer.MIN_VALUE;
|
||||
this.minor = Integer.MIN_VALUE;
|
||||
this.patch = Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
private ApiVersion(int major, int minor, int patch) {
|
||||
this.none = false;
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
this.patch = patch;
|
||||
}
|
||||
|
||||
public static ApiVersion getOrCreateVersion(String versionString) {
|
||||
if (versionString == null || versionString.trim().isEmpty() || versionString.equalsIgnoreCase("none")) {
|
||||
return ApiVersion.versions.computeIfAbsent("none", s -> new ApiVersion());
|
||||
}
|
||||
|
||||
ApiVersion version = ApiVersion.versions.get(versionString);
|
||||
|
||||
if (version != null) {
|
||||
return version;
|
||||
}
|
||||
|
||||
String[] versionParts = versionString.split("\\.");
|
||||
|
||||
if (versionParts.length != 2 && versionParts.length != 3) {
|
||||
throw new IllegalArgumentException(String.format("API version string should be of format \"major.minor.patch\" or \"major.minor\", where \"major\", \"minor\" and \"patch\" are numbers. For example \"1.18.2\" or \"1.13\", but got '%s' instead.", versionString));
|
||||
}
|
||||
|
||||
int major = ApiVersion.parseNumber(versionParts[0]);
|
||||
int minor = ApiVersion.parseNumber(versionParts[1]);
|
||||
|
||||
int patch;
|
||||
if (versionParts.length == 3) {
|
||||
patch = ApiVersion.parseNumber(versionParts[2]);
|
||||
} else {
|
||||
patch = 0;
|
||||
}
|
||||
|
||||
versionString = ApiVersion.toVersionString(major, minor, patch);
|
||||
return ApiVersion.versions.computeIfAbsent(versionString, s -> new ApiVersion(major, minor, patch));
|
||||
}
|
||||
|
||||
private static int parseNumber(String number) {
|
||||
return Integer.parseInt(number);
|
||||
}
|
||||
|
||||
private static String toVersionString(int major, int minor, int patch) {
|
||||
return major + "." + minor + "." + patch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ApiVersion other) {
|
||||
int result = Integer.compare(this.major, other.major);
|
||||
|
||||
if (result == 0) {
|
||||
result = Integer.compare(this.minor, other.minor);
|
||||
}
|
||||
|
||||
if (result == 0) {
|
||||
result = Integer.compare(this.patch, other.patch);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getVersionString() {
|
||||
if (this.none) {
|
||||
return "none";
|
||||
}
|
||||
|
||||
return ApiVersion.toVersionString(this.major, this.minor, this.patch);
|
||||
}
|
||||
|
||||
public boolean isNewerThan(ApiVersion apiVersion) {
|
||||
return this.compareTo(apiVersion) > 0;
|
||||
}
|
||||
|
||||
public boolean isOlderThan(ApiVersion apiVersion) {
|
||||
return this.compareTo(apiVersion) < 0;
|
||||
}
|
||||
|
||||
public boolean isNewerThanOrSameAs(ApiVersion apiVersion) {
|
||||
return this.compareTo(apiVersion) >= 0;
|
||||
}
|
||||
|
||||
public boolean isOlderThanOrSameAs(ApiVersion apiVersion) {
|
||||
return this.compareTo(apiVersion) <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getVersionString();
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package org.bukkit.craftbukkit.util;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
@@ -21,7 +20,6 @@ import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import javax.annotation.Nonnull;
|
||||
import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
@@ -29,6 +27,7 @@ import org.bukkit.Material;
|
||||
import org.bukkit.craftbukkit.legacy.FieldRename;
|
||||
import org.bukkit.craftbukkit.legacy.MaterialRerouting;
|
||||
import org.bukkit.craftbukkit.legacy.MethodRerouting;
|
||||
import org.bukkit.craftbukkit.legacy.enums.EnumEvil;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.Reroute;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.RerouteArgument;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.RerouteBuilder;
|
||||
@@ -67,6 +66,12 @@ public class Commodore {
|
||||
"org/bukkit/inventory/ItemStack (S)V setDurability"
|
||||
));
|
||||
|
||||
private static final Map<String, String> ENUM_RENAMES = Map.of(
|
||||
"java/lang/Enum", "java/lang/Object",
|
||||
"java/util/EnumSet", "org/bukkit/craftbukkit/legacy/enums/ImposterEnumSet",
|
||||
"java/util/EnumMap", "org/bukkit/craftbukkit/legacy/enums/ImposterEnumMap"
|
||||
);
|
||||
|
||||
private static final Map<String, String> RENAMES = Map.of(
|
||||
"org/bukkit/entity/TextDisplay$TextAligment", "org/bukkit/entity/TextDisplay$TextAlignment", // SPIGOT-7335
|
||||
"org/spigotmc/event/entity/EntityMountEvent", "org/bukkit/event/entity/EntityMountEvent",
|
||||
@@ -114,6 +119,7 @@ public class Commodore {
|
||||
.create(compatibilityPresent)
|
||||
.forClass(FieldRename.class)
|
||||
.forClass(MethodRerouting.class)
|
||||
.forClass(EnumEvil.class)
|
||||
.build();
|
||||
|
||||
this.reroutes.clear();
|
||||
@@ -132,11 +138,6 @@ public class Commodore {
|
||||
return CB_PACKAGE_PREFIX;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private static String getOriginalOrRewrite(@Nonnull String original)
|
||||
{
|
||||
return original;
|
||||
}
|
||||
// Paper end - Plugin rewrites
|
||||
|
||||
public static void main(String[] args) {
|
||||
@@ -284,46 +285,22 @@ public class Commodore {
|
||||
// Paper start - Plugin rewrites
|
||||
@Override
|
||||
public void visitTypeInsn(int opcode, String type) {
|
||||
type = getOriginalOrRewrite(type);
|
||||
|
||||
super.visitTypeInsn(opcode, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
|
||||
for (int i = 0; i < local.length; i++)
|
||||
{
|
||||
if (!(local[i] instanceof String)) { continue; }
|
||||
|
||||
local[i] = getOriginalOrRewrite((String) local[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < stack.length; i++)
|
||||
{
|
||||
if (!(stack[i] instanceof String)) { continue; }
|
||||
|
||||
stack[i] = getOriginalOrRewrite((String) stack[i]);
|
||||
}
|
||||
|
||||
super.visitFrame(type, nLocal, local, nStack, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
|
||||
descriptor = getOriginalOrRewrite(descriptor);
|
||||
|
||||
super.visitLocalVariable(name, descriptor, signature, start, end, index);
|
||||
}
|
||||
// Paper end - Plugin rewrites
|
||||
|
||||
@Override
|
||||
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
|
||||
// Paper start - Rewrite plugins
|
||||
owner = getOriginalOrRewrite(owner);
|
||||
if (desc != null) {
|
||||
desc = getOriginalOrRewrite(desc);
|
||||
}
|
||||
// Paper end
|
||||
name = FieldRename.rename(pluginVersion, owner, name);
|
||||
|
||||
if (modern) {
|
||||
@@ -439,13 +416,6 @@ public class Commodore {
|
||||
return;
|
||||
}
|
||||
|
||||
// Paper start - Rewrite plugins
|
||||
owner = getOriginalOrRewrite(owner);
|
||||
if (desc != null) {
|
||||
desc = getOriginalOrRewrite(desc);
|
||||
}
|
||||
// Paper end - Rewrite plugins
|
||||
|
||||
// Paper start - Rewrite plugins
|
||||
if ((owner.equals("org/bukkit/OfflinePlayer") || owner.equals("org/bukkit/entity/Player")) && name.equals("getPlayerProfile") && desc.equals("()Lorg/bukkit/profile/PlayerProfile;")) {
|
||||
super.visitMethodInsn(opcode, owner, name, "()Lcom/destroystokyo/paper/profile/PlayerProfile;", itf);
|
||||
@@ -485,6 +455,15 @@ public class Commodore {
|
||||
}
|
||||
}
|
||||
|
||||
if (pluginVersion.isOlderThan(ApiVersion.ABSTRACT_CUBE_MOB) && owner.equals("org/bukkit/inventory/meta/BookMeta") && name.equals("pages")) {
|
||||
if (desc.contains("Lnet/kyori/adventure/inventory/Book;")) {
|
||||
desc = desc.replace("Lnet/kyori/adventure/inventory/Book;", "Lorg/bukkit/inventory/meta/BookMeta;");
|
||||
visitor.visit(opcode, owner, name, desc, itf, samMethodType, instantiatedMethodType);
|
||||
visitor.visit(opcode, owner, "asBook", "()Lnet/kyori/adventure/inventory/Book;", itf, samMethodType, instantiatedMethodType);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
visitor.visit(opcode, owner, name, desc, itf, samMethodType, instantiatedMethodType);
|
||||
return;
|
||||
}
|
||||
@@ -572,11 +551,11 @@ public class Commodore {
|
||||
// Paper start
|
||||
if (value instanceof Type type) {
|
||||
if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
|
||||
value = Type.getType(getOriginalOrRewrite(type.getDescriptor()));
|
||||
value = Type.getType(type.getDescriptor());
|
||||
}
|
||||
}
|
||||
// Paper end
|
||||
if (value instanceof String && ((String) value).equals("com.mysql.jdbc.Driver")) {
|
||||
if (value instanceof String && value.equals("com.mysql.jdbc.Driver")) {
|
||||
super.visitLdcInsn("com.mysql.cj.jdbc.Driver");
|
||||
return;
|
||||
}
|
||||
@@ -586,14 +565,6 @@ public class Commodore {
|
||||
|
||||
@Override
|
||||
public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {
|
||||
// Paper start - Rewrite plugins
|
||||
name = getOriginalOrRewrite(name);
|
||||
if (descriptor != null) {
|
||||
descriptor = getOriginalOrRewrite(descriptor);
|
||||
}
|
||||
final String fName = name;
|
||||
final String fDescriptor = descriptor;
|
||||
// Paper end - Rewrite plugins
|
||||
if (bootstrapMethodHandle.getOwner().equals("java/lang/invoke/LambdaMetafactory")
|
||||
&& bootstrapMethodHandle.getName().equals("metafactory") && bootstrapMethodArguments.length == 3) {
|
||||
Type samMethodType = (Type) bootstrapMethodArguments[0];
|
||||
@@ -610,7 +581,7 @@ public class Commodore {
|
||||
methodArgs.add(new Handle(newOpcode, newOwner, newName, newDescription, newItf));
|
||||
methodArgs.add(newInstantiated);
|
||||
|
||||
super.visitInvokeDynamicInsn(fName, fDescriptor, bootstrapMethodHandle, methodArgs.toArray(Object[]::new)); // Paper - use final local vars
|
||||
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, methodArgs.toArray(Object[]::new));
|
||||
}, implMethod.getTag(), implMethod.getOwner(), implMethod.getName(), implMethod.getDesc(), implMethod.isInterface(), samMethodType, instantiatedMethodType);
|
||||
return;
|
||||
}
|
||||
@@ -661,12 +632,6 @@ public class Commodore {
|
||||
|
||||
@Override
|
||||
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
|
||||
// Paper start - Rewrite plugins
|
||||
descriptor = getOriginalOrRewrite(descriptor);
|
||||
if (signature != null) {
|
||||
signature = getOriginalOrRewrite(signature);
|
||||
}
|
||||
// Paper end
|
||||
return new FieldVisitor(this.api, super.visitField(access, name, descriptor, signature, value)) {
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
|
||||
|
||||
@@ -19,8 +19,6 @@ import io.papermc.paper.adventure.PaperAdventure;
|
||||
import io.papermc.paper.datapack.DynamicBuiltinPack;
|
||||
import io.papermc.paper.datapack.DynamicBuiltinPacks;
|
||||
import io.papermc.paper.entity.EntitySerializationFlag;
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import io.papermc.paper.plugin.entrypoint.classloader.ClassloaderBytecodeModifier;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
import io.papermc.paper.util.MCUtil;
|
||||
import java.io.IOException;
|
||||
@@ -423,7 +421,6 @@ public final class CraftMagicNumbers implements UnsafeValues {
|
||||
// Paper end
|
||||
try {
|
||||
clazz = this.commodore.convert(clazz, pdf.getName(), ApiVersion.getOrCreateVersion(pdf.getAPIVersion()), ((CraftServer) Bukkit.getServer()).activeCompatibilities);
|
||||
clazz = ClassloaderBytecodeModifier.bytecodeModifier().modify(pdf, clazz);
|
||||
} catch (Exception ex) {
|
||||
Bukkit.getLogger().log(Level.SEVERE, "Fatal error trying to convert " + pdf.getFullName() + ":" + path, ex);
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
io.papermc.paper.plugin.entrypoint.classloader.bytecode.PaperClassloaderBytecodeModifier
|
||||
io.papermc.paper.plugin.entrypoint.classloader.PaperClassloaderBytecodeModifier
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
package io.papermc.paper.inventory.recipe;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import net.minecraft.world.entity.player.StackedContents;
|
||||
import net.minecraft.world.entity.player.StackedItemContents;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import net.minecraft.world.item.crafting.PlacementInfo;
|
||||
import net.minecraft.world.item.crafting.Recipe;
|
||||
import org.bukkit.support.environment.VanillaFeature;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@VanillaFeature
|
||||
class PredicateChoiceMatchingTest {
|
||||
|
||||
private static Ingredient regular(final net.minecraft.world.item.Item item) {
|
||||
return Ingredient.of(item);
|
||||
}
|
||||
|
||||
private static Ingredient exact(final ItemStack... stacks) {
|
||||
return Ingredient.ofStacks(List.of(stacks));
|
||||
}
|
||||
|
||||
private static Ingredient predicate(final Predicate<ItemStack> pred, final ItemStack example) {
|
||||
final Ingredient ingredient = Ingredient.ofStacks(List.of(example));
|
||||
ingredient.stackPredicate = pred;
|
||||
return ingredient;
|
||||
}
|
||||
|
||||
private static ItemStack damaged(final net.minecraft.world.item.Item item, final int damage) {
|
||||
final ItemStack stack = new ItemStack(item);
|
||||
stack.setDamageValue(damage);
|
||||
return stack;
|
||||
}
|
||||
|
||||
private record Outcome(boolean canCraftOne, int biggestStack, int effectiveBatch, List<ItemOrExact> itemsUsed) {}
|
||||
|
||||
private static Outcome run(final List<Ingredient> ingredients, final List<ItemStack> inventory) {
|
||||
final StackedItemContents contents = new StackedItemContents();
|
||||
final PlacementInfo placementInfo = PlacementInfo.create(ingredients);
|
||||
final Recipe<?> recipe = mock(Recipe.class);
|
||||
when(recipe.placementInfo()).thenReturn(placementInfo);
|
||||
|
||||
contents.initializeExtras(recipe, null);
|
||||
for (final ItemStack stack : inventory) {
|
||||
contents.accountSimpleStack(stack);
|
||||
}
|
||||
|
||||
final boolean canCraftOne = contents.canCraft(recipe, null);
|
||||
final int biggest = contents.getBiggestCraftableStack(recipe, null);
|
||||
|
||||
final List<ItemOrExact> itemsUsed = new ArrayList<>();
|
||||
int effectiveBatch = 0;
|
||||
if (biggest > 0) {
|
||||
contents.canCraft(recipe, biggest, itemsUsed::add);
|
||||
effectiveBatch = biggest;
|
||||
for (final ItemOrExact used : itemsUsed) {
|
||||
effectiveBatch = Math.min(effectiveBatch, used.getMaxStackSize());
|
||||
}
|
||||
}
|
||||
return new Outcome(canCraftOne, biggest, effectiveBatch, itemsUsed);
|
||||
}
|
||||
|
||||
@Test
|
||||
void regularIngredient_countsNormally() {
|
||||
final Outcome o = run(List.of(regular(Items.STONE)), List.of(new ItemStack(Items.STONE, 64)));
|
||||
assertTrue(o.canCraftOne(), "one stone should satisfy a stone ingredient");
|
||||
assertEquals(64, o.biggestStack(), "64 stone -> 64 craftable");
|
||||
}
|
||||
|
||||
@Test
|
||||
void exactIngredient_matchesByComponents() {
|
||||
final ItemStack named = new ItemStack(Items.STONE);
|
||||
named.setDamageValue(0);
|
||||
final Outcome o = run(List.of(exact(new ItemStack(Items.DIAMOND, 1))), List.of(new ItemStack(Items.DIAMOND, 5)));
|
||||
assertTrue(o.canCraftOne());
|
||||
assertEquals(5, o.biggestStack());
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicate_singleIngredient_stackableItems() {
|
||||
final Outcome o = run(
|
||||
List.of(predicate(s -> s.is(Items.STONE), new ItemStack(Items.STONE))),
|
||||
List.of(new ItemStack(Items.STONE, 64))
|
||||
);
|
||||
assertTrue(o.canCraftOne(), "stone matches the predicate");
|
||||
assertEquals(64, o.biggestStack(), "64 stackable matching items -> 64 craftable");
|
||||
assertEquals(64, o.effectiveBatch());
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicate_distinctNonStackingItems() {
|
||||
// Two DIFFERENT damaged pickaxes (different components -> distinct Exact keys, maxStack 1).
|
||||
final Outcome o = run(
|
||||
List.of(predicate(s -> s.is(Items.DIAMOND_PICKAXE), new ItemStack(Items.DIAMOND_PICKAXE))),
|
||||
List.of(damaged(Items.DIAMOND_PICKAXE, 10), damaged(Items.DIAMOND_PICKAXE, 20))
|
||||
);
|
||||
assertTrue(o.canCraftOne(), "either pickaxe can satisfy the single ingredient");
|
||||
// Two separate single crafts are possible, but never as one stacked batch (maxStack 1).
|
||||
assertEquals(1, o.effectiveBatch(), "distinct non-stacking items cannot be crafted as a batch");
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicate_identicalNonStackingItems_summing() {
|
||||
// Three IDENTICAL damaged pickaxes: same components -> ONE Exact key with count 3,
|
||||
// even though they are physically non-stackable (maxStack 1). This is the "it thinks it
|
||||
// can stack all the matching items together" case.
|
||||
final Outcome o = run(
|
||||
List.of(predicate(s -> s.is(Items.DIAMOND_PICKAXE), new ItemStack(Items.DIAMOND_PICKAXE))),
|
||||
List.of(damaged(Items.DIAMOND_PICKAXE, 100), damaged(Items.DIAMOND_PICKAXE, 100), damaged(Items.DIAMOND_PICKAXE, 100))
|
||||
);
|
||||
assertTrue(o.canCraftOne());
|
||||
// getBiggestCraftableStack ignores per-item max stack size, so it reports 3 ...
|
||||
assertEquals(3, o.biggestStack(), "raw picker sums the identical items");
|
||||
// ... but the effective (placeable) batch must clamp to the real max stack size of 1.
|
||||
assertEquals(1, o.effectiveBatch(), "cannot actually place a batch bigger than maxStackSize");
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicateAndRegular_shareItem_shouldStillCraft() {
|
||||
// Shapeless recipe: [ any-stone predicate , stone ]. Two stone in inventory is enough:
|
||||
// one satisfies the predicate slot, one satisfies the regular slot.
|
||||
final Outcome o = run(
|
||||
List.of(predicate(s -> s.is(Items.STONE), new ItemStack(Items.STONE)), regular(Items.STONE)),
|
||||
List.of(new ItemStack(Items.STONE, 2))
|
||||
);
|
||||
assertTrue(o.canCraftOne(), "2 stone should satisfy a predicate-stone + regular-stone recipe");
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicateAndRegular_singleItem_correctlyUncraftable() {
|
||||
final Outcome o = run(
|
||||
List.of(predicate(s -> s.is(Items.STONE), new ItemStack(Items.STONE)), regular(Items.STONE)),
|
||||
List.of(new ItemStack(Items.STONE, 1))
|
||||
);
|
||||
assertFalse(o.canCraftOne(), "one stone cannot fill both a predicate and a regular slot");
|
||||
}
|
||||
|
||||
@Test
|
||||
void twoPredicates_sharedPool_halveCorrectly() {
|
||||
final Predicate<ItemStack> isStone = s -> s.is(Items.STONE);
|
||||
final Outcome o = run(
|
||||
List.of(predicate(isStone, new ItemStack(Items.STONE)), predicate(isStone, new ItemStack(Items.STONE))),
|
||||
List.of(new ItemStack(Items.STONE, 64))
|
||||
);
|
||||
assertTrue(o.canCraftOne());
|
||||
assertEquals(32, o.biggestStack(), "64 stone across two ingredient slots -> 32 crafts");
|
||||
}
|
||||
|
||||
@Test
|
||||
void initialize_clearsStalePredicateIngredients() {
|
||||
// initialize() rebuilds the extras from a recipe. It clears exactIngredients but must ALSO
|
||||
// clear predicateIngredients, otherwise a reused extras map (e.g. a cached
|
||||
// CraftingInput#stackedContents whose resetExtras was skipped because amounts was empty)
|
||||
// carries a previous recipe's predicates into the next recipe's matching.
|
||||
final StackedContents<ItemOrExact> raw = new StackedContents<>();
|
||||
final StackedContentsExtrasMap extras = new StackedContentsExtrasMap(raw);
|
||||
|
||||
final Recipe<?> withPredicate = mock(Recipe.class);
|
||||
when(withPredicate.placementInfo()).thenReturn(PlacementInfo.create(List.of(
|
||||
predicate(s -> s.is(Items.STONE), new ItemStack(Items.STONE))
|
||||
)));
|
||||
extras.initialize(withPredicate);
|
||||
assertEquals(1, extras.predicateIngredients.size(), "the predicate was registered");
|
||||
|
||||
// Re-initialize for a predicate-free recipe, mirroring a reuse where resetExtras did not run.
|
||||
final Recipe<?> noPredicate = mock(Recipe.class);
|
||||
when(noPredicate.placementInfo()).thenReturn(PlacementInfo.create(List.of(regular(Items.DIRT))));
|
||||
extras.initialize(noPredicate);
|
||||
assertEquals(0, extras.predicateIngredients.size(), "initialize() must drop the stale predicate");
|
||||
}
|
||||
|
||||
@Test
|
||||
void twoPredicates_notEnough() {
|
||||
final Predicate<ItemStack> isStone = s -> s.is(Items.STONE);
|
||||
final Outcome o = run(
|
||||
List.of(predicate(isStone, new ItemStack(Items.STONE)), predicate(isStone, new ItemStack(Items.STONE))),
|
||||
List.of(new ItemStack(Items.STONE, 1))
|
||||
);
|
||||
assertFalse(o.canCraftOne(), "one stone cannot fill two predicate slots");
|
||||
}
|
||||
|
||||
// ---- lifecycle / leak ----------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void predicateIngredients_doNotLeakAcrossReuse() {
|
||||
// Reuse ONE StackedItemContents across two recipe checks, mirroring how a cached
|
||||
// CraftingInput#stackedContents() is reused. initialize() clears exactIngredients but not
|
||||
// predicateIngredients, and resetExtras() is skipped when amounts is empty.
|
||||
final StackedItemContents contents = new StackedItemContents();
|
||||
|
||||
// Recipe 1: predicate matching DIAMOND. No matching items accounted -> amounts stays empty.
|
||||
final Recipe<?> recipe1 = mock(Recipe.class);
|
||||
when(recipe1.placementInfo()).thenReturn(PlacementInfo.create(List.of(
|
||||
predicate(s -> s.is(Items.DIAMOND), new ItemStack(Items.DIAMOND))
|
||||
)));
|
||||
contents.initializeExtras(recipe1, null);
|
||||
contents.resetExtras(); // amounts empty -> this is a no-op, predicate list NOT cleared
|
||||
contents.clear();
|
||||
|
||||
// Recipe 2: a plain regular DIRT recipe, no predicates at all.
|
||||
final Recipe<?> recipe2 = mock(Recipe.class);
|
||||
when(recipe2.placementInfo()).thenReturn(PlacementInfo.create(List.of(regular(Items.DIRT))));
|
||||
contents.initializeExtras(recipe2, null);
|
||||
|
||||
// Account a DIAMOND. For recipe2 it should be irrelevant regular content. If the stale
|
||||
// DIAMOND predicate leaked, the diamond gets mis-routed into an Exact key.
|
||||
contents.accountSimpleStack(new ItemStack(Items.DIAMOND, 1));
|
||||
// Recipe2 needs DIRT which we do not have -> must be uncraftable regardless.
|
||||
assertFalse(contents.canCraft(recipe2, null), "a diamond must not craft a dirt recipe");
|
||||
}
|
||||
}
|
||||
+804
@@ -0,0 +1,804 @@
|
||||
package io.papermc.paper.inventory.recipe;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.world.SimpleContainer;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.PlayerEquipment;
|
||||
import net.minecraft.world.entity.player.StackedItemContents;
|
||||
import net.minecraft.world.inventory.RecipeBookMenu;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.crafting.AbstractCookingRecipe;
|
||||
import net.minecraft.world.item.crafting.CraftingRecipe;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import net.minecraft.world.item.crafting.PlacementInfo;
|
||||
import net.minecraft.world.item.crafting.RecipeHolder;
|
||||
import net.minecraft.world.item.crafting.ShapedRecipe;
|
||||
import net.minecraft.recipebook.ServerPlaceRecipe;
|
||||
import org.bukkit.support.environment.VanillaFeature;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@VanillaFeature
|
||||
class RecipeAutofillPlacementTest {
|
||||
|
||||
private static final boolean CRAFT_ONE = false;
|
||||
private static final boolean CRAFT_MAX = true;
|
||||
|
||||
// ---- ingredient builders -------------------------------------------------------------------
|
||||
|
||||
private static Ingredient regular(final Item item) {
|
||||
return Ingredient.of(item);
|
||||
}
|
||||
|
||||
private static Ingredient exact(final ItemStack... stacks) {
|
||||
return Ingredient.ofStacks(List.of(stacks));
|
||||
}
|
||||
|
||||
private static Ingredient predicate(final Predicate<ItemStack> pred, final ItemStack example) {
|
||||
final Ingredient ingredient = Ingredient.ofStacks(List.of(example));
|
||||
ingredient.stackPredicate = pred;
|
||||
return ingredient;
|
||||
}
|
||||
|
||||
private static ItemStack stack(final Item item, final int count) {
|
||||
return new ItemStack(item, count);
|
||||
}
|
||||
|
||||
private static ItemStack damaged(final Item item, final int damage) {
|
||||
final ItemStack s = new ItemStack(item);
|
||||
s.setDamageValue(damage);
|
||||
return s;
|
||||
}
|
||||
|
||||
private static ItemStack renamed(final Item item, final int count, final String name) {
|
||||
final ItemStack s = new ItemStack(item, count);
|
||||
s.set(DataComponents.CUSTOM_NAME, net.minecraft.network.chat.Component.literal(name));
|
||||
return s;
|
||||
}
|
||||
|
||||
private record PlaceResult(RecipeBookMenu.PostPlaceAction action, List<ItemStack> grid, Inventory inventory) {
|
||||
boolean placed() {
|
||||
return this.action == RecipeBookMenu.PostPlaceAction.NOTHING;
|
||||
}
|
||||
|
||||
boolean ghost() {
|
||||
return this.action == RecipeBookMenu.PostPlaceAction.PLACE_GHOST_RECIPE;
|
||||
}
|
||||
|
||||
int gridCount(final Item item) {
|
||||
int total = 0;
|
||||
for (final ItemStack s : this.grid) {
|
||||
if (s.is(item)) {
|
||||
total += s.getCount();
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
int inventoryCount(final Item item) {
|
||||
int total = 0;
|
||||
for (final ItemStack s : this.inventory.getNonEquipmentItems()) {
|
||||
if (s.is(item)) {
|
||||
total += s.getCount();
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
long filledSlots() {
|
||||
return this.grid.stream().filter(s -> !s.isEmpty()).count();
|
||||
}
|
||||
}
|
||||
|
||||
private static PlaceResult autofill(
|
||||
final List<Ingredient> ingredients, final int gridW, final int gridH,
|
||||
final List<ItemStack> inventory, final boolean useMaxItems
|
||||
) {
|
||||
final CraftingRecipe recipe = mock(CraftingRecipe.class);
|
||||
when(recipe.placementInfo()).thenReturn(PlacementInfo.create(ingredients));
|
||||
return place(recipe, gridW, gridH, inventory, useMaxItems, List.of(), false);
|
||||
}
|
||||
|
||||
private static PlaceResult autofillShaped(
|
||||
final List<Ingredient> ingredients, final int recipeW, final int recipeH,
|
||||
final int gridW, final int gridH, final List<ItemStack> inventory, final boolean useMaxItems
|
||||
) {
|
||||
final ShapedRecipe recipe = mock(ShapedRecipe.class);
|
||||
when(recipe.getWidth()).thenReturn(recipeW);
|
||||
when(recipe.getHeight()).thenReturn(recipeH);
|
||||
when(recipe.placementInfo()).thenReturn(PlacementInfo.create(ingredients));
|
||||
return place(recipe, gridW, gridH, inventory, useMaxItems, List.of(), false);
|
||||
}
|
||||
|
||||
private static PlaceResult autofillPattern(
|
||||
final List<Optional<Ingredient>> pattern, final int recipeW, final int recipeH,
|
||||
final int gridW, final int gridH, final List<ItemStack> inventory, final boolean useMaxItems
|
||||
) {
|
||||
final ShapedRecipe recipe = mock(ShapedRecipe.class);
|
||||
when(recipe.getWidth()).thenReturn(recipeW);
|
||||
when(recipe.getHeight()).thenReturn(recipeH);
|
||||
when(recipe.placementInfo()).thenReturn(PlacementInfo.createFromOptionals(pattern));
|
||||
return place(recipe, gridW, gridH, inventory, useMaxItems, List.of(), false);
|
||||
}
|
||||
|
||||
private static PlaceResult autofillFurnace(
|
||||
final Ingredient ingredient, final List<ItemStack> inventory, final boolean useMaxItems
|
||||
) {
|
||||
final Inventory inv = new Inventory(null, new PlayerEquipment(null));
|
||||
for (int i = 0; i < inventory.size(); i++) {
|
||||
inv.setItem(i, inventory.get(i).copy());
|
||||
}
|
||||
|
||||
final SimpleContainer furnace = new SimpleContainer(3); // input=0, fuel=1, result=2
|
||||
final Slot inputSlot = new Slot(furnace, 0, 0, 0);
|
||||
final Slot resultSlot = new Slot(furnace, 2, 0, 0);
|
||||
final List<Slot> clear = List.of(inputSlot, resultSlot);
|
||||
|
||||
final AbstractCookingRecipe recipe = mock(AbstractCookingRecipe.class);
|
||||
when(recipe.placementInfo()).thenReturn(PlacementInfo.create(List.of(ingredient)));
|
||||
final RecipeHolder<AbstractCookingRecipe> holder = new RecipeHolder<>(null, recipe);
|
||||
|
||||
final ServerPlaceRecipe.CraftingMenuAccess<AbstractCookingRecipe> menu = new ServerPlaceRecipe.CraftingMenuAccess<>() {
|
||||
@Override
|
||||
public void fillCraftSlotsStackedContents(final StackedItemContents stackedContents) {
|
||||
if (!inputSlot.getItem().isEmpty()) {
|
||||
stackedContents.accountSimpleStack(inputSlot.getItem());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCraftingContent() {
|
||||
clear.forEach(s -> s.set(ItemStack.EMPTY));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean recipeMatches(final RecipeHolder<AbstractCookingRecipe> recipe) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
final RecipeBookMenu.PostPlaceAction action = ServerPlaceRecipe.placeRecipe(
|
||||
menu, 1, 1, List.of(inputSlot), clear, inv, holder, useMaxItems, true
|
||||
);
|
||||
return new PlaceResult(action, List.of(inputSlot.getItem()), inv);
|
||||
}
|
||||
|
||||
private static PlaceResult place(
|
||||
final CraftingRecipe recipe, final int gridW, final int gridH,
|
||||
final List<ItemStack> inventory, final boolean useMaxItems,
|
||||
final List<ItemStack> presetGrid, final boolean recipeMatches
|
||||
) {
|
||||
final Inventory inv = new Inventory(null, new PlayerEquipment(null));
|
||||
for (int i = 0; i < inventory.size(); i++) {
|
||||
inv.setItem(i, inventory.get(i).copy());
|
||||
}
|
||||
|
||||
final SimpleContainer gridContainer = new SimpleContainer(gridW * gridH);
|
||||
for (int i = 0; i < presetGrid.size(); i++) {
|
||||
gridContainer.setItem(i, presetGrid.get(i).copy());
|
||||
}
|
||||
final List<Slot> slots = new ArrayList<>();
|
||||
for (int i = 0; i < gridW * gridH; i++) {
|
||||
slots.add(new Slot(gridContainer, i, 0, 0));
|
||||
}
|
||||
|
||||
final RecipeHolder<CraftingRecipe> holder = new RecipeHolder<>(null, recipe);
|
||||
|
||||
final ServerPlaceRecipe.CraftingMenuAccess<CraftingRecipe> menu = new ServerPlaceRecipe.CraftingMenuAccess<>() {
|
||||
@Override
|
||||
public void fillCraftSlotsStackedContents(final StackedItemContents stackedContents) {
|
||||
for (final Slot slot : slots) {
|
||||
if (!slot.getItem().isEmpty()) {
|
||||
stackedContents.accountSimpleStack(slot.getItem());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCraftingContent() {
|
||||
gridContainer.clearContent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean recipeMatches(final RecipeHolder<CraftingRecipe> recipe) {
|
||||
return recipeMatches;
|
||||
}
|
||||
};
|
||||
|
||||
final RecipeBookMenu.PostPlaceAction action = ServerPlaceRecipe.placeRecipe(
|
||||
menu, gridW, gridH, slots, slots, inv, holder, useMaxItems, true
|
||||
);
|
||||
|
||||
final List<ItemStack> gridOut = new ArrayList<>();
|
||||
for (final Slot slot : slots) {
|
||||
gridOut.add(slot.getItem());
|
||||
}
|
||||
return new PlaceResult(action, gridOut, inv);
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// Category A — regular ingredients (baseline; must always pass)
|
||||
// ============================================================================================
|
||||
|
||||
@Test
|
||||
void regular_single_craftOne() {
|
||||
final PlaceResult r = autofill(List.of(regular(Items.STONE)), 1, 1, List.of(stack(Items.STONE, 64)), CRAFT_ONE);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(1, r.gridCount(Items.STONE), "one placed in the grid");
|
||||
assertEquals(63, r.inventoryCount(Items.STONE), "one consumed from inventory");
|
||||
}
|
||||
|
||||
@Test
|
||||
void regular_single_craftMax_fillsStack() {
|
||||
final PlaceResult r = autofill(List.of(regular(Items.STONE)), 1, 1, List.of(stack(Items.STONE, 64)), CRAFT_MAX);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(64, r.gridCount(Items.STONE), "whole stack moved into the single grid slot");
|
||||
assertEquals(0, r.inventoryCount(Items.STONE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void regular_twoDistinctIngredients_craftMax() {
|
||||
final PlaceResult r = autofill(
|
||||
List.of(regular(Items.STONE), regular(Items.DIRT)), 2, 1,
|
||||
List.of(stack(Items.STONE, 64), stack(Items.DIRT, 64)), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(64, r.gridCount(Items.STONE));
|
||||
assertEquals(64, r.gridCount(Items.DIRT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void regular_sharedPool_twoSlotsSameItem_craftMax_halves() {
|
||||
// 64 stone across two ingredient slots -> 32 per slot (32 crafts).
|
||||
final PlaceResult r = autofill(
|
||||
List.of(regular(Items.STONE), regular(Items.STONE)), 2, 1,
|
||||
List.of(stack(Items.STONE, 64)), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(2, r.filledSlots());
|
||||
assertEquals(32, r.grid.get(0).getCount());
|
||||
assertEquals(32, r.grid.get(1).getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void regular_notEnough_ghost() {
|
||||
final PlaceResult r = autofill(
|
||||
List.of(regular(Items.STONE), regular(Items.DIRT)), 2, 1,
|
||||
List.of(stack(Items.STONE, 1)), CRAFT_ONE
|
||||
);
|
||||
assertTrue(r.ghost(), "missing dirt -> ghost recipe, nothing placed");
|
||||
assertEquals(0, r.filledSlots());
|
||||
assertEquals(1, r.inventoryCount(Items.STONE), "inventory untouched");
|
||||
}
|
||||
|
||||
@Test
|
||||
void regular_nonStackableItem_craftMax_clampsToOne() {
|
||||
// Undamaged pickaxe is usable for crafting; maxStack 1 -> batch clamps to 1.
|
||||
final PlaceResult r = autofill(
|
||||
List.of(regular(Items.DIAMOND_PICKAXE)), 1, 1,
|
||||
List.of(stack(Items.DIAMOND_PICKAXE, 1), stack(Items.DIAMOND_PICKAXE, 1)), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(1, r.gridCount(Items.DIAMOND_PICKAXE));
|
||||
assertEquals(1, r.inventoryCount(Items.DIAMOND_PICKAXE), "only one pickaxe consumed");
|
||||
}
|
||||
|
||||
@Test
|
||||
void regular_multiStackInventory_craftMax() {
|
||||
// Stone spread across three inventory slots (64+64+32) -> up to 64 into one grid slot.
|
||||
final PlaceResult r = autofill(
|
||||
List.of(regular(Items.STONE)), 1, 1,
|
||||
List.of(stack(Items.STONE, 64), stack(Items.STONE, 64), stack(Items.STONE, 32)), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(64, r.gridCount(Items.STONE), "one full stack into the slot");
|
||||
assertEquals(96, r.inventoryCount(Items.STONE), "160 - 64 remain");
|
||||
}
|
||||
|
||||
@Test
|
||||
void regular_fullThreeByThree() {
|
||||
final List<Ingredient> ing = new ArrayList<>();
|
||||
for (int i = 0; i < 9; i++) {
|
||||
ing.add(regular(Items.STONE));
|
||||
}
|
||||
final PlaceResult r = autofill(ing, 3, 3, List.of(stack(Items.STONE, 9)), CRAFT_ONE);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(9, r.filledSlots());
|
||||
assertEquals(9, r.gridCount(Items.STONE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void regular_damagedItem_notUsable_ghost() {
|
||||
// Damaged items are excluded from regular recipe-book crafting.
|
||||
final PlaceResult r = autofill(
|
||||
List.of(regular(Items.DIAMOND_PICKAXE)), 1, 1,
|
||||
List.of(damaged(Items.DIAMOND_PICKAXE, 50)), CRAFT_ONE
|
||||
);
|
||||
assertTrue(r.ghost(), "a damaged pickaxe is not usable for a regular ingredient");
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// Category B — exact ingredients (baseline; user says these already work)
|
||||
// ============================================================================================
|
||||
|
||||
@Test
|
||||
void exact_matchesOnlySameComponents() {
|
||||
final ItemStack named = renamed(Items.DIAMOND, 1, "Special");
|
||||
final PlaceResult r = autofill(
|
||||
List.of(exact(named.copy())), 1, 1,
|
||||
List.of(stack(Items.DIAMOND, 5), named.copy()), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(1, r.gridCount(Items.DIAMOND), "only the named diamond matches; maxStack irrelevant here");
|
||||
// The plain diamonds must be left untouched.
|
||||
assertTrue(r.grid.get(0).has(DataComponents.CUSTOM_NAME), "the exact (named) item was placed");
|
||||
}
|
||||
|
||||
@Test
|
||||
void exact_stackable_craftMax() {
|
||||
final PlaceResult r = autofill(
|
||||
List.of(exact(stack(Items.DIAMOND, 1))), 1, 1,
|
||||
List.of(stack(Items.DIAMOND, 40)), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(40, r.gridCount(Items.DIAMOND));
|
||||
}
|
||||
|
||||
@Test
|
||||
void exact_multipleAllowedStacks() {
|
||||
final PlaceResult r = autofill(
|
||||
List.of(exact(stack(Items.DIAMOND, 1), stack(Items.EMERALD, 1))), 1, 1,
|
||||
List.of(stack(Items.EMERALD, 3)), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(3, r.gridCount(Items.EMERALD));
|
||||
}
|
||||
|
||||
@Test
|
||||
void exact_nonStackable_craftMax_clampsToOne() {
|
||||
final ItemStack tool = damaged(Items.DIAMOND_PICKAXE, 7);
|
||||
final PlaceResult r = autofill(
|
||||
List.of(exact(tool.copy())), 1, 1,
|
||||
List.of(tool.copy(), tool.copy()), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(1, r.gridCount(Items.DIAMOND_PICKAXE), "non-stackable exact -> one per slot");
|
||||
assertEquals(1, r.inventoryCount(Items.DIAMOND_PICKAXE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void exact_twoSlotsSharedPool_craftMax_halves() {
|
||||
final PlaceResult r = autofill(
|
||||
List.of(exact(stack(Items.DIAMOND, 1)), exact(stack(Items.DIAMOND, 1))), 2, 1,
|
||||
List.of(stack(Items.DIAMOND, 10)), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(5, r.grid.get(0).getCount());
|
||||
assertEquals(5, r.grid.get(1).getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void exact_notPresent_ghost() {
|
||||
final PlaceResult r = autofill(
|
||||
List.of(exact(renamed(Items.DIAMOND, 1, "Special"))), 1, 1,
|
||||
List.of(stack(Items.DIAMOND, 5)), CRAFT_ONE
|
||||
);
|
||||
assertTrue(r.ghost(), "plain diamonds do not satisfy an exact named-diamond ingredient");
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// Category C — mixed regular + exact (baseline)
|
||||
// ============================================================================================
|
||||
|
||||
@Test
|
||||
void mixed_regularAndExact_distinctItems() {
|
||||
final PlaceResult r = autofill(
|
||||
List.of(regular(Items.STICK), exact(stack(Items.DIAMOND, 1))), 2, 1,
|
||||
List.of(stack(Items.STICK, 8), stack(Items.DIAMOND, 8)), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(8, r.gridCount(Items.STICK));
|
||||
assertEquals(8, r.gridCount(Items.DIAMOND));
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// Category D — predicate ingredients (bug-hunting; document current behaviour)
|
||||
// ============================================================================================
|
||||
|
||||
@Test
|
||||
void predicate_single_stackable_craftMax() {
|
||||
final PlaceResult r = autofill(
|
||||
List.of(predicate(s -> s.is(Items.STONE), stack(Items.STONE, 1))), 1, 1,
|
||||
List.of(stack(Items.STONE, 64)), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(64, r.gridCount(Items.STONE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicate_single_craftOne_pullsMatchingItem() {
|
||||
final PlaceResult r = autofill(
|
||||
List.of(predicate(s -> s.is(Items.DIAMOND_PICKAXE), stack(Items.DIAMOND_PICKAXE, 1))), 1, 1,
|
||||
List.of(damaged(Items.DIAMOND_PICKAXE, 10)), CRAFT_ONE
|
||||
);
|
||||
assertTrue(r.placed(), "the damaged pickaxe satisfies the predicate even though it is not usable for regular crafting");
|
||||
assertEquals(1, r.gridCount(Items.DIAMOND_PICKAXE));
|
||||
assertEquals(0, r.inventoryCount(Items.DIAMOND_PICKAXE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicate_distinctNonStacking_craftMax_placesOne() {
|
||||
final PlaceResult r = autofill(
|
||||
List.of(predicate(s -> s.is(Items.DIAMOND_PICKAXE), stack(Items.DIAMOND_PICKAXE, 1))), 1, 1,
|
||||
List.of(damaged(Items.DIAMOND_PICKAXE, 10), damaged(Items.DIAMOND_PICKAXE, 20)), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(1, r.gridCount(Items.DIAMOND_PICKAXE), "distinct non-stacking items cannot be batched");
|
||||
assertEquals(1, r.inventoryCount(Items.DIAMOND_PICKAXE), "only one consumed");
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicate_identicalNonStacking_craftMax_clampsToOne() {
|
||||
// Same components (same damage) -> one Exact key summing to 3, but maxStack 1 clamps the batch.
|
||||
final PlaceResult r = autofill(
|
||||
List.of(predicate(s -> s.is(Items.DIAMOND_PICKAXE), stack(Items.DIAMOND_PICKAXE, 1))), 1, 1,
|
||||
List.of(damaged(Items.DIAMOND_PICKAXE, 100), damaged(Items.DIAMOND_PICKAXE, 100), damaged(Items.DIAMOND_PICKAXE, 100)),
|
||||
CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(1, r.gridCount(Items.DIAMOND_PICKAXE), "clamped to a single non-stackable item");
|
||||
assertEquals(2, r.inventoryCount(Items.DIAMOND_PICKAXE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicate_matchesMultipleItems_placesSomethingValid() {
|
||||
// Predicate matches any of several stackable items; whichever is chosen must be placed.
|
||||
final PlaceResult r = autofill(
|
||||
List.of(predicate(s -> s.is(Items.OAK_PLANKS) || s.is(Items.BIRCH_PLANKS), stack(Items.OAK_PLANKS, 1))), 1, 1,
|
||||
List.of(stack(Items.BIRCH_PLANKS, 5)), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(5, r.gridCount(Items.BIRCH_PLANKS), "the matching birch planks were placed");
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicate_twoSlots_sharedPool_craftMax_halves() {
|
||||
final Predicate<ItemStack> isStone = s -> s.is(Items.STONE);
|
||||
final PlaceResult r = autofill(
|
||||
List.of(predicate(isStone, stack(Items.STONE, 1)), predicate(isStone, stack(Items.STONE, 1))), 2, 1,
|
||||
List.of(stack(Items.STONE, 64)), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(32, r.grid.get(0).getCount());
|
||||
assertEquals(32, r.grid.get(1).getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicate_notEnough_ghost() {
|
||||
final Predicate<ItemStack> isStone = s -> s.is(Items.STONE);
|
||||
final PlaceResult r = autofill(
|
||||
List.of(predicate(isStone, stack(Items.STONE, 1)), predicate(isStone, stack(Items.STONE, 1))), 2, 1,
|
||||
List.of(stack(Items.STONE, 1)), CRAFT_ONE
|
||||
);
|
||||
assertTrue(r.ghost(), "one stone cannot fill two predicate slots");
|
||||
}
|
||||
|
||||
// ---- known bugs (kept disabled so the suite stays green; flip when the algorithm is fixed) --
|
||||
|
||||
@Test
|
||||
void predicateAndRegular_shareItem_shouldPlace() {
|
||||
final PlaceResult r = autofill(
|
||||
List.of(predicate(s -> s.is(Items.STONE), stack(Items.STONE, 1)), regular(Items.STONE)), 2, 1,
|
||||
List.of(stack(Items.STONE, 2)), CRAFT_ONE
|
||||
);
|
||||
assertTrue(r.placed(), "2 stone should satisfy predicate-stone + regular-stone");
|
||||
assertEquals(2, r.gridCount(Items.STONE));
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// Category E — more mixed combinations
|
||||
// ============================================================================================
|
||||
|
||||
@Test
|
||||
void predicateAndExact_distinctItems() {
|
||||
final PlaceResult r = autofill(
|
||||
List.of(predicate(s -> s.is(Items.STONE), stack(Items.STONE, 1)), exact(stack(Items.DIAMOND, 1))), 2, 1,
|
||||
List.of(stack(Items.STONE, 8), stack(Items.DIAMOND, 8)), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(8, r.gridCount(Items.STONE));
|
||||
assertEquals(8, r.gridCount(Items.DIAMOND));
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicate_ignoresNonMatchingInventory() {
|
||||
// Predicate matches only stone; unrelated dirt in the inventory must be left alone.
|
||||
final PlaceResult r = autofill(
|
||||
List.of(predicate(s -> s.is(Items.STONE), stack(Items.STONE, 1))), 1, 1,
|
||||
List.of(stack(Items.DIRT, 64), stack(Items.STONE, 3)), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(3, r.gridCount(Items.STONE));
|
||||
assertEquals(64, r.inventoryCount(Items.DIRT), "non-matching dirt untouched");
|
||||
}
|
||||
|
||||
@Test
|
||||
void exact_leavesNonMatchingComponentsUntouched() {
|
||||
final ItemStack special = renamed(Items.DIAMOND, 1, "Special");
|
||||
final PlaceResult r = autofill(
|
||||
List.of(exact(special.copy())), 1, 1,
|
||||
List.of(stack(Items.DIAMOND, 10), special.copy()), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(1, r.gridCount(Items.DIAMOND), "only the named diamond was placed");
|
||||
assertEquals(10, r.inventoryCount(Items.DIAMOND), "the 10 plain diamonds are untouched");
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// Category F — edge cases
|
||||
// ============================================================================================
|
||||
|
||||
@Test
|
||||
void emptyInventory_ghost() {
|
||||
final PlaceResult r = autofill(List.of(regular(Items.STONE)), 1, 1, List.of(), CRAFT_ONE);
|
||||
assertTrue(r.ghost());
|
||||
assertEquals(0, r.filledSlots());
|
||||
}
|
||||
|
||||
@Test
|
||||
void fewerIngredientsThanGrid_leavesRemainderEmpty() {
|
||||
final PlaceResult r = autofill(
|
||||
List.of(regular(Items.STONE), regular(Items.DIRT)), 3, 3,
|
||||
List.of(stack(Items.STONE, 1), stack(Items.DIRT, 1)), CRAFT_ONE
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(2, r.filledSlots(), "only two slots filled in a 3x3 grid");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customMaxStackSizeComponent_regular_clampsBatch() {
|
||||
// A stone whose stack was given a MAX_STACK_SIZE=16 component. As a regular (Item-key)
|
||||
// ingredient the picker uses the item's DEFAULT max stack; this test locks in current
|
||||
// behaviour so a change to that assumption is caught.
|
||||
final ItemStack capped = stack(Items.STONE, 16);
|
||||
capped.set(DataComponents.MAX_STACK_SIZE, 16);
|
||||
final PlaceResult r = autofill(List.of(regular(Items.STONE)), 1, 1, List.of(capped), CRAFT_MAX);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(16, r.gridCount(Items.STONE), "all 16 available were placed");
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// Category G — shaped-recipe centering (exercises the ShapedRecipe branch of PlaceRecipeHelper)
|
||||
// ============================================================================================
|
||||
|
||||
@Test
|
||||
void shaped_twoWide_centeredInThreeByThree() {
|
||||
// A 2x1 recipe placed into a 3x3 grid: PlaceRecipeHelper centers it vertically (row 1).
|
||||
final PlaceResult r = autofillShaped(
|
||||
List.of(regular(Items.STONE), regular(Items.DIRT)), 2, 1, 3, 3,
|
||||
List.of(stack(Items.STONE, 1), stack(Items.DIRT, 1)), CRAFT_ONE
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(2, r.filledSlots());
|
||||
assertEquals(1, r.gridCount(Items.STONE));
|
||||
assertEquals(1, r.gridCount(Items.DIRT));
|
||||
// Centered vertically onto the middle row (slots 3 & 4), not the top-left.
|
||||
assertTrue(r.grid.get(0).isEmpty(), "top-left slot should be empty after centering");
|
||||
assertSame(Items.STONE, r.grid.get(3).getItem());
|
||||
assertSame(Items.DIRT, r.grid.get(4).getItem());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shaped_fullThreeByThree_noCentering() {
|
||||
final List<Ingredient> ing = new ArrayList<>();
|
||||
for (int i = 0; i < 9; i++) {
|
||||
ing.add(regular(Items.STONE));
|
||||
}
|
||||
final PlaceResult r = autofillShaped(ing, 3, 3, 3, 3, List.of(stack(Items.STONE, 9)), CRAFT_ONE);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(9, r.filledSlots());
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// Category H — top-up path (recipe already placed in the grid, recipeMatches == true)
|
||||
// ============================================================================================
|
||||
|
||||
@Test
|
||||
void topUp_existingGrid_addsOneMore() {
|
||||
// Grid already holds 1 stone in each of two slots; a matching craft-one should top each to 2.
|
||||
final CraftingRecipe recipe = mock(CraftingRecipe.class);
|
||||
when(recipe.placementInfo()).thenReturn(PlacementInfo.create(List.of(regular(Items.STONE), regular(Items.STONE))));
|
||||
final PlaceResult r = place(
|
||||
recipe, 2, 1, List.of(stack(Items.STONE, 64)), CRAFT_ONE,
|
||||
List.of(stack(Items.STONE, 1), stack(Items.STONE, 1)), true
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(2, r.grid.get(0).getCount(), "topped up to 2");
|
||||
assertEquals(2, r.grid.get(1).getCount());
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// Category I — shaped recipes WITH gaps (L-shapes, tool shapes, ...)
|
||||
// ============================================================================================
|
||||
|
||||
@Test
|
||||
void shapedWithGap_placesIntoCorrectCells_leavesGapEmpty() {
|
||||
// 2x2 recipe box, bottom-right cell is a gap: [A B / C .]
|
||||
final List<Optional<Ingredient>> pattern = List.of(
|
||||
Optional.of(regular(Items.STONE)), // (0,0)
|
||||
Optional.of(regular(Items.DIRT)), // (1,0)
|
||||
Optional.of(regular(Items.DIAMOND)), // (0,1)
|
||||
Optional.empty() // (1,1) gap
|
||||
);
|
||||
final PlaceResult r = autofillPattern(
|
||||
pattern, 2, 2, 3, 3,
|
||||
List.of(stack(Items.STONE, 1), stack(Items.DIRT, 1), stack(Items.DIAMOND, 1)), CRAFT_ONE
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(3, r.filledSlots(), "only the three non-gap cells are filled");
|
||||
// 2x2 box is not centered in 3x3, so it maps to grid slots 0,1 (top row) and 3,4 (next row).
|
||||
assertSame(Items.STONE, r.grid.get(0).getItem());
|
||||
assertSame(Items.DIRT, r.grid.get(1).getItem());
|
||||
assertSame(Items.DIAMOND, r.grid.get(3).getItem());
|
||||
assertTrue(r.grid.get(4).isEmpty(), "the gap cell (slot 4) stays empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shapedWithGap_notEnoughForOneIngredient_ghost() {
|
||||
final List<Optional<Ingredient>> pattern = List.of(
|
||||
Optional.of(regular(Items.STONE)),
|
||||
Optional.empty(),
|
||||
Optional.of(regular(Items.DIRT))
|
||||
);
|
||||
// 3x1 box [stone, gap, dirt] but no dirt in inventory.
|
||||
final PlaceResult r = autofillPattern(pattern, 3, 1, 3, 3, List.of(stack(Items.STONE, 5)), CRAFT_ONE);
|
||||
assertTrue(r.ghost());
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// Category J — furnace / smoker / blast-furnace recipe book (AbstractFurnaceMenu)
|
||||
// ============================================================================================
|
||||
|
||||
@Test
|
||||
void furnace_regular_craftOne() {
|
||||
final PlaceResult r = autofillFurnace(regular(Items.RAW_IRON), List.of(stack(Items.RAW_IRON, 10)), CRAFT_ONE);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(1, r.gridCount(Items.RAW_IRON), "one raw iron placed into the input slot");
|
||||
assertEquals(9, r.inventoryCount(Items.RAW_IRON));
|
||||
}
|
||||
|
||||
@Test
|
||||
void furnace_regular_craftMax_fillsInputSlot() {
|
||||
final PlaceResult r = autofillFurnace(regular(Items.RAW_IRON), List.of(stack(Items.RAW_IRON, 64)), CRAFT_MAX);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(64, r.gridCount(Items.RAW_IRON), "whole stack moved into the furnace input");
|
||||
}
|
||||
|
||||
@Test
|
||||
void furnace_exactIngredient() {
|
||||
final ItemStack special = renamed(Items.RAW_IRON, 1, "Ore");
|
||||
final PlaceResult r = autofillFurnace(exact(special.copy()), List.of(stack(Items.RAW_IRON, 5), special.copy()), CRAFT_MAX);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(1, r.gridCount(Items.RAW_IRON), "only the exact named ore matches");
|
||||
assertTrue(r.grid.get(0).has(DataComponents.CUSTOM_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
void furnace_predicateIngredient() {
|
||||
final PlaceResult r = autofillFurnace(
|
||||
predicate(s -> s.is(Items.RAW_IRON), stack(Items.RAW_IRON, 1)),
|
||||
List.of(stack(Items.RAW_IRON, 3)), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(3, r.gridCount(Items.RAW_IRON));
|
||||
}
|
||||
|
||||
@Test
|
||||
void furnace_notEnough_ghost() {
|
||||
final PlaceResult r = autofillFurnace(regular(Items.RAW_IRON), List.of(), CRAFT_ONE);
|
||||
assertTrue(r.ghost());
|
||||
assertEquals(0, r.filledSlots());
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// Category K — predicate/exact combined with every other choice type (regression for the fix)
|
||||
// ============================================================================================
|
||||
|
||||
@Test
|
||||
void exactAndRegular_shareItem_shouldPlace() {
|
||||
// The exact-choice analog of the predicate bug: a plain item routed to the Exact pool
|
||||
// (because it matches an exact ingredient) must still satisfy a regular ingredient too.
|
||||
final PlaceResult r = autofill(
|
||||
List.of(exact(stack(Items.STONE, 1)), regular(Items.STONE)), 2, 1,
|
||||
List.of(stack(Items.STONE, 2)), CRAFT_ONE
|
||||
);
|
||||
assertTrue(r.placed(), "2 stone should satisfy exact-stone + regular-stone");
|
||||
assertEquals(2, r.gridCount(Items.STONE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicateAndRegular_shareItem_craftMax() {
|
||||
final PlaceResult r = autofill(
|
||||
List.of(predicate(s -> s.is(Items.STONE), stack(Items.STONE, 1)), regular(Items.STONE)), 2, 1,
|
||||
List.of(stack(Items.STONE, 64)), CRAFT_MAX
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(32, r.grid.get(0).getCount(), "64 stone / 2 slots -> 32 per slot");
|
||||
assertEquals(32, r.grid.get(1).getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicateAndExact_overlappingItem() {
|
||||
// A plain diamond matches both the predicate and the exact ingredient; it is pooled once
|
||||
// (Exact) and shared by both slots. Two diamonds -> one craft.
|
||||
final PlaceResult r = autofill(
|
||||
List.of(predicate(s -> s.is(Items.DIAMOND), stack(Items.DIAMOND, 1)), exact(stack(Items.DIAMOND, 1))), 2, 1,
|
||||
List.of(stack(Items.DIAMOND, 2)), CRAFT_ONE
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(2, r.gridCount(Items.DIAMOND));
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicateAndExact_overlappingItem_onlyOne_ghost() {
|
||||
final PlaceResult r = autofill(
|
||||
List.of(predicate(s -> s.is(Items.DIAMOND), stack(Items.DIAMOND, 1)), exact(stack(Items.DIAMOND, 1))), 2, 1,
|
||||
List.of(stack(Items.DIAMOND, 1)), CRAFT_ONE
|
||||
);
|
||||
assertTrue(r.ghost(), "one diamond cannot fill both overlapping slots");
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicateAndRegular_renamedItem_regularCannotUseIt() {
|
||||
// A renamed stone matches the predicate but is NOT usable for the regular ingredient. With a
|
||||
// plain stone available for the regular slot, the recipe still crafts (predicate<-renamed,
|
||||
// regular<-plain).
|
||||
final PlaceResult r = autofill(
|
||||
List.of(predicate(s -> s.is(Items.STONE), stack(Items.STONE, 1)), regular(Items.STONE)), 2, 1,
|
||||
List.of(renamed(Items.STONE, 1, "Fancy"), stack(Items.STONE, 1)), CRAFT_ONE
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(2, r.filledSlots());
|
||||
assertEquals(2, r.gridCount(Items.STONE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicateAndRegular_onlyRenamedItem_ghost() {
|
||||
// Only a renamed stone (matches predicate but not usable for the regular slot) -> cannot fill
|
||||
// both slots.
|
||||
final PlaceResult r = autofill(
|
||||
List.of(predicate(s -> s.is(Items.STONE), stack(Items.STONE, 1)), regular(Items.STONE)), 2, 1,
|
||||
List.of(renamed(Items.STONE, 1, "Fancy")), CRAFT_ONE
|
||||
);
|
||||
assertTrue(r.ghost(), "a renamed stone cannot satisfy the regular slot");
|
||||
}
|
||||
|
||||
@Test
|
||||
void threeWayMix_predicateExactRegular() {
|
||||
final ItemStack named = renamed(Items.DIAMOND, 1, "Gem");
|
||||
final PlaceResult r = autofill(
|
||||
List.of(
|
||||
predicate(s -> s.is(Items.STONE), stack(Items.STONE, 1)),
|
||||
exact(named.copy()),
|
||||
regular(Items.STICK)
|
||||
), 3, 1,
|
||||
List.of(stack(Items.STONE, 1), named.copy(), stack(Items.STICK, 1)), CRAFT_ONE
|
||||
);
|
||||
assertTrue(r.placed());
|
||||
assertEquals(1, r.gridCount(Items.STONE));
|
||||
assertEquals(1, r.gridCount(Items.DIAMOND));
|
||||
assertEquals(1, r.gridCount(Items.STICK));
|
||||
}
|
||||
}
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
package io.papermc.paper.inventory.recipe;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.ItemStackTemplate;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.crafting.CraftingBookCategory;
|
||||
import net.minecraft.world.item.crafting.CraftingInput;
|
||||
import net.minecraft.world.item.crafting.CraftingRecipe;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import net.minecraft.world.item.crafting.Recipe;
|
||||
import net.minecraft.world.item.crafting.ShapelessRecipe;
|
||||
import org.bukkit.support.environment.VanillaFeature;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests the <b>actual shapeless crafting</b> path: {@link ShapelessRecipe#matches} over a real
|
||||
* {@link ShapelessRecipe}. This exercises a different accounting route than the recipe-book placer
|
||||
* ({@code CraftingInput} ctor + {@code StackedContentsExtrasMap#accountInput}/{@code regularRemoved}),
|
||||
* plus the single-ingredient {@code Ingredient#test} shortcut. The same {@link ShapelessRecipe#matches}
|
||||
* is what the Crafter block and normal crafting menus use to decide whether a grid produces a result.
|
||||
*
|
||||
* <p>Regular/exact ingredients are the must-pass baseline; predicate + combination cases guard the
|
||||
* predicate integration fix.
|
||||
*/
|
||||
@VanillaFeature
|
||||
class ShapelessRecipeMatchTest {
|
||||
|
||||
private static Ingredient regular(final Item item) {
|
||||
return Ingredient.of(item);
|
||||
}
|
||||
|
||||
private static Ingredient exact(final ItemStack... stacks) {
|
||||
return Ingredient.ofStacks(List.of(stacks));
|
||||
}
|
||||
|
||||
private static Ingredient predicate(final Predicate<ItemStack> pred, final ItemStack example) {
|
||||
final Ingredient ingredient = Ingredient.ofStacks(List.of(example));
|
||||
ingredient.stackPredicate = pred;
|
||||
return ingredient;
|
||||
}
|
||||
|
||||
private static ItemStack renamed(final Item item, final String name) {
|
||||
final ItemStack s = new ItemStack(item);
|
||||
s.set(DataComponents.CUSTOM_NAME, net.minecraft.network.chat.Component.literal(name));
|
||||
return s;
|
||||
}
|
||||
|
||||
private static ShapelessRecipe shapeless(final Ingredient... ingredients) {
|
||||
return new ShapelessRecipe(
|
||||
new Recipe.CommonInfo(true),
|
||||
new CraftingRecipe.CraftingBookInfo(CraftingBookCategory.MISC, ""),
|
||||
new ItemStackTemplate(Items.DIAMOND), // result is irrelevant to matches()
|
||||
List.of(ingredients)
|
||||
);
|
||||
}
|
||||
|
||||
/** Runs the real matches() over a grid sized to hold exactly the given non-empty items. */
|
||||
private static boolean matches(final ShapelessRecipe recipe, final ItemStack... grid) {
|
||||
final CraftingInput input = CraftingInput.of(grid.length, 1, List.of(grid));
|
||||
return recipe.matches(input, null); // shapeless matching ignores the level
|
||||
}
|
||||
|
||||
// ---- regular ingredients (baseline) --------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void regular_exactMatch() {
|
||||
assertTrue(matches(shapeless(regular(Items.STONE), regular(Items.DIRT)),
|
||||
new ItemStack(Items.STONE), new ItemStack(Items.DIRT)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void regular_orderIndependent() {
|
||||
// Shapeless: item order in the grid must not matter.
|
||||
assertTrue(matches(shapeless(regular(Items.STONE), regular(Items.DIRT)),
|
||||
new ItemStack(Items.DIRT), new ItemStack(Items.STONE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void regular_wrongItem_noMatch() {
|
||||
assertFalse(matches(shapeless(regular(Items.STONE), regular(Items.DIRT)),
|
||||
new ItemStack(Items.STONE), new ItemStack(Items.COBBLESTONE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void regular_missingItem_noMatch() {
|
||||
assertFalse(matches(shapeless(regular(Items.STONE), regular(Items.DIRT)),
|
||||
new ItemStack(Items.STONE), new ItemStack(Items.STONE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void regular_extraItem_noMatch() {
|
||||
// Three grid items for a two-ingredient recipe -> ingredientCount mismatch.
|
||||
assertFalse(matches(shapeless(regular(Items.STONE), regular(Items.DIRT)),
|
||||
new ItemStack(Items.STONE), new ItemStack(Items.DIRT), new ItemStack(Items.DIAMOND)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void regular_duplicateIngredient() {
|
||||
assertTrue(matches(shapeless(regular(Items.STONE), regular(Items.STONE)),
|
||||
new ItemStack(Items.STONE), new ItemStack(Items.STONE)));
|
||||
assertFalse(matches(shapeless(regular(Items.STONE), regular(Items.STONE)),
|
||||
new ItemStack(Items.STONE), new ItemStack(Items.DIRT)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void regular_singleIngredientShortcut() {
|
||||
assertTrue(matches(shapeless(regular(Items.STONE)), new ItemStack(Items.STONE)));
|
||||
assertFalse(matches(shapeless(regular(Items.STONE)), new ItemStack(Items.DIRT)));
|
||||
}
|
||||
|
||||
// ---- exact ingredients ---------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void exact_matchesOnlySameComponents() {
|
||||
assertTrue(matches(shapeless(exact(renamed(Items.DIAMOND, "Special"))), renamed(Items.DIAMOND, "Special")));
|
||||
assertFalse(matches(shapeless(exact(renamed(Items.DIAMOND, "Special"))), new ItemStack(Items.DIAMOND)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void exact_plusRegular() {
|
||||
assertTrue(matches(shapeless(exact(renamed(Items.DIAMOND, "Special")), regular(Items.STONE)),
|
||||
renamed(Items.DIAMOND, "Special"), new ItemStack(Items.STONE)));
|
||||
}
|
||||
|
||||
// ---- predicate ingredients -----------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void predicate_singleShortcut() {
|
||||
assertTrue(matches(shapeless(predicate(s -> s.is(Items.STONE), new ItemStack(Items.STONE))), new ItemStack(Items.STONE)));
|
||||
assertFalse(matches(shapeless(predicate(s -> s.is(Items.STONE), new ItemStack(Items.STONE))), new ItemStack(Items.DIRT)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicate_twoPredicates() {
|
||||
final Predicate<ItemStack> isStone = s -> s.is(Items.STONE);
|
||||
assertTrue(matches(shapeless(predicate(isStone, new ItemStack(Items.STONE)), predicate(isStone, new ItemStack(Items.STONE))),
|
||||
new ItemStack(Items.STONE), new ItemStack(Items.STONE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicate_plusRegular_shareItem() {
|
||||
// The fix: grid accounting routes both stone to the Exact pool; the regular stone ingredient
|
||||
// must still be satisfiable. (This is the actual-crafting analog of the recipe-book bug.)
|
||||
assertTrue(matches(shapeless(predicate(s -> s.is(Items.STONE), new ItemStack(Items.STONE)), regular(Items.STONE)),
|
||||
new ItemStack(Items.STONE), new ItemStack(Items.STONE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicate_plusRegular_distinctItems_orderIndependent() {
|
||||
final ShapelessRecipe recipe = shapeless(predicate(s -> s.is(Items.DIAMOND), new ItemStack(Items.DIAMOND)), regular(Items.STONE));
|
||||
assertTrue(matches(recipe, new ItemStack(Items.DIAMOND), new ItemStack(Items.STONE)));
|
||||
assertTrue(matches(recipe, new ItemStack(Items.STONE), new ItemStack(Items.DIAMOND)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicate_plusRegular_noMatchingRegularItem() {
|
||||
// grid stone + dirt: dirt matches neither ingredient -> no match.
|
||||
assertFalse(matches(shapeless(predicate(s -> s.is(Items.STONE), new ItemStack(Items.STONE)), regular(Items.STONE)),
|
||||
new ItemStack(Items.STONE), new ItemStack(Items.DIRT)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicate_plusExact() {
|
||||
assertTrue(matches(shapeless(predicate(s -> s.is(Items.DIAMOND), new ItemStack(Items.DIAMOND)), exact(renamed(Items.EMERALD, "Gem"))),
|
||||
new ItemStack(Items.DIAMOND), renamed(Items.EMERALD, "Gem")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicate_plusRegular_renamedSatisfiesPredicateOnly() {
|
||||
// renamed stone -> predicate slot; plain stone -> regular slot. Order-independent.
|
||||
final ShapelessRecipe recipe = shapeless(predicate(s -> s.is(Items.STONE), new ItemStack(Items.STONE)), regular(Items.STONE));
|
||||
assertTrue(matches(recipe, renamed(Items.STONE, "Fancy"), new ItemStack(Items.STONE)));
|
||||
// Two renamed stones: the regular slot cannot use a renamed (non-usable) stone -> no match.
|
||||
assertFalse(matches(recipe, renamed(Items.STONE, "Fancy"), renamed(Items.STONE, "Fancy2")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void threeWayMix_predicateExactRegular() {
|
||||
final ShapelessRecipe recipe = shapeless(
|
||||
predicate(s -> s.is(Items.STONE), new ItemStack(Items.STONE)),
|
||||
exact(renamed(Items.DIAMOND, "Gem")),
|
||||
regular(Items.STICK)
|
||||
);
|
||||
assertTrue(matches(recipe, new ItemStack(Items.STICK), renamed(Items.DIAMOND, "Gem"), new ItemStack(Items.STONE)));
|
||||
assertFalse(matches(recipe, new ItemStack(Items.STICK), new ItemStack(Items.DIAMOND), new ItemStack(Items.STONE)));
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package org.bukkit.craftbukkit.legacy;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Predicates;
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -16,6 +15,7 @@ import java.util.stream.Stream;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.Reroute;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.RerouteBuilder;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
import org.bukkit.craftbukkit.util.Commodore;
|
||||
import org.bukkit.support.environment.Normal;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
|
||||
+3
-3
@@ -2,9 +2,9 @@ package org.bukkit.craftbukkit.legacy.reroute;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import com.google.common.base.Predicates;
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
import org.bukkit.support.environment.Normal;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -20,12 +20,12 @@ public class InjectPluginVersionTest extends AbstractRerouteTest {
|
||||
"java/lang/Object",
|
||||
"getList",
|
||||
false,
|
||||
"(Ljava/lang/Object;Lio/papermc/paper/plugin/ApiVersion;)Ljava/util/List;",
|
||||
"(Ljava/lang/Object;Lorg/bukkit/craftbukkit/util/ApiVersion;)Ljava/util/List;",
|
||||
"org/bukkit/craftbukkit/legacy/reroute/InjectPluginVersionTest$InjectPluginVersionTestData",
|
||||
"getList",
|
||||
this.create(
|
||||
this.create("Ljava/lang/Object;", "Ljava/lang/Object;", false, false, null),
|
||||
this.create("Lio/papermc/paper/plugin/ApiVersion;", "Lio/papermc/paper/plugin/ApiVersion;", false, true, null)
|
||||
this.create("Lorg/bukkit/craftbukkit/util/ApiVersion;", "Lorg/bukkit/craftbukkit/util/ApiVersion;", false, true, null)
|
||||
),
|
||||
"Ljava/util/List;",
|
||||
true,
|
||||
|
||||
+15
-16
@@ -1,9 +1,8 @@
|
||||
package org.bukkit.craftbukkit.legacy.reroute;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import java.lang.annotation.Annotation;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
import org.bukkit.support.environment.Normal;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -20,56 +19,56 @@ public class RequirePluginVersionDataTest {
|
||||
|
||||
@Test
|
||||
public void testDifferent() {
|
||||
RequirePluginVersionData data = RequirePluginVersionData.create(new RequirePluginVersionImpl("", "1.20", "1.21"));
|
||||
RequirePluginVersionData data = RequirePluginVersionData.create(new RequirePluginVersionImpl("", "1.21", "1.23"));
|
||||
|
||||
assertEquals(ApiVersion.getOrCreateVersion("1.20"), data.minInclusive());
|
||||
assertEquals(ApiVersion.getOrCreateVersion("1.21"), data.maxInclusive());
|
||||
assertEquals(ApiVersion.getOrCreateVersion("1.21"), data.minInclusive());
|
||||
assertEquals(ApiVersion.getOrCreateVersion("1.23"), data.maxInclusive());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValue() {
|
||||
RequirePluginVersionData data = RequirePluginVersionData.create(new RequirePluginVersionImpl("1.21", "", ""));
|
||||
RequirePluginVersionData data = RequirePluginVersionData.create(new RequirePluginVersionImpl("1.42", "", ""));
|
||||
|
||||
assertEquals(ApiVersion.getOrCreateVersion("1.21"), data.minInclusive());
|
||||
assertEquals(ApiVersion.getOrCreateVersion("1.21"), data.maxInclusive());
|
||||
assertEquals(ApiVersion.getOrCreateVersion("1.42"), data.minInclusive());
|
||||
assertEquals(ApiVersion.getOrCreateVersion("1.42"), data.maxInclusive());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValueAndMin() {
|
||||
assertThrows(IllegalArgumentException.class, () -> RequirePluginVersionData.create(new RequirePluginVersionImpl("1.20", "1.21", "")));
|
||||
assertThrows(IllegalArgumentException.class, () -> RequirePluginVersionData.create(new RequirePluginVersionImpl("1.42", "1.52", "")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValueAndMax() {
|
||||
assertThrows(IllegalArgumentException.class, () -> RequirePluginVersionData.create(new RequirePluginVersionImpl("1.20", "", "1.21")));
|
||||
assertThrows(IllegalArgumentException.class, () -> RequirePluginVersionData.create(new RequirePluginVersionImpl("1.42", "", "1.53")));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testValueAndMinMax() {
|
||||
assertThrows(IllegalArgumentException.class, () -> RequirePluginVersionData.create(new RequirePluginVersionImpl("1.19", "1.20", "1.21")));
|
||||
assertThrows(IllegalArgumentException.class, () -> RequirePluginVersionData.create(new RequirePluginVersionImpl("1.42", "1.51", "1.53")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMinNewerThanMax() {
|
||||
assertThrows(IllegalArgumentException.class, () -> RequirePluginVersionData.create(new RequirePluginVersionImpl("", "1.21", "1.20")));
|
||||
assertThrows(IllegalArgumentException.class, () -> RequirePluginVersionData.create(new RequirePluginVersionImpl("", "1.59", "1.57")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnlyMin() {
|
||||
RequirePluginVersionData data = RequirePluginVersionData.create(new RequirePluginVersionImpl("", "1.21", ""));
|
||||
RequirePluginVersionData data = RequirePluginVersionData.create(new RequirePluginVersionImpl("", "1.44", ""));
|
||||
|
||||
assertEquals(ApiVersion.getOrCreateVersion("1.21"), data.minInclusive());
|
||||
assertEquals(ApiVersion.getOrCreateVersion("1.44"), data.minInclusive());
|
||||
assertNull(data.maxInclusive());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOnlyMax() {
|
||||
RequirePluginVersionData data = RequirePluginVersionData.create(new RequirePluginVersionImpl("", "", "1.21"));
|
||||
RequirePluginVersionData data = RequirePluginVersionData.create(new RequirePluginVersionImpl("", "", "1.96"));
|
||||
|
||||
assertNull(data.minInclusive());
|
||||
assertEquals(ApiVersion.getOrCreateVersion("1.21"), data.maxInclusive());
|
||||
assertEquals(ApiVersion.getOrCreateVersion("1.96"), data.maxInclusive());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
package org.bukkit.craftbukkit.legacy.reroute;
|
||||
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
import org.bukkit.support.environment.Normal;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -26,7 +26,7 @@ public class RequirePluginVersionTest extends AbstractRerouteTest {
|
||||
),
|
||||
"Ljava/util/List;",
|
||||
true,
|
||||
new RequirePluginVersionData(ApiVersion.getOrCreateVersion("1.21"), ApiVersion.getOrCreateVersion("1.21"))
|
||||
new RequirePluginVersionData(ApiVersion.getOrCreateVersion("1.42"), ApiVersion.getOrCreateVersion("1.42"))
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -34,7 +34,7 @@ public class RequirePluginVersionTest extends AbstractRerouteTest {
|
||||
|
||||
public static class RequirePluginVersionTestData {
|
||||
|
||||
@RequirePluginVersion("1.21")
|
||||
@RequirePluginVersion("1.42")
|
||||
public static List<String> getList(Object o) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package org.bukkit.craftbukkit.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import io.papermc.paper.plugin.ApiVersion;
|
||||
import java.util.stream.Stream;
|
||||
import net.minecraft.SharedConstants;
|
||||
import org.bukkit.support.environment.VanillaFeature;
|
||||
@@ -21,9 +19,7 @@ public class ApiVersionTest {
|
||||
Arguments.of("none", "none"),
|
||||
Arguments.of("1.12", "1.12.0"),
|
||||
Arguments.of("1.13.3", "1.13.3"),
|
||||
Arguments.of("1.+20.3", "1.20.3"),
|
||||
Arguments.of("26.1", "26.1.0"),
|
||||
Arguments.of("28.2.3", "28.2.3")
|
||||
Arguments.of("1.+20.3", "1.20.3")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,11 +27,7 @@ public class ApiVersionTest {
|
||||
return Stream.of(
|
||||
Arguments.of("none", "none", CompareResult.SAME),
|
||||
Arguments.of("none", "1.20", CompareResult.SMALLER),
|
||||
Arguments.of("1.20.3", "1.19.4", CompareResult.BIGGER),
|
||||
Arguments.of("26.1", "1.21.4", CompareResult.BIGGER),
|
||||
Arguments.of("26.3.2", "1.21.11", CompareResult.BIGGER),
|
||||
Arguments.of("none", "26.1.2", CompareResult.SMALLER),
|
||||
Arguments.of("1.8.9", "26.1.2", CompareResult.SMALLER),
|
||||
Arguments.of("2.20.3", "1.30.4", CompareResult.BIGGER),
|
||||
Arguments.of("1.13", "1.12", CompareResult.BIGGER),
|
||||
Arguments.of("1.13.2", "1.13.3", CompareResult.SMALLER)
|
||||
);
|
||||
@@ -98,8 +90,8 @@ public class ApiVersionTest {
|
||||
|
||||
@Test
|
||||
public void testSameInstance() {
|
||||
ApiVersion one = ApiVersion.getOrCreateVersion("1.20.3");
|
||||
ApiVersion second = ApiVersion.getOrCreateVersion("1.+20.3");
|
||||
ApiVersion one = ApiVersion.getOrCreateVersion("1.23.3");
|
||||
ApiVersion second = ApiVersion.getOrCreateVersion("1.+23.3");
|
||||
|
||||
assertSame(one, second);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user