37 Commits

Author SHA1 Message Date
0c41350ae7 Added file identification header
-All files now start with a 16 byte header consisting of "PROG" followed
by the integer position of the region in region coordinates.
	-regionCoords is passed through some functions to allow for this to be
confirmed.
2021-09-10 22:19:18 -04:00
a633c8324e Added Reset World button and fixed some translations 2021-09-11 00:47:28 +03:00
6b33f231b4 Moved region implementation to .server.world.io.region 2021-09-10 23:59:03 +03:00
e2308b825d Removed warnings and formatted code 2021-09-10 23:31:14 +03:00
e0f6a08740 Bug fixes
-Now offset technically starts at 1, so any chunks with offset 0 are
ignored.
-Reduced number of unused chunks, storage efficiency is at about 99% (if
null sectors are counted as useful)
2021-09-10 14:14:18 -04:00
2820e01974 Finished Partition Logic
Note: it still does waste a lot of space, I will work on that next
-Added back the confirmHeaderHealth logic
-Checks to make sure it will not overwrite important chunks
	-Uses PartitionLink chunks to move to a different part of the file
-Added allocateEmptySector() to allow for the file size to be increased
without moving the origin point of the chunk.
2021-09-10 13:51:37 -04:00
46bcb85044 Region File better????
-Moved most of the file accessing of Region to RegionFile
-Disabled most of the header check except the length check(it will be
back soon)
-Max chunk size arbitrarily raised to 4MiB because I wanted sectors
longer than 16B
-Sectors now have a mandatory 1B header that identifies it
	-0 is a null sector, it ends every chunk
	-1 is a normal data sector, it has a "parity" byte that makes sure it
is reading chunks in linear order(fun fact: it isnt at the moment)
	-2 is a jump to a different location. this isnt implemented well yet
	-3 will be a "bulk data" sector. Multiple chunks with identical data
can point here. Probably only useful when it is easily identifiable,
like multiple chunks being one entire block, like air.
-Removed all chunk length references as I think they do not make sense
when it can use different sectors for non-data purposes.
2021-09-09 19:58:44 -04:00
c5dfe3d0b7 Saving Modifications
-Safer saving, it waits for the file to stop being used to close(still
has holes)
-If a chunk is too large, it is moved to the end of the file.
	-Calculates the efficiency of each region file in confirmHeaderHealth
-Fixed import warning
2021-09-09 16:52:55 -04:00
0100c8791d Added player saving and loading from disk 2021-08-30 18:23:42 +03:00
e967a64401 More Compact region files
-Sector length increased to a short, the minimum sector size is now 16
bytes
2021-08-30 10:52:56 -04:00
d2ffe1fe0e Fixed the bug that opfromthestart found, reloading now works 2021-08-29 12:04:02 +03:00
f4300558d5 Formatted and broke the saving mechanism. I'm too tired to bugfix
- Refactored and formatted TestWorldDiskIO
- Removed HashableVec3i
- Added Coordinates methods for custom bit count
- Properly reverted commit 98250cd
- Known bugs:
  - Server shutdown close()s regions too early
  - Re-entering a world does not show saved changes
2021-08-29 02:08:19 +03:00
cd16334db8 Moved TestWorldDiskIO to a subpackage and introduced some abstractions 2021-08-28 23:31:50 +03:00
41a2909f7c Reverted last two commits because no one wants to fix the bugs 2021-08-28 21:31:34 +03:00
a222ea8f67 Fixed multithread chunk IO 2021-08-28 21:14:35 +03:00
98250cd524 Some changes for efficiency(not yet)
-Tried to use threads/executors in ChunkRequestDaemon, it just hangs.
-Added isEmpty and isOpaque attributes to DefaultChunkData (should this
just be in ChunkData?)
	-Added compute and getter functions to access(for everything after
loading)
	-Doesn't render empty chunks(not yet used)
-Using format 65537 allows the empty and opaqueness to be saved. They do
not do anything yet and there is no way to set them yet
	-Added loadRegionX and ".progressia_chunkx" file
-removed formats 0 and 1, which use individual chunk files.
2021-08-27 16:59:05 -04:00
9dcb3a7748 -Better HashableVec3i compare
-Added RandomFileMapped class
	-Made it a wrapper for RandomAccessFile and contains maps of locations
and lengths
-Added confirmHeaderHealth method to made sure the file isnt corrupted
-Changed everything from MappedByteBuffers to RandomFileMappeds.
2021-08-25 16:15:38 -04:00
0ccc108ddd Merge branch 'save-world'
Third time's the charm!
2021-08-24 01:38:22 +03:00
c7e7d3bdac Added ferns, fixed herb render and cleaned up TestContent
- Added Test:Fern
- Herbs are no longer stretched horizontally
- Formatted and cleaned up TestContent
2021-08-23 18:36:15 +03:00
62729f5873 Added packet buffering and fix crash when placing flowers on leaves
- Packets are now buffered before processing to reduce stack depth
- Attempts to place tiles on invalid locations get rejected earlier
2021-08-23 17:37:25 +03:00
84864f8947 Added more grass variants
- Added flat grass tiles with varying opaqueness
- Renamed Test:Grass to Test:GrassOpaque
- Added Chernozem
2021-08-23 15:18:37 +03:00
d01ef3654f Added grasses and flowers
- Added CROSimple to optimize simple non-Surface textures
- Added TileRenderCross
- Added Low, Medium and Tall grass
- Added Blue, Purple and White flat flowers
- Added Bushes
- Added tiny Dandelions and tiny Lavanders
- Improved grass and log textures
2021-08-22 22:18:42 +03:00
fae09edb16 Fixes n Speed n Stuff
-Better comparison for HashableVec3i
-Changed RandomAccessFile to MappedByteBuffer
-natFromInt now works properly
-better region selection
-Changes to make different strategies work
2021-08-19 10:47:16 -04:00
e4ced6507e Hash Things
-Removed unused imports
-Using HashMap to keep track of RandomAccessFile instances
-Using those instances to do stuff
-Made new HashableVec3i class that allows for hashing of 3d vectors.
2021-08-18 18:50:48 -04:00
b7dcbb0f30 Fixing stuff
-Got rid of some annoying logging
-Format
2021-08-18 13:21:54 -04:00
9c26418354 Well, it works now ig
-Better file lengthening
-Made temp files
    -Exchanged temp files for ByteArrayIn/OutputBuffers
-Fixed loadRegion to actually read sector sizes
2021-08-18 13:15:30 -04:00
6891d3a095 Trying to get it to work
-Uses many streams to keep track of things
-Fixed some bad things, still are a ton.
2021-08-06 12:56:21 -04:00
8167c40f64 IDK these errors are weird
-Changed the loading to fit the byte changes
2021-08-05 14:12:37 -04:00
8bc23acb61 idk changes so I can show OLEGSHA
-changed number of index bytes from 4 to 3
-exposed some variables for help with debugger
2021-08-05 13:39:42 -04:00
254faca0a5 Better deletion
-Makes new TestPlayerControls instance each time you enter a world
-Some dynamic sizing of chunk data within region files.
2021-08-05 12:29:29 -04:00
0c66f1751e Made outline of region file
-Improved loading screen loading(so it doesnt crash)
-Implemented region file related things, but it doesnt quite work
    -Uses file header to try to look up location of data
    -Writes the data at the end of the file(I need info to how much it writes)
2021-08-04 19:17:49 -04:00
c88dea6030 Fixing Bugs mostly
-Cleaning up imports
-Better error detection
-Actual thread deletion(still needs a bit of work to delete all conected objects)
-Starting making format file and inplementing it
2021-08-04 16:57:21 -04:00
6521cb5749 Better Listeners
-Specific listeners for the start and stop of loading
-Better(?) layer deletion after exiting a world
-Actual server thread shutdown
2021-08-04 12:52:37 -04:00
94db44e443 Im tired i need sleep
-Added listeners for saving and loading chunks
-Made loading screens for between title and game(they dont work yet)
-Added localized text(some)
-Safeish deletion and saving of chunks
    -It still keeps them in memory I think so this needs work too
2021-08-03 22:42:46 -04:00
53f72b068a Merge branch 'title-screen' into save-world 2021-08-03 20:08:39 -04:00
a9ca5f6b17 Everything Excpeti polishing and options
-Added LayerTitle class that is the title menu
-Edited the launcher and proxy so it only starts the game when it needs to
-Made buttons work with MutableString objects
2021-08-03 19:42:04 -04:00
4ab7cb738e Testing some stuff
-Made functions to convert integers into natural numbers (cause why not)
-Ideas/plans of dynamic/custom region file sizes
2021-08-03 17:53:49 -04:00
75 changed files with 2129 additions and 495 deletions

View File

@ -15,21 +15,31 @@
* 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;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.util.crash.analyzers.OutOfMemoryAnalyzer;
import ru.windcorp.progressia.common.util.crash.providers.*;
import ru.windcorp.progressia.test.LayerTitle;
public class ProgressiaLauncher {
public static String[] arguments;
private static Proxy proxy;
public static void launch(String[] args, Proxy proxy) {
arguments = args.clone();
setupCrashReports();
proxy.initialize();
ProgressiaLauncher.proxy = proxy;
GUI.addTopLayer(new LayerTitle("Title"));
}
public static Proxy getProxy() {
return proxy;
}
private static void setupCrashReports() {

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client;
import ru.windcorp.progressia.Proxy;
@ -30,7 +30,6 @@ import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.localization.Localizer;
import ru.windcorp.progressia.common.resource.ResourceManager;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.TestContent;
import ru.windcorp.progressia.test.TestMusicPlayer;
@ -38,7 +37,9 @@ public class ClientProxy implements Proxy {
@Override
public void initialize() {
GraphicsBackend.initialize();
try {
RenderTaskQueue.waitAndInvoke(FlatRenderProgram::init);
RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init);
@ -58,10 +59,6 @@ public class ClientProxy implements Proxy {
AudioSystem.initialize();
ServerState.startServer();
ClientState.connectToLocalServer();
TestMusicPlayer.start();
}
}

View File

@ -15,15 +15,18 @@
* 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;
import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.Layer;
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.LayerAbout;
import ru.windcorp.progressia.test.LayerTestText;
import ru.windcorp.progressia.test.LayerTestUI;
import ru.windcorp.progressia.test.TestContent;
@ -52,11 +55,39 @@ public class ClientState {
channel.connect(TestContent.PLAYER_LOGIN);
setInstance(client);
displayLoadingScreen();
GUI.addBottomLayer(new LayerWorld(client));
GUI.addTopLayer(new LayerTestUI());
GUI.addTopLayer(new LayerAbout());
}
private static void displayLoadingScreen() {
GUI.addTopLayer(new LayerTestText("Text", new MutableStringLocalized("LayerText.Load"), layer -> {
Client client = ClientState.getInstance();
// TODO refacetor and remove
if (client != null) {
client.getComms().processPackets();
}
if (client != null && client.getLocalPlayer().hasEntity()) {
GUI.removeLayer(layer);
// TODO refactor, this shouldn't be here
LayerWorld layerWorld = new LayerWorld(client);
LayerTestUI layerUI = new LayerTestUI();
LayerAbout layerAbout = new LayerAbout();
GUI.addBottomLayer(layerWorld);
GUI.addTopLayer(layerUI);
GUI.addTopLayer(layerAbout);
}
}));
}
public static void disconnectFromLocalServer() {
getInstance().getComms().disconnect();
for (Layer layer : GUI.getLayers()) {
GUI.removeLayer(layer);
}
}
private ClientState() {

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.comms.localhost;
import java.io.IOException;

View File

@ -15,12 +15,13 @@
* 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.comms.localhost;
import ru.windcorp.progressia.client.comms.ServerCommsChannel;
import ru.windcorp.progressia.common.comms.packets.Packet;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.ServerState;
public class LocalServerCommsChannel extends ServerCommsChannel {
@ -54,7 +55,7 @@ public class LocalServerCommsChannel extends ServerCommsChannel {
@Override
public void disconnect() {
// Do nothing
ServerState.getInstance().getClientManager().disconnectClient(localClient);
}
}

View File

@ -15,12 +15,13 @@
* 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;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import com.google.common.eventbus.Subscribe;
@ -58,6 +59,7 @@ public class GUI {
}
public static void addBottomLayer(Layer layer) {
Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.add(layer);
layer.onAdded();
@ -65,6 +67,7 @@ public class GUI {
}
public static void addTopLayer(Layer layer) {
Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.add(0, layer);
layer.onAdded();
@ -72,6 +75,7 @@ public class GUI {
}
public static void removeLayer(Layer layer) {
Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.remove(layer);
layer.onRemoved();
@ -88,33 +92,33 @@ public class GUI {
public static void render() {
synchronized (LAYERS) {
if (!MODIFICATION_QUEUE.isEmpty()) {
MODIFICATION_QUEUE.forEach(action -> action.affect(LAYERS));
MODIFICATION_QUEUE.clear();
boolean isMouseCurrentlyCaptured = GraphicsInterface.isMouseCaptured();
Layer.CursorPolicy policy = Layer.CursorPolicy.REQUIRE;
for (Layer layer : LAYERS) {
Layer.CursorPolicy currentPolicy = layer.getCursorPolicy();
if (currentPolicy != Layer.CursorPolicy.INDIFFERENT) {
policy = currentPolicy;
break;
}
}
boolean shouldCaptureMouse = (policy == Layer.CursorPolicy.FORBID);
if (shouldCaptureMouse != isMouseCurrentlyCaptured) {
GraphicsInterface.setMouseCaptured(shouldCaptureMouse);
}
}
for (int i = LAYERS.size() - 1; i >= 0; --i) {
LAYERS.get(i).render();
}
}
}

View File

@ -37,15 +37,15 @@ import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
public abstract class BasicButton extends Component {
private final Label label;
private boolean isPressed = false;
private final Collection<Consumer<BasicButton>> actions = Collections.synchronizedCollection(new ArrayList<>());
public BasicButton(String name, String label, Font labelFont) {
public BasicButton(String name, Label label) {
super(name);
this.label = new Label(name + ".Label", labelFont, label);
this.label = label;
setLayout(new LayoutAlign(10));
addChild(this.label);
@ -59,8 +59,8 @@ public abstract class BasicButton extends Component {
return false;
} else if (
e.isLeftMouseButton() ||
e.getKey() == GLFW.GLFW_KEY_SPACE ||
e.getKey() == GLFW.GLFW_KEY_ENTER
e.getKey() == GLFW.GLFW_KEY_SPACE ||
e.getKey() == GLFW.GLFW_KEY_ENTER
) {
setPressed(e.isPress());
return true;
@ -68,9 +68,9 @@ public abstract class BasicButton extends Component {
return false;
}
});
addListener(new Object() {
// Release when losing focus
@Subscribe
public void onFocusChange(FocusEvent e) {
@ -78,7 +78,7 @@ public abstract class BasicButton extends Component {
setPressed(false);
}
}
// Release when hover ends
@Subscribe
public void onHoverEnded(HoverEvent e) {
@ -86,7 +86,7 @@ public abstract class BasicButton extends Component {
setPressed(false);
}
}
// Release when disabled
@Subscribe
public void onDisabled(EnableEvent e) {
@ -94,16 +94,20 @@ public abstract class BasicButton extends Component {
setPressed(false);
}
}
// Trigger virtualClick when button is released
@Subscribe
public void onRelease(ButtonEvent.Release e) {
virtualClick();
}
});
}
public BasicButton(String name, String label, Font labelFont) {
this(name, new Label(name + ".Label", labelFont, label));
}
public BasicButton(String name, String label) {
this(name, label, new Font());
}
@ -111,7 +115,7 @@ public abstract class BasicButton extends Component {
public boolean isPressed() {
return isPressed;
}
public void click() {
setPressed(true);
setPressed(false);
@ -120,7 +124,7 @@ public abstract class BasicButton extends Component {
public void setPressed(boolean isPressed) {
if (this.isPressed != isPressed) {
this.isPressed = isPressed;
if (isPressed) {
takeFocus();
}
@ -128,16 +132,16 @@ public abstract class BasicButton extends Component {
dispatchEvent(ButtonEvent.create(this, this.isPressed));
}
}
public BasicButton addAction(Consumer<BasicButton> action) {
this.actions.add(Objects.requireNonNull(action, "action"));
return this;
}
public boolean removeAction(Consumer<BasicButton> action) {
return this.actions.remove(action);
}
public void virtualClick() {
this.actions.forEach(action -> {
action.accept(this);
@ -147,5 +151,5 @@ public abstract class BasicButton extends Component {
public Label getLabel() {
return label;
}
}

View File

@ -28,7 +28,11 @@ public class Button extends BasicButton {
public Button(String name, String label, Font labelFont) {
super(name, label, labelFont);
}
public Button(String name, Label label) {
super(name, label);
}
public Button(String name, String label) {
this(name, label, new Font());
}
@ -36,7 +40,7 @@ public class Button extends BasicButton {
@Override
protected void assembleSelf(RenderTarget target) {
// Border
Vec4 borderColor;
if (isPressed() || isHovered() || isFocused()) {
borderColor = Colors.BLUE;
@ -44,9 +48,9 @@ public class Button extends BasicButton {
borderColor = Colors.LIGHT_GRAY;
}
target.fill(getX(), getY(), getWidth(), getHeight(), borderColor);
// Inside area
if (isPressed()) {
// Do nothing
} else {
@ -58,20 +62,20 @@ public class Button extends BasicButton {
}
target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, backgroundColor);
}
// Change label font color
if (isPressed()) {
getLabel().setFont(getLabel().getFont().withColor(Colors.WHITE));
} else {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
}
}
@Override
protected void postAssembleSelf(RenderTarget target) {
// Apply disable tint
if (!isEnabled()) {
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
}

View File

@ -27,24 +27,24 @@ import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
public class Checkbox extends BasicButton {
private class Tick extends Component {
public Tick() {
super(Checkbox.this.getName() + ".Tick");
setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2));
}
@Override
protected void assembleSelf(RenderTarget target) {
int size = getPreferredSize().x;
int x = getX();
int y = getY() + (getHeight() - size) / 2;
// Border
Vec4 borderColor;
if (Checkbox.this.isPressed() || Checkbox.this.isHovered() || Checkbox.this.isFocused()) {
borderColor = Colors.BLUE;
@ -52,9 +52,9 @@ public class Checkbox extends BasicButton {
borderColor = Colors.LIGHT_GRAY;
}
target.fill(x, y, size, size, borderColor);
// Inside area
if (Checkbox.this.isPressed()) {
// Do nothing
} else {
@ -66,9 +66,9 @@ public class Checkbox extends BasicButton {
}
target.fill(x + 2, y + 2, size - 4, size - 4, backgroundColor);
}
// "Tick"
if (Checkbox.this.isChecked()) {
target.fill(x + 4, y + 4, size - 8, size - 8, Colors.BLUE);
}
@ -81,10 +81,10 @@ public class Checkbox extends BasicButton {
public Checkbox(String name, String label, Font labelFont, boolean check) {
super(name, label, labelFont);
this.checked = check;
assert getChildren().size() == 1 : "Checkbox expects that BasicButton contains exactly one child";
Component basicChild = getChild(0);
Group group = new Group(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10));
removeChild(basicChild);
setLayout(new LayoutAlign(0, 0.5f, 10));
@ -92,18 +92,18 @@ public class Checkbox extends BasicButton {
group.addChild(new Tick());
group.addChild(basicChild);
addChild(group);
addAction(b -> switchState());
}
public Checkbox(String name, String label, Font labelFont) {
this(name, label, labelFont, false);
}
public Checkbox(String name, String label, boolean check) {
this(name, label, new Font(), check);
}
public Checkbox(String name, String label) {
this(name, label, false);
}
@ -111,14 +111,14 @@ public class Checkbox extends BasicButton {
public void switchState() {
setChecked(!isChecked());
}
/**
* @return the checked
*/
public boolean isChecked() {
return checked;
}
/**
* @param checked the checked to set
*/
@ -129,21 +129,21 @@ public class Checkbox extends BasicButton {
@Override
protected void assembleSelf(RenderTarget target) {
// Change label font color
if (isPressed()) {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE));
} else {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
}
}
@Override
protected void postAssembleSelf(RenderTarget target) {
// Apply disable tint
if (!isEnabled()) {
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
}
}
}

View File

@ -30,30 +30,30 @@ import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
public class RadioButton extends BasicButton {
private class Tick extends Component {
public Tick() {
super(RadioButton.this.getName() + ".Tick");
setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2));
}
private void cross(RenderTarget target, int x, int y, int size, Vec4 color) {
target.fill(x + 4, y, size - 8, size, color);
target.fill(x + 2, y + 2, size - 4, size - 4, color);
target.fill(x, y + 4, size, size - 8, color);
}
@Override
protected void assembleSelf(RenderTarget target) {
int size = getPreferredSize().x;
int x = getX();
int y = getY() + (getHeight() - size) / 2;
// Border
Vec4 borderColor;
if (RadioButton.this.isPressed() || RadioButton.this.isHovered() || RadioButton.this.isFocused()) {
borderColor = Colors.BLUE;
@ -61,9 +61,9 @@ public class RadioButton extends BasicButton {
borderColor = Colors.LIGHT_GRAY;
}
cross(target, x, y, size, borderColor);
// Inside area
if (RadioButton.this.isPressed()) {
// Do nothing
} else {
@ -75,9 +75,9 @@ public class RadioButton extends BasicButton {
}
cross(target, x + 2, y + 2, size - 4, backgroundColor);
}
// "Tick"
if (RadioButton.this.isChecked()) {
cross(target, x + 4, y + 4, size - 8, Colors.BLUE);
}
@ -86,16 +86,16 @@ public class RadioButton extends BasicButton {
}
private boolean checked;
private RadioButtonGroup group = null;
public RadioButton(String name, String label, Font labelFont, boolean check) {
super(name, label, labelFont);
this.checked = check;
assert getChildren().size() == 1 : "RadioButton expects that BasicButton contains exactly one child";
Component basicChild = getChild(0);
Group group = new Group(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10));
removeChild(basicChild);
setLayout(new LayoutAlign(0, 0.5f, 10));
@ -103,16 +103,17 @@ public class RadioButton extends BasicButton {
group.addChild(new Tick());
group.addChild(basicChild);
addChild(group);
addListener(KeyEvent.class, e -> {
if (e.isRelease()) return false;
if (e.isRelease())
return false;
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;
} else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
if (this.group != null) {
@ -121,85 +122,87 @@ public class RadioButton extends BasicButton {
}
return true;
}
return false;
});
addAction(b -> setChecked(true));
}
public RadioButton(String name, String label, Font labelFont) {
this(name, label, labelFont, false);
}
public RadioButton(String name, String label, boolean check) {
this(name, label, new Font(), check);
}
public RadioButton(String name, String label) {
this(name, label, false);
}
/**
* @param group the group to set
*/
public RadioButton setGroup(RadioButtonGroup group) {
if (this.group != null) {
group.selectNext();
removeAction(group.listener);
group.buttons.remove(this);
group.getSelected(); // Clear reference if this was the only button in the group
group.getSelected(); // Clear reference if this was the only button
// in the group
}
this.group = group;
if (this.group != null) {
group.buttons.add(this);
addAction(group.listener);
}
setChecked(false);
return this;
}
/**
* @return the checked
*/
public boolean isChecked() {
return checked;
}
/**
* @param checked the checked to set
*/
public void setChecked(boolean checked) {
this.checked = checked;
if (group != null) {
group.listener.accept(this); // Failsafe for manual invocations of setChecked()
group.listener.accept(this); // Failsafe for manual invocations of
// setChecked()
}
}
@Override
protected void assembleSelf(RenderTarget target) {
// Change label font color
if (isPressed()) {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE));
} else {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
}
}
@Override
protected void postAssembleSelf(RenderTarget target) {
// Apply disable tint
if (!isEnabled()) {
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
}
}
}

View File

@ -76,6 +76,8 @@ public class LayerWorld extends Layer {
@Override
protected void doRender() {
client.getComms().processPackets();
Camera camera = client.getCamera();
if (camera.hasAnchor()) {
renderWorld();

View File

@ -0,0 +1,99 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.world.cro;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class ChunkRenderOptimizerSimple extends ChunkRenderOptimizer {
public interface BlockOptimizedSimple {
void getShapeParts(
DefaultChunkData chunk,
Vec3i relBlockInChunk,
Consumer<ShapePart> output
);
}
public interface TileOptimizedCustom {
void getShapeParts(
DefaultChunkData chunk,
Vec3i relBlockInChunk,
RelFace blockFace,
Consumer<ShapePart> output
);
}
private final Collection<ShapePart> parts = new ArrayList<>();
private final Consumer<ShapePart> partAdder = parts::add;
public ChunkRenderOptimizerSimple(String id) {
super(id);
}
@Override
public void startRender() {
parts.clear();
}
@Override
public void addBlock(BlockRender block, Vec3i relBlockInChunk) {
if (block instanceof BlockOptimizedSimple) {
((BlockOptimizedSimple) block).getShapeParts(chunk.getData(), relBlockInChunk, partAdder);
}
}
@Override
public void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace blockFace) {
if (tile instanceof TileOptimizedCustom) {
((TileOptimizedCustom) tile).getShapeParts(chunk.getData(), relBlockInChunk, blockFace, partAdder);
}
}
@Override
public Renderable endRender() {
if (parts.isEmpty()) {
return null;
}
return new Shape(
Usage.STATIC,
WorldRenderProgram.getDefault(),
parts.toArray(new ShapePart[parts.size()])
);
}
}

View File

@ -0,0 +1,170 @@
/*
* 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.world.tile;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
import glm.mat._3.Mat3;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSimple.TileOptimizedCustom;
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.DefaultChunkData;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.AxisRotations;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class TileRenderCross extends TileRender implements TileOptimizedCustom {
private static final float SQRT_2_OVER_2 = (float) Math.sqrt(2) / 2;
private static final float[] ONE_AND_NEGATIVE_ONE = new float[] { 1, -1 };
private final Texture texture;
private final float width;
public TileRenderCross(String id, Texture texture, boolean allowStretching) {
super(id);
this.texture = texture;
this.width = allowStretching ? 1 : SQRT_2_OVER_2;
}
public Texture getTexture(RelFace blockFace) {
return texture;
}
public Vec4 getColorMultiplier(RelFace blockFace) {
return Colors.WHITE;
}
@Override
public void getShapeParts(
DefaultChunkData chunk,
Vec3i bic,
RelFace blockFace,
Consumer<ShapePart> output
) {
Mat4 transform = Matrices.grab4();
Vec3 origin = Vectors.grab3();
Vec3 width = Vectors.grab3();
Vec3 height = Vectors.grab3();
Mat3 resolutionMatrix = AxisRotations.getResolutionMatrix3(blockFace.resolve(AbsFace.POS_Z));
Vec4 color = getColorMultiplier(blockFace);
Texture texture = getTexture(blockFace);
float originOffset = (1 - this.width) / 2;
WorldRenderProgram program = WorldRenderProgram.getDefault();
for (int i = 0; getTransform(chunk, bic, blockFace, i, transform); i++) {
for (float flip : ONE_AND_NEGATIVE_ONE) {
origin.set(flip * (originOffset - 0.5f), originOffset - 0.5f, 0);
width.set(flip * this.width, this.width, 0);
height.set(0, 0, 1);
VectorUtil.applyMat4(origin, transform);
VectorUtil.rotateOnly(width, transform);
VectorUtil.rotateOnly(height, transform);
origin.z += 1 - 0.5f;
if (blockFace != RelFace.UP) {
resolutionMatrix.mul(origin);
resolutionMatrix.mul(width);
resolutionMatrix.mul(height);
}
origin.add(bic.x, bic.y, bic.z);
output.accept(
ShapeParts.createRectangle(
program,
texture,
color,
origin,
width,
height,
false
)
);
output.accept(
ShapeParts.createRectangle(
program,
texture,
color,
origin,
width,
height,
true
)
);
}
}
Matrices.release(transform);
Vectors.release(origin);
Vectors.release(width);
Vectors.release(height);
}
protected boolean getTransform(
DefaultChunkData chunk,
Vec3i relBlockInChunk,
RelFace blockFace,
int count,
Mat4 output
) {
output.identity();
return count == 0;
}
@Override
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace) {
Collection<ShapePart> parts = new ArrayList<>(4);
getShapeParts(chunk, blockInChunk, blockFace, parts::add);
return new Shape(
Usage.STATIC,
WorldRenderProgram.getDefault(),
parts.toArray(new ShapePart[parts.size()])
);
}
@Override
public boolean needsOwnRenderable() {
return false;
}
}

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import ru.windcorp.progressia.common.comms.packets.Packet;
@ -53,6 +54,8 @@ public abstract class CommsChannel {
private State state = State.CONNECTING;
private final Collection<CommsListener> listeners = Collections.synchronizedCollection(new ArrayList<>());
private final List<Packet> pendingPackets = Collections.synchronizedList(new ArrayList<>());
protected abstract void doSendPacket(Packet packet) throws IOException;
@ -101,8 +104,19 @@ public abstract class CommsChannel {
public abstract void disconnect();
protected void onPacketReceived(Packet packet) {
pendingPackets.add(packet);
}
protected void forwardPacketToListeners(Packet packet) {
listeners.forEach(l -> l.onPacketReceived(packet));
}
public void processPackets() {
synchronized (pendingPackets) {
pendingPackets.forEach(this::forwardPacketToListeners);
pendingPackets.clear();
}
}
public void addListener(CommsListener listener) {
listeners.add(listener);

View File

@ -180,6 +180,25 @@ public class VectorUtil {
return applyMat4(inOut, mat, inOut);
}
public static Vec3 rotateOnly(Vec3 in, Mat4 mat, Vec3 out) {
if (out == null) {
out = new Vec3();
}
Mat3 mat3 = Matrices.grab3();
mat3.set(mat);
mat3.mul(in, out);
Matrices.release(mat3);
return out;
}
public static Vec3 rotateOnly(Vec3 inOut, Mat4 mat) {
return rotateOnly(inOut, mat, inOut);
}
public static Vec3 rotate(Vec3 in, Vec3 axis, float angle, Vec3 out) {
if (out == null) {
out = new Vec3();

View File

@ -162,5 +162,72 @@ public class Coordinates {
public static boolean isOnChunkBorder(int blockInChunk) {
return blockInChunk == 0 || blockInChunk == BLOCKS_PER_CHUNK - 1;
}
/*
* Generalized versions
*/
public static int convertGlobalToCell(int bits, int global) {
return global >> bits;
}
public static Vec3i convertGlobalToCell(
int bits,
Vec3i global,
Vec3i output
) {
if (output == null)
output = new Vec3i();
output.x = convertGlobalToCell(bits, global.x);
output.y = convertGlobalToCell(bits, global.y);
output.z = convertGlobalToCell(bits, global.z);
return output;
}
public static int convertGlobalToInCell(int bits, int global) {
int mask = (1 << bits) - 1;
return global & mask;
}
public static Vec3i convertGlobalToInCell(
int bits,
Vec3i global,
Vec3i output
) {
if (output == null)
output = new Vec3i();
output.x = convertGlobalToInCell(bits, global.x);
output.y = convertGlobalToInCell(bits, global.y);
output.z = convertGlobalToInCell(bits, global.z);
return output;
}
public static int getGlobal(int bits, int cell, int inCell) {
return inCell | (cell << bits);
}
public static Vec3i getGlobal(
int bits,
Vec3i cell,
Vec3i inCell,
Vec3i output
) {
if (output == null)
output = new Vec3i();
output.x = getGlobal(bits, cell.x, inCell.x);
output.y = getGlobal(bits, cell.y, inCell.y);
output.z = getGlobal(bits, cell.z, inCell.z);
return output;
}
public static boolean isOnCellBorder(int bits, int inCell) {
return inCell == 0 || inCell == (1 << bits) - 1;
}
}

View File

@ -15,20 +15,21 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.server;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
import ru.windcorp.progressia.server.comms.ClientPlayer;
import ru.windcorp.progressia.server.events.PlayerJoinedEvent;
import ru.windcorp.progressia.server.events.PlayerLeftEvent;
import ru.windcorp.progressia.test.TestContent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
import ru.windcorp.progressia.server.events.PlayerJoinedEvent;
import ru.windcorp.progressia.test.TestContent;
public class PlayerManager {
private final Server server;
@ -48,30 +49,37 @@ public class PlayerManager {
getServer().postEvent(new PlayerJoinedEvent.Immutable(getServer(), player));
}
public EntityData conjurePlayerEntity(String login) {
// TODO Live up to the name
if (TestContent.PLAYER_LOGIN.equals(login)) {
EntityData entity = spawnPlayerEntity(login);
return entity;
} else {
throw CrashReports.report(null, "Unknown login %s, javahorse stupid", login);
}
public void removePlayer(Player player) {
server.getWorld().getContainer().savePlayer(player, server);
this.players.remove(player);
getServer().postEvent(new PlayerLeftEvent.Immutable(getServer(), player));
}
private EntityData spawnPlayerEntity(String login) {
public Player conjurePlayer(ClientPlayer clientPlayer, String login) {
Player player = getServer().getWorld().getContainer().loadPlayer(login, clientPlayer, getServer());
if (player == null) { // create new player
EntityData entity = spawnPlayerEntity(clientPlayer, login);
player = new Player(entity, getServer(), clientPlayer);
}
getServer().getWorld().getData().addEntity(player.getEntity());
return player;
}
private EntityData spawnPlayerEntity(ClientPlayer clientPlayer, String login) {
EntityData player = EntityDataRegistry.getInstance().create("Test:Player");
player.setEntityId(TestContent.PLAYER_ENTITY_ID);
player.setPosition(getServer().getWorld().getGenerator().suggestSpawnLocation());
player.setUpVector(new Vec3(0, 0, 1));
player.setLookingAt(new Vec3(2, 1, 0));
getServer().getWorld().getData().addEntity(player);
return player;
}
public Object getMutex() {
return players;
}

View File

@ -46,6 +46,7 @@ import ru.windcorp.progressia.server.world.context.impl.DefaultServerContext;
import ru.windcorp.progressia.server.world.context.impl.ReportingServerContext;
import ru.windcorp.progressia.server.world.context.impl.RotatingServerContext;
import ru.windcorp.progressia.server.world.generation.WorldGenerator;
import ru.windcorp.progressia.server.world.io.WorldContainer;
import ru.windcorp.progressia.server.world.tasks.WorldAccessor;
import ru.windcorp.progressia.server.world.ticking.Change;
import ru.windcorp.progressia.server.world.ticking.Evaluation;
@ -78,11 +79,16 @@ public class Server {
private final TickingSettings tickingSettings = new TickingSettings();
public Server(DefaultWorldData world, Function<Server, WorldGenerator> generatorCreator) {
public Server(
DefaultWorldData world,
Function<Server, WorldGenerator> generatorCreator,
WorldContainer worldContainer
) {
this.world = new DefaultWorldLogic(
world,
this,
generatorCreator.apply(this),
worldContainer,
worldAccessor
);
this.serverThread = new ServerThread(this);
@ -91,6 +97,7 @@ public class Server {
this.playerManager = new PlayerManager(this);
this.loadManager = new LoadManager(this);
schedule(getClientManager()::processPackets);
schedule(new ChunkRequestDaemon(loadManager.getChunkManager())::tick);
schedule(new EntityRequestDaemon(loadManager.getEntityManager())::tick);
@ -240,14 +247,14 @@ public class Server {
public void scheduleChange(Change change) {
serverThread.getTicker().requestChange(change);
}
/**
* Delayed
*/
public void scheduleEvaluation(Evaluation evaluation) {
serverThread.getTicker().requestEvaluation(evaluation);
}
/**
* Immediate if possible, otherwise delayed
*/
@ -348,6 +355,7 @@ public class Server {
public void shutdown(String message) {
LogManager.getLogger().warn("Server.shutdown() is not yet implemented");
serverThread.stop();
getWorld().getContainer().close();
}
private void scheduleWorldTicks(Server server) {

View File

@ -18,7 +18,14 @@
package ru.windcorp.progressia.server;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.function.Function;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.server.world.generation.WorldGenerator;
import ru.windcorp.progressia.server.world.io.WorldContainer;
import ru.windcorp.progressia.server.world.io.region.RegionFormat;
import ru.windcorp.progressia.test.gen.TestGenerationConfig;
public class ServerState {
@ -33,10 +40,15 @@ public class ServerState {
ServerState.instance = instance;
}
public static void startServer() {
Server server = new Server(new DefaultWorldData(), TestGenerationConfig.createGenerator());
public static void startServer() throws IOException {
Function<Server, WorldGenerator> generator = new TestGenerationConfig().getGenerator();
WorldContainer container = new RegionFormat("Test:Region").create(Paths.get("tmp_world"));
Server server = new Server(new DefaultWorldData(), generator, container);
setInstance(server);
server.start();
}
private ServerState() {

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.server;
import java.util.concurrent.Executors;
@ -26,10 +26,13 @@ import org.apache.logging.log4j.LogManager;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.server.world.ticking.TickerCoordinator;
@SuppressWarnings("unused")
public class ServerThread implements Runnable {
private static final ThreadLocal<Server> SERVER_THREADS_MAP = new ThreadLocal<>();
private static boolean isShuttingDown;
public static Server getCurrentServer() {
return SERVER_THREADS_MAP.get();
}
@ -63,6 +66,7 @@ public class ServerThread implements Runnable {
}
public void start() {
isShuttingDown = false;
ticker.start();
executor.scheduleAtFixedRate(this, 0, 1000 / 20, TimeUnit.MILLISECONDS);
}
@ -70,6 +74,11 @@ public class ServerThread implements Runnable {
@Override
public void run() {
try {
if (isShuttingDown) {
getTicker().stop();
executor.shutdown();
return;
}
server.tick();
ticker.runOneTick();
} catch (Throwable e) {
@ -78,13 +87,10 @@ public class ServerThread implements Runnable {
}
public void stop() {
try {
executor.awaitTermination(10, TimeUnit.MINUTES);
} catch (InterruptedException e) {
LogManager.getLogger().warn("Received interrupt in ServerThread.stop(), aborting wait");
}
getTicker().stop();
isShuttingDown = true;
// getTicker().stop();
}
public Server getServer() {

View File

@ -26,11 +26,11 @@ import glm.vec._3.i.Vec3i;
import gnu.trove.TCollections;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import ru.windcorp.progressia.common.comms.CommsChannel;
import ru.windcorp.progressia.common.comms.CommsChannel.State;
import ru.windcorp.progressia.common.comms.packets.Packet;
import ru.windcorp.progressia.common.world.PacketSetGravityModel;
import ru.windcorp.progressia.common.world.PacketSetLocalPlayer;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.server.Player;
import ru.windcorp.progressia.server.Server;
@ -79,18 +79,24 @@ public class ClientManager {
setGravityModelPacket.set(getServer().getWorld().getData().getGravityModel());
client.sendPacket(setGravityModelPacket);
EntityData entity = getServer().getPlayerManager().conjurePlayerEntity(login);
Player player = new Player(entity, getServer(), client);
Player player = getServer().getPlayerManager().conjurePlayer(client, login);
getServer().getPlayerManager().addPlayer(player);
PacketSetLocalPlayer packet = new PacketSetLocalPlayer();
packet.set(entity.getEntityId());
packet.set(player.getEntity().getEntityId());
client.sendPacket(packet);
}
public void disconnectClient(Client client) {
client.disconnect();
clientsById.remove(client.getId());
if (client instanceof ClientPlayer) {
getServer().getPlayerManager().removePlayer(((ClientPlayer) client).getPlayer());
}
}
public void processPackets() {
getClients().forEach(CommsChannel::processPackets);
}
/**

View File

@ -24,7 +24,7 @@ import ru.windcorp.progressia.common.world.PacketSendChunk;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.server.Player;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.test.TestWorldDiskIO;
import ru.windcorp.progressia.server.world.io.WorldContainer;
/**
* Chunk manager provides facilities to load, unload and generate chunks for a
@ -51,6 +51,10 @@ public class ChunkManager {
public Server getServer() {
return getLoadManager().getServer();
}
public WorldContainer getContainer() {
return getServer().getWorld().getContainer();
}
/**
* Describes the result of an attempt to load a chunk.
@ -115,7 +119,7 @@ public class ChunkManager {
DefaultWorldData world = getServer().getWorld().getData();
DefaultChunkData chunk = TestWorldDiskIO.tryToLoad(chunkPos, world, getServer());
DefaultChunkData chunk = getServer().getWorld().getContainer().load(chunkPos, world, getServer());
if (chunk != null) {
world.addChunk(chunk);
return LoadResult.LOADED_FROM_DISK;
@ -141,7 +145,7 @@ public class ChunkManager {
}
world.removeChunk(chunk);
TestWorldDiskIO.saveChunk(chunk, getServer());
getContainer().save(chunk, getServer().getWorld().getData(), getServer());
return true;
}

View File

@ -32,6 +32,7 @@ import ru.windcorp.progressia.common.world.WorldDataListener;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.world.generation.WorldGenerator;
import ru.windcorp.progressia.server.world.io.WorldContainer;
import ru.windcorp.progressia.server.world.tasks.TickEntitiesTask;
import ru.windcorp.progressia.server.world.tasks.WorldAccessor;
import ru.windcorp.progressia.server.world.ticking.Evaluation;
@ -42,17 +43,20 @@ public class DefaultWorldLogic implements WorldLogic {
private final Server server;
private final WorldGenerator generator;
private final WorldContainer container;
private final Map<DefaultChunkData, DefaultChunkLogic> chunks = new HashMap<>();
private final Evaluation tickEntitiesTask = new TickEntitiesTask();
public DefaultWorldLogic(DefaultWorldData data, Server server, WorldGenerator generator, WorldAccessor accessor) {
public DefaultWorldLogic(DefaultWorldData data, Server server, WorldGenerator generator, WorldContainer container, WorldAccessor accessor) {
this.data = data;
this.server = server;
this.generator = generator;
data.setGravityModel(getGenerator().getGravityModel());
this.container = container;
data.addListener(new WorldDataListener() {
@Override
@ -105,6 +109,10 @@ public class DefaultWorldLogic implements WorldLogic {
public WorldGenerator getGenerator() {
return generator;
}
public WorldContainer getContainer() {
return container;
}
public DefaultChunkData generate(Vec3i chunkPos) {
DefaultChunkData chunk = getGenerator().generate(chunkPos);

View File

@ -405,7 +405,7 @@ class DefaultServerContextImpl extends DefaultServerContext
@Override
public int getTileCount(Vec3i location, RelFace face) {
assert requireContextRole(Role.TILE_STACK);
assert requireContextRole(Role.WORLD);
TileDataStack stack = world.getTilesOrNull(location, face.resolve(AbsFace.POS_Z));
if (stack == null)
return 0;

View File

@ -48,12 +48,11 @@ class PlanetTerrainGenerator {
this.parent = generator;
int seaLevel = (int) parent.getPlanet().getRadius();
SurfaceFloatField adjustedHeightMap = (f, n, w) -> heightMap.get(f, n, w) + generator.getPlanet().getRadius();
this.surfaceGenerators = AbsFace.mapToFaces(
face -> new SurfaceTerrainGenerator(
new Surface(face, seaLevel),
adjustedHeightMap,
heightMap,
layers
)
);

View File

@ -47,6 +47,7 @@ public class SurfaceTerrainGenerator {
Vec3 offset = new Vec3(chunk.getMinX(), chunk.getMinY(), chunk.getMinZ());
AxisRotations.relativize(offset, chunk.getUp(), offset);
offset.z -= surface.getSeaLevel();
SurfaceWorldContext context = surface.createContext(server, chunk, 0);

View File

@ -20,7 +20,9 @@ package ru.windcorp.progressia.server.world.generation.surface.context;
import java.util.Random;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.generic.TileGenericStackRO;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.server.world.context.ServerTileContext;
import ru.windcorp.progressia.server.world.context.impl.RotatingServerContext;
import ru.windcorp.progressia.server.world.generation.surface.Surface;
@ -110,5 +112,12 @@ public class SurfaceContextImpl extends RotatingServerContext implements Surface
super.push(location, face, layer);
return this;
}
@Override
public void addTile(Vec3i userLocation, RelFace userFace, TileData tile) {
if (getTileCount(userLocation, userFace) < TileGenericStackRO.TILES_PER_FACE) {
super.addTile(userLocation, userFace, tile);
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.server.world.io;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.server.Player;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.comms.ClientPlayer;
import java.nio.file.Path;
public interface WorldContainer {
Path getPath();
DefaultChunkData load(Vec3i position, DefaultWorldData world, Server server);
void save(DefaultChunkData chunk, DefaultWorldData world, Server server);
Player loadPlayer(String login, ClientPlayer clientPlayer, Server server);
void savePlayer(Player player, Server server);
void close();
}

View File

@ -0,0 +1,33 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.server.world.io;
import java.io.IOException;
import java.nio.file.Path;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
public abstract class WorldContainerFormat extends Namespaced {
public WorldContainerFormat(String id) {
super(id);
}
public abstract WorldContainer create(Path directory) throws IOException;
}

View File

@ -0,0 +1,166 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.server.world.io.region;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.state.IOContext;
import ru.windcorp.progressia.common.world.DecodingException;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.common.world.generic.ChunkMap;
import ru.windcorp.progressia.common.world.generic.ChunkMaps;
import ru.windcorp.progressia.common.world.io.ChunkIO;
import ru.windcorp.progressia.server.Server;
public class Region {
private static final boolean RESET_CORRUPTED = true;
public int loadedChunks;
private AtomicBoolean isUsing = new AtomicBoolean(false);
private AtomicBoolean isClosed = new AtomicBoolean(false);
private final RegionFile file;
private final ChunkMap<Integer> offsets = ChunkMaps.newHashMap();
public Region(RandomAccessFile file, Vec3i regionCoords) throws IOException {
this.file = new RegionFile(file);
try {
this.file.confirmHeaderHealth(offsets, regionCoords);
} catch (IOException e) {
RegionWorldContainer.LOG.debug("Uh the file broke");
if (RESET_CORRUPTED) {
this.file.makeHeader(regionCoords);
}
}
}
public RegionFile getFile() {
return file;
}
public void close() throws IOException {
this.file.close();
isClosed.lazySet(true);
}
public int getOffset(Vec3i chunkLoc) {
return offsets.get(chunkLoc);
}
public boolean hasOffset(Vec3i pos) {
return offsets.containsKey(pos);
}
public void putOffset(Vec3i pos, int offset) {
offsets.put(pos, offset);
}
public AtomicBoolean isClosed() {
return isClosed;
}
public AtomicBoolean isUsing() {
return isUsing;
}
public void save(DefaultChunkData chunk, Server server) throws IOException {
isUsing.set(true);
Vec3i pos = RegionWorldContainer.getInRegionCoords(chunk.getPosition());
if (!hasOffset(pos)) {
putOffset(pos, file.allocateChunk(pos));
}
int dataOffset = getOffset(pos);
byte[] buffer = saveToBuffer(chunk, server);
file.writeBuffer(buffer, dataOffset, pos);
isUsing.set(false);
}
private byte[] saveToBuffer(DefaultChunkData chunk, Server server) throws IOException {
ByteArrayOutputStream arrayStream = new ByteArrayOutputStream();
try (
DataOutputStream dataStream = new DataOutputStream(
new DeflaterOutputStream(
new BufferedOutputStream(arrayStream)
)
)
) {
ChunkIO.save(chunk, dataStream, IOContext.SAVE);
RegionWorldContainer.writeGenerationHint(chunk, dataStream, server);
}
return arrayStream.toByteArray();
}
public DefaultChunkData load(Vec3i chunkPos, DefaultWorldData world, Server server)
throws IOException,
DecodingException {
isUsing.set(true);
int dataOffset = 0;
Vec3i pos = RegionWorldContainer.getInRegionCoords(chunkPos);
if (hasOffset(pos)) {
dataOffset = getOffset(pos);
} else {
return null;
}
byte[] buffer = file.readBuffer(dataOffset);
DefaultChunkData result = loadFromBuffer(buffer, chunkPos, world, server);
isUsing.set(false);
return result;
}
private DefaultChunkData loadFromBuffer(byte[] buffer, Vec3i chunkPos, DefaultWorldData world, Server server)
throws IOException,
DecodingException {
DataInputStream dataStream = new DataInputStream(
new InflaterInputStream(
new BufferedInputStream(
new ByteArrayInputStream(buffer)
)
)
);
DefaultChunkData result = ChunkIO.load(world, chunkPos, dataStream, IOContext.SAVE);
RegionWorldContainer.readGenerationHint(result, dataStream, server);
return result;
}
}

View File

@ -0,0 +1,268 @@
package ru.windcorp.progressia.server.world.io.region;
import static ru.windcorp.progressia.server.world.io.region.RegionWorldContainer.REGION_DIAMETER;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.generic.ChunkMap;
/**
* Backend for the .progressia_region file.
* Use similarly to a file object
*/
public class RegionFile {
// 4 MiB
private static final int MAX_CHUNK_SIZE = 4 * 1024 * 1024;
private static final int SECTORS_BYTES = Short.BYTES;
private static final int SECTOR_SIZE = MAX_CHUNK_SIZE >> (SECTORS_BYTES * 8);
private static final int SECTOR_HEADER_SIZE = 1;
private static final int ID_HEADER_SIZE = 16;
private static final byte[] HEADER_ID = {'P','R','O','G'};
final byte endBytes[] = new byte[SECTOR_SIZE];
public static enum SectorType {
Ending(0), // Just an empty block
Data(1), // has a byte counting up in position 1, and then
PartitionLink(2),
BulkSaved(3); // TODO implement this
private final byte data;
SectorType(int i) {
this.data = (byte) i;
}
}
private static final int DEFINITION_SIZE = Integer.BYTES;
private static final int HEADER_SIZE = DEFINITION_SIZE * REGION_DIAMETER * REGION_DIAMETER * REGION_DIAMETER + ID_HEADER_SIZE;
private final RandomAccessFile file;
public RegionFile(RandomAccessFile inFile) {
file = inFile;
}
public void confirmHeaderHealth(ChunkMap<Integer> offsets, Vec3i regionCoords) throws IOException {
Set<Integer> used = new HashSet<Integer>();
int maxUsed = 0;
final int chunksPerRegion = REGION_DIAMETER * REGION_DIAMETER * REGION_DIAMETER;
if (file.length() < HEADER_SIZE) {
throw new IOException("File is too short to contain a header");
}
char prog;
for (int i=0;i<4;i++) {
prog = file.readChar();
if (prog != HEADER_ID[i])
{
throw new IOException("File is not a .progressia_chunk file");
}
}
int tempX = file.readInt();
int tempY = file.readInt();
int tempZ = file.readInt();
if (regionCoords.x != tempX || regionCoords.y != tempY || regionCoords.z != tempZ)
{
throw new IOException("Region file is in the wrong place/ has the wrong name.");
}
for (int i = 0; i < chunksPerRegion; i++) {
file.seek(i * DEFINITION_SIZE + ID_HEADER_SIZE);
int offset = file.readInt();
if (offset == 0) {
continue;
}
offset--;
Vec3i pos = new Vec3i();
pos.x = i / REGION_DIAMETER / REGION_DIAMETER;
pos.y = (i / REGION_DIAMETER) % REGION_DIAMETER;
pos.z = i % REGION_DIAMETER;
offsets.put(pos, offset);
boolean shouldEnd = false;
byte counter = 0;
while (!shouldEnd) {
if (offset > maxUsed) {
maxUsed = offset;
}
if (!used.add(offset)) {
throw new IOException("A sector is used twice");
}
file.seek(HEADER_SIZE + SECTOR_SIZE * offset);
byte type = file.readByte();
if (type == SectorType.Data.data) {
byte fileCounter = file.readByte();
if (fileCounter != counter) {
throw new IOException("An unexpected block was found");
}
counter++;
offset++;
} else if (type == SectorType.Ending.data) {
shouldEnd = true;
} else if (type == SectorType.PartitionLink.data) {
offset = file.readInt();
}
}
}
LogManager.getLogger("Region").debug("Efficiency of {}", (double) used.size() / maxUsed);
}
public void makeHeader(Vec3i regionCoords) throws IOException {
file.seek(0);
file.write(HEADER_ID);
file.writeInt(regionCoords.x);
file.writeInt(regionCoords.y);
file.writeInt(regionCoords.z);
for (int i = 0; i < HEADER_SIZE; i++) {
file.write(0);
}
}
public void writeBuffer(byte[] buffer, int dataOffset, Vec3i pos) throws IOException {
file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset);
int loc = 0;
byte tempBuffer[] = new byte[SECTOR_SIZE];
byte counter = 0;
boolean isDone = false;
while (!isDone) {
if (file.length() > HEADER_SIZE + SECTOR_SIZE * (dataOffset + 1)) {
file.seek(HEADER_SIZE + SECTOR_SIZE * (dataOffset + 1));
byte header = file.readByte();
if (header == SectorType.Data.data) {
byte fileCounter = file.readByte();
if (fileCounter != counter + 1) // This makes the actual
// partition place
{
int newOffset = allocateEmptySector();
file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset);
file.write(2);
file.writeInt(newOffset);
dataOffset = newOffset;
}
}
}
tempBuffer[0] = 1;
tempBuffer[1] = counter;
counter++;
for (int i = 0; i < (SECTOR_SIZE - SECTOR_HEADER_SIZE - 1); i++) {
if (loc * (SECTOR_SIZE - SECTOR_HEADER_SIZE - 1) + i < buffer.length) {
tempBuffer[i + SECTOR_HEADER_SIZE + 1] = buffer[loc * (SECTOR_SIZE - SECTOR_HEADER_SIZE - 1) + i];
} else {
isDone = true;
break;
}
}
loc++;
if (file.getFilePointer() < 256)
LogManager.getLogger("Region")
.debug("at {}, ({},{},{}), {}", file.getFilePointer(), pos.x, pos.y, pos.z, dataOffset);
file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset);
dataOffset++;
file.write(tempBuffer);
}
file.write(endBytes);
}
public int allocateChunk(Vec3i pos) throws IOException {
int definitionOffset = ID_HEADER_SIZE + DEFINITION_SIZE * (pos.z + REGION_DIAMETER * (pos.y + REGION_DIAMETER * pos.x));
int outputLen = (int) file.length();
int dataOffset = (int) Math.ceil((double) (outputLen - HEADER_SIZE) / SECTOR_SIZE);
file.seek(definitionOffset);
file.writeInt(dataOffset + 1);
file.setLength(HEADER_SIZE + dataOffset * SECTOR_SIZE);
return dataOffset;
}
private int allocateEmptySector() throws IOException {
int outputLen = (int) file.length();
int dataOffset = (int) Math.ceil((double) (outputLen - HEADER_SIZE) / SECTOR_SIZE);
file.setLength(HEADER_SIZE + dataOffset * SECTOR_SIZE);
return dataOffset;
}
public byte[] readBuffer(int dataOffset) throws IOException {
file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset);
int bufferPos = 0;
byte buffer[] = new byte[SECTOR_SIZE * 16];
byte tempBuffer[] = new byte[SECTOR_SIZE];
boolean reachedEnd = false;
byte counter = 0;
while (!reachedEnd) {
int bytesRead = file.read(tempBuffer, 0, SECTOR_SIZE);
if (bytesRead < 0) {
reachedEnd = true;
continue;
}
if (tempBuffer[0] == SectorType.Data.data) {
if (tempBuffer[1] != counter) {
throw new IOException(
"Sectors were read out of order\nExpected chunk number " + Byte.toString(counter)
+ " but encountered number " + Byte.toString(tempBuffer[1])
);
}
counter++;
if (buffer.length - bufferPos < SECTOR_SIZE - SECTOR_HEADER_SIZE - 1) {
byte newBuffer[] = new byte[buffer.length + SECTOR_SIZE * 16];
for (int i = 0; i < buffer.length; i++) // TODO dedicated
// copy, java-y at
// least
{
newBuffer[i] = buffer[i];
}
buffer = newBuffer;
}
for (int i = 0; i < SECTOR_SIZE - SECTOR_HEADER_SIZE - 1; i++) {
buffer[bufferPos + i] = tempBuffer[i + 2];
}
bufferPos += SECTOR_SIZE - SECTOR_HEADER_SIZE - 1;
} else if (tempBuffer[0] == SectorType.Ending.data) {
reachedEnd = true;
} else if (tempBuffer[0] == SectorType.PartitionLink.data) {
ByteBuffer intBuffer = ByteBuffer.wrap(tempBuffer);
int newOffset = intBuffer.getInt(1);
file.seek(HEADER_SIZE + SECTOR_SIZE * newOffset);
} else {
throw new IOException("Invalid sector ID.");
}
}
return buffer;
}
public void close() throws IOException {
file.close();
}
}

View File

@ -0,0 +1,37 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.server.world.io.region;
import java.io.IOException;
import java.nio.file.Path;
import ru.windcorp.progressia.server.world.io.WorldContainer;
import ru.windcorp.progressia.server.world.io.WorldContainerFormat;
public class RegionFormat extends WorldContainerFormat {
public RegionFormat(String id) {
super(id);
}
@Override
public WorldContainer create(Path directory) throws IOException {
return new RegionWorldContainer(directory);
}
}

View File

@ -0,0 +1,266 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.server.world.io.region;
import java.util.concurrent.atomic.AtomicBoolean;
import glm.vec._3.i.Vec3i;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import ru.windcorp.progressia.common.state.IOContext;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.DecodingException;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
import ru.windcorp.progressia.common.world.generic.ChunkMap;
import ru.windcorp.progressia.common.world.generic.ChunkMaps;
import ru.windcorp.progressia.server.Player;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.comms.ClientPlayer;
import ru.windcorp.progressia.server.world.io.WorldContainer;
import ru.windcorp.progressia.test.TestContent;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
public class RegionWorldContainer implements WorldContainer {
private static final boolean ENABLE = true;
private static final String REGION_FOLDER_NAME = "regions";
private static final String PLAYERS_FOLDER_NAME = "players";
private static final String REGION_NAME_FORMAT = REGION_FOLDER_NAME + "/" + "region_%d_%d_%d.progressia_region";
private static final String PLAYER_NAME_FORMAT = PLAYERS_FOLDER_NAME + "/" + "%s.progressia_player";
private static final int BITS_IN_CHUNK_COORDS = 4;
public static final int REGION_DIAMETER = 1 << BITS_IN_CHUNK_COORDS;
public static Vec3i getRegionCoords(Vec3i chunkCoords) {
return Coordinates.convertGlobalToCell(BITS_IN_CHUNK_COORDS, chunkCoords, null);
}
public static Vec3i getInRegionCoords(Vec3i chunkCoords) {
return Coordinates.convertGlobalToInCell(BITS_IN_CHUNK_COORDS, chunkCoords, null);
}
static final Logger LOG = LogManager.getLogger("TestWorldDiskIO");
private final Path path;
private final ChunkMap<Region> regions = ChunkMaps.newHashMap();
public RegionWorldContainer(Path path) throws IOException {
this.path = path;
Files.createDirectories(getPath());
Files.createDirectories(getPath().resolve(REGION_FOLDER_NAME));
Files.createDirectories(getPath().resolve(PLAYERS_FOLDER_NAME));
}
@Override
public DefaultChunkData load(Vec3i chunkPos, DefaultWorldData world, Server server) {
if (!ENABLE) {
return null;
}
try {
Region region = getRegion(chunkPos, false);
if (region == null) {
debug("Could not load chunk {} {} {}: region did not load", chunkPos);
return null;
}
DefaultChunkData result = region.load(chunkPos, world, server);
return result;
} catch (IOException | DecodingException e) {
warn("Failed to load chunk {} {} {}", chunkPos);
e.printStackTrace();
return null;
}
}
@Override
public void save(DefaultChunkData chunk, DefaultWorldData world, Server server) {
if (!ENABLE) {
return;
}
try {
debug("Saving chunk {} {} {}", chunk.getPosition());
Region region = getRegion(chunk.getPosition(), true);
region.save(chunk, server);
} catch (IOException e) {
warn("Failed to save chunk {} {} {}", chunk.getPosition());
e.printStackTrace();
}
}
@Override
public Player loadPlayer(String login, ClientPlayer clientPlayer, Server server) {
Path path = getPlayerPath(login);
if (!Files.exists(path)) {
LOG.debug("Could not load player {} because file {} does not exist", login, path);
return null;
}
EntityData player = EntityDataRegistry.getInstance().create("Test:Player");
try (
DataInputStream dataInputStream = new DataInputStream(
new BufferedInputStream(
Files.newInputStream(
getPlayerPath(login)
)
)
)
) {
player.read(dataInputStream, IOContext.SAVE);
player.setEntityId(TestContent.PLAYER_ENTITY_ID);
return new Player(player, server, clientPlayer);
} catch (IOException ioException) {
throw CrashReports.report(ioException, "Could not load player data: " + login);
}
}
@Override
public void savePlayer(Player player, Server server) {
Path path = getPlayerPath(player.getLogin());
try (
DataOutputStream dataOutputStream = new DataOutputStream(
new BufferedOutputStream(
Files.newOutputStream(path)
)
)
) {
player.getEntity().
write(dataOutputStream, IOContext.SAVE);
} catch (IOException ioException) {
throw CrashReports.report(ioException, "Could not save player %s data in file ", player.getLogin(), path);
}
}
private Region getRegion(Vec3i position, boolean createIfMissing) throws IOException {
Vec3i regionCoords = getRegionCoords(position);
Region region = regions.get(regionCoords);
if (region == null) {
debug("Region {} {} {} is not loaded, loading", regionCoords);
Path path = getRegionPath(regionCoords);
if (!createIfMissing && !Files.exists(path)) {
debug("Region {} {} {} does not exist on disk, aborting load", regionCoords);
return null;
}
region = openRegion(path, regionCoords);
debug("Region {} {} {} loaded", regionCoords);
}
return region;
}
private Region openRegion(Path path, Vec3i regionCoords) throws IOException {
RandomAccessFile raf = new RandomAccessFile(path.toFile(), "rw");
Region region = new Region(raf, regionCoords);
regions.put(regionCoords, region);
return region;
}
static void writeGenerationHint(DefaultChunkData chunk, DataOutputStream output, Server server)
throws IOException {
server.getWorld().getGenerator().writeGenerationHint(output, chunk.getGenerationHint());
}
static void readGenerationHint(DefaultChunkData chunk, DataInputStream input, Server server)
throws IOException,
DecodingException {
chunk.setGenerationHint(server.getWorld().getGenerator().readGenerationHint(input));
}
@Override
public Path getPath() {
return path;
}
private Path getRegionPath(Vec3i regionPos) {
return getPath().resolve(
String.format(
REGION_NAME_FORMAT,
regionPos.x,
regionPos.y,
regionPos.z
)
);
}
private Path getPlayerPath(String login) {
return getPath().resolve(
String.format(
PLAYER_NAME_FORMAT,
login
)
);
}
@Override
public void close() {
try {
ChunkMap<AtomicBoolean> isCloseds = ChunkMaps.newHashMap();
ChunkMap<AtomicBoolean> isUsings = ChunkMaps.newHashMap();
for (Vec3i region : regions.keys()) {
isCloseds.put(region, regions.get(region).isClosed());
isUsings.put(region, regions.get(region).isUsing());
}
boolean stillOpen = true;
while (stillOpen) {
stillOpen = false;
for (Vec3i region : regions.keys()) {
if (!isCloseds.get(region).get() && !isUsings.get(region).get()) {
regions.get(region).close();
} else if (isUsings.get(region).get()) {
stillOpen = false;
}
}
}
} catch (IOException e) {
throw CrashReports.report(e, "Could not close region files");
}
}
private static void debug(String message, Vec3i vector) {
if (LOG.isDebugEnabled()) {
LOG.debug(message, vector.x, vector.y, vector.z);
}
}
private static void warn(String message, Vec3i vector) {
LOG.warn(message, vector.x, vector.y, vector.z);
}
}

View File

@ -17,7 +17,11 @@
*/
package ru.windcorp.progressia.test;
import java.util.Collection;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.Button;
import ru.windcorp.progressia.client.graphics.gui.Checkbox;
@ -25,45 +29,71 @@ import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.RadioButton;
import ru.windcorp.progressia.client.graphics.gui.RadioButtonGroup;
import ru.windcorp.progressia.client.graphics.gui.menu.MenuLayer;
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
import ru.windcorp.progressia.server.Player;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.ServerState;
public class LayerButtonTest extends MenuLayer {
boolean alive = true;
public LayerButtonTest() {
super("ButtonTest");
addTitle();
Button blockableButton;
getContent().addChild((blockableButton = new Button("BlockableButton", "Blockable")).addAction(b -> {
System.out.println("Button Blockable!");
}));
blockableButton.setEnabled(false);
getContent().addChild(new Checkbox("EnableButton", "Enable").addAction(b -> {
blockableButton.setEnabled(((Checkbox) b).isChecked());
}));
RadioButtonGroup group = new RadioButtonGroup().addAction(g -> {
System.out.println("RBG! " + g.getSelected().getLabel().getCurrentText());
});
getContent().addChild(new RadioButton("RB1", "Moon").setGroup(group));
getContent().addChild(new RadioButton("RB2", "Type").setGroup(group));
getContent().addChild(new RadioButton("RB3", "Ice").setGroup(group));
getContent().addChild(new RadioButton("RB4", "Cream").setGroup(group));
getContent().getChild(getContent().getChildren().size() - 1).setEnabled(false);
getContent().addChild(new Label("Hint", new Font().withColor(Colors.LIGHT_GRAY), "This is a MenuLayer"));
getContent().addChild(new Button("Continue", "Continue").addAction(b -> {
getCloseAction().run();
}));
getContent().addChild(new Button("Quit", "Quit").addAction(b -> {
System.exit(0);
getContent().addChild(new Button("Menu", "Back To Menu").addAction(b -> {
getCloseAction().run();
Collection<Player> players = ServerState.getInstance().getPlayerManager().getPlayers();
players.clear();
ClientState.disconnectFromLocalServer();
GUI.addTopLayer(new LayerTestText("Text", new MutableStringLocalized("LayerText.Save"), layer -> {
Server server = ServerState.getInstance();
if (server != null && server.getWorld().getChunks().isEmpty()) {
GUI.removeLayer(layer);
// TODO Refactor, this shouldn't be here
GUI.addTopLayer(new LayerTitle("Title"));
ServerState.getInstance().shutdown("Safe Exit");
ServerState.setInstance(null);
TestPlayerControls.resetInstance();
}
}));
ClientState.setInstance(null);
}));
getContent().takeFocus();
}

View File

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

View File

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

View File

@ -82,6 +82,10 @@ public class Rocks {
public BlockData getBlock(RockVariant variant) {
return blocks.get(variant);
}
public Collection<BlockData> getBlocks() {
return blocks.values();
}
private void register() {
for (RockVariant variant : RockVariant.values()) {

View File

@ -15,13 +15,14 @@
* 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 static ru.windcorp.progressia.client.world.block.BlockRenderRegistry.getBlockTexture;
import static ru.windcorp.progressia.client.world.tile.TileRenderRegistry.getTileTexture;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
@ -38,6 +39,7 @@ 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;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface;
import ru.windcorp.progressia.client.world.entity.*;
import ru.windcorp.progressia.client.world.tile.*;
@ -54,6 +56,9 @@ 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.*;
@ -68,7 +73,7 @@ public class TestContent {
public static final List<BlockData> PLACEABLE_BLOCKS = new ArrayList<>();
public static final List<TileData> PLACEABLE_TILES = new ArrayList<>();
public static final Rocks ROCKS = new Rocks();
public static void registerContent() {
@ -96,36 +101,21 @@ public class TestContent {
register(new TestBlockLogicAir("Test:Air"));
placeableBlacklist.add("Test:Air");
register(new BlockData("Test:Dirt"));
register(new BlockRenderOpaqueCube("Test:Dirt", getBlockTexture("Dirt")));
register(new BlockLogic("Test:Dirt"));
register(new BlockData("Test:Stone"));
register(new BlockRenderOpaqueCube("Test:Stone", getBlockTexture("Stone")));
register(new BlockLogic("Test:Stone"));
registerSimplestBlock("Dirt");
registerSimplestBlock("Chernozem");
registerSimplestBlock("Stone");
registerSimplestBlock("Brick");
registerSimplestBlock("BrickWhite");
registerSimplestBlock("Sand");
registerSimplestBlock("Concrete");
registerSimplestBlock("WoodenPlank");
registerRocks();
register(new BlockData("Test:Brick"));
register(new BlockRenderOpaqueCube("Test:Brick", getBlockTexture("Brick")));
register(new BlockLogic("Test:Brick"));
register(new BlockData("Test:BrickWhite"));
register(new BlockRenderOpaqueCube("Test:BrickWhite", getBlockTexture("BrickWhite")));
register(new BlockLogic("Test:BrickWhite"));
register(new BlockData("Test:Glass"));
register(new BlockRenderTransparentCube("Test:Glass", getBlockTexture("Glass")));
register(new TestBlockLogicGlass("Test:Glass"));
register(new BlockData("Test:Sand"));
register(new BlockRenderOpaqueCube("Test:Sand", getBlockTexture("Sand")));
register(new BlockLogic("Test:Sand"));
register(new BlockData("Test:Concrete"));
register(new BlockRenderOpaqueCube("Test:Concrete", getBlockTexture("ConcreteBlock")));
register(new BlockLogic("Test:Concrete"));
register(new BlockData("Test:Log"));
register(
new BlockRenderOpaqueCube(
@ -136,13 +126,11 @@ public class TestContent {
)
);
register(new BlockLogic("Test:Log"));
register(new BlockData("Test:TemporaryLeaves"));
register(new BlockRenderTransparentCube("Test:TemporaryLeaves", getBlockTexture("TemporaryLeaves")));
register(new TestBlockLogicGlass("Test:TemporaryLeaves")); // Sic, using Glass logic for leaves because Test
register(new BlockData("Test:WoodenPlank"));
register(new BlockRenderOpaqueCube("Test:WoodenPlank", getBlockTexture("WoodenPlank")));
register(new BlockLogic("Test:WoodenPlank"));
// Sic, using Glass logic for leaves because Test
register(new TestBlockLogicGlass("Test:TemporaryLeaves"));
BlockDataRegistry.getInstance().values().forEach(PLACEABLE_BLOCKS::add);
PLACEABLE_BLOCKS.removeIf(b -> placeableBlacklist.contains(b.getId()));
@ -151,7 +139,7 @@ public class TestContent {
}
private static void registerRocks() {
ROCKS.create(RockType.IGNEOUS, "BlackGranite");
ROCKS.create(RockType.IGNEOUS, "RedGranite");
ROCKS.create(RockType.IGNEOUS, "Gabbro");
@ -159,94 +147,119 @@ public class TestContent {
ROCKS.create(RockType.METAMORPHIC, "Eclogite");
ROCKS.create(RockType.SEDIMENTARY, "Limestone");
ROCKS.create(RockType.SEDIMENTARY, "Dolomite");
ROCKS.registerAllRocks();
}
private static void registerTiles() {
Set<String> placeableBlacklist = new HashSet<>();
register(new TileData("Test:Grass"));
register(new TestTileRenderGrass("Test:Grass", getTileTexture("GrassTop"), getTileTexture("GrassSide")));
register(new TestTileLogicGrass("Test:Grass"));
Arrays.asList(
"Opaque",
"Patches",
"Web",
"Threads"
).forEach(variant -> {
String fullName = "Grass" + variant;
String id = "Test:" + fullName;
register(new TileData("Test:Stones"));
register(new TileRenderTransparentSurface("Test:Stones", getTileTexture("Stones")));
register(new HangingTileLogic("Test:Stones"));
register(new TileData(id));
register(
new TestTileRenderGrass(
id,
getTileTexture(fullName + "Top"),
getTileTexture(fullName + "Side"),
variant.equals("Opaque")
)
);
register(new TestTileLogicGrass(id));
});
register(new TileData("Test:YellowFlowers"));
register(new TileRenderTransparentSurface("Test:YellowFlowers", getTileTexture("YellowFlowers")));
register(new HangingTileLogic("Test:YellowFlowers"));
Arrays.asList(
"Yellow",
"White",
"Purple",
"Blue"
).forEach(color -> {
registerSimplestTransparentTile(color + "Flowers");
});
register(new TileData("Test:Sand"));
register(new TileRenderTransparentSurface("Test:Sand", getTileTexture("Sand")));
register(new HangingTileLogic("Test:Sand"));
registerSimplestTransparentTile("Stones");
registerSimplestTransparentTile("Sand");
register(new TileData("Test:SnowOpaque"));
register(new TileRenderOpaqueSurface("Test:SnowOpaque", getTileTexture("SnowOpaque")));
register(new HangingTileLogic("Test:SnowOpaque"));
registerSimplestOpaqueTile("SnowOpaque");
Arrays.asList(
"Half",
"Quarter"
).forEach(variant -> {
registerSimplestTransparentTile("Snow" + variant);
});
register(new TileData("Test:SnowHalf"));
register(new TileRenderTransparentSurface("Test:SnowHalf", getTileTexture("SnowHalf")));
register(new HangingTileLogic("Test:SnowHalf"));
registerSimplestTransparentTile("Clock");
registerSimplestOpaqueTile("CeilingTile1");
registerSimplestOpaqueTile("CeilingTile2");
registerSimplestOpaqueTile("WoodenPlank");
registerSimplestOpaqueTile("ParquetFloor");
registerSimplestOpaqueTile("Wallpaper");
registerSimplestOpaqueTile("WhitePaint");
registerSimplestOpaqueTile("RoughPaint");
registerSimplestOpaqueTile("DecorativeBricks");
registerSimplestTransparentTile("Painting");
registerSimplestOpaqueTile("TilesLarge");
registerSimplestOpaqueTile("TilesSmall");
register(new TileData("Test:SnowQuarter"));
register(new TileRenderTransparentSurface("Test:SnowQuarter", getTileTexture("SnowQuarter")));
register(new HangingTileLogic("Test:SnowQuarter"));
registerHerb("LowGrass", 6);
registerHerb("MediumGrass", 6);
registerHerb("TallGrass", 6);
register(new TileData("Test:Clock"));
register(new TileRenderTransparentSurface("Test:Clock", getTileTexture("Clock")));
register(new HangingTileLogic("Test:Clock"));
Arrays.asList(
"Dandelion",
"Lavander"
).forEach(variant -> {
String fullName = "Tiny" + variant + "Flowers";
String id = "Test:" + fullName;
register(new TileData("Test:CeilingTile1"));
register(new TileRenderOpaqueSurface("Test:CeilingTile1", getTileTexture("CeilingTile1")));
register(new HangingTileLogic("Test:CeilingTile1"));
register(new TileData("Test:CeilingTile2"));
register(new TileRenderOpaqueSurface("Test:CeilingTile2", getTileTexture("CeilingTile2")));
register(new HangingTileLogic("Test:CeilingTile2"));
register(new TileData("Test:WoodenPlank"));
register(new TileRenderOpaqueSurface("Test:WoodenPlank", getTileTexture("WoodenPlank")));
register(new HangingTileLogic("Test:WoodenPlank"));
register(new TileData("Test:ParquetFloor"));
register(new TileRenderOpaqueSurface("Test:ParquetFloor", getTileTexture("ParquetFloor")));
register(new HangingTileLogic("Test:ParquetFloor"));
register(new TileData("Test:Wallpaper"));
register(new TileRenderOpaqueSurface("Test:Wallpaper", getTileTexture("Wallpaper")));
register(new HangingTileLogic("Test:Wallpaper"));
register(new TileData("Test:WhitePaint"));
register(new TileRenderOpaqueSurface("Test:WhitePaint", getTileTexture("WhitePaint")));
register(new HangingTileLogic("Test:WhitePaint"));
register(new TileData("Test:RoughPaint"));
register(new TileRenderOpaqueSurface("Test:RoughPaint", getTileTexture("RoughPaint")));
register(new HangingTileLogic("Test:RoughPaint"));
register(new TileData("Test:DecorativeBricks"));
register(new TileRenderOpaqueSurface("Test:DecorativeBricks", getTileTexture("DecorativeBricks")));
register(new HangingTileLogic("Test:DecorativeBricks"));
register(new TileData("Test:Painting"));
register(new TileRenderTransparentSurface("Test:Painting", getTileTexture("Painting")));
register(new HangingTileLogic("Test:Painting"));
register(new TileData("Test:TilesLarge"));
register(new TileRenderOpaqueSurface("Test:TilesLarge", getTileTexture("TilesLarge")));
register(new HangingTileLogic("Test:TilesLarge"));
register(new TileData("Test:TilesSmall"));
register(new TileRenderOpaqueSurface("Test:TilesSmall", getTileTexture("TilesSmall")));
register(new HangingTileLogic("Test:TilesSmall"));
register(new TileData(id));
register(new TileRenderTinyFlower(id, getTileTexture(fullName), 8, 0.5f));
register(new HangingTileLogic(id));
});
registerHerb("Bush", 1);
registerHerb("Fern", 3);
TileDataRegistry.getInstance().values().forEach(PLACEABLE_TILES::add);
PLACEABLE_TILES.removeIf(b -> placeableBlacklist.contains(b.getId()));
PLACEABLE_TILES.sort(Comparator.comparing(TileData::getId));
}
private static void registerSimplestBlock(String name) {
String id = "Test:" + name;
register(new BlockData(id));
register(new BlockRenderOpaqueCube(id, getBlockTexture(name)));
register(new BlockLogic(id));
}
private static void registerSimplestOpaqueTile(String name) {
String id = "Test:" + name;
register(new TileData(id));
register(new TileRenderOpaqueSurface(id, getTileTexture(name)));
register(new HangingTileLogic(id));
}
private static void registerSimplestTransparentTile(String name) {
String id = "Test:" + name;
register(new TileData(id));
register(new TileRenderTransparentSurface(id, getTileTexture(name)));
register(new HangingTileLogic(id));
}
private static void registerHerb(String name, int maxCount) {
String id = "Test:" + name;
register(new TileData(id));
register(new TileRenderHerb(id, getTileTexture(name), maxCount));
register(new HangingTileLogic(id));
}
private static void registerEntities() {
float scale = 1.8f / 8;
registerEntityData("Test:Player", e -> e.setCollisionModel(new AABB(0, 0, 4 * scale, 0.8f, 0.8f, 1.8f)));
@ -285,6 +298,7 @@ public class TestContent {
i -> isAnythingSelected() && TestPlayerControls.getInstance().isBlockSelected()
)
);
logic.register(ControlLogic.of("Test:PlaceBlock", TestContent::onBlockPlaceReceived));
data.register("Test:PlaceTile", ControlPlaceTileData::new);
@ -298,7 +312,7 @@ public class TestContent {
)
);
logic.register(ControlLogic.of("Test:PlaceTile", TestContent::onTilePlaceReceived));
triggers.register(
ControlTriggers.localOf(
"Test:StartNextMusic",
@ -366,7 +380,7 @@ public class TestContent {
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
if (client == null || !client.isReady())
return null;
return client.getLocalPlayer().getSelection();
}
@ -433,16 +447,30 @@ public class TestContent {
Vec3i blockInWorld = controlData.getBlockInWorld();
AbsFace face = controlData.getFace();
if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null)
if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null) {
return;
if (server.getWorld().getData().getTiles(blockInWorld, face).isFull())
}
if (server.getWorld().getData().getTiles(blockInWorld, face).isFull()) {
return;
server.createAbsoluteContext().addTile(blockInWorld, face.relativize(AbsFace.POS_Z), tile);
}
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());
ChunkRenderOptimizerRegistry.getInstance().register("Core:SurfaceOptimizer", ChunkRenderOptimizerSurface::new);
ChunkRenderOptimizerRegistry.getInstance().register("Core:SimpleOptimizer", ChunkRenderOptimizerSimple::new);
GravityModelRegistry.getInstance().register("Test:TheGravityModel", TestGravityModel::new);
GravityModelRegistry.getInstance().register("Test:PlanetGravityModel", PlanetGravityModel::new);
}

View File

@ -46,10 +46,10 @@ import ru.windcorp.progressia.server.ServerState;
public class TestPlayerControls {
private static final TestPlayerControls INSTANCE = new TestPlayerControls();
private static TestPlayerControls instance = new TestPlayerControls();
public static TestPlayerControls getInstance() {
return INSTANCE;
return instance;
}
private static final double MODE_SWITCH_MAX_DELAY = 300 * Units.MILLISECONDS;
@ -90,6 +90,10 @@ public class TestPlayerControls {
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()) {

View File

@ -26,15 +26,18 @@ public class TestTileRenderGrass extends TileRenderSurface {
private final Texture topTexture;
private final Texture sideTexture;
private final boolean isOpaque;
public TestTileRenderGrass(
String id,
Texture top,
Texture side
Texture side,
boolean isOpaque
) {
super(id);
this.topTexture = top;
this.sideTexture = side;
this.isOpaque = isOpaque;
}
@Override
@ -44,7 +47,7 @@ public class TestTileRenderGrass extends TileRenderSurface {
@Override
public boolean isOpaque(RelFace face) {
return face == RelFace.UP;
return isOpaque && face == RelFace.UP;
}
}

View File

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

View File

@ -0,0 +1,89 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test;
import glm.mat._4.Mat4;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.world.tile.TileRenderCross;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class TileRenderHerb extends TileRenderCross {
/*
* Stolen from OpenJDK's Random implementation
* *evil cackling*
*/
private static final long MULTIPLIER = 0x5DEECE66DL;
private static final long ADDEND = 0xBL;
private static final long MASK = (1L << 48) - 1;
private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53)
private static long permute(long seed) {
return (seed * MULTIPLIER + ADDEND) & MASK;
}
private static double getDouble(long seed) {
final int mask26bits = (1 << 26) - 1;
final int mask27bits = (1 << 27) - 1;
int randomBitsX26 = (int) (seed & 0xFFFFFFFF);
int randomBitsX27 = (int) ((seed >>> Integer.SIZE) & 0xFFFFFFFF);
randomBitsX26 = randomBitsX26 & mask26bits;
randomBitsX27 = randomBitsX27 & mask27bits;
return (((long) (randomBitsX26) << 27) + randomBitsX27) * DOUBLE_UNIT;
}
private final int maxCount;
public TileRenderHerb(String id, Texture texture, int maxCount) {
super(id, texture, false);
this.maxCount = maxCount;
}
@Override
protected boolean getTransform(
DefaultChunkData chunk,
Vec3i relBlockInChunk,
RelFace blockFace,
int count,
Mat4 output
) {
long seed = permute(count ^ getId().hashCode());
seed = permute(seed + relBlockInChunk.x);
seed = permute(seed + relBlockInChunk.y);
seed = permute(seed + relBlockInChunk.z);
seed = permute(seed + blockFace.getId());
float x = (float) getDouble(seed) * 0.8f - 0.4f;
seed = permute(seed);
float y = (float) getDouble(seed) * 0.8f - 0.4f;
seed = permute(seed);
float size = (float) getDouble(seed) * 0.5f + 0.5f;
seed = permute(seed);
double rotation = getDouble(seed) * Math.PI / 8;
output.identity().translate(x, y, 0).scale(size).rotateZ(rotation);
return (count == 0) || ((count < maxCount) && (seed % 3 != 0));
}
}

View File

@ -0,0 +1,48 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test;
import glm.mat._4.Mat4;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class TileRenderTinyFlower extends TileRenderHerb {
private final float size;
public TileRenderTinyFlower(String id, Texture texture, int maxCount, float size) {
super(id, texture, maxCount);
this.size = size;
}
@Override
protected boolean getTransform(
DefaultChunkData chunk,
Vec3i relBlockInChunk,
RelFace blockFace,
int count,
Mat4 output
) {
boolean result = super.getTransform(chunk, relBlockInChunk, blockFace, count, output);
output.scale(size);
return result;
}
}

View File

@ -214,6 +214,10 @@ public class Fields {
public static Field bias(Field f, double bias) {
return tweak(f, 1, 1, bias);
}
public static Field anti(Field f) {
return tweak(f, 1, -1, 1);
}
public static Field octaves(Field f, double scaleFactor, double amplitudeFactor, int octaves) {
return (x, y) -> {
@ -221,14 +225,16 @@ public class Fields {
double scale = 1;
double amplitude = 1;
double cumulativeAmplitude = 0;
for (int i = 0; i < octaves; ++i) {
result += f.compute(x * scale, y * scale) * amplitude;
cumulativeAmplitude += amplitude;
scale *= scaleFactor;
amplitude /= amplitudeFactor;
}
return result;
return result / cumulativeAmplitude;
};
}

View File

@ -0,0 +1,95 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test.gen;
import java.util.Set;
import java.util.function.Function;
import com.google.common.collect.ImmutableSet;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.common.world.tile.TileDataRegistry;
import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField;
import ru.windcorp.progressia.server.world.generation.surface.SurfaceTopLayerFeature;
import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext;
import ru.windcorp.progressia.test.TestContent;
public class TestFlowerFeature extends SurfaceTopLayerFeature {
private static class FlowerGenerator {
private final TileData tile;
private final SurfaceFloatField floweriness;
public FlowerGenerator(TileData tile, Function<String, SurfaceFloatField> flowerinessGenerator) {
this.tile = tile;
this.floweriness = flowerinessGenerator.apply(tile.getName());
}
public void generate(SurfaceBlockContext context) {
if (context.getRandom().nextDouble() < floweriness.get(context)) {
context.addTile(RelFace.UP, tile);
}
}
}
private final Set<String> soilWhitelist;
{
ImmutableSet.Builder<String> b = ImmutableSet.builder();
b.add("Test:Dirt", "Test:Stone");
TestContent.ROCKS.getRocks().forEach(rock -> rock.getBlocks().forEach(block -> b.add(block.getId())));
soilWhitelist = b.build();
}
private final FlowerGenerator[] flowers;
public TestFlowerFeature(String id, Function<String, SurfaceFloatField> flowerinessGenerator) {
super(id);
this.flowers = TileDataRegistry.getInstance().values().stream()
.filter(tile -> tile.getName().endsWith("Flowers"))
.map(tile -> new FlowerGenerator(tile, flowerinessGenerator))
.toArray(FlowerGenerator[]::new);
}
@Override
protected void processTopBlock(SurfaceBlockContext context) {
if (context.getLocation().z < 0) {
return;
}
if (!soilWhitelist.contains(context.getBlock().getId())) {
return;
}
if (!context.pushRelative(RelFace.UP).logic().getBlock().isTransparent()) {
context.pop();
return;
}
context.pop();
for (FlowerGenerator flower : flowers) {
flower.generate(context);
}
}
@Override
protected boolean isSolid(SurfaceBlockContext context) {
return context.logic().getBlock().isSolid(RelFace.UP);
}
}

View File

@ -49,9 +49,18 @@ public class TestGenerationConfig {
private static final float CURVATURE = Units.get("100 m");
private static final float INNER_RADIUS = Units.get("200 m");
private static final Fields FIELDS = new Fields(SEED);
private final Fields fields = new Fields(SEED);
private final Function<Server, WorldGenerator> generator;
public TestGenerationConfig() {
this.generator = createGenerator();
}
public Function<Server, WorldGenerator> getGenerator() {
return generator;
}
public static Function<Server, WorldGenerator> createGenerator() {
private Function<Server, WorldGenerator> createGenerator() {
Planet planet = new Planet(
((int) PLANET_RADIUS) / Coordinates.CHUNK_SIZE,
@ -60,7 +69,7 @@ public class TestGenerationConfig {
INNER_RADIUS
);
TestHeightMap heightMap = new TestHeightMap(planet, planet.getRadius() / 4, FIELDS);
TestHeightMap heightMap = new TestHeightMap(planet, planet.getRadius() / 4, fields);
FloatRangeMap<TerrainLayer> layers = new ArrayFloatRangeMap<>();
registerTerrainLayers(layers);
@ -72,11 +81,11 @@ public class TestGenerationConfig {
}
private static void registerTerrainLayers(FloatRangeMap<TerrainLayer> layers) {
private void registerTerrainLayers(FloatRangeMap<TerrainLayer> layers) {
BlockData dirt = BlockDataRegistry.getInstance().get("Test:Dirt");
BlockData air = BlockDataRegistry.getInstance().get("Test:Air");
SurfaceFloatField cliffs = FIELDS.get("Test:CliffSelector");
SurfaceFloatField cliffs = fields.get("Test:Cliff");
WorleyProceduralNoise.Builder<TerrainLayer> builder = WorleyProceduralNoise.builder();
TestContent.ROCKS.getRocks().forEach(rock -> {
@ -88,9 +97,9 @@ public class TestGenerationConfig {
}
}, 1);
});
SurfaceFloatField rockDepthOffsets = FIELDS.register(
SurfaceFloatField rockDepthOffsets = fields.register(
"Test:RockDepthOffsets",
() -> tweak(FIELDS.primitive(), 40, 5)
() -> tweak(fields.primitive(), 40, 5)
);
RockLayer rockLayer = new RockLayer(builder.build(SEED), rockDepthOffsets);
@ -105,24 +114,35 @@ public class TestGenerationConfig {
layers.put(4, Float.POSITIVE_INFINITY, rockLayer);
}
private static void registerFeatures(List<SurfaceFeature> features) {
private void registerFeatures(List<SurfaceFeature> features) {
SurfaceFloatField forestiness = FIELDS.register(
"Test:Forestiness",
() -> squash(scale(FIELDS.primitive(), 200), 5)
SurfaceFloatField forestiness = fields.register(
"Test:Forest",
() -> squash(scale(fields.primitive(), 200), 5)
);
SurfaceFloatField grassiness = fields.register(
"Test:Grass",
f -> multiply(
tweak(octaves(fields.primitive(), 2, 2), 40, 0.5, 1.2),
squash(tweak(fields.get("Test:Forest", f), 1, -1, 1), 10),
anti(squash(fields.get("Test:Cliff", f), 10))
)
);
SurfaceFloatField floweriness = FIELDS.register(
"Test:Floweriness",
Function<String, SurfaceFloatField> floweriness = flowerName -> fields.register(
"Test:Flower" + flowerName,
f -> multiply(
scale(octaves(FIELDS.primitive(), 2, 2), 40),
tweak(FIELDS.get("Test:Forestiness", f), 1, -1, 1.1)
selectPositive(squash(scale(octaves(fields.primitive(), 2, 3), 100), 2), 1, 0.5),
tweak(fields.get("Test:Forest", f), 1, -1, 1.1),
anti(squash(fields.get("Test:Cliff", f), 10))
)
);
features.add(new TestBushFeature("Test:BushFeature", forestiness));
features.add(new TestTreeFeature("Test:TreeFeature", forestiness));
features.add(new TestGrassFeature("Test:GrassFeature", FIELDS.get("Test:CliffSelector"), floweriness));
features.add(new TestGrassFeature("Test:GrassFeature", grassiness));
features.add(new TestFlowerFeature("Test:FlowerFeature", floweriness));
}
}

View File

@ -23,40 +23,58 @@ import java.util.Set;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import ru.windcorp.progressia.common.util.ArrayFloatRangeMap;
import ru.windcorp.progressia.common.util.FloatRangeMap;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.block.BlockDataRegistry;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.common.world.tile.TileDataRegistry;
import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField;
import ru.windcorp.progressia.server.world.generation.surface.SurfaceTopLayerFeature;
import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext;
import ru.windcorp.progressia.test.TestContent;
public class TestGrassFeature extends SurfaceTopLayerFeature {
private static final Set<String> WHITELIST = ImmutableSet.of(
"Test:Dirt",
"Test:Stone",
"Test:GraniteMonolith",
"Test:GraniteCracked",
"Test:GraniteGravel"
);
private final Set<String> soilWhitelist;
{
ImmutableSet.Builder<String> b = ImmutableSet.builder();
b.add("Test:Dirt", "Test:Stone");
TestContent.ROCKS.getRocks().forEach(rock -> rock.getBlocks().forEach(block -> b.add(block.getId())));
soilWhitelist = b.build();
}
private final SurfaceFloatField grassiness;
private final SurfaceFloatField floweriness;
private final double scatterDensity = 1.0 / (3*3);
private final double scatterDensity = 1.0 / (3 * 3);
private final TileData grass = TileDataRegistry.getInstance().get("Test:Grass");
private final List<TileData> flowers = ImmutableList.of(
TileDataRegistry.getInstance().get("Test:YellowFlowers")
);
private final BlockData chernozem = BlockDataRegistry.getInstance().get("Test:Chernozem");
private final FloatRangeMap<TileData> flatGrasses = new ArrayFloatRangeMap<>();
{
flatGrasses.put(0.4f, Float.POSITIVE_INFINITY, TileDataRegistry.getInstance().get("Test:GrassOpaque"));
flatGrasses.put(0.2f, 0.4f, TileDataRegistry.getInstance().get("Test:GrassPatches"));
flatGrasses.put(0.1f, 0.2f, TileDataRegistry.getInstance().get("Test:GrassWeb"));
flatGrasses.put(0.05f, 0.1f, TileDataRegistry.getInstance().get("Test:GrassThreads"));
}
private final FloatRangeMap<TileData> herbGrasses = new ArrayFloatRangeMap<>();
{
herbGrasses.put(0.6f, 1, TileDataRegistry.getInstance().get("Test:TallGrass"));
herbGrasses.put(0.4f, 0.6f, TileDataRegistry.getInstance().get("Test:MediumGrass"));
herbGrasses.put(0.1f, 0.4f, TileDataRegistry.getInstance().get("Test:LowGrass"));
}
private final List<TileData> scatter = ImmutableList.of(
TileDataRegistry.getInstance().get("Test:Stones"),
TileDataRegistry.getInstance().get("Test:Sand")
TileDataRegistry.getInstance().get("Test:Sand"),
TileDataRegistry.getInstance().get("Test:Bush"),
TileDataRegistry.getInstance().get("Test:Fern")
);
public TestGrassFeature(String id, SurfaceFloatField grassiness, SurfaceFloatField floweriness) {
public TestGrassFeature(String id, SurfaceFloatField grassiness) {
super(id);
this.grassiness = grassiness;
this.floweriness = floweriness;
}
@Override
@ -64,26 +82,18 @@ public class TestGrassFeature extends SurfaceTopLayerFeature {
if (context.getLocation().z < 0) {
return;
}
if (!WHITELIST.contains(context.getBlock().getId())) {
if (!soilWhitelist.contains(context.getBlock().getId())) {
return;
}
if (!context.pushRelative(RelFace.UP).logic().getBlock().isTransparent()) {
context.pop();
return;
}
context.pop();
double grassiness = this.grassiness.get(context);
if (grassiness < 0.2) {
growGrass(context);
}
growGrass(context, this.grassiness.get(context));
placeScatter(context);
if (grassiness < 0.2) {
growFlowers(context);
}
}
private void placeScatter(SurfaceBlockContext context) {
@ -93,24 +103,32 @@ public class TestGrassFeature extends SurfaceTopLayerFeature {
}
}
private void growGrass(SurfaceBlockContext context) {
for (RelFace face : RelFace.getFaces()) {
if (face == RelFace.DOWN) continue;
if (context.pushRelative(face).logic().getBlock().isTransparent()) {
context.pop();
context.addTile(face, grass);
} else {
context.pop();
private void growGrass(SurfaceBlockContext context, double grassiness) {
TileData flatGrass = flatGrasses.get((float) grassiness);
if (flatGrass != null) {
for (RelFace face : RelFace.getFaces()) {
if (face == RelFace.DOWN)
continue;
if (context.pushRelative(face).logic().getBlock().isTransparent()) {
context.pop();
context.addTile(face, flatGrass);
} else {
context.pop();
}
}
if (grassiness > 0.8 && context.getBlock().getId().equals("Test:Dirt")) {
context.setBlock(chernozem);
}
}
}
private void growFlowers(SurfaceBlockContext context) {
if (context.getRandom().nextDouble() < floweriness.get(context)) {
TileData tile = pickRandom(context, flowers);
context.addTile(RelFace.UP, tile);
if (context.getRandom().nextDouble() < grassiness) {
TileData herbGrass = herbGrasses.get((float) grassiness);
if (herbGrass != null) {
context.addTile(RelFace.UP, herbGrass);
}
}
}

View File

@ -54,9 +54,9 @@ public class TestHeightMap implements SurfaceFloatField {
tweak(octaves(fields.primitive(), 2, 3), 50, 0.2)
);
fields.register("Test:CliffSelector", face, multiply(
fields.register("Test:Cliff", face, multiply(
shoreCliffSelector,
bias(select(shoreCliffs, 0, 0.07), 0)
select(shoreCliffs, 0, 0.07)
));
fields.register("Test:Height", face, cutoff(add(

View File

@ -21,4 +21,14 @@ LayerTestGUI.PlacementModeHint = (Blocks %s Tiles: Ctrl + Mouse Wheel)
LayerTestGUI.IsFullscreen = Fullscreen: %5s (F11)
LayerTestGUI.IsVSync = VSync: %5s (F12)
LayerButtonTest.Title = Button Test
LayerButtonTest.Title = Button Test
LayerButtonTest.Return = Back To Menu
LayerTitle.Title = Progressia
LayerTitle.Play = Play World
LayerTitle.Reset = Reset World
LayerTitle.Options = Options
LayerTitle.Quit = Quit
LayerText.Load = Loading...
LayerText.Save = Saving...

View File

@ -21,4 +21,14 @@ LayerTestGUI.PlacementModeHint = (Блок %s плитки: Ctrl + прокру
LayerTestGUI.IsFullscreen = Полный экран: %5s (F11)
LayerTestGUI.IsVSync = Верт. синхр.: %5s (F12)
LayerButtonTest.Title = Тест Кнопок
LayerButtonTest.Title = Тест кнопок
LayerButtonTest.Return = Главное меню
LayerTitle.Title = Прогрессия
LayerTitle.Play = Играть
LayerTitle.Reset = Сбросить мир
LayerTitle.Options = Настройки
LayerTitle.Quit = Выход
LayerText.Load = Загрузка...
LayerText.Save = Сохранение...

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

View File

@ -27,6 +27,10 @@
<Logger name="Ticker 0" level="DEBUG" />
-->
<!-- Uncomment to enable Region file logger debugging
<Logger name="TestWorldDiskIO" level="DEBUG" />
-->
<Root level="info">
<AppenderRef ref="FileLog" />
<AppenderRef ref="Console" />