package com.cardshifter.gdx.screens; import com.badlogic.gdx.Application.ApplicationType; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Screen; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.Group; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.Touchable; import com.badlogic.gdx.scenes.scene2d.actions.Actions; import com.badlogic.gdx.scenes.scene2d.actions.SequenceAction; import com.badlogic.gdx.scenes.scene2d.ui.*; import com.badlogic.gdx.scenes.scene2d.ui.List; import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener; import com.badlogic.gdx.scenes.scene2d.utils.Align; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import com.badlogic.gdx.utils.SnapshotArray; import com.cardshifter.api.config.DeckConfig; import com.cardshifter.api.outgoing.CardInfoMessage; import com.cardshifter.gdx.Callback; import com.cardshifter.gdx.CardshifterGame; import com.cardshifter.gdx.TargetStatus; import com.cardshifter.gdx.TargetableCallback; import com.cardshifter.gdx.ZoomCardCallback; import com.cardshifter.gdx.ui.CardshifterClientContext; import com.cardshifter.gdx.ui.EntityView; import com.cardshifter.gdx.ui.cards.CardViewSmall; import java.util.*; /** * Created by Simon on 2/10/2015. */ public class DeckBuilderScreen implements Screen, TargetableCallback, ZoomCardCallback { private static final int ROWS_PER_PAGE = 3; private static final int CARDS_PER_ROW = 4; private static final int CARDS_PER_PAGE = CARDS_PER_ROW * ROWS_PER_PAGE; private final Callback<DeckConfig> callback; private final Table table; private final CardshifterGame game; private final java.util.List<CardInfoMessage> cards; private final DeckConfig config; private final int pageCount; private final Table cardsTable; private final CardshifterClientContext context; private final Map<Integer, Label> countLabels = new HashMap<Integer, Label>(); private final VerticalGroup cardsInDeckList; private final ScrollPane cardsInDeckScrollPane; private final List<String> savedDecks; private final Label nameLabel; private final TextButton previousPageButton; private final TextButton nextPageButton; private int page; private String deckName = "unnamed"; private FileHandle external; private final Label totalLabel; private final Screen lobbyScreen; private final float screenWidth; private final float screenHeight; private boolean cardZoomedIn = false; private float initialCardViewWidth = 0; private float initialCardViewHeight = 0; public DeckBuilderScreen(ClientScreen screen, CardshifterGame game, String modName, int gameId, final DeckConfig deckConfig, final Callback<DeckConfig> callback) { this.config = deckConfig; this.callback = callback; this.lobbyScreen = screen; this.game = game; this.context = new CardshifterClientContext(game.skin, gameId, null, game.stage); this.screenWidth = CardshifterGame.STAGE_WIDTH; this.screenHeight = CardshifterGame.STAGE_HEIGHT; Map<Integer, CardInfoMessage> data = deckConfig.getCardData(); cards = new ArrayList<CardInfoMessage>(data.values()); Collections.sort(cards, new Comparator<CardInfoMessage>() { @Override public int compare(CardInfoMessage o1, CardInfoMessage o2) { return Integer.compare(o1.getId(), o2.getId()); } }); pageCount = (int) Math.ceil(cards.size() / CARDS_PER_PAGE); //normally once i start constructing libGDX UI elements, I will use a separate method //not doing that here in order have the fields be final //this.buildScreen(); TextButton backToMenu = new TextButton("Back to menu", game.skin); backToMenu.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { DeckBuilderScreen.this.game.stage.clear(); DeckBuilderScreen.this.game.setScreen(DeckBuilderScreen.this.lobbyScreen); } }); backToMenu.setPosition(0, this.screenHeight - this.screenHeight/20); this.game.stage.addActor(backToMenu); this.table = new Table(game.skin); this.table.setFillParent(true); totalLabel = new Label("0/" + config.getMaxSize(), game.skin); nameLabel = new Label(deckName, game.skin); table.add(nameLabel); table.add(totalLabel); table.row(); cardsTable = new Table(game.skin); cardsTable.defaults().space(4); table.add(cardsTable); cardsInDeckList = new VerticalGroup(); cardsInDeckList.align(Align.left); this.cardsInDeckScrollPane = new ScrollPane(cardsInDeckList); this.cardsInDeckScrollPane.setScrollingDisabled(true, false); table.add(this.cardsInDeckScrollPane).width(this.screenWidth/5); savedDecks = new List<String>(game.skin); savedDecks.addListener(new ActorGestureListener(){ @Override public boolean longPress(Actor actor, float x, float y) { loadDeck(savedDecks.getSelected()); return true; } }); Table savedTable = scanSavedDecks(game, savedDecks, modName); if (savedTable != null) { savedTable.setHeight(this.screenHeight* 0.9f); ScrollPane savedTableScroll = new ScrollPane(savedTable); savedTableScroll.setScrollingDisabled(false, false); table.add(savedTableScroll).top(); } table.row(); HorizontalGroup prevNextButtons = new HorizontalGroup(); this.previousPageButton = addPageButton(prevNextButtons, "Previous", -1, game.skin); this.nextPageButton = addPageButton(prevNextButtons, "Next", 1, game.skin); table.add(prevNextButtons); TextButton startGameButton = new TextButton("Start Game", game.skin); startGameButton.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { if (config.total() >= config.getMinSize() && config.total() <= config.getMaxSize()) { DeckBuilderScreen.this.game.stage.clear(); callback.callback(deckConfig); } } }); table.add(startGameButton); if (Gdx.app.getType() != ApplicationType.WebGL) { TextButton save = new TextButton("Save", game.skin); save.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { final TextField textField = new TextField(deckName, DeckBuilderScreen.this.game.skin); Dialog dialog = new Dialog("Deck Name", DeckBuilderScreen.this.game.skin) { @Override protected void result(Object object) { boolean result = (Boolean) object; if (!result) { return; } deckName = textField.getText(); saveDeck(deckName); } }; dialog.add(textField); dialog.button("Save", true); dialog.button("Cancel", false); dialog.show(DeckBuilderScreen.this.game.stage); } }); TextButton load = new TextButton("Load", game.skin); load.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { DeckBuilderScreen.this.loadDeck(DeckBuilderScreen.this.savedDecks.getSelected()); } }); TextButton delete = new TextButton("Delete", game.skin); delete.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { Dialog dialog = new Dialog("Confirm Delete", DeckBuilderScreen.this.game.skin) { @Override protected void result(Object object) { boolean result = (Boolean) object; if (!result) { return; } FileHandle handle = external.child(savedDecks.getSelected() + ".deck"); handle.delete(); updateSavedDeckList(); } }; dialog.button("Delete", true); dialog.button("Cancel", false); dialog.show(DeckBuilderScreen.this.game.stage); } }); HorizontalGroup saveButtons = new HorizontalGroup(); saveButtons.addActor(save); saveButtons.addActor(load); saveButtons.addActor(delete); table.add(saveButtons); } displayPage(1); } private Table scanSavedDecks(final CardshifterGame game, final List<String> savedDecks, String modName) { if (Gdx.files.isExternalStorageAvailable()) { Table saveTable = new Table(); external = Gdx.files.external("Cardshifter/decks/" + modName + "/"); external.mkdirs(); if (!external.exists()) { Gdx.app.log("Files", external.path() + " does not exist."); return null; } updateSavedDeckList(); saveTable.add(savedDecks).colspan(2).fill().row(); return saveTable; } return null; } private void updateSavedDeckList() { java.util.List<String> list = new ArrayList<String>(); for (FileHandle handle : external.list()) { if (!handle.isDirectory()) { list.add(handle.nameWithoutExtension()); } } savedDecks.setItems(list.toArray(new String[list.size()])); } private void saveDeck(String deckName) { FileHandle handle = external.child(deckName + ".deck"); StringBuilder str = new StringBuilder(); for (Map.Entry<Integer, Integer> entry : config.getChosen().entrySet()) { int count = entry.getValue(); int id = entry.getKey(); for (int i = 0; i < count; i++) { if (str.length() > 0) { str.append(','); } str.append(id); } } String deckString = str.toString(); savedDecks.getItems().add(deckName); handle.writeString(deckString, false); nameLabel.setText(deckName); updateSavedDeckList(); } private void loadDeck(String deckName) { FileHandle handle = external.child(deckName + ".deck"); String deckString = handle.readString(); config.clearChosen(); this.cardsInDeckList.clear(); for (String id : deckString.split(",")) { try { int cardId = Integer.parseInt(id); if (config.getCardData().get(cardId) != null) { config.add(cardId); } } catch (NumberFormatException ex) { } } for (Map.Entry<Integer, Label> ee : countLabels.entrySet()) { ee.getValue().setText(countText(ee.getKey())); } nameLabel.setText(deckName); for (Map.Entry<Integer, Integer> ee : config.getChosen().entrySet()) { DeckCardView cardView = labelFor(ee.getKey()); cardView.setCount(ee.getValue()); } updateLabels(); } private String countText(int id) { Integer value = config.getChosen().get(id); return countText(id, value == null ? 0 : value); } private String countText(int id, int count) { int max = config.getMaxFor(id); return count + "/" + max; } private TextButton addPageButton(Group table, String text, final int i, Skin skin) { TextButton button = new TextButton(text, skin); button.addListener(new ClickListener(){ @Override public void clicked(InputEvent event, float x, float y) { displayPage(page + i); } }); table.addActor(button); return button; } private void displayPage(int page) { this.page = page; countLabels.clear(); int startIndex = (page - 1) * CARDS_PER_PAGE; cardsTable.clearChildren(); for (int i = startIndex; i < startIndex + CARDS_PER_PAGE; i++) { if (cards.size() <= i) { break; } if (i % CARDS_PER_ROW == 0) { cardsTable.row(); } CardInfoMessage card = cards.get(i); VerticalGroup choosableGroup = new VerticalGroup(); CardViewSmall cardView = new CardViewSmall(context, card, this, false); cardView.setTargetable(TargetStatus.TARGETABLE, this); choosableGroup.addActor(cardView.getActor()); Label label = new Label(countText(card.getId()), game.skin); label.setHeight(this.screenHeight/30); countLabels.put(card.getId(), label); choosableGroup.addActor(label); cardsTable.add(choosableGroup); } setButtonEnabled(previousPageButton, page > 1); setButtonEnabled(nextPageButton, page <= pageCount); } private void setButtonEnabled(TextButton button, boolean enabled) { if (enabled) { button.setTouchable(Touchable.enabled); button.setStyle(game.skin.get(TextButton.TextButtonStyle.class)); } else { button.setTouchable(Touchable.disabled); button.setStyle(game.skin.get("disabled", TextButton.TextButtonStyle.class)); } } @Override public void render(float delta) { } @Override public void resize(int width, int height) { } @Override public void show() { game.stage.addActor(table); } @Override public void hide() { table.remove(); } @Override public void pause() { } @Override public void resume() { } @Override public void dispose() { } @Override public boolean addEntity(final EntityView view) { //prevent other cards from being added when one is zoomed in on if (this.cardZoomedIn) { return false; } final int id = view.getId(); int max = config.getMaxFor(id); Integer chosen = config.getChosen().get(id); if (chosen == null) { chosen = 0; } int newChosen = (chosen + 1) % (max + 1); if (config.total() >= config.getMaxSize() && chosen > 0) { newChosen = 0; } config.setChosen(id, newChosen); countLabels.get(id).setText(countText(id, newChosen)); DeckCardView cardView = labelFor(id); cardView.setCount(newChosen); updateLabels(); return true; } private DeckCardView labelFor(int id) { SnapshotArray<Actor> children = cardsInDeckList.getChildren(); int index = 0; String name = (String) config.getCardData().get(id).getProperties().get("name"); for (Actor actor : children) { DeckCardView view = (DeckCardView) actor; if (view.getId() == id) { return view; } if (name.compareTo(view.getName()) < 0) { break; } index++; } DeckCardView view = new DeckCardView(game.skin, id, name, this); cardsInDeckList.addActorAt(index, view); return view; } private void updateLabels() { totalLabel.setText(config.total() + "/" + config.getMaxSize()); } public void removeCardFromDeck(int id) { for (Actor actor : cardsInDeckList.getChildren()) { if (actor instanceof DeckCardView) { if (actor instanceof DeckCardView) { if (((DeckCardView)actor).getId() == id) { int newCount = ((DeckCardView)actor).getCount() - 1; if (newCount > 0) { ((DeckCardView) actor).setCount(newCount); } else { actor.remove(); } config.setChosen(id, newCount); } } } } this.updateLabels(); this.displayPage(this.page); } @Override public void zoomCard(final CardViewSmall cardView) { if (this.cardZoomedIn) { return; } final CardViewSmall cardViewCopy = new CardViewSmall(this.context, cardView.cardInfo, this, true); cardViewCopy.setTargetable(TargetStatus.TARGETABLE, this); cardViewCopy.getActor().setPosition(this.screenWidth/2.7f, this.screenHeight/30); this.game.stage.addActor(cardViewCopy.getActor()); this.initialCardViewWidth = cardView.getActor().getWidth(); this.initialCardViewHeight = cardView.getActor().getHeight(); SequenceAction sequence = new SequenceAction(); Runnable adjustForZoom = new Runnable() { @Override public void run() { cardViewCopy.zoom(); } }; sequence.addAction(Actions.sizeTo(this.screenWidth/4, this.screenHeight*0.9f, 0.2f)); sequence.addAction(Actions.run(adjustForZoom)); cardViewCopy.getActor().addAction(sequence); this.cardZoomedIn = true; } @Override public void endZoom(final CardViewSmall cardView) { if (cardView.isZoomed){ SequenceAction sequence = new SequenceAction(); Runnable endZoom = new Runnable() { @Override public void run() { cardView.endZoom(); cardView.getActor().remove(); DeckBuilderScreen.this.cardZoomedIn = false; } }; sequence.addAction(Actions.sizeTo(this.initialCardViewWidth, this.initialCardViewHeight, 0.2f)); sequence.addAction(Actions.run(endZoom)); cardView.getActor().addAction(sequence); } } public boolean checkCardDrop(CardViewSmall cardView) { Table table = (Table)cardView.getActor(); Vector2 stageLoc = table.localToStageCoordinates(new Vector2()); Rectangle tableRect = new Rectangle(stageLoc.x, stageLoc.y, table.getWidth(), table.getHeight()); Vector2 stageLocCardList = this.cardsInDeckList.localToStageCoordinates(new Vector2(this.cardsInDeckList.getX(), this.cardsInDeckList.getY())); Vector2 modifiedSLCL = new Vector2(stageLocCardList.x, stageLocCardList.y - this.screenHeight/2); Rectangle deckRect = new Rectangle(modifiedSLCL.x, modifiedSLCL.y, this.cardsInDeckList.getWidth(), this.screenHeight); if (tableRect.overlaps(deckRect)) { this.addEntity(cardView); return true; } return false; //these can be used to double check the location of the rectangles /* Image squareImage = new Image(new Texture(Gdx.files.internal("cardbg.png"))); squareImage.setPosition(modifiedSLCL.x, modifiedSLCL.y); squareImage.setSize(deckRect.width, deckRect.height); this.game.stage.addActor(squareImage); */ /* Image squareImage = new Image(new Texture(Gdx.files.internal("cardbg.png"))); squareImage.setPosition(stageLoc.x, stageLoc.y); squareImage.setSize(tableRect.width, tableRect.height); this.game.stage.addActor(squareImage); */ } }