Skip to content

Commit 6bb96b5

Browse files
committed
Refactor MVInventoriesImporter for player data import
Enhanced `readPlayer` to include data object injection and improved import logic. Added utility methods to handle player stats, location, and potion effects from JSON snapshots.
1 parent 8162f4d commit 6bb96b5

File tree

1 file changed

+194
-6
lines changed

1 file changed

+194
-6
lines changed

src/main/java/net/thenextlvl/perworlds/importer/multiverse/MVInventoriesImporter.java

Lines changed: 194 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,42 @@
11
package net.thenextlvl.perworlds.importer.multiverse;
22

3+
import com.google.gson.JsonArray;
4+
import com.google.gson.JsonElement;
5+
import com.google.gson.JsonObject;
36
import com.google.gson.JsonParseException;
47
import com.google.gson.JsonParser;
8+
import com.google.gson.JsonPrimitive;
59
import com.google.gson.stream.JsonReader;
610
import net.thenextlvl.perworlds.PerWorldsPlugin;
711
import net.thenextlvl.perworlds.WorldGroup;
812
import net.thenextlvl.perworlds.data.PlayerData;
9-
import net.thenextlvl.perworlds.group.PaperWorldGroup;
1013
import net.thenextlvl.perworlds.importer.Importer;
11-
import net.thenextlvl.perworlds.model.PaperPlayerData;
14+
import org.bukkit.GameMode;
15+
import org.bukkit.Location;
16+
import org.bukkit.NamespacedKey;
17+
import org.bukkit.Registry;
1218
import org.bukkit.configuration.file.YamlConfiguration;
19+
import org.bukkit.generator.WorldInfo;
20+
import org.bukkit.inventory.ItemStack;
21+
import org.bukkit.potion.PotionEffect;
1322
import org.jspecify.annotations.NullMarked;
23+
import org.jspecify.annotations.Nullable;
1424

1525
import java.io.IOException;
1626
import java.io.InputStreamReader;
1727
import java.nio.charset.StandardCharsets;
1828
import java.nio.file.Files;
29+
import java.util.ArrayList;
30+
import java.util.Arrays;
31+
import java.util.Base64;
32+
import java.util.Comparator;
1933
import java.util.HashMap;
2034
import java.util.HashSet;
35+
import java.util.List;
36+
import java.util.Locale;
2137
import java.util.Map;
38+
import java.util.Objects;
39+
import java.util.Optional;
2240
import java.util.Set;
2341
import java.util.UUID;
2442

@@ -79,9 +97,179 @@ public Map<UUID, String> readPlayers() throws IOException {
7997
}
8098

8199
@Override
82-
public PlayerData readPlayer(UUID uuid, String name, WorldGroup group) throws IOException {
83-
var data = new PaperPlayerData(uuid, ((PaperWorldGroup) group));
84-
85-
return data;
100+
public void readPlayer(UUID uuid, String name, WorldGroup group, PlayerData data) throws IOException {
101+
var path = group.getWorlds().map(WorldInfo::getName)
102+
.map(getDataPath().resolve("worlds")::resolve)
103+
.map(worlds -> worlds.resolve(name + ".json"))
104+
.filter(Files::isRegularFile)
105+
.findAny().orElse(null);
106+
if (path == null) return;
107+
try (var reader = new JsonReader(new InputStreamReader(
108+
Files.newInputStream(path, READ),
109+
StandardCharsets.UTF_8
110+
))) {
111+
Optional.ofNullable(JsonParser.parseReader(reader))
112+
.map(this::asObject).flatMap(this::selectBestSnapshot)
113+
.ifPresent(snapshot -> applySnapshot(snapshot, group, data));
114+
} catch (RuntimeException e) {
115+
plugin.getComponentLogger().warn("Failed to import player data for {} in group {} from {}", name, group, path, e);
116+
}
117+
}
118+
119+
private void applySnapshot(Map.Entry<String, JsonObject> node, WorldGroup group, PlayerData data) {
120+
applyStats(node.getValue(), data);
121+
122+
data.gameMode(GameMode.valueOf(node.getKey().toUpperCase(Locale.ROOT)));
123+
data.respawnLocation(readLocation(node.getValue().get("bedSpawnLocation"), group));
124+
125+
var potions = asArray(node.getValue().get("potions"));
126+
var effects = potions != null ? readPotions(potions) : null;
127+
if (effects != null) data.potionEffects(effects);
128+
129+
// todo: read armor contents, inventory, offhand, enderchest
130+
}
131+
132+
private void applyStats(JsonObject node, PlayerData data) {
133+
var stats = asObject(node.get("stats"));
134+
if (stats == null) return;
135+
data.health(asDouble(stats.get("hp"), data.health()));
136+
data.level(asInt(stats.get("el"), data.level()));
137+
data.experience(asFloat(stats.get("xp"), data.experience()));
138+
data.foodLevel(asInt(stats.get("fl"), data.foodLevel()));
139+
data.exhaustion(asFloat(stats.get("ex"), data.exhaustion()));
140+
data.saturation(asFloat(stats.get("sa"), data.saturation()));
141+
data.fallDistance(asFloat(stats.get("fd"), data.fallDistance()));
142+
data.fireTicks(asInt(stats.get("ft"), data.fireTicks()));
143+
data.remainingAir(asInt(stats.get("ra"), data.remainingAir()));
144+
}
145+
146+
private Optional<Map.Entry<String, JsonObject>> selectBestSnapshot(JsonObject root) {
147+
var gameModes = Arrays.stream(GameMode.values()).map(Enum::name).toList();
148+
return root.entrySet().stream()
149+
.map(entry -> {
150+
var object = asObject(entry.getValue());
151+
return object != null ? Map.entry(entry.getKey(), object) : null;
152+
})
153+
.filter(Objects::nonNull)
154+
.sorted((entry1, entry2) -> {
155+
var mode1 = gameModes.contains(entry1.getKey().toUpperCase(Locale.ROOT));
156+
var mode2 = gameModes.contains(entry2.getKey().toUpperCase(Locale.ROOT));
157+
return Boolean.compare(mode1, mode2);
158+
})
159+
.max(Comparator.comparingInt(entry -> entry.getValue().size()));
160+
}
161+
162+
@SuppressWarnings("deprecation")
163+
private @Nullable ItemStack readItem(JsonElement node) {
164+
try {
165+
var string = asString(node);
166+
if (string != null) {
167+
var bytes = Base64.getDecoder().decode(string);
168+
return ItemStack.deserializeBytes(bytes);
169+
}
170+
171+
var object = asObject(node);
172+
if (object != null) {
173+
var unsafe = plugin.getServer().getUnsafe();
174+
return unsafe.deserializeItemFromJson(object);
175+
}
176+
177+
plugin.getComponentLogger().warn("Don't know how to turn '{}' into an item", node);
178+
return null;
179+
} catch (Exception e) {
180+
plugin.getComponentLogger().warn("Failed to deserialize item '{}'", node, e);
181+
return null;
182+
}
183+
}
184+
185+
private @Nullable Location readLocation(@Nullable JsonElement element, WorldGroup group) {
186+
var location = asObject(element);
187+
if (location == null) return null;
188+
189+
var worldName = asString(location.get("world"));
190+
if (worldName == null) worldName = asString(location.get("wo"));
191+
192+
var world = worldName != null ? plugin.getServer().getWorld(worldName) : null;
193+
if (world == null || !group.containsWorld(world)) return null;
194+
195+
var x = asDouble(location.get("x"), 0);
196+
var y = asDouble(location.get("y"), 0);
197+
var z = asDouble(location.get("z"), 0);
198+
var pitch = asFloat(location.get("pitch"), asFloat(location.get("pi"), 0));
199+
var yaw = asFloat(location.get("yaw"), asFloat(location.get("ya"), 0));
200+
201+
return new Location(world, x, y, z, yaw, pitch);
202+
}
203+
204+
private List<PotionEffect> readPotions(JsonArray array) {
205+
var list = new ArrayList<PotionEffect>(array.size());
206+
array.forEach(element -> {
207+
var object = asObject(element);
208+
if (object == null) return;
209+
210+
var effect = asString(object.get("effect"));
211+
if (effect == null) effect = asString(object.get("pt"));
212+
213+
var key = effect != null ? NamespacedKey.fromString(effect.toLowerCase(Locale.ROOT)) : null;
214+
var type = key != null ? Registry.EFFECT.get(key) : null;
215+
if (type == null) return;
216+
217+
var duration = asInt(object.get("duration"), asInt(object.get("pd"), 0));
218+
var amplifier = asInt(object.get("amplifier"), asInt(object.get("pa"), 0));
219+
var ambient = asBoolean(object.get("ambient"), false);
220+
var particles = asBoolean(object.get("particles"), true);
221+
var icon = asBoolean(object.get("icon"), true);
222+
223+
list.add(new PotionEffect(type, duration, amplifier, ambient, particles, icon));
224+
});
225+
return list;
226+
}
227+
228+
private @Nullable JsonObject asObject(@Nullable JsonElement element) {
229+
return element instanceof JsonObject primitive ? primitive.getAsJsonObject() : null;
230+
}
231+
232+
private @Nullable JsonArray asArray(@Nullable JsonElement element) {
233+
return element instanceof JsonArray primitive ? primitive.getAsJsonArray() : null;
234+
}
235+
236+
private @Nullable String asString(@Nullable JsonElement element) {
237+
return element instanceof JsonPrimitive primitive ? primitive.getAsString() : null;
238+
}
239+
240+
private double asDouble(@Nullable JsonElement element, double defaultValue) {
241+
if (element instanceof JsonPrimitive primitive) try {
242+
if (primitive.isNumber()) return primitive.getAsDouble();
243+
if (primitive.isString()) return Double.parseDouble(primitive.getAsString());
244+
} catch (NumberFormatException ignored) {
245+
}
246+
return defaultValue;
247+
}
248+
249+
private float asFloat(@Nullable JsonElement element, float defaultValue) {
250+
if (element instanceof JsonPrimitive primitive) try {
251+
if (primitive.isNumber()) return primitive.getAsFloat();
252+
if (primitive.isString()) return Float.parseFloat(primitive.getAsString());
253+
} catch (NumberFormatException ignored) {
254+
}
255+
return defaultValue;
256+
}
257+
258+
private int asInt(@Nullable JsonElement element, int defaultValue) {
259+
if (element instanceof JsonPrimitive primitive) try {
260+
if (primitive.isNumber()) return primitive.getAsInt();
261+
if (primitive.isString()) return Integer.parseInt(primitive.getAsString());
262+
} catch (NumberFormatException ignored) {
263+
}
264+
return defaultValue;
265+
}
266+
267+
private boolean asBoolean(@Nullable JsonElement element, boolean defaultValue) {
268+
if (element instanceof JsonPrimitive primitive) try {
269+
if (primitive.isBoolean()) return primitive.getAsBoolean();
270+
if (primitive.isString()) return Boolean.parseBoolean(primitive.getAsString());
271+
} catch (NumberFormatException ignored) {
272+
}
273+
return defaultValue;
86274
}
87275
}

0 commit comments

Comments
 (0)