Java tutorial
/** * 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; } } }