Ability to modify api

This commit is contained in:
Sotr
2019-04-10 02:22:38 +08:00
parent 17e0e3edca
commit b2b93d9f87
1055 changed files with 107079 additions and 12 deletions

View File

@@ -0,0 +1,84 @@
package co.aikar.timings;
import static co.aikar.timings.TimingsManager.*;
import org.jetbrains.annotations.NotNull;
public class FullServerTickHandler extends TimingHandler {
private static final TimingIdentifier IDENTITY = new TimingIdentifier("Minecraft", "Full Server Tick", null);
final TimingData minuteData;
double avgFreeMemory = -1D;
double avgUsedMemory = -1D;
FullServerTickHandler() {
super(IDENTITY);
minuteData = new TimingData(id);
TIMING_MAP.put(IDENTITY, this);
}
@NotNull
@Override
public Timing startTiming() {
if (TimingsManager.needsFullReset) {
TimingsManager.resetTimings();
} else if (TimingsManager.needsRecheckEnabled) {
TimingsManager.recheckEnabled();
}
return super.startTiming();
}
@Override
public void stopTiming() {
super.stopTiming();
if (!isEnabled()) {
return;
}
if (TimingHistory.timedTicks % 20 == 0) {
final Runtime runtime = Runtime.getRuntime();
double usedMemory = runtime.totalMemory() - runtime.freeMemory();
double freeMemory = runtime.maxMemory() - usedMemory;
if (this.avgFreeMemory == -1) {
this.avgFreeMemory = freeMemory;
} else {
this.avgFreeMemory = (this.avgFreeMemory * (59 / 60D)) + (freeMemory * (1 / 60D));
}
if (this.avgUsedMemory == -1) {
this.avgUsedMemory = usedMemory;
} else {
this.avgUsedMemory = (this.avgUsedMemory * (59 / 60D)) + (usedMemory * (1 / 60D));
}
}
long start = System.nanoTime();
TimingsManager.tick();
long diff = System.nanoTime() - start;
TIMINGS_TICK.addDiff(diff, null);
// addDiff for TIMINGS_TICK incremented this, bring it back down to 1 per tick.
record.setCurTickCount(record.getCurTickCount()-1);
minuteData.setCurTickTotal(record.getCurTickTotal());
minuteData.setCurTickCount(1);
boolean violated = isViolated();
minuteData.processTick(violated);
TIMINGS_TICK.processTick(violated);
processTick(violated);
if (TimingHistory.timedTicks % 1200 == 0) {
MINUTE_REPORTS.add(new TimingHistory.MinuteReport());
TimingHistory.resetTicks(false);
minuteData.reset();
}
if (TimingHistory.timedTicks % Timings.getHistoryInterval() == 0) {
TimingsManager.HISTORY.add(new TimingHistory());
TimingsManager.resetTimings();
}
TimingsExport.reportTimings();
}
boolean isViolated() {
return record.getCurTickTotal() > 50000000;
}
}

View File

@@ -0,0 +1,68 @@
/*
* This file is licensed under the MIT License (MIT).
*
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package co.aikar.timings;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class NullTimingHandler implements Timing {
public static final Timing NULL = new NullTimingHandler();
@NotNull
@Override
public Timing startTiming() {
return this;
}
@Override
public void stopTiming() {
}
@NotNull
@Override
public Timing startTimingIfSync() {
return this;
}
@Override
public void stopTimingIfSync() {
}
@Override
public void abort() {
}
@Nullable
@Override
public TimingHandler getTimingHandler() {
return null;
}
@Override
public void close() {
}
}

View File

@@ -0,0 +1,83 @@
/*
* This file is licensed under the MIT License (MIT).
*
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package co.aikar.timings;
import org.bukkit.Bukkit;
import org.bukkit.event.Event;
import org.bukkit.event.EventException;
import org.bukkit.event.Listener;
import org.bukkit.plugin.EventExecutor;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.Method;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class TimedEventExecutor implements EventExecutor {
private final EventExecutor executor;
private final Timing timings;
/**
* Wraps an event executor and associates a timing handler to it.
*
* @param executor Executor to wrap
* @param plugin Owning plugin
* @param method EventHandler method
* @param eventClass Owning class
*/
public TimedEventExecutor(@NotNull EventExecutor executor, @NotNull Plugin plugin, @Nullable Method method, @NotNull Class<? extends Event> eventClass) {
this.executor = executor;
String id;
if (method == null) {
if (executor.getClass().getEnclosingClass() != null) { // Oh Skript, how we love you
method = executor.getClass().getEnclosingMethod();
}
}
if (method != null) {
id = method.getDeclaringClass().getName();
} else {
id = executor.getClass().getName();
}
final String eventName = eventClass.getSimpleName();
boolean verbose = "BlockPhysicsEvent".equals(eventName);
this.timings = Timings.ofSafe(plugin.getName(), (verbose ? "## " : "") +
"Event: " + id + " (" + eventName + ")", null);
}
@Override
public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException {
if (event.isAsynchronous() || !Timings.timingsEnabled || !Bukkit.isPrimaryThread()) {
executor.execute(listener, event);
return;
}
try (Timing ignored = timings.startTiming()){
executor.execute(listener, event);
}
}
}

View File

@@ -0,0 +1,122 @@
/*
* This file is licensed under the MIT License (MIT).
*
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package co.aikar.timings;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import static co.aikar.util.JSONUtil.toArray;
/**
* <p>Lightweight object for tracking timing data</p>
*
* This is broken out to reduce memory usage
*/
class TimingData {
private final int id;
private int count = 0;
private int lagCount = 0;
private long totalTime = 0;
private long lagTotalTime = 0;
private int curTickCount = 0;
private long curTickTotal = 0;
TimingData(int id) {
this.id = id;
}
private TimingData(TimingData data) {
this.id = data.id;
this.totalTime = data.totalTime;
this.lagTotalTime = data.lagTotalTime;
this.count = data.count;
this.lagCount = data.lagCount;
}
void add(long diff) {
++curTickCount;
curTickTotal += diff;
}
void processTick(boolean violated) {
totalTime += curTickTotal;
count += curTickCount;
if (violated) {
lagTotalTime += curTickTotal;
lagCount += curTickCount;
}
curTickTotal = 0;
curTickCount = 0;
}
void reset() {
count = 0;
lagCount = 0;
curTickTotal = 0;
curTickCount = 0;
totalTime = 0;
lagTotalTime = 0;
}
protected TimingData clone() {
return new TimingData(this);
}
@NotNull
List<Object> export() {
List<Object> list = toArray(
id,
count,
totalTime);
if (lagCount > 0) {
list.add(lagCount);
list.add(lagTotalTime);
}
return list;
}
boolean hasData() {
return count > 0;
}
long getTotalTime() {
return totalTime;
}
int getCurTickCount() {
return curTickCount;
}
void setCurTickCount(int curTickCount) {
this.curTickCount = curTickCount;
}
long getCurTickTotal() {
return curTickTotal;
}
void setCurTickTotal(long curTickTotal) {
this.curTickTotal = curTickTotal;
}
}

View File

@@ -0,0 +1,354 @@
/*
* This file is licensed under the MIT License (MIT).
*
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package co.aikar.timings;
import co.aikar.timings.TimingHistory.RegionData.RegionId;
import com.google.common.base.Function;
import com.google.common.collect.Sets;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.BlockState;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import co.aikar.util.LoadingMap;
import co.aikar.util.MRUMapCache;
import java.lang.management.ManagementFactory;
import java.util.Collection;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static co.aikar.timings.TimingsManager.FULL_SERVER_TICK;
import static co.aikar.timings.TimingsManager.MINUTE_REPORTS;
import static co.aikar.util.JSONUtil.*;
@SuppressWarnings({"deprecation", "SuppressionAnnotation", "Convert2Lambda", "Anonymous2MethodRef"})
public class TimingHistory {
public static long lastMinuteTime;
public static long timedTicks;
public static long playerTicks;
public static long entityTicks;
public static long tileEntityTicks;
public static long activatedEntityTicks;
private static int worldIdPool = 1;
static Map<String, Integer> worldMap = LoadingMap.newHashMap(new Function<String, Integer>() {
@NotNull
@Override
public Integer apply(@Nullable String input) {
return worldIdPool++;
}
});
private final long endTime;
private final long startTime;
private final long totalTicks;
private final long totalTime; // Represents all time spent running the server this history
private final MinuteReport[] minuteReports;
private final TimingHistoryEntry[] entries;
final Set<Material> tileEntityTypeSet = Sets.newHashSet();
final Set<EntityType> entityTypeSet = Sets.newHashSet();
private final Map<Object, Object> worlds;
TimingHistory() {
this.endTime = System.currentTimeMillis() / 1000;
this.startTime = TimingsManager.historyStart / 1000;
if (timedTicks % 1200 != 0 || MINUTE_REPORTS.isEmpty()) {
this.minuteReports = MINUTE_REPORTS.toArray(new MinuteReport[MINUTE_REPORTS.size() + 1]);
this.minuteReports[this.minuteReports.length - 1] = new MinuteReport();
} else {
this.minuteReports = MINUTE_REPORTS.toArray(new MinuteReport[MINUTE_REPORTS.size()]);
}
long ticks = 0;
for (MinuteReport mp : this.minuteReports) {
ticks += mp.ticksRecord.timed;
}
this.totalTicks = ticks;
this.totalTime = FULL_SERVER_TICK.record.getTotalTime();
this.entries = new TimingHistoryEntry[TimingsManager.HANDLERS.size()];
int i = 0;
for (TimingHandler handler : TimingsManager.HANDLERS) {
entries[i++] = new TimingHistoryEntry(handler);
}
// Information about all loaded chunks/entities
//noinspection unchecked
this.worlds = toObjectMapper(Bukkit.getWorlds(), new Function<World, JSONPair>() {
@NotNull
@Override
public JSONPair apply(World world) {
Map<RegionId, RegionData> regions = LoadingMap.newHashMap(RegionData.LOADER);
for (Chunk chunk : world.getLoadedChunks()) {
RegionData data = regions.get(new RegionId(chunk.getX(), chunk.getZ()));
for (Entity entity : chunk.getEntities()) {
if (entity == null) {
Bukkit.getLogger().warning("Null entity detected in chunk at position x: " + chunk.getX() + ", z: " + chunk.getZ());
continue;
}
data.entityCounts.get(entity.getType()).increment();
}
for (BlockState tileEntity : chunk.getTileEntities(false)) {
if (tileEntity == null) {
Bukkit.getLogger().warning("Null tileentity detected in chunk at position x: " + chunk.getX() + ", z: " + chunk.getZ());
continue;
}
data.tileEntityCounts.get(tileEntity.getBlock().getType()).increment();
}
}
return pair(
worldMap.get(world.getName()),
toArrayMapper(regions.values(),new Function<RegionData, Object>() {
@NotNull
@Override
public Object apply(RegionData input) {
return toArray(
input.regionId.x,
input.regionId.z,
toObjectMapper(input.entityCounts.entrySet(),
new Function<Map.Entry<EntityType, Counter>, JSONPair>() {
@NotNull
@Override
public JSONPair apply(Map.Entry<EntityType, Counter> entry) {
entityTypeSet.add(entry.getKey());
return pair(
String.valueOf(entry.getKey().getTypeId()),
entry.getValue().count()
);
}
}
),
toObjectMapper(input.tileEntityCounts.entrySet(),
new Function<Map.Entry<Material, Counter>, JSONPair>() {
@NotNull
@Override
public JSONPair apply(Map.Entry<Material, Counter> entry) {
tileEntityTypeSet.add(entry.getKey());
return pair(
String.valueOf(entry.getKey().getId()),
entry.getValue().count()
);
}
}
)
);
}
})
);
}
});
}
static class RegionData {
final RegionId regionId;
@SuppressWarnings("Guava")
static Function<RegionId, RegionData> LOADER = new Function<RegionId, RegionData>() {
@NotNull
@Override
public RegionData apply(@NotNull RegionId id) {
return new RegionData(id);
}
};
RegionData(@NotNull RegionId id) {
this.regionId = id;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RegionData that = (RegionData) o;
return regionId.equals(that.regionId);
}
@Override
public int hashCode() {
return regionId.hashCode();
}
@SuppressWarnings("unchecked")
final Map<EntityType, Counter> entityCounts = MRUMapCache.of(LoadingMap.of(
new EnumMap<EntityType, Counter>(EntityType.class), k -> new Counter()
));
@SuppressWarnings("unchecked")
final Map<Material, Counter> tileEntityCounts = MRUMapCache.of(LoadingMap.of(
new EnumMap<Material, Counter>(Material.class), k -> new Counter()
));
static class RegionId {
final int x, z;
final long regionId;
RegionId(int x, int z) {
this.x = x >> 5 << 5;
this.z = z >> 5 << 5;
this.regionId = ((long) (this.x) << 32) + (this.z >> 5 << 5) - Integer.MIN_VALUE;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RegionId regionId1 = (RegionId) o;
return regionId == regionId1.regionId;
}
@Override
public int hashCode() {
return (int) (regionId ^ (regionId >>> 32));
}
}
}
static void resetTicks(boolean fullReset) {
if (fullReset) {
// Non full is simply for 1 minute reports
timedTicks = 0;
}
lastMinuteTime = System.nanoTime();
playerTicks = 0;
tileEntityTicks = 0;
entityTicks = 0;
activatedEntityTicks = 0;
}
@NotNull
Object export() {
return createObject(
pair("s", startTime),
pair("e", endTime),
pair("tk", totalTicks),
pair("tm", totalTime),
pair("w", worlds),
pair("h", toArrayMapper(entries, new Function<TimingHistoryEntry, Object>() {
@Nullable
@Override
public Object apply(TimingHistoryEntry entry) {
TimingData record = entry.data;
if (!record.hasData()) {
return null;
}
return entry.export();
}
})),
pair("mp", toArrayMapper(minuteReports, new Function<MinuteReport, Object>() {
@NotNull
@Override
public Object apply(MinuteReport input) {
return input.export();
}
}))
);
}
static class MinuteReport {
final long time = System.currentTimeMillis() / 1000;
final TicksRecord ticksRecord = new TicksRecord();
final PingRecord pingRecord = new PingRecord();
final TimingData fst = TimingsManager.FULL_SERVER_TICK.minuteData.clone();
final double tps = 1E9 / ( System.nanoTime() - lastMinuteTime ) * ticksRecord.timed;
final double usedMemory = TimingsManager.FULL_SERVER_TICK.avgUsedMemory;
final double freeMemory = TimingsManager.FULL_SERVER_TICK.avgFreeMemory;
final double loadAvg = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage();
@NotNull
List<Object> export() {
return toArray(
time,
Math.round(tps * 100D) / 100D,
Math.round(pingRecord.avg * 100D) / 100D,
fst.export(),
toArray(ticksRecord.timed,
ticksRecord.player,
ticksRecord.entity,
ticksRecord.activatedEntity,
ticksRecord.tileEntity
),
usedMemory,
freeMemory,
loadAvg
);
}
}
private static class TicksRecord {
final long timed;
final long player;
final long entity;
final long tileEntity;
final long activatedEntity;
TicksRecord() {
timed = timedTicks - (TimingsManager.MINUTE_REPORTS.size() * 1200);
player = playerTicks;
entity = entityTicks;
tileEntity = tileEntityTicks;
activatedEntity = activatedEntityTicks;
}
}
private static class PingRecord {
final double avg;
PingRecord() {
final Collection<? extends Player> onlinePlayers = Bukkit.getOnlinePlayers();
int totalPing = 0;
for (Player player : onlinePlayers) {
totalPing += player.spigot().getPing();
}
avg = onlinePlayers.isEmpty() ? 0 : totalPing / onlinePlayers.size();
}
}
private static class Counter {
private int count = 0;
public int increment() {
return ++count;
}
public int count() {
return count;
}
}
}

View File

@@ -0,0 +1,58 @@
/*
* This file is licensed under the MIT License (MIT).
*
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package co.aikar.timings;
import com.google.common.base.Function;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import static co.aikar.util.JSONUtil.toArrayMapper;
class TimingHistoryEntry {
final TimingData data;
private final TimingData[] children;
TimingHistoryEntry(@NotNull TimingHandler handler) {
this.data = handler.record.clone();
children = handler.cloneChildren();
}
@NotNull
List<Object> export() {
List<Object> result = data.export();
if (children.length > 0) {
result.add(
toArrayMapper(children, new Function<TimingData, Object>() {
@NotNull
@Override
public Object apply(TimingData child) {
return child.export();
}
})
);
}
return result;
}
}

View File

@@ -0,0 +1,116 @@
/*
* This file is licensed under the MIT License (MIT).
*
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package co.aikar.timings;
import co.aikar.util.LoadingMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* <p>Used as a basis for fast HashMap key comparisons for the Timing Map.</p>
*
* This class uses interned strings giving us the ability to do an identity check instead of equals() on the strings
*/
final class TimingIdentifier {
/**
* Holds all groups. Autoloads on request for a group by name.
*/
static final Map<String, TimingGroup> GROUP_MAP = LoadingMap.of(new ConcurrentHashMap<>(64, .5F), TimingGroup::new);
private static final TimingGroup DEFAULT_GROUP = getGroup("Minecraft");
final String group;
final String name;
final TimingHandler groupHandler;
private final int hashCode;
TimingIdentifier(@Nullable String group, @NotNull String name, @Nullable Timing groupHandler) {
this.group = group != null ? group: DEFAULT_GROUP.name;
this.name = name;
this.groupHandler = groupHandler != null ? groupHandler.getTimingHandler() : null;
this.hashCode = (31 * this.group.hashCode()) + this.name.hashCode();
}
@NotNull
static TimingGroup getGroup(@Nullable String groupName) {
if (groupName == null) {
//noinspection ConstantConditions
return DEFAULT_GROUP;
}
return GROUP_MAP.get(groupName);
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
TimingIdentifier that = (TimingIdentifier) o;
return Objects.equals(group, that.group) && Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public String toString() {
return "TimingIdentifier{id=" + group + ":" + name +'}';
}
static class TimingGroup {
private static AtomicInteger idPool = new AtomicInteger(1);
final int id = idPool.getAndIncrement();
final String name;
final List<TimingHandler> handlers = Collections.synchronizedList(new ArrayList<>(64));
private TimingGroup(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TimingGroup that = (TimingGroup) o;
return id == that.id;
}
@Override
public int hashCode() {
return id;
}
}
}

View File

@@ -0,0 +1,293 @@
/*
* This file is licensed under the MIT License (MIT).
*
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package co.aikar.timings;
import com.google.common.base.Preconditions;
import com.google.common.collect.EvictingQueue;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import java.util.Queue;
import java.util.logging.Level;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@SuppressWarnings({"UnusedDeclaration", "WeakerAccess", "SameParameterValue"})
public final class Timings {
private static final int MAX_HISTORY_FRAMES = 12;
public static final Timing NULL_HANDLER = new NullTimingHandler();
static boolean timingsEnabled = false;
static boolean verboseEnabled = false;
private static int historyInterval = -1;
private static int historyLength = -1;
private Timings() {}
/**
* Returns a Timing for a plugin corresponding to a name.
*
* @param plugin Plugin to own the Timing
* @param name Name of Timing
* @return Handler
*/
@NotNull
public static Timing of(@NotNull Plugin plugin, @NotNull String name) {
Timing pluginHandler = null;
if (plugin != null) {
pluginHandler = ofSafe(plugin.getName(), "Combined Total", TimingsManager.PLUGIN_GROUP_HANDLER);
}
return of(plugin, name, pluginHandler);
}
/**
* <p>Returns a handler that has a groupHandler timer handler. Parent timers should not have their
* start/stop methods called directly, as the children will call it for you.</p>
*
* Parent Timers are used to group multiple subsections together and get a summary of them combined
* Parent Handler can not be changed after first call
*
* @param plugin Plugin to own the Timing
* @param name Name of Timing
* @param groupHandler Parent handler to mirror .start/stop calls to
* @return Timing Handler
*/
@NotNull
public static Timing of(@NotNull Plugin plugin, @NotNull String name, @Nullable Timing groupHandler) {
Preconditions.checkNotNull(plugin, "Plugin can not be null");
return TimingsManager.getHandler(plugin.getName(), name, groupHandler);
}
/**
* Returns a Timing object after starting it, useful for Java7 try-with-resources.
*
* try (Timing ignored = Timings.ofStart(plugin, someName)) {
* // timed section
* }
*
* @param plugin Plugin to own the Timing
* @param name Name of Timing
* @return Timing Handler
*/
@NotNull
public static Timing ofStart(@NotNull Plugin plugin, @NotNull String name) {
return ofStart(plugin, name, null);
}
/**
* Returns a Timing object after starting it, useful for Java7 try-with-resources.
*
* try (Timing ignored = Timings.ofStart(plugin, someName, groupHandler)) {
* // timed section
* }
*
* @param plugin Plugin to own the Timing
* @param name Name of Timing
* @param groupHandler Parent handler to mirror .start/stop calls to
* @return Timing Handler
*/
@NotNull
public static Timing ofStart(@NotNull Plugin plugin, @NotNull String name, @Nullable Timing groupHandler) {
Timing timing = of(plugin, name, groupHandler);
timing.startTiming();
return timing;
}
/**
* Gets whether or not the Spigot Timings system is enabled
*
* @return Enabled or not
*/
public static boolean isTimingsEnabled() {
return timingsEnabled;
}
/**
* <p>Sets whether or not the Spigot Timings system should be enabled</p>
*
* Calling this will reset timing data.
*
* @param enabled Should timings be reported
*/
public static void setTimingsEnabled(boolean enabled) {
timingsEnabled = enabled;
reset();
}
/**
* <p>Sets whether or not the Timings should monitor at Verbose level.</p>
*
* <p>When Verbose is disabled, high-frequency timings will not be available.</p>
*
* @return Enabled or not
*/
public static boolean isVerboseTimingsEnabled() {
return verboseEnabled;
}
/**
* <p>Sets whether or not the Timings should monitor at Verbose level.</p>
*
* When Verbose is disabled, high-frequency timings will not be available.
* Calling this will reset timing data.
*
* @param enabled Should high-frequency timings be reported
*/
public static void setVerboseTimingsEnabled(boolean enabled) {
verboseEnabled = enabled;
TimingsManager.needsRecheckEnabled = true;
}
/**
* <p>Gets the interval between Timing History report generation.</p>
*
* Defaults to 5 minutes (6000 ticks)
*
* @return Interval in ticks
*/
public static int getHistoryInterval() {
return historyInterval;
}
/**
* <p>Sets the interval between Timing History report generations.</p>
*
* <p>Defaults to 5 minutes (6000 ticks)</p>
*
* This will recheck your history length, so lowering this value will lower your
* history length if you need more than 60 history windows.
*
* @param interval Interval in ticks
*/
public static void setHistoryInterval(int interval) {
historyInterval = Math.max(20*60, interval);
// Recheck the history length with the new Interval
if (historyLength != -1) {
setHistoryLength(historyLength);
}
}
/**
* Gets how long in ticks Timings history is kept for the server.
*
* Defaults to 1 hour (72000 ticks)
*
* @return Duration in Ticks
*/
public static int getHistoryLength() {
return historyLength;
}
/**
* Sets how long Timing History reports are kept for the server.
*
* Defaults to 1 hours(72000 ticks)
*
* This value is capped at a maximum of getHistoryInterval() * MAX_HISTORY_FRAMES (12)
*
* Will not reset Timing Data but may truncate old history if the new length is less than old length.
*
* @param length Duration in ticks
*/
public static void setHistoryLength(int length) {
// Cap at 12 History Frames, 1 hour at 5 minute frames.
int maxLength = historyInterval * MAX_HISTORY_FRAMES;
// For special cases of servers with special permission to bypass the max.
// This max helps keep data file sizes reasonable for processing on Aikar's Timing parser side.
// Setting this will not help you bypass the max unless Aikar has added an exception on the API side.
if (System.getProperty("timings.bypassMax") != null) {
maxLength = Integer.MAX_VALUE;
}
historyLength = Math.max(Math.min(maxLength, length), historyInterval);
Queue<TimingHistory> oldQueue = TimingsManager.HISTORY;
int frames = (getHistoryLength() / getHistoryInterval());
if (length > maxLength) {
Bukkit.getLogger().log(Level.WARNING, "Timings Length too high. Requested " + length + ", max is " + maxLength + ". To get longer history, you must increase your interval. Set Interval to " + Math.ceil(length / MAX_HISTORY_FRAMES) + " to achieve this length.");
}
TimingsManager.HISTORY = EvictingQueue.create(frames);
TimingsManager.HISTORY.addAll(oldQueue);
}
/**
* Resets all Timing Data
*/
public static void reset() {
TimingsManager.reset();
}
/**
* Generates a report and sends it to the specified command sender.
*
* If sender is null, ConsoleCommandSender will be used.
* @param sender The sender to send to, or null to use the ConsoleCommandSender
*/
public static void generateReport(@Nullable CommandSender sender) {
if (sender == null) {
sender = Bukkit.getConsoleSender();
}
TimingsExport.requestingReport.add(sender);
}
/**
* Generates a report and sends it to the specified listener.
* Use with {@link org.bukkit.command.BufferedCommandSender} to get full response when done!
* @param sender The listener to send responses too.
*/
public static void generateReport(@NotNull TimingsReportListener sender) {
Validate.notNull(sender);
TimingsExport.requestingReport.add(sender);
}
/*
=================
Protected API: These are for internal use only in Bukkit/CraftBukkit
These do not have isPrimaryThread() checks in the startTiming/stopTiming
=================
*/
@NotNull
static TimingHandler ofSafe(@NotNull String name) {
return ofSafe(null, name, null);
}
@NotNull
static Timing ofSafe(@Nullable Plugin plugin, @NotNull String name) {
Timing pluginHandler = null;
if (plugin != null) {
pluginHandler = ofSafe(plugin.getName(), "Combined Total", TimingsManager.PLUGIN_GROUP_HANDLER);
}
return ofSafe(plugin != null ? plugin.getName() : "Minecraft - Invalid Plugin", name, pluginHandler);
}
@NotNull
static TimingHandler ofSafe(@NotNull String name, @Nullable Timing groupHandler) {
return ofSafe(null, name, groupHandler);
}
@NotNull
static TimingHandler ofSafe(@Nullable String groupName, @NotNull String name, @Nullable Timing groupHandler) {
return TimingsManager.getHandler(groupName, name, groupHandler);
}
}

View File

@@ -0,0 +1,122 @@
/*
* This file is licensed under the MIT License (MIT).
*
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package co.aikar.timings;
import com.google.common.collect.ImmutableList;
import org.apache.commons.lang.Validate;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.defaults.BukkitCommand;
import org.bukkit.util.StringUtil;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class TimingsCommand extends BukkitCommand {
private static final List<String> TIMINGS_SUBCOMMANDS = ImmutableList.of("report", "reset", "on", "off", "paste", "verbon", "verboff");
private long lastResetAttempt = 0;
public TimingsCommand(@NotNull String name) {
super(name);
this.description = "Manages Spigot Timings data to see performance of the server.";
this.usageMessage = "/timings <reset|report|on|off|verbon|verboff>";
this.setPermission("bukkit.command.timings");
}
@Override
public boolean execute(@NotNull CommandSender sender, @NotNull String currentAlias, @NotNull String[] args) {
if (!testPermission(sender)) {
return true;
}
if (args.length < 1) {
sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
return true;
}
final String arg = args[0];
if ("on".equalsIgnoreCase(arg)) {
Timings.setTimingsEnabled(true);
sender.sendMessage("Enabled Timings & Reset");
return true;
} else if ("off".equalsIgnoreCase(arg)) {
Timings.setTimingsEnabled(false);
sender.sendMessage("Disabled Timings");
return true;
}
if (!Timings.isTimingsEnabled()) {
sender.sendMessage("Please enable timings by typing /timings on");
return true;
}
long now = System.currentTimeMillis();
if ("verbon".equalsIgnoreCase(arg)) {
Timings.setVerboseTimingsEnabled(true);
sender.sendMessage("Enabled Verbose Timings");
return true;
} else if ("verboff".equalsIgnoreCase(arg)) {
Timings.setVerboseTimingsEnabled(false);
sender.sendMessage("Disabled Verbose Timings");
return true;
} else if ("reset".equalsIgnoreCase(arg)) {
if (now - lastResetAttempt < 30000) {
TimingsManager.reset();
sender.sendMessage(ChatColor.RED + "Timings reset. Please wait 5-10 minutes before using /timings report.");
} else {
lastResetAttempt = now;
sender.sendMessage(ChatColor.RED + "WARNING: Timings v2 should not be reset. If you are encountering lag, please wait 3 minutes and then issue a report. The best timings will include 10+ minutes, with data before and after your lag period. If you really want to reset, run this command again within 30 seconds.");
}
} else if ("cost".equals(arg)) {
sender.sendMessage("Timings cost: " + TimingsExport.getCost());
} else if (
"paste".equalsIgnoreCase(arg) ||
"report".equalsIgnoreCase(arg) ||
"get".equalsIgnoreCase(arg) ||
"merged".equalsIgnoreCase(arg) ||
"separate".equalsIgnoreCase(arg)
) {
Timings.generateReport(sender);
} else {
sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
}
return true;
}
@NotNull
@Override
public List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) {
Validate.notNull(sender, "Sender cannot be null");
Validate.notNull(args, "Arguments cannot be null");
Validate.notNull(alias, "Alias cannot be null");
if (args.length == 1) {
return StringUtil.copyPartialMatches(args[0], TIMINGS_SUBCOMMANDS,
new ArrayList<String>(TIMINGS_SUBCOMMANDS.size()));
}
return ImmutableList.of();
}
}

View File

@@ -0,0 +1,355 @@
/*
* This file is licensed under the MIT License (MIT).
*
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package co.aikar.timings;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.commons.lang.StringUtils;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.MemorySection;
import org.bukkit.entity.EntityType;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.zip.GZIPOutputStream;
import static co.aikar.timings.TimingsManager.HISTORY;
import static co.aikar.util.JSONUtil.appendObjectData;
import static co.aikar.util.JSONUtil.createObject;
import static co.aikar.util.JSONUtil.pair;
import static co.aikar.util.JSONUtil.toArray;
import static co.aikar.util.JSONUtil.toArrayMapper;
import static co.aikar.util.JSONUtil.toObjectMapper;
@SuppressWarnings({"rawtypes", "SuppressionAnnotation"})
class TimingsExport extends Thread {
private final TimingsReportListener listeners;
private final Map out;
private final TimingHistory[] history;
private static long lastReport = 0;
final static List<CommandSender> requestingReport = Lists.newArrayList();
private TimingsExport(TimingsReportListener listeners, Map out, TimingHistory[] history) {
super("Timings paste thread");
this.listeners = listeners;
this.out = out;
this.history = history;
}
/**
* Checks if any pending reports are being requested, and builds one if needed.
*/
static void reportTimings() {
if (requestingReport.isEmpty()) {
return;
}
TimingsReportListener listeners = new TimingsReportListener(requestingReport);
listeners.addConsoleIfNeeded();
requestingReport.clear();
long now = System.currentTimeMillis();
final long lastReportDiff = now - lastReport;
if (lastReportDiff < 60000) {
listeners.sendMessage(ChatColor.RED + "Please wait at least 1 minute in between Timings reports. (" + (int)((60000 - lastReportDiff) / 1000) + " seconds)");
listeners.done();
return;
}
final long lastStartDiff = now - TimingsManager.timingStart;
if (lastStartDiff < 180000) {
listeners.sendMessage(ChatColor.RED + "Please wait at least 3 minutes before generating a Timings report. Unlike Timings v1, v2 benefits from longer timings and is not as useful with short timings. (" + (int)((180000 - lastStartDiff) / 1000) + " seconds)");
listeners.done();
return;
}
listeners.sendMessage(ChatColor.GREEN + "Preparing Timings Report...");
lastReport = now;
Map parent = createObject(
// Get some basic system details about the server
pair("version", Bukkit.getVersion()),
pair("maxplayers", Bukkit.getMaxPlayers()),
pair("start", TimingsManager.timingStart / 1000),
pair("end", System.currentTimeMillis() / 1000),
pair("sampletime", (System.currentTimeMillis() - TimingsManager.timingStart) / 1000)
);
if (!TimingsManager.privacy) {
appendObjectData(parent,
pair("server", Bukkit.getServerName()),
pair("motd", Bukkit.getServer().getMotd()),
pair("online-mode", Bukkit.getServer().getOnlineMode()),
pair("icon", Bukkit.getServer().getServerIcon().getData())
);
}
final Runtime runtime = Runtime.getRuntime();
RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
parent.put("system", createObject(
pair("timingcost", getCost()),
pair("name", System.getProperty("os.name")),
pair("version", System.getProperty("os.version")),
pair("jvmversion", System.getProperty("java.version")),
pair("arch", System.getProperty("os.arch")),
pair("maxmem", runtime.maxMemory()),
pair("cpu", runtime.availableProcessors()),
pair("runtime", ManagementFactory.getRuntimeMXBean().getUptime()),
pair("flags", StringUtils.join(runtimeBean.getInputArguments(), " ")),
pair("gc", toObjectMapper(ManagementFactory.getGarbageCollectorMXBeans(), input -> pair(input.getName(), toArray(input.getCollectionCount(), input.getCollectionTime()))))
)
);
Set<Material> tileEntityTypeSet = Sets.newHashSet();
Set<EntityType> entityTypeSet = Sets.newHashSet();
int size = HISTORY.size();
TimingHistory[] history = new TimingHistory[size + 1];
int i = 0;
for (TimingHistory timingHistory : HISTORY) {
tileEntityTypeSet.addAll(timingHistory.tileEntityTypeSet);
entityTypeSet.addAll(timingHistory.entityTypeSet);
history[i++] = timingHistory;
}
history[i] = new TimingHistory(); // Current snapshot
tileEntityTypeSet.addAll(history[i].tileEntityTypeSet);
entityTypeSet.addAll(history[i].entityTypeSet);
Map handlers = createObject();
Map groupData;
synchronized (TimingIdentifier.GROUP_MAP) {
for (TimingIdentifier.TimingGroup group : TimingIdentifier.GROUP_MAP.values()) {
synchronized (group.handlers) {
for (TimingHandler id : group.handlers) {
if (!id.isTimed() && !id.isSpecial()) {
continue;
}
String name = id.identifier.name;
if (name.startsWith("##")) {
name = name.substring(3);
}
handlers.put(id.id, toArray(
group.id,
name
));
}
}
}
groupData = toObjectMapper(
TimingIdentifier.GROUP_MAP.values(), group -> pair(group.id, group.name));
}
parent.put("idmap", createObject(
pair("groups", groupData),
pair("handlers", handlers),
pair("worlds", toObjectMapper(TimingHistory.worldMap.entrySet(), input -> pair(input.getValue(), input.getKey()))),
pair("tileentity",
toObjectMapper(tileEntityTypeSet, input -> pair(input.getId(), input.name()))),
pair("entity",
toObjectMapper(entityTypeSet, input -> pair(input.getTypeId(), input.name())))
));
// Information about loaded plugins
parent.put("plugins", toObjectMapper(Bukkit.getPluginManager().getPlugins(),
plugin -> pair(plugin.getName(), createObject(
pair("version", plugin.getDescription().getVersion()),
pair("description", String.valueOf(plugin.getDescription().getDescription()).trim()),
pair("website", plugin.getDescription().getWebsite()),
pair("authors", StringUtils.join(plugin.getDescription().getAuthors(), ", "))
))));
// Information on the users Config
parent.put("config", createObject(
pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)),
pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)),
pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null))
));
new TimingsExport(listeners, parent, history).start();
}
static long getCost() {
// Benchmark the users System.nanotime() for cost basis
int passes = 100;
TimingHandler SAMPLER1 = Timings.ofSafe("Timings Sampler 1");
TimingHandler SAMPLER2 = Timings.ofSafe("Timings Sampler 2");
TimingHandler SAMPLER3 = Timings.ofSafe("Timings Sampler 3");
TimingHandler SAMPLER4 = Timings.ofSafe("Timings Sampler 4");
TimingHandler SAMPLER5 = Timings.ofSafe("Timings Sampler 5");
TimingHandler SAMPLER6 = Timings.ofSafe("Timings Sampler 6");
long start = System.nanoTime();
for (int i = 0; i < passes; i++) {
SAMPLER1.startTiming();
SAMPLER2.startTiming();
SAMPLER3.startTiming();
SAMPLER3.stopTiming();
SAMPLER4.startTiming();
SAMPLER5.startTiming();
SAMPLER6.startTiming();
SAMPLER6.stopTiming();
SAMPLER5.stopTiming();
SAMPLER4.stopTiming();
SAMPLER2.stopTiming();
SAMPLER1.stopTiming();
}
long timingsCost = (System.nanoTime() - start) / passes / 6;
SAMPLER1.reset(true);
SAMPLER2.reset(true);
SAMPLER3.reset(true);
SAMPLER4.reset(true);
SAMPLER5.reset(true);
SAMPLER6.reset(true);
return timingsCost;
}
private static JSONObject mapAsJSON(ConfigurationSection config, String parentKey) {
JSONObject object = new JSONObject();
for (String key : config.getKeys(false)) {
String fullKey = (parentKey != null ? parentKey + "." + key : key);
if (fullKey.equals("database") || fullKey.equals("settings.bungeecord-addresses") || TimingsManager.hiddenConfigs.contains(fullKey)) {
continue;
}
final Object val = config.get(key);
object.put(key, valAsJSON(val, fullKey));
}
return object;
}
private static Object valAsJSON(Object val, final String parentKey) {
if (!(val instanceof MemorySection)) {
if (val instanceof List) {
Iterable<Object> v = (Iterable<Object>) val;
return toArrayMapper(v, input -> valAsJSON(input, parentKey));
} else {
return val.toString();
}
} else {
return mapAsJSON((ConfigurationSection) val, parentKey);
}
}
@Override
public void run() {
out.put("data", toArrayMapper(history, TimingHistory::export));
String response = null;
String timingsURL = null;
try {
HttpURLConnection con = (HttpURLConnection) new URL("http://timings.aikar.co/post").openConnection();
con.setDoOutput(true);
String hostName = "BrokenHost";
try {
hostName = InetAddress.getLocalHost().getHostName();
} catch(Exception ignored) {}
con.setRequestProperty("User-Agent", "Paper/" + Bukkit.getServerName() + "/" + hostName);
con.setRequestMethod("POST");
con.setInstanceFollowRedirects(false);
OutputStream request = new GZIPOutputStream(con.getOutputStream()) {{
this.def.setLevel(7);
}};
request.write(JSONValue.toJSONString(out).getBytes("UTF-8"));
request.close();
response = getResponse(con);
if (con.getResponseCode() != 302) {
listeners.sendMessage(
ChatColor.RED + "Upload Error: " + con.getResponseCode() + ": " + con.getResponseMessage());
listeners.sendMessage(ChatColor.RED + "Check your logs for more information");
if (response != null) {
Bukkit.getLogger().log(Level.SEVERE, response);
}
return;
}
timingsURL = con.getHeaderField("Location");
listeners.sendMessage(ChatColor.GREEN + "View Timings Report: " + timingsURL);
if (response != null && !response.isEmpty()) {
Bukkit.getLogger().log(Level.INFO, "Timing Response: " + response);
}
} catch (IOException ex) {
listeners.sendMessage(ChatColor.RED + "Error uploading timings, check your logs for more information");
if (response != null) {
Bukkit.getLogger().log(Level.SEVERE, response);
}
Bukkit.getLogger().log(Level.SEVERE, "Could not paste timings", ex);
} finally {
this.listeners.done(timingsURL);
}
}
private String getResponse(HttpURLConnection con) throws IOException {
InputStream is = null;
try {
is = con.getInputStream();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int bytesRead;
while ((bytesRead = is.read(b)) != -1) {
bos.write(b, 0, bytesRead);
}
return bos.toString();
} catch (IOException ex) {
listeners.sendMessage(ChatColor.RED + "Error uploading timings, check your logs for more information");
Bukkit.getLogger().log(Level.WARNING, con.getResponseMessage(), ex);
return null;
} finally {
if (is != null) {
is.close();
}
}
}
}

View File

@@ -0,0 +1,188 @@
/*
* This file is licensed under the MIT License (MIT).
*
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package co.aikar.timings;
import co.aikar.util.LoadingMap;
import com.google.common.collect.EvictingQueue;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.PluginClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class TimingsManager {
static final Map<TimingIdentifier, TimingHandler> TIMING_MAP = LoadingMap.of(
new ConcurrentHashMap<>(4096, .5F), TimingHandler::new
);
public static final FullServerTickHandler FULL_SERVER_TICK = new FullServerTickHandler();
public static final TimingHandler TIMINGS_TICK = Timings.ofSafe("Timings Tick", FULL_SERVER_TICK);
public static final Timing PLUGIN_GROUP_HANDLER = Timings.ofSafe("Plugins");
public static List<String> hiddenConfigs = new ArrayList<String>();
public static boolean privacy = false;
static final List<TimingHandler> HANDLERS = new ArrayList<>(1024);
static final List<TimingHistory.MinuteReport> MINUTE_REPORTS = new ArrayList<>(64);
static EvictingQueue<TimingHistory> HISTORY = EvictingQueue.create(12);
static long timingStart = 0;
static long historyStart = 0;
static boolean needsFullReset = false;
static boolean needsRecheckEnabled = false;
private TimingsManager() {}
/**
* Resets all timing data on the next tick
*/
static void reset() {
needsFullReset = true;
}
/**
* Ticked every tick by CraftBukkit to count the number of times a timer
* caused TPS loss.
*/
static void tick() {
if (Timings.timingsEnabled) {
boolean violated = FULL_SERVER_TICK.isViolated();
for (TimingHandler handler : HANDLERS) {
if (handler.isSpecial()) {
// We manually call this
continue;
}
handler.processTick(violated);
}
TimingHistory.playerTicks += Bukkit.getOnlinePlayers().size();
TimingHistory.timedTicks++;
// Generate TPS/Ping/Tick reports every minute
}
}
static void stopServer() {
Timings.timingsEnabled = false;
recheckEnabled();
}
static void recheckEnabled() {
synchronized (TIMING_MAP) {
for (TimingHandler timings : TIMING_MAP.values()) {
timings.checkEnabled();
}
}
needsRecheckEnabled = false;
}
static void resetTimings() {
if (needsFullReset) {
// Full resets need to re-check every handlers enabled state
// Timing map can be modified from async so we must sync on it.
synchronized (TIMING_MAP) {
for (TimingHandler timings : TIMING_MAP.values()) {
timings.reset(true);
}
}
Bukkit.getLogger().log(Level.INFO, "Timings Reset");
HISTORY.clear();
needsFullReset = false;
needsRecheckEnabled = false;
timingStart = System.currentTimeMillis();
} else {
// Soft resets only need to act on timings that have done something
// Handlers can only be modified on main thread.
for (TimingHandler timings : HANDLERS) {
timings.reset(false);
}
}
HANDLERS.clear();
MINUTE_REPORTS.clear();
TimingHistory.resetTicks(true);
historyStart = System.currentTimeMillis();
}
@NotNull
static TimingHandler getHandler(@Nullable String group, @NotNull String name, @Nullable Timing parent) {
return TIMING_MAP.get(new TimingIdentifier(group, name, parent));
}
/**
* <p>Due to access restrictions, we need a helper method to get a Command TimingHandler with String group</p>
*
* Plugins should never call this
*
* @param pluginName Plugin this command is associated with
* @param command Command to get timings for
* @return TimingHandler
*/
@NotNull
public static Timing getCommandTiming(@Nullable String pluginName, @NotNull Command command) {
Plugin plugin = null;
final Server server = Bukkit.getServer();
if (!( server == null || pluginName == null ||
"minecraft".equals(pluginName) || "bukkit".equals(pluginName) ||
"spigot".equalsIgnoreCase(pluginName) || "paper".equals(pluginName)
)) {
plugin = server.getPluginManager().getPlugin(pluginName);
}
if (plugin == null) {
// Plugin is passing custom fallback prefix, try to look up by class loader
plugin = getPluginByClassloader(command.getClass());
}
if (plugin == null) {
return Timings.ofSafe("Command: " + pluginName + ":" + command.getTimingName());
}
return Timings.ofSafe(plugin, "Command: " + pluginName + ":" + command.getTimingName());
}
/**
* Looks up the class loader for the specified class, and if it is a PluginClassLoader, return the
* Plugin that created this class.
*
* @param clazz Class to check
* @return Plugin if created by a plugin
*/
@Nullable
public static Plugin getPluginByClassloader(@Nullable Class<?> clazz) {
if (clazz == null) {
return null;
}
final ClassLoader classLoader = clazz.getClassLoader();
if (classLoader instanceof PluginClassLoader) {
PluginClassLoader pluginClassLoader = (PluginClassLoader) classLoader;
return pluginClassLoader.getPlugin();
}
return null;
}
}

View File

@@ -0,0 +1,75 @@
package co.aikar.timings;
import com.google.common.collect.Lists;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.MessageCommandSender;
import org.bukkit.command.RemoteConsoleCommandSender;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@SuppressWarnings("WeakerAccess")
public class TimingsReportListener implements MessageCommandSender {
private final List<CommandSender> senders;
private final Runnable onDone;
private String timingsURL;
public TimingsReportListener(@NotNull CommandSender senders) {
this(senders, null);
}
public TimingsReportListener(@NotNull CommandSender sender, @Nullable Runnable onDone) {
this(Lists.newArrayList(sender), onDone);
}
public TimingsReportListener(@NotNull List<CommandSender> senders) {
this(senders, null);
}
public TimingsReportListener(@NotNull List<CommandSender> senders, @Nullable Runnable onDone) {
Validate.notNull(senders);
Validate.notEmpty(senders);
this.senders = Lists.newArrayList(senders);
this.onDone = onDone;
}
@Nullable
public String getTimingsURL() {
return timingsURL;
}
public void done() {
done(null);
}
public void done(@Nullable String url) {
this.timingsURL = url;
if (onDone != null) {
onDone.run();
}
for (CommandSender sender : senders) {
if (sender instanceof TimingsReportListener) {
((TimingsReportListener) sender).done();
}
}
}
@Override
public void sendMessage(@NotNull String message) {
senders.forEach((sender) -> sender.sendMessage(message));
}
public void addConsoleIfNeeded() {
boolean hasConsole = false;
for (CommandSender sender : this.senders) {
if (sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender) {
hasConsole = true;
}
}
if (!hasConsole) {
this.senders.add(Bukkit.getConsoleSender());
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* This file is licensed under the MIT License (MIT).
*
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package co.aikar.timings;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
class UnsafeTimingHandler extends TimingHandler {
UnsafeTimingHandler(@NotNull TimingIdentifier id) {
super(id);
}
private static void checkThread() {
if (!Bukkit.isPrimaryThread()) {
throw new IllegalStateException("Calling Timings from Async Operation");
}
}
@NotNull
@Override
public Timing startTiming() {
checkThread();
return super.startTiming();
}
@Override
public void stopTiming() {
checkThread();
super.stopTiming();
}
}

View File

@@ -0,0 +1,38 @@
package co.aikar.util;
import com.google.common.collect.ForwardingMap;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class Counter <T> extends ForwardingMap<T, Long> {
private final Map<T, Long> counts = new HashMap<>();
public long decrement(@Nullable T key) {
return increment(key, -1);
}
public long increment(@Nullable T key) {
return increment(key, 1);
}
public long decrement(@Nullable T key, long amount) {
return decrement(key, -amount);
}
public long increment(@Nullable T key, long amount) {
Long count = this.getCount(key);
count += amount;
this.counts.put(key, count);
return count;
}
public long getCount(@Nullable T key) {
return this.counts.getOrDefault(key, 0L);
}
@NotNull
@Override
protected Map<T, Long> delegate() {
return this.counts;
}
}

View File

@@ -0,0 +1,140 @@
package co.aikar.util;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Provides Utility methods that assist with generating JSON Objects
*/
@SuppressWarnings({"rawtypes", "SuppressionAnnotation"})
public final class JSONUtil {
private JSONUtil() {}
/**
* Creates a key/value "JSONPair" object
*
* @param key Key to use
* @param obj Value to use
* @return JSONPair
*/
@NotNull
public static JSONPair pair(@NotNull String key, @Nullable Object obj) {
return new JSONPair(key, obj);
}
@NotNull
public static JSONPair pair(long key, @Nullable Object obj) {
return new JSONPair(String.valueOf(key), obj);
}
/**
* Creates a new JSON object from multiple JSONPair key/value pairs
*
* @param data JSONPairs
* @return Map
*/
@NotNull
public static Map<String, Object> createObject(@NotNull JSONPair... data) {
return appendObjectData(new LinkedHashMap(), data);
}
/**
* This appends multiple key/value Obj pairs into a JSON Object
*
* @param parent Map to be appended to
* @param data Data to append
* @return Map
*/
@NotNull
public static Map<String, Object> appendObjectData(@NotNull Map parent, @NotNull JSONPair... data) {
for (JSONPair JSONPair : data) {
parent.put(JSONPair.key, JSONPair.val);
}
return parent;
}
/**
* This builds a JSON array from a set of data
*
* @param data Data to build JSON array from
* @return List
*/
@NotNull
public static List toArray(@NotNull Object... data) {
return Lists.newArrayList(data);
}
/**
* These help build a single JSON array using a mapper function
*
* @param collection Collection to apply to
* @param mapper Mapper to apply
* @param <E> Element Type
* @return List
*/
@NotNull
public static <E> List toArrayMapper(@NotNull E[] collection, @NotNull Function<E, Object> mapper) {
return toArrayMapper(Lists.newArrayList(collection), mapper);
}
@NotNull
public static <E> List toArrayMapper(@NotNull Iterable<E> collection, @NotNull Function<E, Object> mapper) {
List array = Lists.newArrayList();
for (E e : collection) {
Object object = mapper.apply(e);
if (object != null) {
array.add(object);
}
}
return array;
}
/**
* These help build a single JSON Object from a collection, using a mapper function
*
* @param collection Collection to apply to
* @param mapper Mapper to apply
* @param <E> Element Type
* @return Map
*/
@NotNull
public static <E> Map toObjectMapper(@NotNull E[] collection, @NotNull Function<E, JSONPair> mapper) {
return toObjectMapper(Lists.newArrayList(collection), mapper);
}
@NotNull
public static <E> Map toObjectMapper(@NotNull Iterable<E> collection, @NotNull Function<E, JSONPair> mapper) {
Map object = Maps.newLinkedHashMap();
for (E e : collection) {
JSONPair JSONPair = mapper.apply(e);
if (JSONPair != null) {
object.put(JSONPair.key, JSONPair.val);
}
}
return object;
}
/**
* Simply stores a key and a value, used internally by many methods below.
*/
@SuppressWarnings("PublicInnerClass")
public static class JSONPair {
final String key;
final Object val;
JSONPair(@NotNull String key, @NotNull Object val) {
this.key = key;
this.val = val;
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) 2015. Starlis LLC / dba Empire Minecraft
*
* This source code is proprietary software and must not be redistributed without Starlis LLC's approval
*
*/
package co.aikar.util;
import com.google.common.base.Function;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Allows you to pass a Loader function that when a key is accessed that doesn't exist,
* automatically loads the entry into the map by calling the loader Function.
*
* .get() Will only return null if the Loader can return null.
*
* You may pass any backing Map to use.
*
* This class is not thread safe and should be wrapped with Collections.synchronizedMap on the OUTSIDE of the LoadingMap if needed.
*
* Do not wrap the backing map with Collections.synchronizedMap.
*
* @param <V> Value
*/
public class LoadingIntMap<V> extends Int2ObjectOpenHashMap<V> {
private final Function<Integer, V> loader;
public LoadingIntMap(@NotNull Function<Integer, V> loader) {
super();
this.loader = loader;
}
public LoadingIntMap(int expectedSize, @NotNull Function<Integer, V> loader) {
super(expectedSize);
this.loader = loader;
}
public LoadingIntMap(int expectedSize, float loadFactor, @NotNull Function<Integer, V> loader) {
super(expectedSize, loadFactor);
this.loader = loader;
}
@Nullable
@Override
public V get(int key) {
V res = super.get(key);
if (res == null) {
res = loader.apply(key);
if (res != null) {
put(key, res);
}
}
return res;
}
/**
* Due to java stuff, you will need to cast it to (Function) for some cases
*
* @param <T> Type
*/
public abstract static class Feeder <T> implements Function<T, T> {
@Nullable
@Override
public T apply(@Nullable Object input) {
return apply();
}
@Nullable
public abstract T apply();
}
}

View File

@@ -0,0 +1,368 @@
/*
* This file is licensed under the MIT License (MIT).
*
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package co.aikar.util;
import com.google.common.base.Preconditions;
import java.lang.reflect.Constructor;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Allows you to pass a Loader function that when a key is accessed that doesn't exists,
* automatically loads the entry into the map by calling the loader Function.
*
* .get() Will only return null if the Loader can return null.
*
* You may pass any backing Map to use.
*
* This class is not thread safe and should be wrapped with Collections.synchronizedMap on the OUTSIDE of the LoadingMap if needed.
*
* Do not wrap the backing map with Collections.synchronizedMap.
*
* @param <K> Key
* @param <V> Value
*/
public class LoadingMap <K, V> extends AbstractMap<K, V> {
private final Map<K, V> backingMap;
private final java.util.function.Function<K, V> loader;
/**
* Initializes an auto loading map using specified loader and backing map
* @param backingMap Map to wrap
* @param loader Loader
*/
public LoadingMap(@NotNull Map<K, V> backingMap, @NotNull java.util.function.Function<K, V> loader) {
this.backingMap = backingMap;
this.loader = loader;
}
/**
* Creates a new LoadingMap with the specified map and loader
*
* @param backingMap Actual map being used.
* @param loader Loader to use
* @param <K> Key Type of the Map
* @param <V> Value Type of the Map
* @return Map
*/
@NotNull
public static <K, V> Map<K, V> of(@NotNull Map<K, V> backingMap, @NotNull Function<K, V> loader) {
return new LoadingMap<>(backingMap, loader);
}
/**
* Creates a LoadingMap with an auto instantiating loader.
*
* Will auto construct class of of Value when not found
*
* Since this uses Reflection, It is more effecient to define your own static loader
* than using this helper, but if performance is not critical, this is easier.
*
* @param backingMap Actual map being used.
* @param keyClass Class used for the K generic
* @param valueClass Class used for the V generic
* @param <K> Key Type of the Map
* @param <V> Value Type of the Map
* @return Map that auto instantiates on .get()
*/
@NotNull
public static <K, V> Map<K, V> newAutoMap(@NotNull Map<K, V> backingMap, @Nullable final Class<? extends K> keyClass,
@NotNull final Class<? extends V> valueClass) {
return new LoadingMap<>(backingMap, new AutoInstantiatingLoader<>(keyClass, valueClass));
}
/**
* Creates a LoadingMap with an auto instantiating loader.
*
* Will auto construct class of of Value when not found
*
* Since this uses Reflection, It is more effecient to define your own static loader
* than using this helper, but if performance is not critical, this is easier.
*
* @param backingMap Actual map being used.
* @param valueClass Class used for the V generic
* @param <K> Key Type of the Map
* @param <V> Value Type of the Map
* @return Map that auto instantiates on .get()
*/
@NotNull
public static <K, V> Map<K, V> newAutoMap(@NotNull Map<K, V> backingMap,
@NotNull final Class<? extends V> valueClass) {
return newAutoMap(backingMap, null, valueClass);
}
/**
* @see #newAutoMap
*
* new Auto initializing map using a HashMap.
*
* @param keyClass Class used for the K generic
* @param valueClass Class used for the V generic
* @param <K> Key Type of the Map
* @param <V> Value Type of the Map
* @return Map that auto instantiates on .get()
*/
@NotNull
public static <K, V> Map<K, V> newHashAutoMap(@Nullable final Class<? extends K> keyClass, @NotNull final Class<? extends V> valueClass) {
return newAutoMap(new HashMap<>(), keyClass, valueClass);
}
/**
* @see #newAutoMap
*
* new Auto initializing map using a HashMap.
*
* @param valueClass Class used for the V generic
* @param <K> Key Type of the Map
* @param <V> Value Type of the Map
* @return Map that auto instantiates on .get()
*/
@NotNull
public static <K, V> Map<K, V> newHashAutoMap(@NotNull final Class<? extends V> valueClass) {
return newHashAutoMap(null, valueClass);
}
/**
* @see #newAutoMap
*
* new Auto initializing map using a HashMap.
*
* @param keyClass Class used for the K generic
* @param valueClass Class used for the V generic
* @param initialCapacity Initial capacity to use
* @param loadFactor Load factor to use
* @param <K> Key Type of the Map
* @param <V> Value Type of the Map
* @return Map that auto instantiates on .get()
*/
@NotNull
public static <K, V> Map<K, V> newHashAutoMap(@Nullable final Class<? extends K> keyClass, @NotNull final Class<? extends V> valueClass, int initialCapacity, float loadFactor) {
return newAutoMap(new HashMap<>(initialCapacity, loadFactor), keyClass, valueClass);
}
/**
* @see #newAutoMap
*
* new Auto initializing map using a HashMap.
*
* @param valueClass Class used for the V generic
* @param initialCapacity Initial capacity to use
* @param loadFactor Load factor to use
* @param <K> Key Type of the Map
* @param <V> Value Type of the Map
* @return Map that auto instantiates on .get()
*/
@NotNull
public static <K, V> Map<K, V> newHashAutoMap(@NotNull final Class<? extends V> valueClass, int initialCapacity, float loadFactor) {
return newHashAutoMap(null, valueClass, initialCapacity, loadFactor);
}
/**
* Initializes an auto loading map using a HashMap
*
* @param loader Loader to use
* @param <K> Key Type of the Map
* @param <V> Value Type of the Map
* @return Map
*/
@NotNull
public static <K, V> Map<K, V> newHashMap(@NotNull Function<K, V> loader) {
return new LoadingMap<>(new HashMap<>(), loader);
}
/**
* Initializes an auto loading map using a HashMap
*
* @param loader Loader to use
* @param initialCapacity Initial capacity to use
* @param <K> Key Type of the Map
* @param <V> Value Type of the Map
* @return Map
*/
@NotNull
public static <K, V> Map<K, V> newHashMap(@NotNull Function<K, V> loader, int initialCapacity) {
return new LoadingMap<>(new HashMap<>(initialCapacity), loader);
}
/**
* Initializes an auto loading map using a HashMap
*
* @param loader Loader to use
* @param initialCapacity Initial capacity to use
* @param loadFactor Load factor to use
* @param <K> Key Type of the Map
* @param <V> Value Type of the Map
* @return Map
*/
@NotNull
public static <K, V> Map<K, V> newHashMap(@NotNull Function<K, V> loader, int initialCapacity, float loadFactor) {
return new LoadingMap<>(new HashMap<>(initialCapacity, loadFactor), loader);
}
/**
* Initializes an auto loading map using an Identity HashMap
*
* @param loader Loader to use
* @param <K> Key Type of the Map
* @param <V> Value Type of the Map
* @return Map
*/
@NotNull
public static <K, V> Map<K, V> newIdentityHashMap(@NotNull Function<K, V> loader) {
return new LoadingMap<>(new IdentityHashMap<>(), loader);
}
/**
* Initializes an auto loading map using an Identity HashMap
*
* @param loader Loader to use
* @param initialCapacity Initial capacity to use
* @param <K> Key Type of the Map
* @param <V> Value Type of the Map
* @return Map
*/
@NotNull
public static <K, V> Map<K, V> newIdentityHashMap(@NotNull Function<K, V> loader, int initialCapacity) {
return new LoadingMap<>(new IdentityHashMap<>(initialCapacity), loader);
}
@Override
public int size() {return backingMap.size();}
@Override
public boolean isEmpty() {return backingMap.isEmpty();}
@Override
public boolean containsKey(@Nullable Object key) {return backingMap.containsKey(key);}
@Override
public boolean containsValue(@Nullable Object value) {return backingMap.containsValue(value);}
@Nullable
@Override
public V get(@Nullable Object key) {
V v = backingMap.get(key);
if (v != null) {
return v;
}
return backingMap.computeIfAbsent((K) key, loader);
}
@Nullable
public V put(@Nullable K key, @Nullable V value) {return backingMap.put(key, value);}
@Nullable
@Override
public V remove(@Nullable Object key) {return backingMap.remove(key);}
public void putAll(@NotNull Map<? extends K, ? extends V> m) {backingMap.putAll(m);}
@Override
public void clear() {backingMap.clear();}
@NotNull
@Override
public Set<K> keySet() {return backingMap.keySet();}
@NotNull
@Override
public Collection<V> values() {return backingMap.values();}
@Override
public boolean equals(@Nullable Object o) {return backingMap.equals(o);}
@Override
public int hashCode() {return backingMap.hashCode();}
@NotNull
@Override
public Set<Entry<K, V>> entrySet() {
return backingMap.entrySet();
}
@NotNull
public LoadingMap<K, V> clone() {
return new LoadingMap<>(backingMap, loader);
}
private static class AutoInstantiatingLoader<K, V> implements Function<K, V> {
final Constructor<? extends V> constructor;
private final Class<? extends V> valueClass;
AutoInstantiatingLoader(@Nullable Class<? extends K> keyClass, @NotNull Class<? extends V> valueClass) {
try {
this.valueClass = valueClass;
if (keyClass != null) {
constructor = valueClass.getConstructor(keyClass);
} else {
constructor = null;
}
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
valueClass.getName() + " does not have a constructor for " + (keyClass != null ? keyClass.getName() : null));
}
}
@NotNull
@Override
public V apply(@Nullable K input) {
try {
return (constructor != null ? constructor.newInstance(input) : valueClass.newInstance());
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object object) {
return false;
}
}
/**
* Due to java stuff, you will need to cast it to (Function) for some cases
*
* @param <T> Type
*/
public abstract static class Feeder <T> implements Function<T, T> {
@Nullable
@Override
public T apply(@Nullable Object input) {
return apply();
}
@Nullable
public abstract T apply();
}
}

View File

@@ -0,0 +1,111 @@
/*
* This file is licensed under the MIT License (MIT).
*
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package co.aikar.util;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Implements a Most Recently Used cache in front of a backing map, to quickly access the last accessed result.
*
* @param <K> Key Type of the Map
* @param <V> Value Type of the Map
*/
public class MRUMapCache<K, V> extends AbstractMap<K, V> {
final Map<K, V> backingMap;
Object cacheKey;
V cacheValue;
public MRUMapCache(@NotNull final Map<K, V> backingMap) {
this.backingMap = backingMap;
}
public int size() {return backingMap.size();}
public boolean isEmpty() {return backingMap.isEmpty();}
public boolean containsKey(@Nullable Object key) {
return key != null && key.equals(cacheKey) || backingMap.containsKey(key);
}
public boolean containsValue(@Nullable Object value) {
return value != null && value == cacheValue || backingMap.containsValue(value);
}
@Nullable
public V get(@Nullable Object key) {
if (cacheKey != null && cacheKey.equals(key)) {
return cacheValue;
}
cacheKey = key;
return cacheValue = backingMap.get(key);
}
@Nullable
public V put(@Nullable K key, @Nullable V value) {
cacheKey = key;
return cacheValue = backingMap.put(key, value);
}
@Nullable
public V remove(@Nullable Object key) {
if (key != null && key.equals(cacheKey)) {
cacheKey = null;
}
return backingMap.remove(key);
}
public void putAll(@NotNull Map<? extends K, ? extends V> m) {backingMap.putAll(m);}
public void clear() {
cacheKey = null;
cacheValue = null;
backingMap.clear();
}
@NotNull
public Set<K> keySet() {return backingMap.keySet();}
@NotNull
public Collection<V> values() {return backingMap.values();}
@NotNull
public Set<Map.Entry<K, V>> entrySet() {return backingMap.entrySet();}
/**
* Wraps the specified map with a most recently used cache
*
* @param map Map to be wrapped
* @param <K> Key Type of the Map
* @param <V> Value Type of the Map
* @return Map
*/
@NotNull
public static <K, V> Map<K, V> of(@NotNull Map<K, V> map) {
return new MRUMapCache<K, V>(map);
}
}

View File

@@ -0,0 +1,190 @@
/*
* Copyright (c) 2018 Daniel Ennis (Aikar) MIT License
*/
package com.destroystokyo.paper;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Tag;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.data.BlockData;
import org.bukkit.inventory.ItemStack;
import java.util.Collection;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class MaterialSetTag implements Tag<Material> {
private final NamespacedKey key;
private final Set<Material> materials;
/**
* @deprecated Use NamespacedKey version of constructor
*/
@Deprecated
public MaterialSetTag(@NotNull Predicate<Material> filter) {
this(null, Stream.of(Material.values()).filter(filter).collect(Collectors.toList()));
}
/**
* @deprecated Use NamespacedKey version of constructor
*/
@Deprecated
public MaterialSetTag(@NotNull Collection<Material> materials) {
this(null, materials);
}
/**
* @deprecated Use NamespacedKey version of constructor
*/
@Deprecated
public MaterialSetTag(@NotNull Material... materials) {
this(null, materials);
}
public MaterialSetTag(@Nullable NamespacedKey key, @NotNull Predicate<Material> filter) {
this(key, Stream.of(Material.values()).filter(filter).collect(Collectors.toList()));
}
public MaterialSetTag(@Nullable NamespacedKey key, @NotNull Material... materials) {
this(key, Lists.newArrayList(materials));
}
public MaterialSetTag(@Nullable NamespacedKey key, @NotNull Collection<Material> materials) {
this.key = key != null ? key : NamespacedKey.randomKey();
this.materials = Sets.newEnumSet(materials, Material.class);
}
@NotNull
@Override
public NamespacedKey getKey() {
return key;
}
@NotNull
public MaterialSetTag add(@NotNull Tag<Material>... tags) {
for (Tag<Material> tag : tags) {
add(tag.getValues());
}
return this;
}
@NotNull
public MaterialSetTag add(@NotNull MaterialSetTag... tags) {
for (Tag<Material> tag : tags) {
add(tag.getValues());
}
return this;
}
@NotNull
public MaterialSetTag add(@NotNull Material... material) {
this.materials.addAll(Lists.newArrayList(material));
return this;
}
@NotNull
public MaterialSetTag add(@NotNull Collection<Material> materials) {
this.materials.addAll(materials);
return this;
}
@NotNull
public MaterialSetTag contains(@NotNull String with) {
return add(mat -> mat.name().contains(with));
}
@NotNull
public MaterialSetTag endsWith(@NotNull String with) {
return add(mat -> mat.name().endsWith(with));
}
@NotNull
public MaterialSetTag startsWith(@NotNull String with) {
return add(mat -> mat.name().startsWith(with));
}
@NotNull
public MaterialSetTag add(@NotNull Predicate<Material> filter) {
add(Stream.of(Material.values()).filter(((Predicate<Material>) Material::isLegacy).negate()).filter(filter).collect(Collectors.toList()));
return this;
}
@NotNull
public MaterialSetTag not(@NotNull MaterialSetTag tags) {
not(tags.getValues());
return this;
}
@NotNull
public MaterialSetTag not(@NotNull Material... material) {
this.materials.removeAll(Lists.newArrayList(material));
return this;
}
@NotNull
public MaterialSetTag not(@NotNull Collection<Material> materials) {
this.materials.removeAll(materials);
return this;
}
@NotNull
public MaterialSetTag not(@NotNull Predicate<Material> filter) {
not(Stream.of(Material.values()).filter(((Predicate<Material>) Material::isLegacy).negate()).filter(filter).collect(Collectors.toList()));
return this;
}
@NotNull
public MaterialSetTag notEndsWith(@NotNull String with) {
return not(mat -> mat.name().endsWith(with));
}
@NotNull
public MaterialSetTag notStartsWith(@NotNull String with) {
return not(mat -> mat.name().startsWith(with));
}
@NotNull
public Set<Material> getValues() {
return this.materials;
}
public boolean isTagged(@NotNull BlockData block) {
return isTagged(block.getMaterial());
}
public boolean isTagged(@NotNull BlockState block) {
return isTagged(block.getType());
}
public boolean isTagged(@NotNull Block block) {
return isTagged(block.getType());
}
public boolean isTagged(@NotNull ItemStack item) {
return isTagged(item.getType());
}
public boolean isTagged(@NotNull Material material) {
return this.materials.contains(material);
}
@NotNull
public MaterialSetTag ensureSize(@NotNull String label, int size) {
long actual = this.materials.stream().filter(((Predicate<Material>) Material::isLegacy).negate()).count();
if (size != actual) {
throw new IllegalStateException(label + " - Expected " + size + " materials, got " + actual);
}
return this;
}
}

View File

@@ -0,0 +1,382 @@
/*
* Copyright (c) 2018 Daniel Ennis (Aikar) MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.destroystokyo.paper;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Tag;
/**
* Represents a collection tags to identify materials that share common properties.
* Will map to minecraft for missing tags, as well as custom ones that may be useful.
*/
@SuppressWarnings({"NonFinalUtilityClass", "unused", "WeakerAccess"})
public class MaterialTags {
private static NamespacedKey keyFor(String key) {
//noinspection deprecation
return new NamespacedKey("paper", key + "_settag");
}
public static final MaterialSetTag ARROWS = new MaterialSetTag(keyFor("arrows"))
.endsWith("ARROW")
.ensureSize("ARROWS", 3);
/**
* Cover all 16 colors of beds.
*/
public static final MaterialSetTag BEDS = new MaterialSetTag(keyFor("beds"))
.endsWith("_BED")
.ensureSize("BEDS", 16);
/**
* Covers all bucket items.
*/
public static final MaterialSetTag BUCKETS = new MaterialSetTag(keyFor("buckets"))
.endsWith("BUCKET")
.ensureSize("BUCKETS", 8);
/**
* Covers coal and charcoal.
*/
public static final MaterialSetTag COALS = new MaterialSetTag(keyFor("coals"))
.add(Material.COAL, Material.CHARCOAL);
/**
* Covers both cobblestone wall variants.
*/
public static final MaterialSetTag COBBLESTONE_WALLS = new MaterialSetTag(keyFor("cobblestone_walls"))
.endsWith("COBBLESTONE_WALL")
.ensureSize("COBBLESTONE_WALLS", 2);
/**
* Covers both cobblestone and mossy Cobblestone.
*/
public static final MaterialSetTag COBBLESTONES = new MaterialSetTag(keyFor("cobblestones"))
.add(Material.COBBLESTONE, Material.MOSSY_COBBLESTONE);
/**
* Covers all 16 colors of concrete.
*/
public static final MaterialSetTag CONCRETES = new MaterialSetTag(keyFor("concretes"))
.endsWith("_CONCRETE")
.ensureSize("CONCRETES", 16);
/**
* Covers all 16 colors of concrete powder.
*/
public static final MaterialSetTag CONCRETE_POWDER = new MaterialSetTag(keyFor("concrete_powder"))
.endsWith("_CONCRETE_POWDER")
.ensureSize("CONCRETE_POWDER", 16);
/**
* Covers the two types of cooked fish.
*/
public static final MaterialSetTag COOKED_FISH = new MaterialSetTag(keyFor("cooked_fish"))
.add(Material.COOKED_COD, Material.COOKED_SALMON);
/**
* Covers all 16 dyes.
*/
public static final MaterialSetTag DYES = new MaterialSetTag(keyFor("dyes"))
.endsWith("_DYE")
.add(Material.BONE_MEAL,
Material.CACTUS_GREEN,
Material.COCOA_BEANS,
Material.DANDELION_YELLOW,
Material.INK_SAC,
Material.LAPIS_LAZULI,
Material.ROSE_RED
)
.ensureSize("DYES", 16);
/**
* Covers all 6 wood variants of gates.
*/
public static final MaterialSetTag FENCE_GATES = new MaterialSetTag(keyFor("fence_gates"))
.endsWith("_GATE")
.ensureSize("FENCE_GATES", 6);
/**
* Covers all 6 wood variants and nether brick fence.
*/
public static final MaterialSetTag FENCES = new MaterialSetTag(keyFor("fences"))
.endsWith("_FENCE")
.ensureSize("FENCES", 7);
/**
* Covers all 4 variants of fish buckets.
*/
public static final MaterialSetTag FISH_BUCKETS = new MaterialSetTag(keyFor("fish_buckets"))
.add(Material.COD_BUCKET, Material.PUFFERFISH_BUCKET, Material.SALMON_BUCKET, Material.TROPICAL_FISH_BUCKET);
/**
* Covers the non-colored glass and 16 stained glass (not panes).
*/
public static final MaterialSetTag GLASS = new MaterialSetTag(keyFor("glass"))
.endsWith("_GLASS")
.add(Material.GLASS)
.ensureSize("GLASS", 17);
/**
* Covers the non-colored glass panes and 16 stained glass panes (panes only).
*/
public static final MaterialSetTag GLASS_PANES = new MaterialSetTag(keyFor("glass_panes"))
.endsWith("GLASS_PANE")
.ensureSize("GLASS_PANES", 17);
/**
* Covers all 16 glazed terracotta blocks.
*/
public static final MaterialSetTag GLAZED_TERRACOTTA = new MaterialSetTag(keyFor("glazed_terracotta"))
.endsWith("GLAZED_TERRACOTTA")
.ensureSize("GLAZED_TERRACOTTA", 16);
/**
* Covers the 16 colors of stained terracotta.
*/
public static final MaterialSetTag STAINED_TERRACOTTA = new MaterialSetTag(keyFor("stained_terracotta"))
.endsWith("TERRACOTTA")
.not(Material.TERRACOTTA)
.notEndsWith("GLAZED_TERRACOTTA")
.ensureSize("STAINED_TERRACOTTA", 16);
/**
* Covers terracotta along with the 16 stained variants.
*/
public static final MaterialSetTag TERRACOTTA = new MaterialSetTag(keyFor("terracotta"))
.endsWith("TERRACOTTA")
.ensureSize("TERRACOTTA", 33);
/**
* Covers both golden apples.
*/
public static final MaterialSetTag GOLDEN_APPLES = new MaterialSetTag(keyFor("golden_apples"))
.endsWith("GOLDEN_APPLE")
.ensureSize("GOLDEN_APPLES", 2);
/**
* Covers the 3 variants of horse armor.
*/
public static final MaterialSetTag HORSE_ARMORS = new MaterialSetTag(keyFor("horse_armors"))
.endsWith("_HORSE_ARMOR")
.ensureSize("HORSE_ARMORS", 3);
/**
* Covers the 6 variants of infested blocks.
*/
public static final MaterialSetTag INFESTED_BLOCKS = new MaterialSetTag(keyFor("infested_blocks"))
.startsWith("INFESTED_")
.ensureSize("INFESTED_BLOCKS", 6);
/**
* Covers the 3 variants of mushroom blocks.
*/
public static final MaterialSetTag MUSHROOM_BLOCKS = new MaterialSetTag(keyFor("mushroom_blocks"))
.endsWith("MUSHROOM_BLOCK")
.add(Material.MUSHROOM_STEM)
.ensureSize("MUSHROOM_BLOCKS", 3);
/**
* Covers both mushrooms.
*/
public static final MaterialSetTag MUSHROOMS = new MaterialSetTag(keyFor("mushrooms"))
.add(Material.BROWN_MUSHROOM, Material.RED_MUSHROOM);
/**
* Covers all 12 music disc items.
*/
public static final MaterialSetTag MUSIC_DISCS = new MaterialSetTag(keyFor("music_discs"))
.startsWith("MUSIC_DISC_")
.ensureSize("MUSIC_DISCS", 12);
/**
* Covers all 8 ores.
*/
public static final MaterialSetTag ORES = new MaterialSetTag(keyFor("ores"))
.endsWith("_ORE")
.ensureSize("ORES", 8);
/**
* Covers all piston typed items and blocks including the piston head and moving piston.
*/
public static final MaterialSetTag PISTONS = new MaterialSetTag(keyFor("pistons"))
.contains("PISTON")
.ensureSize("PISTONS", 4);
/**
* Covers all potato items.
*/
public static final MaterialSetTag POTATOES = new MaterialSetTag(keyFor("potatoes"))
.endsWith("POTATO")
.ensureSize("POTATOES", 3);
/**
* Covers all 6 wooden pressure plates and the 2 weighted pressure plates and 1 stone pressure plate.
*/
public static final MaterialSetTag PRESSURE_PLATES = new MaterialSetTag(keyFor("pressure_plates"))
.endsWith("_PRESSURE_PLATE")
.ensureSize("PRESSURE_PLATES", 9);
/**
* Covers the 3 variants of prismarine blocks.
*/
public static final MaterialSetTag PRISMARINE = new MaterialSetTag(keyFor("prismarine"))
.add(Material.PRISMARINE, Material.PRISMARINE_BRICKS, Material.DARK_PRISMARINE);
/**
* Covers the 3 variants of prismarine slabs.
*/
public static final MaterialSetTag PRISMARINE_SLABS = new MaterialSetTag(keyFor("prismarine_slabs"))
.add(Material.PRISMARINE_SLAB, Material.PRISMARINE_BRICK_SLAB, Material.DARK_PRISMARINE_SLAB);
/**
* Covers the 3 variants of prismarine stairs.
*/
public static final MaterialSetTag PRISMARINE_STAIRS = new MaterialSetTag(keyFor("prismarine_stairs"))
.add(Material.PRISMARINE_STAIRS, Material.PRISMARINE_BRICK_STAIRS, Material.DARK_PRISMARINE_STAIRS);
/**
* Covers the 3 variants of pumpkins.
*/
public static final MaterialSetTag PUMPKINS = new MaterialSetTag(keyFor("pumpkins"))
.add(Material.CARVED_PUMPKIN, Material.JACK_O_LANTERN, Material.PUMPKIN);
/**
* Covers the 4 variants of quartz blocks.
*/
public static final MaterialSetTag QUARTZ_BLOCKS = new MaterialSetTag(keyFor("quartz_blocks"))
.add(Material.QUARTZ_BLOCK, Material.QUARTZ_PILLAR, Material.CHISELED_QUARTZ_BLOCK, Material.SMOOTH_QUARTZ);
/**
* Covers all uncooked fish items.
*/
public static final MaterialSetTag RAW_FISH = new MaterialSetTag(keyFor("raw_fish"))
.add(Material.COD, Material.PUFFERFISH, Material.SALMON, Material.TROPICAL_FISH);
/**
* Covers the 4 variants of red sandstone blocks.
*/
public static final MaterialSetTag RED_SANDSTONES = new MaterialSetTag(keyFor("red_sandstones"))
.endsWith("RED_SANDSTONE")
.ensureSize("RED_SANDSTONES", 4);
/**
* Covers the 4 variants of sandstone blocks.
*/
public static final MaterialSetTag SANDSTONES = new MaterialSetTag(keyFor("sandstones"))
.add(Material.SANDSTONE, Material.CHISELED_SANDSTONE, Material.CUT_SANDSTONE, Material.SMOOTH_SANDSTONE);
/**
* Covers sponge and wet sponge.
*/
public static final MaterialSetTag SPONGES = new MaterialSetTag(keyFor("sponges"))
.endsWith("SPONGE")
.ensureSize("SPONGES", 2);
/**
* Covers the non-colored and 16 colored shulker boxes.
*/
public static final MaterialSetTag SHULKER_BOXES = new MaterialSetTag(keyFor("shulker_boxes"))
.endsWith("SHULKER_BOX")
.ensureSize("SHULKER_BOXES", 17);
/**
* Covers zombie, creeper, skeleton, dragon, and player heads.
*/
public static final MaterialSetTag SKULLS = new MaterialSetTag(keyFor("skulls"))
.endsWith("_HEAD")
.endsWith("_SKULL")
.not(Material.PISTON_HEAD)
.ensureSize("SKULLS", 12);
/**
* Covers all spawn egg items.
*/
public static final MaterialSetTag SPAWN_EGGS = new MaterialSetTag(keyFor("spawn_eggs"))
.endsWith("_SPAWN_EGG")
.ensureSize("SPAWN_EGGS", 51);
/**
* Covers all 16 colors of stained glass.
*/
public static final MaterialSetTag STAINED_GLASS = new MaterialSetTag(keyFor("stained_glass"))
.endsWith("_STAINED_GLASS")
.ensureSize("STAINED_GLASS", 16);
/**
* Covers all 16 colors of stained glass panes.
*/
public static final MaterialSetTag STAINED_GLASS_PANES = new MaterialSetTag(keyFor("stained_glass_panes"))
.endsWith("STAINED_GLASS_PANE")
.ensureSize("STAINED_GLASS_PANES", 16);
/**
* Covers all 7 variants of trapdoors.
*/
public static final MaterialSetTag TRAPDOORS = new MaterialSetTag(keyFor("trapdoors"))
.endsWith("_TRAPDOOR")
.ensureSize("TRAPDOORS", 7);
/**
* Covers all 6 wood variants of fences.
*/
public static final MaterialSetTag WOODEN_FENCES = new MaterialSetTag(keyFor("wooden_fences"))
.endsWith("_FENCE")
.not(Material.NETHER_BRICK_FENCE)
.ensureSize("WOODEN_FENCES", 6);
/**
* Covers all 6 wood variants of trapdoors.
*/
public static final MaterialSetTag WOODEN_TRAPDOORS = new MaterialSetTag(keyFor("wooden_trapdoors"))
.endsWith("_TRAPDOOR")
.not(Material.IRON_TRAPDOOR)
.ensureSize("WOODEN_TRAPDOORS", 6);
public static final MaterialSetTag WOODEN_GATES = new MaterialSetTag(keyFor("wooden_gates"))
.endsWith("_GATE")
.ensureSize("WOODEN_GATES", 6);
public static final MaterialSetTag PURPUR = new MaterialSetTag(keyFor("purpur"))
.startsWith("PURPUR_")
.ensureSize("PURPUR", 4);
public static final MaterialSetTag SIGNS = new MaterialSetTag(keyFor("signs"))
.add(Material.SIGN, Material.WALL_SIGN)
.ensureSize("SIGNS", 2);
public static final MaterialSetTag TORCH = new MaterialSetTag(keyFor("torch"))
.add(Material.TORCH, Material.WALL_TORCH)
.ensureSize("TORCH", 2);
public static final MaterialSetTag REDSTONE_TORCH = new MaterialSetTag(keyFor("restone_torch"))
.add(Material.REDSTONE_TORCH, Material.REDSTONE_WALL_TORCH)
.ensureSize("REDSTONE_TORCH", 2);
@SuppressWarnings("unchecked")
public static final MaterialSetTag COLORABLE = new MaterialSetTag(keyFor("colorable"))
.add(Tag.WOOL, Tag.CARPETS).add(SHULKER_BOXES, STAINED_GLASS, STAINED_GLASS_PANES, CONCRETES, BEDS);
//.ensureSize("COLORABLE", 81); unit test don't have the vanilla item tags, so counts don't line up for real
}

View File

@@ -0,0 +1,40 @@
package com.destroystokyo.paper;
import org.jetbrains.annotations.NotNull;
/**
* Represents a namespaced resource, see {@link org.bukkit.NamespacedKey} for single elements
* or {@link com.destroystokyo.paper.NamespacedTag} for a collection of elements
*
* Namespaces may only contain lowercase alphanumeric characters, periods,
* underscores, and hyphens.
* <p>
* Keys may only contain lowercase alphanumeric characters, periods,
* underscores, hyphens, and forward slashes.
* <p>
* You should not be implementing this interface yourself, use {@link org.bukkit.NamespacedKey}
* or {@link com.destroystokyo.paper.NamespacedTag} as needed instead.
*/
public interface Namespaced {
/**
* Gets the namespace this resource is a part of
* <p>
* This is contractually obligated to only contain lowercase alphanumeric characters,
* periods, underscores, and hyphens.
*
* @return resource namespace
*/
@NotNull
String getNamespace();
/**
* Gets the key corresponding to this resource
* <p>
* This is contractually obligated to only contain lowercase alphanumeric characters,
* periods, underscores, hyphens, and forward slashes.
*
* @return resource key
*/
@NotNull
String getKey();
}

View File

@@ -0,0 +1,142 @@
package com.destroystokyo.paper;
import com.google.common.base.Preconditions;
import java.util.Locale;
import java.util.UUID;
import java.util.regex.Pattern;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
/**
* Represents a String based key pertaining to a tagged entry. Consists of two components - a namespace
* and a key.
* <p>
* Namespaces may only contain lowercase alphanumeric characters, periods,
* underscores, and hyphens.
* <p>
* Keys may only contain lowercase alphanumeric characters, periods,
* underscores, hyphens, and forward slashes.
*
*/
// Paper - entire class, based on org.bukkit.NamespacedKey
public final class NamespacedTag implements com.destroystokyo.paper.Namespaced {
/**
* The namespace representing all inbuilt keys.
*/
public static final String MINECRAFT = "minecraft";
/**
* The namespace representing all keys generated by Bukkit for backwards
* compatibility measures.
*/
public static final String BUKKIT = "bukkit";
//
private static final Pattern VALID_NAMESPACE = Pattern.compile("[a-z0-9._-]+");
private static final Pattern VALID_KEY = Pattern.compile("[a-z0-9/._-]+");
//
private final String namespace;
private final String key;
/**
* Create a key in a specific namespace.
*
* @param namespace String representing a grouping of keys
* @param key Name for this specific key
* @deprecated should never be used by plugins, for internal use only!!
*/
@Deprecated
public NamespacedTag(@NotNull String namespace, @NotNull String key) {
Preconditions.checkArgument(namespace != null && VALID_NAMESPACE.matcher(namespace).matches(), "Invalid namespace. Must be [a-z0-9._-]: %s", namespace);
Preconditions.checkArgument(key != null && VALID_KEY.matcher(key).matches(), "Invalid key. Must be [a-z0-9/._-]: %s", key);
this.namespace = namespace;
this.key = key;
String string = toString();
Preconditions.checkArgument(string.length() < 256, "NamespacedTag must be less than 256 characters", string);
}
/**
* Create a key in the plugin's namespace.
* <p>
* Namespaces may only contain lowercase alphanumeric characters, periods,
* underscores, and hyphens.
* <p>
* Keys may only contain lowercase alphanumeric characters, periods,
* underscores, hyphens, and forward slashes.
*
* @param plugin the plugin to use for the namespace
* @param key the key to create
*/
public NamespacedTag(@NotNull Plugin plugin, @NotNull String key) {
Preconditions.checkArgument(plugin != null, "Plugin cannot be null");
Preconditions.checkArgument(key != null, "Key cannot be null");
this.namespace = plugin.getName().toLowerCase(Locale.ROOT);
this.key = key.toLowerCase().toLowerCase(Locale.ROOT);
// Check validity after normalization
Preconditions.checkArgument(VALID_NAMESPACE.matcher(this.namespace).matches(), "Invalid namespace. Must be [a-z0-9._-]: %s", this.namespace);
Preconditions.checkArgument(VALID_KEY.matcher(this.key).matches(), "Invalid key. Must be [a-z0-9/._-]: %s", this.key);
String string = toString();
Preconditions.checkArgument(string.length() < 256, "NamespacedTag must be less than 256 characters (%s)", string);
}
@NotNull
public String getNamespace() {
return namespace;
}
@NotNull
public String getKey() {
return key;
}
@Override
public int hashCode() {
int hash = 7;
hash = 47 * hash + this.namespace.hashCode();
hash = 47 * hash + this.key.hashCode();
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final NamespacedTag other = (NamespacedTag) obj;
return this.namespace.equals(other.namespace) && this.key.equals(other.key);
}
@Override
public String toString() {
return "#" + this.namespace + ":" + this.key;
}
/**
* Return a new random key in the {@link #BUKKIT} namespace.
*
* @return new key
* @deprecated should never be used by plugins, for internal use only!!
*/
@Deprecated
public static NamespacedTag randomKey() {
return new NamespacedTag(BUKKIT, UUID.randomUUID().toString());
}
/**
* Get a key in the Minecraft namespace.
*
* @param key the key to use
* @return new key in the Minecraft namespace
*/
@NotNull
public static NamespacedTag minecraft(@NotNull String key) {
return new NamespacedTag(MINECRAFT, key);
}
}

View File

@@ -0,0 +1,478 @@
package com.destroystokyo.paper;
import com.google.common.collect.Lists;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.util.NumberConversions;
import java.util.Collection;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Helps prepare a particle to be sent to players.
*
* Usage of the builder is preferred over the super long {@link World#spawnParticle(Particle, Location, int, double, double, double, double, Object)} API
*/
public class ParticleBuilder {
private Particle particle;
private List<Player> receivers;
private Player source;
private Location location;
private int count = 1;
private double offsetX = 0, offsetY = 0, offsetZ = 0;
private double extra = 1;
private Object data;
private boolean force = true;
public ParticleBuilder(@NotNull Particle particle) {
this.particle = particle;
}
/**
* Sends the particle to all receiving players (or all). This method is safe to use
* Asynchronously
*
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder spawn() {
if (this.location == null) {
throw new IllegalStateException("Please specify location for this particle");
}
location.getWorld().spawnParticle(particle, receivers, source,
location.getX(), location.getY(), location.getZ(),
count, offsetX, offsetY, offsetZ, extra, data, force
);
return this;
}
/**
* @return The particle going to be sent
*/
@NotNull
public Particle particle() {
return particle;
}
/**
* Changes what particle will be sent
*
* @param particle The particle
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder particle(@NotNull Particle particle) {
this.particle = particle;
return this;
}
/**
* @return List of players who will receive the particle, or null for all in world
*/
@Nullable
public List<Player> receivers() {
return receivers;
}
/**
* Example use:
*
* builder.receivers(16); if (builder.hasReceivers()) { sendParticleAsync(builder); }
*
* @return If this particle is going to be sent to someone
*/
public boolean hasReceivers() {
return (receivers == null && !location.getWorld().getPlayers().isEmpty()) || (
receivers != null && !receivers.isEmpty());
}
/**
* Sends this particle to all players in the world. This is rather silly and you should likely not
* be doing this.
*
* Just be a logical person and use receivers by radius or collection.
*
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder allPlayers() {
this.receivers = null;
return this;
}
/**
* @param receivers List of players to receive this particle, or null for all players in the
* world
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder receivers(@Nullable List<Player> receivers) {
// Had to keep this as we first made API List<> and not Collection, but removing this may break plugins compiled on older jars
// TODO: deprecate?
this.receivers = receivers != null ? Lists.newArrayList(receivers) : null;
return this;
}
/**
* @param receivers List of players to receive this particle, or null for all players in the
* world
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder receivers(@Nullable Collection<Player> receivers) {
this.receivers = receivers != null ? Lists.newArrayList(receivers) : null;
return this;
}
/**
* @param receivers List of players to be receive this particle, or null for all players in the
* world
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder receivers(@Nullable Player... receivers) {
this.receivers = receivers != null ? Lists.newArrayList(receivers) : null;
return this;
}
/**
* Selects all players within a cuboid selection around the particle location, within the
* specified bounding box. If you want a more spherical check, see {@link #receivers(int,
* boolean)}
*
* @param radius amount to add on all axis
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder receivers(int radius) {
return receivers(radius, radius);
}
/**
* Selects all players within the specified radius around the particle location. If byDistance is
* false, behavior uses cuboid selection the same as {@link #receivers(int, int)} If byDistance is
* true, radius is tested by distance in a spherical shape
*
* @param radius amount to add on each axis
* @param byDistance true to use a spherical radius, false to use a cuboid
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder receivers(int radius, boolean byDistance) {
if (!byDistance) {
return receivers(radius, radius, radius);
} else {
this.receivers = Lists.newArrayList();
for (Player nearbyPlayer : location.getWorld()
.getNearbyPlayers(location, radius, radius, radius)) {
Location loc = nearbyPlayer.getLocation();
double x = NumberConversions.square(location.getX() - loc.getX());
double y = NumberConversions.square(location.getY() - loc.getY());
double z = NumberConversions.square(location.getZ() - loc.getZ());
if (Math.sqrt(x + y + z) > radius) {
continue;
}
this.receivers.add(nearbyPlayer);
}
return this;
}
}
/**
* Selects all players within a cuboid selection around the particle location, within the
* specified bounding box. Allows specifying a different Y size than X and Z If you want a more
* cylinder check, see {@link #receivers(int, int, boolean)} If you want a more spherical check,
* see {@link #receivers(int, boolean)}
*
* @param xzRadius amount to add on the x/z axis
* @param yRadius amount to add on the y axis
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder receivers(int xzRadius, int yRadius) {
return receivers(xzRadius, yRadius, xzRadius);
}
/**
* Selects all players within the specified radius around the particle location. If byDistance is
* false, behavior uses cuboid selection the same as {@link #receivers(int, int)} If byDistance is
* true, radius is tested by distance on the y plane and on the x/z plane, in a cylinder shape.
*
* @param xzRadius amount to add on the x/z axis
* @param yRadius amount to add on the y axis
* @param byDistance true to use a cylinder shape, false to use cuboid
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder receivers(int xzRadius, int yRadius, boolean byDistance) {
if (!byDistance) {
return receivers(xzRadius, yRadius, xzRadius);
} else {
this.receivers = Lists.newArrayList();
for (Player nearbyPlayer : location.getWorld()
.getNearbyPlayers(location, xzRadius, yRadius, xzRadius)) {
Location loc = nearbyPlayer.getLocation();
if (Math.abs(loc.getY() - this.location.getY()) > yRadius) {
continue;
}
double x = NumberConversions.square(location.getX() - loc.getX());
double z = NumberConversions.square(location.getZ() - loc.getZ());
if (x + z > NumberConversions.square(xzRadius)) {
continue;
}
this.receivers.add(nearbyPlayer);
}
return this;
}
}
/**
* Selects all players within a cuboid selection around the particle location, within the
* specified bounding box. If you want a more cylinder check, see {@link #receivers(int, int,
* boolean)} If you want a more spherical check, see {@link #receivers(int, boolean)}
*
* @param xRadius amount to add on the x axis
* @param yRadius amount to add on the y axis
* @param zRadius amount to add on the z axis
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder receivers(int xRadius, int yRadius, int zRadius) {
if (location == null) {
throw new IllegalStateException("Please set location first");
}
return receivers(location.getWorld().getNearbyPlayers(location, xRadius, yRadius, zRadius));
}
/**
* @return The player considered the source of this particle (for Visibility concerns), or null
*/
@Nullable
public Player source() {
return source;
}
/**
* Sets the source of this particle for visibility concerns (Vanish API)
*
* @param source The player who is considered the source
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder source(@Nullable Player source) {
this.source = source;
return this;
}
/**
* @return Location of where the particle will spawn
*/
@Nullable
public Location location() {
return location;
}
/**
* Sets the location of where to spawn the particle
*
* @param location The location of the particle
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder location(@NotNull Location location) {
this.location = location.clone();
return this;
}
/**
* Sets the location of where to spawn the particle
*
* @param world World to spawn particle in
* @param x X location
* @param y Y location
* @param z Z location
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder location(@NotNull World world, double x, double y, double z) {
this.location = new Location(world, x, y, z);
return this;
}
/**
* @return Number of particles to spawn
*/
public int count() {
return count;
}
/**
* Sets the number of particles to spawn
*
* @param count Number of particles
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder count(int count) {
this.count = count;
return this;
}
/**
* Particle offset X. Varies by particle on how this is used
*
* @return Particle offset X.
*/
public double offsetX() {
return offsetX;
}
/**
* Particle offset Y. Varies by particle on how this is used
*
* @return Particle offset Y.
*/
public double offsetY() {
return offsetY;
}
/**
* Particle offset Z. Varies by particle on how this is used
*
* @return Particle offset Z.
*/
public double offsetZ() {
return offsetZ;
}
/**
* Sets the particle offset. Varies by particle on how this is used
*
* @param offsetX Particle offset X
* @param offsetY Particle offset Y
* @param offsetZ Particle offset Z
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder offset(double offsetX, double offsetY, double offsetZ) {
this.offsetX = offsetX;
this.offsetY = offsetY;
this.offsetZ = offsetZ;
return this;
}
/**
* Gets the Particle extra data. Varies by particle on how this is used
*
* @return the extra particle data
*/
public double extra() {
return extra;
}
/**
* Sets the particle extra data. Varies by particle on how this is used
*
* @param extra the extra particle data
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder extra(double extra) {
this.extra = extra;
return this;
}
/**
* Gets the particle custom data. Varies by particle on how this is used
*
* @param <T> The Particle data type
* @return the ParticleData for this particle
*/
@Nullable
public <T> T data() {
//noinspection unchecked
return (T) data;
}
/**
* Sets the particle custom data. Varies by particle on how this is used
*
* @param data The new particle data
* @param <T> The Particle data type
* @return a reference to this object.
*/
@NotNull
public <T> ParticleBuilder data(@Nullable T data) {
this.data = data;
return this;
}
/**
* Sets whether the particle is forcefully shown to the player. If forced, the particle will show
* faraway, as far as the player's view distance allows. If false, the particle will show
* according to the client's particle settings.
*
* @param force true to force, false for normal
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder force(boolean force) {
this.force = force;
return this;
}
/**
* Sets the particle Color. Only valid for REDSTONE.
*
* @param color the new particle color
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder color(@Nullable Color color) {
return color(color, 1);
}
/**
* Sets the particle Color and size. Only valid for REDSTONE.
*
* @param color the new particle color
* @param size the size of the particle
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder color(@Nullable Color color, float size) {
if (particle != Particle.REDSTONE && color != null) {
throw new IllegalStateException("Color may only be set on REDSTONE");
}
// We don't officially support reusing these objects, but here we go
if (color == null) {
if (data instanceof Particle.DustOptions) {
return data(null);
} else {
return this;
}
}
return data(new Particle.DustOptions(color, size));
}
/**
* Sets the particle Color.
* Only valid for REDSTONE.
* @param r red color component
* @param g green color component
* @param b blue color component
* @return a reference to this object.
*/
@NotNull
public ParticleBuilder color(int r, int g, int b) {
return color(Color.fromRGB(r, g, b));
}
}

View File

@@ -0,0 +1,373 @@
package com.destroystokyo.paper;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
/**
* Represents a title to may be sent to a {@link Player}.
*
* <p>A title can be sent without subtitle text.</p>
*/
public final class Title {
/**
* The default number of ticks for the title to fade in.
*/
public static final int DEFAULT_FADE_IN = 20;
/**
* The default number of ticks for the title to stay.
*/
public static final int DEFAULT_STAY = 200;
/**
* The default number of ticks for the title to fade out.
*/
public static final int DEFAULT_FADE_OUT = 20;
private final BaseComponent[] title;
private final BaseComponent[] subtitle;
private final int fadeIn;
private final int stay;
private final int fadeOut;
/**
* Create a title with the default time values and no subtitle.
*
* <p>Times use default values.</p>
*
* @param title the main text of the title
* @throws NullPointerException if the title is null
*/
public Title(@NotNull BaseComponent title) {
this(title, null);
}
/**
* Create a title with the default time values and no subtitle.
*
* <p>Times use default values.</p>
*
* @param title the main text of the title
* @throws NullPointerException if the title is null
*/
public Title(@NotNull BaseComponent[] title) {
this(title, null);
}
/**
* Create a title with the default time values and no subtitle.
*
* <p>Times use default values.</p>
*
* @param title the main text of the title
* @throws NullPointerException if the title is null
*/
public Title(@NotNull String title) {
this(title, null);
}
/**
* Create a title with the default time values.
*
* <p>Times use default values.</p>
*
* @param title the main text of the title
* @param subtitle the secondary text of the title
*/
public Title(@NotNull BaseComponent title, @Nullable BaseComponent subtitle) {
this(title, subtitle, DEFAULT_FADE_IN, DEFAULT_STAY, DEFAULT_FADE_OUT);
}
/**
* Create a title with the default time values.
*
* <p>Times use default values.</p>
*
* @param title the main text of the title
* @param subtitle the secondary text of the title
*/
public Title(@NotNull BaseComponent[] title, @Nullable BaseComponent[] subtitle) {
this(title, subtitle, DEFAULT_FADE_IN, DEFAULT_STAY, DEFAULT_FADE_OUT);
}
/**
* Create a title with the default time values.
*
* <p>Times use default values.</p>
*
* @param title the main text of the title
* @param subtitle the secondary text of the title
*/
public Title(@NotNull String title, @Nullable String subtitle) {
this(title, subtitle, DEFAULT_FADE_IN, DEFAULT_STAY, DEFAULT_FADE_OUT);
}
/**
* Creates a new title.
*
* @param title the main text of the title
* @param subtitle the secondary text of the title
* @param fadeIn the number of ticks for the title to fade in
* @param stay the number of ticks for the title to stay on screen
* @param fadeOut the number of ticks for the title to fade out
* @throws IllegalArgumentException if any of the times are negative
*/
public Title(@NotNull BaseComponent title, @Nullable BaseComponent subtitle, int fadeIn, int stay, int fadeOut) {
this(
new BaseComponent[]{checkNotNull(title, "title")},
subtitle == null ? null : new BaseComponent[]{subtitle},
fadeIn,
stay,
fadeOut
);
}
/**
* Creates a new title.
*
* @param title the main text of the title
* @param subtitle the secondary text of the title
* @param fadeIn the number of ticks for the title to fade in
* @param stay the number of ticks for the title to stay on screen
* @param fadeOut the number of ticks for the title to fade out
* @throws IllegalArgumentException if any of the times are negative
*/
public Title(@Nullable BaseComponent[] title, @NotNull BaseComponent[] subtitle, int fadeIn, int stay, int fadeOut) {
checkArgument(fadeIn >= 0, "Negative fadeIn: %s", fadeIn);
checkArgument(stay >= 0, "Negative stay: %s", stay);
checkArgument(fadeOut >= 0, "Negative fadeOut: %s", fadeOut);
this.title = checkNotNull(title, "title");
this.subtitle = subtitle;
this.fadeIn = fadeIn;
this.stay = stay;
this.fadeOut = fadeOut;
}
/**
* Creates a new title.
*
* <p>It is recommended to the {@link BaseComponent} constrctors.</p>
*
* @param title the main text of the title
* @param subtitle the secondary text of the title
* @param fadeIn the number of ticks for the title to fade in
* @param stay the number of ticks for the title to stay on screen
* @param fadeOut the number of ticks for the title to fade out
*/
public Title(@NotNull String title, @Nullable String subtitle, int fadeIn, int stay, int fadeOut) {
this(
TextComponent.fromLegacyText(checkNotNull(title, "title")),
subtitle == null ? null : TextComponent.fromLegacyText(subtitle),
fadeIn,
stay,
fadeOut
);
}
/**
* Gets the text of this title
*
* @return the text
*/
@NotNull
public BaseComponent[] getTitle() {
return this.title;
}
/**
* Gets the text of this title's subtitle
*
* @return the text
*/
@Nullable
public BaseComponent[] getSubtitle() {
return this.subtitle;
}
/**
* Gets the number of ticks to fade in.
*
* <p>The returned value is never negative.</p>
*
* @return the number of ticks to fade in
*/
public int getFadeIn() {
return this.fadeIn;
}
/**
* Gets the number of ticks to stay.
*
* <p>The returned value is never negative.</p>
*
* @return the number of ticks to stay
*/
public int getStay() {
return this.stay;
}
/**
* Gets the number of ticks to fade out.
*
* <p>The returned value is never negative.</p>
*
* @return the number of ticks to fade out
*/
public int getFadeOut() {
return this.fadeOut;
}
@NotNull
public static Builder builder() {
return new Builder();
}
/**
* A builder for creating titles
*/
public static final class Builder {
private BaseComponent[] title;
private BaseComponent[] subtitle;
private int fadeIn = DEFAULT_FADE_IN;
private int stay = DEFAULT_STAY;
private int fadeOut = DEFAULT_FADE_OUT;
/**
* Sets the title to the given text.
*
* @param title the title text
* @return this builder instance
* @throws NullPointerException if the title is null
*/
@NotNull
public Builder title(@NotNull BaseComponent title) {
return this.title(new BaseComponent[]{checkNotNull(title, "title")});
}
/**
* Sets the title to the given text.
*
* @param title the title text
* @return this builder instance
* @throws NullPointerException if the title is null
*/
@NotNull
public Builder title(@NotNull BaseComponent[] title) {
this.title = checkNotNull(title, "title");
return this;
}
/**
* Sets the title to the given text.
*
* <p>It is recommended to the {@link BaseComponent} methods.</p>
*
* @param title the title text
* @return this builder instance
* @throws NullPointerException if the title is null
*/
@NotNull
public Builder title(@NotNull String title) {
return this.title(TextComponent.fromLegacyText(checkNotNull(title, "title")));
}
/**
* Sets the subtitle to the given text.
*
* @param subtitle the title text
* @return this builder instance
*/
@NotNull
public Builder subtitle(@Nullable BaseComponent subtitle) {
return this.subtitle(subtitle == null ? null : new BaseComponent[]{subtitle});
}
/**
* Sets the subtitle to the given text.
*
* @param subtitle the title text
* @return this builder instance
*/
@NotNull
public Builder subtitle(@Nullable BaseComponent[] subtitle) {
this.subtitle = subtitle;
return this;
}
/**
* Sets the subtitle to the given text.
*
* <p>It is recommended to the {@link BaseComponent} methods.</p>
*
* @param subtitle the title text
* @return this builder instance
*/
@NotNull
public Builder subtitle(@Nullable String subtitle) {
return this.subtitle(subtitle == null ? null : TextComponent.fromLegacyText(subtitle));
}
/**
* Sets the number of ticks for the title to fade in
*
* @param fadeIn the number of ticks to fade in
* @return this builder instance
* @throws IllegalArgumentException if it is negative
*/
@NotNull
public Builder fadeIn(int fadeIn) {
checkArgument(fadeIn >= 0, "Negative fadeIn: %s", fadeIn);
this.fadeIn = fadeIn;
return this;
}
/**
* Sets the number of ticks for the title to stay.
*
* @param stay the number of ticks to stay
* @return this builder instance
* @throws IllegalArgumentException if it is negative
*/
@NotNull
public Builder stay(int stay) {
checkArgument(stay >= 0, "Negative stay: %s", stay);
this.stay = stay;
return this;
}
/**
* Sets the number of ticks for the title to fade out.
*
* @param fadeOut the number of ticks to fade out
* @return this builder instance
* @throws IllegalArgumentException if it is negative
*/
@NotNull
public Builder fadeOut(int fadeOut) {
checkArgument(fadeOut >= 0, "Negative fadeOut: %s", fadeOut);
this.fadeOut = fadeOut;
return this;
}
/**
* Create a title based on the values in the builder.
*
* @return a title from the values in this builder
* @throws IllegalStateException if title isn't specified
*/
@NotNull
public Title build() {
checkState(title != null, "Title not specified");
return new Title(this.title, this.subtitle, this.fadeIn, this.stay, this.fadeOut);
}
}
}

View File

@@ -0,0 +1,144 @@
package com.destroystokyo.paper;
import com.google.common.base.MoreObjects;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public enum VersionHistoryManager {
INSTANCE;
private final Gson gson = new Gson();
private final Logger logger = Bukkit.getLogger();
private VersionData currentData = null;
VersionHistoryManager() {
final Path path = Paths.get("version_history.json");
if (Files.exists(path)) {
// Basic file santiy checks
if (!Files.isRegularFile(path)) {
if (Files.isDirectory(path)) {
logger.severe(path + " is a directory, cannot be used for version history");
} else {
logger.severe(path + " is not a regular file, cannot be used for version history");
}
// We can't continue
return;
}
try (final BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
currentData = gson.fromJson(reader, VersionData.class);
} catch (final IOException e) {
logger.log(Level.SEVERE, "Failed to read version history file '" + path + "'", e);
return;
} catch (final JsonSyntaxException e) {
logger.log(Level.SEVERE, "Invalid json syntax for file '" + path + "'", e);
return;
}
final String version = Bukkit.getVersion();
if (version == null) {
logger.severe("Failed to retrieve current version");
return;
}
if (!version.equals(currentData.getCurrentVersion())) {
// The version appears to have changed
currentData.setOldVersion(currentData.getCurrentVersion());
currentData.setCurrentVersion(version);
writeFile(path);
}
} else {
// File doesn't exist, start fresh
currentData = new VersionData();
// oldVersion is null
currentData.setCurrentVersion(Bukkit.getVersion());
writeFile(path);
}
}
private void writeFile(@NotNull final Path path) {
try (final BufferedWriter writer = Files.newBufferedWriter(
path,
StandardCharsets.UTF_8,
StandardOpenOption.WRITE,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING
)) {
gson.toJson(currentData, writer);
} catch (final IOException e) {
logger.log(Level.SEVERE, "Failed to write to version history file", e);
}
}
@Nullable
public VersionData getVersionData() {
return currentData;
}
public static class VersionData {
private String oldVersion;
private String currentVersion;
@Nullable
public String getOldVersion() {
return oldVersion;
}
public void setOldVersion(@Nullable String oldVersion) {
this.oldVersion = oldVersion;
}
@Nullable
public String getCurrentVersion() {
return currentVersion;
}
public void setCurrentVersion(@Nullable String currentVersion) {
this.currentVersion = currentVersion;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("oldVersion", oldVersion)
.add("currentVersion", currentVersion)
.toString();
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final VersionData versionData = (VersionData) o;
return Objects.equals(oldVersion, versionData.oldVersion) &&
Objects.equals(currentVersion, versionData.currentVersion);
}
@Override
public int hashCode() {
return Objects.hash(oldVersion, currentVersion);
}
}
}

View File

@@ -0,0 +1,54 @@
package com.destroystokyo.paper.block;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.jetbrains.annotations.NotNull;
/**
* Represents information about a targeted block
*/
public class TargetBlockInfo {
private final Block block;
private final BlockFace blockFace;
public TargetBlockInfo(@NotNull Block block, @NotNull BlockFace blockFace) {
this.block = block;
this.blockFace = blockFace;
}
/**
* Get the block that is targeted
*
* @return Targeted block
*/
@NotNull
public Block getBlock() {
return block;
}
/**
* Get the targeted BlockFace
*
* @return Targeted blockface
*/
@NotNull
public BlockFace getBlockFace() {
return blockFace;
}
/**
* Get the relative Block to the targeted block on the side it is targeted at
*
* @return Block relative to targeted block
*/
@NotNull
public Block getRelativeBlock() {
return block.getRelative(blockFace);
}
public enum FluidMode {
NEVER,
SOURCE_ONLY,
ALWAYS
}
}

View File

@@ -0,0 +1,170 @@
package com.destroystokyo.paper.entity;
import org.bukkit.Location;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Mob;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Handles pathfinding operations for an Entity
*/
public interface Pathfinder {
/**
*
* @return The entity that is controlled by this pathfinder
*/
@NotNull
Mob getEntity();
/**
* Instructs the Entity to stop trying to navigate to its current desired location
*/
void stopPathfinding();
/**
* If the entity is currently trying to navigate to a destination, this will return true
* @return true if the entity is navigating to a destination
*/
boolean hasPath();
/**
* @return The location the entity is trying to navigate to, or null if there is no destination
*/
@Nullable
PathResult getCurrentPath();
/**
* Calculates a destination for the Entity to navigate to, but does not set it
* as the current target. Useful for calculating what would happen before setting it.
* @param loc Location to navigate to
* @return The closest Location the Entity can get to for this navigation, or null if no path could be calculated
*/
@Nullable PathResult findPath(@NotNull Location loc);
/**
* Calculates a destination for the Entity to navigate to to reach the target entity,
* but does not set it as the current target.
* Useful for calculating what would happen before setting it.
*
* The behavior of this PathResult is subject to the games pathfinding rules, and may
* result in the pathfinding automatically updating to follow the target Entity.
*
* However, this behavior is not guaranteed, and is subject to the games behavior.
*
* @param target the Entity to navigate to
* @return The closest Location the Entity can get to for this navigation, or null if no path could be calculated
*/
@Nullable PathResult findPath(@NotNull LivingEntity target);
/**
* Calculates a destination for the Entity to navigate to, and sets it with default speed
* as the current target.
* @param loc Location to navigate to
* @return If the pathfinding was successfully started
*/
default boolean moveTo(@NotNull Location loc) {
return moveTo(loc, 1);
}
/**
* Calculates a destination for the Entity to navigate to, with desired speed
* as the current target.
* @param loc Location to navigate to
* @param speed Speed multiplier to navigate at, where 1 is 'normal'
* @return If the pathfinding was successfully started
*/
default boolean moveTo(@NotNull Location loc, double speed) {
PathResult path = findPath(loc);
return path != null && moveTo(path, speed);
}
/**
* Calculates a destination for the Entity to navigate to to reach the target entity,
* and sets it with default speed.
*
* The behavior of this PathResult is subject to the games pathfinding rules, and may
* result in the pathfinding automatically updating to follow the target Entity.
*
* However, this behavior is not guaranteed, and is subject to the games behavior.
*
* @param target the Entity to navigate to
* @return If the pathfinding was successfully started
*/
default boolean moveTo(@NotNull LivingEntity target) {
return moveTo(target, 1);
}
/**
* Calculates a destination for the Entity to navigate to to reach the target entity,
* and sets it with specified speed.
*
* The behavior of this PathResult is subject to the games pathfinding rules, and may
* result in the pathfinding automatically updating to follow the target Entity.
*
* However, this behavior is not guaranteed, and is subject to the games behavior.
*
* @param target the Entity to navigate to
* @param speed Speed multiplier to navigate at, where 1 is 'normal'
* @return If the pathfinding was successfully started
*/
default boolean moveTo(@NotNull LivingEntity target, double speed) {
PathResult path = findPath(target);
return path != null && moveTo(path, speed);
}
/**
* Takes the result of a previous pathfinding calculation and sets it
* as the active pathfinding with default speed.
*
* @param path The Path to start following
* @return If the pathfinding was successfully started
*/
default boolean moveTo(@NotNull PathResult path) {
return moveTo(path, 1);
}
/**
* Takes the result of a previous pathfinding calculation and sets it
* as the active pathfinding,
*
* @param path The Path to start following
* @param speed Speed multiplier to navigate at, where 1 is 'normal'
* @return If the pathfinding was successfully started
*/
boolean moveTo(@NotNull PathResult path, double speed);
/**
* Represents the result of a pathfinding calculation
*/
interface PathResult {
/**
* All currently calculated points to follow along the path to reach the destination location
*
* Will return points the entity has already moved past, see {@link #getNextPointIndex()}
* @return List of points
*/
@NotNull
List<Location> getPoints();
/**
* @return Returns the index of the current point along the points returned in {@link #getPoints()} the entity
* is trying to reach, or null if we are done with this pathfinding.
*/
int getNextPointIndex();
/**
* @return The next location in the path points the entity is trying to reach, or null if there is no next point
*/
@Nullable Location getNextPoint();
/**
* @return The closest point the path can get to the target location
*/
@Nullable Location getFinalPoint();
}
}

View File

@@ -0,0 +1,31 @@
package com.destroystokyo.paper.entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Mob;
import org.jetbrains.annotations.NotNull;
public interface RangedEntity extends Mob {
/**
* Attack the specified entity using a ranged attack.
*
* @param target the entity to target
* @param charge How "charged" the attack is (how far back the bow was pulled for Bow attacks).
* This should be a value between 0 and 1, represented as targetDistance/maxDistance.
*/
void rangedAttack(@NotNull LivingEntity target, float charge);
/**
* Sets that the Entity is "charging" up an attack, by raising its hands
*
* @param raiseHands Whether the entities hands are raised to charge attack
*/
void setChargingAttack(boolean raiseHands);
/**
* Alias to {@link LivingEntity#isHandRaised()}, if the entity is charging an attack
* @return If entities hands are raised
*/
default boolean isChargingAttack() {
return isHandRaised();
}
}

View File

@@ -0,0 +1,38 @@
package com.destroystokyo.paper.entity;
import org.bukkit.entity.Entity;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
/**
* Represents information about a targeted entity
*/
public class TargetEntityInfo {
private final Entity entity;
private final Vector hitVec;
public TargetEntityInfo(@NotNull Entity entity, @NotNull Vector hitVec) {
this.entity = entity;
this.hitVec = hitVec;
}
/**
* Get the entity that is targeted
*
* @return Targeted entity
*/
@NotNull
public Entity getEntity() {
return entity;
}
/**
* Get the position the entity is targeted at
*
* @return Targeted position
*/
@NotNull
public Vector getHitVector() {
return hitVec;
}
}

View File

@@ -0,0 +1,148 @@
package com.destroystokyo.paper.event.block;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.inventory.InventoryEvent;
import org.bukkit.inventory.AnvilInventory;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Called when an anvil is damaged from being used
*/
public class AnvilDamagedEvent extends InventoryEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private boolean cancel;
private DamageState damageState;
public AnvilDamagedEvent(@NotNull InventoryView inventory, @NotNull BlockData blockData) {
super(inventory);
this.damageState = DamageState.getState(blockData);
}
@NotNull
@Override
public AnvilInventory getInventory() {
return (AnvilInventory) super.getInventory();
}
/**
* Gets the new state of damage on the anvil
*
* @return Damage state
*/
@NotNull
public DamageState getDamageState() {
return damageState;
}
/**
* Sets the new state of damage on the anvil
*
* @param damageState Damage state
*/
public void setDamageState(@NotNull DamageState damageState) {
this.damageState = damageState;
}
/**
* Gets if anvil is breaking on this use
*
* @return True if breaking
*/
public boolean isBreaking() {
return damageState == DamageState.BROKEN;
}
/**
* Sets if anvil is breaking on this use
*
* @param breaking True if breaking
*/
public void setBreaking(boolean breaking) {
if (breaking) {
damageState = DamageState.BROKEN;
} else if (damageState == DamageState.BROKEN) {
damageState = DamageState.DAMAGED;
}
}
public boolean isCancelled() {
return cancel;
}
public void setCancelled(boolean cancel) {
this.cancel = cancel;
}
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
/**
* Represents the amount of damage on an anvil block
*/
public enum DamageState {
FULL(Material.ANVIL),
CHIPPED(Material.CHIPPED_ANVIL),
DAMAGED(Material.DAMAGED_ANVIL),
BROKEN(Material.AIR);
private Material material;
DamageState(@NotNull Material material) {
this.material = material;
}
/**
* Get block material of this state
*
* @return Material
*/
@NotNull
public Material getMaterial() {
return material;
}
/**
* Get damaged state by block data
*
* @param blockData Block data
* @return DamageState
* @throws IllegalArgumentException If non anvil block data is given
*/
@NotNull
public static DamageState getState(@Nullable BlockData blockData) {
return blockData == null ? BROKEN : getState(blockData.getMaterial());
}
/**
* Get damaged state by block material
*
* @param material Block material
* @return DamageState
* @throws IllegalArgumentException If non anvil material is given
*/
@NotNull
public static DamageState getState(@Nullable Material material) {
if (material == null) {
return BROKEN;
}
for (DamageState state : values()) {
if (state.material == material) {
return state;
}
}
throw new IllegalArgumentException("Material not an anvil");
}
}
}

View File

@@ -0,0 +1,86 @@
package com.destroystokyo.paper.event.block;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.block.BlockEvent;
import org.bukkit.potion.PotionEffect;
import org.jetbrains.annotations.NotNull;
/**
* Called when a beacon effect is being applied to a player.
*/
public class BeaconEffectEvent extends BlockEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private boolean cancelled;
private PotionEffect effect;
private Player player;
private boolean primary;
public BeaconEffectEvent(@NotNull Block block, @NotNull PotionEffect effect, @NotNull Player player, boolean primary) {
super(block);
this.effect = effect;
this.player = player;
this.primary = primary;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
/**
* Gets the potion effect being applied.
*
* @return Potion effect
*/
@NotNull
public PotionEffect getEffect() {
return effect;
}
/**
* Sets the potion effect that will be applied.
*
* @param effect Potion effect
*/
public void setEffect(@NotNull PotionEffect effect) {
this.effect = effect;
}
/**
* Gets the player who the potion effect is being applied to.
*
* @return Affected player
*/
@NotNull
public Player getPlayer() {
return player;
}
/**
* Gets whether the effect is a primary beacon effect.
*
* @return true if this event represents a primary effect
*/
public boolean isPrimary() {
return primary;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,92 @@
package com.destroystokyo.paper.event.block;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.block.BlockEvent;
import org.jetbrains.annotations.NotNull;
/**
* Fired anytime the server intends to 'destroy' a block through some triggering reason.
* This does not fire anytime a block is set to air, but only with more direct triggers such
* as physics updates, pistons, Entities changing blocks, commands set to "Destroy".
*
* This event is associated with the game playing a sound effect at the block in question, when
* something can be described as "intend to destroy what is there",
*
* Events such as leaves decaying, pistons retracting (where the block is moving), does NOT fire this event.
*
*/
public class BlockDestroyEvent extends BlockEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
@NotNull private final BlockData newState;
private final boolean willDrop;
private boolean playEffect;
private boolean cancelled = false;
public BlockDestroyEvent(@NotNull Block block, @NotNull BlockData newState, boolean willDrop) {
super(block);
this.newState = newState;
this.willDrop = willDrop;
}
/**
* @return The new state of this block (Air, or a Fluid type)
*/
@NotNull
public BlockData getNewState() {
return newState;
}
/**
* @return If the server is going to drop the block in question with this destroy event
*/
public boolean willDrop() {
return this.willDrop;
}
/**
* @return If the server is going to play the sound effect for this destruction
*/
public boolean playEffect() {
return this.playEffect;
}
/**
* @param playEffect If the server should play the sound effect for this destruction
*/
public void setPlayEffect(boolean playEffect) {
this.playEffect = playEffect;
}
/**
* @return If the event is cancelled, meaning the block will not be destroyed
*/
@Override
public boolean isCancelled() {
return cancelled;
}
/**
* If the event is cancelled, the block will remain in its previous state.
* @param cancel true if you wish to cancel this event
*/
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,114 @@
package com.destroystokyo.paper.event.block;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.block.BlockEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Called when TNT block is about to turn into {@link org.bukkit.entity.TNTPrimed}
* <p>
* Cancelling it won't turn TNT into {@link org.bukkit.entity.TNTPrimed} and leaves
* the TNT block as-is
*
* @author Mark Vainomaa
*/
public class TNTPrimeEvent extends BlockEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private boolean cancelled;
@NotNull private PrimeReason reason;
@Nullable private Entity primerEntity;
public TNTPrimeEvent(@NotNull Block theBlock, @NotNull PrimeReason reason, @Nullable Entity primerEntity) {
super(theBlock);
this.reason = reason;
this.primerEntity = primerEntity;
}
/**
* Gets the TNT prime reason
*
* @return Prime reason
*/
@NotNull
public PrimeReason getReason() {
return this.reason;
}
/**
* Gets the TNT primer {@link Entity}.
*
* It's null if {@link #getReason()} is {@link PrimeReason#REDSTONE} or {@link PrimeReason#FIRE}.
* It's not null if {@link #getReason()} is {@link PrimeReason#ITEM} or {@link PrimeReason#PROJECTILE}
* It might be null if {@link #getReason()} is {@link PrimeReason#EXPLOSION}
*
* @return The {@link Entity} who primed the TNT
*/
@Nullable
public Entity getPrimerEntity() {
return this.primerEntity;
}
/**
* Gets whether spawning {@link org.bukkit.entity.TNTPrimed} should be cancelled or not
*
* @return Whether spawning {@link org.bukkit.entity.TNTPrimed} should be cancelled or not
*/
@Override
public boolean isCancelled() {
return this.cancelled;
}
/**
* Sets whether to cancel spawning {@link org.bukkit.entity.TNTPrimed} or not
*
* @param cancel whether spawning {@link org.bukkit.entity.TNTPrimed} should be cancelled or not
*/
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
@Nullable
@Override
public HandlerList getHandlers() {
return handlers;
}
@Nullable
public static HandlerList getHandlerList() {
return handlers;
}
public enum PrimeReason {
/**
* When TNT prime was caused by other explosion (chain reaction)
*/
EXPLOSION,
/**
* When TNT prime was caused by fire
*/
FIRE,
/**
* When {@link org.bukkit.entity.Player} used {@link org.bukkit.Material#FLINT_AND_STEEL} or
* {@link org.bukkit.Material#FIRE_CHARGE} on given TNT block
*/
ITEM,
/**
* When TNT prime was caused by an {@link Entity} shooting TNT
* using a bow with {@link org.bukkit.enchantments.Enchantment#ARROW_FIRE} enchantment
*/
PROJECTILE,
/**
* When redstone power triggered the TNT prime
*/
REDSTONE
}
}

View File

@@ -0,0 +1,54 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.Creeper;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.jetbrains.annotations.NotNull;
/**
* Called when a Creeper is ignite flag is changed (armed/disarmed to explode).
*/
public class CreeperIgniteEvent extends EntityEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private boolean canceled;
private boolean ignited;
public CreeperIgniteEvent(@NotNull Creeper creeper, boolean ignited) {
super(creeper);
this.ignited = ignited;
}
@NotNull
@Override
public Creeper getEntity() {
return (Creeper) entity;
}
public boolean isIgnited() {
return ignited;
}
public void setIgnited(boolean ignited) {
this.ignited = ignited;
}
public boolean isCancelled() {
return canceled;
}
public void setCancelled(boolean cancel) {
canceled = cancel;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,79 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.AreaEffectCloud;
import org.bukkit.entity.DragonFireball;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import java.util.Collection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Fired when a DragonFireball collides with a block/entity and spawns an AreaEffectCloud
*/
public class EnderDragonFireballHitEvent extends EntityEvent implements Cancellable {
@Nullable private final Collection<LivingEntity> targets;
@NotNull private final AreaEffectCloud areaEffectCloud;
public EnderDragonFireballHitEvent(@NotNull DragonFireball fireball, @Nullable Collection<LivingEntity> targets, @NotNull AreaEffectCloud areaEffectCloud) {
super(fireball);
this.targets = targets;
this.areaEffectCloud = areaEffectCloud;
}
/**
* The fireball involved in this event
*/
@NotNull
@Override
public DragonFireball getEntity() {
return (DragonFireball) super.getEntity();
}
/**
* The living entities hit by fireball
*
* May be null if no entities were hit
*
* @return the targets
*/
@Nullable
public Collection<LivingEntity> getTargets() {
return targets;
}
/**
* @return The area effect cloud spawned in this collision
*/
@NotNull
public AreaEffectCloud getAreaEffectCloud() {
return areaEffectCloud;
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
private boolean cancelled = false;
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
}

View File

@@ -0,0 +1,61 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.AreaEffectCloud;
import org.bukkit.entity.EnderDragon;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.jetbrains.annotations.NotNull;
/**
* Fired when an EnderDragon spawns an AreaEffectCloud by shooting flames
*/
public class EnderDragonFlameEvent extends EntityEvent implements Cancellable {
@NotNull private final AreaEffectCloud areaEffectCloud;
public EnderDragonFlameEvent(@NotNull EnderDragon enderDragon, @NotNull AreaEffectCloud areaEffectCloud) {
super(enderDragon);
this.areaEffectCloud = areaEffectCloud;
}
/**
* The enderdragon involved in this event
*/
@NotNull
@Override
public EnderDragon getEntity() {
return (EnderDragon) super.getEntity();
}
/**
* @return The area effect cloud spawned in this collision
*/
@NotNull
public AreaEffectCloud getAreaEffectCloud() {
return areaEffectCloud;
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
private boolean cancelled = false;
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
}

View File

@@ -0,0 +1,61 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.DragonFireball;
import org.bukkit.entity.EnderDragon;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.jetbrains.annotations.NotNull;
/**
* Fired when an EnderDragon shoots a fireball
*/
public class EnderDragonShootFireballEvent extends EntityEvent implements Cancellable {
@NotNull private final DragonFireball fireball;
public EnderDragonShootFireballEvent(@NotNull EnderDragon entity, @NotNull DragonFireball fireball) {
super(entity);
this.fireball = fireball;
}
/**
* The enderdragon shooting the fireball
*/
@NotNull
@Override
public EnderDragon getEntity() {
return (EnderDragon) super.getEntity();
}
/**
* @return The fireball being shot
*/
@NotNull
public DragonFireball getFireball() {
return fireball;
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
private boolean cancelled = false;
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright (c) 2018 Daniel Ennis (Aikar) MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.Enderman;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.jetbrains.annotations.NotNull;
/**
* Fired when an Enderman determines if it should attack a player or not.
* Starts off cancelled if the player is wearing a pumpkin head or is not looking
* at the Enderman, according to Vanilla rules.
*
*/
public class EndermanAttackPlayerEvent extends EntityEvent implements Cancellable {
@NotNull private final Player player;
public EndermanAttackPlayerEvent(@NotNull Enderman entity, @NotNull Player player) {
super(entity);
this.player = player;
}
/**
* The enderman considering attacking
*
* @return The enderman considering attacking
*/
@NotNull
@Override
public Enderman getEntity() {
return (Enderman) super.getEntity();
}
/**
* The player the Enderman is considering attacking
*
* @return The player the Enderman is considering attacking
*/
@NotNull
public Player getPlayer() {
return player;
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
private boolean cancelled = false;
/**
*
* @return If cancelled, the enderman will not attack
*/
@Override
public boolean isCancelled() {
return cancelled;
}
/**
* Cancels if the Enderman will attack this player
* @param cancel true if you wish to cancel this event
*/
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
}

View File

@@ -0,0 +1,87 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.Enderman;
import org.bukkit.entity.Entity;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.jetbrains.annotations.NotNull;
public class EndermanEscapeEvent extends EntityEvent implements Cancellable {
@NotNull private final Reason reason;
public EndermanEscapeEvent(@NotNull Enderman entity, @NotNull Reason reason) {
super(entity);
this.reason = reason;
}
@NotNull
@Override
public Enderman getEntity() {
return (Enderman) super.getEntity();
}
/**
* @return The reason the enderman is trying to escape
*/
@NotNull
public Reason getReason() {
return reason;
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
private boolean cancelled = false;
@Override
public boolean isCancelled() {
return cancelled;
}
/**
* Cancels the escape.
*
* If this escape normally would of resulted in damage avoidance such as indirect,
* the enderman will now take damage.
*
* @param cancel true if you wish to cancel this event
*/
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
public enum Reason {
/**
* The enderman has stopped attacking and ran away
*/
RUNAWAY,
/**
* The enderman has teleported away due to indirect damage (ranged)
*/
INDIRECT,
/**
* The enderman has teleported away due to a critical hit
*/
CRITICAL_HIT,
/**
* The enderman has teleported away due to the player staring at it during combat
*/
STARE,
/**
* Specific case for CRITICAL_HIT where the enderman is taking rain damage
*/
DROWN
}
}

View File

@@ -0,0 +1,32 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.Entity;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.jetbrains.annotations.NotNull;
/**
* Fired any time an entity is being added to the world for any reason.
*
* Not to be confused with {@link org.bukkit.event.entity.CreatureSpawnEvent}
* This will fire anytime a chunk is reloaded too.
*/
public class EntityAddToWorldEvent extends EntityEvent {
public EntityAddToWorldEvent(@NotNull Entity entity) {
super(entity);
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,82 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
/**
* Fired when an Entity is knocked back by the hit of another Entity. The acceleration
* vector can be modified. If this event is cancelled, the entity is not knocked back.
*
*/
public class EntityKnockbackByEntityEvent extends EntityEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
@NotNull private final Entity hitBy;
private final float knockbackStrength;
@NotNull private final Vector acceleration;
private boolean cancelled = false;
public EntityKnockbackByEntityEvent(@NotNull LivingEntity entity, @NotNull Entity hitBy, float knockbackStrength, @NotNull Vector acceleration) {
super(entity);
this.hitBy = hitBy;
this.knockbackStrength = knockbackStrength;
this.acceleration = acceleration;
}
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
/**
* @return the entity which was knocked back
*/
@NotNull
@Override
public LivingEntity getEntity() {
return (LivingEntity) super.getEntity();
}
/**
* @return the original knockback strength.
*/
public float getKnockbackStrength() {
return knockbackStrength;
}
/**
* @return the Entity which hit
*/
@NotNull
public Entity getHitBy() {
return hitBy;
}
/**
* @return the acceleration that will be applied
*/
@NotNull
public Vector getAcceleration() {
return acceleration;
}
}

View File

@@ -0,0 +1,82 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Fired when an Entity decides to start moving towards a location.
*
* This event does not fire for the entities actual movement. Only when it
* is choosing to start moving to a location.
*/
public class EntityPathfindEvent extends EntityEvent implements Cancellable {
@Nullable private final Entity targetEntity;
@NotNull private final Location loc;
public EntityPathfindEvent(@NotNull Entity entity, @NotNull Location loc, @Nullable Entity targetEntity) {
super(entity);
this.targetEntity = targetEntity;
this.loc = loc;
}
/**
* The Entity that is pathfinding.
* @return The Entity that is pathfinding.
*/
@NotNull
public Entity getEntity() {
return entity;
}
/**
* If the Entity is trying to pathfind to an entity, this is the entity in relation.
*
* Otherwise this will return null.
*
* @return The entity target or null
*/
@Nullable
public Entity getTargetEntity() {
return targetEntity;
}
/**
* The Location of where the entity is about to move to.
*
* Note that if the target happened to of been an entity
* @return Location of where the entity is trying to pathfind to.
*/
@NotNull
public Location getLoc() {
return loc;
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
private boolean cancelled = false;
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
}

View File

@@ -0,0 +1,29 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.Entity;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.jetbrains.annotations.NotNull;
/**
* Fired any time an entity is being removed from a world for any reason
*/
public class EntityRemoveFromWorldEvent extends EntityEvent {
public EntityRemoveFromWorldEvent(@NotNull Entity entity) {
super(entity);
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,31 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.Location;
import org.bukkit.block.EndGateway;
import org.bukkit.entity.Entity;
import org.bukkit.event.entity.EntityTeleportEvent;
import org.jetbrains.annotations.NotNull;
/**
* Fired any time an entity attempts to teleport in an end gateway
*/
public class EntityTeleportEndGatewayEvent extends EntityTeleportEvent {
@NotNull private final EndGateway gateway;
public EntityTeleportEndGatewayEvent(@NotNull Entity what, @NotNull Location from, @NotNull Location to, @NotNull EndGateway gateway) {
super(what, from, to);
this.gateway = gateway;
}
/**
* The gateway triggering the teleport
*
* @return EndGateway used
*/
@NotNull
public EndGateway getGateway() {
return gateway;
}
}

View File

@@ -0,0 +1,92 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.Entity;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.bukkit.event.entity.EntityTransformEvent;
/**
* Fired when an entity transforms into another entity
* <p>
* If the event is cancelled, the entity will not transform
* @deprecated Bukkit has added {@link EntityTransformEvent}, you should start using that
*/
@Deprecated
public class EntityTransformedEvent extends EntityEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private boolean cancelled;
private final Entity transformed;
private final TransformedReason reason;
public EntityTransformedEvent(Entity entity, Entity transformed, TransformedReason reason) {
super(entity);
this.transformed = transformed;
this.reason = reason;
}
/**
* The entity after it has transformed
*
* @return Transformed entity
* @deprecated see {@link EntityTransformEvent#getTransformedEntity()}
*/
@Deprecated
public Entity getTransformed() {
return transformed;
}
/**
* @return The reason for the transformation
* @deprecated see {@link EntityTransformEvent#getTransformReason()}
*/
@Deprecated
public TransformedReason getReason() {
return reason;
}
@Override
public HandlerList getHandlers(){
return handlers;
}
public static HandlerList getHandlerList(){
return handlers;
}
@Override
public boolean isCancelled(){
return cancelled;
}
@Override
public void setCancelled(boolean cancel){
cancelled = cancel;
}
public enum TransformedReason {
/**
* When a zombie drowns
*/
DROWNED,
/**
* When a zombie villager is cured
*/
CURED,
/**
* When a villager turns to a zombie villager
*/
INFECTED,
/**
* When a mooshroom turns to a cow
*/
SHEARED,
/**
* When a pig turns to a zombiepigman
*/
LIGHTNING
}
}

View File

@@ -0,0 +1,65 @@
package com.destroystokyo.paper.event.entity;
import org.apache.commons.lang.Validate;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LightningStrike;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.bukkit.event.entity.EntityTransformEvent;
import java.util.Collections;
import org.jetbrains.annotations.NotNull;
/**
* Fired when lightning strikes an entity
*/
public class EntityZapEvent extends EntityTransformEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private boolean cancelled;
@NotNull private final LightningStrike bolt;
public EntityZapEvent(@NotNull final Entity entity, @NotNull final LightningStrike bolt, @NotNull final Entity replacementEntity) {
super(entity, Collections.singletonList(replacementEntity), TransformReason.LIGHTNING);
Validate.notNull(bolt);
Validate.notNull(replacementEntity);
this.bolt = bolt;
}
public boolean isCancelled() {
return cancelled;
}
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
/**
* Gets the lightning bolt that is striking the entity.
* @return The lightning bolt responsible for this event
*/
@NotNull
public LightningStrike getBolt() {
return bolt;
}
/**
* Gets the entity that will replace the struck entity.
* @return The entity that will replace the struck entity
*/
@NotNull
public Entity getReplacementEntity() {
return getTransformedEntity();
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) 2017 Daniel Ennis (Aikar) MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.ExperienceOrb;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.jetbrains.annotations.NotNull;
/**
* Fired anytime the server is about to merge 2 experience orbs into one
*/
public class ExperienceOrbMergeEvent extends EntityEvent implements Cancellable {
@NotNull private final ExperienceOrb mergeTarget;
@NotNull private final ExperienceOrb mergeSource;
public ExperienceOrbMergeEvent(@NotNull ExperienceOrb mergeTarget, @NotNull ExperienceOrb mergeSource) {
super(mergeTarget);
this.mergeTarget = mergeTarget;
this.mergeSource = mergeSource;
}
/**
* @return The orb that will absorb the other experience orb
*/
@NotNull
public ExperienceOrb getMergeTarget() {
return mergeTarget;
}
/**
* @return The orb that is subject to being removed and merged into the target orb
*/
@NotNull
public ExperienceOrb getMergeSource() {
return mergeSource;
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
private boolean cancelled = false;
@Override
public boolean isCancelled() {
return cancelled;
}
/**
* @param cancel true if you wish to cancel this event, and prevent the orbs from merging
*/
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
}

View File

@@ -0,0 +1,31 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Called when a phantom is spawned for an exhausted player
*/
public class PhantomPreSpawnEvent extends PreCreatureSpawnEvent {
@NotNull private final Entity entity;
public PhantomPreSpawnEvent(@NotNull Location location, @NotNull Entity entity, @NotNull CreatureSpawnEvent.SpawnReason reason) {
super(location, EntityType.PHANTOM, reason);
this.entity = entity;
}
/**
* Get the entity this phantom is spawning for
*
* @return Entity
*/
@Nullable
public Entity getSpawningEntity() {
return entity;
}
}

View File

@@ -0,0 +1,64 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.NotNull;
/**
* Fired when the server is calculating what chunks to try to spawn monsters in every Monster Spawn Tick event
*/
public class PlayerNaturallySpawnCreaturesEvent extends PlayerEvent implements Cancellable {
private byte radius;
public PlayerNaturallySpawnCreaturesEvent(@NotNull Player player, byte radius) {
super(player);
this.radius = radius;
}
/**
* @return The radius of chunks around this player to be included in natural spawn selection
*/
public byte getSpawnRadius() {
return radius;
}
/**
* @param radius The radius of chunks around this player to be included in natural spawn selection
*/
public void setSpawnRadius(byte radius) {
this.radius = radius;
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
private boolean cancelled = false;
/**
* @return If this players chunks will be excluded from natural spawns
*/
@Override
public boolean isCancelled() {
return cancelled;
}
/**
* @param cancel true if you wish to cancel this event, and not include this players chunks for natural spawning
*/
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
}

View File

@@ -0,0 +1,104 @@
package com.destroystokyo.paper.event.entity;
import com.google.common.base.Preconditions;
import org.bukkit.Location;
import org.bukkit.entity.EntityType;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.jetbrains.annotations.NotNull;
/**
* WARNING: This event only fires for a limited number of cases, and not for every case that CreatureSpawnEvent does.
*
* You should still listen to CreatureSpawnEvent as a backup, and only use this event as an "enhancement".
* The intent of this event is to improve server performance, so limited use cases.
*
* Currently: NATURAL and SPAWNER based reasons. Please submit a Pull Request for future additions.
* Also, Plugins that replace Entity Registrations with their own custom entities might not fire this event.
*/
public class PreCreatureSpawnEvent extends Event implements Cancellable {
@NotNull private final Location location;
@NotNull private final EntityType type;
@NotNull private final CreatureSpawnEvent.SpawnReason reason;
private boolean shouldAbortSpawn;
public PreCreatureSpawnEvent(@NotNull Location location, @NotNull EntityType type, @NotNull CreatureSpawnEvent.SpawnReason reason) {
this.location = Preconditions.checkNotNull(location, "Location may not be null").clone();
this.type = Preconditions.checkNotNull(type, "Type may not be null");
this.reason = Preconditions.checkNotNull(reason, "Reason may not be null");
}
/**
* @return The location this creature is being spawned at
*/
@NotNull
public Location getSpawnLocation() {
return location;
}
/**
* @return The type of creature being spawned
*/
@NotNull
public EntityType getType() {
return type;
}
/**
* @return Reason this creature is spawning (ie, NATURAL vs SPAWNER)
*/
@NotNull
public CreatureSpawnEvent.SpawnReason getReason() {
return reason;
}
/**
* @return If the spawn process should be aborted vs trying more attempts
*/
public boolean shouldAbortSpawn() {
return shouldAbortSpawn;
}
/**
* Set this if you are more blanket blocking all types of these spawns, and wish to abort the spawn process from
* trying more attempts after this cancellation.
*
* @param shouldAbortSpawn Set if the spawn process should be aborted vs trying more attempts
*/
public void setShouldAbortSpawn(boolean shouldAbortSpawn) {
this.shouldAbortSpawn = shouldAbortSpawn;
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
private boolean cancelled = false;
/**
* @return If the spawn of this creature is cancelled or not
*/
@Override
public boolean isCancelled() {
return cancelled;
}
/**
* Cancelling this event is more effecient than cancelling CreatureSpawnEvent
* @param cancel true if you wish to cancel this event, and abort the spawn of this creature
*/
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
}

View File

@@ -0,0 +1,29 @@
package com.destroystokyo.paper.event.entity;
import com.google.common.base.Preconditions;
import org.bukkit.Location;
import org.bukkit.entity.EntityType;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.jetbrains.annotations.NotNull;
/**
* Called before an entity is spawned into a world by a spawner.
*
* This only includes the spawner's location and not the full BlockState snapshot for performance reasons.
* If you really need it you have to get the spawner yourself.
*/
public class PreSpawnerSpawnEvent extends PreCreatureSpawnEvent {
@NotNull private final Location spawnerLocation;
public PreSpawnerSpawnEvent(@NotNull Location location, @NotNull EntityType type, @NotNull Location spawnerLocation) {
super(location, type, CreatureSpawnEvent.SpawnReason.SPAWNER);
this.spawnerLocation = Preconditions.checkNotNull(spawnerLocation, "Spawner location may not be null");
}
@NotNull
public Location getSpawnerLocation() {
return spawnerLocation;
}
}

View File

@@ -0,0 +1,67 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Projectile;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.jetbrains.annotations.NotNull;
/**
* Called when an projectile collides with an entity
* <p>
* This event is called <b>before</b> {@link org.bukkit.event.entity.EntityDamageByEntityEvent}, and cancelling it will allow the projectile to continue flying
*/
public class ProjectileCollideEvent extends EntityEvent implements Cancellable {
@NotNull private final Entity collidedWith;
/**
* Get the entity the projectile collided with
*
* @return the entity collided with
*/
@NotNull
public Entity getCollidedWith() {
return collidedWith;
}
public ProjectileCollideEvent(@NotNull Projectile what, @NotNull Entity collidedWith) {
super(what);
this.collidedWith = collidedWith;
}
/**
* Get the projectile that collided
*
* @return the projectile that collided
*/
@NotNull
public Projectile getEntity() {
return (Projectile) super.getEntity();
}
private static final HandlerList handlerList = new HandlerList();
@NotNull
public static HandlerList getHandlerList() {
return handlerList;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlerList;
}
private boolean cancelled = false;
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
}

View File

@@ -0,0 +1,47 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.SkeletonHorse;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.jetbrains.annotations.NotNull;
/**
* Event called when a player gets close to a skeleton horse and triggers the lightning trap
*/
public class SkeletonHorseTrapEvent extends EntityEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private boolean cancelled;
public SkeletonHorseTrapEvent(@NotNull SkeletonHorse horse) {
super(horse);
}
@NotNull
@Override
public SkeletonHorse getEntity() {
return (SkeletonHorse) super.getEntity();
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,38 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.Slime;
import org.bukkit.event.Cancellable;
import org.jetbrains.annotations.NotNull;
/**
* Fired when a Slime decides to change it's facing direction.
* <p>
* This event does not fire for the entity's actual movement. Only when it
* is choosing to change direction.
*/
public class SlimeChangeDirectionEvent extends SlimePathfindEvent implements Cancellable {
private float yaw;
public SlimeChangeDirectionEvent(@NotNull Slime slime, float yaw) {
super(slime);
this.yaw = yaw;
}
/**
* Get the new chosen yaw
*
* @return Chosen yaw
*/
public float getNewYaw() {
return yaw;
}
/**
* Set the new chosen yaw
*
* @param yaw Chosen yaw
*/
public void setNewYaw(float yaw) {
this.yaw = yaw;
}
}

View File

@@ -0,0 +1,53 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.Slime;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.jetbrains.annotations.NotNull;
/**
* Fired when a Slime decides to start pathfinding.
* <p>
* This event does not fire for the entity's actual movement. Only when it
* is choosing to start moving.
*/
public class SlimePathfindEvent extends EntityEvent implements Cancellable {
public SlimePathfindEvent(@NotNull Slime slime) {
super(slime);
}
/**
* The Slime that is pathfinding.
*
* @return The Slime that is pathfinding.
*/
@NotNull
public Slime getEntity() {
return (Slime) entity;
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
private boolean cancelled = false;
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
}

View File

@@ -0,0 +1,17 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.Slime;
import org.bukkit.event.Cancellable;
import org.jetbrains.annotations.NotNull;
/**
* Fired when a Slime decides to start jumping while swimming in water/lava.
* <p>
* This event does not fire for the entity's actual movement. Only when it
* is choosing to start jumping.
*/
public class SlimeSwimEvent extends SlimeWanderEvent implements Cancellable {
public SlimeSwimEvent(@NotNull Slime slime) {
super(slime);
}
}

View File

@@ -0,0 +1,31 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Slime;
import org.bukkit.event.Cancellable;
import org.jetbrains.annotations.NotNull;
/**
* Fired when a Slime decides to change direction to target a LivingEntity.
* <p>
* This event does not fire for the entity's actual movement. Only when it
* is choosing to start moving.
*/
public class SlimeTargetLivingEntityEvent extends SlimePathfindEvent implements Cancellable {
@NotNull private final LivingEntity target;
public SlimeTargetLivingEntityEvent(@NotNull Slime slime, @NotNull LivingEntity target) {
super(slime);
this.target = target;
}
/**
* Get the targeted entity
*
* @return Targeted entity
*/
@NotNull
public LivingEntity getTarget() {
return target;
}
}

View File

@@ -0,0 +1,17 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.Slime;
import org.bukkit.event.Cancellable;
import org.jetbrains.annotations.NotNull;
/**
* Fired when a Slime decides to start wandering.
* <p>
* This event does not fire for the entity's actual movement. Only when it
* is choosing to start moving.
*/
public class SlimeWanderEvent extends SlimePathfindEvent implements Cancellable {
public SlimeWanderEvent(@NotNull Slime slime) {
super(slime);
}
}

View File

@@ -0,0 +1,49 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.Turtle;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.jetbrains.annotations.NotNull;
/**
* Fired when a Turtle decides to go home
*/
public class TurtleGoHomeEvent extends EntityEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private boolean cancelled = false;
public TurtleGoHomeEvent(@NotNull Turtle turtle) {
super(turtle);
}
/**
* The turtle going home
*
* @return The turtle
*/
@NotNull
public Turtle getEntity() {
return (Turtle) entity;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,87 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.Location;
import org.bukkit.entity.Turtle;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.jetbrains.annotations.NotNull;
/**
* Fired when a Turtle lays eggs
*/
public class TurtleLayEggEvent extends EntityEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private boolean cancelled = false;
@NotNull
private final Location location;
private int eggCount;
public TurtleLayEggEvent(@NotNull Turtle turtle, @NotNull Location location, int eggCount) {
super(turtle);
this.location = location;
this.eggCount = eggCount;
}
/**
* The turtle laying the eggs
*
* @return The turtle
*/
@NotNull
public Turtle getEntity() {
return (Turtle) entity;
}
/**
* Get the location where the eggs are being laid
*
* @return Location of eggs
*/
@NotNull
public Location getLocation() {
return location;
}
/**
* Get the number of eggs being laid
*
* @return Number of eggs
*/
public int getEggCount() {
return eggCount;
}
/**
* Set the number of eggs being laid
*
* @param eggCount Number of eggs
*/
public void setEggCount(int eggCount) {
if (eggCount < 1) {
cancelled = true;
return;
}
eggCount = Math.min(eggCount, 4);
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,62 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.Location;
import org.bukkit.entity.Turtle;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.jetbrains.annotations.NotNull;
/**
* Fired when a Turtle starts digging to lay eggs
*/
public class TurtleStartDiggingEvent extends EntityEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private boolean cancelled = false;
@NotNull private final Location location;
public TurtleStartDiggingEvent(@NotNull Turtle turtle, @NotNull Location location) {
super(turtle);
this.location = location;
}
/**
* The turtle digging
*
* @return The turtle
*/
@NotNull
public Turtle getEntity() {
return (Turtle) entity;
}
/**
* Get the location where the turtle is digging
*
* @return Location where digging
*/
@NotNull
public Location getLocation() {
return location;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,70 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.Witch;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Fired when a witch consumes the potion in their hand to buff themselves.
*/
public class WitchConsumePotionEvent extends EntityEvent implements Cancellable {
@Nullable private ItemStack potion;
public WitchConsumePotionEvent(@NotNull Witch witch, @Nullable ItemStack potion) {
super(witch);
this.potion = potion;
}
@NotNull
@Override
public Witch getEntity() {
return (Witch) super.getEntity();
}
/**
* @return the potion the witch will consume and have the effects applied.
*/
@Nullable
public ItemStack getPotion() {
return potion;
}
/**
* Sets the potion to be consumed and applied to the witch.
* @param potion The potion
*/
public void setPotion(@Nullable ItemStack potion) {
this.potion = potion != null ? potion.clone() : null;
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
private boolean cancelled = false;
/**
* @return Event was cancelled or potion was null
*/
@Override
public boolean isCancelled() {
return cancelled || potion == null;
}
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
}

View File

@@ -0,0 +1,80 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.Material;
import org.bukkit.entity.Witch;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class WitchReadyPotionEvent extends EntityEvent implements Cancellable {
private ItemStack potion;
public WitchReadyPotionEvent(@NotNull Witch witch, @Nullable ItemStack potion) {
super(witch);
this.potion = potion;
}
/**
* Fires thee event, returning the desired potion, or air of cancelled
* @param witch the witch whom is readying to use a potion
* @param potion the potion to be used
* @return The ItemStack to be used
*/
@Nullable
public static ItemStack process(@NotNull Witch witch, @Nullable ItemStack potion) {
WitchReadyPotionEvent event = new WitchReadyPotionEvent(witch, potion);
if (!event.callEvent() || event.getPotion() == null) {
return new ItemStack(Material.AIR);
}
return event.getPotion();
}
@NotNull
@Override
public Witch getEntity() {
return (Witch) super.getEntity();
}
/**
* @return the potion the witch is readying to use
*/
@Nullable
public ItemStack getPotion() {
return potion;
}
/**
* Sets the potion the which is going to hold and use
* @param potion The potion
*/
public void setPotion(@Nullable ItemStack potion) {
this.potion = potion != null ? potion.clone() : null;
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
private boolean cancelled = false;
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
}

View File

@@ -0,0 +1,81 @@
package com.destroystokyo.paper.event.entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Witch;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Fired when a witch throws a potion at a player
*/
public class WitchThrowPotionEvent extends EntityEvent implements Cancellable {
@NotNull private final LivingEntity target;
@Nullable private ItemStack potion;
public WitchThrowPotionEvent(@NotNull Witch witch, @NotNull LivingEntity target, @Nullable ItemStack potion) {
super(witch);
this.target = target;
this.potion = potion;
}
@NotNull
@Override
public Witch getEntity() {
return (Witch) super.getEntity();
}
/**
* @return The target of the potion
*/
@NotNull
public LivingEntity getTarget() {
return target;
}
/**
* @return The potion the witch will throw at a player
*/
@Nullable
public ItemStack getPotion() {
return potion;
}
/**
* Sets the potion to be thrown at a player
* @param potion The potion
*/
public void setPotion(@Nullable ItemStack potion) {
this.potion = potion != null ? potion.clone() : null;
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
private boolean cancelled = false;
/**
* @return Event was cancelled or potion was null
*/
@Override
public boolean isCancelled() {
return cancelled || potion == null;
}
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
}

View File

@@ -0,0 +1,42 @@
package com.destroystokyo.paper.event.executor;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import com.destroystokyo.paper.util.SneakyThrow;
import org.bukkit.event.Event;
import org.bukkit.event.EventException;
import org.bukkit.event.Listener;
import org.bukkit.plugin.EventExecutor;
import org.jetbrains.annotations.NotNull;
public class MethodHandleEventExecutor implements EventExecutor {
private final Class<? extends Event> eventClass;
private final MethodHandle handle;
public MethodHandleEventExecutor(@NotNull Class<? extends Event> eventClass, @NotNull MethodHandle handle) {
this.eventClass = eventClass;
this.handle = handle;
}
public MethodHandleEventExecutor(@NotNull Class<? extends Event> eventClass, @NotNull Method m) {
this.eventClass = eventClass;
try {
m.setAccessible(true);
this.handle = MethodHandles.lookup().unreflect(m);
} catch (IllegalAccessException e) {
throw new AssertionError("Unable to set accessible", e);
}
}
@Override
public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException {
if (!eventClass.isInstance(event)) return;
try {
handle.invoke(listener, event);
} catch (Throwable t) {
SneakyThrow.sneaky(t);
}
}
}

View File

@@ -0,0 +1,43 @@
package com.destroystokyo.paper.event.executor;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import com.destroystokyo.paper.util.SneakyThrow;
import com.google.common.base.Preconditions;
import org.bukkit.Bukkit;
import org.bukkit.event.Event;
import org.bukkit.event.EventException;
import org.bukkit.event.Listener;
import org.bukkit.plugin.EventExecutor;
import org.jetbrains.annotations.NotNull;
public class StaticMethodHandleEventExecutor implements EventExecutor {
private final Class<? extends Event> eventClass;
private final MethodHandle handle;
public StaticMethodHandleEventExecutor(@NotNull Class<? extends Event> eventClass, @NotNull Method m) {
Preconditions.checkArgument(Modifier.isStatic(m.getModifiers()), "Not a static method: %s", m);
Preconditions.checkArgument(eventClass != null, "eventClass is null");
this.eventClass = eventClass;
try {
m.setAccessible(true);
this.handle = MethodHandles.lookup().unreflect(m);
} catch (IllegalAccessException e) {
throw new AssertionError("Unable to set accessible", e);
}
}
@Override
public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException {
if (!eventClass.isInstance(event)) return;
try {
handle.invoke(event);
} catch (Throwable throwable) {
SneakyThrow.sneaky(throwable);
}
}
}

View File

@@ -0,0 +1,47 @@
package com.destroystokyo.paper.event.executor.asm;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicInteger;
import org.bukkit.plugin.EventExecutor;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import static org.objectweb.asm.Opcodes.*;
public class ASMEventExecutorGenerator {
@NotNull
public static byte[] generateEventExecutor(@NotNull Method m, @NotNull String name) {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
writer.visit(V1_8, ACC_PUBLIC, name.replace('.', '/'), null, Type.getInternalName(Object.class), new String[] {Type.getInternalName(EventExecutor.class)});
// Generate constructor
GeneratorAdapter methodGenerator = new GeneratorAdapter(writer.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null), ACC_PUBLIC, "<init>", "()V");
methodGenerator.loadThis();
methodGenerator.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(Object.class), "<init>", "()V", false); // Invoke the super class (Object) constructor
methodGenerator.returnValue();
methodGenerator.endMethod();
// Generate the execute method
methodGenerator = new GeneratorAdapter(writer.visitMethod(ACC_PUBLIC, "execute", "(Lorg/bukkit/event/Listener;Lorg/bukkit/event/Event;)V", null, null), ACC_PUBLIC, "execute", "(Lorg/bukkit/event/Listener;Lorg/bukkit/event/Listener;)V");;
methodGenerator.loadArg(0);
methodGenerator.checkCast(Type.getType(m.getDeclaringClass()));
methodGenerator.loadArg(1);
methodGenerator.checkCast(Type.getType(m.getParameterTypes()[0]));
methodGenerator.visitMethodInsn(m.getDeclaringClass().isInterface() ? INVOKEINTERFACE : INVOKEVIRTUAL, Type.getInternalName(m.getDeclaringClass()), m.getName(), Type.getMethodDescriptor(m), m.getDeclaringClass().isInterface());
if (m.getReturnType() != void.class) {
methodGenerator.pop();
}
methodGenerator.returnValue();
methodGenerator.endMethod();
writer.visitEnd();
return writer.toByteArray();
}
public static AtomicInteger NEXT_ID = new AtomicInteger(1);
@NotNull
public static String generateName() {
int id = NEXT_ID.getAndIncrement();
return "com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor" + id;
}
}

View File

@@ -0,0 +1,35 @@
package com.destroystokyo.paper.event.executor.asm;
import com.destroystokyo.paper.utils.UnsafeUtils;
import org.jetbrains.annotations.NotNull;
public interface ClassDefiner {
/**
* Returns if the defined classes can bypass access checks
*
* @return if classes bypass access checks
*/
public default boolean isBypassAccessChecks() {
return false;
}
/**
* Define a class
*
* @param parentLoader the parent classloader
* @param name the name of the class
* @param data the class data to load
* @return the defined class
* @throws ClassFormatError if the class data is invalid
* @throws NullPointerException if any of the arguments are null
*/
@NotNull
public Class<?> defineClass(@NotNull ClassLoader parentLoader, @NotNull String name, @NotNull byte[] data);
@NotNull
public static ClassDefiner getInstance() {
return SafeClassDefiner.INSTANCE;
}
}

View File

@@ -0,0 +1,66 @@
package com.destroystokyo.paper.event.executor.asm;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.google.common.base.Preconditions;
import com.google.common.collect.MapMaker;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.Type;
public class SafeClassDefiner implements ClassDefiner {
/* default */ static final SafeClassDefiner INSTANCE = new SafeClassDefiner();
private SafeClassDefiner() {}
private final ConcurrentMap<ClassLoader, GeneratedClassLoader> loaders = new MapMaker().weakKeys().makeMap();
@NotNull
@Override
public Class<?> defineClass(@NotNull ClassLoader parentLoader, @NotNull String name, @NotNull byte[] data) {
GeneratedClassLoader loader = loaders.computeIfAbsent(parentLoader, GeneratedClassLoader::new);
synchronized (loader.getClassLoadingLock(name)) {
Preconditions.checkState(!loader.hasClass(name), "%s already defined", name);
Class<?> c = loader.define(name, data);
assert c.getName().equals(name);
return c;
}
}
private static class GeneratedClassLoader extends ClassLoader {
static {
ClassLoader.registerAsParallelCapable();
}
protected GeneratedClassLoader(@NotNull ClassLoader parent) {
super(parent);
}
private Class<?> define(@NotNull String name, byte[] data) {
synchronized (getClassLoadingLock(name)) {
assert !hasClass(name);
Class<?> c = defineClass(name, data, 0, data.length);
resolveClass(c);
return c;
}
}
@Override
@NotNull
public Object getClassLoadingLock(@NotNull String name) {
return super.getClassLoadingLock(name);
}
public boolean hasClass(@NotNull String name) {
synchronized (getClassLoadingLock(name)) {
try {
Class.forName(name);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
}
}

View File

@@ -0,0 +1,70 @@
package com.destroystokyo.paper.event.player;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class IllegalPacketEvent extends PlayerEvent {
@Nullable private final String type;
@Nullable private final String ex;
@Nullable private String kickMessage;
private boolean shouldKick = true;
public IllegalPacketEvent(@NotNull Player player, @Nullable String type, @Nullable String kickMessage, @NotNull Exception e) {
super(player);
this.type = type;
this.kickMessage = kickMessage;
this.ex = e.getMessage();
}
public boolean isShouldKick() {
return shouldKick;
}
public void setShouldKick(boolean shouldKick) {
this.shouldKick = shouldKick;
}
@Nullable
public String getKickMessage() {
return kickMessage;
}
public void setKickMessage(@Nullable String kickMessage) {
this.kickMessage = kickMessage;
}
@Nullable
public String getType() {
return type;
}
@Nullable
public String getExceptionMessage() {
return ex;
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
public static void process(@NotNull Player player, @Nullable String type, @Nullable String kickMessage, @NotNull Exception exception) {
IllegalPacketEvent event = new IllegalPacketEvent(player, type, kickMessage, exception);
event.callEvent();
if (event.shouldKick) {
player.kickPlayer(kickMessage);
}
Bukkit.getLogger().severe(player.getName() + "/" + type + ": " + exception.getMessage());
}
}

View File

@@ -0,0 +1,63 @@
package com.destroystokyo.paper.event.player;
import org.bukkit.advancement.Advancement;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.NotNull;
/**
* Called when a player is granted a criteria in an advancement.
*/
public class PlayerAdvancementCriterionGrantEvent extends PlayerEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
@NotNull private final Advancement advancement;
@NotNull private final String criterion;
private boolean cancel = false;
public PlayerAdvancementCriterionGrantEvent(@NotNull Player who, @NotNull Advancement advancement, @NotNull String criterion) {
super(who);
this.advancement = advancement;
this.criterion = criterion;
}
/**
* Get the advancement which has been affected.
*
* @return affected advancement
*/
@NotNull
public Advancement getAdvancement() {
return advancement;
}
/**
* Get the criterion which has been granted.
*
* @return granted criterion
*/
@NotNull
public String getCriterion() {
return criterion;
}
public boolean isCancelled() {
return cancel;
}
public void setCancelled(boolean cancel) {
this.cancel = cancel;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,137 @@
package com.destroystokyo.paper.event.player;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.bukkit.inventory.ItemStack;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static org.bukkit.Material.*;
/**
* Called when the player themselves change their armor items
* <p>
* Not currently called for environmental factors though it <strong>MAY BE IN THE FUTURE</strong>
*/
public class PlayerArmorChangeEvent extends PlayerEvent {
private static final HandlerList HANDLERS = new HandlerList();
@NotNull private final SlotType slotType;
@Nullable private final ItemStack oldItem;
@Nullable private final ItemStack newItem;
public PlayerArmorChangeEvent(@NotNull Player player, @NotNull SlotType slotType, @Nullable ItemStack oldItem, @Nullable ItemStack newItem) {
super(player);
this.slotType = slotType;
this.oldItem = oldItem;
this.newItem = newItem;
}
/**
* Gets the type of slot being altered.
*
* @return type of slot being altered
*/
@NotNull
public SlotType getSlotType() {
return this.slotType;
}
/**
* Gets the existing item that's being replaced
*
* @return old item
*/
@Nullable
public ItemStack getOldItem() {
return this.oldItem;
}
/**
* Gets the new item that's replacing the old
*
* @return new item
*/
@Nullable
public ItemStack getNewItem() {
return this.newItem;
}
@Override
public String toString() {
return "ArmorChangeEvent{" + "player=" + player + ", slotType=" + slotType + ", oldItem=" + oldItem + ", newItem=" + newItem + '}';
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLERS;
}
public enum SlotType {
HEAD(DIAMOND_HELMET, GOLDEN_HELMET, IRON_HELMET, CHAINMAIL_HELMET, LEATHER_HELMET, PUMPKIN, JACK_O_LANTERN),
CHEST(DIAMOND_CHESTPLATE, GOLDEN_CHESTPLATE, IRON_CHESTPLATE, CHAINMAIL_CHESTPLATE, LEATHER_CHESTPLATE, ELYTRA),
LEGS(DIAMOND_LEGGINGS, GOLDEN_LEGGINGS, IRON_LEGGINGS, CHAINMAIL_LEGGINGS, LEATHER_LEGGINGS),
FEET(DIAMOND_BOOTS, GOLDEN_BOOTS, IRON_BOOTS, CHAINMAIL_BOOTS, LEATHER_BOOTS);
private final Set<Material> mutableTypes = new HashSet<>();
private Set<Material> immutableTypes;
SlotType(Material... types) {
this.mutableTypes.addAll(Arrays.asList(types));
}
/**
* Gets an immutable set of all allowed material types that can be placed in an
* armor slot.
*
* @return immutable set of material types
*/
@NotNull
public Set<Material> getTypes() {
if (immutableTypes == null) {
immutableTypes = Collections.unmodifiableSet(mutableTypes);
}
return immutableTypes;
}
/**
* Gets the type of slot via the specified material
*
* @param material material to get slot by
* @return slot type the material will go in, or null if it won't
*/
@Nullable
public static SlotType getByMaterial(@NotNull Material material) {
for (SlotType slotType : values()) {
if (slotType.getTypes().contains(material)) {
return slotType;
}
}
return null;
}
/**
* Gets whether or not this material can be equipped to a slot
*
* @param material material to check
* @return whether or not this material can be equipped
*/
public static boolean isEquipable(@NotNull Material material) {
return getByMaterial(material) != null;
}
}
}

View File

@@ -0,0 +1,95 @@
package com.destroystokyo.paper.event.player;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.net.InetAddress;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
/**
* <p>
* This event is invoked when a player has disconnected. It is guaranteed that,
* if the server is in online-mode, that the provided uuid and username have been
* validated.
* </p>
*
* <p>
* The event is invoked for players who have not yet logged into the world, whereas
* {@link org.bukkit.event.player.PlayerQuitEvent} is only invoked on players who have logged into the world.
* </p>
*
* <p>
* The event is invoked for players who have already logged into the world,
* although whether or not the player exists in the world at the time of
* firing is undefined. (That is, whether the plugin can retrieve a Player object
* using the event parameters is undefined). However, it is guaranteed that this
* event is invoked AFTER {@link org.bukkit.event.player.PlayerQuitEvent}, if the player has already logged into the world.
* </p>
*
* <p>
* This event is guaranteed to never fire unless {@link org.bukkit.event.player.AsyncPlayerPreLoginEvent} has
* been fired beforehand, and this event may not be called in parallel with
* {@link org.bukkit.event.player.AsyncPlayerPreLoginEvent} for the same connection.
* </p>
*
* <p>
* Cancelling the {@link org.bukkit.event.player.AsyncPlayerPreLoginEvent} guarantees the corresponding
* {@code PlayerConnectionCloseEvent} is never called.
* </p>
*
* <p>
* The event may be invoked asynchronously or synchronously. Plugins should check
* {@link Event#isAsynchronous()} and handle accordingly.
* </p>
*/
public class PlayerConnectionCloseEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();
@NotNull private final UUID playerUniqueId;
@NotNull private final String playerName;
@NotNull private final InetAddress ipAddress;
public PlayerConnectionCloseEvent(@NotNull final UUID playerUniqueId, @NotNull final String playerName, @NotNull final InetAddress ipAddress, final boolean async) {
super(async);
this.playerUniqueId = playerUniqueId;
this.playerName = playerName;
this.ipAddress = ipAddress;
}
/**
* Returns the {@code UUID} of the player disconnecting.
*/
@NotNull
public UUID getPlayerUniqueId() {
return this.playerUniqueId;
}
/**
* Returns the name of the player disconnecting.
*/
@NotNull
public String getPlayerName() {
return this.playerName;
}
/**
* Returns the player's IP address.
*/
@NotNull
public InetAddress getIpAddress() {
return this.ipAddress;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLERS;
}
}

View File

@@ -0,0 +1,85 @@
package com.destroystokyo.paper.event.player;
import org.bukkit.entity.Firework;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
* Fired when a player boosts elytra flight with a firework
*/
public class PlayerElytraBoostEvent extends PlayerEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private boolean cancelled = false;
@NotNull private final ItemStack itemStack;
@NotNull private Firework firework;
private boolean consume = true;
public PlayerElytraBoostEvent(@NotNull Player player, @NotNull ItemStack itemStack, @NotNull Firework firework) {
super(player);
this.itemStack = itemStack;
this.firework = firework;
}
/**
* Get the firework itemstack used
*
* @return ItemStack of firework
*/
@NotNull
public ItemStack getItemStack() {
return itemStack;
}
/**
* Get the firework entity that was spawned
*
* @return Firework entity
*/
@NotNull
public Firework getFirework() {
return firework;
}
/**
* Get whether to consume the firework or not
*
* @return True to consume
*/
public boolean shouldConsume() {
return consume;
}
/**
* Set whether to consume the firework or not
*
* @param consume True to consume
*/
public void setShouldConsume(boolean consume) {
this.consume = consume;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
}

View File

@@ -0,0 +1,221 @@
package com.destroystokyo.paper.event.player;
import org.apache.commons.lang.Validate;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* This event is fired during a player handshake.
*
* <p>If there are no listeners listening to this event, the logic default
* to your server platform will be ran.</p>
*
* <p>WARNING: TAMPERING WITH THIS EVENT CAN BE DANGEROUS</p>
*/
public class PlayerHandshakeEvent extends Event implements Cancellable {
private static final HandlerList HANDLERS = new HandlerList();
@NotNull private final String originalHandshake;
private boolean cancelled;
@Nullable private String serverHostname;
@Nullable private String socketAddressHostname;
@Nullable private UUID uniqueId;
@Nullable private String propertiesJson;
private boolean failed;
private String failMessage = "If you wish to use IP forwarding, please enable it in your BungeeCord config as well!";
/**
* Creates a new {@link PlayerHandshakeEvent}.
*
* @param originalHandshake the original handshake string
* @param cancelled if this event is enabled
*/
public PlayerHandshakeEvent(@NotNull String originalHandshake, boolean cancelled) {
this.originalHandshake = originalHandshake;
this.cancelled = cancelled;
}
/**
* Determines if this event is cancelled.
*
* <p>When this event is cancelled, custom handshake logic will not
* be processed.</p>
*
* @return {@code true} if this event is cancelled, {@code false} otherwise
*/
@Override
public boolean isCancelled() {
return this.cancelled;
}
/**
* Sets if this event is cancelled.
*
* <p>When this event is cancelled, custom handshake logic will not
* be processed.</p>
*
* @param cancelled {@code true} if this event is cancelled, {@code false} otherwise
*/
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
/**
* Gets the original handshake string.
*
* @return the original handshake string
*/
@NotNull
public String getOriginalHandshake() {
return this.originalHandshake;
}
/**
* Gets the server hostname string.
*
* <p>This should not include the port.</p>
*
* @return the server hostname string
*/
@Nullable
public String getServerHostname() {
return this.serverHostname;
}
/**
* Sets the server hostname string.
*
* <p>This should not include the port.</p>
*
* @param serverHostname the server hostname string
*/
public void setServerHostname(@NotNull String serverHostname) {
this.serverHostname = serverHostname;
}
/**
* Gets the socket address hostname string.
*
* <p>This should not include the port.</p>
*
* @return the socket address hostname string
*/
@Nullable
public String getSocketAddressHostname() {
return this.socketAddressHostname;
}
/**
* Sets the socket address hostname string.
*
* <p>This should not include the port.</p>
*
* @param socketAddressHostname the socket address hostname string
*/
public void setSocketAddressHostname(@NotNull String socketAddressHostname) {
this.socketAddressHostname = socketAddressHostname;
}
/**
* Gets the unique id.
*
* @return the unique id
*/
@Nullable
public UUID getUniqueId() {
return this.uniqueId;
}
/**
* Sets the unique id.
*
* @param uniqueId the unique id
*/
public void setUniqueId(@NotNull UUID uniqueId) {
this.uniqueId = uniqueId;
}
/**
* Gets the profile properties.
*
* <p>This should be a valid JSON string.</p>
*
* @return the profile properties, as JSON
*/
@Nullable
public String getPropertiesJson() {
return this.propertiesJson;
}
/**
* Determines if authentication failed.
*
* <p>When {@code true}, the client connecting will be disconnected
* with the {@link #getFailMessage() fail message}.</p>
*
* @return {@code true} if authentication failed, {@code false} otherwise
*/
public boolean isFailed() {
return this.failed;
}
/**
* Sets if authentication failed and the client should be disconnected.
*
* <p>When {@code true}, the client connecting will be disconnected
* with the {@link #getFailMessage() fail message}.</p>
*
* @param failed {@code true} if authentication failed, {@code false} otherwise
*/
public void setFailed(boolean failed) {
this.failed = failed;
}
/**
* Sets the profile properties.
*
* <p>This should be a valid JSON string.</p>
*
* @param propertiesJson the profile properties, as JSON
*/
public void setPropertiesJson(@NotNull String propertiesJson) {
this.propertiesJson = propertiesJson;
}
/**
* Gets the message to display to the client when authentication fails.
*
* @return the message to display to the client
*/
@NotNull
public String getFailMessage() {
return this.failMessage;
}
/**
* Sets the message to display to the client when authentication fails.
*
* @param failMessage the message to display to the client
*/
public void setFailMessage(@NotNull String failMessage) {
Validate.notEmpty(failMessage, "fail message cannot be null or empty");
this.failMessage = failMessage;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLERS;
}
}

View File

@@ -0,0 +1,47 @@
package com.destroystokyo.paper.event.player;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.NotNull;
public class PlayerInitialSpawnEvent extends PlayerEvent {
private static final HandlerList handlers = new HandlerList();
@NotNull private Location spawnLocation;
public PlayerInitialSpawnEvent(@NotNull final Player player, @NotNull final Location spawnLocation) {
super(player);
this.spawnLocation = spawnLocation;
}
/**
* Gets the current spawn location
*
* @return Location current spawn location
*/
@NotNull
public Location getSpawnLocation() {
return this.spawnLocation;
}
/**
* Sets the new spawn location
*
* @param spawnLocation new location for the spawn
*/
public void setSpawnLocation(@NotNull Location spawnLocation) {
this.spawnLocation = spawnLocation;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,106 @@
package com.destroystokyo.paper.event.player;
import com.google.common.base.Preconditions;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.NotNull;
/**
* Called when the server detects the player is jumping.
* <p>
* Added to avoid the overhead and special case logic that many plugins use
* when checking for jumps via PlayerMoveEvent, this event is fired whenever
* the server detects that the player is jumping.
*/
public class PlayerJumpEvent extends PlayerEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private boolean cancel = false;
@NotNull private Location from;
@NotNull private Location to;
public PlayerJumpEvent(@NotNull final Player player, @NotNull final Location from, @NotNull final Location to) {
super(player);
this.from = from;
this.to = to;
}
/**
* Gets the cancellation state of this event. A cancelled event will not
* be executed in the server, but will still pass to other plugins
* <p>
* If a jump event is cancelled, the player will be moved or
* teleported back to the Location as defined by getFrom(). This will not
* fire an event
*
* @return true if this event is cancelled
*/
public boolean isCancelled() {
return cancel;
}
/**
* Sets the cancellation state of this event. A cancelled event will not
* be executed in the server, but will still pass to other plugins
* <p>
* If a jump event is cancelled, the player will be moved or
* teleported back to the Location as defined by getFrom(). This will not
* fire an event
*
* @param cancel true if you wish to cancel this event
*/
public void setCancelled(boolean cancel) {
this.cancel = cancel;
}
/**
* Gets the location this player jumped from
*
* @return Location the player jumped from
*/
@NotNull
public Location getFrom() {
return from;
}
/**
* Sets the location to mark as where the player jumped from
*
* @param from New location to mark as the players previous location
*/
public void setFrom(@NotNull Location from) {
validateLocation(from);
this.from = from;
}
/**
* Gets the location this player jumped to
*
* This information is based on what the client sends, it typically
* has little relation to the arc of the jump at any given point.
*
* @return Location the player jumped to
*/
@NotNull
public Location getTo() {
return to;
}
private void validateLocation(Location loc) {
Preconditions.checkArgument(loc != null, "Cannot use null location!");
Preconditions.checkArgument(loc.getWorld() != null, "Cannot use location with null world!");
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,83 @@
package com.destroystokyo.paper.event.player;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
* Called when a player shoots a projectile
*/
public class PlayerLaunchProjectileEvent extends PlayerEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
@NotNull private final Projectile projectile;
@NotNull private final ItemStack itemStack;
private boolean consumeItem = true;
private boolean cancelled;
public PlayerLaunchProjectileEvent(@NotNull Player shooter, @NotNull ItemStack itemStack, @NotNull Projectile projectile) {
super(shooter);
this.itemStack = itemStack;
this.projectile = projectile;
}
/**
* Gets the projectile which will be launched by this event
*
* @return the launched projectile
*/
@NotNull
public Projectile getProjectile() {
return projectile;
}
/**
* Get the ItemStack used to fire the projectile
*
* @return The ItemStack used
*/
@NotNull
public ItemStack getItemStack() {
return itemStack;
}
/**
* Get whether to consume the ItemStack or not
*
* @return True to consume
*/
public boolean shouldConsume() {
return consumeItem;
}
/**
* Set whether to consume the ItemStack or not
*
* @param consumeItem True to consume
*/
public void setShouldConsume(boolean consumeItem) {
this.consumeItem = consumeItem;
}
public boolean isCancelled() {
return cancelled;
}
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,50 @@
package com.destroystokyo.paper.event.player;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
/**
* Called when the locale of the player is changed.
*
* @deprecated Replaced by {@link org.bukkit.event.player.PlayerLocaleChangeEvent} upstream
*/
@Deprecated
public class PlayerLocaleChangeEvent extends PlayerEvent {
private static final HandlerList handlers = new HandlerList();
private final String oldLocale;
private final String newLocale;
public PlayerLocaleChangeEvent(final Player player, final String oldLocale, final String newLocale) {
super(player);
this.oldLocale = oldLocale;
this.newLocale = newLocale;
}
/**
* Gets the locale the player switched from.
*
* @return player's old locale
*/
public String getOldLocale() {
return oldLocale;
}
/**
* Gets the locale the player is changed to.
*
* @return player's new locale
*/
public String getNewLocale() {
return newLocale;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) 2017 Daniel Ennis (Aikar) MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.destroystokyo.paper.event.player;
import org.bukkit.entity.ExperienceOrb;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.NotNull;
/**
* Fired when a player is attempting to pick up an experience orb
*/
public class PlayerPickupExperienceEvent extends PlayerEvent implements Cancellable {
@NotNull private final ExperienceOrb experienceOrb;
public PlayerPickupExperienceEvent(@NotNull Player player, @NotNull ExperienceOrb experienceOrb) {
super(player);
this.experienceOrb = experienceOrb;
}
/**
* @return Returns the Orb that the player is picking up
*/
@NotNull
public ExperienceOrb getExperienceOrb() {
return experienceOrb;
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
private boolean cancelled = false;
@Override
public boolean isCancelled() {
return cancelled;
}
/**
* If true, Cancels picking up the experience orb, leaving it in the world
* @param cancel true if you wish to cancel this event
*/
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
}

View File

@@ -0,0 +1,52 @@
package com.destroystokyo.paper.event.player;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.NotNull;
/**
* Fired after a player has respawned
*/
public class PlayerPostRespawnEvent extends PlayerEvent {
private final static HandlerList handlers = new HandlerList();
private final Location respawnedLocation;
private final boolean isBedSpawn;
public PlayerPostRespawnEvent(@NotNull final Player respawnPlayer, @NotNull final Location respawnedLocation, final boolean isBedSpawn) {
super(respawnPlayer);
this.respawnedLocation = respawnedLocation;
this.isBedSpawn = isBedSpawn;
}
/**
* Returns the location of the respawned player
*
* @return location of the respawned player
*/
@NotNull
public Location getRespawnedLocation() {
return respawnedLocation.clone();
}
/**
* Checks if the player respawned to their bed
*
* @return whether the player respawned to their bed
*/
public boolean isBedSpawn() {
return isBedSpawn;
}
@Override
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2018 Daniel Ennis (Aikar) MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.destroystokyo.paper.event.player;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
* Called when a player is firing a bow and the server is choosing an arrow to use.
*/
public class PlayerReadyArrowEvent extends PlayerEvent implements Cancellable {
@NotNull private final ItemStack bow;
@NotNull private final ItemStack arrow;
public PlayerReadyArrowEvent(@NotNull Player player, @NotNull ItemStack bow, @NotNull ItemStack arrow) {
super(player);
this.bow = bow;
this.arrow = arrow;
}
/**
* @return the player is using to fire the arrow
*/
@NotNull
public ItemStack getBow() {
return bow;
}
/**
* @return the arrow that is attempting to be used
*/
@NotNull
public ItemStack getArrow() {
return arrow;
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
private boolean cancelled = false;
/**
* Whether or not use of this arrow is cancelled. On cancel, the server will try the next arrow available and fire another event.
*/
@Override
public boolean isCancelled() {
return cancelled;
}
/**
* Cancel use of this arrow. On cancel, the server will try the next arrow available and fire another event.
* @param cancel true if you wish to cancel this event
*/
@Override
public void setCancelled(boolean cancel) {
cancelled = cancel;
}
}

View File

@@ -0,0 +1,67 @@
package com.destroystokyo.paper.event.player;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.NotNull;
/**
* Triggered when a player starts spectating an entity in spectator mode.
*/
public class PlayerStartSpectatingEntityEvent extends PlayerEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private boolean cancelled;
@NotNull private final Entity currentSpectatorTarget;
@NotNull private final Entity newSpectatorTarget;
public PlayerStartSpectatingEntityEvent(@NotNull Player player, @NotNull Entity currentSpectatorTarget, @NotNull Entity newSpectatorTarget) {
super(player);
this.currentSpectatorTarget = currentSpectatorTarget;
this.newSpectatorTarget = newSpectatorTarget;
}
/**
* Gets the entity that the player is currently spectating or themselves if they weren't spectating anything
*
* @return The entity the player is currently spectating (before they start spectating the new target).
*/
@NotNull
public Entity getCurrentSpectatorTarget() {
return currentSpectatorTarget;
}
/**
* Gets the new entity that the player will now be spectating
*
* @return The entity the player is now going to be spectating.
*/
@NotNull
public Entity getNewSpectatorTarget() {
return newSpectatorTarget;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,54 @@
package com.destroystokyo.paper.event.player;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.NotNull;
/**
* Triggered when a player stops spectating an entity in spectator mode.
*/
public class PlayerStopSpectatingEntityEvent extends PlayerEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private boolean cancelled;
@NotNull private final Entity spectatorTarget;
public PlayerStopSpectatingEntityEvent(@NotNull Player player, @NotNull Entity spectatorTarget) {
super(player);
this.spectatorTarget = spectatorTarget;
}
/**
* Gets the entity that the player is spectating
*
* @return The entity the player is currently spectating (before they will stop).
*/
@NotNull
public Entity getSpectatorTarget() {
return spectatorTarget;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,29 @@
package com.destroystokyo.paper.event.player;
import org.bukkit.Location;
import org.bukkit.block.EndGateway;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.jetbrains.annotations.NotNull;
/**
* Fired when a teleport is triggered for an End Gateway
*/
public class PlayerTeleportEndGatewayEvent extends PlayerTeleportEvent {
@NotNull private final EndGateway gateway;
public PlayerTeleportEndGatewayEvent(@NotNull Player player, @NotNull Location from, @NotNull Location to, @NotNull EndGateway gateway) {
super(player, from, to, PlayerTeleportEvent.TeleportCause.END_GATEWAY);
this.gateway = gateway;
}
/**
* The gateway triggering the teleport
*
* @return EndGateway used
*/
@NotNull
public EndGateway getGateway() {
return gateway;
}
}

View File

@@ -0,0 +1,46 @@
package com.destroystokyo.paper.event.player;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.jetbrains.annotations.NotNull;
public class PlayerUseUnknownEntityEvent extends PlayerEvent {
private static final HandlerList handlers = new HandlerList();
private final int entityId;
private final boolean attack;
@NotNull private final EquipmentSlot hand;
public PlayerUseUnknownEntityEvent(@NotNull Player who, int entityId, boolean attack, @NotNull EquipmentSlot hand) {
super(who);
this.entityId = entityId;
this.attack = attack;
this.hand = hand;
}
public int getEntityId() {
return this.entityId;
}
public boolean isAttack() {
return this.attack;
}
@NotNull
public EquipmentSlot getHand() {
return this.hand;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2018 Daniel Ennis (Aikar) MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.destroystokyo.paper.event.profile;
import com.destroystokyo.paper.profile.PlayerProfile;
import com.destroystokyo.paper.profile.ProfileProperty;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
/**
* Fired once a profiles additional properties (such as textures) has been filled
*/
public class FillProfileEvent extends Event {
@NotNull private final PlayerProfile profile;
public FillProfileEvent(@NotNull PlayerProfile profile) {
super(!org.bukkit.Bukkit.isPrimaryThread());
this.profile = profile;
}
/**
* @return The Profile that had properties filled
*/
@NotNull
public PlayerProfile getPlayerProfile() {
return profile;
}
/**
* Same as .getPlayerProfile().getProperties()
*
* @see PlayerProfile#getProperties()
* @return The new properties on the profile.
*/
@NotNull
public Set<ProfileProperty> getProperties() {
return profile.getProperties();
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,46 @@
package com.destroystokyo.paper.event.profile;
import com.destroystokyo.paper.profile.PlayerProfile;
import org.bukkit.Bukkit;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* Allows a plugin to be notified anytime AFTER a Profile has been looked up from the Mojang API
* This is an opportunity to view the response and potentially cache things.
*
* No guarantees are made about thread execution context for this event. If you need to know, check
* event.isAsync()
*/
public class LookupProfileEvent extends Event {
private static final HandlerList handlers = new HandlerList();
@NotNull private final PlayerProfile profile;
public LookupProfileEvent(@NotNull PlayerProfile profile) {
super(!Bukkit.isPrimaryThread());
this.profile = profile;
}
/**
* @return The profile that was recently looked up. This profile can be mutated
*/
@NotNull
public PlayerProfile getPlayerProfile() {
return profile;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2018 Daniel Ennis (Aikar) MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.destroystokyo.paper.event.profile;
import com.destroystokyo.paper.profile.PlayerProfile;
import com.destroystokyo.paper.profile.ProfileProperty;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.Collection;
import org.jetbrains.annotations.NotNull;
/**
* Fired when the server is requesting to fill in properties of an incomplete profile, such as textures.
*
* Allows plugins to pre populate cached properties and avoid a call to the Mojang API
*/
public class PreFillProfileEvent extends Event {
@NotNull private final PlayerProfile profile;
public PreFillProfileEvent(@NotNull PlayerProfile profile) {
super(!org.bukkit.Bukkit.isPrimaryThread());
this.profile = profile;
}
/**
* @return The profile that needs its properties filled
*/
@NotNull
public PlayerProfile getPlayerProfile() {
return profile;
}
/**
* Sets the properties on the profile, avoiding the call to the Mojang API
* Same as .getPlayerProfile().setProperties(properties);
*
* @see PlayerProfile#setProperties(Collection)
* @param properties The properties to set/append
*/
public void setProperties(@NotNull Collection<ProfileProperty> properties) {
profile.setProperties(properties);
}
private static final HandlerList handlers = new HandlerList();
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,108 @@
package com.destroystokyo.paper.event.profile;
import com.destroystokyo.paper.profile.PlayerProfile;
import com.destroystokyo.paper.profile.ProfileProperty;
import com.google.common.collect.ArrayListMultimap;
import org.bukkit.Bukkit;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Allows a plugin to intercept a Profile Lookup for a Profile by name
*
* At the point of event fire, the UUID and properties are unset.
*
* If a plugin sets the UUID, and optionally the properties, the API call to look up the profile may be skipped.
*
* No guarantees are made about thread execution context for this event. If you need to know, check
* event.isAsync()
*/
public class PreLookupProfileEvent extends Event {
private static final HandlerList handlers = new HandlerList();
@NotNull private final String name;
private UUID uuid;
@NotNull private Set<ProfileProperty> properties = new HashSet<>();
public PreLookupProfileEvent(@NotNull String name) {
super(!Bukkit.isPrimaryThread());
this.name = name;
}
/**
* @return Name of the profile
*/
@NotNull
public String getName() {
return name;
}
/**
* If this value is left null by the completion of the event call, then the server will
* trigger a call to the Mojang API to look up the UUID (Network Request), and subsequently, fire a
* {@link LookupProfileEvent}
*
* @return The UUID of the profile if it has already been provided by a plugin
*/
@Nullable
public UUID getUUID() {
return uuid;
}
/**
* Sets the UUID for this player name. This will skip the initial API call to find the players UUID.
*
* However, if Profile Properties are needed by the server, you must also set them or else an API call might still be made.
*
* @param uuid the UUID to set for the profile or null to reset
*/
public void setUUID(@Nullable UUID uuid) {
this.uuid = uuid;
}
/**
* @return The currently pending prepopulated properties.
* Any property in this Set will be automatically prefilled on this Profile
*/
@NotNull
public Set<ProfileProperty> getProfileProperties() {
return this.properties;
}
/**
* Clears any existing prepopulated properties and uses the supplied properties
* Any property in this Set will be automatically prefilled on this Profile
* @param properties The properties to add
*/
public void setProfileProperties(@NotNull Set<ProfileProperty> properties) {
this.properties = new HashSet<>();
this.properties.addAll(properties);
}
/**
* Adds any properties currently missing to the prepopulated properties set, replacing any that already were set.
* Any property in this Set will be automatically prefilled on this Profile
* @param properties The properties to add
*/
public void addProfileProperties(@NotNull Set<ProfileProperty> properties) {
this.properties.addAll(properties);
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright (c) 2017 - Daniel Ennis (Aikar) - MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.destroystokyo.paper.event.profile;
import com.destroystokyo.paper.profile.PlayerProfile;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Fires when the server needs to verify if a player is whitelisted.
*
* Plugins may override/control the servers whitelist with this event,
* and dynamically change the kick message.
*
*/
public class ProfileWhitelistVerifyEvent extends Event {
private static final HandlerList handlers = new HandlerList();
@NotNull private final PlayerProfile profile;
private final boolean whitelistEnabled;
private boolean whitelisted;
private final boolean isOp;
@Nullable private String kickMessage;
public ProfileWhitelistVerifyEvent(@NotNull final PlayerProfile profile, boolean whitelistEnabled, boolean whitelisted, boolean isOp, @Nullable String kickMessage) {
this.profile = profile;
this.whitelistEnabled = whitelistEnabled;
this.whitelisted = whitelisted;
this.isOp = isOp;
this.kickMessage = kickMessage;
}
/**
* @return the currently planned message to send to the user if they are not whitelisted
*/
@Nullable
public String getKickMessage() {
return kickMessage;
}
/**
* @param kickMessage The message to send to the player on kick if not whitelisted. May set to null to use the server configured default
*/
public void setKickMessage(@Nullable String kickMessage) {
this.kickMessage = kickMessage;
}
/**
* @return The profile of the player trying to connect
*/
@NotNull
public PlayerProfile getPlayerProfile() {
return profile;
}
/**
* @return Whether the player is whitelisted to play on this server (whitelist may be off is why its true)
*/
public boolean isWhitelisted() {
return whitelisted;
}
/**
* Changes the players whitelisted state. false will deny the login
* @param whitelisted The new whitelisted state
*/
public void setWhitelisted(boolean whitelisted) {
this.whitelisted = whitelisted;
}
/**
* @return if the player obtained whitelist status by having op
*/
public boolean isOp() {
return isOp;
}
/**
* @return if the server even has whitelist on
*/
public boolean isWhitelistEnabled() {
return whitelistEnabled;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,177 @@
/*
* Copyright (c) 2017 Daniel Ennis (Aikar) MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.destroystokyo.paper.event.server;
import com.google.common.collect.ImmutableList;
import org.apache.commons.lang.Validate;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Allows plugins to compute tab completion results asynchronously. If this event provides completions, then the standard synchronous process will not be fired to populate the results. However, the synchronous TabCompleteEvent will fire with the Async results.
*
* Only 1 process will be allowed to provide completions, the Async Event, or the standard process.
*/
public class AsyncTabCompleteEvent extends Event implements Cancellable {
@NotNull private final CommandSender sender;
@NotNull private final String buffer;
private final boolean isCommand;
@Nullable
private final Location loc;
@NotNull private List<String> completions;
private boolean cancelled;
private boolean handled = false;
private boolean fireSyncHandler = true;
public AsyncTabCompleteEvent(@NotNull CommandSender sender, @NotNull List<String> completions, @NotNull String buffer, boolean isCommand, @Nullable Location loc) {
super(true);
this.sender = sender;
this.completions = completions;
this.buffer = buffer;
this.isCommand = isCommand;
this.loc = loc;
}
/**
* Get the sender completing this command.
*
* @return the {@link CommandSender} instance
*/
@NotNull
public CommandSender getSender() {
return sender;
}
/**
* The list of completions which will be offered to the sender, in order.
* This list is mutable and reflects what will be offered.
*
* If this collection is not empty after the event is fired, then
* the standard process of calling {@link Command#tabComplete(CommandSender, String, String[])}
* or current player names will not be called.
*
* @return a list of offered completions
*/
@NotNull
public List<String> getCompletions() {
return completions;
}
/**
* Set the completions offered, overriding any already set.
* If this collection is not empty after the event is fired, then
* the standard process of calling {@link Command#tabComplete(CommandSender, String, String[])}
* or current player names will not be called.
*
* The passed collection will be cloned to a new List. You must call {{@link #getCompletions()}} to mutate from here
*
* @param completions the new completions
*/
public void setCompletions(@NotNull List<String> completions) {
Validate.notNull(completions);
this.completions = new ArrayList<>(completions);
}
/**
* Return the entire buffer which formed the basis of this completion.
*
* @return command buffer, as entered
*/
@NotNull
public String getBuffer() {
return buffer;
}
/**
* @return True if it is a command being tab completed, false if it is a chat message.
*/
public boolean isCommand() {
return isCommand;
}
/**
* @return The position looked at by the sender, or null if none
*/
@Nullable
public Location getLocation() {
return loc;
}
/**
* If true, the standard process of calling {@link Command#tabComplete(CommandSender, String, String[])}
* or current player names will not be called.
*
* @return Is completions considered handled. Always true if completions is not empty.
*/
public boolean isHandled() {
return !completions.isEmpty() || handled;
}
/**
* Sets whether or not to consider the completion request handled.
* If true, the standard process of calling {@link Command#tabComplete(CommandSender, String, String[])}
* or current player names will not be called.
*
* @param handled if this completion should be marked as being handled
*/
public void setHandled(boolean handled) {
this.handled = handled;
}
private static final HandlerList handlers = new HandlerList();
@Override
public boolean isCancelled() {
return cancelled;
}
/**
* Will provide no completions, and will not fire the synchronous process
* @param cancelled true if you wish to cancel this event
*/
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,411 @@
package com.destroystokyo.paper.event.server;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* This event is fired if server is getting queried over GS4 Query protocol
*
* Adapted from Velocity's ProxyQueryEvent
*
* @author Mark Vainomaa
*/
public final class GS4QueryEvent extends Event {
private static final HandlerList handlers = new HandlerList();
private final QueryType queryType;
private final InetAddress querierAddress;
private QueryResponse response;
public GS4QueryEvent(@NotNull QueryType queryType, @NotNull InetAddress querierAddress, @NotNull QueryResponse response) {
this.queryType = Preconditions.checkNotNull(queryType, "queryType");
this.querierAddress = Preconditions.checkNotNull(querierAddress, "querierAddress");
this.response = Preconditions.checkNotNull(response, "response");
}
/**
* Get query type
* @return query type
*/
@NotNull
public QueryType getQueryType() {
return queryType;
}
/**
* Get querier address
* @return querier address
*/
@NotNull
public InetAddress getQuerierAddress() {
return querierAddress;
}
/**
* Get query response
* @return query response
*/
@NotNull
public QueryResponse getResponse() {
return response;
}
/**
* Set query response
* @param response query response
*/
public void setResponse(@NotNull QueryResponse response) {
this.response = Preconditions.checkNotNull(response, "response");
}
@Override
public String toString() {
return "GS4QueryEvent{" +
"queryType=" + queryType +
", querierAddress=" + querierAddress +
", response=" + response +
'}';
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
/**
* The type of query
*/
public enum QueryType {
/**
* Basic query asks only a subset of information, such as motd, game type (hardcoded to <pre>MINECRAFT</pre>), map,
* current players, max players, server port and server motd
*/
BASIC,
/**
* Full query asks pretty much everything present on this event (only hardcoded values cannot be modified here).
*/
FULL
;
}
public final static class QueryResponse {
private final String motd;
private final String gameVersion;
private final String map;
private final int currentPlayers;
private final int maxPlayers;
private final String hostname;
private final int port;
private final Collection<String> players;
private final String serverVersion;
private final Collection<PluginInformation> plugins;
private QueryResponse(String motd, String gameVersion, String map, int currentPlayers, int maxPlayers, String hostname, int port, Collection<String> players, String serverVersion, Collection<PluginInformation> plugins) {
this.motd = motd;
this.gameVersion = gameVersion;
this.map = map;
this.currentPlayers = currentPlayers;
this.maxPlayers = maxPlayers;
this.hostname = hostname;
this.port = port;
this.players = players;
this.serverVersion = serverVersion;
this.plugins = plugins;
}
/**
* Get motd which will be used to reply to the query. By default it is {@link org.bukkit.Server#getMotd()}.
* @return motd
*/
@NotNull
public String getMotd() {
return motd;
}
/**
* Get game version which will be used to reply to the query. By default supported Minecraft versions range is sent.
* @return game version
*/
@NotNull
public String getGameVersion() {
return gameVersion;
}
/**
* Get map name which will be used to reply to the query. By default {@code world} is sent.
* @return map name
*/
@NotNull
public String getMap() {
return map;
}
/**
* Get current online player count which will be used to reply to the query.
* @return online player count
*/
public int getCurrentPlayers() {
return currentPlayers;
}
/**
* Get max player count which will be used to reply to the query.
* @return max player count
*/
public int getMaxPlayers() {
return maxPlayers;
}
/**
* Get server (public facing) hostname
* @return server hostname
*/
@NotNull
public String getHostname() {
return hostname;
}
/**
* Get server (public facing) port
* @return server port
*/
public int getPort() {
return port;
}
/**
* Get collection of players which will be used to reply to the query.
* @return collection of players
*/
@NotNull
public Collection<String> getPlayers() {
return players;
}
/**
* Get server software (name and version) which will be used to reply to the query.
* @return server software
*/
@NotNull
public String getServerVersion() {
return serverVersion;
}
/**
* Get list of plugins which will be used to reply to the query.
* @return collection of plugins
*/
@NotNull
public Collection<PluginInformation> getPlugins() {
return plugins;
}
/**
* Creates a new {@link Builder} instance from data represented by this response
* @return {@link QueryResponse} builder
*/
@NotNull
public Builder toBuilder() {
return QueryResponse.builder()
.motd(getMotd())
.gameVersion(getGameVersion())
.map(getMap())
.currentPlayers(getCurrentPlayers())
.maxPlayers(getMaxPlayers())
.hostname(getHostname())
.port(getPort())
.players(getPlayers())
.serverVersion(getServerVersion())
.plugins(getPlugins());
}
/**
* Creates a new {@link Builder} instance
* @return {@link QueryResponse} builder
*/
@NotNull
public static Builder builder() {
return new Builder();
}
/**
* A builder for {@link QueryResponse} objects.
*/
public static final class Builder {
private String motd;
private String gameVersion;
private String map;
private String hostname;
private String serverVersion;
private int currentPlayers;
private int maxPlayers;
private int port;
private List<String> players = new ArrayList<>();
private List<PluginInformation> plugins = new ArrayList<>();
private Builder() {}
@NotNull
public Builder motd(@NotNull String motd) {
this.motd = Preconditions.checkNotNull(motd, "motd");
return this;
}
@NotNull
public Builder gameVersion(@NotNull String gameVersion) {
this.gameVersion = Preconditions.checkNotNull(gameVersion, "gameVersion");
return this;
}
@NotNull
public Builder map(@NotNull String map) {
this.map = Preconditions.checkNotNull(map, "map");
return this;
}
@NotNull
public Builder currentPlayers(int currentPlayers) {
Preconditions.checkArgument(currentPlayers >= 0, "currentPlayers cannot be negative");
this.currentPlayers = currentPlayers;
return this;
}
@NotNull
public Builder maxPlayers(int maxPlayers) {
Preconditions.checkArgument(maxPlayers >= 0, "maxPlayers cannot be negative");
this.maxPlayers = maxPlayers;
return this;
}
@NotNull
public Builder hostname(@NotNull String hostname) {
this.hostname = Preconditions.checkNotNull(hostname, "hostname");
return this;
}
@NotNull
public Builder port(int port) {
Preconditions.checkArgument(port >= 1 && port <= 65535, "port must be between 1-65535");
this.port = port;
return this;
}
@NotNull
public Builder players(@NotNull Collection<String> players) {
this.players.addAll(Preconditions.checkNotNull(players, "players"));
return this;
}
@NotNull
public Builder players(@NotNull String... players) {
this.players.addAll(Arrays.asList(Preconditions.checkNotNull(players, "players")));
return this;
}
@NotNull
public Builder clearPlayers() {
this.players.clear();
return this;
}
@NotNull
public Builder serverVersion(@NotNull String serverVersion) {
this.serverVersion = Preconditions.checkNotNull(serverVersion, "serverVersion");
return this;
}
@NotNull
public Builder plugins(@NotNull Collection<PluginInformation> plugins) {
this.plugins.addAll(Preconditions.checkNotNull(plugins, "plugins"));
return this;
}
@NotNull
public Builder plugins(@NotNull PluginInformation... plugins) {
this.plugins.addAll(Arrays.asList(Preconditions.checkNotNull(plugins, "plugins")));
return this;
}
@NotNull
public Builder clearPlugins() {
this.plugins.clear();
return this;
}
/**
* Builds new {@link QueryResponse} with supplied data
* @return response
*/
@NotNull
public QueryResponse build() {
return new QueryResponse(
Preconditions.checkNotNull(motd, "motd"),
Preconditions.checkNotNull(gameVersion, "gameVersion"),
Preconditions.checkNotNull(map, "map"),
currentPlayers,
maxPlayers,
Preconditions.checkNotNull(hostname, "hostname"),
port,
ImmutableList.copyOf(players),
Preconditions.checkNotNull(serverVersion, "serverVersion"),
ImmutableList.copyOf(plugins)
);
}
}
/**
* Plugin information
*/
public static class PluginInformation {
private String name;
private String version;
public PluginInformation(@NotNull String name, @NotNull String version) {
this.name = Preconditions.checkNotNull(name, "name");
this.version = Preconditions.checkNotNull(version, "version");
}
@NotNull
public String getName() {
return name;
}
public void setName(@NotNull String name) {
this.name = name;
}
public void setVersion(@NotNull String version) {
this.version = version;
}
@NotNull
public String getVersion() {
return version;
}
@NotNull
public static PluginInformation of(@NotNull String name, @NotNull String version) {
return new PluginInformation(name, version);
}
}
}
}

View File

@@ -0,0 +1,323 @@
package com.destroystokyo.paper.event.server;
import static java.util.Objects.requireNonNull;
import com.destroystokyo.paper.network.StatusClient;
import com.destroystokyo.paper.profile.PlayerProfile;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.server.ServerListPingEvent;
import org.bukkit.util.CachedServerIcon;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Extended version of {@link ServerListPingEvent} that allows full control
* of the response sent to the client.
*/
public class PaperServerListPingEvent extends ServerListPingEvent implements Cancellable {
@NotNull private final StatusClient client;
private int numPlayers;
private boolean hidePlayers;
@NotNull private final List<PlayerProfile> playerSample = new ArrayList<>();
@NotNull private String version;
private int protocolVersion;
@Nullable private CachedServerIcon favicon;
private boolean cancelled;
private boolean originalPlayerCount = true;
private Object[] players;
public PaperServerListPingEvent(@NotNull StatusClient client, @NotNull String motd, int numPlayers, int maxPlayers,
@NotNull String version, int protocolVersion, @Nullable CachedServerIcon favicon) {
super(client.getAddress().getAddress(), motd, numPlayers, maxPlayers);
this.client = client;
this.numPlayers = numPlayers;
this.version = version;
this.protocolVersion = protocolVersion;
setServerIcon(favicon);
}
/**
* Returns the {@link StatusClient} pinging the server.
*
* @return The client
*/
@NotNull
public StatusClient getClient() {
return this.client;
}
/**
* {@inheritDoc}
*
* <p>Returns {@code -1} if players are hidden using
* {@link #shouldHidePlayers()}.</p>
*/
@Override
public int getNumPlayers() {
if (this.hidePlayers) {
return -1;
}
return this.numPlayers;
}
/**
* Sets the number of players displayed in the server list.
*
* <p>Note that this won't have any effect if {@link #shouldHidePlayers()}
* is enabled.</p>
*
* @param numPlayers The number of online players
*/
public void setNumPlayers(int numPlayers) {
if (this.numPlayers != numPlayers) {
this.numPlayers = numPlayers;
this.originalPlayerCount = false;
}
}
/**
* {@inheritDoc}
*
* <p>Returns {@code -1} if players are hidden using
* {@link #shouldHidePlayers()}.</p>
*/
@Override
public int getMaxPlayers() {
if (this.hidePlayers) {
return -1;
}
return super.getMaxPlayers();
}
/**
* Returns whether all player related information is hidden in the server
* list. This will cause {@link #getNumPlayers()}, {@link #getMaxPlayers()}
* and {@link #getPlayerSample()} to be skipped in the response.
*
* <p>The Vanilla Minecraft client will display the player count as {@code ???}
* when this option is enabled.</p>
*
* @return {@code true} if the player count is hidden
*/
public boolean shouldHidePlayers() {
return hidePlayers;
}
/**
* Sets whether all player related information is hidden in the server
* list. This will cause {@link #getNumPlayers()}, {@link #getMaxPlayers()}
* and {@link #getPlayerSample()} to be skipped in the response.
*
* <p>The Vanilla Minecraft client will display the player count as {@code ???}
* when this option is enabled.</p>
*
* @param hidePlayers {@code true} if the player count should be hidden
*/
public void setHidePlayers(boolean hidePlayers) {
this.hidePlayers = hidePlayers;
}
/**
* Returns a mutable list of {@link PlayerProfile} that will be displayed
* as online players on the client.
*
* <p>The Vanilla Minecraft client will display them when hovering the
* player count with the mouse.</p>
*
* @return The mutable player sample list
*/
@NotNull
public List<PlayerProfile> getPlayerSample() {
return this.playerSample;
}
/**
* Returns the version that will be sent as server version on the client.
*
* @return The server version
*/
@NotNull
public String getVersion() {
return version;
}
/**
* Sets the version that will be sent as server version to the client.
*
* @param version The server version
*/
public void setVersion(@NotNull String version) {
this.version = requireNonNull(version, "version");
}
/**
* Returns the protocol version that will be sent as the protocol version
* of the server to the client.
*
* @return The protocol version of the server, or {@code -1} if the server
* has not finished initialization yet
*/
public int getProtocolVersion() {
return protocolVersion;
}
/**
* Sets the protocol version that will be sent as the protocol version
* of the server to the client.
*
* @param protocolVersion The protocol version of the server
*/
public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
}
/**
* Gets the server icon sent to the client.
*
* @return The icon to send to the client, or {@code null} for none
*/
@Nullable
public CachedServerIcon getServerIcon() {
return this.favicon;
}
/**
* Sets the server icon sent to the client.
*
* @param icon The icon to send to the client, or {@code null} for none
*/
@Override
public void setServerIcon(@Nullable CachedServerIcon icon) {
if (icon != null && icon.isEmpty()) {
// Represent empty icons as null
icon = null;
}
this.favicon = icon;
}
/**
* {@inheritDoc}
*
* <p>Cancelling this event will cause the connection to be closed immediately,
* without sending a response to the client.</p>
*/
@Override
public boolean isCancelled() {
return this.cancelled;
}
/**
* {@inheritDoc}
*
* <p>Cancelling this event will cause the connection to be closed immediately,
* without sending a response to the client.</p>
*/
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
/**
* {@inheritDoc}
*
* <p><b>Note:</b> For compatibility reasons, this method will return all
* online players, not just the ones referenced in {@link #getPlayerSample()}.
* Removing a player will:</p>
*
* <ul>
* <li>Decrement the online player count (if and only if) the player
* count wasn't changed by another plugin before.</li>
* <li>Remove all entries from {@link #getPlayerSample()} that refer to
* the removed player (based on their {@link UUID}).</li>
* </ul>
*/
@NotNull
@Override
public Iterator<Player> iterator() {
if (this.players == null) {
this.players = getOnlinePlayers();
}
return new PlayerIterator();
}
@NotNull
protected Object[] getOnlinePlayers() {
return Bukkit.getOnlinePlayers().toArray();
}
@NotNull
protected Player getBukkitPlayer(@NotNull Object player) {
return (Player) player;
}
private final class PlayerIterator implements Iterator<Player> {
private int next;
private int current;
@Nullable private Player player;
@Override
public boolean hasNext() {
for (; this.next < players.length; this.next++) {
if (players[this.next] != null) {
return true;
}
}
return false;
}
@NotNull
@Override
public Player next() {
if (!hasNext()) {
this.player = null;
throw new NoSuchElementException();
}
this.current = this.next++;
return this.player = getBukkitPlayer(players[this.current]);
}
@Override
public void remove() {
if (this.player == null) {
throw new IllegalStateException();
}
UUID uniqueId = this.player.getUniqueId();
this.player = null;
// Remove player from iterator
players[this.current] = null;
// Remove player from sample
getPlayerSample().removeIf(p -> uniqueId.equals(p.getId()));
// Decrement player count
if (originalPlayerCount) {
numPlayers--;
}
}
}
}

View File

@@ -0,0 +1,41 @@
package com.destroystokyo.paper.event.server;
import com.google.common.base.Preconditions;
import org.apache.commons.lang.Validate;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import com.destroystokyo.paper.exception.ServerException;
import org.jetbrains.annotations.NotNull;
/**
* Called whenever an exception is thrown in a recoverable section of the server.
*/
public class ServerExceptionEvent extends Event {
private static final HandlerList handlers = new HandlerList();
@NotNull private ServerException exception;
public ServerExceptionEvent(@NotNull ServerException exception) {
this.exception = Preconditions.checkNotNull(exception, "exception");
}
/**
* Gets the wrapped exception that was thrown.
*
* @return Exception thrown
*/
@NotNull
public ServerException getException() {
return exception;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,59 @@
package com.destroystokyo.paper.event.server;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* Called when the server has finished ticking the main loop
*/
public class ServerTickEndEvent extends Event {
private static final HandlerList handlers = new HandlerList();
private final int tickNumber;
private final double tickDuration;
private final long timeEnd;
public ServerTickEndEvent(int tickNumber, double tickDuration, long timeRemaining) {
this.tickNumber = tickNumber;
this.tickDuration = tickDuration;
this.timeEnd = System.nanoTime() + timeRemaining;
}
/**
* @return What tick this was since start (first tick = 1)
*/
public int getTickNumber() {
return tickNumber;
}
/**
* @return Time in milliseconds of how long this tick took
*/
public double getTickDuration() {
return tickDuration;
}
/**
* Amount of nanoseconds remaining before the next tick should start.
*
* If this value is negative, then that means the server has exceeded the tick time limit and TPS has been lost.
*
* Method will continously return the updated time remaining value. (return value is not static)
*
* @return Amount of nanoseconds remaining before the next tick should start
*/
public long getTimeRemaining() {
return this.timeEnd - System.nanoTime();
}
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,32 @@
package com.destroystokyo.paper.event.server;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
public class ServerTickStartEvent extends Event {
private static final HandlerList handlers = new HandlerList();
private final int tickNumber;
public ServerTickStartEvent(int tickNumber) {
this.tickNumber = tickNumber;
}
/**
* @return What tick this is going be since start (first tick = 1)
*/
public int getTickNumber() {
return tickNumber;
}
@NotNull
public HandlerList getHandlers() {
return handlers;
}
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
}

Some files were not shown because too many files have changed in this diff Show More