Implemented entity spawning and despawning and changed some stuff

- Non-player entities can now be added and removed properly
  - WorldLogic.spawnEntity can be used to add entity and create an
entity ID
- Statie is back, more beautiful than ever!
  - Place Test:StatieSpawner block and wait to make her spawn
- TPS display now features a visual tick indicator
- Updated Fern texture
This commit is contained in:
OLEGSHA 2021-08-24 13:59:28 +03:00
parent c7e7d3bdac
commit 1d28f32865
Signed by: OLEGSHA
GPG Key ID: E57A4B08D64AFF7A
17 changed files with 386 additions and 36 deletions

View File

@ -260,6 +260,18 @@ public class Shapes {
return this.setSize(size, size, size); return this.setSize(size, size, size);
} }
public PppBuilder centerAt(float x, float y, float z) {
origin.set(x, y, z);
origin.mul(2);
origin.sub(width);
origin.sub(height);
origin.sub(depth);
origin.div(2);
return this;
}
public PppBuilder flip() { public PppBuilder flip() {
this.flip = true; this.flip = true;
return this; return this;

View File

@ -145,7 +145,7 @@ public class ClientManager {
return; return;
if (!(c instanceof ClientPlayer)) if (!(c instanceof ClientPlayer))
return; return;
if (!((ClientPlayer) c).isChunkVisible(entityId)) if (!((ClientPlayer) c).isEntityVisible(entityId))
return; return;
c.sendPacket(packet); c.sendPacket(packet);
}); });

View File

@ -19,6 +19,9 @@
package ru.windcorp.progressia.server.comms; package ru.windcorp.progressia.server.comms;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import gnu.trove.TCollections;
import gnu.trove.set.TLongSet;
import gnu.trove.set.hash.TLongHashSet;
import ru.windcorp.progressia.common.world.generic.ChunkSet; import ru.windcorp.progressia.common.world.generic.ChunkSet;
import ru.windcorp.progressia.common.world.generic.ChunkSets; import ru.windcorp.progressia.common.world.generic.ChunkSets;
import ru.windcorp.progressia.server.Player; import ru.windcorp.progressia.server.Player;
@ -53,8 +56,19 @@ public abstract class ClientPlayer extends ClientChat {
return player.getServer().getLoadManager().getVisionManager().getVisibleChunks(player); return player.getServer().getLoadManager().getVisionManager().getVisibleChunks(player);
} }
public boolean isChunkVisible(long entityId) { public boolean isEntityVisible(long entityId) {
return true; if (player == null)
return false;
return player.getServer().getLoadManager().getVisionManager().isEntityVisible(entityId, player);
}
private static final TLongSet EMPTY_TLONGSET = TCollections.unmodifiableSet(new TLongHashSet(0));
public TLongSet getVisibleEntities(Player player) {
if (player == null) {
return EMPTY_TLONGSET;
}
return player.getServer().getLoadManager().getVisionManager().getVisibleEntities(player);
} }
} }

View File

@ -17,10 +17,14 @@
*/ */
package ru.windcorp.progressia.server.management.load; package ru.windcorp.progressia.server.management.load;
import java.util.HashSet;
import java.util.Set;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.DefaultChunkData; import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.PacketRevokeChunk; import ru.windcorp.progressia.common.world.PacketRevokeChunk;
import ru.windcorp.progressia.common.world.PacketSendChunk; import ru.windcorp.progressia.common.world.PacketSendChunk;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.DefaultWorldData; import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.server.Player; import ru.windcorp.progressia.server.Player;
import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.Server;
@ -140,6 +144,11 @@ public class ChunkManager {
return false; return false;
} }
// TODO allow entities to be saved first
Set<EntityData> entitiesToRemove = new HashSet<>();
world.forEachEntityInChunk(chunkPos, entitiesToRemove::add);
entitiesToRemove.forEach(world::removeEntity);
world.removeChunk(chunk); world.removeChunk(chunk);
TestWorldDiskIO.saveChunk(chunk, getServer()); TestWorldDiskIO.saveChunk(chunk, getServer());

View File

@ -25,6 +25,9 @@ import java.util.function.Consumer;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import gnu.trove.TCollections;
import gnu.trove.set.TLongSet;
import gnu.trove.set.hash.TLongHashSet;
import ru.windcorp.progressia.common.world.generic.ChunkSet; import ru.windcorp.progressia.common.world.generic.ChunkSet;
import ru.windcorp.progressia.common.world.generic.ChunkSets; import ru.windcorp.progressia.common.world.generic.ChunkSets;
import ru.windcorp.progressia.server.Player; import ru.windcorp.progressia.server.Player;
@ -85,6 +88,28 @@ public class VisionManager {
return vision.getVisibleChunks(); return vision.getVisibleChunks();
} }
public boolean isEntityVisible(long entityId, Player player) {
PlayerVision vision = getVision(player, false);
if (vision == null) {
return false;
}
return vision.isEntityVisible(entityId);
}
private static final TLongSet EMPTY_TLONGSET = TCollections.unmodifiableSet(new TLongHashSet(0));
public TLongSet getVisibleEntities(Player player) {
PlayerVision vision = getVision(player, false);
if (vision == null) {
return EMPTY_TLONGSET;
}
return vision.getVisibleEntities();
}
/** /**
* @return the loadManager * @return the loadManager
*/ */

View File

@ -21,6 +21,7 @@ package ru.windcorp.progressia.server.world;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import glm.Glm; import glm.Glm;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
@ -89,6 +90,27 @@ public class DefaultWorldLogic implements WorldLogic {
return getData().getEntity(entityId); return getData().getEntity(entityId);
} }
@Override
public void spawnEntity(EntityData entity) {
Objects.requireNonNull(entity, "entity");
if (entity.getEntityId() != EntityData.NULL_ENTITY_ID) {
throw new IllegalArgumentException(
"Entity ID of entity " + entity
+ " is not unassigned; use WorldData.addEntity to add entity with assigned entity ID"
);
}
long entityId;
// TODO this should be synchronized on the entity set
do {
entityId = server.getAdHocRandom().nextLong();
} while (entityId == EntityData.NULL_ENTITY_ID || getEntity(entityId) != null);
entity.setEntityId(entityId);
getData().addEntity(entity);
}
public Evaluation getTickEntitiesTask() { public Evaluation getTickEntitiesTask() {
return tickEntitiesTask; return tickEntitiesTask;
} }
@ -110,31 +132,49 @@ public class DefaultWorldLogic implements WorldLogic {
DefaultChunkData chunk = getGenerator().generate(chunkPos); DefaultChunkData chunk = getGenerator().generate(chunkPos);
if (!Glm.equals(chunkPos, chunk.getPosition())) { if (!Glm.equals(chunkPos, chunk.getPosition())) {
throw CrashReports.report(null, "Generator %s has generated a chunk at (%d; %d; %d) when requested to generate a chunk at (%d; %d; %d)", throw CrashReports.report(
null,
"Generator %s has generated a chunk at (%d; %d; %d) when requested to generate a chunk at (%d; %d; %d)",
getGenerator(), getGenerator(),
chunk.getX(), chunk.getY(), chunk.getZ(), chunk.getX(),
chunkPos.x, chunkPos.y, chunkPos.z chunk.getY(),
chunk.getZ(),
chunkPos.x,
chunkPos.y,
chunkPos.z
); );
} }
if (getData().getChunk(chunk.getPosition()) != chunk) { if (getData().getChunk(chunk.getPosition()) != chunk) {
if (isChunkLoaded(chunkPos)) { if (isChunkLoaded(chunkPos)) {
throw CrashReports.report(null, "Generator %s has returned a chunk different to the chunk that is located at (%d; %d; %d)", throw CrashReports.report(
null,
"Generator %s has returned a chunk different to the chunk that is located at (%d; %d; %d)",
getGenerator(), getGenerator(),
chunkPos.x, chunkPos.y, chunkPos.z chunkPos.x,
chunkPos.y,
chunkPos.z
); );
} else { } else {
throw CrashReports.report(null, "Generator %s has returned a chunk that is not loaded when requested to generate a chunk at (%d; %d; %d)", throw CrashReports.report(
null,
"Generator %s has returned a chunk that is not loaded when requested to generate a chunk at (%d; %d; %d)",
getGenerator(), getGenerator(),
chunkPos.x, chunkPos.y, chunkPos.z chunkPos.x,
chunkPos.y,
chunkPos.z
); );
} }
} }
if (!getChunk(chunk).isReady()) { if (!getChunk(chunk).isReady()) {
throw CrashReports.report(null, "Generator %s has returned a chunk that is not ready when requested to generate a chunk at (%d; %d; %d)", throw CrashReports.report(
null,
"Generator %s has returned a chunk that is not ready when requested to generate a chunk at (%d; %d; %d)",
getGenerator(), getGenerator(),
chunkPos.x, chunkPos.y, chunkPos.z chunkPos.x,
chunkPos.y,
chunkPos.z
); );
} }

View File

@ -21,10 +21,13 @@ import java.util.Collection;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.rels.BlockFace; import ru.windcorp.progressia.common.world.rels.BlockFace;
public interface WorldLogic extends WorldLogicRO { public interface WorldLogic extends WorldLogicRO {
void spawnEntity(EntityData entity);
/* /*
* Override return types * Override return types
*/ */

View File

@ -0,0 +1,75 @@
/*
* 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.tasks;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.server.Server;
class AddEntity extends CachedChange {
private EntityData entity;
public AddEntity(Consumer<? super CachedChange> disposer) {
super(disposer);
}
public void set(EntityData entity) {
if (this.entity != null)
throw new IllegalStateException("Entity is not null. Current: " + this.entity + "; requested: " + entity);
this.entity = entity;
}
@Override
public void affect(Server server) {
server.getWorld().spawnEntity(entity);
}
@Override
public void getRelevantChunk(Vec3i output) {
// Do nothing
}
@Override
public boolean isThreadSensitive() {
return false;
}
@Override
public void dispose() {
super.dispose();
this.entity = null;
}
@Override
public int hashCode() {
return System.identityHashCode(entity);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof AddEntity))
return false;
return ((AddEntity) obj).entity == entity;
}
}

View File

@ -0,0 +1,75 @@
/*
* 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.tasks;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.server.Server;
class RemoveEntity extends CachedChange {
private long entityId = EntityData.NULL_ENTITY_ID;
public RemoveEntity(Consumer<? super CachedChange> disposer) {
super(disposer);
}
public void set(long entityId) {
if (this.entityId != EntityData.NULL_ENTITY_ID)
throw new IllegalStateException("Entity ID is not null. Current: " + this.entityId + "; requested: " + entityId);
this.entityId = entityId;
}
@Override
public void affect(Server server) {
server.getWorld().getData().removeEntity(entityId);
}
@Override
public void getRelevantChunk(Vec3i output) {
// Do nothing
}
@Override
public boolean isThreadSensitive() {
return false;
}
@Override
public void dispose() {
super.dispose();
this.entityId = EntityData.NULL_ENTITY_ID;
}
@Override
public int hashCode() {
return Long.hashCode(entityId);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof RemoveEntity))
return false;
return ((RemoveEntity) obj).entityId == entityId;
}
}

View File

@ -46,6 +46,9 @@ public class WorldAccessor implements ReportingServerContext.ChangeListener {
.addClass(SetBlock.class, () -> new SetBlock(disposer)) .addClass(SetBlock.class, () -> new SetBlock(disposer))
.addClass(AddTile.class, () -> new AddTile(disposer)) .addClass(AddTile.class, () -> new AddTile(disposer))
.addClass(RemoveTile.class, () -> new RemoveTile(disposer)) .addClass(RemoveTile.class, () -> new RemoveTile(disposer))
.addClass(AddEntity.class, () -> new AddEntity(disposer))
.addClass(RemoveEntity.class, () -> new RemoveEntity(disposer))
.addClass(ChangeEntity.class, () -> new ChangeEntity(disposer)) .addClass(ChangeEntity.class, () -> new ChangeEntity(disposer))
.addClass(BlockTriggeredUpdate.class, () -> new BlockTriggeredUpdate(disposer)) .addClass(BlockTriggeredUpdate.class, () -> new BlockTriggeredUpdate(disposer))
@ -81,14 +84,16 @@ public class WorldAccessor implements ReportingServerContext.ChangeListener {
@Override @Override
public void onEntityAdded(EntityData entity) { public void onEntityAdded(EntityData entity) {
// TODO Auto-generated method stub AddEntity change = cache.grab(AddEntity.class);
change.set(entity);
server.requestChange(change);
} }
@Override @Override
public void onEntityRemoved(long entityId) { public void onEntityRemoved(long entityId) {
// TODO Auto-generated method stub RemoveEntity change = cache.grab(RemoveEntity.class);
change.set(entityId);
server.requestChange(change);
} }
@Override @Override

View File

@ -266,12 +266,21 @@ public class LayerTestGUI extends GUILayer {
} }
private static final String[] CLOCK_CHARS = "\u2591\u2598\u259d\u2580\u2596\u258c\u259e\u259b\u2597\u259a\u2590\u259c\u2584\u2599\u259f\u2588"
.chars().mapToObj(c -> ((char) c) + "").toArray(String[]::new);
private static String getTPSClockChar() {
return CLOCK_CHARS[(int) (ServerState.getInstance().getUptimeTicks() % CLOCK_CHARS.length)];
}
private static final Averager FPS_RECORD = new Averager(); private static final Averager FPS_RECORD = new Averager();
private static final Averager TPS_RECORD = new Averager(); private static final Averager TPS_RECORD = new Averager();
private static final Supplier<CharSequence> TPS_STRING = DynamicStrings.builder() private static final Supplier<CharSequence> TPS_STRING = DynamicStrings.builder()
.addDyn(new MutableStringLocalized("LayerTestGUI.TPSDisplay")) .addDyn(new MutableStringLocalized("LayerTestGUI.TPSDisplay"))
.addDyn(() -> TPS_RECORD.update(ServerState.getInstance().getTPS()), 5, 1) .addDyn(() -> TPS_RECORD.update(ServerState.getInstance().getTPS()), 5, 1)
.add(' ')
.addDyn(LayerTestGUI::getTPSClockChar)
.buildSupplier(); .buildSupplier();
private static final Supplier<CharSequence> POS_STRING = DynamicStrings.builder() private static final Supplier<CharSequence> POS_STRING = DynamicStrings.builder()

View File

@ -0,0 +1,51 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.block.BlockDataRegistry;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.server.world.block.BlockLogic;
import ru.windcorp.progressia.server.world.block.TickableBlock;
import ru.windcorp.progressia.server.world.context.ServerBlockContext;
import ru.windcorp.progressia.server.world.context.ServerBlockContextRO;
import ru.windcorp.progressia.server.world.ticking.TickingPolicy;
public class TestBlockLogicStatieSpawner extends BlockLogic implements TickableBlock {
public TestBlockLogicStatieSpawner(String id) {
super(id);
}
@Override
public void tick(ServerBlockContext context) {
Vec3i loc = context.toAbsolute(context.getLocation(), null);
EntityData entity = new TestEntityDataStatie();
entity.setPosition(new Vec3(loc.x, loc.y, loc.z));
context.addEntity(entity);
context.setBlock(BlockDataRegistry.getInstance().get("Test:Air"));
}
@Override
public TickingPolicy getTickingPolicy(ServerBlockContextRO context) {
return TickingPolicy.RANDOM;
}
}

View File

@ -132,6 +132,10 @@ public class TestContent {
// Sic, using Glass logic for leaves because Test // Sic, using Glass logic for leaves because Test
register(new TestBlockLogicGlass("Test:TemporaryLeaves")); register(new TestBlockLogicGlass("Test:TemporaryLeaves"));
register(new BlockData("Test:StatieSpawner"));
register(new BlockRenderOpaqueCube("Test:StatieSpawner", getBlockTexture("StatieSpawner")));
register(new TestBlockLogicStatieSpawner("Test:StatieSpawner"));
BlockDataRegistry.getInstance().values().forEach(PLACEABLE_BLOCKS::add); BlockDataRegistry.getInstance().values().forEach(PLACEABLE_BLOCKS::add);
PLACEABLE_BLOCKS.removeIf(b -> placeableBlacklist.contains(b.getId())); PLACEABLE_BLOCKS.removeIf(b -> placeableBlacklist.contains(b.getId()));
PLACEABLE_BLOCKS.sort(Comparator.comparing(BlockData::getId)); PLACEABLE_BLOCKS.sort(Comparator.comparing(BlockData::getId));

View File

@ -18,26 +18,38 @@
package ru.windcorp.progressia.test; package ru.windcorp.progressia.test;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.model.Renderable; import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.Shapes; import ru.windcorp.progressia.client.graphics.model.Shapes.PppBuilder;
import ru.windcorp.progressia.client.graphics.texture.Texture; import ru.windcorp.progressia.client.graphics.texture.ComplexTexture;
import ru.windcorp.progressia.client.graphics.texture.TexturePrimitive;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.entity.EntityRender; import ru.windcorp.progressia.client.world.entity.EntityRender;
import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry;
import ru.windcorp.progressia.client.world.entity.EntityRenderable; import ru.windcorp.progressia.client.world.entity.EntityRenderable;
import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.entity.EntityData;
public class TestEntityRenderStatie extends EntityRender { public class TestEntityRenderStatie extends EntityRender {
private final Renderable cube = new Shapes.PppBuilder( private final static int PARTICLE_COUNT = 16;
WorldRenderProgram.getDefault(), private final Renderable core;
(Texture) null private final Renderable particle;
)
.setColorMultiplier(1, 1, 0)
.create();
public TestEntityRenderStatie(String id) { public TestEntityRenderStatie(String id) {
super(id); super(id);
TexturePrimitive texturePrimitive = EntityRenderRegistry.getEntityTexture("Statie");
ComplexTexture texture = new ComplexTexture(texturePrimitive, 4, 4);
WorldRenderProgram program = WorldRenderProgram.getDefault();
final float coreSize = 1f / 4;
final float particleSize = 1f / 16;
core = new PppBuilder(program, texture.getCuboidTextures(0, 2, 1)).setSize(coreSize).centerAt(0, 0, 0).create();
particle = new PppBuilder(program, texture.getCuboidTextures(0, 0, 1)).setSize(particleSize)
.centerAt(2.5f * coreSize, 0, 0).create();
} }
@Override @Override
@ -45,13 +57,29 @@ public class TestEntityRenderStatie extends EntityRender {
return new EntityRenderable(entity) { return new EntityRenderable(entity) {
@Override @Override
public void doRender(ShapeRenderHelper renderer) { public void doRender(ShapeRenderHelper renderer) {
double phase = GraphicsInterface.getTime();
renderer.pushTransform().translate(0, 0, (float) Math.sin(phase) * 0.1f);
renderer.pushTransform().scale( renderer.pushTransform().scale(
((TestEntityDataStatie) entity).getSize() / 24.0f ((TestEntityDataStatie) entity).getSize() / 24.0f
); ).rotateY((float) -Math.sin(phase - Math.PI / 3) * Math.PI / 12);
cube.render(renderer); core.render(renderer);
renderer.popTransform(); renderer.popTransform();
renderer.popTransform();
renderer.pushTransform().translate(0, 0, (float) Math.sin(phase + Math.PI / 2) * 0.05f);
for (int i = 0; i < PARTICLE_COUNT; ++i) {
double phaseOffset = 2 * Math.PI / PARTICLE_COUNT * i;
renderer.pushTransform()
.translate((float) Math.sin(phase + phaseOffset) * 0.1f, 0, 0)
.rotateX(Math.sin(phase / 2 + phaseOffset) * Math.PI / 6)
.rotateZ(phase + phaseOffset * 2);
particle.render(renderer);
renderer.popTransform();
}
} }
}; };
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 11 KiB