aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/gregtech/common/misc
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/gregtech/common/misc')
-rw-r--r--src/main/java/gregtech/common/misc/GT_ClientPollutionMap.java140
-rw-r--r--src/main/java/gregtech/common/misc/GT_Command.java340
-rw-r--r--src/main/java/gregtech/common/misc/GT_DrillingLogicDelegate.java266
-rw-r--r--src/main/java/gregtech/common/misc/GT_IDrillingLogicDelegateOwner.java22
-rw-r--r--src/main/java/gregtech/common/misc/GlobalEnergyWorldSavedData.java125
-rw-r--r--src/main/java/gregtech/common/misc/GlobalMetricsCoverDatabase.java492
-rw-r--r--src/main/java/gregtech/common/misc/GlobalVariableStorage.java15
-rw-r--r--src/main/java/gregtech/common/misc/WirelessNetworkManager.java93
-rw-r--r--src/main/java/gregtech/common/misc/spaceprojects/SpaceProjectManager.java309
-rw-r--r--src/main/java/gregtech/common/misc/spaceprojects/SpaceProjectWorldSavedData.java343
-rw-r--r--src/main/java/gregtech/common/misc/spaceprojects/base/SP_Requirements.java74
-rw-r--r--src/main/java/gregtech/common/misc/spaceprojects/base/SP_Upgrade.java362
-rw-r--r--src/main/java/gregtech/common/misc/spaceprojects/base/SpaceProject.java451
-rw-r--r--src/main/java/gregtech/common/misc/spaceprojects/commands/SPM_Command.java288
-rw-r--r--src/main/java/gregtech/common/misc/spaceprojects/commands/SP_Command.java166
-rw-r--r--src/main/java/gregtech/common/misc/spaceprojects/enums/JsonVariables.java22
-rw-r--r--src/main/java/gregtech/common/misc/spaceprojects/enums/SolarSystem.java104
-rw-r--r--src/main/java/gregtech/common/misc/spaceprojects/enums/SpaceBodyType.java17
-rw-r--r--src/main/java/gregtech/common/misc/spaceprojects/enums/StarType.java32
-rw-r--r--src/main/java/gregtech/common/misc/spaceprojects/enums/UpgradeStatus.java11
-rw-r--r--src/main/java/gregtech/common/misc/spaceprojects/interfaces/ISpaceBody.java37
-rw-r--r--src/main/java/gregtech/common/misc/spaceprojects/interfaces/ISpaceProject.java430
22 files changed, 4139 insertions, 0 deletions
diff --git a/src/main/java/gregtech/common/misc/GT_ClientPollutionMap.java b/src/main/java/gregtech/common/misc/GT_ClientPollutionMap.java
new file mode 100644
index 0000000000..6e40e5860c
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/GT_ClientPollutionMap.java
@@ -0,0 +1,140 @@
+package gregtech.common.misc;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.entity.EntityClientPlayerMP;
+import net.minecraft.util.MathHelper;
+
+public class GT_ClientPollutionMap {
+
+ private static final byte RADIUS = 24;
+ private static final byte DISTANCE_RELOAD_MAP = 5; // When player moved x chunks, shift the map to new center.
+ private static final byte SIZE = RADIUS * 2 + 1; // Area to keep stored.
+
+ private int x0, z0;
+ private int dim;
+
+ private boolean initialized = false;
+
+ private static short[][] chunkMatrix; // short because reasons.
+
+ public GT_ClientPollutionMap() {}
+
+ public void reset() {
+ initialized = false;
+ }
+
+ private void initialize(int playerChunkX, int playerChunkZ, int dimension) {
+ initialized = true;
+ chunkMatrix = new short[SIZE][SIZE];
+ x0 = playerChunkX;
+ z0 = playerChunkZ;
+ dim = dimension;
+ }
+
+ public void addChunkPollution(int chunkX, int chunkZ, int pollution) {
+ EntityClientPlayerMP player = Minecraft.getMinecraft().thePlayer;
+ if (player == null || player.worldObj == null) return;
+
+ int playerXChunk = MathHelper.floor_double(player.posX) >> 4;
+ int playerZChunk = MathHelper.floor_double(player.posZ) >> 4; // posX/Z seems to be always loaded,
+
+ if (!initialized) {
+ initialize(playerXChunk, playerZChunk, player.dimension);
+ }
+
+ if (dim != player.dimension) {
+ initialize(playerXChunk, playerZChunk, player.dimension);
+ }
+
+ if (Math.abs(x0 - playerXChunk) > DISTANCE_RELOAD_MAP || Math.abs(z0 - playerZChunk) > DISTANCE_RELOAD_MAP)
+ shiftCenter(playerXChunk, playerZChunk);
+
+ int relX = chunkX - x0 + RADIUS;
+ if (relX >= SIZE || relX < 0) // out of bounds
+ return;
+ int relZ = chunkZ - z0 + RADIUS;
+ if (relZ >= SIZE || relZ < 0) // out of bounds
+ return;
+
+ pollution = pollution / 225;
+ if (pollution > Short.MAX_VALUE) // Sanity
+ chunkMatrix[relX][relZ] = Short.MAX_VALUE; // Max pollution = 7,3mill
+ else if (pollution < 0) chunkMatrix[relX][relZ] = 0;
+ else chunkMatrix[relX][relZ] = (short) (pollution);
+ }
+
+ // xy interpolation, between 4 chunks as corners, unknown treated as 0.
+ public int getPollution(double fx, double fz) {
+ if (!initialized) return 0;
+ int x = MathHelper.floor_double(fx);
+ int z = MathHelper.floor_double(fz);
+ int xDiff = ((x - 8) >> 4) - x0;
+ int zDiff = ((z - 8) >> 4) - z0;
+
+ if (xDiff < -RADIUS || zDiff < -RADIUS || xDiff >= RADIUS || zDiff >= RADIUS) return 0;
+
+ // coordinates in shifted chunk.
+ x = (x - 8) % 16;
+ z = (z - 8) % 16;
+ if (x < 0) x = 16 + x;
+ if (z < 0) z = 16 + z;
+
+ int xi = 15 - x;
+ int zi = 15 - z;
+
+ // read pollution in 4 corner chunks
+ int offsetX = RADIUS + xDiff;
+ int offsetZ = RADIUS + zDiff;
+
+ int c00 = chunkMatrix[offsetX][offsetZ];
+ int c10 = chunkMatrix[offsetX + 1][offsetZ];
+ int c01 = chunkMatrix[offsetX][offsetZ + 1];
+ int c11 = chunkMatrix[offsetX + 1][offsetZ + 1];
+
+ // Is divided by 15*15 but is handled when storing chunk data.
+ return c00 * xi * zi + c10 * x * zi + c01 * xi * z + c11 * x * z;
+ }
+
+ // shift the matrix to fit new center
+ private void shiftCenter(int chunkX, int chunkZ) {
+ int xDiff = chunkX - x0;
+ int zDiff = chunkZ - z0;
+ boolean[] allEmpty = new boolean[SIZE]; // skip check z row if its empty.
+ if (xDiff > 0) for (byte x = 0; x < SIZE; x++) {
+ int xOff = x + xDiff;
+ if (xOff < SIZE) {
+ chunkMatrix[x] = chunkMatrix[xOff].clone();
+ } else {
+ chunkMatrix[x] = new short[SIZE];
+ allEmpty[x] = true;
+ }
+ }
+ else if (xDiff < 0) for (byte x = SIZE - 1; x >= 0; x--) {
+ int xOff = x + xDiff;
+ if (xOff > 0) {
+ chunkMatrix[x] = chunkMatrix[xOff].clone();
+ } else {
+ chunkMatrix[x] = new short[SIZE];
+ allEmpty[x] = true;
+ }
+ }
+
+ if (zDiff > 0) for (byte x = 0; x < SIZE; x++) {
+ if (allEmpty[x]) continue;
+ for (int z = 0; z < SIZE; z++) {
+ int zOff = z + zDiff;
+ chunkMatrix[x][z] = (zOff < SIZE) ? chunkMatrix[x][zOff] : 0;
+ }
+ }
+ else if (zDiff < 0) for (byte x = 0; x < SIZE; x++) {
+ if (allEmpty[x]) continue;
+ for (int z = SIZE - 1; z >= 0; z--) {
+ int zOff = z + zDiff;
+ chunkMatrix[x][z] = (zOff > 0) ? chunkMatrix[x][zOff] : 0;
+ }
+ }
+
+ x0 = chunkX;
+ z0 = chunkZ;
+ }
+}
diff --git a/src/main/java/gregtech/common/misc/GT_Command.java b/src/main/java/gregtech/common/misc/GT_Command.java
new file mode 100644
index 0000000000..3bf73b6300
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/GT_Command.java
@@ -0,0 +1,340 @@
+package gregtech.common.misc;
+
+import static gregtech.common.misc.WirelessNetworkManager.addEUToGlobalEnergyMap;
+import static gregtech.common.misc.WirelessNetworkManager.getUserEU;
+import static gregtech.common.misc.WirelessNetworkManager.setUserEU;
+
+import java.lang.reflect.Field;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Stream;
+
+import net.minecraft.command.CommandBase;
+import net.minecraft.command.ICommandSender;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.ChunkCoordinates;
+import net.minecraft.util.EnumChatFormatting;
+
+import com.gtnewhorizon.structurelib.StructureLib;
+
+import gregtech.GT_Mod;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.objects.GT_ChunkManager;
+import gregtech.api.util.GT_Utility;
+import gregtech.common.GT_Pollution;
+import gregtech.common.misc.spaceprojects.SpaceProjectManager;
+
+public final class GT_Command extends CommandBase {
+
+ @Override
+ public String getCommandName() {
+ return "gt";
+ }
+
+ @Override
+ public String getCommandUsage(ICommandSender sender) {
+ return "Usage: gt <subcommand>. Valid subcommands are: toggle, chunks, pollution.";
+ }
+
+ private void printHelp(ICommandSender sender) {
+ sender.addChatMessage(
+ new ChatComponentText(
+ "Usage: gt <toggle|chunks|pollution|global_energy_add|global_energy_set|global_energy_join>"));
+ sender.addChatMessage(new ChatComponentText("\"toggle D1\" - toggles general.Debug (D1)"));
+ sender.addChatMessage(new ChatComponentText("\"toggle D2\" - toggles general.Debug2 (D2)"));
+ sender.addChatMessage(new ChatComponentText("\"toggle debugCleanroom\" - toggles cleanroom debug log"));
+ sender.addChatMessage(new ChatComponentText("\"toggle debugDriller\" - toggles oil drill debug log"));
+ sender.addChatMessage(new ChatComponentText("\"toggle debugBlockPump\" - Possible issues with pumps"));
+ sender.addChatMessage(new ChatComponentText("\"toggle debugBlockMiner\" - Possible issues with miners"));
+ sender.addChatMessage(
+ new ChatComponentText("\"toggle debugEntityCramming\" - How long it takes and how many entities it finds"));
+ sender.addChatMessage(new ChatComponentText("\"toggle debugWorldGen\" - toggles generic worldgen debug"));
+ sender.addChatMessage(new ChatComponentText("\"toggle debugOrevein\" - toggles worldgen ore vein debug"));
+ sender.addChatMessage(new ChatComponentText("\"toggle debugSmallOres\" - toggles worldgen small vein debug"));
+ sender.addChatMessage(new ChatComponentText("\"toggle debugStones\" - toggles worldgen stones debug"));
+ sender.addChatMessage(new ChatComponentText("\"toggle debugChunkloaders\" - toggles chunkloaders debug"));
+ sender.addChatMessage(new ChatComponentText("\"toggle debugMulti\" - toggles structurelib debug"));
+ sender.addChatMessage(new ChatComponentText("\"chunks\" - print a list of the force loaded chunks"));
+ sender.addChatMessage(
+ new ChatComponentText(
+ "\"pollution <amount>\" - adds the <amount> of the pollution to the current chunk, "
+ + "\n if <amount> isnt specified, will add"
+ + GT_Mod.gregtechproxy.mPollutionSmogLimit
+ + "gibbl."));
+ sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GOLD + " --- Global wireless EU controls ---"));
+ sender.addChatMessage(new ChatComponentText("Allows you to set the amount of EU in a users wireless network."));
+ sender.addChatMessage(
+ new ChatComponentText(
+ "Usage:" + EnumChatFormatting.RED
+ + " global_energy_set "
+ + EnumChatFormatting.BLUE
+ + "[Name] "
+ + EnumChatFormatting.LIGHT_PURPLE
+ + "[EU]"));
+ sender.addChatMessage(
+ new ChatComponentText("Allows you to add EU to a users wireless network. Also accepts negative numbers."));
+ sender.addChatMessage(
+ new ChatComponentText(
+ "Usage:" + EnumChatFormatting.RED
+ + " global_energy_add "
+ + EnumChatFormatting.BLUE
+ + "[Name] "
+ + EnumChatFormatting.LIGHT_PURPLE
+ + "[EU]"));
+ sender.addChatMessage(
+ new ChatComponentText(
+ "Allows you to join two users together into one network. Can be undone by writing the users name twice."));
+ sender.addChatMessage(
+ new ChatComponentText(
+ "Usage:" + EnumChatFormatting.RED
+ + " global_energy_join "
+ + EnumChatFormatting.BLUE
+ + "[User joining] [User to join]"));
+ sender.addChatMessage(new ChatComponentText("Shows the amount of EU in a users energy network."));
+ sender.addChatMessage(
+ new ChatComponentText(
+ "Usage:" + EnumChatFormatting.RED + " global_energy_display " + EnumChatFormatting.BLUE + "[Name]"));
+ }
+
+ @Override
+ public List<String> addTabCompletionOptions(ICommandSender sender, String[] ss) {
+ List<String> l = new ArrayList<>();
+ String test = ss.length == 0 ? "" : ss[0].trim();
+ if (ss.length == 0 || ss.length == 1 && (test.isEmpty() || Stream
+ .of(
+ "toggle",
+ "chunks",
+ "pollution",
+ "global_energy_add",
+ "global_energy_set",
+ "global_energy_join",
+ "global_energy_display")
+ .anyMatch(s -> s.startsWith(test)))) {
+ Stream
+ .of(
+ "toggle",
+ "chunks",
+ "pollution",
+ "global_energy_add",
+ "global_energy_set",
+ "global_energy_join",
+ "global_energy_display")
+ .filter(s -> test.isEmpty() || s.startsWith(test))
+ .forEach(l::add);
+ } else if (test.equals("toggle")) {
+ String test1 = ss[1].trim();
+ Stream
+ .of(
+ "D1",
+ "D2",
+ "debugCleanroom",
+ "debugDriller",
+ "debugBlockPump",
+ "debugBlockMiner",
+ "debugWorldGen",
+ "debugEntityCramming",
+ "debugOrevein",
+ "debugSmallOres",
+ "debugStones",
+ "debugChunkloaders",
+ "debugMulti",
+ "debugWorldData")
+ .filter(s -> test1.isEmpty() || s.startsWith(test1))
+ .forEach(l::add);
+ }
+ return l;
+ }
+
+ @Override
+ public void processCommand(ICommandSender sender, String[] strings) {
+ if (strings.length < 1) {
+ printHelp(sender);
+ return;
+ }
+ switch (strings[0]) {
+ case "toggle" -> {
+ if (strings.length < 2) {
+ printHelp(sender);
+ return;
+ }
+ if ("debugMulti".equals(strings[1])) {
+ StructureLib.DEBUG_MODE = !StructureLib.DEBUG_MODE;
+ sender.addChatMessage(
+ new ChatComponentText(strings[1] + " = " + (StructureLib.DEBUG_MODE ? "true" : "false")));
+ return;
+ }
+ try {
+ Field field = GT_Values.class.getDeclaredField(strings[1]);
+ if (field.getType() != boolean.class) {
+ sender.addChatMessage(new ChatComponentText("Wrong variable: " + strings[1]));
+ return;
+ }
+ boolean b = !field.getBoolean(null);
+ field.setBoolean(null, b);
+ sender.addChatMessage(new ChatComponentText(strings[1] + " = " + (b ? "true" : "false")));
+ } catch (Exception e) {
+ sender.addChatMessage(new ChatComponentText("No such variable: " + strings[0]));
+ }
+ }
+ case "chunks" -> {
+ GT_ChunkManager.printTickets();
+ sender.addChatMessage(new ChatComponentText("Forced chunks logged to GregTech.log"));
+ }
+ case "pollution" -> {
+ ChunkCoordinates coordinates = sender.getPlayerCoordinates();
+ int amount = (strings.length < 2) ? GT_Mod.gregtechproxy.mPollutionSmogLimit
+ : Integer.parseInt(strings[1]);
+ GT_Pollution.addPollution(
+ sender.getEntityWorld()
+ .getChunkFromBlockCoords(coordinates.posX, coordinates.posZ),
+ amount);
+ }
+ case "global_energy_add" -> {
+ String username = strings[1];
+ String formatted_username = EnumChatFormatting.BLUE + username + EnumChatFormatting.RESET;
+ UUID uuid = SpaceProjectManager.getPlayerUUIDFromName(username);
+
+ String EU_String = strings[2];
+
+ // Usage is /gt global_energy_add username EU
+
+ String EU_string_formatted = EnumChatFormatting.RED
+ + GT_Utility.formatNumbers(new BigInteger(EU_String))
+ + EnumChatFormatting.RESET;
+
+ if (addEUToGlobalEnergyMap(uuid, new BigInteger(EU_String))) sender.addChatMessage(
+ new ChatComponentText(
+ "Successfully added " + EU_string_formatted
+ + "EU to the global energy network of "
+ + formatted_username
+ + "."));
+ else sender.addChatMessage(
+ new ChatComponentText(
+ "Failed to add " + EU_string_formatted
+ + "EU to the global energy map of "
+ + formatted_username
+ + ". Insufficient energy in network. "));
+
+ sender.addChatMessage(
+ new ChatComponentText(
+ formatted_username + " currently has "
+ + EnumChatFormatting.RED
+ + GT_Utility.formatNumbers(new BigInteger(getUserEU(uuid).toString()))
+ + EnumChatFormatting.RESET
+ + "EU in their network."));
+
+ }
+ case "global_energy_set" -> {
+
+ // Usage is /gt global_energy_set username EU
+
+ String username = strings[1];
+ String formatted_username = EnumChatFormatting.BLUE + username + EnumChatFormatting.RESET;
+ UUID uuid = SpaceProjectManager.getPlayerUUIDFromName(username);
+
+ String EU_String_0 = strings[2];
+
+ if ((new BigInteger(EU_String_0).compareTo(BigInteger.ZERO)) < 0) {
+ sender.addChatMessage(
+ new ChatComponentText("Cannot set a users energy network to a negative value."));
+ break;
+ }
+
+ setUserEU(uuid, new BigInteger(EU_String_0));
+
+ sender.addChatMessage(
+ new ChatComponentText(
+ "Successfully set " + formatted_username
+ + "'s global energy network to "
+ + EnumChatFormatting.RED
+ + GT_Utility.formatNumbers(new BigInteger(EU_String_0))
+ + EnumChatFormatting.RESET
+ + "EU."));
+
+ }
+ case "global_energy_join" -> {
+
+ // Usage is /gt global_energy_join username_of_you username_to_join
+
+ String usernameSubject = strings[1];
+ String usernameTeam = strings[2];
+
+ String formattedUsernameSubject = EnumChatFormatting.BLUE + usernameSubject + EnumChatFormatting.RESET;
+ String formattedUsernameTeam = EnumChatFormatting.BLUE + usernameTeam + EnumChatFormatting.RESET;
+
+ UUID uuidSubject = SpaceProjectManager.getPlayerUUIDFromName(usernameSubject);
+ UUID uuidTeam = SpaceProjectManager.getLeader(SpaceProjectManager.getPlayerUUIDFromName(usernameTeam));
+
+ if (uuidSubject.equals(uuidTeam)) {
+ // leave team
+ SpaceProjectManager.putInTeam(uuidSubject, uuidSubject);
+ sender.addChatMessage(
+ new ChatComponentText(
+ "User " + formattedUsernameSubject + " has rejoined their own global energy network."));
+ break;
+ }
+
+ // join other's team
+
+ if (uuidSubject.equals(uuidTeam)) {
+ sender.addChatMessage(new ChatComponentText("They are already in the same network!"));
+ break;
+ }
+
+ SpaceProjectManager.putInTeam(uuidSubject, uuidTeam);
+
+ sender.addChatMessage(
+ new ChatComponentText(
+ "Success! " + formattedUsernameSubject + " has joined " + formattedUsernameTeam + "."));
+ sender.addChatMessage(
+ new ChatComponentText(
+ "To undo this simply join your own network again with /gt global_energy_join "
+ + formattedUsernameSubject
+ + " "
+ + formattedUsernameSubject
+ + "."));
+
+ }
+ case "global_energy_display" -> {
+
+ // Usage is /gt global_energy_display username.
+
+ String username = strings[1];
+ String formatted_username = EnumChatFormatting.BLUE + username + EnumChatFormatting.RESET;
+ UUID userUUID = SpaceProjectManager.getPlayerUUIDFromName(username);
+
+ if (!SpaceProjectManager.isInTeam(userUUID)) {
+ sender.addChatMessage(
+ new ChatComponentText("User " + formatted_username + " has no global energy network."));
+ break;
+ }
+ UUID teamUUID = SpaceProjectManager.getLeader(userUUID);
+
+ sender.addChatMessage(
+ new ChatComponentText(
+ "User " + formatted_username
+ + " has "
+ + EnumChatFormatting.RED
+ + GT_Utility.formatNumbers(getUserEU(userUUID))
+ + EnumChatFormatting.RESET
+ + "EU in their network."));
+ if (!userUUID.equals(teamUUID)) sender.addChatMessage(
+ new ChatComponentText(
+ "User " + formatted_username
+ + " is currently in network of "
+ + EnumChatFormatting.BLUE
+ + SpaceProjectManager.getPlayerNameFromUUID(teamUUID)
+ + EnumChatFormatting.RESET
+ + "."));
+
+ }
+ default -> {
+ sender
+ .addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "Invalid command/syntax detected."));
+ printHelp(sender);
+ }
+ }
+ }
+}
diff --git a/src/main/java/gregtech/common/misc/GT_DrillingLogicDelegate.java b/src/main/java/gregtech/common/misc/GT_DrillingLogicDelegate.java
new file mode 100644
index 0000000000..9cf7fd7cf8
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/GT_DrillingLogicDelegate.java
@@ -0,0 +1,266 @@
+package gregtech.common.misc;
+
+import static gregtech.api.enums.GT_Values.debugBlockMiner;
+
+import java.util.List;
+
+import net.minecraft.block.Block;
+import net.minecraft.init.Blocks;
+import net.minecraft.item.ItemStack;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraftforge.common.util.FakePlayer;
+
+import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
+import gregtech.api.util.GT_Log;
+import gregtech.api.util.GT_ModHandler;
+import gregtech.api.util.GT_Utility;
+import gregtech.common.blocks.GT_TileEntity_Ores;
+
+/** @author Relvl on 27.01.2022 */
+@SuppressWarnings("ObjectEquality")
+public class GT_DrillingLogicDelegate {
+
+ public static final ItemStack MINING_PIPE_STACK = GT_ModHandler.getIC2Item("miningPipe", 0);
+ public static final Block MINING_PIPE_BLOCK = GT_Utility.getBlockFromStack(MINING_PIPE_STACK);
+ public static final Block MINING_PIPE_TIP_BLOCK = GT_Utility
+ .getBlockFromStack(GT_ModHandler.getIC2Item("miningPipeTip", 0));
+
+ /** The owner machine pointer */
+ private final GT_IDrillingLogicDelegateOwner owner;
+
+ /** Is pipe retracting process done and halts? */
+ private boolean isRetractDone;
+ /** Is machine ran out of mining pipes in its inventory and halts? */
+ private boolean isWaitingForPipeItem;
+ /** Pipe tip depth (relative to machine Y position, NEGATIVE). */
+ private int tipDepth;
+ /** Cached fake player */
+ private FakePlayer mFakePlayer;
+
+ public GT_DrillingLogicDelegate(GT_IDrillingLogicDelegateOwner owner) {
+ this.owner = owner;
+ }
+
+ /** Descents a pipe tip one plock deeper. */
+ public boolean descent(IGregTechTileEntity te) {
+ if (!te.isAllowedToWork()) {
+ return false;
+ }
+
+ int xCoord = te.getXCoord();
+ int zCoord = te.getZCoord();
+ int yCoord = te.getYCoord();
+ int checkY = yCoord + tipDepth - 1;
+ boolean isHitsTheVoid = checkY < 0;
+ boolean isHitsBedrock = GT_Utility.getBlockHardnessAt(te.getWorld(), xCoord, checkY, zCoord) < 0;
+ boolean isFakePlayerAllowed = canFakePlayerInteract(te, xCoord, checkY, zCoord);
+
+ if (isHitsTheVoid || isHitsBedrock || !isFakePlayerAllowed) {
+ // Disable and start retracting process.
+ te.disableWorking();
+ if (debugBlockMiner) {
+ if (isHitsTheVoid) {
+ GT_Log.out.println("MINER: Hit bottom");
+ }
+ if (isHitsBedrock) {
+ GT_Log.out.println("MINER: Hit block with -1 hardness");
+ }
+ if (!isFakePlayerAllowed) {
+ GT_Log.out.println("MINER: Unable to set mining pipe tip");
+ }
+ }
+ return false;
+ }
+
+ // Replace the tip onto pipe
+ if (te.getBlockOffset(0, tipDepth, 0) == MINING_PIPE_TIP_BLOCK) {
+ te.getWorld()
+ .setBlock(xCoord, yCoord + tipDepth, zCoord, MINING_PIPE_BLOCK);
+ }
+ // Get and decrease pipe from the machine
+ boolean pipeTaken = owner.pullInputs(MINING_PIPE_STACK.getItem(), 1, false);
+ if (!pipeTaken) {
+ // If there was nothing - waiting for the pipes (just for prevent unnecessary checks)
+ isWaitingForPipeItem = true;
+ return false;
+ }
+
+ // If there is something - mine it
+ Block block = te.getBlockOffset(0, tipDepth - 1, 0);
+ if (!block.isAir(te.getWorld(), xCoord, yCoord, zCoord)) {
+ mineBlock(te, block, xCoord, yCoord + tipDepth - 1, zCoord);
+ }
+
+ // Descent the pipe tip
+ te.getWorld()
+ .setBlock(xCoord, yCoord + tipDepth - 1, zCoord, MINING_PIPE_TIP_BLOCK);
+ tipDepth--;
+ return true;
+ }
+
+ public void onOwnerPostTick(IGregTechTileEntity te, long tick) {
+ // If the machine was disabled - try to retract pipe
+ if (!te.isAllowedToWork()) {
+ onPostTickRetract(te, tick);
+ return;
+ }
+ // If the machine was re-enabled - we should reset the retracting process
+ isRetractDone = false;
+ }
+
+ /** If the machine are disabled - tried to retract pipe. */
+ private void onPostTickRetract(IGregTechTileEntity aBaseMetaTileEntity, long aTick) {
+ if (isRetractDone) {
+ return;
+ }
+ // If retracting process just touch the miner
+ if (tipDepth == 0) {
+ isRetractDone = true;
+ return;
+ }
+ // Once per N ticks (depends on tier)
+ if ((aTick % (owner.getMachineSpeed() / 5)) != 0) {
+ return;
+ }
+
+ // Check we can push pipe back to machine (inputs allowed for this case!)
+ boolean canPush = owner.pushOutputs(MINING_PIPE_STACK, 1, true, true);
+ if (!canPush) {
+ return;
+ }
+
+ // Inspect target block - it should be a pipe tip, else something went wrong.
+ Block targetBlock = aBaseMetaTileEntity.getBlockOffset(0, tipDepth, 0);
+ if (targetBlock != MINING_PIPE_TIP_BLOCK && targetBlock != MINING_PIPE_BLOCK) {
+ return;
+ }
+
+ // Retract the pipe/tip
+ int xCoord = aBaseMetaTileEntity.getXCoord();
+ int yCoord = aBaseMetaTileEntity.getYCoord();
+ int zCoord = aBaseMetaTileEntity.getZCoord();
+ int actualDrillY = yCoord + tipDepth;
+ // Move the pipe tip position
+ if (actualDrillY < yCoord - 1) {
+ owner.getBaseMetaTileEntity()
+ .getWorld()
+ .setBlock(xCoord, actualDrillY + 1, zCoord, MINING_PIPE_TIP_BLOCK);
+ }
+ // Remove the old pipe tip
+ aBaseMetaTileEntity.getWorld()
+ .setBlock(xCoord, actualDrillY, zCoord, Blocks.air, 0, /* send to client without neighbour updates */ 2);
+
+ // Return the pipe back to the machine (inputs allowed for this case!)
+ owner.pushOutputs(MINING_PIPE_STACK, 1, false, true);
+
+ tipDepth++;
+ }
+
+ /** Minings the block if it is possible. */
+ public void mineBlock(IGregTechTileEntity te, Block block, int x, int y, int z) {
+ if (!GT_Utility.eraseBlockByFakePlayer(getFakePlayer(te), x, y, z, true)) {
+ return;
+ }
+
+ List<ItemStack> drops = getBlockDrops(block, x, y, z);
+
+ boolean canFitDrops = true;
+ for (ItemStack drop : drops) {
+ canFitDrops &= owner.pushOutputs(drop, drop.stackSize, true, false);
+ }
+ if (!canFitDrops) {
+ return;
+ }
+ for (ItemStack drop : drops) {
+ owner.pushOutputs(drop, drop.stackSize, false, false);
+ }
+
+ short metaData = 0;
+ TileEntity tTileEntity = owner.getBaseMetaTileEntity()
+ .getTileEntity(x, y, z);
+ if (tTileEntity instanceof GT_TileEntity_Ores) {
+ metaData = ((GT_TileEntity_Ores) tTileEntity).mMetaData;
+ }
+
+ ItemStack cobble = GT_Utility.getCobbleForOre(block, metaData);
+ te.getWorld()
+ .setBlock(
+ x,
+ y,
+ z,
+ Block.getBlockFromItem(cobble.getItem()),
+ cobble.getItemDamage(), /* cause updates(1) + send to client(2) */
+ 3);
+ }
+
+ /**
+ * Returns NEGATIVE (eg -5) depth of current drilling Y world level. RELATIVELY TO MINER ENTITY! This means '(miner
+ * world Y) + depth = (actual world Y)'.
+ */
+ public int getTipDepth() {
+ return tipDepth;
+ }
+
+ /** Looking for the lowest continuous pipe. */
+ public void findTipDepth() {
+ IGregTechTileEntity ownerTe = owner.getBaseMetaTileEntity();
+ if (!ownerTe.isServerSide()) {
+ return;
+ }
+ while (true) {
+ Block block = ownerTe.getBlockOffset(0, tipDepth - 1, 0);
+ if (block != MINING_PIPE_BLOCK && block != MINING_PIPE_TIP_BLOCK) {
+ return;
+ }
+ tipDepth--;
+ }
+ }
+
+ /**
+ * Creates and provides the Fake Player for owners. todo maybe will provide player owner uuid? im sure some servers
+ * not allow to fakers, in griefing reasons.
+ */
+ public FakePlayer getFakePlayer(IGregTechTileEntity te) {
+ if (mFakePlayer == null) {
+ mFakePlayer = GT_Utility.getFakePlayer(te);
+ }
+ if (mFakePlayer != null) {
+ mFakePlayer.setWorld(te.getWorld());
+ mFakePlayer.setPosition(te.getXCoord(), te.getYCoord(), te.getZCoord());
+ }
+ return mFakePlayer;
+ }
+
+ public boolean canFakePlayerInteract(IGregTechTileEntity te, int xCoord, int yCoord, int zCoord) {
+ return GT_Utility
+ .setBlockByFakePlayer(getFakePlayer(te), xCoord, yCoord, zCoord, MINING_PIPE_TIP_BLOCK, 0, true);
+ }
+
+ /** Get target block drops. We need to encapsulate everyting of mining in this class. */
+ private List<ItemStack> getBlockDrops(final Block oreBlock, int posX, int posY, int posZ) {
+ return oreBlock.getDrops(
+ owner.getBaseMetaTileEntity()
+ .getWorld(),
+ posX,
+ posY,
+ posZ,
+ owner.getBaseMetaTileEntity()
+ .getMetaID(posX, posY, posZ),
+ owner.getMachineTier());
+ }
+
+ /** Can the owner continue doing its work? If we await new pipes - it cannot. */
+ public boolean canContinueDrilling(long tick) {
+ if (isWaitingForPipeItem) {
+ if (tick % 5 != 0) {
+ return false;
+ }
+ boolean hasPipe = owner.pullInputs(MINING_PIPE_STACK.getItem(), 1, true);
+ if (hasPipe) {
+ isWaitingForPipeItem = false;
+ }
+ return hasPipe;
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/gregtech/common/misc/GT_IDrillingLogicDelegateOwner.java b/src/main/java/gregtech/common/misc/GT_IDrillingLogicDelegateOwner.java
new file mode 100644
index 0000000000..9b10b6fcf0
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/GT_IDrillingLogicDelegateOwner.java
@@ -0,0 +1,22 @@
+package gregtech.common.misc;
+
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+
+import gregtech.api.interfaces.metatileentity.IMetaTileEntity;
+
+/** @author Relvl on 27.01.2022 */
+public interface GT_IDrillingLogicDelegateOwner extends IMetaTileEntity {
+
+ /** Returns the machine actual tier. */
+ int getMachineTier();
+
+ /** Returns the machine current processing speed. */
+ int getMachineSpeed();
+
+ /** Pulls (or check can pull) items from an input slots. */
+ boolean pullInputs(Item item, int count, boolean simulate);
+
+ /** Pushes (or check can push) item to output slots. */
+ boolean pushOutputs(ItemStack stack, int count, boolean simulate, boolean allowInputSlots);
+}
diff --git a/src/main/java/gregtech/common/misc/GlobalEnergyWorldSavedData.java b/src/main/java/gregtech/common/misc/GlobalEnergyWorldSavedData.java
new file mode 100644
index 0000000000..1a03012649
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/GlobalEnergyWorldSavedData.java
@@ -0,0 +1,125 @@
+package gregtech.common.misc;
+
+import static gregtech.common.misc.GlobalVariableStorage.GlobalEnergy;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.world.World;
+import net.minecraft.world.WorldSavedData;
+import net.minecraft.world.storage.MapStorage;
+import net.minecraftforge.event.world.WorldEvent;
+
+import cpw.mods.fml.common.eventhandler.SubscribeEvent;
+import gregtech.common.misc.spaceprojects.SpaceProjectManager;
+
+public class GlobalEnergyWorldSavedData extends WorldSavedData {
+
+ public static GlobalEnergyWorldSavedData INSTANCE;
+
+ private static final String DATA_NAME = "GregTech_WirelessEUWorldSavedData";
+
+ private static final String GlobalEnergyNBTTag = "GregTech_GlobalEnergy_MapNBTTag";
+ private static final String GlobalEnergyTeamNBTTag = "GregTech_GlobalEnergyTeam_MapNBTTag";
+
+ private static void loadInstance(World world) {
+
+ GlobalEnergy.clear();
+
+ MapStorage storage = world.mapStorage;
+ INSTANCE = (GlobalEnergyWorldSavedData) storage.loadData(GlobalEnergyWorldSavedData.class, DATA_NAME);
+ if (INSTANCE == null) {
+ INSTANCE = new GlobalEnergyWorldSavedData();
+ storage.setData(DATA_NAME, INSTANCE);
+ }
+ INSTANCE.markDirty();
+ }
+
+ @SuppressWarnings("unused")
+ @SubscribeEvent
+ public void onWorldLoad(WorldEvent.Load event) {
+ if (!event.world.isRemote && event.world.provider.dimensionId == 0) {
+ loadInstance(event.world);
+ }
+ }
+
+ public GlobalEnergyWorldSavedData() {
+ super(DATA_NAME);
+ }
+
+ @SuppressWarnings("unused")
+ public GlobalEnergyWorldSavedData(String name) {
+ super(name);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void readFromNBT(NBTTagCompound nbtTagCompound) {
+
+ try {
+ byte[] ba = nbtTagCompound.getByteArray(GlobalEnergyNBTTag);
+ InputStream byteArrayInputStream = new ByteArrayInputStream(ba);
+ ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
+ Object data = objectInputStream.readObject();
+ HashMap<Object, BigInteger> hashData = (HashMap<Object, BigInteger>) data;
+ for (Map.Entry<Object, BigInteger> entry : hashData.entrySet()) {
+ try {
+ GlobalEnergy.put(
+ UUID.fromString(
+ entry.getKey()
+ .toString()),
+ entry.getValue());
+ } catch (RuntimeException ignored) {
+ // probably a malformed uuid. in any case, try carry on with the load
+ }
+ }
+ } catch (IOException | ClassNotFoundException exception) {
+ System.out.println(GlobalEnergyNBTTag + " FAILED");
+ exception.printStackTrace();
+ }
+ try {
+ if (!nbtTagCompound.hasKey(GlobalEnergyTeamNBTTag)) return;
+ byte[] ba = nbtTagCompound.getByteArray(GlobalEnergyTeamNBTTag);
+ InputStream byteArrayInputStream = new ByteArrayInputStream(ba);
+ ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
+ Object data = objectInputStream.readObject();
+ HashMap<String, String> oldTeams = (HashMap<String, String>) data;
+ for (String member : oldTeams.keySet()) {
+ String leader = oldTeams.get(member);
+ try {
+ SpaceProjectManager.putInTeam(UUID.fromString(member), UUID.fromString(leader));
+ } catch (RuntimeException ignored) {
+ // probably a malformed uuid. in any case, try carry on with the load
+ }
+ }
+ } catch (IOException | ClassNotFoundException exception) {
+ System.out.println(GlobalEnergyTeamNBTTag + " FAILED");
+ exception.printStackTrace();
+ }
+ }
+
+ @Override
+ public void writeToNBT(NBTTagCompound nbtTagCompound) {
+
+ try {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
+ objectOutputStream.writeObject(GlobalEnergy);
+ objectOutputStream.flush();
+ byte[] data = byteArrayOutputStream.toByteArray();
+ nbtTagCompound.setByteArray(GlobalEnergyNBTTag, data);
+ } catch (IOException exception) {
+ System.out.println(GlobalEnergyNBTTag + " SAVE FAILED");
+ exception.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/gregtech/common/misc/GlobalMetricsCoverDatabase.java b/src/main/java/gregtech/common/misc/GlobalMetricsCoverDatabase.java
new file mode 100644
index 0000000000..33e8198bd6
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/GlobalMetricsCoverDatabase.java
@@ -0,0 +1,492 @@
+package gregtech.common.misc;
+
+import static net.minecraftforge.common.util.Constants.NBT.TAG_BYTE_ARRAY;
+import static net.minecraftforge.common.util.Constants.NBT.TAG_COMPOUND;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import net.minecraft.entity.item.EntityItem;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagByteArray;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraft.util.StatCollector;
+import net.minecraft.world.WorldSavedData;
+import net.minecraft.world.storage.MapStorage;
+import net.minecraftforge.common.ForgeHooks;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.event.entity.item.ItemExpireEvent;
+import net.minecraftforge.event.world.BlockEvent;
+import net.minecraftforge.event.world.ExplosionEvent.Detonate;
+import net.minecraftforge.event.world.WorldEvent;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import cpw.mods.fml.common.eventhandler.SubscribeEvent;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.util.GT_Utility;
+import gregtech.common.covers.CoverInfo;
+import gregtech.common.covers.GT_Cover_Metrics_Transmitter;
+import gregtech.common.events.MetricsCoverDataEvent;
+import gregtech.common.events.MetricsCoverHostDeconstructedEvent;
+import gregtech.common.events.MetricsCoverSelfDestructEvent;
+
+/**
+ * Catches and provides data transmitted from deployed Metrics Transmitter covers. Only stores one result per frequency
+ * at a time. Metrics covers are intended to overwrite an old result every time they emit a new event.
+ * <br />
+ * <br />
+ * This information is only partially persisted; frequencies that are in a non-operational state will be written to
+ * disk, while operational frequencies are volatile. The assumption is that any frequency with a broadcasting card will,
+ * fairly quickly, re-assert its presence. Conversely, one-time events like deconstruction or self-destruction can occur
+ * while the card is in a container, rotting on the ground, etc.
+ */
+public class GlobalMetricsCoverDatabase extends WorldSavedData {
+
+ private static GlobalMetricsCoverDatabase INSTANCE;
+
+ /** Holds received metrics. */
+ private static final Map<UUID, Data> DATABASE = new ConcurrentHashMap<>();
+ /** Used to speed up event handlers dealing with block breaking and explosions. Not persisted. */
+ private static final Map<Coordinates, Set<UUID>> REVERSE_LOOKUP = new ConcurrentHashMap<>();
+
+ private static final String DATA_NAME = "GregTech_MetricsCoverDatabase";
+ private static final String DECONSTRUCTED_KEY = "GregTech_MetricsCoverDatabase_Deconstructed";
+ private static final String SELF_DESTRUCTED_KEY = "GregTech_MetricsCoverDatabase_SelfDestructed";
+
+ public GlobalMetricsCoverDatabase() {
+ this(DATA_NAME);
+ }
+
+ public GlobalMetricsCoverDatabase(String name) {
+ super(name);
+ }
+
+ @SuppressWarnings("unused")
+ @SubscribeEvent
+ public void receiveMetricsData(MetricsCoverDataEvent event) {
+ final Coordinates coordinates = event.getCoordinates();
+ store(event.getFrequency(), State.OPERATIONAL, event.getPayload(), coordinates);
+
+ if (!REVERSE_LOOKUP.containsKey(coordinates)) {
+ REVERSE_LOOKUP.put(coordinates, new HashSet<>());
+ }
+ REVERSE_LOOKUP.get(coordinates)
+ .add(event.getFrequency());
+ }
+
+ @SuppressWarnings("unused")
+ @SubscribeEvent
+ public void receiveHostDeconstructed(MetricsCoverHostDeconstructedEvent event) {
+ cullReverseLookupEntry(event.getFrequency());
+ store(event.getFrequency(), State.HOST_DECONSTRUCTED);
+ }
+
+ @SuppressWarnings("unused")
+ @SubscribeEvent
+ public void receiveSelfDestruct(MetricsCoverSelfDestructEvent event) {
+ cullReverseLookupEntry(event.getFrequency());
+ store(event.getFrequency(), State.SELF_DESTRUCTED);
+ }
+
+ @SuppressWarnings("unused")
+ @SubscribeEvent
+ public void onWorldLoad(WorldEvent.Load event) {
+ if (event.world.isRemote || event.world.provider.dimensionId != 0) {
+ return;
+ }
+
+ DATABASE.clear();
+
+ final MapStorage storage = event.world.mapStorage;
+ INSTANCE = (GlobalMetricsCoverDatabase) storage.loadData(GlobalMetricsCoverDatabase.class, DATA_NAME);
+ if (INSTANCE == null) {
+ INSTANCE = new GlobalMetricsCoverDatabase();
+ storage.setData(DATA_NAME, INSTANCE);
+ }
+
+ INSTANCE.markDirty();
+ }
+
+ @SuppressWarnings("unused")
+ @SubscribeEvent
+ public void onBlockBreak(BlockEvent.BreakEvent event) {
+ final Coordinates coords = new Coordinates(event.world.provider.getDimensionName(), event.x, event.y, event.z);
+ // In case someone else wants to listen to these, go the roundabout way.
+ final Set<UUID> uuids = REVERSE_LOOKUP.get(coords);
+ if (uuids != null) {
+ uuids.forEach(
+ uuid -> MinecraftForge.EVENT_BUS.post(
+ ForgeHooks.canHarvestBlock(event.block, event.getPlayer(), event.blockMetadata)
+ && !event.getPlayer().capabilities.isCreativeMode ? new MetricsCoverHostDeconstructedEvent(uuid)
+ : new MetricsCoverSelfDestructEvent(uuid)));
+ }
+ }
+
+ @SuppressWarnings("unused")
+ @SubscribeEvent
+ public void onExplosion(Detonate event) {
+ final String dimensionName = event.world.provider.getDimensionName();
+
+ event.getAffectedBlocks()
+ .forEach(chunkPosition -> {
+ final Set<UUID> uuids = REVERSE_LOOKUP.get(
+ new Coordinates(
+ dimensionName,
+ chunkPosition.chunkPosX,
+ chunkPosition.chunkPosY,
+ chunkPosition.chunkPosZ));
+
+ if (uuids != null) {
+ uuids.forEach(uuid -> MinecraftForge.EVENT_BUS.post(new MetricsCoverSelfDestructEvent(uuid)));
+ }
+ });
+
+ event.getAffectedEntities().forEach(entity -> {
+ if (entity instanceof final EntityItem entityItem) {
+ getCoverUUIDsFromItemStack(entityItem.getEntityItem())
+ .forEach(uuid -> MinecraftForge.EVENT_BUS.post(new MetricsCoverSelfDestructEvent(uuid)));
+ }
+ });
+ }
+
+ @SuppressWarnings("unused")
+ @SubscribeEvent
+ public void onItemExpiration(ItemExpireEvent event) {
+ getCoverUUIDsFromItemStack(event.entityItem.getEntityItem())
+ .forEach(uuid -> MinecraftForge.EVENT_BUS.post(new MetricsCoverSelfDestructEvent(uuid)));
+ }
+
+ /**
+ * Get the data for a frequency, if it exists.
+ *
+ * @param frequency The UUID corresponding to the frequency to retrieve.
+ * @return An Optional with the frequency's data, or an empty Optional if it doesn't exist.
+ */
+ @NotNull
+ public static Optional<Data> getData(UUID frequency) {
+ return Optional.ofNullable(DATABASE.get(frequency));
+ }
+
+ /**
+ * Once a card has received the fact that it has self-destructed, this method can be called to free up its spot
+ * in the database. Does nothing if the frequency is missing or is not in a self-destructed state.
+ *
+ * @param frequency The UUID corresponding to the frequency to possibly cull.
+ */
+ public static void clearSelfDestructedFrequency(UUID frequency) {
+ getData(frequency).ifPresent(data -> {
+ if (data.getState() == State.SELF_DESTRUCTED) {
+ DATABASE.remove(frequency);
+ tryMarkDirty();
+ }
+ });
+ }
+
+ @Override
+ public void readFromNBT(NBTTagCompound nbtTagCompound) {
+ final NBTTagList deconstructed = nbtTagCompound.getTagList(DECONSTRUCTED_KEY, TAG_BYTE_ARRAY);
+ final NBTTagList selfDestructed = nbtTagCompound.getTagList(SELF_DESTRUCTED_KEY, TAG_BYTE_ARRAY);
+
+ for (int i = 0; i < deconstructed.tagCount(); i++) {
+ final NBTTagByteArray byteArray = (NBTTagByteArray) deconstructed.removeTag(0);
+ reconstituteUUID(byteArray.func_150292_c())
+ .ifPresent(uuid -> DATABASE.put(uuid, new Data(State.HOST_DECONSTRUCTED)));
+ }
+
+ for (int i = 0; i < selfDestructed.tagCount(); i++) {
+ final NBTTagByteArray byteArray = (NBTTagByteArray) selfDestructed.removeTag(0);
+ reconstituteUUID(byteArray.func_150292_c())
+ .ifPresent(uuid -> DATABASE.put(uuid, new Data(State.SELF_DESTRUCTED)));
+ }
+ }
+
+ @Override
+ public void writeToNBT(NBTTagCompound nbtTagCompound) {
+ // We only care about persisting frequencies that aren't operational.
+ final NBTTagList deconstructed = new NBTTagList();
+ final NBTTagList selfDestructed = new NBTTagList();
+ DATABASE.forEach((uuid, data) -> {
+ switch (data.getState()) {
+ case HOST_DECONSTRUCTED -> deconstructed.appendTag(new NBTTagByteArray(dumpUUID(uuid)));
+ case SELF_DESTRUCTED -> selfDestructed.appendTag(new NBTTagByteArray(dumpUUID(uuid)));
+ }
+ });
+
+ if (deconstructed.tagCount() > 0) {
+ nbtTagCompound.setTag(DECONSTRUCTED_KEY, deconstructed);
+ }
+ if (selfDestructed.tagCount() > 0) {
+ nbtTagCompound.setTag(SELF_DESTRUCTED_KEY, selfDestructed);
+ }
+ }
+
+ /**
+ * Stores the new result and flag the static {@link MapStorage} instance as dirty if the information updated. Will
+ * not flag dirty for any data in the {@link State#OPERATIONAL OPERATIONAL} state since they aren't stored.
+ *
+ * @param frequency Maps to a unique deployed cover.
+ * @param state The new cover state.
+ */
+ private static void store(@NotNull UUID frequency, @NotNull State state) {
+ store(frequency, state, null, null);
+ }
+
+ /**
+ * Stores the new result and flag the static {@link MapStorage} instance as dirty if the information updated. Will
+ * not flag dirty for any data in the {@link State#OPERATIONAL OPERATIONAL} state since they aren't stored.
+ *
+ * @param frequency Maps to a unique deployed cover.
+ * @param state The new cover state.
+ * @param payload A list of strings to display on the information panel, if the card is slotted properly.
+ * @param coordinates Coordinates of the active machine (including dimension.)
+ */
+ private static void store(@NotNull UUID frequency, @NotNull State state, @Nullable List<String> payload,
+ @Nullable Coordinates coordinates) {
+ final Data newData = new Data(state, payload, coordinates);
+ final Data oldData = DATABASE.put(frequency, newData);
+
+ if (state != State.OPERATIONAL && (oldData == null || oldData != newData)) {
+ tryMarkDirty();
+ }
+ }
+
+ private static void tryMarkDirty() {
+ if (INSTANCE != null) {
+ INSTANCE.markDirty();
+ }
+ }
+
+ private static byte[] dumpUUID(UUID uuid) {
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[16]);
+ buffer.putLong(uuid.getMostSignificantBits());
+ buffer.putLong(uuid.getLeastSignificantBits());
+ return buffer.array();
+ }
+
+ @NotNull
+ private static Optional<UUID> reconstituteUUID(byte[] bytes) throws IllegalArgumentException {
+ if (bytes.length != 16) {
+ return Optional.empty();
+ }
+
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ return Optional.of(new UUID(buffer.getLong(), buffer.getLong()));
+ }
+
+ private static void cullReverseLookupEntry(UUID frequency) {
+ getData(frequency).ifPresent(data -> {
+ if (data.state == State.OPERATIONAL && REVERSE_LOOKUP.containsKey(data.coordinates)) {
+ final Set<UUID> set = REVERSE_LOOKUP.get(data.coordinates);
+ set.remove(frequency);
+ if (set.isEmpty()) {
+ REVERSE_LOOKUP.remove(data.coordinates);
+ }
+ }
+ });
+ }
+
+ private static Stream<UUID> getCoverUUIDsFromItemStack(final ItemStack stack) {
+ if (stack.hasTagCompound() && stack.getTagCompound()
+ .hasKey(GT_Values.NBT.COVERS, TAG_COMPOUND)) {
+ final NBTTagList tagList = stack.getTagCompound()
+ .getTagList(GT_Values.NBT.COVERS, TAG_COMPOUND);
+ return IntStream.range(0, tagList.tagCount())
+ .mapToObj(tagList::getCompoundTagAt)
+ .map(nbt -> new CoverInfo(null, nbt).getCoverData())
+ .filter(
+ serializableObject -> serializableObject instanceof GT_Cover_Metrics_Transmitter.MetricsTransmitterData)
+ .map(data -> ((GT_Cover_Metrics_Transmitter.MetricsTransmitterData) data).getFrequency());
+ }
+ return Stream.empty();
+ }
+
+ /**
+ * Data transmitted by a Metrics Transmitter cover.
+ * <p>
+ * Since only negative states ({@link State#HOST_DECONSTRUCTED HOST_DECONSTRUCTED} and
+ * {@link State#SELF_DESTRUCTED SELF DESTRUCTED}) are persisted, additional fields can be added to this data with
+ * little consequence. Ensure that any new fields are nullable, and make any getter for these fields return an
+ * {@link Optional}.
+ */
+ public static class Data {
+
+ @NotNull
+ private final State state;
+ @Nullable
+ private final List<String> payload;
+ @Nullable
+ private final Coordinates coordinates;
+
+ public Data(@NotNull State state) {
+ this.state = state;
+ this.payload = null;
+ this.coordinates = null;
+ }
+
+ public Data(@NotNull State state, @Nullable List<String> payload) {
+ this.state = state;
+ this.payload = payload;
+ this.coordinates = null;
+ }
+
+ public Data(@NotNull State state, @Nullable List<String> payload, @Nullable Coordinates coordinates) {
+ this.state = state;
+ this.payload = payload;
+ this.coordinates = coordinates;
+
+ }
+
+ /**
+ * Retrieves the payload for this data. Only present if the frequency is in an
+ * {@link State#OPERATIONAL operational} state. Will be cleared if the frequency goes into a
+ * {@link State#HOST_DECONSTRUCTED host-deconstructed} or {@link State#SELF_DESTRUCTED self-destructed} state.
+ *
+ * @return The data if present, or an empty Optional otherwise.
+ */
+ @NotNull
+ public Optional<List<String>> getPayload() {
+ return Optional.ofNullable(payload);
+ }
+
+ /**
+ * Gets the state of the frequency.
+ *
+ * @return The state
+ */
+ @NotNull
+ public State getState() {
+ return state;
+ }
+
+ /**
+ * Gets the last known coordinates for the machine broadcasting metrics. Will only be present in an
+ * {@link State#OPERATIONAL operational} state.
+ *
+ * @return The coordinates
+ */
+ @NotNull
+ public Optional<Coordinates> getCoordinates() {
+ return Optional.ofNullable(coordinates);
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ final Data data = (Data) o;
+ return state == data.state && Objects.equals(payload, data.payload)
+ && Objects.equals(coordinates, data.coordinates);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(state, payload, coordinates);
+ }
+ }
+
+ @SuppressWarnings("ClassCanBeRecord")
+ public static class Coordinates {
+
+ private final String dimension;
+ private final int x;
+ private final int y;
+ private final int z;
+
+ public Coordinates(final String dimension, final int x, final int y, final int z) {
+ this.dimension = dimension;
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ public int getX() {
+ return x;
+ }
+
+ public int getY() {
+ return y;
+ }
+
+ public int getZ() {
+ return z;
+ }
+
+ public String getDimension() {
+ return dimension;
+ }
+
+ public String getLocalizedCoordinates() {
+ return StatCollector.translateToLocalFormatted(
+ "gt.db.metrics_cover.coords",
+ GT_Utility.formatNumbers(x),
+ GT_Utility.formatNumbers(y),
+ GT_Utility.formatNumbers(z));
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ final Coordinates that = (Coordinates) o;
+ return x == that.x && y == that.y && z == that.z && Objects.equals(dimension, that.dimension);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(dimension, x, y, z);
+ }
+ }
+
+ public enum State {
+ // NOTE: type cannot be 0, as NuclearControl returns a 0 when querying for an integer from an item stack's NBT
+ // data when it really means null.
+
+ /** The machine is online and broadcasting metrics. */
+ OPERATIONAL(1),
+ /**
+ * The machine was picked up, but the cover is still attached. Will transition to operational state if the
+ * machine is placed back down and started up again.
+ */
+ HOST_DECONSTRUCTED(2),
+ /**
+ * Cover was removed from its host machine, or machine was destroyed (in the limited number of ways we can
+ * detect.) Any frequency in this state will no longer get updates nor leave this state.
+ */
+ SELF_DESTRUCTED(3);
+
+ private static final Map<Integer, State> VALID_TYPE_INTEGERS = Arrays.stream(State.values())
+ .collect(Collectors.toMap(State::getType, Function.identity()));
+ private final int type;
+
+ State(final int type) {
+ if (type <= 0) {
+ throw new IllegalArgumentException("A state must have a positive, nonzero type parameter.");
+ }
+ this.type = type;
+ }
+
+ @NotNull
+ public static Optional<State> find(int candidate) {
+ return Optional.ofNullable(VALID_TYPE_INTEGERS.get(candidate));
+ }
+
+ public int getType() {
+ return type;
+ }
+ }
+}
diff --git a/src/main/java/gregtech/common/misc/GlobalVariableStorage.java b/src/main/java/gregtech/common/misc/GlobalVariableStorage.java
new file mode 100644
index 0000000000..27aad0a11f
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/GlobalVariableStorage.java
@@ -0,0 +1,15 @@
+package gregtech.common.misc;
+
+import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.UUID;
+
+public abstract class GlobalVariableStorage {
+ // --------------------- NEVER access these maps! Use the methods provided! ---------------------
+
+ // Global EU map.
+ public static HashMap<UUID, BigInteger> GlobalEnergy = new HashMap<>(100, 0.9f);
+
+ // ----------------------------------------------------------------------------------------------
+
+}
diff --git a/src/main/java/gregtech/common/misc/WirelessNetworkManager.java b/src/main/java/gregtech/common/misc/WirelessNetworkManager.java
new file mode 100644
index 0000000000..17107b4e50
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/WirelessNetworkManager.java
@@ -0,0 +1,93 @@
+package gregtech.common.misc;
+
+import static gregtech.common.misc.GlobalVariableStorage.GlobalEnergy;
+
+import java.math.BigInteger;
+import java.util.UUID;
+
+import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
+import gregtech.common.misc.spaceprojects.SpaceProjectManager;
+
+public class WirelessNetworkManager {
+
+ private WirelessNetworkManager() {}
+
+ public static void strongCheckOrAddUser(UUID user_uuid) {
+ SpaceProjectManager.checkOrCreateTeam(user_uuid);
+ if (!GlobalEnergy.containsKey(user_uuid)) {
+ GlobalEnergy.put(SpaceProjectManager.getLeader(user_uuid), BigInteger.ZERO);
+ }
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Add EU to the users global energy. You can enter a negative number to subtract it.
+ // If the value goes below 0 it will return false and not perform the operation.
+ // BigIntegers have much slower operations than longs/ints. You should call these methods
+ // as infrequently as possible and bulk store values to add to the global map.
+ public static boolean addEUToGlobalEnergyMap(UUID user_uuid, BigInteger EU) {
+ // Mark the data as dirty and in need of saving.
+ try {
+ GlobalEnergyWorldSavedData.INSTANCE.markDirty();
+ } catch (Exception exception) {
+ System.out.println("COULD NOT MARK GLOBAL ENERGY AS DIRTY IN ADD EU");
+ exception.printStackTrace();
+ }
+
+ // Get the team UUID. Users are by default in a team with a UUID equal to their player UUID.
+ UUID teamUUID = SpaceProjectManager.getLeader(user_uuid);
+
+ // Get the teams total energy stored. If they are not in the map, return 0 EU.
+ BigInteger totalEU = GlobalEnergy.getOrDefault(teamUUID, BigInteger.ZERO);
+ totalEU = totalEU.add(EU);
+
+ // If there is sufficient EU then complete the operation and return true.
+ if (totalEU.signum() >= 0) {
+ GlobalEnergy.put(teamUUID, totalEU);
+ return true;
+ }
+
+ // There is insufficient EU so cancel the operation and return false.
+ return false;
+ }
+
+ public static boolean addEUToGlobalEnergyMap(UUID user_uuid, long EU) {
+ return addEUToGlobalEnergyMap(user_uuid, BigInteger.valueOf(EU));
+ }
+
+ public static boolean addEUToGlobalEnergyMap(UUID user_uuid, int EU) {
+ return addEUToGlobalEnergyMap(user_uuid, BigInteger.valueOf(EU));
+ }
+
+ // ------------------------------------------------------------------------------------
+
+ public static BigInteger getUserEU(UUID user_uuid) {
+ return GlobalEnergy.getOrDefault(SpaceProjectManager.getLeader(user_uuid), BigInteger.ZERO);
+ }
+
+ // This overwrites the EU in the network. Only use this if you are absolutely sure you know what you are doing.
+ public static void setUserEU(UUID user_uuid, BigInteger EU) {
+ // Mark the data as dirty and in need of saving.
+ try {
+ GlobalEnergyWorldSavedData.INSTANCE.markDirty();
+ } catch (Exception exception) {
+ System.out.println("COULD NOT MARK GLOBAL ENERGY AS DIRTY IN SET EU");
+ exception.printStackTrace();
+ }
+
+ GlobalEnergy.put(SpaceProjectManager.getLeader(user_uuid), EU);
+ }
+
+ public static void clearGlobalEnergyInformationMaps() {
+ // Do not use this unless you are 100% certain you know what you are doing.
+ GlobalEnergy.clear();
+ }
+
+ public static UUID processInitialSettings(final IGregTechTileEntity machine) {
+
+ // UUID and username of the owner.
+ final UUID UUID = machine.getOwnerUuid();
+
+ SpaceProjectManager.checkOrCreateTeam(UUID);
+ return UUID;
+ }
+}
diff --git a/src/main/java/gregtech/common/misc/spaceprojects/SpaceProjectManager.java b/src/main/java/gregtech/common/misc/spaceprojects/SpaceProjectManager.java
new file mode 100644
index 0000000000..323b22e20a
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/spaceprojects/SpaceProjectManager.java
@@ -0,0 +1,309 @@
+package gregtech.common.misc.spaceprojects;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.server.MinecraftServer;
+import net.minecraftforge.fluids.FluidStack;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+import gregtech.api.recipe.RecipeMaps;
+import gregtech.api.util.GT_Recipe;
+import gregtech.common.misc.spaceprojects.interfaces.ISpaceBody;
+import gregtech.common.misc.spaceprojects.interfaces.ISpaceProject;
+
+/**
+ * @author BlueWeabo
+ */
+public class SpaceProjectManager {
+
+ /**
+ * Do not use! Only meant to be used in SpaceProjectWorldSavedData.java
+ */
+ public static Map<UUID, Map<Pair<ISpaceBody, String>, ISpaceProject>> spaceTeamProjects = new HashMap<>();
+ /**
+ * Do not use! Only meant to be used in SpaceProjectWorldSavedData.java Stores a Players UUID to the Leader UUID,
+ * players in lone groups give back their own UUID.
+ */
+ public static Map<UUID, UUID> spaceTeams = new HashMap<>();
+
+ /**
+ * Stores all the locations to a hash map to be accessed easier instead of through an enum
+ */
+ private static final HashMap<String, ISpaceBody> spaceLocations = new HashMap<>();
+
+ /**
+ * Stores all projects that have been made. Only adds them to this map if {@link #addProject(ISpaceProject)} has
+ * been used
+ */
+ private static final Map<String, ISpaceProject> spaceProjects = new HashMap<>();
+
+ // #region Space Project Team Helper methods
+
+ /**
+ * Used to get a specific project of the team dependent on the location and the project's name
+ */
+ public static ISpaceProject getTeamProject(UUID member, ISpaceBody location, String projectName) {
+ Map<Pair<ISpaceBody, String>, ISpaceProject> map = spaceTeamProjects.get(getLeader(member));
+ if (map == null) {
+ return null;
+ }
+ return map.get(Pair.of(location, projectName));
+ }
+
+ /**
+ * Makes a new Map for the teams if they don't have one. Adds a project to the team's project map.
+ *
+ * @param member Member of the team.
+ * @param location The location of where the project will belong to.
+ * @param projectName The name of the project being added.
+ * @param project Project which will be added to the team.
+ * @return Returns true when a project was added to the map of the player. Returns false otherwise.
+ */
+ public static boolean addTeamProject(UUID member, ISpaceBody location, String projectName, ISpaceProject project) {
+ if (!spaceTeamProjects.containsKey(getLeader(member)) || spaceTeamProjects.get(getLeader(member)) == null) {
+ spaceTeamProjects.put(getLeader(member), new HashMap<>());
+ }
+
+ Map<Pair<ISpaceBody, String>, ISpaceProject> map = spaceTeamProjects.get(getLeader(member));
+ if (map.containsKey(Pair.of(location, projectName))) {
+ return false;
+ }
+
+ project.setProjectLocation(location);
+ map.put(Pair.of(location, projectName), project);
+ if (SpaceProjectWorldSavedData.INSTANCE != null) {
+ SpaceProjectWorldSavedData.INSTANCE.markDirty();
+ }
+ return true;
+ }
+
+ /**
+ * Check whether a team has a project or not
+ *
+ * @param member Member of the team
+ * @param project The project, which you are checking for. This only compares the internal names of the project.
+ * @return True if the team has said project, false otherwise
+ */
+ public static boolean teamHasProject(UUID member, ISpaceProject project) {
+ Map<Pair<ISpaceBody, String>, ISpaceProject> map = spaceTeamProjects.get(getLeader(member));
+ if (map == null) {
+ return false;
+ }
+
+ return map.containsValue(project);
+ }
+
+ /**
+ * Used to handle when 2 players want to join together in a team. Player A can join player B's team. If player C
+ * gets an invite from A, C will join player B's team.
+ *
+ * @param teamMember Member which is joining the teamLeader
+ * @param teamLeader Leader of the party
+ */
+ public static void putInTeam(UUID teamMember, UUID teamLeader) {
+ if (teamMember.equals(teamLeader)) {
+ spaceTeams.put(teamMember, teamLeader);
+ } else if (!spaceTeams.get(teamLeader)
+ .equals(teamLeader)) {
+ putInTeam(teamMember, spaceTeams.get(teamLeader));
+ } else {
+ spaceTeams.put(teamMember, teamLeader);
+ }
+
+ if (SpaceProjectWorldSavedData.INSTANCE != null) {
+ SpaceProjectWorldSavedData.INSTANCE.markDirty();
+ }
+ }
+
+ /**
+ * Used to give back the UUID of the team leader.
+ *
+ * @return The UUID of the team leader.
+ */
+ public static UUID getLeader(UUID teamMember) {
+ checkOrCreateTeam(teamMember);
+ return spaceTeams.get(teamMember);
+ }
+
+ /**
+ * Used the multiblocks to check whether a given player has a team or not. If they don't have a team create one
+ * where they are their own leader.
+ *
+ * @param teamMember Member to check for.
+ */
+ public static void checkOrCreateTeam(UUID teamMember) {
+ if (spaceTeams.containsKey(teamMember)) {
+ return;
+ }
+
+ spaceTeams.put(teamMember, teamMember);
+ if (SpaceProjectWorldSavedData.INSTANCE != null) {
+ SpaceProjectWorldSavedData.INSTANCE.markDirty();
+ }
+ }
+
+ public static boolean isInTeam(UUID member) {
+ return spaceTeams.containsKey(member);
+ }
+
+ /**
+ * Will give back all the projects a team has made or is making.
+ *
+ * @param member UUID of the team member, used to find the leader of the team.
+ * @return All the projects a team has.
+ */
+ public static Collection<ISpaceProject> getTeamSpaceProjects(UUID member) {
+ Map<Pair<ISpaceBody, String>, ISpaceProject> map = spaceTeamProjects.get(getLeader(member));
+ if (map == null) {
+ return null;
+ }
+
+ return map.values();
+ }
+
+ /**
+ * Getting the project of a Team or a new copy.
+ *
+ * @param member UUID of the team member, which is used to find the team leader.
+ * @param projectName The name of the project, which needs to be found.
+ * @param location The location at which the project is found at.
+ * @return the project that the team has or a copy of a project.
+ */
+ public static ISpaceProject getTeamProjectOrCopy(UUID member, String projectName, ISpaceBody location) {
+ Map<Pair<ISpaceBody, String>, ISpaceProject> map = spaceTeamProjects.get(getLeader(member));
+ if (map == null) {
+ return getProject(projectName);
+ }
+
+ return map.getOrDefault(Pair.of(location, projectName), getProject(projectName));
+ }
+
+ // #endregion
+
+ // #region Project Helper methods
+
+ public static class FakeSpaceProjectRecipe extends GT_Recipe {
+
+ public final String projectName;
+
+ public FakeSpaceProjectRecipe(boolean aOptimize, ItemStack[] aInputs, FluidStack[] aFluidInputs, int aDuration,
+ int aEUt, int aSpecialValue, String projectName) {
+ super(aOptimize, aInputs, null, null, null, aFluidInputs, null, aDuration, aEUt, aSpecialValue);
+ this.projectName = projectName;
+ }
+ }
+
+ /**
+ * Used to add projects to the internal map.
+ *
+ * @param project Newly created project.
+ */
+ public static void addProject(ISpaceProject project) {
+ spaceProjects.put(project.getProjectName(), project);
+ RecipeMaps.spaceProjectFakeRecipes.add(
+ new FakeSpaceProjectRecipe(
+ false,
+ project.getTotalItemsCost(),
+ project.getTotalFluidsCost(),
+ project.getProjectBuildTime(),
+ (int) project.getProjectVoltage(),
+ project.getTotalStages(),
+ project.getProjectName()));
+ }
+
+ /**
+ * @param projectName Internal name of the project.
+ * @return a copy of the stored project.
+ */
+ public static ISpaceProject getProject(String projectName) {
+ ISpaceProject tProject = spaceProjects.get(projectName);
+ return tProject != null ? tProject.copy() : null;
+ }
+
+ /**
+ * Should only be used for GUIs!
+ *
+ * @return The Map that the projects are stored at.
+ */
+ public static Map<String, ISpaceProject> getProjectsMap() {
+ return spaceProjects;
+ }
+
+ /**
+ * Should only be used for GUIs!
+ *
+ * @return A Collection of all the projects contained in the map.
+ */
+ public static Collection<ISpaceProject> getAllProjects() {
+ return spaceProjects.values();
+ }
+
+ // #endregion
+
+ // #region Location Helper methods
+
+ /**
+ * Adds a location to the internal map. For it to be used later
+ *
+ * @param location to add to the internal map
+ */
+ public static void addLocation(ISpaceBody location) {
+ spaceLocations.put(location.getName(), location);
+ }
+
+ /**
+ *
+ * @return a Collection of all locations, which have been registered.
+ */
+ public static Collection<ISpaceBody> getLocations() {
+ return spaceLocations.values();
+ }
+
+ /**
+ *
+ * @return a Collection fo all location names, which have been registered
+ */
+ public static Collection<String> getLocationNames() {
+ return spaceLocations.keySet();
+ }
+
+ /**
+ *
+ * @param locationName Name used to search for the location
+ * @return The location, which has been registered with said name
+ */
+ public static ISpaceBody getLocation(String locationName) {
+ return spaceLocations.get(locationName);
+ }
+
+ // #endregion
+
+ // #region General Helper methods
+
+ /**
+ * Gets the UUID using the player's username
+ */
+ public static UUID getPlayerUUIDFromName(String playerName) {
+ return MinecraftServer.getServer()
+ .func_152358_ax()
+ .func_152655_a(playerName)
+ .getId();
+ }
+
+ /**
+ * Gets the player's name using their UUID
+ */
+ public static String getPlayerNameFromUUID(UUID playerUUID) {
+ return MinecraftServer.getServer()
+ .func_152358_ax()
+ .func_152652_a(playerUUID)
+ .getName();
+ }
+
+ // #endregion
+}
diff --git a/src/main/java/gregtech/common/misc/spaceprojects/SpaceProjectWorldSavedData.java b/src/main/java/gregtech/common/misc/spaceprojects/SpaceProjectWorldSavedData.java
new file mode 100644
index 0000000000..12f0005aea
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/spaceprojects/SpaceProjectWorldSavedData.java
@@ -0,0 +1,343 @@
+package gregtech.common.misc.spaceprojects;
+
+import static gregtech.common.misc.spaceprojects.SpaceProjectManager.spaceTeamProjects;
+import static gregtech.common.misc.spaceprojects.SpaceProjectManager.spaceTeams;
+import static gregtech.common.misc.spaceprojects.enums.JsonVariables.MAP_MAP;
+import static gregtech.common.misc.spaceprojects.enums.JsonVariables.MAP_PAIR;
+import static gregtech.common.misc.spaceprojects.enums.JsonVariables.MAP_PROJECT;
+import static gregtech.common.misc.spaceprojects.enums.JsonVariables.MAP_UUID;
+import static gregtech.common.misc.spaceprojects.enums.JsonVariables.PAIR_LEFT;
+import static gregtech.common.misc.spaceprojects.enums.JsonVariables.PAIR_RIGHT;
+import static gregtech.common.misc.spaceprojects.enums.JsonVariables.PROJECT_CURRENT_STAGE;
+import static gregtech.common.misc.spaceprojects.enums.JsonVariables.PROJECT_CURRENT_UPGRADE;
+import static gregtech.common.misc.spaceprojects.enums.JsonVariables.PROJECT_LOCATION;
+import static gregtech.common.misc.spaceprojects.enums.JsonVariables.PROJECT_NAME;
+import static gregtech.common.misc.spaceprojects.enums.JsonVariables.PROJECT_UPGRADES_BUILT;
+import static gregtech.common.misc.spaceprojects.enums.JsonVariables.UPGRADE_CURRENT_STAGE;
+import static gregtech.common.misc.spaceprojects.enums.JsonVariables.UPGRADE_NAME;
+import static gregtech.common.misc.spaceprojects.enums.JsonVariables.UPGRADE_PROJECT_PARENT;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
+
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.world.World;
+import net.minecraft.world.WorldSavedData;
+import net.minecraft.world.storage.MapStorage;
+import net.minecraftforge.event.world.WorldEvent;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import cpw.mods.fml.common.eventhandler.SubscribeEvent;
+import gregtech.common.misc.spaceprojects.enums.SolarSystem;
+import gregtech.common.misc.spaceprojects.interfaces.ISpaceBody;
+import gregtech.common.misc.spaceprojects.interfaces.ISpaceProject;
+import gregtech.common.misc.spaceprojects.interfaces.ISpaceProject.ISP_Upgrade;
+
+/**
+ * This class is used so that I can write and read to a json file before the world is opened. On server starting is too
+ * late for this as the data stored in the files is needed before entities load their nbt data
+ *
+ * @author BlueWeabo
+ */
+public class SpaceProjectWorldSavedData extends WorldSavedData {
+
+ public static SpaceProjectWorldSavedData INSTANCE;
+
+ private static final Gson GSON_SPACE_PROJECT = new GsonBuilder().serializeNulls()
+ .enableComplexMapKeySerialization()
+ .registerTypeAdapter(spaceTeamProjects.getClass(), new SpaceTeamProjectsMapAdapter())
+ .registerTypeAdapter(Map.class, new SpaceTeamProjectsMapAdapter())
+ .registerTypeAdapter(
+ Pair.of((ISpaceBody) SolarSystem.Ariel, "")
+ .getClass(),
+ new PairAdapter())
+ .registerTypeAdapter(Pair.class, new PairAdapter())
+ .registerTypeAdapter(ISpaceProject.class, new SpaceProjectAdapter())
+ .registerTypeAdapter(ISP_Upgrade.class, new SP_UpgradeAdapter())
+ .registerTypeHierarchyAdapter(ISpaceProject.class, new SpaceProjectAdapter())
+ .registerTypeHierarchyAdapter(ISP_Upgrade.class, new SP_UpgradeAdapter())
+ .create();
+ private static final Gson GSON_TEAMS = new GsonBuilder().serializeNulls()
+ .create();
+
+ private static final String DATA_NAME = "GT_SpaceProjectData";
+
+ private static final String SPACE_TEAM_PROJECTS_JSON = "spaceTeamProject.json";
+
+ private static final String SPACE_TEAMS_JSON = "spaceTeams.json";
+
+ private static File spaceTeamsFile;
+ private static File teamProjectsFile;
+
+ public SpaceProjectWorldSavedData() {
+ super(DATA_NAME);
+ }
+
+ public SpaceProjectWorldSavedData(String aData) {
+ super(aData);
+ }
+
+ @Override
+ public void readFromNBT(NBTTagCompound aNBT) {
+ try (JsonReader reader = new JsonReader(new FileReader(teamProjectsFile))) {
+ spaceTeamProjects = GSON_SPACE_PROJECT.fromJson(reader, spaceTeamProjects.getClass());
+ } catch (Exception e) {
+ System.out.print("FAILED TO LOAD: " + SPACE_TEAM_PROJECTS_JSON);
+ e.printStackTrace();
+ }
+
+ try (JsonReader reader = new JsonReader(new FileReader(spaceTeamsFile))) {
+ HashMap<UUID, UUID> jsonMap = GSON_TEAMS.fromJson(reader, spaceTeams.getClass());
+ for (UUID member : jsonMap.keySet()) {
+ spaceTeams.put(member, jsonMap.get(member));
+ }
+ } catch (Exception e) {
+ System.out.print("FAILED TO LOAD: " + SPACE_TEAMS_JSON);
+ e.printStackTrace();
+ }
+
+ if (spaceTeamProjects == null) {
+ spaceTeamProjects = new HashMap<>();
+ }
+
+ if (spaceTeams == null) {
+ spaceTeams = new HashMap<>();
+ }
+ }
+
+ @Override
+ public void writeToNBT(NBTTagCompound aNBT) {
+ if (spaceTeamProjects != null) {
+ try (JsonWriter writer = new JsonWriter(new FileWriter(teamProjectsFile))) {
+ GSON_SPACE_PROJECT.toJson(spaceTeamProjects, spaceTeamProjects.getClass(), writer);
+ } catch (Exception ex) {
+ System.out.print("FAILED TO SAVE: " + SPACE_TEAM_PROJECTS_JSON);
+ ex.printStackTrace();
+ }
+ }
+
+ if (spaceTeams != null) {
+ try (JsonWriter writer = new JsonWriter(new FileWriter(spaceTeamsFile))) {
+ GSON_TEAMS.toJson(spaceTeams, spaceTeams.getClass(), writer);
+ } catch (Exception ex) {
+ System.out.print("FAILED TO SAVE: " + SPACE_TEAMS_JSON);
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ private static void loadInstance(World aWorld) {
+ if (spaceTeamProjects != null) {
+ spaceTeamProjects.clear();
+ } else {
+ spaceTeamProjects = new HashMap<>();
+ }
+ if (spaceTeams != null) {
+ spaceTeams.clear();
+ } else {
+ spaceTeams = new HashMap<>();
+ }
+ spaceTeamsFile = new File(
+ aWorld.getSaveHandler()
+ .getWorldDirectory(),
+ SPACE_TEAMS_JSON);
+ teamProjectsFile = new File(
+ aWorld.getSaveHandler()
+ .getWorldDirectory(),
+ SPACE_TEAM_PROJECTS_JSON);
+ MapStorage tStorage = aWorld.mapStorage;
+ INSTANCE = (SpaceProjectWorldSavedData) tStorage.loadData(SpaceProjectWorldSavedData.class, DATA_NAME);
+ if (INSTANCE == null) {
+ INSTANCE = new SpaceProjectWorldSavedData();
+ tStorage.setData(DATA_NAME, INSTANCE);
+ }
+ INSTANCE.markDirty();
+ }
+
+ @SubscribeEvent
+ public void onWorldLoad(WorldEvent.Load aEvent) {
+ if (!aEvent.world.isRemote && aEvent.world.provider.dimensionId == 0) {
+ loadInstance(aEvent.world);
+ }
+ }
+
+ private static class PairAdapter
+ implements JsonSerializer<Pair<ISpaceBody, String>>, JsonDeserializer<Pair<ISpaceBody, String>> {
+
+ @Override
+ public JsonElement serialize(Pair<ISpaceBody, String> src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonObject pair = new JsonObject();
+ pair.addProperty(
+ PAIR_LEFT,
+ src.getLeft()
+ .getName());
+ pair.addProperty(PAIR_RIGHT, src.getRight());
+ return pair;
+ }
+
+ @Override
+ public Pair<ISpaceBody, String> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ Pair<ISpaceBody, String> pair = null;
+ if (json.isJsonObject()) {
+ JsonObject obj = json.getAsJsonObject();
+ pair = Pair.of(
+ SpaceProjectManager.getLocation(
+ obj.get(PAIR_LEFT)
+ .getAsString()),
+ obj.get(PAIR_RIGHT)
+ .getAsString());
+ }
+ return pair;
+ }
+ }
+
+ private static class SpaceProjectAdapter implements JsonSerializer<ISpaceProject>, JsonDeserializer<ISpaceProject> {
+
+ @Override
+ public JsonElement serialize(ISpaceProject src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonObject obj = new JsonObject();
+ obj.addProperty(PROJECT_NAME, src.getProjectName());
+ obj.addProperty(PROJECT_CURRENT_STAGE, src.getCurrentStage());
+ obj.addProperty(
+ PROJECT_LOCATION,
+ src.getProjectLocation()
+ .getName());
+ obj.add(PROJECT_CURRENT_UPGRADE, context.serialize(src.getUpgradeBeingBuilt()));
+ obj.add(PROJECT_UPGRADES_BUILT, context.serialize(src.getAllBuiltUpgrades()));
+ return obj;
+ }
+
+ @Override
+ public ISpaceProject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ if (!json.isJsonObject()) {
+ return null;
+ }
+ JsonObject obj = json.getAsJsonObject();
+ String projectName = obj.get(PROJECT_NAME)
+ .getAsString();
+ ISpaceProject project = SpaceProjectManager.getProject(projectName);
+ int projectCurrentStage = obj.get(PROJECT_CURRENT_STAGE)
+ .getAsInt();
+ ISP_Upgrade[] projectUpgradesBuilt = new ISP_Upgrade[0];
+ projectUpgradesBuilt = context
+ .deserialize(obj.get(PROJECT_UPGRADES_BUILT), projectUpgradesBuilt.getClass());
+ ISP_Upgrade projectCurrentUpgrade = context
+ .deserialize(obj.get(PROJECT_CURRENT_UPGRADE), ISP_Upgrade.class);
+ ISpaceBody projectLocation = SpaceProjectManager.getLocation(
+ obj.get(PROJECT_LOCATION)
+ .getAsString());
+ project.setBuiltUpgrade(projectUpgradesBuilt);
+ project.setProjectLocation(projectLocation);
+ project.setProjectCurrentStage(projectCurrentStage);
+ project.setCurrentUpgradeBeingBuilt(projectCurrentUpgrade);
+ return project;
+ }
+ }
+
+ private static class SP_UpgradeAdapter implements JsonSerializer<ISP_Upgrade>, JsonDeserializer<ISP_Upgrade> {
+
+ @Override
+ public JsonElement serialize(ISP_Upgrade src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonObject obj = new JsonObject();
+ obj.addProperty(UPGRADE_NAME, src.getUpgradeName());
+ obj.addProperty(
+ UPGRADE_PROJECT_PARENT,
+ src.getParentProject()
+ .getProjectName());
+ obj.addProperty(UPGRADE_CURRENT_STAGE, src.getCurrentStage());
+ return obj;
+ }
+
+ @Override
+ public ISP_Upgrade deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ if (!json.isJsonObject()) {
+ return null;
+ }
+ JsonObject obj = json.getAsJsonObject();
+ String projectName = obj.get(UPGRADE_PROJECT_PARENT)
+ .getAsString();
+ ISpaceProject project = SpaceProjectManager.getProject(projectName);
+ ISP_Upgrade upgrade = project.getUpgrade(
+ obj.get(UPGRADE_NAME)
+ .getAsString());
+ if (upgrade == null) {
+ return null;
+ }
+ upgrade = upgrade.copy();
+ upgrade.setUpgradeCurrentStage(
+ obj.get(UPGRADE_CURRENT_STAGE)
+ .getAsInt());
+ return upgrade;
+ }
+ }
+
+ private static class SpaceTeamProjectsMapAdapter
+ implements JsonSerializer<Map<UUID, Map<Pair<ISpaceBody, String>, ISpaceProject>>>,
+ JsonDeserializer<Map<UUID, Map<Pair<ISpaceBody, String>, ISpaceProject>>> {
+
+ @Override
+ public JsonElement serialize(Map<UUID, Map<Pair<ISpaceBody, String>, ISpaceProject>> src, Type typeOfSrc,
+ JsonSerializationContext context) {
+ JsonArray map = new JsonArray();
+ for (Entry<UUID, Map<Pair<ISpaceBody, String>, ISpaceProject>> firstEntry : src.entrySet()) {
+ JsonObject teamMap = new JsonObject();
+ teamMap.add(MAP_UUID, context.serialize(firstEntry.getKey()));
+ JsonArray teamProjectMap = new JsonArray();
+ for (Entry<Pair<ISpaceBody, String>, ISpaceProject> secondEntry : firstEntry.getValue()
+ .entrySet()) {
+ JsonObject projectMap = new JsonObject();
+ projectMap.add(MAP_PAIR, context.serialize(secondEntry.getKey()));
+ projectMap.add(MAP_PROJECT, context.serialize(secondEntry.getValue()));
+ teamProjectMap.add(projectMap);
+ }
+ teamMap.add(MAP_MAP, teamProjectMap);
+ map.add(teamMap);
+ }
+ return map;
+ }
+
+ @Override
+ public Map<UUID, Map<Pair<ISpaceBody, String>, ISpaceProject>> deserialize(JsonElement json, Type typeOfT,
+ JsonDeserializationContext context) throws JsonParseException {
+ JsonArray mapArray = json.getAsJsonArray();
+ Map<UUID, Map<Pair<ISpaceBody, String>, ISpaceProject>> map = new HashMap<>();
+ for (JsonElement teamMapElement : mapArray) {
+ JsonObject teamMap = teamMapElement.getAsJsonObject();
+ UUID uuid = context.deserialize(teamMap.get(MAP_UUID), UUID.class);
+ Map<Pair<ISpaceBody, String>, ISpaceProject> projectMap = new HashMap<>();
+ for (JsonElement teamProjectMapElement : teamMap.get(MAP_MAP)
+ .getAsJsonArray()) {
+ JsonObject teamProjectMap = teamProjectMapElement.getAsJsonObject();
+ Pair<ISpaceBody, String> pair = context.deserialize(teamProjectMap.get(MAP_PAIR), Pair.class);
+ ISpaceProject project = context.deserialize(teamProjectMap.get(MAP_PROJECT), ISpaceProject.class);
+ projectMap.put(pair, project);
+ }
+ map.put(uuid, projectMap);
+ }
+ return map;
+ }
+ }
+}
diff --git a/src/main/java/gregtech/common/misc/spaceprojects/base/SP_Requirements.java b/src/main/java/gregtech/common/misc/spaceprojects/base/SP_Requirements.java
new file mode 100644
index 0000000000..b910b5e344
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/spaceprojects/base/SP_Requirements.java
@@ -0,0 +1,74 @@
+package gregtech.common.misc.spaceprojects.base;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import gregtech.common.misc.spaceprojects.enums.SpaceBodyType;
+import gregtech.common.misc.spaceprojects.enums.StarType;
+import gregtech.common.misc.spaceprojects.interfaces.ISpaceProject;
+import gregtech.common.misc.spaceprojects.interfaces.ISpaceProject.ISP_Requirements;
+import gregtech.common.misc.spaceprojects.interfaces.ISpaceProject.ISP_Upgrade;
+
+/**
+ * @author BlueWeabo
+ */
+public class SP_Requirements implements ISP_Requirements {
+
+ // #region Variables
+
+ protected SpaceBodyType spaceBody = SpaceBodyType.NONE;
+ protected StarType star = StarType.NotAStar;
+ protected List<ISpaceProject> spaceProjects = new ArrayList<>();
+ protected List<ISP_Upgrade> upgrades = new ArrayList<>();
+
+ // #endregion
+
+ // #region Getters
+
+ @Override
+ public SpaceBodyType getBodyType() {
+ return spaceBody;
+ }
+
+ @Override
+ public StarType getStarType() {
+ return star;
+ }
+
+ @Override
+ public List<ISpaceProject> getProjects() {
+ return spaceProjects;
+ }
+
+ @Override
+ public List<ISP_Upgrade> getUpgrades() {
+ return upgrades;
+ }
+
+ // #endregion
+
+ // #region Setters/Builder
+
+ public SP_Requirements setSpaceBodyType(SpaceBodyType spaceBodyType) {
+ spaceBody = spaceBodyType;
+ return this;
+ }
+
+ public SP_Requirements setStarType(StarType starType) {
+ star = starType;
+ return this;
+ }
+
+ public SP_Requirements setUpgrades(ISP_Upgrade... requirementUpgrades) {
+ upgrades.addAll(Arrays.asList(requirementUpgrades));
+ return this;
+ }
+
+ public SP_Requirements setSpaceProjects(ISpaceProject... requirementProjects) {
+ spaceProjects.addAll(Arrays.asList(requirementProjects));
+ return this;
+ }
+
+ // #endregion
+}
diff --git a/src/main/java/gregtech/common/misc/spaceprojects/base/SP_Upgrade.java b/src/main/java/gregtech/common/misc/spaceprojects/base/SP_Upgrade.java
new file mode 100644
index 0000000000..835a57f277
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/spaceprojects/base/SP_Upgrade.java
@@ -0,0 +1,362 @@
+package gregtech.common.misc.spaceprojects.base;
+
+import java.util.UUID;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.StatCollector;
+import net.minecraftforge.fluids.FluidStack;
+
+import gregtech.common.misc.spaceprojects.SpaceProjectManager;
+import gregtech.common.misc.spaceprojects.enums.SpaceBodyType;
+import gregtech.common.misc.spaceprojects.enums.UpgradeStatus;
+import gregtech.common.misc.spaceprojects.interfaces.ISpaceProject;
+import gregtech.common.misc.spaceprojects.interfaces.ISpaceProject.ISP_Upgrade;
+
+/**
+ * @author BlueWeabo
+ */
+public class SP_Upgrade implements ISP_Upgrade {
+
+ // #region Variables
+
+ protected String name;
+ protected String unlocalizedName;
+ protected ItemStack[] itemsCost;
+ protected FluidStack[] fluidsCost;
+ protected int totalStages;
+ protected int currentStage;
+ protected int buildTime;
+ protected long voltage;
+ protected SP_Requirements requirements;
+ protected ISpaceProject projectBelongingTo;
+
+ // #endregion
+
+ // #region Getters
+
+ @Override
+ public String getUpgradeName() {
+ return name;
+ }
+
+ @Override
+ public String getUnlocalizedName() {
+ return unlocalizedName;
+ }
+
+ @Override
+ public String getLocalizedName() {
+ return StatCollector.translateToLocal(unlocalizedName);
+ }
+
+ @Override
+ public ItemStack[] getItemsCostPerStage() {
+ return itemsCost;
+ }
+
+ @Override
+ public ItemStack getItemCostPerStage(int index) {
+ if (itemsCost == null || index < 0 || index >= itemsCost.length) {
+ return null;
+ }
+
+ return itemsCost[index];
+ }
+
+ @Override
+ public ItemStack[] getCurrentItemsProgress() {
+ ItemStack[] currentItemsProgress = new ItemStack[itemsCost.length];
+ int index = 0;
+ for (ItemStack item : itemsCost) {
+ ItemStack copy = item.copy();
+ copy.stackSize *= getCurrentStage();
+ currentItemsProgress[index++] = copy;
+ }
+
+ return currentItemsProgress;
+ }
+
+ @Override
+ public ItemStack getCurrentItemProgress(int index) {
+ if (itemsCost == null || index < 0 || index >= itemsCost.length || itemsCost[index] == null) {
+ return null;
+ }
+
+ ItemStack item = itemsCost[index].copy();
+ item.stackSize *= getCurrentStage();
+ return item;
+ }
+
+ @Override
+ public ItemStack[] getTotalItemsCost() {
+ ItemStack[] totalItemsCost = new ItemStack[itemsCost.length];
+ int index = 0;
+ for (ItemStack item : itemsCost) {
+ ItemStack copy = item.copy();
+ copy.stackSize *= getTotalStages();
+ totalItemsCost[index++] = copy;
+ }
+
+ return totalItemsCost;
+ }
+
+ @Override
+ public ItemStack getTotalItemCost(int index) {
+ if (itemsCost == null || index < 0 || index >= itemsCost.length || itemsCost[index] == null) {
+ return null;
+ }
+
+ ItemStack item = itemsCost[index].copy();
+ item.stackSize *= getTotalStages();
+ return item;
+ }
+
+ @Override
+ public FluidStack[] getFluidsCostPerStage() {
+ return fluidsCost;
+ }
+
+ @Override
+ public FluidStack getFluidCostPerStage(int index) {
+ if (fluidsCost == null || index < 0 || index >= fluidsCost.length) {
+ return null;
+ }
+
+ return fluidsCost[index];
+ }
+
+ @Override
+ public FluidStack[] getCurrentFluidsProgress() {
+ if (fluidsCost == null) {
+ return null;
+ }
+
+ FluidStack[] currentFluidsProgress = new FluidStack[fluidsCost.length];
+ int index = 0;
+ for (FluidStack fluid : fluidsCost) {
+ FluidStack copy = fluid.copy();
+ copy.amount *= getCurrentStage();
+ currentFluidsProgress[index++] = copy;
+ }
+
+ return currentFluidsProgress;
+ }
+
+ @Override
+ public FluidStack getCurrentFluidProgress(int index) {
+ if (fluidsCost == null || index < 0 || index >= fluidsCost.length || fluidsCost[index] == null) {
+ return null;
+ }
+
+ FluidStack fluid = fluidsCost[index].copy();
+ fluid.amount *= getCurrentStage();
+ return fluid;
+ }
+
+ @Override
+ public FluidStack[] getTotalFluidsCost() {
+ if (fluidsCost == null) {
+ return null;
+ }
+
+ FluidStack[] totalFluidsCost = new FluidStack[fluidsCost.length];
+ int index = 0;
+ for (FluidStack fluid : fluidsCost) {
+ FluidStack copy = fluid.copy();
+ copy.amount *= getTotalStages();
+ totalFluidsCost[index++] = copy;
+ }
+
+ return totalFluidsCost;
+ }
+
+ @Override
+ public FluidStack getTotalFluidCost(int index) {
+ if (fluidsCost == null || index < 0 || index >= fluidsCost.length || fluidsCost[index] == null) {
+ return null;
+ }
+
+ FluidStack fluid = fluidsCost[index].copy();
+ fluid.amount *= getTotalStages();
+ return fluid;
+ }
+
+ @Override
+ public int getTotalStages() {
+ return totalStages;
+ }
+
+ @Override
+ public int getUpgradeBuildTime() {
+ return buildTime;
+ }
+
+ @Override
+ public int getCurrentStage() {
+ return currentStage;
+ }
+
+ @Override
+ public float getCurrentProgress() {
+ return currentStage / totalStages * 100.0f;
+ }
+
+ @Override
+ public long getVoltage() {
+ return voltage;
+ }
+
+ @Override
+ public UpgradeStatus getStatus() {
+ if (requirements == null) {
+ return UpgradeStatus.Unlocked;
+ }
+
+ if (isFinished()) {
+ return UpgradeStatus.Finished;
+ }
+ return UpgradeStatus.Locked;
+ }
+
+ @Override
+ public SP_Requirements getUpgradeRequirements() {
+ return requirements;
+ }
+
+ @Override
+ public ISpaceProject getParentProject() {
+ return projectBelongingTo;
+ }
+
+ // #endregion
+
+ // #region Setter/Builder
+
+ public SP_Upgrade() {}
+
+ public SP_Upgrade setUpgradeName(String upgradeName) {
+ name = upgradeName;
+ return this;
+ }
+
+ public SP_Upgrade setUpgradeUnlocalizedName(String upgradeUnlocalizedName) {
+ unlocalizedName = upgradeUnlocalizedName;
+ return this;
+ }
+
+ public SP_Upgrade setUpgradeItemsCost(ItemStack... upgradeItemsCost) {
+ itemsCost = upgradeItemsCost;
+ return this;
+ }
+
+ public SP_Upgrade setUpgradeFluidsCost(FluidStack... upgradeFluidsCost) {
+ fluidsCost = upgradeFluidsCost;
+ return this;
+ }
+
+ public SP_Upgrade setUpgradeRequirements(SP_Requirements upgradeRequirements) {
+ requirements = upgradeRequirements;
+ return this;
+ }
+
+ public SP_Upgrade setUpgradeTotalStages(int upgradeTotalStages) {
+ totalStages = upgradeTotalStages;
+ return this;
+ }
+
+ public SP_Upgrade setUpgradeBuildTime(int upgradeBuildTime) {
+ buildTime = upgradeBuildTime;
+ return this;
+ }
+
+ public SP_Upgrade setUpgradeVoltage(long upgradeVoltage) {
+ voltage = upgradeVoltage;
+ return this;
+ }
+
+ @Override
+ public void setUpgradeProject(ISpaceProject project) {
+ projectBelongingTo = project;
+ }
+
+ @Override
+ public void setUpgradeCurrentStage(int stage) {
+ currentStage = stage;
+ }
+
+ // #endregion
+
+ // #region Other
+
+ @Override
+ public boolean meetsRequirements(UUID aTeam) {
+ if (requirements == null) {
+ return true;
+ }
+
+ if (requirements.getBodyType() != null && !requirements.getBodyType()
+ .equals(SpaceBodyType.NONE)) {
+ if (!requirements.getBodyType()
+ .equals(
+ projectBelongingTo.getProjectLocation()
+ .getType())) {
+ return false;
+ }
+ }
+
+ if (requirements.getStarType() != null) {
+ if (!requirements.getStarType()
+ .equals(
+ projectBelongingTo.getProjectLocation()
+ .getStarType())) {
+ return false;
+ }
+ }
+
+ if (requirements.getProjects() != null) {
+ for (ISpaceProject tProject : requirements.getProjects()) {
+ if (!SpaceProjectManager.teamHasProject(aTeam, tProject)) {
+ return false;
+ }
+ }
+ }
+
+ if (requirements.getUpgrades() != null) {
+ for (ISP_Upgrade upgrade : requirements.getUpgrades()) {
+ if (!projectBelongingTo.hasUpgrade(upgrade.getUpgradeName())) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public SP_Upgrade copy() {
+ return new SP_Upgrade().setUpgradeName(name)
+ .setUpgradeUnlocalizedName(unlocalizedName)
+ .setUpgradeBuildTime(buildTime)
+ .setUpgradeFluidsCost(fluidsCost)
+ .setUpgradeItemsCost(itemsCost)
+ .setUpgradeRequirements(requirements)
+ .setUpgradeTotalStages(totalStages)
+ .setUpgradeVoltage(voltage);
+ }
+
+ @Override
+ public void goToNextStage() {
+ currentStage++;
+ if (isFinished()) {
+ projectBelongingTo.setBuiltUpgrade(this);
+ }
+ }
+
+ @Override
+ public boolean isFinished() {
+ return currentStage == totalStages;
+ }
+
+ // #endregion
+
+}
diff --git a/src/main/java/gregtech/common/misc/spaceprojects/base/SpaceProject.java b/src/main/java/gregtech/common/misc/spaceprojects/base/SpaceProject.java
new file mode 100644
index 0000000000..201b7c27a9
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/spaceprojects/base/SpaceProject.java
@@ -0,0 +1,451 @@
+package gregtech.common.misc.spaceprojects.base;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.StatCollector;
+import net.minecraftforge.fluids.FluidStack;
+
+import com.gtnewhorizons.modularui.api.drawable.UITexture;
+
+import gregtech.common.misc.spaceprojects.SpaceProjectManager;
+import gregtech.common.misc.spaceprojects.interfaces.ISpaceBody;
+import gregtech.common.misc.spaceprojects.interfaces.ISpaceProject;
+
+/**
+ * @author BlueWeabo
+ */
+public class SpaceProject implements ISpaceProject {
+
+ // #region Variables
+
+ protected String name;
+ protected String unlocalizedName;
+ protected long voltage;
+ protected int buildTime;
+ protected int projectTier;
+ protected int currentStage;
+ protected int totalStage;
+ protected Map<String, ISP_Upgrade> upgradesAvailable = new HashMap<>();
+ protected Map<String, ISP_Upgrade> upgradesInstalled = new HashMap<>();
+ protected ISP_Requirements requirements;
+ protected ISP_Upgrade currentUpgrade;
+ protected ItemStack[] itemsCost;
+ protected FluidStack[] fluidsCost;
+ protected ISpaceBody location;
+ protected UITexture texture;
+
+ // #endregion
+
+ // #region Getters
+
+ @Override
+ public String getProjectName() {
+ return name;
+ }
+
+ @Override
+ public String getUnlocalizedName() {
+ return unlocalizedName;
+ }
+
+ public String getLocalizedName() {
+ return StatCollector.translateToLocal(unlocalizedName);
+ }
+
+ @Override
+ public long getProjectVoltage() {
+ return voltage;
+ }
+
+ @Override
+ public int getProjectBuildTime() {
+ return buildTime;
+ }
+
+ @Override
+ public float getProjectCurrentProgress() {
+ return currentStage / totalStage * 100.0f;
+ }
+
+ @Override
+ public int getProjectTier() {
+ return projectTier;
+ }
+
+ @Override
+ public int getCurrentStage() {
+ return currentStage;
+ }
+
+ @Override
+ public int getTotalStages() {
+ return totalStage;
+ }
+
+ @Override
+ public Collection<ISP_Upgrade> getAllUpgrades() {
+ return upgradesAvailable.values();
+ }
+
+ @Override
+ public Map<String, ISP_Upgrade> getUpgradesBuiltMap() {
+ return upgradesInstalled;
+ }
+
+ @Override
+ public Collection<ISP_Upgrade> getAllBuiltUpgrades() {
+ return upgradesInstalled.values();
+ }
+
+ @Override
+ public ISP_Upgrade getUpgrade(String upgradeName) {
+ return upgradesAvailable.get(upgradeName);
+ }
+
+ @Override
+ public ItemStack[] getItemsCostPerStage() {
+ return itemsCost;
+ }
+
+ @Override
+ public ItemStack getItemCostPerStage(int index) {
+ if (itemsCost == null || index < 0 || index >= itemsCost.length) {
+ return null;
+ }
+
+ return itemsCost[index];
+ }
+
+ @Override
+ public ItemStack[] getCurrentItemsProgress() {
+ ItemStack[] currentItemsProgress = new ItemStack[itemsCost.length];
+ int index = 0;
+ for (ItemStack item : itemsCost) {
+ if (item == null) {
+ currentItemsProgress[index++] = null;
+ continue;
+ }
+ ItemStack copy = item.copy();
+ copy.stackSize *= getCurrentStage();
+ currentItemsProgress[index++] = copy;
+ }
+
+ return currentItemsProgress;
+ }
+
+ @Override
+ public ItemStack getCurrentItemProgress(int index) {
+ if (itemsCost == null || index < 0 || index >= itemsCost.length || itemsCost[index] == null) {
+ return null;
+ }
+
+ ItemStack item = itemsCost[index].copy();
+ item.stackSize *= getCurrentStage();
+ return item;
+ }
+
+ @Override
+ public ItemStack[] getTotalItemsCost() {
+ ItemStack[] totalItemsCost = new ItemStack[itemsCost.length];
+ int index = 0;
+ for (ItemStack item : itemsCost) {
+ if (item == null) {
+ totalItemsCost[index++] = null;
+ continue;
+ }
+ ItemStack copy = item.copy();
+ copy.stackSize *= getTotalStages();
+ totalItemsCost[index++] = copy;
+ }
+
+ return totalItemsCost;
+ }
+
+ @Override
+ public ItemStack getTotalItemCost(int index) {
+ if (itemsCost == null || index < 0 || index >= itemsCost.length || itemsCost[index] == null) {
+ return null;
+ }
+
+ ItemStack item = itemsCost[index].copy();
+ item.stackSize *= getTotalStages();
+ return item;
+ }
+
+ @Override
+ public FluidStack[] getFluidsCostPerStage() {
+ return fluidsCost;
+ }
+
+ @Override
+ public FluidStack getFluidCostPerStage(int index) {
+ if (fluidsCost == null || index < 0 || index >= fluidsCost.length || fluidsCost[index] == null) {
+ return null;
+ }
+
+ return fluidsCost[index];
+ }
+
+ @Override
+ public FluidStack[] getCurrentFluidsProgress() {
+ if (fluidsCost == null) {
+ return null;
+ }
+
+ FluidStack[] currentFluidsProgress = new FluidStack[fluidsCost.length];
+ int index = 0;
+ for (FluidStack fluid : fluidsCost) {
+ if (fluid == null) {
+ currentFluidsProgress[index++] = null;
+ continue;
+ }
+ FluidStack copy = fluid.copy();
+ copy.amount *= getCurrentStage();
+ currentFluidsProgress[index++] = copy;
+ }
+
+ return currentFluidsProgress;
+ }
+
+ @Override
+ public FluidStack getCurrentFluidProgress(int index) {
+ if (fluidsCost == null || index < 0 || index >= fluidsCost.length || fluidsCost[index] == null) {
+ return null;
+ }
+
+ FluidStack fluid = fluidsCost[index].copy();
+ fluid.amount *= getCurrentStage();
+ return fluid;
+ }
+
+ @Override
+ public FluidStack[] getTotalFluidsCost() {
+ if (fluidsCost == null) {
+ return null;
+ }
+
+ FluidStack[] totalFluidsCost = new FluidStack[fluidsCost.length];
+ int index = 0;
+ for (FluidStack fluid : fluidsCost) {
+ if (fluid == null) {
+ totalFluidsCost[index++] = null;
+ continue;
+ }
+ FluidStack copy = fluid.copy();
+ copy.amount *= getTotalStages();
+ totalFluidsCost[index++] = copy;
+ }
+
+ return totalFluidsCost;
+ }
+
+ @Override
+ public FluidStack getTotalFluidCost(int index) {
+ if (fluidsCost == null || index < 0 || index >= fluidsCost.length) {
+ return null;
+ }
+
+ FluidStack fluid = fluidsCost[index].copy();
+ fluid.amount *= getTotalStages();
+ return fluid;
+ }
+
+ @Override
+ public ISP_Upgrade getUpgradeBeingBuilt() {
+ return currentUpgrade;
+ }
+
+ @Override
+ public ISpaceBody getProjectLocation() {
+ return location;
+ }
+
+ @Override
+ public UITexture getTexture() {
+ return texture;
+ }
+
+ // #endregion
+
+ // #region Setter/Builder
+
+ public SpaceProject setProjectName(String spaceProjectName) {
+ name = spaceProjectName;
+ return this;
+ }
+
+ public SpaceProject setProjectUnlocalizedName(String spaceProjectUnlocalizedName) {
+ unlocalizedName = spaceProjectUnlocalizedName;
+ return this;
+ }
+
+ public SpaceProject setProjectVoltage(long spaceProjectVoltage) {
+ voltage = spaceProjectVoltage;
+ return this;
+ }
+
+ public SpaceProject setProjectBuildTime(int spaceProjectBuildTime) {
+ buildTime = spaceProjectBuildTime;
+ return this;
+ }
+
+ public SpaceProject setProjectStages(int spaceProjectTotalStages) {
+ totalStage = spaceProjectTotalStages;
+ return this;
+ }
+
+ public SpaceProject setProjectItemsCost(ItemStack... spaceProjectItemsCost) {
+ itemsCost = spaceProjectItemsCost;
+ return this;
+ }
+
+ public SpaceProject setProjectFluidsCost(FluidStack... spaceProjectFluidsCost) {
+ fluidsCost = spaceProjectFluidsCost;
+ return this;
+ }
+
+ public SpaceProject setProjectUpgrades(ISP_Upgrade... spaceProjectUpgrades) {
+ for (ISP_Upgrade upgrade : spaceProjectUpgrades) {
+ upgrade.setUpgradeProject(this);
+ upgradesAvailable.put(upgrade.getUpgradeName(), upgrade);
+ }
+ return this;
+ }
+
+ public SpaceProject setProjectTexture(UITexture projectTexture) {
+ texture = projectTexture;
+ return this;
+ }
+
+ public SpaceProject setProjectRequirements(ISP_Requirements projectRequirements) {
+ requirements = projectRequirements;
+ return this;
+ }
+
+ @Override
+ public void setCurrentUpgradeBeingBuilt(ISP_Upgrade newCurrentUpgrade) {
+ if (newCurrentUpgrade == null) {
+ return;
+ }
+
+ if (totalStage == currentStage) {
+ currentUpgrade = newCurrentUpgrade.copy();
+ currentUpgrade.setUpgradeProject(this);
+ }
+ }
+
+ @Override
+ public void setProjectCurrentStage(int newCurrentStage) {
+ currentStage = newCurrentStage;
+ }
+
+ @Override
+ public void setProjectLocation(ISpaceBody newLocation) {
+ location = newLocation;
+ }
+
+ @Override
+ public void setBuiltUpgrade(ISP_Upgrade... upgrades) {
+ if (upgrades == null) {
+ return;
+ }
+
+ for (ISP_Upgrade upgrade : upgrades) {
+ if (upgrade.equals(currentUpgrade)) {
+ currentUpgrade = null;
+ }
+ upgradesInstalled.put(upgrade.getUpgradeName(), upgrade);
+ }
+ }
+
+ // #endregion
+
+ // #region Other
+
+ @Override
+ public ISpaceProject copy() {
+ SpaceProject copy = new SpaceProject().setProjectName(name)
+ .setProjectUnlocalizedName(unlocalizedName)
+ .setProjectVoltage(voltage)
+ .setProjectBuildTime(buildTime)
+ .setProjectItemsCost(itemsCost)
+ .setProjectFluidsCost(fluidsCost)
+ .setProjectStages(totalStage)
+ .setProjectTexture(texture)
+ .setProjectRequirements(requirements);
+ if (upgradesAvailable != null) {
+ ISP_Upgrade[] upgrades = new SP_Upgrade[upgradesAvailable.size()];
+ int index = 0;
+ for (ISP_Upgrade upgrade : upgradesAvailable.values()) {
+ upgrades[index++] = upgrade.copy();
+ }
+ copy.setProjectUpgrades(upgrades);
+ }
+ return copy;
+ }
+
+ @Override
+ public void goToNextStage() {
+ currentStage++;
+ }
+
+ @Override
+ public boolean meetsRequirements(UUID team) {
+ return meetsRequirements(team, true);
+ }
+
+ @Override
+ public boolean meetsRequirements(UUID team, boolean checkLocation) {
+ if (requirements == null) {
+ return true;
+ }
+
+ if (requirements.getBodyType() != null && checkLocation) {
+ if (!requirements.getBodyType()
+ .equals(location.getType())) {
+ return false;
+ }
+ }
+
+ if (requirements.getStarType() != null && checkLocation) {
+ if (!requirements.getStarType()
+ .equals(location.getStarType())) {
+ return false;
+ }
+ }
+
+ if (requirements.getProjects() != null) {
+ for (ISpaceProject project : requirements.getProjects()) {
+ if (!SpaceProjectManager.teamHasProject(team, project)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof SpaceProject)) {
+ return false;
+ }
+ return getProjectName().equals(((SpaceProject) obj).getProjectName());
+ }
+
+ @Override
+ public boolean isFinished() {
+ return currentStage == totalStage;
+ }
+
+ @Override
+ public boolean hasUpgrade(String upgradeName) {
+ return upgradesInstalled.containsKey(upgradeName);
+ }
+
+ // #endregion
+}
diff --git a/src/main/java/gregtech/common/misc/spaceprojects/commands/SPM_Command.java b/src/main/java/gregtech/common/misc/spaceprojects/commands/SPM_Command.java
new file mode 100644
index 0000000000..896c7e1052
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/spaceprojects/commands/SPM_Command.java
@@ -0,0 +1,288 @@
+package gregtech.common.misc.spaceprojects.commands;
+
+import static gregtech.common.misc.spaceprojects.SpaceProjectManager.getLocation;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import net.minecraft.command.CommandBase;
+import net.minecraft.command.ICommandSender;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.util.ChatComponentText;
+
+import gregtech.common.misc.spaceprojects.SpaceProjectManager;
+import gregtech.common.misc.spaceprojects.SpaceProjectWorldSavedData;
+import gregtech.common.misc.spaceprojects.interfaces.ISpaceProject;
+
+/**
+ * @author BlueWeabo
+ */
+public class SPM_Command extends CommandBase {
+
+ private static final String RESET = "reset";
+ private static final String UNLOCK = "unlock";
+ private static final String UNLOCK_UPGRADE = "unlock_upgrade";
+ private static final String LOCK = "lock";
+ private static final String LIST = "list";
+ private static final String ALL = "-all";
+ private static final String AVAILABLE = "-available";
+ private static final String UNLOCKED = "-unlocked";
+ private static final String COPY = "copy";
+
+ @Override
+ public String getCommandName() {
+ return "spm";
+ }
+
+ @Override
+ public int getRequiredPermissionLevel() {
+ return 0;
+ }
+
+ @Override
+ public String getCommandUsage(ICommandSender sender) {
+ return "/" + this.getCommandName() + " <subCommand>. Available subCommands: reset, unlock, lock, list, copy";
+ }
+
+ @Override
+ public void processCommand(ICommandSender sender, String[] arguments) {
+ if (arguments.length < 1) {
+ printHelp(sender);
+ return;
+ }
+ switch (arguments[0]) {
+ case RESET:
+ if (!sender.canCommandSenderUseCommand(4, getCommandName())) {
+ sender.addChatMessage(
+ new ChatComponentText("You don't have the permissions to execute this command"));
+ return;
+ }
+ processReset(sender, arguments.length >= 2 ? arguments[1] : sender.getCommandSenderName());
+ break;
+ case UNLOCK:
+ if (!sender.canCommandSenderUseCommand(4, getCommandName())) {
+ sender.addChatMessage(
+ new ChatComponentText("You don't have the permissions to execute this command"));
+ return;
+ }
+ if (arguments.length < 3) {
+ sender.addChatMessage(
+ new ChatComponentText("Not enough arguments. Needs to mention a project and a location"));
+ return;
+ }
+ processUnlock(
+ sender,
+ arguments[1],
+ arguments[2],
+ arguments.length >= 4 ? arguments[3] : sender.getCommandSenderName());
+ break;
+ case UNLOCK_UPGRADE:
+ if (!sender.canCommandSenderUseCommand(4, getCommandName())) {
+ sender.addChatMessage(
+ new ChatComponentText("You don't have the permissions to execute this command"));
+ return;
+ }
+ if (arguments.length < 4) {
+ sender.addChatMessage(
+ new ChatComponentText(
+ "Not enough arguments. Needs to mention a project a location and an upgrade name"));
+ return;
+ }
+ processUnlock(
+ sender,
+ arguments[1],
+ arguments[2],
+ arguments[3],
+ arguments.length >= 5 ? arguments[4] : sender.getCommandSenderName());
+ break;
+ case LOCK:
+ if (!sender.canCommandSenderUseCommand(4, getCommandName())) {
+ sender.addChatMessage(
+ new ChatComponentText("You don't have the permissions to execute this command"));
+ return;
+ }
+ if (arguments.length < 3) {
+ sender.addChatMessage(
+ new ChatComponentText("Not enough arguments. Needs to mention a project and a location"));
+ return;
+ }
+ processLock(
+ sender,
+ arguments[1],
+ arguments[2],
+ arguments.length >= 4 ? arguments[3] : sender.getCommandSenderName());
+ case LIST:
+ if (arguments.length < 2) {
+ sender.addChatMessage(
+ new ChatComponentText(
+ "No Argument for list subCommand. Usage /spm list -all, -available or -unlocked"));
+ return;
+ }
+ processList(sender, arguments[1], arguments.length >= 3 ? arguments[2] : sender.getCommandSenderName());
+ break;
+ case COPY:
+ if (!sender.canCommandSenderUseCommand(4, getCommandName())) {
+ sender.addChatMessage(
+ new ChatComponentText("You don't have the permissions to execute this command"));
+ return;
+ }
+ if (arguments.length < 3) {
+ sender.addChatMessage(new ChatComponentText("Not enough arguments. Needs to mention 2 players"));
+ return;
+ }
+ processCopy(sender, arguments[1], arguments[2]);
+ break;
+ }
+ }
+
+ @Override
+ public List<String> addTabCompletionOptions(ICommandSender sender, String[] arguments) {
+ List<String> autoComplete = new ArrayList<>();
+ String filter = arguments.length == 0 ? "" : arguments[0].trim();
+ switch (arguments.length) {
+ case 1 -> autoComplete.addAll(Arrays.asList(getSubCommands()));
+ case 2 -> {
+ filter = arguments[1].trim();
+ if (arguments[0].equals(LIST)) {
+ autoComplete.addAll(Arrays.asList(getListArguments()));
+ } else if (arguments[0].equals(COPY) || arguments[0].equals(RESET)) {
+ autoComplete.addAll(Arrays.asList(getPlayers()));
+ } else {
+ autoComplete.addAll(Arrays.asList(getProjects()));
+ }
+ }
+ case 3 -> {
+ filter = arguments[2].trim();
+ if (arguments[1].equals(ALL)) {} else if (arguments[0].equals(LIST)) {
+ autoComplete.addAll(Arrays.asList(getPlayers()));
+ } else {
+ autoComplete.addAll(Arrays.asList(getLocations()));
+ }
+ }
+ case 4 -> {
+ filter = arguments[3].trim();
+ if (arguments[0].equals(UNLOCK_UPGRADE)) {
+ ISpaceProject project = SpaceProjectManager.getProject(arguments[2]);
+ if (project != null) {
+ autoComplete.addAll(
+ project.getAllUpgrades()
+ .stream()
+ .map(ISpaceProject.ISP_Upgrade::getUnlocalizedName)
+ .collect(Collectors.toList()));
+ }
+ } else {
+ autoComplete.addAll(Arrays.asList(getPlayers()));
+ }
+ }
+ }
+ String finalFilter = filter;
+ return autoComplete.stream()
+ .filter(s -> finalFilter.isEmpty() || s.startsWith(finalFilter))
+ .collect(Collectors.toList());
+ }
+
+ private String[] getPlayers() {
+ return MinecraftServer.getServer()
+ .getAllUsernames();
+ }
+
+ private String[] getLocations() {
+ return SpaceProjectManager.getLocationNames()
+ .toArray(new String[0]);
+ }
+
+ private String[] getProjects() {
+ return SpaceProjectManager.getProjectsMap()
+ .keySet()
+ .toArray(new String[0]);
+ }
+
+ private String[] getSubCommands() {
+ return new String[] { RESET, COPY, UNLOCK, UNLOCK_UPGRADE, LOCK, LIST };
+ }
+
+ private String[] getListArguments() {
+ return new String[] { ALL, AVAILABLE, UNLOCKED };
+ }
+
+ private void processReset(ICommandSender sender, String playerName) {
+ UUID tID = SpaceProjectManager.getPlayerUUIDFromName(playerName);
+ SpaceProjectManager.spaceTeamProjects.put(tID, null);
+ SpaceProjectWorldSavedData.INSTANCE.markDirty();
+ sender.addChatMessage(new ChatComponentText("Cleared away map"));
+ }
+
+ private void processLock(ICommandSender sender, String projectName, String location, String playerName) {
+ UUID tID = SpaceProjectManager.getPlayerUUIDFromName(playerName);
+ SpaceProjectManager.addTeamProject(tID, getLocation(location), projectName, null);
+ sender.addChatMessage(new ChatComponentText("Project locked"));
+ }
+
+ private void processUnlock(ICommandSender sender, String projectName, String location, String playerName) {
+ UUID tID = SpaceProjectManager.getPlayerUUIDFromName(playerName);
+ ISpaceProject tProject = SpaceProjectManager.getTeamProjectOrCopy(tID, projectName, getLocation(location));
+ if (tProject != null) {
+ tProject.setProjectCurrentStage(tProject.getTotalStages());
+ SpaceProjectManager.addTeamProject(tID, getLocation(location), projectName, tProject);
+ sender.addChatMessage(new ChatComponentText("Project unlocked"));
+ } else {
+ sender.addChatMessage(new ChatComponentText("Incorrect internal project name. Try again"));
+ }
+ }
+
+ private void processUnlock(ICommandSender sender, String projectName, String location, String upgradeName,
+ String playerName) {
+ UUID tID = SpaceProjectManager.getPlayerUUIDFromName(playerName);
+ ISpaceProject tProject = SpaceProjectManager.getTeamProjectOrCopy(tID, projectName, getLocation(location));
+ if (tProject != null) {
+ ISpaceProject.ISP_Upgrade upgrade = tProject.getUpgrade(upgradeName);
+ if (upgrade == null) {
+ sender.addChatMessage(new ChatComponentText("Incorrect internal project upgrade name. Try again"));
+ return;
+ }
+ if (!tProject.isFinished()) {
+ tProject.setProjectCurrentStage(tProject.getTotalStages());
+ SpaceProjectManager.addTeamProject(tID, getLocation(location), projectName, tProject);
+ }
+ tProject.setBuiltUpgrade(upgrade);
+ sender.addChatMessage(new ChatComponentText("Project Upgrade unlocked"));
+ } else {
+ sender.addChatMessage(new ChatComponentText("Incorrect internal project name. Try again"));
+ }
+ }
+
+ private void processList(ICommandSender sender, String argument, String playerName) {
+ UUID tID = SpaceProjectManager.getPlayerUUIDFromName(playerName);
+ switch (argument) {
+ case ALL -> {
+ for (String project : SpaceProjectManager.getProjectsMap()
+ .keySet()) {
+ sender.addChatMessage(new ChatComponentText(project));
+ }
+ }
+ case AVAILABLE -> {
+ for (ISpaceProject project : SpaceProjectManager.getAllProjects()) {
+ if (project.meetsRequirements(tID, false)) {
+ sender.addChatMessage(new ChatComponentText(project.getProjectName()));
+ }
+ }
+ }
+ case UNLOCKED -> {
+ for (ISpaceProject project : SpaceProjectManager.getTeamSpaceProjects(tID)) {
+ sender.addChatMessage(new ChatComponentText(project.getProjectName()));
+ }
+ }
+ }
+ }
+
+ private void processCopy(ICommandSender sender, String playerToCopyFrom, String playerCopyingTo) {
+ // This will take a while
+ }
+
+ private void printHelp(ICommandSender sender) {
+
+ }
+}
diff --git a/src/main/java/gregtech/common/misc/spaceprojects/commands/SP_Command.java b/src/main/java/gregtech/common/misc/spaceprojects/commands/SP_Command.java
new file mode 100644
index 0000000000..3c4ad00932
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/spaceprojects/commands/SP_Command.java
@@ -0,0 +1,166 @@
+package gregtech.common.misc.spaceprojects.commands;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.stream.Collectors;
+
+import net.minecraft.command.CommandBase;
+import net.minecraft.command.ICommandSender;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.util.EnumChatFormatting;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+import gregtech.api.util.GT_Utility;
+import gregtech.common.misc.spaceprojects.SpaceProjectManager;
+
+/**
+ * @author BlueWeabo
+ */
+public class SP_Command extends CommandBase {
+
+ private static final Set<Pair<EntityPlayerMP, EntityPlayerMP>> invite = Collections
+ .newSetFromMap(new WeakHashMap<>());
+ private static final Set<EntityPlayerMP> confirm = Collections.newSetFromMap(new WeakHashMap<>());
+
+ private static final String INVITE = "invite";
+ private static final String ACCEPT = "accept";
+ private static final String LEAVE = "leave";
+ private static final String CONFIRM = "confirm";
+
+ @Override
+ public String getCommandName() {
+ return "sp";
+ }
+
+ @Override
+ public int getRequiredPermissionLevel() {
+ return 0;
+ }
+
+ @Override
+ public String getCommandUsage(ICommandSender sender) {
+ return "/" + getCommandName() + "<subCommand> [PlayerName]";
+ }
+
+ @Override
+ public void processCommand(ICommandSender sender, String[] arguments) {
+ if (arguments.length < 1) {
+ return;
+ }
+ switch (arguments[0]) {
+ case INVITE -> {
+ if (arguments.length < 2) {
+ return;
+ }
+ processInvite(sender, arguments[1]);
+ }
+ case ACCEPT -> {
+ if (arguments.length < 2) {
+ return;
+ }
+ processAccept(sender, arguments[1]);
+ }
+ case LEAVE -> processLeave(sender);
+ case CONFIRM -> processConfirm(sender);
+ }
+ }
+
+ private void processInvite(ICommandSender sender, String playerInvited) {
+ EntityPlayerMP teamLeader = getCommandSenderAsPlayer(sender);
+ EntityPlayerMP teamMember = getPlayer(sender, playerInvited);
+ invite.add(Pair.of(teamMember, teamLeader));
+ String message = EnumChatFormatting.GOLD + teamLeader.getCommandSenderName()
+ + EnumChatFormatting.RESET
+ + " has sent you an invite to join their team. Accept it with"
+ + EnumChatFormatting.GOLD
+ + " /sp accept "
+ + teamLeader.getCommandSenderName();
+ GT_Utility.sendChatToPlayer(teamMember, message);
+ }
+
+ private void processAccept(ICommandSender sender, String playerInviter) {
+ EntityPlayerMP teamMember = getCommandSenderAsPlayer(sender);
+ EntityPlayerMP teamLeader = getPlayer(sender, playerInviter);
+ if (invite.contains(Pair.of(teamMember, teamLeader))) {
+ String message = EnumChatFormatting.GOLD + teamMember.getCommandSenderName()
+ + EnumChatFormatting.RESET
+ + " has accepted the invite.";
+ SpaceProjectManager.putInTeam(teamMember.getUniqueID(), teamLeader.getUniqueID());
+ GT_Utility.sendChatToPlayer(teamLeader, message);
+ invite.remove(Pair.of(teamMember, teamLeader));
+ }
+ }
+
+ private void processLeave(ICommandSender sender) {
+ EntityPlayerMP player = getCommandSenderAsPlayer(sender);
+ String message = "Are you sure you want to leave the team. You will lose all progress. Use "
+ + EnumChatFormatting.GOLD
+ + "/sp confirm"
+ + EnumChatFormatting.RESET
+ + " to confirm this. This does nothing if you are the team leader.";
+ GT_Utility.sendChatToPlayer(player, message);
+ confirm.add(player);
+ }
+
+ private void processConfirm(ICommandSender sender) {
+ EntityPlayerMP player = getCommandSenderAsPlayer(sender);
+ if (confirm.contains(player)) {
+ String message = "Successfully left the team.";
+ SpaceProjectManager.putInTeam(player.getUniqueID(), player.getUniqueID());
+ GT_Utility.sendChatToPlayer(player, message);
+ confirm.remove(player);
+ }
+ }
+
+ @Override
+ public List<String> addTabCompletionOptions(ICommandSender sender, String[] arguments) {
+ List<String> autoComplete = new ArrayList<>();
+ String filter = arguments.length == 0 ? "" : arguments[0].trim();
+ switch (arguments.length) {
+ case 1 -> autoComplete.addAll(Arrays.asList(getSubCommands()));
+ case 2 -> {
+ filter = arguments[1].trim();
+ if (arguments[0].equals(INVITE)) {
+ autoComplete.addAll(Arrays.asList(getPlayers()));
+ break;
+ }
+
+ if (arguments[0].equals(CONFIRM)) {
+ Optional<Pair<EntityPlayerMP, EntityPlayerMP>> pairOpt = invite.stream()
+ .filter(
+ (e) -> e.getKey()
+ .getUniqueID() == getCommandSenderAsPlayer(sender).getUniqueID())
+ .findFirst();
+ if (pairOpt.isPresent()) {
+ autoComplete.add(
+ SpaceProjectManager.getPlayerNameFromUUID(
+ pairOpt.get()
+ .getRight()
+ .getUniqueID()));
+ }
+ }
+ }
+ }
+ String finalFilter = filter;
+ return autoComplete.stream()
+ .filter(s -> finalFilter.isEmpty() || s.startsWith(finalFilter))
+ .collect(Collectors.toList());
+ }
+
+ private String[] getPlayers() {
+ return MinecraftServer.getServer()
+ .getAllUsernames();
+ }
+
+ private String[] getSubCommands() {
+ return new String[] { INVITE, ACCEPT, LEAVE, CONFIRM };
+ }
+
+}
diff --git a/src/main/java/gregtech/common/misc/spaceprojects/enums/JsonVariables.java b/src/main/java/gregtech/common/misc/spaceprojects/enums/JsonVariables.java
new file mode 100644
index 0000000000..665f15af74
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/spaceprojects/enums/JsonVariables.java
@@ -0,0 +1,22 @@
+package gregtech.common.misc.spaceprojects.enums;
+
+public class JsonVariables {
+
+ public static final String PAIR_LEFT = "pairLeft";
+ public static final String PAIR_RIGHT = "pairRight";
+
+ public static final String MAP_UUID = "mapUUID";
+ public static final String MAP_MAP = "mapMap";
+ public static final String MAP_PAIR = "mapPair";
+ public static final String MAP_PROJECT = "mapProject";
+
+ public static final String PROJECT_NAME = "projectName";
+ public static final String PROJECT_CURRENT_STAGE = "projectCurrentStage";
+ public static final String PROJECT_UPGRADES_BUILT = "projectUpgradesBuilt";
+ public static final String PROJECT_LOCATION = "projectLocation";
+ public static final String PROJECT_CURRENT_UPGRADE = "projectCurrentUpgrade";
+
+ public static final String UPGRADE_NAME = "upgradeName";
+ public static final String UPGRADE_CURRENT_STAGE = "upgradeCurrentStage";
+ public static final String UPGRADE_PROJECT_PARENT = "upgradeProjectParent";
+}
diff --git a/src/main/java/gregtech/common/misc/spaceprojects/enums/SolarSystem.java b/src/main/java/gregtech/common/misc/spaceprojects/enums/SolarSystem.java
new file mode 100644
index 0000000000..70dee3269d
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/spaceprojects/enums/SolarSystem.java
@@ -0,0 +1,104 @@
+package gregtech.common.misc.spaceprojects.enums;
+
+import static gregtech.api.enums.Mods.GregTech;
+import static gregtech.common.misc.spaceprojects.enums.SpaceBodyType.AsteroidBelt;
+import static gregtech.common.misc.spaceprojects.enums.SpaceBodyType.DwarfPlanet;
+import static gregtech.common.misc.spaceprojects.enums.SpaceBodyType.GasGiant;
+import static gregtech.common.misc.spaceprojects.enums.SpaceBodyType.IceGiant;
+import static gregtech.common.misc.spaceprojects.enums.SpaceBodyType.NaturalSatellite;
+import static gregtech.common.misc.spaceprojects.enums.SpaceBodyType.Planet;
+import static gregtech.common.misc.spaceprojects.enums.SpaceBodyType.Star;
+import static gregtech.common.misc.spaceprojects.enums.StarType.GClass;
+import static gregtech.common.misc.spaceprojects.enums.StarType.NotAStar;
+
+import com.gtnewhorizons.modularui.api.drawable.UITexture;
+
+import gregtech.common.misc.spaceprojects.SpaceProjectManager;
+import gregtech.common.misc.spaceprojects.interfaces.ISpaceBody;
+
+/**
+ * An enum of all space bodies in the Sol Solar System. Or to be exact the more important ones
+ *
+ * @author BlueWeabo
+ */
+public enum SolarSystem implements ISpaceBody {
+
+ Sol(Star, GClass),
+ Overworld(Planet),
+ Moon(NaturalSatellite),
+ Mars(Planet),
+ Deimos(NaturalSatellite),
+ Phobos(NaturalSatellite),
+ Mercury(Planet),
+ Venus(Planet),
+ Jupiter(GasGiant),
+ Io(NaturalSatellite),
+ Ganymede(NaturalSatellite),
+ Europa(NaturalSatellite),
+ Callisto(NaturalSatellite),
+ Saturn(GasGiant),
+ Mimas(NaturalSatellite),
+ Enceladus(NaturalSatellite),
+ Tethys(NaturalSatellite),
+ Rhea(NaturalSatellite),
+ Titan(NaturalSatellite),
+ Hyperion(NaturalSatellite),
+ Iapetus(NaturalSatellite),
+ Phoebe(NaturalSatellite),
+ Uranus(IceGiant),
+ Miranda(NaturalSatellite),
+ Ariel(NaturalSatellite),
+ Umbriel(NaturalSatellite),
+ Titania(NaturalSatellite),
+ Oberon(NaturalSatellite),
+ Neptune(IceGiant),
+ Proteus(NaturalSatellite),
+ Triton(NaturalSatellite),
+ Nereid(NaturalSatellite),
+ Ceres(DwarfPlanet),
+ Pluto(DwarfPlanet),
+ Arrokoth(DwarfPlanet),
+ MakeMake(DwarfPlanet),
+ KuiperBelt(AsteroidBelt),
+ NONE(SpaceBodyType.NONE);
+
+ private final SpaceBodyType spaceBody;
+ private final StarType star;
+ private final UITexture texture;
+
+ SolarSystem(SpaceBodyType aType) {
+ this(aType, NotAStar);
+ }
+
+ SolarSystem(SpaceBodyType aType, StarType aStarType) {
+ star = aStarType;
+ spaceBody = aType;
+ texture = UITexture.fullImage(GregTech.ID, "solarsystem/" + getName());
+ SpaceProjectManager.addLocation(this);
+ }
+
+ @Override
+ public StarType getStarType() {
+ return star;
+ }
+
+ @Override
+ public SpaceBodyType getType() {
+ return spaceBody;
+ }
+
+ @Override
+ public String getName() {
+ return name();
+ }
+
+ @Override
+ public UITexture getTexture() {
+ return texture;
+ }
+
+ @Override
+ public String getUnlocalizedName() {
+ return "gt.solar.system." + getName().toLowerCase();
+ }
+}
diff --git a/src/main/java/gregtech/common/misc/spaceprojects/enums/SpaceBodyType.java b/src/main/java/gregtech/common/misc/spaceprojects/enums/SpaceBodyType.java
new file mode 100644
index 0000000000..8ca8b663da
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/spaceprojects/enums/SpaceBodyType.java
@@ -0,0 +1,17 @@
+package gregtech.common.misc.spaceprojects.enums;
+
+/**
+ * @author BlueWeabo
+ */
+public enum SpaceBodyType {
+ Star,
+ NeutronStar,
+ Planet,
+ NaturalSatellite,
+ AsteroidBelt,
+ BlackHole,
+ GasGiant,
+ DwarfPlanet,
+ IceGiant,
+ NONE
+}
diff --git a/src/main/java/gregtech/common/misc/spaceprojects/enums/StarType.java b/src/main/java/gregtech/common/misc/spaceprojects/enums/StarType.java
new file mode 100644
index 0000000000..7585c95aa0
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/spaceprojects/enums/StarType.java
@@ -0,0 +1,32 @@
+package gregtech.common.misc.spaceprojects.enums;
+
+/**
+ * @author BlueWeabo
+ */
+public enum StarType {
+
+ OClass(30000, 100),
+ BClass(10000, 20),
+ AClass(20, 5),
+ FClass(4, 2),
+ GClass(1, 1),
+ KClass(0.4, 0.5f),
+ MClass(0.08, 0.1f),
+ NotAStar(0, 0);
+
+ private final double solarLuminosity;
+ private final float costMultiplier;
+
+ StarType(double solarLuminosity, float costMultiplier) {
+ this.solarLuminosity = solarLuminosity;
+ this.costMultiplier = costMultiplier;
+ }
+
+ public double getSolarLuminosity() {
+ return solarLuminosity;
+ }
+
+ public float getCostMultiplier() {
+ return costMultiplier;
+ }
+}
diff --git a/src/main/java/gregtech/common/misc/spaceprojects/enums/UpgradeStatus.java b/src/main/java/gregtech/common/misc/spaceprojects/enums/UpgradeStatus.java
new file mode 100644
index 0000000000..3e662720ac
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/spaceprojects/enums/UpgradeStatus.java
@@ -0,0 +1,11 @@
+package gregtech.common.misc.spaceprojects.enums;
+
+/**
+ * @author BlueWeabo
+ */
+public enum UpgradeStatus {
+ Locked,
+ Unlocked,
+ InProgress,
+ Finished
+}
diff --git a/src/main/java/gregtech/common/misc/spaceprojects/interfaces/ISpaceBody.java b/src/main/java/gregtech/common/misc/spaceprojects/interfaces/ISpaceBody.java
new file mode 100644
index 0000000000..79eba4c968
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/spaceprojects/interfaces/ISpaceBody.java
@@ -0,0 +1,37 @@
+package gregtech.common.misc.spaceprojects.interfaces;
+
+import com.gtnewhorizons.modularui.api.drawable.UITexture;
+
+import gregtech.common.misc.spaceprojects.enums.SpaceBodyType;
+import gregtech.common.misc.spaceprojects.enums.StarType;
+
+/**
+ * @author BlueWeabo
+ */
+public interface ISpaceBody {
+
+ /**
+ * @return The star type of the space body, if its a star
+ */
+ StarType getStarType();
+
+ /**
+ * @return The type of space body it is
+ */
+ SpaceBodyType getType();
+
+ /**
+ * @return The internal name of the space body
+ */
+ String getName();
+
+ /**
+ * @return The texture of the space body used for UI
+ */
+ UITexture getTexture();
+
+ /**
+ * @return The Unlocalized name for this body
+ */
+ String getUnlocalizedName();
+}
diff --git a/src/main/java/gregtech/common/misc/spaceprojects/interfaces/ISpaceProject.java b/src/main/java/gregtech/common/misc/spaceprojects/interfaces/ISpaceProject.java
new file mode 100644
index 0000000000..51ae03ff30
--- /dev/null
+++ b/src/main/java/gregtech/common/misc/spaceprojects/interfaces/ISpaceProject.java
@@ -0,0 +1,430 @@
+package gregtech.common.misc.spaceprojects.interfaces;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+
+import com.gtnewhorizons.modularui.api.drawable.UITexture;
+
+import gregtech.common.misc.spaceprojects.enums.SpaceBodyType;
+import gregtech.common.misc.spaceprojects.enums.StarType;
+import gregtech.common.misc.spaceprojects.enums.UpgradeStatus;
+
+/**
+ * @author BlueWeabo
+ */
+public interface ISpaceProject {
+
+ /**
+ * @return the internal name of the project.
+ */
+ String getProjectName();
+
+ /**
+ * @return Unlocalized name of the project.
+ */
+ String getUnlocalizedName();
+
+ /**
+ * @return Localized name of the project using StatCollect#translateToLocal.
+ */
+ String getLocalizedName();
+
+ /**
+ * @return The voltage the project requires to be built at. Used by the project manager.
+ */
+ long getProjectVoltage();
+
+ /**
+ * @return The duration it takes to build out one(1) stage of the project. The time returned is in ticks
+ */
+ int getProjectBuildTime();
+
+ /**
+ * @return The Current Progress of the project in percentage form. 1 being 100% and 0 being 0%.
+ */
+ float getProjectCurrentProgress();
+
+ /**
+ * @return Currently unused, but this is the project's tier. Will be used to determine the min-tier motors needed on
+ * the Space Elevator
+ */
+ int getProjectTier();
+
+ /**
+ * @return The Current stage of the project
+ */
+ int getCurrentStage();
+
+ /**
+ * @return The Total amount of stages the project has
+ */
+ int getTotalStages();
+
+ /**
+ * @return a Collection of all upgrades the project has
+ */
+ Collection<ISP_Upgrade> getAllUpgrades();
+
+ /**
+ * @return a Map of all upgrades that have been built.
+ */
+ Map<String, ISP_Upgrade> getUpgradesBuiltMap();
+
+ /**
+ * @return all built upgrades
+ */
+ Collection<ISP_Upgrade> getAllBuiltUpgrades();
+
+ /**
+ * @param upgradeName The name of the upgrade wanted
+ * @return The upgrade with the appropriate name found in the available upgrades for the project
+ */
+ ISP_Upgrade getUpgrade(String upgradeName);
+
+ /**
+ * @return The Items cost required per stage in an array form. Used for making the recipe.
+ */
+ ItemStack[] getItemsCostPerStage();
+
+ /**
+ * @param index Index at which the itemstack is found at
+ * @return an item's cost at the appropriate index. Null otherwise if it goes above the index or there are no item
+ * costs
+ */
+ ItemStack getItemCostPerStage(int index);
+
+ /**
+ * @return The Items current progress in an array form.
+ */
+ ItemStack[] getCurrentItemsProgress();
+
+ /**
+ * @param index Index at which the itemstack is found at
+ * @return an item's current progress at the appropriate index. Null otherwise if it goes above the index or there
+ * are no item costs
+ */
+ ItemStack getCurrentItemProgress(int index);
+
+ /**
+ * @return The items total cost required in an array form.
+ */
+ ItemStack[] getTotalItemsCost();
+
+ /**
+ * @param index Index at which the itemstack is found at
+ * @return an item's total cost at the appropriate index. Null otherwise if it goes above the index or there are no
+ * item costs
+ */
+ ItemStack getTotalItemCost(int index);
+
+ /**
+ * @return The fluids cost required per stage in an array form. Used for making the recipe.
+ */
+ FluidStack[] getFluidsCostPerStage();
+
+ /**
+ * @param index Index at which the fluidstack is found at
+ * @return a fluid's cost at the appropriate index. Null otherwise if it goes above the index or there are no fluid
+ * costs
+ */
+ FluidStack getFluidCostPerStage(int index);
+
+ /**
+ * @return The fluids current progress in an array form. Null if there are no fluid costs
+ */
+ FluidStack[] getCurrentFluidsProgress();
+
+ /**
+ * @param index Index at which the fluidstack is found at
+ * @return a fluid's current progress at the appropriate index. Null otherwise if it goes above the index or there
+ * are no fluid costs
+ */
+ FluidStack getCurrentFluidProgress(int index);
+
+ /**
+ * @return The fluids total cost required in an array form. Null if there are no fluid costs
+ */
+ FluidStack[] getTotalFluidsCost();
+
+ /**
+ * @param index Index at which the fluidstack is found at
+ * @return a fluid's total cost at the appropriate index. Null otherwise if it goes above the index or there are no
+ * fluid costs
+ */
+ FluidStack getTotalFluidCost(int index);
+
+ /**
+ * @return The current upgrade for this project, which is being built
+ */
+ ISP_Upgrade getUpgradeBeingBuilt();
+
+ /**
+ * @return The location of the project
+ */
+ ISpaceBody getProjectLocation();
+
+ /**
+ * @return The texture used in GUIs for the project
+ */
+ UITexture getTexture();
+
+ /**
+ * Sets the current stage of the project
+ */
+ void setProjectCurrentStage(int stage);
+
+ /**
+ * Sets the current upgrade, which needs to be built
+ */
+ void setCurrentUpgradeBeingBuilt(ISP_Upgrade upgrade);
+
+ /**
+ * Sets the project's location when it starts being built
+ */
+ void setProjectLocation(ISpaceBody newLocation);
+
+ /**
+ * Sets the project's upgrades, which have been built
+ */
+ void setBuiltUpgrade(ISP_Upgrade... upgrades);
+
+ /**
+ * Goes to the next stage of the project
+ */
+ void goToNextStage();
+
+ /**
+ * Creates a copy of the space project
+ */
+ ISpaceProject copy();
+
+ /**
+ * Checks if the project meets all requirements with its current location
+ *
+ * @param team Team wanting said project and checking their projects
+ * @return true if all requirements met, false otherwise
+ */
+ boolean meetsRequirements(UUID team);
+
+ /**
+ * Checks if the project meets requirements if it requires other projects, unless {@code checkLocation} is true,
+ * then it also checks for the location
+ *
+ * @param team Team wanting said project and checking their projects
+ * @param checkLocation If the location position should be checked
+ * @return true if all requirements met, false otherwise
+ */
+ boolean meetsRequirements(UUID team, boolean checkLocation);
+
+ /**
+ * Checks if the projects is finished
+ */
+ boolean isFinished();
+
+ /**
+ * Checks if the project has a certain upgrade installed or not
+ *
+ * @param upgradeName Upgrade being searched for
+ * @return True if that upgrade has been installed, false otherwise
+ */
+ boolean hasUpgrade(String upgradeName);
+
+ /**
+ * @author BlueWeabo
+ */
+ interface ISP_Upgrade {
+
+ /**
+ * @return internal name of the upgrade
+ */
+ String getUpgradeName();
+
+ /**
+ * @return unlocalized name of the upgrade
+ */
+ String getUnlocalizedName();
+
+ /**
+ * @return localized name of the upgrade
+ */
+ String getLocalizedName();
+
+ /**
+ * @return The Items cost required per stage in an array form. Used for making the recipe.
+ */
+ ItemStack[] getItemsCostPerStage();
+
+ /**
+ * @param index Index at which the itemstack is found at
+ * @return an item's cost at the appropriate index. Null otherwise if it goes above the index or there are no
+ * item costs
+ */
+ ItemStack getItemCostPerStage(int index);
+
+ /**
+ * @return The Items current progress in an array form.
+ */
+ ItemStack[] getCurrentItemsProgress();
+
+ /**
+ * @param index Index at which the itemstack is found at
+ * @return an item's current progress at the appropriate index. Null otherwise if it goes above the index or
+ * there are no item costs
+ */
+ ItemStack getCurrentItemProgress(int index);
+
+ /**
+ * @return The items total cost required in an array form.
+ */
+ ItemStack[] getTotalItemsCost();
+
+ /**
+ * @param index Index at which the itemstack is found at
+ * @return an item's total cost at the appropriate index. Null otherwise if it goes above the index or there are
+ * no item costs
+ */
+ ItemStack getTotalItemCost(int index);
+
+ /**
+ * @return The fluids cost required per stage in an array form. Used for making the recipe.
+ */
+ FluidStack[] getFluidsCostPerStage();
+
+ /**
+ * @param index Index at which the fluidstack is found at
+ * @return a fluid's cost at the appropriate index. Null otherwise if it goes above the index or there are no
+ * fluid costs
+ */
+ FluidStack getFluidCostPerStage(int index);
+
+ /**
+ * @return The fluids current progress in an array form. Null if there are no fluid costs
+ */
+ FluidStack[] getCurrentFluidsProgress();
+
+ /**
+ * @param index Index at which the fluidstack is found at
+ * @return a fluid's current progress at the appropriate index. Null otherwise if it goes above the index or
+ * there are no fluid costs
+ */
+ FluidStack getCurrentFluidProgress(int index);
+
+ /**
+ * @return The fluids total cost required in an array form. Null if there are no fluid costs
+ */
+ FluidStack[] getTotalFluidsCost();
+
+ /**
+ * @param index Index at which the fluidstack is found at
+ * @return a fluid's total cost at the appropriate index. Null otherwise if it goes above the index or there are
+ * no fluid costs
+ */
+ FluidStack getTotalFluidCost(int index);
+
+ /**
+ * @return the total stages an upgrade has
+ */
+ int getTotalStages();
+
+ /**
+ * @return the build time for the upgrade to go to its next stage
+ */
+ int getUpgradeBuildTime();
+
+ /**
+ * @return current stage of the upgrade
+ */
+ int getCurrentStage();
+
+ /**
+ * @return The Current Progress of the upgrade in percentage form. 1 being 100% and 0 being 0%.
+ */
+ float getCurrentProgress();
+
+ /**
+ * @return The voltage at which the upgrade requires to be build at.
+ */
+ long getVoltage();
+
+ /**
+ * Unused, unsure if it will get a sure
+ */
+ UpgradeStatus getStatus();
+
+ /**
+ * @return the requirements the upgrade has
+ */
+ ISP_Requirements getUpgradeRequirements();
+
+ /**
+ * @return the parent project, which the upgrade belongs to
+ */
+ ISpaceProject getParentProject();
+
+ /**
+ * @param project The project the upgrade belongs to
+ */
+ void setUpgradeProject(ISpaceProject project);
+
+ /**
+ * Sets the current stage of the upgrade
+ *
+ * @param stage the stage to set
+ */
+ void setUpgradeCurrentStage(int stage);
+
+ /**
+ * Checks if the team has met all requirements to be able to build said upgrade
+ *
+ * @param team The one starting the upgrade
+ * @return true if all requirements are met, false otherwise
+ */
+ boolean meetsRequirements(UUID team);
+
+ /**
+ * Creates a copy of the upgrade
+ */
+ ISP_Upgrade copy();
+
+ /**
+ * Goes to the next stage of the upgrade
+ */
+ void goToNextStage();
+
+ /**
+ * @return true if the upgrade has finished all of its stages, false otherwise
+ */
+ boolean isFinished();
+ }
+
+ /**
+ * @author BlueWeabo
+ */
+ interface ISP_Requirements {
+
+ /**
+ * @return Space Body Type required by the project/upgrade
+ */
+ SpaceBodyType getBodyType();
+
+ /**
+ * @return Star Type required by the project/upgrade
+ */
+ StarType getStarType();
+
+ /**
+ * @return a list of all project required for the team to have to unlock it
+ */
+ List<ISpaceProject> getProjects();
+
+ /**
+ * @return a list of all upgrades an upgrade can have as required.
+ */
+ List<ISP_Upgrade> getUpgrades();
+ }
+}