improve IAnimatedItem and move existing item animations out of the ClientProxy monolith

This commit is contained in:
George Paton 2025-09-12 14:39:38 +10:00
parent 539ecb8130
commit 8c752488ee
7 changed files with 181 additions and 175 deletions

View File

@ -1,14 +1,31 @@
package com.hbm.items;
import com.hbm.packet.PacketDispatcher;
import com.hbm.packet.toclient.GunAnimationPacket;
import com.hbm.render.anim.BusAnimation;
import com.hbm.render.anim.HbmAnimations.AnimType;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
public interface IAnimatedItem {
@SideOnly(Side.CLIENT)
public BusAnimation getAnimation(NBTTagCompound data, ItemStack stack);
// Fetch the animation for a given type
public BusAnimation getAnimation(AnimType type, ItemStack stack);
// Run the swing animation
public default void playAnimation(EntityPlayer player) {
playAnimation(player, AnimType.CYCLE);
}
// Run a specified animation
public default void playAnimation(EntityPlayer player, AnimType type) {
if(player instanceof EntityPlayerMP) {
PacketDispatcher.wrapper.sendTo(new GunAnimationPacket(type.ordinal(), 0, 0), (EntityPlayerMP) player);
}
}
}

View File

@ -9,6 +9,7 @@ import com.hbm.main.MainRegistry;
import com.hbm.packet.toclient.AuxParticlePacketNT;
import com.hbm.render.anim.BusAnimation;
import com.hbm.render.anim.BusAnimationSequence;
import com.hbm.render.anim.HbmAnimations.AnimType;
import com.hbm.util.EntityDamageUtil;
import api.hbm.block.IToolable;
@ -19,7 +20,6 @@ import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.block.Block;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
@ -73,13 +73,10 @@ public class ItemBoltgun extends Item implements IAnimatedItem {
data.setFloat("size", 1F);
data.setByte("count", (byte)1);
PacketThreading.createAllAroundThreadedPacket(new AuxParticlePacketNT(data, entity.posX, entity.posY + entity.height / 2 - entity.yOffset, entity.posZ), new TargetPoint(world.provider.dimensionId, entity.posX, entity.posY, entity.posZ, 50));
} else {
// doing this on the client outright removes the packet delay and makes the animation silky-smooth
NBTTagCompound d0 = new NBTTagCompound();
d0.setString("type", "anim");
d0.setString("mode", "generic");
MainRegistry.proxy.effectNT(d0);
playAnimation(player);
}
return true;
}
}
@ -110,10 +107,7 @@ public class ItemBoltgun extends Item implements IAnimatedItem {
data.setByte("count", (byte)1);
PacketThreading.createAllAroundThreadedPacket(new AuxParticlePacketNT(data, x + fX + dir.offsetX * off, y + fY + dir.offsetY * off, z + fZ + dir.offsetZ * off), new TargetPoint(world.provider.dimensionId, x, y, z, 50));
NBTTagCompound d0 = new NBTTagCompound();
d0.setString("type", "anim");
d0.setString("mode", "generic");
PacketThreading.createSendToThreadedPacket(new AuxParticlePacketNT(d0, 0, 0, 0), (EntityPlayerMP) player);
playAnimation(player);
}
return false;
@ -124,7 +118,7 @@ public class ItemBoltgun extends Item implements IAnimatedItem {
@Override
@SideOnly(Side.CLIENT)
public BusAnimation getAnimation(NBTTagCompound data, ItemStack stack) {
public BusAnimation getAnimation(AnimType type, ItemStack stack) {
return new BusAnimation()
.addBus("RECOIL", new BusAnimationSequence()
.addPos(1, 0, 1, 50)

View File

@ -1,16 +1,19 @@
package com.hbm.items.tool;
import com.hbm.handler.threading.PacketThreading;
import com.hbm.inventory.fluid.FluidType;
import com.hbm.items.IAnimatedItem;
import com.hbm.items.IHeldSoundProvider;
import com.hbm.packet.toclient.AuxParticlePacketNT;
import com.hbm.render.anim.BusAnimation;
import com.hbm.render.anim.BusAnimationSequence;
import com.hbm.render.anim.HbmAnimations;
import com.hbm.render.anim.HbmAnimations.AnimType;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
public class ItemChainsaw extends ItemToolAbilityFueled implements IHeldSoundProvider {
public class ItemChainsaw extends ItemToolAbilityFueled implements IHeldSoundProvider, IAnimatedItem {
public ItemChainsaw(float damage, double movement, ToolMaterial material, EnumToolType type, int maxFuel, int consumption, int fillRate, FluidType... acceptedFuels) {
super(damage, movement, material, type, maxFuel, consumption, fillRate, acceptedFuels);
@ -25,11 +28,46 @@ public class ItemChainsaw extends ItemToolAbilityFueled implements IHeldSoundPro
if(stack.getItemDamage() >= stack.getMaxDamage())
return false;
NBTTagCompound nbt = new NBTTagCompound();
nbt.setString("type", "anim");
nbt.setString("mode", "sSwing");
PacketThreading.createSendToThreadedPacket(new AuxParticlePacketNT(nbt, 0, 0, 0), (EntityPlayerMP)entityLiving);
playAnimation((EntityPlayer) entityLiving);
return false;
}
@Override
public BusAnimation getAnimation(AnimType type, ItemStack stack) {
int forward = 150;
int sideways = 100;
int retire = 200;
if(HbmAnimations.getRelevantAnim() == null) {
return new BusAnimation()
.addBus("SWING_ROT", new BusAnimationSequence()
.addPos(0, 0, 90, forward)
.addPos(45, 0, 90, sideways)
.addPos(0, 0, 0, retire))
.addBus("SWING_TRANS", new BusAnimationSequence()
.addPos(0, 0, 3, forward)
.addPos(2, 0, 2, sideways)
.addPos(0, 0, 0, retire));
} else {
double[] rot = HbmAnimations.getRelevantTransformation("SWING_ROT");
double[] trans = HbmAnimations.getRelevantTransformation("SWING_TRANS");
if(System.currentTimeMillis() - HbmAnimations.getRelevantAnim().startMillis < 50) return null;
return new BusAnimation()
.addBus("SWING_ROT", new BusAnimationSequence()
.addPos(rot[0], rot[1], rot[2], 0)
.addPos(0, 0, 90, forward)
.addPos(45, 0, 90, sideways)
.addPos(0, 0, 0, retire))
.addBus("SWING_TRANS", new BusAnimationSequence()
.addPos(trans[0], trans[1], trans[2], 0)
.addPos(0, 0, 3, forward)
.addPos(2, 0, 2, sideways)
.addPos(0, 0, 0, retire));
}
}
}

View File

@ -1,19 +1,29 @@
package com.hbm.items.weapon;
import java.util.List;
import java.util.Random;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.hbm.handler.threading.PacketThreading;
import com.hbm.items.IAnimatedItem;
import com.hbm.items.IEquipReceiver;
import com.hbm.items.ModItems;
import com.hbm.items.tool.ItemSwordAbility;
import com.hbm.packet.toclient.AuxParticlePacketNT;
import com.hbm.render.anim.BusAnimation;
import com.hbm.render.anim.BusAnimationSequence;
import com.hbm.render.anim.HbmAnimations;
import com.hbm.render.anim.HbmAnimations.AnimType;
import com.hbm.render.anim.HbmAnimations.Animation;
import com.hbm.util.ShadyUtil;
import cpw.mods.fml.common.network.NetworkRegistry.TargetPoint;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.attributes.AttributeModifier;
@ -25,9 +35,10 @@ import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.ChatStyle;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
public class ItemCrucible extends ItemSwordAbility implements IEquipReceiver {
public class ItemCrucible extends ItemSwordAbility implements IEquipReceiver, IAnimatedItem {
public ItemCrucible(float damage, double movement, ToolMaterial material) {
super(damage, movement, material);
@ -44,10 +55,7 @@ public class ItemCrucible extends ItemSwordAbility implements IEquipReceiver {
World world = player.worldObj;
world.playSoundEffect(player.posX, player.posY, player.posZ, "hbm:weapon.cDeploy", 1.0F, 1.0F);
NBTTagCompound nbt = new NBTTagCompound();
nbt.setString("type", "anim");
nbt.setString("mode", "crucible");
PacketThreading.createSendToThreadedPacket(new AuxParticlePacketNT(nbt, 0, 0, 0), (EntityPlayerMP)player);
playAnimation(player, AnimType.EQUIP);
}
}
@ -64,10 +72,7 @@ public class ItemCrucible extends ItemSwordAbility implements IEquipReceiver {
if(stack.getItemDamage() >= stack.getMaxDamage())
return false;
NBTTagCompound nbt = new NBTTagCompound();
nbt.setString("type", "anim");
nbt.setString("mode", "cSwing");
PacketThreading.createSendToThreadedPacket(new AuxParticlePacketNT(nbt, 0, 0, 0), (EntityPlayerMP)entityLiving);
playAnimation((EntityPlayerMP)entityLiving, AnimType.CYCLE);
return false;
}
@ -132,4 +137,49 @@ public class ItemCrucible extends ItemSwordAbility implements IEquipReceiver {
list.add(charge);
}
@Override
public BusAnimation getAnimation(AnimType type, ItemStack stack) {
/* crucible deploy */
if(type == AnimType.EQUIP) {
return new BusAnimation()
.addBus("GUARD_ROT", new BusAnimationSequence()
.addPos(90, 0, 1, 0)
.addPos(90, 0, 1, 800)
.addPos(0, 0, 1, 50));
}
/* crucible swing */
if(type == AnimType.CYCLE) {
if(HbmAnimations.getRelevantTransformation("SWING_ROT")[0] == 0) {
Random rand = Minecraft.getMinecraft().theWorld.rand;
int offset = rand.nextInt(80) - 20;
playSwing(0.8F + rand.nextFloat() * 0.2F);
return new BusAnimation()
.addBus("SWING_ROT", new BusAnimationSequence()
.addPos(90 - offset, 90 - offset, 35, 75)
.addPos(90 + offset, 90 - offset, -45, 150)
.addPos(0, 0, 0, 500))
.addBus("SWING_TRANS", new BusAnimationSequence()
.addPos(-3, 0, 0, 75)
.addPos(8, 0, 0, 150)
.addPos(0, 0, 0, 500));
}
}
return null;
}
// could do this better, but this preserves existing behaviour the closest with the least amount
// of effort, without crashing servers (I'm learning my lesson :o_ )
@SideOnly(Side.CLIENT)
private void playSwing(float pitchProbablyIDontFuckingCare) {
Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.func_147674_a(new ResourceLocation("hbm:weapon.cSwing"), pitchProbablyIDontFuckingCare));
}
}

View File

@ -1787,105 +1787,6 @@ public class ClientProxy extends ServerProxy {
Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleDeadLeaf(man, world, x, y, z));
}
if("anim".equals(type)) {
String mode = data.getString("mode");
/* crucible deploy */
if("crucible".equals(mode) && player.getHeldItem() != null) {
BusAnimation animation = new BusAnimation()
.addBus("GUARD_ROT", new BusAnimationSequence()
.addPos(90, 0, 1, 0)
.addPos(90, 0, 1, 800)
.addPos(0, 0, 1, 50));
String id = ModItems.crucible.getUnlocalizedName();
HbmAnimations.hotbar[player.inventory.currentItem][0] = new Animation(id, System.currentTimeMillis(), animation, null);
}
/* crucible swing */
if("cSwing".equals(mode)) {
if(HbmAnimations.getRelevantTransformation("SWING_ROT")[0] == 0) {
int offset = rand.nextInt(80) - 20;
BusAnimation animation = new BusAnimation()
.addBus("SWING_ROT", new BusAnimationSequence()
.addPos(90 - offset, 90 - offset, 35, 75)
.addPos(90 + offset, 90 - offset, -45, 150)
.addPos(0, 0, 0, 500))
.addBus("SWING_TRANS", new BusAnimationSequence()
.addPos(-3, 0, 0, 75)
.addPos(8, 0, 0, 150)
.addPos(0, 0, 0, 500));
Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.func_147674_a(new ResourceLocation("hbm:weapon.cSwing"), 0.8F + player.getRNG().nextFloat() * 0.2F));
String id = ModItems.crucible.getUnlocalizedName();
HbmAnimations.hotbar[player.inventory.currentItem][0] = new Animation(id, System.currentTimeMillis(), animation, null);
}
}
/* chainsaw swing */
if("sSwing".equals(mode) || "lSwing".equals(mode)) { //temp for lance
int forward = 150;
int sideways = 100;
int retire = 200;
if(HbmAnimations.getRelevantAnim() == null) {
BusAnimation animation = new BusAnimation()
.addBus("SWING_ROT", new BusAnimationSequence()
.addPos(0, 0, 90, forward)
.addPos(45, 0, 90, sideways)
.addPos(0, 0, 0, retire))
.addBus("SWING_TRANS", new BusAnimationSequence()
.addPos(0, 0, 3, forward)
.addPos(2, 0, 2, sideways)
.addPos(0, 0, 0, retire));
HbmAnimations.hotbar[player.inventory.currentItem][0] = new Animation(player.getHeldItem().getItem().getUnlocalizedName(), System.currentTimeMillis(), animation, null);
} else {
double[] rot = HbmAnimations.getRelevantTransformation("SWING_ROT");
double[] trans = HbmAnimations.getRelevantTransformation("SWING_TRANS");
if(System.currentTimeMillis() - HbmAnimations.getRelevantAnim().startMillis < 50) return;
BusAnimation animation = new BusAnimation()
.addBus("SWING_ROT", new BusAnimationSequence()
.addPos(rot[0], rot[1], rot[2], 0)
.addPos(0, 0, 90, forward)
.addPos(45, 0, 90, sideways)
.addPos(0, 0, 0, retire))
.addBus("SWING_TRANS", new BusAnimationSequence()
.addPos(trans[0], trans[1], trans[2], 0)
.addPos(0, 0, 3, forward)
.addPos(2, 0, 2, sideways)
.addPos(0, 0, 0, retire));
HbmAnimations.hotbar[player.inventory.currentItem][0] = new Animation(player.getHeldItem().getItem().getUnlocalizedName(), System.currentTimeMillis(), animation, null);
}
}
if("generic".equals(mode)) {
ItemStack stack = player.getHeldItem();
if(stack != null && stack.getItem() instanceof IAnimatedItem) {
IAnimatedItem item = (IAnimatedItem) stack.getItem();
BusAnimation anim = item.getAnimation(data, stack);
if(anim != null) {
HbmAnimations.hotbar[player.inventory.currentItem][0] = new Animation(player.getHeldItem().getItem().getUnlocalizedName(), System.currentTimeMillis(), anim, null);
}
}
}
}
if("tau".equals(type)) {
for(int i = 0; i < data.getByte("count"); i++)

View File

@ -3,6 +3,7 @@ package com.hbm.packet.toclient;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import com.hbm.items.IAnimatedItem;
import com.hbm.items.armor.ArmorTrenchmaster;
import com.hbm.items.weapon.sedna.GunConfig;
import com.hbm.items.weapon.sedna.ItemGunBaseNT;
@ -64,32 +65,34 @@ public class GunAnimationPacket implements IMessage {
}
public static class Handler implements IMessageHandler<GunAnimationPacket, IMessage> {
@Override
@SideOnly(Side.CLIENT)
public IMessage onMessage(GunAnimationPacket m, MessageContext ctx) {
try {
EntityPlayer player = Minecraft.getMinecraft().thePlayer;
ItemStack stack = player.getHeldItem();
int slot = player.inventory.currentItem;
if(stack == null) return null;
if(stack.getItem() instanceof ItemGunBaseNT) {
handleSedna(player, stack, slot, AnimType.values()[m.type], m.receiverIndex, m.gunIndex);
} else if(stack.getItem() instanceof IAnimatedItem) {
handleItem(player, stack, slot, AnimType.values()[m.type], m.receiverIndex, m.gunIndex);
}
} catch(Exception x) { }
return null;
}
public static void handleSedna(EntityPlayer player, ItemStack stack, int slot, AnimType type, int receiverIndex, int gunIndex) {
ItemGunBaseNT gun = (ItemGunBaseNT) stack.getItem();
GunConfig config = gun.getConfig(stack, gunIndex);
if(type == AnimType.CYCLE) {
if(gunIndex < gun.lastShot.length) gun.lastShot[gunIndex] = System.currentTimeMillis();
gun.shotRand = player.worldObj.rand.nextDouble();
@ -101,24 +104,32 @@ public class GunAnimationPacket implements IMessage {
if(onRecoil != null) onRecoil.accept(stack, new LambdaContext(config, player, player.inventory, receiverIndex));
}
}
BiFunction<ItemStack, AnimType, BusAnimation> anims = config.getAnims(stack);
BusAnimation animation = anims.apply(stack, type);
if(animation == null && type == AnimType.RELOAD_EMPTY) {
animation = anims.apply(stack, AnimType.RELOAD);
}
if(animation == null && (type == AnimType.ALT_CYCLE || type == AnimType.CYCLE_EMPTY)) {
animation = anims.apply(stack, AnimType.CYCLE);
}
if(animation != null) {
Minecraft.getMinecraft().entityRenderer.itemRenderer.resetEquippedProgress();
Minecraft.getMinecraft().entityRenderer.itemRenderer.itemToRender = stack;
boolean isReloadAnimation = type == AnimType.RELOAD || type == AnimType.RELOAD_CYCLE || type == AnimType.RELOAD_EMPTY;
boolean isReloadAnimation = type == AnimType.RELOAD || type == AnimType.RELOAD_CYCLE;
if(isReloadAnimation && ArmorTrenchmaster.isTrenchMaster(player)) animation.setTimeMult(0.5D);
HbmAnimations.hotbar[slot][gunIndex] = new Animation(stack.getItem().getUnlocalizedName(), System.currentTimeMillis(), animation, type, isReloadAnimation && config.getReloadAnimSequential(stack));
HbmAnimations.hotbar[slot][gunIndex] = new Animation(stack.getItem().getUnlocalizedName(), System.currentTimeMillis(), animation, isReloadAnimation && config.getReloadAnimSequential(stack));
}
}
public static void handleItem(EntityPlayer player, ItemStack stack, int slot, AnimType type, int receiverIndex, int itemIndex) {
IAnimatedItem item = (IAnimatedItem) stack.getItem();
BusAnimation animation = item.getAnimation(type, stack);
if(animation != null) {
HbmAnimations.hotbar[slot][itemIndex] = new Animation(stack.getItem().getUnlocalizedName(), System.currentTimeMillis(), animation);
}
}
}
}

View File

@ -8,7 +8,7 @@ import net.minecraft.item.ItemStack;
import org.lwjgl.opengl.GL11;
public class HbmAnimations {
//in flans mod and afaik also MW, there's an issue that there is only one
//single animation timer for each client. this is fine for the most part,
//but once you reload and switch weapons while the animation plays, the
@ -17,13 +17,12 @@ public class HbmAnimations {
//"trick" the system by putting a weapon into a different slot while an
//animation is playing, though this will cancel the animation entirely.
public static final Animation[][] hotbar = new Animation[9][8]; //now with 8 parallel rails per slot! time to get railed!
public static enum AnimType {
RELOAD, //either a full reload or start of a reload
@Deprecated RELOAD_EMPTY, //same as reload, but the mag is completely empty
RELOAD_CYCLE, //animation that plays for every individual round (for shotguns and similar single round loading weapons)
RELOAD_END, //animation for transitioning from our RELOAD_CYCLE to idle
CYCLE, //animation for every firing cycle
CYCLE, //animation for every firing cycle / weapon swing
CYCLE_EMPTY, //animation for the final shot in the magazine
CYCLE_DRY, //animation for trying to fire, but no round is available
ALT_CYCLE, //animation for alt fire cycles
@ -31,14 +30,14 @@ public class HbmAnimations {
SPINDOWN, //animation for actionend
EQUIP, //animation for drawing the weapon
INSPECT, //animation for inspecting the weapon
JAMMED //animation for jammed weapons
JAMMED, //animation for jammed weapons
}
// A NOTE ON SHOTGUN STYLE RELOADS
// Make sure the RELOAD and RELOAD_EMPTY adds shells, not just RELOAD_CYCLE, they all proc once for each loaded shell
// Make sure the RELOAD adds shells, not just RELOAD_CYCLE, they all proc once for each loaded shell
public static class Animation {
//the "name" of the animation slot. if the item has a different key than
//the animation, the animation will be canceled.
public String key;
@ -48,64 +47,60 @@ public class HbmAnimations {
public BusAnimation animation;
// If set, don't cancel this animation when the timer ends, instead wait for the next to start
public boolean holdLastFrame = false;
// so we know what type of animation we're playing, only used rarely
public AnimType type;
public Animation(String key, long startMillis, BusAnimation animation, AnimType type) {
public Animation(String key, long startMillis, BusAnimation animation) {
this.key = key;
this.startMillis = startMillis;
this.animation = animation;
this.type = type;
}
public Animation(String key, long startMillis, BusAnimation animation, AnimType type, boolean holdLastFrame) {
public Animation(String key, long startMillis, BusAnimation animation, boolean holdLastFrame) {
this.key = key;
this.startMillis = startMillis;
this.animation = animation;
this.holdLastFrame = holdLastFrame;
this.type = type;
}
}
public static Animation getRelevantAnim() { return getRelevantAnim(0); }
public static Animation getRelevantAnim(int index) {
EntityPlayer player = Minecraft.getMinecraft().thePlayer;
int slot = player.inventory.currentItem;
ItemStack stack = player.getHeldItem();
if(stack == null)
return null;
if(slot < 0 || slot > 8) { //for freak of nature hotbars, probably won't work right but at least it doesn't crash
slot = Math.abs(slot) % 9;
}
if(hotbar[slot][index] == null)
return null;
if(hotbar[slot][index].key.equals(stack.getItem().getUnlocalizedName())) {
return hotbar[slot][index];
}
return null;
}
public static double[] getRelevantTransformation(String bus) { return getRelevantTransformation(bus, 0); }
public static double[] getRelevantTransformation(String bus, int index) {
Animation anim = HbmAnimations.getRelevantAnim(index);
if(anim != null) {
BusAnimation buses = anim.animation;
int millis = (int)(Clock.get_ms() - anim.startMillis);
BusAnimationSequence seq = buses.getBus(bus);
if(seq != null) {
double[] trans = seq.getTransformation(millis);
if(trans != null)
return trans;
}
@ -124,7 +119,7 @@ public class HbmAnimations {
public static void applyRelevantTransformation(String bus, int index) {
double[] transform = getRelevantTransformation(bus, index);
int[] rot = new int[] { (int)transform[12], (int)transform[13], (int)transform[14] };
GL11.glTranslated(transform[0], transform[1], transform[2]);
GL11.glRotated(transform[3 + rot[0]], rot[0] == 0 ? 1 : 0, rot[0] == 1 ? 1 : 0, rot[0] == 2 ? 1 : 0);
GL11.glRotated(transform[3 + rot[1]], rot[1] == 0 ? 1 : 0, rot[1] == 1 ? 1 : 0, rot[1] == 2 ? 1 : 0);