EntityFactory.java

package com.devcharles.piazzapanic.utility;

import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.HashMap;

import com.badlogic.ashley.core.Entity;
import com.badlogic.ashley.core.PooledEngine;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.CircleShape;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.World;
import com.devcharles.piazzapanic.components.*;
import com.devcharles.piazzapanic.components.FoodComponent.FoodType;
import com.devcharles.piazzapanic.utility.box2d.Box2dSteeringBody;
import com.devcharles.piazzapanic.utility.box2d.CollisionCategory;

/**
 * Factory pattern class that creates entities used in the game.
 */
public class EntityFactory {

    private final PooledEngine engine;
    private final World world;

    private FixtureDef movingFixtureDef;
    private BodyDef movingBodyDef;
    private CircleShape circleShape;

    public EntityFactory(PooledEngine engine, World world) {
        this.engine = engine;
        this.world = world;

        createDefinitions();
    }

    private static final Map<FoodType, TextureRegion> foodTextures = new HashMap<FoodType, TextureRegion>();

    /**
     * Create reusable definitions for bodies and fixtures. These can be then be
     * used while creating the bodies for entities.
     */
    private void createDefinitions() {

        // Moving bodies

        // BodyDef
        movingBodyDef = new BodyDef();

        movingBodyDef.type = BodyType.DynamicBody;
        movingBodyDef.linearDamping = 20f;
        movingBodyDef.fixedRotation = true;

        // Shape - needs to be disposed
        circleShape = new CircleShape();
        circleShape.setRadius(0.5f);

        // FixtureDef
        movingFixtureDef = new FixtureDef();
        movingFixtureDef.shape = circleShape;
        movingFixtureDef.density = 20f;
        movingFixtureDef.friction = 0.4f;
        movingFixtureDef.filter.categoryBits = CollisionCategory.ENTITY.getValue();
        movingFixtureDef.filter.maskBits = (short) (CollisionCategory.BOUNDARY.getValue()
                | CollisionCategory.ENTITY.getValue());
    }

    /**
     * Creates cook entity, and adds it to the engine.
     * 
     * @return Reference to the entity.
     */
    public Entity createCook(int x, int y) {
        Entity entity = engine.createEntity();

        B2dBodyComponent b2dBody = engine.createComponent(B2dBodyComponent.class);

        TransformComponent transform = engine.createComponent(TransformComponent.class);

        ControllableComponent controllable = engine.createComponent(ControllableComponent.class);

        TextureComponent texture = engine.createComponent(TextureComponent.class);

        AnimationComponent an = engine.createComponent(AnimationComponent.class);

        WalkingAnimationComponent animation = engine.createComponent(WalkingAnimationComponent.class);

        // PowerUpComponent powerUp = engine.createComponent(PowerUpComponent.class);

        
        controllable.currentFood.init(engine);

        animation.animator = new CookAnimator();
        // Texture
        TextureRegion[][] tempRegions = TextureRegion.split(new Texture("droplet.png"), 32, 32);

        texture.region = tempRegions[0][0];
        // TODO: Set size in viewport units instead of scale
        texture.scale.set(0.1f, 0.1f);

        // Box2D body
        BodyDef bodyDef = new BodyDef();
        bodyDef.type = BodyType.DynamicBody;
        bodyDef.linearDamping = 20f;
        bodyDef.fixedRotation = true;
        bodyDef.awake = true;

        bodyDef.position.set(x, y);

        b2dBody.body = world.createBody(bodyDef);

        // Create a circle shape and set its radius to 1
        CircleShape circle = new CircleShape();
        circle.setRadius(0.5f);
        // Create a fixture definition to apply our shape to
        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = circle;
        fixtureDef.density = 20f;
        fixtureDef.friction = 0.4f;
        fixtureDef.filter.categoryBits = CollisionCategory.ENTITY.getValue();
        fixtureDef.filter.maskBits = (short) (CollisionCategory.BOUNDARY.getValue()
                | CollisionCategory.NO_SHADOWBOUNDARY.getValue()
                | CollisionCategory.ENTITY.getValue());

        // Create our fixture and attach it to the body
        b2dBody.body.createFixture(fixtureDef).setUserData(entity);

        // BodyDef and FixtureDef don't need disposing, but shapes do.
        circle.dispose();

        entity.add(b2dBody);
        entity.add(transform);
        entity.add(controllable);
        entity.add(texture);
        entity.add(an);
        entity.add(animation);

        engine.addEntity(entity);

        return entity;
    }

    /**
     * Create the food entity at 0,0.
     * 
     * @param foodType The type of food to create.
     * @return reference to the {@link Entity}
     */
    public Entity createFood(FoodType foodType) {
        Entity entity = engine.createEntity();

        TextureComponent texture = engine.createComponent(TextureComponent.class);

        TransformComponent transform = engine.createComponent(TransformComponent.class);

        FoodComponent food = engine.createComponent(FoodComponent.class);      

        // Texture
        texture.region = getFoodTexture(foodType);
        // TODO: Set size in viewport units instead of scale
        texture.scale.set(0.05f, 0.05f);

        // food creation
        food.type = foodType;

        // add components to the entity
        entity.add(transform);
        entity.add(texture);
        entity.add(food);

        engine.addEntity(entity);

        return entity;
    }

    /**
     * Create a station entity with interactable features enabled. This does not
     * render the station as it is rendered in the tilemap.
     * 
     * @param type           Type of station to create. See
     *                       {@link Station.StationType}.
     * @param position       position vector (z is set to 0).
     * @param ingredientType (optional) if this is an Ingredient station, which
     *                       ingredient should it spawn.
     */
    public Entity createStation(Station.StationType type, Vector2 position, FoodType ingredientType, Vector2 tileOnMap, boolean locked) {
        Entity entity = engine.createEntity();

        float[] size = { 1f, 1f };

        B2dBodyComponent b2dBody = engine.createComponent(B2dBodyComponent.class);

        TextureComponent texture = engine.createComponent(TextureComponent.class);

        TransformComponent transform = engine.createComponent(TransformComponent.class);

        StationComponent station = engine.createComponent(StationComponent.class);

        if(type != Station.StationType.ingredient){
            station.tileMapPosition =tileOnMap;
        }
        station.type=type;
        station.locked=locked;

        if (type == Station.StationType.ingredient) {
            station.ingredient = ingredientType;
        }
        // Box2D body
        BodyDef bodyDef = new BodyDef();
        bodyDef.type = BodyType.StaticBody;
        bodyDef.position.set(position.x, position.y);

        b2dBody.body = world.createBody(bodyDef);

        // Create a PolygonShape and set it to be a box of 1x1
        PolygonShape stationBox = new PolygonShape();
        stationBox.setAsBox(size[0], size[1]);

        // Create our fixture and attach it to the body
        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = stationBox;
        fixtureDef.isSensor = true;
        fixtureDef.filter.categoryBits = CollisionCategory.NO_SHADOWBOUNDARY.getValue();
        fixtureDef.filter.maskBits = CollisionCategory.ENTITY.getValue();
        b2dBody.body.createFixture(fixtureDef).setUserData(station);

        // BodyDef and FixtureDef don't need disposing, but shapes do.
        stationBox.dispose();

        // add components to the entity
        entity.add(b2dBody);
        entity.add(transform);
        entity.add(texture);
        entity.add(station);

        engine.addEntity(entity);

        return entity;
    }

    /**
     * Cut the food textures, run at game initialisation.
     * 
     * @param path (optional) custom path for food textures.
     */
    public static void cutFood(String path) {
        if (path == null) {
            path = "v2/food.png";
        }

        Texture foodSheet = new Texture(path);

        TextureRegion[][] tmp = TextureRegion.split(foodSheet, 32, 32);

        int rows = tmp.length;
        int cols = tmp[0].length;

        // Flatten the array
        TextureRegion[] frames = new TextureRegion[rows * cols];
        int index = 0;
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                frames[index++] = tmp[i][j];
            }
        }

        for (int i = 1; i < 26; i++) {
            foodTextures.put(FoodType.from(i), frames[i]);
        }
    }

    /**
     * Get the texture associated with a certain food.
     * 
     * @return {@link TextureRegion} of the food.
     */
    public static TextureRegion getFoodTexture(FoodType type) {
        return foodTextures.get(type);
    }


    /**
     * Create an AI customer entity. The entity will not walk until it receives a
     * {@link com.badlogic.gdx.ai.steer.SteeringBehavior}.
     * 
     * @param position of the customer at spawn point.
     * @return reference to the entity.
     */
    public Entity createCustomer(Vector2 position) {
        Entity entity = engine.createEntity();

        B2dBodyComponent b2dBody = engine.createComponent(B2dBodyComponent.class);

        TextureComponent texture = engine.createComponent(TextureComponent.class);

        TransformComponent transform = engine.createComponent(TransformComponent.class);

        AnimationComponent an = engine.createComponent(AnimationComponent.class);

        CustomerComponent customer = engine.createComponent(CustomerComponent.class);

        WalkingAnimationComponent walkingAnimation = engine.createComponent(WalkingAnimationComponent.class);

        AIAgentComponent aiAgent = engine.createComponent(AIAgentComponent.class);

        walkingAnimation.animator = new CustomerAnimator();



        // Reuse existing body definition
        movingBodyDef.position.set(position.x, position.y);
        b2dBody.body = world.createBody(movingBodyDef);
        b2dBody.body.createFixture(movingFixtureDef).setUserData(entity);

        texture.region = new TextureRegion(new Texture("droplet.png"));
        texture.scale.set(0.1f, 0.1f);

        transform.isHidden = false;

        // Create a steering body with no behaviour (to be set later)
        aiAgent.steeringBody = new Box2dSteeringBody(b2dBody.body, true, 0.5f);

        FoodType[] s = Station.serveRecipes;
        int orderIndex = ThreadLocalRandom.current().nextInt(0, s.length);
        customer.order = FoodType.from(s[orderIndex].getValue());

        Gdx.app.log("Order received", customer.order.name());
        entity.add(b2dBody);
        entity.add(transform);
        entity.add(texture);
        entity.add(an);
        entity.add(walkingAnimation);
        entity.add(aiAgent);
        entity.add(customer);
        engine.addEntity(entity);

        return entity;
    }

    // public static TextureRegion getPowerUpTexture(PowerUpType type){
    //     return powerupTextures.get(type);
    // }

        /**
     * Cut the powerup textures, run at game initialisation.
     * 
     * @param path (optional) custom path for powerup textures.
     */
    // public static void cutPowerUp(String path) {
    //     if (path == null) {
    //         path = "v2/powerups.png";
    //     }

    //     Texture PowerUpSheet = new Texture(path);

    //     TextureRegion[][] tmp = TextureRegion.split(PowerUpSheet, 32, 32);

    //     int rows = tmp.length;
    //     int cols = tmp[0].length;

    //     // Flatten the array
    //     TextureRegion[] frames = new TextureRegion[rows * cols];
    //     int index = 0;
    //     for (int i = 0; i < rows; i++) {
    //         for (int j = 0; j < cols; j++) {
    //             frames[index++] = tmp[i][j];
    //         }
    //     }

    //     for (int i = 1; i < 6; i++) {
    //         powerupTextures.put(PowerUpType.from(i), frames[i]);
    //     }
    // }


    // public Entity createPowerup(PowerUpType powerUpType){
    //     Entity entity = engine.createEntity();

    //     PowerUpComponent powerUp = engine.createComponent(PowerUpComponent.class);

    //     entity.add(powerUp);
    //     engine.addEntity(entity);

    //     return entity;
    // }
}