diff --git a/build.gradle b/build.gradle index 31393a3..ee2c8e8 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ group = project.maven_group repositories { maven { url = "https://jitpack.io/" } - maven { url = "https://maven.gegy.dev/" } + maven { url = 'https://maven.nucleoid.xyz/' } mavenLocal() } diff --git a/gradle.properties b/gradle.properties index debfa69..fa20423 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ maven_group=io.github.restioson archives_base_name=loopdeloop # Dependencies fabric_version=0.29.3+1.16 -plasmid_version=0.4.9 +plasmid_version=0.4.133 diff --git a/src/main/java/io/github/restioson/loopdeloop/LoopDeLoop.java b/src/main/java/io/github/restioson/loopdeloop/LoopDeLoop.java index 7278d40..5cabc3b 100644 --- a/src/main/java/io/github/restioson/loopdeloop/LoopDeLoop.java +++ b/src/main/java/io/github/restioson/loopdeloop/LoopDeLoop.java @@ -7,10 +7,12 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import xyz.nucleoid.plasmid.game.GameType; +import xyz.nucleoid.plasmid.storage.ServerStorage; public final class LoopDeLoop implements ModInitializer { public static final String ID = "loopdeloop"; public static final Logger LOGGER = LogManager.getLogger(ID); + public static final LoopDeLoopTimeStorage SCORE_STORAGE = ServerStorage.createStorage(new Identifier(ID, "time_leaderboards"), new LoopDeLoopTimeStorage()); public static final GameType TYPE = GameType.register( new Identifier(LoopDeLoop.ID, "loopdeloop"), diff --git a/src/main/java/io/github/restioson/loopdeloop/LoopDeLoopTimeStorage.java b/src/main/java/io/github/restioson/loopdeloop/LoopDeLoopTimeStorage.java new file mode 100644 index 0000000..3708835 --- /dev/null +++ b/src/main/java/io/github/restioson/loopdeloop/LoopDeLoopTimeStorage.java @@ -0,0 +1,72 @@ +package io.github.restioson.loopdeloop; + +import it.unimi.dsi.fastutil.objects.Object2LongArrayMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.fabricmc.fabric.api.util.NbtType; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; +import xyz.nucleoid.plasmid.storage.ServerStorage; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +public class LoopDeLoopTimeStorage implements ServerStorage { + public final Object2ObjectMap> timesMap = new Object2ObjectOpenHashMap<>(); + + public void putPlayerTime(Identifier config, ServerPlayerEntity player, long time) { + if (getPlayerTime(config, player) > time) { + this.timesMap.get(config).put(player.getUuid(), time); + } + } + + public long getPlayerTime(Identifier identifier, ServerPlayerEntity player) { + return this.timesMap.computeIfAbsent(identifier, identifier1 -> new Object2LongArrayMap<>()).getOrDefault(player.getUuid(), Long.MAX_VALUE); + } + + public LinkedHashMap getSortedScores(Identifier identifier) { + return timesMap.get(identifier).object2LongEntrySet().stream().sorted(Map.Entry.comparingByValue()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); + } + + @Override + public CompoundTag toTag() { + CompoundTag tag = new CompoundTag(); + CompoundTag configTag = new CompoundTag(); + this.timesMap.forEach(((config, scoreMap) -> { + ListTag playersTag = new ListTag(); + this.timesMap.get(config).forEach((uuid, score) -> playersTag.add(this.createPlayerScoreTag(uuid, score))); + + configTag.put(config.toString(), playersTag); + })); + + tag.put("Configs", configTag); + return tag; + } + + @Override + public void fromTag(CompoundTag tag) { + CompoundTag configTag = (CompoundTag) tag.get("Configs"); + configTag.getKeys().forEach(config -> { + Identifier configId = Identifier.tryParse(config); + timesMap.put(configId, new Object2LongArrayMap<>()); + + ListTag playersTag = configTag.getList(config, NbtType.COMPOUND); + playersTag.forEach(tag1 -> { + CompoundTag scoreTag = (CompoundTag) tag1; + timesMap.get(configId).put(scoreTag.getUuid("UUID"), scoreTag.getLong("Time")); + }); + }); + } + + private CompoundTag createPlayerScoreTag(UUID uuid, long time) { + CompoundTag tag = new CompoundTag(); + tag.putUuid("UUID", uuid); + tag.putLong("Time", time); + return tag; + } +} diff --git a/src/main/java/io/github/restioson/loopdeloop/game/LoopDeLoopActive.java b/src/main/java/io/github/restioson/loopdeloop/game/LoopDeLoopActive.java index 564b14c..278a626 100644 --- a/src/main/java/io/github/restioson/loopdeloop/game/LoopDeLoopActive.java +++ b/src/main/java/io/github/restioson/loopdeloop/game/LoopDeLoopActive.java @@ -2,6 +2,8 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; +import com.mojang.authlib.GameProfile; +import io.github.restioson.loopdeloop.LoopDeLoop; import io.github.restioson.loopdeloop.game.map.LoopDeLoopHoop; import io.github.restioson.loopdeloop.game.map.LoopDeLoopMap; import io.github.restioson.loopdeloop.game.map.LoopDeLoopWinner; @@ -39,30 +41,18 @@ import net.minecraft.world.GameMode; import org.jetbrains.annotations.Nullable; import xyz.nucleoid.plasmid.game.GameSpace; -import xyz.nucleoid.plasmid.game.event.GameCloseListener; -import xyz.nucleoid.plasmid.game.event.GameOpenListener; -import xyz.nucleoid.plasmid.game.event.GameTickListener; -import xyz.nucleoid.plasmid.game.event.OfferPlayerListener; -import xyz.nucleoid.plasmid.game.event.PlayerAddListener; -import xyz.nucleoid.plasmid.game.event.PlayerDamageListener; -import xyz.nucleoid.plasmid.game.event.PlayerDeathListener; -import xyz.nucleoid.plasmid.game.event.PlayerRemoveListener; -import xyz.nucleoid.plasmid.game.event.UseItemListener; +import xyz.nucleoid.plasmid.game.event.*; import xyz.nucleoid.plasmid.game.player.JoinResult; import xyz.nucleoid.plasmid.game.rule.GameRule; import xyz.nucleoid.plasmid.game.rule.RuleResult; import xyz.nucleoid.plasmid.util.ItemStackBuilder; import xyz.nucleoid.plasmid.widget.GlobalWidgets; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; +import static io.github.restioson.loopdeloop.LoopDeLoop.SCORE_STORAGE; + public final class LoopDeLoopActive { private final GameSpace gameSpace; private final LoopDeLoopMap map; @@ -404,6 +394,9 @@ private void onPlayerFinish(ServerPlayerEntity player, long time) { player.sendMessage(new LiteralText("You finished in " + ordinal + " place!"), true); player.playSound(SoundEvents.ENTITY_PLAYER_LEVELUP, SoundCategory.PLAYERS, 1.0F, 1.0F); player.setGameMode(GameMode.SPECTATOR); + if (gameSpace.getGameConfig().getSource() != null) { + SCORE_STORAGE.putPlayerTime(gameSpace.getGameConfig().getSource(), player, (time - this.startTime)); + } } private void failHoop(ServerPlayerEntity player, LoopDeLoopPlayer state, long time) { @@ -468,10 +461,46 @@ private void broadcastWin() { message = new LiteralText(message_string).formatted(Formatting.GOLD); } - this.broadcastMessage(message); + gameSpace.getPlayers().sendMessage(message); + this.sendLeaderboard(); this.broadcastSound(SoundEvents.ENTITY_VILLAGER_YES); } + public void sendLeaderboard() { + LinkedHashMap sortedScores = LoopDeLoop.SCORE_STORAGE.getSortedScores(gameSpace.getGameConfig().getSource()); + ArrayList> scoreboard = new ArrayList<>(sortedScores.entrySet()); + StringBuilder leaderboard_builder = new StringBuilder(); + leaderboard_builder.append("All Time Leaderboard\n"); + for (int i = 0; i < 5; i++) { + if (i >= scoreboard.size()) break; + Map.Entry entry = scoreboard.get(i); + leaderboard_builder.append(i + 1).append(". "); + GameProfile player = gameSpace.getServer().getUserCache().getByUuid(entry.getKey()); + if (player == null) { + leaderboard_builder.append("\n"); + continue; + } + leaderboard_builder.append(player.getName()); + leaderboard_builder.append(String.format(" in %.2fs\n", entry.getValue() / 20.0f)); + } + + gameSpace.getPlayers().sendMessage(new LiteralText(leaderboard_builder.toString()).formatted(Formatting.GOLD)); + + for (int i = 0; i < scoreboard.size(); i++) { + Map.Entry entry = scoreboard.get(i); + int pos = i + 1; + gameSpace.getPlayers().forEach(player -> { + if (player.getUuid().equals(entry.getKey())) { + player.sendMessage(new LiteralText(String.format( + "%s. %s in %.2fs", + pos, + player.getName().asString(), entry.getValue() / 20.0f) + ).formatted(Formatting.GOLD, Formatting.BOLD), false); + } + }); + } + } + private ActionResult onPlayerDamage(ServerPlayerEntity player, DamageSource source, float amount) { long time = this.gameSpace.getWorld().getTime(); this.failHoop(player, this.playerStates.get(player), time); @@ -513,13 +542,6 @@ private void spawnSpectator(ServerPlayerEntity player) { this.spawnLogic.spawnPlayer(player); } - // TODO: extract common broadcast utils into plasmid - private void broadcastMessage(Text message) { - for (ServerPlayerEntity player : this.gameSpace.getPlayers()) { - player.sendMessage(message, false); - } - } - private void broadcastTitle(Text message) { for (ServerPlayerEntity player : this.gameSpace.getPlayers()) { TitleS2CPacket packet = new TitleS2CPacket(TitleS2CPacket.Action.TITLE, message, 1, 5, 3);