Add readme

This commit is contained in:
Dmitrii
2026-04-16 15:58:18 +03:00
parent fbe93f095d
commit bf1cb50c28
21 changed files with 1035 additions and 9 deletions
+1
View File
@@ -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*
+4
View File
@@ -0,0 +1,4 @@
{
"java.configuration.updateBuildConfiguration": "interactive",
"java.compile.nullAnalysis.mode": "automatic"
}
+12 -4
View File
@@ -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
View File
@@ -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;
}
}
+64 -2
View File
@@ -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());
}
}
}
+101
View File
@@ -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"
# Иврит (оригинал)
- "זונה"
- "בן זונה"
- "כוס אמק"
- "לך לעזאזל"
- "בן שרמוטה"
- "מניאק"
- "זדיין"
- "זיין"
# Арабский (оригинал)
- "شرموطة"
- "كلب"
- "ابن الشرموطة"
- "خرا"
- "يخرب بيتك"
- "كس أمك"
- "عيري فيك"
+5 -1
View File
@@ -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