diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml
index 4870574..3803d1b 100644
--- a/.github/workflows/create-release.yml
+++ b/.github/workflows/create-release.yml
@@ -5,6 +5,16 @@ on:
tags:
- '*'
workflow_dispatch:
+ inputs:
+ releaseType:
+ description: 'Release Type'
+ required: true
+ default: release
+ type: choice
+ options:
+ - alpha
+ - beta
+ - release
jobs:
publish-release:
@@ -22,6 +32,7 @@ jobs:
with:
java-version: '17'
distribution: 'zulu'
+ cache: 'maven'
- name: Get Previous tag
id: previous-tag
@@ -61,3 +72,17 @@ jobs:
skipIfReleaseExists: true
artifacts: ~/shops-${{ needs.publish-release.outputs.previous-tag }}/shops-${{ needs.publish-release.outputs.previous-tag }}.jar
tag: ${{ needs.publish-release.outputs.previous-tag }}
+
+ - name: Upload to CurseForge
+ uses: itsmeow/curseforge-upload@v3
+ with:
+ file_path: ~/shops-${{ needs.publish-release.outputs.previous-tag }}/shops-${{ needs.publish-release.outputs.previous-tag }}.jar
+ game_endpoint: bukkit
+ relations: vault:requiredDependency,essentialsx:optionalDependency
+ game_versions: 'Minecraft 1.18:1.18.2,Minecraft 1.19:1.19.4,Minecraft 1.20:1.20.1,Java 17'
+ release_type: ${{ inputs.releaseType }}
+ display_name: Shops ${{ needs.publish-release.outputs.previous-tag }'
+ #changelog: TODO: ADD GENERATED CHANGELOG
+ #changelog_type: markdown
+ project_id: 873479
+ token: ${{ secrets.CF_API_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml
index 83adeae..ad0d1c8 100644
--- a/.github/workflows/pipeline.yml
+++ b/.github/workflows/pipeline.yml
@@ -22,38 +22,22 @@ jobs:
with:
java-version: '17'
distribution: 'zulu'
+ cache: 'maven'
- name: Build and run unit tests
run: mvn clean install
- - name: Generate code coverage report
- run: mvn jacoco:report
-
- - name: Upload coverage reports to Codecov
- uses: codecov/codecov-action@v3
- env:
- CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
-
- integration-tests:
- name: Integration Tests
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v3
-
- - name: Set up Java
- uses: actions/setup-java@v3
+ - name: Upload 'target' folder as an artifact
+ id: upload-target-folder
+ uses: actions/upload-artifact@v3
with:
- java-version: '17'
- distribution: 'zulu'
-
- - name: Run integration tests
- run: mvn verify
+ name: target
+ path: target/
- documentation:
+ documentation-and-reporting:
name: Documentation and Reports
runs-on: ubuntu-latest
+ needs: build-and-test
steps:
- name: Checkout code
@@ -64,6 +48,19 @@ jobs:
with:
java-version: '17'
distribution: 'zulu'
+ cache: 'maven'
+
+ - name: Download the 'target' folder artifact
+ id: download-target-folder
+ uses: actions/download-artifact@v3
+ with:
+ name: target
+ path: target/
- name: Check documentation and publish reports
- run: mvn validate site
\ No newline at end of file
+ run: mvn validate site -DskipTests
+
+ - name: Upload coverage reports to Codecov
+ uses: codecov/codecov-action@v3
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index 20597af..3d11e05 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -2,9 +2,9 @@
-
-
-
+
+
+
@@ -16,36 +16,16 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -61,5 +41,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..308dfbb
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,63 @@
+# Welcome to the Shops contributing guidelines
+
+Thank you for investing your time in contributing to this project! Any contribution you make will be reflected in the [Shops contributors](https://github.com/BrendonButler/Shops/graphs/contributors).
+
+Read our [Code of Conduct](CODE_OF_CONDUCT.md) to keep our community approachable and respectable.
+
+In this guide you will get an overview of the contribution workflow from forking the repository, creating a PR, reviewing, and merging the PR.
+
+To get an overview of the project, read the [README](README.md).
+
+## Getting started
+
+### Pre-requisites
+
+1. Have [Git](https://github.com/git-guides/install-git) installed and [configured](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup) on your machine
+2. [Fork the Shops repository](https://github.com/BrendonButler/Shops/fork) into your own account
+3. Optional: Utilize the Project codestyle for reformatting changes _found here: `Shops/.idea/codeStyles/Project.xml`_
+
+### Issues/Features
+
+#### Create a new issue/feature request
+
+If you identify an issue or want to make a suggestion, [search if an issue already exists](https://docs.github.com/en/github/searching-for-information-on-github/searching-on-github/searching-issues-and-pull-requests#search-by-the-title-body-or-comments). If a related issue doesn't exist, you can open a new issue using a relevant [issue form](https://github.com/BrendonButler/Shops/issues/new).
+
+#### Solve an issue or implement a feature
+
+If an issue doesn't exist for a feature you want to implement, please create an issue first for pre-review. Once it's determined that the feature should be implemented, and you get feedback from owners on the repo, feel free to work on the issue and create a PR.
+
+Scan through the [existing issues](https://github.com/BrendonButler/Shops/issues) to find one that interests you. You can narrow down the search using `labels` as filters. See [Labels](/contributing/how-to-use-labels.md) for more information. As a general rule, we don’t assign issues to anyone. If you find an issue to work on, you are welcome to open a PR with a fix.
+
+#### Make changes locally
+
+1. Fork the repository.
+ - Using GitHub Desktop:
+ - [Getting started with GitHub Desktop](https://docs.github.com/en/desktop/installing-and-configuring-github-desktop/getting-started-with-github-desktop) will guide you through setting up Desktop.
+ - Once Desktop is set up, you can use it to [fork the repo](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/cloning-and-forking-repositories-from-github-desktop)!
+
+ - Using the command line:
+ - [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them.
+
+2. Create a working branch and start with your changes!
+ - Create a branch with the appropriate prefix for the issue type:
+ - `feature/GH-{ISSUE_NUM}` - use the "feature/GH-##" prefix to create the new feature branch
+ - `fix/GH-{ISSUE_NUM}` - use the "fix/GH-##" prefix to create a fix branch for bugs and other issues
+
+### Commit your update
+
+When creating commits, please use this format and if there are multiple updates that don't make sense to be added to the specific issue ID, you can create a new line for another issue:
+- `GH-123 add CreateCommand to create stores`
+- `GH-9999 fix command sender validation in CreateCommand`
+- `update README links`
+
+This will ensure fantastic traceability on issues and how the commits relate. For updates such as README enhancements, you can omit the issue tag. These tags will be clickable in the commit history to quickly bring up the issue.
+
+### Pull Request
+
+When you're finished with the changes, create a pull request, also known as a PR.
+- Don't forget to [link PR to issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) if you are solving one.
+- Enable the checkbox to [allow maintainer edits](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork) so the branch can be updated for a merge.
+ Once you submit your PR, a project admin will review your proposal. We may ask questions or request additional information.
+- We may ask for changes to be made before a PR can be merged, either using [suggested changes](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request) or pull request comments. You can apply suggested changes directly through the UI. You can make any other changes in your fork, then commit them to your branch.
+- As you update your PR and apply changes, mark each conversation as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations).
+- If you run into any merge issues, checkout this [git tutorial](https://github.com/skills/resolve-merge-conflicts) to help you resolve merge conflicts and other issues.
\ No newline at end of file
diff --git a/README.md b/README.md
index 269113b..0701faf 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,52 @@
# Shops
-[![CI Pipeline](https://github.com/MrSparkzz/Shops/actions/workflows/pipeline.yml/badge.svg)](https://github.com/MrSparkzz/Shops/actions/workflows/pipeline.yml)
-![Codecov](https://img.shields.io/codecov/c/github/MrSparkzz/Shops?logo=codecov&logoColor=white&label=Coverage)
+[![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/BrendonButler/Shops/pipeline.yml?logo=github&label=CI%20Pipeline)](https://github.com/BrendonButler/Shops/actions/workflows/pipeline.yml)
+[![Codecov](https://img.shields.io/codecov/c/github/BrendonButler/Shops?logo=codecov&logoColor=white&label=Coverage)](https://app.codecov.io/github/BrendonButler/Shops)
+[![GitHub downloads](https://img.shields.io/github/downloads/BrendonButler/Shops/total?label=GitHub%20Downloads&logo=github)](https://github.com/BrendonButler/Shops/releases)
+[![CurseForge Downloads](https://img.shields.io/curseforge/dt/873479?logo=curseforge&logoColor=white&label=CurseForge%20Downloads&color=f16436)](https://www.curseforge.com/minecraft/bukkit-plugins/command-shops)
+
+
-Shops plugin for Bukkit/Spigot 2022+ [Built for Spigot 1.18]
+Shops plugin for Bukkit/Spigot 2022+
![Shops social image](https://repository-images.githubusercontent.com/388618586/0d033997-0fcd-44db-a53d-c635f8bc38f5)
**Depends on:** [Vault](https://github.com/MilkBowl/Vault)
**Requires economy plugin (compatible with Vault) such as:** [EssentialsX](https://github.com/EssentialsX/Essentials)
+
+### What is Shops?
+_Shops is a vanilla-style, command-driven shopping plugin for Spigot Servers._
+
+As a shopping plugin, you can create location based stores and add/remove/update items in the inventory for your (stores).
+For _customers_, when in a store, you can buy items from the store, sell items to the store, and browse the store's catalog.
+
+### Why Command Shops?
+Really this stemmed from nostalgia when I used to play on my friend's cousin's Minecraft server where it used Towny and
+some location-based shopping plugin to make for an ultimate survival multiplayer server. Back then, most of your
+plugins would be controlled through commands instead of UI elements.
+
+With _newer_ features such as tab complete to autofill suggested command arguments such as online players and
+materials, it really makes these command shops even easier to interact with. Sure, pulling up an inventory UI may be
+much more simple and intuitive for players, I'm sure there are still server operators that enjoy a more vanilla
+approach, accomplished by commands.
+
+I intend to use this on my personal server(s) with friends/family as well. So this was really created as a personal
+project that I could learn from.
+
+### What if there's a feature I feel is missing?
+I believe I have thought of most of the features that I _want_ to implement, but I am definitely open to suggestions.
+If you would like to make a new request, please visit the [issues page](https://github.com/BrendonButler/Shops/issues?q=is%3Aissue)
+on GitHub and search for keywords in your feature/request. If you can't find an existing or closed issue, please feel
+free to click "New Issue" and try to fill it out in detail with what you would like to see added or improved and why.
+
+Being detailed in your request and explaining the need/want clearly will aid in my decision of whether I deem the
+feature or request to be in accordance with my goals for this project.
+
+### How can I contribute?
+If you're looking to implement any of the [open issues](https://github.com/BrendonButler/Shops/issues) please review
+the [Contributing Guidelines](https://github.com/BrendonButler/Shops/blob/develop/CONTRIBUTING.md).
+
+This can be a great way to add projects and workflows to your portfolio and resumé.
+
+I would like to restate the importance of following the [Contributing Guidelines](https://github.com/BrendonButler/Shops/blob/develop/CONTRIBUTING.md) carefully as to make the review process
+smoother.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 648e7a6..f32c4e8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,7 +15,7 @@
2.20.032.1.1-jre
- 4.1.2
+ 4.1.22.15.224.0.05.9.2
@@ -31,6 +31,7 @@
1616
+ 2.38.3.13.5.03.4.5
@@ -41,7 +42,7 @@
Shopsnet.sparkzzshops
- ALPHA
+ 0.1-BETALocation based shops plugin for Spigot ${spigot-api.version}https://shops.sparkzz.net/
@@ -68,6 +69,12 @@
+
+ https://github.com/MrSparkzz/Shops.git
+ scm:git:https://github.com/MrSparkzz/Shops.git
+ scm:git:https://github.com/MrSparkzz/Shops.git
+
+
GitHubhttps://github.com/MrSparkzz/Shops/issues
@@ -171,6 +178,14 @@
${project.build.encoding}
+
+
+
+ org.apache.maven.doxia
+ doxia-module-xhtml
+ 2.0.0-M3
+
+
@@ -193,22 +208,31 @@
true
+
spigot-repohttps://hub.spigotmc.org/nexus/content/repositories/snapshots/
+
jitpack.iohttps://jitpack.io
+
essentials-releaseshttps://repo.essentialsx.net/releases/
+
paper-repohttps://papermc.io/repo/repository/maven-public/
+
+
+ vault-repo
+ http://nexus.hc.to/content/repositories/pub_releases
+
@@ -259,7 +283,14 @@
org.spongepoweredconfigurate-hocon
- ${hocon.version}
+ ${configurate.version}
+
+
+
+
+ org.spongepowered
+ configurate-yaml
+ ${configurate.version}
@@ -353,7 +384,12 @@
org.spongepoweredconfigurate-hocon
- ${hocon.version}
+
+
+
+
+ org.spongepowered
+ configurate-yaml
@@ -434,9 +470,17 @@
spotbugs-maven-plugin${spotbugs.version}
+ ${project.build.encoding}spotbugs-filter.xml
+
+
+
+ org.apache.maven.plugins
+ maven-changelog-plugin
+ ${maven.changelog.version}
+
diff --git a/shops-beta.png b/shops-beta.png
new file mode 100644
index 0000000..de427ab
Binary files /dev/null and b/shops-beta.png differ
diff --git a/src/main/java/net/sparkzz/command/InfoCommand.java b/src/main/java/net/sparkzz/command/InfoCommand.java
index 8b19c00..7f6f7bd 100644
--- a/src/main/java/net/sparkzz/command/InfoCommand.java
+++ b/src/main/java/net/sparkzz/command/InfoCommand.java
@@ -6,6 +6,7 @@
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
+import java.util.Collections;
import java.util.List;
/**
@@ -46,6 +47,6 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command
*/
@Override
public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
- return null;
+ return Collections.emptyList();
}
}
\ No newline at end of file
diff --git a/src/main/java/net/sparkzz/command/ShopCommand.java b/src/main/java/net/sparkzz/command/ShopCommand.java
index f87b1de..95db188 100644
--- a/src/main/java/net/sparkzz/command/ShopCommand.java
+++ b/src/main/java/net/sparkzz/command/ShopCommand.java
@@ -3,16 +3,21 @@
import net.sparkzz.command.sub.*;
import net.sparkzz.shops.Shops;
import net.sparkzz.shops.Store;
+import net.sparkzz.util.InventoryManagementSystem;
import net.sparkzz.util.Notifier;
import net.sparkzz.util.Notifier.CipherKey;
+import org.bukkit.Bukkit;
import org.bukkit.Material;
+import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
+import org.bukkit.generator.WorldInfo;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -52,6 +57,7 @@ public class ShopCommand extends CommandManager {
* @param args the arguments following the command
* @return a list of options for the /shop command arguments
*/
+ // TODO: clean up this mess (reconfigure if-blocks/switches to be more efficient and less clunky
@Override
@SuppressWarnings("all")
public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
@@ -65,13 +71,13 @@ public List onTabComplete(@NotNull CommandSender sender, @NotNull Comman
if (args.length == 2) {
if (args[0].equalsIgnoreCase("browse"))
- return Arrays.asList("");
+ return Collections.singletonList("");
if (args[0].equalsIgnoreCase("deposit"))
- return Arrays.asList("");
+ return Collections.singletonList("");
if (args[0].equalsIgnoreCase("withdraw"))
- return Arrays.asList("", "all");
+ return List.of("", "all");
// Add command autocomplete item list
if (args[0].equalsIgnoreCase("add")) {
@@ -79,12 +85,12 @@ public List onTabComplete(@NotNull CommandSender sender, @NotNull Comman
.map(m -> m.toString().toLowerCase()).collect(Collectors.toList());
}
- Set shopItems = Shops.getDefaultShop().getItems().keySet();
+ Store currentStore = InventoryManagementSystem.locateCurrentStore(((Player) sender));
+ Set shopItems = (currentStore != null ? currentStore.getItems().keySet() : Collections.emptySet());
// Buy/Remove command autocomplete item list
if (args[0].equalsIgnoreCase("buy") || args[0].equalsIgnoreCase("remove"))
- return Arrays.stream(shopItems.toArray())
- .map(m -> m.toString().toLowerCase()).collect(Collectors.toList());
+ return shopItems.stream().map(m -> m.toString().toLowerCase()).collect(Collectors.toList());
// provide a list of items witin the shop, along with some additional items based on permissions
if (args[0].equalsIgnoreCase("update")) {
@@ -103,52 +109,214 @@ public List onTabComplete(@NotNull CommandSender sender, @NotNull Comman
.collect(Collectors.toList());
if (args[0].equalsIgnoreCase("create"))
- return Arrays.asList("");
+ return Collections.singletonList("");
// Only display a list of shops that are owned by the player, and provide a list of "ShopName~UUID"
if (args[0].equalsIgnoreCase("delete") || args[0].equalsIgnoreCase("transfer"))
return Store.STORES.stream().filter(s -> s.getOwner().equals(((Player) sender).getUniqueId())).map(s -> String.format("%s~%s", s.getName(), s.getUUID())).collect(Collectors.toCollection(ArrayList::new));
}
+ Server server = (Shops.isTest() ? Shops.getMockServer() : Shops.getPlugin(Shops.class).getServer());
+
if (args.length == 3) {
if (args[0].equalsIgnoreCase("remove") || args[0].equalsIgnoreCase("sell"))
- return Arrays.asList("[]", "all");
+ return List.of("[]", "all");
if (args[0].equalsIgnoreCase("add"))
- return Arrays.asList("", "[]", "all");
+ return List.of("", "[]", "all");
if (args[0].equalsIgnoreCase("buy"))
- return Arrays.asList("[]");
+ return Collections.singletonList("[]");
if (args[0].equalsIgnoreCase("update")) {
- return switch (args[1].toLowerCase()) {
- case "infinite-funds", "infinite-stock" -> Arrays.asList("true", "false");
- case "shop-name" -> Arrays.asList("");
- default -> Arrays.asList("customer-buy-price", "customer-sell-price", "infinite-quantity", "max-quantity");
+ List options;
+
+ switch (args[1].toLowerCase()) {
+ case "infinite-funds", "infinite-stock" -> options = List.of("true", "false");
+ case "shop-name" -> options = Collections.singletonList("");
+ case "location" -> {
+ options = Store.STORES.stream().filter(s -> s.getOwner().equals(((Player) sender).getUniqueId())).map(s -> String.format("%s~%s", s.getName(), s.getUUID())).collect(Collectors.toCollection(ArrayList::new));
+ options.addAll(Bukkit.getWorlds().stream().map(WorldInfo::getName).toList());
+ }
+ default -> options = List.of("customer-buy-price", "customer-sell-price", "infinite-quantity", "max-quantity");
};
+
+ return options;
}
if (args[0].equalsIgnoreCase("transfer"))
- return Shops.getPlugin(Shops.class).getServer().getOnlinePlayers().stream().map(p -> p.getName()).collect(Collectors.toList());
+ return server.getOnlinePlayers().stream().map(p -> p.getName()).collect(Collectors.toList());
+
+ if (args[0].equalsIgnoreCase("create")) {
+ List options = server.getOnlinePlayers().stream().map(p -> p.getName()).collect(Collectors.toList());
+ options.add("");
+
+ return options;
+ }
}
- if (args.length == 4 && (args[0].equalsIgnoreCase("add"))) {
- return Arrays.asList("");
+ if (args.length == 4) {
+ if (args[0].equalsIgnoreCase("add")) {
+ return Collections.singletonList("");
+ }
+
+ if (args[0].equalsIgnoreCase("update")) {
+ if (args[1].equalsIgnoreCase("location")) {
+ List stores = Store.STORES.stream().map(Store::getName).collect(Collectors.toList());
+ List options = Bukkit.getWorlds().stream().map(WorldInfo::getName).collect(Collectors.toList());
+
+ boolean containsWorld = Bukkit.getWorlds().stream().map(WorldInfo::getName).anyMatch(w -> w.equalsIgnoreCase(args[2]) || w.equalsIgnoreCase(args[3]));
+ boolean containsStore = stores.stream().anyMatch(s -> s.equalsIgnoreCase(args[2]));
+
+ if (containsWorld)
+ options = Collections.singletonList("x1");
+ else if (!containsWorld && !containsStore)
+ options = Collections.singletonList("y1");
+ else if (!containsStore)
+ options.add("x1");
+
+ return options;
+ }
+
+ return switch (args[2].toLowerCase()) {
+ case "infinite-quantity" -> List.of("true", "false");
+ default -> Collections.singletonList("");
+ };
+ }
+
+ if (args[0].equalsIgnoreCase("create") && server.getPlayer(args[2]) != null)
+ return Collections.singletonList("");
+ else if (args[0].equalsIgnoreCase("create"))
+ return Collections.singletonList("");
}
- if (args.length == 4 && (args[0].equalsIgnoreCase("update"))) {
- return switch (args[2].toLowerCase()) {
- case "infinite-quantity" -> Arrays.asList("true", "false");
- default -> Arrays.asList("");
- };
+ if (args.length == 5 ) {
+ if (args[0].equalsIgnoreCase("add")) {
+ return Collections.singletonList("");
+ }
+
+ if (args[0].equalsIgnoreCase("create") && server.getPlayer(args[2]) != null)
+ return Collections.singletonList("");
+ else if (args[0].equalsIgnoreCase("create"))
+ return Collections.singletonList("");
+
+ if (args[0].equalsIgnoreCase("update") && args[1].equalsIgnoreCase("location")) {
+ List stores = Store.STORES.stream().map(Store::getName).collect(Collectors.toList());
+ List options = Collections.singletonList("x1");
+
+ boolean containsWorld = Bukkit.getWorlds().stream().map(WorldInfo::getName).anyMatch(w -> w.equalsIgnoreCase(args[2]) || w.equalsIgnoreCase(args[3]));
+ boolean containsStore = stores.stream().anyMatch(s -> s.equalsIgnoreCase(args[2]));
+
+ if (containsWorld && !containsStore)
+ options = Collections.singletonList("y1");
+ else if (!containsWorld && !containsStore)
+ options = Collections.singletonList("z1");
+
+ return options;
+ }
}
- if (args.length == 5 && (args[0].equalsIgnoreCase("add"))) {
- return Arrays.asList("");
+ if (args.length == 6) {
+ if (args[0].equalsIgnoreCase("add")) {
+ return List.of("[]", "all");
+ }
+
+ if (args[0].equalsIgnoreCase("create") && server.getPlayer(args[2]) != null)
+ return Collections.singletonList("");
+ else if (args[0].equalsIgnoreCase("create"))
+ return Collections.singletonList("");
+
+ if (args[0].equalsIgnoreCase("update") && args[1].equalsIgnoreCase("location")) {
+ List stores = Store.STORES.stream().map(Store::getName).collect(Collectors.toList());
+ List options = Collections.singletonList("y1");
+
+ boolean containsWorld = Bukkit.getWorlds().stream().map(WorldInfo::getName).anyMatch(w -> w.equalsIgnoreCase(args[2]) || w.equalsIgnoreCase(args[3]));
+ boolean containsStore = stores.stream().anyMatch(s -> s.equalsIgnoreCase(args[2]));
+
+ if (containsWorld && !containsStore)
+ options = Collections.singletonList("z1");
+ else if (!containsWorld && !containsStore)
+ options = Collections.singletonList("x2");
+
+ return options;
+ }
+ }
+
+ if (args.length == 7) {
+ if (args[0].equalsIgnoreCase("create") && server.getPlayer(args[2]) != null)
+ return Collections.singletonList("");
+ else if (args.length == 7 && args[0].equalsIgnoreCase("create"))
+ return Collections.singletonList("");
+
+ if (args[0].equalsIgnoreCase("update") && args[1].equalsIgnoreCase("location")) {
+ List stores = Store.STORES.stream().map(Store::getName).collect(Collectors.toList());
+ List options = Collections.singletonList("z1");
+
+ boolean containsWorld = Bukkit.getWorlds().stream().map(WorldInfo::getName).anyMatch(w -> w.equalsIgnoreCase(args[2]) || w.equalsIgnoreCase(args[3]));
+ boolean containsStore = stores.stream().anyMatch(s -> s.equalsIgnoreCase(args[2]));
+
+ if (containsWorld && !containsStore)
+ options = Collections.singletonList("x2");
+ else if (!containsWorld && !containsStore)
+ options = Collections.singletonList("y2");
+
+ return options;
+ }
+ }
+
+ if (args.length == 8) {
+ if (args[0].equalsIgnoreCase("create") && server.getPlayer(args[2]) != null)
+ return Collections.singletonList("");
+ else if (args[0].equalsIgnoreCase("create"))
+ return Collections.singletonList("");
+
+ if (args[0].equalsIgnoreCase("update") && args[1].equalsIgnoreCase("location")) {
+ List stores = Store.STORES.stream().map(Store::getName).collect(Collectors.toList());
+ List options = Collections.singletonList("x2");
+
+ boolean containsWorld = Bukkit.getWorlds().stream().map(WorldInfo::getName).anyMatch(w -> w.equalsIgnoreCase(args[2]) || w.equalsIgnoreCase(args[3]));
+ boolean containsStore = stores.stream().anyMatch(s -> s.equalsIgnoreCase(args[2]));
+
+ if (containsWorld && !containsStore)
+ options = Collections.singletonList("y2");
+ else if (!containsWorld && !containsStore)
+ options = Collections.singletonList("z2");
+
+ return options;
+ }
}
- if (args.length == 6 && (args[0].equalsIgnoreCase("add"))) {
- return Arrays.asList("[]", "all");
+ if (args.length == 9) {
+ if (args[0].equalsIgnoreCase("create") && server.getPlayer(args[2]) != null)
+ return Collections.singletonList("");
+
+ if (args[0].equalsIgnoreCase("update") && args[1].equalsIgnoreCase("location")) {
+ List stores = Store.STORES.stream().map(Store::getName).collect(Collectors.toList());
+ List options = Collections.singletonList("y2");
+
+ boolean containsWorld = Bukkit.getWorlds().stream().map(WorldInfo::getName).anyMatch(w -> w.equalsIgnoreCase(args[2]) || w.equalsIgnoreCase(args[3]));
+ boolean containsStore = stores.stream().anyMatch(s -> s.equalsIgnoreCase(args[2]));
+
+ if (containsWorld && !containsStore)
+ options = Collections.singletonList("z2");
+ else if (!containsWorld && !containsStore)
+ options = Collections.emptyList();
+
+ return options;
+ }
+ }
+
+ if (args.length == 10 && args[0].equalsIgnoreCase("update") && args[1].equalsIgnoreCase("location")) {
+ List stores = Store.STORES.stream().map(Store::getName).collect(Collectors.toList());
+ List options = Collections.singletonList("z2");
+
+ boolean containsWorld = Bukkit.getWorlds().stream().map(WorldInfo::getName).anyMatch(w -> w.equalsIgnoreCase(args[2]) || w.equalsIgnoreCase(args[3]));
+ boolean containsStore = stores.stream().anyMatch(s -> s.equalsIgnoreCase(args[2]));
+
+ if ((containsWorld && !containsStore) || (!containsWorld && !containsStore))
+ options = Collections.emptyList();
+
+ return options;
}
return new ArrayList<>();
diff --git a/src/main/java/net/sparkzz/command/sub/AddCommand.java b/src/main/java/net/sparkzz/command/sub/AddCommand.java
index bc9d38d..90279a6 100644
--- a/src/main/java/net/sparkzz/command/sub/AddCommand.java
+++ b/src/main/java/net/sparkzz/command/sub/AddCommand.java
@@ -30,6 +30,11 @@ public boolean process(CommandSender sender, Command command, String label, Stri
int quantity = (Integer) setAttribute("quantity", 0);
String message = "";
+ if (store == null) {
+ Notifier.process(player, NO_STORE_FOUND, getAttributes());
+ return true;
+ }
+
if (material != null) {
if (args.length == 3) {
quantity = (int) setAttribute("quantity", args[2].equalsIgnoreCase("all") ? InventoryManagementSystem.countQuantity((Player) sender, material) : Integer.parseInt(args[2]));
diff --git a/src/main/java/net/sparkzz/command/sub/BrowseCommand.java b/src/main/java/net/sparkzz/command/sub/BrowseCommand.java
index aa45fc8..108b3f2 100644
--- a/src/main/java/net/sparkzz/command/sub/BrowseCommand.java
+++ b/src/main/java/net/sparkzz/command/sub/BrowseCommand.java
@@ -9,7 +9,7 @@
import org.bukkit.entity.Player;
import static net.sparkzz.util.Notifier.CipherKey.INVALID_PAGE_NUM;
-import static net.sparkzz.util.Notifier.CipherKey.STORE_NOT_FOUND;
+import static net.sparkzz.util.Notifier.CipherKey.NO_STORE_FOUND;
/**
* Browse subcommand used for browsing items to a shop
@@ -26,21 +26,21 @@ public boolean process(CommandSender sender, Command command, String label, Stri
Player player = (Player) setAttribute("sender", sender);
Store store = (Store) setAttribute("store", InventoryManagementSystem.locateCurrentStore(player));
- int pageNumber = (args.length > 1) ? Integer.parseInt(args[1]) : 1;
+ if (store == null) {
+ Notifier.process(player, NO_STORE_FOUND, getAttributes());
+ return true;
+ }
- if (store != null) {
- String page = Notifier.Paginator.buildBrowsePage(store, pageNumber);
+ int pageNumber = (args.length > 1) ? Integer.parseInt(args[1]) : 1;
- if (page == null) {
- Notifier.process(sender, INVALID_PAGE_NUM, getAttributes());
- return true;
- }
+ String page = Notifier.Paginator.buildBrowsePage(store, pageNumber);
- sender.sendMessage(page);
+ if (page == null) {
+ Notifier.process(sender, INVALID_PAGE_NUM, getAttributes());
return true;
}
- Notifier.process(sender, STORE_NOT_FOUND, getAttributes());
+ sender.sendMessage(page);
return true;
}
}
\ No newline at end of file
diff --git a/src/main/java/net/sparkzz/command/sub/BuyCommand.java b/src/main/java/net/sparkzz/command/sub/BuyCommand.java
index 35ae312..72e8c40 100644
--- a/src/main/java/net/sparkzz/command/sub/BuyCommand.java
+++ b/src/main/java/net/sparkzz/command/sub/BuyCommand.java
@@ -1,6 +1,7 @@
package net.sparkzz.command.sub;
import net.sparkzz.command.SubCommand;
+import net.sparkzz.shops.Store;
import net.sparkzz.util.InventoryManagementSystem;
import net.sparkzz.util.Notifier;
import net.sparkzz.util.Transaction;
@@ -27,7 +28,12 @@ public boolean process(CommandSender sender, Command command, String label, Stri
Material material = (Material) setAttribute("material", Material.matchMaterial(args[1]));
Player player = (Player) setAttribute("sender", sender);
int quantity = (Integer) setAttribute("quantity", 1);
- setAttribute("store", InventoryManagementSystem.locateCurrentStore(player));
+ Store store = (Store) setAttribute("store", InventoryManagementSystem.locateCurrentStore(player));
+
+ if (store == null) {
+ Notifier.process(player, NO_STORE_FOUND, getAttributes());
+ return true;
+ }
if (args.length == 3)
quantity = (Integer) setAttribute("quantity", Integer.parseInt(args[2]));
diff --git a/src/main/java/net/sparkzz/command/sub/CreateCommand.java b/src/main/java/net/sparkzz/command/sub/CreateCommand.java
index 5a6d84a..1409729 100644
--- a/src/main/java/net/sparkzz/command/sub/CreateCommand.java
+++ b/src/main/java/net/sparkzz/command/sub/CreateCommand.java
@@ -1,12 +1,22 @@
package net.sparkzz.command.sub;
import net.sparkzz.command.SubCommand;
+import net.sparkzz.shops.Shops;
import net.sparkzz.shops.Store;
+import net.sparkzz.util.Config;
+import net.sparkzz.util.Cuboid;
import net.sparkzz.util.Notifier;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
+import java.util.UUID;
+import java.util.stream.DoubleStream;
+
+import static net.sparkzz.util.Notifier.CipherKey.PLAYER_NOT_FOUND;
+
/**
* Create subcommand used for creating a shop
*
@@ -21,11 +31,128 @@ public boolean process(CommandSender sender, Command command, String label, Stri
setAttribute("sender", sender);
setArgsAsAttributes(args);
// TODO: new permission to limit a player to a number of shops (shops.create.)
+ int shopsOwned = 0;
+
+ for (Store store : Store.STORES)
+ if (store.getOwner().equals(((Player) sender).getUniqueId()))
+ shopsOwned++;
+
+ if (shopsOwned >= (int) setAttribute("max-stores", Config.getMaxOwnedStores())) {
+ Notifier.process(sender, Notifier.CipherKey.STORE_CREATE_FAIL_MAX_STORES, getAttributes());
+ return true;
+ }
+
+ OfflinePlayer owner = (Player) sender;
+ setAttribute("target", owner);
+
+ if (args.length == 3 || args.length == 9) {
+ if (!sender.hasPermission("shops.create.other-player")) {
+ Notifier.process(sender, Notifier.CipherKey.NO_PERMS_CREATE_OTHER, getAttributes());
+ return true;
+ }
+
+ setAttribute("target", args[2]);
+
+ boolean isUUID = args[2].matches("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$");
+ Server server = (!Shops.isTest()) ? Shops.getPlugin(Shops.class).getServer() : Shops.getMockServer();
+ owner = (!isUUID) ? server.getPlayer(args[2]) : server.getOfflinePlayer(UUID.fromString(args[2]));
+ }
+
+ if (owner == null) {
+ Notifier.process(sender, PLAYER_NOT_FOUND, getAttributes());
+ return true;
+ }
+
+ double x1, y1, z1, x2, y2, z2;
+ x1 = y1 = z1 = x2 = y2 = z2 = 0D;
+
+ if (args.length == 8) {
+ x1 = Double.parseDouble(args[2]);
+ y1 = Double.parseDouble(args[3]);
+ z1 = Double.parseDouble(args[4]);
+ x2 = Double.parseDouble(args[5]);
+ y2 = Double.parseDouble(args[6]);
+ z2 = Double.parseDouble(args[7]);
+ }
+
+ if (args.length == 9) {
+ x1 = Double.parseDouble(args[3]);
+ y1 = Double.parseDouble(args[4]);
+ z1 = Double.parseDouble(args[5]);
+ x2 = Double.parseDouble(args[6]);
+ y2 = Double.parseDouble(args[7]);
+ z2 = Double.parseDouble(args[8]);
+ }
- Store store = new Store(args[1], ((Player) sender).getUniqueId());
+ Store store;
+
+ if (DoubleStream.of(x1, y1, z1, x2, y2, z2).allMatch(value -> value == 0D))
+ store = new Store(args[1], owner.getUniqueId());
+ else {
+ double minX = (double) setAttribute("min-x", Math.min(x1, x2));
+ double maxX = (double) setAttribute("max-x", Math.max(x1, x2));
+ double minY = (double) setAttribute("min-y", Math.min(y1, y2));
+ double maxY = (double) setAttribute("max-y", Math.max(y1, y2));
+ double minZ = (double) setAttribute("min-z", Math.min(z1, z2));
+ double maxZ = (double) setAttribute("max-z", Math.max(z1, z2));
+ double[] minDims = Config.getMinDimensions();
+ double[] maxDims = Config.getMaxDimensions();
+ double limitMinX = (double) setAttribute("limit-min-x", minDims[0]);
+ double limitMinY = (double) setAttribute("limit-min-y", minDims[1]);
+ double limitMinZ = (double) setAttribute("limit-min-z", minDims[2]);
+ double limitMaxX = (double) setAttribute("limit-max-x", maxDims[0]);
+ double limitMaxY = (double) setAttribute("limit-max-y", maxDims[1]);
+ double limitMaxZ = (double) setAttribute("limit-max-z", maxDims[2]);
+
+ // TODO: make 0 or negative limits ignore check
+ if ((maxX - minX) < limitMinX || (maxY - minY) < limitMinY || (maxZ - minZ) < limitMinZ) {
+ Notifier.process(sender, Notifier.CipherKey.STORE_CREATE_FAIL_MIN_DIMS, getAttributes());
+ return true;
+ }
+
+ if ((limitMaxX > 0 && (maxX - minX) > limitMaxX) || (limitMaxY > 0 && (maxY - minY) > limitMaxY) || (limitMaxZ > 0 && (maxZ - minZ) > limitMaxZ)) {
+ Notifier.process(sender, Notifier.CipherKey.STORE_CREATE_FAIL_MAX_DIMS, getAttributes());
+ return true;
+ }
+
+ double volume = (double) setAttribute("volume", (maxX - minX) * (maxY - minY) * (maxZ - minZ));
+ double minVolume = (double) setAttribute("limit-min-vol", Config.getMinVolume());
+ double maxVolume = (double) setAttribute("limit-max-vol", Config.getMaxVolume());
+
+ if (volume < minVolume) {
+ Notifier.process(sender, Notifier.CipherKey.STORE_CREATE_FAIL_MIN_VOL, getAttributes());
+ return true;
+ }
+
+ if (maxVolume > 0 && volume > maxVolume) {
+ Notifier.process(sender, Notifier.CipherKey.STORE_CREATE_FAIL_MAX_VOL, getAttributes());
+ return true;
+ }
+
+ Cuboid cuboid = new Cuboid(((Player) sender).getWorld(), x1, y1, z1, x2, y2, z2);
+
+ for (Cuboid currentCuboid : Config.getOffLimitsCuboids()) {
+ if (cuboid.intersects(currentCuboid) || currentCuboid.intersects(cuboid)) {
+ Notifier.process(sender, Notifier.CipherKey.STORE_CREATE_FAIL_OFFLIMITS, getAttributes());
+ return true;
+ }
+ }
+
+ for (Cuboid currentCuboid : Store.STORES.stream().map(Store::getCuboidLocation).toList()) {
+ if (cuboid.intersects(currentCuboid) || currentCuboid.intersects(cuboid)) {
+ Notifier.process(sender, Notifier.CipherKey.STORE_CREATE_FAIL_OVERLAPS, getAttributes());
+ return true;
+ }
+ }
+
+ store = new Store(args[1], owner.getUniqueId(), cuboid);
+ }
setAttribute("store", store.getName());
- Notifier.process(sender, Notifier.CipherKey.STORE_CREATE_SUCCESS, getAttributes());
+
+ if (owner.getUniqueId().equals(((Player) sender).getUniqueId()))
+ Notifier.process(sender, Notifier.CipherKey.STORE_CREATE_SUCCESS, getAttributes());
+ else Notifier.process(sender, Notifier.CipherKey.STORE_CREATE_SUCCESS_OTHER_PLAYER, getAttributes());
return true;
}
}
\ No newline at end of file
diff --git a/src/main/java/net/sparkzz/command/sub/DeleteCommand.java b/src/main/java/net/sparkzz/command/sub/DeleteCommand.java
index 80239be..9e511d8 100644
--- a/src/main/java/net/sparkzz/command/sub/DeleteCommand.java
+++ b/src/main/java/net/sparkzz/command/sub/DeleteCommand.java
@@ -1,12 +1,17 @@
package net.sparkzz.command.sub;
import net.sparkzz.command.SubCommand;
+import net.sparkzz.shops.Shops;
import net.sparkzz.shops.Store;
+import net.sparkzz.util.InventoryManagementSystem;
import net.sparkzz.util.Notifier;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
import java.util.Optional;
+import java.util.stream.Collectors;
import static net.sparkzz.util.Notifier.CipherKey.*;
@@ -36,12 +41,44 @@ public boolean process(CommandSender sender, Command command, String label, Stri
return true;
}
+ boolean ignoreInv = false, ignoreFunds = false;
+
// TODO: determine a way to check if a player can remove all items from the shop, if they can, remove them all
// TODO: add force flags (-f will ignore all inventory, then process) (-F will ignore all inventory and finances, then process)
+ if (args.length == 3) {
+ switch (args[2]) {
+ // soft force delete
+ case "-f" -> ignoreInv = true;
+ // hard force delete
+ case "-F" -> {
+ ignoreInv = true;
+ ignoreFunds = true;
+ }
+ default -> {}
+ }
+ }
+ boolean canInsertAll = false;
Store store = foundStore.get();
+ Player player = (Player) sender;
setAttribute("store", store.getName());
+
+ if (!ignoreInv)
+ canInsertAll = InventoryManagementSystem.canInsertAll(player, store.getItems().entrySet().stream()
+ .map(entry -> new ItemStack(entry.getKey(), (int) entry.getValue().getOrDefault("quantity", 0)))
+ .collect(Collectors.toList()));
+
+ if (!ignoreInv && !canInsertAll) {
+ Notifier.process(sender, STORE_DELETE_INSUFFICIENT_INV_PLAYER, getAttributes());
+ return true;
+ }
+
+ if (!ignoreFunds) {
+ Shops.getEconomy().depositPlayer(player, store.getBalance());
+ store.setBalance(0);
+ }
+
boolean success = Store.STORES.remove(store);
if (success)
diff --git a/src/main/java/net/sparkzz/command/sub/DepositCommand.java b/src/main/java/net/sparkzz/command/sub/DepositCommand.java
index 171990f..ef7069a 100644
--- a/src/main/java/net/sparkzz/command/sub/DepositCommand.java
+++ b/src/main/java/net/sparkzz/command/sub/DepositCommand.java
@@ -24,10 +24,14 @@ public boolean process(CommandSender sender, Command command, String label, Stri
resetAttributes();
setArgsAsAttributes(args);
Player player = (Player) setAttribute("sender", sender);
- Store store = InventoryManagementSystem.locateCurrentStore(player);
- setAttribute("store", store.getName());
+ Store store = (Store) setAttribute("store", InventoryManagementSystem.locateCurrentStore(player));
double amount = (Double) setAttribute("amount", Double.parseDouble(args[1]));
+ if (store == null) {
+ Notifier.process(player, NO_STORE_FOUND, getAttributes());
+ return true;
+ }
+
if (amount < 0) throw new NumberFormatException(String.format("Invalid amount: \"%s\"", args[1]));
if (!store.getOwner().equals(player.getUniqueId())) {
diff --git a/src/main/java/net/sparkzz/command/sub/RemoveCommand.java b/src/main/java/net/sparkzz/command/sub/RemoveCommand.java
index b171f60..f655848 100644
--- a/src/main/java/net/sparkzz/command/sub/RemoveCommand.java
+++ b/src/main/java/net/sparkzz/command/sub/RemoveCommand.java
@@ -26,8 +26,12 @@ public boolean process(CommandSender sender, Command command, String label, Stri
setArgsAsAttributes(args);
Material material = (Material) setAttribute("material", Material.matchMaterial(args[1]));
Player player = (Player) setAttribute("sender", sender);
- Store store = InventoryManagementSystem.locateCurrentStore(player);
- setAttribute("store", store.getName());
+ Store store = (Store) setAttribute("store", InventoryManagementSystem.locateCurrentStore(player));
+
+ if (store == null) {
+ Notifier.process(player, NO_STORE_FOUND, getAttributes());
+ return true;
+ }
int quantity = 0;
diff --git a/src/main/java/net/sparkzz/command/sub/SellCommand.java b/src/main/java/net/sparkzz/command/sub/SellCommand.java
index 94a1b5e..5d24da4 100644
--- a/src/main/java/net/sparkzz/command/sub/SellCommand.java
+++ b/src/main/java/net/sparkzz/command/sub/SellCommand.java
@@ -1,6 +1,7 @@
package net.sparkzz.command.sub;
import net.sparkzz.command.SubCommand;
+import net.sparkzz.shops.Store;
import net.sparkzz.util.InventoryManagementSystem;
import net.sparkzz.util.Notifier;
import net.sparkzz.util.Transaction;
@@ -26,9 +27,14 @@ public boolean process(CommandSender sender, Command command, String label, Stri
setArgsAsAttributes(args);
Material material = (Material) setAttribute("material", Material.matchMaterial(args[1]));
Player player = (Player) setAttribute("sender", sender);
- setAttribute("store", InventoryManagementSystem.locateCurrentStore(player));
+ Store store = (Store) setAttribute("store", InventoryManagementSystem.locateCurrentStore(player));
int quantity = (Integer) setAttribute("quantity", 1);
+ if (store == null) {
+ Notifier.process(player, NO_STORE_FOUND, getAttributes());
+ return true;
+ }
+
if (args.length == 3)
quantity = (Integer) setAttribute("quantity", args[2].equalsIgnoreCase("all") ? InventoryManagementSystem.countQuantity((Player) sender, material) : Integer.parseInt(args[2]));
diff --git a/src/main/java/net/sparkzz/command/sub/TransferCommand.java b/src/main/java/net/sparkzz/command/sub/TransferCommand.java
index a36267b..1e422ad 100644
--- a/src/main/java/net/sparkzz/command/sub/TransferCommand.java
+++ b/src/main/java/net/sparkzz/command/sub/TransferCommand.java
@@ -3,6 +3,7 @@
import net.sparkzz.command.SubCommand;
import net.sparkzz.shops.Shops;
import net.sparkzz.shops.Store;
+import net.sparkzz.util.Config;
import net.sparkzz.util.Notifier;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
@@ -43,6 +44,7 @@ public boolean process(CommandSender sender, Command command, String label, Stri
boolean isUUID = args[2].matches("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$");
// TODO: remove mock references once Server mocking is updated to fix issues with getServer()
+ setAttribute("target", args[2]);
Server server = (!Shops.isTest()) ? Shops.getPlugin(Shops.class).getServer() : Shops.getMockServer();
OfflinePlayer targetPlayer = (!isUUID) ? server.getPlayer(args[2]) : server.getOfflinePlayer(UUID.fromString(args[2]));
@@ -54,6 +56,21 @@ public boolean process(CommandSender sender, Command command, String label, Stri
Store store = foundStore.get();
setAttribute("target", targetPlayer.getName());
+
+ if (!sender.isOp()) {
+ int shopsOwned = 0;
+
+ for (Store existingStore : Store.STORES)
+ if (existingStore.getOwner().equals(targetPlayer.getUniqueId())) {
+ shopsOwned++;
+ }
+
+ if (shopsOwned >= (int) setAttribute("max-stores", Config.getMaxOwnedStores())) {
+ Notifier.process(sender, Notifier.CipherKey.STORE_TRANSFER_FAIL_MAX_STORES, getAttributes());
+ return true;
+ }
+ }
+
store.setOwner(targetPlayer.getUniqueId());
Notifier.process(sender, STORE_TRANSFER_SUCCESS, getAttributes());
return true;
diff --git a/src/main/java/net/sparkzz/command/sub/UpdateCommand.java b/src/main/java/net/sparkzz/command/sub/UpdateCommand.java
index 0ab8e03..04ff2cb 100644
--- a/src/main/java/net/sparkzz/command/sub/UpdateCommand.java
+++ b/src/main/java/net/sparkzz/command/sub/UpdateCommand.java
@@ -2,15 +2,19 @@
import net.sparkzz.command.SubCommand;
import net.sparkzz.shops.Store;
+import net.sparkzz.util.Cuboid;
import net.sparkzz.util.InventoryManagementSystem;
import net.sparkzz.util.Notifier;
+import org.bukkit.Bukkit;
import org.bukkit.Material;
+import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.Map;
+import java.util.Optional;
import static net.sparkzz.util.Notifier.CipherKey.*;
@@ -27,10 +31,73 @@ public boolean process(CommandSender sender, Command command, String label, Stri
resetAttributes();
setArgsAsAttributes(args);
Player player = (Player) setAttribute("sender", sender);
- Store store = InventoryManagementSystem.locateCurrentStore(player);
- setAttribute("store", store.getName());
+ Store store = (Store) setAttribute("store", InventoryManagementSystem.locateCurrentStore(player));
+
+ if (args.length >= 8 && args[1].equalsIgnoreCase("location")) {
+ switch (args.length) {
+ case 8 -> {
+ if (store == null) {
+ Notifier.process(player, NO_STORE_FOUND, getAttributes());
+ return true;
+ }
+
+ World world = Bukkit.getWorld((String) setAttribute("world", store.getCuboidLocation().getWorld().getName()));
+
+ store.setCuboidLocation(generateCuboid(world, args[2], args[3], args[4], args[5], args[6], args[7]));
+ }
+ case 9 -> {
+ Optional foundStore = identifyStore((String) setAttribute("store", args[2]));
+ World world = Bukkit.getWorld((String) setAttribute("world", args[2]));
+ store = (Store) setAttribute("store", foundStore.orElse(store));
+
+ if (store == null) {
+ Notifier.process(player, NO_STORE_FOUND, getAttributes());
+ return true;
+ }
+
+ if (foundStore.isEmpty() && world == null) {
+ Notifier.process(sender, Notifier.CipherKey.WORLD_NOT_FOUND, getAttributes());
+ return true;
+ } else if (foundStore.isPresent())
+ world = Bukkit.getWorld((String) setAttribute("world", store.getCuboidLocation().getWorld().getName()));
+
+ store.setCuboidLocation(generateCuboid(world, args[3], args[4], args[5], args[6], args[7], args[8]));
+ }
+ case 10 -> {
+ Optional foundStore = identifyStore((String) setAttribute("store", args[2]));
+ World world = Bukkit.getWorld((String) setAttribute("world", args[3]));
+
+ if (foundStore.isEmpty()) {
+ Notifier.process(sender, STORE_NO_STORE_FOUND, getAttributes());
+ return true;
+ }
+
+ store = foundStore.get();
+ setAttribute("store", store.getName());
+
+ if (world == null) {
+ Notifier.process(sender, Notifier.CipherKey.WORLD_NOT_FOUND, getAttributes());
+ return true;
+ }
+
+ store.setCuboidLocation(generateCuboid(world, args[4], args[5], args[6], args[7], args[8], args[9]));
+ }
+ default -> {
+ return false;
+ }
+ }
+
+ Notifier.process(sender, Notifier.CipherKey.STORE_UPDATE_SUCCESS_LOCATION, getAttributes());
+ return true;
+ }
+
if (args.length >= 2) setAttribute("material", args[1]);
+ if (store == null) {
+ Notifier.process(player, NO_STORE_FOUND, getAttributes());
+ return true;
+ }
+
if (args.length == 3) {
switch (args[1].toLowerCase()) {
case "infinite-funds" -> {
@@ -104,4 +171,15 @@ public boolean process(CommandSender sender, Command command, String label, Stri
Notifier.process(sender, INVALID_MATERIAL, getAttributes());
return false;
}
+
+ private Cuboid generateCuboid(World world, String x1String, String y1String, String z1String, String x2String, String y2String, String z2String) {
+ double x1 = (double) setAttribute("x1", Double.parseDouble(x1String));
+ double y1 = (double) setAttribute("y1", Double.parseDouble(y1String));
+ double z1 = (double) setAttribute("z1", Double.parseDouble(z1String));
+ double x2 = (double) setAttribute("x2", Double.parseDouble(x2String));
+ double y2 = (double) setAttribute("y2", Double.parseDouble(y2String));
+ double z2 = (double) setAttribute("z2", Double.parseDouble(z2String));
+
+ return new Cuboid(world, x1, y1, z1, x2, y2, z2);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/net/sparkzz/command/sub/WithdrawCommand.java b/src/main/java/net/sparkzz/command/sub/WithdrawCommand.java
index 5827541..2930346 100644
--- a/src/main/java/net/sparkzz/command/sub/WithdrawCommand.java
+++ b/src/main/java/net/sparkzz/command/sub/WithdrawCommand.java
@@ -27,6 +27,11 @@ public boolean process(CommandSender sender, Command command, String label, Stri
Store store = (Store) setAttribute("store", InventoryManagementSystem.locateCurrentStore(player));
double amount = (Double) setAttribute("amount", (args[1].equalsIgnoreCase("all")) ? store.getBalance() : Double.parseDouble(args[1]));
+ if (store == null) {
+ Notifier.process(player, NO_STORE_FOUND, getAttributes());
+ return true;
+ }
+
if (amount < 0) throw new NumberFormatException(String.format("Invalid amount: \"%s\"", args[1]));
if (!store.getOwner().equals(player.getUniqueId())) {
diff --git a/src/main/java/net/sparkzz/event/EntranceListener.java b/src/main/java/net/sparkzz/event/EntranceListener.java
new file mode 100644
index 0000000..d935785
--- /dev/null
+++ b/src/main/java/net/sparkzz/event/EntranceListener.java
@@ -0,0 +1,53 @@
+package net.sparkzz.event;
+
+import net.sparkzz.shops.Store;
+import net.sparkzz.util.Cuboid;
+import net.sparkzz.util.Notifiable;
+import net.sparkzz.util.Notifier;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerMoveEvent;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Listener for checking whether a player enters the bounds of a store
+ */
+public class EntranceListener extends Notifiable implements Listener {
+
+ private final Map playerStoreStatus = new HashMap<>();
+
+ /**
+ * Checks if a player has entered or exited a shop and notifies accordingly
+ *
+ * @param event the PlayerMoveEvent used to determine the player's location
+ */
+ @EventHandler
+ public void onPlayerMove(PlayerMoveEvent event) {
+ Player player = (Player) setAttribute("player", event.getPlayer());
+ boolean isInShop = false;
+
+ for (Store store : Store.STORES) {
+ Cuboid cuboid = store.getCuboidLocation();
+
+ if (cuboid == null || cuboid.getWorld() == null || !cuboid.getWorld().equals(player.getWorld()))
+ continue;
+
+ if (cuboid.isPlayerWithin(player)) {
+ isInShop = true;
+ setAttribute("store", store);
+ break;
+ }
+ }
+
+ if (isInShop && !playerStoreStatus.getOrDefault(player, false)) {
+ playerStoreStatus.put(player, true);
+ Notifier.process(player, Notifier.CipherKey.STORE_WELCOME_MSG, getAttributes());
+ } else if (!isInShop && playerStoreStatus.getOrDefault(player, true)) {
+ playerStoreStatus.put(player, false);
+ Notifier.process(player, Notifier.CipherKey.STORE_GOODBYE_MSG, getAttributes());
+ }
+ }
+}
diff --git a/src/main/java/net/sparkzz/shops/Shops.java b/src/main/java/net/sparkzz/shops/Shops.java
index fc63ff4..eb001bd 100644
--- a/src/main/java/net/sparkzz/shops/Shops.java
+++ b/src/main/java/net/sparkzz/shops/Shops.java
@@ -2,6 +2,8 @@
import net.milkbowl.vault.economy.Economy;
import net.sparkzz.command.CommandManager;
+import net.sparkzz.event.EntranceListener;
+import net.sparkzz.util.Notifier;
import net.sparkzz.util.Warehouse;
import org.bukkit.Server;
import org.bukkit.plugin.PluginDescriptionFile;
@@ -21,7 +23,6 @@ public class Shops extends JavaPlugin {
private static boolean isTest = false;
private static Server server;
- private static Store shop;
private static Economy econ;
private static PluginDescriptionFile desc;
@@ -49,6 +50,7 @@ protected Shops(
File file) {
super(loader, description, dataFolder, file);
isTest = true;
+ setMockServer(this.getServer());
}
/**
@@ -75,10 +77,13 @@ public void onEnable() {
desc = this.getDescription();
CommandManager.registerCommands(this);
+ getServer().getPluginManager().registerEvents(new EntranceListener(), this);
if (!isTest && !Warehouse.loadConfig(this))
getServer().getPluginManager().disablePlugin(this);
+ Notifier.loadCustomMessages();
+
log.info("Shops has been enabled!");
}
@@ -130,24 +135,6 @@ public static Server getMockServer() {
return server;
}
- /**
- * Get the default store, which will be replaced in the future once location-based stores are enabled
- *
- * @return the default store
- */
- public static Store getDefaultShop() {
- return shop;
- }
-
- /**
- * Sets the default store, which will be replaced in the future once location-based shops are enabled
- *
- * @param store the store to be set as default
- */
- public static void setDefaultShop(Store store) {
- shop = store;
- }
-
/**
* Configures the mock server for tests
*
diff --git a/src/main/java/net/sparkzz/shops/Store.java b/src/main/java/net/sparkzz/shops/Store.java
index 0b3d5f2..c6ee15b 100644
--- a/src/main/java/net/sparkzz/shops/Store.java
+++ b/src/main/java/net/sparkzz/shops/Store.java
@@ -1,5 +1,6 @@
package net.sparkzz.shops;
+import net.sparkzz.util.Cuboid;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
@@ -20,9 +21,12 @@ public class Store {
* This List contains all stores that have been created
*/
public static final ArrayList STORES = new ArrayList<>();
+ private static Store defaultStore;
@Setting private boolean infFunds = false;
@Setting private boolean infStock = false;
+ // TODO: Create a map of Worlds to Cuboids to allow for multiple locations for the same store
+ @Setting("location") private Cuboid cuboidLocation;
@Setting private double balance;
@Setting private String name;
// item : attribute, value (block_dirt : quantity, 550)
@@ -52,7 +56,7 @@ public Store(String name) {
}
/**
- * Creates a store with the provided name
+ * Creates a store with the provided name and owner
*
* @param name the name of the store to be created
* @param owner the owner's UUID to be added to the store
@@ -62,6 +66,37 @@ public Store(String name, UUID owner) {
this.owner = owner;
}
+ /**
+ * Creates a store with the provided name, owner, and cuboid location
+ *
+ * @param name the name of the store to be created
+ * @param owner the owner's UUID to be added to the store
+ * @param cuboidLocation the Cuboid location where this store is located
+ */
+ public Store(String name, UUID owner, Cuboid cuboidLocation) {
+ this(name);
+ this.owner = owner;
+ this.cuboidLocation = cuboidLocation;
+ }
+
+ /**
+ * Gets the default store
+ *
+ * @return the default store
+ */
+ public static Store getDefaultStore() {
+ return defaultStore;
+ }
+
+ /**
+ * Sets the default store
+ *
+ * @param store the default store to be set
+ */
+ public static void setDefaultStore(Store store) {
+ defaultStore = store;
+ }
+
/**
* Check if the store contains the provided material
*
@@ -109,6 +144,14 @@ public double getBuyPrice(Material material) {
return (items.containsKey(material) ? items.get(material).get("buy").doubleValue() : -1D);
}
+ /**
+ * Gets the cuboid location of the store
+ * @return
+ */
+ public Cuboid getCuboidLocation() {
+ return cuboidLocation;
+ }
+
/**
* Checks the sell price of a material
*
@@ -119,15 +162,6 @@ public double getSellPrice(Material material) {
return (items.containsKey(material) ? items.get(material).get("sell").doubleValue() : -1D);
}
- /**
- * Get the store's unique ID
- *
- * @return the store's UUID
- */
- public UUID getUUID() {
- return uuid;
- }
-
/**
* Get the items within the store with their attributes
*
@@ -165,6 +199,15 @@ public UUID getOwner() {
return owner;
}
+ /**
+ * Get the store's unique ID
+ *
+ * @return the store's UUID
+ */
+ public UUID getUUID() {
+ return uuid;
+ }
+
/**
* Add funds to the store
*
@@ -274,6 +317,15 @@ public void setBalance(double balance) {
this.balance = balance;
}
+ /**
+ * Sets the bounds of the store based on the Cuboid inputted
+ *
+ * @param cuboid the store bounds defined by a cuboid
+ */
+ public void setCuboidLocation(Cuboid cuboid) {
+ this.cuboidLocation = cuboid;
+ }
+
/**
* Sets the infinite funds flag based on the input value
*
diff --git a/src/main/java/net/sparkzz/util/Config.java b/src/main/java/net/sparkzz/util/Config.java
new file mode 100644
index 0000000..264d667
--- /dev/null
+++ b/src/main/java/net/sparkzz/util/Config.java
@@ -0,0 +1,278 @@
+package net.sparkzz.util;
+
+import net.sparkzz.shops.Shops;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.spongepowered.configurate.CommentedConfigurationNode;
+import org.spongepowered.configurate.serialize.SerializationException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * Configuration class for accessing and updating
+ */
+public class Config {
+
+ private static final Logger log = (Shops.isTest() ? Shops.getMockServer().getLogger() : Shops.getPlugin(Shops.class).getLogger());
+
+ private static CommentedConfigurationNode rootNode;
+
+ public static CommentedConfigurationNode getRootNode() {
+ return rootNode;
+ }
+
+ private static double[] getDimensions(CommentedConfigurationNode node) {
+ double[] dimensions = new double[3];
+
+ dimensions[0] = node.node("x").getDouble();
+ dimensions[1] = node.node("y").getDouble();
+ dimensions[2] = node.node("z").getDouble();
+
+ return dimensions;
+ }
+
+ private static void setDimensions(CommentedConfigurationNode node, double x, double y, double z) {
+ try {
+ node.node("x").set(x);
+ node.node("y").set(y);
+ node.node("z").set(z);
+ } catch (SerializationException exception) {
+ log.severe(exception.getMessage());
+ }
+ }
+
+ /**
+ * Gets the maximum (limit) dimensions to prevent players from creating stores greater than specified X, Y, and Z
+ * coordinates
+ *
+ * @return the maximum dimensions
+ */
+ public static double[] getMaxDimensions() {
+ CommentedConfigurationNode maxDimensions = rootNode.node("store", "max-dimensions");
+
+ return getDimensions(maxDimensions);
+ }
+
+ /**
+ * Gets the maximum (limit) volume to prevent players from creating stores where the product of the difference of
+ * their X1, Y1, Z1, X2, Y2, and Z2 coordinates has a volume greater than said maximum
+ *
+ * @return the maximum volume
+ */
+ public static double getMaxVolume() {
+ return rootNode.node("store", "max-volume").getDouble();
+ }
+
+ /**
+ * Gets the minimum (limit) volume to prevent players from creating stores where the product of the difference of
+ * their X1, Y1, Z1, X2, Y2, and Z2 coordinates has a volume lesser than said minimum
+ *
+ * @return the minimum volume
+ */
+ public static double getMinVolume() {
+ return rootNode.node("store", "min-volume").getDouble();
+ }
+
+ /**
+ * Gets the minimum (limit) dimensions to prevent players from creating stores lesser than specified X, Y, and Z
+ * coordinates
+ *
+ * @return the minimum dimensions
+ */
+ public static double[] getMinDimensions() {
+ CommentedConfigurationNode minDimensions = rootNode.node("store", "min-dimensions");
+
+ return getDimensions(minDimensions);
+ }
+
+ /**
+ * Sets the limit of stores that a player can own (this can be overridden by an admin creating a store and
+ * transferring it to said player
+ *
+ * @return the maximum number of stores a player can own or create
+ */
+ public static int getMaxOwnedStores() {
+ return rootNode.node("store", "max-owned-stores").getInt();
+ }
+
+ /**
+ * Gets the list of off-limits cuboids to prevent players from creating stores within "off-limits" zones
+ *
+ * @return the off-limits cuboids
+ */
+ public static List getOffLimitsCuboids() {
+ List cuboids = new ArrayList<>();
+
+ try {
+ List offLimitsAreas = rootNode.node("store", "off-limits").getList(String.class);
+
+ if (offLimitsAreas == null || offLimitsAreas.isEmpty())
+ return cuboids;
+
+ for (String area : offLimitsAreas) {
+ if (!(area.contains("world(") && area.contains("start(") && area.contains("end(")))
+ continue;
+
+ area = area.replace(" ", "");
+
+ World world;
+ double x1, y1, z1, x2, y2, z2;
+ int currIndex;
+
+ world = Bukkit.getWorld(area.substring(
+ currIndex = area.indexOf("world(") + 6, area.indexOf(")", currIndex)
+ ));
+
+ String[] startCoordinate = area.substring(
+ currIndex = area.indexOf("start(") + 6, area.indexOf(")", currIndex)
+ ).split(",");
+
+ String[] endCoordinate = area.substring(
+ currIndex = area.indexOf("end(") + 4, area.indexOf(")", currIndex)
+ ).split(",");
+
+ x1 = Double.parseDouble(startCoordinate[0]);
+ y1 = Double.parseDouble(startCoordinate[1]);
+ z1 = Double.parseDouble(startCoordinate[2]);
+ x2 = Double.parseDouble(endCoordinate[0]);
+ y2 = Double.parseDouble(endCoordinate[1]);
+ z2 = Double.parseDouble(endCoordinate[2]);
+
+ Cuboid cuboid = new Cuboid(world, x1, y1, z1, x2, y2, z2);
+
+ cuboids.add(cuboid);
+ }
+ } catch (SerializationException exception) {
+ log.severe("Unable to load off-limits areas");
+ } catch (NumberFormatException exception) {
+ log.severe(exception.getMessage());
+ }
+
+ return cuboids;
+ }
+
+ /**
+ * Gets the custom response message to be sent to the player in place of the defaults
+ *
+ * @param key the CipherKey to be used as the map key
+ * @return the custom response message
+ */
+ public static String getMessage(Notifier.CipherKey key) {
+ return rootNode.node("messages").node(key.name()).getString();
+ }
+
+ /**
+ * Adds a new off-limits cuboid area to prevent players from building in that area
+ *
+ * @param cuboid the cuboid area to be added
+ */
+ public static void addOffLimitsArea(Cuboid cuboid) {
+ try {
+ CommentedConfigurationNode offLimitsNode = rootNode.node("store", "off-limits");
+ List offLimitsAreas = offLimitsNode.getList(String.class);
+ if (offLimitsAreas == null)
+ offLimitsAreas = new ArrayList<>();
+
+ offLimitsAreas.add(String.format("world(%s),start(%f,%f,%f),end(%f,%f,%f)", cuboid.getWorld().getName(),
+ cuboid.getX1(), cuboid.getY1(), cuboid.getZ1(), cuboid.getX2(), cuboid.getY2(), cuboid.getZ2()));
+
+ offLimitsNode.setList(String.class, offLimitsAreas);
+ } catch (SerializationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Sets the maximum dimensions
+ *
+ * @param x the X coordinate to be set
+ * @param y the Y coordinate to be set
+ * @param z the Z coordinate to be set
+ */
+ public static void setMaxDimensions(double x, double y, double z) {
+ CommentedConfigurationNode maxDimensionsNode = rootNode.node("store", "max-dimensions");
+
+ setDimensions(maxDimensionsNode, x, y, z);
+ }
+
+ /**
+ * Sets the limit of stores that a player can own
+ *
+ * @param quantity the number of stores that a player can own
+ */
+ public static void setMaxOwnedStores(int quantity) {
+ try {
+ rootNode.node("store", "max-owned-stores").set(quantity);
+ } catch (SerializationException exception) {
+ log.severe(exception.getMessage());
+ }
+ }
+
+ /**
+ * Sets the maximum volume that a store can be based on the product of the difference of its X1, Y1, Z1, X2, Y2, and
+ * Z2 coordinates
+ *
+ * @param volume the maximum volume to be set
+ */
+ public static void setMaxVolume(double volume) {
+ try {
+ rootNode.node("store", "max-volume").set(volume);
+ } catch (SerializationException exception) {
+ log.severe(exception.getMessage());
+ }
+ }
+
+ /**
+ * Sets the minimum dimensions
+ *
+ * @param x the X coordinate to be set
+ * @param y the Y coordinate to be set
+ * @param z the Z coordinate to be set
+ */
+ public static void setMinDimensions(double x, double y, double z) {
+ CommentedConfigurationNode minDimensionsNode = rootNode.node("store", "min-dimensions");
+
+ setDimensions(minDimensionsNode, x, y, z);
+ }
+
+ /**
+ * Sets the minimum volume that a store can be based on the product of the difference of its X1, Y1, Z1, X2, Y2, and
+ * Z2 coordinates
+ *
+ * @param volume the minimum volume to be set
+ */
+ public static void setMinVolume(double volume) {
+ try {
+ rootNode.node("store", "min-volume").set(volume);
+ } catch (SerializationException exception) {
+ log.severe(exception.getMessage());
+ }
+ }
+
+ /**
+ * Sets the off-limits areas to the newly defined list of cuboids
+ *
+ * @param cuboids the cuboids to be set as off-limits areas
+ */
+ public static void setOffLimitsAreas(List cuboids) {
+ try {
+ rootNode.node("store").node("off-limits").setList(String.class, null);
+
+ for (Cuboid cuboid : cuboids)
+ addOffLimitsArea(cuboid);
+ } catch (SerializationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Sets the root node of the configuration
+ *
+ * @param node the root node of the configuration
+ */
+ public static void setRootNode(CommentedConfigurationNode node) {
+ rootNode = node;
+ }
+}
diff --git a/src/main/java/net/sparkzz/util/Cuboid.java b/src/main/java/net/sparkzz/util/Cuboid.java
new file mode 100644
index 0000000..198afc3
--- /dev/null
+++ b/src/main/java/net/sparkzz/util/Cuboid.java
@@ -0,0 +1,340 @@
+package net.sparkzz.util;
+
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.spongepowered.configurate.objectmapping.ConfigSerializable;
+
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * The Cuboid class stores the starting and ending location for a store along with the world
+ */
+@ConfigSerializable
+public class Cuboid {
+
+ private World world;
+ private double x1, y1, z1;
+ private double x2, y2, z2;
+
+ /**
+ * This constructor is required for the deserializer
+ *
+ * @deprecated Do not use this constructor!
+ */
+ @Deprecated
+ public Cuboid() {}
+
+ /**
+ * Constructor for creating a Cuboid within the world
+ *
+ * @param world the world the cuboid is located within
+ * @param x1 the starting 'x' position in the world
+ * @param y1 the starting 'y' position in the world
+ * @param z1 the starting 'z' position in the world
+ * @param x2 the ending 'x' position in the world
+ * @param y2 the ending 'y' position in the world
+ * @param z2 the ending 'z' position in the world
+ */
+ public Cuboid(@Nullable final World world, final double x1, final double y1, final double z1, final double x2, final double y2, final double z2) {
+ this.world = world;
+ this.x1 = x1;
+ this.y1 = y1;
+ this.z1 = z1;
+ this.x2 = x2;
+ this.y2 = y2;
+ this.z2 = z2;
+ }
+
+ /**
+ * determines all points (as integers) between two coordinates on a plane, this plane can be adjusted by the
+ * respective third coordinate
+ * (ex: XY would use Z1 for the front face, and Z2 for the back face)
+ *
+ * face 1 is the XY face (vertical front/rear) (adjust by Z1 and Z2)
+ * face 2 is the ZY face (vertical sides) (adjust by X1 and X2)
+ * face 3 is the XZ face (horizontal) (adjust by Y1 and Y2)
+ *
+ * @return A list of coordinates that create a point on a 2D plane
+ */
+ private List> getFacePoints() {
+ List> facePoints = new LinkedList<>();
+ facePoints.add(0, new ArrayList<>());
+ facePoints.add(1, new ArrayList<>());
+ facePoints.add(2, new ArrayList<>());
+
+ double minX = Math.min(x1, x2);
+ double maxX = Math.max(x1, x2);
+ double minY = Math.min(y1, y2);
+ double maxY = Math.max(y1, y2);
+ double minZ = Math.min(z1, z2);
+ double maxZ = Math.max(z1, z2);
+
+ // loop through 3 faces
+ for (int i = 0; i <= 2; i++) {
+ int min, max, min2, max2;
+
+ switch (i) {
+ case 0 -> {
+ min = (int) Math.floor(minX);
+ max = (int) Math.ceil(maxX);
+ min2 = (int) Math.floor(minY);
+ max2 = (int) Math.ceil(maxY);
+ }
+ case 1 -> {
+ min = (int) Math.floor(minZ);
+ max = (int) Math.ceil(maxZ);
+ min2 = (int) Math.floor(minY);
+ max2 = (int) Math.ceil(maxY);
+ }
+ default -> {
+ min = (int) Math.floor(minX);
+ max = (int) Math.ceil(maxX);
+ min2 = (int) Math.floor(minZ);
+ max2 = (int) Math.ceil(maxZ);
+ }
+ }
+
+ for (double x = min; x <= max; x++) {
+ for (double y = min2; y <= max2; y++) {
+ facePoints.get(i).add(new Point2D.Double(x, y));
+ }
+ }
+ }
+
+ return facePoints;
+ }
+
+ /**
+ * Checks whether the current cuboid intersects another cuboid
+ *
+ * @param cuboid the other cuboid to check intersections against
+ * @return whether the current cuboid intersects another cuboid
+ */
+ public boolean intersects(Cuboid cuboid) {
+ boolean intersects = false;
+
+ if (cuboid == null || world == null || cuboid.getWorld() == null || !world.equals(cuboid.getWorld()))
+ return intersects;
+
+ if (this.equals(cuboid))
+ return true;
+
+ List> faces = cuboid.getFacePoints();
+
+ for (int i = 0; i <= 2; i++) {
+ List facePoints = faces.get(i);
+
+ switch (i) {
+ case 0 -> {
+ int minZ = (int) Math.floor(Math.min(cuboid.getZ1(), cuboid.getZ2()));
+ int maxZ = (int) Math.floor(Math.max(cuboid.getZ1(), cuboid.getZ2()));
+
+ for (Point2D point : facePoints) {
+ if (isPointWithin(point.getX(), point.getY(), minZ))
+ return true;
+ if (isPointWithin(point.getX(), point.getY(), maxZ))
+ return true;
+ }
+ }
+ case 1 -> {
+ int minX = (int) Math.floor(Math.min(cuboid.getX1(), cuboid.getX2()));
+ int maxX = (int) Math.floor(Math.max(cuboid.getX1(), cuboid.getX2()));
+
+ for (Point2D point : facePoints) {
+ if (isPointWithin(minX, point.getY(), point.getX()))
+ return true;
+ if (isPointWithin(maxX, point.getY(), point.getX()))
+ return true;
+ }
+ }
+ default -> {
+ int minY = (int) Math.floor(Math.min(cuboid.getY1(), cuboid.getY2()));
+ int maxY = (int) Math.floor(Math.max(cuboid.getY1(), cuboid.getY2()));
+
+ for (Point2D point : facePoints) {
+ if (isPointWithin(point.getX(), minY, point.getY()))
+ return true;
+ if (isPointWithin(point.getX(), maxY, point.getY()))
+ return true;
+ }
+ }
+ }
+ }
+
+ return intersects;
+ }
+
+ /**
+ * Determines whether a player is within the bounds of the cuboid
+ *
+ * @param player the player to be checked
+ * @return whether the player is within the bounds of the cuboid
+ */
+ public boolean isPlayerWithin(Player player) {
+ Location playerLocation = player.getLocation();
+ double playerX = playerLocation.getX();
+ double playerY = playerLocation.getY();
+ double playerZ = playerLocation.getZ();
+
+ return world == player.getWorld() &&
+ this.x1 <= playerX && playerX <= this.x2 &&
+ this.y1 <= playerY && playerY <= this.y2 &&
+ this.z1 <= playerZ && playerZ <= this.z2;
+ }
+
+ /**
+ * Determines whether a point is within the bounds of the cuboid
+ *
+ * @param x the x coordinate to check
+ * @param y the y coordinate to check
+ * @param z the z coordinate to check
+ * @return whether the point is within the bounds of the cuboid
+ */
+ public boolean isPointWithin(double x, double y, double z) {
+ return this.x1 < x && x < this.x2 &&
+ this.y1 < y && y < this.y2 &&
+ this.z1 < z && z < this.z2;
+ }
+
+ /**
+ * Gets the X1 coordinate
+ *
+ * @return the value of x1
+ */
+ public double getX1() {
+ return x1;
+ }
+
+ /**
+ * Gets the X2 coordinate
+ *
+ * @return the value of x2
+ */
+ public double getX2() {
+ return x2;
+ }
+
+ /**
+ * Gets the Y1 coordinate
+ *
+ * @return the value of y1
+ */
+ public double getY1() {
+ return y1;
+ }
+
+ /**
+ * Gets the Y2 coordinate
+ *
+ * @return the value of y2
+ */
+ public double getY2() {
+ return y2;
+ }
+
+ /**
+ * Gets the Z1 coordinate
+ *
+ * @return the value of z1
+ */
+ public double getZ1() {
+ return z1;
+ }
+
+ /**
+ * Gets the Z2 coordinate
+ *
+ * @return the value of z2
+ */
+ public double getZ2() {
+ return z2;
+ }
+
+ /**
+ * Gets the world that the Cuboid is contained within
+ *
+ * @return the world that the Cuboid is contained within
+ */
+ @Nullable
+ public World getWorld() {
+ if (this.world == null)
+ return null;
+
+ return this.world;
+ }
+
+ /**
+ * Sets the world for the store
+ *
+ * @param world the world to be associated with the Cuboid
+ */
+ public void setWorld(@NotNull World world) {
+ this.world = world;
+ }
+
+ /**
+ * Updates the starting location for the cuboid
+ *
+ * @param x the starting 'x' coordinate
+ * @param y the starting 'y' coordinate
+ * @param z the starting 'z' coordinate
+ */
+ public void updateStartLocation(double x, double y, double z) {
+ this.x1 = x;
+ this.y1 = y;
+ this.z1 = z;
+ }
+
+ /**
+ * Updates the ending location for the cuboid
+ *
+ * @param x the ending 'x' coordinate
+ * @param y the ending 'y' coordinate
+ * @param z the ending 'z' coordinate
+ */
+ public void updateEndingLocation(double x, double y, double z) {
+ this.x2 = x;
+ this.y2 = y;
+ this.z2 = z;
+ }
+
+ /**
+ * Determines whether this cuboid is equal to another object
+ *
+ * @param object the other object to compare against
+ * @return whether the cuboids are equal
+ */
+ @Override
+ public boolean equals(Object object) {
+ if (this == object)
+ return true;
+
+ if (object == null || getClass() != object.getClass())
+ return false;
+
+ Cuboid otherCuboid = (Cuboid) object;
+ return this.world.equals(otherCuboid.getWorld()) &&
+ this.x1 == otherCuboid.getX1() &&
+ this.y1 == otherCuboid.getY1() &&
+ this.z1 == otherCuboid.getZ1() &&
+ this.x2 == otherCuboid.getX2() &&
+ this.y2 == otherCuboid.getY2() &&
+ this.z2 == otherCuboid.getZ2();
+ }
+
+ /**
+ * Generates a string containing the world name and coordinate points
+ *
+ * @return a formatted string for the Cuboid
+ */
+ @Override
+ public String toString() {
+ return String.format("%s(%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f)", ((world != null) ? world.getName() + ", " : ""), x1, y1, z1, x2, y2, z2);
+ }
+}
diff --git a/src/main/java/net/sparkzz/util/InventoryManagementSystem.java b/src/main/java/net/sparkzz/util/InventoryManagementSystem.java
index 641db19..ac4c0cf 100644
--- a/src/main/java/net/sparkzz/util/InventoryManagementSystem.java
+++ b/src/main/java/net/sparkzz/util/InventoryManagementSystem.java
@@ -1,11 +1,12 @@
package net.sparkzz.util;
-import net.sparkzz.shops.Shops;
import net.sparkzz.shops.Store;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.PlayerInventory;
+import java.util.List;
import java.util.ListIterator;
import java.util.Map;
@@ -25,11 +26,33 @@ public class InventoryManagementSystem {
* @return whether the provided quantity of material can be added to the player's inventory
*/
public static boolean canInsert(Player player, Material material, int quantity) {
- int availableSpace = getAvailableSpace(player, material);
+ int availableSpace = getAvailableSpace(player.getInventory(), material);
return (quantity <= availableSpace);
}
+ /**
+ * Checks whether the provided material and quantity can be added to the player's inventory
+ *
+ * @param player the player to have their inventory checked
+ * @param items the item stacks to be checked if they can be added to the player's inventory
+ * @return whether the provided quantity of material can be added to the player's inventory
+ */
+ public static boolean canInsertAll(Player player, List items) {
+ PlayerInventory inventory = player.getInventory();
+ boolean canInsertAll = true;
+
+ for (ItemStack item : items) {
+ int availableSpace = getAvailableSpace(inventory, item.getType());
+
+ if (item.getAmount() <= availableSpace)
+ inventory.addItem(item);
+ else canInsertAll = false;
+ }
+
+ return canInsertAll;
+ }
+
/**
* Checks whether the provided material and quantity can be removed from the player's inventory
*
@@ -101,16 +124,15 @@ public static int countQuantity(Store store, Material material) {
* Gets the available space in the player's inventory based on the material's max stack size, it will even check
* partial stacks of the input material
*
- * @param player the player to have their inventory queried
+ * @param inventory the player's inventory to be queried
* @param material the material to be used to query the player's inventory
* @return the available space based on the material's stack size and inventory space
*/
- private static int getAvailableSpace(Player player, Material material) {
- ListIterator iterator = player.getInventory().iterator();
+ private static int getAvailableSpace(PlayerInventory inventory, Material material) {
int availableSpace = 0;
- while (iterator.hasNext()) {
- ItemStack stack = iterator.next();
+ for (int i = 0; i <= 35; i++) {
+ ItemStack stack = inventory.getItem(i);
if (stack == null)
availableSpace += material.getMaxStackSize();
@@ -150,7 +172,15 @@ public static int getAvailableSpace(Store store, Material material) {
* @return the store the player is currently located in
*/
public static Store locateCurrentStore(Player player) {
- // TODO: locate the player within the bounds of a current shop
- return Shops.getDefaultShop();
+ Store store = Store.getDefaultStore();
+
+ for (Store currentStore : Store.STORES) {
+ if (currentStore.getCuboidLocation() != null && currentStore.getCuboidLocation().isPlayerWithin(player)) {
+ store = currentStore;
+ break;
+ }
+ }
+
+ return store;
}
}
\ No newline at end of file
diff --git a/src/main/java/net/sparkzz/util/Notifier.java b/src/main/java/net/sparkzz/util/Notifier.java
index d2258eb..05d625d 100644
--- a/src/main/java/net/sparkzz/util/Notifier.java
+++ b/src/main/java/net/sparkzz/util/Notifier.java
@@ -27,6 +27,35 @@ public class Notifier {
private static final Map messages = new HashMap<>();
private static final String lineSeparator = System.getProperty("line.separator");
+ /**
+ * Sends the CommandSender a usage message based off invalid command usage
+ *
+ * @param target the target user to send a message to
+ * @param args the arguments for determining the subcommand
+ * @return true if handled, false if default
+ */
+ public static boolean usageSubCommand(CommandSender target, String[] args) {
+ String message = "/shop ";
+
+ message += switch (args[0]) {
+ case "add" -> (args.length < 3 ? "add [|all]" : "add [|all]");
+ case "remove" -> "remove [|all]";
+ case "update" -> "update [|||]";
+ case "buy" -> "buy []";
+ case "sell" -> "sell [|all]";
+ case "create" -> "create ";
+ case "delete" -> "delete [||~]";
+ case "transfer" -> "transfer [||~] ";
+ case "deposit" -> "deposit ";
+ case "withdraw" -> "withdraw ";
+ default -> "default";
+ };
+
+ if (message.contains("default")) return false;
+ target.sendMessage(message);
+ return true;
+ }
+
/**
* Composes a String from the CipherKey by either the default value or a custom value in the messages Map
*
@@ -65,13 +94,20 @@ public static String format(String input, @Nullable Map attribut
}
/**
- * Adds an entry to the messages Map which will be used when composing messages instead of the defaults in the enum
- *
- * @param cipherKey the key to have a value mapped
- * @param message the custom message to be mapped to the CipherKey
+ * Loads custom messages from the config to replace default messages in CipherKey
*/
- public static void updateMessage(CipherKey cipherKey, String message) {
- messages.put(cipherKey, message);
+ public static void loadCustomMessages() {
+ if (Config.getRootNode() == null)
+ return;
+
+ CipherKey[] key = CipherKey.values();
+
+ for (CipherKey cipherKey : key) {
+ String message = Config.getMessage(cipherKey);
+
+ if (message != null && !message.isEmpty())
+ updateMessage(cipherKey, message);
+ }
}
/**
@@ -86,41 +122,22 @@ public static void process(CommandSender target, CipherKey cipherKey, Map (args.length < 3 ? "add [|all]" : "add [|all]");
- case "remove" -> "remove [|all]";
- case "update" -> "update [|||]";
- case "buy" -> "buy []";
- case "sell" -> "sell [|all]";
- case "create" -> "create ";
- case "delete" -> "delete [||~]";
- case "transfer" -> "transfer [||~] ";
- case "deposit" -> "deposit ";
- case "withdraw" -> "withdraw ";
- default -> "default";
- };
-
- if (message.contains("default")) return false;
- target.sendMessage(message);
- return true;
+ public static void resetMessage(CipherKey cipherKey) {
+ messages.remove(cipherKey);
}
/**
- * Resets the custom message to the default by deleting it from the messages Map
+ * Adds an entry to the messages Map which will be used when composing messages instead of the defaults in the enum
*
- * @param cipherKey the key for determining the message value
+ * @param cipherKey the key to have a value mapped
+ * @param message the custom message to be mapped to the CipherKey
*/
- public static void resetMessage(CipherKey cipherKey) {
- messages.remove(cipherKey);
+ public static void updateMessage(CipherKey cipherKey, String message) {
+ messages.put(cipherKey, message);
}
/**
@@ -149,29 +166,44 @@ public enum CipherKey {
MATERIAL_EXISTS_STORE("§cThis material already exists in the store, use `/shop update {material}` to update this item"),
MATERIAL_MISSING_STORE("§cThis material doesn't currently exist in the store, use `/shop add {material}` to add this item"),
NO_PERMS_CMD("§cYou do not have permission to use this command!"),
+ NO_PERMS_CREATE_OTHER("§cYou do not have permission to create stores for other players!"),
NO_PERMS_INF_FUNDS("§cYou do not have permission to set infinite funds in your store!"),
NO_PERMS_INF_STOCK("§cYou do not have permission to set infinite stock in your store!"),
+ NO_STORE_FOUND("§cYou are not currently in a store!"),
NOT_BUYING("§cThe store is not buying any of these at this time!"),
NOT_BUYING_ANYMORE("§cThe Store is not buying any more of these at this time!"),
NOT_SELLING("§cThe store is not selling any of these at this time!"),
NOT_OWNER("§cYou are not the owner of this store, you cannot perform this command!"),
ONLY_PLAYERS_CMD("§cOnly players can use this command!"),
- PLAYER_NOT_FOUND("§aPlayer ({arg2}) not found!"),
+ PLAYER_NOT_FOUND("§cPlayer ({target}) not found!"),
REMOVE_INSUFFICIENT_INV_PLAYER("§cYou don't have enough inventory space to remove §6{material}§c from your store, please try specifying a quantity then removing once the store quantity is lesser!"),
REMOVE_SUCCESS("§aYou have successfully removed §6{material}§a from the store!"),
REMOVE_SUCCESS_QUANTITY("§aYou have successfully removed §6{quantity} §aof §6{material}§a to the store!"),
SELL_SUCCESS("§aSuccess! You have sold §6{quantity}§a of §6{material}§a for §6{cost}§a."),
STORE_CREATE_SUCCESS("§aYou have successfully created §6{store}§a!"),
+ STORE_CREATE_SUCCESS_OTHER_PLAYER("§aYou have successfully created §6{store}§a for §6{target}§a!"),
+ STORE_CREATE_FAIL_MAX_DIMS("§cYou can't create a store that large!§f Maximum dimensions: ({limit-max-x}, {limit-max-y}, {limit-max-z})."),
+ STORE_CREATE_FAIL_MIN_DIMS("§cYou can't create a store that small!§f Minimum dimensions: ({limit-min-x}, {limit-min-y}, {limit-min-z})."),
+ STORE_CREATE_FAIL_MAX_STORES("§cYou can't create any more stores!§f Maximum stores: {max-stores}."),
+ STORE_CREATE_FAIL_MAX_VOL("§cYou can't create a store that large!§f Maximum volume: {limit-max-vol}."),
+ STORE_CREATE_FAIL_MIN_VOL("§cYou can't create a store that small!§f Minimum volume: {limit-min-vol}."),
+ STORE_CREATE_FAIL_OFFLIMITS("§cYou can't create a store within this area (off limits)!"),
+ STORE_CREATE_FAIL_OVERLAPS("§cYou can't create a store within this area (intersects another store)!"),
STORE_DELETE_FAIL("§cSomething went wrong when attempting to delete the store!"),
STORE_DELETE_SUCCESS("§aYou have successfully deleted §6{store}§a!"),
+ STORE_DELETE_INSUFFICIENT_INV_PLAYER("§cYou don't have enough inventory space to delete the store, please try removing items first or use the '-f' flag to ignore inventory!"),
+ STORE_GOODBYE_MSG("§9We hope to see you again!"),
STORE_MULTI_MATCH("§cMultiple stores matched, please specify the store's UUID!"),
STORE_NO_STORE_FOUND("§cCould not find a store with the name and/or UUID of: §6{store}§c!"),
- STORE_NOT_FOUND("§cCould not find a store!"),
+ STORE_TRANSFER_FAIL_MAX_STORES("§c{target} can't have any more stores!§f Maximum stores: {max-stores}."),
STORE_TRANSFER_SUCCESS("§aYou have successfully transferred §6{store}§a to player §6{target}§a!"),
- STORE_UPDATE_SUCCESS("§aYou have successfully updated §6{arg1}§a to §6{arg2}§a in the store!"),
- STORE_UPDATE_SUCCESS_2("§aYou have successfully updated §6{arg2}§a to §6{arg3}§a in the store!"),
+ STORE_UPDATE_SUCCESS("§aYou have successfully updated §6{arg1}§a to §6{arg2}§a in {store}!"),
+ STORE_UPDATE_SUCCESS_2("§aYou have successfully updated §6{arg2}§a to §6{arg3}§a in {store}!"),
+ STORE_UPDATE_SUCCESS_LOCATION("§aYou have successfully updated the location of {store} to ({x1}, {y1}, {z1}) ({x2}, {y2}, {z2}) in {world}!"),
STORE_UPDATE_NO_STOCK("§cPlease ensure there is no stock in the store for this item and try again!"),
- WITHDRAW_SUCCESS("§aYou have successfully withdrawn §6{amount}§a from the store!");
+ STORE_WELCOME_MSG("§9Welcome to §6{store}§9!"),
+ WITHDRAW_SUCCESS("§aYou have successfully withdrawn §6{amount}§a from the store!"),
+ WORLD_NOT_FOUND("§cCould not find world ({world})!");
public final String value;
@@ -186,12 +218,24 @@ public enum CipherKey {
public static class MultilineBuilder {
private final StringBuilder finalMessage;
+ private final Map attributes;
/**
* Constructs a MultilineBuilder without any initial message
*/
public MultilineBuilder() {
finalMessage = new StringBuilder();
+ attributes = null;
+ }
+
+ /**
+ * Constructs a MultilineBuilder without any initial message, but adds attributes
+ *
+ * @param attributes the attributes to be parsed in the message
+ */
+ public MultilineBuilder(Map attributes) {
+ finalMessage = new StringBuilder();
+ this.attributes = attributes;
}
/**
@@ -201,6 +245,18 @@ public MultilineBuilder() {
*/
public MultilineBuilder(String message) {
finalMessage = new StringBuilder(message);
+ attributes = null;
+ }
+
+ /**
+ * Constructs a MultilineBuilder with an initial message and attributes
+ *
+ * @param message the initial message for the builder
+ * @param attributes the attributes to be parsed in the message
+ */
+ public MultilineBuilder(String message, Map attributes) {
+ finalMessage = new StringBuilder(format(message, attributes));
+ this.attributes = attributes;
}
/**
@@ -210,7 +266,7 @@ public MultilineBuilder(String message) {
* @return the current instance
*/
public MultilineBuilder append(CipherKey key) {
- return append(key.value);
+ return append(compose(key, attributes));
}
/**
@@ -223,7 +279,7 @@ public MultilineBuilder append(String message) {
if (!finalMessage.isEmpty())
finalMessage.append(lineSeparator);
- finalMessage.append(message);
+ finalMessage.append(format(message, attributes));
return this;
}
@@ -235,7 +291,7 @@ public MultilineBuilder append(String message) {
* @return the current instance
*/
public MultilineBuilder appendf(CipherKey key, @Nullable Object... args) {
- String tempMessage = String.format(key.value, args);
+ String tempMessage = String.format(compose(key, attributes), args);
return append(tempMessage);
}
diff --git a/src/main/java/net/sparkzz/util/Transaction.java b/src/main/java/net/sparkzz/util/Transaction.java
index a348dae..a163279 100644
--- a/src/main/java/net/sparkzz/util/Transaction.java
+++ b/src/main/java/net/sparkzz/util/Transaction.java
@@ -13,7 +13,7 @@
* This helper class provides a transaction handler so that transactions can be built and verified before being
* processed
*/
-public class Transaction {
+public class Transaction extends Notifiable {
private static final Economy econ = Shops.getEconomy();
private final ItemStack itemStack;
@@ -22,7 +22,7 @@ public class Transaction {
private final Store store;
private final Notifier.MultilineBuilder transactionMessage;
private boolean transactionReady = false, financesReady = false, inventoryReady = false;
- private double cost;
+ private final double cost;
/**
* Constructs the transaction with the player, item stack, and transaction type
@@ -32,17 +32,20 @@ public class Transaction {
* @param type the provided type of transaction
*/
public Transaction(Player player, ItemStack itemStack, TransactionType type) {
- this.player = player;
+ this.player = (Player) setAttribute("player", player);
this.itemStack = itemStack;
- this.type = type;
- this.transactionMessage = new Notifier.MultilineBuilder();
+ this.type = (TransactionType) setAttribute("type", type);
+ this.transactionMessage = new Notifier.MultilineBuilder(getAttributes());
- store = InventoryManagementSystem.locateCurrentStore(player);
+ setAttribute("material", itemStack.getType());
+ setAttribute("quantity", itemStack.getAmount());
+
+ store = (Store) setAttribute("store", InventoryManagementSystem.locateCurrentStore(player));
+ cost = (double) setAttribute("cost", switch (type) {
+ case PURCHASE -> (store.getBuyPrice(itemStack.getType()) * itemStack.getAmount());
+ case SALE -> (store.getSellPrice(itemStack.getType()) * itemStack.getAmount());
+ });
- switch (type) {
- case PURCHASE -> cost = (store.getBuyPrice(itemStack.getType()) * itemStack.getAmount());
- case SALE -> cost = (store.getSellPrice(itemStack.getType()) * itemStack.getAmount());
- }
}
private void validateFinances() {
@@ -59,6 +62,7 @@ private void validateFinances() {
if (!financesReady) transactionMessage.append(INSUFFICIENT_FUNDS_STORE);
}
+ default -> {}
}
}
@@ -91,6 +95,7 @@ private void validateInventory() {
if (storeIsBuying && storeIsBuyingMore && canWithdrawPlayer)
inventoryReady = true;
}
+ default -> {}
}
}
@@ -158,6 +163,7 @@ public void process() {
player.getInventory().removeItem(itemStack);
econ.depositPlayer(player, cost);
}
+ default -> {}
}
}
diff --git a/src/main/java/net/sparkzz/util/Warehouse.java b/src/main/java/net/sparkzz/util/Warehouse.java
index 00875ee..3d0f7cd 100644
--- a/src/main/java/net/sparkzz/util/Warehouse.java
+++ b/src/main/java/net/sparkzz/util/Warehouse.java
@@ -2,12 +2,13 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.SerializationFeature;
import io.leangen.geantyref.TypeToken;
import net.sparkzz.shops.Shops;
import net.sparkzz.shops.Store;
+import org.bukkit.Bukkit;
import org.bukkit.Material;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.bukkit.World;
+import org.jetbrains.annotations.Nullable;
import org.spongepowered.configurate.CommentedConfigurationNode;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.ConfigurationOptions;
@@ -17,29 +18,39 @@
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.serialize.TypeSerializer;
import org.spongepowered.configurate.serialize.TypeSerializerCollection;
+import org.spongepowered.configurate.yaml.NodeStyle;
+import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
import java.util.logging.Logger;
+import java.util.stream.DoubleStream;
import static net.sparkzz.shops.Store.STORES;
/**
- * Helper class to manage saving and loading of Shops
+ * Helper class to manage saving and loading of Stores
*
* @author Brendon Butler
*/
public class Warehouse {
- private static CommentedConfigurationNode config;
- private static ConfigurationLoader loader;
- private static ObjectMapper mapper;
+ private static CommentedConfigurationNode config, storeConfig;
+ private static ConfigurationLoader configLoader, storeLoader;
+ private static ObjectMapper storeMapper;
private static final Logger log = Shops.getPlugin(Shops.class).getLogger();
- private static final String configTitle = "data.shops";
- private static final TypeSerializer