diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1469db3 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,46 @@ +name: Build +on: + push: + branches: + - develop + pull_request: + types: [opened, synchronize, reopened] +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: 'adopt' + java-version: '21' + - name: Cache SonarCloud packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=BentoBoxWorld_Upgrades + - run: mvn --batch-mode clean org.jacoco:jacoco-maven-plugin:prepare-agent install + - run: mkdir staging && cp target/*.jar staging + - name: Save artifacts + uses: actions/upload-artifact@v4 + with: + name: Package + path: staging + + diff --git a/.gitignore b/.gitignore index ce009e4..5e2fb46 100644 --- a/.gitignore +++ b/.gitignore @@ -1,31 +1,87 @@ -# Compiled class file -*.class + # Git +*.orig +!.gitignore +/.settings/ + + # Windows +Thumbs.db +ehthumbs.db +ehthumbs_vista.db +*.stackdump +[Dd]esktop.ini +$RECYCLE.BIN/ +*.lnk -# Log file -*.log + # Linux +*~ +.fuse_hidden* +.directory +.Trash-* +.nfs* -# BlueJ files -*.ctxt + # MacOS +.DS_Store +.AppleDouble +.LSOverride +._* -# Mobile Tools for Java (J2ME) + # Java +*.class +*.log +*.ctxt .mtj.tmp/ - -# Package Files # *.jar *.war *.nar *.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* -/target/ -/.settings/ -/.classpath -/.project -/dependency-reduced-pom.xml -/.DS_Store -/MagicCobblestoneGenerator.iml -/bin + + # Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties + + # Intellij +*.iml +*.java___jb_tmp___ +.idea/* +*.ipr +*.iws +/out/ +.idea_modules/ + + # Eclipse +*.pydevproject +.metadata +.gradle +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.project +.externalToolBuilders/ +*.launch +.cproject +.classpath +.buildpath +.target + + # NetBeans +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +nbactions.xml +nb-configuration.xml +.nb-gradle/ \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..1126f49 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,164 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Upgrades is a BentoBox addon (Paper/Bukkit plugin) that lets island players purchase upgrades using Vault economy. It hooks into BentoBox GameMode addons (BSkyBlock, AcidIsland, CaveBlock, SkyGrid, AOneBlock) and optionally integrates with the Level and Limits addons. + +### Relation to other addons for BentoBox + +Upgrades runs within the BentoBox system as a plugin/addon to it. For coding, testing, and other patterns, it can and should draw on what other addons and BentoBox itself does. So the wider code base should be utilized when needed. This can be found at https://bentobox.world (GitHub) and the CI system is in https://ci.bentobox.world (CodeMC). + +## Build & Test Commands + +```bash +# Build (produces JAR in target/) +mvn clean package + +# Run all tests +mvn test + +# Run a single test class +mvn test -Dtest=UpgradesAddonTest + +# Run a specific test method +mvn test -Dtest=UpgradesAddonTest#testOnEnable +``` + +Java 21 is required. The surefire plugin is configured with `--add-opens` flags so tests work with MockBukkit. + +## Architecture + +### Core Components + +**`UpgradesAddon`** — Main entry point extending BentoBox's `Addon`. Lifecycle: +- `onLoad`: loads config, creates `Settings` +- `onEnable`: iterates all non-disabled GameMode addons, registers `PlayerUpgradeCommand` in each, creates `UpgradesManager`, hooks optional soft-deps (Level, Limits, Vault), instantiates and registers all `Upgrade` objects +- `onDisable`: async-saves all cached `UpgradesData` to DB + +**`UpgradesManager`** — Resolves upgrade tiers per-world by merging global defaults with game-mode-specific overrides from `Settings`. Every public method takes a `World` to look up the active GameModeAddon name and apply the right tier config. + +**`Settings`** — Parses `config.yml` into typed tier maps. Per-upgrade-type maps exist in two layers: default (global) and custom (per game mode). `UpgradesManager` merges them on each lookup. + +**`Upgrade` (abstract)** — Base class for all upgrade types. Key contract: +- `updateUpgradeValue(user, island)` — called each time the panel opens; must populate `playerCache` via `setUpgradeValues()` and `setOwnDescription()` +- `canUpgrade(user, island)` — checks island level and Vault balance; override and call `super` +- `doUpgrade(user, island)` — withdraws Vault cost, increments level in `UpgradesData`; override and call `super` +- `isShowed(user, island)` — returns `true` by default; override to hide when maxed out + +Concrete subclasses: `RangeUpgrade`, `BlockLimitsUpgrade`, `EntityLimitsUpgrade`, `EntityGroupLimitsUpgrade`, `CommandUpgrade`. + +**`UpgradesData`** — BentoBox `DataObject` (table `UpgradesData`) storing a `Map` of upgrade name → current level per island (uniqueId = island UUID). Levels start at 1 (first call to `getUpgradeLevel` inserts 1 via `putIfAbsent`). + +**`Panel` / `PanelClick`** — BentoBox Panel API GUI. `Panel.showPanel()` iterates registered upgrades, calls `updateUpgradeValue`, and builds panel items. `PanelClick` handles the click → `canUpgrade` → `doUpgrade` flow. + +### Data Flow for a Purchase + +1. Player runs `/[gamemode] upgrades` → `PlayerUpgradeCommand` → opens `Panel` +2. Panel calls `upgrade.updateUpgradeValue()` for each registered upgrade (populates per-user cache) +3. Player clicks an item → `PanelClick.onClick()` → `upgrade.canUpgrade()` → `upgrade.doUpgrade()` → panel reopens + +### Key Design Notes + +- Island upgrade data is memory-cached in `UpgradesAddon.upgradesCache` (Map) and saved async on disable or island uncache. +- Per-user `UpgradeValues` (islandLevel req, moneyCost, upgradeValue) are stored in `Upgrade.playerCache` (Map) and are stale between panel opens. +- Soft dependencies (Level, Limits, Vault) are each guarded by `isLevelProvided()`, `isLimitsProvided()`, `isVaultProvided()`. Block/Entity Limits upgrades are only registered if Limits is present. +- Permission-based max level overrides: upgrades check player permissions (e.g. `[gamemode].island.maxrange.[n]`) to cap upgrades, taking the highest permission value found. +- Tier configs support formula strings for `islandMinLevel`, `vaultCost`, and `upgrade` values that can reference `%level%`, `%islandlevel%`, and `%numberofmembers%`. + +### Adding a New Upgrade Type + +1. Extend `Upgrade`, implement `updateUpgradeValue()` and override `doUpgrade()`/`canUpgrade()`/`isShowed()` as needed +2. Register an instance via `UpgradesAddon.registerUpgrade()` in `onEnable()` +3. Add any new config sections to `Settings` and expose them via `UpgradesManager` +4. Add locale keys to `src/main/resources/locales/en-US.yml` + +## Dependency Source Lookup + +When you need to inspect source code for a dependency (e.g., BentoBox, addons): + +1. **Check local Maven repo first**: `~/.m2/repository/` — sources jars are named `*-sources.jar` +2. **Check the workspace**: Look for sibling directories or Git submodules that may contain the dependency as a local project (e.g., `../bentoBox`, `../addon-*`) +3. **Check Maven local cache for already-extracted sources** before downloading anything +4. Only download a jar or fetch from the internet if the above steps yield nothing useful + +Prefer reading `.java` source files directly from a local Git clone over decompiling or extracting a jar. + +In general, the latest version of BentoBox should be targeted. + +## Project Layout + +Related projects are checked out as siblings under `~/git/`: + +**Core:** +- `bentobox/` — core BentoBox framework + +**Game modes:** +- `addon-acidisland/` — AcidIsland game mode +- `addon-bskyblock/` — BSkyBlock game mode +- `Boxed/` — Boxed game mode (expandable box area) +- `CaveBlock/` — CaveBlock game mode +- `OneBlock/` — AOneBlock game mode +- `SkyGrid/` — SkyGrid game mode +- `RaftMode/` — Raft survival game mode +- `StrangerRealms/` — StrangerRealms game mode +- `Brix/` — plot game mode +- `parkour/` — Parkour game mode +- `poseidon/` — Poseidon game mode +- `gg/` — gg game mode + +**Addons:** +- `addon-level/` — island level calculation +- `addon-challenges/` — challenges system +- `addon-welcomewarpsigns/` — warp signs +- `addon-limits/` — block/entity limits +- `addon-invSwitcher/` / `invSwitcher/` — inventory switcher +- `addon-biomes/` / `Biomes/` — biomes management +- `Bank/` — island bank +- `Border/` — world border for islands +- `Chat/` — island chat +- `CheckMeOut/` — island submission/voting +- `ControlPanel/` — game mode control panel +- `Converter/` — ASkyBlock to BSkyBlock converter +- `DimensionalTrees/` — dimension-specific trees +- `discordwebhook/` — Discord integration +- `Downloads/` — BentoBox downloads site +- `DragonFights/` — per-island ender dragon fights +- `ExtraMobs/` — additional mob spawning rules +- `FarmersDance/` — twerking crop growth +- `GravityFlux/` — gravity addon +- `Greenhouses-addon/` — greenhouse biomes +- `IslandFly/` — island flight permission +- `IslandRankup/` — island rankup system +- `Likes/` — island likes/dislikes +- `Limits/` — block/entity limits +- `lost-sheep/` — lost sheep adventure +- `MagicCobblestoneGenerator/` — custom cobblestone generator +- `PortalStart/` — portal-based island start +- `pp/` — pp addon +- `Regionerator/` — region management +- `Residence/` — residence addon +- `TopBlock/` — top ten for OneBlock +- `TwerkingForTrees/` — twerking tree growth +- `Upgrades/` — island upgrades (Vault) +- `Visit/` — island visiting +- `weblink/` — web link addon +- `CrowdBound/` — CrowdBound addon + +**Data packs:** +- `BoxedDataPack/` — advancement datapack for Boxed + +**Documentation & tools:** +- `docs/` — main documentation site +- `docs-chinese/` — Chinese documentation +- `docs-french/` — French documentation +- `BentoBoxWorld.github.io/` — GitHub Pages site +- `website/` — website +- `translation-tool/` — translation tool + +Check these for source before any network fetch. + +## Key Dependencies (source locations) + +- `world.bentobox:bentobox` → `~/git/bentobox/src/` diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fa83bee --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2020-2026 Guillaume-Lebegue, tastybento, and BentoBoxWorld Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md index d2beca4..6e840ef 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,219 @@ -# Island Upgrades Addon -[![Discord](https://img.shields.io/discord/272499714048524288.svg?logo=discord)](https://discord.bentobox.world) +# 🎁 Upgrades Addon -Add-on for BentoBox to provide a Upgrades to upgrade an island of any BentoBox GameMode. +
-## Where to find +[![Discord](https://img.shields.io/discord/272499714048524288.svg?logo=discord&label=Discord)](https://discord.bentobox.world) +[![Build Status](https://ci.codemc.io/buildStatus/icon?job=BentoBoxWorld/Upgrades)](https://ci.codemc.io/job/BentoBoxWorld/job/Upgrades/) +[![Java Version](https://img.shields.io/badge/Java-21+-orange?logo=java)](https://www.java.com) +[![License](https://img.shields.io/github/license/BentoBoxWorld/Upgrades?color=blue)](LICENSE) -Currently Island Upgrades Addon is in **Early Alpha stage**, so it may or may not contain bugs... a lot of bugs. Also it means, that some features are not working or implemented. +A powerful BentoBox addon that enables island upgrades for any game mode within the BentoBox ecosystem. -If you like this addon but something is missing or is not working as you want, you can always submit an [Issue request](https://github.com/Guillaume-Lebegue/IslandUpgrades/issues) +[Download](#-installation) • [Documentation](#-configuration) • [Report Issues](https://github.com/BentoBoxWorld/Upgrades/issues) • [Discord](https://discord.bentobox.world) -## How to use +
-For now, you must download the repo and compile the .jar yourself -Then add it to BentoBox Addons +--- -## Config.yml +## 📋 Overview -The config.yml has the following sections: +**Upgrades** is a BentoBox addon that provides a comprehensive upgrade system for islands. Players can purchase enhancements to expand their island's capabilities, including island size expansion, entity and block limits, and custom command execution. -* **Disabled GameModes** - specify Game Modes where islandUpgrades will not work. -* **Range Upgrade Tiers** - ability to specify default Range upgrade tiers -* **GameMode Range Upgrade Tiers** - ability to specify Range upgrade tiers for specific game mode. +### Key Features + +- 🏝️ **Island Range Expansion** - Upgrade island protection radius +- 📦 **Block Limit Upgrades** - Increase block placement limits per type +- 👥 **Entity Limits** - Expand entity spawning and placement limits +- 🎯 **Entity Group Limits** - Manage specific entity group restrictions +- ⚡ **Command Upgrades** - Execute custom commands on upgrade purchase +- 💰 **Economy Integration** - Full Vault support for monetary costs +- 🎮 **Multi-GameMode Support** - Works with BSkyBlock, AcidIsland, CaveBlock, SkyGrid, AOneBlock, and more +- ⚙️ **Highly Configurable** - Customize tiers, costs, and requirements per game mode +- 🌐 **Multilingual** - Built-in support for multiple languages + +--- + +## 📦 Requirements + +- **Minecraft**: 1.21+ +- **Java**: 21+ +- **BentoBox**: 3.0.0 or higher +- **Paper/Spigot**: Latest stable version recommended + +### Optional Dependencies + +For full functionality, install these addons: + +| Addon | Purpose | +|-------|---------| +| [Vault](https://github.com/MilkBowl/Vault) | Monetary costs for upgrades | +| [Level](https://github.com/BentoBoxWorld/Level) | Island level requirements | +| [Limits](https://github.com/BentoBoxWorld/Limits) | Block and entity limit upgrades | + +> **Note**: Without these dependencies, certain features will be disabled but the addon will still function. + +--- + +## 🚀 Installation + +### Quick Start + +1. **Download** the latest release from the [Jenkins CI](https://ci.codemc.io/job/BentoBoxWorld/job/Upgrades/) +2. **Place** the JAR file in your `plugins/BentoBox/addons/` directory +3. **Restart** your server +4. **Configure** the addon by editing `plugins/BentoBox/addons/Upgrades/config.yml` +5. **Restart** your server again to apply changes + +### Manual Installation + +```bash +# 1. Clone the repository +git clone https://github.com/BentoBoxWorld/Upgrades.git +cd Upgrades + +# 2. Build the project +mvn clean package + +# 3. Copy the artifact to your server +cp target/Upgrades-*.jar /path/to/server/plugins/BentoBox/addons/ +``` + +--- + +## ⚙️ Configuration + +The addon is configured through `config.yml` located in `plugins/BentoBox/addons/Upgrades/`. + +### Main Configuration Sections + +#### Disabled GameModes +```yaml +disabled-gamemodes: + - BSkyBlock # Disable upgrades for specific game modes +``` + +#### Upgrade Tiers + +Define upgrade progression with different tiers: + +- **Range Upgrade Tiers** - Controls island protection radius expansion +- **Block Limits Upgrade Tiers** - Controls per-block-type placement limits +- **Entity Limits Upgrade Tiers** - Controls entity spawning limits +- **Entity Group Limits Upgrade Tiers** - Controls grouped entity limits +- **Command Upgrade Tiers** - Execute commands on purchase + +#### GameMode-Specific Configuration +```yaml +gamemodes: + BSkyBlock: + range: + tier-1: + cost: 1000 + value: 50 +``` + +#### Customization + +- **Entity Icons** - Customize appearance of entity upgrade options +- **Entity Group Icons** - Customize grouped entity upgrade display +- **Command Icons** - Customize command upgrade presentation + +--- + +## 💡 Usage + +### For Players + +1. Use the `/upgrade` command to open the upgrades panel +2. Browse available upgrades for your island +3. Click on an upgrade to purchase it (if you have sufficient funds/level) +4. Upgrades apply immediately after purchase + +### For Administrators + +1. Configure upgrade tiers in `config.yml` +2. Set appropriate costs and requirements +3. Customize icons and descriptions in locale files +4. Reload the addon with `/reload` if needed + +--- + +## 🌍 Localization + +Upgrade supports multiple languages through locale files in `plugins/BentoBox/addons/Upgrades/locales/`: + +- 🇬🇧 English (en-US) +- 🇫🇷 French (fr) +- 🇯🇵 Japanese (ja) +- 🇵🇱 Polish (pl) +- 🇨🇳 Simplified Chinese (zh-CN) + +To add a new language, create a new YAML file following the existing locale format. + +--- + +## 🤝 Related Projects + +This addon integrates with the BentoBox ecosystem. Check out other official addons: + +- [BentoBox](https://github.com/BentoBoxWorld/BentoBox) - Core framework +- [Level](https://github.com/BentoBoxWorld/Level) - Island leveling system +- [Limits](https://github.com/BentoBoxWorld/Limits) - Block and entity limits +- [Visit](https://github.com/BentoBoxWorld/Visit) - Island visiting system +- [More Addons](https://github.com/BentoBoxWorld/BentoBox/blob/develop/ADDON.md) + +--- + +## 🐛 Bug Reports & Feature Requests + +Found an issue? Have an idea for an improvement? We'd love to hear from you! + +- 📝 [Create an Issue](https://github.com/BentoBoxWorld/Upgrades/issues) +- 💬 [Join our Discord](https://discord.bentobox.world) +- 📖 [Check the Wiki](https://github.com/BentoBoxWorld/Upgrades/wiki) + +--- + +## 👥 Contributing + +Contributions are welcome! Whether it's bug fixes, features, or documentation improvements: + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'Add amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +--- + +## 📄 License + +Copyright (c) 2020-2026 Guillaume-Lebegue, tastybento, and BentoBoxWorld Contributors + +This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for the full text. + +--- + +## 🙏 Credits + +- **Guillaume-Lebegue** - Original author +- **tastybento** - Core BentoBox maintainer and contributor +- **BentoBox Community** - For support and feedback + +--- + +## 📞 Support + +Need help? We're here for you! + +- 💬 **Discord**: [Join the BentoBox Discord](https://discord.bentobox.world) +- 📧 **Issues**: [GitHub Issues](https://github.com/BentoBoxWorld/Upgrades/issues) +- 📖 **Documentation**: Check the wiki for detailed guides + +--- + +
+ +Made with ❤️ by the BentoBox Community + +
diff --git a/database_backup/UpgradeData/BSkyBlock_example_cow.json b/database_backup/UpgradeData/BSkyBlock_example_cow.json new file mode 100644 index 0000000..1bc8874 --- /dev/null +++ b/database_backup/UpgradeData/BSkyBlock_example_cow.json @@ -0,0 +1,9 @@ +{ + "uniqueId": "BSkyBlock_example_cow", + "world": "BSkyBlock", + "name": "", + "description": [], + "icon": "is:\n ==: org.bukkit.inventory.ItemStack\n", + "order": -1, + "active": true +} \ No newline at end of file diff --git a/database_backup/UpgradeData/BSkyBlock_example_cropgrowth.json b/database_backup/UpgradeData/BSkyBlock_example_cropgrowth.json new file mode 100644 index 0000000..dbc80fc --- /dev/null +++ b/database_backup/UpgradeData/BSkyBlock_example_cropgrowth.json @@ -0,0 +1,9 @@ +{ + "uniqueId": "BSkyBlock_example_cropgrowth", + "world": "BSkyBlock", + "name": "", + "description": [], + "icon": "is:\n ==: org.bukkit.inventory.ItemStack\n", + "order": -1, + "active": true +} \ No newline at end of file diff --git a/database_backup/UpgradeData/BSkyBlock_example_diamond.json b/database_backup/UpgradeData/BSkyBlock_example_diamond.json new file mode 100644 index 0000000..a503885 --- /dev/null +++ b/database_backup/UpgradeData/BSkyBlock_example_diamond.json @@ -0,0 +1,9 @@ +{ + "uniqueId": "BSkyBlock_example_diamond", + "world": "BSkyBlock", + "name": "", + "description": [], + "icon": "is:\n ==: org.bukkit.inventory.ItemStack\n", + "order": -1, + "active": true +} \ No newline at end of file diff --git a/database_backup/UpgradeData/BSkyBlock_example_donor.json b/database_backup/UpgradeData/BSkyBlock_example_donor.json new file mode 100644 index 0000000..4cad556 --- /dev/null +++ b/database_backup/UpgradeData/BSkyBlock_example_donor.json @@ -0,0 +1,9 @@ +{ + "uniqueId": "BSkyBlock_example_donor", + "world": "BSkyBlock", + "name": "", + "description": [], + "icon": "is:\n ==: org.bukkit.inventory.ItemStack\n", + "order": -1, + "active": true +} \ No newline at end of file diff --git a/database_backup/UpgradeData/BSkyBlock_example_hopper.json b/database_backup/UpgradeData/BSkyBlock_example_hopper.json new file mode 100644 index 0000000..34a471a --- /dev/null +++ b/database_backup/UpgradeData/BSkyBlock_example_hopper.json @@ -0,0 +1,9 @@ +{ + "uniqueId": "BSkyBlock_example_hopper", + "world": "BSkyBlock", + "name": "", + "description": [], + "icon": "is:\n ==: org.bukkit.inventory.ItemStack\n", + "order": -1, + "active": true +} \ No newline at end of file diff --git a/database_backup/UpgradeData/BSkyBlock_example_range1.json b/database_backup/UpgradeData/BSkyBlock_example_range1.json new file mode 100644 index 0000000..b11d4de --- /dev/null +++ b/database_backup/UpgradeData/BSkyBlock_example_range1.json @@ -0,0 +1,9 @@ +{ + "uniqueId": "BSkyBlock_example_range1", + "world": "BSkyBlock", + "name": "", + "description": [], + "icon": "is:\n ==: org.bukkit.inventory.ItemStack\n", + "order": -1, + "active": true +} \ No newline at end of file diff --git a/database_backup/UpgradeData/BSkyBlock_example_range2.json b/database_backup/UpgradeData/BSkyBlock_example_range2.json new file mode 100644 index 0000000..bf9849c --- /dev/null +++ b/database_backup/UpgradeData/BSkyBlock_example_range2.json @@ -0,0 +1,9 @@ +{ + "uniqueId": "BSkyBlock_example_range2", + "world": "BSkyBlock", + "name": "", + "description": [], + "icon": "is:\n ==: org.bukkit.inventory.ItemStack\n", + "order": -1, + "active": true +} \ No newline at end of file diff --git a/database_backup/UpgradeData/BSkyBlock_example_spawner.json b/database_backup/UpgradeData/BSkyBlock_example_spawner.json new file mode 100644 index 0000000..193eb2b --- /dev/null +++ b/database_backup/UpgradeData/BSkyBlock_example_spawner.json @@ -0,0 +1,9 @@ +{ + "uniqueId": "BSkyBlock_example_spawner", + "world": "BSkyBlock", + "name": "", + "description": [], + "icon": "is:\n ==: org.bukkit.inventory.ItemStack\n", + "order": -1, + "active": true +} \ No newline at end of file diff --git a/database_backup/UpgradeTier/BSkyBlock_example_cow_t1.json b/database_backup/UpgradeTier/BSkyBlock_example_cow_t1.json new file mode 100644 index 0000000..0379f52 --- /dev/null +++ b/database_backup/UpgradeTier/BSkyBlock_example_cow_t1.json @@ -0,0 +1,10 @@ +{ + "uniqueId": "BSkyBlock_example_cow_t1", + "upgrade": "BSkyBlock_example_cow", + "name": "", + "description": [], + "startLevel": 0, + "endLevel": 4, + "prices": [], + "rewards": [] +} \ No newline at end of file diff --git a/database_backup/UpgradeTier/BSkyBlock_example_cropgrowth_t1.json b/database_backup/UpgradeTier/BSkyBlock_example_cropgrowth_t1.json new file mode 100644 index 0000000..f03cfd0 --- /dev/null +++ b/database_backup/UpgradeTier/BSkyBlock_example_cropgrowth_t1.json @@ -0,0 +1,10 @@ +{ + "uniqueId": "BSkyBlock_example_cropgrowth_t1", + "upgrade": "BSkyBlock_example_cropgrowth", + "name": "", + "description": [], + "startLevel": 0, + "endLevel": 4, + "prices": [], + "rewards": [] +} \ No newline at end of file diff --git a/database_backup/UpgradeTier/BSkyBlock_example_diamond_t1.json b/database_backup/UpgradeTier/BSkyBlock_example_diamond_t1.json new file mode 100644 index 0000000..efb1494 --- /dev/null +++ b/database_backup/UpgradeTier/BSkyBlock_example_diamond_t1.json @@ -0,0 +1,10 @@ +{ + "uniqueId": "BSkyBlock_example_diamond_t1", + "upgrade": "BSkyBlock_example_diamond", + "name": "", + "description": [], + "startLevel": 0, + "endLevel": 2, + "prices": [], + "rewards": [] +} \ No newline at end of file diff --git a/database_backup/UpgradeTier/BSkyBlock_example_donor_t1.json b/database_backup/UpgradeTier/BSkyBlock_example_donor_t1.json new file mode 100644 index 0000000..7c92a10 --- /dev/null +++ b/database_backup/UpgradeTier/BSkyBlock_example_donor_t1.json @@ -0,0 +1,10 @@ +{ + "uniqueId": "BSkyBlock_example_donor_t1", + "upgrade": "BSkyBlock_example_donor", + "name": "", + "description": [], + "startLevel": 0, + "endLevel": 0, + "prices": [], + "rewards": [] +} \ No newline at end of file diff --git a/database_backup/UpgradeTier/BSkyBlock_example_hopper_t1.json b/database_backup/UpgradeTier/BSkyBlock_example_hopper_t1.json new file mode 100644 index 0000000..75c0329 --- /dev/null +++ b/database_backup/UpgradeTier/BSkyBlock_example_hopper_t1.json @@ -0,0 +1,10 @@ +{ + "uniqueId": "BSkyBlock_example_hopper_t1", + "upgrade": "BSkyBlock_example_hopper", + "name": "", + "description": [], + "startLevel": 0, + "endLevel": 4, + "prices": [], + "rewards": [] +} \ No newline at end of file diff --git a/database_backup/UpgradeTier/BSkyBlock_example_range1_t1.json b/database_backup/UpgradeTier/BSkyBlock_example_range1_t1.json new file mode 100644 index 0000000..b8564ff --- /dev/null +++ b/database_backup/UpgradeTier/BSkyBlock_example_range1_t1.json @@ -0,0 +1,10 @@ +{ + "uniqueId": "BSkyBlock_example_range1_t1", + "upgrade": "BSkyBlock_example_range1", + "name": "", + "description": [], + "startLevel": 0, + "endLevel": 4, + "prices": [], + "rewards": [] +} \ No newline at end of file diff --git a/database_backup/UpgradeTier/BSkyBlock_example_range2_t1.json b/database_backup/UpgradeTier/BSkyBlock_example_range2_t1.json new file mode 100644 index 0000000..e091b7a --- /dev/null +++ b/database_backup/UpgradeTier/BSkyBlock_example_range2_t1.json @@ -0,0 +1,10 @@ +{ + "uniqueId": "BSkyBlock_example_range2_t1", + "upgrade": "BSkyBlock_example_range2", + "name": "", + "description": [], + "startLevel": 0, + "endLevel": 2, + "prices": [], + "rewards": [] +} \ No newline at end of file diff --git a/database_backup/UpgradeTier/BSkyBlock_example_spawner_t1.json b/database_backup/UpgradeTier/BSkyBlock_example_spawner_t1.json new file mode 100644 index 0000000..8d4473a --- /dev/null +++ b/database_backup/UpgradeTier/BSkyBlock_example_spawner_t1.json @@ -0,0 +1,10 @@ +{ + "uniqueId": "BSkyBlock_example_spawner_t1", + "upgrade": "BSkyBlock_example_spawner", + "name": "", + "description": [], + "startLevel": 0, + "endLevel": 4, + "prices": [], + "rewards": [] +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 587d7a8..e54d6a7 100644 --- a/pom.xml +++ b/pom.xml @@ -1,205 +1,330 @@ - - - 4.0.0 - - world.bentobox - IslandUpgrades - ${revision} - - IslandUpgrades - BentoBox Addon for buying upgrade for island with vault money - - - UTF-8 - UTF-8 - 1.8 - - 1.14.4-R0.1-SNAPSHOT - 1.13.1 - 2.0.0 - 1.7 - - ${build.version}-SNAPSHOT - - 0.1.0 - -LOCAL - - - - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots - - - codemc-repo - https://repo.codemc.org/repository/maven-public/ - - - jitpack.io - https://jitpack.io - - - - - - org.spigotmc - spigot-api - ${spigot.version} - provided - - - world.bentobox - bentobox - ${bentobox.version} - provided - - - world.bentobox - level - ${level.version} - provided - - - com.github.MilkBowl - VaultAPI - ${vault.version} - provided - - - - - clean package - - - src/main/resources - true - - - src/main/resources/locales - ./locales - false - - *.yml - - - - - - org.apache.maven.plugins - maven-clean-plugin - 3.1.0 - - - org.apache.maven.plugins - maven-resources-plugin - 3.1.0 - - - org.apache.maven.plugins - maven-compiler-plugin - 3.7.0 - - ${java.version} - ${java.version} - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.22.0 - - - org.apache.maven.plugins - maven-jar-plugin - 3.1.0 - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.0.1 - - public - false - -Xdoclint:none - - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-source-plugin - 3.0.1 - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.1.1 - - true - - - - package - - shade - - - - - - org.apache.maven.plugins - maven-install-plugin - 2.5.2 - - - org.apache.maven.plugins - maven-deploy-plugin - 2.8.2 - - - org.jacoco - jacoco-maven-plugin - 0.8.3 - - true - - - **/*Names* - - - - - pre-unit-test - - prepare-agent - - - - post-unit-test - - report - - - - - - - - \ No newline at end of file + + + 4.0.0 + + world.bentobox + upgrades + ${revision} + + Upgrades + BentoBox Addon for buying upgrade for island with vault money + https://github.com/BentoBoxWorld/Upgrades + 2020 + + + scm:git:https://github.com/BentoBoxWorld/Upgrades.git + scm:git:git@github.com:BentoBoxWorld/Upgrades.git + https://github.com/BentoBoxWorld/Upgrades + + + + jenkins + https://ci.codemc.org/job/BentoBoxWorld/job/Upgrades + + + + GitHub + https://github.com/BentoBoxWorld/Upgrades/issues + + + + + MIT License + https://github.com/BentoBoxWorld/Upgrades/blob/develop/LICENSE + repo + + + + + + bentoboxworld + https://repo.codemc.org/repository/bentoboxworld/ + + + + + UTF-8 + UTF-8 + 21 + + 5.10.2 + 5.11.0 + v1.21-SNAPSHOT + + 1.21.11-R0.1-SNAPSHOT + 3.0.0-SNAPSHOT + 2.17.0 + 1.28.0-SNAPSHOT + 1.7 + + ${build.version}-SNAPSHOT + + + 0.3.0 + + ${build.version}-SNAPSHOT + + -LOCAL + + bentobox-world + https://sonarcloud.io + + + + + + + ci + + + env.BUILD_NUMBER + + + + + -b${env.BUILD_NUMBER} + + + + + + + + master + + + env.GIT_BRANCH + origin/master + + + + + ${build.version} + + + + + + + + + jitpack.io + https://jitpack.io + + + papermc + https://repo.papermc.io/repository/maven-public/ + + + bentoboxworld + https://repo.codemc.org/repository/bentoboxworld/ + + + codemc + https://repo.codemc.org/repository/maven-snapshots/ + + + codemc-repo + https://repo.codemc.org/repository/maven-public/ + + + + + + com.github.MockBukkit + MockBukkit + ${mock-bukkit.version} + test + + + io.papermc.paper + paper-api + ${paper.version} + provided + + + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + org.mockito + mockito-junit-jupiter + 5.11.0 + test + + + org.mockito + mockito-core + ${mockito.version} + test + + + world.bentobox + bentobox + ${bentobox.version} + provided + + + world.bentobox + level + ${level.version} + provided + + + world.bentobox + limits + ${limits.version} + provided + + + com.github.MilkBowl + VaultAPI + ${vault.version} + provided + + + + + + + + + + + ${project.name}-${revision}${build.number} + + clean package + + + src/main/resources + true + + + src/main/resources/locales + ./locales + false + + + + + org.apache.maven.plugins + maven-clean-plugin + 3.1.0 + + + org.apache.maven.plugins + maven-resources-plugin + 3.1.0 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + + --add-opens java.base/java.lang=ALL-UNNAMED + --add-opens java.base/java.math=ALL-UNNAMED + --add-opens java.base/java.io=ALL-UNNAMED + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.util.stream=ALL-UNNAMED + --add-opens java.base/java.text=ALL-UNNAMED + --add-opens java.base/java.util.regex=ALL-UNNAMED + --add-opens java.base/java.nio.channels.spi=ALL-UNNAMED + --add-opens java.base/sun.nio.ch=ALL-UNNAMED + --add-opens java.base/java.net=ALL-UNNAMED + --add-opens java.base/java.util.concurrent=ALL-UNNAMED + --add-opens java.base/sun.nio.fs=ALL-UNNAMED + --add-opens java.base/sun.nio.cs=ALL-UNNAMED + --add-opens java.base/java.nio.file=ALL-UNNAMED + --add-opens java.base/java.nio.charset=ALL-UNNAMED + --add-opens java.base/java.lang.reflect=ALL-UNNAMED + --add-opens java.logging/java.util.logging=ALL-UNNAMED + --add-opens java.base/java.lang.ref=ALL-UNNAMED + --add-opens java.base/java.util.jar=ALL-UNNAMED + --add-opens java.base/java.util.zip=ALL-UNNAMED + --add-opens=java.base/java.security=ALL-UNNAMED + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + org.apache.maven.plugins + maven-install-plugin + 2.5.2 + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + + org.jacoco + jacoco-maven-plugin + 0.8.10 + + true + + + **/*Names* + + org/bukkit/Material* + + + + + prepare-agent + + prepare-agent + + + + report + + report + + + + XML + + + + + + + + + diff --git a/src/main/java/world/bentobox/islandupgrades/IslandUpgradesAddon.java b/src/main/java/world/bentobox/islandupgrades/IslandUpgradesAddon.java deleted file mode 100644 index 414ee82..0000000 --- a/src/main/java/world/bentobox/islandupgrades/IslandUpgradesAddon.java +++ /dev/null @@ -1,178 +0,0 @@ -package world.bentobox.islandupgrades; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; - -import world.bentobox.bentobox.api.addons.Addon; -import world.bentobox.bentobox.database.Database; -import world.bentobox.bentobox.hooks.VaultHook; -import world.bentobox.islandupgrades.api.IslandUpgradeObject; -import world.bentobox.islandupgrades.command.IslandUpgradesPlayerCommand; -import world.bentobox.islandupgrades.config.Settings; -import world.bentobox.islandupgrades.upgrades.RangeUpgrade; -import world.bentobox.level.Level; - -public class IslandUpgradesAddon extends Addon { - - @Override - public void onLoad() { - super.onLoad(); - this.saveDefaultConfig(); - this.settings = new Settings(this); - } - - @Override - public void onEnable() { - if (this.getState().equals(State.DISABLED)) { - this.logWarning("Island Upgrade Addon is not available or disabled!"); - return; - } - - List hookedGameModes = new ArrayList<>(); - - getPlugin().getAddonsManager().getGameModeAddons().stream() - .filter(g -> !settings.getDisabledGameModes().contains(g.getDescription().getName())) - .forEach(g -> { - if (g.getPlayerCommand().isPresent()) { - - new IslandUpgradesPlayerCommand(this, g.getPlayerCommand().get()); - - this.hooked = true; - hookedGameModes.add(g.getDescription().getName()); - } - }); - - if (this.hooked) { - this.islandUpgradesManager = new IslandUpgradesManager(this); - this.islandUpgradesManager.addGameModes(hookedGameModes); - - this.islandUpgradeObject = new HashSet<>(); - - this.dataBase = new Database<>(this, IslandUpgradesData.class); - this.islandUpgradesCache = new HashMap<>(); - - Optional level = this.getAddonByName("Level"); - - if (!level.isPresent()) { - this.logWarning("Level addon not found so Island Upgrade won't look for IslandLevel"); - this.levelAddon = null; - } else - this.levelAddon = (Level) level.get(); - - Optional vault = this.getPlugin().getVault(); - if (!vault.isPresent()) { - this.logWarning("Vault plugin not found si Island Upgrade won't look for money"); - this.vault = null; - } else - this.vault = vault.get(); - - this.addIslandUpgradeObject(new RangeUpgrade(this)); - - this.log("Island upgrade addon enabled"); - } else { - this.logError("Island upgrade addon could not hook into any GameMode ans so, will not do anythings"); - this.setState(State.DISABLED); - } - } - - @Override - public void onDisable() { - if (this.islandUpgradesCache != null) - this.islandUpgradesCache.values().forEach(this.dataBase::saveObjectAsync); - } - - @Override - public void onReload() { - super.onReload(); - - if (this.hooked) - this.settings = new Settings(this); - this.log("Island upgrade addon reloaded"); - } - - /** - * @return the settings - */ - public Settings getSettings() { - return settings; - } - - /** - * @return the islandUpgradesManager - */ - public IslandUpgradesManager getIslandUpgradesManager() { - return islandUpgradesManager; - } - - public Database getDataBase() { - return this.dataBase; - } - - public IslandUpgradesData getIslandUpgradesLevel(@NonNull String targetIsland) { - IslandUpgradesData islandUpgradesData = this.islandUpgradesCache.get(targetIsland); - if (islandUpgradesData != null) - return islandUpgradesData; - IslandUpgradesData data = this.dataBase.objectExists(targetIsland) ? - Optional.ofNullable(this.dataBase.loadObject(targetIsland)).orElse(new IslandUpgradesData(targetIsland)) : - new IslandUpgradesData(targetIsland); - this.islandUpgradesCache.put(targetIsland, data); - return data; - } - - public void uncacheIsland(@Nullable String targetIsland, boolean save) { - IslandUpgradesData data = this.islandUpgradesCache.remove(targetIsland); - if (data == null) - return; - if (save) - this.dataBase.saveObjectAsync(data); - } - - public Level getLevelAddon() { - return this.levelAddon; - } - - public VaultHook getVaultHook() { - return this.vault; - } - - public boolean isLevelProvided() { - return this.levelAddon != null; - } - - public boolean isVaultProvided() { - return this.vault != null; - } - - public Set getIslandUpgradeObjectList() { - return this.islandUpgradeObject; - } - - public void addIslandUpgradeObject(IslandUpgradeObject upgrade) { - this.islandUpgradeObject.add(upgrade); - } - - private Settings settings; - - private boolean hooked; - - private IslandUpgradesManager islandUpgradesManager; - - private Set islandUpgradeObject; - - private Database dataBase; - - private Map islandUpgradesCache; - - private Level levelAddon; - - private VaultHook vault; - -} diff --git a/src/main/java/world/bentobox/islandupgrades/IslandUpgradesManager.java b/src/main/java/world/bentobox/islandupgrades/IslandUpgradesManager.java deleted file mode 100644 index b2ce890..0000000 --- a/src/main/java/world/bentobox/islandupgrades/IslandUpgradesManager.java +++ /dev/null @@ -1,108 +0,0 @@ -package world.bentobox.islandupgrades; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import org.bukkit.World; - -import world.bentobox.bentobox.api.addons.GameModeAddon; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.islandupgrades.config.Settings; - -public class IslandUpgradesManager { - - public IslandUpgradesManager(IslandUpgradesAddon addon) { - this.addon = addon; - this.hookedGameModes = new HashSet<>(); - } - - protected void addGameModes(List gameModes) { - this.hookedGameModes.addAll(gameModes); - } - - public boolean canOperateInWorld(World world) { - Optional addon = this.addon.getPlugin().getIWM().getAddon(world); - - return addon.isPresent() && this.hookedGameModes.contains(addon.get().getDescription().getName()); - } - - public long getIslandLevel(Island island) { - if (!this.addon.isLevelProvided()) - return 0L; - - return this.addon.getLevelAddon().getIslandLevel(island.getWorld(), island.getOwner()); - } - - public List getAllRangeUpgradeTiers(World world) { - String name = this.addon.getPlugin().getIWM().getAddon(world).map(a -> a.getDescription().getName()).orElse(null); - if (name == null) return Collections.emptyList(); - - Map defaultTiers = this.addon.getSettings().getDefaultRangeUpgradeTierMap(); - Map customAddonTiers = this.addon.getSettings().getAddonRangeUpgradeTierMap(name); - - List tierList; - - if (customAddonTiers.isEmpty()) - tierList = new ArrayList<>(defaultTiers.values()); - else { - Set uniqueIDSet = new HashSet<>(customAddonTiers.keySet()); - uniqueIDSet.addAll(defaultTiers.keySet()); - tierList = new ArrayList<>(uniqueIDSet.size()); - - uniqueIDSet.forEach(id -> tierList.add(customAddonTiers.getOrDefault(id, defaultTiers.get(id)))); - } - - if (tierList.isEmpty()) - return Collections.emptyList(); - - tierList.sort(Comparator.comparingInt(Settings.RangeUpgradeTier::getMaxLevel)); - - return tierList; - } - - public Settings.RangeUpgradeTier getRangeUpgradeTier(long rangeLevel, World world) { - List tierList = this.getAllRangeUpgradeTiers(world); - - if (tierList.isEmpty()) - return null; - - Settings.RangeUpgradeTier rangeUpgradeTier = tierList.get(0); - - if (rangeUpgradeTier.getMaxLevel() < 0) - return rangeUpgradeTier; - - for (int i = 0; i < tierList.size(); i++) { - if (rangeLevel < tierList.get(i).getMaxLevel()) - return tierList.get(i); - } - - return null; - } - - public Map getRangeUpgradeInfos(long rangeLevel, long islandLevel, long numberPeople, World world) { - Settings.RangeUpgradeTier rangeUpgradeTier = this.getRangeUpgradeTier(rangeLevel, world); - - if (rangeUpgradeTier == null) - return null; - - Map info = new HashMap<>(); - - info.put("islandMinLevel", (int) rangeUpgradeTier.calculateIslandMinLevel(rangeLevel, islandLevel, numberPeople)); - info.put("vaultCost", (int) rangeUpgradeTier.calculateVaultCost(rangeLevel, islandLevel, numberPeople)); - info.put("upgradeRange", (int) rangeUpgradeTier.calculateUpgradeRange(rangeLevel, islandLevel, numberPeople)); - - return info; - } - - private IslandUpgradesAddon addon; - - private Set hookedGameModes; - -} diff --git a/src/main/java/world/bentobox/islandupgrades/api/IslandUpgradeObject.java b/src/main/java/world/bentobox/islandupgrades/api/IslandUpgradeObject.java deleted file mode 100644 index 1bab1de..0000000 --- a/src/main/java/world/bentobox/islandupgrades/api/IslandUpgradeObject.java +++ /dev/null @@ -1,227 +0,0 @@ -package world.bentobox.islandupgrades.api; - - -import java.util.Optional; - -import org.bukkit.Material; - -import net.milkbowl.vault.economy.EconomyResponse; -import world.bentobox.bentobox.api.addons.Addon; -import world.bentobox.bentobox.api.addons.Addon.State; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.islandupgrades.IslandUpgradesAddon; -import world.bentobox.islandupgrades.IslandUpgradesData; - -/** - * Upgrade Object for IslandUpgradeAddon. Extend this to create a new upgrade - * - * @author Ikkino - * - */ -public abstract class IslandUpgradeObject { - - /** - * Initialize the upgrade object - * you should call it in your init methode - * - * @param addon: This should be your addon - * @param name: This is the name for the upgrade that will be used in the DataBase - * @param displayName: This is the name that is shown to the user - * @param icon: This is the icon shown to the user - */ - public IslandUpgradeObject(Addon addon, String name, String displayName, Material icon) { - this.name = name; - this.displayName = displayName; - this.icon = icon; - this.upgradeValues = null; - this.addon = addon; - - Optional islandUpgrade = this.addon.getAddonByName("IslandUpgrades"); - if (!islandUpgrade.isPresent()) { - this.addon.logError("Island Upgrade Addon couldn't be found"); - this.addon.setState(State.DISABLED); - } else - this.islandUpgradeAddon = (IslandUpgradesAddon) islandUpgrade.get(); - } - - /** - * This fonction is called every times a user open the interface - * You should make it update the upgradeValues - * - * @param user: This is the user that ask for the interface - * @param island: This is the island concerned by the interface - */ - public abstract void updateUpgradeValue(User user, Island island); - - /** - * This function return true if the user can upgrade for this island. - * You can override it and call the super. - * - * The super test for islandLevel and for money - * - * @param user: This is the user that try to upgrade - * @param island: This is the island that is concerned - * @return: Can upgrade - */ - public boolean canUpgrade(User user, Island island) { - boolean can = true; - - if (this.islandUpgradeAddon.isLevelProvided() && - this.islandUpgradeAddon.getIslandUpgradesManager().getIslandLevel(island) < this.upgradeValues.getIslandLevel()) { - - can = false; - } - - if (this.islandUpgradeAddon.isVaultProvided() && - !this.islandUpgradeAddon.getVaultHook().has(user, this.upgradeValues.getMoneyCost())) { - - can = false; - } - - return can; - } - - /** - * This function is called when the user is upgrading for the island - * It is called after the canUpgrade function - * - * You should call the super to update the balance of the user as well as the level is the island - * - * @param user: This is the user that do the upgrade - * @param island: This is the island that is concerned - * @return: If upgrade was successful - */ - public boolean doUpgrade(User user, Island island) { - - if (this.islandUpgradeAddon.isVaultProvided()) { - EconomyResponse response = this.islandUpgradeAddon.getVaultHook().withdraw(user, this.upgradeValues.getMoneyCost()); - if (!response.transactionSuccess()) { - this.addon.logWarning("User Money withdrawing failed user: " + user.getName() + " reason: " + response.errorMessage); - user.sendMessage("islandupgrades.error.costwithdraw"); - return false; - } - } - - IslandUpgradesData data = this.islandUpgradeAddon.getIslandUpgradesLevel(island.getUniqueId()); - data.setUpgradeLevel(this.name, data.getUpgradeLevel(this.name) + 1); - - return true; - } - - /** - * @return: The name that is used for the DataBase - */ - public String getName() { - return this.name; - } - - /** - * @return: The name that is displayed to the user - */ - public String getDisplayName() { - return this.displayName; - } - - /** - * @param displayName: To update the name to display to the user - */ - public void setDisplayName(String displayName) { - this.displayName = displayName; - } - - /** - * @return: The icon that is displayed to the user - */ - public Material getIcon() { - return this.icon; - } - - /** - * @return: The actual upgradeValues - */ - public UpgradeValues getUpgradeValues() { - return this.upgradeValues; - } - - /** - * @param upgrade: Values to upgrades - */ - public void setUpgradeValues(UpgradeValues upgrade) { - this.upgradeValues = upgrade; - } - - /** - * Function that get the IslandUpgradesAddon - * You should use it to use the islandUpgradesAddon methods - * - * @return - */ - public IslandUpgradesAddon getIslandUpgradeAddon() { - return this.islandUpgradeAddon; - } - - /** - * You shouldn't override this function - */ - @Override - public boolean equals(Object obj) { - if (obj == this) - return true; - - if (obj == null) - return false; - - if (!(obj instanceof IslandUpgradeObject)) - return false; - - IslandUpgradeObject cobj = (IslandUpgradeObject) obj; - - return this.name == cobj.getName(); - } - - private final String name; - private String displayName; - private Material icon; - private UpgradeValues upgradeValues; - private Addon addon; - private IslandUpgradesAddon islandUpgradeAddon; - - public class UpgradeValues { - - public UpgradeValues(Integer islandLevel, Integer moneyCost, Integer upgradeValue) { - this.islandLevel = islandLevel; - this.moneyCost = moneyCost; - this.upgradeValue = upgradeValue; - } - - public Long getIslandLevel() { - return islandLevel; - } - - public void setIslandLevel(long islandLevel) { - this.islandLevel = islandLevel; - } - - public Long getMoneyCost() { - return moneyCost; - } - - public void setMoneyCost(long moneyCost) { - this.moneyCost = moneyCost; - } - - public Long getUpgradeValue() { - return upgradeValue; - } - - public void setUpgradeValue(long upgradeValue) { - this.upgradeValue = upgradeValue; - } - - private long islandLevel; - private long moneyCost; - private long upgradeValue; - } - -} diff --git a/src/main/java/world/bentobox/islandupgrades/command/IslandUpgradesPlayerCommand.java b/src/main/java/world/bentobox/islandupgrades/command/IslandUpgradesPlayerCommand.java deleted file mode 100644 index fba3d62..0000000 --- a/src/main/java/world/bentobox/islandupgrades/command/IslandUpgradesPlayerCommand.java +++ /dev/null @@ -1,36 +0,0 @@ -package world.bentobox.islandupgrades.command; - -import java.util.List; - -import world.bentobox.bentobox.api.commands.CompositeCommand; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.islandupgrades.IslandUpgradesAddon; -import world.bentobox.islandupgrades.ui.Panel; - -public class IslandUpgradesPlayerCommand extends CompositeCommand { - - public IslandUpgradesPlayerCommand(IslandUpgradesAddon addon, CompositeCommand cmd) { - super(addon, cmd, "upgrade"); - - this.addon = addon; - } - - @Override - public void setup() { - this.setDescription("islandupgrades.commands.main.description"); - this.setOnlyPlayer(true); - } - - @Override - public boolean execute(User user, String label, List args) { - if (args.size() == 0) { - new Panel(this.addon).showPanel(user); - return true; - } - this.showHelp(this, user); - return false; - } - - IslandUpgradesAddon addon; - -} diff --git a/src/main/java/world/bentobox/islandupgrades/config/Settings.java b/src/main/java/world/bentobox/islandupgrades/config/Settings.java deleted file mode 100644 index 1074215..0000000 --- a/src/main/java/world/bentobox/islandupgrades/config/Settings.java +++ /dev/null @@ -1,347 +0,0 @@ -package world.bentobox.islandupgrades.config; - -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.Objects; - -import org.bukkit.configuration.ConfigurationSection; -import org.eclipse.jdt.annotation.NonNull; - -import world.bentobox.islandupgrades.IslandUpgradesAddon; - -public class Settings { - - public Settings(IslandUpgradesAddon addon) - { - this.addon = addon; - this.addon.saveDefaultConfig(); - - this.disabledGameModes = new HashSet<>(this.addon.getConfig().getStringList("disabled-gamemodes")); - - if (this.addon.getConfig().isSet("range-upgrade")) { - ConfigurationSection section = this.addon.getConfig().getConfigurationSection("range-upgrade"); - for (String key : Objects.requireNonNull(section).getKeys(false)) { - this.rangeUpgradeTierMap.put(key, addRangeSection(section, key)); - } - } - - if (this.addon.getConfig().isSet("gamemodes")) { - ConfigurationSection section = this.addon.getConfig().getConfigurationSection("gamemodes"); - - for (String gameMode : Objects.requireNonNull(section).getKeys(false)) { - ConfigurationSection gameModeSection = section.getConfigurationSection(gameMode); - - if (gameModeSection.isSet("range-upgrade")) { - ConfigurationSection lowSection = gameModeSection.getConfigurationSection("range-upgrade"); - for (String key : Objects.requireNonNull(lowSection).getKeys(false)) { - - this.customRangeUpgradeTierMap.computeIfAbsent(gameMode, k -> new HashMap<>()).put(key, addRangeSection(lowSection, key)); - } - } - } - } - - } - - @NonNull - private RangeUpgradeTier addRangeSection(ConfigurationSection section, String key) { - ConfigurationSection tierSection = section.getConfigurationSection(key); - RangeUpgradeTier rangeUpgradeTier = new RangeUpgradeTier(key); - rangeUpgradeTier.setMaxLevel(tierSection.getInt("max-level")); - rangeUpgradeTier.setUpgradeRange(eval(tierSection.getString("upgrade-range"), rangeUpgradeTier.getExpressionVariable())); - - if (tierSection.isSet("island-min-level")) - rangeUpgradeTier.setIslandMinLevel(eval(tierSection.getString("island-min-level"), rangeUpgradeTier.getExpressionVariable())); - else - rangeUpgradeTier.setIslandMinLevel(eval("0", rangeUpgradeTier.getExpressionVariable())); - - if (tierSection.isSet("vault-cost")) - rangeUpgradeTier.setVaultCost(eval(tierSection.getString("vault-cost"), rangeUpgradeTier.getExpressionVariable())); - else - rangeUpgradeTier.setVaultCost(eval("0", rangeUpgradeTier.getExpressionVariable())); - - return rangeUpgradeTier; - - } - - /** - * @return the disabledGameModes - */ - public Set getDisabledGameModes() { - return disabledGameModes; - } - - public Map getDefaultRangeUpgradeTierMap() { - return this.rangeUpgradeTierMap; - } - - /** - * @return the rangeUpgradeTierMap - */ - public Map getAddonRangeUpgradeTierMap(String addon) { - return this.customRangeUpgradeTierMap.getOrDefault(addon, Collections.emptyMap()); - } - - private IslandUpgradesAddon addon; - - private Set disabledGameModes; - - private Map rangeUpgradeTierMap = new HashMap<>(); - - private Map> customRangeUpgradeTierMap = new HashMap<>(); - - // ------------------------------------------------------------------ - // Section: Private object - // ------------------------------------------------------------------ - - public class RangeUpgradeTier { - /** - * Constructor RangeUpgradeTier create a new RangeUpgradeTier instance - * and set expressionVariables to default value - * - * @param id - */ - public RangeUpgradeTier(String id) { - this.id = id; - this.expressionVariables = new HashMap<>(); - this.expressionVariables.put("[level]", 0.0); - this.expressionVariables.put("[islandLevel]", 0.0); - this.expressionVariables.put("[numberPlayer]", 0.0); - - } - - // -------------------------------------------------------------- - // Section: Methods - // -------------------------------------------------------------- - - /** - * @return the id - */ - public String getId() { - return id; - } - - /** - * @return the maxLevel - */ - public int getMaxLevel() { - return maxLevel; - } - - /** - * @param maxLevel the maxLevel to set - */ - public void setMaxLevel(int maxLevel) { - this.maxLevel = maxLevel; - } - - /** - * @return the upgradeRange - */ - public Expression getUpgradeRange() { - return upgradeRange; - } - - /** - * @param upgradeRange the upgradeRange to set - */ - public void setUpgradeRange(Expression upgradeRange) { - this.upgradeRange = upgradeRange; - } - - /** - * @return the islandMinLevel - */ - public Expression getIslandMinLevel() { - return islandMinLevel; - } - - /** - * @param islandMinLevel the islandMinLevel to set - */ - public void setIslandMinLevel(Expression islandMinLevel) { - this.islandMinLevel = islandMinLevel; - } - - /** - * @return the vaultCost - */ - public Expression getVaultCost() { - return vaultCost; - } - - /** - * @param vaultCost the vaultCost to set - */ - public void setVaultCost(Expression vaultCost) { - this.vaultCost = vaultCost; - } - - /** - * Value to set for the math parser - * @param key - * @param value - */ - public void updateExpressionVariable(String key, double value) { - this.expressionVariables.put(key, value); - } - - public Map getExpressionVariable() { - return expressionVariables; - } - - public double calculateUpgradeRange(double level, double islandLevel, double numberPeople) { - this.updateExpressionVariable("[level]", level); - this.updateExpressionVariable("[islandLevel]", islandLevel); - this.updateExpressionVariable("[numberPlayer]", numberPeople); - return this.getUpgradeRange().eval(); - } - - public double calculateIslandMinLevel(double level, double islandLevel, double numberPeople) { - this.updateExpressionVariable("[level]", level); - this.updateExpressionVariable("[islandLevel]", islandLevel); - this.updateExpressionVariable("[numberPlayer]", numberPeople); - return this.getIslandMinLevel().eval(); - } - - public double calculateVaultCost(double level, double islandLevel, double numberPeople) { - this.updateExpressionVariable("[level]", level); - this.updateExpressionVariable("[islandLevel]", islandLevel); - this.updateExpressionVariable("[numberPlayer]", numberPeople); - return this.getVaultCost().eval(); - } - - - // ---------------------------------------------------------------------- - // Section: Variables - // ---------------------------------------------------------------------- - - - private final String id; - - private int maxLevel = -1; - - private Expression upgradeRange; - - private Expression islandMinLevel; - - private Expression vaultCost; - - private Map expressionVariables; - } - - - // ------------------------------------------------------------------------- - // Section: Arithmetic expressions Parser - // Thanks to Boann on StackOverflow - // Link: https://stackoverflow.com/questions/3422673/how-to-evaluate-a-math-expression-given-in-string-form - // ------------------------------------------------------------------------- - - @FunctionalInterface - interface Expression { - double eval(); - } - - public static Expression eval(final String str, Map variables) { - return new Object() { - int pos = -1, ch; - - void nextChar() { - ch = (++pos < str.length()) ? str.charAt(pos) : -1; - } - - boolean eat(int charToEat) { - while (ch == ' ') nextChar(); - if (ch == charToEat) { - nextChar(); - return true; - } - return false; - } - - Expression parse() { - nextChar(); - Expression x = parseExpression(); - if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch); - return x; - } - - // Grammar: - // expression = term | expression `+` term | expression `-` term - // term = factor | term `*` factor | term `/` factor - // factor = `+` factor | `-` factor | `(` expression `)` - // | number | functionName factor | factor `^` factor - - Expression parseExpression() { - Expression x = parseTerm(); - for (;;) { - if (eat('+')) { - Expression a = x, b = parseTerm(); - x = (() -> a.eval() + b.eval()); - } else if (eat('-')) { - Expression a = x, b = parseTerm(); - x = (() -> a.eval() - b.eval()); - } else - return x; - } - } - - Expression parseTerm() { - Expression x = parseFactor(); - for (;;) { - if (eat('*')) { - Expression a = x, b = parseFactor(); - x = (() -> a.eval() * b.eval()); - } else if (eat('/')) { - Expression a = x, b = parseFactor(); - x = (() -> a.eval() / b.eval()); - } else - return x; - } - } - - Expression parseFactor() { - if (eat('+')) return parseFactor(); // unary plus - if (eat('-')) { - Expression x = (() -> -parseFactor().eval()); - return x; // unary minus - } - - Expression x; - int startPos = this.pos; - if (eat('(')) { // parentheses - x = parseExpression(); - eat(')'); - } else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers - while ((ch >= '0' && ch <= '9') || ch == '.') nextChar(); - x = (() -> Double.parseDouble(str.substring(startPos, this.pos))); - } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '[' || ch == ']') { // functions - while ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '[' || ch == ']') nextChar(); - String func = str.substring(startPos, this.pos); - if (variables.containsKey(func)) x = (() -> variables.get(func)); - else { - Expression a = parseFactor(); - if (func.equals("sqrt")) x = (() -> Math.sqrt(a.eval())); - else if (func.equals("sin")) x = (() -> Math.sin(Math.toRadians(a.eval()))); - else if (func.equals("cos")) x = (() -> Math.cos(Math.toRadians(a.eval()))); - else if (func.equals("tan")) x = (() -> Math.tan(Math.toRadians(a.eval()))); - else throw new RuntimeException("Unknown function: " + func); - } - } else { - throw new RuntimeException("Unexpected: " + (char)ch); - } - - if (eat('^')) { - Expression a = x, b = parseFactor(); - x = (() -> Math.pow(a.eval(), b.eval())); // exponentiation - } - - return x; - } - }.parse(); - } - -} \ No newline at end of file diff --git a/src/main/java/world/bentobox/islandupgrades/ui/Panel.java b/src/main/java/world/bentobox/islandupgrades/ui/Panel.java deleted file mode 100644 index e1e1ac2..0000000 --- a/src/main/java/world/bentobox/islandupgrades/ui/Panel.java +++ /dev/null @@ -1,64 +0,0 @@ -package world.bentobox.islandupgrades.ui; - -import java.util.ArrayList; -import java.util.List; - -import world.bentobox.bentobox.api.panels.builders.PanelBuilder; -import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.islandupgrades.IslandUpgradesAddon; -import world.bentobox.islandupgrades.api.IslandUpgradeObject; - -public class Panel { - - public Panel(IslandUpgradesAddon addon) { - super(); - this.addon = addon; - } - - public void showPanel(User user) { - Island island = this.addon.getIslands().getIsland(user.getWorld(), user); - long islandLevel = this.addon.getIslandUpgradesManager().getIslandLevel(island); - - PanelBuilder pb = new PanelBuilder().name(user.getTranslation("islandupgrades.ui.upgradepanel.title")); - - this.addon.getIslandUpgradeObjectList().forEach(upgrade -> { - upgrade.updateUpgradeValue(user, island); - - pb.item(new PanelItemBuilder() - .name(upgrade.getDisplayName()) - .icon(upgrade.getIcon()) - .description(this.getDescription(user, upgrade, islandLevel)) - .clickHandler(new PanelClick(this.addon, upgrade)) - .build()); - }); - - pb.user(user).build(); - } - - private List getDescription(User user, IslandUpgradeObject upgrade, long islandLevel) { - List descrip = new ArrayList<>(); - if (upgrade.getUpgradeValues() == null) - descrip.add(user.getTranslation("islandupgrades.ui.upgradepanel.maxlevel")); - else { - boolean hasMoney = this.addon.getVaultHook().has(user, upgrade.getUpgradeValues().getMoneyCost()); - descrip.add((upgrade.getUpgradeValues().getIslandLevel() <= islandLevel ? "§a" : "§c") + - user.getTranslation("islandupgrades.ui.upgradepanel.islandneed", - "[islandlevel]", upgrade.getUpgradeValues().getIslandLevel().toString())); - - descrip.add((hasMoney ? "§a" : "§c") + - user.getTranslation("islandupgrades.ui.upgradepanel.moneycost", - "[cost]", upgrade.getUpgradeValues().getMoneyCost().toString())); - - if (upgrade.getUpgradeValues().getIslandLevel() > islandLevel) { - descrip.add("§8" + user.getTranslation("islandupgrades.ui.upgradepanel.tryreloadlevel")); - } - } - - return descrip; - } - - private IslandUpgradesAddon addon; - -} diff --git a/src/main/java/world/bentobox/islandupgrades/ui/PanelClick.java b/src/main/java/world/bentobox/islandupgrades/ui/PanelClick.java deleted file mode 100644 index 31ccdeb..0000000 --- a/src/main/java/world/bentobox/islandupgrades/ui/PanelClick.java +++ /dev/null @@ -1,38 +0,0 @@ -package world.bentobox.islandupgrades.ui; - -import org.bukkit.event.inventory.ClickType; - -import world.bentobox.bentobox.api.panels.Panel; -import world.bentobox.bentobox.api.panels.PanelItem.ClickHandler; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.islandupgrades.IslandUpgradesAddon; -import world.bentobox.islandupgrades.api.IslandUpgradeObject; - -public class PanelClick implements ClickHandler { - - public PanelClick(IslandUpgradesAddon addon, IslandUpgradeObject upgrade) { - this.addon = addon; - this.upgrade = upgrade; - } - - @Override - public boolean onClick(Panel panel, User user, ClickType clickType, int slot) { - if (this.upgrade == null) - return true; - - Island island = this.addon.getIslands().getIsland(user.getWorld(), user); - - if (!this.upgrade.canUpgrade(user, island)) { - return true; - } - - user.closeInventory(); - this.upgrade.doUpgrade(user, island); - return true; - } - - private IslandUpgradesAddon addon; - private IslandUpgradeObject upgrade; - -} diff --git a/src/main/java/world/bentobox/islandupgrades/upgrades/RangeUpgrade.java b/src/main/java/world/bentobox/islandupgrades/upgrades/RangeUpgrade.java deleted file mode 100644 index c86bd33..0000000 --- a/src/main/java/world/bentobox/islandupgrades/upgrades/RangeUpgrade.java +++ /dev/null @@ -1,117 +0,0 @@ -package world.bentobox.islandupgrades.upgrades; - -import java.util.Map; - -import org.bukkit.Material; - -import world.bentobox.bentobox.api.events.island.IslandEvent; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.islandupgrades.IslandUpgradesAddon; -import world.bentobox.islandupgrades.IslandUpgradesData; -import world.bentobox.islandupgrades.api.IslandUpgradeObject; - -/** - * Upgrade Object for range upgrade - * - * @author Ikkino - * - */ -public class RangeUpgrade extends IslandUpgradeObject { - - public RangeUpgrade(IslandUpgradesAddon addon) { - super(addon, "RangeUpgrade", "RangeUpgrade", Material.OAK_FENCE); - } - - /** - * When user open the interface - */ - @Override - public void updateUpgradeValue(User user, Island island) { - // Get the addon - IslandUpgradesAddon islandAddon = this.getIslandUpgradeAddon(); - // Get the data from IslandUpgrade - IslandUpgradesData islandData = islandAddon.getIslandUpgradesLevel(island.getUniqueId()); - // The level of this upgrade - long upgradeLevel = islandData.getUpgradeLevel(getName()); - // The number of members on the island - long numberPeople = island.getMemberSet().size(); - // The level of the island from Level Addon - long islandLevel; - - // If level addon is provided - if (islandAddon.isLevelProvided()) - islandLevel = islandAddon.getIslandUpgradesManager().getIslandLevel(island); - else - islandLevel = 0L; - - // Get upgrades infos of range upgrade from settings - Map upgradeInfos = islandAddon.getIslandUpgradesManager().getRangeUpgradeInfos(upgradeLevel, islandLevel, numberPeople, island.getWorld()); - UpgradeValues upgrade; - - // If null -> no next upgrades - if (upgradeInfos == null) - upgrade = null; - else - upgrade = new UpgradeValues(upgradeInfos.get("islandMinLevel"), upgradeInfos.get("vaultCost"), upgradeInfos.get("upgradeRange")); - - // Update the upgrade values - this.setUpgradeValues(upgrade); - - // Update the display name - String newDisplayName; - - if (upgrade == null) { - // No next upgrade -> lang message - newDisplayName = user.getTranslation("islandupgrades.ui.upgradepanel.norangeupgrade"); - } else { - // get lang message - newDisplayName = user.getTranslation("islandupgrades.ui.upgradepanel.rangeupgrade", - "[rangelevel]", upgrade.getUpgradeValue().toString()); - } - - this.setDisplayName(newDisplayName); - } - - /** - * When user do upgrade - */ - @Override - public boolean doUpgrade(User user, Island island) { - // Get the new range - long newRange = island.getProtectionRange() + this.getUpgradeValues().getUpgradeValue(); - - // If newRange is more than the authorized range (Config problem) - if (newRange > island.getRange()) { - this.getIslandUpgradeAddon().logWarning("User tried to upgrade their island range over the max. This is probably a configuration problem."); - user.sendMessage("islandupgrades.error.rangeovermax"); - return false; - } - - // if super doUpgrade not worked - if (!super.doUpgrade(user, island)) - return false; - - // Save oldRange for rangeChange event - int oldRange = island.getProtectionRange(); - - // Set range - island.setProtectionRange((int) newRange); - - // Launch range change event - IslandEvent.builder() - .island(island) - .location(island.getCenter()) - .reason(IslandEvent.Reason.RANGE_CHANGE) - .involvedPlayer(user.getUniqueId()) - .admin(false) - .protectionRange((int) newRange, oldRange) - .build(); - - user.sendMessage("islandupgrades.ui.upgradepanel.rangeupgradedone", - "[rangelevel]", this.getUpgradeValues().getUpgradeValue().toString()); - - return true; - } - -} diff --git a/src/main/java/world/bentobox/upgrades/DefaultUpgradeSeeder.java b/src/main/java/world/bentobox/upgrades/DefaultUpgradeSeeder.java new file mode 100644 index 0000000..2974f8c --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/DefaultUpgradeSeeder.java @@ -0,0 +1,283 @@ +package world.bentobox.upgrades; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import world.bentobox.upgrades.dataobjects.UpgradeData; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.dataobjects.prices.IslandLevelPriceDB; +import world.bentobox.upgrades.dataobjects.prices.ItemPriceDB; +import world.bentobox.upgrades.dataobjects.prices.MoneyPriceDB; +import world.bentobox.upgrades.dataobjects.prices.PermissionPriceDB; +import world.bentobox.upgrades.dataobjects.rewards.CommandRewardDB; +import world.bentobox.upgrades.dataobjects.rewards.CropGrowthRewardDB; +import world.bentobox.upgrades.dataobjects.rewards.LimitsRewardDB; +import world.bentobox.upgrades.dataobjects.rewards.RangeRewardDB; +import world.bentobox.upgrades.dataobjects.rewards.SpawnerRewardDB; + +import java.util.ArrayList; +import java.util.List; + +/** + * Seeds 6 example upgrades for each hooked game mode on first install. + * Skips any game mode that already has at least one configured upgrade. + */ +public class DefaultUpgradeSeeder { + + private final UpgradesAddon addon; + + public DefaultUpgradeSeeder(UpgradesAddon addon) { + this.addon = addon; + } + + /** + * For each hooked game mode that has no upgrades configured, seed examples. + */ + public void seedIfEmpty() { + UpgradesDataManager dm = addon.getUpgradeDataManager(); + for (String gm : addon.getHookedGameModes()) { + if (dm.getUpgradeDataByGameMode(gm).isEmpty()) { + seed(dm, gm); + addon.log("Seeded 8 example upgrades for " + gm + + " — edit or delete them via /[gamemode] admin upgrade"); + } + } + } + + private void seed(UpgradesDataManager dm, String gm) { + seedBorderBasic(dm, gm); + seedBorderAdvanced(dm, gm); + seedHopperLimit(dm, gm); + seedCowLimit(dm, gm); + seedDiamondBorder(dm, gm); + seedDonorPerk(dm, gm); + seedSpawnerBoost(dm, gm); + seedCropBoost(dm, gm); + } + + // ─── Example 1: Border Expansion I ─────────────────────────────────────── + // Demonstrates: simple MoneyPrice → RangeReward (5 purchases available) + + private void seedBorderBasic(UpgradesDataManager dm, String gm) { + UpgradeData data = dm.createUpgradeData(gm + "_example_range1", gm, null); + if (data == null) return; + data.setName("Border Expansion I"); + data.setIcon(new ItemStack(Material.OAK_FENCE)); + data.setOrder(1); + data.setDescription(List.of("Example: pay $500 to expand island border by 5 blocks. Up to 5 times.")); + dm.saveUpgradeData(data); + + UpgradeTier tier = dm.createUpgradeTier(gm + "_example_range1_t1", data, 0, 4, null); + if (tier == null) return; + tier.setName("Border Expansion I"); + + MoneyPriceDB money = new MoneyPriceDB(); + money.setAmountEquation("500"); + tier.setPrices(List.of(money)); + + RangeRewardDB range = new RangeRewardDB(); + range.setRangeUpgradeEquation("5"); + tier.setRewards(List.of(range)); + + dm.saveUpgradeTier(tier); + } + + // ─── Example 2: Border Expansion II ────────────────────────────────────── + // Demonstrates: IslandLevelPrice gate + MoneyPrice → RangeReward (3 purchases) + + private void seedBorderAdvanced(UpgradesDataManager dm, String gm) { + UpgradeData data = dm.createUpgradeData(gm + "_example_range2", gm, null); + if (data == null) return; + data.setName("Border Expansion II"); + data.setIcon(new ItemStack(Material.IRON_BARS)); + data.setOrder(2); + data.setDescription(List.of("Example: requires island level 100 and costs $2000. Up to 3 times.")); + dm.saveUpgradeData(data); + + UpgradeTier tier = dm.createUpgradeTier(gm + "_example_range2_t1", data, 0, 2, null); + if (tier == null) return; + tier.setName("Border Expansion II"); + + IslandLevelPriceDB level = new IslandLevelPriceDB(); + level.setLevelNeededEquation("100"); + MoneyPriceDB money = new MoneyPriceDB(); + money.setAmountEquation("2000"); + tier.setPrices(List.of(level, money)); + + RangeRewardDB range = new RangeRewardDB(); + range.setRangeUpgradeEquation("10"); + tier.setRewards(List.of(range)); + + dm.saveUpgradeTier(tier); + } + + // ─── Example 3: Hopper Limit ────────────────────────────────────────────── + // Demonstrates: MoneyPrice → LimitsReward (BLOCK) (5 purchases) + + private void seedHopperLimit(UpgradesDataManager dm, String gm) { + UpgradeData data = dm.createUpgradeData(gm + "_example_hopper", gm, null); + if (data == null) return; + data.setName("Hopper Limit"); + data.setIcon(new ItemStack(Material.HOPPER)); + data.setOrder(3); + data.setDescription(List.of("Example: pay $1000 to increase your hopper limit by 2. Up to 5 times.")); + dm.saveUpgradeData(data); + + UpgradeTier tier = dm.createUpgradeTier(gm + "_example_hopper_t1", data, 0, 4, null); + if (tier == null) return; + tier.setName("Hopper Limit"); + + MoneyPriceDB money = new MoneyPriceDB(); + money.setAmountEquation("1000"); + tier.setPrices(List.of(money)); + + LimitsRewardDB limits = new LimitsRewardDB(); + limits.setLimitType("BLOCK"); + limits.setTarget("HOPPER"); + limits.setAmountEquation("2"); + tier.setRewards(List.of(limits)); + + dm.saveUpgradeTier(tier); + } + + // ─── Example 4: Cow Limit ───────────────────────────────────────────────── + // Demonstrates: MoneyPrice → LimitsReward (ENTITY) (5 purchases) + + private void seedCowLimit(UpgradesDataManager dm, String gm) { + UpgradeData data = dm.createUpgradeData(gm + "_example_cow", gm, null); + if (data == null) return; + data.setName("Cow Limit"); + data.setIcon(new ItemStack(Material.BEEF)); + data.setOrder(4); + data.setDescription(List.of("Example: pay $500 to increase your cow entity limit by 2. Up to 5 times.")); + dm.saveUpgradeData(data); + + UpgradeTier tier = dm.createUpgradeTier(gm + "_example_cow_t1", data, 0, 4, null); + if (tier == null) return; + tier.setName("Cow Limit"); + + MoneyPriceDB money = new MoneyPriceDB(); + money.setAmountEquation("500"); + tier.setPrices(List.of(money)); + + LimitsRewardDB limits = new LimitsRewardDB(); + limits.setLimitType("ENTITY"); + limits.setTarget("COW"); + limits.setAmountEquation("2"); + tier.setRewards(List.of(limits)); + + dm.saveUpgradeTier(tier); + } + + // ─── Example 5: Diamond Border ──────────────────────────────────────────── + // Demonstrates: ItemPrice → RangeReward (3 purchases) + + private void seedDiamondBorder(UpgradesDataManager dm, String gm) { + UpgradeData data = dm.createUpgradeData(gm + "_example_diamond", gm, null); + if (data == null) return; + data.setName("Diamond Border"); + data.setIcon(new ItemStack(Material.DIAMOND)); + data.setOrder(5); + data.setDescription(List.of("Example: spend 10 diamonds to expand island border by 3 blocks. Up to 3 times.")); + dm.saveUpgradeData(data); + + UpgradeTier tier = dm.createUpgradeTier(gm + "_example_diamond_t1", data, 0, 2, null); + if (tier == null) return; + tier.setName("Diamond Border"); + + ItemPriceDB item = new ItemPriceDB(); + item.setMaterial("DIAMOND"); + item.setAmount(10); + tier.setPrices(List.of(item)); + + RangeRewardDB range = new RangeRewardDB(); + range.setRangeUpgradeEquation("3"); + tier.setRewards(List.of(range)); + + dm.saveUpgradeTier(tier); + } + + // ─── Example 6: Donor Perk ──────────────────────────────────────────────── + // Demonstrates: PermissionPrice (gate) → CommandReward (one-time purchase) + + private void seedDonorPerk(UpgradesDataManager dm, String gm) { + UpgradeData data = dm.createUpgradeData(gm + "_example_donor", gm, null); + if (data == null) return; + data.setName("Donor Perk"); + data.setIcon(new ItemStack(Material.NETHER_STAR)); + data.setOrder(6); + data.setDescription(List.of("Example: requires the upgrades.example.donor permission. Runs a console command on purchase.")); + dm.saveUpgradeData(data); + + UpgradeTier tier = dm.createUpgradeTier(gm + "_example_donor_t1", data, 0, 0, null); + if (tier == null) return; + tier.setName("Donor Perk"); + + PermissionPriceDB perm = new PermissionPriceDB(); + perm.setPermission("upgrades.example.donor"); + tier.setPrices(List.of(perm)); + + CommandRewardDB cmd = new CommandRewardDB(); + List commands = new ArrayList<>(); + commands.add("say [player] has unlocked the Donor Perk on their island!"); + cmd.setCommands(commands); + cmd.setConsole(true); + tier.setRewards(List.of(cmd)); + + dm.saveUpgradeTier(tier); + } + + // ─── Example 7: Spawner Boost ───────────────────────────────────────────── + // Demonstrates: MoneyPrice → SpawnerRewardDB (5 purchases) + + private void seedSpawnerBoost(UpgradesDataManager dm, String gm) { + UpgradeData data = dm.createUpgradeData(gm + "_example_spawner", gm, null); + if (data == null) return; + data.setName("Spawner Boost"); + data.setIcon(new ItemStack(Material.SPAWNER)); + data.setOrder(7); + data.setDescription(List.of("Example: pay $2000 for +0.5 extra entities per spawner trigger.")); + dm.saveUpgradeData(data); + + UpgradeTier tier = dm.createUpgradeTier(gm + "_example_spawner_t1", data, 0, 4, null); + if (tier == null) return; + tier.setName("Spawner Boost"); + + MoneyPriceDB money = new MoneyPriceDB(); + money.setAmountEquation("2000"); + tier.setPrices(List.of(money)); + + SpawnerRewardDB spawner = new SpawnerRewardDB(); + spawner.setSpawnBonusEquation("0.5"); + tier.setRewards(List.of(spawner)); + + dm.saveUpgradeTier(tier); + } + + // ─── Example 8: Crop Growth Boost ───────────────────────────────────────── + // Demonstrates: MoneyPrice → CropGrowthRewardDB (5 purchases) + + private void seedCropBoost(UpgradesDataManager dm, String gm) { + UpgradeData data = dm.createUpgradeData(gm + "_example_cropgrowth", gm, null); + if (data == null) return; + data.setName("Crop Growth Boost"); + data.setIcon(new ItemStack(Material.WHEAT)); + data.setOrder(8); + data.setDescription(List.of("Example: pay $500 for 50% extra growth chance per crop tick.")); + dm.saveUpgradeData(data); + + UpgradeTier tier = dm.createUpgradeTier(gm + "_example_cropgrowth_t1", data, 0, 4, null); + if (tier == null) return; + tier.setName("Crop Growth Boost"); + + MoneyPriceDB money = new MoneyPriceDB(); + money.setAmountEquation("500"); + tier.setPrices(List.of(money)); + + CropGrowthRewardDB crop = new CropGrowthRewardDB(); + crop.setGrowthBonusEquation("0.5"); + tier.setRewards(List.of(crop)); + + dm.saveUpgradeTier(tier); + } +} diff --git a/src/main/java/world/bentobox/upgrades/UpgradesAddon.java b/src/main/java/world/bentobox/upgrades/UpgradesAddon.java new file mode 100644 index 0000000..c78d9f0 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/UpgradesAddon.java @@ -0,0 +1,296 @@ +package world.bentobox.upgrades; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.bukkit.Material; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import world.bentobox.bentobox.api.addons.Addon; +import world.bentobox.bentobox.api.flags.Flag; +import world.bentobox.bentobox.api.flags.clicklisteners.CycleClick; +import world.bentobox.bentobox.database.Database; +import world.bentobox.bentobox.hooks.VaultHook; +import world.bentobox.bentobox.managers.RanksManager; +import world.bentobox.level.Level; +import world.bentobox.limits.Limits; +import world.bentobox.upgrades.api.UpgradeAPI; +import world.bentobox.upgrades.command.PlayerUpgradeCommand; +import world.bentobox.upgrades.command.admin.AdminCommand; +import world.bentobox.upgrades.config.Settings; +import world.bentobox.upgrades.dataobjects.UpgradesData; +import world.bentobox.upgrades.dataobjects.prices.IslandLevelPrice; +import world.bentobox.upgrades.dataobjects.prices.ItemPrice; +import world.bentobox.upgrades.dataobjects.prices.MoneyPrice; +import world.bentobox.upgrades.dataobjects.prices.PermissionPrice; +import world.bentobox.upgrades.dataobjects.rewards.CommandReward; +import world.bentobox.upgrades.dataobjects.rewards.CropGrowthReward; +import world.bentobox.upgrades.dataobjects.rewards.LimitsReward; +import world.bentobox.upgrades.dataobjects.rewards.RangeReward; +import world.bentobox.upgrades.dataobjects.rewards.SpawnerReward; +import world.bentobox.upgrades.DefaultUpgradeSeeder; +import world.bentobox.upgrades.upgrades.DatabaseUpgrade; +import world.bentobox.upgrades.listeners.CropGrowthListener; +import world.bentobox.upgrades.listeners.IslandChangeListener; +import world.bentobox.upgrades.listeners.JoinPermCheckListener; +import world.bentobox.upgrades.listeners.SpawnerUpgradeListener; +import world.bentobox.upgrades.ui.utils.ChatInput; + +public class UpgradesAddon extends Addon { + + @Override + public void onLoad() { + super.onLoad(); + this.saveDefaultConfig(); + this.settings = new Settings(this); + } + + @Override + public void onEnable() { + if (this.getState() + .equals(State.DISABLED)) { + this.logWarning("Upgrades Addon is not available or disabled!"); + return; + } + + this.hookedGameModes = new ArrayList<>(); + + getPlugin().getAddonsManager() + .getGameModeAddons() + .stream() + .filter(g -> !settings.getDisabledGameModes() + .contains(g.getDescription() + .getName())) + .forEach(g -> { + // Hook player command if present + g.getPlayerCommand() + .ifPresent(pc -> { + new PlayerUpgradeCommand(this, pc); + UpgradesAddon.UPGRADES_RANK_RIGHT.addGameModeAddon(g); + this.hooked = true; + this.hookedGameModes.add(g.getDescription() + .getName()); + }); + // Hook admin command if present + g.getAdminCommand() + .ifPresent(ac -> new AdminCommand(this, ac, g)); + }); + + if (this.hooked) { + this.upgradesManager = new UpgradesManager(this); + this.upgradesManager.addGameModes(this.hookedGameModes); + + this.upgradesDataManager = new UpgradesDataManager(this); + + this.chatInput = new ChatInput(this); + + if ((levelAddon = (Level) this.getAddonByName("Level") + .orElse(null)) == null) { + this.logWarning("Level addon not found so Upgrades won't look for Island Level"); + } + + if ((limitsAddon = (Limits) this.getAddonByName("Limits") + .orElse(null)) == null) { + this.logWarning("Limits addon not found so Island Upgrade won't look for IslandLevel"); + } + + if ((vault = this.getPlugin() + .getVault() + .orElse(null)) == null) { + this.logWarning("Vault plugin not found so Upgrades won't look for money"); + } + + this.upgradesManager.addPrice(new IslandLevelPrice()); + this.upgradesManager.addPrice(new MoneyPrice()); + this.upgradesManager.addPrice(new ItemPrice()); + this.upgradesManager.addPrice(new PermissionPrice()); + this.upgradesManager.addReward(new RangeReward()); + this.upgradesManager.addReward(new LimitsReward()); + this.upgradesManager.addReward(new CommandReward()); + this.upgradesManager.addReward(new SpawnerReward()); + this.upgradesManager.addReward(new CropGrowthReward()); + + saveResource("panels/upgrades_panel.yml", false); + + // Seed example upgrades for any game mode that has none yet + new DefaultUpgradeSeeder(this).seedIfEmpty(); + + // Load database-backed upgrades + this.hookedGameModes.forEach(gameModeName -> + this.upgradesDataManager.getUpgradeDataByGameMode(gameModeName).forEach(data -> { + if (data.isActive()) this.registerUpgrade(new DatabaseUpgrade(this, data)); + }) + ); + + this.registerListener(new IslandChangeListener(this)); + this.registerListener(new SpawnerUpgradeListener(this)); + this.registerListener(new CropGrowthListener(this)); + + if (this.isLimitsProvided()) + this.registerListener(new JoinPermCheckListener(this)); + + getPlugin().getFlagsManager() + .registerFlag(UpgradesAddon.UPGRADES_RANK_RIGHT); + + this.log("Upgrades addon enabled"); + } else { + this.logError( + "Upgrades addon could not hook into any GameMode and therefore will not do anything"); + this.setState(State.DISABLED); + } + } + + @Override + public void onDisable() { + if (this.upgradesCache != null) + this.upgradesCache.values() + .forEach(this.database::saveObjectAsync); + if (this.upgradesDataManager != null) + this.upgradesDataManager.disable(); + } + + @Override + public void onReload() { + super.onReload(); + + if (this.hooked) { + this.settings = new Settings(this); + } + if (this.upgradesDataManager != null) + this.upgradesDataManager.reload(); + this.refreshDatabaseUpgrades(); + this.log("Island upgrade addon reloaded"); + } + + /** + * @return the settings + */ + public Settings getSettings() { + return settings; + } + + /** + * @return the islandUpgradesManager + */ + public UpgradesManager getUpgradesManager() { + return upgradesManager; + } + + public UpgradesDataManager getUpgradeDataManager() { + return this.upgradesDataManager; + } + + public ChatInput getChatInput() { + return this.chatInput; + } + + public Database getDatabase() { + return this.database; + } + + public UpgradesData getUpgradesLevels(@NonNull String targetIsland) { + UpgradesData upgradesData = this.upgradesCache.get(targetIsland); + if (upgradesData != null) + return upgradesData; + UpgradesData data = this.database.objectExists(targetIsland) ? + Optional.ofNullable(this.database.loadObject(targetIsland)) + .orElse(new UpgradesData(targetIsland)) : + new UpgradesData(targetIsland); + this.upgradesCache.put(targetIsland, data); + return data; + } + + public void uncacheIsland(@Nullable String targetIsland, boolean save) { + UpgradesData data = this.upgradesCache.remove(targetIsland); + if (data == null) + return; + if (save) + this.database.saveObjectAsync(data); + } + + public Level getLevelAddon() { + return this.levelAddon; + } + + public Limits getLimitsAddon() { + return this.limitsAddon; + } + + public VaultHook getVaultHook() { + return this.vault; + } + + public boolean isLevelProvided() { + return this.levelAddon != null; + } + + public boolean isLimitsProvided() { + return this.limitsAddon != null; + } + + public boolean isVaultProvided() { + return this.vault != null; + } + + public List getHookedGameModes() { + return Collections.unmodifiableList(hookedGameModes); + } + + public Set getAvailableUpgrades() { + return this.upgrade; + } + + public void registerUpgrade(UpgradeAPI upgrade) { + this.upgrade.add(upgrade); + } + + public void refreshDatabaseUpgrades() { + this.upgrade.removeIf(u -> u instanceof DatabaseUpgrade); + this.hookedGameModes.forEach(gameModeName -> + this.upgradesDataManager.getUpgradeDataByGameMode(gameModeName).forEach(data -> { + if (data.isActive()) this.registerUpgrade(new DatabaseUpgrade(this, data)); + }) + ); + } + + private Settings settings; + + private boolean hooked; + + private List hookedGameModes = new ArrayList<>(); + + private UpgradesManager upgradesManager; + + private UpgradesDataManager upgradesDataManager; + + private ChatInput chatInput; + + private Set upgrade = new HashSet<>(); + + private Database database = new Database<>(this, UpgradesData.class); + + private Map upgradesCache = new HashMap<>(); + + private Level levelAddon; + + private Limits limitsAddon; + + private VaultHook vault; + + public static final Flag UPGRADES_RANK_RIGHT = + new Flag.Builder("UPGRADES_RANK_RIGHT", Material.GOLD_INGOT) + .type(Flag.Type.PROTECTION) + .mode(Flag.Mode.BASIC) + .clickHandler(new CycleClick("UPGRADES_RANK_RIGHT", RanksManager.MEMBER_RANK, + RanksManager.OWNER_RANK)) + .defaultRank(RanksManager.MEMBER_RANK) + .build(); + +} diff --git a/src/main/java/world/bentobox/upgrades/UpgradesDataManager.java b/src/main/java/world/bentobox/upgrades/UpgradesDataManager.java new file mode 100644 index 0000000..0b8b514 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/UpgradesDataManager.java @@ -0,0 +1,551 @@ +package world.bentobox.upgrades; + +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import org.bukkit.World; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.Database; +import world.bentobox.upgrades.dataobjects.UpgradeData; +import world.bentobox.upgrades.dataobjects.UpgradeTier; + +public class UpgradesDataManager { + + // ------------------------------------------------------------ + // Section: Constants + // ------------------------------------------------------------ + + /** + * Constants for storing placeholders of lang messages + */ + private static final String WHAT = "[what]"; + private static final String UPGRADEID = "[upgradeId]"; + + // ------------------------------------------------------------ + // Section: Variables + // ------------------------------------------------------------ + + private UpgradesAddon addon; + + /** + * Database variables + */ + private Database databaseUpgradeData; + private Database databaseUpgradeTier; + + /** + * Cache. Should be filed at load time + */ + private Map upgradeDataCache; + private Map upgradeTierCache; + + // ------------------------------------------------------------ + // Section: Constructor + // ------------------------------------------------------------ + + /** + * Init UpgradesDataManager + * + * @param addon + */ + public UpgradesDataManager(UpgradesAddon addon) { + this.addon = addon; + + this.addon.log("Loading upgrade data"); + + // Init database and cache + this.databaseUpgradeData = new Database(this.addon, UpgradeData.class); + this.databaseUpgradeTier = new Database(this.addon, UpgradeTier.class); + this.upgradeDataCache = new HashMap(); + this.upgradeTierCache = new HashMap(); + + // Load cache + this.load(); + } + + // ------------------------------------------------------------ + // Section: Utils methods + // ------------------------------------------------------------ + + /** + * This function reload the cache + */ + public void reload() { + this.addon.log("Reloading upgrade data"); + // Load cache + // Missing the clear of old cache. Only override + this.load(); + } + + public void disable() { + this.addon.log("Saving upgrade data"); + this.saveAll(); + } + + /** + * Check if uniqueId is in upgradeData cache + * + * @param uniqueId to search + * @return if uniqueId was found + */ + public boolean hasUpgradeData(String uniqueId) { + return this.getUpgradeDataById(uniqueId) != null; + } + + /** + * Check if uniqueId is in upgradeTier cache + * + * @param uniqueId to search + * @return if uniqueId was found + */ + public boolean hasUpgradeTier(String uniqueId) { + return this.getUpgradeTierById(uniqueId) != null; + } + + // ------------------------------------------------------------ + // Section: Comparators + // ------------------------------------------------------------ + + /** + * Comparator used to sort the upgradeData + * First by order from lowest to highest with < 0 at the end + * If tie then sort by name + */ + private final Comparator upgradeDataComparator = (upgrade1, upgrade2) -> { + if (upgrade1.getOrder() == upgrade2.getOrder()) { + return upgrade1.getName().compareToIgnoreCase(upgrade2.getName()); + } else { + if (upgrade1.getOrder() < 0 || upgrade2.getOrder() < 0) + return Boolean.compare(upgrade1.getOrder() < 0, upgrade2.getOrder() < 0); + return Integer.compare(upgrade1.getOrder(), upgrade2.getOrder()); + } + }; + + /** + * Comparator used to sort the upgradeTier + * Compare the startLevel, from lowest to highest + */ + private final Comparator upgradeTierComparator = (upgrade1, upgrade2) -> { + return Integer.compare(upgrade1.getStartLevel(), upgrade2.getStartLevel()); + }; + + // ------------------------------------------------------------ + // Section: Loading + // ------------------------------------------------------------ + + /** + * Load all cache and run validation on it + */ + private void load() { + this.databaseUpgradeData.loadObjects().forEach(this::loadUpgradeData); + this.databaseUpgradeTier.loadObjects().forEach(this::loadUpgradeTier); + this.validate(); + } + + // ------------------------------------------------------------ + // Section: Loading / UpgradeData + // ------------------------------------------------------------ + + /** + * Use {@link #loadUpgradeData(UpgradeData, boolean, User)} by setting overwrite to true and user to null + * + * @param upgrade to add to the cache + * @return If upgrade could be loaded + */ + private boolean loadUpgradeData(UpgradeData upgrade) { + return this.loadUpgradeData(upgrade, true, null); + } + + /** + * Check that upgrade is not null, that it's valid and that it can be overwrite + * If valid It then add it to the cache and return true + * Else return false and if user not null then display message + * + * @param upgrade to add to the cache + * @param overwrite if uniqueId already in cache, should it overwrite it + * @param user if given, will send message in case of error + * @return true if added to the cache, false otherwise + */ + public boolean loadUpgradeData(UpgradeData upgrade, boolean overwrite, @Nullable User user) { + + // Check that upgrade is present + if (upgrade == null) { + if (user != null) + user.sendMessage("upgrades.error.loaderror", WHAT, "Upgrade data"); + this.addon.logWarning("Couldn't load upgrade data from database"); + return false; + } + + // Check that upgrade is valid + if (!upgrade.isValid()) { + if (user != null) + user.sendMessage("upgrades.error.upgradeinvalid", + UPGRADEID, upgrade.getUniqueId(), + WHAT, "data"); + this.addon.logWarning("Data for upgrade data " + upgrade.getUniqueId() + " is invalid. You should look in the database"); + return false; + } + + // Check if uniqueId is already in cache and if it can overwrite it + if (this.upgradeDataCache.containsKey(upgrade.getUniqueId())) { + if (!overwrite) { + if (user != null) + user.sendMessage("upgrades.message.skipupgradeload", + UPGRADEID, upgrade.getUniqueId(), + WHAT, "data"); + this.addon.logWarning("Tried to load " + upgrade.getUniqueId() + " but it was already loaded"); + return false; + } + } + + // Add to cache + this.upgradeDataCache.put(upgrade.getUniqueId(), upgrade); + if (user != null) { + user.sendMessage("upgrades.message.upgradeload", + UPGRADEID, upgrade.getUniqueId(), + WHAT, "data"); + } + this.addon.log("Upgrade data " + upgrade.getUniqueId() + " was loaded"); + return true; + } + + // ------------------------------------------------------------ + // Section: Loading / UpgradeTier + // ------------------------------------------------------------ + + /** + * use {@link #loadUpgradeTier(UpgradeTier, boolean, User)} by setting overwrite to true and use to null + * @param upgade tier to add to the cache + * @return If added to cache true, else false + */ + private boolean loadUpgradeTier(UpgradeTier upgade) { + return this.loadUpgradeTier(upgade, true, null); + } + + /** + * Check that tier is not null, that it's valid and that it can be overwrite + * If valid It then add it to the cache and return true + * Else return false and if user not null then display message + * + * @param upgrade tier to add to the cache + * @param overwrite if uniqueId already in cache, should it overwrite it + * @param user if given, will send message in case of error + * @return true if added to the cache, false otherwise + */ + public boolean loadUpgradeTier(UpgradeTier upgrade, boolean overwrite, @Nullable User user) { + + // Check that upgrade tier is present + if (upgrade == null) { + if (user != null) + user.sendMessage("upgrades.error.loaderror", WHAT, "Upgrade tier"); + this.addon.logWarning("Couldn't load upgrade tier from database"); + return false; + } + + // Check that upgrade tier is valid + if (!upgrade.isValid()) { + if (user != null) + user.sendMessage("upgrades.error.upgradeinvalid", + UPGRADEID, upgrade.getUniqueId(), + WHAT, "tier"); + this.addon.logWarning("Data for upgrade tier " + upgrade.getUniqueId() + " is invalid. You should look in the database"); + return false; + } + + // Check if uniqueId is already in cache and if it can overwrite it + if (this.upgradeTierCache.containsKey(upgrade.getUniqueId())) { + if (!overwrite) { + if (user != null) + user.sendMessage("upgrades.message.skipupgradeload", + UPGRADEID, upgrade.getUniqueId(), + WHAT, "tier"); + this.addon.logWarning("Tried to load " + upgrade.getUniqueId() + " but it was already loaded"); + return false; + } + } + + // Add to cache + this.upgradeTierCache.put(upgrade.getUniqueId(), upgrade); + if (user != null) { + user.sendMessage("upgrades.message.upgradeload", + UPGRADEID, upgrade.getUniqueId(), + WHAT, "tier"); + } + this.addon.log("Upgrade tier " + upgrade.getUniqueId() + " was loaded"); + return true; + } + + // ------------------------------------------------------------ + // Section: Validate loading + // ------------------------------------------------------------ + + /** + * Run all validate of cache + */ + private void validate() { + this.validateUpgradeTier(); + } + + /** + * Check that each tier has a loaded parent + * Unload the tier if not + */ + private void validateUpgradeTier() { + this.upgradeTierCache.values().forEach(tier -> { + if (!this.upgradeDataCache.containsKey(tier.getUpgrade())) { + this.addon.logWarning("Upgrade tier " + tier.getUniqueId() + " has a reference to an unknow upgrade data. It will be skiped"); + this.upgradeTierCache.remove(tier.getUniqueId()); + } + }); + } + + // ------------------------------------------------------------ + // Section: Saving data + // ------------------------------------------------------------ + + /** + * Save all cache + */ + public void saveAll() { + this.saveUpgradeDatas(); + this.saveUpgradeTiers(); + } + + /** + * Save all upgradeData of the upgradeData cache + */ + public void saveUpgradeDatas() { + this.upgradeDataCache.values().forEach(this.databaseUpgradeData::saveObjectAsync); + } + + /** + * Save given upgradeData async + * @param upgrade data to save + * @return + */ + public CompletableFuture saveUpgradeData(@NonNull UpgradeData upgrade) { + return this.databaseUpgradeData.saveObjectAsync(upgrade); + } + + /** + * Save all upgradeTier of the upgradeTier cache + */ + public void saveUpgradeTiers() { + this.upgradeTierCache.values().forEach(this.databaseUpgradeTier::saveObjectAsync); + } + + /** + * Save given upgradeTier asyn + * @param tier upgrade tier to save + * @return + */ + public CompletableFuture saveUpgradeTier(UpgradeTier tier) { + return this.databaseUpgradeTier.saveObjectAsync(tier); + } + + // ------------------------------------------------------------ + // Section: Getting UpgradeData + // ------------------------------------------------------------ + + /** + * use {@link #getUpgradeDataByGameMode(String)} by getting name from world + * @param world to filter for upgrades + * @return all upgradeData link to world + */ + public List getUpgradeDataByGameMode(@NonNull World world) { + return this.addon.getPlugin().getIWM().getAddon(world) + .map(gameMode -> this.getUpgradeDataByGameMode(gameMode.getDescription().getName())) + .orElse(Collections.emptyList()); + } + + /** + * Get all upgradeData link to world + * Sort them using {@link #upgradeDataComparator} + * + * @param world to look for + * @return all upgradeData link to world + */ + public List getUpgradeDataByGameMode(@NonNull String world) { + return this.upgradeDataCache.values().stream() + .filter(upgrade -> upgrade.getWorld().equals(world)) + .sorted(this.upgradeDataComparator) + .collect(Collectors.toList()); + } + + /** + * Get upgradeData from it's uniqueId + * + * @param uniqueId to look for + * @return UpgradeData or null if not found + */ + @Nullable + public UpgradeData getUpgradeDataById(@NonNull String uniqueId) { + return this.upgradeDataCache.get(uniqueId); + } + + // ------------------------------------------------------------ + // Section: Getting UpgradeTier + // ------------------------------------------------------------ + + /** + * use {@link #getUpgradeTierByUpgradeData(String)} by getting id from upgradeData + * + * @param upgradeData to filter for tier + * @return all upgradeTier link to this upgradeData + */ + public List getUpgradeTierByUpgradeData(@NonNull UpgradeData upgradeData) { + return this.getUpgradeTierByUpgradeData(upgradeData.getUniqueId()); + } + + /** + * Get all upgradeTier link to this upgradeData + * Sort them using {@link #upgradeTierComparator} + * + * @param upgradeDataId to look for + * @return all upgradeTier link to this upgradeData + */ + public List getUpgradeTierByUpgradeData(@NonNull String upgradeDataId) { + return this.upgradeTierCache.values().stream() + .filter(upgrade -> upgrade.getUpgrade().equals(upgradeDataId)) + .sorted(this.upgradeTierComparator) + .collect(Collectors.toList()); + } + + /** + * Get upgradeTier from it's uniqueId + * + * @param uniqueId to look for + * @return upgradeTier or null if not found + */ + @Nullable + public UpgradeTier getUpgradeTierById(@NonNull String uniqueId) { + return this.upgradeTierCache.get(uniqueId); + } + + // ------------------------------------------------------------ + // Section: Create Methods + // ------------------------------------------------------------ + + /** + * Use {@link #createUpgradeData(String, String, User)} by getting world name from world + * + * @param uniqueId used to create upgradeData + * @param world in which world this upgradeData should be used + * @param user if given, then send messages + * @return UpgradeData or null if couldn't create it + */ + @Nullable + public UpgradeData createUpgradeData(@NonNull String uniqueId, @NonNull World world, @Nullable User user) { + return this.addon.getPlugin().getIWM().getAddon(world) + .map(gm -> this.createUpgradeData(uniqueId, gm.getDescription().getName(), user)) + .orElse(null); + } + + /** + * Check that uniqueId isn't already used + * Then create a new UpgradeData + * Finally save and load it + * + * @param uniqueId used to create UpgradeData + * @param world in which world this upgradeData should be used + * @param user if given, then send messages + * @return UpgradeData or null if couldn't create it + */ + @Nullable + public UpgradeData createUpgradeData(@NonNull String uniqueId, @NonNull String world, @Nullable User user) { + if (this.hasUpgradeData(uniqueId)) + return null; + + UpgradeData upgrade = new UpgradeData(); + upgrade.setUniqueId(uniqueId); + upgrade.setWorld(world); + + this.saveUpgradeData(upgrade); + this.loadUpgradeData(upgrade, true, user); + + return upgrade; + } + + /** + * Use {@link #createUpgradeTier(String, String, int, int, User)} by getting upgradeDataId from upgradeData + * + * @param uniqueId used to create upgradeTier + * @param upgrade for which upgradeData this upgradeTier should be used + * @param startLevel Start level of this tier + * @param endLevel End level of this tier + * @param user if given, then send message + * @return UpgradeTier or null if couldn't create it + */ + @Nullable + public UpgradeTier createUpgradeTier(@NonNull String uniqueId, @NonNull UpgradeData upgrade, int startLevel, int endLevel, @Nullable User user) { + return this.createUpgradeTier(uniqueId, upgrade.getUniqueId(), startLevel, endLevel, user); + } + + /** + * Check that uniqueId isn't already used + * Then create a new UpgradeTier + * Finally save and load it + * + * @param uniqueId used to create upgradeTier + * @param upgradeId for which upgradeData this upgradeTier should be used + * @param startLevel Start level of this tier + * @param endLevel End level of this tier + * @param user if given, then send message + * @return UpgradeTier or null if couldn't create it + */ + @Nullable + public UpgradeTier createUpgradeTier(@NonNull String uniqueId, @NonNull String upgradeId, int startLevel, int endLevel, @Nullable User user) { + if (this.hasUpgradeTier(uniqueId)) + return null; + + UpgradeTier tier = new UpgradeTier(); + tier.setUniqueId(uniqueId); + tier.setUpgrade(upgradeId); + tier.setStartLevel(startLevel); + tier.setEndLevel(endLevel); + + this.saveUpgradeTier(tier); + this.loadUpgradeTier(tier, true, user); + + return tier; + } + + // ------------------------------------------------------------ + // Section: Delete Methods + // ------------------------------------------------------------ + + /** + * Delete one upgradeData from the cache and database + * + * TODO: Delete it's tier + * @param upgrade to delete + */ + public void deleteUpgradeData(UpgradeData upgrade) { + if (this.upgradeDataCache.containsKey(upgrade.getUniqueId())) { + this.databaseUpgradeData.deleteObject(upgrade); + this.upgradeDataCache.remove(upgrade.getUniqueId()); + } + } + + /** + * Delete one upgradeTier from the cache and database + * + * @param tier to delete + */ + public void deleteUpgradeTier(UpgradeTier tier) { + if (this.upgradeTierCache.containsKey(tier.getUniqueId())) { + this.databaseUpgradeTier.deleteObject(tier); + this.upgradeTierCache.remove(tier.getUniqueId()); + } + } + +} diff --git a/src/main/java/world/bentobox/upgrades/UpgradesManager.java b/src/main/java/world/bentobox/upgrades/UpgradesManager.java new file mode 100644 index 0000000..8480597 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/UpgradesManager.java @@ -0,0 +1,682 @@ +package world.bentobox.upgrades; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.entity.EntityType; + +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.limits.objects.IslandBlockCount; +import world.bentobox.upgrades.config.Settings; +import world.bentobox.upgrades.dataobjects.prices.Price; +import world.bentobox.upgrades.dataobjects.rewards.Reward; + +public class UpgradesManager { + + public UpgradesManager(UpgradesAddon addon) { + this.addon = addon; + this.hookedGameModes = new HashSet<>(); + this.activatedPrices = new HashMap<>(); + this.activatedRewards = new HashMap<>(); + } + + protected void addGameModes(List gameModes) { + this.hookedGameModes.addAll(gameModes); + } + + public boolean canOperateInWorld(World world) { + return this.addon.getPlugin() + .getIWM() + .getAddon(world) + .map(a -> this.hookedGameModes.contains(a.getDescription() + .getName())) + .orElse(false); + } + + public void addPrice(Price price) { + this.activatedPrices.put(price.getClass(), price); + } + + public List getPrices() { + return new ArrayList<>(this.activatedPrices.values()); + } + + public Price searchPrice(Class price) { + return this.activatedPrices.get(price); + } + + public void addReward(Reward reward) { + this.activatedRewards.put(reward.getClass(), reward); + } + + public List getRewards() { + return new ArrayList<>(this.activatedRewards.values()); + } + + public Reward searchReward(Class reward) { + return this.activatedRewards.get(reward); + } + + private Optional getGameModeName(World world) { + return this.addon.getPlugin() + .getIWM() + .getAddon(world) + .map(a -> a.getDescription() + .getName()); + } + + public int getIslandLevel(Island island) { + if (!this.addon.isLevelProvided()) + return 0; + + if (island == null) { + this.addon.logError("Island couldn't be found"); + return 0; + } + + return (int) this.addon.getLevelAddon().getManager().getLevelsData(island).getLevel(); + } + + public List getAllRangeUpgradeTiers(World world) { + Optional nameOpt = getGameModeName(world); + if (nameOpt.isEmpty()) return Collections.emptyList(); + String name = nameOpt.get(); + + Map defaultTiers = this.addon.getSettings() + .getDefaultRangeUpgradeTierMap(); + Map customAddonTiers = this.addon.getSettings() + .getAddonRangeUpgradeTierMap(name); + + List tierList; + + if (customAddonTiers.isEmpty()) + tierList = new ArrayList<>(defaultTiers.values()); + else { + Set uniqueIDSet = new HashSet<>(customAddonTiers.keySet()); + uniqueIDSet.addAll(defaultTiers.keySet()); + tierList = new ArrayList<>(uniqueIDSet.size()); + + uniqueIDSet.forEach( + id -> tierList.add(customAddonTiers.getOrDefault(id, defaultTiers.get(id)))); + } + + if (tierList.isEmpty()) + return Collections.emptyList(); + + tierList.sort(Comparator.comparingInt(Settings.UpgradeTier::getMaxLevel)); + + return tierList; + } + + public Map> getAllBlockLimitsUpgradeTiers(World world) { + Optional nameOpt = getGameModeName(world); + if (nameOpt.isEmpty()) { + return Collections.emptyMap(); + } + String name = nameOpt.get(); + + Map> defaultTiers = this.addon.getSettings() + .getDefaultBlockLimitsUpgradeTierMap(); + Map> customAddonTiers = this.addon.getSettings() + .getAddonBlockLimitsUpgradeTierMap(name); + + Map> tierList = new EnumMap<>(Material.class); + + if (customAddonTiers.isEmpty()) { + defaultTiers.forEach((mat, tiers) -> tierList.put(mat, new ArrayList<>(tiers.values()))); + } else { + customAddonTiers.forEach((mat, tiers) -> { + Set uniqueIDSet = new HashSet<>(tiers.keySet()); + if (defaultTiers.containsKey(mat)) + uniqueIDSet.addAll(defaultTiers.get(mat) + .keySet()); + List matTier = new ArrayList<>(uniqueIDSet.size()); + + uniqueIDSet.forEach(id -> matTier.add(tiers.getOrDefault(id, defaultTiers.get(mat) + .get(id)))); + tierList.put(mat, matTier); + }); + + defaultTiers.forEach( + (mat, tiers) -> tierList.putIfAbsent(mat, new ArrayList<>(tiers.values()))); + } + + if (tierList.isEmpty()) { + return Collections.emptyMap(); + } + + tierList.forEach( + (mat, tiers) -> tiers.sort(Comparator.comparingInt(Settings.UpgradeTier::getMaxLevel))); + + return tierList; + } + + public Map> getAllEntityLimitsUpgradeTiers(World world) { + Optional nameOpt = getGameModeName(world); + if (nameOpt.isEmpty()) { + return Collections.emptyMap(); + } + String name = nameOpt.get(); + + Map> defaultTiers = this.addon.getSettings() + .getDefaultEntityLimitsUpgradeTierMap(); + Map> customAddonTiers = this.addon.getSettings() + .getAddonEntityLimitsUpgradeTierMap(name); + + Map> tierList = new EnumMap<>(EntityType.class); + + if (customAddonTiers.isEmpty()) { + defaultTiers.forEach((ent, tiers) -> tierList.put(ent, new ArrayList<>(tiers.values()))); + } else { + customAddonTiers.forEach((ent, tiers) -> { + Set uniqueIDSet = new HashSet<>(tiers.keySet()); + if (defaultTiers.containsKey(ent)) + uniqueIDSet.addAll(defaultTiers.get(ent) + .keySet()); + List entTier = new ArrayList<>(uniqueIDSet.size()); + + uniqueIDSet.forEach(id -> entTier.add(tiers.getOrDefault(id, defaultTiers.get(ent) + .get(id)))); + tierList.put(ent, entTier); + }); + + defaultTiers.forEach( + (ent, tiers) -> tierList.putIfAbsent(ent, new ArrayList<>(tiers.values()))); + } + + if (tierList.isEmpty()) { + return Collections.emptyMap(); + } + + tierList.forEach( + (ent, tiers) -> tiers.sort(Comparator.comparingInt(Settings.UpgradeTier::getMaxLevel))); + + return tierList; + } + + public Map> getAllEntityGroupLimitsUpgradeTiers(World world) { + Optional nameOpt = getGameModeName(world); + if (nameOpt.isEmpty()) { + return Collections.emptyMap(); + } + String name = nameOpt.get(); + + Map> defaultTiers = this.addon.getSettings() + .getDefaultEntityGroupLimitsUpgradeTierMap(); + Map> customAddonTiers = this.addon.getSettings() + .getAddonEntityGroupLimitsUpgradeTierMap(name); + + Map> tierList = new HashMap<>(); + + if (customAddonTiers.isEmpty()) { + defaultTiers.forEach((ent, tiers) -> tierList.put(ent, new ArrayList<>(tiers.values()))); + } else { + customAddonTiers.forEach((ent, tiers) -> { + Set uniqueIDSet = new HashSet<>(tiers.keySet()); + if (defaultTiers.containsKey(ent)) + uniqueIDSet.addAll(defaultTiers.get(ent) + .keySet()); + List entTier = new ArrayList<>(uniqueIDSet.size()); + + uniqueIDSet.forEach(id -> entTier.add(tiers.getOrDefault(id, defaultTiers.get(ent) + .get(id)))); + tierList.put(ent, entTier); + }); + + defaultTiers.forEach( + (ent, tiers) -> tierList.putIfAbsent(ent, new ArrayList<>(tiers.values()))); + } + + if (tierList.isEmpty()) { + return Collections.emptyMap(); + } + + tierList.forEach( + (ent, tiers) -> tiers.sort(Comparator.comparingInt(Settings.UpgradeTier::getMaxLevel))); + + return tierList; + } + + public Map> getAllCommandUpgradeTiers(World world) { + Optional nameOpt = getGameModeName(world); + if (nameOpt.isEmpty()) { + return Collections.emptyMap(); + } + String name = nameOpt.get(); + + Map> defaultTiers = this.addon.getSettings() + .getDefaultCommandUpgradeTierMap(); + Map> customAddonTiers = this.addon.getSettings() + .getAddonCommandUpgradeTierMap(name); + + Map> tierList = new HashMap<>(); + + if (customAddonTiers.isEmpty()) { + defaultTiers.forEach((cmd, tiers) -> tierList.put(cmd, new ArrayList<>(tiers.values()))); + } else { + customAddonTiers.forEach((cmd, tiers) -> { + Set uniqueIDSet = new HashSet<>(tiers.keySet()); + if (defaultTiers.containsKey(cmd)) + uniqueIDSet.addAll(defaultTiers.get(cmd) + .keySet()); + List cmdTier = new ArrayList<>(uniqueIDSet.size()); + + uniqueIDSet.forEach(id -> cmdTier.add(tiers.getOrDefault(id, defaultTiers.get(cmd) + .get(id)))); + tierList.put(cmd, cmdTier); + }); + + defaultTiers.forEach( + (cmd, tiers) -> tierList.putIfAbsent(cmd, new ArrayList<>(tiers.values()))); + } + + if (tierList.isEmpty()) { + return Collections.emptyMap(); + } + + tierList.forEach( + (cmd, tiers) -> tiers.sort(Comparator.comparingInt(Settings.UpgradeTier::getMaxLevel))); + + return tierList; + } + + public Settings.UpgradeTier getRangeUpgradeTier(int rangeLevel, World world) { + List tierList = this.getAllRangeUpgradeTiers(world); + + if (tierList.isEmpty()) + return null; + + Settings.UpgradeTier rangeUpgradeTier = tierList.get(0); + + if (rangeUpgradeTier.getMaxLevel() < 0) + return rangeUpgradeTier; + + for (int i = 0; i < tierList.size(); i++) { + if (rangeLevel <= tierList.get(i) + .getMaxLevel()) + return tierList.get(i); + } + + return null; + } + + public Settings.UpgradeTier getBlockLimitsUpgradeTier(Material mat, int limitsLevel, World world) { + Map> matTierList = + this.getAllBlockLimitsUpgradeTiers(world); + + if (matTierList.isEmpty()) { + return null; + } + + if (!matTierList.containsKey(mat)) { + return null; + } + + List tierList = matTierList.get(mat); + + for (int i = 0; i < tierList.size(); i++) { + if (limitsLevel <= tierList.get(i) + .getMaxLevel()) + return tierList.get(i); + } + return null; + } + + public Settings.UpgradeTier getEntityLimitsUpgradeTier(EntityType ent, int limitsLevel, + World world) { + Map> entTierList = + this.getAllEntityLimitsUpgradeTiers(world); + + if (entTierList.isEmpty()) { + return null; + } + + if (!entTierList.containsKey(ent)) { + return null; + } + + List tierList = entTierList.get(ent); + + for (int i = 0; i < tierList.size(); i++) { + if (limitsLevel <= tierList.get(i) + .getMaxLevel()) + return tierList.get(i); + } + + return null; + } + + public Settings.UpgradeTier getEntityGroupLimitsUpgradeTier(String group, int limitsLevel, + World world) { + Map> entTierList = + this.getAllEntityGroupLimitsUpgradeTiers(world); + + if (entTierList.isEmpty()) { + return null; + } + + if (!entTierList.containsKey(group)) { + return null; + } + + List tierList = entTierList.get(group); + + for (int i = 0; i < tierList.size(); i++) { + if (limitsLevel <= tierList.get(i) + .getMaxLevel()) + return tierList.get(i); + } + + return null; + } + + public Settings.CommandUpgradeTier getCommandUpgradeTier(String cmd, int cmdLevel, World world) { + Map> cmdTierList = + this.getAllCommandUpgradeTiers(world); + + if (cmdTierList.isEmpty()) { + return null; + } + + if (!cmdTierList.containsKey(cmd)) { + return null; + } + + List tierList = cmdTierList.get(cmd); + + for (int i = 0; i < tierList.size(); i++) { + if (cmdLevel <= tierList.get(i) + .getMaxLevel()) + return tierList.get(i); + } + + return null; + } + + public Map getRangeUpgradeInfos(int rangeLevel, int islandLevel, int numberPeople, + World world) { + Settings.UpgradeTier rangeUpgradeTier = this.getRangeUpgradeTier(rangeLevel, world); + + if (rangeUpgradeTier == null) + return null; + + Map info = new HashMap<>(); + + info.put("islandMinLevel", + (int) rangeUpgradeTier.calculateIslandMinLevel(rangeLevel, islandLevel, numberPeople)); + info.put("vaultCost", + (int) rangeUpgradeTier.calculateVaultCost(rangeLevel, islandLevel, numberPeople)); + info.put("upgrade", + (int) rangeUpgradeTier.calculateUpgrade(rangeLevel, islandLevel, numberPeople)); + + return info; + } + + public int getRangePermissionLevel(int rangeLevel, World world) { + Settings.UpgradeTier rangeUpgradeTier = this.getRangeUpgradeTier(rangeLevel, world); + + if (rangeUpgradeTier == null) + return 0; + return rangeUpgradeTier.getPermissionLevel(); + } + + public String getRangeUpgradeTierName(int rangeLevel, World world) { + Settings.UpgradeTier rangeUpgradeTier = this.getRangeUpgradeTier(rangeLevel, world); + + if (rangeUpgradeTier == null) + return null; + return rangeUpgradeTier.getTierName(); + } + + public int getRangeUpgradeMax(World world) { + String name = getGameModeName(world).orElse(""); + return this.addon.getSettings() + .getMaxRangeUpgrade(name); + } + + public Map getBlockLimitsUpgradeInfos(Material mat, int limitsLevel, + int islandLevel, int numberPeople, + World world) { + Settings.UpgradeTier limitsUpgradeTier = this.getBlockLimitsUpgradeTier(mat, limitsLevel, world); + if (limitsUpgradeTier == null) { + return null; + } + + Map info = new HashMap<>(); + + info.put("islandMinLevel", + (int) limitsUpgradeTier.calculateIslandMinLevel(limitsLevel, islandLevel, numberPeople)); + info.put("vaultCost", + (int) limitsUpgradeTier.calculateVaultCost(limitsLevel, islandLevel, numberPeople)); + info.put("upgrade", + (int) limitsUpgradeTier.calculateUpgrade(limitsLevel, islandLevel, numberPeople)); + + return info; + } + + public int getBlockLimitsPermissionLevel(Material mat, int limitsLevel, World world) { + Settings.UpgradeTier limitsUpgradeTier = this.getBlockLimitsUpgradeTier(mat, limitsLevel, world); + + if (limitsUpgradeTier == null) + return 0; + return limitsUpgradeTier.getPermissionLevel(); + } + + public String getBlockLimitsUpgradeTierName(Material mat, int limitsLevel, World world) { + Settings.UpgradeTier limitsUpgradeTier = this.getBlockLimitsUpgradeTier(mat, limitsLevel, world); + + if (limitsUpgradeTier == null) + return null; + return limitsUpgradeTier.getTierName(); + } + + public int getBlockLimitsUpgradeMax(Material mat, World world) { + String name = getGameModeName(world).orElse(""); + return this.addon.getSettings() + .getMaxBlockLimitsUpgrade(mat, name); + } + + public Map getEntityLimitsUpgradeInfos(EntityType ent, int limitsLevel, + int islandLevel, int numberPeople, + World world) { + Settings.UpgradeTier limitsUpgradeTier = + this.getEntityLimitsUpgradeTier(ent, limitsLevel, world); + if (limitsUpgradeTier == null) { + return null; + } + + Map info = new HashMap<>(); + + info.put("islandMinLevel", + (int) limitsUpgradeTier.calculateIslandMinLevel(limitsLevel, islandLevel, numberPeople)); + info.put("vaultCost", + (int) limitsUpgradeTier.calculateVaultCost(limitsLevel, islandLevel, numberPeople)); + info.put("upgrade", + (int) limitsUpgradeTier.calculateUpgrade(limitsLevel, islandLevel, numberPeople)); + + return info; + } + + public Map getEntityGroupLimitsUpgradeInfos(String group, int limitsLevel, + int islandLevel, int numberPeople, + World world) { + Settings.UpgradeTier limitsUpgradeTier = + this.getEntityGroupLimitsUpgradeTier(group, limitsLevel, world); + if (limitsUpgradeTier == null) { + return null; + } + + Map info = new HashMap<>(); + + info.put("islandMinLevel", + (int) limitsUpgradeTier.calculateIslandMinLevel(limitsLevel, islandLevel, numberPeople)); + info.put("vaultCost", + (int) limitsUpgradeTier.calculateVaultCost(limitsLevel, islandLevel, numberPeople)); + info.put("upgrade", + (int) limitsUpgradeTier.calculateUpgrade(limitsLevel, islandLevel, numberPeople)); + + return info; + } + + public int getEntityLimitsPermissionLevel(EntityType ent, int limitsLevel, World world) { + Settings.UpgradeTier limitsUpgradeTier = + this.getEntityLimitsUpgradeTier(ent, limitsLevel, world); + + if (limitsUpgradeTier == null) + return 0; + return limitsUpgradeTier.getPermissionLevel(); + } + + public int getEntityGroupLimitsPermissionLevel(String group, int limitsLevel, World world) { + Settings.UpgradeTier limitsUpgradeTier = + this.getEntityGroupLimitsUpgradeTier(group, limitsLevel, world); + + if (limitsUpgradeTier == null) + return 0; + return limitsUpgradeTier.getPermissionLevel(); + } + + public String getEntityLimitsUpgradeTierName(EntityType ent, int limitsLevel, World world) { + Settings.UpgradeTier limitsUpgradeTier = + this.getEntityLimitsUpgradeTier(ent, limitsLevel, world); + + if (limitsUpgradeTier == null) + return null; + return limitsUpgradeTier.getTierName(); + } + + public String getEntityGroupLimitsUpgradeTierName(String group, int limitsLevel, World world) { + Settings.UpgradeTier limitsUpgradeTier = + this.getEntityGroupLimitsUpgradeTier(group, limitsLevel, world); + + if (limitsUpgradeTier == null) + return null; + return limitsUpgradeTier.getTierName(); + } + + public int getEntityLimitsUpgradeMax(EntityType ent, World world) { + String name = getGameModeName(world).orElse(""); + return this.addon.getSettings() + .getMaxEntityLimitsUpgrade(ent, name); + } + + public int getEntityGroupLimitsUpgradeMax(String group, World world) { + String name = getGameModeName(world).orElse(""); + return this.addon.getSettings() + .getMaxEntityGroupLimitsUpgrade(group, name); + } + + public Map getCommandUpgradeInfos(String cmd, int cmdLevel, int islandLevel, + int numberPeople, World world) { + Settings.CommandUpgradeTier cmdUpgradeTier = this.getCommandUpgradeTier(cmd, cmdLevel, world); + if (cmdUpgradeTier == null) { + return null; + } + + Map info = new HashMap<>(); + + info.put("islandMinLevel", + (int) cmdUpgradeTier.calculateIslandMinLevel(cmdLevel, islandLevel, numberPeople)); + info.put("vaultCost", + (int) cmdUpgradeTier.calculateVaultCost(cmdLevel, islandLevel, numberPeople)); + info.put("upgrade", (int) cmdUpgradeTier.calculateUpgrade(cmdLevel, islandLevel, numberPeople)); + + return info; + } + + public int getCommandPermissionLevel(String cmd, int cmdLevel, World world) { + Settings.CommandUpgradeTier cmdUpgradeTier = this.getCommandUpgradeTier(cmd, cmdLevel, world); + + if (cmdUpgradeTier == null) + return 0; + return cmdUpgradeTier.getPermissionLevel(); + } + + public String getCommandUpgradeTierName(String cmd, int cmdLevel, World world) { + Settings.CommandUpgradeTier cmdUpgradeTier = this.getCommandUpgradeTier(cmd, cmdLevel, world); + + if (cmdUpgradeTier == null) + return null; + return cmdUpgradeTier.getTierName(); + } + + public int getCommandUpgradeMax(String cmd, World world) { + String name = getGameModeName(world).orElse(""); + return this.addon.getSettings() + .getMaxCommandUpgrade(cmd, name); + } + + public List getCommandList(String cmd, int cmdLevel, Island island, String playerName) { + Settings.CommandUpgradeTier cmdUpgradeTier = + this.getCommandUpgradeTier(cmd, cmdLevel, island.getWorld()); + + if (cmdUpgradeTier == null) + return Collections.emptyList(); + return cmdUpgradeTier.getCommandList(playerName, island, cmdLevel); + } + + public Boolean isCommantConsole(String cmd, int cmdLevel, World world) { + Settings.CommandUpgradeTier cmdUpgradeTier = this.getCommandUpgradeTier(cmd, cmdLevel, world); + + if (cmdUpgradeTier == null) + return false; + return cmdUpgradeTier.getConsole(); + } + + public Map getEntityLimits(Island island) { + if (!this.addon.isLimitsProvided()) + return Collections.emptyMap(); + + Map entityLimits = new HashMap<>(this.addon.getLimitsAddon() + .getSettings() + .getLimits()); + IslandBlockCount ibc = this.addon.getLimitsAddon() + .getBlockLimitListener() + .getIsland(island.getUniqueId()); + if (ibc != null) ibc.getEntityLimits() + .forEach(entityLimits::put); + return entityLimits; + } + + public Map getEntityGroupLimits(Island island) { + if (!this.addon.isLimitsProvided()) + return Collections.emptyMap(); + + Map entityGroupLimits = new HashMap<>(this.addon.getLimitsAddon() + .getSettings() + .getGroupLimits() + .values() + .stream() + .flatMap(e -> e.stream()) + .distinct() + .collect(Collectors.toMap(e -> e.getName(), e -> e.getLimit()))); + IslandBlockCount ibc = this.addon.getLimitsAddon() + .getBlockLimitListener() + .getIsland(island.getUniqueId()); + if (ibc != null) ibc.getEntityGroupLimits() + .forEach(entityGroupLimits::put); + return entityGroupLimits; + } + + private UpgradesAddon addon; + + private Set hookedGameModes; + + private Map, Price> activatedPrices; + + private Map, Reward> activatedRewards; + +} diff --git a/src/main/java/world/bentobox/upgrades/api/UpgradeAPI.java b/src/main/java/world/bentobox/upgrades/api/UpgradeAPI.java new file mode 100644 index 0000000..de78230 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/api/UpgradeAPI.java @@ -0,0 +1,274 @@ +package world.bentobox.upgrades.api; + +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; +import java.util.UUID; + +import org.bukkit.Material; + +import net.milkbowl.vault.economy.EconomyResponse; +import world.bentobox.bentobox.api.addons.Addon; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.dataobjects.UpgradesData; + +/** + * Upgrade Object for IslandUpgradeAddon. Extend this to create a new upgrade + * + * @author Ikkino + * + */ +public abstract class UpgradeAPI { + + /** + * Initialize the upgrade object you should call it in your init methode + * + * @param addon This should be your addon + * @param name This is the name for the upgrade that will be used in the + * DataBase + * @param displayName This is the name that is shown to the user + * @param icon This is the icon shown to the user + */ + public UpgradeAPI(UpgradesAddon addon, String name, String displayName, Material icon) { + this.name = name; + this.displayName = displayName; + this.icon = icon; + this.addon = addon; + + this.playerCache = new TreeMap<>(); + this.ownDescription = new TreeMap<>(); + + this.upgradesAddon = addon; + this.addon.log("Added upgrade -> " + name); + } + + /** + * This function is called every times a user open the interface You should make + * it update the upgradeValues + * + * @param user This is the user that ask for the interface + * @param island This is the island concerned by the interface + */ + public abstract void updateUpgradeValue(User user, Island island); + + /** + * This function is called every times a user open the interface If it return + * false, the upgrade won't be showed to the user + * + * @param user This is the user that ask for the interface + * @param island This is the island concerned by the interface + * @return If true, then upgrade is shown else, it is hided + */ + public boolean isShowed(User user, Island island) { + return true; + } + + /** + * This function return true if the user can upgrade for this island. You can + * override it and call the super. + * + * The super test for islandLevel and for money + * + * @param user This is the user that try to upgrade + * @param island This is the island that is concerned + * @return Can upgrade + */ + public boolean canUpgrade(User user, Island island) { + UpgradeValues upgradeValues = this.getUpgradeValues(user); + boolean can = true; + + if (this.upgradesAddon.isLevelProvided() + && this.upgradesAddon.getUpgradesManager().getIslandLevel(island) < upgradeValues.getIslandLevel()) { + + can = false; + } + + if (this.upgradesAddon.isVaultProvided() + && !this.upgradesAddon.getVaultHook().has(user, upgradeValues.getMoneyCost())) { + + can = false; + } + + return can; + } + + /** + * This function is called when the user is upgrading for the island It is + * called after the canUpgrade function + * + * You should call the super to update the balance of the user as well as the + * level is the island + * + * @param user This is the user that do the upgrade + * @param island This is the island that is concerned + * @return If upgrade was successful + */ + public boolean doUpgrade(User user, Island island) { + UpgradeValues upgradeValues = this.getUpgradeValues(user); + + if (this.upgradesAddon.isVaultProvided()) { + EconomyResponse response = this.upgradesAddon.getVaultHook().withdraw(user, upgradeValues.getMoneyCost()); + if (!response.transactionSuccess()) { + this.addon.logWarning( + "User Money withdrawing failed user: " + user.getName() + " reason: " + response.errorMessage); + user.sendMessage("upgrades.error.costwithdraw"); + return false; + } + } + + UpgradesData data = this.upgradesAddon.getUpgradesLevels(island.getUniqueId()); + data.setUpgradeLevel(this.name, data.getUpgradeLevel(this.name) + 1); + + return true; + } + + /** + * @return The name that is used for the DataBase + */ + public String getName() { + return this.name; + } + + /** + * @return The name that is displayed to the user + */ + public String getDisplayName() { + return this.displayName; + } + + /** + * @param displayName To update the name to display to the user + */ + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + /** + * @return The icon that is displayed to the user + */ + public Material getIcon() { + return this.icon; + } + + public int getUpgradeLevel(Island island) { + return this.upgradesAddon.getUpgradesLevels(island.getUniqueId()).getUpgradeLevel(this.name); + } + + /** + * @return The actual description for the user + */ + public String getOwnDescription(User user) { + return this.ownDescription.get(user.getUniqueId()); + } + + /** + * @param user User to set the description + * @param description Description to set + */ + public void setOwnDescription(User user, String description) { + this.ownDescription.put(user.getUniqueId(), description); + } + + /** + * @return The actual upgradeValues + */ + public UpgradeValues getUpgradeValues(User user) { + return this.playerCache.get(user.getUniqueId()); + } + + /** + * @param upgrade Values to upgrades + */ + public void setUpgradeValues(User user, UpgradeValues upgrade) { + this.playerCache.put(user.getUniqueId(), upgrade); + } + + /** + * Function that get the upgrades addon You should use it to use the upgrades + * addon methods + * + * @return UpgradesAddon + */ + public UpgradesAddon getUpgradesAddon() { + return this.upgradesAddon; + } + + /** + * You shouldn't override this function + */ + @Override + public int hashCode() { + return Objects.hash(name); + } + + /** + * You shouldn't override this function + */ + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (!(obj instanceof UpgradeAPI)) { + return false; + } + UpgradeAPI other = (UpgradeAPI) obj; + return Objects.equals(name, other.name); + } + + private final String name; + private String displayName; + private Material icon; + private Addon addon; + private UpgradesAddon upgradesAddon; + private Map playerCache; + private Map ownDescription; + + public class UpgradeValues { + + public UpgradeValues(Integer islandLevel, Integer moneyCost, Integer upgradeValue) { + this.islandLevel = islandLevel; + this.moneyCost = moneyCost; + this.upgradeValue = upgradeValue; + } + + public int getIslandLevel() { + return islandLevel; + } + + public void setIslandLevel(int islandLevel) { + this.islandLevel = islandLevel; + } + + public int getMoneyCost() { + return moneyCost; + } + + public void setMoneyCost(int moneyCost) { + this.moneyCost = moneyCost; + } + + public int getUpgradeValue() { + return upgradeValue; + } + + public void setUpgradeValue(int upgradeValue) { + this.upgradeValue = upgradeValue; + } + + private int islandLevel; + private int moneyCost; + private int upgradeValue; + } + + @Override + public String toString() { + return "Upgrade [name=" + name + ", displayName=" + displayName + ", icon=" + icon + "]"; + } + +} diff --git a/src/main/java/world/bentobox/upgrades/command/PlayerUpgradeCommand.java b/src/main/java/world/bentobox/upgrades/command/PlayerUpgradeCommand.java new file mode 100644 index 0000000..af2a437 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/command/PlayerUpgradeCommand.java @@ -0,0 +1,81 @@ +package world.bentobox.upgrades.command; + +import java.util.List; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.ui.Panel; + +/** + * Player command for accessing the island upgrades panel. + * This command allows players to view and purchase upgrades for their island. + * Players must be on their island and have sufficient rank to use this command. + * + * @author tastybento + * @since 1.0.0 + */ +public class PlayerUpgradeCommand extends CompositeCommand { + + public PlayerUpgradeCommand(UpgradesAddon addon, CompositeCommand cmd) { + super(addon, cmd, "upgrade"); + + this.addon = addon; + } + + @Override + public void setup() { + this.setDescription("upgrades.commands.main.description"); + this.setOnlyPlayer(true); + } + + @Override + public boolean canExecute(User user, String label, List args) { + Island island = getIslands().getIsland(this.getWorld(), user); + + if (island == null) { + user.sendMessage("general.errors.no-island"); + return false; + } + + if (!island.onIsland(user.getLocation())) { + user.sendMessage("upgrades.error.notonisland"); + return false; + } + + if (!island.isAllowed(user, UpgradesAddon.UPGRADES_RANK_RIGHT)) { + user.sendMessage("general.errors.insufficient-rank", + TextVariables.RANK, + user.getTranslation(this.addon.getPlugin().getRanksManager().getRank(island.getRank(user)))); + return false; + } + + return true; + } + + @Override + public boolean execute(User user, String label, List args) { + if (args.isEmpty()) { + Island island = getIslands().getIsland(this.getWorld(), user); + + if (island == null) { + user.sendMessage("general.errors.no-island"); + return false; + } + + if (!island.onIsland(user.getLocation())) { + user.sendMessage("upgrades.error.notonisland"); + return false; + } + + new Panel(this.addon, island).showPanel(user); + return true; + } + this.showHelp(this, user); + return false; + } + + UpgradesAddon addon; +} diff --git a/src/main/java/world/bentobox/upgrades/command/admin/AdminCommand.java b/src/main/java/world/bentobox/upgrades/command/admin/AdminCommand.java new file mode 100644 index 0000000..ecb2b96 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/command/admin/AdminCommand.java @@ -0,0 +1,69 @@ +package world.bentobox.upgrades.command.admin; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.dataobjects.UpgradeData; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.dataobjects.prices.IslandLevelPrice; +import world.bentobox.upgrades.dataobjects.prices.Price; +import world.bentobox.upgrades.dataobjects.rewards.RangeReward; +import world.bentobox.upgrades.dataobjects.rewards.Reward; +import world.bentobox.upgrades.ui.admin.AdminPanel; + +public class AdminCommand extends CompositeCommand { + + public AdminCommand(UpgradesAddon addon, CompositeCommand cmd, GameModeAddon gameMode) { + super(addon, cmd, "upgrade"); + + this.addon = addon; + this.gameMode = gameMode; + } + + @Override + public void setup() { + this.setOnlyPlayer(true); + this.setDescription("upgrades.commands.admin.description"); + this.setParametersHelp("upgrades.commands.admin.parameters"); + this.setPermission("admin.upgrade"); + } + + @Override + public boolean execute(User user, String label, List args) { + /*UpgradeData upgrade = this.addon.getUpgradeDataManager().createUpgradeData("testUpgradeId", getWorld(), user); + upgrade.setIcon(new ItemStack(Material.GOLD_BLOCK)); + upgrade.setName("Test upgrade"); + this.addon.getUpgradeDataManager().saveUpgradeData(upgrade); + IslandLevelPrice price = new IslandLevelPrice(); + List prices = new ArrayList(); + prices.add(price); + price.setLevelNeededEquation("3"); + RangeReward reward = new RangeReward(); + List rewards = new ArrayList(); + rewards.add(reward); + reward.setRangeUpgradeEquation("5"); + UpgradeTier tier = this.addon.getUpgradeDataManager().createUpgradeTier("testtierid", upgrade, 0, 5, user); + tier.setIcon(new ItemStack(Material.GOLD_INGOT)); + tier.setName("Test tier"); + tier.setPrices(prices); + tier.setRewards(rewards); + this.addon.getUpgradeDataManager().saveUpgradeTier(tier);*/ + + if (user.isPlayer()) { + new AdminPanel(this.addon, this.gameMode, user).getBuild().build(); + return true; + } + return false; + } + + private UpgradesAddon addon; + private GameModeAddon gameMode; + +} diff --git a/src/main/java/world/bentobox/upgrades/config/Settings.java b/src/main/java/world/bentobox/upgrades/config/Settings.java new file mode 100644 index 0000000..1265437 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/config/Settings.java @@ -0,0 +1,1248 @@ +package world.bentobox.upgrades.config; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; + +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.EntityType; +import org.eclipse.jdt.annotation.NonNull; + +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; + +/** + * Represents the settings and configurations for the UpgradesAddon. + * Handles configuration data parsing and storage, enabling upgrades and + * managing limits for various game aspects like blocks, entities, and commands. + */ +public class Settings { + + /** + * The UpgradesAddon instance associated with this settings object. + */ + private final UpgradesAddon addon; + + /** + * Set of game modes where the upgrades are disabled. + */ + private final Set disabledGameModes; + + /** + * The escape string for chat input prompts. + */ + private String chatInputEscape; + + /** + * Maximum range for range upgrades. + */ + private int maxRangeUpgrade = 0; + + /** + * Flag indicating if the range upgrade is enabled. + */ + private boolean hasRangeUpgrade; + + /** + * Custom maximum range upgrades per game mode. + */ + private final Map customMaxRangeUpgrade = new TreeMap<>(); + + /** + * Default range upgrade tiers. + */ + private final Map rangeUpgradeTierMap = new TreeMap<>(); + + /** + * Custom range upgrade tiers per game mode. + */ + private final Map> customRangeUpgradeTierMap = new TreeMap<>(); + + /** + * Default block limits upgrades for each material. + */ + private final Map maxBlockLimitsUpgrade = new EnumMap<>(Material.class); + + /** + * Custom block limits upgrades for each material and game mode. + */ + private final Map> customMaxBlockLimitsUpgrade = new TreeMap<>(); + + /** + * Default block limits upgrade tiers for each material. + */ + private Map> blockLimitsUpgradeTierMap = new EnumMap<>(Material.class); + + /** + * Custom block limits upgrade tiers per game mode. + */ + private final Map>> customBlockLimitsUpgradeTierMap = new TreeMap<>(); + + /** + * Entity type to material icon mapping. + */ + private final Map entityIcon = new EnumMap<>(EntityType.class); + + /** + * Entity group to material icon mapping. + */ + private final Map entityGroupIcon = new TreeMap<>(); + + /** + * Default entity limits upgrades for each entity type. + */ + private final Map maxEntityLimitsUpgrade = new EnumMap<>(EntityType.class); + + /** + * Default entity group limits upgrades. + */ + private final Map maxEntityGroupLimitsUpgrade = new TreeMap<>(); + + /** + * Custom entity limits upgrades per game mode. + */ + private final Map> customMaxEntityLimitsUpgrade = new TreeMap<>(); + + /** + * Custom entity group limits upgrades per game mode. + */ + private final Map> customMaxEntityGroupLimitsUpgrade = new TreeMap<>(); + + /** + * Default entity limits upgrade tiers for each entity type. + */ + private Map> entityLimitsUpgradeTierMap = new EnumMap<>(EntityType.class); + + /** + * Default entity group limits upgrade tiers. + */ + private Map> entityGroupLimitsUpgradeTierMap = new TreeMap<>(); + + /** + * Custom entity limits upgrade tiers per game mode. + */ + private final Map>> customEntityLimitsUpgradeTierMap = new TreeMap<>(); + + /** + * Custom entity group limits upgrade tiers per game mode. + */ + private final Map>> customEntityGroupLimitsUpgradeTierMap = new TreeMap<>(); + + /** + * Default command limits upgrades. + */ + private final Map maxCommandUpgrade = new TreeMap<>(); + + /** + * Custom command limits upgrades per game mode. + */ + private final Map> customMaxCommandUpgrade = new TreeMap<>(); + + /** + * Default command upgrade tiers. + */ + private Map> commandUpgradeTierMap = new TreeMap<>(); + + /** + * Custom command upgrade tiers per game mode. + */ + private final Map>> customCommandUpgradeTierMap = new TreeMap<>(); + + /** + * Command to material icon mapping. + */ + private final Map commandIcon = new TreeMap<>(); + + /** + * Command name mappings. + */ + private final Map commandName = new TreeMap<>(); + + private EntityType getEntityType(String key) { + return Arrays.stream(EntityType.values()).filter(v -> v.name().equalsIgnoreCase(key)).findFirst().orElse(null); + } + + /** + * Constructs a new Settings object and initializes the configurations. + * + * @param addon The UpgradesAddon instance. + */ + public Settings(UpgradesAddon addon) { + this.addon = addon; + this.addon.saveDefaultConfig(); + + this.hasRangeUpgrade = false; + + this.disabledGameModes = new HashSet<>(this.addon.getConfig().getStringList("disabled-gamemodes")); + + this.chatInputEscape = this.addon.getConfig().getString("chat-input-escape", "END"); + + if (this.addon.getConfig().isSet("range-upgrade")) { + ConfigurationSection section = this.addon.getConfig().getConfigurationSection("range-upgrade"); + for (String key : Objects.requireNonNull(section).getKeys(false)) { + UpgradeTier newUpgrade = addUpgradeSection(section, key); + + if (this.maxRangeUpgrade < newUpgrade.getMaxLevel()) + this.maxRangeUpgrade = newUpgrade.getMaxLevel(); + + this.hasRangeUpgrade = true; + this.rangeUpgradeTierMap.put(key, newUpgrade); + } + } + + if (this.addon.getConfig().isSet("block-limits-upgrade")) { + ConfigurationSection section = this.addon.getConfig().getConfigurationSection("block-limits-upgrade"); + this.blockLimitsUpgradeTierMap = this.loadBlockLimits(section, null); + } + + if (this.addon.getConfig().isSet("entity-icon")) { + ConfigurationSection section = this.addon.getConfig().getConfigurationSection("entity-icon"); + for (String entity : Objects.requireNonNull(section).getKeys(false)) { + String material = section.getString(entity); + EntityType ent = this.getEntityType(entity); + Material mat = Material.getMaterial(material); + if (ent == null) + this.addon.logError("Config: EntityType " + entity + " is not valid in icon"); + else if (mat == null) + this.addon.logError("Config: Material " + material + " is not a valid material"); + else + this.entityIcon.put(ent, mat); + } + } + + if (this.addon.getConfig().isSet("entity-group-icon")) { + ConfigurationSection section = this.addon.getConfig().getConfigurationSection("entity-group-icon"); + for (String group : Objects.requireNonNull(section).getKeys(false)) { + String material = section.getString(group); + Material mat = Material.getMaterial(material); + if (mat == null) + this.addon.logError("Config: Material " + material + " is not a valid material"); + else + this.entityGroupIcon.put(group, mat); + } + } + + if (this.addon.getConfig().isSet("entity-limits-upgrade")) { + ConfigurationSection section = this.addon.getConfig().getConfigurationSection("entity-limits-upgrade"); + this.entityLimitsUpgradeTierMap = this.loadEntityLimits(section, null); + } + + if (this.addon.getConfig().isSet("entity-group-limits-upgrade")) { + ConfigurationSection section = this.addon.getConfig() + .getConfigurationSection("entity-group-limits-upgrade"); + this.entityGroupLimitsUpgradeTierMap = this.loadEntityGroupLimits(section, null); + } + + if (this.addon.getConfig().isSet("command-icon")) { + ConfigurationSection section = this.addon.getConfig().getConfigurationSection("command-icon"); + for (String commandId : Objects.requireNonNull(section).getKeys(false)) { + String material = section.getString(commandId); + Material mat = Material.getMaterial(material); + if (mat == null) + this.addon.logError("Config: Material " + material + " is not a valid material"); + else + this.commandIcon.put(commandId, mat); + } + } + + if (this.addon.getConfig().isSet("command-upgrade")) { + ConfigurationSection section = this.addon.getConfig().getConfigurationSection("command-upgrade"); + this.commandUpgradeTierMap = this.loadCommand(section, null); + } + + if (this.addon.getConfig().isSet("gamemodes")) { + ConfigurationSection section = this.addon.getConfig().getConfigurationSection("gamemodes"); + + for (String gameMode : Objects.requireNonNull(section).getKeys(false)) { + ConfigurationSection gameModeSection = section.getConfigurationSection(gameMode); + + if (gameModeSection.isSet("range-upgrade")) { + ConfigurationSection lowSection = gameModeSection.getConfigurationSection("range-upgrade"); + for (String key : Objects.requireNonNull(lowSection).getKeys(false)) { + UpgradeTier newUpgrade = addUpgradeSection(lowSection, key); + + if (this.customMaxRangeUpgrade.get(gameMode) == null + || this.customMaxRangeUpgrade.get(gameMode) < newUpgrade.getMaxLevel()) + this.customMaxRangeUpgrade.put(gameMode, newUpgrade.getMaxLevel()); + + this.hasRangeUpgrade = true; + + this.customRangeUpgradeTierMap.computeIfAbsent(gameMode, k -> new TreeMap<>()).put(key, + newUpgrade); + } + } + + if (gameModeSection.isSet("block-limits-upgrade")) { + ConfigurationSection lowSection = gameModeSection.getConfigurationSection("block-limits-upgrade"); + this.customBlockLimitsUpgradeTierMap.computeIfAbsent(gameMode, + k -> loadBlockLimits(lowSection, gameMode)); + } + + if (gameModeSection.isSet("entity-limits-upgrade")) { + ConfigurationSection lowSection = gameModeSection.getConfigurationSection("entity-limits-upgrade"); + this.customEntityLimitsUpgradeTierMap.computeIfAbsent(gameMode, + k -> loadEntityLimits(lowSection, gameMode)); + } + + if (gameModeSection.isSet("entity-group-limits-upgrade")) { + ConfigurationSection lowSection = gameModeSection + .getConfigurationSection("entity-group-limits-upgrade"); + this.customEntityGroupLimitsUpgradeTierMap.computeIfAbsent(gameMode, + k -> loadEntityGroupLimits(lowSection, gameMode)); + } + + if (gameModeSection.isSet("command-upgrade")) { + ConfigurationSection lowSection = gameModeSection.getConfigurationSection("command-upgrade"); + this.customCommandUpgradeTierMap.computeIfAbsent(gameMode, k -> loadCommand(lowSection, gameMode)); + } + } + } + } + + private Map> loadBlockLimits(ConfigurationSection section, String gameMode) { + Map> mats = new EnumMap<>(Material.class); + for (String material : Objects.requireNonNull(section).getKeys(false)) { + Material mat = Material.getMaterial(material); + if (mat != null && mat.isBlock()) { + Map tier = new TreeMap<>(); + ConfigurationSection matSection = section.getConfigurationSection(material); + for (String key : Objects.requireNonNull(matSection).getKeys(false)) { + UpgradeTier newUpgrade = addUpgradeSection(matSection, key); + + if (gameMode == null) { + if (this.maxBlockLimitsUpgrade.get(mat) == null + || this.maxBlockLimitsUpgrade.get(mat) < newUpgrade.getMaxLevel()) + this.maxBlockLimitsUpgrade.put(mat, newUpgrade.getMaxLevel()); + } else { + if (this.customMaxBlockLimitsUpgrade.get(gameMode) == null) { + Map newMap = new EnumMap<>(Material.class); + newMap.put(mat, newUpgrade.getMaxLevel()); + this.customMaxBlockLimitsUpgrade.put(gameMode, newMap); + } else { + if (this.customMaxBlockLimitsUpgrade.get(gameMode).get(mat) == null + || this.customMaxBlockLimitsUpgrade.get(gameMode).get(mat) < newUpgrade + .getMaxLevel()) + this.customMaxBlockLimitsUpgrade.get(gameMode).put(mat, newUpgrade.getMaxLevel()); + } + } + + tier.put(key, newUpgrade); + } + mats.put(mat, tier); + } else { + this.addon.logError("Material " + material + " is not a valid material. Skipping..."); + } + } + return mats; + } + + private Map> loadEntityLimits(ConfigurationSection section, String gameMode) { + Map> ents = new EnumMap<>(EntityType.class); + for (String entity : Objects.requireNonNull(section).getKeys(false)) { + EntityType ent = this.getEntityType(entity); + if (ent != null && this.entityIcon.containsKey(ent)) { + Map tier = new TreeMap<>(); + ConfigurationSection entSection = section.getConfigurationSection(entity); + for (String key : Objects.requireNonNull(entSection).getKeys(false)) { + UpgradeTier newUpgrade = addUpgradeSection(entSection, key); + + if (gameMode == null) { + if (this.maxEntityLimitsUpgrade.get(ent) == null + || this.maxEntityLimitsUpgrade.get(ent) < newUpgrade.getMaxLevel()) + this.maxEntityLimitsUpgrade.put(ent, newUpgrade.getMaxLevel()); + } else { + if (this.customMaxEntityLimitsUpgrade.get(gameMode) == null) { + Map newMap = new EnumMap<>(EntityType.class); + newMap.put(ent, newUpgrade.getMaxLevel()); + this.customMaxEntityLimitsUpgrade.put(gameMode, newMap); + } else { + if (this.customMaxEntityLimitsUpgrade.get(gameMode).get(ent) == null + || this.customMaxEntityLimitsUpgrade.get(gameMode).get(ent) < newUpgrade + .getMaxLevel()) + this.customMaxEntityLimitsUpgrade.get(gameMode).put(ent, newUpgrade.getMaxLevel()); + } + } + + tier.put(key, newUpgrade); + } + ents.put(ent, tier); + } else { + if (ent != null) + this.addon.logError("Entity " + entity + " is not a valid entity. Skipping..."); + else + this.addon.logError("Entity " + entity + " is missing a corresponding icon. Skipping..."); + } + } + return ents; + } + + private Map> loadEntityGroupLimits(ConfigurationSection section, String gameMode) { + Map> ents = new TreeMap<>(); + for (String entitygroup : Objects.requireNonNull(section).getKeys(false)) { + Map tier = new TreeMap<>(); + ConfigurationSection entSection = section.getConfigurationSection(entitygroup); + for (String key : Objects.requireNonNull(entSection).getKeys(false)) { + UpgradeTier newUpgrade = addUpgradeSection(entSection, key); + + if (gameMode == null) { + if (this.maxEntityGroupLimitsUpgrade.get(entitygroup) == null + || this.maxEntityGroupLimitsUpgrade.get(entitygroup) < newUpgrade.getMaxLevel()) + this.maxEntityGroupLimitsUpgrade.put(entitygroup, newUpgrade.getMaxLevel()); + } else { + if (this.customMaxEntityGroupLimitsUpgrade.get(gameMode) == null) { + Map newMap = new TreeMap<>(); + newMap.put(entitygroup, newUpgrade.getMaxLevel()); + this.customMaxEntityGroupLimitsUpgrade.put(gameMode, newMap); + } else { + if (this.customMaxEntityGroupLimitsUpgrade.get(gameMode).get(entitygroup) == null + || this.customMaxEntityGroupLimitsUpgrade.get(gameMode).get(entitygroup) < newUpgrade + .getMaxLevel()) + this.customMaxEntityGroupLimitsUpgrade.get(gameMode).put(entitygroup, + newUpgrade.getMaxLevel()); + } + } + + tier.put(key, newUpgrade); + } + ents.put(entitygroup, tier); + } + return ents; + } + + private Map> loadCommand(ConfigurationSection section, String gamemode) { + Map> commands = new TreeMap<>(); + + for (String commandId : Objects.requireNonNull(section).getKeys(false)) { + if (this.commandIcon.containsKey(commandId)) { + String name = commandId; + Map tier = new TreeMap<>(); + ConfigurationSection cmdSection = section.getConfigurationSection(commandId); + for (String key : Objects.requireNonNull(cmdSection).getKeys(false)) { + if (key.equals("name")) { + name = cmdSection.getString(key); + } else { + CommandUpgradeTier newUpgrade = addCommandUpgradeSection(cmdSection, key); + + if (gamemode == null) { + if (this.maxCommandUpgrade.get(commandId) == null + || this.maxCommandUpgrade.get(commandId) < newUpgrade.getMaxLevel()) { + this.maxCommandUpgrade.put(commandId, newUpgrade.getMaxLevel()); + } + } else { + if (this.customMaxCommandUpgrade.get(gamemode) == null) { + Map newMap = new TreeMap<>(); + newMap.put(commandId, newUpgrade.getMaxLevel()); + this.customMaxCommandUpgrade.put(gamemode, newMap); + } else { + if (this.customMaxCommandUpgrade.get(gamemode).get(commandId) == null + || this.customMaxCommandUpgrade.get(gamemode).get(commandId) < newUpgrade + .getMaxLevel()) + this.customMaxCommandUpgrade.get(gamemode).put(commandId, newUpgrade.getMaxLevel()); + } + } + + tier.put(key, newUpgrade); + } + } + if (!this.commandName.containsKey(commandId) || !name.equals(commandId)) + this.commandName.put(commandId, name); + commands.put(commandId, tier); + } else { + this.addon.logError("Command " + commandId + " is missing a corresponding icon. Skipping..."); + } + } + + return commands; + } + + @NonNull + private UpgradeTier addUpgradeSection(ConfigurationSection section, String key) { + ConfigurationSection tierSection = section.getConfigurationSection(key); + UpgradeTier upgradeTier = new UpgradeTier(key); + upgradeTier.setTierName(tierSection.getName()); + upgradeTier.setMaxLevel(tierSection.getInt("max-level")); + upgradeTier.setUpgrade(parse(tierSection.getString("upgrade"), upgradeTier.getExpressionVariable())); + + if (tierSection.isSet("island-min-level")) + upgradeTier.setIslandMinLevel( + parse(tierSection.getString("island-min-level"), upgradeTier.getExpressionVariable())); + else + upgradeTier.setIslandMinLevel(parse("0", upgradeTier.getExpressionVariable())); + + if (tierSection.isSet("vault-cost")) + upgradeTier.setVaultCost(parse(tierSection.getString("vault-cost"), upgradeTier.getExpressionVariable())); + else + upgradeTier.setVaultCost(parse("0", upgradeTier.getExpressionVariable())); + + if (tierSection.isSet("permission-level")) + upgradeTier.setPermissionLevel(tierSection.getInt("permission-level")); + else + upgradeTier.setPermissionLevel(0); + + return upgradeTier; + + } + + @NonNull + private CommandUpgradeTier addCommandUpgradeSection(ConfigurationSection section, String key) { + ConfigurationSection tierSection = section.getConfigurationSection(key); + CommandUpgradeTier upgradeTier = new CommandUpgradeTier(key); + upgradeTier.setTierName(tierSection.getName()); + upgradeTier.setMaxLevel(tierSection.getInt("max-level")); + upgradeTier.setUpgrade(parse("0", upgradeTier.getExpressionVariable())); + + if (tierSection.isSet("island-min-level")) + upgradeTier.setIslandMinLevel( + parse(tierSection.getString("island-min-level"), upgradeTier.getExpressionVariable())); + else + upgradeTier.setIslandMinLevel(parse("0", upgradeTier.getExpressionVariable())); + + if (tierSection.isSet("vault-cost")) + upgradeTier.setVaultCost(parse(tierSection.getString("vault-cost"), upgradeTier.getExpressionVariable())); + else + upgradeTier.setVaultCost(parse("0", upgradeTier.getExpressionVariable())); + + if (tierSection.isSet("permission-level")) + upgradeTier.setPermissionLevel(tierSection.getInt("permission-level")); + else + upgradeTier.setPermissionLevel(0); + + if (tierSection.isSet("console") && tierSection.isBoolean("console")) + upgradeTier.setConsole(tierSection.getBoolean("console")); + else + upgradeTier.setConsole(false); + + if (tierSection.isSet("command")) + upgradeTier.setCommandList(tierSection.getStringList("command")); + + return upgradeTier; + + } + + /** + * Retrieves the disabled game modes. + * + * @return A set of disabled game modes. + */ + public Set getDisabledGameModes() { + return disabledGameModes; + } + + public String getChatInputEscape() { + return this.chatInputEscape; + } + + /** + * Checks if the range upgrade is enabled. + * + * @return True if range upgrade is enabled, otherwise false. + */ + public boolean getHasRangeUpgrade() { + return hasRangeUpgrade; + } + + /** + * Gets the maximum range upgrade for a specific addon. + * + * @param addon The name of the addon. + * @return The maximum range upgrade value. + */ + public int getMaxRangeUpgrade(String addon) { + return customMaxRangeUpgrade.getOrDefault(addon, maxRangeUpgrade); + } + + /** + * Retrieves the default range upgrade tier map. + * + * @return A map of range upgrade tiers by their identifiers. + */ + public Map getDefaultRangeUpgradeTierMap() { + return rangeUpgradeTierMap; + } + + /** + * Retrieves the range upgrade tier map for a specific addon. + * + * @param addon The name of the addon. + * @return A map of range upgrade tiers specific to the addon. + */ + public Map getAddonRangeUpgradeTierMap(String addon) { + return customRangeUpgradeTierMap.getOrDefault(addon, Collections.emptyMap()); + } + + /** + * Gets the maximum block limits upgrade for a material and addon. + * + * @param mat The material type. + * @param addon The name of the addon. + * @return The maximum block limit for the material. + */ + public int getMaxBlockLimitsUpgrade(Material mat, String addon) { + return customMaxBlockLimitsUpgrade.getOrDefault(addon, maxBlockLimitsUpgrade).getOrDefault(mat, 0); + } + + /** + * Retrieves the default block limits upgrade tier map. + * + * @return A map of block limits upgrade tiers by material. + */ + public Map> getDefaultBlockLimitsUpgradeTierMap() { + return blockLimitsUpgradeTierMap; + } + + /** + * Retrieves the block limits upgrade tier map for a specific addon. + * + * @param addon The name of the addon. + * @return A map of block limits upgrade tiers specific to the addon. + */ + public Map> getAddonBlockLimitsUpgradeTierMap(String addon) { + return customBlockLimitsUpgradeTierMap.getOrDefault(addon, Collections.emptyMap()); + } + + /** + * Retrieves all materials with block limits upgrades. + * + * @return A set of materials with block limits upgrades. + */ + public Set getMaterialsLimitsUpgrade() { + Set materials = new HashSet<>(); + + customBlockLimitsUpgradeTierMap.forEach((addon, addonUpgrade) -> materials.addAll(addonUpgrade.keySet())); + materials.addAll(blockLimitsUpgradeTierMap.keySet()); + + return materials; + } + + /** + * Retrieves the material icon for a specific entity type. + * + * @param entity The entity type. + * @return The material icon associated with the entity type, or null if not defined. + */ + public Material getEntityIcon(EntityType entity) { + return entityIcon.getOrDefault(entity, null); + } + + /** + * Retrieves the material icon for a specific entity group. + * + * @param group The entity group. + * @return The material icon associated with the entity group, or null if not defined. + */ + public Material getEntityGroupIcon(String group) { + return entityGroupIcon.getOrDefault(group, null); + } + + /** + * Gets the maximum entity limits upgrade for a specific entity type and addon. + * + * @param entity The entity type. + * @param addon The name of the addon. + * @return The maximum entity limit for the entity type. + */ + public int getMaxEntityLimitsUpgrade(EntityType entity, String addon) { + return customMaxEntityLimitsUpgrade.getOrDefault(addon, maxEntityLimitsUpgrade).getOrDefault(entity, 0); + } + + /** + * Gets the maximum entity group limits upgrade for a specific group and addon. + * + * @param group The entity group. + * @param addon The name of the addon. + * @return The maximum entity group limit for the group. + */ + public int getMaxEntityGroupLimitsUpgrade(String group, String addon) { + return customMaxEntityGroupLimitsUpgrade.getOrDefault(addon, maxEntityGroupLimitsUpgrade).getOrDefault(group, + 0); + } + + /** + * Retrieves the default entity limits upgrade tier map. + * + * @return A map of entity limits upgrade tiers by entity type. + */ + public Map> getDefaultEntityLimitsUpgradeTierMap() { + return entityLimitsUpgradeTierMap; + } + + /** + * Retrieves the default entity group limits upgrade tier map. + * + * @return A map of entity group limits upgrade tiers. + */ + public Map> getDefaultEntityGroupLimitsUpgradeTierMap() { + return entityGroupLimitsUpgradeTierMap; + } + + /** + * Retrieves the entity limits upgrade tier map for a specific addon. + * + * @param addon The name of the addon. + * @return A map of entity limits upgrade tiers specific to the addon. + */ + public Map> getAddonEntityLimitsUpgradeTierMap(String addon) { + return customEntityLimitsUpgradeTierMap.getOrDefault(addon, Collections.emptyMap()); + } + + /** + * Retrieves the entity group limits upgrade tier map for a specific addon. + * + * @param addon The name of the addon. + * @return A map of entity group limits upgrade tiers specific to the addon. + */ + public Map> getAddonEntityGroupLimitsUpgradeTierMap(String addon) { + return customEntityGroupLimitsUpgradeTierMap.getOrDefault(addon, Collections.emptyMap()); + } + + /** + * Retrieves all entity types with limits upgrades. + * + * @return A set of entity types with limits upgrades. + */ + public Set getEntityLimitsUpgrade() { + Set entity = new HashSet<>(); + + customEntityLimitsUpgradeTierMap.forEach((addon, addonUpgrade) -> entity.addAll(addonUpgrade.keySet())); + entity.addAll(entityLimitsUpgradeTierMap.keySet()); + + return entity; + } + + /** + * Retrieves all entity groups with limits upgrades. + * + * @return A set of entity groups with limits upgrades. + */ + public Set getEntityGroupLimitsUpgrade() { + Set groups = new HashSet<>(); + + customEntityGroupLimitsUpgradeTierMap.forEach((addon, addonUpgrade) -> groups.addAll(addonUpgrade.keySet())); + groups.addAll(entityGroupLimitsUpgradeTierMap.keySet()); + + return groups; + } + + /** + * Retrieves the maximum command upgrade limit for a specific command and addon. + * + * @param commandUpgrade The command identifier. + * @param addon The name of the addon. + * @return The maximum upgrade limit for the command. + */ + public int getMaxCommandUpgrade(String commandUpgrade, String addon) { + if (customMaxCommandUpgrade.containsKey(addon)) { + if (customMaxCommandUpgrade.get(addon).containsKey(commandUpgrade)) { + return customMaxCommandUpgrade.get(addon).get(commandUpgrade); + } + } + return maxCommandUpgrade.getOrDefault(commandUpgrade, 0); + } + + /** + * Retrieves the default command upgrade tier map. + * + * @return A map of command upgrade tiers by command identifier. + */ + public Map> getDefaultCommandUpgradeTierMap() { + return commandUpgradeTierMap; + } + + /** + * Retrieves the command upgrade tier map for a specific addon. + * + * @param addon The name of the addon. + * @return A map of command upgrade tiers specific to the addon. + */ + public Map> getAddonCommandUpgradeTierMap(String addon) { + return customCommandUpgradeTierMap.getOrDefault(addon, Collections.emptyMap()); + } + + /** + * Retrieves all commands with upgrades. + * + * @return A set of command identifiers with upgrades. + */ + public Set getCommandUpgrade() { + Set command = new HashSet<>(); + + customCommandUpgradeTierMap.forEach((addon, addonUpgrade) -> command.addAll(addonUpgrade.keySet())); + command.addAll(commandUpgradeTierMap.keySet()); + + return command; + } + + /** + * Retrieves the material icon for a specific command. + * + * @param command The command identifier. + * @return The material icon associated with the command, or null if not defined. + */ + public Material getCommandIcon(String command) { + return commandIcon.getOrDefault(command, null); + } + + /** + * Retrieves the display name for a specific command. + * + * @param command The command identifier. + * @return The display name of the command. + */ + public String getCommandName(String command) { + return commandName.get(command); + } + + /** + * Represents an upgrade tier for a specific feature. + */ + public class UpgradeTier { + /** + * The unique identifier for the upgrade tier. + */ + private final String id; + + /** + * The maximum level of the upgrade tier. + */ + private int maxLevel = -1; + + /** + * The name of the upgrade tier. + */ + private String tierName; + + /** + * The permission level required for the upgrade. + */ + private Integer permissionLevel = 0; + + /** + * The expression defining the upgrade behavior. + */ + private Expression upgrade; + + /** + * Minimum island level required for the upgrade. + */ + private Expression islandMinLevel; + + /** + * Vault cost associated with the upgrade. + */ + private Expression vaultCost; + + /** + * Variables used in expressions for calculations. + */ + private final Map expressionVariables; + + /** + * Creates a new UpgradeTier instance. + * + * @param id The unique identifier for the upgrade tier. + */ + public UpgradeTier(String id) { + this.id = id; + this.expressionVariables = new TreeMap<>(); + this.expressionVariables.put("[level]", 0.0); + this.expressionVariables.put("[islandLevel]", 0.0); + this.expressionVariables.put("[numberPlayer]", 0.0); + } + + /** + * Retrieves the ID of the upgrade tier. + * + * @return The ID of the tier. + */ + public String getId() { + return id; + } + + /** + * Retrieves the name of the upgrade tier. + * + * @return The name of the upgrade tier. + */ + public String getTierName() { + return tierName; + } + + /** + * Sets the name of the upgrade tier. + * + * @param tierName The name to set for + */ + public void setTierName(String tierName) { + this.tierName = tierName; + } + + /** + * Retrieves the maximum level of the upgrade tier. + * + * @return The maximum level. + */ + public int getMaxLevel() { + return maxLevel; + } + + /** + * Sets the maximum level of the upgrade tier. + * + * @param maxLevel The maximum level to set. + */ + public void setMaxLevel(int maxLevel) { + this.maxLevel = maxLevel; + } + + /** + * Retrieves the permission level required for the upgrade tier. + * + * @return The permission level. + */ + public Integer getPermissionLevel() { + return permissionLevel; + } + + /** + * Sets the permission level required for the upgrade tier. + * + * @param permissionLevel The permission level to set. + */ + public void setPermissionLevel(Integer permissionLevel) { + this.permissionLevel = permissionLevel; + } + + /** + * Retrieves the upgrade expression. + * + * @return The upgrade expression. + */ + public Expression getUpgrade() { + return upgrade; + } + + /** + * Sets the upgrade expression. + * + * @param upgrade The upgrade expression to set. + */ + public void setUpgrade(Expression upgrade) { + this.upgrade = upgrade; + } + + /** + * Retrieves the minimum island level required for the upgrade. + * + * @return The island minimum level expression. + */ + public Expression getIslandMinLevel() { + return islandMinLevel; + } + + /** + * Sets the minimum island level required for the upgrade. + * + * @param islandMinLevel The island minimum level expression to set. + */ + public void setIslandMinLevel(Expression islandMinLevel) { + this.islandMinLevel = islandMinLevel; + } + + /** + * Retrieves the vault cost associated with the upgrade. + * + * @return The vault cost expression. + */ + public Expression getVaultCost() { + return vaultCost; + } + + /** + * Sets the vault cost associated with the upgrade. + * + * @param vaultCost The vault cost expression to set. + */ + public void setVaultCost(Expression vaultCost) { + this.vaultCost = vaultCost; + } + + /** + * Updates a variable used in the upgrade expression calculations. + * + * @param key The variable name. + * @param value The value to set for the variable. + */ + public void updateExpressionVariable(String key, double value) { + this.expressionVariables.put(key, value); + } + + /** + * Retrieves all variables used in the upgrade expression calculations. + * + * @return A map of variable names to their values. + */ + public Map getExpressionVariable() { + return expressionVariables; + } + + /** + * Calculates the upgrade value based on the provided parameters. + * + * @param level The current level. + * @param islandLevel The island level. + * @param numberPeople The number of players. + * @return The calculated upgrade value. + */ + public double calculateUpgrade(double level, double islandLevel, double numberPeople) { + this.updateExpressionVariable("[level]", level); + this.updateExpressionVariable("[islandLevel]", islandLevel); + this.updateExpressionVariable("[numberPlayer]", numberPeople); + return this.getUpgrade().eval(); + } + + /** + * Calculates the minimum island level required based on the provided parameters. + * + * @param level The current level. + * @param islandLevel The island level. + * @param numberPeople The number of players. + * @return The calculated minimum island level. + */ + public double calculateIslandMinLevel(double level, double islandLevel, double numberPeople) { + this.updateExpressionVariable("[level]", level); + this.updateExpressionVariable("[islandLevel]", islandLevel); + this.updateExpressionVariable("[numberPlayer]", numberPeople); + return this.getIslandMinLevel().eval(); + } + + /** + * Calculates the vault cost based on the provided parameters. + * + * @param level The current level. + * @param islandLevel The island level. + * @param numberPeople The number of players. + * @return The calculated vault cost. + */ + public double calculateVaultCost(double level, double islandLevel, double numberPeople) { + this.updateExpressionVariable("[level]", level); + this.updateExpressionVariable("[islandLevel]", islandLevel); + this.updateExpressionVariable("[numberPlayer]", numberPeople); + return this.getVaultCost().eval(); + } + } + + /** + * Represents a command upgrade tier with additional properties. + */ + public class CommandUpgradeTier extends UpgradeTier { + /** + * List of commands associated with this upgrade tier. + */ + private List commandList; + + /** + * Indicates whether the commands should run on the console. + */ + private Boolean console; + + /** + * Creates a new CommandUpgradeTier instance. + * + * @param id The unique identifier for the upgrade tier. + */ + public CommandUpgradeTier(String id) { + super(id); + this.commandList = new ArrayList<>(); + } + + /** + * Sets whether the commands should run on the console. + * + * @param console True to run commands on the console, false otherwise. + */ + public void setConsole(Boolean console) { + this.console = console; + } + + /** + * Checks whether the commands should run on the console. + * + * @return True if commands run on the console, false otherwise. + */ + public Boolean getConsole() { + return console; + } + + /** + * Sets the list of commands for this upgrade tier. + * + * @param commandsList The list of commands to set. + */ + public void setCommandList(List commandsList) { + this.commandList = commandsList; + } + + /** + * Retrieves the formatted list of commands for execution. + * + * @param playerName The name of the player executing the commands. + * @param island The island associated with the commands. + * @param level The current level of the upgrade. + * @return A list of formatted commands ready for execution. + */ + public List getCommandList(String playerName, Island island, int level) { + List formattedList = new ArrayList<>(this.commandList.size()); + String owner = island.getPlugin().getPlayers().getName(island.getOwner()); + + this.commandList.forEach(cmd -> { + String formattedCmd = cmd.replace("[player]", playerName).replace("[level]", Integer.toString(level)) + .replace("[owner]", owner); + formattedList.add(formattedCmd); + }); + return formattedList; + } + } + + + + // ------------------------------------------------------------------------- + // Section: Arithmetic expressions Parser + // Thanks to Boann on StackOverflow + // Link: + // https://stackoverflow.com/questions/3422673/how-to-evaluate-a-math-expression-given-in-string-form + // ------------------------------------------------------------------------- + + @FunctionalInterface + interface Expression { + double eval(); + } + + private static final List funct = List.of("sqrt", "sin", "cos", "tan"); + + public static Expression parse(final String str, Map variables) { + return new Object() { + int pos = -1, ch; + + void nextChar() { + ch = (++pos < str.length()) ? str.charAt(pos) : -1; + } + + boolean eat(int charToEat) { + while (ch == ' ') + nextChar(); + if (ch == charToEat) { + nextChar(); + return true; + } + return false; + } + + Expression parse() { + nextChar(); + Expression x = parseExpression(); + if (pos < str.length()) + throw new RuntimeException("Unexpected: " + (char) ch); + return x; + } + + // Grammar: + // expression = term | expression `+` term | expression `-` term + // term = factor | term `*` factor | term `/` factor + // factor = `+` factor | `-` factor | `(` expression `)` + // | number | functionName factor | factor `^` factor + + Expression parseExpression() { + Expression x = parseTerm(); + for (;;) { + if (eat('+')) { + Expression a = x, b = parseTerm(); + x = (() -> a.eval() + b.eval()); + } else if (eat('-')) { + Expression a = x, b = parseTerm(); + x = (() -> a.eval() - b.eval()); + } else + return x; + } + } + + Expression parseTerm() { + Expression x = parseFactor(); + for (;;) { + if (eat('*')) { + Expression a = x, b = parseFactor(); + x = (() -> a.eval() * b.eval()); + } else if (eat('/')) { + Expression a = x, b = parseFactor(); + x = (() -> a.eval() / b.eval()); + } else + return x; + } + } + + Expression parseFactor() { + if (eat('+')) + return parseFactor(); // unary plus + if (eat('-')) { + return (() -> -parseFactor().eval()); // unary minus + } + + Expression x; + int startPos = this.pos; + if (eat('(')) { // parentheses + x = parseExpression(); + eat(')'); + } else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers + while ((ch >= '0' && ch <= '9') || ch == '.') + nextChar(); + final int innerPos = this.pos; + x = (() -> Double.parseDouble(str.substring(startPos, innerPos))); + } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '[' || ch == ']') { // functions + while ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '[' || ch == ']') + nextChar(); + String func = str.substring(startPos, this.pos); + if (funct.contains(func)) { + Expression a = parseFactor(); + x = switch (func) { + case "sqrt" -> (() -> Math.sqrt(a.eval())); + case "sin" -> (() -> Math.sin(Math.toRadians(a.eval()))); + case "cos" -> (() -> Math.cos(Math.toRadians(a.eval()))); + case "tan" -> (() -> Math.tan(Math.toRadians(a.eval()))); + default -> throw new RuntimeException("Unknown function: " + func); + }; + } else { + x = (() -> variables.get(func)); + } + } else { + throw new RuntimeException("Unexpected: " + (char) ch); + } + + if (eat('^')) { + Expression a = x, b = parseFactor(); + x = (() -> Math.pow(a.eval(), b.eval())); // exponentiation + } + + return x; + } + }.parse(); + } + + /** + * Evaluate a formula string with the given variables and return the result. + * + * @param equation The formula string (e.g. "100*[level]") + * @param variables Variable bindings (e.g. "[level]" -> 5.0) + * @return The evaluated result as a double + */ + public static double evaluate(String equation, Map variables) { + return parse(equation, variables).eval(); + } + +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/UpgradeData.java b/src/main/java/world/bentobox/upgrades/dataobjects/UpgradeData.java new file mode 100644 index 0000000..d91fd9a --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/UpgradeData.java @@ -0,0 +1,200 @@ +package world.bentobox.upgrades.dataobjects; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import com.google.gson.annotations.Expose; + +import world.bentobox.bentobox.database.objects.DataObject; +import world.bentobox.bentobox.database.objects.Table; + +/** + * This class represent a DataObject that store registered upgrades + * + * @author Ikkino + * + */ +@Table(name = "UpgradeData") +public class UpgradeData implements DataObject { + + // ------------------------------------------------------------ + // Section: Variables + // ------------------------------------------------------------ + + /** + * Unique id for each upgrades + */ + @Expose + private String uniqueId; + + /** + * gamemode in wich this upgrade is used + */ + @Expose + private String world; + + /** + * Readable name for this upgrade + * If not present, uniqueId will be used + * Can contain color Codes + */ + @Expose + private String name = ""; + + /** + * Description of this upgrade + * If not present, default description will be used + * Can contain color Codes + */ + @Expose + private List description = new ArrayList(); + + /** + * Icon representing this upgrade + * Will be used in interfaces + * Default is chest + */ + @Expose + private ItemStack icon = new ItemStack(Material.CHEST); + + /** + * Order of upgrades in the interfaces + * From lowest to highest + * If tie, then sort by character + * If < 0 then placed after the others + * Default to -1 + */ + @Expose + private int order = -1; + + /** + * If this upgrade is shown to the user + * Default to true so newly created upgrades are immediately active + */ + @Expose + private boolean active = true; + + // ------------------------------------------------------------ + // Section: Getters + // ------------------------------------------------------------ + + /** + * @return the uniqueId + */ + @Override + public String getUniqueId() { + return uniqueId; + } + + /** + * @return the world + */ + public String getWorld() { + return world; + } + + /** + * @return the name or uniqueId if empty + */ + public String getName() { + return name.isEmpty() ? uniqueId : name; + } + + /** + * @return the description + */ + public List getDescription() { + return description; + } + + /** + * @return clone if the icon or default chest icon if empty + */ + public ItemStack getIcon() { + return icon != null ? icon.clone() : new ItemStack(Material.CHEST); + } + + /** + * @return the order + */ + public int getOrder() { + return order; + } + + /** + * @return the active + */ + public boolean isActive() { + return active; + } + + // ------------------------------------------------------------ + // Section: Setters + // ------------------------------------------------------------ + + /** + * @param uniqueId the uniqueId to set + */ + @Override + public void setUniqueId(String uniqueId) { + this.uniqueId = uniqueId; + } + + /** + * @param world the world to set + */ + public void setWorld(String world) { + this.world = world; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @param description the description to set + */ + public void setDescription(List description) { + this.description = description; + } + + /** + * @param icon the icon to set + */ + public void setIcon(ItemStack icon) { + this.icon = icon; + } + + /** + * @param order the order to set + */ + public void setOrder(int order) { + this.order = order; + } + + /** + * @param active the active to set + */ + public void setActive(boolean active) { + this.active = active; + } + + // ------------------------------------------------------------ + // Section: Utils Methods + // ------------------------------------------------------------ + + public boolean isValid() { + return this.uniqueId != null && + this.description != null && + this.icon != null && + this.name != null && + this.world != null; + } + +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/UpgradeTier.java b/src/main/java/world/bentobox/upgrades/dataobjects/UpgradeTier.java new file mode 100644 index 0000000..0f66594 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/UpgradeTier.java @@ -0,0 +1,234 @@ +package world.bentobox.upgrades.dataobjects; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.annotations.JsonAdapter; +import org.bukkit.inventory.ItemStack; + +import com.google.gson.annotations.Expose; + +import world.bentobox.bentobox.database.objects.DataObject; +import world.bentobox.bentobox.database.objects.Table; +import world.bentobox.upgrades.dataobjects.adapter.PriceDBAdapter; +import world.bentobox.upgrades.dataobjects.adapter.RewardDBAdapter; +import world.bentobox.upgrades.dataobjects.prices.PriceDB; +import world.bentobox.upgrades.dataobjects.rewards.RewardDB; + +@Table(name = "UpgradeTier") +public class UpgradeTier implements DataObject { + + // ------------------------------------------------------------ + // Section: Variables + // ------------------------------------------------------------ + + /** + * Unique id for each upgrade tiers + */ + @Expose + private String uniqueId; + + /** + * Unique id of the parent upgrade + */ + @Expose + private String upgrade; + + /** + * Readable name for this tier + * If not present, uniqueId will be used + * Can contain color codes + */ + @Expose + private String name = ""; + + /** + * Description of this tier + * If not present, no description will be used + * Can contain color codes + */ + @Expose + private List description = new ArrayList<>(); + + /** + * Icon representing this tier + * Will be used in interfaces + * If not present, it will use it's parent icon + */ + @Expose + private ItemStack icon; + + /** + * Level of the upgrade at which this tier start + */ + @Expose + private int startLevel; + + /** + * Level of the upgrade at which this tier end + */ + @Expose + private int endLevel; + + /** + * List of prices needed for each level of this tier + */ + @Expose + @JsonAdapter(PriceDBAdapter.class) + private List prices = new ArrayList<>(); + + /** + * List of rewards given at each level of this tier + */ + @Expose + @JsonAdapter(RewardDBAdapter.class) + private List rewards = new ArrayList<>(); + + // ------------------------------------------------------------ + // Section: Getters + // ------------------------------------------------------------ + + /** + * @return the uniqueId + */ + public String getUniqueId() { + return uniqueId; + } + + /** + * @return the upgrade + */ + public String getUpgrade() { + return upgrade; + } + + /** + * @return the name + */ + public String getName() { + return name.isEmpty() ? uniqueId : name; + } + + /** + * @return the description + */ + public List getDescription() { + return description; + } + + /** + * @return the icon + */ + public ItemStack getIcon() { + return icon; + } + + /** + * @return the startLevel + */ + public int getStartLevel() { + return startLevel; + } + + /** + * @return the endLevel + */ + public int getEndLevel() { + return endLevel; + } + + /** + * @return the prices + */ + public List getPrices() { + return prices; + } + + /** + * @return the rewards + */ + public List getRewards() { + return rewards; + } + + // ------------------------------------------------------------ + // Section: Setters + // ------------------------------------------------------------ + + /** + * @param uniqueId the uniqueId to set + */ + public void setUniqueId(String uniqueId) { + this.uniqueId = uniqueId; + } + + /** + * @param upgrade the upgrade to set + */ + public void setUpgrade(String upgrade) { + this.upgrade = upgrade; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @param description the description to set + */ + public void setDescription(List description) { + this.description = description; + } + + /** + * @param icon the icon to set + */ + public void setIcon(ItemStack icon) { + this.icon = icon; + } + + /** + * @param startLevel the startLevel to set + */ + public void setStartLevel(int startLevel) { + this.startLevel = startLevel; + } + + /** + * @param endLevel the endLevel to set + */ + public void setEndLevel(int endLevel) { + this.endLevel = endLevel; + } + + /** + * @param prices the prices to set + */ + public void setPrices(List prices) { + this.prices = prices; + } + + /** + * @param rewards the rewards to set + */ + public void setRewards(List rewards) { + this.rewards = rewards; + } + + // ------------------------------------------------------------ + // Section: Utils Methods + // ------------------------------------------------------------ + + public boolean isValid() { + return this.uniqueId != null && + this.upgrade != null && + this.name != null && + this.description != null && + this.prices != null && + this.rewards != null; + } + +} diff --git a/src/main/java/world/bentobox/islandupgrades/IslandUpgradesData.java b/src/main/java/world/bentobox/upgrades/dataobjects/UpgradesData.java similarity index 50% rename from src/main/java/world/bentobox/islandupgrades/IslandUpgradesData.java rename to src/main/java/world/bentobox/upgrades/dataobjects/UpgradesData.java index c91bbe0..ca0062e 100644 --- a/src/main/java/world/bentobox/islandupgrades/IslandUpgradesData.java +++ b/src/main/java/world/bentobox/upgrades/dataobjects/UpgradesData.java @@ -1,48 +1,53 @@ -package world.bentobox.islandupgrades; - -import java.util.HashMap; -import java.util.Map; - -import com.google.gson.annotations.Expose; - -import world.bentobox.bentobox.database.objects.DataObject; - -public class IslandUpgradesData implements DataObject { - - @Expose - private String uniqueId; - - @Expose - private Map upgradesLevels; - - public IslandUpgradesData() {} - - public IslandUpgradesData(String uniqueId, Map upgradesLevel) { - this.uniqueId = uniqueId; - this.upgradesLevels = upgradesLevel; - } - - public IslandUpgradesData(String uniqueId) { - this(uniqueId, new HashMap<>()); - } - - @Override - public String getUniqueId() { - return uniqueId; - } - - @Override - public void setUniqueId(String uniqueId) { - this.uniqueId = uniqueId; - } - - public long getUpgradeLevel(String name) { - this.upgradesLevels.putIfAbsent(name, (long) 0); - return this.upgradesLevels.get(name); - } - - public void setUpgradeLevel(String name, long value) { - this.upgradesLevels.put(name, value); - } - -} +package world.bentobox.upgrades.dataobjects; + +import java.util.HashMap; +import java.util.Map; + +import com.google.gson.annotations.Expose; + +import world.bentobox.bentobox.database.objects.DataObject; +import world.bentobox.bentobox.database.objects.Table; + +/** + * Database object for storing upgrades data + */ +@Table(name = "UpgradesData") +public class UpgradesData implements DataObject { + + @Expose + private String uniqueId; + + @Expose + private Map upgradesLevels; + + public UpgradesData() {} + + public UpgradesData(String uniqueId, Map upgradesLevel) { + this.uniqueId = uniqueId; + this.upgradesLevels = upgradesLevel; + } + + public UpgradesData(String uniqueId) { + this(uniqueId, new HashMap<>()); + } + + @Override + public String getUniqueId() { + return uniqueId; + } + + @Override + public void setUniqueId(String uniqueId) { + this.uniqueId = uniqueId; + } + + public int getUpgradeLevel(String name) { + this.upgradesLevels.putIfAbsent(name, 0); + return this.upgradesLevels.get(name); + } + + public void setUpgradeLevel(String name, int value) { + this.upgradesLevels.put(name, value); + } + +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/adapter/PriceDBAdapter.java b/src/main/java/world/bentobox/upgrades/dataobjects/adapter/PriceDBAdapter.java new file mode 100644 index 0000000..3dca1f0 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/adapter/PriceDBAdapter.java @@ -0,0 +1,52 @@ +package world.bentobox.upgrades.dataobjects.adapter; + +import com.google.gson.*; +import world.bentobox.upgrades.dataobjects.prices.PriceDB; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +public class PriceDBAdapter implements JsonSerializer>, JsonDeserializer> { + + private static final String PACKAGE = "world.bentobox.upgrades.dataobjects.prices."; + + @Override + public List deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + List list = new ArrayList<>(); + JsonArray jsonArray = json.getAsJsonArray(); + + jsonArray.forEach((entry) -> { + JsonObject obj = entry.getAsJsonObject(); + String type = obj.get("class") + .getAsString(); + JsonElement parameters = obj.get("parameters"); + + try { + list.add(context.deserialize(parameters, Class.forName(PACKAGE + type))); + } catch (ClassNotFoundException e) { + throw new JsonParseException("Unknown element type: " + type, e); + } + }); + + return list; + } + + @Override + public JsonElement serialize(List src, Type typeOfSrc, JsonSerializationContext context) { + JsonArray jsonArray = new JsonArray(); + + src.forEach((obj) -> { + JsonObject result = new JsonObject(); + + result.add("class", new JsonPrimitive(obj.getClass() + .getSimpleName())); + result.add("parameters", context.serialize(obj, obj.getClass())); + + jsonArray.add(result); + }); + + return jsonArray; + } +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/adapter/RewardDBAdapter.java b/src/main/java/world/bentobox/upgrades/dataobjects/adapter/RewardDBAdapter.java new file mode 100644 index 0000000..156e43e --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/adapter/RewardDBAdapter.java @@ -0,0 +1,53 @@ +package world.bentobox.upgrades.dataobjects.adapter; + +import com.google.gson.*; +import world.bentobox.upgrades.dataobjects.rewards.RewardDB; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +public class RewardDBAdapter + implements JsonSerializer>, JsonDeserializer> { + + private static final String PACKAGE = "world.bentobox.upgrades.dataobjects.rewards."; + + @Override + public List deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + List list = new ArrayList<>(); + JsonArray jsonArray = json.getAsJsonArray(); + + jsonArray.forEach((entry) -> { + JsonObject obj = entry.getAsJsonObject(); + String type = obj.get("class") + .getAsString(); + JsonElement parameters = obj.get("parameters"); + + try { + list.add(context.deserialize(parameters, Class.forName(PACKAGE + type))); + } catch (ClassNotFoundException e) { + throw new JsonParseException("Unknown element type: " + type, e); + } + }); + + return list; + } + + @Override + public JsonElement serialize(List src, Type typeOfSrc, JsonSerializationContext context) { + JsonArray jsonArray = new JsonArray(); + + src.forEach((obj) -> { + JsonObject result = new JsonObject(); + + result.add("class", new JsonPrimitive(obj.getClass() + .getSimpleName())); + result.add("parameters", context.serialize(obj, obj.getClass())); + + jsonArray.add(result); + }); + + return jsonArray; + } +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/prices/IslandLevelPrice.java b/src/main/java/world/bentobox/upgrades/dataobjects/prices/IslandLevelPrice.java new file mode 100644 index 0000000..161eb47 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/prices/IslandLevelPrice.java @@ -0,0 +1,158 @@ +package world.bentobox.upgrades.dataobjects.prices; + +import org.bukkit.Material; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.ui.utils.AbPanel; + +import java.security.InvalidParameterException; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Consumer; + +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.config.Settings; + +public class IslandLevelPrice extends Price { + + public IslandLevelPrice() { + super("island_level_price", Material.EXPERIENCE_BOTTLE); + } + + @Override + public String getPublicName(User user) { + return user.getTranslation("upgrades.prices.islandlevel.name"); + } + + @Override + public String getAdminName(User user) { + return user.getTranslation("upgrades.prices.islandlevel.name"); + } + + @Override + public String getPublicDescription(User user) { + return user.getTranslation("upgrades.prices.islandlevel.description", LEVEL_VAR, "?"); + } + + @Override + public String getPublicDescription(User user, PriceDB priceDB) { + IslandLevelPriceDB db = (IslandLevelPriceDB) priceDB; + return user.getTranslation("upgrades.prices.islandlevel.description", + LEVEL_VAR, db.getLevelNeededEquation()); + } + + @Override + public String getAdminDescription(User user) { + return user.getTranslation("upgrades.prices.islandlevel.admindescription"); + } + + @Override + public boolean canPay(UpgradesAddon addon, User user, Island island, PriceDB priceDB, int currentLevel) { + IslandLevelPriceDB db = (IslandLevelPriceDB) priceDB; + Map variables = new TreeMap<>(); + variables.put(LEVEL_VAR, (double) currentLevel); + variables.put(ISLAND_LEVEL_VAR, (double) addon.getUpgradesManager().getIslandLevel(island)); + variables.put(NUMBER_PLAYER_VAR, (double) island.getMemberSet().size()); + int required = (int) Settings.evaluate(db.getLevelNeededEquation(), variables); + return addon.getUpgradesManager().getIslandLevel(island) >= required; + } + + @Override + public void pay(UpgradesAddon addon, User user, Island island, PriceDB priceDB, int currentLevel) { + // Island level is a gate, not consumed + } + + @Override + public AbPanel getAdminPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, AbPanel parent, + UpgradeTier tier, @Nullable PriceDB saved) { + IslandLevelPriceDB dbObject; + + if (saved == null) { + dbObject = new IslandLevelPriceDB(); + List prices = tier.getPrices(); + + prices.add(dbObject); + tier.setPrices(prices); + } else if (saved instanceof IslandLevelPriceDB) { + dbObject = (IslandLevelPriceDB) saved; + } else { + throw new InvalidParameterException( + "DB object in IslandLevelPrice which is not an IslandLevelPriceDB"); + } + + return new IslandLevelPricePanel(addon, gamemode, user, parent, tier, dbObject); + } + + private final class IslandLevelPricePanel extends AbPanel { + + private static final String VALID = "valid"; + private static final String INVALID = "invalid"; + private static final String RULE = "rule"; + + private final UpgradeTier tier; + private final IslandLevelPriceDB saved; + + public IslandLevelPricePanel(UpgradesAddon addon, GameModeAddon gamemode, User user, + AbPanel parent, @NonNull UpgradeTier tier, + @NonNull IslandLevelPriceDB saved) { + super(addon, gamemode, user, user.getTranslation("upgrades.prices.islandlevel.paneltitle"), + parent); + + this.tier = tier; + this.saved = saved; + + this.createInterface(); + } + + private void createInterface() { + this.fillBorder(Material.BLACK_STAINED_GLASS_PANE); + + if (this.saved.isValid()) { + this.setItems(VALID, new PanelItemBuilder().name(this.getUser() + .getTranslation("upgrades.ui.buttons.validconf")) + .icon(Material.GREEN_CONCRETE) + .build(), 10); + } else { + this.setItems(INVALID, new PanelItemBuilder().name(this.getUser() + .getTranslation("upgrades.ui.buttons.invalidconf")) + .icon(Material.RED_CONCRETE) + .build(), 10); + } + + this.setItems(RULE, new PanelItemBuilder().name(this.saved.getLevelNeededEquation()) + .icon(Material.PAPER) + .clickHandler(this.onSetRule()) + .build(), 22); + } + + private PanelItem.ClickHandler onSetRule() { + return (panel, client, click, slot) -> { + this.getAddon() + .getChatInput() + .askOneInput(this.doSetRule(), input -> true, + client.getTranslation("upgrades.prices.islandlevel.rulequestion", + "[actual]", this.saved.getLevelNeededEquation()), "", client, + false); + return true; + }; + } + + private Consumer doSetRule() { + return (rule) -> { + this.saved.setLevelNeededEquation(rule); + this.createInterface(); + this.getBuild() + .build(); + }; + } + } +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/prices/IslandLevelPriceDB.java b/src/main/java/world/bentobox/upgrades/dataobjects/prices/IslandLevelPriceDB.java new file mode 100644 index 0000000..a1b8c66 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/prices/IslandLevelPriceDB.java @@ -0,0 +1,51 @@ +package world.bentobox.upgrades.dataobjects.prices; + +import com.google.gson.annotations.Expose; + +public class IslandLevelPriceDB extends PriceDB { + + + /** + * Level needed to make the upgrade It's a string representing an equation to + * calculate the level needed + */ + @Expose + private String levelNeededEquation = "0"; + + // ------------------------------------------------------------ + // Section: Getters + // ------------------------------------------------------------ + + /** + * @return the levelNeededEquation + */ + public String getLevelNeededEquation() { + return levelNeededEquation; + } + + // ------------------------------------------------------------ + // Section: Setters + // ------------------------------------------------------------ + + /** + * @param levelNeededEquation the levelNeededEquation to set + */ + public void setLevelNeededEquation(String levelNeededEquation) { + this.levelNeededEquation = levelNeededEquation; + } + + // ------------------------------------------------------------ + // Section: Utils Methods + // ------------------------------------------------------------ + + @Override + public Class getPriceType() { + return IslandLevelPrice.class; + } + + @Override + public boolean isValid() { + return levelNeededEquation != null && !levelNeededEquation.isEmpty(); + } + +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/prices/ItemPrice.java b/src/main/java/world/bentobox/upgrades/dataobjects/prices/ItemPrice.java new file mode 100644 index 0000000..f824ca5 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/prices/ItemPrice.java @@ -0,0 +1,187 @@ +package world.bentobox.upgrades.dataobjects.prices; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.ui.utils.AbPanel; + +import java.security.InvalidParameterException; +import java.util.List; +import java.util.function.Consumer; + +public class ItemPrice extends Price { + + public ItemPrice() { + super("item_price", Material.CHEST); + } + + @Override + public String getPublicName(User user) { + return user.getTranslation("upgrades.prices.item.name"); + } + + @Override + public String getAdminName(User user) { + return user.getTranslation("upgrades.prices.item.name"); + } + + @Override + public String getPublicDescription(User user) { + return user.getTranslation("upgrades.prices.item.description", "[amount]", "?", "[item]", "?"); + } + + @Override + public String getPublicDescription(User user, PriceDB priceDB) { + ItemPriceDB db = (ItemPriceDB) priceDB; + return user.getTranslation("upgrades.prices.item.description", + "[amount]", Integer.toString(db.getAmount()), + "[item]", db.getMaterial()); + } + + @Override + public String getAdminDescription(User user) { + return user.getTranslation("upgrades.prices.item.admindescription"); + } + + @Override + public boolean canPay(UpgradesAddon addon, User user, Island island, PriceDB priceDB, int currentLevel) { + ItemPriceDB db = (ItemPriceDB) priceDB; + try { + Material mat = Material.valueOf(db.getMaterial().toUpperCase()); + return user.getInventory().containsAtLeast(new ItemStack(mat), db.getAmount()); + } catch (IllegalArgumentException e) { + addon.logWarning("ItemPrice: invalid material '" + db.getMaterial() + "'"); + return false; + } + } + + @Override + public void pay(UpgradesAddon addon, User user, Island island, PriceDB priceDB, int currentLevel) { + ItemPriceDB db = (ItemPriceDB) priceDB; + try { + Material mat = Material.valueOf(db.getMaterial().toUpperCase()); + user.getInventory().removeItem(new ItemStack(mat, db.getAmount())); + } catch (IllegalArgumentException e) { + addon.logWarning("ItemPrice: invalid material '" + db.getMaterial() + "'"); + } + } + + @Override + public AbPanel getAdminPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, AbPanel parent, + UpgradeTier tier, @Nullable PriceDB saved) { + ItemPriceDB dbObject; + + if (saved == null) { + dbObject = new ItemPriceDB(); + List prices = tier.getPrices(); + prices.add(dbObject); + tier.setPrices(prices); + } else if (saved instanceof ItemPriceDB) { + dbObject = (ItemPriceDB) saved; + } else { + throw new InvalidParameterException("DB object in ItemPrice which is not an ItemPriceDB"); + } + + return new ItemPricePanel(addon, gamemode, user, parent, tier, dbObject); + } + + private final class ItemPricePanel extends AbPanel { + + private static final String VALID = "valid"; + private static final String INVALID = "invalid"; + private static final String SET_ITEM = "setitem"; + private static final String SET_AMOUNT = "setamount"; + + private final UpgradeTier tier; + private final ItemPriceDB saved; + + public ItemPricePanel(UpgradesAddon addon, GameModeAddon gamemode, User user, + AbPanel parent, @NonNull UpgradeTier tier, + @NonNull ItemPriceDB saved) { + super(addon, gamemode, user, user.getTranslation("upgrades.prices.item.paneltitle"), parent); + this.tier = tier; + this.saved = saved; + this.createInterface(); + } + + private void createInterface() { + this.fillBorder(Material.BLACK_STAINED_GLASS_PANE); + + if (this.saved.isValid()) { + this.setItems(VALID, new PanelItemBuilder().name(this.getUser() + .getTranslation("upgrades.ui.buttons.validconf")) + .icon(Material.GREEN_CONCRETE).build(), 10); + } else { + this.setItems(INVALID, new PanelItemBuilder().name(this.getUser() + .getTranslation("upgrades.ui.buttons.invalidconf")) + .icon(Material.RED_CONCRETE).build(), 10); + } + + Material iconMat = Material.CHEST; + if (!this.saved.getMaterial().isEmpty()) { + try { + iconMat = Material.valueOf(this.saved.getMaterial().toUpperCase()); + } catch (IllegalArgumentException ignored) {} + } + + this.setItems(SET_ITEM, new PanelItemBuilder() + .name(this.saved.getMaterial().isEmpty() ? "Click to set item (hold in hand)" : this.saved.getMaterial()) + .icon(iconMat) + .clickHandler(this.onSetItem()) + .build(), 20); + + this.setItems(SET_AMOUNT, new PanelItemBuilder() + .name(Integer.toString(this.saved.getAmount())) + .icon(Material.PAPER) + .clickHandler(this.onSetAmount()) + .build(), 24); + } + + private PanelItem.ClickHandler onSetItem() { + return (panel, client, click, slot) -> { + ItemStack inHand = client.getInventory().getItemInMainHand(); + if (inHand.getType() == Material.AIR) { + client.sendMessage(client.getTranslation("upgrades.error.noiteminhand")); + } else { + this.saved.setMaterial(inHand.getType().name()); + this.createInterface(); + this.getBuild().build(); + } + return true; + }; + } + + private PanelItem.ClickHandler onSetAmount() { + return (panel, client, click, slot) -> { + this.getAddon().getChatInput().askOneInput( + input -> { + try { + int amt = Integer.parseInt(input); + if (amt > 0) { + this.saved.setAmount(amt); + this.createInterface(); + this.getBuild().build(); + } + } catch (NumberFormatException ignored) {} + }, + input -> { + try { + return Integer.parseInt(input) > 0; + } catch (NumberFormatException e) { + return false; + } + }, + "Enter the amount (current: " + this.saved.getAmount() + ")", "", client, false); + return true; + }; + } + } +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/prices/ItemPriceDB.java b/src/main/java/world/bentobox/upgrades/dataobjects/prices/ItemPriceDB.java new file mode 100644 index 0000000..68ec559 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/prices/ItemPriceDB.java @@ -0,0 +1,39 @@ +package world.bentobox.upgrades.dataobjects.prices; + +import com.google.gson.annotations.Expose; + +public class ItemPriceDB extends PriceDB { + + @Expose + private String material = ""; + + @Expose + private int amount = 1; + + public String getMaterial() { + return material; + } + + public void setMaterial(String material) { + this.material = material; + } + + public int getAmount() { + return amount; + } + + public void setAmount(int amount) { + this.amount = amount; + } + + @Override + public Class getPriceType() { + return ItemPrice.class; + } + + @Override + public boolean isValid() { + return material != null && !material.isEmpty() && amount > 0; + } + +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/prices/MoneyPrice.java b/src/main/java/world/bentobox/upgrades/dataobjects/prices/MoneyPrice.java new file mode 100644 index 0000000..0e590e5 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/prices/MoneyPrice.java @@ -0,0 +1,155 @@ +package world.bentobox.upgrades.dataobjects.prices; + +import org.bukkit.Material; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.config.Settings; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.ui.utils.AbPanel; + +import java.security.InvalidParameterException; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Consumer; + +public class MoneyPrice extends Price { + + public MoneyPrice() { + super("money_price", Material.GOLD_INGOT); + } + + @Override + public String getPublicName(User user) { + return user.getTranslation("upgrades.prices.money.name"); + } + + @Override + public String getAdminName(User user) { + return user.getTranslation("upgrades.prices.money.name"); + } + + @Override + public String getPublicDescription(User user) { + return user.getTranslation("upgrades.prices.money.description", "[amount]", "?"); + } + + @Override + public String getPublicDescription(User user, PriceDB priceDB) { + MoneyPriceDB db = (MoneyPriceDB) priceDB; + return user.getTranslation("upgrades.prices.money.description", + "[amount]", db.getAmountEquation()); + } + + @Override + public String getAdminDescription(User user) { + return user.getTranslation("upgrades.prices.money.admindescription"); + } + + @Override + public boolean canPay(UpgradesAddon addon, User user, Island island, PriceDB priceDB, int currentLevel) { + if (!addon.isVaultProvided()) return true; + MoneyPriceDB db = (MoneyPriceDB) priceDB; + Map variables = new TreeMap<>(); + variables.put(LEVEL_VAR, (double) currentLevel); + variables.put(ISLAND_LEVEL_VAR, (double) addon.getUpgradesManager().getIslandLevel(island)); + variables.put(NUMBER_PLAYER_VAR, (double) island.getMemberSet().size()); + double amount = Settings.evaluate(db.getAmountEquation(), variables); + return addon.getVaultHook().has(user, amount); + } + + @Override + public void pay(UpgradesAddon addon, User user, Island island, PriceDB priceDB, int currentLevel) { + if (!addon.isVaultProvided()) return; + MoneyPriceDB db = (MoneyPriceDB) priceDB; + Map variables = new TreeMap<>(); + variables.put(LEVEL_VAR, (double) currentLevel); + variables.put(ISLAND_LEVEL_VAR, (double) addon.getUpgradesManager().getIslandLevel(island)); + variables.put(NUMBER_PLAYER_VAR, (double) island.getMemberSet().size()); + double amount = Settings.evaluate(db.getAmountEquation(), variables); + var response = addon.getVaultHook().withdraw(user, amount); + if (!response.transactionSuccess()) { + addon.logWarning("Money withdrawal failed for user " + user.getName() + ": " + response.errorMessage); + } + } + + @Override + public AbPanel getAdminPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, AbPanel parent, + UpgradeTier tier, @Nullable PriceDB saved) { + MoneyPriceDB dbObject; + + if (saved == null) { + dbObject = new MoneyPriceDB(); + List prices = tier.getPrices(); + prices.add(dbObject); + tier.setPrices(prices); + } else if (saved instanceof MoneyPriceDB) { + dbObject = (MoneyPriceDB) saved; + } else { + throw new InvalidParameterException("DB object in MoneyPrice which is not a MoneyPriceDB"); + } + + return new MoneyPricePanel(addon, gamemode, user, parent, tier, dbObject); + } + + private final class MoneyPricePanel extends AbPanel { + + private static final String VALID = "valid"; + private static final String INVALID = "invalid"; + private static final String RULE = "rule"; + + private final UpgradeTier tier; + private final MoneyPriceDB saved; + + public MoneyPricePanel(UpgradesAddon addon, GameModeAddon gamemode, User user, + AbPanel parent, @NonNull UpgradeTier tier, + @NonNull MoneyPriceDB saved) { + super(addon, gamemode, user, user.getTranslation("upgrades.prices.money.paneltitle"), parent); + this.tier = tier; + this.saved = saved; + this.createInterface(); + } + + private void createInterface() { + this.fillBorder(Material.BLACK_STAINED_GLASS_PANE); + + if (this.saved.isValid()) { + this.setItems(VALID, new PanelItemBuilder().name(this.getUser() + .getTranslation("upgrades.ui.buttons.validconf")) + .icon(Material.GREEN_CONCRETE).build(), 10); + } else { + this.setItems(INVALID, new PanelItemBuilder().name(this.getUser() + .getTranslation("upgrades.ui.buttons.invalidconf")) + .icon(Material.RED_CONCRETE).build(), 10); + } + + this.setItems(RULE, new PanelItemBuilder().name(this.saved.getAmountEquation()) + .icon(Material.PAPER) + .clickHandler(this.onSetRule()) + .build(), 22); + } + + private PanelItem.ClickHandler onSetRule() { + return (panel, client, click, slot) -> { + this.getAddon().getChatInput().askOneInput(this.doSetRule(), input -> true, + client.getTranslation("upgrades.prices.money.rulequestion", + "[actual]", this.saved.getAmountEquation()), "", client, false); + return true; + }; + } + + private Consumer doSetRule() { + return (rule) -> { + this.saved.setAmountEquation(rule); + this.createInterface(); + this.getBuild().build(); + }; + } + } +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/prices/MoneyPriceDB.java b/src/main/java/world/bentobox/upgrades/dataobjects/prices/MoneyPriceDB.java new file mode 100644 index 0000000..4ce5a76 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/prices/MoneyPriceDB.java @@ -0,0 +1,28 @@ +package world.bentobox.upgrades.dataobjects.prices; + +import com.google.gson.annotations.Expose; + +public class MoneyPriceDB extends PriceDB { + + @Expose + private String amountEquation = "0"; + + public String getAmountEquation() { + return amountEquation; + } + + public void setAmountEquation(String amountEquation) { + this.amountEquation = amountEquation; + } + + @Override + public Class getPriceType() { + return MoneyPrice.class; + } + + @Override + public boolean isValid() { + return amountEquation != null && !amountEquation.isEmpty(); + } + +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/prices/PermissionPrice.java b/src/main/java/world/bentobox/upgrades/dataobjects/prices/PermissionPrice.java new file mode 100644 index 0000000..91f4160 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/prices/PermissionPrice.java @@ -0,0 +1,136 @@ +package world.bentobox.upgrades.dataobjects.prices; + +import org.bukkit.Material; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.ui.utils.AbPanel; + +import java.security.InvalidParameterException; +import java.util.List; +import java.util.function.Consumer; + +public class PermissionPrice extends Price { + + public PermissionPrice() { + super("permission_price", Material.TRIPWIRE_HOOK); + } + + @Override + public String getPublicName(User user) { + return user.getTranslation("upgrades.prices.permission.name"); + } + + @Override + public String getAdminName(User user) { + return user.getTranslation("upgrades.prices.permission.name"); + } + + @Override + public String getPublicDescription(User user) { + return user.getTranslation("upgrades.prices.permission.description", "[permission]", "?"); + } + + @Override + public String getPublicDescription(User user, PriceDB priceDB) { + PermissionPriceDB db = (PermissionPriceDB) priceDB; + return user.getTranslation("upgrades.prices.permission.description", + "[permission]", db.getPermission()); + } + + @Override + public String getAdminDescription(User user) { + return user.getTranslation("upgrades.prices.permission.admindescription"); + } + + @Override + public boolean canPay(UpgradesAddon addon, User user, Island island, PriceDB priceDB, int currentLevel) { + PermissionPriceDB db = (PermissionPriceDB) priceDB; + return user.hasPermission(db.getPermission()); + } + + @Override + public void pay(UpgradesAddon addon, User user, Island island, PriceDB priceDB, int currentLevel) { + // Permission is a gate, not consumed + } + + @Override + public AbPanel getAdminPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, AbPanel parent, + UpgradeTier tier, @Nullable PriceDB saved) { + PermissionPriceDB dbObject; + + if (saved == null) { + dbObject = new PermissionPriceDB(); + List prices = tier.getPrices(); + prices.add(dbObject); + tier.setPrices(prices); + } else if (saved instanceof PermissionPriceDB) { + dbObject = (PermissionPriceDB) saved; + } else { + throw new InvalidParameterException("DB object in PermissionPrice which is not a PermissionPriceDB"); + } + + return new PermissionPricePanel(addon, gamemode, user, parent, tier, dbObject); + } + + private final class PermissionPricePanel extends AbPanel { + + private static final String VALID = "valid"; + private static final String INVALID = "invalid"; + private static final String RULE = "rule"; + + private final UpgradeTier tier; + private final PermissionPriceDB saved; + + public PermissionPricePanel(UpgradesAddon addon, GameModeAddon gamemode, User user, + AbPanel parent, @NonNull UpgradeTier tier, + @NonNull PermissionPriceDB saved) { + super(addon, gamemode, user, user.getTranslation("upgrades.prices.permission.paneltitle"), parent); + this.tier = tier; + this.saved = saved; + this.createInterface(); + } + + private void createInterface() { + this.fillBorder(Material.BLACK_STAINED_GLASS_PANE); + + if (this.saved.isValid()) { + this.setItems(VALID, new PanelItemBuilder().name(this.getUser() + .getTranslation("upgrades.ui.buttons.validconf")) + .icon(Material.GREEN_CONCRETE).build(), 10); + } else { + this.setItems(INVALID, new PanelItemBuilder().name(this.getUser() + .getTranslation("upgrades.ui.buttons.invalidconf")) + .icon(Material.RED_CONCRETE).build(), 10); + } + + this.setItems(RULE, new PanelItemBuilder().name(this.saved.getPermission().isEmpty() ? "Not set" : this.saved.getPermission()) + .icon(Material.PAPER) + .clickHandler(this.onSetRule()) + .build(), 22); + } + + private PanelItem.ClickHandler onSetRule() { + return (panel, client, click, slot) -> { + this.getAddon().getChatInput().askOneInput(this.doSetRule(), input -> true, + client.getTranslation("upgrades.prices.permission.rulequestion", + "[actual]", this.saved.getPermission()), "", client, false); + return true; + }; + } + + private Consumer doSetRule() { + return (rule) -> { + this.saved.setPermission(rule); + this.createInterface(); + this.getBuild().build(); + }; + } + } +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/prices/PermissionPriceDB.java b/src/main/java/world/bentobox/upgrades/dataobjects/prices/PermissionPriceDB.java new file mode 100644 index 0000000..3e06a99 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/prices/PermissionPriceDB.java @@ -0,0 +1,28 @@ +package world.bentobox.upgrades.dataobjects.prices; + +import com.google.gson.annotations.Expose; + +public class PermissionPriceDB extends PriceDB { + + @Expose + private String permission = ""; + + public String getPermission() { + return permission; + } + + public void setPermission(String permission) { + this.permission = permission; + } + + @Override + public Class getPriceType() { + return PermissionPrice.class; + } + + @Override + public boolean isValid() { + return permission != null && !permission.isEmpty(); + } + +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/prices/Price.java b/src/main/java/world/bentobox/upgrades/dataobjects/prices/Price.java new file mode 100644 index 0000000..9abf869 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/prices/Price.java @@ -0,0 +1,56 @@ +package world.bentobox.upgrades.dataobjects.prices; + +import org.bukkit.Material; + +import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.ui.PanelAdminItem; +import world.bentobox.upgrades.ui.PanelPublicItem; +import world.bentobox.upgrades.ui.utils.AbPanel; + +public abstract class Price implements PanelAdminItem, PanelPublicItem { + + public static final String LEVEL_VAR = "[level]"; + public static final String ISLAND_LEVEL_VAR = "[islandLevel]"; + public static final String NUMBER_PLAYER_VAR = "[numberPlayer]"; + + private final String name; + private final Material icon; + + public Price(String name, Material icon) { + this.name = name; + this.icon = icon; + } + + @Override + public String getName() { + return name; + } + + @Override + public Material getIcon() { + return this.icon; + } + + public abstract AbPanel getAdminPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, + AbPanel parent, UpgradeTier tier, @Nullable PriceDB saved); + + /** + * Returns a player-facing description with any formula placeholders substituted + * using values from the stored DB object. Override in concrete Price types that + * carry a formula (e.g. MoneyPrice substitutes [amount]). + * The default falls back to {@link PanelPublicItem#getPublicDescription(User)}. + */ + public String getPublicDescription(User user, PriceDB priceDB) { + return this.getPublicDescription(user); + } + + public abstract boolean canPay(UpgradesAddon addon, User user, Island island, PriceDB priceDB, int currentLevel); + + public abstract void pay(UpgradesAddon addon, User user, Island island, PriceDB priceDB, int currentLevel); + +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/prices/PriceDB.java b/src/main/java/world/bentobox/upgrades/dataobjects/prices/PriceDB.java new file mode 100644 index 0000000..fdbfe0b --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/prices/PriceDB.java @@ -0,0 +1,12 @@ +package world.bentobox.upgrades.dataobjects.prices; + +import com.google.gson.annotations.Expose; +import world.bentobox.bentobox.database.objects.DataObject; + +public abstract class PriceDB { + + abstract public Class getPriceType(); + + abstract public boolean isValid(); + +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/rewards/CommandReward.java b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/CommandReward.java new file mode 100644 index 0000000..d8bdf7c --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/CommandReward.java @@ -0,0 +1,175 @@ +package world.bentobox.upgrades.dataobjects.rewards; + +import org.bukkit.Material; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.ui.utils.AbPanel; + +import java.security.InvalidParameterException; +import java.util.ArrayList; +import java.util.List; + +public class CommandReward extends Reward { + + public CommandReward() { + super("command_reward", Material.COMMAND_BLOCK); + } + + @Override + public String getAdminName(User user) { + return user.getTranslation("upgrades.rewards.command.name"); + } + + @Override + public String getAdminDescription(User user) { + return user.getTranslation("upgrades.rewards.command.admindescription"); + } + + @Override + public String getPublicName(User user) { + return user.getTranslation("upgrades.rewards.command.name"); + } + + @Override + public String getPublicDescription(User user) { + return user.getTranslation("upgrades.rewards.command.description"); + } + + @Override + public void apply(UpgradesAddon addon, User user, Island island, RewardDB rewardDB, int currentLevel) { + CommandRewardDB db = (CommandRewardDB) rewardDB; + String playerName = user.getName(); + String ownerName = island.getPlugin().getPlayers().getName(island.getOwner()); + if (ownerName == null) ownerName = ""; + + for (String cmd : db.getCommands()) { + String formatted = cmd + .replace("[player]", playerName) + .replace("[owner]", ownerName); + + if (db.isConsole()) { + addon.getServer().dispatchCommand(addon.getServer().getConsoleSender(), formatted); + } else { + addon.getServer().dispatchCommand(user.getSender(), formatted); + } + } + } + + @Override + public AbPanel getAdminPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, AbPanel parent, + UpgradeTier tier, @Nullable RewardDB saved) { + CommandRewardDB dbObject; + + if (saved == null) { + dbObject = new CommandRewardDB(); + List rewards = tier.getRewards(); + rewards.add(dbObject); + tier.setRewards(rewards); + } else if (saved instanceof CommandRewardDB) { + dbObject = (CommandRewardDB) saved; + } else { + throw new InvalidParameterException("DB object in CommandReward which is not a CommandRewardDB"); + } + + return new CommandRewardPanel(addon, gamemode, user, parent, tier, dbObject); + } + + private final class CommandRewardPanel extends AbPanel { + + private static final String VALID = "valid"; + private static final String INVALID = "invalid"; + private static final String TOGGLE = "toggle"; + private static final String ADD_CMD = "addcmd"; + private static final String CLEAR_CMD = "clearcmd"; + + private final UpgradeTier tier; + private final CommandRewardDB saved; + + public CommandRewardPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, + AbPanel parent, @NonNull UpgradeTier tier, + @NonNull CommandRewardDB saved) { + super(addon, gamemode, user, user.getTranslation("upgrades.rewards.command.paneltitle"), parent); + this.tier = tier; + this.saved = saved; + this.createInterface(); + } + + private void createInterface() { + this.fillBorder(Material.BLACK_STAINED_GLASS_PANE); + + if (this.saved.isValid()) { + this.setItems(VALID, new PanelItemBuilder().name(this.getUser() + .getTranslation("upgrades.ui.buttons.validconf")) + .icon(Material.GREEN_CONCRETE).build(), 10); + } else { + this.setItems(INVALID, new PanelItemBuilder().name(this.getUser() + .getTranslation("upgrades.ui.buttons.invalidconf")) + .icon(Material.RED_CONCRETE).build(), 10); + } + + Material toggleIcon = this.saved.isConsole() ? Material.GREEN_CONCRETE : Material.YELLOW_CONCRETE; + String toggleLabel = this.saved.isConsole() ? "Console" : "Player"; + this.setItems(TOGGLE, new PanelItemBuilder() + .name(toggleLabel) + .icon(toggleIcon) + .clickHandler(this.onToggle()) + .build(), 20); + + List cmdLore = new ArrayList<>(this.saved.getCommands()); + this.setItems(ADD_CMD, new PanelItemBuilder() + .name("Add command") + .description(cmdLore) + .icon(Material.PAPER) + .clickHandler(this.onAddCommand()) + .build(), 22); + + this.setItems(CLEAR_CMD, new PanelItemBuilder() + .name("Clear all commands") + .icon(Material.RED_CONCRETE) + .clickHandler(this.onClearCommands()) + .build(), 24); + } + + private PanelItem.ClickHandler onToggle() { + return (panel, client, click, slot) -> { + this.saved.setConsole(!this.saved.isConsole()); + this.createInterface(); + this.getBuild().build(); + return true; + }; + } + + private PanelItem.ClickHandler onAddCommand() { + return (panel, client, click, slot) -> { + this.getAddon().getChatInput().askOneInput( + cmd -> { + List cmds = new ArrayList<>(this.saved.getCommands()); + cmds.add(cmd); + this.saved.setCommands(cmds); + this.createInterface(); + this.getBuild().build(); + }, + input -> true, + "Enter command (use [player], [owner] as placeholders)", + "", client, false); + return true; + }; + } + + private PanelItem.ClickHandler onClearCommands() { + return (panel, client, click, slot) -> { + this.saved.setCommands(new ArrayList<>()); + this.createInterface(); + this.getBuild().build(); + return true; + }; + } + } +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/rewards/CommandRewardDB.java b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/CommandRewardDB.java new file mode 100644 index 0000000..67b7c8e --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/CommandRewardDB.java @@ -0,0 +1,42 @@ +package world.bentobox.upgrades.dataobjects.rewards; + +import com.google.gson.annotations.Expose; + +import java.util.ArrayList; +import java.util.List; + +public class CommandRewardDB extends RewardDB { + + @Expose + private List commands = new ArrayList<>(); + + @Expose + private boolean isConsole = true; + + public List getCommands() { + return commands; + } + + public void setCommands(List commands) { + this.commands = commands; + } + + public boolean isConsole() { + return isConsole; + } + + public void setConsole(boolean console) { + isConsole = console; + } + + @Override + public Class getRewardType() { + return CommandReward.class; + } + + @Override + public boolean isValid() { + return commands != null && !commands.isEmpty(); + } + +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/rewards/CropGrowthReward.java b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/CropGrowthReward.java new file mode 100644 index 0000000..0f3ce23 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/CropGrowthReward.java @@ -0,0 +1,159 @@ +package world.bentobox.upgrades.dataobjects.rewards; + +import org.bukkit.Material; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.config.Settings; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.ui.utils.AbPanel; + +import java.security.InvalidParameterException; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Consumer; + +public class CropGrowthReward extends Reward { + + public CropGrowthReward() { + super("crop_growth_reward", Material.WHEAT); + } + + @Override + public String getAdminName(User user) { + return user.getTranslation("upgrades.rewards.cropgrowth.name"); + } + + @Override + public String getAdminDescription(User user) { + return user.getTranslation("upgrades.rewards.cropgrowth.admindescription"); + } + + @Override + public String getPublicName(User user) { + return user.getTranslation("upgrades.rewards.cropgrowth.name"); + } + + @Override + public String getPublicDescription(User user) { + return user.getTranslation("upgrades.rewards.cropgrowth.description"); + } + + @Override + public String getPublicDescription(User user, RewardDB rewardDB) { + CropGrowthRewardDB db = (CropGrowthRewardDB) rewardDB; + return user.getTranslation("upgrades.rewards.cropgrowth.description", + "[amount]", db.getGrowthBonusEquation()); + } + + @Override + public void apply(UpgradesAddon addon, User user, Island island, RewardDB rewardDB, int currentLevel) { + // No-op: ongoing effect handled by CropGrowthListener + } + + @Override + public AbPanel getAdminPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, AbPanel parent, + UpgradeTier tier, @Nullable RewardDB saved) { + CropGrowthRewardDB dbObject; + + if (saved == null) { + dbObject = new CropGrowthRewardDB(); + List rewards = tier.getRewards(); + rewards.add(dbObject); + tier.setRewards(rewards); + } else if (saved instanceof CropGrowthRewardDB) { + dbObject = (CropGrowthRewardDB) saved; + } else { + throw new InvalidParameterException( + "DB object in CropGrowthReward which is not a CropGrowthRewardDB"); + } + + return new CropGrowthRewardPanel(addon, gamemode, user, parent, tier, dbObject); + } + + private final class CropGrowthRewardPanel extends AbPanel { + private static final String VALID = "valid"; + private static final String INVALID = "invalid"; + private static final String RULE = "rule"; + + private final UpgradeTier tier; + private final CropGrowthRewardDB saved; + + public CropGrowthRewardPanel(UpgradesAddon addon, GameModeAddon gamemode, + User user, AbPanel parent, + @NonNull UpgradeTier tier, + @NonNull CropGrowthRewardDB saved) { + super(addon, gamemode, user, user.getTranslation("upgrades.rewards.cropgrowth.paneltitle"), + parent); + + this.tier = tier; + this.saved = saved; + + this.createInterface(); + } + + private void createInterface() { + this.fillBorder(Material.BLACK_STAINED_GLASS_PANE); + + if (this.saved.isValid()) { + this.setItems(VALID, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.rewards.cropgrowth.formulastatus")) + .description(this.saved.getGrowthBonusEquation()) + .icon(Material.GREEN_CONCRETE) + .build(), 10); + } else { + this.setItems(INVALID, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.rewards.cropgrowth.formulaneeded")) + .description(this.getUser().getTranslation("upgrades.rewards.cropgrowth.formulaneededdesc")) + .icon(Material.RED_CONCRETE) + .build(), 10); + } + + this.setItems(RULE, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.rewards.cropgrowth.setformula")) + .description(this.saved.getGrowthBonusEquation()) + .icon(Material.PAPER) + .clickHandler(this.onSetRule()) + .build(), 22); + } + + private PanelItem.ClickHandler onSetRule() { + return (panel, client, click, slot) -> { + this.getAddon() + .getChatInput() + .askOneInput(this.doSetRule(), + input -> { + try { + Map vars = new TreeMap<>(); + vars.put(LEVEL_VAR, 1.0); + vars.put(ISLAND_LEVEL_VAR, 1.0); + vars.put(NUMBER_PLAYER_VAR, 1.0); + Settings.evaluate(input, vars); + return true; + } catch (Exception e) { + return false; + } + }, + client.getTranslation("upgrades.rewards.cropgrowth.rulequestion", + "[actual]", this.saved.getGrowthBonusEquation()), + client.getTranslation("upgrades.rewards.cropgrowth.invalidrule"), + client, false); + return true; + }; + } + + private Consumer doSetRule() { + return (rule) -> { + this.saved.setGrowthBonusEquation(rule); + this.createInterface(); + this.getBuild().build(); + }; + } + } +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/rewards/CropGrowthRewardDB.java b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/CropGrowthRewardDB.java new file mode 100644 index 0000000..5b2e05e --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/CropGrowthRewardDB.java @@ -0,0 +1,28 @@ +package world.bentobox.upgrades.dataobjects.rewards; + +import com.google.gson.annotations.Expose; + +public class CropGrowthRewardDB extends RewardDB { + + @Expose + private String growthBonusEquation = "0"; + + public String getGrowthBonusEquation() { + return growthBonusEquation; + } + + public void setGrowthBonusEquation(String growthBonusEquation) { + this.growthBonusEquation = growthBonusEquation; + } + + @Override + public boolean isValid() { + return growthBonusEquation != null && !growthBonusEquation.isEmpty(); + } + + @Override + public Class getRewardType() { + return CropGrowthReward.class; + } + +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/rewards/LimitsReward.java b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/LimitsReward.java new file mode 100644 index 0000000..3b32cd0 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/LimitsReward.java @@ -0,0 +1,224 @@ +package world.bentobox.upgrades.dataobjects.rewards; + +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.limits.listeners.BlockLimitsListener; +import world.bentobox.limits.objects.IslandBlockCount; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.config.Settings; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.ui.utils.AbPanel; + +import java.security.InvalidParameterException; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Consumer; + +public class LimitsReward extends Reward { + + public LimitsReward() { + super("limits_reward", Material.BARRIER); + } + + @Override + public String getAdminName(User user) { + return user.getTranslation("upgrades.rewards.limits.name"); + } + + @Override + public String getAdminDescription(User user) { + return user.getTranslation("upgrades.rewards.limits.admindescription"); + } + + @Override + public String getPublicName(User user) { + return user.getTranslation("upgrades.rewards.limits.name"); + } + + @Override + public String getPublicDescription(User user) { + return user.getTranslation("upgrades.rewards.limits.description", + "[type]", "?", "[target]", "?", "[amount]", "?"); + } + + @Override + public String getPublicDescription(User user, RewardDB rewardDB) { + LimitsRewardDB db = (LimitsRewardDB) rewardDB; + return user.getTranslation("upgrades.rewards.limits.description", + "[type]", db.getLimitType(), + "[target]", db.getTarget(), + "[amount]", db.getAmountEquation()); + } + + @Override + public void apply(UpgradesAddon addon, User user, Island island, RewardDB rewardDB, int currentLevel) { + if (!addon.isLimitsProvided()) { + addon.logWarning("LimitsReward: Limits addon not available"); + return; + } + + LimitsRewardDB db = (LimitsRewardDB) rewardDB; + Map variables = new TreeMap<>(); + variables.put(LEVEL_VAR, (double) currentLevel); + variables.put(ISLAND_LEVEL_VAR, (double) addon.getUpgradesManager().getIslandLevel(island)); + variables.put(NUMBER_PLAYER_VAR, (double) island.getMemberSet().size()); + int amount = (int) Settings.evaluate(db.getAmountEquation(), variables); + + BlockLimitsListener bLListener = addon.getLimitsAddon().getBlockLimitListener(); + IslandBlockCount isb = bLListener.getIsland(island); + + if (isb == null) { + addon.logWarning("LimitsReward: IslandBlockCount not found for island " + island.getUniqueId()); + return; + } + + switch (db.getLimitType().toUpperCase()) { + case "BLOCK" -> { + try { + Material mat = Material.valueOf(db.getTarget().toUpperCase()); + int oldCount = isb.getBlockLimitsOffset().getOrDefault(mat.getKey(), 0); + isb.setBlockLimitsOffset(mat.getKey(), oldCount + amount); + } catch (IllegalArgumentException e) { + addon.logWarning("LimitsReward: invalid material '" + db.getTarget() + "'"); + } + } + case "ENTITY" -> { + try { + EntityType et = EntityType.valueOf(db.getTarget().toUpperCase()); + int oldCount = isb.getEntityLimitsOffset().getOrDefault(et, 0); + isb.setEntityLimitsOffset(et, oldCount + amount); + } catch (IllegalArgumentException e) { + addon.logWarning("LimitsReward: invalid entity type '" + db.getTarget() + "'"); + } + } + case "ENTITY_GROUP" -> { + int oldCount = isb.getEntityGroupLimitsOffset().getOrDefault(db.getTarget(), 0); + isb.setEntityGroupLimitsOffset(db.getTarget(), oldCount + amount); + } + default -> addon.logWarning("LimitsReward: unknown limit type '" + db.getLimitType() + "'"); + } + } + + @Override + public AbPanel getAdminPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, AbPanel parent, + UpgradeTier tier, @Nullable RewardDB saved) { + LimitsRewardDB dbObject; + + if (saved == null) { + dbObject = new LimitsRewardDB(); + List rewards = tier.getRewards(); + rewards.add(dbObject); + tier.setRewards(rewards); + } else if (saved instanceof LimitsRewardDB) { + dbObject = (LimitsRewardDB) saved; + } else { + throw new InvalidParameterException("DB object in LimitsReward which is not a LimitsRewardDB"); + } + + return new LimitsRewardPanel(addon, gamemode, user, parent, tier, dbObject); + } + + private final class LimitsRewardPanel extends AbPanel { + + private static final String VALID = "valid"; + private static final String INVALID = "invalid"; + private static final String TYPE = "type"; + private static final String TARGET = "target"; + private static final String AMOUNT = "amount"; + + private final UpgradeTier tier; + private final LimitsRewardDB saved; + + public LimitsRewardPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, + AbPanel parent, @NonNull UpgradeTier tier, + @NonNull LimitsRewardDB saved) { + super(addon, gamemode, user, user.getTranslation("upgrades.rewards.limits.paneltitle"), parent); + this.tier = tier; + this.saved = saved; + this.createInterface(); + } + + private void createInterface() { + this.fillBorder(Material.BLACK_STAINED_GLASS_PANE); + + if (this.saved.isValid()) { + this.setItems(VALID, new PanelItemBuilder().name(this.getUser() + .getTranslation("upgrades.ui.buttons.validconf")) + .icon(Material.GREEN_CONCRETE).build(), 10); + } else { + this.setItems(INVALID, new PanelItemBuilder().name(this.getUser() + .getTranslation("upgrades.ui.buttons.invalidconf")) + .icon(Material.RED_CONCRETE).build(), 10); + } + + this.setItems(TYPE, new PanelItemBuilder() + .name(this.saved.getLimitType()) + .icon(Material.COMPARATOR) + .clickHandler(this.onCycleType()) + .build(), 20); + + this.setItems(TARGET, new PanelItemBuilder() + .name(this.saved.getTarget().isEmpty() ? "Not set" : this.saved.getTarget()) + .icon(Material.PAPER) + .clickHandler(this.onSetTarget()) + .build(), 22); + + this.setItems(AMOUNT, new PanelItemBuilder() + .name(this.saved.getAmountEquation()) + .icon(Material.PAPER) + .clickHandler(this.onSetAmount()) + .build(), 24); + } + + private PanelItem.ClickHandler onCycleType() { + return (panel, client, click, slot) -> { + switch (this.saved.getLimitType()) { + case "BLOCK" -> this.saved.setLimitType("ENTITY"); + case "ENTITY" -> this.saved.setLimitType("ENTITY_GROUP"); + default -> this.saved.setLimitType("BLOCK"); + } + this.createInterface(); + this.getBuild().build(); + return true; + }; + } + + private PanelItem.ClickHandler onSetTarget() { + return (panel, client, click, slot) -> { + this.getAddon().getChatInput().askOneInput( + rule -> { + this.saved.setTarget(rule); + this.createInterface(); + this.getBuild().build(); + }, + input -> true, + "Enter target (" + this.saved.getLimitType() + "). Current: " + this.saved.getTarget(), + "", client, false); + return true; + }; + } + + private PanelItem.ClickHandler onSetAmount() { + return (panel, client, click, slot) -> { + this.getAddon().getChatInput().askOneInput( + rule -> { + this.saved.setAmountEquation(rule); + this.createInterface(); + this.getBuild().build(); + }, + input -> true, + "Enter amount formula. Current: " + this.saved.getAmountEquation(), + "", client, false); + return true; + }; + } + } +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/rewards/LimitsRewardDB.java b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/LimitsRewardDB.java new file mode 100644 index 0000000..2292988 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/LimitsRewardDB.java @@ -0,0 +1,52 @@ +package world.bentobox.upgrades.dataobjects.rewards; + +import com.google.gson.annotations.Expose; + +public class LimitsRewardDB extends RewardDB { + + @Expose + private String limitType = "BLOCK"; + + @Expose + private String target = ""; + + @Expose + private String amountEquation = "0"; + + public String getLimitType() { + return limitType; + } + + public void setLimitType(String limitType) { + this.limitType = limitType; + } + + public String getTarget() { + return target; + } + + public void setTarget(String target) { + this.target = target; + } + + public String getAmountEquation() { + return amountEquation; + } + + public void setAmountEquation(String amountEquation) { + this.amountEquation = amountEquation; + } + + @Override + public Class getRewardType() { + return LimitsReward.class; + } + + @Override + public boolean isValid() { + return limitType != null && !limitType.isEmpty() + && target != null && !target.isEmpty() + && amountEquation != null && !amountEquation.isEmpty(); + } + +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/rewards/RangeReward.java b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/RangeReward.java new file mode 100644 index 0000000..6bee3c0 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/RangeReward.java @@ -0,0 +1,186 @@ +package world.bentobox.upgrades.dataobjects.rewards; + +import com.google.gson.annotations.Expose; +import org.bukkit.Material; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.events.island.IslandEvent; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.config.Settings; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.ui.utils.AbPanel; + +import java.security.InvalidParameterException; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Consumer; + +public class RangeReward extends Reward { + + + public RangeReward() { + super("range_reward", Material.OAK_FENCE); + } + + @Override + public String getAdminName(User user) { + return user.getTranslation("upgrades.rewards.rangeupgrade.name"); + } + + @Override + public String getAdminDescription(User user) { + return user.getTranslation("upgrades.rewards.rangeupgrade.admindescription"); + } + + @Override + public String getPublicName(User user) { + return user.getTranslation("upgrades.rewards.rangeupgrade.name"); + } + + @Override + public String getPublicDescription(User user) { + return user.getTranslation("upgrades.rewards.rangeupgrade.description"); + } + + @Override + public String getPublicDescription(User user, RewardDB rewardDB) { + RangeRewardDB db = (RangeRewardDB) rewardDB; + return user.getTranslation("upgrades.rewards.rangeupgrade.description", + "[rangelevel]", db.getRangeUpgradeEquation()); + } + + @Override + public void apply(UpgradesAddon addon, User user, Island island, RewardDB rewardDB, int currentLevel) { + RangeRewardDB db = (RangeRewardDB) rewardDB; + Map variables = new TreeMap<>(); + variables.put(LEVEL_VAR, (double) currentLevel); + variables.put(ISLAND_LEVEL_VAR, (double) addon.getUpgradesManager().getIslandLevel(island)); + variables.put(NUMBER_PLAYER_VAR, (double) island.getMemberSet().size()); + int amount = (int) Settings.evaluate(db.getRangeUpgradeEquation(), variables); + + int newRange = island.getProtectionRange() + amount; + if (newRange > island.getRange()) { + addon.logWarning("Tried to upgrade island range over max for island " + island.getUniqueId()); + return; + } + + int oldRange = island.getProtectionRange(); + island.addBonusRange(addon.getDescription().getName(), amount, ""); + IslandEvent.builder().island(island).location(island.getCenter()) + .reason(IslandEvent.Reason.RANGE_CHANGE) + .involvedPlayer(user.getUniqueId()).admin(false) + .protectionRange(island.getProtectionRange(), oldRange).build(); + + user.sendMessage("upgrades.ui.upgradepanel.rangeupgradedone", "[rangelevel]", Integer.toString(amount)); + } + + @Override + public AbPanel getAdminPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, AbPanel parent, + UpgradeTier tier, @Nullable RewardDB saved) { + RangeRewardDB dbObject; + + if (saved == null) { + dbObject = new RangeRewardDB(); + List rewards = tier.getRewards(); + + rewards.add(dbObject); + tier.setRewards(rewards); + } else if (saved instanceof RangeRewardDB) { + dbObject = (RangeRewardDB) saved; + } else { + throw new InvalidParameterException( + "DB object in RangeReward which is not an RangeRewardDB"); + } + + return new RangeRewardPanel(addon, gamemode, user, parent, tier, dbObject); + } + + private final class RangeRewardPanel extends AbPanel { + private static final String VALID = "valid"; + private static final String INVALID = "invalid"; + private static final String RULE = "rule"; + + private final UpgradeTier tier; + private final RangeRewardDB saved; + + public RangeRewardPanel(UpgradesAddon addon, GameModeAddon gamemode, + User user, AbPanel parent, + @NonNull UpgradeTier tier, + @NonNull RangeRewardDB saved) { + super(addon, gamemode, user, user.getTranslation("upgrades.rewards.rangeupgrade.paneltitle"), + parent); + + this.tier = tier; + this.saved = saved; + + this.createInterface(); + } + + private void createInterface() { + this.fillBorder(Material.BLACK_STAINED_GLASS_PANE); + + // Show the formula status in-line with the formula item (#70 items 7 & 8) + if (this.saved.isValid()) { + this.setItems(VALID, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.rewards.rangeupgrade.formulastatus")) + .description(this.saved.getRangeUpgradeEquation()) + .icon(Material.GREEN_CONCRETE) + .build(), 10); + } else { + this.setItems(INVALID, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.rewards.rangeupgrade.formulaneeded")) + .description(this.getUser().getTranslation("upgrades.rewards.rangeupgrade.formulaneededdesc")) + .icon(Material.RED_CONCRETE) + .build(), 10); + } + + this.setItems(RULE, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.rewards.rangeupgrade.setformula")) + .description(this.saved.getRangeUpgradeEquation()) + .icon(Material.PAPER) + .clickHandler(this.onSetRule()) + .build(), 22); + } + + private PanelItem.ClickHandler onSetRule() { + return (panel, client, click, slot) -> { + this.getAddon() + .getChatInput() + .askOneInput(this.doSetRule(), + input -> { + // Validate that input is a parseable math expression (#70 item 8) + try { + Map vars = new TreeMap<>(); + vars.put(LEVEL_VAR, 1.0); + vars.put(ISLAND_LEVEL_VAR, 1.0); + vars.put(NUMBER_PLAYER_VAR, 1.0); + Settings.evaluate(input, vars); + return true; + } catch (Exception e) { + return false; + } + }, + client.getTranslation("upgrades.rewards.rangeupgrade.rulequestion", + "[actual]", this.saved.getRangeUpgradeEquation()), + client.getTranslation("upgrades.rewards.rangeupgrade.invalidrule"), + client, false); + return true; + }; + } + + private Consumer doSetRule() { + return (rule) -> { + this.saved.setRangeUpgradeEquation(rule); + this.createInterface(); + this.getBuild() + .build(); + }; + } + } +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/rewards/RangeRewardDB.java b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/RangeRewardDB.java new file mode 100644 index 0000000..bcd9043 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/RangeRewardDB.java @@ -0,0 +1,55 @@ +package world.bentobox.upgrades.dataobjects.rewards; + +import com.google.gson.annotations.Expose; + +public class RangeRewardDB extends RewardDB { + + // ------------------------------------------------------------ + // Section: Variables + // ------------------------------------------------------------ + + /** + * How much is added at each upgrades + * It's a string representing an equation to calculate the range upgrade + */ + @Expose + private String rangeUpgradeEquation = "0"; + + // ------------------------------------------------------------ + // Section: Getters + // ------------------------------------------------------------ + + /** + * @return the rangeUpgradeEquation + */ + public String getRangeUpgradeEquation() { + return rangeUpgradeEquation; + } + + // ------------------------------------------------------------ + // Section: Setters + // ------------------------------------------------------------ + + /** + * @param rangeUpgradeEquation the rangeUpgradeEquation to set + */ + public void setRangeUpgradeEquation(String rangeUpgradeEquation) { + this.rangeUpgradeEquation = rangeUpgradeEquation; + } + + // ------------------------------------------------------------ + // Section: Utils Methods + // ------------------------------------------------------------ + + @Override + public boolean isValid() { + return rangeUpgradeEquation != null && + !rangeUpgradeEquation.isEmpty(); + } + + @Override + public Class getRewardType() { + return RangeReward.class; + } + +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/rewards/Reward.java b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/Reward.java new file mode 100644 index 0000000..b019793 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/Reward.java @@ -0,0 +1,53 @@ +package world.bentobox.upgrades.dataobjects.rewards; + +import org.bukkit.Material; +import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.ui.PanelAdminItem; +import world.bentobox.upgrades.ui.PanelPublicItem; +import world.bentobox.upgrades.ui.utils.AbPanel; + +public abstract class Reward implements PanelAdminItem, PanelPublicItem { + + public static final String LEVEL_VAR = "[level]"; + public static final String ISLAND_LEVEL_VAR = "[islandLevel]"; + public static final String NUMBER_PLAYER_VAR = "[numberPlayer]"; + + private final String name; + private final Material icon; + + public Reward(String name, Material icon) { + this.name = name; + this.icon = icon; + } + + @Override + public String getName() { + return name; + } + + @Override + public Material getIcon() { + return this.icon; + } + + /** + * Returns a player-facing description with any formula placeholders substituted + * using values from the stored DB object. Override in concrete Reward types that + * carry a formula (e.g. RangeReward substitutes the range amount). + * The default falls back to {@link PanelPublicItem#getPublicDescription(User)}. + */ + public String getPublicDescription(User user, RewardDB rewardDB) { + return this.getPublicDescription(user); + } + + public abstract AbPanel getAdminPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, + AbPanel parent, UpgradeTier tier, @Nullable RewardDB saved); + + public abstract void apply(UpgradesAddon addon, User user, Island island, RewardDB rewardDB, int currentLevel); + +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/rewards/RewardDB.java b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/RewardDB.java new file mode 100644 index 0000000..aac1ed6 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/RewardDB.java @@ -0,0 +1,12 @@ +package world.bentobox.upgrades.dataobjects.rewards; + +import com.google.gson.annotations.Expose; +import world.bentobox.bentobox.database.objects.DataObject; + +public abstract class RewardDB { + + abstract public Class getRewardType(); + + abstract public boolean isValid(); + +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/rewards/SpawnerReward.java b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/SpawnerReward.java new file mode 100644 index 0000000..2742d19 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/SpawnerReward.java @@ -0,0 +1,159 @@ +package world.bentobox.upgrades.dataobjects.rewards; + +import org.bukkit.Material; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.config.Settings; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.ui.utils.AbPanel; + +import java.security.InvalidParameterException; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Consumer; + +public class SpawnerReward extends Reward { + + public SpawnerReward() { + super("spawner_reward", Material.SPAWNER); + } + + @Override + public String getAdminName(User user) { + return user.getTranslation("upgrades.rewards.spawner.name"); + } + + @Override + public String getAdminDescription(User user) { + return user.getTranslation("upgrades.rewards.spawner.admindescription"); + } + + @Override + public String getPublicName(User user) { + return user.getTranslation("upgrades.rewards.spawner.name"); + } + + @Override + public String getPublicDescription(User user) { + return user.getTranslation("upgrades.rewards.spawner.description"); + } + + @Override + public String getPublicDescription(User user, RewardDB rewardDB) { + SpawnerRewardDB db = (SpawnerRewardDB) rewardDB; + return user.getTranslation("upgrades.rewards.spawner.description", + "[amount]", db.getSpawnBonusEquation()); + } + + @Override + public void apply(UpgradesAddon addon, User user, Island island, RewardDB rewardDB, int currentLevel) { + // No-op: ongoing effect handled by SpawnerUpgradeListener + } + + @Override + public AbPanel getAdminPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, AbPanel parent, + UpgradeTier tier, @Nullable RewardDB saved) { + SpawnerRewardDB dbObject; + + if (saved == null) { + dbObject = new SpawnerRewardDB(); + List rewards = tier.getRewards(); + rewards.add(dbObject); + tier.setRewards(rewards); + } else if (saved instanceof SpawnerRewardDB) { + dbObject = (SpawnerRewardDB) saved; + } else { + throw new InvalidParameterException( + "DB object in SpawnerReward which is not a SpawnerRewardDB"); + } + + return new SpawnerRewardPanel(addon, gamemode, user, parent, tier, dbObject); + } + + private final class SpawnerRewardPanel extends AbPanel { + private static final String VALID = "valid"; + private static final String INVALID = "invalid"; + private static final String RULE = "rule"; + + private final UpgradeTier tier; + private final SpawnerRewardDB saved; + + public SpawnerRewardPanel(UpgradesAddon addon, GameModeAddon gamemode, + User user, AbPanel parent, + @NonNull UpgradeTier tier, + @NonNull SpawnerRewardDB saved) { + super(addon, gamemode, user, user.getTranslation("upgrades.rewards.spawner.paneltitle"), + parent); + + this.tier = tier; + this.saved = saved; + + this.createInterface(); + } + + private void createInterface() { + this.fillBorder(Material.BLACK_STAINED_GLASS_PANE); + + if (this.saved.isValid()) { + this.setItems(VALID, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.rewards.spawner.formulastatus")) + .description(this.saved.getSpawnBonusEquation()) + .icon(Material.GREEN_CONCRETE) + .build(), 10); + } else { + this.setItems(INVALID, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.rewards.spawner.formulaneeded")) + .description(this.getUser().getTranslation("upgrades.rewards.spawner.formulaneededdesc")) + .icon(Material.RED_CONCRETE) + .build(), 10); + } + + this.setItems(RULE, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.rewards.spawner.setformula")) + .description(this.saved.getSpawnBonusEquation()) + .icon(Material.PAPER) + .clickHandler(this.onSetRule()) + .build(), 22); + } + + private PanelItem.ClickHandler onSetRule() { + return (panel, client, click, slot) -> { + this.getAddon() + .getChatInput() + .askOneInput(this.doSetRule(), + input -> { + try { + Map vars = new TreeMap<>(); + vars.put(LEVEL_VAR, 1.0); + vars.put(ISLAND_LEVEL_VAR, 1.0); + vars.put(NUMBER_PLAYER_VAR, 1.0); + Settings.evaluate(input, vars); + return true; + } catch (Exception e) { + return false; + } + }, + client.getTranslation("upgrades.rewards.spawner.rulequestion", + "[actual]", this.saved.getSpawnBonusEquation()), + client.getTranslation("upgrades.rewards.spawner.invalidrule"), + client, false); + return true; + }; + } + + private Consumer doSetRule() { + return (rule) -> { + this.saved.setSpawnBonusEquation(rule); + this.createInterface(); + this.getBuild().build(); + }; + } + } +} diff --git a/src/main/java/world/bentobox/upgrades/dataobjects/rewards/SpawnerRewardDB.java b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/SpawnerRewardDB.java new file mode 100644 index 0000000..3854311 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/dataobjects/rewards/SpawnerRewardDB.java @@ -0,0 +1,28 @@ +package world.bentobox.upgrades.dataobjects.rewards; + +import com.google.gson.annotations.Expose; + +public class SpawnerRewardDB extends RewardDB { + + @Expose + private String spawnBonusEquation = "0"; + + public String getSpawnBonusEquation() { + return spawnBonusEquation; + } + + public void setSpawnBonusEquation(String spawnBonusEquation) { + this.spawnBonusEquation = spawnBonusEquation; + } + + @Override + public boolean isValid() { + return spawnBonusEquation != null && !spawnBonusEquation.isEmpty(); + } + + @Override + public Class getRewardType() { + return SpawnerReward.class; + } + +} diff --git a/src/main/java/world/bentobox/upgrades/listeners/BonusCalculator.java b/src/main/java/world/bentobox/upgrades/listeners/BonusCalculator.java new file mode 100644 index 0000000..f4d9aa7 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/listeners/BonusCalculator.java @@ -0,0 +1,82 @@ +package world.bentobox.upgrades.listeners; + +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Function; + +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.api.UpgradeAPI; +import world.bentobox.upgrades.config.Settings; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.dataobjects.UpgradesData; +import world.bentobox.upgrades.dataobjects.rewards.Reward; +import world.bentobox.upgrades.dataobjects.rewards.RewardDB; +import world.bentobox.upgrades.upgrades.DatabaseUpgrade; + +final class BonusCalculator { + + private BonusCalculator() { + } + + /** + * Sum the per-reward equation across every purchased tier on the island + * whose rewards match {@code rewardType}. Malformed formulas are skipped. + */ + static double sum(UpgradesAddon addon, Island island, + Class rewardType, + Function equationExtractor) { + double total = 0.0; + long islandLevel = addon.getUpgradesManager().getIslandLevel(island); + int memberCount = island.getMemberSet().size(); + UpgradesData data = addon.getUpgradesLevels(island.getUniqueId()); + + for (UpgradeAPI upgradeAPI : addon.getAvailableUpgrades()) { + if (!(upgradeAPI instanceof DatabaseUpgrade dbUpgrade)) continue; + + int currentLevel = data.getUpgradeLevel(dbUpgrade.getName()); + if (currentLevel <= 0) continue; + + UpgradeTier activeTier = findActiveTier(addon, dbUpgrade, currentLevel); + if (activeTier == null) continue; + + total += sumTierRewards(activeTier, rewardType, equationExtractor, + currentLevel, islandLevel, memberCount); + } + return total; + } + + private static UpgradeTier findActiveTier(UpgradesAddon addon, DatabaseUpgrade dbUpgrade, + int currentLevel) { + List tiers = addon.getUpgradeDataManager() + .getUpgradeTierByUpgradeData(dbUpgrade.getUpgradeData()); + for (UpgradeTier tier : tiers) { + if (tier.getStartLevel() <= currentLevel - 1 && currentLevel - 1 <= tier.getEndLevel()) { + return tier; + } + } + return null; + } + + private static double sumTierRewards(UpgradeTier tier, Class rewardType, + Function equationExtractor, + int currentLevel, long islandLevel, + int memberCount) { + Map vars = new TreeMap<>(); + vars.put(Reward.LEVEL_VAR, (double) currentLevel); + vars.put(Reward.ISLAND_LEVEL_VAR, (double) islandLevel); + vars.put(Reward.NUMBER_PLAYER_VAR, (double) memberCount); + + double subtotal = 0.0; + for (RewardDB rewardDB : tier.getRewards()) { + if (!rewardType.isInstance(rewardDB)) continue; + try { + subtotal += Settings.evaluate(equationExtractor.apply(rewardType.cast(rewardDB)), vars); + } catch (Exception ignored) { + // Malformed formula — skip + } + } + return subtotal; + } +} diff --git a/src/main/java/world/bentobox/upgrades/listeners/CropGrowthListener.java b/src/main/java/world/bentobox/upgrades/listeners/CropGrowthListener.java new file mode 100644 index 0000000..ed6566f --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/listeners/CropGrowthListener.java @@ -0,0 +1,59 @@ +package world.bentobox.upgrades.listeners; + +import org.bukkit.Bukkit; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockGrowEvent; + +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.dataobjects.rewards.CropGrowthRewardDB; + +public class CropGrowthListener implements Listener { + + private final UpgradesAddon addon; + + public CropGrowthListener(UpgradesAddon addon) { + this.addon = addon; + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlockGrow(BlockGrowEvent event) { + Block block = event.getBlock(); + if (!isCrop(block)) return; + + Island island = addon.getPlugin().getIslands() + .getIslandAt(block.getLocation()).orElse(null); + if (island == null) return; + + double bonus = computeBonus(island); + if (bonus <= 0) return; + + int guaranteed = (int) bonus; + double chance = bonus - guaranteed; + int extra = guaranteed + (Math.random() < chance ? 1 : 0); + if (extra <= 0) return; + + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> { + for (int i = 0; i < extra; i++) { + block.applyBoneMeal(BlockFace.UP); + } + }); + } + + private boolean isCrop(Block block) { + return switch (block.getType()) { + case WHEAT, CARROTS, POTATOES, BEETROOTS, NETHER_WART, + SWEET_BERRY_BUSH, TORCHFLOWER_CROP, PITCHER_CROP -> true; + default -> false; + }; + } + + private double computeBonus(Island island) { + return BonusCalculator.sum(addon, island, CropGrowthRewardDB.class, + CropGrowthRewardDB::getGrowthBonusEquation); + } +} diff --git a/src/main/java/world/bentobox/upgrades/listeners/IslandChangeListener.java b/src/main/java/world/bentobox/upgrades/listeners/IslandChangeListener.java new file mode 100644 index 0000000..a3906cd --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/listeners/IslandChangeListener.java @@ -0,0 +1,48 @@ +package world.bentobox.upgrades.listeners; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +import world.bentobox.bentobox.api.events.island.IslandDeleteEvent; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; + +/** + * Listener that handles island-related events for the Upgrades addon. + * This listener is responsible for cleaning up upgrade data when islands are deleted. + * + * @author tastybento + * @since 1.0.0 + */ +public class IslandChangeListener implements Listener { + + /** + * Constructs a new IslandChangeListener. + * + * @param addon the Upgrades addon instance + */ + public IslandChangeListener(UpgradesAddon addon) { + this.addon = addon; + } + + /** + * Handles the island deletion event. + * When an island is deleted, this method removes the island's upgrade data + * from the cache and deletes it from the database to prevent orphaned data. + * + * @param e the IslandDeleteEvent containing information about the deleted island + */ + @EventHandler(priority = EventPriority.HIGHEST) + public void onIslandDeleteEvent(IslandDeleteEvent e) { + Island island = e.getIsland(); + this.addon.uncacheIsland(island.getUniqueId(), false); + this.addon.getDatabase().deleteID(island.getUniqueId()); + } + + /** + * The Upgrades addon instance. + */ + private final UpgradesAddon addon; + +} diff --git a/src/main/java/world/bentobox/upgrades/listeners/JoinPermCheckListener.java b/src/main/java/world/bentobox/upgrades/listeners/JoinPermCheckListener.java new file mode 100644 index 0000000..80f7bf0 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/listeners/JoinPermCheckListener.java @@ -0,0 +1,40 @@ +package world.bentobox.upgrades.listeners; + +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.entity.EntityType; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +import world.bentobox.limits.EntityGroup; +import world.bentobox.limits.events.LimitsPermCheckEvent; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.UpgradesManager; + +public class JoinPermCheckListener implements Listener { + + private final UpgradesAddon addon; + private final UpgradesManager m; + + public JoinPermCheckListener(UpgradesAddon addon) { + this.addon = addon; + this.m = this.addon.getUpgradesManager(); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = false) + public void onLimitsPermCheckEvent(LimitsPermCheckEvent e) { + Material block = e.getMaterial(); + EntityType et = e.getEntityType(); + EntityGroup entgroup = e.getEntityGroup(); + World world = e.getPlayer().getWorld(); + + e.setCancelled(( + (block != null && m.getAllBlockLimitsUpgradeTiers(world).containsKey(block)) || + (et != null && m.getAllEntityLimitsUpgradeTiers(world).containsKey(et)) || + (entgroup != null && m.getAllEntityGroupLimitsUpgradeTiers(world).containsKey(entgroup.getName()))) + ); + + } + +} diff --git a/src/main/java/world/bentobox/upgrades/listeners/LimitsPermCheckListener.java b/src/main/java/world/bentobox/upgrades/listeners/LimitsPermCheckListener.java new file mode 100644 index 0000000..f03c4ae --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/listeners/LimitsPermCheckListener.java @@ -0,0 +1,59 @@ +package world.bentobox.upgrades.listeners; + +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.entity.EntityType; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +import world.bentobox.limits.EntityGroup; +import world.bentobox.limits.events.LimitsPermCheckEvent; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.UpgradesManager; + +/** + * Checks perms of players if Limits changes something + */ +public class LimitsPermCheckListener implements Listener { + + private UpgradesAddon addon; + + public LimitsPermCheckListener(UpgradesAddon addon) { + this.addon = addon; + } + + /** + * Limits changed a permission - cancel it if it's being managed by Upgrades + * @param e LimitsPermCheckEvent + */ + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = false) + public void onLimitsPermCheckEvent(LimitsPermCheckEvent e) { + Material block = e.getMaterial(); + EntityType et = e.getEntityType(); + EntityGroup entgroup = e.getEntityGroup(); + World world = e.getPlayer().getWorld(); + + // Cancel the event if this block is handled by Upgrades + if (block != null) { + if (this.addon.getUpgradesManager().getAllBlockLimitsUpgradeTiers(world).containsKey(block)) { + e.setCancelled(true); + } + } + + // Cancel the event if this entity is being covered by Upgrades + if (et != null) { + if (this.addon.getUpgradesManager().getAllEntityLimitsUpgradeTiers(world).containsKey(et)) { + e.setCancelled(true); + } + } + + // Cancel if this Entity Group is handled by Upgrades + if (entgroup != null) { + if (this.addon.getUpgradesManager().getAllEntityGroupLimitsUpgradeTiers(world).containsKey(entgroup.getName())) { + e.setCancelled(true); + } + } + } + +} diff --git a/src/main/java/world/bentobox/upgrades/listeners/SpawnerUpgradeListener.java b/src/main/java/world/bentobox/upgrades/listeners/SpawnerUpgradeListener.java new file mode 100644 index 0000000..dc1c9bb --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/listeners/SpawnerUpgradeListener.java @@ -0,0 +1,51 @@ +package world.bentobox.upgrades.listeners; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.SpawnerSpawnEvent; + +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.dataobjects.rewards.SpawnerRewardDB; + +public class SpawnerUpgradeListener implements Listener { + + private final UpgradesAddon addon; + + public SpawnerUpgradeListener(UpgradesAddon addon) { + this.addon = addon; + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onSpawnerSpawn(SpawnerSpawnEvent event) { + Island island = addon.getPlugin().getIslands() + .getIslandAt(event.getLocation()).orElse(null); + if (island == null) return; + + double bonus = computeBonus(island); + if (bonus <= 0) return; + + int guaranteed = (int) bonus; + double chance = bonus - guaranteed; + int extra = guaranteed + (Math.random() < chance ? 1 : 0); + if (extra <= 0) return; + + EntityType type = event.getEntityType(); + Location loc = event.getLocation(); + // Schedule 1 tick later to avoid SpawnerSpawnEvent recursion + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> { + for (int i = 0; i < extra; i++) { + loc.getWorld().spawnEntity(loc, type); + } + }); + } + + private double computeBonus(Island island) { + return BonusCalculator.sum(addon, island, SpawnerRewardDB.class, + SpawnerRewardDB::getSpawnBonusEquation); + } +} diff --git a/src/main/java/world/bentobox/upgrades/ui/Panel.java b/src/main/java/world/bentobox/upgrades/ui/Panel.java new file mode 100644 index 0000000..ee6f5a2 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/ui/Panel.java @@ -0,0 +1,153 @@ +package world.bentobox.upgrades.ui; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder; +import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord; +import world.bentobox.bentobox.api.panels.TemplatedPanel; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.api.UpgradeAPI; + +/** + * User interface for Upgrades + */ +public class Panel { + + private final UpgradesAddon addon; + private final Island island; + private int page; + + /** + * Start to create a panel for this island + * @param addon Upgrades + * @param island island + */ + public Panel(UpgradesAddon addon, Island island) { + super(); + this.addon = addon; + this.island = island; + this.page = 0; + } + + /** + * Show the GUI to the user + * @param user user + */ + public void showPanel(User user) { + List visible = addon.getAvailableUpgrades().stream() + .peek(u -> u.updateUpgradeValue(user, island)) + .filter(u -> u.isShowed(user, island)) + .collect(Collectors.toList()); + + new TemplatedPanelBuilder() + .user(user) + .world(island.getWorld()) + .template("upgrades_panel", new File(addon.getDataFolder(), "panels")) + .registerTypeBuilder("UPGRADE", (t, s) -> createUpgradeButton(t, s, user, visible)) + .registerTypeBuilder("NEXT", (t, s) -> createNextButton(t, s, user, visible)) + .registerTypeBuilder("PREVIOUS", (t, s) -> createPreviousButton(t, s, user)) + .build(); + } + + private PanelItem createUpgradeButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot, + User user, List visible) { + int upgradesPerPage = slot.amount("UPGRADE"); + int index = slot.slot() + page * upgradesPerPage; + + if (index >= visible.size()) { + return null; + } + + UpgradeAPI upgrade = visible.get(index); + int islandLevel = addon.getUpgradesManager().getIslandLevel(island); + + String ownDescription = upgrade.getOwnDescription(user); + List fullDescription = new ArrayList<>(); + + if (ownDescription != null) { + fullDescription.add(ownDescription); + if (upgrade.getUpgradeValues(user) != null) { + fullDescription.addAll(getDescription(user, upgrade, islandLevel)); + } + } else { + fullDescription.addAll(getDescription(user, upgrade, islandLevel)); + } + + return new PanelItemBuilder() + .name(upgrade.getDisplayName()) + .icon(upgrade.getIcon()) + .description(fullDescription) + .clickHandler(new PanelClick(upgrade, island)) + .build(); + } + + private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot, + User user, List visible) { + if ((page + 1) * slot.amount("UPGRADE") >= visible.size()) { + return null; + } + + return new PanelItemBuilder() + .icon(template.icon()) + .name(template.title()) + .description(template.description()) + .clickHandler((panel, clicker, clickType, slotNumber) -> { + page++; + showPanel(clicker); + return true; + }) + .build(); + } + + private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot, + User user) { + if (page == 0) { + return null; + } + + return new PanelItemBuilder() + .icon(template.icon()) + .name(template.title()) + .description(template.description()) + .clickHandler((panel, clicker, clickType, slotNumber) -> { + page--; + showPanel(clicker); + return true; + }) + .build(); + } + + private List getDescription(User user, UpgradeAPI upgrade, int islandLevel) { + List descrip = new ArrayList<>(); + + if (upgrade.getUpgradeValues(user) == null) + descrip.add(user.getTranslation("upgrades.ui.upgradepanel.maxlevel")); + else { + if (this.addon.isLevelProvided()) { + descrip.add((upgrade.getUpgradeValues(user).getIslandLevel() <= islandLevel ? "§a" : "§c") + + user.getTranslation("upgrades.ui.upgradepanel.islandneed", "[islandlevel]", + Integer.toString(upgrade.getUpgradeValues(user).getIslandLevel()))); + } + + if (this.addon.isVaultProvided()) { + boolean hasMoney = this.addon.getVaultHook().has(user, upgrade.getUpgradeValues(user).getMoneyCost()); + descrip.add((hasMoney ? "§a" : "§c") + user.getTranslation("upgrades.ui.upgradepanel.moneycost", + "[cost]", Integer.toString(upgrade.getUpgradeValues(user).getMoneyCost()))); + } + + if (this.addon.isLevelProvided() && upgrade.getUpgradeValues(user).getIslandLevel() > islandLevel) { + descrip.add("§8" + user.getTranslation("upgrades.ui.upgradepanel.tryreloadlevel")); + } + } + + return descrip; + } + +} diff --git a/src/main/java/world/bentobox/upgrades/ui/PanelAdminItem.java b/src/main/java/world/bentobox/upgrades/ui/PanelAdminItem.java new file mode 100644 index 0000000..f077b30 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/ui/PanelAdminItem.java @@ -0,0 +1,16 @@ +package world.bentobox.upgrades.ui; + +import org.bukkit.Material; + +import world.bentobox.bentobox.api.user.User; + +public interface PanelAdminItem { + + abstract public Material getIcon(); + + abstract public String getName(); + + abstract public String getAdminName(User user); + + abstract public String getAdminDescription(User user); +} diff --git a/src/main/java/world/bentobox/upgrades/ui/PanelClick.java b/src/main/java/world/bentobox/upgrades/ui/PanelClick.java new file mode 100644 index 0000000..60628a7 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/ui/PanelClick.java @@ -0,0 +1,36 @@ +package world.bentobox.upgrades.ui; + +import org.bukkit.event.inventory.ClickType; + +import world.bentobox.bentobox.api.panels.Panel; +import world.bentobox.bentobox.api.panels.PanelItem.ClickHandler; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.api.UpgradeAPI; + +public class PanelClick implements ClickHandler { + + public PanelClick(UpgradeAPI upgrade, Island island) { + this.upgrade = upgrade; + this.island = island; + } + + @Override + @SuppressWarnings("java:S3516") + public boolean onClick(Panel panel, User user, ClickType clickType, int slot) { + // The ClickHandler contract returns true to consume the click event regardless + // of whether the upgrade actually ran. + if (this.upgrade != null + && (this.upgrade.getUpgradeValues(user) != null + || this.upgrade.getOwnDescription(user) != null) + && this.upgrade.canUpgrade(user, this.island)) { + user.closeInventory(); + this.upgrade.doUpgrade(user, this.island); + } + return true; + } + + private UpgradeAPI upgrade; + private Island island; + +} diff --git a/src/main/java/world/bentobox/upgrades/ui/PanelPublicItem.java b/src/main/java/world/bentobox/upgrades/ui/PanelPublicItem.java new file mode 100644 index 0000000..32b410e --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/ui/PanelPublicItem.java @@ -0,0 +1,15 @@ +package world.bentobox.upgrades.ui; + +import org.bukkit.Material; + +import world.bentobox.bentobox.api.user.User; + +public interface PanelPublicItem { + abstract public Material getIcon(); + + abstract public String getName(); + + abstract public String getPublicName(User user); + + abstract public String getPublicDescription(User user); +} diff --git a/src/main/java/world/bentobox/upgrades/ui/admin/AdminList.java b/src/main/java/world/bentobox/upgrades/ui/admin/AdminList.java new file mode 100644 index 0000000..8ebf426 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/ui/admin/AdminList.java @@ -0,0 +1,115 @@ +package world.bentobox.upgrades.ui.admin; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.IntStream; + +import org.bukkit.Material; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.panels.PanelItem.ClickHandler; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.ui.PanelAdminItem; +import world.bentobox.upgrades.ui.utils.AbPanel; + +public final class AdminList extends AbPanel { + + private static final String CREATE = "create"; + + @NonNull + private final List items; + + @Nullable + private final Consumer onLeftClick; + @Nullable + private final Consumer onRightClick; + @Nullable + private Runnable createButton; + + @Nullable + private final String createName; + @Nullable + private final String leftClickDesc; + @Nullable + private final String rightClickDesc; + + public AdminList(UpgradesAddon addon, GameModeAddon gamemode, User user, String title, + AbPanel parent, + @NonNull List items, @Nullable Consumer onLeftClick, + @Nullable Consumer onRightClick, @Nullable Runnable createButton, + @Nullable String createName, @Nullable String leftClickDesc, + @Nullable String rightClickDesc) { + super(addon, gamemode, user, title, parent); + + this.items = items; + + this.onLeftClick = onLeftClick; + this.onRightClick = onRightClick; + this.createButton = createButton; + + this.createName = createName; + this.leftClickDesc = leftClickDesc; + this.rightClickDesc = rightClickDesc; + + this.createInterface(); + } + + private void createInterface() { + this.fillBorder(Material.BLACK_STAINED_GLASS_PANE); + + if (this.createButton != null && this.createName != null) { + this.setItems(CREATE, new PanelItemBuilder().name(this.createName) + .icon(Material.EMERALD) + .clickHandler(this.onClickCreate) + .build(), 4); + } + + IntStream.range(0, this.items.size()) + .forEach(idx -> { + Item item = this.items.get(idx); + int pos = ((idx / 7) * 9) + (idx % 7) + 10; + List desc = new ArrayList<>(); + + if (this.leftClickDesc != null) + desc.add(this.leftClickDesc); + + if (this.rightClickDesc != null) + desc.add(this.rightClickDesc); + + desc.addAll(wrapText(item.getAdminDescription(this.getUser()), LORE_MAX_WIDTH)); + + this.setItems(item.getName() + "-" + idx, + new PanelItemBuilder().name(item.getAdminName(this.getUser())) + .description(desc) + .icon(item.getIcon()) + .clickHandler(this.onClickItem(item)) + .build(), pos); + }); + } + + private final ClickHandler onClickCreate = (panel, client, click, slot) -> { + this.createButton.run(); + return true; + }; + + private ClickHandler onClickItem(Item item) { + return (panel, client, click, slot) -> { + if (click.isLeftClick() && this.onLeftClick != null) { + this.onLeftClick.accept(item); + } else if (click.isRightClick() && this.onRightClick != null) { + this.onRightClick.accept(item); + } else { + return true; + } + + return true; + }; + } + +} diff --git a/src/main/java/world/bentobox/upgrades/ui/admin/AdminPanel.java b/src/main/java/world/bentobox/upgrades/ui/admin/AdminPanel.java new file mode 100644 index 0000000..eb0049a --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/ui/admin/AdminPanel.java @@ -0,0 +1,149 @@ +package world.bentobox.upgrades.ui.admin; + +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.IntStream; + +import org.bukkit.Material; +import org.bukkit.event.inventory.ClickType; + +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.panels.Panel; +import world.bentobox.bentobox.api.panels.PanelItem.ClickHandler; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.dataobjects.UpgradeData; +import world.bentobox.upgrades.ui.utils.AbPanel; + +/** + * The admin panel for managing DB-backed upgrades. + * Opens directly as a list view — no intermediate screen. + * Left-click an upgrade to edit it; right-click to delete it. + * An "Add upgrade" button is always visible. + */ +public class AdminPanel extends AbPanel { + + private static final String ADD = "add"; + private static final String EMPTY = "empty"; + + public AdminPanel(UpgradesAddon addon, GameModeAddon gamemode, User user) { + this(addon, gamemode, user, null); + } + + public AdminPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, AbPanel parent) { + super(addon, gamemode, user, user.getTranslation("upgrades.ui.titles.adminupgrade"), parent); + this.fillBorder(Material.BLACK_STAINED_GLASS_PANE); + // Content is built in onBuildHook, called by getBuild() + } + + /** + * Called every time getBuild() is invoked. Clears stale items and rebuilds + * from the current DB state so returning from EditUpgradePanel always shows + * fresh data. + */ + @Override + protected void onBuildHook() { + this.clearItems(); + this.setupNavigationButton(); + this.createInterface(); + } + + private void createInterface() { + this.setItems(ADD, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.ui.buttons.addupgrade")) + .icon(Material.ANVIL) + .clickHandler(this.onAdd) + .build(), 4); + + List upgrades = this.getAddon().getUpgradeDataManager() + .getUpgradeDataByGameMode(this.getGamemode().getDescription().getName()); + + if (upgrades.isEmpty()) { + this.setItems(EMPTY, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.ui.buttons.noupgrades")) + .icon(Material.BARRIER) + .build(), 22); + } else { + IntStream.range(0, upgrades.size()).forEach(idx -> { + UpgradeData upgrade = upgrades.get(idx); + int pos = ((idx / 7) * 9) + (idx % 7) + 10; + this.setItems(upgrade.getUniqueId(), new PanelItemBuilder() + .name(upgrade.getName()) + .icon(upgrade.getIcon()) + .description( + this.getUser().getTranslation("upgrades.ui.buttons.leftclickedit"), + this.getUser().getTranslation("upgrades.ui.buttons.rightclickdelete")) + .clickHandler(this.onClickUpgrade(upgrade)) + .build(), pos); + }); + } + } + + private final ClickHandler onAdd = (Panel panel, User client, ClickType click, int slot) -> { + this.getAddon().getChatInput().askOneInput(this.addUpgrade, + input -> { + String uniqueId = this.getGamemode().getDescription().getName() + "_" + input; + return !this.getAddon().getUpgradeDataManager().hasUpgradeData(uniqueId); + }, + client.getTranslation("upgrades.chatinput.admin.question.getupgradeid"), + client.getTranslation("upgrades.chatinput.admin.invalid.getupgradeid"), + client, true); + return true; + }; + + private final Consumer addUpgrade = input -> { + if (input == null) { + // Conversation was cancelled or escaped — just reopen the panel + this.getBuild().build(); + return; + } + + String uniqueId = this.getGamemode().getDescription().getName() + "_" + input; + + UpgradeData newUpgrade = this.getAddon().getUpgradeDataManager() + .createUpgradeData(uniqueId, this.getGamemode().getDescription().getName(), this.getUser()); + + if (newUpgrade == null) { + this.getUser().sendMessage("upgrades.error.unknownerror"); + this.getAddon().logError("Couldn't create the upgradeData with id " + uniqueId); + return; + } + + // Use the user-typed name as the display name (#70 item 1) + newUpgrade.setName(input); + this.getAddon().getUpgradeDataManager().saveUpgradeData(newUpgrade); + + // Register the new upgrade in the player-facing shop immediately. + // Without this call the upgrade lives in the DB but is never added to + // addon.upgrade, so /is upgrade shows nothing for it. + this.getAddon().refreshDatabaseUpgrades(); + + new EditUpgradePanel(this.getAddon(), this.getGamemode(), this.getUser(), newUpgrade, this) + .getBuild().build(); + }; + + private ClickHandler onClickUpgrade(UpgradeData upgrade) { + return (Panel panel, User client, ClickType click, int slot) -> { + if (click.isLeftClick()) { + new EditUpgradePanel(this.getAddon(), this.getGamemode(), client, upgrade, this) + .getBuild().build(); + } else if (click.isRightClick()) { + new YesNoPanel(this.getAddon(), + this.getGamemode(), client, + client.getTranslation("upgrades.ui.titles.delete", + "[name]", upgrade.getName()), + this, delete -> { + if (delete) { + this.getAddon().getUpgradeDataManager().deleteUpgradeData(upgrade); + this.getAddon().refreshDatabaseUpgrades(); + } + // getBuild() triggers onBuildHook() which refreshes the list + this.getBuild().build(); + }).getBuild().build(); + } + return true; + }; + } + +} diff --git a/src/main/java/world/bentobox/upgrades/ui/admin/EditTierPanel.java b/src/main/java/world/bentobox/upgrades/ui/admin/EditTierPanel.java new file mode 100644 index 0000000..5f707e8 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/ui/admin/EditTierPanel.java @@ -0,0 +1,387 @@ +package world.bentobox.upgrades.ui.admin; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.panels.PanelItem.ClickHandler; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.dataobjects.prices.Price; +import world.bentobox.upgrades.dataobjects.prices.PriceDB; +import world.bentobox.upgrades.dataobjects.rewards.Reward; +import world.bentobox.upgrades.dataobjects.rewards.RewardDB; +import world.bentobox.upgrades.ui.utils.AbPanel; + +public final class EditTierPanel extends AbPanel { + + private static final String NAME = "name"; + private static final String DESCRIPTION = "description"; + private static final String ICON = "icon"; + private static final String NB_LEVEL = "nblevel"; + private static final String ORDER = "order"; + private static final String PRICES = "prices"; + private static final String REWARDS = "rewards"; + + private UpgradeTier tier; + private List tiers; + private List tiersLengths; + + public EditTierPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, UpgradeTier tier, + List tiers, AbPanel parent) { + super(addon, gamemode, user, tier.getUniqueId(), parent); + + this.tier = tier; + this.tiers = tiers; + this.tiersLengths = this.computeTierLength(this.tiers); + + this.fillBorder(Material.BLACK_STAINED_GLASS_PANE); + this.setButton(); + } + + private void setButton() { + + this.setItems(NAME, + new PanelItemBuilder().name(this.tier.getName()) + .description(this.getUser() + .getTranslation("upgrades.ui.edittierpanel.namedesc")) + .icon(Material.NAME_TAG) + .clickHandler(this.onSetName) + .build(), + 12); + + this.setItems(DESCRIPTION, + new PanelItemBuilder().name( + this.getUser() + .getTranslation("upgrades.ui.edittierpanel.description")) + .description(wrapLore(this.tier.getDescription())) + .icon(Material.WRITTEN_BOOK) + .clickHandler(this.onSetDescription) + .build(), + 13); + + this.setItems(ICON, + new PanelItemBuilder().name( + this.getUser() + .getTranslation("upgrades.ui.edittierpanel.icon")) + .description(this.getUser() + .getTranslation("upgrades.ui.edittierpanel.icondesc")) + .icon(this.tier.getIcon()) + .clickHandler(this.onSetIcon) + .build(), + 14); + + this.setItems(NB_LEVEL, + new PanelItemBuilder().name( + this.getUser() + .getTranslation("upgrades.ui.edittierpanel.nblevel")) + .description( + this.getUser() + .getTranslation("upgrades.ui.edittierpanel.nbleveldesc", + "[actual]", + Integer.toString( + this.tier.getEndLevel() - this.tier.getStartLevel() + 1))) + .icon(Material.EXPERIENCE_BOTTLE) + .clickHandler(this.onSetNbLevel) + .build(), + 21); + + this.setItems(ORDER, + new PanelItemBuilder().name( + this.getUser() + .getTranslation("upgrades.ui.edittierpanel.order")) + .description(this.getUser() + .getTranslation("upgrades.ui.edittierpanel.orderdesc", + "[actual]", + Integer.toString(this.tiers.indexOf(this.tier) + 1), "[max]", + Integer.toString(this.tiers.size()))) + .icon(Material.OAK_SIGN) + .clickHandler(this.onSetOrder) + .build(), + 23); + + this.setItems(PRICES, + new PanelItemBuilder().name( + this.getUser() + .getTranslation("upgrades.ui.edittierpanel.prices")) + .icon(Material.GOLD_NUGGET) + .clickHandler(this.onSetPrices) + .build(), + 30); + + this.setItems(REWARDS, + new PanelItemBuilder().name( + this.getUser() + .getTranslation("upgrades.ui.edittierpanel.rewards")) + .icon(Material.DIAMOND_BLOCK) + .clickHandler(this.onSetRewards) + .build(), + 32); + + } + + private final ClickHandler onSetName = (panel, client, click, slot) -> { + this.getAddon() + .getChatInput() + .askOneInput(this.doSetName, input -> true, this.getUser() + .getTranslation("upgrades.chatinput.admin.question.getupgradetiername", "[name]", + this.tier.getName()), + "You're not supposed to fail that...", this.getUser(), false); + return true; + }; + + private final Consumer doSetName = (name) -> { + this.tier.setName(name); + this.setButton(); + this.getBuild() + .build(); + }; + + private final ClickHandler onSetDescription = (panel, client, click, slot) -> { + this.getAddon() + .getChatInput() + .askMultiLine(this.doSetDescription, input -> true, + this.getUser() + .getTranslation("upgrades.chatinput.admin.question.getupgradetierdesc", + "[end]", + this.getAddon() + .getSettings() + .getChatInputEscape()), + "You're not supposed to fail that...", this.getUser()); + return true; + }; + + private final Consumer> doSetDescription = (description) -> { + if (description == null) + return; + this.tier.setDescription(description); + this.setButton(); + this.getBuild() + .build(); + }; + + private final ClickHandler onSetIcon = (panel, client, click, slot) -> { + ItemStack inHand = Objects.requireNonNull(client.getInventory()) + .getItemInMainHand(); + + if (BADICON.contains(inHand.getType())) { + client.sendMessage("upgrades.error.noiteminhand"); + client.closeInventory(); + return true; + } + this.tier.setIcon(new ItemStack(inHand.getType())); + this.setButton(); + this.getBuild() + .build(); + return true; + }; + + private final ClickHandler onSetNbLevel = (panel, client, click, slot) -> { + int index = this.tiers.indexOf(this.tier); + int actual = this.tiersLengths.get(index); + + if (click.isLeftClick() && actual > 1) { + this.tiersLengths.set(index, actual - 1); + } else if (click.isRightClick()) { + this.tiersLengths.set(index, actual + 1); + } else + return true; + + this.updateTierLevel(this.tiers, this.tiersLengths); + this.setButton(); + this.getBuild() + .build(); + return true; + }; + + private final ClickHandler onSetOrder = (panel, client, click, slot) -> { + int index = this.tiers.indexOf(this.tier); + int length = this.tiersLengths.get(index); + int newIndex = index; + + if (click.isLeftClick() && index > 0) + newIndex--; + else if (click.isRightClick() && index < this.tiers.size() - 1) + newIndex++; + else + return true; + + this.tiersLengths.set(index, this.tiersLengths.get(newIndex)); + this.tiers.set(index, this.tiers.get(newIndex)); + this.tiersLengths.set(newIndex, length); + this.tiers.set(newIndex, this.tier); + this.updateTierLevel(this.tiers, this.tiersLengths); + this.setButton(); + this.getBuild() + .build(); + return true; + }; + + private final ClickHandler onSetPrices = (panel, client, click, slot) -> { + String title = client.getTranslation("upgrades.ui.listadmintierpricepanel.title"); + String create = client.getTranslation("upgrades.ui.listadmintierpricepanel.create"); + String leftDesc = client.getTranslation("upgrades.ui.listadmintierpricepanel.leftdesc"); + String rightDesc = client.getTranslation("upgrades.ui.listadmintierpricepanel.rightdesc"); + List prices = this.tier.getPrices() + .stream() + .map((PriceDB p) -> + this.getAddon() + .getUpgradesManager() + .searchPrice(p.getPriceType()) + ) + .collect(Collectors.toList()); + + new AdminList<>(this.getAddon(), this.getGamemode(), client, + title, this, prices, + this.onSelectPrice, this.onDeletePrice, this.onCreatePrice, + create, leftDesc, rightDesc).getBuild() + .build(); + return true; + }; + + private final ClickHandler onSetRewards = (panel, client, click, slot) -> { + String title = client.getTranslation("upgrades.ui.listadmintierrewardpanel.title"); + String create = client.getTranslation("upgrades.ui.listadmintierrewardpanel.create"); + String leftDesc = client.getTranslation("upgrades.ui.listadmintierrewardpanel.leftdesc"); + String rightDesc = client.getTranslation("upgrades.ui.listadmintierrewardpanel.rightdesc"); + List rewards = this.tier.getRewards() + .stream() + .map((RewardDB reward) -> + this.getAddon() + .getUpgradesManager() + .searchReward(reward.getRewardType()) + ) + .collect(Collectors.toList()); + + new AdminList<>(this.getAddon(), this.getGamemode(), client, title, this, rewards, + this.onSelectReward, this.onDeleteReward, + this.onCreateReward, create, leftDesc, rightDesc).getBuild() + .build(); + return true; + }; + + private final Consumer onSelectPrice = (price) -> { + PriceDB selected = this.tier.getPrices() + .stream() + .filter((p) -> p.getPriceType() == price.getClass()) + .findFirst() + .orElse(null); + + price.getAdminPanel(this.getAddon(), this.getGamemode(), this.getUser(), this, this.tier, + selected) + .getBuild() + .build(); + }; + + private final Consumer onSelectReward = (reward) -> { + RewardDB selected = this.tier.getRewards() + .stream() + .filter((r) -> r.getRewardType() == reward.getClass()) + .findFirst() + .orElse(null); + + reward.getAdminPanel(this.getAddon(), this.getGamemode(), this.getUser(), this, this.tier, + selected) + .getBuild() + .build(); + }; + + private final Consumer onDeletePrice = (price) -> + new YesNoPanel(this.getAddon(), this.getGamemode(), this.getUser(), + this.getUser() + .getTranslation("upgrades.ui.titles.delete"), this, delete -> { + if (delete) { + List prices = this.tier.getPrices(); + prices = prices.stream() + .filter(p -> p.getPriceType() != price.getClass()) + .collect( + Collectors.toList()); + this.tier.setPrices(prices); + } + this.getBuild() + .build(); + }).getBuild().build(); + + private final Consumer onDeleteReward = (reward) -> + new YesNoPanel(this.getAddon(), this.getGamemode(), this.getUser(), this.getUser() + .getTranslation("upgrades.ui.titles.delete"), this, delete -> { + if (delete) { + List rewards = this.tier.getRewards(); + rewards = rewards.stream() + .filter(r -> r.getRewardType() != reward.getClass()) + .collect( + Collectors.toList()); + this.tier.setRewards(rewards); + } + this.getBuild() + .build(); + }).getBuild().build(); + + private final Runnable onCreatePrice = () -> { + String title = this.getUser() + .getTranslation("upgrades.ui.listadminpricepanel.title"); + List prices = this.getAddon() + .getUpgradesManager() + .getPrices(); + + new AdminList<>(this.getAddon(), this.getGamemode(), this.getUser(), title, this, prices, + this.onSelectNewPrice, null, null, null, null, null) + .getBuild() + .build(); + }; + + private final Runnable onCreateReward = () -> { + String title = this.getUser() + .getTranslation("upgrades.ui.listadminrewardpanel.title"); + List rewards = this.getAddon() + .getUpgradesManager() + .getRewards(); + + new AdminList<>(this.getAddon(), this.getGamemode(), this.getUser(), title, this, rewards, + this.onSelectNewReward, null, null, null, null, null).getBuild() + .build(); + }; + + private final Consumer onSelectNewPrice = (price) -> + price.getAdminPanel(this.getAddon(), this.getGamemode(), this.getUser(), this, this.tier, + null) + .getBuild() + .build(); + + private final Consumer onSelectNewReward = (reward) -> + reward.getAdminPanel(this.getAddon(), this.getGamemode(), this.getUser(), this, this.tier, + null) + .getBuild() + .build(); + + private List computeTierLength(List tiers) { + List lengths = new ArrayList<>(); + + tiers.forEach(tier -> + lengths.add(tier.getEndLevel() - tier.getStartLevel() + 1) + ); + + return lengths; + } + + private void updateTierLevel(List tiers, List lengths) { + int level = 0; + + for (int i = 0; i < tiers.size(); i++) { + tiers.get(i) + .setStartLevel(level); + level += lengths.get(i); + tiers.get(i) + .setEndLevel(level - 1); + } + } + +} diff --git a/src/main/java/world/bentobox/upgrades/ui/admin/EditUpgradePanel.java b/src/main/java/world/bentobox/upgrades/ui/admin/EditUpgradePanel.java new file mode 100644 index 0000000..cec63d1 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/ui/admin/EditUpgradePanel.java @@ -0,0 +1,260 @@ +package world.bentobox.upgrades.ui.admin; + +import java.util.List; +import java.util.function.Consumer; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.panels.PanelItem.ClickHandler; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.dataobjects.UpgradeData; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.ui.utils.AbPanel; + +public class EditUpgradePanel extends AbPanel { + + protected static final String ACTIVE = "active"; + protected static final String DESCRIPTION = "description"; + protected static final String ICON = "icon"; + protected static final String NAME = "name"; + protected static final String ORDER = "order"; + protected static final String TIERADD = "tieradd"; + protected static final String TIEREDIT = "tieredit"; + protected static final String TIERDELETE = "tierdelete"; + + private UpgradeData upgrade; + + public EditUpgradePanel(UpgradesAddon addon, GameModeAddon gamemode, User user, UpgradeData upgrade, AbPanel parent) { + super(addon, gamemode, user, upgrade.getName(), parent); + this.upgrade = upgrade; + + this.fillBorder(Material.BLACK_STAINED_GLASS_PANE); + this.setButton(); + } + + private void setButton() { + + if (this.upgrade.isActive()) { + this.setItems(ACTIVE, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.ui.editupgradepanel.active")) + .description(this.getUser().getTranslation("upgrades.ui.editupgradepanel.activedesc")) + .icon(Material.GREEN_CONCRETE) + .clickHandler(this.onActive) + .build(), 10); + } else { + this.setItems(ACTIVE, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.ui.editupgradepanel.inactive")) + .description(this.getUser().getTranslation("upgrades.ui.editupgradepanel.inactivedesc")) + .icon(Material.RED_CONCRETE) + .clickHandler(this.onActive) + .build(), 10); + } + + this.setItems(NAME, new PanelItemBuilder() + .name(this.upgrade.getName()) + .description(this.getUser().getTranslation("upgrades.ui.editupgradepanel.namedesc")) + .icon(Material.NAME_TAG) + .clickHandler(this.onSetName) + .build(), 12); + + this.setItems(DESCRIPTION, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.ui.editupgradepanel.description")) + .description(wrapLore(this.upgrade.getDescription())) + .icon(Material.WRITTEN_BOOK) + .clickHandler(this.onSetDescription) + .build(), 21); + + this.setItems(ICON, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.ui.editupgradepanel.icon")) + .description(this.getUser().getTranslation("upgrades.ui.editupgradepanel.icondesc")) + .icon(this.upgrade.getIcon()) + .clickHandler(this.onSetIcon) + .build(), 30); + + boolean hasTiers = !this.getAddon().getUpgradeDataManager() + .getUpgradeTierByUpgradeData(this.upgrade).isEmpty(); + + this.setItems(TIERADD, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.ui.editupgradepanel.tieradd")) + .description(hasTiers ? "" : + this.getUser().getTranslation("upgrades.ui.editupgradepanel.tierrequired")) + .icon(Material.ANVIL) + .clickHandler(this.onTierAdd) + .build(), 14); + + if (hasTiers) { + this.setItems(TIEREDIT, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.ui.editupgradepanel.tieredit")) + .icon(Material.WRITABLE_BOOK) + .clickHandler(this.onTierEdit) + .build(), 23); + + this.setItems(TIERDELETE, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.ui.editupgradepanel.tierdelete")) + .icon(Material.LAVA_BUCKET) + .clickHandler(this.onTierDelete) + .build(), 32); + } + + this.setItems(ORDER, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.ui.editupgradepanel.order", + "[order]", Integer.toString(this.upgrade.getOrder()))) + .description(this.getUser().getTranslation("upgrades.ui.editupgradepanel.orderdesc")) + .icon(Material.OAK_SIGN) + .clickHandler(this.onSetOrder) + .build(), 16); + } + + private ClickHandler onActive = (panel, client, click, slot) -> { + this.upgrade.setActive(!this.upgrade.isActive()); + // Persist the change and update the player-facing shop (#71) + this.getAddon().getUpgradeDataManager().saveUpgradeData(this.upgrade); + this.getAddon().refreshDatabaseUpgrades(); + this.setButton(); + this.getBuild().build(); + return true; + }; + + private ClickHandler onSetName = (panel, client, click, slot) -> { + this.getAddon().getChatInput().askOneInput(this.doSetName, + input -> true, + this.getUser().getTranslation("upgrades.chatinput.admin.question.getupgradedataname", + "[name]", this.upgrade.getName()), + "You're not supposed to fail that...", + this.getUser(), false); + return true; + }; + + private Consumer doSetName = (name) -> { + this.upgrade.setName(name); + this.setButton(); + this.getBuild().build(); + }; + + private ClickHandler onSetDescription = (panel, client, click, slot) -> { + this.getAddon().getChatInput().askMultiLine(this.doSetDescription, + input -> true, + this.getUser().getTranslation("upgrades.chatinput.admin.question.getupgradedatadesc", + "[end]", this.getAddon().getSettings().getChatInputEscape()), + "You're not supposed to fail that...", + this.getUser()); + return true; + }; + + private Consumer> doSetDescription = (descrip) -> { + if (descrip == null) + return; + this.upgrade.setDescription(descrip); + this.setButton(); + this.getBuild().build(); + }; + + private ClickHandler onSetIcon = (panel, client, click, slot) -> { + ItemStack inHand = client.getInventory().getItemInMainHand(); + + if (inHand == null || BADICON.contains(inHand.getType())) { + client.sendMessage("upgrades.error.noiteminhand"); + client.closeInventory(); + return true; + } + upgrade.setIcon(new ItemStack(inHand.getType())); + this.setButton(); + this.getBuild().build(); + return true; + }; + + private ClickHandler onTierAdd = (panel, client, click, slot) -> { + this.getAddon().getChatInput().askOneInput(this.doTierAdd, + input -> { + String uniqueId = this.getGamemode().getDescription().getName() + "_" + input; + return !this.getAddon().getUpgradeDataManager().hasUpgradeTier(uniqueId); + }, + client.getTranslation("upgrades.chatinput.admin.question.gettierid"), + client.getTranslation("upgrades.chatinput.admin.invalid.gettierid"), + client, true); + return true; + }; + + private Consumer doTierAdd = input -> { + String uniqueId = this.getGamemode().getDescription().getName() + "_" + input; + List tiers = this.getAddon().getUpgradeDataManager().getUpgradeTierByUpgradeData(this.upgrade); + int lastpos = tiers.size() > 0 ? tiers.get(tiers.size() - 1).getEndLevel() + 1 : 0; + + UpgradeTier newTier = this.getAddon().getUpgradeDataManager() + .createUpgradeTier(uniqueId, this.upgrade, lastpos, lastpos, this.getUser()); + + if (newTier == null) { + this.getUser().sendMessage("upgrades.error.unknownerror"); + this.getAddon().logError("Couldn't create the upgradeTier with id " + uniqueId); + return; + } + + // Use the user-typed name as display name (#70 item 5) + newTier.setName(input); + this.getAddon().getUpgradeDataManager().saveUpgradeTier(newTier); + + new EditTierPanel(this.getAddon(), + this.getGamemode(), this.getUser(), newTier, + this.getAddon().getUpgradeDataManager().getUpgradeTierByUpgradeData(this.upgrade), + this) + .getBuild().build(); + }; + + private ClickHandler onTierEdit = (panel, client, click, slot) -> { + new ListUpgradeTierPanel(this.getAddon(), + this.getGamemode(), client, this.upgrade, + client.getTranslation("upgrades.ui.titles.editlist"), + this, + tier -> { + new EditTierPanel(this.getAddon(), + this.getGamemode(), client, tier, + this.getAddon().getUpgradeDataManager().getUpgradeTierByUpgradeData(this.upgrade), + this) + .getBuild().build(); + }) + .getBuild().build(); + return true; + }; + + private ClickHandler onTierDelete = (panel, client, click, slot) -> { + new ListUpgradeTierPanel(this.getAddon(), + this.getGamemode(), client, this.upgrade, + client.getTranslation("upgrades.ui.titles.deletelist"), + this, this.doTierDelete) + .getBuild().build(); + return true; + }; + + private Consumer doTierDelete = tier -> { + new YesNoPanel(this.getAddon(), + this.getGamemode(), this.getUser(), + this.getUser().getTranslation("upgrades.ui.titles.delete", + "[name]", tier.getUniqueId()), + this, delete -> { + if (delete) { + this.getAddon().getUpgradeDataManager().deleteUpgradeTier(tier); + } + this.getBuild().build(); + }).getBuild().build(); + }; + + private ClickHandler onSetOrder = (panel, client, click, slot) -> { + this.getAddon().getChatInput().askOneNumber(this.doSetOrder, + input -> input.intValue() > -1, + this.getUser().getTranslation("upgrades.chatinput.admin.question.getupgradedataorder"), + this.getUser().getTranslation("upgrades.chatinput.admin.invalid.getupgradedataorder"), + this.getUser()); + return true; + }; + + private Consumer doSetOrder = (order) -> { + this.upgrade.setOrder(order.intValue()); + this.setButton(); + this.getBuild().build(); + }; + +} diff --git a/src/main/java/world/bentobox/upgrades/ui/admin/ListUpgradeDataPanel.java b/src/main/java/world/bentobox/upgrades/ui/admin/ListUpgradeDataPanel.java new file mode 100644 index 0000000..8f7387b --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/ui/admin/ListUpgradeDataPanel.java @@ -0,0 +1,55 @@ +package world.bentobox.upgrades.ui.admin; + +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.IntStream; + +import org.bukkit.Material; +import org.bukkit.event.inventory.ClickType; + +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.panels.Panel; +import world.bentobox.bentobox.api.panels.PanelItem.ClickHandler; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.dataobjects.UpgradeData; +import world.bentobox.upgrades.ui.utils.AbPanel; + +public class ListUpgradeDataPanel extends AbPanel { + + private Consumer consumer; + private List upgrades; + + public ListUpgradeDataPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, String title, AbPanel parent, Consumer consumer) { + super(addon, gamemode, user, title, parent); + + this.consumer = consumer; + + this.upgrades = this.getAddon().getUpgradeDataManager().getUpgradeDataByGameMode(this.getGamemode().getDescription().getName()); + + this.fillBorder(Material.BLACK_STAINED_GLASS_PANE); + this.setUpgrades(); + } + + private void setUpgrades() { + IntStream.range(0, this.upgrades.size()) + .forEach(idx -> { + UpgradeData upgrade = this.upgrades.get(idx); + // (idx / 7) = row (idx % 7) = column + int pos = ((idx / 7) * 9) + (idx % 7) + 10; + this.setItems(upgrade.getUniqueId(), new PanelItemBuilder() + .name(upgrade.getName()) + .icon(upgrade.getIcon()) + .clickHandler(this.onClick(upgrade)) + .build(), pos); + }); + } + + private ClickHandler onClick(UpgradeData upgrade) { + return (Panel panel, User client, ClickType click, int slot) -> { + consumer.accept(upgrade); + return true; + }; + } +} diff --git a/src/main/java/world/bentobox/upgrades/ui/admin/ListUpgradeTierPanel.java b/src/main/java/world/bentobox/upgrades/ui/admin/ListUpgradeTierPanel.java new file mode 100644 index 0000000..b6f1cfa --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/ui/admin/ListUpgradeTierPanel.java @@ -0,0 +1,57 @@ +package world.bentobox.upgrades.ui.admin; + +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.IntStream; + +import org.bukkit.Material; + +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.panels.PanelItem.ClickHandler; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.dataobjects.UpgradeData; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.ui.utils.AbPanel; + +public class ListUpgradeTierPanel extends AbPanel { + + private UpgradeData upgrade; + private List tiers; + + private Consumer consumer; + + public ListUpgradeTierPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, UpgradeData upgrade, String title, AbPanel parent, Consumer consumer) { + super(addon, gamemode, user, title, parent); + + this.upgrade = upgrade; + this.consumer = consumer; + + this.tiers = this.getAddon().getUpgradeDataManager().getUpgradeTierByUpgradeData(this.upgrade); + this.createInterface(); + } + + private void createInterface() { + this.fillBorder(Material.BLACK_STAINED_GLASS_PANE); + + IntStream.range(0, this.tiers.size()) + .forEach(idx -> { + UpgradeTier tier = this.tiers.get(idx); + int pos = ((idx / 7) * 9) + (idx % 7) + 10; + this.setItems(tier.getUniqueId(), new PanelItemBuilder() + .name(tier.getName()) + .icon(tier.getIcon()) + .clickHandler(this.onClickEdit(tier)) + .build(), pos); + }); + } + + private ClickHandler onClickEdit(UpgradeTier tier) { + return (panel, client, click, slot) -> { + this.consumer.accept(tier); + return true; + }; + } + +} diff --git a/src/main/java/world/bentobox/upgrades/ui/admin/YesNoPanel.java b/src/main/java/world/bentobox/upgrades/ui/admin/YesNoPanel.java new file mode 100644 index 0000000..26afdb9 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/ui/admin/YesNoPanel.java @@ -0,0 +1,57 @@ +package world.bentobox.upgrades.ui.admin; + +import java.util.function.Consumer; + +import org.bukkit.Material; +import org.bukkit.event.inventory.ClickType; + +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.panels.Panel; +import world.bentobox.bentobox.api.panels.PanelItem.ClickHandler; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.ui.utils.AbPanel; + +public class YesNoPanel extends AbPanel { + + protected static final String YES = "yes"; + protected static final String NO = "no"; + + private Consumer consumer; + + public YesNoPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, String title, AbPanel parent, Consumer consumer) { + super(addon, gamemode, user, title, parent); + + this.consumer = consumer; + + this.fillBorder(Material.RED_STAINED_GLASS_PANE); + this.setButton(); + } + + private void setButton() { + + this.setItems(YES, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.ui.buttons.yesbutton")) + .icon(Material.GREEN_STAINED_GLASS_PANE) + .clickHandler(this.onYes) + .build(), 21); + + this.setItems(NO, new PanelItemBuilder() + .name(this.getUser().getTranslation("upgrades.ui.buttons.nobutton")) + .icon(Material.RED_STAINED_GLASS_PANE) + .clickHandler(this.onNo) + .build(), 23); + } + + private ClickHandler onYes = (Panel panel, User client, ClickType click, int slot) -> { + this.consumer.accept(true); + return true; + }; + + private ClickHandler onNo = (Panel panel, User client, ClickType click, int slot) -> { + this.consumer.accept(false); + return true; + }; + +} diff --git a/src/main/java/world/bentobox/upgrades/ui/utils/AbPanel.java b/src/main/java/world/bentobox/upgrades/ui/utils/AbPanel.java new file mode 100644 index 0000000..7e18979 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/ui/utils/AbPanel.java @@ -0,0 +1,245 @@ +package world.bentobox.upgrades.ui.utils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.bukkit.Material; + +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelBuilder; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.upgrades.UpgradesAddon; + +public class AbPanel { + + protected static final String RETURN = "return"; + protected static final String EXIT = "exit"; + + /** Maximum characters per lore line before word-wrapping. */ + protected static final int LORE_MAX_WIDTH = 35; + + protected static final Set BADICON = new HashSet(Arrays.asList( + Material.AIR, + Material.CAVE_AIR, + Material.VOID_AIR + )); + + public AbPanel(UpgradesAddon addon, GameModeAddon gamemode, User user, String title, AbPanel parent) { + this.addon = addon; + this.gamemode = gamemode; + this.user = user; + this.title = title; + this.parent = parent; + + this.border = new ArrayList(); + this.items = new HashMap(); + + this.setupNavigationButton(); + } + + /** + * Sets up (or re-sets up) the return or exit button at slot 8. + * Call this after clearItems() to restore the navigation button. + */ + protected void setupNavigationButton() { + if (parent != null) { + PanelItem returnItem = new PanelItemBuilder() + .name(this.user.getTranslation("upgrades.ui.buttons.return")) + .icon(Material.BARRIER) + .clickHandler((panel, client, click, slot) -> { + this.parent.getBuild().build(); + return true; + }) + .build(); + this.setItems(RETURN, returnItem, 8); + } else { + PanelItem exitItem = new PanelItemBuilder() + .name(this.user.getTranslation("upgrades.ui.buttons.exit")) + .icon(Material.BARRIER) + .clickHandler((panel, client, click, slot) -> { + client.closeInventory(); + return true; + }) + .build(); + this.setItems(EXIT, exitItem, 8); + } + } + + /** + * Hook called at the start of getBuild() before building the panel. + * Override in subclasses to refresh panel content dynamically. + */ + protected void onBuildHook() {} + + /** + * Clears all named panel items (but not the border). + * Use in onBuildHook() to allow dynamic refresh of panel content. + */ + protected void clearItems() { + this.items.clear(); + } + + public PanelBuilder getBuild() { + this.onBuildHook(); + PanelBuilder builder = new PanelBuilder(); + + builder.user(this.user); + builder.name(this.title); + + this.border.forEach((PanelSlot item) -> { + builder.item(item.getSlot(), item.getItem()); + }); + + this.items.forEach((String name, PanelSlot item) -> { + builder.item(item.getSlot(), item.getItem()); + }); + + return builder; + } + + protected void fillBorder(Material borderMat) { + this.fillBorder(5, borderMat); + } + + protected void fillBorder(int rowCount, Material borderMat) { + for (int x = 0; x < 9; x++) { + this.border.add(new PanelSlot(this.getBorderItem(borderMat), x)); + } + for (int x = rowCount * 9 - 9; x < rowCount * 9; x++) { + this.border.add(new PanelSlot(this.getBorderItem(borderMat), x)); + } + for (int x = 1; x < rowCount - 1; x++) { + this.border.add(new PanelSlot(this.getBorderItem(borderMat), x * 9)); + this.border.add(new PanelSlot(this.getBorderItem(borderMat), x * 9 + 8)); + } + + } + + private PanelItem getBorderItem(Material borderMat) { + return new PanelItemBuilder() + .name(" ") + .description(Collections.emptyList()) + .glow(false) + .icon(borderMat) + .clickHandler(null) + .build(); + } + + /** + * @return the title + */ + protected String getTitle() { + return title; + } + /** + * @param title the title to set + */ + protected void setTitle(String title) { + this.title = title; + } + /** + * @return the addon + */ + protected UpgradesAddon getAddon() { + return addon; + } + /** + * @return the gamemode + */ + protected GameModeAddon getGamemode() { + return gamemode; + } + /** + * @return the user + */ + protected User getUser() { + return user; + } + /** + * @return the parent + */ + protected AbPanel getParent() { + return parent; + } + + /** + * Word-wraps a single string into multiple lines, each no wider than + * {@code maxWidth} characters. Breaks only on spaces. + * + * @param text the text to wrap (may be null or empty) + * @param maxWidth maximum characters per line + * @return list of wrapped lines + */ + protected static List wrapText(String text, int maxWidth) { + if (text == null || text.isEmpty()) return Collections.emptyList(); + List lines = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + for (String word : text.split(" ")) { + if (current.length() == 0) { + current.append(word); + } else if (current.length() + 1 + word.length() <= maxWidth) { + current.append(' ').append(word); + } else { + lines.add(current.toString()); + current = new StringBuilder(word); + } + } + if (current.length() > 0) lines.add(current.toString()); + return lines; + } + + /** + * Word-wraps every entry in a lore list at {@link #LORE_MAX_WIDTH} characters, + * returning a flat list of wrapped lines. + * + * @param lore source lore lines (may contain long strings) + * @return wrapped lore ready for {@link PanelItemBuilder#description(List)} + */ + protected static List wrapLore(List lore) { + if (lore == null) return Collections.emptyList(); + return lore.stream() + .flatMap(line -> wrapText(line, LORE_MAX_WIDTH).stream()) + .collect(Collectors.toList()); + } + + protected void setItems(String name, PanelItem item, int slot) { + this.items.put(name, new PanelSlot(item, slot)); + } + + private UpgradesAddon addon; + private GameModeAddon gamemode; + private User user; + private String title; + private AbPanel parent; + + private Map items; + private List border; + + private class PanelSlot { + public PanelSlot(PanelItem item, int slot) { + this.item = item; + this.slot = slot; + } + + public PanelItem getItem() { + return this.item; + } + + public int getSlot() { + return this.slot; + } + + private PanelItem item; + private int slot; + } + +} diff --git a/src/main/java/world/bentobox/upgrades/ui/utils/ChatInput.java b/src/main/java/world/bentobox/upgrades/ui/utils/ChatInput.java new file mode 100644 index 0000000..c316a0a --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/ui/utils/ChatInput.java @@ -0,0 +1,205 @@ +package world.bentobox.upgrades.ui.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.bukkit.conversations.Conversation; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.ConversationFactory; +import org.bukkit.conversations.NumericPrompt; +import org.bukkit.conversations.Prompt; +import org.bukkit.conversations.ValidatingPrompt; + +import org.eclipse.jdt.annotation.NonNull; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.upgrades.UpgradesAddon; + +public class ChatInput { + + // ------------------------------------------------------------ + // Section: Variables + // ------------------------------------------------------------ + + private UpgradesAddon addon; + + // ------------------------------------------------------------ + // Section: Constructor + // ------------------------------------------------------------ + + /** + * Init ChatInput + * + * @param addon + */ + public ChatInput(@NonNull UpgradesAddon addon) { + this.addon = addon; + } + + // ------------------------------------------------------------ + // Section: Input methods + // ------------------------------------------------------------ + + /** + * Start conversation with user about question + * If validation return true about user input + * Then call consumer with user input + * If the user cancel the conversation or if it timeout, then consumer get null + * InvalidText used when player input is refused by validation + * + * @param consumer Called when conversation end + * @param validation Called for checking input + * @param question Showed to the user for asking input + * @param invalidText Showed to the user when it's input is invalid + * @param user User to converse with + */ + public void askOneInput(Consumer consumer, Function validation, String question, String invalidText, User user, boolean sanitize) { + // Track whether the consumer was already called with valid input + final boolean[] consumerCalled = {false}; + // Create conversation + Conversation conv = new ConversationFactory(this.addon.getPlugin()) + // Can escape conversation by using the chat-input-escape value in setting + .withEscapeSequence(this.addon.getSettings().getChatInputEscape()) + // Display user input + .withLocalEcho(true) + // When conversation end + .addConversationAbandonedListener(abandoned -> { + // If consumer was not called with valid input, notify caller about cancellation + // (covers timeout, cancel, and escape-sequence exits) + if (!consumerCalled[0]) + consumer.accept(null); + }) + .withFirstPrompt(new ValidatingPrompt() { + + @Override + public String getPromptText(ConversationContext context) { + // Close user inventory + user.closeInventory(); + return question; + } + + @Override + protected boolean isInputValid(ConversationContext context, String input) { + if (sanitize) { + // Get clean input, lowcase, no ' ' and no '-' + input = sanitizeInput(input); + } + // Check if input is valid + return validation.apply(input); + } + + @Override + protected Prompt acceptValidatedInput(ConversationContext context, String input) { + if (sanitize) { + // Get clean input, lowcase, no ' ' and no '-' + input = sanitizeInput(input); + } + // Call consumer with user input + consumerCalled[0] = true; + consumer.accept(input); + // End conversation + return Prompt.END_OF_CONVERSATION; + } + + @Override + protected String getFailedValidationText(ConversationContext context, String invalidInput) { + return invalidText; + } + }) + .buildConversation(user.getPlayer()); + + // Start conversation + conv.begin(); + } + + public void askMultiLine(Consumer> consumer, Function validation, String question, String invalidText, User user) { + List list = new ArrayList(); + UpgradesAddon addon = this.addon; + Conversation conv = new ConversationFactory(addon.getPlugin()) + .withEscapeSequence(addon.getSettings().getChatInputEscape()) + .addConversationAbandonedListener(abandoned -> { + consumer.accept(list); + }) + .withFirstPrompt(new ValidatingPrompt() { + + boolean sayMessage = true; + + @Override + public String getPromptText(ConversationContext context) { + user.closeInventory(); + String message = sayMessage ? question : ""; + sayMessage = false; + return message; + } + + @Override + protected boolean isInputValid(ConversationContext context, String input) { + return validation.apply(input); + } + + @Override + protected Prompt acceptValidatedInput(ConversationContext context, String input) { + list.add(input); + return this; + } + }) + .buildConversation(user.getPlayer()); + + conv.begin(); + } + + public void askOneNumber(Consumer consumer, Function validation, String question, String invalidText, User user) { + Conversation conv = new ConversationFactory(this.addon.getPlugin()) + .withEscapeSequence(this.addon.getSettings().getChatInputEscape()) + .addConversationAbandonedListener(abandoned -> { + if (!abandoned.gracefulExit()) + consumer.accept(null); + }).withFirstPrompt(new NumericPrompt() { + + @Override + public String getPromptText(ConversationContext context) { + user.closeInventory(); + return question; + } + + @Override + protected String getFailedValidationText(ConversationContext context, String invalidInput) { + return invalidText; + } + + @Override + protected boolean isNumberValid(ConversationContext context, Number input) { + return super.isNumberValid(context, input) && validation.apply(input); + } + + @Override + protected Prompt acceptValidatedInput(ConversationContext context, Number input) { + consumer.accept(input); + return Prompt.END_OF_CONVERSATION; + } + }) + .buildConversation(user.getPlayer()); + + conv.begin(); + } + + // ------------------------------------------------------------ + // Section: Utils methods + // ------------------------------------------------------------ + + /** + * Sanitizes the provided input. + * It replaces spaces and hyphens with underscores and lower cases the input. + * @param input input to sanitize + * @return sanitized input + * @author BONNe + */ + public static String sanitizeInput(String input) + { + return input.toLowerCase(Locale.ENGLISH).replace(" ", "_").replace("-", "_"); + } + +} diff --git a/src/main/java/world/bentobox/upgrades/upgrades/BlockLimitsUpgrade.java b/src/main/java/world/bentobox/upgrades/upgrades/BlockLimitsUpgrade.java new file mode 100644 index 0000000..8319c96 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/upgrades/BlockLimitsUpgrade.java @@ -0,0 +1,153 @@ +package world.bentobox.upgrades.upgrades; + +import java.util.Map; + +import org.apache.commons.lang.math.NumberUtils; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.permissions.PermissionAttachmentInfo; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; + +import world.bentobox.limits.listeners.BlockLimitsListener; +import world.bentobox.limits.objects.IslandBlockCount; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.api.UpgradeAPI; +import world.bentobox.upgrades.dataobjects.UpgradesData; + +public class BlockLimitsUpgrade extends UpgradeAPI { + + private static final String BLOCK = "[block]"; + private static final String LEVEL = "[level]"; + private Material block; + + public BlockLimitsUpgrade(UpgradesAddon addon, Material block) { + super(addon, "LimitsUpgrade-" + block.toString(), block.toString() + " limits Upgrade", block); + this.block = block; + } + + @Override + public void updateUpgradeValue(User user, Island island) { + UpgradesAddon upgradeAddon = this.getUpgradesAddon(); + UpgradesData islandData = upgradeAddon.getUpgradesLevels(island.getUniqueId()); + int upgradeLevel = islandData.getUpgradeLevel(getName()); + int numberPeople = island.getMemberSet().size(); + int islandLevel = upgradeAddon.getUpgradesManager().getIslandLevel(island); + + Map upgradeInfos = upgradeAddon.getUpgradesManager().getBlockLimitsUpgradeInfos(this.block, + upgradeLevel, islandLevel, numberPeople, island.getWorld()); + UpgradeValues upgrade; + + if (upgradeInfos == null) { + upgrade = null; + this.setOwnDescription(user, null); + } else { + // Get new description + String description = user.getTranslation("upgrades.ui.upgradepanel.tiernameandlevel", + "[name]", upgradeAddon.getUpgradesManager().getBlockLimitsUpgradeTierName(this.block, upgradeLevel, island.getWorld()), + "[current]", Integer.toString(upgradeLevel), + "[max]", Integer.toString(upgradeAddon.getUpgradesManager().getBlockLimitsUpgradeMax(this.block, island.getWorld()))); + + // Set new description + this.setOwnDescription(user, description); + + upgrade = new UpgradeValues(upgradeInfos.get("islandMinLevel"), upgradeInfos.get("vaultCost"), + upgradeInfos.get("upgrade")); + } + + this.setUpgradeValues(user, upgrade); + + String newDisplayName; + + if (upgrade == null) { + newDisplayName = user.getTranslation("upgrades.ui.upgradepanel.nolimitsupgrade", BLOCK, + this.block.toString()); + } else { + newDisplayName = user.getTranslation("upgrades.ui.upgradepanel.limitsupgrade", BLOCK, + this.block.toString(), LEVEL, Integer.toString(upgrade.getUpgradeValue())); + } + + this.setDisplayName(newDisplayName); + } + + @Override + public boolean isShowed(User user, Island island) { + // Get the addon + UpgradesAddon upgradesAddon = this.getUpgradesAddon(); + // Get the data from upgrades + UpgradesData islandData = upgradesAddon.getUpgradesLevels(island.getUniqueId()); + // Get level of the upgrade + int upgradeLevel = islandData.getUpgradeLevel(this.getName()); + // Permission level required + int permissionLevel = upgradesAddon.getUpgradesManager().getBlockLimitsPermissionLevel(this.block, upgradeLevel, + island.getWorld()); + + // If default permission, then true + if (permissionLevel == 0) + return true; + + Player player = user.getPlayer(); + String gamemode = island.getGameMode(); + String permissionStart = gamemode + ".upgrades." + this.getName() + "."; + permissionStart = permissionStart.toLowerCase(); + + // For each permission of the player + for (PermissionAttachmentInfo perms : player.getEffectivePermissions()) { + + // If permission is the one we search + if (!perms.getValue() || !perms.getPermission().startsWith(permissionStart)) + continue; + + if (perms.getPermission().contains(permissionStart + "*")) { + this.logError(player.getName(), perms.getPermission(), "Wildcards are not allowed."); + return false; + } + + String[] split = perms.getPermission().split("\\."); + if (split.length != 4) { + logError(player.getName(), perms.getPermission(), "format must be '" + permissionStart + "LEVEL'"); + return false; + } + + if (!NumberUtils.isDigits(split[3])) { + logError(player.getName(), perms.getPermission(), "The last part must be a number"); + return false; + } + + if (permissionLevel <= Integer.parseInt(split[3])) + return true; + } + + return false; + } + + private void logError(String name, String perm, String error) { + this.getUpgradesAddon() + .logError("Player " + name + " has permission: '" + perm + "' but " + error + " Ignoring..."); + } + + @Override + public boolean doUpgrade(User user, Island island) { + UpgradesAddon islandAddon = this.getUpgradesAddon(); + + if (!islandAddon.isLimitsProvided()) + return false; + + BlockLimitsListener bLListener = islandAddon.getLimitsAddon().getBlockLimitListener(); + IslandBlockCount isb = bLListener.getIsland(island); + + if (!super.doUpgrade(user, island)) + return false; + + int oldCount = isb.getBlockLimitsOffset().getOrDefault(block.getKey(), 0); + int newCount = oldCount + this.getUpgradeValues(user).getUpgradeValue(); + isb.setBlockLimitsOffset(block.getKey(), newCount); + + user.sendMessage("upgrades.ui.upgradepanel.limitsupgradedone", BLOCK, this.block.toString(), LEVEL, + Integer.toString(this.getUpgradeValues(user).getUpgradeValue())); + + return true; + } + +} diff --git a/src/main/java/world/bentobox/upgrades/upgrades/CommandUpgrade.java b/src/main/java/world/bentobox/upgrades/upgrades/CommandUpgrade.java new file mode 100644 index 0000000..ea96398 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/upgrades/CommandUpgrade.java @@ -0,0 +1,124 @@ +package world.bentobox.upgrades.upgrades; + +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.math.NumberUtils; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.permissions.PermissionAttachmentInfo; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.api.UpgradeAPI; +import world.bentobox.upgrades.dataobjects.UpgradesData; + +public class CommandUpgrade extends UpgradeAPI { + + private String cmdId; + + public CommandUpgrade(UpgradesAddon addon, String cmdId, Material icon) { + super(addon, "command-" + cmdId, addon.getSettings().getCommandName(cmdId), icon); + this.cmdId = cmdId; + } + + @Override + public void updateUpgradeValue(User user, Island island) { + UpgradesAddon upgradesAddon = this.getUpgradesAddon(); + UpgradesData islandData = upgradesAddon.getUpgradesLevels(island.getUniqueId()); + int upgradeLevel = islandData.getUpgradeLevel(this.getName()); + int numberPeople = island.getMemberSet().size(); + int islandLevel = upgradesAddon.getUpgradesManager().getIslandLevel(island); + + Map upgradeInfos = upgradesAddon.getUpgradesManager().getCommandUpgradeInfos(this.cmdId, upgradeLevel, islandLevel, numberPeople, island.getWorld()); + UpgradeValues upgrade; + + if (upgradeInfos == null) { + upgrade = null; + this.setOwnDescription(user, null); + } else { + String description = user.getTranslation("upgrades.ui.upgradepanel.tiernameandlevel", + "[name]", upgradesAddon.getUpgradesManager().getCommandUpgradeTierName(this.cmdId, upgradeLevel, island.getWorld()), + "[current]", Integer.toString(upgradeLevel), + "[max]", Integer.toString(upgradesAddon.getUpgradesManager().getCommandUpgradeMax(this.cmdId, island.getWorld()))); + + this.setOwnDescription(user, description); + + upgrade = new UpgradeValues(upgradeInfos.get("islandMinLevel"), upgradeInfos.get("vaultCost"), upgradeInfos.get("upgrade")); + } + + this.setUpgradeValues(user, upgrade); + } + + @Override + public boolean isShowed(User user, Island island) { + UpgradesAddon upgradeAddon = this.getUpgradesAddon(); + UpgradesData islandData = upgradeAddon.getUpgradesLevels(island.getUniqueId()); + int upgradeLevel = islandData.getUpgradeLevel(this.getName()); + int permissionLevel = upgradeAddon.getUpgradesManager().getCommandPermissionLevel(this.cmdId, upgradeLevel, island.getWorld()); + + if (permissionLevel == 0) + return true; + + Player player = user.getPlayer(); + String gamemode = island.getGameMode(); + String permissionStart = gamemode + ".upgrades." + this.getName() + "."; + permissionStart = permissionStart.toLowerCase(); + + for (PermissionAttachmentInfo perms : player.getEffectivePermissions()) { + + // If permission is the one we search + if (!perms.getValue() || !perms.getPermission().startsWith(permissionStart)) + continue; + + if (perms.getPermission().contains(permissionStart + "*")) { + this.logError(player.getName(), perms.getPermission(), "Wildcards are not allowed."); + return false; + } + + String[] split = perms.getPermission().split("\\."); + if (split.length != 4) { + logError(player.getName(), perms.getPermission(), "format must be '" + permissionStart + "LEVEL'"); + return false; + } + + if (!NumberUtils.isDigits(split[3])) { + logError(player.getName(), perms.getPermission(), "The last part must be a number"); + return false; + } + + if (permissionLevel <= Integer.parseInt(split[3])) + return true; + } + + return false; + } + + private void logError(String name, String perm, String error) { + this.getUpgradesAddon().logError("Player " + name + " has permission: '" + perm + "' but " + error + " Ignoring..."); + } + + @Override + public boolean doUpgrade(User user, Island island) { + UpgradesAddon upgradeAddon = this.getUpgradesAddon(); + UpgradesData islandData = upgradeAddon.getUpgradesLevels(island.getUniqueId()); + int upgradeLevel = islandData.getUpgradeLevel(this.getName()); + + if (!super.doUpgrade(user, island)) + return false; + + List commands = upgradeAddon.getUpgradesManager().getCommandList(this.cmdId, upgradeLevel, island, user.getName()); + Boolean isConsole = upgradeAddon.getUpgradesManager().isCommantConsole(this.cmdId, upgradeLevel, island.getWorld()); + + commands.forEach(cmd -> { + if (Boolean.TRUE.equals(isConsole)) { + upgradeAddon.getServer().dispatchCommand(upgradeAddon.getServer().getConsoleSender(), cmd); + } else { + upgradeAddon.getServer().dispatchCommand(user.getSender(), cmd); + } + }); + return true; + } + +} diff --git a/src/main/java/world/bentobox/upgrades/upgrades/DatabaseUpgrade.java b/src/main/java/world/bentobox/upgrades/upgrades/DatabaseUpgrade.java new file mode 100644 index 0000000..7714eb1 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/upgrades/DatabaseUpgrade.java @@ -0,0 +1,146 @@ +package world.bentobox.upgrades.upgrades; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.api.UpgradeAPI; +import world.bentobox.upgrades.dataobjects.UpgradeData; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.dataobjects.UpgradesData; +import world.bentobox.upgrades.dataobjects.prices.Price; +import world.bentobox.upgrades.dataobjects.prices.PriceDB; +import world.bentobox.upgrades.dataobjects.rewards.Reward; +import world.bentobox.upgrades.dataobjects.rewards.RewardDB; + +import java.util.List; + +/** + * Bridge between database-configured UpgradeData/UpgradeTier and the player shop. + */ +public class DatabaseUpgrade extends UpgradeAPI { + + private final UpgradeData upgradeData; + + public DatabaseUpgrade(UpgradesAddon addon, UpgradeData upgradeData) { + super(addon, upgradeData.getUniqueId(), upgradeData.getName(), + upgradeData.getIcon().getType()); + this.upgradeData = upgradeData; + } + + @Override + public void updateUpgradeValue(User user, Island island) { + UpgradesData data = this.getUpgradesAddon().getUpgradesLevels(island.getUniqueId()); + int currentLevel = data.getUpgradeLevel(this.getName()); + + UpgradeTier nextTier = findNextTier(currentLevel); + + if (nextTier == null) { + // Max level reached — clear both so Panel shows "Max level" + this.setOwnDescription(user, null); + this.setUpgradeValues(user, null); + this.setDisplayName(upgradeData.getName()); + return; + } + + // Build description from prices and rewards, using DB-aware overloads so that + // formula placeholders (e.g. [amount] in MoneyPrice) are substituted correctly. + StringBuilder sb = new StringBuilder(); + for (PriceDB priceDB : nextTier.getPrices()) { + Price price = this.getUpgradesAddon().getUpgradesManager().searchPrice(priceDB.getPriceType()); + if (price != null) { + sb.append(price.getPublicDescription(user, priceDB)).append("\n"); + } + } + for (RewardDB rewardDB : nextTier.getRewards()) { + Reward reward = this.getUpgradesAddon().getUpgradesManager().searchReward(rewardDB.getRewardType()); + if (reward != null) { + sb.append(reward.getPublicDescription(user, rewardDB)).append("\n"); + } + } + + String description = sb.toString().trim(); + // Always set a non-null ownDescription when a tier is found. + // PanelClick uses (upgradeValues == null && ownDescription == null) to detect + // the maxed-out state, so ownDescription must be non-null here even when the + // tier has no prices or rewards configured (free upgrade). + this.setOwnDescription(user, description.isEmpty() ? nextTier.getName() : description); + // Do NOT set upgradeValues so Panel skips legacy vault/level display + this.setUpgradeValues(user, null); + this.setDisplayName(upgradeData.getName()); + } + + @Override + public boolean canUpgrade(User user, Island island) { + UpgradesData data = this.getUpgradesAddon().getUpgradesLevels(island.getUniqueId()); + int currentLevel = data.getUpgradeLevel(this.getName()); + + UpgradeTier nextTier = findNextTier(currentLevel); + if (nextTier == null) return false; + + for (PriceDB priceDB : nextTier.getPrices()) { + Price price = this.getUpgradesAddon().getUpgradesManager().searchPrice(priceDB.getPriceType()); + if (price == null) continue; + if (!price.canPay(this.getUpgradesAddon(), user, island, priceDB, currentLevel)) { + return false; + } + } + return true; + } + + @Override + public boolean doUpgrade(User user, Island island) { + UpgradesData data = this.getUpgradesAddon().getUpgradesLevels(island.getUniqueId()); + int currentLevel = data.getUpgradeLevel(this.getName()); + + UpgradeTier nextTier = findNextTier(currentLevel); + if (nextTier == null) return false; + + // Collect prices first to check for issues before paying + for (PriceDB priceDB : nextTier.getPrices()) { + Price price = this.getUpgradesAddon().getUpgradesManager().searchPrice(priceDB.getPriceType()); + if (price == null) continue; + price.pay(this.getUpgradesAddon(), user, island, priceDB, currentLevel); + } + + for (RewardDB rewardDB : nextTier.getRewards()) { + Reward reward = this.getUpgradesAddon().getUpgradesManager().searchReward(rewardDB.getRewardType()); + if (reward == null) continue; + reward.apply(this.getUpgradesAddon(), user, island, rewardDB, currentLevel); + } + + data.setUpgradeLevel(this.getName(), currentLevel + 1); + return true; + } + + @Override + public boolean isShowed(User user, Island island) { + if (!upgradeData.isActive()) return false; + // Don't show upgrades with no tiers configured — they'd appear "maxed out" + List tiers = this.getUpgradesAddon().getUpgradeDataManager() + .getUpgradeTierByUpgradeData(upgradeData); + if (tiers.isEmpty()) return false; + return this.getUpgradesAddon().getPlugin().getIWM().getAddon(island.getWorld()) + .map(a -> a.getDescription().getName()) + .orElse("") + .equals(upgradeData.getWorld()); + } + + public UpgradeData getUpgradeData() { + return upgradeData; + } + + /** + * Find the tier that covers the current level (i.e. the next purchase available). + * Level 0 = not yet purchased; a tier with startLevel=0, endLevel=0 means one purchase. + */ + private UpgradeTier findNextTier(int currentLevel) { + List tiers = this.getUpgradesAddon().getUpgradeDataManager() + .getUpgradeTierByUpgradeData(upgradeData); + for (UpgradeTier tier : tiers) { + if (tier.getStartLevel() <= currentLevel && currentLevel <= tier.getEndLevel()) { + return tier; + } + } + return null; + } +} diff --git a/src/main/java/world/bentobox/upgrades/upgrades/EntityGroupLimitsUpgrade.java b/src/main/java/world/bentobox/upgrades/upgrades/EntityGroupLimitsUpgrade.java new file mode 100644 index 0000000..2717d6a --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/upgrades/EntityGroupLimitsUpgrade.java @@ -0,0 +1,189 @@ +package world.bentobox.upgrades.upgrades; + +import java.util.Map; + +import org.apache.commons.lang.math.NumberUtils; +import org.bukkit.entity.Player; +import org.bukkit.permissions.PermissionAttachmentInfo; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.limits.listeners.BlockLimitsListener; +import world.bentobox.limits.objects.IslandBlockCount; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.api.UpgradeAPI; +import world.bentobox.upgrades.dataobjects.UpgradesData; + +public class EntityGroupLimitsUpgrade extends UpgradeAPI { + + private final String group; + + /** + * Constructs a new {@code EntityGroupLimitsUpgrade} instance for a specified entity group. + * + * @param addon The instance of the {@code UpgradesAddon}. + * @param group The name of the entity group associated with this upgrade. + */ + public EntityGroupLimitsUpgrade(UpgradesAddon addon, String group) { + super(addon, "LimitsUpgrade-" + group, group + " limits Upgrade", addon.getSettings().getEntityGroupIcon(group)); + this.group = group; + } + + /** + * Updates the upgrade values for the specified user and island. + * This method calculates and sets the upgrade's description, display name, and other + * relevant attributes based on the current upgrade level and island context. + * + * @param user The user for whom the upgrade values are being updated. + * @param island The island associated with the upgrade. + */ + @Override + public void updateUpgradeValue(User user, Island island) { + UpgradesAddon upgradeAddon = this.getUpgradesAddon(); + UpgradesData islandData = upgradeAddon.getUpgradesLevels(island.getUniqueId()); + int upgradeLevel = islandData.getUpgradeLevel(this.getName()); + int numberPeople = island.getMemberSet().size(); + int islandLevel; + + if (upgradeAddon.isLevelProvided()) + islandLevel = upgradeAddon.getUpgradesManager().getIslandLevel(island); + else + islandLevel = 0; + + Map upgradeInfos = upgradeAddon.getUpgradesManager().getEntityGroupLimitsUpgradeInfos(this.group, upgradeLevel, islandLevel, numberPeople, island.getWorld()); + UpgradeValues upgrade; + + if (upgradeInfos == null) { + upgrade = null; + this.setOwnDescription(user, null); + } else { + // Get new description + String description = user.getTranslation("upgrades.ui.upgradepanel.tiernameandlevel", + "[name]", upgradeAddon.getUpgradesManager().getEntityGroupLimitsUpgradeTierName(this.group, upgradeLevel, island.getWorld()), + "[current]", Integer.toString(upgradeLevel), + "[max]", Integer.toString(upgradeAddon.getUpgradesManager().getEntityGroupLimitsUpgradeMax(this.group, island.getWorld()))); + + // Set new description + this.setOwnDescription(user, description); + + upgrade = new UpgradeValues(upgradeInfos.get("islandMinLevel"), upgradeInfos.get("vaultCost"), upgradeInfos.get("upgrade")); + } + + this.setUpgradeValues(user, upgrade); + + String newDisplayName; + + if (upgrade == null) { + newDisplayName = user.getTranslation("upgrades.ui.upgradepanel.nolimitsupgrade", + "[block]", this.group); + } else { + newDisplayName = user.getTranslation("upgrades.ui.upgradepanel.limitsupgrade", + "[block]", this.group, "[level]", Integer.toString(upgrade.getUpgradeValue())); + } + + this.setDisplayName(newDisplayName); + } + + /** + * Determines whether this upgrade should be displayed to the user. + * Checks the visibility conditions for the upgrade, including permissions and other + * contextual requirements, ensuring that only valid upgrades are shown. + * + * @param user The user requesting the visibility check. + * @param island The island associated with the upgrade. + * @return {@code true} if the upgrade should be displayed; {@code false} otherwise. + */ + @Override + public boolean isShowed(User user, Island island) { + // Get the addon + UpgradesAddon upgradesAddon = this.getUpgradesAddon(); + // Get the data from upgrades + UpgradesData islandData = upgradesAddon.getUpgradesLevels(island.getUniqueId()); + // Get level of the upgrade + int upgradeLevel = islandData.getUpgradeLevel(this.getName()); + // Permission level required + int permissionLevel = upgradesAddon.getUpgradesManager().getEntityGroupLimitsPermissionLevel(this.group, upgradeLevel, island.getWorld()); + + // If default permission, then true + if (permissionLevel == 0) + return true; + + Player player = user.getPlayer(); + String gamemode = island.getGameMode(); + String permissionStart = gamemode + ".upgrades." + this.getName() + "."; + permissionStart = permissionStart.toLowerCase(); + + // For each permission of the player + for (PermissionAttachmentInfo perms : player.getEffectivePermissions()) { + + // If permission is the one we search + if (!perms.getValue() || !perms.getPermission().startsWith(permissionStart)) + continue; + + if (perms.getPermission().contains(permissionStart + "*")) { + this.logError(player.getName(), perms.getPermission(), "Wildcards are not allowed."); + return false; + } + + String[] split = perms.getPermission().split("\\."); + if (split.length != 4) { + logError(player.getName(), perms.getPermission(), "format must be '" + permissionStart + "LEVEL'"); + return false; + } + + if (!NumberUtils.isDigits(split[3])) { + logError(player.getName(), perms.getPermission(), "The last part must be a number"); + return false; + } + + if (permissionLevel <= Integer.parseInt(split[3])) + return true; + } + + return false; + } + + /** + * Logs an error message for issues related to permissions or configurations. + * + * @param name The name of the player associated with the error. + * @param perm The permission string causing the error. + * @param error A description of the specific error to log. + */ + private void logError(String name, String perm, String error) { + this.getUpgradesAddon().logError("Player " + name + " has permission: '" + perm + "' but " + error + " Ignoring..."); + } + + /** + * Performs the upgrade for the specified user and island. + * This method applies the upgrade by increasing the limits for the specified entity group + * and updating the island's limit data accordingly. + * + * @param user The user performing the upgrade. + * @param island The island on which the upgrade is applied. + * @return {@code true} if the upgrade was successfully applied; {@code false} otherwise. + */ + @Override + public boolean doUpgrade(User user, Island island) { + UpgradesAddon islandAddon = this.getUpgradesAddon(); + + if (!islandAddon.isLimitsProvided()) + return false; + + BlockLimitsListener bLListener = islandAddon.getLimitsAddon().getBlockLimitListener(); + IslandBlockCount isb = bLListener.getIsland(island); + if (!super.doUpgrade(user, island)) + return false; + + int oldCount = isb.getEntityGroupLimitsOffset().getOrDefault(this.group, 0); + int newCount = oldCount + this.getUpgradeValues(user).getUpgradeValue(); + + isb.setEntityGroupLimitsOffset(this.group, newCount); + + user.sendMessage("upgrades.ui.upgradepanel.limitsupgradedone", + "[block]", this.group, "[level]", Integer.toString(this.getUpgradeValues(user).getUpgradeValue())); + + return true; + } + +} diff --git a/src/main/java/world/bentobox/upgrades/upgrades/EntityLimitsUpgrade.java b/src/main/java/world/bentobox/upgrades/upgrades/EntityLimitsUpgrade.java new file mode 100644 index 0000000..8aa43d2 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/upgrades/EntityLimitsUpgrade.java @@ -0,0 +1,193 @@ +package world.bentobox.upgrades.upgrades; + +import java.util.Map; + +import org.apache.commons.lang.math.NumberUtils; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.permissions.PermissionAttachmentInfo; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.limits.listeners.BlockLimitsListener; +import world.bentobox.limits.objects.IslandBlockCount; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.api.UpgradeAPI; +import world.bentobox.upgrades.dataobjects.UpgradesData; + +public class EntityLimitsUpgrade extends UpgradeAPI { + + /** + * Constructs a new {@code EntityLimitsUpgrade} instance for a specific entity type. + * + * @param addon The instance of the {@code UpgradesAddon}. + * @param entity The {@link EntityType} associated with this upgrade. + */ + public EntityLimitsUpgrade(UpgradesAddon addon, EntityType entity) { + super(addon, "LimitsUpgrade-" + entity.toString(), entity.toString() + " limits Upgrade", + addon.getSettings().getEntityIcon(entity)); + this.entity = entity; + } + + /** + * Updates the upgrade values for the specified user and island. + * This method calculates and sets the upgrade's description, display name, and other + * relevant attributes based on the current upgrade level and island context. + * + * @param user The user for whom the upgrade values are being updated. + * @param island The island associated with the upgrade. + */ + @Override + public void updateUpgradeValue(User user, Island island) { + UpgradesAddon upgradeAddon = this.getUpgradesAddon(); + UpgradesData islandData = upgradeAddon.getUpgradesLevels(island.getUniqueId()); + int upgradeLevel = islandData.getUpgradeLevel(this.getName()); + int numberPeople = island.getMemberSet().size(); + int islandLevel; + + if (upgradeAddon.isLevelProvided()) + islandLevel = upgradeAddon.getUpgradesManager().getIslandLevel(island); + else + islandLevel = 0; + + Map upgradeInfos = upgradeAddon.getUpgradesManager().getEntityLimitsUpgradeInfos(this.entity, + upgradeLevel, islandLevel, numberPeople, island.getWorld()); + UpgradeValues upgrade; + + if (upgradeInfos == null) { + upgrade = null; + this.setOwnDescription(user, null); + } else { + // Get new description + String description = user.getTranslation("upgrades.ui.upgradepanel.tiernameandlevel", + "[name]", upgradeAddon.getUpgradesManager().getEntityLimitsUpgradeTierName(this.entity, upgradeLevel, island.getWorld()), + "[current]", Integer.toString(upgradeLevel), + "[max]", Integer.toString(upgradeAddon.getUpgradesManager().getEntityLimitsUpgradeMax(this.entity, island.getWorld()))); + + // Set new description + this.setOwnDescription(user, description); + + upgrade = new UpgradeValues(upgradeInfos.get("islandMinLevel"), upgradeInfos.get("vaultCost"), upgradeInfos.get("upgrade")); + } + + this.setUpgradeValues(user, upgrade); + + String newDisplayName; + + if (upgrade == null) { + newDisplayName = user.getTranslation("upgrades.ui.upgradepanel.nolimitsupgrade", "[block]", + this.entity.toString()); + } else { + newDisplayName = user.getTranslation("upgrades.ui.upgradepanel.limitsupgrade", "[block]", + this.entity.toString(), "[level]", Integer.toString(upgrade.getUpgradeValue())); + } + + this.setDisplayName(newDisplayName); + } + + /** + * Determines whether this upgrade should be displayed to the user. + * Checks the visibility conditions for the upgrade, including permissions and other + * contextual requirements, ensuring that only valid upgrades are shown. + * + * @param user The user requesting the visibility check. + * @param island The island associated with the upgrade. + * @return {@code true} if the upgrade should be displayed; {@code false} otherwise. + */ + @Override + public boolean isShowed(User user, Island island) { + // Get the addon + UpgradesAddon upgradesAddon = this.getUpgradesAddon(); + // Get the data from upgrades + UpgradesData islandData = upgradesAddon.getUpgradesLevels(island.getUniqueId()); + // Get level of the upgrade + int upgradeLevel = islandData.getUpgradeLevel(this.getName()); + // Permission level required + int permissionLevel = upgradesAddon.getUpgradesManager().getEntityLimitsPermissionLevel(this.entity, + upgradeLevel, island.getWorld()); + + // If default permission, then true + if (permissionLevel == 0) + return true; + + Player player = user.getPlayer(); + String gamemode = island.getGameMode(); + String permissionStart = gamemode + ".upgrades." + this.getName() + "."; + permissionStart = permissionStart.toLowerCase(); + + // For each permission of the player + for (PermissionAttachmentInfo perms : player.getEffectivePermissions()) { + + // If permission is the one we search + if (!perms.getValue() || !perms.getPermission().startsWith(permissionStart)) + continue; + + if (perms.getPermission().contains(permissionStart + "*")) { + this.logError(player.getName(), perms.getPermission(), "Wildcards are not allowed."); + return false; + } + + String[] split = perms.getPermission().split("\\."); + if (split.length != 4) { + logError(player.getName(), perms.getPermission(), "format must be '" + permissionStart + "LEVEL'"); + return false; + } + + if (!NumberUtils.isDigits(split[3])) { + logError(player.getName(), perms.getPermission(), "The last part must be a number"); + return false; + } + + if (permissionLevel <= Integer.parseInt(split[3])) + return true; + } + + return false; + } + + /** + * Logs an error message for issues related to permissions or configurations. + * + * @param name The name of the player associated with the error. + * @param perm The permission string causing the error. + * @param error A description of the specific error to log. + */ + private void logError(String name, String perm, String error) { + this.getUpgradesAddon() + .logError("Player " + name + " has permission: '" + perm + "' but " + error + " Ignoring..."); + } + + /** + * Performs the upgrade for the specified user and island. + * This method applies the upgrade by increasing the limits for the specified entity type + * and updating the island's limit data accordingly. + * + * @param user The user performing the upgrade. + * @param island The island on which the upgrade is applied. + * @return {@code true} if the upgrade was successfully applied; {@code false} otherwise. + */ + @Override + public boolean doUpgrade(User user, Island island) { + UpgradesAddon islandAddon = this.getUpgradesAddon(); + + if (!islandAddon.isLimitsProvided()) + return false; + + BlockLimitsListener bLListener = islandAddon.getLimitsAddon().getBlockLimitListener(); + IslandBlockCount isb = bLListener.getIsland(island); + if (!super.doUpgrade(user, island)) + return false; + int oldCount = isb.getEntityLimitsOffset().getOrDefault(entity, 0); + int newCount = oldCount + this.getUpgradeValues(user).getUpgradeValue(); + + isb.setEntityLimitsOffset(this.entity, newCount); + + user.sendMessage("upgrades.ui.upgradepanel.limitsupgradedone", "[block]", this.entity.toString(), "[level]", + Integer.toString(this.getUpgradeValues(user).getUpgradeValue())); + + return true; + } + + private EntityType entity; + +} diff --git a/src/main/java/world/bentobox/upgrades/upgrades/RangeUpgrade.java b/src/main/java/world/bentobox/upgrades/upgrades/RangeUpgrade.java new file mode 100644 index 0000000..e196e01 --- /dev/null +++ b/src/main/java/world/bentobox/upgrades/upgrades/RangeUpgrade.java @@ -0,0 +1,215 @@ +package world.bentobox.upgrades.upgrades; + +import java.util.Map; + +import org.apache.commons.lang.math.NumberUtils; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.permissions.PermissionAttachmentInfo; + +import world.bentobox.bentobox.api.events.island.IslandEvent; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.dataobjects.UpgradesData; +import world.bentobox.upgrades.api.UpgradeAPI; + +/** + * Represents an upgrade that increases the protection range of an island. + * This class extends the {@link Upgrade} base class and provides functionality + * for managing range upgrades within the BentoBox ecosystem. + * + *

The {@code RangeUpgrade} dynamically calculates upgrade levels, updates + * protection ranges, and ensures that the new range stays within configured limits.

+ * + *

Administrators can configure this upgrade to allow players to extend their + * island's protected area based on various factors such as the island's level, + * number of members, and the current game mode.

+ * + * @author Ikkino, tastybento + */ +public class RangeUpgrade extends UpgradeAPI { + + /** + * Constructs a new {@code RangeUpgrade} instance. + * + * @param addon The instance of the {@code UpgradesAddon}. + */ + public RangeUpgrade(UpgradesAddon addon) { + super(addon, "RangeUpgrade", "RangeUpgrade", Material.OAK_FENCE); + } + + /** + * Updates the upgrade values when the user opens the upgrade interface. + * This method dynamically calculates and sets the upgrade's details such as + * the current level, maximum level, and associated costs. + * + * @param user The user viewing the upgrade interface. + * @param island The island for which the upgrade is being viewed. + */ + @Override + public void updateUpgradeValue(User user, Island island) { + // Get the addon + UpgradesAddon islandAddon = this.getUpgradesAddon(); + // Get the data from IslandUpgrade + UpgradesData islandData = islandAddon.getUpgradesLevels(island.getUniqueId()); + // The level of this upgrade + int upgradeLevel = islandData.getUpgradeLevel(getName()); + // The number of members on the island + int numberPeople = island.getMemberSet().size(); + // The level of the island from Level Addon + int islandLevel = islandAddon.getUpgradesManager().getIslandLevel(island); + + // Get upgrades infos of range upgrade from settings + Map upgradeInfos = islandAddon.getUpgradesManager().getRangeUpgradeInfos(upgradeLevel, + islandLevel, numberPeople, island.getWorld()); + UpgradeValues upgrade; + + // If null -> no next upgrades + if (upgradeInfos == null) { + upgrade = null; + this.setOwnDescription(user, null); + } else { + // Get new description + String description = user.getTranslation("upgrades.ui.upgradepanel.tiernameandlevel", + "[name]", islandAddon.getUpgradesManager().getRangeUpgradeTierName(upgradeLevel, island.getWorld()), + "[current]", Integer.toString(upgradeLevel), + "[max]", Integer.toString(islandAddon.getUpgradesManager().getRangeUpgradeMax(island.getWorld()))); + + // Set new description + this.setOwnDescription(user, description); + + upgrade = new UpgradeValues(upgradeInfos.get("islandMinLevel"), upgradeInfos.get("vaultCost"), upgradeInfos.get("upgrade")); + } + // Update the upgrade values + this.setUpgradeValues(user, upgrade); + + // Update the display name + String newDisplayName; + + if (upgrade == null) { + // No next upgrade -> lang message + newDisplayName = user.getTranslation("upgrades.ui.upgradepanel.norangeupgrade"); + } else { + // get lang message + newDisplayName = user.getTranslation("upgrades.ui.upgradepanel.rangeupgrade", "[rangelevel]", + Integer.toString(upgrade.getUpgradeValue())); + } + + this.setDisplayName(newDisplayName); + } + + /** + * Determines whether the upgrade should be displayed in the user's interface. + * This involves checking user permissions and other contextual requirements. + * + * @param user The user requesting the visibility check. + * @param island The island associated with the upgrade. + * @return {@code true} if the upgrade should be visible; {@code false} otherwise. + */ + @Override + public boolean isShowed(User user, Island island) { + // Get the addon + UpgradesAddon upgradesAddon = this.getUpgradesAddon(); + // Get the data from upgrades + UpgradesData islandData = upgradesAddon.getUpgradesLevels(island.getUniqueId()); + // Get level of the upgrade + int upgradeLevel = islandData.getUpgradeLevel(this.getName()); + // Permission level required + int permissionLevel = upgradesAddon.getUpgradesManager().getRangePermissionLevel(upgradeLevel, + island.getWorld()); + + // If default permission, then true + if (permissionLevel == 0) + return true; + + Player player = user.getPlayer(); + String gamemode = island.getGameMode(); + String permissionStart = gamemode + ".upgrades." + this.getName() + "."; + permissionStart = permissionStart.toLowerCase(); + + // For each permission of the player + for (PermissionAttachmentInfo perms : player.getEffectivePermissions()) { + + // If permission is the one we search + if (!perms.getValue() || !perms.getPermission().startsWith(permissionStart)) + continue; + + if (perms.getPermission().contains(permissionStart + "*")) { + this.logError(player.getName(), perms.getPermission(), "Wildcards are not allowed."); + return false; + } + + String[] split = perms.getPermission().split("\\."); + if (split.length != 4) { + logError(player.getName(), perms.getPermission(), "format must be '" + permissionStart + "LEVEL'"); + return false; + } + + if (!NumberUtils.isDigits(split[3])) { + logError(player.getName(), perms.getPermission(), "The last part must be a number"); + return false; + } + + if (permissionLevel <= Integer.parseInt(split[3])) + return true; + } + + return false; + } + + /** + * Logs an error related to permissions or configuration issues. + * + * @param name The name of the player associated with the error. + * @param perm The permission string causing the error. + * @param error A description of the specific error to log. + */ + private void logError(String name, String perm, String error) { + this.getUpgradesAddon() + .logError("Player " + name + " has permission: '" + perm + "' but " + error + " Ignoring..."); + } + + /** + * Applies the range upgrade to the specified island. + * Increases the island's protection range and triggers relevant events to + * reflect the change. Ensures that the new range does not exceed the maximum limit. + * + * @param user The user performing the upgrade. + * @param island The island to which the upgrade is applied. + * @return {@code true} if the upgrade was successfully applied; {@code false} otherwise. + */ + @Override + public boolean doUpgrade(User user, Island island) { + // Get the new range + int newRange = island.getProtectionRange() + this.getUpgradeValues(user).getUpgradeValue(); + + // If newRange is more than the authorized range (Config problem) + if (newRange > island.getRange()) { + this.getUpgradesAddon().logWarning( + "User tried to upgrade their island range over the max. This is probably a configuration problem."); + user.sendMessage("upgrades.error.rangeovermax"); + return false; + } + + // if super doUpgrade not worked + if (!super.doUpgrade(user, island)) + return false; + + // Save oldRange for rangeChange event + int oldRange = island.getProtectionRange(); + + // Add range bonus + island.addBonusRange(this.getUpgradesAddon().getDescription().getName(), this.getUpgradeValues(user).getUpgradeValue(), ""); + + // Launch range change event + IslandEvent.builder().island(island).location(island.getCenter()).reason(IslandEvent.Reason.RANGE_CHANGE) + .involvedPlayer(user.getUniqueId()).admin(false).protectionRange(island.getProtectionRange(), oldRange).build(); + + user.sendMessage("upgrades.ui.upgradepanel.rangeupgradedone", "[rangelevel]", + Integer.toString(this.getUpgradeValues(user).getUpgradeValue())); + + return true; + } + +} diff --git a/src/main/resources/addon.yml b/src/main/resources/addon.yml index 50e9a6f..56b52dd 100644 --- a/src/main/resources/addon.yml +++ b/src/main/resources/addon.yml @@ -1,11 +1,18 @@ -name: IslandUpgrades -main: world.bentobox.islandupgrades.IslandUpgradesAddon -version: ${version}${build.number} -repository: "Guillaume-Lebegue/IslandUpgrades" -metrics: true -icon: GOLD_INGOT - -authors: - - Guillaume-Lebegue - -softdepend: BSkyBlock, AcidIsland, CaveBlock, SkyGrid, Level \ No newline at end of file +name: Upgrades +main: world.bentobox.upgrades.UpgradesAddon +version: ${version}${build.number} +api-version: '1.21' +repository: "BentoBoxWorld/Upgrades" +metrics: true +icon: GOLD_INGOT + +authors: + - Guillaume-Lebegue + - tastybento + +softdepend: BSkyBlock, AcidIsland, CaveBlock, SkyGrid, AOneBlock, Level, Limits + +permissions: + '[gamemode].admin.upgrade': + description: Access upgrade admin commands + default: op \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 835b926..ab10426 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,44 +1,226 @@ -# Config file for IslandUpgrades addon - -# Disabled Game Mode Addons -# IslandUpgrades will not work in these game modes -disabled-gamemodes: [] - -# Range Upgrade Default Tiers -# Each tier must contain: -# max-level: Upgrade level up to which tier apply -# upgrade-range: How much block is added to the range with each upgrade -# island-min-level: Minimum level required to upgrade (if level addon is provided) -# vault-cost: Money cost of upgrade -# Note: for upgrade-range, island-min-level and vault-cost: -# Mathematical expression can be used (+,-,*,/,^,(,)) -# Mathematical function can be used (sqrt,sin,cost,tan) -# Special value can be used: -# [level] : Is the actual level for this upgrade -# [islandLevel] : Is the islandLevel from level addon -# [numberPlayer] : Is the number of players in team -# -# Caution: You should always check that at max level, your players won't attain max range for island -range-upgrade: - tier1: - max-level: 5 - upgrade-range: "5" - island-min-level: "2" - vault-cost: "[level]*100" - tier2: - max-level: 10 - upgrade-range: "3" - island-min-level: "4" - vault-cost: "[level]*[numberPlayer]*200" - -# GameMode differences -# List any tiers that you want to add -# You can overwrite a tier by using the same name -gamemodes: - BSkyblock: - range-upgrade: - tier3: - max-level: 15 - upgrade-range: "5" - island-min-level: "6" - vault-cost: "[level]*[numberPlayer]*500" \ No newline at end of file +# Config file for Upgrades addon + +# ====================== +# Disabled Game Mode Addons +# ====================== +# List of game modes where the IslandUpgrades functionality will not work. +# Specify game modes by name in the array below. +disabled-gamemodes: [] + +# String used when asking input in chat to cancel input +chat-input-escape: "END" + +# Range Upgrade Default Tiers +# Each tier must contain: +# max-level: Upgrade level up to which tier apply +# upgrade-range: How much block is added to the range with each upgrade +# +# Each tier can contain: +# island-min-level: Minimum level required to upgrade (if level addon is provided) +# vault-cost: Money cost of upgrade +# permission-level: Is the level of permission needed to upgrade +# permission: "[GAMEMODE].upgrades.[UPGRADE].[LEVEL]" +# Example for bskyblock with range-upgrade with a permission level of 2 +# "bskyblock.upgrades.rangeupgrade.2" +# +# Note: for upgrade-range, island-min-level and vault-cost: +# Mathematical expression can be used (+,-,*,/,^,(,)) +# Mathematical function can be used (sqrt,sin,cos,tan) +# Special value can be used: +# [level] : Is the actual level for this upgrade +# [islandLevel] : Is the islandLevel from level addon !!!!Can be 0!!!! +# [numberPlayer] : Is the number of players in team +# +# Caution: You should always check that at max level, your players won't attain max range for island +# Permission upgrade name: rangeupgrade + +# ====================== +# Range Upgrade Configuration +# ====================== +# Range Upgrade Default Tiers +# Each tier must define: +# max-level: The maximum level this tier applies to. +# upgrade: The number of blocks added to the protection range at each level. + +# Optional fields: +# island-min-level: Minimum island level required to unlock this tier (requires Level addon). +# vault-cost: The monetary cost (in Vault currency) for each upgrade. +# permission-level: The required permission level for this upgrade. + +# Permissions format: +# [GAMEMODE].upgrades.[UPGRADE].[LEVEL] +# Example: +# bskyblock.upgrades.rangeupgrade.2 + +# Note: +# - You can use mathematical expressions (+, -, *, /, ^) and functions (sqrt, sin, cos). +# - Special variables: +# [level]: Current upgrade level. +# [islandLevel]: Island level from Level addon (can be 0 if not set). +# [numberPlayer]: Number of players in the island team. +# Ensure the maximum level configuration does not allow islands to exceed their maximum range. +range-upgrade: + tier1: + max-level: 5 + upgrade: "5" # Adds 5 blocks to the protection range per level in this tier. + island-min-level: "2" # Minimum island level required is 2. + vault-cost: "[level]*100" # Cost increases by 100 Vault currency per level. + tier2: + max-level: 10 + upgrade: "3" + island-min-level: "4" + vault-cost: "[level]*[numberPlayer]*200" # Cost scales with island members and level. + +# ====================== +# Block Limits Upgrade Configuration +# ====================== +# Defines tiers for block-specific upgrades. +# Upgrade name format: limitsupgrade-[BLOCK] (lowercase block name). +block-limits-upgrade: + HOPPER: + # Permission upgrade name: limitsupgrade-hopper + tier1: + max-level: 2 + upgrade: "1" # Adds 1 hopper per upgrade level. + island-min-level: "2" + vault-cost: "[level]*100" + tier2: + max-level: 5 + upgrade: "1" + island-min-level: "4" + vault-cost: "([level]-2)*[numberPlayer]*700" # Scales with team size for advanced upgrades. + permission-level: 1 # Requires permission level 1 to access. + +# ====================== +# Entity Limits Upgrade Configuration +# ====================== +# Defines tiers for upgrades specific to entities (e.g., chickens). +# UPermission upgrade name format: limitsupgrade-[ENTITY] (lowercase entity name). +entity-limits-upgrade: + CHICKEN: + # Permission upgrade name: limitsupgrade-chicken + tier1: + max-level: 2 + upgrade: "1" # Adds 1 chicken limit per upgrade level. + island-min-level: "2" + vault-cost: "[level]*100" + tier2: + max-level: 5 + upgrade: "1" + island-min-level: "4" + vault-cost: "([level]-2)*[numberPlayer]*700" + permission-level: 3 # Requires permission level 3 for advanced tiers. + +# ====================== +# Entity Group Limits Upgrade Configuration +# ====================== +# Defines upgrades for groups of entities. Group names must match those in the Limits addon. +# Upgrade name format: limitsupgrade-[GROUP] (lowercase group name). +entity-group-limits-upgrade: + # this name has to match with your entity group names in the limits addon if installed + group1: + # Permission upgrade name: limitsupgrade-group1 + tier1: + max-level: 2 + upgrade: "1" # Adds 1 group entity limit per upgrade level. + island-min-level: "2" + vault-cost: "[level]*100" + tier2: + max-level: 5 + upgrade: "1" + island-min-level: "4" + vault-cost: "([level]-2)*[numberPlayer]*700" + permission-level: 3 + +# ====================== +# Command Upgrade Configuration +# ====================== +# Defines upgrades that trigger commands when unlocked. +# Variables: +# [player]: Name of the player unlocking the upgrade. +# [level]: Upgrade level. +# [owner]: Name of the island owner. +# Permission upgrade name: command-[NAME] -> In lower case + NAME != name: +command-upgrade: + lambda-upgrade: + # Permission upgrade name: command-lambda-upgrade + name: "Lambda upgrade" + tier1: + max-level: 1 + island-min-level: "2" + vault-cost: "[level]*100" + console: true # Indicates the command is executed by the server console. + command: + - "say [player] has upgrade his lambda to level [level]" + tier2: + max-level: 2 + island-min-level: "2" + vault-cost: "[level]*200" + console: true + command: + - "say [player] has upgrade his lambda to level [level]" + - "say [player] has reached the max level" + +# ====================== +# GameMode-Specific Configurations +# ====================== +# Define custom tiers for specific game modes. +# List any tiers that you want to add +# You can overwrite a tier by using the same name +gamemodes: + BSkyBlock: + range-upgrade: + tier3: + max-level: 15 + upgrade: "5" + island-min-level: "6" + vault-cost: "[level]*[numberPlayer]*500" + + block-limits-upgrade: + HOPPER: + tier1: + max-level: 2 + upgrade: "1" + island-min-level: "2" + vault-cost: "[level]*200" + + entity-limits-upgrade: + CHICKEN: + tier1: + max-level: 2 + upgrade: "1" + island-min-level: "2" + vault-cost: "[level]*200" + + entity-group-limits-upgrade: + # has to match with the entity group name in the limits addon + group1: + tier1: + max-level: 2 + upgrade: "1" + island-min-level: "2" + vault-cost: "[level]*200" + + command-upgrade: + lambda-upgrade: + tier2: + max-level: 2 + island-min-level: "2" + vault-cost: "[level]*200" + console: true + command: + - "say [player] has upgrade his lambda to level [level]" + - "say [player] has attained max on BSkyBlock" + +# ====================== +# Icons for UI Representation +# ====================== +# Define icons used to represent entities, groups, and commands in the user interface. +entity-icon: + CHICKEN: CHICKEN_SPAWN_EGG + +entity-group-icon: + group1: CHICKEN_SPAWN_EGG + +command-icon: + lambda-upgrade: GRASS_BLOCK diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index dccc554..5261a72 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -1,17 +1,232 @@ -islandupgrades: - commands: - main: - description: "Open the upgrade shop interface" - ui: - upgradepanel: - title: "Island upgrade shop" - rangeupgrade: "Range upgrade of [rangelevel]" - norangeupgrade: "§7Range upgrade" - rangeupgradedone: "§aYour island range was upgraded of [rangelevel] blocks" - maxlevel: "Max level reached" - islandneed: "Island Min Level: [islandlevel]" - moneycost: "Money cost: [cost]" - tryreloadlevel: "Don't forget to update your island level with the level command" - error: - rangeovermax: "§cYou tried to upgrade you island range over the max? You should talk about this to an administrator" - costwithdraw: "§cCouldn't withdraw money. You should talk about this to an administrator" \ No newline at end of file +upgrades: + commands: + main: + parameters: "" + description: "Open the upgrade shop interface" + admin: + description: "Open the admin interface for upgrade" + error: + costwithdraw: "&c Couldn't withdraw money. You should talk about this to an administrator" + increasenolimits: "&c You can't increase the limits of something not limited. You should talk about this to an administrator" + notonisland: "&c You need to be on your island to do that" + rangeovermax: "&c You tried to upgrade you island range over the max? You should talk about this to an administrator" + placeblock: "&c Before buying this upgrade, you need to place/break at least one block" + loaderror: "&c [what] faild to load. You should look at the consol for error" + upgradeinvalid: "&c Upgrade [what] [upgradeId] not valid. It will be ignored" + unknownerror: "&c An error happened. You should look at the consol for error" + noiteminhand: "&c You dont have any item in your main hand" + message: + skipupgradeload: "&c Upgrade [what] [upgradeId] was already loaded" + upgradeload: "&a Upgrade [what] [upgradeId] was loaded" + chatinput: + admin: + question: + getupgradeid: "Write the unique name for this upgrade" + gettierid: "Write the unique name for this tier" + getupgradedataname: |- + Write shown name + Actual is '[name]' + getupgradedatadesc: |- + Write the upgrade description. Each message will be a new line in description + Write '[end]' to end prompt + getupgradedataorder: |- + Write the upgrade order. Default is -1, -1 is last. + For upgrade on the same order, alphabetical order is used. + getupgradetiername: |- + Write shown name + Actual is '[name]' + getupgradetierdesc: |- + Write the tier description. Each message will be a new line in description + Write '[end]' to end prompt + invalid: + getupgradeid: "&c This name is already taken. Choose another one" + gettierid: "&c This name is already taken. Choose another one" + getupgradedataorder: "&c Order need to be a number greater than -1" + ui: + upgradepanel: + islandneed: "Island Min Level: [islandlevel]" + limitsupgrade: "[block] limits upgrade of [level]" + limitsupgradedone: "&a Your island [block] limits was upgraded by [level]" + maxlevel: "Max level reached" + moneycost: "Money cost: [cost]" + nolimitsupgrade: "&7[block] limits upgrade" + norangeupgrade: "&7 Range upgrade" + rangeupgrade: "Range upgrade of [rangelevel]" + rangeupgradedone: "&a Your island range was upgraded by [rangelevel] blocks" + tiernameandlevel: "&e&o&l[name]&e &7(&a[current] &7/ &2[max]&7)" + title: "Island upgrade shop" + tryreloadlevel: "Don't forget to update your island level with the level command" + previous: + name: "&7Previous page" + description: "&7Go to previous page" + tooltip: "Click to go to previous page" + next: + name: "&7Next page" + description: "&7Go to next page" + tooltip: "Click to go to next page" + upgrade: + tooltip: "Click to purchase upgrade" + editupgradepanel: + active: "Active" + activedesc: "Click to turn inactive" + inactive: "Inactive" + inactivedesc: "Click to turn active" + description: "Actual description" + namedesc: "Actual name, showed to the player" + icon: "Upgrade icon" + icondesc: |- + Click to change + Will take item in hand + tieradd: "Add tier" + tieredit: "Edit tier" + tierdelete: "Delete tier" + tierrequired: "Add at least one tier before editing or deleting" + order: "Actual order: [order]" + orderdesc: |- + Click to change order + upgrade of the same order will be sorted by alphabetical order + edittierpanel: + namedesc: "Actual name, showed to the player" + description: "Actual description" + icon: "Tier icon" + icondesc: |- + Click to change + Will take item in hand + nblevel: "Nb of time you can buy this tier" + nbleveldesc: |- + Actual [actual] + Left click to lower the nb + Right click to increase the nb + order: "Change the order of tiers" + orderdesc: |- + Actual [actual]/[max] + Left click to lower the order + Right click to increase the order + prices: "Prices" + rewards: "Rewards" + listadmintierpricepanel: + title: "List of prices" + create: "Add new price" + leftdesc: "&cLeft click: update" + rightdesc: "&cRight click: delete" + listadminpricepanel: + title: "List of available prices" + listadmintierrewardpanel: + title: "List of rewards" + create: "Add new reward" + leftdesc: "&cLeft click: update" + rightdesc: "&cRight click: delete" + listadminrewardpanel: + title: "List of available rewards" + islandlevelpricepanel: + title: "Island level price" + buttons: + return: "Return" + exit: "Exit" + yesbutton: "Yes" + nobutton: "No" + addupgrade: "Add upgrade" + deleteupgrade: "Delete upgrade" + editupgrade: "Edit upgrade" + active: "Active" + validconf: "Valid configuration" + invalidconf: "Invalid configuration" + noupgrades: "No upgrades yet — click 'Add upgrade' to create one" + leftclickedit: "&aLeft click: Edit" + rightclickdelete: "&cRight click: Delete" + titles: + adminupgrade: "Admin upgrade" + delete: "Delete: [name]" + editlist: "List: edit" + deletelist: "List: delete" + listupgradetier: "List tier for upgrade [upgradedata]" + listupgradeprices: "List of price for this tier" + prices: + islandlevel: + name: "Island level" + description: "Min. island level: [level]" + admindescription: |- + Add a minimum required level + This price don't use anything + paneltitle: "Island level price" + rulequestion: |- + Enter the new rule for this island level price + Actual: [actual] + money: + name: "Money" + description: "Costs [amount] money" + admindescription: "Charge Vault currency" + paneltitle: "Money price" + rulequestion: |- + Enter amount formula (e.g. 100*[level]) + Actual: [actual] + item: + name: "Item" + description: "Costs [amount]x [item]" + admindescription: "Requires items from inventory" + paneltitle: "Item price" + permission: + name: "Permission" + description: "Requires permission [permission]" + admindescription: "Gate by permission node" + paneltitle: "Permission price" + rulequestion: |- + Enter permission node + Actual: [actual] + rewards: + rangeupgrade: + name: "Range upgrade" + description: "Increase border by [rangelevel] blocks" + admindescription: |- + Increase the island border size + Careful to not go over the island limit + paneltitle: "Range upgrade reward" + rulequestion: |- + Enter the range formula (e.g. 5, or 2*[level]) + Actual: [actual] + invalidrule: "Invalid formula. Use a number or math expression (e.g. 5, 2*[level])" + setformula: "Set range formula" + formulastatus: "Formula OK" + formulaneeded: "Formula needed" + formulaneededdesc: "Click the paper icon to set a range formula" + limits: + name: "Limits upgrade" + description: "Increases [type] limit for [target] by [amount]" + admindescription: "Increase block/entity limits (requires Limits addon)" + paneltitle: "Limits reward" + command: + name: "Command" + description: "Runs commands on upgrade" + admindescription: "Execute console or player commands" + paneltitle: "Command reward" + spawner: + name: "Spawner Boost" + description: "+[amount] entities per spawner trigger" + admindescription: "Spawn extra entities per spawner activation" + paneltitle: "Spawner boost reward" + rulequestion: |- + Enter spawn bonus formula (e.g. 0.5, 0.1*[level]) + Actual: [actual] + invalidrule: "Invalid formula. Use a number or math expression (e.g. 0.5, 0.1*[level])" + setformula: "Set spawn bonus formula" + formulastatus: "Formula OK" + formulaneeded: "Formula needed" + formulaneededdesc: "Click the paper icon to set a spawn bonus formula" + cropgrowth: + name: "Crop Growth Boost" + description: "+[amount] extra growth trigger per crop tick" + admindescription: "Increase crop growth speed per island" + paneltitle: "Crop growth reward" + rulequestion: |- + Enter growth bonus formula (e.g. 0.5, 0.1*[level]) + Actual: [actual] + invalidrule: "Invalid formula. Use a number or math expression (e.g. 0.5, 0.1*[level])" + setformula: "Set growth bonus formula" + formulastatus: "Formula OK" + formulaneeded: "Formula needed" + formulaneededdesc: "Click the paper icon to set a growth bonus formula" +protection: + flags: + UPGRADES_RANK_RIGHT: + description: "Switch who can upgrade" + name: "Upgrade Rank" diff --git a/src/main/resources/locales/fr.yml b/src/main/resources/locales/fr.yml new file mode 100644 index 0000000..2b0314a --- /dev/null +++ b/src/main/resources/locales/fr.yml @@ -0,0 +1,25 @@ +upgrades: + commands: + main: + description: Ouvre l'interface d'achats d'améliorations + ui: + upgradepanel: + title: Magasin d'améliorations de l'ile + rangeupgrade: Amélioration de [rangelevel] blocs + norangeupgrade: "&7 Zone agrandie" + rangeupgradedone: "&a La zone de ton ile a été augmentée de [rangelevel] blocs" + limitsupgrade: "[block] limite augmentée de [level]" + nolimitsupgrade: "&7 [block] limites augmentée" + limitsupgradedone: "&a La limite de [block] de ton ile a été augmentée de [level]" + maxlevel: Niveau maximum atteint + islandneed: 'Niveau minimum de l''''ile: [islandlevel]' + moneycost: 'Coût: [cost]' + tryreloadlevel: N'oublie pas de mettre à jour le niveau de ton île + tiernameandlevel: "&e&o&l[name]&e &7(&a[current] &7/ &2[max]&7)" + error: + notonisland: "&c Tu dois etre sur ton ile pour faire ça" + rangeovermax: "&c Tu as essayé d'améliorer ton île au dessus du maximum? Contacte + un administrateur" + increasenolimits: "&c Tu ne peux pas augmenter la limite de quelque chose sans + limite. Contacte un administrateur" + costwithdraw: "&c Impossible de retirer l'argent. Contacte un administrateur" diff --git a/src/main/resources/locales/ja.yml b/src/main/resources/locales/ja.yml new file mode 100644 index 0000000..8818fc3 --- /dev/null +++ b/src/main/resources/locales/ja.yml @@ -0,0 +1,23 @@ +upgrades: + commands: + main: + description: アップグレードショップのインターフェースを開く + ui: + upgradepanel: + title: アイランドアップグレードショップ + rangeupgrade: "[rangelevel]の範囲アップグレード" + norangeupgrade: "&7範囲アップグレード" + rangeupgradedone: "&a島の範囲が[rangelevel]ブロックにアップグレードされました" + limitsupgrade: "[block]は[level]のアップグレードを制限します" + nolimitsupgrade: "&7 [block]制限アップグレード" + limitsupgradedone: "&aあなたの島の[block]制限が[level]にアップグレードされました" + maxlevel: 最大レベルに達しました + islandneed: 島の最小レベル:[islandlevel] + moneycost: 費用:[cost] + tryreloadlevel: levelコマンドで島のレベルを更新することを忘れないでください + tiernameandlevel: "&e&o&l[name]&e &7(&a[current] &7/ &2[max]&7)" + error: + notonisland: "&cあなたはそれをするためにあなたの島にいる必要があります" + rangeovermax: "&c島の範囲を最大値以上にアップグレードしようとしましたか?これについて管理者に相談する必要があります" + increasenolimits: "&c制限されていないものの制限を増やすことはできません。これについて管理者に相談する必要があります" + costwithdraw: "&c出金できませんでした。これについて管理者に相談する必要があります" diff --git a/src/main/resources/locales/pl.yml b/src/main/resources/locales/pl.yml new file mode 100644 index 0000000..09d4617 --- /dev/null +++ b/src/main/resources/locales/pl.yml @@ -0,0 +1,36 @@ +--- +upgrades: + commands: + main: + description: Otwórz interfejs sklepu z aktualizacjami + error: + costwithdraw: "&c Nie można wypłacić pieniędzy. Powinieneś porozmawiać o tym z + administratorem" + increasenolimits: "&c Nie możesz zwiększyć granic czegoś, co nie jest ograniczone. + Powinieneś porozmawiać o tym z administratorem" + notonisland: "&c Aby to zrobić, musisz być na swojej wyspie" + rangeovermax: "&c Próbowałeś zwiększyć zasięg swojej wyspy powyżej maksimum? Powinieneś + porozmawiać o tym z administratorem" + placeblock: "&c Przed zakupem tej aktualizacji musisz umieścić/złamać co najmniej + jeden blok" + ui: + upgradepanel: + islandneed: 'Minimalny poziom wyspy: [islandlevel]' + limitsupgrade: Limit dla [block] to [level] + limitsupgradedone: "&a Limit [block] na Twojej wyspy zostały zwiększone na [level]" + maxlevel: Maksymalny poziom osiągnięty + moneycost: 'Koszt pieniężny: [cost]' + nolimitsupgrade: "&7[block] limit ulepszeń" + norangeupgrade: "&7 Ulepszenie zasięgu" + rangeupgrade: Ulepszenie zasięgu o [rangelevel] + rangeupgradedone: "&a Zasięg Twojej wyspy został powiększony o [rangelevel] + bloków" + tiernameandlevel: "&e&o&l[name]&e &7(&a[current] &7/ &2[max]&7)" + title: Sklep z ulepszeniami wyspy + tryreloadlevel: Nie zapomnij zaktualizować poziomu wyspy za pomocą polecenia + poziomu +protection: + flags: + UPGRADES_RANK_RIGHT: + description: Zmień, kto może uaktualnić + name: Ulepszenie diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml new file mode 100644 index 0000000..5533d98 --- /dev/null +++ b/src/main/resources/locales/zh-CN.yml @@ -0,0 +1,29 @@ +upgrades: + commands: + main: + description: "打开升级商店界面" + error: + costwithdraw: "&c 无法付款. 请将此问题告诉管理员" + increasenolimits: "&c 您不能给一个不受限制的内容加上限制. 请将此问题告诉管理员" + notonisland: "&c 必须要在自己的岛屿上才能这么做" + rangeovermax: "&c 尝试将岛屿边界扩大到最大值以上? 请将此问题告诉管理员" + placeblock: "&c 在购买此升级之前, 您至少需要放置/破坏一个方块" + ui: + upgradepanel: + islandneed: "岛屿最低等级: [islandlevel]" + limitsupgrade: "[block] limits upgrade of [level]" + limitsupgradedone: "&a 您岛屿的 [block] 限制已升级到 [level]" + maxlevel: "已达到最大等级" + moneycost: "需花费: [cost]" + nolimitsupgrade: "&7升级 [block] 限制" + norangeupgrade: "&7 边界升级" + rangeupgrade: "Range upgrade of [rangelevel]" + rangeupgradedone: "&a 您的岛屿边界已扩大 [rangelevel] 个方块" + tiernameandlevel: "&e&o&l[name]&e &7(&a[current] &7/ &2[max]&7)" + title: "岛屿升级商店" + tryreloadlevel: "不要忘了通过岛屿升级指令升级您的岛屿" +protection: + flags: + UPGRADES_RANK_RIGHT: + description: "切换可升级的玩家" + name: "升级等级" diff --git a/src/main/resources/panels/upgrades_panel.yml b/src/main/resources/panels/upgrades_panel.yml new file mode 100644 index 0000000..aff8449 --- /dev/null +++ b/src/main/resources/panels/upgrades_panel.yml @@ -0,0 +1,64 @@ +upgrades_panel: + title: upgrades.ui.upgradepanel.title + type: INVENTORY + background: + icon: BLACK_STAINED_GLASS_PANE + title: " " + border: + icon: BLACK_STAINED_GLASS_PANE + title: " " + force-shown: [] + content: + 1: + 1: upgrade_button + 2: upgrade_button + 3: upgrade_button + 4: upgrade_button + 5: upgrade_button + 6: upgrade_button + 7: upgrade_button + 2: + 1: upgrade_button + 2: upgrade_button + 3: upgrade_button + 4: upgrade_button + 5: upgrade_button + 6: upgrade_button + 7: upgrade_button + 3: + 1: upgrade_button + 2: upgrade_button + 3: upgrade_button + 4: upgrade_button + 5: upgrade_button + 6: upgrade_button + 7: upgrade_button + 5: + 3: + icon: ARROW + title: upgrades.ui.upgradepanel.previous.name + description: upgrades.ui.upgradepanel.previous.description + data: + type: PREVIOUS + actions: + previous: + click-type: UNKNOWN + tooltip: upgrades.ui.upgradepanel.previous.tooltip + 5: + icon: ARROW + title: upgrades.ui.upgradepanel.next.name + description: upgrades.ui.upgradepanel.next.description + data: + type: NEXT + actions: + next: + click-type: UNKNOWN + tooltip: upgrades.ui.upgradepanel.next.tooltip + reusable: + upgrade_button: + data: + type: UPGRADE + actions: + upgrade: + click-type: UNKNOWN + tooltip: upgrades.ui.upgradepanel.upgrade.tooltip diff --git a/src/test/java/world/bentobox/upgrades/UpgradesAddonTest.java b/src/test/java/world/bentobox/upgrades/UpgradesAddonTest.java new file mode 100644 index 0000000..d4d3756 --- /dev/null +++ b/src/test/java/world/bentobox/upgrades/UpgradesAddonTest.java @@ -0,0 +1,442 @@ +package world.bentobox.upgrades; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.logging.Logger; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.UnsafeValues; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.PluginManager; +import org.bukkit.scheduler.BukkitScheduler; +import org.eclipse.jdt.annotation.NonNull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.MockitoAnnotations; +import org.mockito.Mockito; +import org.mockito.stubbing.Answer; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.Settings; +import world.bentobox.bentobox.api.addons.Addon.State; +import world.bentobox.bentobox.api.addons.AddonDescription; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.DatabaseSetup.DatabaseType; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.hooks.VaultHook; +import world.bentobox.bentobox.managers.AddonsManager; +import world.bentobox.bentobox.managers.CommandsManager; +import world.bentobox.bentobox.managers.FlagsManager; +import world.bentobox.bentobox.managers.IslandWorldManager; +import world.bentobox.bentobox.managers.IslandsManager; +import world.bentobox.bentobox.managers.PlaceholdersManager; +import world.bentobox.upgrades.api.UpgradeAPI; + +/** + * @author tastybento + */ +public class UpgradesAddonTest { + + private static File jFile; + @Mock + private User user; + @Mock + private IslandsManager im; + @Mock + private Island island; + @Mock + private BentoBox plugin; + @Mock + private FlagsManager fm; + @Mock + private GameModeAddon gameMode; + @Mock + private AddonsManager am; + @Mock + private BukkitScheduler scheduler; + + @Mock + private Settings pluginSettings; + + private UpgradesAddon addon; + + @Mock + private Logger logger; + @Mock + private PlaceholdersManager phm; + @Mock + private CompositeCommand cmd; + @Mock + private CompositeCommand adminCmd; + @Mock + private World world; + + @Mock + private PluginManager pim; + @Mock + private VaultHook vh; + private final @NonNull String targetIslandId = UUID.randomUUID().toString(); + private MockedStatic mockBukkit; + private MockedStatic mockIslandsManager; + + @BeforeAll + public static void beforeClass() throws IOException { + // Make the addon jar + jFile = new File("addon.jar"); + // Copy over config file from src folder + Path fromPath = Paths.get("src/main/resources/config.yml"); + Path path = Paths.get("config.yml"); + Files.copy(fromPath, path); + try (JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(jFile))) { + // Add config.yml + addFileToJar(tempJarOutputStream, path.toFile(), "config.yml"); + // Add panel template + addFileToJar(tempJarOutputStream, + Paths.get("src/main/resources/panels/upgrades_panel.yml").toFile(), + "panels/upgrades_panel.yml"); + } + } + + private static void addFileToJar(JarOutputStream jar, File file, String entryName) throws IOException { + try (FileInputStream fis = new FileInputStream(file)) { + byte[] buffer = new byte[1024]; + int bytesRead; + JarEntry entry = new JarEntry(entryName); + jar.putNextEntry(entry); + while ((bytesRead = fis.read(buffer)) != -1) { + jar.write(buffer, 0, bytesRead); + } + } + } + /** + */ + @SuppressWarnings("deprecation") + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + MockBukkit.mock(); + mockBukkit = Mockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS); + mockIslandsManager = Mockito.mockStatic(IslandsManager.class, Mockito.RETURNS_MOCKS); + // Set up plugin + WhiteBox.setInternalState(BentoBox.class, "instance", plugin); + + // The database type has to be created one line before the thenReturn() to work! + DatabaseType value = DatabaseType.JSON; + when(plugin.getSettings()).thenReturn(pluginSettings); + when(pluginSettings.getDatabaseType()).thenReturn(value); + + // Command manager + CommandsManager cm = mock(CommandsManager.class); + when(plugin.getCommandsManager()).thenReturn(cm); + + // Player + Player p = mock(Player.class); + when(user.isOp()).thenReturn(false); + UUID uuid = UUID.randomUUID(); + when(user.getUniqueId()).thenReturn(uuid); + when(user.getPlayer()).thenReturn(p); + when(user.getName()).thenReturn("tastybento"); + User.setPlugin(plugin); + + // Island World Manager + IslandWorldManager iwm = mock(IslandWorldManager.class); + when(plugin.getIWM()).thenReturn(iwm); + + // Player has island to begin with + when(im.getIsland(any(), any(UUID.class))).thenReturn(island); + when(plugin.getIslands()).thenReturn(im); + + // Locales + // Return the reference (USE THIS IN THE FUTURE) + when(user.getTranslation(anyString())) + .thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); + + // Server + Server server = mock(Server.class); + when(Bukkit.getServer()).thenReturn(server); + when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger()); + when(Bukkit.getPluginManager()).thenReturn(mock(PluginManager.class)); + + // Addon + addon = new UpgradesAddon(); + File dataFolder = new File("addons/Bank"); + addon.setDataFolder(dataFolder); + addon.setFile(jFile); + AddonDescription desc = new AddonDescription.Builder("bentobox", "Upgrades", "1.3").description("test") + .authors("tastybento").build(); + addon.setDescription(desc); + // Addons manager + when(am.getGameModeAddons()).thenReturn(List.of()); // No game modes + when(plugin.getAddonsManager()).thenReturn(am); + // One game mode + when(am.getGameModeAddons()).thenReturn(Collections.singletonList(gameMode)); + AddonDescription desc2 = new AddonDescription.Builder("bentobox", "BSkyBlock", "1.3").description("test") + .authors("tasty").build(); + when(gameMode.getDescription()).thenReturn(desc2); + when(gameMode.getOverWorld()).thenReturn(world); + + // Player command + @NonNull + Optional opCmd = Optional.of(cmd); + when(gameMode.getPlayerCommand()).thenReturn(opCmd); + // Admin command + Optional opAdminCmd = Optional.of(adminCmd); + when(gameMode.getAdminCommand()).thenReturn(opAdminCmd); + + // Flags manager + when(plugin.getFlagsManager()).thenReturn(fm); + when(fm.getFlags()).thenReturn(Collections.emptyList()); + + // Bukkit + when(Bukkit.getScheduler()).thenReturn(scheduler); + ItemMeta meta = mock(ItemMeta.class); + ItemFactory itemFactory = mock(ItemFactory.class); + when(itemFactory.getItemMeta(any())).thenReturn(meta); + when(Bukkit.getItemFactory()).thenReturn(itemFactory); + UnsafeValues unsafe = mock(UnsafeValues.class); + when(unsafe.getDataVersion()).thenReturn(777); + when(Bukkit.getUnsafe()).thenReturn(unsafe); + when(Bukkit.getPluginManager()).thenReturn(pim); + + // placeholders + when(plugin.getPlaceholdersManager()).thenReturn(phm); + + // World + when(world.getName()).thenReturn("bskyblock-world"); + // Island + when(island.getWorld()).thenReturn(world); + when(island.getOwner()).thenReturn(uuid); + + // Vault + when(plugin.getVault()).thenReturn(Optional.of(vh)); + } + + /** + * @throws java.lang.Exception + */ + @AfterEach + public void tearDown() throws Exception { + MockBukkit.unmock(); + mockBukkit.closeOnDemand(); + mockIslandsManager.closeOnDemand(); + deleteAll(new File("database")); + } + + @AfterAll + public static void cleanUp() throws Exception { + new File("addon.jar").delete(); + new File("config.yml").delete(); + deleteAll(new File("addons")); + } + + private static void deleteAll(File file) throws IOException { + if (file.exists()) { + Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#onEnable()}. + */ + @Test + public void testOnEnableDisabled() { + addon.onLoad(); + addon.setState(State.DISABLED); + addon.onEnable(); + verify(plugin).logWarning("[Upgrades] Upgrades Addon is not available or disabled!"); + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#onEnable()}. + */ + @Test + public void testOnEnableNoAddons() { + addon.onLoad(); + addon.setState(State.ENABLED); + addon.onEnable(); + verify(plugin).logWarning("[Upgrades] Level addon not found so Upgrades won't look for Island Level"); + verify(plugin).logWarning("[Upgrades] Limits addon not found so Island Upgrade won't look for IslandLevel"); + verify(plugin).log("[Upgrades] Upgrades addon enabled"); + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#onDisable()}. + */ + @Test + public void testOnDisable() { + addon.onDisable(); + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#onLoad()}. + */ + @Test + public void testOnLoad() { + addon.onLoad(); + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#onReload()}. + */ + @Test + public void testOnReload() { + addon.onReload(); + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#getSettings()}. + */ + @Test + public void testGetSettings() { + addon.getSettings(); + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#getUpgradesManager()}. + */ + @Test + public void testGetUpgradesManager() { + addon.getUpgradesManager(); + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#getDatabase()}. + */ + @Test + public void testGetDatabase() { + addon.getDatabase(); + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#getUpgradesLevels(java.lang.String)}. + */ + @Test + public void testGetUpgradesLevels() { + addon.onLoad(); + addon.onEnable(); + addon.getUpgradesLevels(targetIslandId); + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#uncacheIsland(java.lang.String, boolean)}. + */ + @Test + public void testUncacheIsland() { + addon.uncacheIsland(targetIslandId, false); + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#getLevelAddon()}. + */ + @Test + public void testGetLevelAddon() { + addon.getLevelAddon(); + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#getLimitsAddon()}. + */ + @Test + public void testGetLimitsAddon() { + addon.getLimitsAddon(); + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#getVaultHook()}. + */ + @Test + public void testGetVaultHook() { + addon.getVaultHook(); + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#isLevelProvided()}. + */ + @Test + public void testIsLevelProvided() { + assertFalse(addon.isLevelProvided()); + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#isLimitsProvided()}. + */ + @Test + public void testIsLimitsProvided() { + assertFalse(addon.isLimitsProvided()); + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#isVaultProvided()}. + */ + @Test + public void testIsVaultProvided() { + assertFalse(addon.isVaultProvided()); + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#getAvailableUpgrades()}. + */ + @Test + public void testGetAvailableUpgrades() { + addon.getAvailableUpgrades(); + } + + /** + * Test method for {@link world.bentobox.upgrades.UpgradesAddon#registerUpgrade(world.bentobox.upgrades.api.Upgrade)}. + */ + @Test + public void testRegisterUpgrade() { + UpgradeAPI upgrade = new TestUpgrade(addon, "name", "Name", Material.ACACIA_BOAT); + addon.registerUpgrade(upgrade); + } + + private static class TestUpgrade extends UpgradeAPI { + + public TestUpgrade(UpgradesAddon addon, String name, String displayName, Material icon) { + super(addon, name, displayName, icon); + } + + @Override + public void updateUpgradeValue(User user, Island island) { + // Test implementation + } + + @Override + public boolean isShowed(User user, Island island) { + return true; + } + } + +} diff --git a/src/test/java/world/bentobox/upgrades/WhiteBox.java b/src/test/java/world/bentobox/upgrades/WhiteBox.java new file mode 100644 index 0000000..ed33b5a --- /dev/null +++ b/src/test/java/world/bentobox/upgrades/WhiteBox.java @@ -0,0 +1,26 @@ +package world.bentobox.upgrades; + +public class WhiteBox { + /** + * Sets the value of a private static field using Java Reflection. + * @param targetClass The class containing the static field. + * @param fieldName The name of the private static field. + * @param value The value to set the field to. + */ + public static void setInternalState(Class targetClass, String fieldName, Object value) { + try { + // 1. Get the Field object from the class + java.lang.reflect.Field field = targetClass.getDeclaredField(fieldName); + + // 2. Make the field accessible (required for private fields) + field.setAccessible(true); + + // 3. Set the new value. The first argument is 'null' for static fields. + field.set(null, value); + + } catch (NoSuchFieldException | IllegalAccessException e) { + // Wrap reflection exceptions in a runtime exception for clarity + throw new RuntimeException("Failed to set static field '" + fieldName + "' on class " + targetClass.getName(), e); + } + } +} diff --git a/src/test/java/world/bentobox/upgrades/api/UpgradeTest.java b/src/test/java/world/bentobox/upgrades/api/UpgradeTest.java new file mode 100644 index 0000000..6f94692 --- /dev/null +++ b/src/test/java/world/bentobox/upgrades/api/UpgradeTest.java @@ -0,0 +1,170 @@ +package world.bentobox.upgrades.api; + + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyDouble; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.UUID; + +import org.bukkit.Material; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import net.milkbowl.vault.economy.EconomyResponse; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.hooks.VaultHook; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.UpgradesManager; +import world.bentobox.upgrades.dataobjects.UpgradesData; + +/** + * @author tastybento + */ +public class UpgradeTest { + + @Mock + private UpgradesAddon addon; + @Mock + private User user; + @Mock + private Island island; + @Mock + private UpgradesData upgradesData; + @Mock + private UpgradesManager um; + @Mock + private VaultHook vh; + + private TestUpgrade testUpgrade; + + @BeforeEach + public void setUp() { + MockBukkit.mock(); + + MockitoAnnotations.openMocks(this); + + UUID userId = UUID.randomUUID(); + String islandId = UUID.randomUUID().toString(); + + when(user.getUniqueId()).thenReturn(userId); + when(island.getUniqueId()).thenReturn(islandId); + when(addon.getAddonByName("upgrades")).thenReturn(java.util.Optional.of(addon)); + when(addon.getUpgradesLevels(islandId)).thenReturn(upgradesData); + + when(um.getIslandLevel(island)).thenReturn(20); + when(addon.getUpgradesManager()).thenReturn(um); + + when(vh.has(any(), anyDouble())).thenReturn(true); // Player has money + when(addon.getVaultHook()).thenReturn(vh); + + testUpgrade = new TestUpgrade(addon, "test_upgrade", "Test Upgrade", Material.DIAMOND); + } + + @AfterEach + public void tearDown() { + MockBukkit.unmock(); + } + + @Test + public void testUpgradeInitialization() { + assertNotNull(testUpgrade.getUpgradesAddon()); + assertEquals("test_upgrade", testUpgrade.getName()); + assertEquals("Test Upgrade", testUpgrade.getDisplayName()); + assertEquals(Material.DIAMOND, testUpgrade.getIcon()); + } + + @Test + public void testCanUpgrade_WithSufficientResources() { + UpgradeAPI.UpgradeValues upgradeValues = testUpgrade.new UpgradeValues(5, 100, 1); + testUpgrade.setUpgradeValues(user, upgradeValues); + + when(addon.isLevelProvided()).thenReturn(true); + when(addon.getUpgradesManager().getIslandLevel(island)).thenReturn(5); + when(addon.isVaultProvided()).thenReturn(true); + when(addon.getVaultHook().has(user, 100)).thenReturn(true); + + assertTrue(testUpgrade.canUpgrade(user, island)); + } + + @Test + public void testCanUpgrade_WithInsufficientResources() { + UpgradeAPI.UpgradeValues upgradeValues = testUpgrade.new UpgradeValues(10, 200, 1); + testUpgrade.setUpgradeValues(user, upgradeValues); + + when(addon.isLevelProvided()).thenReturn(true); + when(addon.getUpgradesManager().getIslandLevel(island)).thenReturn(5); + when(addon.isVaultProvided()).thenReturn(true); + when(addon.getVaultHook().has(user, 200)).thenReturn(false); + + assertFalse(testUpgrade.canUpgrade(user, island)); + } + + @Test + public void testDoUpgrade_SuccessfulTransaction() { + UpgradeAPI.UpgradeValues upgradeValues = testUpgrade.new UpgradeValues(5, 100, 1); + testUpgrade.setUpgradeValues(user, upgradeValues); + + when(addon.isVaultProvided()).thenReturn(true); + when(addon.getVaultHook().withdraw(user, 100)) + .thenReturn(new EconomyResponse(100, 0, EconomyResponse.ResponseType.SUCCESS, "")); + + testUpgrade.doUpgrade(user, island); + verify(upgradesData).setUpgradeLevel("test_upgrade", 1); + } + + @Test + public void testDoUpgrade_FailedTransaction() { + UpgradeAPI.UpgradeValues upgradeValues = testUpgrade.new UpgradeValues(5, 100, 1); + testUpgrade.setUpgradeValues(user, upgradeValues); + + when(addon.isVaultProvided()).thenReturn(true); + when(addon.getVaultHook().withdraw(user, 100)) + .thenReturn(new EconomyResponse(100, 0, EconomyResponse.ResponseType.FAILURE, "Error")); + + assertFalse(testUpgrade.doUpgrade(user, island)); + } + + @Test + public void testGetAndSetDescription() { + String description = "Upgrade description"; + testUpgrade.setOwnDescription(user, description); + + assertEquals(description, testUpgrade.getOwnDescription(user)); + } + + @Test + public void testGetAndSetUpgradeValues() { + UpgradeAPI.UpgradeValues upgradeValues = testUpgrade.new UpgradeValues(5, 100, 1); + testUpgrade.setUpgradeValues(user, upgradeValues); + + assertEquals(upgradeValues, testUpgrade.getUpgradeValues(user)); + } + + private static class TestUpgrade extends UpgradeAPI { + + public TestUpgrade(UpgradesAddon addon, String name, String displayName, Material icon) { + super(addon, name, displayName, icon); + } + + @Override + public void updateUpgradeValue(User user, Island island) { + // Test implementation + } + + @Override + public boolean isShowed(User user, Island island) { + return true; + } + } +} diff --git a/src/test/java/world/bentobox/upgrades/command/PlayerUpgradeCommandTest.java b/src/test/java/world/bentobox/upgrades/command/PlayerUpgradeCommandTest.java new file mode 100644 index 0000000..63f344b --- /dev/null +++ b/src/test/java/world/bentobox/upgrades/command/PlayerUpgradeCommandTest.java @@ -0,0 +1,277 @@ +package world.bentobox.upgrades.command; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.MockitoAnnotations; +import org.mockito.Mockito; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.CommandsManager; +import world.bentobox.bentobox.managers.IslandWorldManager; +import world.bentobox.bentobox.managers.IslandsManager; +import world.bentobox.bentobox.managers.RanksManager; +import world.bentobox.bentobox.util.Util; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.UpgradesManager; +import world.bentobox.upgrades.WhiteBox; +import world.bentobox.upgrades.config.Settings; + +/** + * @author tastybento + */ +public class PlayerUpgradeCommandTest { + + @Mock + private UpgradesAddon addon; + @Mock + private World world; + @Mock + private CompositeCommand ic; + @Mock + private User user; + + @Mock + private Location location; + @Mock + private IslandsManager im; + @Mock + private Island island; + @Mock + private Player p; + private PlayerUpgradeCommand puc; + @Mock + private RanksManager rm; + @Mock + private IslandWorldManager iwm; + @Mock + private UpgradesManager um; + private MockedStatic bukkitMock; + private MockedStatic utilMock; + private MockedStatic ranksManagerStatic; + + + /** + * @throws java.lang.Exception + */ + @BeforeEach + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + MockBukkit.mock(); + // Config + YamlConfiguration config = new YamlConfiguration(); + File configFile = new File("src/main/resources/config.yml"); + assertTrue(configFile.exists()); + config.load(configFile); + + when(addon.getConfig()).thenReturn(config); + + // Set up plugin + BentoBox plugin = mock(BentoBox.class); + WhiteBox.setInternalState(BentoBox.class, "instance", plugin); + User.setPlugin(plugin); + + when(addon.getPlugin()).thenReturn(plugin); + + // RanksManager + when(rm.getRank(anyInt())).thenReturn(RanksManager.MEMBER_RANK_REF); + when(plugin.getRanksManager()).thenReturn(rm); + ranksManagerStatic = Mockito.mockStatic(RanksManager.class); + ranksManagerStatic.when(() -> RanksManager.getInstance()).thenReturn(rm); + + // Command manager + CommandsManager cm = mock(CommandsManager.class); + when(plugin.getCommandsManager()).thenReturn(cm); + // Addon + when(ic.getAddon()).thenReturn(addon); + when(ic.getPermissionPrefix()).thenReturn("bskyblock."); + when(ic.getLabel()).thenReturn("island"); + when(ic.getTopLabel()).thenReturn("island"); + when(ic.getWorld()).thenReturn(world); + when(ic.getTopLabel()).thenReturn("bsb"); + + // World + when(world.toString()).thenReturn("world"); + // Player + // Sometimes use Mockito.withSettings().verboseLogging() + when(user.isOp()).thenReturn(false); + UUID uuid = UUID.randomUUID(); + when(user.getUniqueId()).thenReturn(uuid); + when(user.getPlayer()).thenReturn(p); + when(user.getName()).thenReturn("tastybento"); + when(user.getPermissionValue(anyString(), anyInt())).thenReturn(-1); + when(user.isPlayer()).thenReturn(true); + when(user.getLocation()).thenReturn(location); + when(user.getTranslation(anyString())).thenReturn("translation"); + + // Util + utilMock = Mockito.mockStatic(Util.class); + utilMock.when(() -> Util.getWorld(any())).thenReturn(world); + + // Island Manager + when(plugin.getIslands()).thenReturn(im); + Optional opIsland = Optional.of(island); + when(im.getIslandAt(any())).thenReturn(opIsland); + + // Settings + Settings settings = new Settings(addon); + when(addon.getSettings()).thenReturn(settings); + + // Island + when(location.toVector()).thenReturn(new Vector(0, 60, 0)); + + // IWM + when(iwm.getFriendlyName(world)).thenReturn("BSkyBlock"); + when(plugin.getIWM()).thenReturn(iwm); + + // Upgrades manager + when(um.getIslandLevel(island)).thenReturn(20); + when(addon.getUpgradesManager()).thenReturn(um); + // Bukkit + bukkitMock = Mockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS); + + + puc = new PlayerUpgradeCommand(addon, ic); + } + + /** + */ + @AfterEach + public void tearDown() { + MockBukkit.unmock(); + User.clearUsers(); + bukkitMock.closeOnDemand(); + utilMock.closeOnDemand(); + ranksManagerStatic.closeOnDemand(); + } + + /** + * Test method for {@link world.bentobox.upgrades.command.PlayerUpgradeCommand#PlayerUpgradeCommand(world.bentobox.upgrades.UpgradesAddon, world.bentobox.bentobox.api.commands.CompositeCommand)}. + */ + @Test + public void testPlayerUpgradeCommand() { + assertEquals("upgrade", puc.getLabel()); + } + + /** + * Test method for {@link world.bentobox.upgrades.command.PlayerUpgradeCommand#setup()}. + */ + @Test + public void testSetup() { + assertEquals("", puc.getPermission()); + assertEquals("upgrades.commands.main.description", puc.getDescription()); + assertTrue(puc.isOnlyPlayer()); + } + + /** + * Test method for {@link world.bentobox.upgrades.command.PlayerUpgradeCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testCanExecuteNoIsland() { + assertFalse(puc.canExecute(user, "", List.of())); + verify(user).sendMessage("general.errors.no-island"); + } + + /** + * Test method for {@link world.bentobox.upgrades.command.PlayerUpgradeCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testCanExecuteNotOnIsland() { + when(im.getIsland(world, user)).thenReturn(island); + assertFalse(puc.canExecute(user, "", List.of())); + verify(user).sendMessage("upgrades.error.notonisland"); + } + + /** + * Test method for {@link world.bentobox.upgrades.command.PlayerUpgradeCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testCanExecuteInsufficientRank() { + when(im.getIsland(world, user)).thenReturn(island); + when(island.onIsland(location)).thenReturn(true); + assertFalse(puc.canExecute(user, "", List.of())); + verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "translation"); + } + + /** + * Test method for {@link world.bentobox.upgrades.command.PlayerUpgradeCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testCanExecuteSuccess() { + when(im.getIsland(world, user)).thenReturn(island); + when(island.onIsland(location)).thenReturn(true); + when(island.isAllowed(eq(user), any())).thenReturn(true); + assertTrue(puc.canExecute(user, "", List.of())); + } + + /** + * Test method for {@link world.bentobox.upgrades.command.PlayerUpgradeCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfStringNoIsland() { + assertFalse(puc.execute(user, "", List.of())); + verify(user).sendMessage("general.errors.no-island"); + + } + + /** + * Test method for {@link world.bentobox.upgrades.command.PlayerUpgradeCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfStringShowHelp() { + assertFalse(puc.execute(user, "", List.of("random"))); + verify(user).sendMessage("commands.help.header", "[label]", "BSkyBlock"); + + } + + /** + * Test method for {@link world.bentobox.upgrades.command.PlayerUpgradeCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteNotOnIsland() { + when(im.getIsland(world, user)).thenReturn(island); + assertFalse(puc.execute(user, "", List.of())); + verify(user).sendMessage("upgrades.error.notonisland"); + } + + /** + * Test method for {@link world.bentobox.upgrades.command.PlayerUpgradeCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteSuccess() { + when(im.getIsland(world, user)).thenReturn(island); + when(island.onIsland(location)).thenReturn(true); + when(island.isAllowed(eq(user), any())).thenReturn(true); + assertTrue(puc.execute(user, "", List.of())); + } + +} diff --git a/src/test/java/world/bentobox/upgrades/config/SettingsTest.java b/src/test/java/world/bentobox/upgrades/config/SettingsTest.java new file mode 100644 index 0000000..0ec5411 --- /dev/null +++ b/src/test/java/world/bentobox/upgrades/config/SettingsTest.java @@ -0,0 +1,277 @@ +package world.bentobox.upgrades.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.Map; + +import org.bukkit.Material; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.EntityType; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.config.Settings.Expression; + +/** + * @author tastybento + */ +public class SettingsTest { + + @Mock + private UpgradesAddon addon; + private Settings settings; + + + /** + * @throws java.lang.Exception + */ + @BeforeEach + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + MockBukkit.mock(); + // Config + YamlConfiguration config = new YamlConfiguration(); + File configFile = new File("src/main/resources/config.yml"); + assertTrue(configFile.exists()); + config.load(configFile); + + when(addon.getConfig()).thenReturn(config); + + settings = new Settings(addon); + } + + @AfterEach + public void tearDown() { + MockBukkit.unmock(); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#Settings(world.bentobox.upgrades.UpgradesAddon)}. + */ + @Test + public void testSettings() { + assertNotNull(settings); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getDisabledGameModes()}. + */ + @Test + public void testGetDisabledGameModes() { + assertTrue(settings.getDisabledGameModes().isEmpty()); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getHasRangeUpgrade()}. + */ + @Test + public void testGetHasRangeUpgrade() { + assertTrue(settings.getHasRangeUpgrade()); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getMaxRangeUpgrade(java.lang.String)}. + */ + @Test + public void testGetMaxRangeUpgrade() { + assertEquals(10, settings.getMaxRangeUpgrade("")); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getDefaultRangeUpgradeTierMap()}. + */ + @Test + public void testGetDefaultRangeUpgradeTierMap() { + assertFalse(settings.getDefaultRangeUpgradeTierMap().isEmpty()); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getAddonRangeUpgradeTierMap(java.lang.String)}. + */ + @Test + public void testGetAddonRangeUpgradeTierMap() { + assertTrue(settings.getAddonRangeUpgradeTierMap("").isEmpty()); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getMaxBlockLimitsUpgrade(org.bukkit.Material, java.lang.String)}. + */ + @Test + public void testGetMaxBlockLimitsUpgrade() { + assertEquals(0, settings.getMaxBlockLimitsUpgrade(Material.STONE, "")); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getDefaultBlockLimitsUpgradeTierMap()}. + */ + @Test + public void testGetDefaultBlockLimitsUpgradeTierMap() { + assertFalse(settings.getDefaultBlockLimitsUpgradeTierMap().isEmpty()); + + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getAddonBlockLimitsUpgradeTierMap(java.lang.String)}. + */ + @Test + public void testGetAddonBlockLimitsUpgradeTierMap() { + assertTrue(settings.getAddonBlockLimitsUpgradeTierMap("").isEmpty()); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getMaterialsLimitsUpgrade()}. + */ + @Test + public void testGetMaterialsLimitsUpgrade() { + assertFalse(settings.getMaterialsLimitsUpgrade().isEmpty()); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getEntityIcon(org.bukkit.entity.EntityType)}. + */ + @Test + public void testGetEntityIcon() { + assertNull(settings.getEntityIcon(EntityType.CAT)); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getEntityGroupIcon(java.lang.String)}. + */ + @Test + public void testGetEntityGroupIcon() { + assertNull(settings.getEntityGroupIcon("")); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getMaxEntityLimitsUpgrade(org.bukkit.entity.EntityType, java.lang.String)}. + */ + @Test + public void testGetMaxEntityLimitsUpgrade() { + assertEquals(0, settings.getMaxEntityLimitsUpgrade(EntityType.HOPPER_MINECART, "")); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getMaxEntityGroupLimitsUpgrade(java.lang.String, java.lang.String)}. + */ + @Test + public void testGetMaxEntityGroupLimitsUpgrade() { + assertEquals(0, settings.getMaxEntityGroupLimitsUpgrade("", "")); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getDefaultEntityLimitsUpgradeTierMap()}. + */ + @Test + public void testGetDefaultEntityLimitsUpgradeTierMap() { + assertFalse(settings.getDefaultEntityLimitsUpgradeTierMap().isEmpty()); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getDefaultEntityGroupLimitsUpgradeTierMap()}. + */ + @Test + public void testGetDefaultEntityGroupLimitsUpgradeTierMap() { + assertFalse(settings.getDefaultEntityGroupLimitsUpgradeTierMap().isEmpty()); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getAddonEntityLimitsUpgradeTierMap(java.lang.String)}. + */ + @Test + public void testGetAddonEntityLimitsUpgradeTierMap() { + assertTrue(settings.getAddonEntityLimitsUpgradeTierMap("").isEmpty()); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getAddonEntityGroupLimitsUpgradeTierMap(java.lang.String)}. + */ + @Test + public void testGetAddonEntityGroupLimitsUpgradeTierMap() { + assertTrue(settings.getAddonEntityGroupLimitsUpgradeTierMap("").isEmpty()); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getEntityLimitsUpgrade()}. + */ + @Test + public void testGetEntityLimitsUpgrade() { + assertFalse(settings.getEntityLimitsUpgrade().isEmpty()); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getEntityGroupLimitsUpgrade()}. + */ + @Test + public void testGetEntityGroupLimitsUpgrade() { + assertFalse(settings.getEntityGroupLimitsUpgrade().isEmpty()); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getMaxCommandUpgrade(java.lang.String, java.lang.String)}. + */ + @Test + public void testGetMaxCommandUpgrade() { + assertEquals(0, settings.getMaxCommandUpgrade("", "")); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getDefaultCommandUpgradeTierMap()}. + */ + @Test + public void testGetDefaultCommandUpgradeTierMap() { + assertFalse(settings.getDefaultCommandUpgradeTierMap().isEmpty()); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getAddonCommandUpgradeTierMap(java.lang.String)}. + */ + @Test + public void testGetAddonCommandUpgradeTierMap() { + assertTrue(settings.getAddonCommandUpgradeTierMap("").isEmpty()); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getCommandUpgrade()}. + */ + @Test + public void testGetCommandUpgrade() { + assertFalse(settings.getCommandUpgrade().isEmpty()); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getCommandIcon(java.lang.String)}. + */ + @Test + public void testGetCommandIcon() { + assertNull(settings.getCommandIcon("")); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#getCommandName(java.lang.String)}. + */ + @Test + public void testGetCommandName() { + assertNull(settings.getCommandName("")); + } + + /** + * Test method for {@link world.bentobox.upgrades.config.Settings#parse(java.lang.String, java.util.Map)}. + */ + @Test + public void testParse() { + Expression expression = Settings.parse("40*200", Map.of()); + assertEquals(8000D, expression.eval(), 0.1D); + } + +} diff --git a/src/test/java/world/bentobox/upgrades/dataobjects/UpgradesDataTest.java b/src/test/java/world/bentobox/upgrades/dataobjects/UpgradesDataTest.java new file mode 100644 index 0000000..18f69be --- /dev/null +++ b/src/test/java/world/bentobox/upgrades/dataobjects/UpgradesDataTest.java @@ -0,0 +1,77 @@ +package world.bentobox.upgrades.dataobjects; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link UpgradesData} — per-island upgrade level storage. + * + * Critical invariant: getUpgradeLevel() must return 0 for a key that has + * never been set. If it returned 1 (the old broken default), a tier + * with startLevel=0..endLevel=0 would never match because findNextTier() + * would see currentLevel=0 and the check 0<=0<=0 is true, but with the + * old default of 1, it would see currentLevel=1 and 0<=1<=0 is false — + * causing every DB upgrade to appear permanently maxed on a fresh island. + */ +public class UpgradesDataTest { + + private static final String KEY = "BSkyBlock_diamond"; + private static final String ISLAND_ID = "island-uuid-1234"; + + private UpgradesData upgradesData; + + @BeforeEach + void setUp() { + upgradesData = new UpgradesData(ISLAND_ID); + } + + @Test + void getUniqueId_returnsConstructorValue() { + assertEquals(ISLAND_ID, upgradesData.getUniqueId()); + } + + @Test + void getUpgradeLevel_freshKey_returnsZeroNotOne() { + // This is the core regression test: the level must start at 0 so that + // a tier configured as startLevel=0, endLevel=0 is immediately purchasable. + assertEquals(0, upgradesData.getUpgradeLevel(KEY), + "Fresh island upgrade level must be 0 — returning 1 would make all DB upgrades appear permanently maxed"); + } + + @Test + void getUpgradeLevel_calledTwice_staysAtZero() { + // putIfAbsent must not overwrite an existing 0 on the second call. + assertEquals(0, upgradesData.getUpgradeLevel(KEY)); + assertEquals(0, upgradesData.getUpgradeLevel(KEY)); + } + + @Test + void setAndGet_roundTrip() { + upgradesData.setUpgradeLevel(KEY, 5); + assertEquals(5, upgradesData.getUpgradeLevel(KEY)); + } + + @Test + void setUpgradeLevel_toZero_returnsZero() { + upgradesData.setUpgradeLevel(KEY, 3); + upgradesData.setUpgradeLevel(KEY, 0); + assertEquals(0, upgradesData.getUpgradeLevel(KEY)); + } + + @Test + void differentUpgradesStoredIndependently() { + upgradesData.setUpgradeLevel("upgrade_a", 2); + upgradesData.setUpgradeLevel("upgrade_b", 7); + assertEquals(2, upgradesData.getUpgradeLevel("upgrade_a")); + assertEquals(7, upgradesData.getUpgradeLevel("upgrade_b")); + } + + @Test + void getUpgradeLevel_afterSetToZero_doesNotResetToDefault() { + // Explicitly set to 0, then read again — must NOT re-insert the default. + upgradesData.setUpgradeLevel(KEY, 0); + assertEquals(0, upgradesData.getUpgradeLevel(KEY)); + } +} diff --git a/src/test/java/world/bentobox/upgrades/dataobjects/prices/PricesTest.java b/src/test/java/world/bentobox/upgrades/dataobjects/prices/PricesTest.java new file mode 100644 index 0000000..2920cc5 --- /dev/null +++ b/src/test/java/world/bentobox/upgrades/dataobjects/prices/PricesTest.java @@ -0,0 +1,273 @@ +package world.bentobox.upgrades.dataobjects.prices; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +import java.util.Collections; +import java.util.UUID; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.UpgradesManager; + +/** + * Unit tests for the concrete {@link Price} implementations. Covers + * construction, name/description getters, the DB-aware description + * overloads and the {@code canPay} / {@code pay} branches. + */ +class PricesTest { + + @Mock private UpgradesAddon addon; + @Mock private UpgradesManager upgradesManager; + @Mock private User user; + @Mock private Island island; + @Mock private PlayerInventory inventory; + + @BeforeEach + void setUp() { + MockBukkit.mock(); + MockitoAnnotations.openMocks(this); + // Stub every getTranslation invocation to echo back the key, regardless of arity. + // User.getTranslation(String, String...) uses varargs; the single any(String[].class) + // argument matcher matches any number of varargs parameters. + when(user.getTranslation(anyString())).thenReturn("x"); + when(user.getTranslation(anyString(), any(String[].class))).thenReturn("x"); + when(user.getUniqueId()).thenReturn(UUID.randomUUID()); + when(island.getUniqueId()).thenReturn(UUID.randomUUID().toString()); + when(island.getMemberSet()).thenReturn(com.google.common.collect.ImmutableSet.of()); + when(addon.getUpgradesManager()).thenReturn(upgradesManager); + } + + @AfterEach + void tearDown() { + MockBukkit.unmock(); + } + + // ─── Constants defined on the abstract Price class ─────────────────── + + @Test + void priceConstants_matchExpectedPlaceholders() { + assertEquals("[level]", Price.LEVEL_VAR); + assertEquals("[islandLevel]", Price.ISLAND_LEVEL_VAR); + assertEquals("[numberPlayer]", Price.NUMBER_PLAYER_VAR); + } + + // ─── IslandLevelPrice ──────────────────────────────────────────────── + + @Test + void islandLevelPrice_metadataAndNames() { + IslandLevelPrice p = new IslandLevelPrice(); + assertEquals("island_level_price", p.getName()); + assertEquals(Material.EXPERIENCE_BOTTLE, p.getIcon()); + assertNotNull(p.getPublicName(user)); + assertNotNull(p.getAdminName(user)); + assertNotNull(p.getPublicDescription(user)); + assertNotNull(p.getAdminDescription(user)); + } + + @Test + void islandLevelPrice_publicDescriptionWithDb_usesStoredEquation() { + IslandLevelPrice p = new IslandLevelPrice(); + IslandLevelPriceDB db = new IslandLevelPriceDB(); + db.setLevelNeededEquation("42"); + assertNotNull(p.getPublicDescription(user, db)); + } + + @Test + void islandLevelPrice_canPay_trueWhenIslandLevelMeetsEquation() { + IslandLevelPrice p = new IslandLevelPrice(); + IslandLevelPriceDB db = new IslandLevelPriceDB(); + db.setLevelNeededEquation("10"); + when(upgradesManager.getIslandLevel(island)).thenReturn(15); + + assertTrue(p.canPay(addon, user, island, db, 1)); + } + + @Test + void islandLevelPrice_canPay_falseWhenIslandLevelBelowEquation() { + IslandLevelPrice p = new IslandLevelPrice(); + IslandLevelPriceDB db = new IslandLevelPriceDB(); + db.setLevelNeededEquation("100"); + when(upgradesManager.getIslandLevel(island)).thenReturn(5); + + assertFalse(p.canPay(addon, user, island, db, 1)); + } + + @Test + void islandLevelPrice_pay_isNoOp() { + IslandLevelPrice p = new IslandLevelPrice(); + assertDoesNotThrow(() -> p.pay(addon, user, island, new IslandLevelPriceDB(), 1)); + } + + @Test + void islandLevelPriceDB_validityChecks() { + IslandLevelPriceDB db = new IslandLevelPriceDB(); + assertFalse(db.isValid() && db.getLevelNeededEquation().isEmpty()); + db.setLevelNeededEquation("5"); + assertTrue(db.isValid()); + assertEquals(IslandLevelPrice.class, db.getPriceType()); + } + + // ─── MoneyPrice ────────────────────────────────────────────────────── + + @Test + void moneyPrice_metadataAndNames() { + MoneyPrice p = new MoneyPrice(); + assertEquals("money_price", p.getName()); + assertEquals(Material.GOLD_INGOT, p.getIcon()); + assertNotNull(p.getPublicName(user)); + assertNotNull(p.getAdminName(user)); + assertNotNull(p.getPublicDescription(user)); + assertNotNull(p.getAdminDescription(user)); + } + + @Test + void moneyPrice_publicDescriptionWithDb() { + MoneyPrice p = new MoneyPrice(); + MoneyPriceDB db = new MoneyPriceDB(); + db.setAmountEquation("500"); + assertNotNull(p.getPublicDescription(user, db)); + } + + @Test + void moneyPrice_canPay_returnsTrueWhenVaultNotProvided() { + MoneyPrice p = new MoneyPrice(); + when(addon.isVaultProvided()).thenReturn(false); + assertTrue(p.canPay(addon, user, island, new MoneyPriceDB(), 1)); + } + + @Test + void moneyPrice_pay_noOpWhenVaultNotProvided() { + MoneyPrice p = new MoneyPrice(); + when(addon.isVaultProvided()).thenReturn(false); + assertDoesNotThrow(() -> p.pay(addon, user, island, new MoneyPriceDB(), 1)); + } + + @Test + void moneyPriceDB_validityChecks() { + MoneyPriceDB db = new MoneyPriceDB(); + db.setAmountEquation("100"); + assertTrue(db.isValid()); + assertEquals(MoneyPrice.class, db.getPriceType()); + } + + // ─── PermissionPrice ───────────────────────────────────────────────── + + @Test + void permissionPrice_metadataAndNames() { + PermissionPrice p = new PermissionPrice(); + assertEquals("permission_price", p.getName()); + assertEquals(Material.TRIPWIRE_HOOK, p.getIcon()); + assertNotNull(p.getPublicName(user)); + assertNotNull(p.getAdminName(user)); + assertNotNull(p.getPublicDescription(user)); + assertNotNull(p.getAdminDescription(user)); + } + + @Test + void permissionPrice_publicDescriptionWithDb() { + PermissionPrice p = new PermissionPrice(); + PermissionPriceDB db = new PermissionPriceDB(); + db.setPermission("upgrades.special"); + assertNotNull(p.getPublicDescription(user, db)); + } + + @Test + void permissionPrice_canPay_delegatesToUserHasPermission() { + PermissionPrice p = new PermissionPrice(); + PermissionPriceDB db = new PermissionPriceDB(); + db.setPermission("upgrades.special"); + when(user.hasPermission("upgrades.special")).thenReturn(true); + assertTrue(p.canPay(addon, user, island, db, 1)); + + when(user.hasPermission("upgrades.special")).thenReturn(false); + assertFalse(p.canPay(addon, user, island, db, 1)); + } + + @Test + void permissionPrice_pay_isNoOp() { + PermissionPrice p = new PermissionPrice(); + assertDoesNotThrow(() -> p.pay(addon, user, island, new PermissionPriceDB(), 1)); + } + + @Test + void permissionPriceDB_validityChecks() { + PermissionPriceDB db = new PermissionPriceDB(); + assertFalse(db.isValid()); + db.setPermission("upgrades.special"); + assertTrue(db.isValid()); + assertEquals(PermissionPrice.class, db.getPriceType()); + } + + // ─── ItemPrice ─────────────────────────────────────────────────────── + + @Test + void itemPrice_metadataAndNames() { + ItemPrice p = new ItemPrice(); + assertEquals("item_price", p.getName()); + assertEquals(Material.CHEST, p.getIcon()); + assertNotNull(p.getPublicName(user)); + assertNotNull(p.getAdminName(user)); + assertNotNull(p.getPublicDescription(user)); + assertNotNull(p.getAdminDescription(user)); + } + + @Test + void itemPrice_publicDescriptionWithDb() { + ItemPrice p = new ItemPrice(); + ItemPriceDB db = new ItemPriceDB(); + db.setMaterial("DIAMOND"); + db.setAmount(3); + assertNotNull(p.getPublicDescription(user, db)); + } + + @Test + void itemPrice_canPay_trueWhenInventoryContainsEnough() { + ItemPrice p = new ItemPrice(); + ItemPriceDB db = new ItemPriceDB(); + db.setMaterial("DIAMOND"); + db.setAmount(3); + + when(user.getInventory()).thenReturn(inventory); + when(inventory.containsAtLeast(any(ItemStack.class), eq(3))).thenReturn(true); + + assertTrue(p.canPay(addon, user, island, db, 1)); + } + + @Test + void itemPrice_canPay_falseWhenMaterialInvalid() { + ItemPrice p = new ItemPrice(); + ItemPriceDB db = new ItemPriceDB(); + db.setMaterial("NOT_A_REAL_MATERIAL"); + db.setAmount(1); + + assertFalse(p.canPay(addon, user, island, db, 1)); + verify(addon).logWarning(anyString()); + } + + @Test + void itemPriceDB_validityChecks() { + ItemPriceDB db = new ItemPriceDB(); + assertFalse(db.isValid()); + db.setMaterial("DIAMOND"); + db.setAmount(5); + assertTrue(db.isValid()); + assertEquals(ItemPrice.class, db.getPriceType()); + assertEquals(5, db.getAmount()); + assertEquals("DIAMOND", db.getMaterial()); + } +} diff --git a/src/test/java/world/bentobox/upgrades/dataobjects/rewards/RewardsTest.java b/src/test/java/world/bentobox/upgrades/dataobjects/rewards/RewardsTest.java new file mode 100644 index 0000000..bb58ec5 --- /dev/null +++ b/src/test/java/world/bentobox/upgrades/dataobjects/rewards/RewardsTest.java @@ -0,0 +1,226 @@ +package world.bentobox.upgrades.dataobjects.rewards; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import org.bukkit.Material; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.UpgradesManager; + +/** + * Unit tests for the concrete {@link Reward} implementations. Covers + * construction, name/description getters, the DB-aware description + * overloads and the no-op branches of {@code apply}. + */ +class RewardsTest { + + @Mock private UpgradesAddon addon; + @Mock private UpgradesManager upgradesManager; + @Mock private User user; + @Mock private Island island; + + @BeforeEach + void setUp() { + MockBukkit.mock(); + MockitoAnnotations.openMocks(this); + when(user.getTranslation(anyString())).thenReturn("x"); + when(user.getTranslation(anyString(), any(String[].class))).thenReturn("x"); + when(user.getUniqueId()).thenReturn(UUID.randomUUID()); + when(island.getUniqueId()).thenReturn(UUID.randomUUID().toString()); + when(island.getMemberSet()).thenReturn(com.google.common.collect.ImmutableSet.of()); + when(addon.getUpgradesManager()).thenReturn(upgradesManager); + } + + @AfterEach + void tearDown() { + MockBukkit.unmock(); + } + + @Test + void rewardConstants_matchExpectedPlaceholders() { + assertEquals("[level]", Reward.LEVEL_VAR); + assertEquals("[islandLevel]", Reward.ISLAND_LEVEL_VAR); + assertEquals("[numberPlayer]", Reward.NUMBER_PLAYER_VAR); + } + + // ─── RangeReward ───────────────────────────────────────────────────── + + @Test + void rangeReward_metadataAndNames() { + RangeReward r = new RangeReward(); + assertEquals("range_reward", r.getName()); + assertEquals(Material.OAK_FENCE, r.getIcon()); + assertNotNull(r.getAdminName(user)); + assertNotNull(r.getAdminDescription(user)); + assertNotNull(r.getPublicName(user)); + assertNotNull(r.getPublicDescription(user)); + } + + @Test + void rangeReward_publicDescriptionWithDb() { + RangeReward r = new RangeReward(); + RangeRewardDB db = new RangeRewardDB(); + db.setRangeUpgradeEquation("5"); + assertNotNull(r.getPublicDescription(user, db)); + } + + @Test + void rangeRewardDB_validityChecks() { + RangeRewardDB db = new RangeRewardDB(); + assertTrue(db.isValid()); + db.setRangeUpgradeEquation(""); + assertFalse(db.isValid()); + assertEquals(RangeReward.class, db.getRewardType()); + } + + // ─── CropGrowthReward ──────────────────────────────────────────────── + + @Test + void cropGrowthReward_metadataAndNames() { + CropGrowthReward r = new CropGrowthReward(); + assertEquals("crop_growth_reward", r.getName()); + assertEquals(Material.WHEAT, r.getIcon()); + assertNotNull(r.getAdminName(user)); + assertNotNull(r.getAdminDescription(user)); + assertNotNull(r.getPublicName(user)); + assertNotNull(r.getPublicDescription(user)); + } + + @Test + void cropGrowthReward_publicDescriptionWithDb() { + CropGrowthReward r = new CropGrowthReward(); + CropGrowthRewardDB db = new CropGrowthRewardDB(); + db.setGrowthBonusEquation("0.5"); + assertNotNull(r.getPublicDescription(user, db)); + } + + @Test + void cropGrowthReward_apply_isNoOp() { + CropGrowthReward r = new CropGrowthReward(); + assertDoesNotThrow(() -> r.apply(addon, user, island, new CropGrowthRewardDB(), 1)); + } + + @Test + void cropGrowthRewardDB_validityChecks() { + CropGrowthRewardDB db = new CropGrowthRewardDB(); + assertTrue(db.isValid()); + db.setGrowthBonusEquation(null); + assertFalse(db.isValid()); + assertEquals(CropGrowthReward.class, db.getRewardType()); + } + + // ─── SpawnerReward ─────────────────────────────────────────────────── + + @Test + void spawnerReward_metadataAndNames() { + SpawnerReward r = new SpawnerReward(); + assertEquals("spawner_reward", r.getName()); + assertEquals(Material.SPAWNER, r.getIcon()); + assertNotNull(r.getAdminName(user)); + assertNotNull(r.getAdminDescription(user)); + assertNotNull(r.getPublicName(user)); + assertNotNull(r.getPublicDescription(user)); + } + + @Test + void spawnerReward_publicDescriptionWithDb() { + SpawnerReward r = new SpawnerReward(); + SpawnerRewardDB db = new SpawnerRewardDB(); + db.setSpawnBonusEquation("0.25"); + assertNotNull(r.getPublicDescription(user, db)); + } + + @Test + void spawnerReward_apply_isNoOp() { + SpawnerReward r = new SpawnerReward(); + assertDoesNotThrow(() -> r.apply(addon, user, island, new SpawnerRewardDB(), 1)); + } + + @Test + void spawnerRewardDB_validityChecks() { + SpawnerRewardDB db = new SpawnerRewardDB(); + assertTrue(db.isValid()); + db.setSpawnBonusEquation(""); + assertFalse(db.isValid()); + assertEquals(SpawnerReward.class, db.getRewardType()); + } + + // ─── LimitsReward ──────────────────────────────────────────────────── + + @Test + void limitsReward_metadataAndNames() { + LimitsReward r = new LimitsReward(); + assertEquals("limits_reward", r.getName()); + assertEquals(Material.BARRIER, r.getIcon()); + assertNotNull(r.getAdminName(user)); + assertNotNull(r.getAdminDescription(user)); + assertNotNull(r.getPublicName(user)); + assertNotNull(r.getPublicDescription(user)); + } + + @Test + void limitsReward_publicDescriptionWithDb() { + LimitsReward r = new LimitsReward(); + LimitsRewardDB db = new LimitsRewardDB(); + db.setTarget("STONE"); + db.setAmountEquation("10"); + assertNotNull(r.getPublicDescription(user, db)); + } + + @Test + void limitsReward_apply_bailsOutWhenLimitsNotProvided() { + LimitsReward r = new LimitsReward(); + when(addon.isLimitsProvided()).thenReturn(false); + assertDoesNotThrow(() -> r.apply(addon, user, island, new LimitsRewardDB(), 1)); + verify(addon).logWarning(anyString()); + } + + @Test + void limitsRewardDB_validityChecks() { + LimitsRewardDB db = new LimitsRewardDB(); + assertFalse(db.isValid()); + db.setTarget("STONE"); + db.setAmountEquation("5"); + assertTrue(db.isValid()); + assertEquals(LimitsReward.class, db.getRewardType()); + } + + // ─── CommandReward ─────────────────────────────────────────────────── + + @Test + void commandReward_metadataAndNames() { + CommandReward r = new CommandReward(); + assertEquals("command_reward", r.getName()); + assertEquals(Material.COMMAND_BLOCK, r.getIcon()); + assertNotNull(r.getAdminName(user)); + assertNotNull(r.getAdminDescription(user)); + assertNotNull(r.getPublicName(user)); + assertNotNull(r.getPublicDescription(user)); + } + + @Test + void commandRewardDB_validityChecks() { + CommandRewardDB db = new CommandRewardDB(); + assertFalse(db.isValid()); + db.setCommands(List.of("say hi")); + assertTrue(db.isValid()); + db.setConsole(false); + assertFalse(db.isConsole()); + assertEquals(CommandReward.class, db.getRewardType()); + } +} diff --git a/src/test/java/world/bentobox/upgrades/ui/PanelTest.java b/src/test/java/world/bentobox/upgrades/ui/PanelTest.java new file mode 100644 index 0000000..19ac812 --- /dev/null +++ b/src/test/java/world/bentobox/upgrades/ui/PanelTest.java @@ -0,0 +1,143 @@ +package world.bentobox.upgrades.ui; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.Comparator; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.MockitoAnnotations; +import org.mockito.Mockito; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.IslandsManager; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.UpgradesManager; +import world.bentobox.upgrades.WhiteBox; + +/** + * @author tastybento + */ +public class PanelTest { + + @Mock + private UpgradesAddon addon; + @Mock + private Island island; + @Mock + private UpgradesManager um; + @Mock + private World world; + @Mock + private User user; + @Mock + private BentoBox plugin; + + @Mock + private Location location; + @Mock + private IslandsManager im; + @Mock + private Player p; + private Panel panel; + private MockedStatic mockBukkit; + private File dataFolder; + + /** + */ + @BeforeEach + public void setUp() throws IOException { + MockitoAnnotations.openMocks(this); + MockBukkit.mock(); + + // Set up BentoBox singleton (needed for TemplatedPanel internal error logging) + WhiteBox.setInternalState(BentoBox.class, "instance", plugin); + + // World + when(world.toString()).thenReturn("world"); + // Player + when(user.isOp()).thenReturn(false); + UUID uuid = UUID.randomUUID(); + when(user.getUniqueId()).thenReturn(uuid); + when(user.getPlayer()).thenReturn(p); + when(user.getName()).thenReturn("tastybento"); + when(user.getPermissionValue(anyString(), anyInt())).thenReturn(-1); + when(user.isPlayer()).thenReturn(true); + when(user.getLocation()).thenReturn(location); + when(user.getTranslation(anyString())).thenReturn("translation"); + + when(um.getIslandLevel(island)).thenReturn(20); + when(addon.getUpgradesManager()).thenReturn(um); + when(addon.getAvailableUpgrades()).thenReturn(Collections.emptySet()); + when(island.getWorld()).thenReturn(world); + + // Set up data folder with panel template + dataFolder = Files.createTempDirectory("upgrades-panel-test").toFile(); + File panelsDir = new File(dataFolder, "panels"); + panelsDir.mkdirs(); + Files.copy(Paths.get("src/main/resources/panels/upgrades_panel.yml"), + new File(panelsDir, "upgrades_panel.yml").toPath()); + when(addon.getDataFolder()).thenReturn(dataFolder); + + // Bukkit + mockBukkit = Mockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS); + + panel = new Panel(addon, island); + } + + /** + */ + @AfterEach + public void tearDown() throws IOException { + User.clearUsers(); + WhiteBox.setInternalState(BentoBox.class, "instance", null); + mockBukkit.closeOnDemand(); + MockBukkit.unmock(); + if (dataFolder != null) { + Files.walk(dataFolder.toPath()) + .sorted(Comparator.reverseOrder()) + .map(java.nio.file.Path::toFile) + .forEach(File::delete); + } + } + + /** + * Test method for {@link world.bentobox.upgrades.ui.Panel#Panel(world.bentobox.upgrades.UpgradesAddon, world.bentobox.bentobox.database.objects.Island)}. + */ + @Test + public void testPanel() { + assertNotNull(panel); + } + + /** + * Test method for {@link world.bentobox.upgrades.ui.Panel#showPanel(world.bentobox.bentobox.api.user.User)}. + */ + @Test + public void testShowPanel() { + panel.showPanel(user); + verify(p).openInventory(any(Inventory.class)); + } + +} diff --git a/src/test/java/world/bentobox/upgrades/upgrades/CommandUpgradeTest.java b/src/test/java/world/bentobox/upgrades/upgrades/CommandUpgradeTest.java new file mode 100644 index 0000000..5daf2b9 --- /dev/null +++ b/src/test/java/world/bentobox/upgrades/upgrades/CommandUpgradeTest.java @@ -0,0 +1,201 @@ +package world.bentobox.upgrades.upgrades; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.UpgradesManager; +import world.bentobox.upgrades.config.Settings; +import world.bentobox.upgrades.dataobjects.UpgradesData; + +/** + * Test for CommandUpgrade + */ +public class CommandUpgradeTest { + + @Mock + private UpgradesAddon addon; + @Mock + private User user; + @Mock + private Island island; + @Mock + private UpgradesData upgradesData; + @Mock + private UpgradesManager um; + @Mock + private Player player; + @Mock + private World world; + @Mock + private Settings settings; + + private CommandUpgrade commandUpgrade; + private UUID userId; + private String islandId; + + @BeforeEach + public void setUp() { + MockBukkit.mock(); + + MockitoAnnotations.openMocks(this); + + userId = UUID.randomUUID(); + islandId = UUID.randomUUID().toString(); + + when(user.getUniqueId()).thenReturn(userId); + when(user.getPlayer()).thenReturn(player); + when(island.getUniqueId()).thenReturn(islandId); + when(island.getGameMode()).thenReturn("aoneblock"); + when(island.getWorld()).thenReturn(world); + + when(addon.getUpgradesLevels(islandId)).thenReturn(upgradesData); + when(addon.getUpgradesManager()).thenReturn(um); + when(addon.getSettings()).thenReturn(settings); + when(settings.getCommandName("coal-upgrade")).thenReturn("Coal Upgrade"); + + commandUpgrade = new CommandUpgrade(addon, "coal-upgrade", Material.GRASS_BLOCK); + } + + @AfterEach + public void tearDown() { + MockBukkit.unmock(); + } + + /** + * Test that isShowed returns true when permission level is 0 (no permission required) + */ + @Test + public void testIsShowed_NoPermissionRequired() { + // Set up the mock to return permission level 0 + when(upgradesData.getUpgradeLevel("command-coal-upgrade")).thenReturn(0); + when(um.getCommandPermissionLevel("coal-upgrade", 0, world)).thenReturn(0); + + // Should show when no permission is required + assertTrue(commandUpgrade.isShowed(user, island)); + } + + /** + * Test that isShowed correctly uses this.getName() instead of this.cmdId + * when getting the upgrade level from the island data. + * + * This is a regression test for the bug where isShowed() was using this.cmdId + * while updateUpgradeValue() and doUpgrade() were correctly using this.getName(). + */ + @Test + public void testIsShowed_UsesCorrectUpgradeKey() { + // The upgrade data is stored with the key "command-coal-upgrade" (this.getName()) + // not "coal-upgrade" (this.cmdId) + when(upgradesData.getUpgradeLevel("command-coal-upgrade")).thenReturn(1); + when(um.getCommandPermissionLevel("coal-upgrade", 1, world)).thenReturn(1); + + // Set up permissions for the player + Set permissions = new HashSet<>(); + PermissionAttachmentInfo perm = createMockPermission("aoneblock.upgrades.command-coal-upgrade.1", true); + permissions.add(perm); + when(player.getEffectivePermissions()).thenReturn(permissions); + + // Should show when player has correct permission + assertTrue(commandUpgrade.isShowed(user, island)); + } + + /** + * Test that isShowed returns false when player doesn't have required permission + */ + @Test + public void testIsShowed_NoPermission() { + when(upgradesData.getUpgradeLevel("command-coal-upgrade")).thenReturn(0); + when(um.getCommandPermissionLevel("coal-upgrade", 0, world)).thenReturn(1); + + // Set up empty permissions + Set permissions = new HashSet<>(); + when(player.getEffectivePermissions()).thenReturn(permissions); + + // Should not show when player lacks permission + assertFalse(commandUpgrade.isShowed(user, island)); + } + + /** + * Test that isShowed returns true when player has sufficient permission level + */ + @Test + public void testIsShowed_WithSufficientPermission() { + when(upgradesData.getUpgradeLevel("command-coal-upgrade")).thenReturn(0); + when(um.getCommandPermissionLevel("coal-upgrade", 0, world)).thenReturn(1); + + // Set up permissions - player has level 2, needs level 1 + Set permissions = new HashSet<>(); + PermissionAttachmentInfo perm = createMockPermission("aoneblock.upgrades.command-coal-upgrade.2", true); + permissions.add(perm); + when(player.getEffectivePermissions()).thenReturn(permissions); + + // Should show when player has higher permission level than required + assertTrue(commandUpgrade.isShowed(user, island)); + } + + /** + * Test that isShowed returns false when player has insufficient permission level + */ + @Test + public void testIsShowed_WithInsufficientPermission() { + when(upgradesData.getUpgradeLevel("command-coal-upgrade")).thenReturn(0); + when(um.getCommandPermissionLevel("coal-upgrade", 0, world)).thenReturn(2); + + // Set up permissions - player has level 1, needs level 2 + Set permissions = new HashSet<>(); + PermissionAttachmentInfo perm = createMockPermission("aoneblock.upgrades.command-coal-upgrade.1", true); + permissions.add(perm); + when(player.getEffectivePermissions()).thenReturn(permissions); + + // Should not show when player has lower permission level than required + assertFalse(commandUpgrade.isShowed(user, island)); + } + + /** + * Test that isShowed returns false for wildcard permissions + */ + @Test + public void testIsShowed_WildcardNotAllowed() { + when(upgradesData.getUpgradeLevel("command-coal-upgrade")).thenReturn(0); + when(um.getCommandPermissionLevel("coal-upgrade", 0, world)).thenReturn(1); + + // Set up wildcard permission + Set permissions = new HashSet<>(); + PermissionAttachmentInfo perm = createMockPermission("aoneblock.upgrades.command-coal-upgrade.*", true); + permissions.add(perm); + when(player.getEffectivePermissions()).thenReturn(permissions); + + // Should not show when player has wildcard permission (not allowed) + assertFalse(commandUpgrade.isShowed(user, island)); + } + + /** + * Helper method to create a mock PermissionAttachmentInfo + */ + private PermissionAttachmentInfo createMockPermission(String permission, boolean value) { + PermissionAttachmentInfo info = org.mockito.Mockito.mock(PermissionAttachmentInfo.class); + when(info.getPermission()).thenReturn(permission); + when(info.getValue()).thenReturn(value); + return info; + } +} diff --git a/src/test/java/world/bentobox/upgrades/upgrades/DatabaseUpgradeTest.java b/src/test/java/world/bentobox/upgrades/upgrades/DatabaseUpgradeTest.java new file mode 100644 index 0000000..f80320c --- /dev/null +++ b/src/test/java/world/bentobox/upgrades/upgrades/DatabaseUpgradeTest.java @@ -0,0 +1,376 @@ +package world.bentobox.upgrades.upgrades; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import org.bukkit.World; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.addons.AddonDescription; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.IslandWorldManager; +import world.bentobox.upgrades.UpgradesAddon; +import world.bentobox.upgrades.UpgradesDataManager; +import world.bentobox.upgrades.UpgradesManager; +import world.bentobox.upgrades.dataobjects.UpgradeData; +import world.bentobox.upgrades.dataobjects.UpgradeTier; +import world.bentobox.upgrades.dataobjects.UpgradesData; +import world.bentobox.upgrades.dataobjects.prices.Price; +import world.bentobox.upgrades.dataobjects.prices.PriceDB; +import world.bentobox.upgrades.dataobjects.rewards.Reward; +import world.bentobox.upgrades.dataobjects.rewards.RewardDB; + +/** + * Tests for {@link DatabaseUpgrade} — the bridge between admin-GUI-configured + * upgrades and the player shop. + * + * Key invariants under test: + * - Fresh island level is 0, so a tier at startLevel=0..endLevel=0 is + * immediately purchasable (regression test for the putIfAbsent(1) bug). + * - After one purchase the level becomes 1; a 0-0 tier no longer matches, + * so the upgrade shows as "Max level". + * - isShowed() gates on active flag, non-empty tier list, and game-mode match. + * - canUpgrade() delegates to each Price.canPay(). + * - doUpgrade() calls Price.pay(), Reward.apply(), and increments the level. + */ +public class DatabaseUpgradeTest { + + @Mock private UpgradesAddon addon; + @Mock private BentoBox plugin; + @Mock private IslandWorldManager iwm; + @Mock private UpgradesDataManager upgradesDataManager; + @Mock private UpgradesManager upgradesManager; + @Mock private User user; + @Mock private Island island; + @Mock private World world; + @Mock private GameModeAddon gameModeAddon; + @Mock private AddonDescription gameModeDesc; + + private UpgradeData upgradeData; + /** A single-purchase tier: startLevel=0, endLevel=0 */ + private UpgradeTier tier; + private UpgradesData upgradesData; + private DatabaseUpgrade databaseUpgrade; + private String islandId; + private UUID userId; + + @BeforeEach + void setUp() { + MockBukkit.mock(); + MockitoAnnotations.openMocks(this); + + userId = UUID.randomUUID(); + islandId = UUID.randomUUID().toString(); + + // Upgrade configured for BSkyBlock + upgradeData = new UpgradeData(); + upgradeData.setUniqueId("BSkyBlock_diamond"); + upgradeData.setWorld("BSkyBlock"); + upgradeData.setName("Diamond Upgrade"); + upgradeData.setActive(true); + + // Single-purchase tier: one buy available (level 0 → 1) + tier = new UpgradeTier(); + tier.setUniqueId("BSkyBlock_diamond_t1"); + tier.setUpgrade("BSkyBlock_diamond"); + tier.setName("Tier 1"); + tier.setStartLevel(0); + tier.setEndLevel(0); + + // Fresh island data — must default to level 0 + upgradesData = new UpgradesData(islandId); + + when(user.getUniqueId()).thenReturn(userId); + when(user.getTranslation(any(String.class))) + .thenAnswer(inv -> inv.getArgument(0, String.class)); + when(island.getUniqueId()).thenReturn(islandId); + when(island.getWorld()).thenReturn(world); + + // addon → plugin → IWM → game-mode lookup + when(addon.getPlugin()).thenReturn(plugin); + when(plugin.getIWM()).thenReturn(iwm); + when(iwm.getAddon(world)).thenReturn(Optional.of(gameModeAddon)); + when(gameModeAddon.getDescription()).thenReturn(gameModeDesc); + when(gameModeDesc.getName()).thenReturn("BSkyBlock"); + + when(addon.getUpgradeDataManager()).thenReturn(upgradesDataManager); + when(addon.getUpgradesManager()).thenReturn(upgradesManager); + when(addon.getUpgradesLevels(islandId)).thenReturn(upgradesData); + when(upgradesDataManager.getUpgradeTierByUpgradeData(upgradeData)) + .thenReturn(Collections.singletonList(tier)); + + databaseUpgrade = new DatabaseUpgrade(addon, upgradeData); + } + + @AfterEach + void tearDown() { + MockBukkit.unmock(); + } + + // ─── isShowed() ─────────────────────────────────────────────────────── + + @Test + void isShowed_inactiveUpgrade_returnsFalse() { + upgradeData.setActive(false); + assertFalse(databaseUpgrade.isShowed(user, island)); + } + + @Test + void isShowed_noTiersConfigured_returnsFalse() { + // An upgrade with no tiers would appear permanently maxed; hide it. + when(upgradesDataManager.getUpgradeTierByUpgradeData(upgradeData)) + .thenReturn(Collections.emptyList()); + assertFalse(databaseUpgrade.isShowed(user, island)); + } + + @Test + void isShowed_wrongGameMode_returnsFalse() { + when(gameModeDesc.getName()).thenReturn("AcidIsland"); + assertFalse(databaseUpgrade.isShowed(user, island)); + } + + @Test + void isShowed_gameModeWorldNotFound_returnsFalse() { + when(iwm.getAddon(world)).thenReturn(Optional.empty()); + assertFalse(databaseUpgrade.isShowed(user, island)); + } + + @Test + void isShowed_allConditionsMet_returnsTrue() { + assertTrue(databaseUpgrade.isShowed(user, island)); + } + + // ─── Level storage (UpgradesData) ───────────────────────────────────── + // + // These tests confirm the fix: getUpgradeLevel() must start at 0, not 1. + // A tier with startLevel=0..endLevel=0 must be reachable on a fresh island. + + @Test + void freshIsland_levelStartsAtZero_notOne() { + assertEquals(0, upgradesData.getUpgradeLevel("BSkyBlock_diamond"), + "Fresh island must start at level 0 so tier 0-0 is immediately purchasable"); + } + + @Test + void setAndGetUpgradeLevel_roundTrip() { + upgradesData.setUpgradeLevel("BSkyBlock_diamond", 3); + assertEquals(3, upgradesData.getUpgradeLevel("BSkyBlock_diamond")); + } + + // ─── updateUpgradeValue() / tier resolution ──────────────────────────── + + @Test + void updateUpgradeValue_freshIsland_tierFound_upgradeIsVisible() { + // Level 0: tier 0-0 must match (0 <= 0 <= 0) so upgrade appears in shop. + databaseUpgrade.updateUpgradeValue(user, island); + assertTrue(databaseUpgrade.isShowed(user, island)); + } + + @Test + void updateUpgradeValue_afterOnePurchase_bothNullMeansMaxLevel() { + // Level 1: tier 0-0 no longer matches (0 <= 1 <= 0 is FALSE). + // Both ownDescription and upgradeValues are set to null → Panel shows "Max level". + upgradesData.setUpgradeLevel(upgradeData.getUniqueId(), 1); + databaseUpgrade.updateUpgradeValue(user, island); + + assertNull(databaseUpgrade.getUpgradeValues(user), + "upgradeValues must be null when no tier covers the current level"); + assertNull(databaseUpgrade.getOwnDescription(user), + "ownDescription must be null when no tier covers the current level"); + } + + @Test + void updateUpgradeValue_multiLevelTier_midPointIsStillBuyable() { + // Tier 0-2: three purchases; level 1 is still within range. + tier.setEndLevel(2); + upgradesData.setUpgradeLevel(upgradeData.getUniqueId(), 1); + databaseUpgrade.updateUpgradeValue(user, island); + + assertTrue(databaseUpgrade.isShowed(user, island)); + } + + @Test + void updateUpgradeValue_multiLevelTier_pastEndIsMaxed() { + // Tier 0-2 but level is 3: past the end, should appear maxed. + tier.setEndLevel(2); + upgradesData.setUpgradeLevel(upgradeData.getUniqueId(), 3); + databaseUpgrade.updateUpgradeValue(user, island); + + assertNull(databaseUpgrade.getUpgradeValues(user)); + assertNull(databaseUpgrade.getOwnDescription(user)); + } + + // ─── canUpgrade() ──────────────────────────────────────────────────── + + @Test + void canUpgrade_noTierFound_returnsFalse() { + // Level 1 on a 0-0 tier: already maxed, cannot upgrade. + upgradesData.setUpgradeLevel(upgradeData.getUniqueId(), 1); + assertFalse(databaseUpgrade.canUpgrade(user, island)); + } + + @Test + void canUpgrade_noPricesOnTier_returnsTrue() { + // No prices configured → free upgrade → always purchasable. + assertTrue(databaseUpgrade.canUpgrade(user, island)); + } + + @Test + void canUpgrade_priceCheckPasses_returnsTrue() { + PriceDB priceDB = mock(PriceDB.class); + Price price = mock(Price.class); + when(upgradesManager.searchPrice(any())).thenReturn(price); + when(price.canPay(eq(addon), eq(user), eq(island), eq(priceDB), anyInt())).thenReturn(true); + tier.setPrices(List.of(priceDB)); + + assertTrue(databaseUpgrade.canUpgrade(user, island)); + } + + @Test + void canUpgrade_priceCheckFails_returnsFalse() { + PriceDB priceDB = mock(PriceDB.class); + Price price = mock(Price.class); + when(upgradesManager.searchPrice(any())).thenReturn(price); + when(price.canPay(eq(addon), eq(user), eq(island), eq(priceDB), anyInt())).thenReturn(false); + tier.setPrices(List.of(priceDB)); + + assertFalse(databaseUpgrade.canUpgrade(user, island)); + } + + @Test + void canUpgrade_nullPriceFromManager_skippedAndReturnsTrue() { + // searchPrice returns null (unregistered type) → price is skipped, not a failure. + PriceDB priceDB = mock(PriceDB.class); + when(upgradesManager.searchPrice(any())).thenReturn(null); + tier.setPrices(List.of(priceDB)); + + assertTrue(databaseUpgrade.canUpgrade(user, island)); + } + + // ─── doUpgrade() ───────────────────────────────────────────────────── + + @Test + void doUpgrade_noTierFound_returnsFalse() { + upgradesData.setUpgradeLevel(upgradeData.getUniqueId(), 1); // already maxed + assertFalse(databaseUpgrade.doUpgrade(user, island)); + } + + @Test + void doUpgrade_incrementsLevelByOne() { + assertEquals(0, upgradesData.getUpgradeLevel(upgradeData.getUniqueId())); + + boolean result = databaseUpgrade.doUpgrade(user, island); + + assertTrue(result); + assertEquals(1, upgradesData.getUpgradeLevel(upgradeData.getUniqueId())); + } + + @Test + void doUpgrade_callsPricePayAndRewardApply() { + PriceDB priceDB = mock(PriceDB.class); + Price price = mock(Price.class); + when(upgradesManager.searchPrice(any())).thenReturn(price); + tier.setPrices(List.of(priceDB)); + + RewardDB rewardDB = mock(RewardDB.class); + Reward reward = mock(Reward.class); + when(upgradesManager.searchReward(any())).thenReturn(reward); + tier.setRewards(List.of(rewardDB)); + + databaseUpgrade.doUpgrade(user, island); + + verify(price).pay(eq(addon), eq(user), eq(island), eq(priceDB), anyInt()); + verify(reward).apply(eq(addon), eq(user), eq(island), eq(rewardDB), anyInt()); + } + + @Test + void doUpgrade_afterMaxing_secondCallReturnsFalse() { + // First purchase: level 0 → 1, tier 0-0 consumed. + assertTrue(databaseUpgrade.doUpgrade(user, island)); + // Second call: level 1, no tier covers it. + assertFalse(databaseUpgrade.doUpgrade(user, island)); + } + + @Test + void doUpgrade_nullRewardFromManager_skippedGracefully() { + RewardDB rewardDB = mock(RewardDB.class); + when(upgradesManager.searchReward(any())).thenReturn(null); + tier.setRewards(List.of(rewardDB)); + + assertTrue(databaseUpgrade.doUpgrade(user, island)); + assertEquals(1, upgradesData.getUpgradeLevel(upgradeData.getUniqueId())); + } + + // ─── PanelClick gating regression ───────────────────────────────────── + // + // DB upgrades never populate upgradeValues (it stays null by design). + // PanelClick used to check only getUpgradeValues(user) == null, which + // silently blocked every DB upgrade click. The fix: treat + // (upgradeValues == null && ownDescription == null) as "maxed". + // So ownDescription must be non-null whenever a tier is found. + + @Test + void updateUpgradeValue_withPricesAndRewards_ownDescriptionIsNonNull() { + PriceDB priceDB = mock(PriceDB.class); + Price price = mock(Price.class); + when(upgradesManager.searchPrice(any())).thenReturn(price); + when(price.getPublicDescription(eq(user), eq(priceDB))).thenReturn("Costs 100 money"); + tier.setPrices(List.of(priceDB)); + + databaseUpgrade.updateUpgradeValue(user, island); + + assertNotNull(databaseUpgrade.getOwnDescription(user), + "ownDescription must not be null when a tier is found — PanelClick uses null ownDescription to block clicks"); + } + + @Test + void updateUpgradeValue_noPricesOrRewards_ownDescriptionFallsBackToTierName() { + // A tier with no prices or rewards: description should be tier name so that + // PanelClick can distinguish "available free upgrade" from "maxed". + databaseUpgrade.updateUpgradeValue(user, island); + + assertEquals(tier.getName(), databaseUpgrade.getOwnDescription(user), + "ownDescription must fall back to tier name when there are no price/reward descriptions"); + } + + @Test + void updateUpgradeValue_maxedState_ownDescriptionIsNull() { + // Level 1 with tier 0-0: maxed → ownDescription must be null so PanelClick blocks the click. + upgradesData.setUpgradeLevel(upgradeData.getUniqueId(), 1); + databaseUpgrade.updateUpgradeValue(user, island); + + assertNull(databaseUpgrade.getOwnDescription(user), + "ownDescription must be null when maxed so PanelClick correctly blocks the click"); + } + + // ─── Description substitution ───────────────────────────────────────── + + @Test + void updateUpgradeValue_moneyPrice_substitutesAmountInDescription() { + PriceDB priceDB = mock(PriceDB.class); + Price price = mock(Price.class); + when(upgradesManager.searchPrice(any())).thenReturn(price); + // Simulate MoneyPrice.getPublicDescription(user, priceDB) returning "Costs 500 money" + when(price.getPublicDescription(user, priceDB)).thenReturn("Costs 500 money"); + tier.setPrices(List.of(priceDB)); + + databaseUpgrade.updateUpgradeValue(user, island); + + assertTrue(databaseUpgrade.getOwnDescription(user).contains("Costs 500 money"), + "Description must use the DB-aware overload that substitutes [amount]"); + } +}