Skip to content

Commit

Permalink
Day 8
Browse files Browse the repository at this point in the history
Got screenshots (thanks Labymod!) and made a Lunar Client integration.
  • Loading branch information
minus1over12 committed Jun 21, 2024
1 parent 586d33a commit f14e310
Show file tree
Hide file tree
Showing 15 changed files with 236 additions and 63 deletions.
5 changes: 5 additions & 0 deletions .idea/jarRepositories.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

74 changes: 47 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,68 @@ QuadWars is a plugin allowing four teams to compete in a Minecraft world. Each t
quadrant of the world to build and gather resources in undisturbed, and when it's time for
battle can attack in the world they have been preparing.

# Phases

The plugin has three main phases:

1. The pregame phase allows players to pick out teams or an op to assign players to teams. It also
gives the op time to prepare the game worlds if needed.

2. The prep phase sends teams into their quadrants, where they can explore, build, gather resources,
and do anything else the game lets them. The plugin creates a world border for each team to keep
them inside their quadrant. The length of this is up to you, it can be hours if you want a
one-day game or weeks if you want a long-term game.

3. The battle phase is where the teams fight each other. The plugin disables its per team world
borders, and enables PvP and hardcore. Teams can attack enter each other's quadrants and attack
their bases and players. The game ends when only one team is left.

# Features

## Separate Team World Borders

![Each team's world border, shown using Lunar Client's multiple world border feature](images/FourWorldBordersNether.png)

Each team gets their own world border during the prep phase. This keeps teams from interfering with
each other: flying machines won't travel across, cannons can't remotely kill players, and
attempting to stray outside the world border will result in damage that increases as you get
further out.

## Self-Service Team Selection

![The chat bar showing the command `/jointeam`](images/JoinTeamCommand.png)![Message saying that a player was added to the team they selected](images/JoinedTeamMessage.png)

Don't want to spend time assigning players to teams? Players can join a team themselves using the
`/jointeam` command. The option to have teams assigned by an op is available too by denying a
permission.

## Goal Flexibility

![A yellow team player walking with a CTFBuddy flag](images/PlayerWalkingWithFlag.png)
By default, the plugin config will assume that for your battle you will want to do a last team
standing game and switch on hardcore, but it's not required. If you want to do capture the flag
or another style of event that doesn't need hardcore, you can turn it off.

# Things players should know
## Phases

* Players can send messages to just their team using Vanilla's `/teammsg` (aliased to `/tm`)
command.
* Avoid building nether portals near the inner edges of the world border. It is possible for the
game to place the destination portal outside the team's world border, causing a potentially
deadly scenario where getting your items back can be near impossible.
* Vanilla allows players to see their teammates when using invisibility potions and prevents
friendly fire.
* Players can join a team using `/jointeam`. Players connected using Floodgate+Geyser will get a
popup to select a team. Once a team is selected, they can not change it themselves.
![](images/BattlePhaseStartTimer.png)

The plugin has three main phases:

1. The pregame phase allows players to pick out teams or an op to assign players to teams. It also
gives the op time to prepare the game worlds if needed.

2. The prep phase sends teams into their quadrants, where they can explore, build, gather resources,
and do anything else the game lets them. The plugin creates a world border for each team to keep
them inside their quadrant. The length of this is up to you, it can be hours if you want a
one-day game or weeks if you want a long-term game.

3. The battle phase is where the teams fight each other. The plugin disables its per team world
borders, and enables PvP and hardcore. Teams can attack enter each other's quadrants and attack
their bases and players. The game ends when only one team is left.

## Integrations

* *Floodgate:* QuadWars will utilize Bedrock's forms feature to show players a team selection UI
if they do not already have a team. No need to type on a gamepad!
* *Apollo (Lunar Client):* If you have [Apollo](https://modrinth.com/plugin/lunar-client-apollo)
installed, QuadWars can utilize some of the extra features available in Lunar Client. Lunar
allows servers to render more than one world border at a time, meaning QuadWars can render all
four team borders, instead of just the one for your own team. QuadWars will also have Lunar
show a logout warning during the battle phase.
* *Anything using Scoreboard Teams:* QuadWars
uses [Vanilla's Teams](https://minecraft.wiki/w/Scoreboard#Teams), meaning anything else that
can read Scoreboard Teams will work with QuadWars.

# Admin Usage

## Permissions

Admin functions of the plugin are gated by `quadwars.gamemaser`. This gives access to all admin
Admin functions of the plugin are gated by `quadwars.gamemaster`. This gives access to all admin
commands. The player command provided by the plugin, `/jointeam`, is gated by
`quadwars.player.jointeam`, which is granted by default.

Expand Down Expand Up @@ -133,6 +141,18 @@ the [`/spreadplayers`](https://minecraft.wiki/w/Commands/spreadplayers) command
to a random spot within a certain radius using
`spreadplayers <center> <spreadDistance> <maxRange> true @a`

# Things players should know

* Players can send messages to just their team using Vanilla's `/teammsg` (aliased to `/tm`)
command.
* Avoid building nether portals near the inner edges of the world border. It is possible for the
game to place the destination portal outside the team's world border, causing a potentially
deadly scenario where getting your items back can be near impossible.
* Vanilla allows players to see their teammates when using invisibility potions and prevents
friendly fire.
* Players can join a team using `/jointeam`. Players connected using Floodgate+Geyser will get a
popup to select a team. Once a team is selected, they can not change it themselves.

# Compatibility

QuadWars was built for Minecraft: Java Edition version 1.20.6, running Paper or one of its
Expand Down
7 changes: 6 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,18 @@ repositories {
name = "opencollab-snapshot"
url = "https://repo.opencollab.dev/main/"
}
maven {
name = 'lunarclient'
url = 'https://repo.lunarclient.dev'
}
}

dependencies {
compileOnly "io.papermc.paper:paper-api:1.20.6-R0.1-SNAPSHOT"
compileOnly 'org.jetbrains:annotations:24.1.0'
implementation "org.bstats:bstats-bukkit:3.0.2"
compileOnly 'org.geysermc.floodgate:api:2.2.2-SNAPSHOT'

compileOnly 'com.lunarclient:apollo-api:1.1.3'
}

def targetJavaVersion = 21
Expand Down Expand Up @@ -86,6 +90,7 @@ tasks {
hangar("ViaVersion", "5.0.0")
github("PaperMC", "Debuggery", "v1.5.1", "debuggery-bukkit-1.5.1.jar")
modrinth("ctfbuddy", "1.0.1") // Might as well test my other stuff at the same time.
modrinth("lunar-client-apollo", "1.1.3")
}
}
shadowJar {
Expand Down
Binary file added images/BattlePhaseStartTimer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/FourWorldBordersNether.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/JoinTeamCommand.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/JoinedTeamMessage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/PlayerLookingAtWorldBorder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/PlayerWalkingWithFlag.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -93,25 +93,37 @@ public void onPlayerJoin(PlayerJoinEvent event) {
player.setGameMode(GameMode.ADVENTURE);
switch (gameState) {
case PREGAME -> {
player.sendMessage(Component.text(
"The game has not started yet, but you can pick a team with " +
"/jointeam.")
.clickEvent(ClickEvent.suggestCommand(JOINTEAM_COMMAND_SUGGESTION)));
if (Bukkit.getPluginManager().isPluginEnabled(FLOODGATE_NAME)) {
//Cumulus takes 2 ticks to become functional.
player.getScheduler().runDelayed(plugin,
ignored -> FloodgateIntegration.sendTeamForm(player), null, 2);
if (player.hasPermission(QuadWars.JOIN_TEAM_PERMISSION)) {
player.sendMessage(Component.text(
"The game has not started yet, but you can pick a team with " +
"/jointeam.").clickEvent(
ClickEvent.suggestCommand(JOINTEAM_COMMAND_SUGGESTION)));
if (Bukkit.getPluginManager().isPluginEnabled(FLOODGATE_NAME)) {
//Cumulus takes 2 ticks to become functional.
player.getScheduler().runDelayed(plugin,
ignored -> FloodgateIntegration.sendTeamForm(player), null, 2);
}
} else {
player.sendMessage(Component.text(
"The game has not started yet, please ask an op to add you to a " +
"team."));
}
}
case PREP -> {
player.sendMessage(Component.text(
"The game is in the prep phase, but you can still join a" +
" team with /jointeam.")
.clickEvent(ClickEvent.suggestCommand(JOINTEAM_COMMAND_SUGGESTION)));
if (Bukkit.getPluginManager().isPluginEnabled(FLOODGATE_NAME)) {
//Cumulus takes 2 ticks to become functional.
player.getScheduler().runDelayed(plugin,
ignored -> FloodgateIntegration.sendTeamForm(player), null, 2);
if (player.hasPermission(QuadWars.JOIN_TEAM_PERMISSION)) {
player.sendMessage(Component.text(
"The game is in the prep phase, but you can still join a" +
" team with /jointeam.").clickEvent(
ClickEvent.suggestCommand(JOINTEAM_COMMAND_SUGGESTION)));
if (Bukkit.getPluginManager().isPluginEnabled(FLOODGATE_NAME)) {
//Cumulus takes 2 ticks to become functional.
player.getScheduler().runDelayed(plugin,
ignored -> FloodgateIntegration.sendTeamForm(player), null, 2);
}
} else {
player.sendMessage(Component.text(
"The game is in the prep phase, please ask an op to add you to a " +
"team, then teleport you to the correct place."));
}
}
case BATTLE, POST_GAME -> player.sendMessage(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package io.github.minus1over12.quadwars;

import com.lunarclient.apollo.Apollo;
import com.lunarclient.apollo.common.cuboid.Cuboid2D;
import com.lunarclient.apollo.module.border.Border;
import com.lunarclient.apollo.module.border.BorderModule;
import com.lunarclient.apollo.module.serverrule.ServerRuleModule;
import com.lunarclient.apollo.option.Options;
import com.lunarclient.apollo.player.ApolloPlayer;
import com.lunarclient.apollo.recipients.Recipients;
import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;

import java.awt.Color;
import java.util.Arrays;
import java.util.Collection;

/**
* Integrates QuadWars with Lunar Client using their Apollo plugin.
*
* @author War Pigeon
*/
public class LunarClientIntegration implements Listener {


/**
* Sets the world borders for the player for the other teams.
*
* @param player The player to set the borders for.
* @param size The size of the world.
* @param homeQuadrant The quadrant the player's team is in.
* @param ignoredWorldKeys The keys of the worlds to ignore.
*/
static void setWorldBorders(Entity player, double size, Quadrant homeQuadrant,
Collection<NamespacedKey> ignoredWorldKeys) {
World world = player.getWorld();
if (!(world.getEnvironment().equals(World.Environment.THE_END) ||
ignoredWorldKeys.contains(world.getKey()))) {
Apollo.getPlayerManager().getPlayer(player.getUniqueId()).ifPresent(apolloPlayer -> {
BorderModule borderModule = Apollo.getModuleManager().getModule(BorderModule.class);
for (Quadrant quadrant : Arrays.stream(Quadrant.values())
.filter(quadrant -> !quadrant.equals(homeQuadrant)).toList()) {
makeBorder(size, apolloPlayer, quadrant, world, borderModule, true);
}
});
}
}

/**
* Makes a border for the given player in the given quadrant.
*
* @param size The size of the world.
* @param apolloPlayer The player to set the border for.
* @param quadrant The quadrant to set the border in.
* @param world The world to set the border in.
* @param borderModule The border module to use.
* @param cancelEntry whether to cancel entry into the border.
*/
private static void makeBorder(double size, Recipients apolloPlayer, Quadrant quadrant,
World world, BorderModule borderModule, boolean cancelEntry) {
double minCorner = WorldBorderController.AXIS_BUFFER_OFFSET / world.getCoordinateScale();
double maxCorner = size + minCorner;
// https://minecraft.wiki/w/Miscellaneous_colors#World_border
borderModule.displayBorder(apolloPlayer,
Border.builder().id("qw" + quadrant + world.getName()).world(world.getName())
.cancelEntry(cancelEntry).cancelExit(false).canShrinkOrExpand(false)
.color(new Color(0x20, 0xA0, 0xFF))
.bounds(Cuboid2D.builder().minX(quadrant.xSign * minCorner)
.minZ(quadrant.zSign * minCorner).maxX(quadrant.xSign * maxCorner)
.maxZ(quadrant.zSign * maxCorner).build()).build());
}

/**
* Sets the world borders for the game master, allowing travel through the borders.
*
* @param player The player to set the borders for.
* @param size The size of the world.
* @param ignoredWorldKeys The keys of the worlds to ignore.
*/
static void setGameMasterWorldBorders(Entity player, double size,
Collection<NamespacedKey> ignoredWorldKeys) {
World world = player.getWorld();
if (!(world.getEnvironment().equals(World.Environment.THE_END) ||
ignoredWorldKeys.contains(world.getKey()))) {
Apollo.getPlayerManager().getPlayer(player.getUniqueId()).ifPresent(apolloPlayer -> {
BorderModule borderModule = Apollo.getModuleManager().getModule(BorderModule.class);
for (Quadrant quadrant : Quadrant.values()) {
makeBorder(size, apolloPlayer, quadrant, world, borderModule, false);
}
});
}
}

/**
* Changes the client's competitive game option based on the game state.
*
* @param event The event that triggered this method.
*/
@EventHandler
public static void onGameStateChange(GameStateChangeEvent event) {
GameState state = event.getState();
Options options = Apollo.getModuleManager().getModule(ServerRuleModule.class).getOptions();
options.set(ServerRuleModule.COMPETITIVE_GAME, state.equals(GameState.BATTLE));
if (!state.equals(GameState.PREP)) {
for (ApolloPlayer player : Apollo.getPlayerManager().getPlayers()) {
Apollo.getModuleManager().getModule(BorderModule.class).resetBorders(player);
}
}
}
}
Loading

0 comments on commit f14e310

Please sign in to comment.