Jump to content

Recommended Posts

Posted (edited)

Hi.

 

I'm currently working on some basic cables. I want those cables to work the way that the TileEntityPipeTransferEnergy transfers the energy from TileEntityPipeTransferEnergy A to B. The transfer pipes should be connect

with some cables, but the energy should not go through these cables.

 

So actually, the energy goes directly from point A to B while skipping all cables along the way. This should however only work when point A and B are connected with a cable.

I think Extra Utilities 1 back in 1.7.10 worked also that way, thought not quite sure.

 

Down below I've got my first attempt at doing this, which hasn't been tested yet. My question is know, where and how can I optimize this code to run with maximum possible performance to be able

to have huge machine rooms without huge performance drops (like running MC with 120fps, going into the machine room, MC drops down to 20fps and stuff like that (not only the fps)).

 

How the code currently works:

  • check every 20 ticks the whole network to be sure the cables are still there, for that, check every direction and add checked positions to a list and positions to be checked to a second list, if all possible endings (it might be possible one cable connects 3 or more transfer pipes) are found, stop the scan
  • get the capability and send to every transfer pipe the amount of energy this transfer pipe stores divided by the number of transfer pipes energy should be send to

 

Things to note:

  1. TileEntityPipeTransferEnergy -> transfer pipes
  2. TileEntityEnergy -> base class which creates an Energy Storage for Forge Energy, called container using a wrapper class which differences between producer, consumer and holder just like Tesla (not fully implemented, thought); extends TileEntity implements ITickable
  3. TileEntityPipeEnergy -> the cable, currently no code in there; extends TileEntity (I think there isn't even a use for it being a tile entity, but I haven't changed it yet, in the past it had to be a tile entity, but no longer has to be)
public class TileEntityPipeTransferEnergy extends TileEntityEnergy {
    
    private byte counter = 0;
    private ArrayList<BlockPos> connect = new ArrayList<>(); 
    private ArrayList<BlockPos> copy = new ArrayList<>(); 
    
    public TileEntityPipeTransferEnergy() {
        super(10, 10);
        this.container.setTransferMode(EnergyTransfer.PRODUCER);
    }
    
    @Override
    public void update() {
        boolean flag = false;
        
        ++this.counter;
        if(this.counter > 20 || this.counter < 0) this.counter = 0;
        if(this.counter == 20) {
            flag = true;
            this.findTransferPipes();
        }
        
        for(int i = 0; i <= this.connect.size(); i++) {
            flag = true;
            
            if(this.copy.contains(this.connect.get(i))) continue;
            
            final TileEntity tile = this.getWorld().getTileEntity(this.connect.get(i));
            for(EnumFacing order : EnumFacing.VALUES) {
                if(!tile.isInvalid() && tile.hasCapability(CapabilityEnergy.ENERGY, order)) {
                    IEnergyStorage storage = tile.getCapability(CapabilityEnergy.ENERGY, order);
                    if(storage != null) {
                        this.copy.add(this.connect.get(i));
                        storage.receiveEnergy(this.container.extractEnergy((int)(10 / this.connect.size()), false), false);
                        break;
                    }
                }
            }
        }
        
        if(flag)
            this.markDirty();
    }
    
    private void findTransferPipes() {
        if(this.connect.size() > 0) {
            this.connect.clear();
            this.copy.clear();
        }
        
        LinkedList<BlockPos> toSearch = new LinkedList<>();
        LinkedList<BlockPos> scanned = new LinkedList<BlockPos>();
        scanned.add(this.getPos());
        
        if(toSearch.isEmpty() || toSearch.peek() == null)
            this.getBlocksToScan(toSearch, scanned, this.getPos());
        
        BlockPos curScan = null;
        
        while(toSearch.peek() != null) {
            curScan = toSearch.poll();
            scanned.add(curScan);
            
            if(this.getWorld().getTileEntity(curScan) instanceof TileEntityPipeEnergy) // check cable connection
                this.getBlocksToScan(toSearch, scanned, curScan);
            else if(this.getWorld().getTileEntity(curScan) instanceof TileEntityPipeTransferEnergy &&
                    !this.getPos().equals(curScan) && !scanned.contains(curScan))
                this.connect.add(curScan); // found end of line
        }
    }
    
    private void getBlocksToScan(LinkedList<BlockPos> toSearch, LinkedList<BlockPos> scanned, BlockPos pos) {
        if(!scanned.contains(pos.north())) toSearch.add(pos.north());
        if(!scanned.contains(pos.south())) toSearch.add(pos.south());
        if(!scanned.contains(pos.west())) toSearch.add(pos.west());
        if(!scanned.contains(pos.east())) toSearch.add(pos.east());
        if(!scanned.contains(pos.up())) toSearch.add(pos.up());
        if(!scanned.contains(pos.down())) toSearch.add(pos.down());
    }

    @Override
    public void readFromNBT(NBTTagCompound compound) {
        super.readFromNBT(compound);
        this.container.deserializeNBT(compound.getCompoundTag("Energy"));
    }
    
    @Override
    public NBTTagCompound writeToNBT(NBTTagCompound compound) {
        compound.setTag("Energy", this.container.serializeNBT());
        return super.writeToNBT(compound);
    }
    
    @Override
    public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt) {
        super.onDataPacket(net, pkt);
        this.readFromNBT(pkt.getNbtCompound());
    }
}

 

Also, besides performance, anything else you notice, just post it. Open for feedback of every sort to this code (and I'm sure in nearly every line things can be changed to get better performance etc.), I mean, without feedback you won't learn anything.

 

Thx in advance.

Bektor

 

EDIT:  Updated code:

EDIT 2: Again updated code:

EDIT 3: Updated Code + some problems:

 

Edited by Bektor

Developer of Primeval Forest.

Posted

Hm, how can I pre-calculate the weight for each machine with the cable length? And what would be the best way to store this data (the first thing I would come up with is a HashTable, but not so sure if this is optimal there).

 

And how can I notify a machine when the cable is broken? I mean, currently it doesn't even know to which machines it is connected to and to just move the scan code over into the cable wouldn't gain me anything performance wise. When building a network of cables it would make it even worse.

Developer of Primeval Forest.

Posted

A* is awesome.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Posted

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Posted
On 13.4.2017 at 9:17 PM, Draco18s said:

A* is awesome.

Well, problem is:

    I've got totally no idea how A* can help me with this problem efficiently.

    The only situation I can image where A* is of use and efficient is path finding for an AI.

 

And when I would implement it, wouldn't it result into something similar I've already got?

Developer of Primeval Forest.

Posted

Routing energy through a wirenetwork isn't pathfinding?

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Posted
17 hours ago, Draco18s said:

Routing energy through a wirenetwork isn't pathfinding?

Well, the  transfer pipe knows the location of all other transfer pipes and just teleports the energy to them while making sure that everything is connected via cable.

 

But besides that, I'm wondering where to cache all the data efficiently and how to let the transfer pipe know when a cable is disconnected or connected.

 

Developer of Primeval Forest.

Posted

Ok, I've just finished the new code, here it goes:

 

The transfer pipe for sending and receiving energy:

public class TileEntityPipeTransferEnergy extends TileEntityEnergy {
    
    //private Map<BlockPos, Integer> connected_weight = new HashMap<>(); // with pre-calculated weight, no clue how to do this... :(
    private Set<BlockPos> connected = new HashSet<>(); // without pre-calculated weight
    
    public boolean shouldRecalculate = false;
    
    public TileEntityPipeTransferEnergy() {
        super(10, 10);
        this.container.setTransferMode(EnergyTransfer.PRODUCER);
    }
    
    @Override
    public void update() {
        if(this.shouldRecalculate) {
            this.shouldRecalculate = false;
            this.findTransferPipes();
        }
        
        Iterator<BlockPos> it = connected.iterator();
        while(it.hasNext()) {
            final TileEntity tile = this.getWorld().getTileEntity(it.next());
            
            for(EnumFacing order : EnumFacing.VALUES) {
                IEnergyStorage storage = tile.getCapability(CapabilityEnergy.ENERGY, order);
                if(storage != null) {
                    storage.receiveEnergy(this.container.extractEnergy((int)(10 / this.connected.size()), false), false);
                    break;
                }
            }
        }
        
        this.markDirty();
    }
    
    private void findTransferPipes() {
        if(this.connected.size() > 0)
            this.connected.clear();
        
        LinkedList<BlockPos> toSearch = new LinkedList<>();
        LinkedList<BlockPos> scanned = new LinkedList<BlockPos>();
        scanned.add(this.getPos());
        
        if(toSearch.isEmpty() || toSearch.peek() == null)
            this.getBlocksToScan(toSearch, scanned, this.getPos());
        
        BlockPos curScan = null;
        
        while(toSearch.peek() != null) {
            curScan = toSearch.poll();
            scanned.add(curScan);
            
            if(this.getWorld().getTileEntity(curScan) instanceof TileEntityPipeEnergy) // check cable connection
                this.getBlocksToScan(toSearch, scanned, curScan);
            else if(this.getWorld().getTileEntity(curScan) instanceof TileEntityPipeTransferEnergy &&
                    !this.getPos().equals(curScan) && !scanned.contains(curScan))
                this.connected.add(curScan); // found end of line
        }
    }
    
    private void getBlocksToScan(LinkedList<BlockPos> toSearch, LinkedList<BlockPos> scanned, BlockPos pos) {
        if(!scanned.contains(pos.north())) toSearch.add(pos.north());
        if(!scanned.contains(pos.south())) toSearch.add(pos.south());
        if(!scanned.contains(pos.west())) toSearch.add(pos.west());
        if(!scanned.contains(pos.east())) toSearch.add(pos.east());
        if(!scanned.contains(pos.up())) toSearch.add(pos.up());
        if(!scanned.contains(pos.down())) toSearch.add(pos.down());
    }

    @Override
    public void readFromNBT(NBTTagCompound compound) {
        super.readFromNBT(compound);
        this.container.deserializeNBT(compound.getCompoundTag("Energy"));
    }
    
    @Override
    public NBTTagCompound writeToNBT(NBTTagCompound compound) {
        compound.setTag("Energy", this.container.serializeNBT());
        return super.writeToNBT(compound);
    }
    
    @Override
    public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt) {
        super.onDataPacket(net, pkt);
        this.readFromNBT(pkt.getNbtCompound());
    }
}

 

The cable for just beeing there and give the player the illusion to transfer the energy :P

public class BlockCable extends Block /*implements ITileEntityProvider*/ {
	
    private Set<BlockPos> connected = new HashSet<>(); // connection to transfer pipes
    
	public BlockCable() {
		super(Material.IRON);
		this.setHardness(1.5f);
		this.setResistance(0.f);
		
		this.setCreativeTab(ModCreativeTabs.mcpowerTab);
	}
	
	@Override
	public void onBlockAdded(World worldIn, BlockPos pos, IBlockState state) {
	    super.onBlockAdded(worldIn, pos, state);
	    
	    this.findTransferPipes(worldIn, pos);
	}
	
    private void findTransferPipes(World worldIn, BlockPos pos) {
        if(this.connected.size() > 0)
            this.connected.clear();
        
        LinkedList<BlockPos> toSearch = new LinkedList<>();
        LinkedList<BlockPos> scanned = new LinkedList<BlockPos>();
        scanned.add(pos);
        
        if(toSearch.isEmpty() || toSearch.peek() == null)
            this.getBlocksToScan(toSearch, scanned, pos);
        
        BlockPos curScan = null;
        
        while(toSearch.peek() != null) {
            curScan = toSearch.poll();
            scanned.add(curScan);
            
            if(worldIn.getTileEntity(curScan) instanceof TileEntityPipeEnergy) // check cable connection
                this.getBlocksToScan(toSearch, scanned, curScan);
            else if(worldIn.getTileEntity(curScan) instanceof TileEntityPipeTransferEnergy &&
                    !pos.equals(curScan) && !scanned.contains(curScan))
                this.connected.add(curScan); // found end of line
        }
    }
    
    private void getBlocksToScan(LinkedList<BlockPos> toSearch, LinkedList<BlockPos> scanned, BlockPos pos) {
        if(!scanned.contains(pos.north())) toSearch.add(pos.north());
        if(!scanned.contains(pos.south())) toSearch.add(pos.south());
        if(!scanned.contains(pos.west())) toSearch.add(pos.west());
        if(!scanned.contains(pos.east())) toSearch.add(pos.east());
        if(!scanned.contains(pos.up())) toSearch.add(pos.up());
        if(!scanned.contains(pos.down())) toSearch.add(pos.down());
    }
	
	@Override
	public void breakBlock(World worldIn, BlockPos pos, IBlockState state) {
	    super.breakBlock(worldIn, pos, state);
	    
	    this.recalculate(worldIn);
	    this.findTransferPipes(worldIn, pos);
	}
	
	private void recalculate(World worldIn) {
        Iterator<BlockPos> it = this.connected.iterator();
        while(it.hasNext()) {
            TileEntityPipeTransferEnergy tile = (TileEntityPipeTransferEnergy) worldIn.getTileEntity(it.next());
            if(tile != null)
                tile.shouldRecalculate = true;
        }
    }

    @Override
	public boolean isSideSolid(IBlockState base_state, IBlockAccess world, BlockPos pos, EnumFacing side) {
		return super.isSideSolid(base_state, world, pos, side);
	}
	
	@Override
	public boolean canBeReplacedByLeaves(IBlockState state, IBlockAccess world, BlockPos pos) {
		return false;
	}
	
	/*@Override
	public TileEntity createNewTileEntity(World worldIn, int meta) {
		return new TileEntityPipeEnergy();
	}*/
}

 

So, I hope this attempt is better. ;)

 

@diesieben07  The code hopefully includes everything you said, even if I'm not quite sure about the performance gain from this code. Just to notice, I wasn't able to get the pre-calculated weight done as I'm not quite sure how to do it. I also have no clue why a cable needs a tile entity with the system you explained.

Developer of Primeval Forest.

Posted (edited)
19 minutes ago, diesieben07 said:
  • Please don't use LinkedList. It's terrible in every possible way. If you want a Queue (or Stack) use ArrayDeque.
  • The scanned list should be a Set.
  • You should consider using the enhanced for loop instead of iterators. Makes for a lot cleaner code.
  • You cannot store something like connected in the Block class. There is only one instance of this class for the whole block. This is why you might need a TE for the cables.
  • Don't call markDirty every tick...
  • You might want to call getCapability with side null, too.
  • Whats so terrible about LinkedList? And I just thought it would make sense to use something like a linked list because each object is linked to each other. So it might be that it's actually totally useless to use anything like that there.
  • Why should the scanned list be a Set? It's just the same as the toSearch list and was added to make sure the block was not scanned yet, so that I don't scan stuff twice.
  • Hm... don't ask me why I haven't done this and actually did it just 3 lines below.
  • Well, I thought it would be a good idea as a block has methods like onBlockAdded and breakBlock while I don't know any equivalent method for tile entities.
  • changed. Now it updates only when the list gets updated or when the tile entities energy gets updated 
[...]
storage.receiveEnergy(this.container.extractEnergy((int)(10 / this.connected.size()), false), false);
flag = true;
[...]
  • Why do I want to call getCapability with side null? I'm currently just checking all sides for the capability as it might be that not all sides have one, like a solar panel which has only the energy capability for the EnumFacing.DOWN side. Besides of that, I've also done it because I just don't know how the transfer pipe A knows about the side the cable is connected to the transfer pipe B. If I would know this I can even remove this for loop.

 

Edited by Bektor

Developer of Primeval Forest.

Posted
10 minutes ago, diesieben07 said:

You need to store it with the position in the connected Set. Either you can use a custom class here (storing both the BlockPos and the side) or you can use a Map<BlockPos, EnumFacing>.

Well, a Map comes up with the problem that I can't just loop through it so easy as I am doing it now and how can I get the position from the HashMap?

 

12 minutes ago, diesieben07 said:

Because the capability might be exposed with side null and not any specific side. In that case your pipe would ignore that capability.

final TileEntity tile = this.getWorld().getTileEntity(pos);
            
            boolean flag1 = true;
            for(EnumFacing order : EnumFacing.VALUES) {
                IEnergyStorage storage = tile.getCapability(CapabilityEnergy.ENERGY, order);
                if(storage != null) {
                    storage.receiveEnergy(this.container.extractEnergy((int)(10 / this.connected.size()), false), false);
                    flag = true;
                    flag1 = false;
                    break;
                }
            }
            if(flag1) {
                IEnergyStorage storage = tile.getCapability(CapabilityEnergy.ENERGY, null);
                if(storage != null) {
                    storage.receiveEnergy(this.container.extractEnergy((int)(10 / this.connected.size()), false), false);
                    flag = true;
                    break;
                }
            }

 

13 minutes ago, diesieben07 said:

Not sure what the method selection has to do with it. If you need the onBlockAdded and breakBlock "events" in the TE, pass them on from the Block class.

 

Well, these methods are required so that my cable can scan through the network for all transfer pipes it is connected to and when it get's removed, it can inform those transfer pipes with just accessing their boolean value (not sure if this is the best way).

 

15 minutes ago, diesieben07 said:

It's just that, a linked list. This data structure

 

15 minutes ago, diesieben07 said:

Because you are often checking if it contains a certain element already. If it's a list

Oh, didn't know those things. Thanks. ;)

Developer of Primeval Forest.

Posted
3 minutes ago, diesieben07 said:

Map::keySet

Hm... The target type of this expression must be a functional interface.

 

6 minutes ago, diesieben07 said:

Map::forEach

How should this statement be implement into the loop?

 

Thought this way it works:

for(Map.Entry<BlockPos, EnumFacing> pos : connected.entrySet()) {

 

Developer of Primeval Forest.

Posted (edited)
1 hour ago, diesieben07 said:
  • Please don't use LinkedList. It's terrible in every possible way. If you want a Queue (or Stack) use ArrayDeque.

Well, ArrayDeque makes in this case even more sense than a normal ArrayList, as it provides some methods which directly allow to get an object and remove it from the list and it makes sure that the first added object get's checked first and not maybe never as the list grows and grows and grows.

 

1 hour ago, diesieben07 said:

Are you serious?

 

Well, when each object is linked to each other the list can grow huge and so it makes sense to actually handle the first added thing first.

 

38 minutes ago, diesieben07 said:

It's a method. You call it, usually with a lambda, and it will call that lambda (or whatever other BiConsumer you give it) for every key-value pair in the Map.

 

Ah, ok. So the BiConsumer is the thing that allows me to do this stuff: (key, value) ->

I better do not ask how this whole BiConsumer stuff actually works. ^^

 

So, now I'm stuck with the findTransferPipes method. I mean, I somehow have to get the direction in which the current search goes there to save the direction (or maybe the opposite direction?) from which the cable comes to the connected map.

Edited by Bektor

Developer of Primeval Forest.

Posted
8 hours ago, diesieben07 said:

You need to change toSearch to contain both BlockPos and an EnumFacing. Then change your getBlocksToScan to something like this:



 

Thx.

What does this Pair code do? (Pair.of and Queue<Pair<BlockPos, EnumFacing>>)

And why do I have to change there the ArrayDeque to Queue<Pair<BlockPos, EnumFacing>>, I mean, what's the difference there, because there is also a

HashMap which stores all that stuff in the main class itself. So why not change the toSearch to HashMap ?

Developer of Primeval Forest.

Posted

Ok. Here is my updated code which should include everything:

Just a small question besides: Does this search code in getBlocksToScan (EnumSet.allOf(EnumFacing.class)) also takes care of the null side which you mentioned some time ago?

Just asking because this side or face is added to the connected list from where it is going to be used for the getCapability check, so that my cable does not ignore the Capability when it's not exposed

to a specific side.

 

transfer pipes:

public class TileEntityPipeTransferEnergy extends TileEntityEnergy {
    
    private boolean flag = false;
    private HashMap<BlockPos, EnumFacing> connected = new HashMap<>(); // without pre-calculated weight
    
    public boolean shouldRecalculate = false;
    
    [...]
    
    @Override
    public void update() {
        this.flag = false;
        if(this.shouldRecalculate) {
            this.shouldRecalculate = false;
            this.findTransferPipes();
            this.flag = true;
        }
        
        this.connected.forEach((pos, side) -> {
            final TileEntity tile = this.getWorld().getTileEntity(pos);
            
            IEnergyStorage storage = tile.getCapability(CapabilityEnergy.ENERGY, side);
            if(storage != null) {
                storage.receiveEnergy(this.container.extractEnergy((int)(10 / this.connected.size()), false), false);
                this.flag = true; // flag has to be a class member because of inner classes
            }
        });
        
        if(this.flag)
            this.markDirty();
    }
    
    private void findTransferPipes() {
        if(this.connected.size() > 0)
            this.connected.clear();
        
        // we want to store the facing and the position
        Queue<Pair<BlockPos, EnumFacing>> toSearch = new ArrayDeque<>();
        HashSet<BlockPos> scanned = new HashSet<BlockPos>();
        scanned.add(this.getPos());
        
        if(toSearch.isEmpty() || toSearch.peek() == null)
            this.getBlocksToScan(toSearch, scanned, this.getPos());
        
        // temp object because we poll stuff out of the list
        Pair<BlockPos, EnumFacing> pair = null;
        BlockPos current = null;
        EnumFacing face = null;
        
        while(toSearch.peek() != null) {
            pair = toSearch.poll();
            current = pair.getLeft();
            face = pair.getRight();
            scanned.add(current);
            
            if(this.getWorld().getTileEntity(current) instanceof TileEntityPipeEnergy) // check cable connection
                this.getBlocksToScan(toSearch, scanned, current);
            else if(this.getWorld().getTileEntity(current) instanceof TileEntityPipeTransferEnergy &&
                    !this.getPos().equals(current) && !scanned.contains(current))
                this.connected.put(current, face); // found end of line
        }
    }
    
    private void getBlocksToScan(Queue<Pair<BlockPos, EnumFacing>> toSearch, HashSet<BlockPos> scanned, BlockPos pos) {
        for(EnumFacing face : EnumSet.allOf(EnumFacing.class)) {
            // same as pos.north(), just for all directions
            BlockPos offset = pos.offset(face);
            if(!scanned.contains(offset))
                // create a new pair and add it to the list
                // a pair is just some class which holds two fields
                toSearch.add(Pair.of(offset, face.getOpposite()));
        }
    }

  [...]
}

 

Cable Block:

public class BlockCable extends Block implements ITileEntityProvider {
    
	[...]
	
	@Override
	public void onBlockAdded(World worldIn, BlockPos pos, IBlockState state) {
	    super.onBlockAdded(worldIn, pos, state);
	    
	    TileEntity tile = worldIn.getTileEntity(pos);
	    if(tile != null && tile instanceof TileEntityPipeEnergy) {
	        TileEntityPipeEnergy cable = (TileEntityPipeEnergy) tile;
	        cable.searchNetwork();
	    }
	}
	
	@Override
	public void breakBlock(World worldIn, BlockPos pos, IBlockState state) {
       TileEntity tile = worldIn.getTileEntity(pos);
        if(tile != null && tile instanceof TileEntityPipeEnergy) {
            TileEntityPipeEnergy cable = (TileEntityPipeEnergy) tile;
            cable.searchNetwork();
        }
        
        super.breakBlock(worldIn, pos, state); // execute code above before the tile entity gets removed
	}
	
	@Override
	public TileEntity createNewTileEntity(World worldIn, int meta) {
		return new TileEntityPipeEnergy();
	}
}

 

Cable TileEntity:

public class TileEntityPipeEnergy extends TileEntity {
	
    private HashSet<BlockPos> connected_machines = new HashSet<>();
    
    public void searchNetwork() {
        if(this.connected_machines.size() > 0)
            this.connected_machines.clear();
        
        ArrayDeque<BlockPos> toSearch = new ArrayDeque<>();
        HashSet<BlockPos> scanned = new HashSet<>();
        
        if(toSearch.isEmpty() || toSearch.peek() == null)
            this.getBlocksToScan(toSearch, scanned, this.getPos());
        
        BlockPos current = null;
        
        while(toSearch.peek() != null) {
            current = toSearch.pop();
            scanned.add(current);
            
            TileEntity tile = this.getWorld().getTileEntity(current);
            if(tile != null) {
                if(tile instanceof TileEntityPipeEnergy)
                    this.getBlocksToScan(toSearch, scanned, current);
                else if(tile instanceof TileEntityPipeTransferEnergy)
                    this.connected_machines.add(current);
            }
        }
    }
    
    private void getBlocksToScan(ArrayDeque<BlockPos> toSearch, HashSet<BlockPos> scanned, BlockPos pos) {
        if(!scanned.contains(pos.north())) toSearch.add(pos.north());
        if(!scanned.contains(pos.south())) toSearch.add(pos.south());
        if(!scanned.contains(pos.west())) toSearch.add(pos.west());
        if(!scanned.contains(pos.east())) toSearch.add(pos.east());
        if(!scanned.contains(pos.up())) toSearch.add(pos.up());
        if(!scanned.contains(pos.down())) toSearch.add(pos.down());
    }
    
    public void informNetwork() {
        this.connected_machines.forEach(pos -> {
            TileEntity tile = this.getWorld().getTileEntity(pos);
            // make sure the tile entity at 'pos' is really the one we think it is
            if(tile != null && tile instanceof TileEntityPipeTransferEnergy) {
                TileEntityPipeTransferEnergy transfer = (TileEntityPipeTransferEnergy) tile;
                transfer.shouldRecalculate = true;
            }
        });
    }
}

 

I somehow think I learned with this one topic here more than in most other topics I created. xD 

Developer of Primeval Forest.

Posted
Just now, diesieben07 said:

Looks alright, and no, EnumSet does not contain null.

Hm, so my pipe will ignore all machines with don't care about the side? How can I add then the null check effeciently again?

Developer of Primeval Forest.

Posted
4 minutes ago, diesieben07 said:

Make a collection that contains null and all sides. To avoid the cost of creating it every time, you can store it in a static final field:

 


private static final EnumFacing[] SIDES = ObjectArrays.concat(null, EnumFacing.values());

 

And then add this to the for-loop in getBlocksToScan instead of EnumSet.allOf(EnumFacing.class), I guess.

Also, I'm getting the following error when using your line of code at that line:

The method concat(EnumFacing, EnumFacing[]) is ambiguous for the type ObjectArrays.

Developer of Primeval Forest.

Posted
1 minute ago, diesieben07 said:

You need to cast null to EnumFacing to help out the compiler. And yes, you would use that field in the for loop.

Ah, ok. Thx. :)

Seems a bit weird to cast null to EnumFacing as  null is null.  ^^

Developer of Primeval Forest.

Posted
33 minutes ago, diesieben07 said:

No, null is not null :P Null is not a special type. A variable of a certain object type (such as EnumFacing) actually is a reference to an object of that type, not the object itself. And null is a special "reference to nothing" placeholder.

And now null is a reference to a nothing EnumFacing. :P I hope it's not quite as complicated as it seems to be the case in C++ with references vs pointers.

But quite interesting that no methods want a cast when there is null, but this method does. xD 

But why references, why not just some kind of pointer like nullptr in C++? (and even when Java want's to tell you that it does not has pointers, it has pointers ^^)

 

Developer of Primeval Forest.

Posted
17 minutes ago, Bektor said:

But quite interesting that no methods want a cast when there is null, but this method does. xD

There are circumstances where casting null is necessary.  Such as when two methods have other wise ambiguous signatures, casting the null makes it unique.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Posted
2 minutes ago, Draco18s said:

There are circumstances where casting null is necessary.  Such as when two methods have other wise ambiguous signatures, casting the null makes it unique.

Ah, ok. Didn't know that.

Developer of Primeval Forest.

Posted
2 minutes ago, diesieben07 said:

References are an abstraction over pointers. Pointers are an actual number, like in C. A Reference is a construct which is implemented using pointers, but does not give you access to those pointers.

Ah, ok. :)

Developer of Primeval Forest.

Posted

Hm, got some problems after adding the energy handling logic (WIP) to all the blocks:

 

  1. @diesieben07 NullPointerException when having null as a side like you suggested.
  2. Energy seems not to get transfered to other the transfer pipe at the end of the cable

 

Transfer Pipe:

Spoiler

    private void getBlocksToScan(Queue<Pair<BlockPos, EnumFacing>> toSearch, HashSet<BlockPos> scanned, BlockPos pos) {
        // EnumSet.allOf(EnumFacing.class) does not include the null, so machines which don't require a specific
        // site will be ignored
        for(EnumFacing face : TileEntityPipeTransferEnergy.SIDES) {
            // same as pos.north(), just for all directions
            BlockPos offset = pos.offset(face); //NPE... with if(face == null) return; no error, but it won't check null (internal) energy stuff
            if(!scanned.contains(offset))
                // create a new pair and add it to the list
                // a pair is just some class which holds two fields
                toSearch.add(Pair.of(offset, face.getOpposite()));
        }
    }

 

 

Spoiler

// in update

        this.connected.forEach((pos, side) -> {
            final TileEntity tile = this.getWorld().getTileEntity(pos);
            if(tile == null) return;
            
            IEnergyStorage storage = tile.getCapability(CapabilityEnergy.ENERGY, side);
            if(storage != null) {
                storage.receiveEnergy(this.container.extractEnergy((int)(10 / this.connected.size()), false), false);
                System.out.println("Transfer at " + pos.getX() + "," + pos.getY() + "," + pos.getZ() + " contains " + this.container.getEnergyStored() + "FE");
                this.flag = true; // flag has to be a class member because of inner classes
            }
        });

 

 

Solar Panel update:

Spoiler

        if(this.world.getTileEntity(this.pos.down()) instanceof TileEntityPipeTransferEnergy) {
            TileEntityPipeTransferEnergy tile = (TileEntityPipeTransferEnergy) world.getTileEntity(getPos().down());
            tile.container.receiveEnergy(this.container.extractEnergy(10, false), false);
        }

 

 

There is also no log output from transfer pipe with syso and when having it outside of the forEach loop, the log tells me the transfer pipe at the beginning has energy, but at the end of the pipe doesn't have energy.

Developer of Primeval Forest.

Posted
6 minutes ago, diesieben07 said:

That was a derp on my part. You only need the null when querying getCapability.

 

With getCapability is the problem that the HashMap does not contains null as I removed it from getBlocksToScan and changed it back to EnumSet.allOf(EnumFacing.class).

This can be seen there:

IEnergyStorage storage = tile.getCapability(CapabilityEnergy.ENERGY, side);

 

And the debug mode tells me that there seems to be a problem with the forEach loop. Everything above the forEach loop get's called, but not even the first line of the forEach loop get's executed.

Developer of Primeval Forest.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • It is 1.12.2 - I have no idea if there is a 1.12 pack
    • Okay, but does the modpack works with 1.12 or just with 1.12.2, because I need the Forge client specifically for Minecraft 1.12, not 1.12.2
    • Version 1.19 - Forge 41.0.63 I want to create a wolf entity that I can ride, so far it seems to be working, but the problem is that when I get on the wolf, I can’t control it. I then discovered that the issue is that the server doesn’t detect that I’m riding the wolf, so I’m struggling with synchronization. However, it seems to not be working properly. As I understand it, the server receives the packet but doesn’t register it correctly. I’m a bit new to Java, and I’ll try to provide all the relevant code and prints *The comments and prints are translated by chatgpt since they were originally in Spanish* Thank you very much in advance No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. MountableWolfEntity package com.vals.valscraft.entity; import com.vals.valscraft.network.MountSyncPacket; import com.vals.valscraft.network.NetworkHandler; import net.minecraft.client.Minecraft; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.animal.Wolf; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.Entity; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.network.PacketDistributor; public class MountableWolfEntity extends Wolf { private boolean hasSaddle; private static final EntityDataAccessor<Byte> DATA_ID_FLAGS = SynchedEntityData.defineId(MountableWolfEntity.class, EntityDataSerializers.BYTE); public MountableWolfEntity(EntityType<? extends Wolf> type, Level level) { super(type, level); this.hasSaddle = false; } @Override protected void defineSynchedData() { super.defineSynchedData(); this.entityData.define(DATA_ID_FLAGS, (byte)0); } public static AttributeSupplier.Builder createAttributes() { return Wolf.createAttributes() .add(Attributes.MAX_HEALTH, 20.0) .add(Attributes.MOVEMENT_SPEED, 0.3); } @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { ItemStack itemstack = player.getItemInHand(hand); if (itemstack.getItem() == Items.SADDLE && !this.hasSaddle()) { if (!player.isCreative()) { itemstack.shrink(1); } this.setSaddle(true); return InteractionResult.SUCCESS; } else if (!level.isClientSide && this.hasSaddle()) { player.startRiding(this); MountSyncPacket packet = new MountSyncPacket(true); // 'true' means the player is mounted NetworkHandler.CHANNEL.sendToServer(packet); // Ensure the server handles the packet return InteractionResult.SUCCESS; } return InteractionResult.PASS; } @Override public void travel(Vec3 travelVector) { if (this.isVehicle() && this.getControllingPassenger() instanceof Player) { System.out.println("The wolf has a passenger."); System.out.println("The passenger is a player."); Player player = (Player) this.getControllingPassenger(); // Ensure the player is the controller this.setYRot(player.getYRot()); this.yRotO = this.getYRot(); this.setXRot(player.getXRot() * 0.5F); this.setRot(this.getYRot(), this.getXRot()); this.yBodyRot = this.getYRot(); this.yHeadRot = this.yBodyRot; float forward = player.zza; float strafe = player.xxa; if (forward <= 0.0F) { forward *= 0.25F; } this.flyingSpeed = this.getSpeed() * 0.1F; this.setSpeed((float) this.getAttributeValue(Attributes.MOVEMENT_SPEED) * 1.5F); this.setDeltaMovement(new Vec3(strafe, travelVector.y, forward).scale(this.getSpeed())); this.calculateEntityAnimation(this, false); } else { // The wolf does not have a passenger or the passenger is not a player System.out.println("No player is mounted, or the passenger is not a player."); super.travel(travelVector); } } public boolean hasSaddle() { return this.hasSaddle; } public void setSaddle(boolean hasSaddle) { this.hasSaddle = hasSaddle; } @Override protected void dropEquipment() { super.dropEquipment(); if (this.hasSaddle()) { this.spawnAtLocation(Items.SADDLE); this.setSaddle(false); } } @SubscribeEvent public static void onServerTick(TickEvent.ServerTickEvent event) { if (event.phase == TickEvent.Phase.START) { MinecraftServer server = net.minecraftforge.server.ServerLifecycleHooks.getCurrentServer(); if (server != null) { for (ServerPlayer player : server.getPlayerList().getPlayers()) { if (player.isPassenger() && player.getVehicle() instanceof MountableWolfEntity) { MountableWolfEntity wolf = (MountableWolfEntity) player.getVehicle(); System.out.println("Tick: " + player.getName().getString() + " is correctly mounted on " + wolf); } } } } } private boolean lastMountedState = false; @Override public void tick() { super.tick(); if (!this.level.isClientSide) { // Only on the server boolean isMounted = this.isVehicle() && this.getControllingPassenger() instanceof Player; // Only print if the state changed if (isMounted != lastMountedState) { if (isMounted) { Player player = (Player) this.getControllingPassenger(); // Verify the passenger is a player System.out.println("Server: Player " + player.getName().getString() + " is now mounted."); } else { System.out.println("Server: The wolf no longer has a passenger."); } lastMountedState = isMounted; } } } @Override public void addPassenger(Entity passenger) { super.addPassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(true)); } } } @Override public void removePassenger(Entity passenger) { super.removePassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is no longer mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(false)); } } } @Override public boolean isControlledByLocalInstance() { Entity entity = this.getControllingPassenger(); return entity instanceof Player; } @Override public void positionRider(Entity passenger) { if (this.hasPassenger(passenger)) { double xOffset = Math.cos(Math.toRadians(this.getYRot() + 90)) * 0.4; double zOffset = Math.sin(Math.toRadians(this.getYRot() + 90)) * 0.4; passenger.setPos(this.getX() + xOffset, this.getY() + this.getPassengersRidingOffset() + passenger.getMyRidingOffset(), this.getZ() + zOffset); } } } MountSyncPacket package com.vals.valscraft.network; import com.vals.valscraft.entity.MountableWolfEntity; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class MountSyncPacket { private final boolean isMounted; public MountSyncPacket(boolean isMounted) { this.isMounted = isMounted; } public void encode(FriendlyByteBuf buffer) { buffer.writeBoolean(isMounted); } public static MountSyncPacket decode(FriendlyByteBuf buffer) { return new MountSyncPacket(buffer.readBoolean()); } public void handle(NetworkEvent.Context context) { context.enqueueWork(() -> { ServerPlayer player = context.getSender(); // Get the player from the context if (player != null) { // Verifies if the player has dismounted if (!isMounted) { Entity vehicle = player.getVehicle(); if (vehicle instanceof MountableWolfEntity wolf) { // Logic to remove the player as a passenger wolf.removePassenger(player); System.out.println("Server: Player " + player.getName().getString() + " is no longer mounted."); } } } }); context.setPacketHandled(true); // Marks the packet as handled } } networkHandler package com.vals.valscraft.network; import com.vals.valscraft.valscraft; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.network.NetworkRegistry; import net.minecraftforge.network.simple.SimpleChannel; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class NetworkHandler { private static final String PROTOCOL_VERSION = "1"; public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( new ResourceLocation(valscraft.MODID, "main"), () -> PROTOCOL_VERSION, PROTOCOL_VERSION::equals, PROTOCOL_VERSION::equals ); public static void init() { int packetId = 0; // Register the mount synchronization packet CHANNEL.registerMessage( packetId++, MountSyncPacket.class, MountSyncPacket::encode, MountSyncPacket::decode, (msg, context) -> msg.handle(context.get()) // Get the context with context.get() ); } }  
    • Do you use features of inventory profiles next (ipnext) or is there a change without it?
  • Topics

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.