mirror of
https://github.com/StarWishsama/Slimefun4.git
synced 2024-09-19 19:25:48 +00:00
Storage rewrite - Phase 1 (#4065)
* Phase 1 - wip * Add some tests * wip * Save waypoints * Implement backpacks * Add tests for waypoints * Changes to ADR * Small changes * Fix englandish and some small changes to UUID in PlayerProfile * Fix saving of player data in a few cases * Documentation around deprecated * Add some more tests * Some small doc updates * Make old Waypoint constructor deprecated and fix javadocs
This commit is contained in:
parent
dad6020680
commit
4d710fa0b1
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,6 +5,7 @@
|
|||||||
/.settings/
|
/.settings/
|
||||||
/.idea/
|
/.idea/
|
||||||
/.vscode/
|
/.vscode/
|
||||||
|
/data-store/
|
||||||
|
|
||||||
dependency-reduced-pom.xml
|
dependency-reduced-pom.xml
|
||||||
|
|
||||||
|
129
docs/adr/0001-storage-layer.md
Normal file
129
docs/adr/0001-storage-layer.md
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
# 1. Storage layer
|
||||||
|
|
||||||
|
Date: 2023-11-15
|
||||||
|
Last update: 2023-12-27
|
||||||
|
|
||||||
|
**DO NOT rely on any APIs introduced until we finish the work completely!**
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Work in progress
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Slimefun has been around for a very long time and due to that, the way we
|
||||||
|
wrote persistence of data has also been around for a very long time.
|
||||||
|
While Slimefun has grown, the storage layer has never been adapted.
|
||||||
|
This means that even all these years later, it's using the same old saving/loading.
|
||||||
|
This isn't necessarily always bad, however, as Slimefun has grown both in terms of content
|
||||||
|
and the servers using it - we've seen some issues.
|
||||||
|
|
||||||
|
Today, files are saved as YAML files (sometimes with just a JSON object per line),
|
||||||
|
which is good for a config format but not good for a data store. It can create very large files
|
||||||
|
that can get corrupted, the way we've been saving data often means loading it all at once as well
|
||||||
|
rather than lazy-loading and generally isn't very performant.
|
||||||
|
|
||||||
|
For a long time we've been talking about rewriting our data storage in multiple forms
|
||||||
|
(you may have seen this referenced for "BlockStorage rewrite" or "SQL for PlayerProfiles", etc.).
|
||||||
|
Now is the time we start to do this, this will be a very large change and will not be done quickly or rushed.
|
||||||
|
|
||||||
|
This ADR talks about the future of our data persistence.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
We want to create a new storage layer abstraction and implementations
|
||||||
|
which will be backwards-compatible but open up new ways of storing data
|
||||||
|
within Slimefun. The end end goal is we can quickly and easily support
|
||||||
|
new storage backends (such as binary storage, SQL, etc.) for things like
|
||||||
|
[PlayerProfile](https://github.com/Slimefun/Slimefun4/blob/bbfb9734b9f549d7e82291eff041f9b666a61b63/src/main/java/io/github/thebusybiscuit/slimefun4/api/player/PlayerProfile.java), [BlockStorage](https://github.com/Slimefun/Slimefun4/blob/bbfb9734b9f549d7e82291eff041f9b666a61b63/src/main/java/me/mrCookieSlime/Slimefun/api/BlockStorage.java), etc.
|
||||||
|
|
||||||
|
We also want to be generally more efficient in the way we save and load data.
|
||||||
|
Today, we load way more than is required.
|
||||||
|
We can improve memory usage by only loading what we need, when we need it.
|
||||||
|
|
||||||
|
We will do this incrementally and at first, in an experimental context.
|
||||||
|
In that regard, we should aim to minimise the blast radius and lift as much
|
||||||
|
as possible.
|
||||||
|
|
||||||
|
### Quick changes overview
|
||||||
|
|
||||||
|
* New abstraction over storage to easily support multiple backends.
|
||||||
|
* Work towards moving away from the legacy YAML based storage.
|
||||||
|
* Lazy load and save data to more efficiently handle the data life cycle.
|
||||||
|
|
||||||
|
### Implementation details
|
||||||
|
|
||||||
|
There is a new interface called [`Storage`](TBD) which is what all storage
|
||||||
|
backends will implement.
|
||||||
|
This will have methods for loading and saving things like
|
||||||
|
[`PlayerProfile`](https://github.com/Slimefun/Slimefun4/blob/bbfb9734b9f549d7e82291eff041f9b666a61b63/src/main/java/io/github/thebusybiscuit/slimefun4/api/player/PlayerProfile.java) and [`BlockStorage`](https://github.com/Slimefun/Slimefun4/blob/bbfb9734b9f549d7e82291eff041f9b666a61b63/src/main/java/me/mrCookieSlime/Slimefun/api/BlockStorage.java).
|
||||||
|
|
||||||
|
Then, backends will implement these
|
||||||
|
(e.g. [`LegacyStorageBackend`](TBD) (today's YAML situation))
|
||||||
|
in order to support these functions.
|
||||||
|
Not all storage backends are required support each data type.
|
||||||
|
e.g. SQL may not support [`BlockStorage`](https://github.com/Slimefun/Slimefun4/blob/bbfb9734b9f549d7e82291eff041f9b666a61b63/src/main/java/me/mrCookieSlime/Slimefun/api/BlockStorage.java).
|
||||||
|
|
||||||
|
|
||||||
|
## Addons
|
||||||
|
|
||||||
|
The goal is that Addons will be able to use and implement new storage backends
|
||||||
|
if they wish and also be extended so they can load/save things as they wish.
|
||||||
|
|
||||||
|
The first few iterations will not focus on Addon support. We want to ensure
|
||||||
|
this new storage layer will work and supports what we need it to today.
|
||||||
|
|
||||||
|
This ADR will be updated when we get to supporting Addons properly.
|
||||||
|
|
||||||
|
## Considerations
|
||||||
|
|
||||||
|
This will be a big change therefore we will be doing it as incrementally as
|
||||||
|
possible.
|
||||||
|
Changes will be tested while in the PR stage and merged into the Dev releases when possible.
|
||||||
|
We may do an experimental release if required.
|
||||||
|
|
||||||
|
Phases do not (and very likely will not) be done within a single PR. They will also not have any timeframe attached to them.
|
||||||
|
|
||||||
|
The current plan looks like this:
|
||||||
|
|
||||||
|
* Phase 1 - Implement legacy data backend for [`PlayerProfile`](https://github.com/Slimefun/Slimefun4/blob/bbfb9734b9f549d7e82291eff041f9b666a61b63/src/main/java/io/github/thebusybiscuit/slimefun4/api/player/PlayerProfile.java).
|
||||||
|
* We want to load player data using the new storage layer with the current
|
||||||
|
data system.
|
||||||
|
* We'll want to monitor for any possible issues and generally refine
|
||||||
|
how this system should look
|
||||||
|
* Phase 2 - Implement new experimental binary backend for [`PlayerProfile`](https://github.com/Slimefun/Slimefun4/blob/bbfb9734b9f549d7e82291eff041f9b666a61b63/src/main/java/io/github/thebusybiscuit/slimefun4/api/player/PlayerProfile.java).
|
||||||
|
* Create a new backend for binary storage
|
||||||
|
* Implement in an experimental capacity and allow users to opt-in
|
||||||
|
* Provide a warning that this is **experimental** and there will be bugs.
|
||||||
|
* Implement new metric for storage backend being used
|
||||||
|
* Phase 3 - Mark the new backend as stable for [`PlayerProfile`](https://github.com/Slimefun/Slimefun4/blob/bbfb9734b9f549d7e82291eff041f9b666a61b63/src/main/java/io/github/thebusybiscuit/slimefun4/api/player/PlayerProfile.java).
|
||||||
|
* Mark it as stable and remove the warnings once we're sure things are
|
||||||
|
working correctly
|
||||||
|
* Create a migration path for users currently using "legacy".
|
||||||
|
* Enable by default for new servers
|
||||||
|
* Phase 4 - Move [`BlockStorage`](https://github.com/Slimefun/Slimefun4/blob/bbfb9734b9f549d7e82291eff041f9b666a61b63/src/main/java/me/mrCookieSlime/Slimefun/api/BlockStorage.java) to new storage layer.
|
||||||
|
* The big one! We're gonna tackle adding this to BlockStorage.
|
||||||
|
This will probably be a large change and we'll want to be as
|
||||||
|
careful as possible here.
|
||||||
|
* Implement `legacy` and `binary` as experimental storage backends
|
||||||
|
for BlockStorage and allow users to opt-in
|
||||||
|
* Provide a warning that this is **experimental** and there will be bugs.
|
||||||
|
* Phase 5 - Mark the new storage layer as stable for [`BlockStorage`](https://github.com/Slimefun/Slimefun4/blob/bbfb9734b9f549d7e82291eff041f9b666a61b63/src/main/java/me/mrCookieSlime/Slimefun/api/BlockStorage.java).
|
||||||
|
* Mark it as stable and remove the warnings once we're sure things are
|
||||||
|
working correctly
|
||||||
|
* Ensure migration path works here too.
|
||||||
|
* Enable by default for new servers
|
||||||
|
* Phase 6 - Finish up and move anything else we want over
|
||||||
|
* Move over any other data stores we have to the new layer
|
||||||
|
* We should probably still do experimental -> stable but it should have
|
||||||
|
less of a lead time.
|
||||||
|
|
||||||
|
## State of work
|
||||||
|
|
||||||
|
* Phase 1: In progress
|
||||||
|
* https://github.com/Slimefun/Slimefun4/pull/4065
|
||||||
|
* Phase 2: Not started
|
||||||
|
* Phase 3: Not started
|
||||||
|
* Phase 4: Not started
|
||||||
|
* Phase 5: Not started
|
||||||
|
* Phase 6: Not started
|
11
docs/adr/README.md
Normal file
11
docs/adr/README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# ADR
|
||||||
|
|
||||||
|
An ADR (Architecture Decision Record) is a document describing large changes, why we made them, etc.
|
||||||
|
|
||||||
|
## Making a new ADR
|
||||||
|
|
||||||
|
If you're making a large change to Slimefun, we recommend creating an ADR
|
||||||
|
in order to document why this is being made and how it works for future contributors.
|
||||||
|
|
||||||
|
Please follow the general format of the former ADRs or use a tool
|
||||||
|
such as [`adr-tools`](https://github.com/npryce/adr-tools) to generate a new document.
|
@ -331,7 +331,7 @@ public class GPSNetwork {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
profile.addWaypoint(new Waypoint(profile, id, event.getLocation(), event.getName()));
|
profile.addWaypoint(new Waypoint(p.getUniqueId(), id, event.getLocation(), event.getName()));
|
||||||
|
|
||||||
SoundEffect.GPS_NETWORK_ADD_WAYPOINT.playFor(p);
|
SoundEffect.GPS_NETWORK_ADD_WAYPOINT.playFor(p);
|
||||||
Slimefun.getLocalization().sendMessage(p, "gps.waypoint.added", true);
|
Slimefun.getLocalization().sendMessage(p, "gps.waypoint.added", true);
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package io.github.thebusybiscuit.slimefun4.api.gps;
|
package io.github.thebusybiscuit.slimefun4.api.gps;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.ParametersAreNonnullByDefault;
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
|
||||||
import org.apache.commons.lang.Validate;
|
import org.apache.commons.lang.Validate;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.World.Environment;
|
import org.bukkit.World.Environment;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@ -30,7 +32,7 @@ import io.github.thebusybiscuit.slimefun4.implementation.items.teleporter.Telepo
|
|||||||
*/
|
*/
|
||||||
public class Waypoint {
|
public class Waypoint {
|
||||||
|
|
||||||
private final PlayerProfile profile;
|
private final UUID ownerId;
|
||||||
private final String id;
|
private final String id;
|
||||||
private final String name;
|
private final String name;
|
||||||
private final Location location;
|
private final Location location;
|
||||||
@ -46,28 +48,62 @@ public class Waypoint {
|
|||||||
* The {@link Location} of the {@link Waypoint}
|
* The {@link Location} of the {@link Waypoint}
|
||||||
* @param name
|
* @param name
|
||||||
* The name of this {@link Waypoint}
|
* The name of this {@link Waypoint}
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #Waypoint(UUID, String, Location, String)} instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@ParametersAreNonnullByDefault
|
@ParametersAreNonnullByDefault
|
||||||
public Waypoint(PlayerProfile profile, String id, Location loc, String name) {
|
public Waypoint(PlayerProfile profile, String id, Location loc, String name) {
|
||||||
Validate.notNull(profile, "Profile must never be null!");
|
this(profile.getUUID(), id, loc, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constructs a new {@link Waypoint} object.
|
||||||
|
*
|
||||||
|
* @param ownerId
|
||||||
|
* The owning {@link Player}'s {@link UUID}
|
||||||
|
* @param id
|
||||||
|
* The unique id for this {@link Waypoint}
|
||||||
|
* @param loc
|
||||||
|
* The {@link Location} of the {@link Waypoint}
|
||||||
|
* @param name
|
||||||
|
* The name of this {@link Waypoint}
|
||||||
|
*/
|
||||||
|
@ParametersAreNonnullByDefault
|
||||||
|
public Waypoint(UUID ownerId, String id, Location loc, String name) {
|
||||||
|
Validate.notNull(ownerId, "owner ID must never be null!");
|
||||||
Validate.notNull(id, "id must never be null!");
|
Validate.notNull(id, "id must never be null!");
|
||||||
Validate.notNull(loc, "Location must never be null!");
|
Validate.notNull(loc, "Location must never be null!");
|
||||||
Validate.notNull(name, "Name must never be null!");
|
Validate.notNull(name, "Name must never be null!");
|
||||||
|
|
||||||
this.profile = profile;
|
this.ownerId = ownerId;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.location = loc;
|
this.location = loc;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This returns the owner's {@link UUID} of the {@link Waypoint}.
|
||||||
|
*
|
||||||
|
* @return The corresponding owner's {@link UUID}
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public UUID getOwnerId() {
|
||||||
|
return this.ownerId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This returns the owner of the {@link Waypoint}.
|
* This returns the owner of the {@link Waypoint}.
|
||||||
*
|
*
|
||||||
* @return The corresponding {@link PlayerProfile}
|
* @return The corresponding {@link PlayerProfile}
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #getOwnerId()} instead
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@Deprecated
|
||||||
public PlayerProfile getOwner() {
|
public PlayerProfile getOwner() {
|
||||||
return profile;
|
// This is jank and should never actually return null
|
||||||
|
return PlayerProfile.find(Bukkit.getOfflinePlayer(ownerId)).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,7 +162,7 @@ public class Waypoint {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(profile.getUUID(), id, name, location);
|
return Objects.hash(this.ownerId, this.id, this.name, this.location);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -139,7 +175,9 @@ public class Waypoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Waypoint waypoint = (Waypoint) obj;
|
Waypoint waypoint = (Waypoint) obj;
|
||||||
return profile.getUUID().equals(waypoint.getOwner().getUUID()) && id.equals(waypoint.getId()) && location.equals(waypoint.getLocation()) && name.equals(waypoint.getName());
|
return this.ownerId.equals(waypoint.getOwnerId())
|
||||||
|
&& id.equals(waypoint.getId())
|
||||||
|
&& location.equals(waypoint.getLocation())
|
||||||
|
&& name.equals(waypoint.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1160,11 +1160,11 @@ public class SlimefunItem implements Placeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a {@link Optional}<{@link SlimefunItem}> by its id.
|
* Retrieve a {@link Optional} {@link SlimefunItem} by its id.
|
||||||
*
|
*
|
||||||
* @param id
|
* @param id
|
||||||
* The id of the {@link SlimefunItem}
|
* The id of the {@link SlimefunItem}
|
||||||
* @return The {@link Optional}<{@link SlimefunItem}> associated with that id. Empty if non-existent
|
* @return The {@link Optional} {@link SlimefunItem} associated with that id. Empty if non-existent
|
||||||
*/
|
*/
|
||||||
public static @Nonnull Optional<SlimefunItem> getOptionalById(@Nonnull String id) {
|
public static @Nonnull Optional<SlimefunItem> getOptionalById(@Nonnull String id) {
|
||||||
return Optional.ofNullable(getById(id));
|
return Optional.ofNullable(getById(id));
|
||||||
@ -1193,11 +1193,11 @@ public class SlimefunItem implements Placeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a {@link Optional}<{@link SlimefunItem}> from an {@link ItemStack}.
|
* Retrieve a {@link Optional} {@link SlimefunItem} from an {@link ItemStack}.
|
||||||
*
|
*
|
||||||
* @param item
|
* @param item
|
||||||
* The {@link ItemStack} to check
|
* The {@link ItemStack} to check
|
||||||
* @return The {@link Optional}<{@link SlimefunItem}> associated with this {@link ItemStack} if present, otherwise empty
|
* @return The {@link Optional} {@link SlimefunItem} associated with this {@link ItemStack} if present, otherwise empty
|
||||||
*/
|
*/
|
||||||
public @Nonnull Optional<SlimefunItem> getOptionalByItem(@Nullable ItemStack item) {
|
public @Nonnull Optional<SlimefunItem> getOptionalByItem(@Nullable ItemStack item) {
|
||||||
return Optional.ofNullable(getByItem(item));
|
return Optional.ofNullable(getByItem(item));
|
||||||
|
@ -2,10 +2,13 @@ package io.github.thebusybiscuit.slimefun4.api.player;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.entity.HumanEntity;
|
import org.bukkit.entity.HumanEntity;
|
||||||
@ -34,13 +37,22 @@ public class PlayerBackpack {
|
|||||||
|
|
||||||
private static final String CONFIG_PREFIX = "backpacks.";
|
private static final String CONFIG_PREFIX = "backpacks.";
|
||||||
|
|
||||||
private final PlayerProfile profile;
|
private final UUID ownerId;
|
||||||
private final int id;
|
private final int id;
|
||||||
private final Config cfg;
|
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
private PlayerProfile profile;
|
||||||
|
@Deprecated
|
||||||
|
private Config cfg;
|
||||||
private Inventory inventory;
|
private Inventory inventory;
|
||||||
private int size;
|
private int size;
|
||||||
|
|
||||||
|
private PlayerBackpack(@Nonnull UUID ownerId, int id, int size) {
|
||||||
|
this.ownerId = ownerId;
|
||||||
|
this.id = id;
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This constructor loads an existing Backpack
|
* This constructor loads an existing Backpack
|
||||||
*
|
*
|
||||||
@ -48,7 +60,10 @@ public class PlayerBackpack {
|
|||||||
* The {@link PlayerProfile} of this Backpack
|
* The {@link PlayerProfile} of this Backpack
|
||||||
* @param id
|
* @param id
|
||||||
* The id of this Backpack
|
* The id of this Backpack
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link PlayerBackpack#load(UUID, int, int, HashMap)} instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public PlayerBackpack(@Nonnull PlayerProfile profile, int id) {
|
public PlayerBackpack(@Nonnull PlayerProfile profile, int id) {
|
||||||
this(profile, id, profile.getConfig().getInt(CONFIG_PREFIX + id + ".size"));
|
this(profile, id, profile.getConfig().getInt(CONFIG_PREFIX + id + ".size"));
|
||||||
|
|
||||||
@ -66,12 +81,16 @@ public class PlayerBackpack {
|
|||||||
* The id of this Backpack
|
* The id of this Backpack
|
||||||
* @param size
|
* @param size
|
||||||
* The size of this Backpack
|
* The size of this Backpack
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link PlayerBackpack#newBackpack(UUID, int, int)} instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public PlayerBackpack(@Nonnull PlayerProfile profile, int id, int size) {
|
public PlayerBackpack(@Nonnull PlayerProfile profile, int id, int size) {
|
||||||
if (size < 9 || size > 54 || size % 9 != 0) {
|
if (size < 9 || size > 54 || size % 9 != 0) {
|
||||||
throw new IllegalArgumentException("Invalid size! Size must be one of: [9, 18, 27, 36, 45, 54]");
|
throw new IllegalArgumentException("Invalid size! Size must be one of: [9, 18, 27, 36, 45, 54]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.ownerId = profile.getUUID();
|
||||||
this.profile = profile;
|
this.profile = profile;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.cfg = profile.getConfig();
|
this.cfg = profile.getConfig();
|
||||||
@ -96,10 +115,17 @@ public class PlayerBackpack {
|
|||||||
* This method returns the {@link PlayerProfile} this {@link PlayerBackpack} belongs to
|
* This method returns the {@link PlayerProfile} this {@link PlayerBackpack} belongs to
|
||||||
*
|
*
|
||||||
* @return The owning {@link PlayerProfile}
|
* @return The owning {@link PlayerProfile}
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link PlayerBackpack#getOwnerId()} instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public PlayerProfile getOwner() {
|
public PlayerProfile getOwner() {
|
||||||
return profile;
|
return profile != null ? profile : PlayerProfile.find(Bukkit.getOfflinePlayer(ownerId)).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getOwnerId() {
|
||||||
|
return this.ownerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -172,7 +198,6 @@ public class PlayerBackpack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.size = size;
|
this.size = size;
|
||||||
cfg.setValue(CONFIG_PREFIX + id + ".size", size);
|
|
||||||
|
|
||||||
Inventory inv = Bukkit.createInventory(null, size, "Backpack [" + size + " Slots]");
|
Inventory inv = Bukkit.createInventory(null, size, "Backpack [" + size + " Slots]");
|
||||||
|
|
||||||
@ -187,7 +212,10 @@ public class PlayerBackpack {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This method will save the contents of this backpack to a {@link File}.
|
* This method will save the contents of this backpack to a {@link File}.
|
||||||
|
*
|
||||||
|
* @deprecated Handled by {@link PlayerProfile#save()} now
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void save() {
|
public void save() {
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
cfg.setValue(CONFIG_PREFIX + id + ".contents." + i, inventory.getItem(i));
|
cfg.setValue(CONFIG_PREFIX + id + ".contents." + i, inventory.getItem(i));
|
||||||
@ -199,7 +227,40 @@ public class PlayerBackpack {
|
|||||||
* using {@link PlayerBackpack#save()}
|
* using {@link PlayerBackpack#save()}
|
||||||
*/
|
*/
|
||||||
public void markDirty() {
|
public void markDirty() {
|
||||||
|
if (profile != null) {
|
||||||
profile.markDirty();
|
profile.markDirty();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setContents(int size, HashMap<Integer, ItemStack> contents) {
|
||||||
|
if (this.inventory == null) {
|
||||||
|
this.inventory = Bukkit.createInventory(null, size, "Backpack [" + size + " Slots]");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
this.inventory.setItem(i, contents.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParametersAreNonnullByDefault
|
||||||
|
public static PlayerBackpack load(UUID ownerId, int id, int size, HashMap<Integer, ItemStack> contents) {
|
||||||
|
PlayerBackpack backpack = new PlayerBackpack(ownerId, id, size);
|
||||||
|
|
||||||
|
backpack.setContents(size, contents);
|
||||||
|
|
||||||
|
return backpack;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParametersAreNonnullByDefault
|
||||||
|
public static PlayerBackpack newBackpack(UUID ownerId, int id, int size) {
|
||||||
|
if (size < 9 || size > 54 || size % 9 != 0) {
|
||||||
|
throw new IllegalArgumentException("Invalid size! Size must be one of: [9, 18, 27, 36, 45, 54]");
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerBackpack backpack = new PlayerBackpack(ownerId, id, size);
|
||||||
|
|
||||||
|
backpack.setContents(size, new HashMap<>());
|
||||||
|
|
||||||
|
return backpack;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,22 @@
|
|||||||
package io.github.thebusybiscuit.slimefun4.api.player;
|
package io.github.thebusybiscuit.slimefun4.api.player;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import io.github.thebusybiscuit.slimefun4.storage.data.PlayerData;
|
||||||
import org.apache.commons.lang.Validate;
|
import org.apache.commons.lang.Validate;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.ChatColor;
|
import org.bukkit.ChatColor;
|
||||||
import org.bukkit.Location;
|
|
||||||
import org.bukkit.NamespacedKey;
|
import org.bukkit.NamespacedKey;
|
||||||
import org.bukkit.OfflinePlayer;
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
@ -59,50 +54,26 @@ import io.github.thebusybiscuit.slimefun4.utils.NumberUtils;
|
|||||||
*/
|
*/
|
||||||
public class PlayerProfile {
|
public class PlayerProfile {
|
||||||
|
|
||||||
private final UUID uuid;
|
private final UUID ownerId;
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
private final Config configFile;
|
private final Config configFile;
|
||||||
private final Config waypointsFile;
|
|
||||||
|
|
||||||
private boolean dirty = false;
|
private boolean dirty = false;
|
||||||
private boolean markedForDeletion = false;
|
private boolean markedForDeletion = false;
|
||||||
|
|
||||||
private final Set<Research> researches = new HashSet<>();
|
|
||||||
private final List<Waypoint> waypoints = new ArrayList<>();
|
|
||||||
private final Map<Integer, PlayerBackpack> backpacks = new HashMap<>();
|
|
||||||
private final GuideHistory guideHistory = new GuideHistory(this);
|
private final GuideHistory guideHistory = new GuideHistory(this);
|
||||||
|
|
||||||
private final HashedArmorpiece[] armor = { new HashedArmorpiece(), new HashedArmorpiece(), new HashedArmorpiece(), new HashedArmorpiece() };
|
private final HashedArmorpiece[] armor = { new HashedArmorpiece(), new HashedArmorpiece(), new HashedArmorpiece(), new HashedArmorpiece() };
|
||||||
|
|
||||||
protected PlayerProfile(@Nonnull OfflinePlayer p) {
|
private final PlayerData data;
|
||||||
this.uuid = p.getUniqueId();
|
|
||||||
|
protected PlayerProfile(@Nonnull OfflinePlayer p, PlayerData data) {
|
||||||
|
this.ownerId = p.getUniqueId();
|
||||||
this.name = p.getName();
|
this.name = p.getName();
|
||||||
|
this.data = data;
|
||||||
|
|
||||||
configFile = new Config("data-storage/Slimefun/Players/" + uuid.toString() + ".yml");
|
configFile = new Config("data-storage/Slimefun/Players/" + ownerId.toString() + ".yml");
|
||||||
waypointsFile = new Config("data-storage/Slimefun/waypoints/" + uuid.toString() + ".yml");
|
|
||||||
|
|
||||||
loadProfileData();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadProfileData() {
|
|
||||||
for (Research research : Slimefun.getRegistry().getResearches()) {
|
|
||||||
if (configFile.contains("researches." + research.getID())) {
|
|
||||||
researches.add(research);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String key : waypointsFile.getKeys()) {
|
|
||||||
try {
|
|
||||||
if (waypointsFile.contains(key + ".world") && Bukkit.getWorld(waypointsFile.getString(key + ".world")) != null) {
|
|
||||||
String waypointName = waypointsFile.getString(key + ".name");
|
|
||||||
Location loc = waypointsFile.getLocation(key);
|
|
||||||
waypoints.add(new Waypoint(this, key, loc, waypointName));
|
|
||||||
}
|
|
||||||
} catch (Exception x) {
|
|
||||||
Slimefun.logger().log(Level.WARNING, x, () -> "Could not load Waypoint \"" + key + "\" for Player \"" + name + '"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,7 +102,7 @@ public class PlayerProfile {
|
|||||||
* @return The {@link UUID} of our {@link PlayerProfile}
|
* @return The {@link UUID} of our {@link PlayerProfile}
|
||||||
*/
|
*/
|
||||||
public @Nonnull UUID getUUID() {
|
public @Nonnull UUID getUUID() {
|
||||||
return uuid;
|
return ownerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -157,12 +128,7 @@ public class PlayerProfile {
|
|||||||
* This method will save the Player's Researches and Backpacks to the hard drive
|
* This method will save the Player's Researches and Backpacks to the hard drive
|
||||||
*/
|
*/
|
||||||
public void save() {
|
public void save() {
|
||||||
for (PlayerBackpack backpack : backpacks.values()) {
|
Slimefun.getPlayerStorage().savePlayerData(this.ownerId, this.data);
|
||||||
backpack.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
waypointsFile.save();
|
|
||||||
configFile.save();
|
|
||||||
dirty = false;
|
dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,11 +146,9 @@ public class PlayerProfile {
|
|||||||
dirty = true;
|
dirty = true;
|
||||||
|
|
||||||
if (unlock) {
|
if (unlock) {
|
||||||
configFile.setValue("researches." + research.getID(), true);
|
data.addResearch(research);
|
||||||
researches.add(research);
|
|
||||||
} else {
|
} else {
|
||||||
configFile.setValue("researches." + research.getID(), null);
|
data.removeResearch(research);
|
||||||
researches.remove(research);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,7 +166,7 @@ public class PlayerProfile {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !research.isEnabled() || researches.contains(research);
|
return !research.isEnabled() || data.getResearches().contains(research);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -228,7 +192,7 @@ public class PlayerProfile {
|
|||||||
* @return A {@code Hashset<Research>} of all Researches this {@link Player} has unlocked
|
* @return A {@code Hashset<Research>} of all Researches this {@link Player} has unlocked
|
||||||
*/
|
*/
|
||||||
public @Nonnull Set<Research> getResearches() {
|
public @Nonnull Set<Research> getResearches() {
|
||||||
return ImmutableSet.copyOf(researches);
|
return ImmutableSet.copyOf(this.data.getResearches());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -238,7 +202,7 @@ public class PlayerProfile {
|
|||||||
* @return A {@link List} containing every {@link Waypoint}
|
* @return A {@link List} containing every {@link Waypoint}
|
||||||
*/
|
*/
|
||||||
public @Nonnull List<Waypoint> getWaypoints() {
|
public @Nonnull List<Waypoint> getWaypoints() {
|
||||||
return ImmutableList.copyOf(waypoints);
|
return ImmutableList.copyOf(this.data.getWaypoints());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -249,22 +213,9 @@ public class PlayerProfile {
|
|||||||
* The {@link Waypoint} to add
|
* The {@link Waypoint} to add
|
||||||
*/
|
*/
|
||||||
public void addWaypoint(@Nonnull Waypoint waypoint) {
|
public void addWaypoint(@Nonnull Waypoint waypoint) {
|
||||||
Validate.notNull(waypoint, "Cannot add a 'null' waypoint!");
|
this.data.addWaypoint(waypoint);
|
||||||
|
|
||||||
for (Waypoint wp : waypoints) {
|
|
||||||
if (wp.getId().equals(waypoint.getId())) {
|
|
||||||
throw new IllegalArgumentException("A Waypoint with that id already exists for this Player");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (waypoints.size() < 21) {
|
|
||||||
waypoints.add(waypoint);
|
|
||||||
|
|
||||||
waypointsFile.setValue(waypoint.getId(), waypoint.getLocation());
|
|
||||||
waypointsFile.setValue(waypoint.getId() + ".name", waypoint.getName());
|
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This removes the given {@link Waypoint} from the {@link List} of {@link Waypoint Waypoints}
|
* This removes the given {@link Waypoint} from the {@link List} of {@link Waypoint Waypoints}
|
||||||
@ -274,13 +225,9 @@ public class PlayerProfile {
|
|||||||
* The {@link Waypoint} to remove
|
* The {@link Waypoint} to remove
|
||||||
*/
|
*/
|
||||||
public void removeWaypoint(@Nonnull Waypoint waypoint) {
|
public void removeWaypoint(@Nonnull Waypoint waypoint) {
|
||||||
Validate.notNull(waypoint, "Cannot remove a 'null' waypoint!");
|
this.data.removeWaypoint(waypoint);
|
||||||
|
|
||||||
if (waypoints.remove(waypoint)) {
|
|
||||||
waypointsFile.setValue(waypoint.getId(), null);
|
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call this method if the Player has left.
|
* Call this method if the Player has left.
|
||||||
@ -301,8 +248,10 @@ public class PlayerProfile {
|
|||||||
IntStream stream = IntStream.iterate(0, i -> i + 1).filter(i -> !configFile.contains("backpacks." + i + ".size"));
|
IntStream stream = IntStream.iterate(0, i -> i + 1).filter(i -> !configFile.contains("backpacks." + i + ".size"));
|
||||||
int id = stream.findFirst().getAsInt();
|
int id = stream.findFirst().getAsInt();
|
||||||
|
|
||||||
PlayerBackpack backpack = new PlayerBackpack(this, id, size);
|
PlayerBackpack backpack = PlayerBackpack.newBackpack(this.ownerId, id, size);
|
||||||
backpacks.put(id, backpack);
|
this.data.addBackpack(backpack);
|
||||||
|
|
||||||
|
markDirty();
|
||||||
|
|
||||||
return backpack;
|
return backpack;
|
||||||
}
|
}
|
||||||
@ -312,13 +261,10 @@ public class PlayerProfile {
|
|||||||
throw new IllegalArgumentException("Backpacks cannot have negative ids!");
|
throw new IllegalArgumentException("Backpacks cannot have negative ids!");
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerBackpack backpack = backpacks.get(id);
|
PlayerBackpack backpack = data.getBackpack(id);
|
||||||
|
|
||||||
if (backpack != null) {
|
if (backpack != null) {
|
||||||
return Optional.of(backpack);
|
markDirty();
|
||||||
} else if (configFile.contains("backpacks." + id + ".size")) {
|
|
||||||
backpack = new PlayerBackpack(this, id);
|
|
||||||
backpacks.put(id, backpack);
|
|
||||||
return Optional.of(backpack);
|
return Optional.of(backpack);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,7 +292,7 @@ public class PlayerProfile {
|
|||||||
List<String> titles = Slimefun.getRegistry().getResearchRanks();
|
List<String> titles = Slimefun.getRegistry().getResearchRanks();
|
||||||
|
|
||||||
int allResearches = countNonEmptyResearches(Slimefun.getRegistry().getResearches());
|
int allResearches = countNonEmptyResearches(Slimefun.getRegistry().getResearches());
|
||||||
float fraction = (float) countNonEmptyResearches(researches) / allResearches;
|
float fraction = (float) countNonEmptyResearches(getResearches()) / allResearches;
|
||||||
int index = (int) (fraction * (titles.size() - 1));
|
int index = (int) (fraction * (titles.size() - 1));
|
||||||
|
|
||||||
return titles.get(index);
|
return titles.get(index);
|
||||||
@ -420,7 +366,9 @@ public class PlayerProfile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(Slimefun.instance(), () -> {
|
Bukkit.getScheduler().runTaskAsynchronously(Slimefun.instance(), () -> {
|
||||||
AsyncProfileLoadEvent event = new AsyncProfileLoadEvent(new PlayerProfile(p));
|
PlayerData data = Slimefun.getPlayerStorage().loadPlayerData(p.getUniqueId());
|
||||||
|
|
||||||
|
AsyncProfileLoadEvent event = new AsyncProfileLoadEvent(new PlayerProfile(p, data));
|
||||||
Bukkit.getPluginManager().callEvent(event);
|
Bukkit.getPluginManager().callEvent(event);
|
||||||
|
|
||||||
Slimefun.getRegistry().getPlayerProfiles().put(uuid, event.getProfile());
|
Slimefun.getRegistry().getPlayerProfiles().put(uuid, event.getProfile());
|
||||||
@ -445,7 +393,9 @@ public class PlayerProfile {
|
|||||||
if (!Slimefun.getRegistry().getPlayerProfiles().containsKey(p.getUniqueId())) {
|
if (!Slimefun.getRegistry().getPlayerProfiles().containsKey(p.getUniqueId())) {
|
||||||
// Should probably prevent multiple requests for the same profile in the future
|
// Should probably prevent multiple requests for the same profile in the future
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(Slimefun.instance(), () -> {
|
Bukkit.getScheduler().runTaskAsynchronously(Slimefun.instance(), () -> {
|
||||||
PlayerProfile pp = new PlayerProfile(p);
|
PlayerData data = Slimefun.getPlayerStorage().loadPlayerData(p.getUniqueId());
|
||||||
|
|
||||||
|
PlayerProfile pp = new PlayerProfile(p, data);
|
||||||
Slimefun.getRegistry().getPlayerProfiles().put(p.getUniqueId(), pp);
|
Slimefun.getRegistry().getPlayerProfiles().put(p.getUniqueId(), pp);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -527,19 +477,23 @@ public class PlayerProfile {
|
|||||||
return armorCount == 4;
|
return armorCount == 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PlayerData getPlayerData() {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return uuid.hashCode();
|
return ownerId.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
return obj instanceof PlayerProfile profile && uuid.equals(profile.uuid);
|
return obj instanceof PlayerProfile profile && ownerId.equals(profile.ownerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "PlayerProfile {" + uuid + "}";
|
return "PlayerProfile {" + ownerId + "}";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,9 @@ import javax.annotation.Nonnull;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.ParametersAreNonnullByDefault;
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
|
||||||
|
import io.github.thebusybiscuit.slimefun4.storage.Storage;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.storage.backend.legacy.LegacyStorage;
|
||||||
|
|
||||||
import org.apache.commons.lang.Validate;
|
import org.apache.commons.lang.Validate;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
@ -197,6 +200,9 @@ public final class Slimefun extends JavaPlugin implements SlimefunAddon {
|
|||||||
private final Config items = new Config(this, "Items.yml");
|
private final Config items = new Config(this, "Items.yml");
|
||||||
private final Config researches = new Config(this, "Researches.yml");
|
private final Config researches = new Config(this, "Researches.yml");
|
||||||
|
|
||||||
|
// Data storage
|
||||||
|
private Storage playerStorage;
|
||||||
|
|
||||||
// Listeners that need to be accessed elsewhere
|
// Listeners that need to be accessed elsewhere
|
||||||
private final GrapplingHookListener grapplingHookListener = new GrapplingHookListener();
|
private final GrapplingHookListener grapplingHookListener = new GrapplingHookListener();
|
||||||
private final BackpackListener backpackListener = new BackpackListener();
|
private final BackpackListener backpackListener = new BackpackListener();
|
||||||
@ -258,6 +264,9 @@ public final class Slimefun extends JavaPlugin implements SlimefunAddon {
|
|||||||
registry.load(this, config);
|
registry.load(this, config);
|
||||||
loadTags();
|
loadTags();
|
||||||
soundService.reload(false);
|
soundService.reload(false);
|
||||||
|
// TODO: What do we do if tests want to use another storage backend (e.g. testing new feature on legacy + sql)?
|
||||||
|
// Do we have a way to override this?
|
||||||
|
playerStorage = new LegacyStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -312,6 +321,10 @@ public final class Slimefun extends JavaPlugin implements SlimefunAddon {
|
|||||||
|
|
||||||
networkManager = new NetworkManager(networkSize, config.getBoolean("networks.enable-visualizer"), config.getBoolean("networks.delete-excess-items"));
|
networkManager = new NetworkManager(networkSize, config.getBoolean("networks.enable-visualizer"), config.getBoolean("networks.delete-excess-items"));
|
||||||
|
|
||||||
|
// Data storage
|
||||||
|
playerStorage = new LegacyStorage();
|
||||||
|
logger.log(Level.INFO, "Using legacy storage for player data");
|
||||||
|
|
||||||
// Setting up bStats
|
// Setting up bStats
|
||||||
new Thread(metricsService::start, "Slimefun Metrics").start();
|
new Thread(metricsService::start, "Slimefun Metrics").start();
|
||||||
|
|
||||||
@ -1068,4 +1081,7 @@ public final class Slimefun extends JavaPlugin implements SlimefunAddon {
|
|||||||
return instance.getServer().getScheduler().runTask(instance, runnable);
|
return instance.getServer().getScheduler().runTask(instance, runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @Nonnull Storage getPlayerStorage() {
|
||||||
|
return instance().playerStorage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package io.github.thebusybiscuit.slimefun4.storage;
|
||||||
|
|
||||||
|
import io.github.thebusybiscuit.slimefun4.storage.data.PlayerData;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
import com.google.common.annotations.Beta;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Storage} interface is the abstract layer on top of our storage backends.
|
||||||
|
* Every backend has to implement this interface and has to implement it in a thread-safe way.
|
||||||
|
* There will be no expectation of running functions in here within the main thread.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* <b>This API is still experimental, it may change without notice.</b>
|
||||||
|
*/
|
||||||
|
@Beta
|
||||||
|
@ThreadSafe
|
||||||
|
public interface Storage {
|
||||||
|
|
||||||
|
PlayerData loadPlayerData(UUID uuid);
|
||||||
|
|
||||||
|
void savePlayerData(UUID uuid, PlayerData data);
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
package io.github.thebusybiscuit.slimefun4.storage.backend.legacy;
|
||||||
|
|
||||||
|
import io.github.bakedlibs.dough.config.Config;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.api.gps.Waypoint;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.api.player.PlayerBackpack;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.api.researches.Research;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.implementation.Slimefun;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.storage.Storage;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.storage.data.PlayerData;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
|
import com.google.common.annotations.Beta;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
@Beta
|
||||||
|
public class LegacyStorage implements Storage {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PlayerData loadPlayerData(@Nonnull UUID uuid) {
|
||||||
|
Config playerFile = new Config("data-storage/Slimefun/Players/" + uuid + ".yml");
|
||||||
|
// Not too sure why this is its own file
|
||||||
|
Config waypointsFile = new Config("data-storage/Slimefun/waypoints/" + uuid + ".yml");
|
||||||
|
|
||||||
|
// Load research
|
||||||
|
Set<Research> researches = new HashSet<>();
|
||||||
|
for (Research research : Slimefun.getRegistry().getResearches()) {
|
||||||
|
if (playerFile.contains("researches." + research.getID())) {
|
||||||
|
researches.add(research);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load backpacks
|
||||||
|
HashMap<Integer, PlayerBackpack> backpacks = new HashMap<>();
|
||||||
|
for (String key : playerFile.getKeys("backpacks")) {
|
||||||
|
try {
|
||||||
|
int id = Integer.parseInt(key);
|
||||||
|
int size = playerFile.getInt("backpacks." + key + ".size");
|
||||||
|
|
||||||
|
HashMap<Integer, ItemStack> items = new HashMap<>();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
items.put(i, playerFile.getItem("backpacks." + key + ".contents." + i));
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerBackpack backpack = PlayerBackpack.load(uuid, id, size, items);
|
||||||
|
|
||||||
|
backpacks.put(id, backpack);
|
||||||
|
} catch (Exception x) {
|
||||||
|
Slimefun.logger().log(Level.WARNING, x, () -> "Could not load Backpack \"" + key + "\" for Player \"" + uuid + '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load waypoints
|
||||||
|
Set<Waypoint> waypoints = new HashSet<>();
|
||||||
|
for (String key : waypointsFile.getKeys()) {
|
||||||
|
try {
|
||||||
|
if (waypointsFile.contains(key + ".world") && Bukkit.getWorld(waypointsFile.getString(key + ".world")) != null) {
|
||||||
|
String waypointName = waypointsFile.getString(key + ".name");
|
||||||
|
Location loc = waypointsFile.getLocation(key);
|
||||||
|
waypoints.add(new Waypoint(uuid, key, loc, waypointName));
|
||||||
|
}
|
||||||
|
} catch (Exception x) {
|
||||||
|
Slimefun.logger().log(Level.WARNING, x, () -> "Could not load Waypoint \"" + key + "\" for Player \"" + uuid + '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PlayerData(researches, backpacks, waypoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The current design of saving all at once isn't great, this will be refined.
|
||||||
|
@Override
|
||||||
|
public void savePlayerData(@Nonnull UUID uuid, @Nonnull PlayerData data) {
|
||||||
|
Config playerFile = new Config("data-storage/Slimefun/Players/" + uuid + ".yml");
|
||||||
|
// Not too sure why this is its own file
|
||||||
|
Config waypointsFile = new Config("data-storage/Slimefun/waypoints/" + uuid + ".yml");
|
||||||
|
|
||||||
|
// Save research
|
||||||
|
playerFile.setValue("rearches", null);
|
||||||
|
for (Research research : Slimefun.getRegistry().getResearches()) {
|
||||||
|
// Save the research if it's researched
|
||||||
|
if (data.getResearches().contains(research)) {
|
||||||
|
playerFile.setValue("researches." + research.getID(), true);
|
||||||
|
|
||||||
|
// Remove the research if it's no longer researched
|
||||||
|
} else if (playerFile.contains("researches." + research.getID())) {
|
||||||
|
playerFile.setValue("researches." + research.getID(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save backpacks
|
||||||
|
for (PlayerBackpack backpack : data.getBackpacks().values()) {
|
||||||
|
playerFile.setValue("backpacks." + backpack.getId() + ".size", backpack.getSize());
|
||||||
|
|
||||||
|
for (int i = 0; i < backpack.getSize(); i++) {
|
||||||
|
ItemStack item = backpack.getInventory().getItem(i);
|
||||||
|
if (item != null) {
|
||||||
|
playerFile.setValue("backpacks." + backpack.getId() + ".contents." + i, item);
|
||||||
|
|
||||||
|
// Remove the item if it's no longer in the inventory
|
||||||
|
} else if (playerFile.contains("backpacks." + backpack.getId() + ".contents." + i)) {
|
||||||
|
playerFile.setValue("backpacks." + backpack.getId() + ".contents." + i, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save waypoints
|
||||||
|
waypointsFile.clear();
|
||||||
|
for (Waypoint waypoint : data.getWaypoints()) {
|
||||||
|
// Legacy data uses IDs
|
||||||
|
waypointsFile.setValue(waypoint.getId(), waypoint.getLocation());
|
||||||
|
waypointsFile.setValue(waypoint.getId() + ".name", waypoint.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save files
|
||||||
|
playerFile.save();
|
||||||
|
waypointsFile.save();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
package io.github.thebusybiscuit.slimefun4.storage.data;
|
||||||
|
|
||||||
|
import com.google.common.annotations.Beta;
|
||||||
|
|
||||||
|
import io.github.thebusybiscuit.slimefun4.api.gps.Waypoint;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.api.player.PlayerBackpack;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.api.researches.Research;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.Validate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data which backs {@link io.github.thebusybiscuit.slimefun4.api.player.PlayerProfile}
|
||||||
|
*
|
||||||
|
* <b>This API is still experimental, it may change without notice.</b>
|
||||||
|
*/
|
||||||
|
// TODO: Should we keep this in PlayerProfile?
|
||||||
|
@Beta
|
||||||
|
public class PlayerData {
|
||||||
|
|
||||||
|
private final Set<Research> researches = new HashSet<>();
|
||||||
|
private final Map<Integer, PlayerBackpack> backpacks = new HashMap<>();
|
||||||
|
private final Set<Waypoint> waypoints = new HashSet<>();
|
||||||
|
|
||||||
|
public PlayerData(Set<Research> researches, Map<Integer, PlayerBackpack> backpacks, Set<Waypoint> waypoints) {
|
||||||
|
this.researches.addAll(researches);
|
||||||
|
this.backpacks.putAll(backpacks);
|
||||||
|
this.waypoints.addAll(waypoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Research> getResearches() {
|
||||||
|
return researches;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addResearch(@Nonnull Research research) {
|
||||||
|
Validate.notNull(research, "Cannot add a 'null' research!");
|
||||||
|
researches.add(research);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeResearch(@Nonnull Research research) {
|
||||||
|
Validate.notNull(research, "Cannot remove a 'null' research!");
|
||||||
|
researches.remove(research);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public Map<Integer, PlayerBackpack> getBackpacks() {
|
||||||
|
return backpacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public PlayerBackpack getBackpack(int id) {
|
||||||
|
return backpacks.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addBackpack(@Nonnull PlayerBackpack backpack) {
|
||||||
|
Validate.notNull(backpack, "Cannot add a 'null' backpack!");
|
||||||
|
backpacks.put(backpack.getId(), backpack);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeBackpack(@Nonnull PlayerBackpack backpack) {
|
||||||
|
Validate.notNull(backpack, "Cannot remove a 'null' backpack!");
|
||||||
|
backpacks.remove(backpack.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Waypoint> getWaypoints() {
|
||||||
|
return waypoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addWaypoint(@Nonnull Waypoint waypoint) {
|
||||||
|
Validate.notNull(waypoint, "Cannot add a 'null' waypoint!");
|
||||||
|
|
||||||
|
for (Waypoint wp : waypoints) {
|
||||||
|
if (wp.getId().equals(waypoint.getId())) {
|
||||||
|
throw new IllegalArgumentException("A Waypoint with that id already exists for this Player");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limited to 21 due to limited UI space and no pagination
|
||||||
|
if (waypoints.size() >= 21) {
|
||||||
|
return; // not sure why this doesn't throw but the one above does...
|
||||||
|
}
|
||||||
|
|
||||||
|
waypoints.add(waypoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeWaypoint(@Nonnull Waypoint waypoint) {
|
||||||
|
Validate.notNull(waypoint, "Cannot remove a 'null' waypoint!");
|
||||||
|
waypoints.remove(waypoint);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,9 @@
|
|||||||
package io.github.thebusybiscuit.slimefun4.api.gps;
|
package io.github.thebusybiscuit.slimefun4.api.gps;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
@ -19,16 +23,20 @@ class TestWaypoints {
|
|||||||
|
|
||||||
private static ServerMock server;
|
private static ServerMock server;
|
||||||
private static Slimefun plugin;
|
private static Slimefun plugin;
|
||||||
|
private static File dataFolder;
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void load() {
|
public static void load() {
|
||||||
server = MockBukkit.mock();
|
server = MockBukkit.mock();
|
||||||
plugin = MockBukkit.load(Slimefun.class);
|
plugin = MockBukkit.load(Slimefun.class);
|
||||||
|
dataFolder = new File("data-storage/Slimefun/waypoints");
|
||||||
|
dataFolder.mkdirs();
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterAll
|
@AfterAll
|
||||||
public static void unload() {
|
public static void unload() throws IOException {
|
||||||
MockBukkit.unmock();
|
MockBukkit.unmock();
|
||||||
|
FileUtils.deleteDirectory(dataFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -38,9 +46,8 @@ class TestWaypoints {
|
|||||||
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
||||||
|
|
||||||
Assertions.assertTrue(profile.getWaypoints().isEmpty());
|
Assertions.assertTrue(profile.getWaypoints().isEmpty());
|
||||||
Waypoint waypoint = new Waypoint(profile, "hello", player.getLocation(), "HELLO");
|
Waypoint waypoint = new Waypoint(player.getUniqueId(), "hello", player.getLocation(), "HELLO");
|
||||||
profile.addWaypoint(waypoint);
|
profile.addWaypoint(waypoint);
|
||||||
Assertions.assertTrue(profile.isDirty());
|
|
||||||
|
|
||||||
Assertions.assertThrows(IllegalArgumentException.class, () -> profile.addWaypoint(null));
|
Assertions.assertThrows(IllegalArgumentException.class, () -> profile.addWaypoint(null));
|
||||||
|
|
||||||
@ -55,7 +62,7 @@ class TestWaypoints {
|
|||||||
Player player = server.addPlayer();
|
Player player = server.addPlayer();
|
||||||
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
||||||
|
|
||||||
Waypoint waypoint = new Waypoint(profile, "hello", player.getLocation(), "HELLO");
|
Waypoint waypoint = new Waypoint(player.getUniqueId(), "hello", player.getLocation(), "HELLO");
|
||||||
profile.addWaypoint(waypoint);
|
profile.addWaypoint(waypoint);
|
||||||
Assertions.assertEquals(1, profile.getWaypoints().size());
|
Assertions.assertEquals(1, profile.getWaypoints().size());
|
||||||
|
|
||||||
@ -76,7 +83,7 @@ class TestWaypoints {
|
|||||||
Player player = server.addPlayer();
|
Player player = server.addPlayer();
|
||||||
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
||||||
|
|
||||||
Waypoint waypoint = new Waypoint(profile, "test", player.getLocation(), "Testing");
|
Waypoint waypoint = new Waypoint(player.getUniqueId(), "test", player.getLocation(), "Testing");
|
||||||
profile.addWaypoint(waypoint);
|
profile.addWaypoint(waypoint);
|
||||||
|
|
||||||
Assertions.assertEquals(1, profile.getWaypoints().size());
|
Assertions.assertEquals(1, profile.getWaypoints().size());
|
||||||
@ -91,7 +98,7 @@ class TestWaypoints {
|
|||||||
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
||||||
|
|
||||||
for (int i = 0; i < 99; i++) {
|
for (int i = 0; i < 99; i++) {
|
||||||
Waypoint waypoint = new Waypoint(profile, String.valueOf(i), player.getLocation(), "Test");
|
Waypoint waypoint = new Waypoint(player.getUniqueId(), String.valueOf(i), player.getLocation(), "Test");
|
||||||
profile.addWaypoint(waypoint);
|
profile.addWaypoint(waypoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,11 +121,10 @@ class TestWaypoints {
|
|||||||
@DisplayName("Test equal Waypoints being equal")
|
@DisplayName("Test equal Waypoints being equal")
|
||||||
void testWaypointComparison() throws InterruptedException {
|
void testWaypointComparison() throws InterruptedException {
|
||||||
Player player = server.addPlayer();
|
Player player = server.addPlayer();
|
||||||
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
|
||||||
|
|
||||||
Waypoint waypoint = new Waypoint(profile, "waypoint", player.getLocation(), "Test");
|
Waypoint waypoint = new Waypoint(player.getUniqueId(), "waypoint", player.getLocation(), "Test");
|
||||||
Waypoint same = new Waypoint(profile, "waypoint", player.getLocation(), "Test");
|
Waypoint same = new Waypoint(player.getUniqueId(), "waypoint", player.getLocation(), "Test");
|
||||||
Waypoint different = new Waypoint(profile, "waypoint_nope", player.getLocation(), "Test2");
|
Waypoint different = new Waypoint(player.getUniqueId(), "waypoint_nope", player.getLocation(), "Test2");
|
||||||
|
|
||||||
Assertions.assertEquals(waypoint, same);
|
Assertions.assertEquals(waypoint, same);
|
||||||
Assertions.assertEquals(waypoint.hashCode(), same.hashCode());
|
Assertions.assertEquals(waypoint.hashCode(), same.hashCode());
|
||||||
@ -131,10 +137,9 @@ class TestWaypoints {
|
|||||||
@DisplayName("Test Deathpoints being recognized as Deathpoints")
|
@DisplayName("Test Deathpoints being recognized as Deathpoints")
|
||||||
void testIsDeathpoint() throws InterruptedException {
|
void testIsDeathpoint() throws InterruptedException {
|
||||||
Player player = server.addPlayer();
|
Player player = server.addPlayer();
|
||||||
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
|
||||||
|
|
||||||
Waypoint waypoint = new Waypoint(profile, "waypoint", player.getLocation(), "Some Waypoint");
|
Waypoint waypoint = new Waypoint(player.getUniqueId(), "waypoint", player.getLocation(), "Some Waypoint");
|
||||||
Waypoint deathpoint = new Waypoint(profile, "deathpoint", player.getLocation(), "player:death I died");
|
Waypoint deathpoint = new Waypoint(player.getUniqueId(), "deathpoint", player.getLocation(), "player:death I died");
|
||||||
|
|
||||||
Assertions.assertFalse(waypoint.isDeathpoint());
|
Assertions.assertFalse(waypoint.isDeathpoint());
|
||||||
Assertions.assertTrue(deathpoint.isDeathpoint());
|
Assertions.assertTrue(deathpoint.isDeathpoint());
|
||||||
|
@ -2,9 +2,7 @@ package io.github.thebusybiscuit.slimefun4.api.profiles;
|
|||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.inventory.ItemStack;
|
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
@ -39,16 +37,12 @@ class TestPlayerBackpacks {
|
|||||||
void testCreateBackpack() throws InterruptedException {
|
void testCreateBackpack() throws InterruptedException {
|
||||||
Player player = server.addPlayer();
|
Player player = server.addPlayer();
|
||||||
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
||||||
Assertions.assertFalse(profile.isDirty());
|
|
||||||
|
|
||||||
PlayerBackpack backpack = profile.createBackpack(18);
|
PlayerBackpack backpack = profile.createBackpack(18);
|
||||||
|
|
||||||
Assertions.assertNotNull(backpack);
|
Assertions.assertNotNull(backpack);
|
||||||
|
|
||||||
// Creating a backpack should mark profiles as dirty
|
Assertions.assertEquals(player.getUniqueId(), backpack.getOwnerId());
|
||||||
Assertions.assertTrue(profile.isDirty());
|
|
||||||
|
|
||||||
Assertions.assertEquals(profile, backpack.getOwner());
|
|
||||||
Assertions.assertEquals(18, backpack.getSize());
|
Assertions.assertEquals(18, backpack.getSize());
|
||||||
Assertions.assertEquals(18, backpack.getInventory().getSize());
|
Assertions.assertEquals(18, backpack.getInventory().getSize());
|
||||||
}
|
}
|
||||||
@ -71,7 +65,6 @@ class TestPlayerBackpacks {
|
|||||||
backpack.setSize(27);
|
backpack.setSize(27);
|
||||||
|
|
||||||
Assertions.assertEquals(27, backpack.getSize());
|
Assertions.assertEquals(27, backpack.getSize());
|
||||||
Assertions.assertTrue(profile.isDirty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -90,33 +83,4 @@ class TestPlayerBackpacks {
|
|||||||
|
|
||||||
Assertions.assertFalse(profile.getBackpack(500).isPresent());
|
Assertions.assertFalse(profile.getBackpack(500).isPresent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("Test loading a backpack from file")
|
|
||||||
void testLoadBackpackFromFile() throws InterruptedException {
|
|
||||||
Player player = server.addPlayer();
|
|
||||||
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
|
||||||
|
|
||||||
profile.getConfig().setValue("backpacks.50.size", 27);
|
|
||||||
|
|
||||||
for (int i = 0; i < 27; i++) {
|
|
||||||
profile.getConfig().setValue("backpacks.50.contents." + i, new ItemStack(Material.DIAMOND));
|
|
||||||
}
|
|
||||||
|
|
||||||
Optional<PlayerBackpack> optional = profile.getBackpack(50);
|
|
||||||
Assertions.assertTrue(optional.isPresent());
|
|
||||||
|
|
||||||
PlayerBackpack backpack = optional.get();
|
|
||||||
Assertions.assertEquals(50, backpack.getId());
|
|
||||||
Assertions.assertEquals(27, backpack.getSize());
|
|
||||||
Assertions.assertEquals(-1, backpack.getInventory().firstEmpty());
|
|
||||||
|
|
||||||
backpack.getInventory().setItem(1, new ItemStack(Material.NETHER_STAR));
|
|
||||||
|
|
||||||
Assertions.assertEquals(new ItemStack(Material.DIAMOND), profile.getConfig().getItem("backpacks.50.contents.1"));
|
|
||||||
|
|
||||||
// Saving should write it to the Config file
|
|
||||||
backpack.save();
|
|
||||||
Assertions.assertEquals(new ItemStack(Material.NETHER_STAR), profile.getConfig().getItem("backpacks.50.contents.1"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@ import org.bukkit.entity.Player;
|
|||||||
import org.bukkit.event.inventory.ClickType;
|
import org.bukkit.event.inventory.ClickType;
|
||||||
import org.bukkit.event.inventory.InventoryAction;
|
import org.bukkit.event.inventory.InventoryAction;
|
||||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
|
||||||
import org.bukkit.event.inventory.InventoryType.SlotType;
|
import org.bukkit.event.inventory.InventoryType.SlotType;
|
||||||
import org.bukkit.event.player.PlayerDropItemEvent;
|
import org.bukkit.event.player.PlayerDropItemEvent;
|
||||||
import org.bukkit.inventory.Inventory;
|
import org.bukkit.inventory.Inventory;
|
||||||
@ -119,7 +118,7 @@ class TestBackpackListener {
|
|||||||
Assertions.assertEquals(ChatColor.GRAY + "ID: " + player.getUniqueId() + "#" + id, item.getItemMeta().getLore().get(2));
|
Assertions.assertEquals(ChatColor.GRAY + "ID: " + player.getUniqueId() + "#" + id, item.getItemMeta().getLore().get(2));
|
||||||
|
|
||||||
PlayerBackpack backpack = awaitBackpack(item);
|
PlayerBackpack backpack = awaitBackpack(item);
|
||||||
Assertions.assertEquals(player.getUniqueId(), backpack.getOwner().getUUID());
|
Assertions.assertEquals(player.getUniqueId(), backpack.getOwnerId());
|
||||||
Assertions.assertEquals(id, backpack.getId());
|
Assertions.assertEquals(id, backpack.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,16 +131,6 @@ class TestBackpackListener {
|
|||||||
Assertions.assertEquals(backpack.getInventory(), view.getTopInventory());
|
Assertions.assertEquals(backpack.getInventory(), view.getTopInventory());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("Test backpacks being marked dirty on close")
|
|
||||||
void testCloseBackpack() throws InterruptedException {
|
|
||||||
Player player = server.addPlayer();
|
|
||||||
PlayerBackpack backpack = openMockBackpack(player, "TEST_CLOSE_BACKPACK", 27);
|
|
||||||
listener.onClose(new InventoryCloseEvent(player.getOpenInventory()));
|
|
||||||
|
|
||||||
Assertions.assertTrue(backpack.getOwner().isDirty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("Test backpacks not disturbing normal item dropping")
|
@DisplayName("Test backpacks not disturbing normal item dropping")
|
||||||
void testBackpackDropNormalItem() throws InterruptedException {
|
void testBackpackDropNormalItem() throws InterruptedException {
|
||||||
|
@ -0,0 +1,383 @@
|
|||||||
|
package io.github.thebusybiscuit.slimefun4.storage.backend;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.WorldCreator;
|
||||||
|
import org.bukkit.World.Environment;
|
||||||
|
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import be.seeseemelk.mockbukkit.MockBukkit;
|
||||||
|
import be.seeseemelk.mockbukkit.ServerMock;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.api.gps.Waypoint;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.api.player.PlayerProfile;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.api.researches.Research;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.implementation.Slimefun;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.storage.backend.legacy.LegacyStorage;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.storage.data.PlayerData;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.test.TestUtilities;
|
||||||
|
import net.md_5.bungee.api.ChatColor;
|
||||||
|
|
||||||
|
class TestLegacyBackend {
|
||||||
|
|
||||||
|
private static ServerMock server;
|
||||||
|
private static Slimefun plugin;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void load() {
|
||||||
|
server = MockBukkit.mock();
|
||||||
|
plugin = MockBukkit.load(Slimefun.class);
|
||||||
|
|
||||||
|
File playerFolder = new File("data-storage/Slimefun/Players");
|
||||||
|
playerFolder.mkdirs();
|
||||||
|
File waypointFolder = new File("data-storage/Slimefun/waypoints");
|
||||||
|
waypointFolder.mkdirs();
|
||||||
|
|
||||||
|
// Not too sure why this is needed, we don't use it elsewhere, it should just use the ItemStack serialization
|
||||||
|
// My guess is MockBukkit isn't loading the ConfigurationSerialization class therefore the static block
|
||||||
|
// within the class isn't being fired (where ItemStack and other classes are registered)
|
||||||
|
ConfigurationSerialization.registerClass(ItemStack.class);
|
||||||
|
ConfigurationSerialization.registerClass(ItemMeta.class);
|
||||||
|
|
||||||
|
setupResearches();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public static void unload() throws IOException {
|
||||||
|
MockBukkit.unmock();
|
||||||
|
FileUtils.deleteDirectory(new File("data-storage"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test simple loading and saving of player data
|
||||||
|
@Test
|
||||||
|
void testLoadingResearches() throws IOException {
|
||||||
|
// Create a player file which we can load
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
File playerFile = new File("data-storage/Slimefun/Players/" + uuid + ".yml");
|
||||||
|
Files.writeString(playerFile.toPath(), """
|
||||||
|
researches:
|
||||||
|
'0': true
|
||||||
|
'1': true
|
||||||
|
'2': true
|
||||||
|
'3': true
|
||||||
|
'4': true
|
||||||
|
'5': true
|
||||||
|
'6': true
|
||||||
|
'7': true
|
||||||
|
'8': true
|
||||||
|
'9': true
|
||||||
|
""");
|
||||||
|
|
||||||
|
// Load the player data
|
||||||
|
LegacyStorage storage = new LegacyStorage();
|
||||||
|
PlayerData data = storage.loadPlayerData(uuid);
|
||||||
|
|
||||||
|
// Check if the data is correct
|
||||||
|
Assertions.assertEquals(10, data.getResearches().size());
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
Assertions.assertTrue(data.getResearches().contains(Slimefun.getRegistry().getResearches().get(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There's some issues with deserializing items in tests, I spent quite a while debugging this
|
||||||
|
// and didn't really get anywhere. So commenting this out for now.
|
||||||
|
/*
|
||||||
|
@Test
|
||||||
|
void testLoadingBackpacks() throws IOException {
|
||||||
|
// Create a player file which we can load
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
File playerFile = new File("data-storage/Slimefun/Players/" + uuid + ".yml");
|
||||||
|
Files.writeString(playerFile.toPath(), """
|
||||||
|
backpacks:
|
||||||
|
'0':
|
||||||
|
size: 9
|
||||||
|
contents:
|
||||||
|
'0':
|
||||||
|
==: org.bukkit.inventory.ItemStack
|
||||||
|
v: 1
|
||||||
|
type: IRON_BLOCK
|
||||||
|
meta:
|
||||||
|
==: org.bukkit.inventory.meta.ItemMeta
|
||||||
|
enchants: {}
|
||||||
|
damage: 0
|
||||||
|
persistentDataContainer:
|
||||||
|
slimefun:slimefun_item: TEST
|
||||||
|
displayName: §6Test block
|
||||||
|
itemFlags: !!set {}
|
||||||
|
unbreakable: false
|
||||||
|
repairCost: 0
|
||||||
|
""");
|
||||||
|
|
||||||
|
// Load the player data
|
||||||
|
LegacyStorage storage = new LegacyStorage();
|
||||||
|
PlayerData data = storage.loadPlayerData(uuid);
|
||||||
|
|
||||||
|
// Check if the data is correct
|
||||||
|
Assertions.assertEquals(1, data.getBackpacks().size());
|
||||||
|
Assertions.assertEquals(9, data.getBackpacks().get(0).getSize());
|
||||||
|
|
||||||
|
// Validate item deserialization
|
||||||
|
System.out.println(
|
||||||
|
Arrays.stream(data.getBackpack(0).getInventory().getContents())
|
||||||
|
.map((item) -> item == null ? "null" : item.getType().name())
|
||||||
|
.collect(Collectors.joining(", "))
|
||||||
|
);
|
||||||
|
ItemStack stack = data.getBackpack(0).getInventory().getItem(0);
|
||||||
|
Assertions.assertNotNull(stack);
|
||||||
|
Assertions.assertEquals("IRON_BLOCK", stack.getType().name());
|
||||||
|
Assertions.assertEquals(1, stack.getAmount());
|
||||||
|
Assertions.assertEquals(ChatColor.GREEN + "Test block", stack.getItemMeta().getDisplayName());
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLoadingWaypoints() throws IOException {
|
||||||
|
// Create mock world
|
||||||
|
server.createWorld(WorldCreator.name("world").environment(Environment.NORMAL));
|
||||||
|
|
||||||
|
// Create a player file which we can load
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
File waypointFile = new File("data-storage/Slimefun/waypoints/" + uuid + ".yml");
|
||||||
|
Files.writeString(waypointFile.toPath(), """
|
||||||
|
TEST:
|
||||||
|
x: -173.0
|
||||||
|
y: 75.0
|
||||||
|
z: -11.0
|
||||||
|
pitch: 0.0
|
||||||
|
yaw: 178.0
|
||||||
|
world: world
|
||||||
|
name: test
|
||||||
|
""");
|
||||||
|
|
||||||
|
// Load the player data
|
||||||
|
LegacyStorage storage = new LegacyStorage();
|
||||||
|
PlayerData data = storage.loadPlayerData(uuid);
|
||||||
|
|
||||||
|
// Check if the data is correct
|
||||||
|
Assertions.assertEquals(1, data.getWaypoints().size());
|
||||||
|
|
||||||
|
// Validate waypoint deserialization
|
||||||
|
Waypoint waypoint = data.getWaypoints().iterator().next();
|
||||||
|
|
||||||
|
Assertions.assertEquals("test", waypoint.getName());
|
||||||
|
Assertions.assertEquals(-173.0, waypoint.getLocation().getX());
|
||||||
|
Assertions.assertEquals(75.0, waypoint.getLocation().getY());
|
||||||
|
Assertions.assertEquals(-11.0, waypoint.getLocation().getZ());
|
||||||
|
Assertions.assertEquals(178.0, waypoint.getLocation().getYaw());
|
||||||
|
Assertions.assertEquals(0.0, waypoint.getLocation().getPitch());
|
||||||
|
Assertions.assertEquals("world", waypoint.getLocation().getWorld().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSavingResearches() throws InterruptedException {
|
||||||
|
// Create a player file which we can load
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
File playerFile = new File("data-storage/Slimefun/Players/" + uuid + ".yml");
|
||||||
|
|
||||||
|
OfflinePlayer player = Bukkit.getOfflinePlayer(uuid);
|
||||||
|
|
||||||
|
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
||||||
|
|
||||||
|
for (Research research : Slimefun.getRegistry().getResearches()) {
|
||||||
|
profile.setResearched(research, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the player data
|
||||||
|
LegacyStorage storage = new LegacyStorage();
|
||||||
|
storage.savePlayerData(uuid, profile.getPlayerData());
|
||||||
|
|
||||||
|
// Assert the file exists and data is correct
|
||||||
|
Assertions.assertTrue(playerFile.exists());
|
||||||
|
PlayerData assertion = storage.loadPlayerData(uuid);
|
||||||
|
Assertions.assertEquals(10, assertion.getResearches().size());
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
Assertions.assertTrue(assertion.getResearches().contains(Slimefun.getRegistry().getResearches().get(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There's some issues with deserializing items in tests, I spent quite a while debugging this
|
||||||
|
// and didn't really get anywhere. So commenting this out for now.
|
||||||
|
/*
|
||||||
|
@Test
|
||||||
|
void testSavingBackpacks() throws InterruptedException {
|
||||||
|
// Create a player file which we can load
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
File playerFile = new File("data-storage/Slimefun/Players/" + uuid + ".yml");
|
||||||
|
|
||||||
|
OfflinePlayer player = Bukkit.getOfflinePlayer(uuid);
|
||||||
|
|
||||||
|
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
||||||
|
|
||||||
|
PlayerBackpack backpack = profile.createBackpack(9);
|
||||||
|
backpack.getInventory().addItem(SlimefunItems.AIR_RUNE);
|
||||||
|
|
||||||
|
// Save the player data
|
||||||
|
LegacyStorage storage = new LegacyStorage();
|
||||||
|
storage.savePlayerData(uuid, profile.getPlayerData());
|
||||||
|
|
||||||
|
// Assert the file exists and data is correct
|
||||||
|
Assertions.assertTrue(playerFile.exists());
|
||||||
|
PlayerData assertion = storage.loadPlayerData(uuid);
|
||||||
|
Assertions.assertEquals(1, assertion.getBackpacks().size());
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSavingWaypoints() throws InterruptedException {
|
||||||
|
// Create mock world
|
||||||
|
World world = server.createWorld(WorldCreator.name("world").environment(Environment.NORMAL));
|
||||||
|
|
||||||
|
// Create a player file which we can load
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
File playerFile = new File("data-storage/Slimefun/Players/" + uuid + ".yml");
|
||||||
|
|
||||||
|
OfflinePlayer player = Bukkit.getOfflinePlayer(uuid);
|
||||||
|
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
||||||
|
|
||||||
|
profile.addWaypoint(new Waypoint(
|
||||||
|
player.getUniqueId(),
|
||||||
|
"test",
|
||||||
|
new Location(world, 1, 2, 3, 4, 5),
|
||||||
|
ChatColor.GREEN + "Test waypoint")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save the player data
|
||||||
|
LegacyStorage storage = new LegacyStorage();
|
||||||
|
storage.savePlayerData(uuid, profile.getPlayerData());
|
||||||
|
|
||||||
|
// Assert the file exists and data is correct
|
||||||
|
Assertions.assertTrue(playerFile.exists());
|
||||||
|
PlayerData assertion = storage.loadPlayerData(uuid);
|
||||||
|
Assertions.assertEquals(1, assertion.getWaypoints().size());
|
||||||
|
|
||||||
|
// Validate waypoint deserialization
|
||||||
|
Waypoint waypoint = assertion.getWaypoints().iterator().next();
|
||||||
|
|
||||||
|
Assertions.assertEquals(ChatColor.GREEN + "Test waypoint", waypoint.getName());
|
||||||
|
Assertions.assertEquals(1, waypoint.getLocation().getX());
|
||||||
|
Assertions.assertEquals(2, waypoint.getLocation().getY());
|
||||||
|
Assertions.assertEquals(3, waypoint.getLocation().getZ());
|
||||||
|
Assertions.assertEquals(4, waypoint.getLocation().getYaw());
|
||||||
|
Assertions.assertEquals(5, waypoint.getLocation().getPitch());
|
||||||
|
Assertions.assertEquals("world", waypoint.getLocation().getWorld().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test realistic situations
|
||||||
|
@Test
|
||||||
|
void testResearchChanges() throws InterruptedException {
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
File playerFile = new File("data-storage/Slimefun/Players/" + uuid + ".yml");
|
||||||
|
|
||||||
|
OfflinePlayer player = Bukkit.getOfflinePlayer(uuid);
|
||||||
|
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
||||||
|
|
||||||
|
// Unlock all researches
|
||||||
|
for (Research research : Slimefun.getRegistry().getResearches()) {
|
||||||
|
profile.setResearched(research, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the player data
|
||||||
|
LegacyStorage storage = new LegacyStorage();
|
||||||
|
storage.savePlayerData(uuid, profile.getPlayerData());
|
||||||
|
|
||||||
|
// Assert the file exists and data is correct
|
||||||
|
Assertions.assertTrue(playerFile.exists());
|
||||||
|
PlayerData assertion = storage.loadPlayerData(uuid);
|
||||||
|
Assertions.assertEquals(10, assertion.getResearches().size());
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
Assertions.assertTrue(assertion.getResearches().contains(Slimefun.getRegistry().getResearches().get(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now let's change the data and save it again
|
||||||
|
profile.setResearched(Slimefun.getRegistry().getResearches().get(3), false);
|
||||||
|
|
||||||
|
// Save the player data
|
||||||
|
storage.savePlayerData(uuid, profile.getPlayerData());
|
||||||
|
|
||||||
|
// Assert the file exists and data is correct
|
||||||
|
Assertions.assertTrue(playerFile.exists());
|
||||||
|
System.out.println("update assertion");
|
||||||
|
assertion = storage.loadPlayerData(uuid);
|
||||||
|
Assertions.assertEquals(9, assertion.getResearches().size());
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
if (i != 3) {
|
||||||
|
Assertions.assertTrue(assertion.getResearches().contains(Slimefun.getRegistry().getResearches().get(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test realistic situations - when we fix the serialization issue
|
||||||
|
// @Test
|
||||||
|
// void testBackpackChanges() throws InterruptedException {}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWaypointChanges() throws InterruptedException {
|
||||||
|
// Create mock world
|
||||||
|
World world = server.createWorld(WorldCreator.name("world").environment(Environment.NORMAL));
|
||||||
|
|
||||||
|
// Create a player file which we can load
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
File playerFile = new File("data-storage/Slimefun/Players/" + uuid + ".yml");
|
||||||
|
|
||||||
|
OfflinePlayer player = Bukkit.getOfflinePlayer(uuid);
|
||||||
|
PlayerProfile profile = TestUtilities.awaitProfile(player);
|
||||||
|
|
||||||
|
profile.addWaypoint(new Waypoint(
|
||||||
|
player.getUniqueId(),
|
||||||
|
"test",
|
||||||
|
new Location(world, 1, 2, 3, 4, 5),
|
||||||
|
ChatColor.GREEN + "Test waypoint"
|
||||||
|
));
|
||||||
|
|
||||||
|
Waypoint test2 = new Waypoint(
|
||||||
|
player.getUniqueId(),
|
||||||
|
"test2",
|
||||||
|
new Location(world, 10, 20, 30, 40, 50),
|
||||||
|
ChatColor.GREEN + "Test 2 waypoint"
|
||||||
|
);
|
||||||
|
profile.addWaypoint(test2);
|
||||||
|
|
||||||
|
// Save the player data
|
||||||
|
LegacyStorage storage = new LegacyStorage();
|
||||||
|
storage.savePlayerData(uuid, profile.getPlayerData());
|
||||||
|
|
||||||
|
// Assert the file exists and data is correct
|
||||||
|
Assertions.assertTrue(playerFile.exists());
|
||||||
|
PlayerData assertion = storage.loadPlayerData(uuid);
|
||||||
|
Assertions.assertEquals(2, assertion.getWaypoints().size());
|
||||||
|
|
||||||
|
// Remove one
|
||||||
|
profile.removeWaypoint(test2);
|
||||||
|
|
||||||
|
// Save the player data
|
||||||
|
storage.savePlayerData(uuid, profile.getPlayerData());
|
||||||
|
|
||||||
|
// Assert the file exists and data is correct
|
||||||
|
Assertions.assertTrue(playerFile.exists());
|
||||||
|
assertion = storage.loadPlayerData(uuid);
|
||||||
|
Assertions.assertEquals(1, assertion.getWaypoints().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
private static void setupResearches() {
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
NamespacedKey key = new NamespacedKey(plugin, "test_" + i);
|
||||||
|
Research research = new Research(key, i, "Test " + i, 100);
|
||||||
|
research.register();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,22 @@
|
|||||||
package io.github.thebusybiscuit.slimefun4.test.mocks;
|
package io.github.thebusybiscuit.slimefun4.test.mocks;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
import org.bukkit.OfflinePlayer;
|
import org.bukkit.OfflinePlayer;
|
||||||
|
|
||||||
import io.github.thebusybiscuit.slimefun4.api.player.PlayerProfile;
|
import io.github.thebusybiscuit.slimefun4.api.player.PlayerProfile;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.storage.data.PlayerData;
|
||||||
|
|
||||||
public class MockProfile extends PlayerProfile {
|
public class MockProfile extends PlayerProfile {
|
||||||
|
|
||||||
public MockProfile(@Nonnull OfflinePlayer p) {
|
public MockProfile(@Nonnull OfflinePlayer p) {
|
||||||
super(p);
|
this(p, new PlayerData(Set.of(), new HashMap<>(), Set.of()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MockProfile(@Nonnull OfflinePlayer p, @Nonnull PlayerData data) {
|
||||||
|
super(p, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user