PageRenderTime 59ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/common/buildcraft/transport/plug/FacadeStateManager.java

https://github.com/2xsaiko/BuildCraft
Java | 422 lines | 346 code | 33 blank | 43 comment | 79 complexity | 17df3d0a682b972e010c0e6e7ddae24e MD5 | raw file
Possible License(s): LGPL-2.0
  1. /*
  2. * Copyright (c) 2017 SpaceToad and the BuildCraft team
  3. * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not
  4. * distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/
  5. */
  6. package buildcraft.transport.plug;
  7. import java.util.ArrayList;
  8. import java.util.Arrays;
  9. import java.util.Collection;
  10. import java.util.HashMap;
  11. import java.util.HashSet;
  12. import java.util.List;
  13. import java.util.Map;
  14. import java.util.Map.Entry;
  15. import java.util.Objects;
  16. import java.util.Random;
  17. import java.util.Set;
  18. import java.util.SortedMap;
  19. import java.util.TreeMap;
  20. import java.util.concurrent.Callable;
  21. import javax.annotation.Nonnull;
  22. import com.google.common.base.Optional;
  23. import com.google.common.collect.ImmutableSet;
  24. import io.netty.buffer.Unpooled;
  25. import net.minecraft.block.Block;
  26. import net.minecraft.block.BlockGlass;
  27. import net.minecraft.block.BlockLiquid;
  28. import net.minecraft.block.BlockStainedGlass;
  29. import net.minecraft.block.properties.IProperty;
  30. import net.minecraft.block.state.IBlockState;
  31. import net.minecraft.init.Blocks;
  32. import net.minecraft.init.Items;
  33. import net.minecraft.item.EnumDyeColor;
  34. import net.minecraft.item.Item;
  35. import net.minecraft.item.ItemStack;
  36. import net.minecraft.nbt.NBTTagCompound;
  37. import net.minecraft.util.ActionResult;
  38. import net.minecraft.util.EnumActionResult;
  39. import net.minecraft.util.EnumBlockRenderType;
  40. import net.minecraft.util.ResourceLocation;
  41. import net.minecraftforge.fluids.IFluidBlock;
  42. import net.minecraftforge.fml.common.event.FMLInterModComms.IMCMessage;
  43. import net.minecraftforge.fml.common.registry.ForgeRegistries;
  44. import buildcraft.api.core.BCDebugging;
  45. import buildcraft.api.core.BCLog;
  46. import buildcraft.api.facades.FacadeAPI;
  47. import buildcraft.api.facades.IFacade;
  48. import buildcraft.api.facades.IFacadePhasedState;
  49. import buildcraft.api.facades.IFacadeRegistry;
  50. import buildcraft.api.facades.IFacadeState;
  51. import buildcraft.lib.BCLib;
  52. import buildcraft.lib.misc.BlockUtil;
  53. import buildcraft.lib.misc.ItemStackKey;
  54. import buildcraft.lib.misc.StackUtil;
  55. import buildcraft.lib.net.PacketBufferBC;
  56. import buildcraft.transport.recipe.FacadeSwapRecipe;
  57. public enum FacadeStateManager implements IFacadeRegistry {
  58. INSTANCE;
  59. public static final boolean DEBUG = BCDebugging.shouldDebugLog("transport.facade");
  60. public static final SortedMap<IBlockState, FacadeBlockStateInfo> validFacadeStates;
  61. public static final Map<ItemStackKey, List<FacadeBlockStateInfo>> stackFacades;
  62. public static FacadeBlockStateInfo defaultState, previewState;
  63. private static final Map<Block, String> disabledBlocks = new HashMap<>();
  64. private static final Map<IBlockState, ItemStack> customBlocks = new HashMap<>();
  65. /** An array containing all mods that fail the {@link #doesPropertyConform(IProperty)} check, and any others.
  66. * <p>
  67. * Note: Mods should ONLY be added to this list AFTER it has been reported to them, and taken off the list once a
  68. * version has been released with the fix. */
  69. private static final List<String> KNOWN_INVALID_REPORTED_MODS = Arrays.asList(new String[] { //
  70. });
  71. static {
  72. validFacadeStates = new TreeMap<>(BlockUtil.blockStateComparator());
  73. stackFacades = new HashMap<>();
  74. }
  75. public static FacadeBlockStateInfo getInfoForBlock(Block block) {
  76. return getInfoForState(block.getDefaultState());
  77. }
  78. private static FacadeBlockStateInfo getInfoForState(IBlockState state) {
  79. return validFacadeStates.get(state);
  80. }
  81. public static void receiveInterModComms(IMCMessage message) {
  82. String id = message.key;
  83. if (FacadeAPI.IMC_FACADE_DISABLE.equals(id)) {
  84. if (!message.isResourceLocationMessage()) {
  85. BCLog.logger.warn("[facade.imc] Received an invalid IMC message from " + message.getSender() + " - "
  86. + id + " should have a resourcelocation value, not a " + message);
  87. return;
  88. }
  89. ResourceLocation loc = message.getResourceLocationValue();
  90. Block block = Block.REGISTRY.getObject(loc);
  91. if (block == Blocks.AIR) {
  92. BCLog.logger.warn("[facade.imc] Received an invalid IMC message from " + message.getSender() + " - "
  93. + id + " should have a valid block target, not " + block + " (" + message + ")");
  94. return;
  95. }
  96. disabledBlocks.put(block, message.getSender());
  97. } else if (FacadeAPI.IMC_FACADE_CUSTOM.equals(id)) {
  98. if (!message.isNBTMessage()) {
  99. BCLog.logger.warn("[facade.imc] Received an invalid IMC message from " + message.getSender() + " - "
  100. + id + " should have an nbt value, not a " + message);
  101. return;
  102. }
  103. NBTTagCompound nbt = message.getNBTValue();
  104. String regName = nbt.getString(FacadeAPI.NBT_CUSTOM_BLOCK_REG_KEY);
  105. int meta = nbt.getInteger(FacadeAPI.NBT_CUSTOM_BLOCK_META);
  106. ItemStack stack = new ItemStack(nbt.getCompoundTag(FacadeAPI.NBT_CUSTOM_ITEM_STACK));
  107. if (regName.isEmpty()) {
  108. BCLog.logger.warn("[facade.imc] Received an invalid IMC message from " + message.getSender() + " - "
  109. + id + " should have a registry name for the block, stored as "
  110. + FacadeAPI.NBT_CUSTOM_BLOCK_REG_KEY);
  111. return;
  112. }
  113. if (stack.isEmpty()) {
  114. BCLog.logger.warn("[facade.imc] Received an invalid IMC message from " + message.getSender() + " - "
  115. + id + " should have a valid ItemStack stored in " + FacadeAPI.NBT_CUSTOM_ITEM_STACK);
  116. return;
  117. }
  118. Block block = Block.REGISTRY.getObject(new ResourceLocation(regName));
  119. if (block == Blocks.AIR) {
  120. BCLog.logger.warn("[facade.imc] Received an invalid IMC message from " + message.getSender() + " - "
  121. + id + " should have a valid block target, not " + block + " (" + message + ")");
  122. return;
  123. }
  124. IBlockState state = block.getStateFromMeta(meta);
  125. customBlocks.put(state, stack);
  126. }
  127. }
  128. /** @return One of:
  129. * <ul>
  130. * <li>{@link EnumActionResult#SUCCESS} if every state of the block is valid for a facade.
  131. * <li>{@link EnumActionResult#PASS} if every metadata needs to be checked by
  132. * {@link #isValidFacadeState(IBlockState)}</li>
  133. * <li>{@link EnumActionResult#FAIL} with string describing the problem with this block (if it is not valid
  134. * for a facade)</li>
  135. * </ul>
  136. */
  137. private static ActionResult<String> isValidFacadeBlock(Block block) {
  138. String disablingMod = disabledBlocks.get(block);
  139. if (disablingMod != null) {
  140. return new ActionResult<>(EnumActionResult.FAIL, "it has been disabled by " + disablingMod);
  141. }
  142. if (block instanceof IFluidBlock || block instanceof BlockLiquid) {
  143. return new ActionResult<>(EnumActionResult.FAIL, "it is a fluid block");
  144. }
  145. // if (block instanceof BlockSlime) {
  146. // return "it is a slime block";
  147. // }
  148. if (block instanceof BlockGlass || block instanceof BlockStainedGlass) {
  149. return new ActionResult<>(EnumActionResult.SUCCESS, "");
  150. }
  151. return new ActionResult<>(EnumActionResult.PASS, "");
  152. }
  153. /** @return Any of:
  154. * <ul>
  155. * <li>{@link EnumActionResult#SUCCESS} if this state is valid for a facade.
  156. * <li>{@link EnumActionResult#FAIL} with string describing the problem with this state (if it is not valid
  157. * for a facade)</li>
  158. * </ul>
  159. */
  160. private static ActionResult<String> isValidFacadeState(IBlockState state) {
  161. if (state.getBlock().hasTileEntity(state)) {
  162. return new ActionResult<>(EnumActionResult.FAIL, "it has a tile entity");
  163. }
  164. if (state.getRenderType() != EnumBlockRenderType.MODEL) {
  165. return new ActionResult<>(EnumActionResult.FAIL, "it doesn't have a normal model");
  166. }
  167. if (!state.isFullCube()) {
  168. return new ActionResult<>(EnumActionResult.FAIL, "it isn't a full cube");
  169. }
  170. return new ActionResult<>(EnumActionResult.SUCCESS, "");
  171. }
  172. @Nonnull
  173. private static ItemStack getRequiredStack(IBlockState state) {
  174. ItemStack stack = customBlocks.get(state);
  175. if (stack != null) {
  176. return stack;
  177. }
  178. Block block = state.getBlock();
  179. Item item = Item.getItemFromBlock(block);
  180. if (item == Items.AIR) {
  181. item = block.getItemDropped(state, new Random(0), 0);
  182. }
  183. return new ItemStack(item, 1, block.damageDropped(state));
  184. }
  185. public static void init() {
  186. defaultState = new FacadeBlockStateInfo(Blocks.AIR.getDefaultState(), StackUtil.EMPTY, ImmutableSet.of());
  187. for (Block block : ForgeRegistries.BLOCKS) {
  188. if (!DEBUG && KNOWN_INVALID_REPORTED_MODS.contains(block.getRegistryName().getResourceDomain())) {
  189. if (BCLib.VERSION.startsWith("7.99")) {
  190. BCLog.logger.warn(
  191. "[transport.facade] Skipping " + block + " as it has been added to the list of broken mods!");
  192. continue;
  193. }
  194. }
  195. // Check to make sure that all the properties work properly
  196. // Fixes a bug in extra utilities who doesn't serialise and deserialise properties properly
  197. boolean allPropertiesOk = true;
  198. for (IProperty<?> property : block.getBlockState().getProperties()) {
  199. allPropertiesOk &= doesPropertyConform(property);
  200. }
  201. if (!allPropertiesOk) {
  202. continue;
  203. }
  204. ActionResult<String> result = isValidFacadeBlock(block);
  205. // These strings are hardcoded, so we can get away with not needing the .equals check
  206. if (result.getType() != EnumActionResult.PASS && result.getType() != EnumActionResult.SUCCESS) {
  207. if (DEBUG) {
  208. BCLog.logger
  209. .info("[transport.facade] Disallowed block " + block.getRegistryName() + " because " + result);
  210. }
  211. continue;
  212. } else if (DEBUG) {
  213. if (result.getType() == EnumActionResult.SUCCESS) {
  214. BCLog.logger.info("[transport.facade] Allowed block " + block.getRegistryName());
  215. }
  216. }
  217. Set<IBlockState> checkedStates = new HashSet<>();
  218. Map<IBlockState, ItemStack> usedStates = new HashMap<>();
  219. Map<ItemStackKey, Map<IProperty<?>, Comparable<?>>> varyingProperties = new HashMap<>();
  220. for (IBlockState state : block.getBlockState().getValidStates()) {
  221. // state = block.getStateFromMeta(block.getMetaFromState(state));
  222. // if (!checkedStates.add(state)) {
  223. // continue;
  224. // }
  225. if (result.getType() != EnumActionResult.SUCCESS) {
  226. result = isValidFacadeState(state);
  227. if (result.getType() == EnumActionResult.SUCCESS) {
  228. if (DEBUG) {
  229. BCLog.logger.info("[transport.facade] Allowed state " + state);
  230. }
  231. } else {
  232. if (DEBUG) {
  233. BCLog.logger.info("[transport.facade] Disallowed state " + state + " because " + result);
  234. }
  235. continue;
  236. }
  237. }
  238. ItemStack stack = getRequiredStack(state);
  239. usedStates.put(state, stack);
  240. ItemStackKey stackKey = new ItemStackKey(stack);
  241. Map<IProperty<?>, Comparable<?>> vars = varyingProperties.get(stackKey);
  242. if (vars == null) {
  243. vars = new HashMap<>(state.getProperties());
  244. varyingProperties.put(stackKey, vars);
  245. } else {
  246. for (Entry<IProperty<?>, Comparable<?>> entry : state.getProperties().entrySet()) {
  247. IProperty<?> prop = entry.getKey();
  248. Comparable<?> value = entry.getValue();
  249. if (vars.get(prop) != value) {
  250. vars.put(prop, null);
  251. }
  252. }
  253. }
  254. }
  255. PacketBufferBC testingBuffer = PacketBufferBC.asPacketBufferBc(Unpooled.buffer());
  256. varyingProperties.forEach((key, vars) -> {
  257. if (DEBUG) {
  258. BCLog.logger.info("[transport.facade] pre-" + key + ":");
  259. vars.keySet().forEach(p -> BCLog.logger.info("[transport.facade] " + p));
  260. }
  261. vars.values().removeIf(Objects::nonNull);
  262. if (DEBUG && !vars.isEmpty()) {
  263. BCLog.logger.info("[transport.facade] " + key + ":");
  264. vars.keySet().forEach(p -> BCLog.logger.info("[transport.facade] " + p));
  265. }
  266. });
  267. for (Entry<IBlockState, ItemStack> entry : usedStates.entrySet()) {
  268. IBlockState state = entry.getKey();
  269. ItemStack stack = entry.getValue();
  270. Map<IProperty<?>, Comparable<?>> vars = varyingProperties.get(new ItemStackKey(stack));
  271. try {
  272. ImmutableSet<IProperty<?>> varSet = ImmutableSet.copyOf(vars.keySet());
  273. FacadeBlockStateInfo info = new FacadeBlockStateInfo(state, stack, varSet);
  274. validFacadeStates.put(state, info);
  275. if (!info.requiredStack.isEmpty()) {
  276. ItemStackKey stackKey = new ItemStackKey(info.requiredStack);
  277. stackFacades.computeIfAbsent(stackKey, k -> new ArrayList<>()).add(info);
  278. }
  279. // Test to make sure that we can read + write it
  280. FacadePhasedState phasedState = info.createPhased(false, null);
  281. NBTTagCompound nbt = phasedState.writeToNbt();
  282. FacadePhasedState read = FacadePhasedState.readFromNbt(nbt);
  283. if (read.stateInfo != info) {
  284. throw new IllegalStateException("Read (from NBT) state was different! (\n\t" + read.stateInfo
  285. + "\n !=\n\t" + info + "\n\tNBT = " + nbt + "\n)");
  286. }
  287. phasedState.writeToBuffer(testingBuffer);
  288. read = FacadePhasedState.readFromBuffer(testingBuffer);
  289. if (read.stateInfo != info) {
  290. throw new IllegalStateException("Read (from buffer) state was different! (\n\t" + read.stateInfo
  291. + "\n !=\n\t" + info + "\n)");
  292. }
  293. testingBuffer.clear();
  294. if (DEBUG) {
  295. BCLog.logger.info("[transport.facade] Added " + info);
  296. }
  297. } catch (Throwable t) {
  298. String msg = "Scanning facade states";
  299. msg += "\n\tState = " + state;
  300. msg += "\n\tBlock = " + safeToString(() -> state.getBlock().getRegistryName());
  301. msg += "\n\tStack = " + stack;
  302. msg += "\n\tvarying-properties: {";
  303. for (Entry<IProperty<?>, Comparable<?>> varEntry : vars.entrySet()) {
  304. msg += "\n\t\t" + varEntry.getKey() + " = " + varEntry.getValue();
  305. }
  306. msg += "\n\t}";
  307. throw new IllegalStateException(msg.replace("\t", " "), t);
  308. }
  309. }
  310. }
  311. previewState = validFacadeStates.get(Blocks.BRICK_BLOCK.getDefaultState());
  312. FacadeSwapRecipe.genRecipes();
  313. }
  314. private static <V extends Comparable<V>> boolean doesPropertyConform(IProperty<V> property) {
  315. try {
  316. property.parseValue("");
  317. } catch (AbstractMethodError error) {
  318. String message = "Invalid IProperty object detected!";
  319. message += "\n Class = " + property.getClass();
  320. message += "\n Method not overriden: IProperty.parseValue(String)";
  321. RuntimeException exception = new RuntimeException(message, error);
  322. if (BCLib.DEV || !BCLib.MC_VERSION.equals("1.12.2")) {
  323. throw exception;
  324. } else {
  325. BCLog.logger.error("[transport.facade] Invalid property!", exception);
  326. }
  327. return false;
  328. }
  329. boolean allFine = true;
  330. for (V value : property.getAllowedValues()) {
  331. String name = property.getName(value);
  332. Optional<V> optional = property.parseValue(name);
  333. V parsed = optional == null ? null : optional.orNull();
  334. if (!Objects.equals(value, parsed)) {
  335. allFine = false;
  336. // A property is *wrong*
  337. // this is a big problem
  338. String message = "Invalid property value detected!";
  339. message += "\n Property class = " + property.getClass();
  340. message += "\n Property = " + property;
  341. message += "\n Possible Values = " + property.getAllowedValues();
  342. message += "\n Value Name = " + name;
  343. message += "\n Value (original) = " + value;
  344. message += "\n Value (parsed) = " + parsed;
  345. message += "\n Value class (original) = " + (value == null ? null : value.getClass());
  346. message += "\n Value class (parsed) = " + (parsed == null ? null : parsed.getClass());
  347. if (optional == null) {
  348. // Massive issue
  349. message += "\n IProperty.parseValue() -> Null com.google.common.base.Optional!!";
  350. }
  351. message += "\n";
  352. // This check *intentionally* crashes on a new MC version
  353. // or in a dev environment
  354. // as this really needs to be fixed
  355. RuntimeException exception = new RuntimeException(message);
  356. if (BCLib.DEV || !BCLib.MC_VERSION.equals("1.12.2")) {
  357. throw exception;
  358. } else {
  359. BCLog.logger.error("[transport.facade] Invalid property!", exception);
  360. }
  361. }
  362. }
  363. return allFine;
  364. }
  365. private static String safeToString(Callable<Object> callable) {
  366. try {
  367. return Objects.toString(callable.call());
  368. } catch (Throwable t) {
  369. return "~~ERROR~~" + t.getMessage();
  370. }
  371. }
  372. // IFacadeRegistry
  373. @Override
  374. public Collection<? extends IFacadeState> getValidFacades() {
  375. return validFacadeStates.values();
  376. }
  377. @Override
  378. public IFacadePhasedState createPhasedState(IFacadeState state, boolean isHollow, EnumDyeColor activeColor) {
  379. return new FacadePhasedState((FacadeBlockStateInfo) state, isHollow, activeColor);
  380. }
  381. @Override
  382. public IFacade createPhasedFacade(IFacadePhasedState[] states) {
  383. FacadePhasedState[] realStates = new FacadePhasedState[states.length];
  384. for (int i = 0; i < states.length; i++) {
  385. realStates[i] = (FacadePhasedState) states[i];
  386. }
  387. return new FacadeInstance(realStates);
  388. }
  389. }