Skip to content

Commit 393805b

Browse files
committed
Added Stats Sync module -> 3.5 update
1 parent f3a57db commit 393805b

8 files changed

Lines changed: 255 additions & 3 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Uses the **NBT API** for reliable and version-independent data transfer.
4343
- Economy (via Vault)
4444
- Potion Effects
4545
- Advancements
46+
- Statistics
4647

4748
### 🧠 **Fail-Safe Data Handling**
4849
Automatic retries, optional player kick on repeated failure, and secure local backups.
@@ -76,7 +77,6 @@ Here’s what’s coming soon:
7677

7778
- 🧰 **Extended Sync Modules**
7879
Planned additions include:
79-
- 📊 **Statistics** – ensure global tracking of minecraft statistics.
8080
- 🔧 **Commands** – New admin commands for manual intervention
8181

8282
These features are already in development and will be introduced step by step —
@@ -139,6 +139,7 @@ sync:
139139
vaultEconomy: false
140140
effects: false
141141
advancements: false
142+
statistics: false
142143

143144
# Create an automatic running task which saves all online player's inventory every given time.
144145
syncTask:

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>de.lostesburger</groupId>
88
<artifactId>MySqlPlayerBridge</artifactId>
9-
<version>3.4</version>
9+
<version>3.5</version>
1010
<packaging>jar</packaging>
1111

1212
<name>MySqlPlayerBridge</name>

src/main/java/de/lostesburger/mySqlPlayerBridge/Main.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
import de.lostesburger.mySqlPlayerBridge.Managers.NbtAPI.NBTAPIManager;
1414
import de.lostesburger.mySqlPlayerBridge.Managers.Player.PlayerManager;
1515
import de.lostesburger.mySqlPlayerBridge.Managers.PlayerBridge.PlayerBridgeManager;
16+
import de.lostesburger.mySqlPlayerBridge.Managers.StatsDataManager;
1617
import de.lostesburger.mySqlPlayerBridge.Managers.Vault.VaultManager;
1718
import de.lostesburger.mySqlPlayerBridge.Serialization.Serialization.AdvancementSerializer;
1819
import de.lostesburger.mySqlPlayerBridge.Serialization.Serialization.PotionSerializer;
20+
import de.lostesburger.mySqlPlayerBridge.Serialization.Serialization.StatsSerializer;
1921
import de.lostesburger.mySqlPlayerBridge.Serialization.SerializationType;
2022
import de.lostesburger.mySqlPlayerBridge.Utils.Chat;
2123
import de.lostesburger.mySqlPlayerBridge.Utils.Checks.DatabaseConfigCheck;
@@ -47,7 +49,7 @@ public final class Main extends JavaPlugin {
4749
public static Version McVer;
4850
public static String serverType = "Unknown";
4951
private static Plugin instance;
50-
public static String version = "3.4";
52+
public static String version = "3.5";
5153
public static String pluginName = "MySqlPlayerBridge";
5254
public static String prefix;
5355

@@ -58,12 +60,14 @@ public final class Main extends JavaPlugin {
5860
public static CommandManager commandManager;
5961
public static EffectDataManager effectDataManager;
6062
public static AdvancementDataManager advancementDataManager;
63+
public static StatsDataManager statsDataManager;
6164

6265
public static MySqlConnectionHandler mySqlConnectionHandler;
6366

6467
public static NBTSerializer nbtSerializer = null;
6568
public static PotionSerializer potionSerializer;
6669
public static AdvancementSerializer advancementSerializer;
70+
public static StatsSerializer statsSerializer;
6771

6872
public static String TABLE_NAME = "player_data";
6973
public static String TABLE_NAME_EFFECTS;
@@ -212,6 +216,8 @@ public void onPluginEnable(PluginEnableEvent event) {
212216
effectDataManager = new EffectDataManager();
213217
advancementSerializer = new AdvancementSerializer();
214218
advancementDataManager = new AdvancementDataManager();
219+
statsSerializer = new StatsSerializer();
220+
statsDataManager = new StatsDataManager();
215221
}
216222

217223

src/main/java/de/lostesburger/mySqlPlayerBridge/Managers/Modules/ModulesManager.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class ModulesManager {
1717
public String pathSyncTaskDelay;
1818
public String pathSyncEffects;
1919
public String pathSyncAdvancements;
20+
public String pathSyncStats;
2021

2122
public boolean kickOnSyncFail;
2223
public boolean syncEnderChest;
@@ -31,6 +32,7 @@ public class ModulesManager {
3132
public int syncTaskDelay;
3233
public boolean syncEffects;
3334
public boolean syncAdvancements;
35+
public boolean syncStats;
3436

3537

3638
public ModulesManager(){
@@ -48,6 +50,7 @@ public ModulesManager(){
4850
this.pathSyncTaskDelay = "syncTask.delay";
4951
this.pathSyncEffects = "sync.effects";
5052
this.pathSyncAdvancements = "sync.advancements";
53+
this.pathSyncStats = "sync.statistics";
5154

5255

5356
this.syncVaultEconomy = conf.getBoolean(this.pathSyncVaultEconomy);
@@ -63,5 +66,6 @@ public ModulesManager(){
6366
this.syncTaskDelay = (conf.getInt(this.pathSyncTaskDelay)*20);
6467
this.syncEffects = conf.getBoolean(this.pathSyncEffects);
6568
this.syncAdvancements = conf.getBoolean(this.pathSyncAdvancements);
69+
this.syncStats = conf.getBoolean(this.pathSyncStats);
6670
}
6771
}

src/main/java/de/lostesburger/mySqlPlayerBridge/Managers/MySqlData/MySqlDataManager.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ public void savePlayerData(Player player){
131131

132132
Main.effectDataManager.savePlayer(player);
133133
Main.advancementDataManager.savePlayer(player);
134+
Main.statsDataManager.savePlayer(player);
134135
}
135136

136137

@@ -246,6 +247,7 @@ public void applyDataToPlayer(Player player){
246247

247248
Main.effectDataManager.applyPlayer(player);
248249
Main.advancementDataManager.applyPlayer(player);
250+
Main.statsDataManager.applyPlayer(player);
249251
}
250252

251253
public void saveAllOnlinePlayers(){
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package de.lostesburger.mySqlPlayerBridge.Managers;
2+
3+
import de.lostesburger.corelib.MySQL.MySqlError;
4+
import de.lostesburger.corelib.MySQL.MySqlManager;
5+
import de.lostesburger.corelib.Scheduler.Scheduler;
6+
import de.lostesburger.mySqlPlayerBridge.Main;
7+
import org.bukkit.entity.Player;
8+
9+
import java.util.Map;
10+
11+
public class StatsDataManager {
12+
private final boolean enabled;
13+
private final MySqlManager mySqlManager;
14+
15+
public StatsDataManager() {
16+
this.enabled = Main.modulesManager.syncAdvancements;
17+
this.mySqlManager = Main.mySqlConnectionHandler.getManager();
18+
19+
try {
20+
if(!this.mySqlManager.tableExists(Main.TABLE_NAME_STATS)){
21+
throw new RuntimeException("Statistics mysql table is missing!");
22+
}
23+
} catch (MySqlError e) {
24+
throw new RuntimeException(e);
25+
}
26+
}
27+
28+
public void savePlayer(Player player){
29+
Scheduler.runAsync(() -> {
30+
if(!this.enabled) return;
31+
String serialized = Main.statsSerializer.serialize(player);
32+
try {
33+
mySqlManager.setOrUpdateEntry(
34+
Main.TABLE_NAME_STATS,
35+
Map.of("uuid", player.getUniqueId().toString()),
36+
Map.of("stats", serialized)
37+
);
38+
} catch (MySqlError e) {
39+
throw new RuntimeException(e);
40+
}
41+
}, Main.getInstance());
42+
}
43+
44+
public void applyPlayer(Player player){
45+
Scheduler.runAsync(() -> {
46+
if(!this.enabled) return;
47+
48+
Map<String, Object> entry;
49+
try {
50+
entry = mySqlManager.getEntry(Main.TABLE_NAME_STATS,
51+
Map.of("uuid", player.getUniqueId().toString())
52+
);
53+
} catch (MySqlError e) {
54+
throw new RuntimeException(e);
55+
}
56+
if(entry == null) return;
57+
if(entry.isEmpty()) return;
58+
59+
String serialized = (String) entry.get("stats");
60+
Main.statsSerializer.deserialize(serialized, player, true);
61+
}, Main.getInstance());
62+
63+
}
64+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package de.lostesburger.mySqlPlayerBridge.Serialization.Serialization;
2+
3+
import com.google.gson.Gson;
4+
import com.google.gson.JsonArray;
5+
import com.google.gson.JsonObject;
6+
import de.lostesburger.corelib.NMS.Minecraft;
7+
import de.lostesburger.corelib.Scheduler.Scheduler;
8+
import de.lostesburger.mySqlPlayerBridge.Main;
9+
import org.bukkit.Statistic;
10+
import org.bukkit.entity.EntityType;
11+
import org.bukkit.entity.Player;
12+
import org.bukkit.Material;
13+
import org.bukkit.Bukkit;
14+
15+
import java.nio.charset.StandardCharsets;
16+
import java.util.Base64;
17+
18+
public class StatsSerializer {
19+
private final Gson gson = new Gson();
20+
private final boolean DEBUG = true;
21+
22+
public StatsSerializer() {}
23+
24+
public String serialize(Player player) {
25+
JsonArray statsArray = new JsonArray();
26+
27+
for (Statistic stat : Statistic.values()) {
28+
try {
29+
JsonObject statObj = new JsonObject();
30+
statObj.addProperty("type", stat.name());
31+
32+
switch (stat.getType()) {
33+
case UNTYPED -> {
34+
int value = player.getStatistic(stat);
35+
if (value != 0) {
36+
statObj.addProperty("value", value);
37+
statsArray.add(statObj);
38+
}
39+
}
40+
case BLOCK -> {
41+
for (Material mat : Material.values()) {
42+
if (mat.isBlock()) {
43+
int value = player.getStatistic(stat, mat);
44+
if (value != 0) {
45+
JsonObject blockObj = statObj.deepCopy();
46+
blockObj.addProperty("subType", mat.name());
47+
blockObj.addProperty("value", value);
48+
statsArray.add(blockObj);
49+
}
50+
}
51+
}
52+
}
53+
case ITEM -> {
54+
for (Material mat : Material.values()) {
55+
if (mat.isItem()) {
56+
int value = player.getStatistic(stat, mat);
57+
if (value != 0) {
58+
JsonObject itemObj = statObj.deepCopy();
59+
itemObj.addProperty("subType", mat.name());
60+
itemObj.addProperty("value", value);
61+
statsArray.add(itemObj);
62+
}
63+
}
64+
}
65+
}
66+
case ENTITY -> {
67+
for (EntityType type : EntityType.values()) {
68+
try {
69+
int value = player.getStatistic(stat, type);
70+
if (value != 0) {
71+
JsonObject entityObj = statObj.deepCopy();
72+
entityObj.addProperty("subType", type.name());
73+
entityObj.addProperty("value", value);
74+
statsArray.add(entityObj);
75+
}
76+
} catch (Exception ignored) {
77+
}
78+
}
79+
}
80+
}
81+
82+
} catch (Exception e) {
83+
if (DEBUG) Bukkit.getLogger().warning("[StatsSerializer] Fehler bei Statistik: " + stat.name());
84+
}
85+
}
86+
87+
String jsonString = gson.toJson(statsArray);
88+
return Base64.getEncoder().encodeToString(jsonString.getBytes(StandardCharsets.UTF_8));
89+
}
90+
91+
public JsonArray deserialize(String base64, Player player, boolean apply) {
92+
String jsonString = new String(Base64.getDecoder().decode(base64), StandardCharsets.UTF_8);
93+
JsonArray statsArray = gson.fromJson(jsonString, JsonArray.class);
94+
95+
for (int i = 0; i < statsArray.size(); i++) {
96+
JsonObject statObj = statsArray.get(i).getAsJsonObject();
97+
String typeName = statObj.get("type").getAsString();
98+
int value = statObj.get("value").getAsInt();
99+
100+
Statistic stat;
101+
try {
102+
stat = Statistic.valueOf(typeName);
103+
} catch (IllegalArgumentException e) {
104+
if (DEBUG) Bukkit.getLogger().warning("[StatsSerializer] Unbekannte Statistik: " + typeName);
105+
continue;
106+
}
107+
108+
if (apply) {
109+
try {
110+
switch (stat.getType()) {
111+
case UNTYPED -> {
112+
if(Minecraft.isFolia()){
113+
Scheduler.runRegionalScheduler(() -> {
114+
player.setStatistic(stat, value);
115+
}, Main.getInstance(), player.getLocation());
116+
}else {
117+
Scheduler.run(() -> {
118+
player.setStatistic(stat, value);
119+
}, Main.getInstance());
120+
}
121+
}
122+
case BLOCK -> {
123+
Material block = Material.matchMaterial(statObj.get("subType").getAsString());
124+
if (block != null && block.isBlock()){
125+
if(Minecraft.isFolia()){
126+
Scheduler.runRegionalScheduler(() -> {
127+
player.setStatistic(stat, block, value);
128+
}, Main.getInstance(), player.getLocation());
129+
}else {
130+
Scheduler.run(() -> {
131+
player.setStatistic(stat, block, value);
132+
}, Main.getInstance());
133+
}
134+
}
135+
}
136+
case ITEM -> {
137+
Material item = Material.matchMaterial(statObj.get("subType").getAsString());
138+
if (item != null && item.isItem()){
139+
if(Minecraft.isFolia()){
140+
Scheduler.runRegionalScheduler(() -> {
141+
player.setStatistic(stat, item, value);
142+
}, Main.getInstance(), player.getLocation());
143+
}else {
144+
Scheduler.run(() -> {
145+
player.setStatistic(stat, item, value);
146+
}, Main.getInstance());
147+
}
148+
}
149+
}
150+
case ENTITY -> {
151+
try {
152+
EntityType entity = EntityType.valueOf(statObj.get("subType").getAsString());
153+
if(Minecraft.isFolia()){
154+
Scheduler.runRegionalScheduler(() -> {
155+
player.setStatistic(stat, entity, value);
156+
}, Main.getInstance(), player.getLocation());
157+
}else {
158+
Scheduler.run(() -> {
159+
player.setStatistic(stat, entity, value);
160+
}, Main.getInstance());
161+
}
162+
} catch (Exception ignored) {
163+
}
164+
}
165+
}
166+
} catch (Exception e) {
167+
if (DEBUG) Bukkit.getLogger().warning("[StatsSerializer] Fehler beim Setzen von " + stat.name() + ": " + e.getMessage());
168+
}
169+
}
170+
}
171+
172+
return statsArray;
173+
}
174+
}

src/main/resources/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ sync:
4040
vaultEconomy: false
4141
effects: false
4242
advancements: false
43+
statistics: false
4344

4445
# Create an automatic running task which saves all online player's inventory every given time.
4546
syncTask:

0 commit comments

Comments
 (0)