1
0
mirror of https://github.com/GeyserMC/Geyser.git synced 2026-01-04 15:31:36 +00:00

Scoreboard rework (#4947)

* Initial version of the great scoreboard rework

* Fixed some issues and added some initial tests

* Addressed review

* Added CubeCraft's scoreboard as a test, and fixed a discovered bug

* Removed var usage for primitives and String, removed star imports
This commit is contained in:
Tim203
2024-10-08 19:26:46 +02:00
committed by GitHub
parent c656e415f3
commit ef4acb121f
40 changed files with 4838 additions and 1110 deletions

View File

@@ -0,0 +1,270 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.scoreboard.network;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockAndAddPlayerEntity;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
import net.kyori.adventure.text.Component;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetPlayerTeamTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.CollisionRule;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.NameTagVisibility;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamAction;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetPlayerTeamPacket;
import org.junit.jupiter.api.Test;
public class NameVisibilityScoreboardTest {
@Test
void playerVisibilityNever() {
mockContextScoreboard(context -> {
var setPlayerTeamTranslator = new JavaSetPlayerTeamTranslator();
mockAndAddPlayerEntity(context, "player1", 2);
context.translate(
setPlayerTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"team1",
Component.text("displayName"),
Component.text("prefix"),
Component.text("suffix"),
false,
false,
NameTagVisibility.NEVER,
CollisionRule.NEVER,
TeamColor.DARK_RED,
new String[]{"player1"}
)
);
assertNextPacket(() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(2);
packet.getMetadata().put(EntityDataTypes.NAME, "");
return packet;
}, context);
});
}
@Test
void playerVisibilityHideForOtherTeam() {
mockContextScoreboard(context -> {
var setPlayerTeamTranslator = new JavaSetPlayerTeamTranslator();
mockAndAddPlayerEntity(context, "player1", 2);
context.translate(
setPlayerTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"team1",
Component.text("displayName"),
Component.text("prefix"),
Component.text("suffix"),
false,
false,
NameTagVisibility.HIDE_FOR_OTHER_TEAMS,
CollisionRule.NEVER,
TeamColor.DARK_RED,
new String[]{"player1"}
)
);
// only hidden if session player (Tim203) is in a team as well
assertNextPacket(() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(2);
packet.getMetadata().put(EntityDataTypes.NAME, "§4prefix§r§4player1§r§4suffix");
return packet;
}, context);
assertNoNextPacket(context);
// create another team and add Tim203 to it
context.translate(
setPlayerTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"team2",
Component.text("displayName"),
Component.text("prefix"),
Component.text("suffix"),
false,
false,
NameTagVisibility.NEVER,
CollisionRule.NEVER,
TeamColor.DARK_RED,
new String[]{"Tim203"}
)
);
// Tim203 is now in another team, so it should be hidden
assertNextPacket(() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(2);
packet.getMetadata().put(EntityDataTypes.NAME, "");
return packet;
}, context);
assertNoNextPacket(context);
// add Tim203 to same team as player1, score should be visible again
context.translate(
setPlayerTeamTranslator,
new ClientboundSetPlayerTeamPacket("team1", TeamAction.ADD_PLAYER, new String[]{"Tim203"})
);
assertNextPacket(() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(2);
packet.getMetadata().put(EntityDataTypes.NAME, "§4prefix§r§4player1§r§4suffix");
return packet;
}, context);
});
}
@Test
void playerVisibilityHideForOwnTeam() {
mockContextScoreboard(context -> {
var setPlayerTeamTranslator = new JavaSetPlayerTeamTranslator();
mockAndAddPlayerEntity(context, "player1", 2);
context.translate(
setPlayerTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"team1",
Component.text("displayName"),
Component.text("prefix"),
Component.text("suffix"),
false,
false,
NameTagVisibility.HIDE_FOR_OWN_TEAM,
CollisionRule.NEVER,
TeamColor.DARK_RED,
new String[]{"player1"}
)
);
// Tim203 is not in a team (let alone the same team), so should be visible
assertNextPacket(() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(2);
packet.getMetadata().put(EntityDataTypes.NAME, "§4prefix§r§4player1§r§4suffix");
return packet;
}, context);
assertNoNextPacket(context);
// Tim203 is now in the same team as player1, so should be hidden
context.translate(
setPlayerTeamTranslator,
new ClientboundSetPlayerTeamPacket("team1", TeamAction.ADD_PLAYER, new String[]{"Tim203"})
);
assertNextPacket(() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(2);
packet.getMetadata().put(EntityDataTypes.NAME, "");
return packet;
}, context);
assertNoNextPacket(context);
// create another team and add Tim203 to there, score should be visible again
context.translate(
setPlayerTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"team2",
Component.text("displayName"),
Component.text("prefix"),
Component.text("suffix"),
false,
false,
NameTagVisibility.NEVER,
CollisionRule.NEVER,
TeamColor.DARK_RED,
new String[]{"Tim203"}
)
);
assertNextPacket(() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(2);
packet.getMetadata().put(EntityDataTypes.NAME, "§4prefix§r§4player1§r§4suffix");
return packet;
}, context);
});
}
@Test
void playerVisibilityAlways() {
mockContextScoreboard(context -> {
var setPlayerTeamTranslator = new JavaSetPlayerTeamTranslator();
mockAndAddPlayerEntity(context, "player1", 2);
context.translate(
setPlayerTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"team1",
Component.text("displayName"),
Component.text("prefix"),
Component.text("suffix"),
false,
false,
NameTagVisibility.ALWAYS,
CollisionRule.NEVER,
TeamColor.DARK_RED,
new String[]{"player1"}
)
);
assertNextPacket(() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(2);
packet.getMetadata().put(EntityDataTypes.NAME, "§4prefix§r§4player1§r§4suffix");
return packet;
}, context);
// adding self to another team shouldn't make a difference
context.translate(
setPlayerTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"team2",
Component.text("displayName"),
Component.text("prefix"),
Component.text("suffix"),
false,
false,
NameTagVisibility.ALWAYS,
CollisionRule.NEVER,
TeamColor.DARK_RED,
new String[]{"Tim203"}
)
);
assertNoNextPacket(context);
// adding self to player1 team shouldn't matter
context.translate(
setPlayerTeamTranslator,
new ClientboundSetPlayerTeamPacket("team1", TeamAction.ADD_PLAYER, new String[]{"Tim203"})
);
assertNoNextPacket(context);
});
}
}

View File

@@ -0,0 +1,227 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.scoreboard.network.belowname;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockAndAddPlayerEntity;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetDisplayObjectiveTranslator;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetObjectiveTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetDisplayObjectivePacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket;
import org.junit.jupiter.api.Test;
public class BasicBelownameScoreboardTests {
@Test
void displayWithNoPlayersAndRemove() {
mockContextScoreboard(context -> {
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective",
ObjectiveAction.ADD,
Component.text("objective"),
ScoreType.INTEGER,
null
)
);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "objective")
);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "")
);
assertNoNextPacket(context);
});
}
@Test
void displayColorWithOnePlayer() {
mockContextScoreboard(context -> {
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
mockAndAddPlayerEntity(context, "player1", 2);
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective",
ObjectiveAction.ADD,
Component.text("objective", NamedTextColor.BLUE),
ScoreType.INTEGER,
null
)
);
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "objective")
);
assertNextPacket(() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(2);
packet.getMetadata().put(EntityDataTypes.SCORE, "0 §r§9objective");
return packet;
}, context);
});
}
@Test
void displayWithOnePlayerAndRemove() {
mockContextScoreboard(context -> {
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
mockAndAddPlayerEntity(context, "player1", 2);
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective",
ObjectiveAction.ADD,
Component.text("objective"),
ScoreType.INTEGER,
null
)
);
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "objective")
);
assertNextPacket(() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(2);
packet.getMetadata().put(EntityDataTypes.SCORE, "0 §robjective");
return packet;
}, context);
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "")
);
assertNextPacket(() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(2);
packet.getMetadata().put(EntityDataTypes.SCORE, "");
return packet;
}, context);
});
}
@Test
void overrideAndRemove() {
mockContextScoreboard(context -> {
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
mockAndAddPlayerEntity(context, "player1", 2);
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective1",
ObjectiveAction.ADD,
Component.text("objective1"),
ScoreType.INTEGER,
null
)
);
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective2",
ObjectiveAction.ADD,
Component.text("objective2"),
ScoreType.INTEGER,
null
)
);
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "objective2")
);
assertNextPacket(() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(2);
packet.getMetadata().put(EntityDataTypes.SCORE, "0 §robjective2");
return packet;
}, context);
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "objective1")
);
assertNextPacket(() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(2);
packet.getMetadata().put(EntityDataTypes.SCORE, "");
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(2);
packet.getMetadata().put(EntityDataTypes.SCORE, "0 §robjective1");
return packet;
}, context);
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "")
);
assertNextPacket(() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(2);
packet.getMetadata().put(EntityDataTypes.SCORE, "");
return packet;
}, context);
});
}
}

View File

@@ -0,0 +1,204 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.scoreboard.network.playerlist;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
import java.util.List;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextDecoration;
import org.cloudburstmc.protocol.bedrock.data.ScoreInfo;
import org.cloudburstmc.protocol.bedrock.packet.RemoveObjectivePacket;
import org.cloudburstmc.protocol.bedrock.packet.SetDisplayObjectivePacket;
import org.cloudburstmc.protocol.bedrock.packet.SetScorePacket;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetDisplayObjectiveTranslator;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetObjectiveTranslator;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetScoreTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetDisplayObjectivePacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket;
import org.junit.jupiter.api.Test;
/*
Identical to sidebar
*/
public class BasicPlayerlistScoreboardTests {
@Test
void display() {
mockContextScoreboard(context -> {
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective",
ObjectiveAction.ADD,
Component.text("objective"),
ScoreType.INTEGER,
null
)
);
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "objective")
);
assertNextPacket(() -> {
var packet = new SetDisplayObjectivePacket();
packet.setObjectiveId("0");
packet.setDisplayName("objective");
packet.setCriteria("dummy");
packet.setDisplaySlot("list");
packet.setSortOrder(1);
return packet;
}, context);
});
}
@Test
void displayNameColors() {
mockContextScoreboard(context -> {
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective",
ObjectiveAction.ADD,
Component.text("objective", Style.style(NamedTextColor.AQUA, TextDecoration.BOLD)),
ScoreType.INTEGER,
null
)
);
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "objective")
);
assertNextPacket(() -> {
var packet = new SetDisplayObjectivePacket();
packet.setObjectiveId("0");
packet.setDisplayName("§b§lobjective");
packet.setCriteria("dummy");
packet.setDisplaySlot("list");
packet.setSortOrder(1);
return packet;
}, context);
});
}
@Test
void overrideWithOneScore() {
mockContextScoreboard(context -> {
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
var setScoreTranslator = new JavaSetScoreTranslator();
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective1",
ObjectiveAction.ADD,
Component.text("objective1"),
ScoreType.INTEGER,
null
)
);
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective2",
ObjectiveAction.ADD,
Component.text("objective2"),
ScoreType.INTEGER,
null
)
);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("Tim203", "objective1", 1));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("Tim203", "objective2", 2));
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "objective2")
);
assertNextPacket(() -> {
var packet = new SetDisplayObjectivePacket();
packet.setObjectiveId("0");
packet.setDisplayName("objective2");
packet.setCriteria("dummy");
packet.setDisplaySlot("list");
packet.setSortOrder(1);
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
// session player name is Tim203
packet.setInfos(List.of(new ScoreInfo(1, "0", 2, ScoreInfo.ScorerType.PLAYER, 1)));
return packet;
}, context);
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "objective1")
);
assertNextPacket(() -> {
var packet = new RemoveObjectivePacket();
packet.setObjectiveId("0");
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetDisplayObjectivePacket();
packet.setObjectiveId("2");
packet.setDisplayName("objective1");
packet.setCriteria("dummy");
packet.setDisplaySlot("list");
packet.setSortOrder(1);
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
// session player name is Tim203
packet.setInfos(List.of(new ScoreInfo(3, "2", 1, ScoreInfo.ScorerType.PLAYER, 1)));
return packet;
}, context);
});
}
}

View File

@@ -0,0 +1,756 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.scoreboard.network.server;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockAndAddPlayerEntity;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
import java.util.List;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.cloudburstmc.protocol.bedrock.data.ScoreInfo;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.packet.RemoveObjectivePacket;
import org.cloudburstmc.protocol.bedrock.packet.SetDisplayObjectivePacket;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetScorePacket;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetDisplayObjectiveTranslator;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetObjectiveTranslator;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetPlayerTeamTranslator;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetScoreTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.CollisionRule;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.NameTagVisibility;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamAction;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetDisplayObjectivePacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetPlayerTeamPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket;
import org.junit.jupiter.api.Test;
public class CubecraftScoreboardTest {
@Test
void test() {
mockContextScoreboard(context -> {
var setTeamTranslator = new JavaSetPlayerTeamTranslator();
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
var setScoreTranslator = new JavaSetScoreTranslator();
// unused
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("SB_NoName", Component.text("SB_NoName"), Component.empty(), Component.empty(), true, true, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.RESET, new String[0]));
assertNoNextPacket(context);
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"sidebar",
ObjectiveAction.ADD,
Component.text("sidebar"),
ScoreType.INTEGER,
null
)
);
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "sidebar")
);
assertNextPacket(() -> {
var packet = new SetDisplayObjectivePacket();
packet.setObjectiveId("0");
packet.setDisplayName("sidebar");
packet.setCriteria("dummy");
packet.setDisplaySlot("sidebar");
packet.setSortOrder(1);
return packet;
}, context);
// Now they're going to create a bunch of teams and add players to those teams in a very inefficient way.
// Presumably this is a leftover from an old system, as these don't seem to do anything but hide their nametags.
// For which you could just use a single team.
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0]));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.DARK_GRAY));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.DARK_GRAY));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.DARK_GRAY));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.DARK_GRAY));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.DARK_GRAY));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.DARK_GRAY));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.DARK_GRAY));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "A_Player" }));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0]));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.LIGHT_PURPLE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", TeamAction.ADD_PLAYER, new String[] { "B_Player" }));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "C_Player" }));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "D_Player" }));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", TeamAction.ADD_PLAYER, new String[] { "E_Player" }));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "F_Player" }));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "G_Player" }));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0]));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.BLUE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.BLUE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.BLUE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.BLUE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.BLUE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.BLUE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.BLUE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", TeamAction.ADD_PLAYER, new String[] { "H_Player" }));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "I_Player" }));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0]));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.AQUA));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.AQUA));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", TeamAction.ADD_PLAYER, new String[] { "J_Player" }));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "K_Player" }));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0]));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.AQUA));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.AQUA));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", TeamAction.ADD_PLAYER, new String[] { "L_Player" }));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", TeamAction.ADD_PLAYER, new String[] { "M_Player" }));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "N_Player" }));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0]));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.LIGHT_PURPLE));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", TeamAction.ADD_PLAYER, new String[] { "O_Player" }));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "P_Player" }));
context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "Q_Player" }));
assertNoNextPacket(context);
// Now that those teams are created and people added to it, they set the final sidebar name and add the lines to it.
// They're also not doing this efficiently, because they don't add the players when the team is created.
// Instead, they send an additional packet.
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"sidebar",
ObjectiveAction.UPDATE,
Component.empty()
.append(Component.text(
"CubeCraft", Style.style(NamedTextColor.WHITE, TextDecoration.BOLD))),
ScoreType.INTEGER,
null));
assertNextPacket(
() -> {
var packet = new RemoveObjectivePacket();
packet.setObjectiveId("0");
return packet;
},
context);
assertNextPacket(
() -> {
var packet = new SetDisplayObjectivePacket();
packet.setObjectiveId("0");
packet.setDisplayName("§f§lCubeCraft");
packet.setCriteria("dummy");
packet.setDisplaySlot("sidebar");
packet.setSortOrder(1);
return packet;
},
context);
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-0",
Component.text("SB_l-0"),
Component.empty(),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET,
new String[0]));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket("SB_l-0", TeamAction.ADD_PLAYER, new String[] {"§0§0"}));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-0",
Component.text("SB_l-0"),
Component.empty().append(Component.text("", Style.style(NamedTextColor.BLACK))),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET));
assertNoNextPacket(context);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§0", "sidebar", 10));
assertNextPacket(
() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(1, "0", 10, "§r§0§0§r")));
return packet;
},
context);
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-1",
Component.text("SB_l-1"),
Component.empty(),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET,
new String[0]));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket("SB_l-1", TeamAction.ADD_PLAYER, new String[] {"§0§1"}));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-1",
Component.text("SB_l-1"),
Component.empty()
.append(Component.textOfChildren(
Component.text("User: ", TextColor.color(0x3aa9ff)),
Component.text("Tim203", NamedTextColor.WHITE))),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET));
assertNoNextPacket(context);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§1", "sidebar", 9));
assertNextPacket(
() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(2, "0", 9, "§bUser: §r§fTim203§r§0§1§r")));
return packet;
},
context);
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-2",
Component.text("SB_l-2"),
Component.empty(),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET,
new String[0]));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket("SB_l-2", TeamAction.ADD_PLAYER, new String[] {"§0§2"}));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-2",
Component.text("SB_l-2"),
Component.empty()
.append(Component.textOfChildren(
Component.text("Rank: ", TextColor.color(0x3aa9ff)),
Component.text("\uE1AB ", NamedTextColor.WHITE))),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET));
assertNoNextPacket(context);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§2", "sidebar", 8));
assertNextPacket(
() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(3, "0", 8, "§bRank: §r§f\uE1AB §r§0§2§r")));
return packet;
},
context);
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-3",
Component.text("SB_l-3"),
Component.empty(),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET,
new String[0]));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket("SB_l-3", TeamAction.ADD_PLAYER, new String[] {"§0§3"}));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-3",
Component.text("SB_l-3"),
Component.empty(),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET));
assertNoNextPacket(context);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§3", "sidebar", 7));
assertNextPacket(
() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(4, "0", 7, "§r§0§3§r")));
return packet;
},
context);
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-4",
Component.text("SB_l-4"),
Component.empty(),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET,
new String[0]));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket("SB_l-4", TeamAction.ADD_PLAYER, new String[] {"§0§4"}));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-4",
Component.text("SB_l-4"),
Component.empty(),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET));
assertNoNextPacket(context);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§4", "sidebar", 6));
assertNextPacket(
() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(5, "0", 6, "§r§0§4§r")));
return packet;
},
context);
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-5",
Component.text("SB_l-5"),
Component.empty(),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET,
new String[0]));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket("SB_l-5", TeamAction.ADD_PLAYER, new String[] {"§0§5"}));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-5",
Component.text("SB_l-5"),
Component.empty().append(Component.text("", NamedTextColor.DARK_BLUE)),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET));
assertNoNextPacket(context);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§5", "sidebar", 5));
assertNextPacket(
() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(6, "0", 5, "§r§0§5§r")));
return packet;
},
context);
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-6",
Component.text("SB_l-6"),
Component.empty(),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET,
new String[0]));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket("SB_l-6", TeamAction.ADD_PLAYER, new String[] {"§0§6"}));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-6",
Component.text("SB_l-6"),
Component.empty()
.append(Component.textOfChildren(
Component.text("Lobby: ", TextColor.color(0x3aa9ff)),
Component.text("EU #10", NamedTextColor.WHITE))),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET));
assertNoNextPacket(context);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§6", "sidebar", 4));
assertNextPacket(
() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(7, "0", 4, "§bLobby: §r§fEU #10§r§0§6§r")));
return packet;
},
context);
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-7",
Component.text("SB_l-7"),
Component.empty(),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET,
new String[0]));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket("SB_l-7", TeamAction.ADD_PLAYER, new String[] {"§0§7"}));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-7",
Component.text("SB_l-7"),
Component.empty()
.append(Component.textOfChildren(
Component.text("Players: ", TextColor.color(0x3aa9ff)),
Component.text("783", NamedTextColor.WHITE))),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET));
assertNoNextPacket(context);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§7", "sidebar", 3));
assertNextPacket(
() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(8, "0", 3, "§bPlayers: §r§f783§r§0§7§r")));
return packet;
},
context);
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-8",
Component.text("SB_l-8"),
Component.empty(),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET,
new String[0]));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket("SB_l-8", TeamAction.ADD_PLAYER, new String[] {"§0§8"}));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-8",
Component.text("SB_l-8"),
Component.empty().append(Component.text("", NamedTextColor.DARK_GREEN)),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET));
assertNoNextPacket(context);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§8", "sidebar", 2));
assertNextPacket(
() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(9, "0", 2, "§r§0§8§r")));
return packet;
},
context);
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-9",
Component.text("SB_l-9"),
Component.empty(),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET,
new String[0]));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket("SB_l-9", TeamAction.ADD_PLAYER, new String[] {"§0§9"}));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-9",
Component.text("SB_l-9"),
Component.empty().append(Component.text("24/09/24 (g2208)", TextColor.color(0x777777))),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET));
assertNoNextPacket(context);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§9", "sidebar", 1));
assertNextPacket(
() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(10, "0", 1, "§824/09/24 (g2208)§r§0§9§r")));
return packet;
},
context);
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-10",
Component.text("SB_l-10"),
Component.empty(),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET,
new String[0]));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket("SB_l-10", TeamAction.ADD_PLAYER, new String[] {"§0§a"}));
context.translate(
setTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"SB_l-10",
Component.text("SB_l-10"),
Component.empty().append(Component.text("play.cubecraft.net", NamedTextColor.GOLD)),
Component.empty(),
true,
true,
NameTagVisibility.ALWAYS,
CollisionRule.ALWAYS,
TeamColor.RESET));
assertNoNextPacket(context);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§a", "sidebar", 0));
assertNextPacket(
() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(11, "0", 0, "§6play.cubecraft.net§r§0§a§r")));
return packet;
},
context);
// after this we get a ClientboundPlayerInfoUpdatePacket with the action UPDATE_DISPLAY_NAME,
// but that one is only shown in the tablist so we don't have to handle that.
// And after that we get each player's ClientboundPlayerInfoUpdatePacket with also a UPDATE_DISPLAY_NAME,
// which is also not interesting for us.
// CubeCraft seems to use two armor stands per player: 1 for the rank badge and 1 for the player name.
// So the only thing we have to verify is that the nametag is hidden
mockAndAddPlayerEntity(context, "A_Player", 2);
assertNextPacket(
() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(2);
packet.getMetadata().put(EntityDataTypes.NAME, "");
return packet;
},
context);
mockAndAddPlayerEntity(context, "B_Player", 3);
assertNextPacket(
() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(3);
packet.getMetadata().put(EntityDataTypes.NAME, "");
return packet;
},
context);
mockAndAddPlayerEntity(context, "E_Player", 4);
assertNextPacket(
() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(4);
packet.getMetadata().put(EntityDataTypes.NAME, "");
return packet;
},
context);
mockAndAddPlayerEntity(context, "H_Player", 5);
assertNextPacket(
() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(5);
packet.getMetadata().put(EntityDataTypes.NAME, "");
return packet;
},
context);
mockAndAddPlayerEntity(context, "J_Player", 6);
assertNextPacket(
() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(6);
packet.getMetadata().put(EntityDataTypes.NAME, "");
return packet;
},
context);
mockAndAddPlayerEntity(context, "K_Player", 7);
assertNextPacket(
() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(7);
packet.getMetadata().put(EntityDataTypes.NAME, "");
return packet;
},
context);
mockAndAddPlayerEntity(context, "L_Player", 8);
assertNextPacket(
() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(8);
packet.getMetadata().put(EntityDataTypes.NAME, "");
return packet;
},
context);
mockAndAddPlayerEntity(context, "O_Player", 9);
assertNextPacket(
() -> {
var packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(9);
packet.getMetadata().put(EntityDataTypes.NAME, "");
return packet;
},
context);
});
}
}

View File

@@ -0,0 +1,218 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.scoreboard.network.sidebar;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
import java.util.List;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextDecoration;
import org.cloudburstmc.protocol.bedrock.data.ScoreInfo;
import org.cloudburstmc.protocol.bedrock.packet.RemoveObjectivePacket;
import org.cloudburstmc.protocol.bedrock.packet.SetDisplayObjectivePacket;
import org.cloudburstmc.protocol.bedrock.packet.SetScorePacket;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetDisplayObjectiveTranslator;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetObjectiveTranslator;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetScoreTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetDisplayObjectivePacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket;
import org.junit.jupiter.api.Test;
/*
Identical to playerlist
*/
public class BasicSidebarScoreboardTests {
@Test
void displayAndRemove() {
mockContextScoreboard(context -> {
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective",
ObjectiveAction.ADD,
Component.text("objective"),
ScoreType.INTEGER,
null
)
);
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "objective")
);
assertNextPacket(() -> {
var packet = new SetDisplayObjectivePacket();
packet.setObjectiveId("0");
packet.setDisplayName("objective");
packet.setCriteria("dummy");
packet.setDisplaySlot("list");
packet.setSortOrder(1);
return packet;
}, context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "")
);
assertNextPacket(() -> {
var packet = new RemoveObjectivePacket();
packet.setObjectiveId("0");
return packet;
}, context);
});
}
@Test
void displayNameColors() {
mockContextScoreboard(context -> {
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective",
ObjectiveAction.ADD,
Component.text("objective", Style.style(NamedTextColor.AQUA, TextDecoration.BOLD)),
ScoreType.INTEGER,
null
)
);
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective")
);
assertNextPacket(() -> {
var packet = new SetDisplayObjectivePacket();
packet.setObjectiveId("0");
packet.setDisplayName("§b§lobjective");
packet.setCriteria("dummy");
packet.setDisplaySlot("sidebar");
packet.setSortOrder(1);
return packet;
}, context);
});
}
@Test
void override() {
mockContextScoreboard(context -> {
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
var setScoreTranslator = new JavaSetScoreTranslator();
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective1",
ObjectiveAction.ADD,
Component.text("objective1"),
ScoreType.INTEGER,
null
)
);
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective2",
ObjectiveAction.ADD,
Component.text("objective2"),
ScoreType.INTEGER,
null
)
);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("Tim203", "objective1", 1));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("Tim203", "objective2", 2));
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective2")
);
assertNextPacket(() -> {
var packet = new SetDisplayObjectivePacket();
packet.setObjectiveId("0");
packet.setDisplayName("objective2");
packet.setCriteria("dummy");
packet.setDisplaySlot("sidebar");
packet.setSortOrder(1);
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(1, "0", 2, "Tim203")));
return packet;
}, context);
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective1")
);
assertNextPacket(() -> {
var packet = new RemoveObjectivePacket();
packet.setObjectiveId("0");
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetDisplayObjectivePacket();
packet.setObjectiveId("2");
packet.setDisplayName("objective1");
packet.setCriteria("dummy");
packet.setDisplaySlot("sidebar");
packet.setSortOrder(1);
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(3, "2", 1, "Tim203")));
return packet;
}, context);
});
}
}

View File

@@ -0,0 +1,533 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.scoreboard.network.sidebar;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
import java.util.List;
import net.kyori.adventure.text.Component;
import org.cloudburstmc.protocol.bedrock.data.ScoreInfo;
import org.cloudburstmc.protocol.bedrock.packet.SetDisplayObjectivePacket;
import org.cloudburstmc.protocol.bedrock.packet.SetScorePacket;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaResetScorePacket;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetDisplayObjectiveTranslator;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetObjectiveTranslator;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetPlayerTeamTranslator;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetScoreTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.CollisionRule;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.NameTagVisibility;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundResetScorePacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetDisplayObjectivePacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetPlayerTeamPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket;
import org.junit.jupiter.api.Test;
public class OrderAndLimitSidebarScoreboardTests {
@Test
void aboveDisplayLimit() {
mockContextScoreboard(context -> {
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
var setScoreTranslator = new JavaSetScoreTranslator();
var resetScoreTranslator = new JavaResetScorePacket();
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective",
ObjectiveAction.ADD,
Component.text("objective"),
ScoreType.INTEGER,
null
)
);
// some are in an odd order to make sure that there is no bias for which score is send first,
// and to make sure that the score value also doesn't influence the order
context.translate(setScoreTranslator, new ClientboundSetScorePacket("a", "objective", 1));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("b", "objective", 2));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("c", "objective", 3));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("d", "objective", 5));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("e", "objective", 4));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("f", "objective", 6));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("g", "objective", 9));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("h", "objective", 8));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("i", "objective", 7));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("p", "objective", 10));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("o", "objective", 11));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("n", "objective", 12));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("m", "objective", 13));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("k", "objective", 14));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("l", "objective", 15));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("j", "objective", 16));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("q", "objective", 17));
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective")
);
assertNextPacket(() -> {
var packet = new SetDisplayObjectivePacket();
packet.setObjectiveId("0");
packet.setDisplayName("objective");
packet.setCriteria("dummy");
packet.setDisplaySlot("sidebar");
packet.setSortOrder(1);
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(
new ScoreInfo(1, "0", 17, "q"),
new ScoreInfo(2, "0", 16, "j"),
new ScoreInfo(3, "0", 15, "l"),
new ScoreInfo(4, "0", 14, "k"),
new ScoreInfo(5, "0", 13, "m"),
new ScoreInfo(6, "0", 12, "n"),
new ScoreInfo(7, "0", 11, "o"),
new ScoreInfo(8, "0", 10, "p"),
new ScoreInfo(9, "0", 9, "g"),
new ScoreInfo(10, "0", 8, "h"),
new ScoreInfo(11, "0", 7, "i"),
new ScoreInfo(12, "0", 6, "f"),
new ScoreInfo(13, "0", 5, "d"),
new ScoreInfo(14, "0", 4, "e"),
new ScoreInfo(15, "0", 3, "c")
));
return packet;
}, context);
assertNoNextPacket(context);
// remove a score
context.translate(
resetScoreTranslator,
new ClientboundResetScorePacket("m", "objective")
);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.REMOVE);
packet.setInfos(List.of(new ScoreInfo(5, "0", 13, "m")));
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(16, "0", 2, "b")));
return packet;
}, context);
// add a score
context.translate(
setScoreTranslator,
new ClientboundSetScorePacket("aa", "objective", 13)
);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.REMOVE);
packet.setInfos(List.of(new ScoreInfo(16, "0", 2, "b")));
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(17, "0", 13, "aa")));
return packet;
}, context);
// add score with same score value (after)
context.translate(
setScoreTranslator,
new ClientboundSetScorePacket("ga", "objective", 9)
);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.REMOVE);
packet.setInfos(List.of(
new ScoreInfo(15, "0", 3, "c"),
new ScoreInfo(9, "0", 9, "§0§rg")
));
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(
new ScoreInfo(9, "0", 9, "§0§rg"),
new ScoreInfo(18, "0", 9, "§1§rga")
));
return packet;
}, context);
// add another score with same score value (before all)
context.translate(
setScoreTranslator,
new ClientboundSetScorePacket("ag", "objective", 9)
);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.REMOVE);
packet.setInfos(List.of(
new ScoreInfo(14, "0", 4, "e"),
new ScoreInfo(9, "0", 9, "§1§rg"),
new ScoreInfo(18, "0", 9, "§2§rga")
));
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(
new ScoreInfo(19, "0", 9, "§0§rag"),
new ScoreInfo(9, "0", 9, "§1§rg"),
new ScoreInfo(18, "0", 9, "§2§rga")
));
return packet;
}, context);
// remove score with same value
context.translate(
resetScoreTranslator,
new ClientboundResetScorePacket("g", "objective")
);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.REMOVE);
packet.setInfos(List.of(
new ScoreInfo(9, "0", 9, "§1§rg"),
new ScoreInfo(18, "0", 9, "§1§rga")
));
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(
new ScoreInfo(18, "0", 9, "§1§rga"),
new ScoreInfo(20, "0", 4, "e")
));
return packet;
}, context);
// remove the other score with the same value
context.translate(
resetScoreTranslator,
new ClientboundResetScorePacket("ga", "objective")
);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.REMOVE);
packet.setInfos(List.of(
new ScoreInfo(18, "0", 9, "§1§rga"),
new ScoreInfo(19, "0", 9, "ag")
));
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(
new ScoreInfo(19, "0", 9, "ag"),
new ScoreInfo(21, "0", 3, "c")
));
return packet;
}, context);
});
}
@Test
void aboveDisplayLimitWithTeam() {
mockContextScoreboard(context -> {
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
var setScoreTranslator = new JavaSetScoreTranslator();
var resetScoreTranslator = new JavaResetScorePacket();
var setPlayerTeamTranslator = new JavaSetPlayerTeamTranslator();
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective",
ObjectiveAction.ADD,
Component.text("objective"),
ScoreType.INTEGER,
null
)
);
// some are in an odd order to make sure that there is no bias for which score is send first,
// and to make sure that the score value also doesn't influence the order
context.translate(setScoreTranslator, new ClientboundSetScorePacket("a", "objective", 1));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("b", "objective", 2));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("c", "objective", 3));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("d", "objective", 5));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("e", "objective", 4));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("f", "objective", 6));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("g", "objective", 9));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("h", "objective", 8));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("i", "objective", 7));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("p", "objective", 10));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("o", "objective", 11));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("n", "objective", 12));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("m", "objective", 13));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("k", "objective", 14));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("l", "objective", 15));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("j", "objective", 16));
context.translate(setScoreTranslator, new ClientboundSetScorePacket("q", "objective", 17));
context.translate(
setPlayerTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"team1",
Component.text("displayName"),
Component.text("prefix"),
Component.text("suffix"),
false,
false,
NameTagVisibility.ALWAYS,
CollisionRule.NEVER,
TeamColor.DARK_RED,
new String[]{ "f", "o" }
)
);
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective")
);
assertNextPacket(() -> {
var packet = new SetDisplayObjectivePacket();
packet.setObjectiveId("0");
packet.setDisplayName("objective");
packet.setCriteria("dummy");
packet.setDisplaySlot("sidebar");
packet.setSortOrder(1);
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(
new ScoreInfo(1, "0", 17, "q"),
new ScoreInfo(2, "0", 16, "j"),
new ScoreInfo(3, "0", 15, "l"),
new ScoreInfo(4, "0", 14, "k"),
new ScoreInfo(5, "0", 13, "m"),
new ScoreInfo(6, "0", 12, "n"),
new ScoreInfo(7, "0", 11, "§4prefix§r§4o§r§4suffix"),
new ScoreInfo(8, "0", 10, "p"),
new ScoreInfo(9, "0", 9, "g"),
new ScoreInfo(10, "0", 8, "h"),
new ScoreInfo(11, "0", 7, "i"),
new ScoreInfo(12, "0", 6, "§4prefix§r§4f§r§4suffix"),
new ScoreInfo(13, "0", 5, "d"),
new ScoreInfo(14, "0", 4, "e"),
new ScoreInfo(15, "0", 3, "c")
));
return packet;
}, context);
assertNoNextPacket(context);
// remove a score
context.translate(
resetScoreTranslator,
new ClientboundResetScorePacket("m", "objective")
);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.REMOVE);
packet.setInfos(List.of(new ScoreInfo(5, "0", 13, "m")));
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(16, "0", 2, "b")));
return packet;
}, context);
// add a score
context.translate(
setScoreTranslator,
new ClientboundSetScorePacket("aa", "objective", 13)
);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.REMOVE);
packet.setInfos(List.of(new ScoreInfo(16, "0", 2, "b")));
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(17, "0", 13, "aa")));
return packet;
}, context);
// add some teams for the upcoming score adds
context.translate(
setPlayerTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"team2",
Component.text("displayName"),
Component.text("prefix"),
Component.text("suffix"),
false,
false,
NameTagVisibility.ALWAYS,
CollisionRule.NEVER,
TeamColor.DARK_AQUA,
new String[]{ "oa" }
)
);
context.translate(
setPlayerTeamTranslator,
new ClientboundSetPlayerTeamPacket(
"team3",
Component.text("displayName"),
Component.text("prefix"),
Component.text("suffix"),
false,
false,
NameTagVisibility.ALWAYS,
CollisionRule.NEVER,
TeamColor.DARK_PURPLE,
new String[]{ "ao" }
)
);
assertNoNextPacket(context);
// add a score that on Java should be after 'o', but would be before on Bedrock without manual order
// due to the team color
context.translate(
setScoreTranslator,
new ClientboundSetScorePacket("oa", "objective", 11)
);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.REMOVE);
packet.setInfos(List.of(
new ScoreInfo(15, "0", 3, "c"),
new ScoreInfo(7, "0", 11, "§0§r§4prefix§r§4o§r§4suffix")
));
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(
new ScoreInfo(7, "0", 11, "§0§r§4prefix§r§4o§r§4suffix"),
new ScoreInfo(18, "0", 11, "§1§r§3prefix§r§3oa§r§3suffix")
));
return packet;
}, context);
// add a score that on Java should be before 'o', but would be after on Bedrock without manual order
// due to the team color
context.translate(
setScoreTranslator,
new ClientboundSetScorePacket("ao", "objective", 11)
);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.REMOVE);
packet.setInfos(List.of(
new ScoreInfo(14, "0", 4, "e"),
new ScoreInfo(7, "0", 11, "§1§r§4prefix§r§4o§r§4suffix"),
new ScoreInfo(18, "0", 11, "§2§r§3prefix§r§3oa§r§3suffix")
));
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(
new ScoreInfo(19, "0", 11, "§0§r§5prefix§r§5ao§r§5suffix"),
new ScoreInfo(7, "0", 11, "§1§r§4prefix§r§4o§r§4suffix"),
new ScoreInfo(18, "0", 11, "§2§r§3prefix§r§3oa§r§3suffix")
));
return packet;
}, context);
// remove original 'o' score
context.translate(
resetScoreTranslator,
new ClientboundResetScorePacket("o", "objective")
);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.REMOVE);
packet.setInfos(List.of(
new ScoreInfo(7, "0", 11, "§1§r§4prefix§r§4o§r§4suffix"),
new ScoreInfo(18, "0", 11, "§1§r§3prefix§r§3oa§r§3suffix")
));
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(
new ScoreInfo(18, "0", 11, "§1§r§3prefix§r§3oa§r§3suffix"),
new ScoreInfo(20, "0", 4, "e")
));
return packet;
}, context);
// remove the other score with the same value as 'o'
context.translate(
resetScoreTranslator,
new ClientboundResetScorePacket("oa", "objective")
);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.REMOVE);
packet.setInfos(List.of(
new ScoreInfo(18, "0", 11, "§1§r§3prefix§r§3oa§r§3suffix"),
new ScoreInfo(19, "0", 11, "§5prefix§r§5ao§r§5suffix")
));
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(
new ScoreInfo(19, "0", 11, "§5prefix§r§5ao§r§5suffix"),
new ScoreInfo(21, "0", 3, "c")
));
return packet;
}, context);
});
}
}

View File

@@ -0,0 +1,265 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.scoreboard.network.sidebar;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
import java.util.List;
import net.kyori.adventure.text.Component;
import org.cloudburstmc.protocol.bedrock.data.ScoreInfo;
import org.cloudburstmc.protocol.bedrock.packet.SetDisplayObjectivePacket;
import org.cloudburstmc.protocol.bedrock.packet.SetScorePacket;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetDisplayObjectiveTranslator;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetObjectiveTranslator;
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetScoreTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetDisplayObjectivePacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket;
import org.junit.jupiter.api.Test;
public class VanillaSidebarScoreboardTests {
@Test
void displayAndAddScore() {
mockContextScoreboard(context -> {
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
var setScoreTranslator = new JavaSetScoreTranslator();
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective",
ObjectiveAction.ADD,
Component.text("objective"),
ScoreType.INTEGER,
null
)
);
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective")
);
assertNextPacket(() -> {
var packet = new SetDisplayObjectivePacket();
packet.setObjectiveId("0");
packet.setDisplayName("objective");
packet.setCriteria("dummy");
packet.setDisplaySlot("sidebar");
packet.setSortOrder(1);
return packet;
}, context);
assertNoNextPacket(context);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("owner", "objective", 1));
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "owner")));
return packet;
}, context);
});
}
@Test
void displayAndChangeScoreValue() {
mockContextScoreboard(context -> {
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
var setScoreTranslator = new JavaSetScoreTranslator();
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective",
ObjectiveAction.ADD,
Component.text("objective"),
ScoreType.INTEGER,
null
)
);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("owner", "objective", 1));
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective")
);
assertNextPacket(() -> {
var packet = new SetDisplayObjectivePacket();
packet.setObjectiveId("0");
packet.setDisplayName("objective");
packet.setCriteria("dummy");
packet.setDisplaySlot("sidebar");
packet.setSortOrder(1);
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "owner")));
return packet;
}, context);
assertNoNextPacket(context);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("owner", "objective", 2));
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(1, "0", 2, "owner")));
return packet;
}, context);
});
}
@Test
void displayAndChangeScoreDisplayName() {
// this ensures that MCPE-143063 is properly handled
mockContextScoreboard(context -> {
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
var setScoreTranslator = new JavaSetScoreTranslator();
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective",
ObjectiveAction.ADD,
Component.text("objective"),
ScoreType.INTEGER,
null
)
);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("owner", "objective", 1));
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective")
);
assertNextPacket(() -> {
var packet = new SetDisplayObjectivePacket();
packet.setObjectiveId("0");
packet.setDisplayName("objective");
packet.setCriteria("dummy");
packet.setDisplaySlot("sidebar");
packet.setSortOrder(1);
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "owner")));
return packet;
}, context);
assertNoNextPacket(context);
context.translate(
setScoreTranslator,
new ClientboundSetScorePacket("owner", "objective", 1).withDisplay(Component.text("hi"))
);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.REMOVE);
packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "hi")));
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "hi")));
return packet;
}, context);
});
}
@Test
void displayAndChangeScoreDisplayNameAndValue() {
// this ensures that MCPE-143063 is properly handled
mockContextScoreboard(context -> {
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
var setScoreTranslator = new JavaSetScoreTranslator();
context.translate(
setObjectiveTranslator,
new ClientboundSetObjectivePacket(
"objective",
ObjectiveAction.ADD,
Component.text("objective"),
ScoreType.INTEGER,
null
)
);
context.translate(setScoreTranslator, new ClientboundSetScorePacket("owner", "objective", 1));
assertNoNextPacket(context);
context.translate(
setDisplayObjectiveTranslator,
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective")
);
assertNextPacket(() -> {
var packet = new SetDisplayObjectivePacket();
packet.setObjectiveId("0");
packet.setDisplayName("objective");
packet.setCriteria("dummy");
packet.setDisplaySlot("sidebar");
packet.setSortOrder(1);
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "owner")));
return packet;
}, context);
assertNoNextPacket(context);
context.translate(
setScoreTranslator,
new ClientboundSetScorePacket("owner", "objective", 2).withDisplay(Component.text("hi"))
);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.REMOVE);
packet.setInfos(List.of(new ScoreInfo(1, "0", 2, "hi")));
return packet;
}, context);
assertNextPacket(() -> {
var packet = new SetScorePacket();
packet.setAction(SetScorePacket.Action.SET);
packet.setInfos(List.of(new ScoreInfo(1, "0", 2, "hi")));
return packet;
}, context);
});
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.scoreboard.network.util;
import java.util.Collections;
import java.util.function.Supplier;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.junit.jupiter.api.Assertions;
public class AssertUtils {
public static <T> void assertContextEquals(Supplier<? extends T> expected, T actual) {
if (actual == null) {
Assertions.fail("Expected another packet! " + expected.get());
}
Assertions.assertEquals(expected.get(), actual);
}
public static void assertNextPacket(Supplier<BedrockPacket> expected, GeyserMockContext context) {
assertContextEquals(expected, context.nextPacket());
}
public static void assertNoNextPacket(GeyserMockContext context) {
Assertions.assertEquals(
Collections.emptyList(),
context.packets(),
"Expected no remaining packets, got " + context.packetCount()
);
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.scoreboard.network.util;
import org.geysermc.geyser.GeyserLogger;
public class EmptyGeyserLogger implements GeyserLogger {
@Override
public void severe(String message) {
}
@Override
public void severe(String message, Throwable error) {
}
@Override
public void error(String message) {
}
@Override
public void error(String message, Throwable error) {
}
@Override
public void warning(String message) {
}
@Override
public void info(String message) {
}
@Override
public void debug(String message) {
}
@Override
public void setDebug(boolean debug) {
}
@Override
public boolean isDebug() {
return false;
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.scoreboard.network.util;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
public class GeyserMockContext {
private final List<Object> mocksAndSpies = new ArrayList<>();
private final List<Object> storedObjects = new ArrayList<>();
private final List<BedrockPacket> packets = Collections.synchronizedList(new ArrayList<>());
private MockedStatic<GeyserImpl> geyserImplMock;
public static void mockContext(Consumer<GeyserMockContext> geyserContext) {
var context = new GeyserMockContext();
var geyserImpl = context.mock(GeyserImpl.class);
var config = context.mock(GeyserConfiguration.class);
when(config.getScoreboardPacketThreshold()).thenReturn(1_000);
when(geyserImpl.getConfig()).thenReturn(config);
var logger = context.storeObject(new EmptyGeyserLogger());
when(geyserImpl.getLogger()).thenReturn(logger);
try (var mocked = mockStatic(GeyserImpl.class)) {
mocked.when(GeyserImpl::getInstance).thenReturn(geyserImpl);
context.geyserImplMock = mocked;
geyserContext.accept(context);
}
}
public static void mockContext(Runnable runnable) {
mockContext(context -> runnable.run());
}
public <T> T mock(Class<T> type) {
return addMockOrSpy(Mockito.mock(type));
}
public <T> T spy(T object) {
return addMockOrSpy(Mockito.spy(object));
}
private <T> T addMockOrSpy(T mockOrSpy) {
mocksAndSpies.add(mockOrSpy);
return mockOrSpy;
}
public <T> T storeObject(T object) {
storedObjects.add(object);
return object;
}
/**
* Retries the mock or spy that is an instance of the specified type.
* This is only really intended for classes where you only need a single instance of.
*/
public <T> T mockOrSpy(Class<T> type) {
for (Object mock : mocksAndSpies) {
if (type.isInstance(mock)) {
return type.cast(mock);
}
}
return null;
}
public <T> T storedObject(Class<T> type) {
for (Object storedObject : storedObjects) {
if (type.isInstance(storedObject)) {
return type.cast(storedObject);
}
}
return null;
}
public GeyserSession session() {
return mockOrSpy(GeyserSession.class);
}
void addPacket(BedrockPacket packet) {
packets.add(packet);
}
public int packetCount() {
return packets.size();
}
public BedrockPacket nextPacket() {
if (packets.isEmpty()) {
return null;
}
return packets.remove(0);
}
public List<BedrockPacket> packets() {
return Collections.unmodifiableList(packets);
}
public <T> void translate(PacketTranslator<T> translator, T packet) {
translator.translate(session(), packet);
}
public MockedStatic<GeyserImpl> geyserImplMock() {
return geyserImplMock;
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.scoreboard.network.util;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContext.mockContext;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import java.util.UUID;
import java.util.function.Consumer;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataMap;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.EntityCache;
import org.geysermc.geyser.session.cache.WorldCache;
import org.mockito.stubbing.Answer;
public class GeyserMockContextScoreboard {
public static void mockContextScoreboard(Consumer<GeyserMockContext> geyserContext) {
mockContext(context -> {
createSessionSpy(context);
geyserContext.accept(context);
assertNoNextPacket(context);
});
}
private static void createSessionSpy(GeyserMockContext context) {
// GeyserSession has so many dependencies, it's easier to just mock it
var session = context.mock(GeyserSession.class);
when(session.locale()).thenReturn("en_US");
doAnswer((Answer<Void>) invocation -> {
context.addPacket(invocation.getArgument(0, BedrockPacket.class));
return null;
}).when(session).sendUpstreamPacket(any());
// SessionPlayerEntity loads stuff in like blocks, which is not what we want
var playerEntity = context.mock(SessionPlayerEntity.class);
when(playerEntity.getGeyserId()).thenReturn(1L);
when(playerEntity.getUsername()).thenReturn("Tim203");
when(session.getPlayerEntity()).thenReturn(playerEntity);
var entityCache = context.spy(new EntityCache(session));
when(session.getEntityCache()).thenReturn(entityCache);
var worldCache = context.spy(new WorldCache(session));
when(session.getWorldCache()).thenReturn(worldCache);
// disable global scoreboard updater
when(worldCache.increaseAndGetScoreboardPacketsPerSecond()).thenReturn(0);
}
public static PlayerEntity mockAndAddPlayerEntity(GeyserMockContext context, String username, long geyserId) {
var playerEntity = spy(new PlayerEntity(context.session(), geyserId, UUID.randomUUID(), username));
// fake the player being spawned
when(playerEntity.isValid()).thenReturn(true);
var entityCache = context.mockOrSpy(EntityCache.class);
entityCache.addPlayerEntity(playerEntity);
// called when the player spawns
entityCache.cacheEntity(playerEntity);
return playerEntity;
}
}