public class BurrowSystem extends EntityProcessingSystem {

    protected M<Anim> mAnim;
    protected M<Pos> mPos;
    protected M<Burrow> mBurrow;
    protected M<Tremble> mTremble;
    protected M<Scale> mScale;

    protected AssetSystem assetSystem;
    protected SmokeSystem smokeSystem;

    public BurrowSystem() {
        super(Aspect.all(Burrow.class, Pos.class));
    }

    @Override
    protected void process(Entity e) {

        Pos pos = mPos.get(e);
        Burrow burrow = mBurrow.get(e);
        Anim anim = mAnim.get(e);

        Animation animation = assetSystem.get(anim.id);

        if (Math.abs(burrow.targetPercentage - burrow.percentage) >= 0.01f) {
            float distance = Math.abs(burrow.targetPercentage - burrow.percentage);
            float delta = MathUtils.clamp(burrow.targetPercentage - burrow.percentage, -0.01f, 0.01f) * world.getDelta() * burrow.speed;
            if (distance < 0.05f) {
                burrow.percentage += delta * 0.5f;
                burrow.smokeAge += world.delta * 0.5f;
                startTrembling(e, 0.5f);
            } else {
                burrow.smokeAge += world.delta;
                burrow.percentage += delta;
                startTrembling(e, 1);
            }

            float scale = mScale.has(e) ? mScale.get(e).scale : 1;

            TextureRegion keyFrame = animation.getKeyFrame(0);
            pos.xy.y = burrow.surfaceY - (keyFrame.getRegionHeight() * scale *
                    burrow.percentage);

            if ( burrow.smokeAge > 1f/30f )
            {
                burrow.smokeAge -= 1f/30f;
                float maxWidth = keyFrame.getRegionWidth() * scale;
                // some structures are wider at the bottom. account for this with the dust.
                float surfacedWidth = Interpolation.linear.apply(maxWidth, maxWidth * burrow.topWidthPercentage, burrow.percentage);
                smokeSystem.dust(burrow.surfaceY, pos.xy.x + maxWidth/2f - surfacedWidth/2f, pos.xy.x + maxWidth/2f + surfacedWidth/2f, 5, burrow.smokeLayer);
            }


        } else {
            stopTrembling(e);
        }
    }

    private void stopTrembling(Entity e) {
        if (mTremble.has(e)) {
            mTremble.remove(e);
        }
    }

    private void startTrembling(Entity e, float intensity) {
        mTremble.create(e).intensity = intensity;
    }
} public class CheerSystem extends IteratingSystem {

    protected M<Pos> mPos;
    protected M<Cheer> mCheer;

    public CheerSystem() {
        super(Aspect.all(Cheer.class, Pos.class));
    }

    @Override
    protected void process(int e) {

        final Cheer cheer = mCheer.get(e);
        cheer.age += world.delta * cheer.intensity * 100f;
        if ( cheer.age >= 180 ) cheer.age -= 180;

        revertTremble(e);
        setTremble(e, MathUtils.cosDeg(cheer.age-90f) * (4f * cheer.intensity));
    }

    @Override
    public void removed(int e) {
        revertTremble(e);
    }

    private void setTremble(int e, float offset) {
        Cheer tremble = mCheer.get(e);
        tremble.appliedY = offset;
        mPos.get(e).xy.y += tremble.appliedY;
    }

    private void revertTremble(int e) {
        Cheer tremble = mCheer.get(e);
        mPos.get(e).xy.y -= tremble.appliedY;
        tremble.appliedY=0;
    }
} public class EndgameSystem extends IteratingSystem {

    protected M<Stockpile> mStockpile;
    protected StockpileSystem stockpileSystem;
    protected MinionSystem minionSystem;
    protected DilemmaSystem dilemmaSystem;
    ScaffoldDioramaSystem scaffoldDioramaSystem;
    protected M<EndgameReached> mEndgameReached;
    private StructureSystem structureSystem;
    private EntitySetupSystem entitySetupSystem;
    private FireballSystem fireballSystem;

    public EndgameSystem() {
        super(Aspect.all(Stockpile.class).exclude(EndgameReached.class));
    }

    @Override
    protected void process(int e) {
        checkForDeath(e);
        checkForSuperDynasty(e);
    }

    private void checkForSuperDynasty(int e) {
        Stockpile stockpile = mStockpile.get(e);
        if (stockpile.completion >= G.MAX_COMPLETION) {
            dilemmaSystem.ENDGAME(Success.SUPER);
            mEndgameReached.create(e);
        }
    }

    private static final int COMPLETION_SCORE_FACTOR = 20;
    public static final int FULL_COMPLETION_SCORE = G.MAX_COMPLETION * G.MAX_COMPLETION * COMPLETION_SCORE_FACTOR;
    public static final int HALF_COMPLETION_SCORE = (int) ((G.MAX_COMPLETION*0.75f) * (G.MAX_COMPLETION*0.75f) * COMPLETION_SCORE_FACTOR);
    public static final int QUARTER_COMPLETION_SCORE = (int) ((G.MAX_COMPLETION*0.5f) * (G.MAX_COMPLETION*0.5f) * COMPLETION_SCORE_FACTOR);
    public static final int SOME_COMPLETION_SCORE = 6 * 6 * COMPLETION_SCORE_FACTOR;
    public static final int MODERATE_COMPLETION_SCORE = 4 * 4 * COMPLETION_SCORE_FACTOR;
    public static final int TINY_COMPLETION_SCORE = 2 * 2 * COMPLETION_SCORE_FACTOR;

    public int getScore() {
        float completion = (stockpileSystem.get(StockpileSystem.Resource.COMPLETION) +
                (structureSystem.getObeliskCount()*0.5f));
        return
                (int) (((completion * completion * COMPLETION_SCORE_FACTOR) +
                                minionSystem.totalProductivity(false)) * structureSystem.decor.scoreMultiplier);
    }

    public Success getSuccess() {
        if ( getScore() >= FULL_COMPLETION_SCORE) {
            return Success.SUPER;
        } else if ( getScore() >= HALF_COMPLETION_SCORE) {
            return Success.AMAZING;
        } else if ( getScore() >= QUARTER_COMPLETION_SCORE) {
            return Success.GREAT;
        } else if ( getScore() >= SOME_COMPLETION_SCORE) {
            return Success.GOOD;
        } else if ( getScore() >= MODERATE_COMPLETION_SCORE) {
            return Success.MODERATE;
        } else if ( getScore() >= TINY_COMPLETION_SCORE) {
            return Success.TINY;
        } else{
            return Success.BAD;
        }
    }

    public enum Success {
        BAD,
        TINY,
        MODERATE,
        GOOD,
        GREAT,
        AMAZING,
        SUPER
    }

    private void checkForDeath(int e) {
        Stockpile stockpile = mStockpile.get(e);
        if (stockpile.age >= stockpile.lifespan) {
            dilemmaSystem.ENDGAME(getSuccess());
            mEndgameReached.create(e);
        }
    }

    public void setFutureScene() {
        entitySetupSystem.createSkyscrapers();
        scaffoldDioramaSystem.kill();
        fireballSystem.kill();
        minionSystem.future();
    }
} public class TrembleSystem extends IteratingSystem {

    protected M<Anim> mAnim;
    protected M<Pos> mPos;
    protected M<Tremble> mTremble;

    public TrembleSystem() {
        super(Aspect.all(Tremble.class, Pos.class));
    }

    @Override
    protected void process(int e) {

        Tremble tremble = mTremble.get(e);
        tremble.age += world.delta * tremble.intensity;

        revertTremble(e);
        setTremble(e, MathUtils.sin(tremble.age * 100f) * (1.5f * tremble.intensity));
    }

    @Override
    public void removed(int e) {
        revertTremble(e);
    }

    private void setTremble(int e, float offset) {
        Tremble tremble = mTremble.get(e);
        tremble.appliedX = offset;
        mPos.get(e).xy.x += tremble.appliedX;
    }

    private void revertTremble(int e) {
        Tremble tremble = mTremble.get(e);
        mPos.get(e).xy.x -= tremble.appliedX;
        tremble.appliedX=0;
    }
} public class DilemmaSystem extends EntityProcessingSystem {

    public static final int TEXT_ZOOM = G.ZOOM;
    public static final String DILEMMA_SHADOW_TEXT_COLOR = "9f9a9b";
    public static final String DILEMMA_SCROLL_SHADOW_COLOR = "f3b072";
    public static final int DISCIPLINE_FOLLOWUP_WAIT_TIME = 4;
    public static final int INITIAL_DISCIPLINE_WAIT_TIME = 6;
    private DilemmaLibrary dilemmaLibrary;

    public static final String DILEMMA_GROUP = "dilemma";
    public static final int ROW_HEIGHT = 16;

//    public static final String COLOR_DILEMMA = "00000080";
    public static final String COLOR_DILEMMA = "000000BB";
    public static final String COLOR_RAW_BRIGHT = "ae121f";
    public static final String COLOR_RAW_DIMMED = "86161f";
    private boolean dilemmaActive;

    private LinkedList<String> questQueue = new LinkedList<>();

    private GroupManager groupManager;
    private StockpileSystem stockpileSystem;
    private StructureSystem structureSystem;

    private M<Tint> mColor;
    private M<Pos> mPos;
    private M<Scale> mScale;
    private M<Renderable> mRenderable;
    private RiverDioramaSystem riverSystem;
    private ProgressAlgorithmSystem progressAlgorithmSystem;
    private LabelRenderSystem labelRenderSystem;
    private EndgameSystem endgameSystem;
    private FireballSystem fireballSystem;
    private MinionSystem minionSystem;

    public DilemmaSystem() {
        super(Aspect.all(Pos.class, DilemmaChoice.class));
    }

    public float createLabel(int x, int y, String color, String text, String shadowTextColor, int maxWidth) {
        Label label = new Label(text, TEXT_ZOOM);
        label.shadowColor = new Tint(shadowTextColor);
        label.maxWidth = maxWidth;
        int insertDistanceY =AssetSystem.SLAB_HEIGHT*G.ZOOM;
        DynastyEntityBuilder builder = new DynastyEntityBuilder(world)
                .with(label)
                .group(DILEMMA_GROUP)
                .pos(x, y- insertDistanceY)
                .renderable(920)
                .scale(TEXT_ZOOM)
                .tint(color);

        builder.schedule(OperationFactory.tween(new Pos(x,y -insertDistanceY ),
                new Pos(x, y), 1f, Interpolation.pow4Out ));

        builder
                .build();
        return labelRenderSystem.estimateHeight(label);
    }

    private float createOption(int x, int y, String text, ButtonListener listener, int maxWidth) {
        //createLabel(x, y, COLOR_DILEMMA, text);
        Label label = new Label(text, TEXT_ZOOM);
        label.shadowColor = new Tint(DILEMMA_SHADOW_TEXT_COLOR);
        label.maxWidth = maxWidth;
        float height = labelRenderSystem.estimateHeight(label);
        int insertDistanceY =AssetSystem.SLAB_HEIGHT*G.ZOOM;
        DynastyEntityBuilder builder = new DynastyEntityBuilder(world)
                .with(Tint.class).with(
                        new Bounds(0, (int) -height, text.length() * 8, 0),
                        new Clickable(),
                        new Button(COLOR_RAW_DIMMED, COLOR_RAW_BRIGHT, COLOR_RAW_BRIGHT, listener),
                        label
                )
                .group(DILEMMA_GROUP)
                .renderable(920)
                .pos(x, y-insertDistanceY)
                .scale(TEXT_ZOOM);

        builder.schedule(OperationFactory.tween(new Pos(x,y-insertDistanceY),
                new Pos(x, y), 1f, Interpolation.pow4Out ));
        builder.build();
        return height;
    }

    public boolean isDilemmaActive() {
        return dilemmaActive;
    }

    @Override
    protected void initialize() {
        super.initialize();
        loadDilemmas();
        questQueue.add("INTRODUCTION");
        //startDebugDilemma();
    }

    public void startDebugDilemma() {
        startDilemmaConditionally("DEBUG");
    }

    private void startDilemmaConditionally(String dilemmaId) {
        Dilemma dilemma = dilemmaLibrary.getById(dilemmaId);
        if (dilemma != null && predicatesMet(dilemma)) {
            startDilemma(dilemma);
        }
    }


    private void loadDilemmas() {
        final Json json = new Json();
        dilemmaLibrary = json.fromJson(DilemmaLibrary.class, Gdx.files.internal("dilemma.json"));
        dilemmaLibrary.assignToGroups();
    }

    private void startDilemma(String dilemmaId) {
        Dilemma dilemma = dilemmaLibrary.getById(dilemmaId);
        if (dilemma == null) {
            throw new RuntimeException("Missing dilemma logic for " + dilemmaId);
        }
        startDilemma(dilemma);
    }

    private Dilemma startDilemma(Dilemma dilemma) {
        if (!dilemmaActive) {
            int row = 0;

            // run all immediate actions.
            if (dilemma.immediate != null) {
                for (String action : dilemma.immediate) {
                    triggerAction(action);
                }
            }

            int slabX = 7 * G.ZOOM;
            int slabY = 7 * G.ZOOM;

            DilemmaActor dilemmaActor = dilemmaLibrary.getActorById(dilemma.actor);
            createBackground(slabX, slabY,
                    dilemmaActor != null ? dilemmaActor.asset : null,
                    dilemmaActor != null ? dilemmaActor.name : null,
                    dilemmaActor != null ? dilemmaActor.role : null);

            int textMarginX = 15 * G.ZOOM;
            int textMarginY = 15 * G.ZOOM;

            int optionMarginY = 5 * G.ZOOM;

            dilemmaActive = true;
            int maxLabelWidth = AssetSystem.SLAB_WIDTH * G.ZOOM - 20 * G.ZOOM;

            for (String text : dilemma.text) {
                row+=2*G.ZOOM+createLabel(slabX + textMarginX, slabY - textMarginY + AssetSystem.SLAB_HEIGHT * G.ZOOM - row, COLOR_DILEMMA, replaceTokens(text), DILEMMA_SHADOW_TEXT_COLOR, maxLabelWidth);
            }

            for (Dilemma.Choice choice : dilemma.choices) {

                // random chance of succes, if no failure options defined, always failure.
                final String[] choices = (choice.failure == null) || (MathUtils.random(0, 100) < 100 - choice.risk) ? choice.success : choice.failure;

                String choiceText = "] " + choice.label[MathUtils.random(0, choice.label.length - 1)];
                row+= 3*G.ZOOM+createOption(slabX + textMarginX, slabY - textMarginY - optionMarginY + AssetSystem.SLAB_HEIGHT * G.ZOOM - row, choiceText, new DilemmaListener(choices), maxLabelWidth);
            }
        }

        return dilemma;
    }

    private String replaceTokens(String text) {
        return text.replace("{score}", ""+endgameSystem.getScore()).replace("{rank}", ""+endgameSystem.getSuccess().name());
    }

    private void createBackground(int x, int y, String actorId, String actorName, String actorRole) {

        int insertDistanceY =AssetSystem.SLAB_HEIGHT*G.ZOOM;

        Entity slab =
                new DynastyEntityBuilder(world)
                        .pos(x, y- insertDistanceY)
                        .anim("SLAB")
                        .renderable(910)
                        .scale(G.ZOOM)
                        .group(DILEMMA_GROUP)
                        .schedule(OperationFactory.tween(new Pos(x,y - insertDistanceY), new Pos(x,y), 1f, Interpolation.pow4Out ))
                        .build();

        int actorSlabOverlap = 6;
        int actorOffsetX = 16 * G.ZOOM;
        int actorOffsetY = (AssetSystem.SLAB_HEIGHT - actorSlabOverlap) * G.ZOOM;

        if (actorId != null) {
            Entity actor =
                    new DynastyEntityBuilder(world)
                            .pos(x + actorOffsetX, y + actorOffsetY- insertDistanceY)
                            .anim(actorId)
                            .renderable(908)
                            .scale(G.ZOOM)
                            .group(DILEMMA_GROUP)
                            .schedule(OperationFactory.tween(new Pos(x + actorOffsetX, y + actorOffsetY - insertDistanceY), new Pos(x + actorOffsetX, y + actorOffsetY), 1f, Interpolation.pow4Out ))
                            .build();

            int actorVsScrollMargin = 0;
            int scrollSlabOverlap = 11;
            int scrollOffsetX = (AssetSystem.DEFAULT_ACTOR_WIDTH + actorVsScrollMargin) * G.ZOOM + actorOffsetX;
            int scrollOffsetY = (AssetSystem.SLAB_HEIGHT - scrollSlabOverlap) * G.ZOOM;

            Entity scroll =
                    new DynastyEntityBuilder(world)
                            .pos(x + scrollOffsetX, y + scrollOffsetY- insertDistanceY)
                            .anim("SCROLL")
                            .renderable(912)
                            .scale(G.ZOOM)
                            .group(DILEMMA_GROUP)
                            .schedule(OperationFactory.tween(new Pos(x + scrollOffsetX, y + scrollOffsetY - insertDistanceY),
                                    new Pos(x + scrollOffsetX, y + scrollOffsetY), 1f, Interpolation.pow4Out ))
                            .build();

            int labelHeight = 8 * G.ZOOM;
            int labelMarginX = 12 * G.ZOOM;
            int labelMarginY = 8 * G.ZOOM;
            int labelOffsetY = scrollOffsetY + AssetSystem.SCROLL_HEIGHT * G.ZOOM;


            createLabel(x + scrollOffsetX + labelMarginX, y + labelOffsetY - labelMarginY, "3e2819", actorName, DILEMMA_SCROLL_SHADOW_COLOR, G.CANVAS_WIDTH - 10);
            createLabel(x + scrollOffsetX + labelMarginX, y + labelOffsetY - labelHeight * 1 - labelMarginY, "333333", actorRole, DILEMMA_SCROLL_SHADOW_COLOR, G.CANVAS_WIDTH - 10);
        }
    }

    /**
     * Remove active dilemma from screen.
     */
    private void stopDilemma() {
        EntityUtil.safeDeleteAll(groupManager.getEntities(DILEMMA_GROUP));
        dilemmaActive = false;
    }

    float noDisciplineCooldown = INITIAL_DISCIPLINE_WAIT_TIME;

    @Override
    protected void begin() {
        super.begin();

        if ( !dilemmaActive && !progressAlgorithmSystem.isReadyToProgress() ) {

            if ( Gdx.input.isKeyPressed(Input.Keys.F6))
            {
                startDebugDilemma();
            }
            noDisciplineCooldown -= world.delta;
            if (noDisciplineCooldown <= 0) {
                noDisciplineCooldown = DISCIPLINE_FOLLOWUP_WAIT_TIME;
                if ( !questQueue.isEmpty() )
                {
                    String next = questQueue.getFirst();
                    questQueue.removeFirst();
                    startDilemma(next);
                } else {
                    randomDilemma();
                }
            }
        }
    }

    @Override
    protected void process(Entity e) {
    }

    /**
     * Spawn a dilemma, with a bias towards positive dilemmas.
     */
    public void randomDilemma() {
        randomDilemma(60);
    }

    private void randomDilemma(int positiveChance) {
        if (MathUtils.random(0, 99) < positiveChance) {
            startRandomDilemmaFromGroup("positive");
        } else {
            startRandomDilemmaFromGroup("negative");
        }
    }

    private void startRandomDilemmaFromGroup(String group) {
        List<Dilemma> dilemmas = dilemmaLibrary.getGroup(group);

        Dilemma dilemma = null;
        int count = 0;
        while (dilemma == null && count++ < 1000) {
            dilemma = dilemmas.get(MathUtils.random(0, dilemmas.size() - 1));

            if (!predicatesMet(dilemma))
                dilemma = null;

            if (dilemma != null) {
                dilemma = startDilemma(dilemma);
                // if startdilemma fails, it returns NULL and we will search again.
            }
        }
    }

    /**
     * @return {@code true} if all predicates met (or no predicates. {@code false} if failed.
     */
    private boolean predicatesMet(Dilemma dilemma) {
        if (dilemma.predicates != null) {
            for (String predicate : dilemma.predicates) {
                switch (predicate) {
                    case "RIVER_DRY":
                        return riverSystem.getState() == RiverDioramaSystem.RiverState.RIVER_NONE;
                    case "RIVER_WATER":
                        return riverSystem.getState() == RiverDioramaSystem.RiverState.RIVER_WATER;
                    case "RIVER_BLOOD":
                        return riverSystem.getState() == RiverDioramaSystem.RiverState.RIVER_BLOOD;
                    case "NO_WIFE_PYRAMID":
                        return !structureSystem.hasWifePyramid();
                    case "OBELISK":
                        return structureSystem.hasObelisk();
                    default:
                        throw new RuntimeException("Missing predicate logic.");
                }
            }
        }
        return true;
    }

    /**
     * Just closes dilemma, no action
     */
    private class CloseDilemmaListener extends ButtonListener {
        @Override
        public void run() {
            stopDilemma();
        }
    }

    public final String[] DEFAULT_ACTION = new String[]{"CLOSE"};

    private class DilemmaListener extends ButtonListener {

        private String[] actions;

        public DilemmaListener(String[] actions) {
            super();
            this.actions = (actions == null || actions.length == 0) ? DEFAULT_ACTION : actions;
        }

        @Override
        public void run() {
            super.run();

            // run all success.
            for (String action : actions) {
                triggerAction(action);
            }
        }

    }

    /**
     * pharao died
     * @param success
     */
    public void ENDGAME(EndgameSystem.Success success) {
        stopDilemma();
        startDilemma(success.name() + "_DYNASTY");
    }

    /**
     * Trigger hardcodede action indicated by string. If not exists, assume we are starting a dilemma.
     */
    private void triggerAction(String action) {
        stopDilemma();
        switch (action) {
            case "CLOSE":
                break;
            case "RESTART":
                restartGame();
                break;
            case "+AGE":
                stockpileSystem.alter(StockpileSystem.Resource.AGE, 1);
                break;
            case "+LIFESPAN":
                stockpileSystem.alter(StockpileSystem.Resource.LIFESPAN, 10);
                break;
            case "+WEALTH":
                stockpileSystem.alter(StockpileSystem.Resource.WEALTH, 1);
                break;
            case "+BUILDSPEED":
                stockpileSystem.alter(StockpileSystem.Resource.BUILDSPEED, 1);
                break;
            case "+WORKERS":
                stockpileSystem.alter(StockpileSystem.Resource.WORKERS, 1);
                break;
            case "+ELEPHANT":
                stockpileSystem.alter(StockpileSystem.Resource.ELEPHANTS, 1);
                break;
            case "+CAMELS":
                stockpileSystem.alter(StockpileSystem.Resource.CAMELS, 1);
                break;
            case "+SOLDIERS":
                stockpileSystem.alter(StockpileSystem.Resource.SOLDIERS, 1);
                break;
            case "+COMPLETION":
                stockpileSystem.alter(StockpileSystem.Resource.COMPLETION, 1);
                break;
            case "PROGRESS":
                progressAlgorithmSystem.setReadyToProgress(true);
                break;
            case "+WORSHIP":
                stockpileSystem.alter(StockpileSystem.Resource.WORSHIP, 1);
                break;
            case "-LIFESPAN":
                stockpileSystem.alter(StockpileSystem.Resource.LIFESPAN, -1);
                break;
            case "-WEALTH":
                stockpileSystem.alter(StockpileSystem.Resource.WEALTH, -1);
                break;
            case "-BUILDSPEED":
                stockpileSystem.alter(StockpileSystem.Resource.BUILDSPEED, -1);
                break;
            case "-WORKERS":
                minionSystem.killCheapestUnit();
                break;
            case "-COMPLETION":
                stockpileSystem.alter(StockpileSystem.Resource.COMPLETION, -1);
                break;
            case "-WORSHIP":
                stockpileSystem.alter(StockpileSystem.Resource.WORSHIP, -1);
                break;

            case "+WIFE_PYRAMID":
                structureSystem.createWifePyramid();
                break;
            case "+OBELISK":
                structureSystem.createObelisk();
                break;
            case "-OBELISK":
                structureSystem.destroyObelisks();
                break;
            case "PYRAMID_SANDSTONE":
                structureSystem.pyramidDecor(StructureSystem.Decor.SANDSTONE);
                break;
            case "PYRAMID_MARBLE":
                structureSystem.pyramidDecor(StructureSystem.Decor.MARBLE);
                break;
            case "PYRAMID_GRANITE":
                structureSystem.pyramidDecor(StructureSystem.Decor.GRANITE);
                break;
            case "PYRAMID_PLAID":
                structureSystem.pyramidDecor(StructureSystem.Decor.PLAID);
                break;
            case "RIVER_BLOOD":
                riverSystem.blood();
                break;
            case "RIVER_DRY":
                riverSystem.clear();
                break;
            case "RIVER_WATER":
                riverSystem.water();
                break;
            case "NEGATIVE":
                startRandomDilemmaFromGroup("negative");
                break;
            case "POSITIVE":
                startRandomDilemmaFromGroup("positive");
                break;
            case "TEST":
                startRandomDilemmaFromGroup("test");
                break;
            case "TIMETRAVEL":
                endgameSystem.setFutureScene();
                break;
            case "FIREBALL":
                fireballSystem.queueFireball();
                break;
            case "SPACESHIP":
                fireballSystem.queueSpaceship();
                break;
            case "ROCK":
                fireballSystem.queueRock();
                break;
            default:
                startDilemma(action);
                break;
        }
    }

    private static void restartGame() {
        GdxArtemisGame.getInstance().restart();
    }
} public class ProgressAlgorithmSystem extends IteratingSystem {

    public static final float COMPLETION_SCALE = 0.001f;
    protected StockpileSystem stockpileSystem;
    protected MinionSystem minionSystem;

    protected M<Stockpile> mStockpile;
    protected RiverDioramaSystem riverDioramaSystem;

    private boolean readyToProgress = false;
    public int projectedIncrease = 0;
    private boolean increaseAlert = false;
    public boolean tallying = false;
    private EntitySubscription hammerSubscription;
    public Integer score;
    private EndgameSystem endgameSystem;
    private AssetSystem assetSystem;

    public boolean isReadyToProgress() {
        return readyToProgress;
    }

    @Override
    protected void initialize() {
        super.initialize();
        hammerSubscription = world.getAspectSubscriptionManager().get(Aspect.all(Hammer.class));
    }

    float tallyingAge = 0;

    public void setReadyToProgress(boolean readyToProgress) {
        this.readyToProgress = readyToProgress;
        tallying = true;
        tallyingAge=0;
    }

    public ProgressAlgorithmSystem() {
        super(Aspect.all(Stockpile.class));
    }

    public void progress()
    {
        if ( readyToProgress && !tallying ) {
            readyToProgress=false;
            processSystem();
            assetSystem.playRandomHammer();
            assetSystem.playRandomHammer();

            System.out.println("Completion increase by " + (projectedIncrease * COMPLETION_SCALE));
            System.out.println("Alert? " + increaseAlert);

            stockpileSystem.alter(StockpileSystem.Resource.COMPLETION_PERCENTILE, projectedIncrease);
            stockpileSystem.alter(StockpileSystem.Resource.AGE, 1);
        }
    }

    // increase cost based on pyramid size.
    private int getProjectedIncrease(boolean hammer) {
        int workforceProductivity = minionSystem.totalProductivity(hammer);
        return (int) (workforceProductivity *
                getProductivityFactor(stockpileSystem.get(StockpileSystem.Resource.COMPLETION))
                * getRiverFactor()
                * getBuildspeedFactor());
    }

    private float getRiverFactor() {
        switch (riverDioramaSystem.getState())
        {
            case RIVER_BLOOD: return 0.5f;
            case RIVER_WATER: return 1.5f;
            default: return 1f;
        }
    }

    private float getBuildspeedFactor() {
        return 1 + stockpileSystem.get(StockpileSystem.Resource.BUILDSPEED) * 0.25f;
    }

    private float getProductivityFactor(int pyramidLevel) {
        return MathUtils.clamp(30 - pyramidLevel*2,2,30);
    }

    @Override
    protected void process(int entityId) {

        // progress if needed.
        Stockpile stockpile = mStockpile.get(entityId);
        while ( stockpile.completionPercentile >= 1000 )
        {
            stockpile.completionPercentile -= 1000;
            stockpileSystem.alter(StockpileSystem.Resource.COMPLETION, 1);
        }

        if ( tallying )
        {
            if ( tallyingAge == 0  ) {
                projectedIncrease = getProjectedIncrease(true);
                increaseAlert = getRiverFactor() < 1f; // anything wrong?
                score = endgameSystem.getScore();
            }
            tallyingAge += world.delta;

            if ( !isHammersLeft() )
            {
                tallying=false;
            }
        }
    }

    private boolean isHammersLeft() {
        return !hammerSubscription.getEntities().isEmpty();
    }

    public float getProgressPercentile() {
        return stockpileSystem.get(StockpileSystem.Resource.COMPLETION_PERCENTILE) * COMPLETION_SCALE;
    }

    public float getProjectedPercentile() {
        return projectedIncrease * COMPLETION_SCALE;
    }
} @Wire
public class BarRenderSystem extends DeferredEntityProcessingSystem {

    private ComponentMapper<Pos> pm;
    protected ComponentMapper<Tint> mTint;
    private ComponentMapper<Bar> mBar;

    private AbstractAssetSystem abstractAssetSystem;
    private CameraSystem cameraSystem;
    private FontManager fontManager;

    private SpriteBatch batch;
    private GlyphLayout glyphLayout;

    @Override
    protected void initialize() {
        super.initialize();
        glyphLayout = new GlyphLayout();
    }

    public BarRenderSystem(EntityProcessPrincipal principal) {
        super(Aspect.all(Pos.class, Bar.class).exclude(Invisible.class), principal);
        batch  = new SpriteBatch(1000);
    }

    @Override
    protected void begin() {
        batch.setProjectionMatrix(cameraSystem.camera.combined);
        batch.begin();
        batch.setColor(1f, 1f, 1f, 1f);
    }

    @Override
    protected void end() {
        batch.end();
    }

    @Override
    protected boolean checkProcessing() {
        return true;
    }

    /** Pixel perfect aligning. */
    private float roundToPixels(final float val) {
        // since we use camera zoom rounding to integers doesn't work properly.
        return ((int)(val * cameraSystem.zoom)) / (float)cameraSystem.zoom;
    }

    @Override
    protected void process(int entity) {

        final Bar bar = mBar.get(entity);
        final Pos pos = pm.get(entity);

        final BitmapFont font = fontManager.font;
        if ( mTint.has(entity) ) {
            final Color color = mTint.get(entity).color;
            font.setColor(color.r, color.g, color.b, color.a);
            batch.setColor(color.r, color.g, color.b, color.a);
        } else {
            font.setColor(1f,1f,1f,1f);
            batch.setColor(1f,1f,1f,1f);
        }

        font.draw(batch, bar.text, roundToPixels(pos.xy.x), roundToPixels(pos.xy.y));

        glyphLayout.setText(font,bar.text);

        final com.badlogic.gdx.graphics.g2d.Animation gdxanim = abstractAssetSystem.get(bar.animationId);
        if ( gdxanim == null) return;

        final TextureRegion frame = gdxanim.getKeyFrame(0,false);

        // make sure one bubble is always shown.
        int emptyCount = ( bar.value == 0 && bar.valueEmpty == 0 ) ? 1 : bar.valueEmpty;

        int barWidth = frame.getRegionWidth() + 1;

        if ( bar.value + bar.valueEmpty >= 10 ) barWidth -=1;
        if ( bar.value + bar.valueEmpty >= 20 ) barWidth -=1;
        if ( bar.value + bar.valueEmpty >= 30 ) barWidth -=1;
        if ( bar.value + bar.valueEmpty >= 40 ) barWidth -=1;

        if ( bar.value >= 8 )
            barWidth=10;
        if ( bar.value >= 16 )
            barWidth=6;
        if ( bar.value >= 24 )
            barWidth=3;
        for ( int i =0; i< bar.value; i++)
        {

            batch.draw(frame,
                    roundToPixels(pos.xy.x + glyphLayout.width + 5 + i * barWidth),
                    roundToPixels(pos.xy.y - glyphLayout.height - 2),
                    frame.getRegionWidth(),
                    frame.getRegionHeight());
        }

        final com.badlogic.gdx.graphics.g2d.Animation gdxanim2 = abstractAssetSystem.get(bar.animationIdEmpty);
        if ( gdxanim2 == null) return;
        final TextureRegion frame2 = gdxanim2.getKeyFrame(0,false);
        for ( int i =0; i< emptyCount; i++)
        {
            batch.draw(frame2,
                    roundToPixels(pos.xy.x + glyphLayout.width + 5 + (i+bar.value) * barWidth),
                    roundToPixels(pos.xy.y - glyphLayout.height - 2),
                    frame.getRegionWidth(),
                    frame.getRegionHeight());
        }
    }

} @Wire
public class LabelRenderSystem extends DeferredEntityProcessingSystem {

    protected ComponentMapper<Pos> mPos;
    protected ComponentMapper<Label> mLabel;
    protected ComponentMapper<Tint> mTint;

    protected CameraSystem cameraSystem;
    protected FontManager fontManager;

    protected SpriteBatch batch;
    private GlyphLayout glyphLayout;

    public LabelRenderSystem(EntityProcessPrincipal principal) {
        super(Aspect.all(Pos.class, Label.class, Renderable.class).exclude(Invisible.class), principal);
        batch = new SpriteBatch(1000);
    }

    @Override
    protected void begin() {
        batch.setProjectionMatrix(cameraSystem.camera.combined);
        batch.begin();
        batch.setColor(1f, 1f, 1f, 1f);
    }

    @Override
    protected void initialize() {
        super.initialize();
        glyphLayout = new GlyphLayout();
    }

    @Override
    protected void end() {
        batch.end();
    }

    @Override
    protected boolean checkProcessing() {
        return true;
    }

    @Override
    protected void process(int entity) {

        final Label label = mLabel.get(entity);
        final Pos pos = mPos.get(entity);

        if (label.text != null) {

            final BitmapFont font = label.scale == 3f ? fontManager.bigFont : fontManager.font;


            glyphLayout.setText(font, label.text);

            // shadow, if any.
            if (label.shadowColor != null) {
                final Color color = label.shadowColor.color;
                font.setColor(color.r, color.g, color.b, color.a);
                switch (label.align) {
                    case LEFT:
                        font.draw(batch, label.text, pos.xy.x - 2, pos.xy.y + 2, label.maxWidth, Align.topLeft, true);
                        font.draw(batch, label.text, pos.xy.x + 2, pos.xy.y - 2, label.maxWidth, Align.topLeft, true);
                        break;
                    case RIGHT:
                        font.draw(batch, label.text, pos.xy.x - glyphLayout.width + 2, pos.xy.y - 2);
                        font.draw(batch, label.text, pos.xy.x - glyphLayout.width - 2, pos.xy.y + 2);
                        break;
                }
            }

            if (mTint.has(entity)) {
                final Color color = mTint.get(entity).color;
                font.setColor(color.r, color.g, color.b, color.a);
            } else {
                font.setColor(1f, 1f, 1f, 1f);
            }
            switch (label.align) {
                case LEFT:
                    font.draw(batch, label.text, pos.xy.x, pos.xy.y, label.maxWidth, Align.topLeft, true);
                    break;
                case RIGHT:
                    font.draw(batch, label.text, pos.xy.x - glyphLayout.width, pos.xy.y);
                    break;
            }
        }
    }

    public float estimateHeight(Label label) {
        final BitmapFont font = label.scale == 3f ? fontManager.bigFont : fontManager.font;
        return new GlyphLayout(font, label.text,new Color(),label.maxWidth, Align.topLeft, true).height;
    }
} @Wire
public class ProgressRenderSystem extends DeferredEntityProcessingSystem {

    private ComponentMapper<Pos> pm;
    protected ComponentMapper<Tint> mTint;
    private ComponentMapper<Progress> mProgress;

    private AbstractAssetSystem abstractAssetSystem;
    private CameraSystem cameraSystem;
    private FontManager fontManager;

    private SpriteBatch batch;
    private GlyphLayout glyphLayout;
    private float age;
    private ProgressAlgorithmSystem progressAlgorithmSystem;
    private Entity progressButton;
    private M<Invisible> mInvisible;
    private AssetSystem assetSystem;
    private Entity buildLabel;
    private Entity scoreLabel;
    private M<Label> mLabel;


    public Entity createLabel(int x, int y, String color, String text, String shadowTextColor, int maxWidth) {
        Label label = new Label(text, TEXT_ZOOM);
        label.shadowColor = new Tint(shadowTextColor);
        label.maxWidth = maxWidth;
        int insertDistanceY =AssetSystem.SLAB_HEIGHT*G.ZOOM;
        DynastyEntityBuilder builder = new DynastyEntityBuilder(world)
                .with(label)
                .pos(x, y)
                .renderable(3010)
                .scale(TEXT_ZOOM)
                .tint(color);

        return builder
                .build();
    }

    @Override
    protected void initialize() {
        super.initialize();
        glyphLayout = new GlyphLayout();

        buildLabel =  createLabel(12 * G.ZOOM, 4 * G.ZOOM + 18 * G.ZOOM, "FFFFFFFF", "Build progress", "000000FF", 4000);
        scoreLabel = createLabel(80 * G.ZOOM, 4 * G.ZOOM + 18 * G.ZOOM, "FFFFFFFF", "Score:", "000000FF", 4000);
        progressButton = new DynastyEntityBuilder(world)
                .with(Tint.class).with(
                new Bounds(0, 0, 26*G.ZOOM, 16*G.ZOOM),
                new Clickable(),
                new Button("btn-turn-up", "btn-turn-hover", "btn-turn-down", new ButtonListener() {
                    @Override
                    public void run() {
                        progressAlgorithmSystem.progress();
                    }
                })
        )
                .group("progress")
                .anim("btn-test-up")
                .renderable(920)
                .pos(7 * G.ZOOM + AssetSystem.PROGRESS_BAR_BACKGROUND_WIDTH * G.ZOOM, 4 * G.ZOOM)
                .scale(G.ZOOM)
                .build();

    }

    public ProgressRenderSystem(EntityProcessPrincipal principal) {
        super(Aspect.all(Pos.class, Progress.class).exclude(Invisible.class), principal);
        batch = new SpriteBatch(100);
    }

    @Override
    protected void begin() {
        batch.setProjectionMatrix(cameraSystem.camera.combined);
        batch.begin();
        batch.setColor(1f, 1f, 1f, 0.7f);
    }

    @Override
    protected void end() {
        batch.end();
    }

    @Override
    protected boolean checkProcessing() {
        return true;
    }


    @Override
    protected void process(int entity) {

        age += world.delta;

        if ( progressAlgorithmSystem.isReadyToProgress() )
        {
            renderBars(entity);
        }

        if ( progressAlgorithmSystem.isReadyToProgress() && !progressAlgorithmSystem.tallying )
        {
            mInvisible.remove(progressButton);
            mInvisible.remove(buildLabel);
            if ( mInvisible.has(scoreLabel))
            {
                mLabel.get(scoreLabel).text = "score: " + progressAlgorithmSystem.score;
            }
            mInvisible.remove(scoreLabel);
        } else {
            mInvisible.create(progressButton);
            mInvisible.create(buildLabel);

            mInvisible.create(scoreLabel);
        }

    }

    private void renderBars(int entity) {
        final Progress progress = mProgress.get(entity);
        final Pos pos = pm.get(entity);

        TextureRegion barBackground = abstractAssetSystem.get("PROGRESS BAR BACKGROUND").getKeyFrame(0, false);
        TextureRegion bar = abstractAssetSystem.get("PROGRESS BAR").getKeyFrame(0, false);
        TextureRegion barPlanned = abstractAssetSystem.get("PROGRESS BAR PLANNED").getKeyFrame(0, false);
        if (progress == null) return;

        int barBackgroundX = 7 * G.ZOOM;
        int barBackgroundY = 4 * G.ZOOM;

        batch.draw(barBackground,
                barBackgroundX,
                barBackgroundY,
                barBackground.getRegionWidth() * G.ZOOM,
                barBackground.getRegionHeight() * G.ZOOM);

        int maxBarWidth = barBackground.getRegionWidth() - 12;
        float filledWidth = maxBarWidth * progress.value;

        int barX = 13 * G.ZOOM;
        int barY = barBackgroundY + 4 * G.ZOOM;

        if ( progress.value > 0 ) {

            batch.draw(bar,
                    barX,
                    barY,
                    filledWidth * G.ZOOM,
                    bar.getRegionHeight() * G.ZOOM);
        }

        if ( progress.planned > 0 && filledWidth < maxBarWidth )
        {
            int filledPlannedWidth = (int) MathUtils.clamp(maxBarWidth * progress.planned,0f,(float)(maxBarWidth-filledWidth));

            barX += filledWidth * G.ZOOM;
            batch.setColor(1f,1f,1f,0.9f + (0.1f * MathUtils.sinDeg(age * 200f)));
            batch.draw(barPlanned,
                    barX,
                    barY,
                    filledPlannedWidth * G.ZOOM,
                    barPlanned.getRegionHeight() * G.ZOOM);
            batch.setColor(1f,1f,1f,1f);
        }
    }

} public class FireballSystem extends IteratingSystem {

    public static final int MINION_LAYER = 600;
    public static final String TINT_INVISIBLE = "ffffff00";

    protected AssetSystem assetSystem;
    private M<Renderable> mRenderable;
    private M<Scale> mScale;
    private M<Pos> mPos;
    private M<Schedule> mSchedule;
    private M<Cheer> mCheer;
    private M<Invisible> mInvisible;
    private M<Minion> mMinion;
    private M<Anim> mAnim;
    private M<Physics> mPhysics;
    private EndgameSystem endgameSystem;
    private M<Tint> mColor;
    private M<Fireball> mFireball;
    private M<Angle> mAngle;
    private SmokeSystem smokeSystem;
    private M<Tremble> mTremble;
    private M<ZPos> mZPos;
    private String[] SMOKE_PARTICLE_IDS = {"dust_particle"};
    private String[] FIRE_PARTICLE_IDS = {"FIRE PARTICLE 1", "FIRE PARTICLE 2", "FIRE PARTICLE 3", "FIRE PARTICLE 4"};
    private MinionSystem minionSystem;

    public FireballSystem() {
        super(Aspect.all(Fireball.class));
    }

    @Override
    protected void initialize() {
        super.initialize();
    }

    float cooldown = 0;
    int pendingFireballs = 0;
    int pendingRocks = 0;
    int pendingSpaceships = 0;

    public void queueFireball() {
        pendingFireballs++;
    }

    public void queueRock() {
        pendingRocks++;
    }

    public void queueSpaceship() {
        pendingSpaceships++;
    }

    @Override
    protected void begin() {
        super.begin();
        cooldown -= world.delta;
        if (cooldown < 0 && (pendingFireballs > 0 || pendingRocks > 0 || pendingSpaceships > 0)) {
            cooldown = 1;
            if (pendingFireballs > 0) {
                pendingFireballs--;
                spawnFireball();
            }
            if (pendingRocks > 0) {
                pendingRocks--;
                spawnRock();
            }
            if (pendingSpaceships > 0) {
                pendingSpaceships--;
                spawnSpaceship();
            }
        }
    }

    private void spawnSpaceship() {
        spawn(MathUtils.random(0, G.CANVAS_WIDTH), (int) (G.CANVAS_HEIGHT), "SPACESHIP", "ffffffff", true, false, false);
    }

    private void spawnRock() {
        spawn(MathUtils.random(0, G.CANVAS_WIDTH), (int) (G.CANVAS_HEIGHT), "ROCK", "ffffffff", true, false, true);
    }

    private void spawnFireball() {
        spawn(MathUtils.random(0, G.CANVAS_WIDTH), (int) (G.CANVAS_HEIGHT), "FIREBALL", "ffffffaa", true, true, true);
    }

    Vector2 v2 = new Vector2();

    @Override
    protected void process(int e) {
        Physics physics = mPhysics.get(e);
        Pos pos = mPos.get(e);
        Angle angle = mAngle.get(e);
        angle.ox = AssetSystem.FIREBALL_WIDTH / 2;
        angle.oy = AssetSystem.FIREBALL_HEIGHT / 2;
        angle.rotation =
                v2.set(physics.vx, physics.vy).angle() + 90;

        Fireball fireball = mFireball.get(e);
        fireball.smokeCooldown -= world.delta;
        fireball.sparkCooldown -= world.delta;
        ZPos zPos = mZPos.get(e);

        if (fireball.smokeCooldown <= 0 && fireball.hasSmoke) {
            v2.rotateRad(90).nor().scl(20);
            fireball.smokeCooldown += 0.1f;
            smokeSystem.smoke(pos.xy.x + v2.x, pos.xy.y + v2.y, 4, 3, (int) zPos.z, zPos.layerOffset - 1, SMOKE_PARTICLE_IDS, 1f, 5f, -1, 1, false, 2f, 2f, 2f, 2f, 6f);
        }

        if (fireball.sparkCooldown <= 0 && fireball.hasSparks) {
            fireball.sparkCooldown += 0.05f;
            smokeSystem.smoke(pos.xy.x + v2.x, pos.xy.y + v2.y, 4, 3, (int) zPos.z, zPos.layerOffset - 1, FIRE_PARTICLE_IDS, 1f, 3f, -180f, 180f, true, 2f, 2f, 2f, 2f, 2f);
        }

        clampToBorders(e);

        if (zPos.height <= 5) {
            crashMaybeExplode(e);
        }
    }

    private void clampToBorders(int e) {
        Physics physics = mPhysics.get(e);
        Pos pos = mPos.get(e);
        if (pos.xy.x < 0) {
            physics.vx = Math.abs(physics.vx);
        }
        if (pos.xy.x > G.CANVAS_WIDTH - AssetSystem.FIREBALL_WIDTH * G.ZOOM) {
            physics.vx = -Math.abs(physics.vx);
        }
    }

    private void crashMaybeExplode(int e) {
        Pos pos = mPos.get(e);
        ZPos zPos = mZPos.get(e);
        Fireball fireball = mFireball.get(e);
        smokeSystem.smoke(pos.xy.x, pos.xy.y, 3, 50, (int) zPos.z, zPos.layerOffset - 1,
                fireball.hasSparks ? FIRE_PARTICLE_IDS : SMOKE_PARTICLE_IDS, 1f, 3f, -180f, 180f, true, -50f, 50f, 100f, 200f, 6f);
        if (fireball.explodes) {
            assetSystem.playSfx("catapult_impact");
            world.delete(e);
            minionSystem.explodeMinions(pos.xy.x, pos.xy.y, 10 * G.ZOOM);
        } else {
            mAngle.get(e).rotation = 0;
            mPhysics.get(e).friction = 100f;
            mTremble.create(e).intensity = 1;
            mSchedule.create(e).operation.add(OperationFactory.sequence(OperationFactory.delay(1f), OperationFactory.remove(Tremble.class)));
            mFireball.remove(e);
        }
    }

    public void spawn(int x, int y, String id, String color, boolean smoke, boolean spark, boolean explodes) {

        assetSystem.playSfx("catapult");
        new DynastyEntityBuilder(world).pos(x, y)
                .with(Angle.class)
                .ancient()
                .gravity(0, -1f)
                .velocity(MathUtils.random(-400, 400), MathUtils.random(-100, -50), 0.2f)
                .tint(color)
                .anim(id)
                .renderable(800)
                .scale(G.ZOOM)
                .z(MathUtils.random(0, 48))
                .fireball(smoke, spark, explodes)
                .build();
    }

    private Tint invis = new Tint(1f, 1f, 1f, 0f);

    public void kill() {
        IntBag actives = world.getAspectSubscriptionManager().get(Aspect.all(Ancient.class)).getEntities();
        int[] ids = actives.getData();
        for (int i = 0, s = actives.size(); s > i; i++) {
            int entity = ids[i];
            mSchedule.create(entity).operation.add(
                    OperationFactory.sequence(
                            OperationFactory.tween(new Tint(mColor.get(entity).color), invis, 0.5f),
                            OperationFactory.deleteFromWorld()
                    ));
        }
    }
} public class MinionSystem extends IteratingSystem {

    public static final int MINION_LAYER = 600;
    public static final String TINT_INVISIBLE = "ffffff00";

    protected AssetSystem assetSystem;
    private M<Renderable> mRenderable;
    private M<Scale> mScale;
    private M<Pos> mPos;
    private M<Schedule> mSchedule;
    private M<Cheer> mCheer;
    private M<Invisible> mInvisible;
    private M<Minion> mMinion;
    private M<Anim> mAnim;
    private M<Physics> mPhysics;
    private EndgameSystem endgameSystem;
    private M<Tint> mColor;
    private M<Angle> mAngle;

    public MinionSystem() {
        super(Aspect.all(Minion.class));
    }

    @Override
    protected void process(int e) {
        Minion minion = mMinion.get(e);
    }

    /**
     * How productive is the total work force?
     *
     * @param hammer
     */
    public int totalProductivity(boolean hammer) {

        int result = 0;
        float delay = 0;

        if ( hammer ) {
            assetSystem.playSfx("hammer_blop");
        }

        IntBag actives = subscription.getEntities();
        int[] ids = actives.getData();
        for (int i = 0, s = actives.size(); s > i; i++) {
            int entity = ids[i];
            Minion minion = mMinion.get(entity);
            result += minion.productivity;

            if (hammer) {
                for(int j=0;j<minion.productivity;j++) {
                    spawnHammer(entity, delay);
                    delay += 0.1;
                }
            }
        }

        return result > 1 ? result : 1;
    }

    private Tint invis = new Tint(1f, 1f, 1f, 0f);
    private void spawnHammer(int e, float delayTime) {

        Pos pos = mPos.get(e);

        Entity entity = new DynastyEntityBuilder(world)
                .with(Hammer.class)
                .pos(pos.xy.x, pos.xy.y + 8 * G.ZOOM)
                .renderable(9000)
                .anim("GO-HAMMER")
                .scale(G.ZOOM)
                .tint("FFFFFF00")
                .schedule(
                        sequence(
                                delay(delayTime),
                                tween( new Tint("FFFFFF00"), new Tint("FFFFFFFF"), 0.1f),
                                tween(
                                        new Pos(pos.xy.x, pos.xy.y + 8 * G.ZOOM),
                                        new Pos(G.CANVAS_WIDTH / 2, 10 * G.ZOOM), 2f, Interpolation.pow2In),
                                deleteFromWorld()
                                ))
                .build();
        Angle angle = mAngle.create(entity);
        angle.ox = AssetSystem.HAMMER_WIDTH / 2 * G.ZOOM;
        angle.oy = AssetSystem.HAMMER_HEIGHT / 2 * G.ZOOM;
        Physics physics = mPhysics.create(entity);
        physics.friction=0;
        physics.vr = 400;
    }

    public Entity spawn(String id, int productivity, String deathSfx) {
        System.out.println("Spawn " + id);
        Entity e = new DynastyEntityBuilder(world).with(
                new Bounds(0, 0, 0, 0),
                new Anim(id))
                .with(Pos.class, Scale.class,
                        Renderable.class, Physics.class, Gravity.class, Tint.class, ZPos.class)
                .schedule(tween(new Tint(TINT_INVISIBLE), new Tint("ffffffff"), 0.5f))
                .minion(productivity).build();
        randomizeLocation(e);
        Physics physics = mPhysics.get(e);
        physics.vy = 500;
        physics.friction = 20f;
        mColor.get(e).setHex(TINT_INVISIBLE);
        mScale.get(e).scale = G.ZOOM;
        mRenderable.get(e).layer = MINION_LAYER;
        mMinion.get(e).deathSfx = deathSfx;

        return e;
    }

    public void explodeMinions(float x, float y, int distance) {
        IntBag actives = subscription.getEntities();
        int[] ids = actives.getData();
        for (int i = 0, s = actives.size(); s > i; i++) {
            int entity = ids[i];
            Pos pos = mPos.get(entity);
            if (pos.xy.dst2(x, y) < distance * distance) {

                explode(entity);
            }
        }
    }

    private void explode(int entity) {
        mAngle.create(entity);
        Physics physics = mPhysics.create(entity);
        physics.vy = 500;
        physics.vx = MathUtils.random(-360, 360);
        physics.vr = MathUtils.random(-360, 360);
        physics.friction = 1f;

        assetSystem.playSfx(mMinion.get(entity).deathSfx);
        mSchedule.create(entity).operation.add(
                OperationFactory.sequence(
                        OperationFactory.delay(MathUtils.random(1f, 2f)),
                        OperationFactory.deleteFromWorld()
                ));
    }

    public void allCheer() {
        assetSystem.playSfx("workers_cheering");
        IntBag actives = subscription.getEntities();
        int[] ids = actives.getData();
        for (int i = 0, s = actives.size(); s > i; i++) {
            int entity = ids[i];
            mCheer.create(entity);
            mSchedule.create(entity).operation.add(
                    OperationFactory.sequence(
                            OperationFactory.delay(MathUtils.random(1f, 2f)),
                            OperationFactory.remove(Cheer.class)
                    ));
        }
    }

    public void future() {
        clear();
        spawnMultiple(endgameSystem.getSuccess().ordinal() * endgameSystem.getSuccess().ordinal() * 5, new String[]{
                "TOURIST MALE 1",
                "TOURIST MALE 2",
                "TOURIST MALE 3",
                "TOURIST MALE 4",
                "TOURIST FEMALE 1",
                "TOURIST FEMALE 2",
                "TOURIST FEMALE 3",
                "TOURIST FEMALE 4"
        }, 0, "worker_scream");

        spawnMultiple(1, "GUIDE PUPPET", 0, "worker_scream");
    }

    private void clear() {
        IntBag actives = subscription.getEntities();
        int[] ids = actives.getData();
        for (int i = 0, s = actives.size(); s > i; i++) {
            int entity = ids[i];
            mInvisible.create(entity); // invisible, cause we still want to count them towards score.
        }
    }

    private void randomizeLocation(Entity entity) {
        mPos.get(entity).xy.set(
                MathUtils.random(8 * G.ZOOM, G.CANVAS_WIDTH - 8 * G.ZOOM), 0);
    }

    public void spawnMultiple(int count, String id, int productivity, String deathSfx) {
        for (int i = 0; i < count; i++) spawn(id, productivity, deathSfx);
    }

    public void spawnMultiple(int count, String[] id, int productivity, String deathSfx) {
        for (int i = 0; i < count; i++) spawn(id[MathUtils.random(0, id.length - 1)], productivity, deathSfx);
    }

    public void killCheapestUnit() {

        int productivity = 999;
        int cheapestId=-1;

        IntBag actives = subscription.getEntities();
        int[] ids = actives.getData();
        for (int i = 0, s = actives.size(); s > i; i++) {
            int entity = ids[i];
            Minion minion = mMinion.get(entity);
            if ( minion.productivity < productivity && !mSchedule.has(entity) )
            {
                productivity = minion.productivity;
                cheapestId = entity;
            }
        }

        if ( cheapestId != -1 )
        {
            explode(cheapestId);
        }
    }
} public class StockpileSystem extends EntityProcessingSystem {

    protected ComponentMapper<Stockpile> mStockpile;
    private Stockpile Stockpile;
    private TagManager tagManager;

    public Stockpile getStockpile() {
        final Entity entity = tagManager.getEntity("dynasty");
        if (entity != null) {
            return mStockpile.get(entity);
        }
        return null;
    }

    public enum Resource {
        AGE(1),
        LIFESPAN(1),
        WEALTH(1),
        BUILDSPEED(1),
        WORKERS(1),
        ELEPHANTS(1),
        CAMELS(1),
        SOLDIERS(1),
        COMPLETION(1),
        COMPLETION_PERCENTILE(50),
        WORSHIP(1);

        private int barScale;

        Resource(int barScale) {
            this.barScale = barScale;
        }

        public int getBarScale() {
            return barScale;
        }
    }

    public StockpileSystem() {
        super(Aspect.all(net.mostlyoriginal.game.component.resource.Stockpile.class));
    }

    /**
     * inc/dec resource by amount.
     */
    public void alter(Resource resource, int amount) {
        final Stockpile stockpile = getStockpile();
        if (stockpile != null) {
            switch (resource) {
                case AGE:
                    stockpile.age = MathUtils.clamp(stockpile.age + amount, 0,100);
                    break;
                case LIFESPAN:
                    stockpile.lifespan = MathUtils.clamp(stockpile.lifespan + amount, 0,100);
                    break;
                case BUILDSPEED:
                    stockpile.buildspeed = MathUtils.clamp(stockpile.buildspeed + amount, 0,100);
                    break;
                case WORKERS:
                    stockpile.workers = MathUtils.clamp(stockpile.workers + amount, 0,1000);
                    break;
                case ELEPHANTS:
                    stockpile.elephants = MathUtils.clamp(stockpile.elephants + amount, 0,1000);
                    break;
                case CAMELS:
                    stockpile.camels = MathUtils.clamp(stockpile.camels + amount, 0,1000);
                    break;
                case SOLDIERS:
                    stockpile.soldiers = MathUtils.clamp(stockpile.soldiers + amount, 0,1000);
                    break;
                case WEALTH:
                    stockpile.wealth = MathUtils.clamp(stockpile.wealth + amount, 0,100);
                    break;
                case COMPLETION:
                    stockpile.completion = MathUtils.clamp(stockpile.completion + amount, 0,100);
                    break;
                case COMPLETION_PERCENTILE:
                    stockpile.completionPercentile = MathUtils.clamp(stockpile.completionPercentile + amount, 0,2000);
                    break;
                case WORSHIP:
                    stockpile.worship = MathUtils.clamp(stockpile.worship + amount, 0,100);
                    break;
            }
        }
    }

    /**
     * get resource amount.
     */
    public int get(Resource resource) {
        final Stockpile stockpile = getStockpile();
        if (stockpile != null) {
            switch (resource) {
                case AGE:
                    return stockpile.age;
                case LIFESPAN:
                    return stockpile.lifespan;
                case BUILDSPEED:
                    return stockpile.buildspeed;
                case WORKERS:
                    return stockpile.workers;
                case CAMELS:
                    return stockpile.camels;
                case ELEPHANTS:
                    return stockpile.elephants;
                case SOLDIERS:
                    return stockpile.soldiers;
                case WEALTH:
                    return stockpile.wealth;
                case COMPLETION:
                    return stockpile.completion;
                case COMPLETION_PERCENTILE:
                    return stockpile.completionPercentile;
                case WORSHIP:
                    return stockpile.worship;
            }
        }
        return 0;
    }

    @Override
    protected void initialize() {
        super.initialize();
    }

    @Override
    protected void process(Entity e) {
        Stockpile stockpile = mStockpile.get(e);
    }
} public class ZPosSystem extends IteratingSystem {
    private M<Pos> mPos;
    private M<ZPos> mZPos;
    private M<Renderable> mRenderable;

    public ZPosSystem() {
        super(Aspect.all(Renderable.class, ZPos.class, Pos.class));
    }

    @Override
    protected void process(int e) {

        // min-y based on z.
        ZPos zPos = mZPos.get(e);
        Pos pos = mPos.get(e);
        float minY = G.CANVAS_HEIGHT / 2 - zPos.z / 2;
        if ( pos.xy.y < minY ) {
            pos.xy.y = minY;
        }

        zPos.height = pos.xy.y - minY;

        mRenderable.get(e).layer = MINION_LAYER + (int)zPos.z * 5 + zPos.layerOffset;

    }
}
import com.artemis.World;
import com.artemis.WorldConfigurationBuilder;
import com.artemis.managers.GroupManager;
import com.artemis.managers.TagManager;
import com.badlogic.gdx.graphics.Color;
import net.mostlyoriginal.api.plugin.extendedcomponentmapper.ExtendedComponentMapperPlugin;
import net.mostlyoriginal.api.screen.core.WorldScreen;
import net.mostlyoriginal.api.system.camera.CameraShakeSystem;
import net.mostlyoriginal.api.system.camera.CameraSystem;
import net.mostlyoriginal.api.system.camera.EntityCameraSystem;
import net.mostlyoriginal.api.system.graphics.ColorAnimationSystem;
import net.mostlyoriginal.api.system.graphics.RenderBatchingSystem;
import net.mostlyoriginal.api.system.mouse.MouseCursorSystem;
import net.mostlyoriginal.api.system.physics.CollisionSystem;
import net.mostlyoriginal.api.system.physics.GravitySystem;
import net.mostlyoriginal.api.system.physics.PhysicsSystem;
import net.mostlyoriginal.api.system.render.AnimRenderSystem;
import net.mostlyoriginal.api.system.render.ClearScreenSystem;
import net.mostlyoriginal.api.system.script.EntitySpawnerSystem;
import net.mostlyoriginal.game.manager.*;
import net.mostlyoriginal.game.system.agent.BurrowSystem;
import net.mostlyoriginal.game.system.agent.CheerSystem;
import net.mostlyoriginal.game.system.endgame.EndgameSystem;
import net.mostlyoriginal.game.system.agent.TrembleSystem;
import net.mostlyoriginal.game.system.dilemma.DilemmaSystem;
import net.mostlyoriginal.game.system.logic.ProgressAlgorithmSystem;
import net.mostlyoriginal.game.system.render.BarRenderSystem;
import net.mostlyoriginal.game.system.render.LabelRenderSystem;
import net.mostlyoriginal.game.system.render.ProgressRenderSystem;
import net.mostlyoriginal.game.system.resource.FireballSystem;
import net.mostlyoriginal.game.system.resource.MinionSystem;
import net.mostlyoriginal.game.system.resource.StockpileSystem;
import net.mostlyoriginal.game.system.resource.ZPosSystem;
import net.mostlyoriginal.game.system.ui.*;
import net.mostlyoriginal.plugin.OperationsPlugin;
package net.mostlyoriginal.game.screen;


/**
 * Example main game screen.
 *
 * @author Daan van Yperen
 */
public class GameScreen extends WorldScreen {

	public static final String BACKGROUND_COLOR_HEX = "969291";

	@Override
	protected World createWorld() {
		RenderBatchingSystem renderBatchingSystem;
		return new World(new WorldConfigurationBuilder()
				.dependsOn(OperationsPlugin.class)
                .dependsOn(ExtendedComponentMapperPlugin.class)
				.dependsOn(OperationsPlugin.class)
				.with(
                        new GroupManager(),
						new TagManager(),
						new FontManager(),
						new StructureSystem(),
						new SmokeSystem(),

						new CameraSystem(1),
						new AssetSystem(),
						new ClearScreenSystem(Color.valueOf(BACKGROUND_COLOR_HEX)),

						new StockpileSystem(),
						new EntitySetupSystem(),

						new DilemmaSystem(),

						new EntitySpawnerSystem(),

						new MouseCursorSystem(),
						new MouseClickSystem(),
						new ButtonSystem(),

						new FireballSystem(),

						new BurrowSystem(),

						new CollisionSystem(),
						new PhysicsSystem(),
						new GravitySystem(),
						new ZPosSystem(),

						new TrembleSystem(),
						new CheerSystem(),

						new ProgressAlgorithmSystem(),

//						new StockpileUISystem(),
						new ProgressUISystem(),

						new DioramaSystem(),
						new SunDioramaSystem(),
						new RiverDioramaSystem(),
                        new ScaffoldDioramaSystem(),

						new EndgameSystem(),

                        new MinionSystem(),

                        new ColorAnimationSystem(),

						new EntityCameraSystem(),
						new CameraShakeSystem(),

						// Replace with your own systems!
						renderBatchingSystem = new RenderBatchingSystem(),
						new AnimRenderSystem(renderBatchingSystem),
						new LabelRenderSystem(renderBatchingSystem),
						new BarRenderSystem(renderBatchingSystem),
						new ProgressRenderSystem(renderBatchingSystem)