Redstone change events: working out who did it

Discussion in 'Plugin Development' started by desht, Oct 24, 2011.

Thread Status:
Not open for further replies.
  1. Offline

    desht

    Here's a tricky one, and there probably is no perfect solution, just a best guess: the BlockRedstoneChange event doesn't provide a way to get the player, which makes sense, since the redstone change could come from a highly indirect source (and a player may not even be involved at all - e.g. an automated change from a plugin like RedstoneChips).

    But I'm looking for ways of tracing back to the player that ultimately triggered the event, if any. I suspect this could be highly tricky and I'm wondering if anyone else has considered this problem in the past?

    One possibility might be to record the last switch change for each player (lever, plate, button), along with the time and the location. Then when a redstone change event is received, trace the redstone leading from the change event's location, and see if it leads to a switch recently (say in the last couple of seconds) triggered by a player. But I can see ways where this might fail or give a false positive. It could also be quite CPU-heavy if there are complex redstone circuits involved.

    Any other thoughts?
     
  2. Offline

    bergerkiller

    @desht When powering a wire, or any other element, a redstone change event is given. You can check if the block that has a change, received it from a previously set block. You can use the player interact and player move events to mark blocks as origin of a player, and once a change is given, set the owner of this changed block to the owner of the block it came from.

     
  3. Offline

    desht

    Thanks, but the only relevant event I can see is the BlockRedstoneEvent - that lets me check the old and new power of the block, but that's all - I don't see how I would check where the change was received from...?
     
  4. Offline

    bergerkiller

    @desht there you need to add some alghorithmic coding. (which I am doing as I write Redstone Mania)

    You basically follow these rules:
    It's pretty hard to do it all, so I'll post what I made for Redstone Mania so far:

    Code:
    package com.bergerkiller.bukkit.rm.element;
    
    import java.util.ArrayList;
    
    import org.bukkit.Material;
    import org.bukkit.block.Block;
    import org.bukkit.block.BlockFace;
    
    import com.bergerkiller.bukkit.rm.Util;
    
    /**
     * Used during circuit creation to get the input and outputs of a solid block
     * IS NOT USED IN THE ACTUAL CIRCUITS!
     */
    public class SolidComponent extends Redstone {
        /*
         * inputs: torches, wires and repeaters supplying power to outputs and wires
         * outputs: torches, wires and repeaters receiving input from this solid block
         */
        public ArrayList<Block> inputs = new ArrayList<Block>();
        public ArrayList<Block> outputs = new ArrayList<Block>();
    
        public boolean hasInput(Block block) {
            for (Block b : inputs) {
                if (Util.equals(b, block)) return true;
            }
            return false;
        }
    
        public SolidComponent(Block block) {
            //initialize the blocks
            //Is the block below a redstone torch or wire?
            Block below = block.getRelative(BlockFace.DOWN);
            Material belowtype = below.getType();
            if (belowtype == Material.REDSTONE_TORCH_ON || belowtype == Material.REDSTONE_TORCH_OFF) {
                inputs.add(below);
            } else if (belowtype == Material.REDSTONE_WIRE) {
                outputs.add(below);
            }
            //Check all sides and up for torches
            for (BlockFace face : Util.dome) {
                Block b = block.getRelative(face);
                Material type = b.getType();
                if (type == Material.REDSTONE_TORCH_ON || type == Material.REDSTONE_TORCH_OFF) {
                    if (Util.isAttached(b, block)) {
                        //we found an attached torch
                        outputs.add(b);
                    }
                } else if (type == Material.REDSTONE_WIRE) {
                    if (face == BlockFace.UP) {
                        //wire ontop - acts as input
                        inputs.add(b);
                        outputs.add(b);
                    } else {
                        //pointing towards the block or not?
                        outputs.add(b);
                        if (!isDistracted(b, face)) {
                            inputs.add(b);
                        }
                    }
                } else if (type == Material.DIODE_BLOCK_ON || type == Material.DIODE_BLOCK_OFF) {
                    BlockFace facing = Util.getFacing(b, type);
                    //supplying or receiving, or none?
                    if (facing == face) {
                        //receiving
                        outputs.add(b);
                    } else if (facing.getOppositeFace() == face) {
                        //supplying
                        inputs.add(b);
                    }
                }
            }
        }
    
        private static boolean isDistracting(Material type) {
            return type == Material.REDSTONE_WIRE || type == Material.REDSTONE_TORCH_ON || type == Material.REDSTONE_TORCH_OFF;
        }
        private static boolean isDistractingColumn(Block main, BlockFace face) {
            Block side = main.getRelative(face);
            Material type = side.getType();
            if (isDistracting(type)) {
                return true;
            } else if (type == Material.AIR) {
                //check level below
                if (isDistracting(side.getRelative(BlockFace.DOWN).getType())) {
                    return true;
                }
            } else if (type == Material.DIODE_BLOCK_ON || type == Material.DIODE_BLOCK_OFF) {
                //powered by repeater?
                BlockFace facing = Util.getFacing(side, Material.DIODE_BLOCK_ON);
                return facing == face;
            }
            //check level on top
            return isDistracting(side.getRelative(BlockFace.UP).getType());
        }
        private static boolean isDistracted(Block wire, BlockFace face) {
            if (face == BlockFace.NORTH) face = BlockFace.SOUTH;
            if (face == BlockFace.EAST) face = BlockFace.WEST;
            BlockFace f1 = (face == BlockFace.SOUTH) ? BlockFace.WEST : BlockFace.SOUTH;
            BlockFace f2 = (face == BlockFace.SOUTH) ? BlockFace.EAST : BlockFace.NORTH;
            return isDistractingColumn(wire, f1) || isDistractingColumn(wire, f2);
        }
    }
    
     
  5. Offline

    desht

    Yep, that was conclusion I had come to :) As it happens, I've been piecing together a similar set of rules myself. Don't forget repeaters too... check for wire/torch in the direction the repeater is facing, but also below any block that a repeater is facing (if tracing back from the event to the source, check for a block above wire with a repeater facing it). Update: I see your code already accounts for repeaters!

    There's also the matter of which blocks actually propagate power; e.g. a stone block above a redstone torch propagates power to neighbouring blocks, but a ladder above the same torch does not.

    You're right, tracing redstone is quite non-trivial...
     
  6. Offline

    bergerkiller

    @desht that snippet is a VERY small portion of the full code. That class is used to get the input and output blocks of a solid block, I also have code to create:
    I'll post it here, since it's open source anyway, but I am afraid it is a bit of a shock to see it at first...
    Code:
    package com.bergerkiller.bukkit.rm.circuit;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.logging.Level;
    
    import org.bukkit.ChatColor;
    import org.bukkit.Material;
    import org.bukkit.block.Block;
    import org.bukkit.block.BlockFace;
    import org.bukkit.entity.Player;
    import org.bukkit.material.Diode;
    
    import com.bergerkiller.bukkit.rm.PlayerSelect;
    import com.bergerkiller.bukkit.rm.Position;
    import com.bergerkiller.bukkit.rm.RedstoneMania;
    import com.bergerkiller.bukkit.rm.RedstoneMap;
    import com.bergerkiller.bukkit.rm.Util;
    import com.bergerkiller.bukkit.rm.RedstoneMap.IntMap;
    import com.bergerkiller.bukkit.rm.element.PhysicalPort;
    import com.bergerkiller.bukkit.rm.element.SolidComponent;
    import com.bergerkiller.bukkit.rm.element.Inverter;
    import com.bergerkiller.bukkit.rm.element.Port;
    import com.bergerkiller.bukkit.rm.element.Redstone;
    import com.bergerkiller.bukkit.rm.element.Repeater;
    
    public class CircuitCreator {
    
        private Player by;
        private RedstoneMap map = new RedstoneMap();
        private ArrayList<Redstone> items = new ArrayList<Redstone>();
        private HashMap<String, CircuitInstance> subcircuits = new HashMap<String, CircuitInstance>();
        private ArrayList<Block> ports = new ArrayList<Block>();
        private HashMap<Position, Integer> delays = new HashMap<Position, Integer>();
        private final int torchdelay = 2;
    
        public CircuitCreator(Player by, PlayerSelect from) {
            this.by = by;
            //prepare the ports, items and delays
            this.delays = from.delays;
            for (Map.Entry<Position, String> entry : from.portnames.entrySet()) {
                Port p = new Port();
                p.name = entry.getValue();
                Block b = entry.getKey().getBlock();
                ports.add(b);
                map.setValue(map.get(b), p);
                items.add(p);
            }
        }
    
        public Circuit create() {
            //generate circuit for ALL ports
            for (Block p : ports) {
                createWire(map.get(p).value, p, Material.REDSTONE_WIRE);
            }
            //save
            Circuit c = new Circuit();
            c.elements = items.toArray(new Redstone[0]);
            c.subcircuits = subcircuits.values().toArray(new CircuitInstance[0]);
            c.initialize();
            return c;
        }
    
        private CircuitInstance getCircuit(CircuitBase from) {
            CircuitInstance cb = (CircuitInstance) from;
            String fullname = cb.getFullName();
            CircuitInstance ci = subcircuits.get(fullname);
            if (ci == null) {
                ci = cb.source.createInstance();
                subcircuits.put(fullname, ci);
            }
            return ci;
        }
    
        private int getDelay(Block b, Material type) {
            int delay = 0;
            Position pos = new Position(b);
            if (delays.containsKey(pos)) {
                delay = delays.get(pos);
            } else if (Util.isRedstoneTorch(type)) {
                delay = 1;
            } else if (Util.isDiode(type)) {
                delay = ((Diode) type.getNewData(b.getData())).getDelay();
            }
            return delay * torchdelay;
        }
    
        private void transfer(Redstone from, Redstone to) {
            if (from == to) return;
            map.merge(from, to);
            from.transfer(to);
            items.remove(from);
        }
    
        private IntMap create(Block block) {
            Material type = block.getType();
            IntMap m = map.get(block);
            if (m.value == null) {
                if (Util.isRedstoneTorch(type)) {
                    map.setValue(m, new Inverter());
                    m.value.setPowered(type == Material.REDSTONE_TORCH_ON);
                    m.value.setDelay(getDelay(block, type));
                    m.value.setPosition(block.getX(), block.getZ());
                    items.add(m.value);
                    createInverter((Inverter) m.value, block, type);
                } else if (Util.isDiode(type)) {
                    map.setValue(m, new Repeater());
                    m.value.setPowered(type == Material.DIODE_BLOCK_ON);
                    m.value.setDelay(getDelay(block, type));
                    m.value.setPosition(block.getX(), block.getZ());
                    items.add(m.value);
                    createRepeater((Repeater) m.value, block, type);
                } else if (type == Material.REDSTONE_WIRE) {
                    map.setValue(m, new Redstone());
                    m.value.setPosition(block.getX(), block.getZ());
                    items.add(m.value);
                    createWire(m.value, block, type);
                } else if (type == Material.LEVER) {
                    //is it a port?
                    Port searchport = Port.get(block);
                    if (searchport != null) {
                        CircuitBase base = searchport.getCircuit();
                        if (base == null) {
                            RedstoneMania.log(Level.SEVERE, "[Creation] Failed to obtain circuit from port '" + searchport.name + "'!");
                        } else {
                            CircuitInstance ci = getCircuit(base);
                            if (ci == null) {
                                RedstoneMania.log(Level.SEVERE, "[Creation] Failed to convert circuit '" + base.getFullName() + "'!");
                            } else {
                                //get the ports of the found circuit
                                Collection<Port> realports = base.getPorts();
                                for (Port realport : realports) {
                                    Port port = ci.getPort(realport.name);
                                    if (port == null) {
                                        RedstoneMania.log(Level.WARNING, "[Creation] Failed to find port '" + realport.name + "' in circuit '" + ci.getFullName() + "'!");
                                    } else {
                                        boolean outofreach = false;
                                        for (PhysicalPort pp : realport.locations) {
                                            Block at = pp.position.getBlock();
                                            if (at == null) {
                                                outofreach = true;
                                            } else {
                                                for (BlockFace leverface : Util.dome) {
                                                    Block lever = at.getRelative(leverface);
                                                    if (lever.getType() == Material.LEVER) {
                                                        m = map.get(lever);
                                                        map.setValue(m, port);
                                                        m.value.setPosition(lever.getX(), lever.getZ());
                                                        createPort((Port) m.value, lever, Material.LEVER);
                                                    }
                                                }
                                            }
                                        }
                                        if (outofreach) {
                                            msg("One or more ports of '" + ci.getFullName() + "' are out of reach!");
                                        }
                                    }
                                }
                            }
                        }
                    }
                } else if (Util.isSolid(type)) {
                    map.setValue(m, new SolidComponent(block));
                    createSolid((SolidComponent) m.value, block, type);
                }
            }
            return m;
        }
    
        private void createPort(Port redstone, Block lever, Material type) {
            for (BlockFace face : Util.bowl) {
                Block b = lever.getRelative(face);
                Material btype = b.getType();
                if (btype == Material.REDSTONE_WIRE) {
                    create(b).value.connect(redstone);
                } else if (Util.isRedstoneTorch(btype)) {
                    create(b).value.connectTo(redstone);
                }
            }
        }
    
        private void createInverter(Inverter redstone, Block inverter, Material type) {
            for (BlockFace face : Util.bowl) {
                Block b = inverter.getRelative(face);
                Material btype = b.getType();
                if (btype == Material.REDSTONE_WIRE) {
                    redstone.connectTo(create(b).value);
                } else if (btype == Material.DIODE_BLOCK_OFF || btype == Material.DIODE_BLOCK_ON) {
                    if (face != BlockFace.DOWN) {
                        //connected to the input?
                        BlockFace facing = Util.getFacing(b, btype);
                        if (facing == face) {
                            redstone.connectTo(create(b).value);
                        }
                    }
                }
            }
            Block above = inverter.getRelative(BlockFace.UP);
            Material abovetype = above.getType();
            if (Util.isSolid(abovetype)) {
                create(above);
            }
            create(Util.getAttachedBlock(inverter));
        }
    
        private void createRepeater(Repeater redstone, Block repeater, Material type) {
            BlockFace facing = Util.getFacing(repeater, type);
            Block output = repeater.getRelative(facing);
            Material outputtype = output.getType();
            if (outputtype == Material.REDSTONE_WIRE) {
                //connect this repeater to wire
                redstone.connectTo(create(output).value);
            } else if (Util.isDiode(outputtype)) {
                BlockFace oface = Util.getFacing(output, outputtype);
                if (facing == oface) {
                    redstone.connectTo(create(output).value);
                }
            } else if (Util.isSolid(outputtype)) {
                create(output);
            }
            Block input = repeater.getRelative(facing.getOppositeFace());
            Material inputtype = repeater.getType();
            if (inputtype == Material.REDSTONE_WIRE) {
                //connect this repeater to wire
                create(input).value.connectTo(redstone);
            } else if (Util.isDiode(inputtype)) {
                BlockFace oface = Util.getFacing(input, inputtype);
                if (facing == oface) {
                    create(input).value.connectTo(redstone);
                }
            } else if (Util.isSolid(inputtype)) {
                create(input);
            }
        }
    
        private Redstone connectWire(Block wire, Redstone redstone) {
            IntMap m = map.get(wire);
            if (m.value == redstone) return redstone;
            if (m.value == null) {
                map.setValue(m, redstone);
                //added block to this wire
                createWire(redstone, wire, Material.REDSTONE_WIRE);
                return redstone;
            } else {
                //merge the two wires
                if (redstone instanceof Port) {
                    if (m.value instanceof Port) {
                        Port p1 = (Port) redstone;
                        Port p2 = (Port) m.value;
                        msg("Port '" + p1.name + "' merged with port '" + p2.name + "'!");
                    }
                    transfer(m.value, redstone);
                    return redstone;
                } else {
                    transfer(redstone, m.value);
                    return m.value;
                }
            }
        }
    
        private void createWire(Redstone redstone, Block wire, Material type) {
            //wire - first find all nearby elements
            for (BlockFace face : Util.radial) {
                Block b = wire.getRelative(face);
                Material btype = b.getType();
                if (btype == Material.REDSTONE_WIRE) {
                    //same wire
                    redstone = connectWire(b, redstone);
                } else if (btype == Material.AIR) {
                    //wire below?
                    Block below = b.getRelative(BlockFace.DOWN);
                    if (below.getType() == Material.REDSTONE_WIRE) {
                        redstone = connectWire(below, redstone);
                    }
                } else if (Util.isRedstoneTorch(btype)) {
                    //this wire receives input from this torch
                    create(b); //we assume that the torch handles direct wire connection
                } else if (Util.isDiode(btype)) {
                    //powering or receiving power
                    BlockFace facing = Util.getFacing(b, btype);
                    if (facing == face) {
                        //wire powers repeater
                        redstone.connectTo(create(b).value);
                    } else if (facing.getOppositeFace() == face) {
                        //repeater powers wire
                        create(b); //we assume that the repeater handles direct wire connections
                    }
                } else if (btype == Material.LEVER) {
                    //let the port handle this
                    create(b);
                } else if (Util.isSolid(btype)) {
                    //wire on top?
                    Block above = b.getRelative(BlockFace.UP);
                    if (above.getType() == Material.REDSTONE_WIRE) {
                        redstone = connectWire(above, redstone);
                    }
                    create(b);
                }
            }
            //update the block this wire sits on
            create(wire.getRelative(BlockFace.DOWN));
            //a torch above this wire?
            Block above = wire.getRelative(BlockFace.UP);
            if (Util.isRedstoneTorch(above)) create(above);
        }
    
        private void createSolid(SolidComponent comp, Block block, Material type) {
            //create block data
            IntMap[] inputs = new IntMap[comp.inputs.size()];
            IntMap[] outputs = new IntMap[comp.outputs.size()];
            for (int i = 0; i < inputs.length; i++) {
                inputs[i] = create(comp.inputs.get(i));
            }
            for (int i = 0; i < outputs.length; i++) {
                outputs[i] = create(comp.outputs.get(i));
            }
            //connect inputs with outputs
            for (IntMap input : inputs) {
                for (IntMap output : outputs) {
                    if (input.value.isType(0, 3)) {
                        if (output.value.isType(0, 3)) {
                            //a wire does NOT power other wires!
                            continue;
                        }
                    }
                    input.value.connectTo(output.value);
                }
            }
        }
    
        private void msg(String message) {
            this.by.sendMessage(ChatColor.YELLOW + message);
        }
    
    }
    
    Also, could be of help to other people too:
    Code:
        public static boolean isSolid(Material type) {
            switch(type) {
            case STONE : return true;
            case COBBLESTONE : return true;
            case GRASS : return true;
            case DIRT : return true;
            case WOOD : return true;
            case LOG : return true;
            case NETHERRACK : return true;
            case IRON_BLOCK : return true;
            case GOLD_BLOCK : return true;
            case DIAMOND_BLOCK : return true;
            case SAND : return true;
            case SANDSTONE : return true;
            case BEDROCK : return true;
            case REDSTONE_ORE : return true;
            case COAL_ORE : return true;
            case DIAMOND_ORE : return true;
            case IRON_ORE : return true;
            case GOLD_ORE : return true;
            case SMOOTH_BRICK : return true;
            case BRICK : return true;
            case CLAY : return true;
            case DOUBLE_STEP : return true;
            case LAPIS_BLOCK : return true;
            case LAPIS_ORE : return true;
              case SPONGE : return true;
              case SNOW : return true;
              case HUGE_MUSHROOM_1 : return true;
              case HUGE_MUSHROOM_2 : return true;
            }
            return false;
        }
    Since I failed to find a method like this in the Bukkit API I made one my self.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 20, 2016
  7. Offline

    desht

    @bergerkiller you've done your homework :)

    Will Redstone Mania offer an API for other plugins to use? I'm looking at redstone support for my ScrollingMenuSign plugin where a change in the powered state of a specific block can be used to trigger a command to be run. Finding out just who caused the power level to change is important to know who to run the command as.

    I'd hate to reimplement all your good work :)
     
  8. Offline

    bergerkiller

    @desht problem is, however, that Redstone Mania is 'virtual'. The code you see here, is used to generate a virtual circuit out of the blocks. This is already working, this is what I get in the log:
    Code:
    19:31:32 [INFO] [Redstone Mania] Logging circuit: inverter.
    19:31:32 [INFO] [Redstone Mania] inverter.[Port 'in' 0]
    19:31:32 [INFO] [Redstone Mania]     Supplies output for: inverter.[Inverter 2]
    19:31:32 [INFO] [Redstone Mania] inverter.[Port 'out' 1]
    19:31:32 [INFO] [Redstone Mania]     Receives input from: inverter.[Inverter 2]
    19:31:32 [INFO] [Redstone Mania] inverter.[Inverter 2]
    19:31:32 [INFO] [Redstone Mania]     Receives input from: inverter.[Port 'in' 0]
    19:31:32 [INFO] [Redstone Mania]     Supplies output for: inverter.[Port 'out' 1]
    It is a very complicated system, using various 'elements' that communicate with each other. It can already make circuits like inverters, xor, repeaters, memory snxor (or smthing), anything really. Still need to implement sub-circuit fixing: at this point I get direct connections between wires, which is obviously not allowed.

    (if two wires are directly connected, they would keep eachother powered)

    In short: it hardly uses the blocks in the end, and the circuit generation code is optimized to work in this node system. It is a bit hard to make it block-only...

    Anyway, the code is up at Github, feel free to modify it as you wish.
    I'll upload the latest version I have here.
     
  9. Offline

    desht

    @bergerkiller Yeah I'd guessed it wouldn't be simple. Sounds like the problem I'm working on will involve some similar algorithms but probably not close enough for any direct code reuse. I'll take a look at your code though - sounds like a very interesting project in any case!
     
  10. Offline

    bergerkiller

    @desht it's in the WIP section, and it is very near to a first WIP release now :)

    Today: I managed to do the following, which is pretty sick to think about it:
    yes, it was a circuit, in a circuit, in a circuit, CircuitCeption!
     
    desht likes this.
Thread Status:
Not open for further replies.

Share This Page