001/* 002 * Trident - A Multithreaded Server Alternative 003 * Copyright 2014 The TridentSDK Team 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package net.tridentsdk.server.world.change; 018 019import net.tridentsdk.base.Position; 020import net.tridentsdk.base.Substance; 021import net.tridentsdk.server.data.RecordBuilder; 022import net.tridentsdk.server.packets.play.out.PacketPlayOutMultiBlockChange; 023import net.tridentsdk.server.player.TridentPlayer; 024import net.tridentsdk.server.world.TridentChunk; 025import net.tridentsdk.server.world.WorldUtils; 026import net.tridentsdk.world.ChunkLocation; 027import net.tridentsdk.world.World; 028import net.tridentsdk.world.MassChange; 029 030import javax.annotation.concurrent.NotThreadSafe; 031import java.util.*; 032 033/** 034 * The default implementation of MassChange, should be suitable for most usage cases 035 * 036 * <p>Should only be used by one thread, if needed by more than one, make two separate changes instead</p> 037 * 038 * <p>Example usage:</p> 039 * <pre>{@code 040 * MassChange change = new DefaultMassChange(...); 041 * change.setBlock(...); 042 * change.setBlock(...); 043 * . 044 * . 045 * . 046 * change.commitChanges()}</pre> 047 */ 048@NotThreadSafe 049public class DefaultMassChange implements MassChange { 050 private final World world; 051 private boolean committed = false; 052 private List<BlockChange> changes = new LinkedList<>(); 053 054 public DefaultMassChange(World world) { 055 this.world = world; 056 } 057 058 @Override 059 public void setBlock(int x, int y, int z, int id) throws IllegalStateException { 060 setBlock(x, y, z, id, (byte) 0); 061 } 062 063 @Override 064 public void setBlock(int x, int y, int z, Substance substance) throws IllegalStateException { 065 setBlock(x, y, z, substance.id(), (byte) 0); 066 } 067 068 @Override 069 public void setBlock(int x, int y, int z, Substance substance, byte data) throws IllegalStateException { 070 setBlock(x, y, z, substance.id(), data); 071 } 072 073 @Override 074 public void setBlock(int x, int y, int z, int id, byte data) throws IllegalStateException { 075 // real function 076 077 if(committed) { 078 throw new IllegalArgumentException("Change has already been committed."); 079 } 080 changes.add(new BlockChange(x, y, z, (byte) id, data)); 081 } 082 083 084 @Override 085 public void setBlock(Position coords, int id) throws IllegalStateException { 086 setBlock(coords, id, (byte) 0); 087 } 088 089 @Override 090 public void setBlock(Position coords, int id, byte data) throws IllegalArgumentException, 091 IllegalStateException { 092 if (coords.world().equals(this.world)) { 093 setBlock((int) Math.round(coords.x()), 094 (int) Math.round(coords.y()), 095 (int) Math.round(coords.z()), id, data); 096 } else { 097 throw new IllegalArgumentException("PositionWritable provided do not match the world that this change is for"); 098 } 099 } 100 101 @Override 102 public void setBlock(Position coords, Substance substance) throws IllegalArgumentException, 103 IllegalStateException { 104 setBlock(coords, substance, (byte) 0); 105 } 106 107 @Override 108 public void setBlock(Position coords, Substance substance, byte data) throws IllegalArgumentException, 109 IllegalStateException { 110 setBlock(coords, substance.id(), data); 111 } 112 113 @Override 114 public boolean commitChanges() throws IllegalStateException { 115 if(committed) { 116 throw new IllegalArgumentException("Change has already been committed."); 117 } 118 119 Map<ChunkLocation, List<BlockChange>> map = new HashMap<>(); 120 121 for(BlockChange change : changes) { 122 ChunkLocation location = WorldUtils.chunkLocation(change.x(), change.z()); 123 List<BlockChange> updatedChanges = map.get(location); 124 125 if(updatedChanges == null) { 126 updatedChanges = new ArrayList<>(); 127 } 128 129 updatedChanges.add(change); 130 map.put(location, updatedChanges); 131 } 132 133 for(Map.Entry<ChunkLocation, List<BlockChange>> entry : map.entrySet()) { 134 List<BlockChange> changes = entry.getValue(); 135 PacketPlayOutMultiBlockChange packet = new PacketPlayOutMultiBlockChange(); 136 RecordBuilder[] records = new RecordBuilder[changes.size()]; 137 TridentChunk chunk = (TridentChunk) world.chunkAt(entry.getKey(), false); 138 139 for (int i = 0; i < records.length; i++) { 140 BlockChange change = changes.get(i); 141 142 records[i] = new RecordBuilder().setBlockId(change.id()) 143 .setX((byte) change.x()) 144 .setY((byte) change.y()) 145 .setZ((byte) change.z()) 146 .setData(change.data()); 147 chunk.setAt(change.x(), change.y(), change.z(), Substance.fromId(change.id()), 148 change.data(), (byte) 255, (byte) 15); 149 } 150 151 packet.set("records", records).set("chunkLocation", entry.getKey()); 152 TridentPlayer.sendAll(packet); 153 } 154 155 return true; 156 } 157}