forked from SteamWar/SteamWar
Add BauSystem module
Fix ci java version Fix LinkageProcessor
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2023 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.features.tracer;
|
||||
|
||||
import de.steamwar.bausystem.region.Region;
|
||||
import de.steamwar.bausystem.region.utils.RegionExtensionType;
|
||||
import de.steamwar.bausystem.region.utils.RegionType;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.TNTPrimed;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Recording of a tnt at a specific tick
|
||||
*/
|
||||
@Getter
|
||||
public class TNTPoint implements Externalizable {
|
||||
/**
|
||||
* Unique number to identify records being of the same tnt
|
||||
*/
|
||||
private int tntId;
|
||||
|
||||
/**
|
||||
* Whether this is a record of a tnt explosion or an entity
|
||||
*/
|
||||
private boolean explosion;
|
||||
|
||||
/**
|
||||
* Whether this is a record of a tnt that was in water
|
||||
*/
|
||||
private boolean inWater;
|
||||
|
||||
/**
|
||||
* Whether this record was taken after the first tnt exploded
|
||||
*/
|
||||
private boolean afterFirstExplosion;
|
||||
|
||||
/**
|
||||
* Whether this record has destroyed blocks in build area
|
||||
*/
|
||||
private boolean destroyedBuildArea;
|
||||
|
||||
/**
|
||||
* Whether this record has destroyed blocks in testblock area
|
||||
*/
|
||||
private boolean destroyedTestBlock;
|
||||
|
||||
/**
|
||||
* Tick offset, from this record being taken to the start of the trace
|
||||
*/
|
||||
private long ticksSinceStart;
|
||||
|
||||
/**
|
||||
* Fuse ticks of the recorded tnt (0 if this is an explosion)
|
||||
*/
|
||||
private int fuse;
|
||||
|
||||
/**
|
||||
* Location of the recorded tnt
|
||||
*/
|
||||
private Location location;
|
||||
|
||||
/**
|
||||
* Velocity of the recorded tnt
|
||||
*/
|
||||
private Vector velocity;
|
||||
|
||||
/**
|
||||
* List of all tnt records, that are represent the same tnt
|
||||
*/
|
||||
private List<TNTPoint> history;
|
||||
|
||||
/**
|
||||
* Constructor for deserialization only !! Do not Call !!
|
||||
*/
|
||||
public TNTPoint() {
|
||||
}
|
||||
|
||||
public TNTPoint(int tntId, TNTPrimed tnt, boolean explosion, boolean afterFirstExplosion, long ticksSinceStart,
|
||||
List<TNTPoint> history, List<Block> destroyedBlocks) {
|
||||
this.tntId = tntId;
|
||||
this.explosion = explosion;
|
||||
this.inWater = tnt.isInWater();
|
||||
this.afterFirstExplosion = afterFirstExplosion;
|
||||
this.ticksSinceStart = ticksSinceStart;
|
||||
fuse = tnt.getFuseTicks();
|
||||
location = tnt.getLocation();
|
||||
velocity = tnt.getVelocity();
|
||||
this.history = history;
|
||||
|
||||
boolean buildDestroy = false;
|
||||
boolean testblockDestroy = false;
|
||||
for (Block destroyedBlock : destroyedBlocks) {
|
||||
if (Region.getRegion(destroyedBlock.getLocation()).inRegion(destroyedBlock.getLocation(), RegionType.BUILD,
|
||||
RegionExtensionType.EXTENSION)) {
|
||||
buildDestroy = true;
|
||||
}
|
||||
if (Region.getRegion(destroyedBlock.getLocation()).inRegion(destroyedBlock.getLocation(),
|
||||
RegionType.TESTBLOCK, RegionExtensionType.EXTENSION)) {
|
||||
testblockDestroy = true;
|
||||
}
|
||||
}
|
||||
|
||||
destroyedBuildArea = buildDestroy;
|
||||
destroyedTestBlock = testblockDestroy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for getting the next record of the tnt represented by this record
|
||||
*
|
||||
* @return the next record
|
||||
*/
|
||||
public Optional<TNTPoint> getNext() {
|
||||
int index = history.indexOf(this);
|
||||
return index == history.size() - 1 ? Optional.empty() : Optional.of(history.get(index + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for getting the previous record of the tnt represented by this record
|
||||
*
|
||||
* @return the previous record
|
||||
*/
|
||||
public Optional<TNTPoint> getPrevious() {
|
||||
int index = history.indexOf(this);
|
||||
return index == 0 ? Optional.empty() : Optional.of(history.get(index - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal methode for setting the history of this record
|
||||
* during deserialization.
|
||||
*
|
||||
* @param history
|
||||
*/
|
||||
void setHistory(List<TNTPoint> history) {
|
||||
this.history = history;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeExternal(ObjectOutput objectOutput) throws IOException {
|
||||
objectOutput.writeInt(tntId);
|
||||
objectOutput.writeBoolean(explosion);
|
||||
objectOutput.writeBoolean(inWater);
|
||||
objectOutput.writeBoolean(afterFirstExplosion);
|
||||
objectOutput.writeBoolean(destroyedBuildArea);
|
||||
objectOutput.writeBoolean(destroyedTestBlock);
|
||||
objectOutput.writeLong(ticksSinceStart);
|
||||
objectOutput.writeInt(fuse);
|
||||
objectOutput.writeDouble(location.getX());
|
||||
objectOutput.writeDouble(location.getY());
|
||||
objectOutput.writeDouble(location.getZ());
|
||||
objectOutput.writeDouble(velocity.getX());
|
||||
objectOutput.writeDouble(velocity.getY());
|
||||
objectOutput.writeDouble(velocity.getZ());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput objectInput) throws IOException {
|
||||
tntId = objectInput.readInt();
|
||||
explosion = objectInput.readBoolean();
|
||||
inWater = objectInput.readBoolean();
|
||||
afterFirstExplosion = objectInput.readBoolean();
|
||||
destroyedBuildArea = objectInput.readBoolean();
|
||||
destroyedTestBlock = objectInput.readBoolean();
|
||||
ticksSinceStart = objectInput.readLong();
|
||||
fuse = objectInput.readInt();
|
||||
double locX = objectInput.readDouble();
|
||||
double locY = objectInput.readDouble();
|
||||
double locZ = objectInput.readDouble();
|
||||
location = new Location(Bukkit.getWorlds().get(0), locX, locY, locZ);
|
||||
double velX = objectInput.readDouble();
|
||||
double velY = objectInput.readDouble();
|
||||
double velZ = objectInput.readDouble();
|
||||
velocity = new Vector(velX, velY, velZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TNTPoint{" +
|
||||
"tntId=" + tntId +
|
||||
", explosion=" + explosion +
|
||||
", inWater=" + inWater +
|
||||
", afterFirstExplosion=" + afterFirstExplosion +
|
||||
", destroyedBuildArea=" + destroyedBuildArea +
|
||||
", destroyedTestBlock=" + destroyedTestBlock +
|
||||
", ticksSinceStart=" + ticksSinceStart +
|
||||
", fuse=" + fuse +
|
||||
", location=" + location +
|
||||
", velocity=" + velocity +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof TNTPoint)) return false;
|
||||
TNTPoint record = (TNTPoint) obj;
|
||||
if (record.getTntId() != tntId) return false;
|
||||
if (record.getFuse() != fuse) return false;
|
||||
if (record.getTicksSinceStart() != ticksSinceStart) return false;
|
||||
if (record.isExplosion() != explosion) return false;
|
||||
if (!record.getLocation().equals(location)) return false;
|
||||
if (!record.getVelocity().equals(velocity)) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2023 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.features.tracer;
|
||||
|
||||
import de.steamwar.bausystem.features.tracer.rendering.BundleFilter;
|
||||
import de.steamwar.bausystem.features.tracer.rendering.PlayerTraceShowData;
|
||||
import de.steamwar.bausystem.features.tracer.rendering.TraceEntity;
|
||||
import de.steamwar.bausystem.features.tracer.rendering.ViewFlag;
|
||||
import de.steamwar.bausystem.region.Region;
|
||||
import de.steamwar.entity.REntity;
|
||||
import de.steamwar.entity.REntityServer;
|
||||
import lombok.Cleanup;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class Trace {
|
||||
/**
|
||||
* UUID of the trace used for
|
||||
*/
|
||||
@Getter
|
||||
private final UUID uuid;
|
||||
|
||||
/**
|
||||
* File the records are saved in
|
||||
*/
|
||||
@Getter
|
||||
private final File recordsSaveFile;
|
||||
|
||||
/**
|
||||
* File the metadata are saved in
|
||||
*/
|
||||
@Getter
|
||||
private final File metadataSaveFile;
|
||||
|
||||
/**
|
||||
* Region the trace was recorded in
|
||||
*/
|
||||
@Getter
|
||||
private final Region region;
|
||||
|
||||
/**
|
||||
* Date the trace was recorded at
|
||||
*/
|
||||
@Getter
|
||||
private final Date date;
|
||||
|
||||
/**
|
||||
* Records of TNTs, making up the trace
|
||||
*/
|
||||
private SoftReference<List<TNTPoint>> records;
|
||||
|
||||
/**
|
||||
* A map of all REntityServers rendering this trace
|
||||
*/
|
||||
private final Map<Player, REntityServer> entityServerMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructor for the creation of a new trace
|
||||
*
|
||||
* @param region the region the trace is created at
|
||||
* @param recordList the list for the records making up this trace
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Trace(Region region, List<TNTPoint> recordList) {
|
||||
this.uuid = UUID.randomUUID();
|
||||
recordsSaveFile = new File(TraceManager.tracesFolder, uuid + ".records");
|
||||
this.region = region;
|
||||
this.date = new Date();
|
||||
records = new SoftReference<>(recordList);
|
||||
metadataSaveFile = new File(TraceManager.tracesFolder, uuid + ".meta");
|
||||
|
||||
@Cleanup
|
||||
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(metadataSaveFile));
|
||||
outputStream.writeUTF(uuid.toString());
|
||||
outputStream.writeUTF(region.getName());
|
||||
outputStream.writeObject(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for serialising a trace from the file system
|
||||
*
|
||||
* @param metadataSaveFile the file for this traces metadata
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Trace(File metadataSaveFile) {
|
||||
String uuid = null;
|
||||
Region region = null;
|
||||
Date date = null;
|
||||
|
||||
this.metadataSaveFile = metadataSaveFile;
|
||||
|
||||
try {
|
||||
@Cleanup
|
||||
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(metadataSaveFile));
|
||||
uuid = inputStream.readUTF();
|
||||
region = Region.getREGION_MAP().get(inputStream.readUTF());
|
||||
date = (Date) inputStream.readObject();
|
||||
inputStream.close();
|
||||
} finally {
|
||||
this.uuid = UUID.fromString(uuid);
|
||||
this.region = region;
|
||||
this.date = date;
|
||||
recordsSaveFile = new File(TraceManager.tracesFolder, uuid + ".records");
|
||||
this.records = new SoftReference<>(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the histories of all tnts in this trace
|
||||
*
|
||||
* @return the histories of this trace
|
||||
*/
|
||||
public Set<List<TNTPoint>> getHistories() {
|
||||
Set<List<TNTPoint>> histories = new HashSet<>();
|
||||
|
||||
for (TNTPoint record : getRecords()) {
|
||||
histories.add(record.getHistory());
|
||||
}
|
||||
|
||||
return histories;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of all used ids
|
||||
*/
|
||||
public List<String> getUsedIds() {
|
||||
return getRecords()
|
||||
.stream()
|
||||
.map(TNTPoint::getTntId)
|
||||
.map(i -> i + "")
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders this traces
|
||||
*
|
||||
* @param player The player the trace is rendered to
|
||||
* @param playerTraceShowData The showData for modifying the rendering
|
||||
*/
|
||||
public void render(Player player, PlayerTraceShowData playerTraceShowData) {
|
||||
REntityServer entityServer = entityServerMap.get(player);
|
||||
if (entityServer != null) {
|
||||
entityServer.getEntities().forEach(REntity::die);
|
||||
} else {
|
||||
entityServer = new REntityServer();
|
||||
entityServer.addPlayer(player);
|
||||
entityServer.setCallback((p, rEntity, entityAction) -> {
|
||||
if (entityAction != REntityServer.EntityAction.INTERACT) return;
|
||||
if (rEntity instanceof TraceEntity) {
|
||||
((TraceEntity) rEntity).printIntoChat(p);
|
||||
}
|
||||
});
|
||||
entityServerMap.put(player, entityServer);
|
||||
}
|
||||
render(getRecords(), entityServer, playerTraceShowData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders specific records to this trace rendering
|
||||
*
|
||||
* @param records The records to be rendered
|
||||
* @param player The player the records are rendered to
|
||||
* @param playerTraceShowData The showData for modifying the rendering
|
||||
*/
|
||||
protected void render(List<TNTPoint> records, Player player, PlayerTraceShowData playerTraceShowData) {
|
||||
REntityServer entityServer = entityServerMap.computeIfAbsent(player, k -> {
|
||||
REntityServer newEntityServer = new REntityServer();
|
||||
newEntityServer.addPlayer(k);
|
||||
newEntityServer.setCallback((p, rEntity, entityAction) -> {
|
||||
if (entityAction != REntityServer.EntityAction.INTERACT) return;
|
||||
if (rEntity instanceof TraceEntity) {
|
||||
((TraceEntity) rEntity).printIntoChat(p);
|
||||
}
|
||||
});
|
||||
return newEntityServer;
|
||||
});
|
||||
|
||||
render(records, entityServer, playerTraceShowData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal methode to render records to a REntityServer
|
||||
*
|
||||
* @param records Records to render
|
||||
* @param entityServer The show server the trace will be renderd to
|
||||
* @param playerTraceShowData The showData for modifying the rendering
|
||||
*/
|
||||
private void render(List<TNTPoint> records, REntityServer entityServer, PlayerTraceShowData playerTraceShowData) {
|
||||
if (records.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<ViewFlag> flagList = playerTraceShowData.getEffectiveViewFlags();
|
||||
|
||||
// Apply filters
|
||||
Stream<TNTPoint> workingRecordsStream = records.stream();
|
||||
for (ViewFlag flag : flagList) {
|
||||
workingRecordsStream = flag.filter(workingRecordsStream);
|
||||
}
|
||||
List<TNTPoint> workingRecords = workingRecordsStream.collect(Collectors.toList());
|
||||
|
||||
// Bundle records at unique positions
|
||||
List<List<TNTPoint>> bundles = bundleRecords(workingRecords, playerTraceShowData.getBundleFilter());
|
||||
|
||||
// Render bundled records
|
||||
List<TraceEntity> entities = new LinkedList<>();
|
||||
|
||||
for (List<TNTPoint> bundle : bundles) {
|
||||
entities.add(new TraceEntity(entityServer, bundle.get(0).getLocation(), bundle.get(0).isExplosion(), bundle, this));
|
||||
}
|
||||
|
||||
// Apply modifiers
|
||||
for (ViewFlag flag : flagList) {
|
||||
flag.modify(entityServer, entities);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundles the passed TNTRecords based on whether they are at the same location
|
||||
*
|
||||
* @param records The TNTRecords that are supposed to be bundled
|
||||
* @param filter A filter specefieng whether records can be bundled
|
||||
* @return A list of bundles
|
||||
*/
|
||||
private List<List<TNTPoint>> bundleRecords(List<TNTPoint> records, BundleFilter filter) {
|
||||
if (filter == BundleFilter.NONE) {
|
||||
return records.stream().map(List::of).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
List<List<TNTPoint>> bundles = new ArrayList<>();
|
||||
|
||||
recordsLoop:
|
||||
for (TNTPoint record : records) {
|
||||
for (int i = bundles.size() - 1; i >= 0; i--) {
|
||||
List<TNTPoint> bundle = bundles.get(i);
|
||||
|
||||
Boolean filterResult = filter.function.apply(record, bundle.get(0));
|
||||
if (filterResult == null) {
|
||||
ArrayList<TNTPoint> newBundle = new ArrayList<>();
|
||||
newBundle.add(record);
|
||||
bundles.add(newBundle);
|
||||
continue recordsLoop;
|
||||
} else if (filterResult) {
|
||||
bundle.add(record);
|
||||
continue recordsLoop;
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<TNTPoint> newBundle = new ArrayList<>();
|
||||
newBundle.add(record);
|
||||
bundles.add(newBundle);
|
||||
}
|
||||
|
||||
return bundles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides this trail for the given player
|
||||
*
|
||||
* @param player
|
||||
*/
|
||||
public void hide(Player player) {
|
||||
REntityServer entityServer = entityServerMap.remove(player);
|
||||
if (entityServer == null) {
|
||||
return;
|
||||
}
|
||||
entityServer.removePlayer(player);
|
||||
if (entityServer.getPlayers().isEmpty()) {
|
||||
entityServer.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides this trace for all players
|
||||
*/
|
||||
public void hide() {
|
||||
entityServerMap.forEach((player, entityServer) -> {
|
||||
entityServer.close();
|
||||
});
|
||||
entityServerMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the records of this trace from storage to memory
|
||||
*/
|
||||
private void loadRecords() {
|
||||
List<TNTPoint> records = new ArrayList<>();
|
||||
long readBytes = 0;
|
||||
try {
|
||||
FileInputStream fileInputStream = new FileInputStream(recordsSaveFile);
|
||||
|
||||
@Cleanup
|
||||
ObjectInputStream inputStream = new ObjectInputStream(new GZIPInputStream(fileInputStream));
|
||||
long fileLenght = recordsSaveFile.length();
|
||||
while (fileInputStream.getChannel().position() < fileLenght) {
|
||||
records.add((TNTPoint) inputStream.readObject());
|
||||
readBytes = fileInputStream.getChannel().position();
|
||||
}
|
||||
} catch (EOFException e) {
|
||||
Logger logger = Bukkit.getLogger();
|
||||
logger.log(Level.WARNING, "EOF in trace read detected in " + uuid);
|
||||
logger.log(Level.WARNING, "Read " + readBytes + "/" + recordsSaveFile.length() + " Bytes");
|
||||
logger.log(Level.WARNING, "Read so far: " + records);
|
||||
|
||||
e.printStackTrace();
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
Map<Integer, List<TNTPoint>> histories = new HashMap<>();
|
||||
for (TNTPoint record : records) {
|
||||
int tntId = record.getTntId();
|
||||
List<TNTPoint> history = histories.computeIfAbsent(tntId, id -> new ArrayList<>());
|
||||
history.add(record);
|
||||
record.setHistory(history);
|
||||
}
|
||||
|
||||
this.records = new SoftReference<>(records);
|
||||
}
|
||||
|
||||
public synchronized List<TNTPoint> getRecords() {
|
||||
if (records.get() == null) {
|
||||
loadRecords();
|
||||
}
|
||||
return records.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Trace{" +
|
||||
"uuid=" + uuid +
|
||||
", region=" + region +
|
||||
", creationTime=" + date +
|
||||
", recordsSaveFile=" + recordsSaveFile.getName() +
|
||||
", records=" + getRecords() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2023 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.features.tracer;
|
||||
|
||||
import de.steamwar.bausystem.BauSystem;
|
||||
import de.steamwar.bausystem.features.tracer.rendering.BundleFilter;
|
||||
import de.steamwar.bausystem.features.tracer.rendering.PlayerTraceShowData;
|
||||
import de.steamwar.bausystem.features.tracer.rendering.ViewFlag;
|
||||
import de.steamwar.bausystem.features.tracer.rendering.dynamicflags.AtFlag;
|
||||
import de.steamwar.bausystem.region.Region;
|
||||
import de.steamwar.command.PreviousArguments;
|
||||
import de.steamwar.command.SWCommand;
|
||||
import de.steamwar.command.TypeMapper;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Linked
|
||||
public class TraceCommand extends SWCommand {
|
||||
|
||||
public TraceCommand() {
|
||||
super("trace", "trail");
|
||||
}
|
||||
|
||||
@Register(value = "start", description = "TRACE_COMMAND_HELP_START")
|
||||
public void start(@Validator Player player) {
|
||||
Region region = Region.getRegion(player.getLocation());
|
||||
TraceRecorder.instance.startRecording(region);
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_START", player);
|
||||
}
|
||||
|
||||
@Register(value = "stop", description = "TRACE_COMMAND_HELP_STOP")
|
||||
public void stop(@Validator Player player) {
|
||||
Region region = Region.getRegion(player.getLocation());
|
||||
TraceRecorder.instance.stopRecording(region);
|
||||
|
||||
|
||||
if (TraceRecorder.instance.isAutoTraceEnabledInRegion(region)) {
|
||||
TraceRecorder.instance.removeAutoTraceRegion(region);
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_AUTO_STOP", player);
|
||||
} else {
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_STOP", player);
|
||||
}
|
||||
}
|
||||
|
||||
@Register(value = "auto", description = "TRACE_COMMAND_HELP_AUTO")
|
||||
public void auto(@Validator Player player) {
|
||||
Region region = Region.getRegion(player.getLocation());
|
||||
TraceRecorder.instance.addAutoTraceRegion(region);
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_AUTO_START", player);
|
||||
}
|
||||
|
||||
@Register(value = "show", description = "TRACE_COMMAND_HELP_SHOW")
|
||||
public void show(@Validator Player player, @OptionalValue("DEFAULT") BundleFilter bundleFilter, ViewFlag... flags) {
|
||||
showInternal(player, bundleFilter, flags);
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW", player);
|
||||
}
|
||||
|
||||
@Register(value = {"show", "at"}, description = "TRACE_COMMAND_HELP_SHOW_AT_WITH")
|
||||
public void showAt(@Validator Player player, @Min(intValue = 0) int time, @StaticValue("with") String with, @OptionalValue("DEFAULT") BundleFilter bundleFilter, ViewFlag... flags) {
|
||||
showInternal(player, time, time, bundleFilter, flags);
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW_AT", player, time);
|
||||
}
|
||||
|
||||
@Register(value = {"show", "from"}, description = "TRACE_COMMAND_HELP_SHOW_FROM_WITH")
|
||||
public void showFromTo(@Validator Player player, @Min(intValue = 0) int from, @StaticValue("with") String with, @OptionalValue("DEFAULT") BundleFilter bundleFilter, ViewFlag... flags) {
|
||||
showInternal(player, from, Integer.MAX_VALUE, bundleFilter, flags);
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW_FROM", player, from);
|
||||
}
|
||||
|
||||
@Register(value = {"show", "from"}, description = "TRACE_COMMAND_HELP_SHOW_FROM_TO_WITH")
|
||||
public void showFromTo(@Validator Player player, @Min(intValue = 0) int from, @StaticValue("to") String toString, int to, @StaticValue("with") String with, @OptionalValue("DEFAULT") BundleFilter bundleFilter, ViewFlag... flags) {
|
||||
if (to < from) {
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW_TO_SMALLER", player);
|
||||
return;
|
||||
}
|
||||
showInternal(player, from, to, bundleFilter, flags);
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW_FROM_TO", player, from, to);
|
||||
}
|
||||
|
||||
|
||||
@Register(value = {"show", "at"}, description = "TRACE_COMMAND_HELP_SHOW_AT")
|
||||
public void showAt(@Validator Player player, @Min(intValue = 0) int time) {
|
||||
TraceManager.instance.renderAt(player, time, time);
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW_AT", player, time);
|
||||
}
|
||||
|
||||
@Register(value = {"show", "from"}, description = "TRACE_COMMAND_HELP_SHOW_FROM")
|
||||
public void showFrom(@Validator Player player, @Min(intValue = 0) int from) {
|
||||
TraceManager.instance.renderAt(player, from, Integer.MAX_VALUE);
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW_FROM", player, from);
|
||||
}
|
||||
|
||||
@Register(value = {"show", "from"}, description = "TRACE_COMMAND_HELP_SHOW_FROM_TO")
|
||||
public void showFromTo(@Validator Player player, @Min(intValue = 0) int from, @StaticValue("to") String toString, int to) {
|
||||
TraceManager.instance.renderAt(player, from, to);
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW_FROM_TO", player, from, to);
|
||||
}
|
||||
|
||||
private void showInternal(Player player, BundleFilter bundleFilter, ViewFlag... flags) {
|
||||
PlayerTraceShowData playerTraceShowData = new PlayerTraceShowData(bundleFilter, flags);
|
||||
TraceManager.instance.show(player, playerTraceShowData);
|
||||
}
|
||||
|
||||
private void showInternal(Player player, int from, int to, BundleFilter bundleFilter, ViewFlag... flags) {
|
||||
PlayerTraceShowData playerTraceShowData = new PlayerTraceShowData(bundleFilter, flags);
|
||||
playerTraceShowData.addViewFlag(new AtFlag(from, to));
|
||||
TraceManager.instance.show(player, playerTraceShowData);
|
||||
}
|
||||
|
||||
@Register(value = "hide", description = "TRACE_COMMAND_HELP_HIDE")
|
||||
public void hide(@Validator Player player) {
|
||||
TraceManager.instance.hide(player);
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_HIDE", player);
|
||||
}
|
||||
|
||||
@Register(value = "delete")
|
||||
@Register(value = "clear")
|
||||
public void clear(@Validator Player player) {
|
||||
TraceManager.instance.clear(Region.getRegion(player.getLocation()));
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_CLEAR", player);
|
||||
}
|
||||
|
||||
@Register(value = "delete", description = "TRACE_COMMAND_HELP_DELETE")
|
||||
public void delete(@Validator Player player, Trace trace) {
|
||||
TraceManager.instance.remove(trace);
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_DELETE", player);
|
||||
}
|
||||
|
||||
@Register(value = "isolate", description = "TRACE_COMMAND_HELP_ISOLATE")
|
||||
public void isolate(@Validator Player player, Trace trace, @ErrorMessage("TRACE_RECORD_ID_INVALID") TNTPoint... records) {
|
||||
TraceManager.instance.isolate(player, records);
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_ISOLATE", player);
|
||||
}
|
||||
|
||||
@Register(value = "broadcast", description = "TRACE_COMMAND_HELP_BROADCAST")
|
||||
public void broadcast(@Validator Player player) {
|
||||
BauSystem.MESSAGE.broadcast("TRACE_MESSAGE_BROADCAST", "TRACE_MESSAGE_BROADCAST_HOVER", new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/trace follow " + player.getName()), player.getName());
|
||||
}
|
||||
|
||||
@Register(value = "follow", description = "TRACE_COMMAND_HELP_FOLLOW")
|
||||
public void follow(@Validator Player player, Player toFollow) {
|
||||
if (player == toFollow) {
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_FOLLOW_SELF", player);
|
||||
return;
|
||||
}
|
||||
TraceManager.instance.follow(player, toFollow);
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_FOLLOW", player, toFollow.getName());
|
||||
}
|
||||
|
||||
@Register(value = "unfollow", description = "TRACE_COMMAND_HELP_UNFOLLOW")
|
||||
public void unfollow(@Validator Player player) {
|
||||
TraceManager.instance.unfollow(player);
|
||||
BauSystem.MESSAGE.send("TRACE_MESSAGE_UNFOLLOW", player);
|
||||
}
|
||||
|
||||
@ClassMapper(value = Trace.class, local = true)
|
||||
public TypeMapper<Trace> traceClassMapper() {
|
||||
return new TypeMapper<>() {
|
||||
@Override
|
||||
public Trace map(CommandSender commandSender, String[] previousArguments, String s) {
|
||||
return TraceManager.instance.get(Integer.parseInt(s)).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments, String s) {
|
||||
return TraceManager.instance.getAllIds().stream()
|
||||
.map(Object::toString)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ClassMapper(value = TNTPoint.class, local = true)
|
||||
public TypeMapper<TNTPoint> recordMapper() {
|
||||
return new TypeMapper<>() {
|
||||
@Override
|
||||
public TNTPoint map(CommandSender commandSender, PreviousArguments previousArguments, String s) {
|
||||
Trace trace = previousArguments.getFirst(Trace.class).orElse(null);
|
||||
if (trace == null) return null;
|
||||
|
||||
int id = Integer.parseInt(s);
|
||||
return trace.getRecords().stream()
|
||||
.filter(record -> record.getTntId() == id)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
// TODO change when new command framework update
|
||||
@Override
|
||||
public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments, String s) {
|
||||
Trace trace = previousArguments.getFirst(Trace.class).orElse(null);
|
||||
if (trace == null) return null;
|
||||
return trace.getUsedIds();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ClassMapper(value = BundleFilter.class, local = true)
|
||||
public TypeMapper<BundleFilter> bundleFilterClassMapper() {
|
||||
return new TypeMapper<>() {
|
||||
@Override
|
||||
public BundleFilter map(CommandSender commandSender, String[] previousArguments, String s) {
|
||||
for (BundleFilter filter : BundleFilter.values()) {
|
||||
if (s.equals(filter.toString())) {
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments, String s) {
|
||||
if (s.length() == 0) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
return Arrays.stream(BundleFilter.values())
|
||||
.map(Enum::toString)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ClassMapper(value = ViewFlag.class, local = true)
|
||||
public TypeMapper<ViewFlag> viewFlagClassMapper() {
|
||||
return new TypeMapper<ViewFlag>() {
|
||||
@Override
|
||||
public ViewFlag map(CommandSender commandSender, String[] previousArguments, String s) {
|
||||
for (ViewFlag flag : ViewFlag.flags) {
|
||||
if (s.equals("-" + flag.name)) {
|
||||
return flag;
|
||||
}
|
||||
|
||||
for (String alias : flag.aliases) {
|
||||
if (s.equals("-" + alias)) {
|
||||
return flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments, String s) {
|
||||
return ViewFlag.flags.stream()
|
||||
.flatMap(viewFlag -> Stream.concat(Stream.of("-" + viewFlag.name),
|
||||
Arrays.stream(viewFlag.aliases).map(name -> "-" + name)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2023 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.features.tracer;
|
||||
|
||||
import de.steamwar.bausystem.features.tracer.rendering.BundleFilter;
|
||||
import de.steamwar.bausystem.features.tracer.rendering.PlayerTraceShowData;
|
||||
import de.steamwar.bausystem.features.tracer.rendering.dynamicflags.AtFlag;
|
||||
import de.steamwar.bausystem.features.tracer.rendering.dynamicflags.IsolateFlag;
|
||||
import de.steamwar.bausystem.region.Region;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Linked
|
||||
public class TraceManager implements Listener {
|
||||
|
||||
public static TraceManager instance;
|
||||
|
||||
{
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public static File tracesFolder = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "traces");
|
||||
|
||||
public TraceManager() {
|
||||
if (!tracesFolder.exists())
|
||||
tracesFolder.mkdir();
|
||||
|
||||
File[] traceFiles = tracesFolder.listFiles();
|
||||
if (traceFiles == null)
|
||||
return;
|
||||
|
||||
for (File traceFile : traceFiles) {
|
||||
if (traceFile.getName().contains(".records"))
|
||||
continue;
|
||||
|
||||
add(new Trace(traceFile));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List of all current traces
|
||||
*/
|
||||
private final Map<Region, Map<Integer, Trace>> tracesByRegion = new HashMap<>();
|
||||
|
||||
private final Map<Region, Map<Player, PlayerTraceShowData>> showDataPerRegionPerPlayer = new HashMap<>();
|
||||
|
||||
private final Map<Player, Set<Player>> followerMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Utility variable to keep track of the next open trace id;
|
||||
*/
|
||||
private int nextOpenId = 0;
|
||||
|
||||
/**
|
||||
* Adds a new trace to the global record
|
||||
*
|
||||
* @param trace Trace to be added
|
||||
* @return id of the created trace
|
||||
*/
|
||||
protected int add(Trace trace) {
|
||||
showDataPerRegionPerPlayer.getOrDefault(trace.getRegion(), Collections.emptyMap()).forEach((player, playerTraceShowData) -> {
|
||||
trace.render(player, playerTraceShowData);
|
||||
followerMap.getOrDefault(player, Collections.emptySet()).forEach(follower -> {
|
||||
trace.render(follower, playerTraceShowData);
|
||||
});
|
||||
});
|
||||
|
||||
tracesByRegion.computeIfAbsent(trace.getRegion(), region -> new HashMap<>()).put(nextOpenId, trace);
|
||||
nextOpenId++;
|
||||
return nextOpenId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the given trace
|
||||
*
|
||||
* @param trace
|
||||
*/
|
||||
public int getId(Trace trace) {
|
||||
for (Map.Entry<Integer, Trace> entry : tracesByRegion.getOrDefault(trace.getRegion(), Collections.emptyMap()).entrySet()) {
|
||||
if (entry.getValue() == trace) return entry.getKey();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders only the given records to the specified trace
|
||||
*
|
||||
* @param trace
|
||||
* @param recordsToAdd
|
||||
*/
|
||||
protected void showPartial(Trace trace, List<TNTPoint> recordsToAdd) {
|
||||
showDataPerRegionPerPlayer.getOrDefault(trace.getRegion(), Collections.emptyMap()).forEach((player, playerTraceShowData) -> {
|
||||
trace.render(recordsToAdd, player, playerTraceShowData);
|
||||
followerMap.getOrDefault(player, Collections.emptySet()).forEach(follower -> {
|
||||
trace.render(recordsToAdd, follower, playerTraceShowData);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected Map<Player, PlayerTraceShowData> getTraceShowDataPlayerMapping(Region region) {
|
||||
return showDataPerRegionPerPlayer.getOrDefault(region, new HashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the trace with the given id
|
||||
*
|
||||
* @param trace the trace to be removed
|
||||
*/
|
||||
public boolean remove(Trace trace) {
|
||||
Map<Integer, Trace> traces = tracesByRegion.getOrDefault(trace.getRegion(), Collections.emptyMap());
|
||||
Integer traceId = traces.entrySet().stream()
|
||||
.filter(entry -> entry.getValue() == trace)
|
||||
.map(Map.Entry::getKey)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (traceId == null) return false;
|
||||
traces.remove(traceId);
|
||||
trace.hide();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all traces
|
||||
*/
|
||||
public void clear(Region region) {
|
||||
showDataPerRegionPerPlayer.getOrDefault(region, new HashMap<>())
|
||||
.keySet()
|
||||
.forEach(player -> {
|
||||
Set<Player> players = followerMap.getOrDefault(player, Collections.emptySet());
|
||||
tracesByRegion.getOrDefault(region, new HashMap<>()).values().forEach(trace -> {
|
||||
trace.hide(player);
|
||||
players.forEach(trace::hide);
|
||||
});
|
||||
});
|
||||
tracesByRegion.getOrDefault(region, new HashMap<>())
|
||||
.forEach((i, trace) -> {
|
||||
if (trace.getRegion() != region) return;
|
||||
trace.getMetadataSaveFile().delete();
|
||||
trace.getRecordsSaveFile().delete();
|
||||
});
|
||||
tracesByRegion.getOrDefault(region, new HashMap<>()).clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Methode to get all traces in a certain region
|
||||
*
|
||||
* @param region Region to look for traces in
|
||||
* @return All traces recorded in the given Region
|
||||
*/
|
||||
public Collection<Trace> get(Region region) {
|
||||
return tracesByRegion.getOrDefault(region, Collections.emptyMap()).values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Methode to get the trace with specific id
|
||||
*
|
||||
* @param index index of the trace
|
||||
* @return the trace with given id
|
||||
*/
|
||||
public Optional<Trace> get(int index) {
|
||||
for (Map.Entry<Region, Map<Integer, Trace>> intermediate : tracesByRegion.entrySet()) {
|
||||
if (intermediate.getValue().containsKey(index)) {
|
||||
return Optional.ofNullable(intermediate.getValue().get(index));
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Methode to get all traces
|
||||
*
|
||||
* @return internal list of all current traces
|
||||
*/
|
||||
public Collection<Trace> getAll() {
|
||||
return tracesByRegion.values().stream().map(Map::values).flatMap(Collection::stream).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all ids of active traces
|
||||
*/
|
||||
public Set<Integer> getAllIds() {
|
||||
return tracesByRegion.values().stream().map(Map::keySet).flatMap(Collection::stream).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows traces for the player of the current Region
|
||||
*
|
||||
* @param player
|
||||
* @param playerTraceShowData
|
||||
*/
|
||||
public void show(Player player, PlayerTraceShowData playerTraceShowData) {
|
||||
unfollow(player);
|
||||
|
||||
Region region = Region.getRegion(player.getLocation());
|
||||
showDataPerRegionPerPlayer.computeIfAbsent(region, ignored -> new HashMap<>()).put(player, playerTraceShowData);
|
||||
tracesByRegion.getOrDefault(region, Collections.emptyMap()).forEach((integer, trace) -> {
|
||||
trace.render(player, playerTraceShowData);
|
||||
followerMap.getOrDefault(player, Collections.emptySet()).forEach(follower -> {
|
||||
trace.render(follower, playerTraceShowData);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides traces for the player of the current Region
|
||||
*
|
||||
* @param player
|
||||
*/
|
||||
public void hide(Player player) {
|
||||
unfollow(player);
|
||||
|
||||
Region region = Region.getRegion(player.getLocation());
|
||||
PlayerTraceShowData previous = showDataPerRegionPerPlayer.getOrDefault(region, Collections.emptyMap()).remove(player);
|
||||
if (previous == null) return;
|
||||
tracesByRegion.getOrDefault(region, Collections.emptyMap()).forEach((integer, trace) -> {
|
||||
trace.hide(player);
|
||||
followerMap.getOrDefault(player, Collections.emptySet()).forEach(trace::hide);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the given player
|
||||
*
|
||||
* @param follower
|
||||
* @param following
|
||||
* @return
|
||||
*/
|
||||
public boolean follow(Player follower, Player following) {
|
||||
if (followerMap.containsKey(follower)) return false;
|
||||
if (followerMap.entrySet().stream().anyMatch(playerSetEntry -> playerSetEntry.getValue().contains(follower))) {
|
||||
unfollow(follower);
|
||||
}
|
||||
|
||||
followerMap.computeIfAbsent(following, ignored -> new HashSet<>()).add(follower);
|
||||
|
||||
showDataPerRegionPerPlayer.forEach((region, playerPlayerTraceShowDataMap) -> {
|
||||
if (playerPlayerTraceShowDataMap.containsKey(follower)) {
|
||||
tracesByRegion.getOrDefault(region, Collections.emptyMap()).forEach((integer, trace) -> trace.hide(follower));
|
||||
}
|
||||
|
||||
PlayerTraceShowData playerTraceShowData = playerPlayerTraceShowDataMap.get(following);
|
||||
if (playerTraceShowData == null) return;
|
||||
tracesByRegion.getOrDefault(region, Collections.emptyMap()).forEach((integer, trace) -> {
|
||||
trace.render(follower, playerTraceShowData);
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public void unfollow(Player follower) {
|
||||
if (followerMap.containsKey(follower)) return;
|
||||
List<Player> toRemove = new ArrayList<>();
|
||||
Set<Player> toHide = new HashSet<>();
|
||||
followerMap.forEach((player, players) -> {
|
||||
if (players.remove(follower)) toHide.add(follower);
|
||||
if (players.isEmpty()) toRemove.add(player);
|
||||
});
|
||||
toRemove.forEach(followerMap::remove);
|
||||
|
||||
showDataPerRegionPerPlayer.forEach((region, playerPlayerTraceShowDataMap) -> {
|
||||
toHide.forEach(player -> {
|
||||
if (!playerPlayerTraceShowDataMap.containsKey(player)) return;
|
||||
tracesByRegion.getOrDefault(region, Collections.emptyMap()).forEach((integer, trace) -> {
|
||||
trace.hide(follower);
|
||||
});
|
||||
});
|
||||
|
||||
PlayerTraceShowData playerTraceShowData = playerPlayerTraceShowDataMap.get(follower);
|
||||
if (playerTraceShowData == null) return;
|
||||
tracesByRegion.getOrDefault(region, Collections.emptyMap()).forEach((integer, trace) -> {
|
||||
trace.render(follower, playerTraceShowData);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the render for the given player, to only show tnts at the given time
|
||||
* interval
|
||||
*
|
||||
* @param player
|
||||
* @param from start of time interval
|
||||
* @param to end of time interval
|
||||
*/
|
||||
public void renderAt(Player player, int from, int to) {
|
||||
unfollow(player);
|
||||
|
||||
Region region = Region.getRegion(player.getLocation());
|
||||
PlayerTraceShowData playerTraceShowData = showDataPerRegionPerPlayer
|
||||
.computeIfAbsent(region, ignored -> new HashMap<>())
|
||||
.computeIfAbsent(player, ignored -> new PlayerTraceShowData(BundleFilter.DEFAULT));
|
||||
|
||||
AtFlag atFlag = playerTraceShowData.getViewFlag(AtFlag.class);
|
||||
if (atFlag == null) {
|
||||
atFlag = new AtFlag(from, to);
|
||||
playerTraceShowData.addViewFlag(atFlag);
|
||||
} else {
|
||||
atFlag.update(from, to);
|
||||
}
|
||||
|
||||
tracesByRegion.getOrDefault(region, Collections.emptyMap()).forEach((integer, trace) -> {
|
||||
trace.render(player, playerTraceShowData);
|
||||
followerMap.getOrDefault(player, Collections.emptySet()).forEach(follower -> {
|
||||
trace.render(follower, playerTraceShowData);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the isolated render for the given records and player
|
||||
*
|
||||
* @param player the player the trace is shown to
|
||||
* @param records the record for which isolation is toggled
|
||||
*/
|
||||
public void isolate(Player player, TNTPoint... records) {
|
||||
unfollow(player);
|
||||
|
||||
Region region = Region.getRegion(player.getLocation());
|
||||
PlayerTraceShowData playerTraceShowData = showDataPerRegionPerPlayer
|
||||
.computeIfAbsent(region, ignored -> new HashMap<>())
|
||||
.computeIfAbsent(player, ignored -> new PlayerTraceShowData(BundleFilter.DEFAULT));
|
||||
|
||||
IsolateFlag isolateFlag;
|
||||
if (playerTraceShowData.hasViewFlagOnly(IsolateFlag.class)) {
|
||||
isolateFlag = playerTraceShowData.getViewFlag(IsolateFlag.class);
|
||||
} else if (playerTraceShowData.hasNoViewFlags()) {
|
||||
isolateFlag = new IsolateFlag();
|
||||
playerTraceShowData.addViewFlag(isolateFlag);
|
||||
} else {
|
||||
playerTraceShowData = new PlayerTraceShowData(BundleFilter.DEFAULT);
|
||||
isolateFlag = new IsolateFlag();
|
||||
playerTraceShowData.addViewFlag(isolateFlag);
|
||||
showDataPerRegionPerPlayer.get(region).put(player, playerTraceShowData);
|
||||
}
|
||||
|
||||
for (TNTPoint record : records) {
|
||||
isolateFlag.toggleId(record.getTntId());
|
||||
}
|
||||
|
||||
PlayerTraceShowData finalPlayerTraceShowData = playerTraceShowData;
|
||||
tracesByRegion.getOrDefault(region, Collections.emptyMap()).forEach((integer, trace) -> {
|
||||
trace.render(player, finalPlayerTraceShowData);
|
||||
followerMap.getOrDefault(player, Collections.emptySet()).forEach(follower -> {
|
||||
trace.render(follower, finalPlayerTraceShowData);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
unfollow(event.getPlayer());
|
||||
new ArrayList<>(followerMap.getOrDefault(event.getPlayer(), Collections.emptySet())).forEach(this::unfollow);
|
||||
|
||||
showDataPerRegionPerPlayer.forEach((region, playerPlayerTraceShowDataMap) -> {
|
||||
playerPlayerTraceShowDataMap.remove(event.getPlayer());
|
||||
});
|
||||
|
||||
tracesByRegion.forEach((region, integerTraceMap) -> {
|
||||
integerTraceMap.forEach((integer, trace) -> {
|
||||
trace.hide(event.getPlayer());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2023 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.features.tracer;
|
||||
|
||||
import de.steamwar.bausystem.BauSystem;
|
||||
import de.steamwar.bausystem.features.tpslimit.TPSUtils;
|
||||
import de.steamwar.bausystem.region.Region;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.TNTPrimed;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityExplodeEvent;
|
||||
import org.bukkit.event.entity.EntitySpawnEvent;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@Linked
|
||||
public class TraceRecorder implements Listener {
|
||||
|
||||
public static TraceRecorder instance;
|
||||
|
||||
{
|
||||
instance = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map for all traces being actively recorded
|
||||
*/
|
||||
private final Map<Region, TraceRecordingWrapper> activeTraces = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Map for all TNTs being traced, by region
|
||||
*/
|
||||
private final Map<Region, List<TNTPrimed>> trackedTNT = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Map from TNT to Region the TNT spawned in
|
||||
*/
|
||||
private final Map<TNTPrimed, Region> tntSpawnRegion = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Maps a tracked tnt entity to its entire recording history
|
||||
*/
|
||||
private final Map<TNTPrimed, List<TNTPoint>> historyMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Regions where auto-trace is enabled
|
||||
*/
|
||||
private final Set<Region> autoTraceRegions = new HashSet<>();
|
||||
|
||||
public TraceRecorder() {
|
||||
BauSystem.runTaskTimer(BauSystem.getInstance(), () -> {
|
||||
record();
|
||||
checkForAutoTraceFinish();
|
||||
}, 0, 1);
|
||||
}
|
||||
|
||||
public void addAutoTraceRegion(Region region) {
|
||||
autoTraceRegions.add(region);
|
||||
}
|
||||
|
||||
public void removeAutoTraceRegion(Region region) {
|
||||
autoTraceRegions.remove(region);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes checks for whether auto traces finished
|
||||
*/
|
||||
public void checkForAutoTraceFinish() {
|
||||
for (Region region : autoTraceRegions) {
|
||||
if (autoTraceRegions.contains(region) && trackedTNT.getOrDefault(region, Collections.emptyList()).size() == 0) {
|
||||
stopRecording(region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a recording at the given region
|
||||
*
|
||||
* @param region region to be recorded
|
||||
*/
|
||||
public void startRecording(Region region) {
|
||||
if (activeTraces.containsKey(region)) return;
|
||||
|
||||
TraceRecordingWrapper wrappedTrace = new TraceRecordingWrapper(region);
|
||||
activeTraces.put(region, wrappedTrace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the recording at the given region
|
||||
*
|
||||
* @param region region to stop recording
|
||||
*/
|
||||
public void stopRecording(Region region) {
|
||||
TraceRecordingWrapper wrappedTrace = activeTraces.getOrDefault(region, null);
|
||||
if (wrappedTrace == null) return;
|
||||
|
||||
wrappedTrace.finalizeRecording();
|
||||
activeTraces.remove(region);
|
||||
for (TNTPrimed tnt : trackedTNT.getOrDefault(region, Collections.emptyList())) {
|
||||
historyMap.remove(tnt);
|
||||
}
|
||||
trackedTNT.put(region, new ArrayList<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal methode to record all tracked TNT Entities
|
||||
*/
|
||||
private void record() {
|
||||
for (Region region : activeTraces.keySet()) {
|
||||
TraceRecordingWrapper wrappedTrace = activeTraces.get(region);
|
||||
Iterator<TNTPrimed> iter = trackedTNT.getOrDefault(region, Collections.emptyList()).iterator();
|
||||
while (iter.hasNext()) {
|
||||
TNTPrimed tnt = iter.next();
|
||||
if (tnt.getFuseTicks() == 80) continue;
|
||||
TNTPoint record = record(tnt, wrappedTrace, Collections.emptyList());
|
||||
if (record == null) {
|
||||
iter.remove();
|
||||
tntSpawnRegion.remove(tnt);
|
||||
historyMap.remove(tnt);
|
||||
tntSpawnRegion.remove(tnt);
|
||||
} else {
|
||||
wrappedTrace.addRecord(record);
|
||||
}
|
||||
}
|
||||
wrappedTrace.commitRecorded();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal methode to record exploded tnt
|
||||
*
|
||||
* @param tntPrimed tnt exploding
|
||||
* @param wrappedTrace the trace to record the tnt for wrapped with metadata
|
||||
* @param destroyedBlocks the blocks destoryed by the passed tnt
|
||||
* @return the record creaded of the passed tnt
|
||||
*/
|
||||
private TNTPoint record(TNTPrimed tntPrimed, TraceRecordingWrapper wrappedTrace, List<Block> destroyedBlocks) {
|
||||
List<TNTPoint> history = historyMap.getOrDefault(tntPrimed, new ArrayList<>());
|
||||
|
||||
// Failsave for tnt entering unloaded chunks
|
||||
if (tntPrimed == null || tntPrimed.isDead() || history.size() > 0 && history.get(history.size() - 1).getFuse() == tntPrimed.getFuseTicks()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int tntID;
|
||||
|
||||
if (history.size() == 0) {
|
||||
try {
|
||||
historyMap.put(tntPrimed, history);
|
||||
}
|
||||
catch (NullPointerException e) {
|
||||
Logger logger = Bukkit.getLogger();
|
||||
//TODO remove when no longer neccecary
|
||||
logger.log(Level.WARNING, "Nullpointer thrown by historyMap");
|
||||
logger.log(Level.WARNING, "TNT History: " + history);
|
||||
logger.log(Level.WARNING, "History Map: " + historyMap);
|
||||
throw e;
|
||||
}
|
||||
tntID = wrappedTrace.getNextOpenRecordIdAndIncrement();
|
||||
} else {
|
||||
tntID = history.get(0).getTntId();
|
||||
}
|
||||
|
||||
boolean isExplosion = tntPrimed.getFuseTicks() == 0;
|
||||
if (isExplosion) {
|
||||
wrappedTrace.activateExplosionRecorded();
|
||||
}
|
||||
boolean afterFirstExplosion = wrappedTrace.isExplosionRecorded();
|
||||
|
||||
TNTPoint record = new TNTPoint(tntID, tntPrimed, isExplosion, afterFirstExplosion, TPSUtils.currentRealTick.get() - wrappedTrace.getStartTick(), history, destroyedBlocks);
|
||||
history.add(record);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
public boolean isAutoTraceEnabledInRegion(Region region) {
|
||||
return autoTraceRegions.contains(region);
|
||||
}
|
||||
|
||||
public boolean isTraceActiveInRegion(Region region) {
|
||||
return activeTraces.containsKey(region);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the trace that is currently recorded in the given region
|
||||
*
|
||||
* @param region the region to get the trace for
|
||||
* @return the trace recorded in region or empty if no trace is recorded in region
|
||||
*/
|
||||
public Optional<Trace> getActiveTraceForRegion(Region region) {
|
||||
TraceRecordingWrapper traceWrapper = activeTraces.get(region);
|
||||
|
||||
if (traceWrapper == null) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(traceWrapper.getTrace());
|
||||
}
|
||||
}
|
||||
|
||||
public long getStartTimeOfTraceInRegion(Region region) {
|
||||
TraceRecordingWrapper wrapper = activeTraces.get(region);
|
||||
if (wrapper == null) return 0;
|
||||
return wrapper.getStartTick();
|
||||
}
|
||||
|
||||
/**
|
||||
* Event for TNTs beeing spawn.
|
||||
* Registers newly spawned TNT to be traced if reqired
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onTNTSpawn(EntitySpawnEvent event) {
|
||||
if (!(event.getEntity() instanceof TNTPrimed)) return;
|
||||
|
||||
Region region = Region.getRegion(event.getLocation());
|
||||
|
||||
if (autoTraceRegions.contains(region) && !activeTraces.containsKey(region)) {
|
||||
startRecording(region);
|
||||
}
|
||||
|
||||
if (activeTraces.containsKey(region)) {
|
||||
// Check whether set for tracking already exists. Creating it if necessary
|
||||
if (!trackedTNT.containsKey(region)) {
|
||||
trackedTNT.put(region, new ArrayList<>());
|
||||
}
|
||||
|
||||
trackedTNT.get(region).add((TNTPrimed) event.getEntity());
|
||||
tntSpawnRegion.put((TNTPrimed) event.getEntity(), region);
|
||||
activeTraces.get(region).addRecord(record((TNTPrimed) event.getEntity(), activeTraces.get(region), Collections.emptyList()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event for TNTs exploding
|
||||
* Unregisters TNTs from beeing traced on explode
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onTNTExplode(EntityExplodeEvent event) {
|
||||
if (!(event.getEntity() instanceof TNTPrimed)) return;
|
||||
Region region = tntSpawnRegion.getOrDefault((TNTPrimed) event.getEntity(), null);
|
||||
if (region == null) return;
|
||||
trackedTNT.get(region).remove((TNTPrimed) event.getEntity());
|
||||
tntSpawnRegion.remove((TNTPrimed) event.getEntity());
|
||||
|
||||
activeTraces.get(region).addRecord(record((TNTPrimed) event.getEntity(), activeTraces.get(region), event.blockList()));
|
||||
tntSpawnRegion.remove((TNTPrimed) event.getEntity());
|
||||
historyMap.remove((TNTPrimed) event.getEntity());
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2024 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.features.tracer;
|
||||
|
||||
import de.steamwar.bausystem.features.tpslimit.TPSUtils;
|
||||
import de.steamwar.bausystem.region.Region;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
public class TraceRecordingWrapper {
|
||||
|
||||
@Getter
|
||||
private final long startTick;
|
||||
private final List<TNTPoint> recordsToAdd;
|
||||
private final List<TNTPoint> recordList;
|
||||
private final ObjectOutputStream recordsOutputStream;
|
||||
private int nextOpenRecordId = 0;
|
||||
@Getter
|
||||
private boolean explosionRecorded = false;
|
||||
|
||||
@Getter
|
||||
private final Trace trace;
|
||||
|
||||
@SneakyThrows
|
||||
public TraceRecordingWrapper(Region region) {
|
||||
startTick = TPSUtils.currentRealTick.get();
|
||||
recordsToAdd = new ArrayList<>();
|
||||
recordList = new ArrayList<>();
|
||||
|
||||
trace = new Trace(region, recordList);
|
||||
File recordsSaveFile = new File(TraceManager.tracesFolder, trace.getUuid() + ".records");
|
||||
recordsOutputStream = new ObjectOutputStream(new GZIPOutputStream(new FileOutputStream(recordsSaveFile)));
|
||||
}
|
||||
|
||||
public int getNextOpenRecordIdAndIncrement() {
|
||||
return nextOpenRecordId++;
|
||||
}
|
||||
|
||||
public void activateExplosionRecorded() {
|
||||
explosionRecorded = true;
|
||||
}
|
||||
|
||||
public void addRecord(TNTPoint record) {
|
||||
recordsToAdd.add(record);
|
||||
}
|
||||
|
||||
public void commitRecorded() {
|
||||
TraceManager.instance.showPartial(trace, recordsToAdd);
|
||||
|
||||
recordsToAdd.forEach(record -> {
|
||||
try {
|
||||
recordsOutputStream.writeObject(record);
|
||||
recordsOutputStream.flush();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
recordList.addAll(recordsToAdd);
|
||||
recordsToAdd.clear();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
protected void finalizeRecording() {
|
||||
recordsOutputStream.flush();
|
||||
recordsOutputStream.close();
|
||||
TraceManager.instance.add(trace);
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2024 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.features.tracer;
|
||||
|
||||
import de.steamwar.bausystem.BauSystem;
|
||||
import de.steamwar.bausystem.Permission;
|
||||
import de.steamwar.bausystem.features.tpslimit.TPSUtils;
|
||||
import de.steamwar.bausystem.region.Region;
|
||||
import de.steamwar.bausystem.utils.ScoreboardElement;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@Linked
|
||||
public class TraceScoreboardElement implements ScoreboardElement {
|
||||
|
||||
@Override
|
||||
public ScoreboardGroup getGroup() {
|
||||
return ScoreboardGroup.OTHER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(Region region, Player p) {
|
||||
if (!Permission.BUILD.hasPermission(p)) return null;
|
||||
if (TraceRecorder.instance.isTraceActiveInRegion(region)) {
|
||||
return "§e" + BauSystem.MESSAGE.parse("SCOREBOARD_TRACE", p) + "§8: " + BauSystem.MESSAGE.parse("TRACE_RECORD", p) + " §8| §e" + (TPSUtils.currentRealTick.get() - TraceRecorder.instance.getStartTimeOfTraceInRegion(region)) + " §7" + BauSystem.MESSAGE.parse("SCOREBOARD_TRACE_TICKS", p);
|
||||
} else if (TraceRecorder.instance.isAutoTraceEnabledInRegion(region)) {
|
||||
return "§e" + BauSystem.MESSAGE.parse("SCOREBOARD_TRACE", p) + "§8: " + BauSystem.MESSAGE.parse("TRACE_IDLE_AUTO", p);
|
||||
}
|
||||
|
||||
Collection<Trace> traces = TraceManager.instance.get(region);
|
||||
if (traces.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return "§e" + BauSystem.MESSAGE.parse("SCOREBOARD_TRACE", p) + "§8: " + BauSystem.MESSAGE.parse("TRACE_HAS_TRACES", p);
|
||||
}
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2024 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.features.tracer.rendering;
|
||||
|
||||
import de.steamwar.bausystem.features.tracer.TNTPoint;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* A Comparator for determining whether two records should be bundled
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public enum BundleFilter {
|
||||
|
||||
LOOSE((TNTPoint a, TNTPoint b) -> {
|
||||
if (a.isExplosion() != b.isExplosion()) return false;
|
||||
if (a.getLocation().distanceSquared(b.getLocation()) > BundleFilter.pixelSizeSquared * 8) return false;
|
||||
if (a.getVelocity().distanceSquared(b.getVelocity()) > BundleFilter.pixelSizeSquared * 8) return false;
|
||||
return true;
|
||||
}),
|
||||
|
||||
DEFAULT((TNTPoint a, TNTPoint b) -> {
|
||||
if (a.isExplosion() != b.isExplosion()) return false;
|
||||
if (a.getTicksSinceStart() != b.getTicksSinceStart()) return null;
|
||||
if (a.getLocation().distanceSquared(b.getLocation()) > BundleFilter.pixelSizeSquared) return false;
|
||||
if (a.getVelocity().distanceSquared(b.getVelocity()) > BundleFilter.pixelSizeSquared) return false;
|
||||
return true;
|
||||
}),
|
||||
|
||||
RAW((TNTPoint a, TNTPoint b) -> {
|
||||
if (a.isExplosion() != b.isExplosion()) return false;
|
||||
if (!a.getLocation().equals(b.getLocation())) return false;
|
||||
if (!a.getVelocity().equals(b.getVelocity())) return false;
|
||||
if (a.getTicksSinceStart() != b.getTicksSinceStart()) return null;
|
||||
return true;
|
||||
}),
|
||||
|
||||
NONE((TNTPoint a, TNTPoint b) -> {
|
||||
return null;
|
||||
});
|
||||
|
||||
/**
|
||||
* {@code null}: Bundling can be stopped from this point forward
|
||||
* {@code false}: No bundling allowed
|
||||
* {@code true}: Bundling should be applied
|
||||
*/
|
||||
public final BiFunction<TNTPoint, TNTPoint, Boolean> function;
|
||||
|
||||
private static final double pixelSize = 0.0625;
|
||||
private static final double pixelSizeSquared = pixelSize * pixelSize;
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2024 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.features.tracer.rendering;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A holder for the view data of a player trace render
|
||||
*/
|
||||
public class PlayerTraceShowData {
|
||||
|
||||
/**
|
||||
* The bundle filter applied by this class
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
private BundleFilter bundleFilter;
|
||||
|
||||
/**
|
||||
* A map for stating whether a flag is contained in this holder or not
|
||||
*/
|
||||
private final Map<Class<? extends ViewFlag>, ViewFlag> viewFlags = new HashMap<>();
|
||||
|
||||
public PlayerTraceShowData(BundleFilter bundleFilter, ViewFlag... viewFlags) {
|
||||
this.bundleFilter = bundleFilter;
|
||||
for (ViewFlag viewFlag : viewFlags) {
|
||||
this.viewFlags.put(viewFlag.getClass(), viewFlag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A methode that returns the flags that should be used by renders according
|
||||
* to this holder. Especially handles inverse and required flags
|
||||
*
|
||||
* @return the flags that should be used in a render according to this holder
|
||||
*/
|
||||
public Set<ViewFlag> getEffectiveViewFlags() {
|
||||
// Manage flags and required flags
|
||||
Set<ViewFlag> flagList = new HashSet<>();
|
||||
for (ViewFlag flag : viewFlags.values()) {
|
||||
flagList.add(flag);
|
||||
if (flag.required != null) {
|
||||
flagList.addAll(Arrays.asList(flag.required));
|
||||
}
|
||||
}
|
||||
|
||||
// Manage inverse flags
|
||||
ViewFlag.inverseFlags.forEach(viewFlag -> {
|
||||
if (!flagList.remove(viewFlag)) {
|
||||
flagList.add(viewFlag);
|
||||
}
|
||||
});
|
||||
|
||||
return flagList;
|
||||
}
|
||||
|
||||
public boolean hasNoViewFlags() {
|
||||
return viewFlags.isEmpty();
|
||||
}
|
||||
|
||||
public boolean hasViewFlag(Class<? extends ViewFlag> clazz) {
|
||||
return viewFlags.containsKey(clazz);
|
||||
}
|
||||
|
||||
public boolean hasViewFlagOnly(Class<? extends ViewFlag> clazz) {
|
||||
return viewFlags.containsKey(clazz) && viewFlags.size() == 1;
|
||||
}
|
||||
|
||||
// TODO ?
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends ViewFlag> T getViewFlag(Class<T> clazz) {
|
||||
return (T) viewFlags.get(clazz);
|
||||
}
|
||||
|
||||
public void addViewFlag(ViewFlag viewFlag) {
|
||||
viewFlags.put(viewFlag.getClass(), viewFlag);
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2024 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.features.tracer.rendering;
|
||||
|
||||
import de.steamwar.bausystem.BauSystem;
|
||||
import de.steamwar.bausystem.features.tracer.TNTPoint;
|
||||
import de.steamwar.bausystem.features.tracer.Trace;
|
||||
import de.steamwar.bausystem.features.tracer.TraceManager;
|
||||
import de.steamwar.entity.REntityServer;
|
||||
import de.steamwar.entity.RFallingBlockEntity;
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Wrapper for the rendering of a record bundle
|
||||
*/
|
||||
public class TraceEntity extends RFallingBlockEntity {
|
||||
|
||||
/**
|
||||
* The records represented by this REntity
|
||||
*/
|
||||
@Getter
|
||||
private final List<TNTPoint> records;
|
||||
|
||||
/**
|
||||
* A string of all unique tnt records
|
||||
*/
|
||||
private final String uniqueTntIdsString;
|
||||
|
||||
private final Trace trace;
|
||||
|
||||
public TraceEntity(REntityServer server, Location location, boolean isExplosion, List<TNTPoint> records, Trace trace) {
|
||||
super(server, location, isExplosion ? Material.RED_STAINED_GLASS : Material.TNT);
|
||||
this.records = records;
|
||||
this.trace = trace;
|
||||
uniqueTntIdsString = records.stream().map(TNTPoint::getTntId).distinct().map(Object::toString).collect(Collectors.joining(" "));
|
||||
setNoGravity(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Message for printing the data contained in this wrapper into player chat
|
||||
*
|
||||
* @param player the player the message should be printed for
|
||||
*/
|
||||
public void printIntoChat(Player player) {
|
||||
TNTPoint representative = records.get(0);
|
||||
|
||||
BauSystem.MESSAGE.sendPrefixless("TNT_CLICK_HEADER", player);
|
||||
BauSystem.MESSAGE.sendPrefixless("TNT_CLICK_FUSE_TIME", player, representative.getFuse());
|
||||
BauSystem.MESSAGE.sendPrefixless("TNT_CLICK_POSITION_X", player, representative.getLocation().getX() + "");
|
||||
BauSystem.MESSAGE.sendPrefixless("TNT_CLICK_POSITION_Y", player, representative.getLocation().getY() + "");
|
||||
BauSystem.MESSAGE.sendPrefixless("TNT_CLICK_POSITION_Z", player, representative.getLocation().getZ() + "");
|
||||
BauSystem.MESSAGE.sendPrefixless("TNT_CLICK_VELOCITY_X", player, representative.getVelocity().getX() + "");
|
||||
BauSystem.MESSAGE.sendPrefixless("TNT_CLICK_VELOCITY_Y", player, representative.getVelocity().getY() + "");
|
||||
BauSystem.MESSAGE.sendPrefixless("TNT_CLICK_VELOCITY_Z", player, representative.getVelocity().getZ() + "");
|
||||
BauSystem.MESSAGE.sendPrefixless("TNT_CLICK_ISOLATE", player, BauSystem.MESSAGE.parse("TRACE_MESSAGE_CLICK_ISOLATE", player), new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/trace isolate " + TraceManager.instance.getId(trace) + " " + uniqueTntIdsString));
|
||||
}
|
||||
}
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2024 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.features.tracer.rendering;
|
||||
|
||||
import de.steamwar.bausystem.features.tracer.TNTPoint;
|
||||
import de.steamwar.entity.REntityServer;
|
||||
import de.steamwar.entity.RFallingBlockEntity;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A settable flag that changes how a trace is rendered
|
||||
*/
|
||||
public abstract class ViewFlag {
|
||||
public static final Vector GRAVATY = new Vector(0.0, -0.04, 0.0);
|
||||
public static final Vector DRAG_FACTOR = new Vector(0.98, 0.98, 0.98);
|
||||
|
||||
/**
|
||||
* Static registry of static flags
|
||||
*/
|
||||
public static final List<ViewFlag> flags = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Inverse flags are used by trace render by default, as long as they are not explicitly added as argument
|
||||
*/
|
||||
public static final List<ViewFlag> inverseFlags = new ArrayList<>();
|
||||
|
||||
public static ViewFlag EXPLOSION = new ViewFlag(true, false, "explosion", "e") {
|
||||
@Override
|
||||
public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
|
||||
return records.filter(TNTPoint::isExplosion);
|
||||
}
|
||||
};
|
||||
|
||||
public static ViewFlag IGNITE = new ViewFlag(true, true, "ignite", "i") {
|
||||
@Override
|
||||
public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
|
||||
return records.filter(record -> record.isAfterFirstExplosion());
|
||||
}
|
||||
};
|
||||
|
||||
public static ViewFlag SOURCE = new ViewFlag(true, false, IGNITE, "source", "s") {
|
||||
@Override
|
||||
public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
|
||||
return records.filter(record -> record.getFuse() == 80);
|
||||
}
|
||||
};
|
||||
|
||||
public static ViewFlag BUILD_DESTROY_ONLY = new ViewFlag(true, false, "build-destroy-only") {
|
||||
@Override
|
||||
public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
|
||||
return records.filter(record -> record.getHistory().get(record.getHistory().size() - 1).isDestroyedBuildArea());
|
||||
}
|
||||
};
|
||||
|
||||
public static ViewFlag TESTBLOCK_DESTROY_ONLY = new ViewFlag(true, false, "testblock-destroy-only") {
|
||||
@Override
|
||||
public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
|
||||
return records.filter(record -> record.getHistory().get(record.getHistory().size() - 1).isDestroyedTestBlock());
|
||||
}
|
||||
};
|
||||
|
||||
public static ViewFlag MICROMOTION = new ViewFlag(true, false, "micromotion", "m") {
|
||||
@Override
|
||||
public Stream<TNTPoint> filter(Stream<TNTPoint> stream) {
|
||||
List<TNTPoint> records = stream.collect(Collectors.toList());
|
||||
;
|
||||
Set<Integer> seen = new HashSet<>();
|
||||
Set<TNTPoint> toRemove = new HashSet<>();
|
||||
|
||||
for (TNTPoint uniqueRecord : records) {
|
||||
if (seen.contains(uniqueRecord.getTntId())) continue;
|
||||
|
||||
boolean hasMicromotion = false;
|
||||
for (TNTPoint record : uniqueRecord.getHistory()) {
|
||||
Vector velocity = record.getVelocity();
|
||||
if (velocity.getY() == 0 && (Math.abs(velocity.getX()) < 0.001 || Math.abs(velocity.getZ()) < 0.001)) {
|
||||
hasMicromotion = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasMicromotion)
|
||||
toRemove.add(uniqueRecord);
|
||||
|
||||
seen.add(uniqueRecord.getTntId());
|
||||
}
|
||||
|
||||
for (TNTPoint record : toRemove) {
|
||||
records.removeAll(record.getHistory());
|
||||
}
|
||||
|
||||
return records.stream();
|
||||
}
|
||||
};
|
||||
|
||||
public static ViewFlag ADVANCED = new ViewFlag(true, false, "advanced", "a") {
|
||||
@Override
|
||||
public void modify(REntityServer server, List<TraceEntity> entities) {
|
||||
for (TraceEntity entity : entities) {
|
||||
TNTPoint representative = entity.getRecords().get(0);
|
||||
Optional<TNTPoint> prev = representative.getPrevious();
|
||||
if (prev.isEmpty()) continue;
|
||||
|
||||
TNTPoint previous = prev.get();
|
||||
Location delta = representative.getLocation().clone().subtract(previous.getLocation());
|
||||
Vector previousVelocity = previous.isAfterFirstExplosion() ? previous.getVelocity() : delta.toVector().clone().divide(DRAG_FACTOR).subtract(GRAVATY);
|
||||
|
||||
Location yLocation = previous.getLocation().clone().add(0, delta.getY(), 0);
|
||||
if (yLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && yLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) {
|
||||
RFallingBlockEntity y = new RFallingBlockEntity(server, yLocation, Material.WHITE_STAINED_GLASS);
|
||||
y.setNoGravity(true);
|
||||
}
|
||||
|
||||
Location secoundLocation;
|
||||
if (previousVelocity.getX() >= previousVelocity.getZ()) {
|
||||
secoundLocation = previous.getLocation().clone().add(delta.getX(), delta.getY(), 0);
|
||||
} else {
|
||||
secoundLocation = previous.getLocation().clone().add(0, delta.getY(), delta.getZ());
|
||||
}
|
||||
|
||||
if (secoundLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && secoundLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) {
|
||||
RFallingBlockEntity second = new RFallingBlockEntity(server, secoundLocation, Material.WHITE_STAINED_GLASS);
|
||||
second.setNoGravity(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static ViewFlag COUNT = new ViewFlag(true, false, "count", "c") {
|
||||
@Override
|
||||
public void modify(REntityServer server, List<TraceEntity> entities) {
|
||||
for (TraceEntity entity : entities) {
|
||||
entity.setDisplayName(String.valueOf(entity.getRecords().size()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static ViewFlag FUSE = new ViewFlag(true, false, "fuse", "f") {
|
||||
@Override
|
||||
public void modify(REntityServer server, List<TraceEntity> entities) {
|
||||
for (TraceEntity entity : entities) {
|
||||
List<String> fuses = entity.getRecords()
|
||||
.stream()
|
||||
.map(TNTPoint::getFuse)
|
||||
.distinct()
|
||||
.sorted()
|
||||
.map(i -> i + "")
|
||||
.collect(Collectors.toList());
|
||||
if (fuses.size() <= 5) {
|
||||
entity.setDisplayName(String.join(",", fuses));
|
||||
} else {
|
||||
entity.setDisplayName(fuses.stream().limit(5).collect(Collectors.joining(",")) + ", +" + (fuses.size() - 5));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static ViewFlag TIME = new ViewFlag(true, false, "time", "t") {
|
||||
@Override
|
||||
public void modify(REntityServer server, List<TraceEntity> entities) {
|
||||
for (TraceEntity entity : entities) {
|
||||
List<String> time = entity.getRecords()
|
||||
.stream()
|
||||
.map(TNTPoint::getTicksSinceStart)
|
||||
.distinct()
|
||||
.sorted()
|
||||
.map(i -> i + "")
|
||||
.collect(Collectors.toList());
|
||||
if (time.size() <= 5) {
|
||||
entity.setDisplayName(String.join(",", time));
|
||||
} else {
|
||||
entity.setDisplayName(time.stream().limit(5).collect(Collectors.joining(",")) + ", +" + (time.size() - 5));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Name of the flag
|
||||
*/
|
||||
public final String name;
|
||||
|
||||
/**
|
||||
* Aliases of the flag
|
||||
*/
|
||||
public final String[] aliases;
|
||||
|
||||
/**
|
||||
* A flag that is used whenever this flag is used
|
||||
*/
|
||||
public final ViewFlag[] required;
|
||||
|
||||
protected ViewFlag(boolean isStatic, boolean isInverse, String name, String... aliases) {
|
||||
this(isStatic, isInverse, new ViewFlag[0], name, aliases);
|
||||
}
|
||||
|
||||
protected ViewFlag(boolean isStatic, boolean isInverse, ViewFlag required, String name, String... aliases) {
|
||||
this(isStatic, isInverse, new ViewFlag[]{required}, name, aliases);
|
||||
}
|
||||
|
||||
protected ViewFlag(boolean isStatic, boolean isInverse, ViewFlag[] required, String name, String... aliases) {
|
||||
this.name = name;
|
||||
this.aliases = aliases;
|
||||
if (isStatic) flags.add(this);
|
||||
if (isInverse) inverseFlags.add(this);
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the given records for a given condition
|
||||
*
|
||||
* @param records Records to be filtered
|
||||
* @return Filtered records
|
||||
*/
|
||||
public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the trace rendering
|
||||
*
|
||||
* @param server the server the trace is rendered on
|
||||
* @param entities the entities representing tnts
|
||||
*/
|
||||
public void modify(REntityServer server, List<TraceEntity> entities) {
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2024 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.features.tracer.rendering.dynamicflags;
|
||||
|
||||
import de.steamwar.bausystem.features.tracer.TNTPoint;
|
||||
import de.steamwar.bausystem.features.tracer.rendering.ViewFlag;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A view flag for rendering a trace only in a given time intervall
|
||||
*/
|
||||
public class AtFlag extends ViewFlag {
|
||||
/**
|
||||
* Start of the time interval
|
||||
*/
|
||||
private int start;
|
||||
/**
|
||||
* End of the time interval
|
||||
*/
|
||||
private int end;
|
||||
|
||||
public AtFlag(int start, int end) {
|
||||
super(false, false, ViewFlag.IGNITE, null);
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this flag to represent another time interval
|
||||
*
|
||||
* @param start new interval start
|
||||
* @param end new interval end
|
||||
*/
|
||||
public void update(int start, int end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
|
||||
return records.filter(record -> record.getTicksSinceStart() >= start && record.getTicksSinceStart() <= end);
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2024 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.features.tracer.rendering.dynamicflags;
|
||||
|
||||
import de.steamwar.bausystem.features.tracer.TNTPoint;
|
||||
import de.steamwar.bausystem.features.tracer.rendering.ViewFlag;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A flag for rendering only the records of specific tnts
|
||||
*/
|
||||
public class IsolateFlag extends ViewFlag {
|
||||
|
||||
/**
|
||||
* Tnt ids that will be isolated
|
||||
*/
|
||||
private final Set<Integer> tntToIsolate = new HashSet<>();
|
||||
|
||||
public IsolateFlag() {
|
||||
super(false, false, ViewFlag.IGNITE, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the given id to be or not to be rendered
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
public void toggleId(int id) {
|
||||
if (!tntToIsolate.remove(id)) {
|
||||
tntToIsolate.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
|
||||
if (tntToIsolate.isEmpty()) return records;
|
||||
return records.filter(record -> tntToIsolate.contains(record.getTntId()));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user