Fix some things maybe!
Alle Prüfungen waren erfolgreich
SteamWarCI Build successful

Dieser Commit ist enthalten in:
yoyosource 2024-04-17 16:31:36 +02:00
Ursprung d13c415565
Commit ed799b2937
8 geänderte Dateien mit 449 neuen und 137 gelöschten Zeilen

Datei anzeigen

@ -0,0 +1,115 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.portablehole;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.tpslimit.TPSUtils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
// @Linked
public class PortableHoleListener implements Listener {
private Map<Long, List<BlockState>> toReplace = new HashMap<>();
{
Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> {
List<BlockState> blockStates = toReplace.remove(TPSUtils.currentRealTick.get());
if (blockStates == null) return;
blockStates.forEach(blockState -> {
blockState.update(true, false);
});
}, 1, 1);
}
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
if (!event.hasItem()) return;
if (event.getItem().getType() != Material.FIREWORK_STAR) return;
BlockFace blockFace;
if (event.getPlayer().getLocation().getPitch() < -45) {
blockFace = BlockFace.UP;
} else if (event.getPlayer().getLocation().getPitch() > 45) {
blockFace = BlockFace.DOWN;
} else if (event.getPlayer().getLocation().getYaw() > 135) {
blockFace = BlockFace.NORTH;
} else if (event.getPlayer().getLocation().getYaw() > 45) {
blockFace = BlockFace.WEST;
} else if (event.getPlayer().getLocation().getYaw() > -45) {
blockFace = BlockFace.SOUTH;
} else if (event.getPlayer().getLocation().getYaw() > -135) {
blockFace = BlockFace.EAST;
} else {
blockFace = BlockFace.NORTH;
}
for (int i = 0; i < 10; i++) {
Location source = event.getPlayer().getLocation().getBlock().getLocation().clone();
source.add(blockFace.getDirection().clone().multiply(i + 1 + (blockFace == BlockFace.UP ? 1 : 0)));
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
long reappearTime = TPSUtils.currentRealTick.get() + 100;
if (blockFace == BlockFace.UP || blockFace == BlockFace.DOWN) {
for (int dx = -1; dx <= 1; dx++) {
for (int dz = -1; dz <= 1; dz++) {
Block block = source.clone().add(dx, 0, dz).getBlock();
BlockState blockState = block.getState();
if (blockState.getType().isAir()) continue;
toReplace.computeIfAbsent(reappearTime, __ -> new ArrayList<>()).add(blockState);
block.setType(Material.AIR);
}
}
} else if (blockFace == BlockFace.NORTH || blockFace == BlockFace.SOUTH) {
for (int dx = -1; dx <= 1; dx++) {
for (int dy = 0; dy <= 2; dy++) {
Block block = source.clone().add(dx, dy, 0).getBlock();
BlockState blockState = block.getState();
if (blockState.getType().isAir()) continue;
toReplace.computeIfAbsent(reappearTime, __ -> new ArrayList<>()).add(blockState);
block.setType(Material.AIR);
}
}
} else {
for (int dy = 0; dy <= 2; dy++) {
for (int dz = -1; dz <= 1; dz++) {
Block block = source.clone().add(0, dy, dz).getBlock();
BlockState blockState = block.getState();
if (blockState.getType().isAir()) continue;
toReplace.computeIfAbsent(reappearTime, __ -> new ArrayList<>()).add(blockState);
block.setType(Material.AIR);
}
}
}
}, i);
}
}
}

Datei anzeigen

@ -149,7 +149,6 @@ public class TNTPoint implements Externalizable {
objectOutput.writeBoolean(destroyedTestBlock);
objectOutput.writeLong(ticksSinceStart);
objectOutput.writeInt(fuse);
objectOutput.writeUTF(location.getWorld().getName());
objectOutput.writeDouble(location.getX());
objectOutput.writeDouble(location.getY());
objectOutput.writeDouble(location.getZ());
@ -168,11 +167,10 @@ public class TNTPoint implements Externalizable {
destroyedTestBlock = objectInput.readBoolean();
ticksSinceStart = objectInput.readLong();
fuse = objectInput.readInt();
String worldName = objectInput.readUTF();
double locX = objectInput.readDouble();
double locY = objectInput.readDouble();
double locZ = objectInput.readDouble();
location = new Location(Bukkit.getWorld(worldName), locX, locY, locZ);
location = new Location(Bukkit.getWorlds().get(0), locX, locY, locZ);
double velX = objectInput.readDouble();
double velY = objectInput.readDouble();
double velZ = objectInput.readDouble();
@ -192,15 +190,11 @@ public class TNTPoint implements Externalizable {
@Override
public boolean equals(Object obj) {
if (!(obj instanceof TNTPoint))
return false;
if (!(obj instanceof TNTPoint)) return false;
TNTPoint record = (TNTPoint) obj;
if (record.isExplosion() != explosion)
return false;
if (!record.getLocation().equals(location))
return false;
if (!record.getVelocity().equals(velocity))
return false;
if (record.isExplosion() != explosion) return false;
if (!record.getLocation().equals(location)) return false;
if (!record.getVelocity().equals(velocity)) return false;
return true;
}
}

Datei anzeigen

@ -28,6 +28,7 @@ import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import lombok.Cleanup;
import lombok.Getter;
import lombok.SneakyThrows;
import org.bukkit.entity.Player;
import java.io.*;
@ -73,6 +74,7 @@ public class Trace {
private final Map<Player, REntityServer> entityServerMap = new HashMap<>();
@SneakyThrows
public Trace(Region region, List<TNTPoint> recordList) {
this.uuid = UUID.randomUUID();
recordsSaveFile = new File(TraceManager.tracesFolder, uuid + ".records");
@ -81,17 +83,14 @@ public class Trace {
records = new SoftReference<>(recordList);
metadataSaveFile = new File(TraceManager.tracesFolder, uuid + ".meta");
try {
@Cleanup
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(metadataSaveFile));
outputStream.writeUTF(uuid.toString());
outputStream.writeUTF(region.getName());
outputStream.writeObject(date);
} catch (IOException | ClassCastException e) {
e.printStackTrace();
}
@Cleanup
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(metadataSaveFile));
outputStream.writeUTF(uuid.toString());
outputStream.writeUTF(region.getName());
outputStream.writeObject(date);
}
@SneakyThrows
public Trace(File metadataSaveFile) {
String uuid = null;
Region region = null;
@ -106,8 +105,6 @@ public class Trace {
region = Region.getREGION_MAP().get(inputStream.readUTF());
date = (Date) inputStream.readObject();
inputStream.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
this.uuid = UUID.fromString(uuid);
this.region = region;
@ -115,8 +112,6 @@ public class Trace {
recordsSaveFile = new File(TraceManager.tracesFolder, uuid + ".records");
this.records = new SoftReference<>(null);
}
System.out.println(this);
}
/**
@ -175,8 +170,9 @@ public class Trace {
* @param playerTraceShowData The showData for modifying the rendering
*/
private void render(List<TNTPoint> records, REntityServer entityServer, PlayerTraceShowData playerTraceShowData) {
if (records.isEmpty())
if (records.isEmpty()) {
return;
}
List<TNTPoint> workingRecords = new ArrayList<>(records);
Set<ViewFlag> flagList = playerTraceShowData.getEffectiveViewFlags();
@ -221,7 +217,8 @@ public class Trace {
private List<List<TNTPoint>> bundleRecords(List<TNTPoint> records, BundleFilter filter) {
List<List<TNTPoint>> bundles = new ArrayList<>();
recordsLoop: for (TNTPoint record : records) {
recordsLoop:
for (TNTPoint record : records) {
if (bundles.isEmpty()) {
List<TNTPoint> firstBundle = new ArrayList<>();
firstBundle.add(record);
@ -246,26 +243,6 @@ public class Trace {
return bundles;
}
/**
* Makes the first passed player follow the trace render of the second passed
* player
*
* @param player
* @param toFollow
*/
public void follow(Player player, Player toFollow) {
throw new UnsupportedOperationException();
}
/**
* Makes the passed player stop following any other players trace render
*
* @param player
*/
public void unfollow(Player player) {
throw new UnsupportedOperationException();
}
/**
* Hides this trail for the given player
*
@ -273,11 +250,13 @@ public class Trace {
*/
public void hide(Player player) {
REntityServer entityServer = entityServerMap.remove(player);
if (entityServer == null)
if (entityServer == null) {
return;
}
entityServer.removePlayer(player);
if (entityServer.getPlayers().isEmpty())
if (entityServer.getPlayers().isEmpty()) {
entityServer.close();
}
}
public void hide() {
@ -287,36 +266,36 @@ public class Trace {
entityServerMap.clear();
}
@SneakyThrows
private void loadRecords() {
try {
List<TNTPoint> records = new ArrayList<>();
@Cleanup
FileInputStream fileInputStream = new FileInputStream(recordsSaveFile);
@Cleanup
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
while (fileInputStream.getChannel().position() < recordsSaveFile.length()) {
records.add((TNTPoint) inputStream.readObject());
}
List<TNTPoint> records = new ArrayList<>();
System.out.println("Loaded... " + records);
@Cleanup
FileInputStream fileInputStream = new FileInputStream(recordsSaveFile);
Map<Integer, List<TNTPoint>> histories = new HashMap<>();
for (TNTPoint record : records) {
int tntId = record.getTntId();
List<TNTPoint> history = histories.computeIfAbsent(tntId, id -> new ArrayList<>());
history.add(record);
record.setHistory(history);
}
this.records = new SoftReference<>(records);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
@Cleanup
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
while (fileInputStream.getChannel().position() < recordsSaveFile.length()) {
records.add((TNTPoint) inputStream.readObject());
}
System.out.println("Loaded... " + records);
Map<Integer, List<TNTPoint>> histories = new HashMap<>();
for (TNTPoint record : records) {
int tntId = record.getTntId();
List<TNTPoint> history = histories.computeIfAbsent(tntId, id -> new ArrayList<>());
history.add(record);
record.setHistory(history);
}
this.records = new SoftReference<>(records);
}
public List<TNTPoint> getRecords() {
if (records.get() == null)
if (records.get() == null) {
loadRecords();
}
return records.get();
}

Datei anzeigen

@ -73,8 +73,7 @@ public class TraceCommand extends SWCommand {
}
@Register(value = { "show", "at" }, description = "TRACE_COMMAND_HELP_SHOW_AT_WITH")
public void showAt(@Validator Player player, @Min(intValue = 0) int time, @StaticValue("with") String with,
@OptionalValue("STRICT") BundleFilter bundleFilter, @ArrayLength(min = 1) ViewFlag... flags) {
public void showAt(@Validator Player player, @Min(intValue = 0) int time, @StaticValue("with") String with, @OptionalValue("STRICT") BundleFilter bundleFilter, @ArrayLength(min = 1) ViewFlag... flags) {
showInternal(player, time, time, bundleFilter, flags);
BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW_AT", player, time);
}
@ -87,9 +86,7 @@ public class TraceCommand extends SWCommand {
}
@Register(value = { "show", "from" }, description = "TRACE_COMMAND_HELP_SHOW_FROM_TO_WITH")
public void showFromTo(@Validator Player player, @Min(intValue = 0) int from, @StaticValue("to") String toString,
int to, @StaticValue("with") String with, @OptionalValue("STRICT") BundleFilter bundleFilter,
@ArrayLength(min = 1) ViewFlag... flags) {
public void showFromTo(@Validator Player player, @Min(intValue = 0) int from, @StaticValue("to") String toString, int to, @StaticValue("with") String with, @OptionalValue("STRICT") BundleFilter bundleFilter, @ArrayLength(min = 1) ViewFlag... flags) {
if (to < from) {
BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW_TO_SMALLER", player);
return;
@ -117,8 +114,7 @@ public class TraceCommand extends SWCommand {
}
@Register(value = { "show", "from" }, description = "TRACE_COMMAND_HELP_SHOW_FROM_TO")
public void showFromTo(@Validator Player player, @Min(intValue = 0) int from, @StaticValue("to") String toString,
int to) {
public void showFromTo(@Validator Player player, @Min(intValue = 0) int from, @StaticValue("to") String toString, int to) {
TraceManager.instance.renderAt(player, from, to);
BauSystem.MESSAGE.send("TRACE_MESSAGE_SHOW_FROM_TO", player, from, to);
}
@ -143,8 +139,7 @@ public class TraceCommand extends SWCommand {
}
@Register(value = "isolate", description = "TRACE_COMMAND_HELP_ISOLATE")
public void isolate(@Validator Player player, Trace trace,
@ErrorMessage("TRACE_RECORD_ID_INVALID") TNTPoint... records) {
public void isolate(@Validator Player player, Trace trace, @ErrorMessage("TRACE_RECORD_ID_INVALID") TNTPoint... records) {
TraceManager.instance.isolate(player, records);
// TODO: Add Message!
}
@ -156,18 +151,18 @@ public class TraceCommand extends SWCommand {
@Register(value = "follow", description = "TRACE_COMMAND_HELP_SHOW")
public void follow(@Validator Player player, Player toFollow) {
// TODO: Implement
for (Trace trace : TraceManager.instance.getAll()) {
trace.follow(player, toFollow);
if (player == toFollow) {
// TODO: Implement message
return;
}
TraceManager.instance.follow(player, toFollow);
// TODO: Implement message
}
@Register(value = "unfollow", description = "TRACE_COMMAND_HELP_SHOW")
public void unfollow(@Validator Player player) {
// TODO: Implement
for (Trace trace : TraceManager.instance.getAll()) {
trace.unfollow(player);
}
TraceManager.instance.unfollow(player);
// TODO: Implement message
}
@ClassMapper(value = Trace.class, local = true)
@ -179,8 +174,7 @@ public class TraceCommand extends SWCommand {
}
@Override
public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments,
String s) {
public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments, String s) {
return TraceManager.instance.getAllIds().stream()
.map(Object::toString)
.collect(Collectors.toList());
@ -194,8 +188,7 @@ public class TraceCommand extends SWCommand {
@Override
public TNTPoint map(CommandSender commandSender, PreviousArguments previousArguments, String s) {
Trace trace = previousArguments.getFirst(Trace.class).orElse(null);
if (trace == null)
return null;
if (trace == null) return null;
int id = Integer.parseInt(s);
return trace.getRecords().stream()
@ -206,11 +199,9 @@ public class TraceCommand extends SWCommand {
// TODO change when new command framework update
@Override
public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments,
String s) {
public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments, String s) {
Trace trace = previousArguments.getFirst(Trace.class).orElse(null);
if (trace == null)
return null;
if (trace == null) return null;
return trace.getUsedIds();
}
};
@ -230,8 +221,7 @@ public class TraceCommand extends SWCommand {
}
@Override
public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments,
String s) {
public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments, String s) {
if (s.length() == 0) {
return new ArrayList<>();
}
@ -263,8 +253,7 @@ public class TraceCommand extends SWCommand {
}
@Override
public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments,
String s) {
public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments, String s) {
return ViewFlag.flags.stream()
.flatMap(viewFlag -> Stream.concat(Stream.of("--" + viewFlag.name),
Arrays.stream(viewFlag.aliases).map(name -> "-" + name)))

Datei anzeigen

@ -204,8 +204,7 @@ public class TraceManager implements Listener {
Region region = Region.getRegion(player.getLocation());
PlayerTraceShowData previous = showDataPerRegionPerPlayer.getOrDefault(region, Collections.emptyMap())
.remove(player);
if (previous == null)
return;
if (previous == null) return;
tracesByRegion.getOrDefault(player, Collections.emptyMap()).forEach((integer, trace) -> {
trace.hide(player);
followerMap.getOrDefault(player, Collections.emptySet()).forEach(trace::hide);
@ -213,8 +212,7 @@ public class TraceManager implements Listener {
}
public boolean follow(Player follower, Player following) {
if (followerMap.containsKey(follower))
return false;
if (followerMap.containsKey(follower)) return false;
if (followerMap.entrySet().stream().anyMatch(playerSetEntry -> playerSetEntry.getValue().contains(follower))) {
unfollow(follower);
}
@ -229,8 +227,7 @@ public class TraceManager implements Listener {
}
PlayerTraceShowData playerTraceShowData = playerPlayerTraceShowDataMap.get(following);
if (playerTraceShowData == null)
return;
if (playerTraceShowData == null) return;
tracesByRegion.getOrDefault(region, Collections.emptyMap()).forEach((integer, trace) -> {
trace.render(follower, playerTraceShowData);
});
@ -239,30 +236,25 @@ public class TraceManager implements Listener {
}
public void unfollow(Player follower) {
if (followerMap.containsKey(follower))
return;
if (followerMap.containsKey(follower)) return;
List<Player> toRemove = new ArrayList<>();
Set<Player> toHide = new HashSet<>();
followerMap.forEach((player, players) -> {
if (players.remove(follower))
toHide.add(follower);
if (players.isEmpty())
toRemove.add(player);
if (players.remove(follower)) toHide.add(follower);
if (players.isEmpty()) toRemove.add(player);
});
toRemove.forEach(followerMap::remove);
showDataPerRegionPerPlayer.forEach((region, playerPlayerTraceShowDataMap) -> {
toHide.forEach(player -> {
if (!playerPlayerTraceShowDataMap.containsKey(player))
return;
if (!playerPlayerTraceShowDataMap.containsKey(player)) return;
tracesByRegion.getOrDefault(region, Collections.emptyMap()).forEach((integer, trace) -> {
trace.hide(follower);
});
});
PlayerTraceShowData playerTraceShowData = playerPlayerTraceShowDataMap.get(follower);
if (playerTraceShowData == null)
return;
if (playerTraceShowData == null) return;
tracesByRegion.getOrDefault(region, Collections.emptyMap()).forEach((integer, trace) -> {
trace.render(follower, playerTraceShowData);
});

Datei anzeigen

@ -92,8 +92,7 @@ public class TraceRecorder implements Listener {
*/
public void checkForAutoTraceFinish() {
for (Region region : autoTraceRegions) {
if (autoTraceRegions.contains(region)
&& trackedTNT.getOrDefault(region, Collections.emptyList()).size() == 0) {
if (autoTraceRegions.contains(region) && trackedTNT.getOrDefault(region, Collections.emptyList()).size() == 0) {
stopRecording(region);
}
}
@ -105,8 +104,9 @@ public class TraceRecorder implements Listener {
* @param region region to be recorded
*/
public int startRecording(Region region) {
if (activeTraces.containsKey(region))
if (activeTraces.containsKey(region)) {
return -1;
}
TraceRecordingWrapper wrappedTrace = new TraceRecordingWrapper(region);
activeTraces.put(region, wrappedTrace);
@ -120,8 +120,7 @@ public class TraceRecorder implements Listener {
*/
public void stopRecording(Region region) {
TraceRecordingWrapper wrappedTrace = activeTraces.getOrDefault(region, null);
if (wrappedTrace == null)
return;
if (wrappedTrace == null) return;
activeTraces.remove(region);
for (TNTPrimed tnt : trackedTNT.getOrDefault(region, Collections.emptyList())) {
@ -184,8 +183,7 @@ public class TraceRecorder implements Listener {
*/
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onTNTSpawn(EntitySpawnEvent event) {
if (!(event.getEntity() instanceof TNTPrimed))
return;
if (!(event.getEntity() instanceof TNTPrimed)) return;
Region region = Region.getRegion(event.getLocation());
@ -214,12 +212,10 @@ public class TraceRecorder implements Listener {
*/
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onTNTExplode(EntityExplodeEvent event) {
if (!(event.getEntity() instanceof TNTPrimed))
return;
if (!(event.getEntity() instanceof TNTPrimed)) return;
Region region = tntSpawnRegion.getOrDefault((TNTPrimed) event.getEntity(), null);
if (region == null)
return;
if (region == null) return;
trackedTNT.get(region).remove((TNTPrimed) event.getEntity());
tntSpawnRegion.remove((TNTPrimed) event.getEntity());

Datei anzeigen

@ -22,6 +22,7 @@ package de.steamwar.bausystem.features.tracer;
import de.steamwar.bausystem.features.tpslimit.TPSUtils;
import de.steamwar.bausystem.region.Region;
import lombok.Getter;
import lombok.SneakyThrows;
import java.io.File;
import java.io.FileOutputStream;
@ -45,6 +46,7 @@ public class TraceRecordingWrapper {
@Getter
private final Trace trace;
@SneakyThrows
public TraceRecordingWrapper(Region region) {
this.region = region;
startTick = TPSUtils.currentTick.get();
@ -54,12 +56,7 @@ public class TraceRecordingWrapper {
trace = new Trace(region, recordList);
TraceManager.instance.add(trace);
File recordsSaveFile = new File(TraceManager.tracesFolder, trace.getUuid() + ".records");
try {
recordsOutputStream = new ObjectOutputStream(new FileOutputStream(recordsSaveFile));
} catch (IOException e) {
e.printStackTrace();
}
recordsOutputStream = new ObjectOutputStream(new FileOutputStream(recordsSaveFile));
}
public int getNextOpenRecordIdAndIncrement() {
@ -80,6 +77,7 @@ public class TraceRecordingWrapper {
recordsToAdd.forEach(record -> {
try {
recordsOutputStream.writeObject(record);
recordsOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
@ -91,12 +89,9 @@ public class TraceRecordingWrapper {
System.out.println(recordList);
}
@SneakyThrows // TODO: Why is this not called?
protected void finalizeRecording() {
try {
recordsOutputStream.flush();
recordsOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
recordsOutputStream.flush();
recordsOutputStream.close();
}
}

252
MiningIII.md Normale Datei
Datei anzeigen

@ -0,0 +1,252 @@
# Mining III
Stats menu, blocks broken
Pickaxe skins? / Cosmetics (Armor)
Crystals?
Music selector
McPrison enchantments?
Legendary enchantments? -> Overdrive entchantments via prestige points
Combo Perk Enchantments?
Show proc rate enchtantment
Crystal Level increase by mining?
Keeping -> Keep an enchantment or n levels on random enchantments
Max upgrade? +5 upgrade?
Auto buy?
## Enchantments
### Efficiency
- Increases Mining speed
- id: efficiency
- max: 10
- cost_block_base: 50
- cost_block_inc: 10
- cost_shards_base: -80
- cost_shards_inc: 20
### Haste
- Gain Haste
- id: haste
- max: 10
- cost_block_base: 1000
- cost_block_inc: 0
- cost_gold_base: 100
- cost_gold_inc: 100
### Speed
- Gain Speed
- id: speed
- max: 5
- cost_blocks_base: 500
- cost_blocks_inc: -100
- cost_shards_base: 100
- cost_shards_inc: 150
### Fortune
- Increases Blocks gained
- id: fortune
- max: 50
- cost_block_base: 100
- cost_block_inc: 25
- cost_gold_base: 200
- cost_gold_inc: 50
- cost_shards_base: -40
- cost_shards_inc: 10
### Prosperity
- Rare chance to greatly increse block gain
- id: prosperity
- max: 10
- cost_block_base: 1000
- cost_block_inc: 100
- cost_gold_base: 1000
- cost_gold_inc: 100
- cost_shards_base: 100
- cost_shards_inc: 100
### Gifted
- Get luckier prosperity procs
- id: gifted
- max: 25
- cost_block_base: 10000
- cost_block_inc: 1000
- cost_shards_base: 1000
- cost_shards_inc: 250
### Shard Fusing Tome
- Chance to turn ores into shards
- id: shard_fusing_tome
- max: 10
- cost_block_base: 5000
- cost_block_inc: 500
- cost_shards_base: 1000
- cost_shards_inc: 0
### Unstable shards
- Destroy every shard created by Shard Fusing Tome
- id: unstable_shards
- max: 1
- cost_block_base: 100000
- cost_block_inc: 0
- cost_gold_base: 25000
- cost_gold_inc: 0
- cost_shards_base: 10000
- cost_shards_inc: 0
### Gatherer
- Chance to gain Gold on Block break
- id: gatherer
- max: 10
- cost_block_base: 1000
- cost_block_inc: 1000
- cost_gold_base: 1000
- cost_gold_inc: 0
### Block Shatterer
- Greatly increase Block gain
- id: block_shatterer
- max: 10
- cost_block_base: 5000
- cost_block_inc: 5000
- cost_gold_base: 1000
- cost_gold_inc: 1000
- cost_shards_base: 500
- cost_shards_inc: 500
### Gold Shatterer
- Greatly increase Gold gain
- id: gold_shatterer
- max: 10
- cost_block_base: 1000
- cost_block_inc: 1000
- cost_gold_base: 5000
- cost_gold_inc: 5000
- cost_shards_base: 500
- cost_shards_inc: 500
### Shard Shatterer
- Greatly increase Shard gain
- id: shard_shatterer
- max: 10
- cost_block_base: 5000
- cost_block_inc: 5000
- cost_gold_base: 500
- cost_gold_inc: 500
- cost_shards_base: 1000
- cost_shards_inc: 1000
### Explosive
- Chance to explode
- id: explosive
- max: 30
- cost_block_base: 200
- cost_block_inc: 50
- cost_gold_base: -700
- cost_gold_inc: 100
- cost_shards_base: -3200
- cost_shards_inc: 200
### Laser
- Chance to fire a laser
- id: laser
- max: 300
- cost_block_base: 1000
- cost_block_inc: 0
- cost_gold_base: 3000
- cost_gold_inc: -20
- cost_shards_base: 0
- cost_shards_inc: 30
### Jackhammer
- Chance to destroy 3*3*3 (100) or 5*5*5 (200) area
- id: jackhammer
- max: 200
- cost_block_base: 10000
- cost_block_inc: 100
- cost_gold_base: -12250
- cost_gold_inc: 250
- cost_shards_base: -74500
- cost_shards_inc: 500
### Corrosive Mine
- Destroy level number of blocks in the vicinity
- id: corrosive_mine
- max: 25
- cost_block_base: 5000
- cost_block_inc: 100
- cost_gold_base: 5000
- cost_gold_inc: 100
- cost_shards_base: 5000
- cost_shards_inc: 100
### Jackpot
- Chance to receive the jackpot of 1000 blocks, 1000 gold and 1000 shards
- id: jackpot
- max: 1
- cost_shards_base: 50000
- cost_shards_inc: 0
### Dual Pickaxe
- Chance to proc enchantments on another block in vicinity
- id: dual_pickaxe
- max: 10
- cost_block_base: 2500
- cost_block_inc: 500
- cost_gold_base: 2500
- cost_gold_inc: 500
- cost_shards_base: 2500
- cost_shards_inc: 500
### Luck
- Chance for laser bounces to proc any enchantment
- id: luck
- max: 10
- cost_block_base: 10000
- cost_block_inc: 10000
- cost_gold_base: 10000
- cost_gold_inc: 10000
- cost_shards_base: 10000
- cost_shards_inc: 10000
### Lumberjack
- Change to axe while looking onto Wood
- id: lumberjack
- max: 1
- cost_block_base: 5000
- cost_block_inc: 0
- cost_gold_base: 5000
- cost_gold_inc: 0
- cost_shards_base: 5000
- cost_shards_inc: 0
### Soul Remover
- Change to shovel while looking onto Wood
- id: soul_remover
- max: 1
- cost_block_base: 5000
- cost_block_inc: 0
- cost_gold_base: 5000
- cost_gold_inc: 0
- cost_shards_base: 5000
- cost_shards_inc: 0