Add online (aka ReplayCenter) module

Add localization extra (formally known as LocalizedResourcePack)
Add version checker extra
This commit is contained in:
johni0702
2015-11-14 15:36:49 +01:00
parent 5e5172fd3f
commit 7c8dde3322
54 changed files with 1186 additions and 1189 deletions

View File

@@ -2,16 +2,15 @@ package com.replaymod.core;
import com.google.common.util.concurrent.ListenableFutureTask;
import com.replaymod.replay.ReplaySender;
import eu.crushedpixel.replaymod.api.ApiClient;
import eu.crushedpixel.replaymod.chat.ChatMessageHandler;
import eu.crushedpixel.replaymod.events.handlers.CrosshairRenderHandler;
import eu.crushedpixel.replaymod.events.handlers.GuiEventHandler;
import eu.crushedpixel.replaymod.events.handlers.MouseInputHandler;
import eu.crushedpixel.replaymod.events.handlers.TickAndRenderListener;
import eu.crushedpixel.replaymod.events.handlers.keyboard.KeyInputHandler;
import eu.crushedpixel.replaymod.localization.LocalizedResourcePack;
import eu.crushedpixel.replaymod.online.authentication.ConfigurationAuthData;
import eu.crushedpixel.replaymod.registry.*;
import eu.crushedpixel.replaymod.registry.KeybindRegistry;
import eu.crushedpixel.replaymod.registry.ReplayFileAppender;
import eu.crushedpixel.replaymod.registry.UploadedFileHandler;
import eu.crushedpixel.replaymod.renderer.CustomObjectRenderer;
import eu.crushedpixel.replaymod.renderer.PathPreviewRenderer;
import eu.crushedpixel.replaymod.renderer.SpectatorRenderer;
@@ -24,7 +23,6 @@ import eu.crushedpixel.replaymod.utils.TooltipRenderer;
import eu.crushedpixel.replaymod.video.rendering.Pipelines;
import lombok.Getter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.IResourcePack;
import net.minecraft.client.settings.GameSettings;
import net.minecraft.crash.CrashReport;
import net.minecraft.crash.CrashReportCategory;
@@ -99,12 +97,6 @@ public class ReplayMod {
@Deprecated
public static UploadedFileHandler uploadedFileHandler;
@Deprecated
public static DownloadedFileHandler downloadedFileHandler;
@Deprecated
public static FavoritedFileHandler favoritedFileHandler;
@Deprecated
public static RatedFileHandler ratedFileHandler;
@Deprecated
public static SpectatorRenderer spectatorRenderer;
@Deprecated
public static TooltipRenderer tooltipRenderer;
@@ -116,16 +108,10 @@ public class ReplayMod {
public static SoundHandler soundHandler = new SoundHandler();
@Deprecated
public static CrosshairRenderHandler crosshairRenderHandler;
@Deprecated
public static ApiClient apiClient;
private final KeyBindingRegistry keyBindingRegistry = new KeyBindingRegistry();
private final SettingsRegistry settingsRegistry = new SettingsRegistry();
@Getter
@Deprecated
private static boolean latestModVersion = true;
// The instance of your mod that Forge uses.
@Instance(MOD_ID)
public static ReplayMod instance;
@@ -153,27 +139,13 @@ public class ReplayMod {
config = new Configuration(event.getSuggestedConfigurationFile());
config.load();
settingsRegistry.setConfiguration(config);
ConfigurationAuthData authData = new ConfigurationAuthData(config);
apiClient = new ApiClient(authData);
authData.load(apiClient);
uploadedFileHandler = new UploadedFileHandler(event.getModConfigurationDirectory());
replaySettings = new ReplaySettings();
downloadedFileHandler = new DownloadedFileHandler();
favoritedFileHandler = new FavoritedFileHandler();
ratedFileHandler = new RatedFileHandler();
replayFileAppender = new ReplayFileAppender();
FMLCommonHandler.instance().bus().register(replayFileAppender);
//check if latest mod version
try {
latestModVersion = apiClient.isVersionUpToDate(getContainer().getVersion());
} catch(Exception e) {
e.printStackTrace();
}
}
@EventHandler
@@ -215,26 +187,6 @@ public class ReplayMod {
tooltipRenderer = new TooltipRenderer();
Thread localizedResourcePackLoader = new Thread(new Runnable() {
@Override
public void run() {
try {
@SuppressWarnings("unchecked")
List<IResourcePack> defaultResourcePacks = mc.defaultResourcePacks;
defaultResourcePacks.add(new LocalizedResourcePack());
mc.addScheduledTask(new Runnable() {
@Override
public void run() {
mc.refreshResources();
}
});
} catch(Exception e) {
e.printStackTrace();
}
}
}, "localizedResourcePackLoader");
localizedResourcePackLoader.start();
if (System.getProperty("replaymod.render.file") != null) {
final File file = new File(System.getProperty("replaymod.render.file"));
if (!file.exists()) {
@@ -382,6 +334,10 @@ public class ReplayMod {
}
}
public String getVersion() {
return getContainer().getVersion();
}
private void testIfMoeshAndExitMinecraft() {
if("currentPlayer".equals("Moesh")) {
System.exit(-1);

View File

@@ -0,0 +1,117 @@
package com.replaymod.extras;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableSet;
import com.replaymod.core.ReplayMod;
import com.replaymod.online.ReplayModOnline;
import com.replaymod.online.api.ApiClient;
import com.replaymod.online.api.ApiException;
import lombok.RequiredArgsConstructor;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.IResourcePack;
import net.minecraft.client.resources.data.IMetadataSection;
import net.minecraft.client.resources.data.IMetadataSerializer;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.common.Mod;
import org.apache.commons.lang3.StringEscapeUtils;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class LocalizationExtra implements Extra {
@Mod.Instance(ReplayModOnline.MOD_ID)
private static ReplayModOnline module;
@Override
public void register(ReplayMod mod) throws Exception {
final Minecraft mc = mod.getMinecraft();
Thread localizedResourcePackLoader = new Thread(new Runnable() {
@Override
public void run() {
try {
@SuppressWarnings("unchecked")
List<IResourcePack> defaultResourcePacks = mc.defaultResourcePacks;
defaultResourcePacks.add(new LocalizedResourcePack(module.getApiClient()));
mc.addScheduledTask(new Runnable() {
@Override
public void run() {
mc.refreshResources();
}
});
} catch(Exception e) {
e.printStackTrace();
}
}
}, "localizedResourcePackLoader");
localizedResourcePackLoader.start();
}
@RequiredArgsConstructor
public static class LocalizedResourcePack implements IResourcePack {
private final ApiClient apiClient;
private Map<String, String> availableLanguages = new HashMap<>();
private boolean websiteAvailable = true;
@Override
public InputStream getInputStream(ResourceLocation loc) {
if(!loc.getResourcePath().endsWith(".lang")) return null;
String langcode = loc.getResourcePath().split("/")[1].split("\\.")[0];
if(availableLanguages.containsKey(langcode)) return new ByteArrayInputStream(availableLanguages.get(langcode).getBytes(Charsets.UTF_8));
return null;
}
@Override
public boolean resourceExists(ResourceLocation loc) {
if(!(loc.getResourcePath().endsWith(".lang"))) return false;
String langcode = loc.getResourcePath().split("/")[1].split("\\.")[0];
if(availableLanguages.containsKey(langcode)) return true;
if(!websiteAvailable) return false;
try {
if (Boolean.parseBoolean(System.getProperty("replaymod.offline", "false"))) {
return false;
}
String lang = apiClient.getTranslation(langcode);
String prop = StringEscapeUtils.unescapeHtml4(lang);
availableLanguages.put(langcode, prop);
return true;
} catch (ApiException e) {
if (e.getError().getId() != 16) { // This language has not been translated
e.printStackTrace();
}
} catch(ConnectException ce) {
websiteAvailable = false;
ce.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
}
return false;
}
@Override
public Set getResourceDomains() {
return ImmutableSet.of("minecraft", "replaymod");
}
@Override
public IMetadataSection getPackMetadata(IMetadataSerializer p_135058_1_, String p_135058_2_) throws IOException {
return null;
}
@Override
public BufferedImage getPackImage() throws IOException {
return null;
}
@Override
public String getPackName() {
return "ReplayModLocalizationResourcePack";
}
}
}

View File

@@ -25,7 +25,9 @@ public class ReplayModExtras {
PlayerOverview.class,
UriSchemeExtra.class,
FullBrightness.class,
HotkeyButtons.class
HotkeyButtons.class,
LocalizationExtra.class,
VersionChecker.class
);
private Logger logger;

View File

@@ -0,0 +1,64 @@
package com.replaymod.extras;
import com.replaymod.core.ReplayMod;
import com.replaymod.online.ReplayModOnline;
import eu.crushedpixel.replaymod.utils.StringUtils;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.awt.*;
public class VersionChecker implements Extra {
@Mod.Instance(ReplayModOnline.MOD_ID)
private static ReplayModOnline module;
@Override
public void register(ReplayMod mod) throws Exception {
final String currentVersion = mod.getVersion();
new Thread(new Runnable() {
@Override
public void run() {
try {
boolean upToDate = module.getApiClient().isVersionUpToDate(currentVersion);
if (!upToDate) {
MinecraftForge.EVENT_BUS.register(VersionChecker.this);
}
} catch(Exception e) {
e.printStackTrace();
}
}
}, "ReplayMod-VersionChecker").start();
}
@SubscribeEvent
public void onDrawScreen(GuiScreenEvent.DrawScreenEvent.Post event) {
if (!(event.gui instanceof GuiMainMenu)) {
return;
}
int width = Math.max(100, event.gui.width / 2 - 100 - 10);
String[] lines = StringUtils.splitStringInMultipleRows(I18n.format("replaymod.gui.outdated"), width);
int maxLineWidth = 0;
for(String line : lines) {
int lineWidth = event.gui.mc.fontRendererObj.getStringWidth(line);
if(lineWidth > maxLineWidth) {
maxLineWidth = lineWidth;
}
}
Gui.drawRect(2, 77, 5 + maxLineWidth + 3, 80 + (lines.length * 10), 0x80FF0000);
int i = 0;
for(String line : lines) {
event.gui.mc.fontRendererObj.drawStringWithShadow(line, 5, 80 + (i * 10), Color.WHITE.getRGB());
i++;
}
}
}

View File

@@ -2,6 +2,9 @@ package com.replaymod.extras.urischeme;
import com.replaymod.core.ReplayMod;
import com.replaymod.extras.Extra;
import com.replaymod.online.ReplayModOnline;
import de.johni0702.minecraft.gui.container.GuiScreen;
import net.minecraftforge.fml.common.Mod;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
@@ -10,6 +13,9 @@ import java.net.ServerSocket;
import java.net.Socket;
public class UriSchemeExtra implements Extra {
@Mod.Instance(ReplayModOnline.MOD_ID)
private static ReplayModOnline module;
private ReplayMod mod;
@Override
@@ -72,17 +78,10 @@ public class UriSchemeExtra implements Extra {
}
private void loadReplay(int id) {
// TODO
// File file = ReplayMod.downloadedFileHandler.getFileForID(id);
// if (file == null) {
// FileInfo info = new FileInfo(id, null, null, null, 0, 0, 0, String.valueOf(id), false, 0);
// new GuiReplayDownloading(info).display();
// } else {
// try {
// ReplayHandler.startReplay(file);
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
try {
module.startReplay(id, "Replay #" + id, GuiScreen.wrap(null));
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,39 @@
package com.replaymod.online;
import net.minecraft.client.Minecraft;
import java.util.Random;
public class AuthenticationHash {
private static final Random random = new Random();
public AuthenticationHash() {
username = Minecraft.getMinecraft().getSession().getUsername();
currentTime = System.currentTimeMillis();
randomLong = random.nextLong();
hash = getAuthenticationHash();
}
public final String username;
public final long currentTime;
public final long randomLong;
public final String hash;
private String getAuthenticationHash() {
String md5 = username + currentTime + randomLong;
try {
java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
byte[] array = md.digest(md5.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : array) {
sb.append(Integer.toHexString((b & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString();
} catch (java.security.NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}

View File

@@ -0,0 +1,63 @@
package com.replaymod.online;
import com.replaymod.online.api.ApiClient;
import com.replaymod.online.api.AuthData;
import com.replaymod.online.api.replay.holders.AuthConfirmation;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.common.config.Property;
/**
* Auth data stored in a {@link Configuration}.
*/
public class ConfigurationAuthData implements AuthData {
private final Configuration config;
private String userName;
private String authKey;
public ConfigurationAuthData(Configuration config) {
this.config = config;
}
/**
* Loads the data from the configuration and checks for its validity.
* If the data is invalid, it is removed from the config.
* @param apiClient Api client used for validating the auth data
*/
public void load(ApiClient apiClient) {
Property property = config.get("authkey", "authkey", (String) null);
if (property != null) {
String authKey = property.getString();
AuthConfirmation result = apiClient.checkAuthkey(authKey);
if (result != null) {
this.authKey = authKey;
this.userName = result.getUsername();
} else {
setData(null, null);
}
}
}
@Override
public String getUserName() {
return userName;
}
@Override
public String getAuthKey() {
return authKey;
}
@Override
public void setData(String userName, String authKey) {
this.userName = userName;
this.authKey = authKey;
config.removeCategory(config.getCategory("authkey"));
if (authKey != null) {
// Note: .get() actually creates the entry with the default value if it doesn't exist
config.get("authkey", "authkey", authKey);
}
config.save();
}
}

View File

@@ -0,0 +1,110 @@
package com.replaymod.online;
import com.replaymod.core.ReplayMod;
import com.replaymod.online.api.ApiClient;
import com.replaymod.online.gui.GuiLoginPrompt;
import com.replaymod.online.gui.GuiReplayDownloading;
import com.replaymod.online.handler.GuiHandler;
import com.replaymod.replay.ReplayModReplay;
import de.johni0702.minecraft.gui.container.GuiScreen;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
@Mod(modid = ReplayModOnline.MOD_ID, useMetadata = true)
public class ReplayModOnline {
public static final String MOD_ID = "replaymod-online";
@Mod.Instance(MOD_ID)
public static ReplayModOnline instance;
@Mod.Instance(ReplayMod.MOD_ID)
private static ReplayMod core;
@Mod.Instance(ReplayModReplay.MOD_ID)
private static ReplayModReplay replayModule;
private Logger logger;
private File downloadsFolder = new File("replay_downloads");
private ApiClient apiClient;
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
logger = event.getModLog();
core.getSettingsRegistry().register(Setting.class);
Configuration config = new Configuration(event.getSuggestedConfigurationFile());
ConfigurationAuthData authData = new ConfigurationAuthData(config);
apiClient = new ApiClient(authData);
authData.load(apiClient);
}
@Mod.EventHandler
public void init(FMLInitializationEvent event) {
if (!downloadsFolder.mkdirs()) {
logger.warn("Failed to create downloads folder: " + downloadsFolder);
}
new GuiHandler(this).register();
}
@Mod.EventHandler
public void postInit(FMLPostInitializationEvent event) {
// Initial login prompt
if (!core.getSettingsRegistry().get(Setting.SKIP_LOGIN_PROMPT)) {
if (!isLoggedIn()) {
new GuiLoginPrompt(apiClient, null, null, false).display();
}
}
}
public ReplayMod getCore() {
return core;
}
public ReplayModReplay getReplayModule() {
return replayModule;
}
public Logger getLogger() {
return logger;
}
public ApiClient getApiClient() {
return apiClient;
}
public boolean isLoggedIn() {
return apiClient.isLoggedIn();
}
public File getDownloadsFolder() {
return downloadsFolder;
}
public File getDownloadedFile(int id) {
return new File(downloadsFolder, id + ".mcpr");
}
public boolean hasDownloaded(int id) {
return getDownloadedFile(id).exists();
}
public void startReplay(int id, String name, GuiScreen onDownloadCancelled) throws IOException {
File file = getDownloadedFile(id);
if (file.exists()) {
replayModule.startReplay(file);
} else {
new GuiReplayDownloading(onDownloadCancelled, this, id, name).display();
}
}
}

View File

@@ -0,0 +1,15 @@
package com.replaymod.online;
import com.replaymod.core.SettingsRegistry;
public final class Setting<T> extends SettingsRegistry.SettingKeys<T> {
public static final Setting<Boolean> SKIP_LOGIN_PROMPT = make("skipLoginPrompt", null, false);
private static <T> Setting<T> make(String key, String displayName, T defaultValue) {
return new Setting<>(key, displayName, defaultValue);
}
public Setting(String key, String displayString, T defaultValue) {
super("online", key, displayString == null ? null : "replaymod.gui.settings." + displayString, defaultValue);
}
}

View File

@@ -0,0 +1,268 @@
package com.replaymod.online.api;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.mojang.authlib.exceptions.AuthenticationException;
import com.replaymod.core.ReplayMod;
import com.replaymod.online.api.replay.ReplayModApiMethods;
import com.replaymod.online.api.replay.SearchQuery;
import com.replaymod.online.api.replay.holders.*;
import eu.crushedpixel.replaymod.gui.elements.listeners.ProgressUpdateListener;
import com.replaymod.online.AuthenticationHash;
import eu.crushedpixel.replaymod.utils.Api;
import net.minecraft.client.Minecraft;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
public class ApiClient {
private static final Minecraft mc = Minecraft.getMinecraft();
private static final Gson gson = new Gson();
private static final JsonParser jsonParser = new JsonParser();
private final AuthData authData;
public ApiClient(AuthData authData) {
this.authData = authData;
}
public boolean isLoggedIn() {
return authData.getUserName() != null && authData.getAuthKey() != null;
}
public String getAuthKey() {
return authData.getAuthKey();
}
public void register(String userName, String eMail, String password) throws AuthenticationException, IOException, ApiException {
AuthenticationHash authenticationHash = sessionserverJoin();
QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.register);
builder.put("username", userName);
builder.put("email", eMail);
builder.put("password", password);
builder.put("uuid", mc.getSession().getProfile().getId().toString());
builder.put("mcusername", authenticationHash.username);
builder.put("timelong", authenticationHash.currentTime);
builder.put("randomlong", authenticationHash.randomLong);
AuthKey auth = invokeAndReturn(builder, AuthKey.class);
authData.setData(userName, auth.getAuth());
}
public AuthData.AuthResult login(String userName, String password) {
try {
QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.login);
builder.put("user", userName);
builder.put("pw", password);
builder.put("mod", true);
AuthKey result = invokeAndReturn(builder, AuthKey.class);
authData.setData(userName, result.getAuth());
return AuthData.AuthResult.SUCCESS;
} catch(ApiException e) {
return AuthData.AuthResult.INVALID_DATA;
} catch(Exception e) {
return AuthData.AuthResult.IO_ERROR;
}
}
public AuthData.AuthResult logout() {
try {
authData.setData(null, null);
QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.logout);
builder.put("auth", authData.getAuthKey());
builder.put("mod", true);
invokeAndReturn(builder, Success.class);
return AuthData.AuthResult.SUCCESS;
} catch(ApiException e) {
return AuthData.AuthResult.INVALID_DATA;
} catch(Exception e) {
return AuthData.AuthResult.IO_ERROR;
}
}
private AuthenticationHash sessionserverJoin() throws AuthenticationException {
AuthenticationHash hash = new AuthenticationHash();
mc.getSessionService().joinServer(
mc.getSession().getProfile(), mc.getSession().getToken(), hash.hash);
return hash;
}
public AuthConfirmation checkAuthkey(String auth) {
try {
QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.check_authkey);
builder.put("auth", auth);
return invokeAndReturn(builder, AuthConfirmation.class);
} catch(Exception e) {
return null;
}
}
public FileInfo[] getFileInfo(List<Integer> ids) throws IOException, ApiException {
QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.file_details);
builder.put("id", buildListString(ids));
return invokeAndReturn(builder, FileInfo[].class);
}
public FileInfo[] searchFiles(SearchQuery query) throws IOException, ApiException {
QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.search);
return invokeAndReturn(builder.toString() + query.buildQuery(), SearchResult.class).getResults();
}
public String getTranslation(String languageCode) throws IOException, ApiException {
QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.get_language);
builder.put("language", languageCode);
return SimpleApiClient.invokeUrl(builder.toString());
}
public BufferedImage downloadThumbnail(int file) throws IOException {
QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.get_thumbnail);
builder.put("id", file);
URL url = new URL(builder.toString());
return ImageIO.read(url);
}
private boolean cancelDownload = false;
public void downloadFile(int file, File target, ProgressUpdateListener listener) throws IOException, ApiException {
cancelDownload = false;
QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.download_file);
builder.put("auth", authData.getAuthKey());
builder.put("id", file);
String url = builder.toString();
URL website = new URL(url);
HttpURLConnection con = (HttpURLConnection) website.openConnection();
int fileSize = con.getContentLength();
InputStream is = con.getInputStream();
if(con.getResponseCode() == 200) {
BufferedInputStream bin = new BufferedInputStream(is);
FileOutputStream fout = new FileOutputStream(target);
try {
final byte data[] = new byte[1024];
int count;
int read = 0;
while ((count = bin.read(data, 0, 1024)) != -1) {
if(cancelDownload) {
bin.close();
fout.close();
FileUtils.deleteQuietly(target);
return;
}
fout.write(data, 0, count);
read += count;
listener.onProgressChanged((float)(read)/fileSize);
}
} finally {
bin.close();
fout.close();
}
} else {
JsonElement element = jsonParser.parse(IOUtils.toString(is));
try {
ApiError err = gson.fromJson(element, ApiError.class);
if(err.getDesc() != null) {
throw new ApiException(err);
}
} catch(JsonParseException e) {
throw new ApiException(IOUtils.toString(is));
}
}
}
public void cancelDownload() {
this.cancelDownload = true;
}
public void rateFile(int file, Rating.RatingType rating) throws IOException, ApiException {
QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.rate_file);
builder.put("auth", authData.getAuthKey());
builder.put("id", file);
builder.put("rating", rating.getKey());
invokeAndReturn(builder, Success.class);
}
public FileRating[] getRatedFiles() throws IOException, ApiException {
QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.get_ratings);
builder.put("auth", authData.getAuthKey());
return invokeAndReturn(builder, RatedFiles.class).getRated();
}
public void favFile(int file, boolean fav) throws IOException, ApiException {
QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.fav_file);
builder.put("auth", authData.getAuthKey());
builder.put("id", file);
builder.put("fav", fav);
invokeAndReturn(builder, Success.class);
}
public int[] getFavorites() throws IOException, ApiException {
QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.get_favorites);
builder.put("auth", authData.getAuthKey());
return invokeAndReturn(builder, Favorites.class).getFavorited();
}
@Api
public void removeFile(int file) throws IOException, ApiException {
QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.remove_file);
builder.put("auth", authData.getAuthKey());
builder.put("id", file);
invokeAndReturn(builder, Success.class);
}
public boolean isVersionUpToDate(String versionIdentifier) throws IOException, ApiException {
//in a development environment, getContainer().getVersion() will return ${version}
if(versionIdentifier.equals("${version}")) return true;
QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.up_to_date);
builder.put("version", versionIdentifier);
builder.put("minecraft", ReplayMod.getMinecraftVersion());
return invokeAndReturn(builder, Success.class).isSuccess();
}
private <T> T invokeAndReturn(QueryBuilder builder, Class<T> classOfT) throws IOException, ApiException {
return invokeAndReturn(builder.toString(), classOfT);
}
private <T> T invokeAndReturn(String url, Class<T> classOfT) throws IOException, ApiException {
JsonElement ele = GsonApiClient.invokeJson(url);
return gson.fromJson(ele, classOfT);
}
@SuppressWarnings("rawtypes")
private String buildListString(List idList) {
if(idList == null) return null;
String ids = "";
Integer x = 0;
for(Object id : idList) {
x++;
ids += id.toString();
if(x != idList.size()) {
ids += ",";
}
}
return ids;
}
}

View File

@@ -0,0 +1,28 @@
package com.replaymod.online.api;
import com.replaymod.online.api.replay.holders.ApiError;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class ApiException extends Exception {
private static final long serialVersionUID = 349073390504232810L;
private ApiError error;
public ApiException(ApiError error) {
super(error.getTranslatedDesc());
this.error = error;
}
public ApiException(String error) {
super(error);
}
public ApiError getError() {
return error;
}
}

View File

@@ -0,0 +1,45 @@
package com.replaymod.online.api;
/**
* Represents a set of persistent authentication data.
*/
public interface AuthData {
/**
* Returns the user name of the authenticated user.
* @return user name or {@code null} if not logged in
*/
String getUserName();
/**
* Returns the authentication key of the authenticated user.
* @return auth key or {@code null} if not logged in
*/
String getAuthKey();
/**
* Store the authentication data after login.
* @param userName The user name
* @param authKey The authentication key
*/
void setData(String userName, String authKey);
/**
* Result of authentication operations.
*/
enum AuthResult {
/**
* The operation succeeded without errors.
*/
SUCCESS,
/**
* The data provided got rejected due to it being invalid.
*/
INVALID_DATA,
/**
* Operation could not be performed due to connectivity problems.
*/
IO_ERROR,
}
}

View File

@@ -0,0 +1,26 @@
package com.replaymod.online.api;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import org.apache.commons.lang3.StringEscapeUtils;
import java.io.IOException;
public class GsonApiClient {
private static final JsonParser parser = new JsonParser();
public static JsonElement invoke(QueryBuilder query) throws IOException, ApiException {
String apiResult = SimpleApiClient.invoke(query);
return wrapWithJson(apiResult);
}
public static JsonElement invokeJson(String url) throws IOException, ApiException {
String apiResult = StringEscapeUtils.unescapeHtml4(SimpleApiClient.invokeUrl(url).replace("&#34;", "\\\""));
return wrapWithJson(apiResult);
}
private static JsonElement wrapWithJson(String apiResult) {
return parser.parse(apiResult);
}
}

View File

@@ -0,0 +1,60 @@
package com.replaymod.online.api;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
public class QueryBuilder {
public String apiMethod;
public Map<String, String> paramMap;
public QueryBuilder(String apiMethod) {
this.apiMethod = apiMethod;
}
public void put(String key, Object value) {
if(key != null && value != null) {
if(paramMap == null) {
paramMap = new HashMap<String, String>();
}
paramMap.put(key, value.toString());
}
}
public void put(Map<String, Object> paraMap) {
if(paraMap == null) return;
for(String key : paraMap.keySet()) {
put(key, paraMap.get(key));
}
}
public String toString() {
if(apiMethod == null) throw new IllegalArgumentException("apiMethod may not be null");
StringBuilder sb = new StringBuilder();
// build base url
sb.append(apiMethod);
// process parameters
try {
if(paramMap != null) {
boolean first = true;
for(String paramName : paramMap.keySet()) {
if(first) sb.append("?");
if(!first) sb.append("&");
first = false;
sb.append(paramName);
sb.append("=");
String value = paramMap.get(paramName);
sb.append(URLEncoder.encode(value, "UTF-8"));
}
}
return sb.toString();
} catch(UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,123 @@
package com.replaymod.online.api;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.replaymod.online.api.replay.holders.ApiError;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
public class SimpleApiClient {
private static final JsonParser jsonParser = new JsonParser();
private static final Gson gson = new Gson();
/**
* Returns a Json String from the given QueryBuilder
*
* @param query The QueryBuilder to use
* @return A Json String from the API
* @throws IOException
* @throws ApiException
*/
public static String invoke(QueryBuilder query) throws IOException, ApiException {
return invokeImpl(query.toString());
}
/**
* Returns a Json String from the given URL
*
* @param url The URL to parse the Json from
* @return A Json String from the API
* @throws IOException
* @throws ApiException
*/
public static String invokeUrl(String url) throws IOException, ApiException {
return invokeImpl(url);
}
/**
* Returns a Json String from the API
*
* @param method The apiMethod to be called
* @param paramMap The parameters to apply
* @return A Json String from the API
* @throws IOException
* @throws ApiException
*/
public static String invoke(String method, Map<String, Object> paramMap) throws IOException, ApiException {
return invokeImpl(method, paramMap);
}
/**
* Returns a Json String from the API
*
* @param method The apiMethod to be called
* @return A Json String from the API
* @throws IOException
* @throws ApiException
*/
public static String invoke(String method) throws IOException, ApiException {
return invokeImpl(method, null);
}
private static String invokeImpl(String urlString) throws IOException, ApiException {
// read response
String responseContent = null;
InputStream is = null;
HttpURLConnection httpUrlConnection = null;
try {
URL url = new URL(urlString);
httpUrlConnection = (HttpURLConnection) url.openConnection();
httpUrlConnection.setRequestMethod("GET");
// give it 15 seconds to respond
httpUrlConnection.setReadTimeout(15 * 1000);
httpUrlConnection.connect();
int responseCode = httpUrlConnection.getResponseCode();
if(responseCode != 200) {
is = httpUrlConnection.getErrorStream();
if(is != null) {
responseContent = IOUtils.toString(is, "UTF-8");
} else {
responseContent = "";
}
try {
JsonObject response = jsonParser.parse(responseContent).getAsJsonObject();
throw new ApiException(gson.fromJson(response, ApiError.class));
} catch(JsonParseException e) {
throw new ApiException(responseContent);
}
}
is = httpUrlConnection.getInputStream();
responseContent = IOUtils.toString(is, "UTF-8");
} finally {
if(is != null) {
is.close();
}
if(httpUrlConnection != null) {
httpUrlConnection.disconnect();
}
}
return responseContent;
}
private static String invokeImpl(String method, Map<String, Object> paramMap) throws IOException, ApiException {
QueryBuilder queryBuilder = new QueryBuilder(method);
queryBuilder.put(paramMap);
return invokeImpl(queryBuilder.toString());
}
}

View File

@@ -0,0 +1,159 @@
package com.replaymod.online.api.replay;
import com.google.gson.Gson;
import com.replaymod.online.api.ApiClient;
import com.replaymod.online.api.replay.holders.ApiError;
import com.replaymod.online.api.replay.holders.Category;
import com.replaymod.online.gui.GuiUploadFile;
import lombok.RequiredArgsConstructor;
import net.minecraft.client.resources.I18n;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.List;
@RequiredArgsConstructor
public class FileUploader {
private static final Gson gson = new Gson();
private final ApiClient apiClient;
private boolean uploading = false;
private long filesize;
private long current;
private boolean cancel = false;
private GuiUploadFile parent;
public void uploadFile(GuiUploadFile gui, String filename, List<String> tags, File file, Category category, String description) {
boolean success = false;
String info = null;
try {
parent = gui;
gui.onStartUploading();
filesize = 0;
if(uploading) throw new RuntimeException("FileUploader is already uploading");
uploading = true;
String postData = "?auth=" + apiClient.getAuthKey() + "&category=" + category.getId();
if(tags.size() > 0) {
postData += "&tags=";
for(String tag : tags) {
postData += tag;
if(!tag.equals(tags.get(tags.size() - 1))) {
postData += ",";
}
}
}
if(description != null && description.length() > 0) {
postData += "&description=" + URLEncoder.encode(description, "UTF-8");
}
postData += "&name=" + URLEncoder.encode(filename, "UTF-8");
String url = ReplayModApiMethods.upload_file + postData;
HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
con.setUseCaches(false);
con.setDoOutput(true);
con.setRequestMethod("POST");
con.setChunkedStreamingMode(1024);
con.setRequestProperty("Connection", "Keep-Alive");
con.setRequestProperty("Cache-Control", "no-cache");
String boundary = "*****";
con.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
DataOutputStream request = new DataOutputStream(con.getOutputStream());
String crlf = "\r\n";
String twoHyphens = "--";
request.writeBytes(twoHyphens + boundary + crlf);
String attachmentName = "file";
String attachmentFileName = "file.mcpr";
request.writeBytes("Content-Disposition: form-data; name=\"" + attachmentName + "\";filename=\"" + attachmentFileName + "\"" + crlf);
request.writeBytes(crlf);
byte[] buf = new byte[1024];
FileInputStream fis = new FileInputStream(file);
filesize = fis.getChannel().size();
current = 0;
int len;
while((len = fis.read(buf)) != -1) {
request.write(buf);
current += len;
parent.onProgressChanged(getUploadProgress());
if(cancel) {
parent.onProgressChanged(0f);
uploading = false;
current = 0;
cancel = false;
parent.onFinishUploading(false, I18n.format("replaymod.gui.upload.canceled"));
fis.close();
return;
}
}
fis.close();
request.writeBytes(crlf);
request.writeBytes(twoHyphens + boundary + twoHyphens + crlf);
request.flush();
request.close();
success = false;
int responseCode = con.getResponseCode();
InputStream is;
if(responseCode == 200) {
success = true;
is = con.getInputStream();
} else {
success = false;
is = con.getErrorStream();
}
if(is != null) {
BufferedReader r = new BufferedReader(new InputStreamReader(is));
info = null;
String result = "";
while(r.ready()) {
result += r.readLine();
}
if(responseCode != 200) {
ApiError error = gson.fromJson(result, ApiError.class);
info = error.getTranslatedDesc();
}
}
con.disconnect();
if(info == null) info = I18n.format("replaymod.gui.unknownerror");
} catch(Exception e) {
success = false;
e.printStackTrace();
} finally {
parent.onFinishUploading(success, info);
uploading = false;
}
}
public float getUploadProgress() {
if(!uploading || filesize == 0) return 0;
return (float) ((double) current / (double) filesize);
}
public boolean isUploading() {
return uploading;
}
public void cancelUploading() {
cancel = true;
}
}

View File

@@ -0,0 +1,24 @@
package com.replaymod.online.api.replay;
public class ReplayModApiMethods {
public static final String REPLAYMOD_BASE_URL = "http://ReplayMod.com/api/";
public static final String register = REPLAYMOD_BASE_URL+"register";
public static final String check_authkey = REPLAYMOD_BASE_URL+"check_authkey";
public static final String login = REPLAYMOD_BASE_URL+"login";
public static final String logout = REPLAYMOD_BASE_URL+"logout";
public static final String file_details = REPLAYMOD_BASE_URL+"file_details";
public static final String upload_file = REPLAYMOD_BASE_URL+"upload_file";
public static final String download_file = REPLAYMOD_BASE_URL+"download_file";
public static final String get_thumbnail = REPLAYMOD_BASE_URL+"get_thumbnail";
public static final String remove_file = REPLAYMOD_BASE_URL+"remove_file";
public static final String rate_file = REPLAYMOD_BASE_URL+"rate_file";
public static final String get_ratings = REPLAYMOD_BASE_URL+"get_ratings";
public static final String fav_file = REPLAYMOD_BASE_URL+"fav_file";
public static final String get_favorites = REPLAYMOD_BASE_URL+"get_favorites";
public static final String check_auth = REPLAYMOD_BASE_URL+"check_auth";
public static final String get_language = REPLAYMOD_BASE_URL+"get_language";
public static final String search = REPLAYMOD_BASE_URL+"search";
public static final String up_to_date = REPLAYMOD_BASE_URL+"up_to_date";
}

View File

@@ -0,0 +1,43 @@
package com.replaymod.online.api.replay;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import java.lang.reflect.Field;
import java.net.URLEncoder;
@AllArgsConstructor
@NoArgsConstructor
public class SearchQuery {
public Boolean order, singleplayer;
public String player, tag, version, server, name, auth;
public Integer category, offset;
public String buildQuery() {
String query = "";
boolean first = true;
//Please don't slaughter me for this code,
//even if I deserve it, which I certainly do.
for(Field f : this.getClass().getDeclaredFields()) {
try {
Object value = f.get(this);
if(value == null) continue;
query += first ? "?" : "&";
first = false;
query += f.getName() + "=";
query += URLEncoder.encode(String.valueOf(value), "UTF-8");
} catch(Exception e) {
e.printStackTrace();
}
}
return query;
}
@Override
public String toString() {
return buildQuery();
}
}

View File

@@ -0,0 +1,22 @@
package com.replaymod.online.api.replay.holders;
import lombok.Data;
import net.minecraft.client.resources.I18n;
@Data
public class ApiError {
private int id;
private String desc;
private String key;
private String[] objects;
public String getTranslatedDesc() {
try {
return I18n.format(key, (Object[]) objects);
} catch(Exception e) {
e.printStackTrace();
return desc;
}
}
}

View File

@@ -0,0 +1,9 @@
package com.replaymod.online.api.replay.holders;
import lombok.Data;
@Data
public class AuthConfirmation {
private String username;
private int id;
}

View File

@@ -0,0 +1,12 @@
package com.replaymod.online.api.replay.holders;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AuthKey {
private String auth;
}

View File

@@ -0,0 +1,53 @@
package com.replaymod.online.api.replay.holders;
import net.minecraft.client.resources.I18n;
public enum Category {
SURVIVAL(0, "replaymod.category.survival"), MINIGAME(1, "replaymod.category.minigame"),
BUILD(2, "replaymod.category.build"), MISCELLANEOUS(3, "replaymod.category.misc");
private int id;
private String name;
Category(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return this.id;
}
public static Category fromId(int id) {
for(Category c : values()) {
if(c.id == id) return c;
}
return null;
}
public String toNiceString() {
return I18n.format(this.name);
}
public Category next() {
for(int i = 0; i < values().length; i++) {
if(values()[i] == this) {
if(i == values().length - 1) {
i = -1;
}
return values()[i + 1];
}
}
return this;
}
public static String[] stringValues() {
String[] values = new String[Category.values().length];
int i = 0;
for (Category c : Category.values()) {
values[i++] = c.toNiceString();
}
return values;
}
}

View File

@@ -0,0 +1,16 @@
package com.replaymod.online.api.replay.holders;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
@Data
public class Donated {
@Getter(AccessLevel.NONE)
private boolean donated = false;
public boolean hasDonated() {
return donated;
}
}

View File

@@ -0,0 +1,8 @@
package com.replaymod.online.api.replay.holders;
import lombok.Data;
@Data
public class Favorites {
private int[] favorited;
}

View File

@@ -0,0 +1,27 @@
package com.replaymod.online.api.replay.holders;
import de.johni0702.replaystudio.replay.ReplayMetaData;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
@Data
@AllArgsConstructor
public class FileInfo {
private int id;
private ReplayMetaData metadata;
private String owner;
private Rating ratings;
private int size;
private int category;
private int downloads;
private String name;
@Getter(AccessLevel.NONE)
private boolean thumbnail;
private int favorites;
public boolean hasThumbnail() {
return thumbnail;
}
}

View File

@@ -0,0 +1,10 @@
package com.replaymod.online.api.replay.holders;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
@Data
public class FileRating {
private int file;
@SerializedName("rating") private boolean ratingPositive;
}

View File

@@ -0,0 +1,21 @@
package com.replaymod.online.api.replay.holders;
public enum MinecraftVersion {
MC_1_8("Minecraft 1.8", "1.8");
private String niceName, apiName;
MinecraftVersion(String niceName, String apiName) {
this.niceName = niceName;
this.apiName = apiName;
}
public String toNiceName() {
return niceName;
}
public String getApiName() {
return apiName;
}
}

View File

@@ -0,0 +1,8 @@
package com.replaymod.online.api.replay.holders;
import lombok.Data;
@Data
public class RatedFiles {
private FileRating[] rated;
}

View File

@@ -0,0 +1,26 @@
package com.replaymod.online.api.replay.holders;
import lombok.Data;
@Data
public class Rating {
private int negative, positive;
public enum RatingType {
LIKE("like"), DISLIKE("dislike"), NEUTRAL("neutral");
private String key;
public String getKey() { return key; }
RatingType(String key) {
this.key = key;
}
public static RatingType fromBoolean(Boolean rating) {
return rating == null ? RatingType.NEUTRAL :
(rating ? RatingType.LIKE : RatingType.DISLIKE);
}
}
}

View File

@@ -0,0 +1,8 @@
package com.replaymod.online.api.replay.holders;
import lombok.Data;
@Data
public class SearchResult {
private FileInfo[] results;
}

View File

@@ -0,0 +1,8 @@
package com.replaymod.online.api.replay.holders;
import lombok.Data;
@Data
public class Success {
private boolean success = false;
}

View File

@@ -0,0 +1,10 @@
package com.replaymod.online.api.replay.holders;
import lombok.Data;
@Data
public class UserFiles {
private String user;
private FileInfo[] files;
private int total_size;
}

View File

@@ -0,0 +1,68 @@
package com.replaymod.online.api.replay.pagination;
import com.replaymod.online.ReplayModOnline;
import com.replaymod.online.api.replay.holders.FileInfo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class DownloadedFilePagination implements Pagination {
private final ReplayModOnline mod;
private int page;
private HashMap<Integer, FileInfo> files = new HashMap<>();
public DownloadedFilePagination(ReplayModOnline mod) {
this.mod = mod;
this.page = -1;
}
@Override
public List<FileInfo> getFiles() {
return new ArrayList<>(files.values());
}
@Override
public int getLoadedPages() {
return page;
}
@Override
public boolean fetchPage() {
page++;
List<Integer> toAdd = new ArrayList<>();
for(String fileName : mod.getDownloadsFolder().list()) {
int i;
try {
i = Integer.parseInt(fileName.substring(0, fileName.indexOf('.')));
} catch (NumberFormatException e) {
e.printStackTrace();
continue;
}
if(!files.containsKey(i)) {
toAdd.add(i);
if(toAdd.size() >= Pagination.PAGE_SIZE) break;
}
}
try {
FileInfo[] fis = mod.getApiClient().getFileInfo(toAdd);
if(fis.length < 1) {
page--;
return false;
}
for(FileInfo info : fis) {
files.put(info.getId(), info);
}
return true;
} catch(Exception e) {
e.printStackTrace();
}
page--;
return false;
}
}

View File

@@ -0,0 +1,62 @@
package com.replaymod.online.api.replay.pagination;
import com.replaymod.online.api.ApiClient;
import com.replaymod.online.api.replay.holders.FileInfo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class FavoritedFilePagination implements Pagination {
private final ApiClient apiClient;
private int page;
private HashMap<Integer, FileInfo> files = new HashMap<>();
public FavoritedFilePagination(ApiClient apiClient) {
this.apiClient = apiClient;
this.page = -1;
}
@Override
public List<FileInfo> getFiles() {
return new ArrayList<>(files.values());
}
@Override
public int getLoadedPages() {
return page;
}
@Override
public boolean fetchPage() {
page++;
try {
int[] f = apiClient.getFavorites();
List<Integer> toAdd = new ArrayList<>();
for(int i : f) {
if(!files.containsKey(i)) {
toAdd.add(i);
if(toAdd.size() >= Pagination.PAGE_SIZE) break;
}
}
FileInfo[] fis = apiClient.getFileInfo(toAdd);
if(fis.length < 1) {
page--;
return false;
}
for(FileInfo info : fis) {
files.put(info.getId(), info);
}
return true;
} catch(Exception e) {
e.printStackTrace();
}
page--;
return false;
}
}

View File

@@ -0,0 +1,15 @@
package com.replaymod.online.api.replay.pagination;
import com.replaymod.online.api.replay.holders.FileInfo;
import java.util.List;
public interface Pagination {
List<FileInfo> getFiles();
int getLoadedPages();
boolean fetchPage();
int PAGE_SIZE = 30; //defined by the Website API
}

View File

@@ -0,0 +1,56 @@
package com.replaymod.online.api.replay.pagination;
import com.replaymod.online.api.ApiClient;
import com.replaymod.online.api.replay.SearchQuery;
import com.replaymod.online.api.replay.holders.FileInfo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class SearchPagination implements Pagination {
private final ApiClient apiClient;
private final SearchQuery searchQuery;
private int page;
private List<FileInfo> files = new ArrayList<FileInfo>();
public SearchPagination(ApiClient apiClient, SearchQuery searchQuery) {
this.apiClient = apiClient;
this.page = -1;
this.searchQuery = searchQuery;
}
@Override
public List<FileInfo> getFiles() {
return Collections.unmodifiableList(files);
}
@Override
public int getLoadedPages() {
return page;
}
@Override
public boolean fetchPage() {
page++;
searchQuery.offset = page;
try {
FileInfo[] fis = apiClient.searchFiles(searchQuery);
if(fis.length < 1) {
page--;
return false;
}
files.addAll(Arrays.asList(fis));
return true;
} catch(Exception e) {
e.printStackTrace();
}
page--;
return false;
}
}

View File

@@ -0,0 +1,127 @@
package com.replaymod.online.gui;
import com.replaymod.online.api.ApiClient;
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
import de.johni0702.minecraft.gui.container.GuiScreen;
import de.johni0702.minecraft.gui.element.GuiButton;
import de.johni0702.minecraft.gui.element.GuiLabel;
import de.johni0702.minecraft.gui.element.GuiPasswordField;
import de.johni0702.minecraft.gui.element.GuiTextField;
import de.johni0702.minecraft.gui.layout.CustomLayout;
import eu.crushedpixel.replaymod.gui.GuiConstants;
public class GuiLoginPrompt extends AbstractGuiScreen<GuiLoginPrompt> {
private final GuiScreen parent, successScreen;
private final ApiClient apiClient;
private GuiLabel usernameLabel = new GuiLabel(this).setI18nText("replaymod.gui.username");
private GuiLabel passwordLabel = new GuiLabel(this).setI18nText("replaymod.gui.password");
private GuiLabel noAccountLabel = new GuiLabel(this).setI18nText("replaymod.gui.login.noacc");
private GuiLabel statusLabel = new GuiLabel(this);
private GuiButton loginButton = new GuiButton(this).setI18nLabel("replaymod.gui.login").setSize(150, 20).setEnabled(false);
private GuiButton cancelButton = new GuiButton(this).setI18nLabel("replaymod.gui.cancel").setSize(150, 20);
private GuiButton registerButton = new GuiButton(this).setI18nLabel("replaymod.gui.register").setSize(150, 20);
private GuiTextField username = new GuiTextField(this).setSize(145, 20).setMaxLength(16).setFocused(true);
private GuiPasswordField password = new GuiPasswordField(this).setSize(145, 20).setNext(username).setPrevious(username)
.setMaxLength(GuiConstants.MAX_PW_LENGTH);
{
Runnable doLogin = new Runnable() {
@Override
public void run() {
if (!loginButton.isEnabled()) return;
statusLabel.setI18nText("replaymod.gui.login.logging");
new Thread(new Runnable() {
@Override
public void run() {
switch (apiClient.login(username.getText(), password.getText())) {
case SUCCESS:
statusLabel.setText("");
getMinecraft().addScheduledTask(new Runnable() {
@Override
public void run() {
successScreen.display();
}
});
break;
case INVALID_DATA:
statusLabel.setI18nText("replaymod.gui.login.incorrect");
break;
case IO_ERROR:
statusLabel.setI18nText("replaymod.gui.login.connectionerror");
break;
}
}
}, "replaymod-auth").start();
}
};
username.onEnter(doLogin);
password.onEnter(doLogin);
loginButton.onClick(doLogin);
cancelButton.onClick(new Runnable() {
@Override
public void run() {
successScreen.display();
}
});
registerButton.onClick(new Runnable() {
@Override
public void run() {
new GuiRegister(apiClient, GuiLoginPrompt.this).display();
}
});
Runnable contentValidation = new Runnable() {
@Override
public void run() {
loginButton.setEnabled(!username.getText().isEmpty() && !password.getText().isEmpty());
}
};
username.onTextChanged(contentValidation);
password.onTextChanged(contentValidation);
}
public GuiLoginPrompt(ApiClient apiClient, GuiScreen parent, GuiScreen successScreen, boolean manuallyTriggered) {
this.apiClient = apiClient;
this.parent = parent;
this.successScreen = successScreen;
//if the login prompt was opened automatically (on mod startup), show a "skip" instead of "cancel" button
if(!manuallyTriggered) {
cancelButton.setI18nLabel("replaymod.gui.login.skip");
}
setLayout(new CustomLayout<GuiLoginPrompt>() {
@Override
protected void layout(GuiLoginPrompt container, int width, int height) {
pos(username, width / 2 - 45, 30);
pos(password, width / 2 - 45, 60);
pos(loginButton, width / 2 - 152, 110);
pos(cancelButton, width / 2 + 2, 110);
pos(usernameLabel, x(username) - 10 - usernameLabel.getMinSize().getWidth(), y(username) + 7);
pos(passwordLabel, x(password) - 10 - passwordLabel.getMinSize().getWidth(), y(password) + 7);
pos(statusLabel, width / 2 - statusLabel.getMinSize().getWidth() / 2, 92);
int labelWidth = noAccountLabel.getMinSize().getWidth();
int buttonWidth = registerButton.getMaxSize().getWidth();
int lineWidth = labelWidth + 5 + buttonWidth;
int lineStart = width / 2 - lineWidth / 2;
pos(noAccountLabel, lineStart, height - 22);
pos(registerButton, lineStart + labelWidth + 5, height - 30);
}
});
setTitle(new GuiLabel().setI18nText("replaymod.gui.login.title"));
}
public GuiScreen getSuccessScreen() {
return successScreen;
}
@Override
protected GuiLoginPrompt getThis() {
return this;
}
}

View File

@@ -0,0 +1,162 @@
package com.replaymod.online.gui;
import com.mojang.authlib.exceptions.AuthenticationException;
import com.replaymod.online.api.ApiClient;
import com.replaymod.online.api.ApiException;
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
import de.johni0702.minecraft.gui.container.GuiPanel;
import de.johni0702.minecraft.gui.element.*;
import de.johni0702.minecraft.gui.layout.CustomLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout;
import eu.crushedpixel.replaymod.gui.GuiConstants;
import eu.crushedpixel.replaymod.utils.EmailAddressUtils;
import eu.crushedpixel.replaymod.utils.RegexUtils;
import net.minecraft.client.gui.FontRenderer;
import org.lwjgl.util.Dimension;
import org.lwjgl.util.ReadableColor;
public class GuiRegister extends AbstractGuiScreen<GuiRegister> {
private final GuiPanel inputs;
private final GuiTextField usernameInput, mailInput;
private final GuiPasswordField passwordInput, passwordConfirmation;
private final GuiButton registerButton = new GuiButton(this).setI18nLabel("replaymod.gui.register").setSize(150, 20).setDisabled();
private final GuiButton cancelButton = new GuiButton(this).setI18nLabel("replaymod.gui.cancel").setSize(150, 20);
private final GuiLabel disclaimerLabel = new GuiLabel(this).setI18nText("replaymod.gui.register.disclaimer");
private final GuiLabel statusLabel = new GuiLabel(this).setColor(ReadableColor.RED);
{
inputs = new GuiPanel(this).setLayout(new VerticalLayout().setSpacing(10));
HorizontalLayout.Data data = new HorizontalLayout.Data(0.5);
inputs.addElements(new VerticalLayout.Data(1),
new GuiPanel().setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(10))
.addElements(data, new GuiLabel().setI18nText("replaymod.gui.username"))
.addElements(data, usernameInput = new GuiTextField().setMaxLength(16).setSize(145, 20)),
new GuiPanel().setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(10))
.addElements(data, new GuiLabel().setI18nText("replaymod.gui.mail"))
.addElements(data, mailInput = new GuiTextField().setSize(145, 20)),
new GuiPanel().setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(10))
.addElements(data, new GuiLabel().setI18nText("replaymod.gui.password"))
.addElements(data, passwordInput = new GuiPasswordField().setMaxLength(GuiConstants.MAX_PW_LENGTH+1).setSize(145, 20)),
new GuiPanel().setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(10))
.addElements(data, new GuiLabel().setI18nText("replaymod.gui.register.confirmpw"))
.addElements(data, passwordConfirmation = new GuiPasswordField().setMaxLength(GuiConstants.MAX_PW_LENGTH+1).setSize(145, 20))
);
usernameInput.setNext(mailInput)
.getNext().setNext(passwordInput)
.getNext().setNext(passwordConfirmation)
.getNext().setNext(usernameInput);
setLayout(new CustomLayout<GuiRegister>() {
@Override
protected void layout(GuiRegister container, int width, int height) {
pos(inputs, width / 2 - inputs.getMinSize().getWidth() / 2, 30);
pos(registerButton, width / 2 - 152, 170);
pos(cancelButton, width / 2 + 2, 170);
pos(statusLabel, width / 2 - statusLabel.getMinSize().getWidth() / 2, 152);
FontRenderer font = getMinecraft().fontRendererObj;
int lineCount = font.listFormattedStringToWidth(disclaimerLabel.getText(), width - 10).size();
Dimension dim = new Dimension(width - 10, font.FONT_HEIGHT * lineCount);
disclaimerLabel.setSize(dim);
pos(disclaimerLabel, 5, height - dim.getHeight() - 5);
}
});
setTitle(new GuiLabel().setI18nText("replaymod.gui.register.title"));
Runnable doRegister = new Runnable() {
@Override
public void run() {
if (!registerButton.isEnabled()) return;
inputs.forEach(IGuiTextField.class).setDisabled();
statusLabel.setI18nText("replaymod.gui.login.logging");
new Thread(new Runnable() {
@Override
public void run() {
try {
String username = usernameInput.getText().trim();
String mail = mailInput.getText().trim();
String password = passwordInput.getText();
apiClient.register(username, mail, password);
getMinecraft().addScheduledTask(new Runnable() {
@Override
public void run() {
parent.getSuccessScreen().display();
}
});
} catch (ApiException ae) {
statusLabel.setText(ae.getLocalizedMessage());
} catch (AuthenticationException aue) {
aue.printStackTrace();
statusLabel.setI18nText("replaymod.gui.register.error.authfailed");
} catch (Exception e) {
e.printStackTrace();
statusLabel.setI18nText("replaymod.gui.login.connectionerror");
} finally {
inputs.forEach(IGuiTextField.class).setEnabled();
}
}
}, "replaymod-register").start();
}
};
inputs.forEach(IGuiTextField.class).onEnter(doRegister);
registerButton.onClick(doRegister);
Runnable contentValidation = new Runnable() {
@Override
public void run() {
String status = null;
if (usernameInput.getText().length() < 5) {
status = "replaymod.gui.register.error.shortusername";
} else if(!RegexUtils.isValid(RegexUtils.ALPHANUMERIC_UNDERSCORE, usernameInput.getText().trim())) {
status = "replaymod.gui.register.error.invalidname";
} else if (passwordInput.getText().length() < GuiConstants.MIN_PW_LENGTH) {
status = "replaymod.gui.register.error.shortpw";
} else if (passwordInput.getText().length() > GuiConstants.MAX_PW_LENGTH) {
status = "replaymod.gui.register.error.longpw";
} else if (!EmailAddressUtils.isValidEmailAddress(mailInput.getText())) {
status = "replaymod.api.invalidmail";
} else if (!passwordConfirmation.getText().equals(passwordInput.getText())) {
status = "replaymod.gui.register.error.nomatch";
}
registerButton.setEnabled(status == null);
if (status != null
&& !usernameInput.getText().isEmpty()
&& !mailInput.getText().isEmpty()
&& !passwordInput.getText().isEmpty()
&& !passwordConfirmation.getText().isEmpty()) {
statusLabel.setI18nText(status);
} else {
statusLabel.setText("");
}
}
};
inputs.forEach(IGuiTextField.class).onTextChanged(contentValidation);
cancelButton.onClick(new Runnable() {
@Override
public void run() {
parent.display();
}
});
}
private final ApiClient apiClient;
private final GuiLoginPrompt parent;
public GuiRegister(ApiClient apiClient, GuiLoginPrompt parent) {
this.apiClient = apiClient;
this.parent = parent;
}
@Override
protected GuiRegister getThis() {
return this;
}
}

View File

@@ -0,0 +1,456 @@
package com.replaymod.online.gui;
import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.mojang.realmsclient.gui.ChatFormatting;
import com.replaymod.online.ReplayModOnline;
import com.replaymod.online.api.ApiClient;
import com.replaymod.online.api.ApiException;
import com.replaymod.online.api.replay.SearchQuery;
import com.replaymod.online.api.replay.holders.Category;
import com.replaymod.online.api.replay.holders.FileInfo;
import com.replaymod.online.api.replay.holders.FileRating;
import com.replaymod.online.api.replay.holders.Rating;
import com.replaymod.online.api.replay.pagination.DownloadedFilePagination;
import com.replaymod.online.api.replay.pagination.FavoritedFilePagination;
import com.replaymod.online.api.replay.pagination.Pagination;
import com.replaymod.online.api.replay.pagination.SearchPagination;
import de.johni0702.minecraft.gui.container.AbstractGuiContainer;
import de.johni0702.minecraft.gui.container.GuiContainer;
import de.johni0702.minecraft.gui.container.GuiPanel;
import de.johni0702.minecraft.gui.container.GuiScreen;
import de.johni0702.minecraft.gui.element.GuiButton;
import de.johni0702.minecraft.gui.element.GuiImage;
import de.johni0702.minecraft.gui.element.GuiLabel;
import de.johni0702.minecraft.gui.element.IGuiButton;
import de.johni0702.minecraft.gui.element.advanced.GuiResourceLoadingList;
import de.johni0702.minecraft.gui.layout.CustomLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout;
import de.johni0702.minecraft.gui.popup.GuiYesNoPopup;
import de.johni0702.minecraft.gui.utils.Colors;
import de.johni0702.minecraft.gui.utils.Consumer;
import de.johni0702.replaystudio.replay.ReplayMetaData;
import eu.crushedpixel.replaymod.registry.ResourceHelper;
import eu.crushedpixel.replaymod.utils.DurationUtils;
import net.minecraftforge.fml.common.FMLLog;
import org.apache.commons.io.FilenameUtils;
import org.apache.logging.log4j.core.helpers.Strings;
import org.lwjgl.util.Dimension;
import org.lwjgl.util.ReadableDimension;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
public class GuiReplayCenter extends GuiScreen {
private final ReplayModOnline mod;
private final ApiClient apiClient;
private GuiButton makeCategory(String name) {
return new GuiButton().setI18nLabel("replaymod.gui.center.top." + name).setSize(75, 20);
}
public final GuiButton categoryRecent = makeCategory("recent").onClick(new Runnable() {
@Override
public void run() {
show(new SearchPagination(apiClient,
new SearchQuery(false, null, null, null, null, null, null, null, null, null)));
categoryRecent.setDisabled();
}
});
public final GuiButton categoryBest = makeCategory("best").onClick(new Runnable() {
@Override
public void run() {
show(new SearchPagination(apiClient,
new SearchQuery(true, null, null, null, null, null, null, null, null, null)));
categoryBest.setDisabled();
}
});
public final GuiButton categoryDownloaded = makeCategory("downloaded").onClick(new Runnable()
{
@Override
public void run() {
show(new DownloadedFilePagination(mod));
categoryDownloaded.setDisabled();
}
});
public final GuiButton categoryFavorited = makeCategory("favorited").onClick(new Runnable() {
@Override
public void run() {
show(new FavoritedFilePagination(apiClient));
categoryFavorited.setDisabled();
}
});
public final GuiButton categorySearch = makeCategory("search").onClick(new Runnable() {
@Override
public void run() {
searchPopup.open();
}
});
public final GuiPanel categories = new GuiPanel(this).setLayout(new HorizontalLayout().setSpacing(5))
.addElements(null, categoryRecent, categoryBest, categoryDownloaded, categoryFavorited, categorySearch);
public final GuiResourceLoadingList<GuiReplayEntry> list = new GuiResourceLoadingList<GuiReplayEntry>(this).onSelectionChanged(new Runnable() {
@Override
public void run() {
GuiReplayEntry selected = list.getSelected();
replayButtonPanel.forEach(IGuiButton.class).setEnabled(selected != null);
if (selected != null) {
int replayId = selected.fileInfo.getId();
boolean favorited = favoritedReplays.contains(replayId);
boolean liked = likedReplays.contains(replayId);
boolean disliked = dislikedReplays.contains(replayId);
loadButton.setI18nLabel(selected.downloaded ? "replaymod.gui.load" : "replaymod.gui.download");
favoriteButton.setI18nLabel("replaymod.gui.center." + (favorited ? "unfavorite" : "favorite"));
// Only allow button usage for either unfavorite or favorite after they've actually downloaded it
favoriteButton.setEnabled(favorited || selected.downloaded);
// Similar for like/dislike buttons
likeButton.setEnabled(selected.downloaded);
dislikeButton.setEnabled(selected.downloaded);
likeButton.setI18nLabel("replaymod.gui." + (liked ? "removelike" : "like"));
dislikeButton.setI18nLabel("replaymod.gui." + (disliked ? "removedislike" : "dislike"));
}
}
}).onLoad(new Consumer<Consumer<Supplier<GuiReplayEntry>>>() {
@Override
public void consume(Consumer<Supplier<GuiReplayEntry>> obj) {
// Do not load any replays until the user has selected the category they'd like to view
}
}).setDrawShadow(true).setDrawSlider(true);
public final GuiButton loadButton = new GuiButton().onClick(new Runnable() {
@Override
public void run() {
try {
FileInfo fileInfo = list.getSelected().fileInfo;
mod.startReplay(fileInfo.getId(), fileInfo.getName(), GuiReplayCenter.this);
} catch (IOException e) {
e.printStackTrace();
}
}
}).setSize(95, 20).setI18nLabel("replaymod.gui.download").setDisabled();
public final GuiButton favoriteButton = new GuiButton().onClick(new Runnable() {
@Override
public void run() {
try {
int replayId = list.getSelected().fileInfo.getId();
boolean favorited = favoritedReplays.contains(replayId);
apiClient.favFile(replayId, !favorited);
reloadFavorited();
list.onSelectionChanged();
} catch (IOException | ApiException e) {
e.printStackTrace();
}
}
}).setSize(95, 20).setI18nLabel("replaymod.gui.center.favorite").setDisabled();
public final GuiButton likeButton = new GuiButton().onClick(new Runnable() {
@Override
public void run() {
try {
int replayId = list.getSelected().fileInfo.getId();
boolean liked = likedReplays.contains(replayId);
apiClient.rateFile(replayId, liked ? Rating.RatingType.NEUTRAL : Rating.RatingType.LIKE);
reloadRated();
list.onSelectionChanged();
} catch (IOException | ApiException e) {
e.printStackTrace();
}
}
}).setSize(95, 20).setI18nLabel("replaymod.gui.like").setDisabled();
public final GuiButton dislikeButton = new GuiButton().onClick(new Runnable() {
@Override
public void run() {
try {
int replayId = list.getSelected().fileInfo.getId();
boolean disliked = dislikedReplays.contains(replayId);
apiClient.rateFile(replayId, disliked ? Rating.RatingType.NEUTRAL : Rating.RatingType.DISLIKE);
reloadRated();
list.onSelectionChanged();
} catch (IOException | ApiException e) {
e.printStackTrace();
}
}
}).setSize(95, 20).setI18nLabel("replaymod.gui.dislike").setDisabled();
public final GuiButton logoutButton = new GuiButton().onClick(new Runnable() {
@Override
public void run() {
Futures.addCallback(GuiYesNoPopup.open(GuiReplayCenter.this,
new GuiLabel().setI18nText("replaymod.gui.center.logoutcallback").setColor(Colors.BLACK))
.setYesI18nLabel("replaymod.gui.logout").setNoI18nLabel("replaymod.gui.cancel")
.getFuture(), new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
if (result) {
apiClient.logout();
getMinecraft().displayGuiScreen(null);
}
}
@Override
public void onFailure(Throwable t) {
t.printStackTrace();
}
});
}
}).setSize(195, 20).setI18nLabel("replaymod.gui.logout");
public final GuiButton mainMenuButton = new GuiButton().onClick(new Runnable() {
@Override
public void run() {
getMinecraft().displayGuiScreen(null);
}
}).setSize(195, 20).setI18nLabel("replaymod.gui.mainmenu");
public final GuiReplayCenterSearch searchPopup;
public final GuiPanel replayButtonPanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(5))
.addElements(null, loadButton, favoriteButton, likeButton, dislikeButton);
public final GuiPanel generalButtonPanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(5))
.addElements(null, logoutButton, mainMenuButton);
public final GuiPanel buttonPanel = new GuiPanel(this).setLayout(new VerticalLayout().setSpacing(5))
.addElements(null, replayButtonPanel, generalButtonPanel);
private volatile Set<Integer> favoritedReplays = Collections.emptySet();
private volatile Set<Integer> likedReplays = Collections.emptySet();
private volatile Set<Integer> dislikedReplays = Collections.emptySet();
public GuiReplayCenter(ReplayModOnline mod) {
this.mod = mod;
this.apiClient = mod.getApiClient();
this.searchPopup = new GuiReplayCenterSearch(this, apiClient);
setTitle(new GuiLabel().setI18nText("replaymod.gui.replaycenter"));
setLayout(new CustomLayout<GuiScreen>() {
@Override
protected void layout(GuiScreen container, int width, int height) {
pos(buttonPanel, width / 2 - width(buttonPanel) / 2, height - 10 - height(buttonPanel));
pos(list, 0, 50);
size(list, width, y(buttonPanel) - 10 - y(list));
pos(categories, width / 2 - width(categories) / 2, y(list) - 7 - height(categories));
}
});
}
private void reloadFavorited() {
try {
favoritedReplays = new HashSet<>(Ints.asList(apiClient.getFavorites()));
} catch (IOException | ApiException e) {
e.printStackTrace();
}
}
private void reloadRated() {
try {
Set<Integer> liked = new HashSet<>(), disliked = new HashSet<>();
for (FileRating rating : apiClient.getRatedFiles()) {
(rating.isRatingPositive() ? liked : disliked).add(rating.getFile());
}
this.likedReplays = liked;
this.dislikedReplays = disliked;
} catch (IOException | ApiException e) {
e.printStackTrace();
}
}
public void show(final Pagination pagination) {
list.setOffsetY(0);
list.onLoad(new Consumer<Consumer<Supplier<GuiReplayEntry>>>() {
@Override
public void consume(Consumer<Supplier<GuiReplayEntry>> obj) {
if (pagination.getLoadedPages() < 0) {
pagination.fetchPage();
}
reloadFavorited();
reloadRated();
int i = 0;
for (final FileInfo fileInfo : pagination.getFiles()) {
if (Thread.interrupted()) return;
try {
// Make sure that to int[] conversion doesn't have to occur in main thread
final BufferedImage theThumb;
if (fileInfo.hasThumbnail()) {
BufferedImage buf = apiClient.downloadThumbnail(fileInfo.getId());
// This is the same way minecraft calls this method, we cache the result and hand
// minecraft a BufferedImage with way simpler logic using the precomputed values
final int[] theIntArray = buf.getRGB(0, 0, buf.getWidth(), buf.getHeight(), null, 0, buf.getWidth());
theThumb = new BufferedImage(buf.getWidth(), buf.getHeight(), BufferedImage.TYPE_INT_ARGB) {
@Override
public int[] getRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize) {
System.arraycopy(theIntArray, 0, rgbArray, 0, theIntArray.length);
return null; // Minecraft doesn't use the return value
}
};
} else {
theThumb = null;
}
final int sortId = i++;
final boolean downloaded = mod.hasDownloaded(fileInfo.getId());
obj.consume(new Supplier<GuiReplayEntry>() {
@Override
public GuiReplayEntry get() {
return new GuiReplayEntry(fileInfo, theThumb, sortId, downloaded);
}
});
} catch (Exception e) {
FMLLog.getLogger().error("Could not load Replay File " + fileInfo.getId(), e);
}
}
}
}).load();
categories.forEach(IGuiButton.class).setEnabled();
}
@Override
protected GuiReplayCenter getThis() {
return this;
}
private final GuiImage defaultThumbnail = new GuiImage().setTexture(ResourceHelper.getDefaultThumbnail());
public class GuiReplayEntry extends AbstractGuiContainer<GuiReplayEntry> implements Comparable<GuiReplayEntry> {
public final FileInfo fileInfo;
public final GuiLabel name = new GuiLabel();
public final GuiLabel author = new GuiLabel();
public final GuiLabel date = new GuiLabel().setColor(Colors.LIGHT_GRAY);
public final GuiLabel server = new GuiLabel().setColor(Colors.LIGHT_GRAY);
public final GuiLabel category = new GuiLabel().setColor(Colors.GREY);
public final GuiPanel stats = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(3));
public final GuiLabel favorites = new GuiLabel(stats).setColor(Colors.ORANGE);
public final GuiLabel likes = new GuiLabel(stats).setColor(Colors.GREEN);
public final GuiLabel dislikes = new GuiLabel(stats).setColor(Colors.RED);
public final GuiPanel infoPanel = new GuiPanel(this).setLayout(new CustomLayout<GuiPanel>() {
@Override
protected void layout(GuiPanel container, int width, int height) {
pos(name, 0, 0);
pos(author, 0, height(name) + 2);
pos(server, 0, y(author) + height(author) + 3);
pos(category, 0, y(server) + height(server) + 3);
pos(date, width - width(date), y(author));
pos(stats, width - width(stats), y(category));
}
}).addElements(null, name, author, date, server, category, stats);
public final GuiImage thumbnail;
public final GuiLabel duration = new GuiLabel();
public final GuiPanel durationPanel = new GuiPanel().setBackgroundColor(Colors.HALF_TRANSPARENT)
.addElements(null, duration).setLayout(new CustomLayout<GuiPanel>() {
@Override
protected void layout(GuiPanel container, int width, int height) {
pos(duration, 2, 2);
}
@Override
public ReadableDimension calcMinSize(GuiContainer<?> container) {
ReadableDimension dimension = duration.calcMinSize();
return new Dimension(dimension.getWidth() + 2, dimension.getHeight() + 2);
}
});
public final GuiLabel downloads = new GuiLabel();
public final GuiPanel downloadsPanel = new GuiPanel().setBackgroundColor(Colors.HALF_TRANSPARENT)
.addElements(null, downloads).setLayout(new CustomLayout<GuiPanel>() {
@Override
protected void layout(GuiPanel container, int width, int height) {
pos(downloads, 2, 2);
}
@Override
public ReadableDimension calcMinSize(GuiContainer<?> container) {
ReadableDimension dimension = downloads.calcMinSize();
return new Dimension(dimension.getWidth() + 2, dimension.getHeight() + 2);
}
});
private final long dateMillis;
private final int sortId;
private final boolean downloaded;
public GuiReplayEntry(FileInfo fileInfo, BufferedImage thumbImage, int sortId, boolean downloaded) {
this.fileInfo = fileInfo;
this.sortId = sortId;
this.downloaded = downloaded;
ReplayMetaData metaData = fileInfo.getMetadata();
name.setText(ChatFormatting.UNDERLINE + FilenameUtils.getBaseName(fileInfo.getName()));
author.setI18nText("replaymod.gui.center.author",
"" + ChatFormatting.GRAY + ChatFormatting.ITALIC, fileInfo.getOwner());
if (Strings.isEmpty(metaData.getServerName())) {
server.setI18nText("replaymod.gui.iphidden").setColor(Colors.DARK_RED);
} else {
server.setText(metaData.getServerName());
}
dateMillis = metaData.getDate();
date.setText(new SimpleDateFormat().format(new Date(dateMillis)));
if (thumbImage == null) {
thumbnail = new GuiImage(defaultThumbnail);
addElements(null, thumbnail);
} else {
thumbnail = new GuiImage(this).setTexture(thumbImage);
}
thumbnail.setSize(45 * 16 / 9, 45);
duration.setText(DurationUtils.convertSecondsToShortString(metaData.getDuration() / 1000));
downloads.setText(fileInfo.getDownloads() + "");
favorites.setText("" + fileInfo.getFavorites());
likes.setText("" + fileInfo.getRatings().getPositive());
dislikes.setText("" + fileInfo.getRatings().getNegative());
category.setText(ChatFormatting.ITALIC + Optional.fromNullable(Category.fromId(fileInfo.getCategory()))
.or(Category.MISCELLANEOUS).toNiceString());
addElements(null, durationPanel, downloadsPanel);
setLayout(new CustomLayout<GuiReplayEntry>() {
@Override
protected void layout(GuiReplayEntry container, int width, int height) {
pos(thumbnail, 0, 0);
x(durationPanel, width(thumbnail) - width(durationPanel));
y(durationPanel, height(thumbnail) - height(durationPanel));
x(downloadsPanel, width(thumbnail) - width(downloadsPanel));
y(downloadsPanel, height(thumbnail) - height(durationPanel) - height(downloadsPanel));
pos(infoPanel, width(thumbnail) + 5, 0);
size(infoPanel, width - width(thumbnail) - 5, height(thumbnail));
}
@Override
public ReadableDimension calcMinSize(GuiContainer<?> container) {
return new org.lwjgl.util.Dimension(300, thumbnail.getMinSize().getHeight());
}
});
}
@Override
protected GuiReplayEntry getThis() {
return this;
}
@Override
public int compareTo(GuiReplayEntry o) {
return Integer.compare(sortId, o.sortId);
}
}
}

View File

@@ -0,0 +1,103 @@
package com.replaymod.online.gui;
import com.google.common.base.Strings;
import com.replaymod.online.api.ApiClient;
import com.replaymod.online.api.replay.SearchQuery;
import com.replaymod.online.api.replay.holders.Category;
import com.replaymod.online.api.replay.holders.MinecraftVersion;
import com.replaymod.online.api.replay.pagination.SearchPagination;
import de.johni0702.minecraft.gui.element.GuiButton;
import de.johni0702.minecraft.gui.element.GuiLabel;
import de.johni0702.minecraft.gui.element.GuiTextField;
import de.johni0702.minecraft.gui.element.GuiToggleButton;
import de.johni0702.minecraft.gui.element.advanced.GuiDropdownMenu;
import de.johni0702.minecraft.gui.layout.GridLayout;
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
import de.johni0702.minecraft.gui.utils.Colors;
import net.minecraft.client.resources.I18n;
import java.util.ArrayList;
import java.util.List;
public class GuiReplayCenterSearch extends AbstractGuiPopup<GuiReplayCenterSearch> {
private final GuiReplayCenter replayCenter;
private final ApiClient apiClient;
public final GuiLabel title = new GuiLabel().setI18nText("replaymod.gui.center.search.filters").setColor(Colors.BLACK);
public final GuiTextField name = new GuiTextField().setSize(120, 20).setI18nHint("replaymod.gui.center.search.name");
public final GuiTextField server = new GuiTextField().setSize(120, 20).setI18nHint("replaymod.gui.center.search.server");
public final GuiDropdownMenu<String> category = new GuiDropdownMenu<String>().setSize(120, 20);
public final GuiDropdownMenu<String> version = new GuiDropdownMenu<String>().setSize(120, 20);
public final GuiToggleButton<String> gameType = new GuiToggleButton<String>().setSize(120, 20);
public final GuiToggleButton<String> sort = new GuiToggleButton<String>().setSize(120, 20);
public final GuiButton cancelButton = new GuiButton().setSize(120, 20).onClick(new Runnable() {
@Override
public void run() {
close();
}
}).setI18nLabel("replaymod.gui.cancel");
public final GuiButton searchButton = new GuiButton().setSize(120, 20).onClick(new Runnable() {
@Override
public void run() {
SearchQuery searchQuery = new SearchQuery();
searchQuery.singleplayer = new Boolean[]{null,true, false}[gameType.getSelected()];
searchQuery.category = category.getSelected() == 0 ? null : category.getSelected() - 1;
searchQuery.version = version.getSelected() == 0
? null : MinecraftVersion.values()[version.getSelected() - 1].getApiName();
searchQuery.name = Strings.emptyToNull(name.getText().trim());
searchQuery.server = Strings.emptyToNull(server.getText().trim());
searchQuery.order = sort.getSelected() == 0;
close();
replayCenter.show(new SearchPagination(apiClient, searchQuery));
}
}).setI18nLabel("replaymod.gui.center.top.search");
{
List<String> categories = new ArrayList<>();
categories.add(I18n.format("replaymod.gui.center.search.category"));
for (Category c : Category.values()) {
categories.add(c.toNiceString());
}
category.setValues(categories.toArray(new String[categories.size()]));
List<String> versions = new ArrayList<>();
versions.add(I18n.format("replaymod.gui.center.search.version"));
for (MinecraftVersion v : MinecraftVersion.values()) {
versions.add(v.toNiceName());
}
version.setValues(versions.toArray(new String[versions.size()]));
gameType.setI18nLabel("replaymod.gui.center.search.gametype")
.setValues(I18n.format("options.particles.all"),
I18n.format("menu.singleplayer"),
I18n.format("menu.multiplayer"));
sort.setI18nLabel("replaymod.gui.center.search.order")
.setValues(I18n.format("replaymod.gui.center.search.order.best"),
I18n.format("replaymod.gui.center.search.order.recent"));
popup.setLayout(new GridLayout().setColumns(3).setSpacingX(5).setSpacingY(5));
popup.addElements(null,
title, new GuiLabel(), new GuiLabel(),
name, category, gameType,
server, version, sort,
new GuiLabel(), new GuiLabel(), new GuiLabel(),
new GuiLabel(), cancelButton, searchButton);
setBackgroundColor(Colors.DARK_TRANSPARENT);
}
public GuiReplayCenterSearch(GuiReplayCenter replayCenter, ApiClient apiClient) {
super(replayCenter);
this.replayCenter = replayCenter;
this.apiClient = apiClient;
}
@Override
public void open() {
super.open();
}
@Override
protected GuiReplayCenterSearch getThis() {
return this;
}
}

View File

@@ -0,0 +1,89 @@
package com.replaymod.online.gui;
import com.mojang.realmsclient.gui.ChatFormatting;
import com.replaymod.online.ReplayModOnline;
import com.replaymod.online.api.ApiClient;
import com.replaymod.online.api.ApiException;
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
import de.johni0702.minecraft.gui.container.GuiScreen;
import de.johni0702.minecraft.gui.element.GuiButton;
import de.johni0702.minecraft.gui.element.GuiLabel;
import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar;
import de.johni0702.minecraft.gui.layout.CustomLayout;
import eu.crushedpixel.replaymod.gui.elements.listeners.ProgressUpdateListener;
import java.io.File;
import java.io.IOException;
public class GuiReplayDownloading extends AbstractGuiScreen<GuiReplayDownloading> implements ProgressUpdateListener {
private final GuiScreen cancelScreen;
private final ApiClient apiClient;
private GuiProgressBar progressBar = new GuiProgressBar(this).setHeight(20);
private GuiButton cancelButton = new GuiButton(this).setI18nLabel("gui.cancel").setSize(150, 20).onClick(new Runnable() {
@Override
public void run() {
apiClient.cancelDownload();
cancelScreen.display();
}
});
public GuiReplayDownloading(GuiScreen cancelScreen, final ReplayModOnline mod,
final int replayId, String name) {
this.cancelScreen = cancelScreen;
this.apiClient = mod.getApiClient();
setTitle(new GuiLabel().setI18nText("replaymod.gui.viewer.download.title"));
final GuiLabel subTitle = new GuiLabel(this).setI18nText("replaymod.gui.viewer.download.message",
ChatFormatting.UNDERLINE + name + ChatFormatting.RESET);
setLayout(new CustomLayout<GuiReplayDownloading>() {
@Override
protected void layout(GuiReplayDownloading container, int width, int height) {
width(progressBar, width - 20);
pos(progressBar, 10, height - 50);
pos(cancelButton, width - 5 - 150, height - 5 - 20);
pos(subTitle, width / 2 - subTitle.getMinSize().getWidth() / 2, 40);
}
});
new Thread(new Runnable() {
@Override
public void run() {
final File replayFile = mod.getDownloadedFile(replayId);
try {
apiClient.downloadFile(replayId, replayFile, GuiReplayDownloading.this);
if (replayFile.exists()) {
getMinecraft().addScheduledTask(new Runnable() {
@Override
public void run() {
try {
mod.getReplayModule().startReplay(replayFile);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
} catch (IOException | ApiException e) {
e.printStackTrace();
}
}
}).start();
}
@Override
public void onProgressChanged(float progress) {
progressBar.setProgress(progress);
}
@Override
public void onProgressChanged(float progress, String progressString) {
onProgressChanged(progress);
}
@Override
protected GuiReplayDownloading getThis() {
return this;
}
}

View File

@@ -0,0 +1,167 @@
package com.replaymod.online.gui;
import com.mojang.realmsclient.gui.ChatFormatting;
import com.replaymod.online.api.replay.holders.FileInfo;
import eu.crushedpixel.replaymod.gui.elements.ComposedElement;
import eu.crushedpixel.replaymod.gui.elements.GuiAdvancedButton;
import eu.crushedpixel.replaymod.gui.elements.GuiDropdown;
import eu.crushedpixel.replaymod.gui.elements.GuiString;
import eu.crushedpixel.replaymod.holders.GuiEntryListValueEntry;
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class GuiReplayInstanceChooser extends GuiScreen {
private boolean initialized = false;
private GuiString dropdownTitle;
private GuiDropdown<GuiEntryListValueEntry<File>> fileDropdown;
private GuiAdvancedButton chooseButton, cancelButton;
private ComposedElement composedElement;
private List<File> filesToChooseFrom = new ArrayList<File>();
private final String TITLE = I18n.format("replaymod.gui.viewer.chooser.title");
private final String REPLAYFILE = I18n.format("replaymod.gui.editor.replayfile")+":";
private final String MESSAGE;
private final String ORIGINAL = ChatFormatting.GREEN+I18n.format("replaymod.gui.original")+ChatFormatting.RESET;
private final String MODIFIED = ChatFormatting.RED+I18n.format("replaymod.gui.modified")+ChatFormatting.RESET;
public GuiReplayInstanceChooser(final FileInfo fileInfo, File downloadedFile) {
int id = fileInfo.getId();
this.MESSAGE = I18n.format("replaymod.gui.viewer.chooser.message", ChatFormatting.UNDERLINE+fileInfo.getName()+ChatFormatting.RESET);
//gather all applicable replay files
try {
File replayFolder = ReplayFileIO.getReplayFolder();
List<File> chooseableFiles = new ArrayList<File>();
File[] files = replayFolder.listFiles();
if(files != null) {
for(File file : files) {
try {
String extension = FilenameUtils.getExtension(file.getAbsolutePath());
if(!"zip".equals(extension)) continue;
String filename = FilenameUtils.getBaseName(file.getAbsolutePath());
String[] split = filename.split("_");
String first = split[0];
if(StringUtils.isNumeric(first) && Integer.valueOf(first) == id) {
chooseableFiles.add(file);
}
} catch(Exception e) {
e.printStackTrace();
}
}
}
//if no modified versions of the replay were found, start the downloaded one
if(chooseableFiles.isEmpty()) {
// TODO
// ReplayHandler.startReplay(downloadedFile);
return;
}
chooseableFiles.add(0, downloadedFile);
this.filesToChooseFrom = chooseableFiles;
} catch(IOException e) {
e.printStackTrace();
}
}
@Override
public void initGui() {
if(!initialized) {
dropdownTitle = new GuiString(0, 0, Color.WHITE, REPLAYFILE);
fileDropdown = new GuiDropdown<GuiEntryListValueEntry<File>>(fontRendererObj, 0, 0, 0, 5);
int i = 0;
for(File file : filesToChooseFrom) {
String displayName = FilenameUtils.getName(file.getAbsolutePath()) + " (" + (i == 0 ? ORIGINAL : MODIFIED) + ")";
fileDropdown.addElement(new GuiEntryListValueEntry<File>(displayName, file));
i++;
}
chooseButton = new GuiAdvancedButton(0, 0, 100, 20, I18n.format("replaymod.gui.load"), new Runnable() {
@Override
public void run() {
try {
File file = fileDropdown.getElement(fileDropdown.getSelectionIndex()).getValue();
//TODO
// ReplayHandler.startReplay(file);
} catch(Exception e) {
e.printStackTrace();
}
}
}, null);
cancelButton = new GuiAdvancedButton(0, 0, 100, 20, I18n.format("replaymod.gui.cancel"), new Runnable() {
@Override
public void run() {
// mc.displayGuiScreen(new GuiReplayCenter());
}
}, null);
composedElement = new ComposedElement(dropdownTitle, fileDropdown, chooseButton, cancelButton);
}
fileDropdown.width = 200;
int strWidth = fontRendererObj.getStringWidth(REPLAYFILE);
int totWidth = strWidth + fileDropdown.width + 5;
dropdownTitle.positionX = (this.width-totWidth)/2;
fileDropdown.xPosition = dropdownTitle.positionX + strWidth + 5;
fileDropdown.yPosition = this.height/2 - 10;
dropdownTitle.positionY = fileDropdown.yPosition + 6;
cancelButton.xPosition = this.width - 100 - 5;
chooseButton.xPosition = cancelButton.xPosition - 100 - 5;
cancelButton.yPosition = chooseButton.yPosition = this.height - 5 - 20;
this.initialized = true;
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
this.drawDefaultBackground();
drawCenteredString(fontRendererObj, ChatFormatting.UNDERLINE+TITLE, this.width / 2, 15, Color.WHITE.getRGB());
String[] lines = eu.crushedpixel.replaymod.utils.StringUtils.splitStringInMultipleRows(MESSAGE, this.width-40);
int i = 0;
for(String line : lines) {
drawString(fontRendererObj, line, 20, 40+(15*i), Color.WHITE.getRGB());
i++;
}
composedElement.draw(mc, mouseX, mouseY);
}
@Override
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException {
composedElement.mouseClick(mc, mouseX, mouseY, mouseButton);
}
@Override
protected void mouseReleased(int mouseX, int mouseY, int state) {
composedElement.mouseRelease(mc, mouseX, mouseY, state);
}
}

View File

@@ -0,0 +1,469 @@
package com.replaymod.online.gui;
import com.google.common.base.Optional;
import com.replaymod.core.ReplayMod;
import com.replaymod.online.api.ApiClient;
import com.replaymod.replay.gui.screen.GuiReplayViewer;
import de.johni0702.replaystudio.replay.ReplayFile;
import de.johni0702.replaystudio.replay.ReplayMetaData;
import de.johni0702.replaystudio.replay.ZipReplayFile;
import de.johni0702.replaystudio.studio.ReplayStudio;
import com.replaymod.online.api.replay.FileUploader;
import com.replaymod.online.api.replay.holders.Category;
import eu.crushedpixel.replaymod.gui.GuiConstants;
import eu.crushedpixel.replaymod.gui.elements.*;
import eu.crushedpixel.replaymod.gui.elements.listeners.ProgressUpdateListener;
import eu.crushedpixel.replaymod.registry.ResourceHelper;
import eu.crushedpixel.replaymod.utils.ImageUtils;
import eu.crushedpixel.replaymod.utils.MouseUtils;
import eu.crushedpixel.replaymod.utils.RegexUtils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.client.resources.I18n;
import net.minecraft.client.settings.GameSettings;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.input.Keyboard;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
// TODO: Rewrite using new GUI API
public class GuiUploadFile extends GuiScreen implements ProgressUpdateListener {
private final Minecraft mc = Minecraft.getMinecraft();
private final ResourceLocation textureResource;
private DynamicTexture dynTex = null;
private boolean initialized;
private int columnWidth;
private int columnRight;
private GuiAdvancedTextField name, tags;
private GuiToggleButton category;
private GuiString serverIP, duration;
private GuiAdvancedCheckBox hideServerIP;
private GuiTextArea description;
private ComposedElement content;
private GuiTextField messageTextField;
private GuiAdvancedButton startUploadButton, cancelUploadButton, backButton;
private GuiProgressBar progressBar;
private File replayFile;
private ReplayMetaData metaData;
private BufferedImage thumb;
private boolean hasThumbnail;
private FileUploader uploader;
private GuiReplayViewer parent;
private boolean lockUploadButton = false;
public GuiUploadFile(ApiClient apiClient, File file, GuiReplayViewer parent) {
this.parent = parent;
uploader = new FileUploader(apiClient);
this.textureResource = new ResourceLocation("upload_thumbs/" + FilenameUtils.getBaseName(file.getAbsolutePath()));
dynTex = null;
boolean correctFile = false;
this.replayFile = file;
ReplayFile archive = null;
try {
archive = new ZipReplayFile(new ReplayStudio(), file);
metaData = archive.getMetaData();
Optional<BufferedImage> img = archive.getThumb();
if(img.isPresent()) {
thumb = ImageUtils.scaleImage(img.get(), new Dimension(1280, 720));
hasThumbnail = true;
}
archive.close();
correctFile = true;
} catch(Exception e) {
e.printStackTrace();
} finally {
try {
if (archive != null) {
archive.close();
}
} catch (IOException ignored) {}
}
if(!correctFile) {
Logger logger = LogManager.getLogger();
logger.error("Invalid file provided to upload");
parent.display();
replayFile = null;
return;
}
//If thumb is null, set image to placeholder
if(thumb == null) {
try {
thumb = ImageIO.read(GuiUploadFile.class.getClassLoader().getResourceAsStream("default_thumb.jpg"));
} catch(Exception e) {
e.printStackTrace();
}
}
}
@Override
public void initGui() {
if(replayFile == null) {
parent.display();
return;
}
if (!initialized) {
initialized = true;
int y = 20;
name = new GuiAdvancedTextField(fontRendererObj, 0, y, 0, 20);
name.hint = I18n.format("replaymod.gui.upload.namehint");
name.text = FilenameUtils.getBaseName(replayFile.getAbsolutePath());
name.setMaxStringLength(30);
y+=25;
int secs = metaData.getDuration() / 1000;
String durationString = I18n.format("replaymod.gui.duration") + String.format(": %02dm%02ds", secs / 60, secs % 60);
duration = new GuiString(0, y, Color.WHITE, durationString);
y+=15;
hideServerIP = new GuiAdvancedCheckBox(0, y, I18n.format("replaymod.gui.upload.hideip"), false);
hideServerIP.enabled = !metaData.isSingleplayer();
y+=15;
serverIP = new GuiString(0, y, Color.GRAY, new Callable<String>() {
@Override
public String call() throws Exception {
if (hideServerIP.isChecked()){
return I18n.format("replaymod.gui.iphidden");
} else {
return metaData.getServerName();
}
}
});
y+=15;
category = new GuiToggleButton(0, 0, y, I18n.format("replaymod.category") + ": ", Category.stringValues());
y+=25;
tags = new GuiAdvancedTextField(fontRendererObj, 0, y, 0, 20);
tags.setMaxStringLength(30);
tags.hint = I18n.format("replaymod.gui.upload.tagshint");
y+=20;
description = new GuiTextArea(fontRendererObj, 0, name.yPosition, 0, y - name.yPosition, 1000, 100, 1000);
}
columnWidth = Math.min(200, (width - 60) / 3);
int columnLeft = width / 2 - columnWidth / 2 * 3 - 10;
int columnMiddle = width / 2 - columnWidth / 2;
columnRight = width / 2 + columnWidth / 2 + 10;
name.xPosition = columnLeft;
name.width = columnWidth;
duration.positionX = columnLeft;
hideServerIP.xPosition = columnLeft - 1;
serverIP.positionX = columnLeft;
category.xPosition = columnLeft - 1;
category.width = columnWidth + 2;
tags.xPosition = columnLeft;
tags.width = columnWidth;
description.positionX = columnMiddle;
description.setWidth(columnWidth);
List<GuiElement> elements = new ArrayList<GuiElement>(Arrays.asList(
name, category, tags, description, serverIP, duration
));
if (!metaData.isSingleplayer()) {
elements.add(hideServerIP);
}
content = new ComposedElement(elements.toArray(new GuiElement[elements.size()]));
@SuppressWarnings("unchecked")
List<GuiButton> buttonList = this.buttonList;
if(startUploadButton == null) {
List<GuiButton> bottomBar = new ArrayList<GuiButton>();
startUploadButton = new GuiAdvancedButton(GuiConstants.UPLOAD_START_BUTTON, 0, 0, I18n.format("replaymod.gui.upload.start"));
bottomBar.add(startUploadButton);
cancelUploadButton = new GuiAdvancedButton(GuiConstants.UPLOAD_CANCEL_BUTTON, 0, 0, I18n.format("replaymod.gui.upload.cancel"));
cancelUploadButton.enabled = false;
bottomBar.add(cancelUploadButton);
backButton = new GuiAdvancedButton(GuiConstants.UPLOAD_BACK_BUTTON, 0, 0, I18n.format("replaymod.gui.back"));
bottomBar.add(backButton);
int i = 0;
for(GuiButton b : bottomBar) {
int w = this.width - 30;
int w2 = w / bottomBar.size();
int x = 15 + (w2 * i);
b.xPosition = x + 2;
b.yPosition = height - 30;
b.width = w2 - 4;
buttonList.add(b);
i++;
}
} else {
List<GuiButton> bottomBar = new ArrayList<GuiButton>();
bottomBar.add(startUploadButton);
bottomBar.add(cancelUploadButton);
bottomBar.add(backButton);
int i = 0;
for(GuiButton b : bottomBar) {
int w = this.width - 30;
int w2 = w / bottomBar.size();
int x = 15 + (w2 * i);
b.xPosition = x + 2;
b.yPosition = height - 30;
b.width = w2 - 4;
buttonList.add(b);
i++;
}
}
if(messageTextField == null) {
messageTextField = new GuiTextField(GuiConstants.UPLOAD_INFO_FIELD, fontRendererObj, 20, height - 80, width - 40, 20);
messageTextField.setEnabled(true);
messageTextField.setFocused(false);
messageTextField.setMaxStringLength(Integer.MAX_VALUE);
} else {
messageTextField.yPosition = height - 80;
messageTextField.width = width - 40;
}
if(progressBar == null) {
progressBar = new GuiProgressBar(19, height - 52, width - (2*19), 15);
} else {
progressBar.setBounds(19, height - 52, width - (2*19), 15);
}
validateStartButton();
}
@Override
protected void actionPerformed(GuiButton button) throws IOException {
if(!button.enabled) return;
if(button.id == GuiConstants.UPLOAD_BACK_BUTTON) {
parent.display();
} else if(button.id == GuiConstants.UPLOAD_START_BUTTON) {
final String name = this.name.getText().trim();
new Thread(new Runnable() {
@Override
public void run() {
try {
String tagsRaw = tags.getText();
String[] split = tagsRaw.split(",");
List<String> tags = new ArrayList<String>();
for(String str : split) {
if(!tags.contains(str) && str.length() > 0) {
tags.add(str);
}
}
Category category = Category.values()[GuiUploadFile.this.category.getValue()];
String desc = StringUtils.join(description.getText(), '\n');
if(hideServerIP.isChecked()) {
File tmp = File.createTempFile("replay_hidden_ip", "mcpr");
ReplayMetaData newMetaData = new ReplayMetaData(metaData);
newMetaData.setServerName(null);
ReplayFile replay = new ZipReplayFile(new ReplayStudio(), replayFile);
replay.writeMetaData(newMetaData);
replay.saveTo(tmp);
uploader.uploadFile(GuiUploadFile.this, name, tags, tmp, category, desc);
FileUtils.deleteQuietly(tmp);
} else {
uploader.uploadFile(GuiUploadFile.this, name, tags, replayFile, category, desc);
}
ReplayMod.uploadedFileHandler.markAsUploaded(replayFile);
} catch(Exception e) {
messageTextField.setText(I18n.format("replaymod.gui.unknownerror"));
messageTextField.setTextColor(Color.RED.getRGB());
e.printStackTrace();
}
}
}, "replaymod-file-uploader").start();
} else if(button.id == GuiConstants.UPLOAD_CANCEL_BUTTON) {
uploader.cancelUploading();
}
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
this.drawDefaultBackground();
drawCenteredString(fontRendererObj, I18n.format("replaymod.gui.upload.title"), this.width / 2, 5, Color.WHITE.getRGB());
//Draw thumbnail
if(thumb != null) {
if(dynTex == null) {
dynTex = new DynamicTexture(thumb);
mc.getTextureManager().loadTexture(textureResource, dynTex);
dynTex.updateDynamicTexture();
ResourceHelper.registerResource(textureResource);
}
mc.getTextureManager().bindTexture(textureResource); //Will be freed by the ResourceHelper
int height = columnWidth * 720 / 1280;
Gui.drawScaledCustomSizeModalRect(columnRight, 20, 0, 0, 1280, 720, columnWidth, height, 1280, 720);
if (!hasThumbnail) {
KeyBinding keyBinding = ReplayMod.instance.getKeyBindingRegistry().getKeyBindings().get("replaymod.input.thumbnail");
String str = I18n.format("replaymod.gui.upload.nothumbnail",
keyBinding == null ? "???" : GameSettings.getKeyDisplayString(keyBinding.getKeyCode()));
int y = 20 + height + 10;
fontRendererObj.drawSplitString(str, columnRight, y, columnWidth, Color.RED.getRGB());
}
}
messageTextField.drawTextBox();
super.drawScreen(mouseX, mouseY, partialTicks);
progressBar.drawProgressBar();
startUploadButton.drawOverlay(mc, mouseX, mouseY);
content.draw(mc, mouseX, mouseY);
content.drawOverlay(mc, mouseX, mouseY);
}
@Override
public void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException {
super.mouseClicked(mouseX, mouseY, mouseButton);
content.mouseClick(mc, mouseX, mouseY, mouseButton);
}
@Override
public void updateScreen() {
content.tick(mc);
}
@Override
public void onGuiClosed() {
ResourceHelper.freeAllResources();
Keyboard.enableRepeatEvents(false);
super.onGuiClosed();
}
@Override
protected void keyTyped(char typedChar, int keyCode) throws IOException {
org.lwjgl.util.Point mouse = MouseUtils.getMousePos();
content.buttonPressed(mc, mouse.getX(), mouse.getY(), typedChar, keyCode);
if(uploader.isUploading()) {
startUploadButton.enabled = false;
} else {
validateStartButton();
}
}
private void validateStartButton() {
boolean enabled = true;
if(name.getText().trim().length() < 5 || name.getText().trim().length() > 30) {
enabled = false;
name.setTextColor(Color.RED.getRGB());
startUploadButton.hoverText = I18n.format("replaymod.gui.upload.error.name.length");
} else if(!RegexUtils.isValid(RegexUtils.ALPHANUMERIC_SPACE_HYPHEN_UNDERSCORE, name.getText())) {
enabled = false;
name.setTextColor(Color.RED.getRGB());
startUploadButton.hoverText = I18n.format("replaymod.gui.upload.error.name");
} else if(!RegexUtils.isValid(RegexUtils.ALPHANUMERIC_COMMA, tags.getText())) {
enabled = false;
tags.setTextColor(Color.RED.getRGB());
startUploadButton.hoverText = I18n.format("replaymod.gui.upload.error.tags");
} else {
name.setTextColor(Color.WHITE.getRGB());
tags.setTextColor(Color.WHITE.getRGB());
startUploadButton.hoverText = null;
}
if(lockUploadButton) enabled = false;
startUploadButton.enabled = enabled;
}
public void onStartUploading() {
startUploadButton.enabled = false;
cancelUploadButton.enabled = true;
backButton.enabled = false;
category.enabled = false;
name.setElementEnabled(false);
description.enabled = false;
messageTextField.setText(I18n.format("replaymod.gui.upload.uploading"));
messageTextField.setTextColor(Color.WHITE.getRGB());
}
public void onFinishUploading(boolean success, String info) {
validateStartButton();
cancelUploadButton.enabled = false;
backButton.enabled = true;
category.enabled = true;
name.setElementEnabled(true);
description.enabled = true;
if(success) {
messageTextField.setText(I18n.format("replaymod.gui.upload.success"));
messageTextField.setTextColor(Color.GREEN.getRGB());
startUploadButton.enabled = false;
lockUploadButton = true;
} else {
messageTextField.setText(info);
messageTextField.setTextColor(Color.RED.getRGB());
}
}
@Override
public void onProgressChanged(float progress) {
if(progressBar != null) progressBar.setProgress(progress);
}
@Override
public void onProgressChanged(float progress, String progressString) {}
}

View File

@@ -0,0 +1,94 @@
package com.replaymod.online.handler;
import com.replaymod.online.ReplayModOnline;
import com.replaymod.online.gui.GuiLoginPrompt;
import com.replaymod.online.gui.GuiReplayCenter;
import com.replaymod.online.gui.GuiUploadFile;
import com.replaymod.replay.gui.screen.GuiReplayViewer;
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
import de.johni0702.minecraft.gui.container.GuiPanel;
import de.johni0702.minecraft.gui.container.GuiScreen;
import de.johni0702.minecraft.gui.element.GuiElement;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.io.File;
import java.util.List;
public class GuiHandler {
private static final int BUTTON_REPLAY_CENTER = 17890236;
private static final Minecraft mc = Minecraft.getMinecraft();
private final ReplayModOnline mod;
public GuiHandler(ReplayModOnline mod) {
this.mod = mod;
}
public void register() {
FMLCommonHandler.instance().bus().register(this);
MinecraftForge.EVENT_BUS.register(this);
}
@SubscribeEvent
public void injectIntoMainMenu(GuiScreenEvent.InitGuiEvent event) {
if (!(event.gui instanceof GuiMainMenu)) {
return;
}
@SuppressWarnings("unchecked")
List<GuiButton> buttonList = event.buttonList;
GuiButton button = new GuiButton(BUTTON_REPLAY_CENTER, event.gui.width / 2 - 100,
event.gui.height / 4 + 10 + 4 * 24, I18n.format("replaymod.gui.replaycenter"));
buttonList.add(button);
}
@SubscribeEvent
public void injectIntoReplayViewer(GuiScreenEvent.InitGuiEvent.Post event) {
AbstractGuiScreen guiScreen = GuiScreen.from(event.gui);
if (!(guiScreen instanceof GuiReplayViewer)) {
return;
}
final GuiReplayViewer replayViewer = (GuiReplayViewer) guiScreen;
// Inject Upload button
for (GuiElement element : replayViewer.replayButtonPanel.getChildren()) {
if (element instanceof GuiPanel && (((GuiPanel) element).getChildren().isEmpty())) {
new de.johni0702.minecraft.gui.element.GuiButton((GuiPanel) element).onClick(new Runnable() {
@Override
public void run() {
File replayFile = replayViewer.list.getSelected().file;
GuiUploadFile uploadGui = new GuiUploadFile(mod.getApiClient(), replayFile, replayViewer);
if (mod.isLoggedIn()) {
mc.displayGuiScreen(uploadGui);
} else {
new GuiLoginPrompt(mod.getApiClient(), replayViewer, GuiScreen.wrap(uploadGui), true);
}
}
}).setSize(73, 20).setI18nLabel("replaymod.gui.upload").setDisabled();
}
}
}
@SubscribeEvent
public void onButton(GuiScreenEvent.ActionPerformedEvent.Pre event) {
if(!event.button.enabled) return;
if (event.gui instanceof GuiMainMenu) {
if (event.button.id == BUTTON_REPLAY_CENTER) {
GuiReplayCenter replayCenter = new GuiReplayCenter(mod);
if (mod.isLoggedIn()) {
replayCenter.display();
} else {
new GuiLoginPrompt(mod.getApiClient(), GuiScreen.wrap(event.gui), replayCenter, true).display();
}
}
}
}
}

View File

@@ -8,9 +8,9 @@ import com.mojang.realmsclient.gui.ChatFormatting;
import com.replaymod.core.gui.GuiReplaySettings;
import com.replaymod.replay.ReplayModReplay;
import de.johni0702.minecraft.gui.container.AbstractGuiContainer;
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
import de.johni0702.minecraft.gui.container.GuiContainer;
import de.johni0702.minecraft.gui.container.GuiPanel;
import de.johni0702.minecraft.gui.container.GuiScreen;
import de.johni0702.minecraft.gui.element.*;
import de.johni0702.minecraft.gui.element.advanced.GuiResourceLoadingList;
import de.johni0702.minecraft.gui.layout.CustomLayout;
@@ -49,7 +49,7 @@ import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class GuiReplayViewer extends AbstractGuiScreen<GuiReplayViewer> {
public class GuiReplayViewer extends GuiScreen {
private final ReplayModReplay mod;
public final GuiResourceLoadingList<GuiReplayEntry> list = new GuiResourceLoadingList<GuiReplayEntry>(this).onSelectionChanged(new Runnable() {
@@ -245,7 +245,7 @@ public class GuiReplayViewer extends AbstractGuiScreen<GuiReplayViewer> {
public void run() {
getMinecraft().displayGuiScreen(null);
}
}).setSize(73, 20).setI18nLabel("replaymod.gui.cancel");;
}).setSize(73, 20).setI18nLabel("replaymod.gui.cancel");
public final GuiPanel replayButtonPanel = new GuiPanel().setLayout(new GridLayout().setSpacingX(5).setSpacingY(5)
.setColumns(2)).addElements(null, loadButton, new GuiPanel() /* Upload */, renameButton, deleteButton);
@@ -260,9 +260,9 @@ public class GuiReplayViewer extends AbstractGuiScreen<GuiReplayViewer> {
setTitle(new GuiLabel().setI18nText("replaymod.gui.replayviewer"));
setLayout(new CustomLayout<GuiReplayViewer>() {
setLayout(new CustomLayout<GuiScreen>() {
@Override
protected void layout(GuiReplayViewer container, int width, int height) {
protected void layout(GuiScreen container, int width, int height) {
pos(buttonPanel, width / 2 - width(buttonPanel) / 2, height - 10 - height(buttonPanel));
pos(list, 0, 30);
@@ -271,11 +271,6 @@ public class GuiReplayViewer extends AbstractGuiScreen<GuiReplayViewer> {
});
}
@Override
protected GuiReplayViewer getThis() {
return this;
}
private final GuiImage defaultThumbnail = new GuiImage().setTexture(ResourceHelper.getDefaultThumbnail());
public class GuiReplayEntry extends AbstractGuiContainer<GuiReplayEntry> implements Comparable<GuiReplayEntry> {
public final File file;
@@ -349,29 +344,4 @@ public class GuiReplayViewer extends AbstractGuiScreen<GuiReplayViewer> {
return Long.compare(o.dateMillis, dateMillis);
}
}
// TODO: Online module
// if(uploadButton.isMouseOver() && !uploadButton.enabled && loadButton.enabled) {
// if(currentFileUploaded) {
// ReplayMod.tooltipRenderer.drawTooltip(mouseX, mouseY, I18n.format("replaymod.gui.viewer.alreadyuploaded"), this, Color.RED);
// }
// }
//
// private boolean currentFileUploaded = false;
//
// public void setButtonsEnabled(boolean b) {
// loadButton.enabled = b;
//
// if(b) {
// currentFileUploaded = ReplayMod.uploadedFileHandler.isUploaded(replayFileList.get(replayGuiList.selected).first().first());
// uploadButton.enabled = !currentFileUploaded;
// } else {
// uploadButton.enabled = false;
// }
//
//
// renameButton.enabled = b;
// deleteButton.enabled = b;
// }
}