-
Notifications
You must be signed in to change notification settings - Fork 755
/
BoxAOEIterator.java
284 lines (260 loc) · 11.1 KB
/
BoxAOEIterator.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
package slimeknights.tconstruct.library.tools.definition.aoe;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.ImmutableList;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import net.minecraft.core.BlockPos;
import net.minecraft.core.BlockPos.MutableBlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import slimeknights.mantle.data.loadable.primitive.IntLoadable;
import slimeknights.mantle.data.loadable.record.RecordLoadable;
import slimeknights.tconstruct.library.tools.definition.aoe.IBoxExpansion.ExpansionDirections;
import slimeknights.tconstruct.library.tools.nbt.IToolStackView;
import slimeknights.tconstruct.tools.TinkerModifiers;
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
/**
* AOE harvest logic that mines blocks in a rectangle
* @param base Base size of the AOE
* @param expansions Values to boost the size by for each expansion
* @param direction Direction for expanding
*/
public record BoxAOEIterator(BoxSize base, List<BoxSize> expansions, IBoxExpansion direction) implements IAreaOfEffectIterator {
public static final RecordLoadable<BoxAOEIterator> LOADER = RecordLoadable.create(
BoxSize.LOADER.defaultField("bonus", BoxSize.ZERO, BoxAOEIterator::base),
BoxSize.LOADER.list(0).defaultField("expansions", List.of(), BoxAOEIterator::expansions),
IBoxExpansion.REGISTRY.requiredField("expansion_direction", BoxAOEIterator::direction),
BoxAOEIterator::new);
/** Creates a builder for this iterator */
public static BoxAOEIterator.Builder builder(int width, int height, int depth) {
return new Builder(new BoxSize(width, height, depth));
}
@Override
public RecordLoadable<BoxAOEIterator> getLoader() {
return LOADER;
}
/** Gets the size for a given level of expanded */
private BoxSize sizeFor(int level) {
int size = expansions.size();
if (level == 0 || size == 0) {
return base;
}
int width = base.width;
int height = base.height;
int depth = base.depth;
// if we have the number of expansions or more, add in all expansions as many times as needed
if (level >= size) {
int cycles = level / size;
for (BoxSize expansion : expansions) {
width += expansion.width * cycles;
height += expansion.height * cycles;
depth += expansion.depth * cycles;
}
}
// partial iteration through the list for the remaining expansions
int remainder = level % size;
for (int i = 0; i < remainder; i++) {
BoxSize expansion = expansions.get(i);
width += expansion.width;
height += expansion.height;
depth += expansion.depth;
}
return new BoxSize(width, height, depth);
}
@Override
public Iterable<BlockPos> getBlocks(IToolStackView tool, ItemStack stack, Player player, BlockState state, Level world, BlockPos origin, Direction sideHit, AOEMatchType matchType) {
// expanded gives an extra width every odd level, and an extra height every even level
int expanded = tool.getModifierLevel(TinkerModifiers.expanded.getId());
return calculate(tool, stack, world, player, origin, sideHit, sizeFor(expanded), direction, matchType);
}
/**
*
* @param tool Tool used for harvest
* @param stack Item stack used for harvest (for vanilla hooks)
* @param world World containing the block
* @param player Player harvesting
* @param origin Center of harvest
* @param sideHit Block side hit
* @param extraSize Extra size to iterate
* @param matchType Type of harvest being performed
* @return List of block positions
*/
public static Iterable<BlockPos> calculate(IToolStackView tool, ItemStack stack, Level world, Player player, BlockPos origin, Direction sideHit, BoxSize extraSize, IBoxExpansion expansionDirection, AOEMatchType matchType) {
// skip if no work
if (extraSize.isZero()) {
return Collections.emptyList();
}
ExpansionDirections expansion = expansionDirection.getDirections(player, sideHit);
Predicate<BlockPos> posPredicate = IAreaOfEffectIterator.defaultBlockPredicate(tool, stack, world, origin, matchType);
return () -> new RectangleIterator(origin, expansion.width(), extraSize.width, expansion.height(), extraSize.height, expansion.traverseDown(), expansion.depth(), extraSize.depth, posPredicate);
}
/** Iterator used for getting the blocks */
public static class RectangleIterator extends AbstractIterator<BlockPos> {
/** Primary direction of iteration */
private final Direction widthDir;
/** Secondary direction of iteration, mostly interchangeable with primary */
private final Direction heightDir;
/** Direction of iteration away from the player */
private final Direction depthDir;
/* Bounding box size in the direction of width */
private final int maxWidth;
/* Bounding box size in the direction of height */
private final int maxHeight;
/* Bounding box size in the direction of depth */
private final int maxDepth;
/** Current position in the direction of width */
private int currentWidth = 0;
/** Current position in the direction of height */
private int currentHeight = 0;
/** Current position in the direction of depth */
private int currentDepth = 0;
/** Original position, skipped in iteration */
protected final BlockPos origin;
/** Position modified as we iterate */
protected final BlockPos.MutableBlockPos mutablePos;
/** Predicate to check before returning a position */
protected final Predicate<BlockPos> posPredicate;
/** Last returned values for the three coords */
protected int lastX, lastY, lastZ;
/**
* Iterates through a rectangular solid
* @param origin Center position
* @param widthDir Direction for width traversal
* @param extraWidth Radius in width direction
* @param heightDir Direction for height traversal
* @param extraHeight Amount in the height direction
* @param traverseDown If true, navigates extraHeight both up and down
* @param depthDir Direction to travel backwards
* @param extraDepth Extra amount to traverse in the backwards direction
* @param posPredicate Predicate to validate positions
*/
public RectangleIterator(BlockPos origin, Direction widthDir, int extraWidth, Direction heightDir, int extraHeight, boolean traverseDown, Direction depthDir, int extraDepth, Predicate<BlockPos> posPredicate) {
this.origin = origin;
this.widthDir = widthDir;
this.heightDir = heightDir;
this.depthDir = depthDir;
this.maxWidth = extraWidth * 2;
this.maxHeight = traverseDown ? extraHeight * 2 : extraHeight;
this.maxDepth = extraDepth;
// start 1 block before start on the correct axis
// computed values
this.mutablePos = new MutableBlockPos(origin.getX(), origin.getY(), origin.getZ());
this.posPredicate = posPredicate;
// offset position back by 1 so we start at 0, 0, 0
if (extraWidth > 0) {
currentWidth--;
} else if (extraHeight > 0) {
currentHeight--;
}
// offset the mutable position back along the rectangle
this.mutablePos.move(widthDir, -extraWidth + currentWidth);
if (traverseDown) {
this.mutablePos.move(heightDir, -extraHeight + currentHeight);
} else if (currentHeight != 0) {
this.mutablePos.move(heightDir, currentHeight);
}
this.lastX = this.mutablePos.getX();
this.lastY = this.mutablePos.getY();
this.lastZ = this.mutablePos.getZ();
}
/**
* Updates the mutable block position
* @return False if at the end of data
*/
protected boolean incrementPosition() {
// first, increment values
// if at the end of the width, increment height
if (currentWidth == maxWidth) {
// at the end of the height, increment depth
if (currentHeight == maxHeight) {
// at the end of depth, we are done
if (currentDepth == maxDepth) {
return false;
}
// increase depth
currentDepth++;
mutablePos.move(depthDir);
// reset height
currentHeight = 0;
mutablePos.move(heightDir, -maxHeight);
} else {
currentHeight++;
mutablePos.move(heightDir);
}
currentWidth = 0;
mutablePos.move(widthDir, -maxWidth);
} else {
currentWidth++;
mutablePos.move(widthDir);
}
return true;
}
@Override
protected BlockPos computeNext() {
// ensure the position did not get changed by the consumer last time
mutablePos.set(lastX, lastY, lastZ);
// as long as we have another position, try using it
while (incrementPosition()) {
// skip over the origin, ensure it matches the predicate
if (!mutablePos.equals(origin) && posPredicate.test(mutablePos)) {
// store position in case the consumer changes it
lastX = mutablePos.getX();
lastY = mutablePos.getY();
lastZ = mutablePos.getZ();
return mutablePos;
}
}
return endOfData();
}
}
/** Record encoding how AOE expands with each level */
private record BoxSize(int width, int height, int depth) {
public static final BoxSize ZERO = new BoxSize(0, 0, 0);
public static final RecordLoadable<BoxSize> LOADER = RecordLoadable.create(
IntLoadable.FROM_ZERO.defaultField("width", 0, BoxSize::width),
IntLoadable.FROM_ZERO.defaultField("height", 0, BoxSize::height),
IntLoadable.FROM_ZERO.defaultField("depth", 0, BoxSize::depth),
BoxSize::new);
/** If true, the box is 0 in all dimensions */
public boolean isZero() {
return width == 0 && height == 0 && depth == 0;
}
}
/** Builder to create a rectangle AOE iterator */
@RequiredArgsConstructor
public static class Builder {
private final BoxSize base;
/** Direction to expand the AOE */
@Nonnull @Setter @Accessors(fluent = true)
private IBoxExpansion direction = IBoxExpansion.SIDE_HIT;
private final ImmutableList.Builder<BoxSize> expansions = ImmutableList.builder();
/** Adds an expansion to the AOE logic */
public Builder addExpansion(int width, int height, int depth) {
expansions.add(new BoxSize(width, height, depth));
return this;
}
/** Adds an expansion to the AOE logic */
public Builder addWidth(int width) {
return addExpansion(width, 0, 0);
}
/** Adds an expansion to the AOE logic */
public Builder addHeight(int height) {
return addExpansion(0, height, 0);
}
/** Adds an expansion to the AOE logic */
public Builder addDepth(int depth) {
return addExpansion(0, 0, depth);
}
/** Builds the AOE iterator */
public BoxAOEIterator build() {
return new BoxAOEIterator(base, expansions.build(), direction);
}
}
}