/* * CraftBook Copyright (C) 2010-2017 sk89q <http://www.sk89q.com> * CraftBook Copyright (C) 2011-2017 me4502 <http://www.me4502.com> * CraftBook Copyright (C) Contributors * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, * see <http://www.gnu.org/licenses/>. */ package com.sk89q.craftbook.sponge.mechanics.ics; import static com.sk89q.craftbook.core.util.documentation.DocumentationGenerator.createStringOfLength; import static com.sk89q.craftbook.core.util.documentation.DocumentationGenerator.padToLength; import com.google.common.reflect.TypeToken; import com.google.inject.Inject; import com.me4502.modularframework.module.Module; import com.me4502.modularframework.module.guice.ModuleConfiguration; import com.sk89q.craftbook.core.util.ConfigValue; import com.sk89q.craftbook.core.util.CraftBookException; import com.sk89q.craftbook.core.util.documentation.DocumentationGenerator; import com.sk89q.craftbook.core.util.documentation.DocumentationProvider; import com.sk89q.craftbook.sponge.CraftBookPlugin; import com.sk89q.craftbook.sponge.mechanics.ics.command.SetDataCommand; import com.sk89q.craftbook.sponge.mechanics.ics.command.ShowDataCommand; import com.sk89q.craftbook.sponge.mechanics.ics.factory.SerializedICFactory; import com.sk89q.craftbook.sponge.mechanics.ics.pinsets.PinSet; import com.sk89q.craftbook.sponge.mechanics.ics.pinsets.Pins3ISO; import com.sk89q.craftbook.sponge.mechanics.ics.pinsets.PinsSI3O; import com.sk89q.craftbook.sponge.mechanics.ics.pinsets.PinsSISO; import com.sk89q.craftbook.sponge.mechanics.types.SpongeBlockMechanic; import com.sk89q.craftbook.sponge.st.SelfTriggeringMechanic; import com.sk89q.craftbook.sponge.st.SpongeSelfTriggerManager; import com.sk89q.craftbook.sponge.util.SignUtil; import com.sk89q.craftbook.sponge.util.data.CraftBookKeys; import com.sk89q.craftbook.sponge.util.data.mutable.ICData; import ninja.leaping.configurate.ConfigurationNode; import org.spongepowered.api.Sponge; import org.spongepowered.api.block.BlockState; import org.spongepowered.api.block.BlockTypes; import org.spongepowered.api.block.tileentity.Sign; import org.spongepowered.api.command.CommandMapping; import org.spongepowered.api.command.args.GenericArguments; import org.spongepowered.api.command.spec.CommandSpec; import org.spongepowered.api.data.key.Keys; import org.spongepowered.api.entity.living.player.Player; import org.spongepowered.api.event.Listener; import org.spongepowered.api.event.block.ChangeBlockEvent; import org.spongepowered.api.event.block.tileentity.ChangeSignEvent; import org.spongepowered.api.event.filter.cause.First; import org.spongepowered.api.text.Text; import org.spongepowered.api.text.format.TextColors; import org.spongepowered.api.util.Direction; import org.spongepowered.api.world.Location; import org.spongepowered.api.world.World; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @Module(id = "icsocket", name = "ICSocket", onEnable="onInitialize", onDisable="onDisable") public class ICSocket extends SpongeBlockMechanic implements SelfTriggeringMechanic, DocumentationProvider { static final HashMap<String, PinSet> PINSETS = new HashMap<>(); private static final Pattern IC_TABLE_PATTERN = Pattern.compile("%IC_TABLE%", Pattern.LITERAL); static { PINSETS.put("SISO", new PinsSISO()); PINSETS.put("SI3O", new PinsSI3O()); PINSETS.put("3ISO", new Pins3ISO()); } @Inject @ModuleConfiguration public ConfigurationNode config; public ConfigValue<Double> maxRadius = new ConfigValue<>("max-radius", "Maximum radius of IC mechanics.", 10d, TypeToken.of(Double.class)); private Map<Location<World>, IC> loadedICs = new HashMap<>(); private CommandMapping icCommandMapping; @Override public void onInitialize() throws CraftBookException { super.onInitialize(); maxRadius.load(config); if ("true".equalsIgnoreCase(System.getProperty("craftbook.generate-docs"))) { ICManager.getICTypes().forEach(DocumentationGenerator::generateDocumentation); } CommandSpec showDataCommand = CommandSpec.builder() .arguments(GenericArguments.optional(GenericArguments.location(Text.of("block")))) .executor(new ShowDataCommand(this)) .build(); CommandSpec setDataCommand = CommandSpec.builder() .arguments( GenericArguments.string(Text.of("variable")), GenericArguments.string(Text.of("value")), GenericArguments.optional(GenericArguments.location(Text.of("block"))) ) .executor(new SetDataCommand(this)) .build(); CommandSpec dataCommand = CommandSpec.builder() .child(showDataCommand, "show") .child(setDataCommand, "set") .build(); CommandSpec icCommand = CommandSpec.builder() .description(Text.of("Base command for Integrated Circuits.")) .child(dataCommand, "data") .build(); icCommandMapping = Sponge.getCommandManager().register(CraftBookPlugin.spongeInst(), icCommand, "ic", "ics", "integratedcircuit").orElse(null); } @Override public void onDisable() { super.onDisable(); loadedICs.forEach((worldLocation, ic) -> { ic.unload(); if (ic.getFactory() instanceof SerializedICFactory) { ic.getBlock().offer(new ICData(((SerializedICFactory) ic.getFactory()).getData(ic))); } }); loadedICs.clear(); if (icCommandMapping != null) { Sponge.getCommandManager().removeMapping(icCommandMapping); } } @Override public String getName() { return "ICs"; } @Listener public void onChangeSign(ChangeSignEvent event, @First Player player) { ICType<? extends IC> icType = ICManager.getICType((event.getText().lines().get(1)).toPlain()); if (icType == null) return; List<Text> lines = event.getText().lines().get(); lines.set(0, Text.of(icType.getShorthand().toUpperCase())); lines.set(1, Text.of(SignUtil.getTextRaw(lines.get(1)).toUpperCase())); try { createICData(event.getTargetTile().getLocation(), lines, player); player.sendMessage(Text.of(TextColors.YELLOW, "Created " + icType.getName())); event.getText().set(Keys.SIGN_LINES, lines); } catch (InvalidICException e) { event.setCancelled(true); player.sendMessage(Text.of("Failed to create IC. " + e.getMessage())); } } @Listener public void onBlockBreak(ChangeBlockEvent.Break event) { event.getTransactions().stream().map(transaction -> transaction.getOriginal().getLocation().get()).forEach(location -> { if (loadedICs.containsKey(location)) { IC ic = loadedICs.remove(location); ic.unload(); if (ic instanceof SelfTriggeringIC) { ((SpongeSelfTriggerManager) CraftBookPlugin.inst().getSelfTriggerManager().get()).unregister(this, location); } } }); } @Listener public void onBlockChange(ChangeBlockEvent.Post event) { event.getTransactions().forEach(blockSnapshotTransaction -> { Location<World> baseLocation = blockSnapshotTransaction.getFinal().getLocation().get(); BlockState originalState = blockSnapshotTransaction.getOriginal().getExtendedState(); List<Location<World>> icCheckSpots = new ArrayList<>(); boolean wasPowered = false; if (originalState.getType() == BlockTypes.REDSTONE_WIRE) { wasPowered = originalState.get(Keys.POWER).orElse(0) > 0; icCheckSpots.addAll(blockSnapshotTransaction.getFinal().get(Keys.WIRE_ATTACHMENTS).map(Map::keySet).orElse(EnumSet.noneOf(Direction.class)) .stream().map(baseLocation::getRelative) .collect(Collectors.toList())); // TODO REMOVE icCheckSpots.add(baseLocation.getRelative(Direction.NORTH)); icCheckSpots.add(baseLocation.getRelative(Direction.SOUTH)); icCheckSpots.add(baseLocation.getRelative(Direction.EAST)); icCheckSpots.add(baseLocation.getRelative(Direction.WEST)); } else if (originalState.getType() == BlockTypes.POWERED_REPEATER || originalState.getType() == BlockTypes.UNPOWERED_REPEATER || originalState.getType() == BlockTypes.POWERED_COMPARATOR || originalState.getType() == BlockTypes.UNPOWERED_COMPARATOR) { //TODO icCheckSpots.add(baseLocation.getRelative(blockSnapshotTransaction.getFinal().getState().get(Keys.DIRECTION).get())); wasPowered = originalState.getType() == BlockTypes.POWERED_REPEATER || originalState.getType() == BlockTypes.POWERED_COMPARATOR; // TODO REMOVE icCheckSpots.add(baseLocation.getRelative(Direction.NORTH)); icCheckSpots.add(baseLocation.getRelative(Direction.SOUTH)); icCheckSpots.add(baseLocation.getRelative(Direction.EAST)); icCheckSpots.add(baseLocation.getRelative(Direction.WEST)); } else if (originalState.getType() == BlockTypes.LEVER) { wasPowered = originalState.get(Keys.POWERED).orElse(false); icCheckSpots.add(baseLocation.getRelative(Direction.NORTH)); icCheckSpots.add(baseLocation.getRelative(Direction.SOUTH)); icCheckSpots.add(baseLocation.getRelative(Direction.EAST)); icCheckSpots.add(baseLocation.getRelative(Direction.WEST)); } else if (originalState.getType() == BlockTypes.REDSTONE_TORCH || originalState.getType() == BlockTypes.UNLIT_REDSTONE_TORCH) { wasPowered = originalState.getType() == BlockTypes.REDSTONE_TORCH; icCheckSpots.add(baseLocation.getRelative(Direction.NORTH)); icCheckSpots.add(baseLocation.getRelative(Direction.SOUTH)); icCheckSpots.add(baseLocation.getRelative(Direction.EAST)); icCheckSpots.add(baseLocation.getRelative(Direction.WEST)); } for (Location<World> location : icCheckSpots) { boolean powered = wasPowered; getIC(location).ifPresent(ic -> { int pin = ic.getPinSet().getPinForLocation(ic, baseLocation); if (pin >= 0) { if (powered != ic.getPinSet().getInput(pin, ic)) { Sponge.getScheduler().createTaskBuilder().execute(ic::trigger).submit(CraftBookPlugin.spongeInst().container); } } }); } }); } @Override public void onThink(Location<World> block) { Optional<IC> icOptional = getIC(block); icOptional.filter(ic -> ic instanceof SelfTriggeringIC) .map(ic -> (SelfTriggeringIC) ic) .ifPresent(SelfTriggeringIC::think); } @Override public boolean isValid(Location<World> location) { return SignUtil.isSign(location) && ICManager.getICType(SignUtil.getTextRaw((Sign) location.getTileEntity().get(), 1)) != null; } public Optional<IC> getIC(Location<World> location) { if (loadedICs.get(location) == null) { if (location.getBlockType() == BlockTypes.WALL_SIGN) { ICType<? extends IC> icType = ICManager.getICType(SignUtil.getTextRaw(location.getTileEntity().get().get(Keys.SIGN_LINES).get().get(1))); if (icType != null) { IC ic = icType.getFactory().createInstance((Location<World>) location); if (ic.getFactory() instanceof SerializedICFactory) { SerializedICData data = location.get(CraftBookKeys.IC_DATA).orElse(null); if (data != null) { ((SerializedICFactory) ic.getFactory()).setData(ic, data); } else { CraftBookPlugin.inst().getLogger().warn("Broken IC at " + location.toString()); location.removeBlock(CraftBookPlugin.spongeInst().getCause().build()); return Optional.empty(); } } ic.load(); loadedICs.put(location, ic); return Optional.of(ic); } } else { return Optional.empty(); } } return Optional.ofNullable(loadedICs.get(location)); } private void createICData(Location<World> block, List<Text> lines, Player player) throws InvalidICException { if (block.getBlockType() == BlockTypes.WALL_SIGN) { ICType<? extends IC> icType = ICManager.getICType(SignUtil.getTextRaw(lines.get(1))); if (icType == null) { throw new InvalidICException("Invalid IC Type"); } IC ic = icType.getFactory().create(player, lines, block); ic.create(player, lines); if (icType.getFactory() instanceof SerializedICFactory) { block.offer(new ICData(((SerializedICFactory) icType.getFactory()).getData(ic))); } Sponge.getScheduler().createTaskBuilder().execute(task -> { ic.load(); if (ic instanceof SelfTriggeringIC && (SignUtil.getTextRaw(lines.get(1)).endsWith("S") || (((SelfTriggeringIC) ic).isAlwaysST()))) { ((SpongeSelfTriggerManager) CraftBookPlugin.inst().getSelfTriggerManager().get()).register(this, block); } }).submit(CraftBookPlugin.spongeInst().getContainer()); } else { throw new InvalidICException("Block is not a sign"); } } @Override public String getPath() { return "mechanics/ics/index"; } @Override public ConfigValue<?>[] getConfigurationNodes() { return new ConfigValue[] { maxRadius }; } @Override public String performCustomConversions(String input) { StringBuilder icTable = new StringBuilder(); icTable.append(".. toctree::\n"); icTable.append(" :hidden:\n"); icTable.append(" :glob:\n"); icTable.append(" :titlesonly:\n\n"); icTable.append(" *\n\n"); icTable.append("ICs\n"); icTable.append("===\n\n"); int idLength = "IC ID".length(), shorthandLength = "Shorthand".length(), nameLength = "Name".length(), descriptionLength = "Description".length(), familiesLength = "Family".length(), stLength = "Self Triggering".length(); for(ICType<? extends IC> icType : ICManager.getICTypes()) { if((":doc:`" + icType.getModel() + '`').length() > idLength) idLength = (":doc:`ics/" + icType.getModel() + '`').length(); if(icType.getShorthand().length() > shorthandLength) shorthandLength = icType.getShorthand().length(); if(icType.getName().length() > nameLength) nameLength = icType.getName().length(); if(icType.getDescription().length() > descriptionLength) descriptionLength = icType.getDescription().length(); if(icType.getDefaultPinSet().length() > familiesLength) familiesLength = icType.getDefaultPinSet().length(); if((SelfTriggeringIC.class.isAssignableFrom(icType.getFactory().createInstance(null).getClass()) ? "Yes" : "No").length() > stLength) stLength = (SelfTriggeringIC.class.isAssignableFrom(icType.getFactory().createInstance(null).getClass()) ? "Yes" : "No").length(); } String border = createStringOfLength(idLength, '=') + ' ' + createStringOfLength(shorthandLength, '=') + ' ' + createStringOfLength(nameLength, '=') + ' ' + createStringOfLength(descriptionLength, '=') + ' ' + createStringOfLength(familiesLength, '=') + ' ' + createStringOfLength(stLength, '='); icTable.append(border).append('\n'); icTable.append(padToLength("IC ID", idLength + 1)) .append(padToLength("Shorthand", shorthandLength + 1)) .append(padToLength("Name", nameLength + 1)) .append(padToLength("Description", descriptionLength + 1)) .append(padToLength("Family", familiesLength + 1)) .append(padToLength("Self Triggering", stLength + 1)) .append('\n'); icTable.append(border).append('\n'); for(ICType<? extends IC> icType : ICManager.getICTypes()) { icTable.append(padToLength(":doc:`" + icType.getModel() + '`', idLength + 1)) .append(padToLength(icType.getShorthand(), shorthandLength + 1)) .append(padToLength(icType.getName(), nameLength + 1)) .append(padToLength(icType.getDescription(), descriptionLength + 1)) .append(padToLength(icType.getDefaultPinSet(), familiesLength + 1)) .append(padToLength((SelfTriggeringIC.class.isAssignableFrom(icType.getFactory().createInstance(null).getClass()) ? "Yes" : "No"), stLength + 1)) .append('\n'); } icTable.append(border).append('\n'); return IC_TABLE_PATTERN.matcher(input).replaceAll(Matcher.quoteReplacement(icTable.toString())); } }