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:
OLEGSHA 2021-12-16 15:17:08 +03:00
commit 706800218c
Signed by: OLEGSHA
GPG Key ID: E57A4B08D64AFF7A
45 changed files with 2172 additions and 1412 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,77 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui;
import java.util.Objects;
import glm.vec._2.d.Vec2d;
import ru.windcorp.progressia.client.graphics.gui.event.DragEvent;
import ru.windcorp.progressia.client.graphics.gui.event.DragStartEvent;
import ru.windcorp.progressia.client.graphics.gui.event.DragStopEvent;
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
public class DragManager {
private Component component;
private boolean isDragged = false;
private final Vec2d change = new Vec2d();
public void install(Component c) {
Objects.requireNonNull(c, "c");
if (c == component) {
return;
}
if (component != null) {
throw new IllegalStateException("Already installed on " + component + "; attempted to install on " + c);
}
component = c;
c.addInputListener(CursorMoveEvent.class, this::onCursorMove, InputBus.Option.ALWAYS);
c.addKeyListener(KeyMatcher.LMB, this::onLMB, InputBus.Option.ALWAYS, InputBus.Option.IGNORE_ACTION);
}
private void onCursorMove(CursorMoveEvent e) {
if (isDragged) {
Vec2d currentChange = e.getChange(null);
change.add(currentChange);
component.dispatchEvent(new DragEvent(component, currentChange, change));
}
}
private void onLMB(KeyEvent e) {
if (isDragged && e.isRelease()) {
isDragged = false;
component.dispatchEvent(new DragStopEvent(component, change));
} else if (!isDragged && !e.isConsumed() && e.isPress() && component.isHovered()) {
isDragged = true;
change.set(0, 0);
component.dispatchEvent(new DragStartEvent(component));
e.consume();
}
}
}

View File

@ -18,9 +18,16 @@
package ru.windcorp.progressia.client.graphics.gui; 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

View File

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

View File

@ -0,0 +1,58 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui.event;
import glm.vec._2.d.Vec2d;
import ru.windcorp.progressia.client.graphics.gui.Component;
public class DragEvent extends ComponentEvent {
private final Vec2d currentChange = new Vec2d();
private final Vec2d totalChange = new Vec2d();
public DragEvent(Component component, Vec2d currentChange, Vec2d totalChange) {
super(component);
this.currentChange.set(currentChange.x, currentChange.y);
this.totalChange.set(totalChange.x, totalChange.y);
}
public Vec2d getCurrentChange() {
return currentChange;
}
public double getCurrentChangeX() {
return currentChange.x;
}
public double getCurrentChangeY() {
return currentChange.y;
}
public Vec2d getTotalChange() {
return totalChange;
}
public double getTotalChangeX() {
return totalChange.x;
}
public double getTotalChangeY() {
return totalChange.y;
}
}

View File

@ -0,0 +1,28 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui.event;
import ru.windcorp.progressia.client.graphics.gui.Component;
public class DragStartEvent extends ComponentEvent {
public DragStartEvent(Component component) {
super(component);
}
}

View File

@ -15,48 +15,30 @@
* You should have received a copy of the GNU General Public License * 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;
} }
} }

View File

@ -33,7 +33,6 @@ import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical; import ru.windcorp.progressia.client.graphics.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();
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,7 +31,7 @@ import ru.windcorp.progressia.client.comms.controls.InputBasedControls;
import ru.windcorp.progressia.client.graphics.Layer; import ru.windcorp.progressia.client.graphics.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);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,261 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.Client;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.DynamicLabel;
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.Group;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
import ru.windcorp.progressia.client.localization.Localizer;
import ru.windcorp.progressia.client.localization.MutableString;
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.util.dynstr.DynamicStrings;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.controls.TestPlayerControls;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
public class LayerDebug extends GUILayer {
private final List<Runnable> updateTriggers = new ArrayList<>();
public LayerDebug() {
super("LayerDebug", new LayoutAlign(0, 1, 5));
getRoot().addChild(new Group("Displays", new LayoutVertical(5)));
TestPlayerControls tpc = TestPlayerControls.getInstance();
addDynamicDisplay(
"FPSDisplay",
DynamicStrings.builder()
.addDyn(new MutableStringLocalized("LayerDebug.FPSDisplay"))
.addDyn(() -> FPS_RECORD.update(GraphicsInterface.getFPS()), 5, 1)
.addDyn(() -> GraphicsBackend.isFullscreen() ? " Fullscreen" : "")
.addDyn(() -> GraphicsBackend.isVSyncEnabled() ? " VSync" : "")
.buildSupplier()
);
addDynamicDisplay("TPSDisplay", LayerDebug::getTPS);
addDynamicDisplay(
"ChunkStatsDisplay",
DynamicStrings.builder()
.addDyn(new MutableStringLocalized("LayerDebug.ChunkStatsDisplay"))
.addDyn(() -> {
if (ClientState.getInstance() == null) {
return -1;
} else {
WorldRender world = ClientState.getInstance().getWorld();
return world.getChunks().size() - world.getPendingChunkUpdates();
}
}, 4)
.add('/')
.addDyn(() -> {
if (ClientState.getInstance() == null) {
return -1;
} else {
return ClientState.getInstance().getWorld().getPendingChunkUpdates();
}
}, 4)
.add('/')
.addDyn(() -> {
if (ServerState.getInstance() == null) {
return -1;
} else {
return ServerState.getInstance().getWorld().getChunks().size();
}
}, 4)
.buildSupplier()
);
addDynamicDisplay("PosDisplay", LayerDebug::getPos);
addDisplay("SelectedBlockDisplay", () -> tpc.isBlockSelected() ? ">" : " ", () -> tpc.getSelectedBlock().getId());
addDisplay("SelectedTileDisplay", () -> tpc.isBlockSelected() ? " " : ">", () -> tpc.getSelectedTile().getId());
addDisplay("PlacementModeHint", () -> "\u2B04");
}
private void addDisplay(String name, Supplier<?>... params) {
Font font = new Font().withColor(Colors.WHITE).deriveOutlined();
Label component = new Label(name, font, tmp_dynFormat("LayerDebug." + name, params));
getRoot().getChild(0).addChild(component);
for (Supplier<?> param : params) {
if (param == null) {
continue;
}
updateTriggers.add(new Runnable() {
private Object displayedValue;
@Override
public void run() {
Object newValue = param.get();
if (!Objects.equals(newValue, displayedValue)) {
component.update();
}
displayedValue = newValue;
}
});
}
}
private void addDynamicDisplay(String name, Supplier<CharSequence> contents) {
Font font = new Font().withColor(Colors.WHITE).deriveOutlined();
DynamicLabel component = new DynamicLabel(name, font, contents, 128);
getRoot().getChild(0).addChild(component);
}
@Override
protected void doRender() {
updateTriggers.forEach(Runnable::run);
super.doRender();
}
private static class Averager {
private static final int DISPLAY_INERTIA = 32;
private static final double UPDATE_INTERVAL = Units.get(50.0, "ms");
private final double[] values = new double[DISPLAY_INERTIA];
private int size;
private int head;
private long lastUpdate;
public void add(double value) {
if (size == values.length) {
values[head] = value;
head++;
if (head == values.length)
head = 0;
} else {
values[size] = value;
size++;
}
}
public double average() {
double product = 1;
if (size == values.length) {
for (double d : values)
product *= d;
} else {
for (int i = 0; i < size; ++i)
product *= values[i];
}
return Math.pow(product, 1.0 / size);
}
public double update(double value) {
long now = (long) (GraphicsInterface.getTime() / UPDATE_INTERVAL);
if (lastUpdate != now) {
lastUpdate = now;
add(value);
}
return average();
}
}
private static final String[] CLOCK_CHARS = "\u2591\u2598\u259d\u2580\u2596\u258c\u259e\u259b\u2597\u259a\u2590\u259c\u2584\u2599\u259f\u2588"
.chars().mapToObj(c -> ((char) c) + "").toArray(String[]::new);
private static String getTPSClockChar() {
return CLOCK_CHARS[(int) (ServerState.getInstance().getUptimeTicks() % CLOCK_CHARS.length)];
}
private static final Averager FPS_RECORD = new Averager();
private static final Averager TPS_RECORD = new Averager();
private static final Supplier<CharSequence> TPS_STRING = DynamicStrings.builder()
.addDyn(new MutableStringLocalized("LayerDebug.TPSDisplay"))
.addDyn(() -> TPS_RECORD.update(ServerState.getInstance().getTPS()), 5, 1)
.add(' ')
.addDyn(LayerDebug::getTPSClockChar)
.buildSupplier();
private static final Supplier<CharSequence> POS_STRING = DynamicStrings.builder()
.addDyn(new MutableStringLocalized("LayerDebug.PosDisplay"))
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().x, 7, 1)
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().y, 7, 1)
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().z, 7, 1)
.buildSupplier();
private static CharSequence getTPS() {
Server server = ServerState.getInstance();
if (server == null)
return Localizer.getInstance().getValue("LayerDebug.TPSDisplay.NA");
return TPS_STRING.get();
}
private static CharSequence getPos() {
Client client = ClientState.getInstance();
if (client == null)
return Localizer.getInstance().getValue("LayerDebug.PosDisplay.NA.Client");
Vec3 pos = client.getCamera().getLastAnchorPosition();
if (Float.isNaN(pos.x)) {
return Localizer.getInstance().getValue("LayerDebug.PosDisplay.NA.Entity");
} else {
return POS_STRING.get();
}
}
private static MutableString tmp_dynFormat(String formatKey, Supplier<?>... suppliers) {
return new MutableStringLocalized(formatKey).apply(s -> {
Object[] args = new Object[suppliers.length];
for (int i = 0; i < suppliers.length; ++i) {
Supplier<?> supplier = suppliers[i];
Object value = supplier != null ? supplier.get() : "null";
if (!(value instanceof Number)) {
value = Objects.toString(value);
}
args[i] = value;
}
return String.format(s, args);
});
}
}

View File

@ -1,422 +0,0 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test;
import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.Client;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.DynamicLabel;
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.Group;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
import ru.windcorp.progressia.client.localization.Localizer;
import ru.windcorp.progressia.client.localization.MutableString;
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.util.dynstr.DynamicStrings;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.ServerState;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Supplier;
public class LayerTestGUI extends GUILayer {
public LayerTestGUI() {
super("LayerTestGui", new LayoutAlign(0, 1, 5));
Group group = new Group("ControlDisplays", new LayoutVertical(5));
Vec4 color = Colors.WHITE;
Font font = new Font().withColor(color).deriveOutlined();
TestPlayerControls tpc = TestPlayerControls.getInstance();
group.addChild(
new Label(
"IsFlyingDisplay",
font,
tmp_dynFormat("LayerTestGUI.IsFlyingDisplay", tpc::isFlying)
)
);
group.addChild(
new Label(
"IsSprintingDisplay",
font,
tmp_dynFormat("LayerTestGUI.IsSprintingDisplay", tpc::isSprinting)
)
);
group.addChild(
new Label(
"CameraModeDisplay",
font,
tmp_dynFormat(
"LayerTestGUI.CameraModeDisplay",
ClientState.getInstance().getCamera()::getCurrentModeIndex
)
)
);
group.addChild(
new Label(
"LanguageDisplay",
font,
tmp_dynFormat("LayerTestGUI.LanguageDisplay", Localizer.getInstance()::getLanguage)
)
);
group.addChild(
new Label(
"FullscreenDisplay",
font,
tmp_dynFormat("LayerTestGUI.IsFullscreen", GraphicsBackend::isFullscreen)
)
);
group.addChild(
new Label(
"VSyncDisplay",
font,
tmp_dynFormat("LayerTestGUI.IsVSync", GraphicsBackend::isVSyncEnabled)
)
);
group.addChild(
new DynamicLabel(
"FPSDisplay",
font,
DynamicStrings.builder()
.addDyn(new MutableStringLocalized("LayerTestGUI.FPSDisplay"))
.addDyn(() -> FPS_RECORD.update(GraphicsInterface.getFPS()), 5, 1)
.buildSupplier(),
128
)
);
group.addChild(
new DynamicLabel(
"TPSDisplay",
font,
LayerTestGUI::getTPS,
128
)
);
group.addChild(
new DynamicLabel(
"ChunkStatsDisplay",
font,
DynamicStrings.builder()
.addDyn(new MutableStringLocalized("LayerTestGUI.ChunkStatsDisplay"))
.addDyn(() -> {
if (ClientState.getInstance() == null) {
return -1;
} else {
WorldRender world = ClientState.getInstance().getWorld();
return world.getChunks().size() - world.getPendingChunkUpdates();
}
}, 4)
.add('/')
.addDyn(() -> {
if (ClientState.getInstance() == null) {
return -1;
} else {
return ClientState.getInstance().getWorld().getPendingChunkUpdates();
}
}, 4)
.add('/')
.addDyn(() -> {
if (ServerState.getInstance() == null) {
return -1;
} else {
return ServerState.getInstance().getWorld().getChunks().size();
}
}, 4)
.buildSupplier(),
128
)
);
group.addChild(
new DynamicLabel(
"PosDisplay",
font,
LayerTestGUI::getPos,
128
)
);
group.addChild(
new Label(
"SelectedBlockDisplay",
font,
tmp_dynFormat(
"LayerTestGUI.SelectedBlockDisplay",
() -> tpc.isBlockSelected() ? ">" : " ",
() -> tpc.getSelectedBlock().getId()
)
)
);
group.addChild(
new Label(
"SelectedTileDisplay",
font,
tmp_dynFormat(
"LayerTestGUI.SelectedTileDisplay",
() -> tpc.isBlockSelected() ? " " : ">",
() -> tpc.getSelectedTile().getId()
)
)
);
group.addChild(
new Label(
"PlacementModeHint",
font,
new MutableStringLocalized("LayerTestGUI.PlacementModeHint").format("\u2B04")
)
);
getRoot().addChild(group);
}
public Runnable getUpdateCallback() {
Collection<Label> labels = new ArrayList<>();
getRoot().getChild(0).getChildren().forEach(c -> {
if (c instanceof Label) {
labels.add((Label) c);
}
});
return () -> labels.forEach(Label::update);
}
private static class Averager {
private static final int DISPLAY_INERTIA = 32;
private static final double UPDATE_INTERVAL = Units.get(50.0, "ms");
private final double[] values = new double[DISPLAY_INERTIA];
private int size;
private int head;
private long lastUpdate;
public void add(double value) {
if (size == values.length) {
values[head] = value;
head++;
if (head == values.length)
head = 0;
} else {
values[size] = value;
size++;
}
}
public double average() {
double product = 1;
if (size == values.length) {
for (double d : values)
product *= d;
} else {
for (int i = 0; i < size; ++i)
product *= values[i];
}
return Math.pow(product, 1.0 / size);
}
public double update(double value) {
long now = (long) (GraphicsInterface.getTime() / UPDATE_INTERVAL);
if (lastUpdate != now) {
lastUpdate = now;
add(value);
}
return average();
}
}
private static final String[] CLOCK_CHARS = "\u2591\u2598\u259d\u2580\u2596\u258c\u259e\u259b\u2597\u259a\u2590\u259c\u2584\u2599\u259f\u2588"
.chars().mapToObj(c -> ((char) c) + "").toArray(String[]::new);
private static String getTPSClockChar() {
return CLOCK_CHARS[(int) (ServerState.getInstance().getUptimeTicks() % CLOCK_CHARS.length)];
}
private static final Averager FPS_RECORD = new Averager();
private static final Averager TPS_RECORD = new Averager();
private static final Supplier<CharSequence> TPS_STRING = DynamicStrings.builder()
.addDyn(new MutableStringLocalized("LayerTestGUI.TPSDisplay"))
.addDyn(() -> TPS_RECORD.update(ServerState.getInstance().getTPS()), 5, 1)
.add(' ')
.addDyn(LayerTestGUI::getTPSClockChar)
.buildSupplier();
private static final Supplier<CharSequence> POS_STRING = DynamicStrings.builder()
.addDyn(new MutableStringLocalized("LayerTestGUI.PosDisplay"))
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().x, 7, 1)
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().y, 7, 1)
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().z, 7, 1)
.buildSupplier();
private static CharSequence getTPS() {
Server server = ServerState.getInstance();
if (server == null)
return Localizer.getInstance().getValue("LayerTestGUI.TPSDisplay.NA");
return TPS_STRING.get();
}
private static CharSequence getPos() {
Client client = ClientState.getInstance();
if (client == null)
return Localizer.getInstance().getValue("LayerTestGUI.PosDisplay.NA.Client");
Vec3 pos = client.getCamera().getLastAnchorPosition();
if (Float.isNaN(pos.x)) {
return Localizer.getInstance().getValue("LayerTestGUI.PosDisplay.NA.Entity");
} else {
return POS_STRING.get();
}
}
private static MutableString tmp_dynFormat(String formatKey, Supplier<?>... suppliers) {
return new MutableStringLocalized(formatKey).apply(s -> {
Object[] args = new Object[suppliers.length];
for (int i = 0; i < suppliers.length; ++i) {
Supplier<?> supplier = suppliers[i];
Object value = supplier != null ? supplier.get() : "null";
if (!(value instanceof Number)) {
value = Objects.toString(value);
}
args[i] = value;
}
return String.format(s, args);
});
}
// private static class DebugComponent extends Component {
// private final int color;
//
// public DebugComponent(String name, Vec2i size, int color) {
// super(name);
// this.color = color;
//
// setPreferredSize(size);
//
// addListener(new Object() {
// @Subscribe
// public void onHoverChanged(HoverEvent e) {
// requestReassembly();
// }
// });
//
// addListener(KeyEvent.class, this::onClicked);
// }
//
// private boolean onClicked(KeyEvent event) {
// if (!event.isMouse()) {
// return false;
// } else if (event.isPress() && event.isLeftMouseButton()) {
// System.out.println("You pressed a Component!");
// }
// return true;
// }
//
// @Override
// protected void assembleSelf(RenderTarget target) {
// target.fill(getX(), getY(), getWidth(), getHeight(), Colors.BLACK);
//
// target.fill(
// getX() + 2, getY() + 2,
// getWidth() - 4, getHeight() - 4,
// isHovered() ? Colors.DEBUG_YELLOW : color
// );
// }
// }
//
// public LayerTestGUI() {
// super("LayerTestGui", new LayoutAlign(1, 0.75, 5));
//
// Panel panel = new Panel("Alex", new LayoutVertical(5));
//
// panel.addChild(new DebugComponent("Bravo", new Vec2i(200, 100), 0x44FF44));
//
// Component charlie = new DebugComponent("Charlie", null, 0x222222);
// charlie.setLayout(new LayoutVertical(5));
//
// //Debug
// Localizer.getInstance().setLanguage("ru-RU");
// MutableString epsilon = new MutableStringLocalized("Epsilon")
// .addListener(() -> ((Label)charlie.getChild(0)).update()).format(34, "thirty-four");
// // These two are swapped in code due to a bug in layouts, fixing ATM
// charlie.addChild(
// new Label(
// "Delta",
// new Font().withColor(0xCCBB44).deriveShadow().deriveBold(),
// "Пре-альфа!"
// )
// );
// charlie.addChild(
// new Label(
// "Epsilon",
// new Font().withColor(0x4444BB).deriveItalic(),
// () -> epsilon.get().concat("\u269b")
// )
// );
// panel.addChild(charlie);
//
//
// charlie.addListener(KeyEvent.class, e -> {
// if(e.isPress() && e.getKey() == GLFW.GLFW_KEY_L) {
// Localizer localizer = Localizer.getInstance();
// if (localizer.getLanguage().equals("ru-RU")) {
// localizer.setLanguage("en-US");
// } else {
// localizer.setLanguage("ru-RU");
// }
// return true;
// } return false;
// });
// charlie.setFocusable(true);
// charlie.takeFocus();
//
// getRoot().addChild(panel);
// }
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,130 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.controls;
import glm.Glm;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.comms.controls.ControlTriggerRegistry;
import ru.windcorp.progressia.client.comms.controls.ControlTriggers;
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
import ru.windcorp.progressia.client.graphics.world.Camera;
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
import ru.windcorp.progressia.common.util.Matrices;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.entity.EntityData;
public class CameraControls {
{
reset();
}
public void reset() {
// No instance fields; futureproofing
}
public void registerControls() {
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
triggers.register(
ControlTriggers.localOf(
"Test:TurnCameraWithCursor",
CursorMoveEvent.class,
this::turnCameraWithCursor
)
);
triggers.register(
ControlTriggers.localOf(
"Test:SwitchCameraMode",
KeyEvent.class,
this::switchCameraMode,
new KeyMatcher("F5")::matches
)
);
NoclipCamera.register();
triggers.register(
ControlTriggers.localOf(
"Test:ToggleNoclip",
KeyEvent.class,
NoclipCamera::toggleNoclip,
new KeyMatcher("V")::matches
)
);
}
private void turnCameraWithCursor(CursorMoveEvent event) {
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return;
}
Camera.Anchor cameraAnchor = ClientState.getInstance().getCamera().getAnchor();
if (!(cameraAnchor instanceof EntityAnchor)) {
return;
}
EntityData player = ((EntityAnchor) cameraAnchor).getEntity();
final double yawScale = -0.002f;
final double pitchScale = -yawScale;
final double pitchExtremum = Math.PI / 2 * 0.995f;
double yawChange = event.getChangeX() * yawScale;
double pitchChange = event.getChangeY() * pitchScale;
double startPitch = player.getPitch();
double endPitch = startPitch + pitchChange;
endPitch = Glm.clamp(endPitch, -pitchExtremum, +pitchExtremum);
pitchChange = endPitch - startPitch;
Mat4 mat = Matrices.grab4();
Vec3 lookingAt = Vectors.grab3();
Vec3 rightVector = Vectors.grab3();
rightVector.set(player.getLookingAt()).cross(player.getUpVector()).normalize();
mat.identity()
.rotate((float) yawChange, player.getUpVector())
.rotate((float) pitchChange, rightVector);
VectorUtil.applyMat4(player.getLookingAt(), mat, lookingAt);
player.setLookingAt(lookingAt);
Vectors.release(rightVector);
Vectors.release(lookingAt);
Matrices.release(mat);
}
private void switchCameraMode() {
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return;
}
if (ClientState.getInstance().getCamera().hasAnchor()) {
ClientState.getInstance().getCamera().selectNextMode();
}
}
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * 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;

View File

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

View File

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

View File

@ -0,0 +1,264 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.controls;
import org.lwjgl.glfw.GLFW;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.audio.Sound;
import ru.windcorp.progressia.client.comms.controls.ControlTriggerRegistry;
import ru.windcorp.progressia.client.comms.controls.ControlTriggers;
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
import ru.windcorp.progressia.client.graphics.input.WheelScrollEvent;
import ru.windcorp.progressia.client.graphics.world.Selection;
import ru.windcorp.progressia.common.comms.controls.ControlData;
import ru.windcorp.progressia.common.comms.controls.ControlDataRegistry;
import ru.windcorp.progressia.common.comms.controls.PacketControl;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.block.BlockDataRegistry;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.comms.controls.ControlLogic;
import ru.windcorp.progressia.server.comms.controls.ControlLogicRegistry;
import ru.windcorp.progressia.server.world.context.ServerBlockContext;
import ru.windcorp.progressia.server.world.context.ServerTileContext;
import ru.windcorp.progressia.server.world.context.ServerTileStackContext;
import ru.windcorp.progressia.server.world.tile.TileLogic;
import ru.windcorp.progressia.server.world.tile.TileLogicRegistry;
import ru.windcorp.progressia.test.TestContent;
public class InteractionControls {
private int selectedBlock;
private int selectedTile;
private boolean isBlockSelected;
{
reset();
}
public void reset() {
selectedBlock = 0;
selectedTile = 0;
isBlockSelected = true;
}
public void registerControls() {
ControlDataRegistry data = ControlDataRegistry.getInstance();
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
ControlLogicRegistry logic = ControlLogicRegistry.getInstance();
data.register("Test:BreakBlock", ControlBreakBlockData::new);
triggers.register(
ControlTriggers.of(
"Test:BreakBlock",
KeyEvent.class,
this::onBlockBreakTrigger,
KeyMatcher.LMB::matches,
i -> isAnythingSelected()
)
);
logic.register(ControlLogic.of("Test:BreakBlock", InteractionControls::onBlockBreakReceived));
data.register("Test:PlaceBlock", ControlPlaceBlockData::new);
triggers.register(
ControlTriggers.of(
"Test:PlaceBlock",
KeyEvent.class,
this::onBlockPlaceTrigger,
KeyMatcher.RMB::matches,
i -> isAnythingSelected() && isBlockSelected()
)
);
logic.register(ControlLogic.of("Test:PlaceBlock", InteractionControls::onBlockPlaceReceived));
data.register("Test:PlaceTile", ControlPlaceTileData::new);
triggers.register(
ControlTriggers.of(
"Test:PlaceTile",
KeyEvent.class,
this::onTilePlaceTrigger,
KeyMatcher.RMB::matches,
i -> isAnythingSelected() && !isBlockSelected()
)
);
logic.register(ControlLogic.of("Test:PlaceTile", InteractionControls::onTilePlaceReceived));
triggers.register(
ControlTriggers.localOf(
"Test:SwitchPlacingModeMMB",
KeyEvent.class,
this::switchPlacingMode,
KeyMatcher.MMB::matches
)
);
triggers.register(
ControlTriggers.localOf(
"Test:SwitchPlacingModeWheel",
WheelScrollEvent.class,
this::switchPlacingMode,
e -> e.hasHorizontalMovement() || InputTracker.isKeyPressed(GLFW.GLFW_KEY_LEFT_CONTROL)
)
);
triggers.register(
ControlTriggers.localOf(
"Test:SelectNextBlockOrTile",
WheelScrollEvent.class,
this::selectNextBlockOrTile,
e -> !e.hasHorizontalMovement() && !InputTracker.isKeyPressed(GLFW.GLFW_KEY_LEFT_CONTROL)
)
);
}
private static Selection getSelection() {
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
if (client == null || !client.isReady())
return null;
return client.getLocalPlayer().getSelection();
}
private static boolean isAnythingSelected() {
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
if (client == null || !client.isReady())
return false;
return client.getLocalPlayer().getSelection().exists();
}
private void onBlockBreakTrigger(ControlData control) {
((ControlBreakBlockData) control).setBlockInWorld(getSelection().getBlock());
Sound sfx = new Sound("Progressia:BlockDestroy");
sfx.setPosition(getSelection().getPoint());
sfx.setPitch((float) (Math.random() + 1 * 0.5));
sfx.play(false);
}
private static void onBlockBreakReceived(
Server server,
PacketControl packet,
ru.windcorp.progressia.server.comms.Client client
) {
Vec3i blockInWorld = ((ControlBreakBlockData) packet.getControl()).getBlockInWorld();
server.createAbsoluteContext().setBlock(blockInWorld, BlockDataRegistry.getInstance().get("Test:Air"));
}
private void onBlockPlaceTrigger(ControlData control) {
((ControlPlaceBlockData) control).set(
getSelectedBlock(),
getSelection().getBlock().add_(getSelection().getSurface().getVector())
);
}
private static void onBlockPlaceReceived(
Server server,
PacketControl packet,
ru.windcorp.progressia.server.comms.Client client
) {
ControlPlaceBlockData controlData = ((ControlPlaceBlockData) packet.getControl());
BlockData block = controlData.getBlock();
Vec3i blockInWorld = controlData.getBlockInWorld();
if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null)
return;
server.createAbsoluteContext().setBlock(blockInWorld, block);
}
private void onTilePlaceTrigger(ControlData control) {
((ControlPlaceTileData) control).set(
getSelectedTile(),
getSelection().getBlock(),
getSelection().getSurface()
);
}
private static void onTilePlaceReceived(
Server server,
PacketControl packet,
ru.windcorp.progressia.server.comms.Client client
) {
ControlPlaceTileData controlData = ((ControlPlaceTileData) packet.getControl());
TileData tile = controlData.getTile();
Vec3i blockInWorld = controlData.getBlockInWorld();
AbsFace face = controlData.getFace();
if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null) {
return;
}
if (server.getWorld().getData().getTiles(blockInWorld, face).isFull()) {
return;
}
ServerBlockContext context = server.createContext(blockInWorld);
ServerTileStackContext tsContext = context.push(context.toContext(face));
ServerTileContext tileContext = tsContext.push(tsContext.getTileCount());
TileLogic logic = TileLogicRegistry.getInstance().get(tile.getId());
if (!logic.canOccupyFace(tileContext)) {
return;
}
tileContext.addTile(tile);
}
public void switchPlacingMode() {
isBlockSelected = !isBlockSelected;
}
public void selectNextBlockOrTile(WheelScrollEvent event) {
if (isBlockSelected) {
selectedBlock += event.isUp() ? +1 : -1;
int size = TestContent.PLACEABLE_BLOCKS.size();
if (selectedBlock < 0) {
selectedBlock = size - 1;
} else if (selectedBlock >= size) {
selectedBlock = 0;
}
} else {
selectedTile += event.isUp() ? +1 : -1;
int size = TestContent.PLACEABLE_TILES.size();
if (selectedTile < 0) {
selectedTile = size - 1;
} else if (selectedTile >= size) {
selectedTile = 0;
}
}
}
public BlockData getSelectedBlock() {
return TestContent.PLACEABLE_BLOCKS.get(selectedBlock);
}
public TileData getSelectedTile() {
return TestContent.PLACEABLE_TILES.get(selectedTile);
}
public boolean isBlockSelected() {
return isBlockSelected;
}
}

View File

@ -0,0 +1,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;
}
}

View File

@ -0,0 +1,99 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.controls;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.world.Camera;
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.client.world.entity.EntityRender;
import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry;
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
public class NoclipCamera {
private static class NoclipEntityRender extends EntityRender {
public NoclipEntityRender() {
super("Test:NoclipCamera");
}
@Override
public EntityRenderable createRenderable(EntityData entity) {
return new NoclipEntityRenderable(entity);
}
}
private static class NoclipEntityRenderable extends EntityRenderable {
public NoclipEntityRenderable(EntityData data) {
super(data);
}
@Override
protected void doRender(ShapeRenderHelper renderer) {
// Do nothing
}
}
public static void register() {
EntityDataRegistry.getInstance().register("Test:NoclipCamera");
EntityRenderRegistry.getInstance().register(new NoclipEntityRender());
}
public static void toggleNoclip() {
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return;
}
Camera camera = ClientState.getInstance().getCamera();
WorldRender world = ClientState.getInstance().getWorld();
EntityData player = ClientState.getInstance().getLocalPlayer().getEntity();
List<EntityData> existingCameras = world.getData().getEntities().stream().filter(e -> e.getId().equals("Test:NoclipCamera")).collect(Collectors.toList());
if (!existingCameras.isEmpty()) {
existingCameras.forEach(world.getData()::removeEntity);
camera.setAnchor(new EntityAnchor(world.getEntityRenderable(player)));
return;
}
EntityData noclipCamera = EntityDataRegistry.getInstance().create("Test:NoclipCamera");
noclipCamera.setLookingAt(player.getLookingAt());
noclipCamera.setUpVector(player.getUpVector());
noclipCamera.setPosition(player.getPosition());
noclipCamera.setVelocity(player.getVelocity());
noclipCamera.setEntityId(new Random().nextLong());
player.setVelocity(new Vec3(0));
world.getData().addEntity(noclipCamera);
camera.setAnchor(new EntityAnchor(world.getEntityRenderable(noclipCamera)));
}
}

View File

@ -0,0 +1,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();
}
}

View File

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

View File

@ -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 = Главное меню