Add Discord integration for teams and user linking
Introduced database connectivity and Discord role management via MyBatis and JDA. Enhanced `Team` functionality to support Discord roles and integrated player-Discord linking during server join. Updated Gradle build to include required dependencies.
This commit is contained in:
parent
2c80b2d474
commit
c09c55ed7a
|
|
@ -2,6 +2,7 @@ import java.util.Properties
|
|||
|
||||
plugins {
|
||||
id("java")
|
||||
id("com.gradleup.shadow") version "9.0.0-beta4"
|
||||
}
|
||||
|
||||
group = "com.alttd.ctf"
|
||||
|
|
@ -33,6 +34,10 @@ dependencies {
|
|||
// End JSON config dependencies
|
||||
// WorldBorderAPI
|
||||
compileOnly("com.github.yannicklamprecht:worldborderapi:1.210.0:dev")
|
||||
|
||||
//Database
|
||||
implementation("org.mybatis:mybatis:3.5.13")
|
||||
implementation("mysql:mysql-connector-java:8.0.33")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
|
|
@ -43,6 +48,21 @@ tasks.jar {
|
|||
archiveFileName.set("CaptureTheFlag.jar")
|
||||
}
|
||||
|
||||
tasks {
|
||||
shadowJar {
|
||||
archiveFileName.set("CaptureTheFlag.jar")
|
||||
listOf(
|
||||
"org.apache.ibatis"
|
||||
).forEach { relocate(it, "${rootProject.group}.lib.$it") }
|
||||
}
|
||||
|
||||
build {
|
||||
dependsOn(shadowJar)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
val versionPropsFile = file("version.properties")
|
||||
val versionProps = Properties().apply {
|
||||
if (versionPropsFile.exists()) {
|
||||
|
|
@ -70,6 +90,10 @@ tasks.named("build") {
|
|||
dependsOn(incrementBuildNumber)
|
||||
}
|
||||
|
||||
tasks.named("shadowJar") {
|
||||
dependsOn(incrementBuildNumber)
|
||||
}
|
||||
|
||||
tasks.withType<Jar> {
|
||||
manifest {
|
||||
attributes(
|
||||
|
|
|
|||
|
|
@ -77,8 +77,7 @@ public class CreateTeam extends SubCommand {
|
|||
int highestId = gameManager.getMaxTeamId();
|
||||
Team team = new Team(MiniMessage.miniMessage().deserialize(String.format("<color:%s>%s</color>", color, name)),
|
||||
highestId + 1, player.getLocation(), player.getLocation(), player.getLocation(), teamColor,
|
||||
Material.RED_BANNER, "§c");
|
||||
|
||||
Material.RED_BANNER, "§c", -1L);
|
||||
return consumer.apply(team);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,12 @@ abstract class AbstractConfig {
|
|||
return yaml.getInt(path, yaml.getInt(path));
|
||||
}
|
||||
|
||||
public long getLong(String prefix, String path, long def) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getLong(path, yaml.getLong(path));
|
||||
}
|
||||
|
||||
double getDouble(String prefix, String path, double def) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, def);
|
||||
|
|
|
|||
|
|
@ -41,4 +41,36 @@ public class Config extends AbstractConfig{
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class DISCORD {
|
||||
private static final String prefix = "discord.";
|
||||
|
||||
public static long PLAYER_SERVER_ID = 0L;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void load() {
|
||||
PLAYER_SERVER_ID = config.getLong(prefix, "player-server-id", PLAYER_SERVER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class DISCORD_DATABASE {
|
||||
private static final String prefix = "discord-database.";
|
||||
|
||||
public static String HOST = "localhost";
|
||||
public static int PORT = 3306;
|
||||
public static String DATABASE = "discordLink";
|
||||
public static String USERNAME = "username";
|
||||
public static String PASSWORD = "password";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void load() {
|
||||
HOST = config.getString(prefix, "host", HOST);
|
||||
DATABASE = config.getString(prefix, "database", DATABASE);
|
||||
PORT = config.getInt(prefix, "port", PORT);
|
||||
USERNAME = config.getString(prefix, "username", USERNAME);
|
||||
PASSWORD = config.getString(prefix, "password", PASSWORD);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
49
src/main/java/com/alttd/ctf/database/Connections.java
Normal file
49
src/main/java/com/alttd/ctf/database/Connections.java
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package com.alttd.ctf.database;
|
||||
|
||||
import com.alttd.ctf.config.Config;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.datasource.pooled.PooledDataSource;
|
||||
import org.apache.ibatis.mapping.Environment;
|
||||
import org.apache.ibatis.session.Configuration;
|
||||
import org.apache.ibatis.session.SqlSession;
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
|
||||
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Slf4j
|
||||
public class Connections {
|
||||
|
||||
private static SqlSessionFactory discordSqlSessionFactory = null;
|
||||
|
||||
public static void runDiscordQuery(Consumer<SqlSession> consumer) {
|
||||
new Thread(() -> {
|
||||
if (discordSqlSessionFactory == null) {
|
||||
discordSqlSessionFactory = createDiscordSqlSessionFactory();
|
||||
}
|
||||
|
||||
try (SqlSession session = discordSqlSessionFactory.openSession()) {
|
||||
consumer.accept(session);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to run discord query", e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private static SqlSessionFactory createDiscordSqlSessionFactory() {
|
||||
PooledDataSource dataSource = new PooledDataSource();
|
||||
dataSource.setDriver("com.mysql.cj.jdbc.Driver");
|
||||
dataSource.setUrl(String.format("jdbc:mysql://%s:%d/%s", Config.DISCORD_DATABASE.HOST,
|
||||
Config.DISCORD_DATABASE.PORT, Config.DISCORD_DATABASE.DATABASE));
|
||||
dataSource.setUsername(Config.DISCORD_DATABASE.USERNAME);
|
||||
dataSource.setPassword(Config.DISCORD_DATABASE.PASSWORD);
|
||||
|
||||
Environment environment = new Environment("production", new JdbcTransactionFactory(), dataSource);
|
||||
Configuration configuration = new Configuration(environment);
|
||||
configuration.addMapper(DiscordUserMapper.class);
|
||||
|
||||
return new SqlSessionFactoryBuilder().build(configuration);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.alttd.ctf.database;
|
||||
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
public interface DiscordUserMapper {
|
||||
@Select("SELECT discord_id FROM linked_accounts WHERE player_uuid = #{uuid}")
|
||||
Long getDiscordId(String uuid);
|
||||
|
||||
}
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
package com.alttd.ctf.events;
|
||||
|
||||
import com.alttd.ctf.database.Connections;
|
||||
import com.alttd.ctf.database.DiscordUserMapper;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.exceptions.PersistenceException;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.attribute.AttributeInstance;
|
||||
import org.bukkit.entity.Player;
|
||||
|
|
@ -32,17 +35,12 @@ public class OnPlayerOnlineStatus implements Listener {
|
|||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
AttributeInstance maxHealthAttribute = player.getAttribute(Attribute.GENERIC_MAX_HEALTH);
|
||||
if (maxHealthAttribute == null) {
|
||||
log.error("Player does not have max health attribute");
|
||||
return;
|
||||
}
|
||||
maxHealthAttribute.setBaseValue(20);
|
||||
player.setHealth(20);
|
||||
resetPlayer(player);
|
||||
handleRunningGame(player);
|
||||
handleDiscordLink(player);
|
||||
}
|
||||
|
||||
flag.addPlayer(player);
|
||||
player.getInventory().clear();
|
||||
player.updateInventory();
|
||||
private void handleRunningGame(Player player) {
|
||||
Optional<GamePhase> optionalGamePhase = gameManager.getGamePhase();
|
||||
if (optionalGamePhase.isEmpty()) {
|
||||
return;
|
||||
|
|
@ -66,9 +64,41 @@ public class OnPlayerOnlineStatus implements Listener {
|
|||
player.teleportAsync(teamPlayer.getTeam().getSpawnLocation());
|
||||
}
|
||||
|
||||
private void resetPlayer(Player player) {
|
||||
AttributeInstance maxHealthAttribute = player.getAttribute(Attribute.GENERIC_MAX_HEALTH);
|
||||
if (maxHealthAttribute == null) {
|
||||
log.error("Player does not have max health attribute");
|
||||
return;
|
||||
}
|
||||
maxHealthAttribute.setBaseValue(20);
|
||||
player.setHealth(20);
|
||||
|
||||
flag.addPlayer(player);
|
||||
player.getInventory().clear();
|
||||
player.updateInventory();
|
||||
}
|
||||
|
||||
private void handleDiscordLink(Player player) {
|
||||
Connections.runDiscordQuery(sqlSession -> {
|
||||
try {
|
||||
DiscordUserMapper mapper = sqlSession.getMapper(DiscordUserMapper.class);
|
||||
Long discordId = mapper.getDiscordId(player.getUniqueId().toString());
|
||||
if (discordId == null) {
|
||||
log.info("Player {} is not linked", player.getName());
|
||||
return;
|
||||
}
|
||||
log.info("Set discord id to {} for {}", discordId, player.getName());
|
||||
player.setDiscordId(discordId);
|
||||
} catch (PersistenceException e) {
|
||||
log.error("Failed to set discord id for {}", player.getName(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(@NotNull PlayerQuitEvent event) {
|
||||
flag.handleCarrierDeathOrDisconnect(event.getPlayer());
|
||||
public void onPlayerQuit(@NotNull PlayerQuitEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
flag.handleCarrierDeathOrDisconnect(player);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ public class GameManager {
|
|||
}
|
||||
|
||||
public void registerTeam(Team team) {
|
||||
team.registerGameManger(this);
|
||||
teams.put(team.getId(), team);
|
||||
}
|
||||
|
||||
|
|
|
|||
106
src/main/java/com/alttd/ctf/team/DiscordTeam.java
Normal file
106
src/main/java/com/alttd/ctf/team/DiscordTeam.java
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
package com.alttd.ctf.team;
|
||||
|
||||
import com.alttd.ctf.config.Config;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.galaxy.discord.Bot;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.*;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Slf4j
|
||||
public class DiscordTeam {
|
||||
|
||||
private final GameManager gameManager;
|
||||
|
||||
public DiscordTeam(GameManager gameManager) {
|
||||
this.gameManager = gameManager;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface Validate {
|
||||
void apply(Role role, Member member);
|
||||
}
|
||||
|
||||
private void validateModifyRole(Team team, Player player, Validate consumer) {
|
||||
if (!player.isDiscordLinked()) {
|
||||
log.info("{} is not discord linked", player.getName());
|
||||
return;
|
||||
}
|
||||
if (team.getDiscordRole() == -1) {
|
||||
log.warn("No valid discord role set for team {}", team.getId());
|
||||
return;
|
||||
}
|
||||
Bot bot = Bukkit.getBot();
|
||||
if (bot == null) {
|
||||
log.error("Unable to get bot");
|
||||
return;
|
||||
}
|
||||
JDA jda = bot.getJDA();
|
||||
if (jda == null) {
|
||||
log.error("Unable to get JDA");
|
||||
return;
|
||||
}
|
||||
Guild guild = jda.getGuildById(Config.DISCORD.PLAYER_SERVER_ID);
|
||||
if (guild == null) {
|
||||
log.warn("Unable to get guild {}", Config.DISCORD.PLAYER_SERVER_ID);
|
||||
return;
|
||||
}
|
||||
Role role = guild.getRoleById(team.getDiscordRole());
|
||||
if (role == null) {
|
||||
log.warn("Unable to get role {} for team {}", team.getDiscordRole(), team.getId());
|
||||
return;
|
||||
}
|
||||
Member nullableMember = guild.getMemberById(player.getDiscordID());
|
||||
if (nullableMember == null) {
|
||||
guild.retrieveMemberById(player.getDiscordID()).queue(member -> {
|
||||
consumer.apply(role, member);
|
||||
});
|
||||
} else {
|
||||
consumer.apply(role, nullableMember);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeRole(Team team, @NotNull Player player) {
|
||||
validateModifyRole(team, player, (role, member) -> {
|
||||
if (member.isOwner()) {
|
||||
log.info("Unable to remove team role from server owner");
|
||||
return;
|
||||
}
|
||||
member.getGuild().removeRoleFromMember(member, role).queue(ignored -> kickFromVoiceIfNeeded(member));
|
||||
});
|
||||
}
|
||||
|
||||
public void addRole(Team team, Player player) {
|
||||
validateModifyRole(team, player, (role, member) -> {
|
||||
if (member.isOwner()) {
|
||||
log.info("Unable to add team role to server owner");
|
||||
return;
|
||||
}
|
||||
gameManager.getTeams().forEach(otherTeam ->
|
||||
member.getRoles().stream()
|
||||
.filter(otherRole -> otherRole.getIdLong() == otherTeam.getDiscordRole())
|
||||
.forEach(otherRole -> {
|
||||
member.getGuild().removeRoleFromMember(member, otherRole).queue();
|
||||
}));
|
||||
member.getGuild().addRoleToMember(member, role).queue(ignored -> kickFromVoiceIfNeeded(member));
|
||||
});
|
||||
}
|
||||
|
||||
private void kickFromVoiceIfNeeded(@NotNull Member member) {
|
||||
GuildVoiceState voiceState = member.getVoiceState();
|
||||
if (voiceState == null || !voiceState.inAudioChannel()) {
|
||||
return;
|
||||
}
|
||||
AudioChannel channel = voiceState.getChannel();
|
||||
if (channel == null) {
|
||||
return;
|
||||
}
|
||||
if (!member.hasPermission(channel, Permission.VOICE_CONNECT)) {
|
||||
member.getGuild().kickVoiceMember(member).queue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
package com.alttd.ctf.team;
|
||||
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
|
|
@ -23,7 +23,7 @@ import java.util.*;
|
|||
|
||||
@Slf4j
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
public class Team {
|
||||
|
||||
@JsonIgnore
|
||||
|
|
@ -36,7 +36,8 @@ public class Team {
|
|||
private Component name;
|
||||
@JsonProperty("id")
|
||||
@Getter
|
||||
private int id;
|
||||
@NotNull
|
||||
private Integer id;
|
||||
@JsonProperty("spawnLocation")
|
||||
@NotNull
|
||||
@Getter
|
||||
|
|
@ -61,6 +62,16 @@ public class Team {
|
|||
@NotNull
|
||||
@Getter
|
||||
private String legacyTeamColor;
|
||||
@JsonProperty("discordRole")
|
||||
@Getter
|
||||
@NotNull
|
||||
private Long discordRole;
|
||||
@JsonIgnore
|
||||
private DiscordTeam discordTeam;
|
||||
|
||||
public void registerGameManger(GameManager gameManager) {
|
||||
discordTeam = new DiscordTeam(gameManager);
|
||||
}
|
||||
|
||||
public TeamPlayer addPlayer(Player player) {
|
||||
removeFromScoreBoard(player);
|
||||
|
|
@ -68,6 +79,11 @@ public class Team {
|
|||
TeamPlayer teamPlayer = new TeamPlayer(uuid, this);
|
||||
players.put(uuid, teamPlayer);
|
||||
addToScoreboard(player);
|
||||
if (discordTeam != null) {
|
||||
discordTeam.addRole(this, player);
|
||||
} else {
|
||||
log.warn("No discord team set for {} to add role", id);
|
||||
}
|
||||
log.debug("Added player {} to team with id {}", player.getName(), id);
|
||||
return teamPlayer;
|
||||
}
|
||||
|
|
@ -86,9 +102,15 @@ public class Team {
|
|||
public void removePlayer(@NotNull Player player) {
|
||||
removeFromScoreBoard(player);
|
||||
TeamPlayer remove = players.remove(player.getUniqueId());
|
||||
if (remove != null) {
|
||||
log.debug("Removed player {} from team with id {}", player.getName(), id);
|
||||
if (remove == null) {
|
||||
return;
|
||||
}
|
||||
if (discordTeam != null) {
|
||||
discordTeam.removeRole(this, player);
|
||||
} else {
|
||||
log.warn("No discord team set for {} to remove role", id);
|
||||
}
|
||||
log.debug("Removed player {} from team with id {}", player.getName(), id);
|
||||
}
|
||||
|
||||
private void addToScoreboard(Player player) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
#Sat Feb 15 04:08:38 CET 2025
|
||||
buildNumber=55
|
||||
#Sat Feb 15 22:27:11 CET 2025
|
||||
buildNumber=66
|
||||
version=0.1
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user