Merge branch 'overhaul-input'
Summary of changes: - Refactored input handling - Added noclip/freecam mode for debugging - Added drag events - Removed useless info from debug layer - Movement controls now work in menus (feature? bug? not sure)
This commit is contained in:
commit
706800218c
@ -143,20 +143,6 @@ public class ControlTriggers {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
///
|
|
||||||
///
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
public static ControlTriggerInputBased localOf(
|
public static ControlTriggerInputBased localOf(
|
||||||
String id,
|
String id,
|
||||||
Consumer<InputEvent> action,
|
Consumer<InputEvent> action,
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
package ru.windcorp.progressia.client.comms.controls;
|
package ru.windcorp.progressia.client.comms.controls;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.Client;
|
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;
|
import ru.windcorp.progressia.common.comms.packets.Packet;
|
||||||
|
|
||||||
public class InputBasedControls {
|
public class InputBasedControls {
|
||||||
@ -36,12 +36,12 @@ public class InputBasedControls {
|
|||||||
.toArray(ControlTriggerInputBased[]::new);
|
.toArray(ControlTriggerInputBased[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleInput(Input input) {
|
public void handleInput(InputEvent event) {
|
||||||
for (ControlTriggerInputBased c : controls) {
|
for (ControlTriggerInputBased c : controls) {
|
||||||
Packet packet = c.onInputEvent(input.getEvent());
|
Packet packet = c.onInputEvent(event);
|
||||||
|
|
||||||
if (packet != null) {
|
if (packet != null) {
|
||||||
input.consume();
|
event.consume();
|
||||||
client.getComms().sendPacket(packet);
|
client.getComms().sendPacket(packet);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -23,15 +23,8 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
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.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 {
|
public class GUI {
|
||||||
|
|
||||||
@ -46,15 +39,6 @@ public class GUI {
|
|||||||
private static final List<LayerStackModification> MODIFICATION_QUEUE = Collections
|
private static final List<LayerStackModification> MODIFICATION_QUEUE = Collections
|
||||||
.synchronizedList(new ArrayList<>());
|
.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() {
|
private GUI() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,43 +110,12 @@ public class GUI {
|
|||||||
LAYERS.forEach(Layer::invalidate);
|
LAYERS.forEach(Layer::invalidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void dispatchInputEvent(InputEvent event) {
|
public static void dispatchInput(InputEvent event) {
|
||||||
Input.Target target;
|
synchronized (LAYERS) {
|
||||||
|
for (int i = 0; i < LAYERS.size(); ++i) {
|
||||||
if (event instanceof KeyEvent) {
|
LAYERS.get(i).handleInput(event);
|
||||||
if (((KeyEvent) event).isMouse()) {
|
|
||||||
target = Input.Target.HOVERED;
|
|
||||||
} else {
|
|
||||||
target = Input.Target.FOCUSED;
|
|
||||||
}
|
}
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.graphics;
|
|||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
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 {
|
public abstract class Layer {
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ public abstract class Layer {
|
|||||||
|
|
||||||
protected abstract void doRender();
|
protected abstract void doRender();
|
||||||
|
|
||||||
protected abstract void handleInput(Input input);
|
public abstract void handleInput(InputEvent input);
|
||||||
|
|
||||||
protected int getWidth() {
|
protected int getWidth() {
|
||||||
return GraphicsInterface.getFrameWidth();
|
return GraphicsInterface.getFrameWidth();
|
||||||
|
@ -69,6 +69,10 @@ public class GraphicsInterface {
|
|||||||
InputHandler.register(listener);
|
InputHandler.register(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void unsubscribeFromInputEvents(Object listener) {
|
||||||
|
InputHandler.unregister(listener);
|
||||||
|
}
|
||||||
|
|
||||||
public static void startNextLayer() {
|
public static void startNextLayer() {
|
||||||
GraphicsBackend.startNextLayer();
|
GraphicsBackend.startNextLayer();
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ public class InputHandler {
|
|||||||
|
|
||||||
public void initialize(int key, int scancode, int action, int mods) {
|
public void initialize(int key, int scancode, int action, int mods) {
|
||||||
this.setTime(GraphicsInterface.getTime());
|
this.setTime(GraphicsInterface.getTime());
|
||||||
|
this.setConsumed(false);
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.scancode = scancode;
|
this.scancode = scancode;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
@ -59,7 +60,7 @@ public class InputHandler {
|
|||||||
if (GraphicsBackend.getWindowHandle() != window)
|
if (GraphicsBackend.getWindowHandle() != window)
|
||||||
return;
|
return;
|
||||||
THE_KEY_EVENT.initialize(key, scancode, action, mods);
|
THE_KEY_EVENT.initialize(key, scancode, action, mods);
|
||||||
dispatch(THE_KEY_EVENT);
|
INPUT_EVENT_BUS.post(THE_KEY_EVENT);
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case GLFW.GLFW_PRESS:
|
case GLFW.GLFW_PRESS:
|
||||||
@ -90,6 +91,7 @@ public class InputHandler {
|
|||||||
|
|
||||||
public void initialize(double x, double y) {
|
public void initialize(double x, double y) {
|
||||||
this.setTime(GraphicsInterface.getTime());
|
this.setTime(GraphicsInterface.getTime());
|
||||||
|
this.setConsumed(false);
|
||||||
getNewPosition().set(x, y);
|
getNewPosition().set(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +111,7 @@ public class InputHandler {
|
|||||||
InputTracker.initializeCursorPosition(x, y);
|
InputTracker.initializeCursorPosition(x, y);
|
||||||
|
|
||||||
THE_CURSOR_MOVE_EVENT.initialize(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);
|
InputTracker.getCursorPosition().set(x, y);
|
||||||
}
|
}
|
||||||
@ -124,6 +126,7 @@ public class InputHandler {
|
|||||||
|
|
||||||
public void initialize(double xOffset, double yOffset) {
|
public void initialize(double xOffset, double yOffset) {
|
||||||
this.setTime(GraphicsInterface.getTime());
|
this.setTime(GraphicsInterface.getTime());
|
||||||
|
this.setConsumed(false);
|
||||||
this.getOffset().set(xOffset, yOffset);
|
this.getOffset().set(xOffset, yOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +142,7 @@ public class InputHandler {
|
|||||||
if (GraphicsBackend.getWindowHandle() != window)
|
if (GraphicsBackend.getWindowHandle() != window)
|
||||||
return;
|
return;
|
||||||
THE_WHEEL_SCROLL_EVENT.initialize(xoffset, yoffset);
|
THE_WHEEL_SCROLL_EVENT.initialize(xoffset, yoffset);
|
||||||
dispatch(THE_WHEEL_SCROLL_EVENT);
|
INPUT_EVENT_BUS.post(THE_WHEEL_SCROLL_EVENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FrameResizeEvent
|
// FrameResizeEvent
|
||||||
@ -152,6 +155,7 @@ public class InputHandler {
|
|||||||
|
|
||||||
public void initialize(int width, int height) {
|
public void initialize(int width, int height) {
|
||||||
this.setTime(GraphicsInterface.getTime());
|
this.setTime(GraphicsInterface.getTime());
|
||||||
|
this.setConsumed(false);
|
||||||
this.getNewSize().set(width, height);
|
this.getNewSize().set(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,17 +171,17 @@ public class InputHandler {
|
|||||||
int height
|
int height
|
||||||
) {
|
) {
|
||||||
THE_FRAME_RESIZE_EVENT.initialize(width, height);
|
THE_FRAME_RESIZE_EVENT.initialize(width, height);
|
||||||
dispatch(THE_FRAME_RESIZE_EVENT);
|
INPUT_EVENT_BUS.post(THE_FRAME_RESIZE_EVENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
|
|
||||||
private static void dispatch(InputEvent event) {
|
|
||||||
INPUT_EVENT_BUS.post(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void register(Object listener) {
|
public static void register(Object listener) {
|
||||||
INPUT_EVENT_BUS.register(listener);
|
INPUT_EVENT_BUS.register(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void unregister(Object listener) {
|
||||||
|
INPUT_EVENT_BUS.unregister(listener);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,11 @@ import static org.lwjgl.system.MemoryUtil.*;
|
|||||||
|
|
||||||
import org.lwjgl.opengl.GL;
|
import org.lwjgl.opengl.GL;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.GUI;
|
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 {
|
class LWJGLInitializer {
|
||||||
|
|
||||||
@ -107,7 +111,20 @@ class LWJGLInitializer {
|
|||||||
|
|
||||||
glfwSetScrollCallback(handle, InputHandler::handleWheelScroll);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -54,18 +54,17 @@ public abstract class BasicButton extends Component {
|
|||||||
reassembleAt(ARTrigger.HOVER, ARTrigger.FOCUS, ARTrigger.ENABLE);
|
reassembleAt(ARTrigger.HOVER, ARTrigger.FOCUS, ARTrigger.ENABLE);
|
||||||
|
|
||||||
// Click triggers
|
// Click triggers
|
||||||
addListener(KeyEvent.class, e -> {
|
addInputListener(KeyEvent.class, e -> {
|
||||||
if (e.isRepeat()) {
|
if (e.isRepeat())
|
||||||
return false;
|
return;
|
||||||
} else if (
|
|
||||||
|
if (
|
||||||
e.isLeftMouseButton() ||
|
e.isLeftMouseButton() ||
|
||||||
e.getKey() == GLFW.GLFW_KEY_SPACE ||
|
e.getKey() == GLFW.GLFW_KEY_SPACE ||
|
||||||
e.getKey() == GLFW.GLFW_KEY_ENTER
|
e.getKey() == GLFW.GLFW_KEY_ENTER
|
||||||
) {
|
) {
|
||||||
setPressed(e.isPress());
|
setPressed(e.isPress());
|
||||||
return true;
|
e.consume();
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -25,8 +25,6 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
import com.google.common.eventbus.EventBus;
|
import com.google.common.eventbus.EventBus;
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
|
||||||
@ -39,9 +37,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.FocusEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
|
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.InputEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
|
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.InputListener;
|
import ru.windcorp.progressia.client.graphics.input.bus.InputListener;
|
||||||
import ru.windcorp.progressia.common.util.Named;
|
import ru.windcorp.progressia.common.util.Named;
|
||||||
@ -55,7 +54,7 @@ public class Component extends Named {
|
|||||||
private Component parent = null;
|
private Component parent = null;
|
||||||
|
|
||||||
private EventBus eventBus = null;
|
private EventBus eventBus = null;
|
||||||
private InputBus inputBus = null;
|
private final InputBus inputBus = new InputBus(this);
|
||||||
|
|
||||||
private int x, y;
|
private int x, y;
|
||||||
private int width, height;
|
private int width, height;
|
||||||
@ -76,6 +75,9 @@ public class Component extends Named {
|
|||||||
|
|
||||||
public Component(String name) {
|
public Component(String name) {
|
||||||
super(name);
|
super(name);
|
||||||
|
|
||||||
|
// Update hover flag when cursor moves
|
||||||
|
addInputListener(CursorMoveEvent.class, this::updateHoverFlag, InputBus.Option.ALWAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Component getParent() {
|
public Component getParent() {
|
||||||
@ -522,6 +524,10 @@ public class Component extends Named {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateHoverFlag(CursorMoveEvent e) {
|
||||||
|
setHovered(contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY()));
|
||||||
|
}
|
||||||
|
|
||||||
public void addListener(Object listener) {
|
public void addListener(Object listener) {
|
||||||
if (eventBus == null) {
|
if (eventBus == null) {
|
||||||
eventBus = ReportingEventBus.create(getName());
|
eventBus = ReportingEventBus.create(getName());
|
||||||
@ -542,121 +548,28 @@ public class Component extends Named {
|
|||||||
eventBus.post(event);
|
eventBus.post(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends InputEvent> void addListener(
|
public <T extends InputEvent> void addInputListener(Class<? extends T> type, InputListener<T> listener, InputBus.Option... options) {
|
||||||
Class<? extends T> type,
|
inputBus.register(type, listener, options);
|
||||||
boolean handlesConsumed,
|
|
||||||
InputListener<T> listener
|
|
||||||
) {
|
|
||||||
if (inputBus == null) {
|
|
||||||
inputBus = new InputBus();
|
|
||||||
}
|
|
||||||
|
|
||||||
inputBus.register(type, handlesConsumed, listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends InputEvent> void addListener(Class<? extends T> type, InputListener<T> listener) {
|
public void addKeyListener(KeyMatcher matcher, InputListener<? super KeyEvent> listener, InputBus.Option... options) {
|
||||||
if (inputBus == null) {
|
inputBus.register(matcher, listener, options);
|
||||||
inputBus = new InputBus();
|
|
||||||
}
|
|
||||||
|
|
||||||
inputBus.register(type, listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeListener(InputListener<?> listener) {
|
public void removeInputListener(InputListener<?> listener) {
|
||||||
if (inputBus != null) {
|
if (inputBus != null) {
|
||||||
inputBus.unregister(listener);
|
inputBus.unregister(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleInput(Input input) {
|
InputBus getInputBus() {
|
||||||
if (inputBus != null && isEnabled()) {
|
return inputBus;
|
||||||
inputBus.dispatch(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispatchInput(Input input) {
|
|
||||||
try {
|
|
||||||
switch (input.getTarget()) {
|
|
||||||
case FOCUSED:
|
|
||||||
dispatchInputToFocused(input);
|
|
||||||
break;
|
|
||||||
case HOVERED:
|
|
||||||
dispatchInputToHovered(input);
|
|
||||||
break;
|
|
||||||
case ALL:
|
|
||||||
default:
|
|
||||||
dispatchInputToAll(input);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw CrashReports.report(e, "Could not dispatch input to Component %s", this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void dispatchInputToFocused(Input input) {
|
|
||||||
Component c = findFocused();
|
|
||||||
|
|
||||||
if (c == null)
|
|
||||||
return;
|
|
||||||
if (attemptFocusTransfer(input, c))
|
|
||||||
return;
|
|
||||||
|
|
||||||
while (c != null) {
|
|
||||||
c.handleInput(input);
|
|
||||||
c = c.getParent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void dispatchInputToHovered(Input input) {
|
|
||||||
getChildren().forEach(child -> {
|
|
||||||
if (child.containsCursor()) {
|
|
||||||
child.setHovered(true);
|
|
||||||
|
|
||||||
if (!input.isConsumed()) {
|
|
||||||
child.dispatchInput(input);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
child.setHovered(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
handleInput(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void dispatchInputToAll(Input input) {
|
|
||||||
getChildren().forEach(c -> c.dispatchInput(input));
|
|
||||||
handleInput(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean attemptFocusTransfer(Input input, Component focused) {
|
|
||||||
if (input.isConsumed())
|
|
||||||
return false;
|
|
||||||
if (!(input.getEvent() instanceof KeyEvent))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
KeyEvent keyInput = (KeyEvent) input.getEvent();
|
|
||||||
|
|
||||||
if (keyInput.getKey() == GLFW.GLFW_KEY_TAB && !keyInput.isRelease()) {
|
|
||||||
input.consume();
|
|
||||||
if (keyInput.hasShift()) {
|
|
||||||
focused.focusPrevious();
|
|
||||||
} else {
|
|
||||||
focused.focusNext();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean contains(int x, int y) {
|
public synchronized boolean contains(int x, int y) {
|
||||||
return x >= getX() && x < getX() + getWidth() && y >= getY() && y < getY() + getHeight();
|
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() {
|
public void requestReassembly() {
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
parent.requestReassembly();
|
parent.requestReassembly();
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -18,9 +18,16 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.gui;
|
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.AssembledFlatLayer;
|
||||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
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 {
|
public abstract class GUILayer extends AssembledFlatLayer {
|
||||||
|
|
||||||
@ -33,7 +40,9 @@ public abstract class GUILayer extends AssembledFlatLayer {
|
|||||||
|
|
||||||
public GUILayer(String name, Layout layout) {
|
public GUILayer(String name, Layout layout) {
|
||||||
super(name);
|
super(name);
|
||||||
|
|
||||||
getRoot().setLayout(layout);
|
getRoot().setLayout(layout);
|
||||||
|
getRoot().addInputListener(KeyEvent.class, this::attemptFocusTransfer, InputBus.Option.IGNORE_FOCUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Component getRoot() {
|
public Component getRoot() {
|
||||||
@ -47,9 +56,81 @@ public abstract class GUILayer extends AssembledFlatLayer {
|
|||||||
getRoot().assemble(target);
|
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
|
@Override
|
||||||
protected void handleInput(Input input) {
|
public void handleInput(InputEvent event) {
|
||||||
getRoot().dispatchInput(input);
|
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
|
@Override
|
||||||
|
@ -104,26 +104,22 @@ public class RadioButton extends BasicButton {
|
|||||||
group.addChild(basicChild);
|
group.addChild(basicChild);
|
||||||
addChild(group);
|
addChild(group);
|
||||||
|
|
||||||
addListener(KeyEvent.class, e -> {
|
addInputListener(KeyEvent.class, e -> {
|
||||||
if (e.isRelease())
|
if (e.isRelease()) return;
|
||||||
return false;
|
|
||||||
|
|
||||||
if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) {
|
if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) {
|
||||||
if (this.group != null) {
|
if (this.group != null) {
|
||||||
this.group.selectPrevious();
|
this.group.selectPrevious();
|
||||||
this.group.getSelected().takeFocus();
|
this.group.getSelected().takeFocus();
|
||||||
}
|
}
|
||||||
|
e.consume();
|
||||||
return true;
|
|
||||||
} else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
|
} else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
|
||||||
if (this.group != null) {
|
if (this.group != null) {
|
||||||
this.group.selectNext();
|
this.group.selectNext();
|
||||||
this.group.getSelected().takeFocus();
|
this.group.getSelected().takeFocus();
|
||||||
}
|
}
|
||||||
return true;
|
e.consume();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
addAction(b -> setChecked(true));
|
addAction(b -> setChecked(true));
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -15,48 +15,30 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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 {
|
public DragStopEvent(Component component, Vec2d totalChange) {
|
||||||
FOCUSED, HOVERED, ALL
|
super(component);
|
||||||
|
this.totalChange.set(totalChange.x, totalChange.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InputEvent event;
|
public Vec2d getTotalChange() {
|
||||||
|
return totalChange;
|
||||||
private boolean isConsumed;
|
|
||||||
|
|
||||||
private Target target;
|
|
||||||
|
|
||||||
protected void initialize(InputEvent event, Target target) {
|
|
||||||
this.event = event;
|
|
||||||
this.target = target;
|
|
||||||
|
|
||||||
this.isConsumed = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public InputEvent getEvent() {
|
public double getTotalChangeX() {
|
||||||
return event;
|
return totalChange.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isConsumed() {
|
public double getTotalChangeY() {
|
||||||
return isConsumed;
|
return totalChange.y;
|
||||||
}
|
|
||||||
|
|
||||||
public void setConsumed(boolean isConsumed) {
|
|
||||||
this.isConsumed = isConsumed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void consume() {
|
|
||||||
setConsumed(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Target getTarget() {
|
|
||||||
return target;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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.gui.layout.LayoutVertical;
|
||||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
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.MutableString;
|
||||||
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
||||||
|
|
||||||
@ -97,11 +96,9 @@ public class MenuLayer extends GUILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleInput(Input input) {
|
public void handleInput(InputEvent event) {
|
||||||
|
|
||||||
if (!input.isConsumed()) {
|
|
||||||
InputEvent event = input.getEvent();
|
|
||||||
|
|
||||||
|
if (!event.isConsumed()) {
|
||||||
if (event instanceof KeyEvent) {
|
if (event instanceof KeyEvent) {
|
||||||
KeyEvent keyEvent = (KeyEvent) event;
|
KeyEvent keyEvent = (KeyEvent) event;
|
||||||
if (keyEvent.isPress() && keyEvent.getKey() == GLFW.GLFW_KEY_ESCAPE) {
|
if (keyEvent.isPress() && keyEvent.getKey() == GLFW.GLFW_KEY_ESCAPE) {
|
||||||
@ -110,8 +107,8 @@ public class MenuLayer extends GUILayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
super.handleInput(input);
|
super.handleInput(event);
|
||||||
input.consume();
|
event.consume();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.input;
|
package ru.windcorp.progressia.client.graphics.input;
|
||||||
|
|
||||||
import glm.vec._2.Vec2;
|
|
||||||
import glm.vec._2.d.Vec2d;
|
import glm.vec._2.d.Vec2d;
|
||||||
|
|
||||||
public class CursorMoveEvent extends CursorEvent {
|
public class CursorMoveEvent extends CursorEvent {
|
||||||
@ -81,7 +80,10 @@ public class CursorMoveEvent extends CursorEvent {
|
|||||||
return getNewY() - getPreviousY();
|
return getNewY() - getPreviousY();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vec2 getChange(Vec2 result) {
|
public Vec2d getChange(Vec2d result) {
|
||||||
|
if (result == null) {
|
||||||
|
result = new Vec2d();
|
||||||
|
}
|
||||||
return result.set(getChangeX(), getChangeY());
|
return result.set(getChangeX(), getChangeY());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,10 +18,32 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.input;
|
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 {
|
public abstract class InputEvent {
|
||||||
|
|
||||||
private double time;
|
private double time;
|
||||||
|
|
||||||
|
private boolean isConsumed = false;
|
||||||
|
|
||||||
public InputEvent(double time) {
|
public InputEvent(double time) {
|
||||||
this.time = time;
|
this.time = time;
|
||||||
}
|
}
|
||||||
@ -36,4 +58,16 @@ public abstract class InputEvent {
|
|||||||
|
|
||||||
public abstract InputEvent snapshot();
|
public abstract InputEvent snapshot();
|
||||||
|
|
||||||
|
public boolean isConsumed() {
|
||||||
|
return isConsumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConsumed(boolean isConsumed) {
|
||||||
|
this.isConsumed = isConsumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void consume() {
|
||||||
|
setConsumed(true);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,16 +18,75 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.input;
|
package ru.windcorp.progressia.client.graphics.input;
|
||||||
|
|
||||||
import java.util.function.Predicate;
|
import java.util.Map;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
public class KeyMatcher {
|
public class KeyMatcher {
|
||||||
|
|
||||||
|
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 key;
|
||||||
private final int mods;
|
private final int mods;
|
||||||
|
|
||||||
protected KeyMatcher(int key, int mods) {
|
public KeyMatcher(int key, int mods) {
|
||||||
|
this.key = key;
|
||||||
|
this.mods = mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyMatcher(int key) {
|
||||||
|
this.key = key;
|
||||||
|
this.mods = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyMatcher(String declar) {
|
||||||
|
String[] tokens = DECLAR_SPLIT_REGEX.split(declar);
|
||||||
|
if (tokens.length == 0) {
|
||||||
|
throw new IllegalArgumentException("No tokens found in \"" + declar + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
int key = -1;
|
||||||
|
int mods = 0;
|
||||||
|
|
||||||
|
for (String token : tokens) {
|
||||||
|
token = token.toUpperCase();
|
||||||
|
|
||||||
|
if (MOD_TOKENS.containsKey(token)) {
|
||||||
|
int mod = MOD_TOKENS.get(token);
|
||||||
|
if ((mods & mod) != 0) {
|
||||||
|
throw new IllegalArgumentException("Duplicate modifier \"" + token + "\" in \"" + declar + "\"");
|
||||||
|
}
|
||||||
|
mods |= mod;
|
||||||
|
} else if (key != -1) {
|
||||||
|
throw new IllegalArgumentException("Too many non-modifier tokens in \"" + declar + "\": maximum one key, first offender: \"" + token + "\"");
|
||||||
|
} else {
|
||||||
|
token = token.replace(' ', '_');
|
||||||
|
|
||||||
|
if (token.startsWith("KEYPAD_")) {
|
||||||
|
token = "KP_" + token.substring("KEYPAD_".length());
|
||||||
|
}
|
||||||
|
|
||||||
|
key = Keys.getCode(token);
|
||||||
|
|
||||||
|
if (key == -1) {
|
||||||
|
throw new IllegalArgumentException("Unknown token \"" + token + "\" in \"" + declar + "\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.mods = mods;
|
this.mods = mods;
|
||||||
}
|
}
|
||||||
@ -43,6 +102,15 @@ public class KeyMatcher {
|
|||||||
return true;
|
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() {
|
public int getKey() {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
@ -51,48 +119,24 @@ public class KeyMatcher {
|
|||||||
return mods;
|
return mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KeyMatcher.Builder of(int key) {
|
public KeyMatcher with(int modifier) {
|
||||||
return new KeyMatcher.Builder(key);
|
return new KeyMatcher(key, mods | modifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public KeyMatcher withShift() {
|
||||||
|
return with(GLFW.GLFW_MOD_SHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
private final int key;
|
public KeyMatcher withCtrl() {
|
||||||
private int mods = 0;
|
return with(GLFW.GLFW_MOD_CONTROL);
|
||||||
|
}
|
||||||
|
|
||||||
public Builder(int key) {
|
public KeyMatcher withAlt() {
|
||||||
this.key = key;
|
return with(GLFW.GLFW_MOD_ALT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder with(int modifier) {
|
|
||||||
this.mods += modifier;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withShift() {
|
|
||||||
return with(GLFW.GLFW_MOD_SHIFT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withCtrl() {
|
|
||||||
return with(GLFW.GLFW_MOD_CONTROL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withAlt() {
|
|
||||||
return with(GLFW.GLFW_MOD_ALT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withSuper() {
|
|
||||||
return with(GLFW.GLFW_MOD_SUPER);
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyMatcher build() {
|
|
||||||
return new KeyMatcher(key, mods);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Predicate<KeyEvent> matcher() {
|
|
||||||
return build()::matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public KeyMatcher withSuper() {
|
||||||
|
return with(GLFW.GLFW_MOD_SUPER);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,7 @@ public class Keys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int getCode(String internalName) {
|
public static int getCode(String internalName) {
|
||||||
if (NAMES_TO_CODES.containsKey(internalName)) {
|
if (!NAMES_TO_CODES.containsKey(internalName)) {
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
return NAMES_TO_CODES.get(internalName);
|
return NAMES_TO_CODES.get(internalName);
|
||||||
|
@ -20,68 +20,396 @@ package ru.windcorp.progressia.client.graphics.input.bus;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
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.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 {
|
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 Class<?> type;
|
||||||
private final boolean handleConsumed;
|
|
||||||
|
private final boolean dropIfConsumed;
|
||||||
|
private final YesNoDefault dropIfNotHovered;
|
||||||
|
private final YesNoDefault dropIfNotFocused;
|
||||||
|
|
||||||
private final InputListener<?> listener;
|
private final InputListener<?> listener;
|
||||||
|
|
||||||
public WrappedListener(
|
public WrappedListener(
|
||||||
Class<?> type,
|
Class<?> type,
|
||||||
boolean handleConsumed,
|
boolean dropIfConsumed,
|
||||||
|
YesNoDefault dropIfNotHovered,
|
||||||
|
YesNoDefault dropIfNotFocused,
|
||||||
InputListener<?> listener
|
InputListener<?> listener
|
||||||
) {
|
) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.handleConsumed = handleConsumed;
|
this.dropIfConsumed = dropIfConsumed;
|
||||||
|
this.dropIfNotHovered = dropIfNotHovered;
|
||||||
|
this.dropIfNotFocused = dropIfNotFocused;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handles(Input input) {
|
private boolean handles(InputEvent input) {
|
||||||
return (!input.isConsumed() || handleConsumed) &&
|
if (dropIfConsumed && input.isConsumed())
|
||||||
type.isInstance(input.getEvent());
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
public void handle(Input input) {
|
public void handle(InputEvent event) {
|
||||||
if (handles(input)) {
|
if (handles(event)) {
|
||||||
boolean consumed = ((InputListener<InputEvent>) listener)
|
// A runtime check of types has been performed; this is safe.
|
||||||
.handle(
|
InputListener<InputEvent> castListener = (InputListener<InputEvent>) listener;
|
||||||
(InputEvent) type.cast(input.getEvent())
|
|
||||||
);
|
|
||||||
|
|
||||||
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);
|
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(
|
public <T extends InputEvent> void register(
|
||||||
Class<? extends T> type,
|
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,
|
* Registers a {@link KeyEvent} listener on this bus. An event has to match
|
||||||
InputListener<T> listener
|
* the provided {@link KeyMatcher} to be delivered to the listener.
|
||||||
) {
|
* <p>
|
||||||
register(type, false, listener);
|
* Zero or more {@link Option}s may be specified to enable or disable the
|
||||||
|
* processing of certain events in certain circumstances. See
|
||||||
|
* {@linkplain InputBus class description} for a detailed breakdown of the
|
||||||
|
* checks performed and the effects of various options. When providing
|
||||||
|
* options to this method, later options override the effects of previous
|
||||||
|
* options.
|
||||||
|
* <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) {
|
public void unregister(InputListener<?> listener) {
|
||||||
|
if (listener == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
listeners.removeIf(l -> l.listener == listener);
|
listeners.removeIf(l -> l.listener == listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,6 @@ import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
|||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface InputListener<T extends InputEvent> {
|
public interface InputListener<T extends InputEvent> {
|
||||||
|
|
||||||
boolean handle(T event);
|
void handle(T event);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -92,4 +92,13 @@ public class EntityAnchor implements Anchor {
|
|||||||
return modes;
|
return modes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EntityData getEntity() {
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Anchor for entity " + entity;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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.Layer;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.FaceCulling;
|
import ru.windcorp.progressia.client.graphics.backend.FaceCulling;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
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.Renderable;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shapes.PppBuilder;
|
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.GravityModel;
|
||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
import ru.windcorp.progressia.test.CollisionModelRenderer;
|
import ru.windcorp.progressia.test.CollisionModelRenderer;
|
||||||
import ru.windcorp.progressia.test.TestPlayerControls;
|
import ru.windcorp.progressia.test.controls.TestPlayerControls;
|
||||||
|
|
||||||
public class LayerWorld extends Layer {
|
public class LayerWorld extends Layer {
|
||||||
|
|
||||||
@ -214,6 +214,9 @@ public class LayerWorld extends Layer {
|
|||||||
if (ClientState.getInstance().getLocalPlayer().getEntity() == entity && tmp_testControls.isFlying()) {
|
if (ClientState.getInstance().getLocalPlayer().getEntity() == entity && tmp_testControls.isFlying()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (entity.getId().equals("Test:NoclipCamera")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Vec3 gravitationalAcceleration = Vectors.grab3();
|
Vec3 gravitationalAcceleration = Vectors.grab3();
|
||||||
gm.getGravity(entity.getPosition(), gravitationalAcceleration);
|
gm.getGravity(entity.getPosition(), gravitationalAcceleration);
|
||||||
@ -225,14 +228,9 @@ public class LayerWorld extends Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleInput(Input input) {
|
public void handleInput(InputEvent event) {
|
||||||
if (input.isConsumed())
|
if (!event.isConsumed()) {
|
||||||
return;
|
inputBasedControls.handleInput(event);
|
||||||
|
|
||||||
tmp_testControls.handleInput(input);
|
|
||||||
|
|
||||||
if (!input.isConsumed()) {
|
|
||||||
inputBasedControls.handleInput(input);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +73,12 @@ public class Localizer {
|
|||||||
return language;
|
return language;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getLanguages() {
|
||||||
|
List<String> result = new ArrayList<>(langList.keySet());
|
||||||
|
result.sort(null);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized String getValue(String key) {
|
public synchronized String getValue(String key) {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
throw new IllegalStateException("Localizer not yet initialized");
|
throw new IllegalStateException("Localizer not yet initialized");
|
||||||
|
@ -105,6 +105,9 @@ public class Collider {
|
|||||||
// For every pair of colls
|
// For every pair of colls
|
||||||
for (int i = 0; i < colls.size(); ++i) {
|
for (int i = 0; i < colls.size(); ++i) {
|
||||||
Collideable a = colls.get(i);
|
Collideable a = colls.get(i);
|
||||||
|
if (a.getCollisionModel() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
tuneWorldCollisionHelper(a, tickLength, world, workspace);
|
tuneWorldCollisionHelper(a, tickLength, world, workspace);
|
||||||
|
|
||||||
@ -115,6 +118,10 @@ public class Collider {
|
|||||||
|
|
||||||
for (int j = i + 1; j < colls.size(); ++j) {
|
for (int j = i + 1; j < colls.size(); ++j) {
|
||||||
Collideable b = colls.get(j);
|
Collideable b = colls.get(j);
|
||||||
|
if (b.getCollisionModel() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
Collision collision = getCollision(a, b, tickLength, workspace);
|
Collision collision = getCollision(a, b, tickLength, workspace);
|
||||||
result = workspace.updateLatestCollision(result, collision);
|
result = workspace.updateLatestCollision(result, collision);
|
||||||
}
|
}
|
||||||
|
@ -235,7 +235,7 @@ public abstract class StatefulObject extends Namespaced implements Encodable {
|
|||||||
|
|
||||||
StatefulObject statefulObj = (StatefulObject) obj;
|
StatefulObject statefulObj = (StatefulObject) obj;
|
||||||
|
|
||||||
if (statefulObj.getId().equals(this.getId()))
|
if (!statefulObj.getId().equals(this.getId()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -380,4 +380,14 @@ public class EntityData extends StatefulObject implements Collideable, EntityGen
|
|||||||
super.read(input, context);
|
super.read(input, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return this == obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Long.hashCode(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
|||||||
import ru.windcorp.progressia.server.Player;
|
import ru.windcorp.progressia.server.Player;
|
||||||
import ru.windcorp.progressia.server.Server;
|
import ru.windcorp.progressia.server.Server;
|
||||||
import ru.windcorp.progressia.server.ServerState;
|
import ru.windcorp.progressia.server.ServerState;
|
||||||
|
import ru.windcorp.progressia.test.controls.TestPlayerControls;
|
||||||
|
|
||||||
public class LayerButtonTest extends MenuLayer {
|
public class LayerButtonTest extends MenuLayer {
|
||||||
|
|
||||||
|
261
src/main/java/ru/windcorp/progressia/test/LayerDebug.java
Executable file
261
src/main/java/ru/windcorp/progressia/test/LayerDebug.java
Executable 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
@ -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.backend.GraphicsInterface;
|
||||||
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer;
|
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer;
|
||||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
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.KeyEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
|
||||||
|
|
||||||
public class LayerTestUI extends AssembledFlatLayer {
|
public class LayerTestUI extends AssembledFlatLayer {
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ public class LayerTestUI extends AssembledFlatLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleInput(Input input) {
|
public void handleInput(InputEvent event) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,15 +28,7 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
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.*;
|
|
||||||
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.world.block.*;
|
import ru.windcorp.progressia.client.world.block.*;
|
||||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerRegistry;
|
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerRegistry;
|
||||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSimple;
|
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSimple;
|
||||||
@ -45,25 +37,19 @@ import ru.windcorp.progressia.client.world.entity.*;
|
|||||||
import ru.windcorp.progressia.client.world.tile.*;
|
import ru.windcorp.progressia.client.world.tile.*;
|
||||||
import ru.windcorp.progressia.common.collision.AABB;
|
import ru.windcorp.progressia.common.collision.AABB;
|
||||||
import ru.windcorp.progressia.common.collision.CollisionModel;
|
import ru.windcorp.progressia.common.collision.CollisionModel;
|
||||||
import ru.windcorp.progressia.common.comms.controls.*;
|
|
||||||
import ru.windcorp.progressia.common.state.StatefulObjectRegistry.Factory;
|
import ru.windcorp.progressia.common.state.StatefulObjectRegistry.Factory;
|
||||||
import ru.windcorp.progressia.common.world.GravityModelRegistry;
|
import ru.windcorp.progressia.common.world.GravityModelRegistry;
|
||||||
import ru.windcorp.progressia.common.world.block.*;
|
import ru.windcorp.progressia.common.world.block.*;
|
||||||
import ru.windcorp.progressia.common.world.entity.*;
|
import ru.windcorp.progressia.common.world.entity.*;
|
||||||
import ru.windcorp.progressia.common.world.io.ChunkIO;
|
import ru.windcorp.progressia.common.world.io.ChunkIO;
|
||||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
|
||||||
import ru.windcorp.progressia.common.world.tile.*;
|
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.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.entity.*;
|
||||||
import ru.windcorp.progressia.server.world.generation.planet.PlanetGravityModel;
|
import ru.windcorp.progressia.server.world.generation.planet.PlanetGravityModel;
|
||||||
import ru.windcorp.progressia.server.world.tile.*;
|
import ru.windcorp.progressia.server.world.tile.*;
|
||||||
import ru.windcorp.progressia.test.Flowers.FlowerVariant;
|
import ru.windcorp.progressia.test.Flowers.FlowerVariant;
|
||||||
import ru.windcorp.progressia.test.Rocks.RockType;
|
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.gen.TestGravityModel;
|
||||||
import ru.windcorp.progressia.test.trees.BlockRenderLeavesHazel;
|
import ru.windcorp.progressia.test.trees.BlockRenderLeavesHazel;
|
||||||
import ru.windcorp.progressia.test.trees.BlockRenderLeavesPine;
|
import ru.windcorp.progressia.test.trees.BlockRenderLeavesPine;
|
||||||
@ -288,55 +274,9 @@ public class TestContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void regsiterControls() {
|
private static void regsiterControls() {
|
||||||
ControlDataRegistry data = ControlDataRegistry.getInstance();
|
TestPlayerControls.getInstance().registerControls();
|
||||||
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.of(GLFW.GLFW_MOUSE_BUTTON_LEFT).matcher(),
|
|
||||||
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.of(GLFW.GLFW_MOUSE_BUTTON_RIGHT).matcher(),
|
|
||||||
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.of(GLFW.GLFW_MOUSE_BUTTON_RIGHT).matcher(),
|
|
||||||
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(GLFW.GLFW_KEY_M).matcher()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void register(BlockData x) {
|
private static void register(BlockData x) {
|
||||||
@ -392,95 +332,6 @@ public class TestContent {
|
|||||||
EntityLogicRegistry.getInstance().register(x);
|
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 registerMisc() {
|
private static void registerMisc() {
|
||||||
ChunkIO.registerCodec(new TestChunkCodec());
|
ChunkIO.registerCodec(new TestChunkCodec());
|
||||||
|
|
||||||
|
@ -110,6 +110,7 @@ public class TestMusicPlayer implements Runnable {
|
|||||||
String file = it.next().toString();
|
String file = it.next().toString();
|
||||||
if (!file.endsWith(".ogg") && !file.endsWith(".oga")) {
|
if (!file.endsWith(".ogg") && !file.endsWith(".oga")) {
|
||||||
LogManager.getLogger().warn("Skipping " + file + ": not .ogg nor .oga");
|
LogManager.getLogger().warn("Skipping " + file + ": not .ogg nor .oga");
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
String id = "Progressia:Music" + (i++);
|
String id = "Progressia:Music" + (i++);
|
||||||
|
@ -1,469 +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.tile.TileData;
|
|
||||||
import ru.windcorp.progressia.server.ServerState;
|
|
||||||
|
|
||||||
public class TestPlayerControls {
|
|
||||||
|
|
||||||
private static 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 int selectedBlock = 0;
|
|
||||||
private int selectedTile = 0;
|
|
||||||
private boolean isBlockSelected = true;
|
|
||||||
|
|
||||||
private LayerTestGUI debugLayer = null;
|
|
||||||
private Runnable updateCallback = null;
|
|
||||||
|
|
||||||
public static void resetInstance() {
|
|
||||||
instance = new TestPlayerControls();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void applyPlayerControls() {
|
|
||||||
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
|
||||||
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().getData()
|
|
||||||
.getEntity(TestContent.PLAYER_ENTITY_ID);
|
|
||||||
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;
|
|
||||||
|
|
||||||
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 EntityData 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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 glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.common.comms.controls.ControlData;
|
import ru.windcorp.progressia.common.comms.controls.ControlData;
|
@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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 glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.common.comms.controls.ControlData;
|
import ru.windcorp.progressia.common.comms.controls.ControlData;
|
@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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 glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.common.comms.controls.ControlData;
|
import ru.windcorp.progressia.common.comms.controls.ControlData;
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,316 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
import ru.windcorp.progressia.test.TestContent;
|
||||||
|
|
||||||
|
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(TestContent.PLAYER_ENTITY_ID);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,207 @@
|
|||||||
|
/*
|
||||||
|
* 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.LayerDebug;
|
||||||
|
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 LayerDebug debugLayer = null;
|
||||||
|
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void resetInstance() {
|
||||||
|
INSTANCE.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset() {
|
||||||
|
movementControls.reset();
|
||||||
|
cameraControls.reset();
|
||||||
|
interactionControls.reset();
|
||||||
|
|
||||||
|
debugLayer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyPlayerControls() {
|
||||||
|
movementControls.applyPlayerControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerControls() {
|
||||||
|
movementControls.registerControls();
|
||||||
|
cameraControls.registerControls();
|
||||||
|
interactionControls.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,
|
||||||
|
this::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 toggleDebugLayer() {
|
||||||
|
if (debugLayer == null) {
|
||||||
|
this.debugLayer = new LayerDebug();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUI.getLayers().contains(debugLayer)) {
|
||||||
|
GUI.removeLayer(debugLayer);
|
||||||
|
} else {
|
||||||
|
GUI.addTopLayer(debugLayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -4,22 +4,16 @@ LayerAbout.Title = Progressia
|
|||||||
LayerAbout.Version = Version: %s
|
LayerAbout.Version = Version: %s
|
||||||
LayerAbout.DebugHint = Debug GUI: F3
|
LayerAbout.DebugHint = Debug GUI: F3
|
||||||
|
|
||||||
LayerTestGUI.IsFlyingDisplay = Flying: %5s (Space bar x2)
|
LayerDebug.FPSDisplay = FPS:
|
||||||
LayerTestGUI.IsSprintingDisplay = Sprinting: %5s (W x2)
|
LayerDebug.TPSDisplay = TPS:
|
||||||
LayerTestGUI.CameraModeDisplay = Camera mode: %5d (F5)
|
LayerDebug.TPSDisplay.NA = TPS: n/a
|
||||||
LayerTestGUI.LanguageDisplay = Language: %5s (L)
|
LayerDebug.ChunkStatsDisplay = Chunks vis/pnd/load:
|
||||||
LayerTestGUI.FPSDisplay = FPS:
|
LayerDebug.PosDisplay = Pos:
|
||||||
LayerTestGUI.TPSDisplay = TPS:
|
LayerDebug.PosDisplay.NA.Client = Pos: client n/a
|
||||||
LayerTestGUI.TPSDisplay.NA = TPS: n/a
|
LayerDebug.PosDisplay.NA.Entity = Pos: entity n/a
|
||||||
LayerTestGUI.ChunkStatsDisplay = Chunks vis/pnd/load:
|
LayerDebug.SelectedBlockDisplay = %s Block: %s
|
||||||
LayerTestGUI.PosDisplay = Pos:
|
LayerDebug.SelectedTileDisplay = %s Tile: %s
|
||||||
LayerTestGUI.PosDisplay.NA.Client = Pos: client n/a
|
LayerDebug.PlacementModeHint = (Blocks %s Tiles: Ctrl + Mouse Wheel)
|
||||||
LayerTestGUI.PosDisplay.NA.Entity = Pos: entity n/a
|
|
||||||
LayerTestGUI.SelectedBlockDisplay = %s Block: %s
|
|
||||||
LayerTestGUI.SelectedTileDisplay = %s Tile: %s
|
|
||||||
LayerTestGUI.PlacementModeHint = (Blocks %s Tiles: Ctrl + Mouse Wheel)
|
|
||||||
LayerTestGUI.IsFullscreen = Fullscreen: %5s (F11)
|
|
||||||
LayerTestGUI.IsVSync = VSync: %5s (F12)
|
|
||||||
|
|
||||||
LayerButtonTest.Title = Button Test
|
LayerButtonTest.Title = Button Test
|
||||||
LayerButtonTest.Return = Back To Menu
|
LayerButtonTest.Return = Back To Menu
|
||||||
|
@ -4,22 +4,16 @@ LayerAbout.Title = Прогрессия
|
|||||||
LayerAbout.Version = Версия: %s
|
LayerAbout.Version = Версия: %s
|
||||||
LayerAbout.DebugHint = Отладочный GUI: F3
|
LayerAbout.DebugHint = Отладочный GUI: F3
|
||||||
|
|
||||||
LayerTestGUI.IsFlyingDisplay = Полёт: %5s (Пробел x2)
|
LayerDebug.FPSDisplay = FPS:
|
||||||
LayerTestGUI.IsSprintingDisplay = Бег: %5s (W x2)
|
LayerDebug.TPSDisplay = TPS:
|
||||||
LayerTestGUI.CameraModeDisplay = Камера: %5d (F5)
|
LayerDebug.TPSDisplay.NA = TPS: н/д
|
||||||
LayerTestGUI.LanguageDisplay = Язык: %5s (L)
|
LayerDebug.ChunkStatsDisplay = Чанки вид/очр/загр:
|
||||||
LayerTestGUI.FPSDisplay = FPS:
|
LayerDebug.PosDisplay = Поз:
|
||||||
LayerTestGUI.TPSDisplay = TPS:
|
LayerDebug.PosDisplay.NA.Client = Поз: клиент н/д
|
||||||
LayerTestGUI.TPSDisplay.NA = TPS: н/д
|
LayerDebug.PosDisplay.NA.Entity = Поз: сущность н/д
|
||||||
LayerTestGUI.ChunkStatsDisplay = Чанки вид/очр/загр:
|
LayerDebug.SelectedBlockDisplay = %s Блок: %s
|
||||||
LayerTestGUI.PosDisplay = Поз:
|
LayerDebug.SelectedTileDisplay = %s Плитка: %s
|
||||||
LayerTestGUI.PosDisplay.NA.Client = Поз: клиент н/д
|
LayerDebug.PlacementModeHint = (Блок %s плитки: Ctrl + прокрутка)
|
||||||
LayerTestGUI.PosDisplay.NA.Entity = Поз: сущность н/д
|
|
||||||
LayerTestGUI.SelectedBlockDisplay = %s Блок: %s
|
|
||||||
LayerTestGUI.SelectedTileDisplay = %s Плитка: %s
|
|
||||||
LayerTestGUI.PlacementModeHint = (Блок %s плитки: Ctrl + прокрутка)
|
|
||||||
LayerTestGUI.IsFullscreen = Полный экран: %5s (F11)
|
|
||||||
LayerTestGUI.IsVSync = Верт. синхр.: %5s (F12)
|
|
||||||
|
|
||||||
LayerButtonTest.Title = Тест кнопок
|
LayerButtonTest.Title = Тест кнопок
|
||||||
LayerButtonTest.Return = Главное меню
|
LayerButtonTest.Return = Главное меню
|
||||||
|
Reference in New Issue
Block a user