diff --git a/build.gradle b/build.gradle
index 5adae10..fffa45c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -66,12 +66,12 @@ dependencies {
// Log4j
// A logging library
- implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.3'
- implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.3'
+ implementation 'org.apache.logging.log4j:log4j-api:2.16.0'
+ implementation 'org.apache.logging.log4j:log4j-core:2.16.0'
// JUnit
// A unit-testing library
- testImplementation 'junit:junit:4.12'
+ testImplementation 'junit:junit:4.13.2'
// See LWJGL dependencies below
}
diff --git a/docs/ProgressiaRegion.md b/docs/ProgressiaRegion.md
new file mode 100644
index 0000000..b3404e1
--- /dev/null
+++ b/docs/ProgressiaRegion.md
@@ -0,0 +1,12 @@
+# Progressia Region File
+## Description
+The `.progressia_region` file type is used for all region files in the game Progressia. Each region file contains a cube of 16x16x16 chunks.
+## Header
+The header of the file is 16 400 bytes. Every file starts with the string byte sequence `\x50\x52\x4F\x47` (UTF-8 for `PROG`), followed with the three integer values of the region position, in region coordinates. After this, there is exactly 16KiB of space in the header, which stores the offsets to the chunks' data. This space holds an integer, 4 bytes, for each chunk in the region. The integer value starts at 0 for every chunk, and is changed to the location of the chunk data once created. These are indexed in order by flattening the 3D in-chunk coordinates into a number between 0 and 4095 according to the formula `offset = 256*x+ 16*y + z` for chunk at (x, y, z). To convert from this offset value to the offset in bytes, use `byte_offset = 16400 + 64*n`.
+## Sectors
+Sectors are what is used to store chunk data, and are not linear, but are followed until they reach an ending block. Each is 64 bytes, which is used in the header section to find the byte offset. Each sector starts with a identification byte, followed by the sector data.
+
+ 0. Ending - This sector is empty, and marks the end of the chunk data (This may change in the future.
+ 1. Data - This sector contains chunk data for a single chunk. The second byte of this sector contains a counter byte, which is a form of "checksum" to make sure that the program is reading the proper sectors in order. This starts at 0 for the first data sector and increments by one for each new data sector.
+ 2. Partition Link - This sector only contains another offset value, which is where the next sector is. This allows for infinite chunk size, avoiding "chunk dupes" as were present in Minecraft without reverting any chunks.
+ 3. Bulk Data - These would be used for many chunks in the same region that contain exactly the same data, e.g. all solid chunks underground. Exists so the program knows not to overwrite them, and just make new chunk data if modified. Not yet implemented.
\ No newline at end of file
diff --git a/docs/building/IntelliJIDEAGuide.md b/docs/building/IntelliJIDEAGuide.md
index 7ce7c4c..e40bd69 100644
--- a/docs/building/IntelliJIDEAGuide.md
+++ b/docs/building/IntelliJIDEAGuide.md
@@ -41,4 +41,17 @@ Run configurations are used by Intellij IDEA to specify how a project must be ru
8. Append `\run` to the 'Working directory' field. Alternatively, specify another location outside of the project's root directory.
9. Click 'Apply' to save changes.
-Step 8 is required to specify that the game must run in some directory other than the project root, which is the default in Intellij IDEA.
\ No newline at end of file
+Step 8 is required to specify that the game must run in some directory other than the project root, which is the default in Intellij IDEA.
+
+### Applying formatting templates
+
+Windcorp's Progressia repository is formatted with a style defined for Eclipse IDE (sic) in
+`templates_and_presets/eclipse_ide`.
+Please apply these templates to the project to automatically format the source in a similar fashion.
+
+1. In project context menu, click 'File->Properties'. (`Ctrl+Alt+S`)
+2. In 'Editor' > 'Code Style' > 'Java', press gear icon, then click 'Import Scheme' > 'Eclipse code style'
+3. In Scheme select 'Project'
+4. Open the file `templates_and_presets/eclipse_ide/FormatterProfile.xml` in 'Select Path'.
+5. Inside 'Import Scheme' widow click 'Current Scheme' check box after press OK
+
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/Client.java b/src/main/java/ru/windcorp/progressia/client/Client.java
index 3251dff..d95cea0 100644
--- a/src/main/java/ru/windcorp/progressia/client/Client.java
+++ b/src/main/java/ru/windcorp/progressia/client/Client.java
@@ -35,6 +35,7 @@ import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.common.util.crash.ReportingEventBus;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.test.LayerAbout;
+import ru.windcorp.progressia.test.LayerDebug;
import ru.windcorp.progressia.test.LayerTestUI;
public class Client {
@@ -44,6 +45,7 @@ public class Client {
private final LayerWorld layerWorld = new LayerWorld(this);
private final LayerTestUI layerTestUI = new LayerTestUI();
private final LayerAbout layerAbout = new LayerAbout();
+ private final LayerDebug layerDebug = new LayerDebug();
private final LocalPlayer localPlayer = new LocalPlayer(this);
@@ -75,6 +77,7 @@ public class Client {
GUI.removeLayer(layerTestUI);
hudManager.remove();
GUI.removeLayer(layerAbout);
+ GUI.removeLayer(layerDebug);
}
public WorldRender getWorld() {
@@ -101,6 +104,14 @@ public class Client {
return hudManager;
}
+ public void toggleDebugLayer() {
+ if (GUI.getLayers().contains(layerDebug)) {
+ GUI.removeLayer(layerDebug);
+ } else {
+ GUI.addTopLayer(layerDebug);
+ }
+ }
+
@Subscribe
private void onLocalPlayerEntityChanged(NewLocalEntityEvent e) {
if (e.getNewEntity() == null) {
diff --git a/src/main/java/ru/windcorp/progressia/client/ClientProxy.java b/src/main/java/ru/windcorp/progressia/client/ClientProxy.java
index c75286d..365d9d7 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;
@@ -31,7 +31,6 @@ import ru.windcorp.progressia.client.graphics.world.hud.HUDTextures;
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;
@@ -39,7 +38,9 @@ public class ClientProxy implements Proxy {
@Override
public void initialize() {
+
GraphicsBackend.initialize();
+
try {
RenderTaskQueue.waitAndInvoke(FlatRenderProgram::init);
RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init);
@@ -60,10 +61,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 fae2b5d..dbb45a2 100644
--- a/src/main/java/ru/windcorp/progressia/client/ClientState.java
+++ b/src/main/java/ru/windcorp/progressia/client/ClientState.java
@@ -15,12 +15,15 @@
* 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.common.world.DefaultWorldData;
+import ru.windcorp.progressia.client.localization.MutableStringLocalized;
import ru.windcorp.progressia.server.ServerState;
+import ru.windcorp.progressia.test.LayerTestText;
import ru.windcorp.progressia.test.TestContent;
public class ClientState {
@@ -48,8 +51,29 @@ public class ClientState {
channel.connect(TestContent.PLAYER_LOGIN);
setInstance(client);
+ displayLoadingScreen();
- client.install();
+ }
+
+ 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);
+ client.install();
+ }
+ }));
+ }
+
+ public static void disconnectFromLocalServer() {
+ getInstance().getComms().disconnect();
+ getInstance().remove();
}
private ClientState() {
diff --git a/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggers.java b/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggers.java
index 2c0d61d..2cc73f4 100644
--- a/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggers.java
+++ b/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggers.java
@@ -143,20 +143,6 @@ public class ControlTriggers {
);
}
- //
- //
- ///
- ///
- //
- //
- //
- //
- //
- //
- //
- //
- //
-
public static ControlTriggerInputBased localOf(
String id,
Consumer action,
diff --git a/src/main/java/ru/windcorp/progressia/client/comms/controls/InputBasedControls.java b/src/main/java/ru/windcorp/progressia/client/comms/controls/InputBasedControls.java
index 28b9e1b..1eea281 100644
--- a/src/main/java/ru/windcorp/progressia/client/comms/controls/InputBasedControls.java
+++ b/src/main/java/ru/windcorp/progressia/client/comms/controls/InputBasedControls.java
@@ -19,7 +19,7 @@
package ru.windcorp.progressia.client.comms.controls;
import ru.windcorp.progressia.client.Client;
-import ru.windcorp.progressia.client.graphics.input.bus.Input;
+import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.common.comms.packets.Packet;
public class InputBasedControls {
@@ -36,12 +36,12 @@ public class InputBasedControls {
.toArray(ControlTriggerInputBased[]::new);
}
- public void handleInput(Input input) {
+ public void handleInput(InputEvent event) {
for (ControlTriggerInputBased c : controls) {
- Packet packet = c.onInputEvent(input.getEvent());
+ Packet packet = c.onInputEvent(event);
if (packet != null) {
- input.consume();
+ event.consume();
client.getComms().sendPacket(packet);
break;
}
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 f29aeed..67c205d 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/GUI.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/GUI.java
@@ -15,22 +15,16 @@
* 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 com.google.common.eventbus.Subscribe;
+import java.util.Objects;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
-import ru.windcorp.progressia.client.graphics.input.CursorEvent;
-import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
-import ru.windcorp.progressia.client.graphics.input.KeyEvent;
-import ru.windcorp.progressia.client.graphics.input.WheelEvent;
-import ru.windcorp.progressia.client.graphics.input.bus.Input;
public class GUI {
@@ -45,19 +39,11 @@ public class GUI {
private static final List MODIFICATION_QUEUE = Collections
.synchronizedList(new ArrayList<>());
- private static class ModifiableInput extends Input {
- @Override
- public void initialize(InputEvent event, Target target) {
- super.initialize(event, target);
- }
- }
-
- private static final ModifiableInput THE_INPUT = new ModifiableInput();
-
private GUI() {
}
public static void addBottomLayer(Layer layer) {
+ Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.add(layer);
layer.onAdded();
@@ -65,6 +51,7 @@ public class GUI {
}
public static void addTopLayer(Layer layer) {
+ Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.add(0, layer);
layer.onAdded();
@@ -72,6 +59,7 @@ public class GUI {
}
public static void removeLayer(Layer layer) {
+ Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.remove(layer);
layer.onRemoved();
@@ -94,33 +82,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();
}
-
+
}
}
@@ -128,43 +116,12 @@ public class GUI {
LAYERS.forEach(Layer::invalidate);
}
- private static void dispatchInputEvent(InputEvent event) {
- Input.Target target;
-
- if (event instanceof KeyEvent) {
- if (((KeyEvent) event).isMouse()) {
- target = Input.Target.HOVERED;
- } else {
- target = Input.Target.FOCUSED;
+ public static void dispatchInput(InputEvent event) {
+ synchronized (LAYERS) {
+ for (int i = 0; i < LAYERS.size(); ++i) {
+ LAYERS.get(i).handleInput(event);
}
- } else if (event instanceof CursorEvent) {
- target = Input.Target.HOVERED;
- } else if (event instanceof WheelEvent) {
- target = Input.Target.HOVERED;
- } else if (event instanceof FrameResizeEvent) {
- return;
- } else {
- target = Input.Target.ALL;
}
-
- THE_INPUT.initialize(event, target);
- LAYERS.forEach(l -> l.handleInput(THE_INPUT));
- }
-
- public static Object getEventSubscriber() {
- return new Object() {
-
- @Subscribe
- public void onFrameResized(FrameResizeEvent event) {
- GUI.invalidateEverything();
- }
-
- @Subscribe
- public void onInput(InputEvent event) {
- dispatchInputEvent(event);
- }
-
- };
}
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/Layer.java b/src/main/java/ru/windcorp/progressia/client/graphics/Layer.java
index dfa72d5..ad685e4 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/Layer.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/Layer.java
@@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.graphics;
import java.util.concurrent.atomic.AtomicBoolean;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
-import ru.windcorp.progressia.client.graphics.input.bus.Input;
+import ru.windcorp.progressia.client.graphics.input.InputEvent;
public abstract class Layer {
@@ -106,7 +106,7 @@ public abstract class Layer {
protected abstract void doRender();
- protected abstract void handleInput(Input input);
+ public abstract void handleInput(InputEvent input);
protected int getWidth() {
return GraphicsInterface.getFrameWidth();
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/backend/GraphicsInterface.java b/src/main/java/ru/windcorp/progressia/client/graphics/backend/GraphicsInterface.java
index ffd0b49..6f59d39 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/backend/GraphicsInterface.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/backend/GraphicsInterface.java
@@ -68,6 +68,10 @@ public class GraphicsInterface {
public static void subscribeToInputEvents(Object listener) {
InputHandler.register(listener);
}
+
+ public static void unsubscribeFromInputEvents(Object listener) {
+ InputHandler.unregister(listener);
+ }
public static void startNextLayer() {
GraphicsBackend.startNextLayer();
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/backend/InputHandler.java b/src/main/java/ru/windcorp/progressia/client/graphics/backend/InputHandler.java
index 34d936c..560ccaf 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/backend/InputHandler.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/backend/InputHandler.java
@@ -39,6 +39,7 @@ public class InputHandler {
public void initialize(int key, int scancode, int action, int mods) {
this.setTime(GraphicsInterface.getTime());
+ this.setConsumed(false);
this.key = key;
this.scancode = scancode;
this.action = action;
@@ -59,7 +60,7 @@ public class InputHandler {
if (GraphicsBackend.getWindowHandle() != window)
return;
THE_KEY_EVENT.initialize(key, scancode, action, mods);
- dispatch(THE_KEY_EVENT);
+ INPUT_EVENT_BUS.post(THE_KEY_EVENT);
switch (action) {
case GLFW.GLFW_PRESS:
@@ -90,6 +91,7 @@ public class InputHandler {
public void initialize(double x, double y) {
this.setTime(GraphicsInterface.getTime());
+ this.setConsumed(false);
getNewPosition().set(x, y);
}
@@ -109,7 +111,7 @@ public class InputHandler {
InputTracker.initializeCursorPosition(x, y);
THE_CURSOR_MOVE_EVENT.initialize(x, y);
- dispatch(THE_CURSOR_MOVE_EVENT);
+ INPUT_EVENT_BUS.post(THE_CURSOR_MOVE_EVENT);
InputTracker.getCursorPosition().set(x, y);
}
@@ -124,6 +126,7 @@ public class InputHandler {
public void initialize(double xOffset, double yOffset) {
this.setTime(GraphicsInterface.getTime());
+ this.setConsumed(false);
this.getOffset().set(xOffset, yOffset);
}
@@ -139,7 +142,7 @@ public class InputHandler {
if (GraphicsBackend.getWindowHandle() != window)
return;
THE_WHEEL_SCROLL_EVENT.initialize(xoffset, yoffset);
- dispatch(THE_WHEEL_SCROLL_EVENT);
+ INPUT_EVENT_BUS.post(THE_WHEEL_SCROLL_EVENT);
}
// FrameResizeEvent
@@ -152,6 +155,7 @@ public class InputHandler {
public void initialize(int width, int height) {
this.setTime(GraphicsInterface.getTime());
+ this.setConsumed(false);
this.getNewSize().set(width, height);
}
@@ -167,17 +171,17 @@ public class InputHandler {
int height
) {
THE_FRAME_RESIZE_EVENT.initialize(width, height);
- dispatch(THE_FRAME_RESIZE_EVENT);
+ INPUT_EVENT_BUS.post(THE_FRAME_RESIZE_EVENT);
}
// Misc
- private static void dispatch(InputEvent event) {
- INPUT_EVENT_BUS.post(event);
- }
-
public static void register(Object listener) {
INPUT_EVENT_BUS.register(listener);
}
+
+ public static void unregister(Object listener) {
+ INPUT_EVENT_BUS.unregister(listener);
+ }
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/backend/LWJGLInitializer.java b/src/main/java/ru/windcorp/progressia/client/graphics/backend/LWJGLInitializer.java
index 9239150..9bc280e 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/backend/LWJGLInitializer.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/backend/LWJGLInitializer.java
@@ -24,7 +24,11 @@ import static org.lwjgl.system.MemoryUtil.*;
import org.lwjgl.opengl.GL;
+import com.google.common.eventbus.Subscribe;
+
import ru.windcorp.progressia.client.graphics.GUI;
+import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
+import ru.windcorp.progressia.client.graphics.input.InputEvent;
class LWJGLInitializer {
@@ -107,7 +111,20 @@ class LWJGLInitializer {
glfwSetScrollCallback(handle, InputHandler::handleWheelScroll);
- GraphicsInterface.subscribeToInputEvents(GUI.getEventSubscriber());
+ GraphicsInterface.subscribeToInputEvents(new Object() {
+
+ @Subscribe
+ public void onFrameResized(FrameResizeEvent event) {
+ GUI.invalidateEverything();
+ }
+
+ @Subscribe
+ public void onInputEvent(InputEvent event) {
+ GUI.dispatchInput(event);
+ }
+
+ });
+
}
}
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 464a6bf..6607331 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,21 +37,18 @@ 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 = label;
setLayout(new LayoutAlign(10));
-
- if (label == null) {
- this.label = null;
- } else {
- this.label = new Label(name + ".Label", labelFont, label);
+ if (label != null) {
addChild(this.label);
}
@@ -59,23 +56,22 @@ public abstract class BasicButton extends Component {
reassembleAt(ARTrigger.HOVER, ARTrigger.FOCUS, ARTrigger.ENABLE);
// Click triggers
- addListener(KeyEvent.class, e -> {
- if (e.isRepeat()) {
- return false;
- } else if (
+ addInputListener(KeyEvent.class, e -> {
+ if (e.isRepeat())
+ return;
+
+ 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;
- } else {
- return false;
+ e.consume();
}
});
-
+
addListener(new Object() {
-
+
// Release when losing focus
@Subscribe
public void onFocusChange(FocusEvent e) {
@@ -83,7 +79,7 @@ public abstract class BasicButton extends Component {
setPressed(false);
}
}
-
+
// Release when hover ends
@Subscribe
public void onHoverEnded(HoverEvent e) {
@@ -91,7 +87,7 @@ public abstract class BasicButton extends Component {
setPressed(false);
}
}
-
+
// Release when disabled
@Subscribe
public void onDisabled(EnableEvent e) {
@@ -99,16 +95,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());
}
@@ -116,7 +116,7 @@ public abstract class BasicButton extends Component {
public boolean isPressed() {
return isPressed;
}
-
+
public void click() {
setPressed(true);
setPressed(false);
@@ -125,7 +125,8 @@ public abstract class BasicButton extends Component {
public void setPressed(boolean isPressed) {
if (this.isPressed != isPressed) {
this.isPressed = isPressed;
-
+ requestReassembly();
+
if (isPressed) {
takeFocus();
}
@@ -133,16 +134,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);
@@ -156,5 +157,5 @@ public abstract class BasicButton extends Component {
public boolean hasLabel() {
return label != null;
}
-
+
}
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 80d59dc..558461d 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
@@ -32,6 +32,10 @@ public class Button extends BasicButton {
super(name, label, labelFont);
}
+ public Button(String name, Label label) {
+ super(name, label);
+ }
+
public Button(String name, String label) {
this(name, label, new Font());
}
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/Component.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Component.java
index 724c80a..f6959d4 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/gui/Component.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Component.java
@@ -25,8 +25,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
-import org.lwjgl.glfw.GLFW;
-
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
@@ -41,10 +39,10 @@ import ru.windcorp.progressia.client.graphics.gui.event.EnableEvent;
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
+import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
-import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
import ru.windcorp.progressia.client.graphics.input.bus.InputListener;
import ru.windcorp.progressia.client.graphics.model.Renderable;
@@ -59,7 +57,7 @@ public class Component extends Named {
private Component parent = null;
private EventBus eventBus = null;
- private InputBus inputBus = null;
+ private final InputBus inputBus = new InputBus(this);
private int x, y;
private int width, height;
@@ -80,6 +78,9 @@ public class Component extends Named {
public Component(String name) {
super(name);
+
+ // Update hover flag when cursor moves
+ addInputListener(CursorMoveEvent.class, this::updateHoverFlag, InputBus.Option.ALWAYS);
}
public Component getParent() {
@@ -525,6 +526,10 @@ public class Component extends Named {
dispatchEvent(new HoverEvent(this, isHovered));
}
}
+
+ private void updateHoverFlag(CursorMoveEvent e) {
+ setHovered(contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY()));
+ }
public void addListener(Object listener) {
if (eventBus == null) {
@@ -546,144 +551,26 @@ public class Component extends Named {
eventBus.post(event);
}
- public void addListener(
- Class extends T> type,
- boolean handlesConsumed,
- InputListener listener
- ) {
- if (inputBus == null) {
- inputBus = new InputBus();
- }
-
- inputBus.register(type, handlesConsumed, listener);
- }
-
- public void addListener(Class type, InputListener super T> listener) {
- if (inputBus == null) {
- inputBus = new InputBus();
- }
-
- inputBus.register(type, listener);
+ public void addInputListener(Class extends T> type, InputListener listener, InputBus.Option... options) {
+ inputBus.register(type, listener, options);
}
- public void addListener(Class type, Runnable listener) {
- addListener(type, event -> {
- listener.run();
- return true;
- });
+ public void addKeyListener(KeyMatcher matcher, InputListener super KeyEvent> listener, InputBus.Option... options) {
+ inputBus.register(matcher, listener, options);
+ }
+
+ public void removeInputListener(InputListener> listener) {
+ inputBus.unregister(listener);
}
- public void addListener(KeyMatcher matcher, InputListener super KeyEvent> listener) {
- addListener(KeyEvent.class, event -> {
- if (matcher.test(event)) {
- return listener.handle(event);
- }
- return false;
- });
+ InputBus getInputBus() {
+ return inputBus;
}
- public void addListener(KeyMatcher matcher, Runnable listener) {
- addListener(matcher, event -> {
- listener.run();
- return true;
- });
- }
-
- public void removeListener(InputListener> listener) {
- if (inputBus != null) {
- inputBus.unregister(listener);
- }
- }
-
- protected void handleInput(Input input) {
- if (inputBus != null && isEnabled()) {
- inputBus.dispatch(input);
- }
- }
-
- public void dispatchInput(Input input) {
- try {
- switch (input.getTarget()) {
- case FOCUSED:
- dispatchInputToFocused(input);
- break;
- case HOVERED:
- dispatchInputToHovered(input);
- break;
- case ALL:
- default:
- dispatchInputToAll(input);
- break;
- }
- } catch (Exception e) {
- throw CrashReports.report(e, "Could not dispatch input to Component %s", this);
- }
- }
-
- private void dispatchInputToFocused(Input input) {
- Component c = findFocused();
-
- if (c == null)
- return;
- if (attemptFocusTransfer(input, c))
- return;
-
- while (c != null) {
- c.handleInput(input);
- c = c.getParent();
- }
- }
-
- private void dispatchInputToHovered(Input input) {
- getChildren().forEach(child -> {
- if (child.containsCursor()) {
- child.setHovered(true);
-
- if (!input.isConsumed()) {
- child.dispatchInput(input);
- }
- } else {
- child.setHovered(false);
- }
- });
-
- handleInput(input);
- }
-
- private void dispatchInputToAll(Input input) {
- getChildren().forEach(c -> c.dispatchInput(input));
- handleInput(input);
- }
-
- private boolean attemptFocusTransfer(Input input, Component focused) {
- if (input.isConsumed())
- return false;
- if (!(input.getEvent() instanceof KeyEvent))
- return false;
-
- KeyEvent keyInput = (KeyEvent) input.getEvent();
-
- if (keyInput.getKey() == GLFW.GLFW_KEY_TAB && !keyInput.isRelease()) {
- input.consume();
- if (keyInput.hasShift()) {
- focused.focusPrevious();
- } else {
- focused.focusNext();
- }
- return true;
- }
-
- return false;
- }
-
public synchronized boolean contains(int x, int y) {
return x >= getX() && x < getX() + getWidth() && y >= getY() && y < getY() + getHeight();
}
- public boolean containsCursor() {
- return contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY());
- }
-
public void requestReassembly() {
if (parent != null) {
parent.requestReassembly();
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/DragManager.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/DragManager.java
new file mode 100644
index 0000000..462be26
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/DragManager.java
@@ -0,0 +1,77 @@
+/*
+ * 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.client.graphics.gui;
+
+import java.util.Objects;
+
+import glm.vec._2.d.Vec2d;
+import ru.windcorp.progressia.client.graphics.gui.event.DragEvent;
+import ru.windcorp.progressia.client.graphics.gui.event.DragStartEvent;
+import ru.windcorp.progressia.client.graphics.gui.event.DragStopEvent;
+import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
+import ru.windcorp.progressia.client.graphics.input.KeyEvent;
+import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
+import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
+
+public class DragManager {
+
+ private Component component;
+
+ private boolean isDragged = false;
+ private final Vec2d change = new Vec2d();
+
+ public void install(Component c) {
+ Objects.requireNonNull(c, "c");
+ if (c == component) {
+ return;
+ }
+ if (component != null) {
+ throw new IllegalStateException("Already installed on " + component + "; attempted to install on " + c);
+ }
+
+ component = c;
+
+ c.addInputListener(CursorMoveEvent.class, this::onCursorMove, InputBus.Option.ALWAYS);
+ c.addKeyListener(KeyMatcher.LMB, this::onLMB, InputBus.Option.ALWAYS, InputBus.Option.IGNORE_ACTION);
+ }
+
+ private void onCursorMove(CursorMoveEvent e) {
+ if (isDragged) {
+ Vec2d currentChange = e.getChange(null);
+ change.add(currentChange);
+ component.dispatchEvent(new DragEvent(component, currentChange, change));
+ }
+ }
+
+ private void onLMB(KeyEvent e) {
+ if (isDragged && e.isRelease()) {
+
+ isDragged = false;
+ component.dispatchEvent(new DragStopEvent(component, change));
+
+ } else if (!isDragged && !e.isConsumed() && e.isPress() && component.isHovered()) {
+
+ isDragged = true;
+ change.set(0, 0);
+ component.dispatchEvent(new DragStartEvent(component));
+ e.consume();
+
+ }
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/GUILayer.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/GUILayer.java
index 2e0981c..b808aed 100755
--- a/src/main/java/ru/windcorp/progressia/client/graphics/gui/GUILayer.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/GUILayer.java
@@ -15,12 +15,19 @@
* 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.gui;
+import java.util.Iterator;
+
+import org.lwjgl.glfw.GLFW;
+
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
-import ru.windcorp.progressia.client.graphics.input.bus.Input;
+import ru.windcorp.progressia.client.graphics.input.InputEvent;
+import ru.windcorp.progressia.client.graphics.input.KeyEvent;
+import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
+import ru.windcorp.progressia.common.util.StashingStack;
public abstract class GUILayer extends AssembledFlatLayer {
@@ -33,7 +40,9 @@ public abstract class GUILayer extends AssembledFlatLayer {
public GUILayer(String name, Layout layout) {
super(name);
+
getRoot().setLayout(layout);
+ getRoot().addInputListener(KeyEvent.class, this::attemptFocusTransfer, InputBus.Option.IGNORE_FOCUS);
}
public Component getRoot() {
@@ -47,9 +56,81 @@ public abstract class GUILayer extends AssembledFlatLayer {
getRoot().assemble(target);
}
+ /**
+ * Stack frame for {@link #handleInput(InputEvent)}.
+ */
+ private static class EventHandlingFrame {
+ Component component;
+ Iterator children;
+
+ void init(Component c) {
+ component = c;
+ children = c.getChildren().iterator();
+ }
+
+ void reset() {
+ component = null;
+ children = null;
+ }
+ }
+
+ /**
+ * Stack for {@link #handleInput(InputEvent)}.
+ */
+ private StashingStack path = new StashingStack<>(64, EventHandlingFrame::new);
+
+ /*
+ * This is essentially a depth-first iteration of the component tree. The
+ * recursive procedure has been unrolled to reduce call stack length.
+ */
@Override
- protected void handleInput(Input input) {
- getRoot().dispatchInput(input);
+ public void handleInput(InputEvent event) {
+ if (!path.isEmpty()) {
+ throw new IllegalStateException(
+ "path is not empty: " + path + ". Are events being processed concurrently?"
+ );
+ }
+
+ path.push().init(root);
+
+ while (!path.isEmpty()) {
+
+ Iterator it = path.peek().children;
+ if (it.hasNext()) {
+
+ Component c = it.next();
+
+ if (c.isEnabled()) {
+ if (c.getChildren().isEmpty()) {
+ c.getInputBus().dispatch(event);
+ } else {
+ path.push().init(c);
+ }
+ }
+
+ } else {
+ path.peek().component.getInputBus().dispatch(event);
+ path.pop().reset();
+ }
+
+ }
+ }
+
+ private void attemptFocusTransfer(KeyEvent e) {
+ Component focused = getRoot().findFocused();
+
+ if (focused == null) {
+ return;
+ }
+
+ if (e.getKey() == GLFW.GLFW_KEY_TAB && !e.isRelease()) {
+ e.consume();
+ if (e.hasShift()) {
+ focused.focusPrevious();
+ } else {
+ focused.focusNext();
+ }
+ }
}
@Override
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/Hider.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Hider.java
index 1182eb4..95eebe7 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/gui/Hider.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Hider.java
@@ -21,7 +21,6 @@ import java.util.function.BooleanSupplier;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
-import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.client.graphics.model.Renderable;
public class Hider extends Component {
@@ -38,14 +37,16 @@ public class Hider extends Component {
addChild(contents);
}
- @Override
- public void dispatchInput(Input input) {
- if (shouldHide.getAsBoolean()) {
- return;
- }
-
- super.dispatchInput(input);
- }
+// FIXME
+//
+// @Override
+// public void dispatchInput(Input input) {
+// if (shouldHide.getAsBoolean()) {
+// return;
+// }
+//
+// super.dispatchInput(input);
+// }
@Override
public synchronized Component findFocused() {
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..8ea76f3 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,103 +103,102 @@ public class RadioButton extends BasicButton {
group.addChild(new Tick());
group.addChild(basicChild);
addChild(group);
-
- addListener(KeyEvent.class, e -> {
- if (e.isRelease()) return false;
-
+
+ addInputListener(KeyEvent.class, e -> {
+ if (e.isRelease()) return;
+
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;
+ e.consume();
} else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
if (this.group != null) {
this.group.selectNext();
this.group.getSelected().takeFocus();
}
- return true;
+ e.consume();
}
-
- 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/client/graphics/gui/event/DragEvent.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/event/DragEvent.java
new file mode 100644
index 0000000..74c91ae
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/event/DragEvent.java
@@ -0,0 +1,58 @@
+/*
+ * 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.client.graphics.gui.event;
+
+import glm.vec._2.d.Vec2d;
+import ru.windcorp.progressia.client.graphics.gui.Component;
+
+public class DragEvent extends ComponentEvent {
+
+ private final Vec2d currentChange = new Vec2d();
+ private final Vec2d totalChange = new Vec2d();
+
+ public DragEvent(Component component, Vec2d currentChange, Vec2d totalChange) {
+ super(component);
+ this.currentChange.set(currentChange.x, currentChange.y);
+ this.totalChange.set(totalChange.x, totalChange.y);
+ }
+
+ public Vec2d getCurrentChange() {
+ return currentChange;
+ }
+
+ public double getCurrentChangeX() {
+ return currentChange.x;
+ }
+
+ public double getCurrentChangeY() {
+ return currentChange.y;
+ }
+
+ public Vec2d getTotalChange() {
+ return totalChange;
+ }
+
+ public double getTotalChangeX() {
+ return totalChange.x;
+ }
+
+ public double getTotalChangeY() {
+ return totalChange.y;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/event/DragStartEvent.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/event/DragStartEvent.java
new file mode 100644
index 0000000..232b02a
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/event/DragStartEvent.java
@@ -0,0 +1,28 @@
+/*
+ * 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.client.graphics.gui.event;
+
+import ru.windcorp.progressia.client.graphics.gui.Component;
+
+public class DragStartEvent extends ComponentEvent {
+
+ public DragStartEvent(Component component) {
+ super(component);
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/input/bus/Input.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/event/DragStopEvent.java
similarity index 51%
rename from src/main/java/ru/windcorp/progressia/client/graphics/input/bus/Input.java
rename to src/main/java/ru/windcorp/progressia/client/graphics/gui/event/DragStopEvent.java
index 1aad6bb..bc23628 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/input/bus/Input.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/event/DragStopEvent.java
@@ -15,48 +15,30 @@
* 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.input.bus;
+package ru.windcorp.progressia.client.graphics.gui.event;
-import ru.windcorp.progressia.client.graphics.input.InputEvent;
+import glm.vec._2.d.Vec2d;
+import ru.windcorp.progressia.client.graphics.gui.Component;
-public class Input {
+public class DragStopEvent extends ComponentEvent {
- public static enum Target {
- FOCUSED, HOVERED, ALL
+ private final Vec2d totalChange = new Vec2d();
+
+ public DragStopEvent(Component component, Vec2d totalChange) {
+ super(component);
+ this.totalChange.set(totalChange.x, totalChange.y);
}
-
- private InputEvent event;
-
- private boolean isConsumed;
-
- private Target target;
-
- protected void initialize(InputEvent event, Target target) {
- this.event = event;
- this.target = target;
-
- this.isConsumed = false;
+
+ public Vec2d getTotalChange() {
+ return totalChange;
}
-
- public InputEvent getEvent() {
- return event;
+
+ public double getTotalChangeX() {
+ return totalChange.x;
}
-
- public boolean isConsumed() {
- return isConsumed;
- }
-
- public void setConsumed(boolean isConsumed) {
- this.isConsumed = isConsumed;
- }
-
- public void consume() {
- setConsumed(true);
- }
-
- public Target getTarget() {
- return target;
+
+ public double getTotalChangeY() {
+ return totalChange.y;
}
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/menu/MenuLayer.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/menu/MenuLayer.java
index 4fa155c..2c42cf0 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/gui/menu/MenuLayer.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/menu/MenuLayer.java
@@ -33,7 +33,6 @@ import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
-import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.client.localization.MutableString;
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
@@ -97,11 +96,9 @@ public class MenuLayer extends GUILayer {
}
@Override
- protected void handleInput(Input input) {
+ public void handleInput(InputEvent event) {
- if (!input.isConsumed()) {
- InputEvent event = input.getEvent();
-
+ if (!event.isConsumed()) {
if (event instanceof KeyEvent) {
KeyEvent keyEvent = (KeyEvent) event;
if (keyEvent.isPress() && keyEvent.getKey() == GLFW.GLFW_KEY_ESCAPE) {
@@ -110,8 +107,8 @@ public class MenuLayer extends GUILayer {
}
}
- super.handleInput(input);
- input.consume();
+ super.handleInput(event);
+ event.consume();
}
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/input/CursorMoveEvent.java b/src/main/java/ru/windcorp/progressia/client/graphics/input/CursorMoveEvent.java
index c87fc62..ff7ea82 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/input/CursorMoveEvent.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/input/CursorMoveEvent.java
@@ -18,7 +18,6 @@
package ru.windcorp.progressia.client.graphics.input;
-import glm.vec._2.Vec2;
import glm.vec._2.d.Vec2d;
public class CursorMoveEvent extends CursorEvent {
@@ -81,7 +80,10 @@ public class CursorMoveEvent extends CursorEvent {
return getNewY() - getPreviousY();
}
- public Vec2 getChange(Vec2 result) {
+ public Vec2d getChange(Vec2d result) {
+ if (result == null) {
+ result = new Vec2d();
+ }
return result.set(getChangeX(), getChangeY());
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/input/InputEvent.java b/src/main/java/ru/windcorp/progressia/client/graphics/input/InputEvent.java
index b0c2483..5edf055 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/input/InputEvent.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/input/InputEvent.java
@@ -15,13 +15,35 @@
* 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.input;
+import ru.windcorp.progressia.client.graphics.gui.Component;
+
+/**
+ * An instance of user input.
+ *
+ * User input events are typically generated by graphics backend between frames
+ * and passed to the graphics layers from top to bottom. Layers that use
+ * {@link Component}s will forward this event through the Component hierarchy.
+ *
+ * Events have a {@code consumed} flag. A freshly-generated event will have this
+ * flag set to {@code false}. Event listeners that process the event will
+ * usually choose to raise the flag ("consume the event") to ask future
+ * listeners to ignore this event. This is done to avoid multiple UI interfaces
+ * reacting to single input. By default, listeners will not receive consumed
+ * events; however, some listeners may choose to receive, handle and even
+ * un-consume the event.
+ *
+ * {@code InputEvent} objects may be reused for future input events after their
+ * processing is complete; to obtain a static copy, use {@link #snapshot()}.
+ */
public abstract class InputEvent {
private double time;
+ private boolean isConsumed = false;
+
public InputEvent(double time) {
this.time = time;
}
@@ -36,4 +58,16 @@ public abstract class InputEvent {
public abstract InputEvent snapshot();
+ public boolean isConsumed() {
+ return isConsumed;
+ }
+
+ public void setConsumed(boolean isConsumed) {
+ this.isConsumed = isConsumed;
+ }
+
+ public void consume() {
+ setConsumed(true);
+ }
+
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/input/KeyMatcher.java b/src/main/java/ru/windcorp/progressia/client/graphics/input/KeyMatcher.java
index 6d96ca2..048ddb5 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/input/KeyMatcher.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/input/KeyMatcher.java
@@ -18,33 +18,81 @@
package ru.windcorp.progressia.client.graphics.input;
-import java.lang.reflect.Field;
-import java.util.Collections;
-import java.util.HashMap;
import java.util.Map;
-import java.util.function.Predicate;
+import java.util.regex.Pattern;
import org.lwjgl.glfw.GLFW;
-import ru.windcorp.progressia.common.util.crash.CrashReports;
+import com.google.common.collect.ImmutableMap;
-public class KeyMatcher implements Predicate {
+public class KeyMatcher {
- private static final int ANY_ACTION = -1;
+ private static final Pattern DECLAR_SPLIT_REGEX = Pattern.compile("\\s*\\+\\s*");
+ private static final Map MOD_TOKENS = ImmutableMap.of(
+ "SHIFT", GLFW.GLFW_MOD_SHIFT,
+ "CONTROL", GLFW.GLFW_MOD_CONTROL,
+ "ALT", GLFW.GLFW_MOD_ALT,
+ "SUPER", GLFW.GLFW_MOD_SUPER
+ );
+
+ public static final KeyMatcher LMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_LEFT);
+ public static final KeyMatcher RMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_RIGHT);
+ public static final KeyMatcher MMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_MIDDLE);
private final int key;
private final int mods;
- private final int action;
- protected KeyMatcher(int key, int mods, int action) {
+ public KeyMatcher(int key, int mods) {
+ this.key = key;
+ this.mods = mods;
+ }
+
+ public KeyMatcher(int key) {
+ this.key = key;
+ this.mods = 0;
+ }
+
+ public KeyMatcher(String declar) {
+ String[] tokens = DECLAR_SPLIT_REGEX.split(declar);
+ if (tokens.length == 0) {
+ throw new IllegalArgumentException("No tokens found in \"" + declar + "\"");
+ }
+
+ int key = -1;
+ int mods = 0;
+
+ for (String token : tokens) {
+ token = token.toUpperCase();
+
+ if (MOD_TOKENS.containsKey(token)) {
+ int mod = MOD_TOKENS.get(token);
+ if ((mods & mod) != 0) {
+ throw new IllegalArgumentException("Duplicate modifier \"" + token + "\" in \"" + declar + "\"");
+ }
+ mods |= mod;
+ } else if (key != -1) {
+ throw new IllegalArgumentException("Too many non-modifier tokens in \"" + declar + "\": maximum one key, first offender: \"" + token + "\"");
+ } else {
+ token = token.replace(' ', '_');
+
+ if (token.startsWith("KEYPAD_")) {
+ token = "KP_" + token.substring("KEYPAD_".length());
+ }
+
+ key = Keys.getCode(token);
+
+ if (key == -1) {
+ throw new IllegalArgumentException("Unknown token \"" + token + "\" in \"" + declar + "\"");
+ }
+ }
+ }
+
this.key = key;
this.mods = mods;
- this.action = action;
}
- @Override
- public boolean test(KeyEvent event) {
- if (action != ANY_ACTION && event.getAction() != action)
+ public boolean matches(KeyEvent event) {
+ if (!event.isPress())
return false;
if (event.getKey() != getKey())
return false;
@@ -53,6 +101,15 @@ public class KeyMatcher implements Predicate {
return true;
}
+
+ public boolean matchesIgnoringAction(KeyEvent event) {
+ if (event.getKey() != getKey())
+ return false;
+ if ((event.getMods() & getMods()) != getMods())
+ return false;
+
+ return true;
+ }
public int getKey() {
return key;
@@ -62,51 +119,8 @@ public class KeyMatcher implements Predicate {
return mods;
}
- public int getAction() {
- return action;
- }
-
- public static KeyMatcher of(int key) {
- return new KeyMatcher(key, 0, GLFW.GLFW_PRESS);
- }
-
- private static final Map RESOLVED_KEYS = Collections.synchronizedMap(new HashMap<>());
-
- public static KeyMatcher of(String glfwConstantName) {
- return RESOLVED_KEYS.computeIfAbsent(glfwConstantName, givenName -> {
- String expectedName = "GLFW_KEY_" + givenName.toUpperCase();
-
- try {
- Field field = GLFW.class.getDeclaredField(expectedName);
- return of(field.getInt(null));
- } catch (NoSuchFieldException e) {
- String hint = "";
-
- if (glfwConstantName.startsWith("GLFW_KEY_")) {
- hint = " (remove prefix \"GLFW_KEY_\")";
- }
-
- throw new IllegalArgumentException("Unknown key constant \"" + glfwConstantName + "\"" + hint);
- } catch (IllegalArgumentException | IllegalAccessException e) {
- throw CrashReports.report(e, "Could not access GLFW key field {}", expectedName);
- }
- });
- }
-
- public static KeyMatcher ofLeftMouseButton() {
- return new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_LEFT, 0, GLFW.GLFW_PRESS);
- }
-
- public static KeyMatcher ofRightMouseButton() {
- return new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_RIGHT, 0, GLFW.GLFW_PRESS);
- }
-
- public static KeyMatcher ofMiddleMouseButton() {
- return new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_MIDDLE, 0, GLFW.GLFW_PRESS);
- }
-
public KeyMatcher with(int modifier) {
- return new KeyMatcher(key, this.mods + modifier, action);
+ return new KeyMatcher(key, mods | modifier);
}
public KeyMatcher withShift() {
@@ -124,17 +138,5 @@ public class KeyMatcher implements Predicate {
public KeyMatcher withSuper() {
return with(GLFW.GLFW_MOD_SUPER);
}
-
- public KeyMatcher onRelease() {
- return new KeyMatcher(key, mods, GLFW.GLFW_RELEASE);
- }
-
- public KeyMatcher onRepeat() {
- return new KeyMatcher(key, mods, GLFW.GLFW_REPEAT);
- }
-
- public KeyMatcher onAnyAction() {
- return new KeyMatcher(key, mods, ANY_ACTION);
- }
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/input/Keys.java b/src/main/java/ru/windcorp/progressia/client/graphics/input/Keys.java
index 3904f59..9132c37 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/input/Keys.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/input/Keys.java
@@ -139,7 +139,7 @@ public class Keys {
}
public static int getCode(String internalName) {
- if (NAMES_TO_CODES.containsKey(internalName)) {
+ if (!NAMES_TO_CODES.containsKey(internalName)) {
return -1;
} else {
return NAMES_TO_CODES.get(internalName);
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/input/bus/InputBus.java b/src/main/java/ru/windcorp/progressia/client/graphics/input/bus/InputBus.java
index a22a243..82f7275 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/input/bus/InputBus.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/input/bus/InputBus.java
@@ -15,73 +15,401 @@
* 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.input.bus;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Objects;
+import ru.windcorp.jputil.ArrayUtil;
+import ru.windcorp.progressia.client.graphics.gui.Component;
+import ru.windcorp.progressia.client.graphics.input.CursorEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
+import ru.windcorp.progressia.client.graphics.input.KeyEvent;
+import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
+import ru.windcorp.progressia.client.graphics.input.WheelEvent;
+import ru.windcorp.progressia.common.util.crash.CrashReports;
+/**
+ * An event bus optionally related to a {@link Component} that delivers input
+ * events to input listeners. This bus may skip listeners based on circumstance;
+ * behavior can be customized for each listener with {@link Option}s.
+ *
+ * By default, events are filtered by four checks before being delivered to each
+ * listener:
+ *
+ *
Consumption check: unless {@link Option#RECEIVE_CONSUMED
+ * RECEIVE_CONSUMED} is set, events that are consumed will not be
+ * delivered.
+ *
Hover check: for certain event types (for example,
+ * {@link WheelEvent} or {@link KeyEvent} that {@link KeyEvent#isMouse()
+ * isMouse()}), the event will only be delivered if the component is hovered.
+ * This check may be bypassed with option {@link Option#IGNORE_HOVER
+ * IGNORE_HOVER} or made mandatory for all events with
+ * {@link Option#REQUIRE_HOVER REQUIRE_HOVER}. Hover check automatically
+ * succeeds if no component is provided.
+ *
Focus check: for certain event types (for example,
+ * {@link KeyEvent} that {@code !isMouse()}), the event will only be delivered
+ * if the component has focus. This check may be bypassed with option
+ * {@link Option#IGNORE_FOCUS IGNORE_FOCUS} or made mandatory for all events
+ * with {@link Option#REQUIRE_FOCUS REQUIRE_FOCUS}. Focus check automatically
+ * succeeds if no component is provided.
+ *
Type check: events of type {@code E} are only delivered to
+ * listeners registered with event type {@code T} if objects of type {@code E}
+ * can be cast to {@code T}.
+ *
+ * Checks 1-3 are bypassed when option {@link Option#ALWAYS ALWAYS} is
+ * specified.
+ */
public class InputBus {
- private static class WrappedListener {
+ /**
+ * Options that allow customization of checks for listeners.
+ */
+ public enum Option {
+
+ /**
+ * Ignore checks for consumed events, hover and focus; deliver event if
+ * at all possible. This is shorthand for {@link #RECEIVE_CONSUMED},
+ * {@link #IGNORE_HOVER} and {@link #IGNORE_FOCUS}.
+ */
+ ALWAYS,
+
+ /**
+ * Receive events that were previously consumed.
+ */
+ RECEIVE_CONSUMED,
+
+ /**
+ * Do not process events if the listener is registered with a component
+ * and the component is not hovered.
+ */
+ REQUIRE_HOVER,
+
+ /**
+ * Deliver events even if the event is limited to hovered components by
+ * default.
+ */
+ IGNORE_HOVER,
+
+ /**
+ * Do not process events if the listener is registered with a component
+ * and the component is not focused.
+ */
+ REQUIRE_FOCUS,
+
+ /**
+ * Deliver events even if the event is limited to focused components by
+ * default.
+ */
+ IGNORE_FOCUS,
+
+ /**
+ * Deliver events according to
+ * {@link KeyMatcher#matchesIgnoringAction(KeyEvent)} rather than
+ * {@link KeyMatcher#matches(KeyEvent)} when a {@link KeyMatcher} is
+ * specified.
+ */
+ IGNORE_ACTION;
+
+ }
+
+ private enum YesNoDefault {
+ YES, NO, DEFAULT;
+ }
+
+ /**
+ * A listener with check preferences resolved and type specified.
+ */
+ private class WrappedListener {
private final Class> type;
- private final boolean handleConsumed;
+
+ private final boolean dropIfConsumed;
+ private final YesNoDefault dropIfNotHovered;
+ private final YesNoDefault dropIfNotFocused;
+
private final InputListener> listener;
public WrappedListener(
Class> type,
- boolean handleConsumed,
+ boolean dropIfConsumed,
+ YesNoDefault dropIfNotHovered,
+ YesNoDefault dropIfNotFocused,
InputListener> listener
) {
this.type = type;
- this.handleConsumed = handleConsumed;
+ this.dropIfConsumed = dropIfConsumed;
+ this.dropIfNotHovered = dropIfNotHovered;
+ this.dropIfNotFocused = dropIfNotFocused;
this.listener = listener;
}
- private boolean handles(Input input) {
- return (!input.isConsumed() || handleConsumed) &&
- type.isInstance(input.getEvent());
+ private boolean handles(InputEvent input) {
+ if (dropIfConsumed && input.isConsumed())
+ return false;
+
+ switch (dropIfNotHovered) {
+ case YES:
+ if (!isHovered())
+ return false;
+ break;
+ case NO:
+ break;
+ default:
+
+ if (isHovered())
+ break;
+
+ if (input instanceof KeyEvent && ((KeyEvent) input).isMouse())
+ return false;
+
+ if (input instanceof CursorEvent)
+ return false;
+
+ if (input instanceof WheelEvent)
+ return false;
+
+ break;
+ }
+
+ switch (dropIfNotFocused) {
+ case YES:
+ if (!isFocused())
+ return false;
+ break;
+ case NO:
+ break;
+ default:
+
+ if (isFocused())
+ break;
+
+ if (input instanceof KeyEvent && !((KeyEvent) input).isMouse())
+ return false;
+
+ break;
+ }
+
+ if (!type.isInstance(input))
+ return false;
+
+ return true;
}
+ /**
+ * Invokes the listener if the event is deemed appropriate by the four
+ * checks.
+ *
+ * @param event the event to deliver
+ */
@SuppressWarnings("unchecked")
- public void handle(Input input) {
- if (handles(input)) {
- boolean consumed = ((InputListener) listener)
- .handle(
- (InputEvent) type.cast(input.getEvent())
- );
+ public void handle(InputEvent event) {
+ if (handles(event)) {
+ // A runtime check of types has been performed; this is safe.
+ InputListener castListener = (InputListener) listener;
- input.setConsumed(consumed);
+ try {
+ castListener.handle(event);
+ } catch (Exception e) {
+ throw CrashReports.report(
+ e,
+ "InputListener %s for component %s has failed to receive event %s",
+ listener,
+ owner,
+ event
+ );
+ }
}
}
}
+ /**
+ * The component queried for focus and hover. May be {@code null}.
+ */
+ private final Component owner;
+
+ /**
+ * Registered listeners.
+ */
private final Collection listeners = new ArrayList<>(4);
- public void dispatch(Input input) {
- listeners.forEach(l -> l.handle(input));
+ /**
+ * Creates a new input bus that consults the specified {@link Component} to
+ * determine hover and focus.
+ *
+ * @param owner the component to use for hover and focus tests
+ * @see #InputBus()
+ */
+ public InputBus(Component owner) {
+ this.owner = Objects.requireNonNull(owner, "owner");
}
+ /**
+ * Creates a new input bus that assumes all hover and focus checks are
+ * successful.
+ *
+ * @see #InputBus(Component)
+ */
+ public InputBus() {
+ this.owner = null;
+ }
+
+ /**
+ * Determines whether hover should be assumed for this event bus.
+ *
+ * @return {@code true} iff no component is linked or the linked component
+ * is hovered
+ */
+ private boolean isHovered() {
+ return owner == null ? true : owner.isHovered();
+ }
+
+ /**
+ * Determines whether focus should be assumed for this event bus.
+ *
+ * @return {@code true} iff no component is linked or the linked component
+ * is focused
+ */
+ private boolean isFocused() {
+ return owner == null ? true : owner.isFocused();
+ }
+
+ /**
+ * Dispatches (delivers) the provided event to all appropriate listeners.
+ *
+ * @param event the event to process
+ */
+ public void dispatch(InputEvent event) {
+ Objects.requireNonNull(event, "event");
+ for (WrappedListener listener : listeners) {
+ listener.handle(event);
+ }
+ }
+
+ /**
+ * Registers a listener on this bus.
+ *
+ * {@code type} specifies the class of events that should be passed to this
+ * listener. Only events of types that extend, implement or equal
+ * {@code type} are processed.
+ *
+ * Zero or more {@link Option}s may be specified to enable or disable the
+ * processing of certain events in certain circumstances. See
+ * {@linkplain InputBus class description} for a detailed breakdown of the
+ * checks performed and the effects of various options. When providing
+ * options to this method, later options override the effects of previous
+ * options.
+ *
+ * Option {@link Option#IGNORE_ACTION IGNORE_ACTION} is ignored silently.
+ *
+ * @param type the event class to deliver
+ * @param listener the listener
+ * @param options the options for this listener
+ */
public void register(
Class extends T> type,
- boolean handlesConsumed,
- InputListener listener
+ InputListener listener,
+ Option... options
) {
- listeners.add(new WrappedListener(type, handlesConsumed, listener));
+ Objects.requireNonNull(type, "type");
+ Objects.requireNonNull(listener, "listener");
+
+ boolean dropIfConsumed = true;
+ YesNoDefault dropIfNotHovered = YesNoDefault.DEFAULT;
+ YesNoDefault dropIfNotFocused = YesNoDefault.DEFAULT;
+
+ if (options != null) {
+ for (Option option : options) {
+ switch (option) {
+ case ALWAYS:
+ dropIfConsumed = false;
+ dropIfNotHovered = YesNoDefault.NO;
+ dropIfNotFocused = YesNoDefault.NO;
+ break;
+ case RECEIVE_CONSUMED:
+ dropIfConsumed = false;
+ break;
+ case REQUIRE_HOVER:
+ dropIfNotHovered = YesNoDefault.YES;
+ break;
+ case IGNORE_HOVER:
+ dropIfNotFocused = YesNoDefault.NO;
+ break;
+ case REQUIRE_FOCUS:
+ dropIfNotHovered = YesNoDefault.YES;
+ break;
+ case IGNORE_FOCUS:
+ dropIfNotFocused = YesNoDefault.NO;
+ break;
+ case IGNORE_ACTION:
+ // Ignore
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected option " + option);
+ }
+ }
+ }
+
+ listeners.add(new WrappedListener(type, dropIfConsumed, dropIfNotHovered, dropIfNotFocused, listener));
}
- public void register(
- Class extends T> type,
- InputListener listener
- ) {
- register(type, false, listener);
+ /**
+ * Registers a {@link KeyEvent} listener on this bus. An event has to match
+ * the provided {@link KeyMatcher} to be delivered to the listener.
+ *
+ * Zero or more {@link Option}s may be specified to enable or disable the
+ * processing of certain events in certain circumstances. See
+ * {@linkplain InputBus class description} for a detailed breakdown of the
+ * checks performed and the effects of various options. When providing
+ * options to this method, later options override the effects of previous
+ * options.
+ *
+ * Option {@link Option#IGNORE_ACTION IGNORE_ACTION} requests that events
+ * are delivered according to
+ * {@link KeyMatcher#matchesIgnoringAction(KeyEvent)} rather than
+ * {@link KeyMatcher#matches(KeyEvent)}.
+ * specified.
+ *
+ * @param matcher an event filter
+ * @param listener the listener
+ * @param options the options for this listener
+ */
+ public void register(KeyMatcher matcher, InputListener super KeyEvent> listener, Option... options) {
+ Objects.requireNonNull(matcher, "matcher");
+ Objects.requireNonNull(listener, "listener");
+
+ InputListener filteringListener;
+
+ if (ArrayUtil.firstIndexOf(options, Option.IGNORE_ACTION) != -1) {
+ filteringListener = e -> {
+ if (matcher.matchesIgnoringAction(e)) {
+ listener.handle(e);
+ }
+ };
+ } else {
+ filteringListener = e -> {
+ if (matcher.matches(e)) {
+ listener.handle(e);
+ }
+ };
+ }
+
+ register(KeyEvent.class, filteringListener, options);
}
+ /**
+ * Removes all occurrences of the provided listener from this bus.
+ *
+ * @param listener the listener to unregister
+ */
public void unregister(InputListener> listener) {
+ if (listener == null) {
+ return;
+ }
+
listeners.removeIf(l -> l.listener == listener);
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/input/bus/InputListener.java b/src/main/java/ru/windcorp/progressia/client/graphics/input/bus/InputListener.java
index 0d68b5e..1db2410 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/input/bus/InputListener.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/input/bus/InputListener.java
@@ -23,6 +23,6 @@ import ru.windcorp.progressia.client.graphics.input.InputEvent;
@FunctionalInterface
public interface InputListener {
- boolean handle(T event);
+ void handle(T event);
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/model/ShapeParts.java b/src/main/java/ru/windcorp/progressia/client/graphics/model/ShapeParts.java
index 50f744f..807fcda 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/model/ShapeParts.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/model/ShapeParts.java
@@ -25,13 +25,14 @@ import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
import ru.windcorp.progressia.client.graphics.texture.Texture;
+import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram.WRPVertexBuilder;
import ru.windcorp.progressia.common.world.rels.AbsFace;
public class ShapeParts {
private ShapeParts() {
}
-
+
public static ShapePart createRectangle(
ShapeRenderProgram program,
Texture texture,
@@ -41,25 +42,65 @@ public class ShapeParts {
Vec3 height,
boolean flip
) {
- VertexBuilder builder = program.getVertexBuilder();
+ return createRectangle(program, texture, colorMultiplier, origin, width, height, flip, null);
+ }
- builder.addVertex(
- origin,
- colorMultiplier,
- new Vec2(0, 0)
- ).addVertex(
- origin.add_(height),
- colorMultiplier,
- new Vec2(0, 1)
- ).addVertex(
- origin.add_(width),
- colorMultiplier,
- new Vec2(1, 0)
- ).addVertex(
- origin.add_(width).add(height),
- colorMultiplier,
- new Vec2(1, 1)
- );
+ public static ShapePart createRectangle(
+ ShapeRenderProgram program,
+ Texture texture,
+ Vec4 colorMultiplier,
+ Vec3 origin,
+ Vec3 width,
+ Vec3 height,
+ boolean flip,
+ Vec3 forcedNormals
+ ) {
+ VertexBuilder builder = program.getVertexBuilder();
+
+ if (forcedNormals != null && builder instanceof WRPVertexBuilder) {
+ ((WRPVertexBuilder) builder).addVertex(
+ origin,
+ colorMultiplier,
+ new Vec2(0, 0),
+ forcedNormals
+ );
+ ((WRPVertexBuilder) builder).addVertex(
+ origin.add_(height),
+ colorMultiplier,
+ new Vec2(0, 1),
+ forcedNormals
+ );
+ ((WRPVertexBuilder) builder).addVertex(
+ origin.add_(width),
+ colorMultiplier,
+ new Vec2(1, 0),
+ forcedNormals
+ );
+ ((WRPVertexBuilder) builder).addVertex(
+ origin.add_(width).add(height),
+ colorMultiplier,
+ new Vec2(1, 1),
+ forcedNormals
+ );
+ } else {
+ builder.addVertex(
+ origin,
+ colorMultiplier,
+ new Vec2(0, 0)
+ ).addVertex(
+ origin.add_(height),
+ colorMultiplier,
+ new Vec2(0, 1)
+ ).addVertex(
+ origin.add_(width),
+ colorMultiplier,
+ new Vec2(1, 0)
+ ).addVertex(
+ origin.add_(width).add(height),
+ colorMultiplier,
+ new Vec2(1, 1)
+ );
+ }
ShortBuffer buffer = flip ? ShortBuffer.wrap(
new short[] {
@@ -112,6 +153,6 @@ public class ShapeParts {
height,
inner
);
- }
+ }
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/model/ShapePrototype.java b/src/main/java/ru/windcorp/progressia/client/graphics/model/ShapePrototype.java
new file mode 100644
index 0000000..f2cc631
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/model/ShapePrototype.java
@@ -0,0 +1,248 @@
+/*
+ * 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.client.graphics.model;
+
+import java.nio.ShortBuffer;
+import java.util.Objects;
+
+import glm.mat._3.Mat3;
+import glm.mat._4.Mat4;
+import glm.vec._2.Vec2;
+import glm.vec._3.Vec3;
+import glm.vec._4.Vec4;
+import ru.windcorp.progressia.client.graphics.Colors;
+import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
+import ru.windcorp.progressia.client.graphics.texture.Texture;
+import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
+import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram.WRPVertexBuilder;
+import ru.windcorp.progressia.common.util.StashingStack;
+import ru.windcorp.progressia.common.util.VectorUtil;
+
+public class ShapePrototype {
+
+ private final ShapeRenderProgram program;
+
+ private Texture texture;
+
+ private final Vec3[] positions;
+ private final Vec4[] colorMultipliers;
+ private final Vec2[] textureCoords;
+ private final Vec3[] forcedNormals;
+
+ private ShortBuffer indices;
+ private ShortBuffer flippedIndices = null;
+
+ protected static final int TRANSFORM_STACK_SIZE = 64;
+ private final StashingStack transformStack = new StashingStack<>(
+ TRANSFORM_STACK_SIZE,
+ Mat4::new
+ );
+
+ public ShapePrototype(
+ ShapeRenderProgram program,
+ Texture texture,
+ Vec3[] positions,
+ Vec4[] colorMultipliers,
+ Vec2[] textureCoords,
+ Vec3[] forcedNormals,
+ ShortBuffer indices
+ ) {
+ this.program = Objects.requireNonNull(program, "program");
+ this.texture = texture;
+ this.indices = Objects.requireNonNull(indices, "indices");
+
+ Objects.requireNonNull(positions, "positions");
+ Objects.requireNonNull(colorMultipliers, "colorMultipliers");
+ Objects.requireNonNull(textureCoords, "textureCoords");
+
+ if (forcedNormals != null && !(program instanceof WorldRenderProgram)) {
+ throw new IllegalArgumentException("Cannot force normals on non-WorldRenderPrograms cuz javahorse stupiddd");
+ }
+
+ if (forcedNormals == null) {
+ forcedNormals = new Vec3[positions.length];
+ }
+
+ this.positions = positions;
+ this.colorMultipliers = colorMultipliers;
+ this.textureCoords = textureCoords;
+ this.forcedNormals = forcedNormals;
+
+ if (positions.length != colorMultipliers.length) {
+ throw new IllegalArgumentException("positions.length (" + positions.length + ") != colorMultipliers.length (" + colorMultipliers + ")");
+ }
+
+ if (positions.length != textureCoords.length) {
+ throw new IllegalArgumentException("positions.length (" + positions.length + ") != textureCoords.length (" + textureCoords + ")");
+ }
+
+ if (positions.length != forcedNormals.length) {
+ throw new IllegalArgumentException("positions.length (" + positions.length + ") != forcedNormals.length (" + forcedNormals + ")");
+ }
+
+ transformStack.push().identity();
+ }
+
+ public ShapePart build() {
+ VertexBuilder builder = program.getVertexBuilder();
+
+ Vec3 transformedPosition = new Vec3();
+
+ for (int i = 0; i < positions.length; ++i) {
+
+ transformedPosition.set(positions[i]);
+ if (transformStack.getSize() > 1) {
+ VectorUtil.applyMat4(transformedPosition, transformStack.getHead());
+ }
+
+ if (forcedNormals[i] != null && builder instanceof WRPVertexBuilder) {
+ ((WRPVertexBuilder) builder).addVertex(
+ transformedPosition, colorMultipliers[i], textureCoords[i], forcedNormals[i]
+ );
+ } else {
+ builder.addVertex(transformedPosition, colorMultipliers[i], textureCoords[i]);
+ }
+ }
+
+ return new ShapePart(texture, builder.assemble(), indices);
+ }
+
+ public ShapePrototype apply(Mat4 transform) {
+ for (Vec3 vector : positions) {
+ VectorUtil.applyMat4(vector, transform);
+ }
+ return this;
+ }
+
+ public ShapePrototype apply(Mat3 transform) {
+ for (Vec3 vector : positions) {
+ transform.mul(vector);
+ }
+ return this;
+ }
+
+ public Mat4 push() {
+ Mat4 previous = transformStack.getHead();
+ return transformStack.push().set(previous);
+ }
+
+ public ShapePrototype pop() {
+ transformStack.pop();
+ return this;
+ }
+
+ public ShapePrototype setTexture(Texture texture) {
+ this.texture = texture;
+ return this;
+ }
+
+ public ShapePrototype deleteTexture() {
+ this.texture = null;
+ return this;
+ }
+
+ public ShapePrototype resetColorMultiplier() {
+ for (Vec4 color : colorMultipliers) {
+ color.set(Colors.WHITE);
+ }
+ return this;
+ }
+
+ public ShapePrototype addColorMultiplier(Vec4 color) {
+ for (Vec4 c : colorMultipliers) {
+ c.mul(color);
+ }
+ return this;
+ }
+
+ public ShapePrototype flip() {
+ ShortBuffer tmp = indices;
+ indices = flippedIndices;
+ flippedIndices = tmp;
+
+ if (indices == null) {
+ int length = flippedIndices.limit();
+ indices = ShortBuffer.allocate(length);
+ for (int i = 0; i < length; ++i) {
+ indices.put(i, flippedIndices.get(length - i - 1));
+ }
+ }
+
+ return this;
+ }
+
+ public ShapePrototype makeDoubleSided() {
+ int length = indices.limit();
+ ShortBuffer newIndices = ShortBuffer.allocate(length * 2);
+
+ for (int i = 0; i < length; ++i) {
+ newIndices.put(i, indices.get(i));
+ }
+ for (int i = 0; i < length; ++i) {
+ newIndices.put(i + length, indices.get(length - i - 1));
+ }
+
+ indices = flippedIndices = newIndices;
+
+ return this;
+ }
+
+ public static ShapePrototype unitSquare(Texture texture, Vec4 color, ShapeRenderProgram program) {
+ return new ShapePrototype(
+ program,
+ texture,
+ new Vec3[] {
+ new Vec3(0, 0, 0),
+ new Vec3(0, 1, 0),
+ new Vec3(1, 0, 0),
+ new Vec3(1, 1, 0)
+ },
+ new Vec4[] {
+ new Vec4(color),
+ new Vec4(color),
+ new Vec4(color),
+ new Vec4(color)
+ },
+ new Vec2[] {
+ new Vec2(0, 0),
+ new Vec2(0, 1),
+ new Vec2(1, 0),
+ new Vec2(1, 1)
+ },
+ null,
+ ShortBuffer.wrap(new short[] {3, 1, 0, 2, 3, 0})
+ );
+ }
+
+ public static ShapePrototype unitSquare(Texture texture, Vec4 color) {
+ return unitSquare(texture, color, WorldRenderProgram.getDefault());
+ }
+
+ public static ShapePrototype unitSquare(Texture texture) {
+ return unitSquare(texture, Colors.WHITE, WorldRenderProgram.getDefault());
+ }
+
+ public static ShapePrototype unitSquare(Vec4 color) {
+ return unitSquare(null, color, WorldRenderProgram.getDefault());
+ }
+
+ public static ShapePrototype unitSquare() {
+ return unitSquare(null, Colors.WHITE, WorldRenderProgram.getDefault());
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/model/Shapes.java b/src/main/java/ru/windcorp/progressia/client/graphics/model/Shapes.java
index add322e..3d30470 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/model/Shapes.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/model/Shapes.java
@@ -20,10 +20,12 @@ package ru.windcorp.progressia.client.graphics.model;
import java.util.Map;
+import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.texture.Texture;
+import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.world.rels.AbsFace;
public class Shapes {
@@ -301,6 +303,22 @@ public class Shapes {
return this;
}
+
+ public PppBuilder apply(Mat4 transform) {
+ VectorUtil.applyMat4(origin, transform);
+ VectorUtil.rotateOnly(width, transform);
+ VectorUtil.rotateOnly(height, transform);
+ VectorUtil.rotateOnly(depth, transform);
+ return this;
+ }
+
+ public PppBuilder scale(float factor) {
+ origin.mul(factor);
+ width.mul(factor);
+ height.mul(factor);
+ depth.mul(factor);
+ return this;
+ }
public PppBuilder flip() {
this.flip = true;
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureLoader.java b/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureLoader.java
index 18f2326..1ce3998 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureLoader.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureLoader.java
@@ -28,6 +28,7 @@ import javax.imageio.ImageIO;
import ru.windcorp.progressia.common.resource.Resource;
import ru.windcorp.progressia.common.util.BinUtil;
+import ru.windcorp.progressia.common.util.crash.CrashReports;
public class TextureLoader {
@@ -99,7 +100,12 @@ public class TextureLoader {
TextureSettings settings
)
throws IOException {
- return loadPixels(resource.getInputStream(), settings);
+
+ InputStream stream = resource.getInputStream();
+ if (stream == null) {
+ throw CrashReports.report(null, "Texture \"%s\" not found", resource.getName());
+ }
+ return loadPixels(stream, settings);
}
private TextureLoader() {
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/EntityAnchor.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/EntityAnchor.java
index cb578d9..1df46cf 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/world/EntityAnchor.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/EntityAnchor.java
@@ -91,5 +91,14 @@ public class EntityAnchor implements Anchor {
public Collection getCameraModes() {
return modes;
}
+
+ public EntityData getEntity() {
+ return entity;
+ }
+
+ @Override
+ public String toString() {
+ return "Anchor for entity " + entity;
+ }
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java
index c81e7a7..df3c99a 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java
@@ -31,7 +31,7 @@ import ru.windcorp.progressia.client.comms.controls.InputBasedControls;
import ru.windcorp.progressia.client.graphics.Layer;
import ru.windcorp.progressia.client.graphics.backend.FaceCulling;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
-import ru.windcorp.progressia.client.graphics.input.bus.Input;
+import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
import ru.windcorp.progressia.client.graphics.model.Shapes.PppBuilder;
@@ -45,7 +45,7 @@ import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.GravityModel;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.test.CollisionModelRenderer;
-import ru.windcorp.progressia.test.TestPlayerControls;
+import ru.windcorp.progressia.test.controls.TestPlayerControls;
public class LayerWorld extends Layer {
@@ -214,6 +214,9 @@ public class LayerWorld extends Layer {
if (ClientState.getInstance().getLocalPlayer().getEntity() == entity && tmp_testControls.isFlying()) {
return;
}
+ if (entity.getId().equals("Test:NoclipCamera")) {
+ return;
+ }
Vec3 gravitationalAcceleration = Vectors.grab3();
gm.getGravity(entity.getPosition(), gravitationalAcceleration);
@@ -225,14 +228,9 @@ public class LayerWorld extends Layer {
}
@Override
- protected void handleInput(Input input) {
- if (input.isConsumed())
- return;
-
- tmp_testControls.handleInput(input);
-
- if (!input.isConsumed()) {
- inputBasedControls.handleInput(input);
+ public void handleInput(InputEvent event) {
+ if (!event.isConsumed()) {
+ inputBasedControls.handleInput(event);
}
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/WorldRenderProgram.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/WorldRenderProgram.java
index c005454..55ef3f8 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/world/WorldRenderProgram.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/WorldRenderProgram.java
@@ -199,6 +199,10 @@ public class WorldRenderProgram extends ShapeRenderProgram {
int offset = vertices.position() + index * getBytesPerVertex() + (3 * Float.BYTES +
4 * Float.BYTES +
2 * Float.BYTES);
+
+ if (!Float.isNaN(vertices.getFloat(offset + 0 * Float.BYTES))) {
+ return; // normals are forced
+ }
vertices.putFloat(offset + 0 * Float.BYTES, normal.x);
vertices.putFloat(offset + 1 * Float.BYTES, normal.y);
@@ -212,7 +216,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
return new WRPVertexBuilder();
}
- private static class WRPVertexBuilder implements VertexBuilder {
+ public static class WRPVertexBuilder implements VertexBuilder {
// TODO Throw VertexBuilders out the window and rewrite completely.
// I want to _extend_ VBs, not re-implement them for children of SRP
@@ -220,11 +224,17 @@ public class WorldRenderProgram extends ShapeRenderProgram {
final Vec3 position;
final Vec4 colorMultiplier;
final Vec2 textureCoords;
-
+ final Vec3 normals;
+
Vertex(Vec3 position, Vec4 colorMultiplier, Vec2 textureCoords) {
+ this(position, colorMultiplier, textureCoords, new Vec3(Float.NaN, Float.NaN, Float.NaN));
+ }
+
+ Vertex(Vec3 position, Vec4 colorMultiplier, Vec2 textureCoords, Vec3 normals) {
this.position = position;
this.colorMultiplier = colorMultiplier;
this.textureCoords = textureCoords;
+ this.normals = normals;
}
}
@@ -291,6 +301,24 @@ public class WorldRenderProgram extends ShapeRenderProgram {
return this;
}
+
+ public VertexBuilder addVertex(
+ Vec3 position,
+ Vec4 colorMultiplier,
+ Vec2 textureCoords,
+ Vec3 normals
+ ) {
+ vertices.add(
+ new Vertex(
+ new Vec3(position),
+ new Vec4(colorMultiplier),
+ new Vec2(textureCoords),
+ new Vec3(normals)
+ )
+ );
+
+ return this;
+ }
@Override
public ByteBuffer assemble() {
@@ -309,9 +337,9 @@ public class WorldRenderProgram extends ShapeRenderProgram {
.putFloat(v.colorMultiplier.w)
.putFloat(v.textureCoords.x)
.putFloat(v.textureCoords.y)
- .putFloat(Float.NaN)
- .putFloat(Float.NaN)
- .putFloat(Float.NaN);
+ .putFloat(v.normals.x)
+ .putFloat(v.normals.y)
+ .putFloat(v.normals.z);
}
result.flip();
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/hud/InteractiveSlotComponent.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/hud/InteractiveSlotComponent.java
index af030ae..3de6eea 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/world/hud/InteractiveSlotComponent.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/hud/InteractiveSlotComponent.java
@@ -20,7 +20,9 @@ package ru.windcorp.progressia.client.graphics.world.hud;
import glm.vec._2.i.Vec2i;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.gui.Button;
+import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
+import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
import ru.windcorp.progressia.client.graphics.input.WheelScrollEvent;
import ru.windcorp.progressia.common.Units;
@@ -47,7 +49,7 @@ public class InteractiveSlotComponent extends Button {
}
public InteractiveSlotComponent(String name, SlotComponent component, HUDWorkspace workspace) {
- super(name, null, null);
+ super(name, (Label) null);
this.slotComponent = component;
this.workspace = workspace;
@@ -63,14 +65,13 @@ public class InteractiveSlotComponent extends Button {
private void addListeners() {
addAction(button -> onMainAction());
- addListener(KeyMatcher.ofRightMouseButton(), this::onAltAction);
+ addKeyListener(KeyMatcher.RMB, this::onAltAction);
- addListener(WheelScrollEvent.class, event -> {
+ addInputListener(WheelScrollEvent.class, event -> {
if (event.hasVerticalMovement()) {
onSingleMoveAction(event.isDown());
- return true;
+ event.consume();
}
- return false;
});
}
@@ -119,7 +120,7 @@ public class InteractiveSlotComponent extends Button {
}
}
- private void onAltAction() {
+ private void onAltAction(KeyEvent e) {
ItemSlot handSlot = workspace.getHand().slot();
ItemSlot invSlot = getSlot();
if (invSlot == null) {
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/hud/LayerHUD.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/hud/LayerHUD.java
index 1f0031b..91fade5 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/world/hud/LayerHUD.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/hud/LayerHUD.java
@@ -17,8 +17,6 @@
*/
package ru.windcorp.progressia.client.graphics.world.hud;
-import org.lwjgl.glfw.GLFW;
-
import com.google.common.eventbus.Subscribe;
import ru.windcorp.progressia.client.events.NewLocalEntityEvent;
@@ -28,9 +26,7 @@ import ru.windcorp.progressia.client.graphics.gui.Components;
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
import ru.windcorp.progressia.client.graphics.gui.Group;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
-import ru.windcorp.progressia.client.graphics.input.KeyEvent;
-import ru.windcorp.progressia.client.graphics.input.bus.Input;
-import ru.windcorp.progressia.test.TestPlayerControls;
+import ru.windcorp.progressia.client.graphics.input.InputEvent;
public class LayerHUD extends GUILayer {
@@ -109,31 +105,12 @@ public class LayerHUD extends GUILayer {
}
@Override
- protected void handleInput(Input input) {
+ public void handleInput(InputEvent e) {
if (isHidden) {
return;
}
- super.handleInput(input);
-
- if (showInventory) {
- TestPlayerControls.getInstance().handleCtrlIfApplicable(input);
- if (!input.isConsumed()) {
- handleCloseInventoryIfApplicable(input);
- }
- input.consume();
- }
- }
-
- private void handleCloseInventoryIfApplicable(Input input) {
- if (input.getEvent() instanceof KeyEvent) {
- KeyEvent event = (KeyEvent) input.getEvent();
-
- if (event.isPress() && (event.getKey() == GLFW.GLFW_KEY_E || event.getKey() == GLFW.GLFW_KEY_ESCAPE)) {
- setInventoryShown(false);
- input.consume();
- }
- }
+ super.handleInput(e);
}
@Override
diff --git a/src/main/java/ru/windcorp/progressia/client/localization/Localizer.java b/src/main/java/ru/windcorp/progressia/client/localization/Localizer.java
index b61d000..ee7494d 100644
--- a/src/main/java/ru/windcorp/progressia/client/localization/Localizer.java
+++ b/src/main/java/ru/windcorp/progressia/client/localization/Localizer.java
@@ -72,6 +72,12 @@ public class Localizer {
public synchronized String getLanguage() {
return language;
}
+
+ public List getLanguages() {
+ List result = new ArrayList<>(langList.keySet());
+ result.sort(null);
+ return result;
+ }
public synchronized String getValue(String key) {
if (data == null) {
diff --git a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderCross.java b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderCross.java
index 01af8f6..f312ed2 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderCross.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderCross.java
@@ -114,7 +114,8 @@ public class TileRenderCross extends TileRender implements TileOptimizedCustom {
origin,
width,
height,
- false
+ false,
+ new Vec3(0, 0, 1)
)
);
output.accept(
@@ -125,7 +126,8 @@ public class TileRenderCross extends TileRender implements TileOptimizedCustom {
origin,
width,
height,
- true
+ true,
+ new Vec3(0, 0, 1)
)
);
}
diff --git a/src/main/java/ru/windcorp/progressia/common/collision/colliders/Collider.java b/src/main/java/ru/windcorp/progressia/common/collision/colliders/Collider.java
index 9b97e40..be85416 100644
--- a/src/main/java/ru/windcorp/progressia/common/collision/colliders/Collider.java
+++ b/src/main/java/ru/windcorp/progressia/common/collision/colliders/Collider.java
@@ -105,6 +105,9 @@ public class Collider {
// For every pair of colls
for (int i = 0; i < colls.size(); ++i) {
Collideable a = colls.get(i);
+ if (a.getCollisionModel() == null) {
+ continue;
+ }
tuneWorldCollisionHelper(a, tickLength, world, workspace);
@@ -115,6 +118,10 @@ public class Collider {
for (int j = i + 1; j < colls.size(); ++j) {
Collideable b = colls.get(j);
+ if (b.getCollisionModel() == null) {
+ continue;
+ }
+
Collision collision = getCollision(a, b, tickLength, workspace);
result = workspace.updateLatestCollision(result, collision);
}
diff --git a/src/main/java/ru/windcorp/progressia/common/util/math/FloatFunction.java b/src/main/java/ru/windcorp/progressia/common/util/math/FloatFunction.java
new file mode 100644
index 0000000..f2438b3
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/util/math/FloatFunction.java
@@ -0,0 +1,25 @@
+/*
+ * 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.common.util.math;
+
+@FunctionalInterface
+public interface FloatFunction {
+
+ float apply(float x);
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/util/math/FloatFunction2D.java b/src/main/java/ru/windcorp/progressia/common/util/math/FloatFunction2D.java
new file mode 100644
index 0000000..f41ea25
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/util/math/FloatFunction2D.java
@@ -0,0 +1,24 @@
+/*
+ * 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.common.util.math;
+
+public interface FloatFunction2D {
+
+ float apply(float x, float y);
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/util/math/FloatFunction3D.java b/src/main/java/ru/windcorp/progressia/common/util/math/FloatFunction3D.java
new file mode 100644
index 0000000..3e5e7a7
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/util/math/FloatFunction3D.java
@@ -0,0 +1,24 @@
+/*
+ * 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.common.util.math;
+
+public interface FloatFunction3D {
+
+ float apply(float x, float y, float z);
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/util/math/PiecewiseLinearFunction.java b/src/main/java/ru/windcorp/progressia/common/util/math/PiecewiseLinearFunction.java
new file mode 100644
index 0000000..051d7f2
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/util/math/PiecewiseLinearFunction.java
@@ -0,0 +1,124 @@
+/*
+ * 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.common.util.math;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+import glm.vec._2.Vec2;
+
+public class PiecewiseLinearFunction implements FloatFunction {
+
+ public static class Builder {
+
+ private final List points = new ArrayList<>();
+ private float slopeAtNegInf = 0;
+ private float slopeAtPosInf = 0;
+
+ public Builder add(float x, float y) {
+ points.add(new Vec2(x, y));
+ return this;
+ }
+
+ public Builder setNegativeSlope(float slope) {
+ slopeAtNegInf = slope;
+ return this;
+ }
+
+ public Builder setPositiveSlope(float slope) {
+ slopeAtPosInf = slope;
+ return this;
+ }
+
+ public Builder setDefaultUndefined() {
+ slopeAtPosInf = Float.NaN;
+ slopeAtNegInf = Float.NaN;
+ return this;
+ }
+
+ public PiecewiseLinearFunction build() {
+ float[] pointXs = new float[points.size()];
+ float[] pointYs = new float[points.size()];
+
+ points.sort(Comparator.comparingDouble(v -> v.x));
+ for (int i = 0; i < points.size(); ++i) {
+ pointXs[i] = points.get(i).x;
+ pointYs[i] = points.get(i).y;
+ }
+
+ return new PiecewiseLinearFunction(pointXs, pointYs, slopeAtNegInf, slopeAtPosInf);
+ }
+
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * The set of the X coordinates of all defining points, sorted in increasing order
+ */
+ private final float[] pointXs;
+
+ /**
+ * The set of the Y coordinates of all defining points, sorted to match the order of {@link #pointXs}
+ */
+ private final float[] pointYs;
+
+ /**
+ * Slope of the segment (-inf; x[0]), or NaN to exclude the segment from the function
+ */
+ private final float slopeAtNegInf;
+
+ /**
+ * Slope of the segment (x[x.length - 1]; +inf), or NaN to exclude the segment from the function
+ */
+ private final float slopeAtPosInf;
+
+ protected PiecewiseLinearFunction(float[] pointXs, float[] pointYs, float slopeAtNegInf, float slopeAtPosInf) {
+ this.pointXs = pointXs;
+ this.pointYs = pointYs;
+ this.slopeAtNegInf = slopeAtNegInf;
+ this.slopeAtPosInf = slopeAtPosInf;
+ }
+
+ @Override
+ public float apply(float x) {
+ int index = Arrays.binarySearch(pointXs, x);
+
+ if (index >= 0) {
+ // Wow, exact match, me surprised
+ return pointYs[index];
+ }
+
+ int bigger = -index - 1;
+ int smaller = bigger - 1;
+
+ if (smaller == -1) {
+ return pointYs[bigger] + (x - pointXs[bigger]) * slopeAtNegInf;
+ } else if (bigger == pointXs.length) {
+ return pointYs[smaller] + (x - pointXs[smaller]) * slopeAtPosInf;
+ } else {
+ float t = (x - pointXs[smaller]) / (pointXs[bigger] - pointXs[smaller]);
+ return pointYs[smaller] * (1 - t) + pointYs[bigger] * t;
+ }
+ }
+
+}
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/common/world/entity/EntityData.java b/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java
index bc00974..599893e 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java
@@ -379,5 +379,15 @@ public class EntityData extends StatefulObject implements Collideable, EntityGen
super.read(input, context);
}
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj;
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(entityId);
+ }
}
diff --git a/src/main/java/ru/windcorp/progressia/server/PlayerManager.java b/src/main/java/ru/windcorp/progressia/server/PlayerManager.java
index 0236bc8..1672932 100644
--- a/src/main/java/ru/windcorp/progressia/server/PlayerManager.java
+++ b/src/main/java/ru/windcorp/progressia/server/PlayerManager.java
@@ -15,22 +15,22 @@
* 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.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.EntityDataPlayer;
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
import ru.windcorp.progressia.common.world.item.ItemDataRegistry;
import ru.windcorp.progressia.common.world.item.inventory.Items;
+import ru.windcorp.progressia.server.comms.ClientPlayer;
import ru.windcorp.progressia.server.events.PlayerJoinedEvent;
-import ru.windcorp.progressia.test.TestContent;
+import ru.windcorp.progressia.server.events.PlayerLeftEvent;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
public class PlayerManager {
@@ -51,14 +51,23 @@ 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)) {
+ public void removePlayer(Player player) {
+ server.getWorld().getContainer().savePlayer(player, server);
+ this.players.remove(player);
+ getServer().postEvent(new PlayerLeftEvent.Immutable(getServer(), player));
+ }
+
+ 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(login);
- return entity;
- } else {
- throw CrashReports.report(null, "Unknown login %s, javahorse stupid", login);
+ player = new Player(entity, getServer(), clientPlayer);
}
+
+ getServer().getWorld().spawnEntity(player.getEntity());
+
+ return player;
}
private EntityDataPlayer spawnPlayerEntity(String login) {
@@ -70,12 +79,10 @@ public class PlayerManager {
player.setPosition(getServer().getWorld().getGenerator().suggestSpawnLocation());
player.setUpVector(new Vec3(0, 0, 1));
player.setLookingAt(new Vec3(2, 1, 0));
-
- getServer().getWorld().spawnEntity(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 6b1595e..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,22 +79,24 @@ 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() {
- getClients().forEach(CommsChannel::processPackets);
+ clients.forEach(CommsChannel::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/generation/planet/PlanetGenerator.java b/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetGenerator.java
index b2c5c8c..dad0435 100644
--- a/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetGenerator.java
+++ b/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetGenerator.java
@@ -24,7 +24,6 @@ import java.util.List;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
-import ru.windcorp.progressia.common.util.FloatRangeMap;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.DecodingException;
@@ -32,7 +31,7 @@ import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.world.generation.AbstractWorldGenerator;
import ru.windcorp.progressia.server.world.generation.surface.SurfaceFeature;
import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField;
-import ru.windcorp.progressia.server.world.generation.surface.TerrainLayer;
+import ru.windcorp.progressia.server.world.generation.surface.TerrainSupplier;
public class PlanetGenerator extends AbstractWorldGenerator {
@@ -46,7 +45,7 @@ public class PlanetGenerator extends AbstractWorldGenerator {
Server server,
Planet planet,
SurfaceFloatField heightMap,
- FloatRangeMap layers,
+ TerrainSupplier terrain,
List features
) {
super(id, server, Boolean.class, "Test:PlanetGravityModel");
@@ -56,7 +55,7 @@ public class PlanetGenerator extends AbstractWorldGenerator {
PlanetGravityModel model = (PlanetGravityModel) this.getGravityModel();
model.configure(planet.getGravityModelSettings());
- this.terrainGenerator = new PlanetTerrainGenerator(this, heightMap, layers);
+ this.terrainGenerator = new PlanetTerrainGenerator(this, heightMap, terrain);
this.featureGenerator = new PlanetFeatureGenerator(this, features);
}
diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetTerrainGenerator.java b/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetTerrainGenerator.java
index a43ad9f..368a59a 100644
--- a/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetTerrainGenerator.java
+++ b/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetTerrainGenerator.java
@@ -21,7 +21,6 @@ import java.util.Map;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
-import ru.windcorp.progressia.common.util.FloatRangeMap;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.Coordinates;
@@ -33,7 +32,7 @@ import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.world.generation.surface.Surface;
import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField;
import ru.windcorp.progressia.server.world.generation.surface.SurfaceTerrainGenerator;
-import ru.windcorp.progressia.server.world.generation.surface.TerrainLayer;
+import ru.windcorp.progressia.server.world.generation.surface.TerrainSupplier;
class PlanetTerrainGenerator {
@@ -43,7 +42,7 @@ class PlanetTerrainGenerator {
public PlanetTerrainGenerator(
PlanetGenerator generator,
SurfaceFloatField heightMap,
- FloatRangeMap layers
+ TerrainSupplier terrain
) {
this.parent = generator;
@@ -53,7 +52,7 @@ class PlanetTerrainGenerator {
face -> new SurfaceTerrainGenerator(
new Surface(face, seaLevel),
heightMap,
- layers
+ terrain
)
);
}
@@ -86,6 +85,11 @@ class PlanetTerrainGenerator {
}
private void generateBorderTerrain(Server server, DefaultChunkData chunk) {
+ if (chunk.getPosition().x == 0 && chunk.getPosition().y == 0 && chunk.getPosition().z == 0) {
+ generateCore(server, chunk);
+ return;
+ }
+
BlockData stone = BlockDataRegistry.getInstance().get("Test:Stone");
BlockData air = BlockDataRegistry.getInstance().get("Test:Air");
@@ -109,4 +113,16 @@ class PlanetTerrainGenerator {
});
}
+ private void generateCore(Server server, DefaultChunkData chunk) {
+ BlockData tux = BlockDataRegistry.getInstance().get("Test:Tux");
+ BlockData air = BlockDataRegistry.getInstance().get("Test:Air");
+
+
+ GenericChunks.forEachBiC(bic -> {
+ chunk.setBlock(bic, air, false);
+ });
+
+ chunk.setBlock(new Vec3i(7, 7, 0), tux, false);
+ }
+
}
diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceTerrainGenerator.java b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceTerrainGenerator.java
index ea7cb9b..ead1d47 100644
--- a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceTerrainGenerator.java
+++ b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceTerrainGenerator.java
@@ -19,7 +19,6 @@ package ru.windcorp.progressia.server.world.generation.surface;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
-import ru.windcorp.progressia.common.util.FloatRangeMap;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.block.BlockData;
@@ -33,12 +32,12 @@ public class SurfaceTerrainGenerator {
private final Surface surface;
private final SurfaceFloatField heightMap;
- private final FloatRangeMap layers;
+ private final TerrainSupplier terrain;
- public SurfaceTerrainGenerator(Surface surface, SurfaceFloatField heightMap, FloatRangeMap layers) {
+ public SurfaceTerrainGenerator(Surface surface, SurfaceFloatField heightMap, TerrainSupplier terrain) {
this.surface = surface;
this.heightMap = heightMap;
- this.layers = layers;
+ this.terrain = terrain;
}
public void generateTerrain(Server server, DefaultChunkData chunk) {
@@ -74,7 +73,7 @@ public class SurfaceTerrainGenerator {
location.set(north, west, altitude);
SurfaceBlockContext blockContext = context.push(location);
- BlockData block = layers.get(depth).get(blockContext, depth);
+ BlockData block = terrain.get(blockContext, depth);
blockContext.pop();
diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/TerrainLayer.java b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/TerrainSupplier.java
similarity index 96%
rename from src/main/java/ru/windcorp/progressia/server/world/generation/surface/TerrainLayer.java
rename to src/main/java/ru/windcorp/progressia/server/world/generation/surface/TerrainSupplier.java
index 5ec131c..a3a1376 100644
--- a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/TerrainLayer.java
+++ b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/TerrainSupplier.java
@@ -21,7 +21,7 @@ import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext;
@FunctionalInterface
-public interface TerrainLayer {
+public interface TerrainSupplier {
BlockData get(SurfaceBlockContext context, float depth);
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..0b7457a
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/server/world/io/region/RegionWorldContainer.java
@@ -0,0 +1,264 @@
+/*
+ * 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 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("Core:Player");
+ try (
+ DataInputStream dataInputStream = new DataInputStream(
+ new BufferedInputStream(
+ Files.newInputStream(
+ getPlayerPath(login)
+ )
+ )
+ )
+ ) {
+ player.read(dataInputStream, IOContext.SAVE);
+ 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/Flowers.java b/src/main/java/ru/windcorp/progressia/test/Flowers.java
new file mode 100644
index 0000000..9eb45a8
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/test/Flowers.java
@@ -0,0 +1,147 @@
+/*
+ * 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.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import ru.windcorp.progressia.client.graphics.texture.Texture;
+import ru.windcorp.progressia.client.world.tile.TileRenderRegistry;
+import ru.windcorp.progressia.client.world.tile.TileRenderTransparentSurface;
+import ru.windcorp.progressia.common.world.tile.TileData;
+import ru.windcorp.progressia.common.world.tile.TileDataRegistry;
+import ru.windcorp.progressia.server.world.tile.HangingTileLogic;
+import ru.windcorp.progressia.server.world.tile.TileLogicRegistry;
+
+public class Flowers {
+
+ private static final int HERB_MAX_COUNT = 3;
+ private static final int TINY_MAX_COUNT = 8;
+ private static final float TINY_SIZE = 0.5f;
+
+ public enum FlowerVariant {
+
+ FLAT("Flat"),
+ TINY("Tiny"),
+ HERB("Herb");
+
+ private final String name;
+
+ private FlowerVariant(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ }
+
+ public static class Flower {
+
+ private final String name;
+
+ /**
+ * 1 disables spawning, 0 spawns everywhere
+ */
+ private final float rarity;
+
+ private final FlowerVariant[] requestedVariants;
+ private final Map tiles = new HashMap<>();
+
+ public Flower(String name, float rarity, FlowerVariant... variants) {
+ Objects.requireNonNull(name, "name");
+ Objects.requireNonNull(variants, "variants");
+
+ this.name = name;
+ this.rarity = rarity;
+ this.requestedVariants = variants;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public float getRarity() {
+ return rarity;
+ }
+
+ public TileData getTile(FlowerVariant variant) {
+ return tiles.get(variant);
+ }
+
+ public Collection getTiles() {
+ return tiles.values();
+ }
+
+ private void register() {
+ for (FlowerVariant variant : requestedVariants) {
+
+ String fullName = "Flower" + name + variant.getName();
+ String id = "Test:" + fullName;
+
+ TileData tileData = new TileData(id);
+ tiles.put(variant, tileData);
+ TileDataRegistry.getInstance().register(tileData);
+
+ Texture texture = TileRenderRegistry.getTileTexture(fullName);
+
+ TileLogicRegistry logic = TileLogicRegistry.getInstance();
+ TileRenderRegistry render = TileRenderRegistry.getInstance();
+
+ switch (variant) {
+ case HERB:
+ logic.register(new HangingTileLogic(id));
+ render.register(new TileRenderHerb(id, texture, HERB_MAX_COUNT));
+ break;
+ case TINY:
+ logic.register(new HangingTileLogic(id));
+ render.register(new TileRenderTinyFlower(id, texture, TINY_MAX_COUNT, TINY_SIZE));
+ break;
+ case FLAT:
+ default:
+ logic.register(new TestTileLogicGrass(id));
+ render.register(new TileRenderTransparentSurface(id, texture));
+ break;
+ }
+
+ }
+ }
+
+ }
+
+ private final Map flowersByName = Collections.synchronizedMap(new HashMap<>());
+
+ public Flower create(String name, float rarity, FlowerVariant... variants) {
+ Flower flower = new Flower(name, rarity, variants);
+ flowersByName.put(name, flower);
+ return flower;
+ }
+
+ public void registerAllFlowers() {
+ getFlowers().forEach(Flower::register);
+ }
+
+ public Collection getFlowers() {
+ return flowersByName.values();
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/test/LayerAbout.java b/src/main/java/ru/windcorp/progressia/test/LayerAbout.java
index 334b462..fae2357 100644
--- a/src/main/java/ru/windcorp/progressia/test/LayerAbout.java
+++ b/src/main/java/ru/windcorp/progressia/test/LayerAbout.java
@@ -50,7 +50,7 @@ public class LayerAbout extends GUILayer {
new Label(
"Version",
font,
- new MutableStringLocalized("LayerAbout.Version").format("pre-alpha 2")
+ new MutableStringLocalized("LayerAbout.Version").format("pre-alpha 3")
)
);
diff --git a/src/main/java/ru/windcorp/progressia/test/LayerButtonTest.java b/src/main/java/ru/windcorp/progressia/test/LayerButtonTest.java
index e505291..4e6cd77 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,72 @@ 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;
+import ru.windcorp.progressia.test.controls.TestPlayerControls;
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/LayerDebug.java b/src/main/java/ru/windcorp/progressia/test/LayerDebug.java
new file mode 100755
index 0000000..541a50e
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/test/LayerDebug.java
@@ -0,0 +1,261 @@
+/*
+ * 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 glm.vec._3.Vec3;
+import ru.windcorp.progressia.client.Client;
+import ru.windcorp.progressia.client.ClientState;
+import ru.windcorp.progressia.client.graphics.Colors;
+import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
+import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
+import ru.windcorp.progressia.client.graphics.font.Font;
+import ru.windcorp.progressia.client.graphics.gui.DynamicLabel;
+import ru.windcorp.progressia.client.graphics.gui.GUILayer;
+import ru.windcorp.progressia.client.graphics.gui.Label;
+import ru.windcorp.progressia.client.graphics.gui.Group;
+import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
+import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
+import ru.windcorp.progressia.client.localization.Localizer;
+import ru.windcorp.progressia.client.localization.MutableString;
+import ru.windcorp.progressia.client.localization.MutableStringLocalized;
+import ru.windcorp.progressia.client.world.WorldRender;
+import ru.windcorp.progressia.common.Units;
+import ru.windcorp.progressia.common.util.dynstr.DynamicStrings;
+import ru.windcorp.progressia.server.Server;
+import ru.windcorp.progressia.server.ServerState;
+import ru.windcorp.progressia.test.controls.TestPlayerControls;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+public class LayerDebug extends GUILayer {
+
+ private final List updateTriggers = new ArrayList<>();
+
+ public LayerDebug() {
+ super("LayerDebug", new LayoutAlign(0, 1, 5));
+ getRoot().addChild(new Group("Displays", new LayoutVertical(5)));
+
+ TestPlayerControls tpc = TestPlayerControls.getInstance();
+
+ addDynamicDisplay(
+ "FPSDisplay",
+ DynamicStrings.builder()
+ .addDyn(new MutableStringLocalized("LayerDebug.FPSDisplay"))
+ .addDyn(() -> FPS_RECORD.update(GraphicsInterface.getFPS()), 5, 1)
+ .addDyn(() -> GraphicsBackend.isFullscreen() ? " Fullscreen" : "")
+ .addDyn(() -> GraphicsBackend.isVSyncEnabled() ? " VSync" : "")
+ .buildSupplier()
+ );
+
+ addDynamicDisplay("TPSDisplay", LayerDebug::getTPS);
+
+ addDynamicDisplay(
+ "ChunkStatsDisplay",
+ DynamicStrings.builder()
+ .addDyn(new MutableStringLocalized("LayerDebug.ChunkStatsDisplay"))
+ .addDyn(() -> {
+ if (ClientState.getInstance() == null) {
+ return -1;
+ } else {
+ WorldRender world = ClientState.getInstance().getWorld();
+ return world.getChunks().size() - world.getPendingChunkUpdates();
+ }
+ }, 4)
+ .add('/')
+ .addDyn(() -> {
+ if (ClientState.getInstance() == null) {
+ return -1;
+ } else {
+ return ClientState.getInstance().getWorld().getPendingChunkUpdates();
+ }
+ }, 4)
+ .add('/')
+ .addDyn(() -> {
+ if (ServerState.getInstance() == null) {
+ return -1;
+ } else {
+ return ServerState.getInstance().getWorld().getChunks().size();
+ }
+ }, 4)
+ .buildSupplier()
+ );
+
+ addDynamicDisplay("PosDisplay", LayerDebug::getPos);
+
+ addDisplay("SelectedBlockDisplay", () -> tpc.isBlockSelected() ? ">" : " ", () -> tpc.getSelectedBlock().getId());
+ addDisplay("SelectedTileDisplay", () -> tpc.isBlockSelected() ? " " : ">", () -> tpc.getSelectedTile().getId());
+ addDisplay("PlacementModeHint", () -> "\u2B04");
+ }
+
+ private void addDisplay(String name, Supplier>... params) {
+ Font font = new Font().withColor(Colors.WHITE).deriveOutlined();
+ Label component = new Label(name, font, tmp_dynFormat("LayerDebug." + name, params));
+ getRoot().getChild(0).addChild(component);
+
+ for (Supplier> param : params) {
+ if (param == null) {
+ continue;
+ }
+
+ updateTriggers.add(new Runnable() {
+
+ private Object displayedValue;
+
+ @Override
+ public void run() {
+ Object newValue = param.get();
+ if (!Objects.equals(newValue, displayedValue)) {
+ component.update();
+ }
+ displayedValue = newValue;
+ }
+
+ });
+ }
+ }
+
+ private void addDynamicDisplay(String name, Supplier contents) {
+ Font font = new Font().withColor(Colors.WHITE).deriveOutlined();
+ DynamicLabel component = new DynamicLabel(name, font, contents, 128);
+ getRoot().getChild(0).addChild(component);
+ }
+
+ @Override
+ protected void doRender() {
+ updateTriggers.forEach(Runnable::run);
+ super.doRender();
+ }
+
+ private static class Averager {
+
+ private static final int DISPLAY_INERTIA = 32;
+ private static final double UPDATE_INTERVAL = Units.get(50.0, "ms");
+
+ private final double[] values = new double[DISPLAY_INERTIA];
+ private int size;
+ private int head;
+
+ private long lastUpdate;
+
+ public void add(double value) {
+ if (size == values.length) {
+ values[head] = value;
+ head++;
+ if (head == values.length)
+ head = 0;
+ } else {
+ values[size] = value;
+ size++;
+ }
+ }
+
+ public double average() {
+ double product = 1;
+
+ if (size == values.length) {
+ for (double d : values)
+ product *= d;
+ } else {
+ for (int i = 0; i < size; ++i)
+ product *= values[i];
+ }
+
+ return Math.pow(product, 1.0 / size);
+ }
+
+ public double update(double value) {
+ long now = (long) (GraphicsInterface.getTime() / UPDATE_INTERVAL);
+ if (lastUpdate != now) {
+ lastUpdate = now;
+ add(value);
+ }
+
+ return average();
+ }
+
+ }
+
+ private static final String[] CLOCK_CHARS = "\u2591\u2598\u259d\u2580\u2596\u258c\u259e\u259b\u2597\u259a\u2590\u259c\u2584\u2599\u259f\u2588"
+ .chars().mapToObj(c -> ((char) c) + "").toArray(String[]::new);
+
+ private static String getTPSClockChar() {
+ return CLOCK_CHARS[(int) (ServerState.getInstance().getUptimeTicks() % CLOCK_CHARS.length)];
+ }
+
+ private static final Averager FPS_RECORD = new Averager();
+ private static final Averager TPS_RECORD = new Averager();
+
+ private static final Supplier TPS_STRING = DynamicStrings.builder()
+ .addDyn(new MutableStringLocalized("LayerDebug.TPSDisplay"))
+ .addDyn(() -> TPS_RECORD.update(ServerState.getInstance().getTPS()), 5, 1)
+ .add(' ')
+ .addDyn(LayerDebug::getTPSClockChar)
+ .buildSupplier();
+
+ private static final Supplier POS_STRING = DynamicStrings.builder()
+ .addDyn(new MutableStringLocalized("LayerDebug.PosDisplay"))
+ .addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().x, 7, 1)
+ .addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().y, 7, 1)
+ .addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().z, 7, 1)
+ .buildSupplier();
+
+ private static CharSequence getTPS() {
+ Server server = ServerState.getInstance();
+ if (server == null)
+ return Localizer.getInstance().getValue("LayerDebug.TPSDisplay.NA");
+
+ return TPS_STRING.get();
+ }
+
+ private static CharSequence getPos() {
+ Client client = ClientState.getInstance();
+ if (client == null)
+ return Localizer.getInstance().getValue("LayerDebug.PosDisplay.NA.Client");
+
+ Vec3 pos = client.getCamera().getLastAnchorPosition();
+ if (Float.isNaN(pos.x)) {
+ return Localizer.getInstance().getValue("LayerDebug.PosDisplay.NA.Entity");
+ } else {
+ return POS_STRING.get();
+ }
+ }
+
+ private static MutableString tmp_dynFormat(String formatKey, Supplier>... suppliers) {
+ return new MutableStringLocalized(formatKey).apply(s -> {
+ Object[] args = new Object[suppliers.length];
+
+ for (int i = 0; i < suppliers.length; ++i) {
+ Supplier> supplier = suppliers[i];
+
+ Object value = supplier != null ? supplier.get() : "null";
+ if (!(value instanceof Number)) {
+ value = Objects.toString(value);
+ }
+
+ args[i] = value;
+ }
+
+ return String.format(s, args);
+ });
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java b/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java
deleted file mode 100755
index 0c082fa..0000000
--- a/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java
+++ /dev/null
@@ -1,422 +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 glm.vec._3.Vec3;
-import glm.vec._4.Vec4;
-import ru.windcorp.progressia.client.Client;
-import ru.windcorp.progressia.client.ClientState;
-import ru.windcorp.progressia.client.graphics.Colors;
-import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
-import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
-import ru.windcorp.progressia.client.graphics.font.Font;
-import ru.windcorp.progressia.client.graphics.gui.DynamicLabel;
-import ru.windcorp.progressia.client.graphics.gui.GUILayer;
-import ru.windcorp.progressia.client.graphics.gui.Label;
-import ru.windcorp.progressia.client.graphics.gui.Group;
-import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
-import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
-import ru.windcorp.progressia.client.localization.Localizer;
-import ru.windcorp.progressia.client.localization.MutableString;
-import ru.windcorp.progressia.client.localization.MutableStringLocalized;
-import ru.windcorp.progressia.client.world.WorldRender;
-import ru.windcorp.progressia.common.Units;
-import ru.windcorp.progressia.common.util.dynstr.DynamicStrings;
-import ru.windcorp.progressia.server.Server;
-import ru.windcorp.progressia.server.ServerState;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Objects;
-import java.util.function.Supplier;
-
-public class LayerTestGUI extends GUILayer {
-
- public LayerTestGUI() {
- super("LayerTestGui", new LayoutAlign(0, 1, 5));
-
- Group group = new Group("ControlDisplays", new LayoutVertical(5));
-
- Vec4 color = Colors.WHITE;
- Font font = new Font().withColor(color).deriveOutlined();
-
- TestPlayerControls tpc = TestPlayerControls.getInstance();
-
- group.addChild(
- new Label(
- "IsFlyingDisplay",
- font,
- tmp_dynFormat("LayerTestGUI.IsFlyingDisplay", tpc::isFlying)
- )
- );
-
- group.addChild(
- new Label(
- "IsSprintingDisplay",
- font,
- tmp_dynFormat("LayerTestGUI.IsSprintingDisplay", tpc::isSprinting)
- )
- );
-
- group.addChild(
- new Label(
- "CameraModeDisplay",
- font,
- tmp_dynFormat(
- "LayerTestGUI.CameraModeDisplay",
- ClientState.getInstance().getCamera()::getCurrentModeIndex
- )
- )
- );
-
- group.addChild(
- new Label(
- "LanguageDisplay",
- font,
- tmp_dynFormat("LayerTestGUI.LanguageDisplay", Localizer.getInstance()::getLanguage)
- )
- );
-
- group.addChild(
- new Label(
- "FullscreenDisplay",
- font,
- tmp_dynFormat("LayerTestGUI.IsFullscreen", GraphicsBackend::isFullscreen)
- )
- );
-
- group.addChild(
- new Label(
- "VSyncDisplay",
- font,
- tmp_dynFormat("LayerTestGUI.IsVSync", GraphicsBackend::isVSyncEnabled)
- )
- );
-
- group.addChild(
- new DynamicLabel(
- "FPSDisplay",
- font,
- DynamicStrings.builder()
- .addDyn(new MutableStringLocalized("LayerTestGUI.FPSDisplay"))
- .addDyn(() -> FPS_RECORD.update(GraphicsInterface.getFPS()), 5, 1)
- .buildSupplier(),
- 128
- )
- );
-
- group.addChild(
- new DynamicLabel(
- "TPSDisplay",
- font,
- LayerTestGUI::getTPS,
- 128
- )
- );
-
- group.addChild(
- new DynamicLabel(
- "ChunkStatsDisplay",
- font,
- DynamicStrings.builder()
- .addDyn(new MutableStringLocalized("LayerTestGUI.ChunkStatsDisplay"))
- .addDyn(() -> {
- if (ClientState.getInstance() == null) {
- return -1;
- } else {
- WorldRender world = ClientState.getInstance().getWorld();
- return world.getChunks().size() - world.getPendingChunkUpdates();
- }
- }, 4)
- .add('/')
- .addDyn(() -> {
- if (ClientState.getInstance() == null) {
- return -1;
- } else {
- return ClientState.getInstance().getWorld().getPendingChunkUpdates();
- }
- }, 4)
- .add('/')
- .addDyn(() -> {
- if (ServerState.getInstance() == null) {
- return -1;
- } else {
- return ServerState.getInstance().getWorld().getChunks().size();
- }
- }, 4)
- .buildSupplier(),
- 128
- )
- );
-
- group.addChild(
- new DynamicLabel(
- "PosDisplay",
- font,
- LayerTestGUI::getPos,
- 128
- )
- );
-
- group.addChild(
- new Label(
- "SelectedBlockDisplay",
- font,
- tmp_dynFormat(
- "LayerTestGUI.SelectedBlockDisplay",
- () -> tpc.isBlockSelected() ? ">" : " ",
- () -> tpc.getSelectedBlock().getId()
- )
- )
- );
- group.addChild(
- new Label(
- "SelectedTileDisplay",
- font,
- tmp_dynFormat(
- "LayerTestGUI.SelectedTileDisplay",
- () -> tpc.isBlockSelected() ? " " : ">",
- () -> tpc.getSelectedTile().getId()
- )
- )
- );
- group.addChild(
- new Label(
- "PlacementModeHint",
- font,
- new MutableStringLocalized("LayerTestGUI.PlacementModeHint").format("\u2B04")
- )
- );
-
- getRoot().addChild(group);
- }
-
- public Runnable getUpdateCallback() {
- Collection