PageRenderTime 82ms CodeModel.GetById 16ms app.highlight 60ms RepoModel.GetById 1ms app.codeStats 1ms

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