From 07c219cbc6736755702133931ca2b44973fbeaca Mon Sep 17 00:00:00 2001 From: PotatoPresident Date: Tue, 16 Mar 2021 15:10:44 -0700 Subject: [PATCH 1/2] Add leaderboard system --- build.gradle | 2 +- gradle.properties | 2 +- .../restioson/loopdeloop/LoopDeLoop.java | 2 + .../LoopDeLoopTimeScoreStorage.java | 74 +++++++++++++++++++ .../loopdeloop/game/LoopDeLoopActive.java | 54 +++++++++----- .../loopdeloop/game/LoopDeLoopWaiting.java | 2 +- 6 files changed, 114 insertions(+), 22 deletions(-) create mode 100644 src/main/java/io/github/restioson/loopdeloop/LoopDeLoopTimeScoreStorage.java 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..33f92bc 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 LoopDeLoopTimeScoreStorage SCORE_STORAGE = ServerStorage.createStorage(new Identifier(ID, "time_leaderboards"), new LoopDeLoopTimeScoreStorage()); public static final GameType TYPE = GameType.register( new Identifier(LoopDeLoop.ID, "loopdeloop"), diff --git a/src/main/java/io/github/restioson/loopdeloop/LoopDeLoopTimeScoreStorage.java b/src/main/java/io/github/restioson/loopdeloop/LoopDeLoopTimeScoreStorage.java new file mode 100644 index 0000000..e54f539 --- /dev/null +++ b/src/main/java/io/github/restioson/loopdeloop/LoopDeLoopTimeScoreStorage.java @@ -0,0 +1,74 @@ +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.nbt.Tag; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; +import xyz.nucleoid.plasmid.storage.ServerStorage; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +public class LoopDeLoopTimeScoreStorage implements ServerStorage { + public final Object2ObjectMap> scoreMap = new Object2ObjectOpenHashMap<>(); + + public void putPlayerTime(Identifier config, ServerPlayerEntity player, long score) { + if (getPlayerTime(config, player) > score) { + this.scoreMap.get(config).put(player.getUuid(), score); + } + } + + public long getPlayerTime(Identifier identifier, ServerPlayerEntity player) { + return this.scoreMap.computeIfAbsent(identifier, identifier1 -> new Object2LongArrayMap<>()).getOrDefault(player.getUuid(), Long.MAX_VALUE); + } + + public LinkedHashMap getSortedScores(Identifier identifier) { + return scoreMap.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.scoreMap.forEach(((config, scoreMap) -> { + ListTag playersTag = new ListTag(); + this.scoreMap.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); + scoreMap.put(configId, new Object2LongArrayMap<>()); + + ListTag playersTag = configTag.getList(config, NbtType.COMPOUND); + playersTag.forEach(tag1 -> { + CompoundTag scoreTag = (CompoundTag) tag1; + scoreMap.get(configId).put(scoreTag.getUuid("UUID"), scoreTag.getLong("Score")); + }); + }); + } + + private CompoundTag createPlayerScoreTag(UUID uuid, long score) { + CompoundTag tag = new CompoundTag(); + tag.putUuid("UUID", uuid); + tag.putLong("Score", score); + 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..f7ec0f0 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; @@ -29,15 +31,13 @@ import net.minecraft.sound.SoundEvents; import net.minecraft.text.LiteralText; import net.minecraft.text.Text; -import net.minecraft.util.ActionResult; -import net.minecraft.util.Formatting; -import net.minecraft.util.Hand; -import net.minecraft.util.TypedActionResult; +import net.minecraft.util.*; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.world.GameMode; import org.jetbrains.annotations.Nullable; +import xyz.nucleoid.plasmid.game.ConfiguredGame; import xyz.nucleoid.plasmid.game.GameSpace; import xyz.nucleoid.plasmid.game.event.GameCloseListener; import xyz.nucleoid.plasmid.game.event.GameOpenListener; @@ -51,18 +51,16 @@ 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.storage.ServerStorage; 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.nio.file.Path; +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 +402,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 +469,32 @@ private void broadcastWin() { message = new LiteralText(message_string).formatted(Formatting.GOLD); } - this.broadcastMessage(message); + gameSpace.getPlayers().sendMessage(message); + 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("Leaderboard\n"); + for (int i = 0; i < 10; 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)); + } + 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 +536,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); diff --git a/src/main/java/io/github/restioson/loopdeloop/game/LoopDeLoopWaiting.java b/src/main/java/io/github/restioson/loopdeloop/game/LoopDeLoopWaiting.java index 9d73c28..87fe642 100644 --- a/src/main/java/io/github/restioson/loopdeloop/game/LoopDeLoopWaiting.java +++ b/src/main/java/io/github/restioson/loopdeloop/game/LoopDeLoopWaiting.java @@ -45,7 +45,7 @@ public static GameOpenProcedure open(GameOpenContext context) return context.createOpenProcedure(worldConfig, game -> { LoopDeLoopWaiting waiting = new LoopDeLoopWaiting(game.getSpace(), map, context.getConfig()); - + GameWaitingLobby.applyTo(game, context.getConfig().players); game.setRule(GameRule.FALL_DAMAGE, RuleResult.ALLOW); From af8c13a76fb79b587424c1256542ddf7b38994aa Mon Sep 17 00:00:00 2001 From: Cat Core <34719527+arthurbambou@users.noreply.github.com> Date: Thu, 4 Mar 2021 07:13:21 +0100 Subject: [PATCH 2/2] Fixed issues --- .../restioson/loopdeloop/LoopDeLoop.java | 2 +- ...torage.java => LoopDeLoopTimeStorage.java} | 28 +++++++------- .../loopdeloop/game/LoopDeLoopActive.java | 38 +++++++++++-------- .../loopdeloop/game/LoopDeLoopWaiting.java | 2 +- 4 files changed, 37 insertions(+), 33 deletions(-) rename src/main/java/io/github/restioson/loopdeloop/{LoopDeLoopTimeScoreStorage.java => LoopDeLoopTimeStorage.java} (70%) diff --git a/src/main/java/io/github/restioson/loopdeloop/LoopDeLoop.java b/src/main/java/io/github/restioson/loopdeloop/LoopDeLoop.java index 33f92bc..5cabc3b 100644 --- a/src/main/java/io/github/restioson/loopdeloop/LoopDeLoop.java +++ b/src/main/java/io/github/restioson/loopdeloop/LoopDeLoop.java @@ -12,7 +12,7 @@ public final class LoopDeLoop implements ModInitializer { public static final String ID = "loopdeloop"; public static final Logger LOGGER = LogManager.getLogger(ID); - public static final LoopDeLoopTimeScoreStorage SCORE_STORAGE = ServerStorage.createStorage(new Identifier(ID, "time_leaderboards"), new LoopDeLoopTimeScoreStorage()); + 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/LoopDeLoopTimeScoreStorage.java b/src/main/java/io/github/restioson/loopdeloop/LoopDeLoopTimeStorage.java similarity index 70% rename from src/main/java/io/github/restioson/loopdeloop/LoopDeLoopTimeScoreStorage.java rename to src/main/java/io/github/restioson/loopdeloop/LoopDeLoopTimeStorage.java index e54f539..3708835 100644 --- a/src/main/java/io/github/restioson/loopdeloop/LoopDeLoopTimeScoreStorage.java +++ b/src/main/java/io/github/restioson/loopdeloop/LoopDeLoopTimeStorage.java @@ -6,32 +6,30 @@ import net.fabricmc.fabric.api.util.NbtType; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.Tag; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.util.Identifier; import xyz.nucleoid.plasmid.storage.ServerStorage; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; -public class LoopDeLoopTimeScoreStorage implements ServerStorage { - public final Object2ObjectMap> scoreMap = new Object2ObjectOpenHashMap<>(); +public class LoopDeLoopTimeStorage implements ServerStorage { + public final Object2ObjectMap> timesMap = new Object2ObjectOpenHashMap<>(); - public void putPlayerTime(Identifier config, ServerPlayerEntity player, long score) { - if (getPlayerTime(config, player) > score) { - this.scoreMap.get(config).put(player.getUuid(), score); + 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.scoreMap.computeIfAbsent(identifier, identifier1 -> new Object2LongArrayMap<>()).getOrDefault(player.getUuid(), Long.MAX_VALUE); + return this.timesMap.computeIfAbsent(identifier, identifier1 -> new Object2LongArrayMap<>()).getOrDefault(player.getUuid(), Long.MAX_VALUE); } public LinkedHashMap getSortedScores(Identifier identifier) { - return scoreMap.get(identifier).object2LongEntrySet().stream().sorted(Map.Entry.comparingByValue()) + return timesMap.get(identifier).object2LongEntrySet().stream().sorted(Map.Entry.comparingByValue()) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); } @@ -39,9 +37,9 @@ public LinkedHashMap getSortedScores(Identifier identifier) { public CompoundTag toTag() { CompoundTag tag = new CompoundTag(); CompoundTag configTag = new CompoundTag(); - this.scoreMap.forEach(((config, scoreMap) -> { + this.timesMap.forEach(((config, scoreMap) -> { ListTag playersTag = new ListTag(); - this.scoreMap.get(config).forEach((uuid, score) -> playersTag.add(this.createPlayerScoreTag(uuid, score))); + this.timesMap.get(config).forEach((uuid, score) -> playersTag.add(this.createPlayerScoreTag(uuid, score))); configTag.put(config.toString(), playersTag); })); @@ -55,20 +53,20 @@ public void fromTag(CompoundTag tag) { CompoundTag configTag = (CompoundTag) tag.get("Configs"); configTag.getKeys().forEach(config -> { Identifier configId = Identifier.tryParse(config); - scoreMap.put(configId, new Object2LongArrayMap<>()); + timesMap.put(configId, new Object2LongArrayMap<>()); ListTag playersTag = configTag.getList(config, NbtType.COMPOUND); playersTag.forEach(tag1 -> { CompoundTag scoreTag = (CompoundTag) tag1; - scoreMap.get(configId).put(scoreTag.getUuid("UUID"), scoreTag.getLong("Score")); + timesMap.get(configId).put(scoreTag.getUuid("UUID"), scoreTag.getLong("Time")); }); }); } - private CompoundTag createPlayerScoreTag(UUID uuid, long score) { + private CompoundTag createPlayerScoreTag(UUID uuid, long time) { CompoundTag tag = new CompoundTag(); tag.putUuid("UUID", uuid); - tag.putLong("Score", score); + 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 f7ec0f0..278a626 100644 --- a/src/main/java/io/github/restioson/loopdeloop/game/LoopDeLoopActive.java +++ b/src/main/java/io/github/restioson/loopdeloop/game/LoopDeLoopActive.java @@ -31,31 +31,23 @@ import net.minecraft.sound.SoundEvents; import net.minecraft.text.LiteralText; import net.minecraft.text.Text; -import net.minecraft.util.*; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Formatting; +import net.minecraft.util.Hand; +import net.minecraft.util.TypedActionResult; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.world.GameMode; import org.jetbrains.annotations.Nullable; -import xyz.nucleoid.plasmid.game.ConfiguredGame; 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.storage.ServerStorage; import xyz.nucleoid.plasmid.util.ItemStackBuilder; import xyz.nucleoid.plasmid.widget.GlobalWidgets; -import java.nio.file.Path; import java.util.*; import java.util.stream.Collectors; @@ -470,7 +462,7 @@ private void broadcastWin() { } gameSpace.getPlayers().sendMessage(message); - sendLeaderboard(); + this.sendLeaderboard(); this.broadcastSound(SoundEvents.ENTITY_VILLAGER_YES); } @@ -478,8 +470,8 @@ 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("Leaderboard\n"); - for (int i = 0; i < 10; i++) { + 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(". "); @@ -493,6 +485,20 @@ public void sendLeaderboard() { } 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) { diff --git a/src/main/java/io/github/restioson/loopdeloop/game/LoopDeLoopWaiting.java b/src/main/java/io/github/restioson/loopdeloop/game/LoopDeLoopWaiting.java index 87fe642..9d73c28 100644 --- a/src/main/java/io/github/restioson/loopdeloop/game/LoopDeLoopWaiting.java +++ b/src/main/java/io/github/restioson/loopdeloop/game/LoopDeLoopWaiting.java @@ -45,7 +45,7 @@ public static GameOpenProcedure open(GameOpenContext context) return context.createOpenProcedure(worldConfig, game -> { LoopDeLoopWaiting waiting = new LoopDeLoopWaiting(game.getSpace(), map, context.getConfig()); - + GameWaitingLobby.applyTo(game, context.getConfig().players); game.setRule(GameRule.FALL_DAMAGE, RuleResult.ALLOW);