Merge branch 'overhaul-input'
Summary of changes: - Refactored input handling - Added noclip/freecam mode for debugging - Added drag events - Removed useless info from debug layer - Movement controls now work in menus (feature? bug? not sure)
This commit is contained in:
commit
706800218c
@ -143,20 +143,6 @@ public class ControlTriggers {
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
///
|
||||
///
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
public static ControlTriggerInputBased localOf(
|
||||
String id,
|
||||
Consumer<InputEvent> action,
|
||||
|
@ -19,7 +19,7 @@
|
||||
package ru.windcorp.progressia.client.comms.controls;
|
||||
|
||||
import ru.windcorp.progressia.client.Client;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
import ru.windcorp.progressia.common.comms.packets.Packet;
|
||||
|
||||
public class InputBasedControls {
|
||||
@ -36,12 +36,12 @@ public class InputBasedControls {
|
||||
.toArray(ControlTriggerInputBased[]::new);
|
||||
}
|
||||
|
||||
public void handleInput(Input input) {
|
||||
public void handleInput(InputEvent event) {
|
||||
for (ControlTriggerInputBased c : controls) {
|
||||
Packet packet = c.onInputEvent(input.getEvent());
|
||||
Packet packet = c.onInputEvent(event);
|
||||
|
||||
if (packet != null) {
|
||||
input.consume();
|
||||
event.consume();
|
||||
client.getComms().sendPacket(packet);
|
||||
break;
|
||||
}
|
||||
|
@ -23,15 +23,8 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
import ru.windcorp.progressia.client.graphics.input.CursorEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.WheelEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||
|
||||
public class GUI {
|
||||
|
||||
@ -46,15 +39,6 @@ public class GUI {
|
||||
private static final List<LayerStackModification> MODIFICATION_QUEUE = Collections
|
||||
.synchronizedList(new ArrayList<>());
|
||||
|
||||
private static class ModifiableInput extends Input {
|
||||
@Override
|
||||
public void initialize(InputEvent event, Target target) {
|
||||
super.initialize(event, target);
|
||||
}
|
||||
}
|
||||
|
||||
private static final ModifiableInput THE_INPUT = new ModifiableInput();
|
||||
|
||||
private GUI() {
|
||||
}
|
||||
|
||||
@ -126,43 +110,12 @@ public class GUI {
|
||||
LAYERS.forEach(Layer::invalidate);
|
||||
}
|
||||
|
||||
private static void dispatchInputEvent(InputEvent event) {
|
||||
Input.Target target;
|
||||
|
||||
if (event instanceof KeyEvent) {
|
||||
if (((KeyEvent) event).isMouse()) {
|
||||
target = Input.Target.HOVERED;
|
||||
} else {
|
||||
target = Input.Target.FOCUSED;
|
||||
public static void dispatchInput(InputEvent event) {
|
||||
synchronized (LAYERS) {
|
||||
for (int i = 0; i < LAYERS.size(); ++i) {
|
||||
LAYERS.get(i).handleInput(event);
|
||||
}
|
||||
} else if (event instanceof CursorEvent) {
|
||||
target = Input.Target.HOVERED;
|
||||
} else if (event instanceof WheelEvent) {
|
||||
target = Input.Target.HOVERED;
|
||||
} else if (event instanceof FrameResizeEvent) {
|
||||
return;
|
||||
} else {
|
||||
target = Input.Target.ALL;
|
||||
}
|
||||
|
||||
THE_INPUT.initialize(event, target);
|
||||
LAYERS.forEach(l -> l.handleInput(THE_INPUT));
|
||||
}
|
||||
|
||||
public static Object getEventSubscriber() {
|
||||
return new Object() {
|
||||
|
||||
@Subscribe
|
||||
public void onFrameResized(FrameResizeEvent event) {
|
||||
GUI.invalidateEverything();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onInput(InputEvent event) {
|
||||
dispatchInputEvent(event);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.graphics;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
|
||||
public abstract class Layer {
|
||||
|
||||
@ -106,7 +106,7 @@ public abstract class Layer {
|
||||
|
||||
protected abstract void doRender();
|
||||
|
||||
protected abstract void handleInput(Input input);
|
||||
public abstract void handleInput(InputEvent input);
|
||||
|
||||
protected int getWidth() {
|
||||
return GraphicsInterface.getFrameWidth();
|
||||
|
@ -69,6 +69,10 @@ public class GraphicsInterface {
|
||||
InputHandler.register(listener);
|
||||
}
|
||||
|
||||
public static void unsubscribeFromInputEvents(Object listener) {
|
||||
InputHandler.unregister(listener);
|
||||
}
|
||||
|
||||
public static void startNextLayer() {
|
||||
GraphicsBackend.startNextLayer();
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ public class InputHandler {
|
||||
|
||||
public void initialize(int key, int scancode, int action, int mods) {
|
||||
this.setTime(GraphicsInterface.getTime());
|
||||
this.setConsumed(false);
|
||||
this.key = key;
|
||||
this.scancode = scancode;
|
||||
this.action = action;
|
||||
@ -59,7 +60,7 @@ public class InputHandler {
|
||||
if (GraphicsBackend.getWindowHandle() != window)
|
||||
return;
|
||||
THE_KEY_EVENT.initialize(key, scancode, action, mods);
|
||||
dispatch(THE_KEY_EVENT);
|
||||
INPUT_EVENT_BUS.post(THE_KEY_EVENT);
|
||||
|
||||
switch (action) {
|
||||
case GLFW.GLFW_PRESS:
|
||||
@ -90,6 +91,7 @@ public class InputHandler {
|
||||
|
||||
public void initialize(double x, double y) {
|
||||
this.setTime(GraphicsInterface.getTime());
|
||||
this.setConsumed(false);
|
||||
getNewPosition().set(x, y);
|
||||
}
|
||||
|
||||
@ -109,7 +111,7 @@ public class InputHandler {
|
||||
InputTracker.initializeCursorPosition(x, y);
|
||||
|
||||
THE_CURSOR_MOVE_EVENT.initialize(x, y);
|
||||
dispatch(THE_CURSOR_MOVE_EVENT);
|
||||
INPUT_EVENT_BUS.post(THE_CURSOR_MOVE_EVENT);
|
||||
|
||||
InputTracker.getCursorPosition().set(x, y);
|
||||
}
|
||||
@ -124,6 +126,7 @@ public class InputHandler {
|
||||
|
||||
public void initialize(double xOffset, double yOffset) {
|
||||
this.setTime(GraphicsInterface.getTime());
|
||||
this.setConsumed(false);
|
||||
this.getOffset().set(xOffset, yOffset);
|
||||
}
|
||||
|
||||
@ -139,7 +142,7 @@ public class InputHandler {
|
||||
if (GraphicsBackend.getWindowHandle() != window)
|
||||
return;
|
||||
THE_WHEEL_SCROLL_EVENT.initialize(xoffset, yoffset);
|
||||
dispatch(THE_WHEEL_SCROLL_EVENT);
|
||||
INPUT_EVENT_BUS.post(THE_WHEEL_SCROLL_EVENT);
|
||||
}
|
||||
|
||||
// FrameResizeEvent
|
||||
@ -152,6 +155,7 @@ public class InputHandler {
|
||||
|
||||
public void initialize(int width, int height) {
|
||||
this.setTime(GraphicsInterface.getTime());
|
||||
this.setConsumed(false);
|
||||
this.getNewSize().set(width, height);
|
||||
}
|
||||
|
||||
@ -167,17 +171,17 @@ public class InputHandler {
|
||||
int height
|
||||
) {
|
||||
THE_FRAME_RESIZE_EVENT.initialize(width, height);
|
||||
dispatch(THE_FRAME_RESIZE_EVENT);
|
||||
INPUT_EVENT_BUS.post(THE_FRAME_RESIZE_EVENT);
|
||||
}
|
||||
|
||||
// Misc
|
||||
|
||||
private static void dispatch(InputEvent event) {
|
||||
INPUT_EVENT_BUS.post(event);
|
||||
}
|
||||
|
||||
public static void register(Object listener) {
|
||||
INPUT_EVENT_BUS.register(listener);
|
||||
}
|
||||
|
||||
public static void unregister(Object listener) {
|
||||
INPUT_EVENT_BUS.unregister(listener);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,7 +24,11 @@ import static org.lwjgl.system.MemoryUtil.*;
|
||||
|
||||
import org.lwjgl.opengl.GL;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.GUI;
|
||||
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
|
||||
class LWJGLInitializer {
|
||||
|
||||
@ -107,7 +111,20 @@ class LWJGLInitializer {
|
||||
|
||||
glfwSetScrollCallback(handle, InputHandler::handleWheelScroll);
|
||||
|
||||
GraphicsInterface.subscribeToInputEvents(GUI.getEventSubscriber());
|
||||
GraphicsInterface.subscribeToInputEvents(new Object() {
|
||||
|
||||
@Subscribe
|
||||
public void onFrameResized(FrameResizeEvent event) {
|
||||
GUI.invalidateEverything();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onInputEvent(InputEvent event) {
|
||||
GUI.dispatchInput(event);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -54,18 +54,17 @@ public abstract class BasicButton extends Component {
|
||||
reassembleAt(ARTrigger.HOVER, ARTrigger.FOCUS, ARTrigger.ENABLE);
|
||||
|
||||
// Click triggers
|
||||
addListener(KeyEvent.class, e -> {
|
||||
if (e.isRepeat()) {
|
||||
return false;
|
||||
} else if (
|
||||
addInputListener(KeyEvent.class, e -> {
|
||||
if (e.isRepeat())
|
||||
return;
|
||||
|
||||
if (
|
||||
e.isLeftMouseButton() ||
|
||||
e.getKey() == GLFW.GLFW_KEY_SPACE ||
|
||||
e.getKey() == GLFW.GLFW_KEY_ENTER
|
||||
) {
|
||||
setPressed(e.isPress());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -25,8 +25,6 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
@ -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.HoverEvent;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.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.InputListener;
|
||||
import ru.windcorp.progressia.common.util.Named;
|
||||
@ -55,7 +54,7 @@ public class Component extends Named {
|
||||
private Component parent = null;
|
||||
|
||||
private EventBus eventBus = null;
|
||||
private InputBus inputBus = null;
|
||||
private final InputBus inputBus = new InputBus(this);
|
||||
|
||||
private int x, y;
|
||||
private int width, height;
|
||||
@ -76,6 +75,9 @@ public class Component extends Named {
|
||||
|
||||
public Component(String name) {
|
||||
super(name);
|
||||
|
||||
// Update hover flag when cursor moves
|
||||
addInputListener(CursorMoveEvent.class, this::updateHoverFlag, InputBus.Option.ALWAYS);
|
||||
}
|
||||
|
||||
public Component getParent() {
|
||||
@ -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) {
|
||||
if (eventBus == null) {
|
||||
eventBus = ReportingEventBus.create(getName());
|
||||
@ -542,121 +548,28 @@ public class Component extends Named {
|
||||
eventBus.post(event);
|
||||
}
|
||||
|
||||
public <T extends InputEvent> void addListener(
|
||||
Class<? extends T> type,
|
||||
boolean handlesConsumed,
|
||||
InputListener<T> listener
|
||||
) {
|
||||
if (inputBus == null) {
|
||||
inputBus = new InputBus();
|
||||
}
|
||||
|
||||
inputBus.register(type, handlesConsumed, listener);
|
||||
public <T extends InputEvent> void addInputListener(Class<? extends T> type, InputListener<T> listener, InputBus.Option... options) {
|
||||
inputBus.register(type, listener, options);
|
||||
}
|
||||
|
||||
public <T extends InputEvent> void addListener(Class<? extends T> type, InputListener<T> listener) {
|
||||
if (inputBus == null) {
|
||||
inputBus = new InputBus();
|
||||
}
|
||||
|
||||
inputBus.register(type, listener);
|
||||
public void addKeyListener(KeyMatcher matcher, InputListener<? super KeyEvent> listener, InputBus.Option... options) {
|
||||
inputBus.register(matcher, listener, options);
|
||||
}
|
||||
|
||||
public void removeListener(InputListener<?> listener) {
|
||||
public void removeInputListener(InputListener<?> listener) {
|
||||
if (inputBus != null) {
|
||||
inputBus.unregister(listener);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleInput(Input input) {
|
||||
if (inputBus != null && isEnabled()) {
|
||||
inputBus.dispatch(input);
|
||||
}
|
||||
}
|
||||
|
||||
public void dispatchInput(Input input) {
|
||||
try {
|
||||
switch (input.getTarget()) {
|
||||
case FOCUSED:
|
||||
dispatchInputToFocused(input);
|
||||
break;
|
||||
case HOVERED:
|
||||
dispatchInputToHovered(input);
|
||||
break;
|
||||
case ALL:
|
||||
default:
|
||||
dispatchInputToAll(input);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw CrashReports.report(e, "Could not dispatch input to Component %s", this);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchInputToFocused(Input input) {
|
||||
Component c = findFocused();
|
||||
|
||||
if (c == null)
|
||||
return;
|
||||
if (attemptFocusTransfer(input, c))
|
||||
return;
|
||||
|
||||
while (c != null) {
|
||||
c.handleInput(input);
|
||||
c = c.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchInputToHovered(Input input) {
|
||||
getChildren().forEach(child -> {
|
||||
if (child.containsCursor()) {
|
||||
child.setHovered(true);
|
||||
|
||||
if (!input.isConsumed()) {
|
||||
child.dispatchInput(input);
|
||||
}
|
||||
} else {
|
||||
child.setHovered(false);
|
||||
}
|
||||
});
|
||||
|
||||
handleInput(input);
|
||||
}
|
||||
|
||||
private void dispatchInputToAll(Input input) {
|
||||
getChildren().forEach(c -> c.dispatchInput(input));
|
||||
handleInput(input);
|
||||
}
|
||||
|
||||
private boolean attemptFocusTransfer(Input input, Component focused) {
|
||||
if (input.isConsumed())
|
||||
return false;
|
||||
if (!(input.getEvent() instanceof KeyEvent))
|
||||
return false;
|
||||
|
||||
KeyEvent keyInput = (KeyEvent) input.getEvent();
|
||||
|
||||
if (keyInput.getKey() == GLFW.GLFW_KEY_TAB && !keyInput.isRelease()) {
|
||||
input.consume();
|
||||
if (keyInput.hasShift()) {
|
||||
focused.focusPrevious();
|
||||
} else {
|
||||
focused.focusNext();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
InputBus getInputBus() {
|
||||
return inputBus;
|
||||
}
|
||||
|
||||
public synchronized boolean contains(int x, int y) {
|
||||
return x >= getX() && x < getX() + getWidth() && y >= getY() && y < getY() + getHeight();
|
||||
}
|
||||
|
||||
public boolean containsCursor() {
|
||||
return contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY());
|
||||
}
|
||||
|
||||
public void requestReassembly() {
|
||||
if (parent != null) {
|
||||
parent.requestReassembly();
|
||||
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.gui;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import glm.vec._2.d.Vec2d;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.DragEvent;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.DragStartEvent;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.DragStopEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
|
||||
|
||||
public class DragManager {
|
||||
|
||||
private Component component;
|
||||
|
||||
private boolean isDragged = false;
|
||||
private final Vec2d change = new Vec2d();
|
||||
|
||||
public void install(Component c) {
|
||||
Objects.requireNonNull(c, "c");
|
||||
if (c == component) {
|
||||
return;
|
||||
}
|
||||
if (component != null) {
|
||||
throw new IllegalStateException("Already installed on " + component + "; attempted to install on " + c);
|
||||
}
|
||||
|
||||
component = c;
|
||||
|
||||
c.addInputListener(CursorMoveEvent.class, this::onCursorMove, InputBus.Option.ALWAYS);
|
||||
c.addKeyListener(KeyMatcher.LMB, this::onLMB, InputBus.Option.ALWAYS, InputBus.Option.IGNORE_ACTION);
|
||||
}
|
||||
|
||||
private void onCursorMove(CursorMoveEvent e) {
|
||||
if (isDragged) {
|
||||
Vec2d currentChange = e.getChange(null);
|
||||
change.add(currentChange);
|
||||
component.dispatchEvent(new DragEvent(component, currentChange, change));
|
||||
}
|
||||
}
|
||||
|
||||
private void onLMB(KeyEvent e) {
|
||||
if (isDragged && e.isRelease()) {
|
||||
|
||||
isDragged = false;
|
||||
component.dispatchEvent(new DragStopEvent(component, change));
|
||||
|
||||
} else if (!isDragged && !e.isConsumed() && e.isPress() && component.isHovered()) {
|
||||
|
||||
isDragged = true;
|
||||
change.set(0, 0);
|
||||
component.dispatchEvent(new DragStartEvent(component));
|
||||
e.consume();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -18,9 +18,16 @@
|
||||
|
||||
package ru.windcorp.progressia.client.graphics.gui;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer;
|
||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
|
||||
import ru.windcorp.progressia.common.util.StashingStack;
|
||||
|
||||
public abstract class GUILayer extends AssembledFlatLayer {
|
||||
|
||||
@ -33,7 +40,9 @@ public abstract class GUILayer extends AssembledFlatLayer {
|
||||
|
||||
public GUILayer(String name, Layout layout) {
|
||||
super(name);
|
||||
|
||||
getRoot().setLayout(layout);
|
||||
getRoot().addInputListener(KeyEvent.class, this::attemptFocusTransfer, InputBus.Option.IGNORE_FOCUS);
|
||||
}
|
||||
|
||||
public Component getRoot() {
|
||||
@ -47,9 +56,81 @@ public abstract class GUILayer extends AssembledFlatLayer {
|
||||
getRoot().assemble(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stack frame for {@link #handleInput(InputEvent)}.
|
||||
*/
|
||||
private static class EventHandlingFrame {
|
||||
Component component;
|
||||
Iterator<Component> children;
|
||||
|
||||
void init(Component c) {
|
||||
component = c;
|
||||
children = c.getChildren().iterator();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
component = null;
|
||||
children = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stack for {@link #handleInput(InputEvent)}.
|
||||
*/
|
||||
private StashingStack<EventHandlingFrame> path = new StashingStack<>(64, EventHandlingFrame::new);
|
||||
|
||||
/*
|
||||
* This is essentially a depth-first iteration of the component tree. The
|
||||
* recursive procedure has been unrolled to reduce call stack length.
|
||||
*/
|
||||
@Override
|
||||
protected void handleInput(Input input) {
|
||||
getRoot().dispatchInput(input);
|
||||
public void handleInput(InputEvent event) {
|
||||
if (!path.isEmpty()) {
|
||||
throw new IllegalStateException(
|
||||
"path is not empty: " + path + ". Are events being processed concurrently?"
|
||||
);
|
||||
}
|
||||
|
||||
path.push().init(root);
|
||||
|
||||
while (!path.isEmpty()) {
|
||||
|
||||
Iterator<Component> it = path.peek().children;
|
||||
if (it.hasNext()) {
|
||||
|
||||
Component c = it.next();
|
||||
|
||||
if (c.isEnabled()) {
|
||||
if (c.getChildren().isEmpty()) {
|
||||
c.getInputBus().dispatch(event);
|
||||
} else {
|
||||
path.push().init(c);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
path.peek().component.getInputBus().dispatch(event);
|
||||
path.pop().reset();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void attemptFocusTransfer(KeyEvent e) {
|
||||
Component focused = getRoot().findFocused();
|
||||
|
||||
if (focused == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.getKey() == GLFW.GLFW_KEY_TAB && !e.isRelease()) {
|
||||
e.consume();
|
||||
if (e.hasShift()) {
|
||||
focused.focusPrevious();
|
||||
} else {
|
||||
focused.focusNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -104,26 +104,22 @@ public class RadioButton extends BasicButton {
|
||||
group.addChild(basicChild);
|
||||
addChild(group);
|
||||
|
||||
addListener(KeyEvent.class, e -> {
|
||||
if (e.isRelease())
|
||||
return false;
|
||||
addInputListener(KeyEvent.class, e -> {
|
||||
if (e.isRelease()) return;
|
||||
|
||||
if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) {
|
||||
if (this.group != null) {
|
||||
this.group.selectPrevious();
|
||||
this.group.getSelected().takeFocus();
|
||||
}
|
||||
|
||||
return true;
|
||||
e.consume();
|
||||
} else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
|
||||
if (this.group != null) {
|
||||
this.group.selectNext();
|
||||
this.group.getSelected().takeFocus();
|
||||
}
|
||||
return true;
|
||||
e.consume();
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
addAction(b -> setChecked(true));
|
||||
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.gui.event;
|
||||
|
||||
import glm.vec._2.d.Vec2d;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
|
||||
public class DragEvent extends ComponentEvent {
|
||||
|
||||
private final Vec2d currentChange = new Vec2d();
|
||||
private final Vec2d totalChange = new Vec2d();
|
||||
|
||||
public DragEvent(Component component, Vec2d currentChange, Vec2d totalChange) {
|
||||
super(component);
|
||||
this.currentChange.set(currentChange.x, currentChange.y);
|
||||
this.totalChange.set(totalChange.x, totalChange.y);
|
||||
}
|
||||
|
||||
public Vec2d getCurrentChange() {
|
||||
return currentChange;
|
||||
}
|
||||
|
||||
public double getCurrentChangeX() {
|
||||
return currentChange.x;
|
||||
}
|
||||
|
||||
public double getCurrentChangeY() {
|
||||
return currentChange.y;
|
||||
}
|
||||
|
||||
public Vec2d getTotalChange() {
|
||||
return totalChange;
|
||||
}
|
||||
|
||||
public double getTotalChangeX() {
|
||||
return totalChange.x;
|
||||
}
|
||||
|
||||
public double getTotalChangeY() {
|
||||
return totalChange.y;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.gui.event;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
|
||||
public class DragStartEvent extends ComponentEvent {
|
||||
|
||||
public DragStartEvent(Component component) {
|
||||
super(component);
|
||||
}
|
||||
|
||||
}
|
@ -15,48 +15,30 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.gui.event;
|
||||
|
||||
package ru.windcorp.progressia.client.graphics.input.bus;
|
||||
import glm.vec._2.d.Vec2d;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
public class DragStopEvent extends ComponentEvent {
|
||||
|
||||
public class Input {
|
||||
private final Vec2d totalChange = new Vec2d();
|
||||
|
||||
public static enum Target {
|
||||
FOCUSED, HOVERED, ALL
|
||||
public DragStopEvent(Component component, Vec2d totalChange) {
|
||||
super(component);
|
||||
this.totalChange.set(totalChange.x, totalChange.y);
|
||||
}
|
||||
|
||||
private InputEvent event;
|
||||
|
||||
private boolean isConsumed;
|
||||
|
||||
private Target target;
|
||||
|
||||
protected void initialize(InputEvent event, Target target) {
|
||||
this.event = event;
|
||||
this.target = target;
|
||||
|
||||
this.isConsumed = false;
|
||||
public Vec2d getTotalChange() {
|
||||
return totalChange;
|
||||
}
|
||||
|
||||
public InputEvent getEvent() {
|
||||
return event;
|
||||
public double getTotalChangeX() {
|
||||
return totalChange.x;
|
||||
}
|
||||
|
||||
public boolean isConsumed() {
|
||||
return isConsumed;
|
||||
}
|
||||
|
||||
public void setConsumed(boolean isConsumed) {
|
||||
this.isConsumed = isConsumed;
|
||||
}
|
||||
|
||||
public void consume() {
|
||||
setConsumed(true);
|
||||
}
|
||||
|
||||
public Target getTarget() {
|
||||
return target;
|
||||
public double getTotalChangeY() {
|
||||
return totalChange.y;
|
||||
}
|
||||
|
||||
}
|
@ -33,7 +33,6 @@ import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||
import ru.windcorp.progressia.client.localization.MutableString;
|
||||
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
||||
|
||||
@ -97,11 +96,9 @@ public class MenuLayer extends GUILayer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleInput(Input input) {
|
||||
|
||||
if (!input.isConsumed()) {
|
||||
InputEvent event = input.getEvent();
|
||||
public void handleInput(InputEvent event) {
|
||||
|
||||
if (!event.isConsumed()) {
|
||||
if (event instanceof KeyEvent) {
|
||||
KeyEvent keyEvent = (KeyEvent) event;
|
||||
if (keyEvent.isPress() && keyEvent.getKey() == GLFW.GLFW_KEY_ESCAPE) {
|
||||
@ -110,8 +107,8 @@ public class MenuLayer extends GUILayer {
|
||||
}
|
||||
}
|
||||
|
||||
super.handleInput(input);
|
||||
input.consume();
|
||||
super.handleInput(event);
|
||||
event.consume();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
package ru.windcorp.progressia.client.graphics.input;
|
||||
|
||||
import glm.vec._2.Vec2;
|
||||
import glm.vec._2.d.Vec2d;
|
||||
|
||||
public class CursorMoveEvent extends CursorEvent {
|
||||
@ -81,7 +80,10 @@ public class CursorMoveEvent extends CursorEvent {
|
||||
return getNewY() - getPreviousY();
|
||||
}
|
||||
|
||||
public Vec2 getChange(Vec2 result) {
|
||||
public Vec2d getChange(Vec2d result) {
|
||||
if (result == null) {
|
||||
result = new Vec2d();
|
||||
}
|
||||
return result.set(getChangeX(), getChangeY());
|
||||
}
|
||||
|
||||
|
@ -18,10 +18,32 @@
|
||||
|
||||
package ru.windcorp.progressia.client.graphics.input;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
|
||||
/**
|
||||
* An instance of user input.
|
||||
* <p>
|
||||
* User input events are typically generated by graphics backend between frames
|
||||
* and passed to the graphics layers from top to bottom. Layers that use
|
||||
* {@link Component}s will forward this event through the Component hierarchy.
|
||||
* <p>
|
||||
* Events have a {@code consumed} flag. A freshly-generated event will have this
|
||||
* flag set to {@code false}. Event listeners that process the event will
|
||||
* usually choose to raise the flag ("consume the event") to ask future
|
||||
* listeners to ignore this event. This is done to avoid multiple UI interfaces
|
||||
* reacting to single input. By default, listeners will not receive consumed
|
||||
* events; however, some listeners may choose to receive, handle and even
|
||||
* un-consume the event.
|
||||
* <p>
|
||||
* {@code InputEvent} objects may be reused for future input events after their
|
||||
* processing is complete; to obtain a static copy, use {@link #snapshot()}.
|
||||
*/
|
||||
public abstract class InputEvent {
|
||||
|
||||
private double time;
|
||||
|
||||
private boolean isConsumed = false;
|
||||
|
||||
public InputEvent(double time) {
|
||||
this.time = time;
|
||||
}
|
||||
@ -36,4 +58,16 @@ public abstract class InputEvent {
|
||||
|
||||
public abstract InputEvent snapshot();
|
||||
|
||||
public boolean isConsumed() {
|
||||
return isConsumed;
|
||||
}
|
||||
|
||||
public void setConsumed(boolean isConsumed) {
|
||||
this.isConsumed = isConsumed;
|
||||
}
|
||||
|
||||
public void consume() {
|
||||
setConsumed(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,16 +18,75 @@
|
||||
|
||||
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 com.google.common.collect.ImmutableMap;
|
||||
|
||||
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 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.mods = mods;
|
||||
}
|
||||
@ -43,6 +102,15 @@ public class KeyMatcher {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean matchesIgnoringAction(KeyEvent event) {
|
||||
if (event.getKey() != getKey())
|
||||
return false;
|
||||
if ((event.getMods() & getMods()) != getMods())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getKey() {
|
||||
return key;
|
||||
}
|
||||
@ -51,48 +119,24 @@ public class KeyMatcher {
|
||||
return mods;
|
||||
}
|
||||
|
||||
public static KeyMatcher.Builder of(int key) {
|
||||
return new KeyMatcher.Builder(key);
|
||||
public KeyMatcher with(int modifier) {
|
||||
return new KeyMatcher(key, mods | modifier);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
public KeyMatcher withShift() {
|
||||
return with(GLFW.GLFW_MOD_SHIFT);
|
||||
}
|
||||
|
||||
private final int key;
|
||||
private int mods = 0;
|
||||
public KeyMatcher withCtrl() {
|
||||
return with(GLFW.GLFW_MOD_CONTROL);
|
||||
}
|
||||
|
||||
public Builder(int key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
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 withAlt() {
|
||||
return with(GLFW.GLFW_MOD_ALT);
|
||||
}
|
||||
|
||||
public KeyMatcher withSuper() {
|
||||
return with(GLFW.GLFW_MOD_SUPER);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ public class Keys {
|
||||
}
|
||||
|
||||
public static int getCode(String internalName) {
|
||||
if (NAMES_TO_CODES.containsKey(internalName)) {
|
||||
if (!NAMES_TO_CODES.containsKey(internalName)) {
|
||||
return -1;
|
||||
} else {
|
||||
return NAMES_TO_CODES.get(internalName);
|
||||
|
@ -20,68 +20,396 @@ package ru.windcorp.progressia.client.graphics.input.bus;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
import ru.windcorp.jputil.ArrayUtil;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
import ru.windcorp.progressia.client.graphics.input.CursorEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||
import ru.windcorp.progressia.client.graphics.input.WheelEvent;
|
||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||
|
||||
/**
|
||||
* An event bus optionally related to a {@link Component} that delivers input
|
||||
* events to input listeners. This bus may skip listeners based on circumstance;
|
||||
* behavior can be customized for each listener with {@link Option}s.
|
||||
* <p>
|
||||
* By default, events are filtered by four checks before being delivered to each
|
||||
* listener:
|
||||
* <ol>
|
||||
* <li><em>Consumption check</em>: unless {@link Option#RECEIVE_CONSUMED
|
||||
* RECEIVE_CONSUMED} is set, events that are consumed will not be
|
||||
* delivered.</li>
|
||||
* <li><em>Hover check</em>: for certain event types (for example,
|
||||
* {@link WheelEvent} or {@link KeyEvent} that {@link KeyEvent#isMouse()
|
||||
* isMouse()}), the event will only be delivered if the component is hovered.
|
||||
* This check may be bypassed with option {@link Option#IGNORE_HOVER
|
||||
* IGNORE_HOVER} or made mandatory for all events with
|
||||
* {@link Option#REQUIRE_HOVER REQUIRE_HOVER}. Hover check automatically
|
||||
* succeeds if no component is provided.</li>
|
||||
* <li><em>Focus check</em>: for certain event types (for example,
|
||||
* {@link KeyEvent} that {@code !isMouse()}), the event will only be delivered
|
||||
* if the component has focus. This check may be bypassed with option
|
||||
* {@link Option#IGNORE_FOCUS IGNORE_FOCUS} or made mandatory for all events
|
||||
* with {@link Option#REQUIRE_FOCUS REQUIRE_FOCUS}. Focus check automatically
|
||||
* succeeds if no component is provided.</li>
|
||||
* <li><em>Type check</em>: events of type {@code E} are only delivered to
|
||||
* listeners registered with event type {@code T} if objects of type {@code E}
|
||||
* can be cast to {@code T}.</li>
|
||||
* </ol>
|
||||
* Checks 1-3 are bypassed when option {@link Option#ALWAYS ALWAYS} is
|
||||
* specified.
|
||||
*/
|
||||
public class InputBus {
|
||||
|
||||
private static class WrappedListener {
|
||||
/**
|
||||
* Options that allow customization of checks for listeners.
|
||||
*/
|
||||
public enum Option {
|
||||
|
||||
/**
|
||||
* Ignore checks for consumed events, hover and focus; deliver event if
|
||||
* at all possible. This is shorthand for {@link #RECEIVE_CONSUMED},
|
||||
* {@link #IGNORE_HOVER} and {@link #IGNORE_FOCUS}.
|
||||
*/
|
||||
ALWAYS,
|
||||
|
||||
/**
|
||||
* Receive events that were previously consumed.
|
||||
*/
|
||||
RECEIVE_CONSUMED,
|
||||
|
||||
/**
|
||||
* Do not process events if the listener is registered with a component
|
||||
* and the component is not hovered.
|
||||
*/
|
||||
REQUIRE_HOVER,
|
||||
|
||||
/**
|
||||
* Deliver events even if the event is limited to hovered components by
|
||||
* default.
|
||||
*/
|
||||
IGNORE_HOVER,
|
||||
|
||||
/**
|
||||
* Do not process events if the listener is registered with a component
|
||||
* and the component is not focused.
|
||||
*/
|
||||
REQUIRE_FOCUS,
|
||||
|
||||
/**
|
||||
* Deliver events even if the event is limited to focused components by
|
||||
* default.
|
||||
*/
|
||||
IGNORE_FOCUS,
|
||||
|
||||
/**
|
||||
* Deliver events according to
|
||||
* {@link KeyMatcher#matchesIgnoringAction(KeyEvent)} rather than
|
||||
* {@link KeyMatcher#matches(KeyEvent)} when a {@link KeyMatcher} is
|
||||
* specified.
|
||||
*/
|
||||
IGNORE_ACTION;
|
||||
|
||||
}
|
||||
|
||||
private enum YesNoDefault {
|
||||
YES, NO, DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener with check preferences resolved and type specified.
|
||||
*/
|
||||
private class WrappedListener {
|
||||
|
||||
private final Class<?> type;
|
||||
private final boolean handleConsumed;
|
||||
|
||||
private final boolean dropIfConsumed;
|
||||
private final YesNoDefault dropIfNotHovered;
|
||||
private final YesNoDefault dropIfNotFocused;
|
||||
|
||||
private final InputListener<?> listener;
|
||||
|
||||
public WrappedListener(
|
||||
Class<?> type,
|
||||
boolean handleConsumed,
|
||||
boolean dropIfConsumed,
|
||||
YesNoDefault dropIfNotHovered,
|
||||
YesNoDefault dropIfNotFocused,
|
||||
InputListener<?> listener
|
||||
) {
|
||||
this.type = type;
|
||||
this.handleConsumed = handleConsumed;
|
||||
this.dropIfConsumed = dropIfConsumed;
|
||||
this.dropIfNotHovered = dropIfNotHovered;
|
||||
this.dropIfNotFocused = dropIfNotFocused;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private boolean handles(Input input) {
|
||||
return (!input.isConsumed() || handleConsumed) &&
|
||||
type.isInstance(input.getEvent());
|
||||
private boolean handles(InputEvent input) {
|
||||
if (dropIfConsumed && input.isConsumed())
|
||||
return false;
|
||||
|
||||
switch (dropIfNotHovered) {
|
||||
case YES:
|
||||
if (!isHovered())
|
||||
return false;
|
||||
break;
|
||||
case NO:
|
||||
break;
|
||||
default:
|
||||
|
||||
if (isHovered())
|
||||
break;
|
||||
|
||||
if (input instanceof KeyEvent && ((KeyEvent) input).isMouse())
|
||||
return false;
|
||||
|
||||
if (input instanceof CursorEvent)
|
||||
return false;
|
||||
|
||||
if (input instanceof WheelEvent)
|
||||
return false;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
switch (dropIfNotFocused) {
|
||||
case YES:
|
||||
if (!isFocused())
|
||||
return false;
|
||||
break;
|
||||
case NO:
|
||||
break;
|
||||
default:
|
||||
|
||||
if (isFocused())
|
||||
break;
|
||||
|
||||
if (input instanceof KeyEvent && !((KeyEvent) input).isMouse())
|
||||
return false;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!type.isInstance(input))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the listener if the event is deemed appropriate by the four
|
||||
* checks.
|
||||
*
|
||||
* @param event the event to deliver
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void handle(Input input) {
|
||||
if (handles(input)) {
|
||||
boolean consumed = ((InputListener<InputEvent>) listener)
|
||||
.handle(
|
||||
(InputEvent) type.cast(input.getEvent())
|
||||
);
|
||||
public void handle(InputEvent event) {
|
||||
if (handles(event)) {
|
||||
// A runtime check of types has been performed; this is safe.
|
||||
InputListener<InputEvent> castListener = (InputListener<InputEvent>) listener;
|
||||
|
||||
input.setConsumed(consumed);
|
||||
try {
|
||||
castListener.handle(event);
|
||||
} catch (Exception e) {
|
||||
throw CrashReports.report(
|
||||
e,
|
||||
"InputListener %s for component %s has failed to receive event %s",
|
||||
listener,
|
||||
owner,
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The component queried for focus and hover. May be {@code null}.
|
||||
*/
|
||||
private final Component owner;
|
||||
|
||||
/**
|
||||
* Registered listeners.
|
||||
*/
|
||||
private final Collection<WrappedListener> listeners = new ArrayList<>(4);
|
||||
|
||||
public void dispatch(Input input) {
|
||||
listeners.forEach(l -> l.handle(input));
|
||||
/**
|
||||
* Creates a new input bus that consults the specified {@link Component} to
|
||||
* determine hover and focus.
|
||||
*
|
||||
* @param owner the component to use for hover and focus tests
|
||||
* @see #InputBus()
|
||||
*/
|
||||
public InputBus(Component owner) {
|
||||
this.owner = Objects.requireNonNull(owner, "owner");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new input bus that assumes all hover and focus checks are
|
||||
* successful.
|
||||
*
|
||||
* @see #InputBus(Component)
|
||||
*/
|
||||
public InputBus() {
|
||||
this.owner = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether hover should be assumed for this event bus.
|
||||
*
|
||||
* @return {@code true} iff no component is linked or the linked component
|
||||
* is hovered
|
||||
*/
|
||||
private boolean isHovered() {
|
||||
return owner == null ? true : owner.isHovered();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether focus should be assumed for this event bus.
|
||||
*
|
||||
* @return {@code true} iff no component is linked or the linked component
|
||||
* is focused
|
||||
*/
|
||||
private boolean isFocused() {
|
||||
return owner == null ? true : owner.isFocused();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches (delivers) the provided event to all appropriate listeners.
|
||||
*
|
||||
* @param event the event to process
|
||||
*/
|
||||
public void dispatch(InputEvent event) {
|
||||
Objects.requireNonNull(event, "event");
|
||||
for (WrappedListener listener : listeners) {
|
||||
listener.handle(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener on this bus.
|
||||
* <p>
|
||||
* {@code type} specifies the class of events that should be passed to this
|
||||
* listener. Only events of types that extend, implement or equal
|
||||
* {@code type} are processed.
|
||||
* <p>
|
||||
* Zero or more {@link Option}s may be specified to enable or disable the
|
||||
* processing of certain events in certain circumstances. See
|
||||
* {@linkplain InputBus class description} for a detailed breakdown of the
|
||||
* checks performed and the effects of various options. When providing
|
||||
* options to this method, later options override the effects of previous
|
||||
* options.
|
||||
* <p>
|
||||
* Option {@link Option#IGNORE_ACTION IGNORE_ACTION} is ignored silently.
|
||||
*
|
||||
* @param type the event class to deliver
|
||||
* @param listener the listener
|
||||
* @param options the options for this listener
|
||||
*/
|
||||
public <T extends InputEvent> void register(
|
||||
Class<? extends T> type,
|
||||
boolean handlesConsumed,
|
||||
InputListener<T> listener
|
||||
InputListener<T> listener,
|
||||
Option... options
|
||||
) {
|
||||
listeners.add(new WrappedListener(type, handlesConsumed, listener));
|
||||
Objects.requireNonNull(type, "type");
|
||||
Objects.requireNonNull(listener, "listener");
|
||||
|
||||
boolean dropIfConsumed = true;
|
||||
YesNoDefault dropIfNotHovered = YesNoDefault.DEFAULT;
|
||||
YesNoDefault dropIfNotFocused = YesNoDefault.DEFAULT;
|
||||
|
||||
if (options != null) {
|
||||
for (Option option : options) {
|
||||
switch (option) {
|
||||
case ALWAYS:
|
||||
dropIfConsumed = false;
|
||||
dropIfNotHovered = YesNoDefault.NO;
|
||||
dropIfNotFocused = YesNoDefault.NO;
|
||||
break;
|
||||
case RECEIVE_CONSUMED:
|
||||
dropIfConsumed = false;
|
||||
break;
|
||||
case REQUIRE_HOVER:
|
||||
dropIfNotHovered = YesNoDefault.YES;
|
||||
break;
|
||||
case IGNORE_HOVER:
|
||||
dropIfNotFocused = YesNoDefault.NO;
|
||||
break;
|
||||
case REQUIRE_FOCUS:
|
||||
dropIfNotHovered = YesNoDefault.YES;
|
||||
break;
|
||||
case IGNORE_FOCUS:
|
||||
dropIfNotFocused = YesNoDefault.NO;
|
||||
break;
|
||||
case IGNORE_ACTION:
|
||||
// Ignore
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected option " + option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
listeners.add(new WrappedListener(type, dropIfConsumed, dropIfNotHovered, dropIfNotFocused, listener));
|
||||
}
|
||||
|
||||
public <T extends InputEvent> void register(
|
||||
Class<? extends T> type,
|
||||
InputListener<T> listener
|
||||
) {
|
||||
register(type, false, listener);
|
||||
/**
|
||||
* Registers a {@link KeyEvent} listener on this bus. An event has to match
|
||||
* the provided {@link KeyMatcher} to be delivered to the listener.
|
||||
* <p>
|
||||
* Zero or more {@link Option}s may be specified to enable or disable the
|
||||
* processing of certain events in certain circumstances. See
|
||||
* {@linkplain InputBus class description} for a detailed breakdown of the
|
||||
* checks performed and the effects of various options. When providing
|
||||
* options to this method, later options override the effects of previous
|
||||
* options.
|
||||
* <p>
|
||||
* Option {@link Option#IGNORE_ACTION IGNORE_ACTION} requests that events
|
||||
* are delivered according to
|
||||
* {@link KeyMatcher#matchesIgnoringAction(KeyEvent)} rather than
|
||||
* {@link KeyMatcher#matches(KeyEvent)}.
|
||||
* specified.
|
||||
*
|
||||
* @param matcher an event filter
|
||||
* @param listener the listener
|
||||
* @param options the options for this listener
|
||||
*/
|
||||
public void register(KeyMatcher matcher, InputListener<? super KeyEvent> listener, Option... options) {
|
||||
Objects.requireNonNull(matcher, "matcher");
|
||||
Objects.requireNonNull(listener, "listener");
|
||||
|
||||
InputListener<KeyEvent> filteringListener;
|
||||
|
||||
if (ArrayUtil.firstIndexOf(options, Option.IGNORE_ACTION) != -1) {
|
||||
filteringListener = e -> {
|
||||
if (matcher.matchesIgnoringAction(e)) {
|
||||
listener.handle(e);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
filteringListener = e -> {
|
||||
if (matcher.matches(e)) {
|
||||
listener.handle(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
register(KeyEvent.class, filteringListener, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all occurrences of the provided listener from this bus.
|
||||
*
|
||||
* @param listener the listener to unregister
|
||||
*/
|
||||
public void unregister(InputListener<?> listener) {
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
listeners.removeIf(l -> l.listener == listener);
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,6 @@ import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
@FunctionalInterface
|
||||
public interface InputListener<T extends InputEvent> {
|
||||
|
||||
boolean handle(T event);
|
||||
void handle(T event);
|
||||
|
||||
}
|
||||
|
@ -92,4 +92,13 @@ public class EntityAnchor implements Anchor {
|
||||
return modes;
|
||||
}
|
||||
|
||||
public EntityData getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Anchor for entity " + entity;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import ru.windcorp.progressia.client.comms.controls.InputBasedControls;
|
||||
import ru.windcorp.progressia.client.graphics.Layer;
|
||||
import ru.windcorp.progressia.client.graphics.backend.FaceCulling;
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||
import ru.windcorp.progressia.client.graphics.model.Shapes.PppBuilder;
|
||||
@ -45,7 +45,7 @@ import ru.windcorp.progressia.common.util.Vectors;
|
||||
import ru.windcorp.progressia.common.world.GravityModel;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||
import ru.windcorp.progressia.test.CollisionModelRenderer;
|
||||
import ru.windcorp.progressia.test.TestPlayerControls;
|
||||
import ru.windcorp.progressia.test.controls.TestPlayerControls;
|
||||
|
||||
public class LayerWorld extends Layer {
|
||||
|
||||
@ -214,6 +214,9 @@ public class LayerWorld extends Layer {
|
||||
if (ClientState.getInstance().getLocalPlayer().getEntity() == entity && tmp_testControls.isFlying()) {
|
||||
return;
|
||||
}
|
||||
if (entity.getId().equals("Test:NoclipCamera")) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vec3 gravitationalAcceleration = Vectors.grab3();
|
||||
gm.getGravity(entity.getPosition(), gravitationalAcceleration);
|
||||
@ -225,14 +228,9 @@ public class LayerWorld extends Layer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleInput(Input input) {
|
||||
if (input.isConsumed())
|
||||
return;
|
||||
|
||||
tmp_testControls.handleInput(input);
|
||||
|
||||
if (!input.isConsumed()) {
|
||||
inputBasedControls.handleInput(input);
|
||||
public void handleInput(InputEvent event) {
|
||||
if (!event.isConsumed()) {
|
||||
inputBasedControls.handleInput(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,6 +73,12 @@ public class Localizer {
|
||||
return language;
|
||||
}
|
||||
|
||||
public List<String> getLanguages() {
|
||||
List<String> result = new ArrayList<>(langList.keySet());
|
||||
result.sort(null);
|
||||
return result;
|
||||
}
|
||||
|
||||
public synchronized String getValue(String key) {
|
||||
if (data == null) {
|
||||
throw new IllegalStateException("Localizer not yet initialized");
|
||||
|
@ -105,6 +105,9 @@ public class Collider {
|
||||
// For every pair of colls
|
||||
for (int i = 0; i < colls.size(); ++i) {
|
||||
Collideable a = colls.get(i);
|
||||
if (a.getCollisionModel() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tuneWorldCollisionHelper(a, tickLength, world, workspace);
|
||||
|
||||
@ -115,6 +118,10 @@ public class Collider {
|
||||
|
||||
for (int j = i + 1; j < colls.size(); ++j) {
|
||||
Collideable b = colls.get(j);
|
||||
if (b.getCollisionModel() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Collision collision = getCollision(a, b, tickLength, workspace);
|
||||
result = workspace.updateLatestCollision(result, collision);
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ public abstract class StatefulObject extends Namespaced implements Encodable {
|
||||
|
||||
StatefulObject statefulObj = (StatefulObject) obj;
|
||||
|
||||
if (statefulObj.getId().equals(this.getId()))
|
||||
if (!statefulObj.getId().equals(this.getId()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
@ -380,4 +380,14 @@ public class EntityData extends StatefulObject implements Collideable, EntityGen
|
||||
super.read(input, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return this == obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Long.hashCode(entityId);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
||||
import ru.windcorp.progressia.server.Player;
|
||||
import ru.windcorp.progressia.server.Server;
|
||||
import ru.windcorp.progressia.server.ServerState;
|
||||
import ru.windcorp.progressia.test.controls.TestPlayerControls;
|
||||
|
||||
public class LayerButtonTest extends MenuLayer {
|
||||
|
||||
|
261
src/main/java/ru/windcorp/progressia/test/LayerDebug.java
Executable file
261
src/main/java/ru/windcorp/progressia/test/LayerDebug.java
Executable file
@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ru.windcorp.progressia.test;
|
||||
|
||||
import glm.vec._3.Vec3;
|
||||
import ru.windcorp.progressia.client.Client;
|
||||
import ru.windcorp.progressia.client.ClientState;
|
||||
import ru.windcorp.progressia.client.graphics.Colors;
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||
import ru.windcorp.progressia.client.graphics.gui.DynamicLabel;
|
||||
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Label;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Group;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
||||
import ru.windcorp.progressia.client.localization.Localizer;
|
||||
import ru.windcorp.progressia.client.localization.MutableString;
|
||||
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
||||
import ru.windcorp.progressia.client.world.WorldRender;
|
||||
import ru.windcorp.progressia.common.Units;
|
||||
import ru.windcorp.progressia.common.util.dynstr.DynamicStrings;
|
||||
import ru.windcorp.progressia.server.Server;
|
||||
import ru.windcorp.progressia.server.ServerState;
|
||||
import ru.windcorp.progressia.test.controls.TestPlayerControls;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class LayerDebug extends GUILayer {
|
||||
|
||||
private final List<Runnable> updateTriggers = new ArrayList<>();
|
||||
|
||||
public LayerDebug() {
|
||||
super("LayerDebug", new LayoutAlign(0, 1, 5));
|
||||
getRoot().addChild(new Group("Displays", new LayoutVertical(5)));
|
||||
|
||||
TestPlayerControls tpc = TestPlayerControls.getInstance();
|
||||
|
||||
addDynamicDisplay(
|
||||
"FPSDisplay",
|
||||
DynamicStrings.builder()
|
||||
.addDyn(new MutableStringLocalized("LayerDebug.FPSDisplay"))
|
||||
.addDyn(() -> FPS_RECORD.update(GraphicsInterface.getFPS()), 5, 1)
|
||||
.addDyn(() -> GraphicsBackend.isFullscreen() ? " Fullscreen" : "")
|
||||
.addDyn(() -> GraphicsBackend.isVSyncEnabled() ? " VSync" : "")
|
||||
.buildSupplier()
|
||||
);
|
||||
|
||||
addDynamicDisplay("TPSDisplay", LayerDebug::getTPS);
|
||||
|
||||
addDynamicDisplay(
|
||||
"ChunkStatsDisplay",
|
||||
DynamicStrings.builder()
|
||||
.addDyn(new MutableStringLocalized("LayerDebug.ChunkStatsDisplay"))
|
||||
.addDyn(() -> {
|
||||
if (ClientState.getInstance() == null) {
|
||||
return -1;
|
||||
} else {
|
||||
WorldRender world = ClientState.getInstance().getWorld();
|
||||
return world.getChunks().size() - world.getPendingChunkUpdates();
|
||||
}
|
||||
}, 4)
|
||||
.add('/')
|
||||
.addDyn(() -> {
|
||||
if (ClientState.getInstance() == null) {
|
||||
return -1;
|
||||
} else {
|
||||
return ClientState.getInstance().getWorld().getPendingChunkUpdates();
|
||||
}
|
||||
}, 4)
|
||||
.add('/')
|
||||
.addDyn(() -> {
|
||||
if (ServerState.getInstance() == null) {
|
||||
return -1;
|
||||
} else {
|
||||
return ServerState.getInstance().getWorld().getChunks().size();
|
||||
}
|
||||
}, 4)
|
||||
.buildSupplier()
|
||||
);
|
||||
|
||||
addDynamicDisplay("PosDisplay", LayerDebug::getPos);
|
||||
|
||||
addDisplay("SelectedBlockDisplay", () -> tpc.isBlockSelected() ? ">" : " ", () -> tpc.getSelectedBlock().getId());
|
||||
addDisplay("SelectedTileDisplay", () -> tpc.isBlockSelected() ? " " : ">", () -> tpc.getSelectedTile().getId());
|
||||
addDisplay("PlacementModeHint", () -> "\u2B04");
|
||||
}
|
||||
|
||||
private void addDisplay(String name, Supplier<?>... params) {
|
||||
Font font = new Font().withColor(Colors.WHITE).deriveOutlined();
|
||||
Label component = new Label(name, font, tmp_dynFormat("LayerDebug." + name, params));
|
||||
getRoot().getChild(0).addChild(component);
|
||||
|
||||
for (Supplier<?> param : params) {
|
||||
if (param == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
updateTriggers.add(new Runnable() {
|
||||
|
||||
private Object displayedValue;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Object newValue = param.get();
|
||||
if (!Objects.equals(newValue, displayedValue)) {
|
||||
component.update();
|
||||
}
|
||||
displayedValue = newValue;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void addDynamicDisplay(String name, Supplier<CharSequence> contents) {
|
||||
Font font = new Font().withColor(Colors.WHITE).deriveOutlined();
|
||||
DynamicLabel component = new DynamicLabel(name, font, contents, 128);
|
||||
getRoot().getChild(0).addChild(component);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRender() {
|
||||
updateTriggers.forEach(Runnable::run);
|
||||
super.doRender();
|
||||
}
|
||||
|
||||
private static class Averager {
|
||||
|
||||
private static final int DISPLAY_INERTIA = 32;
|
||||
private static final double UPDATE_INTERVAL = Units.get(50.0, "ms");
|
||||
|
||||
private final double[] values = new double[DISPLAY_INERTIA];
|
||||
private int size;
|
||||
private int head;
|
||||
|
||||
private long lastUpdate;
|
||||
|
||||
public void add(double value) {
|
||||
if (size == values.length) {
|
||||
values[head] = value;
|
||||
head++;
|
||||
if (head == values.length)
|
||||
head = 0;
|
||||
} else {
|
||||
values[size] = value;
|
||||
size++;
|
||||
}
|
||||
}
|
||||
|
||||
public double average() {
|
||||
double product = 1;
|
||||
|
||||
if (size == values.length) {
|
||||
for (double d : values)
|
||||
product *= d;
|
||||
} else {
|
||||
for (int i = 0; i < size; ++i)
|
||||
product *= values[i];
|
||||
}
|
||||
|
||||
return Math.pow(product, 1.0 / size);
|
||||
}
|
||||
|
||||
public double update(double value) {
|
||||
long now = (long) (GraphicsInterface.getTime() / UPDATE_INTERVAL);
|
||||
if (lastUpdate != now) {
|
||||
lastUpdate = now;
|
||||
add(value);
|
||||
}
|
||||
|
||||
return average();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final String[] CLOCK_CHARS = "\u2591\u2598\u259d\u2580\u2596\u258c\u259e\u259b\u2597\u259a\u2590\u259c\u2584\u2599\u259f\u2588"
|
||||
.chars().mapToObj(c -> ((char) c) + "").toArray(String[]::new);
|
||||
|
||||
private static String getTPSClockChar() {
|
||||
return CLOCK_CHARS[(int) (ServerState.getInstance().getUptimeTicks() % CLOCK_CHARS.length)];
|
||||
}
|
||||
|
||||
private static final Averager FPS_RECORD = new Averager();
|
||||
private static final Averager TPS_RECORD = new Averager();
|
||||
|
||||
private static final Supplier<CharSequence> TPS_STRING = DynamicStrings.builder()
|
||||
.addDyn(new MutableStringLocalized("LayerDebug.TPSDisplay"))
|
||||
.addDyn(() -> TPS_RECORD.update(ServerState.getInstance().getTPS()), 5, 1)
|
||||
.add(' ')
|
||||
.addDyn(LayerDebug::getTPSClockChar)
|
||||
.buildSupplier();
|
||||
|
||||
private static final Supplier<CharSequence> POS_STRING = DynamicStrings.builder()
|
||||
.addDyn(new MutableStringLocalized("LayerDebug.PosDisplay"))
|
||||
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().x, 7, 1)
|
||||
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().y, 7, 1)
|
||||
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().z, 7, 1)
|
||||
.buildSupplier();
|
||||
|
||||
private static CharSequence getTPS() {
|
||||
Server server = ServerState.getInstance();
|
||||
if (server == null)
|
||||
return Localizer.getInstance().getValue("LayerDebug.TPSDisplay.NA");
|
||||
|
||||
return TPS_STRING.get();
|
||||
}
|
||||
|
||||
private static CharSequence getPos() {
|
||||
Client client = ClientState.getInstance();
|
||||
if (client == null)
|
||||
return Localizer.getInstance().getValue("LayerDebug.PosDisplay.NA.Client");
|
||||
|
||||
Vec3 pos = client.getCamera().getLastAnchorPosition();
|
||||
if (Float.isNaN(pos.x)) {
|
||||
return Localizer.getInstance().getValue("LayerDebug.PosDisplay.NA.Entity");
|
||||
} else {
|
||||
return POS_STRING.get();
|
||||
}
|
||||
}
|
||||
|
||||
private static MutableString tmp_dynFormat(String formatKey, Supplier<?>... suppliers) {
|
||||
return new MutableStringLocalized(formatKey).apply(s -> {
|
||||
Object[] args = new Object[suppliers.length];
|
||||
|
||||
for (int i = 0; i < suppliers.length; ++i) {
|
||||
Supplier<?> supplier = suppliers[i];
|
||||
|
||||
Object value = supplier != null ? supplier.get() : "null";
|
||||
if (!(value instanceof Number)) {
|
||||
value = Objects.toString(value);
|
||||
}
|
||||
|
||||
args[i] = value;
|
||||
}
|
||||
|
||||
return String.format(s, args);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -1,422 +0,0 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ru.windcorp.progressia.test;
|
||||
|
||||
import glm.vec._3.Vec3;
|
||||
import glm.vec._4.Vec4;
|
||||
import ru.windcorp.progressia.client.Client;
|
||||
import ru.windcorp.progressia.client.ClientState;
|
||||
import ru.windcorp.progressia.client.graphics.Colors;
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||
import ru.windcorp.progressia.client.graphics.gui.DynamicLabel;
|
||||
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Label;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Group;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
||||
import ru.windcorp.progressia.client.localization.Localizer;
|
||||
import ru.windcorp.progressia.client.localization.MutableString;
|
||||
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
||||
import ru.windcorp.progressia.client.world.WorldRender;
|
||||
import ru.windcorp.progressia.common.Units;
|
||||
import ru.windcorp.progressia.common.util.dynstr.DynamicStrings;
|
||||
import ru.windcorp.progressia.server.Server;
|
||||
import ru.windcorp.progressia.server.ServerState;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class LayerTestGUI extends GUILayer {
|
||||
|
||||
public LayerTestGUI() {
|
||||
super("LayerTestGui", new LayoutAlign(0, 1, 5));
|
||||
|
||||
Group group = new Group("ControlDisplays", new LayoutVertical(5));
|
||||
|
||||
Vec4 color = Colors.WHITE;
|
||||
Font font = new Font().withColor(color).deriveOutlined();
|
||||
|
||||
TestPlayerControls tpc = TestPlayerControls.getInstance();
|
||||
|
||||
group.addChild(
|
||||
new Label(
|
||||
"IsFlyingDisplay",
|
||||
font,
|
||||
tmp_dynFormat("LayerTestGUI.IsFlyingDisplay", tpc::isFlying)
|
||||
)
|
||||
);
|
||||
|
||||
group.addChild(
|
||||
new Label(
|
||||
"IsSprintingDisplay",
|
||||
font,
|
||||
tmp_dynFormat("LayerTestGUI.IsSprintingDisplay", tpc::isSprinting)
|
||||
)
|
||||
);
|
||||
|
||||
group.addChild(
|
||||
new Label(
|
||||
"CameraModeDisplay",
|
||||
font,
|
||||
tmp_dynFormat(
|
||||
"LayerTestGUI.CameraModeDisplay",
|
||||
ClientState.getInstance().getCamera()::getCurrentModeIndex
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
group.addChild(
|
||||
new Label(
|
||||
"LanguageDisplay",
|
||||
font,
|
||||
tmp_dynFormat("LayerTestGUI.LanguageDisplay", Localizer.getInstance()::getLanguage)
|
||||
)
|
||||
);
|
||||
|
||||
group.addChild(
|
||||
new Label(
|
||||
"FullscreenDisplay",
|
||||
font,
|
||||
tmp_dynFormat("LayerTestGUI.IsFullscreen", GraphicsBackend::isFullscreen)
|
||||
)
|
||||
);
|
||||
|
||||
group.addChild(
|
||||
new Label(
|
||||
"VSyncDisplay",
|
||||
font,
|
||||
tmp_dynFormat("LayerTestGUI.IsVSync", GraphicsBackend::isVSyncEnabled)
|
||||
)
|
||||
);
|
||||
|
||||
group.addChild(
|
||||
new DynamicLabel(
|
||||
"FPSDisplay",
|
||||
font,
|
||||
DynamicStrings.builder()
|
||||
.addDyn(new MutableStringLocalized("LayerTestGUI.FPSDisplay"))
|
||||
.addDyn(() -> FPS_RECORD.update(GraphicsInterface.getFPS()), 5, 1)
|
||||
.buildSupplier(),
|
||||
128
|
||||
)
|
||||
);
|
||||
|
||||
group.addChild(
|
||||
new DynamicLabel(
|
||||
"TPSDisplay",
|
||||
font,
|
||||
LayerTestGUI::getTPS,
|
||||
128
|
||||
)
|
||||
);
|
||||
|
||||
group.addChild(
|
||||
new DynamicLabel(
|
||||
"ChunkStatsDisplay",
|
||||
font,
|
||||
DynamicStrings.builder()
|
||||
.addDyn(new MutableStringLocalized("LayerTestGUI.ChunkStatsDisplay"))
|
||||
.addDyn(() -> {
|
||||
if (ClientState.getInstance() == null) {
|
||||
return -1;
|
||||
} else {
|
||||
WorldRender world = ClientState.getInstance().getWorld();
|
||||
return world.getChunks().size() - world.getPendingChunkUpdates();
|
||||
}
|
||||
}, 4)
|
||||
.add('/')
|
||||
.addDyn(() -> {
|
||||
if (ClientState.getInstance() == null) {
|
||||
return -1;
|
||||
} else {
|
||||
return ClientState.getInstance().getWorld().getPendingChunkUpdates();
|
||||
}
|
||||
}, 4)
|
||||
.add('/')
|
||||
.addDyn(() -> {
|
||||
if (ServerState.getInstance() == null) {
|
||||
return -1;
|
||||
} else {
|
||||
return ServerState.getInstance().getWorld().getChunks().size();
|
||||
}
|
||||
}, 4)
|
||||
.buildSupplier(),
|
||||
128
|
||||
)
|
||||
);
|
||||
|
||||
group.addChild(
|
||||
new DynamicLabel(
|
||||
"PosDisplay",
|
||||
font,
|
||||
LayerTestGUI::getPos,
|
||||
128
|
||||
)
|
||||
);
|
||||
|
||||
group.addChild(
|
||||
new Label(
|
||||
"SelectedBlockDisplay",
|
||||
font,
|
||||
tmp_dynFormat(
|
||||
"LayerTestGUI.SelectedBlockDisplay",
|
||||
() -> tpc.isBlockSelected() ? ">" : " ",
|
||||
() -> tpc.getSelectedBlock().getId()
|
||||
)
|
||||
)
|
||||
);
|
||||
group.addChild(
|
||||
new Label(
|
||||
"SelectedTileDisplay",
|
||||
font,
|
||||
tmp_dynFormat(
|
||||
"LayerTestGUI.SelectedTileDisplay",
|
||||
() -> tpc.isBlockSelected() ? " " : ">",
|
||||
() -> tpc.getSelectedTile().getId()
|
||||
)
|
||||
)
|
||||
);
|
||||
group.addChild(
|
||||
new Label(
|
||||
"PlacementModeHint",
|
||||
font,
|
||||
new MutableStringLocalized("LayerTestGUI.PlacementModeHint").format("\u2B04")
|
||||
)
|
||||
);
|
||||
|
||||
getRoot().addChild(group);
|
||||
}
|
||||
|
||||
public Runnable getUpdateCallback() {
|
||||
Collection<Label> labels = new ArrayList<>();
|
||||
getRoot().getChild(0).getChildren().forEach(c -> {
|
||||
if (c instanceof Label) {
|
||||
labels.add((Label) c);
|
||||
}
|
||||
});
|
||||
return () -> labels.forEach(Label::update);
|
||||
}
|
||||
|
||||
private static class Averager {
|
||||
|
||||
private static final int DISPLAY_INERTIA = 32;
|
||||
private static final double UPDATE_INTERVAL = Units.get(50.0, "ms");
|
||||
|
||||
private final double[] values = new double[DISPLAY_INERTIA];
|
||||
private int size;
|
||||
private int head;
|
||||
|
||||
private long lastUpdate;
|
||||
|
||||
public void add(double value) {
|
||||
if (size == values.length) {
|
||||
values[head] = value;
|
||||
head++;
|
||||
if (head == values.length)
|
||||
head = 0;
|
||||
} else {
|
||||
values[size] = value;
|
||||
size++;
|
||||
}
|
||||
}
|
||||
|
||||
public double average() {
|
||||
double product = 1;
|
||||
|
||||
if (size == values.length) {
|
||||
for (double d : values)
|
||||
product *= d;
|
||||
} else {
|
||||
for (int i = 0; i < size; ++i)
|
||||
product *= values[i];
|
||||
}
|
||||
|
||||
return Math.pow(product, 1.0 / size);
|
||||
}
|
||||
|
||||
public double update(double value) {
|
||||
long now = (long) (GraphicsInterface.getTime() / UPDATE_INTERVAL);
|
||||
if (lastUpdate != now) {
|
||||
lastUpdate = now;
|
||||
add(value);
|
||||
}
|
||||
|
||||
return average();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final String[] CLOCK_CHARS = "\u2591\u2598\u259d\u2580\u2596\u258c\u259e\u259b\u2597\u259a\u2590\u259c\u2584\u2599\u259f\u2588"
|
||||
.chars().mapToObj(c -> ((char) c) + "").toArray(String[]::new);
|
||||
|
||||
private static String getTPSClockChar() {
|
||||
return CLOCK_CHARS[(int) (ServerState.getInstance().getUptimeTicks() % CLOCK_CHARS.length)];
|
||||
}
|
||||
|
||||
private static final Averager FPS_RECORD = new Averager();
|
||||
private static final Averager TPS_RECORD = new Averager();
|
||||
|
||||
private static final Supplier<CharSequence> TPS_STRING = DynamicStrings.builder()
|
||||
.addDyn(new MutableStringLocalized("LayerTestGUI.TPSDisplay"))
|
||||
.addDyn(() -> TPS_RECORD.update(ServerState.getInstance().getTPS()), 5, 1)
|
||||
.add(' ')
|
||||
.addDyn(LayerTestGUI::getTPSClockChar)
|
||||
.buildSupplier();
|
||||
|
||||
private static final Supplier<CharSequence> POS_STRING = DynamicStrings.builder()
|
||||
.addDyn(new MutableStringLocalized("LayerTestGUI.PosDisplay"))
|
||||
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().x, 7, 1)
|
||||
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().y, 7, 1)
|
||||
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().z, 7, 1)
|
||||
.buildSupplier();
|
||||
|
||||
private static CharSequence getTPS() {
|
||||
Server server = ServerState.getInstance();
|
||||
if (server == null)
|
||||
return Localizer.getInstance().getValue("LayerTestGUI.TPSDisplay.NA");
|
||||
|
||||
return TPS_STRING.get();
|
||||
}
|
||||
|
||||
private static CharSequence getPos() {
|
||||
Client client = ClientState.getInstance();
|
||||
if (client == null)
|
||||
return Localizer.getInstance().getValue("LayerTestGUI.PosDisplay.NA.Client");
|
||||
|
||||
Vec3 pos = client.getCamera().getLastAnchorPosition();
|
||||
if (Float.isNaN(pos.x)) {
|
||||
return Localizer.getInstance().getValue("LayerTestGUI.PosDisplay.NA.Entity");
|
||||
} else {
|
||||
return POS_STRING.get();
|
||||
}
|
||||
}
|
||||
|
||||
private static MutableString tmp_dynFormat(String formatKey, Supplier<?>... suppliers) {
|
||||
return new MutableStringLocalized(formatKey).apply(s -> {
|
||||
Object[] args = new Object[suppliers.length];
|
||||
|
||||
for (int i = 0; i < suppliers.length; ++i) {
|
||||
Supplier<?> supplier = suppliers[i];
|
||||
|
||||
Object value = supplier != null ? supplier.get() : "null";
|
||||
if (!(value instanceof Number)) {
|
||||
value = Objects.toString(value);
|
||||
}
|
||||
|
||||
args[i] = value;
|
||||
}
|
||||
|
||||
return String.format(s, args);
|
||||
});
|
||||
}
|
||||
|
||||
// private static class DebugComponent extends Component {
|
||||
// private final int color;
|
||||
//
|
||||
// public DebugComponent(String name, Vec2i size, int color) {
|
||||
// super(name);
|
||||
// this.color = color;
|
||||
//
|
||||
// setPreferredSize(size);
|
||||
//
|
||||
// addListener(new Object() {
|
||||
// @Subscribe
|
||||
// public void onHoverChanged(HoverEvent e) {
|
||||
// requestReassembly();
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// addListener(KeyEvent.class, this::onClicked);
|
||||
// }
|
||||
//
|
||||
// private boolean onClicked(KeyEvent event) {
|
||||
// if (!event.isMouse()) {
|
||||
// return false;
|
||||
// } else if (event.isPress() && event.isLeftMouseButton()) {
|
||||
// System.out.println("You pressed a Component!");
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected void assembleSelf(RenderTarget target) {
|
||||
// target.fill(getX(), getY(), getWidth(), getHeight(), Colors.BLACK);
|
||||
//
|
||||
// target.fill(
|
||||
// getX() + 2, getY() + 2,
|
||||
// getWidth() - 4, getHeight() - 4,
|
||||
// isHovered() ? Colors.DEBUG_YELLOW : color
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public LayerTestGUI() {
|
||||
// super("LayerTestGui", new LayoutAlign(1, 0.75, 5));
|
||||
//
|
||||
// Panel panel = new Panel("Alex", new LayoutVertical(5));
|
||||
//
|
||||
// panel.addChild(new DebugComponent("Bravo", new Vec2i(200, 100), 0x44FF44));
|
||||
//
|
||||
// Component charlie = new DebugComponent("Charlie", null, 0x222222);
|
||||
// charlie.setLayout(new LayoutVertical(5));
|
||||
//
|
||||
// //Debug
|
||||
// Localizer.getInstance().setLanguage("ru-RU");
|
||||
// MutableString epsilon = new MutableStringLocalized("Epsilon")
|
||||
// .addListener(() -> ((Label)charlie.getChild(0)).update()).format(34, "thirty-four");
|
||||
// // These two are swapped in code due to a bug in layouts, fixing ATM
|
||||
// charlie.addChild(
|
||||
// new Label(
|
||||
// "Delta",
|
||||
// new Font().withColor(0xCCBB44).deriveShadow().deriveBold(),
|
||||
// "Пре-альфа!"
|
||||
// )
|
||||
// );
|
||||
// charlie.addChild(
|
||||
// new Label(
|
||||
// "Epsilon",
|
||||
// new Font().withColor(0x4444BB).deriveItalic(),
|
||||
// () -> epsilon.get().concat("\u269b")
|
||||
// )
|
||||
// );
|
||||
// panel.addChild(charlie);
|
||||
//
|
||||
//
|
||||
// charlie.addListener(KeyEvent.class, e -> {
|
||||
// if(e.isPress() && e.getKey() == GLFW.GLFW_KEY_L) {
|
||||
// Localizer localizer = Localizer.getInstance();
|
||||
// if (localizer.getLanguage().equals("ru-RU")) {
|
||||
// localizer.setLanguage("en-US");
|
||||
// } else {
|
||||
// localizer.setLanguage("ru-RU");
|
||||
// }
|
||||
// return true;
|
||||
// } return false;
|
||||
// });
|
||||
// charlie.setFocusable(true);
|
||||
// charlie.takeFocus();
|
||||
//
|
||||
// getRoot().addChild(panel);
|
||||
// }
|
||||
|
||||
}
|
@ -27,8 +27,8 @@ import ru.windcorp.progressia.client.graphics.Colors;
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer;
|
||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||
|
||||
public class LayerTestUI extends AssembledFlatLayer {
|
||||
|
||||
@ -91,7 +91,7 @@ public class LayerTestUI extends AssembledFlatLayer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleInput(Input input) {
|
||||
public void handleInput(InputEvent event) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
|
@ -28,15 +28,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
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.cro.ChunkRenderOptimizerRegistry;
|
||||
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.common.collision.AABB;
|
||||
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.world.GravityModelRegistry;
|
||||
import ru.windcorp.progressia.common.world.block.*;
|
||||
import ru.windcorp.progressia.common.world.entity.*;
|
||||
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.server.Server;
|
||||
import ru.windcorp.progressia.server.comms.controls.*;
|
||||
import ru.windcorp.progressia.server.world.block.*;
|
||||
import ru.windcorp.progressia.server.world.context.ServerBlockContext;
|
||||
import ru.windcorp.progressia.server.world.context.ServerTileContext;
|
||||
import ru.windcorp.progressia.server.world.context.ServerTileStackContext;
|
||||
import ru.windcorp.progressia.server.world.entity.*;
|
||||
import ru.windcorp.progressia.server.world.generation.planet.PlanetGravityModel;
|
||||
import ru.windcorp.progressia.server.world.tile.*;
|
||||
import ru.windcorp.progressia.test.Flowers.FlowerVariant;
|
||||
import ru.windcorp.progressia.test.Rocks.RockType;
|
||||
import ru.windcorp.progressia.test.controls.TestPlayerControls;
|
||||
import ru.windcorp.progressia.test.gen.TestGravityModel;
|
||||
import ru.windcorp.progressia.test.trees.BlockRenderLeavesHazel;
|
||||
import ru.windcorp.progressia.test.trees.BlockRenderLeavesPine;
|
||||
@ -288,55 +274,9 @@ public class TestContent {
|
||||
}
|
||||
|
||||
private static void regsiterControls() {
|
||||
ControlDataRegistry data = ControlDataRegistry.getInstance();
|
||||
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
|
||||
ControlLogicRegistry logic = ControlLogicRegistry.getInstance();
|
||||
TestPlayerControls.getInstance().registerControls();
|
||||
|
||||
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) {
|
||||
@ -392,95 +332,6 @@ public class TestContent {
|
||||
EntityLogicRegistry.getInstance().register(x);
|
||||
}
|
||||
|
||||
private static Selection getSelection() {
|
||||
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
|
||||
if (client == null || !client.isReady())
|
||||
return null;
|
||||
|
||||
return client.getLocalPlayer().getSelection();
|
||||
}
|
||||
|
||||
private static boolean isAnythingSelected() {
|
||||
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
|
||||
if (client == null || !client.isReady())
|
||||
return false;
|
||||
|
||||
return client.getLocalPlayer().getSelection().exists();
|
||||
}
|
||||
|
||||
private static void onBlockBreakTrigger(ControlData control) {
|
||||
((ControlBreakBlockData) control).setBlockInWorld(getSelection().getBlock());
|
||||
Sound sfx = new Sound("Progressia:BlockDestroy");
|
||||
sfx.setPosition(getSelection().getPoint());
|
||||
sfx.setPitch((float) (Math.random() + 1 * 0.5));
|
||||
sfx.play(false);
|
||||
}
|
||||
|
||||
private static void onBlockBreakReceived(
|
||||
Server server,
|
||||
PacketControl packet,
|
||||
ru.windcorp.progressia.server.comms.Client client
|
||||
) {
|
||||
Vec3i blockInWorld = ((ControlBreakBlockData) packet.getControl()).getBlockInWorld();
|
||||
server.createAbsoluteContext().setBlock(blockInWorld, BlockDataRegistry.getInstance().get("Test:Air"));
|
||||
}
|
||||
|
||||
private static void onBlockPlaceTrigger(ControlData control) {
|
||||
((ControlPlaceBlockData) control).set(
|
||||
TestPlayerControls.getInstance().getSelectedBlock(),
|
||||
getSelection().getBlock().add_(getSelection().getSurface().getVector())
|
||||
);
|
||||
}
|
||||
|
||||
private static void onBlockPlaceReceived(
|
||||
Server server,
|
||||
PacketControl packet,
|
||||
ru.windcorp.progressia.server.comms.Client client
|
||||
) {
|
||||
ControlPlaceBlockData controlData = ((ControlPlaceBlockData) packet.getControl());
|
||||
BlockData block = controlData.getBlock();
|
||||
Vec3i blockInWorld = controlData.getBlockInWorld();
|
||||
if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null)
|
||||
return;
|
||||
server.createAbsoluteContext().setBlock(blockInWorld, block);
|
||||
}
|
||||
|
||||
private static void onTilePlaceTrigger(ControlData control) {
|
||||
((ControlPlaceTileData) control).set(
|
||||
TestPlayerControls.getInstance().getSelectedTile(),
|
||||
getSelection().getBlock(),
|
||||
getSelection().getSurface()
|
||||
);
|
||||
}
|
||||
|
||||
private static void onTilePlaceReceived(
|
||||
Server server,
|
||||
PacketControl packet,
|
||||
ru.windcorp.progressia.server.comms.Client client
|
||||
) {
|
||||
ControlPlaceTileData controlData = ((ControlPlaceTileData) packet.getControl());
|
||||
TileData tile = controlData.getTile();
|
||||
Vec3i blockInWorld = controlData.getBlockInWorld();
|
||||
AbsFace face = controlData.getFace();
|
||||
|
||||
if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null) {
|
||||
return;
|
||||
}
|
||||
if (server.getWorld().getData().getTiles(blockInWorld, face).isFull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerBlockContext context = server.createContext(blockInWorld);
|
||||
ServerTileStackContext tsContext = context.push(context.toContext(face));
|
||||
ServerTileContext tileContext = tsContext.push(tsContext.getTileCount());
|
||||
|
||||
TileLogic logic = TileLogicRegistry.getInstance().get(tile.getId());
|
||||
if (!logic.canOccupyFace(tileContext)) {
|
||||
return;
|
||||
}
|
||||
tileContext.addTile(tile);
|
||||
}
|
||||
|
||||
private static void registerMisc() {
|
||||
ChunkIO.registerCodec(new TestChunkCodec());
|
||||
|
||||
|
@ -110,6 +110,7 @@ public class TestMusicPlayer implements Runnable {
|
||||
String file = it.next().toString();
|
||||
if (!file.endsWith(".ogg") && !file.endsWith(".oga")) {
|
||||
LogManager.getLogger().warn("Skipping " + file + ": not .ogg nor .oga");
|
||||
continue;
|
||||
}
|
||||
|
||||
String id = "Progressia:Music" + (i++);
|
||||
|
@ -1,469 +0,0 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ru.windcorp.progressia.test;
|
||||
|
||||
import glm.Glm;
|
||||
import glm.mat._3.Mat3;
|
||||
import glm.mat._4.Mat4;
|
||||
import glm.vec._3.Vec3;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import ru.windcorp.progressia.client.ClientState;
|
||||
import ru.windcorp.progressia.client.graphics.GUI;
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
|
||||
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.WheelScrollEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
|
||||
import ru.windcorp.progressia.client.localization.Localizer;
|
||||
import ru.windcorp.progressia.common.Units;
|
||||
import ru.windcorp.progressia.common.util.Matrices;
|
||||
import ru.windcorp.progressia.common.util.VectorUtil;
|
||||
import ru.windcorp.progressia.common.util.Vectors;
|
||||
import ru.windcorp.progressia.common.world.block.BlockData;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||
import ru.windcorp.progressia.common.world.tile.TileData;
|
||||
import ru.windcorp.progressia.server.ServerState;
|
||||
|
||||
public class TestPlayerControls {
|
||||
|
||||
private static TestPlayerControls instance = new TestPlayerControls();
|
||||
|
||||
public static TestPlayerControls getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static final double MODE_SWITCH_MAX_DELAY = 300 * Units.MILLISECONDS;
|
||||
private static final double MODE_SPRINT_SWITCH_MAX_DELAY = 100 * Units.MILLISECONDS;
|
||||
private static final double MIN_JUMP_DELAY = 300 * Units.MILLISECONDS;
|
||||
|
||||
// Horizontal and vertical max control speed when flying
|
||||
private static final float FLYING_SPEED = Units.get("6 m/s");
|
||||
|
||||
// (0; 1], 1 is instant change, 0 is no control authority
|
||||
private static final float FLYING_CONTROL_AUTHORITY = Units.get("2 1/s");
|
||||
|
||||
// Horizontal max control speed when walking
|
||||
private static final float WALKING_SPEED = Units.get("4 m/s");
|
||||
|
||||
// Horizontal max control speed when sprinting
|
||||
private static final float SPRINTING_SPEED = Units.get("6 m/s");
|
||||
|
||||
// (0; 1], 1 is instant change, 0 is no control authority
|
||||
private static final float WALKING_CONTROL_AUTHORITY = Units.get("15 1/s");
|
||||
|
||||
// Vertical velocity instantly added to player when they jump
|
||||
private static final float JUMP_VELOCITY = 5f * Units.METERS_PER_SECOND;
|
||||
|
||||
private boolean isFlying = true;
|
||||
private boolean isSprinting = false;
|
||||
|
||||
private int movementForward = 0;
|
||||
private int movementRight = 0;
|
||||
private int movementUp = 0;
|
||||
|
||||
private double lastSpacePress = Double.NEGATIVE_INFINITY;
|
||||
private double lastSprintPress = Double.NEGATIVE_INFINITY;
|
||||
|
||||
private int selectedBlock = 0;
|
||||
private int selectedTile = 0;
|
||||
private boolean isBlockSelected = true;
|
||||
|
||||
private LayerTestGUI debugLayer = null;
|
||||
private Runnable updateCallback = null;
|
||||
|
||||
public static void resetInstance() {
|
||||
instance = new TestPlayerControls();
|
||||
}
|
||||
|
||||
public void applyPlayerControls() {
|
||||
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
EntityData player = getEntity();
|
||||
|
||||
final float speed, authority;
|
||||
|
||||
if (isFlying) {
|
||||
speed = FLYING_SPEED;
|
||||
authority = FLYING_CONTROL_AUTHORITY;
|
||||
} else {
|
||||
speed = isSprinting ? SPRINTING_SPEED : WALKING_SPEED;
|
||||
authority = WALKING_CONTROL_AUTHORITY;
|
||||
}
|
||||
|
||||
Mat3 movementTransform = getMovementTransform(player, null);
|
||||
Vec3 desiredVelocity = new Vec3(movementForward, movementRight, 0);
|
||||
if (movementForward != 0 && movementRight != 0) {
|
||||
desiredVelocity.normalize();
|
||||
}
|
||||
desiredVelocity.z = movementUp;
|
||||
movementTransform.mul_(desiredVelocity); // bug in jglm, .mul() and .mul_() are
|
||||
// swapped
|
||||
desiredVelocity.mul(speed);
|
||||
|
||||
Vec3 newVelocity = new Vec3()
|
||||
.set(desiredVelocity)
|
||||
.sub(player.getVelocity())
|
||||
.mul((float) Math.exp(-authority * GraphicsInterface.getFrameLength()))
|
||||
.negate()
|
||||
.add(desiredVelocity);
|
||||
|
||||
if (!isFlying) {
|
||||
Vec3 up = player.getUpVector();
|
||||
Vec3 wantedVertical = VectorUtil.projectOnVector(player.getVelocity(), up, null);
|
||||
VectorUtil.projectOnSurface(newVelocity, up).add(wantedVertical);
|
||||
}
|
||||
|
||||
player.getVelocity().set(newVelocity);
|
||||
|
||||
// THIS IS TERRIBLE TEST
|
||||
EntityData serverEntity = ServerState.getInstance().getWorld().getData()
|
||||
.getEntity(TestContent.PLAYER_ENTITY_ID);
|
||||
if (serverEntity != null) {
|
||||
serverEntity.setPosition(player.getPosition());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Mat3 getMovementTransform(EntityData player, Mat3 mat) {
|
||||
if (mat == null) {
|
||||
mat = new Mat3();
|
||||
}
|
||||
|
||||
Vec3 f = player.getForwardVector(null);
|
||||
Vec3 u = player.getUpVector();
|
||||
Vec3 s = u.cross_(f);
|
||||
|
||||
return mat.set(
|
||||
+f.x, +f.y, +f.z,
|
||||
-s.x, -s.y, -s.z,
|
||||
+u.x, +u.y, +u.z
|
||||
);
|
||||
}
|
||||
|
||||
public void handleInput(Input input) {
|
||||
InputEvent event = input.getEvent();
|
||||
|
||||
if (event instanceof KeyEvent) {
|
||||
if (onKeyEvent((KeyEvent) event)) {
|
||||
input.consume();
|
||||
}
|
||||
} else if (event instanceof CursorMoveEvent) {
|
||||
onMouseMoved((CursorMoveEvent) event);
|
||||
input.consume();
|
||||
} else if (event instanceof WheelScrollEvent) {
|
||||
onWheelScroll((WheelScrollEvent) event);
|
||||
input.consume();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean onKeyEvent(KeyEvent event) {
|
||||
if (event.isRepeat())
|
||||
return false;
|
||||
int multiplier = event.isPress() ? 1 : -1;
|
||||
switch (event.getKey()) {
|
||||
case GLFW.GLFW_KEY_W:
|
||||
movementForward += +1 * multiplier;
|
||||
handleSprint(event);
|
||||
break;
|
||||
case GLFW.GLFW_KEY_S:
|
||||
movementForward += -1 * multiplier;
|
||||
break;
|
||||
case GLFW.GLFW_KEY_A:
|
||||
movementRight += -1 * multiplier;
|
||||
break;
|
||||
case GLFW.GLFW_KEY_D:
|
||||
movementRight += +1 * multiplier;
|
||||
break;
|
||||
case GLFW.GLFW_KEY_SPACE:
|
||||
handleSpace(multiplier);
|
||||
break;
|
||||
case GLFW.GLFW_KEY_LEFT_SHIFT:
|
||||
handleShift(multiplier);
|
||||
break;
|
||||
case GLFW.GLFW_KEY_ESCAPE:
|
||||
if (!event.isPress())
|
||||
return false;
|
||||
|
||||
handleEscape();
|
||||
break;
|
||||
|
||||
case GLFW.GLFW_KEY_F11:
|
||||
if (!event.isPress())
|
||||
return false;
|
||||
GraphicsInterface.makeFullscreen(!GraphicsBackend.isFullscreen());
|
||||
updateGUI();
|
||||
break;
|
||||
|
||||
case GLFW.GLFW_KEY_F12:
|
||||
if (!event.isPress())
|
||||
return false;
|
||||
GraphicsBackend.setVSyncEnabled(!GraphicsBackend.isVSyncEnabled());
|
||||
updateGUI();
|
||||
break;
|
||||
|
||||
case GLFW.GLFW_KEY_F3:
|
||||
if (!event.isPress())
|
||||
return false;
|
||||
handleDebugLayerSwitch();
|
||||
break;
|
||||
|
||||
case GLFW.GLFW_KEY_F5:
|
||||
if (!event.isPress())
|
||||
return false;
|
||||
handleCameraMode();
|
||||
break;
|
||||
|
||||
case GLFW.GLFW_KEY_L:
|
||||
if (!event.isPress())
|
||||
return false;
|
||||
handleLanguageSwitch();
|
||||
break;
|
||||
|
||||
case GLFW.GLFW_MOUSE_BUTTON_3:
|
||||
if (!event.isPress())
|
||||
return false;
|
||||
switchPlacingMode();
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void handleSpace(int multiplier) {
|
||||
boolean isPressed = multiplier > 0;
|
||||
|
||||
double timeSinceLastSpacePress = GraphicsInterface.getTime() - lastSpacePress;
|
||||
|
||||
if (isPressed && timeSinceLastSpacePress < MODE_SWITCH_MAX_DELAY) {
|
||||
isSprinting = false;
|
||||
isFlying = !isFlying;
|
||||
updateGUI();
|
||||
movementUp = +1;
|
||||
} else {
|
||||
if (isFlying) {
|
||||
movementUp += +1 * multiplier;
|
||||
} else {
|
||||
if (isPressed && timeSinceLastSpacePress > MIN_JUMP_DELAY) {
|
||||
jump();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastSpacePress = GraphicsInterface.getTime();
|
||||
}
|
||||
|
||||
private void handleSprint(KeyEvent event) {
|
||||
|
||||
double timeSinceLastSpacePress = GraphicsInterface.getTime() - lastSprintPress;
|
||||
|
||||
if (event.isPress() && timeSinceLastSpacePress < MODE_SPRINT_SWITCH_MAX_DELAY && !isFlying) {
|
||||
isSprinting = !isSprinting;
|
||||
updateGUI();
|
||||
}
|
||||
|
||||
lastSprintPress = GraphicsInterface.getTime();
|
||||
|
||||
if (isSprinting && event.isRelease()) {
|
||||
isSprinting = false;
|
||||
updateGUI();
|
||||
}
|
||||
}
|
||||
|
||||
private void jump() {
|
||||
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vec3 up = getEntity().getUpVector();
|
||||
|
||||
getEntity().getVelocity().add(
|
||||
up.x * JUMP_VELOCITY,
|
||||
up.y * JUMP_VELOCITY,
|
||||
up.z * JUMP_VELOCITY
|
||||
);
|
||||
}
|
||||
|
||||
private void handleShift(int multiplier) {
|
||||
if (isFlying) {
|
||||
movementUp += -1 * multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEscape() {
|
||||
movementForward = 0;
|
||||
movementRight = 0;
|
||||
movementUp = 0;
|
||||
GUI.addTopLayer(new LayerButtonTest());
|
||||
}
|
||||
|
||||
private void handleDebugLayerSwitch() {
|
||||
if (debugLayer == null) {
|
||||
this.debugLayer = new LayerTestGUI();
|
||||
this.updateCallback = debugLayer.getUpdateCallback();
|
||||
}
|
||||
|
||||
if (GUI.getLayers().contains(debugLayer)) {
|
||||
GUI.removeLayer(debugLayer);
|
||||
} else {
|
||||
GUI.addTopLayer(debugLayer);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCameraMode() {
|
||||
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ClientState.getInstance().getCamera().hasAnchor()) {
|
||||
ClientState.getInstance().getCamera().selectNextMode();
|
||||
updateGUI();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLanguageSwitch() {
|
||||
Localizer localizer = Localizer.getInstance();
|
||||
if (localizer.getLanguage().equals("ru-RU")) {
|
||||
localizer.setLanguage("en-US");
|
||||
} else {
|
||||
localizer.setLanguage("ru-RU");
|
||||
}
|
||||
|
||||
updateGUI();
|
||||
}
|
||||
|
||||
private void onMouseMoved(CursorMoveEvent event) {
|
||||
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final double yawScale = -0.002f;
|
||||
final double pitchScale = -yawScale;
|
||||
final double pitchExtremum = Math.PI/2 * 0.95f;
|
||||
|
||||
double yawChange = event.getChangeX() * yawScale;
|
||||
double pitchChange = event.getChangeY() * pitchScale;
|
||||
|
||||
EntityData player = getEntity();
|
||||
|
||||
double startPitch = player.getPitch();
|
||||
double endPitch = startPitch + pitchChange;
|
||||
endPitch = Glm.clamp(endPitch, -pitchExtremum, +pitchExtremum);
|
||||
pitchChange = endPitch - startPitch;
|
||||
|
||||
Mat4 mat = Matrices.grab4();
|
||||
Vec3 lookingAt = Vectors.grab3();
|
||||
Vec3 rightVector = Vectors.grab3();
|
||||
|
||||
rightVector.set(player.getLookingAt()).cross(player.getUpVector()).normalize();
|
||||
|
||||
mat.identity()
|
||||
.rotate((float) yawChange, player.getUpVector())
|
||||
.rotate((float) pitchChange, rightVector);
|
||||
|
||||
VectorUtil.applyMat4(player.getLookingAt(), mat, lookingAt);
|
||||
player.setLookingAt(lookingAt);
|
||||
|
||||
Vectors.release(rightVector);
|
||||
Vectors.release(lookingAt);
|
||||
Matrices.release(mat);
|
||||
}
|
||||
|
||||
private void onWheelScroll(WheelScrollEvent event) {
|
||||
if (event.hasHorizontalMovement()) {
|
||||
switchPlacingMode();
|
||||
}
|
||||
if (InputTracker.isKeyPressed(GLFW.GLFW_KEY_LEFT_CONTROL)) {
|
||||
switchPlacingMode();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBlockSelected) {
|
||||
selectedBlock += event.isUp() ? +1 : -1;
|
||||
|
||||
int size = TestContent.PLACEABLE_BLOCKS.size();
|
||||
|
||||
if (selectedBlock < 0) {
|
||||
selectedBlock = size - 1;
|
||||
} else if (selectedBlock >= size) {
|
||||
selectedBlock = 0;
|
||||
}
|
||||
} else {
|
||||
selectedTile += event.isUp() ? +1 : -1;
|
||||
|
||||
int size = TestContent.PLACEABLE_TILES.size();
|
||||
|
||||
if (selectedTile < 0) {
|
||||
selectedTile = size - 1;
|
||||
} else if (selectedTile >= size) {
|
||||
selectedTile = 0;
|
||||
}
|
||||
}
|
||||
|
||||
updateGUI();
|
||||
}
|
||||
|
||||
private void switchPlacingMode() {
|
||||
isBlockSelected = !isBlockSelected;
|
||||
updateGUI();
|
||||
}
|
||||
|
||||
public EntityData getEntity() {
|
||||
return getPlayer().getEntity();
|
||||
}
|
||||
|
||||
public LocalPlayer getPlayer() {
|
||||
return ClientState.getInstance().getLocalPlayer();
|
||||
}
|
||||
|
||||
private void updateGUI() {
|
||||
if (this.updateCallback != null) {
|
||||
this.updateCallback.run();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFlying() {
|
||||
return isFlying;
|
||||
}
|
||||
|
||||
public boolean isSprinting() {
|
||||
return isSprinting;
|
||||
}
|
||||
|
||||
public BlockData getSelectedBlock() {
|
||||
return TestContent.PLACEABLE_BLOCKS.get(selectedBlock);
|
||||
}
|
||||
|
||||
public TileData getSelectedTile() {
|
||||
return TestContent.PLACEABLE_TILES.get(selectedTile);
|
||||
}
|
||||
|
||||
public boolean isBlockSelected() {
|
||||
return isBlockSelected;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.test.controls;
|
||||
|
||||
import glm.Glm;
|
||||
import glm.mat._4.Mat4;
|
||||
import glm.vec._3.Vec3;
|
||||
import ru.windcorp.progressia.client.ClientState;
|
||||
import ru.windcorp.progressia.client.comms.controls.ControlTriggerRegistry;
|
||||
import ru.windcorp.progressia.client.comms.controls.ControlTriggers;
|
||||
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||
import ru.windcorp.progressia.client.graphics.world.Camera;
|
||||
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
|
||||
import ru.windcorp.progressia.common.util.Matrices;
|
||||
import ru.windcorp.progressia.common.util.VectorUtil;
|
||||
import ru.windcorp.progressia.common.util.Vectors;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||
|
||||
public class CameraControls {
|
||||
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
// No instance fields; futureproofing
|
||||
}
|
||||
|
||||
public void registerControls() {
|
||||
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
|
||||
|
||||
triggers.register(
|
||||
ControlTriggers.localOf(
|
||||
"Test:TurnCameraWithCursor",
|
||||
CursorMoveEvent.class,
|
||||
this::turnCameraWithCursor
|
||||
)
|
||||
);
|
||||
|
||||
triggers.register(
|
||||
ControlTriggers.localOf(
|
||||
"Test:SwitchCameraMode",
|
||||
KeyEvent.class,
|
||||
this::switchCameraMode,
|
||||
new KeyMatcher("F5")::matches
|
||||
)
|
||||
);
|
||||
|
||||
NoclipCamera.register();
|
||||
|
||||
triggers.register(
|
||||
ControlTriggers.localOf(
|
||||
"Test:ToggleNoclip",
|
||||
KeyEvent.class,
|
||||
NoclipCamera::toggleNoclip,
|
||||
new KeyMatcher("V")::matches
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void turnCameraWithCursor(CursorMoveEvent event) {
|
||||
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Camera.Anchor cameraAnchor = ClientState.getInstance().getCamera().getAnchor();
|
||||
if (!(cameraAnchor instanceof EntityAnchor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
EntityData player = ((EntityAnchor) cameraAnchor).getEntity();
|
||||
|
||||
final double yawScale = -0.002f;
|
||||
final double pitchScale = -yawScale;
|
||||
final double pitchExtremum = Math.PI / 2 * 0.995f;
|
||||
|
||||
double yawChange = event.getChangeX() * yawScale;
|
||||
double pitchChange = event.getChangeY() * pitchScale;
|
||||
|
||||
double startPitch = player.getPitch();
|
||||
double endPitch = startPitch + pitchChange;
|
||||
endPitch = Glm.clamp(endPitch, -pitchExtremum, +pitchExtremum);
|
||||
pitchChange = endPitch - startPitch;
|
||||
|
||||
Mat4 mat = Matrices.grab4();
|
||||
Vec3 lookingAt = Vectors.grab3();
|
||||
Vec3 rightVector = Vectors.grab3();
|
||||
|
||||
rightVector.set(player.getLookingAt()).cross(player.getUpVector()).normalize();
|
||||
|
||||
mat.identity()
|
||||
.rotate((float) yawChange, player.getUpVector())
|
||||
.rotate((float) pitchChange, rightVector);
|
||||
|
||||
VectorUtil.applyMat4(player.getLookingAt(), mat, lookingAt);
|
||||
player.setLookingAt(lookingAt);
|
||||
|
||||
Vectors.release(rightVector);
|
||||
Vectors.release(lookingAt);
|
||||
Matrices.release(mat);
|
||||
}
|
||||
|
||||
private void switchCameraMode() {
|
||||
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ClientState.getInstance().getCamera().hasAnchor()) {
|
||||
ClientState.getInstance().getCamera().selectNextMode();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ru.windcorp.progressia.test;
|
||||
package ru.windcorp.progressia.test.controls;
|
||||
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.common.comms.controls.ControlData;
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ru.windcorp.progressia.test;
|
||||
package ru.windcorp.progressia.test.controls;
|
||||
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.common.comms.controls.ControlData;
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ru.windcorp.progressia.test;
|
||||
package ru.windcorp.progressia.test.controls;
|
||||
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.common.comms.controls.ControlData;
|
@ -0,0 +1,264 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.test.controls;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.client.ClientState;
|
||||
import ru.windcorp.progressia.client.audio.Sound;
|
||||
import ru.windcorp.progressia.client.comms.controls.ControlTriggerRegistry;
|
||||
import ru.windcorp.progressia.client.comms.controls.ControlTriggers;
|
||||
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||
import ru.windcorp.progressia.client.graphics.input.WheelScrollEvent;
|
||||
import ru.windcorp.progressia.client.graphics.world.Selection;
|
||||
import ru.windcorp.progressia.common.comms.controls.ControlData;
|
||||
import ru.windcorp.progressia.common.comms.controls.ControlDataRegistry;
|
||||
import ru.windcorp.progressia.common.comms.controls.PacketControl;
|
||||
import ru.windcorp.progressia.common.world.block.BlockData;
|
||||
import ru.windcorp.progressia.common.world.block.BlockDataRegistry;
|
||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||
import ru.windcorp.progressia.common.world.tile.TileData;
|
||||
import ru.windcorp.progressia.server.Server;
|
||||
import ru.windcorp.progressia.server.comms.controls.ControlLogic;
|
||||
import ru.windcorp.progressia.server.comms.controls.ControlLogicRegistry;
|
||||
import ru.windcorp.progressia.server.world.context.ServerBlockContext;
|
||||
import ru.windcorp.progressia.server.world.context.ServerTileContext;
|
||||
import ru.windcorp.progressia.server.world.context.ServerTileStackContext;
|
||||
import ru.windcorp.progressia.server.world.tile.TileLogic;
|
||||
import ru.windcorp.progressia.server.world.tile.TileLogicRegistry;
|
||||
import ru.windcorp.progressia.test.TestContent;
|
||||
|
||||
public class InteractionControls {
|
||||
|
||||
private int selectedBlock;
|
||||
private int selectedTile;
|
||||
private boolean isBlockSelected;
|
||||
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
selectedBlock = 0;
|
||||
selectedTile = 0;
|
||||
isBlockSelected = true;
|
||||
}
|
||||
|
||||
public void registerControls() {
|
||||
ControlDataRegistry data = ControlDataRegistry.getInstance();
|
||||
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
|
||||
ControlLogicRegistry logic = ControlLogicRegistry.getInstance();
|
||||
|
||||
data.register("Test:BreakBlock", ControlBreakBlockData::new);
|
||||
triggers.register(
|
||||
ControlTriggers.of(
|
||||
"Test:BreakBlock",
|
||||
KeyEvent.class,
|
||||
this::onBlockBreakTrigger,
|
||||
KeyMatcher.LMB::matches,
|
||||
i -> isAnythingSelected()
|
||||
)
|
||||
);
|
||||
logic.register(ControlLogic.of("Test:BreakBlock", InteractionControls::onBlockBreakReceived));
|
||||
|
||||
data.register("Test:PlaceBlock", ControlPlaceBlockData::new);
|
||||
triggers.register(
|
||||
ControlTriggers.of(
|
||||
"Test:PlaceBlock",
|
||||
KeyEvent.class,
|
||||
this::onBlockPlaceTrigger,
|
||||
KeyMatcher.RMB::matches,
|
||||
i -> isAnythingSelected() && isBlockSelected()
|
||||
)
|
||||
);
|
||||
|
||||
logic.register(ControlLogic.of("Test:PlaceBlock", InteractionControls::onBlockPlaceReceived));
|
||||
|
||||
data.register("Test:PlaceTile", ControlPlaceTileData::new);
|
||||
triggers.register(
|
||||
ControlTriggers.of(
|
||||
"Test:PlaceTile",
|
||||
KeyEvent.class,
|
||||
this::onTilePlaceTrigger,
|
||||
KeyMatcher.RMB::matches,
|
||||
i -> isAnythingSelected() && !isBlockSelected()
|
||||
)
|
||||
);
|
||||
logic.register(ControlLogic.of("Test:PlaceTile", InteractionControls::onTilePlaceReceived));
|
||||
|
||||
triggers.register(
|
||||
ControlTriggers.localOf(
|
||||
"Test:SwitchPlacingModeMMB",
|
||||
KeyEvent.class,
|
||||
this::switchPlacingMode,
|
||||
KeyMatcher.MMB::matches
|
||||
)
|
||||
);
|
||||
|
||||
triggers.register(
|
||||
ControlTriggers.localOf(
|
||||
"Test:SwitchPlacingModeWheel",
|
||||
WheelScrollEvent.class,
|
||||
this::switchPlacingMode,
|
||||
e -> e.hasHorizontalMovement() || InputTracker.isKeyPressed(GLFW.GLFW_KEY_LEFT_CONTROL)
|
||||
)
|
||||
);
|
||||
|
||||
triggers.register(
|
||||
ControlTriggers.localOf(
|
||||
"Test:SelectNextBlockOrTile",
|
||||
WheelScrollEvent.class,
|
||||
this::selectNextBlockOrTile,
|
||||
e -> !e.hasHorizontalMovement() && !InputTracker.isKeyPressed(GLFW.GLFW_KEY_LEFT_CONTROL)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private static Selection getSelection() {
|
||||
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
|
||||
if (client == null || !client.isReady())
|
||||
return null;
|
||||
|
||||
return client.getLocalPlayer().getSelection();
|
||||
}
|
||||
|
||||
private static boolean isAnythingSelected() {
|
||||
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
|
||||
if (client == null || !client.isReady())
|
||||
return false;
|
||||
|
||||
return client.getLocalPlayer().getSelection().exists();
|
||||
}
|
||||
|
||||
private void onBlockBreakTrigger(ControlData control) {
|
||||
((ControlBreakBlockData) control).setBlockInWorld(getSelection().getBlock());
|
||||
Sound sfx = new Sound("Progressia:BlockDestroy");
|
||||
sfx.setPosition(getSelection().getPoint());
|
||||
sfx.setPitch((float) (Math.random() + 1 * 0.5));
|
||||
sfx.play(false);
|
||||
}
|
||||
|
||||
private static void onBlockBreakReceived(
|
||||
Server server,
|
||||
PacketControl packet,
|
||||
ru.windcorp.progressia.server.comms.Client client
|
||||
) {
|
||||
Vec3i blockInWorld = ((ControlBreakBlockData) packet.getControl()).getBlockInWorld();
|
||||
server.createAbsoluteContext().setBlock(blockInWorld, BlockDataRegistry.getInstance().get("Test:Air"));
|
||||
}
|
||||
|
||||
private void onBlockPlaceTrigger(ControlData control) {
|
||||
((ControlPlaceBlockData) control).set(
|
||||
getSelectedBlock(),
|
||||
getSelection().getBlock().add_(getSelection().getSurface().getVector())
|
||||
);
|
||||
}
|
||||
|
||||
private static void onBlockPlaceReceived(
|
||||
Server server,
|
||||
PacketControl packet,
|
||||
ru.windcorp.progressia.server.comms.Client client
|
||||
) {
|
||||
ControlPlaceBlockData controlData = ((ControlPlaceBlockData) packet.getControl());
|
||||
BlockData block = controlData.getBlock();
|
||||
Vec3i blockInWorld = controlData.getBlockInWorld();
|
||||
if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null)
|
||||
return;
|
||||
server.createAbsoluteContext().setBlock(blockInWorld, block);
|
||||
}
|
||||
|
||||
private void onTilePlaceTrigger(ControlData control) {
|
||||
((ControlPlaceTileData) control).set(
|
||||
getSelectedTile(),
|
||||
getSelection().getBlock(),
|
||||
getSelection().getSurface()
|
||||
);
|
||||
}
|
||||
|
||||
private static void onTilePlaceReceived(
|
||||
Server server,
|
||||
PacketControl packet,
|
||||
ru.windcorp.progressia.server.comms.Client client
|
||||
) {
|
||||
ControlPlaceTileData controlData = ((ControlPlaceTileData) packet.getControl());
|
||||
TileData tile = controlData.getTile();
|
||||
Vec3i blockInWorld = controlData.getBlockInWorld();
|
||||
AbsFace face = controlData.getFace();
|
||||
|
||||
if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null) {
|
||||
return;
|
||||
}
|
||||
if (server.getWorld().getData().getTiles(blockInWorld, face).isFull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerBlockContext context = server.createContext(blockInWorld);
|
||||
ServerTileStackContext tsContext = context.push(context.toContext(face));
|
||||
ServerTileContext tileContext = tsContext.push(tsContext.getTileCount());
|
||||
|
||||
TileLogic logic = TileLogicRegistry.getInstance().get(tile.getId());
|
||||
if (!logic.canOccupyFace(tileContext)) {
|
||||
return;
|
||||
}
|
||||
tileContext.addTile(tile);
|
||||
}
|
||||
|
||||
public void switchPlacingMode() {
|
||||
isBlockSelected = !isBlockSelected;
|
||||
}
|
||||
|
||||
public void selectNextBlockOrTile(WheelScrollEvent event) {
|
||||
if (isBlockSelected) {
|
||||
selectedBlock += event.isUp() ? +1 : -1;
|
||||
|
||||
int size = TestContent.PLACEABLE_BLOCKS.size();
|
||||
|
||||
if (selectedBlock < 0) {
|
||||
selectedBlock = size - 1;
|
||||
} else if (selectedBlock >= size) {
|
||||
selectedBlock = 0;
|
||||
}
|
||||
} else {
|
||||
selectedTile += event.isUp() ? +1 : -1;
|
||||
|
||||
int size = TestContent.PLACEABLE_TILES.size();
|
||||
|
||||
if (selectedTile < 0) {
|
||||
selectedTile = size - 1;
|
||||
} else if (selectedTile >= size) {
|
||||
selectedTile = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BlockData getSelectedBlock() {
|
||||
return TestContent.PLACEABLE_BLOCKS.get(selectedBlock);
|
||||
}
|
||||
|
||||
public TileData getSelectedTile() {
|
||||
return TestContent.PLACEABLE_TILES.get(selectedTile);
|
||||
}
|
||||
|
||||
public boolean isBlockSelected() {
|
||||
return isBlockSelected;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,316 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.test.controls;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import glm.mat._3.Mat3;
|
||||
import glm.vec._3.Vec3;
|
||||
import ru.windcorp.progressia.client.ClientState;
|
||||
import ru.windcorp.progressia.client.comms.controls.ControlTriggerRegistry;
|
||||
import ru.windcorp.progressia.client.comms.controls.ControlTriggers;
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||
import ru.windcorp.progressia.client.graphics.world.Camera;
|
||||
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
|
||||
import ru.windcorp.progressia.common.Units;
|
||||
import ru.windcorp.progressia.common.util.VectorUtil;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||
import ru.windcorp.progressia.server.ServerState;
|
||||
import ru.windcorp.progressia.test.TestContent;
|
||||
|
||||
public class MovementControls {
|
||||
|
||||
/**
|
||||
* Max delay between space presses that can toggle flying
|
||||
*/
|
||||
private static final double FLYING_SWITCH_MAX_DELAY = Units.get("300 ms");
|
||||
|
||||
/**
|
||||
* Max delay between W presses that can toggle sprinting
|
||||
*/
|
||||
private static final double SPRINTING_SWITCH_MAX_DELAY = Units.get("300 ms");
|
||||
|
||||
/**
|
||||
* Min delay between jumps
|
||||
*/
|
||||
private static final double JUMP_MIN_DELAY = Units.get("300 ms");
|
||||
|
||||
/**
|
||||
* Horizontal and vertical max control speed when flying
|
||||
*/
|
||||
private static final float FLYING_SPEED = Units.get("6 m/s");
|
||||
|
||||
/**
|
||||
* (0; 1], 1 is instant change, 0 is no control authority
|
||||
*/
|
||||
private static final float FLYING_CONTROL_AUTHORITY = Units.get("2 1/s");
|
||||
|
||||
/**
|
||||
* Horizontal max control speed when walking
|
||||
*/
|
||||
private static final float WALKING_SPEED = Units.get("4 m/s");
|
||||
|
||||
/**
|
||||
* Horizontal max control speed when sprinting
|
||||
*/
|
||||
private static final float SPRINTING_SPEED = Units.get("6 m/s");
|
||||
|
||||
/**
|
||||
* (0; 1], 1 is instant change, 0 is no control authority
|
||||
*/
|
||||
private static final float WALKING_CONTROL_AUTHORITY = Units.get("15 1/s");
|
||||
|
||||
/**
|
||||
* Vertical velocity instantly added to player when they jump
|
||||
*/
|
||||
private static final float JUMP_VELOCITY = Units.get("5 m/s");
|
||||
|
||||
private boolean isFlying;
|
||||
private boolean isSprinting;
|
||||
|
||||
private double lastSpacePress;
|
||||
private double lastSprintPress;
|
||||
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
public void applyPlayerControls() {
|
||||
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Camera.Anchor cameraAnchor = ClientState.getInstance().getCamera().getAnchor();
|
||||
if (!(cameraAnchor instanceof EntityAnchor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
EntityData player = ((EntityAnchor) cameraAnchor).getEntity();
|
||||
|
||||
boolean isFlying = this.isFlying || player.getId().equals("Test:NoclipCamera");
|
||||
boolean isSprinting = this.isSprinting || player.getId().equals("Test:NoclipCamera");
|
||||
|
||||
final float speed, authority;
|
||||
|
||||
if (isFlying) {
|
||||
speed = FLYING_SPEED;
|
||||
authority = FLYING_CONTROL_AUTHORITY;
|
||||
} else {
|
||||
speed = isSprinting ? SPRINTING_SPEED : WALKING_SPEED;
|
||||
authority = WALKING_CONTROL_AUTHORITY;
|
||||
}
|
||||
|
||||
Mat3 movementTransform = getMovementTransform(player, null);
|
||||
Vec3 desiredVelocity = getDesiredVelocity(movementTransform, speed, isFlying);
|
||||
Vec3 newVelocity = getNewVelocity(
|
||||
desiredVelocity,
|
||||
player.getVelocity(),
|
||||
authority,
|
||||
player.getUpVector(),
|
||||
isFlying
|
||||
);
|
||||
player.getVelocity().set(newVelocity);
|
||||
|
||||
tmp_syncServerEntity();
|
||||
}
|
||||
|
||||
private void tmp_syncServerEntity() {
|
||||
// THIS IS TERRIBLE TEST
|
||||
EntityData serverEntity = ServerState.getInstance().getWorld().getData()
|
||||
.getEntity(TestContent.PLAYER_ENTITY_ID);
|
||||
if (serverEntity != null) {
|
||||
EntityData clientEntity = ClientState.getInstance().getLocalPlayer().getEntity();
|
||||
|
||||
clientEntity.copy(serverEntity);
|
||||
serverEntity.setLookingAt(clientEntity.getLookingAt());
|
||||
serverEntity.setUpVector(clientEntity.getUpVector());
|
||||
serverEntity.setPosition(clientEntity.getPosition());
|
||||
serverEntity.setVelocity(clientEntity.getVelocity());
|
||||
}
|
||||
}
|
||||
|
||||
private Mat3 getMovementTransform(EntityData player, Mat3 mat) {
|
||||
if (mat == null) {
|
||||
mat = new Mat3();
|
||||
}
|
||||
|
||||
Vec3 f = player.getForwardVector(null);
|
||||
Vec3 u = player.getUpVector();
|
||||
Vec3 s = f.cross_(u);
|
||||
|
||||
//@formatter:off
|
||||
return mat.set(
|
||||
f.x, f.y, f.z,
|
||||
s.x, s.y, s.z,
|
||||
u.x, u.y, u.z
|
||||
);
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
private Vec3 getDesiredVelocity(Mat3 movementTransform, float speed, boolean isFlying) {
|
||||
int forward = 0;
|
||||
int right = 0;
|
||||
int up = 0;
|
||||
|
||||
forward += InputTracker.isKeyPressed(GLFW.GLFW_KEY_W) ? 1 : 0;
|
||||
forward -= InputTracker.isKeyPressed(GLFW.GLFW_KEY_S) ? 1 : 0;
|
||||
|
||||
right += InputTracker.isKeyPressed(GLFW.GLFW_KEY_D) ? 1 : 0;
|
||||
right -= InputTracker.isKeyPressed(GLFW.GLFW_KEY_A) ? 1 : 0;
|
||||
|
||||
if (isFlying) {
|
||||
up += InputTracker.isKeyPressed(GLFW.GLFW_KEY_SPACE) ? 1 : 0;
|
||||
up -= InputTracker.isKeyPressed(GLFW.GLFW_KEY_LEFT_SHIFT) ? 1 : 0;
|
||||
}
|
||||
|
||||
Vec3 desiredVelocity = new Vec3(forward, right, 0);
|
||||
if (forward != 0 && right != 0) {
|
||||
desiredVelocity.normalize();
|
||||
}
|
||||
desiredVelocity.z = up;
|
||||
|
||||
// bug in jglm, .mul() and .mul_() are swapped
|
||||
movementTransform.mul_(desiredVelocity);
|
||||
desiredVelocity.mul(speed);
|
||||
|
||||
return desiredVelocity;
|
||||
}
|
||||
|
||||
private Vec3 getNewVelocity(Vec3 desiredVelocity, Vec3 oldVelocity, float authority, Vec3 up, boolean isFlying) {
|
||||
|
||||
// newVelocity = oldVelocity + small change toward desiredVelocity
|
||||
Vec3 newVelocity = new Vec3()
|
||||
.set(desiredVelocity)
|
||||
.sub(oldVelocity)
|
||||
.mul((float) Math.exp(-authority * GraphicsInterface.getFrameLength()))
|
||||
.negate()
|
||||
.add(desiredVelocity);
|
||||
|
||||
// If we aren't flying, don't change vertical component
|
||||
if (!isFlying) {
|
||||
Vec3 wantedVertical = VectorUtil.projectOnVector(oldVelocity, up, null);
|
||||
VectorUtil.projectOnSurface(newVelocity, up);
|
||||
newVelocity.add(wantedVertical);
|
||||
}
|
||||
|
||||
return newVelocity;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
isFlying = true;
|
||||
isSprinting = false;
|
||||
lastSpacePress = Double.NEGATIVE_INFINITY;
|
||||
lastSprintPress = Double.NEGATIVE_INFINITY;
|
||||
}
|
||||
|
||||
public void registerControls() {
|
||||
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
|
||||
|
||||
triggers.register(
|
||||
ControlTriggers.localOf(
|
||||
"Test:JumpOrToggleFlight",
|
||||
KeyEvent.class,
|
||||
this::handleSpacePress,
|
||||
new KeyMatcher("Space")::matches
|
||||
)
|
||||
);
|
||||
|
||||
triggers.register(
|
||||
ControlTriggers.localOf(
|
||||
"Test:ToggleSprint",
|
||||
KeyEvent.class,
|
||||
this::toggleSprint,
|
||||
|
||||
new KeyMatcher("W")::matches,
|
||||
e -> !isFlying
|
||||
)
|
||||
);
|
||||
|
||||
triggers.register(
|
||||
ControlTriggers.localOf(
|
||||
"Test:DisableSprint",
|
||||
KeyEvent.class,
|
||||
this::disableSprint,
|
||||
|
||||
new KeyMatcher("W")::matchesIgnoringAction,
|
||||
KeyEvent::isRelease
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void handleSpacePress(KeyEvent e) {
|
||||
if (
|
||||
ClientState.getInstance().getCamera().getAnchor() instanceof EntityAnchor
|
||||
&& ((EntityAnchor) ClientState.getInstance().getCamera().getAnchor()).getEntity().getId()
|
||||
.equals("Test:NoclipCamera")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
double timeSinceLastSpacePress = e.getTime() - lastSpacePress;
|
||||
|
||||
if (timeSinceLastSpacePress < FLYING_SWITCH_MAX_DELAY) {
|
||||
isSprinting = false;
|
||||
isFlying = !isFlying;
|
||||
} else if (!isFlying && timeSinceLastSpacePress >= JUMP_MIN_DELAY) {
|
||||
jump();
|
||||
}
|
||||
|
||||
lastSpacePress = e.getTime();
|
||||
}
|
||||
|
||||
private void jump() {
|
||||
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
EntityData player = ClientState.getInstance().getLocalPlayer().getEntity();
|
||||
assert player != null;
|
||||
|
||||
Vec3 up = player.getUpVector();
|
||||
|
||||
player.getVelocity().add(
|
||||
up.x * JUMP_VELOCITY,
|
||||
up.y * JUMP_VELOCITY,
|
||||
up.z * JUMP_VELOCITY
|
||||
);
|
||||
}
|
||||
|
||||
private void toggleSprint(KeyEvent e) {
|
||||
if (e.getTime() - lastSprintPress < SPRINTING_SWITCH_MAX_DELAY) {
|
||||
isSprinting = !isSprinting;
|
||||
}
|
||||
lastSprintPress = e.getTime();
|
||||
}
|
||||
|
||||
private void disableSprint(KeyEvent e) {
|
||||
isSprinting = false;
|
||||
}
|
||||
|
||||
public boolean isFlying() {
|
||||
return isFlying;
|
||||
}
|
||||
|
||||
public boolean isSprinting() {
|
||||
return isSprinting;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.test.controls;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import glm.vec._3.Vec3;
|
||||
import ru.windcorp.progressia.client.ClientState;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||
import ru.windcorp.progressia.client.graphics.world.Camera;
|
||||
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
|
||||
import ru.windcorp.progressia.client.world.WorldRender;
|
||||
import ru.windcorp.progressia.client.world.entity.EntityRender;
|
||||
import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry;
|
||||
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
|
||||
|
||||
public class NoclipCamera {
|
||||
|
||||
private static class NoclipEntityRender extends EntityRender {
|
||||
|
||||
public NoclipEntityRender() {
|
||||
super("Test:NoclipCamera");
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityRenderable createRenderable(EntityData entity) {
|
||||
return new NoclipEntityRenderable(entity);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class NoclipEntityRenderable extends EntityRenderable {
|
||||
|
||||
public NoclipEntityRenderable(EntityData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRender(ShapeRenderHelper renderer) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void register() {
|
||||
EntityDataRegistry.getInstance().register("Test:NoclipCamera");
|
||||
EntityRenderRegistry.getInstance().register(new NoclipEntityRender());
|
||||
}
|
||||
|
||||
public static void toggleNoclip() {
|
||||
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Camera camera = ClientState.getInstance().getCamera();
|
||||
WorldRender world = ClientState.getInstance().getWorld();
|
||||
EntityData player = ClientState.getInstance().getLocalPlayer().getEntity();
|
||||
|
||||
List<EntityData> existingCameras = world.getData().getEntities().stream().filter(e -> e.getId().equals("Test:NoclipCamera")).collect(Collectors.toList());
|
||||
if (!existingCameras.isEmpty()) {
|
||||
existingCameras.forEach(world.getData()::removeEntity);
|
||||
camera.setAnchor(new EntityAnchor(world.getEntityRenderable(player)));
|
||||
return;
|
||||
}
|
||||
|
||||
EntityData noclipCamera = EntityDataRegistry.getInstance().create("Test:NoclipCamera");
|
||||
|
||||
noclipCamera.setLookingAt(player.getLookingAt());
|
||||
noclipCamera.setUpVector(player.getUpVector());
|
||||
noclipCamera.setPosition(player.getPosition());
|
||||
noclipCamera.setVelocity(player.getVelocity());
|
||||
noclipCamera.setEntityId(new Random().nextLong());
|
||||
|
||||
player.setVelocity(new Vec3(0));
|
||||
|
||||
world.getData().addEntity(noclipCamera);
|
||||
camera.setAnchor(new EntityAnchor(world.getEntityRenderable(noclipCamera)));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ru.windcorp.progressia.test.controls;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ru.windcorp.progressia.client.ClientState;
|
||||
import ru.windcorp.progressia.client.comms.controls.ControlTriggerRegistry;
|
||||
import ru.windcorp.progressia.client.comms.controls.ControlTriggers;
|
||||
import ru.windcorp.progressia.client.graphics.GUI;
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
|
||||
import ru.windcorp.progressia.client.localization.Localizer;
|
||||
import ru.windcorp.progressia.common.world.block.BlockData;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||
import ru.windcorp.progressia.common.world.tile.TileData;
|
||||
import ru.windcorp.progressia.test.LayerButtonTest;
|
||||
import ru.windcorp.progressia.test.LayerDebug;
|
||||
import ru.windcorp.progressia.test.TestMusicPlayer;
|
||||
|
||||
public class TestPlayerControls {
|
||||
|
||||
private static final TestPlayerControls INSTANCE = new TestPlayerControls();
|
||||
|
||||
public static TestPlayerControls getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private final MovementControls movementControls = new MovementControls();
|
||||
private final CameraControls cameraControls = new CameraControls();
|
||||
private final InteractionControls interactionControls = new InteractionControls();
|
||||
|
||||
private LayerDebug debugLayer = null;
|
||||
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
public static void resetInstance() {
|
||||
INSTANCE.reset();
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
movementControls.reset();
|
||||
cameraControls.reset();
|
||||
interactionControls.reset();
|
||||
|
||||
debugLayer = null;
|
||||
}
|
||||
|
||||
public void applyPlayerControls() {
|
||||
movementControls.applyPlayerControls();
|
||||
}
|
||||
|
||||
public void registerControls() {
|
||||
movementControls.registerControls();
|
||||
cameraControls.registerControls();
|
||||
interactionControls.registerControls();
|
||||
|
||||
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
|
||||
|
||||
triggers.register(
|
||||
ControlTriggers.localOf(
|
||||
"Test:PauseGame",
|
||||
KeyEvent.class,
|
||||
this::pauseGame,
|
||||
new KeyMatcher("Escape")::matches
|
||||
)
|
||||
);
|
||||
|
||||
triggers.register(
|
||||
ControlTriggers.localOf(
|
||||
"Test:ToggleFullscreen",
|
||||
KeyEvent.class,
|
||||
this::toggleFullscreen,
|
||||
new KeyMatcher("F11")::matches
|
||||
)
|
||||
);
|
||||
|
||||
triggers.register(
|
||||
ControlTriggers.localOf(
|
||||
"Test:ToggleVSync",
|
||||
KeyEvent.class,
|
||||
this::toggleVSync,
|
||||
new KeyMatcher("F12")::matches
|
||||
)
|
||||
);
|
||||
|
||||
triggers.register(
|
||||
ControlTriggers.localOf(
|
||||
"Test:ToggleDebugLayer",
|
||||
KeyEvent.class,
|
||||
this::toggleDebugLayer,
|
||||
new KeyMatcher("F3")::matches
|
||||
)
|
||||
);
|
||||
|
||||
triggers.register(
|
||||
ControlTriggers.localOf(
|
||||
"Test:SwitchLanguage",
|
||||
KeyEvent.class,
|
||||
this::switchLanguage,
|
||||
new KeyMatcher("L")::matches
|
||||
)
|
||||
);
|
||||
|
||||
triggers.register(
|
||||
ControlTriggers.localOf(
|
||||
"Test:StartNextMusic",
|
||||
KeyEvent.class,
|
||||
TestMusicPlayer::startNextNow,
|
||||
new KeyMatcher("M")::matches
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void pauseGame() {
|
||||
GUI.addTopLayer(new LayerButtonTest());
|
||||
}
|
||||
|
||||
private void toggleFullscreen() {
|
||||
GraphicsInterface.makeFullscreen(!GraphicsBackend.isFullscreen());
|
||||
}
|
||||
|
||||
private void toggleVSync() {
|
||||
GraphicsBackend.setVSyncEnabled(!GraphicsBackend.isVSyncEnabled());
|
||||
}
|
||||
|
||||
private void toggleDebugLayer() {
|
||||
if (debugLayer == null) {
|
||||
this.debugLayer = new LayerDebug();
|
||||
}
|
||||
|
||||
if (GUI.getLayers().contains(debugLayer)) {
|
||||
GUI.removeLayer(debugLayer);
|
||||
} else {
|
||||
GUI.addTopLayer(debugLayer);
|
||||
}
|
||||
}
|
||||
|
||||
private void switchLanguage() {
|
||||
Localizer localizer = Localizer.getInstance();
|
||||
List<String> languages = localizer.getLanguages();
|
||||
|
||||
int index = languages.indexOf(localizer.getLanguage());
|
||||
|
||||
if (index == languages.size() - 1) {
|
||||
index = 0;
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
|
||||
localizer.setLanguage(languages.get(index));
|
||||
}
|
||||
|
||||
public EntityData getEntity() {
|
||||
return getPlayer().getEntity();
|
||||
}
|
||||
|
||||
public LocalPlayer getPlayer() {
|
||||
return ClientState.getInstance().getLocalPlayer();
|
||||
}
|
||||
|
||||
public MovementControls getMovementControls() {
|
||||
return movementControls;
|
||||
}
|
||||
|
||||
public BlockData getSelectedBlock() {
|
||||
return interactionControls.getSelectedBlock();
|
||||
}
|
||||
|
||||
public TileData getSelectedTile() {
|
||||
return interactionControls.getSelectedTile();
|
||||
}
|
||||
|
||||
public boolean isBlockSelected() {
|
||||
return interactionControls.isBlockSelected();
|
||||
}
|
||||
|
||||
public boolean isFlying() {
|
||||
return movementControls.isFlying();
|
||||
}
|
||||
|
||||
public boolean isSprinting() {
|
||||
return movementControls.isSprinting();
|
||||
}
|
||||
|
||||
}
|
@ -4,22 +4,16 @@ LayerAbout.Title = Progressia
|
||||
LayerAbout.Version = Version: %s
|
||||
LayerAbout.DebugHint = Debug GUI: F3
|
||||
|
||||
LayerTestGUI.IsFlyingDisplay = Flying: %5s (Space bar x2)
|
||||
LayerTestGUI.IsSprintingDisplay = Sprinting: %5s (W x2)
|
||||
LayerTestGUI.CameraModeDisplay = Camera mode: %5d (F5)
|
||||
LayerTestGUI.LanguageDisplay = Language: %5s (L)
|
||||
LayerTestGUI.FPSDisplay = FPS:
|
||||
LayerTestGUI.TPSDisplay = TPS:
|
||||
LayerTestGUI.TPSDisplay.NA = TPS: n/a
|
||||
LayerTestGUI.ChunkStatsDisplay = Chunks vis/pnd/load:
|
||||
LayerTestGUI.PosDisplay = Pos:
|
||||
LayerTestGUI.PosDisplay.NA.Client = Pos: client n/a
|
||||
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)
|
||||
LayerDebug.FPSDisplay = FPS:
|
||||
LayerDebug.TPSDisplay = TPS:
|
||||
LayerDebug.TPSDisplay.NA = TPS: n/a
|
||||
LayerDebug.ChunkStatsDisplay = Chunks vis/pnd/load:
|
||||
LayerDebug.PosDisplay = Pos:
|
||||
LayerDebug.PosDisplay.NA.Client = Pos: client n/a
|
||||
LayerDebug.PosDisplay.NA.Entity = Pos: entity n/a
|
||||
LayerDebug.SelectedBlockDisplay = %s Block: %s
|
||||
LayerDebug.SelectedTileDisplay = %s Tile: %s
|
||||
LayerDebug.PlacementModeHint = (Blocks %s Tiles: Ctrl + Mouse Wheel)
|
||||
|
||||
LayerButtonTest.Title = Button Test
|
||||
LayerButtonTest.Return = Back To Menu
|
||||
|
@ -4,22 +4,16 @@ LayerAbout.Title = Прогрессия
|
||||
LayerAbout.Version = Версия: %s
|
||||
LayerAbout.DebugHint = Отладочный GUI: F3
|
||||
|
||||
LayerTestGUI.IsFlyingDisplay = Полёт: %5s (Пробел x2)
|
||||
LayerTestGUI.IsSprintingDisplay = Бег: %5s (W x2)
|
||||
LayerTestGUI.CameraModeDisplay = Камера: %5d (F5)
|
||||
LayerTestGUI.LanguageDisplay = Язык: %5s (L)
|
||||
LayerTestGUI.FPSDisplay = FPS:
|
||||
LayerTestGUI.TPSDisplay = TPS:
|
||||
LayerTestGUI.TPSDisplay.NA = TPS: н/д
|
||||
LayerTestGUI.ChunkStatsDisplay = Чанки вид/очр/загр:
|
||||
LayerTestGUI.PosDisplay = Поз:
|
||||
LayerTestGUI.PosDisplay.NA.Client = Поз: клиент н/д
|
||||
LayerTestGUI.PosDisplay.NA.Entity = Поз: сущность н/д
|
||||
LayerTestGUI.SelectedBlockDisplay = %s Блок: %s
|
||||
LayerTestGUI.SelectedTileDisplay = %s Плитка: %s
|
||||
LayerTestGUI.PlacementModeHint = (Блок %s плитки: Ctrl + прокрутка)
|
||||
LayerTestGUI.IsFullscreen = Полный экран: %5s (F11)
|
||||
LayerTestGUI.IsVSync = Верт. синхр.: %5s (F12)
|
||||
LayerDebug.FPSDisplay = FPS:
|
||||
LayerDebug.TPSDisplay = TPS:
|
||||
LayerDebug.TPSDisplay.NA = TPS: н/д
|
||||
LayerDebug.ChunkStatsDisplay = Чанки вид/очр/загр:
|
||||
LayerDebug.PosDisplay = Поз:
|
||||
LayerDebug.PosDisplay.NA.Client = Поз: клиент н/д
|
||||
LayerDebug.PosDisplay.NA.Entity = Поз: сущность н/д
|
||||
LayerDebug.SelectedBlockDisplay = %s Блок: %s
|
||||
LayerDebug.SelectedTileDisplay = %s Плитка: %s
|
||||
LayerDebug.PlacementModeHint = (Блок %s плитки: Ctrl + прокрутка)
|
||||
|
||||
LayerButtonTest.Title = Тест кнопок
|
||||
LayerButtonTest.Return = Главное меню
|
||||
|
Reference in New Issue
Block a user