package de.fau.cs.mad.fly.game; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import com.badlogic.gdx.Application.ApplicationType; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.InputMultiplexer; import com.badlogic.gdx.graphics.Texture.TextureFilter; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.g3d.ModelBatch; import com.badlogic.gdx.physics.bullet.Bullet; import com.badlogic.gdx.physics.bullet.collision.btCollisionShape; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.viewport.FillViewport; import com.badlogic.gdx.utils.viewport.Viewport; import de.fau.cs.mad.fly.Fly; import de.fau.cs.mad.fly.Loader; import de.fau.cs.mad.fly.features.ICollisionListener; import de.fau.cs.mad.fly.features.IFeatureDispose; import de.fau.cs.mad.fly.features.IFeatureDraw; import de.fau.cs.mad.fly.features.IFeatureFinish; import de.fau.cs.mad.fly.features.IFeatureInit; import de.fau.cs.mad.fly.features.IFeatureLoad; import de.fau.cs.mad.fly.features.IFeatureRender; import de.fau.cs.mad.fly.features.IFeatureUpdate; import de.fau.cs.mad.fly.features.game.EndlessLevelGenerator; import de.fau.cs.mad.fly.features.game.EndlessRailLevelGenerator; import de.fau.cs.mad.fly.features.overlay.BackButtonOverlay; import de.fau.cs.mad.fly.features.overlay.FPSOverlay; import de.fau.cs.mad.fly.features.overlay.GameFinishedOverlay; import de.fau.cs.mad.fly.features.overlay.GateIndicator; import de.fau.cs.mad.fly.features.overlay.InfoButtonOverlay; import de.fau.cs.mad.fly.features.overlay.InfoOverlay; import de.fau.cs.mad.fly.features.overlay.ScoreOverlay; import de.fau.cs.mad.fly.features.overlay.TimeLeftOverlay; import de.fau.cs.mad.fly.features.overlay.TouchScreenOverlay; import de.fau.cs.mad.fly.features.upgrades.ChangePointsUpgradeHandler; import de.fau.cs.mad.fly.features.upgrades.ChangeSteeringUpgradeHandler; import de.fau.cs.mad.fly.features.upgrades.ChangeTimeUpgradeHandler; import de.fau.cs.mad.fly.features.upgrades.ResizeGatesUpgradeHandler; import de.fau.cs.mad.fly.features.upgrades.TemporarySpeedUpgradeHandler; import de.fau.cs.mad.fly.features.upgrades.types.TemporarySpeedUpgrade; import de.fau.cs.mad.fly.game.GameController.GameState; import de.fau.cs.mad.fly.graphics.shaders.FlyShaderProvider; import de.fau.cs.mad.fly.levels.DefaultLevel; import de.fau.cs.mad.fly.levels.ILevel; import de.fau.cs.mad.fly.player.IPlane; import de.fau.cs.mad.fly.player.Player; import de.fau.cs.mad.fly.player.Spaceship; import de.fau.cs.mad.fly.profile.PlayerProfile; import de.fau.cs.mad.fly.profile.PlayerProfileManager; import de.fau.cs.mad.fly.res.GateCircuit; import de.fau.cs.mad.fly.res.GateCircuitAdapter; import de.fau.cs.mad.fly.res.GateCircuitListener; import de.fau.cs.mad.fly.res.GateGoal; import de.fau.cs.mad.fly.res.Level; import de.fau.cs.mad.fly.res.Perspective; import de.fau.cs.mad.fly.settings.SettingManager; import de.fau.cs.mad.fly.sound.AudioManager; import de.fau.cs.mad.fly.ui.BackProcessor; import de.fau.cs.mad.fly.ui.SkinManager; import de.fau.cs.mad.fly.ui.UI; import de.fau.cs.mad.fly.ui.screens.MainMenuScreen; /** * This class implements the builder pattern to create a GameController with all * of its dependent components. * * @author Lukas Hahmann * */ public class GameControllerBuilder { private Player player; private PlayerProfile playerProfile; private Stage stage; private Level level; private List<IFeatureLoad> optionalFeaturesToLoad; private List<IFeatureInit> optionalFeaturesToInit; private List<IFeatureUpdate> optionalFeaturesToUpdate; private List<IFeatureRender> optionalFeaturesToRender; private List<IFeatureDraw> optionalFeaturesToDraw; private List<IFeatureFinish> optionalFeaturesToFinish; private List<IFeatureDispose> optionalFeaturesToDispose; private List<GameStateListener> gameStateListener; private FlightController flightController; private CameraController cameraController; private TimeController timeController; private ScoreController scoreController; private EndlessLevelGenerator generator; private AudioManager audioManager; /** * Creates a basic {@link GameController} with a certain level, linked to * the current player, its settings and the selected level. It interprets * the setting of the player and and creates based on the settings optional * features. * * @param game * needed to get the player for the current settings and the * level * @return new GameController with the current selected level and the * selected settings */ public GameControllerBuilder init(final Fly game) { clearFeatureLists(); gameStateListener = new ArrayList<GameStateListener>(); player = new Player(); playerProfile = PlayerProfileManager.getInstance().getCurrentPlayerProfile(); level = Loader.getInstance().getCurrentLevel(); switch( Gdx.app.getType() ) { case iOS: try { Constructor c = Class.forName("de.fau.cs.mad.fly.ios.input.IOSFlightController").getConstructor(Player.class, PlayerProfile.class); flightController = (FlightController) c.newInstance(player, playerProfile); } catch (Exception e) { throw new GdxRuntimeException("Error instantiating IOSFlightController", e); } break; default: flightController = new FlightController(player, playerProfile); } flightController.init(); cameraController = new CameraController(player, playerProfile); stage = new Stage(); timeController = new TimeController(); scoreController = new ScoreController(); audioManager = new AudioManager(); float widthScalingFactor = UI.Window.REFERENCE_WIDTH / (float) Gdx.graphics.getWidth(); float heightScalingFactor = UI.Window.REFERENCE_HEIGHT / (float) Gdx.graphics.getHeight(); float scalingFactor = Math.max(widthScalingFactor, heightScalingFactor); Viewport viewport = new FillViewport(Gdx.graphics.getWidth() * scalingFactor, Gdx.graphics.getHeight() * scalingFactor, stage.getCamera()); stage.setViewport(viewport); GateCircuit gateCircuit = level.getGateCircuit(); optionalFeaturesToLoad.add(gateCircuit); addPlayerPlane(); Bullet.init(); CollisionDetector.createCollisionDetector(); CollisionDetector collisionDetector = CollisionDetector.getInstance(); collisionDetector.getCollisionContactListener().addListener(gateCircuit); collisionDetector.getCollisionContactListener().addListener(new ICollisionListener() { @Override public void onCollision(GameObject g1, GameObject g2) { GameObject g; if (g1 instanceof Spaceship) { g = g2; } else if (g2 instanceof Spaceship) { g = g1; } else { return; } if (g.isDummy()) { return; } audioManager.play(AudioManager.Sounds.CRASH); Player currentPlayer = GameController.getInstance().getPlayer(); if (!currentPlayer.decreaseLives()) { game.getGameController().setGameState(GameState.NO_LIVES); } } }); gateCircuit.createGateRigidBodies(); createDecoRigidBodies(collisionDetector, level); gateCircuit.addListener(new GateCircuitAdapter() { @Override public void onGatePassed(GateGoal passed) { GateCircuit gateCircuit = level.getGateCircuit(); List<GateGoal> gates; if (level.head.isEndless()) { gates = generator.getGates(); } else { gates = gateCircuit.allGateGoals(); } int size = gates.size(); for (int i = 0; i < size; i++) { gates.get(i).unmark(); } int len = passed.successors.length; for (int i = 0; i < len; i++) { gateCircuit.getGateGoalById(passed.successors[i]).mark(); } } }); gateCircuit.addListener(scoreController); if (level.head.isEndless()) { generator = new EndlessLevelGenerator(Loader.getInstance().getCurrentLevel(), this); gateCircuit.addListener(new GateCircuitAdapter() { @Override public void onGatePassed(GateGoal passed) { generator.addRandomGate(passed); int extraTime = generator.getExtraTime(); timeController.addBonusTime(extraTime); } }); } else if (level.head.isEndlessRails()) { generator = new EndlessRailLevelGenerator(Loader.getInstance().getCurrentLevel(), this); if(Gdx.app.getType().equals(ApplicationType.iOS)) { try { Constructor c = Class.forName("de.fau.cs.mad.fly.ios.input.IOSRailFlightController").getConstructor(Player.class, PlayerProfile.class, EndlessLevelGenerator.class, Perspective.class); flightController = (FlightController) c.newInstance(player, playerProfile, generator, level.start); } catch(Exception e) { throw new GdxRuntimeException("Error instantiating IOSRailFlightController",e); } } else { flightController = new RailFlightController(player, playerProfile, generator, level.start); } CollisionDetector.getInstance().getCollisionContactListener().addListener((ICollisionListener) flightController); gateCircuit.addListener(new GateCircuitAdapter() { @Override public void onGatePassed(GateGoal passed) { int extraTime = generator.getExtraTime(); timeController.addBonusTime(extraTime); } }); } checkAndAddSettingFeatures(); checkAndAddUpgradeHandler(); addLevelFeatures(level); return this; } /** * Clears all feature lists, when created, else they are new created. * <p> * As they are created together it is sufficient to check only one of them. */ private void clearFeatureLists() { if (optionalFeaturesToLoad == null) { optionalFeaturesToLoad = new ArrayList<IFeatureLoad>(); optionalFeaturesToInit = new ArrayList<IFeatureInit>(); optionalFeaturesToUpdate = new ArrayList<IFeatureUpdate>(); optionalFeaturesToRender = new ArrayList<IFeatureRender>(); optionalFeaturesToDraw = new ArrayList<IFeatureDraw>(); optionalFeaturesToFinish = new ArrayList<IFeatureFinish>(); optionalFeaturesToDispose = new ArrayList<IFeatureDispose>(); } else { optionalFeaturesToLoad.clear(); optionalFeaturesToInit.clear(); optionalFeaturesToUpdate.clear(); optionalFeaturesToRender.clear(); optionalFeaturesToDraw.clear(); optionalFeaturesToFinish.clear(); optionalFeaturesToDispose.clear(); } } /** * Creates the rigid bodies for the static decoration in the level. * * @param collisionDetector * The collision detector. * @param level * The level with the list of decorations. */ private void createDecoRigidBodies(CollisionDetector collisionDetector, Level level) { Gdx.app.log("GameControllerBuilder.createDecoRigidBodies", "Setting up collision for decoration."); GameObject o; List<GameObject> gameObjects = level.components; int size = gameObjects.size(); btCollisionShape displayShape; for (int i = 0; i < size; i++) { o = gameObjects.get(i); if (!o.getId().equals(Level.BORDER_NAME)) { displayShape = collisionDetector.getShapeManager().createConvexShape(o.getModelId(), o); o.createRigidBody(o.getModelId(), displayShape, 0.0f, CollisionDetector.OBJECT_FLAG, CollisionDetector.ALL_FLAG); collisionDetector.addRigidBody(o); } } } /** * Checks the preferences if the standard features should be used and adds * them to the game controller if necessary. */ private void checkAndAddSettingFeatures() { // if needed for debugging: Debug.init(game.getSkin(), stage, 1); SettingManager settings = playerProfile.getSettingManager(); addGateIndicator(); addTimeLeftOverlay(); addScoreOverlay(); addInfoOverlays(); // do not show back button on Android, because there the softkey back // button is available if (!ApplicationType.Android.equals(Gdx.app.getType())) { addBackButtonOverlay(); } if (settings.getBoolean(SettingManager.SHOW_FPS)) { addFPSOverlay(); } if (settings.getBoolean(SettingManager.USE_TOUCH)) { addTouchScreenOverlay(); } if (settings.getBoolean(SettingManager.VIBRATE_WHEN_COLLIDE)) { CollisionDetector.getInstance().getCollisionContactListener().addListener(new ICollisionListener() { @Override public void onCollision(GameObject g1, GameObject g2) { GameObject g; if (g1 instanceof Spaceship) { g = g2; } else if (g2 instanceof Spaceship) { g = g1; } else { return; } if (g.isDummy()) { return; } Gdx.input.vibrate(500); } }); } audioManager.use(settings); addGameFinishedOverlay(); } /** * Checks the level upgrades and adds the corresponding handler to the game * controller. */ private void checkAndAddUpgradeHandler() { if (checkUpgrade(TemporarySpeedUpgrade.TYPE)) { addFeatureToLists(new TemporarySpeedUpgradeHandler()); } if (checkUpgrade("ChangeTimeUpgrade")) { addFeatureToLists(new ChangeTimeUpgradeHandler()); } if (checkUpgrade("ChangePointsUpgrade")) { addFeatureToLists(new ChangePointsUpgradeHandler()); } if (checkUpgrade("ResizeGatesUpgrade")) { addFeatureToLists(new ResizeGatesUpgradeHandler()); } if (checkUpgrade("ChangeSteeringUpgrade")) { addFeatureToLists(new ChangeSteeringUpgradeHandler()); } } /** * Checks in the upgrade list if it contains an upgrade with the specified * type. * * @param type * The type to check for. * @return true, if it contains an upgrade, false otherwise. */ private boolean checkUpgrade(String type) { int size = level.getCollectibleManager().getCollectibles().size(); for (int i = 0; i < size; i++) { if (level.getCollectibleManager().getCollectibles().get(i).getType().equals(type)) { return true; } } return false; } /** * Checks the {@link Level#levelClass} value and uses the default class * features or the features of a given class if found and invoked correctly. * * @param level * The currently used level with a specific level class if stored * in the json. */ private void addLevelFeatures(Level level) { if (level.levelClass == null || "".equals(level.levelClass) || level.levelClass.equals("DefaultClass")) { addDefaultLevel(); } else { try { Class<?> c = Class.forName("de.fau.cs.mad.fly.levels." + level.levelClass); Object obj = c.newInstance(); @SuppressWarnings("rawtypes") Class[] parameterTypes = new Class[1]; parameterTypes[0] = GameControllerBuilder.class; Method method = c.getDeclaredMethod("create", parameterTypes); method.invoke(obj, this); Gdx.app.log("Builder", level.levelClass + " used."); } catch (Exception e) { Gdx.app.log("GameController.addFeatures", "unknown level: " + level.levelClass); addDefaultLevel(); } } } /** * Adds the default level features. */ private void addDefaultLevel() { ILevel defaultLevel = new DefaultLevel(); defaultLevel.create(this); Gdx.app.log("Builder", "DefaultLevel used."); } /** * Puts the feature in the optional feature lists depending on the * interfaces it implements. * * @param feature * The feature to put in the lists. */ public void addFeatureToLists(Object feature) { if (feature instanceof IFeatureLoad) { optionalFeaturesToLoad.add((IFeatureLoad) feature); } if (feature instanceof IFeatureInit) { optionalFeaturesToInit.add((IFeatureInit) feature); } if (feature instanceof IFeatureUpdate) { optionalFeaturesToUpdate.add((IFeatureUpdate) feature); } if (feature instanceof IFeatureRender) { optionalFeaturesToRender.add((IFeatureRender) feature); } if (feature instanceof IFeatureDraw) { optionalFeaturesToDraw.add((IFeatureDraw) feature); } if (feature instanceof IFeatureFinish) { optionalFeaturesToFinish.add((IFeatureFinish) feature); } if (feature instanceof IFeatureDispose) { optionalFeaturesToDispose.add((IFeatureDispose) feature); } if (feature instanceof ICollisionListener) { CollisionDetector.getInstance().getCollisionContactListener().addListener((ICollisionListener) feature); } if (feature instanceof IntegerTimeListener) { timeController.registerIntegerTimeListener((IntegerTimeListener) feature); } if (feature instanceof GateCircuitListener) { level.getGateCircuit().addListener((GateCircuitListener) feature); } } /** * Adds a {@link GateIndicator} to the GameController, that is initialized, * updated every frame and updated, when a gate is passed. * * @return Builder instance with GateIndicator */ private GameControllerBuilder addGateIndicator() { TextureRegion region = SkinManager.getInstance().getSkin().getRegion("arrow"); //region.getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear); GateIndicator gateIndicator = new GateIndicator(region); addFeatureToLists(gateIndicator); return this; } /** * Adds a {@link TimeLeftOverlay} to the GameController. * * @return Builder instance with TimeLeftOverlay */ private GameControllerBuilder addTimeLeftOverlay() { TimeLeftOverlay timeLeftOverlay = new TimeLeftOverlay(stage, level.getLeftTime()); timeController.registerIntegerTimeListener(timeLeftOverlay); return this; } /** * Adds a {@link ScoreOverlay} to the GameController. * * @return Builder instance with ScoreOverlay */ private GameControllerBuilder addScoreOverlay() { ScoreOverlay scoreOverlay = new ScoreOverlay(stage); scoreController.registerScoreChangeListener(scoreOverlay); return this; } /** * Adds a {@link InfoOverlay} and a {@link InfoButtonOverlay} to the * GameController. * * @return Builder instance with InfoOverlay and InfoButtonOverlay */ private GameControllerBuilder addInfoOverlays() { InfoOverlay.createInfoOverlay(stage); addFeatureToLists(InfoOverlay.getInstance()); InfoButtonOverlay.createInfoButtonOverlay(stage); return this; } /** * Adds a {@link FPSOverlay} to the {@link GameController}, that is updated * every frame. * * @return Builder instance with {@link FPSOverlay} */ private GameControllerBuilder addFPSOverlay() { FPSOverlay fpsOverlay = new FPSOverlay(stage); addFeatureToLists(fpsOverlay); return this; } /** * Adds {@link BackButtonOverlay} to show a button to return to * {@link MainMenuScreen}. * * @return Builder instance with {@link BackButtonOverlay} */ private GameControllerBuilder addBackButtonOverlay() { BackButtonOverlay backButtonOverlay = new BackButtonOverlay(stage); addFeatureToLists(backButtonOverlay); return this; } /** * Adds a {@link TouchScreenOverlay} to the GameController, that is updated * every frame. * * @return Builder instance with TouchScreenOverlay */ private GameControllerBuilder addTouchScreenOverlay() { TouchScreenOverlay touchScreenOverlay = new TouchScreenOverlay(stage); addFeatureToLists(touchScreenOverlay); return this; } /** * Adds a {@link GameFinishedOverlay} to the GameController, that is * initialized, updated every frame and updated when the game is finished. * * @return Builder instance with GameFinishedOverlay */ private GameControllerBuilder addGameFinishedOverlay() { GameFinishedOverlay gameFinishedOverlay = new GameFinishedOverlay(stage); addFeatureToLists(gameFinishedOverlay); return this; } /** * Adds a {@link IPlane} to the GameController, that is initialized, updated * every frame and updated when the game is finished. * * @return Builder instance with IPlane */ private GameControllerBuilder addPlayerPlane() { IPlane plane = player.getPlane(); addFeatureToLists(plane); return this; } /** * Creates a new GameController out of your defined preferences in the other * methods before. * * @return new GameController */ public GameController build() { GameController gc = GameController.getInstance(); gc.stage = stage; gc.optionalFeaturesToLoad = optionalFeaturesToLoad; gc.optionalFeaturesToInit = optionalFeaturesToInit; gc.optionalFeaturesToUpdate = optionalFeaturesToUpdate; gc.optionalFeaturesToRender = optionalFeaturesToRender; gc.optionalFeaturesToDraw = optionalFeaturesToDraw; gc.optionalFeaturesToFinish = optionalFeaturesToFinish; gc.optionalFeaturesToDispose = optionalFeaturesToDispose; gc.gameStateListeners = gameStateListener; gc.level = level; gc.player = player; gc.flightController = flightController; gc.cameraController = cameraController; // gc.batch = new ModelBatch(); gc.batch = new ModelBatch(null, new FlyShaderProvider(), null); gc.setTimeController(timeController); gc.registerGameStateListener(timeController); gc.scoreController = scoreController; gc.setInputProcessor(new InputMultiplexer(stage, flightController, new BackProcessor())); gc.audioManager = audioManager; gc.setGameState(GameState.PAUSED); level.getGateCircuit().addListener(new GateCircuitAdapter() { @Override public void onFinished() { GameController.getInstance().setGameState(GameState.VICTORY); } }); return gc; } }