Java tutorial
/* * Dice heroes is a turn based rpg-strategy game where characters are dice. * Copyright (C) 2016 Vladislav Protsenko * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.vlaaad.dice.game.world.controllers; import com.badlogic.gdx.math.Interpolation; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.EventListener; 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.ui.*; import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener; import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; import com.badlogic.gdx.scenes.scene2d.utils.DragListener; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.ObjectSet; import com.badlogic.gdx.utils.Pools; import com.vlaaad.common.tutorial.Tutorial; import com.vlaaad.common.ui.WindowManager; import com.vlaaad.common.util.Grid2D; import com.vlaaad.dice.Config; import com.vlaaad.dice.game.config.abilities.Ability; import com.vlaaad.dice.game.config.levels.LevelElementType; import com.vlaaad.dice.game.objects.Creature; import com.vlaaad.dice.game.objects.StepDetector; import com.vlaaad.dice.game.objects.WorldObject; import com.vlaaad.dice.game.tutorial.DiceTutorial; import com.vlaaad.dice.game.user.Die; import com.vlaaad.dice.game.world.World; import com.vlaaad.dice.game.world.WorldController; import com.vlaaad.dice.game.world.events.EventType; import com.vlaaad.dice.game.world.players.Fraction; import com.vlaaad.dice.game.world.view.WorldObjectView; import com.vlaaad.dice.managers.SoundManager; import com.vlaaad.dice.ui.scene2d.LocTextButton; import com.vlaaad.dice.ui.windows.CreatureInfoWindow; import java.util.Map; import java.util.Set; /** * Created 06.10.13 by vlaaad */ public class SpawnController extends WorldController { public static final EventType<Void> START = new EventType<Void>(); private static final float DIE_PADDING = 2; public TextButton startButton; public Table table; private ObjectMap<WorldObjectView, EventListener> moveListeners = new ObjectMap<WorldObjectView, EventListener>(); private ObjectMap<WorldObjectView, EventListener> spawnListeners = new ObjectMap<WorldObjectView, EventListener>(); private final CreatureInfoWindow creatureInfoWindow = new CreatureInfoWindow(); private ObjectMap<Die, WorldObjectView> dieToIconToSpawn = new ObjectMap<Die, WorldObjectView>(); private ViewController viewController; public final Button autoPlaceButton = new Button(Config.skin, "auto-place"); private boolean scrollingEnabled; private Array<Creature> creatures; private Container autoPlaceContainer; public SpawnController(World world) { super(world); } private ObjectSet<Creature> placed = new ObjectSet<Creature>(); @Override protected void start() { Table diceList = new Table(); Table placeHolderList = new Table(); diceList.setSize(ViewController.CELL_SIZE * world.viewer.creatures.size, ViewController.CELL_SIZE); placeHolderList.setSize(ViewController.CELL_SIZE * world.viewer.creatures.size, ViewController.CELL_SIZE); creatures = new Array<Creature>(world.viewer.creatures.size); for (Die die : world.viewer.dice) { creatures.add(new Creature(die, world.viewer)); } creatures.sort(Ability.INITIATIVE_COMPARATOR); for (Creature creature : creatures) { WorldObjectView view = ViewController.createView(world.viewer, world.playerColors, creature); dieToIconToSpawn.put(creature.description, view); EventListener listener = createDragToSpawnListener(view, creature); spawnListeners.put(view, listener); view.addListener(listener); diceList.add(view).padLeft(DIE_PADDING).padRight(DIE_PADDING); placeHolderList.add(new Image(Config.skin, "ui-spawn-placeholder")).padLeft(DIE_PADDING) .padRight(DIE_PADDING); } startButton = new LocTextButton("ui-spawn-fight", "fight", 7, 5); startButton.getLabel().setAlignment(Align.center); startButton.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { for (WorldObjectView view : moveListeners.keys()) { view.removeListener(moveListeners.get(view)); } for (Creature creature : placed) { world.viewer.addCreature(creature); } world.dispatcher.dispatch(START, null); } }); Stack stack = new Stack(); stack.add(placeHolderList); stack.add(diceList); Table wrapper = new Table(); wrapper.add(stack).padTop(15).padBottom(2); final ScrollPane scrollPane = new ScrollPane(wrapper); scrollPane.setupOverscroll(10, 40, 50); // scrollPane.setCancelTouchFocus(false); // scrollPane.setScrollingDisabled(true,true); table = new Table(Config.skin); table.setBackground("ui-spawn-background"); scrollingEnabled = scrollPane.getPrefWidth() >= world.stage.getWidth(); table.add(scrollPane).expandX().fillX().padLeft(-1).padRight(-1).padTop(-20); table.setSize(world.stage.getWidth(), table.getPrefHeight()); viewController = world.getController(ViewController.class); world.stage.addActor(startButton); startButton.setPosition(world.stage.getWidth() / 2 - startButton.getPrefWidth() / 2, world.stage.getHeight() + startButton.getPrefHeight()); world.stage.addActor(table); table.setPosition(world.stage.getWidth() / 2 - table.getWidth() / 2, 0); refreshStartButton(); if (creatures.size > 1 && !world.viewer.tutorialProvider.isInitiativeTutorialCompleted()) { table.invalidate(); table.validate(); new Tutorial(Tutorial.resources().with("world", world).with("stage", world.stage).with( "tutorial-provider", world.viewer.tutorialProvider), DiceTutorial.initiativeTutorialTasks()) .start(); } autoPlaceContainer = new Container(autoPlaceButton); autoPlaceContainer.setFillParent(true); autoPlaceContainer.top().right().pad(4); world.stage.addActor(autoPlaceContainer); autoPlaceButton.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { autoPlace(); } }); if (scrollingEnabled) { if (!Tutorial.hasRunningTutorials() && !world.viewer.tutorialProvider.isSpawnSwipeTutorialCompleted()) { new Tutorial( Tutorial.resources().with("stage", world.stage).with("world", world) .with("tutorial-provider", world.viewer.tutorialProvider), DiceTutorial.spawnScrollingTasks()).start(); } } else { scrollPane.setCancelTouchFocus(false); } } @SuppressWarnings("unchecked") private void autoPlace() { if (placed.size > 0) { ObjectSet<Creature> tmp = Pools.obtain(ObjectSet.class); tmp.addAll(placed); for (Creature c : tmp) { removeFromPlaced(c); } tmp.clear(); Pools.free(tmp); } Array<Grid2D.Coordinate> coordinates = Pools.obtain(Array.class); Set<Map.Entry<Grid2D.Coordinate, Fraction>> spawns = world.level.getElements(LevelElementType.spawn); for (Map.Entry<Grid2D.Coordinate, Fraction> e : spawns) { if (e.getValue() == world.viewer.fraction) { coordinates.add(e.getKey()); } } coordinates.shuffle(); int usedCount = Math.min(creatures.size, coordinates.size); Array<Creature> toPlace = Pools.obtain(Array.class); toPlace.addAll(creatures); toPlace.shuffle(); toPlace.truncate(usedCount); for (Creature creature : toPlace) { Grid2D.Coordinate coordinate = coordinates.pop(); place(creature, coordinate.x(), coordinate.y()); } toPlace.clear(); coordinates.clear(); Pools.free(toPlace); Pools.free(coordinates); } private static final Vector2 tmp = new Vector2(); private boolean shouldHideOnDrag() { return viewController.worldToStageCoordinates(tmp.set(0, 0)).y < table.getHeight(); } @Override protected void stop() { table.remove(); startButton.remove(); autoPlaceContainer.remove(); autoPlaceButton.setTouchable(Touchable.disabled); for (WorldObjectView view : moveListeners.keys()) { view.removeListener(moveListeners.get(view)); } } private DragListener createDragToSpawnListener(final WorldObjectView view, final Creature creature) { return new DragListener() { private float initialX; private float initialY; { setTapSquareSize(4); } private boolean shouldHideOnDrag; private WorldObjectView draggedView = ViewController.createView(world.viewer, world.playerColors, creature); @Override public void touchUp(InputEvent event, float x, float y, int pointer, int button) { if (!isDragging()) { creatureInfoWindow.show(new CreatureInfoWindow.Params(creature, world)); } if (shouldHideOnDrag) { table.clearActions(); table.addAction(Actions.alpha(1, 0.3f)); } super.touchUp(event, x, y, pointer, button); } @Override public void dragStart(InputEvent event, float x, float y, int pointer) { if (scrollingEnabled) { if (Math.abs(event.getStageX() - initialX) < Math.abs(event.getStageY() - initialY)) { event.getStage().cancelTouchFocusExcept(this, event.getListenerActor()); } else { return; } } if (placed.contains(creature)) return; // event.getStage().cancelTouchFocus(this, event.getListenerActor()); shouldHideOnDrag = shouldHideOnDrag(); if (shouldHideOnDrag) { table.clearActions(); table.addAction(Actions.alpha(0, 0.3f)); } view.getColor().a = 0f; view.setTouchable(Touchable.disabled); world.stage.addActor(draggedView); drag(event, x, y, pointer); SoundManager.instance.playSound("ui-button-down"); } @Override public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { initialX = event.getStageX(); initialY = event.getStageY(); return super.touchDown(event, x, y, pointer, button); } @Override public void drag(InputEvent event, float x, float y, int pointer) { if (placed.contains(creature)) return; draggedView.setPosition(event.getStageX() - view.getWidth() / 2, event.getStageY() - view.getHeight() / 2); } @Override public void dragStop(InputEvent event, float x, float y, int pointer) { if (placed.contains(creature)) return; draggedView.remove(); Vector2 coordinate = world.getController(ViewController.class).root .stageToLocalCoordinates(new Vector2(draggedView.getX() + draggedView.getWidth() / 2, draggedView.getY() + draggedView.getHeight() / 2)); coordinate.scl(1 / ViewController.CELL_SIZE); final int cellX = MathUtils.floor(coordinate.x); final int cellY = MathUtils.floor(coordinate.y); WorldObject w = world.get(cellX, cellY); if (cellX < 0 || cellY < 0 || cellX >= world.width || cellY >= world.height || (w != null && !(w instanceof Creature && ((Creature) w).initialPlayer == world.viewer))) { view.getColor().a = 1f; view.setTouchable(Touchable.enabled); refreshStartButton(); return; } if (world.level.getElement(LevelElementType.spawn, cellX, cellY) == world.viewer.fraction) { if (w != null) { removeFromPlaced((Creature) w); } SoundManager.instance.playSound("ui-button-up"); place(creature, cellX, cellY); } else { view.setTouchable(Touchable.enabled); view.getColor().a = 1f; } } }; } private void place(Creature creature, int x, int y) { WorldObjectView spawnView = dieToIconToSpawn.get(creature.description); placed.add(creature); refreshStartButton(); world.add(x, y, creature); WorldObjectView worldView = world.getController(ViewController.class).getView(creature); EventListener listener = createMoveSpawnedListener(creature, worldView, spawnView); EventListener prev = moveListeners.remove(worldView); if (prev != null) { worldView.removeListener(prev); } moveListeners.put(worldView, listener); worldView.addListener(listener); spawnView.getColor().a = 0f; spawnView.setTouchable(Touchable.disabled); } private void removeFromPlaced(Creature creature) { placed.remove(creature); refreshStartButton(); world.remove(creature); WorldObjectView spawnView = dieToIconToSpawn.get(creature.description); spawnView.getColor().a = 1f; spawnView.setTouchable(Touchable.enabled); } private EventListener createMoveSpawnedListener(final Creature creature, final WorldObjectView view, final WorldObjectView spawnView) { return new ActorGestureListener(8, 0.4f, 1.1f, 0.15f) { private boolean isDragging; @Override public void touchDown(InputEvent event, float x, float y, int pointer, int button) { view.toFront(); SoundManager.instance.playSound("ui-button-down"); } @Override public void touchUp(InputEvent event, float x, float y, int pointer, int button) { if (!isDragging) { return; } isDragging = false; viewController.scroller.enable(); Vector2 coordinate = world.getController(ViewController.class) .stageToWorldCoordinates(event.getStageX(), event.getStageY()); final int cellX = (int) coordinate.x; final int cellY = (int) coordinate.y; boolean isAllowedCell = world.level.getElement(LevelElementType.spawn, cellX, cellY) == world.viewer.fraction; if (!isAllowedCell) { removeFromPlaced(creature); SoundManager.instance.playSound("ui-button-up"); return; } if (!world.inBounds(cellX, cellY) || world.get(cellX, cellY) != null) { WorldObject prev = world.get(cellX, cellY); boolean shouldSwap = false; int swapX = 0; int swapY = 0; Creature other = null; if (prev instanceof Creature) { other = (Creature) prev; if (other.player == world.viewer && other != creature) { shouldSwap = true; for (int i = 0; i < world.width; i++) { for (int j = 0; j < world.height; j++) { if (world.get(i, j) == null) { swapX = i; swapY = j; shouldSwap = true; break; } } } } } if (shouldSwap) { int prevX = prev.getX(); int prevY = prev.getY(); int creatureX = creature.getX(); int creatureY = creature.getY(); world.move(creature, swapX, swapY); world.move(prev, creatureX, creatureY); world.move(creature, prevX, prevY); } else { view.setPosition(creature.getX() * ViewController.CELL_SIZE, creature.getY() * ViewController.CELL_SIZE); } } else { world.move(creature, cellX, cellY); SoundManager.instance.playSound("ui-button-up"); } world.getController(ViewController.class).resort(); } @Override public void tap(InputEvent event, float x, float y, int count, int button) { if (event.isCancelled() || WindowManager.instance.isShown(CreatureInfoWindow.class)) { world.getController(ViewController.class).resort(); return; } removeFromPlaced(creature); SoundManager.instance.playSound("ui-button-up"); } private Vector2 tmp = new Vector2(); @Override public void pan(InputEvent event, float x, float y, float deltaX, float deltaY) { Vector2 local = view.getParent() .stageToLocalCoordinates(tmp.set(event.getStageX(), event.getStageY())); view.setPosition(local.x - ViewController.CELL_SIZE / 2, local.y - ViewController.CELL_SIZE / 2); isDragging = true; viewController.scroller.disable(); } }; } private void refreshStartButton() { startButton.setDisabled(placed.size == 0); startButton.clearActions(); if (placed.size == 0) { startButton.addAction(Actions.moveTo(world.stage.getWidth() / 2 - startButton.getWidth() / 2, world.stage.getHeight() + startButton.getHeight(), 0.5f, Interpolation.swingIn)); } else { startButton.addAction(Actions.moveTo(world.stage.getWidth() / 2 - startButton.getWidth() / 2, world.stage.getHeight() - startButton.getHeight() - 4, 0.5f, Interpolation.swingOut)); } } public Actor getDieIconToSpawn(Die die) { return dieToIconToSpawn.get(die); } }