Exit Full View

Itchy / src / main / java / uk / co / nickthecoder / drunkinvaders / Ship.java

/*******************************************************************************
 * Copyright (c) 2013 Nick Robinson All rights reserved. This program and the accompanying materials are made available under the terms of
 * the GNU Public License v3.0 which accompanies this distribution, and is available at http://www.gnu.org/licenses/gpl.html
 ******************************************************************************/
package uk.co.nickthecoder.drunkinvaders;

import java.util.ArrayList;
import java.util.List;

import uk.co.nickthecoder.itchy.Actor;
import uk.co.nickthecoder.itchy.Input;
import uk.co.nickthecoder.itchy.Role;
import uk.co.nickthecoder.itchy.extras.Fragments;
import uk.co.nickthecoder.itchy.property.DoubleProperty;
import uk.co.nickthecoder.itchy.property.Property;
import uk.co.nickthecoder.itchy.role.ExplosionBuilder;
import uk.co.nickthecoder.itchy.role.OnionSkinBuilder;
import uk.co.nickthecoder.itchy.role.TalkBuilder;

public class Ship extends Bouncy implements Shootable
{

    protected static final List<Property<Role, ?>> properties = new ArrayList<Property<Role, ?>>();

    static {
        properties.addAll( Bouncy.properties );
        properties.add(new DoubleProperty<Role>("ox").label("Planet's Center X"));
        properties.add(new DoubleProperty<Role>("oy").label("Planet's Center Y"));
        properties.add(new DoubleProperty<Role>("rotationSpeed"));
        properties.add(new DoubleProperty<Role>("shieldRechargeRate"));
        properties.add(new DoubleProperty<Role>("shieldDischargeRate"));
    }

    private static final int TIMER_DURATION = 40;

    private static final int SHIELD_POSE_COUNT = 7;

    public static final String[] DEADLY_LIST = new String[] { "deadly" };

    private Fragments fragments;

    public double ox = 320;

    public double oy = -1300;

    public double rotationSpeed = 0.003;

    public double shieldRechargeRate = 0.001;

    public double shieldDischargeRate = 0.01;

    public double radius;

    private double angle;

    private Actor latestBullet;

    private boolean shielded = false;

    private int recharge = 0;

    /**
     * 1 is fully charged 0 is fully drained.
     */
    private double shieldStrength = 1.0;

    protected Input inputLeft;

    protected Input inputRight;

    protected Input inputShield;

    protected Input inputFire;

    protected Input inputDie;

    @Override
    public List<Property<Role, ?>> getProperties()
    {
        return properties;
    }

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

        addTag("killable");
        new OnionSkinBuilder(getActor()).alpha(128).create();
        this.mass = 100000000000.0;

        this.radius = Math.sqrt((getActor().getX() - this.ox) * (getActor().getX() - this.ox)
                        + (getActor().getY() - this.oy) * (getActor().getY() - this.oy));

        this.angle = Math.atan2(getActor().getY() - this.oy, getActor().getX() - this.ox);
        getActor().getAppearance().setDirectionRadians(this.angle);
        turn(0); // calculates the direction

        // Create the fragments for the explosions when I get shot.
        fragments = new Fragments().create( getActor().getCostume() );

        this.inputLeft = Input.find("left");
        this.inputRight = Input.find("right");
        this.inputShield = Input.find("shield");
        this.inputFire = Input.find("fire");
        this.inputDie = Input.find("die");
    }

    @Override
    public void onAttach()
    {
        // Do NOT call super, as we don't want to be bouncy while the shields are off.
    }

    @Override
    public void onDetach()
    {
        super.onDetach();
    }

    @Override
    public void tick()
    {
        if (this.inputDie.pressed()) {
            this.shot(this.getActor());
            return;
        }

        if (this.inputShield.pressed()) {
            if (!this.shielded) {
                activateShield();
            }
        } else {
            if (this.shielded) {
                deactivateShield();
            }
        }

        if (this.shielded) {
            super.tick();
            dischargeShield();

        } else {

            chargeShield();

            if (this.inputLeft.pressed()) {
                this.turn(this.rotationSpeed);
            }
            if (this.inputRight.pressed()) {
                this.turn(-this.rotationSpeed);
            }

            if (this.recharge > 0) {
                this.recharge--;
                if ((this.recharge == 0) || ((this.latestBullet != null) && this.latestBullet.isDead())) {
                    this.event("charged");
                    this.recharge = 0;
                }
            } else {

                if (this.inputFire.pressed()) {
                    this.fire();
                }
            }

            getCollisionStrategy().update();

            for (Role role : collisions(DEADLY_LIST)) {
                Actor other = role.getActor();
                this.shot(other);
                if (other.getRole() instanceof Shootable) {
                    ((Shootable) other.getRole()).shot(getActor());
                }
                break;
            }

        }

    }

    private void turn(double speed)
    {
        double oldX = getActor().getX();
        double oldY = getActor().getY();
        double oldDirection = getActor().getHeading();

        this.angle += speed;
        getActor().setDirectionRadians(this.angle);
        getActor().setX(this.radius * Math.cos(this.angle) + this.ox);
        getActor().setY(this.radius * Math.sin(this.angle) + this.oy);
        if (!getActor().getAppearance().getWorldRectangle().within(DrunkInvaders.director.worldBounds)) {
            this.angle -= speed;
            getActor().moveTo(oldX, oldY);
            getActor().setDirection(oldDirection);
        }
    }

    public void activateShield()
    {
        long level = Math.round(this.shieldStrength * SHIELD_POSE_COUNT);
        if (level > 0) {
            this.event("shield");
            addTag("bouncy");
            long newLevel = Math.round(this.shieldStrength * SHIELD_POSE_COUNT);
            event("shielded" + newLevel);
            this.shielded = true;
        } else {
            this.event("shieldFailed");
        }
    }

    public void deactivateShield()
    {
        removeTag("bouncy");
        this.event("deshield");
        this.shielded = false;
    }

    private void dischargeShield()
    {
        long oldLevel = Math.round(this.shieldStrength * SHIELD_POSE_COUNT);
        this.shieldStrength -= this.shieldDischargeRate;
        long newLevel = Math.round(this.shieldStrength * SHIELD_POSE_COUNT);

        if (this.shieldStrength < 0) {
            this.deactivateShield();
            return;
        }

        if (oldLevel != newLevel) {
            event("shielded" + newLevel);
        }
    }

    private void chargeShield()
    {
        this.shieldStrength += this.shieldRechargeRate;
        if (this.shieldStrength > 1) {
            this.shieldStrength = 1;
        }
    }

    public void fire()
    {
        if (this.shielded) {
            return;
        }

        this.event("fire");

        Actor bulletActor = getActor().createCompanion("bullet");
        this.latestBullet = bulletActor;
        bulletActor.setDirection(getActor().getAppearance().getDirection());
        Bullet bullet = (Bullet) bulletActor.getRole();
        bullet.addTag("killable");
        bulletActor.moveForwards(10);

        this.recharge = TIMER_DURATION;
    }

    @Override
    public void shot(Actor other)
    {
        if (this.shielded) {
            this.event("deflect");
            return;
        }

        Actor yell = new TalkBuilder(getActor()).eventName("death").style("shout").offset(0, 60).alignment(0.5, 0)
                        .direction(0).create().getActor();
        yell.setCostume(getActor().getCostume());
        yell.deathEvent("shout");

        new ExplosionBuilder(getActor()).speed(1.5, 1.6, 0, 0).fade(2).spin(-0.4, 0.4).fragments(fragments).create();

        new ExplosionBuilder(getActor()).projectiles(40).offsetForwards(-10, 10).offsetSidewards(-10, 10)
                        .speed(1, 3, 0, 0).fade(2).eventName("pixel").create();

        deathEvent("death");
    }
}