package ch.asynk.rustanddust.game; import java.io.StringWriter; import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.Json; import com.badlogic.gdx.utils.JsonValue; import com.badlogic.gdx.utils.JsonReader; import com.badlogic.gdx.utils.JsonWriter.OutputType; import ch.asynk.rustanddust.RustAndDust; import ch.asynk.rustanddust.engine.util.IterableQueue; import ch.asynk.rustanddust.engine.util.IterableStack; import ch.asynk.rustanddust.ui.Position; import ch.asynk.rustanddust.util.Marshal; import ch.asynk.rustanddust.game.ctrl.Solo; import ch.asynk.rustanddust.game.State.StateType; import ch.asynk.rustanddust.game.states.StateCommon; import ch.asynk.rustanddust.game.states.StateSelect; import ch.asynk.rustanddust.game.states.StateMove; import ch.asynk.rustanddust.game.states.StatePromote; import ch.asynk.rustanddust.game.states.StateEngage; import ch.asynk.rustanddust.game.states.StateAnimation; import ch.asynk.rustanddust.game.states.StateDeployment; // import ch.asynk.rustanddust.game.states.StateReinforcement; import ch.asynk.rustanddust.game.states.StateReplay; public abstract class Ctrl implements Disposable { private static final boolean debugCtrl = true; public enum MsgType { OK, CANCEL, PROMOTE, ANIMATIONS_DONE, UNIT_DOCK_SELECT, UNIT_DOCK_TOGGLE, UNIT_DEPLOYED, } public enum EventType { ORDER, ORDER_DONE, STATE_CHANGE, ANIMATED_STATE_CHANGE, REPLAY_STEP, REPLAY_DONE, TURN_DONE, // ACTION_DONE, ACTION_ABORTED, EXIT_BATTLE, } class Event { public EventType type; public Object data; @Override public String toString() { return String.format("Event : %s - %s", type, (data == null) ? "" : data); } } public final RustAndDust game; public final Battle battle; private final StringWriter writer = new StringWriter(2048); private final IterableQueue events = new IterableQueue(4); private final IterableStack freeEvents = new IterableStack(4); public Map map; public Hud hud; public boolean blockMap; public boolean blockHud; private Hex touchedHex; protected boolean synched; private int depth; private Order lastOrder; private final State selectState; private final State moveState; private final State promoteState; private final State engageState; private final State animationState; private final State deploymentState; // private final State reinforcementState; private final State replayState; private State state; private StateType stateType; private StateType stateAfterAnimation; public abstract void init(); protected abstract void actionDoneCb(); protected abstract void turnDoneCb(); public abstract void orderProcessedCb(); public static Ctrl getCtrl(final RustAndDust game) { Ctrl ctrl = null; switch(game.config.gameMode) { case SOLO: ctrl = new Solo(game); break; } return ctrl; } public Ctrl(final RustAndDust game) { game.ctrl = this; this.game = game; this.battle = game.config.battle; this.hud = new Hud(game); this.blockMap = false; this.blockHud = false; this.touchedHex = null; this.synched = false; this.depth = 0; this.lastOrder = null; this.selectState = new StateSelect(); this.moveState = new StateMove(); this.promoteState = new StatePromote(); this.engageState = new StateEngage(); this.animationState = new StateAnimation(); this.deploymentState = new StateDeployment(); // this.reinforcementState = new StateReinforcement(); this.replayState = new StateReplay(); this.stateType = StateType.LOADING; battle.init(); this.map = battle.getMap(); init(); StateCommon.set(game); hud.update(); this.state = selectState; this.stateType = StateType.WAIT_EVENT; this.stateAfterAnimation = StateType.WAIT_EVENT; if (synched) { setState(battle.getState()); this.hud.notify(battle.toString(), 2, Position.MIDDLE_CENTER, false); return; } switch(game.config.loadMode) { case REPLAY_ALL: // TODO REPLAY_ALL break; case REPLAY_LAST: map.prepareReplayLastTurn(); setState(StateType.REPLAY); break; case LOAD: map.prepareReplayLastAction(); setState(StateType.REPLAY); break; } } @Override public void dispose() { hud.dispose(); map.dispose(); battle.desinit(); events.clear(); freeEvents.clear(); } // JSON protected boolean isLoading() { return (stateType == StateType.LOADING); } protected void load(Marshal.Mode mode, String payload) { if (payload == null) return; JsonValue root = new JsonReader().parse(payload); battle.load(mode, root); } protected String unload(Marshal.Mode mode) { Json json = new Json(OutputType.json); writer.getBuffer().setLength(0); json.setWriter(writer); battle.unload(mode, json); writer.flush(); return writer.toString(); } // INPUTS public boolean drag(float x, float y, int dx, int dy) { if (!blockHud && hud.drag(x, y, dx, dy)) return true; return false; } public void touchDown(float hudX, float hudY, float mapX, float mapY) { if (!blockHud && hud.hit(hudX, hudY, inAnimation())) return; touchedHex = (blockMap ? null : map.getHexAt(mapX, mapY)); } public void touchUp(float hudX, float hudY, float mapX, float mapY) { if (!blockMap && (touchedHex != null) && (touchedHex == map.getHexAt(mapX, mapY))) state.touch(touchedHex); } // MESSAGES public void sendMsg(MsgType msgType) { sendMsg(msgType, null); } public void sendMsg(MsgType msgType, Object data) { switch(msgType) { case ANIMATIONS_DONE: animationsDone(); break; case UNIT_DOCK_TOGGLE: unitDockToggle(); break; case UNIT_DEPLOYED: deploymentState.processMsg(msgType, data); break; default: if (!this.state.processMsg(msgType, data)) RustAndDust.error(String.format("%s does not handle msg : %s %s", this.state, msgType, data)); break; } } // EVENTS public void postReplayDone() { postEvent(EventType.REPLAY_DONE); } public void postTurnDone() { postEvent(EventType.TURN_DONE); } // public void postActionDone() { postEvent(EventType.ACTION_DONE); } public void postActionAborted() { postEvent(EventType.ACTION_ABORTED); } public void post(StateType stateType) { postEvent(EventType.STATE_CHANGE, stateType); } // public void postTransitionTo(StateType stateType) // { // postEvent(EventType.ANIMATED_STATE_CHANGE, stateType); // } // public void postTransitionToDone() // { // postEvent(EventType.ANIMATED_STATE_CHANGE, StateType.WAIT_EVENT); // postEvent(EventType.ACTION_DONE); // } // public void postTransitionToAborted() // { // postEvent(EventType.ANIMATED_STATE_CHANGE, StateType.WAIT_EVENT); // postEvent(EventType.ACTION_ABORTED); // } public void postOrder(Order order) { postOrder(order, null); } public void postOrder(Order order, StateType stateType) { postEvent(EventType.ORDER, order); // FIXME maybe use postActionDone() if (order.type != Order.OrderType.END) postEvent(EventType.ANIMATED_STATE_CHANGE, StateType.WAIT_EVENT); postEvent(EventType.ORDER_DONE, stateType); } public void postEvent(EventType type) { postEvent(type, null); } public void postEvent(EventType type, Object data) { Event evt = freeEvents.pop(); if (evt == null) evt = new Event(); evt.type = type; evt.data = data; events.enqueue(evt); } public void processEvent() { if ((events.size() <= 0) || inAnimation()) return; Event evt = events.dequeue(); RustAndDust.debug(evt.toString()); switch(evt.type) { case ORDER: lastOrder = (Order) evt.data; map.execute(lastOrder); break; case ORDER_DONE: orderDone((StateType) evt.data); break; case STATE_CHANGE: setState((StateType) evt.data); break; case ANIMATED_STATE_CHANGE: stateAfterAnimation = (StateType) evt.data; setState(StateType.ANIMATION); break; case REPLAY_STEP: replayStep((boolean) evt.data); break; case REPLAY_DONE: replayDone(); break; case TURN_DONE: turnDone(); break; // case ACTION_DONE: // actionDone(); // break; case ACTION_ABORTED: abortAction(); break; case EXIT_BATTLE: exitBattle(); break; default: RustAndDust.error(String.format("Unhandled Event Type : %s %s", evt.type, evt.data)); } freeEvents.push(evt); } private boolean inAnimation() { return (this.stateType == StateType.ANIMATION); } private void animationsDone() { if (debugCtrl) RustAndDust.debug("ANIMATIONS DONE"); if (hud.dialogActive()) hud.notifyAnimationsDone(); if (stateType == StateType.ANIMATION) { sendMsg(MsgType.OK, null); StateType tmp = stateAfterAnimation; stateAfterAnimation = StateType.WAIT_EVENT; setState(tmp); } } private void replayStep(boolean burnAp) { if (debugCtrl) RustAndDust.debug("REPLAY STEP --> burn down 1 AP " + burnAp); if (burnAp) battle.getPlayer().burnDownOneAp(); map.actionDone(); hud.update(); } private void replayDone() { if (debugCtrl) RustAndDust.debug("REPLAY DONE --> check turn change"); if (battle.getPlayer().apExhausted()) postTurnDone(); else if (!battle.getPlayer().canDoSomething()) postTurnDone(); else post(battle.getState()); } private void orderDone(StateType nextState) { if (debugCtrl) RustAndDust.debug("ORDER DONE"); if (lastOrder.cost == 0) { post((nextState == null) ? battle.getState() : nextState); return; } battle.getPlayer().burnDownOneAp(); hud.notify("1 Action Point burnt"); hud.update(); if (battle.getPlayer().apExhausted()) { hud.notify("No more Action Points"); postTurnDone(); } else if (!battle.getPlayer().canDoSomething()) { hud.notify("No available Actions"); postTurnDone(); } else { post((nextState == null) ? battle.getState() : nextState); } } // private void actionDone() // { // if (debugCtrl) RustAndDust.debug("ACTION DONE"); // if (battle.actionDone()) { // hud.notify("1 Action Point burnt"); // hud.update(); // } // if (battle.getPlayer().apExhausted()) { // hud.notify("No more Action Points"); // postTurnDone(); // } else if (!battle.getPlayer().canDoSomething()) { // hud.notify("No available Actions"); // postTurnDone(); // } else { // post(battle.getState()); // } // } private void turnDone() { if (debugCtrl) RustAndDust.debug("TURN DONE"); if (battle.turnDone()) { hud.victory(battle.getPlayer(), battle.getOpponent()); // FIXME BATTLE OVER } else { if (battle.getPlayer().hasReinforcement()) hud.notify("You have reinforcement", 2, Position.MIDDLE_CENTER, true); hud.update(); if (!battle.getPlayer().canDoSomething()) { hud.notify("No available Actions"); // FIXME DOUBLE TURN DONE postTurnDone(); } else { post(battle.getState()); } } } private void abortAction() { if (debugCtrl) RustAndDust.debug("ABORT ACTION"); // sendMsg(MsgType.CANCEL); this will loop post(battle.getState()); } private void exitBattle() { if (debugCtrl) RustAndDust.debug("EXIT BATTLE"); System.err.println("FIXME exitBattle NOT IMPLEMENTED YET"); } private void unitDockToggle() { // if (this.stateType == StateType.SELECT) // post(StateType.REINFORCEMENT); // else if (this.stateType == StateType.REINFORCEMENT) { // sendMsg(MsgType.OK); // post(StateType.SELECT); // } } // private void setState(StateType nextState) { if (stateType == nextState) { RustAndDust.error(String.format("***!!!*** STATE LOOP : %d", depth)); return; } if (nextState == StateType.WAIT_EVENT) { stateType = nextState; if (debugCtrl) RustAndDust.debug("WAIT_EVENT"); return; } depth += 1; if (depth > 1) RustAndDust.error(String.format("***!!!*** STATE DEPTH : %d", depth)); // FIXME handle corner cases (is ok here ?) if (nextState == StateType.DEPLOYMENT) { if (battle.isDeploymentDone()) hud.askEndDeployment(); } // hud.playerInfo.blockEndOfTurn(nextState != StateType.SELECT); this.state = getNextState(nextState); StateType tmp = stateType; stateType = nextState; this.state.enterFrom(tmp); depth -= 1; } private State getNextState(StateType nextState) { RustAndDust.debug("Ctrl", String.format("%s -> %s : %s", stateType, nextState, battle.getPlayer())); switch(nextState) { case SELECT: return selectState; case MOVE: return moveState; case PROMOTE: return promoteState; case ENGAGE: return engageState; case ANIMATION: return animationState; case DEPLOYMENT: return deploymentState; // case REINFORCEMENT: return reinforcementState; case REPLAY: return replayState; default: RustAndDust.error(String.format("Unhandled State : %s", nextState)); } return this.state; } }