Last active
August 4, 2019 12:26
-
-
Save coldblade2000/59ba2acf7420bb201df7862fea303bf0 to your computer and use it in GitHub Desktop.
Focus on shootEach for EntityDrivable.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.flansmod.common.driveables; | |
import java.util.ArrayList; | |
import java.util.List; | |
import com.flansmod.common.network.PacketShotData; | |
import io.netty.buffer.ByteBuf; | |
import net.minecraft.block.Block; | |
import net.minecraft.block.material.Material; | |
import net.minecraft.block.state.IBlockState; | |
import net.minecraft.client.Minecraft; | |
import net.minecraft.entity.Entity; | |
import net.minecraft.entity.EntityLivingBase; | |
import net.minecraft.entity.item.EntityItem; | |
import net.minecraft.entity.player.EntityPlayer; | |
import net.minecraft.entity.player.EntityPlayerMP; | |
import net.minecraft.init.Items; | |
import net.minecraft.item.Item; | |
import net.minecraft.item.ItemStack; | |
import net.minecraft.nbt.NBTTagCompound; | |
import net.minecraft.util.DamageSource; | |
import net.minecraft.util.EnumHand; | |
import net.minecraft.util.EnumParticleTypes; | |
import net.minecraft.util.NonNullList; | |
import net.minecraft.util.math.AxisAlignedBB; | |
import net.minecraft.util.math.BlockPos; | |
import net.minecraft.util.math.MathHelper; | |
import net.minecraft.util.math.RayTraceResult; | |
import net.minecraft.util.math.RayTraceResult.Type; | |
import net.minecraft.util.math.Vec3d; | |
import net.minecraft.util.text.TextComponentString; | |
import net.minecraft.world.GameType; | |
import net.minecraft.world.World; | |
import net.minecraft.world.WorldServer; | |
import net.minecraftforge.common.ForgeHooks; | |
import net.minecraftforge.fml.common.network.ByteBufUtils; | |
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; | |
import net.minecraftforge.fml.relauncher.Side; | |
import net.minecraftforge.fml.relauncher.SideOnly; | |
import com.flansmod.api.IControllable; | |
import com.flansmod.api.IExplodeable; | |
import com.flansmod.client.EntityCamera; | |
import com.flansmod.client.FlansModClient; | |
import com.flansmod.client.debug.EntityDebugDot; | |
import com.flansmod.client.debug.EntityDebugVector; | |
import com.flansmod.common.FlansMod; | |
import com.flansmod.common.RotatedAxes; | |
import com.flansmod.common.driveables.DriveableType.ParticleEmitter; | |
import com.flansmod.common.guns.BulletType; | |
import com.flansmod.common.guns.EntityShootable; | |
import com.flansmod.common.guns.EnumFireMode; | |
import com.flansmod.common.guns.GunType; | |
import com.flansmod.common.guns.InventoryHelper; | |
import com.flansmod.common.guns.ItemBullet; | |
import com.flansmod.common.guns.ItemGun; | |
import com.flansmod.common.guns.ItemShootable; | |
import com.flansmod.common.guns.ShootableType; | |
import com.flansmod.common.guns.ShotData; | |
import com.flansmod.common.guns.ShotData.InstantShotData; | |
import com.flansmod.common.guns.ShotData.SpawnEntityShotData; | |
import com.flansmod.common.guns.raytracing.FlansModRaytracer; | |
import com.flansmod.common.guns.raytracing.FlansModRaytracer.BulletHit; | |
import com.flansmod.common.guns.raytracing.FlansModRaytracer.DriveableHit; | |
import com.flansmod.common.network.PacketDriveableDamage; | |
import com.flansmod.common.network.PacketDriveableKeyHeld; | |
import com.flansmod.common.network.PacketPlaySound; | |
import com.flansmod.common.parts.EnumPartCategory; | |
import com.flansmod.common.parts.ItemPart; | |
import com.flansmod.common.parts.PartType; | |
import com.flansmod.common.teams.TeamsManager; | |
import com.flansmod.common.vector.Vector3f; | |
import static com.flansmod.common.util.BlockUtil.destroyBlock; | |
//import cofh.api.energy.IEnergyContainerItem; | |
public abstract class EntityDriveable extends Entity implements IControllable, IExplodeable, IEntityAdditionalSpawnData | |
{ | |
public boolean syncFromServer = true; | |
/** | |
* Ticks since last server update. Use to smoothly transition to new position | |
*/ | |
public int serverPositionTransitionTicker; | |
/** | |
* Server side position, as synced by PacketVehicleControl packets | |
*/ | |
public double serverPosX, serverPosY, serverPosZ; | |
/** | |
* Server side rotation, as synced by PacketVehicleControl packets | |
*/ | |
public double serverYaw, serverPitch, serverRoll; | |
/** | |
* The driveable data which contains the inventory, the engine and the fuel | |
*/ | |
public DriveableData driveableData; | |
/** | |
* The shortName of the driveable type, used to obtain said type | |
*/ | |
public String driveableType; | |
/** | |
* The throttle, in the range -1, 1 is multiplied by the maxThrottle (or maxNegativeThrottle) from the plane type to obtain the thrust | |
*/ | |
public float throttle; | |
/** | |
* The wheels on this plane | |
*/ | |
public EntityWheel[] wheels; | |
public boolean fuelling; | |
/** | |
* Extra prevRoation field for smoothness in all 3 rotational axes | |
*/ | |
public float prevRotationRoll; | |
/** | |
* Angular velocity | |
*/ | |
public Vector3f angularVelocity = new Vector3f(0F, 0F, 0F); | |
/** | |
* Whether each mouse button is held | |
*/ | |
public boolean leftMouseHeld = false, rightMouseHeld = false; | |
/** | |
* Shoot delay variables | |
*/ | |
public float shootDelayPrimary, shootDelaySecondary; | |
/** | |
* Minigun speed variables | |
*/ | |
public float minigunSpeedPrimary, minigunSpeedSecondary; | |
/** | |
* Current gun variables for alternating weapons. | |
*/ | |
public int currentGunPrimary, currentGunSecondary; | |
/** | |
* Angle of harvester aesthetic piece | |
*/ | |
public float harvesterAngle; | |
public RotatedAxes prevAxes; | |
public RotatedAxes axes; | |
private EntitySeat[] seats; | |
/** | |
* Until this is true, just look for seat and wheel connections | |
*/ | |
protected boolean readyForUpdates = false; | |
private float yOffset; | |
@SideOnly(Side.CLIENT) | |
public EntityLivingBase camera; | |
private int[] emitterTimers; | |
public int animCount = 0; | |
public int animFrame = 0; | |
public EntityDriveable(World world) | |
{ | |
super(world); | |
axes = new RotatedAxes(); | |
prevAxes = new RotatedAxes(); | |
preventEntitySpawning = true; | |
setSize(1F, 1F); | |
yOffset = 6F / 16F; | |
ignoreFrustumCheck = true; | |
} | |
public EntityDriveable(World world, DriveableType t, DriveableData d) | |
{ | |
this(world); | |
driveableType = t.shortName; | |
driveableData = d; | |
} | |
protected void initType(DriveableType type, boolean firstSpawn, boolean clientSide) | |
{ | |
seats = new EntitySeat[type.numPassengers + 1]; | |
wheels = new EntityWheel[type.wheelPositions.length]; | |
if(!clientSide && firstSpawn) | |
{ | |
for(int i = 0; i < type.numPassengers + 1; i++) | |
{ | |
seats[i] = new EntitySeat(world, this, i); | |
world.spawnEntity(seats[i]); | |
seats[i].startRiding(this); | |
} | |
for(int i = 0; i < wheels.length; i++) | |
{ | |
wheels[i] = new EntityWheel(world, this, i); | |
world.spawnEntity(wheels[i]); | |
wheels[i].startRiding(this); | |
} | |
} | |
stepHeight = type.wheelStepHeight; | |
yOffset = type.yOffset; | |
emitterTimers = new int[type.emitters.size()]; | |
for(int i = 0; i < type.emitters.size(); i++) | |
{ | |
emitterTimers[i] = rand.nextInt(type.emitters.get(i).emitRate); | |
} | |
//Register Plane to Radar on Spawning | |
//if(type.onRadar == true) | |
// RadarRegistry.register(this); | |
} | |
@SideOnly(Side.CLIENT) | |
@Override | |
public boolean isInRangeToRender3d(double x, double y, double z) | |
{ | |
double dX = this.posX - x; | |
double dY = this.posY - y; | |
double dZ = this.posZ - z; | |
double distSq = dX * dX + dY * dY + dZ * dZ; | |
double maxDist = 200.0D * getRenderDistanceWeight(); | |
return distSq < maxDist * maxDist; | |
} | |
@Override | |
protected void writeEntityToNBT(NBTTagCompound tag) | |
{ | |
driveableData.writeToNBT(tag); | |
tag.setString("Type", driveableType); | |
tag.setFloat("RotationYaw", axes.getYaw()); | |
tag.setFloat("RotationPitch", axes.getPitch()); | |
tag.setFloat("RotationRoll", axes.getRoll()); | |
} | |
@Override | |
protected void readEntityFromNBT(NBTTagCompound tag) | |
{ | |
driveableType = tag.getString("Type"); | |
driveableData = new DriveableData(tag); | |
initType(DriveableType.getDriveable(driveableType), false, false); | |
prevRotationYaw = tag.getFloat("RotationYaw"); | |
prevRotationPitch = tag.getFloat("RotationPitch"); | |
prevRotationRoll = tag.getFloat("RotationRoll"); | |
axes = new RotatedAxes(prevRotationYaw, prevRotationPitch, prevRotationRoll); | |
} | |
@Override | |
public void writeSpawnData(ByteBuf data) | |
{ | |
ByteBufUtils.writeUTF8String(data, driveableType); | |
NBTTagCompound tag = new NBTTagCompound(); | |
driveableData.writeToNBT(tag); | |
ByteBufUtils.writeTag(data, tag); | |
data.writeFloat(axes.getYaw()); | |
data.writeFloat(axes.getPitch()); | |
data.writeFloat(axes.getRoll()); | |
//Write damage | |
for(EnumDriveablePart ep : EnumDriveablePart.values()) | |
{ | |
DriveablePart part = getDriveableData().parts.get(ep); | |
data.writeShort((short)part.health); | |
data.writeBoolean(part.onFire); | |
} | |
} | |
@Override | |
public void readSpawnData(ByteBuf data) | |
{ | |
try | |
{ | |
driveableType = ByteBufUtils.readUTF8String(data); | |
driveableData = new DriveableData(ByteBufUtils.readTag(data)); | |
initType(getDriveableType(), false, true); | |
axes.setAngles(data.readFloat(), data.readFloat(), data.readFloat()); | |
prevRotationYaw = axes.getYaw(); | |
prevRotationPitch = axes.getPitch(); | |
prevRotationRoll = axes.getRoll(); | |
//Read damage | |
for(EnumDriveablePart ep : EnumDriveablePart.values()) | |
{ | |
DriveablePart part = getDriveableData().parts.get(ep); | |
part.health = data.readShort(); | |
part.onFire = data.readBoolean(); | |
} | |
} | |
catch(Exception e) | |
{ | |
FlansMod.log.error("Failed to retreive plane type from server."); | |
super.setDead(); | |
FlansMod.log.throwing(e); | |
} | |
camera = new EntityCamera(world, this); | |
world.spawnEntity(camera); | |
} | |
/** | |
* Called with the movement of the mouse. Used in controlling vehicles if need be. | |
* | |
* @param deltaY | |
* @param deltaX | |
* @return if mouse movement was handled. | |
*/ | |
@Override | |
public abstract void onMouseMoved(int deltaX, int deltaY); | |
@Override | |
@SideOnly(Side.CLIENT) | |
public EntityLivingBase getCamera() | |
{ | |
return camera; | |
} | |
protected boolean canSit(int seat) | |
{ | |
return getDriveableType().numPassengers >= seat && seats[seat].getControllingPassenger() == null; | |
} | |
@Override | |
protected boolean canTriggerWalking() | |
{ | |
return false; | |
} | |
@Override | |
protected void entityInit() | |
{ | |
} | |
@Override | |
public AxisAlignedBB getCollisionBox(Entity entity) | |
{ | |
return null;//entity.boundingBox; | |
} | |
@Override | |
public boolean canBePushed() | |
{ | |
return false; | |
} | |
@Override | |
public double getMountedYOffset() | |
{ | |
return -0.3D; | |
} | |
@Override | |
public double getYOffset() | |
{ | |
return yOffset; | |
} | |
@Override | |
/** Pass generic damage to the core */ | |
public boolean attackEntityFrom(DamageSource damagesource, float i) | |
{ | |
return world.isRemote || isDead || attackPart(EnumDriveablePart.core, damagesource, i); | |
} | |
@Override | |
public void setDead() | |
{ | |
super.setDead(); | |
//Unregister to Radar | |
//RadarRegistry.unregister(this); | |
if(world.isRemote) | |
camera.setDead(); | |
for(EntitySeat seat : seats) | |
{ | |
if(seat != null) | |
seat.reallySetDead(); | |
} | |
for(EntityWheel wheel : wheels) | |
{ | |
if(wheel != null) | |
wheel.reallySetDead(); | |
} | |
} | |
@Override | |
public void onCollideWithPlayer(EntityPlayer par1EntityPlayer) | |
{ | |
//Do nothing. Like a boss. | |
// TODO: perhaps send the player flying?? | |
//Sounds good. ^ | |
} | |
@SideOnly(Side.CLIENT) | |
private void ReportVehicleError() | |
{ | |
FlansMod.log.warn("Vehicle error in " + this); | |
FlansModClient.numVehicleExceptions++; | |
} | |
@Override | |
public boolean canBeCollidedWith() | |
{ | |
return !isDead; | |
} | |
@Override | |
public void applyEntityCollision(Entity entity) | |
{ | |
if(!isPartOfThis(entity)) | |
super.applyEntityCollision(entity); | |
} | |
@Override | |
public void setPositionAndRotationDirect(double d, double d1, double d2, float f, float f1, int i, boolean b) | |
{ | |
if(ticksExisted > 1) | |
return; | |
if(!(getControllingPassenger() instanceof EntityPlayer) || !FlansMod.proxy.isThePlayer((EntityPlayer)getControllingPassenger())) | |
{ | |
if(syncFromServer) | |
{ | |
serverPositionTransitionTicker = i + 5; | |
} | |
else | |
{ | |
double var10 = d - posX; | |
double var12 = d1 - posY; | |
double var14 = d2 - posZ; | |
double var16 = var10 * var10 + var12 * var12 + var14 * var14; | |
if(var16 <= 1.0D) | |
{ | |
return; | |
} | |
serverPositionTransitionTicker = 3; | |
} | |
serverPosX = d; | |
serverPosY = d1; | |
serverPosZ = d2; | |
serverYaw = f; | |
serverPitch = f1; | |
} | |
} | |
public void setPositionRotationAndMotion(double x, double y, double z, float yaw, float pitch, float roll, double motX, double motY, double motZ, float velYaw, float velPitch, float velRoll, float throt, float steeringYaw) | |
{ | |
if(world.isRemote) | |
{ | |
serverPosX = x; | |
serverPosY = y; | |
serverPosZ = z; | |
serverYaw = yaw; | |
serverPitch = pitch; | |
serverRoll = roll; | |
serverPositionTransitionTicker = 5; | |
} | |
else | |
{ | |
setPosition(x, y, z); | |
prevRotationYaw = yaw; | |
prevRotationPitch = pitch; | |
prevRotationRoll = roll; | |
setRotation(yaw, pitch, roll); | |
} | |
//Set the motions regardless of side. | |
motionX = motX; | |
motionY = motY; | |
motionZ = motZ; | |
angularVelocity = new Vector3f(velYaw, velPitch, velRoll); | |
throttle = throt; | |
} | |
@Override | |
public void setVelocity(double d, double d1, double d2) | |
{ | |
motionX = d; | |
motionY = d1; | |
motionZ = d2; | |
} | |
@Override | |
public boolean pressKey(int key, EntityPlayer player) | |
{ | |
if(!world.isRemote && key == 9 && getDriveableType().modePrimary == EnumFireMode.SEMIAUTO) //Primary | |
{ | |
shoot(false); | |
return true; | |
} | |
else if(!world.isRemote && key == 8 && getDriveableType().modeSecondary == EnumFireMode.SEMIAUTO) //Secondary | |
{ | |
shoot(true); | |
return true; | |
} | |
return false; | |
} | |
@Override | |
public void updateKeyHeldState(int key, boolean held) | |
{ | |
if(world.isRemote) | |
{ | |
FlansMod.getPacketHandler().sendToServer(new PacketDriveableKeyHeld(key, held)); | |
} | |
switch(key) | |
{ | |
case 9: leftMouseHeld = held; | |
break; | |
case 8: rightMouseHeld = held; | |
break; | |
} | |
} | |
/** | |
* Shoot method called by pressing / holding shoot buttons | |
*/ | |
public void shoot(boolean secondary) | |
{ | |
DriveableType type = getDriveableType(); | |
if(seats[0] == null || !(seats[0].getControllingPassenger() instanceof EntityLivingBase)) | |
return; | |
//Check shoot delay | |
while(getShootDelay(secondary) <= 0.0f) | |
{ | |
//We can shoot, so grab the available shoot points and the weaponType | |
ArrayList<DriveablePosition> shootPoints = type.shootPoints(secondary); | |
EnumWeaponType weaponType = type.weaponType(secondary); | |
//If there are no shoot points, return | |
if(shootPoints.isEmpty()) | |
return; | |
//For alternating guns, move on to the next one | |
int currentGun = getCurrentGun(secondary); | |
if(type.alternate(secondary)) | |
{ | |
currentGun = (currentGun + 1) % shootPoints.size(); | |
setCurrentGun(currentGun, secondary); | |
shootEach(type, shootPoints.get(currentGun), currentGun, secondary, weaponType); | |
} | |
else for(int i = 0; i < shootPoints.size(); i++) | |
shootEach(type, shootPoints.get(i), i, secondary, weaponType); | |
} | |
} | |
private boolean driverIsCreative() | |
{ | |
return seats != null && seats[0] != null && seats[0].getControllingPassenger() instanceof EntityPlayer && ((EntityPlayer)seats[0].getControllingPassenger()).capabilities.isCreativeMode; | |
} | |
private EntityPlayer getDriver() | |
{ | |
if(seats != null && seats[0] != null && seats[0].getControllingPassenger() instanceof EntityPlayer) | |
return ((EntityPlayer)seats[0].getControllingPassenger()); | |
else return null; | |
} | |
private void shootEach(DriveableType type, DriveablePosition shootPoint, int currentGun, boolean secondary, EnumWeaponType weaponType) | |
{ | |
//Rotate the gun vector to global axes | |
Vector3f gunVec = getOrigin(shootPoint); | |
Vector3f globalGunVec = Vector3f.add(new Vector3f(posX, posY, posZ), gunVec, null); //Added a separate global gunV ec, and replaced all mentions of gunVec to globalGunVec | |
Vector3f lookVector = getLookVector(shootPoint); | |
//If its a pilot gun, then it is using a gun type, so do the following | |
if(shootPoint instanceof PilotGun) | |
{ | |
PilotGun pilotGun = (PilotGun)shootPoint; | |
//Get the gun from the plane type and the ammo from the data | |
GunType gunType = pilotGun.type; | |
ItemStack shootableStack = driveableData.ammo[getDriveableType().numPassengerGunners + currentGun]; | |
EntityPlayer driver = getDriver(); | |
if(shootableStack == null || !(shootableStack.getItem() instanceof ItemShootable)) | |
{ | |
shootDelayPrimary = shootDelaySecondary = 1; | |
return; | |
} | |
// For each | |
ItemShootable shootableItem = (ItemShootable)shootableStack.getItem(); | |
ShootableType shootableType = shootableItem.type; | |
float spread = 0.005f * gunType.bulletSpread * shootableType.bulletSpread; | |
lookVector.x += (float)world.rand.nextGaussian() * spread; | |
lookVector.y += (float)world.rand.nextGaussian() * spread; | |
lookVector.z += (float)world.rand.nextGaussian() * spread; | |
lookVector.scale(500.0f); | |
// Instant bullets. Do a raytrace | |
if(gunType.bulletSpeed == 0.0f) | |
{ | |
for(int i = 0; i < gunType.numBullets * shootableType.numBullets; i++) | |
{ | |
List<BulletHit> hits = FlansModRaytracer.Raytrace(world, driver, false, null, globalGunVec, lookVector, 0); | |
Entity victim = null; | |
//Sound to make sure this runs | |
// PacketPlaySound.sendSoundPacket(posX, posY, posZ, FlansMod.soundRange, dimension, type.shootSound(secondary), false); | |
Vector3f hitPos = Vector3f.add(globalGunVec, lookVector, null); | |
BulletHit firstHit = null; | |
if(!hits.isEmpty()) | |
{ | |
firstHit = hits.get(0); | |
hitPos = Vector3f.add(globalGunVec, (Vector3f)lookVector.scale(firstHit.intersectTime), null); | |
//debug chat message to get the location of impact | |
Minecraft.getMinecraft().player.sendMessage(new TextComponentString(" X:"+hitPos.x+" Y:"+hitPos.y+" Z:"+hitPos.z)); | |
victim = firstHit.GetEntity(); | |
if(victim != null){ | |
//Name any entity that is hit. Though I get expected results where the entity I aim at is mentioned, they are not affected in game (no damage, no knockback, etc) | |
Minecraft.getMinecraft().player.sendMessage(new TextComponentString(victim.getName())); | |
} | |
}else{ | |
Minecraft.getMinecraft().player.sendMessage(new TextComponentString("No hits")); | |
} | |
if(FlansMod.DEBUG) //Removed &&world.isRemote to ensure the line is run, but I could never get it to work | |
{ | |
world.spawnEntity(new EntityDebugDot(world, globalGunVec, 100, 1.0f, 1.0f, 1.0f)); | |
} | |
if(driver != null) | |
{ | |
ShotData shotData = new InstantShotData(-1, EnumHand.MAIN_HAND, type, shootableType, driver, globalGunVec, firstHit, hitPos, gunType.damage, i < gunType.numBullets * shootableType.numBullets - 1, false); | |
FlansMod.getPacketHandler().sendToServer(new PacketShotData(shotData)); //I tried using this to get shots to register, I haven't noticed if it made any difference | |
//There was no gunstack passed in this method before, which would cause a NullPointerException. I guessed with sending shootablestack, I'm not sure if it was right | |
((ItemGun)gunType.item).ServerHandleDrivableShotData(shootableStack, -1, world, this, this.getDriver(), false, shotData); | |
} | |
} | |
} | |
// Else, spawn an entity | |
else | |
{ | |
if(driver != null) | |
{ | |
ShotData shotData = new SpawnEntityShotData(-1, EnumHand.MAIN_HAND, type, shootableType, driver, lookVector); | |
((ItemGun)gunType.item).ServerHandleShotData(null, -1, world, this, false, shotData); | |
} | |
} | |
//Reset the shoot delay | |
setShootDelay(type.shootDelay(secondary), secondary); | |
} | |
else //One of the other modes | |
{ | |
switch(weaponType) | |
{ | |
case BOMB: | |
{ | |
if(TeamsManager.bombsEnabled) | |
{ | |
int slot = -1; | |
for(int i = driveableData.getBombInventoryStart(); i < driveableData.getBombInventoryStart() + type.numBombSlots; i++) | |
{ | |
ItemStack bomb = driveableData.getStackInSlot(i); | |
if(bomb != null && bomb.getItem() instanceof ItemBullet && type.isValidAmmo(((ItemBullet)bomb.getItem()).type, weaponType)) | |
{ | |
slot = i; | |
} | |
} | |
if(slot != -1) | |
{ | |
int spread = 0; | |
int damageMultiplier = secondary ? type.damageModifierSecondary : type.damageModifierPrimary; | |
float shellSpeed = 0F; | |
ItemStack bulletStack = driveableData.getStackInSlot(slot); | |
ItemBullet bulletItem = (ItemBullet)bulletStack.getItem(); | |
EntityShootable bulletEntity = bulletItem.getEntity(world, | |
new Vec3d(posX + gunVec.x, posY + gunVec.y, posZ + gunVec.z), | |
axes.getYaw(), | |
axes.getPitch(), | |
motionX, | |
motionY, | |
motionZ, | |
(EntityLivingBase)seats[0].getControllingPassenger(), | |
damageMultiplier, | |
type); | |
world.spawnEntity(bulletEntity); | |
if(type.shootSound(secondary) != null) | |
PacketPlaySound.sendSoundPacket(posX, posY, posZ, FlansMod.soundRange, dimension, type.shootSound(secondary), false); | |
if(!driverIsCreative()) | |
{ | |
bulletStack.setItemDamage(bulletStack.getItemDamage() + 1); | |
if(bulletStack.getItemDamage() == bulletStack.getMaxDamage()) | |
{ | |
bulletStack.setItemDamage(0); | |
bulletStack.setCount(bulletStack.getCount() - 1); | |
if(bulletStack.getCount() == 0) | |
bulletStack = ItemStack.EMPTY.copy(); | |
} | |
driveableData.setInventorySlotContents(slot, bulletStack); | |
} | |
setShootDelay(type.shootDelay(secondary), secondary); | |
} | |
else | |
{ | |
shootDelayPrimary = shootDelaySecondary = 1; | |
} | |
} | |
break; | |
} | |
case MISSILE: //These two are actually almost identical | |
case SHELL: | |
{ | |
if(TeamsManager.shellsEnabled) | |
{ | |
int slot = -1; | |
for(int i = driveableData.getMissileInventoryStart(); i < driveableData.getMissileInventoryStart() + type.numMissileSlots; i++) | |
{ | |
ItemStack shell = driveableData.getStackInSlot(i); | |
if(shell != null && shell.getItem() instanceof ItemBullet && type.isValidAmmo(((ItemBullet)shell.getItem()).type, weaponType)) | |
{ | |
slot = i; | |
} | |
} | |
if(slot != -1) | |
{ | |
int spread = 0; | |
int damageMultiplier = secondary ? type.damageModifierSecondary : type.damageModifierPrimary; | |
float shellSpeed = 3F; | |
ItemStack bulletStack = driveableData.getStackInSlot(slot); | |
ItemBullet bulletItem = (ItemBullet)bulletStack.getItem(); | |
EntityShootable bulletEntity = bulletItem.getEntity(world, | |
Vector3f.add(new Vector3f(posX, posY, posZ), gunVec, null), | |
lookVector, | |
(EntityLivingBase)seats[0].getControllingPassenger(), | |
spread, | |
damageMultiplier, | |
shellSpeed, | |
type); | |
world.spawnEntity(bulletEntity); | |
if(type.shootSound(secondary) != null) | |
PacketPlaySound.sendSoundPacket(posX, posY, posZ, FlansMod.soundRange, dimension, type.shootSound(secondary), false); | |
if(!driverIsCreative()) | |
{ | |
bulletStack.setItemDamage(bulletStack.getItemDamage() + 1); | |
if(bulletStack.getItemDamage() == bulletStack.getMaxDamage()) | |
{ | |
bulletStack.setItemDamage(0); | |
bulletStack.setCount(bulletStack.getCount() - 1); | |
if(bulletStack.getCount() == 0) | |
bulletStack = ItemStack.EMPTY.copy(); | |
} | |
driveableData.setInventorySlotContents(slot, bulletStack); | |
} | |
setShootDelay(type.shootDelay(secondary), secondary); | |
} | |
else | |
{ | |
shootDelayPrimary = shootDelaySecondary = 1; | |
} | |
} | |
break; | |
} | |
case GUN: //Handled above | |
break; | |
case MINE: | |
break; | |
case NONE: | |
break; | |
} | |
} | |
} | |
public Vector3f getOrigin(DriveablePosition dp) | |
{ | |
//Rotate the gun vector to global axes | |
Vector3f localGunVec = new Vector3f(dp.position); | |
if(dp.part == EnumDriveablePart.turret) | |
{ | |
//Untranslate by the turret origin, to get the rotation about the right point | |
Vector3f.sub(localGunVec, getDriveableType().turretOrigin, localGunVec); | |
//Rotate by the turret angles | |
localGunVec = seats[0].looking.findLocalVectorGlobally(localGunVec); | |
//Translate by the turret origin | |
Vector3f.add(localGunVec, getDriveableType().turretOrigin, localGunVec); | |
} | |
return rotate(localGunVec); | |
} | |
public Vector3f getLookVector(DriveablePosition dp) | |
{ | |
return axes.getXAxis(); | |
} | |
@Override | |
public void onUpdate() | |
{ | |
super.onUpdate(); | |
DriveableType type = getDriveableType(); | |
// Do a full check of our passengers for wheels or seats | |
for(Entity passenger : getPassengers()) | |
{ | |
if(passenger instanceof EntitySeat) | |
{ | |
EntitySeat seat = (EntitySeat)passenger; | |
if(seat.getExpectedSeatID() >= 0 && seats[seat.getExpectedSeatID()] == null) | |
{ | |
seats[seat.getExpectedSeatID()] = seat; | |
} | |
} | |
else if(passenger instanceof EntityWheel) | |
{ | |
EntityWheel wheel = (EntityWheel)passenger; | |
if(wheel.getExpectedWheelID() >= 0 && wheels[wheel.getExpectedWheelID()] == null) | |
{ | |
wheels[wheel.getExpectedWheelID()] = wheel; | |
} | |
} | |
else | |
{ | |
FlansMod.log.warn("Entity " + passenger + " is riding a driveable core entity."); | |
} | |
} | |
readyForUpdates = true; | |
for(int i = 0; i < type.numPassengers; i++) | |
{ | |
if(seats[i] == null) | |
{ | |
readyForUpdates = false; | |
} | |
} | |
for(int i = 0; i < type.wheelPositions.length; i++) | |
{ | |
if(wheels[i] == null) | |
{ | |
readyForUpdates = false; | |
} | |
} | |
if(!readyForUpdates) | |
{ | |
if(!world.isRemote) | |
{ | |
// Well heck, if it's bork, let's make new ones | |
initType(type, true, false); | |
} | |
// If we end up stuck like this on a client, handle updates from server | |
if(world.isRemote) | |
{ | |
//The driveable is currently moving towards its server position. Continue doing so. | |
if(serverPositionTransitionTicker > 0) | |
{ | |
double x = posX + (serverPosX - posX) / serverPositionTransitionTicker; | |
double y = posY + (serverPosY - posY) / serverPositionTransitionTicker; | |
double z = posZ + (serverPosZ - posZ) / serverPositionTransitionTicker; | |
double dYaw = MathHelper.wrapDegrees(serverYaw - axes.getYaw()); | |
double dPitch = MathHelper.wrapDegrees(serverPitch - axes.getPitch()); | |
double dRoll = MathHelper.wrapDegrees(serverRoll - axes.getRoll()); | |
rotationYaw = (float)(axes.getYaw() + dYaw / serverPositionTransitionTicker); | |
rotationPitch = (float)(axes.getPitch() + dPitch / serverPositionTransitionTicker); | |
float rotationRoll = (float)(axes.getRoll() + dRoll / serverPositionTransitionTicker); | |
--serverPositionTransitionTicker; | |
setPosition(x, y, z); | |
setRotation(rotationYaw, rotationPitch, rotationRoll); | |
//return; | |
} | |
//If the driveable is at its server position and does not have the next update, it should just simulate itself as a server side driveable would, so continue | |
} | |
return; | |
} | |
//Harvest stuff | |
//Aesthetics | |
if(hasEnoughFuel()) | |
{ | |
harvesterAngle += throttle / 5F; | |
} | |
//Actual harvesting | |
if(type.harvestBlocks && type.health.get(EnumDriveablePart.harvester) != null) | |
{ | |
CollisionBox box = type.health.get(EnumDriveablePart.harvester); | |
for(float x = box.x; x <= box.x + box.w; x++) | |
{ | |
for(float y = box.y; y <= box.y + box.h; y++) | |
{ | |
for(float z = box.z; z <= box.z + box.d; z++) | |
{ | |
Vector3f v = axes.findLocalVectorGlobally(new Vector3f(x, y, z)); | |
int blockX = (int)Math.round(posX + v.x); | |
int blockY = (int)Math.round(posY + v.y); | |
int blockZ = (int)Math.round(posZ + v.z); | |
IBlockState block = world.getBlockState(new BlockPos(blockX, blockY, blockZ)); | |
boolean cancelled = false; | |
if(seats[0] != null && seats[0].getControllingPassenger() instanceof EntityPlayerMP) | |
{ | |
int eventOutcome = ForgeHooks.onBlockBreakEvent(world, | |
((EntityPlayerMP)seats[0].getControllingPassenger()).capabilities.isCreativeMode ? GameType.CREATIVE : ((EntityPlayerMP)seats[0].getControllingPassenger()).capabilities.allowEdit ? GameType.SURVIVAL : GameType.ADVENTURE, | |
(EntityPlayerMP)seats[0].getControllingPassenger(), new BlockPos(blockX, blockY, blockZ)); | |
cancelled = eventOutcome == -1; | |
} | |
if(!cancelled) | |
{ | |
if(type.materialsHarvested.contains(block.getMaterial()) && block.getBlockHardness(world, new BlockPos(blockX, blockY, blockZ)) >= 0F) | |
{ | |
//Add the itemstack to mecha inventory | |
NonNullList<ItemStack> stacks = NonNullList.create(); | |
block.getBlock().getDrops(stacks, world, new BlockPos(blockX, blockY, blockZ), world.getBlockState(new BlockPos(blockX, blockY, blockZ)), 0); | |
for(ItemStack stack : stacks) | |
{ | |
if(!InventoryHelper.addItemStackToInventory(driveableData, stack, driverIsCreative()) && !world.isRemote && world.getGameRules().getBoolean("doTileDrops")) | |
{ | |
world.spawnEntity(new EntityItem(world, blockX + 0.5F, blockY + 0.5F, blockZ + 0.5F, stack)); | |
} | |
} | |
//Destroy block | |
if(!world.isRemote) | |
{ | |
WorldServer worldServer = (WorldServer)world; | |
BlockPos pos = new BlockPos(blockX, blockY, blockZ); | |
destroyBlock(worldServer, pos, getDriver(), false); | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
for(DriveablePart part : getDriveableData().parts.values()) | |
{ | |
if(part.box != null) | |
{ | |
part.update(this); | |
//Client side particles | |
if(world.isRemote) | |
{ | |
if(part.onFire) | |
{ | |
//Pick a random position within the bounding box and spawn a flame there | |
Vector3f pos = axes.findLocalVectorGlobally(new Vector3f(part.box.x + rand.nextFloat() * part.box.w, part.box.y + rand.nextFloat() * part.box.h, part.box.z + rand.nextFloat() * part.box.d)); | |
world.spawnParticle(EnumParticleTypes.FLAME, posX + pos.x, posY + pos.y, posZ + pos.z, 0, 0, 0); | |
} | |
if(part.health > 0 && part.health < part.maxHealth / 2) | |
{ | |
//Pick a random position within the bounding box and spawn a flame there | |
Vector3f pos = axes.findLocalVectorGlobally(new Vector3f(part.box.x + rand.nextFloat() * part.box.w, part.box.y + rand.nextFloat() * part.box.h, part.box.z + rand.nextFloat() * part.box.d)); | |
world.spawnParticle(part.health < part.maxHealth / 4 ? EnumParticleTypes.SMOKE_LARGE : EnumParticleTypes.SMOKE_NORMAL, posX + pos.x, posY + pos.y, posZ + pos.z, 0, 0, 0); | |
} | |
} | |
//Server side fire handling | |
if(part.onFire) | |
{ | |
//Rain can put out fire | |
if(world.isRaining() && rand.nextInt(40) == 0) | |
part.onFire = false; | |
//Also water blocks | |
//Get the centre point of the part | |
Vector3f pos = axes.findLocalVectorGlobally(new Vector3f(part.box.x + part.box.w / 2F, part.box.y + part.box.h / 2F, part.box.z + part.box.d / 2F)); | |
if(world.getBlockState(new BlockPos(MathHelper.floor(posX + pos.x), MathHelper.floor(posY + pos.y), MathHelper.floor(posZ + pos.z))).getMaterial() == Material.WATER) | |
{ | |
part.onFire = false; | |
} | |
} | |
else | |
{ | |
Vector3f pos = axes.findLocalVectorGlobally(new Vector3f(part.box.x / 16F + part.box.w / 32F, part.box.y / 16F + part.box.h / 32F, part.box.z / 16F + part.box.d / 32F)); | |
if(world.getBlockState(new BlockPos(MathHelper.floor(posX + pos.x), MathHelper.floor(posY + pos.y), MathHelper.floor(posZ + pos.z))).getMaterial() == Material.LAVA) | |
{ | |
part.onFire = true; | |
} | |
} | |
} | |
} | |
for(int i = 0; i < type.emitters.size(); i++) | |
{ | |
ParticleEmitter emitter = type.emitters.get(i); | |
emitterTimers[i]--; | |
boolean canEmit = false; | |
DriveablePart part = getDriveableData().parts.get(EnumDriveablePart.getPart(emitter.part)); | |
float healthPercentage = (float)part.health / (float)part.maxHealth; | |
if(isPartIntact(EnumDriveablePart.getPart(emitter.part)) && healthPercentage >= emitter.minHealth && healthPercentage <= emitter.maxHealth) | |
{ | |
canEmit = true; | |
} | |
if(emitterTimers[i] <= 0) | |
{ | |
if(throttle >= emitter.minThrottle && throttle <= emitter.maxThrottle && canEmit) | |
{ | |
//Emit! | |
Vector3f velocity = new Vector3f(0, 0, 0); | |
Vector3f pos = new Vector3f(0, 0, 0); | |
if(seats != null && seats[0] != null) | |
{ | |
if(EnumDriveablePart.getPart(emitter.part) != EnumDriveablePart.turret && EnumDriveablePart.getPart(emitter.part) != EnumDriveablePart.head) | |
{ | |
Vector3f localPosition = new Vector3f(emitter.origin.x + rand.nextFloat() * emitter.extents.x - emitter.extents.x * 0.5f, | |
emitter.origin.y + rand.nextFloat() * emitter.extents.y - emitter.extents.y * 0.5f, | |
emitter.origin.z + rand.nextFloat() * emitter.extents.z - emitter.extents.z * 0.5f); | |
pos = axes.findLocalVectorGlobally(localPosition); | |
velocity = axes.findLocalVectorGlobally(emitter.velocity); | |
} | |
else if(EnumDriveablePart.getPart(emitter.part) == EnumDriveablePart.turret || EnumDriveablePart.getPart(emitter.part) != EnumDriveablePart.head) | |
{ | |
Vector3f localPosition2 = new Vector3f(emitter.origin.x + rand.nextFloat() * emitter.extents.x - emitter.extents.x * 0.5f, | |
emitter.origin.y + rand.nextFloat() * emitter.extents.y - emitter.extents.y * 0.5f, | |
emitter.origin.z + rand.nextFloat() * emitter.extents.z - emitter.extents.z * 0.5f); | |
RotatedAxes yawOnlyLooking = new RotatedAxes(seats[0].looking.getYaw() + axes.getYaw(), axes.getPitch(), axes.getRoll()); | |
pos = yawOnlyLooking.findLocalVectorGlobally(localPosition2); | |
velocity = yawOnlyLooking.findLocalVectorGlobally(emitter.velocity); | |
} | |
world.spawnParticle(emitter.effectType, | |
posX + pos.x, posY + pos.y, posZ + pos.z, velocity.x, velocity.y, velocity.z); | |
} | |
} | |
emitterTimers[i] = emitter.emitRate; | |
} | |
} | |
checkParts(); | |
prevRotationYaw = axes.getYaw(); | |
prevRotationPitch = axes.getPitch(); | |
prevRotationRoll = axes.getRoll(); | |
prevAxes = axes.clone(); | |
boolean canThrust = driverIsCreative() || driveableData.fuelInTank > 0; | |
//If there's no player in the driveable or it cannot thrust, slow the plane and turn off mouse held actions | |
if((seats[0] != null && seats[0].getControllingPassenger() == null) || !canThrust && getDriveableType().maxThrottle != 0 && getDriveableType().maxNegativeThrottle != 0) | |
{ | |
throttle *= 0.98F; | |
rightMouseHeld = leftMouseHeld = false; | |
} | |
else if(seats[0] != null && seats[0].getControllingPassenger() != null && seats[0].getControllingPassenger() == getControllingPassenger()) | |
{ | |
ReportVehicleError(); | |
} | |
//Check if shooting | |
if(shootDelayPrimary > 0) | |
shootDelayPrimary--; | |
if(shootDelaySecondary > 0) | |
shootDelaySecondary--; | |
if(!world.isRemote) | |
{ | |
if(leftMouseHeld && getDriveableType().modePrimary == EnumFireMode.FULLAUTO) | |
shoot(false); | |
if(rightMouseHeld && getDriveableType().modeSecondary == EnumFireMode.FULLAUTO) | |
shoot(true); | |
minigunSpeedPrimary *= 0.9F; | |
minigunSpeedSecondary *= 0.9F; | |
if(leftMouseHeld && getDriveableType().modePrimary == EnumFireMode.MINIGUN) | |
{ | |
minigunSpeedPrimary += 0.1F; | |
if(minigunSpeedPrimary > 1F) | |
shoot(false); | |
} | |
if(rightMouseHeld && getDriveableType().modeSecondary == EnumFireMode.MINIGUN) | |
{ | |
minigunSpeedSecondary += 0.1F; | |
if(minigunSpeedSecondary > 1F) | |
shoot(true); | |
} | |
} | |
//Handle fuel | |
int fuelMultiplier = 2; | |
//The tank is currently full, so do nothing | |
if(getDriveableData().fuelInTank >= type.fuelTankSize) | |
return; | |
//Look through the entire inventory for fuel cans, buildcraft fuel buckets and RedstoneFlux power sources | |
for(int i = 0; i < getDriveableData().getSizeInventory(); i++) | |
{ | |
ItemStack stack = getDriveableData().getStackInSlot(i); | |
if(stack == null || stack.isEmpty()) | |
continue; | |
Item item = stack.getItem(); | |
//If we have an electric engine, look for RedstoneFlux power source items, such as power cubes | |
/* | |
if(getDriveableData().engine.useRFPower) | |
{ | |
if(item instanceof IEnergyContainerItem) | |
{ | |
IEnergyContainerItem energy = (IEnergyContainerItem)item; | |
getDriveableData().fuelInTank += (fuelMultiplier * energy.extractEnergy(stack, getDriveableData().engine.RFDrawRate, false)) / getDriveableData().engine.RFDrawRate; | |
} | |
} | |
else | |
*/ | |
{ | |
//Check for Flan's Mod fuel items | |
if(item instanceof ItemPart) | |
{ | |
PartType part = ((ItemPart)item).type; | |
//Check it is a fuel item | |
if(part.category == EnumPartCategory.FUEL) | |
{ | |
//Put 2 points of fuel | |
getDriveableData().fuelInTank += fuelMultiplier; | |
//Damage the fuel item to indicate being used up | |
int damage = stack.getItemDamage(); | |
stack.setItemDamage(damage + 1); | |
//If we have finished this fuel item | |
if(damage >= stack.getMaxDamage()) | |
{ | |
//Reset the damage to 0 | |
stack.setItemDamage(0); | |
//Consume one item | |
stack.setCount(stack.getCount() - 1); | |
//If we consumed the last one, destroy the stack | |
if(stack.getCount() <= 0) | |
getDriveableData().setInventorySlotContents(i, ItemStack.EMPTY.copy()); | |
} | |
//We found a fuel item and consumed some, so we are done | |
break; | |
} | |
} | |
//Check for Buildcraft oil and fuel buckets | |
else if(FlansMod.hooks.BuildCraftLoaded && stack.isItemEqual(FlansMod.hooks.BuildCraftOilBucket) && getDriveableData().fuelInTank + 1000 * fuelMultiplier <= type.fuelTankSize) | |
{ | |
getDriveableData().fuelInTank += 1000 * fuelMultiplier; | |
getDriveableData().setInventorySlotContents(i, new ItemStack(Items.BUCKET)); | |
} | |
else if(FlansMod.hooks.BuildCraftLoaded && stack.isItemEqual(FlansMod.hooks.BuildCraftFuelBucket) && getDriveableData().fuelInTank + 2000 * fuelMultiplier <= type.fuelTankSize) | |
{ | |
getDriveableData().fuelInTank += 2000 * fuelMultiplier; | |
getDriveableData().setInventorySlotContents(i, new ItemStack(Items.BUCKET)); | |
} | |
} | |
} | |
} | |
public void checkForCollisions() | |
{ | |
boolean crashInWater = false; | |
double speed = getSpeedXYZ(); | |
for(DriveablePosition p : getDriveableType().collisionPoints) | |
{ | |
if(driveableData.parts.get(p.part).dead) | |
continue; | |
Vector3f lastRelPos = prevAxes.findLocalVectorGlobally(p.position); | |
Vec3d lastPos = new Vec3d(prevPosX + lastRelPos.x, prevPosY + lastRelPos.y, prevPosZ + lastRelPos.z); | |
Vector3f currentRelPos = axes.findLocalVectorGlobally(p.position); | |
Vec3d currentPos = new Vec3d(posX + currentRelPos.x, posY + currentRelPos.y, posZ + currentRelPos.z); | |
if(FlansMod.DEBUG && world.isRemote) | |
{ | |
world.spawnEntity(new EntityDebugVector(world, new Vector3f(lastPos), Vector3f.sub(currentRelPos, lastRelPos, null), 10, 1F, 0F, 0F)); | |
} | |
RayTraceResult hit = world.rayTraceBlocks(lastPos, currentPos, crashInWater); | |
if(hit != null && hit.typeOfHit == Type.BLOCK) | |
{ | |
BlockPos pos = hit.getBlockPos(); | |
IBlockState state = world.getBlockState(pos); | |
Block blockHit = state.getBlock(); | |
float blockHardness = state.getBlockHardness(world, pos); | |
//Attack the part | |
if(!attackPart(p.part, DamageSource.IN_WALL, blockHardness * blockHardness * (float)speed) && TeamsManager.driveablesBreakBlocks) | |
{ | |
//And if it didn't die from the attack, break the block | |
// TODO: [1.12] Heck | |
//playAuxSFXAtEntity(null, 2001, pos, Block.getStateId(state)); | |
if(!world.isRemote) | |
{ | |
WorldServer worldServer = (WorldServer)world; | |
destroyBlock(worldServer, pos, getDriver(), true); | |
} | |
} | |
else | |
{ | |
//The part died! | |
world.createExplosion(this, currentPos.x, currentPos.y, currentPos.z, 1F, false); | |
} | |
} | |
} | |
} | |
@Override | |
public void fall(float distance, float damageMultiplier) | |
{ | |
if(distance <= 0) | |
return; | |
//super.fall(k); | |
int i = MathHelper.ceil(distance - 10F); | |
if(i > 0) | |
attackPart(EnumDriveablePart.core, DamageSource.FALL, damageMultiplier * i / 5); | |
} | |
/** | |
* Attack a certain part of a driveable and return whether it broke or not | |
*/ | |
public boolean attackPart(EnumDriveablePart ep, DamageSource source, float damage) | |
{ | |
DriveablePart part = driveableData.parts.get(ep); | |
return part.attack(damage, source.isFireDamage()); | |
} | |
/** | |
* Takes a vector (such as the origin of a seat / gun) and translates it from local coordinates to global coordinates | |
*/ | |
public Vector3f rotate(Vector3f inVec) | |
{ | |
return axes.findLocalVectorGlobally(inVec); | |
} | |
/** | |
* Takes a vector (such as the origin of a seat / gun) and translates it from local coordinates to global coordinates | |
*/ | |
public Vector3f rotate(Vec3d inVec) | |
{ | |
return rotate(inVec.x, inVec.y, inVec.z); | |
} | |
/** | |
* Takes a vector (such as the origin of a seat / gun) and translates it from local coordinates to global coordinates | |
*/ | |
public Vector3f rotate(double x, double y, double z) | |
{ | |
return rotate(new Vector3f((float)x, (float)y, (float)z)); | |
} | |
//Rotate the plane locally by some angle about the yaw axis | |
public void rotateYaw(float rotateBy) | |
{ | |
if(Math.abs(rotateBy) < 0.01F) | |
return; | |
axes.rotateLocalYaw(rotateBy); | |
updatePrevAngles(); | |
} | |
//Rotate the plane locally by some angle about the pitch axis | |
public void rotatePitch(float rotateBy) | |
{ | |
if(Math.abs(rotateBy) < 0.01F) | |
return; | |
axes.rotateLocalPitch(rotateBy); | |
updatePrevAngles(); | |
} | |
//Rotate the plane locally by some angle about the roll axis | |
public void rotateRoll(float rotateBy) | |
{ | |
if(Math.abs(rotateBy) < 0.01F) | |
return; | |
axes.rotateLocalRoll(rotateBy); | |
updatePrevAngles(); | |
} | |
public void updatePrevAngles() | |
{ | |
//Correct angles that crossed the +/- 180 line, so that rendering doesnt make them swing 360 degrees in one tick. | |
double dYaw = axes.getYaw() - prevRotationYaw; | |
if(dYaw > 180) | |
prevRotationYaw += 360F; | |
if(dYaw < -180) | |
prevRotationYaw -= 360F; | |
double dPitch = axes.getPitch() - prevRotationPitch; | |
if(dPitch > 180) | |
prevRotationPitch += 360F; | |
if(dPitch < -180) | |
prevRotationPitch -= 360F; | |
double dRoll = axes.getRoll() - prevRotationRoll; | |
if(dRoll > 180) | |
prevRotationRoll += 360F; | |
if(dRoll < -180) | |
prevRotationRoll -= 360F; | |
} | |
public void setRotation(float rotYaw, float rotPitch, float rotRoll) | |
{ | |
axes.setAngles(rotYaw, rotPitch, rotRoll); | |
} | |
//Used to stop self collision | |
public boolean isPartOfThis(Entity ent) | |
{ | |
for(EntitySeat seat : seats) | |
{ | |
if(seat == null) | |
continue; | |
if(ent == seat) | |
return true; | |
if(seat.getControllingPassenger() == ent) | |
return true; | |
} | |
for(EntityWheel wheel : wheels) | |
{ | |
if(ent == wheel) | |
return true; | |
} | |
return ent == this; | |
} | |
public DriveableType getDriveableType() | |
{ | |
return DriveableType.getDriveable(driveableType); | |
} | |
public DriveableData getDriveableData() | |
{ | |
return driveableData; | |
} | |
@Override | |
public boolean isDead() | |
{ | |
return isDead; | |
} | |
@Override | |
public Entity getControllingEntity() | |
{ | |
return seats[0].getControllingEntity(); | |
} | |
@Override | |
public ItemStack getPickedResult(RayTraceResult target) | |
{ | |
ItemStack stack = new ItemStack(getDriveableType().item, 1, 0); | |
NBTTagCompound tags = new NBTTagCompound(); | |
stack.setTagCompound(tags); | |
driveableData.writeToNBT(tags); | |
return stack; | |
} | |
public boolean hasFuel() | |
{ | |
if(seats == null || seats[0] == null || seats[0].getControllingPassenger() == null) | |
return false; | |
return driverIsCreative() || driveableData.fuelInTank > 0; | |
} | |
public boolean hasEnoughFuel() | |
{ | |
if(seats == null || seats[0] == null || seats[0].getControllingPassenger() == null) | |
return false; | |
return driverIsCreative() || driveableData.fuelInTank > driveableData.engine.fuelConsumption * throttle; | |
} | |
//Physics time! Oooh yeah | |
public double getSpeedXYZ() | |
{ | |
return Math.sqrt(motionX * motionX + motionY * motionY + motionZ * motionZ); | |
} | |
public double getSpeedXZ() | |
{ | |
return Math.sqrt(motionX * motionX + motionZ * motionZ); | |
} | |
/** | |
* To be overriden by vehicles to get alternate collision system | |
*/ | |
public boolean landVehicle() | |
{ | |
return false; | |
} | |
/** | |
* Overriden by planes for wheel parts | |
*/ | |
public boolean gearDown() | |
{ | |
return true; | |
} | |
/** | |
* Whether or not the plane is on the ground | |
* TODO : Replace with proper check based on wheels | |
*/ | |
public boolean onGround() | |
{ | |
return onGround; | |
} | |
/** | |
* Attack method called by bullets hitting the plane. Does advanced raytracing to detect which part of the plane is hit | |
*/ | |
public ArrayList<BulletHit> attackFromBullet(Vector3f origin, Vector3f motion) | |
{ | |
//Make an array to contain the hits | |
ArrayList<BulletHit> hits = new ArrayList<>(); | |
//Get the position of the bullet origin, relative to the centre of the plane, and then rotate the vectors onto local co-ordinates | |
Vector3f relativePosVector = Vector3f.sub(origin, new Vector3f((float)posX, (float)posY, (float)posZ), null); | |
Vector3f rotatedPosVector = axes.findGlobalVectorLocally(relativePosVector); | |
Vector3f rotatedMotVector = axes.findGlobalVectorLocally(motion); | |
//Check each part | |
for(DriveablePart part : getDriveableData().parts.values()) | |
{ | |
//Ray trace the bullet | |
DriveableHit hit = part.rayTrace(this, rotatedPosVector, rotatedMotVector); | |
if(hit != null) | |
hits.add(hit); | |
} | |
return hits; | |
} | |
/** | |
* Called if the bullet actually hit the part returned by the raytrace | |
* | |
* @param penetratingPower | |
*/ | |
public float bulletHit(BulletType bulletType, float damage, DriveableHit hit, float penetratingPower) | |
{ | |
DriveablePart part = getDriveableData().parts.get(hit.part); | |
part.hitByBullet(bulletType, damage); | |
//This is server side bsns | |
if(!world.isRemote) | |
{ | |
checkParts(); | |
//If it hit, send a damage update packet | |
FlansMod.getPacketHandler().sendToAllAround(new PacketDriveableDamage(this), posX, posY, posZ, 100, dimension); | |
} | |
return penetratingPower - 5F; | |
} | |
/** | |
* A simple raytracer for the driveable. Called by tools | |
*/ | |
public DriveablePart raytraceParts(Vector3f origin, Vector3f motion) | |
{ | |
//Get the position of the bullet origin, relative to the centre of the plane, and then rotate the vectors onto local co-ordinates | |
Vector3f relativePosVector = Vector3f.sub(origin, new Vector3f((float)posX, (float)posY, (float)posZ), null); | |
Vector3f rotatedPosVector = axes.findGlobalVectorLocally(relativePosVector); | |
Vector3f rotatedMotVector = axes.findGlobalVectorLocally(motion); | |
//Check each part | |
for(DriveablePart part : getDriveableData().parts.values()) | |
{ | |
//Ray trace the bullet | |
if(part.rayTrace(this, rotatedPosVector, rotatedMotVector) != null) | |
{ | |
return part; | |
} | |
} | |
return null; | |
} | |
/** | |
* For overriding for toggles such as gear up / down on planes | |
*/ | |
public boolean canHitPart(EnumDriveablePart part) | |
{ | |
return true; | |
} | |
/** | |
* Internal method for checking that all parts are ok, destroying broken ones, dropping items and making sure that child parts are destroyed when their parents are | |
*/ | |
public void checkParts() | |
{ | |
for(DriveablePart part : getDriveableData().parts.values()) | |
{ | |
if(part != null && !part.dead && part.health <= 0 && part.maxHealth > 0) | |
{ | |
killPart(part); | |
} | |
} | |
//If the core was destroyed, kill the driveable | |
if(getDriveableData().parts.get(EnumDriveablePart.core).dead) | |
{ | |
if(!world.isRemote) | |
{ | |
for(DriveablePart part : driveableData.parts.values()) | |
{ | |
if(part.health > 0 && !part.dead) | |
killPart(part); | |
} | |
} | |
setDead(); | |
} | |
} | |
/** | |
* Internal method for killing driveable parts | |
*/ | |
private void killPart(DriveablePart part) | |
{ | |
if(part.dead) | |
return; | |
part.health = 0; | |
part.dead = true; | |
//Drop items | |
DriveableType type = getDriveableType(); | |
if(!world.isRemote) | |
{ | |
Vector3f pos = new Vector3f(0, 0, 0); | |
//Get the midpoint of the part | |
if(part.box != null) | |
pos = axes.findLocalVectorGlobally(new Vector3f(part.box.x / 16F + part.box.w / 32F, part.box.y / 16F + part.box.h / 32F, part.box.z / 16F + part.box.d / 32F)); | |
ArrayList<ItemStack> drops = type.getItemsRequired(part, getDriveableData().engine); | |
if(drops != null) | |
{ | |
//Drop each itemstack | |
for(ItemStack stack : drops) | |
{ | |
world.spawnEntity(new EntityItem(world, posX + pos.x, posY + pos.y, posZ + pos.z, stack.copy())); | |
} | |
} | |
dropItemsOnPartDeath(pos, part); | |
//Inventory is in the core, so drop it if the core is broken | |
if(part.type == EnumDriveablePart.core) | |
{ | |
for(int i = 0; i < getDriveableData().getSizeInventory(); i++) | |
{ | |
ItemStack stack = getDriveableData().getStackInSlot(i); | |
if(stack != null && !stack.isEmpty()) | |
{ | |
world.spawnEntity(new EntityItem(world, posX + rand.nextGaussian(), posY + rand.nextGaussian(), posZ + rand.nextGaussian(), stack)); | |
} | |
} | |
} | |
} | |
//Kill all child parts to stop things floating unconnected | |
for(EnumDriveablePart child : part.type.getChildren()) | |
{ | |
killPart(getDriveableData().parts.get(child)); | |
} | |
} | |
/** | |
* Method for planes, vehicles and whatnot to drop their own specific items if they wish | |
*/ | |
protected abstract void dropItemsOnPartDeath(Vector3f midpoint, DriveablePart part); | |
@Override | |
public float getPlayerRoll() | |
{ | |
return axes.getRoll(); | |
} | |
@Override | |
public float getPrevPlayerRoll() | |
{ | |
return prevAxes.getRoll(); | |
} | |
@Override | |
public void explode() | |
{ | |
} | |
@Override | |
public float getCameraDistance() | |
{ | |
return getDriveableType().cameraDistance; | |
} | |
public boolean isPartIntact(EnumDriveablePart part) | |
{ | |
DriveablePart thisPart = getDriveableData().parts.get(part); | |
return thisPart.maxHealth == 0 || thisPart.health > 0; | |
} | |
public abstract boolean hasMouseControlMode(); | |
public abstract String getBombInventoryName(); | |
public abstract String getMissileInventoryName(); | |
public boolean rotateWithTurret(Seat seat) | |
{ | |
return seat.part == EnumDriveablePart.turret; | |
} | |
@Override | |
public String getName() | |
{ | |
return getDriveableType().name; | |
} | |
@SideOnly(Side.CLIENT) | |
public boolean showInventory(int seat) | |
{ | |
return seat != 0 || !FlansModClient.controlModeMouse; | |
} | |
//------------------------------------- | |
// Getters and setters for dual fields | |
//------------------------------------- | |
public float getShootDelay(boolean secondary) | |
{ | |
return secondary ? shootDelaySecondary : shootDelayPrimary; | |
} | |
public float getMinigunSpeed(boolean secondary) | |
{ | |
return secondary ? minigunSpeedSecondary : minigunSpeedPrimary; | |
} | |
public int getCurrentGun(boolean secondary) | |
{ | |
return secondary ? currentGunSecondary : currentGunPrimary; | |
} | |
public void setShootDelay(float f, boolean secondary) | |
{ | |
if(secondary) | |
shootDelaySecondary = f; | |
else shootDelayPrimary = f; | |
} | |
public void setMinigunSpeed(float f, boolean secondary) | |
{ | |
if(secondary) | |
minigunSpeedSecondary = f; | |
else minigunSpeedPrimary = f; | |
} | |
public void setCurrentGun(int i, boolean secondary) | |
{ | |
if(secondary) | |
currentGunSecondary = i; | |
else currentGunPrimary = i; | |
} | |
@Override | |
protected boolean canFitPassenger(Entity passenger) | |
{ | |
if(passenger instanceof EntitySeat || passenger instanceof EntityWheel) | |
{ | |
return getPassengers().size() < getDriveableType().numPassengers + getDriveableType().wheelPositions.length + 1; | |
} | |
return false; | |
} | |
@Override | |
public void updatePassenger(Entity passenger) | |
{ | |
// They can handle themselves, but maybe the code should be moved to here | |
} | |
@Override | |
public void removePassenger(Entity passenger) | |
{ | |
super.removePassenger(passenger); | |
} | |
public EntitySeat getSeat(EntityLivingBase passenger) | |
{ | |
for(EntitySeat seat : seats) | |
{ | |
if(seat.getControllingEntity() == passenger) | |
{ | |
return seat; | |
} | |
} | |
return null; | |
} | |
@Override | |
protected void addPassenger(Entity passenger) | |
{ | |
super.addPassenger(passenger); | |
if(world.isRemote) | |
{ | |
// We need to do some handling to work out which seat to get into. Or not? | |
} | |
} | |
public void registerSeat(EntitySeat seat) | |
{ | |
seats[seat.getExpectedSeatID()] = seat; | |
} | |
public void registerWheel(EntityWheel wheel) | |
{ | |
wheels[wheel.getExpectedWheelID()] = wheel; | |
} | |
public EntitySeat[] getSeats() | |
{ | |
return seats; | |
} | |
public EntitySeat getSeat(int id) | |
{ | |
if(seats[id] == null) | |
{ | |
for(Entity passenger : getPassengers()) | |
{ | |
if(passenger instanceof EntitySeat) | |
{ | |
EntitySeat seat = (EntitySeat)passenger; | |
if(seat.getExpectedSeatID() == id) | |
{ | |
seats[id] = seat; | |
seats[id].driveable = this; | |
break; | |
} | |
} | |
} | |
} | |
return seats[id]; | |
} | |
public EntityWheel getWheel(int id) | |
{ | |
if(wheels[id] == null) | |
{ | |
for(Entity passenger : getPassengers()) | |
{ | |
if(passenger instanceof EntityWheel) | |
{ | |
EntityWheel wheel = (EntityWheel)passenger; | |
if(wheel.getExpectedWheelID() == id) | |
{ | |
wheels[id] = wheel; | |
break; | |
} | |
} | |
} | |
} | |
return wheels[id]; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.flansmod.common.guns; | |
import javax.annotation.Nullable; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.List; | |
import java.util.UUID; | |
import org.lwjgl.input.Mouse; | |
import com.google.common.collect.Multimap; | |
import net.minecraft.block.Block; | |
import net.minecraft.block.state.IBlockState; | |
import net.minecraft.client.Minecraft; | |
import net.minecraft.client.audio.PositionedSoundRecord; | |
import net.minecraft.client.particle.Particle; | |
import net.minecraft.client.util.ITooltipFlag; | |
import net.minecraft.creativetab.CreativeTabs; | |
import net.minecraft.entity.Entity; | |
import net.minecraft.entity.EntityLivingBase; | |
import net.minecraft.entity.SharedMonsterAttributes; | |
import net.minecraft.entity.ai.attributes.AttributeModifier; | |
import net.minecraft.entity.player.EntityPlayer; | |
import net.minecraft.entity.player.EntityPlayerMP; | |
import net.minecraft.init.Blocks; | |
import net.minecraft.inventory.EntityEquipmentSlot; | |
import net.minecraft.inventory.IInventory; | |
import net.minecraft.item.EnumAction; | |
import net.minecraft.item.Item; | |
import net.minecraft.item.ItemStack; | |
import net.minecraft.nbt.NBTTagCompound; | |
import net.minecraft.nbt.NBTTagList; | |
import net.minecraft.util.ActionResult; | |
import net.minecraft.util.DamageSource; | |
import net.minecraft.util.EnumActionResult; | |
import net.minecraft.util.EnumFacing; | |
import net.minecraft.util.EnumHand; | |
import net.minecraft.util.EnumParticleTypes; | |
import net.minecraft.util.NonNullList; | |
import net.minecraft.util.SoundCategory; | |
import net.minecraft.util.math.BlockPos; | |
import net.minecraft.util.math.MathHelper; | |
import net.minecraft.util.math.RayTraceResult; | |
import net.minecraft.util.math.RayTraceResult.Type; | |
import net.minecraft.util.math.Vec3d; | |
import net.minecraft.util.math.Vec3i; | |
import net.minecraft.world.World; | |
import net.minecraftforge.common.util.Constants; | |
import net.minecraftforge.fml.client.FMLClientHandler; | |
import net.minecraftforge.fml.relauncher.Side; | |
import net.minecraftforge.fml.relauncher.SideOnly; | |
import com.flansmod.client.FlansModClient; | |
import com.flansmod.client.FlansModResourceHandler; | |
import com.flansmod.client.debug.EntityDebugDot; | |
import com.flansmod.client.debug.EntityDebugVector; | |
import com.flansmod.client.model.GunAnimations; | |
import com.flansmod.client.model.GunAnimations.LookAtState; | |
import com.flansmod.client.model.InstantBulletRenderer; | |
import com.flansmod.client.model.InstantBulletRenderer.InstantShotTrail; | |
import com.flansmod.common.EntityItemCustomRender; | |
import com.flansmod.common.FlansMod; | |
import com.flansmod.common.PlayerData; | |
import com.flansmod.common.PlayerHandler; | |
import com.flansmod.common.guns.ShotData.InstantShotData; | |
import com.flansmod.common.guns.ShotData.SpawnEntityShotData; | |
import com.flansmod.common.guns.raytracing.FlansModRaytracer; | |
import com.flansmod.common.guns.raytracing.FlansModRaytracer.BlockHit; | |
import com.flansmod.common.guns.raytracing.FlansModRaytracer.BulletHit; | |
import com.flansmod.common.guns.raytracing.FlansModRaytracer.DriveableHit; | |
import com.flansmod.common.guns.raytracing.FlansModRaytracer.EntityHit; | |
import com.flansmod.common.guns.raytracing.FlansModRaytracer.PlayerBulletHit; | |
import com.flansmod.common.network.PacketPlaySound; | |
import com.flansmod.common.network.PacketReload; | |
import com.flansmod.common.network.PacketShotData; | |
import com.flansmod.common.teams.EntityFlag; | |
import com.flansmod.common.teams.EntityFlagpole; | |
import com.flansmod.common.teams.EntityGunItem; | |
import com.flansmod.common.teams.Team; | |
import com.flansmod.common.types.IPaintableItem; | |
import com.flansmod.common.types.InfoType; | |
import com.flansmod.common.types.PaintableType; | |
import com.flansmod.common.vector.Vector3f; | |
public class ItemGun extends Item implements IPaintableItem | |
{ | |
private static final int CLIENT_TO_SERVER_UPDATE_INTERVAL = 1; | |
private static final int SERVER_TO_CLIENT_UPDATE_INTERVAL = 2; | |
private GunType type; | |
public GunType GetType() | |
{ | |
return type; | |
} | |
@Override | |
public InfoType getInfoType() | |
{ | |
return type; | |
} | |
@Override | |
public PaintableType GetPaintableType() | |
{ | |
return type; | |
} | |
private int soundDelay = 0; | |
private static boolean rightMouseHeld; | |
private static boolean lastRightMouseHeld; | |
private static boolean leftMouseHeld; | |
private static boolean lastLeftMouseHeld; | |
private static boolean GetMouseHeld(EnumHand hand) | |
{ | |
if(FlansMod.shootOnRightClick) | |
return hand == EnumHand.MAIN_HAND ? leftMouseHeld : rightMouseHeld; | |
else return hand == EnumHand.MAIN_HAND ? rightMouseHeld : leftMouseHeld; | |
} | |
private static boolean GetLastMouseHeld(EnumHand hand) | |
{ | |
if(FlansMod.shootOnRightClick) | |
return hand == EnumHand.MAIN_HAND ? lastLeftMouseHeld : lastRightMouseHeld; | |
else return hand == EnumHand.MAIN_HAND ? lastRightMouseHeld : lastLeftMouseHeld; | |
} | |
private static List<ShotData> shotsFiredClient = new ArrayList<>(), shotsFiredServer = new ArrayList<>(); | |
public ItemGun(GunType type) | |
{ | |
maxStackSize = 1; | |
this.type = type; | |
type.item = this; | |
setMaxDamage(0); | |
setRegistryName(type.shortName); | |
setCreativeTab(FlansMod.tabFlanGuns); | |
} | |
/** | |
* Get the bullet item stack stored in the gun's NBT data (the loaded magazine / bullets) | |
*/ | |
public ItemStack getBulletItemStack(ItemStack gun, int id) | |
{ | |
//If the gun has no tags, give it some | |
if(!gun.hasTagCompound()) | |
{ | |
gun.setTagCompound(new NBTTagCompound()); | |
return ItemStack.EMPTY.copy(); | |
} | |
//If the gun has no ammo tags, give it some | |
if(!gun.getTagCompound().hasKey("ammo")) | |
{ | |
NBTTagList ammoTagsList = new NBTTagList(); | |
for(int i = 0; i < type.numAmmoItemsInGun; i++) | |
{ | |
ammoTagsList.appendTag(new NBTTagCompound()); | |
} | |
gun.getTagCompound().setTag("ammo", ammoTagsList); | |
return ItemStack.EMPTY.copy(); | |
} | |
//Take the list of ammo tags | |
NBTTagList ammoTagsList = gun.getTagCompound().getTagList("ammo", Constants.NBT.TAG_COMPOUND); | |
//Get the specific ammo tags required | |
NBTTagCompound ammoTags = ammoTagsList.getCompoundTagAt(id); | |
return new ItemStack(ammoTags); | |
} | |
/** | |
* Set the bullet item stack stored in the gun's NBT data (the loaded magazine / bullets) | |
*/ | |
public void setBulletItemStack(ItemStack gun, ItemStack bullet, int id) | |
{ | |
//If the gun has no tags, give it some | |
if(!gun.hasTagCompound()) | |
{ | |
gun.setTagCompound(new NBTTagCompound()); | |
} | |
//If the gun has no ammo tags, give it some | |
if(!gun.getTagCompound().hasKey("ammo")) | |
{ | |
NBTTagList ammoTagsList = new NBTTagList(); | |
for(int i = 0; i < type.numAmmoItemsInGun; i++) | |
{ | |
ammoTagsList.appendTag(new NBTTagCompound()); | |
} | |
gun.getTagCompound().setTag("ammo", ammoTagsList); | |
} | |
//Take the list of ammo tags | |
NBTTagList ammoTagsList = gun.getTagCompound().getTagList("ammo", Constants.NBT.TAG_COMPOUND); | |
//Get the specific ammo tags required | |
NBTTagCompound ammoTags = ammoTagsList.getCompoundTagAt(id); | |
//Represent empty slots by nulltypes | |
if(bullet == null) | |
{ | |
ammoTags = new NBTTagCompound(); | |
} | |
//Set the tags to match the bullet stack | |
bullet.writeToNBT(ammoTags); | |
} | |
/** | |
* Method for dropping items on reload and on shoot | |
*/ | |
public static void dropItem(World world, Entity entity, String itemName) | |
{ | |
if(itemName != null) | |
{ | |
int damage = 0; | |
if(itemName.contains(".")) | |
{ | |
damage = Integer.parseInt(itemName.split("\\.")[1]); | |
itemName = itemName.split("\\.")[0]; | |
} | |
ItemStack dropStack = InfoType.getRecipeElement(itemName, damage); | |
entity.entityDropItem(dropStack, 0.5F); | |
} | |
} | |
/** | |
* Deployable guns only | |
*/ | |
@Override | |
public ActionResult<ItemStack> onItemRightClick(World world, EntityPlayer entityplayer, EnumHand hand) | |
{ | |
ItemStack itemstack = entityplayer.getHeldItem(hand); | |
if(type.deployable) | |
{ | |
//Raytracing | |
float cosYaw = MathHelper.cos(-entityplayer.rotationYaw * 0.01745329F - 3.141593F); | |
float sinYaw = MathHelper.sin(-entityplayer.rotationYaw * 0.01745329F - 3.141593F); | |
float cosPitch = -MathHelper.cos(-entityplayer.rotationPitch * 0.01745329F); | |
float sinPitch = MathHelper.sin(-entityplayer.rotationPitch * 0.01745329F); | |
double length = 5D; | |
Vec3d posVec = new Vec3d(entityplayer.posX, entityplayer.posY + 1.62D - entityplayer.getYOffset(), entityplayer.posZ); | |
Vec3d lookVec = posVec.add(sinYaw * cosPitch * length, sinPitch * length, cosYaw * cosPitch * length); | |
RayTraceResult look = world.rayTraceBlocks(posVec, lookVec, true); | |
//Result check | |
if(look != null && look.typeOfHit == Type.BLOCK) | |
{ | |
if(look.sideHit == EnumFacing.UP) | |
{ | |
int playerDir = MathHelper.floor(((entityplayer.rotationYaw * 4F) / 360F) + 0.5D) & 3; | |
int i = look.getBlockPos().getX(); | |
int j = look.getBlockPos().getY(); | |
int k = look.getBlockPos().getZ(); | |
if(!world.isRemote) | |
{ | |
if(world.getBlockState(new BlockPos(i, j, k)).getBlock() == Blocks.SNOW) | |
{ | |
j--; | |
} | |
if(isSolid(world, i, j, k) && | |
(world.getBlockState(new BlockPos(i, j + 1, k)).getBlock() == Blocks.AIR || world.getBlockState(new BlockPos(i, j + 1, k)).getBlock() == Blocks.SNOW) | |
&& | |
(world.getBlockState(new BlockPos(i + (playerDir == 1 ? 1 : 0) - (playerDir == 3 ? 1 : 0), j + 1, k - (playerDir == 0 ? 1 : 0) + (playerDir == 2 ? 1 : 0))).getBlock() == Blocks.AIR) | |
&& | |
(world.getBlockState(new BlockPos(i + (playerDir == 1 ? 1 : 0) - (playerDir == 3 ? 1 : 0), j, k - (playerDir == 0 ? 1 : 0) + (playerDir == 2 ? 1 : 0))).getBlock() == Blocks.AIR | |
|| world.getBlockState(new BlockPos(i + (playerDir == 1 ? 1 : 0) - (playerDir == 3 ? 1 : 0), j, k - (playerDir == 0 ? 1 : 0) + (playerDir == 2 ? 1 : 0))).getBlock() == Blocks.SNOW)) | |
{ | |
for(EntityMG mg : EntityMG.mgs) | |
{ | |
if(mg.blockX == i && mg.blockY == j + 1 && mg.blockZ == k && !mg.isDead) | |
return new ActionResult<>(EnumActionResult.SUCCESS, itemstack); | |
} | |
EntityMG mg = new EntityMG(world, i, j + 1, k, playerDir, type); | |
if(getBulletItemStack(itemstack, 0) != null) | |
{ | |
mg.ammo = getBulletItemStack(itemstack, 0); | |
} | |
world.spawnEntity(mg); | |
if(!entityplayer.capabilities.isCreativeMode) | |
itemstack.setCount(0); | |
} | |
} | |
} | |
} | |
} | |
//Stop the gun bobbing up and down when holding shoot and looking at a block | |
if(world.isRemote) | |
{ | |
for(int i = 0; i < 3; i++) | |
Minecraft.getMinecraft().entityRenderer.itemRenderer.updateEquippedItem(); | |
} | |
return new ActionResult<>(EnumActionResult.PASS, itemstack); | |
} | |
// _____________________________________________________________________________ | |
// | |
// Shooting code | |
// _____________________________________________________________________________ | |
@SideOnly(Side.CLIENT) | |
public void onUpdateClient(ItemStack gunstack, int gunSlot, World world, Entity entity, EnumHand hand, boolean hasOffHand) | |
{ | |
if(!(entity instanceof EntityPlayer)) | |
{ | |
return; | |
} | |
// Get useful objects | |
Minecraft mc = Minecraft.getMinecraft(); | |
EntityPlayer player = (EntityPlayer)entity; | |
PlayerData data = PlayerHandler.getPlayerData(player, Side.CLIENT); | |
// Play idle sounds | |
if(soundDelay <= 0 && type.idleSound != null) | |
{ | |
PacketPlaySound.sendSoundPacket(entity.posX, entity.posY, entity.posZ, FlansMod.soundRange, entity.dimension, type.idleSound, false); | |
soundDelay = type.idleSoundLength; | |
} | |
// This code is not for deployables | |
if(type.deployable) | |
return; | |
// Do not shoot ammo bags, flags or dropped gun items | |
if(mc.objectMouseOver != null && (mc.objectMouseOver.entityHit instanceof EntityFlagpole || mc.objectMouseOver.entityHit instanceof EntityFlag || mc.objectMouseOver.entityHit instanceof EntityGunItem || (mc.objectMouseOver.entityHit instanceof EntityGrenade && ((EntityGrenade)mc.objectMouseOver.entityHit).type.isDeployableBag))) | |
return; | |
if(hasOffHand && !type.oneHanded) | |
return; | |
// If we have an off hand item, then disable our secondary functions | |
boolean secondaryFunctionsEnabled = true; | |
if(type.usableByPlayers) | |
{ | |
boolean needsToReload = needsToReload(gunstack); | |
boolean shouldShootThisTick = false; | |
switch(type.getFireMode(gunstack)) | |
{ | |
case BURST: | |
{ | |
if(data.GetBurstRoundsRemaining(hand) > 0) | |
{ | |
shouldShootThisTick = true; | |
} | |
// Fallthrough to semi auto | |
} | |
case SEMIAUTO: | |
{ | |
if(GetMouseHeld(hand) && !GetLastMouseHeld(hand)) | |
{ | |
shouldShootThisTick = true; | |
} | |
else needsToReload = false; | |
break; | |
} | |
case MINIGUN: | |
{ | |
if(needsToReload) | |
{ | |
needsToReload = GetMouseHeld(hand); | |
break; | |
} | |
if(GetMouseHeld(hand)) | |
{ | |
data.minigunSpeed += 2.0f; | |
data.minigunSpeed *= 0.9f; | |
// TODO : Re-add looping sounds | |
if(data.minigunSpeed < type.minigunStartSpeed) | |
{ | |
if(type.useLoopingSounds) | |
{ | |
data.shouldPlayWarmupSound = true; | |
} | |
break; | |
} | |
} | |
else if(data.minigunSpeed > 0.0f) | |
{ | |
data.shouldPlayCooldownSound = true; | |
} | |
//else fallthrough to full auto | |
} | |
case FULLAUTO: | |
{ | |
shouldShootThisTick = GetMouseHeld(hand); | |
if(!shouldShootThisTick) | |
{ | |
needsToReload = false; | |
} | |
break; | |
} | |
default: | |
needsToReload = false; | |
break; | |
} | |
// Do reload if we pressed fire. | |
if(needsToReload) | |
{ | |
if(Reload(gunstack, world, player, player.inventory, hand, hasOffHand, false, player.capabilities.isCreativeMode)) | |
{ | |
//Set player shoot delay to be the reload delay | |
//Set both gun delays to avoid reloading two guns at once | |
data.shootTimeRight = data.shootTimeLeft = (int)type.getReloadTime(gunstack); | |
GunAnimations animations = FlansModClient.getGunAnimations(player, hand); | |
int pumpDelay = type.model == null ? 0 : type.model.pumpDelayAfterReload; | |
int pumpTime = type.model == null ? 1 : type.model.pumpTime; | |
animations.doReload(type.reloadTime, pumpDelay, pumpTime); | |
if(hand == EnumHand.OFF_HAND) | |
{ | |
data.reloadingLeft = true; | |
data.burstRoundsRemainingLeft = 0; | |
} | |
else | |
{ | |
data.reloadingRight = true; | |
data.burstRoundsRemainingRight = 0; | |
} | |
//Send reload packet to server | |
FlansMod.getPacketHandler().sendToServer(new PacketReload(hand, false)); | |
} | |
} | |
// Fire! | |
else if(shouldShootThisTick) | |
{ | |
GunAnimations animations = FlansModClient.getGunAnimations(player, hand); | |
animations.lookAt = LookAtState.NONE; | |
float shootTime = data.GetShootTime(hand); | |
// For each | |
while(shootTime <= 0.0f) | |
{ | |
// Add the delay for this shot and shoot it! | |
shootTime += type.GetShootDelay(gunstack); | |
ItemStack shootableStack = getBestNonEmptyShootableStack(gunstack); | |
if(shootableStack == null || shootableStack.isEmpty()) | |
{ | |
continue; | |
} | |
ItemShootable shootableItem = (ItemShootable)shootableStack.getItem(); | |
ShootableType shootableType = shootableItem.type; | |
// Instant bullets. Do a raytrace | |
if(type.bulletSpeed == 0.0f) | |
{ | |
for(int i = 0; i < type.numBullets * shootableType.numBullets; i++) | |
{ | |
Vector3f rayTraceOrigin = new Vector3f(player.getPositionEyes(0.0f)); | |
Vector3f rayTraceDirection = new Vector3f(player.getLookVec()); | |
float spread = 0.0025f * type.getSpread(gunstack) * shootableType.bulletSpread; | |
rayTraceDirection.x += (float)world.rand.nextGaussian() * spread; | |
rayTraceDirection.y += (float)world.rand.nextGaussian() * spread; | |
rayTraceDirection.z += (float)world.rand.nextGaussian() * spread; | |
rayTraceDirection.scale(500.0f); | |
List<BulletHit> hits = FlansModRaytracer.Raytrace(world, player, false, null, rayTraceOrigin, rayTraceDirection, 0); | |
Entity victim = null; | |
Vector3f hitPos = Vector3f.add(rayTraceOrigin, rayTraceDirection, null); | |
BulletHit firstHit = null; | |
if(!hits.isEmpty()) | |
{ | |
firstHit = hits.get(0); | |
hitPos = Vector3f.add(rayTraceOrigin, (Vector3f)rayTraceDirection.scale(firstHit.intersectTime), null); | |
victim = firstHit.GetEntity(); | |
} | |
Vector3f gunOrigin = FlansModRaytracer.GetPlayerMuzzlePosition(player, hand); | |
if(FlansMod.DEBUG) | |
{ | |
world.spawnEntity(new EntityDebugDot(world, gunOrigin, 100, 1.0f, 1.0f, 1.0f)); | |
} | |
boolean silenced = type.getBarrel(gunstack) != null && type.getBarrel(gunstack).silencer; | |
ShotData shotData = new InstantShotData(gunSlot, hand, type, shootableType, player, gunOrigin, firstHit, hitPos, type.getDamage(gunstack), i < type.numBullets * shootableType.numBullets - 1, silenced); | |
shotsFiredClient.add(shotData); | |
} | |
} | |
// Else, spawn an entity | |
else | |
{ | |
ShotData shotData = new SpawnEntityShotData(gunSlot, hand, type, shootableType, player, new Vector3f(player.getLookVec())); | |
shotsFiredClient.add(shotData); | |
} | |
// Now do client side things | |
int pumpDelay = type.model == null ? 0 : type.model.pumpDelay; | |
int pumpTime = type.model == null ? 1 : type.model.pumpTime; | |
animations.doShoot(pumpDelay, pumpTime); | |
FlansModClient.playerRecoil += type.getRecoil(gunstack); | |
animations.recoil += type.getRecoil(gunstack); | |
if(type.consumeGunUponUse) | |
player.inventory.setInventorySlotContents(gunSlot, ItemStack.EMPTY.copy()); | |
// Update burst fire | |
if(type.getFireMode(gunstack) == EnumFireMode.BURST) | |
{ | |
int burstRoundsRemaining = data.GetBurstRoundsRemaining(hand); | |
if(burstRoundsRemaining > 0) | |
burstRoundsRemaining--; | |
else burstRoundsRemaining = type.numBurstRounds; | |
data.SetBurstRoundsRemaining(hand, burstRoundsRemaining); | |
} | |
} | |
data.SetShootTime(hand, shootTime); | |
} | |
Vector3f gunOrigin = FlansModRaytracer.GetPlayerMuzzlePosition(player, hand); | |
if(FlansMod.DEBUG) | |
{ | |
world.spawnEntity(new EntityDebugDot(world, gunOrigin, 100, 1.0f, 1.0f, 1.0f)); | |
} | |
// Now send shooting data to the server | |
if(!shotsFiredClient.isEmpty() && player.ticksExisted % CLIENT_TO_SERVER_UPDATE_INTERVAL == 0) | |
{ | |
FlansMod.getPacketHandler().sendToServer(new PacketShotData(shotsFiredClient)); | |
shotsFiredClient.clear(); | |
} | |
// Check for scoping in / out | |
IScope currentScope = type.getCurrentScope(gunstack); | |
if(!hasOffHand) | |
{ | |
switch(hand) | |
{ | |
case MAIN_HAND: | |
{ | |
if(GetMouseHeld(EnumHand.OFF_HAND) && !GetLastMouseHeld(EnumHand.OFF_HAND) | |
&& (type.secondaryFunction == EnumSecondaryFunction.ADS_ZOOM || type.secondaryFunction == EnumSecondaryFunction.ZOOM)) | |
{ | |
FlansModClient.SetScope(currentScope); | |
} | |
break; | |
} | |
case OFF_HAND: | |
{ | |
if(GetMouseHeld(EnumHand.MAIN_HAND) && !GetLastMouseHeld(EnumHand.MAIN_HAND) | |
&& (type.secondaryFunction == EnumSecondaryFunction.ADS_ZOOM || type.secondaryFunction == EnumSecondaryFunction.ZOOM)) | |
{ | |
FlansModClient.SetScope(currentScope); | |
} | |
break; | |
} | |
} | |
} | |
} | |
// And finally do sounds | |
if(soundDelay > 0) | |
{ | |
soundDelay--; | |
} | |
} | |
public void ServerHandleShotData(ItemStack gunstack, int gunSlot, World world, Entity entity, boolean isOffHand, ShotData shotData) | |
{ | |
// Get useful things | |
if(!(entity instanceof EntityPlayerMP)) //Vehicle entities quickly make the method return nothing, and so the shots are never handled. | |
{ | |
return; | |
} | |
EntityPlayerMP player = (EntityPlayerMP)entity; | |
PlayerData data = PlayerHandler.getPlayerData(player, Side.SERVER); | |
if(data == null) | |
{ | |
return; | |
} | |
boolean isExtraBullet = shotData instanceof InstantShotData && ((InstantShotData)shotData).isExtraBullet; | |
//Go through the bullet stacks in the gun and see if any of them are not null | |
int bulletID = 0; | |
ItemStack bulletStack = ItemStack.EMPTY.copy(); | |
for(; bulletID < type.numAmmoItemsInGun; bulletID++) | |
{ | |
ItemStack checkingStack = getBulletItemStack(gunstack, bulletID); | |
if(checkingStack != null && checkingStack.getItemDamage() < checkingStack.getMaxDamage()) | |
{ | |
bulletStack = checkingStack; | |
break; | |
} | |
} | |
// We have no bullet stack. So we need to reload. The player will send us a message requesting we do a reload | |
if(bulletStack.isEmpty()) | |
{ | |
return; | |
} | |
if(bulletStack.getItem() instanceof ItemShootable) | |
{ | |
ShootableType bullet = ((ItemShootable)bulletStack.getItem()).type; | |
if(!isExtraBullet) | |
{ | |
// Drop item on shooting if bullet requires it | |
if(bullet.dropItemOnShoot != null && !player.capabilities.isCreativeMode) | |
dropItem(world, player, bullet.dropItemOnShoot); | |
// Drop item on shooting if gun requires it | |
if(type.dropItemOnShoot != null)// && !entityplayer.capabilities.isCreativeMode) | |
dropItem(world, player, type.dropItemOnShoot); | |
if(type.knockback > 0) | |
{ | |
//TODO : Apply knockback | |
} | |
//Damage the bullet item | |
bulletStack.setItemDamage(bulletStack.getItemDamage() + 1); | |
//Update the stack in the gun | |
setBulletItemStack(gunstack, bulletStack, bulletID); | |
if(type.consumeGunUponUse && gunSlot != -1) | |
player.inventory.setInventorySlotContents(gunSlot, ItemStack.EMPTY.copy()); | |
} | |
// Spawn an entity, classic style | |
if(shotData instanceof SpawnEntityShotData) | |
{ | |
// Play a sound if the previous sound has finished | |
if(soundDelay <= 0 && type.shootSound != null) | |
{ | |
AttachmentType barrel = type.getBarrel(gunstack); | |
boolean silenced = barrel != null && barrel.silencer; | |
//world.playSoundAtEntity(entityplayer, type.shootSound, 10F, type.distortSound ? 1.0F / (world.rand.nextFloat() * 0.4F + 0.8F) : 1.0F); | |
PacketPlaySound.sendSoundPacket(player.posX, player.posY, player.posZ, FlansMod.soundRange, player.dimension, type.shootSound, type.distortSound, silenced); | |
soundDelay = type.shootSoundLength; | |
} | |
//Shoot | |
// Spawn the bullet entities | |
for(int k = 0; k < type.numBullets * bullet.numBullets; k++) | |
{ | |
// Actually shoot the bullet | |
((ItemShootable)bulletStack.getItem()).Shoot(world, | |
new Vector3f(player.posX, player.posY + player.getEyeHeight(), player.posZ), | |
new Vector3f(player.getLookVec()), | |
type.getDamage(gunstack), | |
(player.isSneaking() ? 0.7F : 1F) * type.getSpread(gunstack) * bullet.bulletSpread, | |
type.getBulletSpeed(gunstack), | |
type, | |
player); | |
} | |
} | |
// Do a raytrace check on what they've sent us. | |
else if(shotData instanceof InstantShotData) | |
{ | |
InstantShotData instantData = (InstantShotData)shotData; | |
//if(stuff) | |
//{ | |
// calculate our own raytrace to verify they're not cheating | |
//} | |
// else | |
{ | |
// Take a point halfway along. Then make the radius encapsulate both ends and then some | |
//Vector3f targetPoint = Vector3f.add(instantData.origin, instantData.hitPos, null); | |
//targetPoint.scale(0.5f); | |
//float radius = Vector3f.sub(instantData.origin, instantData.hitPos, null).length(); | |
//radius += 50.0f; | |
AttachmentType barrel = type.getBarrel(gunstack); | |
boolean silenced = barrel != null && barrel.silencer; | |
DoInstantShot(world, player, type, (BulletType)bullet, instantData.origin, instantData.hitPos, instantData.hitData, type.getDamage(gunstack), isExtraBullet, silenced); | |
shotsFiredServer.add(shotData); | |
} | |
} | |
} | |
} | |
// I wanted to make a method specific to handling vehicle weapons, but I don't know enough about flans to hack one up. This whole thing doesn't work, don't expect it to. | |
public void ServerHandleDrivableShotData(ItemStack gunstack, int gunSlot, World world, Entity entity, Entity driver, boolean isOffHand, ShotData shotData) | |
{ | |
// Get useful things | |
if(!(driver instanceof EntityPlayerMP)) | |
{ | |
return; | |
} | |
EntityPlayerMP player = (EntityPlayerMP)driver; | |
PlayerData data = PlayerHandler.getPlayerData(player, Side.SERVER); | |
if(data == null) | |
{ | |
return; | |
} | |
boolean isExtraBullet = shotData instanceof InstantShotData && ((InstantShotData)shotData).isExtraBullet; | |
//Go through the bullet stacks in the gun and see if any of them are not null | |
int bulletID = 0; | |
ItemStack bulletStack = ItemStack.EMPTY.copy(); | |
for(; bulletID < type.numAmmoItemsInGun; bulletID++) | |
{ | |
ItemStack checkingStack = getBulletItemStack(gunstack, bulletID); //Hey | |
if(checkingStack != null && checkingStack.getItemDamage() < checkingStack.getMaxDamage()) | |
{ | |
bulletStack = checkingStack; | |
break; | |
} | |
} | |
// We have no bullet stack. So we need to reload. The player will send us a message requesting we do a reload | |
// if(bulletStack.isEmpty()) | |
// { | |
// return; | |
// } | |
// if(bulletStack.getItem() instanceof ItemShootable) | |
if(true) | |
{ | |
// ShootableType bullet = ((ItemShootable)bulletStack.getItem()).type; | |
// | |
// if(!isExtraBullet) | |
// { | |
// //Damage the bullet item | |
// bulletStack.setItemDamage(bulletStack.getItemDamage() + 1); | |
// | |
// //Update the stack in the gun | |
// setBulletItemStack(gunstack, bulletStack, bulletID); | |
// | |
// } | |
// Spawn an entity, classic style | |
if(shotData instanceof SpawnEntityShotData) | |
{ | |
// Play a sound if the previous sound has finished | |
if(soundDelay <= 0 && type.shootSound != null) | |
{ | |
AttachmentType barrel = type.getBarrel(gunstack); | |
boolean silenced = barrel != null && barrel.silencer; | |
//world.playSoundAtEntity(entityplayer, type.shootSound, 10F, type.distortSound ? 1.0F / (world.rand.nextFloat() * 0.4F + 0.8F) : 1.0F); | |
PacketPlaySound.sendSoundPacket(player.posX, player.posY, player.posZ, FlansMod.soundRange, player.dimension, type.shootSound, type.distortSound, silenced); | |
soundDelay = type.shootSoundLength; | |
} | |
//Shoot | |
// Spawn the bullet entities | |
for(int k = 0; k < type.numBullets * bullet.numBullets; k++) | |
{ | |
// Actually shoot the bullet | |
((ItemShootable)bulletStack.getItem()).Shoot(world, | |
new Vector3f(player.posX, player.posY + player.getEyeHeight(), player.posZ), | |
new Vector3f(player.getLookVec()), | |
type.getDamage(gunstack), | |
(player.isSneaking() ? 0.7F : 1F) * type.getSpread(gunstack) * bullet.bulletSpread, | |
type.getBulletSpeed(gunstack), | |
type, | |
player); | |
} | |
} | |
// Do a raytrace check on what they've sent us. | |
else if(shotData instanceof InstantShotData) | |
{ | |
InstantShotData instantData = (InstantShotData)shotData; | |
//if(stuff) | |
//{ | |
// calculate our own raytrace to verify they're not cheating | |
//} | |
// else | |
{ | |
// Take a point halfway along. Then make the radius encapsulate both ends and then some | |
//Vector3f targetPoint = Vector3f.add(instantData.origin, instantData.hitPos, null); | |
//targetPoint.scale(0.5f); | |
//float radius = Vector3f.sub(instantData.origin, instantData.hitPos, null).length(); | |
//radius += 50.0f; | |
AttachmentType barrel = type.getBarrel(gunstack); | |
boolean silenced = barrel != null && barrel.silencer; | |
DoInstantShot(world, player, type, (BulletType)bullet, instantData.origin, instantData.hitPos, instantData.hitData, type.getDamage(gunstack), isExtraBullet, silenced); | |
shotsFiredServer.add(shotData); | |
} | |
} | |
} | |
} | |
@SideOnly(Side.CLIENT) | |
private void PlayShotSound(World world, boolean silenced, float x, float y, float z) | |
{ | |
FMLClientHandler.instance().getClient().getSoundHandler().playSound( | |
new PositionedSoundRecord(FlansModResourceHandler.getSoundEvent(type.shootSound), | |
SoundCategory.PLAYERS, | |
silenced ? 5F : 10F, | |
(type.distortSound ? 1.0F / (world.rand.nextFloat() * 0.4F + 0.8F) : 1.0F) * (silenced ? 2F : 1F), | |
x, y, z)); | |
} | |
public void DoInstantShot(World world, EntityLivingBase shooter, InfoType shotFrom, BulletType shotType, Vector3f origin, Vector3f hit, BulletHit hitData, float damage, boolean isExtraBullet, boolean silenced) | |
{ | |
if(EntityBullet.OnHit(world, origin, hit, shooter, shotFrom, shotType, null, damage, hitData)) | |
{ | |
EntityBullet fakeBullet = new EntityBullet(world, hit.toVec3(), 0f, 0f, shooter, 0f, 0f, shotType, 0f, shotFrom); | |
EntityBullet.OnDetonate(world, hit, shooter, fakeBullet, shotFrom, shotType); | |
} | |
if(world.isRemote) | |
{ | |
// Play a sound if the previous sound has finished | |
if(!isExtraBullet && soundDelay <= 0 && type.shootSound != null && shooter != null) | |
{ | |
PlayShotSound(world, silenced, (float)shooter.posX, (float)shooter.posY, (float)shooter.posZ); | |
soundDelay = type.shootSoundLength; | |
} | |
if(FlansMod.DEBUG) | |
{ | |
world.spawnEntity(new EntityDebugVector(world, origin, Vector3f.sub(hit, origin, null), 100, 0.5f, 0.5f, 1.0f)); | |
} | |
InstantBulletRenderer.AddTrail(new InstantShotTrail(origin, hit, shotType)); | |
if(hitData instanceof BlockHit) | |
{ | |
BlockHit blockHit = (BlockHit)hitData; | |
BlockPos blockPos = blockHit.raytraceResult.getBlockPos(); | |
IBlockState blockState = world.getBlockState(blockHit.raytraceResult.getBlockPos()); | |
Vec3i normal = blockHit.raytraceResult.sideHit.getDirectionVec(); | |
Vector3f bulletDir = Vector3f.sub(hit, origin, null); | |
bulletDir.normalise(); | |
bulletDir.scale(0.5f); | |
for(int i = 0; i < 2; i++) | |
{ | |
// TODO: [1.12] Check why this isn't moving right | |
float scale = (float)world.rand.nextGaussian() * 0.1f + 0.5f; | |
double motionX = (double)normal.getX() * scale + world.rand.nextGaussian() * 0.025d; | |
double motionY = (double)normal.getY() * scale + world.rand.nextGaussian() * 0.025d; | |
double motionZ = (double)normal.getZ() * scale + world.rand.nextGaussian() * 0.025d; | |
motionX += bulletDir.x; | |
motionY += bulletDir.y; | |
motionZ += bulletDir.z; | |
Minecraft.getMinecraft().effectRenderer.spawnEffectParticle( | |
EnumParticleTypes.BLOCK_CRACK.getParticleID(), hit.x, hit.y, hit.z, motionX, motionY, motionZ, | |
Block.getIdFromBlock(blockState.getBlock())); | |
} | |
double scale = world.rand.nextGaussian() * 0.05d + 0.05d; | |
double motionX = (double)normal.getX() * scale + world.rand.nextGaussian() * 0.025d; | |
double motionY = (double)normal.getY() * scale + world.rand.nextGaussian() * 0.025d; | |
double motionZ = (double)normal.getZ() * scale + world.rand.nextGaussian() * 0.025d; | |
Particle fx = Minecraft.getMinecraft().effectRenderer.spawnEffectParticle(EnumParticleTypes.CLOUD.getParticleID(), hit.x, hit.y, hit.z, motionX, motionY, motionZ); | |
} | |
if(shooter == Minecraft.getMinecraft().player) | |
{ | |
if(hitData instanceof EntityHit || hitData instanceof DriveableHit) | |
{ | |
// Add a hit marker | |
FlansModClient.AddHitMarker(); | |
} | |
else if(hitData instanceof PlayerBulletHit) | |
{ | |
// Check teams | |
if(FlansModClient.teamInfo != null) | |
{ | |
Team shooterTeam = FlansModClient.teamInfo.getTeam((EntityPlayer)shooter); | |
Team victimTeam = FlansModClient.teamInfo.getTeam(((PlayerBulletHit)hitData).hitbox.player); | |
if(shooterTeam == null || shooterTeam != victimTeam) | |
{ | |
FlansModClient.AddHitMarker(); | |
} | |
} | |
else // No teams mod, just add marker | |
{ | |
FlansModClient.AddHitMarker(); | |
} | |
} | |
} | |
} | |
} | |
public void onUpdateServer(ItemStack itemstack, int gunSlot, World world, Entity entity, EnumHand hand, boolean hasOffHand) | |
{ | |
if(!(entity instanceof EntityPlayerMP)) | |
{ | |
return; | |
} | |
EntityPlayerMP player = (EntityPlayerMP)entity; | |
PlayerData data = PlayerHandler.getPlayerData(player); | |
if(data == null) | |
return; | |
if(player.inventory.getCurrentItem() != itemstack) | |
{ | |
//If the player is no longer holding a gun, emulate a release of the shoot button | |
if(player.inventory.getCurrentItem().isEmpty() || !(player.inventory.getCurrentItem().getItem() instanceof ItemGun)) | |
{ | |
data.isShootingRight = data.isShootingLeft = false; | |
} | |
return; | |
} | |
if(!shotsFiredServer.isEmpty())// && entity.ticksExisted % SERVER_TO_CLIENT_UPDATE_INTERVAL == 0) | |
{ | |
FlansMod.getPacketHandler().sendToDimension(new PacketShotData(shotsFiredServer), player.dimension); | |
shotsFiredServer.clear(); | |
} | |
} | |
/** | |
* Generic update method. If we have an off hand weapon, it will also make calls for that | |
* Passes on to onUpdateEach | |
*/ | |
@Override | |
public void onUpdate(ItemStack itemstack, World world, Entity entity, int i, boolean flag) | |
{ | |
if(entity instanceof EntityPlayer) | |
{ | |
EntityPlayer player = (EntityPlayer)entity; | |
EnumHand hand; | |
if(itemstack == player.getHeldItemMainhand()) | |
{ | |
hand = EnumHand.MAIN_HAND; | |
} | |
else if(itemstack == player.getHeldItemOffhand()) | |
{ | |
hand = EnumHand.OFF_HAND; | |
} | |
else | |
{ | |
return; | |
} | |
if(world.isRemote && Minecraft.getMinecraft().currentScreen == null) | |
{ | |
// Get button presses. Do this before splitting into each hand. Prevents second pass wiping the data | |
lastRightMouseHeld = rightMouseHeld; | |
lastLeftMouseHeld = leftMouseHeld; | |
rightMouseHeld = Mouse.isButtonDown(1); | |
leftMouseHeld = Mouse.isButtonDown(0); | |
} | |
ItemStack main = player.getHeldItemMainhand(); | |
ItemStack off = player.getHeldItemOffhand(); | |
boolean hasOffHand = !main.isEmpty() && !off.isEmpty(); | |
onUpdateEach(itemstack, i, world, entity, hand, hasOffHand); | |
} | |
} | |
/** | |
* Called once for each weapon we are weilding | |
*/ | |
private void onUpdateEach(ItemStack itemstack, int gunSlot, World world, Entity entity, EnumHand hand, boolean hasOffHand) | |
{ | |
if(world.isRemote) | |
onUpdateClient(itemstack, gunSlot, world, entity, hand, hasOffHand); | |
else onUpdateServer(itemstack, gunSlot, world, entity, hand, hasOffHand); | |
} | |
public boolean Reload(ItemStack gunstack, World world, Entity entity, IInventory inventory, EnumHand hand, boolean hasOffHand, boolean forceReload, boolean isCreative) | |
{ | |
//Deployable guns cannot be reloaded in the inventory | |
if(type.deployable) | |
return false; | |
//If you cannot reload half way through a clip, reject the player for trying to do so | |
if(forceReload && !type.canForceReload) | |
return false; | |
//For playing sounds afterwards | |
boolean reloadedSomething = false; | |
//Check each ammo slot, one at a time | |
for(int i = 0; i < type.numAmmoItemsInGun; i++) | |
{ | |
//Get the stack in the slot | |
ItemStack bulletStack = getBulletItemStack(gunstack, i); | |
//If there is no magazine, if the magazine is empty or if this is a forced reload | |
if(bulletStack == null || bulletStack.isEmpty() || bulletStack.getItemDamage() == bulletStack.getMaxDamage() || forceReload) | |
{ | |
//Iterate over all inventory slots and find the magazine / bullet item with the most bullets | |
int bestSlot = -1; | |
int bulletsInBestSlot = 0; | |
for(int j = 0; j < inventory.getSizeInventory(); j++) | |
{ | |
ItemStack item = inventory.getStackInSlot(j); | |
if(item.getItem() instanceof ItemShootable && type.isAmmo(((ItemShootable)(item.getItem())).type)) | |
{ | |
int bulletsInThisSlot = item.getMaxDamage() - item.getItemDamage(); | |
if(bulletsInThisSlot > bulletsInBestSlot) | |
{ | |
bestSlot = j; | |
bulletsInBestSlot = bulletsInThisSlot; | |
} | |
} | |
} | |
//If there was a valid non-empty magazine / bullet item somewhere in the inventory, load it | |
if(bestSlot != -1) | |
{ | |
ItemStack newBulletStack = inventory.getStackInSlot(bestSlot); | |
ShootableType newBulletType = ((ItemShootable)newBulletStack.getItem()).type; | |
//Unload the old magazine (Drop an item if it is required and the player is not in creative mode) | |
if(bulletStack != null && bulletStack.getItem() instanceof ItemShootable && ((ItemShootable)bulletStack.getItem()).type.dropItemOnReload != null && !isCreative && bulletStack.getItemDamage() == bulletStack.getMaxDamage()) | |
{ | |
if(!world.isRemote) | |
dropItem(world, entity, ((ItemShootable)bulletStack.getItem()).type.dropItemOnReload); | |
} | |
//The magazine was not finished, pull it out and give it back to the player or, failing that, drop it | |
if(bulletStack != null && !bulletStack.isEmpty() && bulletStack.getItemDamage() < bulletStack.getMaxDamage()) | |
{ | |
if(!InventoryHelper.addItemStackToInventory(inventory, bulletStack, isCreative)) | |
{ | |
if(!world.isRemote) | |
entity.entityDropItem(bulletStack, 0.5F); | |
} | |
} | |
//Load the new magazine | |
ItemStack stackToLoad = newBulletStack.copy(); | |
stackToLoad.setCount(1); | |
setBulletItemStack(gunstack, stackToLoad, i); | |
//Remove the magazine from the inventory | |
if(!isCreative) | |
newBulletStack.setCount(newBulletStack.getCount() - 1); | |
if(newBulletStack.getCount() <= 0) | |
newBulletStack = ItemStack.EMPTY.copy(); | |
inventory.setInventorySlotContents(bestSlot, newBulletStack); | |
//Tell the sound player that we reloaded something | |
reloadedSomething = true; | |
} | |
} | |
} | |
return reloadedSomething; | |
} | |
// TODO : All this bunk | |
/* Melee MESS | |
* @Override | |
public void onUpdate(ItemStack itemstack, World world, Entity pEnt, int i, boolean flag) | |
{ | |
if(world.isRemote) | |
onUpdateClient(itemstack, world, pEnt, i, flag); | |
else onUpdateServer(itemstack, world, pEnt, i, flag); | |
if(pEnt instanceof EntityPlayer) | |
{ | |
EntityPlayer player = (EntityPlayer)pEnt; | |
PlayerData data = PlayerHandler.getPlayerData(player); | |
if(data == null) | |
return; | |
//if(data.lastMeleePositions == null || data.lastMeleePositions.length != type.meleeDamagePoints.size()) | |
//{ | |
// data.lastMeleePositions = new Vector3f[type.meleeDamagePoints.size()]; | |
// for(int j = 0; j < type.meleeDamagePoints.size(); j++) | |
// data.lastMeleePositions[j] = new Vector3f(player.posX, player.posY, player.posZ); | |
//} | |
//Melee weapon | |
if(data.meleeLength > 0 && type.meleePath.size() > 0 && player.inventory.getCurrentItem() == itemstack) | |
{ | |
for(int k = 0; k < type.meleeDamagePoints.size(); k++) | |
{ | |
Vector3f meleeDamagePoint = type.meleeDamagePoints.get(k); | |
//Do a raytrace from the prev pos to the current pos and attack anything in the way | |
Vector3f nextPos = type.meleePath.get((data.meleeProgress + 1) % type.meleePath.size()); | |
Vector3f nextAngles = type.meleePathAngles.get((data.meleeProgress + 1) % type.meleePathAngles.size()); | |
RotatedAxes nextAxes = new RotatedAxes().rotateGlobalRoll(-nextAngles.x).rotateGlobalPitch(-nextAngles.z).rotateGlobalYaw(-nextAngles.y); | |
Vector3f nextPosInGunCoords = nextAxes.findLocalVectorGlobally(meleeDamagePoint); | |
Vector3f.add(nextPos, nextPosInGunCoords, nextPosInGunCoords); | |
Vector3f.add(new Vector3f(0F, 0F, 0F), nextPosInGunCoords, nextPosInGunCoords); | |
Vector3f nextPosInPlayerCoords = new RotatedAxes(player.rotationYaw + 90F, player.rotationPitch, 0F).findLocalVectorGlobally(nextPosInGunCoords); | |
if(!FlansMod.proxy.isThePlayer(player)) | |
nextPosInPlayerCoords.y += 1.6F; | |
Vector3f nextPosInWorldCoords = new Vector3f(player.posX + nextPosInPlayerCoords.x, player.posY + nextPosInPlayerCoords.y, player.posZ + nextPosInPlayerCoords.z); | |
Vector3f dPos = data.lastMeleePositions[k] == null ? new Vector3f() : Vector3f.sub(nextPosInWorldCoords, data.lastMeleePositions[k], null); | |
if(player.world.isRemote && FlansMod.DEBUG) | |
player.world.spawnEntity(new EntityDebugVector(player.world, data.lastMeleePositions[k], dPos, 200, 1F, 0F, 0F)); | |
//Do the raytrace | |
{ | |
//Create a list for all bullet hits | |
ArrayList<BulletHit> hits = new ArrayList<BulletHit>(); | |
//Iterate over all entities | |
for(int j = 0; j < world.loadedEntityList.size(); j++) | |
{ | |
Object obj = world.loadedEntityList.get(j); | |
//Get players | |
if(obj instanceof EntityPlayer) | |
{ | |
EntityPlayer otherPlayer = (EntityPlayer)obj; | |
PlayerData otherData = PlayerHandler.getPlayerData(otherPlayer); | |
boolean shouldDoNormalHitDetect = false; | |
if(otherPlayer == player) | |
continue; | |
if(otherData != null) | |
{ | |
if(otherPlayer.isDead || otherData.team == Team.spectators) | |
{ | |
continue; | |
} | |
int snapshotToTry = player instanceof EntityPlayerMP ? ((EntityPlayerMP)player).ping / 50 : 0; | |
if(snapshotToTry >= otherData.snapshots.length) | |
snapshotToTry = otherData.snapshots.length - 1; | |
PlayerSnapshot snapshot = otherData.snapshots[snapshotToTry]; | |
if(snapshot == null) | |
snapshot = otherData.snapshots[0]; | |
//DEBUG | |
//snapshot = new PlayerSnapshot(player); | |
//Check one last time for a null snapshot. If this is the case, fall back to normal hit detection | |
if(snapshot == null) | |
shouldDoNormalHitDetect = true; | |
else | |
{ | |
//Raytrace | |
ArrayList<BulletHit> playerHits = snapshot.raytrace(data.lastMeleePositions[k] == null ? nextPosInWorldCoords : data.lastMeleePositions[k], dPos); | |
hits.addAll(playerHits); | |
} | |
} | |
//If we couldn't get a snapshot, use normal entity hitbox calculations | |
if(otherData == null || shouldDoNormalHitDetect) | |
{ | |
RayTraceResult mop = data.lastMeleePositions[k] == null ? player.getEntityBoundingBox().calculateIntercept(nextPosInWorldCoords.toVec3(), new Vec3d(0F, 0F, 0F)) : player.getBoundingBox().calculateIntercept(data.lastMeleePositions[k].toVec3(), nextPosInWorldCoords.toVec3()); | |
if(mop != null) | |
{ | |
Vector3f hitPoint = new Vector3f(mop.hitVec.x - data.lastMeleePositions[k].x, mop.hitVec.y - data.lastMeleePositions[k].y, mop.hitVec.z - data.lastMeleePositions[k].z); | |
float hitLambda = 1F; | |
if(dPos.x != 0F) | |
hitLambda = hitPoint.x / dPos.x; | |
else if(dPos.y != 0F) | |
hitLambda = hitPoint.y / dPos.y; | |
else if(dPos.z != 0F) | |
hitLambda = hitPoint.z / dPos.z; | |
if(hitLambda < 0) | |
hitLambda = -hitLambda; | |
hits.add(new PlayerBulletHit(new PlayerHitbox(otherPlayer, new RotatedAxes(), new Vector3f(), new Vector3f(), new Vector3f(), EnumHitboxType.BODY), hitLambda)); | |
} | |
} | |
} | |
else | |
{ | |
Entity entity = (Entity)obj; | |
if(entity != player && !entity.isDead && (entity instanceof EntityLivingBase || entity instanceof EntityAAGun)) | |
{ | |
RayTraceResult mop = entity.getEntityBoundingBox().calculateIntercept(data.lastMeleePositions[k].toVec3(), nextPosInWorldCoords.toVec3()); | |
if(mop != null) | |
{ | |
Vector3f hitPoint = new Vector3f(mop.hitVec.x - data.lastMeleePositions[k].x, mop.hitVec.y - data.lastMeleePositions[k].y, mop.hitVec.z - data.lastMeleePositions[k].z); | |
float hitLambda = 1F; | |
if(dPos.x != 0F) | |
hitLambda = hitPoint.x / dPos.x; | |
else if(dPos.y != 0F) | |
hitLambda = hitPoint.y / dPos.y; | |
else if(dPos.z != 0F) | |
hitLambda = hitPoint.z / dPos.z; | |
if(hitLambda < 0) | |
hitLambda = -hitLambda; | |
hits.add(new EntityHit(entity, hitLambda)); | |
} | |
} | |
} | |
} | |
//We hit something | |
if(!hits.isEmpty()) | |
{ | |
//Sort the hits according to the intercept position | |
Collections.sort(hits); | |
float swingDistance = dPos.length(); | |
for(BulletHit bulletHit : hits) | |
{ | |
if(bulletHit instanceof PlayerBulletHit) | |
{ | |
PlayerBulletHit playerHit = (PlayerBulletHit)bulletHit; | |
float damageMultiplier = 1F; | |
switch(playerHit.hitbox.type) | |
{ | |
case LEFTITEM : case RIGHTITEM : //Hit a shield. Stop the swing. | |
{ | |
data.meleeProgress = data.meleeLength = 0; | |
return; | |
} | |
case HEAD : damageMultiplier = 2F; break; | |
case RIGHTARM : case LEFTARM : damageMultiplier = 0.6F; break; | |
default : | |
} | |
if(playerHit.hitbox.player.attackEntityFrom(getMeleeDamage(player), swingDistance * type.meleeDamage)) | |
{ | |
//If the attack was allowed, we should remove their immortality cooldown so we can shoot them again. Without this, any rapid fire gun become useless | |
playerHit.hitbox.player.arrowHitTimer++; | |
playerHit.hitbox.player.hurtResistantTime = playerHit.hitbox.player.maxHurtResistantTime / 2; | |
} | |
if(FlansMod.DEBUG) | |
world.spawnEntity(new EntityDebugDot(world, new Vector3f(data.lastMeleePositions[k].x + dPos.x * playerHit.intersectTime, data.lastMeleePositions[k].y + dPos.y * playerHit.intersectTime, data.lastMeleePositions[k].z + dPos.z * playerHit.intersectTime), 1000, 1F, 0F, 0F)); | |
} | |
else if(bulletHit instanceof EntityHit) | |
{ | |
EntityHit entityHit = (EntityHit)bulletHit; | |
if(entityHit.entity.attackEntityFrom(DamageSource.causePlayerDamage(player), swingDistance * type.meleeDamage) && entityHit.entity instanceof EntityLivingBase) | |
{ | |
EntityLivingBase living = (EntityLivingBase)entityHit.entity; | |
//If the attack was allowed, we should remove their immortality cooldown so we can shoot them again. Without this, any rapid fire gun become useless | |
living.arrowHitTimer++; | |
living.hurtResistantTime = living.maxHurtResistantTime / 2; | |
} | |
if(FlansMod.DEBUG) | |
world.spawnEntity(new EntityDebugDot(world, new Vector3f(data.lastMeleePositions[k].x + dPos.x * entityHit.intersectTime, data.lastMeleePositions[k].y + dPos.y * entityHit.intersectTime, data.lastMeleePositions[k].z + dPos.z * entityHit.intersectTime), 1000, 1F, 0F, 0F)); | |
} | |
} | |
} | |
} | |
//End raytrace | |
data.lastMeleePositions[k] = nextPosInWorldCoords; | |
} | |
//Increment the progress meter | |
data.meleeProgress++; | |
//If we are done, reset the counters | |
if(data.meleeProgress == data.meleeLength) | |
data.meleeProgress = data.meleeLength = 0; | |
} | |
} | |
} | |
* | |
*/ | |
private boolean needsToReload(ItemStack stack) | |
{ | |
for(int i = 0; i < type.numAmmoItemsInGun; i++) | |
{ | |
ItemStack bulletStack = getBulletItemStack(stack, i); | |
if(bulletStack != null && !bulletStack.isEmpty() && bulletStack.getItemDamage() < bulletStack.getMaxDamage()) | |
{ | |
return false; | |
} | |
} | |
return true; | |
} | |
public boolean CanReload(ItemStack gunstack, IInventory inventory) | |
{ | |
for(int i = 0; i < inventory.getSizeInventory(); i++) | |
{ | |
ItemStack stack = inventory.getStackInSlot(i); | |
if(type.isAmmo(stack)) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} | |
private ItemStack getBestNonEmptyShootableStack(ItemStack stack) | |
{ | |
for(int i = 0; i < type.numAmmoItemsInGun; i++) | |
{ | |
ItemStack shootableStack = getBulletItemStack(stack, i); | |
if(shootableStack != null && !shootableStack.isEmpty() && shootableStack.getItemDamage() < shootableStack.getMaxDamage()) | |
{ | |
return shootableStack; | |
} | |
} | |
return null; | |
} | |
// _____________________________________________________________________________ | |
// | |
// Minecraft base item overrides | |
// _____________________________________________________________________________ | |
@Override | |
public void addInformation(ItemStack stack, World world, List<String> lines, ITooltipFlag b) | |
{ | |
if(type.description != null) | |
{ | |
Collections.addAll(lines, type.description.split("_")); | |
} | |
if(type.showDamage) | |
lines.add("\u00a79Damage" + "\u00a77: " + type.getDamage(stack)); | |
if(type.showRecoil) | |
lines.add("\u00a79Recoil" + "\u00a77: " + type.getRecoil(stack)); | |
if(type.showSpread) | |
lines.add("\u00a79Accuracy" + "\u00a77: " + type.getSpread(stack)); | |
if(type.showReloadTime) | |
lines.add("\u00a79Reload Time" + "\u00a77: " + type.getReloadTime(stack) / 20 + "s"); | |
for(AttachmentType attachment : type.getCurrentAttachments(stack)) | |
{ | |
if(type.showAttachments) | |
{ | |
String line = attachment.name; | |
lines.add(line); | |
} | |
} | |
for(int i = 0; i < type.numAmmoItemsInGun; i++) | |
{ | |
ItemStack bulletStack = getBulletItemStack(stack, i); | |
if(bulletStack != null && bulletStack.getItem() instanceof ItemBullet) | |
{ | |
BulletType bulletType = ((ItemBullet)bulletStack.getItem()).type; | |
//String line = bulletType.name + (bulletStack.getMaxDamage() == 1 ? "" : " " + (bulletStack.getMaxDamage() - bulletStack.getItemDamage()) + "/" + bulletStack.getMaxDamage()); | |
String line = bulletType.name + " " + (bulletStack.getMaxDamage() - bulletStack.getItemDamage()) + "/" + bulletStack.getMaxDamage(); | |
lines.add(line); | |
} | |
} | |
} | |
@Override | |
/** Make sure client and server side NBTtags update */ | |
public boolean getShareTag() | |
{ | |
return true; | |
} | |
public DamageSource getMeleeDamage(EntityPlayer attacker) | |
{ | |
return new EntityDamageSourceGun(type.shortName, attacker, attacker, type, false); | |
} | |
private boolean isSolid(World world, int i, int j, int k) | |
{ | |
IBlockState state = world.getBlockState(new BlockPos(i, j, k)); | |
return state.getMaterial().isSolid() && state.isOpaqueCube(); | |
} | |
//Stop damage being done to entities when scoping etc. | |
@Override | |
public boolean onLeftClickEntity(ItemStack stack, EntityPlayer player, Entity entity) | |
{ | |
return type.secondaryFunction != EnumSecondaryFunction.MELEE; | |
} | |
@Override | |
public boolean hasCustomEntity(ItemStack stack) | |
{ | |
return true; | |
} | |
@Nullable | |
@Override | |
public Entity createEntity(World world, Entity location, ItemStack itemstack) | |
{ | |
return new EntityItemCustomRender(location, itemstack); | |
} | |
@Override | |
public boolean isFull3D() | |
{ | |
return true; | |
} | |
@Override | |
public boolean onEntitySwing(EntityLivingBase entityLiving, ItemStack stack) | |
{ | |
if(type.meleeSound != null) | |
PacketPlaySound.sendSoundPacket(entityLiving.posX, entityLiving.posY, entityLiving.posZ, FlansMod.soundRange, entityLiving.dimension, type.meleeSound, true); | |
//Do custom melee code here | |
if(type.secondaryFunction == EnumSecondaryFunction.CUSTOM_MELEE) | |
{ | |
//Do animation | |
if(entityLiving.world.isRemote) | |
{ | |
GunAnimations animations = FlansModClient.getGunAnimations(entityLiving, EnumHand.MAIN_HAND); | |
animations.doMelee(type.meleeTime); | |
} | |
//Do custom melee hit detection | |
if(entityLiving instanceof EntityPlayer) | |
{ | |
PlayerData data = PlayerHandler.getPlayerData((EntityPlayer)entityLiving); | |
data.doMelee((EntityPlayer)entityLiving, type.meleeTime, type); | |
} | |
} | |
return type.secondaryFunction != EnumSecondaryFunction.MELEE; | |
} | |
@Override | |
public boolean onBlockStartBreak(ItemStack itemstack, BlockPos pos, EntityPlayer player) | |
{ | |
World world = player.world; | |
if(!world.isRemote) | |
{ | |
// Client will still render block break if player is in creative so update block state | |
IBlockState state = world.getBlockState(pos); | |
world.notifyBlockUpdate(pos, state, state, 3); | |
} | |
return true; | |
} | |
@Override | |
public boolean canHarvestBlock(IBlockState state, ItemStack stack) | |
{ | |
return false; | |
} | |
public boolean isItemStackDamageable() | |
{ | |
return true; | |
} | |
// ----------------- Paintjobs ----------------- | |
@Override | |
public void getSubItems(CreativeTabs tab, NonNullList<ItemStack> items) | |
{ | |
if(tab != FlansMod.tabFlanGuns && tab != CreativeTabs.SEARCH) | |
return; | |
PaintableType type = ((IPaintableItem)this).GetPaintableType(); | |
if(FlansMod.addAllPaintjobsToCreative) | |
{ | |
for(Paintjob paintjob : type.paintjobs) | |
addPaintjobToList(this, type, paintjob, items); | |
} | |
else addPaintjobToList(this, type, type.defaultPaintjob, items); | |
} | |
private void addPaintjobToList(Item item, PaintableType type, Paintjob paintjob, List<ItemStack> list) | |
{ | |
ItemStack paintableStack = new ItemStack(item, 1, paintjob.ID); | |
NBTTagCompound tags = new NBTTagCompound(); | |
paintableStack.setTagCompound(tags); | |
list.add(paintableStack); | |
} | |
// --------------------------------------------- | |
@Override | |
public int getMaxItemUseDuration(ItemStack par1ItemStack) | |
{ | |
return 100; | |
} | |
@Override | |
public EnumAction getItemUseAction(ItemStack par1ItemStack) | |
{ | |
return EnumAction.BOW; | |
} | |
protected static final UUID KNOCKBACK_RESIST_MODIFIER = UUID.fromString("77777777-645C-4F38-A497-9C13A33DB5CF"); | |
protected static final UUID MOVEMENT_SPEED_MODIFIER = UUID.fromString("99999999-4180-4865-B01B-BCCE9785ACA3"); | |
@Override | |
public Multimap<String, AttributeModifier> getAttributeModifiers(EntityEquipmentSlot slot, ItemStack stack) | |
{ | |
Multimap<String, AttributeModifier> multimap = super.getAttributeModifiers(slot, stack); | |
if(slot == EntityEquipmentSlot.MAINHAND) | |
{ | |
multimap.put(SharedMonsterAttributes.KNOCKBACK_RESISTANCE.getName(), new AttributeModifier(KNOCKBACK_RESIST_MODIFIER, "KnockbackResist", type.knockbackModifier, 0)); | |
multimap.put(SharedMonsterAttributes.MOVEMENT_SPEED.getName(), new AttributeModifier(MOVEMENT_SPEED_MODIFIER, "MovementSpeed", type.moveSpeedModifier - 1.0f, 2)); | |
multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(ATTACK_DAMAGE_MODIFIER, "Weapon modifier", type.meleeDamage, 0)); | |
} | |
return multimap; | |
} | |
@Override | |
public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStack, boolean slotChanged) | |
{ | |
return slotChanged; | |
} | |
// For when we have custom paintjob names | |
//@Override | |
//public String getTranslationKey(ItemStack stack) | |
//{ | |
// return getTranslationKey();//stack.getTagCompound().getString("Paint"); | |
//} | |
@Override | |
public boolean canItemEditBlocks() | |
{ | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How do we implement this bug fix?