Skip to content

Commit

Permalink
Add compass tracking module
Browse files Browse the repository at this point in the history
Signed-off-by: Christopher White <[email protected]>
  • Loading branch information
cswhite2000 committed Mar 11, 2024
1 parent ffd116e commit 4d25e14
Show file tree
Hide file tree
Showing 9 changed files with 443 additions and 0 deletions.
3 changes: 3 additions & 0 deletions core/src/main/java/tc/oc/pgm/api/Modules.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import tc.oc.pgm.broadcast.BroadcastModule;
import tc.oc.pgm.classes.ClassMatchModule;
import tc.oc.pgm.classes.ClassModule;
import tc.oc.pgm.compass.CompassMatchModule;
import tc.oc.pgm.compass.CompassModule;
import tc.oc.pgm.consumable.ConsumableMatchModule;
import tc.oc.pgm.consumable.ConsumableModule;
import tc.oc.pgm.controlpoint.ControlPointMatchModule;
Expand Down Expand Up @@ -247,6 +249,7 @@ void registerAll() {
register(ScoreModule.class, ScoreMatchModule.class, new ScoreModule.Factory());
register(KitModule.class, KitMatchModule.class, new KitModule.Factory());
register(ActionModule.class, ActionMatchModule.class, new ActionModule.Factory());
register(CompassModule.class, CompassMatchModule.class, new CompassModule.Factory());
register(
ItemDestroyModule.class, ItemDestroyMatchModule.class, new ItemDestroyModule.Factory());
register(ToolRepairModule.class, ToolRepairMatchModule.class, new ToolRepairModule.Factory());
Expand Down
177 changes: 177 additions & 0 deletions core/src/main/java/tc/oc/pgm/compass/CompassMatchModule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package tc.oc.pgm.compass;

import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.translatable;
import static net.kyori.adventure.text.format.Style.style;
import static tc.oc.pgm.util.text.TextTranslations.translateLegacy;

import com.google.common.collect.ImmutableList;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.bukkit.Material;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.ItemSpawnEvent;
import org.bukkit.event.inventory.InventoryMoveItemEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.ItemMeta;
import tc.oc.pgm.api.match.Match;
import tc.oc.pgm.api.match.MatchModule;
import tc.oc.pgm.api.match.Tickable;
import tc.oc.pgm.api.player.MatchPlayer;
import tc.oc.pgm.api.time.Tick;
import tc.oc.pgm.events.PlayerResetEvent;
import tc.oc.pgm.spawns.events.ParticipantKitApplyEvent;

public class CompassMatchModule implements MatchModule, Tickable, Listener {

private static final long REFRESH_TICKS = 20 * 2;
private final Match match;
private final Map<UUID, Long> lastRefresh;
private final ImmutableList<CompassTarget> compassTargets;
private final OrderStrategy orderStrategy;
private final boolean showDistance;

public CompassMatchModule(
Match match,
ImmutableList<CompassTarget> compassTargets,
OrderStrategy orderStrategy,
boolean showDistance) {
this.match = match;
this.lastRefresh = new ConcurrentHashMap<>();
this.compassTargets = compassTargets;
this.orderStrategy = orderStrategy;
this.showDistance = showDistance;
}

@Override
public synchronized void tick(Match match, Tick tick) {
for (Map.Entry<UUID, Long> lastRefreshEntry : lastRefresh.entrySet()) {
if (tick.tick - lastRefreshEntry.getValue() >= REFRESH_TICKS) {
refreshPlayer(lastRefreshEntry.getKey(), tick.tick);
}
}
}

private void refreshPlayer(UUID uuid, long tick) {
MatchPlayer player = match.getPlayer(uuid);
if (player == null || !player.isAlive()) {
lastRefresh.remove(uuid);
return;
} else {
lastRefresh.put(uuid, tick);
}

updatePlayerCompass(player, chooseCompassTarget(player));
}

private void updatePlayerCompass(
MatchPlayer player, Optional<CompassTargetResult> compassResult) {
compassResult.ifPresent(
compassTargetResult ->
player.getBukkit().setCompassTarget(compassTargetResult.getLocation()));

PlayerInventory inventory = player.getInventory();
if (inventory == null) {
return;
}
ItemStack[] contents = inventory.getContents();
if (contents == null) {
return;
}

for (ItemStack content : contents) {
if (content != null && Material.COMPASS.equals(content.getType())) {
ItemMeta itemMeta = content.getItemMeta();

Component itemNameComponent;
if (compassResult.isPresent()) {
Component resultComponent = compassResult.get().getComponent();

TextComponent.Builder builder =
text()
.append(
translatable(
"compass.tracking", style(NamedTextColor.GRAY, TextDecoration.BOLD)))
.append(text(": ", style(NamedTextColor.WHITE, TextDecoration.BOLD)))
.append(resultComponent);

if (showDistance) {
builder
.append(text(" "))
.append(
translatable(
"compass.tracking.distance",
style(NamedTextColor.AQUA, TextDecoration.BOLD),
text((int) compassResult.get().getDistance())));
}

itemNameComponent = builder.build();
} else {
itemNameComponent =
translatable(
"compass.tracking.unknown", style(NamedTextColor.WHITE, TextDecoration.BOLD));
}

itemMeta.setDisplayName(translateLegacy(itemNameComponent, player));
content.setItemMeta(itemMeta);
}
}
inventory.setContents(contents);
}

private Optional<CompassTargetResult> chooseCompassTarget(MatchPlayer player) {
Optional<CompassTargetResult> result = Optional.empty();
Stream<CompassTargetResult> targetStream =
compassTargets.stream()
.map((compassTarget -> compassTarget.getResult(match, player)))
.filter(Optional::isPresent)
.map(Optional::get);
switch (orderStrategy) {
case FIRST_DEFINED:
result = targetStream.findFirst();
break;
case CLOSEST:
result = targetStream.min(CompassTargetResult::compareTo);
break;
}
return result;
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerReset(PlayerResetEvent event) {
this.lastRefresh.put(event.getPlayer().getId(), -REFRESH_TICKS);
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onItemDrop(ItemSpawnEvent event) {
ItemStack itemStack = event.getEntity().getItemStack();
if (Material.COMPASS.equals(itemStack.getType())) {
event.getEntity().setItemStack(new ItemStack(Material.COMPASS, itemStack.getAmount()));
}
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onItemMove(InventoryMoveItemEvent event) {
if (!(event.getDestination() instanceof PlayerInventory)) {
ItemStack itemStack = event.getItem();
if (Material.COMPASS.equals(itemStack.getType())) {
event.setItem(new ItemStack(Material.COMPASS, itemStack.getAmount()));
}
}
}

@EventHandler(priority = EventPriority.MONITOR)
public void onSpawn(ParticipantKitApplyEvent event) {
this.lastRefresh.put(event.getPlayer().getId(), -REFRESH_TICKS);
}
}
71 changes: 71 additions & 0 deletions core/src/main/java/tc/oc/pgm/compass/CompassModule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package tc.oc.pgm.compass;

import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.logging.Logger;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jetbrains.annotations.Nullable;
import tc.oc.pgm.api.map.MapModule;
import tc.oc.pgm.api.map.factory.MapFactory;
import tc.oc.pgm.api.map.factory.MapModuleFactory;
import tc.oc.pgm.api.match.Match;
import tc.oc.pgm.api.match.MatchModule;
import tc.oc.pgm.api.module.exception.ModuleLoadException;
import tc.oc.pgm.filters.FilterMatchModule;
import tc.oc.pgm.util.xml.InvalidXMLException;
import tc.oc.pgm.util.xml.Node;
import tc.oc.pgm.util.xml.XMLUtils;

public class CompassModule implements MapModule<CompassMatchModule> {

private final ImmutableList<CompassTarget> compassTargets;
private final OrderStrategy orderStrategy;
private final boolean showDistance;

public CompassModule(
ImmutableList<CompassTarget> compassTargets,
OrderStrategy orderStrategy,
boolean showDistance) {
this.compassTargets = compassTargets;
this.orderStrategy = orderStrategy;
this.showDistance = showDistance;
}

@Nullable
@Override
public Collection<Class<? extends MatchModule>> getHardDependencies() {
return ImmutableList.of(FilterMatchModule.class);
}

@Override
public @Nullable CompassMatchModule createMatchModule(Match match) throws ModuleLoadException {
return new CompassMatchModule(match, compassTargets, orderStrategy, showDistance);
}

public static class Factory implements MapModuleFactory<CompassModule> {
@Override
public @Nullable CompassModule parse(MapFactory factory, Logger logger, Document doc)
throws InvalidXMLException {
CompassParser parser = new CompassParser(factory);

ImmutableList.Builder<CompassTarget> compassTargets = ImmutableList.builder();
OrderStrategy orderStrategy = OrderStrategy.FIRST_DEFINED;
boolean showDistance = false;
for (Element compassRoot : doc.getRootElement().getChildren("compass")) {
orderStrategy =
XMLUtils.parseEnum(
Node.fromAttr(compassRoot, "order"), OrderStrategy.class, orderStrategy);
showDistance =
XMLUtils.parseBoolean(Node.fromAttr(compassRoot, "show-distance"), showDistance);
for (Element compassTarget : compassRoot.getChildren()) {
if (parser.isTarget(compassTarget)) {
compassTargets.add(parser.parseCompassTarget(compassTarget));
}
}
}

return new CompassModule(compassTargets.build(), orderStrategy, showDistance);
}
}
}
61 changes: 61 additions & 0 deletions core/src/main/java/tc/oc/pgm/compass/CompassParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package tc.oc.pgm.compass;

import java.lang.reflect.Method;
import java.util.Map;
import net.kyori.adventure.text.Component;
import org.jdom2.Element;
import tc.oc.pgm.api.filter.Filter;
import tc.oc.pgm.api.map.factory.MapFactory;
import tc.oc.pgm.compass.targets.PlayerCompassTarget;
import tc.oc.pgm.features.FeatureDefinitionContext;
import tc.oc.pgm.filters.parse.FilterParser;
import tc.oc.pgm.util.MethodParser;
import tc.oc.pgm.util.MethodParsers;
import tc.oc.pgm.util.xml.InvalidXMLException;
import tc.oc.pgm.util.xml.Node;
import tc.oc.pgm.util.xml.XMLUtils;

public class CompassParser {

private final MapFactory factory;
private final FeatureDefinitionContext features;
private final FilterParser filters;
private final Map<String, Method> methodParsers;

public CompassParser(MapFactory factory) {
this.factory = factory;
this.features = factory.getFeatures();
this.filters = factory.getFilters();
this.methodParsers = MethodParsers.getMethodParsersForClass(getClass());
}

public boolean isTarget(Element el) {
return getParserFor(el) != null;
}

protected Method getParserFor(Element el) {
return methodParsers.get(el.getName().toLowerCase());
}

public CompassTarget parseCompassTarget(Element el) throws InvalidXMLException {
Method parser = getParserFor(el);
if (parser != null) {
try {
return (CompassTarget) parser.invoke(this, el);
} catch (Exception e) {
throw InvalidXMLException.coerce(e, new Node(el));
}
} else {
throw new InvalidXMLException("Unknown compass tracker type: " + el.getName(), el);
}
}

@MethodParser("player")
public PlayerCompassTarget parsePlayerTarget(Element el) throws InvalidXMLException {
Component name = XMLUtils.parseFormattedText(Node.fromChildOrAttr(el, "name"));
Filter filter = filters.parseProperty(el, "filter");
boolean showPlayerName = XMLUtils.parseBoolean(Node.fromAttr(el, "show-player"), false);

return new PlayerCompassTarget(filter, name, showPlayerName);
}
}
18 changes: 18 additions & 0 deletions core/src/main/java/tc/oc/pgm/compass/CompassTarget.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package tc.oc.pgm.compass;

import java.util.Optional;
import net.kyori.adventure.text.Component;
import org.bukkit.Location;
import tc.oc.pgm.api.filter.Filter;
import tc.oc.pgm.api.match.Match;
import tc.oc.pgm.api.player.MatchPlayer;

public interface CompassTarget {
Filter getFilter();

Component getName(Match match, MatchPlayer player);

Optional<Location> getLocation(Match match, MatchPlayer player);

Optional<CompassTargetResult> getResult(Match match, MatchPlayer player);
}
32 changes: 32 additions & 0 deletions core/src/main/java/tc/oc/pgm/compass/CompassTargetResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package tc.oc.pgm.compass;

import net.kyori.adventure.text.Component;
import org.bukkit.Location;

public class CompassTargetResult {
private final Location location;
private final double distance;
private final Component component;

public CompassTargetResult(Location location, double distance, Component component) {
this.location = location;
this.distance = distance;
this.component = component;
}

public Location getLocation() {
return location;
}

public double getDistance() {
return distance;
}

public Component getComponent() {
return component;
}

public int compareTo(CompassTargetResult other) {
return (int) (distance - other.distance);
}
}
6 changes: 6 additions & 0 deletions core/src/main/java/tc/oc/pgm/compass/OrderStrategy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package tc.oc.pgm.compass;

public enum OrderStrategy {
FIRST_DEFINED,
CLOSEST,
}
Loading

0 comments on commit 4d25e14

Please sign in to comment.