mirror of
https://github.com/OvercastNetwork/ProjectAres.git
synced 2025-04-11 22:56:08 +02:00
Announce support
This commit is contained in:
parent
0562d80699
commit
b883ef5799
@ -3,6 +3,7 @@ package tc.oc.api;
|
|||||||
import tc.oc.api.document.DocumentsManifest;
|
import tc.oc.api.document.DocumentsManifest;
|
||||||
import tc.oc.api.engagement.EngagementModelManifest;
|
import tc.oc.api.engagement.EngagementModelManifest;
|
||||||
import tc.oc.api.games.GameModelManifest;
|
import tc.oc.api.games.GameModelManifest;
|
||||||
|
import tc.oc.api.http.HttpManifest;
|
||||||
import tc.oc.api.maps.MapModelManifest;
|
import tc.oc.api.maps.MapModelManifest;
|
||||||
import tc.oc.api.match.MatchModelManifest;
|
import tc.oc.api.match.MatchModelManifest;
|
||||||
import tc.oc.api.message.MessagesManifest;
|
import tc.oc.api.message.MessagesManifest;
|
||||||
@ -29,6 +30,7 @@ public final class ApiManifest extends HybridManifest {
|
|||||||
install(new DocumentsManifest());
|
install(new DocumentsManifest());
|
||||||
install(new MessagesManifest());
|
install(new MessagesManifest());
|
||||||
install(new ModelsManifest());
|
install(new ModelsManifest());
|
||||||
|
install(new HttpManifest());
|
||||||
|
|
||||||
install(new ServerModelManifest());
|
install(new ServerModelManifest());
|
||||||
install(new UserModelManifest());
|
install(new UserModelManifest());
|
||||||
|
@ -3,6 +3,8 @@ package tc.oc.api.http;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
@ -105,10 +107,6 @@ public class HttpClient implements Connectable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBaseUrl() {
|
|
||||||
return this.config.getBaseUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ListenableFuture<?> get(String path, HttpOption... options) {
|
public ListenableFuture<?> get(String path, HttpOption... options) {
|
||||||
return get(path, (TypeToken) null, options);
|
return get(path, (TypeToken) null, options);
|
||||||
}
|
}
|
||||||
@ -166,11 +164,17 @@ public class HttpClient implements Connectable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected <T> ListenableFuture<T> request(String method, String path, @Nullable Object content, @Nullable TypeToken<T> returnType, HttpOption...options) {
|
protected <T> ListenableFuture<T> request(String method, String path, @Nullable Object content, @Nullable TypeToken<T> returnType, HttpOption...options) {
|
||||||
|
final GenericUrl url;
|
||||||
|
try {
|
||||||
|
url = new GenericUrl(new URL(config.getBaseUrl(), path));
|
||||||
|
} catch(MalformedURLException e) {
|
||||||
|
throw new IllegalArgumentException(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: Serialization must happen synchronously, because getter methods may not be thread-safe
|
// NOTE: Serialization must happen synchronously, because getter methods may not be thread-safe
|
||||||
final HttpContent httpContent = content == null ? null : new Content(gson.toJson(content));
|
final HttpContent httpContent = content == null ? null : new Content(gson.toJson(content));
|
||||||
|
|
||||||
GenericUrl url = new GenericUrl(this.getBaseUrl() + path);
|
final HttpRequest request;
|
||||||
HttpRequest request;
|
|
||||||
try {
|
try {
|
||||||
request = requestFactory.buildRequest(method, url, httpContent).setThrowExceptionOnExecuteError(false);
|
request = requestFactory.buildRequest(method, url, httpContent).setThrowExceptionOnExecuteError(false);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package tc.oc.api.http;
|
package tc.oc.api.http;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
public interface HttpClientConfiguration {
|
public interface HttpClientConfiguration {
|
||||||
|
|
||||||
int DEFAULT_THREADS = 0;
|
int DEFAULT_THREADS = 0;
|
||||||
@ -10,7 +12,7 @@ public interface HttpClientConfiguration {
|
|||||||
/**
|
/**
|
||||||
* Base URL of the API. End points will be appended to this address.
|
* Base URL of the API. End points will be appended to this address.
|
||||||
*/
|
*/
|
||||||
String getBaseUrl();
|
URL getBaseUrl();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of threads to execute requests. 0 indicates an unbounded number
|
* Number of threads to execute requests. 0 indicates an unbounded number
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package tc.oc.api.http;
|
package tc.oc.api.http;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import tc.oc.commons.core.configuration.ConfigUtils;
|
||||||
import tc.oc.minecraft.api.configuration.Configuration;
|
import tc.oc.minecraft.api.configuration.Configuration;
|
||||||
import tc.oc.minecraft.api.configuration.ConfigurationSection;
|
import tc.oc.minecraft.api.configuration.ConfigurationSection;
|
||||||
|
|
||||||
@ -24,8 +26,8 @@ public class HttpClientConfigurationImpl implements HttpClientConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getBaseUrl() {
|
public URL getBaseUrl() {
|
||||||
return config.getString(BASE_URL_PATH);
|
return ConfigUtils.needUrl(config, BASE_URL_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -7,7 +7,7 @@ public class HttpManifest extends HybridManifest {
|
|||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
expose(HttpClient.class);
|
expose(HttpClient.class);
|
||||||
bind(HttpClient.class);
|
bind(HttpClient.class).asEagerSingleton();
|
||||||
bind(HttpClientConfiguration.class)
|
bind(HttpClientConfiguration.class)
|
||||||
.to(HttpClientConfigurationImpl.class);
|
.to(HttpClientConfigurationImpl.class);
|
||||||
}
|
}
|
||||||
|
@ -33,5 +33,3 @@ queue:
|
|||||||
logging:
|
logging:
|
||||||
root:
|
root:
|
||||||
level: INFO
|
level: INFO
|
||||||
tc-oc-api-bukkit-BukkitApi:
|
|
||||||
level: INFO
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package tc.oc.api.ocn;
|
package tc.oc.api.ocn;
|
||||||
|
|
||||||
import tc.oc.api.http.HttpManifest;
|
|
||||||
import tc.oc.api.minecraft.queue.MinecraftQueueManifest;
|
import tc.oc.api.minecraft.queue.MinecraftQueueManifest;
|
||||||
import tc.oc.api.model.ModelBinders;
|
import tc.oc.api.model.ModelBinders;
|
||||||
import tc.oc.commons.core.inject.HybridManifest;
|
import tc.oc.commons.core.inject.HybridManifest;
|
||||||
@ -11,6 +10,5 @@ public class OCNApiManifest extends HybridManifest implements ModelBinders {
|
|||||||
protected void configure() {
|
protected void configure() {
|
||||||
install(new OCNModelsManifest());
|
install(new OCNModelsManifest());
|
||||||
install(new MinecraftQueueManifest());
|
install(new MinecraftQueueManifest());
|
||||||
install(new HttpManifest());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,3 +224,6 @@ tnt.license.use.restricted = You need a TNT license to use TNT or Redstone on th
|
|||||||
item.locked = This item cannot be removed from its slot
|
item.locked = This item cannot be removed from its slot
|
||||||
|
|
||||||
stats.hotbar = {0} kills ({1} streak) {2} deaths {3} K/D
|
stats.hotbar = {0} kills ({1} streak) {2} deaths {3} K/D
|
||||||
|
|
||||||
|
announce.online = Announced server as online
|
||||||
|
announce.offline = Announced server as offline
|
||||||
|
@ -19,6 +19,7 @@ import tc.oc.pgm.freeze.FreezeListener;
|
|||||||
import tc.oc.pgm.listeners.BlockTransformListener;
|
import tc.oc.pgm.listeners.BlockTransformListener;
|
||||||
import tc.oc.pgm.listeners.MatchAnnouncer;
|
import tc.oc.pgm.listeners.MatchAnnouncer;
|
||||||
import tc.oc.pgm.listeners.PGMListener;
|
import tc.oc.pgm.listeners.PGMListener;
|
||||||
|
import tc.oc.pgm.listing.ListingManifest;
|
||||||
import tc.oc.pgm.map.MapLibrary;
|
import tc.oc.pgm.map.MapLibrary;
|
||||||
import tc.oc.pgm.map.MapLibraryImpl;
|
import tc.oc.pgm.map.MapLibraryImpl;
|
||||||
import tc.oc.pgm.map.MapLoader;
|
import tc.oc.pgm.map.MapLoader;
|
||||||
@ -56,6 +57,8 @@ public final class PGMManifest extends HybridManifest {
|
|||||||
install(new MatchPlayerEventRouter.Manifest());
|
install(new MatchPlayerEventRouter.Manifest());
|
||||||
install(new MatchAnalyticsManifest());
|
install(new MatchAnalyticsManifest());
|
||||||
|
|
||||||
|
install(new ListingManifest());
|
||||||
|
|
||||||
bind(MatchManager.class);
|
bind(MatchManager.class);
|
||||||
bind(MatchLoader.class);
|
bind(MatchLoader.class);
|
||||||
bind(MatchFinder.class).to(MatchLoader.class);
|
bind(MatchFinder.class).to(MatchLoader.class);
|
||||||
|
34
PGM/src/main/java/tc/oc/pgm/listing/ListingCommands.java
Normal file
34
PGM/src/main/java/tc/oc/pgm/listing/ListingCommands.java
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package tc.oc.pgm.listing;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.sk89q.minecraft.util.commands.Command;
|
||||||
|
import com.sk89q.minecraft.util.commands.CommandContext;
|
||||||
|
import com.sk89q.minecraft.util.commands.CommandException;
|
||||||
|
import com.sk89q.minecraft.util.commands.CommandPermissions;
|
||||||
|
import com.sk89q.minecraft.util.commands.SuggestException;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class ListingCommands {
|
||||||
|
|
||||||
|
private final ListingService listingService;
|
||||||
|
|
||||||
|
@Inject ListingCommands(ListingService listingService) {
|
||||||
|
this.listingService = listingService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command(
|
||||||
|
aliases = "announce",
|
||||||
|
usage = "[on|off]",
|
||||||
|
desc = "Announce the server to the public listing service",
|
||||||
|
min = 0,
|
||||||
|
max = 1
|
||||||
|
)
|
||||||
|
@CommandPermissions("pgm.listing.announce")
|
||||||
|
public void announce(CommandContext args, CommandSender sender) throws CommandException, SuggestException {
|
||||||
|
listingService.update("on".equals(args.tryString(0, ImmutableList.of("on", "off")).orElse("on")), sender);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package tc.oc.pgm.listing;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import tc.oc.commons.core.configuration.ConfigUtils;
|
||||||
|
import tc.oc.minecraft.api.configuration.Configuration;
|
||||||
|
import tc.oc.minecraft.api.configuration.ConfigurationSection;
|
||||||
|
import tc.oc.net.UriUtils;
|
||||||
|
|
||||||
|
class ListingConfiguration {
|
||||||
|
|
||||||
|
private final ConfigurationSection config;
|
||||||
|
|
||||||
|
@Inject ListingConfiguration(Configuration root) {
|
||||||
|
this.config = root.needSection("announce");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean enabled() {
|
||||||
|
return config.getBoolean("enabled", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public URL announceUrl() {
|
||||||
|
return ConfigUtils.getUrl(config, "url", UriUtils.url("https://oc.tc/announce"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String serverHost() {
|
||||||
|
return config.getString("server-host");
|
||||||
|
}
|
||||||
|
|
||||||
|
public OptionalInt serverPort() {
|
||||||
|
final int port = config.getInt("server-port", 0);
|
||||||
|
return port != 0 ? OptionalInt.of(port) : OptionalInt.empty();
|
||||||
|
}
|
||||||
|
}
|
21
PGM/src/main/java/tc/oc/pgm/listing/ListingManifest.java
Normal file
21
PGM/src/main/java/tc/oc/pgm/listing/ListingManifest.java
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package tc.oc.pgm.listing;
|
||||||
|
|
||||||
|
import tc.oc.commons.core.commands.CommandBinder;
|
||||||
|
import tc.oc.commons.core.inject.HybridManifest;
|
||||||
|
import tc.oc.minecraft.api.event.ListenerBinder;
|
||||||
|
|
||||||
|
public class ListingManifest extends HybridManifest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
bind(ListingConfiguration.class);
|
||||||
|
bind(ListingService.class).to(ListingServiceImpl.class);
|
||||||
|
|
||||||
|
final ListenerBinder listeners = new ListenerBinder(binder());
|
||||||
|
listeners.bindListener().to(PingListener.class);
|
||||||
|
listeners.bindListener().to(ListingServiceImpl.class);
|
||||||
|
|
||||||
|
new CommandBinder(binder())
|
||||||
|
.register(ListingCommands.class);
|
||||||
|
}
|
||||||
|
}
|
16
PGM/src/main/java/tc/oc/pgm/listing/ListingService.java
Normal file
16
PGM/src/main/java/tc/oc/pgm/listing/ListingService.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package tc.oc.pgm.listing;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import tc.oc.api.message.types.Reply;
|
||||||
|
|
||||||
|
public interface ListingService {
|
||||||
|
|
||||||
|
ListenableFuture<Reply> update(boolean online);
|
||||||
|
|
||||||
|
ListenableFuture<Reply> update(boolean online, CommandSender sender);
|
||||||
|
|
||||||
|
@Nullable String sessionDigest();
|
||||||
|
}
|
118
PGM/src/main/java/tc/oc/pgm/listing/ListingServiceImpl.java
Normal file
118
PGM/src/main/java/tc/oc/pgm/listing/ListingServiceImpl.java
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package tc.oc.pgm.listing;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import net.md_5.bungee.api.chat.TranslatableComponent;
|
||||||
|
import org.apache.commons.codec.binary.Hex;
|
||||||
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.ConsoleCommandSender;
|
||||||
|
import tc.oc.api.http.HttpClient;
|
||||||
|
import tc.oc.api.http.HttpOption;
|
||||||
|
import tc.oc.api.message.types.Reply;
|
||||||
|
import tc.oc.commons.bukkit.chat.Audiences;
|
||||||
|
import tc.oc.commons.core.commands.CommandFutureCallback;
|
||||||
|
import tc.oc.commons.core.concurrent.Flexecutor;
|
||||||
|
import tc.oc.minecraft.api.event.Enableable;
|
||||||
|
import tc.oc.minecraft.api.server.LocalServer;
|
||||||
|
import tc.oc.minecraft.scheduler.Sync;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class ListingServiceImpl implements ListingService, Enableable {
|
||||||
|
|
||||||
|
private final HttpClient http;
|
||||||
|
private final ListingConfiguration config;
|
||||||
|
private final LocalServer localServer;
|
||||||
|
private final SecureRandom random = new SecureRandom();
|
||||||
|
private final Flexecutor executor;
|
||||||
|
private final Audiences audiences;
|
||||||
|
private final ConsoleCommandSender console;
|
||||||
|
|
||||||
|
private boolean online;
|
||||||
|
private @Nullable String sessionId;
|
||||||
|
private @Nullable String sessionDigest;
|
||||||
|
|
||||||
|
@Inject ListingServiceImpl(HttpClient http, ListingConfiguration config, LocalServer localServer, @Sync(defer = true) Flexecutor executor, Audiences audiences, ConsoleCommandSender console) {
|
||||||
|
this.http = http;
|
||||||
|
this.config = config;
|
||||||
|
this.localServer = localServer;
|
||||||
|
this.executor = executor;
|
||||||
|
this.audiences = audiences;
|
||||||
|
this.console = console;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String sessionDigest() {
|
||||||
|
return sessionDigest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
if(config.enabled()) {
|
||||||
|
// Don't announce until we are ready to receive the ping
|
||||||
|
executor.execute(() -> update(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disable() {
|
||||||
|
if(online) {
|
||||||
|
update(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListenableFuture<Reply> update(boolean online) {
|
||||||
|
return update(online, console);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListenableFuture<Reply> update(boolean online, CommandSender sender) {
|
||||||
|
this.online = online;
|
||||||
|
|
||||||
|
if(sessionId == null) {
|
||||||
|
final byte[] bytes = new byte[20];
|
||||||
|
random.nextBytes(bytes);
|
||||||
|
sessionId = Hex.encodeHexString(bytes);
|
||||||
|
sessionDigest = DigestUtils.sha1Hex(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ListenableFuture<Reply> future = http.post(config.announceUrl().toString(), new ListingUpdate() {
|
||||||
|
@Override public @Nullable String host() {
|
||||||
|
return config.serverHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int port() {
|
||||||
|
return config.serverPort().orElseGet(localServer::getPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean online() {
|
||||||
|
return online;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String session() {
|
||||||
|
return sessionId;
|
||||||
|
}
|
||||||
|
}, Reply.class, HttpOption.INFINITE_RETRY);
|
||||||
|
|
||||||
|
executor.callback(
|
||||||
|
future,
|
||||||
|
CommandFutureCallback.onSuccess(sender, reply -> {
|
||||||
|
if(!online) {
|
||||||
|
sessionId = sessionDigest = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
audiences.get(sender).sendMessage(new TranslatableComponent(online ? "announce.online" : "announce.offline"));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
}
|
18
PGM/src/main/java/tc/oc/pgm/listing/ListingUpdate.java
Normal file
18
PGM/src/main/java/tc/oc/pgm/listing/ListingUpdate.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package tc.oc.pgm.listing;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import tc.oc.api.annotations.Serialize;
|
||||||
|
import tc.oc.api.docs.virtual.Document;
|
||||||
|
|
||||||
|
@Serialize
|
||||||
|
public interface ListingUpdate extends Document {
|
||||||
|
|
||||||
|
@Nullable String host();
|
||||||
|
|
||||||
|
int port();
|
||||||
|
|
||||||
|
boolean online();
|
||||||
|
|
||||||
|
String session();
|
||||||
|
}
|
54
PGM/src/main/java/tc/oc/pgm/listing/PingListener.java
Normal file
54
PGM/src/main/java/tc/oc/pgm/listing/PingListener.java
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package tc.oc.pgm.listing;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Provider;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.server.ServerListPingEvent;
|
||||||
|
import tc.oc.minecraft.api.event.Enableable;
|
||||||
|
import tc.oc.pgm.match.Match;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class PingListener implements Listener, Enableable {
|
||||||
|
|
||||||
|
private final Provider<Match> matchProvider;
|
||||||
|
private final ListingService listingService;
|
||||||
|
|
||||||
|
@Inject PingListener(Provider<Match> matchProvider, ListingService listingService) {
|
||||||
|
this.matchProvider = matchProvider;
|
||||||
|
this.listingService = listingService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
private void onPing(ServerListPingEvent event) {
|
||||||
|
event.getExtra().put("pgm", new Info());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Info {
|
||||||
|
private class Map {
|
||||||
|
final String name;
|
||||||
|
final @Nullable String icon;
|
||||||
|
|
||||||
|
private Map(String name, String icon) {
|
||||||
|
this.name = name;
|
||||||
|
this.icon = icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final @Nullable String session = listingService.sessionDigest();
|
||||||
|
final Map map;
|
||||||
|
final int participants;
|
||||||
|
final int observers;
|
||||||
|
|
||||||
|
Info() {
|
||||||
|
final Match match = matchProvider.get();
|
||||||
|
this.map = new Map(match.getMap().getName(),
|
||||||
|
match.getMap().getThumbnailUri().orElse(null));
|
||||||
|
this.participants = match.getParticipatingPlayers().size();
|
||||||
|
this.observers = match.getObservingPlayers().size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import java.io.IOException;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
@ -64,6 +65,10 @@ public class MapDefinition {
|
|||||||
return folder;
|
return folder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<String> getThumbnailUri() {
|
||||||
|
return getFolder().getThumbnailUri();
|
||||||
|
}
|
||||||
|
|
||||||
public String getDottedPath() {
|
public String getDottedPath() {
|
||||||
return Joiner.on(".").join(getFolder().getRelativePath());
|
return Joiner.on(".").join(getFolder().getRelativePath());
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import java.util.UUID;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import com.google.common.collect.Collections2;
|
||||||
import tc.oc.api.docs.AbstractModel;
|
import tc.oc.api.docs.AbstractModel;
|
||||||
import tc.oc.api.docs.SemanticVersion;
|
import tc.oc.api.docs.SemanticVersion;
|
||||||
import tc.oc.api.docs.virtual.MapDoc;
|
import tc.oc.api.docs.virtual.MapDoc;
|
||||||
@ -105,7 +106,7 @@ public class MapDocument extends AbstractModel implements MapDoc {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<String> images() {
|
public Collection<String> images() {
|
||||||
return folder.getThumbnails();
|
return Collections2.transform(folder.getImages(), path -> path.getFileName().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -9,14 +9,18 @@ import java.nio.file.Path;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import tc.oc.commons.core.util.Lazy;
|
||||||
import tc.oc.commons.core.util.Utils;
|
import tc.oc.commons.core.util.Utils;
|
||||||
|
import tc.oc.image.ImageUtils;
|
||||||
|
|
||||||
public class MapFolder {
|
public class MapFolder {
|
||||||
|
|
||||||
public static final String MAP_DESCRIPTION_FILE_NAME = "map.xml";
|
public static final String MAP_DESCRIPTION_FILE_NAME = "map.xml";
|
||||||
public static final String THUMBNAIL_FILE_NAME = "map.png";
|
public static final String IMAGE_FILE_NAME = "map.png";
|
||||||
|
public static final int THUMBNAIL_HEIGHT = 64;
|
||||||
|
|
||||||
public static boolean isMapFolder(Path path) {
|
public static boolean isMapFolder(Path path) {
|
||||||
return Files.isDirectory(path) && Files.isRegularFile(path.resolve(MAP_DESCRIPTION_FILE_NAME));
|
return Files.isDirectory(path) && Files.isRegularFile(path.resolve(MAP_DESCRIPTION_FILE_NAME));
|
||||||
@ -24,7 +28,17 @@ public class MapFolder {
|
|||||||
|
|
||||||
private final MapSource source;
|
private final MapSource source;
|
||||||
private final Path path;
|
private final Path path;
|
||||||
private Collection<String> thumbnails;
|
|
||||||
|
private final Lazy<Collection<Path>> images = Lazy.from(() -> {
|
||||||
|
final Path path = getAbsolutePath().resolve(IMAGE_FILE_NAME);
|
||||||
|
return Files.isRegularFile(path)? Collections.singleton(path)
|
||||||
|
: Collections.emptySet();
|
||||||
|
});
|
||||||
|
|
||||||
|
private final Lazy<Optional<String>> thumbnailUri = Lazy.from(
|
||||||
|
() -> getImages().stream().findAny()
|
||||||
|
.map(path -> ImageUtils.thumbnailUri(path, THUMBNAIL_HEIGHT))
|
||||||
|
);
|
||||||
|
|
||||||
public MapFolder(MapSource source, Path path) {
|
public MapFolder(MapSource source, Path path) {
|
||||||
this.source = source;
|
this.source = source;
|
||||||
@ -111,14 +125,11 @@ public class MapFolder {
|
|||||||
return getRelativeUrl(getRelativeDescriptionFilePath());
|
return getRelativeUrl(getRelativeDescriptionFilePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<String> getThumbnails() {
|
public Optional<String> getThumbnailUri() {
|
||||||
if(thumbnails == null) {
|
return thumbnailUri.get();
|
||||||
if(Files.isRegularFile(getAbsolutePath().resolve(THUMBNAIL_FILE_NAME))) {
|
}
|
||||||
thumbnails = Collections.singleton(THUMBNAIL_FILE_NAME);
|
|
||||||
} else {
|
public Collection<Path> getImages() {
|
||||||
thumbnails = Collections.emptySet();
|
return images.get();
|
||||||
}
|
|
||||||
}
|
|
||||||
return thumbnails;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,21 @@
|
|||||||
# If false, PGM will load its classes but not enable itself
|
# If false, PGM will load its classes but not enable itself
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
|
# Public PGM listing service
|
||||||
|
# --------------------------
|
||||||
|
# If announce is enabled, and this server is running the PGM plugin,
|
||||||
|
# the listing service will be notified whenever this server starts up
|
||||||
|
# or shuts down. If the server is reachable at the announced address,
|
||||||
|
# it will be included in the public list.
|
||||||
|
#
|
||||||
|
# WARNING: Enabling this will publish your IP address to the world,
|
||||||
|
# unless you set server-host to something else.
|
||||||
|
announce:
|
||||||
|
enabled: false # Announce this server?
|
||||||
|
# server-port: 25565 # Public port - defaults to whatever port is bound at startup
|
||||||
|
# server-host: myserver.com # Public hostname or IP - if not set, the listing service will use
|
||||||
|
# the IP address that the announcement originates from
|
||||||
|
|
||||||
# Variables accessible by the XML pre-processor
|
# Variables accessible by the XML pre-processor
|
||||||
environment:
|
environment:
|
||||||
ranked: false
|
ranked: false
|
||||||
|
@ -201,3 +201,7 @@ permissions:
|
|||||||
|
|
||||||
pgm.destroyable.edit:
|
pgm.destroyable.edit:
|
||||||
description: Allows the player to edit the properties of destroyables
|
description: Allows the player to edit the properties of destroyables
|
||||||
|
|
||||||
|
pgm.listing.announce:
|
||||||
|
description: Allows the /announce command
|
||||||
|
default: op
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package tc.oc.commons.core.configuration;
|
package tc.oc.commons.core.configuration;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -15,6 +17,7 @@ import java.time.Duration;
|
|||||||
import tc.oc.commons.core.util.Predicates;
|
import tc.oc.commons.core.util.Predicates;
|
||||||
import tc.oc.commons.core.util.TimeUtils;
|
import tc.oc.commons.core.util.TimeUtils;
|
||||||
import tc.oc.minecraft.api.configuration.ConfigurationSection;
|
import tc.oc.minecraft.api.configuration.ConfigurationSection;
|
||||||
|
import tc.oc.minecraft.api.configuration.InvalidConfigurationException;
|
||||||
|
|
||||||
public final class ConfigUtils {
|
public final class ConfigUtils {
|
||||||
private ConfigUtils() {}
|
private ConfigUtils() {}
|
||||||
@ -50,6 +53,26 @@ public final class ConfigUtils {
|
|||||||
return getDuration(section, path, null);
|
return getDuration(section, path, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static URL needUrl(ConfigurationSection section, String path) {
|
||||||
|
return parseUrl(section, path, section.needString(path), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @Nullable URL getUrl(ConfigurationSection section, String path) {
|
||||||
|
return getUrl(section, path, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static URL getUrl(ConfigurationSection section, String path, URL def) {
|
||||||
|
return parseUrl(section, path, section.getString(path), def);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static URL parseUrl(ConfigurationSection section, String path, @Nullable String value, URL def) {
|
||||||
|
try {
|
||||||
|
return value == null ? def : new URL(value);
|
||||||
|
} catch(MalformedURLException e) {
|
||||||
|
throw new InvalidConfigurationException(section, path, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void buildDeepMap(Map<String, Object> map, ConfigurationSection section, String prefix) {
|
private static void buildDeepMap(Map<String, Object> map, ConfigurationSection section, String prefix) {
|
||||||
for(String key : section.getKeys()) {
|
for(String key : section.getKeys()) {
|
||||||
final Object obj = section.get(key);
|
final Object obj = section.get(key);
|
||||||
|
49
Util/core/src/main/java/tc/oc/image/ImageUtils.java
Normal file
49
Util/core/src/main/java/tc/oc/image/ImageUtils.java
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package tc.oc.image;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import tc.oc.net.UriUtils;
|
||||||
|
|
||||||
|
public interface ImageUtils {
|
||||||
|
|
||||||
|
static BufferedImage resize(BufferedImage image, int width, int height) {
|
||||||
|
final BufferedImage resized = new BufferedImage(width, 64, image.getType());
|
||||||
|
final Graphics2D g = resized.createGraphics();
|
||||||
|
g.addRenderingHints(ImmutableMap.of(RenderingHints.KEY_INTERPOLATION,
|
||||||
|
RenderingHints.VALUE_INTERPOLATION_BILINEAR));
|
||||||
|
g.drawImage(image, 0, 0, width, height, null);
|
||||||
|
g.dispose();
|
||||||
|
return resized;
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[] png(BufferedImage image) {
|
||||||
|
try {
|
||||||
|
final ByteArrayOutputStream data = new ByteArrayOutputStream();
|
||||||
|
ImageIO.write(image, "png", data);
|
||||||
|
return data.toByteArray();
|
||||||
|
} catch(IOException e) {
|
||||||
|
throw Throwables.propagate(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[] thumbnail(Path path, int height) {
|
||||||
|
try {
|
||||||
|
final BufferedImage image = ImageIO.read(path.toFile());
|
||||||
|
final BufferedImage thumb = resize(image, height * image.getWidth() / image.getHeight(), height);
|
||||||
|
return png(thumb);
|
||||||
|
} catch(IOException e) {
|
||||||
|
throw Throwables.propagate(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String thumbnailUri(Path path, int height) {
|
||||||
|
return UriUtils.dataUri("image/png", thumbnail(path, height));
|
||||||
|
}
|
||||||
|
}
|
20
Util/core/src/main/java/tc/oc/net/UriUtils.java
Normal file
20
Util/core/src/main/java/tc/oc/net/UriUtils.java
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package tc.oc.net;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
public interface UriUtils {
|
||||||
|
|
||||||
|
static String dataUri(String mime, byte[] data) {
|
||||||
|
return "data:" + mime + ";base64," + Base64.getEncoder().encodeToString(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static URL url(String spec) {
|
||||||
|
try {
|
||||||
|
return new URL(spec);
|
||||||
|
} catch(MalformedURLException e) {
|
||||||
|
throw new IllegalArgumentException("Malformed URL: " + spec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
pom.xml
2
pom.xml
@ -92,7 +92,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-codec</groupId>
|
<groupId>commons-codec</groupId>
|
||||||
<artifactId>commons-codec</artifactId>
|
<artifactId>commons-codec</artifactId>
|
||||||
<version>1.3</version>
|
<version>1.8</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.sf.trove4j</groupId>
|
<groupId>net.sf.trove4j</groupId>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user