diff --git a/forge-gui-mobile/src/forge/adventure/character/RewardSprite.java b/forge-gui-mobile/src/forge/adventure/character/RewardSprite.java index 1d9f02578cb..1e5e0ca9d87 100644 --- a/forge-gui-mobile/src/forge/adventure/character/RewardSprite.java +++ b/forge-gui-mobile/src/forge/adventure/character/RewardSprite.java @@ -27,7 +27,7 @@ public class RewardSprite extends CharacterSprite { if (data != null) { rewards = JSONStringLoader.parse(RewardData[].class, data, default_reward); } else { //Shouldn't happen, but make sure it doesn't fly by. - System.err.printf("Reward data is null. Using a default reward."); + System.err.print("Reward data is null. Using a default reward."); rewards = JSONStringLoader.parse(RewardData[].class, default_reward, default_reward); } } diff --git a/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java b/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java index 23b9d2c5487..5dde0955d8f 100644 --- a/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java +++ b/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java @@ -426,7 +426,7 @@ public class AdventureEventData implements Serializable { if (eventStatus == AdventureEventController.EventStatus.Ready) { currentRound = 1; eventStatus = AdventureEventController.EventStatus.Started; - } + } } public void generateParticipants(int numberOfOpponents) { @@ -599,7 +599,7 @@ public class AdventureEventData implements Serializable { } else { description += "\n"; } - description += String.format("Prizes\n3 round wins: 500 gold\n2 round wins: 200 gold\n1 round win: 100 gold\n"); + description += "Prizes\n3 round wins: 500 gold\n2 round wins: 200 gold\n1 round win: 100 gold\n"; description += "Finishing event will award an unsellable copy of each card in your Jumpstart deck."; } return description; diff --git a/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java b/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java index 3d3fbd441b5..1ed1ac02d2a 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java +++ b/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java @@ -1106,10 +1106,13 @@ public class AdventureDeckEditor extends TabPageScreen { if (canOnlyBePartnerCommander(card)) { return; //don't auto-change commander unexpectedly } + DeckSectionPage main = getMainDeckPage(); + if (main == null) + return; if (!cardManager.isInfinite()) { removeCard(card); } - getMainDeckPage().addCard(card); + main.addCard(card); } @Override diff --git a/forge-gui-mobile/src/forge/adventure/scene/ArenaScene.java b/forge-gui-mobile/src/forge/adventure/scene/ArenaScene.java index 96b41d3b26a..6949d4052a2 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/ArenaScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/ArenaScene.java @@ -238,12 +238,18 @@ public class ArenaScene extends UIScene implements IAfterMatch { arenaPlane.addActor(lost); } + boolean started = false; + private void startRound() { + if (started) + return; + started = true; DuelScene duelScene = DuelScene.instance(); EnemySprite enemy = enemies.get(enemies.size - 1); FThreads.invokeInEdtNowOrLater(() -> { Forge.setTransitionScreen(new TransitionScreen(() -> { - duelScene.initDuels(WorldStage.getInstance().getPlayerSprite(), enemy); + started = false; + duelScene.initDuels(WorldStage.getInstance().getPlayerSprite(), enemy, true, null); Forge.switchScene(duelScene); }, Forge.takeScreenshot(), true, false, false, false, "", Current.player().avatar(), enemy.getAtlasPath(), Current.player().getName(), enemy.getName())); }); diff --git a/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java b/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java index a2e1503bbfe..6e7e689f96d 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java @@ -127,7 +127,7 @@ public class NewGameScene extends MenuScene { modeNames[i] = modes.get(i).getName(); mode.setTextList(modeNames); - gender.setTextList(new String[]{Forge.getLocalizer().getInstance().getMessage("lblMale"), Forge.getLocalizer().getInstance().getMessage("lblFemale")}); + gender.setTextList(new String[]{Forge.getLocalizer().getMessage("lblMale"), Forge.getLocalizer().getMessage("lblFemale")}); gender.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { @@ -217,7 +217,7 @@ public class NewGameScene extends MenuScene { } Forge.switchScene(GameScene.instance()); }; - Forge.setTransitionScreen(new TransitionScreen(runnable, null, false, true, "Generating World...")); + Forge.setTransitionScreen(new TransitionScreen(runnable, null, false, true, Forge.getLocalizer().getMessage("lblGeneratingWorld"))); return true; } diff --git a/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java b/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java index 9b224eb2e1d..c95d826a21a 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java @@ -273,6 +273,7 @@ public class PlayerStatisticScene extends UIScene { if (g != null) //skip variants continue; } + a.updateTrophyImage(); TextureRegion textureRegion = new TextureRegion(((FBufferedImage) a.getImage()).getTexture()); textureRegion.flip(false, true); Image image = new Image(textureRegion); diff --git a/forge-gui-mobile/src/forge/adventure/scene/SaveLoadScene.java b/forge-gui-mobile/src/forge/adventure/scene/SaveLoadScene.java index dc3a9b0e0ce..10e6e05dfb3 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/SaveLoadScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/SaveLoadScene.java @@ -196,14 +196,19 @@ public class SaveLoadScene extends UIScene { return true; } + boolean loaded = false; + public void loadSave() { + if (loaded) + return; + loaded = true; switch (mode) { case Save: if (TileMapScene.instance().currentMap().isInMap()) { //Access to screen should be disabled, but stop the process just in case. //Saving needs to be disabled inside maps until we can capture and load exact map state //Otherwise location based events for quests can be skipped by saving and then loading outside the map - Dialog noSave = createGenericDialog("", "!!GAME NOT SAVED!!\nManual saving is only available on the world map","OK",null, null, null); + Dialog noSave = createGenericDialog("", Forge.getLocalizer().getMessage("lblGameNotSaved"), Forge.getLocalizer().getMessage("lblOK"),null, null, null); showDialog(noSave); return; } @@ -226,17 +231,19 @@ public class SaveLoadScene extends UIScene { showDialog(saveDialog); stage.setKeyboardFocus(textInput); } + loaded = false; break; case Load: try { Forge.setTransitionScreen(new TransitionScreen(() -> { + loaded = false; if (WorldSave.load(currentSlot)) { SoundSystem.instance.changeBackgroundTrack(); Forge.switchScene(GameScene.instance()); } else { Forge.clearTransitionScreen(); } - }, null, false, true, "Loading World...")); + }, null, false, true, Forge.getLocalizer().getMessage("lblLoadingWorld"))); } catch (Exception e) { Forge.clearTransitionScreen(); } @@ -244,6 +251,7 @@ public class SaveLoadScene extends UIScene { case NewGamePlus: try { Forge.setTransitionScreen(new TransitionScreen(() -> { + loaded = false; if (WorldSave.load(currentSlot)) { WorldSave.getCurrentSave().clearChanges(); WorldSave.getCurrentSave().getWorld().generateNew(0); @@ -261,8 +269,9 @@ public class SaveLoadScene extends UIScene { } else { Forge.clearTransitionScreen(); } - }, null, false, true, "Generating World...")); + }, null, false, true, Forge.getLocalizer().getMessage("lblGeneratingWorld"))); } catch (Exception e) { + loaded = false; Forge.clearTransitionScreen(); } break; diff --git a/forge-gui-mobile/src/forge/adventure/scene/StartScene.java b/forge-gui-mobile/src/forge/adventure/scene/StartScene.java index 757e8cdfc03..da00bfc4c20 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/StartScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/StartScene.java @@ -1,24 +1,33 @@ package forge.adventure.scene; +import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.scenes.scene2d.ui.Dialog; +import com.badlogic.gdx.utils.Timer; import com.github.tommyettinger.textra.TextraButton; +import com.github.tommyettinger.textra.TypingLabel; import forge.Forge; import forge.adventure.stage.GameHUD; import forge.adventure.stage.GameStage; import forge.adventure.stage.MapStage; import forge.adventure.util.Config; +import forge.adventure.util.Controls; import forge.adventure.world.WorldSave; +import forge.localinstance.properties.ForgeProfileProperties; import forge.screens.TransitionScreen; import forge.sound.SoundSystem; +import forge.util.ZipUtil; + +import java.io.File; +import java.io.IOException; /** * First scene after the splash screen */ public class StartScene extends UIScene { - private static StartScene object; - Dialog exitDialog; + Dialog exitDialog, backupDialog, zipDialog, unzipDialog; TextraButton saveButton, resumeButton, continueButton; + TypingLabel version = Controls.newTypingLabel("{GRADIENT}[%80]" + Forge.CURRENT_VERSION + "{ENDGRADIENT}"); public StartScene() { @@ -30,6 +39,7 @@ public class StartScene extends UIScene { ui.onButtonPress("Resume", StartScene.this::Resume); ui.onButtonPress("Continue", StartScene.this::Continue); ui.onButtonPress("Settings", StartScene.this::settings); + ui.onButtonPress("Backup", StartScene.this::backup); ui.onButtonPress("Exit", StartScene.this::Exit); ui.onButtonPress("Switch", StartScene.this::switchToClassic); @@ -40,6 +50,9 @@ public class StartScene extends UIScene { saveButton.setVisible(false); resumeButton.setVisible(false); + version.setHeight(5); + version.skipToTheEnd(); + ui.addActor(version); } public static StartScene instance() { @@ -55,7 +68,7 @@ public class StartScene extends UIScene { public boolean Save() { if (TileMapScene.instance().currentMap().isInMap()) { - Dialog noSave = createGenericDialog("", "!!GAME NOT SAVED!!\nManual saving is only available on the world map","OK",null, null, null); + Dialog noSave = createGenericDialog("", Forge.getLocalizer().getMessage("lblGameNotSaved"), Forge.getLocalizer().getMessage("lblOK"),null, null, null); showDialog(noSave); } else { SaveLoadScene.instance().setMode(SaveLoadScene.Modes.Save); @@ -79,20 +92,27 @@ public class StartScene extends UIScene { return true; } + boolean loaded = false; + public boolean Continue() { final String lastActiveSave = Config.instance().getSettingData().lastActiveSave; if (WorldSave.isSafeFile(lastActiveSave)) { + if (loaded) + return true; + loaded = true; try { Forge.setTransitionScreen(new TransitionScreen(() -> { + loaded = false; if (WorldSave.load(WorldSave.filenameToSlot(lastActiveSave))) { SoundSystem.instance.changeBackgroundTrack(); Forge.switchScene(GameScene.instance()); } else { Forge.clearTransitionScreen(); } - }, null, false, true, "Loading World...")); + }, null, false, true, Forge.getLocalizer().getMessage("lblLoadingWorld"))); } catch (Exception e) { + loaded = false; Forge.clearTransitionScreen(); } } @@ -105,6 +125,86 @@ public class StartScene extends UIScene { return true; } + public boolean backup() { + if (backupDialog == null) { + backupDialog = createGenericDialog(Forge.getLocalizer().getMessage("lblData"), + null, Forge.getLocalizer().getMessage("lblBackup"), + Forge.getLocalizer().getMessage("lblRestore"), + () -> { + removeDialog(); + Timer.schedule(new Timer.Task() { + @Override + public void run() { + generateBackup(); + } + }, 0.2f); + }, + () -> { + removeDialog(); + Timer.schedule(new Timer.Task() { + @Override + public void run() { + restoreBackup(); + } + }, 0.2f); + }, true, Forge.getLocalizer().getMessage("lblCancel")); + } + showDialog(backupDialog); + return true; + } + public boolean generateBackup() { + try { + File source = new FileHandle(ForgeProfileProperties.getUserDir() + "/adventure/Shandalar").file(); + File target = new FileHandle(Forge.getDeviceAdapter().getDownloadsDir()).file(); + ZipUtil.zip(source, target); + zipDialog = createGenericDialog("", + Forge.getLocalizer().getMessage("lblSaveLocation") + "\n" + target.getAbsolutePath() + File.separator + ZipUtil.backupFile, + Forge.getLocalizer().getMessage("lblOK"), null, this::removeDialog, null); + } catch (IOException e) { + zipDialog = createGenericDialog("", + Forge.getLocalizer().getMessage("lblErrorSavingFile") + "\n\n" + e.getMessage(), + Forge.getLocalizer().getMessage("lblOK"), null, this::removeDialog, null); + } finally { + showDialog(zipDialog); + } + return true; + } + public boolean restoreBackup() { + File source = new FileHandle(Forge.getDeviceAdapter().getDownloadsDir() + ZipUtil.backupFile).file(); + File target = new FileHandle(ForgeProfileProperties.getUserDir() + "/adventure/Shandalar").file().getParentFile(); + if (unzipDialog == null) { + unzipDialog = createGenericDialog("", + Forge.getLocalizer().getMessage("lblDoYouWantToRestoreBackup"), + Forge.getLocalizer().getMessage("lblYes"), Forge.getLocalizer().getMessage("lblNo"), + () -> { + removeDialog(); + Timer.schedule(new Timer.Task() { + @Override + public void run() { + extract(source, target); + } + }, 0.2f); + }, this::removeDialog); + } + showDialog(unzipDialog); + return true; + } + public boolean extract(File source, File target) { + String title = "", val = ""; + try { + val = Forge.getLocalizer().getMessage("lblFiles") + ":\n" + ZipUtil.unzip(source, target); + } catch (IOException e) { + title = Forge.getLocalizer().getMessage("lblError"); + val = e.getMessage(); + } finally { + Config.instance().getSettingData().lastActiveSave = null; + Config.instance().saveSettings(); + showDialog(createGenericDialog(title, val, + Forge.getLocalizer().getMessage("lblOK"), null, this::removeDialog, null)); + } + return true; + } + public boolean Exit() { if (exitDialog == null) { exitDialog = createGenericDialog(Forge.getLocalizer().getMessage("lblExitForge"), @@ -123,16 +223,7 @@ public class StartScene extends UIScene { Forge.switchToClassic(); } - @Override - public void enter() { - boolean hasSaveButton = WorldSave.getCurrentSave().getWorld().getData() != null; - if (hasSaveButton) { - TileMapScene scene = TileMapScene.instance(); - hasSaveButton = !scene.currentMap().isInMap() || scene.isAutoHealLocation(); - } - saveButton.setVisible(hasSaveButton); - saveButton.setDisabled(TileMapScene.instance().currentMap().isInMap()); - + public void updateResumeContinue() { boolean hasResumeButton = WorldSave.getCurrentSave().getWorld().getData() != null; resumeButton.setVisible(hasResumeButton); @@ -146,7 +237,18 @@ public class StartScene extends UIScene { } else { continueButton.setVisible(false); } + } + @Override + public void enter() { + boolean hasSaveButton = WorldSave.getCurrentSave().getWorld().getData() != null; + if (hasSaveButton) { + TileMapScene scene = TileMapScene.instance(); + hasSaveButton = !scene.currentMap().isInMap() || scene.isAutoHealLocation(); + } + saveButton.setVisible(hasSaveButton); + saveButton.setDisabled(TileMapScene.instance().currentMap().isInMap()); + updateResumeContinue(); if (Forge.createNewAdventureMap) { this.NewGame(); diff --git a/forge-gui-mobile/src/forge/adventure/scene/UIScene.java b/forge-gui-mobile/src/forge/adventure/scene/UIScene.java index 94174cf7bb4..d34ccd6819a 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/UIScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/UIScene.java @@ -239,6 +239,9 @@ public class UIScene extends Scene { } public Dialog createGenericDialog(String title, String label, String stringYes, String stringNo, Runnable runnableYes, Runnable runnableNo) { + return createGenericDialog(title, label, stringYes, stringNo, runnableYes, runnableNo, false, ""); + } + public Dialog createGenericDialog(String title, String label, String stringYes, String stringNo, Runnable runnableYes, Runnable runnableNo, boolean cancelButton, String stringCancel) { Dialog dialog = new Dialog(title == null ? "" : title, Controls.getSkin()); if (label != null) dialog.text(label); @@ -248,6 +251,10 @@ public class UIScene extends Scene { TextraButton no = Controls.newTextButton(stringNo, runnableNo); dialog.button(no); } + if (cancelButton) { + TextraButton cancel = Controls.newTextButton(stringCancel, this::removeDialog); + dialog.button(cancel); + } return dialog; } diff --git a/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java b/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java index df57bab8aa8..eb8ecbcabc4 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java +++ b/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java @@ -39,7 +39,7 @@ public class ConsoleCommandInterpreter { public String complete(String text) { String[] words = splitOnSpace(text); Command currentCommand = root; - String completionString = ""; + StringBuilder completionString = new StringBuilder(); for (String name : words) { if (!currentCommand.children.containsKey(name)) { for (String key : currentCommand.children.keySet()) { @@ -49,7 +49,7 @@ public class ConsoleCommandInterpreter { } break; } - completionString += name + " "; + completionString.append(name).append(" "); currentCommand = currentCommand.children.get(name); } return text; diff --git a/forge-gui-mobile/src/forge/adventure/stage/MapStage.java b/forge-gui-mobile/src/forge/adventure/stage/MapStage.java index 767f5995145..3466f478301 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/MapStage.java +++ b/forge-gui-mobile/src/forge/adventure/stage/MapStage.java @@ -1039,6 +1039,7 @@ public class MapStage extends GameStage { } } + boolean started = false; public void beginDuel(EnemySprite mob) { if (mob == null) return; mob.clearCollisionHeight(); @@ -1054,6 +1055,9 @@ public class MapStage extends GameStage { Forge.restrictAdvMenus = true; player.clearCollisionHeight(); startPause(0.8f, () -> { + if (started) + return; + started = true; Forge.setCursor(null, Forge.magnifyToggle ? "1" : "2"); SoundSystem.instance.play(SoundEffectType.ManaBurn, false); DuelScene duelScene = DuelScene.instance(); @@ -1061,6 +1065,7 @@ public class MapStage extends GameStage { if (!isLoadingMatch) { isLoadingMatch = true; Forge.setTransitionScreen(new TransitionScreen(() -> { + started = false; duelScene.initDuels(player, mob); if (isInMap && effect != null && !mob.ignoreDungeonEffect) duelScene.setDungeonEffect(effect); diff --git a/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java b/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java index 5b2b9c2da20..aba1a56aea1 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java +++ b/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java @@ -64,6 +64,7 @@ public class WorldStage extends GameStage implements SaveFileContent { final Rectangle tempBoundingRect = new Rectangle(); final Vector2 enemyMoveVector = new Vector2(); + boolean collided = false; @Override protected void onActing(float delta) { if (isPaused() || MapStage.getInstance().isDialogOnlyInput()) @@ -110,6 +111,9 @@ public class WorldStage extends GameStage implements SaveFileContent { } if (player.collideWith(mob)) { + if (collided) + return; + collided = true; player.setAnimation(CharacterSprite.AnimationTypes.Attack); player.playEffect(Paths.EFFECT_SPARKS, 0.5f); mob.setAnimation(CharacterSprite.AnimationTypes.Attack); @@ -126,6 +130,7 @@ public class WorldStage extends GameStage implements SaveFileContent { DuelScene duelScene = DuelScene.instance(); FThreads.invokeInEdtNowOrLater(() -> { Forge.setTransitionScreen(new TransitionScreen(() -> { + collided = false; duelScene.initDuels(player, mob); Forge.switchScene(duelScene); }, Forge.takeScreenshot(), true, false, false, false, "", Current.player().avatar(), mob.getAtlasPath(), Current.player().getName(), mob.getName())); diff --git a/forge-gui-mobile/src/forge/adventure/util/Controls.java b/forge-gui-mobile/src/forge/adventure/util/Controls.java index a6402f57257..ccbb56268a6 100644 --- a/forge-gui-mobile/src/forge/adventure/util/Controls.java +++ b/forge-gui-mobile/src/forge/adventure/util/Controls.java @@ -21,6 +21,7 @@ import com.badlogic.gdx.utils.Timer; import com.github.tommyettinger.textra.Font; import com.github.tommyettinger.textra.TextraButton; import com.github.tommyettinger.textra.TextraLabel; +import com.github.tommyettinger.textra.TypingButton; import com.github.tommyettinger.textra.TypingLabel; import forge.Forge; import forge.adventure.player.AdventurePlayer; @@ -84,12 +85,53 @@ public class Controls { } + static class TypingButtonFix extends TypingButton { + public TypingButtonFix(@Null String text) { + super(text == null ? "NULL" : text, Controls.getSkin(), Controls.getTextraFont()); + addListener(new ClickListener(){ + @Override + public void clicked(InputEvent event, float x, float y) { + super.clicked(event, x, y); + SoundSystem.instance.play(SoundEffectType.ButtonPress, false); + } + }); + } + + @Override + public void setStyle(Button.ButtonStyle style, boolean makeGridGlyphs) { + super.setStyle(style, makeGridGlyphs); + this.getTextraLabel().setFont(Controls.getTextraFont()); + + } + + @Override + public String getText() { + return this.getTextraLabel().storedText; + } + + @Override + public void setText(@Null String text) { + getTextraLabel().storedText = text; + getTextraLabel().layout.setTargetWidth(getTextraLabel().getMaxWidth()); + getTextraLabel().getFont().markup(text, getTextraLabel().layout.clear()); + getTextraLabel().setWidth(getTextraLabel().layout.getWidth() + (getTextraLabel().style != null && getTextraLabel().style.background != null ? getTextraLabel().style.background.getLeftWidth() + getTextraLabel().style.background.getRightWidth() : 0.0F)); + layout(); + } + + } + static public TextraButton newTextButton(String text) { TextraButton button = new TextButtonFix(text); button.getTextraLabel().setWrap(false); return button; } + static public TypingButton newTypingButton(String text) { + TypingButton button = new TypingButtonFix(text); + button.getTextraLabel().setWrap(false); + return button; + } + static public Rectangle getBoundingRect(Actor actor) { return new Rectangle(actor.getX(), actor.getY(), actor.getWidth(), actor.getHeight()); } diff --git a/forge-gui-mobile/src/forge/adventure/util/SaveFileData.java b/forge-gui-mobile/src/forge/adventure/util/SaveFileData.java index 1c6d1dc5185..a2ce19e433f 100644 --- a/forge-gui-mobile/src/forge/adventure/util/SaveFileData.java +++ b/forge-gui-mobile/src/forge/adventure/util/SaveFileData.java @@ -354,11 +354,10 @@ public class SaveFileData extends HashMap final long localSUID = localClassDescriptor.getSerialVersionUID(); final long streamSUID = resultClassDescriptor.getSerialVersionUID(); if (streamSUID != localSUID) { // check for serialVersionUID mismatch. - final StringBuffer s = new StringBuffer("Overriding serialized class version mismatch: "); - s.append("local serialVersionUID = ").append(localSUID); - s.append(" stream serialVersionUID = ").append(streamSUID); + String s = "Overriding serialized class version mismatch: " + "local serialVersionUID = " + localSUID + + " stream serialVersionUID = " + streamSUID; - System.err.println("[Invalid Class Exception]\n"+s); + System.err.println("[Invalid Class Exception]\n"+ s); resultClassDescriptor = localClassDescriptor; // Use local class descriptor for deserialization } } diff --git a/forge-gui-mobile/src/forge/adventure/util/TemplateTmxMapLoader.java b/forge-gui-mobile/src/forge/adventure/util/TemplateTmxMapLoader.java index 6e4e9e3d5da..e9de0ec258d 100644 --- a/forge-gui-mobile/src/forge/adventure/util/TemplateTmxMapLoader.java +++ b/forge-gui-mobile/src/forge/adventure/util/TemplateTmxMapLoader.java @@ -31,6 +31,8 @@ public class TemplateTmxMapLoader extends TmxMapLoader { this.root = xml.parse(tmxFile); parameter.generateMipMaps=true; + parameter.textureMinFilter = Texture.TextureFilter.Nearest; + parameter.textureMagFilter = Texture.TextureFilter.Nearest; final Array textureFiles = getDependencyFileHandles(tmxFile); for (FileHandle textureFile : textureFiles) { Texture texture = new Texture(textureFile, parameter.generateMipMaps); diff --git a/forge-gui-mobile/src/forge/adventure/util/pathfinding/NavigationMap.java b/forge-gui-mobile/src/forge/adventure/util/pathfinding/NavigationMap.java index 17ecf50c282..6b9c62508f1 100644 --- a/forge-gui-mobile/src/forge/adventure/util/pathfinding/NavigationMap.java +++ b/forge-gui-mobile/src/forge/adventure/util/pathfinding/NavigationMap.java @@ -23,13 +23,10 @@ public class NavigationMap { this.half = spriteSize / 2; } - RayCastCallback callback = new RayCastCallback() { - @Override - public float reportRayFixture(Fixture fixture, Vector2 vector2, Vector2 vector21, float v) { - if (v < 1.0) - rayCollided = true; - return 0; - } + RayCastCallback callback = (fixture, vector2, vector21, v) -> { + if (v < 1.0) + rayCollided = true; + return 0; }; // public void initializeOverworldGeometryGraph() { diff --git a/forge-gui-mobile/src/forge/adventure/world/BiomeTexture.java b/forge-gui-mobile/src/forge/adventure/world/BiomeTexture.java index 498b5d65064..01d53a95724 100644 --- a/forge-gui-mobile/src/forge/adventure/world/BiomeTexture.java +++ b/forge-gui-mobile/src/forge/adventure/world/BiomeTexture.java @@ -20,7 +20,7 @@ import java.util.ArrayList; public class BiomeTexture implements Serializable { private final BiomeData data; private final int tileSize; - public static Pixmap emptyPixmap = null; + public Pixmap emptyPixmap = null; ArrayList> images = new ArrayList<>(); ArrayList> smallImages = new ArrayList<>(); ArrayList> edgeImages = new ArrayList<>(); diff --git a/forge-gui-mobile/src/forge/animation/AbilityEffect.java b/forge-gui-mobile/src/forge/animation/AbilityEffect.java index bda4ca6a8cc..60e3ce91091 100644 --- a/forge-gui-mobile/src/forge/animation/AbilityEffect.java +++ b/forge-gui-mobile/src/forge/animation/AbilityEffect.java @@ -25,7 +25,8 @@ public enum AbilityEffect { if (soundClip == null) { soundClip = AudioClip.createClip(ForgeConstants.EFFECTS_DIR + wav); } - soundClip.play(FModel.getPreferences().getPrefInt(ForgePreferences.FPref.UI_VOL_SOUNDS)/100f); + if (soundClip != null) + soundClip.play(FModel.getPreferences().getPrefInt(ForgePreferences.FPref.UI_VOL_SOUNDS)/100f); animation.start(); } diff --git a/forge-gui/res/adventure/common/ui/start_menu.json b/forge-gui/res/adventure/common/ui/start_menu.json index da3427c52fc..7e0d89bdd35 100644 --- a/forge-gui/res/adventure/common/ui/start_menu.json +++ b/forge-gui/res/adventure/common/ui/start_menu.json @@ -75,11 +75,21 @@ "name": "Settings", "text": "tr(lblSettings)", "selectable": true, - "width": 160, + "width": 80, "height": 30, "x": 160, "y": 180 }, + { + "type": "TextButton", + "name": "Backup", + "text": "tr(lblData)", + "selectable": true, + "width": 80, + "height": 30, + "x": 240, + "y": 180 + }, { "type": "TextButton", "name": "Exit", diff --git a/forge-gui/res/adventure/common/ui/start_menu_portrait.json b/forge-gui/res/adventure/common/ui/start_menu_portrait.json index 484bab545b7..faf686bc8b5 100644 --- a/forge-gui/res/adventure/common/ui/start_menu_portrait.json +++ b/forge-gui/res/adventure/common/ui/start_menu_portrait.json @@ -64,11 +64,21 @@ "name": "Settings", "text": "tr(lblSettings)", "selectable": true, - "width": 238, + "width": 118, "height": 48, "x": 16, "yOffset": 8 }, + { + "type": "TextButton", + "name": "Backup", + "text": "tr(lblData)", + "selectable": true, + "width": 118, + "height": 48, + "x": 134, + "yOffset": -48 + }, { "type": "TextButton", "name": "Switch", diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index c4f5de1accb..cca72ea738d 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -361,6 +361,14 @@ lblAutoYields=Automatische Bestätigung lblDeckList=Deckliste lblClose=Schließen lblExitForge=Forge verlassen +lblLoadingWorld=Welt wird geladen... +lblGeneratingWorld=Welt erzeugen... +lblGameNotSaved=!!SPIEL NICHT GESPEICHERT!!\nManuelles Speichern ist nur auf der Weltkarte verfügbar +lblBackup=Sicherung +lblRestore=Wiederherstellen +lblData=Daten +lblSaveLocation=Sicherer Ort: +lblDoYouWantToRestoreBackup=Durch das Wiederherstellen des Backups werden alle Ihre Speicherungen überschrieben. Möchtest du fortfahren? #ConstructedGameMenu.java lblSelectAvatarFor=Wähle Avatar für %s lblRemoveSmallCreatures=Entferne 1/1- und 0/X-Kreaturen aus erzeugten Decks diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index 8e208e38300..8d5e8546f4f 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -363,6 +363,14 @@ lblAutoYields=Auto-Yields lblDeckList=Deck List lblClose=Close lblExitForge=Exit Forge +lblLoadingWorld=Loading World... +lblGeneratingWorld=Generating World... +lblGameNotSaved=!!GAME NOT SAVED!!\nManual saving is only available on the world map +lblBackup=Backup +lblRestore=Restore +lblData=Data +lblSaveLocation=Save Location: +lblDoYouWantToRestoreBackup=Restoring backup will overwrite all of your save. Do you want to continue? #ConstructedGameMenu.java lblSelectAvatarFor=Select avatar for %s lblRemoveSmallCreatures=Remove 1/1 and 0/X creatures in generated decks. diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index 5bdea68940d..1c7131e6854 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -361,6 +361,14 @@ lblAutoYields=Auto-ceder lblDeckList=Lista del mazo lblClose=Cerrar lblExitForge=Salir de Forge +lblLoadingWorld=Cargando mundo... +lblGeneratingWorld=Generando mundo... +lblGameNotSaved=!!JUEGO NO GUARDADO!!\nEl guardado manual solo está disponible en el mapa mundial +lblBackup=Respaldo +lblRestore=Restaurar +lblData=Datos +lblSaveLocation=Guardar dirección: +lblDoYouWantToRestoreBackup=Restaurar la copia de seguridad sobrescribirá todo lo que guardó. ¿Quieres continuar? #ConstructedGameMenu.java lblSelectAvatarFor=Seleccionar avatar para %s lblRemoveSmallCreatures=Elimina 1/1 y 0 /X criaturas en los mazos generados. diff --git a/forge-gui/res/languages/fr-FR.properties b/forge-gui/res/languages/fr-FR.properties index 47b62b2e3f5..7b95820dbc2 100644 --- a/forge-gui/res/languages/fr-FR.properties +++ b/forge-gui/res/languages/fr-FR.properties @@ -361,6 +361,14 @@ lblAutoYields=Rendements automatiques lblDeckList=Liste des decks lblClose=Fermer lblExitForge=Quitter Forge +lblLoadingWorld=Chargement du monde... +lblGeneratingWorld=Générer le monde... +lblGameNotSaved=!!JEU NON ENREGISTRÉ !!\nLa sauvegarde manuelle n'est disponible que sur la carte du monde +lblBackup=Sauvegarde +lblRestore=Restaurer +lblData=Données +lblSaveLocation=Enregistrer l'emplacement: +lblDoYouWantToRestoreBackup=La restauration de la sauvegarde écrasera toute votre sauvegarde. Voulez-vous continuer? #ConstructedGameMenu.java lblSelectAvatarFor=Sélectionner un avatar pour %s lblRemoveSmallCreatures=Retire les créatures 1/1 et 0/X dans les decks générés. diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index 2be2b54947b..1b8c2582619 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -360,6 +360,14 @@ lblAutoYields=Consensi automatici lblDeckList=Lista del mazzo lblClose=Chiudi lblExitForge=Esci da Forge +lblLoadingWorld=Caricamento del mondo... +lblGeneratingWorld=Generazione del mondo... +lblGameNotSaved=!!GIOCO NON SALVATO!!\nIl salvataggio manuale è disponibile solo sulla mappa del mondo +lblBackup=Backup +lblRestore=Ristabilire +lblData=Dati +lblSaveLocation=Salva l'indirizzo: +lblDoYouWantToRestoreBackup=Il ripristino del backup sovrascriverà tutti i tuoi salvataggi. Vuoi continuare? #ConstructedGameMenu.java lblSelectAvatarFor=Seleziona avatar per %s lblRemoveSmallCreatures=Rimuovi le creature 1/1 e 0 / X nei mazzi generati. diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index 47ae620b7d5..54f2c407037 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -361,6 +361,14 @@ lblAutoYields=優先権の自動放棄 lblDeckList=デッキリスト lblClose=閉じる lblExitForge=Forge を終了します +lblLoadingWorld=世界を読み込んでいます... +lblGeneratingWorld=ワールドを生成中... +lblGameNotSaved=!!ゲームは保存されていません!!\n手動保存はワールドマップでのみ使用できます +lblBackup=バックアップ +lblRestore=復元する +lblData=データ +lblSaveLocation=位置を保存: +lblDoYouWantToRestoreBackup=バックアップを復元すると、保存内容がすべて上書きされます。続けたいですか? #ConstructedGameMenu.java lblSelectAvatarFor=%sアバターのデッキを選択 lblRemoveSmallCreatures=生成されたデッキに 1/1 および 0/X クリーチャーを含まない。 diff --git a/forge-gui/res/languages/pt-BR.properties b/forge-gui/res/languages/pt-BR.properties index 79e9b9a6071..1e6616b8817 100644 --- a/forge-gui/res/languages/pt-BR.properties +++ b/forge-gui/res/languages/pt-BR.properties @@ -373,6 +373,14 @@ lblAutoYields=Resolver automático lblDeckList=Lista de Deck lblClose=Fechar lblExitForge=Sair da Forge +lblLoadingWorld=Carregando mundo... +lblGeneratingWorld=Gerando mundo... +lblGameNotSaved=!!JOGO NÃO SALVO!!\nO salvamento manual só está disponível no mapa mundial +lblBackup=Cópia de segurança +lblRestore=Restaurar +lblData=Dados +lblSaveLocation=Salvar localização: +lblDoYouWantToRestoreBackup=A restauração do backup substituirá todos os seus dados salvos. Você quer continuar? #ConstructedGameMenu.java lblSelectAvatarFor=Escolha o avatar para %s lblRemoveSmallCreatures=Remover criaturas 1/1 e 0/X dos decks gerados. diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index a30a4d5cc89..4a977afe4e9 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -361,6 +361,14 @@ lblAutoYields=自动让过 lblDeckList=套牌列表 lblClose=关闭 lblExitForge=退出Forge +lblLoadingWorld=正在加载世界... +lblGeneratingWorld=生成世界... +lblGameNotSaved=!!游戏未保存!!\n手动保存仅在世界地图上可用 +lblBackup=备份 +lblRestore=恢复 +lblData=数据 +lblSaveLocation=保存位置: +lblDoYouWantToRestoreBackup=恢复备份将覆盖您的所有保存。你想继续吗? #ConstructedGameMenu.java lblSelectAvatarFor=为%s选择头像 lblRemoveSmallCreatures=将1/1和0/X生物从生成套牌中移除。 diff --git a/forge-gui/src/main/java/forge/localinstance/achievements/Achievement.java b/forge-gui/src/main/java/forge/localinstance/achievements/Achievement.java index 3a4d10a65e8..2adb929c998 100644 --- a/forge-gui/src/main/java/forge/localinstance/achievements/Achievement.java +++ b/forge-gui/src/main/java/forge/localinstance/achievements/Achievement.java @@ -128,7 +128,7 @@ public abstract class Achievement { return earnedSpecial() || earnedMythic() || earnedRare() || earnedUncommon() || earnedCommon(); } - private void updateTrophyImage() { + public void updateTrophyImage() { FSkinProp background; float opacity = 1; if (earnedSpecial()) { diff --git a/forge-gui/src/main/java/forge/util/ZipUtil.java b/forge-gui/src/main/java/forge/util/ZipUtil.java new file mode 100644 index 00000000000..16f5f76ffc8 --- /dev/null +++ b/forge-gui/src/main/java/forge/util/ZipUtil.java @@ -0,0 +1,103 @@ +package forge.util; + +import java.io.*; +//import java.time.Instant; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +/* +* https://www.baeldung.com/java-compress-and-uncompress +*/ +public class ZipUtil { + public static String backupFile = "forge.adv"; + public static void zip(File source, File dest) throws IOException { + //String now = Instant.now().toString() + "-"; + FileOutputStream fos = new FileOutputStream(dest.getAbsolutePath() + File.separator /*+ now*/ + backupFile); + ZipOutputStream zipOut = new ZipOutputStream(fos); + + //File fileToZip = new File(sourceFile); + zipFile(source, source.getName(), zipOut); + zipOut.close(); + fos.close(); + } + + private static void zipFile(File fileToZip, String fileName, ZipOutputStream zipOut) throws IOException { + if (fileToZip.isHidden()) { + return; + } + if (fileToZip.isDirectory()) { + if (fileName.endsWith("/")) { + zipOut.putNextEntry(new ZipEntry(fileName)); + zipOut.closeEntry(); + } else { + zipOut.putNextEntry(new ZipEntry(fileName + "/")); + zipOut.closeEntry(); + } + File[] children = fileToZip.listFiles(); + if (children != null) { + for (File childFile : children) { + zipFile(childFile, fileName + "/" + childFile.getName(), zipOut); + } + } + return; + } + FileInputStream fis = new FileInputStream(fileToZip); + ZipEntry zipEntry = new ZipEntry(fileName); + zipOut.putNextEntry(zipEntry); + byte[] bytes = new byte[1024]; + int length; + while ((length = fis.read(bytes)) >= 0) { + zipOut.write(bytes, 0, length); + } + fis.close(); + } + + public static String unzip(File fileZip, File destDir) throws IOException { + StringBuilder val = new StringBuilder(); + byte[] buffer = new byte[1024]; + ZipInputStream zis = new ZipInputStream(new FileInputStream(fileZip)); + ZipEntry zipEntry = zis.getNextEntry(); + while (zipEntry != null) { + File newFile = newFile(destDir, zipEntry); + if (zipEntry.isDirectory()) { + if (!newFile.isDirectory() && !newFile.mkdirs()) { + throw new IOException("Failed to create directory " + newFile); + } + } else { + // fix for Windows-created archives + File parent = newFile.getParentFile(); + if (!parent.isDirectory() && !parent.mkdirs()) { + throw new IOException("Failed to create directory " + parent); + } + + // write file content + val.append(" * "). append(newFile.getName()).append("\n"); + FileOutputStream fos = new FileOutputStream(newFile); + int len; + while ((len = zis.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + fos.close(); + } + zipEntry = zis.getNextEntry(); + } + + zis.closeEntry(); + zis.close(); + return val.toString(); + } + + private static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException { + File destFile = new File(destinationDir, zipEntry.getName()); + + String destDirPath = destinationDir.getCanonicalPath(); + String destFilePath = destFile.getCanonicalPath(); + + if (!destFilePath.startsWith(destDirPath + File.separator)) { + throw new IOException("Entry is outside of the target dir: " + zipEntry.getName()); + } + + return destFile; + } +}