diff --git a/src/main/java/ru/windcorp/progressia/ProgressiaLauncher.java b/src/main/java/ru/windcorp/progressia/ProgressiaLauncher.java index c8935fc..099d378 100644 --- a/src/main/java/ru/windcorp/progressia/ProgressiaLauncher.java +++ b/src/main/java/ru/windcorp/progressia/ProgressiaLauncher.java @@ -15,21 +15,31 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package ru.windcorp.progressia; +import ru.windcorp.progressia.client.graphics.GUI; import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.util.crash.analyzers.OutOfMemoryAnalyzer; import ru.windcorp.progressia.common.util.crash.providers.*; +import ru.windcorp.progressia.test.LayerTitle; public class ProgressiaLauncher { public static String[] arguments; + private static Proxy proxy; public static void launch(String[] args, Proxy proxy) { arguments = args.clone(); setupCrashReports(); + proxy.initialize(); + ProgressiaLauncher.proxy = proxy; + GUI.addTopLayer(new LayerTitle("Title")); + } + + public static Proxy getProxy() { + return proxy; } private static void setupCrashReports() { diff --git a/src/main/java/ru/windcorp/progressia/client/ClientProxy.java b/src/main/java/ru/windcorp/progressia/client/ClientProxy.java index 1d154e7..ac9d61f 100644 --- a/src/main/java/ru/windcorp/progressia/client/ClientProxy.java +++ b/src/main/java/ru/windcorp/progressia/client/ClientProxy.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package ru.windcorp.progressia.client; import ru.windcorp.progressia.Proxy; @@ -30,7 +30,6 @@ import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; import ru.windcorp.progressia.client.localization.Localizer; import ru.windcorp.progressia.common.resource.ResourceManager; import ru.windcorp.progressia.common.util.crash.CrashReports; -import ru.windcorp.progressia.server.ServerState; import ru.windcorp.progressia.test.TestContent; import ru.windcorp.progressia.test.TestMusicPlayer; @@ -38,7 +37,9 @@ public class ClientProxy implements Proxy { @Override public void initialize() { + GraphicsBackend.initialize(); + try { RenderTaskQueue.waitAndInvoke(FlatRenderProgram::init); RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init); @@ -58,10 +59,6 @@ public class ClientProxy implements Proxy { AudioSystem.initialize(); - ServerState.startServer(); - ClientState.connectToLocalServer(); - TestMusicPlayer.start(); } - } diff --git a/src/main/java/ru/windcorp/progressia/client/ClientState.java b/src/main/java/ru/windcorp/progressia/client/ClientState.java index 31c366e..f473eb4 100644 --- a/src/main/java/ru/windcorp/progressia/client/ClientState.java +++ b/src/main/java/ru/windcorp/progressia/client/ClientState.java @@ -15,15 +15,18 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package ru.windcorp.progressia.client; import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel; import ru.windcorp.progressia.client.graphics.GUI; +import ru.windcorp.progressia.client.graphics.Layer; import ru.windcorp.progressia.client.graphics.world.LayerWorld; import ru.windcorp.progressia.common.world.DefaultWorldData; +import ru.windcorp.progressia.client.localization.MutableStringLocalized; import ru.windcorp.progressia.server.ServerState; import ru.windcorp.progressia.test.LayerAbout; +import ru.windcorp.progressia.test.LayerTestText; import ru.windcorp.progressia.test.LayerTestUI; import ru.windcorp.progressia.test.TestContent; @@ -52,11 +55,39 @@ public class ClientState { channel.connect(TestContent.PLAYER_LOGIN); setInstance(client); + displayLoadingScreen(); - GUI.addBottomLayer(new LayerWorld(client)); - GUI.addTopLayer(new LayerTestUI()); - GUI.addTopLayer(new LayerAbout()); + } + private static void displayLoadingScreen() { + GUI.addTopLayer(new LayerTestText("Text", new MutableStringLocalized("LayerText.Load"), layer -> { + Client client = ClientState.getInstance(); + + // TODO refacetor and remove + if (client != null) { + client.getComms().processPackets(); + } + + if (client != null && client.getLocalPlayer().hasEntity()) { + GUI.removeLayer(layer); + + // TODO refactor, this shouldn't be here + LayerWorld layerWorld = new LayerWorld(client); + LayerTestUI layerUI = new LayerTestUI(); + LayerAbout layerAbout = new LayerAbout(); + GUI.addBottomLayer(layerWorld); + GUI.addTopLayer(layerUI); + GUI.addTopLayer(layerAbout); + } + })); + } + + public static void disconnectFromLocalServer() { + getInstance().getComms().disconnect(); + + for (Layer layer : GUI.getLayers()) { + GUI.removeLayer(layer); + } } private ClientState() { diff --git a/src/main/java/ru/windcorp/progressia/client/comms/localhost/LocalClient.java b/src/main/java/ru/windcorp/progressia/client/comms/localhost/LocalClient.java index 816fba8..9fc0e47 100644 --- a/src/main/java/ru/windcorp/progressia/client/comms/localhost/LocalClient.java +++ b/src/main/java/ru/windcorp/progressia/client/comms/localhost/LocalClient.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package ru.windcorp.progressia.client.comms.localhost; import java.io.IOException; diff --git a/src/main/java/ru/windcorp/progressia/client/comms/localhost/LocalServerCommsChannel.java b/src/main/java/ru/windcorp/progressia/client/comms/localhost/LocalServerCommsChannel.java index 194a2a1..fcf8dd0 100644 --- a/src/main/java/ru/windcorp/progressia/client/comms/localhost/LocalServerCommsChannel.java +++ b/src/main/java/ru/windcorp/progressia/client/comms/localhost/LocalServerCommsChannel.java @@ -15,12 +15,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package ru.windcorp.progressia.client.comms.localhost; import ru.windcorp.progressia.client.comms.ServerCommsChannel; import ru.windcorp.progressia.common.comms.packets.Packet; import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.ServerState; public class LocalServerCommsChannel extends ServerCommsChannel { @@ -54,7 +55,7 @@ public class LocalServerCommsChannel extends ServerCommsChannel { @Override public void disconnect() { - // Do nothing + ServerState.getInstance().getClientManager().disconnectClient(localClient); } } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/GUI.java b/src/main/java/ru/windcorp/progressia/client/graphics/GUI.java index bb4d85b..59b89da 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/GUI.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/GUI.java @@ -15,12 +15,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package ru.windcorp.progressia.client.graphics; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import com.google.common.eventbus.Subscribe; @@ -58,6 +59,7 @@ public class GUI { } public static void addBottomLayer(Layer layer) { + Objects.requireNonNull(layer, "layer"); modify(layers -> { layers.add(layer); layer.onAdded(); @@ -65,6 +67,7 @@ public class GUI { } public static void addTopLayer(Layer layer) { + Objects.requireNonNull(layer, "layer"); modify(layers -> { layers.add(0, layer); layer.onAdded(); @@ -72,6 +75,7 @@ public class GUI { } public static void removeLayer(Layer layer) { + Objects.requireNonNull(layer, "layer"); modify(layers -> { layers.remove(layer); layer.onRemoved(); @@ -88,33 +92,33 @@ public class GUI { public static void render() { synchronized (LAYERS) { - + if (!MODIFICATION_QUEUE.isEmpty()) { MODIFICATION_QUEUE.forEach(action -> action.affect(LAYERS)); MODIFICATION_QUEUE.clear(); - + boolean isMouseCurrentlyCaptured = GraphicsInterface.isMouseCaptured(); Layer.CursorPolicy policy = Layer.CursorPolicy.REQUIRE; - + for (Layer layer : LAYERS) { Layer.CursorPolicy currentPolicy = layer.getCursorPolicy(); - + if (currentPolicy != Layer.CursorPolicy.INDIFFERENT) { policy = currentPolicy; break; } } - + boolean shouldCaptureMouse = (policy == Layer.CursorPolicy.FORBID); if (shouldCaptureMouse != isMouseCurrentlyCaptured) { GraphicsInterface.setMouseCaptured(shouldCaptureMouse); } } - + for (int i = LAYERS.size() - 1; i >= 0; --i) { LAYERS.get(i).render(); } - + } } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/BasicButton.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/BasicButton.java index 0ae328e..6b86627 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/gui/BasicButton.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/BasicButton.java @@ -37,15 +37,15 @@ import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign; import ru.windcorp.progressia.client.graphics.input.KeyEvent; public abstract class BasicButton extends Component { - + private final Label label; private boolean isPressed = false; private final Collection> actions = Collections.synchronizedCollection(new ArrayList<>()); - public BasicButton(String name, String label, Font labelFont) { + public BasicButton(String name, Label label) { super(name); - this.label = new Label(name + ".Label", labelFont, label); + this.label = label; setLayout(new LayoutAlign(10)); addChild(this.label); @@ -59,8 +59,8 @@ public abstract class BasicButton extends Component { return false; } else if ( e.isLeftMouseButton() || - e.getKey() == GLFW.GLFW_KEY_SPACE || - e.getKey() == GLFW.GLFW_KEY_ENTER + e.getKey() == GLFW.GLFW_KEY_SPACE || + e.getKey() == GLFW.GLFW_KEY_ENTER ) { setPressed(e.isPress()); return true; @@ -68,9 +68,9 @@ public abstract class BasicButton extends Component { return false; } }); - + addListener(new Object() { - + // Release when losing focus @Subscribe public void onFocusChange(FocusEvent e) { @@ -78,7 +78,7 @@ public abstract class BasicButton extends Component { setPressed(false); } } - + // Release when hover ends @Subscribe public void onHoverEnded(HoverEvent e) { @@ -86,7 +86,7 @@ public abstract class BasicButton extends Component { setPressed(false); } } - + // Release when disabled @Subscribe public void onDisabled(EnableEvent e) { @@ -94,16 +94,20 @@ public abstract class BasicButton extends Component { setPressed(false); } } - + // Trigger virtualClick when button is released @Subscribe public void onRelease(ButtonEvent.Release e) { virtualClick(); } - + }); } + public BasicButton(String name, String label, Font labelFont) { + this(name, new Label(name + ".Label", labelFont, label)); + } + public BasicButton(String name, String label) { this(name, label, new Font()); } @@ -111,7 +115,7 @@ public abstract class BasicButton extends Component { public boolean isPressed() { return isPressed; } - + public void click() { setPressed(true); setPressed(false); @@ -121,7 +125,7 @@ public abstract class BasicButton extends Component { if (this.isPressed != isPressed) { this.isPressed = isPressed; requestReassembly(); - + if (isPressed) { takeFocus(); } @@ -129,16 +133,16 @@ public abstract class BasicButton extends Component { dispatchEvent(ButtonEvent.create(this, this.isPressed)); } } - + public BasicButton addAction(Consumer action) { this.actions.add(Objects.requireNonNull(action, "action")); return this; } - + public boolean removeAction(Consumer action) { return this.actions.remove(action); } - + public void virtualClick() { this.actions.forEach(action -> { action.accept(this); @@ -148,5 +152,5 @@ public abstract class BasicButton extends Component { public Label getLabel() { return label; } - + } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/Button.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Button.java index bbeb361..5d42241 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/gui/Button.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Button.java @@ -28,7 +28,11 @@ public class Button extends BasicButton { public Button(String name, String label, Font labelFont) { super(name, label, labelFont); } - + + public Button(String name, Label label) { + super(name, label); + } + public Button(String name, String label) { this(name, label, new Font()); } @@ -36,7 +40,7 @@ public class Button extends BasicButton { @Override protected void assembleSelf(RenderTarget target) { // Border - + Vec4 borderColor; if (isPressed() || isHovered() || isFocused()) { borderColor = Colors.BLUE; @@ -44,9 +48,9 @@ public class Button extends BasicButton { borderColor = Colors.LIGHT_GRAY; } target.fill(getX(), getY(), getWidth(), getHeight(), borderColor); - + // Inside area - + if (isPressed()) { // Do nothing } else { @@ -58,20 +62,20 @@ public class Button extends BasicButton { } target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, backgroundColor); } - + // Change label font color - + if (isPressed()) { getLabel().setFont(getLabel().getFont().withColor(Colors.WHITE)); } else { getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK)); } } - + @Override protected void postAssembleSelf(RenderTarget target) { // Apply disable tint - + if (!isEnabled()) { target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF)); } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/Checkbox.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Checkbox.java index 5f9d0df..990ed43 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/gui/Checkbox.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Checkbox.java @@ -27,24 +27,24 @@ import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign; import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal; public class Checkbox extends BasicButton { - + private class Tick extends Component { public Tick() { super(Checkbox.this.getName() + ".Tick"); - + setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2)); } - + @Override protected void assembleSelf(RenderTarget target) { - + int size = getPreferredSize().x; int x = getX(); int y = getY() + (getHeight() - size) / 2; - + // Border - + Vec4 borderColor; if (Checkbox.this.isPressed() || Checkbox.this.isHovered() || Checkbox.this.isFocused()) { borderColor = Colors.BLUE; @@ -52,9 +52,9 @@ public class Checkbox extends BasicButton { borderColor = Colors.LIGHT_GRAY; } target.fill(x, y, size, size, borderColor); - + // Inside area - + if (Checkbox.this.isPressed()) { // Do nothing } else { @@ -66,9 +66,9 @@ public class Checkbox extends BasicButton { } target.fill(x + 2, y + 2, size - 4, size - 4, backgroundColor); } - + // "Tick" - + if (Checkbox.this.isChecked()) { target.fill(x + 4, y + 4, size - 8, size - 8, Colors.BLUE); } @@ -81,10 +81,10 @@ public class Checkbox extends BasicButton { public Checkbox(String name, String label, Font labelFont, boolean check) { super(name, label, labelFont); this.checked = check; - + assert getChildren().size() == 1 : "Checkbox expects that BasicButton contains exactly one child"; Component basicChild = getChild(0); - + Group group = new Group(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10)); removeChild(basicChild); setLayout(new LayoutAlign(0, 0.5f, 10)); @@ -92,18 +92,18 @@ public class Checkbox extends BasicButton { group.addChild(new Tick()); group.addChild(basicChild); addChild(group); - + addAction(b -> switchState()); } - + public Checkbox(String name, String label, Font labelFont) { this(name, label, labelFont, false); } - + public Checkbox(String name, String label, boolean check) { this(name, label, new Font(), check); } - + public Checkbox(String name, String label) { this(name, label, false); } @@ -111,14 +111,14 @@ public class Checkbox extends BasicButton { public void switchState() { setChecked(!isChecked()); } - + /** * @return the checked */ public boolean isChecked() { return checked; } - + /** * @param checked the checked to set */ @@ -129,21 +129,21 @@ public class Checkbox extends BasicButton { @Override protected void assembleSelf(RenderTarget target) { // Change label font color - + if (isPressed()) { getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE)); } else { getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK)); } } - + @Override protected void postAssembleSelf(RenderTarget target) { // Apply disable tint - + if (!isEnabled()) { target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF)); } } - + } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/RadioButton.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/RadioButton.java index 471efb6..1ee7f66 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/gui/RadioButton.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/RadioButton.java @@ -30,30 +30,30 @@ import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal; import ru.windcorp.progressia.client.graphics.input.KeyEvent; public class RadioButton extends BasicButton { - + private class Tick extends Component { public Tick() { super(RadioButton.this.getName() + ".Tick"); - + setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2)); } - + private void cross(RenderTarget target, int x, int y, int size, Vec4 color) { target.fill(x + 4, y, size - 8, size, color); target.fill(x + 2, y + 2, size - 4, size - 4, color); target.fill(x, y + 4, size, size - 8, color); } - + @Override protected void assembleSelf(RenderTarget target) { - + int size = getPreferredSize().x; int x = getX(); int y = getY() + (getHeight() - size) / 2; - + // Border - + Vec4 borderColor; if (RadioButton.this.isPressed() || RadioButton.this.isHovered() || RadioButton.this.isFocused()) { borderColor = Colors.BLUE; @@ -61,9 +61,9 @@ public class RadioButton extends BasicButton { borderColor = Colors.LIGHT_GRAY; } cross(target, x, y, size, borderColor); - + // Inside area - + if (RadioButton.this.isPressed()) { // Do nothing } else { @@ -75,9 +75,9 @@ public class RadioButton extends BasicButton { } cross(target, x + 2, y + 2, size - 4, backgroundColor); } - + // "Tick" - + if (RadioButton.this.isChecked()) { cross(target, x + 4, y + 4, size - 8, Colors.BLUE); } @@ -86,16 +86,16 @@ public class RadioButton extends BasicButton { } private boolean checked; - + private RadioButtonGroup group = null; public RadioButton(String name, String label, Font labelFont, boolean check) { super(name, label, labelFont); this.checked = check; - + assert getChildren().size() == 1 : "RadioButton expects that BasicButton contains exactly one child"; Component basicChild = getChild(0); - + Group group = new Group(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10)); removeChild(basicChild); setLayout(new LayoutAlign(0, 0.5f, 10)); @@ -103,16 +103,17 @@ public class RadioButton extends BasicButton { group.addChild(new Tick()); group.addChild(basicChild); addChild(group); - + addListener(KeyEvent.class, e -> { - if (e.isRelease()) return false; - + if (e.isRelease()) + return false; + if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) { if (this.group != null) { this.group.selectPrevious(); this.group.getSelected().takeFocus(); } - + return true; } else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) { if (this.group != null) { @@ -121,85 +122,87 @@ public class RadioButton extends BasicButton { } return true; } - + return false; }); - + addAction(b -> setChecked(true)); } - + public RadioButton(String name, String label, Font labelFont) { this(name, label, labelFont, false); } - + public RadioButton(String name, String label, boolean check) { this(name, label, new Font(), check); } - + public RadioButton(String name, String label) { this(name, label, false); } - + /** * @param group the group to set */ public RadioButton setGroup(RadioButtonGroup group) { - + if (this.group != null) { group.selectNext(); removeAction(group.listener); group.buttons.remove(this); - group.getSelected(); // Clear reference if this was the only button in the group + group.getSelected(); // Clear reference if this was the only button + // in the group } - + this.group = group; - + if (this.group != null) { group.buttons.add(this); addAction(group.listener); } - + setChecked(false); - + return this; } - + /** * @return the checked */ public boolean isChecked() { return checked; } - + /** * @param checked the checked to set */ public void setChecked(boolean checked) { this.checked = checked; - + if (group != null) { - group.listener.accept(this); // Failsafe for manual invocations of setChecked() + group.listener.accept(this); // Failsafe for manual invocations of + // setChecked() } } @Override protected void assembleSelf(RenderTarget target) { // Change label font color - + if (isPressed()) { getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE)); } else { getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK)); } } - + @Override protected void postAssembleSelf(RenderTarget target) { // Apply disable tint - + if (!isEnabled()) { target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF)); } } - + } diff --git a/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java b/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java index b43231b..634b05e 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java +++ b/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java @@ -162,5 +162,72 @@ public class Coordinates { public static boolean isOnChunkBorder(int blockInChunk) { return blockInChunk == 0 || blockInChunk == BLOCKS_PER_CHUNK - 1; } + + /* + * Generalized versions + */ + + public static int convertGlobalToCell(int bits, int global) { + return global >> bits; + } + + public static Vec3i convertGlobalToCell( + int bits, + Vec3i global, + Vec3i output + ) { + if (output == null) + output = new Vec3i(); + + output.x = convertGlobalToCell(bits, global.x); + output.y = convertGlobalToCell(bits, global.y); + output.z = convertGlobalToCell(bits, global.z); + + return output; + } + + public static int convertGlobalToInCell(int bits, int global) { + int mask = (1 << bits) - 1; + return global & mask; + } + + public static Vec3i convertGlobalToInCell( + int bits, + Vec3i global, + Vec3i output + ) { + if (output == null) + output = new Vec3i(); + + output.x = convertGlobalToInCell(bits, global.x); + output.y = convertGlobalToInCell(bits, global.y); + output.z = convertGlobalToInCell(bits, global.z); + + return output; + } + + public static int getGlobal(int bits, int cell, int inCell) { + return inCell | (cell << bits); + } + + public static Vec3i getGlobal( + int bits, + Vec3i cell, + Vec3i inCell, + Vec3i output + ) { + if (output == null) + output = new Vec3i(); + + output.x = getGlobal(bits, cell.x, inCell.x); + output.y = getGlobal(bits, cell.y, inCell.y); + output.z = getGlobal(bits, cell.z, inCell.z); + + return output; + } + + public static boolean isOnCellBorder(int bits, int inCell) { + return inCell == 0 || inCell == (1 << bits) - 1; + } } diff --git a/src/main/java/ru/windcorp/progressia/server/PlayerManager.java b/src/main/java/ru/windcorp/progressia/server/PlayerManager.java index f762778..5e94784 100644 --- a/src/main/java/ru/windcorp/progressia/server/PlayerManager.java +++ b/src/main/java/ru/windcorp/progressia/server/PlayerManager.java @@ -15,20 +15,21 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package ru.windcorp.progressia.server; +import glm.vec._3.Vec3; +import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.common.world.entity.EntityDataRegistry; +import ru.windcorp.progressia.server.comms.ClientPlayer; +import ru.windcorp.progressia.server.events.PlayerJoinedEvent; +import ru.windcorp.progressia.server.events.PlayerLeftEvent; +import ru.windcorp.progressia.test.TestContent; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import glm.vec._3.Vec3; -import ru.windcorp.progressia.common.util.crash.CrashReports; -import ru.windcorp.progressia.common.world.entity.EntityData; -import ru.windcorp.progressia.common.world.entity.EntityDataRegistry; -import ru.windcorp.progressia.server.events.PlayerJoinedEvent; -import ru.windcorp.progressia.test.TestContent; - public class PlayerManager { private final Server server; @@ -48,30 +49,37 @@ public class PlayerManager { getServer().postEvent(new PlayerJoinedEvent.Immutable(getServer(), player)); } - public EntityData conjurePlayerEntity(String login) { - // TODO Live up to the name - if (TestContent.PLAYER_LOGIN.equals(login)) { - EntityData entity = spawnPlayerEntity(login); - return entity; - } else { - throw CrashReports.report(null, "Unknown login %s, javahorse stupid", login); - } + public void removePlayer(Player player) { + server.getWorld().getContainer().savePlayer(player, server); + this.players.remove(player); + getServer().postEvent(new PlayerLeftEvent.Immutable(getServer(), player)); } - private EntityData spawnPlayerEntity(String login) { + public Player conjurePlayer(ClientPlayer clientPlayer, String login) { + + Player player = getServer().getWorld().getContainer().loadPlayer(login, clientPlayer, getServer()); + if (player == null) { // create new player + EntityData entity = spawnPlayerEntity(clientPlayer, login); + player = new Player(entity, getServer(), clientPlayer); + } + + getServer().getWorld().getData().addEntity(player.getEntity()); + + return player; + } + + private EntityData spawnPlayerEntity(ClientPlayer clientPlayer, String login) { EntityData player = EntityDataRegistry.getInstance().create("Test:Player"); player.setEntityId(TestContent.PLAYER_ENTITY_ID); player.setPosition(getServer().getWorld().getGenerator().suggestSpawnLocation()); - + player.setUpVector(new Vec3(0, 0, 1)); player.setLookingAt(new Vec3(2, 1, 0)); - getServer().getWorld().getData().addEntity(player); - return player; } - + public Object getMutex() { return players; } diff --git a/src/main/java/ru/windcorp/progressia/server/Server.java b/src/main/java/ru/windcorp/progressia/server/Server.java index ea8504f..518adea 100644 --- a/src/main/java/ru/windcorp/progressia/server/Server.java +++ b/src/main/java/ru/windcorp/progressia/server/Server.java @@ -21,8 +21,6 @@ package ru.windcorp.progressia.server; import java.util.function.Consumer; import java.util.function.Function; -import org.apache.logging.log4j.LogManager; - import com.google.common.eventbus.EventBus; import glm.vec._3.i.Vec3i; @@ -46,6 +44,7 @@ import ru.windcorp.progressia.server.world.context.impl.DefaultServerContext; import ru.windcorp.progressia.server.world.context.impl.ReportingServerContext; import ru.windcorp.progressia.server.world.context.impl.RotatingServerContext; import ru.windcorp.progressia.server.world.generation.WorldGenerator; +import ru.windcorp.progressia.server.world.io.WorldContainer; import ru.windcorp.progressia.server.world.tasks.WorldAccessor; import ru.windcorp.progressia.server.world.ticking.Change; import ru.windcorp.progressia.server.world.ticking.Evaluation; @@ -78,11 +77,16 @@ public class Server { private final TickingSettings tickingSettings = new TickingSettings(); - public Server(DefaultWorldData world, Function generatorCreator) { + public Server( + DefaultWorldData world, + Function generatorCreator, + WorldContainer worldContainer + ) { this.world = new DefaultWorldLogic( world, this, generatorCreator.apply(this), + worldContainer, worldAccessor ); this.serverThread = new ServerThread(this); @@ -241,14 +245,14 @@ public class Server { public void scheduleChange(Change change) { serverThread.getTicker().requestChange(change); } - + /** * Delayed */ public void scheduleEvaluation(Evaluation evaluation) { serverThread.getTicker().requestEvaluation(evaluation); } - + /** * Immediate if possible, otherwise delayed */ @@ -347,8 +351,8 @@ public class Server { * reason */ public void shutdown(String message) { - LogManager.getLogger().warn("Server.shutdown() is not yet implemented"); serverThread.stop(); + getWorld().getContainer().close(); } private void scheduleWorldTicks(Server server) { diff --git a/src/main/java/ru/windcorp/progressia/server/ServerState.java b/src/main/java/ru/windcorp/progressia/server/ServerState.java index 00aa901..41b2c6b 100644 --- a/src/main/java/ru/windcorp/progressia/server/ServerState.java +++ b/src/main/java/ru/windcorp/progressia/server/ServerState.java @@ -18,7 +18,14 @@ package ru.windcorp.progressia.server; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.function.Function; + import ru.windcorp.progressia.common.world.DefaultWorldData; +import ru.windcorp.progressia.server.world.generation.WorldGenerator; +import ru.windcorp.progressia.server.world.io.WorldContainer; +import ru.windcorp.progressia.server.world.io.region.RegionFormat; import ru.windcorp.progressia.test.gen.TestGenerationConfig; public class ServerState { @@ -33,10 +40,15 @@ public class ServerState { ServerState.instance = instance; } - public static void startServer() { - Server server = new Server(new DefaultWorldData(), TestGenerationConfig.createGenerator()); + public static void startServer() throws IOException { + + Function generator = new TestGenerationConfig().getGenerator(); + WorldContainer container = new RegionFormat("Test:Region").create(Paths.get("tmp_world")); + + Server server = new Server(new DefaultWorldData(), generator, container); setInstance(server); server.start(); + } private ServerState() { diff --git a/src/main/java/ru/windcorp/progressia/server/ServerThread.java b/src/main/java/ru/windcorp/progressia/server/ServerThread.java index 90f3ce9..aa0ff67 100644 --- a/src/main/java/ru/windcorp/progressia/server/ServerThread.java +++ b/src/main/java/ru/windcorp/progressia/server/ServerThread.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package ru.windcorp.progressia.server; import java.util.concurrent.Executors; @@ -26,10 +26,13 @@ import org.apache.logging.log4j.LogManager; import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.server.world.ticking.TickerCoordinator; +@SuppressWarnings("unused") public class ServerThread implements Runnable { private static final ThreadLocal SERVER_THREADS_MAP = new ThreadLocal<>(); + private static boolean isShuttingDown; + public static Server getCurrentServer() { return SERVER_THREADS_MAP.get(); } @@ -63,6 +66,7 @@ public class ServerThread implements Runnable { } public void start() { + isShuttingDown = false; ticker.start(); executor.scheduleAtFixedRate(this, 0, 1000 / 20, TimeUnit.MILLISECONDS); } @@ -70,6 +74,11 @@ public class ServerThread implements Runnable { @Override public void run() { try { + if (isShuttingDown) { + getTicker().stop(); + executor.shutdown(); + return; + } server.tick(); ticker.runOneTick(); } catch (Throwable e) { @@ -78,13 +87,10 @@ public class ServerThread implements Runnable { } public void stop() { - try { - executor.awaitTermination(10, TimeUnit.MINUTES); - } catch (InterruptedException e) { - LogManager.getLogger().warn("Received interrupt in ServerThread.stop(), aborting wait"); - } - getTicker().stop(); + isShuttingDown = true; + + // getTicker().stop(); } public Server getServer() { diff --git a/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java b/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java index dbcf081..dd2de69 100644 --- a/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java +++ b/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java @@ -31,7 +31,6 @@ import ru.windcorp.progressia.common.comms.CommsChannel.State; import ru.windcorp.progressia.common.comms.packets.Packet; import ru.windcorp.progressia.common.world.PacketSetGravityModel; import ru.windcorp.progressia.common.world.PacketSetLocalPlayer; -import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.server.Player; import ru.windcorp.progressia.server.Server; @@ -80,18 +79,20 @@ public class ClientManager { setGravityModelPacket.set(getServer().getWorld().getData().getGravityModel()); client.sendPacket(setGravityModelPacket); - EntityData entity = getServer().getPlayerManager().conjurePlayerEntity(login); - Player player = new Player(entity, getServer(), client); + Player player = getServer().getPlayerManager().conjurePlayer(client, login); getServer().getPlayerManager().addPlayer(player); PacketSetLocalPlayer packet = new PacketSetLocalPlayer(); - packet.set(entity.getEntityId()); + packet.set(player.getEntity().getEntityId()); client.sendPacket(packet); } public void disconnectClient(Client client) { client.disconnect(); clientsById.remove(client.getId()); + if (client instanceof ClientPlayer) { + getServer().getPlayerManager().removePlayer(((ClientPlayer) client).getPlayer()); + } } public void processPackets() { diff --git a/src/main/java/ru/windcorp/progressia/server/management/load/ChunkManager.java b/src/main/java/ru/windcorp/progressia/server/management/load/ChunkManager.java index 9513aa2..1354cf9 100644 --- a/src/main/java/ru/windcorp/progressia/server/management/load/ChunkManager.java +++ b/src/main/java/ru/windcorp/progressia/server/management/load/ChunkManager.java @@ -28,7 +28,7 @@ import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.DefaultWorldData; import ru.windcorp.progressia.server.Player; import ru.windcorp.progressia.server.Server; -import ru.windcorp.progressia.test.TestWorldDiskIO; +import ru.windcorp.progressia.server.world.io.WorldContainer; /** * Chunk manager provides facilities to load, unload and generate chunks for a @@ -55,6 +55,10 @@ public class ChunkManager { public Server getServer() { return getLoadManager().getServer(); } + + public WorldContainer getContainer() { + return getServer().getWorld().getContainer(); + } /** * Describes the result of an attempt to load a chunk. @@ -119,7 +123,7 @@ public class ChunkManager { DefaultWorldData world = getServer().getWorld().getData(); - DefaultChunkData chunk = TestWorldDiskIO.tryToLoad(chunkPos, world, getServer()); + DefaultChunkData chunk = getServer().getWorld().getContainer().load(chunkPos, world, getServer()); if (chunk != null) { world.addChunk(chunk); return LoadResult.LOADED_FROM_DISK; @@ -150,7 +154,7 @@ public class ChunkManager { entitiesToRemove.forEach(world::removeEntity); world.removeChunk(chunk); - TestWorldDiskIO.saveChunk(chunk, getServer()); + getContainer().save(chunk, getServer().getWorld().getData(), getServer()); return true; } diff --git a/src/main/java/ru/windcorp/progressia/server/world/DefaultWorldLogic.java b/src/main/java/ru/windcorp/progressia/server/world/DefaultWorldLogic.java index bbaa4d3..d2e9a6b 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/DefaultWorldLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/DefaultWorldLogic.java @@ -33,6 +33,7 @@ import ru.windcorp.progressia.common.world.WorldDataListener; import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.world.generation.WorldGenerator; +import ru.windcorp.progressia.server.world.io.WorldContainer; import ru.windcorp.progressia.server.world.tasks.TickEntitiesTask; import ru.windcorp.progressia.server.world.tasks.WorldAccessor; import ru.windcorp.progressia.server.world.ticking.Evaluation; @@ -43,17 +44,20 @@ public class DefaultWorldLogic implements WorldLogic { private final Server server; private final WorldGenerator generator; + private final WorldContainer container; private final Map chunks = new HashMap<>(); private final Evaluation tickEntitiesTask = new TickEntitiesTask(); - public DefaultWorldLogic(DefaultWorldData data, Server server, WorldGenerator generator, WorldAccessor accessor) { + public DefaultWorldLogic(DefaultWorldData data, Server server, WorldGenerator generator, WorldContainer container, WorldAccessor accessor) { this.data = data; this.server = server; this.generator = generator; data.setGravityModel(getGenerator().getGravityModel()); + + this.container = container; data.addListener(new WorldDataListener() { @Override @@ -127,6 +131,10 @@ public class DefaultWorldLogic implements WorldLogic { public WorldGenerator getGenerator() { return generator; } + + public WorldContainer getContainer() { + return container; + } public DefaultChunkData generate(Vec3i chunkPos) { DefaultChunkData chunk = getGenerator().generate(chunkPos); diff --git a/src/main/java/ru/windcorp/progressia/server/world/io/WorldContainer.java b/src/main/java/ru/windcorp/progressia/server/world/io/WorldContainer.java new file mode 100644 index 0000000..42451ad --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/io/WorldContainer.java @@ -0,0 +1,43 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.server.world.io; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.DefaultChunkData; +import ru.windcorp.progressia.common.world.DefaultWorldData; +import ru.windcorp.progressia.server.Player; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.comms.ClientPlayer; + +import java.nio.file.Path; + +public interface WorldContainer { + + Path getPath(); + + DefaultChunkData load(Vec3i position, DefaultWorldData world, Server server); + + void save(DefaultChunkData chunk, DefaultWorldData world, Server server); + + Player loadPlayer(String login, ClientPlayer clientPlayer, Server server); + + void savePlayer(Player player, Server server); + + void close(); + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/io/WorldContainerFormat.java b/src/main/java/ru/windcorp/progressia/server/world/io/WorldContainerFormat.java new file mode 100644 index 0000000..4c89621 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/io/WorldContainerFormat.java @@ -0,0 +1,33 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.server.world.io; + +import java.io.IOException; +import java.nio.file.Path; + +import ru.windcorp.progressia.common.util.namespaces.Namespaced; + +public abstract class WorldContainerFormat extends Namespaced { + + public WorldContainerFormat(String id) { + super(id); + } + + public abstract WorldContainer create(Path directory) throws IOException; + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/io/region/Region.java b/src/main/java/ru/windcorp/progressia/server/world/io/region/Region.java new file mode 100644 index 0000000..b25535c --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/io/region/Region.java @@ -0,0 +1,166 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.server.world.io.region; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.InflaterInputStream; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.state.IOContext; +import ru.windcorp.progressia.common.world.DecodingException; +import ru.windcorp.progressia.common.world.DefaultChunkData; +import ru.windcorp.progressia.common.world.DefaultWorldData; +import ru.windcorp.progressia.common.world.generic.ChunkMap; +import ru.windcorp.progressia.common.world.generic.ChunkMaps; +import ru.windcorp.progressia.common.world.io.ChunkIO; +import ru.windcorp.progressia.server.Server; + +public class Region { + + private static final boolean RESET_CORRUPTED = true; + + public int loadedChunks; + + private AtomicBoolean isUsing = new AtomicBoolean(false); + private AtomicBoolean isClosed = new AtomicBoolean(false); + + private final RegionFile file; + + private final ChunkMap offsets = ChunkMaps.newHashMap(); + + public Region(RandomAccessFile file, Vec3i regionCoords) throws IOException { + this.file = new RegionFile(file); + + try { + this.file.confirmHeaderHealth(offsets, regionCoords); + } catch (IOException e) { + + RegionWorldContainer.LOG.debug("Uh the file broke"); + if (RESET_CORRUPTED) { + this.file.makeHeader(regionCoords); + } + + } + } + + public RegionFile getFile() { + return file; + } + + public void close() throws IOException { + this.file.close(); + isClosed.lazySet(true); + } + + public int getOffset(Vec3i chunkLoc) { + return offsets.get(chunkLoc); + } + + public boolean hasOffset(Vec3i pos) { + return offsets.containsKey(pos); + } + + public void putOffset(Vec3i pos, int offset) { + offsets.put(pos, offset); + } + + public AtomicBoolean isClosed() { + return isClosed; + } + + public AtomicBoolean isUsing() { + return isUsing; + } + + public void save(DefaultChunkData chunk, Server server) throws IOException { + isUsing.set(true); + Vec3i pos = RegionWorldContainer.getInRegionCoords(chunk.getPosition()); + + if (!hasOffset(pos)) { + putOffset(pos, file.allocateChunk(pos)); + } + int dataOffset = getOffset(pos); + + byte[] buffer = saveToBuffer(chunk, server); + + file.writeBuffer(buffer, dataOffset, pos); + isUsing.set(false); + } + + private byte[] saveToBuffer(DefaultChunkData chunk, Server server) throws IOException { + ByteArrayOutputStream arrayStream = new ByteArrayOutputStream(); + try ( + DataOutputStream dataStream = new DataOutputStream( + new DeflaterOutputStream( + new BufferedOutputStream(arrayStream) + ) + ) + ) { + ChunkIO.save(chunk, dataStream, IOContext.SAVE); + RegionWorldContainer.writeGenerationHint(chunk, dataStream, server); + } + + return arrayStream.toByteArray(); + } + + public DefaultChunkData load(Vec3i chunkPos, DefaultWorldData world, Server server) + throws IOException, + DecodingException { + isUsing.set(true); + + int dataOffset = 0; + Vec3i pos = RegionWorldContainer.getInRegionCoords(chunkPos); + + if (hasOffset(pos)) { + dataOffset = getOffset(pos); + } else { + return null; + } + + byte[] buffer = file.readBuffer(dataOffset); + DefaultChunkData result = loadFromBuffer(buffer, chunkPos, world, server); + isUsing.set(false); + return result; + } + + private DefaultChunkData loadFromBuffer(byte[] buffer, Vec3i chunkPos, DefaultWorldData world, Server server) + throws IOException, + DecodingException { + + DataInputStream dataStream = new DataInputStream( + new InflaterInputStream( + new BufferedInputStream( + new ByteArrayInputStream(buffer) + ) + ) + ); + + DefaultChunkData result = ChunkIO.load(world, chunkPos, dataStream, IOContext.SAVE); + RegionWorldContainer.readGenerationHint(result, dataStream, server); + return result; + } +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/server/world/io/region/RegionFile.java b/src/main/java/ru/windcorp/progressia/server/world/io/region/RegionFile.java new file mode 100644 index 0000000..520d24e --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/io/region/RegionFile.java @@ -0,0 +1,268 @@ +package ru.windcorp.progressia.server.world.io.region; + +import static ru.windcorp.progressia.server.world.io.region.RegionWorldContainer.REGION_DIAMETER; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.util.HashSet; +import java.util.Set; + +import org.apache.logging.log4j.LogManager; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.generic.ChunkMap; + +/** + * Backend for the .progressia_region file. + * Use similarly to a file object + */ +public class RegionFile { + // 4 MiB + private static final int MAX_CHUNK_SIZE = 4 * 1024 * 1024; + private static final int SECTORS_BYTES = Short.BYTES; + private static final int SECTOR_SIZE = MAX_CHUNK_SIZE >> (SECTORS_BYTES * 8); + private static final int SECTOR_HEADER_SIZE = 1; + private static final int ID_HEADER_SIZE = 16; + private static final byte[] HEADER_ID = {'P','R','O','G'}; + + final byte endBytes[] = new byte[SECTOR_SIZE]; + + public static enum SectorType { + Ending(0), // Just an empty block + Data(1), // has a byte counting up in position 1, and then + PartitionLink(2), + BulkSaved(3); // TODO implement this + + private final byte data; + + SectorType(int i) { + this.data = (byte) i; + } + + } + + private static final int DEFINITION_SIZE = Integer.BYTES; + + private static final int HEADER_SIZE = DEFINITION_SIZE * REGION_DIAMETER * REGION_DIAMETER * REGION_DIAMETER + ID_HEADER_SIZE; + + private final RandomAccessFile file; + + public RegionFile(RandomAccessFile inFile) { + file = inFile; + } + + public void confirmHeaderHealth(ChunkMap offsets, Vec3i regionCoords) throws IOException { + + Set used = new HashSet(); + int maxUsed = 0; + final int chunksPerRegion = REGION_DIAMETER * REGION_DIAMETER * REGION_DIAMETER; + + if (file.length() < HEADER_SIZE) { + throw new IOException("File is too short to contain a header"); + } + + + char prog; + for (int i=0;i<4;i++) { + prog = file.readChar(); + if (prog != HEADER_ID[i]) + { + throw new IOException("File is not a .progressia_chunk file"); + } + } + + int tempX = file.readInt(); + int tempY = file.readInt(); + int tempZ = file.readInt(); + + if (regionCoords.x != tempX || regionCoords.y != tempY || regionCoords.z != tempZ) + { + throw new IOException("Region file is in the wrong place/ has the wrong name."); + } + + for (int i = 0; i < chunksPerRegion; i++) { + file.seek(i * DEFINITION_SIZE + ID_HEADER_SIZE); + int offset = file.readInt(); + + if (offset == 0) { + continue; + } + + offset--; + + Vec3i pos = new Vec3i(); + pos.x = i / REGION_DIAMETER / REGION_DIAMETER; + pos.y = (i / REGION_DIAMETER) % REGION_DIAMETER; + pos.z = i % REGION_DIAMETER; + + offsets.put(pos, offset); + + boolean shouldEnd = false; + byte counter = 0; + while (!shouldEnd) { + if (offset > maxUsed) { + maxUsed = offset; + } + + if (!used.add(offset)) { + throw new IOException("A sector is used twice"); + } + + file.seek(HEADER_SIZE + SECTOR_SIZE * offset); + byte type = file.readByte(); + + if (type == SectorType.Data.data) { + byte fileCounter = file.readByte(); + if (fileCounter != counter) { + throw new IOException("An unexpected block was found"); + } + counter++; + offset++; + } else if (type == SectorType.Ending.data) { + shouldEnd = true; + } else if (type == SectorType.PartitionLink.data) { + offset = file.readInt(); + } + + } + } + LogManager.getLogger("Region").debug("Efficiency of {}", (double) used.size() / maxUsed); + } + + public void makeHeader(Vec3i regionCoords) throws IOException { + file.seek(0); + file.write(HEADER_ID); + file.writeInt(regionCoords.x); + file.writeInt(regionCoords.y); + file.writeInt(regionCoords.z); + for (int i = 0; i < HEADER_SIZE; i++) { + file.write(0); + } + } + + public void writeBuffer(byte[] buffer, int dataOffset, Vec3i pos) throws IOException { + file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset); + int loc = 0; + byte tempBuffer[] = new byte[SECTOR_SIZE]; + byte counter = 0; + boolean isDone = false; + while (!isDone) { + if (file.length() > HEADER_SIZE + SECTOR_SIZE * (dataOffset + 1)) { + file.seek(HEADER_SIZE + SECTOR_SIZE * (dataOffset + 1)); + byte header = file.readByte(); + if (header == SectorType.Data.data) { + byte fileCounter = file.readByte(); + if (fileCounter != counter + 1) // This makes the actual + // partition place + { + int newOffset = allocateEmptySector(); + file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset); + file.write(2); + file.writeInt(newOffset); + dataOffset = newOffset; + } + } + } + tempBuffer[0] = 1; + tempBuffer[1] = counter; + counter++; + for (int i = 0; i < (SECTOR_SIZE - SECTOR_HEADER_SIZE - 1); i++) { + if (loc * (SECTOR_SIZE - SECTOR_HEADER_SIZE - 1) + i < buffer.length) { + tempBuffer[i + SECTOR_HEADER_SIZE + 1] = buffer[loc * (SECTOR_SIZE - SECTOR_HEADER_SIZE - 1) + i]; + } else { + isDone = true; + break; + } + } + loc++; + if (file.getFilePointer() < 256) + LogManager.getLogger("Region") + .debug("at {}, ({},{},{}), {}", file.getFilePointer(), pos.x, pos.y, pos.z, dataOffset); + file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset); + dataOffset++; + file.write(tempBuffer); + } + + file.write(endBytes); + } + + public int allocateChunk(Vec3i pos) throws IOException { + int definitionOffset = ID_HEADER_SIZE + DEFINITION_SIZE * (pos.z + REGION_DIAMETER * (pos.y + REGION_DIAMETER * pos.x)); + + int outputLen = (int) file.length(); + + int dataOffset = (int) Math.ceil((double) (outputLen - HEADER_SIZE) / SECTOR_SIZE); + + file.seek(definitionOffset); + file.writeInt(dataOffset + 1); + + file.setLength(HEADER_SIZE + dataOffset * SECTOR_SIZE); + return dataOffset; + } + + private int allocateEmptySector() throws IOException { + int outputLen = (int) file.length(); + + int dataOffset = (int) Math.ceil((double) (outputLen - HEADER_SIZE) / SECTOR_SIZE); + + file.setLength(HEADER_SIZE + dataOffset * SECTOR_SIZE); + + return dataOffset; + } + + public byte[] readBuffer(int dataOffset) throws IOException { + file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset); + + int bufferPos = 0; + byte buffer[] = new byte[SECTOR_SIZE * 16]; + byte tempBuffer[] = new byte[SECTOR_SIZE]; + + boolean reachedEnd = false; + byte counter = 0; + while (!reachedEnd) { + int bytesRead = file.read(tempBuffer, 0, SECTOR_SIZE); + if (bytesRead < 0) { + reachedEnd = true; + continue; + } + if (tempBuffer[0] == SectorType.Data.data) { + if (tempBuffer[1] != counter) { + throw new IOException( + "Sectors were read out of order\nExpected chunk number " + Byte.toString(counter) + + " but encountered number " + Byte.toString(tempBuffer[1]) + ); + } + counter++; + if (buffer.length - bufferPos < SECTOR_SIZE - SECTOR_HEADER_SIZE - 1) { + byte newBuffer[] = new byte[buffer.length + SECTOR_SIZE * 16]; + for (int i = 0; i < buffer.length; i++) // TODO dedicated + // copy, java-y at + // least + { + newBuffer[i] = buffer[i]; + } + buffer = newBuffer; + } + for (int i = 0; i < SECTOR_SIZE - SECTOR_HEADER_SIZE - 1; i++) { + buffer[bufferPos + i] = tempBuffer[i + 2]; + } + bufferPos += SECTOR_SIZE - SECTOR_HEADER_SIZE - 1; + } else if (tempBuffer[0] == SectorType.Ending.data) { + reachedEnd = true; + } else if (tempBuffer[0] == SectorType.PartitionLink.data) { + ByteBuffer intBuffer = ByteBuffer.wrap(tempBuffer); + int newOffset = intBuffer.getInt(1); + file.seek(HEADER_SIZE + SECTOR_SIZE * newOffset); + } else { + throw new IOException("Invalid sector ID."); + } + } + return buffer; + } + + public void close() throws IOException { + file.close(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/io/region/RegionFormat.java b/src/main/java/ru/windcorp/progressia/server/world/io/region/RegionFormat.java new file mode 100644 index 0000000..89c5361 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/io/region/RegionFormat.java @@ -0,0 +1,37 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.server.world.io.region; + +import java.io.IOException; +import java.nio.file.Path; + +import ru.windcorp.progressia.server.world.io.WorldContainer; +import ru.windcorp.progressia.server.world.io.WorldContainerFormat; + +public class RegionFormat extends WorldContainerFormat { + + public RegionFormat(String id) { + super(id); + } + + @Override + public WorldContainer create(Path directory) throws IOException { + return new RegionWorldContainer(directory); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/io/region/RegionWorldContainer.java b/src/main/java/ru/windcorp/progressia/server/world/io/region/RegionWorldContainer.java new file mode 100644 index 0000000..ca83027 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/io/region/RegionWorldContainer.java @@ -0,0 +1,266 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package ru.windcorp.progressia.server.world.io.region; + +import java.util.concurrent.atomic.AtomicBoolean; + +import glm.vec._3.i.Vec3i; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import ru.windcorp.progressia.common.state.IOContext; +import ru.windcorp.progressia.common.util.crash.CrashReports; +import ru.windcorp.progressia.common.world.Coordinates; +import ru.windcorp.progressia.common.world.DecodingException; +import ru.windcorp.progressia.common.world.DefaultChunkData; +import ru.windcorp.progressia.common.world.DefaultWorldData; +import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.common.world.entity.EntityDataRegistry; +import ru.windcorp.progressia.common.world.generic.ChunkMap; +import ru.windcorp.progressia.common.world.generic.ChunkMaps; +import ru.windcorp.progressia.server.Player; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.comms.ClientPlayer; +import ru.windcorp.progressia.server.world.io.WorldContainer; +import ru.windcorp.progressia.test.TestContent; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; + +public class RegionWorldContainer implements WorldContainer { + + private static final boolean ENABLE = true; + + private static final String REGION_FOLDER_NAME = "regions"; + private static final String PLAYERS_FOLDER_NAME = "players"; + private static final String REGION_NAME_FORMAT = REGION_FOLDER_NAME + "/" + "region_%d_%d_%d.progressia_region"; + private static final String PLAYER_NAME_FORMAT = PLAYERS_FOLDER_NAME + "/" + "%s.progressia_player"; + + private static final int BITS_IN_CHUNK_COORDS = 4; + public static final int REGION_DIAMETER = 1 << BITS_IN_CHUNK_COORDS; + + public static Vec3i getRegionCoords(Vec3i chunkCoords) { + return Coordinates.convertGlobalToCell(BITS_IN_CHUNK_COORDS, chunkCoords, null); + } + + public static Vec3i getInRegionCoords(Vec3i chunkCoords) { + return Coordinates.convertGlobalToInCell(BITS_IN_CHUNK_COORDS, chunkCoords, null); + } + + static final Logger LOG = LogManager.getLogger("TestWorldDiskIO"); + + private final Path path; + private final ChunkMap regions = ChunkMaps.newHashMap(); + + public RegionWorldContainer(Path path) throws IOException { + this.path = path; + + Files.createDirectories(getPath()); + Files.createDirectories(getPath().resolve(REGION_FOLDER_NAME)); + Files.createDirectories(getPath().resolve(PLAYERS_FOLDER_NAME)); + } + + @Override + public DefaultChunkData load(Vec3i chunkPos, DefaultWorldData world, Server server) { + if (!ENABLE) { + return null; + } + + try { + + Region region = getRegion(chunkPos, false); + if (region == null) { + debug("Could not load chunk {} {} {}: region did not load", chunkPos); + return null; + } + + DefaultChunkData result = region.load(chunkPos, world, server); + return result; + + } catch (IOException | DecodingException e) { + warn("Failed to load chunk {} {} {}", chunkPos); + e.printStackTrace(); + return null; + } + } + + @Override + public void save(DefaultChunkData chunk, DefaultWorldData world, Server server) { + if (!ENABLE) { + return; + } + + try { + debug("Saving chunk {} {} {}", chunk.getPosition()); + Region region = getRegion(chunk.getPosition(), true); + region.save(chunk, server); + } catch (IOException e) { + warn("Failed to save chunk {} {} {}", chunk.getPosition()); + e.printStackTrace(); + } + } + + @Override + public Player loadPlayer(String login, ClientPlayer clientPlayer, Server server) { + + Path path = getPlayerPath(login); + if (!Files.exists(path)) { + LOG.debug("Could not load player {} because file {} does not exist", login, path); + return null; + } + + EntityData player = EntityDataRegistry.getInstance().create("Test:Player"); + try ( + DataInputStream dataInputStream = new DataInputStream( + new BufferedInputStream( + Files.newInputStream( + getPlayerPath(login) + ) + ) + ) + ) { + player.read(dataInputStream, IOContext.SAVE); + player.setEntityId(TestContent.PLAYER_ENTITY_ID); + return new Player(player, server, clientPlayer); + } catch (IOException ioException) { + throw CrashReports.report(ioException, "Could not load player data: " + login); + } + } + + @Override + public void savePlayer(Player player, Server server) { + Path path = getPlayerPath(player.getLogin()); + try ( + DataOutputStream dataOutputStream = new DataOutputStream( + new BufferedOutputStream( + Files.newOutputStream(path) + ) + ) + ) { + player.getEntity(). + + write(dataOutputStream, IOContext.SAVE); + } catch (IOException ioException) { + throw CrashReports.report(ioException, "Could not save player %s data in file ", player.getLogin(), path); + } + } + + private Region getRegion(Vec3i position, boolean createIfMissing) throws IOException { + + Vec3i regionCoords = getRegionCoords(position); + + Region region = regions.get(regionCoords); + if (region == null) { + debug("Region {} {} {} is not loaded, loading", regionCoords); + + Path path = getRegionPath(regionCoords); + + if (!createIfMissing && !Files.exists(path)) { + debug("Region {} {} {} does not exist on disk, aborting load", regionCoords); + return null; + } + + region = openRegion(path, regionCoords); + debug("Region {} {} {} loaded", regionCoords); + } + + return region; + } + + private Region openRegion(Path path, Vec3i regionCoords) throws IOException { + RandomAccessFile raf = new RandomAccessFile(path.toFile(), "rw"); + Region region = new Region(raf, regionCoords); + regions.put(regionCoords, region); + return region; + } + + static void writeGenerationHint(DefaultChunkData chunk, DataOutputStream output, Server server) + throws IOException { + server.getWorld().getGenerator().writeGenerationHint(output, chunk.getGenerationHint()); + } + + static void readGenerationHint(DefaultChunkData chunk, DataInputStream input, Server server) + throws IOException, + DecodingException { + chunk.setGenerationHint(server.getWorld().getGenerator().readGenerationHint(input)); + } + + @Override + public Path getPath() { + return path; + } + + private Path getRegionPath(Vec3i regionPos) { + return getPath().resolve( + String.format( + REGION_NAME_FORMAT, + regionPos.x, + regionPos.y, + regionPos.z + ) + ); + } + + private Path getPlayerPath(String login) { + return getPath().resolve( + String.format( + PLAYER_NAME_FORMAT, + login + ) + ); + } + + @Override + public void close() { + try { + ChunkMap isCloseds = ChunkMaps.newHashMap(); + ChunkMap isUsings = ChunkMaps.newHashMap(); + + for (Vec3i region : regions.keys()) { + isCloseds.put(region, regions.get(region).isClosed()); + isUsings.put(region, regions.get(region).isUsing()); + } + + boolean stillOpen = true; + while (stillOpen) { + stillOpen = false; + for (Vec3i region : regions.keys()) { + if (!isCloseds.get(region).get() && !isUsings.get(region).get()) { + regions.get(region).close(); + } else if (isUsings.get(region).get()) { + stillOpen = false; + } + } + } + } catch (IOException e) { + throw CrashReports.report(e, "Could not close region files"); + } + } + + private static void debug(String message, Vec3i vector) { + if (LOG.isDebugEnabled()) { + LOG.debug(message, vector.x, vector.y, vector.z); + } + } + + private static void warn(String message, Vec3i vector) { + LOG.warn(message, vector.x, vector.y, vector.z); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/LayerButtonTest.java b/src/main/java/ru/windcorp/progressia/test/LayerButtonTest.java index e505291..a9812ea 100644 --- a/src/main/java/ru/windcorp/progressia/test/LayerButtonTest.java +++ b/src/main/java/ru/windcorp/progressia/test/LayerButtonTest.java @@ -17,7 +17,11 @@ */ package ru.windcorp.progressia.test; +import java.util.Collection; + +import ru.windcorp.progressia.client.ClientState; import ru.windcorp.progressia.client.graphics.Colors; +import ru.windcorp.progressia.client.graphics.GUI; import ru.windcorp.progressia.client.graphics.font.Font; import ru.windcorp.progressia.client.graphics.gui.Button; import ru.windcorp.progressia.client.graphics.gui.Checkbox; @@ -25,45 +29,71 @@ import ru.windcorp.progressia.client.graphics.gui.Label; import ru.windcorp.progressia.client.graphics.gui.RadioButton; import ru.windcorp.progressia.client.graphics.gui.RadioButtonGroup; import ru.windcorp.progressia.client.graphics.gui.menu.MenuLayer; +import ru.windcorp.progressia.client.localization.MutableStringLocalized; +import ru.windcorp.progressia.server.Player; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.ServerState; public class LayerButtonTest extends MenuLayer { + boolean alive = true; + public LayerButtonTest() { super("ButtonTest"); - + addTitle(); - + Button blockableButton; getContent().addChild((blockableButton = new Button("BlockableButton", "Blockable")).addAction(b -> { System.out.println("Button Blockable!"); })); blockableButton.setEnabled(false); - + getContent().addChild(new Checkbox("EnableButton", "Enable").addAction(b -> { blockableButton.setEnabled(((Checkbox) b).isChecked()); })); - + RadioButtonGroup group = new RadioButtonGroup().addAction(g -> { System.out.println("RBG! " + g.getSelected().getLabel().getCurrentText()); }); - + getContent().addChild(new RadioButton("RB1", "Moon").setGroup(group)); getContent().addChild(new RadioButton("RB2", "Type").setGroup(group)); getContent().addChild(new RadioButton("RB3", "Ice").setGroup(group)); getContent().addChild(new RadioButton("RB4", "Cream").setGroup(group)); - + getContent().getChild(getContent().getChildren().size() - 1).setEnabled(false); - + getContent().addChild(new Label("Hint", new Font().withColor(Colors.LIGHT_GRAY), "This is a MenuLayer")); - + getContent().addChild(new Button("Continue", "Continue").addAction(b -> { getCloseAction().run(); })); - - getContent().addChild(new Button("Quit", "Quit").addAction(b -> { - System.exit(0); + + getContent().addChild(new Button("Menu", "Back To Menu").addAction(b -> { + getCloseAction().run(); + + Collection players = ServerState.getInstance().getPlayerManager().getPlayers(); + players.clear(); + + ClientState.disconnectFromLocalServer(); + + GUI.addTopLayer(new LayerTestText("Text", new MutableStringLocalized("LayerText.Save"), layer -> { + Server server = ServerState.getInstance(); + if (server != null && server.getWorld().getChunks().isEmpty()) { + GUI.removeLayer(layer); + + // TODO Refactor, this shouldn't be here + GUI.addTopLayer(new LayerTitle("Title")); + ServerState.getInstance().shutdown("Safe Exit"); + ServerState.setInstance(null); + TestPlayerControls.resetInstance(); + } + })); + + ClientState.setInstance(null); })); - + getContent().takeFocus(); } diff --git a/src/main/java/ru/windcorp/progressia/test/LayerTestText.java b/src/main/java/ru/windcorp/progressia/test/LayerTestText.java new file mode 100644 index 0000000..a4d202b --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/LayerTestText.java @@ -0,0 +1,29 @@ +package ru.windcorp.progressia.test; + +import java.util.function.Consumer; +import ru.windcorp.progressia.client.graphics.Colors; +import ru.windcorp.progressia.client.graphics.font.Font; +import ru.windcorp.progressia.client.graphics.gui.GUILayer; +import ru.windcorp.progressia.client.graphics.gui.Label; +import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign; +import ru.windcorp.progressia.client.localization.MutableString; + +public class LayerTestText extends GUILayer { + + private final Consumer remover; + + public LayerTestText(String name, MutableString value, Consumer remover) { + super(name, new LayoutAlign(15)); + this.remover = remover; + + Font titleFont = new Font().deriveBold().withColor(Colors.BLACK).withAlign(0.5f); + getRoot().addChild(new Label(name + ".Text", titleFont, value)); + } + + @Override + protected void doRender() { + super.doRender(); + remover.accept(this); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/LayerTitle.java b/src/main/java/ru/windcorp/progressia/test/LayerTitle.java new file mode 100644 index 0000000..365e437 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/LayerTitle.java @@ -0,0 +1,95 @@ +package ru.windcorp.progressia.test; + +import ru.windcorp.progressia.client.ClientState; +import ru.windcorp.progressia.client.graphics.Colors; +import ru.windcorp.progressia.client.graphics.GUI; +import ru.windcorp.progressia.client.graphics.font.Font; +import ru.windcorp.progressia.client.graphics.gui.BasicButton; +import ru.windcorp.progressia.client.graphics.gui.Button; +import ru.windcorp.progressia.client.graphics.gui.GUILayer; +import ru.windcorp.progressia.client.graphics.gui.Group; +import ru.windcorp.progressia.client.graphics.gui.Label; +import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign; +import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical; +import ru.windcorp.progressia.client.localization.MutableString; +import ru.windcorp.progressia.client.localization.MutableStringLocalized; +import ru.windcorp.progressia.common.util.crash.CrashReports; +import ru.windcorp.progressia.server.ServerState; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +public class LayerTitle extends GUILayer { + + private final BasicButton resetButton; + + public LayerTitle(String name) { + super(name, new LayoutAlign(0.5f, 0.7f, 15)); + Group content = new Group("Layer" + name + ".Group", new LayoutVertical(15)); + + MutableString title = new MutableStringLocalized("Layer" + name + ".Title"); + Font titleFont = new Font().deriveBold().withColor(Colors.BLACK).withAlign(0.5f); + content.addChild(new Label(name + ".Title", titleFont, title)); + + Font buttonFont = titleFont.deriveNotBold(); + MutableString playText = new MutableStringLocalized("Layer" + name + ".Play"); + content.addChild(new Button(name + ".Play", new Label(name + ".Play", buttonFont, playText)).addAction(this::startGame)); + + MutableString resetText = new MutableStringLocalized("Layer" + name + ".Reset"); + this.resetButton = new Button(name + ".Reset", new Label(name + ".Reset", buttonFont, resetText)).addAction(this::resetWorld); + content.addChild(resetButton); + + updateResetButton(); + + MutableString quitText = new MutableStringLocalized("Layer" + name + ".Quit"); + content.addChild(new Button(name + "Quit", new Label(name + ".Quit", buttonFont, quitText)).addAction(b -> { + System.exit(0); + })); + + getRoot().addChild(content); + } + + private void updateResetButton() { + resetButton.setEnabled(Files.exists(Paths.get("tmp_world"))); + } + + private void startGame(BasicButton basicButton) { + GUI.removeLayer(this); + try { + ServerState.startServer(); + ClientState.connectToLocalServer(); + } catch (IOException e) { + throw CrashReports.report(e, "Problem with loading server"); + } + } + + private void resetWorld(BasicButton basicButton) { + Path rootPath = Paths.get("tmp_world"); + + try { + Files.walkFileTree(rootPath, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + throw CrashReports.report(e, "Could not reset world"); + } + + updateResetButton(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/TestContent.java b/src/main/java/ru/windcorp/progressia/test/TestContent.java index 75e32e4..3651f60 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestContent.java +++ b/src/main/java/ru/windcorp/progressia/test/TestContent.java @@ -390,7 +390,7 @@ public class TestContent { ru.windcorp.progressia.client.Client client = ClientState.getInstance(); if (client == null || !client.isReady()) return null; - + return client.getLocalPlayer().getSelection(); } diff --git a/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java b/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java index ee42396..4ca0ea6 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java +++ b/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java @@ -46,10 +46,10 @@ import ru.windcorp.progressia.server.ServerState; public class TestPlayerControls { - private static final TestPlayerControls INSTANCE = new TestPlayerControls(); + private static TestPlayerControls instance = new TestPlayerControls(); public static TestPlayerControls getInstance() { - return INSTANCE; + return instance; } private static final double MODE_SWITCH_MAX_DELAY = 300 * Units.MILLISECONDS; @@ -90,6 +90,10 @@ public class TestPlayerControls { private LayerTestGUI debugLayer = null; private Runnable updateCallback = null; + + public static void resetInstance() { + instance = new TestPlayerControls(); + } public void applyPlayerControls() { if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) { diff --git a/src/main/java/ru/windcorp/progressia/test/TestWorldDiskIO.java b/src/main/java/ru/windcorp/progressia/test/TestWorldDiskIO.java deleted file mode 100644 index ebfafde..0000000 --- a/src/main/java/ru/windcorp/progressia/test/TestWorldDiskIO.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Progressia - * Copyright (C) 2020-2021 Wind Corporation and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package ru.windcorp.progressia.test; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.zip.DeflaterOutputStream; -import java.util.zip.InflaterInputStream; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import glm.vec._3.i.Vec3i; -import ru.windcorp.progressia.common.state.IOContext; -import ru.windcorp.progressia.common.world.DefaultChunkData; -import ru.windcorp.progressia.common.world.DecodingException; -import ru.windcorp.progressia.common.world.DefaultWorldData; -import ru.windcorp.progressia.common.world.io.ChunkIO; -import ru.windcorp.progressia.server.Server; - -public class TestWorldDiskIO { - - private static final Path SAVE_DIR = Paths.get("tmp_world"); - private static final Logger LOG = LogManager.getLogger("TestWorldDiskIO"); - - private static final boolean ENABLE = false; - - public static void saveChunk(DefaultChunkData chunk, Server server) { - if (!ENABLE) - return; - - try { - LOG.debug( - "Saving {} {} {}", - chunk.getPosition().x, - chunk.getPosition().y, - chunk.getPosition().z - ); - - Files.createDirectories(SAVE_DIR); - - Path path = SAVE_DIR.resolve( - String.format( - "chunk_%+d_%+d_%+d.progressia_chunk", - chunk.getPosition().x, - chunk.getPosition().y, - chunk.getPosition().z - ) - ); - - try ( - DataOutputStream output = new DataOutputStream( - new DeflaterOutputStream(new BufferedOutputStream(Files.newOutputStream(path))) - ) - ) { - ChunkIO.save(chunk, output, IOContext.SAVE); - writeGenerationHint(chunk, output, server); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - private static void writeGenerationHint(DefaultChunkData chunk, DataOutputStream output, Server server) - throws IOException { - server.getWorld().getGenerator().writeGenerationHint(output, chunk.getGenerationHint()); - } - - public static DefaultChunkData tryToLoad(Vec3i chunkPos, DefaultWorldData world, Server server) { - if (!ENABLE) - return null; - - Path path = SAVE_DIR.resolve( - String.format( - "chunk_%+d_%+d_%+d.progressia_chunk", - chunkPos.x, - chunkPos.y, - chunkPos.z - ) - ); - - if (!Files.exists(path)) { - LOG.debug( - "Not found {} {} {}", - chunkPos.x, - chunkPos.y, - chunkPos.z - ); - - return null; - } - - try { - DefaultChunkData result = load(path, chunkPos, world, server); - - LOG.debug( - "Loaded {} {} {}", - chunkPos.x, - chunkPos.y, - chunkPos.z - ); - - return result; - } catch (Exception e) { - e.printStackTrace(); - LOG.debug( - "Could not load {} {} {}", - chunkPos.x, - chunkPos.y, - chunkPos.z - ); - return null; - } - } - - private static DefaultChunkData load(Path path, Vec3i chunkPos, DefaultWorldData world, Server server) - throws IOException, - DecodingException { - try ( - DataInputStream input = new DataInputStream( - new InflaterInputStream(new BufferedInputStream(Files.newInputStream(path))) - ) - ) { - DefaultChunkData chunk = ChunkIO.load(world, chunkPos, input, IOContext.SAVE); - readGenerationHint(chunk, input, server); - return chunk; - } - } - - private static void readGenerationHint(DefaultChunkData chunk, DataInputStream input, Server server) - throws IOException, - DecodingException { - chunk.setGenerationHint(server.getWorld().getGenerator().readGenerationHint(input)); - } - -} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/TestGenerationConfig.java b/src/main/java/ru/windcorp/progressia/test/gen/TestGenerationConfig.java index 2147889..1fe8334 100644 --- a/src/main/java/ru/windcorp/progressia/test/gen/TestGenerationConfig.java +++ b/src/main/java/ru/windcorp/progressia/test/gen/TestGenerationConfig.java @@ -46,9 +46,18 @@ public class TestGenerationConfig { private static final float CURVATURE = Units.get("100 m"); private static final float INNER_RADIUS = Units.get("200 m"); - private static final Fields FIELDS = new Fields(SEED); + private final Fields fields = new Fields(SEED); + private final Function generator; + + public TestGenerationConfig() { + this.generator = createGenerator(); + } + + public Function getGenerator() { + return generator; + } - public static Function createGenerator() { + private Function createGenerator() { Planet planet = new Planet( ((int) PLANET_RADIUS) / Coordinates.CHUNK_SIZE, @@ -57,7 +66,7 @@ public class TestGenerationConfig { INNER_RADIUS ); - TestHeightMap heightMap = new TestHeightMap(planet, planet.getRadius() / 4, FIELDS); + TestHeightMap heightMap = new TestHeightMap(planet, planet.getRadius() / 4, fields); LayeredTerrain terrain = new LayeredTerrain(); registerTerrainLayers(terrain); @@ -69,12 +78,12 @@ public class TestGenerationConfig { } - private static void registerTerrainLayers(LayeredTerrain terrain) { - SurfaceFloatField cliffs = FIELDS.get("Test:Cliff"); - SurfaceFloatField beaches = FIELDS.register( + private void registerTerrainLayers(LayeredTerrain terrain) { + SurfaceFloatField cliffs = fields.get("Test:Cliff"); + SurfaceFloatField beaches = fields.register( "Test:Beach", f -> multiply( - anti(FIELDS.get("Test:Cliff", f)) + anti(fields.get("Test:Cliff", f)) ) ); RockStrata rockStrata = createStrata(); @@ -88,40 +97,40 @@ public class TestGenerationConfig { terrain.addLayer(new BeachLayer("Test:Beaches", beaches, rockStrata)); } - private static RockStrata createStrata() { + private RockStrata createStrata() { WorleyProceduralNoise.Builder builder = WorleyProceduralNoise.builder(); TestContent.ROCKS.getRocks().forEach(rock -> builder.add(rock, 1)); - SurfaceFloatField rockDepthOffsets = FIELDS.register( + SurfaceFloatField rockDepthOffsets = fields.register( "Test:RockDepthOffsets", - () -> tweak(FIELDS.primitive(), 40, 5) + () -> tweak(fields.primitive(), 40, 5) ); return new RockStrata(builder.build(SEED), rockDepthOffsets); } - private static void registerFeatures(List features) { + private void registerFeatures(List features) { - SurfaceFloatField forestiness = FIELDS.register( + SurfaceFloatField forestiness = fields.register( "Test:Forest", - () -> squash(scale(FIELDS.primitive(), 200), 5) + () -> squash(scale(fields.primitive(), 200), 5) ); - SurfaceFloatField grassiness = FIELDS.register( + SurfaceFloatField grassiness = fields.register( "Test:Grass", f -> multiply( - tweak(octaves(FIELDS.primitive(), 2, 2), 40, 0.5, 1.2), - squash(tweak(FIELDS.get("Test:Forest", f), 1, -1, 1), 10), - anti(squash(FIELDS.get("Test:Cliff", f), 10)) + tweak(octaves(fields.primitive(), 2, 2), 40, 0.5, 1.2), + squash(tweak(fields.get("Test:Forest", f), 1, -1, 1), 10), + anti(squash(fields.get("Test:Cliff", f), 10)) ) ); - Function floweriness = flowerName -> FIELDS.register( + Function floweriness = flowerName -> fields.register( "Test:Flower" + flowerName, f -> multiply( - selectPositive(squash(scale(octaves(FIELDS.primitive(), 2, 3), 100), 2), 1, 0.5), - tweak(FIELDS.get("Test:Forest", f), 1, -1, 1.1), - anti(squash(FIELDS.get("Test:Cliff", f), 10)) + selectPositive(squash(scale(octaves(fields.primitive(), 2, 3), 100), 2), 1, 0.5), + tweak(fields.get("Test:Forest", f), 1, -1, 1.1), + anti(squash(fields.get("Test:Cliff", f), 10)) ) ); diff --git a/src/main/resources/assets/languages/en-US.lang b/src/main/resources/assets/languages/en-US.lang index e2ff70b..b3de478 100644 --- a/src/main/resources/assets/languages/en-US.lang +++ b/src/main/resources/assets/languages/en-US.lang @@ -21,4 +21,14 @@ LayerTestGUI.PlacementModeHint = (Blocks %s Tiles: Ctrl + Mouse Wheel) LayerTestGUI.IsFullscreen = Fullscreen: %5s (F11) LayerTestGUI.IsVSync = VSync: %5s (F12) -LayerButtonTest.Title = Button Test \ No newline at end of file +LayerButtonTest.Title = Button Test +LayerButtonTest.Return = Back To Menu + +LayerTitle.Title = Progressia +LayerTitle.Play = Play World +LayerTitle.Reset = Reset World +LayerTitle.Options = Options +LayerTitle.Quit = Quit + +LayerText.Load = Loading... +LayerText.Save = Saving... \ No newline at end of file diff --git a/src/main/resources/assets/languages/ru-RU.lang b/src/main/resources/assets/languages/ru-RU.lang index 97935d6..69e07f2 100644 --- a/src/main/resources/assets/languages/ru-RU.lang +++ b/src/main/resources/assets/languages/ru-RU.lang @@ -21,4 +21,14 @@ LayerTestGUI.PlacementModeHint = (Блок %s плитки: Ctrl + прокру LayerTestGUI.IsFullscreen = Полный экран: %5s (F11) LayerTestGUI.IsVSync = Верт. синхр.: %5s (F12) -LayerButtonTest.Title = Тест Кнопок \ No newline at end of file +LayerButtonTest.Title = Тест кнопок +LayerButtonTest.Return = Главное меню + +LayerTitle.Title = Прогрессия +LayerTitle.Play = Играть +LayerTitle.Reset = Сбросить мир +LayerTitle.Options = Настройки +LayerTitle.Quit = Выход + +LayerText.Load = Загрузка... +LayerText.Save = Сохранение... \ No newline at end of file diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index ce70734..68d804f 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -27,6 +27,10 @@ --> + +