From d6f5bf20f483b5ebd7ced73890293aaa98db369d Mon Sep 17 00:00:00 2001 From: jjayers99 <56438137+jjayers99@users.noreply.github.com> Date: Wed, 31 May 2023 00:00:21 -0400 Subject: [PATCH] Adventure tavern events initial implementation --- .../src/main/java/forge/deck/DeckFormat.java | 1 + .../src/main/java/forge/game/GameType.java | 2 + .../adventure/data/AdventureEventData.java | 526 +++++++++++++++++ .../adventure/data/AdventureQuestData.java | 11 + .../adventure/data/AdventureQuestStage.java | 47 +- .../src/forge/adventure/data/DialogData.java | 12 +- .../src/forge/adventure/data/RewardData.java | 9 + .../adventure/player/AdventurePlayer.java | 62 ++ .../adventure/player/PlayerStatistic.java | 66 +++ .../adventure/scene/AdventureDeckEditor.java | 279 +++++++-- .../forge/adventure/scene/DeckEditScene.java | 2 +- .../src/forge/adventure/scene/DraftScene.java | 47 ++ .../src/forge/adventure/scene/DuelScene.java | 121 ++-- .../src/forge/adventure/scene/EventScene.java | 539 ++++++++++++++++++ .../src/forge/adventure/scene/InnScene.java | 90 ++- .../forge/adventure/scene/InventoryScene.java | 167 ++++-- .../src/forge/adventure/scene/MenuScene.java | 295 ++++++++++ .../adventure/scene/PlayerStatisticScene.java | 41 +- .../forge/adventure/scene/RewardScene.java | 10 + .../src/forge/adventure/scene/ShopScene.java | 2 +- .../src/forge/adventure/stage/MapStage.java | 2 +- .../util/AdventureEventController.java | 140 +++++ .../util/AdventureQuestController.java | 13 + .../src/forge/adventure/util/CardUtil.java | 63 ++ .../src/forge/adventure/util/MapDialog.java | 9 + .../src/forge/adventure/util/Reward.java | 11 +- .../src/forge/adventure/util/RewardActor.java | 47 ++ .../src/forge/deck/FDeckEditor.java | 10 +- .../src/forge/screens/TransitionScreen.java | 31 +- .../limited/DraftingProcessScreen.java | 4 +- .../match/winlose/AdventureWinLose.java | 19 +- .../screens/match/winlose/ViewWinLose.java | 20 +- .../res/adventure/Shandalar/ui/event.json | 104 ++++ .../Shandalar/ui/event_portrait.json | 102 ++++ forge-gui/res/adventure/Shandalar/ui/inn.json | 23 +- .../adventure/Shandalar/ui/inn_portrait.json | 42 +- .../adventure/Shandalar/ui/tavernevent.png | Bin 0 -> 1181035 bytes forge-gui/res/languages/en-US.properties | 3 + .../forge/gamemodes/limited/BoosterDraft.java | 7 +- 39 files changed, 2807 insertions(+), 172 deletions(-) create mode 100644 forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java create mode 100644 forge-gui-mobile/src/forge/adventure/scene/DraftScene.java create mode 100644 forge-gui-mobile/src/forge/adventure/scene/EventScene.java create mode 100644 forge-gui-mobile/src/forge/adventure/scene/MenuScene.java create mode 100644 forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java create mode 100644 forge-gui/res/adventure/Shandalar/ui/event.json create mode 100644 forge-gui/res/adventure/Shandalar/ui/event_portrait.json create mode 100644 forge-gui/res/adventure/Shandalar/ui/tavernevent.png diff --git a/forge-core/src/main/java/forge/deck/DeckFormat.java b/forge-core/src/main/java/forge/deck/DeckFormat.java index 4d05053adb4..a27f53c074c 100644 --- a/forge-core/src/main/java/forge/deck/DeckFormat.java +++ b/forge-core/src/main/java/forge/deck/DeckFormat.java @@ -106,6 +106,7 @@ public enum DeckFormat { } }, PlanarConquest ( Range.between(40, Integer.MAX_VALUE), Range.is(0), 1), + Adventure ( Range.between(40, Integer.MAX_VALUE), Range.between(0, 15), 4), Vanguard ( Range.between(60, Integer.MAX_VALUE), Range.is(0), 4), Planechase ( Range.between(60, Integer.MAX_VALUE), Range.is(0), 4), Archenemy ( Range.between(60, Integer.MAX_VALUE), Range.is(0), 4), diff --git a/forge-game/src/main/java/forge/game/GameType.java b/forge-game/src/main/java/forge/game/GameType.java index 27cbe965a5b..2e76ddb170d 100644 --- a/forge-game/src/main/java/forge/game/GameType.java +++ b/forge-game/src/main/java/forge/game/GameType.java @@ -26,6 +26,8 @@ public enum GameType { Quest (DeckFormat.QuestDeck, true, true, false, "lblQuest", ""), QuestDraft (DeckFormat.Limited, true, true, true, "lblQuestDraft", ""), PlanarConquest (DeckFormat.PlanarConquest, true, false, false, "lblPlanarConquest", ""), + Adventure (DeckFormat.Adventure, true, false, false, "lblAdventure", ""), + AdventureEvent (DeckFormat.Limited, true, true, true, "lblAdventure", ""), Puzzle (DeckFormat.Puzzle, false, false, false, "lblPuzzle", "lblPuzzleDesc"), Constructed (DeckFormat.Constructed, false, true, true, "lblConstructed", ""), DeckManager (DeckFormat.Constructed, false, true, true, "lblDeckManager", ""), diff --git a/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java b/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java new file mode 100644 index 00000000000..805e5fdb8b5 --- /dev/null +++ b/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java @@ -0,0 +1,526 @@ +package forge.adventure.data; + +import com.badlogic.gdx.scenes.scene2d.ui.Image; +import com.badlogic.gdx.utils.Array; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import forge.Forge; +import forge.adventure.character.EnemySprite; +import forge.adventure.scene.RewardScene; +import forge.adventure.util.AdventureEventController; +import forge.adventure.util.Config; +import forge.adventure.util.Current; +import forge.adventure.util.Reward; +import forge.card.CardEdition; +import forge.deck.Deck; +import forge.game.GameType; +import forge.gamemodes.limited.BoosterDraft; +import forge.gamemodes.limited.LimitedPoolType; +import forge.model.CardBlock; +import forge.model.FModel; +import forge.util.Aggregates; +import forge.util.MyRandom; +import org.apache.commons.lang3.tuple.Pair; + +import java.io.Serializable; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class AdventureEventData implements Serializable { + private static final long serialVersionUID = 1L; + + public transient BoosterDraft draft; + public AdventureEventParticipant[] participants; + public int rounds; + public int currentRound; + public AdventureEventRules eventRules = new AdventureEventRules(); + public AdventureEventReward[] rewards; + + public int eventOrigin; + public String sourceID; + + public long eventSeed; + public AdventureEventController.EventStyle style; + public AdventureEventController.EventStatus eventStatus; + public AdventureEventController.EventFormat format; + private transient final Random random = new Random(); + public Deck registeredDeck; + public Deck draftedDeck; //Copy of registered before basic lands are added for event reward purposes + public boolean isDraftComplete = false; + public String description = ""; + public String[] packConfiguration = new String[0]; + public transient CardBlock cardBlock; + public String cardBlockName; + public boolean playerWon; + public int matchesWon, matchesLost; + private Deck[] rewardPacks; + + public AdventureEventData(AdventureEventData other){ + participants = other.participants.clone(); + rounds = other.rounds; + eventRules = other.eventRules; + rewards = other.rewards.clone(); + eventOrigin = other.eventOrigin; + sourceID = other.sourceID; + eventSeed = other.eventSeed; + style = other.style; + random.setSeed(eventSeed); + eventStatus = other.eventStatus; + registeredDeck = other.registeredDeck; + isDraftComplete = other.isDraftComplete; + description = other.description; + cardBlockName = other.cardBlockName; + packConfiguration = other.packConfiguration; + playerWon = other.playerWon; + matchesWon = other.matchesWon; + matchesLost = other.matchesLost; + } + + + public Deck[] getRewardPacks(int count) { + Deck[] ret = new Deck[count]; + for (int i = 0; i < count; i++){ + ret[i] = AdventureEventController.instance().generateBooster(Aggregates.random(cardBlock.getSets()).getCode()); + } + return ret; + } + + + public AdventureEventData(Long seed){ + setEventSeed(seed); + + random.setSeed(eventSeed); + eventStatus = AdventureEventController.EventStatus.Available; + registeredDeck = new Deck(); + cardBlock = pickWeightedCardBlock(); + if (cardBlock == null) + return; + cardBlockName = cardBlock.getName(); + + //Below all to be fully generated in later release + rewardPacks = getRewardPacks(3); + generateParticipants(7); + format = AdventureEventController.EventFormat.Draft; + if (cardBlock != null){ + packConfiguration = getBoosterConfiguration(cardBlock); + + rewards = new AdventureEventData.AdventureEventReward[4]; + AdventureEventData.AdventureEventReward r0 = new AdventureEventData.AdventureEventReward(); + AdventureEventData.AdventureEventReward r1 = new AdventureEventData.AdventureEventReward(); + AdventureEventData.AdventureEventReward r2 = new AdventureEventData.AdventureEventReward(); + AdventureEventData.AdventureEventReward r3 = new AdventureEventData.AdventureEventReward(); + r0.minWins = 0; + r0.maxWins = 0; + r0.cardRewards = new Deck[]{rewardPacks[0]}; + rewards[0] = r0; + r1.minWins = 1; + r1.maxWins = 3; + r1.cardRewards = new Deck[]{rewardPacks[1], rewardPacks[2]}; + rewards[1] = r1; + r2.minWins = 2; + r2.maxWins = 3; + r2.itemRewards = new String[]{"Challenge Coin"}; + rewards[2] = r2; + + + } + } + + public void setEventSeed(long seed){ + random.setSeed(seed); + } + + public CardBlock getCardBlock() { + if (cardBlock == null){ + cardBlock = FModel.getBlocks().get(cardBlockName); + } + return cardBlock; + } + + public BoosterDraft getDraft(){ + Random placeholder = MyRandom.getRandom(); + MyRandom.setRandom(random); + if (draft == null && (eventStatus == AdventureEventController.EventStatus.Available || eventStatus == AdventureEventController.EventStatus.Entered)){ + draft = BoosterDraft.createDraft(LimitedPoolType.Block, getCardBlock(), packConfiguration); + } + if (packConfiguration == null){ + packConfiguration = getBoosterConfiguration(cardBlock); + } + MyRandom.setRandom(random); + return draft; + } + + private static final Predicate filterPioneer = FModel.getFormats().getPioneer().editionLegalPredicate; + private static final Predicate filterModern= FModel.getFormats().getModern().editionLegalPredicate; + private static final Predicate filterVintage = FModel.getFormats().getVintage().editionLegalPredicate; + private static final Predicate filterStandard = FModel.getFormats().getStandard().editionLegalPredicate; + + public static Predicate selectSetPool() { + final int rollD100 = MyRandom.getRandom().nextInt(100); + + Predicate rolledFilter; + + if (rollD100 < 40) { + rolledFilter = filterStandard; + } else if (rollD100 < 70) { + rolledFilter = filterPioneer; + } else if (rollD100 < 90) { + rolledFilter = filterModern; + } else { + rolledFilter = filterVintage; + } + + return rolledFilter; + } + + + private CardBlock pickWeightedCardBlock() { + FModel.getMagicDb().getEditions(); + Iterable src = FModel.getBlocks(); //all blocks + Predicate filter = Predicates.and(CardEdition.Predicates.CAN_MAKE_BOOSTER, selectSetPool()); + List allEditions = new ArrayList<>(); + StreamSupport.stream(FModel.getMagicDb().getEditions().spliterator(), false).filter(filter::apply).filter(CardEdition::hasBoosterTemplate).collect(Collectors.toList()).iterator().forEachRemaining(allEditions::add); + List legalBlocks = new ArrayList<>(); + + for (CardBlock b : src) { // for each block + boolean isOkay = !b.getSets().isEmpty(); + for (CardEdition c : b.getSets()) { + if (!allEditions.contains(c)) { + isOkay = false; + break; + } + if (!c.hasBoosterTemplate()) { + isOkay = false; + break; + } + else { + final List> slots = c.getBoosterTemplate().getSlots(); + int boosterSize = 0; + for (Pair slot : slots) { + boosterSize += slot.getRight(); + } + isOkay = boosterSize == 15; + } + } + if (isOkay) + legalBlocks.add(b); + } + + for (String restricted : Config.instance().getConfigData().restrictedEditions){ + legalBlocks.removeIf(q -> q.getName().equals(restricted)); + } + return legalBlocks.isEmpty()?null:Aggregates.random(legalBlocks); + } + + /** + * Default filter that only allows actual sets that were printed as 15-card boosters + */ + private static final Predicate DEFAULT_FILTER = new Predicate() { + @Override + public boolean apply(final CardEdition cardEdition) { + boolean isExpansion = cardEdition.getType().equals(CardEdition.Type.EXPANSION); + boolean isCoreSet = cardEdition.getType().equals(CardEdition.Type.CORE); + boolean isReprintSet = cardEdition.getType().equals(CardEdition.Type.REPRINT); + if (isExpansion || isCoreSet || isReprintSet) { + // Only allow sets with 15 cards in booster packs + if (cardEdition.hasBoosterTemplate()) { + final List> slots = cardEdition.getBoosterTemplate().getSlots(); + int boosterSize = 0; + for (Pair slot : slots) { + boosterSize += slot.getRight(); + } + return boosterSize == 15; + } + } + return false; + } + }; + + public String[] getBoosterConfiguration(CardBlock selectedBlock) { + Random placeholder = MyRandom.getRandom(); + MyRandom.setRandom(random); + String[] ret = new String[selectedBlock.getCntBoostersDraft()]; + + for (int i = 0; i < selectedBlock.getCntBoostersDraft(); i++) { + if (i < selectedBlock.getNumberSets()) + ret[i] = selectedBlock.getSets().get(i).getCode(); + else + ret[i] = Aggregates.random(selectedBlock.getSets()).getCode(); + } + MyRandom.setRandom(placeholder); + return ret; + } + + public void startEvent(){ + if (eventStatus == AdventureEventController.EventStatus.Ready){ + currentRound = 1; + eventStatus = AdventureEventController.EventStatus.Started; + } + } + + public void generateParticipants(int numberOfOpponents){ + participants = new AdventureEventParticipant[numberOfOpponents+1]; + List data = Aggregates.random(WorldData.getAllEnemies(),numberOfOpponents); + for (int i = 0; i < numberOfOpponents; i++){ + participants[i] = new AdventureEventParticipant().generate(data.get(i)); + } + + participants[numberOfOpponents] = getHumanPlayer(); + } + + private transient AdventureEventHuman humanPlayerInstance; + public AdventureEventHuman getHumanPlayer(){ + if (humanPlayerInstance == null){ + for (AdventureEventParticipant p: participants){ + if (p instanceof AdventureEventHuman) { + humanPlayerInstance = (AdventureEventHuman) p; + break; + } + } + humanPlayerInstance = new AdventureEventHuman(); + } + return humanPlayerInstance; + } + + public AdventureEventParticipant nextOpponent = null; + + public List getMatches(int round){ + if (matches.containsKey(round)){ + return matches.get(round); + } + + List activePlayers = new ArrayList<>(); + if (style == AdventureEventController.EventStyle.Bracket){ + if (round == 1){ + activePlayers = Arrays.stream(participants).collect(Collectors.toList()); + } + else{ + if (matches.get(round-1) ==null){ + return null; + } + for (int i = 0; i < matches.get(round-1).size(); i++){ + AdventureEventParticipant w = matches.get(round-1).get(i).winner; + if (w == null) + return null; + else + activePlayers.add(w); + } + } + matches.put(round, new ArrayList<>()); + while (!activePlayers.isEmpty()){ + AdventureEventMatch match = new AdventureEventMatch(); + match.p1 = activePlayers.remove(MyRandom.getRandom().nextInt(activePlayers.size())); + if (!activePlayers.isEmpty()) { + match.p2 = activePlayers.remove(MyRandom.getRandom().nextInt(activePlayers.size())); + } + matches.get(round).add(match); + } + } + return matches.get(currentRound); + } + + public Map> matches = new HashMap<>(); + + public void giveRewards(){ + int wins = getHumanPlayer().wins; + Array ret = new Array<>(); + + //Todo: this should be automatic... "somehow" + rewards[3] = new AdventureEventReward(); + rewards[3].minWins = 3; + rewards[3].maxWins = 3; + draftedDeck.setName("Drafted Deck"); + draftedDeck.setComment("Prize for placing 1st overall in draft event"); + rewards[3].cardRewards = new Deck[]{draftedDeck}; + //end todo + + + for (AdventureEventReward r : rewards){ + if (r.minWins > wins || r.maxWins < wins) { + continue; + } + for (Deck pack : r.cardRewards) { + RewardData data = new RewardData(); + data.type = "cardPack"; + data.count = 1; + data.cardPack = pack; + ret.addAll(data.generate(false, true)); + } + for (String item : (r.itemRewards)) { + RewardData data = new RewardData(); + data.type = "item"; + data.count = 1; + data.itemName = item; + ret.addAll(data.generate(false, true)); + } + + } + if (ret.size > 0){ + RewardScene.instance().loadRewards(ret, RewardScene.Type.Loot, null); + Forge.switchScene(RewardScene.instance()); + } + + //todo: more robust logic for event types that can be won without perfect record (Swiss w/cut, round robin) + if (matchesLost == 0 || matchesWon == rounds){ + playerWon = true; + } + else{ + playerWon = false; + } + + eventStatus = AdventureEventController.EventStatus.Awarded; + } + + public String getPairingDescription(){ + switch (eventRules.pairingStyle){ + case Swiss: + return "swiss"; + case SwissWithCut: + return "swiss (with cut)"; + case RoundRobin: + return "round robin"; + case SingleElimination: + return "single elimination"; + case DoubleElimination: + return "double elimination"; + } + return ""; + }; + + public String getDescription(){ + description = "Event Type: Booster Draft\n"; + description += "Block: " + cardBlock + "\n"; + description += "Boosters: " + String.join(", ", packConfiguration) + "\n"; + description += "Competition Style: " + participants.length + " players, matches played as best of " + eventRules.gamesPerMatch + ", " + (getPairingDescription()) + "\n\n"; + description += String.format("Entry Fee (incl. reputation)\nGold %d[][+Gold][BLACK]\nMana Shards %d[][+Shards][BLACK]\n\n",eventRules.goldToEnter,eventRules.shardsToEnter); + description += String.format("Prizes\nChampion: Keep drafted deck\n2+ round wins: Challenge Coin \n1+ round wins: %s Booster, %s Booster\n0 round wins: %s Booster", rewardPacks[0].getComment(),rewardPacks[1].getComment(),rewardPacks[2].getComment()); + return description; + } + + + public static class AdventureEventParticipant implements Serializable, Comparable { + private static final long serialVersionUID = 1L; + private transient EnemySprite sprite; + String enemyDataName; + Deck registeredDeck; + + public int wins; + public int losses; + + public AdventureEventParticipant generate(EnemyData data){ + AdventureEventParticipant ret = new AdventureEventParticipant(); + ret.enemyDataName = data.getName(); + ret.sprite = new EnemySprite(data); + + return ret; + } + + public String getRecord(){ + return String.format("%d-%d", wins, losses); + } + + + private AdventureEventParticipant(){ + + } + + public Deck getDeck(){ + return registeredDeck; + } + + public String getName(){ + return enemyDataName; + } + + public Image getAvatar(){ + if (sprite == null){ + sprite = new EnemySprite(WorldData.getEnemy(enemyDataName)); + } + + return sprite == null?new Image():new Image(sprite.getAvatar()); + } + + public String getAtlasPath(){ + return sprite == null?"":sprite.getAtlasPath(); + } + + public EnemySprite getSprite(){ + if (sprite == null){ + sprite = new EnemySprite(WorldData.getEnemy(enemyDataName)); + } + return sprite; + } + + @Override public int compareTo(AdventureEventParticipant other) + { + if (this.wins != other.wins) + return other.wins - this.wins; + else + return this.losses - other.losses; + } + + public void setDeck(Deck deck) { + registeredDeck = deck; + } + } + + public static class AdventureEventHuman extends AdventureEventParticipant { + + @Override + public Deck getDeck(){ + return registeredDeck == null?Current.player().getSelectedDeck():registeredDeck; + } + + @Override + public String getName(){ + return Current.player().getName(); + } + + @Override + public Image getAvatar(){ + return new Image(Current.player().avatar()); + } + + } + + public static class AdventureEventRules implements Serializable { + private static final long SerialVersionUID = 1L; + + public int goldToEnter; + public int shardsToEnter; + public boolean acceptsChallengeCoin; + public GameType gameType = GameType.AdventureEvent; + public int startingLife = 20; + public boolean allowsShards = false; + public boolean allowsItems = false; + public boolean allowsBlessings = false; + public boolean allowsAddBasicLands = true; + public int gamesPerMatch = 3; + + public PairingStyle pairingStyle = PairingStyle.SingleElimination; + } + + public static class AdventureEventMatch implements Serializable { + private static final long SerialVersionUID = 1L; + public AdventureEventParticipant p1; + public AdventureEventParticipant p2; + public AdventureEventParticipant winner; + public int round; + } + + public static class AdventureEventReward implements Serializable{ + public int minWins = -1; + public int maxWins = -1; + public Deck[] cardRewards = new Deck[0]; + public String[] itemRewards = new String[0]; + } + + enum PairingStyle { + SingleElimination, + DoubleElimination, + Swiss, + SwissWithCut, + RoundRobin + } +} diff --git a/forge-gui-mobile/src/forge/adventure/data/AdventureQuestData.java b/forge-gui-mobile/src/forge/adventure/data/AdventureQuestData.java index 8b308098150..c732894ce97 100644 --- a/forge-gui-mobile/src/forge/adventure/data/AdventureQuestData.java +++ b/forge-gui-mobile/src/forge/adventure/data/AdventureQuestData.java @@ -411,6 +411,17 @@ public class AdventureQuestData implements Serializable { updateStatus(); } + public void updateEventComplete(AdventureEventData completedEvent) { + for (AdventureQuestStage stage: stages) { + if(failed) + break; + stage.updateEventComplete(completedEvent); + failed = failed || stage.getStatus() == AdventureQuestController.QuestStatus.Failed; + } + if (!failed) + updateStatus(); + } + public void updateStatus(){ for (AdventureQuestStage stage: stages) { switch (stage.getStatus()) { diff --git a/forge-gui-mobile/src/forge/adventure/data/AdventureQuestStage.java b/forge-gui-mobile/src/forge/adventure/data/AdventureQuestStage.java index 3cd904f40db..7b20282e9c1 100644 --- a/forge-gui-mobile/src/forge/adventure/data/AdventureQuestStage.java +++ b/forge-gui-mobile/src/forge/adventure/data/AdventureQuestStage.java @@ -112,7 +112,7 @@ public class AdventureQuestStage implements Serializable { count2 = (count2 * candidates.size()) / 100; int targetIndex = Math.max(0, (int) (count1 - count2 + (new Random().nextFloat() * count2 * 2))); - if (targetIndex < candidates.size() && targetIndex > 0 && count1 > 0) { + if (targetIndex < candidates.size() && targetIndex >= 0) { candidates.sort(new AdventureQuestController.DistanceSort()); setTargetPOI(candidates.get(targetIndex)); } else { @@ -297,6 +297,49 @@ public class AdventureQuestStage implements Serializable { } } + public void updateEventComplete(AdventureEventData completedEvent) { + if (this.objective == AdventureQuestController.ObjectiveTypes.EventFinish) { + if (inTargetLocation) { + if (++progress1 >= count1) { + status = AdventureQuestController.QuestStatus.Complete; + } + } + } + if (this.objective == AdventureQuestController.ObjectiveTypes.EventWinMatches) { + if (inTargetLocation) { + progress1 += completedEvent.matchesWon; + progress2 += completedEvent.matchesLost; + + + if (status == AdventureQuestController.QuestStatus.Active && ++progress2 >= count2 && count2 > 0) { + status = AdventureQuestController.QuestStatus.Failed; + } + else if (++progress1 >= count1) { + status = AdventureQuestController.QuestStatus.Complete; + } + } + } + if (this.objective == AdventureQuestController.ObjectiveTypes.EventWin) { + if (inTargetLocation) { + + if (completedEvent.playerWon){ + progress1++; + } + else{ + progress2++; + } + + + if (status == AdventureQuestController.QuestStatus.Active && ++progress2 >= count2 && count2 > 0) { + status = AdventureQuestController.QuestStatus.Failed; + } + else if (++progress1 >= count1) { + status = AdventureQuestController.QuestStatus.Complete; + } + } + } + } + public AdventureQuestStage() { } @@ -336,4 +379,6 @@ public class AdventureQuestStage implements Serializable { // if (this.stageID == null) // this.stageID = other.stageID; } + + } diff --git a/forge-gui-mobile/src/forge/adventure/data/DialogData.java b/forge-gui-mobile/src/forge/adventure/data/DialogData.java index 9648f7a11fe..65ace856b19 100644 --- a/forge-gui-mobile/src/forge/adventure/data/DialogData.java +++ b/forge-gui-mobile/src/forge/adventure/data/DialogData.java @@ -1,5 +1,7 @@ package forge.adventure.data; +import forge.util.Callback; + import java.io.Serializable; import java.util.ArrayList; import java.util.List; @@ -9,6 +11,8 @@ import java.util.List; * Carries all text, branches and effects of dialogs. */ public class DialogData implements Serializable { + //private static final long SerialVersionUID = 1; // TODO: set to current value + public ActionData[] action = new ActionData[0]; //List of effects to cause when the dialog shows. public ConditionData[] condition = new ConditionData[0]; //List of conditions for the action to show. public String name = ""; //Text to display when action is listed as a button. @@ -17,6 +21,8 @@ public class DialogData implements Serializable { public String loctext= ""; //References a localized string for the text body. public DialogData[] options = new DialogData[0]; //List of sub-dialogs. Show up as options in the current one. + public transient Callback callback; + public DialogData(){} public DialogData(DialogData other){ if (other == null) @@ -53,6 +59,7 @@ public class DialogData implements Serializable { public String addItem; //Add item name to inventory. public int addLife = 0; //Gives the player X health. Negative to take. public int addGold = 0; //Gives the player X gold. Negative to take. + public int addShards = 0; //Gives the player X shards. Negative to take. public int deleteMapObject = 0; //Remove ID from the map. -1 for self. public int activateMapObject = 0; //Remove inactive state from ID. @@ -78,6 +85,7 @@ public class DialogData implements Serializable { addItem = other.removeItem; addLife = other.addLife; addGold = other.addGold; + addShards = other.addShards; deleteMapObject = other.deleteMapObject; activateMapObject = other.activateMapObject; battleWithActorID = other.battleWithActorID; @@ -103,7 +111,8 @@ public class DialogData implements Serializable { } } - static public class ConditionData { + static public class ConditionData implements Serializable { + private static final long SerialVersionUID = 1L; // TODO: set to current value? static public class QueryQuestFlag{ public String key; public String op; @@ -113,6 +122,7 @@ public class DialogData implements Serializable { public int actorID = 0; //Check for an actor ID. public String hasBlessing = null; //Check for specific blessing, if named. public int hasGold = 0; //Check for player gold. True if gold is equal or higher than X. + public int hasShards = 0; //Check player's mana shards. True if equal or higher than X. public int hasMapReputation = Integer.MIN_VALUE; //Check for player reputation in this POI. True if reputation is equal or higher than X. public int hasLife = 0; //Check for player life. True if life is equal or higher than X. public String colorIdentity = null; //Check for player's current color identity. diff --git a/forge-gui-mobile/src/forge/adventure/data/RewardData.java b/forge-gui-mobile/src/forge/adventure/data/RewardData.java index a334be1dfc7..57c5f24a443 100644 --- a/forge-gui-mobile/src/forge/adventure/data/RewardData.java +++ b/forge-gui-mobile/src/forge/adventure/data/RewardData.java @@ -9,6 +9,7 @@ import forge.adventure.util.Config; import forge.adventure.util.Current; import forge.adventure.util.Reward; import forge.adventure.world.WorldSave; +import forge.deck.Deck; import forge.item.PaperCard; import forge.model.FModel; @@ -45,6 +46,7 @@ public class RewardData implements Serializable { public RewardData[] cardUnion; public String[] deckNeeds; public RewardData[] rotation; + public Deck cardPack; public RewardData() { } @@ -74,6 +76,7 @@ public class RewardData implements Serializable { cardUnion =rewardData.cardUnion==null?null:rewardData.cardUnion.clone(); rotation =rewardData.rotation==null?null:rewardData.rotation.clone(); deckNeeds =rewardData.deckNeeds==null?null:rewardData.deckNeeds.clone(); + cardPack = rewardData.cardPack; } private static Iterable allCards; @@ -174,6 +177,12 @@ public class RewardData implements Serializable { } } break; + case "cardPack": + if(cardPack!=null) + { + ret.add(new Reward(cardPack)); + } + break; case "deckCard": if(cards == null) return ret; for(PaperCard card: CardUtil.generateCards(cards,this, count + addedCount + Current.player().bonusDeckCards() ,rewardRandom)) { diff --git a/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java b/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java index b78a6271577..43f885277a6 100644 --- a/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java +++ b/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java @@ -53,8 +53,10 @@ public class AdventurePlayer implements Serializable, SaveFileContent { private final Map questFlags = new HashMap<>(); private final Array inventoryItems = new Array<>(); + private final Array boostersOwned = new Array<>(); private final HashMap equippedItems = new HashMap<>(); private final List quests = new ArrayList<>(); + private final List events = new ArrayList<>(); // Fantasy/Chaos mode settings. private boolean fantasyMode = false; @@ -97,12 +99,16 @@ public class AdventurePlayer implements Serializable, SaveFileContent { shards = 0; clearDecks(); inventoryItems.clear(); + boostersOwned.clear(); equippedItems.clear(); questFlags.clear(); quests.clear(); + events.clear(); cards.clear(); statistic.clear(); newCards.clear(); + AdventureEventController.clear(); + AdventureQuestController.clear(); } @@ -184,6 +190,10 @@ public class AdventurePlayer implements Serializable, SaveFileContent { return inventoryItems; } + public Array getBoostersOwned(){ + return boostersOwned; + } + public Deck getDeck(int index) { return decks[index]; } @@ -322,6 +332,18 @@ public class AdventurePlayer implements Serializable, SaveFileContent { } } } + if (data.containsKey("boosters")) { + Deck[] decks = (Deck[]) data.readObject("boosters"); + for (Deck d : decks){ + if (d != null && !d.isEmpty()){ + boostersOwned.add(d); + } + else{ + System.err.printf("Null or empty booster %s\n", d); + System.out.println("You have an empty booster pack in your inventory."); + } + } + } deck = new Deck(data.readString("deckName")); deck.getMain().addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("deckCards")))); @@ -344,6 +366,15 @@ public class AdventurePlayer implements Serializable, SaveFileContent { quests.add((AdventureQuestData) itsReallyAQuest); } } + if (data.containsKey("events")) { + events.clear(); + Object[] q = (Object[]) data.readObject("events"); + if (q != null) { + for (Object itsReallyAnEvent : q){ + events.add((AdventureEventData) itsReallyAnEvent); + } + } + } for (int i = 0; i < NUMBER_OF_DECKS; i++) { if (!data.containsKey("deck_name_" + i)) { @@ -413,6 +444,8 @@ public class AdventurePlayer implements Serializable, SaveFileContent { data.storeObject("equippedSlots", slots.toArray(new String[0])); data.storeObject("equippedItems", items.toArray(new String[0])); + data.storeObject("boosters", boostersOwned.toArray(Deck.class)); + data.storeObject("blessing", blessing); //Save quest flags. @@ -425,6 +458,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent { data.storeObject("questFlagsKey", questFlagsKey.toArray(new String[0])); data.storeObject("questFlagsValue", questFlagsValue.toArray(new Byte[0])); data.storeObject("quests", quests.toArray()); + data.storeObject("events", events.toArray()); data.storeObject("deckCards", deck.getMain().toCardList("\n").split("\n")); if (deck.get(DeckSection.Sideboard) != null) @@ -471,6 +505,11 @@ public class AdventurePlayer implements Serializable, SaveFileContent { if (reward.getItem() != null) inventoryItems.add(reward.getItem().name); break; + case CardPack: + if (reward.getDeck() != null) { + boostersOwned.add(reward.getDeck()); + } + break; case Life: addMaxLife(reward.getCount()); break; @@ -770,6 +809,17 @@ public class AdventurePlayer implements Serializable, SaveFileContent { return true; } + public boolean addBooster(Deck booster) { + if (booster == null || booster.isEmpty()) + return false; + boostersOwned.add(booster); + return true; + } + + public void removeBooster(Deck booster) { + boostersOwned.removeValue(booster, true); + } + // Quest functions. public void setQuestFlag(String key, int value) { @@ -826,6 +876,14 @@ public class AdventurePlayer implements Serializable, SaveFileContent { return quests; } + public void addEvent(AdventureEventData e) { + events.add(e); + } + + public List getEvents() { + return events; + } + public int getEnemyDeckNumber(String enemyName, int maxDecks) { int deckNumber = 0; if (statistic.getWinLossRecord().get(enemyName) != null) { @@ -873,4 +931,8 @@ public class AdventurePlayer implements Serializable, SaveFileContent { public boolean isEmptyDeck(int deckIndex) { return decks[deckIndex].isEmpty() && decks[deckIndex].getName().equals(Forge.getLocalizer().getMessage("lblEmptyDeck")); } + + public void removeEvent(AdventureEventData completedEvent) { + events.remove(completedEvent); + } } diff --git a/forge-gui-mobile/src/forge/adventure/player/PlayerStatistic.java b/forge-gui-mobile/src/forge/adventure/player/PlayerStatistic.java index 835f893ebac..d71e36ce7c2 100644 --- a/forge-gui-mobile/src/forge/adventure/player/PlayerStatistic.java +++ b/forge-gui-mobile/src/forge/adventure/player/PlayerStatistic.java @@ -1,15 +1,20 @@ package forge.adventure.player; +import forge.adventure.data.AdventureEventData; +import forge.adventure.util.AdventureQuestController; import forge.adventure.util.SaveFileContent; import forge.adventure.util.SaveFileData; import org.apache.commons.lang3.tuple.Pair; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class PlayerStatistic implements SaveFileContent { HashMap> winLossRecord=new HashMap<>(); + List completedEvents = new ArrayList<>(); int secondPlayed=0; public HashMap> getWinLossRecord() @@ -43,6 +48,56 @@ public class PlayerStatistic implements SaveFileContent { return (float) totalWins()/(float)totalLoss(); } + public int eventWins(){ + int win = 0; + for (AdventureEventData event : completedEvents){ + if (event.playerWon) + win++; + } + return win; + } + public int eventLosses(){ + int loss = 0; + for (AdventureEventData event : completedEvents){ + if (!event.playerWon) + loss++; + } + return loss; + } + public float eventWinLossRatio() + { + if (eventLosses() == 0) { + // Not a true ratio but fixes division by zero + return eventWins(); + } + + return (float) eventWins()/(float)eventLosses(); + } + + public int eventMatchWins(){ + int win = 0; + for (AdventureEventData event : completedEvents){ + win+= event.matchesWon; + } + return win; + } + public int eventMatchLosses(){ + int loss = 0; + for (AdventureEventData event : completedEvents){ + loss += event.matchesLost; + } + return loss; + } + public float eventMatchWinLossRatio() + { + if (eventMatchLosses() == 0) { + // Not a true ratio but fixes division by zero + return eventMatchWins(); + } + + return (float) eventMatchWins()/(float)eventMatchLosses(); + } + public int getPlayTime() { return secondPlayed; @@ -54,6 +109,11 @@ public class PlayerStatistic implements SaveFileContent { winLossRecord = (HashMap>) data.readObject("winLossRecord"); else winLossRecord.clear(); + + if (data!=null&&data.containsKey("completedEvents")) + completedEvents = (ArrayList) data.readObject("completedEvents"); + else + completedEvents.clear(); } public void setResult(String enemy,boolean win) @@ -81,6 +141,7 @@ public class PlayerStatistic implements SaveFileContent { SaveFileData data=new SaveFileData(); data.storeObject("winLossRecord",winLossRecord); + data.storeObject("completedEvents", completedEvents); return data; } @@ -88,4 +149,9 @@ public class PlayerStatistic implements SaveFileContent { public void clear() { winLossRecord.clear(); } + + public void setResult(AdventureEventData completedEvent) { + completedEvents.add(completedEvent); + AdventureQuestController.instance().updateEventComplete(completedEvent); + } } diff --git a/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java b/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java index 8fa2fd23b88..7cd356fb53c 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java +++ b/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java @@ -7,12 +7,16 @@ import com.badlogic.gdx.utils.Align; import com.google.common.base.Function; import forge.Forge; import forge.Graphics; +import forge.adventure.data.AdventureEventData; import forge.adventure.player.AdventurePlayer; +import forge.adventure.util.AdventureEventController; import forge.adventure.util.Config; import forge.assets.FImage; import forge.assets.FSkinFont; import forge.assets.FSkinImage; +import forge.card.CardEdition; import forge.deck.*; +import forge.gamemodes.limited.BoosterDraft; import forge.item.InventoryItem; import forge.item.PaperCard; import forge.itemmanager.*; @@ -25,19 +29,101 @@ import forge.menu.FPopupMenu; import forge.model.FModel; import forge.screens.FScreen; import forge.screens.TabPageScreen; -import forge.toolbox.FContainer; -import forge.toolbox.FEvent; -import forge.toolbox.FLabel; -import forge.toolbox.GuiChoose; +import forge.toolbox.*; import forge.util.Callback; import forge.util.ItemPool; import forge.util.Utils; import org.apache.commons.lang3.StringUtils; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; - - public class AdventureDeckEditor extends TabPageScreen { +import java.util.Set; + +public class AdventureDeckEditor extends TabPageScreen { + + private static class DraftPackPage extends CatalogPage { + protected DraftPackPage() { + super(ItemManagerConfig.DRAFT_PACK, Forge.getLocalizer().getInstance().getMessage("lblPackN", String.valueOf(1)), FSkinImage.PACK); + } + + @Override + public void refresh() { + BoosterDraft draft = parentScreen.getDraft(); + if (draft == null || !draft.hasNextChoice()) { return; } + + CardPool pool = draft.nextChoice(); + + if (pool == null || pool.isEmpty()) { return; } + + int packNumber = draft.getCurrentBoosterIndex() + 1; + caption = Forge.getLocalizer().getMessage("lblPackN", String.valueOf(packNumber)); + cardManager.setPool(pool); + cardManager.setShowRanking(true); + } + + @Override + protected void onCardActivated(PaperCard card) { + super.onCardActivated(card); + afterCardPicked(card); + } + + private void afterCardPicked(PaperCard card) { + BoosterDraft draft = parentScreen.getDraft(); + draft.setChoice(card); + + if (draft.hasNextChoice()) { + refresh(); + } + else { + hideTab(); //hide this tab page when finished drafting + parentScreen.completeDraft(); + } + } + + @Override + protected void buildMenu(final FDropDownMenu menu, final PaperCard card) { + addItem(menu, Forge.getLocalizer().getMessage("lblAdd"), Forge.getLocalizer().getMessage("lblToMainDeck"), getMainDeckPage().getIcon(), true, true, new Callback() { + @Override + public void run(Integer result) { //ignore quantity + mainDeckPage.addCard(card); + + afterCardPicked(card); + } + }); + addItem(menu, Forge.getLocalizer().getMessage("lblAdd"), Forge.getLocalizer().getMessage("lbltosideboard"), getSideboardPage().getIcon(), true, true, new Callback() { + @Override + public void run(Integer result) { //ignore quantity + getSideboardPage().addCard(card); + afterCardPicked(card); + } + }); + } + } + + public BoosterDraft getDraft(){ + return currentEvent.getDraft(); + } + + private AdventureEventData currentEvent; + + public void completeDraft(){ + currentEvent.isDraftComplete = true; + Deck[] opponentDecks = currentEvent.getDraft().getDecks(); + for (int i = 0; i < currentEvent.participants.length && i < opponentDecks.length; i++) { + currentEvent.participants[i].setDeck(opponentDecks[i]); + } + currentEvent.draftedDeck = (Deck)currentEvent.registeredDeck.copyTo("Draft Deck"); + if (allowsAddBasic()){ + launchBasicLandDialog(); + //Might be annoying if you haven't pruned your deck yet, but best to remind player that + //this probably needs to be done since it's there since it's not normally part of Adventure + } + if (currentEvent.eventStatus == AdventureEventController.EventStatus.Entered) { + currentEvent.eventStatus = AdventureEventController.EventStatus.Ready; + } + } + private static final FileHandle deckIcon = Config.instance().getFile("ui/maindeck.png"); private static FImage MAIN_DECK_ICON = deckIcon.exists() ? new FImage() { @Override @@ -105,9 +191,24 @@ import java.util.Map; private static ItemPool decksUsingMyCards=new ItemPool<>(InventoryItem.class); private int selected = 0; public static void leave() { - AdventurePlayer.current().getNewCards().clear(); - Forge.clearCurrentScreen(); - Forge.switchToLast(); + if(EventScene.currentEvent != null && EventScene.currentEvent.getDraft() != null && !EventScene.currentEvent.isDraftComplete){ + FOptionPane.showConfirmDialog(Forge.getLocalizer().getMessage("lblEndAdventureEventConfirm"), Forge.getLocalizer().getMessage("lblLeaveDraft"), Forge.getLocalizer().getMessage("lblLeave"), Forge.getLocalizer().getMessage("lblCancel"), false, new Callback() { + @Override + public void run(Boolean result) { + if (result) { + EventScene.currentEvent.eventStatus = AdventureEventController.EventStatus.Abandoned; + AdventurePlayer.current().getNewCards().clear(); + Forge.clearCurrentScreen(); + Forge.switchToLast(); + } + } + }); + } + else{ + AdventurePlayer.current().getNewCards().clear(); + Forge.clearCurrentScreen(); + Forge.switchToLast(); + } } @Override @@ -130,27 +231,55 @@ import java.util.Map; } } lblGold.setText(String.valueOf(AdventurePlayer.current().getGold())); + +// if (currentEvent.registeredDeck!=null && !currentEvent.registeredDeck.isEmpty()){ +// //Use this deck instead of selected deck +// } } public void refresh() { for(TabPage page:tabPages) { if(page instanceof CardManagerPage) - ((CardManagerPage)page).refresh(); + ((CardManagerPage)page).initialize(); } for (TabPage tabPage : tabPages) { ((DeckEditorPage)tabPage).initialize(); } } - private static DeckEditorPage[] getPages() { - return new DeckEditorPage[] { - new CatalogPage(ItemManagerConfig.QUEST_EDITOR_POOL, Forge.getLocalizer().getMessage("lblInventory"), CATALOG_ICON), - new DeckSectionPage(DeckSection.Main, ItemManagerConfig.QUEST_DECK_EDITOR), - new DeckSectionPage(DeckSection.Sideboard, ItemManagerConfig.QUEST_DECK_EDITOR) - }; + private static DeckEditorPage[] getPages(AdventureEventData event) { + if (event == null){ + return new DeckEditorPage[]{ + new CatalogPage(ItemManagerConfig.QUEST_EDITOR_POOL, Forge.getLocalizer().getMessage("lblInventory"), CATALOG_ICON), + new DeckSectionPage(DeckSection.Main, ItemManagerConfig.QUEST_DECK_EDITOR), + new DeckSectionPage(DeckSection.Sideboard, ItemManagerConfig.QUEST_DECK_EDITOR)}; + } + switch (event.eventStatus) { + case Available: + return null; + case Started: + case Completed: + case Abandoned: + case Ready: + return new DeckEditorPage[]{ + new DeckSectionPage(DeckSection.Main, ItemManagerConfig.DRAFT_POOL), + new DeckSectionPage(DeckSection.Sideboard, ItemManagerConfig.SIDEBOARD)}; + case Entered: + return new DeckEditorPage[]{ + event.getDraft() != null ? (new DraftPackPage()) : + new CatalogPage(ItemManagerConfig.DRAFT_PACK, Forge.getLocalizer().getMessage("lblInventory"), CATALOG_ICON), + new DeckSectionPage(DeckSection.Main, ItemManagerConfig.DRAFT_POOL), + new DeckSectionPage(DeckSection.Sideboard, ItemManagerConfig.SIDEBOARD)}; + default: + return new DeckEditorPage[]{ + new CatalogPage(ItemManagerConfig.QUEST_EDITOR_POOL, Forge.getLocalizer().getMessage("lblInventory"), CATALOG_ICON), + new DeckSectionPage(DeckSection.Main, ItemManagerConfig.QUEST_DECK_EDITOR), + new DeckSectionPage(DeckSection.Sideboard, ItemManagerConfig.SIDEBOARD)}; + + } } private CatalogPage catalogPage; - private DeckSectionPage mainDeckPage; - private DeckSectionPage sideboardPage; + private static DeckSectionPage mainDeckPage; + private static DeckSectionPage sideboardPage; private DeckSectionPage commanderPage; protected final DeckHeader deckHeader = add(new DeckHeader()); @@ -159,10 +288,10 @@ import java.util.Map; boolean isShop; - public AdventureDeckEditor(boolean createAsShop) { - super(e -> leave(),getPages()); - + public AdventureDeckEditor(boolean createAsShop, AdventureEventData event) { + super(e -> leave(), getPages(event)); isShop=createAsShop; + currentEvent = event; //cache specific pages for (TabPage tabPage : tabPages) { @@ -196,6 +325,14 @@ import java.util.Map; @Override protected void buildMenu() { addItem(new FMenuItem(Forge.getLocalizer().getMessage("btnCopyToClipboard"), Forge.hdbuttons ? FSkinImage.HDEXPORT : FSkinImage.BLANK, e1 -> FDeckViewer.copyDeckToClipboard(getDeck()))); + FMenuItem addBasic = new FMenuItem(Forge.getLocalizer().getMessage("lblAddBasicLands"), FSkinImage.LANDLOGO, new FEvent.FEventHandler() { + @Override + public void handleEvent(FEvent e) { + launchBasicLandDialog(); + } + }); + addBasic.setEnabled(allowsAddBasic()); + addItem(addBasic); ((DeckEditorPage)getSelectedPage()).buildDeckMenu(this); } }; @@ -203,6 +340,34 @@ import java.util.Map; } }); } + + protected void launchBasicLandDialog(){ + CardEdition defaultLandSet; + //suggest a random set from the ones used in the limited card pool that have all basic lands + Set availableEditionCodes = new HashSet<>(); + for (PaperCard p : currentEvent.registeredDeck.getAllCardsInASinglePool().toFlatList()) { + availableEditionCodes.add(FModel.getMagicDb().getEditions().get(p.getEdition())); + } + defaultLandSet = CardEdition.Predicates.getRandomSetWithAllBasicLands(availableEditionCodes); + + AddBasicLandsDialog dialog = new AddBasicLandsDialog(currentEvent.registeredDeck, defaultLandSet, new Callback() { + @Override + public void run(CardPool landsToAdd) { + getMainDeckPage().addCards(landsToAdd); + } + }); + dialog.show(); + setSelectedPage(getMainDeckPage()); //select main deck page if needed so main deck is visible below dialog + } + + protected boolean allowsAddBasic() { + if (currentEvent == null || !currentEvent.eventRules.allowsAddBasicLands) + return false; + if (currentEvent.eventStatus == AdventureEventController.EventStatus.Entered && currentEvent.isDraftComplete) + return true; + else return currentEvent.eventStatus == AdventureEventController.EventStatus.Ready; + } + @Override protected void doLayout(float startY, float width, float height) { if (deckHeader.isVisible()) { @@ -212,17 +377,17 @@ import java.util.Map; super.doLayout(startY, width, height); } public Deck getDeck() { - return AdventurePlayer.current().getSelectedDeck(); + return (currentEvent != null && currentEvent.registeredDeck != null)?currentEvent.registeredDeck:AdventurePlayer.current().getSelectedDeck(); } protected CatalogPage getCatalogPage() { return catalogPage; } - protected DeckSectionPage getMainDeckPage() { + protected static DeckSectionPage getMainDeckPage() { return mainDeckPage; } - protected DeckSectionPage getSideboardPage() { + protected static DeckSectionPage getSideboardPage() { return sideboardPage; } @@ -233,6 +398,15 @@ import java.util.Map; @Override public void onClose(final Callback canCloseCallback) { + if (currentEvent.getDraft() != null) { + if (currentEvent.isDraftComplete || canCloseCallback == null) { + super.onClose(canCloseCallback); //can skip prompt if draft saved + return; + } + + + FOptionPane.showConfirmDialog(Forge.getLocalizer().getMessage("lblEndAdventureEventConfirm"), Forge.getLocalizer().getMessage("lblLeaveDraft"), Forge.getLocalizer().getMessage("lblLeave"), Forge.getLocalizer().getMessage("lblCancel"), false, canCloseCallback); + } } @Override @@ -794,9 +968,12 @@ import java.util.Map; case Planes: case Schemes: removeCard(card); - if (parentScreen.getCatalogPage() != null) { + if (parentScreen.getCatalogPage() != null && parentScreen.currentEvent == null || parentScreen.currentEvent.getDraft() == null) { parentScreen.getCatalogPage().addCard(card); } + else if (parentScreen.getSideboardPage() != null) { + parentScreen.getSideboardPage().addCard(card); + } break; case Sideboard: removeCard(card); @@ -821,17 +998,21 @@ import java.util.Map; addCard(card, result); } }); - addItem(menu, Forge.getLocalizer().getMessage("lblRemove"), null, Forge.hdbuttons ? FSkinImage.HDMINUS : FSkinImage.MINUS, false, false, new Callback() { - @Override - public void run(Integer result) { - if (result == null || result <= 0) { return; } + if (parentScreen.currentEvent == null || parentScreen.currentEvent.getDraft() == null) { + addItem(menu, Forge.getLocalizer().getMessage("lblRemove"), null, Forge.hdbuttons ? FSkinImage.HDMINUS : FSkinImage.MINUS, false, false, new Callback() { + @Override + public void run(Integer result) { + if (result == null || result <= 0) { + return; + } - removeCard(card, result); - if (parentScreen.getCatalogPage() != null) { - parentScreen.getCatalogPage().addCard(card, result); + removeCard(card, result); + if (parentScreen.getCatalogPage() != null) { + parentScreen.getCatalogPage().addCard(card, result); + } } - } - }); + }); + } if (parentScreen.getSideboardPage() != null) { addItem(menu, Forge.getLocalizer().getMessage("lblMove"), Forge.getLocalizer().getMessage("lbltosideboard"), parentScreen.getSideboardPage().getIcon(), false, false, new Callback() { @Override @@ -855,17 +1036,21 @@ import java.util.Map; addCard(card, result); } }); - addItem(menu, Forge.getLocalizer().getMessage("lblRemove"), null, Forge.hdbuttons ? FSkinImage.HDMINUS : FSkinImage.MINUS, false, false, new Callback() { - @Override - public void run(Integer result) { - if (result == null || result <= 0) { return; } + if (parentScreen.currentEvent == null || parentScreen.currentEvent.getDraft() == null) { + addItem(menu, Forge.getLocalizer().getMessage("lblRemove"), null, Forge.hdbuttons ? FSkinImage.HDMINUS : FSkinImage.MINUS, false, false, new Callback() { + @Override + public void run(Integer result) { + if (result == null || result <= 0) { + return; + } - removeCard(card, result); - if (parentScreen.getCatalogPage() != null) { - parentScreen.getCatalogPage().addCard(card, result); + removeCard(card, result); + if (parentScreen.getCatalogPage() != null) { + parentScreen.getCatalogPage().addCard(card, result); + } } - } - }); + }); + } addItem(menu, Forge.getLocalizer().getMessage("lblMove"), Forge.getLocalizer().getMessage("lblToMainDeck"), parentScreen.getMainDeckPage().getIcon(), false, false, new Callback() { @Override public void run(Integer result) { @@ -896,6 +1081,14 @@ import java.util.Map; } } + public void addCards(Iterable> cards) { + if (canAddCards()) { + cardManager.addItems(cards); + //parentScreen.getEditorType().getController().notifyModelChanged(); + updateCaption(); + } + } + private boolean isPartnerCommander(final PaperCard card) { if (parentScreen.getCommanderPage() == null || parentScreen.getDeck().getCommanders().isEmpty()) { return false; diff --git a/forge-gui-mobile/src/forge/adventure/scene/DeckEditScene.java b/forge-gui-mobile/src/forge/adventure/scene/DeckEditScene.java index ad3671fdf1a..912250441c5 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/DeckEditScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/DeckEditScene.java @@ -42,7 +42,7 @@ public class DeckEditScene extends ForgeScene { } @Override public FScreen getScreen() { - return screen==null?screen = new AdventureDeckEditor(false):screen; + return screen==null?screen = new AdventureDeckEditor(false, null):screen; } } diff --git a/forge-gui-mobile/src/forge/adventure/scene/DraftScene.java b/forge-gui-mobile/src/forge/adventure/scene/DraftScene.java new file mode 100644 index 00000000000..4aad2532112 --- /dev/null +++ b/forge-gui-mobile/src/forge/adventure/scene/DraftScene.java @@ -0,0 +1,47 @@ +package forge.adventure.scene; + +import forge.adventure.data.AdventureEventData; +import forge.adventure.stage.GameHUD; +import forge.screens.FScreen; + +/** + * DraftScene + * scene class that contains the Deck editor used for draft events + */ +public class DraftScene extends ForgeScene { + private static DraftScene object; + + public static DraftScene instance() { + if(object==null) + object=new DraftScene(); + return object; + } + + AdventureDeckEditor screen; + AdventureEventData currentEvent; + + private DraftScene() { + + } + + @Override + public void dispose() { + } + + @Override + public void enter() { + GameHUD.getInstance().getTouchpad().setVisible(false); + screen = null; + getScreen(); + screen.refresh(); + super.enter(); + } + @Override + public FScreen getScreen() { + return screen==null?screen = new AdventureDeckEditor(false, currentEvent):screen; + } + + public void loadEvent(AdventureEventData event) { + this.currentEvent = event; + } +} diff --git a/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java b/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java index 2d31b59a7ac..421c68c156a 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java @@ -9,6 +9,7 @@ import forge.Graphics; import forge.LobbyPlayer; import forge.adventure.character.EnemySprite; import forge.adventure.character.PlayerSprite; +import forge.adventure.data.AdventureEventData; import forge.adventure.data.EffectData; import forge.adventure.data.EnemyData; import forge.adventure.data.ItemData; @@ -70,6 +71,7 @@ public class DuelScene extends ForgeScene { boolean callbackExit = false; boolean arenaBattleChallenge = false; boolean isArena = false; + AdventureEventData eventData; private LoadingOverlay matchOverlay; List playerExtras = new ArrayList<>(); List AIExtras = new ArrayList<>(); @@ -88,15 +90,20 @@ public class DuelScene extends ForgeScene { } public void GameEnd() { + //TODO: Progress towards applicable Adventure quests also needs to be reported here. + if (eventData != null) + eventData.nextOpponent = null; boolean winner = false; try { winner = humanPlayer == hostedMatch.getGame().getMatch().getWinner(); //Persists expended (or potentially gained) shards back to Adventure - //TODO: Progress towards applicable Adventure quests also needs to be reported here. - List humans = hostedMatch.getHumanControllers(); - if (humans.size() == 1) { - Current.player().setShards(humans.get(0).getPlayer().getNumManaShards()); + if (eventData == null || eventData.eventRules.allowsShards) { + List humans = hostedMatch.getHumanControllers(); { + if (humans.size() == 1) { + Current.player().setShards(humans.get(0).getPlayer().getNumManaShards()); + } + } } } catch (Exception e) { e.printStackTrace(); @@ -188,7 +195,8 @@ public class DuelScene extends ForgeScene { player.addExtraCardsOnBattlefield(startCards); player.addExtraCardsInCommandZone(startCardsInCommandZone); - player.setStartingLife(Math.max(1, lifeMod + player.getStartingLife())); + if (lifeMod != 0) + player.setStartingLife(Math.max(1, lifeMod + player.getStartingLife())); player.setStartingHand(player.getStartingHand() + changeStartCards); player.setManaShards((player.getManaShards() + extraManaShards)); player.setEnableETBCountersEffect(true); //enable etbcounters on starting cards like Ring of Three Wishes, etc... @@ -202,10 +210,17 @@ public class DuelScene extends ForgeScene { public void enter() { GameHUD.getInstance().unloadAudio(); Set appliedVariants = new HashSet<>(); - appliedVariants.add(GameType.Constructed); + if (eventData!= null && eventData.eventRules != null){ + appliedVariants.add(eventData.eventRules.gameType); + } + else{ + appliedVariants.add(GameType.Adventure); + } + AdventurePlayer advPlayer = Current.player(); List players = new ArrayList<>(); + int missingCards = Config.instance().getConfigData().minDeckSize - playerDeck.getMain().countAll(); if (missingCards > 0) //Replace unknown cards for a Wastes. playerDeck.getMain().add("Wastes", missingCards); @@ -222,8 +237,9 @@ public class DuelScene extends ForgeScene { playerObject.setAvatarIndex(90000); humanPlayer.setPlayer(playerObject); humanPlayer.setTeamNumber(0); - humanPlayer.setStartingLife(advPlayer.getLife()); - humanPlayer.setManaShards((advPlayer.getShards())); + humanPlayer.setStartingLife(eventData!=null?eventData.eventRules.startingLife:advPlayer.getLife()); + if (eventData!=null && eventData.eventRules.allowsShards) + humanPlayer.setManaShards(advPlayer.getShards()); Array playerEffects = new Array<>(); Array oppEffects = new Array<>(); @@ -242,30 +258,32 @@ public class DuelScene extends ForgeScene { humanPlayer.addExtraCardsOnBattlefield(playerCards); } - //Collect and add items effects first. - for (String playerItem : advPlayer.getEquippedItems()) { - ItemData item = ItemData.getItem(playerItem); - if (item != null && item.effect != null) { - playerEffects.add(item.effect); - if (item.effect.opponent != null) oppEffects.add(item.effect.opponent); - } else { - System.err.printf("Item %s not found.", playerItem); + if (eventData ==null || eventData.eventRules.allowsItems) { + //Collect and add items effects first. + for (String playerItem : advPlayer.getEquippedItems()) { + ItemData item = ItemData.getItem(playerItem); + if (item != null && item.effect != null) { + playerEffects.add(item.effect); + if (item.effect.opponent != null) oppEffects.add(item.effect.opponent); + } else { + System.err.printf("Item %s not found.", playerItem); + } } } + if (eventData ==null || eventData.eventRules.allowsBlessings) { + //Collect and add player blessings. + if (advPlayer.getBlessing() != null) { + playerEffects.add(advPlayer.getBlessing()); + if (advPlayer.getBlessing().opponent != null) oppEffects.add(advPlayer.getBlessing().opponent); + } - //Collect and add player blessings. - if (advPlayer.getBlessing() != null) { - playerEffects.add(advPlayer.getBlessing()); - if (advPlayer.getBlessing().opponent != null) oppEffects.add(advPlayer.getBlessing().opponent); + //Collect and add enemy effects (same as blessings but for individual enemies). + if (enemy.effect != null) { + oppEffects.add(enemy.effect); + if (enemy.effect.opponent != null) + playerEffects.add(enemy.effect.opponent); + } } - - //Collect and add enemy effects (same as blessings but for individual enemies). - if (enemy.effect != null) { - oppEffects.add(enemy.effect); - if (enemy.effect.opponent != null) - playerEffects.add(enemy.effect.opponent); - } - //Collect and add dungeon-wide effects. if (dungeonEffect != null) { oppEffects.add(dungeonEffect); @@ -290,6 +308,8 @@ public class DuelScene extends ForgeScene { deck = deckProxy.getDeck(); } else if (this.arenaBattleChallenge) { deck = Aggregates.random(DeckProxy.getAllGeneticAIDecks()).getDeck(); + } else if (this.eventData != null){ + deck = eventData.nextOpponent.getDeck(); } else { deck = currentEnemy.copyPlayerDeck ? this.playerDeck : currentEnemy.generateDeck(Current.player().isFantasyMode(), Current.player().isUsingCustomDeck() || Current.player().getDifficulty().name.equalsIgnoreCase("Hard")); } @@ -304,14 +324,16 @@ public class DuelScene extends ForgeScene { enemyPlayer.setAvatarIndex(90001 + i); aiPlayer.setPlayer(enemyPlayer); aiPlayer.setTeamNumber(currentEnemy.teamNumber); - aiPlayer.setStartingLife(Math.round((float) currentEnemy.life * advPlayer.getDifficulty().enemyLifeFactor)); + aiPlayer.setStartingLife(eventData!=null?eventData.eventRules.startingLife:Math.round((float) currentEnemy.life * advPlayer.getDifficulty().enemyLifeFactor)); Array equipmentEffects = new Array<>(); - if (currentEnemy.equipment != null) { - for (String oppItem : currentEnemy.equipment) { - ItemData item = ItemData.getItem(oppItem); - equipmentEffects.add(item.effect); - if (item.effect.opponent != null) playerEffects.add(item.effect.opponent); + if (eventData!=null && eventData.eventRules.allowsItems) { + if (currentEnemy.equipment != null) { + for (String oppItem : currentEnemy.equipment) { + ItemData item = ItemData.getItem(oppItem); + equipmentEffects.add(item.effect); + if (item.effect.opponent != null) playerEffects.add(item.effect.opponent); + } } } addEffects(aiPlayer, oppEffects); @@ -324,7 +346,9 @@ public class DuelScene extends ForgeScene { players.add(aiPlayer); - Current.setLatestDeck(deck); + if (eventData==null) { + Current.setLatestDeck(deck); + } currentEnemy = currentEnemy.nextEnemy; } @@ -336,14 +360,23 @@ public class DuelScene extends ForgeScene { hostedMatch = MatchController.hostMatch(); - GameRules rules = new GameRules(GameType.Constructed); + GameRules rules; + + if (eventData != null){ + rules = new GameRules(eventData.eventRules.gameType); + rules.setGamesPerMatch(eventData.eventRules.gamesPerMatch); + bossBattle = false; + } + else{ + rules = new GameRules(GameType.Adventure); + rules.setGamesPerMatch(1); + } rules.setPlayForAnte(false); rules.setMatchAnteRarity(true); - rules.setGamesPerMatch(1); rules.setManaBurn(false); rules.setWarnAboutAICards(false); - hostedMatch.setEndGameHook(() -> DuelScene.this.GameEnd()); + //hostedMatch.setEndGameHook(() -> DuelScene.this.GameEnd()); hostedMatch.startMatch(rules, appliedVariants, players, guiMap, bossBattle ? MusicPlaylist.BOSS : MusicPlaylist.MATCH); MatchController.instance.setGameView(hostedMatch.getGameView()); boolean showMessages = enemy.getData().copyPlayerDeck && Current.player().isUsingCustomDeck(); @@ -393,16 +426,22 @@ public class DuelScene extends ForgeScene { } public void initDuels(PlayerSprite playerSprite, EnemySprite enemySprite) { - initDuels(playerSprite, enemySprite, false); + initDuels(playerSprite, enemySprite, false, null); } - public void initDuels(PlayerSprite playerSprite, EnemySprite enemySprite, boolean isArena) { + public void initDuels(PlayerSprite playerSprite, EnemySprite enemySprite, boolean isArena, AdventureEventData eventData) { this.player = playerSprite; this.enemy = enemySprite; this.isArena = isArena; + this.eventData = eventData; + if (eventData!= null && eventData.eventRules == null) + eventData.eventRules = new AdventureEventData.AdventureEventRules(); this.arenaBattleChallenge = isArena && (Current.player().getDifficulty().name.equalsIgnoreCase("Hard") || Current.player().getDifficulty().name.equalsIgnoreCase("Insane")); - this.playerDeck = (Deck) Current.player().getSelectedDeck().copyTo("PlayerDeckCopy"); + if (eventData != null && eventData.registeredDeck != null) + this.playerDeck = eventData.registeredDeck; + else + this.playerDeck = (Deck) Current.player().getSelectedDeck().copyTo("PlayerDeckCopy"); this.chaosBattle = this.enemy.getData().copyPlayerDeck && Current.player().isFantasyMode(); this.AIExtras.clear(); this.playerExtras.clear(); diff --git a/forge-gui-mobile/src/forge/adventure/scene/EventScene.java b/forge-gui-mobile/src/forge/adventure/scene/EventScene.java new file mode 100644 index 00000000000..5e5b27bcaad --- /dev/null +++ b/forge-gui-mobile/src/forge/adventure/scene/EventScene.java @@ -0,0 +1,539 @@ +package forge.adventure.scene; + +import com.badlogic.gdx.scenes.scene2d.InputEvent; +import com.badlogic.gdx.scenes.scene2d.ui.*; +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; +import com.badlogic.gdx.utils.Align; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Scaling; +import com.github.tommyettinger.textra.TextraButton; +import com.github.tommyettinger.textra.TextraLabel; +import com.github.tommyettinger.textra.TypingLabel; +import forge.Forge; +import forge.adventure.character.EnemySprite; +import forge.adventure.data.*; +import forge.adventure.player.AdventurePlayer; +import forge.adventure.stage.IAfterMatch; +import forge.adventure.stage.WorldStage; +import forge.adventure.util.*; +import forge.adventure.world.WorldSave; +import forge.gui.FThreads; +import forge.screens.TransitionScreen; +import forge.util.Callback; +import forge.util.MyRandom; + +import java.util.Arrays; +import java.util.List; + +import static forge.adventure.util.AdventureEventController.EventStatus.Awarded; +import static forge.adventure.util.AdventureEventController.EventStatus.Ready; + +public class EventScene extends MenuScene implements IAfterMatch { + TextraLabel money, shards; + TextraButton advance, back, editDeck, nextPage, previousPage; + private Table scrollContainer; + ScrollPane scroller; + Table root, headerTable; + int pageIndex = 0; + Scene lastGameScene; + + Table[] eventPages; + + static AdventureEventData currentEvent; + + private Array entryDialog; + + + private EventScene() { + super(Forge.isLandscapeMode() ? "ui/event.json" : "ui/event_portrait.json"); + + DialogData introDialog = new DialogData(); + introDialog.text = "Enter this event?"; + DialogData enterWithCoin = new DialogData(); + enterWithCoin.name = "Redeem a Challenge Coin [+ChallengeCoin]"; + DialogData enterWithShards = new DialogData(); + enterWithShards.name = String.format("Spend %d [+shards]", currentEvent.eventRules.shardsToEnter); + DialogData enterWithGold = new DialogData(); + enterWithGold.name = String.format("Spend %d [+gold]", currentEvent.eventRules.goldToEnter); + + DialogData.ConditionData hasGold = new DialogData.ConditionData(); + hasGold.hasGold = currentEvent.eventRules.goldToEnter; + enterWithGold.condition = new DialogData.ConditionData[]{hasGold}; + + DialogData.ConditionData hasShards = new DialogData.ConditionData(); + hasShards.hasShards = currentEvent.eventRules.shardsToEnter; + enterWithShards.condition = new DialogData.ConditionData[]{hasShards}; + + if (currentEvent.eventRules.acceptsChallengeCoin){ + DialogData.ConditionData hasCoin = new DialogData.ConditionData(); + hasCoin.item="Challenge Coin"; + enterWithCoin.condition = new DialogData.ConditionData[]{hasCoin}; + } + else{ + DialogData.ConditionData alwaysFalse = new DialogData.ConditionData(); + alwaysFalse.not = true; + enterWithCoin.condition = new DialogData.ConditionData[]{alwaysFalse}; + } + + DialogData.ActionData spendGold = new DialogData.ActionData(); + spendGold.addGold=-currentEvent.eventRules.goldToEnter; + enterWithGold.action = new DialogData.ActionData[]{spendGold}; + + DialogData.ActionData spendShards = new DialogData.ActionData(); + spendShards.addShards =-currentEvent.eventRules.shardsToEnter; + enterWithShards.action = new DialogData.ActionData[]{spendShards}; + + DialogData.ActionData giveCoin = new DialogData.ActionData(); + giveCoin.removeItem = "Challenge Coin"; + enterWithCoin.action = new DialogData.ActionData[]{giveCoin}; + + DialogData decline = new DialogData(); + decline.name = "Do not enter event"; + + enterWithCoin.callback = new Callback() { + @Override + public void run(Boolean result) { + currentEvent.eventStatus = AdventureEventController.EventStatus.Entered; + refresh(); + } + }; + enterWithShards.callback = new Callback() { + @Override + public void run(Boolean result) { + currentEvent.eventStatus = AdventureEventController.EventStatus.Entered; + refresh(); + } + }; + enterWithGold.callback = new Callback() { + @Override + public void run(Boolean result) { + currentEvent.eventStatus = AdventureEventController.EventStatus.Entered; + refresh(); + } + }; + + introDialog.options = new DialogData[4]; + introDialog.options[0] = enterWithCoin; + introDialog.options[1] = enterWithShards; + introDialog.options[2] = enterWithGold; + introDialog.options[3] = decline; + + entryDialog = new Array<>(); + entryDialog.add(introDialog); + + TypingLabel blessingScroll = Controls.newTypingLabel("[BLACK]" + currentEvent.getDescription()); + blessingScroll.skipToTheEnd(); + blessingScroll.setAlignment(Align.topLeft); + blessingScroll.setWrap(true); + + ui.onButtonPress("return", EventScene.this::back); + ui.onButtonPress("advance", EventScene.this::advance); + ui.onButtonPress("editDeck", EventScene.this::editDeck); + + back = ui.findActor("return"); + advance = ui.findActor("advance"); + nextPage = ui.findActor("nextPage"); + nextPage.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + if (nextPage.isDisabled()) + return; + nextPage(false); + } + }); + + previousPage = ui.findActor("previousPage"); + previousPage.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + if (previousPage.isDisabled()) + return; + nextPage(true); + } + }); + + editDeck = ui.findActor("editDeck"); + editDeck.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + if (currentEvent.getDraft() != null && currentEvent.eventStatus == Ready) { + DraftScene.instance().loadEvent(currentEvent); + Forge.switchScene(DraftScene.instance()); + } + } + }); + + Window window = ui.findActor("scrollWindow"); + root = ui.findActor("enemies"); + + + Window header = ui.findActor("header"); + header.toFront(); + headerTable = new Table(Controls.getSkin()); + headerTable.add("Event Standings").expand(); + header.add(headerTable).expand(); + + ScrollPane blessing = ui.findActor("blessingInfo"); + blessing.setActor(blessingScroll); + blessingScroll.setWidth(blessing.getWidth()-5); + blessing.layout(); + window.add(root); + + refresh(); + } + + private void refresh(){ + scrollContainer = new Table(Controls.getSkin()); + scrollContainer.row(); + + Arrays.sort(currentEvent.participants); + + for (AdventureEventData.AdventureEventParticipant participant: currentEvent.participants){ + Image avatar = participant.getAvatar(); + avatar.setScaling(Scaling.stretch); + scrollContainer.add(avatar).pad(5).size(16).fillY(); + scrollContainer.add().width(16); + + boolean notEliminated = !currentEvent.eventStatus.equals(AdventureEventController.EventStatus.Started) || !currentEvent.matches.containsKey(currentEvent.currentRound) || currentEvent.matches.get(currentEvent.currentRound).stream().anyMatch(q -> q.p1.equals(participant) || q.p2.equals(participant)); + TextraLabel participantName = Controls.newTextraLabel((notEliminated?"":"[RED]") + participant.getName()); + participantName.setWrap(true); + + scrollContainer.add(participantName).fillX().pad(5).width(120); + scrollContainer.add().width(16); + scrollContainer.add(String.format("%d-%d", participant.wins, participant.losses)).pad(5); + scrollContainer.row(); + } + + eventPages = new Table[currentEvent.rounds + 1]; + eventPages[0] = scrollContainer; + for (int i = 0; i < currentEvent.rounds; i++){ + + Table round = new Table(Controls.getSkin()); + round.row(); + + List matches = currentEvent.getMatches(i+1); + + if (matches == null){ + round.add(Controls.newTextraLabel("Pairings not yet generated")); + } + else{ + Table roundScrollContainer = new Table(Controls.getSkin()); + for (AdventureEventData.AdventureEventMatch match: matches){ + + Table p1Table = new Table(Controls.getSkin()); + + Image p1Avatar = match.p1.getAvatar(); + p1Avatar.setScaling(Scaling.stretch); + + p1Table.add(p1Avatar).pad(5).size(16).fillY().top(); + String color = match.winner == null?"":match.winner.equals(match.p1)?"[GREEN]":match.winner.equals(match.p2)?"[RED]":""; + TypingLabel p1Name = Controls.newTypingLabel(color + match.p1.getName()); + p1Name.skipToTheEnd(); + p1Name.setWrap(true); + p1Table.add(p1Name).width(50).expandX().top(); + + roundScrollContainer.add(p1Table).left().uniformY().top().padBottom(10); + + Table verbTable = new Table(Controls.getSkin()); + + if (match.p2 == null){ + verbTable.add("has a bye").expand().fillX().top(); + } + else if (match.winner != null && match.winner.equals(match.p1)){ + verbTable.add("defeated").expand().fillX().top(); + } + else if (match.winner != null && match.winner.equals(match.p2)){ + verbTable.add("defeated by").expand().fillX().top(); + } + else { + verbTable.add("versus").expand().fillX().top(); + } + + roundScrollContainer.add(verbTable).padLeft(10).padRight(10).top(); + + Table p2Table = new Table(Controls.getSkin()); + if (match.p2 != null) { + Image p2Avatar = match.p2.getAvatar(); + p2Avatar.setScaling(Scaling.stretch); + String color2 = match.winner == null?"":match.winner.equals(match.p2)?"[GREEN]":match.winner.equals(match.p1)?"[RED]":""; + TypingLabel p2Name = Controls.newTypingLabel(color2 + match.p2.getName()); + p2Name.skipToTheEnd(); + p2Name.setWrap(true); + p2Table.add(p2Name).width(50).expandX().top(); + p2Table.add(p2Avatar).pad(5).size(16).fillY().top(); + } + roundScrollContainer.add(p2Table).right().uniformY().top().padBottom(10); + roundScrollContainer.row(); + } + round.add(roundScrollContainer).expandX().fillX(); + round.row(); + } + eventPages[i+1] = round; + } + + performTouch(scrollPaneOfActor(scrollContainer)); //can use mouse wheel if available to scroll + + root.clear(); + scrollContainer.layout(); + scroller = new ScrollPane(scrollContainer); + + root.add(scroller).fill().prefWidth(root.getWidth()); + + root.layout(); + scroller.layout(); + + scroller.clear(); + scroller.setActor(eventPages[pageIndex]); + performTouch(scroller); + + + switch (currentEvent.eventStatus){ + case Available: + nextPage.setDisabled(true); + previousPage.setDisabled(true); + editDeck.setDisabled(true); + editDeck.setVisible(false); + advance.setText("Join Event"); + advance.setVisible(true); + break; + case Entered: + nextPage.setDisabled(true); + previousPage.setDisabled(true); + editDeck.setDisabled(true); + editDeck.setVisible(false); + if (currentEvent.getDraft() != null){ + advance.setText("Enter Draft"); + } + else{ + advance.setText("Select Deck"); + } + advance.setVisible(true); + break; + case Ready: + advance.setText("Start Event"); + advance.setVisible(true); + editDeck.setDisabled(false); + editDeck.setVisible(true); + nextPage.setDisabled(false); + previousPage.setDisabled(false); + break; + case Started: + advance.setText("Play round " +currentEvent.currentRound); + advance.setVisible(true); + editDeck.setDisabled(true); + editDeck.setVisible(false); + nextPage.setDisabled(false); + previousPage.setDisabled(false); + break; + case Completed: + advance.setText("Collect Rewards"); + advance.setVisible(true); + editDeck.setDisabled(true); + editDeck.setVisible(false); + nextPage.setDisabled(false); + previousPage.setDisabled(false); + break; + case Awarded: + case Abandoned: + advance.setVisible(false); + editDeck.setDisabled(true); + editDeck.setVisible(false); + nextPage.setDisabled(false); + previousPage.setDisabled(false); + AdventureEventController.instance().finalizeEvent(currentEvent); + break; + } + } + + private static EventScene object; + + public static EventScene instance(Scene lastGameScene, AdventureEventData event) { + currentEvent = event; + // if (object == null) + object = new EventScene(); + if (lastGameScene != null) + object.lastGameScene=lastGameScene; + return object; + } + + private void nextPage(boolean reverse) { + headerTable.clear(); + if (!reverse && ++pageIndex >= eventPages.length){ + pageIndex = 0; + } + else if (reverse && --pageIndex < 0) { + pageIndex = eventPages.length - 1; + } + if (pageIndex == 0){ + headerTable.add("Event Standings").expand(); + } + else{ + headerTable.add("Round " + (pageIndex) + " of " + (eventPages.length - 1)); + } + + refresh(); + } + + @Override + public void enter() { + super.enter(); + scrollContainer.clear(); + + if (money != null) { + WorldSave.getCurrentSave().getPlayer().onGoldChange(() -> money.setText("[+Gold] [BLACK]" + AdventurePlayer.current().getGold())); + } + if (shards != null) { + WorldSave.getCurrentSave().getPlayer().onShardsChange(() -> shards.setText("[+Shards] [BLACK]" + AdventurePlayer.current().getShards())); + } + performTouch(scrollPaneOfActor(scrollContainer)); //can use mouse wheel if available to scroll + + refresh(); + } + + public void editDeck(){ + if (currentEvent.eventStatus == Ready){ + DraftScene.instance().loadEvent(currentEvent); + Forge.switchScene(DraftScene.instance()); + } + } + + public void advance() { + switch (currentEvent.eventStatus){ + case Available: + activate(entryDialog); //Entry fee pop-up + + break; + case Entered: //Start draft or select deck + //Show progress / wait indicator? Draft can take a while to generate + if (currentEvent.getDraft() != null) { + DraftScene.instance().loadEvent(currentEvent); + Forge.switchScene(DraftScene.instance()); + } + break; + case Ready: //Commit to selected deck + //Add confirmation pop-up? + currentEvent.startEvent(); + case Started: //Play next round + advance.setDisabled(true); + startRound(); + break; + case Completed://Show results, allow collection of rewards + case Awarded: //Show results but don't allow any further interaction + advance.setDisabled(true); + currentEvent.giveRewards(); + break; + + case Abandoned: //Show results but don't allow any interaction + + break; + } + refresh(); + } + + @Override + public boolean back(){ + if (currentEvent.eventStatus.equals(Awarded)){ + AdventureEventController.instance().finalizeEvent(currentEvent); + currentEvent = null; + } + Forge.switchScene(lastGameScene==null?GameScene.instance():lastGameScene); + return true; + } + + public void startRound() { + for (AdventureEventData.AdventureEventMatch match : currentEvent.matches.get(currentEvent.currentRound)) { + match.round = currentEvent.currentRound; + if (match.winner != null) continue; + + if (match.p2 == null) { + //shouldn't happen under current setup, but this would be a bye + match.winner = match.p1; + match.p1.wins +=1; + } + + if (match.p1 instanceof AdventureEventData.AdventureEventHuman) { + humanMatch = match; + continue; + } else if (match.p2 instanceof AdventureEventData.AdventureEventHuman) { + AdventureEventData.AdventureEventParticipant placeholder = match.p1; + match.p1 = match.p2; + match.p2 = placeholder; + humanMatch = match; + continue; + } else { + //Todo: Actually run match simulation here + if(MyRandom.percentTrue(50)){ + match.p1.wins++; + match.p2.losses++; + match.winner = match.p1; + } + else{ + match.p1.losses++; + match.p2.wins++; + match.winner = match.p2; + } + } + + } + + if (humanMatch != null && humanMatch.round != currentEvent.currentRound) + humanMatch = null; + if (humanMatch != null) + { + DuelScene duelScene = DuelScene.instance(); + EnemySprite enemy = humanMatch.p2.getSprite(); + currentEvent.nextOpponent = humanMatch.p2; + FThreads.invokeInEdtNowOrLater(() -> { + Forge.setTransitionScreen(new TransitionScreen(() -> { + duelScene.initDuels(WorldStage.getInstance().getPlayerSprite(), enemy, false, currentEvent); + Forge.switchScene(duelScene); + }, Forge.takeScreenshot(), true, false, false, false, "", Current.player().avatar(), enemy.getAtlasPath(), Current.player().getName(), enemy.nameOverride.isEmpty() ? enemy.getData().name : enemy.nameOverride, humanMatch.p1.getRecord(), humanMatch.p2.getRecord())); + }); + } + else + { + finishRound(); + } + advance.setDisabled(false); + } + + AdventureEventData.AdventureEventMatch humanMatch = null; + + public void setWinner(boolean winner) { + if (winner) { + humanMatch.winner = humanMatch.p1; + humanMatch.p1.wins++; + humanMatch.p2.losses++; + currentEvent.matchesWon++; + } else { + humanMatch.winner = humanMatch.p2; + humanMatch.p2.wins++; + humanMatch.p1.losses++; + currentEvent.matchesLost++; + } + + if (winner) { + //AdventureQuestController.instance().updateQuestsWin(currentMob,enemies); + //AdventureQuestController.instance().showQuestDialogs(MapStage.this); + } + else{ +// AdventureQuestController.instance().updateQuestsLose(currentMob); +// AdventureQuestController.instance().showQuestDialogs(MapStage.this); + } + + finishRound(); + } + + public void finishRound(){ + if (currentEvent.currentRound == currentEvent.rounds){ + finishEvent(); + } + else currentEvent.currentRound += 1; + refresh(); + } + + public void finishEvent(){ + currentEvent.eventStatus = AdventureEventController.EventStatus.Completed; + } + +} diff --git a/forge-gui-mobile/src/forge/adventure/scene/InnScene.java b/forge-gui-mobile/src/forge/adventure/scene/InnScene.java index d1d17a5db48..b23bf3baccd 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/InnScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/InnScene.java @@ -4,25 +4,45 @@ import com.badlogic.gdx.scenes.scene2d.ui.Image; import com.github.tommyettinger.textra.TextraButton; import com.github.tommyettinger.textra.TextraLabel; import forge.Forge; -import forge.adventure.stage.GameHUD; +import forge.adventure.data.AdventureEventData; +import forge.adventure.player.AdventurePlayer; +import forge.adventure.pointofintrest.PointOfInterestChanges; +import forge.adventure.util.AdventureEventController; import forge.adventure.util.Controls; import forge.adventure.util.Current; +import forge.adventure.world.WorldSave; /** * Scene for the Inn in towns */ public class InnScene extends UIScene { private static InnScene object; - + private static int localObjectId; + private static String localPointOfInterestId; + private static AdventureEventData localEvent; + Scene lastGameScene; public static InnScene instance() { + return instance(null, "",-1); + } + + public static InnScene instance(Scene lastGameScene, String pointOfInterestId, int objectId){ if(object==null) object=new InnScene(); + + changes = WorldSave.getCurrentSave().getPointOfInterestChanges(pointOfInterestId); + localPointOfInterestId = pointOfInterestId; + localObjectId = objectId; + if (lastGameScene != null) + object.lastGameScene=lastGameScene; + getLocalEvent(); + return object; } - TextraButton tempHitPointCost, sell, leave; + + TextraButton tempHitPointCost, sell, leave, event; Image healIcon, sellIcon, leaveIcon; - private TextraLabel playerGold,playerShards; + private TextraLabel playerGold,playerShards,eventDescription; private InnScene() { @@ -39,13 +59,17 @@ public class InnScene extends UIScene { leaveIcon = ui.findActor("leaveIcon"); healIcon = ui.findActor("healIcon"); sellIcon = ui.findActor("sellIcon"); + + event = ui.findActor("event"); + eventDescription = ui.findActor("eventDescription"); + + ui.onButtonPress("event", InnScene.this::startEvent); } public void done() { - GameHUD.getInstance().getTouchpad().setVisible(false); - Forge.switchToLast(); + Forge.switchScene(lastGameScene==null?GameScene.instance():lastGameScene); } public void potionOfFalseLife() { @@ -66,6 +90,7 @@ public class InnScene extends UIScene { } int tempHealthCost = 0; + static PointOfInterestChanges changes; @Override public void enter() { @@ -74,16 +99,69 @@ public class InnScene extends UIScene { } private void refreshStatus(){ + tempHealthCost = Current.player().falseLifeCost(); boolean purchaseable = Current.player().getMaxLife() == Current.player().getLife() && tempHealthCost <= Current.player().getGold(); tempHitPointCost.setDisabled(!purchaseable); tempHitPointCost.setText( tempHealthCost+"[+Gold]"); + + getLocalEvent(); + if (localEvent == null){ + eventDescription.setText("[GREY]No events at this time"); + event.setDisabled(true); + } + else{ + event.setDisabled(false); + switch (localEvent.eventStatus){ + case Available: + eventDescription.setText(localEvent.format.toString() + " available"); + break; + case Entered: + eventDescription.setText(localEvent.format.toString() + " [GREEN]entered"); + break; + case Ready: + eventDescription.setText(localEvent.format.toString() + " [GREEN]ready"); + break; + case Started: + eventDescription.setText(localEvent.format.toString() + " [GREEN]in progress"); + break; + case Completed: + eventDescription.setText(localEvent.format.toString() + " [GREEN]rewards available"); + break; + case Awarded: + eventDescription.setText(localEvent.format.toString() + " complete"); + break; + case Abandoned: + eventDescription.setText(localEvent.format.toString() + " [RED]abandoned"); + event.setDisabled(true); + break; + } + } } private void sell() { Forge.switchScene(ShopScene.instance()); } + + + private static void getLocalEvent() { + localEvent = null; + for (AdventureEventData data : AdventurePlayer.current().getEvents()){ + if (data.sourceID.equals(localPointOfInterestId) && data.eventOrigin == localObjectId){ + localEvent = data; + return; + } + } + localEvent = AdventureEventController.instance().createEvent(AdventureEventController.EventFormat.Draft, AdventureEventController.EventStyle.Bracket, localPointOfInterestId, localObjectId, changes); + } + + private void startEvent(){ + + Forge.switchScene(EventScene.instance(this, localEvent), true); + + } + } diff --git a/forge-gui-mobile/src/forge/adventure/scene/InventoryScene.java b/forge-gui-mobile/src/forge/adventure/scene/InventoryScene.java index f7e6c07c25d..f8550d34115 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/InventoryScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/InventoryScene.java @@ -1,6 +1,8 @@ package forge.adventure.scene; import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.ui.*; import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; @@ -17,10 +19,13 @@ import forge.adventure.util.Config; import forge.adventure.util.Controls; import forge.adventure.util.Current; import forge.adventure.util.Paths; +import forge.deck.Deck; import java.util.HashMap; import java.util.Map; +import static forge.adventure.util.Paths.ITEMS_ATLAS; + public class InventoryScene extends UIScene { TextraButton leave; Button equipButton; @@ -30,10 +35,11 @@ public class InventoryScene extends UIScene { private final Array