From 1d28f32865a809ce0b1ea6ff592975962ae006e3 Mon Sep 17 00:00:00 2001 From: OLEGSHA Date: Tue, 24 Aug 2021 13:59:28 +0300 Subject: [PATCH] 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 --- .../client/graphics/model/Shapes.java | 12 +++ .../server/comms/ClientManager.java | 2 +- .../progressia/server/comms/ClientPlayer.java | 18 ++++- .../server/management/load/ChunkManager.java | 9 +++ .../server/management/load/VisionManager.java | 25 ++++++ .../server/world/DefaultWorldLogic.java | 72 +++++++++++++---- .../progressia/server/world/WorldLogic.java | 3 + .../server/world/tasks/AddEntity.java | 75 ++++++++++++++++++ .../server/world/tasks/RemoveEntity.java | 75 ++++++++++++++++++ .../server/world/tasks/WorldAccessor.java | 13 ++- .../progressia/test/LayerTestGUI.java | 13 ++- .../test/TestBlockLogicStatieSpawner.java | 51 ++++++++++++ .../windcorp/progressia/test/TestContent.java | 4 + .../test/TestEntityRenderStatie.java | 50 +++++++++--- .../assets/textures/blocks/StatieSpawner.png | Bin 0 -> 1559 bytes .../assets/textures/entities/Statie.png | Bin 0 -> 1285 bytes .../resources/assets/textures/tiles/Fern.png | Bin 1968 -> 11312 bytes 17 files changed, 386 insertions(+), 36 deletions(-) create mode 100644 src/main/java/ru/windcorp/progressia/server/world/tasks/AddEntity.java create mode 100644 src/main/java/ru/windcorp/progressia/server/world/tasks/RemoveEntity.java create mode 100644 src/main/java/ru/windcorp/progressia/test/TestBlockLogicStatieSpawner.java create mode 100644 src/main/resources/assets/textures/blocks/StatieSpawner.png create mode 100644 src/main/resources/assets/textures/entities/Statie.png diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/model/Shapes.java b/src/main/java/ru/windcorp/progressia/client/graphics/model/Shapes.java index 92d9872..29d8629 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/model/Shapes.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/model/Shapes.java @@ -259,6 +259,18 @@ public class Shapes { public PppBuilder setSize(float 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() { this.flip = true; diff --git a/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java b/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java index 935e846..6b1595e 100644 --- a/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java +++ b/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java @@ -145,7 +145,7 @@ public class ClientManager { return; if (!(c instanceof ClientPlayer)) return; - if (!((ClientPlayer) c).isChunkVisible(entityId)) + if (!((ClientPlayer) c).isEntityVisible(entityId)) return; c.sendPacket(packet); }); diff --git a/src/main/java/ru/windcorp/progressia/server/comms/ClientPlayer.java b/src/main/java/ru/windcorp/progressia/server/comms/ClientPlayer.java index c05d952..7f4f26a 100644 --- a/src/main/java/ru/windcorp/progressia/server/comms/ClientPlayer.java +++ b/src/main/java/ru/windcorp/progressia/server/comms/ClientPlayer.java @@ -19,6 +19,9 @@ package ru.windcorp.progressia.server.comms; 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.ChunkSets; import ru.windcorp.progressia.server.Player; @@ -53,8 +56,19 @@ public abstract class ClientPlayer extends ClientChat { return player.getServer().getLoadManager().getVisionManager().getVisibleChunks(player); } - public boolean isChunkVisible(long entityId) { - return true; + public boolean isEntityVisible(long entityId) { + 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); } } diff --git a/src/main/java/ru/windcorp/progressia/server/management/load/ChunkManager.java b/src/main/java/ru/windcorp/progressia/server/management/load/ChunkManager.java index 3071530..9513aa2 100644 --- a/src/main/java/ru/windcorp/progressia/server/management/load/ChunkManager.java +++ b/src/main/java/ru/windcorp/progressia/server/management/load/ChunkManager.java @@ -17,10 +17,14 @@ */ package ru.windcorp.progressia.server.management.load; +import java.util.HashSet; +import java.util.Set; + import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.world.DefaultChunkData; import ru.windcorp.progressia.common.world.PacketRevokeChunk; 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.server.Player; import ru.windcorp.progressia.server.Server; @@ -140,6 +144,11 @@ public class ChunkManager { return false; } + // TODO allow entities to be saved first + Set entitiesToRemove = new HashSet<>(); + world.forEachEntityInChunk(chunkPos, entitiesToRemove::add); + entitiesToRemove.forEach(world::removeEntity); + world.removeChunk(chunk); TestWorldDiskIO.saveChunk(chunk, getServer()); diff --git a/src/main/java/ru/windcorp/progressia/server/management/load/VisionManager.java b/src/main/java/ru/windcorp/progressia/server/management/load/VisionManager.java index b4d2a93..b166fd0 100644 --- a/src/main/java/ru/windcorp/progressia/server/management/load/VisionManager.java +++ b/src/main/java/ru/windcorp/progressia/server/management/load/VisionManager.java @@ -25,6 +25,9 @@ import java.util.function.Consumer; import com.google.common.eventbus.Subscribe; 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.ChunkSets; import ru.windcorp.progressia.server.Player; @@ -85,6 +88,28 @@ public class VisionManager { 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 */ diff --git a/src/main/java/ru/windcorp/progressia/server/world/DefaultWorldLogic.java b/src/main/java/ru/windcorp/progressia/server/world/DefaultWorldLogic.java index 3e654b6..bbaa4d3 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/DefaultWorldLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/DefaultWorldLogic.java @@ -15,12 +15,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package ru.windcorp.progressia.server.world; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import glm.Glm; import glm.vec._3.i.Vec3i; @@ -50,7 +51,7 @@ public class DefaultWorldLogic implements WorldLogic { public DefaultWorldLogic(DefaultWorldData data, Server server, WorldGenerator generator, WorldAccessor accessor) { this.data = data; this.server = server; - + this.generator = generator; data.setGravityModel(getGenerator().getGravityModel()); @@ -83,12 +84,33 @@ public class DefaultWorldLogic implements WorldLogic { public Collection getEntities() { return getData().getEntities(); } - + @Override public EntityData getEntity(long 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() { return tickEntitiesTask; } @@ -108,36 +130,54 @@ public class DefaultWorldLogic implements WorldLogic { public DefaultChunkData generate(Vec3i chunkPos) { DefaultChunkData chunk = getGenerator().generate(chunkPos); - + 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(), - chunk.getX(), chunk.getY(), chunk.getZ(), - chunkPos.x, chunkPos.y, chunkPos.z + chunk.getX(), + chunk.getY(), + chunk.getZ(), + chunkPos.x, + chunkPos.y, + chunkPos.z ); } - + if (getData().getChunk(chunk.getPosition()) != chunk) { 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(), - chunkPos.x, chunkPos.y, chunkPos.z + chunkPos.x, + chunkPos.y, + chunkPos.z ); } 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(), - chunkPos.x, chunkPos.y, chunkPos.z + chunkPos.x, + chunkPos.y, + chunkPos.z ); } } - + 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(), - chunkPos.x, chunkPos.y, chunkPos.z + chunkPos.x, + chunkPos.y, + chunkPos.z ); } - + return chunk; } diff --git a/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java b/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java index f0cdb0a..4e94afe 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java @@ -21,9 +21,12 @@ import java.util.Collection; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.world.WorldData; +import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.rels.BlockFace; public interface WorldLogic extends WorldLogicRO { + + void spawnEntity(EntityData entity); /* * Override return types diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/AddEntity.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/AddEntity.java new file mode 100644 index 0000000..7bbdaa6 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/AddEntity.java @@ -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 . + */ + +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 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; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/RemoveEntity.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/RemoveEntity.java new file mode 100644 index 0000000..11fd898 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/RemoveEntity.java @@ -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 . + */ + +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 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; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/WorldAccessor.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/WorldAccessor.java index 76ab835..0a639c8 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tasks/WorldAccessor.java +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/WorldAccessor.java @@ -46,6 +46,9 @@ public class WorldAccessor implements ReportingServerContext.ChangeListener { .addClass(SetBlock.class, () -> new SetBlock(disposer)) .addClass(AddTile.class, () -> new AddTile(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(BlockTriggeredUpdate.class, () -> new BlockTriggeredUpdate(disposer)) @@ -81,14 +84,16 @@ public class WorldAccessor implements ReportingServerContext.ChangeListener { @Override public void onEntityAdded(EntityData entity) { - // TODO Auto-generated method stub - + AddEntity change = cache.grab(AddEntity.class); + change.set(entity); + server.requestChange(change); } @Override public void onEntityRemoved(long entityId) { - // TODO Auto-generated method stub - + RemoveEntity change = cache.grab(RemoveEntity.class); + change.set(entityId); + server.requestChange(change); } @Override diff --git a/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java b/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java index 65913d2..0c082fa 100755 --- a/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java +++ b/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package ru.windcorp.progressia.test; import glm.vec._3.Vec3; @@ -129,7 +129,7 @@ public class LayerTestGUI extends GUILayer { 128 ) ); - + group.addChild( new DynamicLabel( "ChunkStatsDisplay", @@ -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 TPS_RECORD = new Averager(); private static final Supplier TPS_STRING = DynamicStrings.builder() .addDyn(new MutableStringLocalized("LayerTestGUI.TPSDisplay")) .addDyn(() -> TPS_RECORD.update(ServerState.getInstance().getTPS()), 5, 1) + .add(' ') + .addDyn(LayerTestGUI::getTPSClockChar) .buildSupplier(); private static final Supplier POS_STRING = DynamicStrings.builder() diff --git a/src/main/java/ru/windcorp/progressia/test/TestBlockLogicStatieSpawner.java b/src/main/java/ru/windcorp/progressia/test/TestBlockLogicStatieSpawner.java new file mode 100644 index 0000000..99335ae --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/TestBlockLogicStatieSpawner.java @@ -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 . + */ +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; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/TestContent.java b/src/main/java/ru/windcorp/progressia/test/TestContent.java index d927613..9ad6518 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestContent.java +++ b/src/main/java/ru/windcorp/progressia/test/TestContent.java @@ -131,6 +131,10 @@ public class TestContent { register(new BlockRenderTransparentCube("Test:TemporaryLeaves", getBlockTexture("TemporaryLeaves"))); // Sic, using Glass logic for leaves because Test 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); PLACEABLE_BLOCKS.removeIf(b -> placeableBlacklist.contains(b.getId())); diff --git a/src/main/java/ru/windcorp/progressia/test/TestEntityRenderStatie.java b/src/main/java/ru/windcorp/progressia/test/TestEntityRenderStatie.java index fa595e7..89f7ca9 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestEntityRenderStatie.java +++ b/src/main/java/ru/windcorp/progressia/test/TestEntityRenderStatie.java @@ -15,29 +15,41 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + 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.ShapeRenderHelper; -import ru.windcorp.progressia.client.graphics.model.Shapes; -import ru.windcorp.progressia.client.graphics.texture.Texture; +import ru.windcorp.progressia.client.graphics.model.Shapes.PppBuilder; +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.world.entity.EntityRender; +import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry; import ru.windcorp.progressia.client.world.entity.EntityRenderable; import ru.windcorp.progressia.common.world.entity.EntityData; public class TestEntityRenderStatie extends EntityRender { - private final Renderable cube = new Shapes.PppBuilder( - WorldRenderProgram.getDefault(), - (Texture) null - ) - .setColorMultiplier(1, 1, 0) - .create(); + private final static int PARTICLE_COUNT = 16; + private final Renderable core; + private final Renderable particle; public TestEntityRenderStatie(String 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 @@ -45,13 +57,29 @@ public class TestEntityRenderStatie extends EntityRender { return new EntityRenderable(entity) { @Override public void doRender(ShapeRenderHelper renderer) { + double phase = GraphicsInterface.getTime(); + renderer.pushTransform().translate(0, 0, (float) Math.sin(phase) * 0.1f); + renderer.pushTransform().scale( ((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.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(); + } + } }; } diff --git a/src/main/resources/assets/textures/blocks/StatieSpawner.png b/src/main/resources/assets/textures/blocks/StatieSpawner.png new file mode 100644 index 0000000000000000000000000000000000000000..815e0aba96cd99341a70f2f937b41667ab57f5be GIT binary patch literal 1559 zcmV+y2I%>TP)xE8yW1*Cc*#!02y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00n|k5|_`9AKw=du0a-Wfco1c3U$-nk(f1GXqZm6Rf+1jZo7 z0E&hPVi0T4n21e<7>jWkOLxbJwWRR~Df>8$5i!F2ut1{&O_(erg#ZGYA}DCnfH4^B z01zYXZVj&Jk)kJ76ZQ;pjLTRC(s+csS3DxdAkJ}oaXTS+nz|v&GRm?<6+nqG5@Q6w z7=tkeYb^lJz4I~cQs##x%Yf4m+M5F>fAa`2Mtsu{V`MlSBF3O8QP(xb7(xh$w6{+w z(d+f7s*18KIsLnNVvM|a_H+DXg!+ivGb~8KG$opB zIIt7vEC`^`0pRevpP-s}W#3i=s2fke-y_d+{_xH+;xZf}h_U#(AukGy2q`5%IrYRm z&N)&_c<*`W^^078W z)*9-%X3Oc1aL%DIu)6j=B0|$Nj7B5YzF%WHo$%86FUax?9|B1enj)GK#<+|W1J+tp zl@L6jWSJwzgte9%haY1)nV@w=wKifj9<%Kq%Vb%G7(-E%#OV|x!pzJ}+Xe5jd4WON z)*2D4wHRYL`P<#Z*tX@DuV199D%K_=B+L2f=FRMW_X=OX@HqW`pB?}H6k`m>5AGtV zB1mUQ0Kw>wTq+`%u2mrfoOAr|nf*Nc<`SFcHgWUCgJfC8wPzpY>zg+TG4jpxi^Leo z^PF$Ly@hqQD9AIs2#Dixp@QXK=u-3A0;W}H6 zy~Ts4&aw5)Wnzr{>FP@75ovb;2cWG@JN1Bwu;cINk+N)ImL=Dpn(t()IOn+X6qS@J5cD;RxnVBIeC4TGgYr@d#@zf^z519j-mS zkEZd|b-gh~7nW|3=Q(9jw2CWx)OC$-8jLkRLXg_go98*JtE&L~?TLBT)>)sJFzB*5aq_^6N{Qich_RNLeh+6|TPFlQo_HL_lDx;{8Am_5 zil)S$_iZJ{F=bh{aVQW&%T@I4Sh#)rHZwCbomVVbmT~0$PumcR0aFyDl~rW(v41H> z*gH3D!`^#Pz4xl<@^^h!DT>C_A`xjNDFUjfCQ|T-&G^r)|6__?>tqy`?u-Cs{{#0! zQc?({;IUbTSVycYOqPKN##%On4($s>+o#O(-HKGLV9Eg)_yM@A^p+9fiT(fp002ov JPDHLkV1mp4-#7pO literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/textures/entities/Statie.png b/src/main/resources/assets/textures/entities/Statie.png new file mode 100644 index 0000000000000000000000000000000000000000..ed8a351313dc1d07de7fd446fdeeb476eba179b1 GIT binary patch literal 1285 zcmV+g1^W7lP)xE7CQR}Dg6Kd02y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00eDGL_t(Y$CZ^$j}=7}hM!X( z-F@fYI}9R(#084tMobVxN%|p5hk#3>;Evswb2#9koXlf{sRaM$RwbmD+5Mx zWbW<0-CZBYMPCMnIHG4$l{!`LTXoKRP6@l%+3R!lMp!KjeNy_Ygajie)G=d4c&svn ztkgky``NMl6OIE=RYV5=y?JScH=i0&*!$-`-k4*J;jO1vsGN{g2@2X83EL=T4tVAf~8{bj*wDP)C3P?9QLQog#` zF!crmZq5W>usQlSx4vsCTB1ZUQkvK52td@gP7D0JpYPnq}+Mv`)**~(> zA+x76^l5leUzGgjo?*y} z4(=t{8^IaaYzN=&`g-@A`ccKoXoa#WnayS_?%qYp5}nN`_w6Hg0X2|ggb&}5N7liP z3M@Rwj~^eZJ44Em_4ReEwLJIgt9a)TBg7QBvVMj5s7ij=#*pW?hYtf#l_hQ45|a{B#9B+}dy1m?Z>{1% z>$?S-m7~ue9g@6Q&@>Hg+v2P&k19n`(Efhs{}Uj>rAwC?PgZE#77;k5r=(d-m+%k2`lrZO3%)8covxLP`;G231^H;=Mz& z(uaVt0t(Lg?Y19d#La%Y%`30HzJvAB#fzkrc;TfJ0|GaOB7l43>n&7(Yqi<8alW1252Z9hw>tc>d^7 zvVt!>sq3lR4uuDZpk6Ex_ul6kH#Zp{JV@X7h$i|)hp#G%s-i4QQc5h^mKegI{kq1C vDy;KpN_g*CG%YkURvte<2m#fM^`5^0W)P2U3Oq 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 mN@%p!goIq9M%{*#nc#owhf|2hN$@WK0000bBVc delta 1893 zcmV-r2b%b>Sg;R}BYy#eX+uL$Nkc;*aB^>EX>4Tx04R}tkv&MmKpe$i(~3nZ4i*$~ z2w0sgh>AE$6^me@v=v%)FuC*#nlvOSE{=k0!NHHks)LKOt`4q(Aou~|=H{g6A|?JW zDYS_3;J6>}?mh0_0Yam~R5LIEsG4P@;xRFsTNS%s5ySvO2!CKiVy2!*FJ|C5zV6}U z>s^#*d7t}p^eTCi0X~st?f%Ws^E4huXp zY-CdN#9?Bw*uioKvy!0_PZLKJRik_%>$1Xmi?dp(vDQ8L3qyHrCCzo3!-!)E2_zvx zMh#_DU?EDYMt_Ql6z#`6{6mgENiLaOWiWCqpb8a|;|KqP-`$#psYy2}6a%_mZ2Myr z=-vgIb=&?vw(aH#;C}|Lw6?$60A@c)ueY_x5zw~{TwJ#`c@MbU0S2FR$&eh$Pg5uq zfcG={rX0|J3-qkHy|wmn`T%68tK}Qu;1C!qQueyXyCZ|0z5RQp)!z?U!*ZS2rrs9- z000JJOGiWi{{a60|De66larMtAP5%^9u7}NQInIsB`1Fch)G02R7l57m1}QR#}$U3 zwPt4T<713vC~70MKdT>_Qj!$XltONe(g=zm#3r>%A`}RPB#jM}OF*>$scoVrzJPDX z=j^>_*6N3C+~;y8j}W~c~lcB-UwW+YVr+yQaK4I}_iN}-g3 z?w^Si35r2wF+6f>%%m>J}LAE4w0}*31hnH8 zQ6))4gHud!cMw5T$T|P7rD9Z7g%`)o`g>Bs6>@)e#-lw{RB6Wz#T{2gqcRzfF)xG= zPzAhKXvGLXX$v5)e01w;kM`)UNl3xnYhv0Ok6cSUu-?|`ZRLen%be3G-uvv7 zH%&$`u79v1*$ZO1!$o=b+Uq{qo3OR9!{MWc7z_qne}7qCC>9a^e_hP+>_~ujuATDI zW}dBXGJE6@qn!!&ul_@F&Zr0B&Ba%J^`C!hOtu^9s$yq-#1H>oYrpq0;A;Ecb={2{eu)8&8W_HM}Ki(5np;+1fR{}9bBq^a%R{r!z28U`=O00dm zhI^ry(RD`WPD%;cH`u$Mp7g4U?Cy-H>%bS69*Ks?^vsZ;ftVC=@6meW;>&*f@Y(Cp2)rsZ#Z$wpTnufZrDV_-u zQGl-Nxc$)s@#3f|DI{)PxG&Q)Q&e?Ag7DVTuiXI^&=4?jp093E4-$8-JdvC;4y2U& z=#bbGW@f~c5EnEEntEK@-16mz4`qKiJ7lyu?y;2K8!<-Qpqff7FFX6izQ3_YeXh1U^4^SI#cJ;u->5>)YI2dMW^K zU46~lafgaf4J($;{6(}<;_3P09z$Yxb41s5)H4IDbDHslvrDgd?lL#d-;;mWFTCt? zN2Ytz6=QpCmwTW7U4A?NOP3f3t~^_PPBp9<(T?srJzYEUO4vB5&rY9HMZAxsi$jh zp1&(0MQ(lcH)$sgqT0s~0yod!dtuaru={%cvD7n(;b4jw184vIs+VGTHs+4a;5dio zhRhzBVR`MX7RWSj);<~#PvU{$bs3iBWJFhaEXCjI<{7K!JX;ZDVkBk;iEGY9IR)$ z9#oXBA2x>JCe$g=HXVN(8(SPbHj8NB^K&awIwPilnitl-U1#)sk9t^9&(z$$cwbVT z*nT!*XKjbg@3#&HP%edBjPcHd(bf)|8{6Exa8K&G#)>g|K0**e>}Q9r|FA`>BANm# zS02k(7avFnfxj+1mP3b!q&hJ*H{jXYHqE3#L|FT7mBFxLW$`JgpwKiODJr)wK9D#F fm}lCy#m)IIfm|~gO^TW*00000NkvXXu0mjf-IIe0