Merge branch 'master' into add-items

If I ever become a supervillain, I will torture my victims by forcing
them to merge branches in Progressia

Does not work properly, waiting for an additional patch from master
This commit is contained in:
OLEGSHA 2021-12-20 23:36:55 +03:00
commit a028f6f3c7
Signed by: OLEGSHA
GPG Key ID: E57A4B08D64AFF7A
153 changed files with 5403 additions and 2106 deletions

View File

@ -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
}

12
docs/ProgressiaRegion.md Normal file
View File

@ -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.

View File

@ -42,3 +42,16 @@ Run configurations are used by Intellij IDEA to specify how a project must be ru
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.
### 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

View File

@ -18,18 +18,28 @@
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() {

View File

@ -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) {

View File

@ -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();
}
}

View File

@ -19,8 +19,11 @@
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() {

View File

@ -143,20 +143,6 @@ public class ControlTriggers {
);
}
//
//
///
///
//
//
//
//
//
//
//
//
//
public static ControlTriggerInputBased localOf(
String id,
Consumer<InputEvent> action,

View File

@ -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;
}

View File

@ -21,6 +21,7 @@ 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);
}
}

View File

@ -21,16 +21,10 @@ 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<LayerStackModification> 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();
@ -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);
}
};
}
}

View File

@ -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();

View File

@ -69,6 +69,10 @@ public class GraphicsInterface {
InputHandler.register(listener);
}
public static void unsubscribeFromInputEvents(Object listener) {
InputHandler.unregister(listener);
}
public static void startNextLayer() {
GraphicsBackend.startNextLayer();
}

View File

@ -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);
}
}

View File

@ -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);
}
});
}
}

View File

@ -43,15 +43,12 @@ public abstract class BasicButton extends Component {
private boolean isPressed = false;
private final Collection<Consumer<BasicButton>> 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,18 +56,17 @@ 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();
}
});
@ -109,6 +105,10 @@ public abstract class BasicButton extends Component {
});
}
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());
}
@ -125,6 +125,7 @@ public abstract class BasicButton extends Component {
public void setPressed(boolean isPressed) {
if (this.isPressed != isPressed) {
this.isPressed = isPressed;
requestReassembly();
if (isPressed) {
takeFocus();

View File

@ -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());
}

View File

@ -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() {
@ -526,6 +527,10 @@ public class Component extends Named {
}
}
private void updateHoverFlag(CursorMoveEvent e) {
setHovered(contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY()));
}
public void addListener(Object listener) {
if (eventBus == null) {
eventBus = ReportingEventBus.create(getName());
@ -546,144 +551,26 @@ public class Component extends Named {
eventBus.post(event);
}
public <T extends InputEvent> void addListener(
Class<? extends T> type,
boolean handlesConsumed,
InputListener<T> listener
) {
if (inputBus == null) {
inputBus = new InputBus();
}
inputBus.register(type, handlesConsumed, listener);
public <T extends InputEvent> void addInputListener(Class<? extends T> type, InputListener<T> listener, InputBus.Option... options) {
inputBus.register(type, listener, options);
}
public <T extends InputEvent> void addListener(Class<T> type, InputListener<? super T> listener) {
if (inputBus == null) {
inputBus = new InputBus();
}
inputBus.register(type, listener);
public void addKeyListener(KeyMatcher matcher, InputListener<? super KeyEvent> listener, InputBus.Option... options) {
inputBus.register(matcher, listener, options);
}
public <T extends InputEvent> void addListener(Class<T> type, Runnable listener) {
addListener(type, event -> {
listener.run();
return true;
});
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;
});
}
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;
InputBus getInputBus() {
return inputBus;
}
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();

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}
}
}

View File

@ -18,9 +18,16 @@
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<Component> children;
void init(Component c) {
component = c;
children = c.getChildren().iterator();
}
void reset() {
component = null;
children = null;
}
}
/**
* Stack for {@link #handleInput(InputEvent)}.
*/
private StashingStack<EventHandlingFrame> 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<Component> 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

View File

@ -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() {

View File

@ -104,25 +104,22 @@ public class RadioButton extends BasicButton {
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));
@ -149,7 +146,8 @@ public class RadioButton extends BasicButton {
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;
@ -178,7 +176,8 @@ public class RadioButton extends BasicButton {
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()
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -15,48 +15,30 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui.event;
package ru.windcorp.progressia.client.graphics.input.bus;
import glm.vec._2.d.Vec2d;
import ru.windcorp.progressia.client.graphics.gui.Component;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
public class DragStopEvent extends ComponentEvent {
public class Input {
private final Vec2d totalChange = new Vec2d();
public static enum Target {
FOCUSED, HOVERED, ALL
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;
}
}

View File

@ -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) {
if (!input.isConsumed()) {
InputEvent event = input.getEvent();
public void handleInput(InputEvent event) {
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();
}
}

View File

@ -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());
}

View File

@ -18,10 +18,32 @@
package ru.windcorp.progressia.client.graphics.input;
import ru.windcorp.progressia.client.graphics.gui.Component;
/**
* An instance of user input.
* <p>
* 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.
* <p>
* 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.
* <p>
* {@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);
}
}

View File

@ -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<KeyEvent> {
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<String, Integer> 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;
this.action = action;
}
@Override
public boolean test(KeyEvent event) {
if (action != ANY_ACTION && event.getAction() != action)
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;
}
public boolean matches(KeyEvent event) {
if (!event.isPress())
return false;
if (event.getKey() != getKey())
return false;
@ -54,6 +102,15 @@ public class KeyMatcher implements Predicate<KeyEvent> {
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<KeyEvent> {
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<String, KeyMatcher> 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() {
@ -125,16 +139,4 @@ public class KeyMatcher implements Predicate<KeyEvent> {
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);
}
}

View File

@ -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);

View File

@ -20,68 +20,396 @@ 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.
* <p>
* By default, events are filtered by four checks before being delivered to each
* listener:
* <ol>
* <li><em>Consumption check</em>: unless {@link Option#RECEIVE_CONSUMED
* RECEIVE_CONSUMED} is set, events that are consumed will not be
* delivered.</li>
* <li><em>Hover check</em>: 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.</li>
* <li><em>Focus check</em>: 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.</li>
* <li><em>Type check</em>: 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}.</li>
* </ol>
* 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<InputEvent>) 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<InputEvent> castListener = (InputListener<InputEvent>) 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<WrappedListener> 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.
* <p>
* {@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.
* <p>
* 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.
* <p>
* 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 <T extends InputEvent> void register(
Class<? extends T> type,
boolean handlesConsumed,
InputListener<T> listener
InputListener<T> 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 <T extends InputEvent> void register(
Class<? extends T> type,
InputListener<T> 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.
* <p>
* 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.
* <p>
* 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<KeyEvent> 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);
}

View File

@ -23,6 +23,6 @@ import ru.windcorp.progressia.client.graphics.input.InputEvent;
@FunctionalInterface
public interface InputListener<T extends InputEvent> {
boolean handle(T event);
void handle(T event);
}

View File

@ -25,6 +25,7 @@ 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 {
@ -40,26 +41,66 @@ public class ShapeParts {
Vec3 width,
Vec3 height,
boolean flip
) {
return createRectangle(program, texture, colorMultiplier, origin, width, height, flip, null);
}
public static ShapePart createRectangle(
ShapeRenderProgram program,
Texture texture,
Vec4 colorMultiplier,
Vec3 origin,
Vec3 width,
Vec3 height,
boolean flip,
Vec3 forcedNormals
) {
VertexBuilder builder = program.getVertexBuilder();
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)
);
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[] {

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Mat4> 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());
}
}

View File

@ -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 {
@ -302,6 +304,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;
return this;

View File

@ -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() {

View File

@ -92,4 +92,13 @@ public class EntityAnchor implements Anchor {
return modes;
}
public EntityData getEntity() {
return entity;
}
@Override
public String toString() {
return "Anchor for entity " + entity;
}
}

View File

@ -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);
}
}

View File

@ -200,6 +200,10 @@ public class WorldRenderProgram extends ShapeRenderProgram {
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);
vertices.putFloat(offset + 2 * Float.BYTES, normal.z);
@ -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;
}
}
@ -292,6 +302,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() {
ByteBuffer result = BufferUtils.createByteBuffer(
@ -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();

View File

@ -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) {

View File

@ -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

View File

@ -73,6 +73,12 @@ public class Localizer {
return language;
}
public List<String> getLanguages() {
List<String> result = new ArrayList<>(langList.keySet());
result.sort(null);
return result;
}
public synchronized String getValue(String key) {
if (data == null) {
throw new IllegalStateException("Localizer not yet initialized");

View File

@ -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)
)
);
}

View File

@ -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);
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.common.util.math;
@FunctionalInterface
public interface FloatFunction {
float apply(float x);
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.common.util.math;
public interface FloatFunction2D {
float apply(float x, float y);
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.common.util.math;
public interface FloatFunction3D {
float apply(float x, float y, float z);
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Vec2> 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;
}
}
}

View File

@ -163,4 +163,71 @@ public class Coordinates {
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;
}
}

View File

@ -380,4 +380,14 @@ 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);
}
}

View File

@ -18,19 +18,19 @@
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) {
@ -71,8 +80,6 @@ public class PlayerManager {
player.setUpVector(new Vec3(0, 0, 1));
player.setLookingAt(new Vec3(2, 1, 0));
getServer().getWorld().spawnEntity(player);
return player;
}

View File

@ -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<Server, WorldGenerator> generatorCreator) {
public Server(
DefaultWorldData world,
Function<Server, WorldGenerator> generatorCreator,
WorldContainer worldContainer
) {
this.world = new DefaultWorldLogic(
world,
this,
generatorCreator.apply(this),
worldContainer,
worldAccessor
);
this.serverThread = new ServerThread(this);
@ -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) {

View File

@ -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<Server, WorldGenerator> 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() {

View File

@ -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> 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() {

View File

@ -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);
}
/**

View File

@ -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
@ -56,6 +56,10 @@ public class ChunkManager {
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;
}

View File

@ -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,18 +44,21 @@ public class DefaultWorldLogic implements WorldLogic {
private final Server server;
private final WorldGenerator generator;
private final WorldContainer container;
private final Map<DefaultChunkData, DefaultChunkLogic> 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
public void onChunkLoaded(DefaultWorldData world, DefaultChunkData chunk) {
@ -128,6 +132,10 @@ public class DefaultWorldLogic implements WorldLogic {
return generator;
}
public WorldContainer getContainer() {
return container;
}
public DefaultChunkData generate(Vec3i chunkPos) {
DefaultChunkData chunk = getGenerator().generate(chunkPos);

View File

@ -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<Boolean> {
@ -46,7 +45,7 @@ public class PlanetGenerator extends AbstractWorldGenerator<Boolean> {
Server server,
Planet planet,
SurfaceFloatField heightMap,
FloatRangeMap<TerrainLayer> layers,
TerrainSupplier terrain,
List<SurfaceFeature> features
) {
super(id, server, Boolean.class, "Test:PlanetGravityModel");
@ -56,7 +55,7 @@ public class PlanetGenerator extends AbstractWorldGenerator<Boolean> {
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);
}

View File

@ -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<TerrainLayer> 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);
}
}

View File

@ -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<TerrainLayer> layers;
private final TerrainSupplier terrain;
public SurfaceTerrainGenerator(Surface surface, SurfaceFloatField heightMap, FloatRangeMap<TerrainLayer> 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();

View File

@ -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);

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Integer> 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;
}
}

View File

@ -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<Integer> offsets, Vec3i regionCoords) throws IOException {
Set<Integer> used = new HashSet<Integer>();
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();
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Region> 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<AtomicBoolean> isCloseds = ChunkMaps.newHashMap();
ChunkMap<AtomicBoolean> 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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<FlowerVariant, TileData> 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<TileData> 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<String, Flower> 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<Flower> getFlowers() {
return flowersByName.values();
}
}

View File

@ -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")
)
);

View File

@ -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,9 +29,16 @@ 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");
@ -60,8 +71,28 @@ public class LayerButtonTest extends MenuLayer {
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<Player> 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();

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Runnable> 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<CharSequence> 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<CharSequence> 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<CharSequence> 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);
});
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Label> labels = new ArrayList<>();
getRoot().getChild(0).getChildren().forEach(c -> {
if (c instanceof Label) {
labels.add((Label) c);
}
});
return () -> labels.forEach(Label::update);
}
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<CharSequence> TPS_STRING = DynamicStrings.builder()
.addDyn(new MutableStringLocalized("LayerTestGUI.TPSDisplay"))
.addDyn(() -> TPS_RECORD.update(ServerState.getInstance().getTPS()), 5, 1)
.add(' ')
.addDyn(LayerTestGUI::getTPSClockChar)
.buildSupplier();
private static final Supplier<CharSequence> POS_STRING = DynamicStrings.builder()
.addDyn(new MutableStringLocalized("LayerTestGUI.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("LayerTestGUI.TPSDisplay.NA");
return TPS_STRING.get();
}
private static CharSequence getPos() {
Client client = ClientState.getInstance();
if (client == null)
return Localizer.getInstance().getValue("LayerTestGUI.PosDisplay.NA.Client");
Vec3 pos = client.getCamera().getLastAnchorPosition();
if (Float.isNaN(pos.x)) {
return Localizer.getInstance().getValue("LayerTestGUI.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);
});
}
// private static class DebugComponent extends Component {
// private final int color;
//
// public DebugComponent(String name, Vec2i size, int color) {
// super(name);
// this.color = color;
//
// setPreferredSize(size);
//
// addListener(new Object() {
// @Subscribe
// public void onHoverChanged(HoverEvent e) {
// requestReassembly();
// }
// });
//
// addListener(KeyEvent.class, this::onClicked);
// }
//
// private boolean onClicked(KeyEvent event) {
// if (!event.isMouse()) {
// return false;
// } else if (event.isPress() && event.isLeftMouseButton()) {
// System.out.println("You pressed a Component!");
// }
// return true;
// }
//
// @Override
// protected void assembleSelf(RenderTarget target) {
// target.fill(getX(), getY(), getWidth(), getHeight(), Colors.BLACK);
//
// target.fill(
// getX() + 2, getY() + 2,
// getWidth() - 4, getHeight() - 4,
// isHovered() ? Colors.DEBUG_YELLOW : color
// );
// }
// }
//
// public LayerTestGUI() {
// super("LayerTestGui", new LayoutAlign(1, 0.75, 5));
//
// Panel panel = new Panel("Alex", new LayoutVertical(5));
//
// panel.addChild(new DebugComponent("Bravo", new Vec2i(200, 100), 0x44FF44));
//
// Component charlie = new DebugComponent("Charlie", null, 0x222222);
// charlie.setLayout(new LayoutVertical(5));
//
// //Debug
// Localizer.getInstance().setLanguage("ru-RU");
// MutableString epsilon = new MutableStringLocalized("Epsilon")
// .addListener(() -> ((Label)charlie.getChild(0)).update()).format(34, "thirty-four");
// // These two are swapped in code due to a bug in layouts, fixing ATM
// charlie.addChild(
// new Label(
// "Delta",
// new Font().withColor(0xCCBB44).deriveShadow().deriveBold(),
// "Пре-альфа!"
// )
// );
// charlie.addChild(
// new Label(
// "Epsilon",
// new Font().withColor(0x4444BB).deriveItalic(),
// () -> epsilon.get().concat("\u269b")
// )
// );
// panel.addChild(charlie);
//
//
// charlie.addListener(KeyEvent.class, e -> {
// if(e.isPress() && e.getKey() == GLFW.GLFW_KEY_L) {
// Localizer localizer = Localizer.getInstance();
// if (localizer.getLanguage().equals("ru-RU")) {
// localizer.setLanguage("en-US");
// } else {
// localizer.setLanguage("ru-RU");
// }
// return true;
// } return false;
// });
// charlie.setFocusable(true);
// charlie.takeFocus();
//
// getRoot().addChild(panel);
// }
}

View File

@ -0,0 +1,29 @@
package ru.windcorp.progressia.test;
import java.util.function.Consumer;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.localization.MutableString;
public class LayerTestText extends GUILayer {
private final Consumer<LayerTestText> remover;
public LayerTestText(String name, MutableString value, Consumer<LayerTestText> remover) {
super(name, new LayoutAlign(15));
this.remover = remover;
Font titleFont = new Font().deriveBold().withColor(Colors.BLACK).withAlign(0.5f);
getRoot().addChild(new Label(name + ".Text", titleFont, value));
}
@Override
protected void doRender() {
super.doRender();
remover.accept(this);
}
}

View File

@ -27,8 +27,8 @@ import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
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;
public class LayerTestUI extends AssembledFlatLayer {
@ -91,7 +91,7 @@ public class LayerTestUI extends AssembledFlatLayer {
}
@Override
protected void handleInput(Input input) {
public void handleInput(InputEvent event) {
// Do nothing
}

View File

@ -0,0 +1,95 @@
package ru.windcorp.progressia.test;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.BasicButton;
import ru.windcorp.progressia.client.graphics.gui.Button;
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
import ru.windcorp.progressia.client.graphics.gui.Group;
import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
import ru.windcorp.progressia.client.localization.MutableString;
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.server.ServerState;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
public class LayerTitle extends GUILayer {
private final BasicButton resetButton;
public LayerTitle(String name) {
super(name, new LayoutAlign(0.5f, 0.7f, 15));
Group content = new Group("Layer" + name + ".Group", new LayoutVertical(15));
MutableString title = new MutableStringLocalized("Layer" + name + ".Title");
Font titleFont = new Font().deriveBold().withColor(Colors.BLACK).withAlign(0.5f);
content.addChild(new Label(name + ".Title", titleFont, title));
Font buttonFont = titleFont.deriveNotBold();
MutableString playText = new MutableStringLocalized("Layer" + name + ".Play");
content.addChild(new Button(name + ".Play", new Label(name + ".Play", buttonFont, playText)).addAction(this::startGame));
MutableString resetText = new MutableStringLocalized("Layer" + name + ".Reset");
this.resetButton = new Button(name + ".Reset", new Label(name + ".Reset", buttonFont, resetText)).addAction(this::resetWorld);
content.addChild(resetButton);
updateResetButton();
MutableString quitText = new MutableStringLocalized("Layer" + name + ".Quit");
content.addChild(new Button(name + "Quit", new Label(name + ".Quit", buttonFont, quitText)).addAction(b -> {
System.exit(0);
}));
getRoot().addChild(content);
}
private void updateResetButton() {
resetButton.setEnabled(Files.exists(Paths.get("tmp_world")));
}
private void startGame(BasicButton basicButton) {
GUI.removeLayer(this);
try {
ServerState.startServer();
ClientState.connectToLocalServer();
} catch (IOException e) {
throw CrashReports.report(e, "Problem with loading server");
}
}
private void resetWorld(BasicButton basicButton) {
Path rootPath = Paths.get("tmp_world");
try {
Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
throw CrashReports.report(e, "Could not reset world");
}
updateResetButton();
}
}

View File

@ -0,0 +1,100 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Shapes;
import ru.windcorp.progressia.client.graphics.model.StaticModel;
import ru.windcorp.progressia.client.graphics.texture.ComplexTexture;
import ru.windcorp.progressia.client.graphics.texture.SimpleTextures;
import ru.windcorp.progressia.client.graphics.texture.TexturePrimitive;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.common.world.DefaultChunkData;
public class TestBlockRenderTux extends BlockRender {
private final Renderable model;
public TestBlockRenderTux(String id) {
super(id);
TexturePrimitive primitive = SimpleTextures.get("blocks/Tux").getSprite().getPrimitive();
ComplexTexture texture = new ComplexTexture(primitive, 72, 60);
WorldRenderProgram program = WorldRenderProgram.getDefault();
StaticModel.Builder builder = StaticModel.builder();
final float scale = 1f / 40;
final float lift = -1f / 2 / scale;
builder.addPart(
new Shapes.PppBuilder(program, texture.getCuboidTextures(0, 36, 12)).setSize(12)
.centerAt(0, 0, 18 + (12 / 2) + lift).scale(scale).create()
);
builder.addPart(
new Shapes.PppBuilder(program, texture.getCuboidTextures(0, 0, 18)).setSize(18)
.centerAt(0, 0, 18 / 2 + lift).scale(scale).create()
);
builder.addPart(
new Shape(
Usage.STATIC,
program,
ShapeParts.createRectangle(
program,
texture.get(48, 44, 24, 16),
Colors.WHITE,
new Vec3(18 / 2 + 1, -(24 / 2), lift),
new Vec3(0, 24, 0),
new Vec3(0, 0, 16),
false
),
ShapeParts.createRectangle(
program,
texture.get(48, 44, 24, 16),
Colors.WHITE,
new Vec3(18 / 2 + 1, -(24 / 2), lift),
new Vec3(0, 24, 0),
new Vec3(0, 0, 16),
true
)
),
new Mat4().scale(scale)
);
this.model = builder.build();
}
@Override
public Renderable createRenderable(DefaultChunkData chunk, Vec3i relBlockInChunk) {
return model;
}
@Override
public boolean needsOwnRenderable() {
return true;
}
}

View File

@ -29,13 +29,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.audio.Sound;
import ru.windcorp.progressia.client.comms.controls.*;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
import ru.windcorp.progressia.client.graphics.world.Selection;
import ru.windcorp.progressia.client.graphics.world.hud.HUDWorkspace;
import ru.windcorp.progressia.client.world.block.*;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerRegistry;
@ -52,7 +45,6 @@ import ru.windcorp.progressia.client.world.item.inventory.InventoryRenderRegistr
import ru.windcorp.progressia.client.world.tile.*;
import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.comms.controls.*;
import ru.windcorp.progressia.common.state.StatefulObjectRegistry.IdFactory;
import ru.windcorp.progressia.common.world.GravityModelRegistry;
import ru.windcorp.progressia.common.world.block.*;
@ -63,19 +55,17 @@ import ru.windcorp.progressia.common.world.item.ItemDataContainer;
import ru.windcorp.progressia.common.world.item.ItemDataRegistry;
import ru.windcorp.progressia.common.world.item.ItemDataSimple;
import ru.windcorp.progressia.common.world.item.inventory.Inventory;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.tile.*;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.comms.controls.*;
import ru.windcorp.progressia.server.world.block.*;
import ru.windcorp.progressia.server.world.context.ServerBlockContext;
import ru.windcorp.progressia.server.world.context.ServerTileContext;
import ru.windcorp.progressia.server.world.context.ServerTileStackContext;
import ru.windcorp.progressia.server.world.entity.*;
import ru.windcorp.progressia.server.world.generation.planet.PlanetGravityModel;
import ru.windcorp.progressia.server.world.tile.*;
import ru.windcorp.progressia.test.Flowers.FlowerVariant;
import ru.windcorp.progressia.test.Rocks.RockType;
import ru.windcorp.progressia.test.controls.TestPlayerControls;
import ru.windcorp.progressia.test.gen.TestGravityModel;
import ru.windcorp.progressia.test.trees.BlockRenderLeavesHazel;
import ru.windcorp.progressia.test.trees.BlockRenderLeavesPine;
public class TestContent {
@ -85,6 +75,7 @@ public class TestContent {
public static final List<TileData> PLACEABLE_TILES = new ArrayList<>();
public static final Rocks ROCKS = new Rocks();
public static final Flowers FLOWERS = new Flowers();
public static void registerContent() {
registerWorldContent();
@ -115,9 +106,10 @@ public class TestContent {
registerSimplestBlock("Dirt");
registerSimplestBlock("Chernozem");
registerSimplestBlock("Stone");
registerSimplestBlock("Mantle");
registerSimplestBlock("Water");
registerSimplestBlock("Brick");
registerSimplestBlock("BrickWhite");
registerSimplestBlock("Sand");
registerSimplestBlock("Concrete");
registerSimplestBlock("WoodenPlank");
@ -139,7 +131,7 @@ public class TestContent {
register(new BlockLogic("Test:Log"));
register(new BlockData("Test:TemporaryLeaves"));
register(new BlockRenderTransparentCube("Test:TemporaryLeaves", getBlockTexture("TemporaryLeaves")));
register(new BlockRenderLeavesHazel("Test:TemporaryLeaves", getBlockTexture("LeavesHazel")));
// Sic, using Glass logic for leaves because Test
register(new TestBlockLogicGlass("Test:TemporaryLeaves"));
@ -147,6 +139,15 @@ public class TestContent {
register(new BlockRenderOpaqueCube("Test:StatieSpawner", getBlockTexture("StatieSpawner")));
register(new TestBlockLogicStatieSpawner("Test:StatieSpawner"));
register(new BlockData("Test:Tux"));
register(new TestBlockRenderTux("Test:Tux"));
register(new BlockLogic("Test:Tux"));
register(new BlockData("Test:LeavesPine"));
register(new BlockRenderLeavesPine("Test:LeavesPine", getBlockTexture("LeavesPine")));
// Sic, using Glass logic for leaves because Test
register(new TestBlockLogicGlass("Test:LeavesPine"));
BlockDataRegistry.getInstance().values().forEach(PLACEABLE_BLOCKS::add);
PLACEABLE_BLOCKS.removeIf(b -> placeableBlacklist.contains(b.getId()));
PLACEABLE_BLOCKS.sort(Comparator.comparing(BlockData::getId));
@ -190,17 +191,9 @@ public class TestContent {
register(new TestTileLogicGrass(id));
});
Arrays.asList(
"Yellow",
"White",
"Purple",
"Blue"
).forEach(color -> {
registerSimplestTransparentTile(color + "Flowers");
});
registerFlowers();
registerSimplestTransparentTile("Stones");
registerSimplestTransparentTile("Sand");
registerSimplestOpaqueTile("SnowOpaque");
Arrays.asList(
@ -223,21 +216,12 @@ public class TestContent {
registerSimplestOpaqueTile("TilesLarge");
registerSimplestOpaqueTile("TilesSmall");
registerHerb("LowGrass", 6);
registerHerb("MediumGrass", 6);
registerHerb("TallGrass", 6);
Arrays.asList(
"Dandelion",
"Lavander"
).forEach(variant -> {
String fullName = "Tiny" + variant + "Flowers";
String id = "Test:" + fullName;
register(new TileData(id));
register(new TileRenderTinyFlower(id, getTileTexture(fullName), 8, 0.5f));
register(new HangingTileLogic(id));
});
registerHerb("GrassMeadow0", 6);
registerHerb("GrassMeadow1", 6);
registerHerb("GrassMeadow2", 6);
registerHerb("GrassBluegrass0", 6);
registerHerb("GrassBluegrass1", 6);
registerHerb("GrassBluegrass2", 6);
registerHerb("Bush", 1);
registerHerb("Fern", 3);
@ -247,6 +231,21 @@ public class TestContent {
PLACEABLE_TILES.sort(Comparator.comparing(TileData::getId));
}
private static void registerFlowers() {
final float common = 0.8f;
FLOWERS.create("Clover", common, FlowerVariant.HERB, FlowerVariant.TINY, FlowerVariant.FLAT);
FLOWERS.create("Dandelion", common, FlowerVariant.TINY, FlowerVariant.FLAT);
FLOWERS.create("Geranium", common, FlowerVariant.TINY, FlowerVariant.FLAT);
FLOWERS.create("Knapweed", common, FlowerVariant.TINY, FlowerVariant.FLAT);
FLOWERS.create("YellowPea", common, FlowerVariant.TINY, FlowerVariant.FLAT);
FLOWERS.create("Daisy", common, FlowerVariant.TINY, FlowerVariant.FLAT);
FLOWERS.create("Lavander", common, FlowerVariant.TINY, FlowerVariant.FLAT);
FLOWERS.registerAllFlowers();
}
private static void registerItems() {
registerSimplestItem("MoonTypeIceCream", Units.get("200 g"), Units.get("1 L"));
registerSimplestItem("Stick", Units.get("260 g"), Units.get("0.5 L"));
@ -329,73 +328,7 @@ public class TestContent {
}
private static void regsiterControls() {
ControlDataRegistry data = ControlDataRegistry.getInstance();
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
ControlLogicRegistry logic = ControlLogicRegistry.getInstance();
data.register("Test:BreakBlock", ControlBreakBlockData::new);
triggers.register(
ControlTriggers.of(
"Test:BreakBlock",
KeyEvent.class,
TestContent::onBlockBreakTrigger,
KeyMatcher.ofLeftMouseButton(),
i -> isAnythingSelected()
)
);
logic.register(ControlLogic.of("Test:BreakBlock", TestContent::onBlockBreakReceived));
data.register("Test:PlaceBlock", ControlPlaceBlockData::new);
triggers.register(
ControlTriggers.of(
"Test:PlaceBlock",
KeyEvent.class,
TestContent::onBlockPlaceTrigger,
KeyMatcher.ofRightMouseButton(),
i -> isAnythingSelected() && TestPlayerControls.getInstance().isBlockSelected()
)
);
logic.register(ControlLogic.of("Test:PlaceBlock", TestContent::onBlockPlaceReceived));
data.register("Test:PlaceTile", ControlPlaceTileData::new);
triggers.register(
ControlTriggers.of(
"Test:PlaceTile",
KeyEvent.class,
TestContent::onTilePlaceTrigger,
KeyMatcher.ofRightMouseButton(),
i -> isAnythingSelected() && !TestPlayerControls.getInstance().isBlockSelected()
)
);
logic.register(ControlLogic.of("Test:PlaceTile", TestContent::onTilePlaceReceived));
triggers.register(
ControlTriggers.localOf(
"Test:StartNextMusic",
KeyEvent.class,
TestMusicPlayer::startNextNow,
KeyMatcher.of("M")
)
);
triggers.register(
ControlTriggers.localOf(
"Test:ShowInventory",
KeyEvent.class,
TestContent::switchInventory,
KeyMatcher.of("E")
)
);
triggers.register(
ControlTriggers.localOf(
"Test:HideHUD",
KeyEvent.class,
TestContent::switchHUD,
KeyMatcher.of("F1")
)
);
TestPlayerControls.getInstance().registerControls();
}
private static void register(BlockData x) {
@ -445,111 +378,6 @@ public class TestContent {
EntityLogicRegistry.getInstance().register(x);
}
private static Selection getSelection() {
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
if (client == null || !client.isReady())
return null;
return client.getLocalPlayer().getSelection();
}
private static boolean isAnythingSelected() {
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
if (client == null || !client.isReady())
return false;
return client.getLocalPlayer().getSelection().exists();
}
private static void onBlockBreakTrigger(ControlData control) {
((ControlBreakBlockData) control).setBlockInWorld(getSelection().getBlock());
Sound sfx = new Sound("Progressia:BlockDestroy");
sfx.setPosition(getSelection().getPoint());
sfx.setPitch((float) (Math.random() + 1 * 0.5));
sfx.play(false);
}
private static void onBlockBreakReceived(
Server server,
PacketControl packet,
ru.windcorp.progressia.server.comms.Client client
) {
Vec3i blockInWorld = ((ControlBreakBlockData) packet.getControl()).getBlockInWorld();
server.createAbsoluteContext().setBlock(blockInWorld, BlockDataRegistry.getInstance().get("Test:Air"));
}
private static void onBlockPlaceTrigger(ControlData control) {
((ControlPlaceBlockData) control).set(
TestPlayerControls.getInstance().getSelectedBlock(),
getSelection().getBlock().add_(getSelection().getSurface().getVector())
);
}
private static void onBlockPlaceReceived(
Server server,
PacketControl packet,
ru.windcorp.progressia.server.comms.Client client
) {
ControlPlaceBlockData controlData = ((ControlPlaceBlockData) packet.getControl());
BlockData block = controlData.getBlock();
Vec3i blockInWorld = controlData.getBlockInWorld();
if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null)
return;
server.createAbsoluteContext().setBlock(blockInWorld, block);
}
private static void onTilePlaceTrigger(ControlData control) {
((ControlPlaceTileData) control).set(
TestPlayerControls.getInstance().getSelectedTile(),
getSelection().getBlock(),
getSelection().getSurface()
);
}
private static void onTilePlaceReceived(
Server server,
PacketControl packet,
ru.windcorp.progressia.server.comms.Client client
) {
ControlPlaceTileData controlData = ((ControlPlaceTileData) packet.getControl());
TileData tile = controlData.getTile();
Vec3i blockInWorld = controlData.getBlockInWorld();
AbsFace face = controlData.getFace();
if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null) {
return;
}
if (server.getWorld().getData().getTiles(blockInWorld, face).isFull()) {
return;
}
ServerBlockContext context = server.createContext(blockInWorld);
ServerTileStackContext tsContext = context.push(context.toContext(face));
ServerTileContext tileContext = tsContext.push(tsContext.getTileCount());
TileLogic logic = TileLogicRegistry.getInstance().get(tile.getId());
if (!logic.canOccupyFace(tileContext)) {
return;
}
tileContext.addTile(tile);
}
private static void switchInventory() {
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
if (client == null || !client.isReady())
return;
client.getHUD().setInventoryShown(!client.getHUD().isInventoryShown());
}
private static void switchHUD() {
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
if (client == null || !client.isReady())
return;
client.getHUD().setHidden(!client.getHUD().isHidden());
}
private static void registerMisc() {
ChunkIO.registerCodec(new TestChunkCodec());

View File

@ -110,6 +110,7 @@ public class TestMusicPlayer implements Runnable {
String file = it.next().toString();
if (!file.endsWith(".ogg") && !file.endsWith(".oga")) {
LogManager.getLogger().warn("Skipping " + file + ": not .ogg nor .oga");
continue;
}
String id = "Progressia:Music" + (i++);

View File

@ -1,533 +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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test;
import glm.Glm;
import glm.mat._3.Mat3;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import org.lwjgl.glfw.GLFW;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
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.WheelScrollEvent;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
import ru.windcorp.progressia.client.localization.Localizer;
import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.util.Matrices;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.entity.EntityDataPlayer;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.server.ServerState;
public class TestPlayerControls {
private static final TestPlayerControls INSTANCE = new TestPlayerControls();
public static TestPlayerControls getInstance() {
return INSTANCE;
}
private static final double MODE_SWITCH_MAX_DELAY = 300 * Units.MILLISECONDS;
private static final double MODE_SPRINT_SWITCH_MAX_DELAY = 100 * Units.MILLISECONDS;
private static final double MIN_JUMP_DELAY = 300 * Units.MILLISECONDS;
// Horizontal and vertical max control speed when flying
private static final float FLYING_SPEED = Units.get("6 m/s");
// (0; 1], 1 is instant change, 0 is no control authority
private static final float FLYING_CONTROL_AUTHORITY = Units.get("2 1/s");
// Horizontal max control speed when walking
private static final float WALKING_SPEED = Units.get("4 m/s");
// Horizontal max control speed when sprinting
private static final float SPRINTING_SPEED = Units.get("6 m/s");
// (0; 1], 1 is instant change, 0 is no control authority
private static final float WALKING_CONTROL_AUTHORITY = Units.get("15 1/s");
// Vertical velocity instantly added to player when they jump
private static final float JUMP_VELOCITY = 5f * Units.METERS_PER_SECOND;
private boolean isFlying = true;
private boolean isSprinting = false;
private int movementForward = 0;
private int movementRight = 0;
private int movementUp = 0;
private double lastSpacePress = Double.NEGATIVE_INFINITY;
private double lastSprintPress = Double.NEGATIVE_INFINITY;
private double lastCtrlPress = Double.NEGATIVE_INFINITY;
private int selectedBlock = 0;
private int selectedTile = 0;
private boolean isBlockSelected = true;
private LayerTestGUI debugLayer = null;
private Runnable updateCallback = null;
public void applyPlayerControls() {
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return;
}
EntityData player = getEntity();
final float speed, authority;
if (isFlying) {
speed = FLYING_SPEED;
authority = FLYING_CONTROL_AUTHORITY;
} else {
speed = isSprinting ? SPRINTING_SPEED : WALKING_SPEED;
authority = WALKING_CONTROL_AUTHORITY;
}
Mat3 movementTransform = getMovementTransform(player, null);
Vec3 desiredVelocity = new Vec3(movementForward, movementRight, 0);
if (movementForward != 0 && movementRight != 0) {
desiredVelocity.normalize();
}
desiredVelocity.z = movementUp;
movementTransform.mul_(desiredVelocity); // bug in jglm, .mul() and
// .mul_() are
// swapped
desiredVelocity.mul(speed);
Vec3 newVelocity = new Vec3()
.set(desiredVelocity)
.sub(player.getVelocity())
.mul((float) Math.exp(-authority * GraphicsInterface.getFrameLength()))
.negate()
.add(desiredVelocity);
if (!isFlying) {
Vec3 up = player.getUpVector();
Vec3 wantedVertical = VectorUtil.projectOnVector(player.getVelocity(), up, null);
VectorUtil.projectOnSurface(newVelocity, up).add(wantedVertical);
}
player.getVelocity().set(newVelocity);
// THIS IS TERRIBLE TEST
EntityData serverEntity = ServerState.getInstance().getWorld().getEntities().stream()
.filter(EntityDataPlayer.class::isInstance).findAny().orElse(null);
if (serverEntity != null) {
serverEntity.setPosition(player.getPosition());
}
}
private Mat3 getMovementTransform(EntityData player, Mat3 mat) {
if (mat == null) {
mat = new Mat3();
}
Vec3 f = player.getForwardVector(null);
Vec3 u = player.getUpVector();
Vec3 s = u.cross_(f);
return mat.set(
+f.x,
+f.y,
+f.z,
-s.x,
-s.y,
-s.z,
+u.x,
+u.y,
+u.z
);
}
public void handleInput(Input input) {
InputEvent event = input.getEvent();
if (event instanceof KeyEvent) {
if (onKeyEvent((KeyEvent) event)) {
input.consume();
}
} else if (event instanceof CursorMoveEvent) {
onMouseMoved((CursorMoveEvent) event);
input.consume();
} else if (event instanceof WheelScrollEvent) {
onWheelScroll((WheelScrollEvent) event);
input.consume();
}
}
private boolean onKeyEvent(KeyEvent event) {
if (event.isRepeat())
return false;
int multiplier = event.isPress() ? 1 : -1;
switch (event.getKey()) {
case GLFW.GLFW_KEY_W:
movementForward += +1 * multiplier;
handleSprint(event);
break;
case GLFW.GLFW_KEY_S:
movementForward += -1 * multiplier;
break;
case GLFW.GLFW_KEY_A:
movementRight += -1 * multiplier;
break;
case GLFW.GLFW_KEY_D:
movementRight += +1 * multiplier;
break;
case GLFW.GLFW_KEY_SPACE:
handleSpace(multiplier);
break;
case GLFW.GLFW_KEY_LEFT_SHIFT:
handleShift(multiplier);
break;
case GLFW.GLFW_KEY_ESCAPE:
if (!event.isPress())
return false;
handleEscape();
break;
case GLFW.GLFW_KEY_F11:
if (!event.isPress())
return false;
GraphicsInterface.makeFullscreen(!GraphicsBackend.isFullscreen());
updateGUI();
break;
case GLFW.GLFW_KEY_F12:
if (!event.isPress())
return false;
GraphicsBackend.setVSyncEnabled(!GraphicsBackend.isVSyncEnabled());
updateGUI();
break;
case GLFW.GLFW_KEY_F3:
if (!event.isPress())
return false;
handleDebugLayerSwitch();
break;
case GLFW.GLFW_KEY_F5:
if (!event.isPress())
return false;
handleCameraMode();
break;
case GLFW.GLFW_KEY_L:
if (!event.isPress())
return false;
handleLanguageSwitch();
break;
case GLFW.GLFW_MOUSE_BUTTON_3:
if (!event.isPress())
return false;
switchPlacingMode();
break;
case GLFW.GLFW_KEY_LEFT_CONTROL:
case GLFW.GLFW_KEY_RIGHT_CONTROL:
if (event.isRepeat())
return false;
handleCtrl(event);
break;
default:
return false;
}
return true;
}
private void handleSpace(int multiplier) {
boolean isPressed = multiplier > 0;
double timeSinceLastSpacePress = GraphicsInterface.getTime() - lastSpacePress;
if (isPressed && timeSinceLastSpacePress < MODE_SWITCH_MAX_DELAY) {
isSprinting = false;
isFlying = !isFlying;
updateGUI();
movementUp = +1;
} else {
if (isFlying) {
movementUp += +1 * multiplier;
} else {
if (isPressed && timeSinceLastSpacePress > MIN_JUMP_DELAY) {
jump();
}
}
}
lastSpacePress = GraphicsInterface.getTime();
}
private void handleSprint(KeyEvent event) {
double timeSinceLastSpacePress = GraphicsInterface.getTime() - lastSprintPress;
if (event.isPress() && timeSinceLastSpacePress < MODE_SPRINT_SWITCH_MAX_DELAY && !isFlying) {
isSprinting = !isSprinting;
updateGUI();
}
lastSprintPress = GraphicsInterface.getTime();
if (isSprinting && event.isRelease()) {
isSprinting = false;
updateGUI();
}
}
private void jump() {
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return;
}
Vec3 up = getEntity().getUpVector();
getEntity().getVelocity().add(
up.x * JUMP_VELOCITY,
up.y * JUMP_VELOCITY,
up.z * JUMP_VELOCITY
);
}
private void handleShift(int multiplier) {
if (isFlying) {
movementUp += -1 * multiplier;
}
}
private void handleEscape() {
movementForward = 0;
movementRight = 0;
movementUp = 0;
GUI.addTopLayer(new LayerButtonTest());
}
private void handleDebugLayerSwitch() {
if (debugLayer == null) {
this.debugLayer = new LayerTestGUI();
this.updateCallback = debugLayer.getUpdateCallback();
}
if (GUI.getLayers().contains(debugLayer)) {
GUI.removeLayer(debugLayer);
} else {
GUI.addTopLayer(debugLayer);
}
}
private void handleCameraMode() {
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return;
}
if (ClientState.getInstance().getCamera().hasAnchor()) {
ClientState.getInstance().getCamera().selectNextMode();
updateGUI();
}
}
private void handleLanguageSwitch() {
Localizer localizer = Localizer.getInstance();
if (localizer.getLanguage().equals("ru-RU")) {
localizer.setLanguage("en-US");
} else {
localizer.setLanguage("ru-RU");
}
updateGUI();
}
private void onMouseMoved(CursorMoveEvent event) {
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return;
}
final double yawScale = -0.002f;
final double pitchScale = -yawScale;
final double pitchExtremum = Math.PI / 2 * 0.95f;
double yawChange = event.getChangeX() * yawScale;
double pitchChange = event.getChangeY() * pitchScale;
EntityData player = getEntity();
double startPitch = player.getPitch();
double endPitch = startPitch + pitchChange;
endPitch = Glm.clamp(endPitch, -pitchExtremum, +pitchExtremum);
pitchChange = endPitch - startPitch;
Mat4 mat = Matrices.grab4();
Vec3 lookingAt = Vectors.grab3();
Vec3 rightVector = Vectors.grab3();
rightVector.set(player.getLookingAt()).cross(player.getUpVector()).normalize();
mat.identity()
.rotate((float) yawChange, player.getUpVector())
.rotate((float) pitchChange, rightVector);
VectorUtil.applyMat4(player.getLookingAt(), mat, lookingAt);
player.setLookingAt(lookingAt);
Vectors.release(rightVector);
Vectors.release(lookingAt);
Matrices.release(mat);
}
private void onWheelScroll(WheelScrollEvent event) {
if (event.hasHorizontalMovement()) {
switchPlacingMode();
}
if (InputTracker.isKeyPressed(GLFW.GLFW_KEY_LEFT_CONTROL)) {
switchPlacingMode();
return;
}
if (isBlockSelected) {
selectedBlock += event.isUp() ? +1 : -1;
int size = TestContent.PLACEABLE_BLOCKS.size();
if (selectedBlock < 0) {
selectedBlock = size - 1;
} else if (selectedBlock >= size) {
selectedBlock = 0;
}
} else {
selectedTile += event.isUp() ? +1 : -1;
int size = TestContent.PLACEABLE_TILES.size();
if (selectedTile < 0) {
selectedTile = size - 1;
} else if (selectedTile >= size) {
selectedTile = 0;
}
}
updateGUI();
}
private void switchPlacingMode() {
isBlockSelected = !isBlockSelected;
updateGUI();
}
public void handleCtrlIfApplicable(Input input) {
if (input.getEvent() instanceof KeyEvent) {
KeyEvent ke = (KeyEvent) input.getEvent();
if (ke.isRepeat()) {
return;
}
if (ke.getKey() == GLFW.GLFW_KEY_LEFT_CONTROL || ke.getKey() == GLFW.GLFW_KEY_RIGHT_CONTROL) {
handleCtrl(ke);
input.consume();
}
}
}
private void handleCtrl(KeyEvent event) {
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return;
}
double now = GraphicsInterface.getTime();
int change = 0;
if (event.isPress()) {
change = +1;
lastCtrlPress = now;
} else {
if (now - lastCtrlPress > Units.get("200 ms")) {
change = -1;
lastCtrlPress = Double.NEGATIVE_INFINITY;
}
}
if (change == 0) {
return;
}
if (event.hasShift()) {
change *= -1;
}
int selected = getEntity().getSelectedHandIndex();
int maxSelected = getEntity().getHandCount() - 1;
selected += change;
if (selected < 0) {
selected = maxSelected;
} else if (selected > maxSelected) {
selected = 0;
}
getEntity().setSelectedHandIndexNow(selected);
}
public EntityDataPlayer getEntity() {
return getPlayer().getEntity();
}
public LocalPlayer getPlayer() {
return ClientState.getInstance().getLocalPlayer();
}
private void updateGUI() {
if (this.updateCallback != null) {
this.updateCallback.run();
}
}
public boolean isFlying() {
return isFlying;
}
public boolean isSprinting() {
return isSprinting;
}
public BlockData getSelectedBlock() {
return TestContent.PLACEABLE_BLOCKS.get(selectedBlock);
}
public TileData getSelectedTile() {
return TestContent.PLACEABLE_TILES.get(selectedTile);
}
public boolean isBlockSelected() {
return isBlockSelected;
}
}

View File

@ -1,158 +0,0 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.state.IOContext;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.DecodingException;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.common.world.io.ChunkIO;
import ru.windcorp.progressia.server.Server;
public class TestWorldDiskIO {
private static final Path SAVE_DIR = Paths.get("tmp_world");
private static final Logger LOG = LogManager.getLogger("TestWorldDiskIO");
private static final boolean ENABLE = false;
public static void saveChunk(DefaultChunkData chunk, Server server) {
if (!ENABLE)
return;
try {
LOG.debug(
"Saving {} {} {}",
chunk.getPosition().x,
chunk.getPosition().y,
chunk.getPosition().z
);
Files.createDirectories(SAVE_DIR);
Path path = SAVE_DIR.resolve(
String.format(
"chunk_%+d_%+d_%+d.progressia_chunk",
chunk.getPosition().x,
chunk.getPosition().y,
chunk.getPosition().z
)
);
try (
DataOutputStream output = new DataOutputStream(
new DeflaterOutputStream(new BufferedOutputStream(Files.newOutputStream(path)))
)
) {
ChunkIO.save(chunk, output, IOContext.SAVE);
writeGenerationHint(chunk, output, server);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void writeGenerationHint(DefaultChunkData chunk, DataOutputStream output, Server server)
throws IOException {
server.getWorld().getGenerator().writeGenerationHint(output, chunk.getGenerationHint());
}
public static DefaultChunkData tryToLoad(Vec3i chunkPos, DefaultWorldData world, Server server) {
if (!ENABLE)
return null;
Path path = SAVE_DIR.resolve(
String.format(
"chunk_%+d_%+d_%+d.progressia_chunk",
chunkPos.x,
chunkPos.y,
chunkPos.z
)
);
if (!Files.exists(path)) {
LOG.debug(
"Not found {} {} {}",
chunkPos.x,
chunkPos.y,
chunkPos.z
);
return null;
}
try {
DefaultChunkData result = load(path, chunkPos, world, server);
LOG.debug(
"Loaded {} {} {}",
chunkPos.x,
chunkPos.y,
chunkPos.z
);
return result;
} catch (Exception e) {
e.printStackTrace();
LOG.debug(
"Could not load {} {} {}",
chunkPos.x,
chunkPos.y,
chunkPos.z
);
return null;
}
}
private static DefaultChunkData load(Path path, Vec3i chunkPos, DefaultWorldData world, Server server)
throws IOException,
DecodingException {
try (
DataInputStream input = new DataInputStream(
new InflaterInputStream(new BufferedInputStream(Files.newInputStream(path)))
)
) {
DefaultChunkData chunk = ChunkIO.load(world, chunkPos, input, IOContext.SAVE);
readGenerationHint(chunk, input, server);
return chunk;
}
}
private static void readGenerationHint(DefaultChunkData chunk, DataInputStream input, Server server)
throws IOException,
DecodingException {
chunk.setGenerationHint(server.getWorld().getGenerator().readGenerationHint(input));
}
}

View File

@ -0,0 +1,130 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.controls;
import glm.Glm;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.comms.controls.ControlTriggerRegistry;
import ru.windcorp.progressia.client.comms.controls.ControlTriggers;
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.world.Camera;
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
import ru.windcorp.progressia.common.util.Matrices;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.entity.EntityData;
public class CameraControls {
{
reset();
}
public void reset() {
// No instance fields; futureproofing
}
public void registerControls() {
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
triggers.register(
ControlTriggers.localOf(
"Test:TurnCameraWithCursor",
CursorMoveEvent.class,
this::turnCameraWithCursor
)
);
triggers.register(
ControlTriggers.localOf(
"Test:SwitchCameraMode",
KeyEvent.class,
this::switchCameraMode,
new KeyMatcher("F5")::matches
)
);
NoclipCamera.register();
triggers.register(
ControlTriggers.localOf(
"Test:ToggleNoclip",
KeyEvent.class,
NoclipCamera::toggleNoclip,
new KeyMatcher("V")::matches
)
);
}
private void turnCameraWithCursor(CursorMoveEvent event) {
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return;
}
Camera.Anchor cameraAnchor = ClientState.getInstance().getCamera().getAnchor();
if (!(cameraAnchor instanceof EntityAnchor)) {
return;
}
EntityData player = ((EntityAnchor) cameraAnchor).getEntity();
final double yawScale = -0.002f;
final double pitchScale = -yawScale;
final double pitchExtremum = Math.PI / 2 * 0.995f;
double yawChange = event.getChangeX() * yawScale;
double pitchChange = event.getChangeY() * pitchScale;
double startPitch = player.getPitch();
double endPitch = startPitch + pitchChange;
endPitch = Glm.clamp(endPitch, -pitchExtremum, +pitchExtremum);
pitchChange = endPitch - startPitch;
Mat4 mat = Matrices.grab4();
Vec3 lookingAt = Vectors.grab3();
Vec3 rightVector = Vectors.grab3();
rightVector.set(player.getLookingAt()).cross(player.getUpVector()).normalize();
mat.identity()
.rotate((float) yawChange, player.getUpVector())
.rotate((float) pitchChange, rightVector);
VectorUtil.applyMat4(player.getLookingAt(), mat, lookingAt);
player.setLookingAt(lookingAt);
Vectors.release(rightVector);
Vectors.release(lookingAt);
Matrices.release(mat);
}
private void switchCameraMode() {
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return;
}
if (ClientState.getInstance().getCamera().hasAnchor()) {
ClientState.getInstance().getCamera().selectNextMode();
}
}
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test;
package ru.windcorp.progressia.test.controls;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.comms.controls.ControlData;

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test;
package ru.windcorp.progressia.test.controls;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.comms.controls.ControlData;

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test;
package ru.windcorp.progressia.test.controls;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.comms.controls.ControlData;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.controls;
import org.lwjgl.glfw.GLFW;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.audio.Sound;
import ru.windcorp.progressia.client.comms.controls.ControlTriggerRegistry;
import ru.windcorp.progressia.client.comms.controls.ControlTriggers;
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
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.client.graphics.world.Selection;
import ru.windcorp.progressia.common.comms.controls.ControlData;
import ru.windcorp.progressia.common.comms.controls.ControlDataRegistry;
import ru.windcorp.progressia.common.comms.controls.PacketControl;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.block.BlockDataRegistry;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.comms.controls.ControlLogic;
import ru.windcorp.progressia.server.comms.controls.ControlLogicRegistry;
import ru.windcorp.progressia.server.world.context.ServerBlockContext;
import ru.windcorp.progressia.server.world.context.ServerTileContext;
import ru.windcorp.progressia.server.world.context.ServerTileStackContext;
import ru.windcorp.progressia.server.world.tile.TileLogic;
import ru.windcorp.progressia.server.world.tile.TileLogicRegistry;
import ru.windcorp.progressia.test.TestContent;
public class InteractionControls {
private int selectedBlock;
private int selectedTile;
private boolean isBlockSelected;
{
reset();
}
public void reset() {
selectedBlock = 0;
selectedTile = 0;
isBlockSelected = true;
}
public void registerControls() {
ControlDataRegistry data = ControlDataRegistry.getInstance();
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
ControlLogicRegistry logic = ControlLogicRegistry.getInstance();
data.register("Test:BreakBlock", ControlBreakBlockData::new);
triggers.register(
ControlTriggers.of(
"Test:BreakBlock",
KeyEvent.class,
this::onBlockBreakTrigger,
KeyMatcher.LMB::matches,
i -> isAnythingSelected()
)
);
logic.register(ControlLogic.of("Test:BreakBlock", InteractionControls::onBlockBreakReceived));
data.register("Test:PlaceBlock", ControlPlaceBlockData::new);
triggers.register(
ControlTriggers.of(
"Test:PlaceBlock",
KeyEvent.class,
this::onBlockPlaceTrigger,
KeyMatcher.RMB::matches,
i -> isAnythingSelected() && isBlockSelected()
)
);
logic.register(ControlLogic.of("Test:PlaceBlock", InteractionControls::onBlockPlaceReceived));
data.register("Test:PlaceTile", ControlPlaceTileData::new);
triggers.register(
ControlTriggers.of(
"Test:PlaceTile",
KeyEvent.class,
this::onTilePlaceTrigger,
KeyMatcher.RMB::matches,
i -> isAnythingSelected() && !isBlockSelected()
)
);
logic.register(ControlLogic.of("Test:PlaceTile", InteractionControls::onTilePlaceReceived));
triggers.register(
ControlTriggers.localOf(
"Test:SwitchPlacingModeMMB",
KeyEvent.class,
this::switchPlacingMode,
KeyMatcher.MMB::matches
)
);
triggers.register(
ControlTriggers.localOf(
"Test:SwitchPlacingModeWheel",
WheelScrollEvent.class,
this::switchPlacingMode,
e -> e.hasHorizontalMovement() || InputTracker.isKeyPressed(GLFW.GLFW_KEY_LEFT_CONTROL)
)
);
triggers.register(
ControlTriggers.localOf(
"Test:SelectNextBlockOrTile",
WheelScrollEvent.class,
this::selectNextBlockOrTile,
e -> !e.hasHorizontalMovement() && !InputTracker.isKeyPressed(GLFW.GLFW_KEY_LEFT_CONTROL)
)
);
}
private static Selection getSelection() {
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
if (client == null || !client.isReady())
return null;
return client.getLocalPlayer().getSelection();
}
private static boolean isAnythingSelected() {
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
if (client == null || !client.isReady())
return false;
return client.getLocalPlayer().getSelection().exists();
}
private void onBlockBreakTrigger(ControlData control) {
((ControlBreakBlockData) control).setBlockInWorld(getSelection().getBlock());
Sound sfx = new Sound("Progressia:BlockDestroy");
sfx.setPosition(getSelection().getPoint());
sfx.setPitch((float) (Math.random() + 1 * 0.5));
sfx.play(false);
}
private static void onBlockBreakReceived(
Server server,
PacketControl packet,
ru.windcorp.progressia.server.comms.Client client
) {
Vec3i blockInWorld = ((ControlBreakBlockData) packet.getControl()).getBlockInWorld();
server.createAbsoluteContext().setBlock(blockInWorld, BlockDataRegistry.getInstance().get("Test:Air"));
}
private void onBlockPlaceTrigger(ControlData control) {
((ControlPlaceBlockData) control).set(
getSelectedBlock(),
getSelection().getBlock().add_(getSelection().getSurface().getVector())
);
}
private static void onBlockPlaceReceived(
Server server,
PacketControl packet,
ru.windcorp.progressia.server.comms.Client client
) {
ControlPlaceBlockData controlData = ((ControlPlaceBlockData) packet.getControl());
BlockData block = controlData.getBlock();
Vec3i blockInWorld = controlData.getBlockInWorld();
if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null)
return;
server.createAbsoluteContext().setBlock(blockInWorld, block);
}
private void onTilePlaceTrigger(ControlData control) {
((ControlPlaceTileData) control).set(
getSelectedTile(),
getSelection().getBlock(),
getSelection().getSurface()
);
}
private static void onTilePlaceReceived(
Server server,
PacketControl packet,
ru.windcorp.progressia.server.comms.Client client
) {
ControlPlaceTileData controlData = ((ControlPlaceTileData) packet.getControl());
TileData tile = controlData.getTile();
Vec3i blockInWorld = controlData.getBlockInWorld();
AbsFace face = controlData.getFace();
if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null) {
return;
}
if (server.getWorld().getData().getTiles(blockInWorld, face).isFull()) {
return;
}
ServerBlockContext context = server.createContext(blockInWorld);
ServerTileStackContext tsContext = context.push(context.toContext(face));
ServerTileContext tileContext = tsContext.push(tsContext.getTileCount());
TileLogic logic = TileLogicRegistry.getInstance().get(tile.getId());
if (!logic.canOccupyFace(tileContext)) {
return;
}
tileContext.addTile(tile);
}
public void switchPlacingMode() {
isBlockSelected = !isBlockSelected;
}
public void selectNextBlockOrTile(WheelScrollEvent event) {
if (isBlockSelected) {
selectedBlock += event.isUp() ? +1 : -1;
int size = TestContent.PLACEABLE_BLOCKS.size();
if (selectedBlock < 0) {
selectedBlock = size - 1;
} else if (selectedBlock >= size) {
selectedBlock = 0;
}
} else {
selectedTile += event.isUp() ? +1 : -1;
int size = TestContent.PLACEABLE_TILES.size();
if (selectedTile < 0) {
selectedTile = size - 1;
} else if (selectedTile >= size) {
selectedTile = 0;
}
}
}
public BlockData getSelectedBlock() {
return TestContent.PLACEABLE_BLOCKS.get(selectedBlock);
}
public TileData getSelectedTile() {
return TestContent.PLACEABLE_TILES.get(selectedTile);
}
public boolean isBlockSelected() {
return isBlockSelected;
}
}

View File

@ -0,0 +1,151 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.controls;
import org.lwjgl.glfw.GLFW;
import ru.windcorp.progressia.client.Client;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.comms.controls.ControlTriggerRegistry;
import ru.windcorp.progressia.client.comms.controls.ControlTriggers;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.world.entity.EntityDataPlayer;
public class InventoryControls {
/**
* The minimum duration of a ctrl stroke for it to be consider holding the key down
*/
private static final double MIN_CTRL_HOLD_LENGTH = Units.get("200 ms");
private double lastCtrlPress;
{
reset();
}
public void reset() {
lastCtrlPress = Double.NEGATIVE_INFINITY;
}
public void registerControls() {
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
triggers.register(
ControlTriggers.localOf(
"Test:ToggleInventory",
KeyEvent.class,
this::toggleInventory,
new KeyMatcher("E")::matches
)
);
triggers.register(
ControlTriggers.localOf(
"Test:CloseInventory",
KeyEvent.class,
this::toggleInventory,
new KeyMatcher("Escape")::matches,
e -> ClientState.getInstance().getHUD().isInventoryShown()
)
);
triggers.register(
ControlTriggers.localOf(
"Test:HideHUD",
KeyEvent.class,
this::switchHUD,
new KeyMatcher("F1")::matches
)
);
triggers.register(
ControlTriggers.localOf(
"Test:SwitchHandsWithCtrl",
KeyEvent.class,
this::switchHandsWithCtrl,
e -> !e.isRepeat(),
e -> e.getKey() == GLFW.GLFW_KEY_LEFT_CONTROL || e.getKey() == GLFW.GLFW_KEY_RIGHT_CONTROL
)
);
}
private void toggleInventory() {
Client client = ClientState.getInstance();
if (client == null || !client.isReady())
return;
client.getHUD().setInventoryShown(!client.getHUD().isInventoryShown());
}
private void switchHUD() {
Client client = ClientState.getInstance();
if (client == null || !client.isReady())
return;
client.getHUD().setHidden(!client.getHUD().isHidden());
}
private void switchHandsWithCtrl(KeyEvent event) {
int change = 0;
if (event.isPress()) {
change = +1;
lastCtrlPress = event.getTime();
} else {
if (event.getTime() - lastCtrlPress > MIN_CTRL_HOLD_LENGTH) {
change = -1;
lastCtrlPress = Double.NEGATIVE_INFINITY;
}
}
if (event.hasShift()) {
change *= -1;
}
switchHands(change);
}
private void switchHands(int change) {
if (change == 0) {
return;
}
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return;
}
EntityDataPlayer entity = ClientState.getInstance().getLocalPlayer().getEntity();
int selected = entity.getSelectedHandIndex();
int maxSelected = entity.getHandCount() - 1;
selected += change;
if (selected < 0) {
selected = maxSelected;
} else if (selected > maxSelected) {
selected = 0;
}
entity.setSelectedHandIndexNow(selected);
}
}

View File

@ -0,0 +1,315 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.controls;
import org.lwjgl.glfw.GLFW;
import glm.mat._3.Mat3;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.comms.controls.ControlTriggerRegistry;
import ru.windcorp.progressia.client.comms.controls.ControlTriggers;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
import ru.windcorp.progressia.client.graphics.world.Camera;
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.server.ServerState;
public class MovementControls {
/**
* Max delay between space presses that can toggle flying
*/
private static final double FLYING_SWITCH_MAX_DELAY = Units.get("300 ms");
/**
* Max delay between W presses that can toggle sprinting
*/
private static final double SPRINTING_SWITCH_MAX_DELAY = Units.get("300 ms");
/**
* Min delay between jumps
*/
private static final double JUMP_MIN_DELAY = Units.get("300 ms");
/**
* Horizontal and vertical max control speed when flying
*/
private static final float FLYING_SPEED = Units.get("6 m/s");
/**
* (0; 1], 1 is instant change, 0 is no control authority
*/
private static final float FLYING_CONTROL_AUTHORITY = Units.get("2 1/s");
/**
* Horizontal max control speed when walking
*/
private static final float WALKING_SPEED = Units.get("4 m/s");
/**
* Horizontal max control speed when sprinting
*/
private static final float SPRINTING_SPEED = Units.get("6 m/s");
/**
* (0; 1], 1 is instant change, 0 is no control authority
*/
private static final float WALKING_CONTROL_AUTHORITY = Units.get("15 1/s");
/**
* Vertical velocity instantly added to player when they jump
*/
private static final float JUMP_VELOCITY = Units.get("5 m/s");
private boolean isFlying;
private boolean isSprinting;
private double lastSpacePress;
private double lastSprintPress;
{
reset();
}
public void applyPlayerControls() {
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return;
}
Camera.Anchor cameraAnchor = ClientState.getInstance().getCamera().getAnchor();
if (!(cameraAnchor instanceof EntityAnchor)) {
return;
}
EntityData player = ((EntityAnchor) cameraAnchor).getEntity();
boolean isFlying = this.isFlying || player.getId().equals("Test:NoclipCamera");
boolean isSprinting = this.isSprinting || player.getId().equals("Test:NoclipCamera");
final float speed, authority;
if (isFlying) {
speed = FLYING_SPEED;
authority = FLYING_CONTROL_AUTHORITY;
} else {
speed = isSprinting ? SPRINTING_SPEED : WALKING_SPEED;
authority = WALKING_CONTROL_AUTHORITY;
}
Mat3 movementTransform = getMovementTransform(player, null);
Vec3 desiredVelocity = getDesiredVelocity(movementTransform, speed, isFlying);
Vec3 newVelocity = getNewVelocity(
desiredVelocity,
player.getVelocity(),
authority,
player.getUpVector(),
isFlying
);
player.getVelocity().set(newVelocity);
tmp_syncServerEntity();
}
private void tmp_syncServerEntity() {
// THIS IS TERRIBLE TEST
EntityData serverEntity = ServerState.getInstance().getWorld().getData()
.getEntity(ClientState.getInstance().getLocalPlayer().getEntityId());
if (serverEntity != null) {
EntityData clientEntity = ClientState.getInstance().getLocalPlayer().getEntity();
clientEntity.copy(serverEntity);
serverEntity.setLookingAt(clientEntity.getLookingAt());
serverEntity.setUpVector(clientEntity.getUpVector());
serverEntity.setPosition(clientEntity.getPosition());
serverEntity.setVelocity(clientEntity.getVelocity());
}
}
private Mat3 getMovementTransform(EntityData player, Mat3 mat) {
if (mat == null) {
mat = new Mat3();
}
Vec3 f = player.getForwardVector(null);
Vec3 u = player.getUpVector();
Vec3 s = f.cross_(u);
//@formatter:off
return mat.set(
f.x, f.y, f.z,
s.x, s.y, s.z,
u.x, u.y, u.z
);
//@formatter:on
}
private Vec3 getDesiredVelocity(Mat3 movementTransform, float speed, boolean isFlying) {
int forward = 0;
int right = 0;
int up = 0;
forward += InputTracker.isKeyPressed(GLFW.GLFW_KEY_W) ? 1 : 0;
forward -= InputTracker.isKeyPressed(GLFW.GLFW_KEY_S) ? 1 : 0;
right += InputTracker.isKeyPressed(GLFW.GLFW_KEY_D) ? 1 : 0;
right -= InputTracker.isKeyPressed(GLFW.GLFW_KEY_A) ? 1 : 0;
if (isFlying) {
up += InputTracker.isKeyPressed(GLFW.GLFW_KEY_SPACE) ? 1 : 0;
up -= InputTracker.isKeyPressed(GLFW.GLFW_KEY_LEFT_SHIFT) ? 1 : 0;
}
Vec3 desiredVelocity = new Vec3(forward, right, 0);
if (forward != 0 && right != 0) {
desiredVelocity.normalize();
}
desiredVelocity.z = up;
// bug in jglm, .mul() and .mul_() are swapped
movementTransform.mul_(desiredVelocity);
desiredVelocity.mul(speed);
return desiredVelocity;
}
private Vec3 getNewVelocity(Vec3 desiredVelocity, Vec3 oldVelocity, float authority, Vec3 up, boolean isFlying) {
// newVelocity = oldVelocity + small change toward desiredVelocity
Vec3 newVelocity = new Vec3()
.set(desiredVelocity)
.sub(oldVelocity)
.mul((float) Math.exp(-authority * GraphicsInterface.getFrameLength()))
.negate()
.add(desiredVelocity);
// If we aren't flying, don't change vertical component
if (!isFlying) {
Vec3 wantedVertical = VectorUtil.projectOnVector(oldVelocity, up, null);
VectorUtil.projectOnSurface(newVelocity, up);
newVelocity.add(wantedVertical);
}
return newVelocity;
}
public void reset() {
isFlying = true;
isSprinting = false;
lastSpacePress = Double.NEGATIVE_INFINITY;
lastSprintPress = Double.NEGATIVE_INFINITY;
}
public void registerControls() {
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
triggers.register(
ControlTriggers.localOf(
"Test:JumpOrToggleFlight",
KeyEvent.class,
this::handleSpacePress,
new KeyMatcher("Space")::matches
)
);
triggers.register(
ControlTriggers.localOf(
"Test:ToggleSprint",
KeyEvent.class,
this::toggleSprint,
new KeyMatcher("W")::matches,
e -> !isFlying
)
);
triggers.register(
ControlTriggers.localOf(
"Test:DisableSprint",
KeyEvent.class,
this::disableSprint,
new KeyMatcher("W")::matchesIgnoringAction,
KeyEvent::isRelease
)
);
}
private void handleSpacePress(KeyEvent e) {
if (
ClientState.getInstance().getCamera().getAnchor() instanceof EntityAnchor
&& ((EntityAnchor) ClientState.getInstance().getCamera().getAnchor()).getEntity().getId()
.equals("Test:NoclipCamera")
) {
return;
}
double timeSinceLastSpacePress = e.getTime() - lastSpacePress;
if (timeSinceLastSpacePress < FLYING_SWITCH_MAX_DELAY) {
isSprinting = false;
isFlying = !isFlying;
} else if (!isFlying && timeSinceLastSpacePress >= JUMP_MIN_DELAY) {
jump();
}
lastSpacePress = e.getTime();
}
private void jump() {
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return;
}
EntityData player = ClientState.getInstance().getLocalPlayer().getEntity();
assert player != null;
Vec3 up = player.getUpVector();
player.getVelocity().add(
up.x * JUMP_VELOCITY,
up.y * JUMP_VELOCITY,
up.z * JUMP_VELOCITY
);
}
private void toggleSprint(KeyEvent e) {
if (e.getTime() - lastSprintPress < SPRINTING_SWITCH_MAX_DELAY) {
isSprinting = !isSprinting;
}
lastSprintPress = e.getTime();
}
private void disableSprint(KeyEvent e) {
isSprinting = false;
}
public boolean isFlying() {
return isFlying;
}
public boolean isSprinting() {
return isSprinting;
}
}

View File

@ -0,0 +1,99 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.controls;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.world.Camera;
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.client.world.entity.EntityRender;
import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry;
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
public class NoclipCamera {
private static class NoclipEntityRender extends EntityRender {
public NoclipEntityRender() {
super("Test:NoclipCamera");
}
@Override
public EntityRenderable createRenderable(EntityData entity) {
return new NoclipEntityRenderable(entity);
}
}
private static class NoclipEntityRenderable extends EntityRenderable {
public NoclipEntityRenderable(EntityData data) {
super(data);
}
@Override
protected void doRender(ShapeRenderHelper renderer) {
// Do nothing
}
}
public static void register() {
EntityDataRegistry.getInstance().register("Test:NoclipCamera");
EntityRenderRegistry.getInstance().register(new NoclipEntityRender());
}
public static void toggleNoclip() {
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return;
}
Camera camera = ClientState.getInstance().getCamera();
WorldRender world = ClientState.getInstance().getWorld();
EntityData player = ClientState.getInstance().getLocalPlayer().getEntity();
List<EntityData> existingCameras = world.getData().getEntities().stream().filter(e -> e.getId().equals("Test:NoclipCamera")).collect(Collectors.toList());
if (!existingCameras.isEmpty()) {
existingCameras.forEach(world.getData()::removeEntity);
camera.setAnchor(new EntityAnchor(world.getEntityRenderable(player)));
return;
}
EntityData noclipCamera = EntityDataRegistry.getInstance().create("Test:NoclipCamera");
noclipCamera.setLookingAt(player.getLookingAt());
noclipCamera.setUpVector(player.getUpVector());
noclipCamera.setPosition(player.getPosition());
noclipCamera.setVelocity(player.getVelocity());
noclipCamera.setEntityId(new Random().nextLong());
player.setVelocity(new Vec3(0));
world.getData().addEntity(noclipCamera);
camera.setAnchor(new EntityAnchor(world.getEntityRenderable(noclipCamera)));
}
}

View File

@ -0,0 +1,193 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.controls;
import java.util.List;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.comms.controls.ControlTriggerRegistry;
import ru.windcorp.progressia.client.comms.controls.ControlTriggers;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
import ru.windcorp.progressia.client.localization.Localizer;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.test.LayerButtonTest;
import ru.windcorp.progressia.test.TestMusicPlayer;
public class TestPlayerControls {
private static final TestPlayerControls INSTANCE = new TestPlayerControls();
public static TestPlayerControls getInstance() {
return INSTANCE;
}
private final MovementControls movementControls = new MovementControls();
private final CameraControls cameraControls = new CameraControls();
private final InteractionControls interactionControls = new InteractionControls();
private final InventoryControls inventoryControls = new InventoryControls();
{
reset();
}
public static void resetInstance() {
INSTANCE.reset();
}
private void reset() {
movementControls.reset();
cameraControls.reset();
interactionControls.reset();
inventoryControls.reset();
}
public void applyPlayerControls() {
movementControls.applyPlayerControls();
}
public void registerControls() {
movementControls.registerControls();
cameraControls.registerControls();
interactionControls.registerControls();
inventoryControls.registerControls();
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
triggers.register(
ControlTriggers.localOf(
"Test:PauseGame",
KeyEvent.class,
this::pauseGame,
new KeyMatcher("Escape")::matches
)
);
triggers.register(
ControlTriggers.localOf(
"Test:ToggleFullscreen",
KeyEvent.class,
this::toggleFullscreen,
new KeyMatcher("F11")::matches
)
);
triggers.register(
ControlTriggers.localOf(
"Test:ToggleVSync",
KeyEvent.class,
this::toggleVSync,
new KeyMatcher("F12")::matches
)
);
triggers.register(
ControlTriggers.localOf(
"Test:ToggleDebugLayer",
KeyEvent.class,
() -> ClientState.getInstance().toggleDebugLayer(),
new KeyMatcher("F3")::matches
)
);
triggers.register(
ControlTriggers.localOf(
"Test:SwitchLanguage",
KeyEvent.class,
this::switchLanguage,
new KeyMatcher("L")::matches
)
);
triggers.register(
ControlTriggers.localOf(
"Test:StartNextMusic",
KeyEvent.class,
TestMusicPlayer::startNextNow,
new KeyMatcher("M")::matches
)
);
}
private void pauseGame() {
GUI.addTopLayer(new LayerButtonTest());
}
private void toggleFullscreen() {
GraphicsInterface.makeFullscreen(!GraphicsBackend.isFullscreen());
}
private void toggleVSync() {
GraphicsBackend.setVSyncEnabled(!GraphicsBackend.isVSyncEnabled());
}
private void switchLanguage() {
Localizer localizer = Localizer.getInstance();
List<String> languages = localizer.getLanguages();
int index = languages.indexOf(localizer.getLanguage());
if (index == languages.size() - 1) {
index = 0;
} else {
index++;
}
localizer.setLanguage(languages.get(index));
}
public EntityData getEntity() {
return getPlayer().getEntity();
}
public LocalPlayer getPlayer() {
return ClientState.getInstance().getLocalPlayer();
}
public MovementControls getMovementControls() {
return movementControls;
}
public BlockData getSelectedBlock() {
return interactionControls.getSelectedBlock();
}
public TileData getSelectedTile() {
return interactionControls.getSelectedTile();
}
public boolean isBlockSelected() {
return interactionControls.isBlockSelected();
}
public boolean isFlying() {
return movementControls.isFlying();
}
public boolean isSprinting() {
return movementControls.isSprinting();
}
}

View File

@ -219,6 +219,10 @@ public class Fields {
return tweak(f, 1, -1, 1);
}
public static Field rarify(Field f, float rarity) {
return (x, y) -> Math.max((f.compute(x, y) - rarity) / (1 - rarity), 0);
}
public static Field octaves(Field f, double scaleFactor, double amplitudeFactor, int octaves) {
return (x, y) -> {
double result = 0;

View File

@ -24,21 +24,19 @@ import java.util.List;
import java.util.function.Function;
import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.util.ArrayFloatRangeMap;
import ru.windcorp.progressia.common.util.FloatRangeMap;
import ru.windcorp.progressia.common.util.noise.discrete.WorleyProceduralNoise;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.block.BlockDataRegistry;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.world.generation.WorldGenerator;
import ru.windcorp.progressia.server.world.generation.planet.Planet;
import ru.windcorp.progressia.server.world.generation.planet.PlanetGenerator;
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.test.Rocks.RockVariant;
import ru.windcorp.progressia.test.Rocks.Rock;
import ru.windcorp.progressia.test.TestContent;
import ru.windcorp.progressia.test.gen.feature.*;
import ru.windcorp.progressia.test.gen.terrain.*;
public class TestGenerationConfig {
@ -49,9 +47,18 @@ public class TestGenerationConfig {
private static final float CURVATURE = Units.get("100 m");
private static final float INNER_RADIUS = Units.get("200 m");
private static final Fields FIELDS = new Fields(SEED);
private final Fields fields = new Fields(SEED);
private final Function<Server, WorldGenerator> generator;
public static Function<Server, WorldGenerator> createGenerator() {
public TestGenerationConfig() {
this.generator = createGenerator();
}
public Function<Server, WorldGenerator> getGenerator() {
return generator;
}
private Function<Server, WorldGenerator> createGenerator() {
Planet planet = new Planet(
((int) PLANET_RADIUS) / Coordinates.CHUNK_SIZE,
@ -60,80 +67,75 @@ public class TestGenerationConfig {
INNER_RADIUS
);
TestHeightMap heightMap = new TestHeightMap(planet, planet.getRadius() / 4, FIELDS);
TestHeightMap heightMap = new TestHeightMap(planet, planet.getRadius() / 4, fields);
FloatRangeMap<TerrainLayer> layers = new ArrayFloatRangeMap<>();
registerTerrainLayers(layers);
LayeredTerrain terrain = new LayeredTerrain();
registerTerrainLayers(terrain);
List<SurfaceFeature> features = new ArrayList<>();
registerFeatures(features);
return server -> new PlanetGenerator("Test:PlanetGenerator", server, planet, heightMap, layers, features);
return server -> new PlanetGenerator("Test:PlanetGenerator", server, planet, heightMap, terrain, features);
}
private static void registerTerrainLayers(FloatRangeMap<TerrainLayer> layers) {
BlockData dirt = BlockDataRegistry.getInstance().get("Test:Dirt");
BlockData air = BlockDataRegistry.getInstance().get("Test:Air");
private void registerTerrainLayers(LayeredTerrain terrain) {
SurfaceFloatField cliffs = fields.get("Test:Cliff");
SurfaceFloatField beaches = fields.register(
"Test:Beach",
f -> multiply(
anti(fields.get("Test:Cliff", f))
)
);
RockStrata rockStrata = createStrata();
SurfaceFloatField cliffs = FIELDS.get("Test:Cliff");
terrain.addLayer(new AirLayer("Test:Air"));
terrain.addLayer(new MantleLayer("Test:Mantle"));
terrain.addLayer(new CrustLayer("Test:Crust", rockStrata));
terrain.addLayer(new WaterLayer("Test:Water"));
terrain.addLayer(new SoilLayer("Test:Soil"));
terrain.addLayer(new CliffLayer("Test:Cliffs", cliffs, rockStrata));
terrain.addLayer(new BeachLayer("Test:Beaches", beaches, rockStrata));
}
WorleyProceduralNoise.Builder<TerrainLayer> builder = WorleyProceduralNoise.builder();
TestContent.ROCKS.getRocks().forEach(rock -> {
builder.add((c, d) -> {
if (c.getRandom().nextInt(3) == 0) {
return rock.getBlock(RockVariant.CRACKED);
} else {
return rock.getBlock(RockVariant.MONOLITH);
}
}, 1);
});
SurfaceFloatField rockDepthOffsets = FIELDS.register(
private RockStrata createStrata() {
WorleyProceduralNoise.Builder<Rock> builder = WorleyProceduralNoise.builder();
TestContent.ROCKS.getRocks().forEach(rock -> builder.add(rock, 1));
SurfaceFloatField rockDepthOffsets = fields.register(
"Test:RockDepthOffsets",
() -> tweak(FIELDS.primitive(), 40, 5)
() -> tweak(fields.primitive(), 40, 5)
);
RockLayer rockLayer = new RockLayer(builder.build(SEED), rockDepthOffsets);
layers.put(Float.NEGATIVE_INFINITY, 0, (c, d) -> air);
layers.put(0, 4, (c, d) -> {
if (cliffs.get(c.getSurface().getUp(), c.getLocation().x, c.getLocation().y) > 0) {
return rockLayer.get(c, d);
} else {
return dirt;
}
});
layers.put(4, Float.POSITIVE_INFINITY, rockLayer);
return new RockStrata(builder.build(SEED), rockDepthOffsets);
}
private static void registerFeatures(List<SurfaceFeature> features) {
private void registerFeatures(List<SurfaceFeature> features) {
SurfaceFloatField forestiness = FIELDS.register(
SurfaceFloatField forestiness = fields.register(
"Test:Forest",
() -> squash(scale(FIELDS.primitive(), 200), 5)
() -> squash(scale(fields.primitive(), 200), 5)
);
SurfaceFloatField grassiness = FIELDS.register(
SurfaceFloatField grassiness = fields.register(
"Test:Grass",
f -> multiply(
tweak(octaves(FIELDS.primitive(), 2, 2), 40, 0.5, 1.2),
squash(tweak(FIELDS.get("Test:Forest", f), 1, -1, 1), 10),
anti(squash(FIELDS.get("Test:Cliff", f), 10))
tweak(octaves(fields.primitive(), 2, 2), 40, 0.5, 1.2),
squash(tweak(fields.get("Test:Forest", f), 1, -1, 1), 10),
anti(squash(fields.get("Test:Cliff", f), 10))
)
);
Function<String, SurfaceFloatField> floweriness = flowerName -> FIELDS.register(
"Test:Flower" + flowerName,
f -> multiply(
selectPositive(squash(scale(octaves(FIELDS.primitive(), 2, 3), 100), 2), 1, 0.5),
tweak(FIELDS.get("Test:Forest", f), 1, -1, 1.1),
anti(squash(FIELDS.get("Test:Cliff", f), 10))
)
Function<AbsFace, Field> floweriness = f -> multiply(
amplify(withMin(squash(scale(octaves(fields.primitive(), 2, 3), 100), 5), 0), 2),
tweak(fields.get("Test:Forest", f), 1, -1, 1.1),
anti(squash(fields.get("Test:Cliff", f), 10))
);
features.add(new TestBushFeature("Test:BushFeature", forestiness));
features.add(new TestTreeFeature("Test:TreeFeature", forestiness));
features.add(new TestGrassFeature("Test:GrassFeature", grassiness));
features.add(new TestFlowerFeature("Test:FlowerFeature", floweriness));
features.add(new TestFlowerFeature("Test:FlowerFeature", TestContent.FLOWERS, floweriness, fields));
}
}

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.gen;
package ru.windcorp.progressia.test.gen.feature;
import java.util.Set;
import java.util.function.Consumer;

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.gen;
package ru.windcorp.progressia.test.gen.feature;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.block.BlockData;
@ -38,10 +38,12 @@ public class TestBushFeature extends MultiblockVegetationFeature {
Vec3i center = context.getLocation().add_(0, 0, 1);
context.setBlock(center, trunk);
context.setBlock(center.add_(0, 0, 1), leaves);
if (size > 0.8) {
context.setBlock(center, trunk);
context.setBlock(center.add_(0, 0, 1), leaves);
}
iterateBlob(center, stretch(size, 1.3, 2.5), stretch(size, 0.6, 1.5), 0.7, 2, p -> {
iterateBlob(center, stretch(size, 0.5, 2.5), stretch(size, 0.6, 1.5), 0.7, 2, p -> {
setLeaves(context, p, leaves);
});
}

View File

@ -15,37 +15,75 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.gen;
package ru.windcorp.progressia.test.gen.feature;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import com.google.common.collect.ImmutableSet;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.common.world.tile.TileDataRegistry;
import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField;
import ru.windcorp.progressia.server.world.generation.surface.SurfaceTopLayerFeature;
import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext;
import ru.windcorp.progressia.test.Flowers;
import ru.windcorp.progressia.test.Flowers.Flower;
import ru.windcorp.progressia.test.Flowers.FlowerVariant;
import ru.windcorp.progressia.test.TestContent;
import ru.windcorp.progressia.test.gen.Fields;
import ru.windcorp.progressia.test.gen.Fields.Field;
public class TestFlowerFeature extends SurfaceTopLayerFeature {
private static class FlowerGenerator {
private final TileData tile;
private final TileData[] variants;
private final SurfaceFloatField floweriness;
public FlowerGenerator(TileData tile, Function<String, SurfaceFloatField> flowerinessGenerator) {
this.tile = tile;
this.floweriness = flowerinessGenerator.apply(tile.getName());
public FlowerGenerator(Flower flower, Function<AbsFace, Field> flowerinessGenerator, Fields fields) {
this.floweriness = fields.register(
"Test:Flower" + flower.getName(),
f -> Fields.rarify(flowerinessGenerator.apply(f), flower.getRarity())
);
List<TileData> tiles = new ArrayList<>();
for (FlowerVariant variant : FlowerVariant.values()) {
TileData tile = flower.getTile(variant);
if (tile == null) {
continue;
}
tiles.add(tile);
}
this.variants = tiles.toArray(new TileData[tiles.size()]);
}
public void generate(SurfaceBlockContext context) {
if (context.getRandom().nextDouble() < floweriness.get(context)) {
context.addTile(RelFace.UP, tile);
float floweriness = this.floweriness.get(context);
if (floweriness <= 0) {
return;
}
float random = context.getRandom().nextFloat();
int variant = (int) Math.floor((random + floweriness - 1) * variants.length);
if (variant < 0) {
return;
} else if (variant >= variants.length) {
// Screw doing float math properly, just clamp it
variant = variants.length - 1;
}
context.addTile(RelFace.UP, variants[variant]);
}
}
private final Set<String> soilWhitelist;
@ -58,12 +96,11 @@ public class TestFlowerFeature extends SurfaceTopLayerFeature {
private final FlowerGenerator[] flowers;
public TestFlowerFeature(String id, Function<String, SurfaceFloatField> flowerinessGenerator) {
public TestFlowerFeature(String id, Flowers flowers, Function<AbsFace, Field> flowerinessGenerator, Fields fields) {
super(id);
this.flowers = TileDataRegistry.getInstance().values().stream()
.filter(tile -> tile.getName().endsWith("Flowers"))
.map(tile -> new FlowerGenerator(tile, flowerinessGenerator))
this.flowers = flowers.getFlowers().stream()
.map(flower -> new FlowerGenerator(flower, flowerinessGenerator, fields))
.toArray(FlowerGenerator[]::new);
}

View File

@ -15,10 +15,11 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.gen;
package ru.windcorp.progressia.test.gen.feature;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@ -58,16 +59,29 @@ public class TestGrassFeature extends SurfaceTopLayerFeature {
flatGrasses.put(0.05f, 0.1f, TileDataRegistry.getInstance().get("Test:GrassThreads"));
}
private final FloatRangeMap<TileData> herbGrasses = new ArrayFloatRangeMap<>();
private static final Function<SurfaceBlockContext, TileData> randomPicker(String commonId, String rareId) {
TileData common = TileDataRegistry.getInstance().get(commonId);
TileData rare = TileDataRegistry.getInstance().get(rareId);
final float rareChance = 0.2f;
return context -> {
if (context.getRandom().nextFloat() < rareChance) {
return rare;
} else {
return common;
}
};
}
private final FloatRangeMap<Function<SurfaceBlockContext, TileData>> herbGrasses = new ArrayFloatRangeMap<>();
{
herbGrasses.put(0.6f, 1, TileDataRegistry.getInstance().get("Test:TallGrass"));
herbGrasses.put(0.4f, 0.6f, TileDataRegistry.getInstance().get("Test:MediumGrass"));
herbGrasses.put(0.1f, 0.4f, TileDataRegistry.getInstance().get("Test:LowGrass"));
herbGrasses.put(0.6f, 1, randomPicker("Test:GrassMeadow2", "Test:GrassBluegrass2"));
herbGrasses.put(0.4f, 0.6f, randomPicker("Test:GrassMeadow1", "Test:GrassBluegrass1"));
herbGrasses.put(0.1f, 0.4f, randomPicker("Test:GrassMeadow0", "Test:GrassBluegrass0"));
}
private final List<TileData> scatter = ImmutableList.of(
TileDataRegistry.getInstance().get("Test:Stones"),
TileDataRegistry.getInstance().get("Test:Sand"),
TileDataRegistry.getInstance().get("Test:Bush"),
TileDataRegistry.getInstance().get("Test:Fern")
);
@ -104,7 +118,17 @@ public class TestGrassFeature extends SurfaceTopLayerFeature {
}
private void growGrass(SurfaceBlockContext context, double grassiness) {
TileData flatGrass = flatGrasses.get((float) grassiness);
double flatGrassiness = grassiness;
BlockData soil = context.getBlock();
if (soil.getId().endsWith("Sand")) {
flatGrassiness = flatGrassiness / 2 - 0.2;
if (flatGrassiness < 0) {
flatGrassiness = 0;
}
}
TileData flatGrass = flatGrasses.get((float) flatGrassiness);
if (flatGrass != null) {
for (RelFace face : RelFace.getFaces()) {
if (face == RelFace.DOWN)
@ -125,9 +149,9 @@ public class TestGrassFeature extends SurfaceTopLayerFeature {
}
if (context.getRandom().nextDouble() < grassiness) {
TileData herbGrass = herbGrasses.get((float) grassiness);
Function<SurfaceBlockContext, TileData> herbGrass = herbGrasses.get((float) grassiness);
if (herbGrass != null) {
context.addTile(RelFace.UP, herbGrass);
context.addTile(RelFace.UP, herbGrass.apply(context));
}
}
}

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.gen;
package ru.windcorp.progressia.test.gen.feature;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.block.BlockData;
@ -26,10 +26,10 @@ import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlo
public class TestTreeFeature extends MultiblockVegetationFeature {
private final BlockData trunk = BlockDataRegistry.getInstance().get("Test:Log");
private final BlockData leaves = BlockDataRegistry.getInstance().get("Test:TemporaryLeaves");
private final BlockData leaves = BlockDataRegistry.getInstance().get("Test:LeavesPine");
public TestTreeFeature(String id, SurfaceFloatField selector) {
super(id, selector, 10 * 10);
super(id, selector, 7 * 7);
}
@Override
@ -39,18 +39,29 @@ public class TestTreeFeature extends MultiblockVegetationFeature {
Vec3i center = start.add_(0);
double size = selectorValue * randomDouble(context, 0.8, 1.2);
double volume = stretch(size, 2.5, 1);
int height = (int) stretch(size, 3, 7);
int height = (int) stretch(size, 8, 12);
for (; center.z < start.z + height; ++center.z) {
context.setBlock(center, trunk);
}
--center.z;
double branchHorDistance = 0;
iterateBlob(center, 1.5 * volume, 1.75, 0.5, 3, p -> {
setLeaves(context, p, leaves);
});
int branchCount = 1 + context.getRandom().nextInt(2) + (int) (stretch(size, 0, 4));
for (int i = 0; i < branchCount; ++i) {
do {
double branchSize = 0.5 + randomDouble(context, 1, 2) * size;
double branchHorAngle = randomDouble(context, 0, 2 * Math.PI);
int branchVertOffset = (int) randomDouble(context, -2, 0);
double branchHorDistance = randomDouble(context, 0.7, 1.5) * volume;
int branchVertOffset = (int) randomDouble(context, -height / 3.0, -height / 1.5);
double branchSize = randomDouble(context, 1, 2) * volume;
double branchHeight = 1.5;
Vec3i branchCenter = center.add_(
(int) (Math.sin(branchHorAngle) * branchHorDistance),
@ -58,12 +69,12 @@ public class TestTreeFeature extends MultiblockVegetationFeature {
branchVertOffset
);
iterateBlob(branchCenter, 1 * branchSize, 2.3 * branchSize, 0.5, 3, p -> {
iterateBlob(branchCenter, branchSize, branchHeight, 0.5, 3, p -> {
setLeaves(context, p, leaves);
});
branchHorDistance = randomDouble(context, 0.7, 1.5);
} while (context.getRandom().nextInt(8) > 1);
}
}
}

View File

@ -0,0 +1,42 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.gen.terrain;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.block.BlockDataRegistry;
import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext;
public class AirLayer extends TerrainLayer {
private final BlockData air = BlockDataRegistry.getInstance().get("Test:Air");
public AirLayer(String id) {
super(id);
}
@Override
public BlockData generate(SurfaceBlockContext context, float depth, float intensity) {
return air;
}
@Override
public float getIntensity(SurfaceBlockContext context, float depth) {
return depth <= 0 ? 1 : 0;
}
}

View File

@ -0,0 +1,55 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.gen.terrain;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField;
import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext;
import ru.windcorp.progressia.test.Rocks.RockVariant;
public class BeachLayer extends TerrainLayer {
private final SurfaceFloatField beachSelector;
private final RockStrata strata;
public BeachLayer(String id, SurfaceFloatField beachSelector, RockStrata strata) {
super(id);
this.beachSelector = beachSelector;
this.strata = strata;
}
@Override
public BlockData generate(SurfaceBlockContext context, float depth, float intensity) {
return strata.get(context, depth).getBlock(RockVariant.SAND);
}
@Override
public float getIntensity(SurfaceBlockContext context, float depth) {
if (depth < 0 || depth > 3) {
return 0;
}
float altitude = context.getLocation().z;
if (altitude < -5| altitude > 1) {
return 0;
}
return 3 * beachSelector.get(context);
}
}

View File

@ -0,0 +1,64 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.gen.terrain;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField;
import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext;
import ru.windcorp.progressia.test.Rocks.RockVariant;
public class CliffLayer extends TerrainLayer {
private final SurfaceFloatField cliffSelector;
private final RockStrata strata;
public CliffLayer(String id, SurfaceFloatField cliffSelector, RockStrata strata) {
super(id);
this.cliffSelector = cliffSelector;
this.strata = strata;
}
@Override
public BlockData generate(SurfaceBlockContext context, float depth, float intensity) {
RockVariant variant;
switch (context.getRandom().nextInt(4)) {
case 0:
variant = RockVariant.GRAVEL;
break;
case 1:
variant = RockVariant.MONOLITH;
break;
default:
variant = RockVariant.CRACKED;
break;
}
return strata.get(context, depth).getBlock(variant);
}
@Override
public float getIntensity(SurfaceBlockContext context, float depth) {
if (depth < 0 || depth > 7) {
return 0;
}
return 100 * cliffSelector.get(context);
}
}

View File

@ -0,0 +1,69 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.gen.terrain;
import ru.windcorp.progressia.common.util.ArrayFloatRangeMap;
import ru.windcorp.progressia.common.util.FloatRangeMap;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext;
import ru.windcorp.progressia.test.Rocks.RockVariant;
public class CrustLayer extends TerrainLayer {
private static final FloatRangeMap<RockVariant> WEAR_TABLE = new ArrayFloatRangeMap<>();
static {
WEAR_TABLE.put(Float.NEGATIVE_INFINITY, 0.25f, RockVariant.MONOLITH);
WEAR_TABLE.put(0.25f, 0.5f, RockVariant.CRACKED);
WEAR_TABLE.put(0.5f, 0.75f, RockVariant.GRAVEL);
WEAR_TABLE.put(0.75f, Float.POSITIVE_INFINITY, RockVariant.SAND);
}
private final RockStrata strata;
public CrustLayer(String id, RockStrata strata) {
super(id);
this.strata = strata;
}
@Override
public BlockData generate(SurfaceBlockContext context, float depth, float intensity) {
RockVariant variant;
if (depth < 8) {
float wear = 1 - depth / 8;
float offset = (context.getRandom().nextFloat() * 2 - 1) * 0.5f;
variant = WEAR_TABLE.get(wear + offset);
} else {
variant = RockVariant.MONOLITH;
}
return strata.get(context, depth).getBlock(variant);
}
@Override
public float getIntensity(SurfaceBlockContext context, float depth) {
if (depth < 0) {
return 0;
} else if (context.getLocation().z > -100) {
return 1;
} else {
return 0;
}
}
}

Some files were not shown because too many files have changed in this diff Show More