Compare commits

...

39 Commits

Author SHA1 Message Date
ec2d243b7b fixed bug in HotBarRefill when using Blocks 2025-07-19 22:32:47 +02:00
ef6f34c2b2 added interaction sounds to settings 2025-07-19 16:36:31 +02:00
977f4ff4ec project dependency update 2025-07-19 16:27:39 +02:00
337727b0f0 fixed bug in FightDetector 2025-07-19 15:57:58 +02:00
44dae51e1c fixed playtimer 2025-06-24 21:23:05 +02:00
035864631d changed behavior to spawn in survival mode 2025-06-24 20:09:36 +02:00
f3b884058e code cleanup shrinkingborder 2025-06-23 20:34:48 +02:00
03d4f4e6d8 fixed bug in ShrinkingBorder 2025-06-23 20:28:13 +02:00
7422a89d98 fixed bug in fight detector 2025-06-23 19:52:30 +02:00
3590a5d278 finalized strikesystem 2025-06-22 14:20:45 +02:00
15ac47b314 auto playtime increment 2025-06-22 11:59:46 +02:00
af644a71ee ticketing enable and disable 2025-06-22 11:57:46 +02:00
0ce69f207f fixed bugs in strike handling 2025-06-22 10:59:38 +02:00
76297bb3af WIP: basic strike handling 2025-06-22 10:34:27 +02:00
1aad8f07c4 various bugfixes 2025-06-21 23:16:30 +02:00
f26f4ed56a cleanup 2025-06-21 21:35:31 +02:00
831eacaf47 added verbose logging for api requests
added autostrike for early leave
2025-06-21 21:22:49 +02:00
c71a2567bd fixed adminmarker handling api data wrong 2025-06-21 20:18:32 +02:00
72e88ce491 added spawnpoint for varo 2025-06-21 18:51:37 +02:00
66d84f4677 projectstart for varo 2025-06-21 18:15:25 +02:00
427aed9a7e fixed bug in teamtasks 2025-06-21 17:55:52 +02:00
0d1e6070ce updated playtimer and teamtasks 2025-06-21 17:18:47 +02:00
220fb9e229 moved existing spawning behavior to craftattack 2025-06-21 11:41:13 +02:00
9acac488f2 added api for querying admin-players 2025-06-21 11:38:09 +02:00
d71c0d768e configured shrinkingBorder for production use 2025-06-21 11:31:16 +02:00
9ef4c2e96b added playtimer ticket api 2025-06-20 17:07:53 +02:00
5d33d2aff7 updated adminmarker 2025-06-20 14:29:46 +02:00
3f1065fd3a added teamlist command 2025-06-19 23:49:48 +02:00
aa868deeca added team task management 2025-06-19 21:41:43 +02:00
b6c298cec3 unlimited admin access 2025-06-19 01:18:14 +02:00
8f5a96dc31 changed report text 2025-06-19 00:54:40 +02:00
2824c1053b WIP: report implementation for varo 2025-06-19 00:40:49 +02:00
ccf383cdb5 fixed configuration file not saving correctly 2025-06-15 18:55:17 +02:00
fce9449b7e implemented PlayTimer 2025-06-15 18:42:49 +02:00
69e971f618 Teams corrections
full implementation of FightDetector
2025-06-11 21:36:22 +02:00
b1f188dece generic tweaks
started implementation of FightDetector
2025-06-09 13:52:39 +02:00
a4289d5ac9 periodic team fetch 2025-05-30 22:00:42 +02:00
1fef363c50 Merge remote-tracking branch 'origin/master' 2025-05-30 18:35:14 +02:00
558e6f84f1 api header support, team api integration 2025-05-30 18:35:11 +02:00
59 changed files with 1904 additions and 166 deletions

View File

@ -1,7 +1,8 @@
dependencies { dependencies {
implementation project(':core') implementation project(':core')
compileOnly 'io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT' compileOnly 'io.papermc.paper:paper-api:1.21.7-R0.1-SNAPSHOT'
compileOnly 'org.geysermc.floodgate:api:2.2.2-SNAPSHOT' compileOnly 'org.geysermc.floodgate:api:2.2.4-SNAPSHOT'
implementation 'org.apache.httpcomponents:httpclient:4.5.14' implementation 'org.apache.httpcomponents:httpclient:4.5.14'
implementation 'com.sparkjava:spark-core:2.9.4'
} }

View File

@ -1,4 +1,4 @@
package eu.mhsl.craftattack.spawn.varo.api; package eu.mhsl.craftattack.spawn.common.api;
import eu.mhsl.craftattack.spawn.core.config.Configuration; import eu.mhsl.craftattack.spawn.core.config.Configuration;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;

View File

@ -0,0 +1,27 @@
package eu.mhsl.craftattack.spawn.common.api.repositories;
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
import eu.mhsl.craftattack.spawn.common.api.CraftAttackApi;
import java.util.UUID;
public class CraftAttackReportRepository extends ReportRepository {
public CraftAttackReportRepository() {
super(CraftAttackApi.getBaseUri(), new RequestModifier(CraftAttackApi::withAuthorizationSecret, null));
}
public ReqResp<PlayerReports> queryReports(UUID player) {
return this.get(
"report",
(parameters) -> parameters.addParameter("uuid", player.toString()),
PlayerReports.class
);
}
public ReqResp<ReportUrl> createReport(ReportCreationInfo data) {
return this.post(
"report",
data,
ReportUrl.class
);
}
}

View File

@ -1,17 +1,18 @@
package eu.mhsl.craftattack.spawn.common.api.repositories; package eu.mhsl.craftattack.spawn.common.api.repositories;
import eu.mhsl.craftattack.spawn.core.api.client.HttpRepository; import eu.mhsl.craftattack.spawn.core.api.client.HttpRepository;
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp; import eu.mhsl.craftattack.spawn.core.api.client.RepositoryLoader;
import eu.mhsl.craftattack.spawn.common.api.CraftAttackApi;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.net.URI;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
public class ReportRepository extends HttpRepository { @RepositoryLoader.Abstraction
public ReportRepository() { public abstract class ReportRepository extends HttpRepository {
super(CraftAttackApi.getBaseUri(), new RequestModifier(CraftAttackApi::withAuthorizationSecret, null)); public ReportRepository(URI basePath, RequestModifier... baseRequestModifier) {
super(basePath, baseRequestModifier);
} }
public record ReportCreationInfo(@NotNull UUID reporter, @Nullable UUID reported, String reason) { public record ReportCreationInfo(@NotNull UUID reporter, @Nullable UUID reported, String reason) {
@ -38,20 +39,4 @@ public class ReportRepository extends HttpRepository {
} }
} }
} }
public ReqResp<PlayerReports> queryReports(UUID player) {
return this.get(
"report",
(parameters) -> parameters.addParameter("uuid", player.toString()),
PlayerReports.class
);
}
public ReqResp<ReportUrl> createReport(ReportCreationInfo data) {
return this.post(
"report",
data,
ReportUrl.class
);
}
} }

View File

@ -0,0 +1,45 @@
package eu.mhsl.craftattack.spawn.common.api.repositories;
import eu.mhsl.craftattack.spawn.common.api.VaroApi;
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
import org.apache.commons.lang3.NotImplementedException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
public class VaroReportRepository extends ReportRepository {
public VaroReportRepository() {
super(VaroApi.getBaseUri(), new RequestModifier(null, VaroApi::authorizationHeader));
}
public ReqResp<PlayerReports> queryReports(UUID player) {
throw new NotImplementedException("Report querying is not supported in Varo!");
}
public ReqResp<ReportUrl> createReport(ReportCreationInfo data) {
return this.post(
"report",
data,
ReportUrl.class
);
}
public record StrikeCreationInfo(
@Nullable UUID reporter, // null for automatic creations
@NotNull UUID reported,
@NotNull String reason,
@Nullable String body,
@Nullable String notice,
@Nullable String statement,
int strike_reason_id // internal strike mapping
) {
}
public ReqResp<Void> createStrike(StrikeCreationInfo data) {
return this.put(
"report",
data,
Void.class
);
}
}

View File

@ -0,0 +1,34 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.adminMarker;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.api.server.HttpServer;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.displayName.DisplayName;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
public class AdminMarker extends Appliance implements DisplayName.Colored {
public final static String adminPermission = "admin";
@Override
public @Nullable TextColor getNameColor(Player player) {
if(player.hasPermission(adminPermission))
return TextColor.color(Color.AQUA.asRGB()); // TODO read permission from config
return TextColor.color(Color.WHITE.asRGB());
}
@Override
public void httpApi(HttpServer.ApiBuilder apiBuilder) {
apiBuilder.get("isAdmin", request -> {
OfflinePlayer player = Bukkit.getOfflinePlayer(UUID.fromString(request.queryParams("player")));
Main.logger().info(String.format("Adminstatus requested for %s, response: %s", player.getUniqueId(), player.isOp()));
return player.isOp();
});
}
}

View File

@ -30,7 +30,9 @@ class ChatMessagesListener extends ApplianceListener<ChatMessages> {
@EventHandler(priority = EventPriority.HIGH) @EventHandler(priority = EventPriority.HIGH)
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
boolean wasHidden = event.joinMessage() == null;
event.joinMessage(null); event.joinMessage(null);
if(wasHidden) return;
IteratorUtil.onlinePlayers(player -> { IteratorUtil.onlinePlayers(player -> {
if(!Settings.instance().getSetting(player, Settings.Key.ShowJoinAndLeaveMessages, Boolean.class)) return; if(!Settings.instance().getSetting(player, Settings.Key.ShowJoinAndLeaveMessages, Boolean.class)) return;
player.sendMessage( player.sendMessage(
@ -43,7 +45,9 @@ class ChatMessagesListener extends ApplianceListener<ChatMessages> {
@EventHandler @EventHandler
public void onPlayerLeave(PlayerQuitEvent event) { public void onPlayerLeave(PlayerQuitEvent event) {
boolean wasHidden = event.quitMessage() == null;
event.quitMessage(null); event.quitMessage(null);
if(wasHidden) return;
IteratorUtil.onlinePlayers(player -> { IteratorUtil.onlinePlayers(player -> {
if(!Settings.instance().getSetting(player, Settings.Key.ShowJoinAndLeaveMessages, Boolean.class)) return; if(!Settings.instance().getSetting(player, Settings.Key.ShowJoinAndLeaveMessages, Boolean.class)) return;
player.sendMessage( player.sendMessage(

View File

@ -1,8 +1,10 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report; package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report;
import eu.mhsl.craftattack.spawn.common.api.repositories.ReportRepository;
import eu.mhsl.craftattack.spawn.common.api.repositories.VaroReportRepository;
import eu.mhsl.craftattack.spawn.core.Main; import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp; import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
import eu.mhsl.craftattack.spawn.common.api.repositories.ReportRepository; import eu.mhsl.craftattack.spawn.common.api.repositories.CraftAttackReportRepository;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance; import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
@ -36,7 +38,7 @@ public class Report extends Appliance {
} }
public void reportToUnknown(@NotNull Player issuer) { public void reportToUnknown(@NotNull Player issuer) {
ReportRepository.ReportCreationInfo request = new ReportRepository.ReportCreationInfo(issuer.getUniqueId(), null, ""); CraftAttackReportRepository.ReportCreationInfo request = new CraftAttackReportRepository.ReportCreationInfo(issuer.getUniqueId(), null, "");
Bukkit.getScheduler().runTaskAsynchronously( Bukkit.getScheduler().runTaskAsynchronously(
Main.instance(), Main.instance(),
() -> this.createReport(issuer, request) () -> this.createReport(issuer, request)
@ -62,16 +64,17 @@ public class Report extends Appliance {
} }
private void createReport(Player issuer, ReportRepository.ReportCreationInfo reportRequest) { private void createReport(Player issuer, ReportRepository.ReportCreationInfo reportRequest) {
ReqResp<ReportRepository.ReportUrl> createdReport = this.queryRepository(ReportRepository.class) ReqResp<ReportRepository.ReportUrl> createdReport = this.queryRepository(VaroReportRepository.class)
.createReport(reportRequest); .createReport(reportRequest);
switch(createdReport.status()) { switch(createdReport.status()) {
case 200: // varo-endpoint specific
case 201: case 201:
issuer.sendMessage( issuer.sendMessage(
Component.text() Component.text()
.append(Component.text("\\/".repeat(20), NamedTextColor.DARK_GRAY)) .append(Component.text("\\/".repeat(20), NamedTextColor.DARK_GRAY))
.appendNewline() .appendNewline()
.append(Component.text("⚠ Der Report muss über den folgenden Link fertiggestellt werden!", NamedTextColor.GOLD)) .append(Component.text("⚠ Der Report muss über den folgenden Link fertiggestellt werden:", NamedTextColor.GOLD))
.appendNewline() .appendNewline()
.appendNewline() .appendNewline()
.append( .append(
@ -112,7 +115,7 @@ public class Report extends Appliance {
} }
public void queryReports(Player issuer) { public void queryReports(Player issuer) {
ReqResp<ReportRepository.PlayerReports> userReports = this.queryRepository(ReportRepository.class) ReqResp<ReportRepository.PlayerReports> userReports = this.queryRepository(VaroReportRepository.class)
.queryReports(issuer.getUniqueId()); .queryReports(issuer.getUniqueId());
if(userReports.status() != 200) { if(userReports.status() != 200) {

View File

@ -6,6 +6,7 @@ import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes.Setting; import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes.Setting;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.listeners.OpenSettingsShortcutListener; import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.listeners.OpenSettingsShortcutListener;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.listeners.SettingsInventoryListener; import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.listeners.SettingsInventoryListener;
import eu.mhsl.craftattack.spawn.core.util.world.InteractSounds;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -136,6 +137,7 @@ public class Settings extends Appliance {
} }
player.openInventory(inventory); player.openInventory(inventory);
InteractSounds.of(player).open();
this.openSettingsInventories.put(player, new OpenSettingsInventory(inventory, settings)); this.openSettingsInventories.put(player, new OpenSettingsInventory(inventory, settings));
} }
@ -166,6 +168,7 @@ public class Settings extends Appliance {
if(!this.openSettingsInventories.containsKey(player)) return; if(!this.openSettingsInventories.containsKey(player)) return;
this.openSettingsInventories.remove(player); this.openSettingsInventories.remove(player);
player.updateInventory(); player.updateInventory();
InteractSounds.of(player).close();
} }
public boolean hasSettingsNotOpen(Player player) { public boolean hasSettingsNotOpen(Player player) {

View File

@ -3,6 +3,7 @@ package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.dataty
import eu.mhsl.craftattack.spawn.core.Main; import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings; import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.core.util.text.ComponentUtil; import eu.mhsl.craftattack.spawn.core.util.text.ComponentUtil;
import eu.mhsl.craftattack.spawn.core.util.world.InteractSounds;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
@ -37,7 +38,9 @@ public abstract class Setting<TDataType> {
} }
public void triggerChange(Player p, ClickType clickType) { public void triggerChange(Player p, ClickType clickType) {
if(clickType.equals(ClickType.DOUBLE_CLICK)) return;
this.change(p, clickType); this.change(p, clickType);
InteractSounds.of(p).click();
this.toStorage(p.getPersistentDataContainer(), this.state()); this.toStorage(p.getPersistentDataContainer(), this.state());
} }

View File

@ -1,6 +1,6 @@
dependencies { dependencies {
compileOnly 'io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT' compileOnly 'io.papermc.paper:paper-api:1.21.7-R0.1-SNAPSHOT'
compileOnly 'org.geysermc.floodgate:api:2.2.2-SNAPSHOT' compileOnly 'org.geysermc.floodgate:api:2.2.4-SNAPSHOT'
implementation 'org.apache.httpcomponents:httpclient:4.5.14' implementation 'org.apache.httpcomponents:httpclient:4.5.14'
implementation 'com.sparkjava:spark-core:2.9.4' implementation 'com.sparkjava:spark-core:2.9.4'
implementation 'org.reflections:reflections:0.10.2' implementation 'org.reflections:reflections:0.10.2'

View File

@ -45,7 +45,20 @@ public abstract class HttpRepository extends Repository {
.POST(HttpRequest.BodyPublishers.ofString(this.gson.toJson(data))) .POST(HttpRequest.BodyPublishers.ofString(this.gson.toJson(data)))
.build(); .build();
return this.execute(request, outputType); return this.execute(request, outputType, data);
}
protected <TInput, TOutput> ReqResp<TOutput> put(String command, TInput data, Class<TOutput> outputType) {
return this.put(command, parameters -> {
}, data, outputType);
}
protected <TInput, TOutput> ReqResp<TOutput> put(String command, Consumer<URIBuilder> parameters, TInput data, Class<TOutput> outputType) {
HttpRequest request = this.getRequestBuilder(this.getUri(command, parameters))
.PUT(HttpRequest.BodyPublishers.ofString(this.gson.toJson(data)))
.build();
return this.execute(request, outputType, data);
} }
protected <TOutput> ReqResp<TOutput> get(String command, Class<TOutput> outputType) { protected <TOutput> ReqResp<TOutput> get(String command, Class<TOutput> outputType) {
@ -58,7 +71,7 @@ public abstract class HttpRepository extends Repository {
.GET() .GET()
.build(); .build();
return this.execute(request, outputType); return this.execute(request, outputType, null);
} }
private URI getUri(String command, Consumer<URIBuilder> parameters) { private URI getUri(String command, Consumer<URIBuilder> parameters) {
@ -90,9 +103,14 @@ public abstract class HttpRepository extends Repository {
return builder; return builder;
} }
private <TResponse> ReqResp<TResponse> execute(HttpRequest request, Class<TResponse> clazz) { private <TResponse> ReqResp<TResponse> execute(HttpRequest request, Class<TResponse> clazz, Object original) {
ReqResp<String> rawResponse = this.sendHttp(request); ReqResp<String> rawResponse = this.sendHttp(request);
Main.logger().info(String.format("HTTP-Repository fired %s, response: %s", request, rawResponse)); Main.logger().info(String.format(
"Request: %s\nRequest-Data: %s\nResponse: %s",
request,
this.gson.toJson(original),
rawResponse
));
return new ReqResp<>(rawResponse.status(), this.gson.fromJson(rawResponse.data(), clazz)); return new ReqResp<>(rawResponse.status(), this.gson.fromJson(rawResponse.data(), clazz));
} }

View File

@ -1,6 +1,7 @@
package eu.mhsl.craftattack.spawn.core.api.client; package eu.mhsl.craftattack.spawn.core.api.client;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import eu.mhsl.craftattack.spawn.core.Main; import eu.mhsl.craftattack.spawn.core.Main;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -12,7 +13,9 @@ public abstract class Repository {
public Repository(URI basePath) { public Repository(URI basePath) {
this.basePath = basePath; this.basePath = basePath;
this.gson = new Gson(); this.gson = new GsonBuilder()
.serializeNulls()
.create();
} }
protected void validateThread(String commandName) { protected void validateThread(String commandName) {

View File

@ -34,12 +34,12 @@ public class Countdown {
this.onDone = onDone; this.onDone = onDone;
this.defaultAnnouncements = count -> { this.defaultAnnouncements = count -> {
if(this.current > 60 && this.current % 60 == 0) { if(count > 60 && count % 60 == 0) {
return new AnnouncementData(this.current / 60, "Minuten"); return new AnnouncementData(count / 60, "Minuten");
} }
if(this.current <= 60 && (this.current <= 10 || this.current % 10 == 0)) { if(count <= 60 && (count <= 10 || count % 10 == 0)) {
return new AnnouncementData(this.current, "Sekunden"); return new AnnouncementData(count, "Sekunden");
} }
return null; return null;
@ -87,11 +87,11 @@ public class Countdown {
if(this.isDone()) { if(this.isDone()) {
this.onDone.run(); this.onDone.run();
this.cancel(); this.cancelIfRunning();
} }
} }
public boolean isDone() { private boolean isDone() {
return this.current <= 0; return this.current <= 0;
} }

View File

@ -1,6 +1,7 @@
package eu.mhsl.craftattack.spawn.core.util.world; package eu.mhsl.craftattack.spawn.core.util.world;
import net.kyori.adventure.key.Key; import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.registry.keys.SoundEventKeys;
import net.kyori.adventure.sound.Sound; import net.kyori.adventure.sound.Sound;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -15,23 +16,30 @@ public class InteractSounds {
this.player = player; this.player = player;
} }
private void playSound(org.bukkit.Sound sound) { private void playSound(TypedKey<org.bukkit.Sound> sound) {
this.player.playSound(this.getSound(sound.key()), Sound.Emitter.self()); this.player.playSound(
} Sound.sound(sound, Sound.Source.PLAYER, 1f, 1f),
Sound.Emitter.self()
private Sound getSound(Key soundKey) { );
return Sound.sound(soundKey, Sound.Source.PLAYER, 1f, 1f);
} }
public void click() { public void click() {
this.playSound(org.bukkit.Sound.UI_BUTTON_CLICK); this.playSound(SoundEventKeys.UI_BUTTON_CLICK);
} }
public void success() { public void success() {
this.playSound(org.bukkit.Sound.ENTITY_PLAYER_LEVELUP); this.playSound(SoundEventKeys.ENTITY_PLAYER_LEVELUP);
} }
public void delete() { public void delete() {
this.playSound(org.bukkit.Sound.ENTITY_SILVERFISH_DEATH); this.playSound(SoundEventKeys.ENTITY_SILVERFISH_DEATH);
}
public void open() {
this.playSound(SoundEventKeys.BLOCK_BARREL_OPEN);
}
public void close() {
this.playSound(SoundEventKeys.BLOCK_BARREL_CLOSE);
} }
} }

View File

@ -2,8 +2,8 @@ dependencies {
implementation project(':core') implementation project(':core')
implementation project(':common') implementation project(':common')
compileOnly 'io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT' compileOnly 'io.papermc.paper:paper-api:1.21.7-R0.1-SNAPSHOT'
compileOnly 'org.geysermc.floodgate:api:2.2.2-SNAPSHOT' compileOnly 'org.geysermc.floodgate:api:2.2.4-SNAPSHOT'
implementation 'org.apache.httpcomponents:httpclient:4.5.14' implementation 'org.apache.httpcomponents:httpclient:4.5.14'
implementation 'com.sparkjava:spark-core:2.9.4' implementation 'com.sparkjava:spark-core:2.9.4'
} }

View File

@ -1,6 +1,7 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.glowingBerries; package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.glowingBerries;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance; import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import io.papermc.paper.registry.keys.SoundEventKeys;
import net.kyori.adventure.sound.Sound; import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.util.Ticks; import net.kyori.adventure.util.Ticks;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -23,7 +24,7 @@ public class GlowingBerries extends Appliance {
public void letPlayerGlow(Player player) { public void letPlayerGlow(Player player) {
player.addPotionEffect(glowEffect); player.addPotionEffect(glowEffect);
Sound sound = Sound.sound(org.bukkit.Sound.BLOCK_AMETHYST_BLOCK_CHIME.key(), Sound.Source.PLAYER, 1f, 1f); Sound sound = Sound.sound(SoundEventKeys.BLOCK_AMETHYST_BLOCK_CHIME, Sound.Source.PLAYER, 1f, 1f);
player.stopSound(sound); player.stopSound(sound);
player.playSound(sound, Sound.Emitter.self()); player.playSound(sound, Sound.Emitter.self());
} }

View File

@ -44,7 +44,7 @@ public class HotbarRefill extends Appliance {
inventory.setItem(itemSlot, secondItem); inventory.setItem(itemSlot, secondItem);
inventory.setItem(replacementSlot, firstItem); inventory.setItem(replacementSlot, firstItem);
player.sendActionBar(Component.text("Die Hotbar wurde aufgefüllt", NamedTextColor.GREEN)); player.sendActionBar(Component.text("Deine Hotbar wurde nachgefüllt \uD83D\uDCE5", NamedTextColor.GREEN));
}, 1); }, 1);
} catch(NoSuchElementException ignored) { } catch(NoSuchElementException ignored) {
} }

View File

@ -26,7 +26,7 @@ class HotbarRefillListener extends ApplianceListener<HotbarRefill> {
ItemStack stackInHand = event.getItemInHand(); ItemStack stackInHand = event.getItemInHand();
if(stackInHand.getAmount() != 1) return; if(stackInHand.getAmount() != 1) return;
if(stackInHand.getType().getMaxDurability() > 0) return; if(stackInHand.getType().getMaxDurability() > 0) return;
if(stackInHand.getType().getMaxStackSize() > 0) return; if(stackInHand.getType().getMaxStackSize() == 1) return;
if(!this.getPlayerSetting(event.getPlayer()).onBlocks()) return; if(!this.getPlayerSetting(event.getPlayer()).onBlocks()) return;
this.getAppliance().handleHotbarChange(event.getPlayer(), stackInHand); this.getAppliance().handleHotbarChange(event.getPlayer(), stackInHand);
@ -35,15 +35,14 @@ class HotbarRefillListener extends ApplianceListener<HotbarRefill> {
@EventHandler @EventHandler
public void onPlayerItemBreak(PlayerItemBreakEvent event) { public void onPlayerItemBreak(PlayerItemBreakEvent event) {
if(!this.getPlayerSetting(event.getPlayer()).onTools()) return; if(!this.getPlayerSetting(event.getPlayer()).onTools()) return;
this.getAppliance().handleHotbarChange(event.getPlayer(), event.getBrokenItem()); this.getAppliance().handleHotbarChange(event.getPlayer(), event.getBrokenItem());
} }
@EventHandler @EventHandler
public void onPlayerItemConsume(PlayerItemConsumeEvent event) { public void onPlayerItemConsume(PlayerItemConsumeEvent event) {
if(List.of(Material.POTION, Material.HONEY_BOTTLE).contains(event.getItem().getType())) return; if(List.of(Material.POTION, Material.HONEY_BOTTLE).contains(event.getItem().getType())) return;
if(!this.getPlayerSetting(event.getPlayer()).onConsumable()) return;
if(!this.getPlayerSetting(event.getPlayer()).onConsumable()) return;
this.getAppliance().handleHotbarChange(event.getPlayer(), event.getItem()); this.getAppliance().handleHotbarChange(event.getPlayer(), event.getItem());
} }
} }

View File

@ -1,17 +0,0 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.metaGameplay.adminMarker;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.displayName.DisplayName;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Color;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
public class AdminMarker extends Appliance implements DisplayName.Colored {
@Override
public @Nullable TextColor getNameColor(Player player) {
if(player.hasPermission("chatcolor"))
return TextColor.color(Color.AQUA.asRGB()); // TODO read permission from config
return TextColor.color(Color.WHITE.asRGB());
}
}

View File

@ -59,7 +59,6 @@ public class ProjectStart extends Appliance {
private final Map<GameRule<Boolean>, Boolean> gameRulesAfterStart = Map.ofEntries( private final Map<GameRule<Boolean>, Boolean> gameRulesAfterStart = Map.ofEntries(
entry(GameRule.DO_DAYLIGHT_CYCLE, true), entry(GameRule.DO_DAYLIGHT_CYCLE, true),
entry(GameRule.DO_INSOMNIA, true), entry(GameRule.DO_INSOMNIA, true),
entry(GameRule.ANNOUNCE_ADVANCEMENTS, true),
entry(GameRule.DISABLE_RAIDS, false), entry(GameRule.DISABLE_RAIDS, false),
entry(GameRule.DO_FIRE_TICK, true), entry(GameRule.DO_FIRE_TICK, true),
entry(GameRule.DO_ENTITY_DROPS, true), entry(GameRule.DO_ENTITY_DROPS, true),

View File

@ -1,4 +1,4 @@
package eu.mhsl.craftattack.spawn.common.appliances.tooling.spawnpoint; package eu.mhsl.craftattack.spawn.craftattack.appliances.tooling.spawnpoint;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;

View File

@ -1,4 +1,4 @@
package eu.mhsl.craftattack.spawn.common.appliances.tooling.spawnpoint; package eu.mhsl.craftattack.spawn.craftattack.appliances.tooling.spawnpoint;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;

View File

@ -1,4 +1,4 @@
package eu.mhsl.craftattack.spawn.common.appliances.tooling.spawnpoint; package eu.mhsl.craftattack.spawn.craftattack.appliances.tooling.spawnpoint;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance; import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;

View File

@ -2,7 +2,7 @@ dependencies {
implementation project(':core') implementation project(':core')
implementation project(':common') implementation project(':common')
compileOnly 'io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT' compileOnly 'io.papermc.paper:paper-api:1.21.7-R0.1-SNAPSHOT'
implementation 'org.apache.httpcomponents:httpclient:4.5.14' implementation 'org.apache.httpcomponents:httpclient:4.5.14'
implementation 'com.sparkjava:spark-core:2.9.4' implementation 'com.sparkjava:spark-core:2.9.4'
} }

View File

@ -3,7 +3,7 @@ package eu.mhsl.craftattack.spawn.varo.api.repositories;
import com.google.common.reflect.TypeToken; import com.google.common.reflect.TypeToken;
import eu.mhsl.craftattack.spawn.core.api.client.HttpRepository; import eu.mhsl.craftattack.spawn.core.api.client.HttpRepository;
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp; import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
import eu.mhsl.craftattack.spawn.varo.api.VaroApi; import eu.mhsl.craftattack.spawn.common.api.VaroApi;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -28,7 +28,6 @@ public class TeamRepository extends HttpRepository {
public ReqResp<List<Team>> getTeams() { public ReqResp<List<Team>> getTeams() {
var resp = this.get("team", Object.class); var resp = this.get("team", Object.class);
System.out.println(resp.toString());
return resp return resp
.convertToTypeToken(new TypeToken<List<Team>>() {}.getType()) .convertToTypeToken(new TypeToken<List<Team>>() {}.getType())
.cast(); .cast();

View File

@ -0,0 +1,19 @@
package eu.mhsl.craftattack.spawn.varo.api.repositories;
import eu.mhsl.craftattack.spawn.core.api.client.HttpRepository;
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
import eu.mhsl.craftattack.spawn.common.api.VaroApi;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
public class VaroPlayerRepository extends HttpRepository {
public VaroPlayerRepository() {
super(VaroApi.getBaseUri(), new RequestModifier(null, VaroApi::authorizationHeader));
}
public record VaroDeath(UUID user, @Nullable UUID killer, String message) {}
public ReqResp<Void> registerDeath(VaroDeath death) {
return this.post("player/death", death, Void.class);
}
}

View File

@ -0,0 +1,6 @@
package eu.mhsl.craftattack.spawn.varo.appliances.internal.teamTasks;
public interface Task {
void stopTask();
boolean isTaskRunning();
}

View File

@ -0,0 +1,52 @@
package eu.mhsl.craftattack.spawn.varo.appliances.internal.teamTasks;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams.VaroTeam;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class TeamTasks extends Appliance {
public enum Type {
/**
* Task for kicking Team after the desired Playtime
*/
TIME_KICK,
JOIN_PAIR
}
private final Map<VaroTeam, Map<Type, Task>> tasks = new HashMap<>();
private Map<Type, Task> getTeamTasks(VaroTeam team) {
return this.tasks.computeIfAbsent(team, varoTeam -> new HashMap<>());
}
public Map<Type, Task> getRunningTeamTasks(VaroTeam team) {
return this.getTeamTasks(team).entrySet().stream()
.filter(entry -> entry.getValue().isTaskRunning())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
public void cancelTeamTasks(VaroTeam team) {
Main.logger().info(String.format("All TeamTasks for Team %s were cancelled: %s", team.name, this.getRunningTeamTasks(team)));
this.getTeamTasks(team).forEach((type, task) -> task.stopTask());
}
public void addTask(VaroTeam team, Type type, Task runnable) {
if(this.getTeamTasks(team).containsKey(type) && this.getTeamTasks(team).get(type).isTaskRunning()) {
throw new IllegalStateException(String.format("Task %s for Team %s was already running!", type.name(), team.name));
}
this.getTeamTasks(team).put(type, runnable);
}
@Override
protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of(new TeamTasksCommand());
}
}

View File

@ -0,0 +1,42 @@
package eu.mhsl.craftattack.spawn.varo.appliances.internal.teamTasks;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams.Teams;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.stream.Collectors;
public class TeamTasksCommand extends ApplianceCommand<TeamTasks> {
public TeamTasksCommand() {
super("teamTasks");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(args.length < 1) throw new Error("Please specify Teamname");
var team = Main.instance().getAppliance(Teams.class).findTeamByName(args[0]);
if(team == null) throw new Error("Team not found!");
var tasks = this.getAppliance().getRunningTeamTasks(team);
if(tasks.isEmpty()) {
sender.sendMessage("No Tasks found!");
} else {
sender.sendMessage(
tasks.entrySet()
.stream()
.map(entry -> String.format("%s: %s", entry.getKey().name(), entry.getValue().getClass().getSimpleName()))
.collect(Collectors.joining("\n"))
);
}
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
var teams = Main.instance().getAppliance(Teams.class).getAllTeams();
return teams.stream().map(team -> team.name).toList();
}
}

View File

@ -0,0 +1,16 @@
package eu.mhsl.craftattack.spawn.varo.appliances.internal.teamTasks.tasks;
import eu.mhsl.craftattack.spawn.varo.appliances.internal.teamTasks.Task;
import org.bukkit.scheduler.BukkitTask;
public abstract class BukkitTeamTask implements Task, BukkitTask {
@Override
public void stopTask() {
this.cancel();
}
@Override
public boolean isTaskRunning() {
return !this.isCancelled();
}
}

View File

@ -0,0 +1,24 @@
package eu.mhsl.craftattack.spawn.varo.appliances.internal.teamTasks.tasks;
import eu.mhsl.craftattack.spawn.core.util.text.Countdown;
import eu.mhsl.craftattack.spawn.varo.appliances.internal.teamTasks.Task;
import net.kyori.adventure.text.Component;
import java.util.function.Consumer;
import java.util.function.Function;
public class CountdownTeamTask extends Countdown implements Task {
public CountdownTeamTask(int countdownFrom, Function<AnnouncementData, Component> announcementBuilder, Consumer<Component> announcementConsumer, Runnable onDone) {
super(countdownFrom, announcementBuilder, announcementConsumer, onDone);
}
@Override
public void stopTask() {
super.cancelIfRunning();
}
@Override
public boolean isTaskRunning() {
return super.isRunning();
}
}

View File

@ -0,0 +1,99 @@
package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.fightDetector;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams.Teams;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams.VaroTeam;
import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class FightDetector extends Appliance {
public static final long FIGHT_TIMEOUT = 60 * 1000;
private static final long BLOCK_RADIUS = 30;
public final Map<VaroTeam, Long> fights = new HashMap<>();
public FightDetector() {
Bukkit.getScheduler().runTaskTimer(
Main.instance(),
() -> {
var teamFights = this.fights.keySet().stream()
.filter(this::isInFight)
.toList();
if(teamFights.isEmpty()) return;
Main.logger().info(String.format(
"There are %d Teams in Fight: %s",
teamFights.size(),
teamFights.stream()
.map(varoTeam -> String.format(
"%s[%s]",
varoTeam.name,
varoTeam.members.stream()
.map(member -> member.player.getName())
.collect(Collectors.joining(","))))
.collect(Collectors.joining(", "))
));
},
Ticks.TICKS_PER_SECOND * 15,
Ticks.TICKS_PER_SECOND * 15
);
Bukkit.getScheduler().runTaskTimer(
Main.instance(),
this::detectNearbyFights,
Ticks.TICKS_PER_SECOND,
Ticks.TICKS_PER_SECOND
);
}
private void detectNearbyFights() {
var players = Bukkit.getOnlinePlayers();
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), () -> {
for (Player player : players) {
VaroTeam ownTeam = this.queryAppliance(Teams.class).getTeamFromPlayer(player.getUniqueId());
if (ownTeam == null) continue;
for (Player otherPlayer : players) {
if (player.equals(otherPlayer)) continue;
VaroTeam otherTeam = this.queryAppliance(Teams.class).getTeamFromPlayer(otherPlayer.getUniqueId());
if (otherTeam == null || ownTeam.equals(otherTeam)) continue;
if(!player.getLocation().getWorld().equals(otherPlayer.getLocation().getWorld())) continue;
if (player.getLocation().distance(otherPlayer.getLocation()) <= BLOCK_RADIUS) {
this.setInFight(ownTeam);
this.setInFight(otherTeam);
}
}
}
});
}
public boolean isInFight(VaroTeam team) {
Long lastFightTime = this.fights.get(team);
if(lastFightTime == null) return false;
return (System.currentTimeMillis() - lastFightTime <= FIGHT_TIMEOUT);
}
public void setInFight(VaroTeam team) {
this.fights.put(team, System.currentTimeMillis());
}
public void setInFight(Player player) {
this.setInFight(this.queryAppliance(Teams.class).getTeamFromPlayer(player.getUniqueId()));
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(
new FightOnInteractionListener()
);
}
}

View File

@ -0,0 +1,28 @@
package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.fightDetector;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams.Teams;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams.VaroTeam;
import io.papermc.paper.event.player.PrePlayerAttackEntityEvent;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
class FightOnInteractionListener extends ApplianceListener<FightDetector> {
@EventHandler
public void onAttack(PrePlayerAttackEntityEvent event) {
if(!event.willAttack()) return;
if(event.getAttacked() instanceof Player attackedPlayer) {
Teams teamsAppliance = Main.instance().getAppliance(Teams.class);
VaroTeam attacker = teamsAppliance.getTeamFromPlayer(event.getPlayer().getUniqueId());
VaroTeam attacked = teamsAppliance.getTeamFromPlayer(event.getAttacked().getUniqueId());
if(attacked == null) return;
if(attacker == null) return;
if(attacker.equals(attacked)) return;
this.getAppliance().setInFight(event.getPlayer());
this.getAppliance().setInFight(attackedPlayer);
}
}
}

View File

@ -2,7 +2,7 @@ package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.joinProtection;
import eu.mhsl.craftattack.spawn.core.Main; import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance; import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams.Teams; import eu.mhsl.craftattack.spawn.core.util.IteratorUtil;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.util.Ticks; import net.kyori.adventure.util.Ticks;
@ -27,15 +27,18 @@ public class JoinProtection extends Appliance {
private final Map<UUID, Options> protectedPlayers = new HashMap<>(); private final Map<UUID, Options> protectedPlayers = new HashMap<>();
public void addProtection(Player player) { public void addProtection(Player player) {
if(player.isOp()) return;
this.protectedPlayers.put(player.getUniqueId(), new Options()); this.protectedPlayers.put(player.getUniqueId(), new Options());
PotionEffect resistance = new PotionEffect(PotionEffectType.RESISTANCE, Ticks.TICKS_PER_SECOND * resistanceDuration, 1); PotionEffect resistance = new PotionEffect(PotionEffectType.RESISTANCE, Ticks.TICKS_PER_SECOND * resistanceDuration, 1);
PotionEffect blindness = new PotionEffect(PotionEffectType.DARKNESS, Ticks.TICKS_PER_SECOND * 3, 1); PotionEffect blindness = new PotionEffect(PotionEffectType.DARKNESS, Ticks.TICKS_PER_SECOND * 3, 1);
player.addPotionEffects(List.of(resistance, blindness)); player.addPotionEffects(List.of(resistance, blindness));
Bukkit.getScheduler().runTaskLater( Bukkit.getScheduler().runTaskTimer(
Main.instance(), Main.instance(),
() -> this.protectedPlayers.remove(player.getUniqueId()), this::updateStatus,
Ticks.TICKS_PER_SECOND * resistanceDuration Ticks.TICKS_PER_SECOND,
Ticks.TICKS_PER_SECOND
); );
} }
@ -43,18 +46,36 @@ public class JoinProtection extends Appliance {
return this.protectedPlayers.get(player.getUniqueId()); return this.protectedPlayers.get(player.getUniqueId());
} }
public void cancelEvent(Player player, Cancellable event) { public boolean isNotProtected(Player player) {
var teamCountdown = Main.instance().getAppliance(Teams.class).getTeamJoinCountdown(player); Options options = this.protectedPlayers.get(player.getUniqueId());
if(teamCountdown.isFree(resistanceDuration)) return; if(options == null) return true;
event.setCancelled(true); return options.joinTime <= System.currentTimeMillis() - (resistanceDuration * 1000L);
}
int secondsLeft = Math.abs((int) ((System.currentTimeMillis() - teamCountdown.timestampSince()) / 1000) - resistanceDuration); public void cancelEvent(Player player, Cancellable event) {
player.sendActionBar( if(this.isNotProtected(player)) return;
Component.text( event.setCancelled(true);
String.format("Du bist in %d Sekunden angreifbar", secondsLeft), }
NamedTextColor.RED
) public void updateStatus() {
); IteratorUtil.onlinePlayers(player -> {
Options options = this.protectedPlayers.get(player.getUniqueId());
if(options == null) return;
if(this.isNotProtected(player)) {
this.protectedPlayers.remove(player.getUniqueId());
}
int secondsLeft = Math.abs((int) ((System.currentTimeMillis() - options.joinTime) / 1000) - resistanceDuration);
player.sendActionBar(
Component.text(
secondsLeft > 0
? String.format("Du bist in %d Sekunden angreifbar!", secondsLeft)
: "Du bist jetzt angreifbar!",
NamedTextColor.RED
)
);
});
} }
@Override @Override

View File

@ -0,0 +1,128 @@
package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.playTimer;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.api.server.HttpServer;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.core.config.Configuration;
import eu.mhsl.craftattack.spawn.varo.appliances.internal.teamTasks.TeamTasks;
import eu.mhsl.craftattack.spawn.varo.appliances.internal.teamTasks.tasks.CountdownTeamTask;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams.Teams;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams.VaroTeam;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
public class PlayTimer extends Appliance {
public static final int PLAYTIME_MINUTES = 30;
private final Map<String, Integer> joinTickets = new HashMap<>();
private final Path saveFile = Paths.get(Main.instance().getDataFolder().getAbsolutePath() + "/playtime.json");
public PlayTimer() {
super("playTimer");
this.load();
}
public void changeEnabled(boolean enabled) {
this.localConfig().set("enableTicketing", enabled);
Configuration.saveChanges();
}
public boolean isEnabled() {
return this.localConfig().getBoolean("enableTicketing", true);
}
private void load() {
if (!Files.exists(this.saveFile)) return;
try (Reader reader = Files.newBufferedReader(this.saveFile)) {
Type type = new TypeToken<Map<String, Object>>() {}.getType();
Map<String, Object> data = new Gson().fromJson(reader, type);
@SuppressWarnings("unchecked") Map<String, Double> ticketMap = (Map<String, Double>) data.get("tickets");
if (ticketMap != null) {
for (Map.Entry<String, Double> entry : ticketMap.entrySet()) {
this.joinTickets.put(entry.getKey(), entry.getValue().intValue());
}
}
} catch (IOException e) {
Main.logger().warning("Failed reading playtime from teams: " + e.getMessage());
}
}
private void save() {
try {
Files.createDirectories(this.saveFile.getParent());
try (Writer writer = Files.newBufferedWriter(this.saveFile)) {
new Gson().toJson(Map.of("tickets", this.joinTickets), writer);
}
} catch (IOException e) {
Main.logger().warning("Failed to save playtime for teams: " + e.getMessage());
}
}
public void incrementAll() {
Main.logger().info("Incrementing all PlayTime Tickets by one!");
this.joinTickets.replaceAll((n, v) -> this.joinTickets.get(n) + 1);
this.save();
}
public void setTickets(VaroTeam team, int amount) {
this.joinTickets.put(team.name, amount);
this.save();
}
public int getTickets(VaroTeam team) {
return this.joinTickets.getOrDefault(team.name, 1);
}
public boolean tryConsumeTicket(VaroTeam team) {
String teamName = team.name;
var teamTasks = Main.instance().getAppliance(TeamTasks.class);
boolean isSecond = teamTasks.getRunningTeamTasks(team).containsKey(TeamTasks.Type.JOIN_PAIR);
if(!isSecond) {
int current = this.joinTickets.getOrDefault(teamName, 1);
if (current <= 0) return false;
this.joinTickets.put(teamName, current - 1);
var task = new CountdownTeamTask(10, announcementData -> null, component -> {}, () -> {});
task.start();
teamTasks.addTask(team, TeamTasks.Type.JOIN_PAIR, task);
}
this.save();
return true;
}
@Override
public void httpApi(HttpServer.ApiBuilder apiBuilder) {
record Ticket(String team, int tickets) {}
apiBuilder.get("tickets", request -> {
String teamName = request.queryParamsSafe("team");
VaroTeam team = Main.instance().getAppliance(Teams.class).findTeamByName(teamName);
if(team == null) throw new NoSuchElementException("Team not found!");
return new Ticket(team.name, this.getTickets(team));
});
}
@Override
protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of(
new PlayTimerCommand(),
new TicketingCommand()
);
}
}

View File

@ -0,0 +1,95 @@
package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.playTimer;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams.Teams;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams.VaroTeam;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.stream.Stream;
public class PlayTimerCommand extends ApplianceCommand<PlayTimer> {
public PlayTimerCommand() {
super("playTimer");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 3) throw new Error("Usage: playTimer <user|team> <identifier> <get|set> [amount]");
String mode = args[0].toLowerCase();
String identifier = args[1];
String action = args[2].toLowerCase();
Teams teamAppliance = Main.instance().getAppliance(Teams.class);
VaroTeam team = switch (mode) {
case "user" -> {
OfflinePlayer player = Bukkit.getOfflinePlayer(identifier);
yield teamAppliance.getTeamFromPlayer(player.getUniqueId());
}
case "team" -> teamAppliance.findTeamByName(identifier);
case "incallbyone" -> {
this.getAppliance().incrementAll();
throw new Error("KEIN FEHLER!: Incremented all Teams by one!");
}
default -> throw new Error("Ungültiger Modus: " + mode + ". Erlaubt: user | team");
};
if(team == null) throw new Error("Team nicht gefunden.");
switch (action) {
case "get" -> {
int ticketCount = this.getAppliance().getTickets(team);
sender.sendMessage(String.format("Team %s hat %d tickets!", team.name, ticketCount));
}
case "set" -> {
if (args.length < 4) throw new Error("Usage: playTimer <user|team> <identifier> set <amount>");
int amount = Integer.parseInt(args[3]);
this.getAppliance().setTickets(team, amount);
sender.sendMessage("Tickets wurden gesetzt!");
}
default -> throw new Error("Ungültige Aktion: " + action + ". Erlaubt: get | set");
}
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
List<VaroTeam> teams = Main.instance().getAppliance(Teams.class).getAllTeams();
return switch (args.length) {
case 1 -> Stream.of("user", "team", "incAllByOne")
.filter(opt -> opt.startsWith(args[0].toLowerCase()))
.toList();
case 2 -> {
if (args[0].equalsIgnoreCase("user")) {
yield Bukkit.getOnlinePlayers().stream()
.map(Player::getName)
.filter(name -> name.toLowerCase().startsWith(args[1].toLowerCase()))
.toList();
} else if (args[0].equalsIgnoreCase("team")) {
yield teams.stream()
.map(team -> team.name)
.filter(name -> name.toLowerCase().startsWith(args[1].toLowerCase()))
.toList();
} else {
yield List.of();
}
}
case 3 -> Stream.of("get", "set")
.filter(opt -> opt.startsWith(args[2].toLowerCase()))
.toList();
default -> List.of();
};
}
}

View File

@ -0,0 +1,41 @@
package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.playTimer;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams.Teams;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class TicketingCommand extends ApplianceCommand<PlayTimer> {
public TicketingCommand() {
super("ticketing");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
sender.sendMessage(String.format("Ticketing was %b", this.getAppliance().isEnabled()));
if(args.length < 1)
throw new Error("Stop Ticketing with 'stop' or start ticketing (now subtracting one) and start team-countdown with 'start'");
switch(args[0]) {
case "stop" -> this.getAppliance().changeEnabled(false);
case "start" -> {
this.getAppliance().changeEnabled(true);
Main.instance().getAppliance(Teams.class).getAllTeams().forEach(team -> {
boolean isAllowed = this.getAppliance().tryConsumeTicket(team);
if(!isAllowed) {
Main.logger().warning(String.format("Team %s already on Server, Ticketing got enabled but no Tickets were left! KICKING!", team.name));
team.kickTeam();
}
team.startCountDown();
});
sender.sendMessage(String.format("Ticketing (ticket reduction on join) is now %b", this.getAppliance().isEnabled()));
}
default -> sender.sendMessage("Unknown command");
}
sender.sendMessage(String.format("Ticketing is now %b", this.getAppliance().isEnabled()));
}
}

View File

@ -5,6 +5,7 @@ import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.core.config.Configuration; import eu.mhsl.craftattack.spawn.core.config.Configuration;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings; import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.playTimer.PlayTimer;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.WorldBorder; import org.bukkit.WorldBorder;
@ -35,7 +36,7 @@ public class ShrinkingBorder extends Appliance {
@Override @Override
public void run() { public void run() {
Bukkit.getScheduler().runTask(Main.instance(), ShrinkingBorder.this::shrinkBorder); Bukkit.getScheduler().runTask(Main.instance(), ShrinkingBorder.this::shrinkBorder);
Main.instance().getAppliance(PlayTimer.class).incrementAll();
} }
} }

View File

@ -25,6 +25,8 @@ public class ShrinkingBorderListener extends ApplianceListener<ShrinkingBorder>
String actionBarText; String actionBarText;
if(remainingDays <= 0) { if(remainingDays <= 0) {
actionBarText = "Du befindest dich in der Worldborder!"; actionBarText = "Du befindest dich in der Worldborder!";
} else if(remainingDays == 1) {
actionBarText = "Morgen ist die Worldborder hier! Ausloggen = ☠";
} else { } else {
actionBarText = String.format("In %d Tagen ist die Worldborder hier!", remainingDays); actionBarText = String.format("In %d Tagen ist die Worldborder hier!", remainingDays);
} }
@ -40,8 +42,9 @@ public class ShrinkingBorderListener extends ApplianceListener<ShrinkingBorder>
Location relativeLocation = playerLocation.clone().subtract(worldBorder.getCenter()); Location relativeLocation = playerLocation.clone().subtract(worldBorder.getCenter());
double playerBorderDistanceX = worldBorder.getSize()/2 - Math.abs(relativeLocation.getX()); double playerBorderDistanceX = worldBorder.getSize()/2 - Math.abs(relativeLocation.getX());
double playerBorderDistanceZ = worldBorder.getSize()/2 - Math.abs(relativeLocation.getZ()); double playerBorderDistanceZ = worldBorder.getSize()/2 - Math.abs(relativeLocation.getZ());
int xSteps = (int) Math.ceil(playerBorderDistanceX / this.getAppliance().getOption(ShrinkingBorderCommand.Argument.SHRINK_PER_DAY)); double halfShrinkPerDayX = (double) this.getAppliance().getOption(ShrinkingBorderCommand.Argument.SHRINK_PER_DAY) / 2;
int zSteps = (int) Math.ceil(playerBorderDistanceZ / this.getAppliance().getOption(ShrinkingBorderCommand.Argument.SHRINK_PER_DAY)); int xSteps = (int) Math.ceil(playerBorderDistanceX / halfShrinkPerDayX);
int zSteps = (int) Math.ceil(playerBorderDistanceZ / halfShrinkPerDayX);
return Math.min(xSteps, zSteps); return Math.min(xSteps, zSteps);
} }

View File

@ -38,6 +38,6 @@ public class ShrinkingBorderSetting extends IntegerSetting implements Categorize
@Override @Override
protected Integer defaultValue() { protected Integer defaultValue() {
return 1; return 2;
} }
} }

View File

@ -0,0 +1,118 @@
package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.strike;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.util.IteratorUtil;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.*;
public class CordLeak {
private static final Path saveFile = Paths.get(Main.instance().getDataFolder().getAbsolutePath(), "cordleak.json");
private static final Map<UUID, LeakInfo> leaks = new HashMap<>();
static {
load();
}
private record LocationDto(String world, double x, double y, double z, float yaw, float pitch) {
static LocationDto from(Location loc) {
return new LocationDto(loc.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch());
}
Location toLocation() {
return new Location(Bukkit.getWorld(this.world), this.x, this.y, this.z, this.yaw, this.pitch);
}
}
public record LeakInfo(Location location, Instant leakedAt) {}
private record LeakInfoDto(LocationDto location, long leakedAt) {
static LeakInfoDto from(LeakInfo info) {
return new LeakInfoDto(LocationDto.from(info.location), info.leakedAt.toEpochMilli());
}
LeakInfo toLeakInfo() {
return new LeakInfo(this.location.toLocation(), Instant.ofEpochMilli(this.leakedAt));
}
}
public static void update() {
IteratorUtil.onlinePlayers(player -> {
UUID uuid = player.getUniqueId();
Location loc = player.getLocation();
leaks.compute(uuid, (key, existing) -> {
// Falls bereits geleakt, nicht aktualisieren
if (existing != null && !existing.leakedAt.equals(Instant.EPOCH)) {
return existing;
}
return new LeakInfo(loc, existing != null ? existing.leakedAt : Instant.EPOCH);
});
});
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), CordLeak::save);
}
public static void markAsLeaked(UUID uuid) {
LeakInfo existing = leaks.get(uuid);
if (existing == null) return;
leaks.put(uuid, new LeakInfo(existing.location, Instant.now()));
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), CordLeak::save);
}
public static LeakInfo getLastKnownLocation(UUID uuid) {
return leaks.get(uuid);
}
public static Map<UUID, LeakInfo> getAllLeaked() {
Map<UUID, LeakInfo> result = new HashMap<>();
Instant cutoff = Instant.now().minusSeconds(3 * 24 * 60 * 60); // 3 Tage in Sekunden
for (Map.Entry<UUID, LeakInfo> entry : leaks.entrySet()) {
LeakInfo info = entry.getValue();
if (info.leakedAt.isAfter(cutoff)) {
result.put(entry.getKey(), info);
}
}
return result;
}
private static void load() {
if (!Files.exists(saveFile)) return;
try (Reader reader = Files.newBufferedReader(saveFile)) {
Type type = new TypeToken<Map<String, LeakInfoDto>>() {}.getType();
Map<String, LeakInfoDto> raw = new Gson().fromJson(reader, type);
for (Map.Entry<String, LeakInfoDto> entry : raw.entrySet()) {
leaks.put(UUID.fromString(entry.getKey()), entry.getValue().toLeakInfo());
}
} catch (IOException e) {
Main.logger().warning("Failed to load CordLeak data: " + e.getMessage());
}
}
private static void save() {
try {
Files.createDirectories(saveFile.getParent());
Map<String, LeakInfoDto> raw = new HashMap<>();
for (Map.Entry<UUID, LeakInfo> entry : leaks.entrySet()) {
raw.put(entry.getKey().toString(), LeakInfoDto.from(entry.getValue()));
}
try (Writer writer = Files.newBufferedWriter(saveFile)) {
new Gson().toJson(raw, writer);
}
} catch (IOException e) {
Main.logger().warning("Failed to save CordLeak data: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,64 @@
package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.strike;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import eu.mhsl.craftattack.spawn.core.Main;
import org.bukkit.Bukkit;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public class InvClear {
private static final Path saveFile = Paths.get(Main.instance().getDataFolder().getAbsolutePath(), "invClear.json");
private static final List<UUID> players = new ArrayList<>();
static {
load();
}
public static void add(UUID player) {
Main.logger().info(String.format("Cannot clear inv because Player is offline. Remembering %s", player));
players.add(player);
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), InvClear::save);
}
public static boolean contains(UUID player) {
return players.contains(player);
}
public static void remove(UUID player) {
players.remove(player);
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), InvClear::save);
}
private static void load() {
if (!Files.exists(saveFile)) return;
try (Reader reader = Files.newBufferedReader(saveFile)) {
Type type = new TypeToken<List<UUID>>() {}.getType();
List<UUID> loaded = new Gson().fromJson(reader, type);
if (loaded != null) {
players.clear();
players.addAll(loaded);
}
} catch (IOException e) {
Main.logger().warning("Failed to load InvClear data: " + e.getMessage());
}
}
private static void save() {
try {
Files.createDirectories(saveFile.getParent());
try (Writer writer = Files.newBufferedWriter(saveFile)) {
new Gson().toJson(players, writer);
}
} catch (IOException e) {
Main.logger().warning("Failed to save InvClear data: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,25 @@
package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.strike;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
public class OnStrikeActionListener extends ApplianceListener<Strike> {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
this.getAppliance().checkJoin(event.getPlayer());
var leaks = CordLeak.getAllLeaked();
var text = Component.text();
if(!leaks.isEmpty()) {
text.append(Component.text("Geleakte Koordinaten:", NamedTextColor.AQUA));
leaks.forEach((uuid, leakInfo) -> {
this.getAppliance().appendCordLeak(text, uuid);
});
event.getPlayer().sendMessage(text);
}
}
}

View File

@ -0,0 +1,169 @@
package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.strike;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.api.server.HttpServer;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.core.util.IteratorUtil;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams.Teams;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams.VaroTeam;
import io.papermc.paper.ban.BanListType;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentBuilder;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
public class Strike extends Appliance {
public Strike() {
Bukkit.getScheduler().scheduleSyncRepeatingTask(
Main.instance(),
CordLeak::update,
Ticks.TICKS_PER_SECOND,
Ticks.TICKS_PER_SECOND * 3
);
}
record StrikeInfo(List<UUID> users, int totalWeight) {
}
@Override
public void httpApi(HttpServer.ApiBuilder apiBuilder) {
apiBuilder.post("update", StrikeInfo.class, (data, request) -> {
Main.instance().getLogger().info(String.format(
"API Triggered Strike-Profile update for %s",
data.users.stream().map(UUID::toString).collect(Collectors.joining(", ")))
);
this.processUpdate(data);
return HttpServer.nothing;
});
apiBuilder.get("cords", req -> {
Map<UUID, CordLeak.LeakInfo> raw = CordLeak.getAllLeaked();
Map<String, LeakInfoDto> dto = new HashMap<>();
for (Map.Entry<UUID, CordLeak.LeakInfo> entry : raw.entrySet()) {
dto.put(entry.getKey().toString(), LeakInfoDto.from(entry.getKey(), entry.getValue()));
}
return dto;
});
}
record LocationDto(String world, double x, double y, double z, float yaw, float pitch) {
static LocationDto from(Location loc) {
return new LocationDto(loc.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch());
}
}
record LeakInfoDto(String name, LocationDto location, long leakedAt) {
static LeakInfoDto from(UUID uuid, CordLeak.LeakInfo info) {
String name = Bukkit.getOfflinePlayer(uuid).getName();
return new LeakInfoDto(name, LocationDto.from(info.location()), info.leakedAt().toEpochMilli());
}
}
private void processUpdate(StrikeInfo data) {
try {
VaroTeam select = null;
for(UUID uuid : data.users) {
select = this.queryAppliance(Teams.class).getTeamFromPlayer(uuid);
if(select != null) break;
}
Objects.requireNonNull(select);
VaroTeam team = select;
if(data.totalWeight < 3) {
team.members.forEach(member -> {
Main.logger().info(String.format("Unbanning player %s because there are less than 3 Strikes!", member.player.getName()));
Bukkit.getBanList(BanListType.PROFILE).pardon(member.player.getPlayerProfile());
});
}
switch(data.totalWeight) {
case 1 -> {
Main.logger().info(String.format("Cord leak for Team %s", team.name));
ComponentBuilder<TextComponent, TextComponent.Builder> text = Component.text();
IteratorUtil.onlinePlayers(player -> player.sendMessage(Component.text(
String.format("Das Team %s hat einen Strike erhalten. Daher werden folgende Koordinaten des Teams veröffentlicht:", team.name),
NamedTextColor.RED
)));
data.users.forEach(uuid -> {
CordLeak.markAsLeaked(uuid);
this.appendCordLeak(text, uuid);
});
IteratorUtil.onlinePlayers(p -> p.sendMessage(text));
}
case 2 -> team.members.forEach(member -> {
Main.logger().info(String.format("Clearing Inventory of %s", member.player.getName()));
Optional<Player> player = Optional.ofNullable(Bukkit.getPlayer(member.player.getUniqueId()));
player.ifPresentOrElse(
p -> p.getInventory().clear(),
() -> InvClear.add(member.player.getUniqueId())
);
});
case 3 -> {
team.members.forEach(member -> {
Main.logger().info(String.format("Banning player %s because of third Strike!", member.player.getName()));
Bukkit.getBanList(BanListType.PROFILE)
.addBan(member.player.getPlayerProfile(), "projektausschluss", (Date) null, null);
});
Bukkit.getScheduler().runTask(Main.instance(), () -> team.getOnlinePlayers().forEach(Player::kick));
}
}
} catch(Exception e) {
Main.logger().warning("Failed to process Strikes: " + e.getMessage());
Main.logger().throwing("Strike", "process", e);
}
}
public void appendCordLeak(ComponentBuilder<TextComponent, TextComponent.Builder> text, UUID playerToLeak) {
OfflinePlayer player = Bukkit.getOfflinePlayer(playerToLeak);
CordLeak.LeakInfo cords = CordLeak.getLastKnownLocation(playerToLeak);
if(cords == null) return;
String timestamp = DateTimeFormatter.ofPattern("dd.MM HH:mm")
.withZone(ZoneId.of("Europe/Berlin"))
.format(cords.leakedAt());
text.appendNewline();
text.append(Component.text(Objects.requireNonNull(player.getName()), NamedTextColor.DARK_AQUA));
text.append(Component.text(": ", NamedTextColor.GRAY));
text.append(Component.text(
String.format(
"(%s) %d, %d, %d - Uhrzeit: %s",
cords.location().getWorld().getName(),
cords.location().getBlockX(),
cords.location().getBlockY(),
cords.location().getBlockZ(),
timestamp
),
NamedTextColor.AQUA)
);
}
public void checkJoin(@NotNull Player player) {
if(InvClear.contains(player.getUniqueId())) {
player.getInventory().clear();
InvClear.remove(player.getUniqueId());
}
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new OnStrikeActionListener());
}
}

View File

@ -1,23 +1,31 @@
package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams; package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams;
import eu.mhsl.craftattack.spawn.common.api.repositories.VaroReportRepository;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.core.util.text.DisconnectInfo; import eu.mhsl.craftattack.spawn.core.util.text.DisconnectInfo;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.playTimer.PlayTimer;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.player.AsyncPlayerPreLoginEvent; import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import java.util.Objects;
class ConnectivityChangeListener extends ApplianceListener<Teams> { class ConnectivityChangeListener extends ApplianceListener<Teams> {
@EventHandler @EventHandler
public void onLogin(AsyncPlayerPreLoginEvent event) { public void onLogin(AsyncPlayerPreLoginEvent event) {
boolean result = this.getAppliance().canLogin(event.getUniqueId()); boolean result = this.getAppliance().canLogin(event.getUniqueId());
event.kickMessage(new DisconnectInfo( if(!result) {
"Kein Teilnehmer", event.kickMessage(new DisconnectInfo(
"Du bist nicht als Teilnehmer registriert oder bist ausgeschieden!", "Kein Teilnehmer",
"Sollte dies ein Fehler sein, kontaktiere bitte einen Admin.", "Du bist nicht als Teilnehmer registriert oder bist ausgeschieden!",
event.getUniqueId() "Sollte dies ein Fehler sein, kontaktiere bitte einen Admin.",
).getComponent()); event.getUniqueId()
if(!result) event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); ).getComponent());
event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER);
}
} }
@EventHandler @EventHandler
@ -27,6 +35,37 @@ class ConnectivityChangeListener extends ApplianceListener<Teams> {
@EventHandler @EventHandler
public void onLeave(PlayerQuitEvent event) { public void onLeave(PlayerQuitEvent event) {
if(event.getReason().equals(PlayerQuitEvent.QuitReason.KICKED)) {
Main.logger().info(String.format(
"Player %s left the Server. The 'teamLeave' enforcement was skipped, since the QuitReason is 'kicked'",
event.getPlayer().getName()
));
return;
}
if(event.getPlayer().isOp()) return;
VaroTeam team = Main.instance().getAppliance(Teams.class).getTeamFromPlayer(event.getPlayer().getUniqueId());
Objects.requireNonNull(team, "Team not found for player " + event.getPlayer().getUniqueId());
if(Main.instance().getAppliance(PlayTimer.class).isEnabled()) {
Main.logger().info(String.format("Team %s got a Strike, because they %s left early!", team.name, event.getPlayer().getName()));
VaroReportRepository.StrikeCreationInfo report = new VaroReportRepository.StrikeCreationInfo(
null,
event.getPlayer().getUniqueId(),
"early left",
"player left the server too early",
null,
null,
1
);
ReqResp<Void> response = Main.instance().getRepositoryLoader().getRepository(VaroReportRepository.class).createStrike(report);
Main.logger().info(String.format("Autostrike response for Team %s: %s", team.name, response));
} else {
Main.logger().info("Allow ealry leave because PlayTimer is not active!");
}
this.getAppliance().enforceTeamLeave(event.getPlayer()); this.getAppliance().enforceTeamLeave(event.getPlayer());
} }
} }

View File

@ -0,0 +1,28 @@
package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.stream.Collectors;
public class TeamListCommand extends ApplianceCommand<Teams> {
public TeamListCommand() {
super("teamList");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
String teams = this.getAppliance().getAllTeams().stream()
.map(team -> {
String members = team.members.stream()
.map(member -> String.format("%s(%s)", member.player.getName(), member.isDead ? "" : ""))
.collect(Collectors.joining(", "));
return String.format("%s: %s", team.name, members);
})
.collect(Collectors.joining("\n"));
sender.sendMessage(teams);
}
}

View File

@ -2,15 +2,18 @@ package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams;
import eu.mhsl.craftattack.spawn.core.Main; import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance; import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.core.util.text.DisconnectInfo; import eu.mhsl.craftattack.spawn.core.util.text.DisconnectInfo;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.displayName.DisplayName; import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.displayName.DisplayName;
import eu.mhsl.craftattack.spawn.varo.api.repositories.TeamRepository; import eu.mhsl.craftattack.spawn.varo.api.repositories.TeamRepository;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.joinProtection.JoinProtection; import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.joinProtection.JoinProtection;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.playTimer.PlayTimer;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.util.Ticks; import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -18,45 +21,128 @@ import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
public class Teams extends Appliance implements DisplayName.Prefixed, DisplayName.Colored { public class Teams extends Appliance implements DisplayName.Prefixed {
private List<VaroTeam> teams = List.of(); private final List<VaroTeam> teams = new ArrayList<>();
public Teams() { public Teams() {
this.refreshTeamList(); Bukkit.getScheduler().runTaskTimerAsynchronously(
Main.instance(),
this::refreshTeamList,
0,
Ticks.TICKS_PER_SECOND * 60
);
} }
public void refreshTeamList() { public void refreshTeamList() {
this.teams = this.queryRepository(TeamRepository.class).getTeams().data().stream() var updatedTeams = this.queryRepository(TeamRepository.class).getTeams().data();
.map(team -> new VaroTeam(
team.users().stream().map(user -> new VaroTeam.Member(user.uuid(), user.dead())).toList(), for (var updatedTeam : updatedTeams) {
team.name(), VaroTeam existingTeam = this.findTeamByName(updatedTeam.name());
team.color()
)).toList(); if (existingTeam != null) {
existingTeam.members = updatedTeam.users().stream()
.map(user -> new VaroTeam.Member(user.uuid(), user.dead()))
.toList();
existingTeam.color = updatedTeam.color();
existingTeam.name = updatedTeam.name();
} else {
VaroTeam newTeam = new VaroTeam(
updatedTeam.users().stream()
.map(user -> new VaroTeam.Member(user.uuid(), user.dead()))
.toList(),
updatedTeam.name(),
updatedTeam.color()
);
this.teams.add(newTeam);
Main.logger().info("Added missing team to Teams registry: " + newTeam.name);
}
}
}
public @Nullable VaroTeam findTeamByName(String name) {
for (VaroTeam team : this.teams) {
if (team.name.equals(name)) return team;
}
return null;
} }
public boolean canLogin(UUID playerId) { public boolean canLogin(UUID playerId) {
return this.teams.stream() OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerId);
.anyMatch(varoTeam -> varoTeam.hasMember(playerId) && !Objects.requireNonNull(varoTeam.getMemberById(playerId)).isDead); if(offlinePlayer.isOp()) {
Main.logger().info(String.format("Allowing player %s to login, because he ist OP!", playerId));
return true;
}
Optional<VaroTeam> team = this.teams.stream()
.filter(varoTeam -> varoTeam.hasMember(playerId) && !Objects.requireNonNull(varoTeam.getMemberById(playerId)).isDead)
.findAny();
team.ifPresentOrElse(
found -> Main.logger().info(String.format("Player %s is in Team %s!", playerId, found.name)),
() -> Main.logger().info(String.format("No valid Team found for %s (or he is in a Team but dead)!", playerId))
);
return team.isPresent();
} }
VaroTeam getTeamFromPlayer(UUID playerId) { public @Nullable VaroTeam getTeamFromPlayer(UUID playerId) {
return this.teams.stream() return this.teams.stream()
.filter(varoTeam -> varoTeam.hasMember(playerId)) .filter(varoTeam -> varoTeam.hasMember(playerId))
.findFirst() .findFirst()
.orElseThrow(); .orElse(null);
}
public List<VaroTeam> getAllTeams() {
return this.teams;
} }
public void enforceTeamJoin(Player joinedPlayer) { public void enforceTeamJoin(Player joinedPlayer) {
this.getTeamFromPlayer(joinedPlayer.getUniqueId()).joinCountdown = new VaroTeam.JoinCountdown(); if(joinedPlayer.isOp()) return;
DisconnectInfo disconnectInfo = new DisconnectInfo( DisconnectInfo teamNotCompleteInfo = new DisconnectInfo(
"Teampartner nicht beigetreten", "Teampartner nicht beigetreten",
"Deine Verbindung wurde getrennt, da dein Teampartner keine Verbindung zum Server hergestellt hat!", "Deine Verbindung wurde getrennt, da dein Teampartner keine Verbindung zum Server hergestellt hat!",
"Bitte sorge dafür, dass alle anderen Teammitglieder eine einwandfreie Internetverbindung haben und melde dich im Zweifel bei einem Admin!", "Bitte sorge dafür, dass alle anderen Teammitglieder eine einwandfreie Internetverbindung haben und melde dich im Zweifel bei einem Admin!",
joinedPlayer.getUniqueId() joinedPlayer.getUniqueId()
); );
DisconnectInfo teamNoPlaytime = new DisconnectInfo(
"Keine Spielzeit verfügbar",
"Deine Verbindung wurde getrennt, da dein Team keine verbleibende Spielzeit auf dem Server hat!",
"Falls dies ein Fehler ist, melde dich bitte bei einem Admin!",
joinedPlayer.getUniqueId()
);
VaroTeam team = this.getTeamFromPlayer(joinedPlayer.getUniqueId()); VaroTeam team = this.getTeamFromPlayer(joinedPlayer.getUniqueId());
if(team == null) throw new IllegalStateException("Player must be in a Team");
PlayTimer playTimer = Main.instance().getAppliance(PlayTimer.class);
if(playTimer.isEnabled()) {
boolean isAllowed = playTimer.tryConsumeTicket(team);
if(!isAllowed) {
Main.logger().warning(String.format("Team %s joined, but got denied from Ticketing. Team will be kicked!", team.name));
team.kickTeam(teamNoPlaytime);
return;
}
} else {
Main.logger().info(String.format("Allowing team %s to join, because PlayTimer is not active. Not subtracting from PlayTimer!", team.name));
}
int leftTickets = playTimer.getTickets(team);
Main.logger().info(String.format("Player %s joined. There are %d tickets left!", joinedPlayer.getName(), leftTickets));
String playtimeOverview = String.format(
"Dein Team hat noch %d Beitritte, also %dx%d=%d Minuten übrig.",
leftTickets,
leftTickets,
PlayTimer.PLAYTIME_MINUTES,
leftTickets * PlayTimer.PLAYTIME_MINUTES
);
joinedPlayer.sendMessage(Component.text(
leftTickets == 0
? String.format("Dein Team hat ab jetzt %d Minuten Spielzeit!", PlayTimer.PLAYTIME_MINUTES)
: playtimeOverview,
NamedTextColor.GREEN
));
Bukkit.getScheduler().scheduleSyncDelayedTask( Bukkit.getScheduler().scheduleSyncDelayedTask(
Main.instance(), Main.instance(),
() -> team.members.stream() () -> team.members.stream()
@ -66,7 +152,10 @@ public class Teams extends Appliance implements DisplayName.Prefixed, DisplayNam
return p == null || !p.isOnline(); return p == null || !p.isOnline();
}) })
.findAny() .findAny()
.ifPresent(member -> team.kickTeam(disconnectInfo)), .ifPresentOrElse(
member -> team.kickTeam(teamNotCompleteInfo),
team::startCountDown
),
Ticks.TICKS_PER_SECOND * (JoinProtection.resistanceDuration / 2) Ticks.TICKS_PER_SECOND * (JoinProtection.resistanceDuration / 2)
); );
} }
@ -79,28 +168,15 @@ public class Teams extends Appliance implements DisplayName.Prefixed, DisplayNam
leftPlayer.getUniqueId() leftPlayer.getUniqueId()
); );
VaroTeam team = this.getTeamFromPlayer(leftPlayer.getUniqueId()); VaroTeam team = this.getTeamFromPlayer(leftPlayer.getUniqueId());
Bukkit.getScheduler().scheduleSyncDelayedTask( if(team != null) team.kickTeam(disconnectInfo);
Main.instance(),
() -> team.kickTeam(disconnectInfo),
Ticks.TICKS_PER_SECOND
);
}
public VaroTeam.JoinCountdown getTeamJoinCountdown(Player player) {
return this.getTeamFromPlayer(player.getUniqueId()).joinCountdown;
}
@Override
public @Nullable TextColor getNameColor(Player player) {
return NamedTextColor.WHITE ;
} }
@Override @Override
public @Nullable Component getNamePrefix(Player player) { public @Nullable Component getNamePrefix(Player player) {
VaroTeam team = this.getTeamFromPlayer(player.getUniqueId()); VaroTeam team = this.getTeamFromPlayer(player.getUniqueId());
return Component.text( return Component.text(
String.format("[%s]", team.name), String.format("[%s]", team != null ? team.name : "?"),
TextColor.fromCSSHexString(team.color) TextColor.fromCSSHexString(team != null ? team.color : "#ffffff")
); );
} }
@ -108,4 +184,9 @@ public class Teams extends Appliance implements DisplayName.Prefixed, DisplayNam
protected @NotNull List<Listener> listeners() { protected @NotNull List<Listener> listeners() {
return List.of(new ConnectivityChangeListener()); return List.of(new ConnectivityChangeListener());
} }
@Override
protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of(new TeamListCommand());
}
} }

View File

@ -1,15 +1,26 @@
package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams; package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.util.text.Countdown;
import eu.mhsl.craftattack.spawn.core.util.text.DisconnectInfo; import eu.mhsl.craftattack.spawn.core.util.text.DisconnectInfo;
import eu.mhsl.craftattack.spawn.varo.appliances.internal.teamTasks.TeamTasks;
import eu.mhsl.craftattack.spawn.varo.appliances.internal.teamTasks.tasks.CountdownTeamTask;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.fightDetector.FightDetector;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.playTimer.PlayTimer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
class VaroTeam { public class VaroTeam {
public static class Member { public static class Member {
public final OfflinePlayer player; public final OfflinePlayer player;
public boolean isDead; public boolean isDead;
@ -20,26 +31,12 @@ class VaroTeam {
} }
} }
public record JoinCountdown(long timestampSince) { public String name;
public JoinCountdown() { public String color;
this(System.currentTimeMillis()); public List<Member> members;
}
public boolean isFree(int countdownSeconds) { public VaroTeam(List<Member> members, String name, String color) {
return this.timestampSince < System.currentTimeMillis() - (countdownSeconds * 1000); this.members = new ArrayList<>(members);
}
}
public final Set<Member> members;
public final UUID teamUuid;
public final String name;
public final String color;
public JoinCountdown joinCountdown;
public VaroTeam(Set<Member> members, String name, String color) {
this.teamUuid = UUID.randomUUID();
this.members = members;
this.name = name; this.name = name;
this.color = color; this.color = color;
} }
@ -65,14 +62,80 @@ class VaroTeam {
"Unbekannter Fehler", "Unbekannter Fehler",
"Verbindung wurde aufgrund eines unbekannten Grundes getrennt", "Verbindung wurde aufgrund eines unbekannten Grundes getrennt",
"Falls du denkst, dass dies ein Fehler ist, melde dich bei einem Admin!", "Falls du denkst, dass dies ein Fehler ist, melde dich bei einem Admin!",
this.teamUuid UUID.randomUUID()
)); ));
} }
public void kickTeam(DisconnectInfo disconnectInfo) { public List<Player> getOnlinePlayers() {
this.members.stream() return this.members.stream()
.map(member -> Bukkit.getPlayer(member.player.getUniqueId())) .map(member -> Bukkit.getPlayer(member.player.getUniqueId()))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.toList();
}
public void startCountDown() {
if(!Main.instance().getAppliance(PlayTimer.class).isEnabled()) {
Main.logger().warning(String.format("PlayTimer for %s not started, because it is deactivated!", this.name));
return;
}
Main.logger().info(String.format("Starting Time countdown for Team %s with %d", this.name, PlayTimer.PLAYTIME_MINUTES));
CountdownTeamTask countdown = new CountdownTeamTask(
60 * PlayTimer.PLAYTIME_MINUTES,
announcementData -> Component.text(
String.format("Es verbleiben noch %d %s Spielzeit!", announcementData.count(), announcementData.unit()),
NamedTextColor.RED
),
component -> this.getOnlinePlayers().forEach(player -> player.sendMessage(component)),
this::timeOverKick
);
countdown.setDefaultAnnouncements(count -> {
if(count > 300) return null;
if(count > 60 && count % 60 == 0) {
return new Countdown.AnnouncementData(count / 60, "Minuten");
}
if(count <= 30 && (count <= 10 || count % 10 == 0)) {
return new Countdown.AnnouncementData(count, "Sekunden");
}
return null;
});
countdown.start();
Main.instance().getAppliance(TeamTasks.class).addTask(this, TeamTasks.Type.TIME_KICK, countdown);
}
public void kickTeam(DisconnectInfo disconnectInfo) {
this.getOnlinePlayers()
.forEach(player -> player.kick(disconnectInfo.getComponent())); .forEach(player -> player.kick(disconnectInfo.getComponent()));
Main.instance().getAppliance(TeamTasks.class).cancelTeamTasks(this);
}
public void timeOverKick() {
boolean isInFight = Main.instance().getAppliance(FightDetector.class).isInFight(this);
if(isInFight) {
Main.logger().info(String.format("Cannot kick Team %s because it is in a fight!", this.name));
this.getOnlinePlayers()
.forEach(player -> player.sendMessage(
"Du befindest dich in einer Kampfhandlung oder in der Nähe eines gegnerischen Teams. Der Kick wird verzögert!"
));
Bukkit.getScheduler().runTaskLater(
Main.instance(),
this::timeOverKick,
Ticks.TICKS_PER_SECOND * 15
);
return;
}
Main.logger().info(String.format("Kicking Team %s because time is up!", this.name));
DisconnectInfo timeOverInfo = new DisconnectInfo(
"Die Zeit ist abgelaufen!",
"Deine Spielzeit ist vorüber. Falls dir noch weitere Zeit zusteht kannst du jetzt eine Pause machen und anschließend erneut beitreten.",
"Falls du Fragen hast, melde dich bitte bei einem Admin!",
UUID.nameUUIDFromBytes("".getBytes())
);
this.kickTeam(timeOverInfo);
} }
} }

View File

@ -1,11 +1,15 @@
package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.varoDeath; package eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.varoDeath;
import eu.mhsl.craftattack.spawn.core.Main; import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.api.HttpStatus;
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance; import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.core.util.text.DisconnectInfo; import eu.mhsl.craftattack.spawn.core.util.text.DisconnectInfo;
import eu.mhsl.craftattack.spawn.varo.api.repositories.VaroPlayerRepository;
import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams.Teams; import eu.mhsl.craftattack.spawn.varo.appliances.metaGameplay.teams.Teams;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -13,6 +17,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
public class VaroDeath extends Appliance { public class VaroDeath extends Appliance {
public void registerPlayerDeath(Player player, @Nullable Component component) { public void registerPlayerDeath(Player player, @Nullable Component component) {
@ -24,7 +29,16 @@ public class VaroDeath extends Appliance {
player.getUniqueId() player.getUniqueId()
); );
//TODO send player death to backend var death = new VaroPlayerRepository.VaroDeath(
player.getUniqueId(),
Optional.ofNullable(player.getKiller())
.map(Entity::getUniqueId)
.orElse(null),
deathMessage
);
ReqResp<Void> response = this.queryRepository(VaroPlayerRepository.class).registerDeath(death);
if(response.status() != HttpStatus.OK) Main.logger().warning(String.format("Failed to send death: %s", response));
disconnectInfo.applyKick(player); disconnectInfo.applyKick(player);
Main.instance().getAppliance(Teams.class).refreshTeamList(); Main.instance().getAppliance(Teams.class).refreshTeamList();

View File

@ -0,0 +1,173 @@
package eu.mhsl.craftattack.spawn.varo.appliances.tooling.projectStart;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.core.config.Configuration;
import eu.mhsl.craftattack.spawn.core.util.IteratorUtil;
import eu.mhsl.craftattack.spawn.core.util.entity.PlayerUtils;
import eu.mhsl.craftattack.spawn.core.util.text.ComponentUtil;
import eu.mhsl.craftattack.spawn.core.util.text.Countdown;
import eu.mhsl.craftattack.spawn.varo.appliances.tooling.projectStart.command.ProjectStartCancelCommand;
import eu.mhsl.craftattack.spawn.varo.appliances.tooling.projectStart.command.ProjectStartCommand;
import eu.mhsl.craftattack.spawn.varo.appliances.tooling.projectStart.command.ProjectStartResetCommand;
import eu.mhsl.craftattack.spawn.varo.appliances.tooling.projectStart.listener.NoAdvancementsListener;
import eu.mhsl.craftattack.spawn.varo.appliances.tooling.projectStart.listener.PlayerInvincibleListener;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.*;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static java.util.Map.entry;
import static org.bukkit.Sound.MUSIC_DISC_PRECIPICE;
public class ProjectStart extends Appliance {
private final int startMusicAt = 293;
private final World startWorld = Bukkit.getWorld("world");
private final Countdown countdown = new Countdown(
this.localConfig().getInt("countdown"),
this::format,
this::announce,
this::startProject
);
private final Map<GameRule<Boolean>, Boolean> gameRulesAfterStart = Map.ofEntries(
entry(GameRule.DO_DAYLIGHT_CYCLE, true),
entry(GameRule.DO_INSOMNIA, true),
entry(GameRule.DISABLE_RAIDS, false),
entry(GameRule.DO_FIRE_TICK, true),
entry(GameRule.DO_ENTITY_DROPS, true),
entry(GameRule.DO_PATROL_SPAWNING, true),
entry(GameRule.DO_TRADER_SPAWNING, true),
entry(GameRule.DO_WEATHER_CYCLE, true),
entry(GameRule.FALL_DAMAGE, true),
entry(GameRule.FIRE_DAMAGE, true)
);
public ProjectStart() {
super("countdown");
this.countdown.addCustomAnnouncement(
new Countdown.CustomAnnouncements(
counter -> counter == this.startMusicAt,
counter -> {
Objects.requireNonNull(this.startWorld);
this.startWorld.playSound(new Location(this.startWorld, 152, 77, 179), MUSIC_DISC_PRECIPICE, SoundCategory.RECORDS, 500f, 1f);
}
)
);
}
private Component format(Countdown.AnnouncementData data) {
return Component.text()
.append(ComponentUtil.createRainbowText("Varo", 10))
.append(Component.text(" startet in ", NamedTextColor.GOLD))
.append(Component.text(data.count(), NamedTextColor.AQUA))
.append(Component.text(" " + data.unit() + "!", NamedTextColor.GOLD))
.build();
}
private void announce(Component message) {
IteratorUtil.onlinePlayers(player -> player.sendMessage(message));
}
private void resetAdvancements() {
Bukkit.getServer().advancementIterator().forEachRemaining(
advancement -> Bukkit.getOnlinePlayers().forEach(
player -> player.getAdvancementProgress(advancement).getAwardedCriteria().forEach(
criteria -> player.getAdvancementProgress(advancement).revokeCriteria(criteria)
)
)
);
}
public void startCountdown() {
if(!this.isEnabled()) return;
this.countdown.start();
}
public void cancelCountdown() {
this.countdown.cancel();
this.restoreBeforeStart();
}
public void startProject() {
this.setEnabled(false);
IteratorUtil.worlds(World::getWorldBorder, worldBorder -> worldBorder.setSize(5000));
IteratorUtil.worlds(world -> IteratorUtil.setGameRules(this.gameRulesAfterStart, false));
IteratorUtil.worlds(world -> world.setFullTime(0));
Bukkit.getOnlinePlayers().forEach(player -> {
player.setFoodLevel(20);
player.setHealth(20);
player.getInventory().clear();
player.setGameMode(GameMode.SURVIVAL);
player.setExp(0);
player.setLevel(0);
player.playSound(Sound.sound(org.bukkit.Sound.ITEM_GOAT_HORN_SOUND_5, Sound.Source.MASTER, 500f, 1f));
player.sendMessage(Component.text("Viel Spaß bei Varo!", NamedTextColor.GREEN));
player.setStatistic(Statistic.TIME_SINCE_REST, 0);
PlayerUtils.resetStatistics(player);
});
this.resetAdvancements();
}
public void restoreBeforeStart() {
this.setEnabled(true);
IteratorUtil.onlinePlayers(Player::stopAllSounds);
IteratorUtil.worlds(World::getWorldBorder, worldBorder -> {
worldBorder.setSize(this.localConfig().getLong("worldborder-before"));
worldBorder.setWarningDistance(0);
worldBorder.setDamageAmount(0);
});
IteratorUtil.worlds(world -> world, world -> IteratorUtil.setGameRules(this.gameRulesAfterStart, true));
this.resetAdvancements();
}
public boolean isEnabled() {
return this.localConfig().getBoolean("enabled");
}
public void setEnabled(boolean enabled) {
this.localConfig().set("enabled", enabled);
Configuration.saveChanges();
}
public Countdown getCountdown() {
return this.countdown;
}
@Override
@NotNull
protected List<Listener> listeners() {
return List.of(
new PlayerInvincibleListener(),
new NoAdvancementsListener()
);
}
@Override
@NotNull
protected List<ApplianceCommand<?>> commands() {
return List.of(
new ProjectStartCommand(),
new ProjectStartCancelCommand(),
new ProjectStartResetCommand()
);
}
}

View File

@ -0,0 +1,25 @@
package eu.mhsl.craftattack.spawn.varo.appliances.tooling.projectStart.command;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.varo.appliances.tooling.projectStart.ProjectStart;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class ProjectStartCancelCommand extends ApplianceCommand<ProjectStart> {
public ProjectStartCancelCommand() {
super("projectStartCancel");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(this.getAppliance().getCountdown().isRunning()) {
this.getAppliance().cancelCountdown();
sender.sendMessage(Component.text("Countdown cancelled successfully!").color(NamedTextColor.GREEN));
} else {
sender.sendMessage(Component.text("Countdown is not running!").color(NamedTextColor.RED));
}
}
}

View File

@ -0,0 +1,30 @@
package eu.mhsl.craftattack.spawn.varo.appliances.tooling.projectStart.command;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.varo.appliances.tooling.projectStart.ProjectStart;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class ProjectStartCommand extends ApplianceCommand<ProjectStart> {
public ProjectStartCommand() {
super("projectStart");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(!this.getAppliance().isEnabled()) {
sender.sendMessage(Component.text("Countdown not enabled or executed once before!").color(NamedTextColor.RED));
return;
}
if(this.getAppliance().getCountdown().isRunning()) {
sender.sendMessage(Component.text("Countdown already running!").color(NamedTextColor.RED));
return;
}
this.getAppliance().startCountdown();
}
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.craftattack.spawn.varo.appliances.tooling.projectStart.command;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.varo.appliances.tooling.projectStart.ProjectStart;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class ProjectStartResetCommand extends ApplianceCommand<ProjectStart> {
public ProjectStartResetCommand() {
super("projectStartReset");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
this.getAppliance().restoreBeforeStart();
}
}

View File

@ -0,0 +1,22 @@
package eu.mhsl.craftattack.spawn.varo.appliances.tooling.projectStart.listener;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.varo.appliances.tooling.projectStart.ProjectStart;
import org.bukkit.advancement.Advancement;
import org.bukkit.advancement.AdvancementProgress;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerAdvancementDoneEvent;
public class NoAdvancementsListener extends ApplianceListener<ProjectStart> {
@EventHandler
public void onAdvancement(PlayerAdvancementDoneEvent event) {
if(!this.getAppliance().isEnabled()) return;
event.message(null);
Advancement advancement = event.getAdvancement();
AdvancementProgress progress = event.getPlayer().getAdvancementProgress(advancement);
for(String criteria : progress.getAwardedCriteria()) {
progress.revokeCriteria(criteria);
}
}
}

View File

@ -0,0 +1,27 @@
package eu.mhsl.craftattack.spawn.varo.appliances.tooling.projectStart.listener;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.varo.appliances.tooling.projectStart.ProjectStart;
import io.papermc.paper.event.player.PrePlayerAttackEntityEvent;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.FoodLevelChangeEvent;
public class PlayerInvincibleListener extends ApplianceListener<ProjectStart> {
@EventHandler
public void onDamage(EntityDamageEvent event) {
if(!(event.getEntity() instanceof Player)) return;
if(this.getAppliance().isEnabled()) event.setCancelled(true);
}
@EventHandler
public void onHunger(FoodLevelChangeEvent event) {
if(this.getAppliance().isEnabled()) event.setCancelled(true);
}
@EventHandler
public void onHit(PrePlayerAttackEntityEvent event) {
if(this.getAppliance().isEnabled()) event.setCancelled(true);
}
}

View File

@ -0,0 +1,27 @@
package eu.mhsl.craftattack.spawn.varo.appliances.tooling.spawnpoint;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
class SetSpawnpointCommand extends ApplianceCommand.PlayerChecked<Spawnpoint> {
public SetSpawnpointCommand() {
super("setSpawnpoint");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(args.length < 1) throw new Error("Specify playername for the Spawnpoint");
var location = this.getPlayer().getLocation()
.toBlockLocation()
.add(0.5, 0.5, 0.5);
location.setYaw(0);
location.setPitch(0);
this.getAppliance().setSpawnpoint(location, args[0]);
sender.sendMessage(Component.text("Spawnpoint updated!", NamedTextColor.GREEN));
}
}

View File

@ -0,0 +1,20 @@
package eu.mhsl.craftattack.spawn.varo.appliances.tooling.spawnpoint;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
class SpawnAtSpawnpointListener extends ApplianceListener<Spawnpoint> {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
this.getAppliance().handlePlayerLogin(event.getPlayer());
}
@EventHandler
public void onRespawn(PlayerRespawnEvent event) {
if(event.isBedSpawn()) return;
if(event.isAnchorSpawn()) return;
event.setRespawnLocation(this.getAppliance().getSpawnPoint(event.getPlayer()));
}
}

View File

@ -0,0 +1,105 @@
package eu.mhsl.craftattack.spawn.varo.appliances.tooling.spawnpoint;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public class Spawnpoint extends Appliance {
private static final String namespace = Spawnpoint.class.getSimpleName().toLowerCase(Locale.ROOT);
public static NamespacedKey alreadySpawned = new NamespacedKey(namespace, "alreadySpawned".toLowerCase());
private final Path saveFile = Paths.get(Main.instance().getDataFolder().getAbsolutePath() + "/spawnpoints.json");
private final Map<UUID, Location> spawnPoints = new HashMap<>();
public Spawnpoint() {
super("spawnpoint");
this.load();
}
private record LocationDto(String world, double x, double y, double z, float yaw, float pitch) {
static LocationDto from(Location loc) {
return new LocationDto(loc.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch());
}
Location toLocation() {
return new Location(Bukkit.getWorld(this.world), this.x, this.y, this.z, this.yaw, this.pitch);
}
}
private void load() {
if (!Files.exists(this.saveFile)) return;
try (Reader reader = Files.newBufferedReader(this.saveFile)) {
Type type = new TypeToken<Map<String, LocationDto>>() {}.getType();
Map<String, LocationDto> raw = new Gson().fromJson(reader, type);
for (var entry : raw.entrySet()) {
this.spawnPoints.put(UUID.fromString(entry.getKey()), entry.getValue().toLocation());
}
} catch (IOException e) {
Main.logger().warning("Failed to load spawnpoints: " + e.getMessage());
}
}
private void save() {
try {
Files.createDirectories(this.saveFile.getParent());
Map<String, LocationDto> raw = new HashMap<>();
for (var entry : this.spawnPoints.entrySet()) {
raw.put(entry.getKey().toString(), LocationDto.from(entry.getValue()));
}
try (Writer writer = Files.newBufferedWriter(this.saveFile)) {
new Gson().toJson(raw, writer);
}
} catch (IOException e) {
Main.logger().warning("Failed to save spawnpoints: " + e.getMessage());
}
}
public void setSpawnpoint(Location location, String playerName) {
UUID uuid = Bukkit.getOfflinePlayer(playerName).getUniqueId();
this.spawnPoints.put(uuid, location);
this.save();
}
public void handlePlayerLogin(Player player) {
PersistentDataContainer dataContainer = player.getPersistentDataContainer();
if(dataContainer.has(alreadySpawned)) return;
player.teleportAsync(this.spawnPoints.get(player.getUniqueId()));
player.setGameMode(GameMode.SURVIVAL);
dataContainer.set(alreadySpawned, PersistentDataType.BOOLEAN, true);
}
public Location getSpawnPoint(Player player) {
return this.spawnPoints.get(player.getUniqueId());
}
@Override
protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of(new SetSpawnpointCommand());
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new SpawnAtSpawnpointListener());
}
}