Add bridge and rescue totem jokers and fix dark block displays during the transition
This commit is contained in:
1 parent
f56a3765c6
commit
ef1662b144
4 files changed
+272
-28
No files matched your search
@@ -7,11 +7,17 @@ import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.entity.PlayerSkin;
|
||||
import net.minestom.server.event.GlobalEventHandler;
|
||||
import net.minestom.server.event.item.ItemDropEvent;
|
||||
import net.minestom.server.event.player.AsyncPlayerConfigurationEvent;
|
||||
import net.minestom.server.event.player.PlayerBlockBreakEvent;
|
||||
import net.minestom.server.event.player.PlayerBlockPlaceEvent;
|
||||
import net.minestom.server.event.player.PlayerDisconnectEvent;
|
||||
import net.minestom.server.event.player.PlayerMoveEvent;
|
||||
import net.minestom.server.event.player.PlayerSpawnEvent;
|
||||
import net.minestom.server.event.player.PlayerSwapItemEvent;
|
||||
import net.minestom.server.event.player.PlayerUseItemEvent;
|
||||
import net.minestom.server.event.player.PlayerUseItemOnBlockEvent;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.instance.InstanceContainer;
|
||||
import net.minestom.server.instance.InstanceManager;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
@@ -21,6 +27,7 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class EndlessJumper {
|
||||
public static void main(String[] args) throws IOException {
|
||||
@@ -65,8 +72,7 @@ public class EndlessJumper {
|
||||
|
||||
JumpGenerator generator = games.get(p);
|
||||
|
||||
//reset when falling down
|
||||
if(p.getPosition().y() < generator.getNextLanding().y()-5 && p.getPosition().y() < generator.getCheckpoint().y()) generator.reset();
|
||||
generator.checkFall();
|
||||
|
||||
Pos p1 = BlockMath.roundToBlock(event.getNewPosition().sub(0.0,1.0,0.0));
|
||||
Pos p2 = BlockMath.roundToBlock(event.getNewPosition().sub(0.0, 2.0, 0.0));
|
||||
@@ -81,14 +87,37 @@ public class EndlessJumper {
|
||||
//if(p.getPosition().z() > target.z()+2) generator.highlight();
|
||||
});
|
||||
|
||||
eventHandler.addListener(PlayerBlockBreakEvent.class, event -> event.setCancelled(true));
|
||||
// nur Brücken- und Rettungsblöcke dürfen abgebaut werden (Notausgang), Sprungblöcke nie
|
||||
eventHandler.addListener(PlayerBlockBreakEvent.class, event -> {
|
||||
JumpGenerator generator = games.get(event.getPlayer());
|
||||
if(generator == null || !generator.isBreakable(new Pos(event.getBlockPosition()))) event.setCancelled(true);
|
||||
});
|
||||
eventHandler.addListener(PlayerBlockPlaceEvent.class, event -> event.setCancelled(true));
|
||||
eventHandler.addListener(ItemDropEvent.class, event -> event.setCancelled(true));
|
||||
eventHandler.addListener(PlayerSwapItemEvent.class, event -> event.setCancelled(true));
|
||||
|
||||
// Rechtsklick auf Luft und auf Blöcke lösen beide aus; kurze Sperre gegen doppelte Nutzung
|
||||
Map<Player, Long> lastJokerUse = new HashMap<>();
|
||||
java.util.function.BiConsumer<Player, ItemStack> useJoker = (p, item) -> {
|
||||
JumpGenerator generator = games.get(p);
|
||||
if(generator == null || !Jokers.isBridge(item)) return;
|
||||
long now = System.currentTimeMillis();
|
||||
if(now - lastJokerUse.getOrDefault(p, 0L) < 500) return;
|
||||
lastJokerUse.put(p, now);
|
||||
if(generator.useBridge()) Jokers.takeBridge(p);
|
||||
};
|
||||
eventHandler.addListener(PlayerUseItemEvent.class, event -> useJoker.accept(event.getPlayer(), event.getItemStack()));
|
||||
eventHandler.addListener(PlayerUseItemOnBlockEvent.class, event -> useJoker.accept(event.getPlayer(), event.getItemStack()));
|
||||
|
||||
eventHandler.addListener(PlayerSpawnEvent.class, event -> {
|
||||
if(event.isFirstSpawn()) ScoreTab.join(event.getPlayer());
|
||||
if(!event.isFirstSpawn()) return;
|
||||
ScoreTab.join(event.getPlayer());
|
||||
Jokers.giveStart(event.getPlayer());
|
||||
});
|
||||
|
||||
eventHandler.addListener(PlayerDisconnectEvent.class, event -> {
|
||||
games.remove(event.getPlayer());
|
||||
lastJokerUse.remove(event.getPlayer());
|
||||
ScoreTab.leave(event.getPlayer());
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package eu.mhsl.minecraft.endlessjumper;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
|
||||
/**
|
||||
* Joker-Items in der Hotbar. Die Anzahl ist die Stapelgröße.
|
||||
* Brücke: Rechtsklick baut einen Weg bis zum nächsten Checkpoint.
|
||||
* Totem: rettet automatisch beim Runterfallen (Plattform mit Leiter).
|
||||
*/
|
||||
public class Jokers {
|
||||
public static final int BRIDGE_SLOT = 0;
|
||||
public static final int TOTEM_SLOT = 8;
|
||||
|
||||
private static final Material BRIDGE = Material.BRICK;
|
||||
private static final Material TOTEM = Material.TOTEM_OF_UNDYING;
|
||||
|
||||
public static void giveStart(Player player) {
|
||||
player.getInventory().setItemStack(BRIDGE_SLOT, bridge(1));
|
||||
player.getInventory().setItemStack(TOTEM_SLOT, totem(1));
|
||||
}
|
||||
|
||||
public static boolean isBridge(ItemStack item) {
|
||||
return item.material() == BRIDGE;
|
||||
}
|
||||
|
||||
public static void addBridge(Player player) {
|
||||
add(player, BRIDGE_SLOT, bridge(1));
|
||||
player.sendMessage(Component.text("+1 Brücke", NamedTextColor.GOLD));
|
||||
}
|
||||
|
||||
public static void addTotem(Player player) {
|
||||
add(player, TOTEM_SLOT, totem(1));
|
||||
player.sendMessage(Component.text("+1 Rettungstotem", NamedTextColor.GOLD));
|
||||
}
|
||||
|
||||
public static boolean takeBridge(Player player) {
|
||||
return take(player, BRIDGE_SLOT, BRIDGE);
|
||||
}
|
||||
|
||||
public static boolean takeTotem(Player player) {
|
||||
return take(player, TOTEM_SLOT, TOTEM);
|
||||
}
|
||||
|
||||
private static void add(Player player, int slot, ItemStack item) {
|
||||
ItemStack current = player.getInventory().getItemStack(slot);
|
||||
if(current.material() == item.material()) {
|
||||
player.getInventory().setItemStack(slot, current.withAmount(current.amount() + 1));
|
||||
} else {
|
||||
player.getInventory().setItemStack(slot, item);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean take(Player player, int slot, Material material) {
|
||||
ItemStack current = player.getInventory().getItemStack(slot);
|
||||
if(current.material() != material || current.amount() <= 0) return false;
|
||||
player.getInventory().setItemStack(slot, current.amount() > 1 ? current.withAmount(current.amount() - 1) : ItemStack.AIR);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ItemStack bridge(int amount) {
|
||||
return ItemStack.builder(BRIDGE)
|
||||
.amount(amount)
|
||||
.customName(Component.text("Brücke", NamedTextColor.AQUA).decoration(TextDecoration.ITALIC, false))
|
||||
.lore(Component.text("Rechtsklick: Weg bis zum nächsten Checkpoint", NamedTextColor.GRAY).decoration(TextDecoration.ITALIC, false))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static ItemStack totem(int amount) {
|
||||
return ItemStack.builder(TOTEM)
|
||||
.amount(amount)
|
||||
.customName(Component.text("Rettungstotem", NamedTextColor.GOLD).decoration(TextDecoration.ITALIC, false))
|
||||
.lore(Component.text("Rettet dich automatisch beim Runterfallen", NamedTextColor.GRAY).decoration(TextDecoration.ITALIC, false))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,8 @@ public class BlockTransition {
|
||||
display.setNoGravity(true);
|
||||
display.editEntityMeta(BlockDisplayMeta.class, meta -> {
|
||||
meta.setBlockState(block);
|
||||
// feste Helligkeit: das Display startet im vorherigen Block, dort ist Lichtstufe 0 (sonst schwarz)
|
||||
meta.setBrightness(15, 15);
|
||||
meta.setPosRotInterpolationDuration(DURATION_TICKS);
|
||||
meta.setTransformationInterpolationDuration(DURATION_TICKS);
|
||||
// klein und mittig auf der Oberseite des vorherigen Blocks starten
|
||||
|
||||
@@ -4,16 +4,21 @@ import net.kyori.adventure.sound.Sound;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.title.Title;
|
||||
import eu.mhsl.minecraft.endlessjumper.Jokers;
|
||||
import eu.mhsl.minecraft.endlessjumper.ScoreTab;
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.instance.InstanceContainer;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.network.packet.server.play.ParticlePacket;
|
||||
import net.minestom.server.particle.Particle;
|
||||
import net.minestom.server.sound.SoundEvent;
|
||||
import net.minestom.server.timer.TaskSchedule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
public class JumpGenerator {
|
||||
@@ -31,6 +36,16 @@ public class JumpGenerator {
|
||||
|
||||
private static final int FULL_DIFFICULTY_AT = 1000;
|
||||
|
||||
private static final Block BRIDGE_BLOCK = Block.WHITE_STAINED_GLASS;
|
||||
private static final int BRIDGE_STEP_TICKS = 4;
|
||||
private static final Block RESCUE_BLOCK = Block.OAK_PLANKS;
|
||||
private static final int RESCUE_DEPTH = 6;
|
||||
|
||||
// Rettung durch das Totem: Plattform mit Leiter unter dem Block, von dem man gefallen ist
|
||||
private final Map<Pos, Block> rescueBlocks = new HashMap<>();
|
||||
private Pos rescueSafe;
|
||||
private int rescueFloorY;
|
||||
|
||||
|
||||
public JumpGenerator(Pos starting, Player player, InstanceContainer world) {
|
||||
this.player = player;
|
||||
@@ -40,13 +55,7 @@ public class JumpGenerator {
|
||||
}
|
||||
|
||||
public void generateNext() {
|
||||
this.generateNext(0);
|
||||
}
|
||||
|
||||
private void generateNext(int animationDelayTicks) {
|
||||
boolean isTenth = this.blocks.size() % 10 == 0;
|
||||
boolean standingTenth = (this.blocks.size()-this.preview-1) % 10 == 0;
|
||||
boolean isHundredth = this.blocks.size() % 100 == 0;
|
||||
|
||||
float pitch = 1 + (combo * 0.025f);
|
||||
this.player.playSound(Sound.sound(SoundEvent.BLOCK_NOTE_BLOCK_PLING, Sound.Source.MASTER, 0.1f, pitch));
|
||||
@@ -55,21 +64,13 @@ public class JumpGenerator {
|
||||
this.player.setLevel(score);
|
||||
ScoreTab.update(this.player, score);
|
||||
|
||||
Random r = new Random();
|
||||
int index = this.blocks.size();
|
||||
Pos previous = blocks.get(blocks.size()-1);
|
||||
Pos target = previous.add(this.nextOffset(index, r));
|
||||
Block block = isHundredth ? Block.DIAMOND_BLOCK : isTenth ? Block.GOLD_BLOCK : BlockPalette.getRandom(index, r);
|
||||
this.lastBlock = block;
|
||||
BlockTransition.animate(this.world, previous, target, block, animationDelayTicks, () -> {
|
||||
this.world.setBlock(target, block);
|
||||
this.world.setBlock(target.add(0, 1, 0), Block.LIGHT);
|
||||
this.player.sendPacket(new ParticlePacket(Particle.CRIT, target, target.add(0.5), 1, 10));
|
||||
this.player.sendPacket(new ParticlePacket(Particle.FALLING_NECTAR, target, target.add(0.5), 0.3f, 10));
|
||||
});
|
||||
if(score > 0 && score % 100 == 0) Jokers.addBridge(this.player);
|
||||
if(score > 0 && score % 200 == 0) Jokers.addTotem(this.player);
|
||||
|
||||
this.generateBlock(0);
|
||||
|
||||
if(standingTenth) {
|
||||
Pos current = this.blocks.get(this.blocks.size()-this.preview-1);
|
||||
Pos current = this.blocks.get(this.blocks.size()-this.preview-2);
|
||||
this.checkpoint = current;
|
||||
this.player.playSound(Sound.sound(SoundEvent.ENTITY_PLAYER_LEVELUP, Sound.Source.BLOCK, 0.3f, 1f));
|
||||
this.player.showTitle(Title.title(Component.text(""), Component.text("Checkpoint", TextColor.color(0, 255, 0))));
|
||||
@@ -77,18 +78,33 @@ public class JumpGenerator {
|
||||
}
|
||||
|
||||
// Abschnitt dauerhaft in der Actionbar (Titel werden von der Combo-Anzeige überschrieben)
|
||||
int standingIndex = Math.max(0, this.blocks.size()-this.preview-1);
|
||||
BlockPalette.Stage reachedStage = BlockPalette.stageStartingAt(standingIndex);
|
||||
BlockPalette.Stage reachedStage = BlockPalette.stageStartingAt(score);
|
||||
if(reachedStage != null && reachedStage.from() > 0) {
|
||||
this.player.playSound(Sound.sound(SoundEvent.UI_TOAST_CHALLENGE_COMPLETE, Sound.Source.MASTER, 0.4f, 1f));
|
||||
this.player.sendActionBar(Component.text(reachedStage.name(), TextColor.color(255, 170, 0)));
|
||||
} else {
|
||||
this.player.sendActionBar(Component.text(BlockPalette.stageAt(standingIndex).name(), TextColor.color(170, 170, 170)));
|
||||
this.player.sendActionBar(Component.text(BlockPalette.stageAt(score).name(), TextColor.color(170, 170, 170)));
|
||||
}
|
||||
this.blocks.add(target);
|
||||
this.combo++;
|
||||
}
|
||||
|
||||
/** Erzeugt den nächsten Sprungblock ohne Rückmeldung an den Spieler (Combo, Titel, Sounds). */
|
||||
private void generateBlock(int animationDelayTicks) {
|
||||
int index = this.blocks.size();
|
||||
Random r = new Random();
|
||||
Pos previous = blocks.getLast();
|
||||
Pos target = previous.add(this.nextOffset(index, r));
|
||||
Block block = index % 100 == 0 ? Block.DIAMOND_BLOCK : index % 10 == 0 ? Block.GOLD_BLOCK : BlockPalette.getRandom(index, r);
|
||||
this.lastBlock = block;
|
||||
BlockTransition.animate(this.world, previous, target, block, animationDelayTicks, () -> {
|
||||
this.world.setBlock(target, block);
|
||||
this.world.setBlock(target.add(0, 1, 0), Block.LIGHT);
|
||||
this.player.sendPacket(new ParticlePacket(Particle.CRIT, target, target.add(0.5), 1, 10));
|
||||
this.player.sendPacket(new ParticlePacket(Particle.FALLING_NECTAR, target, target.add(0.5), 0.3f, 10));
|
||||
});
|
||||
this.blocks.add(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstand zum nächsten Block. Sprünge sind immer mindestens 3 weit (natürliche Sprint-Weite,
|
||||
* kürzere werden überjumpt). Über die ersten {@link #FULL_DIFFICULTY_AT} Sprünge steigen der
|
||||
@@ -118,6 +134,123 @@ public class JumpGenerator {
|
||||
return new Pos(dx, dy, dz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Joker „Brücke“: erzeugt die Sprungblöcke bis zum nächsten Checkpoint und verbindet sie
|
||||
* mit einem begehbaren Weg. Die übersprungenen Blöcke zählen nicht zur Combo.
|
||||
*/
|
||||
public boolean useBridge() {
|
||||
if(this.rescueSafe != null) return false;
|
||||
|
||||
int standing = this.blocks.size() - this.preview - 2;
|
||||
int nextCheckpoint = (standing / 10 + 1) * 10;
|
||||
|
||||
// so weit vorgenerieren, dass der Checkpoint der nächste Landeblock ist
|
||||
int delay = 0;
|
||||
while(this.blocks.size() - this.preview - 1 < nextCheckpoint) {
|
||||
this.generateBlock(delay);
|
||||
delay += BRIDGE_STEP_TICKS;
|
||||
}
|
||||
|
||||
for(int i = standing; i < nextCheckpoint; i++) {
|
||||
Pos from = this.blocks.get(i);
|
||||
Pos to = this.blocks.get(i + 1);
|
||||
this.world.scheduler().buildTask(() -> this.buildPath(from, to))
|
||||
.delay(TaskSchedule.tick(1 + (i - standing) * BRIDGE_STEP_TICKS))
|
||||
.schedule();
|
||||
}
|
||||
|
||||
this.player.playSound(Sound.sound(SoundEvent.BLOCK_AMETHYST_BLOCK_CHIME, Sound.Source.MASTER, 1f, 1f));
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Weg auf Höhe des Startblocks; bei seitlichem Versatz zwei Blöcke breit, damit er durchgehend ist. */
|
||||
private void buildPath(Pos from, Pos to) {
|
||||
int minX = Math.min(from.blockX(), to.blockX());
|
||||
int maxX = Math.max(from.blockX(), to.blockX());
|
||||
for(int z = from.blockZ() + 1; z < to.blockZ(); z++) {
|
||||
for(int x = minX; x <= maxX; x++) {
|
||||
Pos pos = new Pos(x, from.blockY(), z);
|
||||
if(this.world.getBlock(pos).compare(Block.AIR)) this.world.setBlock(pos, BRIDGE_BLOCK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Wird bei jeder Bewegung aufgerufen: Rettung durch Totem oder Reset beim Runterfallen. */
|
||||
public void checkFall() {
|
||||
double y = this.player.getPosition().y();
|
||||
|
||||
if(this.rescueSafe != null) {
|
||||
if(y >= this.rescueSafe.y() + 0.9) this.endRescue();
|
||||
else if(y < this.rescueFloorY - 3) {
|
||||
this.endRescue();
|
||||
this.reset();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(y < this.getNextLanding().y() - 5 && y < this.checkpoint.y()) {
|
||||
if(Jokers.takeTotem(this.player)) this.rescue();
|
||||
else this.reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Joker „Rettungstotem“: Plattform unter dem zuletzt erreichten Block, davor eine Stützsäule
|
||||
* mit Leiter, über die man direkt wieder auf den Block klettert.
|
||||
*/
|
||||
private void rescue() {
|
||||
Pos safe = this.blocks.get(Math.max(0, this.blocks.size() - this.preview - 2));
|
||||
int x = safe.blockX();
|
||||
int z = safe.blockZ();
|
||||
this.rescueSafe = safe;
|
||||
this.rescueFloorY = safe.blockY() - RESCUE_DEPTH;
|
||||
|
||||
for(int dx = -1; dx <= 1; dx++) {
|
||||
for(int dz = -3; dz <= -1; dz++) this.placeRescue(new Pos(x + dx, this.rescueFloorY, z + dz), RESCUE_BLOCK);
|
||||
}
|
||||
// Säule direkt hinter dem Block bis auf seine Höhe, Leiter davor (hängt an der Säule)
|
||||
for(int y = this.rescueFloorY + 1; y <= safe.blockY(); y++) {
|
||||
this.placeRescue(new Pos(x, y, z - 1), RESCUE_BLOCK);
|
||||
this.placeRescue(new Pos(x, y, z - 2), Block.LADDER.withProperty("facing", "north"));
|
||||
}
|
||||
|
||||
this.player.setVelocity(Vec.ZERO);
|
||||
this.player.teleport(new Pos(x + 0.5, this.rescueFloorY + 1, z - 1.5, 0, 0));
|
||||
this.player.triggerStatus((byte) 35);
|
||||
this.player.playSound(Sound.sound(SoundEvent.ITEM_TOTEM_USE, Sound.Source.MASTER, 0.6f, 1f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt einen Rettungsblock. Brückenglas darf überschrieben werden, sonst könnte ein früherer
|
||||
* Brückenweg die Leiter von oben versperren. Sprungblöcke liegen nie im Rettungsbereich, da
|
||||
* Sprünge immer mindestens 3 Blöcke nach vorne gehen und die Rettung nur dahinter baut.
|
||||
*/
|
||||
private void placeRescue(Pos pos, Block block) {
|
||||
Block existing = this.world.getBlock(pos);
|
||||
if(!existing.compare(Block.AIR) && !existing.compare(BRIDGE_BLOCK)) return;
|
||||
this.world.setBlock(pos, block);
|
||||
this.rescueBlocks.put(pos, block);
|
||||
}
|
||||
|
||||
private void endRescue() {
|
||||
this.rescueSafe = null;
|
||||
Map<Pos, Block> toRemove = new HashMap<>(this.rescueBlocks);
|
||||
this.rescueBlocks.clear();
|
||||
// nur entfernen, was noch unverändert steht (der Spieler darf Rettungsblöcke abbauen)
|
||||
this.world.scheduler().buildTask(() -> toRemove.forEach((pos, block) -> {
|
||||
if(this.world.getBlock(pos).compare(block)) this.world.setBlock(pos, Block.AIR);
|
||||
})).delay(TaskSchedule.seconds(8)).schedule();
|
||||
}
|
||||
|
||||
/** Sprungblöcke und ihr Licht sind geschützt, alles andere (Brücke, Rettung) darf abgebaut werden. */
|
||||
public boolean isBreakable(Pos pos) {
|
||||
if(this.world.getBlock(pos).compare(Block.LIGHT)) return false;
|
||||
for(Pos block : this.blocks) {
|
||||
if(block.sameBlock(pos)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Pos getNextLanding() {
|
||||
return this.blocks.get(this.blocks.size()-(preview+1));
|
||||
}
|
||||
@@ -129,7 +262,7 @@ public class JumpGenerator {
|
||||
this.blocks.add(this.starting);
|
||||
this.world.setBlock(this.starting, Block.DIAMOND_BLOCK);
|
||||
// nacheinander animieren, damit jeder Block aus seinem bereits stehenden Vorgänger wächst
|
||||
for(int i = 0; i <= preview; i++) generateNext(i * BlockTransition.TOTAL_TICKS);
|
||||
for(int i = 0; i <= preview; i++) generateBlock(i * BlockTransition.TOTAL_TICKS);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
|
||||
Reference in new issue
Block a user