From b0bf2b6511142c79bf1c48481f318ab6325d9c96 Mon Sep 17 00:00:00 2001 From: Bob Date: Mon, 30 Jan 2023 20:49:41 +0100 Subject: [PATCH] SPAS spent shells, fixed lighting and wobblyness --- .../com/hbm/handler/GunConfiguration.java | 3 +- .../hbm/handler/guncfg/Gun12GaugeFactory.java | 34 + .../com/hbm/items/weapon/ItemGunBase.java | 24 + src/main/java/com/hbm/main/ClientProxy.java | 6 + .../java/com/hbm/main/ResourceManager.java | 2 + .../com/hbm/particle/ParticleSpentCasing.java | 209 ++++ .../com/hbm/particle/SpentCasingConfig.java | 323 +++++ .../particle/SpentCasingConfigBuilder.java | 377 ++++++ .../assets/hbm/models/effect/casings.obj | 1084 +++++++++++++++++ .../assets/hbm/textures/particle/casings.png | Bin 0 -> 10168 bytes 10 files changed, 2061 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/hbm/particle/ParticleSpentCasing.java create mode 100644 src/main/java/com/hbm/particle/SpentCasingConfig.java create mode 100644 src/main/java/com/hbm/particle/SpentCasingConfigBuilder.java create mode 100644 src/main/resources/assets/hbm/models/effect/casings.obj create mode 100644 src/main/resources/assets/hbm/textures/particle/casings.png diff --git a/src/main/java/com/hbm/handler/GunConfiguration.java b/src/main/java/com/hbm/handler/GunConfiguration.java index cfac864c4..4efa68883 100644 --- a/src/main/java/com/hbm/handler/GunConfiguration.java +++ b/src/main/java/com/hbm/handler/GunConfiguration.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.List; import com.hbm.lib.HbmCollection.EnumGunManufacturer; +import com.hbm.particle.SpentCasingConfig; import com.hbm.render.anim.BusAnimation; import com.hbm.render.anim.HbmAnimations.AnimType; import com.hbm.render.util.RenderScreenOverlay.Crosshair; @@ -77,7 +78,7 @@ public class GunConfiguration implements Cloneable { public Crosshair crosshair; //casing eject behavior - //public Optional casingConfig = Optional.empty(); + public SpentCasingConfig casingConfig = null; public static final int MODE_NORMAL = 0; public static final int MODE_RELEASE = 1; diff --git a/src/main/java/com/hbm/handler/guncfg/Gun12GaugeFactory.java b/src/main/java/com/hbm/handler/guncfg/Gun12GaugeFactory.java index 4d8a728d8..5ad09cde8 100644 --- a/src/main/java/com/hbm/handler/guncfg/Gun12GaugeFactory.java +++ b/src/main/java/com/hbm/handler/guncfg/Gun12GaugeFactory.java @@ -12,6 +12,9 @@ import com.hbm.items.ItemAmmoEnums.Ammo12Gauge; import com.hbm.items.ModItems; import com.hbm.lib.HbmCollection; import com.hbm.lib.HbmCollection.EnumGunManufacturer; +import com.hbm.particle.SpentCasingConfig; +import com.hbm.particle.SpentCasingConfig.CasingType; +import com.hbm.particle.SpentCasingConfigBuilder; import com.hbm.potion.HbmPotion; import com.hbm.render.anim.BusAnimation; import com.hbm.render.anim.BusAnimationKeyframe; @@ -22,9 +25,38 @@ import com.hbm.render.util.RenderScreenOverlay.Crosshair; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.potion.PotionEffect; +import net.minecraft.util.Vec3; public class Gun12GaugeFactory { + static final SpentCasingConfig CASING_SPAS, CASING_SPAS_ALT, CASING_BENELLI, CASING_UBOINIK, CASING_SSG; + + static + { + final SpentCasingConfigBuilder CASING_12G_BUILDER = new SpentCasingConfigBuilder("", CasingType.SHOTGUN, false) + .setScaleX(1.5f).setScaleY(1.5f).setScaleZ(1.5f); + CASING_SPAS = CASING_12G_BUILDER.setRegistryName("spas12").setInitialMotion(Vec3.createVectorHelper(-0.4, 0.1, 0)) + .setPosOffset(Vec3.createVectorHelper(-0.35, 0, 0.5)).setPitchFactor(0.03f).setYawFactor(0.01f) + .setSmokeChance(0).setDelay(10) + .build(); + + CASING_SPAS_ALT = CASING_12G_BUILDER.setRegistryName("spas12alt").setCasingAmount(2) + .build(); + + CASING_BENELLI = CASING_12G_BUILDER.setRegistryName("benelli").setCasingAmount(1).setDelay(0) + .setInitialMotion(Vec3.createVectorHelper(-0.3, 1, 0)) + .build(); + + CASING_UBOINIK = CASING_12G_BUILDER.setRegistryName("uboinik").setOverrideColor(true) + .setBlueOverride(255).setPosOffset(Vec3.createVectorHelper(-0.35, -0.3, 0.5)) + .build(); + + CASING_SSG = CASING_12G_BUILDER.setRegistryName("ssg").setBlueOverride(0).setRedOverride(255).setCasingAmount(2) + .setPosOffset(Vec3.createVectorHelper(0.8, 0, 0)).setInitialMotion(Vec3.createVectorHelper(0.2, 0, -0.2)) + .setPitchFactor(0.05f).setYawFactor(0.02f) + .build(); + } + public static GunConfiguration getSpas12Config() { GunConfiguration config = new GunConfiguration(); @@ -66,6 +98,8 @@ public class Gun12GaugeFactory { ) ); + config.casingConfig = CASING_SPAS; + return config; } diff --git a/src/main/java/com/hbm/items/weapon/ItemGunBase.java b/src/main/java/com/hbm/items/weapon/ItemGunBase.java index fb39750dc..ef1b37507 100644 --- a/src/main/java/com/hbm/items/weapon/ItemGunBase.java +++ b/src/main/java/com/hbm/items/weapon/ItemGunBase.java @@ -15,9 +15,11 @@ import com.hbm.interfaces.IItemHUD; import com.hbm.inventory.RecipesCommon.ComparableStack; import com.hbm.items.IEquipReceiver; import com.hbm.lib.HbmCollection; +import com.hbm.main.MainRegistry; import com.hbm.packet.GunAnimationPacket; import com.hbm.packet.GunButtonPacket; import com.hbm.packet.PacketDispatcher; +import com.hbm.particle.SpentCasingConfig; import com.hbm.render.anim.BusAnimation; import com.hbm.render.anim.HbmAnimations.AnimType; import com.hbm.render.util.RenderScreenOverlay; @@ -208,6 +210,9 @@ public class ItemGunBase extends Item implements IHoldableWeapon, IItemHUD, IEqu } world.playSoundAtEntity(player, mainConfig.firingSound, 1.0F, mainConfig.firingPitch); + + if(mainConfig.casingConfig != null && !mainConfig.casingConfig.isAfterReload()) + spawnCasing(player, mainConfig.casingConfig, stack); } //unlike fire(), being called does not automatically imply success, some things may still have to be handled before spawning the projectile @@ -239,6 +244,9 @@ public class ItemGunBase extends Item implements IHoldableWeapon, IItemHUD, IEqu } world.playSoundAtEntity(player, altConfig.firingSound, 1.0F, altConfig.firingPitch); + + if(altConfig.casingConfig != null) + spawnCasing(player, altConfig.casingConfig, stack); } //spawns the actual projectile, can be overridden to change projectile entity @@ -319,6 +327,9 @@ public class ItemGunBase extends Item implements IHoldableWeapon, IItemHUD, IEqu if(hasLoaded && mainConfig.reloadSoundEnd) world.playSoundAtEntity(player, mainConfig.reloadSound, 1.0F, 1.0F); + if(mainConfig.casingConfig != null && mainConfig.casingConfig.isAfterReload()) + spawnCasing(player, mainConfig.casingConfig, stack); + InventoryUtil.tryConsumeAStack(player.inventory.mainInventory, 0, player.inventory.mainInventory.length, ammo); } else { setReloadCycle(stack, getReloadCycle(stack) - 1); @@ -692,4 +703,17 @@ public class ItemGunBase extends Item implements IHoldableWeapon, IItemHUD, IEqu player.worldObj.playSoundAtEntity(player, mainConfig.equipSound, 1, 1); } } + + protected static void spawnCasing(Entity entity, SpentCasingConfig config, ItemStack stack) { + NBTTagCompound data = new NBTTagCompound(); + data.setString("type", "casing"); + data.setDouble("posX", entity.posX); + data.setDouble("posY", entity.posY + entity.getEyeHeight()); + data.setDouble("posZ", entity.posZ); + data.setFloat("pitch", (float) Math.toRadians(entity.rotationPitch)); + data.setFloat("yaw", (float) Math.toRadians(entity.rotationYaw)); + data.setBoolean("crouched", entity.isSneaking()); + data.setString("name", config.getRegistryName()); + MainRegistry.proxy.effectNT(data); + } } diff --git a/src/main/java/com/hbm/main/ClientProxy.java b/src/main/java/com/hbm/main/ClientProxy.java index 43c6bbb6e..185e7955e 100644 --- a/src/main/java/com/hbm/main/ClientProxy.java +++ b/src/main/java/com/hbm/main/ClientProxy.java @@ -1789,6 +1789,12 @@ public class ClientProxy extends ServerProxy { RenderOverhead.queuedMarkers.put(new BlockPos(x, y, z), new Marker(color).setDist(dist).setExpire(expires > 0 ? System.currentTimeMillis() + expires : 0).withLabel(label.isEmpty() ? null : label)); } + + if("casing".equals(type)) { + SpentCasingConfig casingConfig = SpentCasingConfig.get(data.getString("name")); + for(int i = 0; i < casingConfig.getCasingAmount(); i++) + casingConfig.spawnCasing(man, world, x, y, z, data.getFloat("pitch"), data.getFloat("yaw"), data.getBoolean("crouched")); + } } private HashMap vanished = new HashMap(); diff --git a/src/main/java/com/hbm/main/ResourceManager.java b/src/main/java/com/hbm/main/ResourceManager.java index f872cebad..7945004f4 100644 --- a/src/main/java/com/hbm/main/ResourceManager.java +++ b/src/main/java/com/hbm/main/ResourceManager.java @@ -865,6 +865,7 @@ public class ResourceManager { //Projectiles public static final IModelCustom projectiles = new HFRWavefrontObject(new ResourceLocation(RefStrings.MODID, "models/projectiles/projectiles.obj")); + public static final IModelCustom casings = new HFRWavefrontObject(new ResourceLocation(RefStrings.MODID, "models/effect/casings.obj")); //Bomber public static final IModelCustom dornier = AdvancedModelLoader.loadModel(new ResourceLocation(RefStrings.MODID, "models/dornier.obj")); @@ -990,6 +991,7 @@ public class ResourceManager { public static final ResourceLocation rocket_mirv_tex = new ResourceLocation(RefStrings.MODID, "textures/models/projectiles/rocket_mirv.png"); public static final ResourceLocation mini_nuke_tex = new ResourceLocation(RefStrings.MODID, "textures/models/projectiles/mini_nuke.png"); public static final ResourceLocation mini_mirv_tex = new ResourceLocation(RefStrings.MODID, "textures/models/projectiles/mini_mirv.png"); + public static final ResourceLocation casings_tex = new ResourceLocation(RefStrings.MODID, "textures/particle/casings.png"); //Bomber public static final ResourceLocation dornier_0_tex = new ResourceLocation(RefStrings.MODID, "textures/models/dornier_0.png"); diff --git a/src/main/java/com/hbm/particle/ParticleSpentCasing.java b/src/main/java/com/hbm/particle/ParticleSpentCasing.java new file mode 100644 index 000000000..ced897913 --- /dev/null +++ b/src/main/java/com/hbm/particle/ParticleSpentCasing.java @@ -0,0 +1,209 @@ +package com.hbm.particle; + +import java.util.ArrayList; +import java.util.List; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; + +import com.hbm.main.ResourceManager; +import com.hbm.util.Tuple.Pair; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.minecraft.client.Minecraft; +import net.minecraft.client.particle.EntityFX; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.Vec3; +import net.minecraft.world.World; + +@SideOnly(Side.CLIENT) +public class ParticleSpentCasing extends EntityFX +{ + private static float dScale = 0.05F, smokeJitter = 0.025F, smokeAccel = 0.5F; + private static byte maxSmokeGen = 60, maxSmokeLife = 120; + + private final List> smokeNodes = new ArrayList(); + + private final TextureManager textureManager; + + private final SpentCasingConfig config; + private boolean smoke; + + private float momentumPitch, momentumYaw; + private boolean onGroundPreviously = false; + private double maxHeight; + + public ParticleSpentCasing(TextureManager textureManager, World world, double x, double y, double z, double mx, double my, double mz, float momentumPitch, float momentumYaw, SpentCasingConfig config) { + super(world, x, y, z, 0, 0, 0); + this.textureManager = textureManager; + this.momentumPitch = momentumPitch; + this.momentumYaw = momentumYaw; + this.config = config; + + particleMaxAge = 240; +// smoke = config.getSmokeChance() == 0 ? true +// : config.getSmokeChance() < 0 ? false +// : rand.nextInt(config.getSmokeChance()) == 0; + + motionX = mx; + motionY = my; + motionZ = mz; + + particleGravity = 8f; + + maxHeight = y; + } + + @Override + public int getFXLayer() { + return 3; + } + + @Override + public void onUpdate() { + super.onUpdate(); + + if(motionY > 0 && posY > maxHeight) + maxHeight = posY; + + if(!onGroundPreviously && onGround) + tryPlayBounceSound(); + + // TODO Bounce factor in config + if(!onGroundPreviously && onGround) { + + onGroundPreviously = true; + motionY = Math.log10(maxHeight - posY + 2); + momentumPitch = (float) rand.nextGaussian() * config.getPitchFactor(); + momentumYaw = (float) rand.nextGaussian() * config.getYawFactor(); + maxHeight = posY; + + } else if(onGroundPreviously && !onGround) { + onGroundPreviously = false; + } + +// if (particleAge > maxSmokeLife && !smokeNodes.isEmpty()) +// smokeNodes.clear(); + +// if (smoke && particleAge <= maxSmokeLife) +// { +// final double side = (rotationYaw - prevRotationYaw) * 0.1D; +// final Vec3 prev = Vec3.createVectorHelper(motionX, motionY, motionZ); +// prev.rotateAroundY((float) Math.toRadians(rotationYaw)); +// +// for (Pair pair : smokeNodes) +// { +// final EasyLocation node = pair.getKey(); +// +// node.posX += prev.xCoord * smokeAccel + rand.nextGaussian() * smokeJitter + side; +// node.posY += prev.yCoord + smokeAccel; +// node.posZ += prev.zCoord * smokeAccel + rand.nextGaussian() * smokeJitter; +// } +// +// if (particleAge < maxSmokeGen || inWater) +// { +// final double alpha = (particleAge / 20d); +// smokeNodes.add(new Pair(EasyLocation.getZeroLocation(), alpha)); +// } +// } + + prevRotationPitch = rotationPitch; + prevRotationYaw = rotationYaw; + +// if (motionY > gravity && !onGround) +// motionY += gravity; +// if (motionY < -0.75) +// motionY = -0.75; + + if(onGround) + rotationPitch = 0; + else { + rotationPitch += momentumPitch; + rotationYaw += momentumYaw; + } + } + + @Override + public void renderParticle(Tessellator tessellator, float interp, float x, float y, float z, float tx, float tz) { + + GL11.glPushMatrix(); + RenderHelper.enableStandardItemLighting(); + GL11.glEnable(GL11.GL_CULL_FACE); + GL11.glShadeModel(GL11.GL_SMOOTH); + GL11.glEnable(GL12.GL_RESCALE_NORMAL); + + textureManager.bindTexture(ResourceManager.casings_tex); + + EntityPlayer player = Minecraft.getMinecraft().thePlayer; + double dX = player.lastTickPosX + (player.posX - player.lastTickPosX) * (double)interp; + double dY = player.lastTickPosY + (player.posY - player.lastTickPosY) * (double)interp; + double dZ = player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * (double)interp; + + GL11.glTranslated(prevPosX + (posX - prevPosX) * interp - dX, prevPosY + (posY - prevPosY) * interp - dY - this.height / 4, prevPosZ + (posZ - prevPosZ) * interp - dZ); + + GL11.glScalef(dScale, dScale, dScale); + + GL11.glRotatef(180 - rotationYaw, 0, 1, 0); + GL11.glRotatef(-rotationPitch, 1, 0, 0); + + GL11.glScalef(config.getScaleX(), config.getScaleY(), config.getScaleZ()); + + if(config.doesOverrideColor()) + GL11.glColor3b((byte) config.getRedOverride(), (byte) config.getGreenOverride(), (byte) config.getBlueOverride()); + + ResourceManager.casings.renderPart(config.getCasingType().objName); + GL11.glDisable(GL12.GL_RESCALE_NORMAL); + + /*if(!smokeNodes.isEmpty()) { + tessellator.startDrawingQuads(); + tessellator.setNormal(0F, 1F, 0F); + + for(int i = 0; i < smokeNodes.size() - 1; i++) { + final Pair node = smokeNodes.get(i), past = smokeNodes.get(i + 1); + final Vec3 nodeLoc = node.getKey(), pastLoc = past.getKey(); + final float nodeAlpha = node.getValue().floatValue(), pastAlpha = past.getValue().floatValue(), scale = config.getScaleX(); + + tessellator.setColorRGBA_F(1F, 1F, 1F, nodeAlpha); + tessellator.addVertex(nodeLoc.xCoord, nodeLoc.yCoord, nodeLoc.zCoord); + tessellator.setColorRGBA_F(1F, 1F, 1F, 0F); + tessellator.addVertex(nodeLoc.xCoord + scale, nodeLoc.yCoord, nodeLoc.zCoord); + tessellator.setColorRGBA_F(1F, 1F, 1F, 0F); + tessellator.addVertex(pastLoc.xCoord + scale, pastLoc.yCoord, pastLoc.zCoord); + tessellator.setColorRGBA_F(1F, 1F, 1F, pastAlpha); + tessellator.addVertex(pastLoc.xCoord, pastLoc.yCoord, pastLoc.zCoord); + + tessellator.setColorRGBA_F(1F, 1F, 1F, nodeAlpha); + tessellator.addVertex(nodeLoc.xCoord, nodeLoc.yCoord, nodeLoc.zCoord); + tessellator.setColorRGBA_F(1F, 1F, 1F, 0F); + tessellator.addVertex(nodeLoc.xCoord - scale, nodeLoc.yCoord, nodeLoc.zCoord); + tessellator.setColorRGBA_F(1F, 1F, 1F, 0F); + tessellator.addVertex(pastLoc.xCoord - scale, pastLoc.yCoord, pastLoc.zCoord); + tessellator.setColorRGBA_F(1F, 1F, 1F, pastAlpha); + tessellator.addVertex(pastLoc.xCoord, pastLoc.yCoord, pastLoc.zCoord); + } + + GL11.glAlphaFunc(GL11.GL_GREATER, 0F); + GL11.glEnable(GL11.GL_BLEND); + GL11.glDisable(GL11.GL_TEXTURE_2D); + tessellator.draw(); + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glDisable(GL11.GL_BLEND); + GL11.glAlphaFunc(GL11.GL_GEQUAL, 0.1F); + }*/ + + RenderHelper.disableStandardItemLighting(); + GL11.glShadeModel(GL11.GL_FLAT); + GL11.glPopMatrix(); + } + + private void tryPlayBounceSound() { + + if(!config.getBounceSound().isEmpty()) { + worldObj.playSoundAtEntity(this, config.getBounceSound(), 2, 1); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/hbm/particle/SpentCasingConfig.java b/src/main/java/com/hbm/particle/SpentCasingConfig.java new file mode 100644 index 000000000..89f55b076 --- /dev/null +++ b/src/main/java/com/hbm/particle/SpentCasingConfig.java @@ -0,0 +1,323 @@ +package com.hbm.particle; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Random; + +import org.lwjgl.util.vector.Matrix4f; +import org.lwjgl.util.vector.Vector3f; +import org.lwjgl.util.vector.Vector4f; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ImmutableMap; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.util.Vec3; +import net.minecraft.world.World; + +@Beta +public class SpentCasingConfig { + + private static final Map CONFIG_MAP = new HashMap(); + private static final Random RANDOM = new Random(); + + public enum CasingType { + BRASS_STRAIGHT_WALL("Straight"), + BRASS_BOTTLENECK("Bottleneck"), + SHOTGUN("Shotgun"), + AR2("AR2"); + + public final String objName; + + private CasingType(String objName) { + this.objName = objName; + } + } + + /**Unique name used for map lookup.**/ + private final String registryName; + /**Change position of the ejecting shell.**/ + private final Vec3 posOffset; + /**Set initial motion after ejecting.**/ + private final Vec3 initialMotion; + /**Multipliers for random pitch and yaw.**/ + private final float pitchFactor, yawFactor; + /**Rescale the sprite to match the approximate scale of the rounds.**/ + private final float scaleX, scaleY, scaleZ; + /**Overrides for the sprite colors.**/ + private final int redOverride, greenOverride, blueOverride; + /**Whether or not to override the default sprite color scheme.**/ + private final boolean overrideColor; + /**The type of casing.**/ + private final CasingType casingType; + /**Amount of casings to spawn per event. Default 1.**/ + private final byte casingAmount; + /**If the casing(s) should be spawned after reloading, instead of after firing.**/ + private final boolean afterReload; + /**Sound effect for bouncing. Make empty string to have no sound.**/ + private final String bounceSound; + /**Delay before casings are actually spawned**/ + private final byte delay; + /**Chance for the casing to emit smoke. 0 for 100% chance and -1 for it to never make smoke.**/ + private final byte smokeChance; + // TODO Setting to disregard crouch effect and/or another offset specifically for crouching which can be set to null to use the default one + public SpentCasingConfig( + String registryName, Vec3 posOffset, Vec3 initialMotion, float pitchFactor, float yawFactor, + float scaleX, float scaleY, float scaleZ, int redOverride, int greenOverride, int blueOverride, + boolean overrideColor, CasingType casingType, byte casingAmount, boolean afterReload, String bounceSound, + byte delay, byte smokeChance) { + + this.registryName = registryName; + this.posOffset = posOffset; + this.initialMotion = initialMotion; + this.pitchFactor = pitchFactor; + this.yawFactor = yawFactor; + this.scaleX = scaleX; + this.scaleY = scaleY; + this.scaleZ = scaleZ; + this.redOverride = redOverride; + this.greenOverride = greenOverride; + this.blueOverride = blueOverride; + this.overrideColor = overrideColor; + this.casingType = casingType; + this.casingAmount = casingAmount; + this.afterReload = afterReload; + this.bounceSound = bounceSound; + this.delay = delay; + this.smokeChance = smokeChance; + + CONFIG_MAP.put(registryName, this); + } + + public void spawnCasing(TextureManager textureManager, World world, double x, double y, double z, float pitch, float yaw, boolean crouched) { + Vec3 rotatedMotionVec = rotateVector(getInitialMotion(), + pitch + (float) RANDOM.nextGaussian() * getPitchFactor(), yaw + (float) RANDOM.nextGaussian() * getPitchFactor(), + getPitchFactor(), getPitchFactor()); + + ParticleSpentCasing casing = new ParticleSpentCasing(textureManager, world, x, + y, z, rotatedMotionVec.xCoord, rotatedMotionVec.yCoord, rotatedMotionVec.zCoord, +// 0, 0, + (float) (getPitchFactor() * RANDOM.nextGaussian()), (float) (getYawFactor() * RANDOM.nextGaussian()), + this); + + offsetCasing(casing, getPosOffset(), pitch, yaw, crouched); + + casing.rotationPitch = (float) Math.toDegrees(pitch); + casing.rotationYaw = (float) Math.toDegrees(yaw); + + if(overrideColor) + casing.setRBGColorF(redOverride / 255f, blueOverride / 255f, greenOverride / 255f); + Minecraft.getMinecraft().effectRenderer.addEffect(casing); + } + + // Rotate a position + private static void offsetCasing(ParticleSpentCasing casing, Vec3 offset, float pitch, float yaw, boolean crouched) + { +// // x-axis offset, 0 if crouched to center +// final double oX = crouched ? 0 : offset.posX(); +// // Trigonometric operations, saved for convenience +// final double sinP = Math.sin(pitch), cosP = Math.cos(pitch), sinY = Math.sin(yaw), cosY = Math.cos(yaw); +// // New offsets +// final double newX = oX * cosY - offset.posZ() * sinY, +// newY = offset.posY() * cosP - sinP * (oX * sinY + offset.posZ() * cosY), +// newZ = offset.posZ() * sinP + cosP * (oX * sinY + offset.posZ() * cosY); +// +// // Apply +// casing.setPosition(casing.posX + newX, casing.posY + newY, casing.posZ + newZ); + + // x-axis offset, 0 if crouched to center + final float oX = (float) (crouched ? 0 : offset.xCoord); + // Create rotation matrices for pitch and yaw + final Matrix4f pitchMatrix = new Matrix4f(), yawMatrix = new Matrix4f(); + + pitchMatrix.rotate(pitch, new Vector3f(1, 0, 0)); // modify axis of rotation + yawMatrix.rotate(-yaw, new Vector3f(0, 1, 0)); + + // Multiply matrices to get combined rotation matrix + final Matrix4f rotMatrix = Matrix4f.mul(yawMatrix, pitchMatrix, null); + // Create vector representing the offset and apply rotation + final Vector4f offsetVector = new Vector4f(oX, (float) offset.yCoord, (float) offset.zCoord, 1); // set fourth coordinate to 1 + Matrix4f.transform(rotMatrix, offsetVector, offsetVector); + final Vector3f result = new Vector3f(); // create result vector + result.set(offsetVector.x, offsetVector.y, offsetVector.z); // set result vector using transformed coordinates + // Apply rotation + casing.setPosition(casing.posX + result.x, casing.posY + result.y, casing.posZ + result.z); + } + +// Rotate a vector + private static Vec3 rotateVector(Vec3 vector, float pitch, float yaw, float pitchFactor, float yawFactor) + { + // Apply randomness to vector + vector.xCoord += RANDOM.nextGaussian() * yawFactor; + vector.yCoord += RANDOM.nextGaussian() * pitchFactor; + vector.zCoord += RANDOM.nextGaussian() * yawFactor; + + final Matrix4f pitchMatrix = new Matrix4f(), yawMatrix = new Matrix4f(); + + pitchMatrix.setIdentity(); + pitchMatrix.rotate(-pitch, new Vector3f(1, 0, 0)); + + yawMatrix.setIdentity(); + yawMatrix.rotate(-yaw, new Vector3f(0, 1, 0)); + + final Vector4f vector4f = new Vector4f((float) vector.xCoord, (float) vector.yCoord, (float) vector.zCoord, 1); + + Matrix4f.transform(pitchMatrix, vector4f, vector4f); + Matrix4f.transform(yawMatrix, vector4f, vector4f); + + return Vec3.createVectorHelper(vector4f.x, vector4f.y, vector4f.z); + } + + public Vec3 getPosOffset() + { + return posOffset; + } + public Vec3 getInitialMotion() + { + return Vec3.createVectorHelper(initialMotion.xCoord, initialMotion.yCoord, initialMotion.zCoord); + } + public float getScaleX() + { + return scaleX; + } + public float getScaleY() + { + return scaleY; + } + public float getScaleZ() + { + return scaleZ; + } + public float getPitchFactor() + { + return pitchFactor; + } + public float getYawFactor() + { + return yawFactor; + } + public int getRedOverride() + { + return redOverride; + } + public int getGreenOverride() + { + return greenOverride; + } + public int getBlueOverride() + { + return blueOverride; + } + public boolean doesOverrideColor() + { + return overrideColor; + } + public CasingType getCasingType() + { + return casingType; + } + public byte getCasingAmount() + { + return casingAmount; + } + public boolean isAfterReload() + { + return afterReload; + } + public String getRegistryName() + { + return registryName; + } + public String getBounceSound() + { + return bounceSound; + } + public byte getDelay() + { + return delay; + } + public byte getSmokeChance() + { + return smokeChance; + } + + /** + * Convert to a new builder for modification. + * @param newName The new registry name. + * @return A new builder with all the settings inherited from this config, except for registry name. + */ + public SpentCasingConfigBuilder toBuilder(String newName) + { + final SpentCasingConfigBuilder builder = new SpentCasingConfigBuilder(newName, casingType, overrideColor); + builder.setAfterReload(afterReload).setBlueOverride(blueOverride).setBounceSound(bounceSound).setCasingAmount(casingAmount) + .setDelay(delay).setGreenOverride(greenOverride).setInitialMotion(getInitialMotion()).setPitchFactor(pitchFactor) + .setPosOffset(getPosOffset()).setRedOverride(redOverride).setScaleX(scaleX).setScaleY(scaleY).setScaleZ(scaleZ) + .setSmokeChance(smokeChance).setYawFactor(yawFactor); + return builder; + } + + @Override + public int hashCode() + { + return Objects.hash(afterReload, blueOverride, bounceSound, casingAmount, casingType, delay, greenOverride, + initialMotion.xCoord, initialMotion.yCoord, initialMotion.zCoord, overrideColor, pitchFactor, + posOffset, redOverride, registryName, scaleX, scaleY, scaleZ, smokeChance, yawFactor); + } + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (!(obj instanceof SpentCasingConfig)) + return false; + final SpentCasingConfig other = (SpentCasingConfig) obj; + return afterReload == other.afterReload && blueOverride == other.blueOverride + && Objects.equals(bounceSound, other.bounceSound) && casingAmount == other.casingAmount + && casingType == other.casingType && delay == other.delay && greenOverride == other.greenOverride + && Double.doubleToLongBits(initialMotion.xCoord) == Double.doubleToLongBits(other.initialMotion.xCoord) + && Double.doubleToLongBits(initialMotion.yCoord) == Double.doubleToLongBits(other.initialMotion.yCoord) + && Double.doubleToLongBits(initialMotion.zCoord) == Double.doubleToLongBits(other.initialMotion.zCoord) + && overrideColor == other.overrideColor + && Float.floatToIntBits(pitchFactor) == Float.floatToIntBits(other.pitchFactor) + && Objects.equals(posOffset, other.posOffset) && redOverride == other.redOverride + && Objects.equals(registryName, other.registryName) + && Float.floatToIntBits(scaleX) == Float.floatToIntBits(other.scaleX) + && Float.floatToIntBits(scaleY) == Float.floatToIntBits(other.scaleY) + && Float.floatToIntBits(scaleZ) == Float.floatToIntBits(other.scaleZ) + && smokeChance == other.smokeChance + && Float.floatToIntBits(yawFactor) == Float.floatToIntBits(other.yawFactor); + } + @Override + public String toString() + { + final StringBuilder builder = new StringBuilder(); + builder.append("SpentCasingConfig [registryName=").append(registryName).append(", posOffset=").append(posOffset) + .append(", initialMotion=").append(initialMotion).append(", pitchFactor=").append(pitchFactor) + .append(", yawFactor=").append(yawFactor).append(", scaleX=").append(scaleX).append(", scaleY=") + .append(scaleY).append(", scaleZ=").append(scaleZ).append(", redOverride=").append(redOverride) + .append(", greenOverride=").append(greenOverride).append(", blueOverride=").append(blueOverride) + .append(", overrideColor=").append(overrideColor).append(", casingType=").append(casingType) + .append(", casingAmount=").append(casingAmount).append(", afterReload=").append(afterReload) + .append(", bounceSound=").append(bounceSound).append(", delay=").append(delay).append(", smokeChance=") + .append(smokeChance).append("]"); + return builder.toString(); + } + + public static boolean containsKey(String key) + { + return CONFIG_MAP.containsKey(key); + } + + public static SpentCasingConfig get(String key) + { + return CONFIG_MAP.get(key); + } + + public static Map getConfigMap() + { + return ImmutableMap.copyOf(CONFIG_MAP); + } + +} \ No newline at end of file diff --git a/src/main/java/com/hbm/particle/SpentCasingConfigBuilder.java b/src/main/java/com/hbm/particle/SpentCasingConfigBuilder.java new file mode 100644 index 000000000..67c30cad9 --- /dev/null +++ b/src/main/java/com/hbm/particle/SpentCasingConfigBuilder.java @@ -0,0 +1,377 @@ +package com.hbm.particle; + +import java.util.Objects; + +import com.google.common.annotations.Beta; +import com.hbm.main.MainRegistry; +import com.hbm.particle.SpentCasingConfig.CasingType; + +import net.minecraft.util.MathHelper; +import net.minecraft.util.Vec3; + +@Beta +public class SpentCasingConfigBuilder implements Cloneable +{ + /**Unique name used for map lookup.**/ + private String registryName; + /**Change position of the ejecting shell.**/ + private Vec3 posOffset = Vec3.createVectorHelper(0, 0, 0); + /**Set initial motion after ejecting.**/ + private Vec3 initialMotion = Vec3.createVectorHelper(0, 0, 0); + /**Multipliers for random pitch and yaw.**/ + private float pitchFactor, yawFactor; + /**Rescale the sprite to match the approximate scale of the rounds.**/ + private float scaleX = 1, scaleY = 1, scaleZ = 1; + /**Overrides for the sprite colors.**/ + private int redOverride, greenOverride, blueOverride; + /**Whether or not to override the default sprite color scheme.**/ + private boolean overrideColor; + /**The type of casing.**/ + private CasingType casingType = CasingType.BRASS_STRAIGHT_WALL; + /**Amount of casings to spawn per event. Default 1.**/ + private byte casingAmount = 1; + /**If the casing(s) should be spawned after reloading, instead of after firing.**/ + private boolean afterReload; + /**Sound effect for bouncing. Make empty string to have no sound.**/ + private String bounceSound = ""; + /**Delay before casings are actually spawned**/ + private byte delay; + /**Chance for the casing to emit smoke. 0 for 100% chance and -1 for it to never make smoke.**/ + private byte smokeChance = -1; + // TODO Setting to disregard crouch effect and/or another offset specifically for crouching which can be set to null to use the default one + /** + * Constructor with fields for the required bare minimum parameters.
+ * All parameters may overridden using setters at any time at your discretion, however. + * @param registryName The unique name for map lookup. Null not permitted, becomes empty string. + * @param casingType Type of casing model to use. Null not permitted, defaults to straight wall type. + * @param overrideColor Whether or not the config will override the model texture's color. + */ + public SpentCasingConfigBuilder(String registryName, CasingType casingType, boolean overrideColor) { + this.registryName = registryName == null ? "" : registryName; + this.casingType = casingType == null ? CasingType.BRASS_STRAIGHT_WALL : casingType; + this.overrideColor = overrideColor; + } + + public Vec3 getPosOffset() { + return posOffset; + } + + /** + * Set the relative x, y, z coordinate offset on spawn. + * @param posOffset Any ILocationProvider that serves as the offset. Null becomes a zero location. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setPosOffset(Vec3 posOffset) { + this.posOffset = posOffset == null ? Vec3.createVectorHelper(0, 0, 0) : posOffset; + return this; + } + + public Vec3 getInitialMotion() { + return initialMotion; + } + + /** + * Sets the casing's initial relative x, y, z motion on spawn. + * @param initialMotion Vector representing the initial motion. Null becomes a zero vector. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setInitialMotion(Vec3 initialMotion) { + this.initialMotion = initialMotion == null ? Vec3.createVectorHelper(0, 0, 0) : initialMotion; + return this; + } + + public double getScaleX() { + return scaleX; + } + + /** + * Scale of the model on its x-axis. + * @param scaleX The scale/stretch factor of the model on the x-axis. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setScaleX(float scaleX) { + this.scaleX = scaleX; + return this; + } + + public double getScaleY() { + return scaleY; + } + + /** + * Scale of the model on its y-axis. + * @param scaleY The scale/stretch factor of the model on the y-axis. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setScaleY(float scaleY) { + this.scaleY = scaleY; + return this; + } + + public float getScaleZ() { + return scaleZ; + } + + /** + * Scale of the model on its z-axis. + * @param scaleZ The scale/stretch of the model on the z-axis. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setScaleZ(float scaleZ) { + this.scaleZ = scaleZ; + return this; + } + + public float getPitchFactor() { + return pitchFactor; + } + + /** + * Multiplier for random pitch-related motion. Recommended to keep in the thousanths (0.00X), as even with the hundreths (0.0X) the pitch can get crazy at times. + * @param pitchFactor The multiplier. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setPitchFactor(float pitchFactor) { + this.pitchFactor = pitchFactor; + return this; + } + + public float getYawFactor() { + return yawFactor; + } + + /** + * Multiplier for random yaw-related motion. Recommended to keep in the thousanths (0.00X), as even with the hundreths (0.0X) the yaw can get crazy at times. + * @param yawFactor The multiplier. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setYawFactor(float yawFactor) { + this.yawFactor = yawFactor; + return this; + } + + public int getRedOverride() { + return redOverride; + } + + /** + * Red color override. Clamped between 0-255, but I don't know how it actually works on the receiving end, so feel free to change. + * @param redOverride Red color override. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setRedOverride(int redOverride) { + this.redOverride = MathHelper.clamp_int(redOverride, 0, 255); + return this; + } + + public int getGreenOverride() { + return greenOverride; + } + + /** + * Green color override. Clamped between 0-255, but I don't know how it actually works on the receiving end, so feel free to change. + * @param greenOverride Green color override. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setGreenOverride(int greenOverride) { + this.greenOverride = MathHelper.clamp_int(greenOverride, 0, 255); + return this; + } + + public int getBlueOverride() { + return blueOverride; + } + + /** + * Blue color override. Clamped between 0-255, but I don't know how it actually works on the receiving end, so feel free to change. + * @param blueOverride Blue color override. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setBlueOverride(int blueOverride) { + this.blueOverride = MathHelper.clamp_int(blueOverride, 0, 255); + return this; + } + + public boolean doesOverrideColor() { + return overrideColor; + } + + /** + * Sets whether or not the config will override color. If false, the integer overrides will be ignored. + * @param overrideColor True, to use the integer color overrides, false to ignore them. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setOverrideColor(boolean overrideColor) { + this.overrideColor = overrideColor; + return this; + } + + public CasingType getCasingType() { + return casingType; + } + + /** + * Sets the model the casing will use. + * @param casingType One of the 4 casing model types. Null is not permitted, will have no effect. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setCasingType(CasingType casingType) { + this.casingType = casingType == null ? this.casingType : casingType; + return this; + } + + public byte getCasingAmount() { + return casingAmount; + } + + /** + * Sets the amount of casings to spawn. Default is 1. + * @param casingAmount Amount of casings to spawn. Clamped to a positive byte (0 - 127). + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setCasingAmount(int casingAmount) { + this.casingAmount = (byte) MathHelper.clamp_int(casingAmount, 0, 127); + return this; + } + + public boolean isAfterReload() { + return afterReload; + } + + /** + * Sets whether or not the casing will be spawned after reloading is done or after firing. + * @param afterReload If true, casings will be spawned when reloading, false, they will be spawned after firing. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setAfterReload(boolean afterReload) { + this.afterReload = afterReload; + return this; + } + + public String getRegistryName() { + return registryName; + } + + /** + * Resets the unique name for the config used in map lookup.
+ * As the real class is self-registering, make sure to set this in reused builders. + * @param registryName The registry name to use. Null is not permitted, becomes an empty string. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setRegistryName(String registryName) { + this.registryName = registryName == null ? "" : registryName; + return this; + } + + public String getBounceSound() { + return bounceSound; + } + + /** + * The sound used when bouncing. If empty, no sound will be made. + * @param bounceSound The sound path. Null is not permitted, becomes an empty string. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setBounceSound(String bounceSound) { + this.bounceSound = bounceSound == null ? "" : bounceSound; + return this; + } + + public byte getDelay() { + return delay; + } + + /** + * The delay in ticks before spawning. + * @param delay Tick spawn delay. Must be nonnegative, otherwise 0. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setDelay(int delay) { + this.delay = (byte) (delay < 0 ? 0 : delay); + return this; + } + + public byte getSmokeChance() { + return smokeChance; + } + + /** + * Chance for the casing to emit smoke. 0 for 100% chance and -1 for it to never make smoke. + * @param smokeChance Chance to emit smoke. Clamped to a byte. + * @return Itself for chaining. + */ + public SpentCasingConfigBuilder setSmokeChance(int smokeChance) { + this.smokeChance = (byte) smokeChance; + return this; + } + + /** + * Constructs the true immutable config with all settings loaded from this builder.
+ * This builder may be reused as all non-immutable and primitive fields are copied before being passed to the constructor.
+ * The config's constructor is self-registering. + * @return The finished config with its settings specified by this builder. + */ + public SpentCasingConfig build() { + return new SpentCasingConfig(registryName, Vec3.createVectorHelper(posOffset.xCoord, posOffset.yCoord, posOffset.zCoord), + Vec3.createVectorHelper(initialMotion.xCoord, initialMotion.yCoord, initialMotion.zCoord), pitchFactor, + yawFactor, scaleX, scaleY, scaleZ, redOverride, greenOverride, blueOverride, overrideColor, casingType, + casingAmount, afterReload, bounceSound, delay, smokeChance); + } + + @Override + public int hashCode() { + return Objects.hash(afterReload, blueOverride, bounceSound, casingAmount, casingType, delay, greenOverride, + initialMotion.xCoord, initialMotion.yCoord, initialMotion.zCoord, overrideColor, pitchFactor, + posOffset, redOverride, registryName, scaleX, scaleY, scaleZ, smokeChance, yawFactor); + } + + @Override + public boolean equals(Object obj) { + + if(this == obj) return true; + if(!(obj instanceof SpentCasingConfigBuilder)) return false; + + SpentCasingConfigBuilder other = (SpentCasingConfigBuilder) obj; + return afterReload == other.afterReload && blueOverride == other.blueOverride + && Objects.equals(bounceSound, other.bounceSound) && casingAmount == other.casingAmount + && casingType == other.casingType && delay == other.delay && greenOverride == other.greenOverride + && Double.doubleToLongBits(initialMotion.xCoord) == Double.doubleToLongBits(other.initialMotion.xCoord) + && Double.doubleToLongBits(initialMotion.yCoord) == Double.doubleToLongBits(other.initialMotion.yCoord) + && Double.doubleToLongBits(initialMotion.zCoord) == Double.doubleToLongBits(other.initialMotion.zCoord) + && overrideColor == other.overrideColor + && Float.floatToIntBits(pitchFactor) == Float.floatToIntBits(other.pitchFactor) + && Objects.equals(posOffset, other.posOffset) && redOverride == other.redOverride + && Objects.equals(registryName, other.registryName) + && Float.floatToIntBits(scaleX) == Float.floatToIntBits(other.scaleX) + && Float.floatToIntBits(scaleY) == Float.floatToIntBits(other.scaleY) + && Float.floatToIntBits(scaleZ) == Float.floatToIntBits(other.scaleZ) + && smokeChance == other.smokeChance + && Float.floatToIntBits(yawFactor) == Float.floatToIntBits(other.yawFactor); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("SpentCasingConfigBuilder [registryName=").append(registryName).append(", posOffset=") + .append(posOffset).append(", initialMotion=").append(initialMotion).append(", pitchFactor=") + .append(pitchFactor).append(", yawFactor=").append(yawFactor).append(", scaleX=").append(scaleX) + .append(", scaleY=").append(scaleY).append(", scaleZ=").append(scaleZ).append(", redOverride=") + .append(redOverride).append(", greenOverride=").append(greenOverride).append(", blueOverride=") + .append(blueOverride).append(", overrideColor=").append(overrideColor).append(", casingType=") + .append(casingType).append(", casingAmount=").append(casingAmount).append(", afterReload=") + .append(afterReload).append(", bounceSound=").append(bounceSound).append(", delay=").append(delay) + .append(", smokeChance=").append(smokeChance).append(']'); + return builder.toString(); + } + + @Override + public SpentCasingConfigBuilder clone() { + try { + return (SpentCasingConfigBuilder) super.clone(); + } catch(CloneNotSupportedException e) { + MainRegistry.logger.catching(e); + return new SpentCasingConfigBuilder(registryName, casingType, overrideColor); + } + } + +} \ No newline at end of file diff --git a/src/main/resources/assets/hbm/models/effect/casings.obj b/src/main/resources/assets/hbm/models/effect/casings.obj new file mode 100644 index 000000000..2ce04cf4b --- /dev/null +++ b/src/main/resources/assets/hbm/models/effect/casings.obj @@ -0,0 +1,1084 @@ +# Blender v2.79 (sub 0) OBJ File: '' +# www.blender.org +o AR2 +v 0.254558 0.254558 -1.200000 +v 0.254558 -0.254558 -1.200000 +v -0.254558 -0.254558 -1.200000 +v 0.424264 -0.424264 -0.000000 +v 0.600000 0.000000 0.000000 +v -0.600000 0.000000 0.000000 +v 0.424264 -0.424264 -0.400000 +v -0.424264 -0.424264 -0.400000 +v -0.424264 0.424264 -0.400000 +v -0.254559 0.254558 -1.200000 +v 0.000000 0.360000 -1.200000 +v 0.360000 0.000000 -1.200000 +v 0.000000 -0.360000 -1.200000 +v -0.360000 0.000000 -1.200000 +v 0.424264 0.424264 0.000000 +v 0.000000 0.600000 0.000000 +v -0.424264 0.424264 0.000000 +v -0.424264 -0.424264 -0.000000 +v 0.000000 -0.600000 -0.000000 +v 0.000000 0.600000 -0.400000 +v 0.424264 0.424264 -0.400000 +v 0.600000 0.000000 -0.400000 +v 0.000000 -0.600000 -0.400000 +v -0.600000 0.000000 -0.400000 +v 0.000000 0.360000 -0.400000 +v 0.254558 0.254558 -1.200000 +v 0.000000 0.360000 -1.200000 +v 0.254558 0.254558 -0.400000 +v 0.360000 0.000000 -1.200000 +v 0.360000 0.000000 -0.400000 +v 0.254558 -0.254558 -1.200000 +v 0.254558 -0.254558 -0.400000 +v 0.000000 -0.360000 -1.200000 +v 0.000000 -0.360000 -0.400000 +v -0.254558 -0.254558 -1.200000 +v -0.254558 -0.254559 -0.400000 +v -0.360000 0.000000 -1.200000 +v -0.360000 0.000000 -0.400000 +v -0.254559 0.254558 -1.200000 +v -0.254559 0.254558 -0.400000 +vt 0.171253 0.570129 +vt 0.171253 0.430121 +vt 0.031246 0.430122 +vt 0.171253 0.430121 +vt 0.200250 0.500125 +vt 0.002250 0.500125 +vt 0.171253 0.430121 +vt 0.031246 0.430122 +vt 0.031246 0.570129 +vt 0.031246 0.570129 +vt 0.101250 0.599125 +vt 0.200250 0.500125 +vt 0.101250 0.401125 +vt 0.002250 0.500125 +vt 0.171253 0.570129 +vt 0.101250 0.599125 +vt 0.031246 0.570129 +vt 0.031246 0.430122 +vt 0.101250 0.401125 +vt 0.101250 0.599125 +vt 0.171253 0.570129 +vt 0.200250 0.500125 +vt 0.101250 0.401125 +vt 0.002250 0.500125 +vt 0.162500 0.566875 +vt 0.146875 0.504375 +vt 0.162500 0.504375 +vt 0.146875 0.566875 +vt 0.131250 0.504375 +vt 0.131250 0.566875 +vt 0.115625 0.504375 +vt 0.115625 0.566875 +vt 0.100000 0.504375 +vt 0.100000 0.566875 +vt 0.084375 0.504375 +vt 0.084375 0.566875 +vt 0.068750 0.504375 +vt 0.068750 0.566875 +vt 0.053125 0.504375 +vt 0.053125 0.566875 +vt 0.037500 0.504375 +vt 0.037500 0.566875 +vt 0.162500 0.566875 +vt 0.146875 0.504375 +vt 0.162500 0.504375 +vt 0.146875 0.566875 +vt 0.131250 0.504375 +vt 0.131250 0.566875 +vt 0.115625 0.504375 +vt 0.115625 0.566875 +vt 0.100000 0.504375 +vt 0.100000 0.566875 +vt 0.084375 0.504375 +vt 0.084375 0.566875 +vt 0.068750 0.504375 +vt 0.068750 0.566875 +vt 0.053125 0.504375 +vt 0.053125 0.566875 +vt 0.037500 0.504375 +vt 0.037500 0.566875 +vn 0.0000 0.0000 -1.0000 +vn 0.0000 -0.0000 1.0000 +vn 0.0000 1.0000 0.0000 +vn 0.7071 0.7071 0.0000 +vn 1.0000 0.0000 -0.0000 +vn 0.7071 -0.7071 -0.0000 +vn -0.0000 -1.0000 -0.0000 +vn -0.7071 -0.7071 -0.0000 +vn -1.0000 -0.0000 0.0000 +vn -0.7071 0.7071 0.0000 +s off +f 1/1/1 2/2/1 3/3/1 +f 4/4/2 5/5/2 6/6/2 +f 7/7/1 8/8/1 9/9/1 +f 10/10/1 11/11/1 1/1/1 +f 1/1/1 12/12/1 2/2/1 +f 2/2/1 13/13/1 3/3/1 +f 3/3/1 14/14/1 10/10/1 +f 10/10/1 1/1/1 3/3/1 +f 5/5/2 15/15/2 16/16/2 +f 16/16/2 17/17/2 5/5/2 +f 17/17/2 6/6/2 5/5/2 +f 6/6/2 18/18/2 19/19/2 +f 19/19/2 4/4/2 6/6/2 +f 9/9/1 20/20/1 21/21/1 +f 21/21/1 22/22/1 7/7/1 +f 7/7/1 23/23/1 8/8/1 +f 8/8/1 24/24/1 9/9/1 +f 9/9/1 21/21/1 7/7/1 +s 1 +f 16/25/3 21/26/4 20/27/3 +f 15/28/4 22/29/5 21/26/4 +f 5/30/5 7/31/6 22/29/5 +f 4/32/6 23/33/7 7/31/6 +f 19/34/7 8/35/8 23/33/7 +f 18/36/8 24/37/9 8/35/8 +f 6/38/9 9/39/10 24/37/9 +f 17/40/10 20/41/3 9/39/10 +f 16/25/3 15/28/4 21/26/4 +f 15/28/4 5/30/5 22/29/5 +f 5/30/5 4/32/6 7/31/6 +f 4/32/6 19/34/7 23/33/7 +f 19/34/7 18/36/8 8/35/8 +f 18/36/8 6/38/9 24/37/9 +f 6/38/9 17/40/10 9/39/10 +f 17/40/10 16/42/3 20/41/3 +f 25/43/3 26/44/4 27/45/3 +f 28/46/4 29/47/5 26/44/4 +f 30/48/5 31/49/6 29/47/5 +f 32/50/6 33/51/7 31/49/6 +f 34/52/7 35/53/8 33/51/7 +f 36/54/8 37/55/9 35/53/8 +f 38/56/9 39/57/10 37/55/9 +f 40/58/10 27/59/3 39/57/10 +f 25/43/3 28/46/4 26/44/4 +f 28/46/4 30/48/5 29/47/5 +f 30/48/5 32/50/6 31/49/6 +f 32/50/6 34/52/7 33/51/7 +f 34/52/7 36/54/8 35/53/8 +f 36/54/8 38/56/9 37/55/9 +f 38/56/9 40/58/10 39/57/10 +f 40/58/10 25/60/3 27/59/3 +o Shotgun +v -0.353356 -0.353356 1.148335 +v 0.000000 -0.499721 1.148335 +v 0.353356 -0.353356 1.148335 +v 0.000000 0.449124 -1.223040 +v 0.317579 0.317579 -1.223040 +v -0.317579 -0.317578 -1.223040 +v 0.499721 -0.000000 1.148335 +v 0.353356 0.353357 1.148335 +v 0.000000 0.499721 1.148335 +v -0.353356 0.353357 1.148335 +v -0.499721 -0.000000 1.148335 +v 0.449124 0.000000 -1.223040 +v 0.317579 -0.317578 -1.223040 +v 0.000000 -0.449123 -1.223040 +v -0.449124 0.000000 -1.223040 +v -0.317579 0.317579 -1.223040 +v 0.317579 -0.317578 1.112764 +v 0.499721 -0.000000 1.148335 +v 0.353356 -0.353356 1.148335 +v 0.000000 -0.449124 1.112764 +v -0.353356 -0.353356 1.148335 +v -0.317579 -0.317578 1.112764 +v 0.000000 -0.499721 1.148335 +v 0.000000 0.449124 1.112764 +v 0.353356 0.353357 1.148335 +v 0.317579 0.317579 1.112764 +v 0.449124 -0.000000 1.112764 +v -0.317579 0.317579 1.112764 +v -0.499721 -0.000000 1.148335 +v -0.353356 0.353357 1.148335 +v 0.000000 0.499721 1.148335 +v -0.449124 -0.000000 1.112764 +v 0.000000 -0.449124 1.112764 +v 0.317579 -0.317578 -1.223040 +v 0.317579 -0.317578 1.112764 +v -0.317579 0.317579 1.112764 +v -0.449124 0.000000 -1.223040 +v -0.449124 -0.000000 1.112764 +v 0.317579 0.317579 1.112764 +v 0.000000 0.449124 -1.223040 +v 0.000000 0.449124 1.112764 +v -0.317579 0.317579 -1.223040 +v -0.317579 -0.317578 1.112764 +v 0.000000 -0.449123 -1.223040 +v -0.317579 -0.317578 -1.223040 +v 0.449124 0.000000 -1.223040 +v 0.449124 -0.000000 1.112764 +v 0.317579 0.317579 -1.223040 +vt 0.067942 0.886801 +vt 0.086790 0.867953 +vt 0.113444 0.867953 +vt 0.960365 0.912783 +vt 0.918017 0.955131 +vt 0.858128 0.810547 +vt 0.132291 0.886801 +vt 0.132291 0.913455 +vt 0.113444 0.932302 +vt 0.086790 0.932302 +vt 0.067942 0.913455 +vt 0.858129 0.955131 +vt 0.815781 0.912784 +vt 0.815781 0.852895 +vt 0.918017 0.810547 +vt 0.960365 0.852895 +vt 0.124726 0.724156 +vt 0.120654 0.755786 +vt 0.120654 0.722469 +vt 0.145900 0.702982 +vt 0.177531 0.698910 +vt 0.175844 0.702982 +vt 0.144213 0.698910 +vt 0.175844 0.775274 +vt 0.144213 0.779346 +vt 0.145900 0.775274 +vt 0.124726 0.754100 +vt 0.197018 0.754100 +vt 0.201090 0.722469 +vt 0.201090 0.755787 +vt 0.177531 0.779346 +vt 0.197018 0.724155 +vt 0.323497 0.764346 +vt 0.293553 0.933910 +vt 0.293553 0.764346 +vt 0.468081 0.764346 +vt 0.438137 0.933910 +vt 0.438137 0.764346 +vt 0.365845 0.764346 +vt 0.344671 0.933910 +vt 0.344671 0.764346 +vt 0.489255 0.764346 +vt 0.468081 0.933910 +vt 0.344671 0.764346 +vt 0.323497 0.933910 +vt 0.416963 0.933910 +vt 0.416963 0.764346 +vt 0.416963 0.764346 +vt 0.395789 0.933910 +vt 0.395789 0.764346 +vt 0.365845 0.933910 +vt 0.489255 0.933910 +vt 0.344671 0.933910 +vt 0.416963 0.933910 +vn 0.0000 0.0000 1.0000 +vn 0.0000 0.0000 -1.0000 +vn 0.4067 -0.4067 -0.8181 +vn 0.5751 -0.0000 -0.8181 +vn -0.0000 -0.5751 -0.8181 +vn -0.4067 -0.4067 -0.8181 +vn 0.0000 0.5751 -0.8181 +vn 0.4067 0.4067 -0.8181 +vn -0.4067 0.4067 -0.8181 +vn -0.5751 -0.0000 -0.8181 +vn -0.0000 -1.0000 -0.0000 +vn 0.7071 -0.7071 -0.0000 +vn -0.7071 0.7071 0.0000 +vn -1.0000 -0.0000 -0.0000 +vn 0.7071 0.7071 0.0000 +vn 0.0000 1.0000 0.0000 +vn -0.7071 -0.7071 -0.0000 +vn 1.0000 -0.0000 0.0000 +s off +f 41/61/11 42/62/11 43/63/11 +f 44/64/12 45/65/12 46/66/12 +f 43/63/11 47/67/11 48/68/11 +f 48/68/11 49/69/11 43/63/11 +f 49/69/11 50/70/11 43/63/11 +f 50/70/11 51/71/11 43/63/11 +f 51/71/11 41/61/11 43/63/11 +f 45/65/12 52/72/12 53/73/12 +f 53/73/12 54/74/12 45/65/12 +f 54/74/12 46/66/12 45/65/12 +f 46/66/12 55/75/12 56/76/12 +f 56/76/12 44/64/12 46/66/12 +s 1 +f 57/77/13 58/78/14 59/79/13 +f 60/80/15 61/81/16 62/82/16 +f 60/80/15 59/79/13 63/83/15 +f 64/84/17 65/85/18 66/86/18 +f 66/86/18 58/78/14 67/87/14 +f 68/88/19 69/89/20 70/90/19 +f 64/84/17 70/90/19 71/91/17 +f 62/82/16 69/89/20 72/92/20 +f 73/93/21 74/94/22 75/95/22 +f 76/96/23 77/97/24 78/98/24 +f 79/99/25 80/100/26 81/101/26 +f 81/102/26 82/103/23 76/96/23 +f 83/104/27 84/105/21 73/93/21 +f 78/98/24 85/106/27 83/107/27 +f 75/108/22 86/109/28 87/110/28 +f 87/110/28 88/111/25 79/99/25 +f 57/77/13 67/87/14 58/78/14 +f 60/80/15 63/83/15 61/81/16 +f 60/80/15 57/77/13 59/79/13 +f 64/84/17 71/91/17 65/85/18 +f 66/86/18 65/85/18 58/78/14 +f 68/88/19 72/92/20 69/89/20 +f 64/84/17 68/88/19 70/90/19 +f 62/82/16 61/81/16 69/89/20 +f 73/93/21 84/105/21 74/94/22 +f 76/96/23 82/103/23 77/97/24 +f 79/99/25 88/111/25 80/100/26 +f 81/102/26 80/112/26 82/103/23 +f 83/104/27 85/113/27 84/105/21 +f 78/98/24 77/97/24 85/106/27 +f 75/108/22 74/114/22 86/109/28 +f 87/110/28 86/109/28 88/111/25 +o Straight +v 0.300002 -0.000003 0.685047 +v 0.185616 0.185613 0.685047 +v 0.262502 -0.000003 0.685047 +v -0.212132 -0.212133 0.735047 +v 0.000000 -0.262503 0.735047 +v 0.000000 -0.300001 0.735047 +v -0.212132 -0.212133 0.685047 +v 0.000000 -0.262503 0.685047 +v -0.185614 -0.185617 0.685047 +v -0.212132 0.212131 0.685047 +v 0.000000 0.262499 0.685047 +v 0.000000 0.299999 0.685047 +v 0.212132 0.212131 0.685047 +v 0.212132 -0.212133 0.685047 +v 0.185616 -0.185617 0.685047 +v -0.262500 -0.000003 0.685047 +v -0.300000 -0.000003 0.685047 +v 0.000000 -0.300001 0.685047 +v -0.185614 0.185613 0.685047 +v 0.212132 -0.212133 0.735047 +v 0.262502 -0.000003 0.735047 +v 0.300002 -0.000003 0.735047 +v 0.212132 0.212131 0.735047 +v 0.000000 0.262499 0.735047 +v 0.000000 0.299999 0.735047 +v -0.212132 0.212131 0.735047 +v -0.185614 0.185613 0.735047 +v -0.262500 -0.000003 0.735047 +v -0.185614 -0.185617 0.735047 +v 0.185616 -0.185617 0.735047 +v 0.185616 0.185613 0.735047 +v -0.300000 -0.000003 0.735047 +v -0.212132 0.212131 -0.814953 +v 0.212132 0.212131 -0.814953 +v 0.212132 -0.212133 -0.814953 +v 0.000000 -0.300001 -0.814953 +v -0.212132 -0.212133 -0.814953 +v -0.300000 -0.000003 -0.814953 +v 0.000000 0.299999 -0.814953 +v 0.300002 -0.000003 -0.814953 +v 0.212132 -0.212133 0.785047 +v 0.212132 0.212131 0.785047 +v -0.212132 0.212131 0.785047 +v -0.300000 -0.000001 0.785047 +v -0.212132 -0.212133 0.785047 +v 0.000000 -0.300001 0.785047 +v 0.300002 -0.000001 0.785047 +v 0.000000 0.299999 0.785047 +v -0.300000 -0.000003 -0.814953 +v -0.212132 -0.212133 0.685047 +v -0.300000 -0.000003 0.685047 +v -0.212132 0.212131 -0.814953 +v 0.000000 0.299999 0.685047 +v 0.000000 0.299999 -0.814953 +v -0.212132 0.212131 0.685047 +v 0.000000 -0.300001 -0.814953 +v 0.000000 -0.300001 0.685047 +v 0.212132 -0.212133 -0.814953 +v 0.300002 -0.000003 0.685047 +v 0.212132 -0.212133 0.685047 +v 0.212132 0.212131 -0.814953 +v 0.212132 0.212131 0.685047 +v -0.212132 -0.212133 -0.814953 +v 0.300002 -0.000003 -0.814953 +v -0.212132 -0.212133 0.735047 +v 0.000000 -0.300001 0.785047 +v -0.212132 -0.212133 0.785047 +v 0.300002 -0.000003 0.735047 +v 0.212132 -0.212133 0.785047 +v 0.212132 -0.212133 0.735047 +v 0.000000 0.299999 0.735047 +v 0.212132 0.212131 0.785047 +v 0.212132 0.212131 0.735047 +v -0.212132 0.212131 0.785047 +v 0.000000 0.299999 0.785047 +v -0.300000 -0.000003 0.735047 +v -0.300000 -0.000001 0.785047 +v 0.000000 -0.300001 0.735047 +v 0.300002 -0.000001 0.785047 +v -0.212132 0.212131 0.735047 +v 0.185616 -0.185617 0.735047 +v 0.000000 -0.262503 0.685047 +v 0.185616 -0.185617 0.685047 +v 0.000000 -0.262503 0.735047 +v -0.185614 -0.185617 0.685047 +v 0.262502 -0.000003 0.735047 +v 0.262502 -0.000003 0.685047 +v 0.185616 0.185613 0.735047 +v 0.185616 0.185613 0.685047 +v 0.000000 0.262499 0.735047 +v 0.000000 0.262499 0.685047 +v -0.185614 0.185613 0.735047 +v -0.185614 0.185613 0.685047 +v -0.262500 -0.000003 0.735047 +v -0.262500 -0.000003 0.685047 +v -0.185614 -0.185617 0.735047 +vt 0.555701 0.706657 +vt 0.559782 0.681299 +vt 0.559782 0.704967 +vt 0.374299 0.628133 +vt 0.353483 0.613088 +vt 0.355173 0.609007 +vt 0.621002 0.706657 +vt 0.600185 0.721702 +vt 0.616920 0.704966 +vt 0.601875 0.660482 +vt 0.576517 0.664564 +vt 0.574827 0.660482 +vt 0.555701 0.679609 +vt 0.574827 0.725783 +vt 0.576518 0.721702 +vt 0.616920 0.681299 +vt 0.621002 0.679609 +vt 0.601876 0.725783 +vt 0.600185 0.664564 +vt 0.328125 0.609007 +vt 0.313080 0.629823 +vt 0.308998 0.628133 +vt 0.308998 0.655181 +vt 0.329815 0.670226 +vt 0.328124 0.674308 +vt 0.355173 0.674308 +vt 0.353482 0.670226 +vt 0.370218 0.653491 +vt 0.370218 0.629824 +vt 0.329815 0.613088 +vt 0.313080 0.653491 +vt 0.374299 0.655181 +vt 0.834098 0.918854 +vt 0.872350 0.826504 +vt 0.964699 0.864757 +vt 0.964699 0.918854 +vt 0.926447 0.957106 +vt 0.872350 0.957106 +vt 0.834098 0.864757 +vt 0.926448 0.826504 +vt 0.078463 0.847672 +vt 0.152342 0.878274 +vt 0.121740 0.952153 +vt 0.078463 0.952153 +vt 0.047861 0.921551 +vt 0.047861 0.878274 +vt 0.121740 0.847672 +vt 0.152342 0.921551 +vt 0.555701 0.614308 +vt 0.378998 0.641356 +vt 0.378998 0.614308 +vt 0.378998 0.725783 +vt 0.555701 0.752832 +vt 0.378998 0.752832 +vt 0.555701 0.725783 +vt 0.378998 0.706657 +vt 0.555701 0.706657 +vt 0.555701 0.660482 +vt 0.378998 0.660482 +vt 0.378998 0.679609 +vt 0.555701 0.660482 +vt 0.555701 0.679609 +vt 0.555701 0.706657 +vt 0.378998 0.706657 +vt 0.555701 0.799007 +vt 0.378998 0.771958 +vt 0.555701 0.771958 +vt 0.555701 0.641356 +vt 0.378998 0.660482 +vt 0.378998 0.799007 +vt 0.578726 0.628418 +vt 0.559600 0.622528 +vt 0.578726 0.622528 +vt 0.559600 0.622528 +vt 0.578726 0.616637 +vt 0.578726 0.622528 +vt 0.605775 0.628418 +vt 0.586649 0.634308 +vt 0.586649 0.628418 +vt 0.605775 0.616637 +vt 0.578726 0.610747 +vt 0.605775 0.610747 +vt 0.605775 0.628418 +vt 0.605775 0.622528 +vt 0.605775 0.616637 +vt 0.605775 0.622528 +vt 0.559600 0.634308 +vt 0.559600 0.628418 +vt 0.578726 0.616637 +vt 0.559600 0.610747 +vt 0.572436 0.725783 +vt 0.596104 0.731673 +vt 0.572436 0.731673 +vt 0.600003 0.604857 +vt 0.616739 0.610747 +vt 0.600003 0.610747 +vt 0.555701 0.725783 +vt 0.555701 0.731673 +vt 0.583268 0.610747 +vt 0.559600 0.604857 +vt 0.583268 0.604857 +vt 0.600003 0.610747 +vt 0.600003 0.604857 +vt 0.622437 0.634308 +vt 0.646104 0.640198 +vt 0.622437 0.640198 +vt 0.605701 0.634308 +vt 0.605701 0.640198 +vt 0.616739 0.604857 +vt 0.640406 0.610747 +vt 0.559600 0.628418 +vt 0.559600 0.616637 +vt 0.605775 0.634308 +vt 0.559600 0.616637 +vt 0.596104 0.725783 +vt 0.559600 0.610747 +vt 0.646104 0.634308 +vt 0.640406 0.604857 +vn 0.0000 0.0000 1.0000 +vn 0.0000 0.0000 -1.0000 +vn -1.0000 -0.0000 -0.0000 +vn -0.7071 -0.7071 -0.0000 +vn -0.7071 0.7071 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 -1.0000 -0.0000 +vn 0.7071 -0.7071 -0.0000 +vn 1.0000 -0.0000 0.0000 +vn 0.7071 0.7071 0.0000 +s off +f 89/115/29 90/116/29 91/117/29 +f 92/118/30 93/119/30 94/120/30 +f 95/121/29 96/122/29 97/123/29 +f 98/124/29 99/125/29 100/126/29 +f 101/127/29 99/125/29 90/116/29 +f 102/128/29 91/117/29 103/129/29 +f 95/121/29 104/130/29 105/131/29 +f 102/128/29 96/122/29 106/132/29 +f 105/131/29 107/133/29 98/124/29 +f 108/134/30 109/135/30 110/136/30 +f 111/137/30 112/138/30 113/139/30 +f 114/140/30 112/138/30 115/141/30 +f 92/118/30 116/142/30 117/143/30 +f 108/134/30 93/119/30 118/144/30 +f 110/136/30 119/145/30 111/137/30 +f 120/146/30 115/141/30 116/142/30 +f 89/115/29 101/127/29 90/116/29 +f 92/118/30 117/143/30 93/119/30 +f 95/121/29 106/132/29 96/122/29 +f 98/124/29 107/133/29 99/125/29 +f 101/127/29 100/126/29 99/125/29 +f 102/128/29 89/115/29 91/117/29 +f 95/121/29 97/123/29 104/130/29 +f 102/128/29 103/129/29 96/122/29 +f 105/131/29 104/130/29 107/133/29 +f 108/134/30 118/144/30 109/135/30 +f 111/137/30 119/145/30 112/138/30 +f 114/140/30 113/139/30 112/138/30 +f 92/118/30 120/146/30 116/142/30 +f 108/134/30 94/120/30 93/119/30 +f 110/136/30 109/135/30 119/145/30 +f 120/146/30 114/140/30 115/141/30 +f 121/147/30 122/148/30 123/149/30 +f 123/149/30 124/150/30 125/151/30 +f 125/151/30 126/152/30 121/147/30 +f 121/147/30 127/153/30 122/148/30 +f 122/148/30 128/154/30 123/149/30 +f 123/149/30 125/151/30 121/147/30 +f 129/155/29 130/156/29 131/157/29 +f 131/157/29 132/158/29 133/159/29 +f 133/159/29 134/160/29 129/155/29 +f 129/155/29 135/161/29 130/156/29 +f 130/156/29 136/162/29 131/157/29 +f 131/157/29 133/159/29 129/155/29 +s 1 +f 137/163/31 138/164/32 139/165/31 +f 140/166/33 141/167/34 142/168/34 +f 143/169/33 137/170/31 139/171/31 +f 138/164/32 144/172/35 145/173/35 +f 146/174/36 147/175/37 148/176/36 +f 146/174/36 145/177/35 144/178/35 +f 147/179/37 149/180/38 150/181/38 +f 142/168/34 150/181/38 149/180/38 +f 137/163/31 151/182/32 138/164/32 +f 140/166/33 143/169/33 141/167/34 +f 143/169/33 140/166/33 137/170/31 +f 138/164/32 151/182/32 144/172/35 +f 146/174/36 152/183/37 147/175/37 +f 146/174/36 148/176/36 145/177/35 +f 147/179/37 152/184/37 149/180/38 +f 142/168/34 141/167/34 150/181/38 +f 153/185/32 154/186/35 155/187/32 +f 156/188/37 157/189/36 158/190/36 +f 159/191/34 160/192/38 161/193/38 +f 159/194/34 162/195/33 163/196/34 +f 164/197/31 155/187/32 165/198/31 +f 158/190/36 154/199/35 166/200/35 +f 161/193/38 167/201/37 156/202/37 +f 168/203/33 165/204/31 162/195/33 +f 169/205/36 170/206/35 171/207/36 +f 172/208/35 173/209/32 170/210/35 +f 174/211/37 171/207/36 175/212/37 +f 176/213/38 175/214/37 177/215/38 +f 178/216/34 177/215/38 179/217/34 +f 180/218/33 179/219/34 181/220/33 +f 182/221/31 181/220/33 183/222/31 +f 184/223/32 183/224/31 173/209/32 +f 153/185/32 166/225/35 154/186/35 +f 156/188/37 167/226/37 157/189/36 +f 159/191/34 163/227/34 160/192/38 +f 159/194/34 168/203/33 162/195/33 +f 164/197/31 153/185/32 155/187/32 +f 158/190/36 157/189/36 154/199/35 +f 161/193/38 160/192/38 167/201/37 +f 168/203/33 164/228/31 165/204/31 +f 169/205/36 172/229/35 170/206/35 +f 172/208/35 184/223/32 173/209/32 +f 174/211/37 169/205/36 171/207/36 +f 176/213/38 174/230/37 175/214/37 +f 178/216/34 176/213/38 177/215/38 +f 180/218/33 178/231/34 179/219/34 +f 182/221/31 180/218/33 181/220/33 +f 184/223/32 182/232/31 183/224/31 +o Bottleneck +v 0.300002 -0.000003 0.685047 +v 0.185616 0.185613 0.685047 +v 0.262502 -0.000003 0.685047 +v -0.212132 -0.212133 0.735047 +v 0.000000 -0.262503 0.735047 +v 0.000000 -0.300001 0.735047 +v -0.212132 -0.212133 0.685047 +v 0.000000 -0.262503 0.685047 +v -0.185614 -0.185617 0.685047 +v -0.212132 0.212131 0.685047 +v 0.000000 0.262499 0.685047 +v 0.000000 0.299999 0.685047 +v 0.212132 0.212131 0.685047 +v 0.212132 -0.212133 0.685047 +v 0.185616 -0.185617 0.685047 +v -0.262500 -0.000003 0.685047 +v -0.300000 -0.000003 0.685047 +v 0.000000 -0.300001 0.685047 +v -0.185614 0.185613 0.685047 +v 0.212132 -0.212133 0.735047 +v 0.262502 -0.000003 0.735047 +v 0.300002 -0.000003 0.735047 +v 0.212132 0.212131 0.735047 +v 0.000000 0.262499 0.735047 +v 0.000000 0.299999 0.735047 +v -0.212132 0.212131 0.735047 +v -0.185614 0.185613 0.735047 +v -0.262500 -0.000003 0.735047 +v -0.185614 -0.185617 0.735047 +v 0.185616 -0.185617 0.735047 +v 0.185616 0.185613 0.735047 +v -0.300000 -0.000003 0.735047 +v -0.135595 0.135594 -1.014953 +v 0.135595 0.135594 -1.014953 +v 0.135596 -0.135596 -1.014953 +v 0.000000 -0.191762 -1.014953 +v -0.135596 -0.135597 -1.014953 +v -0.191761 -0.000003 -1.014953 +v 0.000000 0.191760 -1.014953 +v 0.191762 -0.000003 -1.014953 +v 0.212132 -0.212133 0.785047 +v 0.212132 0.212131 0.785047 +v -0.212132 0.212131 0.785047 +v -0.300000 -0.000001 0.785047 +v -0.212132 -0.212133 0.785047 +v 0.000000 -0.300001 0.785047 +v 0.300002 -0.000001 0.785047 +v 0.000000 0.299999 0.785047 +v -0.135595 0.135594 -0.764953 +v -0.135595 0.135594 -1.014953 +v -0.191761 -0.000003 -1.014953 +v 0.000000 0.191760 -0.764953 +v 0.000000 0.191760 -1.014953 +v 0.135595 0.135594 -0.764953 +v 0.191762 -0.000003 -0.764953 +v 0.191762 -0.000003 -1.014953 +v -0.135596 -0.135597 -0.764953 +v -0.135596 -0.135597 -1.014953 +v 0.000000 -0.191762 -1.014953 +v 0.135596 -0.135596 -0.764953 +v 0.135596 -0.135596 -1.014953 +v -0.191761 -0.000003 -0.764953 +v 0.135595 0.135594 -1.014953 +v 0.212132 -0.212133 -0.414953 +v 0.000000 -0.191762 -0.764953 +v 0.135596 -0.135596 -0.764953 +v -0.300000 -0.000003 -0.414953 +v -0.135595 0.135594 -0.764953 +v -0.191761 -0.000003 -0.764953 +v 0.000000 0.299999 -0.414953 +v 0.000000 0.191760 -0.764953 +v 0.212132 0.212131 -0.414953 +v 0.135595 0.135594 -0.764953 +v -0.212132 -0.212133 -0.414953 +v -0.135596 -0.135597 -0.764953 +v 0.191762 -0.000003 -0.764953 +v 0.300002 -0.000003 -0.414953 +v 0.000000 -0.191762 -0.764953 +v 0.000000 -0.300001 -0.414953 +v -0.212132 0.212131 -0.414953 +v 0.300002 -0.000003 0.685047 +v 0.212132 0.212131 -0.414953 +v 0.212132 0.212131 0.685047 +v 0.212132 -0.212133 -0.414953 +v 0.000000 -0.300001 0.685047 +v 0.000000 -0.300001 -0.414953 +v -0.300000 -0.000003 -0.414953 +v -0.212132 -0.212133 0.685047 +v -0.300000 -0.000003 0.685047 +v 0.000000 0.299999 -0.414953 +v -0.212132 0.212131 0.685047 +v 0.212132 -0.212133 0.685047 +v -0.212132 0.212131 -0.414953 +v 0.000000 0.299999 0.685047 +v 0.300002 -0.000003 -0.414953 +v -0.212132 -0.212133 -0.414953 +v 0.185616 -0.185617 0.735047 +v 0.000000 -0.262503 0.685047 +v 0.185616 -0.185617 0.685047 +v 0.000000 -0.262503 0.735047 +v -0.185614 -0.185617 0.685047 +v 0.262502 -0.000003 0.735047 +v 0.262502 -0.000003 0.685047 +v 0.185616 0.185613 0.735047 +v 0.185616 0.185613 0.685047 +v 0.000000 0.262499 0.735047 +v 0.000000 0.262499 0.685047 +v -0.185614 0.185613 0.735047 +v -0.185614 0.185613 0.685047 +v -0.262500 -0.000003 0.735047 +v -0.262500 -0.000003 0.685047 +v -0.185614 -0.185617 0.735047 +v -0.212132 -0.212133 0.735047 +v 0.000000 -0.300001 0.785047 +v -0.212132 -0.212133 0.785047 +v 0.300002 -0.000003 0.735047 +v 0.212132 -0.212133 0.785047 +v 0.212132 -0.212133 0.735047 +v 0.000000 0.299999 0.735047 +v 0.212132 0.212131 0.785047 +v 0.212132 0.212131 0.735047 +v -0.212132 0.212131 0.785047 +v 0.000000 0.299999 0.785047 +v -0.300000 -0.000003 0.735047 +v -0.300000 -0.000001 0.785047 +v 0.000000 -0.300001 0.735047 +v 0.300002 -0.000001 0.785047 +v -0.212132 0.212131 0.735047 +vt 0.630972 0.689316 +vt 0.635004 0.664263 +vt 0.635004 0.687646 +vt 0.760004 0.662592 +vt 0.739438 0.647728 +vt 0.741108 0.643696 +vt 0.695488 0.689316 +vt 0.674922 0.704180 +vt 0.691456 0.687645 +vt 0.676592 0.643696 +vt 0.651539 0.647728 +vt 0.649868 0.643696 +vt 0.630972 0.662592 +vt 0.649869 0.708212 +vt 0.651539 0.704180 +vt 0.691456 0.664263 +vt 0.695488 0.662592 +vt 0.676592 0.708212 +vt 0.674922 0.647728 +vt 0.714385 0.643696 +vt 0.699521 0.664262 +vt 0.695488 0.662592 +vt 0.695488 0.689316 +vt 0.716055 0.704180 +vt 0.714385 0.708212 +vt 0.741108 0.708212 +vt 0.739438 0.704180 +vt 0.755972 0.687645 +vt 0.755972 0.664262 +vt 0.716055 0.647728 +vt 0.699521 0.687645 +vt 0.760005 0.689316 +vt 0.818608 0.930835 +vt 0.866923 0.814195 +vt 0.983563 0.862509 +vt 0.983563 0.930836 +vt 0.935248 0.979150 +vt 0.866923 0.979150 +vt 0.818608 0.862509 +vt 0.935250 0.814194 +vt 0.078761 0.848420 +vt 0.151753 0.878654 +vt 0.121519 0.951645 +vt 0.078761 0.951645 +vt 0.048527 0.921411 +vt 0.048527 0.878654 +vt 0.121519 0.848420 +vt 0.151753 0.921411 +vt 0.396006 0.631929 +vt 0.366932 0.631929 +vt 0.366456 0.619851 +vt 0.396006 0.649011 +vt 0.366932 0.649011 +vt 0.396006 0.742065 +vt 0.396006 0.759147 +vt 0.366932 0.759147 +vt 0.396006 0.694631 +vt 0.366932 0.694631 +vt 0.366932 0.677549 +vt 0.395530 0.771225 +vt 0.366456 0.771225 +vt 0.395530 0.706709 +vt 0.366456 0.706709 +vt 0.366932 0.742065 +vt 0.366456 0.729986 +vt 0.436423 0.653832 +vt 0.396006 0.677549 +vt 0.395530 0.665471 +vt 0.436423 0.608212 +vt 0.396006 0.631929 +vt 0.395530 0.619851 +vt 0.437168 0.653832 +vt 0.396006 0.649011 +vt 0.395530 0.729986 +vt 0.437169 0.737244 +vt 0.396006 0.742065 +vt 0.437169 0.699452 +vt 0.396006 0.694631 +vt 0.436423 0.782864 +vt 0.396006 0.759147 +vt 0.437169 0.763968 +vt 0.436423 0.718348 +vt 0.395530 0.665471 +vt 0.396006 0.677549 +vt 0.395530 0.619851 +vt 0.395530 0.729986 +vt 0.437169 0.672728 +vt 0.437168 0.627109 +vt 0.436423 0.718348 +vt 0.395530 0.771225 +vt 0.395530 0.706709 +vt 0.366456 0.665471 +vt 0.565094 0.763968 +vt 0.437169 0.737244 +vt 0.565094 0.737244 +vt 0.436423 0.653832 +vt 0.565094 0.672728 +vt 0.437169 0.672728 +vt 0.436423 0.718348 +vt 0.565094 0.699452 +vt 0.564349 0.718348 +vt 0.436423 0.718348 +vt 0.565094 0.627109 +vt 0.436423 0.608212 +vt 0.564349 0.608212 +vt 0.436423 0.782864 +vt 0.564348 0.782864 +vt 0.437168 0.627109 +vt 0.565094 0.653832 +vt 0.437168 0.653832 +vt 0.437169 0.763968 +vt 0.564349 0.653832 +vt 0.437169 0.699452 +vt 0.564349 0.718348 +vt 0.816456 0.643044 +vt 0.799922 0.637881 +vt 0.816456 0.637229 +vt 0.799922 0.643696 +vt 0.776539 0.637881 +vt 0.655571 0.643696 +vt 0.639037 0.637229 +vt 0.655571 0.637881 +vt 0.678954 0.643696 +vt 0.678954 0.637881 +vt 0.695488 0.643044 +vt 0.695488 0.637229 +vt 0.783384 0.660723 +vt 0.760099 0.654527 +vt 0.783479 0.654909 +vt 0.799927 0.660341 +vt 0.800022 0.654527 +vt 0.776539 0.643696 +vt 0.760004 0.637229 +vt 0.639037 0.643044 +vt 0.760004 0.660341 +vt 0.760004 0.643044 +vt 0.714385 0.637881 +vt 0.741108 0.643696 +vt 0.714385 0.643696 +vt 0.585352 0.637881 +vt 0.566456 0.642951 +vt 0.566456 0.637136 +vt 0.630972 0.637136 +vt 0.612076 0.643696 +vt 0.612076 0.637881 +vt 0.760099 0.660723 +vt 0.786724 0.666973 +vt 0.760004 0.666537 +vt 0.695488 0.637136 +vt 0.695488 0.642951 +vt 0.760004 0.637136 +vt 0.741108 0.637881 +vt 0.585352 0.643696 +vt 0.786819 0.661159 +vt 0.805630 0.666537 +vt 0.630972 0.642951 +vt 0.760004 0.642951 +vt 0.805725 0.660723 +vn 0.0000 0.0000 1.0000 +vn 0.0000 0.0000 -1.0000 +vn -0.7071 0.7071 0.0000 +vn -1.0000 -0.0000 -0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.7071 0.7071 0.0000 +vn 1.0000 -0.0000 -0.0000 +vn -0.7071 -0.7071 -0.0000 +vn 0.0000 -1.0000 -0.0000 +vn 0.7071 -0.7071 -0.0000 +vn 0.6755 -0.6755 -0.2954 +vn 0.0000 -0.9554 -0.2954 +vn -0.9554 -0.0000 -0.2954 +vn -0.6755 0.6755 -0.2954 +vn -0.0000 0.9554 -0.2954 +vn 0.6755 0.6755 -0.2954 +vn 0.6755 0.6755 -0.2955 +vn -0.6755 -0.6755 -0.2954 +vn 0.9554 -0.0000 -0.2955 +vn -0.6755 0.6755 -0.2955 +s off +f 185/233/39 186/234/39 187/235/39 +f 188/236/40 189/237/40 190/238/40 +f 191/239/39 192/240/39 193/241/39 +f 194/242/39 195/243/39 196/244/39 +f 197/245/39 195/243/39 186/234/39 +f 198/246/39 187/235/39 199/247/39 +f 191/239/39 200/248/39 201/249/39 +f 198/246/39 192/240/39 202/250/39 +f 201/249/39 203/251/39 194/242/39 +f 204/252/40 205/253/40 206/254/40 +f 207/255/40 208/256/40 209/257/40 +f 210/258/40 208/256/40 211/259/40 +f 188/236/40 212/260/40 213/261/40 +f 204/252/40 189/237/40 214/262/40 +f 206/254/40 215/263/40 207/255/40 +f 216/264/40 211/259/40 212/260/40 +f 185/233/39 197/245/39 186/234/39 +f 188/236/40 213/261/40 189/237/40 +f 191/239/39 202/250/39 192/240/39 +f 194/242/39 203/251/39 195/243/39 +f 197/245/39 196/244/39 195/243/39 +f 198/246/39 185/233/39 187/235/39 +f 191/239/39 193/241/39 200/248/39 +f 198/246/39 199/247/39 192/240/39 +f 201/249/39 200/248/39 203/251/39 +f 204/252/40 214/262/40 205/253/40 +f 207/255/40 215/263/40 208/256/40 +f 210/258/40 209/257/40 208/256/40 +f 188/236/40 216/264/40 212/260/40 +f 204/252/40 190/238/40 189/237/40 +f 206/254/40 205/253/40 215/263/40 +f 216/264/40 210/258/40 211/259/40 +f 217/265/40 218/266/40 219/267/40 +f 219/267/40 220/268/40 217/265/40 +f 220/268/40 221/269/40 217/265/40 +f 221/269/40 222/270/40 217/265/40 +f 217/265/40 223/271/40 218/266/40 +f 218/266/40 224/272/40 219/267/40 +f 225/273/39 226/274/39 227/275/39 +f 227/275/39 228/276/39 229/277/39 +f 229/277/39 230/278/39 225/273/39 +f 225/273/39 231/279/39 226/274/39 +f 226/274/39 232/280/39 227/275/39 +f 227/275/39 229/277/39 225/273/39 +s 1 +f 233/281/41 234/282/41 235/283/42 +f 236/284/43 237/285/43 234/282/41 +f 238/286/44 239/287/45 240/288/45 +f 241/289/46 242/290/46 243/291/47 +f 244/292/48 245/293/48 240/288/45 +f 246/294/42 235/295/42 242/290/46 +f 238/286/44 247/296/44 237/297/43 +f 248/298/49 249/299/50 250/300/49 +f 251/301/51 252/302/52 253/303/51 +f 252/302/52 254/304/53 255/305/53 +f 255/306/53 256/307/54 257/308/55 +f 249/299/50 258/309/56 259/310/56 +f 248/311/49 260/312/57 261/313/57 +f 251/314/51 259/310/56 258/309/56 +f 260/312/57 256/307/54 261/313/57 +f 244/315/48 262/316/47 243/291/47 +f 233/281/41 235/283/42 246/317/42 +f 236/284/43 234/282/41 233/281/41 +f 238/286/44 240/288/45 247/296/44 +f 241/289/46 243/291/47 262/316/47 +f 244/292/48 240/288/45 239/287/45 +f 246/294/42 242/290/46 241/289/46 +f 238/286/44 237/297/43 236/318/43 +f 248/298/49 263/319/50 249/299/50 +f 251/301/51 264/320/58 252/302/52 +f 252/302/52 264/320/58 254/304/53 +f 255/306/53 254/321/53 256/307/54 +f 249/299/50 263/319/50 258/309/56 +f 248/311/49 250/322/49 260/312/57 +f 251/314/51 253/323/51 259/310/56 +f 260/312/57 257/308/55 256/307/54 +f 244/315/48 243/291/47 245/324/48 +f 265/325/45 266/326/44 267/327/44 +f 268/328/48 269/329/47 270/330/47 +f 271/331/42 272/332/46 273/333/42 +f 274/334/43 267/327/44 266/326/44 +f 275/335/41 271/336/42 273/337/42 +f 268/338/48 265/325/45 276/339/48 +f 277/340/41 278/341/43 274/342/43 +f 272/332/46 270/330/47 269/329/47 +f 265/325/45 279/343/45 266/326/44 +f 268/328/48 276/344/48 269/329/47 +f 271/331/42 280/345/46 272/332/46 +f 274/334/43 278/346/43 267/327/44 +f 275/335/41 277/340/41 271/336/42 +f 268/338/48 279/343/45 265/325/45 +f 277/340/41 275/335/41 278/341/43 +f 272/332/46 280/345/46 270/330/47 +f 281/347/48 282/348/47 283/349/48 +f 284/350/47 285/351/46 282/348/47 +f 286/352/45 283/353/48 287/354/45 +f 288/355/44 287/354/45 289/356/44 +f 290/357/43 289/356/44 291/358/43 +f 292/359/41 291/360/43 293/361/41 +f 294/362/42 293/361/41 295/363/42 +f 296/364/46 295/365/42 285/351/46 +f 281/347/48 284/350/47 282/348/47 +f 284/350/47 296/364/46 285/351/46 +f 286/352/45 281/366/48 283/353/48 +f 288/355/44 286/352/45 287/354/45 +f 290/357/43 288/355/44 289/356/44 +f 292/359/41 290/367/43 291/360/43 +f 294/362/42 292/359/41 293/361/41 +f 296/364/46 294/368/42 295/365/42 +f 297/369/46 298/370/47 299/371/46 +f 300/372/45 301/373/48 302/374/48 +f 303/375/43 304/376/44 305/377/44 +f 303/378/43 306/379/41 307/380/43 +f 308/381/42 299/371/46 309/382/42 +f 302/383/48 298/370/47 310/384/47 +f 305/377/44 311/385/45 300/372/45 +f 312/386/41 309/387/42 306/379/41 +f 297/369/46 310/384/47 298/370/47 +f 300/372/45 311/385/45 301/373/48 +f 303/375/43 307/388/43 304/376/44 +f 303/378/43 312/386/41 306/379/41 +f 308/381/42 297/369/46 299/371/46 +f 302/383/48 301/389/48 298/370/47 +f 305/377/44 304/376/44 311/385/45 +f 312/386/41 308/390/42 309/387/42 diff --git a/src/main/resources/assets/hbm/textures/particle/casings.png b/src/main/resources/assets/hbm/textures/particle/casings.png new file mode 100644 index 0000000000000000000000000000000000000000..e9ddbafa51b249f36c2795aff5d89ad6f97484b8 GIT binary patch literal 10168 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