Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ updates:
interval: monthly
time: "10:00"
open-pull-requests-limit: 10
ignore:
# Spigot-API is pinned to the point when the hard fork occurred.
- dependency-name: "org.spigotmc:*"
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ on:

jobs:
build:
uses: Jikoo/PlanarActions/.github/workflows/ci_gradle_sonar.yml@master
secrets: inherit
uses: Jikoo/PlanarActions/.github/workflows/ci_gradle.yml@master
with:
include-artifacts: "./enchanting-bundler/build/libs/*.jar"
3 changes: 2 additions & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ on:
jobs:
run-ci:
uses: Jikoo/PlanarActions/.github/workflows/ci_gradle_sonar.yml@master
secrets: inherit
secrets:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
approve-and-merge-dependabot:
if: "github.event_name == 'pull_request' && github.event.pull_request.user.login == 'dependabot[bot]'"
needs: [ "run-ci" ]
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ bin/
out/
*.iml

# VS Code
.vscode/

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

Expand Down
93 changes: 61 additions & 32 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -17,65 +17,95 @@ systems and divide them up into readable, manageable, customizable segments.
### Anvil Enchanting

Anvil enchanting is primarily accessed via the
[`Anvil`](enchanting-core/src/main/java/com/github/jikoo/planarenchanting/anvil/Anvil.java).
This is a class containing several simple ways to modify anvil behaviors.
[`AnvilCreator`](enchanting-bundler/src/main/java/com/github/jikoo/planarenchanting/anvil/AnvilCreator.java).
This is a wrapper allowing instantiation of implementation-specific anvil data.

For basic vanilla-style combination, all you need is an ordinary `Anvil`.

```java
class MyAnvilHandler implements Listener {
private final Anvil anvil = new Anvil();
private final Anvil anvil = AnvilCreator.create();
@EventHandler
private void onPrepareAnvil(PrepareAnvilEvent event) {
AnvilView view = event.getView();
AnvilResult result = anvil.getResult(view);

event.setResult(result.item());

// Note: depending on server implementation, may need to update costs on a 0-tick delay.
// Note: Depending on the server implementation, you may need to
// update costs on a 0-tick delay. It is also advisable to ensure
// that the result item has not been changed when doing so.
view.setRepairItemCostAmount(result.materialCost());
view.setRepairCost(result.levelCost());
}
}
```

For specific tweaks (i.e. removing or changing enchantment level cap) you can provide the
`Anvil` with a different `AnvilBehavior` implementation.
For specific tweaks (i.e. removing or changing enchantment level cap) you can provide
a different `AnvilBehavior` implementation.

> [!IMPORTANT]
> For better compatibility, `Anvil` has two main implementations: component-based
> (using Paper's `DataComponentType`) and meta-based (using the Spigot API and `ItemMeta`).
> For detecting compatibility, PlanarEnchanting uses `ServerCapabilities.DATA_COMPONENT`.

Allowing conflicting enchantments to be added:

```java
Anvil anvil = new Anvil(new AnvilBehavior() {
@Override
public boolean getEnchantsConflict(Enchantment enchant1, Enchantment enchant2) {
return false;
}
});
// PlanarForge is the default Anvil implementation, wrapping a WorkPiece provider,
// an AnvilBehavior, and an AnvilFunctionsProvider.
Anvil anvil = new PlanarForge<ItemStack>(
AnvilCreator::createComponentPiece,
new ComponentVanillaBehavior() {
@Override
public boolean getEnchantsConflict(Enchantment enchant1, Enchantment enchant2) {
return false;
}
},
ComponentAnvilFunctions.INSTANCE
);
```

If you have even more specific needs but still want to leverage certain vanilla-style functionality,
you can write your own anvil functionality.
you can write your own Anvil implementation. You can also implement your own
[AnvilFunctions](enchanting-common/src/main/java/com/github/jikoo/planarenchanting/anvil/AnvilFunction.java)
if you need additional behavior changes.

An implementation that only allows the input to be renamed:

```java
class RenameOnlyAnvil extends Anvil {
@NullMarked
class RenameOnlyAnvil<T> implements Anvil {

protected final Function<AnvilView, WorkPiece<T>> createPiece;
protected final AnvilBehavior<T> behavior;
protected final AnvilFunctionsProvider<T> functions;

RenameOnlyAnvil(
Function<AnvilView, WorkPiece<T>> createPiece,
AnvilBehavior<T> behavior,
AnvilFunctionsProvider<T> functions
) {
this.createPiece = createPiece;
this.behavior = behavior;
this.functions = functions;
}

@Override
public void getResult(@NotNull AnvilView view) {
AnvilState state = new AnvilState(view);
public AnvilResult getResult(AnvilView view) {
WorkPiece<T> piece = createPiece.apply(view);

// Require first item to be set, second item to be unset.
if (ItemUtil.isEmpty(state.getBase().getItem())
|| !ItemUtil.isEmpty(state.getAddition().getItem())) {
if (piece.getBase().isEmpty() || !piece.getAddition().isEmpty()) {
return AnvilResult.EMPTY;
}

// Apply base cost.
apply(state, AnvilFunctions.PRIOR_WORK_LEVEL_COST);
piece.apply(behavior, functions.setPriorWorkCost());
// Apply rename.
apply(state, AnvilFunctions.RENAME);
piece.apply(behavior, functions.rename());

return forge(state);
return piece.forge();
}
}
```
Expand All @@ -96,37 +126,34 @@ the surface. You may manipulate incompatibility between enchantments and enchant
Allow enchanting `Material.STONE` with enchantments usually available for tools:

```java
@NullMarked
class StoneEnchantListener extends TableEnchantListener {

// Set up table.
private final EnchantingTable table = new EnchantingTable(
List.of(
Enchantment.DIG_SPEED,
Enchantment.DURABILITY,
Enchantment.LOOT_BONUS_BLOCKS,
Enchantment.SILK_TOUCH),
Enchantability.forMaterial(Material.STONE_PICKAXE));
Enchantment.SILK_TOUCH
),
Enchantabilies.forMaterial(Material.STONE_PICKAXE)
);

StoneEnchantListener(@NotNull Plugin plugin) {
StoneEnchantListener(Plugin plugin) {
super(plugin);
}

@Override
protected @Nullable EnchantingTable getTable(
@NotNull Player player,
@NotNull ItemStack enchanted) {
protected EnchantingTable getTable(Player player, ItemStack enchanted) {
// Use stored instance.
return table;
}

@Override
protected boolean isIneligible(
@NotNull Player player,
@NotNull ItemStack enchanted) {
protected boolean isIneligible(Player player, ItemStack enchanted) {
// Only allow enchanting stone.
return itemStack.getType() != Material.STONE;
}

}
```

Expand All @@ -139,4 +166,6 @@ JitPack supports Gradle, Maven, SBT, and Leiningen.

## For Developers

The project is compiled using Gradle via the `build` task, i.e. `./gradlew build`.
Generated source files can be recreated from Minecraft internals with the `generate` task.
The generator module is not built by default so as to not require the Paper server to be compiled.
62 changes: 54 additions & 8 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@ import java.lang.Runtime
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent

plugins {
`java-library`
jacoco
alias(libs.plugins.shadow) apply false
}

repositories {
mavenCentral()
}

subprojects {
apply(plugin = "java-library")

repositories {
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://jitpack.io/")
}

tasks.withType<JavaCompile>().configureEach {
Expand All @@ -22,14 +33,49 @@ subprojects {
filteringCharset = Charsets.UTF_8.name()
}

tasks.withType<Test>().configureEach {
// Use as many cores as possible to run tests.
maxParallelForks = Runtime.getRuntime().availableProcessors()
jvmArgs("-Xshare:off")
testLogging {
showStackTraces = true
exceptionFormat = TestExceptionFormat.FULL
events(TestLogEvent.STANDARD_OUT)
if ("enchanting-generator" != this.name) {
apply(plugin = "jacoco")

val mockitoAgent: Configuration = configurations.create("mockitoAgent")
dependencies {
compileOnly(rootProject.libs.org.jspecify.jspecify)
compileOnly(rootProject.libs.org.jetbrains.annotations)

testCompileOnly(rootProject.libs.org.jspecify.jspecify)
testImplementation(rootProject.libs.org.hamcrest.hamcrest)
testImplementation(rootProject.libs.org.mockito.mockito.core)
mockitoAgent(rootProject.libs.org.mockito.mockito.core) { isTransitive = false }
testImplementation(rootProject.libs.com.jparams.to.string.verifier)
}

testing {
suites {
named<JvmTestSuite>("test") {
useJUnitJupiter("6.0.2")
}
}
}

tasks.withType<Test>().configureEach {
// Use as many cores as possible to run tests.
maxParallelForks = Runtime.getRuntime().availableProcessors()
// As Bukkit is very heavily statically initialized, don't reuse forks.
forkEvery = 1
jvmArgs("-Xshare:off", "-javaagent:${mockitoAgent.asPath}")
testLogging {
showStackTraces = true
exceptionFormat = TestExceptionFormat.FULL
events(TestLogEvent.STANDARD_OUT)
}
finalizedBy(tasks.jacocoTestReport)
}

tasks.jacocoTestReport {
enabled = true
reports {
// Produce XML report for Sonar
xml.required = true
}
}
}
}
35 changes: 35 additions & 0 deletions enchanting-bundler/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
plugins {
alias(libs.plugins.shadow)
}

dependencies {
compileOnly(libs.org.spigotmc.spigot.api)
implementation(project(":enchanting-common", configuration = "shadowRuntimeElements")) {
exclude(group = "com.github.jikoo", module = "planarwrappers")
}
implementation(project(":enchanting-components")) {
exclude(group = "io.papermc.paper", module = "paper-api")
}
implementation(project(":enchanting-meta"))

testImplementation(libs.org.spigotmc.spigot.api)
}

tasks.jar {
enabled = false
}

tasks.shadowJar {
dependsOn("classes")
archiveClassifier.set("")

archiveBaseName = "${project.name}"
}

tasks.assemble {
dependsOn(tasks.shadowJar)
}

artifacts {
add("default", tasks.shadowJar)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.github.jikoo.planarenchanting.anvil;

import com.github.jikoo.planarenchanting.util.ServerCapabilities;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.view.AnvilView;
import org.jspecify.annotations.NullMarked;

/**
* Accessor for creating an appropriate {@link Anvil} for the platform.
*/
@NullMarked
public final class AnvilCreator {

/**
* Create a new platform-dependent {@link Anvil}. It will use vanilla-style behavior to produce
* results.
*
* @return the anvil implementation
*/
public static Anvil create() {
if (ServerCapabilities.DATA_COMPONENT) {
return new PlanarForge<>(AnvilCreator::createComponentPiece, new ComponentVanillaBehavior(), ComponentAnvilFunctions.INSTANCE);
} else {
return new PlanarForge<>(AnvilCreator::createMetaPiece, new MetaVanillaBehavior(), MetaAnvilFunctions.INSTANCE);
}
}

/**
* Create a new anvil {@link WorkPiece} based on Paper's {@code DataComponent}.
*
* @param view the AnvilView to operate on
* @return the resulting work piece
* @see ServerCapabilities#DATA_COMPONENT
*/
public static WorkPiece<ItemStack> createComponentPiece(AnvilView view) {
return new WorkPiece<>(new ComponentViewState(view), ComponentTemperer.INSTANCE);
}

/**
* Create a new anvil {@link WorkPiece} based on the Bukkit API.
*
* <p>Note that this relies on methods which are now deprecated in Paper. It is advisable to use
* {@link #createComponentPiece(AnvilView)} instead if possible.</p>
*
* <p>To prevent creation of numerous duplicate copies of the item's
* {@link org.bukkit.inventory.meta.ItemMeta}, operations are performed on a
* {@link MetaCachedStack}. Changes are only applied to the result when the piece is tempered.</p>
*
* @param view the AnvilView to operate on
* @return the resulting work piece
* @see #createComponentPiece(AnvilView)
* @see ServerCapabilities#DATA_COMPONENT
*/
public static WorkPiece<MetaCachedStack> createMetaPiece(AnvilView view) {
return new WorkPiece<>(new MetaViewState(view), MetaTemperer.INSTANCE);
}

private AnvilCreator() {}

}
Loading
Loading