Compare commits

..

43 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
68 changed files with 1942 additions and 522 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

3
.gitignore vendored
View file

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

View file

@ -1,12 +1,24 @@
<img alt="eye_of_nemesis_social_cover" src="https://github.com/user-attachments/assets/a24bc92f-4dc2-4594-93c9-d056130f9695" />
[![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
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. 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.
## Warnings
- Even though running `/eye` will tell you to run `/eye help` to list all available commands, this is not implemented yet, however all commands are available as tab-complete of `/eye`.
- This plugin is in a very early stage.
## Motivations ## 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. 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 ## 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. 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

@ -4,7 +4,7 @@ plugins {
} }
group = "io.github.adrianvic" group = "io.github.adrianvic"
version = "1.0.3-SNAPSHOT" version = System.getenv("NEMESIS_VERSION_NAME") ?: "unknown"
repositories { repositories {
mavenCentral() mavenCentral()
@ -24,6 +24,14 @@ val mcVersions = listOf(
/* CREATE SOURCE SET PER VERSION */ /* CREATE SOURCE SET PER VERSION */
/* ----------------------------------------- */ /* ----------------------------------------- */
tasks.withType<ProcessResources> {
inputs.property("version", project.version)
filesMatching("plugin.yml") {
expand("version" to project.version)
}
}
mcVersions.forEach { ver -> mcVersions.forEach { ver ->
val ss = sourceSets.create(ver) { val ss = sourceSets.create(ver) {
java.srcDir("src/$ver/java") java.srcDir("src/$ver/java")
@ -51,9 +59,24 @@ mcVersions.forEach { ver ->
/* ----------------------------------------- */ /* ----------------------------------------- */
dependencies { dependencies {
add("compileOnly", "io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT") add("compileOnly", "io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT")
add("r1_21CompileOnly", "io.papermc.paper:paper-api:1.21.10-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")) 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()
} }
/* ----------------------------------------- */ /* ----------------------------------------- */
@ -69,13 +92,37 @@ mcVersions.forEach { ver ->
manifest { manifest {
attributes( attributes(
"Nemesis-Impl-Version" to ver "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 SETTINGS */
/* ----------------------------------------- */ /* ----------------------------------------- */
@ -90,4 +137,18 @@ tasks.withType<JavaCompile> {
tasks.runServer { tasks.runServer {
minecraftVersion("1.21") 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,13 +0,0 @@
package io.github.adrianvic.nemesiseye.impl;
import io.github.adrianvic.nemesiseye.Events;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityListener;
public class EntityEventListener extends EntityListener {
public void onEntityDamage(EntityDamageByEntityEvent event) {
// Events.onEntityDamageByEntityEvent(event);
event.setCancelled(true);
}
}

View file

@ -2,11 +2,15 @@ package io.github.adrianvic.nemesiseye.impl;
import io.github.adrianvic.nemesiseye.Nemesis; import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.impl.commands.Eye; 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.Policy;
import io.github.adrianvic.nemesiseye.policy.PolicyParsers; import io.github.adrianvic.nemesiseye.policy.PolicyParsers;
import io.github.adrianvic.nemesiseye.policy.policies.LocationPolicy;
import io.github.adrianvic.nemesiseye.reflection.Glimmer; import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.HumanEntity; import org.bukkit.entity.HumanEntity;
import org.bukkit.event.Event; import org.bukkit.event.Event;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -18,6 +22,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@SuppressWarnings("unused")
public class b1_7_3 implements Glimmer { public class b1_7_3 implements Glimmer {
JavaPlugin plugin; JavaPlugin plugin;
PluginManager pm; PluginManager pm;
@ -47,30 +52,13 @@ public class b1_7_3 implements Glimmer {
} }
List<Policy> allPolicies = new ArrayList<>(); List<Policy> allPolicies = new ArrayList<>();
for (Map<?, ?> map : result) { for (Map<?, ?> policyMap : result) {
for (Map.Entry<?, ?> entry : map.entrySet()) { if (policyMap.get("type") != null && policyMap.get("type") instanceof String type) {
if (entry.getKey() instanceof String k && entry.getValue() instanceof List<?> v) { allPolicies.add(PolicyParsers.get(type).parse(policyMap));
List<Policy> parsed = PolicyParsers.get(k).parse(v);
allPolicies.addAll(parsed);
}
} }
} }
return allPolicies;
}
@Override return allPolicies;
public List<Policy> getApplyingPoliciesForEntity(HumanEntity entity, List<Policy> policies) {
List<Policy> result = new ArrayList<>();
for (Policy p : policies) {
if (p instanceof LocationPolicy lp) {
for (List<Box> boxList : lp.locations()) {
for (Box b : boxList) {
if (b.contains(entity.getLocation().toVector())) result.add(p);
}
}
}
}
return result;
} }
@Override @Override
@ -88,6 +76,46 @@ public class b1_7_3 implements Glimmer {
return entity.getItemInHand(); 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 @Override
public boolean hasItemMeta(ItemStack item) { public boolean hasItemMeta(ItemStack item) {
return false; return false;
@ -107,4 +135,18 @@ public class b1_7_3 implements Glimmer {
public boolean hasAnyEnchantment(ItemStack itemStack) { public boolean hasAnyEnchantment(ItemStack itemStack) {
return false; 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

@ -6,7 +6,7 @@ import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
public class Eye implements CommandExecutor { public class Eye implements CommandExecutor {
private EyeCore core; private final EyeCore core;
public Eye() { public Eye() {
core = new EyeCore(); core = new EyeCore();

View file

@ -1,8 +1,9 @@
package io.github.adrianvic.nemesiseye.impl; package io.github.adrianvic.nemesiseye.impl.events;
import io.github.adrianvic.nemesiseye.Events; import io.github.adrianvic.nemesiseye.Events;
import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockListener; import org.bukkit.event.block.BlockListener;
import org.bukkit.event.block.BlockPlaceEvent;
public class BlockEventListener extends BlockListener { public class BlockEventListener extends BlockListener {
@Override @Override
@ -10,4 +11,8 @@ public class BlockEventListener extends BlockListener {
Events.onBlockBreak(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

@ -1,8 +1,6 @@
package io.github.adrianvic.nemesiseye.impl; package io.github.adrianvic.nemesiseye.impl.events;
import io.github.adrianvic.nemesiseye.Events; import io.github.adrianvic.nemesiseye.Events;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockListener;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerListener; import org.bukkit.event.player.PlayerListener;

View file

@ -1,8 +0,0 @@
name: "Eye-of-Nemesis"
version: '1.0.3-SNAPSHOT'
main: io.github.adrianvic.nemesiseye.Nemesis
author: 'Adrian Victor'
description: "Change what players can do based in custom criteria."
commands:
eye:
description: "Run /eye help to see all available commands."

View file

@ -1 +0,0 @@
impl.version=b1_7_3

View file

@ -1,26 +1,30 @@
package io.github.adrianvic.nemesiseye; package io.github.adrianvic.nemesiseye;
import io.github.adrianvic.nemesiseye.policy.Policy; import io.github.adrianvic.nemesiseye.policy.Policy;
import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import io.github.adrianvic.nemesiseye.policy.policies.GlobalPolicy;
import io.github.adrianvic.nemesiseye.reflection.Glimmer; import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
public class Config { public class Config {
private static Config instance = new Config(); private static Config instance = new Config();
private Glimmer glim = Nemesis.getInstance().getGlimmer(); private final Glimmer glim = Nemesis.getInstance().getGlimmer();
private File file; private File file;
private List<Policy> policies = new ArrayList<>(); private List<Policy> policies = new ArrayList<>();
private Config() {} private Config() {}
public void load() { public void load() {
List<Policy> newPolicies = glim.loadPoliciesFromFile(glim.loadConfigFile()); policies = glim.loadPoliciesFromFile(glim.loadConfigFile());
policies = newPolicies; policies.sort(Comparator.comparingInt(Policy::weight).reversed());
} }
// TODO: Implement config saving
//
// public void save() { // public void save() {
// try { // try {
// config.save(file); // config.save(file);

View file

@ -13,6 +13,16 @@ public class DataShifter {
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) { public static List<String> parseValueToStringList(List<Object> values) {
List<String> result = new ArrayList<>(); List<String> result = new ArrayList<>();
for (Object o : values) { for (Object o : values) {
@ -40,14 +50,14 @@ public class DataShifter {
return out; return out;
} }
public static List<Map<?, ?>> parseValueToListOfMaps(List<?> values) { public static <T extends Enum<T>> T enumOrDefault(Class<T> type, String string, T def) {
List<Map<?, ?>> result = new ArrayList<>(); try {
return Enum.valueOf(type, string);
for (Object o : values) { } catch (IllegalArgumentException e) {
if (o instanceof Map<?, ?> raw) { return def;
result.add(raw); } catch (Exception e) {
} e.printStackTrace();
return def;
} }
return result;
} }
} }

View file

@ -1,25 +1,98 @@
package io.github.adrianvic.nemesiseye; 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.HumanEntity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.block.BlockBreakEvent; 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.EntityDamageByEntityEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.inventory.ItemStack;
import java.util.List;
public class Events { public class Events {
private static Glimmer g() {
return Nemesis.getInstance().getGlimmer();
}
public static void onBlockBreak(BlockBreakEvent event) { public static void onBlockBreak(BlockBreakEvent event) {
event.setCancelled(!Validator.canBreak(event.getPlayer())); event.setCancelled(
!Validator.can(
event.getPlayer(),
List.of(Action.BREAK, Action.USE_ENCHANTMENT),
event
)
);
} }
public static void onInteractionEvent(PlayerInteractEvent event) { public static void onInteractionEvent(PlayerInteractEvent event) {
if (event.getItem() != null) { ItemStack item = event.getItem();
event.setCancelled(!Validator.canInteract(event.getPlayer()));
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) { public static void onEntityDamageByEntityEvent(EntityDamageByEntityEvent event) {
if (event.getDamager() instanceof Player) { if (event.getDamager() instanceof Player player) {
event.setCancelled(!Validator.canHit((HumanEntity) event.getDamager())); 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,14 +1,10 @@
package io.github.adrianvic.nemesiseye; package io.github.adrianvic.nemesiseye;
import io.github.adrianvic.nemesiseye.commands.EyeCore;
import io.github.adrianvic.nemesiseye.reflection.Glimmer; import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import io.github.adrianvic.nemesiseye.reflection.VersionMatcher;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public final class Nemesis extends JavaPlugin { public final class Nemesis extends JavaPlugin {
private Glimmer glim; private Glimmer glim;
private static final String VERSION_PROP = "impl.version"; private static final String VERSION_PROP = "impl.version";
@ -17,44 +13,11 @@ public final class Nemesis extends JavaPlugin {
@Override @Override
public void onEnable() { public void onEnable() {
instance = this; instance = this;
glim = loadGlim(); glim = new VersionMatcher().loadGlim();
glim.onLoad(); glim.onLoad();
Config.getInstance().load(); Config.getInstance().load();
} }
private String readImplVersion() {
Properties props = new Properties();
try (InputStream is = getClass().getClassLoader()
.getResourceAsStream("version.properties")) {
if (is == null) {
throw new IllegalStateException("version.properties not found on classpath.");
}
props.load(is);
} catch (IOException e) {
throw new IllegalStateException("Failed to load version.properties", e);
}
String version = props.getProperty(VERSION_PROP);
if (version == null || version.isBlank()) {
throw new IllegalStateException(VERSION_PROP + " property missing in version.properties.");
}
return version.trim();
}
private Glimmer loadGlim() {
String implVersion = readImplVersion();
String className = "io.github.adrianvic.nemesiseye.impl." + implVersion;
try {
Class<?> clazz = Class.forName(className, true, getClass().getClassLoader());
if (!Glimmer.class.isAssignableFrom(clazz)) {
throw new IllegalStateException(className + " does not implement Glimmer.");
}
return (Glimmer) clazz.getDeclaredConstructor().newInstance();
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("Failed to instantiate " + className, e);
}
}
@Override @Override
public void onDisable() { public void onDisable() {
} }
@ -62,4 +25,4 @@ public final class Nemesis extends JavaPlugin {
public static Nemesis getInstance() { return instance; } public static Nemesis getInstance() { return instance; }
public Glimmer getGlimmer() { return glim; } public Glimmer getGlimmer() { return glim; }
public PluginManager getPluginManager() { return this.getServer().getPluginManager(); } public PluginManager getPluginManager() { return this.getServer().getPluginManager(); }
} }

View file

@ -2,52 +2,54 @@ 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.Policy; 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.HumanEntity;
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 {
private final static Glimmer glim = Nemesis.getInstance().getGlimmer(); public static boolean can(HumanEntity entity, List<Action> actions, Event event) {
for (Action action : actions) {
public static boolean canInteract(HumanEntity entity) { if (!can(entity, action, event)) {
return checkAgainstEntity(entity, Action.INTERACT); return false;
} }
public static boolean canBreak(HumanEntity entity) {
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) {
return node.getHandler().allows(entity, node, action); boolean restricted = false;
} boolean allowed = false;
public static List<PolicyNode> getNodesForPolicies(List<Policy> policies) { for (Policy policy : getPoliciesForEntity(entity)) {
List<PolicyNode> nodes = new ArrayList<>(); boolean matches = policy.matches(entity, action, event);
for (Policy p : policies) {
nodes.addAll(p.nodes()); switch (policy.effect()) {
case ALLOW:
if (matches) return true;
break;
case DENY:
if (matches) return false;
break;
}
} }
return nodes;
return true;
} }
public static List<Policy> getPoliciesForEntity(HumanEntity entity) {
public static List<Policy> getPoliciesForEntity(LivingEntity entity) {
List<Policy> ps = Config.getInstance().getPolicies(); List<Policy> ps = Config.getInstance().getPolicies();
return glim.getApplyingPoliciesForEntity(entity, ps); 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

@ -2,52 +2,56 @@ package io.github.adrianvic.nemesiseye.commands;
import io.github.adrianvic.nemesiseye.Nemesis; import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.commands.sub.*; 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.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import java.util.*; import java.util.*;
public class EyeCore { public class EyeCore {
public final Map<String, Subcommand> subs = new HashMap<>(); private final Glimmer glim = Nemesis.getInstance().getGlimmer();
public EyeCore() { public EyeCore() {}
register(new Reload());
register(new ListPolicies());
register(new PolicyInfo());
register(new CurrentPolicies());
}
private void register(Subcommand sub) {
subs.put(sub.name(), sub);
}
public boolean onCommand(CommandSender commandSender, Command command, String s, String [] strings) { public boolean onCommand(CommandSender commandSender, Command command, String s, String [] strings) {
if (strings.length == 0) { if (strings.length == 0) {
commandSender.sendMessage(""" glim.sendMessage(commandSender, """
Eye of Nemesis version %s %sEye of Nemesis%s version %s%s%s
Usage: '/eye <command>' Usage: '/eye <command>'
Use '/eye help' for a list of available commands Use '/eye help' for a list of available commands
""".formatted(Nemesis.getInstance().getDescription().getVersion())); """.formatted(ChatColor.AQUA, ChatColor.WHITE, ChatColor.GRAY, Nemesis.getInstance().getDescription().getVersion(), ChatColor.WHITE));
} else { } else {
Subcommand sub = subs.get(strings[0].toLowerCase()); Subcommand sub = Commands.get(strings[0].toLowerCase());
if (sub == null) { if (sub == null) {
commandSender.sendMessage("Unknown command, try '/eye help' to list available commands."); commandSender.sendMessage("Unknown command, try '/eye help' to list available commands.");
return true; return true;
} }
return sub.execute(commandSender, Arrays.copyOfRange(strings, 1, strings.length)); 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; return false;
} }
public List<String> onTabComplete(CommandSender commandSender, Command command, String s, String [] strings) { public List<String> onTabComplete(CommandSender commandSender, Command command, String s, String [] strings) {
if (strings.length == 1) { if (strings.length == 1) {
return new ArrayList<>(subs.keySet()); 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 = subs.get(strings[0].toLowerCase()); Subcommand sub = Commands.get(strings[0].toLowerCase());
if (sub != null) { if (sub != null && glim.hasPermission(commandSender, sub.permission())) {
return sub.onTabComplete(commandSender, Arrays.copyOfRange(strings, 1, strings.length)); return sub.onTabComplete(commandSender, Arrays.copyOfRange(strings, 1, strings.length));
} }
return List.of(); return List.of();
} }
public Map<String, Subcommand> getSubs() { return subs; };
} }

View file

@ -9,11 +9,6 @@ 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<Policy> policies = Validator.getPoliciesForEntity((HumanEntity) commandSender); List<Policy> policies = Validator.getPoliciesForEntity((HumanEntity) commandSender);
@ -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

@ -8,12 +8,6 @@ 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<>();
@ -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.Nemesis;
import io.github.adrianvic.nemesiseye.policy.Policy; 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,22 +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<Policy> policies = Config.getInstance().getPolicies(); List<Policy> policies = Config.getInstance().getPolicies();
for (Policy policy : policies) { for (Policy policy : policies) {
if (policy.name().equals(strings[0])) { if (policy.name().equals(strings[0])) {
commandSender.sendMessage(String.format(""" glim.sendMessage(commandSender, String.format("""
Showing info for policy "%s": Showing info for policy %s%s%s:
Type: %s Type: %s
Nodes: %s Nodes: %s
%s """, ChatColor.GREEN, policy.name(), ChatColor.WHITE, policy.getClass().getTypeName(), policy.nodes().size()));
""", policy.name(), "location", policy.nodes().size(), policy.allowlist() ? "Is allowlist" : "Is blacklist"));
} }
} }
return true; return true;
@ -38,4 +36,19 @@ public class PolicyInfo implements Subcommand {
} }
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,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,13 +1,35 @@
package io.github.adrianvic.nemesiseye.policy; 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; import java.util.List;
public interface Policy { public interface Policy {
String name(); String name();
List<PolicyNode> nodes(); List<PolicyNode> nodes();
boolean allowlist(); boolean policyAllowList();
boolean applies(LivingEntity entity);
Effect effect();
int weight();
List<String> worlds();
default PolicyParser getParser() { default void addNode(PolicyNode node) {
return PolicyParsers.get(""); 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

@ -1,7 +1,47 @@
package io.github.adrianvic.nemesiseye.policy; 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.List;
import java.util.Map;
public interface PolicyParser { public interface PolicyParser {
List<Policy> parse(List<?> raw); 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

@ -1,6 +1,9 @@
package io.github.adrianvic.nemesiseye.policy; 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.LocationPolicyParser;
import io.github.adrianvic.nemesiseye.policy.parser.PermissionPolicyParser;
import io.github.adrianvic.nemesiseye.policy.parser.PlayerNamePolicyParser;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -9,7 +12,10 @@ public class PolicyParsers {
private static final Map<String, PolicyParser> handlers = new HashMap<>(); private static final Map<String, PolicyParser> handlers = new HashMap<>();
static { static {
handlers.put("Location", new LocationPolicyParser()); 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) { public static PolicyParser get(String 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,25 +0,0 @@
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;
public class attackWith implements NodeHandler {
private final static Glimmer glim = Nemesis.getInstance().getGlimmer();
@Override
public boolean allows(HumanEntity entity, PolicyNode node, Action action) {
if (action == Action.HIT) {
for (String s : DataShifter.parseValueToStringList(node.values())) {
boolean matches = DataShifter.safeMatches(s, glim.getItemInMainHandHumanEntity(entity).getType().toString());
if (matches) return node.isWhitelist();
}
}
return !node.isWhitelist();
}
}

View file

@ -1,27 +0,0 @@
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.inventory.ItemStack;
public class useEnchantment implements NodeHandler {
private Glimmer glim = Nemesis.getInstance().getGlimmer();
@Override
public boolean allows(HumanEntity entity, PolicyNode node, Action action) {
ItemStack item = glim.getItemInMainHandHumanEntity(entity);
if (!glim.hasItemMeta(item)) return true;
if (!glim.hasAnyEnchantment(item)) return true;
boolean matches = glim.hasEnchantment(item,
DataShifter.parseValueToStringMap(node.values()));
return matches ? node.isWhitelist() : !node.isWhitelist();
}
}

View file

@ -1,25 +0,0 @@
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;
public class useItem implements NodeHandler {
private Glimmer glim = Nemesis.getInstance().getGlimmer();
@Override
public boolean allows(HumanEntity entity, PolicyNode node, Action action) {
String type = glim.getItemInMainHandHumanEntity(entity).getType().toString();
for (String s : DataShifter.parseValueToStringList(node.values())) {
boolean matches = DataShifter.safeMatches(s, type);
if (matches) return node.isWhitelist();
}
return !node.isWhitelist();
}
}

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

@ -1,8 +1,8 @@
package io.github.adrianvic.nemesiseye.policy.parser; package io.github.adrianvic.nemesiseye.policy.parser;
import io.github.adrianvic.nemesiseye.DataShifter;
import io.github.adrianvic.nemesiseye.Nemesis; import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.policy.*; 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.policy.policies.LocationPolicy;
import io.github.adrianvic.nemesiseye.reflection.Glimmer; import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.Location; import org.bukkit.Location;
@ -14,57 +14,49 @@ import java.util.Map;
public class LocationPolicyParser implements PolicyParser { public class LocationPolicyParser implements PolicyParser {
private Glimmer glim = Nemesis.getInstance().getGlimmer(); private Glimmer glim = Nemesis.getInstance().getGlimmer();
public List<Policy> parse(List<?> raw) { public Policy parse(Core corePolicy, Map<?, ?> raw) {
List<Policy> out = new ArrayList<>(raw.size()); Object rawLocations = raw.get("locations");
List<Map<?, ?>> parsedRawMap = DataShifter.parseValueToListOfMaps(raw); Object rawCoordinates = null;
List<String> worlds = new ArrayList<>();
if (rawLocations instanceof Map<?,?> rawLocationMap) {
rawCoordinates = rawLocationMap.get("coordinates");
for (Map<?, ?> m : parsedRawMap) { if (rawLocationMap.get("worlds") instanceof List<?> rawWorldsList) {
String name = (String) m.get("name"); for (Object worldObject : rawWorldsList) {
boolean allowlist = Boolean.TRUE.equals(m.get("allowList")); if (worldObject instanceof String worldString) {
worlds.add(worldString);
// 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);
} }
} else {
worlds.add("world");
} }
List<PolicyNode> nodes = PolicyNode.parseNodes(nodeList, allowlist);
// Parsing locations
List<ArrayList<Glimmer.Box>> locations = new ArrayList<>();
Object rawLocations = m.get("locations");
List<?> groups = rawLocations instanceof List ? (List<?>) rawLocations : List.of();
ArrayList<Glimmer.Box> boxes = new ArrayList<>(groups.size());
// 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);
boxes.add(Glimmer.Box.of(loc1, loc2));
}
locations.add(boxes);
out.add(new LocationPolicy(name, locations, nodes, allowlist));
} }
return out;
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

@ -1,10 +1,22 @@
package io.github.adrianvic.nemesiseye.policy.policies; 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.Policy;
import io.github.adrianvic.nemesiseye.policy.PolicyNode; import io.github.adrianvic.nemesiseye.policy.PolicyNode;
import io.github.adrianvic.nemesiseye.reflection.Glimmer; import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.LivingEntity;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public record LocationPolicy(String name, List<ArrayList<Glimmer.Box>> locations, List<PolicyNode> nodes, boolean allowlist) implements Policy {} 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

@ -1,8 +1,24 @@
package io.github.adrianvic.nemesiseye.policy.policies; 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 io.github.adrianvic.nemesiseye.policy.PolicyNode;
import org.bukkit.permissions.Permission; import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.LivingEntity;
import java.util.ArrayList; import java.util.List;
public record PermissionPolicy(String name, ArrayList<Permission> permissions, PolicyNode nodes, boolean allowlist) {} 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

@ -1,7 +1,21 @@
package io.github.adrianvic.nemesiseye.policy.policies; 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.policy.PolicyNode;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.LivingEntity;
import java.util.ArrayList; import java.util.List;
public record PlayerNamePolicy(String name, ArrayList<String> playerName, PolicyNode nodes, boolean allowlist) {} 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

@ -3,6 +3,7 @@ package io.github.adrianvic.nemesiseye.reflection;
import io.github.adrianvic.nemesiseye.policy.Policy; import io.github.adrianvic.nemesiseye.policy.Policy;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.HumanEntity; import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
@ -10,28 +11,49 @@ import org.bukkit.util.Vector;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
public interface Glimmer { public interface Glimmer {
void onLoad();
List<World> getWorlds();
// Configuration
File loadConfigFile(); File loadConfigFile();
List<Policy> loadPoliciesFromFile(File file); List<Policy> loadPoliciesFromFile(File file);
List<Policy> getApplyingPoliciesForEntity(HumanEntity entity, List<Policy> policies);
void onLoad(); // Items
ItemStack getItemInMainHandHumanEntity(HumanEntity entity);
boolean hasItemMeta(ItemStack item); boolean hasItemMeta(ItemStack item);
List<World> getWorlds();
boolean hasEnchantment(ItemStack item, Map<String, String> valuesmap); boolean hasEnchantment(ItemStack item, Map<String, String> valuesmap);
boolean hasAnyEnchantment(ItemStack itemStack); 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 { class Box {
public final double x1, y1, z1, x2, y2, z2; public final double x1, y1, z1, x2, y2, z2;
public final String world;
public Box(double x1, double y1, double z1, double x2, double y2, double z2) { public Box(String world, double x1, double y1, double z1, double x2, double y2, double z2) {
this.x1 = Math.min(x1, x2); this.x1 = Math.min(x1, x2);
this.y1 = Math.min(y1, y2); this.y1 = Math.min(y1, y2);
this.z1 = Math.min(z1, z2); this.z1 = Math.min(z1, z2);
this.x2 = Math.max(x1, x2); this.x2 = Math.max(x1, x2);
this.y2 = Math.max(y1, y2); this.y2 = Math.max(y1, y2);
this.z2 = Math.max(z1, z2); this.z2 = Math.max(z1, z2);
this.world = world;
} }
public boolean contains(double x, double y, double z) { public boolean contains(double x, double y, double z) {
@ -39,14 +61,22 @@ public interface Glimmer {
&& y >= y1 && y <= y2 && y >= y1 && y <= y2
&& z >= z1 && z <= z2; && z >= z1 && z <= z2;
} }
public boolean contains(Vector v) { public boolean contains(Vector v) {
return v.getX() >= x1 && v.getX() <= x2 return v.getX() >= x1 && v.getX() <= x2
&& v.getY() >= y1 && v.getY() <= y2 && v.getY() >= y1 && v.getY() <= y2
&& v.getZ() >= z1 && v.getZ() <= z2; && v.getZ() >= z1 && v.getZ() <= z2;
} }
public boolean contains(Vector v, String w) {
return (Objects.equals(w, world)) && contains(v);
}
public static Box of(Location loc1, Location loc2) { return new Box(loc1.getX(), loc1.getY(), loc1.getZ(), loc2.getX(), loc2.getY(), loc2.getZ()); } 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

@ -0,0 +1,19 @@
name: "Eye-of-Nemesis"
version: ${version}
main: io.github.adrianvic.nemesiseye.Nemesis
api-version: '1.13'
author: 'Adrian Victor'
website: "https://github.io/adrianvic/NemesisEye"
description: "Change what players can do based in custom criteria."
commands:
eye:
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,121 +1,171 @@
# __ _______ __ ___________________ _______
# / / \ _ \ \ \ \_ _____/\_____ \ \ \
# / / / /_\ \ \ \ | __)_ / | \ / | \
# \ \ \ \_/ \ / / | \/ | \/ | \
# \_\ \_____ / /_/ /_______ /\_______ /\____|__ /
# \/ \/ \/ \/
# 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"
- AIR nodes:
- STONE - [BREAK, PLACE, HIT, INTERACT]:
- COBBLESTONE values:
- "^(OAK|SPRUCE|BIRCH)_(LOG|SAPLING|PLANKS|LEAVES)$" - BEDROCK
- "^(DIAMOND|GOLD|IRON|COAL|LAPIS|REDSTONE)_ORE$"
- "^(DIAMOND|GOLD|IRON|LAPIS)_BLOCK$" - name: "Bedrock-deny"
- GRAVEL type: "global"
- BEDROCK worlds: [world]
- SAND effect: DENY
- SPONGE weight: 2
- WET_SPONGE nodes:
- GLASS - [BREAK, PLACE, HIT, INTERACT]:
- LAPIS_LAZULI values:
- COBWEB - BEDROCK
- PISTON
- STICKY_PISTON - name: "Block-non-beta-items" # No spaces here or else commands which need the name as argument will not work
- GRASS type: "location" # global | location | permission | playerName
- DISPENSER effect: DENY # DENY | ALLOW (overrides deny) | ALLOWONLY (allow only if met)
- NOTE_BLOCK worlds: [world]
- SANDSTONE weight: 0 # Greater weight overrides lower
- RED_BED policyAllowList: false # Inverts the policy validation logic, for example a location policy will affect all players NOT inside it's location
- "^(POWERED|DETECTOR)_RAIL$" locations:
- RAIL worlds: [world]
- SHORT_GRASS coordinates:
- "^(WHITE|BLACK|GREEN|YELLOW|PINK|PURPLE|CYAN|BLUE|RED|LIME|BROWN|LIGHT_GRAY|GRAY)_(WOOL|DYE)$" - corner1: { x: 2100, y: 256, z: 1400 }
- POPPY corner2: { x: 1000, y: -64, z: 2200 }
- DANDELION nodes:
- "^(RED|BROWN)_MUSHROOM$" - [INTERACT, BREAK, HIT, PLACE]:
- "^(OAK|COBBLESTONE)_SLAB$" values:
- BRICK_BLOCK - '.*'
- TNT - [USE_ENCHANTMENT]:
- BOOKSHELF values:
- OBSIDIAN - ".*": ".*"
- MOSSY_COBBLESTONE
- TORCH - name: "Allow-beta-items"
- SPAWNER type: "location"
- REDSTONE effect: ALLOW
- CHEST weight: 1
- CRAFTING_TABLE worlds: [world]
- FARMLAND locations:
- FURNACE coordinates:
- SIGN - corner1: { x: 2100, y: 256, z: 1400 }
- LADDER corner2: { x: 1000, y: -64, z: 2200 }
- "^(COBBLESTONE|OAK)_STAIRS$" nodes:
- LEVER - [INTERACT, BREAK, HIT, PLACE]:
- "^(OAK|STONE)_PRESSURE_PLATE$" values:
- "^(OAK|IRON)_DOOR$" - AIR
- BLUE_ICE - STONE
- REDSTONE_TORCH - COBBLESTONE
- STONE_BUTTON - "^(OAK|SPRUCE|BIRCH)_(LOG|SAPLING|PLANKS|LEAVES)$"
- SNOW - "^(DIAMOND|GOLD|IRON|COAL|LAPIS|REDSTONE)_ORE$"
- SNOW_BLOCK - "^(DIAMOND|GOLD|IRON|LAPIS)_BLOCK$"
- CLAY - GRAVEL
- SUGAR_CANE - BEDROCK
- JUKEBOX - SAND
- OAK_FENCE - SPONGE
- PUMPKIN - WET_SPONGE
- NETHERRACK - GLASS
- SOUL_SAND - LAPIS_LAZULI
- GLOWSTONE - COBWEB
- JACK_O_LANTERN - PISTON
- CAKE - STICKY_PISTON
- REPEATER - GRASS_BLOCK
- OAK_TRAPDOOR - DISPENSER
- "^(IRON|STONE|DIAMOND|WOODEN|GOLDEN)_(SHOVEL|AXE|PICKAXE|SWORD|HOE)$" - NOTE_BLOCK
- "^(IRON|LEATHER|DIAMOND|GOLDEN)_(HELMET|CHESTPLATE|LEGGINGS|BOOTS)$" - SANDSTONE
- STICK - RED_BED
- BOWL - "^(POWERED|DETECTOR)_RAIL$"
- MUSHROOM_STEW - RAIL
- FEATHER - SHORT_GRASS
- STRING - "^(WHITE|BLACK|GREEN|YELLOW|PINK|PURPLE|CYAN|BLUE|RED|LIME|BROWN|LIGHT_GRAY|GRAY)_(WOOL|DYE)$"
- GUNPOWDER - POPPY
- WHEAT_SEEDS - DANDELION
- WHEAT - "^(RED|BROWN)_MUSHROOM$"
- FLINT - "^(OAK|COBBLESTONE|SMOOTH_STONE)_SLAB$"
- FLINT_AND_STEEL - BRICK_BLOCK
- PORKCHOP - TNT
- "^(COOKED|RAW)_(PORKCHOP|FISH)$" - BOOKSHELF
- PAINTING - OBSIDIAN
- GOLDEN_APPLE - MOSSY_COBBLESTONE
- BUCKET - TORCH
- "^(LAVA|MILK|WATER)_BUCKET$" - SPAWNER
- MINECART - REDSTONE
- SADDLE - CHEST
- SNOWBALL - CRAFTING_TABLE
- OAK_BOAT - FARMLAND
- LEATHER - FURNACE
- "^(FURNACE|CHEST)_MINECART$" - SIGN
- EGG - LADDER
- BOOK - "^(COBBLESTONE|OAK)_STAIRS$"
- PAPER - LEVER
- BRICK - "^(OAK|STONE)_PRESSURE_PLATE$"
- SLIME_BALL - "^(OAK|IRON)_DOOR$"
- COMPASS - BLUE_ICE
- FISHING_ROD - REDSTONE_TORCH
- CLOCK - STONE_BUTTON
- GLOWSTONE_DUST - SNOW
- INK_SACw - SNOW_BLOCK
- BONE_MEAL - CLAY
- SUGAR - SUGAR_CANE
- COOKIE - JUKEBOX
- MAP - OAK_FENCE
- FILLED_MAP - PUMPKIN
- SHEARS - NETHERRACK
- MUSIC_DISK_CAT - SOUL_SAND
- MUSIC_DISK_13 - GLOWSTONE
- DIRT - JACK_O_LANTERN
- BREAD - CAKE
- useEnchantment: - REPEATER
"theresnoenchantmentwiththisname": "3" - OAK_TRAPDOOR
locations: - "^(IRON|STONE|DIAMOND|WOODEN|GOLDEN)_(SHOVEL|AXE|PICKAXE|SWORD|HOE)$"
- corner1: { x: 2100, y: 256, z: 1400 } - "^(IRON|LEATHER|DIAMOND|GOLDEN)_(HELMET|CHESTPLATE|LEGGINGS|BOOTS)$"
corner2: { x: 1000, y: -64, z: 2200 } - STICK
- BOWL
- MUSHROOM_STEW
- FEATHER
- STRING
- GUNPOWDER
- WHEAT_SEEDS
- WHEAT
- FLINT
- FLINT_AND_STEEL
- PORKCHOP
- "^(COOKED|RAW)_(PORKCHOP|FISH)$"
- PAINTING
- GOLDEN_APPLE
- BUCKET
- "^(LAVA|MILK|WATER)_BUCKET$"
- MINECART
- SADDLE
- SNOWBALL
- OAK_BOAT
- LEATHER
- "^(FURNACE|CHEST)_MINECART$"
- EGG
- BOOK
- PAPER
- BRICK
- SLIME_BALL
- COMPASS
- FISHING_ROD
- CLOCK
- GLOWSTONE_DUST
- INK_SAC
- BONE_MEAL
- SUGAR
- COOKIE
- MAP
- FILLED_MAP
- SHEARS
- MUSIC_DISK_CAT
- MUSIC_DISK_13
- DIRT
- BREAD

View file

@ -2,10 +2,16 @@ package io.github.adrianvic.nemesiseye.impl;
import io.github.adrianvic.nemesiseye.Events; import io.github.adrianvic.nemesiseye.Events;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent; 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.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntitySpawnEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
public class EventListener implements Listener { public class EventListener implements Listener {
@EventHandler @EventHandler
@ -22,4 +28,18 @@ public class EventListener implements Listener {
public void onEntityDamageByEntityEvent(EntityDamageByEntityEvent event) { public void onEntityDamageByEntityEvent(EntityDamageByEntityEvent event) {
Events.onEntityDamageByEntityEvent(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

@ -1,7 +1,6 @@
package io.github.adrianvic.nemesiseye.impl.commands; package io.github.adrianvic.nemesiseye.impl.commands;
import io.github.adrianvic.nemesiseye.commands.EyeCore; import io.github.adrianvic.nemesiseye.commands.EyeCore;
import io.github.adrianvic.nemesiseye.commands.sub.*;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -12,7 +11,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
public class Eye implements CommandExecutor, TabCompleter { public class Eye implements CommandExecutor, TabCompleter {
private EyeCore core; private final EyeCore core;
public Eye() { public Eye() {
core = new EyeCore(); core = new EyeCore();

View file

@ -5,10 +5,10 @@ import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.impl.commands.Eye; import io.github.adrianvic.nemesiseye.impl.commands.Eye;
import io.github.adrianvic.nemesiseye.policy.Policy; import io.github.adrianvic.nemesiseye.policy.Policy;
import io.github.adrianvic.nemesiseye.policy.PolicyParsers; import io.github.adrianvic.nemesiseye.policy.PolicyParsers;
import io.github.adrianvic.nemesiseye.policy.policies.LocationPolicy;
import io.github.adrianvic.nemesiseye.reflection.Glimmer; import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.HumanEntity; import org.bukkit.entity.HumanEntity;
@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@SuppressWarnings("unused")
public class r1_21 implements Glimmer { public class r1_21 implements Glimmer {
@Override @Override
public File loadConfigFile() { public File loadConfigFile() {
@ -44,32 +45,14 @@ public class r1_21 implements Glimmer {
List<Map<?, ?>> rawPolicies = config.getMapList("Policies"); List<Map<?, ?>> rawPolicies = config.getMapList("Policies");
List<Policy> allPolicies = new ArrayList<>(); List<Policy> allPolicies = new ArrayList<>();
for (Map<?, ?> map : rawPolicies) {
for (Map.Entry<?, ?> entry : map.entrySet()) {
if (entry.getKey() instanceof String k && entry.getValue() instanceof List<?> v) {
List<Policy> parsed = PolicyParsers.get(k).parse(v);
allPolicies.addAll(parsed);
}
}
}
return allPolicies;
}
@Override for (Map<?, ?> policyMap : rawPolicies) {
public List<Policy> getApplyingPoliciesForEntity(HumanEntity entity, List<Policy> policies) { if (policyMap.get("type") != null && policyMap.get("type") instanceof String type) {
List<Policy> applyingLPS = new ArrayList<>(); allPolicies.add(PolicyParsers.get(type).parse(policyMap));
for (Policy p : policies) {
if (p instanceof LocationPolicy lp) {
for (ArrayList<Box> boxes : lp.locations()) {
for (Box box : boxes) {
if (box.contains(entity.getLocation().toVector())) {
applyingLPS.add(lp);
}
}
}
} }
} }
return applyingLPS;
return allPolicies;
} }
@Override @Override
@ -85,11 +68,91 @@ public class r1_21 implements Glimmer {
} }
@Override @Override
public boolean hasItemMeta(ItemStack item) { public boolean isAir(ItemStack item) {
if (item.getItemMeta() == null) { 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; return false;
} }
return true;
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 @Override
@ -99,10 +162,17 @@ public class r1_21 implements Glimmer {
@Override @Override
public boolean hasEnchantment(ItemStack item, Map<String, String> valuesmap) { public boolean hasEnchantment(ItemStack item, Map<String, String> valuesmap) {
Map<Enchantment, Integer> enchantmentList = item.getEnchantments(); Map<Enchantment, Integer> enchantments = item.getEnchantments();
for (Map.Entry<Enchantment, Integer> enchantmentEntry : enchantmentList.entrySet()) {
for (Map.Entry<String, String> valueEntry : valuesmap.entrySet()) { for (Map.Entry<Enchantment, Integer> ench : enchantments.entrySet()) {
if (enchantmentEntry.getKey().getKey().getKey().equals(valueEntry.getKey()) && enchantmentEntry.getValue().toString().equals(valueEntry.getValue())) { 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 true;
} }
} }
@ -113,4 +183,19 @@ public class r1_21 implements Glimmer {
public boolean hasAnyEnchantment(ItemStack item) { public boolean hasAnyEnchantment(ItemStack item) {
return !(item.getItemMeta().getEnchants().isEmpty()); 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

@ -1,10 +0,0 @@
name: "Eye-of-Nemesis"
version: '1.0.3-SNAPSHOT'
main: io.github.adrianvic.nemesiseye.Nemesis
api-version: '1.21'
author: 'Adrian Victor'
website: "https://github.io/adrianvic/NemesisEye"
description: "Change what players can do based in custom criteria."
commands:
eye:
usage: "/eye <option> (help for all available options)"

View file

@ -1 +0,0 @@
impl.version=r1_21

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)));
}
}