cn.weaponry.impl.classic.WeaponClassic.java Source code

Java tutorial

Introduction

Here is the source code for cn.weaponry.impl.classic.WeaponClassic.java

Source

/**
 * Copyright (c) Lambda Innovation, 2013-2015
 * ??Lambda Innovation
 * http://www.li-dev.cn/
 *
 * This project is open-source, and it is distributed under  
 * the terms of GNU General Public License. You can modify
 * and distribute freely as long as you follow the license.
 * ??GNU???
 * ????
 * http://www.gnu.org/licenses/gpl.html
 */
package cn.weaponry.impl.classic;

import java.util.List;

import org.lwjgl.opengl.GL11;

import cn.liutils.util.helper.Motion3D;
import cn.liutils.util.mc.EntitySelectors;
import cn.liutils.util.raytrace.Raytrace;
import cn.weaponry.api.ItemInfo;
import cn.weaponry.api.action.Action;
import cn.weaponry.api.client.render.PartedModel;
import cn.weaponry.api.client.render.RenderInfo;
import cn.weaponry.api.client.render.RenderInfo.Animation;
import cn.weaponry.api.ctrl.KeyEventType;
import cn.weaponry.api.event.WeaponCallback;
import cn.weaponry.api.event.WpnEventLoader;
import cn.weaponry.api.item.WeaponBase;
import cn.weaponry.api.state.WeaponState;
import cn.weaponry.api.state.WeaponStateMachine;
import cn.weaponry.core.blob.SoundUtils;
import cn.weaponry.impl.classic.ammo.AmmoStrategy;
import cn.weaponry.impl.classic.ammo.ClassicAmmoStrategy;
import cn.weaponry.impl.classic.ammo.ClassicReloadStrategy;
import cn.weaponry.impl.classic.ammo.ReloadStrategy;
import cn.weaponry.impl.classic.client.animation.Muzzleflash;
import cn.weaponry.impl.classic.client.animation.Recoil;
import cn.weaponry.impl.classic.client.animation.ReloadAnimation;
import cn.weaponry.impl.classic.event.ClassicEvents.CanReload;
import cn.weaponry.impl.classic.event.ClassicEvents.CanShoot;
import cn.weaponry.impl.classic.event.ClassicEvents.ReloadEvent;
import cn.weaponry.impl.classic.event.ClassicEvents.ShootEvent;
import cn.weaponry.impl.classic.event.ClassicEvents.StartReloadEvent;
import cn.weaponry.impl.generic.action.ScreenUplift;
import cn.weaponry.impl.generic.action.SwingSilencer;
import cn.weaponry.impl.generic.entity.EntityBullet;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.Minecraft;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.DamageSource;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.MovingObjectPosition.MovingObjectType;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;

/**
 * <code>WeaponClassic</code> provides a schema for a Half-Life like or CS-like weapon. <br/>
 * It has 3 fixed states:<br/>
 *  * shooting<br/>
 *  * reloading<br/>
 *  * idle<br/>
 * and a optional state: <br/>
 *  * action.(mouse right click to switch)
 * <br/><br/>
 * State diagram is given in state classes' descriptions.
 * <br/>
 * The ammoTyped field must be initialized before use, or it will crash MC when reloading.
 * <br/>
 * WARNING: This is a data heavy class. You would probably want to use a json loader to load its instances.
 * @author WeAthFolD
 * @see cn.weaponry.impl.classic.loading
 */
public class WeaponClassic extends WeaponBase {

    //Weapon basic info
    public int maxAmmo = 30;
    public Item ammoType;

    //Shooting
    public int shootInterval = 5;
    public int shootDamage = 10; //Per bullet
    public int shootScatter = 10;
    public int shootBucks = 1; //How many bullets per shoot
    public String shootSound;
    public boolean isAutomatic = true;

    //Reloading
    public int reloadTime = 20;
    //Is the reloading state a 'reload one at a time' reloading style. (Used for stuffs like shotguns)
    public boolean isBuckReload;
    public String reloadStartSound;
    public String reloadEndSound;
    public String reloadAbortSound;

    //Misc
    public String jamSound;

    //Ammo strategies
    @LoaderExclude
    public AmmoStrategy ammoStrategy;
    @LoaderExclude
    public ReloadStrategy reloadStrategy;

    //Render data
    @SideOnly(Side.CLIENT)
    public ScreenUplift screenUplift;
    @SideOnly(Side.CLIENT)
    public Muzzleflash animMuzzleflash;
    @SideOnly(Side.CLIENT)
    public ReloadAnimation reloadAnim;
    @SideOnly(Side.CLIENT)
    public Recoil recoilAnim;

    /**
     * This ctor is used for item loader. When use this explicitly call finishInit().
     */
    public WeaponClassic() {
        ammoStrategy = new ClassicAmmoStrategy(this);
        reloadStrategy = new ClassicReloadStrategy(this);

        WpnEventLoader.load(this);

        if (FMLCommonHandler.instance().getSide() == Side.CLIENT) {
            screenUplift = new ScreenUplift();
            animMuzzleflash = new Muzzleflash();
            reloadAnim = new ReloadAnimation();
            recoilAnim = new Recoil();
        }
    }

    public WeaponClassic(Item ammoType, int maxAmmo) {
        this.ammoType = ammoType;
        this.maxAmmo = maxAmmo;
    }

    @WeaponCallback(side = Side.CLIENT)
    @SideOnly(Side.CLIENT)
    public void onShootCli(ItemInfo item, ShootEvent event) {
        item.addAction(screenUplift.copy());
        RenderInfo.get(item).addAnimation(recoilAnim.copy());
        RenderInfo.get(item).addAnimation(animMuzzleflash.copy());
    }

    @WeaponCallback(side = Side.SERVER)
    public void onShootSvr(ItemInfo info, ShootEvent event) {
        World world = info.getWorld();
        EntityPlayer player = info.getPlayer();
        //System.out.println("OnShootSvr called");
        for (int i = 0; i < shootBucks; ++i) {
            Motion3D mo = new Motion3D(player, true);
            mo.setMotionOffset(shootScatter);
            Vec3 start = mo.getPosVec(), end = mo.move(60).getPosVec();
            MovingObjectPosition trace = Raytrace.perform(world, start, end, EntitySelectors.excludeOf(player));
            if (trace != null && trace.typeOfHit == MovingObjectType.ENTITY) {
                trace.entityHit.hurtResistantTime = -1;
                trace.entityHit.attackEntityFrom(DamageSource.causePlayerDamage(player), shootDamage);
            }
        }
    }

    @WeaponCallback(side = Side.CLIENT)
    @SideOnly(Side.CLIENT)
    public void spawnClientBullet(ItemInfo info, ShootEvent event) {
        World world = info.getWorld();
        for (int i = 0; i < shootBucks; ++i) {
            Entity spawn = createBulletEffect(info);
            if (spawn != null) {
                world.spawnEntityInWorld(spawn);
            }
        }
    }

    @WeaponCallback(side = Side.CLIENT)
    @SideOnly(Side.CLIENT)
    public void onReload(ItemInfo item, StartReloadEvent event) {
        RenderInfo.get(item).addAnimation(reloadAnim.copy());
    }

    @Override
    public void onInfoStart(ItemInfo info) {
        super.onInfoStart(info);
        info.addAction(new SwingSilencer());
        info.addAction(new Action() {

            @Override
            public void onTick(int tick) {
                int shootCount = itemInfo.dataTag().getInteger("shootCount");
                if (shootCount > 0)
                    shootCount--;
                itemInfo.dataTag().setInteger("shootCount", shootCount);
            }

            @Override
            public String getName() {
                return "Miscs";
            }

        });
    }

    @SideOnly(Side.CLIENT)
    public void addInformation(ItemStack stack, EntityPlayer player, List list, boolean idk) {
        list.add(ammoStrategy.getDescription(stack));
    }

    @Override
    public void initStates(WeaponStateMachine machine) {
        machine.addState("idle", new StateIdle());
        machine.addState("reload", isBuckReload ? new StateBuckReload() : new StateReload());
        machine.addState("shoot", new StateShoot());
    }

    @SideOnly(Side.CLIENT)
    public void getSubItems(Item item, CreativeTabs cct, List list) {
        ItemStack add = new ItemStack(item, 1, 0);
        ammoStrategy.setAmmo(add, ammoStrategy.getMaxAmmo(add));
        list.add(add);
    }

    @Override
    @SideOnly(Side.CLIENT)
    public void initDefaultAnims(RenderInfo render) {
        // An idle-swing effect
        render.addAnimation(new Animation() {
            @Override
            public void render(ItemInfo info, PartedModel model, boolean firstPerson) {
                long time = Minecraft.getSystemTime();
                double dx = 0.005 * Math.sin(time / 1000.0), dy = 0.01 * Math.sin(time / 700.0),
                        dz = 0.012 * Math.sin(time / 1400.0);
                //System.out.println("Translate");
                GL11.glTranslated(dx, dy, dz);
            }

            @Override
            public boolean shouldRenderInPass(int pass) {
                return pass == 0;
            }
        });

    }

    /**
     * Create the shooting entity to be spawned. The spawn is only down in client side.
     */
    public Entity createBulletEffect(ItemInfo item) {
        return new EntityBullet(item.getPlayer(), shootScatter);
    }

    public class StateIdle extends WeaponState {
        //Idle->(Hold ML)->Shoot
        //Idle->(R)->Reload
        //Idle->(MR)->Action, if that state exists

        @Override
        public void onCtrl(int key, KeyEventType type) {
            if (key == 0 && (type == KeyEventType.DOWN || type == KeyEventType.TICK)) {
                if (ammoStrategy.canConsume(getPlayer(), getStack(), 1)) {
                    transitState("shoot");
                } else {
                    SoundUtils.playBothSideSound(getPlayer(), jamSound);
                }
            } else if (key == 1 && type == KeyEventType.DOWN) {
                if (machine.hasState("action")) {
                    transitState("action");
                }
            } else if (key == 2 && type == KeyEventType.DOWN) {
                transitState("reload");
            }
        }
    }

    public class StateReload extends WeaponState {
        //Solely a handler of ClassicReload action.
        //TODO: Wasty code?
        //Reload->(Any)->Idle

        @Override
        public void enterState() {
            WeaponClassic weapon = getWeapon();
            ReloadStrategy rs = weapon.reloadStrategy;
            if (post(getItem(), new CanReload())) {
                if (rs.canReload(getPlayer(), getStack())) {
                    post(getItem(), new StartReloadEvent());
                    SoundUtils.playBothSideSound(getPlayer(), reloadStartSound);
                } else {
                    SoundUtils.playBothSideSound(getPlayer(), reloadAbortSound);
                    transitState("idle");
                }
            } else {
                transitState("idle");
            }
        }

        @Override
        public void tickState(int tick) {
            if (tick == reloadTime) {
                if (post(getItem(), new CanReload())) {
                    post(getItem(), new ReloadEvent());
                    reloadStrategy.doReload(getPlayer(), getStack());
                    SoundUtils.playBothSideSound(getPlayer(), reloadEndSound);
                }
                transitState("idle");
            }
        }

        @Override
        public void leaveState() {
        }

        @Override
        public void onCtrl(int key, KeyEventType type) {
            if (key != 2 && type == KeyEventType.DOWN) {
                transitState("idle");
            }
        }
    }

    public class StateBuckReload extends WeaponState {

        @Override
        public void tickState(int tick) {
            if (tick % reloadTime == 0) {
                // Load one buck
                boolean quit = false;
                if (post(getItem(), new CanReload())) {
                    post(getItem(), new ReloadEvent());
                    if (ammoStrategy.getAmmo(getStack()) == ammoStrategy.getMaxAmmo(getStack())) {
                        quit = true;
                    } else {
                        if (!isRemote()) {
                            reloadStrategy.doReload(getPlayer(), getStack());
                        }
                        SoundUtils.playBothSideSound(getPlayer(), reloadEndSound);
                    }
                } else
                    quit = true;

                if (quit)
                    transitState("idle");
            }
        }
    }

    public class StateShoot extends WeaponState {
        //Shoot->(Release ML)->Idle

        @Override
        public void onCtrl(int key, KeyEventType type) {
            if (key == 0 && type == KeyEventType.UP) {
                transitState("idle");
            }
        }

        @Override
        public void enterState() {
        }

        @Override
        public void tickState(int tick) {
            int shootTick = getItem().dataTag().getInteger("shootTick");
            int time = machine.getTick();

            if (time - shootTick >= shootInterval) {
                if (tryShoot()) {
                    getItem().dataTag().setInteger("shootTick", time);
                } else {
                    transitState("idle");
                }
                if (!isAutomatic) {
                    transitState("idle");
                }
            }
        }

        private boolean tryShoot() {
            if (!post(getItem(), new CanShoot()) || !ammoStrategy.canConsume(getPlayer(), getStack(), 1)) {
                return false;
            }

            post(getItem(), new ShootEvent());
            SoundUtils.playBothSideSound(getPlayer(), shootSound);
            if (!isRemote())
                ammoStrategy.consumeAmmo(getPlayer(), getStack(), 1);

            return true;
        }
    }

}