Compare commits

...

52 commits

Author SHA1 Message Date
aa1a17c8fd Atualizar .forgejo/workflows/release-build.yml 2026-05-20 22:48:23 -03:00
0de8990d91 Atualizar .forgejo/workflows/build.yml 2026-05-20 22:47:42 -03:00
fe35fc0a41 Atualizar README.md 2026-05-20 22:45:45 -03:00
da27a0a40c Merge remote-tracking branch 'origin/main'
Some checks failed
Build / build (push) Has been cancelled
2026-05-20 18:14:59 -03:00
76f13a2412 Move version-specific code to reflection implementation and add basic testing. 2026-05-20 18:13:16 -03:00
b044052b6d Move isArmor to reflection implementations and refactor NodeHandler replace use of safeMatches(String expression, String against) usage with safeMatches(List<String> expressions, String against). 2026-05-20 15:54:14 -03:00
af6ac27960 Refactor codebase to use LivingEntity, add SPAWN action and handler, add DataShifter.parseValueToStringList overload that iterates over a list of strings. 2026-05-20 15:38:45 -03:00
4b5ee42547 Atualizar .github/workflows/release-build.yml
Some checks failed
Build / build (push) Has been cancelled
2026-05-19 15:49:04 -03:00
7aadf220f5 Split github and forgejo workflows.
Some checks are pending
Build / build (push) Waiting to run
2026-05-19 15:43:35 -03:00
a8bbf78a3c Merge pull request '1.0.6-SNAPSHOT' (#1) from 1.0.6-SNAPSHOT into main
Some checks failed
Build / build (push) Has been cancelled
Release Build / build-and-release (release) Failing after 8s
Reviewed-on: #1
2026-05-19 15:36:37 -03:00
167a1faec1 Merge branch 'main' into 1.0.6-SNAPSHOT
All checks were successful
Build / build (push) Successful in 1m21s
2026-05-19 15:34:24 -03:00
a802a09c3c Atualizar .github/workflows/release-build.yml
All checks were successful
Build / build (push) Successful in 2m20s
2026-05-19 15:32:30 -03:00
f4a362c1c7 Add action and handler for gliding, equip, refactored PolicyParser and implementations to support world matching
Some checks are pending
Build / build (push) Waiting to run
2026-05-19 15:27:04 -03:00
e193cf1bc8 Fix link typo in README.md 2026-05-12 19:13:58 -03:00
0bd48db43d Remove GitHub specific badges from README.md. 2026-05-12 18:50:58 -03:00
3b77de2e63 Downgrade actions/upload-artifact to v3 for compatibility with Forgejo.
All checks were successful
Build / build (push) Successful in 1m43s
2026-05-12 16:49:52 -03:00
ec17e06965 Add workflow_dispatch condition to build.yml for debugging Forgejo Runner.
Some checks failed
Build / build (push) Failing after 4m10s
2026-05-12 16:18:02 -03:00
96005d8c86 I'm going crazy. 2025-12-23 20:56:46 -03:00
27c7fd16cc Started implementing new structure. 2025-12-22 23:42:17 -03:00
cc38b286cb Merge remote-tracking branch 'origin/main' 2025-12-18 23:33:47 -03:00
708f65fe36 Added permission support for commands. 2025-12-18 23:33:34 -03:00
Tenkuma
9180807f0a
Update release-build.yml 2025-12-15 14:46:23 -03:00
Tenkuma
80591dc8a7
Only run release-build when publishing a release 2025-12-15 14:36:34 -03:00
Tenkuma
d4510a7257
Update release-build.yml 2025-12-15 14:26:52 -03:00
81bf64c463 Policies now provide their own checks to whether the player applies or not.
Added GlobalPolicy.

Revamped config YAML structure for more consistency.

Added ChatColor back to commands, should be correctly implemented in the future.

Build environment is now passed to plugin manifest.

Added workflow to automatically build releases.
2025-12-15 14:16:07 -03:00
7bf4346750 Merge remote-tracking branch 'origin/main' 2025-12-15 02:09:24 -03:00
0b04c0553a Add sendCommand() on Glimmer because beta does not break line on sendMessage() line break.
Subcommands are now mapped in the class Commands.

Add usage() and description() to Subcommand.

Add help command.
2025-12-15 02:09:09 -03:00
Tenkuma
21df2cc7bf
Run build only when pushing changes to src/**, .github/** and Gradle files. 2025-12-15 00:54:44 -03:00
Tenkuma
8ee5891dba
Fix badges links. 2025-12-15 00:47:44 -03:00
Tenkuma
616a4b92e5
More badges! 2025-12-15 00:42:21 -03:00
6250c48d81 Stop truncating the version string in the gradle build file. 2025-12-15 00:27:03 -03:00
75b0259943 Fix build workflow env injecting. 2025-12-15 00:23:43 -03:00
5302092596 Build now concats "autobuild-" the commit sha to make the version string and sets unused build channel variable for future implementation.
Added badges to README.
2025-12-15 00:21:00 -03:00
Tenkuma
fcafe470aa
Update build.yml 2025-12-14 23:52:07 -03:00
Tenkuma
2ce90cd14a
Update build.yml 2025-12-14 23:41:27 -03:00
Tenkuma
f37ba38178
Fix typo in Java distribution name 2025-12-14 23:40:04 -03:00
Tenkuma
6bce18a04d
Create build.yml 2025-12-14 23:36:36 -03:00
df60600073 Preparing Gradle build for automatic build from Actions. 2025-12-14 23:36:20 -03:00
42ab7e4d89 Fixed attackWith that was returning !node.isWhitelist() instead of true whe it's not in effect. 2025-12-14 16:25:18 -03:00
a5adc828dc Fixed some warnings. 2025-12-14 16:01:15 -03:00
7a3aa34b43 Fixed EntityDamageByEntity not triggering because the method was not overriding and checked against the correct event. 2025-12-14 14:51:33 -03:00
Tenkuma
7f65a8605d
Add image to README for Eye of Nemesis plugin 2025-12-13 17:13:32 -03:00
Tenkuma
1f923283d8
Set GitHub Sponsors username to 'adrianvic'
Updated GitHub Sponsors username in FUNDING.yml
2025-12-13 15:40:54 -03:00
Tenkuma
0db49aba02
Merge pull request #1 from adrianvic/reflection
Add reflection with initial support to Bukkit CB1060 (b1.7.3)
2025-12-12 21:46:36 -03:00
Tenkuma
c4b112c860
Merge branch 'main' into reflection 2025-12-12 21:45:30 -03:00
4857bd155a Finished initial b1_7_3 implementation. 2025-12-12 21:42:27 -03:00
90d43ac3de Finished r1_21 implementation.
- Better NodeHandler evaluation.
2025-12-12 17:31:28 -03:00
Tenkuma
b7b43d6aef
Update README.md 2025-12-12 00:32:53 -03:00
Tenkuma
8562a51543
Update README.md 2025-12-12 00:32:10 -03:00
56f337110f Initial work on reflection, pulls version from resources. 2025-12-12 00:02:11 -03:00
Tenkuma
9b98d8a2a9
Add project status and documentation notice
Added important project status notice and documentation update info.
2025-12-10 01:42:15 -03:00
e64b563190 Refactored policy system, fixed config file parsing inconsistencies.
- LocationPolicy is now an extension of Policy
- Policy.parse() is now PolicyParser mapped in PolicyParsers.
- NodeValueParser was merged with DataShifter
- More code small changes to enable the project to work with this new configuration.
2025-12-08 17:19:06 -03:00
73 changed files with 2575 additions and 512 deletions

View file

@ -0,0 +1,43 @@
name: Build
on:
push:
paths:
- 'src/**'
- '.github/**'
- 'build.gradle.kts'
- 'gradle.properties'
- 'settings.gradle'
jobs:
build:
runs-on: arch-linux
env:
NEMESIS_VERSION_NAME: "autobuild-${{ github.sha }}"
NEMESIS_BUILD_CHANNEL: "autobuild"
steps:
- name: Checkout
uses: actions/checkout@v6.0.1
- name: Setup Java enviroment
uses: actions/setup-java@v5.1.0
with:
distribution: temurin
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Download CB1060
run: |
mkdir -p libs
curl -L -o libs/craftbukkit-1060.jar https://archive.org/download/craftbukkit1060/craftbukkit1-7-3%281060%29.jar
- name: Build with Gradle
run: ./gradlew buildAll
- name: Upload artifacts
uses: actions/upload-artifact@v6.0.0
with:
path: build/libs/*.jar

View file

@ -0,0 +1,40 @@
name: Release Build
on:
release:
types: [published]
jobs:
build-and-release:
runs-on: arch-linux
env:
NEMESIS_VERSION_NAME: "${{ github.ref_name }}"
NEMESIS_BUILD_CHANNEL: "production"
steps:
- name: Checkout
uses: actions/checkout@v6.0.1
- name: Set up Java
uses: actions/setup-java@v5.1.0
with:
distribution: temurin
java-version: 21
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v5
- name: Download CB1060
run: |
mkdir -p libs
curl -L -o libs/craftbukkit-1060.jar \
https://archive.org/download/craftbukkit1060/craftbukkit1-7-3%281060%29.jar
- name: Build with Gradle
run: ./gradlew buildAll
- name: Upload JARs to release
uses: softprops/action-gh-release@v2.5.0
with:
files: build/libs/*.jar
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}

15
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,15 @@
# These are supported funding model platforms
github: adrianvic
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

44
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,44 @@
name: Build
on:
workflow_dispatch:
push:
paths:
- 'src/**'
- '.github/**'
- 'build.gradle.kts'
- 'gradle.properties'
- 'settings.gradle'
jobs:
build:
runs-on: ubuntu-latest
env:
NEMESIS_VERSION_NAME: "autobuild-${{ github.sha }}"
NEMESIS_BUILD_CHANNEL: "autobuild"
steps:
- name: Checkout
uses: actions/checkout@v6.0.1
- name: Setup Java enviroment
uses: actions/setup-java@v5.1.0
with:
distribution: temurin
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Download CB1060
run: |
mkdir -p libs
curl -L -o libs/craftbukkit-1060.jar https://archive.org/download/craftbukkit1060/craftbukkit1-7-3%281060%29.jar
- name: Build with Gradle
run: ./gradlew buildAll
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
path: build/libs/*.jar

41
.github/workflows/release-build.yml vendored Normal file
View file

@ -0,0 +1,41 @@
name: Release Build
on:
release:
types: [published]
jobs:
build-and-release:
runs-on: ubuntu-latest
env:
NEMESIS_VERSION_NAME: "${{ github.ref_name }}"
NEMESIS_BUILD_CHANNEL: "production"
steps:
- name: Checkout
uses: actions/checkout@v6.0.1
- name: Set up Java
uses: actions/setup-java@v5.1.0
with:
distribution: temurin
java-version: 21
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v5
- name: Download CB1060
run: |
mkdir -p libs
curl -L -o libs/craftbukkit-1060.jar \
https://archive.org/download/craftbukkit1060/craftbukkit1-7-3%281060%29.jar
- name: Build with Gradle
run: ./gradlew buildAll
- name: Upload artifacts to release
run: |
curl -X POST \
-H "Authorization: token ${{ secrets.FORGEJO_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
https://inspiran.beetal-castor.ts.net/git/api/v1/repos/${{ github.repository }}/releases/tags/${{ github.ref_name }}/assets \
--data-binary @build/libs/your-artifact.jar

2
.gitignore vendored
View file

@ -2,3 +2,5 @@
build/ build/
out/ out/
.idea/ .idea/
libs/
run/

View file

@ -1,4 +1,24 @@
# Eye of Nemesis <img alt="eye_of_nemesis_social_cover" src="https://github.com/user-attachments/assets/a24bc92f-4dc2-4594-93c9-d056130f9695" />
Eye of Nemesis is a Minecraft Paper plugin that allows server admins to write *policies* that will deny or allow (black/whitelist) players to do specific things.
You can create policies based on player location, ~but other types of policies are available, like permission and player-name policy.~ (WIP) [![Modrinth Badge](https://img.shields.io/badge/Modrinth-Black?style=social&logo=Modrinth&logoColor=green)](https://modrinth.com/plugin/eye-of-nemesis)
[![English Wiki Badge](https://img.shields.io/badge/English-White?style=flat-square&label=Wiki&color=black)](https://github.com/adrianvic/NemesisEye/wiki)
[![Portuguese Wiki Badge](https://img.shields.io/badge/Portuguese-White?style=flat-square&label=Wiki&color=black)](https://mgr.rf.gd/w/Eye_of_Nemesis)
> [!IMPORTANT]
> This project is in an early stage, please report any bug you find.
# Eye of Nemesis
Eye of Nemesis is a plugin that allows server admins to write policies that will deny or allow (black/whitelist) players to do specific things based on the value of nodes.
## Motivations
I made this plugin as an effort to preserve a village from my private server. Originally from beta 1.7.3 Betalands server, then transferred to RetroMC, and then finally we downloaded the chunks to merge into our server, I was afraid it would not have the same feeling after all the updates, so I had the idea to make a plugin that can block the newer features.
## Game version and loaders
Since version 1.0.3-SNAPSHOT, Eye of Nemesis has _reflection_, a technique that allows me to target multiple versions of the game while sharing the codebase across versions.
Currently, we support the following Minecraft versions/loaders:
- **PaperMC** `1.21, 1.21.1, 1.21.2, 1.21.3, 1.21.4, 1.21.5, 1.21.6, 1.21.7, 1.21.8, 1.21.9, 1.21.10`
- **Bukkit** `b1.7.3 (CB1060)`
## Performance
This plugin is not scalable as it is and will end up running unoptimized checks when your players do things with policies in effect, I made it for a server with a few friends. I'll look forward into writing more performant code after all my other priorities are implemented.

View file

@ -1,55 +0,0 @@
plugins {
id 'java'
id("xyz.jpenilla.run-paper") version "2.3.1"
}
group = 'io.github.adrianvic'
version = '1.0.1-SNAPSHOT'
repositories {
mavenCentral()
maven {
name = "papermc-repo"
url = "https://repo.papermc.io/repository/maven-public/"
}
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT")
}
tasks {
runServer {
// Configure the Minecraft version for our task.
// This is the only required configuration besides applying the plugin.
// Your plugin's jar (or shadowJar if present) will be used automatically.
minecraftVersion("1.21")
}
}
def targetJavaVersion = 21
java {
def javaVersion = JavaVersion.toVersion(targetJavaVersion)
sourceCompatibility = javaVersion
targetCompatibility = javaVersion
if (JavaVersion.current() < javaVersion) {
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
}
}
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8'
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
options.release.set(targetJavaVersion)
}
}
processResources {
def props = [version: version]
inputs.properties props
filteringCharset 'UTF-8'
filesMatching('plugin.yml') {
expand props
}
}

154
build.gradle.kts Normal file
View file

@ -0,0 +1,154 @@
plugins {
java
id("xyz.jpenilla.run-paper") version "2.3.1"
}
group = "io.github.adrianvic"
version = System.getenv("NEMESIS_VERSION_NAME") ?: "unknown"
repositories {
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
}
/* ----------------------------------------- */
/* SUPPORTED VERSIONS */
/* ----------------------------------------- */
val mcVersions = listOf(
"b1_7_3",
"r1_21"
)
/* ----------------------------------------- */
/* CREATE SOURCE SET PER VERSION */
/* ----------------------------------------- */
tasks.withType<ProcessResources> {
inputs.property("version", project.version)
filesMatching("plugin.yml") {
expand("version" to project.version)
}
}
mcVersions.forEach { ver ->
val ss = sourceSets.create(ver) {
java.srcDir("src/$ver/java")
resources.setSrcDirs(listOf("src/$ver/resources", "src/main/resources"))
compileClasspath += sourceSets["main"].output
runtimeClasspath += output + compileClasspath
}
tasks.withType<ProcessResources> {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
configurations[ss.implementationConfigurationName]
.extendsFrom(configurations["implementation"])
configurations[ss.compileOnlyConfigurationName]
.extendsFrom(configurations["compileOnly"])
}
/* ----------------------------------------- */
/* DEPENDENCIES */
/* ----------------------------------------- */
dependencies {
add("compileOnly", "io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT")
add("r1_21CompileOnly", "io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT")
add("b1_7_3CompileOnly", files("libs/craftbukkit-1060.jar"))
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
testImplementation("org.mockito:mockito-core:5.5.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testImplementation("io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT")
testImplementation("com.github.seeseemelk:MockBukkit-v1.21:3.102.0")
// Allow tests to see the versioned implementations
mcVersions.forEach { ver ->
testImplementation(sourceSets[ver].output)
}
}
tasks.test {
useJUnitPlatform()
}
/* ----------------------------------------- */
/* BUILD TASKS */
/* ----------------------------------------- */
mcVersions.forEach { ver ->
tasks.register<Jar>("jar${ver.replace(".", "_").replace("-", "_").replace("/", "_").capitalize()}") {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from(sourceSets["main"].output)
from(sourceSets[ver].output)
archiveClassifier.set(ver)
manifest {
attributes(
"Nemesis-Impl-Version" to ver,
"Nemesis-Environment" to (System.getenv("NEMESIS_BUILD_CHANNEL") ?: "dev")
)
}
}
}
tasks.register("buildAll") {
dependsOn(tasks.withType<Jar>())
}
tasks.register<Jar>("shadowJar") {
from(sourceSets["main"].output)
mcVersions.forEach { ver ->
from(sourceSets[ver].output)
}
// This is kinda gross and, essentially, we shouldn't have it here...
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveClassifier.set("all-implementations")
archiveVersion.set(project.version.toString())
manifest {
attributes(
"Implemented-Versions" to mcVersions.joinToString(",")
)
}
}
/* ----------------------------------------- */
/* JAVA SETTINGS */
/* ----------------------------------------- */
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
}
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
}
tasks.runServer {
minecraftVersion("1.21")
downloadPlugins {
modrinth("viaversion", "5.7.0")
modrinth("luckperms", "v5.5.17-bukkit")
}
}
tasks.register("runServerClean") {
doFirst {
exec {
commandLine("rm", "run/plugins/Eye-of-Nemesis/settings.yml")
}
}
dependsOn("runServer")
}

34
docs/Nodes.md Normal file
View file

@ -0,0 +1,34 @@
# Nodes
Nodes narrow down even further if an action can be done or not. A node needs _properties_ to know what it should check against, for example, a HIT node needs a _list_ of blocks that it will check if the player is holding; while a USE_ENCHANTMENT node needs a mapping of enchantment to level. Some nodes like glyde don't need a value and will ignore if you provide one.
```yaml
nodes:
- [HIT]:
values:
- 'GOLD_SWORD'
- [USE_ENCHANTMENT]:
values:
- "UNBREAKING": "1"
- [GLYDE]
```
## Available nodes
### INTERACT
Triggered on `InteractionEvent`, returns true when the provided `list` contains the material used to interact.
### HIT
Triggered on `EntityDamageByEntityEvent`, returns true when the provided `list` contains the material used to hit.
### PLACE
Triggered on `BlockPlaceEvent`, returns true when the provided `list` contains the material being placed.
### BREAK
Triggered on `BlockBreakEvent`, returns true when the provided `list` contains the material used to break.
### EQUIP
Triggered on `InventoryClickEvent` and `PlayerInteractEvent` (only if item is an armor), returns true when the provided `list` contains the material being equipped.
### GLYDE
Triggered on `PlayerMoveEvent`, returns true if player is gliding.
### USE_ENCHANTMENT
Triggered on `BlockBreakEvent` and `onEntityDamageByEntityEvent`, returns true the used item has an enchantment matching the provided mapping of enchantment-level.

89
docs/Policies.md Normal file
View file

@ -0,0 +1,89 @@
# Policies
Policies are a list of nodes with a specific condition to be met, they also determine what effect a node is going to have. Below is an example policy:
```yaml
- name: "Allow example"
type: "location"
effect: ALLOW
weight: 1
worlds: [world]
locations:
coordinates:
- corner1: { x: 2100, y: 256, z: 1400 }
corner2: { x: 1000, y: -64, z: 2200 }
nodes:
- [INTERACT, BREAK, HIT, PLACE]:
values:
- AIR
```
## Properties
### `name`
Name of the policy, must not contain spaces or else commands that require you to type the policy name will not work.
Example: `name: "disable-axe-damage"`
### `type`
One of the policy [types](#Types).
Example: `type: location`
### `effect`
Can be `deny` to block the action if a node check returns true, `ALLOW` to revert `DENY` effects if the node check is true or `ALLOWONLY` to allow the action **only** if the node check returns true.
Example: `effect: DENY`
### `weight`
A policy with greater weight will override a conflicting policy of lower weight.
Example: `weight: 0`
### `nodes`
Must contain a list of node mappings to evaluate if the policy applies to a player.
Example:
```yaml
nodes:
- [HIT]:
values:
- '.*_AXE$'
```
### `locations`
Only used for `type: "location"`, is a list of mappings where corner1 and corner2 should map x, y and z to two corners of a rectangular area where the policy will take effect; `worlds` may be defined here.
Example:
```yaml
locations:
worlds: [world]
coordinates:
- corner1: { x: 2100, y: 256, z: 1400 }
corner2: { x: 1000, y: -64, z: 2200 }
```
### `permissions`
Only used for `type: "permission"`, is a list of permissions.
Example:
```yaml
permissions:
- "server.axehit"
```
### `names`
Only used for `type: "playerName"`, is a list of player names.
## Types
### `global`
Always matches.
### `location`
Matches if the player location is inside any rectangle defined in the `locations` property.
### `playerName`
Matches if the player name is in the `names` property.
### `permission`
Matches if the player has a permission in the `permissions` property.

View file

@ -1 +1 @@
rootProject.name = 'regions' rootProject.name = 'eyeofnemesis'

View file

@ -0,0 +1,89 @@
package io.github.adrianvic.nemesiseye.impl;
import io.github.adrianvic.nemesiseye.Nemesis;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.util.config.Configuration;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
public class ConfigurationEx extends Configuration {
private final File configFile;
private final Log logger;
JavaPlugin plugin;
public ConfigurationEx(String fileName, Log _logger) {
super(new File(Nemesis.getInstance().getDataFolder(), fileName));
this.plugin = Nemesis.getInstance();
logger = _logger;
this.configFile = new File(plugin.getDataFolder(), fileName);
}
@Override
public void load() {
createParentDirectories();
if (!configFile.exists()) {
copyDefaultConfig();
}
try {
super.load();
} catch (Exception e) {
logger.severe(String.format("Failed to load config '%s': %s", configFile.getName(), e.getMessage()));
}
}
private void createParentDirectories() {
try {
Files.createDirectories(configFile.getParentFile().toPath());
} catch (IOException e) {
logger.severe(String.format("Failed to generate default config directory: %s", e.getMessage()));
}
}
private void copyDefaultConfig() {
// Load the config from the JAR directly (it is located at the root level)
String resourcePath = "/" + configFile.getName(); // Root path of JAR
try (InputStream input = plugin.getClass().getResourceAsStream(resourcePath)) {
if (input == null) {
logger.severe(String.format("Default config '%s' wasn't found in the JAR.", configFile.getName()));
return;
}
Files.copy(input, configFile.toPath());
if (Files.exists(configFile.toPath())) {
logger.info(String.format("Default config '%s' generated successfully.", configFile.getName()));
} else {
logger.warning("We tried to generate the default config file, but it was not found even after the creation. Maybe your permissions are broken?");
}
} catch (IOException e) {
logger.severe(String.format("Failed to generate default config '%s': %s", configFile.getName(), e.getMessage()));
}
}
public void loadConfig() {
try {
this.load();
logger.info(String.format("Config '%s' loaded successfully.", configFile.getName()));
} catch (Exception e) {
logger.severe(String.format("Failed to load config '%s': %s", configFile.getName(), e.getMessage()));
}
}
public void saveConfig() {
try {
this.save();
logger.info(String.format("Config '%s' saved successfully.", configFile.getName()));
} catch (Exception e) {
logger.severe(String.format("Failed to save config '%s': %s", configFile.getName(), e.getMessage()));
}
}
public File getConfig() {
return configFile;
}
}

View file

@ -0,0 +1,41 @@
package io.github.adrianvic.nemesiseye.impl;
import static org.bukkit.Bukkit.getServer;
import io.github.adrianvic.nemesiseye.Nemesis;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.java.JavaPlugin;
public class Log {
JavaPlugin plugin;
PluginDescriptionFile pdf;
public Log() {
plugin = Nemesis.getInstance();
pdf = plugin.getDescription();
}
public void info(String message) {
getServer().getLogger().info("[" + pdf.getName() + "] " + message);
}
public void infoc(String message) {
getServer().getLogger().info("[" + pdf.getName() + "] " + message);
}
public void warning(String message) {
getServer().getLogger().warning("[" + pdf.getName() + "] " + message);
}
public void warningc(String message) {
getServer().getLogger().warning("[" + pdf.getName() + "] " + message);
}
public void severe(String message) {
getServer().getLogger().severe("[" + pdf.getName() + "] " + message);
}
public void severec(String message) {
getServer().getLogger().severe("[" + pdf.getName() + "] " + message);
}
}

View file

@ -0,0 +1,152 @@
package io.github.adrianvic.nemesiseye.impl;
import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.impl.commands.Eye;
import io.github.adrianvic.nemesiseye.impl.events.BlockEventListener;
import io.github.adrianvic.nemesiseye.impl.events.EntityEventListener;
import io.github.adrianvic.nemesiseye.impl.events.PlayerEventListener;
import io.github.adrianvic.nemesiseye.policy.Policy;
import io.github.adrianvic.nemesiseye.policy.PolicyParsers;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.Event;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@SuppressWarnings("unused")
public class b1_7_3 implements Glimmer {
JavaPlugin plugin;
PluginManager pm;
ConfigurationEx config;
@Override
public File loadConfigFile() {
config = new ConfigurationEx("settings.yml", new Log());
config.load();
return config.getConfig();
}
@Override
public List<Policy> loadPoliciesFromFile(File file) {
List<?> rawPolicies = config.getList("Policies");
if (rawPolicies == null) {
return new ArrayList<>();
}
List<Map<?, ?>> result = new ArrayList<>(rawPolicies.size());
for (Object entry : rawPolicies) {
if (entry instanceof Map<?,?> m) {
result.add(m);
}
}
List<Policy> allPolicies = new ArrayList<>();
for (Map<?, ?> policyMap : result) {
if (policyMap.get("type") != null && policyMap.get("type") instanceof String type) {
allPolicies.add(PolicyParsers.get(type).parse(policyMap));
}
}
return allPolicies;
}
@Override
public void onLoad() {
plugin = Nemesis.getInstance();
pm = Nemesis.getInstance().getPluginManager();
pm.registerEvent(Event.Type.ENTITY_DAMAGE, new EntityEventListener(), Event.Priority.Normal, plugin);
pm.registerEvent(Event.Type.BLOCK_BREAK, new BlockEventListener(), Event.Priority.Normal, plugin);
pm.registerEvent(Event.Type.PLAYER_INTERACT, new PlayerEventListener(), Event.Priority.Normal, plugin);
plugin.getCommand("eye").setExecutor(new Eye());
}
@Override
public ItemStack getItemInMainHandHumanEntity(HumanEntity entity) {
return entity.getItemInHand();
}
@Override
public boolean isAir(ItemStack item) {
return item == null || item.getType() == Material.AIR;
}
@Override
public boolean isGliding(org.bukkit.entity.Player player) {
return false;
}
@Override
public void setGliding(org.bukkit.entity.Player player, boolean gliding) {
}
@Override
public boolean hasPermission(org.bukkit.command.CommandSender sender, String permission) {
if (sender instanceof org.bukkit.entity.Player p) {
return p.isOp();
}
return true; // Console always has permission
}
@Override
public boolean isArmorEquipAttempt(org.bukkit.event.Event event) {
return false;
}
@Override
public ItemStack getEquippedItem(org.bukkit.event.Event event) {
return null;
}
@Override
public void sendMessage(CommandSender commandSender, String text) {
String[] lines = text.split("\\r?\\n");
for (String line : lines) {
commandSender.sendMessage(line);
}
}
@Override
public boolean hasItemMeta(ItemStack item) {
return false;
}
@Override
public List<World> getWorlds() {
return plugin.getServer().getWorlds();
}
@Override
public boolean hasEnchantment(ItemStack item, Map<String, String> valuesmap) {
return false;
}
@Override
public boolean hasAnyEnchantment(ItemStack itemStack) {
return false;
}
@Override
public boolean isArmor(ItemStack item) {
if (item == null || item.getType() == Material.AIR) {
return false;
}
String name = item.getType().name();
return name.endsWith("_HELMET")
|| name.endsWith("_CHESTPLATE")
|| name.endsWith("_LEGGINGS")
|| name.endsWith("_BOOTS");
}
}

View file

@ -0,0 +1,19 @@
package io.github.adrianvic.nemesiseye.impl.commands;
import io.github.adrianvic.nemesiseye.commands.EyeCore;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
public class Eye implements CommandExecutor {
private final EyeCore core;
public Eye() {
core = new EyeCore();
}
@Override
public boolean onCommand(CommandSender commandSender, Command command, String s, String [] strings) {
return core.onCommand(commandSender, command, s, strings);
}
}

View file

@ -0,0 +1,18 @@
package io.github.adrianvic.nemesiseye.impl.events;
import io.github.adrianvic.nemesiseye.Events;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockListener;
import org.bukkit.event.block.BlockPlaceEvent;
public class BlockEventListener extends BlockListener {
@Override
public void onBlockBreak(BlockBreakEvent event) {
Events.onBlockBreak(event);
}
@Override
public void onBlockPlace(BlockPlaceEvent event) {
Events.onBlockPlaceEvent(event);
}
}

View file

@ -0,0 +1,13 @@
package io.github.adrianvic.nemesiseye.impl.events;
import io.github.adrianvic.nemesiseye.Events;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityListener;
public class EntityEventListener extends EntityListener {
@Override
public void onEntityDamage(EntityDamageEvent event) {
if (event instanceof EntityDamageByEntityEvent e) Events.onEntityDamageByEntityEvent(e);
}
}

View file

@ -0,0 +1,13 @@
package io.github.adrianvic.nemesiseye.impl.events;
import io.github.adrianvic.nemesiseye.Events;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerListener;
public class PlayerEventListener extends PlayerListener {
@Override
public void onPlayerInteract(PlayerInteractEvent event) {
Events.onInteractionEvent(event);
}
}

View file

@ -1,56 +1,45 @@
package io.github.adrianvic.nemesiseye; package io.github.adrianvic.nemesiseye;
import io.github.adrianvic.nemesiseye.policy.LocationPolicy; import io.github.adrianvic.nemesiseye.policy.Policy;
import org.bukkit.configuration.file.YamlConfiguration; import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import io.github.adrianvic.nemesiseye.policy.policies.GlobalPolicy;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
public class Config { public class Config {
private final static Config instance = new Config(); private static Config instance = new Config();
private final Glimmer glim = Nemesis.getInstance().getGlimmer();
private File file; private File file;
private YamlConfiguration config; private List<Policy> policies = new ArrayList<>();
private List<LocationPolicy> locationPolicies; private Config() {}
// private List<PermissionPolicy> permissionPolicies;
// private List<PlayerNamePolicy> playerNamePolicies;
private Config() {
}
public void load() { public void load() {
file = new File(Nemesis.getInstance().getDataFolder(), "settings.yml"); policies = glim.loadPoliciesFromFile(glim.loadConfigFile());
policies.sort(Comparator.comparingInt(Policy::weight).reversed());
if (!file.exists())
Nemesis.getInstance().saveResource("settings.yml", false);
config = new YamlConfiguration();
config.options().parseComments(true);
try {
config.load(file);
} catch (Exception e) {
e.printStackTrace();
}
locationPolicies = LocationPolicy.parseLocationPolicy(config.getMapList("Policies.Location"));
} }
public void save() { // TODO: Implement config saving
try { //
config.save(file); // public void save() {
} catch (Exception e) { // try {
e.printStackTrace(); // config.save(file);
} // } catch (Exception e) {
} // e.printStackTrace();
// }
// }
//
// public void set(String path, Object value) {
// config.set(path, value);
// save();
// }
public void set(String path, Object value) { public List<Policy> getPolicies() {
config.set(path, value); return policies;
save();
}
public List<LocationPolicy> getLocationPolicies() {
return locationPolicies;
} }
public static Config getInstance() { public static Config getInstance() {

View file

@ -1,5 +1,9 @@
package io.github.adrianvic.nemesiseye; package io.github.adrianvic.nemesiseye;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class DataShifter { public class DataShifter {
@ -8,4 +12,52 @@ public class DataShifter {
Pattern pattern = Pattern.compile(cleanPattern, Pattern.CASE_INSENSITIVE); Pattern pattern = Pattern.compile(cleanPattern, Pattern.CASE_INSENSITIVE);
return pattern.matcher(against).matches(); return pattern.matcher(against).matches();
} }
public static boolean safeMatches(List<String> expressions, String against) {
for (String s : expressions) {
if (DataShifter.safeMatches(s, against)) {
return true;
}
}
return false;
}
public static List<String> parseValueToStringList(List<Object> values) {
List<String> result = new ArrayList<>();
for (Object o : values) {
if (o instanceof String) result.add((String) o);
}
return result;
}
public static Map<String,String> parseValueToStringMap(List<Object> raw) {
Map<String,String> out = new HashMap<>();
for (Object o : raw) {
if (o instanceof Map<?,?> map) {
for (Map.Entry<?,?> e : map.entrySet()) {
out.put(String.valueOf(e.getKey()), String.valueOf(e.getValue()));
}
} else if (o instanceof String s) {
String[] parts = s.split(":", 2);
if (parts.length == 2) {
out.put(parts[0].trim(), parts[1].trim());
}
}
}
return out;
}
public static <T extends Enum<T>> T enumOrDefault(Class<T> type, String string, T def) {
try {
return Enum.valueOf(type, string);
} catch (IllegalArgumentException e) {
return def;
} catch (Exception e) {
e.printStackTrace();
return def;
}
}
} }

View file

@ -1,30 +0,0 @@
package io.github.adrianvic.nemesiseye;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
public class EventListener implements Listener {
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
event.setCancelled(!Validator.canBreak(event.getPlayer()));
}
@EventHandler
public void onInteractionEvent(PlayerInteractEvent event) {
if (event.getItem() != null) {
event.setCancelled(!Validator.canInteract(event.getPlayer()));
}
}
@EventHandler
public void onEntityDamageByEntityEvent(EntityDamageByEntityEvent event) {
if (event.getDamager() instanceof Player) {
event.setCancelled(!Validator.canHit((HumanEntity) event.getDamager()));
}
}
}

View file

@ -0,0 +1,98 @@
package io.github.adrianvic.nemesiseye;
import io.github.adrianvic.nemesiseye.policy.Action;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.inventory.ItemStack;
import java.util.List;
public class Events {
private static Glimmer g() {
return Nemesis.getInstance().getGlimmer();
}
public static void onBlockBreak(BlockBreakEvent event) {
event.setCancelled(
!Validator.can(
event.getPlayer(),
List.of(Action.BREAK, Action.USE_ENCHANTMENT),
event
)
);
}
public static void onInteractionEvent(PlayerInteractEvent event) {
ItemStack item = event.getItem();
if (g().isAir(item)) {
return;
}
// Right-click armor equipping
if (g().isArmor(item)
&& !Validator.can(event.getPlayer(), Action.EQUIP, event)) {
event.setCancelled(true);
return;
}
// Normal item interaction
event.setCancelled(
!Validator.can(event.getPlayer(), Action.INTERACT, event)
);
}
public static void onBlockPlaceEvent(BlockPlaceEvent event) {
event.setCancelled(
!Validator.can(event.getPlayer(), Action.PLACE, event)
);
}
public static void onEntityDamageByEntityEvent(EntityDamageByEntityEvent event) {
if (event.getDamager() instanceof Player player) {
event.setCancelled(
!Validator.can(
player,
List.of(Action.HIT, Action.USE_ENCHANTMENT),
event
)
);
}
}
public static void onPlayerMoveEvent(PlayerMoveEvent event) {
if (g().isGliding(event.getPlayer())
&& !Validator.can(
event.getPlayer(),
List.of(Action.GLYDE),
event
)) {
g().setGliding(event.getPlayer(), false);
}
}
public static void onInventoryClickEvent(InventoryClickEvent event) {
if (!g().isArmorEquipAttempt(event)) {
return;
}
HumanEntity entity = event.getWhoClicked();
if (!Validator.can(entity, Action.EQUIP, event)) {
event.setCancelled(true);
}
}
public static void onCreatureSpawnEvent(CreatureSpawnEvent event) {
event.setCancelled(!Validator.can(event.getEntity(), Action.SPAWN, event));
}
}

View file

@ -1,23 +1,28 @@
package io.github.adrianvic.nemesiseye; package io.github.adrianvic.nemesiseye;
import io.github.adrianvic.nemesiseye.commands.Eye; import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import io.github.adrianvic.nemesiseye.reflection.VersionMatcher;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
public final class Nemesis extends JavaPlugin { public final class Nemesis extends JavaPlugin {
private Glimmer glim;
private static final String VERSION_PROP = "impl.version";
private static Nemesis instance;
@Override @Override
public void onEnable() { public void onEnable() {
getServer().getPluginManager().registerEvents(new EventListener(), this); instance = this;
glim = new VersionMatcher().loadGlim();
glim.onLoad();
Config.getInstance().load(); Config.getInstance().load();
getCommand("eye").setExecutor(new Eye());
} }
@Override @Override
public void onDisable() { public void onDisable() {
// Plugin shutdown logic
} }
public static Nemesis getInstance() { public static Nemesis getInstance() { return instance; }
return getPlugin(Nemesis.class); public Glimmer getGlimmer() { return glim; }
} public PluginManager getPluginManager() { return this.getServer().getPluginManager(); }
} }

View file

@ -1,63 +1,55 @@
package io.github.adrianvic.nemesiseye; package io.github.adrianvic.nemesiseye;
import io.github.adrianvic.nemesiseye.policy.Action; import io.github.adrianvic.nemesiseye.policy.Action;
import io.github.adrianvic.nemesiseye.policy.LocationPolicy; import io.github.adrianvic.nemesiseye.policy.Policy;
import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import org.bukkit.entity.HumanEntity; import org.bukkit.entity.HumanEntity;
import org.bukkit.util.BoundingBox; import org.bukkit.entity.LivingEntity;
import org.bukkit.event.Event;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class Validator { public class Validator {
public static boolean canInteract(HumanEntity entity) { public static boolean can(HumanEntity entity, List<Action> actions, Event event) {
return checkAgainstEntity(entity, Action.INTERACT); for (Action action : actions) {
} if (!can(entity, action, event)) {
public static boolean canBreak(HumanEntity entity) { return false;
return checkAgainstEntity(entity, Action.BREAK); }
}
public static boolean canHit(HumanEntity entity) {
return checkAgainstEntity(entity, Action.HIT);
}
public static boolean checkAgainstEntity(HumanEntity entity, Action action) {
return checkAgainstNodes(entity, getNodesForPolicies(getPoliciesForEntity(entity)), action);
}
public static boolean checkAgainstNodes(HumanEntity entity, List<PolicyNode> nodes, Action action) {
for (PolicyNode n : nodes) {
if (!checkAgainstNode(entity, n, action)) return false;
} }
return true; return true;
} }
public static boolean checkAgainstNode(HumanEntity entity, PolicyNode node, Action action) { public static boolean can(LivingEntity entity, Action action, Event event) {
boolean allowed = node.getHandler().allows(entity, node, action); boolean restricted = false;
return node.isWhitelist() != allowed; boolean allowed = false;
}
public static List<PolicyNode> getNodesForPolicies(List<LocationPolicy> policies) { for (Policy policy : getPoliciesForEntity(entity)) {
List<PolicyNode> nodes = new ArrayList<>(); boolean matches = policy.matches(entity, action, event);
for (LocationPolicy p : policies) {
nodes.addAll(p.nodes());
}
return nodes;
}
public static List<LocationPolicy> getPoliciesForEntity(HumanEntity entity) { switch (policy.effect()) {
List<LocationPolicy> lps = Config.getInstance().getLocationPolicies(); case ALLOW:
List<LocationPolicy> applyingLPS = new ArrayList<>(); if (matches) return true;
for (LocationPolicy lp : lps) { break;
for (ArrayList<BoundingBox> boxes : lp.locations()) {
for (BoundingBox box : boxes) { case DENY:
if (box.contains(entity.getLocation().toVector())) { if (matches) return false;
applyingLPS.add(lp); break;
}
}
} }
} }
return applyingLPS;
return true;
}
public static List<Policy> getPoliciesForEntity(LivingEntity entity) {
List<Policy> ps = Config.getInstance().getPolicies();
List<Policy> result = new ArrayList<>();
for (Policy policy : ps) {
if (policy.applies(entity)) result.add(policy);
}
return result;
} }
} }

View file

@ -0,0 +1,35 @@
package io.github.adrianvic.nemesiseye.commands;
import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.commands.sub.*;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.command.CommandSender;
import java.util.HashMap;
import java.util.Map;
public class Commands {
private static final Map<String, Subcommand> commands = new HashMap<>();
private static final Glimmer glim = Nemesis.getInstance().getGlimmer();
static {
commands.put("help", new Help());
commands.put("listpolicies", new ListPolicies());
commands.put("currentpolicies", new CurrentPolicies());
commands.put("policyinfo", new PolicyInfo());
commands.put("reload", new Reload());
}
public static Map<String, Subcommand> getAll() {
return commands;
}
public static Subcommand get(String type) {
return commands.get(type);
}
public static void sendNoPermissionError(CommandSender sender) {
glim.sendMessage(sender, "You do not have the necessary permission to use this command.");
}
}

View file

@ -1,59 +0,0 @@
package io.github.adrianvic.nemesiseye.commands;
import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.commands.sub.*;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class Eye implements CommandExecutor, TabCompleter {
private final Map<String, Subcommand> subs = new HashMap<>();
public Eye() {
register(new Reload());
register(new ListPolicies());
register(new PolicyInfo());
register(new CurrentPolicies());
}
private void register(Subcommand sub) {
subs.put(sub.name(), sub);
}
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String @NotNull [] strings) {
if (strings.length == 0) {
commandSender.sendMessage("""
%sEye of Nemesis%s version %s
Usage: '/eye <command>'
Use '/eye help' for a list of available commands
""".formatted(ChatColor.RED, ChatColor.RESET, Nemesis.getInstance().getDescription().getVersion()));
} else {
Subcommand sub = subs.get(strings[0].toLowerCase());
if (sub == null) {
commandSender.sendMessage("Unknown command, try '/eye help' to list available commands.");
return true;
}
return sub.execute(commandSender, Arrays.copyOfRange(strings, 1, strings.length));
}
return false;
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String @NotNull [] strings) {
if (strings.length == 1) {
return new ArrayList<>(subs.keySet());
}
Subcommand sub = subs.get(strings[0].toLowerCase());
if (sub != null) {
return sub.onTabComplete(commandSender, Arrays.copyOfRange(strings, 1, strings.length));
}
return List.of();
}
}

View file

@ -0,0 +1,57 @@
package io.github.adrianvic.nemesiseye.commands;
import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.commands.sub.*;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import java.util.*;
public class EyeCore {
private final Glimmer glim = Nemesis.getInstance().getGlimmer();
public EyeCore() {}
public boolean onCommand(CommandSender commandSender, Command command, String s, String [] strings) {
if (strings.length == 0) {
glim.sendMessage(commandSender, """
%sEye of Nemesis%s version %s%s%s
Usage: '/eye <command>'
Use '/eye help' for a list of available commands
""".formatted(ChatColor.AQUA, ChatColor.WHITE, ChatColor.GRAY, Nemesis.getInstance().getDescription().getVersion(), ChatColor.WHITE));
} else {
Subcommand sub = Commands.get(strings[0].toLowerCase());
if (sub == null) {
commandSender.sendMessage("Unknown command, try '/eye help' to list available commands.");
return true;
}
else if (glim.hasPermission(commandSender, sub.permission())) {
return sub.execute(commandSender, Arrays.copyOfRange(strings, 1, strings.length));
} else {
// Nemesis.getInstance().getLogger().info("does not have %s".formatted(sub.permission()));
Commands.sendNoPermissionError(commandSender);
return true;
}
}
return false;
}
public List<String> onTabComplete(CommandSender commandSender, Command command, String s, String [] strings) {
if (strings.length == 1) {
Map<String, Subcommand> cmds = new HashMap<>();
for (Map.Entry<String, Subcommand> e : Commands.getAll().entrySet()) {
if (glim.hasPermission(commandSender, e.getValue().permission())) {
cmds.put(e.getKey(), e.getValue());
cmds.put(e.getKey(), e.getValue());
}
}
return new ArrayList<>(cmds.keySet());
}
Subcommand sub = Commands.get(strings[0].toLowerCase());
if (sub != null && glim.hasPermission(commandSender, sub.permission())) {
return sub.onTabComplete(commandSender, Arrays.copyOfRange(strings, 1, strings.length));
}
return List.of();
}
}

View file

@ -1,7 +1,7 @@
package io.github.adrianvic.nemesiseye.commands.sub; package io.github.adrianvic.nemesiseye.commands.sub;
import io.github.adrianvic.nemesiseye.Validator; import io.github.adrianvic.nemesiseye.Validator;
import io.github.adrianvic.nemesiseye.policy.LocationPolicy; import io.github.adrianvic.nemesiseye.policy.Policy;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.HumanEntity; import org.bukkit.entity.HumanEntity;
@ -9,16 +9,11 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
public class CurrentPolicies implements Subcommand { public class CurrentPolicies implements Subcommand {
@Override
public String name() {
return "currentpolicies";
}
@Override @Override
public boolean execute(CommandSender commandSender, String[] args) { public boolean execute(CommandSender commandSender, String[] args) {
List<LocationPolicy> policies = Validator.getPoliciesForEntity((HumanEntity) commandSender); List<Policy> policies = Validator.getPoliciesForEntity((HumanEntity) commandSender);
List<String> pstrings = new ArrayList<>(); List<String> pstrings = new ArrayList<>();
for (LocationPolicy p : policies) { for (Policy p : policies) {
pstrings.add(" %s (%s nodes)".formatted(p.name(), p.nodes().size())); pstrings.add(" %s (%s nodes)".formatted(p.name(), p.nodes().size()));
} }
if (pstrings.isEmpty()) { if (pstrings.isEmpty()) {
@ -34,4 +29,19 @@ public class CurrentPolicies implements Subcommand {
public List<String> onTabComplete(CommandSender sender, String[] args) { public List<String> onTabComplete(CommandSender sender, String[] args) {
return List.of(); return List.of();
} }
@Override
public String description() {
return "Lists policies appliying to you.";
}
@Override
public String usage() {
return "";
}
@Override
public String permission() {
return "nemsiseye.policies.list.self";
}
} }

View file

@ -0,0 +1,54 @@
package io.github.adrianvic.nemesiseye.commands.sub;
import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.commands.Commands;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.command.CommandSender;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class Help implements Subcommand {
private final Glimmer glim = Nemesis.getInstance().getGlimmer();
@Override
public boolean execute(CommandSender commandSender, String[] strings) {
List<String> rstr = new ArrayList<>();
Map<String, Subcommand> allSubcommands = Commands.getAll();
if (allSubcommands.isEmpty()) {
rstr.add("No commands found.");
}
for (Map.Entry<String, Subcommand> e : allSubcommands.entrySet()) {
if (e.getValue().hasPermission(commandSender)) {
rstr.add("""
%s - %s
Usage: /eye %s %s
""".formatted(e.getKey(), e.getValue().description(), e.getKey(), e.getValue().usage()));
}
}
glim.sendMessage(commandSender, String.join("\n", rstr));
return true;
}
@Override
public List<String> onTabComplete(CommandSender sender, String[] strings) {
return List.of();
}
@Override
public String description() {
return "Lists all commands.";
}
@Override
public String usage() {
return "";
}
@Override
public String permission() {
return "nemsiseye.help";
}
}

View file

@ -1,23 +1,17 @@
package io.github.adrianvic.nemesiseye.commands.sub; package io.github.adrianvic.nemesiseye.commands.sub;
import io.github.adrianvic.nemesiseye.Config; import io.github.adrianvic.nemesiseye.Config;
import io.github.adrianvic.nemesiseye.policy.LocationPolicy; import io.github.adrianvic.nemesiseye.policy.Policy;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class ListPolicies implements Subcommand { public class ListPolicies implements Subcommand {
@Override
public String name() {
return "listpolicies";
}
@Override @Override
public boolean execute(CommandSender commandSender, String[] args) { public boolean execute(CommandSender commandSender, String[] args) {
List<String> rstr = new ArrayList<>(); List<String> rstr = new ArrayList<>();
for (LocationPolicy p : Config.getInstance().getLocationPolicies()) { for (Policy p : Config.getInstance().getPolicies()) {
rstr.add(p.name()); rstr.add(p.name());
} }
commandSender.sendMessage(String.join(", ", rstr) + "."); commandSender.sendMessage(String.join(", ", rstr) + ".");
@ -28,4 +22,19 @@ public class ListPolicies implements Subcommand {
public List<String> onTabComplete(CommandSender sender, String[] args) { public List<String> onTabComplete(CommandSender sender, String[] args) {
return List.of(); return List.of();
} }
@Override
public String description() {
return "Lists all loaded policies.";
}
@Override
public String usage() {
return "";
}
@Override
public String permission() {
return "nemsiseye.policy.list.all";
}
} }

View file

@ -1,7 +1,9 @@
package io.github.adrianvic.nemesiseye.commands.sub; package io.github.adrianvic.nemesiseye.commands.sub;
import io.github.adrianvic.nemesiseye.Config; import io.github.adrianvic.nemesiseye.Config;
import io.github.adrianvic.nemesiseye.policy.LocationPolicy; import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.policy.Policy;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -9,25 +11,18 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
public class PolicyInfo implements Subcommand { public class PolicyInfo implements Subcommand {
@Override private final Glimmer glim = Nemesis.getInstance().getGlimmer();
public String name() {
return "policyinfo";
}
@Override @Override
public boolean execute(CommandSender commandSender, String[] strings) { public boolean execute(CommandSender commandSender, String[] strings) {
List<LocationPolicy> policies = Config.getInstance().getLocationPolicies(); List<Policy> policies = Config.getInstance().getPolicies();
for (LocationPolicy lp : policies) { for (Policy policy : policies) {
if (lp.name().equals(strings[0])) { if (policy.name().equals(strings[0])) {
String locations = lp.locations().toString(); glim.sendMessage(commandSender, String.format("""
Showing info for policy %s%s%s:
commandSender.sendMessage(String.format("""
Showing info for policy "%s%s%s":
Type: %s Type: %s
Locations: %s
Nodes: %s Nodes: %s
%s """, ChatColor.GREEN, policy.name(), ChatColor.WHITE, policy.getClass().getTypeName(), policy.nodes().size()));
""", ChatColor.UNDERLINE, lp.name(), ChatColor.RESET, "location", locations, lp.nodes().size(), lp.allowlist() ? "Is allowlist" : "Is blacklist"));
} }
} }
return true; return true;
@ -36,9 +31,24 @@ public class PolicyInfo implements Subcommand {
@Override @Override
public List<String> onTabComplete(CommandSender sender, String[] args) { public List<String> onTabComplete(CommandSender sender, String[] args) {
List<String> rstr = new ArrayList<>(); List<String> rstr = new ArrayList<>();
for (LocationPolicy p : Config.getInstance().getLocationPolicies()) { for (Policy p : Config.getInstance().getPolicies()) {
rstr.add(p.name()); rstr.add(p.name());
} }
return rstr; return rstr;
} }
@Override
public String description() {
return "Shows info for a specified policy.";
}
@Override
public String usage() {
return "<policy>";
}
@Override
public String permission() {
return "nemsiseye.policy.info";
}
} }

View file

@ -1,16 +1,16 @@
package io.github.adrianvic.nemesiseye.commands.sub; package io.github.adrianvic.nemesiseye.commands.sub;
import io.github.adrianvic.nemesiseye.Config; import io.github.adrianvic.nemesiseye.Config;
import io.github.adrianvic.nemesiseye.commands.Commands;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import java.util.List; import java.util.List;
public class Reload implements Subcommand { public class Reload implements Subcommand {
@Override @Override
public boolean execute(CommandSender commandSender, String [] strings) { public boolean execute(CommandSender commandSender, String [] strings) {
Config.getInstance().load(); Config.getInstance().load();
commandSender.sendMessage("Reloading..."); commandSender.sendMessage("Reloading...");
return true; return true;
} }
@ -20,7 +20,17 @@ public class Reload implements Subcommand {
} }
@Override @Override
public String name() { public String permission() {
return "reload"; return "nemsiseye.reload";
}
@Override
public String description() {
return "Reloads the plugin configuration file.";
}
@Override
public String usage() {
return "";
} }
} }

View file

@ -1,12 +1,19 @@
package io.github.adrianvic.nemesiseye.commands.sub; package io.github.adrianvic.nemesiseye.commands.sub;
import io.github.adrianvic.nemesiseye.Nemesis;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;
import java.util.List; import java.util.List;
public interface Subcommand { public interface Subcommand {
String name(); String description();
String usage();
@SuppressWarnings("SameReturnValue") @SuppressWarnings("SameReturnValue")
boolean execute(CommandSender commandSender, String[] strings); boolean execute(CommandSender commandSender, String[] strings);
List<String> onTabComplete(CommandSender sender, String[] strings); List<String> onTabComplete(CommandSender sender, String[] strings);
String permission();
default boolean hasPermission(CommandSender sender) {
return Nemesis.getInstance().getGlimmer().hasPermission(sender, permission());
}
} }

View file

@ -4,6 +4,10 @@ public enum Action {
INTERACT, INTERACT,
BREAK, BREAK,
HIT, HIT,
CRAFT, // TODO CRAFT,
EQUIP EQUIP,
PLACE,
USE_ENCHANTMENT,
GLYDE,
SPAWN
} }

View file

@ -0,0 +1,6 @@
package io.github.adrianvic.nemesiseye.policy;
public enum Effect {
DENY,
ALLOW,
}

View file

@ -1,65 +0,0 @@
package io.github.adrianvic.nemesiseye.policy;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.util.BoundingBox;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public record LocationPolicy(String name, List<ArrayList<BoundingBox>> locations, List<PolicyNode> nodes, boolean allowlist) {
public static List<LocationPolicy> parseLocationPolicy(List<Map<?,?>> raw) {
List<LocationPolicy> out = new ArrayList<>(raw.size());
for (Map<?,?> m : raw) {
String name = (String) m.get("name");
boolean allowlist = Boolean.TRUE.equals(m.get("allowList"));
// Nodes
Object rawNodes = m.get("nodes");
List<Map<String, Object>> nodeList = new ArrayList<>();
if (rawNodes instanceof List<?> list) {
for (Object o : list) {
if (o instanceof Map<?, ?> map)
nodeList.add((Map<String, Object>) map);
}
}
List<PolicyNode> nodes = PolicyNode.parseNodes(nodeList, allowlist);
// Parsing locations
List<ArrayList<BoundingBox>> locations = new ArrayList<>();
Object rawGroups = m.get("locations");
List<?> groups = rawGroups instanceof List ? (List<?>) rawGroups : List.of();
// Getting groups
for (Object gObj : groups) {
List<?> group = (List<?>) gObj;
ArrayList<BoundingBox> boxes = new ArrayList<>(group.size());
// Now iterate over regions inside the group
for (Object rObj : group) {
Map<?,?> region = (Map<?, ?>) rObj;
Map<?,?> c1 = (Map<?, ?>) region.get("corner1");
Map<?,?> c2 = (Map<?, ?>) region.get("corner2");
double x1 = ((Number) c1.get("x")).doubleValue();
double y1 = ((Number) c1.get("y")).doubleValue();
double z1 = ((Number) c1.get("z")).doubleValue();
double x2 = ((Number) c2.get("x")).doubleValue();
double y2 = ((Number) c2.get("y")).doubleValue();
double z2 = ((Number) c2.get("z")).doubleValue();
Location loc1 = new Location(Bukkit.getWorlds().getFirst(), x1, y1, z1);
Location loc2 = new Location(Bukkit.getWorlds().getFirst(), x2, y2, z2);
boxes.add(BoundingBox.of(loc1, loc2));
}
locations.add(boxes);
}
out.add(new LocationPolicy(name, locations, nodes, allowlist));
}
return out;
}
}

View file

@ -1,7 +1,9 @@
package io.github.adrianvic.nemesiseye.policy; package io.github.adrianvic.nemesiseye.policy;
import org.bukkit.entity.HumanEntity; import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.Event;
public interface NodeHandler { public interface NodeHandler {
boolean allows(HumanEntity entity, PolicyNode node, Action action); boolean check(LivingEntity entity, PolicyNode node, Action action, Event event);
} }

View file

@ -1,22 +1,25 @@
package io.github.adrianvic.nemesiseye.policy; package io.github.adrianvic.nemesiseye.policy;
import io.github.adrianvic.nemesiseye.policy.handlers.attackWith; import io.github.adrianvic.nemesiseye.policy.handlers.*;
import io.github.adrianvic.nemesiseye.policy.handlers.useEnchantment;
import io.github.adrianvic.nemesiseye.policy.handlers.useItem;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class NodeHandlers { public class NodeHandlers {
private static final Map<String, NodeHandler> handlers = new HashMap<>(); private static final Map<Action, NodeHandler> handlers = new HashMap<>();
static { static {
handlers.put("attackWithItemInHand", new attackWith()); handlers.put(Action.HIT, new UseItem());
handlers.put("useItem", new useItem()); handlers.put(Action.PLACE, new BePlaced());
handlers.put("useEnchantment", new useEnchantment()); handlers.put(Action.INTERACT, new UseItem());
handlers.put(Action.USE_ENCHANTMENT, new UseEnchantment());
handlers.put(Action.GLYDE, new Glyde());
handlers.put(Action.EQUIP, new Equip());
handlers.put(Action.SPAWN, new Spawn());
handlers.put(Action.BREAK, new UseItem()); // TODO: implement place handler
} }
public static NodeHandler get(String type) { public static NodeHandler get(Action type) {
return handlers.get(type); return handlers.get(type);
} }
} }

View file

@ -1,28 +0,0 @@
package io.github.adrianvic.nemesiseye.policy;
import java.util.*;
public class NodeValueParser {
public static List<String> parseValueToStringList(List<Object> values) {
List<String> result = new ArrayList<>();
for (Object o : values) {
if (o instanceof String) result.add((String) o);
}
return result;
}
public static Map<String, String> parseValueToStringMap(List<Object> values) {
Map<String, String> result = new HashMap<>();
for (Object o : values) {
if (o instanceof Map<?, ?> raw) {
for (Map.Entry<?, ?> e : raw.entrySet()) {
if (e.getKey() instanceof String k && e.getValue() instanceof String v) {
result.put(k, v);
}
}
}
}
return result;
}
}

View file

@ -1,7 +0,0 @@
package io.github.adrianvic.nemesiseye.policy;
import org.bukkit.permissions.Permission;
import java.util.ArrayList;
public record PermissionPolicy(String name, ArrayList<Permission> permissions, PolicyNode nodes, boolean allowlist) {}

View file

@ -1,5 +0,0 @@
package io.github.adrianvic.nemesiseye.policy;
import java.util.ArrayList;
public record PlayerNamePolicy(String name, ArrayList<String> playerName, PolicyNode nodes, boolean allowlist) {}

View file

@ -0,0 +1,35 @@
package io.github.adrianvic.nemesiseye.policy;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.Event;
import java.util.List;
public interface Policy {
String name();
List<PolicyNode> nodes();
boolean policyAllowList();
boolean applies(LivingEntity entity);
Effect effect();
int weight();
List<String> worlds();
default void addNode(PolicyNode node) {
nodes().add(node);
}
default boolean matches(LivingEntity entity, Action action, Event event) {
if (!worlds().contains(entity.getWorld().getName())) {
return false;
}
for (PolicyNode node : nodes()) {
if (node.matches(entity, action, event)) {
return true;
}
}
return false;
}
}

View file

@ -1,34 +1,73 @@
package io.github.adrianvic.nemesiseye.policy; package io.github.adrianvic.nemesiseye.policy;
import io.github.adrianvic.nemesiseye.DataShifter;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.Event;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public record PolicyNode(String type, List<Object> values, boolean isWhitelist) { public record PolicyNode(List<Action> actions, List<Object> values) {
public static List<PolicyNode> parseNodes(List<Map<String,Object>> raw, boolean isWhitelist) { public static List<PolicyNode> parseNodes(List<Map<Object,Object>> raw, Effect effect) {
List<PolicyNode> nodes = new ArrayList<>(); List<PolicyNode> nodes = new ArrayList<>();
for (Map<String, Object> m : raw) { for (Map<Object, Object> m : raw) {
for (Map.Entry<String, Object> entry : m.entrySet()) { for (Map.Entry<Object, Object> rawNode : m.entrySet()) {
String type = entry.getKey(); List<Action> nodeActions = new ArrayList<>();
List<Object> values = new ArrayList<>(); List<Object> nodeValues = new ArrayList<>();
Object val = entry.getValue();
if (val instanceof String s) { if (rawNode.getKey() instanceof List<?> rawTypes && rawNode.getValue() instanceof Map<?,?> rawNodeValues) {
values.add(s); for (Object rawType : rawTypes) {
} else if (val instanceof List<?> l) { if (rawType instanceof String rts && !rts.isEmpty() && !(rts == null) && DataShifter.enumOrDefault(Action.class, rts, null) != null) {
values.addAll(l); nodeActions.add(DataShifter.enumOrDefault(Action.class, rts, null));
} else if (val instanceof Map<?,?> map) { }
values.add(map); }
Map<String, Object> semiParsedNodeValue = new HashMap<>();
for (Map.Entry<?,?> rawNodeValueEntries : rawNodeValues.entrySet()) {
if (rawNodeValueEntries.getKey() instanceof String stringKey) {
semiParsedNodeValue.put(stringKey, rawNodeValueEntries.getValue());
}
}
if (semiParsedNodeValue.get("values") instanceof List<?> l) {
for (Object v : l) {
nodeValues.add(v);
}
}
} }
nodes.add(new PolicyNode(type, values, isWhitelist)); if (!nodeActions.isEmpty() && !nodeValues.isEmpty()) {
PolicyNode newNode = new PolicyNode(nodeActions, nodeValues);
nodes.add(newNode);
}
} }
} }
return nodes; return nodes;
} }
public NodeHandler getHandler() { public List<NodeHandler> getHandler() {
return NodeHandlers.get(type); List<NodeHandler> handlers = new ArrayList<>();
for (Action a : actions) {
handlers.add(NodeHandlers.get(a));
}
return handlers;
}
public boolean matches(LivingEntity entity, Action action, Event event) {
if (!actions.contains(action)) {
return false;
}
NodeHandler handler = NodeHandlers.get(action);
if (handler == null) {
return false;
}
boolean result = handler.check(entity, this, action, event);
return result;
} }
} }

View file

@ -0,0 +1,47 @@
package io.github.adrianvic.nemesiseye.policy;
import io.github.adrianvic.nemesiseye.DataShifter;
import io.github.adrianvic.nemesiseye.policy.policies.Core;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public interface PolicyParser {
default Policy parse(Map<?, ?> raw) {
boolean policyAllowList = Boolean.TRUE.equals(raw.get("policyAllowList"));
boolean nodesAllowList = Boolean.TRUE.equals(raw.get("nodesAllowList"));
Effect effect = DataShifter.enumOrDefault(Effect.class, (String) raw.get("effect"), Effect.DENY);
String name = (String) raw.get("name");
Integer weightObj = (Integer) raw.get("weight");
int weight = weightObj != null ? weightObj : 0;
// Worlds
List<String> worlds = new ArrayList<>();
if (raw.get("worlds") instanceof List<?> list) {
for (Object object : list) {
if (object instanceof String result) {
worlds.add(result);
}
}
} else {
worlds.add("world");
}
// Nodes
Object rawNodes = raw.get("nodes");
List<Map<Object, Object>> nodeList = new ArrayList<>();
if (rawNodes instanceof List<?> list) {
for (Object o : list) {
if (o instanceof Map<?, ?> map)
nodeList.add((Map<Object, Object>) map);
}
}
List<PolicyNode> nodes = PolicyNode.parseNodes(nodeList, effect);
return parse(new Core(name, worlds, nodes, nodesAllowList, policyAllowList, effect, weight), raw);
}
Policy parse(Core corePolicy, Map<?, ?> raw);
}

View file

@ -0,0 +1,24 @@
package io.github.adrianvic.nemesiseye.policy;
import io.github.adrianvic.nemesiseye.policy.parser.GlobalPolicyParser;
import io.github.adrianvic.nemesiseye.policy.parser.LocationPolicyParser;
import io.github.adrianvic.nemesiseye.policy.parser.PermissionPolicyParser;
import io.github.adrianvic.nemesiseye.policy.parser.PlayerNamePolicyParser;
import java.util.HashMap;
import java.util.Map;
public class PolicyParsers {
private static final Map<String, PolicyParser> handlers = new HashMap<>();
static {
handlers.put("location", new LocationPolicyParser());
handlers.put("global", new GlobalPolicyParser());
handlers.put("playerName", new PlayerNamePolicyParser());
handlers.put("permission", new PermissionPolicyParser());
}
public static PolicyParser get(String type) {
return handlers.get(type);
}
}

View file

@ -0,0 +1,26 @@
package io.github.adrianvic.nemesiseye.policy.handlers;
import io.github.adrianvic.nemesiseye.DataShifter;
import io.github.adrianvic.nemesiseye.policy.Action;
import io.github.adrianvic.nemesiseye.policy.NodeHandler;
import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.Event;
import org.bukkit.event.block.BlockPlaceEvent;
import java.util.List;
public class BePlaced implements NodeHandler {
@Override
public boolean check(LivingEntity entity, PolicyNode node, Action action, Event event) {
if (event instanceof BlockPlaceEvent bpe) {
String type = bpe.getBlock().getType().toString();
List<String> parsedValue = DataShifter.parseValueToStringList(node.values());
return DataShifter.safeMatches(parsedValue, type);
}
return false;
}
}

View file

@ -0,0 +1,31 @@
package io.github.adrianvic.nemesiseye.policy.handlers;
import io.github.adrianvic.nemesiseye.DataShifter;
import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.policy.Action;
import io.github.adrianvic.nemesiseye.policy.NodeHandler;
import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.Event;
import org.bukkit.inventory.ItemStack;
import java.util.List;
public class Equip implements NodeHandler {
private final Glimmer glim = Nemesis.getInstance().getGlimmer();
@Override
public boolean check(LivingEntity entity, PolicyNode node, Action action, Event event) {
ItemStack item = glim.getEquippedItem(event);
if (!glim.isArmor(item)) {
return false;
}
String type = item.getType().name();
List<String> parsedValue = DataShifter.parseValueToStringList(node.values());
return DataShifter.safeMatches(parsedValue, type);
}
}

View file

@ -0,0 +1,14 @@
package io.github.adrianvic.nemesiseye.policy.handlers;
import io.github.adrianvic.nemesiseye.policy.Action;
import io.github.adrianvic.nemesiseye.policy.NodeHandler;
import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.Event;
public class Glyde implements NodeHandler {
@Override
public boolean check(LivingEntity entity, PolicyNode node, Action action, Event event) {
return true;
}
}

View file

@ -0,0 +1,21 @@
package io.github.adrianvic.nemesiseye.policy.handlers;
import io.github.adrianvic.nemesiseye.DataShifter;
import io.github.adrianvic.nemesiseye.policy.Action;
import io.github.adrianvic.nemesiseye.policy.NodeHandler;
import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.Event;
import java.util.List;
public class Spawn implements NodeHandler {
@Override
public boolean check(LivingEntity entity, PolicyNode node, Action action, Event event) {
String type = entity.getType().name();
List<String> parsedValue = DataShifter.parseValueToStringList(node.values());
return DataShifter.safeMatches(parsedValue, type);
}
}

View file

@ -0,0 +1,34 @@
package io.github.adrianvic.nemesiseye.policy.handlers;
import io.github.adrianvic.nemesiseye.DataShifter;
import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.policy.Action;
import io.github.adrianvic.nemesiseye.policy.NodeHandler;
import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.Event;
import org.bukkit.inventory.ItemStack;
public class UseEnchantment implements NodeHandler {
private final Glimmer glim = Nemesis.getInstance().getGlimmer();
@Override
public boolean check(LivingEntity entity, PolicyNode node, Action action, Event event) {
if (entity instanceof HumanEntity e) {
ItemStack item = glim.getItemInMainHandHumanEntity(e);
if (glim.isAir(item)) return false;
if (!glim.hasItemMeta(item)) return false;
if (!glim.hasAnyEnchantment(item)) return false;
boolean matches = glim.hasEnchantment(item,
DataShifter.parseValueToStringMap(node.values()));
return matches;
}
return false;
}
}

View file

@ -0,0 +1,33 @@
package io.github.adrianvic.nemesiseye.policy.handlers;
import io.github.adrianvic.nemesiseye.DataShifter;
import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.policy.Action;
import io.github.adrianvic.nemesiseye.policy.NodeHandler;
import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.Event;
import java.util.List;
public class UseItem implements NodeHandler {
private final Glimmer glim = Nemesis.getInstance().getGlimmer();
@Override
public boolean check(LivingEntity entity, PolicyNode node, Action action, Event event) {
if (entity instanceof HumanEntity e) {
org.bukkit.inventory.ItemStack item = glim.getItemInMainHandHumanEntity(e);
if (glim.isAir(item)) return false;
String type = item.getType().toString();
List<String> parsedValue = DataShifter.parseValueToStringList(node.values());
return DataShifter.safeMatches(parsedValue, type);
}
return false;
}
}

View file

@ -1,21 +0,0 @@
package io.github.adrianvic.nemesiseye.policy.handlers;
import io.github.adrianvic.nemesiseye.DataShifter;
import io.github.adrianvic.nemesiseye.policy.Action;
import io.github.adrianvic.nemesiseye.policy.NodeHandler;
import io.github.adrianvic.nemesiseye.policy.NodeValueParser;
import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import org.bukkit.entity.HumanEntity;
public class attackWith implements NodeHandler {
@Override
public boolean allows(HumanEntity entity, PolicyNode node, Action action) {
if (action == Action.HIT) {
for (String s : NodeValueParser.parseValueToStringList(node.values())) {
if (DataShifter.safeMatches(s, entity.getInventory().getItemInMainHand().getType().toString())) return false;
}
}
return true;
}
}

View file

@ -1,42 +0,0 @@
package io.github.adrianvic.nemesiseye.policy.handlers;
import io.github.adrianvic.nemesiseye.DataShifter;
import io.github.adrianvic.nemesiseye.policy.Action;
import io.github.adrianvic.nemesiseye.policy.NodeHandler;
import io.github.adrianvic.nemesiseye.policy.NodeValueParser;
import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.ItemStack;
import java.util.Map;
public class useEnchantment implements NodeHandler {
@Override
public boolean allows(HumanEntity entity, PolicyNode node, Action action) {
ItemStack item = entity.getInventory().getItemInMainHand();
if (item.getItemMeta() == null) {
return !node.isWhitelist();
}
Map<Enchantment, Integer> enchants = item.getItemMeta().getEnchants();
if (enchants.isEmpty()) {
return !node.isWhitelist();
}
Map<String, String> valuesmap = NodeValueParser.parseValueToStringMap(node.values());
for (Map.Entry<Enchantment, Integer> e : enchants.entrySet()) {
String enchantment = e.getKey().getKey().getKey();
String level = e.getValue().toString();
for (Map.Entry<String, String> entry : valuesmap.entrySet()) {
if (DataShifter.safeMatches(entry.getKey().trim(), enchantment) && DataShifter.safeMatches(entry.getValue().trim(), level)) {
return false;
}
}
}
return true;
}
}

View file

@ -1,23 +0,0 @@
package io.github.adrianvic.nemesiseye.policy.handlers;
import io.github.adrianvic.nemesiseye.DataShifter;
import io.github.adrianvic.nemesiseye.policy.Action;
import io.github.adrianvic.nemesiseye.policy.NodeHandler;
import io.github.adrianvic.nemesiseye.policy.NodeValueParser;
import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import org.bukkit.entity.HumanEntity;
public class useItem implements NodeHandler {
@Override
public boolean allows(HumanEntity entity, PolicyNode node, Action action) {
String type = entity.getInventory().getItemInMainHand().getType().toString();
for (String s : NodeValueParser.parseValueToStringList(node.values())) {
if (DataShifter.safeMatches(s, type)) {
return false;
}
}
return true;
}
}

View file

@ -0,0 +1,15 @@
package io.github.adrianvic.nemesiseye.policy.parser;
import io.github.adrianvic.nemesiseye.policy.Policy;
import io.github.adrianvic.nemesiseye.policy.PolicyParser;
import io.github.adrianvic.nemesiseye.policy.policies.Core;
import io.github.adrianvic.nemesiseye.policy.policies.GlobalPolicy;
import java.util.Map;
public class GlobalPolicyParser implements PolicyParser {
@Override
public Policy parse(Core corePolicy, Map<?, ?> raw) {
return new GlobalPolicy(corePolicy.name(), corePolicy.worlds(), corePolicy.nodes(), corePolicy.policyAllowList(), corePolicy.effect(), corePolicy.weight());
}
}

View file

@ -0,0 +1,62 @@
package io.github.adrianvic.nemesiseye.policy.parser;
import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.policy.*;
import io.github.adrianvic.nemesiseye.policy.policies.Core;
import io.github.adrianvic.nemesiseye.policy.policies.LocationPolicy;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.Location;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class LocationPolicyParser implements PolicyParser {
private Glimmer glim = Nemesis.getInstance().getGlimmer();
public Policy parse(Core corePolicy, Map<?, ?> raw) {
Object rawLocations = raw.get("locations");
Object rawCoordinates = null;
List<String> worlds = new ArrayList<>();
if (rawLocations instanceof Map<?,?> rawLocationMap) {
rawCoordinates = rawLocationMap.get("coordinates");
if (rawLocationMap.get("worlds") instanceof List<?> rawWorldsList) {
for (Object worldObject : rawWorldsList) {
if (worldObject instanceof String worldString) {
worlds.add(worldString);
}
}
} else {
worlds.add("world");
}
}
List<Glimmer.Box> locations = new ArrayList<>();
// Parsing locations
List<?> groups = rawCoordinates instanceof List ? (List<?>) rawCoordinates : List.of();
// Now iterate over regions
for (Object rObj : groups) {
Map<?, ?> region = (Map<?, ?>) rObj;
Map<?, ?> c1 = (Map<?, ?>) region.get("corner1");
Map<?, ?> c2 = (Map<?, ?>) region.get("corner2");
double x1 = ((Number) c1.get("x")).doubleValue();
double y1 = ((Number) c1.get("y")).doubleValue();
double z1 = ((Number) c1.get("z")).doubleValue();
double x2 = ((Number) c2.get("x")).doubleValue();
double y2 = ((Number) c2.get("y")).doubleValue();
double z2 = ((Number) c2.get("z")).doubleValue();
Location loc1 = new Location(glim.getWorlds().getFirst(), x1, y1, z1);
Location loc2 = new Location(glim.getWorlds().getFirst(), x2, y2, z2);
locations.add(Glimmer.Box.of(loc1, loc2));
}
return new LocationPolicy(corePolicy.name(), corePolicy.worlds(), locations, corePolicy.nodes(), corePolicy.nodeAllowlist(), corePolicy.policyAllowList(), corePolicy.effect(), corePolicy.weight());
}
}

View file

@ -0,0 +1,28 @@
package io.github.adrianvic.nemesiseye.policy.parser;
import io.github.adrianvic.nemesiseye.policy.Policy;
import io.github.adrianvic.nemesiseye.policy.PolicyParser;
import io.github.adrianvic.nemesiseye.policy.policies.Core;
import io.github.adrianvic.nemesiseye.policy.policies.PermissionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class PermissionPolicyParser implements PolicyParser {
@Override
public Policy parse(Core corePolicy, Map<?, ?> raw) {
Object rawPerms = raw.get("permissions");
List<String> permissions = new ArrayList<>();
if (rawPerms instanceof List<?> list) {
for (Object o : list) {
if (o instanceof String s) {
permissions.add(s);
}
}
}
return new PermissionPolicy(corePolicy.name(), corePolicy.worlds(), permissions, corePolicy.nodes(), corePolicy.policyAllowList(), corePolicy.effect(), corePolicy.weight());
}
}

View file

@ -0,0 +1,28 @@
package io.github.adrianvic.nemesiseye.policy.parser;
import io.github.adrianvic.nemesiseye.policy.Policy;
import io.github.adrianvic.nemesiseye.policy.PolicyParser;
import io.github.adrianvic.nemesiseye.policy.policies.Core;
import io.github.adrianvic.nemesiseye.policy.policies.PlayerNamePolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class PlayerNamePolicyParser implements PolicyParser {
@Override
public Policy parse(Core corePolicy, Map<?, ?> raw) {
Object rawNames = raw.get("names");
List<String> names = new ArrayList<>();
if (rawNames instanceof List<?> list) {
for (Object o : list) {
if (o instanceof String s) {
names.add(s);
}
}
}
return new PlayerNamePolicy(corePolicy.name(), corePolicy.worlds(), names, corePolicy.nodes(), corePolicy.effect(), corePolicy.policyAllowList(), corePolicy.weight());
}
}

View file

@ -0,0 +1,16 @@
package io.github.adrianvic.nemesiseye.policy.policies;
import io.github.adrianvic.nemesiseye.policy.Effect;
import io.github.adrianvic.nemesiseye.policy.Policy;
import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.LivingEntity;
import java.util.List;
public record Core(String name, List<String> worlds, List<PolicyNode> nodes, boolean nodeAllowlist, boolean policyAllowList, Effect effect, int weight) implements Policy {
@Override
public boolean applies(LivingEntity entity) {
return false;
}
}

View file

@ -0,0 +1,15 @@
package io.github.adrianvic.nemesiseye.policy.policies;
import io.github.adrianvic.nemesiseye.policy.Effect;
import io.github.adrianvic.nemesiseye.policy.Policy;
import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.LivingEntity;
import java.util.List;
public record GlobalPolicy(String name, List<String> worlds, List<PolicyNode> nodes, boolean policyAllowList, Effect effect, int weight) implements Policy {
public boolean applies(LivingEntity entity) {
return true;
}
}

View file

@ -0,0 +1,22 @@
package io.github.adrianvic.nemesiseye.policy.policies;
import io.github.adrianvic.nemesiseye.policy.Effect;
import io.github.adrianvic.nemesiseye.policy.Policy;
import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.LivingEntity;
import java.util.List;
public record LocationPolicy(String name, List<String> worlds, List<Glimmer.Box> locations, List<PolicyNode> nodes, boolean nodeAllowlist, boolean policyAllowList, Effect effect, int weight) implements Policy {
@Override
public boolean applies(LivingEntity entity) {
for (Glimmer.Box box : locations) {
if (box.contains(entity.getLocation().toVector(), entity.getWorld())) {
return !policyAllowList;
}
}
return policyAllowList;
}
}

View file

@ -0,0 +1,24 @@
package io.github.adrianvic.nemesiseye.policy.policies;
import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.policy.Effect;
import io.github.adrianvic.nemesiseye.policy.Policy;
import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.LivingEntity;
import java.util.List;
public record PermissionPolicy(String name, List<String> worlds, List<String> permissions, List<PolicyNode> nodes, boolean policyAllowList, Effect effect, int weight) implements Policy {
@Override
public boolean applies(LivingEntity entity) {
for (String perm : permissions) {
if (Nemesis.getInstance().getGlimmer().hasPermission(entity, perm)) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,21 @@
package io.github.adrianvic.nemesiseye.policy.policies;
import io.github.adrianvic.nemesiseye.policy.Effect;
import io.github.adrianvic.nemesiseye.policy.Policy;
import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.LivingEntity;
import java.util.List;
public record PlayerNamePolicy(String name, List<String> worlds, List<String> playerName, List<PolicyNode> nodes, Effect effect, boolean policyAllowList, int weight) implements Policy {
@Override
public boolean applies(LivingEntity entity) {
if (playerName.contains(entity.getName())) {
return !policyAllowList();
} else {
return policyAllowList();
}
}
}

View file

@ -0,0 +1,82 @@
package io.github.adrianvic.nemesiseye.reflection;
import io.github.adrianvic.nemesiseye.policy.Policy;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public interface Glimmer {
void onLoad();
List<World> getWorlds();
// Configuration
File loadConfigFile();
List<Policy> loadPoliciesFromFile(File file);
// Items
boolean hasItemMeta(ItemStack item);
boolean hasEnchantment(ItemStack item, Map<String, String> valuesmap);
boolean hasAnyEnchantment(ItemStack itemStack);
boolean isArmor(ItemStack item);
boolean isAir(ItemStack item);
ItemStack getItemInMainHandHumanEntity(HumanEntity entity);
// Players
boolean isGliding(org.bukkit.entity.Player player);
void setGliding(org.bukkit.entity.Player player, boolean gliding);
boolean hasPermission(org.bukkit.command.CommandSender sender, String permission);
// Events
boolean isArmorEquipAttempt(org.bukkit.event.Event event);
ItemStack getEquippedItem(org.bukkit.event.Event event);
// Commands
void sendMessage(CommandSender commandSender, String text);
class Box {
public final double x1, y1, z1, x2, y2, z2;
public final String world;
public Box(String world, double x1, double y1, double z1, double x2, double y2, double z2) {
this.x1 = Math.min(x1, x2);
this.y1 = Math.min(y1, y2);
this.z1 = Math.min(z1, z2);
this.x2 = Math.max(x1, x2);
this.y2 = Math.max(y1, y2);
this.z2 = Math.max(z1, z2);
this.world = world;
}
public boolean contains(double x, double y, double z) {
return x >= x1 && x <= x2
&& y >= y1 && y <= y2
&& z >= z1 && z <= z2;
}
public boolean contains(Vector v) {
return v.getX() >= x1 && v.getX() <= x2
&& v.getY() >= y1 && v.getY() <= y2
&& v.getZ() >= z1 && v.getZ() <= z2;
}
public boolean contains(Vector v, String w) {
return (Objects.equals(w, world)) && contains(v);
}
public boolean contains(Vector v, World w) {
return contains(v, w.getName());
}
public static Box of(Location loc1, Location loc2) { return new Box(loc1.getWorld().getName(), loc1.getX(), loc1.getY(), loc1.getZ(), loc2.getX(), loc2.getY(), loc2.getZ()); }
}
}

View file

@ -0,0 +1,171 @@
package io.github.adrianvic.nemesiseye.reflection;
import io.github.adrianvic.nemesiseye.DataShifter;
import org.bukkit.Bukkit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class VersionMatcher {
public VersionMatcher() {}
private record Entry(String pattern, String classSuffix) {}
public String getVersion(String type, String serverVersion) {
if (type == null || serverVersion == null) {
return "";
}
Map<String, List<Entry>> map = populateMap();
List<Entry> entries = map.get(type.toLowerCase());
if (entries == null || entries.isEmpty()) {
return "";
}
for (Entry e : entries) {
if (DataShifter.safeMatches(e.pattern, serverVersion)) {
return e.pattern + "|" + e.classSuffix;
}
}
List<Entry> sorted = new ArrayList<>(entries);
Collections.sort(sorted, (a, b) -> compareVersions(a.pattern, b.pattern));
Entry oldest = sorted.get(0);
Entry newest = sorted.get(sorted.size() - 1);
int cmpOldest = compareVersions(serverVersion, oldest.pattern);
int cmpNewest = compareVersions(serverVersion, newest.pattern);
if (cmpOldest < 0) {
return oldest.pattern + "|" + oldest.classSuffix;
} else if (cmpNewest > 0) {
return newest.pattern + "|" + newest.classSuffix;
}
// should not happen because we already tried all patterns
return newest.pattern + "|" + newest.classSuffix;
}
private Map<String, List<Entry>> populateMap() {
Map<String, List<Entry>> map = new HashMap<>();
// RELEASE patterns, newest first (order does not matter for matching)
map.put("release", List.of(
new Entry("^1\\.21\\..*$", "r1_21")
));
// BETA patterns
map.put("beta", List.of(
new Entry("^1\\.7\\.3$", "b1_7_3")
));
return map;
}
private int compareVersions(String v1, String v2) {
String clean1 = v1.replaceAll("[^0-9.]", "");
String clean2 = v2.replaceAll("[^0-9.]", "");
String[] a1 = clean1.split("\\.");
String[] a2 = clean2.split("\\.");
int len = Math.max(a1.length, a2.length);
for (int i = 0; i < len; i++) {
int n1 = i < a1.length ? parseInt(a1[i]) : 0;
int n2 = i < a2.length ? parseInt(a2[i]) : 0;
if (n1 != n2) {
return n1 - n2;
}
}
return 0;
}
private int parseInt(String s) {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
return 0;
}
}
public Glimmer loadGlim() {
String rawVersion = null;
try {
rawVersion = Bukkit.getMinecraftVersion();
} catch (NoSuchMethodError ignored) {}
if (rawVersion == null || rawVersion.isEmpty()) {
String v = Bukkit.getVersion(); // e.g. "git-Bukkit-0.0.0-1060-... (MC: 1.7.3)"
int start = v.lastIndexOf("MC: ");
if (start != -1) {
rawVersion = v.substring(start + 4, v.length() - 1);
} else {
rawVersion = "unknown";
}
}
String matchInfo = getVersion("release", rawVersion);
if (matchInfo.isEmpty()) {
matchInfo = getVersion("beta", rawVersion);
}
if (matchInfo.isEmpty()) {
// Fallback to b1.7.3 if everything fails, or throw error?
// User said it supports b1.7.3 and 1.21.x.
Glimmer fallback = tryInstantiate("io.github.adrianvic.nemesiseye.impl.b1_7_3");
if (fallback != null) return fallback;
throw new IllegalStateException("No suitable implementation found for version " + rawVersion);
}
String[] partsInfo = matchInfo.split("\\|");
String classSuffix = partsInfo[1];
Glimmer glimmer = tryInstantiate("io.github.adrianvic.nemesiseye.impl." + classSuffix);
if (glimmer != null) return glimmer;
// Backward search for older implementations
String[] versionParts = rawVersion.split("\\.");
int major = parseInt(versionParts[0]);
int minor = versionParts.length > 1 ? parseInt(versionParts[1]) : 0;
int patch = versionParts.length > 2 ? parseInt(versionParts[2]) : 0;
while (major >= 0) {
while (minor >= 0) {
while (patch >= 0) {
String className = String.format("io.github.adrianvic.nemesiseye.impl.r%d_%d_%d", major, minor, patch);
glimmer = tryInstantiate(className);
if (glimmer != null) return glimmer;
className = String.format("io.github.adrianvic.nemesiseye.impl.r%d_%d", major, minor);
glimmer = tryInstantiate(className);
if (glimmer != null) return glimmer;
patch--;
}
minor--;
patch = 20; // Search up to .20 patch of previous minor
}
major--;
minor = 30; // Search up to .30 minor of previous major
}
throw new IllegalStateException("No suitable implementation found for version " + rawVersion);
}
private Glimmer tryInstantiate(String className) {
try {
Class<?> clazz = Class.forName(className, true, getClass().getClassLoader());
if (!Glimmer.class.isAssignableFrom(clazz)) {
return null;
}
return (Glimmer) clazz.getDeclaredConstructor().newInstance();
} catch (ReflectiveOperationException ignored) {
return null;
}
}
}

View file

@ -1,10 +1,19 @@
name: "Eye-of-Nemesis" name: "Eye-of-Nemesis"
version: '1.0.1-SNAPSHOT' version: ${version}
main: io.github.adrianvic.nemesiseye.Nemesis main: io.github.adrianvic.nemesiseye.Nemesis
api-version: '1.21' api-version: '1.13'
author: 'Adrian Victor' author: 'Adrian Victor'
website: "https://github.io/adrianvic/NemesisEye" website: "https://github.io/adrianvic/NemesisEye"
description: "Change what players can do based in custom criteria." description: "Change what players can do based in custom criteria."
commands: commands:
eye: eye:
usage: "/eye <option> (help for all available options)" usage: "/eye <option> (help for all available options)"
permissions:
nemesiseye.reload:
default: op
nemesiseye.policy.list.all:
default: op
nemesiseye.policy.list.self:
default: true
nemesiseye.help:
default: true

View file

@ -1,11 +1,66 @@
# __ _______ __ ___________________ _______
# / / \ _ \ \ \ \_ _____/\_____ \ \ \
# / / / /_\ \ \ \ | __)_ / | \ / | \
# \ \ \ \_/ \ / / | \/ | \/ | \
# \_\ \_____ / /_/ /_______ /\_______ /\____|__ /
# \/ \/ \/ \/
# EYE OF NEMESIS - Example config file.
# Documentation in our wiki: https://github.com/adrianvic/NemesisEye
Policies: Policies:
Location: - name: "Bedrock-allow-admins"
# NO SPACES type: "permission"
- name: "Beta-1.7.3-items-only" worlds: [world]
# Will deny anything that's not allowed by the nodes if set to true effect: ALLOW
allowList: true weight: 3
nodes: permissions:
- useItem: - "server.usebedrock"
nodes:
- [BREAK, PLACE, HIT, INTERACT]:
values:
- BEDROCK
- name: "Bedrock-deny"
type: "global"
worlds: [world]
effect: DENY
weight: 2
nodes:
- [BREAK, PLACE, HIT, INTERACT]:
values:
- BEDROCK
- name: "Block-non-beta-items" # No spaces here or else commands which need the name as argument will not work
type: "location" # global | location | permission | playerName
effect: DENY # DENY | ALLOW (overrides deny) | ALLOWONLY (allow only if met)
worlds: [world]
weight: 0 # Greater weight overrides lower
policyAllowList: false # Inverts the policy validation logic, for example a location policy will affect all players NOT inside it's location
locations:
worlds: [world]
coordinates:
- corner1: { x: 2100, y: 256, z: 1400 }
corner2: { x: 1000, y: -64, z: 2200 }
nodes:
- [INTERACT, BREAK, HIT, PLACE]:
values:
- '.*'
- [USE_ENCHANTMENT]:
values:
- ".*": ".*"
- name: "Allow-beta-items"
type: "location"
effect: ALLOW
weight: 1
worlds: [world]
locations:
coordinates:
- corner1: { x: 2100, y: 256, z: 1400 }
corner2: { x: 1000, y: -64, z: 2200 }
nodes:
- [INTERACT, BREAK, HIT, PLACE]:
values:
- AIR - AIR
- STONE - STONE
- COBBLESTONE - COBBLESTONE
@ -22,7 +77,7 @@ Policies:
- COBWEB - COBWEB
- PISTON - PISTON
- STICKY_PISTON - STICKY_PISTON
- GRASS - GRASS_BLOCK
- DISPENSER - DISPENSER
- NOTE_BLOCK - NOTE_BLOCK
- SANDSTONE - SANDSTONE
@ -34,7 +89,7 @@ Policies:
- POPPY - POPPY
- DANDELION - DANDELION
- "^(RED|BROWN)_MUSHROOM$" - "^(RED|BROWN)_MUSHROOM$"
- "^(OAK|COBBLESTONE)_SLAB$" - "^(OAK|COBBLESTONE|SMOOTH_STONE)_SLAB$"
- BRICK_BLOCK - BRICK_BLOCK
- TNT - TNT
- BOOKSHELF - BOOKSHELF
@ -114,9 +169,3 @@ Policies:
- MUSIC_DISK_13 - MUSIC_DISK_13
- DIRT - DIRT
- BREAD - BREAD
- useEnchantment:
"gibberish": 999999
locations:
-
- corner1: { x: 2100, y: 256, z: 1400 }
corner2: { x: 1000, y: -64, z: 2200 }

View file

@ -0,0 +1,45 @@
package io.github.adrianvic.nemesiseye.impl;
import io.github.adrianvic.nemesiseye.Events;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntitySpawnEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
public class EventListener implements Listener {
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
Events.onBlockBreak(event);
}
@EventHandler
public void onInteractionEvent(PlayerInteractEvent event) {
Events.onInteractionEvent(event);
}
@EventHandler
public void onEntityDamageByEntityEvent(EntityDamageByEntityEvent event) {
Events.onEntityDamageByEntityEvent(event);
}
@EventHandler
public void onBlockPlaceEvent(BlockPlaceEvent event) {
Events.onBlockPlaceEvent(event);
}
@EventHandler
public void onPlayerMoveEvent(PlayerMoveEvent event) { Events.onPlayerMoveEvent(event); }
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onInventoryClickEvent(InventoryClickEvent event) { Events.onInventoryClickEvent(event); }
@EventHandler
public void onCreatureSpawnEvent(CreatureSpawnEvent event) { Events.onCreatureSpawnEvent(event); }
}

View file

@ -0,0 +1,29 @@
package io.github.adrianvic.nemesiseye.impl.commands;
import io.github.adrianvic.nemesiseye.commands.EyeCore;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class Eye implements CommandExecutor, TabCompleter {
private final EyeCore core;
public Eye() {
core = new EyeCore();
}
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String @NotNull [] strings) {
return core.onCommand(commandSender, command, s, strings);
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String @NotNull [] strings) {
return core.onTabComplete(commandSender, command, s, strings);
}
}

View file

@ -0,0 +1,201 @@
package io.github.adrianvic.nemesiseye.impl;
import io.github.adrianvic.nemesiseye.DataShifter;
import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.impl.commands.Eye;
import io.github.adrianvic.nemesiseye.policy.Policy;
import io.github.adrianvic.nemesiseye.policy.PolicyParsers;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.PluginManager;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@SuppressWarnings("unused")
public class r1_21 implements Glimmer {
@Override
public File loadConfigFile() {
File file = new File(Nemesis.getInstance().getDataFolder(), "settings.yml");
if (!file.exists())
Nemesis.getInstance().saveResource("settings.yml", false);
return file;
}
@Override
public List<Policy> loadPoliciesFromFile(File file) {
YamlConfiguration config = new YamlConfiguration();
config.options().parseComments(true);
try {
config.load(file);
} catch (Exception e) {
e.printStackTrace();
}
List<Map<?, ?>> rawPolicies = config.getMapList("Policies");
List<Policy> allPolicies = new ArrayList<>();
for (Map<?, ?> policyMap : rawPolicies) {
if (policyMap.get("type") != null && policyMap.get("type") instanceof String type) {
allPolicies.add(PolicyParsers.get(type).parse(policyMap));
}
}
return allPolicies;
}
@Override
public void onLoad() {
PluginManager pm = Nemesis.getInstance().getPluginManager();
Nemesis.getInstance().getCommand("eye").setExecutor(new Eye());
pm.registerEvents(new EventListener(), Nemesis.getInstance());
}
@Override
public ItemStack getItemInMainHandHumanEntity(HumanEntity entity) {
return entity.getInventory().getItemInMainHand();
}
@Override
public boolean isAir(ItemStack item) {
return item == null || item.getType().isAir();
}
@Override
public boolean isGliding(org.bukkit.entity.Player player) {
return player.isGliding();
}
@Override
public void setGliding(org.bukkit.entity.Player player, boolean gliding) {
player.setGliding(gliding);
}
@Override
public boolean hasPermission(org.bukkit.command.CommandSender sender, String permission) {
return sender.hasPermission(permission);
}
@Override
public boolean isArmorEquipAttempt(org.bukkit.event.Event event) {
if (!(event instanceof org.bukkit.event.inventory.InventoryClickEvent e)) {
return false;
}
if (e.getSlotType() == org.bukkit.event.inventory.InventoryType.SlotType.ARMOR) {
return true;
}
if (e.isShiftClick()) {
return isArmor(e.getCurrentItem());
}
if (e.getClick() == org.bukkit.event.inventory.ClickType.NUMBER_KEY
&& e.getSlotType() == org.bukkit.event.inventory.InventoryType.SlotType.ARMOR
&& e.getWhoClicked() instanceof org.bukkit.entity.Player player) {
return isArmor(
player.getInventory().getItem(e.getHotbarButton())
);
}
return false;
}
@Override
public ItemStack getEquippedItem(org.bukkit.event.Event event) {
if (event instanceof org.bukkit.event.inventory.InventoryClickEvent e) {
org.bukkit.event.inventory.InventoryType.SlotType slotType = e.getSlotType();
if (e.getClick() == org.bukkit.event.inventory.ClickType.NUMBER_KEY // hotbar key swap
&& slotType == org.bukkit.event.inventory.InventoryType.SlotType.ARMOR
&& e.getWhoClicked() instanceof org.bukkit.entity.Player player) {
return player.getInventory().getItem(e.getHotbarButton());
}
if (e.isShiftClick()) {
ItemStack current = e.getCurrentItem();
if (isArmor(current)) return current;
}
// regular click
if (slotType == org.bukkit.event.inventory.InventoryType.SlotType.ARMOR) {
ItemStack cursor = e.getCursor();
if (isArmor(cursor)) return cursor;
}
}
// Try Paper's PlayerArmorChangeEvent via reflection or just check if class exists
try {
if (event instanceof com.destroystokyo.paper.event.player.PlayerArmorChangeEvent e) {
return e.getNewItem();
}
} catch (NoClassDefFoundError | Exception ignored) {}
return null;
}
@Override
public void sendMessage(CommandSender commandSender, String text) {
commandSender.sendMessage(text);
}
@Override
public boolean hasItemMeta(ItemStack item) {
return item.getItemMeta() != null;
}
@Override
public List<World> getWorlds() {
return Bukkit.getWorlds();
}
@Override
public boolean hasEnchantment(ItemStack item, Map<String, String> valuesmap) {
Map<Enchantment, Integer> enchantments = item.getEnchantments();
for (Map.Entry<Enchantment, Integer> ench : enchantments.entrySet()) {
String enchKey = ench.getKey().getKey().getKey();
String enchLevel = ench.getValue().toString();
for (Map.Entry<String, String> rule : valuesmap.entrySet()) {
if (
DataShifter.safeMatches(rule.getKey(), enchKey) &&
DataShifter.safeMatches(rule.getValue(), enchLevel)
) {
return true;
}
}
}
return false;
}
public boolean hasAnyEnchantment(ItemStack item) {
return !(item.getItemMeta().getEnchants().isEmpty());
}
@Override
public boolean isArmor(ItemStack item) {
if (item == null || item.getType().isAir()) {
return false;
}
String name = item.getType().name();
return name.endsWith("_HELMET")
|| name.endsWith("_CHESTPLATE")
|| name.endsWith("_LEGGINGS")
|| name.endsWith("_BOOTS")
|| item.getType() == org.bukkit.Material.ELYTRA;
}
}

View file

@ -0,0 +1,43 @@
package io.github.adrianvic.nemesiseye;
import io.github.adrianvic.nemesiseye.policy.Action;
import io.github.adrianvic.nemesiseye.policy.Effect;
import io.github.adrianvic.nemesiseye.policy.Policy;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
public class ValidatorTest {
private Glimmer mockGlim;
private Config mockConfig;
@BeforeEach
void setUp() {
mockGlim = mock(Glimmer.class);
}
@Test
void testValidatorCanDeny() {
Player player = mock(Player.class);
Event event = mock(Event.class);
Policy policy = mock(Policy.class);
when(policy.applies(player)).thenReturn(true);
when(policy.matches(player, Action.BREAK, event)).thenReturn(true);
when(policy.effect()).thenReturn(Effect.DENY);
// We need to handle the static Config.getInstance()
// This is tricky without refactoring or Mockito-inline
// For now, let's just demonstrate the concept if Validator was more testable
}
}

View file

@ -0,0 +1,64 @@
package io.github.adrianvic.nemesiseye.impl;
import be.seeseemelk.mockbukkit.MockBukkit;
import be.seeseemelk.mockbukkit.ServerMock;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
public class ImplementationContractTest {
private ServerMock server;
@BeforeEach
void setUp() {
server = MockBukkit.mock();
}
@AfterEach
void tearDown() {
MockBukkit.unmock();
}
static Stream<Glimmer> implementations() {
return Stream.of(
new b1_7_3(),
new r1_21()
);
}
@ParameterizedTest
@MethodSource("implementations")
void testIsArmor(Glimmer glim) {
// Helmets
assertTrue(glim.isArmor(new ItemStack(Material.IRON_HELMET)));
assertTrue(glim.isArmor(new ItemStack(Material.DIAMOND_HELMET)));
// Chestplates
assertTrue(glim.isArmor(new ItemStack(Material.GOLDEN_CHESTPLATE)));
// Non-armor
assertFalse(glim.isArmor(new ItemStack(Material.STICK)));
assertFalse(glim.isArmor(new ItemStack(Material.DIRT)));
// Null/Air
assertFalse(glim.isArmor(null));
assertFalse(glim.isArmor(new ItemStack(Material.AIR)));
}
@ParameterizedTest
@MethodSource("implementations")
void testIsAir(Glimmer glim) {
assertTrue(glim.isAir(null));
assertTrue(glim.isAir(new ItemStack(Material.AIR)));
assertFalse(glim.isAir(new ItemStack(Material.STONE)));
}
}