想法

做一个能够直接破坏矿物的爆炸的物品,类似于匠魂2的EPLE物品,可以直接用来挖矿,并再这个基础上做一些拓展

灵感

来自匠魂的[EFLN - TiC2]匠魂2 (Tinkers’ Construct 2) - MC百科|最大的Minecraft中文MOD百科 (mcmod.cn)

实现的内容

  1. 实现匠魂物品的基础功能。
  2. 实现不同的范爆炸范围,通过木头,石头,铁,钻石,下届合金(之后也可以加入金和铜不过目前没想到什么特性)合成具有不同的爆炸范围。
  3. 加入一种圆石进行的合成,可以爆炸后仅保留矿物。
  4. 加入一种末影珍珠合成的物品,可以爆炸后将所有物品传到玩家背包中,而不是掉落在地上。
  5. 添加一套新的火药的获的途径

目前想法开发过程

  1. 实现匠魂代码的运行,保证和原物品一样的功能
  2. 在此基础上添加具有不同范围的“物品”。 ^1
  3. 在此基础上添加受到时运效果影响的“物品”
  4. 在此基础上添加能够传送掉落物品的“物品”

关于该模组命名

暂定Mine helper吧。挖矿小助手。欢迎各位想想啥中二名字。

也欢迎给该模组提供能,我会选择考虑是否加入。

结论

没啥结论。之后在更新。

目前存在进度和存在问题

进度

开发了一个物品wooditem和一个实体woodentity,添加了爆炸和爆炸帮助类。

存在问题

  • 没有系统设计每个物品的名称
  • 需要对系统的架构进行设计对代码进行设计
  • 没有美术
  • 整个内容写的比较随意,还是测试代码阶段。

考虑

  1. 作为正式开发的前置研究。完成基本功能后考虑进行详细的设计在进行开发。
  2. 目前需要进行对结构的设计了。
  3. 还有部分的代码不明白具体什么意思之后再说。

开发日志

  • 11.3

爆炸不会破坏方块不知道什么鬼bug,完全复制的匠魂的代码。明天看看怎么回事

  • 11.5

修复了为什么爆炸不会破坏方块,对代码进行了注解。以下贴出来

exploer类

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
package com.example.examplemod.entity.custom;

import com.google.common.collect.Lists;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntitySelector;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.storage.loot.LootParams;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.EventHooks;
import net.neoforged.neoforge.event.TickEvent;

import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.function.Predicate;

public class Exploder {

public final double r;
private final double rr;
public final int dist;
private final double explosionStrength;
private final int blocksPerIteration;
public final int x, y, z;
public final Level world;
private final Entity exploder;
private final WoodBallExplosion explosion;

private int currentRadius;
private int curX, curY, curZ;

private List<ItemStack> droppedItems; // map containing all items dropped by the explosion and their amounts

/**
* 构造方法
* @param world 爆炸的level
* @param explosion 爆炸对象的引用
* @param exploder 触发爆炸的实体
* @param location 爆炸的中心位置
* @param r 爆炸半径
* @param explosionStrength 爆炸的破坏力
* @param blocksPerIteration 每次迭代处理的方块次数
*/
public Exploder(Level world, WoodBallExplosion explosion, Entity exploder, BlockPos location, double r, double explosionStrength, int blocksPerIteration) {
this.r = r;
this.world = world;
this.explosion = explosion;
this.exploder = exploder;
this.rr = r * r;
this.dist = (int) r + 1;
this.explosionStrength = explosionStrength;
this.blocksPerIteration = blocksPerIteration;
this.currentRadius = 0;

this.x = location.getX();
this.y = location.getY();
this.z = location.getZ();

this.curX = 0;
this.curY = 0;
this.curZ = 0;

this.droppedItems = Lists.newArrayList();
}

/**
* 启动爆炸 静态方法
* @param world 爆炸的level
* @param explosion 爆炸的引用
* @param entity 爆炸的实体
* @param location 爆炸的位置
* @param r 爆炸的半径
* @param explosionStrength 爆炸的强度
*/
public static void startExplosion(Level world, WoodBallExplosion explosion, Entity entity, BlockPos location, double r, double explosionStrength) {
// 创建类
Exploder exploder = new Exploder(world, explosion, entity, location, r, explosionStrength, Math.max(50, (int) (r * r * r / 10d)));
// 提前处理爆炸范围内的实体
exploder.handleEntities();
// 播放爆炸的声音
world.playSound(null, location, SoundEvents.GENERIC_EXPLODE, SoundSource.BLOCKS, 4.0F, (1.0F + (world.random.nextFloat() - world.random.nextFloat()) * 0.2F) * 0.7F);
// 在事件总线上注册exploer,用于在tick时迭代处理
NeoForge.EVENT_BUS.register(exploder);
}

/**
* 处理爆炸涉及到的实体
*/
private void handleEntities() {
// 定义了一个Predicate判断条件,用于过滤实体
final Predicate<Entity> predicate = entity -> entity != null
&& !entity.ignoreExplosion()
&& EntitySelector.NO_SPECTATORS.test(entity)
&& EntitySelector.ENTITY_STILL_ALIVE.test(entity)
&& entity.position().distanceToSqr(this.x, this.y, this.z) <= this.r * this.r;

// 使用Predicate获得爆炸范围内的实体
// damage and blast back entities
List<Entity> list = this.world.getEntities(this.exploder,
new AABB(this.x - this.r - 1,
this.y - this.r - 1,
this.z - this.r - 1,
this.x + this.r + 1,
this.y + this.r + 1,
this.z + this.r + 1),
predicate
);
// 触发爆炸开始事件
EventHooks.onExplosionDetonate(this.world, this.explosion, list, this.r * 2);

for (Entity entity : list) {
// move it away from the center depending on distance and explosion strength
// 计算爆炸中心到实体方向的向量dir
Vec3 dir = entity.position().subtract(this.exploder.position().add(0, -this.r / 2, 0));
// 计算实体距离爆炸中心的距离占爆炸半径的比例,作为震动系数str
double str = (this.r - dir.length()) / this.r;
//
str = Math.max(0.3, str);
//
dir = dir.normalize();
//
dir = dir.scale(this.explosionStrength * str * 0.3);
// 给实体添加一个dir的力
entity.push(dir.x, dir.y + 0.5, dir.z);
// 实体受伤
entity.hurt(entity.damageSources().explosion(this.explosion), (float) (str * this.explosionStrength));

if (entity instanceof ServerPlayer) {
// TinkerNetwork.getInstance().sendTo(new EntityMovementChangePacket(entity), (ServerPlayer) entity);
}
}
}

/**
* tick执行
* @param event 世界Tick回调事件
*/
@SubscribeEvent
public void onTick(TickEvent.LevelTickEvent event) {
// 给定世界Tick结束时候回调
if(event.level == this.world && event.phase == TickEvent.Phase.END){
// 调用iteration进行一次爆炸迭代,返回false表示爆炸结束,放置范围过大,导致一次破坏方块过多卡死
if(!this.iteration()){
// 爆炸结束,调用finish
this.finish();
}
}
}

/**
* 爆炸结束下执行
*/
private void finish() {
// 爆炸半径的一半
final int d = (int) this.r / 2;
// 根据爆炸中心和爆炸半径计算掉落的中心位置
final BlockPos pos = new BlockPos(this.x - d, this.y - d, this.z - d);
// 创建随机数生成器
final Random random = new Random();
// 创建列表存储掉落的物品
List<ItemStack> aggregatedDrops = Lists.newArrayList();
//
for (ItemStack drop : this.droppedItems) {
boolean notInList = true;

// check if it's already in our list
// 检查是否在列表中,如果在则合并数量
for (ItemStack stack : aggregatedDrops) {
if (ItemStack.isSameItem(drop, stack) && ItemStack.isSameItemSameTags(drop, stack)) {
stack.grow(drop.getCount());
notInList = false;
break;
}
}

if (notInList) {
aggregatedDrops.add(drop);
}
}

// actually drop the aggregated items
// 遍历合在一起的物品,分批掉落
for (ItemStack drop : aggregatedDrops) {
int stacksize = drop.getCount();
do {
BlockPos spawnPos = pos.offset(random.nextInt((int) this.r), random.nextInt((int) this.r), random.nextInt((int) this.r));
ItemStack dropItemstack = drop.copy();
dropItemstack.setCount(Math.min(stacksize, 64));
Block.popResource(this.world, spawnPos, dropItemstack);
stacksize -= dropItemstack.getCount();
}
while (stacksize > 0);
}
// 取消事件总线上的注册
NeoForge.EVENT_BUS.unregister(this);
}

/**
* Explodes away all blocks for the current iteration
* 每次迭代执行
*/
private boolean iteration() {
// 本次迭代的方块个数
int count = 0;
// 清除上次的迭代处理的方块
this.explosion.clearToBlow();
// 当处理方块小于每次迭代方块,并且当前半径小于爆炸半径r
while (count < this.blocksPerIteration && this.currentRadius < (int) this.r + 1) {
double d = this.curX * this.curX + this.curY * this.curY + this.curZ * this.curZ;
// inside the explosion?
// 当前位置是否在爆炸范围内
if (d <= this.rr) {
//
BlockPos blockpos = new BlockPos(this.x + this.curX, this.y + this.curY, this.z + this.curZ);
BlockState blockState = this.world.getBlockState(blockpos);
FluidState ifluidstate = this.world.getFluidState(blockpos);

// no air blocks
// 如果不是方块 或者流体不为空。
if (!blockState.isAir() || !ifluidstate.isEmpty()) {
// explosion "strength" at the current position
// 随着爆炸范围的扩展爆炸强度减小
double f = this.explosionStrength * (1f - d / this.rr);
// 获得方块或者液体的抗爆炸强度
float f2 = Math.max(blockState.getExplosionResistance(this.world, blockpos, this.explosion), ifluidstate.getExplosionResistance(this.world, blockpos, this.explosion));
// 如果当前产生爆炸的实体不为空
if (this.exploder != null) {
// 获得当前产生爆炸实体的抗爆炸能力
f2 = this.exploder.getBlockExplosionResistance(this.explosion, this.world, blockpos, blockState, ifluidstate, f2);
}
// 判断是否抗爆炸
f -= (f2 + 0.3F) * 0.3F;
// 如果该方块应该被炸坏
if (f > 0.0F && (this.exploder == null || this.exploder.shouldBlockExplode(this.explosion, this.world, blockpos, blockState, (float) f))) {
// block should be exploded
// 爆炸方块++
count++;
// 将该方块添加到列表中
this.explosion.addAffectedBlock(blockpos);
}
}
}
// get next coordinate;
this.step();
}
// 触发爆炸事件
EventHooks.onExplosionDetonate(this.world, this.explosion, Collections.emptyList(), this.r * 2);
// 对每个记录的方块处理
this.explosion.getToBlow().forEach(this::explodeBlock);
// 处理方块是否达到上限
return count == this.blocksPerIteration; // can lead to 1 more call where nothing is done, but that's ok
}

// get the next coordinate

/**
* 计算下一个block坐标
*/
private void step() {
// we go X/Z plane wise from top to bottom
if (++this.curX > this.currentRadius) {
this.curX = -this.currentRadius;
if (++this.curZ > this.currentRadius) {
this.curZ = -this.currentRadius;
if (--this.curY < -this.currentRadius) {
this.currentRadius++;
this.curX = this.curZ = -this.currentRadius;
this.curY = this.currentRadius;
}
}
}
// we skip the internals
if (this.curY != -this.currentRadius && this.curY != this.currentRadius) {
// we're not in the top or bottom plane
if (this.curZ != -this.currentRadius && this.curZ != this.currentRadius) {
// we're not in the X/Y planes of the cube, we can therefore skip the x to the end if we're inside
if (this.curX > -this.currentRadius) {
this.curX = this.currentRadius;
}
}
}
}

/**
* 处理单个爆炸方块
* @param blockpos
*/
private void explodeBlock(BlockPos blockpos) {
BlockState blockstate = this.world.getBlockState(blockpos);
// 将该方块的掉落物添加到list中
if (!this.world.isClientSide && blockstate.canDropFromExplosion(this.world, blockpos, this.explosion)) {
BlockEntity tileentity = blockstate.hasBlockEntity() ? this.world.getBlockEntity(blockpos) : null;
LootParams.Builder builder = (new LootParams.Builder((ServerLevel) this.world)).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(blockpos)).withParameter(LootContextParams.TOOL, ItemStack.EMPTY).withOptionalParameter(LootContextParams.BLOCK_ENTITY, tileentity);

this.droppedItems.addAll(blockstate.getDrops(builder));
}
// 在该方块位置生成粒子效果
if (this.world instanceof ServerLevel) {
for (ServerPlayer serverplayerentity : ((ServerLevel) this.world).players()) {
((ServerLevel) this.world).sendParticles(serverplayerentity, ParticleTypes.POOF, true, blockpos.getX(), blockpos.getY(), blockpos.getZ(), 2, 0, 0, 0, 0d);
((ServerLevel) this.world).sendParticles(serverplayerentity, ParticleTypes.SMOKE, true, blockpos.getX(), blockpos.getY(), blockpos.getZ(), 1, 0, 0, 0, 0d);
}
}
// 方块爆炸
blockstate.onBlockExploded(this.world, blockpos, this.explosion);
}

}

该类进行了修改,添加了两个方法,修复了为什么爆炸不破坏的原因。

explosion类

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
package com.example.examplemod.entity.custom;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.EntityBasedExplosionDamageCalculator;
import net.minecraft.world.level.Explosion;
import net.minecraft.world.level.ExplosionDamageCalculator;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.LootParams;
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.phys.Vec3;
import org.apache.logging.log4j.core.jmx.Server;

import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;

public class WoodBallExplosion extends Explosion {
private static final ExplosionDamageCalculator EXPLOSION_DAMAGE_CALCULATOR = new ExplosionDamageCalculator();
private static final int MAX_DROPS_PER_COMBINED_STACK = 16;
private final boolean fire;
private final Explosion.BlockInteraction blockInteraction;
private final RandomSource random = RandomSource.create();
private final Level level;
private final double x;
private final double y;
private final double z;
@Nullable
private final Entity source;
private final float radius;
private final DamageSource damageSource;
private final ExplosionDamageCalculator damageCalculator;
private final ObjectArrayList<BlockPos> toBlow = new ObjectArrayList<>();
private final Map<Player, Vec3> hitPlayers = Maps.newHashMap();
private final Vec3 position;
protected ImmutableSet<BlockPos> affectedBlockPositionsInternal;

public WoodBallExplosion(Level world, @Nullable Entity entity, @Nullable DamageSource damage, @Nullable ExplosionDamageCalculator context, double x, double y, double z, float size, boolean causesFire, Explosion.BlockInteraction mode) {
super(world, entity, damage, context, x, y, z, size, causesFire, mode);
this.level = world;
this.source = entity;
this.radius = size;
this.x = x;
this.y = y;
this.z = z;
this.fire = causesFire;
this.blockInteraction = mode;
this.damageSource = damage == null ? world.damageSources().explosion(this) : damage;
this.damageCalculator = context == null ? this.makeDamageCalculator(entity) : context;
this.position = new Vec3(this.x, this.y, this.z);

}

private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity p_46063_) {
return (ExplosionDamageCalculator)(p_46063_ == null ? EXPLOSION_DAMAGE_CALCULATOR : new EntityBasedExplosionDamageCalculator(p_46063_));
}


/**
* Does the first part of the explosion (destroy blocks)
*/
@Override
public void explode() { // 未执行
ImmutableSet.Builder<BlockPos> builder = ImmutableSet.builder();

// we do a sphere of a certain radius, and check if the blockpos is inside the radius
float r = this.radius * this.radius;
int i = (int) r + 1;

for (int j = -i; j < i; ++j) {
for (int k = -i; k < i; ++k) {
for (int l = -i; l < i; ++l) {
int d = j * j + k * k + l * l;
// inside the sphere?
if (d <= r) {
BlockPos blockpos = new BlockPos(j, k, l).offset((int) this.x, (int) this.y, (int) this.z);
// no air blocks
if (this.level.isEmptyBlock(blockpos)) {
continue;
}

// explosion "strength" at the current position
float f = this.radius * (1f - d / (r));
BlockState blockstate = this.level.getBlockState(blockpos);

FluidState ifluidstate = this.level.getFluidState(blockpos);
float f2 = Math.max(blockstate.getExplosionResistance(this.level, blockpos, this), ifluidstate.getExplosionResistance(this.level, blockpos, this));
if (this.source != null) {
f2 = this.source.getBlockExplosionResistance(this, this.level, blockpos, blockstate, ifluidstate, f2);
}

f -= (f2 + 0.3F) * 0.3F;

if (f > 0.0F && (this.source == null || this.source.shouldBlockExplode(this, this.level, blockpos, blockstate, f))) {
builder.add(blockpos);
}
}
}
}
}

this.affectedBlockPositionsInternal = builder.build();
}

@Override
public void finalizeExplosion(boolean spawnParticles) { // 未执行
if (this.level.isClientSide) {
this.level.playLocalSound(this.x, this.y, this.z, SoundEvents.GENERIC_EXPLODE, SoundSource.BLOCKS, 4.0F, (1.0F + (this.level.random.nextFloat() - this.level.random.nextFloat()) * 0.2F) * 0.7F, false);
}

this.level.addParticle(ParticleTypes.EXPLOSION, this.x, this.y, this.z, 1.0D, 0.0D, 0.0D);

ObjectArrayList<Pair<ItemStack, BlockPos>> arrayList = new ObjectArrayList<>();
Util.shuffle(this.toBlow, this.level.random);

for (BlockPos blockpos : this.toBlow) {
BlockState blockstate = this.level.getBlockState(blockpos);
Block block = blockstate.getBlock();

if (!blockstate.isAir()) {
BlockPos blockpos1 = blockpos.immutable();

this.level.getProfiler().push("explosion_blocks");

if (blockstate.canDropFromExplosion(this.level, blockpos, this) && this.level instanceof ServerLevel) {
BlockEntity tileentity = blockstate.hasBlockEntity() ? this.level.getBlockEntity(blockpos) : null;
LootParams.Builder builder = (new LootParams.Builder((ServerLevel) level)).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(blockpos)).withParameter(LootContextParams.TOOL, ItemStack.EMPTY).withOptionalParameter(LootContextParams.BLOCK_ENTITY, tileentity).withOptionalParameter(LootContextParams.THIS_ENTITY, this.source);

if (this.blockInteraction == Explosion.BlockInteraction.DESTROY) {
builder.withParameter(LootContextParams.EXPLOSION_RADIUS, this.radius);
}

blockstate.getDrops(builder).forEach((stack) -> addStack(arrayList, stack, blockpos1));
}

blockstate.onBlockExploded(this.level, blockpos, this);
this.level.getProfiler().pop();
}
}
}

public void addAffectedBlock(BlockPos blockPos) {
this.toBlow.add(blockPos);
}

private static void addStack(ObjectArrayList<Pair<ItemStack, BlockPos>> arrayList, ItemStack merge, BlockPos blockPos) {
int i = arrayList.size();

for (int j = 0; j < i; ++j) {
Pair<ItemStack, BlockPos> pair = arrayList.get(j);
ItemStack itemstack = pair.getFirst();

if (ItemEntity.areMergable(itemstack, merge)) {
ItemStack itemstack1 = ItemEntity.merge(itemstack, merge, 16);
arrayList.set(j, Pair.of(itemstack1, pair.getSecond()));

if (merge.isEmpty()) {
return;
}
}
}

arrayList.add(Pair.of(merge, blockPos));
}


public List<BlockPos> getToBlow() {
return this.toBlow;
}

public void clearToBlow() {
this.toBlow.clear();
}
}

考虑到尽可能的复用代码,需要对该项目的架构进行设计。需要在考虑下。

  • 11.8

基本上想到的几个物品都写出来了,不过还是没采用高代码的复用,而且还希望加如对时运和精准采集的支持,没有复用代码可能导致之后需要改动较多的重复代码。

还不知道怎么支持时运和精准采集

其次还需要画贴图和加入合成表。