|
| 1 | +package zmaster587.advancedRocketry.subsystem.ejection; |
| 2 | + |
| 3 | +import net.minecraft.entity.item.EntityItem; |
| 4 | +import net.minecraft.item.ItemStack; |
| 5 | +import net.minecraft.util.EnumFacing; |
| 6 | +import net.minecraft.util.math.BlockPos; |
| 7 | +import net.minecraft.world.World; |
| 8 | +import zmaster587.advancedRocketry.integration.vs.VSIntegration; |
| 9 | + |
| 10 | +/** |
| 11 | + * Throwing matter out of a hull, done once for everyone who needs to. |
| 12 | + * <p> |
| 13 | + * Two subsystems eject solid objects overboard — life support sheds the carbon its scrubbers pull |
| 14 | + * out of the air, and the thermal system fires charged heat slugs when rejection cannot win. They |
| 15 | + * are deliberately DIFFERENT machines: charging a slug to just under its melting point needs |
| 16 | + * materials and a duty cycle an ordinary airlock has no business carrying, and the thermal dump is |
| 17 | + * required to be unusable as a steady state while the carbon sink IS one. What they share is this — |
| 18 | + * the act itself, which is fiddlier than it looks aboard a ship. |
| 19 | + * <p> |
| 20 | + * <b>The clearance walk needs no frame conversion, and the ejection does.</b> A ship's blocks live |
| 21 | + * in a stationary subspace and that is exactly where the world stores them, so asking "are the next |
| 22 | + * few blocks along my facing empty?" is ordinary block arithmetic and is correct on a flying ship |
| 23 | + * without touching a transform. An item ENTITY, on the other hand, lives in the world frame, so the |
| 24 | + * spawn crosses the seam and every part of it must be mapped. |
| 25 | + */ |
| 26 | +public final class EjectionPort { |
| 27 | + |
| 28 | + /** Clear of the port's mouth before the item is released, so it never spawns inside the hull. */ |
| 29 | + private static final double MUZZLE_OFFSET = 0.6D; |
| 30 | + |
| 31 | + private EjectionPort() { |
| 32 | + } |
| 33 | + |
| 34 | + /** |
| 35 | + * How far along {@code facing} the first obstruction sits, or 0 when the exit is clear. |
| 36 | + * <p> |
| 37 | + * Reported as a distance rather than a boolean because a blocked port is something a player has |
| 38 | + * to go and FIND: "blocked at 2" sends them to the right block, "blocked" sends them around the |
| 39 | + * ship. Air and replaceable blocks (grass, snow) do not count as obstructions — a port that |
| 40 | + * refused to fire because of a snow layer would read as broken. |
| 41 | + */ |
| 42 | + public static int obstructionDistance(World world, BlockPos pos, EnumFacing facing, int clearance) { |
| 43 | + if (world == null || pos == null || facing == null) { |
| 44 | + return 1; |
| 45 | + } |
| 46 | + for (int step = 1; step <= Math.max(1, clearance); step++) { |
| 47 | + BlockPos ahead = pos.offset(facing, step); |
| 48 | + if (!world.isAirBlock(ahead) |
| 49 | + && !world.getBlockState(ahead).getBlock().isReplaceable(world, ahead)) { |
| 50 | + return step; |
| 51 | + } |
| 52 | + } |
| 53 | + return 0; |
| 54 | + } |
| 55 | + |
| 56 | + /** |
| 57 | + * Releases one stack out of the port, in the world frame, carrying the ship's own motion. |
| 58 | + * <p> |
| 59 | + * The carry matters as much as the direction: an item released with the ship's velocity |
| 60 | + * subtracted out simply reappears inside the hull a tick later, because the hull is moving and |
| 61 | + * the item is not. Sampling the velocity at the port's own world point rather than at the |
| 62 | + * ship's centre also gets rotation right — a port far out on a spinning hull is moving faster |
| 63 | + * than the hub it turns around. |
| 64 | + * |
| 65 | + * @return false when the stack was empty or the world refused the spawn; the caller keeps the |
| 66 | + * stack in that case rather than losing it |
| 67 | + */ |
| 68 | + public static boolean eject(World world, BlockPos pos, EnumFacing facing, ItemStack stack) { |
| 69 | + if (world == null || world.isRemote || pos == null || facing == null |
| 70 | + || stack == null || stack.isEmpty()) { |
| 71 | + return false; |
| 72 | + } |
| 73 | + |
| 74 | + double[] mouth = worldPointOf(world, pos); |
| 75 | + double[] direction = worldDirectionOf(world, pos, facing); |
| 76 | + |
| 77 | + double x = mouth[0] + 0.5D + direction[0] * MUZZLE_OFFSET; |
| 78 | + double y = mouth[1] + 0.5D + direction[1] * MUZZLE_OFFSET; |
| 79 | + double z = mouth[2] + 0.5D + direction[2] * MUZZLE_OFFSET; |
| 80 | + |
| 81 | + EntityItem item = new EntityItem(world, x, y, z, stack.copy()); |
| 82 | + item.setPickupDelay(20); |
| 83 | + |
| 84 | + double[] carry = VSIntegration.shipVelocityAtPoint(world, x, y, z); |
| 85 | + double carryX = carry == null ? 0.0D : carry[0]; |
| 86 | + double carryY = carry == null ? 0.0D : carry[1]; |
| 87 | + double carryZ = carry == null ? 0.0D : carry[2]; |
| 88 | + |
| 89 | + item.motionX = carryX + direction[0] * EJECT_SPEED; |
| 90 | + item.motionY = carryY + direction[1] * EJECT_SPEED; |
| 91 | + item.motionZ = carryZ + direction[2] * EJECT_SPEED; |
| 92 | + |
| 93 | + return world.spawnEntity(item); |
| 94 | + } |
| 95 | + |
| 96 | + /** Slow enough to read as "pushed out", fast enough to clear a hull that is turning. */ |
| 97 | + private static final double EJECT_SPEED = 0.35D; |
| 98 | + |
| 99 | + /** |
| 100 | + * Where this block actually IS right now. On a ship the block's own coordinates are a subspace |
| 101 | + * address that has nothing to do with where the hull is flying, so they are mapped; off a ship |
| 102 | + * the two frames coincide and the mapper answers null. |
| 103 | + */ |
| 104 | + private static double[] worldPointOf(World world, BlockPos pos) { |
| 105 | + double[] mapped = VSIntegration.getShipWorldPosition(world, pos); |
| 106 | + return mapped != null ? mapped : new double[]{pos.getX(), pos.getY(), pos.getZ()}; |
| 107 | + } |
| 108 | + |
| 109 | + /** |
| 110 | + * Which way "out of this face" points in the world. |
| 111 | + * <p> |
| 112 | + * Taken as the difference between two SUBSPACE points mapped through the same transform, rather |
| 113 | + * than by rotating a vector: it needs no ship identity, it is exact for any hull attitude, and |
| 114 | + * it degrades to the plain facing vector by construction when there is no ship, because then |
| 115 | + * both points map to themselves. |
| 116 | + */ |
| 117 | + private static double[] worldDirectionOf(World world, BlockPos pos, EnumFacing facing) { |
| 118 | + double[] here = VSIntegration.getShipWorldPosition(world, pos); |
| 119 | + double[] ahead = VSIntegration.getShipWorldPosition(world, pos.offset(facing)); |
| 120 | + if (here != null && ahead != null) { |
| 121 | + double dx = ahead[0] - here[0]; |
| 122 | + double dy = ahead[1] - here[1]; |
| 123 | + double dz = ahead[2] - here[2]; |
| 124 | + double length = Math.sqrt(dx * dx + dy * dy + dz * dz); |
| 125 | + if (length > 1.0E-6D) { |
| 126 | + return new double[]{dx / length, dy / length, dz / length}; |
| 127 | + } |
| 128 | + } |
| 129 | + return new double[]{facing.getFrontOffsetX(), facing.getFrontOffsetY(), facing.getFrontOffsetZ()}; |
| 130 | + } |
| 131 | +} |
0 commit comments