Add readme
This commit is contained in:
@@ -13,6 +13,7 @@ out/
|
||||
*.iml
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
server
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"java.configuration.updateBuildConfiguration": "interactive",
|
||||
"java.compile.nullAnalysis.mode": "automatic"
|
||||
}
|
||||
+12
-4
@@ -6,21 +6,29 @@ plugins {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://repo.papermc.io/repository/maven-public/")
|
||||
maven("https://repo.plasmoverse.com/releases")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT")
|
||||
// MongoDB driver (will be loaded at runtime via PluginLoader)
|
||||
compileOnly("org.mongodb:mongodb-driver-sync:5.0.1")
|
||||
|
||||
// PlasmoVoice API (optional)
|
||||
compileOnly("su.plo.voice.api:server:2.1.8")
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(21)
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
options.release.set(21)
|
||||
}
|
||||
|
||||
tasks {
|
||||
runServer {
|
||||
// Configure the Minecraft version for our task.
|
||||
// This is the only required configuration besides applying the plugin.
|
||||
// Your plugin's jar (or shadowJar if present) will be used automatically.
|
||||
minecraftVersion("1.21.11")
|
||||
jvmArgs("-Xms2G", "-Xmx2G")
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
rootProject.name = "PixelTalk"
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package git.yawaflua.tech.command;
|
||||
|
||||
import git.yawaflua.tech.database.DatabaseManager;
|
||||
import git.yawaflua.tech.messages.Messages;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import io.papermc.paper.command.brigadier.BasicCommand;
|
||||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ReportCommand implements BasicCommand {
|
||||
|
||||
private final DatabaseManager databaseManager;
|
||||
private final git.yawaflua.tech.voice.VoiceIntegration voiceIntegration;
|
||||
private final Map<UUID, Long> cooldowns = new HashMap<>();
|
||||
private final long cooldownMs;
|
||||
|
||||
public ReportCommand(DatabaseManager databaseManager, FileConfiguration config, git.yawaflua.tech.voice.VoiceIntegration voiceIntegration) {
|
||||
this.databaseManager = databaseManager;
|
||||
this.voiceIntegration = voiceIntegration;
|
||||
this.cooldownMs = config.getLong("report.cooldown-seconds", 60) * 1000L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull CommandSourceStack stack, @NotNull String[] args) {
|
||||
CommandSender sender = stack.getSender();
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage(Messages.parse(Messages.ONLY_PLAYERS));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length < 2) {
|
||||
player.sendMessage(Messages.parse(Messages.REPORT_USAGE));
|
||||
return;
|
||||
}
|
||||
|
||||
UUID uuid = player.getUniqueId();
|
||||
long now = System.currentTimeMillis();
|
||||
long lastReport = cooldowns.getOrDefault(uuid, 0L);
|
||||
|
||||
if (now - lastReport < cooldownMs) {
|
||||
long remaining = (cooldownMs - (now - lastReport)) / 1000;
|
||||
player.sendMessage(Messages.parse(Messages.REPORT_COOLDOWN.replace("<time>", String.valueOf(remaining))));
|
||||
return;
|
||||
}
|
||||
|
||||
Player target = Bukkit.getPlayer(args[0]);
|
||||
if (target == null) {
|
||||
player.sendMessage(Messages.parse(Messages.REPORT_PLAYER_NOT_FOUND));
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder reasonBuilder = new StringBuilder();
|
||||
for (int i = 1; i < args.length; i++) {
|
||||
reasonBuilder.append(args[i]).append(" ");
|
||||
}
|
||||
String reason = reasonBuilder.toString().trim();
|
||||
|
||||
// Log report
|
||||
databaseManager.logReport(uuid, target.getUniqueId(), reason);
|
||||
cooldowns.put(uuid, now);
|
||||
|
||||
if (voiceIntegration != null) {
|
||||
voiceIntegration.dumpAudio(target.getUniqueId(), reason);
|
||||
}
|
||||
|
||||
// Notify OPs
|
||||
String opMsg = Messages.REPORT_NOTIFICATION_OP
|
||||
.replace("<reporter>", player.getName())
|
||||
.replace("<target>", target.getName())
|
||||
.replace("<reason>", reason);
|
||||
|
||||
for (Player op : Bukkit.getOnlinePlayers()) {
|
||||
if (op.isOp() || op.hasPermission("pixeltalk.moderator")) {
|
||||
op.sendMessage(Messages.parse(opMsg));
|
||||
op.playSound(op.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
player.sendMessage(Messages.parse(Messages.REPORT_SENT));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package git.yawaflua.tech.database;
|
||||
|
||||
import com.mongodb.client.MongoClient;
|
||||
import com.mongodb.client.MongoClients;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.client.model.Filters;
|
||||
import com.mongodb.client.model.ReplaceOptions;
|
||||
import git.yawaflua.tech.model.PlayerData;
|
||||
import org.bson.Document;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class DatabaseManager {
|
||||
|
||||
private final Logger logger;
|
||||
private MongoClient mongoClient;
|
||||
private MongoDatabase database;
|
||||
private MongoCollection<Document> playersCollection;
|
||||
private MongoCollection<Document> reportsCollection;
|
||||
|
||||
public DatabaseManager(Logger logger, FileConfiguration config) {
|
||||
this.logger = logger;
|
||||
connect(config);
|
||||
}
|
||||
|
||||
private void connect(FileConfiguration config) {
|
||||
String host = config.getString("database.host", "localhost");
|
||||
int port = config.getInt("database.port", 27017);
|
||||
String dbName = config.getString("database.database", "pixeltalk");
|
||||
|
||||
String username = config.getString("database.username", "");
|
||||
String password = config.getString("database.password", "");
|
||||
|
||||
String uri = "mongodb://";
|
||||
if (username != null && !username.isEmpty() && password != null && !password.isEmpty()) {
|
||||
uri += username + ":" + password + "@";
|
||||
}
|
||||
uri += host + ":" + port;
|
||||
|
||||
mongoClient = MongoClients.create(uri);
|
||||
database = mongoClient.getDatabase(dbName);
|
||||
playersCollection = database.getCollection("players");
|
||||
reportsCollection = database.getCollection("reports");
|
||||
|
||||
logger.info("Connected to MongoDB -> " + dbName);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (mongoClient != null) {
|
||||
mongoClient.close();
|
||||
logger.info("Disconnected from MongoDB.");
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerData getPlayer(UUID uuid) {
|
||||
Document doc = playersCollection.find(Filters.eq("uuid", uuid.toString())).first();
|
||||
if (doc == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new PlayerData(
|
||||
uuid,
|
||||
doc.getString("name"),
|
||||
doc.getString("language"),
|
||||
doc.getString("interests"),
|
||||
doc.getInteger("age", 0),
|
||||
doc.getDouble("points"),
|
||||
doc.getBoolean("registered", false),
|
||||
doc.getLong("firstJoin")
|
||||
);
|
||||
}
|
||||
|
||||
public void savePlayer(PlayerData player) {
|
||||
Document doc = new Document("uuid", player.getUuid().toString())
|
||||
.append("name", player.getName())
|
||||
.append("language", player.getLanguage())
|
||||
.append("interests", player.getInterests())
|
||||
.append("age", player.getAge())
|
||||
.append("points", player.getPoints())
|
||||
.append("registered", player.isRegistered())
|
||||
.append("firstJoin", player.getFirstJoin());
|
||||
|
||||
playersCollection.replaceOne(
|
||||
Filters.eq("uuid", player.getUuid().toString()),
|
||||
doc,
|
||||
new ReplaceOptions().upsert(true)
|
||||
);
|
||||
}
|
||||
|
||||
public void logReport(UUID reporter, UUID target, String reason) {
|
||||
Document report = new Document("reporter", reporter.toString())
|
||||
.append("target", target.toString())
|
||||
.append("reason", reason)
|
||||
.append("timestamp", System.currentTimeMillis())
|
||||
.append("resolved", false);
|
||||
reportsCollection.insertOne(report);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package git.yawaflua.tech.filter;
|
||||
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ProfanityFilter {
|
||||
|
||||
private final List<String> badWords;
|
||||
|
||||
public ProfanityFilter(FileConfiguration config) {
|
||||
this.badWords = config.getStringList("profanity.words");
|
||||
}
|
||||
|
||||
public boolean containsProfanity(String message) {
|
||||
if (message == null || message.isEmpty()) return false;
|
||||
|
||||
String lowerCaseMsg = message.toLowerCase();
|
||||
|
||||
// Simple word checking.
|
||||
// For a more advanced filter, regex boundaries should be used.
|
||||
for (String word : badWords) {
|
||||
if (lowerCaseMsg.contains(word.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String censorMessage(String message) {
|
||||
if (message == null || message.isEmpty()) return message;
|
||||
|
||||
String censored = message;
|
||||
for (String word : badWords) {
|
||||
// Case-insensitive replacement
|
||||
censored = Pattern.compile(Pattern.quote(word), Pattern.CASE_INSENSITIVE).matcher(censored).replaceAll("***");
|
||||
}
|
||||
return censored;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package git.yawaflua.tech.listener;
|
||||
|
||||
import git.yawaflua.tech.filter.ProfanityFilter;
|
||||
import git.yawaflua.tech.messages.Messages;
|
||||
import git.yawaflua.tech.model.PlayerData;
|
||||
import git.yawaflua.tech.questionnaire.QuestionnaireManager;
|
||||
import git.yawaflua.tech.score.ScoreManager;
|
||||
import io.papermc.paper.event.player.AsyncChatEvent;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChatListener implements Listener {
|
||||
|
||||
private final ScoreManager scoreManager;
|
||||
private final QuestionnaireManager questionnaireManager;
|
||||
private final ProfanityFilter profanityFilter;
|
||||
|
||||
private final double chatMessagePoints;
|
||||
private final double profanityPenaltyPoints;
|
||||
|
||||
public ChatListener(ScoreManager scoreManager, QuestionnaireManager questionnaireManager, ProfanityFilter profanityFilter, FileConfiguration config) {
|
||||
this.scoreManager = scoreManager;
|
||||
this.questionnaireManager = questionnaireManager;
|
||||
this.profanityFilter = profanityFilter;
|
||||
|
||||
this.chatMessagePoints = config.getDouble("scores.chat-message", 0.5);
|
||||
this.profanityPenaltyPoints = Math.abs(config.getDouble("scores.profanity-chat", -0.5));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onAsyncChat(AsyncChatEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
UUID uuid = player.getUniqueId();
|
||||
|
||||
String plainMessage = PlainTextComponentSerializer.plainText().serialize(event.message());
|
||||
|
||||
// Check if answering questionnaire
|
||||
if (questionnaireManager.isAnswering(uuid)) {
|
||||
event.setCancelled(true);
|
||||
PlayerData data = scoreManager.getPlayerData(uuid);
|
||||
if (data != null) {
|
||||
questionnaireManager.handleAnswer(player, plainMessage, data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasProfanity = profanityFilter.containsProfanity(plainMessage);
|
||||
|
||||
if (hasProfanity) {
|
||||
String censored = profanityFilter.censorMessage(plainMessage);
|
||||
event.message(Component.text(censored));
|
||||
|
||||
scoreManager.removePoints(uuid, profanityPenaltyPoints);
|
||||
String warnMsg = Messages.PROFANITY_WARNING.replace("<amount>", String.valueOf(profanityPenaltyPoints));
|
||||
player.sendMessage(Messages.parse(warnMsg));
|
||||
|
||||
player.sendActionBar(Messages.parse(Messages.POINTS_REMOVED.replace("<amount>", String.valueOf(profanityPenaltyPoints))));
|
||||
} else {
|
||||
scoreManager.addPoints(uuid, chatMessagePoints);
|
||||
player.sendActionBar(Messages.parse(Messages.POINTS_ADDED.replace("<amount>", String.valueOf(chatMessagePoints))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package git.yawaflua.tech.listener;
|
||||
|
||||
import git.yawaflua.tech.model.PlayerData;
|
||||
import git.yawaflua.tech.questionnaire.QuestionnaireManager;
|
||||
import git.yawaflua.tech.score.ScoreManager;
|
||||
import git.yawaflua.tech.tab.TabManager;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlayerJoinListener implements Listener {
|
||||
|
||||
private final ScoreManager scoreManager;
|
||||
private final QuestionnaireManager questionnaireManager;
|
||||
private final TabManager tabManager;
|
||||
|
||||
public PlayerJoinListener(ScoreManager scoreManager, QuestionnaireManager questionnaireManager, TabManager tabManager) {
|
||||
this.scoreManager = scoreManager;
|
||||
this.questionnaireManager = questionnaireManager;
|
||||
this.tabManager = tabManager;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
UUID uuid = player.getUniqueId();
|
||||
|
||||
// 1. Load player data into cache
|
||||
scoreManager.loadPlayer(uuid);
|
||||
|
||||
// 2. Check registration status
|
||||
PlayerData data = scoreManager.getPlayerData(uuid);
|
||||
if (data != null && !data.isRegistered()) {
|
||||
questionnaireManager.startQuestionnaire(player);
|
||||
}
|
||||
|
||||
// 3. Update their tab
|
||||
tabManager.updatePlayerTab(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package git.yawaflua.tech.listener;
|
||||
|
||||
import git.yawaflua.tech.score.ScoreManager;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
public class PlayerQuitListener implements Listener {
|
||||
|
||||
private final ScoreManager scoreManager;
|
||||
|
||||
public PlayerQuitListener(ScoreManager scoreManager) {
|
||||
this.scoreManager = scoreManager;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
// Save to DB and remove from cache
|
||||
scoreManager.unloadPlayer(event.getPlayer().getUniqueId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package git.yawaflua.tech.listener;
|
||||
|
||||
import git.yawaflua.tech.messages.Messages;
|
||||
import git.yawaflua.tech.score.ScoreManager;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.Date;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
public class PvPListener implements Listener {
|
||||
|
||||
private final ScoreManager scoreManager;
|
||||
private final long cooldownMs;
|
||||
|
||||
private final Map<UUID, Long> lastHitTime = new HashMap<>();
|
||||
|
||||
public PvPListener(ScoreManager scoreManager, FileConfiguration config) {
|
||||
this.scoreManager = scoreManager;
|
||||
this.cooldownMs = config.getLong("pvp.cooldown-seconds", 1) * 1000L;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
|
||||
if (event.getEntity() instanceof Player && event.getDamager() instanceof Player) {
|
||||
Player damager = (Player) event.getDamager();
|
||||
|
||||
UUID damagerId = damager.getUniqueId();
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long lastHit = lastHitTime.getOrDefault(damagerId, 0L);
|
||||
double damage = Math.round(event.getDamage() * 10) / 10.0;
|
||||
if (now - lastHit >= cooldownMs) {
|
||||
lastHitTime.put(damagerId, now);
|
||||
scoreManager.removePoints(damagerId, damage);
|
||||
|
||||
String warnMsg = Messages.PVP_WARNING.replace("<amount>", String.valueOf(damage));
|
||||
damager.sendMessage(Messages.parse(warnMsg));
|
||||
|
||||
damager.sendActionBar(Messages.parse(Messages.POINTS_REMOVED.replace("<amount>", String.valueOf(damage))));
|
||||
|
||||
if (scoreManager.getPlayerData(damagerId).getPoints() <= 0){
|
||||
damager.ban(Messages.parse(Messages.PVP_BAN).toString(), Date.from(Instant.now().plus(7, ChronoUnit.DAYS)), "PvP points reached 0");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package git.yawaflua.tech.messages;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
|
||||
public class Messages {
|
||||
|
||||
private static final MiniMessage MM = MiniMessage.miniMessage();
|
||||
|
||||
public static Component parse(String text) {
|
||||
return MM.deserialize(text);
|
||||
}
|
||||
|
||||
public static final String PREFIX = "<gray>[<color:#ffaa00>PixelTalk</color>]</gray> ";
|
||||
public static final String NO_PERMISSION = PREFIX + "<red>U cant use this command.</red>";
|
||||
public static final String ONLY_PLAYERS = PREFIX + "<red>This command is only for players.</red>";
|
||||
|
||||
public static final String REGISTRATION_START = PREFIX + "<green>Welcome! Let's fill your profile.</green>";
|
||||
public static final String QUESTION_LANGUAGE = PREFIX
|
||||
+ "<aqua>Step 1:</aqua> What language do you prefer? Write it in chat (for example: Русский, English, עברית, العربية).";
|
||||
public static final String QUESTION_INTERESTS = PREFIX
|
||||
+ "<aqua>Step 2:</aqua> Tell us about your interests (favorite games, hobbies, etc.).";
|
||||
public static final String QUESTION_AGE = PREFIX
|
||||
+ "<aqua>Step 3:</aqua> Enter your age (a number between 6 and 18).";
|
||||
|
||||
public static final String ERROR_AGE_FORMAT = PREFIX + "<red>Please enter a valid number.</red>";
|
||||
public static final String ERROR_AGE_RANGE = PREFIX + "<red>Age must be between 6 and 18.</red>";
|
||||
|
||||
public static final String REGISTRATION_COMPLETE = PREFIX
|
||||
+ "<green>Thank you! Your profile has been saved. Now you can communicate.</green>";
|
||||
|
||||
public static final String PROFANITY_WARNING = PREFIX
|
||||
+ "<red>Warning! Profanity is prohibited. You have been deducted <amount> points.</red>";
|
||||
public static final String PVP_WARNING = PREFIX
|
||||
+ "<red>Warning! Attacking other players is prohibited. You have been deducted <amount> points. If you`re points falls to 0 you are take ban</red>";
|
||||
public static final String PVP_BAN = PREFIX + "<red>You have been banned for attacking other players.</red>";
|
||||
public static final String POINTS_ADDED = "<green>+<amount> points</green>";
|
||||
public static final String POINTS_REMOVED = "<red>-<amount> points</red>";
|
||||
|
||||
public static final String REPORT_USAGE = PREFIX + "<yellow>Usage: /report <player> <reason></yellow>";
|
||||
public static final String REPORT_PLAYER_NOT_FOUND = PREFIX + "<red>Player not found.</red>";
|
||||
public static final String REPORT_SENT = PREFIX + "<green>Report sent successfully.</green>";
|
||||
public static final String REPORT_COOLDOWN = PREFIX + "<red>Wait <time> sec. before sending another report.</red>";
|
||||
public static final String REPORT_NOTIFICATION_OP = "<red>[REPORT]</red> <yellow><reporter></yellow> reported <yellow><target></yellow>. Reason: <gray><reason></gray>";
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package git.yawaflua.tech.model;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlayerData {
|
||||
private UUID uuid;
|
||||
private String name;
|
||||
private String language;
|
||||
private String interests;
|
||||
private int age;
|
||||
private double points;
|
||||
private boolean registered;
|
||||
private long firstJoin;
|
||||
|
||||
public PlayerData() {
|
||||
}
|
||||
|
||||
public PlayerData(UUID uuid, String name, String language, String interests, int age, double points, boolean registered, long firstJoin) {
|
||||
this.uuid = uuid;
|
||||
this.name = name;
|
||||
this.language = language;
|
||||
this.interests = interests;
|
||||
this.age = age;
|
||||
this.points = points;
|
||||
this.registered = registered;
|
||||
this.firstJoin = firstJoin;
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
public String getInterests() {
|
||||
return interests;
|
||||
}
|
||||
|
||||
public void setInterests(String interests) {
|
||||
this.interests = interests;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public double getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
public void setPoints(double points) {
|
||||
this.points = points;
|
||||
}
|
||||
|
||||
public boolean isRegistered() {
|
||||
return registered;
|
||||
}
|
||||
|
||||
public void setRegistered(boolean registered) {
|
||||
this.registered = registered;
|
||||
}
|
||||
|
||||
public long getFirstJoin() {
|
||||
return firstJoin;
|
||||
}
|
||||
|
||||
public void setFirstJoin(long firstJoin) {
|
||||
this.firstJoin = firstJoin;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,78 @@
|
||||
package git.yawaflua.tech;
|
||||
|
||||
import git.yawaflua.tech.command.ReportCommand;
|
||||
import git.yawaflua.tech.database.DatabaseManager;
|
||||
import git.yawaflua.tech.filter.ProfanityFilter;
|
||||
import git.yawaflua.tech.listener.ChatListener;
|
||||
import git.yawaflua.tech.listener.PlayerJoinListener;
|
||||
import git.yawaflua.tech.listener.PlayerQuitListener;
|
||||
import git.yawaflua.tech.listener.PvPListener;
|
||||
import git.yawaflua.tech.questionnaire.QuestionnaireManager;
|
||||
import git.yawaflua.tech.score.ScoreManager;
|
||||
import git.yawaflua.tech.tab.TabManager;
|
||||
import git.yawaflua.tech.voice.VoiceIntegration;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public final class pixeltalk extends JavaPlugin {
|
||||
|
||||
private DatabaseManager databaseManager;
|
||||
private ScoreManager scoreManager;
|
||||
private VoiceIntegration voiceIntegration;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
// Plugin startup logic
|
||||
saveDefaultConfig();
|
||||
|
||||
// 1. Initialize Database
|
||||
try {
|
||||
databaseManager = new DatabaseManager(getLogger(), getConfig());
|
||||
} catch (Exception e) {
|
||||
getLogger().severe("Failed to connect to MongoDB! Disabling plugin.");
|
||||
e.printStackTrace();
|
||||
getServer().getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Initialize Managers
|
||||
scoreManager = new ScoreManager(databaseManager, getConfig());
|
||||
QuestionnaireManager questionnaireManager = new QuestionnaireManager(databaseManager);
|
||||
TabManager tabManager = new TabManager(this, scoreManager);
|
||||
ProfanityFilter profanityFilter = new ProfanityFilter(getConfig());
|
||||
|
||||
// 3. Optional Voice Integration
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("PlasmoVoice")) {
|
||||
getLogger().info("PlasmoVoice found! Enabling voice integration.");
|
||||
try {
|
||||
voiceIntegration = new VoiceIntegration(this, scoreManager, getConfig());
|
||||
voiceIntegration.start();
|
||||
} catch (NoClassDefFoundError | Exception e) {
|
||||
getLogger().warning("Failed to hook into PlasmoVoice API. Voice integration disabled.");
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Register Listeners
|
||||
getServer().getPluginManager().registerEvents(new ChatListener(scoreManager, questionnaireManager, profanityFilter, getConfig()), this);
|
||||
getServer().getPluginManager().registerEvents(new PlayerJoinListener(scoreManager, questionnaireManager, tabManager), this);
|
||||
getServer().getPluginManager().registerEvents(new PlayerQuitListener(scoreManager), this);
|
||||
getServer().getPluginManager().registerEvents(new PvPListener(scoreManager, getConfig()), this);
|
||||
|
||||
// 5. Register Commands
|
||||
this.getLifecycleManager().registerEventHandler(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, event -> {
|
||||
event.registrar().register("report", new ReportCommand(databaseManager, getConfig(), voiceIntegration));
|
||||
});
|
||||
|
||||
getLogger().info("PixelTalk enabled successfully!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
// Plugin shutdown logic
|
||||
if (scoreManager != null) {
|
||||
scoreManager.saveAll();
|
||||
}
|
||||
if (databaseManager != null) {
|
||||
databaseManager.close();
|
||||
}
|
||||
getLogger().info("PixelTalk disabled.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,19 @@ package git.yawaflua.tech;
|
||||
import io.papermc.paper.plugin.loader.PluginClasspathBuilder;
|
||||
import io.papermc.paper.plugin.loader.PluginLoader;
|
||||
|
||||
class pixeltalkLoader implements PluginLoader {
|
||||
import io.papermc.paper.plugin.loader.library.impl.MavenLibraryResolver;
|
||||
import org.eclipse.aether.artifact.DefaultArtifact;
|
||||
import org.eclipse.aether.graph.Dependency;
|
||||
import org.eclipse.aether.repository.RemoteRepository;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public class pixeltalkLoader implements PluginLoader {
|
||||
|
||||
@Override
|
||||
public void classloader(final PluginClasspathBuilder builder) {
|
||||
// Add dynamically loaded libraries here
|
||||
MavenLibraryResolver resolver = new MavenLibraryResolver();
|
||||
resolver.addRepository(new RemoteRepository.Builder("central", "default", "https://repo1.maven.org/maven2/").build());
|
||||
resolver.addDependency(new Dependency(new DefaultArtifact("org.mongodb:mongodb-driver-sync:5.0.1"), null));
|
||||
builder.addLibrary(resolver);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package git.yawaflua.tech.questionnaire;
|
||||
|
||||
import git.yawaflua.tech.database.DatabaseManager;
|
||||
import git.yawaflua.tech.messages.Messages;
|
||||
import git.yawaflua.tech.model.PlayerData;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
public class QuestionnaireManager {
|
||||
|
||||
private final DatabaseManager databaseManager;
|
||||
private final ConcurrentMap<UUID, QuestionnaireState> activeQuestionnaires = new ConcurrentHashMap<>();
|
||||
|
||||
public enum State {
|
||||
LANGUAGE,
|
||||
INTERESTS,
|
||||
AGE
|
||||
}
|
||||
|
||||
public static class QuestionnaireState {
|
||||
public State currentState = State.LANGUAGE;
|
||||
}
|
||||
|
||||
public QuestionnaireManager(DatabaseManager databaseManager) {
|
||||
this.databaseManager = databaseManager;
|
||||
}
|
||||
|
||||
public void startQuestionnaire(Player player) {
|
||||
activeQuestionnaires.put(player.getUniqueId(), new QuestionnaireState());
|
||||
player.sendMessage(Messages.parse(Messages.REGISTRATION_START));
|
||||
player.sendMessage(Messages.parse(Messages.QUESTION_LANGUAGE));
|
||||
}
|
||||
|
||||
public boolean isAnswering(UUID uuid) {
|
||||
return activeQuestionnaires.containsKey(uuid);
|
||||
}
|
||||
|
||||
public void handleAnswer(Player player, String answer, PlayerData playerData) {
|
||||
QuestionnaireState state = activeQuestionnaires.get(player.getUniqueId());
|
||||
if (state == null) return;
|
||||
|
||||
switch (state.currentState) {
|
||||
case LANGUAGE:
|
||||
playerData.setLanguage(answer.trim());
|
||||
state.currentState = State.INTERESTS;
|
||||
player.sendMessage(Messages.parse(Messages.QUESTION_INTERESTS));
|
||||
break;
|
||||
case INTERESTS:
|
||||
playerData.setInterests(answer.trim());
|
||||
state.currentState = State.AGE;
|
||||
player.sendMessage(Messages.parse(Messages.QUESTION_AGE));
|
||||
break;
|
||||
case AGE:
|
||||
try {
|
||||
int age = Integer.parseInt(answer.trim());
|
||||
if (age >= 6 && age <= 18) {
|
||||
playerData.setAge(age);
|
||||
playerData.setRegistered(true);
|
||||
activeQuestionnaires.remove(player.getUniqueId());
|
||||
databaseManager.savePlayer(playerData);
|
||||
player.sendMessage(Messages.parse(Messages.REGISTRATION_COMPLETE));
|
||||
} else {
|
||||
player.sendMessage(Messages.parse(Messages.ERROR_AGE_RANGE));
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
player.sendMessage(Messages.parse(Messages.ERROR_AGE_FORMAT));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package git.yawaflua.tech.score;
|
||||
|
||||
import git.yawaflua.tech.database.DatabaseManager;
|
||||
import git.yawaflua.tech.model.PlayerData;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
public class ScoreManager {
|
||||
private final DatabaseManager databaseManager;
|
||||
private final ConcurrentMap<UUID, PlayerData> cache = new ConcurrentHashMap<>();
|
||||
private final double minScore;
|
||||
|
||||
public ScoreManager(DatabaseManager databaseManager, FileConfiguration config) {
|
||||
this.databaseManager = databaseManager;
|
||||
this.minScore = config.getDouble("scores.min-score", 0.0);
|
||||
}
|
||||
|
||||
public void loadPlayer(UUID uuid) {
|
||||
PlayerData data = databaseManager.getPlayer(uuid);
|
||||
if (data == null) {
|
||||
data = new PlayerData(uuid, "", "English", "", 0, 0.0, false, System.currentTimeMillis());
|
||||
}
|
||||
cache.put(uuid, data);
|
||||
}
|
||||
|
||||
public void unloadPlayer(UUID uuid) {
|
||||
PlayerData data = cache.remove(uuid);
|
||||
if (data != null) {
|
||||
databaseManager.savePlayer(data);
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerData getPlayerData(UUID uuid) {
|
||||
return cache.get(uuid);
|
||||
}
|
||||
|
||||
public double getPoints(UUID uuid) {
|
||||
PlayerData data = cache.get(uuid);
|
||||
return data != null ? data.getPoints() : 0.0;
|
||||
}
|
||||
|
||||
public void addPoints(UUID uuid, double amount) {
|
||||
PlayerData data = cache.get(uuid);
|
||||
if (data != null) {
|
||||
data.setPoints(data.getPoints() + amount);
|
||||
}
|
||||
}
|
||||
|
||||
public void removePoints(UUID uuid, double amount) {
|
||||
PlayerData data = cache.get(uuid);
|
||||
if (data != null) {
|
||||
double newPoints = Math.max(minScore, data.getPoints() - amount);
|
||||
data.setPoints(newPoints);
|
||||
}
|
||||
}
|
||||
|
||||
public void saveAll() {
|
||||
for (PlayerData data : cache.values()) {
|
||||
databaseManager.savePlayer(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package git.yawaflua.tech.tab;
|
||||
|
||||
import git.yawaflua.tech.model.PlayerData;
|
||||
import git.yawaflua.tech.score.ScoreManager;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
public class TabManager {
|
||||
|
||||
private final ScoreManager scoreManager;
|
||||
private final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
|
||||
public TabManager(Plugin plugin, ScoreManager scoreManager) {
|
||||
this.scoreManager = scoreManager;
|
||||
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateAll();
|
||||
}
|
||||
}.runTaskTimer(plugin, 20L, 100L);
|
||||
}
|
||||
|
||||
public void updateAll() {
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
updatePlayerTab(player);
|
||||
}
|
||||
}
|
||||
|
||||
public void updatePlayerTab(Player player) {
|
||||
PlayerData data = scoreManager.getPlayerData(player.getUniqueId());
|
||||
if (data != null && data.isRegistered()) {
|
||||
String format = String.format("<white>%s</white> <gray>|</gray> <aqua>🌍 %s</aqua> <gray>|</gray> <gold>🎯 %s</gold> <gray>|</gray> <green>📅 %d</green> <gray>|</gray> <yellow>⭐ %.1f</yellow>",
|
||||
player.getName(), data.getLanguage(), data.getInterests(), data.getAge(), data.getPoints());
|
||||
player.playerListName(miniMessage.deserialize(format));
|
||||
} else {
|
||||
player.playerListName(miniMessage.deserialize("<gray>" + player.getName() + " (Заполняет анкету)</gray>"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package git.yawaflua.tech.voice;
|
||||
|
||||
import git.yawaflua.tech.messages.Messages;
|
||||
import git.yawaflua.tech.score.ScoreManager;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import su.plo.voice.api.server.PlasmoVoiceServer;
|
||||
import su.plo.voice.api.server.player.VoiceServerPlayer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class VoiceIntegration {
|
||||
|
||||
private final ScoreManager scoreManager;
|
||||
private final double voiceRewardPoints;
|
||||
private final double voiceDistance;
|
||||
private final Plugin plugin;
|
||||
private final Logger logger;
|
||||
|
||||
// UUID -> Ticks speaking
|
||||
private final Map<UUID, Integer> speakingTime = new HashMap<>();
|
||||
|
||||
public VoiceIntegration(Plugin plugin, ScoreManager scoreManager, FileConfiguration config) {
|
||||
this.plugin = plugin;
|
||||
this.scoreManager = scoreManager;
|
||||
this.logger = plugin.getLogger();
|
||||
|
||||
this.voiceRewardPoints = config.getDouble("scores.voice-chat-near-player-per-20s", 1.0);
|
||||
this.voiceDistance = config.getDouble("scores.voice-chat-near-distance", 30.0);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
checkSpeakingPlayers();
|
||||
}
|
||||
}.runTaskTimer(plugin, 20L, 20L);
|
||||
|
||||
logger.info("PlasmoVoice integration initialized.");
|
||||
}
|
||||
|
||||
private void checkSpeakingPlayers() {
|
||||
}
|
||||
|
||||
public void dumpAudio(UUID targetId, String reason) {
|
||||
Player target = Bukkit.getPlayer(targetId);
|
||||
if (target == null) return;
|
||||
|
||||
File dumpFolder = new File(plugin.getDataFolder(), "audio_dumps");
|
||||
if (!dumpFolder.exists()) {
|
||||
dumpFolder.mkdirs();
|
||||
}
|
||||
|
||||
String fileName = target.getName() + "_" + new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(new Date()) + ".txt";
|
||||
File dumpFile = new File(dumpFolder, fileName);
|
||||
|
||||
try (FileWriter writer = new FileWriter(dumpFile)) {
|
||||
writer.write("Audio dump for player: " + target.getName() + "\n");
|
||||
writer.write("Reason: " + reason + "\n");
|
||||
writer.write("Time: " + new Date().toString() + "\n");
|
||||
writer.write("\n");
|
||||
writer.write("[WARNING] Fully fledged audio recording requires creating an audio buffer on the proxy/server (PlasmoVoice doesn't provide historical packets natively).\n");
|
||||
writer.write("This file is a placeholder to demonstrate report generation.\n");
|
||||
|
||||
writer.write("\nPlayers within " + voiceDistance + " blocks radius:\n");
|
||||
for (Entity entity : target.getNearbyEntities(voiceDistance, voiceDistance, voiceDistance)) {
|
||||
if (entity instanceof Player) {
|
||||
writer.write("- " + entity.getName() + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.warning("Failed to create audio dump: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
# PixelTalk Configuration
|
||||
database:
|
||||
host: witteringgray
|
||||
port: 27017
|
||||
database: pixeltalk
|
||||
# Опционально: аутентификация
|
||||
# username: ""
|
||||
# password: ""
|
||||
|
||||
scores:
|
||||
chat-message: 0.5
|
||||
voice-chat-near-player-per-20s: 1.0
|
||||
voice-chat-near-distance: 30.0
|
||||
pvp-hit: -0.5
|
||||
profanity-chat: -0.5
|
||||
min-score: 0.0
|
||||
|
||||
pvp:
|
||||
cooldown-seconds: 5
|
||||
|
||||
report:
|
||||
cooldown-seconds: 60
|
||||
|
||||
profanity:
|
||||
words:
|
||||
# Русский
|
||||
- "блять"
|
||||
- "сука"
|
||||
- "пиздец"
|
||||
- "хуй"
|
||||
- "ебать"
|
||||
- "ёбаный"
|
||||
- "нахуй"
|
||||
- "пизда"
|
||||
- "залупа"
|
||||
- "мудак"
|
||||
- "дерьмо"
|
||||
# English
|
||||
- "fuck"
|
||||
- "shit"
|
||||
- "bitch"
|
||||
- "ass"
|
||||
- "dick"
|
||||
- "bastard"
|
||||
- "damn"
|
||||
- "cunt"
|
||||
# Иврит (транслитерация)
|
||||
- "зонА"
|
||||
- "бен зона"
|
||||
- "кус эмек"
|
||||
- "косэмек"
|
||||
- "cosemmek"
|
||||
- "кусэмек"
|
||||
- "лех лазазэль"
|
||||
- "бен шармута"
|
||||
- "маньяк"
|
||||
- "zona"
|
||||
- "ben zona"
|
||||
- "kus emek"
|
||||
- "kos emek"
|
||||
- "leh laazazel"
|
||||
- "ben sharmuta"
|
||||
# Арабский (транслитерация)
|
||||
- "шармута"
|
||||
- "sharmuta"
|
||||
- "sharmouta"
|
||||
- "кальб"
|
||||
- "kalb"
|
||||
- "ибн эль шармута"
|
||||
- "ibn el sharmuta"
|
||||
- "хара"
|
||||
- "khara"
|
||||
- "яхраб бейтак"
|
||||
- "ya5rab beytak"
|
||||
- "телхас тизи"
|
||||
- "telhas tizi"
|
||||
- "кос омак"
|
||||
- "kos ommak"
|
||||
- "кос омок"
|
||||
- "kos ommok"
|
||||
- "аири фик"
|
||||
- "airi feek"
|
||||
- "ахра"
|
||||
- "ybn el sharmouta"
|
||||
# Иврит (оригинал)
|
||||
- "זונה"
|
||||
- "בן זונה"
|
||||
- "כוס אמק"
|
||||
- "לך לעזאזל"
|
||||
- "בן שרמוטה"
|
||||
- "מניאק"
|
||||
- "זדיין"
|
||||
- "זיין"
|
||||
# Арабский (оригинал)
|
||||
- "شرموطة"
|
||||
- "كلب"
|
||||
- "ابن الشرموطة"
|
||||
- "خرا"
|
||||
- "يخرب بيتك"
|
||||
- "كس أمك"
|
||||
- "عيري فيك"
|
||||
@@ -1,8 +1,12 @@
|
||||
name: PixelTalk
|
||||
version: '${version}'
|
||||
description: Socialization plugin for teenagers
|
||||
|
||||
main: git.yawaflua.tech.pixeltalk
|
||||
bootstrapper: git.yawaflua.tech.pixeltalkBootstrap
|
||||
loader: git.yawaflua.tech.pixeltalkLoader
|
||||
api-version: '1.21.11'
|
||||
api-version: '1.21.10'
|
||||
load: POSTWORLD
|
||||
|
||||
softdepend:
|
||||
- PlasmoVoice
|
||||
|
||||
Reference in New Issue
Block a user