/
RuneEntityMountain.java
377 lines (359 loc) · 13.2 KB
/
RuneEntityMountain.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
package xilef11.mc.runesofwizardry_classics.runes.entity;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import com.zpig333.runesofwizardry.api.RuneEntity;
import com.zpig333.runesofwizardry.core.WizardryRegistry;
import com.zpig333.runesofwizardry.tileentity.TileEntityDustActive;
import com.zpig333.runesofwizardry.tileentity.TileEntityDustActive.BeamType;
import com.zpig333.runesofwizardry.tileentity.TileEntityDustPlaced;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.monster.EntityIronGolem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import xilef11.mc.runesofwizardry_classics.Refs;
import xilef11.mc.runesofwizardry_classics.RunesofWizardry_Classics;
import xilef11.mc.runesofwizardry_classics.items.EnumDustTypes;
import xilef11.mc.runesofwizardry_classics.runes.RuneMountain;
import xilef11.mc.runesofwizardry_classics.utils.Utils;
import xilef11.mc.runesofwizardry_classics.utils.Utils.Coords;
/* Behaviour:
* (DELiftTerrain)
* lifts an area delimited by clay blocks (which must be connected to the rune)
* can't test original, has a tendency to crash the game
*
* dust type defines height:
* plant: 12
* gunpowder: 16
* lapis: 22
* blaze: 32
*
*/
public class RuneEntityMountain extends RuneEntity {
public RuneEntityMountain(ItemStack[][] actualPattern, EnumFacing facing,
Set<BlockPos> dusts, TileEntityDustActive entity, RuneMountain creator) {
//we will need to add to the dusts, so make a new set that accepts the "add" operation
super(actualPattern, facing, new HashSet<>(dusts), entity, creator);
}
public static final int TICKRATE = 32;
private boolean gotGolem=false;
private int ticksSinceGolem=0;
private int height=0;
private LinkedHashSet<BlockPos> initialPos;
private int[] currentHeight;
@Override
public void onRuneActivatedbyPlayer(EntityPlayer player,ItemStack[] sacrifice, boolean negated) {
World world=player.world;
if(!world.isRemote){
if(!negated && !Utils.takeXP(player, 10)){
this.onPatternBrokenByPlayer(player);
return;
}
Coords c = ((RuneMountain)creator).getVariableDusts().iterator().next();
switch(EnumDustTypes.getByMeta(this.placedPattern[c.row][c.col].getMetadata())){
case PLANT:height=12;
break;
case GUNPOWDER:height=16;
break;
case LAPIS:height=22;
break;
case BLAZE:height=32;
break;
default:RunesofWizardry_Classics.log().error("Wrong Dust Type in RuneEntityMountain#onRuneActivatedByPlayer");
break;
}
entity.setupStar(0xFFFF00, 0xFFFFFF);
entity.setupBeam(0, BeamType.SPIRAL);
entity.setDrawStar(true);
BlockPos down = getPos().down();
initialPos = findArea(world,down);
//make sure the active dust column is the last one
if(initialPos==null){
player.sendMessage(new TextComponentTranslation(Refs.Lang.RUNE+".mountain.noarea"));
this.onPatternBrokenByPlayer(player);
return;
}
if(initialPos.contains(down)){
initialPos.remove(down);
initialPos.add(down);
}
currentHeight = new int[initialPos.size()];
Arrays.fill(currentHeight, 0);
if(negated){
gotGolem=true;
entity.setDrawBeam(true);
}
}
}
/* (non-Javadoc)
* @see com.zpig333.runesofwizardry.api.RuneEntity#handleEntityCollision(net.minecraft.world.World, net.minecraft.util.math.BlockPos, net.minecraft.block.state.IBlockState, net.minecraft.entity.Entity)
*/
@Override
public boolean handleEntityCollision(World worldIn, BlockPos pos,IBlockState state, Entity entityIn) {
if(!worldIn.isRemote&&!gotGolem){
if(entityIn instanceof EntityIronGolem){
gotGolem=true;
((EntityIronGolem) entityIn).setDead();
entity.setDrawBeam(true);
}
}
return super.handleEntityCollision(worldIn, pos, state, entityIn);
}
@Override
public void update() {
World world = entity.getWorld();
if(!world.isRemote && gotGolem){
ticksSinceGolem++;
if(ticksSinceGolem<TICKRATE*2)return;
if(ticksSinceGolem%TICKRATE==0){
Iterator<BlockPos> it = initialPos.iterator();
//for all columns
columns: for(int i=0;i<currentHeight.length&&it.hasNext();i++){
int ch = currentHeight[i];
if(ch>=height)continue;
BlockPos base = it.next();
int bottom = base.getY()-height+ch;
BlockPos nextUp = base.add(0, ch+1, 0);
//special case for the dusts, we want to lift them and not stop.
if(world.getBlockState(nextUp).getBlock()==WizardryRegistry.dust_placed){
//RunesofWizardry_Classics.log().info("Moving placed dust column at "+nextUp);
BlockPos up2=nextUp.up();
//update the posSet of the rune if we will move the dust
if(world.isAirBlock(up2)){
TileEntity te = world.getTileEntity(nextUp);
if(te instanceof TileEntityDustPlaced){
//if(te instanceof TileEntityDustActive)RunesofWizardry_Classics.log().info("active dust");
TileEntityDustPlaced ted = (TileEntityDustPlaced)te;
RuneEntity ent = ted.getRune();
if(ent!=null){
//replace the dust position in the rune with 1 block higher (because it will get moved)
Iterator<BlockPos> dp = ent.dustPositions.iterator();
while(dp.hasNext()){
BlockPos b = dp.next();
if(b.equals(nextUp)){
dp.remove();
break;
}
}
ent.dustPositions.add(up2);
ent.entity.markDirty();
}
}
}
nextUp=up2;
ch++;
}
if(world.isAirBlock(nextUp)){
currentHeight[i]++;//this is here to avoid changing TE data after moving it
entity.markDirty();
//setBlockState calls breakBlock, thus breaking the rune. this is used as a workaround
world.restoringBlockSnapshots=true;
for(int y=ch;y>bottom-base.getY();y--){
BlockPos current = base.add(0,y,0);
IBlockState toPush = world.getBlockState(current);
BlockPos up = current.up();
//move entities
for(Entity e:world.getEntitiesWithinAABB(Entity.class, new AxisAlignedBB(current, current.add(1,2,1)))){
e.setPositionAndUpdate(e.posX, current.getY()+1, e.posZ);
}
if(toPush.getBlock()==Blocks.BEDROCK)continue columns;
world.setBlockState(up, toPush);
TileEntity te = world.getTileEntity(current);
if(te!=null){
NBTTagCompound oldData = te.writeToNBT(new NBTTagCompound());
TileEntity newTE = world.getTileEntity(up);
//see https://github.com/gigaherz/PackingTape/blob/master/src/main/java/gigaherz/packingtape/tape/BlockPackaged.java#L219,L232
if(newTE!=null){
NBTTagCompound merged = new NBTTagCompound();
NBTTagCompound empty = merged.copy();
newTE.writeToNBT(merged);
merged.merge(oldData);
merged.setInteger("x", up.getX());
merged.setInteger("y", up.getY());
merged.setInteger("z", up.getZ());
if (!merged.equals(empty))
{
newTE.readFromNBT(merged);
newTE.markDirty();
}
}
}
world.setBlockToAir(current);
}
world.restoringBlockSnapshots=false;
}
}
if(ticksSinceGolem>=TICKRATE*(height+2))this.onPatternBroken();
}
}
}
/* (non-Javadoc)
* @see com.zpig333.runesofwizardry.api.RuneEntity#readFromNBT(net.minecraft.nbt.NBTTagCompound)
*/
@Override
public void readFromNBT(NBTTagCompound compound) {
super.readFromNBT(compound);
gotGolem = compound.getBoolean("GOLEM");
height = compound.getInteger("HEIGHT");
NBTTagList positions = (NBTTagList) compound.getTag("POSITIONS");
initialPos = new LinkedHashSet<>();
for(int i=0;i<positions.tagCount();i++){
NBTTagCompound coords = positions.getCompoundTagAt(i);
int[] c = coords.getIntArray("Coords");
initialPos.add(new BlockPos(c[0],c[1],c[2]));
}
currentHeight = compound.getIntArray("CURRENT_HEIGHT");
ticksSinceGolem = compound.getInteger("GolemTicks");
}
/* (non-Javadoc)
* @see com.zpig333.runesofwizardry.api.RuneEntity#writeToNBT(net.minecraft.nbt.NBTTagCompound)
*/
@Override
public void writeToNBT(NBTTagCompound compound) {
super.writeToNBT(compound);
compound.setBoolean("GOLEM", gotGolem);
compound.setInteger("HEIGHT", height);
NBTTagList positions = new NBTTagList();
for(BlockPos p:initialPos){
NBTTagCompound coords = new NBTTagCompound();
coords.setIntArray("Coords", new int[]{p.getX(),p.getY(),p.getZ()});
positions.appendTag(coords);
}
compound.setTag("POSITIONS", positions);
compound.setIntArray("CURRENT_HEIGHT", currentHeight);
compound.setInteger("GolemTicks", ticksSinceGolem);
}
private static final int MAX_SEARCH_RANGE=32;
private static LinkedHashSet<BlockPos> findArea(World world, BlockPos initial){
search:for(int offset=0;offset<=MAX_SEARCH_RANGE;offset++){
for(EnumFacing face:EnumFacing.HORIZONTALS){
BlockPos current = initial.offset(face,offset);
if(isMarker(world, current)){
initial=current;
break search;
}
}
if(offset==MAX_SEARCH_RANGE){
RunesofWizardry_Classics.log().warn("Could not find marked area");
return null;
}
}
LinkedHashSet<BlockPos> result = new LinkedHashSet<>();
EdgeResult edge = new EdgeResult(initial.getX(),initial.getZ());
edge = findEdge(world,initial,edge);
RunesofWizardry_Classics.log().info("Found "+edge);
int y = initial.getY();
//add edge to result
for(Integer x:edge.positions.keySet()){
for(Integer z: edge.positions.get(x)){
result.add(new BlockPos(x,y,z));
}
}
//find the area
for(int cx=edge.smallX;cx<=edge.bigX;cx++){
for(int cz = edge.smallZ;cz<=edge.bigZ;cz++){
if(isInside(cx,cz,edge))result.add(new BlockPos(cx,y,cz));
}
}
RunesofWizardry_Classics.log().info("Found "+result.size()+" blocks in area");
return result;
}
private static EdgeResult findEdge(World world, BlockPos currentBlock, EdgeResult edge){
//add the current block to the edge
int currentX=currentBlock.getX(), currentZ=currentBlock.getZ();
//set the extreme positions
if(currentX<edge.smallX)edge.smallX=currentX;
if(currentZ<edge.smallZ)edge.smallZ=currentZ;
if(currentX>edge.bigX)edge.bigX=currentX;
if(currentZ>edge.bigZ)edge.bigZ=currentZ;
Set<Integer> zpos = edge.positions.get(currentX);
if(zpos==null){
zpos=new HashSet<>();
edge.positions.put(currentX, zpos);
}
zpos.add(currentZ);
//find surrounding blocks
//XXX we may want to also look at corners, that might reduce the likeliness of "bad" patterns
for(EnumFacing f:EnumFacing.HORIZONTALS){
BlockPos next = currentBlock.offset(f);
if(isMarker(world, next)){
Set<Integer> zz = edge.positions.get(next.getX());
if(zz==null || !zz.contains(next.getZ())){
findEdge(world, next, edge);
}
}
}
return edge;
}
private static boolean isMarker(World world, BlockPos pos){
return world.getBlockState(pos).getBlock()==Blocks.CLAY;
}
private static boolean isInside(int x, int z, EdgeResult edge){
//note: we must rework this to be 2 non-adjacent blocks...
//i.e adjacent blocks should count as 1 point
//XXX actually, this adds another problem with adjacent blocks...
//might need to switch algorithms here...
//http://stackoverflow.com/questions/14685739/find-all-inner-grid-points-of-a-polygon-made-up-from-neighbouring-grid-points
/*
* XX XX <- this row will be ignored if we don't "merge" adjacent blocks
* XXXXXXX
*
* x x
* x xx x <- this one will also be ignored
* x xxxx x <- this row will be ignored if we merge adjacent blocks
* xxx xxx
*
*/
Set<Integer> zset = edge.positions.get(x);
if(zset==null)return false;
//XXX might be better to do the sort somewhere else (so it's always sorted) instead of re-sorting every block
Integer[] za = new Integer[zset.size()];
za = zset.toArray(za);
Arrays.sort(za);
int smaller=0,larger=0;
for(int i=0;i<za.length;i++){
int cz = za[i];
if(i==0||cz!=(za[i-1]+1)){
if(cz<z)smaller++;
if(cz>z)larger++;
}
}
//for the point to be inside the polygon, the number of points on the edge on both sides (left/right) must be odd http://alienryderflex.com/polygon/
return smaller%2!=0 && larger%2!=0;
}
private static class EdgeResult{
public EdgeResult(int initialX,int initialZ){
smallX=bigX=initialX;
smallZ=bigZ=initialZ;
positions = new HashMap<>();
}
public int smallX,smallZ,bigX,bigZ;
public Map<Integer, Set<Integer>> positions;
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append("Edge: ");
sb.append("x: ").append(smallX).append(" to ").append(bigX);
sb.append(" z: ").append(smallZ).append(" to ").append(bigZ);
int blocks=0;
for(Set<Integer> s:positions.values()){
blocks+=s.size();
}
sb.append(" total blocks: ").append(blocks);
return sb.toString();
}
}
}