diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 7786e09..82d6223 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,45 +1,12 @@
-name: Build and Release
+name: Release
on:
push:
branches: [main]
- pull_request:
- branches: [main]
jobs:
- build:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
-
- - name: Set up JDK 17
- uses: actions/setup-java@v4
- with:
- java-version: "17"
- distribution: "temurin"
-
- - name: Cache Maven packages
- uses: actions/cache@v3
- with:
- path: ~/.m2
- key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
- restore-keys: ${{ runner.os }}-m2
-
- - name: Build with Maven
- run: mvn clean compile test package
-
- - name: Upload build artifacts
- uses: actions/upload-artifact@v3
- with:
- name: SimpleEco-jar
- path: target/*.jar
-
release:
- needs: build
runs-on: ubuntu-latest
- if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- name: Checkout code
@@ -51,8 +18,8 @@ jobs:
java-version: "17"
distribution: "temurin"
- - name: Cache Maven packages
- uses: actions/cache@v3
+ - name: Cache Maven dependencies
+ uses: actions/cache@v4
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
@@ -61,57 +28,36 @@ jobs:
- name: Build with Maven
run: mvn clean package
- - name: Get version from pom.xml
- id: get_version
- run: |
- VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
- echo "version=$VERSION" >> $GITHUB_OUTPUT
- echo "Version: $VERSION"
-
- - name: Check if release exists
- id: check_release
- run: |
- if gh release view "v${{ steps.get_version.outputs.version }}" >/dev/null 2>&1; then
- echo "exists=true" >> $GITHUB_OUTPUT
- else
- echo "exists=false" >> $GITHUB_OUTPUT
- fi
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Get version
+ id: version
+ run: echo "VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_OUTPUT
- name: Create Release
- if: steps.check_release.outputs.exists == 'false'
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
- tag_name: v${{ steps.get_version.outputs.version }}
- release_name: SimpleEco v${{ steps.get_version.outputs.version }}
+ tag_name: v${{ steps.version.outputs.VERSION }}
+ release_name: SimpleEco v${{ steps.version.outputs.VERSION }}
body: |
- ## SimpleEco v${{ steps.get_version.outputs.version }}
+ ## SimpleEco v${{ steps.version.outputs.VERSION }}
- ### Changes
- - Automatic release from main branch
+ Automatisch erstelltes Release.
### Installation
- 1. Download the `SimpleEco-${{ steps.get_version.outputs.version }}.jar` file
- 2. Place it in your server's `plugins/` directory
- 3. Restart your server
-
- ### Requirements
- - Java 17 or higher
- - Paper/Spigot 1.20.4 or compatible
+ 1. Download der `SimpleEco.jar`
+ 2. In den `plugins/` Ordner kopieren
+ 3. Server neustarten
draft: false
prerelease: false
- name: Upload Release Asset
- if: steps.check_release.outputs.exists == 'false'
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./target/SimpleEco-${{ steps.get_version.outputs.version }}.jar
- asset_name: SimpleEco-${{ steps.get_version.outputs.version }}.jar
+ asset_path: ./target/SimpleEco-${{ steps.version.outputs.VERSION }}.jar
+ asset_name: SimpleEco.jar
asset_content_type: application/java-archive
diff --git a/.gitignore b/.gitignore
index dbf52e7..ba653f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,31 @@
+# Created by https://www.toptal.com/developers/gitignore/api/maven
+# Edit at https://www.toptal.com/developers/gitignore?templates=maven
+
+### Maven ###
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+# https://github.com/takari/maven-wrapper#usage-without-binary-jar
+.mvn/wrapper/maven-wrapper.jar
+
+# Eclipse m2e generated files
+# Eclipse Core
+.project
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+# End of https://www.toptal.com/developers/gitignore/api/maven
+
+# Created by https://www.toptal.com/developers/gitignore/api/java
+# Edit at https://www.toptal.com/developers/gitignore?templates=java
+
+### Java ###
# Compiled class file
*.class
@@ -19,101 +47,137 @@
*.tar.gz
*.rar
-# Virtual machine crash logs
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
-# Maven
-target/
-pom.xml.tag
-pom.xml.releaseBackup
-pom.xml.versionsBackup
-pom.xml.next
-release.properties
-dependency-reduced-pom.xml
-buildNumber.properties
-.mvn/timing.properties
-.mvn/wrapper/maven-wrapper.jar
+# End of https://www.toptal.com/developers/gitignore/api/java
+
+# Created by https://www.toptal.com/developers/gitignore/api/intellij
+# Edit at https://www.toptal.com/developers/gitignore?templates=intellij
+
+### Intellij ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
# Gradle
-.gradle
-build/
-!gradle/wrapper/gradle-wrapper.jar
-!**/src/main/**/build/
-!**/src/test/**/build/
-
-# IntelliJ IDEA
-.idea/
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
*.iws
-*.iml
-*.ipr
+
+# IntelliJ
out/
-!**/src/main/**/out/
-!**/src/test/**/out/
-# Eclipse
-.apt_generated
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Intellij Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+# https://plugins.jetbrains.com/plugin/7973-sonarlint
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator-enh.xml
+.idea/**/markdown-navigator/
+
+# Cache file creation bug
+# See https://youtrack.jetbrains.com/issue/JBR-2257
+.idea/$CACHE_FILE$
+
+# CodeStream plugin
+# https://plugins.jetbrains.com/plugin/12206-codestream
+.idea/codestream.xml
+
+# Azure Toolkit for IntelliJ plugin
+# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
+.idea/**/azureSettings.xml
+
+.idea/*
+
.classpath
-.factorypath
.project
-.settings
-.springBeans
-.sts4-cache
-bin/
-!**/src/main/**/bin/
-!**/src/test/**/bin/
-
-# NetBeans
-/nbproject/private/
-/nbbuild/
-/dist/
-/nbdist/
-/.nb-gradle/
-
-# VS Code
-.vscode/
+.settings/
+target/
-# Mac
-.DS_Store
-
-# Windows
-Thumbs.db
-ehthumbs.db
-Desktop.ini
-
-# Linux
-*~
-
-# Temporary files
-*.tmp
-*.temp
-*.swp
-*.swo
-
-# Application specific
-*.db
-*.sqlite
-*.sqlite3
-
-# Server files (for testing)
-server/
-plugins/
-world/
-world_nether/
-world_the_end/
-logs/
-crash-reports/
-debug/
-
-# Test server configuration
-server.properties
-bukkit.yml
-spigot.yml
-paper.yml
-eula.txt
-banned-ips.json
-banned-players.json
-ops.json
-whitelist.json
-usercache.json
-usernamecache.json
\ No newline at end of file
+
+# End of https://www.toptal.com/developers/gitignore/api/intellij
+
+.vscode/
+.vscode/*
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index a0ccf77..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Environment-dependent path to Maven home directory
-/mavenHomeManager.xml
diff --git a/.idea/SimpleEco.iml b/.idea/SimpleEco.iml
deleted file mode 100644
index 3cf00db..0000000
--- a/.idea/SimpleEco.iml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
- PAPER
-
- 1
-
-
-
-
\ No newline at end of file
diff --git a/.idea/artifacts/SimpleEco_jar.xml b/.idea/artifacts/SimpleEco_jar.xml
deleted file mode 100644
index 551fc4c..0000000
--- a/.idea/artifacts/SimpleEco_jar.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
- $PROJECT_DIR$/out/artifacts/SimpleEco_jar
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index bfbece6..0000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
deleted file mode 100644
index aa00ffa..0000000
--- a/.idea/encodings.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
deleted file mode 100644
index 8c89be5..0000000
--- a/.idea/jarRepositories.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index c990afb..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 4978308..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 849f79e..0000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "java.compile.nullAnalysis.mode": "automatic"
-}
diff --git a/README.md b/README.md
index dfa0f24..3c686b8 100644
--- a/README.md
+++ b/README.md
@@ -1,271 +1,31 @@
-# SimpleEco - Dynamisches Wirtschaftssystem Plugin
+# SimpleEco
-Ein vollständiges Paper Spigot Plugin für Minecraft, das ein dynamisches Wirtschaftssystem mit intelligenter Preisbildung und Villager-Trading implementiert.
+Ein dynamisches Wirtschaftssystem Plugin für Minecraft Paper/Spigot.
-## 🌟 Features
+## Features
-### Grundlegende Währung
-- **BasicCurrency-System**: Vollständige Verwaltung von Spielerkonten
-- **Konfigurierbare Währung**: Name, Symbol und Startguthaben anpassbar
-- **Asynchrone Operationen**: Alle Datenbankzugriffe erfolgen asynchron für optimale Performance
+- **Dynamische Preise**: Preise ändern sich basierend auf Angebot und Nachfrage
+- **Villager Trading**: Interaktive Händler mit Live-Preisen
+- **SQLite Datenbank**: Persistente Speicherung aller Daten
+- **Vollständig konfigurierbar**: Alle Parameter anpassbar
-### Dynamische Preisbildung
-- **Intelligente Preisformel**: `Preis = clamp(basisPreis * (1 + preisFaktor * nettoVerkäufe * regressionFaktor / referenzMenge), minPreis, maxPreis)`
-- **Preis-Regression**: Preise kehren über konfigurierbare Zeit zum Basispreis zurück
-- **Item-spezifische Parameter**: Jedes Item kann eigene `priceFactor` und `referenceAmount` haben
-- **Echzeit-Updates**: Preise ändern sich sofort basierend auf Handelstätigkeiten
-- **Item-Kontrolle**: Konfigurierbare Kaufbarkeit/Verkaufbarkeit pro Item
-- **Vollständig konfigurierbar**: Alle Preisparameter in der `config.yml` anpassbar
+## Installation
-### Villager-Trading-Interface
-- **Interaktives Menü**: Rechtsklick auf Villager öffnet Trading-Interface
-- **Live-Preisinformationen**: Aktuelle Kauf-/Verkaufspreise, Trends und Volatilität
-- **Multi-Handels-Support**: Einzel- oder 64er-Handel per Klick
+1. [Latest Release](../../releases/latest) herunterladen
+2. `SimpleEco.jar` in den `plugins/` Ordner kopieren
+3. Server neustarten
-### SQLite-Persistierung
-- **Robuste Datenbank**: SQLite mit WAL-Modus für bessere Concurrency
-- **Zwei Haupttabellen**:
- - `player_balance`: Spielerkontostände
- - `item_stats`: Item-Handelsstatistiken
-- **Performance-Optimiert**: Caching und asynchrone Operationen
+## Commands
-## 🚀 Installation
+| Command | Beschreibung |
+| ---------------------------- | ------------------- |
+| `/eco balance [player]` | Kontostand anzeigen |
+| `/eco pay ` | Geld überweisen |
-1. **Download**: Lade die neueste `SimpleEco.jar` Datei herunter
-2. **Installation**: Platziere die JAR-Datei in deinem `plugins/` Ordner
-3. **Server-Neustart**: Starte deinen Paper Spigot Server neu
-4. **Konfiguration**: Passe die `plugins/SimpleEco/config.yml` nach deinen Wünschen an
+## Konfiguration
-## ⚙️ Konfiguration
+Die `config.yml` wird automatisch erstellt und enthält alle verfügbaren Optionen mit Erklärungen.
-### Grundeinstellungen
-```yaml
-# Währungseinstellungen
-currency:
- name: "Gold"
- startBalance: 1000.0
- symbol: "G"
+## Support
-# Datenbankeinstellungen
-database:
- path: "plugins/SimpleEco/economy.db"
-
-# Preiseinstellungen
-pricing:
- priceFactor: 0.05 # 5% Elastizität (global, überschreibbar pro Item)
- referenceAmount: 1000 # Referenzmenge (global, überschreibbar pro Item)
- regressionTimeMinutes: 60 # Zeit bis Preise zum Default zurückkehren
- regressionUpdateInterval: 5 # Update-Intervall in Minuten
-```
-
-### Item-Preise konfigurieren
-```yaml
-pricing:
- priceFactor: 0.05 # Globaler Standard-Preisfaktor
- referenceAmount: 1000 # Globale Standard-Referenzmenge
- regressionTimeMinutes: 60 # Zeit bis Preise zum Basispreis zurückkehren
- regressionUpdateInterval: 5 # Update-Intervall in Minuten
-
- items:
- WHEAT:
- basePrice: 10.0
- minPrice: 5.0
- maxPrice: 50.0
- buyable: true # Kann gekauft werden
- sellable: true # Kann verkauft werden
- # Verwendet globale priceFactor und referenceAmount
-
- DIAMOND:
- basePrice: 500.0
- minPrice: 250.0
- maxPrice: 2500.0
- buyable: true
- sellable: false # Nur kaufbar, nicht verkaufbar
- priceFactor: 0.02 # Item-spezifisch: Weniger volatil
- referenceAmount: 100 # Item-spezifisch: Reagiert schneller
-
- COAL:
- basePrice: 5.0
- minPrice: 2.0
- maxPrice: 25.0
- buyable: false # Nur verkaufbar (Rohstoff)
- sellable: true
- priceFactor: 0.1 # Item-spezifisch: Sehr volatil
- referenceAmount: 2000 # Item-spezifisch: Massengut
-```
-
-## 🎮 Commands
-
-| Command | Beschreibung | Permission |
-|---------|-------------|------------|
-| `/eco balance [Spieler]` | Zeigt Kontostand an | `simpleeco.use` |
-| `/eco pay ` | Überweist Geld | `simpleeco.use` |
-| `/eco help` | Zeigt Hilfe an | `simpleeco.use` |
-
-## 🔑 Permissions
-
-| Permission | Beschreibung | Standard |
-|------------|-------------|----------|
-| `simpleeco.use` | Grundlegende Plugin-Nutzung | `true` |
-| `simpleeco.admin` | Admin-Funktionen | `op` |
-| `simpleeco.balance.other` | Fremde Kontostände einsehen | `op` |
-
-## 🛠️ Villager-Trading
-
-### Wie es funktioniert
-1. **Rechtsklick auf Villager**: Öffnet das Trading-Menü
-2. **Linksklick auf Item**: Kauft 1x Item
-3. **Rechtsklick auf Item**: Verkauft 1x Item
-4. **Shift+Klick**: Handelt mit 64x Items
-5. **Shift+Rechtsklick auf Villager**: Normales Villager-Trading
-
-### Preisinformationen
-Das Interface zeigt für jedes Item:
-- Aktueller Kauf-/Verkaufspreis
-- Preistoleranz-Trends (steigend/fallend/stabil)
-- Volatilität (niedrig/mittel/hoch)
-- Handelsstatistiken (verkauft/gekauft/netto)
-- Effektive Preisparameter (Faktor und Referenzmenge)
-
-## 📊 Dynamische Preisbildung
-
-### Preisformel
-```
-Preis = clamp(
- basisPreis * (1 + preisFaktor * (verkauft - gekauft) * regressionFaktor / referenzMenge),
- minPreis,
- maxPreis
-)
-
-RegressionFaktor = 1.0 - (zeitSeitLetztemHandel / regressionZeit)
-```
-
-### Beispiel
-- **Basispreis**: 10 Gold
-- **Preis-Faktor**: 0.05 (5%)
-- **Referenzmenge**: 1000
-- **Verkauft**: 1500, **Gekauft**: 500
-- **Netto**: +1000
-- **Zeit seit letztem Handel**: 30 Minuten
-- **Regressions-Zeit**: 60 Minuten
-- **Regressions-Faktor**: 0.5 (1.0 - 30/60)
-
-**Berechneter Preis**: `10 * (1 + 0.05 * 1000 * 0.5 / 1000) = 10 * 1.025 = 10.25 Gold`
-
-### Beispiel: Item-spezifische Parameter
-
-**Diamant** (wenig volatil, reagiert schnell):
-- **Basispreis**: 500 Gold
-- **Item-spezifischer Preis-Faktor**: 0.02 (vs. global 0.05)
-- **Item-spezifische Referenzmenge**: 100 (vs. global 1000)
-- **Verkauft**: 50, **Gekauft**: 30, **Netto**: +20
-- **Berechneter Preis**: `500 * (1 + 0.02 * 20 * 1.0 / 100) = 500 * 1.004 = 502 Gold`
-
-**Kohle** (sehr volatil, Massengut):
-- **Basispreis**: 5 Gold
-- **Item-spezifischer Preis-Faktor**: 0.1 (vs. global 0.05)
-- **Item-spezifische Referenzmenge**: 2000 (vs. global 1000)
-- **Verkauft**: 3000, **Gekauft**: 1000, **Netto**: +2000
-- **Berechneter Preis**: `5 * (1 + 0.1 * 2000 * 1.0 / 2000) = 5 * 1.1 = 5.5 Gold`
-
-## 🔧 Technische Details
-
-### Architektur
-- **Thread-Safe**: Alle Operationen sind thread-sicher implementiert
-- **Asynchron**: Datenbankzugriffe blockieren nie den Haupt-Thread
-- **Modularer Aufbau**: Saubere Trennung der Komponenten
-- **Performance-Optimiert**: Caching und effiziente Datenbankabfragen
-
-### Systemanforderungen
-- **Minecraft**: 1.20.4+
-- **Server**: Paper Spigot (empfohlen)
-- **Java**: 17+
-- **RAM**: Minimal 512MB für Plugin-Daten
-
-### Datenbank-Schema
-```sql
--- Spieler-Kontostände
-CREATE TABLE player_balance (
- uuid TEXT PRIMARY KEY,
- balance REAL NOT NULL DEFAULT 0.0,
- last_updated INTEGER NOT NULL
-);
-
--- Item-Handelsstatistiken
-CREATE TABLE item_stats (
- item TEXT PRIMARY KEY,
- sold BIGINT NOT NULL DEFAULT 0,
- bought BIGINT NOT NULL DEFAULT 0,
- last_updated INTEGER NOT NULL
-);
-```
-
-## 📈 Performance
-
-### Optimierungen
-- **SQLite WAL-Modus**: Bessere Concurrency bei Datenbankzugriffen
-- **In-Memory-Caching**: Häufig abgerufene Daten werden gecacht
-- **Asynchrone Verarbeitung**: Keine Blockierung des Haupt-Threads
-- **Batch-Operationen**: Effiziente Datenbank-Updates
-
-### Benchmarks
-- **Spieler-Balance-Abfrage**: < 1ms (gecacht), < 10ms (Datenbank)
-- **Preis-Berechnung**: < 0.1ms
-- **Trading-Transaktion**: < 50ms (komplett)
-
-## 🐛 Troubleshooting
-
-### Häufige Probleme
-
-**Plugin startet nicht**
-- Überprüfe Java-Version (benötigt Java 17+)
-- Kontrolliere Konsolen-Logs auf Fehlermeldungen
-- Stelle sicher, dass Paper Spigot 1.20.4+ verwendet wird
-
-**Datenbank-Fehler**
-- Überprüfe Schreibrechte im Plugin-Verzeichnis
-- Kontrolliere verfügbaren Speicherplatz
-- Prüfe auf SQLite-Korruption
-
-**Preise aktualisieren nicht**
-- Überprüfe `priceFactor` in der Konfiguration
-- Kontrolliere Item-Konfiguration
-- Prüfe Konsolen-Logs auf Fehler
-
-## 📝 Changelog
-
-### Version 1.0.0
-- Erste vollständige Version
-- Dynamisches Preissystem implementiert
-- Villager-Trading-Interface
-- Vollständiges Command-System
-- SQLite-Persistierung
-- Umfassende Konfigurationsmöglichkeiten
-
-### Version 1.1.0
-- **Preis-Regression**: Preise kehren über Zeit zum Basispreis zurück
-- **Item-Kontrolle**: Konfigurierbare Kaufbarkeit/Verkaufbarkeit pro Item
-- **Automatische Updates**: Scheduler-Task für regelmäßige Preisanpassungen
-- **Verbesserte Performance**: Optimierte Datenbankzugriffe mit Zeitstempel-Tracking
-- **Enhanced UI**: Bessere Anzeige von handelbaren Optionen im Villager-Menü
-
-### Version 1.2.0 (Aktuelle Version)
-- **Item-spezifische Parameter**: Jedes Item kann eigene `priceFactor` und `referenceAmount` haben
-- **Granulare Kontrolle**: Verschiedene Volatilitäten und Reaktionsgeschwindigkeiten pro Item
-- **Erweiterte Anzeige**: UI zeigt effektive Preisparameter für jedes Item
-- **Flexible Konfiguration**: Globale Standards mit optionalen item-spezifischen Überschreibungen
-
-## 👥 Support
-
-Bei Fragen oder Problemen:
-1. Überprüfe die Dokumentation
-2. Kontrolliere die Konsolen-Logs
-3. Erstelle ein Issue auf GitHub mit detaillierter Fehlerbeschreibung
-
-## 📄 Lizenz
-
-Dieses Plugin ist unter der MIT-Lizenz veröffentlicht. Siehe `LICENSE` Datei für Details.
-
----
-
-**Entwickelt mit ❤️ für die Minecraft-Community**
\ No newline at end of file
+Bei Problemen erstelle ein [Issue](../../issues/new).
diff --git a/out/artifacts/SimpleEco_jar/SimpleEco.jar b/out/artifacts/SimpleEco_jar/SimpleEco.jar
deleted file mode 100644
index b955a01..0000000
Binary files a/out/artifacts/SimpleEco_jar/SimpleEco.jar and /dev/null differ
diff --git a/src/main/java/de/simpleeco/SimpleEcoPlugin.java b/src/main/java/de/simpleeco/SimpleEcoPlugin.java
index 8273c57..8793034 100644
--- a/src/main/java/de/simpleeco/SimpleEcoPlugin.java
+++ b/src/main/java/de/simpleeco/SimpleEcoPlugin.java
@@ -1,15 +1,16 @@
package de.simpleeco;
import de.simpleeco.commands.EcoCommand;
-import de.simpleeco.commands.SpawnCommand;
import de.simpleeco.config.ConfigManager;
import de.simpleeco.currency.BasicCurrency;
import de.simpleeco.database.DatabaseManager;
import de.simpleeco.listeners.PlayerJoinListener;
+import de.simpleeco.listeners.PlayerDeathListener;
import de.simpleeco.listeners.VillagerInteractListener;
import de.simpleeco.pricing.PriceManager;
import de.simpleeco.scoreboard.ScoreboardManager;
import de.simpleeco.tasks.PriceRegressionTask;
+import de.simpleeco.tasks.VillagerLookTask;
import de.simpleeco.trading.CustomVillagerTrader;
import de.simpleeco.villager.ShopVillagerManager;
import de.simpleeco.bank.BankManager;
@@ -42,6 +43,7 @@ public class SimpleEcoPlugin extends JavaPlugin {
private AtmTrader atmTrader;
private ScoreboardManager scoreboardManager;
private PriceRegressionTask regressionTask;
+ private VillagerLookTask villagerLookTask;
@Override
public void onEnable() {
@@ -90,7 +92,7 @@ public void onEnable() {
getLogger().info("ATM-Trading-System initialisiert");
// 9. Scoreboard-Manager initialisieren
- this.scoreboardManager = new ScoreboardManager(this, configManager, currency);
+ this.scoreboardManager = new ScoreboardManager(this, configManager, currency, bankManager);
getLogger().info("Scoreboard-Manager initialisiert");
// 10. Villager-Trading initialisieren
@@ -109,8 +111,16 @@ public void onEnable() {
this.regressionTask = PriceRegressionTask.start(this);
getLogger().info("Preis-Regression-Task gestartet");
- getLogger().info("SimpleEco Plugin erfolgreich aktiviert!");
-
+ // 14. Villager-Look-Task starten (falls aktiviert)
+ if (configManager.getConfig().getBoolean("villagerBehavior.lookAtPlayers", true)) {
+ double lookDistance = configManager.getConfig().getDouble("villagerBehavior.lookDistance", 8.0);
+ long updateInterval = configManager.getConfig().getLong("villagerBehavior.lookUpdateInterval", 20);
+
+ this.villagerLookTask = VillagerLookTask.start(this, shopVillagerManager, atmVillagerManager,
+ lookDistance, updateInterval);
+ getLogger().info("Villager-Look-Task gestartet");
+ }
+
} catch (Exception e) {
getLogger().log(Level.SEVERE, "Fehler beim Starten des Plugins:", e);
getServer().getPluginManager().disablePlugin(this);
@@ -128,6 +138,12 @@ public void onDisable() {
getLogger().info("Preis-Regression-Task gestoppt");
}
+ // Villager-Look-Task stoppen
+ if (villagerLookTask != null && !villagerLookTask.isCancelled()) {
+ villagerLookTask.cancel();
+ getLogger().info("Villager-Look-Task gestoppt");
+ }
+
// Scoreboard-Manager herunterfahren
if (scoreboardManager != null) {
scoreboardManager.shutdown();
@@ -150,15 +166,10 @@ public void onDisable() {
* Registriert alle Plugin-Commands
*/
private void registerCommands() {
- // Eco Command
- EcoCommand ecoCommand = new EcoCommand(this, currency, configManager);
+ // SimpleEco Command (vereinigt alle Subcommands)
+ EcoCommand ecoCommand = new EcoCommand(this, currency, configManager, shopVillagerManager, atmVillagerManager);
getCommand("eco").setExecutor(ecoCommand);
getCommand("eco").setTabCompleter(ecoCommand);
-
- // Spawn Command
- SpawnCommand spawnCommand = new SpawnCommand(this, configManager, shopVillagerManager, atmVillagerManager);
- getCommand("spawn").setExecutor(spawnCommand);
- getCommand("spawn").setTabCompleter(spawnCommand);
}
/**
@@ -167,6 +178,8 @@ private void registerCommands() {
private void registerListeners() {
getServer().getPluginManager().registerEvents(
new PlayerJoinListener(currency, configManager, scoreboardManager), this);
+ getServer().getPluginManager().registerEvents(
+ new PlayerDeathListener(this, configManager, bankManager, scoreboardManager), this);
getServer().getPluginManager().registerEvents(
new VillagerInteractListener(villagerTrader, shopVillagerManager, atmTrader, atmVillagerManager, scoreboardManager), this);
}
diff --git a/src/main/java/de/simpleeco/bank/AtmTrader.java b/src/main/java/de/simpleeco/bank/AtmTrader.java
index aeaf337..25f35a9 100644
--- a/src/main/java/de/simpleeco/bank/AtmTrader.java
+++ b/src/main/java/de/simpleeco/bank/AtmTrader.java
@@ -250,7 +250,11 @@ private ItemStack createGlassPane() {
*/
public void openDepositMenu(Player player) {
AtmSession session = activeSessions.get(player);
- if (session == null) return;
+ if (session == null) {
+ player.sendMessage(configManager.getMessage("prefix") +
+ "§cATM-Session ungültig. Bitte öffnen Sie das ATM erneut.");
+ return;
+ }
Inventory depositInventory = Bukkit.createInventory(null, 27, "§2§lGeld einzahlen");
@@ -271,9 +275,16 @@ public void openDepositMenu(Player player) {
// Zurück-Button
depositInventory.setItem(18, createBackButton());
- player.openInventory(depositInventory);
+ // Session-Update BEFORE opening inventory
session.currentInventory = depositInventory;
session.menuType = AtmSession.MenuType.DEPOSIT;
+
+ // Session nochmals in Map speichern (sicherheitshalber)
+ activeSessions.put(player, session);
+
+ player.openInventory(depositInventory);
+
+ plugin.getLogger().info("Einzahl-Menü für " + player.getName() + " geöffnet. Session-Typ: " + session.menuType);
}
/**
@@ -283,7 +294,11 @@ public void openDepositMenu(Player player) {
*/
public void openWithdrawMenu(Player player) {
AtmSession session = activeSessions.get(player);
- if (session == null) return;
+ if (session == null) {
+ player.sendMessage(configManager.getMessage("prefix") +
+ "§cATM-Session ungültig. Bitte öffnen Sie das ATM erneut.");
+ return;
+ }
Inventory withdrawInventory = Bukkit.createInventory(null, 27, "§c§lGeld abheben");
@@ -304,9 +319,16 @@ public void openWithdrawMenu(Player player) {
// Zurück-Button
withdrawInventory.setItem(18, createBackButton());
- player.openInventory(withdrawInventory);
+ // Session-Update BEFORE opening inventory
session.currentInventory = withdrawInventory;
session.menuType = AtmSession.MenuType.WITHDRAW;
+
+ // Session nochmals in Map speichern (sicherheitshalber)
+ activeSessions.put(player, session);
+
+ player.openInventory(withdrawInventory);
+
+ plugin.getLogger().info("Abheb-Menü für " + player.getName() + " geöffnet. Session-Typ: " + session.menuType);
}
/**
@@ -317,17 +339,26 @@ public void openWithdrawMenu(Player player) {
* @return ItemStack für den Button
*/
private ItemStack createAmountButton(double amount, String displayName) {
- ItemStack item = new ItemStack(Material.GOLD_NUGGET);
+ Material material = amount > 0 ? Material.GOLD_NUGGET : Material.GRAY_STAINED_GLASS_PANE;
+ ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
- meta.setDisplayName(displayName);
- List lore = new ArrayList<>();
- lore.add("§7Betrag: §e" + bankManager.formatAmount(amount));
- lore.add("");
- lore.add("§eLinksklick: Ausführen");
- meta.setLore(lore);
- item.setItemMeta(meta);
+ if (amount > 0) {
+ meta.setDisplayName(displayName);
+ List lore = new ArrayList<>();
+ lore.add("§7Betrag: §e" + bankManager.formatAmount(amount));
+ lore.add("");
+ lore.add("§eLinksklick: Ausführen");
+ meta.setLore(lore);
+ } else {
+ meta.setDisplayName("§8" + displayName.replace("§a", "").replace("§c", "") + " §7(Nicht verfügbar)");
+ List lore = new ArrayList<>();
+ lore.add("§7Nicht genügend Guthaben");
+ lore.add("§7für diese Aktion verfügbar.");
+ meta.setLore(lore);
+ }
+ item.setItemMeta(meta);
return item;
}
@@ -359,7 +390,18 @@ private ItemStack createBackButton() {
*/
public void handleInventoryClick(Player player, int slot, ClickType clickType, ItemStack item) {
AtmSession session = activeSessions.get(player);
- if (session == null || item == null || !item.hasItemMeta()) return;
+ if (session == null) {
+ plugin.getLogger().warning("Keine aktive ATM-Session für Spieler " + player.getName());
+ player.sendMessage(configManager.getMessage("prefix") +
+ "§cATM-Session ungültig. Bitte öffnen Sie das ATM erneut.");
+ player.closeInventory();
+ return;
+ }
+
+ // Null-Check für Item und ItemMeta
+ if (item == null || !item.hasItemMeta() || item.getItemMeta().getDisplayName() == null) {
+ return;
+ }
String itemName = item.getItemMeta().getDisplayName();
@@ -367,6 +409,7 @@ public void handleInventoryClick(Player player, int slot, ClickType clickType, I
case MAIN -> handleMainMenuClick(player, session, slot, itemName);
case DEPOSIT -> handleDepositMenuClick(player, session, slot, itemName);
case WITHDRAW -> handleWithdrawMenuClick(player, session, slot, itemName);
+ default -> { /* Unbekannter Menü-Typ */ }
}
}
@@ -375,8 +418,12 @@ public void handleInventoryClick(Player player, int slot, ClickType clickType, I
*/
private void handleMainMenuClick(Player player, AtmSession session, int slot, String itemName) {
switch (slot) {
- case 14 -> openDepositMenu(player); // Einzahlen
- case 16 -> openWithdrawMenu(player); // Abheben
+ case 14 -> { // Einzahlen
+ openDepositMenu(player);
+ }
+ case 16 -> { // Abheben
+ openWithdrawMenu(player);
+ }
case 22 -> { // Schließen
player.closeInventory();
activeSessions.remove(player);
@@ -388,7 +435,7 @@ private void handleMainMenuClick(Player player, AtmSession session, int slot, St
* Behandelt Klicks im Einzahl-Menü
*/
private void handleDepositMenuClick(Player player, AtmSession session, int slot, String itemName) {
- if (slot == 18) { // Zurück
+ if (slot == 18 && itemName != null && itemName.contains("Zurück")) { // Zurück-Button
openAtmMenu(player);
return;
}
@@ -403,7 +450,7 @@ private void handleDepositMenuClick(Player player, AtmSession session, int slot,
* Behandelt Klicks im Abheb-Menü
*/
private void handleWithdrawMenuClick(Player player, AtmSession session, int slot, String itemName) {
- if (slot == 18) { // Zurück
+ if (slot == 18 && itemName != null && itemName.contains("Zurück")) { // Zurück-Button
openAtmMenu(player);
return;
}
@@ -418,32 +465,73 @@ private void handleWithdrawMenuClick(Player player, AtmSession session, int slot
* Ermittelt den Betrag basierend auf dem Slot
*/
private double getAmountFromSlot(int slot, AtmSession session, boolean isDeposit) {
- return switch (slot) {
+ double amount = switch (slot) {
case 10 -> 10.0;
case 11 -> 100.0;
case 12 -> 1000.0;
case 13 -> isDeposit ? session.cashBalance : session.bankBalance;
default -> 0.0;
};
+
+ // Zusätzliche Validierung für "Alles" einzahlen/abheben
+ if (slot == 13) {
+ if (isDeposit && session.cashBalance <= 0) {
+ return 0.0; // Kein Bargeld verfügbar
+ }
+ if (!isDeposit && session.bankBalance <= 0) {
+ return 0.0; // Kein Bank-Guthaben verfügbar
+ }
+ }
+
+ return amount;
}
/**
* Führt eine Einzahlung durch
*/
private void performDeposit(Player player, double amount) {
- if (amount <= 0) return;
+ if (amount <= 0) {
+ player.sendMessage(configManager.getMessage("prefix") +
+ "§cUngültiger Betrag für die Einzahlung!");
+ return;
+ }
+ AtmSession session = activeSessions.get(player);
+ if (session == null) {
+ player.sendMessage(configManager.getMessage("prefix") +
+ "§cATM-Session ungültig. Bitte öffnen Sie das ATM erneut.");
+ return;
+ }
+
+ // Prüfe ob genügend Bargeld vorhanden ist
+ if (amount > session.cashBalance) {
+ player.sendMessage(configManager.getMessage("prefix") +
+ "§cNicht genügend Bargeld verfügbar! Sie haben nur " +
+ bankManager.formatAmount(session.cashBalance));
+ return;
+ }
+
bankManager.depositToBank(player, amount).thenAccept(success -> {
Bukkit.getScheduler().runTask(plugin, () -> {
if (success) {
player.sendMessage(configManager.getMessage("prefix") +
"§a" + bankManager.formatAmount(amount) + " erfolgreich eingezahlt!");
+ // Session aktualisieren
+ session.cashBalance -= amount;
+ session.bankBalance += amount;
openAtmMenu(player); // Menü aktualisieren
} else {
player.sendMessage(configManager.getMessage("prefix") +
- "§cNicht genügend Bargeld verfügbar!");
+ "§cFehler bei der Einzahlung! Bitte versuchen Sie es erneut.");
}
});
+ }).exceptionally(throwable -> {
+ plugin.getLogger().severe("Unerwarteter Fehler bei ATM-Einzahlung: " + throwable.getMessage());
+ Bukkit.getScheduler().runTask(plugin, () -> {
+ player.sendMessage(configManager.getMessage("prefix") +
+ "§cEin unerwarteter Fehler ist aufgetreten. Bitte wenden Sie sich an einen Administrator.");
+ });
+ return null;
});
}
@@ -451,19 +539,48 @@ private void performDeposit(Player player, double amount) {
* Führt eine Abhebung durch
*/
private void performWithdraw(Player player, double amount) {
- if (amount <= 0) return;
+ if (amount <= 0) {
+ player.sendMessage(configManager.getMessage("prefix") +
+ "§cUngültiger Betrag für die Abhebung!");
+ return;
+ }
+
+ AtmSession session = activeSessions.get(player);
+ if (session == null) {
+ player.sendMessage(configManager.getMessage("prefix") +
+ "§cATM-Session ungültig. Bitte öffnen Sie das ATM erneut.");
+ return;
+ }
+ // Prüfe ob genügend Bank-Guthaben vorhanden ist
+ if (amount > session.bankBalance) {
+ player.sendMessage(configManager.getMessage("prefix") +
+ "§cNicht genügend Bank-Guthaben verfügbar! Sie haben nur " +
+ bankManager.formatAmount(session.bankBalance));
+ return;
+ }
+
bankManager.withdrawFromBank(player, amount).thenAccept(success -> {
Bukkit.getScheduler().runTask(plugin, () -> {
if (success) {
player.sendMessage(configManager.getMessage("prefix") +
"§a" + bankManager.formatAmount(amount) + " erfolgreich abgehoben!");
+ // Session aktualisieren
+ session.bankBalance -= amount;
+ session.cashBalance += amount;
openAtmMenu(player); // Menü aktualisieren
} else {
player.sendMessage(configManager.getMessage("prefix") +
- "§cNicht genügend Bank-Guthaben verfügbar!");
+ "§cFehler bei der Abhebung! Bitte versuchen Sie es erneut.");
}
});
+ }).exceptionally(throwable -> {
+ plugin.getLogger().severe("Unerwarteter Fehler bei ATM-Abhebung: " + throwable.getMessage());
+ Bukkit.getScheduler().runTask(plugin, () -> {
+ player.sendMessage(configManager.getMessage("prefix") +
+ "§cEin unerwarteter Fehler ist aufgetreten. Bitte wenden Sie sich an einen Administrator.");
+ });
+ return null;
});
}
diff --git a/src/main/java/de/simpleeco/bank/AtmVillagerManager.java b/src/main/java/de/simpleeco/bank/AtmVillagerManager.java
index 94db07b..0a82cfb 100644
--- a/src/main/java/de/simpleeco/bank/AtmVillagerManager.java
+++ b/src/main/java/de/simpleeco/bank/AtmVillagerManager.java
@@ -80,8 +80,17 @@ private void configureAtmVillager(Villager villager) {
villager.setCustomName("§6§l" + villagerName);
villager.setCustomNameVisible(true);
- // Bewegung verhindern
- villager.setAI(false);
+ // Bewegung konfigurieren - AI nur teilweise deaktivieren für Look-Funktionalität
+ boolean lookAtPlayers = configManager.getConfig().getBoolean("villagerBehavior.lookAtPlayers", true);
+ if (lookAtPlayers) {
+ // AI aktiviert lassen, aber Bewegung einschränken
+ villager.setAI(true);
+ villager.setCollidable(false); // Keine Kollision mit anderen Entities
+ } else {
+ // Vollständig deaktivieren
+ villager.setAI(false);
+ }
+
villager.setSilent(true);
villager.setInvulnerable(true);
villager.setRemoveWhenFarAway(false);
diff --git a/src/main/java/de/simpleeco/bank/BankManager.java b/src/main/java/de/simpleeco/bank/BankManager.java
index ba586a5..e12d5df 100644
--- a/src/main/java/de/simpleeco/bank/BankManager.java
+++ b/src/main/java/de/simpleeco/bank/BankManager.java
@@ -67,7 +67,12 @@ public CompletableFuture setCashBalance(UUID playerId, double amount) {
* @return CompletableFuture das abgeschlossen wird wenn die Operation fertig ist
*/
public CompletableFuture setCashBalance(Player player, double amount) {
- return setCashBalance(player.getUniqueId(), amount);
+ return setCashBalance(player.getUniqueId(), amount).thenRun(() -> {
+ // Scoreboard benachrichtigen
+ if (plugin.getScoreboardManager() != null) {
+ plugin.getScoreboardManager().onBalanceChanged(player);
+ }
+ });
}
/**
@@ -89,7 +94,13 @@ public CompletableFuture addCashBalance(UUID playerId, double amount) {
* @return CompletableFuture mit dem neuen Bargeld-Betrag
*/
public CompletableFuture addCashBalance(Player player, double amount) {
- return addCashBalance(player.getUniqueId(), amount);
+ return addCashBalance(player.getUniqueId(), amount).thenApply(newBalance -> {
+ // Scoreboard benachrichtigen
+ if (plugin.getScoreboardManager() != null) {
+ plugin.getScoreboardManager().onBalanceChanged(player);
+ }
+ return newBalance;
+ });
}
/**
@@ -183,7 +194,12 @@ public CompletableFuture setBankBalance(UUID playerId, double amount) {
* @return CompletableFuture das abgeschlossen wird wenn die Operation fertig ist
*/
public CompletableFuture setBankBalance(Player player, double amount) {
- return setBankBalance(player.getUniqueId(), amount);
+ return setBankBalance(player.getUniqueId(), amount).thenRun(() -> {
+ // Scoreboard benachrichtigen
+ if (plugin.getScoreboardManager() != null) {
+ plugin.getScoreboardManager().onBankBalanceChanged(player);
+ }
+ });
}
/**
@@ -205,7 +221,13 @@ public CompletableFuture addBankBalance(UUID playerId, double amount) {
* @return CompletableFuture mit dem neuen Bank-Guthaben
*/
public CompletableFuture addBankBalance(Player player, double amount) {
- return addBankBalance(player.getUniqueId(), amount);
+ return addBankBalance(player.getUniqueId(), amount).thenApply(newBalance -> {
+ // Scoreboard benachrichtigen
+ if (plugin.getScoreboardManager() != null) {
+ plugin.getScoreboardManager().onBankBalanceChanged(player);
+ }
+ return newBalance;
+ });
}
/**
diff --git a/src/main/java/de/simpleeco/commands/EcoCommand.java b/src/main/java/de/simpleeco/commands/EcoCommand.java
index e97287c..4a3a35f 100644
--- a/src/main/java/de/simpleeco/commands/EcoCommand.java
+++ b/src/main/java/de/simpleeco/commands/EcoCommand.java
@@ -4,6 +4,9 @@
import de.simpleeco.config.ConfigManager;
import de.simpleeco.currency.BasicCurrency;
import de.simpleeco.scoreboard.ScoreboardManager;
+import de.simpleeco.villager.ShopVillagerManager;
+import de.simpleeco.bank.AtmVillagerManager;
+import de.simpleeco.bank.BankManager;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
@@ -15,26 +18,35 @@
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
+import java.util.concurrent.CompletableFuture;
/**
- * Command-Handler für alle Economy-Commands
+ * Haupt-Command-Handler für alle SimpleEco-Commands
*
* Behandelt:
- * - /eco balance [Spieler] - Zeigt Kontostand an
+ * - /eco balance [Spieler] - Zeigt Kontostand an (Bargeld und Bank)
* - /eco pay - Überweist Geld
+ * - /eco spawn - Spawnt Entities
*/
public class EcoCommand implements CommandExecutor, TabCompleter {
private final SimpleEcoPlugin plugin;
private final BasicCurrency currency;
+ private final BankManager bankManager;
private final ConfigManager configManager;
private final ScoreboardManager scoreboardManager;
+ private final SpawnCommand spawnCommand;
- public EcoCommand(SimpleEcoPlugin plugin, BasicCurrency currency, ConfigManager configManager) {
+ public EcoCommand(SimpleEcoPlugin plugin, BasicCurrency currency, ConfigManager configManager,
+ ShopVillagerManager shopVillagerManager, AtmVillagerManager atmVillagerManager) {
this.plugin = plugin;
this.currency = currency;
+ this.bankManager = plugin.getBankManager();
this.configManager = configManager;
this.scoreboardManager = plugin.getScoreboardManager();
+
+ // SpawnCommand als Subcommand-Handler erstellen
+ this.spawnCommand = new SpawnCommand(plugin, configManager, shopVillagerManager, atmVillagerManager, currency);
}
@Override
@@ -53,8 +65,14 @@ public boolean onCommand(CommandSender sender, Command command, String label, St
String subCommand = args[0].toLowerCase();
switch (subCommand) {
- case "balance", "bal", "b" -> handleBalanceCommand(sender, args);
- case "pay", "transfer", "send" -> handlePayCommand(sender, args);
+ case "balance", "bal" -> handleBalance(sender, args);
+ case "pay" -> handlePay(sender, args);
+ case "spawn" -> {
+ // Spawn-Argumente weiterleiten (ohne das "spawn" Argument)
+ String[] spawnArgs = Arrays.copyOfRange(args, 1, args.length);
+ return spawnCommand.handleSpawnCommand(sender, spawnArgs);
+ }
+ case "reload" -> handleReload(sender);
case "help", "?" -> sendUsage(sender);
default -> sendUsage(sender);
}
@@ -68,7 +86,7 @@ public boolean onCommand(CommandSender sender, Command command, String label, St
* @param sender Der Command-Sender
* @param args Command-Argumente
*/
- private void handleBalanceCommand(CommandSender sender, String[] args) {
+ private void handleBalance(CommandSender sender, String[] args) {
if (args.length == 1) {
// Eigenen Kontostand anzeigen
if (!(sender instanceof Player player)) {
@@ -77,17 +95,7 @@ private void handleBalanceCommand(CommandSender sender, String[] args) {
return;
}
- currency.getBalance(player).thenAccept(balance -> {
- String message = configManager.getMessage("balanceYour",
- "balance", currency.formatAmount(balance),
- "currency", configManager.getCurrencyName());
- player.sendMessage(configManager.getMessage("prefix") + message);
- }).exceptionally(throwable -> {
- player.sendMessage(configManager.getMessage("prefix") +
- "§cFehler beim Laden des Kontostands!");
- plugin.getLogger().severe("Fehler beim Laden des Kontostands: " + throwable.getMessage());
- return null;
- });
+ showPlayerBalance(sender, player);
} else if (args.length == 2) {
// Kontostand eines anderen Spielers anzeigen
@@ -106,32 +114,166 @@ private void handleBalanceCommand(CommandSender sender, String[] args) {
return;
}
- currency.getBalance(targetPlayer).thenAccept(balance -> {
- String message = configManager.getMessage("balanceOther",
- "player", targetPlayer.getName(),
- "balance", currency.formatAmount(balance),
- "currency", configManager.getCurrencyName());
- sender.sendMessage(configManager.getMessage("prefix") + message);
- }).exceptionally(throwable -> {
+ showPlayerBalance(sender, targetPlayer);
+
+ } else if (args.length == 4) {
+ // Balance add/remove: /eco balance
+ if (!sender.hasPermission("simpleeco.balance.admin")) {
sender.sendMessage(configManager.getMessage("prefix") +
- "§cFehler beim Laden des Kontostands!");
- plugin.getLogger().severe("Fehler beim Laden des Kontostands: " + throwable.getMessage());
- return null;
- });
+ configManager.getMessage("noPermission"));
+ return;
+ }
+
+ String targetName = args[1];
+ String operation = args[2].toLowerCase();
+ String amountString = args[3];
+
+ // Ziel-Spieler finden
+ Player targetPlayer = Bukkit.getPlayer(targetName);
+ if (targetPlayer == null) {
+ sender.sendMessage(configManager.getMessage("prefix") +
+ configManager.getMessage("playerNotFound"));
+ return;
+ }
+
+ // Operation validieren
+ if (!operation.equals("add") && !operation.equals("remove")) {
+ sender.sendMessage(configManager.getMessage("prefix") +
+ "§cVerwendung: /eco balance ");
+ return;
+ }
+
+ // Betrag parsen
+ double amount;
+ try {
+ amount = Double.parseDouble(amountString);
+ } catch (NumberFormatException e) {
+ sender.sendMessage(configManager.getMessage("prefix") +
+ configManager.getMessage("invalidAmount"));
+ return;
+ }
+
+ // Betrag muss positiv sein
+ if (amount <= 0) {
+ sender.sendMessage(configManager.getMessage("prefix") +
+ configManager.getMessage("invalidAmount"));
+ return;
+ }
+
+ // Operation ausführen
+ if (operation.equals("add")) {
+ handleBalanceAdd(sender, targetPlayer, amount);
+ } else {
+ handleBalanceRemove(sender, targetPlayer, amount);
+ }
} else {
sender.sendMessage(configManager.getMessage("prefix") +
- "§cVerwendung: /eco balance [Spieler]");
+ "§cVerwendung: /eco balance [Spieler] [add|remove] [Betrag]");
+ }
+ }
+
+ /**
+ * Behandelt den Reload-Command
+ *
+ * @param sender Der Command-Sender
+ */
+ private void handleReload(CommandSender sender) {
+ if (!sender.hasPermission("simpleeco.admin")) {
+ sender.sendMessage(configManager.getMessage("prefix") +
+ configManager.getMessage("noPermission"));
+ return;
+ }
+
+ try {
+ // Konfiguration neu laden
+ configManager.reload();
+
+ // Scoreboard-Manager neu laden
+ if (scoreboardManager != null) {
+ scoreboardManager.reload();
+ }
+
+ sender.sendMessage(configManager.getMessage("prefix") +
+ "§a§l✓ §7Konfiguration erfolgreich neu geladen!");
+ sender.sendMessage(configManager.getMessage("prefix") +
+ "§7Scoreboards wurden aktualisiert.");
+
+ } catch (Exception e) {
+ sender.sendMessage(configManager.getMessage("prefix") +
+ "§c§l✗ §7Fehler beim Neuladen der Konfiguration!");
+ plugin.getLogger().severe("Fehler beim Neuladen der Konfiguration: " + e.getMessage());
}
}
+ /**
+ * Zeigt die Balance eines Spielers an (Bargeld und Bank)
+ *
+ * @param sender Der Command-Sender
+ * @param targetPlayer Der Spieler dessen Balance angezeigt wird
+ */
+ private void showPlayerBalance(CommandSender sender, Player targetPlayer) {
+ // Beide Guthaben parallel laden
+ CompletableFuture cashFuture = bankManager.getCashBalance(targetPlayer);
+ CompletableFuture bankFuture = bankManager.getBankBalance(targetPlayer);
+
+ CompletableFuture.allOf(cashFuture, bankFuture).thenRun(() -> {
+ try {
+ double cashBalance = cashFuture.get();
+ double bankBalance = bankFuture.get();
+ double totalBalance = cashBalance + bankBalance;
+
+ String currencySymbol = configManager.getConfig().getString("currency.symbol", "G");
+ boolean isOwnBalance = sender.equals(targetPlayer);
+
+ // Header
+ sender.sendMessage("§8§m §r §6§lKontostand" +
+ (isOwnBalance ? "" : " von §e" + targetPlayer.getName()) + " §8§m ");
+
+ // Bargeld
+ sender.sendMessage("§a💵 Bargeld: §f" + formatAmount(cashBalance) + " " + currencySymbol);
+
+ // Bank
+ sender.sendMessage("§6🏦 Bank: §f" + formatAmount(bankBalance) + " " + currencySymbol);
+
+ // Trennlinie
+ sender.sendMessage("§8§m ");
+
+ // Gesamt
+ sender.sendMessage("§e💰 Gesamt: §f" + formatAmount(totalBalance) + " " + currencySymbol);
+
+ sender.sendMessage("§8§m ");
+
+ } catch (Exception e) {
+ sender.sendMessage(configManager.getMessage("prefix") +
+ "§cFehler beim Laden der Kontostände!");
+ plugin.getLogger().severe("Fehler beim Laden der Kontostände: " + e.getMessage());
+ }
+ }).exceptionally(throwable -> {
+ sender.sendMessage(configManager.getMessage("prefix") +
+ "§cFehler beim Laden der Kontostände!");
+ plugin.getLogger().severe("Fehler beim Laden der Kontostände: " + throwable.getMessage());
+ return null;
+ });
+ }
+
+ /**
+ * Formatiert einen Betrag
+ *
+ * @param amount Der Betrag
+ * @return Formatierter String
+ */
+ private String formatAmount(double amount) {
+ return String.format("%.2f", amount);
+ }
+
/**
* Behandelt den Pay-Command
*
* @param sender Der Command-Sender
* @param args Command-Argumente
*/
- private void handlePayCommand(CommandSender sender, String[] args) {
+ private void handlePay(CommandSender sender, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage(configManager.getMessage("prefix") +
"§cDieser Befehl kann nur von Spielern ausgeführt werden!");
@@ -215,6 +357,104 @@ private void handlePayCommand(CommandSender sender, String[] args) {
});
}
+ /**
+ * Fügt Geld zum Bargeld-Kontostand eines Spielers hinzu
+ *
+ * @param sender Der Command-Sender
+ * @param targetPlayer Der Ziel-Spieler
+ * @param amount Der hinzuzufügende Betrag
+ */
+ private void handleBalanceAdd(CommandSender sender, Player targetPlayer, double amount) {
+ currency.addBalance(targetPlayer, amount).thenAccept(newBalance -> {
+ // Erfolgsnachrichten senden
+ String currencySymbol = configManager.getConfig().getString("currency.symbol", "G");
+
+ sender.sendMessage(configManager.getMessage("prefix") +
+ "§a§l✓ §7Dem Spieler §e" + targetPlayer.getName() +
+ " §7wurden §a" + formatAmount(amount) + " " + currencySymbol +
+ " §7hinzugefügt!");
+
+ sender.sendMessage(configManager.getMessage("prefix") +
+ "§7Neuer Bargeld-Kontostand: §f" + formatAmount(newBalance) + " " + currencySymbol);
+
+ // Dem Ziel-Spieler eine Benachrichtigung senden
+ targetPlayer.sendMessage(configManager.getMessage("prefix") +
+ "§a§l✓ §7Dir wurden §a" + formatAmount(amount) + " " + currencySymbol +
+ " §7zu deinem Bargeld hinzugefügt!");
+
+ // Scoreboard des Ziel-Spielers aktualisieren
+ if (scoreboardManager != null) {
+ Bukkit.getScheduler().runTaskLater(plugin, () -> {
+ scoreboardManager.updatePlayerScoreboard(targetPlayer);
+ }, 2L); // 0.1 Sekunden Verzögerung
+ }
+
+ }).exceptionally(throwable -> {
+ sender.sendMessage(configManager.getMessage("prefix") +
+ "§cFehler beim Hinzufügen des Betrags!");
+ return null;
+ });
+ }
+
+ /**
+ * Entfernt Geld vom Bargeld-Kontostand eines Spielers
+ *
+ * @param sender Der Command-Sender
+ * @param targetPlayer Der Ziel-Spieler
+ * @param amount Der zu entfernende Betrag
+ */
+ private void handleBalanceRemove(CommandSender sender, Player targetPlayer, double amount) {
+ // Erst prüfen, ob genügend Bargeld vorhanden ist
+ currency.getBalance(targetPlayer).thenAccept(currentBalance -> {
+ if (currentBalance < amount) {
+ String currencySymbol = configManager.getConfig().getString("currency.symbol", "G");
+ sender.sendMessage(configManager.getMessage("prefix") +
+ "§c§l✗ §7Der Spieler §e" + targetPlayer.getName() +
+ " §7hat nicht genügend Bargeld!");
+ sender.sendMessage(configManager.getMessage("prefix") +
+ "§7Verfügbares Bargeld: §f" + formatAmount(currentBalance) + " " + currencySymbol +
+ " §8| §7Benötigt: §f" + formatAmount(amount) + " " + currencySymbol);
+ return;
+ }
+
+ // Betrag entfernen
+ currency.removeBalance(targetPlayer, amount).thenAccept(newBalance -> {
+ // Erfolgsnachrichten senden
+ String currencySymbol = configManager.getConfig().getString("currency.symbol", "G");
+
+ sender.sendMessage(configManager.getMessage("prefix") +
+ "§c§l✓ §7Dem Spieler §e" + targetPlayer.getName() +
+ " §7wurden §c" + formatAmount(amount) + " " + currencySymbol +
+ " §7vom Bargeld entfernt!");
+
+ sender.sendMessage(configManager.getMessage("prefix") +
+ "§7Neuer Bargeld-Kontostand: §f" + formatAmount(newBalance) + " " + currencySymbol);
+
+ // Dem Ziel-Spieler eine Benachrichtigung senden
+ targetPlayer.sendMessage(configManager.getMessage("prefix") +
+ "§c§l✗ §7Dir wurden §c" + formatAmount(amount) + " " + currencySymbol +
+ " §7von deinem Bargeld entfernt!");
+
+ // Scoreboard des Ziel-Spielers aktualisieren
+ if (scoreboardManager != null) {
+ Bukkit.getScheduler().runTaskLater(plugin, () -> {
+ scoreboardManager.updatePlayerScoreboard(targetPlayer);
+ }, 2L); // 0.1 Sekunden Verzögerung
+ }
+
+ }).exceptionally(throwable -> {
+ sender.sendMessage(configManager.getMessage("prefix") +
+ "§cFehler beim Entfernen des Betrags!");
+ return null;
+ });
+
+ }).exceptionally(throwable -> {
+ sender.sendMessage(configManager.getMessage("prefix") +
+ "§cFehler beim Prüfen des Kontostands!");
+ return null;
+ });
+ }
+
/**
* Sendet die Verwendungshinweise
*
@@ -224,8 +464,25 @@ private void sendUsage(CommandSender sender) {
sender.sendMessage("§8§m §r §6§lSimpleEco Commands §8§m ");
sender.sendMessage("§e/eco balance [Spieler] §8- §7Zeigt Kontostand an");
sender.sendMessage("§e/eco pay §8- §7Überweist Geld");
+
+ // Admin-Commands nur anzeigen wenn Permission vorhanden
+ if (sender.hasPermission("simpleeco.balance.admin")) {
+ sender.sendMessage("§c/eco balance add §8- §7Fügt Geld hinzu");
+ sender.sendMessage("§c/eco balance remove §8- §7Entfernt Geld");
+ }
+
+ // Spawn-Commands nur anzeigen wenn Permission vorhanden
+ if (sender.hasPermission("simpleeco.spawn")) {
+ sender.sendMessage("§e/eco spawn §8- §7Spawnt Entities");
+ }
+
+ // Reload-Command nur anzeigen wenn Permission vorhanden
+ if (sender.hasPermission("simpleeco.admin")) {
+ sender.sendMessage("§e/eco reload §8- §7Lädt die Konfiguration neu");
+ }
+
sender.sendMessage("§e/eco help §8- §7Zeigt diese Hilfe an");
- sender.sendMessage("§8§m ");
+ sender.sendMessage("§8§m ");
}
@Override
@@ -238,7 +495,18 @@ public List onTabComplete(CommandSender sender, Command command, String
if (args.length == 1) {
// Erste Ebene: Subcommands
- List subCommands = Arrays.asList("balance", "pay", "help");
+ List subCommands = new ArrayList<>(Arrays.asList("balance", "pay", "help"));
+
+ // Spawn hinzufügen wenn Permission vorhanden
+ if (sender.hasPermission("simpleeco.spawn")) {
+ subCommands.add("spawn");
+ }
+
+ // Reload hinzufügen wenn Permission vorhanden
+ if (sender.hasPermission("simpleeco.admin")) {
+ subCommands.add("reload");
+ }
+
String input = args[0].toLowerCase();
completions = subCommands.stream()
@@ -258,10 +526,30 @@ public List onTabComplete(CommandSender sender, Command command, String
if (sender instanceof Player player) {
completions.remove(player.getName());
}
+
+ } else if (subCommand.equals("spawn") && sender.hasPermission("simpleeco.spawn")) {
+ // Spawn-Subcommands
+ String[] spawnArgs = {args[1]};
+ completions = spawnCommand.getSpawnTabComplete(sender, spawnArgs);
+ }
+
+ } else if (args.length == 3) {
+ String subCommand = args[0].toLowerCase();
+
+ if (subCommand.equals("balance") && sender.hasPermission("simpleeco.balance.admin")) {
+ // Add/Remove subcommands für Balance-Command
+ String input = args[2].toLowerCase();
+ completions = Arrays.asList("add", "remove").stream()
+ .filter(op -> op.startsWith(input))
+ .collect(Collectors.toList());
+
+ } else if (subCommand.equals("pay")) {
+ // Betrag-Vorschläge für Pay-Command
+ completions = Arrays.asList("10", "50", "100", "500", "1000");
}
- } else if (args.length == 3 && args[0].equalsIgnoreCase("pay")) {
- // Betrag-Vorschläge für Pay-Command
+ } else if (args.length == 4 && args[0].equalsIgnoreCase("balance") && sender.hasPermission("simpleeco.balance.admin")) {
+ // Betrag-Vorschläge für Balance-Add/Remove
completions = Arrays.asList("10", "50", "100", "500", "1000");
}
diff --git a/src/main/java/de/simpleeco/commands/SpawnCommand.java b/src/main/java/de/simpleeco/commands/SpawnCommand.java
index 0420683..7276afc 100644
--- a/src/main/java/de/simpleeco/commands/SpawnCommand.java
+++ b/src/main/java/de/simpleeco/commands/SpawnCommand.java
@@ -4,6 +4,7 @@
import de.simpleeco.config.ConfigManager;
import de.simpleeco.villager.ShopVillagerManager;
import de.simpleeco.bank.AtmVillagerManager;
+import de.simpleeco.currency.BasicCurrency;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.command.Command;
@@ -22,8 +23,8 @@
* Command-Handler für Spawn-Commands
*
* Behandelt:
- * - /spawn villager - Spawnt einen Shop-Villager auf dem angeschauten Block
- * - /spawn atm - Spawnt einen ATM-Villager auf dem angeschauten Block
+ * - /eco spawn shop - Spawnt einen Shop-Villager auf dem angeschauten Block
+ * - /eco spawn atm - Spawnt einen ATM-Villager auf dem angeschauten Block
*/
public class SpawnCommand implements CommandExecutor, TabCompleter {
@@ -31,46 +32,16 @@ public class SpawnCommand implements CommandExecutor, TabCompleter {
private final ConfigManager configManager;
private final ShopVillagerManager shopVillagerManager;
private final AtmVillagerManager atmVillagerManager;
+ private final BasicCurrency currency;
public SpawnCommand(SimpleEcoPlugin plugin, ConfigManager configManager,
- ShopVillagerManager shopVillagerManager, AtmVillagerManager atmVillagerManager) {
+ ShopVillagerManager shopVillagerManager, AtmVillagerManager atmVillagerManager,
+ BasicCurrency currency) {
this.plugin = plugin;
this.configManager = configManager;
this.shopVillagerManager = shopVillagerManager;
this.atmVillagerManager = atmVillagerManager;
- }
-
- @Override
- public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
- // Permissions prüfen
- if (!sender.hasPermission("simpleeco.spawn")) {
- sender.sendMessage(configManager.getMessage("prefix") +
- configManager.getMessage("noPermission"));
- return true;
- }
-
- // Nur Spieler können diesen Command ausführen
- if (!(sender instanceof Player player)) {
- sender.sendMessage(configManager.getMessage("prefix") +
- "§cDieser Befehl kann nur von Spielern ausgeführt werden!");
- return true;
- }
-
- if (args.length == 0) {
- sendUsage(sender);
- return true;
- }
-
- String subCommand = args[0].toLowerCase();
-
- switch (subCommand) {
- case "villager" -> handleVillagerSpawn(player);
- case "atm" -> handleAtmSpawn(player);
- case "help", "?" -> sendUsage(sender);
- default -> sendUsage(sender);
- }
-
- return true;
+ this.currency = currency;
}
/**
@@ -78,14 +49,19 @@ public boolean onCommand(CommandSender sender, Command command, String label, St
*
* @param player Der Spieler der den Command ausführt
*/
- private void handleVillagerSpawn(Player player) {
- // Permissions für Villager-Spawn prüfen
- if (!player.hasPermission("simpleeco.spawn.villager")) {
+ private void handleShopSpawn(Player player) {
+ // Permissions für Shop-Spawn prüfen
+ if (!player.hasPermission("simpleeco.spawn.shop")) {
player.sendMessage(configManager.getMessage("prefix") +
configManager.getMessage("noPermission"));
return;
}
+ // Kosten prüfen und abziehen
+ if (!checkAndChargeSpawnCost(player, "shopCost")) {
+ return;
+ }
+
// Block finden, auf den der Spieler schaut
RayTraceResult rayTrace = player.rayTraceBlocks(5.0);
if (rayTrace == null || rayTrace.getHitBlock() == null) {
@@ -104,18 +80,20 @@ private void handleVillagerSpawn(Player player) {
if (success) {
player.sendMessage(configManager.getMessage("prefix") +
configManager.getMessage("villagerSpawned"));
- plugin.getLogger().info("Shop-Villager gespawnt von " + player.getName() +
- " bei " + spawnLocation.getBlockX() + ", " +
- spawnLocation.getBlockY() + ", " + spawnLocation.getBlockZ());
+ // Shop-Villager erfolgreich gespawnt
} else {
player.sendMessage(configManager.getMessage("prefix") +
configManager.getMessage("villagerSpawnFailed"));
+ // Kosten zurückerstatten bei Fehler
+ refundSpawnCost(player, "shopCost");
}
} catch (Exception e) {
player.sendMessage(configManager.getMessage("prefix") +
configManager.getMessage("villagerSpawnFailed"));
plugin.getLogger().severe("Fehler beim Spawnen des Shop-Villagers: " + e.getMessage());
+ // Kosten zurückerstatten bei Fehler
+ refundSpawnCost(player, "shopCost");
}
}
@@ -132,6 +110,11 @@ private void handleAtmSpawn(Player player) {
return;
}
+ // Kosten prüfen und abziehen
+ if (!checkAndChargeSpawnCost(player, "atmCost")) {
+ return;
+ }
+
// Block finden, auf den der Spieler schaut
RayTraceResult rayTrace = player.rayTraceBlocks(5.0);
if (rayTrace == null || rayTrace.getHitBlock() == null) {
@@ -149,19 +132,116 @@ private void handleAtmSpawn(Player player) {
if (success) {
player.sendMessage(configManager.getMessage("prefix") +
- "§a§lATM-Villager erfolgreich gespawnt!");
- plugin.getLogger().info("ATM-Villager gespawnt von " + player.getName() +
- " bei " + spawnLocation.getBlockX() + ", " +
- spawnLocation.getBlockY() + ", " + spawnLocation.getBlockZ());
+ configManager.getMessage("atmSpawned"));
+ // ATM-Villager erfolgreich gespawnt
} else {
player.sendMessage(configManager.getMessage("prefix") +
- "§c§lFehler beim Spawnen des ATM-Villagers!");
+ configManager.getMessage("atmSpawnFailed"));
+ // Kosten zurückerstatten bei Fehler
+ refundSpawnCost(player, "atmCost");
}
} catch (Exception e) {
player.sendMessage(configManager.getMessage("prefix") +
- "§c§lFehler beim Spawnen des ATM-Villagers!");
+ configManager.getMessage("atmSpawnFailed"));
plugin.getLogger().severe("Fehler beim Spawnen des ATM-Villagers: " + e.getMessage());
+ // Kosten zurückerstatten bei Fehler
+ refundSpawnCost(player, "atmCost");
+ }
+ }
+
+ /**
+ * Prüft und berechnet die Spawn-Kosten
+ *
+ * @param player Der Spieler
+ * @param costConfigKey Der Konfigurationsschlüssel für die Kosten
+ * @return true wenn Kosten erfolgreich abgezogen oder nicht erforderlich
+ */
+ private boolean checkAndChargeSpawnCost(Player player, String costConfigKey) {
+ // Prüfen ob Kosten aktiviert sind
+ if (!configManager.getConfig().getBoolean("spawnCosts.enabled", true)) {
+ return true;
+ }
+
+ // Kosten aus Konfiguration laden
+ double cost = configManager.getConfig().getDouble("spawnCosts." + costConfigKey, 0.0);
+
+ if (cost <= 0) {
+ return true; // Keine Kosten konfiguriert
+ }
+
+ // Prüfen ob Kosten für alle Spieler erzwungen werden sollen
+ boolean enforceForAll = configManager.getConfig().getBoolean("spawnCosts.enforceForAll", true);
+
+ if (!enforceForAll) {
+ // Nur normale Spieler zahlen - Admins sind befreit
+ boolean freeForAdmins = configManager.getConfig().getBoolean("spawnCosts.freeForAdmins", false);
+ if (freeForAdmins && player.hasPermission("simpleeco.spawn.free")) {
+ player.sendMessage(configManager.getMessage("prefix") +
+ "§a§lKostenloses Spawning §7(Admin-Berechtigung)");
+ return true;
+ }
+ }
+
+ // Wenn enforceForAll = true ist, zahlen alle Spieler (auch OPs/Admins)
+
+ try {
+ // Guthaben prüfen (synchron für bessere UX)
+ double balance = currency.getBalance(player.getUniqueId()).get();
+ if (balance < cost) {
+ String message = configManager.getMessage("insufficientFundsForSpawn",
+ "amount", String.valueOf(cost),
+ "currency", configManager.getConfig().getString("currency.symbol", "G"));
+ player.sendMessage(configManager.getMessage("prefix") + message);
+ return false;
+ }
+
+ // Geld abziehen
+ currency.removeBalance(player.getUniqueId(), cost).get();
+ String message = configManager.getMessage("spawnCostCharged",
+ "amount", String.valueOf(cost),
+ "currency", configManager.getConfig().getString("currency.symbol", "G"));
+ player.sendMessage(configManager.getMessage("prefix") + message);
+
+ return true;
+ } catch (Exception e) {
+ player.sendMessage(configManager.getMessage("prefix") +
+ "§cFehler beim Verarbeiten der Zahlung!");
+ plugin.getLogger().severe("Fehler beim Spawn-Kosten abziehen: " + e.getMessage());
+ return false;
+ }
+ }
+
+ /**
+ * Erstattet Spawn-Kosten zurück bei Fehlern
+ *
+ * @param player Der Spieler
+ * @param costConfigKey Der Konfigurationsschlüssel für die Kosten
+ */
+ private void refundSpawnCost(Player player, String costConfigKey) {
+ // Prüfen ob Kosten aktiviert sind
+ if (!configManager.getConfig().getBoolean("spawnCosts.enabled", true)) {
+ return;
+ }
+
+ // Prüfen ob Spieler kostenlose Berechtigung hat
+ if (player.hasPermission("simpleeco.spawn.free") &&
+ configManager.getConfig().getBoolean("spawnCosts.freeForAdmins", true)) {
+ return;
+ }
+
+ double cost = configManager.getConfig().getDouble("spawnCosts." + costConfigKey, 0.0);
+
+ if (cost > 0) {
+ try {
+ currency.addBalance(player.getUniqueId(), cost).get();
+ String message = configManager.getMessage("spawnCostRefunded",
+ "amount", String.valueOf(cost),
+ "currency", configManager.getConfig().getString("currency.symbol", "G"));
+ player.sendMessage(configManager.getMessage("prefix") + message);
+ } catch (Exception e) {
+ plugin.getLogger().severe("Fehler beim Zurückerstatten der Spawn-Kosten: " + e.getMessage());
+ }
}
}
@@ -172,12 +252,77 @@ private void handleAtmSpawn(Player player) {
*/
private void sendUsage(CommandSender sender) {
sender.sendMessage("§8§m §r §6§lSimpleEco Spawn Commands §8§m ");
- sender.sendMessage("§e/spawn villager §8- §7Spawnt einen Shop-Villager");
- sender.sendMessage("§e/spawn atm §8- §7Spawnt einen ATM-Villager");
- sender.sendMessage("§e/spawn help §8- §7Zeigt diese Hilfe an");
+ sender.sendMessage("§e/eco spawn shop §8- §7Spawnt einen Shop-Villager");
+ sender.sendMessage("§e/eco spawn atm §8- §7Spawnt einen ATM-Villager");
+ sender.sendMessage("§e/eco spawn help §8- §7Zeigt diese Hilfe an");
+
+ // Kosteneninformationen anzeigen
+ if (configManager.getConfig().getBoolean("spawnCosts.enabled", true)) {
+ sender.sendMessage("§8§m ");
+ sender.sendMessage("§c§lKosten:");
+ double shopCost = configManager.getConfig().getDouble("spawnCosts.shopCost", 0.0);
+ double atmCost = configManager.getConfig().getDouble("spawnCosts.atmCost", 0.0);
+ String symbol = configManager.getConfig().getString("currency.symbol", "G");
+
+ if (shopCost > 0) {
+ sender.sendMessage("§7Shop: §e" + shopCost + " " + symbol);
+ }
+ if (atmCost > 0) {
+ sender.sendMessage("§7ATM: §e" + atmCost + " " + symbol);
+ }
+
+ // Informationen über kostenlose Berechtigung
+ boolean enforceForAll = configManager.getConfig().getBoolean("spawnCosts.enforceForAll", true);
+ boolean freeForAdmins = configManager.getConfig().getBoolean("spawnCosts.freeForAdmins", false);
+
+ if (enforceForAll) {
+ sender.sendMessage("§7§o(Alle Spieler zahlen Kosten)");
+ } else if (freeForAdmins && sender.hasPermission("simpleeco.spawn.free")) {
+ sender.sendMessage("§a§o(Du hast kostenlose Berechtigung)");
+ }
+ }
+
sender.sendMessage("§8§m ");
}
+ // Diese Methode wird jetzt von der übergeordneten EcoCommand aufgerufen
+ public boolean handleSpawnCommand(CommandSender sender, String[] args) {
+ // Permissions prüfen
+ if (!sender.hasPermission("simpleeco.spawn")) {
+ sender.sendMessage(configManager.getMessage("prefix") +
+ configManager.getMessage("noPermission"));
+ return true;
+ }
+
+ // Nur Spieler können diesen Command ausführen
+ if (!(sender instanceof Player player)) {
+ sender.sendMessage(configManager.getMessage("prefix") +
+ "§cDieser Befehl kann nur von Spielern ausgeführt werden!");
+ return true;
+ }
+
+ if (args.length == 0) {
+ sendUsage(sender);
+ return true;
+ }
+
+ String subCommand = args[0].toLowerCase();
+
+ switch (subCommand) {
+ case "shop" -> handleShopSpawn(player);
+ case "atm" -> handleAtmSpawn(player);
+ case "help", "?" -> sendUsage(sender);
+ default -> sendUsage(sender);
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+ return handleSpawnCommand(sender, args);
+ }
+
@Override
public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (!sender.hasPermission("simpleeco.spawn")) {
@@ -188,15 +333,15 @@ public List onTabComplete(CommandSender sender, Command command, String
if (args.length == 1) {
// Erste Ebene: Subcommands
- List subCommands = Arrays.asList("villager", "atm", "help");
+ List subCommands = Arrays.asList("shop", "atm", "help");
String input = args[0].toLowerCase();
completions = subCommands.stream()
.filter(sub -> sub.startsWith(input))
.filter(sub -> {
// Permissions für spezifische Subcommands prüfen
- if (sub.equals("villager")) {
- return sender.hasPermission("simpleeco.spawn.villager");
+ if (sub.equals("shop")) {
+ return sender.hasPermission("simpleeco.spawn.shop");
} else if (sub.equals("atm")) {
return sender.hasPermission("simpleeco.spawn.atm");
}
@@ -207,4 +352,8 @@ public List onTabComplete(CommandSender sender, Command command, String
return completions;
}
+
+ public List getSpawnTabComplete(CommandSender sender, String[] args) {
+ return onTabComplete(sender, null, "spawn", args);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/de/simpleeco/config/ConfigManager.java b/src/main/java/de/simpleeco/config/ConfigManager.java
index 00bc8ca..659cede 100644
--- a/src/main/java/de/simpleeco/config/ConfigManager.java
+++ b/src/main/java/de/simpleeco/config/ConfigManager.java
@@ -217,6 +217,13 @@ public String getInfoButtonName() {
return config.getString("trading.infoButtonName", "§e§lInformation");
}
+ /**
+ * Lädt die Konfiguration neu
+ */
+ public void reload() {
+ loadConfig();
+ }
+
/**
* Datenklasse für Item-Preiskonfiguration
*/
diff --git a/src/main/java/de/simpleeco/listeners/PlayerDeathListener.java b/src/main/java/de/simpleeco/listeners/PlayerDeathListener.java
new file mode 100644
index 0000000..817a28e
--- /dev/null
+++ b/src/main/java/de/simpleeco/listeners/PlayerDeathListener.java
@@ -0,0 +1,121 @@
+package de.simpleeco.listeners;
+
+import de.simpleeco.SimpleEcoPlugin;
+import de.simpleeco.config.ConfigManager;
+import de.simpleeco.bank.BankManager;
+import de.simpleeco.scoreboard.ScoreboardManager;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.PlayerDeathEvent;
+
+/**
+ * Listener für Spieler-Tod Events
+ *
+ * Behandelt den konfigurierbaren Bargeldverlust beim Tod.
+ * Nur Bargeld geht verloren, Bank-Guthaben bleibt sicher.
+ */
+public class PlayerDeathListener implements Listener {
+
+ private final SimpleEcoPlugin plugin;
+ private final ConfigManager configManager;
+ private final BankManager bankManager;
+ private final ScoreboardManager scoreboardManager;
+
+ public PlayerDeathListener(SimpleEcoPlugin plugin, ConfigManager configManager,
+ BankManager bankManager, ScoreboardManager scoreboardManager) {
+ this.plugin = plugin;
+ this.configManager = configManager;
+ this.bankManager = bankManager;
+ this.scoreboardManager = scoreboardManager;
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL)
+ public void onPlayerDeath(PlayerDeathEvent event) {
+ Player player = event.getEntity();
+
+ // Prüfen ob Todesstrafe aktiviert ist
+ if (!configManager.getConfig().getBoolean("deathPenalty.enabled", true)) {
+ return;
+ }
+
+ // Prüfen ob Spieler Berechtigung zum Umgehen hat
+ String exemptPermission = configManager.getConfig().getString("deathPenalty.exemptPermission", "simpleeco.death.exempt");
+ if (player.hasPermission(exemptPermission)) {
+ // Optional: Nachricht senden dass Spieler geschützt ist
+ String exemptMessage = configManager.getMessage("deathPenaltyExempt");
+ if (exemptMessage != null && !exemptMessage.isEmpty()) {
+ player.sendMessage(configManager.getMessage("prefix") + exemptMessage);
+ }
+ return;
+ }
+
+ // Prüfen ob nur PvP-Tode berücksichtigt werden sollen
+ boolean onlyPvPDeath = configManager.getConfig().getBoolean("deathPenalty.onlyPvPDeath", false);
+ if (onlyPvPDeath && player.getKiller() == null) {
+ return; // Kein PvP-Tod, keine Strafe
+ }
+
+ // Bargeld-Verlust berechnen und anwenden
+ applyCashLossPenalty(player);
+ }
+
+ /**
+ * Wendet die Bargeld-Verlust-Strafe auf einen Spieler an
+ *
+ * @param player Der Spieler der die Strafe erhält
+ */
+ private void applyCashLossPenalty(Player player) {
+ bankManager.getCashBalance(player).thenAccept(currentCash -> {
+ if (currentCash <= 0) {
+ return; // Kein Bargeld vorhanden
+ }
+
+ // Verlust-Prozentsatz aus Konfiguration
+ double lossPercentage = configManager.getConfig().getDouble("deathPenalty.cashLossPercentage", 0.25);
+ double minLoss = configManager.getConfig().getDouble("deathPenalty.minLossAmount", 1.0);
+ double maxLoss = configManager.getConfig().getDouble("deathPenalty.maxLossAmount", 10000.0);
+
+ // Verlust berechnen
+ double calculatedLoss = currentCash * lossPercentage;
+
+ // Min/Max-Grenzen anwenden
+ calculatedLoss = Math.max(minLoss, Math.min(maxLoss, calculatedLoss));
+
+ // Nicht mehr verlieren als vorhanden ist
+ final double lossAmount = Math.min(calculatedLoss, currentCash);
+
+ // Verlust anwenden
+ if (lossAmount > 0) {
+ bankManager.removeCashBalance(player, lossAmount).thenAccept(newBalance -> {
+ // Nachricht an Spieler senden
+ String currencySymbol = configManager.getConfig().getString("currency.symbol", "G");
+ String message = configManager.getMessage("deathPenaltyCash",
+ "amount", String.format("%.2f", lossAmount),
+ "currency", currencySymbol);
+
+ // Nachricht mit Verzögerung senden (nach Respawn)
+ Bukkit.getScheduler().runTaskLater(plugin, () -> {
+ player.sendMessage(configManager.getMessage("prefix") + message);
+
+ // Scoreboard aktualisieren
+ if (scoreboardManager != null) {
+ scoreboardManager.updatePlayerScoreboard(player);
+ }
+ }, 20L); // 1 Sekunde Verzögerung
+ }).exceptionally(throwable -> {
+ plugin.getLogger().severe("Fehler beim Anwenden der Strafe für " +
+ player.getName() + ": " + throwable.getMessage());
+ return null;
+ });
+ }
+
+ }).exceptionally(throwable -> {
+ plugin.getLogger().severe("Fehler beim Laden des Bargeldes für Strafe von " +
+ player.getName() + ": " + throwable.getMessage());
+ return null;
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/simpleeco/listeners/VillagerInteractListener.java b/src/main/java/de/simpleeco/listeners/VillagerInteractListener.java
index e048eb4..e5f99e3 100644
--- a/src/main/java/de/simpleeco/listeners/VillagerInteractListener.java
+++ b/src/main/java/de/simpleeco/listeners/VillagerInteractListener.java
@@ -173,12 +173,24 @@ public void onInventoryClose(InventoryCloseEvent event) {
return;
}
- // Prüfen ob es sich um ein ATM-Menü handelt
- if (inventoryTitle.contains("Bank-Automat") || inventoryTitle.contains("Geld einzahlen") || inventoryTitle.contains("Geld abheben")) {
- // ATM-Session schließen
- atmTrader.removeSession(player);
- return;
- }
+ // Prüfen ob es sich um ein ATM-Menü handelt
+ if (inventoryTitle.contains("Bank-Automat") || inventoryTitle.contains("Geld einzahlen") || inventoryTitle.contains("Geld abheben")) {
+ // Verzögerte Session-Entfernung - nur wenn kein neues ATM-Menü innerhalb von 1 Tick geöffnet wird
+ org.bukkit.Bukkit.getScheduler().runTaskLater(scoreboardManager.getPlugin(), () -> {
+ // Prüfen ob der Spieler noch ein ATM-Menü offen hat
+ String currentTitle = player.getOpenInventory().getTitle();
+ if (!currentTitle.contains("Bank-Automat") &&
+ !currentTitle.contains("Geld einzahlen") &&
+ !currentTitle.contains("Geld abheben")) {
+ // Spieler hat kein ATM-Menü mehr offen - Session entfernen
+ atmTrader.removeSession(player);
+ scoreboardManager.getPlugin().getLogger().info("ATM-Session für " + player.getName() + " entfernt (Menü geschlossen)");
+ } else {
+ scoreboardManager.getPlugin().getLogger().info("ATM-Session für " + player.getName() + " beibehalten (Menü-Wechsel)");
+ }
+ }, 1L);
+ return;
+ }
}
/**
diff --git a/src/main/java/de/simpleeco/scoreboard/ScoreboardManager.java b/src/main/java/de/simpleeco/scoreboard/ScoreboardManager.java
index 93e7a53..3fa7cff 100644
--- a/src/main/java/de/simpleeco/scoreboard/ScoreboardManager.java
+++ b/src/main/java/de/simpleeco/scoreboard/ScoreboardManager.java
@@ -3,6 +3,7 @@
import de.simpleeco.SimpleEcoPlugin;
import de.simpleeco.config.ConfigManager;
import de.simpleeco.currency.BasicCurrency;
+import de.simpleeco.bank.BankManager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
@@ -10,35 +11,45 @@
import org.bukkit.scoreboard.*;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Manager für Scoreboards zur Anzeige der Spieler-Balance
*
* Verwaltet individuelle Scoreboards für jeden Spieler und aktualisiert
- * die Balance-Anzeige in regelmäßigen Intervallen.
+ * die Balance-Anzeige (Bargeld und Bank-Guthaben) in regelmäßigen Intervallen.
*/
public class ScoreboardManager {
private final SimpleEcoPlugin plugin;
private final ConfigManager configManager;
private final BasicCurrency currency;
+ private final BankManager bankManager;
- // Map zum Tracking der Player-Scoreboards
- private final Map playerScoreboards;
- private final Map playerObjectives;
+ // Map zum Tracking der Player-Scoreboards (Thread-safe)
+ private final ConcurrentHashMap playerScoreboards;
+ private final ConcurrentHashMap playerObjectives;
+
+ // Rate-Limiting für Scoreboard Updates (Thread-safe)
+ private final ConcurrentHashMap lastScoreboardUpdate;
// Update-Task
private BukkitTask updateTask;
- public ScoreboardManager(SimpleEcoPlugin plugin, ConfigManager configManager, BasicCurrency currency) {
+ public ScoreboardManager(SimpleEcoPlugin plugin, ConfigManager configManager, BasicCurrency currency, BankManager bankManager) {
this.plugin = plugin;
this.configManager = configManager;
this.currency = currency;
- this.playerScoreboards = new HashMap<>();
- this.playerObjectives = new HashMap<>();
+ this.bankManager = bankManager;
+ this.playerScoreboards = new ConcurrentHashMap<>();
+ this.playerObjectives = new ConcurrentHashMap<>();
+ this.lastScoreboardUpdate = new ConcurrentHashMap<>();
// Update-Task starten wenn Scoreboard aktiviert ist
if (isScoreboardEnabled()) {
@@ -119,6 +130,9 @@ public void removeScoreboard(Player player) {
// Scoreboard entfernen
playerScoreboards.remove(playerUUID);
+ // Update-Tracking entfernen
+ lastScoreboardUpdate.remove(playerUUID);
+
// Standard-Scoreboard zuweisen
try {
org.bukkit.scoreboard.ScoreboardManager bukkitScoreboardManager = Bukkit.getScoreboardManager();
@@ -136,6 +150,16 @@ public void removeScoreboard(Player player) {
* @param player Der Spieler
*/
public void updatePlayerScoreboard(Player player) {
+ updatePlayerScoreboard(player, false);
+ }
+
+ /**
+ * Aktualisiert das Scoreboard eines einzelnen Spielers
+ *
+ * @param player Der Spieler
+ * @param forceUpdate Wenn true, wird das Update erzwungen auch bei Rate-Limiting
+ */
+ public void updatePlayerScoreboard(Player player, boolean forceUpdate) {
if (!isScoreboardEnabled()) {
return;
}
@@ -149,45 +173,217 @@ public void updatePlayerScoreboard(Player player) {
return;
}
- try {
- // Alle bestehenden Scores löschen
- for (String entry : objective.getScoreboard().getEntries()) {
- objective.getScoreboard().resetScores(entry);
+ // Rate-Limiting um zu häufige Updates zu vermeiden (außer bei forceUpdate)
+ if (!forceUpdate) {
+ long currentTime = System.currentTimeMillis();
+ Long lastUpdate = lastScoreboardUpdate.get(playerUUID);
+ if (lastUpdate != null && (currentTime - lastUpdate) < 500) { // Max alle 0.5 Sekunden
+ return;
}
+ lastScoreboardUpdate.put(playerUUID, currentTime);
+ }
+
+ try {
+ // Beide Balances asynchron laden
+ CompletableFuture cashFuture = bankManager.getCashBalance(player);
+ CompletableFuture bankFuture = bankManager.getBankBalance(player);
- // Balance asynchron laden und Scoreboard aktualisieren
- currency.getBalance(player).thenAccept(balance -> {
+ CompletableFuture.allOf(cashFuture, bankFuture).thenAccept(ignored -> {
// Sicherstellen dass der Spieler noch online ist
if (!player.isOnline()) {
return;
}
- // Scoreboard-Zeilen aus Config laden
- List lines = configManager.getConfig().getStringList("scoreboard.lines");
-
- // Zeilen durchgehen und anzeigen (von unten nach oben)
- int score = lines.size();
- for (String line : lines) {
- // Platzhalter ersetzen
- String processedLine = line
- .replace("{balance}", currency.formatAmount(balance))
- .replace("{currency}", configManager.getCurrencyName())
- .replace("{player}", player.getName());
+ try {
+ double cashBalance = cashFuture.get();
+ double bankBalance = bankFuture.get();
+ double totalBalance = cashBalance + bankBalance;
- // Score setzen
- Score scoreEntry = objective.getScore(processedLine);
- scoreEntry.setScore(score--);
+ // Hauptthread für Scoreboard-Updates verwenden
+ Bukkit.getScheduler().runTask(plugin, () -> {
+ try {
+ // Prüfen ob Objective noch gültig ist (Player könnte disconnect gewesen sein)
+ Objective currentObjective = playerObjectives.get(playerUUID);
+ if (currentObjective != null && currentObjective.getScoreboard() != null) {
+ updateScoreboardDisplay(player, currentObjective, cashBalance, bankBalance, totalBalance);
+ }
+ } catch (Exception e) {
+ plugin.getLogger().warning("Fehler beim Aktualisieren der Scoreboard-Anzeige für " + player.getName() + ": " + e.getMessage());
+ }
+ });
+
+ } catch (Exception e) {
+ plugin.getLogger().warning("Fehler beim Abrufen der Kontostände für " + player.getName() + ": " + e.getMessage());
}
}).exceptionally(throwable -> {
- plugin.getLogger().severe("Fehler beim Laden der Balance für Scoreboard: " + throwable.getMessage());
+ plugin.getLogger().warning("Fehler beim Laden der Balances für Scoreboard von " + player.getName() + ": " + throwable.getMessage());
return null;
});
} catch (Exception e) {
- plugin.getLogger().severe("Fehler beim Aktualisieren des Scoreboards für " + player.getName() + ": " + e.getMessage());
+ plugin.getLogger().warning("Fehler beim Aktualisieren des Scoreboards für " + player.getName() + ": " + e.getMessage());
+ }
+ }
+
+ /**
+ * Aktualisiert die Scoreboard-Anzeige mit den Kontodaten
+ *
+ * @param player Der Spieler
+ * @param objective Das Scoreboard-Objective
+ * @param cashBalance Bargeld-Betrag
+ * @param bankBalance Bank-Guthaben
+ * @param totalBalance Gesamt-Guthaben
+ */
+ private void updateScoreboardDisplay(Player player, Objective objective, double cashBalance, double bankBalance, double totalBalance) {
+ try {
+ // Alle bestehenden Scores löschen (sichere Methode)
+ Set entries = new HashSet<>(objective.getScoreboard().getEntries());
+ for (String entry : entries) {
+ try {
+ objective.getScoreboard().resetScores(entry);
+ } catch (Exception e) {
+ // Ignoriere Fehler beim Entfernen einzelner Einträge
+ }
+ }
+
+ // Scoreboard-Zeilen aus Config laden oder Standard verwenden
+ List lines = configManager.getConfig().getStringList("scoreboard.lines");
+
+ // Falls keine Zeilen konfiguriert sind, Standard-Design verwenden
+ if (lines.isEmpty()) {
+ lines = getDefaultScoreboardLines();
+ }
+
+ // Zeilen durchgehen und anzeigen (von unten nach oben)
+ int score = lines.size();
+ int emptyLineCounter = 0; // Zähler für leere Zeilen um Duplikate zu vermeiden
+
+ for (String line : lines) {
+ // Platzhalter ersetzen
+ String processedLine = line
+ .replace("{cash}", formatAmountForScoreboard(cashBalance))
+ .replace("{bank}", formatAmountForScoreboard(bankBalance))
+ .replace("{total}", formatAmountForScoreboard(totalBalance))
+ .replace("{balance}", formatAmountForScoreboard(cashBalance)) // Für Rückwärtskompatibilität
+ .replace("{currency}", configManager.getCurrencyName())
+ .replace("{symbol}", configManager.getCurrencySymbol())
+ .replace("{player}", player.getName());
+
+ // Leere Zeilen behandeln (für bessere Formatierung)
+ if (processedLine.trim().isEmpty()) {
+ // Jede leere Zeile muss einzigartig sein, sonst wird sie nicht angezeigt
+ processedLine = " ".repeat(++emptyLineCounter);
+ }
+
+ // Zeile kürzen falls zu lang (Scoreboard max 40 Zeichen)
+ processedLine = truncateScoreboardLine(processedLine);
+
+ // Sicherstellen dass die Zeile einzigartig ist (Minecraft Scoreboard Requirement)
+ processedLine = ensureUniqueScoreboardEntry(processedLine, objective, score);
+
+ // Score setzen
+ Score scoreEntry = objective.getScore(processedLine);
+ scoreEntry.setScore(score--);
+ }
+ } catch (Exception e) {
+ plugin.getLogger().warning("Fehler beim Anzeigen des Scoreboards für " + player.getName() + ": " + e.getMessage());
}
}
+ /**
+ * Formatiert einen Betrag speziell für Scoreboard-Anzeige (kürzere Darstellung)
+ *
+ * @param amount Der Betrag
+ * @return Formatierter String
+ */
+ private String formatAmountForScoreboard(double amount) {
+ if (amount >= 1000000) {
+ return String.format("%.1fM", amount / 1000000);
+ } else if (amount >= 1000) {
+ return String.format("%.1fK", amount / 1000);
+ } else {
+ return String.format("%.0f", amount);
+ }
+ }
+
+ /**
+ * Kürzt eine Scoreboard-Zeile auf die maximale Länge
+ *
+ * @param line Die ursprüngliche Zeile
+ * @return Gekürzte Zeile
+ */
+ private String truncateScoreboardLine(String line) {
+ if (line.length() <= 40) {
+ return line;
+ }
+
+ // Intelligentes Kürzen: Versuche an Leerzeichen zu trennen
+ if (line.length() > 37) {
+ String truncated = line.substring(0, 37);
+ int lastSpace = truncated.lastIndexOf(' ');
+ if (lastSpace > 20) { // Nur wenn genug Text übrig bleibt
+ return truncated.substring(0, lastSpace) + "...";
+ } else {
+ return truncated + "...";
+ }
+ }
+
+ return line.substring(0, 40);
+ }
+
+ /**
+ * Stellt sicher, dass ein Scoreboard-Eintrag einzigartig ist
+ * (Minecraft zeigt doppelte Einträge nicht an)
+ *
+ * @param line Die ursprüngliche Zeile
+ * @param objective Das Scoreboard-Objective
+ * @param score Der Score-Wert
+ * @return Einzigartige Zeile
+ */
+ private String ensureUniqueScoreboardEntry(String line, Objective objective, int score) {
+ String originalLine = line;
+ int attempts = 0;
+
+ // Prüfen ob die Zeile bereits existiert
+ while (objective.getScoreboard().getEntries().contains(line) && attempts < 10) {
+ attempts++;
+ // Füge unsichtbare Zeichen hinzu um Einzigartigkeit zu gewährleisten
+ if (line.trim().isEmpty()) {
+ // Für leere Zeilen: zusätzliche Leerzeichen
+ line = " ".repeat(attempts + line.length());
+ } else {
+ // Für normale Zeilen: unsichtbare Farbcodes am Ende
+ String[] colors = {"§0", "§1", "§2", "§3", "§4", "§5", "§6", "§7", "§8", "§9"};
+ line = originalLine + colors[attempts % colors.length] + "§r";
+ }
+ }
+
+ return line;
+ }
+
+ /**
+ * Gibt die Standard-Scoreboard-Zeilen zurück
+ *
+ * @return Liste der Standard-Zeilen
+ */
+ private List getDefaultScoreboardLines() {
+ return List.of(
+ "§7§m────────────────────",
+ "§e§l💰 Finanzen",
+ "",
+ "§a💵 Bargeld:",
+ "§f {cash}",
+ "",
+ "§6🏦 Bank:",
+ "§f {bank}",
+ "",
+ "§e📊 Gesamt:",
+ "§f {total}",
+ "",
+ "§7§m────────────────────"
+ );
+ }
+
/**
* Aktualisiert alle Scoreboards
*/
@@ -242,6 +438,7 @@ public void shutdown() {
// Maps leeren
playerScoreboards.clear();
playerObjectives.clear();
+ lastScoreboardUpdate.clear();
plugin.getLogger().info("ScoreboardManager heruntergefahren");
}
@@ -258,14 +455,16 @@ public void reload() {
// Update-Task stoppen
stopUpdateTask();
- // Neu starten wenn aktiviert
+ // Neu starten wenn aktiviert (mit kleiner Verzögerung)
if (isScoreboardEnabled()) {
- startUpdateTask();
-
- // Scoreboards für alle Online-Spieler erstellen
- for (Player player : Bukkit.getOnlinePlayers()) {
- createScoreboard(player);
- }
+ Bukkit.getScheduler().runTaskLater(plugin, () -> {
+ startUpdateTask();
+
+ // Scoreboards für alle Online-Spieler erstellen
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ createScoreboard(player);
+ }
+ }, 5L); // 0.25 Sekunden Verzögerung
}
plugin.getLogger().info("ScoreboardManager neu geladen");
@@ -280,6 +479,42 @@ public int getActiveScoreboardCount() {
return playerScoreboards.size();
}
+ /**
+ * Wird aufgerufen wenn sich die Balance eines Spielers ändert
+ * Aktualisiert das Scoreboard sofort
+ *
+ * @param player Der Spieler dessen Balance sich geändert hat
+ */
+ public void onBalanceChanged(Player player) {
+ if (player != null && player.isOnline()) {
+ updatePlayerScoreboard(player, true); // Force Update
+ }
+ }
+
+ /**
+ * Wird aufgerufen wenn sich die Bank-Balance eines Spielers ändert
+ * Aktualisiert das Scoreboard sofort
+ *
+ * @param player Der Spieler dessen Bank-Balance sich geändert hat
+ */
+ public void onBankBalanceChanged(Player player) {
+ if (player != null && player.isOnline()) {
+ updatePlayerScoreboard(player, true); // Force Update
+ }
+ }
+
+ /**
+ * Aktualisiert das Scoreboard für einen Spieler basierend auf seiner UUID
+ *
+ * @param playerUuid Die UUID des Spielers
+ */
+ public void onBalanceChanged(UUID playerUuid) {
+ Player player = Bukkit.getPlayer(playerUuid);
+ if (player != null && player.isOnline()) {
+ updatePlayerScoreboard(player, true); // Force Update
+ }
+ }
+
/**
* Gibt die Plugin-Instanz zurück
*
diff --git a/src/main/java/de/simpleeco/tasks/VillagerLookTask.java b/src/main/java/de/simpleeco/tasks/VillagerLookTask.java
new file mode 100644
index 0000000..4291af4
--- /dev/null
+++ b/src/main/java/de/simpleeco/tasks/VillagerLookTask.java
@@ -0,0 +1,172 @@
+package de.simpleeco.tasks;
+
+import de.simpleeco.SimpleEcoPlugin;
+import de.simpleeco.villager.ShopVillagerManager;
+import de.simpleeco.bank.AtmVillagerManager;
+import org.bukkit.Location;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Villager;
+import org.bukkit.scheduler.BukkitRunnable;
+import org.bukkit.util.Vector;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.UUID;
+
+/**
+ * Task für Villager-Blickrichtung
+ *
+ * Diese Task sorgt dafür, dass Shop- und ATM-Villager zum nächsten Spieler
+ * in der Nähe schauen und sich entsprechend drehen.
+ */
+public class VillagerLookTask extends BukkitRunnable {
+
+ private final SimpleEcoPlugin plugin;
+ private final ShopVillagerManager shopVillagerManager;
+ private final AtmVillagerManager atmVillagerManager;
+ private final double maxLookDistance;
+
+ // Cache für Original-Positionen der Villager
+ private final ConcurrentHashMap villagerPositions = new ConcurrentHashMap<>();
+
+ public VillagerLookTask(SimpleEcoPlugin plugin, ShopVillagerManager shopVillagerManager,
+ AtmVillagerManager atmVillagerManager, double maxLookDistance) {
+ this.plugin = plugin;
+ this.shopVillagerManager = shopVillagerManager;
+ this.atmVillagerManager = atmVillagerManager;
+ this.maxLookDistance = maxLookDistance;
+ }
+
+ @Override
+ public void run() {
+ // Alle geladenen Welten durchgehen
+ plugin.getServer().getWorlds().forEach(world -> {
+ // Alle Villager in der Welt finden
+ List entities = world.getEntities();
+
+ for (Entity entity : entities) {
+ if (!(entity instanceof Villager villager)) {
+ continue;
+ }
+
+ // Prüfen ob es sich um einen speziellen Villager handelt
+ if (!shopVillagerManager.isShopVillager(villager) &&
+ !atmVillagerManager.isAtmVillager(villager)) {
+ continue;
+ }
+
+ // Original-Position des Villagers speichern/überprüfen
+ UUID villagerId = villager.getUniqueId();
+ Location currentLocation = villager.getLocation();
+ Location originalPosition = villagerPositions.get(villagerId);
+
+ if (originalPosition == null) {
+ // Erste Begegnung mit diesem Villager - Position speichern
+ villagerPositions.put(villagerId, currentLocation.clone());
+ originalPosition = currentLocation;
+ } else {
+ // Prüfen ob Villager sich bewegt hat (mehr als 0.5 Blöcke)
+ double distance = originalPosition.distance(currentLocation);
+ if (distance > 0.5) {
+ // Villager zurück zur Original-Position teleportieren
+ Location resetLoc = originalPosition.clone();
+ resetLoc.setYaw(currentLocation.getYaw()); // Blickrichtung beibehalten
+ resetLoc.setPitch(currentLocation.getPitch());
+ villager.teleport(resetLoc);
+ currentLocation = resetLoc;
+ }
+ }
+
+ // Nächsten Spieler in der Nähe finden
+ Player nearestPlayer = findNearestPlayer(villager);
+
+ if (nearestPlayer != null) {
+ // Villager zum Spieler drehen lassen (ohne Position zu ändern)
+ lookAtPlayer(villager, nearestPlayer, originalPosition);
+ }
+ }
+ });
+ }
+
+ /**
+ * Findet den nächsten Spieler in der Nähe des Villagers
+ *
+ * @param villager Der Villager
+ * @return Der nächste Spieler oder null wenn keiner in der Nähe ist
+ */
+ private Player findNearestPlayer(Villager villager) {
+ Location villagerLocation = villager.getLocation();
+ Player nearestPlayer = null;
+ double nearestDistance = maxLookDistance;
+
+ // Alle Spieler in der Welt durchgehen
+ for (Player player : villager.getWorld().getPlayers()) {
+ // Entfernung berechnen
+ double distance = player.getLocation().distance(villagerLocation);
+
+ // Prüfen ob Spieler näher ist als der bisherige nächste
+ if (distance < nearestDistance) {
+ nearestPlayer = player;
+ nearestDistance = distance;
+ }
+ }
+
+ return nearestPlayer;
+ }
+
+ /**
+ * Lässt den Villager zum Spieler schauen
+ *
+ * @param villager Der Villager
+ * @param player Der Spieler
+ * @param fixedPosition Die feste Position des Villagers (darf nicht verändert werden)
+ */
+ private void lookAtPlayer(Villager villager, Player player, Location fixedPosition) {
+ Location playerLoc = player.getLocation();
+
+ // Vektor vom Villager zum Spieler berechnen
+ Vector direction = playerLoc.toVector().subtract(fixedPosition.toVector());
+
+ // Y-Komponente ignorieren (nur horizontale Drehung)
+ direction.setY(0);
+ direction.normalize();
+
+ // Yaw berechnen (horizontale Drehung)
+ double yaw = Math.toDegrees(Math.atan2(-direction.getX(), direction.getZ()));
+
+ // Pitch berechnen (vertikale Drehung) - leicht nach unten schauen da Spieler meist höher sind
+ double heightDiff = playerLoc.getY() - fixedPosition.getY();
+ double horizontalDistance = Math.sqrt(direction.getX() * direction.getX() + direction.getZ() * direction.getZ());
+ double pitch = Math.toDegrees(Math.atan2(-heightDiff, horizontalDistance));
+
+ // Pitch begrenzen für realistisches Aussehen
+ pitch = Math.max(-30, Math.min(30, pitch));
+
+ // Neue Location mit angepasster Blickrichtung erstellen (Position bleibt gleich)
+ Location newLoc = fixedPosition.clone();
+ newLoc.setYaw((float) yaw);
+ newLoc.setPitch((float) pitch);
+
+ // Villager teleportieren (mit neuer Blickrichtung, aber fester Position)
+ villager.teleport(newLoc);
+ }
+
+ /**
+ * Startet die VillagerLookTask
+ *
+ * @param plugin Das Plugin
+ * @param shopVillagerManager Der Shop-Villager-Manager
+ * @param atmVillagerManager Der ATM-Villager-Manager
+ * @param maxLookDistance Maximale Entfernung zum Spieler schauen
+ * @param updateInterval Update-Intervall in Ticks
+ * @return Die gestartete Task
+ */
+ public static VillagerLookTask start(SimpleEcoPlugin plugin, ShopVillagerManager shopVillagerManager,
+ AtmVillagerManager atmVillagerManager, double maxLookDistance,
+ long updateInterval) {
+ VillagerLookTask task = new VillagerLookTask(plugin, shopVillagerManager, atmVillagerManager, maxLookDistance);
+ task.runTaskTimer(plugin, 20L, updateInterval); // Start nach 1 Sekunde, dann alle updateInterval Ticks
+ return task;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/simpleeco/trading/CustomVillagerTrader.java b/src/main/java/de/simpleeco/trading/CustomVillagerTrader.java
index abfb6c9..46ac9c6 100644
--- a/src/main/java/de/simpleeco/trading/CustomVillagerTrader.java
+++ b/src/main/java/de/simpleeco/trading/CustomVillagerTrader.java
@@ -73,20 +73,35 @@ public void openTradingMenu(Player player) {
private CompletableFuture populateMenu(TradingSession session) {
Inventory inventory = session.getInventory();
+ // Inventar leeren
+ inventory.clear();
+
// Liste aller handelbaren Items (nur Items die kaufbar oder verkaufbar sind)
- List tradeableItems = configManager.getItemPrices().entrySet()
+ List allTradeableItems = configManager.getItemPrices().entrySet()
.stream()
.filter(entry -> entry.getValue().isBuyable() || entry.getValue().isSellable())
.map(Map.Entry::getKey)
.sorted((a, b) -> a.name().compareTo(b.name()))
.toList();
+ // Items in Session speichern und Seitenzahl berechnen
+ session.setAllTradeableItems(allTradeableItems);
+
+ // Aktuelle Seite validieren
+ if (session.getCurrentPage() >= session.getTotalPages()) {
+ session.setCurrentPage(0);
+ }
+
+ // Items für aktuelle Seite berechnen
+ int itemsPerPage = getItemsPerPage();
+ int startIndex = session.getCurrentPage() * itemsPerPage;
+ int endIndex = Math.min(startIndex + itemsPerPage, allTradeableItems.size());
+
+ List pageItems = allTradeableItems.subList(startIndex, endIndex);
List> itemFutures = new ArrayList<>();
int slot = 0;
- for (Material material : tradeableItems) {
- if (slot >= 45) break; // Platz für Navigationselemente lassen
-
+ for (Material material : pageItems) {
final int itemSlot = slot;
CompletableFuture itemFuture = createTradeItem(material)
.thenAccept(itemStack -> {
@@ -100,7 +115,7 @@ private CompletableFuture populateMenu(TradingSession session) {
}
// Navigation und Info-Items hinzufügen
- addNavigationItems(inventory);
+ addNavigationItems(inventory, session);
return CompletableFuture.allOf(itemFutures.toArray(new CompletableFuture[0]));
}
@@ -184,8 +199,51 @@ private CompletableFuture createTradeItem(Material material) {
* Fügt Navigations- und Info-Items zum Inventar hinzu
*
* @param inventory Das Inventar
+ * @param session Die Trading-Session
*/
- private void addNavigationItems(Inventory inventory) {
+ private void addNavigationItems(Inventory inventory, TradingSession session) {
+ // Vorherige Seite Button (nur anzeigen wenn verfügbar)
+ if (session.hasPreviousPage()) {
+ ItemStack prevItem = new ItemStack(Material.ARROW);
+ ItemMeta prevMeta = prevItem.getItemMeta();
+ if (prevMeta != null) {
+ prevMeta.setDisplayName("§a§l← Vorherige Seite");
+ List prevLore = new ArrayList<>();
+ prevLore.add("§7Gehe zur Seite " + session.getCurrentPage());
+ prevMeta.setLore(prevLore);
+ prevItem.setItemMeta(prevMeta);
+ }
+ inventory.setItem(45, prevItem);
+ }
+
+ // Nächste Seite Button (nur anzeigen wenn verfügbar)
+ if (session.hasNextPage()) {
+ ItemStack nextItem = new ItemStack(Material.ARROW);
+ ItemMeta nextMeta = nextItem.getItemMeta();
+ if (nextMeta != null) {
+ nextMeta.setDisplayName("§a§lNächste Seite →");
+ List nextLore = new ArrayList<>();
+ nextLore.add("§7Gehe zur Seite " + (session.getCurrentPage() + 2));
+ nextMeta.setLore(nextLore);
+ nextItem.setItemMeta(nextMeta);
+ }
+ inventory.setItem(53, nextItem);
+ }
+
+ // Seiten-Info in der Mitte
+ ItemStack pageInfo = new ItemStack(Material.PAPER);
+ ItemMeta pageInfoMeta = pageInfo.getItemMeta();
+ if (pageInfoMeta != null) {
+ pageInfoMeta.setDisplayName("§6§lSeite " + (session.getCurrentPage() + 1) + " von " + session.getTotalPages());
+ List pageInfoLore = new ArrayList<>();
+ pageInfoLore.add("§7");
+ pageInfoLore.add("§7Zeigt " + session.getAllTradeableItems().size() + " handelbare Items");
+ pageInfoLore.add("§7auf " + session.getTotalPages() + " Seiten");
+ pageInfoMeta.setLore(pageInfoLore);
+ pageInfo.setItemMeta(pageInfoMeta);
+ }
+ inventory.setItem(49, pageInfo);
+
// Info-Item
ItemStack infoItem = new ItemStack(Material.BOOK);
ItemMeta infoMeta = infoItem.getItemMeta();
@@ -205,16 +263,27 @@ private void addNavigationItems(Inventory inventory) {
infoMeta.setLore(infoLore);
infoItem.setItemMeta(infoMeta);
}
- inventory.setItem(49, infoItem);
+ inventory.setItem(47, infoItem);
- // Schließen-Button
- ItemStack closeItem = new ItemStack(Material.BARRIER);
- ItemMeta closeMeta = closeItem.getItemMeta();
- if (closeMeta != null) {
- closeMeta.setDisplayName("§c§lMenü schließen");
- closeItem.setItemMeta(closeMeta);
+ // Schließen-Button (nur wenn keine nächste Seite verfügbar ist, sonst wird der Slot verwendet)
+ if (!session.hasNextPage()) {
+ ItemStack closeItem = new ItemStack(Material.BARRIER);
+ ItemMeta closeMeta = closeItem.getItemMeta();
+ if (closeMeta != null) {
+ closeMeta.setDisplayName("§c§lMenü schließen");
+ closeItem.setItemMeta(closeMeta);
+ }
+ inventory.setItem(53, closeItem);
+ } else {
+ // Schließen-Button auf anderen Slot verschieben
+ ItemStack closeItem = new ItemStack(Material.BARRIER);
+ ItemMeta closeMeta = closeItem.getItemMeta();
+ if (closeMeta != null) {
+ closeMeta.setDisplayName("§c§lMenü schließen");
+ closeItem.setItemMeta(closeMeta);
+ }
+ inventory.setItem(51, closeItem);
}
- inventory.setItem(53, closeItem);
}
/**
@@ -231,13 +300,35 @@ public void handleMenuClick(Player player, ItemStack clickedItem, ClickType clic
return;
}
+ // Pagination Navigation behandeln
+ if (slot == 45 && session.hasPreviousPage()) { // Vorherige Seite
+ session.setCurrentPage(session.getCurrentPage() - 1);
+ populateMenu(session);
+ return;
+ }
+
+ if (slot == 53 && session.hasNextPage()) { // Nächste Seite
+ session.setCurrentPage(session.getCurrentPage() + 1);
+ populateMenu(session);
+ return;
+ }
+
// Spezielle Slots behandeln
- if (slot == 53) { // Schließen-Button
+ if (slot == 53 && !session.hasNextPage()) { // Schließen-Button (wenn keine nächste Seite)
player.closeInventory();
return;
}
- if (slot == 49) { // Info-Button
+ if (slot == 51) { // Schließen-Button (alternativer Slot)
+ player.closeInventory();
+ return;
+ }
+
+ if (slot == 49) { // Seiten-Info
+ return; // Nur anzeigen, keine Aktion
+ }
+
+ if (slot == 47) { // Info-Button
return; // Nur anzeigen, keine Aktion
}
@@ -253,6 +344,11 @@ public void handleMenuClick(Player player, ItemStack clickedItem, ClickType clic
return;
}
+ // Prüfen ob der Slot tatsächlich ein handelbares Item enthält (0-44 sind handelbare Items)
+ if (slot >= 45) {
+ return; // Navigation-Bereich, kein handelbares Item
+ }
+
// Handelsaktion bestimmen
boolean isBuying = clickType == ClickType.LEFT || clickType == ClickType.SHIFT_LEFT;
boolean isSelling = clickType == ClickType.RIGHT || clickType == ClickType.SHIFT_RIGHT;
@@ -398,6 +494,39 @@ private void refreshMenu(Player player) {
}
}
+ /**
+ * Springt zu einer bestimmten Seite im Shop-Menü
+ *
+ * @param player Der Spieler
+ * @param page Die Seitennummer (0-basiert)
+ */
+ public void goToPage(Player player, int page) {
+ TradingSession session = activeSessions.get(player);
+ if (session != null) {
+ session.setCurrentPage(page);
+ populateMenu(session);
+ }
+ }
+
+ /**
+ * Berechnet die maximale Anzahl von Items pro Seite
+ *
+ * @return Anzahl Items pro Seite
+ */
+ private int getItemsPerPage() {
+ return 45; // 9 * 5 Zeilen für Items, letzte Zeile für Navigation
+ }
+
+ /**
+ * Berechnet die Gesamtzahl der Seiten basierend auf der Anzahl der Items
+ *
+ * @param totalItems Gesamtanzahl der Items
+ * @return Anzahl der Seiten
+ */
+ private int calculateTotalPages(int totalItems) {
+ return Math.max(1, (int) Math.ceil((double) totalItems / getItemsPerPage()));
+ }
+
/**
* Prüft ob ein Spieler genügend Inventar-Platz hat
*
@@ -498,8 +627,9 @@ public void closeSession(Player player) {
* @return Der deutsche Name
*/
private String getGermanItemName(Material material) {
- // Einfache deutsche Übersetzungen für die wichtigsten Items
+ // Deutsche Übersetzungen für alle handelbaren Items
return switch (material) {
+ // Traditionelle Lebensmittel
case WHEAT -> "Weizen";
case CARROT -> "Karotte";
case POTATO -> "Kartoffel";
@@ -509,11 +639,225 @@ private String getGermanItemName(Material material) {
case COOKED_BEEF -> "Gebratenes Rindfleisch";
case COOKED_PORKCHOP -> "Gebratenes Schweinefleisch";
case COOKED_CHICKEN -> "Gebratenes Hühnchen";
- case DIAMOND -> "Diamant";
+
+ // Holz und Holzprodukte
+ case OAK_LOG -> "Eichenstamm";
+ case OAK_LEAVES -> "Eichenlaub";
+ case OAK_SAPLING -> "Eichensetzling";
+ case SPRUCE_LOG -> "Fichtenstamm";
+ case SPRUCE_LEAVES -> "Fichtenlaub";
+ case SPRUCE_SAPLING -> "Fichtensetzling";
+ case BIRCH_LOG -> "Birkenstamm";
+ case BIRCH_LEAVES -> "Birkenlaub";
+ case BIRCH_SAPLING -> "Birkensetzling";
+ case JUNGLE_LOG -> "Tropenstamm";
+ case JUNGLE_LEAVES -> "Tropenlaub";
+ case JUNGLE_SAPLING -> "Tropensetzling";
+ case ACACIA_LOG -> "Akazienstamm";
+ case ACACIA_LEAVES -> "Akazienlaub";
+ case ACACIA_SAPLING -> "Akaziensetzling";
+ case DARK_OAK_LOG -> "Schwarzeichenstamm";
+ case DARK_OAK_LEAVES -> "Schwarzeichenlaub";
+ case DARK_OAK_SAPLING -> "Schwarzeichensetzling";
+ case MANGROVE_LOG -> "Mangrovenstamm";
+ case MANGROVE_LEAVES -> "Mangrovenlaub";
+ case MANGROVE_PROPAGULE -> "Mangrovenkeim";
+ case CHERRY_LOG -> "Kirschstamm";
+ case CHERRY_LEAVES -> "Kirschlaub";
+ case CHERRY_SAPLING -> "Kirschsetzling";
+ case BAMBOO_BLOCK -> "Bambusblock";
+ case BAMBOO -> "Bambus";
+ case CRIMSON_STEM -> "Karmesinroter Stamm";
+ case CRIMSON_FUNGUS -> "Karmesinroter Pilz";
+ case CRIMSON_ROOTS -> "Karmesinrote Wurzeln";
+ case WARPED_STEM -> "Wirriger Stamm";
+ case WARPED_FUNGUS -> "Wirriger Pilz";
+ case WARPED_ROOTS -> "Wirrige Wurzeln";
+
+ // Mob-Drops
+ case BEEHIVE -> "Bienenstock";
+ case BEE_NEST -> "Bienennest";
+ case HONEY_BLOCK -> "Honigblock";
+ case HONEYCOMB_BLOCK -> "Wabenblock";
+ case HONEY_BOTTLE -> "Honigflasche";
+ case EGG -> "Ei";
+ case FEATHER -> "Feder";
+ case LEATHER -> "Leder";
+ case RABBIT_HIDE -> "Kaninchenfell";
+ case TURTLE_EGG -> "Schildkrötenei";
+ case SCUTE -> "Schildkrötenpanzer";
+ case PUFFERFISH -> "Kugelfisch";
+ case INK_SAC -> "Tintenbeutel";
+ case GLOW_INK_SAC -> "Leucht-Tintenbeutel";
+ case BONE -> "Knochen";
+ case ARROW -> "Pfeil";
+ case BONE_MEAL -> "Knochenmehl";
+ case BONE_BLOCK -> "Knochenblock";
+ case STRING -> "Faden";
+ case SPIDER_EYE -> "Spinnenauge";
+ case SLIME_BALL -> "Schleimball";
+ case SLIME_BLOCK -> "Schleimblock";
+ case GUNPOWDER -> "Schwarzpulver";
+ case PHANTOM_MEMBRANE -> "Phantomhaut";
+ case ROTTEN_FLESH -> "Verrottetes Fleisch";
+ case BLAZE_ROD -> "Lohenrute";
+ case BLAZE_POWDER -> "Lohenpulver";
+ case MAGMA_CREAM -> "Magmacreme";
+ case GHAST_TEAR -> "Ghastträne";
+ case ENDER_PEARL -> "Enderperle";
+ case ENDER_EYE -> "Enderauge";
+ case SHULKER_SHELL -> "Shulkerschale";
+ case DRAGON_BREATH -> "Drachenatem";
+
+ // Blumen und Pflanzen
+ case ALLIUM -> "Zierlauch";
+ case AZURE_BLUET -> "Porzellansternchen";
+ case BLUE_ORCHID -> "Blaue Orchidee";
+ case CORNFLOWER -> "Kornblume";
+ case DANDELION -> "Löwenzahn";
+ case LILAC -> "Flieder";
+ case LILY_OF_THE_VALLEY -> "Maiglöckchen";
+ case PEONY -> "Pfingstrose";
+ case POPPY -> "Mohn";
+ case ROSE_BUSH -> "Rosenstrauch";
+ case SUNFLOWER -> "Sonnenblume";
+ case RED_TULIP -> "Rote Tulpe";
+ case ORANGE_TULIP -> "Orange Tulpe";
+ case WHITE_TULIP -> "Weiße Tulpe";
+ case PINK_TULIP -> "Rosa Tulpe";
+ case OXEYE_DAISY -> "Margerite";
+ case DEAD_BUSH -> "Toter Busch";
+ case CACTUS -> "Kaktus";
+ case FERN -> "Farn";
+ case LARGE_FERN -> "Großer Farn";
+ case SHORT_GRASS -> "Gras";
+ case TALL_GRASS -> "Hohes Gras";
+ case LILY_PAD -> "Seerosenblatt";
+
+ // Spezielle Pflanzen
+ case AZALEA -> "Azalee";
+ case FLOWERING_AZALEA -> "Blühende Azalee";
+ case HANGING_ROOTS -> "Hängewurzeln";
+ case MOSS_BLOCK -> "Moosblock";
+ case MOSS_CARPET -> "Moosteppich";
+ case CHORUS_FLOWER -> "Chorusblüte";
+ case CHORUS_PLANT -> "Choruspflanze";
+ case BIG_DRIPLEAF -> "Großes Tropfblatt";
+ case SMALL_DRIPLEAF -> "Kleines Tropfblatt";
+ case BROWN_MUSHROOM -> "Brauner Pilz";
+ case BROWN_MUSHROOM_BLOCK -> "Brauner Pilzblock";
+ case RED_MUSHROOM -> "Roter Pilz";
+ case RED_MUSHROOM_BLOCK -> "Roter Pilzblock";
+ case MUSHROOM_STEM -> "Pilzstiel";
+ case NETHER_SPROUTS -> "Nether-Sprossen";
+ case TWISTING_VINES -> "Gedrehte Ranken";
+ case WEEPING_VINES -> "Weinende Ranken";
+ case VINE -> "Ranken";
+ case SHROOMLIGHT -> "Pilzlicht";
+ case GLOW_BERRIES -> "Leuchtbeeren";
+ case GLOW_LICHEN -> "Leuchtflechte";
+ case SPORE_BLOSSOM -> "Sporenblüte";
+ case SWEET_BERRY_BUSH -> "Süßbeerenstrauch";
+
+ // Blöcke und Baumaterialien
+ case ANDESITE -> "Andesit";
+ case DIORITE -> "Diorit";
+ case GRANITE -> "Granit";
+ case TUFF -> "Tuffstein";
+ case CALCITE -> "Kalzit";
+ case BLACKSTONE -> "Schwarzstein";
+ case POLISHED_BLACKSTONE -> "Polierter Schwarzstein";
+ case POLISHED_BLACKSTONE_BRICKS -> "Polierte Schwarzstein-Ziegel";
+ case DRIPSTONE_BLOCK -> "Tropfstein";
+ case POINTED_DRIPSTONE -> "Spitzer Tropfstein";
+ case MAGMA_BLOCK -> "Magmablock";
+ case SAND -> "Sand";
+ case RED_SAND -> "Roter Sand";
+ case SANDSTONE -> "Sandstein";
+ case RED_SANDSTONE -> "Roter Sandstein";
+ case TERRACOTTA -> "Terrakotta";
+ case CLAY -> "Ton";
+ case BRICK -> "Ziegel";
+ case GRAVEL -> "Kies";
+ case DIRT -> "Erde";
+ case COARSE_DIRT -> "Grobe Erde";
+ case GRASS_BLOCK -> "Grasblock";
+ case SOUL_SAND -> "Seelensand";
+ case SOUL_SOIL -> "Seelenerde";
+ case BLUE_ICE -> "Blaues Eis";
+ case ICE -> "Eis";
+ case PACKED_ICE -> "Packeis";
+ case SNOW_BLOCK -> "Schneeblock";
+
+ // Seltene Blöcke
+ case NETHER_BRICKS -> "Netherziegel";
+ case QUARTZ_BLOCK -> "Quarzblock";
+ case GLOWSTONE -> "Glowstone";
+ case OBSIDIAN -> "Obsidian";
+ case CRYING_OBSIDIAN -> "Weinender Obsidian";
+ case REDSTONE_BLOCK -> "Redstone-Block";
+ case CHAIN -> "Kette";
+ case IRON_BARS -> "Eisengitter";
+ case LANTERN -> "Laterne";
+ case SOUL_LANTERN -> "Seelenlaterne";
+ case TORCH -> "Fackel";
+ case SOUL_TORCH -> "Seelenfackel";
+ case TNT -> "TNT";
+ case SCAFFOLDING -> "Gerüst";
+ case LEAD -> "Leine";
+ case NAME_TAG -> "Namensschild";
+
+ // Funktionale Blöcke
+ case ENDER_CHEST -> "Endertruhe";
+ case BARREL -> "Fass";
+ case TRAPPED_CHEST -> "Redstone-Truhe";
+ case BLAST_FURNACE -> "Schmelzofen";
+ case SMOKER -> "Räucherofen";
+ case CAMPFIRE -> "Lagerfeuer";
+ case COMPOSTER -> "Komposter";
+ case GRINDSTONE -> "Schleifstein";
+ case CARTOGRAPHY_TABLE -> "Kartentisch";
+ case SMITHING_TABLE -> "Schmiedetisch";
+ case FLETCHING_TABLE -> "Bognerisch";
+ case LOOM -> "Webstuhl";
+ case LECTERN -> "Lesepult";
+ case ANVIL -> "Amboss";
+
+ // Erze
+ case COAL_ORE -> "Kohleerz";
+ case IRON_ORE -> "Eisenerz";
+ case COPPER_ORE -> "Kupfererz";
+ case GOLD_ORE -> "Golderz";
+ case DIAMOND_ORE -> "Diamanterz";
+ case EMERALD_ORE -> "Smaragderz";
+ case NETHER_QUARTZ_ORE -> "Netherquarzerz";
+ case NETHER_GOLD_ORE -> "Nethergolderz";
+ case LAPIS_ORE -> "Lapislazulierz";
+ case ANCIENT_DEBRIS -> "Antike Trümmer";
+ case REDSTONE_ORE -> "Redstone-Erz";
+
+ // Ingots und raffinierte Materialien
+ case COAL -> "Kohle";
case IRON_INGOT -> "Eisenbarren";
+ case COPPER_INGOT -> "Kupferbarren";
case GOLD_INGOT -> "Goldbarren";
+ case DIAMOND -> "Diamant";
case EMERALD -> "Smaragd";
- case COAL -> "Kohle";
+ case QUARTZ -> "Netherquarz";
+ case LAPIS_LAZULI -> "Lapislazuli";
+ case REDSTONE -> "Redstone-Staub";
+
+ // Spawn Eggs
+ case ALLAY_SPAWN_EGG -> "Allay-Spawn-Ei";
+ // case ARMADILLO_SPAWN_EGG -> "Gürteltier-Spawn-Ei"; // Not available in this version
+ case ENDERMITE_SPAWN_EGG -> "Endermilbe-Spawn-Ei";
+ case FROG_SPAWN_EGG -> "Frosch-Spawn-Ei";
+ case GOAT_SPAWN_EGG -> "Ziegen-Spawn-Ei";
+ case ZOMBIE_SPAWN_EGG -> "Zombie-Spawn-Ei";
+ case SHULKER_SPAWN_EGG -> "Shulker-Spawn-Ei";
+ case STRIDER_SPAWN_EGG -> "Schreiter-Spawn-Ei";
+ case VILLAGER_SPAWN_EGG -> "Dorfbewohner-Spawn-Ei";
+
default -> material.name().replace("_", " ").toLowerCase();
};
}
@@ -524,10 +868,16 @@ private String getGermanItemName(Material material) {
private static class TradingSession {
private final Player player;
private final Inventory inventory;
+ private int currentPage;
+ private int totalPages;
+ private List allTradeableItems;
public TradingSession(Player player, Inventory inventory) {
this.player = player;
this.inventory = inventory;
+ this.currentPage = 0;
+ this.totalPages = 1;
+ this.allTradeableItems = new ArrayList<>();
}
public Player getPlayer() {
@@ -537,5 +887,39 @@ public Player getPlayer() {
public Inventory getInventory() {
return inventory;
}
+
+ public int getCurrentPage() {
+ return currentPage;
+ }
+
+ public void setCurrentPage(int currentPage) {
+ this.currentPage = Math.max(0, Math.min(currentPage, totalPages - 1));
+ }
+
+ public int getTotalPages() {
+ return totalPages;
+ }
+
+ public void setTotalPages(int totalPages) {
+ this.totalPages = Math.max(1, totalPages);
+ }
+
+ public List getAllTradeableItems() {
+ return allTradeableItems;
+ }
+
+ public void setAllTradeableItems(List allTradeableItems) {
+ this.allTradeableItems = allTradeableItems != null ? allTradeableItems : new ArrayList<>();
+ // Berechne die Gesamtseitenzahl basierend auf Items pro Seite
+ this.totalPages = Math.max(1, (int) Math.ceil((double) this.allTradeableItems.size() / 45.0));
+ }
+
+ public boolean hasNextPage() {
+ return currentPage < totalPages - 1;
+ }
+
+ public boolean hasPreviousPage() {
+ return currentPage > 0;
+ }
}
}
\ No newline at end of file
diff --git a/src/main/java/de/simpleeco/villager/ShopVillagerManager.java b/src/main/java/de/simpleeco/villager/ShopVillagerManager.java
index a386888..1ba498d 100644
--- a/src/main/java/de/simpleeco/villager/ShopVillagerManager.java
+++ b/src/main/java/de/simpleeco/villager/ShopVillagerManager.java
@@ -80,8 +80,17 @@ private void configureShopVillager(Villager villager) {
villager.setCustomName("§e§l" + villagerName);
villager.setCustomNameVisible(true);
- // Bewegung verhindern
- villager.setAI(false);
+ // Bewegung konfigurieren - AI nur teilweise deaktivieren für Look-Funktionalität
+ boolean lookAtPlayers = configManager.getConfig().getBoolean("villagerBehavior.lookAtPlayers", true);
+ if (lookAtPlayers) {
+ // AI aktiviert lassen, aber Bewegung einschränken
+ villager.setAI(true);
+ villager.setCollidable(false); // Keine Kollision mit anderen Entities
+ } else {
+ // Vollständig deaktivieren
+ villager.setAI(false);
+ }
+
villager.setSilent(true);
villager.setInvulnerable(true);
villager.setRemoveWhenFarAway(false);
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 0c9deb9..a12a37a 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -22,17 +22,45 @@ atmVillager:
profession: "LIBRARIAN" # Beruf des ATM-Villagers (LIBRARIAN für Banker-Look)
villagerType: "PLAINS" # Typ des Villagers (PLAINS, DESERT, JUNGLE, etc.)
+# Villager-Verhalten Einstellungen
+villagerBehavior:
+ lookAtPlayers: true # Sollen Villager zu Spielern schauen?
+ lookDistance: 8.0 # Maximale Entfernung für das Anschauen von Spielern
+ lookUpdateInterval: 20 # Update-Intervall in Ticks (20 = 1 Sekunde)
+
+# Spawn-Kosteneinstellungen
+spawnCosts:
+ shopCost: 500.0 # Kosten für das Spawnen eines Shops
+ atmCost: 750.0 # Kosten für das Spawnen eines ATMs
+ enabled: true # Kosten aktivieren/deaktivieren
+ freeForAdmins: false # Admins mit simpleeco.spawn.free spawnen kostenlos
+ enforceForAll: true # Wenn true, zahlen ALLE Spieler (auch OPs), wenn false nur normale Spieler
+
+# Todesstrafe-Einstellungen
+deathPenalty:
+ enabled: true # Todesstrafe aktivieren/deaktivieren
+ cashLossPercentage: 0.25 # Anteil des Bargeldes der verloren geht (0.25 = 25%)
+ minLossAmount: 1.0 # Minimaler Verlust-Betrag
+ maxLossAmount: 10000.0 # Maximaler Verlust-Betrag
+ exemptPermission: "simpleeco.death.exempt" # Berechtigung um Todesstrafe zu umgehen
+ onlyPvPDeath: false # Nur bei PvP-Tod Geld verlieren (false = bei jedem Tod)
+
# Scoreboard Einstellungen
scoreboard:
enabled: true # Scoreboard aktivieren/deaktivieren
- title: "§6§lSimpleEco" # Titel des Scoreboards
+ title: "§6§l✦ SimpleEco ✦" # Titel des Scoreboards
updateInterval: 20 # Update-Intervall in Ticks (20 = 1 Sekunde)
lines:
- - "§7§m "
- - "§e§lKontostand:"
- - "§a{balance} {currency}"
+ - "§7§m━━━━━━━━━━━━━━━━"
+ - "§e§lFinanzen"
+ - ""
+ - "§aBargeld:"
+ - "§f {cash}"
- ""
- - "§7§m "
+ - "§6Bank:"
+ - "§f {bank}"
+ - ""
+ - "§7§m━━━━━━━━━━━━━━━━"
# Preiseinstellungen
pricing:
@@ -55,100 +83,1270 @@ pricing:
# Höherer priceFactor = volatiler (stärkere Preisschwankungen)
# Niedrigere referenceAmount = reaktionsschneller (weniger Handel nötig für Preisänderung)
items:
- WHEAT:
+ # Holz und Holzprodukte
+ OAK_LOG:
basePrice: 10.0
minPrice: 5.0
- maxPrice: 50.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: false
+ OAK_LEAVES:
+ basePrice: 15.0
+ minPrice: 8.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: false
+ OAK_SAPLING:
+ basePrice: 15.0
+ minPrice: 8.0
+ maxPrice: 30.0
buyable: true
sellable: true
- # referenceAmount: 1000 # Optional: Überschreibt globalen Wert
- # priceFactor: 0.05 # Optional: Überschreibt globalen Wert
- CARROT:
- basePrice: 8.0
- minPrice: 4.0
+ SPRUCE_LOG:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: false
+ SPRUCE_LEAVES:
+ basePrice: 15.0
+ minPrice: 8.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: false
+ SPRUCE_SAPLING:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: false
+ BIRCH_LOG:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: false
+ BIRCH_LEAVES:
+ basePrice: 15.0
+ minPrice: 8.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: false
+ BIRCH_SAPLING:
+ basePrice: 15.0
+ minPrice: 8.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: false
+ JUNGLE_LOG:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: false
+ JUNGLE_LEAVES:
+ basePrice: 15.0
+ minPrice: 8.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: false
+ JUNGLE_SAPLING:
+ basePrice: 15.0
+ minPrice: 8.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: false
+ ACACIA_LOG:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: false
+ ACACIA_LEAVES:
+ basePrice: 15.0
+ minPrice: 8.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: false
+ ACACIA_SAPLING:
+ basePrice: 15.0
+ minPrice: 8.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: false
+ DARK_OAK_LOG:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: false
+ DARK_OAK_LEAVES:
+ basePrice: 15.0
+ minPrice: 8.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: false
+ DARK_OAK_SAPLING:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: false
+ MANGROVE_LOG:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: false
+ MANGROVE_LEAVES:
+ basePrice: 15.0
+ minPrice: 8.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: false
+ MANGROVE_PROPAGULE:
+ basePrice: 18.0
+ minPrice: 10.0
+ maxPrice: 35.0
+ buyable: true
+ sellable: false
+ CHERRY_LOG:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: false
+ CHERRY_LEAVES:
+ basePrice: 15.0
+ minPrice: 8.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: false
+ CHERRY_SAPLING:
+ basePrice: 15.0
+ minPrice: 8.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: false
+ BAMBOO_BLOCK:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 35.0
+ buyable: true
+ sellable: false
+ BAMBOO:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 22.0
+ buyable: true
+ sellable: false
+ CRIMSON_STEM:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: false
+ CRIMSON_FUNGUS:
+ basePrice: 12.0
+ minPrice: 6.0
+ maxPrice: 25.0
+ buyable: true
+ sellable: false
+ CRIMSON_ROOTS:
+ basePrice: 16.0
+ minPrice: 8.0
+ maxPrice: 28.0
+ buyable: true
+ sellable: false
+ WARPED_STEM:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: false
+ WARPED_FUNGUS:
+ basePrice: 12.0
+ minPrice: 6.0
+ maxPrice: 25.0
+ buyable: true
+ sellable: false
+ WARPED_ROOTS:
+ basePrice: 16.0
+ minPrice: 8.0
+ maxPrice: 28.0
+ buyable: true
+ sellable: false
+
+ # Mob-Drops
+ BEEHIVE:
+ basePrice: 35.0
+ minPrice: 20.0
+ maxPrice: 60.0
+ buyable: true
+ sellable: true
+ BEE_NEST:
+ basePrice: 35.0
+ minPrice: 20.0
+ maxPrice: 60.0
+ buyable: true
+ sellable: true
+ HONEY_BLOCK:
+ basePrice: 35.0
+ minPrice: 20.0
+ maxPrice: 60.0
+ buyable: true
+ sellable: true
+ HONEYCOMB_BLOCK:
+ basePrice: 35.0
+ minPrice: 20.0
+ maxPrice: 60.0
+ buyable: true
+ sellable: true
+ HONEY_BOTTLE:
+ basePrice: 35.0
+ minPrice: 20.0
+ maxPrice: 60.0
+ buyable: true
+ sellable: true
+ EGG:
+ basePrice: 2.0
+ minPrice: 1.0
maxPrice: 40.0
buyable: true
+ sellable: false
+ FEATHER:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 18.0
+ buyable: true
sellable: true
- POTATO:
- basePrice: 8.0
- minPrice: 4.0
+ LEATHER:
+ basePrice: 2.0
+ minPrice: 1.0
maxPrice: 40.0
buyable: true
sellable: true
- BEETROOT:
- basePrice: 12.0
- minPrice: 6.0
- maxPrice: 60.0
+ RABBIT_HIDE:
+ basePrice: 25.0
+ minPrice: 15.0
+ maxPrice: 40.0
buyable: true
sellable: true
- APPLE:
- basePrice: 15.0
- minPrice: 8.0
- maxPrice: 75.0
+ TURTLE_EGG:
+ basePrice: 150.0
+ minPrice: 105.0
+ maxPrice: 180.0
buyable: true
sellable: true
- BREAD:
+ SCUTE:
basePrice: 25.0
- minPrice: 12.0
- maxPrice: 125.0
+ minPrice: 15.0
+ maxPrice: 40.0
buyable: true
- sellable: false # Nur kaufbar, nicht verkaufbar
- COOKED_BEEF:
+ sellable: true
+ PUFFERFISH:
+ basePrice: 30.0
+ minPrice: 18.0
+ maxPrice: 45.0
+ buyable: true
+ sellable: true
+ INK_SAC:
basePrice: 20.0
minPrice: 10.0
- maxPrice: 100.0
+ maxPrice: 30.0
buyable: true
sellable: true
- COOKED_PORKCHOP:
- basePrice: 18.0
- minPrice: 9.0
- maxPrice: 90.0
+ GLOW_INK_SAC:
+ basePrice: 22.0
+ minPrice: 12.0
+ maxPrice: 35.0
buyable: true
sellable: true
- COOKED_CHICKEN:
- basePrice: 16.0
- minPrice: 8.0
- maxPrice: 80.0
+ BONE:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 18.0
+ buyable: true
+ sellable: false
+ ARROW:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: false
+ BONE_MEAL:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 18.0
+ buyable: true
+ sellable: false
+ BONE_BLOCK:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 35.0
+ buyable: true
+ sellable: false
+ STRING:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 28.0
+ buyable: true
+ sellable: false
+ SPIDER_EYE:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 32.0
+ buyable: true
+ sellable: false
+ SLIME_BALL:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 50.0
+ buyable: true
+ sellable: false
+ SLIME_BLOCK:
+ basePrice: 32.0
+ minPrice: 22.0
+ maxPrice: 52.0
buyable: true
sellable: true
- DIAMOND:
- basePrice: 500.0
- minPrice: 250.0
- maxPrice: 2500.0
+ GUNPOWDER:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 35.0
buyable: true
- sellable: false # Nur kaufbar (zu wertvoll für Verkauf)
- priceFactor: 0.02 # Niedriger Faktor - weniger volatil
- referenceAmount: 100 # Kleine Referenzmenge - reagiert schneller
- IRON_INGOT:
- basePrice: 50.0
- minPrice: 25.0
- maxPrice: 250.0
+ sellable: false
+ PHANTOM_MEMBRANE:
+ basePrice: 80.0
+ minPrice: 50.0
+ maxPrice: 120.0
buyable: true
sellable: true
- priceFactor: 0.08 # Höherer Faktor - volatiler
- GOLD_INGOT:
- basePrice: 100.0
+ ROTTEN_FLESH:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 15.0
+ buyable: true
+ sellable: false
+ BLAZE_ROD:
+ basePrice: 80.0
minPrice: 50.0
- maxPrice: 500.0
+ maxPrice: 120.0
buyable: true
sellable: true
- priceFactor: 0.06 # Mittel-volatil
- EMERALD:
+ BLAZE_POWDER:
+ basePrice: 40.0
+ minPrice: 25.0
+ maxPrice: 65.0
+ buyable: true
+ sellable: true
+ MAGMA_CREAM:
+ basePrice: 40.0
+ minPrice: 25.0
+ maxPrice: 65.0
+ buyable: true
+ sellable: true
+ GHAST_TEAR:
basePrice: 200.0
- minPrice: 100.0
- maxPrice: 1000.0
+ minPrice: 160.0
+ maxPrice: 350.0
buyable: true
- sellable: false # Nur kaufbar
- priceFactor: 0.03 # Sehr stabil
- referenceAmount: 50 # Kleine Menge - exklusiv
- COAL:
- basePrice: 5.0
- minPrice: 2.0
+ sellable: true
+ ENDER_PEARL:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 150.0
+ buyable: true
+ sellable: false
+ ENDER_EYE:
+ basePrice: 160.0
+ minPrice: 80.0
+ maxPrice: 280.0
+ buyable: true
+ sellable: false
+ SHULKER_SHELL:
+ basePrice: 330.0
+ minPrice: 190.0
+ maxPrice: 2000.0
+ buyable: true
+ sellable: true
+ DRAGON_BREATH:
+ basePrice: 200.0
+ minPrice: 150.0
+ maxPrice: 300.0
+ buyable: true
+ sellable: false
+
+ # Pflanzen
+ ALLIUM:
+ basePrice: 12.0
+ minPrice: 6.0
+ maxPrice: 25.0
+ buyable: true
+ sellable: true
+ AZURE_BLUET:
+ basePrice: 12.0
+ minPrice: 6.0
+ maxPrice: 25.0
+ buyable: true
+ sellable: true
+ BLUE_ORCHID:
+ basePrice: 12.0
+ minPrice: 6.0
maxPrice: 25.0
- buyable: false # Nur verkaufbar (Rohstoff)
+ buyable: true
+ sellable: true
+ CORNFLOWER:
+ basePrice: 12.0
+ minPrice: 6.0
+ maxPrice: 25.0
+ buyable: true
+ sellable: true
+ DANDELION:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: true
+ LILAC:
+ basePrice: 14.0
+ minPrice: 7.0
+ maxPrice: 28.0
+ buyable: true
+ sellable: true
+ LILY_OF_THE_VALLEY:
+ basePrice: 14.0
+ minPrice: 7.0
+ maxPrice: 28.0
+ buyable: true
+ sellable: true
+ PEONY:
+ basePrice: 14.0
+ minPrice: 7.0
+ maxPrice: 28.0
+ buyable: true
+ sellable: true
+ POPPY:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: true
+ ROSE_BUSH:
+ basePrice: 14.0
+ minPrice: 7.0
+ maxPrice: 28.0
+ buyable: true
+ sellable: true
+ SUNFLOWER:
+ basePrice: 14.0
+ minPrice: 7.0
+ maxPrice: 28.0
+ buyable: true
+ sellable: true
+ RED_TULIP:
+ basePrice: 11.0
+ minPrice: 6.0
+ maxPrice: 22.0
+ buyable: true
+ sellable: true
+ ORANGE_TULIP:
+ basePrice: 11.0
+ minPrice: 6.0
+ maxPrice: 22.0
+ buyable: true
+ sellable: true
+ WHITE_TULIP:
+ basePrice: 11.0
+ minPrice: 6.0
+ maxPrice: 22.0
+ buyable: true
+ sellable: true
+ PINK_TULIP:
+ basePrice: 11.0
+ minPrice: 6.0
+ maxPrice: 22.0
+ buyable: true
+ sellable: true
+ OXEYE_DAISY:
+ basePrice: 12.0
+ minPrice: 6.0
+ maxPrice: 25.0
+ buyable: true
+ sellable: true
+ DEAD_BUSH:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 18.0
+ buyable: true
+ sellable: true
+ CACTUS:
+ basePrice: 12.0
+ minPrice: 6.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: true
+ FERN:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: true
+ LARGE_FERN:
+ basePrice: 12.0
+ minPrice: 6.0
+ maxPrice: 24.0
+ buyable: true
+ sellable: true
+ SHORT_GRASS:
+ basePrice: 8.0
+ minPrice: 4.0
+ maxPrice: 15.0
+ buyable: true
+ sellable: true
+ TALL_GRASS:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 18.0
+ buyable: true
+ sellable: true
+ LILY_PAD:
+ basePrice: 10.0
+ minPrice: 3.0
+ maxPrice: 23.0
+ buyable: true
+ sellable: true
+
+ # Spezielle Pflanzen und Deko
+ AZALEA:
+ basePrice: 16.0
+ minPrice: 8.0
+ maxPrice: 28.0
+ buyable: true
+ sellable: true
+ FLOWERING_AZALEA:
+ basePrice: 18.0
+ minPrice: 9.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: true
+ HANGING_ROOTS:
+ basePrice: 14.0
+ minPrice: 7.0
+ maxPrice: 24.0
+ buyable: true
+ sellable: true
+ MOSS_BLOCK:
+ basePrice: 16.0
+ minPrice: 8.0
+ maxPrice: 28.0
+ buyable: true
+ sellable: false
+ MOSS_CARPET:
+ basePrice: 12.0
+ minPrice: 6.0
+ maxPrice: 22.0
+ buyable: true
+ sellable: false
+ CHORUS_FLOWER:
+ basePrice: 20.0
+ minPrice: 10.0
+ maxPrice: 35.0
+ buyable: true
+ sellable: true
+ CHORUS_PLANT:
+ basePrice: 18.0
+ minPrice: 9.0
+ maxPrice: 32.0
+ buyable: true
+ sellable: true
+ BIG_DRIPLEAF:
+ basePrice: 18.0
+ minPrice: 9.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: true
+ SMALL_DRIPLEAF:
+ basePrice: 15.0
+ minPrice: 7.0
+ maxPrice: 26.0
+ buyable: true
+ sellable: true
+ BROWN_MUSHROOM:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 18.0
+ buyable: true
+ sellable: true
+ BROWN_MUSHROOM_BLOCK:
+ basePrice: 18.0
+ minPrice: 10.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: true
+ RED_MUSHROOM:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 18.0
+ buyable: true
+ sellable: true
+ RED_MUSHROOM_BLOCK:
+ basePrice: 18.0
+ minPrice: 10.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: true
+ MUSHROOM_STEM:
+ basePrice: 16.0
+ minPrice: 8.0
+ maxPrice: 26.0
+ buyable: true
+ sellable: true
+ NETHER_SPROUTS:
+ basePrice: 15.0
+ minPrice: 8.0
+ maxPrice: 26.0
+ buyable: true
+ sellable: true
+ TWISTING_VINES:
+ basePrice: 18.0
+ minPrice: 9.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: true
+ WEEPING_VINES:
+ basePrice: 18.0
+ minPrice: 9.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: true
+ VINE:
+ basePrice: 14.0
+ minPrice: 7.0
+ maxPrice: 24.0
+ buyable: true
+ sellable: false
+ SHROOMLIGHT:
+ basePrice: 22.0
+ minPrice: 12.0
+ maxPrice: 35.0
+ buyable: true
+ sellable: true
+ GLOW_BERRIES:
+ basePrice: 20.0
+ minPrice: 10.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: true
+ GLOW_LICHEN:
+ basePrice: 18.0
+ minPrice: 9.0
+ maxPrice: 28.0
+ buyable: true
+ sellable: true
+ SPORE_BLOSSOM:
+ basePrice: 28.0
+ minPrice: 15.0
+ maxPrice: 45.0
+ buyable: true
+ sellable: true
+ SWEET_BERRY_BUSH:
+ basePrice: 16.0
+ minPrice: 8.0
+ maxPrice: 28.0
+ buyable: true
+ sellable: true
+
+ # Blöcke und Baumaterialien
+ ANDESITE:
+ basePrice: 8.0
+ minPrice: 4.0
+ maxPrice: 14.0
+ buyable: true
+ sellable: true
+ DIORITE:
+ basePrice: 8.0
+ minPrice: 4.0
+ maxPrice: 14.0
+ buyable: true
+ sellable: true
+ GRANITE:
+ basePrice: 8.0
+ minPrice: 4.0
+ maxPrice: 14.0
+ buyable: true
+ sellable: true
+ TUFF:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 18.0
+ buyable: true
+ sellable: false
+ CALCITE:
+ basePrice: 12.0
+ minPrice: 6.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: true
+ BLACKSTONE:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 18.0
+ buyable: true
+ sellable: false
+ POLISHED_BLACKSTONE:
+ basePrice: 12.0
+ minPrice: 6.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: true
+ POLISHED_BLACKSTONE_BRICKS:
+ basePrice: 14.0
+ minPrice: 7.0
+ maxPrice: 24.0
+ buyable: true
+ sellable: true
+ DRIPSTONE_BLOCK:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 18.0
+ buyable: true
+ sellable: true
+ POINTED_DRIPSTONE:
+ basePrice: 12.0
+ minPrice: 6.0
+ maxPrice: 22.0
+ buyable: true
+ sellable: true
+ MAGMA_BLOCK:
+ basePrice: 14.0
+ minPrice: 7.0
+ maxPrice: 24.0
+ buyable: true
+ sellable: true
+ SAND:
+ basePrice: 6.0
+ minPrice: 3.0
+ maxPrice: 10.0
+ buyable: true
+ sellable: false
+ RED_SAND:
+ basePrice: 7.0
+ minPrice: 4.0
+ maxPrice: 12.0
+ buyable: true
+ sellable: false
+ SANDSTONE:
+ basePrice: 4.0
+ minPrice: 2.0
+ maxPrice: 16.0
+ buyable: true
+ sellable: true
+ RED_SANDSTONE:
+ basePrice: 4.0
+ minPrice: 2.0
+ maxPrice: 18.0
+ buyable: true
+ sellable: true
+ TERRACOTTA:
+ basePrice: 4.0
+ minPrice: 2.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: true
+ CLAY:
+ basePrice: 4.0
+ minPrice: 2.0
+ maxPrice: 18.0
+ buyable: true
+ sellable: true
+ BRICK:
+ basePrice: 16.0
+ minPrice: 8.0
+ maxPrice: 28.0
+ buyable: true
+ sellable: true
+ GRAVEL:
+ basePrice: 4.0
+ minPrice: 2.0
+ maxPrice: 10.0
+ buyable: true
+ sellable: true
+ DIRT:
+ basePrice: 4.0
+ minPrice: 2.0
+ maxPrice: 8.0
+ buyable: true
+ sellable: false
+ COARSE_DIRT:
+ basePrice: 5.0
+ minPrice: 3.0
+ maxPrice: 9.0
+ buyable: true
+ sellable: true
+ GRASS_BLOCK:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 5.0
+ buyable: true
+ sellable: true
+ SOUL_SAND:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 18.0
+ buyable: true
+ sellable: true
+ SOUL_SOIL:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 18.0
+ buyable: true
+ sellable: true
+ BLUE_ICE:
+ basePrice: 10.0
+ minPrice: 2.0
+ maxPrice: 45.0
+ buyable: true
+ sellable: true
+ ICE:
+ basePrice: 10.0
+ minPrice: 2.0
+ maxPrice: 22.0
+ buyable: true
+ sellable: true
+ PACKED_ICE:
+ basePrice: 12.0
+ minPrice: 6.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: true
+ SNOW_BLOCK:
+ basePrice: 10.0
+ minPrice: 2.0
+ maxPrice: 18.0
+ buyable: true
+ sellable: true
+
+ # Seltene Blöcke
+ NETHER_BRICKS:
+ basePrice: 14.0
+ minPrice: 7.0
+ maxPrice: 25.0
+ buyable: true
+ sellable: true
+ QUARTZ_BLOCK:
+ basePrice: 20.0
+ minPrice: 12.0
+ maxPrice: 35.0
+ buyable: true
+ sellable: true
+ GLOWSTONE:
+ basePrice: 18.0
+ minPrice: 9.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: true
+ OBSIDIAN:
+ basePrice: 25.0
+ minPrice: 15.0
+ maxPrice: 40.0
+ buyable: true
+ sellable: true
+ CRYING_OBSIDIAN:
+ basePrice: 35.0
+ minPrice: 20.0
+ maxPrice: 50.0
+ buyable: true
+ sellable: true
+ REDSTONE_BLOCK:
+ basePrice: 6.0
+ minPrice: 4.0
+ maxPrice: 15.0
+ buyable: true
+ sellable: true
+ CHAIN:
+ basePrice: 10.0
+ minPrice: 6.0
+ maxPrice: 18.0
+ buyable: true
+ sellable: false
+ IRON_BARS:
+ basePrice: 8.0
+ minPrice: 4.0
+ maxPrice: 14.0
+ buyable: true
+ sellable: false
+ LANTERN:
+ basePrice: 10.0
+ minPrice: 2.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: true
+ SOUL_LANTERN:
+ basePrice: 16.0
+ minPrice: 9.0
+ maxPrice: 26.0
+ buyable: true
+ sellable: true
+ TORCH:
+ basePrice: 4.0
+ minPrice: 2.0
+ maxPrice: 6.0
+ buyable: true
+ sellable: true
+ SOUL_TORCH:
+ basePrice: 6.0
+ minPrice: 3.0
+ maxPrice: 10.0
+ buyable: true
+ sellable: true
+ TNT:
+ basePrice: 130.0
+ minPrice: 120.0
+ maxPrice: 180.0
+ buyable: false
+ sellable: true
+ SCAFFOLDING:
+ basePrice: 8.0
+ minPrice: 4.0
+ maxPrice: 14.0
+ buyable: true
+ sellable: true
+ LEAD:
+ basePrice: 10.0
+ minPrice: 2.0
+ maxPrice: 28.0
+ buyable: true
+ sellable: true
+ NAME_TAG:
+ basePrice: 40.0
+ minPrice: 25.0
+ maxPrice: 70.0
+ buyable: true
+ sellable: true
+
+ # Funktionale Blöcke
+ ENDER_CHEST:
+ basePrice: 40.0
+ minPrice: 25.0
+ maxPrice: 70.0
+ buyable: true
+ sellable: false
+ BARREL:
+ basePrice: 40.0
+ minPrice: 25.0
+ maxPrice: 70.0
+ buyable: true
+ sellable: false
+ TRAPPED_CHEST:
+ basePrice: 40.0
+ minPrice: 25.0
+ maxPrice: 70.0
+ buyable: true
+ sellable: false
+ BLAST_FURNACE:
+ basePrice: 40.0
+ minPrice: 25.0
+ maxPrice: 70.0
+ buyable: true
+ sellable: false
+ SMOKER:
+ basePrice: 40.0
+ minPrice: 25.0
+ maxPrice: 70.0
+ buyable: true
+ sellable: false
+ CAMPFIRE:
+ basePrice: 40.0
+ minPrice: 25.0
+ maxPrice: 70.0
+ buyable: true
+ sellable: false
+ COMPOSTER:
+ basePrice: 40.0
+ minPrice: 25.0
+ maxPrice: 70.0
+ buyable: true
+ sellable: false
+ GRINDSTONE:
+ basePrice: 40.0
+ minPrice: 25.0
+ maxPrice: 70.0
+ buyable: true
+ sellable: false
+ CARTOGRAPHY_TABLE:
+ basePrice: 40.0
+ minPrice: 25.0
+ maxPrice: 70.0
+ buyable: true
+ sellable: false
+ SMITHING_TABLE:
+ basePrice: 40.0
+ minPrice: 25.0
+ maxPrice: 70.0
+ buyable: true
+ sellable: false
+ FLETCHING_TABLE:
+ basePrice: 40.0
+ minPrice: 25.0
+ maxPrice: 70.0
+ buyable: true
+ sellable: false
+ LOOM:
+ basePrice: 40.0
+ minPrice: 25.0
+ maxPrice: 70.0
+ buyable: true
+ sellable: false
+ LECTERN:
+ basePrice: 40.0
+ minPrice: 25.0
+ maxPrice: 70.0
+ buyable: true
+ sellable: false
+ ANVIL:
+ basePrice: 40.0
+ minPrice: 25.0
+ maxPrice: 70.0
+ buyable: true
+ sellable: false
+
+ # Erze
+ COAL_ORE:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: true
+ IRON_ORE:
+ basePrice: 12.0
+ minPrice: 8.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: true
+ COPPER_ORE:
+ basePrice: 18.0
+ minPrice: 10.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: true
+ GOLD_ORE:
+ basePrice: 30.0
+ minPrice: 18.0
+ maxPrice: 50.0
+ buyable: true
+ sellable: true
+ DIAMOND_ORE:
+ basePrice: 190.0
+ minPrice: 100.0
+ maxPrice: 400.0
+ buyable: true
+ sellable: true
+ EMERALD_ORE:
+ basePrice: 110.0
+ minPrice: 85.0
+ maxPrice: 150.0
+ buyable: true
+ sellable: true
+ NETHER_QUARTZ_ORE:
+ basePrice: 25.0
+ minPrice: 15.0
+ maxPrice: 40.0
+ buyable: true
+ sellable: true
+ NETHER_GOLD_ORE:
+ basePrice: 28.0
+ minPrice: 17.0
+ maxPrice: 45.0
+ buyable: true
+ sellable: true
+ LAPIS_ORE:
+ basePrice: 22.0
+ minPrice: 13.0
+ maxPrice: 35.0
+ buyable: true
+ sellable: true
+ ANCIENT_DEBRIS:
+ basePrice: 650.0
+ minPrice: 520.0
+ maxPrice: 800.0
+ buyable: true
+ sellable: true
+ REDSTONE_ORE:
+ basePrice: 18.0
+ minPrice: 10.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: true
+
+ # Ingots und raffinierte Materialien
+ COAL:
+ basePrice: 12.0
+ minPrice: 7.0
+ maxPrice: 25.0
+ buyable: true
+ sellable: true
+ IRON_INGOT:
+ basePrice: 1.0
+ minPrice: 1.0
+ maxPrice: 30.0
+ buyable: true
+ sellable: false
+ COPPER_INGOT:
+ basePrice: 35.0
+ minPrice: 20.0
+ maxPrice: 55.0
+ buyable: true
+ sellable: true
+ GOLD_INGOT:
+ basePrice: 50.0
+ minPrice: 30.0
+ maxPrice: 75.0
+ buyable: true
+ sellable: true
+ DIAMOND:
+ basePrice: 280.0
+ minPrice: 130.0
+ maxPrice: 600.0
+ buyable: true
+ sellable: true
+ EMERALD:
+ basePrice: 2.0
+ minPrice: 1.0
+ maxPrice: 20.0
+ buyable: true
+ sellable: true
+ QUARTZ:
+ basePrice: 30.0
+ minPrice: 18.0
+ maxPrice: 45.0
+ buyable: true
+ sellable: true
+ LAPIS_LAZULI:
+ basePrice: 28.0
+ minPrice: 15.0
+ maxPrice: 40.0
+ buyable: true
+ sellable: true
+ REDSTONE:
+ basePrice: 20.0
+ minPrice: 12.0
+ maxPrice: 35.0
+ buyable: true
+ sellable: false
+
+ # Spawn Eggs
+ ALLAY_SPAWN_EGG:
+ basePrice: 1200.0
+ minPrice: 950.0
+ maxPrice: 1500.0
+ buyable: true
+ sellable: true
+ # ARMADILLO_SPAWN_EGG not available in this version
+ # ARMADILLO_SPAWN_EGG:
+ # basePrice: 1100.0
+ # minPrice: 900.0
+ # maxPrice: 1400.0
+ # buyable: true
+ # sellable: true
+ ENDERMITE_SPAWN_EGG:
+ basePrice: 850.0
+ minPrice: 700.0
+ maxPrice: 1000.0
+ buyable: true
+ sellable: true
+ FROG_SPAWN_EGG:
+ basePrice: 200.0
+ minPrice: 100.0
+ maxPrice: 450.0
+ buyable: true
+ sellable: true
+ GOAT_SPAWN_EGG:
+ basePrice: 350.0
+ minPrice: 250.0
+ maxPrice: 700.0
+ buyable: true
+ sellable: true
+ ZOMBIE_SPAWN_EGG:
+ basePrice: 300.0
+ minPrice: 200.0
+ maxPrice: 400.0
+ buyable: true
+ sellable: true
+ SHULKER_SPAWN_EGG:
+ basePrice: 2500.0
+ minPrice: 2000.0
+ maxPrice: 3000.0
+ buyable: true
+ sellable: true
+ STRIDER_SPAWN_EGG:
+ basePrice: 500.0
+ minPrice: 400.0
+ maxPrice: 650.0
+ buyable: true
+ sellable: true
+ VILLAGER_SPAWN_EGG:
+ basePrice: 180.0
+ minPrice: 150.0
+ maxPrice: 220.0
+ buyable: true
+ sellable: true
+
+ # Traditionelle Lebensmittel (aus dem Original behalten)
+ WHEAT:
+ basePrice: 10.0
+ minPrice: 5.0
+ maxPrice: 50.0
+ buyable: true
+ sellable: true
+ CARROT:
+ basePrice: 8.0
+ minPrice: 4.0
+ maxPrice: 40.0
+ buyable: true
+ sellable: true
+ POTATO:
+ basePrice: 8.0
+ minPrice: 4.0
+ maxPrice: 40.0
+ buyable: true
+ sellable: true
+ BEETROOT:
+ basePrice: 12.0
+ minPrice: 6.0
+ maxPrice: 60.0
+ buyable: true
+ sellable: true
+ APPLE:
+ basePrice: 15.0
+ minPrice: 8.0
+ maxPrice: 75.0
+ buyable: true
+ sellable: true
+ BREAD:
+ basePrice: 25.0
+ minPrice: 12.0
+ maxPrice: 125.0
+ buyable: true
+ sellable: false
+ COOKED_BEEF:
+ basePrice: 20.0
+ minPrice: 10.0
+ maxPrice: 100.0
+ buyable: true
+ sellable: true
+ COOKED_PORKCHOP:
+ basePrice: 18.0
+ minPrice: 9.0
+ maxPrice: 90.0
+ buyable: true
+ sellable: true
+ COOKED_CHICKEN:
+ basePrice: 16.0
+ minPrice: 8.0
+ maxPrice: 80.0
+ buyable: true
sellable: true
- priceFactor: 0.1 # Sehr volatil
- referenceAmount: 2000 # Große Menge - Massengut
# Villager-Handelseinstellungen
trading:
@@ -175,3 +1373,10 @@ messages:
villagerSpawned: "§aShop-Villager erfolgreich gespawnt!"
lookingAtNoBlock: "§cDu musst auf einen Block schauen!"
villagerSpawnFailed: "§cFehler beim Spawnen des Villagers!"
+ atmSpawned: "§aATM-Villager erfolgreich gespawnt!"
+ atmSpawnFailed: "§cFehler beim Spawnen des ATM-Villagers!"
+ spawnCostCharged: "§7Kosten abgezogen: §c-{amount} {currency}"
+ spawnCostRefunded: "§aKosten zurückerstattet: §e+{amount} {currency}"
+ insufficientFundsForSpawn: "§cNicht genügend Guthaben! Benötigt: §e{amount} {currency}"
+ deathPenaltyCash: "§c💀 Du hast beim Tod §e{amount} {currency} §cBargeld verloren!"
+ deathPenaltyExempt: "§a🛡️ Du bist vor Geldverlust beim Tod geschützt!"
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index ea4a301..84bf16e 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -1,20 +1,16 @@
name: SimpleEco
version: 1.0.0
main: de.simpleeco.SimpleEcoPlugin
-api-version: 1.20
+api-version: "1.20"
description: Dynamisches Wirtschaftssystem Plugin mit Villager-Trading
-author: SimpleEco Team
+author: Nichtmetall
website: https://github.com/simpleeco
commands:
eco:
- description: Hauptkommando für das Wirtschaftssystem
- usage: /eco [args...]
+ description: Hauptkommando für das SimpleEco Plugin
+ usage: /eco [args...]
permission: simpleeco.use
- spawn:
- description: Spawnt spezielle Entities für das Plugin
- usage: /spawn
- permission: simpleeco.spawn
permissions:
simpleeco.use:
@@ -25,10 +21,22 @@ permissions:
default: op
simpleeco.balance.other:
description: Erlaubt das Einsehen fremder Kontostände
+ default: true
+ simpleeco.balance.admin:
+ description: Erlaubt das Hinzufügen und Entfernen von Geld bei Spielern
default: op
simpleeco.spawn:
description: Erlaubt das Spawnen von Shop-Entities
- default: op
- simpleeco.spawn.villager:
+ default: true
+ simpleeco.spawn.shop:
description: Erlaubt das Spawnen von Shop-Villagern
+ default: true
+ simpleeco.spawn.atm:
+ description: Erlaubt das Spawnen von ATM-Villagern
+ default: true
+ simpleeco.spawn.free:
+ description: Erlaubt kostenloses Spawnen (ohne Kosten)
+ default: op
+ simpleeco.death.exempt:
+ description: Befreit von Geldverlust beim Tod
default: op
diff --git a/target/classes/config.yml b/target/classes/config.yml
deleted file mode 100644
index 0c9deb9..0000000
--- a/target/classes/config.yml
+++ /dev/null
@@ -1,177 +0,0 @@
-# SimpleEco Plugin Konfiguration
-
-# Währungseinstellungen
-currency:
- name: "Gold" # Name der Währung
- startBalance: 1000.0 # Startguthaben für neue Spieler
- symbol: "G" # Symbol der Währung
-
-# Datenbankeinstellungen
-database:
- path: "plugins/SimpleEco/economy.db" # Pfad zur SQLite-Datenbankdatei
-
-# Shop-Villager Einstellungen
-shopVillager:
- name: "Shop" # Name der Shop-Villager
- profession: "TOOLSMITH" # Beruf des Villagers (TOOLSMITH, LIBRARIAN, etc.)
- villagerType: "PLAINS" # Typ des Villagers (PLAINS, DESERT, JUNGLE, etc.)
-
-# ATM-Villager Einstellungen
-atmVillager:
- name: "Bank-Automat" # Name der ATM-Villager
- profession: "LIBRARIAN" # Beruf des ATM-Villagers (LIBRARIAN für Banker-Look)
- villagerType: "PLAINS" # Typ des Villagers (PLAINS, DESERT, JUNGLE, etc.)
-
-# Scoreboard Einstellungen
-scoreboard:
- enabled: true # Scoreboard aktivieren/deaktivieren
- title: "§6§lSimpleEco" # Titel des Scoreboards
- updateInterval: 20 # Update-Intervall in Ticks (20 = 1 Sekunde)
- lines:
- - "§7§m "
- - "§e§lKontostand:"
- - "§a{balance} {currency}"
- - ""
- - "§7§m "
-
-# Preiseinstellungen
-pricing:
- priceFactor: 0.05 # Elastizitätsfaktor (5% = 0.05) - Globaler Standard
- referenceAmount: 1000 # Referenzmenge für Preisberechnung - Globaler Standard
- regressionTimeMinutes: 60 # Zeit in Minuten bis Preise sich zum Default zurückbewegen
- regressionUpdateInterval: 5 # Intervall in Minuten für Preis-Updates
-
- # Standard-Items mit Preiseinstellungen
- #
- # Jedes Item kann folgende Parameter haben:
- # - basePrice: Basispreis des Items
- # - minPrice: Minimaler Preis (Untergrenze)
- # - maxPrice: Maximaler Preis (Obergrenze)
- # - buyable: true/false - Kann das Item gekauft werden?
- # - sellable: true/false - Kann das Item verkauft werden?
- # - priceFactor: Optional - Überschreibt globalen priceFactor (Volatilität)
- # - referenceAmount: Optional - Überschreibt globale referenceAmount (Reaktionsgeschwindigkeit)
- #
- # Höherer priceFactor = volatiler (stärkere Preisschwankungen)
- # Niedrigere referenceAmount = reaktionsschneller (weniger Handel nötig für Preisänderung)
- items:
- WHEAT:
- basePrice: 10.0
- minPrice: 5.0
- maxPrice: 50.0
- buyable: true
- sellable: true
- # referenceAmount: 1000 # Optional: Überschreibt globalen Wert
- # priceFactor: 0.05 # Optional: Überschreibt globalen Wert
- CARROT:
- basePrice: 8.0
- minPrice: 4.0
- maxPrice: 40.0
- buyable: true
- sellable: true
- POTATO:
- basePrice: 8.0
- minPrice: 4.0
- maxPrice: 40.0
- buyable: true
- sellable: true
- BEETROOT:
- basePrice: 12.0
- minPrice: 6.0
- maxPrice: 60.0
- buyable: true
- sellable: true
- APPLE:
- basePrice: 15.0
- minPrice: 8.0
- maxPrice: 75.0
- buyable: true
- sellable: true
- BREAD:
- basePrice: 25.0
- minPrice: 12.0
- maxPrice: 125.0
- buyable: true
- sellable: false # Nur kaufbar, nicht verkaufbar
- COOKED_BEEF:
- basePrice: 20.0
- minPrice: 10.0
- maxPrice: 100.0
- buyable: true
- sellable: true
- COOKED_PORKCHOP:
- basePrice: 18.0
- minPrice: 9.0
- maxPrice: 90.0
- buyable: true
- sellable: true
- COOKED_CHICKEN:
- basePrice: 16.0
- minPrice: 8.0
- maxPrice: 80.0
- buyable: true
- sellable: true
- DIAMOND:
- basePrice: 500.0
- minPrice: 250.0
- maxPrice: 2500.0
- buyable: true
- sellable: false # Nur kaufbar (zu wertvoll für Verkauf)
- priceFactor: 0.02 # Niedriger Faktor - weniger volatil
- referenceAmount: 100 # Kleine Referenzmenge - reagiert schneller
- IRON_INGOT:
- basePrice: 50.0
- minPrice: 25.0
- maxPrice: 250.0
- buyable: true
- sellable: true
- priceFactor: 0.08 # Höherer Faktor - volatiler
- GOLD_INGOT:
- basePrice: 100.0
- minPrice: 50.0
- maxPrice: 500.0
- buyable: true
- sellable: true
- priceFactor: 0.06 # Mittel-volatil
- EMERALD:
- basePrice: 200.0
- minPrice: 100.0
- maxPrice: 1000.0
- buyable: true
- sellable: false # Nur kaufbar
- priceFactor: 0.03 # Sehr stabil
- referenceAmount: 50 # Kleine Menge - exklusiv
- COAL:
- basePrice: 5.0
- minPrice: 2.0
- maxPrice: 25.0
- buyable: false # Nur verkaufbar (Rohstoff)
- sellable: true
- priceFactor: 0.1 # Sehr volatil
- referenceAmount: 2000 # Große Menge - Massengut
-
-# Villager-Handelseinstellungen
-trading:
- menuTitle: "§6§lWirtschaftshandel"
- buyButtonName: "§a§lKaufen"
- sellButtonName: "§c§lVerkaufen"
- infoButtonName: "§e§lInformation"
-
-# Nachrichten
-messages:
- prefix: "§8[§6SimpleEco§8] §7"
- noPermission: "§cDu hast keine Berechtigung für diesen Befehl!"
- playerNotFound: "§cSpieler nicht gefunden!"
- invalidAmount: "§cUngültiger Betrag!"
- insufficientFunds: "§cNicht genügend Guthaben!"
- paymentSent: "§aDu hast §e{amount} {currency} §aan §e{player} §aüberwiesen!"
- paymentReceived: "§aDu hast §e{amount} {currency} §avon §e{player} §aerhalten!"
- balanceYour: "§aDein Kontostand: §e{balance} {currency}"
- balanceOther: "§aKontostand von §e{player}§a: §e{balance} {currency}"
- tradeSuccess: "§aHandel erfolgreich abgeschlossen!"
- tradeFailed: "§cHandel fehlgeschlagen!"
- insufficientItems: "§cNicht genügend Items im Inventar!"
- inventoryFull: "§cDein Inventar ist voll!"
- villagerSpawned: "§aShop-Villager erfolgreich gespawnt!"
- lookingAtNoBlock: "§cDu musst auf einen Block schauen!"
- villagerSpawnFailed: "§cFehler beim Spawnen des Villagers!"
diff --git a/target/classes/de/simpleeco/SimpleEcoPlugin.class b/target/classes/de/simpleeco/SimpleEcoPlugin.class
deleted file mode 100644
index 8a591b7..0000000
Binary files a/target/classes/de/simpleeco/SimpleEcoPlugin.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/bank/AtmTrader$AtmSession$MenuType.class b/target/classes/de/simpleeco/bank/AtmTrader$AtmSession$MenuType.class
deleted file mode 100644
index f2c9d45..0000000
Binary files a/target/classes/de/simpleeco/bank/AtmTrader$AtmSession$MenuType.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/bank/AtmTrader$AtmSession.class b/target/classes/de/simpleeco/bank/AtmTrader$AtmSession.class
deleted file mode 100644
index e73102b..0000000
Binary files a/target/classes/de/simpleeco/bank/AtmTrader$AtmSession.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/bank/AtmTrader.class b/target/classes/de/simpleeco/bank/AtmTrader.class
deleted file mode 100644
index 317149a..0000000
Binary files a/target/classes/de/simpleeco/bank/AtmTrader.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/bank/AtmVillagerManager.class b/target/classes/de/simpleeco/bank/AtmVillagerManager.class
deleted file mode 100644
index 211a5a6..0000000
Binary files a/target/classes/de/simpleeco/bank/AtmVillagerManager.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/bank/BankManager.class b/target/classes/de/simpleeco/bank/BankManager.class
deleted file mode 100644
index 9a51e38..0000000
Binary files a/target/classes/de/simpleeco/bank/BankManager.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/commands/EcoCommand.class b/target/classes/de/simpleeco/commands/EcoCommand.class
deleted file mode 100644
index 5a08685..0000000
Binary files a/target/classes/de/simpleeco/commands/EcoCommand.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/commands/SpawnCommand.class b/target/classes/de/simpleeco/commands/SpawnCommand.class
deleted file mode 100644
index df4226c..0000000
Binary files a/target/classes/de/simpleeco/commands/SpawnCommand.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/config/ConfigManager$ItemPriceConfig.class b/target/classes/de/simpleeco/config/ConfigManager$ItemPriceConfig.class
deleted file mode 100644
index 2437d3e..0000000
Binary files a/target/classes/de/simpleeco/config/ConfigManager$ItemPriceConfig.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/config/ConfigManager.class b/target/classes/de/simpleeco/config/ConfigManager.class
deleted file mode 100644
index a82f02f..0000000
Binary files a/target/classes/de/simpleeco/config/ConfigManager.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/currency/BasicCurrency.class b/target/classes/de/simpleeco/currency/BasicCurrency.class
deleted file mode 100644
index 57a248e..0000000
Binary files a/target/classes/de/simpleeco/currency/BasicCurrency.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/database/DatabaseManager$ItemStats.class b/target/classes/de/simpleeco/database/DatabaseManager$ItemStats.class
deleted file mode 100644
index 80d02fd..0000000
Binary files a/target/classes/de/simpleeco/database/DatabaseManager$ItemStats.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/database/DatabaseManager.class b/target/classes/de/simpleeco/database/DatabaseManager.class
deleted file mode 100644
index 91585d6..0000000
Binary files a/target/classes/de/simpleeco/database/DatabaseManager.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/listeners/PlayerJoinListener.class b/target/classes/de/simpleeco/listeners/PlayerJoinListener.class
deleted file mode 100644
index 4b970f5..0000000
Binary files a/target/classes/de/simpleeco/listeners/PlayerJoinListener.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/listeners/VillagerInteractListener.class b/target/classes/de/simpleeco/listeners/VillagerInteractListener.class
deleted file mode 100644
index cebae4c..0000000
Binary files a/target/classes/de/simpleeco/listeners/VillagerInteractListener.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/pricing/PriceManager$PriceInfo.class b/target/classes/de/simpleeco/pricing/PriceManager$PriceInfo.class
deleted file mode 100644
index bbb4159..0000000
Binary files a/target/classes/de/simpleeco/pricing/PriceManager$PriceInfo.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/pricing/PriceManager.class b/target/classes/de/simpleeco/pricing/PriceManager.class
deleted file mode 100644
index 435b771..0000000
Binary files a/target/classes/de/simpleeco/pricing/PriceManager.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/scoreboard/ScoreboardManager$1.class b/target/classes/de/simpleeco/scoreboard/ScoreboardManager$1.class
deleted file mode 100644
index e9e071d..0000000
Binary files a/target/classes/de/simpleeco/scoreboard/ScoreboardManager$1.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/scoreboard/ScoreboardManager.class b/target/classes/de/simpleeco/scoreboard/ScoreboardManager.class
deleted file mode 100644
index 73ea8c2..0000000
Binary files a/target/classes/de/simpleeco/scoreboard/ScoreboardManager.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/tasks/PriceRegressionTask.class b/target/classes/de/simpleeco/tasks/PriceRegressionTask.class
deleted file mode 100644
index c182dee..0000000
Binary files a/target/classes/de/simpleeco/tasks/PriceRegressionTask.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/trading/CustomVillagerTrader$1.class b/target/classes/de/simpleeco/trading/CustomVillagerTrader$1.class
deleted file mode 100644
index 3e692a0..0000000
Binary files a/target/classes/de/simpleeco/trading/CustomVillagerTrader$1.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/trading/CustomVillagerTrader$TradingSession.class b/target/classes/de/simpleeco/trading/CustomVillagerTrader$TradingSession.class
deleted file mode 100644
index 0d3f0bd..0000000
Binary files a/target/classes/de/simpleeco/trading/CustomVillagerTrader$TradingSession.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/trading/CustomVillagerTrader.class b/target/classes/de/simpleeco/trading/CustomVillagerTrader.class
deleted file mode 100644
index 7c3702f..0000000
Binary files a/target/classes/de/simpleeco/trading/CustomVillagerTrader.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/utils/EconomyUtils$1.class b/target/classes/de/simpleeco/utils/EconomyUtils$1.class
deleted file mode 100644
index 285d4cc..0000000
Binary files a/target/classes/de/simpleeco/utils/EconomyUtils$1.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/utils/EconomyUtils.class b/target/classes/de/simpleeco/utils/EconomyUtils.class
deleted file mode 100644
index adb2343..0000000
Binary files a/target/classes/de/simpleeco/utils/EconomyUtils.class and /dev/null differ
diff --git a/target/classes/de/simpleeco/villager/ShopVillagerManager.class b/target/classes/de/simpleeco/villager/ShopVillagerManager.class
deleted file mode 100644
index 47bd725..0000000
Binary files a/target/classes/de/simpleeco/villager/ShopVillagerManager.class and /dev/null differ
diff --git a/target/classes/plugin.yml b/target/classes/plugin.yml
deleted file mode 100644
index ea4a301..0000000
--- a/target/classes/plugin.yml
+++ /dev/null
@@ -1,34 +0,0 @@
-name: SimpleEco
-version: 1.0.0
-main: de.simpleeco.SimpleEcoPlugin
-api-version: 1.20
-description: Dynamisches Wirtschaftssystem Plugin mit Villager-Trading
-author: SimpleEco Team
-website: https://github.com/simpleeco
-
-commands:
- eco:
- description: Hauptkommando für das Wirtschaftssystem
- usage: /eco [args...]
- permission: simpleeco.use
- spawn:
- description: Spawnt spezielle Entities für das Plugin
- usage: /spawn
- permission: simpleeco.spawn
-
-permissions:
- simpleeco.use:
- description: Erlaubt die Nutzung der Eco-Commands
- default: true
- simpleeco.admin:
- description: Erlaubt Admin-Funktionen
- default: op
- simpleeco.balance.other:
- description: Erlaubt das Einsehen fremder Kontostände
- default: op
- simpleeco.spawn:
- description: Erlaubt das Spawnen von Shop-Entities
- default: op
- simpleeco.spawn.villager:
- description: Erlaubt das Spawnen von Shop-Villagern
- default: op