add java 8 support (fixes #33), change folia support, restructure project

This commit is contained in:
Pyrbu 2023-05-03 07:24:34 +01:00
parent b3c9b246c7
commit 26105d514c
24 changed files with 386 additions and 102 deletions

@ -1,11 +1,10 @@
# ZNPCsPlus
[ZNPCsPlus](https://www.spigotmc.org/resources/znpcsplus.109380/) is an unofficial fork of the popular NPC plugin ZNPCs written with the Spigot/Bukkit API originally made by
gonalez/ZNetwork. This fork was made because the original maintainer of the plugin decided to announce that he was
[dropping support for the plugin](https://media.discordapp.net/attachments/1093914615873806477/1098409384855474237/znpc.png)
in the original project's [official discord server](https://discord.com/invite/RhNMH4T).
[ZNPCsPlus](https://www.spigotmc.org/resources/znpcsplus.109380/) is an remake of the popular NPC plugin ZNPCs written with the Spigot/Bukkit API originally made by
gonalez/ZNetwork. This project was originally started because the maintainer of ZNPCs decided to announce that he was
[dropping support for the plugin](https://media.discordapp.net/attachments/1093914615873806477/1098409384855474237/znpc.png).
### Dependencies
- Java 17
- Java 8
- Spigot 1.8 - 1.19.4
- PlaceholderAPI (OPTIONAL)
@ -15,7 +14,10 @@ If you're using a Minecraft version that rejects Java 17 use the `-DPaper.Ignore
This fork makes several improvements over the original including:
- More performance-conscious code
- Latest version support (1.19.4)
- Fixes for long-running issues of the original plugin (ones that the original maintainer refused to even acknowledge)
- Fixes for long-running issues of the original plugin
- Better stability
- Support for multiple different storage options (WIP)
- Better command system (WIP)
## Found a bug?
Open an issue in the GitHub [issue tracker](https://github.com/Pyrbu/ZNPCsPlus/issues)

@ -1,4 +1,5 @@
plugins {
id "java"
id "maven-publish"
}

@ -86,7 +86,7 @@ public class NPCType {
}
public Builder addProperties(EntityProperty<?>... properties) {
allowedProperties.addAll(List.of(properties));
allowedProperties.addAll(Arrays.asList(properties));
return this;
}
@ -108,7 +108,7 @@ public class NPCType {
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9))
allowedProperties.add(EntityProperty.GLOW);
}
return new NPCType(name, type, hologramOffset, Set.copyOf(allowedProperties));
return new NPCType(name, type, hologramOffset, new HashSet<>(allowedProperties));
}
}
}

@ -1,27 +1,11 @@
plugins {
id "java"
id "com.github.johnrengelman.shadow" version "8.1.1"
id "xyz.jpenilla.run-paper" version "2.0.1"
}
dependencies {
implementation project(path: ":spigot", configuration: "shadow")
implementation project(":api")
implementation project(":folia")
}
runServer {
minecraftVersion "1.19.4"
}
allprojects {
subprojects {
apply plugin: "java"
group "lol.pyr"
version "2.0.0"
compileJava {
options.release.set(17)
options.release.set(8)
}
repositories {
@ -32,6 +16,9 @@ allprojects {
maven {
url "https://repo.codemc.io/repository/maven-snapshots/"
}
maven {
url "https://libraries.minecraft.net"
}
maven {
url "https://repo.papermc.io/repository/maven-public/"
}
@ -46,6 +33,3 @@ allprojects {
}
}
}
tasks.assemble.dependsOn shadowJar
tasks.compileJava.dependsOn clean

@ -1,3 +0,0 @@
dependencies {
compileOnly "dev.folia:folia-api:1.19.4-R0.1-SNAPSHOT"
}

@ -1,27 +0,0 @@
package lol.pyr.znpcsplus.scheduling;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import java.util.concurrent.TimeUnit;
public class FoliaScheduler extends TaskScheduler {
public FoliaScheduler(Plugin plugin) {
super(plugin);
}
@Override
public void runAsync(Runnable runnable) {
Bukkit.getAsyncScheduler().runNow(plugin, task -> runnable.run());
}
@Override
public void runLaterAsync(Runnable runnable, long ticks) {
Bukkit.getAsyncScheduler().runDelayed(plugin, task -> runnable.run(), ticks * 50, TimeUnit.MILLISECONDS);
}
@Override
public void runDelayedTimerAsync(Runnable runnable, long delay, long ticks) {
Bukkit.getAsyncScheduler().runAtFixedRate(plugin, task -> runnable.run(), delay * 50, ticks * 50, TimeUnit.MILLISECONDS);
}
}

@ -1,3 +1,3 @@
rootProject.name = "ZNPCsPlus"
include "api", "spigot", "folia"
include "api", "spigot"

@ -1,12 +1,22 @@
plugins {
id "java"
id "com.github.johnrengelman.shadow" version "8.1.1"
id "xyz.jpenilla.run-paper" version "2.0.1"
}
runServer {
minecraftVersion "1.19.4"
}
processResources {
expand("version": version)
}
dependencies {
compileOnly "org.spigotmc:spigot-api:1.19.4-R0.1-SNAPSHOT"
compileOnly "me.clip:placeholderapi:2.11.1"
compileOnly "com.mojang:authlib:3.4.40"
compileOnly "com.mojang:authlib:1.5.21"
compileOnly "com.mojang:datafixerupper:4.0.26"
implementation "commons-io:commons-io:2.11.0"
@ -19,13 +29,13 @@ dependencies {
implementation "space.arim.dazzleconf:dazzleconf-ext-snakeyaml:1.2.1"
implementation "lol.pyr:director-adventure:2.0.1"
compileOnly project(":api")
compileOnly project(":folia")
implementation project(":api")
}
shadowJar {
archivesBaseName = "ZNPCsPlus"
archiveClassifier.set ""
relocate "org.bstats", "lol.pyr.znpcsplus.lib.bstats"
relocate "org.apache.commons.io", "lol.pyr.znpcsplus.lib.commonsio"
relocate "me.robertlit.spigotresources", "lol.pyr.znpcsplus.lib.spigotresources"
@ -43,3 +53,5 @@ shadowJar {
relocate "lol.pyr.director", "lol.pyr.znpcsplus.lib.command"
minimize()
}
tasks.assemble.dependsOn shadowJar

@ -15,6 +15,11 @@ public class ReflectionBuilder {
private Class<?> expectType;
private boolean strict = true;
public ReflectionBuilder(Class<?> clazz) {
this("");
withClassName(clazz);
}
public ReflectionBuilder(String reflectionPackage) {
this(reflectionPackage, "", "", null);
}

@ -5,40 +5,92 @@ import io.github.znetworkw.znpcservers.reflection.types.ClassReflection;
import io.github.znetworkw.znpcservers.reflection.types.FieldReflection;
import io.github.znetworkw.znpcservers.reflection.types.MethodReflection;
import io.github.znetworkw.znpcservers.utility.Utils;
import lol.pyr.znpcsplus.util.FoliaUtil;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
/**
* Class containing all of the lazy-loaded reflections that the plugin
* uses to accessinaccessible things from the server jar.
*/
public final class Reflections {
public static final Class<?> ENTITY_CLASS = new ClassReflection(new ReflectionBuilder(ReflectionPackage.ENTITY)
public static final Class<?> ENTITY_CLASS = new ClassReflection(
new ReflectionBuilder(ReflectionPackage.ENTITY)
.withClassName("Entity")).get();
public static final Class<?> ENTITY_HUMAN_CLASS = new ClassReflection(new ReflectionBuilder(ReflectionPackage.ENTITY)
public static final Class<?> ENTITY_HUMAN_CLASS = new ClassReflection(
new ReflectionBuilder(ReflectionPackage.ENTITY)
.withSubClass("player")
.withClassName("EntityHuman")).get();
public static final ReflectionLazyLoader<Method> GET_PROFILE_METHOD = new MethodReflection(new ReflectionBuilder(ReflectionPackage.ENTITY)
public static final ReflectionLazyLoader<Method> GET_PROFILE_METHOD = new MethodReflection(
new ReflectionBuilder(ReflectionPackage.ENTITY)
.withClassName(ENTITY_HUMAN_CLASS)
.withExpectResult(GameProfile.class));
public static final ReflectionLazyLoader<Method> GET_HANDLE_PLAYER_METHOD = new MethodReflection(new ReflectionBuilder(ReflectionPackage.BUKKIT)
public static final ReflectionLazyLoader<Method> GET_HANDLE_PLAYER_METHOD = new MethodReflection(
new ReflectionBuilder(ReflectionPackage.BUKKIT)
.withClassName("entity.CraftPlayer").withClassName("entity.CraftHumanEntity")
.withMethodName("getHandle"));
public static final FieldReflection.ValueModifier<Integer> ENTITY_ID_MODIFIER = new FieldReflection(new ReflectionBuilder(ReflectionPackage.ENTITY)
public static final FieldReflection.ValueModifier<Integer> ENTITY_ID_MODIFIER = new FieldReflection(
new ReflectionBuilder(ReflectionPackage.ENTITY)
.withClassName(ENTITY_CLASS)
.withFieldName("entityCount")
.setStrict(!Utils.versionNewer(14))).staticValueModifier(int.class);
public static final ReflectionLazyLoader<AtomicInteger> ATOMIC_ENTITY_ID_FIELD = new FieldReflection(new ReflectionBuilder(ReflectionPackage.ENTITY)
public static final ReflectionLazyLoader<AtomicInteger> ATOMIC_ENTITY_ID_FIELD = new FieldReflection(
new ReflectionBuilder(ReflectionPackage.ENTITY)
.withClassName(ENTITY_CLASS)
.withFieldName("entityCount")
.withFieldName("d")
.withFieldName("c")
.withExpectResult(AtomicInteger.class)
.setStrict(Utils.versionNewer(14))).staticValueLoader(AtomicInteger.class);
public static final Class<?> ASYNC_SCHEDULER_CLASS = new ClassReflection(
new ReflectionBuilder("io.papermc.paper.threadedregions.scheduler")
.withClassName("AsyncScheduler")
.setStrict(FoliaUtil.isFolia())).get();
public static final Class<?> SCHEDULED_TASK_CLASS = new ClassReflection(
new ReflectionBuilder("io.papermc.paper.threadedregions.scheduler")
.withClassName("ScheduledTask")
.setStrict(FoliaUtil.isFolia())).get();
public static final ReflectionLazyLoader<Method> FOLIA_GET_ASYNC_SCHEDULER = new MethodReflection(
new ReflectionBuilder(Bukkit.class)
.withMethodName("getAsyncScheduler")
.withExpectResult(ASYNC_SCHEDULER_CLASS)
.setStrict(FoliaUtil.isFolia()));
public static final ReflectionLazyLoader<Method> FOLIA_RUN_NOW = new MethodReflection(
new ReflectionBuilder(ASYNC_SCHEDULER_CLASS)
.withMethodName("runNow")
.withParameterTypes(Plugin.class, Consumer.class)
.withExpectResult(SCHEDULED_TASK_CLASS)
.setStrict(FoliaUtil.isFolia()));
public static final ReflectionLazyLoader<Method> FOLIA_RUN_DELAYED = new MethodReflection(
new ReflectionBuilder(ASYNC_SCHEDULER_CLASS)
.withMethodName("runDelayed")
.withParameterTypes(Plugin.class, Consumer.class, long.class, TimeUnit.class)
.withExpectResult(SCHEDULED_TASK_CLASS)
.setStrict(FoliaUtil.isFolia()));
public static final ReflectionLazyLoader<Method> FOLIA_RUN_AT_FIXED_RATE = new MethodReflection(
new ReflectionBuilder(ASYNC_SCHEDULER_CLASS)
.withMethodName("runAtFixedRate")
.withParameterTypes(Plugin.class, Consumer.class, long.class, long.class, TimeUnit.class)
.withExpectResult(SCHEDULED_TASK_CLASS)
.setStrict(FoliaUtil.isFolia()));
}
// Bukkit.getAsyncScheduler().runNow(plugin, task -> runnable.run());
// Bukkit.getAsyncScheduler().runDelayed(plugin, task -> runnable.run(), ticks * 50, TimeUnit.MILLISECONDS);
// Bukkit.getAsyncScheduler().runAtFixedRate(plugin, task -> runnable.run(), delay * 50, ticks * 50, TimeUnit.MILLISECONDS);

@ -1,14 +1,14 @@
package lol.pyr.znpcsplus.hologram;
import lol.pyr.znpcsplus.config.Configs;
import lol.pyr.znpcsplus.util.ZLocation;
import lol.pyr.znpcsplus.util.Viewable;
import lol.pyr.znpcsplus.util.ZLocation;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class Hologram extends Viewable implements lol.pyr.znpcsplus.api.hologram.Hologram {
private ZLocation location;
@ -70,7 +70,7 @@ public class Hologram extends Viewable implements lol.pyr.znpcsplus.api.hologram
final double lineSpacing = Configs.config().lineSpacing();
double height = location.getY() + lines.size() * lineSpacing;
for (HologramLine line : lines) {
line.setLocation(location.withY(height), line == newLine ? Set.of() : getViewers());
line.setLocation(location.withY(height), line == newLine ? Collections.emptySet() : getViewers());
height -= lineSpacing;
}
}

@ -48,7 +48,7 @@ public interface MetadataFactory {
throw new RuntimeException("Unsupported version!");
}
private static Map<ServerVersion, LazyLoader<? extends MetadataFactory>> buildFactoryMap() {
static Map<ServerVersion, LazyLoader<? extends MetadataFactory>> buildFactoryMap() {
HashMap<ServerVersion, LazyLoader<? extends MetadataFactory>> map = new HashMap<>();
map.put(ServerVersion.V_1_8, LazyLoader.of(V1_8Factory::new));
map.put(ServerVersion.V_1_9, LazyLoader.of(V1_9Factory::new));

@ -3,16 +3,16 @@ package lol.pyr.znpcsplus.metadata;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import lol.pyr.znpcsplus.util.list.ListUtil;
import net.kyori.adventure.text.Component;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
public class V1_13Factory extends V1_9Factory {
@Override
public Collection<EntityData> name(Component name) {
return List.of(
return ListUtil.immutableList(
new EntityData(2, EntityDataTypes.OPTIONAL_COMPONENT, Optional.of(AdventureSerializer.getGsonSerializer().serialize(name))),
new EntityData(3, EntityDataTypes.BOOLEAN, true)
);

@ -3,10 +3,10 @@ package lol.pyr.znpcsplus.metadata;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import lol.pyr.znpcsplus.util.list.ListUtil;
import net.kyori.adventure.text.Component;
import java.util.Collection;
import java.util.List;
public class V1_8Factory implements MetadataFactory {
@Override
@ -21,7 +21,7 @@ public class V1_8Factory implements MetadataFactory {
@Override
public Collection<EntityData> name(Component name) {
return List.of(
return ListUtil.immutableList(
new EntityData(2, EntityDataTypes.STRING, AdventureSerializer.getGsonSerializer().serialize(name)),
new EntityData(3, EntityDataTypes.BYTE, 1)
);

@ -3,10 +3,10 @@ package lol.pyr.znpcsplus.metadata;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import lol.pyr.znpcsplus.util.list.ListUtil;
import net.kyori.adventure.text.Component;
import java.util.Collection;
import java.util.List;
public class V1_9Factory extends V1_8Factory {
@Override
@ -21,7 +21,7 @@ public class V1_9Factory extends V1_8Factory {
@Override
public Collection<EntityData> name(Component name) {
return List.of(
return ListUtil.immutableList(
new EntityData(2, EntityDataTypes.STRING, AdventureSerializer.getGsonSerializer().serialize(name)),
new EntityData(3, EntityDataTypes.BOOLEAN, true)
);

@ -43,7 +43,7 @@ public interface PacketFactory {
throw new RuntimeException("Unsupported version!");
}
private static Map<ServerVersion, LazyLoader<? extends PacketFactory>> buildFactoryMap() {
static Map<ServerVersion, LazyLoader<? extends PacketFactory>> buildFactoryMap() {
HashMap<ServerVersion, LazyLoader<? extends PacketFactory>> map = new HashMap<>();
map.put(ServerVersion.V_1_8, LazyLoader.of(V1_8Factory::new));
map.put(ServerVersion.V_1_9, LazyLoader.of(V1_9Factory::new));

@ -22,6 +22,7 @@ import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@ -33,7 +34,7 @@ public class V1_8Factory implements PacketFactory {
createTeam(player, entity, properties);
ZLocation location = entity.getLocation();
sendPacket(player, new WrapperPlayServerSpawnPlayer(entity.getEntityId(),
entity.getUuid(), location.toVector3d(), location.getYaw(), location.getPitch(), List.of()));
entity.getUuid(), location.toVector3d(), location.getYaw(), location.getPitch(), Collections.emptyList()));
sendAllMetadata(player, entity, properties);
ZNPCsPlus.SCHEDULER.runLaterAsync(() -> removeTabPlayer(player, entity), 60);
});
@ -46,7 +47,7 @@ public class V1_8Factory implements PacketFactory {
ClientVersion clientVersion = PacketEvents.getAPI().getServerManager().getVersion().toClientVersion();
sendPacket(player, type.getLegacyId(clientVersion) == -1 ?
new WrapperPlayServerSpawnLivingEntity(entity.getEntityId(), entity.getUuid(), type, location.toVector3d(),
location.getYaw(), location.getPitch(), location.getPitch(), new Vector3d(), List.of()) :
location.getYaw(), location.getPitch(), location.getPitch(), new Vector3d(), Collections.emptyList()) :
new WrapperPlayServerSpawnEntity(entity.getEntityId(), Optional.of(entity.getUuid()), entity.getType(), location.toVector3d(),
location.getPitch(), location.getYaw(), location.getYaw(), 0, Optional.empty()));
sendAllMetadata(player, entity, properties);

@ -0,0 +1,48 @@
package lol.pyr.znpcsplus.scheduling;
import io.github.znetworkw.znpcservers.reflection.Reflections;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class FoliaScheduler extends TaskScheduler {
public FoliaScheduler(Plugin plugin) {
super(plugin);
}
@Override
public void runAsync(Runnable runnable) {
try {
Object scheduler = Reflections.FOLIA_GET_ASYNC_SCHEDULER.get().invoke(null);
Reflections.FOLIA_RUN_NOW.get().invoke(scheduler, plugin, (Consumer<Object>) o -> runnable.run());
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
// Bukkit.getAsyncScheduler().runNow(plugin, task -> runnable.run());
}
@Override
public void runLaterAsync(Runnable runnable, long ticks) {
try {
Object scheduler = Reflections.FOLIA_GET_ASYNC_SCHEDULER.get().invoke(null);
Reflections.FOLIA_RUN_DELAYED.get().invoke(scheduler, plugin, (Consumer<Object>) o -> runnable.run(), ticks * 50, TimeUnit.MILLISECONDS);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
// Bukkit.getAsyncScheduler().runDelayed(plugin, task -> runnable.run(), ticks * 50, TimeUnit.MILLISECONDS);
}
@Override
public void runDelayedTimerAsync(Runnable runnable, long delay, long ticks) {
try {
Object scheduler = Reflections.FOLIA_GET_ASYNC_SCHEDULER.get().invoke(null);
Reflections.FOLIA_RUN_AT_FIXED_RATE.get().invoke(scheduler, plugin, (Consumer<Object>) o -> runnable.run(), delay * 50, ticks * 50, TimeUnit.MILLISECONDS);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
// Bukkit.getAsyncScheduler().runAtFixedRate(plugin, task -> runnable.run(), delay * 50, ticks * 50, TimeUnit.MILLISECONDS);
}
}

@ -5,9 +5,11 @@ import com.github.retrooper.packetevents.protocol.player.UserProfile;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.authlib.properties.PropertyMap;
import lol.pyr.znpcsplus.util.list.ListUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class Skin {
private final long timestamp = System.currentTimeMillis();
@ -18,13 +20,13 @@ public class Skin {
}
public Skin(TextureProperty... properties) {
this.properties.addAll(List.of(properties));
this.properties.addAll(ListUtil.immutableList(properties));
}
public Skin(PropertyMap properties) {
this.properties.addAll(properties.values().stream()
.map(property -> new TextureProperty(property.getName(), property.getValue(), property.getSignature()))
.toList());
.collect(Collectors.toList()));
}
public Skin(JsonObject obj) {

@ -1,7 +1,9 @@
package lol.pyr.znpcsplus.util;
public class FoliaUtil {
private static final Boolean FOLIA = isFolia();
public static boolean isFolia() {
if (FOLIA != null) return FOLIA;
try {
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
return true;

@ -0,0 +1,62 @@
package lol.pyr.znpcsplus.util.list;
import java.util.Iterator;
import java.util.ListIterator;
public class ArrayIterator<T> implements Iterator<T>, ListIterator<T> {
private final T[] array;
private int index = 0;
public ArrayIterator(T[] array) {
this.array = array;
}
@Override
public boolean hasNext() {
return array.length > index;
}
@Override
public T next() {
return array[index++];
}
private boolean inBounds(int index) {
return index >= 0 && index < array.length;
}
@Override
public boolean hasPrevious() {
return inBounds(index - 1);
}
@Override
public T previous() {
return array[--index];
}
@Override
public int nextIndex() {
return index + 1;
}
@Override
public int previousIndex() {
return index - 1;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public void set(T t) {
throw new UnsupportedOperationException();
}
@Override
public void add(T t) {
throw new UnsupportedOperationException();
}
}

@ -0,0 +1,133 @@
package lol.pyr.znpcsplus.util.list;
import java.util.*;
public class ImmutableArrayList<T> implements List<T> {
private final T[] elements;
public ImmutableArrayList(T[] array) {
this.elements = array;
}
@Override
public int size() {
return elements.length;
}
@Override
public boolean isEmpty() {
return elements.length != 0;
}
@Override
public boolean contains(Object o) {
return indexOf(o) != -1;
}
@Override
public Iterator<T> iterator() {
return new ArrayIterator<>(elements);
}
@Override
public Object[] toArray() {
return elements;
}
@SuppressWarnings("unchecked")
@Override
public <T1> T1[] toArray(T1[] a) {
return (T1[]) elements;
}
@Override
public boolean containsAll(Collection<?> c) {
for (Object obj : c) if (!contains(obj)) return false;
return true;
}
@Override
public List<T> subList(int fromIndex, int toIndex) {
return new ImmutableArrayList<>(Arrays.copyOfRange(elements, fromIndex, toIndex));
}
@Override
public T get(int index) {
return elements[index];
}
@Override
public int indexOf(Object o) {
for (int i = 0; i < elements.length; i++) if (Objects.equals(elements[i], o)) return i;
return -1;
}
@Override
public int lastIndexOf(Object o) {
for (int i = 0; i < elements.length; i++) {
int index = elements.length - (i + 1);
if (Objects.equals(elements[index], o)) return index;
}
return -1;
}
@Override
public ListIterator<T> listIterator() {
return new ArrayIterator<>(elements);
}
@Override
public ListIterator<T> listIterator(int index) {
return new ArrayIterator<>(elements);
}
@Override
public boolean add(T t) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(Collection<? extends T> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(int index, Collection<? extends T> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public T set(int index, T element) {
throw new UnsupportedOperationException();
}
@Override
public void add(int index, T element) {
throw new UnsupportedOperationException();
}
@Override
public T remove(int index) {
throw new UnsupportedOperationException();
}
}

@ -0,0 +1,10 @@
package lol.pyr.znpcsplus.util.list;
import java.util.*;
public class ListUtil {
@SafeVarargs
public static <T> List<T> immutableList(T... elements) {
return new ImmutableArrayList<>(elements);
}
}