Add BauSystem module

Fix ci java version
Fix LinkageProcessor
This commit is contained in:
2024-08-05 13:28:50 +02:00
parent 41d31e6c9c
commit 3366a30b0c
526 changed files with 43550 additions and 149479 deletions
@@ -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());
});
});
}
}
@@ -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());
}
}
@@ -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);
}
}
@@ -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);
}
}
}
@@ -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;
}
@@ -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);
}
}
@@ -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));
}
}
@@ -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) {
}
}
@@ -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);
}
}
@@ -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()));
}
}