Compare commits

..

No commits in common. "main" and "1.0.3-SNAPSHOT" have entirely different histories.

68 changed files with 522 additions and 1942 deletions

View file

@ -1,43 +0,0 @@
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

@ -1,40 +0,0 @@
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
View file

@ -1,15 +0,0 @@
# 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']

View file

@ -1,44 +0,0 @@
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

View file

@ -1,41 +0,0 @@
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,5 +2,4 @@
build/
out/
.idea/
libs/
run/
libs/

View file

@ -1,24 +1,12 @@
<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 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
I made this plugin as an effort to preserve a village from my private server. Originally from beta 1.7.3 Betalands server, then transferred to RetroMC, and then finally we downloaded the chunks to merge into our server, I was afraid it would not have the same feeling after all the updates, so I had the idea to make a plugin that can block the newer features.
## Game version and loaders
Since version 1.0.3-SNAPSHOT, Eye of Nemesis has _reflection_, a technique that allows me to target multiple versions of the game while sharing the codebase across versions.
Currently, we support the following Minecraft versions/loaders:
- **PaperMC** `1.21, 1.21.1, 1.21.2, 1.21.3, 1.21.4, 1.21.5, 1.21.6, 1.21.7, 1.21.8, 1.21.9, 1.21.10`
- **Bukkit** `b1.7.3 (CB1060)`
## Performance
This plugin is not scalable as it is and will end up running unoptimized checks when your players do things with policies in effect, I made it for a server with a few friends. I'll look forward into writing more performant code after all my other priorities are implemented.

View file

@ -4,7 +4,7 @@ plugins {
}
group = "io.github.adrianvic"
version = System.getenv("NEMESIS_VERSION_NAME") ?: "unknown"
version = "1.0.3-SNAPSHOT"
repositories {
mavenCentral()
@ -24,14 +24,6 @@ val mcVersions = listOf(
/* CREATE SOURCE SET PER VERSION */
/* ----------------------------------------- */
tasks.withType<ProcessResources> {
inputs.property("version", project.version)
filesMatching("plugin.yml") {
expand("version" to project.version)
}
}
mcVersions.forEach { ver ->
val ss = sourceSets.create(ver) {
java.srcDir("src/$ver/java")
@ -59,24 +51,9 @@ mcVersions.forEach { ver ->
/* ----------------------------------------- */
dependencies {
add("compileOnly", "io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT")
add("r1_21CompileOnly", "io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT")
add("compileOnly", "io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT")
add("r1_21CompileOnly", "io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT")
add("b1_7_3CompileOnly", files("libs/craftbukkit-1060.jar"))
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
testImplementation("org.mockito:mockito-core:5.5.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testImplementation("io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT")
testImplementation("com.github.seeseemelk:MockBukkit-v1.21:3.102.0")
// Allow tests to see the versioned implementations
mcVersions.forEach { ver ->
testImplementation(sourceSets[ver].output)
}
}
tasks.test {
useJUnitPlatform()
}
/* ----------------------------------------- */
@ -92,37 +69,13 @@ mcVersions.forEach { ver ->
manifest {
attributes(
"Nemesis-Impl-Version" to ver,
"Nemesis-Environment" to (System.getenv("NEMESIS_BUILD_CHANNEL") ?: "dev")
"Nemesis-Impl-Version" to ver
)
}
}
}
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 */
/* ----------------------------------------- */
@ -137,18 +90,4 @@ tasks.withType<JavaCompile> {
tasks.runServer {
minecraftVersion("1.21")
downloadPlugins {
modrinth("viaversion", "5.7.0")
modrinth("luckperms", "v5.5.17-bukkit")
}
}
tasks.register("runServerClean") {
doFirst {
exec {
commandLine("rm", "run/plugins/Eye-of-Nemesis/settings.yml")
}
}
dependsOn("runServer")
}

View file

@ -1,34 +0,0 @@
# 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.

View file

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

View file

@ -0,0 +1,13 @@
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

@ -1,6 +1,8 @@
package io.github.adrianvic.nemesiseye.impl.events;
package io.github.adrianvic.nemesiseye.impl;
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.PlayerListener;

View file

@ -2,15 +2,11 @@ package io.github.adrianvic.nemesiseye.impl;
import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.impl.commands.Eye;
import io.github.adrianvic.nemesiseye.impl.events.BlockEventListener;
import io.github.adrianvic.nemesiseye.impl.events.EntityEventListener;
import io.github.adrianvic.nemesiseye.impl.events.PlayerEventListener;
import io.github.adrianvic.nemesiseye.policy.Policy;
import io.github.adrianvic.nemesiseye.policy.PolicyParsers;
import io.github.adrianvic.nemesiseye.policy.policies.LocationPolicy;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.Event;
import org.bukkit.inventory.ItemStack;
@ -22,7 +18,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@SuppressWarnings("unused")
public class b1_7_3 implements Glimmer {
JavaPlugin plugin;
PluginManager pm;
@ -52,15 +47,32 @@ public class b1_7_3 implements Glimmer {
}
List<Policy> allPolicies = new ArrayList<>();
for (Map<?, ?> policyMap : result) {
if (policyMap.get("type") != null && policyMap.get("type") instanceof String type) {
allPolicies.add(PolicyParsers.get(type).parse(policyMap));
for (Map<?, ?> map : result) {
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
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
public void onLoad() {
plugin = Nemesis.getInstance();
@ -76,46 +88,6 @@ public class b1_7_3 implements Glimmer {
return entity.getItemInHand();
}
@Override
public boolean isAir(ItemStack item) {
return item == null || item.getType() == Material.AIR;
}
@Override
public boolean isGliding(org.bukkit.entity.Player player) {
return false;
}
@Override
public void setGliding(org.bukkit.entity.Player player, boolean gliding) {
}
@Override
public boolean hasPermission(org.bukkit.command.CommandSender sender, String permission) {
if (sender instanceof org.bukkit.entity.Player p) {
return p.isOp();
}
return true; // Console always has permission
}
@Override
public boolean isArmorEquipAttempt(org.bukkit.event.Event event) {
return false;
}
@Override
public ItemStack getEquippedItem(org.bukkit.event.Event event) {
return null;
}
@Override
public void sendMessage(CommandSender commandSender, String text) {
String[] lines = text.split("\\r?\\n");
for (String line : lines) {
commandSender.sendMessage(line);
}
}
@Override
public boolean hasItemMeta(ItemStack item) {
return false;
@ -135,18 +107,4 @@ public class b1_7_3 implements Glimmer {
public boolean hasAnyEnchantment(ItemStack itemStack) {
return false;
}
@Override
public boolean isArmor(ItemStack item) {
if (item == null || item.getType() == Material.AIR) {
return false;
}
String name = item.getType().name();
return name.endsWith("_HELMET")
|| name.endsWith("_CHESTPLATE")
|| name.endsWith("_LEGGINGS")
|| name.endsWith("_BOOTS");
}
}

View file

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

View file

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

View file

@ -0,0 +1,8 @@
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

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

View file

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

View file

@ -13,16 +13,6 @@ public class DataShifter {
return pattern.matcher(against).matches();
}
public static boolean safeMatches(List<String> expressions, String against) {
for (String s : expressions) {
if (DataShifter.safeMatches(s, against)) {
return true;
}
}
return false;
}
public static List<String> parseValueToStringList(List<Object> values) {
List<String> result = new ArrayList<>();
for (Object o : values) {
@ -50,14 +40,14 @@ public class DataShifter {
return out;
}
public static <T extends Enum<T>> T enumOrDefault(Class<T> type, String string, T def) {
try {
return Enum.valueOf(type, string);
} catch (IllegalArgumentException e) {
return def;
} catch (Exception e) {
e.printStackTrace();
return def;
public static List<Map<?, ?>> parseValueToListOfMaps(List<?> values) {
List<Map<?, ?>> result = new ArrayList<>();
for (Object o : values) {
if (o instanceof Map<?, ?> raw) {
result.add(raw);
}
}
return result;
}
}

View file

@ -1,98 +1,25 @@
package io.github.adrianvic.nemesiseye;
import io.github.adrianvic.nemesiseye.policy.Action;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.inventory.ItemStack;
import java.util.List;
public class Events {
private static Glimmer g() {
return Nemesis.getInstance().getGlimmer();
}
public static void onBlockBreak(BlockBreakEvent event) {
event.setCancelled(
!Validator.can(
event.getPlayer(),
List.of(Action.BREAK, Action.USE_ENCHANTMENT),
event
)
);
event.setCancelled(!Validator.canBreak(event.getPlayer()));
}
public static void onInteractionEvent(PlayerInteractEvent event) {
ItemStack item = event.getItem();
if (g().isAir(item)) {
return;
if (event.getItem() != null) {
event.setCancelled(!Validator.canInteract(event.getPlayer()));
}
// Right-click armor equipping
if (g().isArmor(item)
&& !Validator.can(event.getPlayer(), Action.EQUIP, event)) {
event.setCancelled(true);
return;
}
// Normal item interaction
event.setCancelled(
!Validator.can(event.getPlayer(), Action.INTERACT, event)
);
}
public static void onBlockPlaceEvent(BlockPlaceEvent event) {
event.setCancelled(
!Validator.can(event.getPlayer(), Action.PLACE, event)
);
}
public static void onEntityDamageByEntityEvent(EntityDamageByEntityEvent event) {
if (event.getDamager() instanceof Player player) {
event.setCancelled(
!Validator.can(
player,
List.of(Action.HIT, Action.USE_ENCHANTMENT),
event
)
);
if (event.getDamager() instanceof Player) {
event.setCancelled(!Validator.canHit((HumanEntity) event.getDamager()));
}
}
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,10 +1,14 @@
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.VersionMatcher;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public final class Nemesis extends JavaPlugin {
private Glimmer glim;
private static final String VERSION_PROP = "impl.version";
@ -13,11 +17,44 @@ public final class Nemesis extends JavaPlugin {
@Override
public void onEnable() {
instance = this;
glim = new VersionMatcher().loadGlim();
glim = loadGlim();
glim.onLoad();
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
public void onDisable() {
}
@ -25,4 +62,4 @@ public final class Nemesis extends JavaPlugin {
public static Nemesis getInstance() { return instance; }
public Glimmer getGlimmer() { return glim; }
public PluginManager getPluginManager() { return this.getServer().getPluginManager(); }
}
}

View file

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

View file

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

View file

@ -9,6 +9,11 @@ import java.util.ArrayList;
import java.util.List;
public class CurrentPolicies implements Subcommand {
@Override
public String name() {
return "currentpolicies";
}
@Override
public boolean execute(CommandSender commandSender, String[] args) {
List<Policy> policies = Validator.getPoliciesForEntity((HumanEntity) commandSender);
@ -29,19 +34,4 @@ public class CurrentPolicies implements Subcommand {
public List<String> onTabComplete(CommandSender sender, String[] args) {
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

@ -1,54 +0,0 @@
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,6 +8,12 @@ import java.util.ArrayList;
import java.util.List;
public class ListPolicies implements Subcommand {
@Override
public String name() {
return "listpolicies";
}
@Override
public boolean execute(CommandSender commandSender, String[] args) {
List<String> rstr = new ArrayList<>();
@ -22,19 +28,4 @@ public class ListPolicies implements Subcommand {
public List<String> onTabComplete(CommandSender sender, String[] args) {
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,9 +1,7 @@
package io.github.adrianvic.nemesiseye.commands.sub;
import io.github.adrianvic.nemesiseye.Config;
import io.github.adrianvic.nemesiseye.Nemesis;
import io.github.adrianvic.nemesiseye.policy.Policy;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
@ -11,18 +9,22 @@ import java.util.ArrayList;
import java.util.List;
public class PolicyInfo implements Subcommand {
private final Glimmer glim = Nemesis.getInstance().getGlimmer();
@Override
public String name() {
return "policyinfo";
}
@Override
public boolean execute(CommandSender commandSender, String[] strings) {
List<Policy> policies = Config.getInstance().getPolicies();
for (Policy policy : policies) {
if (policy.name().equals(strings[0])) {
glim.sendMessage(commandSender, String.format("""
Showing info for policy %s%s%s:
commandSender.sendMessage(String.format("""
Showing info for policy "%s":
Type: %s
Nodes: %s
""", ChatColor.GREEN, policy.name(), ChatColor.WHITE, policy.getClass().getTypeName(), policy.nodes().size()));
%s
""", policy.name(), "location", policy.nodes().size(), policy.allowlist() ? "Is allowlist" : "Is blacklist"));
}
}
return true;
@ -36,19 +38,4 @@ public class PolicyInfo implements Subcommand {
}
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;
import io.github.adrianvic.nemesiseye.Config;
import io.github.adrianvic.nemesiseye.commands.Commands;
import org.bukkit.command.CommandSender;
import java.util.List;
public class Reload implements Subcommand {
@Override
public boolean execute(CommandSender commandSender, String [] strings) {
Config.getInstance().load();
commandSender.sendMessage("Reloading...");
Config.getInstance().load();
commandSender.sendMessage("Reloading...");
return true;
}
@ -20,17 +20,7 @@ public class Reload implements Subcommand {
}
@Override
public String permission() {
return "nemsiseye.reload";
}
@Override
public String description() {
return "Reloads the plugin configuration file.";
}
@Override
public String usage() {
return "";
public String name() {
return "reload";
}
}

View file

@ -1,19 +1,12 @@
package io.github.adrianvic.nemesiseye.commands.sub;
import io.github.adrianvic.nemesiseye.Nemesis;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;
import java.util.List;
public interface Subcommand {
String description();
String usage();
String name();
@SuppressWarnings("SameReturnValue")
boolean execute(CommandSender commandSender, 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,10 +4,6 @@ public enum Action {
INTERACT,
BREAK,
HIT,
// TODO CRAFT,
EQUIP,
PLACE,
USE_ENCHANTMENT,
GLYDE,
SPAWN
CRAFT,
EQUIP
}

View file

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

View file

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

View file

@ -1,25 +1,22 @@
package io.github.adrianvic.nemesiseye.policy;
import io.github.adrianvic.nemesiseye.policy.handlers.*;
import io.github.adrianvic.nemesiseye.policy.handlers.attackWith;
import io.github.adrianvic.nemesiseye.policy.handlers.useEnchantment;
import io.github.adrianvic.nemesiseye.policy.handlers.useItem;
import java.util.HashMap;
import java.util.Map;
public class NodeHandlers {
private static final Map<Action, NodeHandler> handlers = new HashMap<>();
private static final Map<String, NodeHandler> handlers = new HashMap<>();
static {
handlers.put(Action.HIT, new UseItem());
handlers.put(Action.PLACE, new BePlaced());
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
handlers.put("attackWithItemInHand", new attackWith());
handlers.put("useItem", new useItem());
handlers.put("useEnchantment", new useEnchantment());
}
public static NodeHandler get(Action type) {
public static NodeHandler get(String type) {
return handlers.get(type);
}
}

View file

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

View file

@ -1,73 +1,34 @@
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.HashMap;
import java.util.List;
import java.util.Map;
public record PolicyNode(List<Action> actions, List<Object> values) {
public static List<PolicyNode> parseNodes(List<Map<Object,Object>> raw, Effect effect) {
public record PolicyNode(String type, List<Object> values, boolean isWhitelist) {
public static List<PolicyNode> parseNodes(List<Map<String,Object>> raw, boolean isWhitelist) {
List<PolicyNode> nodes = new ArrayList<>();
for (Map<Object, Object> m : raw) {
for (Map.Entry<Object, Object> rawNode : m.entrySet()) {
List<Action> nodeActions = new ArrayList<>();
List<Object> nodeValues = new ArrayList<>();
for (Map<String, Object> m : raw) {
for (Map.Entry<String, Object> entry : m.entrySet()) {
String type = entry.getKey();
List<Object> values = new ArrayList<>();
Object val = entry.getValue();
if (rawNode.getKey() instanceof List<?> rawTypes && rawNode.getValue() instanceof Map<?,?> rawNodeValues) {
for (Object rawType : rawTypes) {
if (rawType instanceof String rts && !rts.isEmpty() && !(rts == null) && DataShifter.enumOrDefault(Action.class, rts, null) != null) {
nodeActions.add(DataShifter.enumOrDefault(Action.class, rts, null));
}
}
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);
}
}
if (val instanceof String s) {
values.add(s);
} else if (val instanceof List<?> l) {
values.addAll(l);
} else if (val instanceof Map<?,?> map) {
values.add(map);
}
if (!nodeActions.isEmpty() && !nodeValues.isEmpty()) {
PolicyNode newNode = new PolicyNode(nodeActions, nodeValues);
nodes.add(newNode);
}
nodes.add(new PolicyNode(type, values, isWhitelist));
}
}
return nodes;
}
public List<NodeHandler> getHandler() {
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;
public NodeHandler getHandler() {
return NodeHandlers.get(type);
}
}

View file

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

View file

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

View file

@ -1,26 +0,0 @@
package io.github.adrianvic.nemesiseye.policy.handlers;
import io.github.adrianvic.nemesiseye.DataShifter;
import io.github.adrianvic.nemesiseye.policy.Action;
import io.github.adrianvic.nemesiseye.policy.NodeHandler;
import io.github.adrianvic.nemesiseye.policy.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

@ -1,31 +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.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

@ -1,14 +0,0 @@
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

@ -1,21 +0,0 @@
package io.github.adrianvic.nemesiseye.policy.handlers;
import io.github.adrianvic.nemesiseye.DataShifter;
import io.github.adrianvic.nemesiseye.policy.Action;
import io.github.adrianvic.nemesiseye.policy.NodeHandler;
import io.github.adrianvic.nemesiseye.policy.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

@ -1,34 +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.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

@ -1,33 +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.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

@ -0,0 +1,25 @@
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

@ -0,0 +1,27 @@
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

@ -0,0 +1,25 @@
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

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

View file

@ -1,28 +0,0 @@
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

@ -1,28 +0,0 @@
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

@ -1,16 +0,0 @@
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

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

View file

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

View file

@ -1,21 +1,7 @@
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;
import java.util.ArrayList;
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();
}
}
}
public record PlayerNamePolicy(String name, ArrayList<String> playerName, PolicyNode nodes, boolean allowlist) {}

View file

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

View file

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

View file

@ -1,19 +0,0 @@
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,171 +1,121 @@
# __ _______ __ ___________________ _______
# / / \ _ \ \ \ \_ _____/\_____ \ \ \
# / / / /_\ \ \ \ | __)_ / | \ / | \
# \ \ \ \_/ \ / / | \/ | \/ | \
# \_\ \_____ / /_/ /_______ /\_______ /\____|__ /
# \/ \/ \/ \/
# EYE OF NEMESIS - Example config file.
# Documentation in our wiki: https://github.com/adrianvic/NemesisEye
Policies:
- name: "Bedrock-allow-admins"
type: "permission"
worlds: [world]
effect: ALLOW
weight: 3
permissions:
- "server.usebedrock"
nodes:
- [BREAK, PLACE, HIT, INTERACT]:
values:
- BEDROCK
- name: "Bedrock-deny"
type: "global"
worlds: [world]
effect: DENY
weight: 2
nodes:
- [BREAK, PLACE, HIT, INTERACT]:
values:
- BEDROCK
- name: "Block-non-beta-items" # No spaces here or else commands which need the name as argument will not work
type: "location" # global | location | permission | playerName
effect: DENY # DENY | ALLOW (overrides deny) | ALLOWONLY (allow only if met)
worlds: [world]
weight: 0 # Greater weight overrides lower
policyAllowList: false # Inverts the policy validation logic, for example a location policy will affect all players NOT inside it's location
locations:
worlds: [world]
coordinates:
- corner1: { x: 2100, y: 256, z: 1400 }
corner2: { x: 1000, y: -64, z: 2200 }
nodes:
- [INTERACT, BREAK, HIT, PLACE]:
values:
- '.*'
- [USE_ENCHANTMENT]:
values:
- ".*": ".*"
- name: "Allow-beta-items"
type: "location"
effect: ALLOW
weight: 1
worlds: [world]
locations:
coordinates:
- corner1: { x: 2100, y: 256, z: 1400 }
corner2: { x: 1000, y: -64, z: 2200 }
nodes:
- [INTERACT, BREAK, HIT, PLACE]:
values:
- AIR
- STONE
- COBBLESTONE
- "^(OAK|SPRUCE|BIRCH)_(LOG|SAPLING|PLANKS|LEAVES)$"
- "^(DIAMOND|GOLD|IRON|COAL|LAPIS|REDSTONE)_ORE$"
- "^(DIAMOND|GOLD|IRON|LAPIS)_BLOCK$"
- GRAVEL
- BEDROCK
- SAND
- SPONGE
- WET_SPONGE
- GLASS
- LAPIS_LAZULI
- COBWEB
- PISTON
- STICKY_PISTON
- GRASS_BLOCK
- DISPENSER
- NOTE_BLOCK
- SANDSTONE
- RED_BED
- "^(POWERED|DETECTOR)_RAIL$"
- RAIL
- SHORT_GRASS
- "^(WHITE|BLACK|GREEN|YELLOW|PINK|PURPLE|CYAN|BLUE|RED|LIME|BROWN|LIGHT_GRAY|GRAY)_(WOOL|DYE)$"
- POPPY
- DANDELION
- "^(RED|BROWN)_MUSHROOM$"
- "^(OAK|COBBLESTONE|SMOOTH_STONE)_SLAB$"
- BRICK_BLOCK
- TNT
- BOOKSHELF
- OBSIDIAN
- MOSSY_COBBLESTONE
- TORCH
- SPAWNER
- REDSTONE
- CHEST
- CRAFTING_TABLE
- FARMLAND
- FURNACE
- SIGN
- LADDER
- "^(COBBLESTONE|OAK)_STAIRS$"
- LEVER
- "^(OAK|STONE)_PRESSURE_PLATE$"
- "^(OAK|IRON)_DOOR$"
- BLUE_ICE
- REDSTONE_TORCH
- STONE_BUTTON
- SNOW
- SNOW_BLOCK
- CLAY
- SUGAR_CANE
- JUKEBOX
- OAK_FENCE
- PUMPKIN
- NETHERRACK
- SOUL_SAND
- GLOWSTONE
- JACK_O_LANTERN
- CAKE
- REPEATER
- OAK_TRAPDOOR
- "^(IRON|STONE|DIAMOND|WOODEN|GOLDEN)_(SHOVEL|AXE|PICKAXE|SWORD|HOE)$"
- "^(IRON|LEATHER|DIAMOND|GOLDEN)_(HELMET|CHESTPLATE|LEGGINGS|BOOTS)$"
- 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
- Location:
# NO SPACES
- name: "Beta-1.7.3-items-only"
# Will deny anything that's not allowed by the nodes if set to true
allowList: true
nodes:
- useItem:
- AIR
- STONE
- COBBLESTONE
- "^(OAK|SPRUCE|BIRCH)_(LOG|SAPLING|PLANKS|LEAVES)$"
- "^(DIAMOND|GOLD|IRON|COAL|LAPIS|REDSTONE)_ORE$"
- "^(DIAMOND|GOLD|IRON|LAPIS)_BLOCK$"
- GRAVEL
- BEDROCK
- SAND
- SPONGE
- WET_SPONGE
- GLASS
- LAPIS_LAZULI
- COBWEB
- PISTON
- STICKY_PISTON
- GRASS
- DISPENSER
- NOTE_BLOCK
- SANDSTONE
- RED_BED
- "^(POWERED|DETECTOR)_RAIL$"
- RAIL
- SHORT_GRASS
- "^(WHITE|BLACK|GREEN|YELLOW|PINK|PURPLE|CYAN|BLUE|RED|LIME|BROWN|LIGHT_GRAY|GRAY)_(WOOL|DYE)$"
- POPPY
- DANDELION
- "^(RED|BROWN)_MUSHROOM$"
- "^(OAK|COBBLESTONE)_SLAB$"
- BRICK_BLOCK
- TNT
- BOOKSHELF
- OBSIDIAN
- MOSSY_COBBLESTONE
- TORCH
- SPAWNER
- REDSTONE
- CHEST
- CRAFTING_TABLE
- FARMLAND
- FURNACE
- SIGN
- LADDER
- "^(COBBLESTONE|OAK)_STAIRS$"
- LEVER
- "^(OAK|STONE)_PRESSURE_PLATE$"
- "^(OAK|IRON)_DOOR$"
- BLUE_ICE
- REDSTONE_TORCH
- STONE_BUTTON
- SNOW
- SNOW_BLOCK
- CLAY
- SUGAR_CANE
- JUKEBOX
- OAK_FENCE
- PUMPKIN
- NETHERRACK
- SOUL_SAND
- GLOWSTONE
- JACK_O_LANTERN
- CAKE
- REPEATER
- OAK_TRAPDOOR
- "^(IRON|STONE|DIAMOND|WOODEN|GOLDEN)_(SHOVEL|AXE|PICKAXE|SWORD|HOE)$"
- "^(IRON|LEATHER|DIAMOND|GOLDEN)_(HELMET|CHESTPLATE|LEGGINGS|BOOTS)$"
- 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_SACw
- BONE_MEAL
- SUGAR
- COOKIE
- MAP
- FILLED_MAP
- SHEARS
- MUSIC_DISK_CAT
- MUSIC_DISK_13
- DIRT
- BREAD
- useEnchantment:
"theresnoenchantmentwiththisname": "3"
locations:
- corner1: { x: 2100, y: 256, z: 1400 }
corner2: { x: 1000, y: -64, z: 2200 }

View file

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

View file

@ -1,6 +1,7 @@
package io.github.adrianvic.nemesiseye.impl.commands;
import io.github.adrianvic.nemesiseye.commands.EyeCore;
import io.github.adrianvic.nemesiseye.commands.sub.*;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
@ -11,7 +12,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
public class Eye implements CommandExecutor, TabCompleter {
private final EyeCore core;
private EyeCore core;
public Eye() {
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.policy.Policy;
import io.github.adrianvic.nemesiseye.policy.PolicyParsers;
import io.github.adrianvic.nemesiseye.policy.policies.LocationPolicy;
import io.github.adrianvic.nemesiseye.reflection.Glimmer;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.HumanEntity;
@ -20,7 +20,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@SuppressWarnings("unused")
public class r1_21 implements Glimmer {
@Override
public File loadConfigFile() {
@ -45,16 +44,34 @@ public class r1_21 implements Glimmer {
List<Map<?, ?>> rawPolicies = config.getMapList("Policies");
List<Policy> allPolicies = new ArrayList<>();
for (Map<?, ?> policyMap : rawPolicies) {
if (policyMap.get("type") != null && policyMap.get("type") instanceof String type) {
allPolicies.add(PolicyParsers.get(type).parse(policyMap));
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
public List<Policy> getApplyingPoliciesForEntity(HumanEntity entity, List<Policy> policies) {
List<Policy> applyingLPS = new ArrayList<>();
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;
}
@Override
public void onLoad() {
PluginManager pm = Nemesis.getInstance().getPluginManager();
@ -68,91 +85,11 @@ public class r1_21 implements Glimmer {
}
@Override
public boolean isAir(ItemStack item) {
return item == null || item.getType().isAir();
}
@Override
public boolean isGliding(org.bukkit.entity.Player player) {
return player.isGliding();
}
@Override
public void setGliding(org.bukkit.entity.Player player, boolean gliding) {
player.setGliding(gliding);
}
@Override
public boolean hasPermission(org.bukkit.command.CommandSender sender, String permission) {
return sender.hasPermission(permission);
}
@Override
public boolean isArmorEquipAttempt(org.bukkit.event.Event event) {
if (!(event instanceof org.bukkit.event.inventory.InventoryClickEvent e)) {
public boolean hasItemMeta(ItemStack item) {
if (item.getItemMeta() == null) {
return false;
}
if (e.getSlotType() == org.bukkit.event.inventory.InventoryType.SlotType.ARMOR) {
return true;
}
if (e.isShiftClick()) {
return isArmor(e.getCurrentItem());
}
if (e.getClick() == org.bukkit.event.inventory.ClickType.NUMBER_KEY
&& e.getSlotType() == org.bukkit.event.inventory.InventoryType.SlotType.ARMOR
&& e.getWhoClicked() instanceof org.bukkit.entity.Player player) {
return isArmor(
player.getInventory().getItem(e.getHotbarButton())
);
}
return false;
}
@Override
public ItemStack getEquippedItem(org.bukkit.event.Event event) {
if (event instanceof org.bukkit.event.inventory.InventoryClickEvent e) {
org.bukkit.event.inventory.InventoryType.SlotType slotType = e.getSlotType();
if (e.getClick() == org.bukkit.event.inventory.ClickType.NUMBER_KEY // hotbar key swap
&& slotType == org.bukkit.event.inventory.InventoryType.SlotType.ARMOR
&& e.getWhoClicked() instanceof org.bukkit.entity.Player player) {
return player.getInventory().getItem(e.getHotbarButton());
}
if (e.isShiftClick()) {
ItemStack current = e.getCurrentItem();
if (isArmor(current)) return current;
}
// regular click
if (slotType == org.bukkit.event.inventory.InventoryType.SlotType.ARMOR) {
ItemStack cursor = e.getCursor();
if (isArmor(cursor)) return cursor;
}
}
// Try Paper's PlayerArmorChangeEvent via reflection or just check if class exists
try {
if (event instanceof com.destroystokyo.paper.event.player.PlayerArmorChangeEvent e) {
return e.getNewItem();
}
} catch (NoClassDefFoundError | Exception ignored) {}
return null;
}
@Override
public void sendMessage(CommandSender commandSender, String text) {
commandSender.sendMessage(text);
}
@Override
public boolean hasItemMeta(ItemStack item) {
return item.getItemMeta() != null;
return true;
}
@Override
@ -162,17 +99,10 @@ public class r1_21 implements Glimmer {
@Override
public boolean hasEnchantment(ItemStack item, Map<String, String> valuesmap) {
Map<Enchantment, Integer> enchantments = item.getEnchantments();
for (Map.Entry<Enchantment, Integer> ench : enchantments.entrySet()) {
String enchKey = ench.getKey().getKey().getKey();
String enchLevel = ench.getValue().toString();
for (Map.Entry<String, String> rule : valuesmap.entrySet()) {
if (
DataShifter.safeMatches(rule.getKey(), enchKey) &&
DataShifter.safeMatches(rule.getValue(), enchLevel)
) {
Map<Enchantment, Integer> enchantmentList = item.getEnchantments();
for (Map.Entry<Enchantment, Integer> enchantmentEntry : enchantmentList.entrySet()) {
for (Map.Entry<String, String> valueEntry : valuesmap.entrySet()) {
if (enchantmentEntry.getKey().getKey().getKey().equals(valueEntry.getKey()) && enchantmentEntry.getValue().toString().equals(valueEntry.getValue())) {
return true;
}
}
@ -183,19 +113,4 @@ public class r1_21 implements Glimmer {
public boolean hasAnyEnchantment(ItemStack item) {
return !(item.getItemMeta().getEnchants().isEmpty());
}
@Override
public boolean isArmor(ItemStack item) {
if (item == null || item.getType().isAir()) {
return false;
}
String name = item.getType().name();
return name.endsWith("_HELMET")
|| name.endsWith("_CHESTPLATE")
|| name.endsWith("_LEGGINGS")
|| name.endsWith("_BOOTS")
|| item.getType() == org.bukkit.Material.ELYTRA;
}
}

View file

@ -0,0 +1,10 @@
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

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

View file

@ -1,43 +0,0 @@
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

@ -1,64 +0,0 @@
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)));
}
}