diff --git a/src/main/java/me/makkuusen/timing/system/ContextResolvers.java b/src/main/java/me/makkuusen/timing/system/ContextResolvers.java index 74ac4094..8da5b3df 100644 --- a/src/main/java/me/makkuusen/timing/system/ContextResolvers.java +++ b/src/main/java/me/makkuusen/timing/system/ContextResolvers.java @@ -26,7 +26,10 @@ import me.makkuusen.timing.system.track.options.TrackOption; import me.makkuusen.timing.system.track.regions.TrackRegion; import me.makkuusen.timing.system.track.tags.TrackTag; +import me.makkuusen.timing.system.tuning.Attribute; +import me.makkuusen.timing.system.tuning.PartManager; import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Material; import org.bukkit.entity.Boat; import java.util.*; @@ -220,6 +223,24 @@ static void loadCommandContextsAndCompletions(PaperCommandManager manager) { } return new ArrayList<>(); }); + + // parts stuff i guess (am tired) + manager.getCommandCompletions().registerCompletion("parts", context -> { + return PartManager.getPartNames(); + }); + + manager.getCommandCompletions().registerCompletion( + "materials", + c -> Arrays.stream(Material.values()) + .map(Material::name) + .toList() + ); + + manager.getCommandCompletions().registerCompletion("tuningAttributes", context -> { + return Arrays.stream(Attribute.values()) + .map(Enum::name) + .toList(); + }); } public static ContextResolver getTrackTypeContextResolver() { return (c) -> { diff --git a/src/main/java/me/makkuusen/timing/system/TSListener.java b/src/main/java/me/makkuusen/timing/system/TSListener.java index b611fa08..c36d4b4f 100644 --- a/src/main/java/me/makkuusen/timing/system/TSListener.java +++ b/src/main/java/me/makkuusen/timing/system/TSListener.java @@ -222,10 +222,10 @@ public void onVehicleExit(VehicleExitEvent event) { event.setCancelled(true); return; } - + if (driver.getState() == DriverState.LOADED || driver.getState() == DriverState.STARTING || driver.getState() == DriverState.RUNNING || driver.getState() == DriverState.RESET || driver.getState() == DriverState.LAPRESET) { event.setCancelled(true); - + Long currentTime = System.currentTimeMillis(); UUID playerID = player.getUniqueId(); @@ -721,13 +721,26 @@ private static void handleHeat(Driver driver, PlayerMoveEvent e) { } else { driver.lapReset(e.getFrom(), e.getTo(), r); } + driver.resetQualyLap(e.getFrom(), e.getTo(), r); + heat.updatePositions(); + if (heat.getEvent().getTuningEnabled()){ + heat.applyTeamTuning(); + } + } else if (driver.getState() == DriverState.LAPRESET) { + driver.lapReset(e.getFrom(), e.getTo(), r); heat.updatePositions(); + if (heat.getEvent().getTuningEnabled()){ + heat.applyTeamTuning(); + } return; } else if (driver.getCurrentLap() != null && driver.getCurrentLap().getLatestCheckpoint() != 0) { if (!driver.getCurrentLap().hasPassedAllCheckpoints()) { int checkpoint = driver.getCurrentLap().getLatestCheckpoint(); performInHeatReset(driver); Text.send(driver.getTPlayer().getPlayer(), Error.MISSED_CHECKPOINTS); + if (heat.getEvent().getTuningEnabled()){ + heat.applyTeamTuning(); + } return; } @@ -740,6 +753,9 @@ private static void handleHeat(Driver driver, PlayerMoveEvent e) { var teamEntry = maybeTeamEntry.get(); teamEntry.updateRaceProgress(driver.getLaps().size(), 0); } + if (heat.getEvent().getTuningEnabled()){ + heat.applyTeamTuning(); + } } if (heat.getGhostingDelta() != null) { diff --git a/src/main/java/me/makkuusen/timing/system/TimingSystem.java b/src/main/java/me/makkuusen/timing/system/TimingSystem.java index e882b7a4..2ca1b6df 100644 --- a/src/main/java/me/makkuusen/timing/system/TimingSystem.java +++ b/src/main/java/me/makkuusen/timing/system/TimingSystem.java @@ -22,6 +22,7 @@ import me.makkuusen.timing.system.theme.Theme; import me.makkuusen.timing.system.timetrial.TimeTrialListener; import me.makkuusen.timing.system.tplayer.TPlayer; +import me.makkuusen.timing.system.tuning.PartManager; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextColor; import net.megavex.scoreboardlibrary.api.ScoreboardLibrary; @@ -139,6 +140,7 @@ public void onEnable() { manager.registerCommand(new CommandUnghost()); manager.registerCommand(new CommandBoatUtilsModeEdit()); manager.registerCommand(new CommandTeam()); + manager.registerCommand(new CommandParts()); taskChainFactory = BukkitTaskChainFactory.create(this); database = configuration.getDatabaseType(); @@ -181,6 +183,7 @@ public void onEnable() { int pluginId = 16012; new Metrics(this, pluginId); + PartManager.loadParts(); } private void setConfigDefaultColors() { diff --git a/src/main/java/me/makkuusen/timing/system/TimingSystemConfiguration.java b/src/main/java/me/makkuusen/timing/system/TimingSystemConfiguration.java index c3569e92..905c609a 100644 --- a/src/main/java/me/makkuusen/timing/system/TimingSystemConfiguration.java +++ b/src/main/java/me/makkuusen/timing/system/TimingSystemConfiguration.java @@ -52,6 +52,7 @@ public class TimingSystemConfiguration { private final double copperPos; private boolean dynamicDiamondPosEnabled; private final List dynamicDiamondPoses = new ArrayList<>(); + private int tuningEffect; private final Object databaseType; @@ -82,6 +83,7 @@ public class TimingSystemConfiguration { drsMaxDelta = plugin.getConfig().getInt("drs.maxDelta", 1150); drsDuration = plugin.getConfig().getInt("drs.duration", 2000); drsForwardAccel = plugin.getConfig().getDouble("drs.forwardAccel", 0.06); + tuningEffect = plugin.getConfig().getInt("tuning.effect", 2); pushToPassMaxUseTime = plugin.getConfig().getInt("pushtopass.maxUseTime", 5000); pushToPassFullChargeTime = plugin.getConfig().getInt("pushtopass.fullChargeTime", 60000); pushToPassForwardAccel = plugin.getConfig().getDouble("pushtopass.forwardAccel", 0.05); @@ -164,6 +166,10 @@ public void setDrsForwardAccel(double value) { drsForwardAccel = value; } + public void setTuningEffect(int value){ + tuningEffect = value; + } + public void setPushToPassMaxUseTime(int value) { pushToPassMaxUseTime = value; } diff --git a/src/main/java/me/makkuusen/timing/system/commands/CommandEvent.java b/src/main/java/me/makkuusen/timing/system/commands/CommandEvent.java index a1fec72f..7389e944 100644 --- a/src/main/java/me/makkuusen/timing/system/commands/CommandEvent.java +++ b/src/main/java/me/makkuusen/timing/system/commands/CommandEvent.java @@ -160,6 +160,9 @@ public static void onInfo(CommandSender sender, Event event) { } sender.sendMessage(trackMessage); + Component tuningMessage = Component.text("Tuning Enabled: ").color(theme.getSecondary()).append(theme.getBrackets(event.getTuningEnabled().toString()).clickEvent(ClickEvent.suggestCommand("/event tuning "))); + sender.sendMessage(tuningMessage); + var signsMessage = Text.get(sender, Info.EVENT_INFO_SIGNS); Component open = Text.get(sender, Word.OPEN); Component closed = Text.get(sender, Word.CLOSED); @@ -655,4 +658,40 @@ public static void onSendReserve(Player player, @Optional Event event) { p.sendMessage(Component.empty()); } } + + @Subcommand("tuning enable") + @CommandPermission("%permissionevent_manage") + @Description("Enable tuning for an event") + public void onTuningEnable(Player player, @Optional Event event) { + if (event == null) { + var maybeEvent = EventDatabase.getPlayerSelectedEvent(player.getUniqueId()); + if (maybeEvent.isPresent()) { + event = maybeEvent.get(); + } else { + Text.send(player, Error.NO_EVENT_SELECTED); + return; + } + } + + event.setTuningEnabled(true); + player.sendMessage("§aTuning enabled for this event"); + } + + @Subcommand("tuning disable") + @CommandPermission("%permissionevent_manage") + @Description("Disable tuning for an event") + public void onTuningDisable(Player player, @Optional Event event) { + if (event == null) { + var maybeEvent = EventDatabase.getPlayerSelectedEvent(player.getUniqueId()); + if (maybeEvent.isPresent()) { + event = maybeEvent.get(); + } else { + Text.send(player, Error.NO_EVENT_SELECTED); + return; + } + } + + event.setTuningEnabled(false); + player.sendMessage("§cTuning disabled for this event"); + } } diff --git a/src/main/java/me/makkuusen/timing/system/commands/CommandHeat.java b/src/main/java/me/makkuusen/timing/system/commands/CommandHeat.java index 8e5ac662..b088555c 100644 --- a/src/main/java/me/makkuusen/timing/system/commands/CommandHeat.java +++ b/src/main/java/me/makkuusen/timing/system/commands/CommandHeat.java @@ -35,6 +35,7 @@ import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.entity.Boat; import org.bukkit.entity.Player; import java.time.Duration; @@ -216,6 +217,17 @@ public static void onHeatInfo(Player player, Heat heat) { } player.sendMessage(boatSwitchingMessage); + var liveTuningMessage = Component.text("Live Tuning: ").color(theme.getPrimary()); + + if (!heat.isFinished() && player.hasPermission("timingsystem.packs.eventadmin")) { + String liveTuningValue = (heat.getLiveTuningEnabled() != null && heat.getLiveTuningEnabled()) ? "true" : "false"; + liveTuningMessage = liveTuningMessage.append(theme.getEditButton(player, liveTuningValue, theme).clickEvent(ClickEvent.suggestCommand("/heat set livetuning " + heat.getName()))); + } else { + String liveTuningValue = (heat.getLiveTuningEnabled() != null && heat.getLiveTuningEnabled()) ? "enabled" : "disabled"; + liveTuningMessage = liveTuningMessage.append(theme.highlight(liveTuningValue)); + } + player.sendMessage(liveTuningMessage); + if (heat.getFastestLapUUID() != null) { Driver d = heat.getDrivers().get(heat.getFastestLapUUID()); player.sendMessage(Text.get(player, Info.HEAT_INFO_FASTEST_LAP, "%time%", ApiUtilities.formatAsTime(d.getBestLap().get().getPreciseLapTime()), "%player%", d.getTPlayer().getName())); @@ -489,6 +501,14 @@ public static void onHeatSetPushToPass(Player player, Heat heat, Boolean pushToP Text.send(player, Success.SAVED); } + @Subcommand("set livetuning") + @CommandCompletion("@heat true|false") + @CommandPermission("%permissionheat_set_livetuning") + @Description("Enable/disable live tuning adjustments during the heat") + public static void onHeatSetLiveTuning(Player player, Heat heat, Boolean enabled) { + heat.setLiveTuningEnabled(enabled); + Text.send(player, Success.SAVED); + } @Subcommand("set lonely") @CommandCompletion("@heat true|false") @CommandPermission("%permissionheat_set_lonely") diff --git a/src/main/java/me/makkuusen/timing/system/commands/CommandParts.java b/src/main/java/me/makkuusen/timing/system/commands/CommandParts.java new file mode 100644 index 00000000..4ee60fd8 --- /dev/null +++ b/src/main/java/me/makkuusen/timing/system/commands/CommandParts.java @@ -0,0 +1,195 @@ +package me.makkuusen.timing.system.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Subcommand; +import me.makkuusen.timing.system.database.EventDatabase; +import me.makkuusen.timing.system.gui.BoatSetupGui; +import me.makkuusen.timing.system.heat.Heat; +import me.makkuusen.timing.system.participant.Driver; +import me.makkuusen.timing.system.team.Team; +import me.makkuusen.timing.system.team.TeamManager; +import me.makkuusen.timing.system.theme.Theme; +import me.makkuusen.timing.system.tplayer.TPlayer; +import me.makkuusen.timing.system.tuning.Attribute; +import me.makkuusen.timing.system.tuning.Part; +import me.makkuusen.timing.system.tuning.PartCategory; +import me.makkuusen.timing.system.tuning.PartManager; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import org.bukkit.Material; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.Arrays; +import java.util.Map; + +@CommandAlias("parts") +public class CommandParts extends BaseCommand { + @Subcommand("create") + @CommandCompletion("OARS|HULL|RUDDER ") + @CommandPermission("%permissiontimingsystem_part_create") + public void create(CommandSender sender,PartCategory category, String name){ + Theme theme = Theme.getTheme(sender); + + Part lePart = new Part(); + lePart.setName(name); + lePart.setCategoryName(category); + + PartManager.addPart(lePart); + PartManager.saveParts(); + sender.sendMessage(Component.text("part was added").color(theme.getSuccess())); + } + + @Subcommand("list") + @CommandPermission("%permissiontimingsystem_part_list") + public void list(CommandSender sender){ + Theme theme = Theme.getTheme(sender); + + sender.sendMessage(theme.getRefreshButton().clickEvent(ClickEvent.runCommand("/parts list")) + .append(theme.getTitleLine(Component.text("parts").color(theme.getSecondary()))) + ); + + for (String name : PartManager.getPartNames()){ + sender.sendMessage(Component.text(name).color(theme.getSecondary())); + } + } + + @Subcommand("delete") + @CommandCompletion("@parts") + public void delete(CommandSender sender, String name){ + Theme theme = Theme.getTheme(sender); + + if (!PartManager.removePart(name)) { + sender.sendMessage(Component.text("Part not found").color(theme.getError())); + return; + } + sender.sendMessage(Component.text("Part deleted").color(theme.getSuccess())); + PartManager.saveParts(); + applyLiveTuningIfActive(sender); + } + + @Subcommand("manage") + @CommandCompletion("@parts") + @CommandPermission("%permissiontimingsystem_part_manage") + public void manage(CommandSender sender, String name){ + Theme theme = Theme.getTheme(sender); + Part workingPart = PartManager.getPartByName(name); + + if (workingPart == null){ + sender.sendMessage(Component.text("Part not found").color(theme.getError())); + return; + } + + Map attributes = workingPart.getAttributes(); + + // Title + sender.sendMessage(theme.getRefreshButton().clickEvent(ClickEvent.runCommand("/parts manage " + name)) + .append(theme.getTitleLine(Component.text(workingPart.getName()).color(theme.getSecondary()))) + ); + + // Description + sender.sendMessage(Component.text(workingPart.getDescription() == null ? "No Description" : workingPart.getDescription()).clickEvent(ClickEvent.suggestCommand("/parts set description " + name + " "))); + + //rating + sender.sendMessage(Component.text(workingPart.getRating()).clickEvent(ClickEvent.suggestCommand("/parts set rating " + name + " "))); + + // attributes + for (Attribute attribute : attributes.keySet()){ + sendTuningAttribute(sender, workingPart, attribute); + } + PartManager.saveParts(); + applyLiveTuningIfActive(sender); + } + + + @Subcommand("set description") + @CommandCompletion("@parts") + @CommandPermission("%permissiontimingsystem_part_manage") + public void setDescription(CommandSender sender, String part,String... description){ + Theme theme = Theme.getTheme(sender); + Part thePart = PartManager.getPartByName(part); + + thePart.setDescription(String.join(" ", description)); + sender.sendMessage(Component.text(thePart.getName() + "'s description was changed").color(theme.getSuccess())); + PartManager.saveParts(); + + applyLiveTuningIfActive(sender); + } + + @Subcommand("set attribute") + @CommandCompletion("@parts @tuningAttributes") + @CommandPermission("%permissiontimingsystem_part_manage") + public void setAttribute(CommandSender sender, String part, Attribute attribute, Integer value) { + Theme theme = Theme.getTheme(sender); + Part thePart = PartManager.getPartByName(part); + + thePart.removeAttribute(attribute); // ik its lazy but icba right now + thePart.addAttribute(attribute, value); + PartManager.saveParts(); + sender.sendMessage(Component.text(thePart.getName() + "'s " + attribute.toString() + " has been set to " + value.toString()).color(theme.getSuccess())); + + for (Team team : TeamManager.getAllTeams()){ + BoatSetupGui.applyLiveTuningIfActive(team); + } + applyLiveTuningIfActive(sender); + } + + @Subcommand("set rating") + @CommandCompletion("@parts") + @CommandPermission("%permissiontimingsystem_part_manage") + public void setRating(CommandSender sender, String part, Integer value){ + Theme theme = Theme.getTheme(sender); + Part thePart = PartManager.getPartByName(part); + + thePart.setRating(value); + PartManager.saveParts(); + sender.sendMessage(Component.text(thePart.getName() + "'s rating was set to " + value.toString()).color(theme.getSuccess())); + applyLiveTuningIfActive(sender); + } + + @Subcommand("set item") + @CommandCompletion("@parts @materials") + @CommandPermission("%permissiontimingsystem_part_manage") + public void setItem(CommandSender sender, String part, String item){ + Theme theme = Theme.getTheme(sender); + Part thePart = PartManager.getPartByName(part); + + Material material = Material.matchMaterial(item); + + thePart.setItem(material); + PartManager.saveParts(); + sender.sendMessage(Component.text(thePart.getName() + "'s item was set to " + item).color(theme.getSuccess())); + } + + + private void sendTuningAttribute(CommandSender sender, Part part, Attribute attribute){ + Theme theme = Theme.getTheme(sender); + Component toSend; + + toSend = Component.text(attribute + ": ") + .color(theme.getPrimary()) + .append( + theme.getBrackets(Component.text(part.getValue(attribute).toString()) + .clickEvent(ClickEvent.suggestCommand("/parts set attribute " + part.getName() + " " + attribute.name() + " ") )) + ); + + sender.sendMessage(toSend); + } + + public void applyLiveTuningIfActive(CommandSender sender) { + if (!(sender instanceof Player player)) return; + + Driver driver = EventDatabase.playerInRunningHeat.get(player.getUniqueId()); + if (driver == null) return; + + Heat heat = driver.getHeat(); + if (!heat.getLiveTuningEnabled()) return; // Live tuning disabled + + // Apply the updated tuning immediately + heat.applyTeamTuning(); + } + +} diff --git a/src/main/java/me/makkuusen/timing/system/commands/CommandReset.java b/src/main/java/me/makkuusen/timing/system/commands/CommandReset.java index 762dcd0e..f061c5a3 100644 --- a/src/main/java/me/makkuusen/timing/system/commands/CommandReset.java +++ b/src/main/java/me/makkuusen/timing/system/commands/CommandReset.java @@ -5,6 +5,7 @@ import co.aikar.commands.annotation.CommandPermission; import co.aikar.commands.annotation.Default; import me.makkuusen.timing.system.ApiUtilities; +import me.makkuusen.timing.system.TimingSystem; import me.makkuusen.timing.system.api.TimingSystemAPI; import me.makkuusen.timing.system.participant.Driver; import me.makkuusen.timing.system.participant.DriverState; @@ -15,9 +16,12 @@ import me.makkuusen.timing.system.track.Track; import me.makkuusen.timing.system.track.locations.TrackLocation; import me.makkuusen.timing.system.track.regions.TrackRegion; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; +import static me.makkuusen.timing.system.event.Event.plugin; import static me.makkuusen.timing.system.heat.QualifyHeat.timeIsOver; @CommandAlias("reset|re") @@ -86,6 +90,9 @@ public static void performInHeatReset(Driver driver) { driver.getState() == DriverState.RUNNING) { driver.setState(DriverState.RESET); resetToTrackSpawn(driver); + if (driver.getHeat().getEvent().getTuningEnabled()){ + driver.getHeat().applyTeamTuning(); + } return; } @@ -96,6 +103,13 @@ public static void performInHeatReset(Driver driver) { } else if (driver.getState() == DriverState.RESET) { resetToTrackSpawn(driver); } + TimingSystem plugin = JavaPlugin.getPlugin(TimingSystem.class); + + Bukkit.getScheduler().runTaskLater(plugin, () -> { + if (driver.getHeat().getEvent().getTuningEnabled()) { + driver.getHeat().applyTeamTuning(); + } + }, 5L); } private static void resetToCheckpoint(Driver driver) { diff --git a/src/main/java/me/makkuusen/timing/system/commands/CommandTeam.java b/src/main/java/me/makkuusen/timing/system/commands/CommandTeam.java index 913b7d03..a9a94d61 100644 --- a/src/main/java/me/makkuusen/timing/system/commands/CommandTeam.java +++ b/src/main/java/me/makkuusen/timing/system/commands/CommandTeam.java @@ -4,20 +4,33 @@ import co.aikar.commands.annotation.*; import me.makkuusen.timing.system.ApiUtilities; import me.makkuusen.timing.system.TimingSystem; +import me.makkuusen.timing.system.database.EventDatabase; import me.makkuusen.timing.system.database.TSDatabase; +import me.makkuusen.timing.system.gui.BoatSetupGui; +import me.makkuusen.timing.system.gui.PartSelectGui; +import me.makkuusen.timing.system.heat.Heat; +import me.makkuusen.timing.system.participant.Driver; import me.makkuusen.timing.system.permissions.PermissionTeam; import me.makkuusen.timing.system.team.Team; import me.makkuusen.timing.system.team.TeamManager; +import me.makkuusen.timing.system.team.TeamTuning; +import me.makkuusen.timing.system.team.TuningAttribute; import me.makkuusen.timing.system.theme.Text; +import me.makkuusen.timing.system.theme.Theme; import me.makkuusen.timing.system.theme.messages.Error; import me.makkuusen.timing.system.theme.messages.Info; import me.makkuusen.timing.system.theme.messages.Success; import me.makkuusen.timing.system.tplayer.TPlayer; +import me.makkuusen.timing.system.tuning.Attribute; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.Player; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import java.util.List; +import java.util.Map; /** * Command handler for team management operations @@ -243,4 +256,178 @@ public void onTeamDebug(CommandSender sender) { e.printStackTrace(); } } + + // 16/02/26 so I'm just now realising I didn't comment any of this so good luck to whoever works here next + // 16/05/26 doesn't matter anymore its gone now + @Subcommand("tuning") + @CommandCompletion("@teams") + @CommandPermission("%permissionteam_tuning") + @Description("Configure team tuning") + public void onTuning(Player player, CommandSender sender, Team team){ + new BoatSetupGui(TSDatabase.getPlayer(player.getUniqueId()), team).show(player); + applyLiveTuningIfActive(team); +// Theme theme = Theme.getTheme(sender); + +// sender.sendMessage( +// theme.getRefreshButton().clickEvent(ClickEvent.runCommand("/team tuning " + team.getName())) +// .append(Component.space()) +// .append(theme.getTitleLine(Component.text(team.getName()) +// .append(Component.text(" tuning")) +// )) +// ); +// +// TeamTuning tuning = team.getTuning(); +// Map attributes = tuning.getAttributes(); +// +// sender.sendMessage(Component.text("acceleration: ").color(theme.getPrimary())); +// for(Attribute attribute : attributes.keySet()){ +// if (tuning.AVAILABLE_ATTRIBUTES.get(attribute).getCategory().equals("acceleration")){ +// sendTuningAttribute(sender, team, attribute); +// } +// +// } +// sender.sendMessage(""); +// +// +// sender.sendMessage(Component.text("speed: ").color(theme.getPrimary())); +// for(Attribute attribute : attributes.keySet()){ +// if (tuning.AVAILABLE_ATTRIBUTES.get(attribute).getCategory().equals("speed")){ +// sendTuningAttribute(sender, team, attribute); +// } +// } +// sender.sendMessage(""); +// +// sender.sendMessage(Component.text("handling: ").color(theme.getPrimary())); +// for(Attribute attribute : attributes.keySet()){ +// if (tuning.AVAILABLE_ATTRIBUTES.get(attribute).getCategory().equals("handling")){ +// sendTuningAttribute(sender, team, attribute); +// } +// } +// +// int totalPoints = tuning.getTotalPoints(); +// int remaining = tuning.MAX_TOTAL_POINTS - totalPoints; +// +// sender.sendMessage(Component.empty()); +// sender.sendMessage(Component.text("Total Points: " + totalPoints + " / " + team.getTuning().getMAX_TOTAL_POINTS()) +// .color(remaining < 0 ? NamedTextColor.RED : theme.getPrimary())); +// sender.sendMessage(Component.text("Remaining: " + remaining) +// .color(remaining < 0 ? NamedTextColor.RED : NamedTextColor.GREEN)); + } + + @Subcommand("tuning increase") + @CommandCompletion("@teams topSpeed|acceleration|handling") + @CommandPermission("%permissionteam_tuning") + @Description("Increase a tuning attribute") + public void onTuningIncrease(Player player, Team team, Attribute attribute){ + TeamTuning tuning = team.getTuning(); + + if (!tuning.getAttributes().containsKey(attribute)) { + player.sendMessage("§cInvalid attribute: " + attribute); + return; + } + + int current = tuning.getAttributes().get(attribute); + int total = tuning.getTotalPoints(); + + if (current >= tuning.MAX_STAT_VALUE){ + player.sendMessage("§c" + attribute + " is already at maximum " + team.getTuning().getMAX_TOTAL_POINTS()); + return; + } + + if (total >= tuning.MAX_TOTAL_POINTS){ + player.sendMessage("§cNo points remaining! Total is already " + team.getTuning().getMAX_TOTAL_POINTS()); + return; + } + + tuning.increaseAttribute(attribute); + tuning.save(); + + applyLiveTuningIfActive(team); + + //onTuning(player, team); + } + + @Subcommand("tuning decrease") + @CommandCompletion("@teams topSpeed|acceleration|handling") + @CommandPermission("%permissionteam_tuning") + @Description("decrease a tuning attribute") + public void onTuningDecrease(Player player, Team team, Attribute attribute){ + TeamTuning tuning = team.getTuning(); + + if (!tuning.getAttributes().containsKey(attribute)) { + player.sendMessage("§cInvalid attribute: " + attribute); + return; + } + + int current = tuning.getAttributes().get(attribute); + int total = tuning.getTotalPoints(); + + if (current <= tuning.MIN_STAT_VALUE){ + player.sendMessage("§c" + attribute + " is already at maximum (0)"); + return; + } + + tuning.decreaseAttribute(attribute); + tuning.save(); + + applyLiveTuningIfActive(team); + + //onTuning(player, team); + } + + @Subcommand("tuning setmaxpoints") + @CommandCompletion("") + @CommandPermission("%permissionteam_tuning_admin") + @Description("Set the maximum total tuning points allowed") + public void onSetMaxPoints(CommandSender sender, int points) { + if (points < 1) { + sender.sendMessage("§cPoints must be at least 1"); + return; + } + + for (Team team : TeamManager.getAllTeams()){ + team.getTuning().setMAX_TOTAL_POINTS(points); + } + + sender.sendMessage("§aMax tuning points set to " + points); + } + + private void sendTuningAttribute(CommandSender sender, Team team, Attribute attribute){ + Theme theme = Theme.getTheme(sender); + Map attributes = team.getTuning().getAttributes(); + Component toSend; + int currentValue = attributes.get(attribute); + + toSend = Component.text(attribute + ": ") + .color(theme.getPrimary()) + .append(theme.getBrackets(Component.text("-"), NamedTextColor.RED) + .clickEvent(ClickEvent.runCommand("/team tuning decrease " + team.getName() + " " + attribute)) + .hoverEvent(Component.text("Decrease " + attribute))) + .append(Component.space()) + .append(Component.text(currentValue).color(theme.getSecondary())) // Fixed: wrap in Component.text() + .append(Component.space()) + .append(theme.getBrackets(Component.text("+"), NamedTextColor.GREEN) + .clickEvent(ClickEvent.runCommand("/team tuning increase " + team.getName() + " " + attribute)) + .hoverEvent(Component.text("Increase " + attribute))); + + sender.sendMessage(toSend); + } + + public void applyLiveTuningIfActive(Team team) { + // For each online player on the team + for (TPlayer tPlayer : team.getPlayers()) { + Player player = tPlayer.getPlayer(); + if (player == null) continue; // Offline + + // Check if they're in an active heat (O(1) lookup) + Driver driver = EventDatabase.playerInRunningHeat.get(player.getUniqueId()); + if (driver == null) continue; // Not racing + + Heat heat = driver.getHeat(); + if (!heat.getLiveTuningEnabled()) continue; // Live tuning disabled + + // Apply the updated tuning immediately + heat.applyTuningToPlayer(player, team.getTuning()); + } + } } \ No newline at end of file diff --git a/src/main/java/me/makkuusen/timing/system/commands/CommandTimingSystem.java b/src/main/java/me/makkuusen/timing/system/commands/CommandTimingSystem.java index 8fc3771d..01d186b8 100644 --- a/src/main/java/me/makkuusen/timing/system/commands/CommandTimingSystem.java +++ b/src/main/java/me/makkuusen/timing/system/commands/CommandTimingSystem.java @@ -6,6 +6,7 @@ import me.makkuusen.timing.system.TrackTagManager; import me.makkuusen.timing.system.database.TSDatabase; import me.makkuusen.timing.system.permissions.PermissionTimingSystem; +import me.makkuusen.timing.system.team.TeamTuning; import me.makkuusen.timing.system.theme.TSColor; import me.makkuusen.timing.system.theme.Text; import me.makkuusen.timing.system.theme.Theme; @@ -13,12 +14,17 @@ import me.makkuusen.timing.system.theme.messages.Success; import me.makkuusen.timing.system.tplayer.TPlayer; import me.makkuusen.timing.system.track.tags.TrackTag; +import me.makkuusen.timing.system.tuning.Attribute; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextColor; import org.bukkit.block.data.type.Switch; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -136,6 +142,18 @@ public static void onDrsForwardAccelChange(CommandSender sender, double value) { Text.send(sender, Success.SAVED); } + @Subcommand("tuning effect") + @CommandCompletion("") + @CommandPermission("%permissiontimingsystem_tuning_set_effect") + public static void onTuningEffectChange(CommandSender sender, int value) { + if (value < 0 || value > 300) { + sender.sendMessage("§cValue must be between 0 and 300 (percent per point)"); + return; + } + TimingSystem.configuration.setTuningEffect(value); + Text.send(sender, Success.SAVED); + } + @Subcommand("pushtopass|p2p maxusetime") @CommandCompletion("") @CommandPermission("%permissiontimingsystem_pushtopass_set_maxusetime") @@ -328,4 +346,48 @@ public static boolean isValidHexCode(String str) { return m.matches(); } + @Subcommand("tuning modifier") + @CommandCompletion(" ") + @CommandPermission("%permissiontimingsystem_tuning_modifier") + @Description("Set the balance multiplier for a tuning attribute") + public static void onTuningModifier(CommandSender sender, String attribute, float multiplier) { + Attribute selectedAttribute = null; + + for (Attribute attr : TeamTuning.AVAILABLE_ATTRIBUTES.keySet()) { + if (attr.toString().equalsIgnoreCase(attribute)) { + selectedAttribute = attr; + break; + } + } + + if (selectedAttribute == null) { + sender.sendMessage("§cUnknown attribute: " + attribute); + sender.sendMessage("§7Available: " + TeamTuning.AVAILABLE_ATTRIBUTES.keySet()); + return; + } + if (multiplier <= 0) { + sender.sendMessage("§cMultiplier must be greater than 0"); + return; + } + TeamTuning.AVAILABLE_ATTRIBUTES.get(attribute).setMultiplier(multiplier); + sender.sendMessage("§aSet multiplier for §e" + attribute + " §ato §e" + multiplier); + } + + @Subcommand("tuning modifiers") + @CommandPermission("%permissiontimingsystem_tuning_modifier") + @Description("List all tuning attribute multipliers") + public static void onTuningModifierList(CommandSender sender) { + Theme theme = Theme.getTheme(sender); + + sender.sendMessage( + theme.getRefreshButton().clickEvent(ClickEvent.runCommand("/ts tuning modifiers")) + .append(Component.space()) + .append(theme.getTitleLine(Component.text("Tuning Attribute Multipliers"))) + ); + + for (var entry : TeamTuning.AVAILABLE_ATTRIBUTES.entrySet()) { + sender.sendMessage( entry.getKey() + ": x" + entry.getValue().getMultiplier()); + } + } + } diff --git a/src/main/java/me/makkuusen/timing/system/database/MySQLDatabase.java b/src/main/java/me/makkuusen/timing/system/database/MySQLDatabase.java index 92458430..57d9b6cd 100644 --- a/src/main/java/me/makkuusen/timing/system/database/MySQLDatabase.java +++ b/src/main/java/me/makkuusen/timing/system/database/MySQLDatabase.java @@ -153,6 +153,9 @@ private static void updateDatabase(int previousVersion) throws SQLException { if (previousVersion < 15) { Version15.updateMySQL(); } + if (previousVersion < 16){ + Version16.updateMySQL(); + } } @@ -271,6 +274,7 @@ PRIMARY KEY (`id`) `state` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `open` tinyint(1) NOT NULL DEFAULT '1', `isRemoved` tinyint(1) NOT NULL DEFAULT '0', + `tuningEnabled` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; """); @@ -299,6 +303,7 @@ PRIMARY KEY (`id`) `drs` tinyint(1) NOT NULL DEFAULT '0', `drsDowntime` int(11) DEFAULT NULL, `pushToPass` tinyint(1) NOT NULL DEFAULT '0', + `liveTuningEnabled` tinyint(1) NOT NULL DEFAULT '0', `isRemoved` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"""); @@ -467,6 +472,16 @@ PRIMARY KEY (`id`), FOREIGN KEY (`teamHeatEntryId`) REFERENCES `ts_team_heat_entries`(`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; """); + + DB.executeUpdate(""" + CREATE TABLE IF NOT EXISTS `ts_team_tuning` ( + `teamId` int(11) NOT NULL, + `attributesJson` TEXT NOT NULL, + PRIMARY KEY (`teamId`), + FOREIGN KEY (`teamId`) REFERENCES `ts_teams`(`id`) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + """); + return true; } catch (SQLException e) { e.printStackTrace(); @@ -1327,4 +1342,22 @@ public int getNextPlayerPosition(int teamId) throws SQLException { public java.util.Optional getTeam(Integer teamId) { return TeamManager.getTeam(teamId); } + + @Override + public void saveTeamTuning(int teamId, String attributesJson) { + try { + DB.executeUpdate( + "INSERT INTO ts_team_tuning (teamId, attributesJson) VALUES (?, ?) ON DUPLICATE KEY UPDATE attributesJson = ?", + teamId, attributesJson, attributesJson + ); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + @Override + public String loadTeamTuning(int teamId) throws SQLException { + DbRow row = DB.getFirstRow("SELECT attributesJson FROM ts_team_tuning WHERE teamId = ?", teamId); + return row != null ? row.getString("attributesJson") : null; + } } diff --git a/src/main/java/me/makkuusen/timing/system/database/SQLiteDatabase.java b/src/main/java/me/makkuusen/timing/system/database/SQLiteDatabase.java index a3ac4727..de1c1155 100644 --- a/src/main/java/me/makkuusen/timing/system/database/SQLiteDatabase.java +++ b/src/main/java/me/makkuusen/timing/system/database/SQLiteDatabase.java @@ -118,6 +118,10 @@ private static void updateDatabase(int previousVersion) throws SQLException { if (previousVersion < 15) { Version15.updateSQLite(); } + + if (previousVersion < 16) { + Version16.updateSQLite(); + } } @@ -255,6 +259,7 @@ public boolean createTables() { `drs` INTEGER NOT NULL DEFAULT 0, `drsDowntime` INTEGER DEFAULT NULL, `pushToPass` INTEGER NOT NULL DEFAULT 0, + `liveTuningEnabled` INTEGER NOT NULL DEFAULT 0, `isRemoved` INTEGER NOT NULL DEFAULT '0' );"""); @@ -401,6 +406,14 @@ FOREIGN KEY (teamId) REFERENCES ts_teams(id) ON DELETE CASCADE FOREIGN KEY (teamHeatEntryId) REFERENCES ts_team_heat_entries(id) ON DELETE CASCADE );"""); + DB.executeUpdate(""" + CREATE TABLE IF NOT EXISTS `ts_team_tuning` ( + `teamId` INTEGER NOT NULL, + `attributesJson` TEXT NOT NULL, + PRIMARY KEY (`teamId`), + FOREIGN KEY (`teamId`) REFERENCES `ts_teams`(`id`) ON DELETE CASCADE + );"""); + return true; } catch (SQLException e) { e.printStackTrace(); @@ -474,4 +487,16 @@ public boolean saveOrUpdateCustomBoatUtilsMode(CustomBoatUtilsMode mode) { return false; } } + + @Override + public void saveTeamTuning(int teamId, String attributesJson) { + try { + DB.executeUpdate( + "INSERT OR REPLACE INTO ts_team_tuning (teamId, attributesJson) VALUES (?, ?)", + teamId, attributesJson + ); + } catch (SQLException e) { + e.printStackTrace(); + } + } } diff --git a/src/main/java/me/makkuusen/timing/system/database/TeamDatabase.java b/src/main/java/me/makkuusen/timing/system/database/TeamDatabase.java index 5b34b3ac..f8a6cff5 100644 --- a/src/main/java/me/makkuusen/timing/system/database/TeamDatabase.java +++ b/src/main/java/me/makkuusen/timing/system/database/TeamDatabase.java @@ -105,4 +105,18 @@ public interface TeamDatabase { * @return Optional containing the team if found */ java.util.Optional getTeam(Integer teamId); + + /** + * Save or update a team's tuning profile as JSON + * @param teamId the team ID + * @param attributesJson the JSON string of tuning attributes + */ + void saveTeamTuning(int teamId, String attributesJson); + + /** + * Load a team's tuning profile JSON + * @param teamId the team ID + * @return the JSON string, or null if no tuning saved yet + */ + String loadTeamTuning(int teamId) throws SQLException; } \ No newline at end of file diff --git a/src/main/java/me/makkuusen/timing/system/database/updates/Version14.java b/src/main/java/me/makkuusen/timing/system/database/updates/Version14.java index d07f1280..cad0d65e 100644 --- a/src/main/java/me/makkuusen/timing/system/database/updates/Version14.java +++ b/src/main/java/me/makkuusen/timing/system/database/updates/Version14.java @@ -5,7 +5,7 @@ import java.sql.SQLException; public class Version14 { - + public static void updateMySQL() throws SQLException { try { DB.executeUpdate("ALTER TABLE `ts_heats` ADD COLUMN `pushToPass` tinyint(1) NOT NULL DEFAULT 0"); @@ -25,4 +25,4 @@ public static void updateSQLite() throws SQLException { } } } -} +} \ No newline at end of file diff --git a/src/main/java/me/makkuusen/timing/system/database/updates/Version16.java b/src/main/java/me/makkuusen/timing/system/database/updates/Version16.java new file mode 100644 index 00000000..4d7166bb --- /dev/null +++ b/src/main/java/me/makkuusen/timing/system/database/updates/Version16.java @@ -0,0 +1,62 @@ +package me.makkuusen.timing.system.database.updates; + +import co.aikar.idb.DB; + +import java.sql.SQLException; + +public class Version16 { + + public static void updateMySQL() throws SQLException { + DB.executeUpdate(""" + CREATE TABLE IF NOT EXISTS `ts_team_tuning` ( + `teamId` int(11) NOT NULL, + `attributesJson` TEXT NOT NULL, + PRIMARY KEY (`teamId`), + FOREIGN KEY (`teamId`) REFERENCES `ts_teams`(`id`) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + """); + + try { + DB.executeUpdate("ALTER TABLE `ts_events` ADD COLUMN `tuningEnabled` tinyint(1) NOT NULL DEFAULT 0"); + } catch (SQLException e) { + if (e.getErrorCode() != 1060) { + throw e; + } + } + + try { + DB.executeUpdate("ALTER TABLE `ts_heats` ADD COLUMN `liveTuningEnabled` tinyint(1) NOT NULL DEFAULT 0"); + } catch (SQLException e) { + if (e.getErrorCode() != 1060) { + throw e; + } + } + } + + public static void updateSQLite() throws SQLException { + DB.executeUpdate(""" + CREATE TABLE IF NOT EXISTS `ts_team_tuning` ( + `teamId` INTEGER NOT NULL, + `attributesJson` TEXT NOT NULL, + PRIMARY KEY (`teamId`), + FOREIGN KEY (`teamId`) REFERENCES `ts_teams`(`id`) ON DELETE CASCADE + ); + """); + + try { + DB.executeUpdate("ALTER TABLE `ts_events` ADD COLUMN `tuningEnabled` INTEGER NOT NULL DEFAULT 0"); + } catch (SQLException e) { + if (!e.getMessage().toLowerCase().contains("duplicate column")) { + throw e; + } + } + + try { + DB.executeUpdate("ALTER TABLE `ts_heats` ADD COLUMN `liveTuningEnabled` INTEGER NOT NULL DEFAULT 0"); + } catch (SQLException e) { + if (!e.getMessage().toLowerCase().contains("duplicate column")) { + throw e; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/me/makkuusen/timing/system/event/Event.java b/src/main/java/me/makkuusen/timing/system/event/Event.java index 1127e278..69eba6fe 100644 --- a/src/main/java/me/makkuusen/timing/system/event/Event.java +++ b/src/main/java/me/makkuusen/timing/system/event/Event.java @@ -40,6 +40,7 @@ public class Event { private long date; private boolean openSign; private EventState state; + private Boolean tuningEnabled; public Event(DbRow data) { id = data.getInt("id"); @@ -50,6 +51,7 @@ public Event(DbRow data) { track = maybeTrack.orElse(null); state = EventState.valueOf(data.getString("state")); openSign = data.get("open") instanceof Boolean ? data.get("open") : data.get("open").equals(1); + tuningEnabled = data.get("tuningEnabled") instanceof Boolean ? data.get("tuningEnabled") : Integer.valueOf(1).equals(data.get("tuningEnabled")); eventSchedule = new EventSchedule(); eventCountdown = new EventCountdown(this); } @@ -198,4 +200,11 @@ public void setEventSchedule(EventSchedule es) { public enum EventState { SETUP, RUNNING, FINISHED } + + public boolean isTuningEnabled(){return tuningEnabled;} + + public void setTuningEnabled(boolean enabled) { + this.tuningEnabled = enabled; + TimingSystem.getEventDatabase().eventSet(id, "tuningEnabled", enabled); + } } diff --git a/src/main/java/me/makkuusen/timing/system/gui/BoatSetupGui.java b/src/main/java/me/makkuusen/timing/system/gui/BoatSetupGui.java new file mode 100644 index 00000000..4a90abde --- /dev/null +++ b/src/main/java/me/makkuusen/timing/system/gui/BoatSetupGui.java @@ -0,0 +1,159 @@ +package me.makkuusen.timing.system.gui; + +import me.makkuusen.timing.system.ItemBuilder; +import me.makkuusen.timing.system.database.EventDatabase; +import me.makkuusen.timing.system.heat.Heat; +import me.makkuusen.timing.system.participant.Driver; +import me.makkuusen.timing.system.sounds.PlaySound; +import me.makkuusen.timing.system.team.Team; +import me.makkuusen.timing.system.team.TeamTuning; +import me.makkuusen.timing.system.theme.Text; +import me.makkuusen.timing.system.theme.messages.Gui; +import me.makkuusen.timing.system.tplayer.TPlayer; +import me.makkuusen.timing.system.tuning.Attribute; +import me.makkuusen.timing.system.tuning.Part; +import me.makkuusen.timing.system.tuning.PartCategory; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.ArrayList; +import java.util.List; + +public class BoatSetupGui extends BaseGui{ + + private final Team team; + + public BoatSetupGui(TPlayer tPlayer, Team team){ + super(Text.getGuiComponent(tPlayer.getPlayer(), Gui.SETTINGS_TITLE), 3); + this.team = team; + setButtons(tPlayer); + } + + public GuiButton boatDisplay(TPlayer tPlayer){ + TeamTuning tuning = team.getTuning(); + + List loreToSet = new ArrayList<>(); + int rating = 0; + + for (Part part : tuning.getEquippedParts().values()){ + rating += part.getRating(); + } + + loreToSet.add(Component.text("rating: " + rating).color(NamedTextColor.YELLOW)); + + applyLiveTuningIfActive(team); + + for (Attribute thing : tuning.getAttributes().keySet()){ + loreToSet.add(Component.text( + thing.toString() + ": [" + + (tuning.getAttributes().get(thing) - 5) + + "]" + ).color(NamedTextColor.WHITE)); + } + + ItemStack item; + + Component itemName = Component.text(team.getName() + " tuning") + .color(getRatingColor(rating)); + + // Check if all three categories contain the same item + List equippedParts = new ArrayList<>(tuning.getEquippedParts().values()); + + boolean sameItem = equippedParts.size() == 3 && + equippedParts.get(0).getItem(tPlayer).getType() == equippedParts.get(1).getItem(tPlayer).getType() && + equippedParts.get(0).getItem(tPlayer).getType() == equippedParts.get(2).getItem(tPlayer).getType(); + + if (sameItem) { + item = equippedParts.get(0).getItem(tPlayer).clone(); + } else { + item = new ItemBuilder(Material.OAK_BOAT).build(); + } + + + ItemMeta im = item.getItemMeta(); + + if (im != null) { + im.displayName(itemName); + + im.addItemFlags(ItemFlag.HIDE_ENCHANTS); + im.addItemFlags(ItemFlag.HIDE_ITEM_SPECIFICS); + im.addItemFlags(ItemFlag.HIDE_DYE); + im.addItemFlags(ItemFlag.HIDE_ATTRIBUTES); + + im.lore(loreToSet); + item.setItemMeta(im); + } + + return new GuiButton(item); + } + + public GuiButton getCategoryButton(TPlayer tPlayer, PartCategory category){ + TeamTuning tuning = team.getTuning(); + Part currentlyEquipped = tuning.getEquippedParts().get(category); + GuiButton button; + if (currentlyEquipped == null){ + button = new GuiButton( + new ItemBuilder(category.getMaterial()) + .setName("Empty " + category) + .build() + ); + } else{ + button = new GuiButton(currentlyEquipped.getItem(tPlayer)); + } + + button.setAction(() -> new PartSelectGui(tPlayer, team, category).show(tPlayer.getPlayer())); + return button; + } + + private void setButtons(TPlayer tPlayer){ + setItem(boatDisplay(tPlayer), 13); + + int x = 18; + + for (PartCategory category : PartCategory.values()){ + setItem(getCategoryButton(tPlayer, category), x); + x++; + } + } + + private NamedTextColor getRatingColor(int rating) { + if (rating > 0 && rating < 300) { + return NamedTextColor.GREEN; + } else if (rating < 0){ + return NamedTextColor.DARK_GRAY; + }else if (rating == 0){ + return NamedTextColor.WHITE; + }else if (rating < 900) { + return NamedTextColor.YELLOW; + } else if (rating < 1500) { + return NamedTextColor.GOLD; + } else if (rating < 2400) { + return NamedTextColor.RED; + } else { + return NamedTextColor.DARK_BLUE; + } + } + + public static void applyLiveTuningIfActive(Team team) { + // For each online player on the team + for (TPlayer tPlayer : team.getPlayers()) { + Player player = tPlayer.getPlayer(); + if (player == null) continue; // Offline + + // Check if they're in an active heat (O(1) lookup) + Driver driver = EventDatabase.playerInRunningHeat.get(player.getUniqueId()); + if (driver == null) continue; // Not racing + + Heat heat = driver.getHeat(); + if (!heat.getLiveTuningEnabled()) continue; // Live tuning disabled + + // Apply the updated tuning immediately + heat.applyTuningToPlayer(player, team.getTuning()); + } + } +} diff --git a/src/main/java/me/makkuusen/timing/system/gui/PartSelectGui.java b/src/main/java/me/makkuusen/timing/system/gui/PartSelectGui.java new file mode 100644 index 00000000..0fd25e0f --- /dev/null +++ b/src/main/java/me/makkuusen/timing/system/gui/PartSelectGui.java @@ -0,0 +1,153 @@ +package me.makkuusen.timing.system.gui; + +import me.makkuusen.timing.system.ItemBuilder; +import me.makkuusen.timing.system.team.Team; +import me.makkuusen.timing.system.team.TeamTuning; +import me.makkuusen.timing.system.tplayer.TPlayer; +import me.makkuusen.timing.system.tuning.Part; +import me.makkuusen.timing.system.tuning.PartCategory; +import me.makkuusen.timing.system.tuning.PartManager; +import me.makkuusen.timing.system.sounds.PlaySound; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.Comparator; +import java.util.List; + +public class PartSelectGui extends BaseGui { + + private static final int PARTS_PER_PAGE = 45; + + private final Team team; + private final TPlayer tPlayer; + private final PartCategory category; + + private int page; + private int maxPage; + + public PartSelectGui(TPlayer tPlayer, Team team, PartCategory category) { + this(tPlayer, team, category, 0); + } + + public PartSelectGui(TPlayer tPlayer, Team team, PartCategory category, int page) { + super(Component.text(category.toString()), 6); + + this.tPlayer = tPlayer; + this.team = team; + this.category = category; + this.page = page; + + update(); + } + + private void update() { + setButtons(); + setNavigation(); + } + + public GuiButton getCategoryButton(TPlayer tPlayer, PartCategory category) { + return new GuiButton( + new ItemBuilder(category.getMaterial()) + .setName(category.toString()) + .build() + ); + } + + public GuiButton getPartButton(TPlayer tPlayer, Part part) { + var button = new GuiButton(part.getItem(tPlayer)); + + button.setAction(() -> { + TeamTuning tuning = team.getTuning(); + + tuning.equipPart(part); + + new BoatSetupGui(tPlayer, team).show(tPlayer.getPlayer()); + }); + + return button; + } + + private List getParts() { + return PartManager.getParts().stream() + .filter(part -> part.getCategoryName() == category) + .sorted(Comparator.comparingInt(Part::getRating)) + .toList(); + } + + private void setButtons() { + setItem(getCategoryButton(tPlayer, category), 0); + + List parts = getParts(); + + maxPage = Math.max(0, (parts.size() - 1) / PARTS_PER_PAGE); + + // Prevent invalid pages + if (page > maxPage) { + page = maxPage; + } + + int start = page * PARTS_PER_PAGE; + int end = Math.min(start + PARTS_PER_PAGE, parts.size()); + + for (int i = start; i < end; i++) { + Part part = parts.get(i); + + // Slots 0-44 are reserved for parts + int slot = i - start; + + setItem(getPartButton(tPlayer, part), slot); + } + } + + private void setNavigation() { + // Clear bottom row + for (int slot = 45; slot <= 53; slot++) { + removeItem(slot); + } + + // Previous page + if (page > 0) { + ItemStack previous = new ItemBuilder(Material.RED_STAINED_GLASS_PANE) + .setName(Component.text("Previous Page")) + .build(); + + GuiButton button = new GuiButton(previous); + + button.setAction(() -> { + PlaySound.pageTurn(tPlayer); + openPage(page - 1); + }); + + setItem(button, 48); + } + + // Page indicator + ItemStack pageItem = new ItemBuilder(Material.PAPER) + .setName(Component.text("Page " + (page + 1) + "/" + (maxPage + 1))) + .build(); + + setItem(new GuiButton(pageItem), 49); + + // Next page + if (page < maxPage) { + ItemStack next = new ItemBuilder(Material.LIME_STAINED_GLASS_PANE) + .setName(Component.text("Next Page")) + .build(); + + GuiButton button = new GuiButton(next); + + button.setAction(() -> { + PlaySound.pageTurn(tPlayer); + openPage(page + 1); + }); + + setItem(button, 50); + } + } + + private void openPage(int newPage) { + new PartSelectGui(tPlayer, team, category, newPage) + .show(tPlayer.getPlayer()); + } +} \ No newline at end of file diff --git a/src/main/java/me/makkuusen/timing/system/heat/Heat.java b/src/main/java/me/makkuusen/timing/system/heat/Heat.java index 068d1a7e..dfabf94d 100644 --- a/src/main/java/me/makkuusen/timing/system/heat/Heat.java +++ b/src/main/java/me/makkuusen/timing/system/heat/Heat.java @@ -8,6 +8,9 @@ import me.makkuusen.timing.system.TimingSystem; import me.makkuusen.timing.system.api.events.HeatFinishEvent; import me.makkuusen.timing.system.api.events.driver.DriverPlacedOnGrid; +import me.makkuusen.timing.system.boatutils.BoatUtilsManager; +import me.makkuusen.timing.system.boatutils.BoatUtilsMode; +import me.makkuusen.timing.system.boatutils.CustomBoatUtilsMode; import me.makkuusen.timing.system.database.EventDatabase; import me.makkuusen.timing.system.database.TSDatabase; import me.makkuusen.timing.system.drs.PushToPass; @@ -24,23 +27,23 @@ import me.makkuusen.timing.system.round.QualificationRound; import me.makkuusen.timing.system.round.Round; import me.makkuusen.timing.system.team.Team; +import me.makkuusen.timing.system.team.TeamManager; +import me.makkuusen.timing.system.team.TeamTuning; +import me.makkuusen.timing.system.team.TuningAttribute; +import me.makkuusen.timing.system.track.Track; import me.makkuusen.timing.system.track.locations.TrackLocation; import me.makkuusen.timing.system.tplayer.TPlayer; +import me.makkuusen.timing.system.tuning.Attribute; import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; import java.sql.SQLException; import java.time.Duration; import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static me.makkuusen.timing.system.loneliness.DeltaGhostingController.checkDeltas; +import java.util.*; @Getter @Setter @@ -75,6 +78,7 @@ public class Heat { private Boolean drs; private Integer drsDowntime; private Boolean pushToPass; + private Boolean liveTuningEnabled; private SpectatorScoreboard scoreboard; private Instant lastScoreboardUpdate = Instant.now(); @@ -105,6 +109,7 @@ public Heat(DbRow data, Round round) { drs = data.get("drs") instanceof Boolean ? data.get("drs") : data.get("drs") == null ? false : data.get("drs").equals(1); drsDowntime = data.get("drsDowntime") == null ? 1 : data.getInt("drsDowntime"); pushToPass = data.get("pushToPass") instanceof Boolean ? data.get("pushToPass") : data.get("pushToPass") == null ? false : data.get("pushToPass").equals(1); + liveTuningEnabled = data.get("liveTuningEnabled") instanceof Boolean ? data.get("liveTuningEnabled") : data.get("liveTuningEnabled") == null ? false : data.get("liveTuningEnabled").equals(1); startDelay = data.get("startDelay") == null ? round instanceof FinalRound ? TimingSystem.configuration.getFinalStartDelayInMS() : TimingSystem.configuration.getQualyStartDelayInMS() : data.getInt("startDelay"); rowStartDelay = data.get("rowStartDelay") == null ? null : data.getInt("rowStartDelay"); fastestLapUUID = data.getString("fastestLapUUID") == null ? null : UUID.fromString(data.getString("fastestLapUUID")); @@ -226,15 +231,20 @@ public void startHeat() { entry.setStartTime(TimingSystem.currentTime); } } - + + if (getEvent().isTuningEnabled()) { + applyTeamTuning(); + } + if (getPushToPass() != null && getPushToPass()) { - getDrivers().values().forEach(driver -> + getDrivers().values().forEach(driver -> me.makkuusen.timing.system.drs.PushToPass.initializePushToPass(driver.getTPlayer().getUniqueId()) ); } - + int gridsPerRow = getEvent().getTrack() == null ? 0 : getEvent().getTrack().getGridsPerRow(); + if (round instanceof QualificationRound) { gridManager.startDriversWithDelay(getStartDelay(), true, getStartPositions(), gridsPerRow, getRowStartDelay()); return; @@ -291,9 +301,9 @@ public boolean finishHeat() { getDrivers().values().forEach(driver -> { EventDatabase.removePlayerFromRunningHeat(driver.getTPlayer().getUniqueId()); - + PushToPass.cleanupPlayer(driver.getTPlayer().getUniqueId()); - + if (driver.getEndTime() == null) { driver.removeUnfinishedLap(); if (!driver.getLaps().isEmpty()) { @@ -363,9 +373,9 @@ public boolean resetHeat() { getDrivers().values().forEach(driver -> { driver.reset(); EventDatabase.removePlayerFromRunningHeat(driver.getTPlayer().getUniqueId()); - + PushToPass.cleanupPlayer(driver.getTPlayer().getUniqueId()); - + if (driver.getTPlayer().getPlayer() != null) { LonelinessController.updatePlayersVisibility(driver.getTPlayer().getPlayer()); if (!LonelinessController.unghost(driver.getTPlayer().getUniqueId())) { @@ -650,6 +660,11 @@ public void setPushToPass(Boolean pushToPass) { TimingSystem.getEventDatabase().heatSet(getId(), "pushToPass", pushToPass); } + public void setLiveTuningEnabled(Boolean liveTuningEnabled) { + this.liveTuningEnabled = liveTuningEnabled; + TimingSystem.getEventDatabase().heatSet(getId(), "liveTuningEnabled", liveTuningEnabled); + } + public void setCollisionMode(CollisionMode collisionMode) { this.collisionMode = collisionMode; TimingSystem.getEventDatabase().heatSet(getId(), "collisionMode", collisionMode.name()); @@ -820,4 +835,121 @@ private void loadTeamEntries() { ApiUtilities.msgConsole("Failed to load team heat entries for heat " + id + ": " + e.getMessage()); } } + + public void applyTeamTuning() { + TimingSystem.getPlugin().getLogger().info("[TimingSystem] Applying team tuning to heat"); + + for (Map.Entry entry : getDrivers().entrySet()) { + Driver driver = entry.getValue(); + Player player = driver.getTPlayer().getPlayer(); + if (player == null) continue; + + Optional teamOpt = getDriverTeam(driver); + if (teamOpt.isEmpty()) continue; + + applyTuningToPlayer(player, teamOpt.get().getTuning()); + } + } + + /** + * Get the team for a driver - works with or without boat switching + */ + private Optional getDriverTeam(Driver driver) { + // If boat switching is enabled, check TeamHeatEntry first + if (isBoatSwitchingEnabled()) { + Optional teamEntry = getTeamEntryByPlayer(driver.getTPlayer().getUniqueId()); + if (teamEntry.isPresent() && teamEntry.get().getTeam() != null) { + return Optional.of(teamEntry.get().getTeam()); + } + } + + // Fallback: check if the player is in any team + List playerTeams = TeamManager.getPlayerTeams(driver.getTPlayer()); + if (!playerTeams.isEmpty()) { + return Optional.of(playerTeams.get(0)); + } + + return Optional.empty(); + } + + public void applyTuningToPlayer(Player player, TeamTuning tuning) { + Track track = getEvent().getTrack(); + float effectPercent = TimingSystem.configuration.getTuningEffect() / 200.0f; + int baseStat = TeamTuning.BASE_STAT_VALUE; + + // Apply base track mode first + Integer customModeID = track.getCustomBoatUtilsModeId(); + CustomBoatUtilsMode baseMode = null; + + if (customModeID != null) { + baseMode = TimingSystem.getTrackDatabase().getCustomBoatUtilsModeFromId(customModeID); + if (baseMode != null) { + baseMode.applyToPlayer(player); + BoatUtilsManager.playerCustomBoatUtilsModeId.put(player.getUniqueId(), customModeID); + } + } else { + BoatUtilsManager.sendBoatUtilsModePluginMessage(player, track.getBoatUtilsMode(), track, false); + } + + TimingSystem.getPlugin().getLogger().info("[TimingSystem] Applying tuning to " + player.getName()); + + // Loop through all attributes and apply them + for (Map.Entry attrEntry : tuning.getAttributes().entrySet()) { + Attribute attrName = attrEntry.getKey(); + int statValue = attrEntry.getValue(); + + TuningAttribute attribute = TeamTuning.AVAILABLE_ATTRIBUTES.get(attrName); + if (attribute == null) { + TimingSystem.getPlugin().getLogger().warning("[TimingSystem] Unknown attribute: " + attrName); + continue; + } + + // Modifier scaled by the attribute's balance multiplier + float modifier = 1.0f + ((statValue - baseStat) * effectPercent * attribute.getMultiplier()); + float baseValue = attribute.getBaseValue(baseMode); + float newValue = baseValue * modifier; + + if (attribute.isPerBlock()) { + sendPerBlockSlipperinessPacket(player, attribute.getBlockId(), newValue); + } else { + sendTuningPacket(player, attribute.getBoatUtilsPacketId(), newValue); + } + sendTuningPacket(player, (short) 14, 1); + + //TimingSystem.getPlugin().getLogger().info(String.format("[TimingSystem] %s: %d pts -> x%.2f (mult:%.1f) -> base %.4f -> final %.4f", attrName, statValue, modifier, attribute.getMultiplier(), baseValue, newValue)); + } + } + + private void sendTuningPacket(Player player, short packetId, float value) { + try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(byteStream)) { + out.writeShort(packetId); + out.writeFloat(value); + player.sendPluginMessage(TimingSystem.getPlugin(), "openboatutils:settings", byteStream.toByteArray()); + } catch (IOException e) { + TimingSystem.getPlugin().getLogger().severe("[TimingSystem] Failed to send tuning packet: " + packetId); + e.printStackTrace(); + } + } + + private void sendPerBlockSlipperinessPacket(Player player, String blockId, float value) { + try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(byteStream)) { + out.writeShort(3); // PACKET_ID_SET_BLOCKS_SLIPPERINESS + out.writeFloat(value); + byte[] blockIdBytes = blockId.getBytes(java.nio.charset.StandardCharsets.UTF_8); + // Write string as varint length + bytes (BoatUtils wire format) + int length = blockIdBytes.length; + while ((length & ~0x7F) != 0) { + out.writeByte((length & 0x7F) | 0x80); + length >>>= 7; + } + out.writeByte(length); + out.write(blockIdBytes); + player.sendPluginMessage(TimingSystem.getPlugin(), "openboatutils:settings", byteStream.toByteArray()); + } catch (IOException e) { + TimingSystem.getPlugin().getLogger().severe("[TimingSystem] Failed to send per-block packet for: " + blockId); + e.printStackTrace(); + } + } } diff --git a/src/main/java/me/makkuusen/timing/system/team/Team.java b/src/main/java/me/makkuusen/timing/system/team/Team.java index dbe16e67..4b6d63c4 100644 --- a/src/main/java/me/makkuusen/timing/system/team/Team.java +++ b/src/main/java/me/makkuusen/timing/system/team/Team.java @@ -3,9 +3,11 @@ import co.aikar.idb.DbRow; import lombok.Getter; import me.makkuusen.timing.system.ApiUtilities; +import me.makkuusen.timing.system.TimingSystem; import me.makkuusen.timing.system.database.TSDatabase; import me.makkuusen.timing.system.tplayer.TPlayer; +import java.sql.SQLException; import java.util.*; @Getter @@ -16,6 +18,7 @@ public class Team implements Comparable { private final long dateCreated; private final UUID creator; private boolean playersLoaded = false; + private TeamTuning tuning; /** * Constructor for creating a Team from database data @@ -23,7 +26,7 @@ public class Team implements Comparable { public Team(DbRow data) { this.id = data.getInt("id"); this.name = data.getString("name"); - this.dateCreated = data.getLong("dateCreated"); + this.dateCreated = data.getInt("dateCreated"); this.creator = UUID.fromString(data.getString("creator")); this.players = new ArrayList<>(); } @@ -178,4 +181,18 @@ public String toString() { ", playerCount=" + players.size() + '}'; } + + public TeamTuning getTuning() { + if (tuning == null) { + try { + String json = TimingSystem.getTeamDatabase().loadTeamTuning(id); + tuning = TeamTuning.fromJson(id, json); + } catch (SQLException e) { + tuning = new TeamTuning(id); + } + } + return tuning; + } + + } \ No newline at end of file diff --git a/src/main/java/me/makkuusen/timing/system/team/TeamTuning.java b/src/main/java/me/makkuusen/timing/system/team/TeamTuning.java new file mode 100644 index 00000000..6fabef03 --- /dev/null +++ b/src/main/java/me/makkuusen/timing/system/team/TeamTuning.java @@ -0,0 +1,228 @@ +package me.makkuusen.timing.system.team; + +import com.google.gson.Gson; +import lombok.Getter; +import lombok.Setter; +import me.makkuusen.timing.system.TimingSystem; +import me.makkuusen.timing.system.tuning.Attribute; +import me.makkuusen.timing.system.tuning.Part; +import me.makkuusen.timing.system.tuning.PartCategory; +import me.makkuusen.timing.system.tuning.PartManager; +import org.w3c.dom.Attr; + +import java.util.*; + +@Getter +@Setter +public class TeamTuning { + private int id; + private int teamID; + private Map attributes = new LinkedHashMap<>(); + private Map equippedParts = new EnumMap<>(PartCategory.class); + + public int MAX_TOTAL_POINTS = 30; + public static final int MIN_STAT_VALUE = 0; + public static final int MAX_STAT_VALUE = 30000; + public static final int BASE_STAT_VALUE = 5; + + // Define all available attributes here + // when adding new ones change here and parts.java + public static final Map AVAILABLE_ATTRIBUTES = new LinkedHashMap<>(); + static { + // name, packetId, vanillaDefault, category, multiplier + // Multiplier > 1 amplifies the effect per point, < 1 dampens it + + // --- Acceleration --- + AVAILABLE_ATTRIBUTES.put(Attribute.FORWARD_ACCEL, + new TuningAttribute("forwardAcceleration", (short)11, 0.04f, "acceleration", 0.6f)); + + AVAILABLE_ATTRIBUTES.put(Attribute.TURNING_FORWARD_ACCEL, + new TuningAttribute("turningForwardAcceleration", (short)13, 0.005f, "acceleration", 10.0f)); + + AVAILABLE_ATTRIBUTES.put(Attribute.BACKWARD_ACCEL, + new TuningAttribute("backwardAcceleration", (short)12, 0.005f, "acceleration", 9.0f)); + + // --- Speed --- + AVAILABLE_ATTRIBUTES.put(Attribute.DEFAULT_SLIPPERINESS, + new TuningAttribute("defaultSlipperiness", (short)2, 0.6f, "speed", 3f)); + + AVAILABLE_ATTRIBUTES.put(Attribute.PACKED_ICE_SLIPPERINESS, + new TuningAttribute("packedIceSlipperiness", (short)3, 0.98f, "speed", 0.1f)); + + AVAILABLE_ATTRIBUTES.put(Attribute.BLUE_ICE_SLIPPERINESS, + new TuningAttribute("blueIceSlipperiness", (short)3, 0.989f, "speed", 0.1f)); + + // --- Handling --- + AVAILABLE_ATTRIBUTES.put(Attribute.YAW_ACCEL, + new TuningAttribute("yawAcceleration", (short)10, 1.0f, "handling", 9.0f)); + + // --- new stuff i'll move later --- + AVAILABLE_ATTRIBUTES.put(Attribute.SCALE, + new TuningAttribute("scale", (short)36, 1.0f, "handling", 1.0f)); + AVAILABLE_ATTRIBUTES.put(Attribute.MAX_SPEED, + new TuningAttribute("maxSpeed", (short)45, 3.0f, "speed", 1.0f)); + + AVAILABLE_ATTRIBUTES.put(Attribute.MAX_SPEED_RESISTANCE, + new TuningAttribute("maxSpeedResistance", (short)46, 1.0f, "speed", 1.0f)); + + AVAILABLE_ATTRIBUTES.put(Attribute.BRAKE_SLIPPERINESS, + new TuningAttribute("brakeSlipperiness", (short)41, 1.0f, "speed", -0.1f)); + + AVAILABLE_ATTRIBUTES.put(Attribute.WALLTAP_MULTIPLIER, + new TuningAttribute("WALLTAP_MULTIPLIER", (short)34, 0.001f, "handling", 1000.0f)); + + AVAILABLE_ATTRIBUTES.put(Attribute.LATERAL_SLIPPERINESS, + new TuningAttribute("LATERAL_SLIPPERINESS", (short)40, 1.0f, "handling", -1.0f)); + } + + + public void setMAX_TOTAL_POINTS(int MAX_TOTAL_POINTS) { + this.MAX_TOTAL_POINTS = MAX_TOTAL_POINTS; + } + + public TeamTuning(int teamID){ + this.teamID = teamID; + // Initialize all attributes at base value + for (Attribute attrName : AVAILABLE_ATTRIBUTES.keySet()) { + attributes.put(attrName, BASE_STAT_VALUE); + } + } + + public void equipPart(Part part){ + equippedParts.put(part.getCategoryName(), part); + rebuildStats(); + } + + public void unequipPart(PartCategory category){ + equippedParts.remove(category); + rebuildStats(); + } + + public void rebuildStats() { + resetAttributes(); + + for (Part part : equippedParts.values()) { + for (Attribute attr : part.getAttributes().keySet()) { + + int value = part.getValue(attr); + + // there is probbably a better way of doing this but i'm tired + int toReplace; + try{ + toReplace = attributes.get(attr); + } catch (Exception e) { + toReplace = BASE_STAT_VALUE; + } + + attributes.put( + attr, + toReplace + value + ); + } + } + } + + public void resetAttributes(){ + Set keySet = attributes.keySet(); + attributes.clear(); + for (Attribute attr : keySet){ + attributes.put(attr, BASE_STAT_VALUE); + } + } + + public int getTotalRating(){ + int total = 0; + + for (Part part : equippedParts.values()){ + total += part.getRating(); + } + + return total; + } + + public void increaseAttribute(Attribute name){ + try{ + int current = attributes.get(name); + if (current < MAX_STAT_VALUE && getTotalPoints() < MAX_TOTAL_POINTS) { + attributes.put(name, current + 1); + } + } catch(Exception e){ + e.printStackTrace(); + } + } + + public void decreaseAttribute(Attribute name){ + try{ + int current = attributes.get(name); + if (current > MIN_STAT_VALUE) { + attributes.put(name, current - 1); + } + } catch(Exception e){ + e.printStackTrace(); + } + } + + public int getTotalPoints(){ + int total = 0; + for (int value : attributes.values()){ + total += value; + } + return total; + } + + public boolean isValid() { + return getTotalPoints() <= MAX_TOTAL_POINTS; + } + + public String toJson() { + TeamTuningData data = new TeamTuningData(); + + for (Map.Entry entry : equippedParts.entrySet()) { + data.equippedParts.put( + entry.getKey(), + entry.getValue().getId() + ); + } + + return new Gson().toJson(data); + } + + public static TeamTuning fromJson(int teamID, String json) { + TeamTuning tuning = new TeamTuning(teamID); + + if (json == null || json.isEmpty()) { + return tuning; + } + + Gson gson = new Gson(); + + // Detect legacy format + if (json.contains("forwardAcceleration")) { + + // old system fallback + return tuning; + } + + TeamTuningData data = + gson.fromJson(json, TeamTuningData.class); + + for (Map.Entry entry : + data.equippedParts.entrySet()) { + + + Part part = PartManager.getPart(entry.getValue()); + + if (part != null) { + tuning.equipPart(part); + } + } + + tuning.rebuildStats(); + + return tuning; + } + + public void save() { + TimingSystem.getTeamDatabase().saveTeamTuning(teamID, toJson()); + } +} diff --git a/src/main/java/me/makkuusen/timing/system/team/TeamTuningData.java b/src/main/java/me/makkuusen/timing/system/team/TeamTuningData.java new file mode 100644 index 00000000..25a0452f --- /dev/null +++ b/src/main/java/me/makkuusen/timing/system/team/TeamTuningData.java @@ -0,0 +1,10 @@ +package me.makkuusen.timing.system.team; + +import me.makkuusen.timing.system.tuning.PartCategory; + +import java.util.HashMap; +import java.util.Map; + +public class TeamTuningData { + public Map equippedParts = new HashMap<>(); +} diff --git a/src/main/java/me/makkuusen/timing/system/team/TuningAttribute.java b/src/main/java/me/makkuusen/timing/system/team/TuningAttribute.java new file mode 100644 index 00000000..3dc1ab7a --- /dev/null +++ b/src/main/java/me/makkuusen/timing/system/team/TuningAttribute.java @@ -0,0 +1,67 @@ +package me.makkuusen.timing.system.team; + +import lombok.Getter; +import lombok.Setter; +import me.makkuusen.timing.system.boatutils.CustomBoatUtilsMode; + +@Getter +public class TuningAttribute { + private final String name; + private final short boatUtilsPacketId; + private final float vanillaDefault; + @Getter private final String category; + @Setter private float multiplier; // Balance knob: >1 amplifies effect, <1 dampens it + + public TuningAttribute(String name, short boatUtilsPacketId, float vanillaDefault, String category, float multiplier) { + this.name = name; + this.boatUtilsPacketId = boatUtilsPacketId; + this.vanillaDefault = vanillaDefault; + this.category = category; + this.multiplier = multiplier; + } + + /** + * For simple float attributes - get base from track mode or fall back to vanilla + */ + public float getBaseValue(CustomBoatUtilsMode mode) { + if (mode == null) return vanillaDefault; + + return switch (name) { + case "forwardAcceleration" -> mode.getForwardAcceleration(); + case "turningForwardAcceleration" -> mode.getTurningForwardAcceleration(); + case "backwardAcceleration" -> mode.getBackwardAcceleration(); + case "defaultSlipperiness" -> mode.getDefaultSlipperiness(); + case "yawAcceleration" -> mode.getYawAcceleration(); + // Per-block slipperiness: fall back to vanilla, track mode doesn't expose these easily + case "packedIceSlipperiness" -> mode.getBlocksSlipperiness().getOrDefault("minecraft:packed_ice", vanillaDefault); + case "blueIceSlipperiness" -> mode.getBlocksSlipperiness().getOrDefault("minecraft:blue_ice", vanillaDefault); + + case "scale" -> mode.getScale(); + case "maxSpeed" -> mode.getMaxSpeed(); + case "maxSpeedResistance" -> mode.getMaxSpeedResistance(); + case "brakeSlipperiness" -> mode.getBrakeSlipperiness(); + + case "LATERAL_SLIPPERINESS" -> mode.getLateralSlipperiness(); + case "WALLTAP_MULTIPLIER" -> mode.getWalltapMultiplier(); + default -> vanillaDefault; + }; + } + + /** + * Whether this attribute needs a per-block packet (packet 3) instead of a simple float packet + */ + public boolean isPerBlock() { + return name.equals("packedIceSlipperiness") || name.equals("blueIceSlipperiness"); + } + + /** + * Get the block ID string for per-block attributes + */ + public String getBlockId() { + return switch (name) { + case "packedIceSlipperiness" -> "minecraft:packed_ice"; + case "blueIceSlipperiness" -> "minecraft:blue_ice"; + default -> null; + }; + } +} diff --git a/src/main/java/me/makkuusen/timing/system/tuning/Attribute.java b/src/main/java/me/makkuusen/timing/system/tuning/Attribute.java new file mode 100644 index 00000000..b8042b10 --- /dev/null +++ b/src/main/java/me/makkuusen/timing/system/tuning/Attribute.java @@ -0,0 +1,17 @@ +package me.makkuusen.timing.system.tuning; + +public enum Attribute { + FORWARD_ACCEL, + YAW_ACCEL, + DEFAULT_SLIPPERINESS, + PACKED_ICE_SLIPPERINESS, + BLUE_ICE_SLIPPERINESS, + TURNING_FORWARD_ACCEL, + BACKWARD_ACCEL, + SCALE, + MAX_SPEED, + MAX_SPEED_RESISTANCE, + BRAKE_SLIPPERINESS, + LATERAL_SLIPPERINESS, + WALLTAP_MULTIPLIER, +} diff --git a/src/main/java/me/makkuusen/timing/system/tuning/Part.java b/src/main/java/me/makkuusen/timing/system/tuning/Part.java new file mode 100644 index 00000000..8453d13c --- /dev/null +++ b/src/main/java/me/makkuusen/timing/system/tuning/Part.java @@ -0,0 +1,127 @@ +package me.makkuusen.timing.system.tuning; + +import lombok.Getter; +import lombok.Setter; +import me.makkuusen.timing.system.ItemBuilder; +import me.makkuusen.timing.system.tplayer.TPlayer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Material; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.w3c.dom.Attr; + +import java.util.*; + +public class Part { + @Getter + @Setter + private String name; + @Getter + private String id; + @Getter + @Setter + public PartCategory CategoryName; + @Getter + @Setter + public Integer rating; + @Getter + @Setter + private String description = "replace me"; + @Setter + private Material item; + private Map attributes = new HashMap<>(); + + public Part(){ + this.id = UUID.randomUUID().toString(); + this.rating = 0; + + addAttribute(Attribute.FORWARD_ACCEL, 0); + addAttribute(Attribute.YAW_ACCEL, 0); + addAttribute(Attribute.DEFAULT_SLIPPERINESS, 0); + addAttribute(Attribute.PACKED_ICE_SLIPPERINESS, 0); + addAttribute(Attribute.BLUE_ICE_SLIPPERINESS, 0); + addAttribute(Attribute.TURNING_FORWARD_ACCEL, 0); + addAttribute(Attribute.BACKWARD_ACCEL, 0); + addAttribute(Attribute.SCALE, 0); + addAttribute(Attribute.MAX_SPEED, 0); + addAttribute(Attribute.MAX_SPEED_RESISTANCE, 0); + addAttribute(Attribute.BRAKE_SLIPPERINESS, 0); + addAttribute(Attribute.WALLTAP_MULTIPLIER, 0); + addAttribute(Attribute.LATERAL_SLIPPERINESS, 0); + } + + public void addAttribute(Attribute name, Integer value){ + attributes.put(name, value); + } + + public void removeAttribute(Attribute name){ + attributes.remove(name); + } + + public Map getAttributes(){ + return attributes; + } + + public Integer getValue(Attribute attribute){ + return attributes.get(attribute); + } + + public ItemStack getItem(TPlayer tPlayer){ + if (this.item == null){ + return new ItemBuilder(Material.PUFFERFISH).setName(this.getName()).build(); + } + + NamedTextColor nameColor; + + if (rating > 800){ + nameColor = NamedTextColor.DARK_BLUE; + } + else if (rating > 500){ + nameColor = NamedTextColor.RED; + } + else if (rating > 300){ + nameColor = NamedTextColor.GOLD; + } + else if (rating > 100){ + nameColor = NamedTextColor.YELLOW; + } + else if (rating > 1){ + nameColor = NamedTextColor.GREEN; + } + else if (rating < 0){ + nameColor = NamedTextColor.DARK_GRAY; + } + else{ + nameColor = NamedTextColor.WHITE; + } + + + ItemStack Item = new ItemBuilder(this.item).setName(Component.text(this.getName()).color(nameColor) ).build(); + + List loreToSet = new ArrayList<>(); + + loreToSet.add(Component.text(this.getDescription())); + loreToSet.add(Component.text("rating: " + getRating()).color(NamedTextColor.YELLOW)); + + for (Attribute thing : this.attributes.keySet()){ + if (attributes.get(thing) != 0){ + loreToSet.add(Component.text(thing.toString() +": [" + attributes.get(thing) + "]").color(NamedTextColor.GRAY)); + } + } + + ItemMeta im = Item.getItemMeta(); + + if (im != null) { + im.addItemFlags(ItemFlag.HIDE_ENCHANTS); + im.addItemFlags(ItemFlag.HIDE_ITEM_SPECIFICS); + im.addItemFlags(ItemFlag.HIDE_DYE); + im.addItemFlags(ItemFlag.HIDE_ATTRIBUTES); + im.lore(loreToSet); + Item.setItemMeta(im); + } + + return Item; + } +} diff --git a/src/main/java/me/makkuusen/timing/system/tuning/PartCategory.java b/src/main/java/me/makkuusen/timing/system/tuning/PartCategory.java new file mode 100644 index 00000000..e5d62791 --- /dev/null +++ b/src/main/java/me/makkuusen/timing/system/tuning/PartCategory.java @@ -0,0 +1,19 @@ +package me.makkuusen.timing.system.tuning; + +import org.bukkit.Material; + +public enum PartCategory { + OARS(Material.WOODEN_SHOVEL), + HULL(Material.OAK_PLANKS), + RUDDER(Material.OAK_FENCE); + + private final Material material; + + PartCategory(Material material){ + this.material = material; + } + + public Material getMaterial(){ + return material; + } +} diff --git a/src/main/java/me/makkuusen/timing/system/tuning/PartManager.java b/src/main/java/me/makkuusen/timing/system/tuning/PartManager.java new file mode 100644 index 00000000..34557c5f --- /dev/null +++ b/src/main/java/me/makkuusen/timing/system/tuning/PartManager.java @@ -0,0 +1,109 @@ +package me.makkuusen.timing.system.tuning; + +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import me.makkuusen.timing.system.TimingSystem; + +import javax.swing.*; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.lang.reflect.Type; +import java.util.*; + +public class PartManager { + private static Map parts = new HashMap<>(); + + public PartManager(){ + + } + + public static boolean addPart(Part part){ + if (partExists(part.getName())) return false; + + parts.put(part.getId(), part); + return true; + } + + public static boolean removePart(String name){ + String id = getPartByName(name).getId(); + return parts.remove(id) != null; + } + + public static List getPartNames() { + List names = new ArrayList<>(); + + for (Part part : parts.values()) { + names.add(part.getName()); + } + + return names; + } + + public static Collection getParts() { + return parts.values(); + } + + public static Part getPart(String id){ + return parts.get(id); + } + + public static boolean partExists(String name) { + return getPartByName(name) != null; + } + + public static Part getPartByName(String name) { + for (Part part : parts.values()) { + if (part.getName().equalsIgnoreCase(name)) { + return part; + } + } + return null; + } + + public static void saveParts(){ + try{ + Gson gson = new Gson(); + File file = new File(TimingSystem.getPlugin().getDataFolder(), "parts.json"); + + file.getParentFile().mkdirs(); + + FileWriter writer = new FileWriter(file); + + gson.toJson(parts.values(), writer); + + writer.flush(); + writer.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void loadParts(){ + try{ + Gson gson = new Gson(); + File file = new File(TimingSystem.getPlugin().getDataFolder(), "parts.json"); + + if (!file.exists()){ + saveParts(); + return; + } + + FileReader reader = new FileReader(file); + + Type type = new TypeToken>(){}.getType(); + + List loadedParts = gson.fromJson(reader, type); + + parts.clear(); + + for (Part part : loadedParts){ + parts.put(part.getId(), part); + } + + reader.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 3b7d2e85..d2304e31 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -73,6 +73,8 @@ drs: maxDelta: 1150 duration: 2000 forwardAccel: 0.06 +tuning: + effect: 2 pushtopass: maxUseTime: 5000 fullChargeTime: 60000