diff --git a/build-logic/src/main/kotlin/terasology-module.gradle.kts b/build-logic/src/main/kotlin/terasology-module.gradle.kts index 1ab75596202..360856359d1 100644 --- a/build-logic/src/main/kotlin/terasology-module.gradle.kts +++ b/build-logic/src/main/kotlin/terasology-module.gradle.kts @@ -37,7 +37,14 @@ configure { configurations { all { - resolutionStrategy.preferProjectModules() + resolutionStrategy { + preferProjectModules() + // always pick reflections fork + dependencySubstitution { + @Suppress("UnstableApiUsage") + substitute(module("org.reflections:reflections")).using(module("org.terasology:reflections:0.9.12-MB")) + } + } } } diff --git a/config/groovy/common.groovy b/config/groovy/common.groovy index 61167b19ab3..b260add02a6 100644 --- a/config/groovy/common.groovy +++ b/config/groovy/common.groovy @@ -124,19 +124,20 @@ class common { } else { itemsRetrieved << itemName def targetUrl = "https://github.com/${githubTargetHome}/${itemName}" - if (!isUrlValid(targetUrl)) { - println "Can't retrieve $itemType from $targetUrl - URL appears invalid. Typo? Not created yet?" + try { + println "Retrieving $itemType $itemName from $targetUrl" + if (githubTargetHome != githubDefaultHome) { + println "Doing a retrieve from a custom remote: $githubTargetHome - will name it as such plus add the $githubDefaultHome remote as '$defaultRemote'" + Grgit.clone dir: targetDir, uri: targetUrl, remote: githubTargetHome + println "Primary clone operation complete, about to add the '$defaultRemote' remote for the $githubDefaultHome org address" + addRemote(itemName, defaultRemote, "https://github.com/${githubDefaultHome}/${itemName}") + } else { + Grgit.clone dir: targetDir, uri: targetUrl + } + } catch (GrgitException exception) { + println color("Unable to clone $itemName, Skipping: ${exception.getMessage()}", Ansi.RED) return } - println "Retrieving $itemType $itemName from $targetUrl" - if (githubTargetHome != githubDefaultHome) { - println "Doing a retrieve from a custom remote: $githubTargetHome - will name it as such plus add the $githubDefaultHome remote as '$defaultRemote'" - Grgit.clone dir: targetDir, uri: targetUrl, remote: githubTargetHome - println "Primary clone operation complete, about to add the '$defaultRemote' remote for the $githubDefaultHome org address" - addRemote(itemName, defaultRemote, "https://github.com/${githubDefaultHome}/${itemName}") - } else { - Grgit.clone dir: targetDir, uri: targetUrl - } // This step allows the item type to check the newly cloned item and add in extra template stuff itemTypeScript.copyInTemplateFiles(targetDir) @@ -222,8 +223,8 @@ class common { def targetUrl = remotes.find { it.name == defaultRemote }?.url - if (targetUrl == null || !isUrlValid(targetUrl)) { - println color("While updating $itemName found its '$defaultRemote' remote invalid or its URL unresponsive: $targetUrl", Ansi.RED) + if (targetUrl == null) { + println color("While updating $itemName remote `$defaultRemote` is not found.", Ansi.RED) return } diff --git a/engine-tests/src/main/java/org/terasology/engine/Environment.java b/engine-tests/src/main/java/org/terasology/engine/Environment.java index e67e29ba8cc..56f0f27a702 100644 --- a/engine-tests/src/main/java/org/terasology/engine/Environment.java +++ b/engine-tests/src/main/java/org/terasology/engine/Environment.java @@ -19,10 +19,11 @@ import java.util.Set; /** - * Setup an empty Terasology environment - * + * Set up an empty Terasology environment. + *

+ * Not for use outside {@code engine-tests}. Modules should use ModuleTestingEnvironment. */ -public class Environment { +class Environment { private static final Logger logger = LoggerFactory.getLogger(Environment.class); diff --git a/engine-tests/src/main/java/org/terasology/engine/HeadlessEnvironment.java b/engine-tests/src/main/java/org/terasology/engine/HeadlessEnvironment.java index c4587750076..75ebf4c7c81 100644 --- a/engine-tests/src/main/java/org/terasology/engine/HeadlessEnvironment.java +++ b/engine-tests/src/main/java/org/terasology/engine/HeadlessEnvironment.java @@ -101,7 +101,8 @@ /** * Setup a headless ( = no graphics ) environment. * Based on TerasologyTestingEnvironment code. - * + *

+ * Deprecated for use outside of {@code engine-tests}; modules should use ModuleTestingEnvironment. */ public class HeadlessEnvironment extends Environment { diff --git a/engine-tests/src/main/java/org/terasology/engine/WorldProvidingHeadlessEnvironment.java b/engine-tests/src/main/java/org/terasology/engine/WorldProvidingHeadlessEnvironment.java index 7900840150e..58fa323f739 100644 --- a/engine-tests/src/main/java/org/terasology/engine/WorldProvidingHeadlessEnvironment.java +++ b/engine-tests/src/main/java/org/terasology/engine/WorldProvidingHeadlessEnvironment.java @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 package org.terasology.engine; -import org.terasology.gestalt.naming.Name; import org.terasology.engine.registry.CoreRegistry; import org.terasology.engine.world.BlockEntityRegistry; import org.terasology.engine.world.WorldProvider; @@ -12,10 +11,14 @@ import org.terasology.engine.world.internal.EntityAwareWorldProvider; import org.terasology.engine.world.internal.WorldProviderCore; import org.terasology.engine.world.internal.WorldProviderWrapper; +import org.terasology.gestalt.naming.Name; /** * Environment with a MapWorldProvider and BlockManager. Useful to get headless environment with a generated world. + * + * @deprecated Use ModuleTestingEnvironment. */ +@Deprecated public class WorldProvidingHeadlessEnvironment extends HeadlessEnvironment { public WorldProvidingHeadlessEnvironment(Name... modules) { diff --git a/engine-tests/src/main/resources/org/terasology/unittest/module.txt b/engine-tests/src/main/resources/org/terasology/unittest/module.txt index 3b44ef12ea5..4a32511bcd9 100644 --- a/engine-tests/src/main/resources/org/terasology/unittest/module.txt +++ b/engine-tests/src/main/resources/org/terasology/unittest/module.txt @@ -1,6 +1,6 @@ { "id" : "unittest", - "version" : "5.0.0", + "version" : "5.1.0", "displayName" : "Terasology Engine Test", "description" : "Engine unit test content" } diff --git a/engine-tests/src/main/java/org/terasology/engine/TerasologyTestingEnvironment.java b/engine-tests/src/test/java/org/terasology/engine/TerasologyTestingEnvironment.java similarity index 100% rename from engine-tests/src/main/java/org/terasology/engine/TerasologyTestingEnvironment.java rename to engine-tests/src/test/java/org/terasology/engine/TerasologyTestingEnvironment.java diff --git a/engine-tests/src/test/java/org/terasology/engine/core/module/ClasspathCompromisingModuleFactoryTest.java b/engine-tests/src/test/java/org/terasology/engine/core/module/ClasspathCompromisingModuleFactoryTest.java index 041f8646600..b9ee465aa1e 100644 --- a/engine-tests/src/test/java/org/terasology/engine/core/module/ClasspathCompromisingModuleFactoryTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/core/module/ClasspathCompromisingModuleFactoryTest.java @@ -3,10 +3,10 @@ package org.terasology.engine.core.module; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.terasology.gestalt.module.Module; -import org.terasology.gestalt.module.ModuleFactory; import org.terasology.gestalt.module.ModuleMetadata; import org.terasology.gestalt.module.sandbox.API; import org.terasology.gestalt.naming.Name; @@ -15,17 +15,29 @@ import java.io.File; import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import static com.google.common.truth.Truth8.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; public class ClasspathCompromisingModuleFactoryTest { static final Class someClassOutsideTheModule = ClasspathCompromisingModuleFactory.class; + static final String METADATA_NAME = "module.json"; + + ClasspathCompromisingModuleFactory factory; + + @BeforeEach + public void newFactory() { + factory = new ClasspathCompromisingModuleFactory(); + factory.setDefaultLibsSubpath("build/libs"); + } @Test public void directoryModuleContainsClass() { - ModuleFactory factory = new ClasspathCompromisingModuleFactory(); - // This test assumes that the unittest module is under the current working directory (`engine-test/`) File engineTestDirectory = new File(System.getProperty("user.dir", ".")); ModuleMetadata metadata = new ModuleMetadata(new Name("unittest"), new Version("1.0.0")); @@ -42,8 +54,6 @@ public void directoryModuleContainsClass() { @Test @Disabled("TODO: need a jar module containing classes") public void archiveModuleContainsClass() throws IOException { - ModuleFactory factory = new ClasspathCompromisingModuleFactory(); - Module module = factory.createArchiveModule(new File("FIXME.jar")); Class someClassInTheModule = module.getModuleManifest().getTypesAnnotatedWith(API.class).iterator().next(); @@ -60,4 +70,31 @@ public void directoryModuleContainsClassLoadedFromJar() { // - m/build/libs/foo.jar // load m as directory module while foo.jar is on classpath } + + @Test + public void canGetPathFromJarResource() throws MalformedURLException { + // A jar file on the classpath but not in a local build directory. + URL jarUrl = new URL("jar:file:/example/Terasology/cachedModules/CoreAssets-2.3.0-SNAPSHOT.jar!/module.json"); + Path expectedPath = Paths.get("/example/Terasology/cachedModules/CoreAssets-2.3.0-SNAPSHOT.jar"); + + assertThat(factory.canonicalModuleLocation(METADATA_NAME, jarUrl)).isEqualTo(expectedPath); + } + + @Test + public void canGetPathFromLocalJarBuild() throws MalformedURLException { + // A jar file on the classpath that is a build directory in a local development workspace + URL jarUrl = new URL("jar:file:/example/Terasology/modules/CoreAssets/build/libs/CoreAssets-2.3.0-SNAPSHOT.jar!/module.json"); + Path expectedPath = Paths.get("/example/Terasology/modules/CoreAssets"); + + assertThat(factory.canonicalModuleLocation(METADATA_NAME, jarUrl)).isEqualTo(expectedPath); + } + + @Test + public void canGetPathFromFilesystemResource() throws MalformedURLException { + // A directory on the classpath that is a build directory in a local development workspace + URL fileUrl = new URL("file:/example/Terasology/modules/Health/build/classes/module.json"); + Path expectedPath = Paths.get("/example/Terasology/modules/Health"); + + assertThat(factory.canonicalModuleLocation(METADATA_NAME, fileUrl)).isEqualTo(expectedPath); + } } diff --git a/engine/build.gradle b/engine/build.gradle index 5995bdb8899..d3466ebc82b 100644 --- a/engine/build.gradle +++ b/engine/build.gradle @@ -51,14 +51,24 @@ configurations { } } +configurations.all { + resolutionStrategy { + // always pick reflections fork + dependencySubstitution { + substitute(module("org.reflections:reflections")).using(module("org.terasology:reflections:0.9.12-MB")) + } + } +} + // Primary dependencies definition dependencies { // Storage and networking api group: 'com.google.guava', name: 'guava', version: '30.1-jre' api group: 'com.google.code.gson', name: 'gson', version: '2.8.6' api group: 'net.sf.trove4j', name: 'trove4j', version: '3.0.3' - implementation group: 'io.netty', name: 'netty-all', version: '4.1.53.Final' + implementation group: 'io.netty', name: 'netty-all', version: '4.1.65.Final' implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '2.6.1' + implementation group: 'org.lz4', name: 'lz4-java', version: '1.8.0' implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.2' // Javax for protobuf due to @Generated - needed on Java 9 or newer Javas // TODO: Can likely replace with protobuf Gradle task and omit the generated source files instead diff --git a/engine/src/main/java/org/terasology/engine/core/TerasologyEngine.java b/engine/src/main/java/org/terasology/engine/core/TerasologyEngine.java index ffeeaf41560..bb7c5d36cf6 100644 --- a/engine/src/main/java/org/terasology/engine/core/TerasologyEngine.java +++ b/engine/src/main/java/org/terasology/engine/core/TerasologyEngine.java @@ -29,7 +29,6 @@ import org.terasology.engine.core.subsystem.common.ThreadManagerSubsystem; import org.terasology.engine.core.subsystem.common.TimeSubsystem; import org.terasology.engine.core.subsystem.common.WorldGenerationSubsystem; -import org.terasology.engine.core.subsystem.rendering.ModuleRenderingSubsystem; import org.terasology.engine.entitySystem.prefab.Prefab; import org.terasology.engine.entitySystem.prefab.internal.PojoPrefab; import org.terasology.engine.i18n.I18nSubsystem; @@ -175,7 +174,6 @@ public TerasologyEngine(TimeSubsystem timeSubsystem, Collection this.allSubsystems.add(new GameSubsystem()); this.allSubsystems.add(new I18nSubsystem()); this.allSubsystems.add(new TelemetrySubSystem()); - this.allSubsystems.add(new ModuleRenderingSubsystem()); // add all subsystem as engine module part. (needs for ECS classes loaded from external subsystems) allSubsystems.stream().map(Object::getClass).forEach(this::addToClassesOnClasspathsToAddToEngine); @@ -489,7 +487,6 @@ public boolean tick() { } Iterator updateCycles = timeSubsystem.getEngineTime().tick(); - CoreRegistry.setContext(currentState.getContext()); rootContext.get(NetworkSystem.class).setContext(currentState.getContext()); for (EngineSubsystem subsystem : allSubsystems) { @@ -586,6 +583,7 @@ private void switchState(GameState newState) { if (currentState != null) { currentState.dispose(); } + CoreRegistry.setContext(newState.getContext()); currentState = newState; LoggingContext.setGameState(newState); newState.init(this); diff --git a/engine/src/main/java/org/terasology/engine/core/bootstrap/EnvironmentSwitchHandler.java b/engine/src/main/java/org/terasology/engine/core/bootstrap/EnvironmentSwitchHandler.java index d24027b9322..152a3ab6862 100644 --- a/engine/src/main/java/org/terasology/engine/core/bootstrap/EnvironmentSwitchHandler.java +++ b/engine/src/main/java/org/terasology/engine/core/bootstrap/EnvironmentSwitchHandler.java @@ -107,7 +107,6 @@ public void handleSwitchToGameEnvironment(Context context) { autoConfigManager.loadConfigsIn(context); ModuleAwareAssetTypeManager assetTypeManager = context.get(ModuleAwareAssetTypeManager.class); - /* * The registering of the prefab formats is done in this method, because it needs to be done before * the environment of the asset manager gets changed. @@ -116,6 +115,7 @@ public void handleSwitchToGameEnvironment(Context context) { * existing then yet. */ unregisterPrefabFormats(assetTypeManager); + registeredPrefabFormat = new PrefabFormat(componentLibrary, typeHandlerLibrary); assetTypeManager.getAssetFileDataProducer(assetTypeManager .getAssetType(Prefab.class) diff --git a/engine/src/main/java/org/terasology/engine/core/modes/AbstractState.java b/engine/src/main/java/org/terasology/engine/core/modes/AbstractState.java new file mode 100644 index 00000000000..11f03d46254 --- /dev/null +++ b/engine/src/main/java/org/terasology/engine/core/modes/AbstractState.java @@ -0,0 +1,84 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.engine.core.modes; + +import org.terasology.engine.context.Context; +import org.terasology.engine.core.ComponentSystemManager; +import org.terasology.engine.core.bootstrap.EntitySystemSetupUtil; +import org.terasology.engine.entitySystem.entity.EntityRef; +import org.terasology.engine.entitySystem.entity.internal.EngineEntityManager; +import org.terasology.engine.entitySystem.event.internal.EventSystem; +import org.terasology.engine.logic.console.Console; +import org.terasology.engine.logic.console.ConsoleImpl; +import org.terasology.engine.logic.console.ConsoleSystem; +import org.terasology.engine.logic.console.commands.CoreCommands; +import org.terasology.engine.logic.players.LocalPlayer; +import org.terasology.engine.network.ClientComponent; +import org.terasology.engine.recording.DirectionAndOriginPosRecorderList; +import org.terasology.engine.recording.RecordAndReplayCurrentStatus; +import org.terasology.engine.registry.CoreRegistry; +import org.terasology.engine.rendering.nui.NUIManager; +import org.terasology.engine.rendering.nui.internal.NUIManagerInternal; +import org.terasology.engine.rendering.nui.internal.TerasologyCanvasRenderer; +import org.terasology.nui.canvas.CanvasRenderer; + +import static com.google.common.base.Verify.verifyNotNull; + +public abstract class AbstractState implements GameState { + protected Context context; + protected EngineEntityManager entityManager; + protected EventSystem eventSystem; + protected ComponentSystemManager componentSystemManager; + + protected void initEntityAndComponentManagers() { + verifyNotNull(context); + CoreRegistry.setContext(context); + + // let's get the entity event system running + EntitySystemSetupUtil.addEntityManagementRelatedClasses(context); + entityManager = context.get(EngineEntityManager.class); + + eventSystem = context.get(EventSystem.class); + context.put(Console.class, new ConsoleImpl(context)); + + NUIManager nuiManager = new NUIManagerInternal((TerasologyCanvasRenderer) context.get(CanvasRenderer.class), context); + context.put(NUIManager.class, nuiManager); + + componentSystemManager = new ComponentSystemManager(context); + context.put(ComponentSystemManager.class, componentSystemManager); + + componentSystemManager.register(new ConsoleSystem(), "engine:ConsoleSystem"); + componentSystemManager.register(new CoreCommands(), "engine:CoreCommands"); + } + + protected static void createLocalPlayer(Context context) { + EngineEntityManager entityManager = context.get(EngineEntityManager.class); + EntityRef localPlayerEntity = entityManager.create(new ClientComponent()); + LocalPlayer localPlayer = new LocalPlayer(); + localPlayer.setRecordAndReplayClasses(context.get(DirectionAndOriginPosRecorderList.class), + context.get(RecordAndReplayCurrentStatus.class)); + context.put(LocalPlayer.class, localPlayer); + localPlayer.setClientEntity(localPlayerEntity); + } + + @Override + public void dispose(boolean shuttingDown) { + // Apparently this can be disposed of before it is completely initialized? Probably only during + // crashes, but crashing again during shutdown complicates the diagnosis. + if (eventSystem != null) { + eventSystem.process(); + } + if (componentSystemManager != null) { + componentSystemManager.shutdown(); + } + if (entityManager != null) { + entityManager.clear(); + } + } + + @Override + public Context getContext() { + return context; + } +} diff --git a/engine/src/main/java/org/terasology/engine/core/modes/StateIngame.java b/engine/src/main/java/org/terasology/engine/core/modes/StateIngame.java index 5ec94ad180b..0c3bf91a4a2 100644 --- a/engine/src/main/java/org/terasology/engine/core/modes/StateIngame.java +++ b/engine/src/main/java/org/terasology/engine/core/modes/StateIngame.java @@ -8,11 +8,13 @@ import org.terasology.engine.core.ComponentSystemManager; import org.terasology.engine.core.GameEngine; import org.terasology.engine.core.GameThread; +import org.terasology.engine.core.TerasologyConstants; import org.terasology.engine.core.bootstrap.EnvironmentSwitchHandler; import org.terasology.engine.core.module.ModuleManager; import org.terasology.engine.core.subsystem.DisplayDevice; import org.terasology.engine.entitySystem.entity.internal.EngineEntityManager; import org.terasology.engine.entitySystem.event.internal.EventSystem; +import org.terasology.engine.entitySystem.prefab.Prefab; import org.terasology.engine.entitySystem.systems.UpdateSubscriberSystem; import org.terasology.engine.game.GameManifest; import org.terasology.engine.identity.storageServiceClient.StorageServiceWorker; @@ -28,7 +30,13 @@ import org.terasology.engine.rendering.nui.layers.mainMenu.MessagePopup; import org.terasology.engine.rendering.world.WorldRenderer; import org.terasology.engine.rendering.world.WorldRenderer.RenderingStage; +import org.terasology.engine.world.block.loader.BlockFamilyDefinition; import org.terasology.engine.world.chunks.ChunkProvider; +import org.terasology.gestalt.assets.Asset; +import org.terasology.gestalt.assets.AssetType; +import org.terasology.gestalt.assets.ResourceUrn; +import org.terasology.gestalt.assets.management.AssetTypeManager; +import org.terasology.gestalt.assets.module.ModuleAwareAssetTypeManager; import org.terasology.gestalt.module.Module; import org.terasology.gestalt.module.ModuleEnvironment; import org.terasology.nui.databinding.ReadOnlyBinding; @@ -103,6 +111,19 @@ public void dispose(boolean shuttingDown) { ChunkProvider chunkProvider = context.get(ChunkProvider.class); chunkProvider.dispose(); + AssetTypeManager assetTypeManager = context.get(ModuleAwareAssetTypeManager.class); + // dispose all module assets + assetTypeManager.getAssetTypes().forEach(assetType -> { + for (ResourceUrn urn : assetType.getLoadedAssetUrns()) { + if (!urn.getModuleName().equals(TerasologyConstants.ENGINE_MODULE)) { + assetType.getAsset(urn).ifPresent(Asset::dispose); + } + } + }); + // dispose engine assets that should not be kept when switching game states + assetTypeManager.getAssetType(BlockFamilyDefinition.class).ifPresent(AssetType::disposeAll); + assetTypeManager.getAssetType(Prefab.class).ifPresent(AssetType::disposeAll); + boolean save = networkSystem.getMode().isAuthority(); if (save) { storageManager.waitForCompletionOfPreviousSaveAndStartSaving(); diff --git a/engine/src/main/java/org/terasology/engine/core/modes/StateLoading.java b/engine/src/main/java/org/terasology/engine/core/modes/StateLoading.java index f6ea5ff83de..f303a955e5b 100644 --- a/engine/src/main/java/org/terasology/engine/core/modes/StateLoading.java +++ b/engine/src/main/java/org/terasology/engine/core/modes/StateLoading.java @@ -8,7 +8,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.crashreporter.CrashReporter; -import org.terasology.engine.config.Config; import org.terasology.engine.config.SystemConfig; import org.terasology.engine.context.Context; import org.terasology.engine.core.EngineTime; @@ -30,6 +29,7 @@ import org.terasology.engine.core.modes.loadProcesses.InitialiseSystems; import org.terasology.engine.core.modes.loadProcesses.InitialiseWorld; import org.terasology.engine.core.modes.loadProcesses.InitialiseWorldGenerator; +import org.terasology.engine.core.modes.loadProcesses.InitialiseRendering; import org.terasology.engine.core.modes.loadProcesses.JoinServer; import org.terasology.engine.core.modes.loadProcesses.LoadEntities; import org.terasology.engine.core.modes.loadProcesses.LoadExtraBlockData; @@ -66,9 +66,9 @@ public class StateLoading implements GameState { private static final Logger logger = LoggerFactory.getLogger(StateLoading.class); private Context context; - private GameManifest gameManifest; - private NetworkMode netMode; - private Queue loadProcesses = Queues.newArrayDeque(); + private final GameManifest gameManifest; + private final NetworkMode netMode; + private final Queue loadProcesses = Queues.newArrayDeque(); private LoadProcess current; private JoinStatus joinStatus; @@ -76,7 +76,6 @@ public class StateLoading implements GameState { private LoadingScreen loadingScreen; - private Config config; private SystemConfig systemConfig; private int progress; @@ -109,7 +108,6 @@ public void init(GameEngine engine) { this.context = engine.createChildContext(); CoreRegistry.setContext(context); - config = context.get(Config.class); systemConfig = context.get(SystemConfig.class); this.nuiManager = new NUIManagerInternal((TerasologyCanvasRenderer) context.get(CanvasRenderer.class), context); @@ -144,6 +142,7 @@ public void init(GameEngine engine) { private void initClient() { loadProcesses.add(new JoinServer(context, gameManifest, joinStatus)); + loadProcesses.add(new InitialiseRendering(context)); loadProcesses.add(new InitialiseEntitySystem(context)); loadProcesses.add(new RegisterBlocks(context, gameManifest)); loadProcesses.add(new InitialiseGraphics(context)); @@ -168,6 +167,9 @@ private void initClient() { private void initHost() { loadProcesses.add(new RegisterMods(context, gameManifest)); + if(netMode.hasLocalClient()) { + loadProcesses.add(new InitialiseRendering(context)); + } loadProcesses.add(new InitialiseEntitySystem(context)); loadProcesses.add(new RegisterBlocks(context, gameManifest)); loadProcesses.add(new InitialiseGraphics(context)); diff --git a/engine/src/main/java/org/terasology/engine/core/modes/StateMainMenu.java b/engine/src/main/java/org/terasology/engine/core/modes/StateMainMenu.java index da49c23380d..5ea120c2d25 100644 --- a/engine/src/main/java/org/terasology/engine/core/modes/StateMainMenu.java +++ b/engine/src/main/java/org/terasology/engine/core/modes/StateMainMenu.java @@ -5,52 +5,27 @@ import org.terasology.engine.audio.AudioManager; import org.terasology.engine.config.Config; import org.terasology.engine.config.TelemetryConfig; -import org.terasology.engine.context.Context; -import org.terasology.engine.core.ComponentSystemManager; import org.terasology.engine.core.GameEngine; import org.terasology.engine.core.LoggingContext; -import org.terasology.engine.core.bootstrap.EntitySystemSetupUtil; import org.terasology.engine.core.modes.loadProcesses.RegisterInputSystem; -import org.terasology.engine.entitySystem.entity.EntityRef; -import org.terasology.engine.entitySystem.entity.internal.EngineEntityManager; -import org.terasology.engine.entitySystem.event.internal.EventSystem; import org.terasology.engine.i18n.TranslationSystem; import org.terasology.engine.identity.storageServiceClient.StorageServiceWorker; import org.terasology.engine.input.InputSystem; -import org.terasology.engine.input.cameraTarget.CameraTargetSystem; import org.terasology.engine.logic.console.Console; -import org.terasology.engine.logic.console.ConsoleImpl; -import org.terasology.engine.logic.console.ConsoleSystem; -import org.terasology.engine.logic.console.commands.CoreCommands; -import org.terasology.engine.logic.players.LocalPlayer; -import org.terasology.engine.network.ClientComponent; -import org.terasology.engine.recording.DirectionAndOriginPosRecorderList; -import org.terasology.engine.recording.RecordAndReplayCurrentStatus; -import org.terasology.engine.registry.CoreRegistry; import org.terasology.engine.rendering.nui.NUIManager; import org.terasology.engine.rendering.nui.editor.systems.NUIEditorSystem; import org.terasology.engine.rendering.nui.editor.systems.NUISkinEditorSystem; -import org.terasology.engine.rendering.nui.internal.NUIManagerInternal; -import org.terasology.engine.rendering.nui.internal.TerasologyCanvasRenderer; import org.terasology.engine.rendering.nui.layers.mainMenu.LaunchPopup; import org.terasology.engine.rendering.nui.layers.mainMenu.MessagePopup; import org.terasology.engine.telemetry.TelemetryScreen; import org.terasology.engine.telemetry.TelemetryUtils; import org.terasology.engine.telemetry.logstash.TelemetryLogstashAppender; import org.terasology.engine.utilities.Assets; -import org.terasology.nui.canvas.CanvasRenderer; /** * The class implements the main game menu. - *

- * - * @version 0.3 */ -public class StateMainMenu implements GameState { - private Context context; - private EngineEntityManager entityManager; - private EventSystem eventSystem; - private ComponentSystemManager componentSystemManager; +public class StateMainMenu extends AbstractState { private NUIManager nuiManager; private InputSystem inputSystem; private Console console; @@ -69,33 +44,15 @@ public StateMainMenu(String showMessageOnLoad) { @Override public void init(GameEngine gameEngine) { context = gameEngine.createChildContext(); - CoreRegistry.setContext(context); + initEntityAndComponentManagers(); - //let's get the entity event system running - EntitySystemSetupUtil.addEntityManagementRelatedClasses(context); - entityManager = context.get(EngineEntityManager.class); + createLocalPlayer(context); - eventSystem = context.get(EventSystem.class); - console = new ConsoleImpl(context); - context.put(Console.class, console); - - nuiManager = new NUIManagerInternal((TerasologyCanvasRenderer) context.get(CanvasRenderer.class), context); - context.put(NUIManager.class, nuiManager); + // TODO: REMOVE this and handle refreshing of core game state at the engine level - see Issue #1127 + new RegisterInputSystem(context).step(); + nuiManager = context.get(NUIManager.class); eventSystem.registerEventHandler(nuiManager); - - componentSystemManager = new ComponentSystemManager(context); - context.put(ComponentSystemManager.class, componentSystemManager); - - // TODO: Reduce coupling between Input system and CameraTargetSystem, - // TODO: potentially eliminating the following lines. See Issue #1126 - CameraTargetSystem cameraTargetSystem = new CameraTargetSystem(); - context.put(CameraTargetSystem.class, cameraTargetSystem); - - componentSystemManager.register(cameraTargetSystem, "engine:CameraTargetSystem"); - componentSystemManager.register(new ConsoleSystem(), "engine:ConsoleSystem"); - componentSystemManager.register(new CoreCommands(), "engine:CoreCommands"); - NUIEditorSystem nuiEditorSystem = new NUIEditorSystem(); context.put(NUIEditorSystem.class, nuiEditorSystem); componentSystemManager.register(nuiEditorSystem, "engine:NUIEditorSystem"); @@ -106,17 +63,9 @@ public void init(GameEngine gameEngine) { inputSystem = context.get(InputSystem.class); - // TODO: REMOVE this and handle refreshing of core game state at the engine level - see Issue #1127 - new RegisterInputSystem(context).step(); - - EntityRef localPlayerEntity = entityManager.create(new ClientComponent()); - LocalPlayer localPlayer = new LocalPlayer(); - localPlayer.setRecordAndReplayClasses(context.get(DirectionAndOriginPosRecorderList.class), context.get(RecordAndReplayCurrentStatus.class)); - context.put(LocalPlayer.class, localPlayer); - localPlayer.setClientEntity(localPlayerEntity); - componentSystemManager.initialise(); + console = context.get(Console.class); storageServiceWorker = context.get(StorageServiceWorker.class); playBackgroundMusic(); @@ -164,22 +113,11 @@ private void pushLaunchPopup() { @Override public void dispose(boolean shuttingDown) { - // Apparently this can be disposed of before it is completely initialized? Probably only during - // crashes, but crashing again during shutdown complicates the diagnosis. - if (eventSystem != null) { - eventSystem.process(); - } - if (componentSystemManager != null) { - componentSystemManager.shutdown(); - } stopBackgroundMusic(); - if (nuiManager != null) { nuiManager.clear(); } - if (entityManager != null) { - entityManager.clear(); - } + super.dispose(shuttingDown); } private void playBackgroundMusic() { @@ -214,11 +152,6 @@ public String getLoggingPhase() { return LoggingContext.MENU; } - @Override - public Context getContext() { - return context; - } - @Override public boolean isHibernationAllowed() { return true; diff --git a/engine/src/main/java/org/terasology/engine/core/modes/loadProcesses/InitialiseRemoteWorld.java b/engine/src/main/java/org/terasology/engine/core/modes/loadProcesses/InitialiseRemoteWorld.java index 9de5d38c81d..80bd4f6af2f 100644 --- a/engine/src/main/java/org/terasology/engine/core/modes/loadProcesses/InitialiseRemoteWorld.java +++ b/engine/src/main/java/org/terasology/engine/core/modes/loadProcesses/InitialiseRemoteWorld.java @@ -69,9 +69,7 @@ public boolean step() { context.get(ComponentSystemManager.class).register(celestialSystem); // Init. a new world - Skysphere skysphere = new Skysphere(context); - BackdropProvider backdropProvider = skysphere; - context.put(BackdropProvider.class, backdropProvider); + context.put(BackdropProvider.class, new Skysphere(context)); RenderingSubsystemFactory engineSubsystemFactory = context.get(RenderingSubsystemFactory.class); WorldRenderer worldRenderer = engineSubsystemFactory.createWorldRenderer(context); diff --git a/engine/src/main/java/org/terasology/engine/core/modes/loadProcesses/InitialiseRendering.java b/engine/src/main/java/org/terasology/engine/core/modes/loadProcesses/InitialiseRendering.java new file mode 100644 index 00000000000..eb3a7394799 --- /dev/null +++ b/engine/src/main/java/org/terasology/engine/core/modes/loadProcesses/InitialiseRendering.java @@ -0,0 +1,42 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.engine.core.modes.loadProcesses; + +import org.terasology.engine.context.Context; +import org.terasology.engine.core.modes.SingleStepLoadProcess; +import org.terasology.engine.core.module.rendering.RenderingModuleRegistry; + +/** + * Add {@link RenderingModuleRegistry} to the game {@link Context}. + * + * The rendering system is required whenever a client starts or joins a game. As rendering may fail to re-initialise + * correctly when it has previously been constructed, this loading process will populate the {@link Context} with a + * freshly created rendering system. + * + * When switching the game state, the rendering system can just be disposed with the old state. + */ +public class InitialiseRendering extends SingleStepLoadProcess { + private final Context context; + + public InitialiseRendering(Context context) { + this.context = context; + } + + + @Override + public String getMessage() { + return "Initialising Rendering System..."; + } + + @Override + public boolean step() { + context.put(RenderingModuleRegistry.class, new RenderingModuleRegistry()); + return true; + } + + @Override + public int getExpectedCost() { + return 1; + } +} diff --git a/engine/src/main/java/org/terasology/engine/core/modes/loadProcesses/InitialiseWorld.java b/engine/src/main/java/org/terasology/engine/core/modes/loadProcesses/InitialiseWorld.java index 3ff6271c975..f77b57ad2e2 100644 --- a/engine/src/main/java/org/terasology/engine/core/modes/loadProcesses/InitialiseWorld.java +++ b/engine/src/main/java/org/terasology/engine/core/modes/loadProcesses/InitialiseWorld.java @@ -57,6 +57,8 @@ import java.io.IOException; import java.nio.file.Path; +import static com.google.common.base.Verify.verify; + public class InitialiseWorld extends SingleStepLoadProcess { private static final Logger logger = LoggerFactory.getLogger(InitialiseWorld.class); @@ -83,6 +85,7 @@ public boolean step() { context.put(WorldGeneratorPluginLibrary.class, new DefaultWorldGeneratorPluginLibrary(environment, context)); WorldInfo worldInfo = gameManifest.getWorldInfo(TerasologyConstants.MAIN_WORLD); + verify(worldInfo.getWorldGenerator().isValid(), "Game manifest did not specify world type."); if (worldInfo.getSeed() == null || worldInfo.getSeed().isEmpty()) { FastRandom random = new FastRandom(); worldInfo.setSeed(random.nextString(16)); diff --git a/engine/src/main/java/org/terasology/engine/core/module/ClasspathCompromisingModuleFactory.java b/engine/src/main/java/org/terasology/engine/core/module/ClasspathCompromisingModuleFactory.java index 2c268541299..a84c5501429 100644 --- a/engine/src/main/java/org/terasology/engine/core/module/ClasspathCompromisingModuleFactory.java +++ b/engine/src/main/java/org/terasology/engine/core/module/ClasspathCompromisingModuleFactory.java @@ -6,6 +6,8 @@ import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableSet; import org.reflections.util.ClasspathHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.terasology.gestalt.module.Module; import org.terasology.gestalt.module.ModuleEnvironment; import org.terasology.gestalt.module.ModuleFactory; @@ -13,11 +15,18 @@ import java.io.File; import java.io.IOException; +import java.net.JarURLConnection; import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; import java.util.Set; import java.util.function.Predicate; +import static com.google.common.base.Preconditions.checkArgument; + /** * Creates modules that can own classes that were loaded without a ModuleClassLoader. *

@@ -34,6 +43,8 @@ * acceptable to run without the protections ModuleClassLoader provides. */ class ClasspathCompromisingModuleFactory extends ModuleFactory { + private static final Logger logger = LoggerFactory.getLogger(ClasspathCompromisingModuleFactory.class); + @Override public Module createDirectoryModule(ModuleMetadata metadata, File directory) { Module module = super.createDirectoryModule(metadata, directory); @@ -52,6 +63,98 @@ public Module createArchiveModule(ModuleMetadata metadata, File archive) throws new ClassesInModule(module)); } + /** + * Find the location of the module containing this URL. + *

+ * Accounts for loading modules from development workspaces that may have their build directories + * on the classpath, as is the case when running tests. + * + * @see #setDefaultCodeSubpath + * @see #setDefaultLibsSubpath + * + * @param metadataName the expected name of the metadata file, as it would appear in {@link #getModuleMetadataLoaderMap()} + * @param metadataUrl a URL of a metadata file, such as might be returned from {@link ClassLoader#getSystemResource} + * @return the module's base directory, or a jar file if it doesn't look like a local build + */ + Path canonicalModuleLocation(String metadataName, URL metadataUrl) { + checkArgument(getModuleMetadataLoaderMap().containsKey(metadataName), + "metadataName `%s` is not in loader map", metadataName); + if (metadataUrl.getProtocol().equals("jar")) { + return modulePathFromMetadataJarUrl(metadataUrl); + } else { + return modulePathFromMetadataFileUrl(metadataName, metadataUrl); + } + } + + private Path modulePathFromMetadataFileUrl(String metadataName, URL url) { + Path path = fromUrl(url); + // We are considering the location of a resource file, so compare it to the code path. + // Include the metadata name in case it has path components of its own. + Path relativePathFromModuleRoot = Paths.get(getDefaultCodeSubpath(), metadataName); + return findModuleRoot(relativePathFromModuleRoot, path).orElse(path); + } + + private Path modulePathFromMetadataJarUrl(URL jarUrl) { + checkArgument(jarUrl.getProtocol().equals("jar"), "Not a jar URL: %s", jarUrl); + URL fileUrl; + try { + JarURLConnection connection = (JarURLConnection) jarUrl.openConnection(); + fileUrl = connection.getJarFileURL(); + // despite the method name, openConnection doesn't open anything unless we + // call connect(), so we needn't clean up anything here. + } catch (IOException e) { + throw new RuntimeException("Failed to get file from " + jarUrl, e); + } + Path path = fromUrl(fileUrl); + // We are considering the location of a jar file, so compare it to the libs path. + Path relativePathFromModuleRoot = Paths.get(getDefaultLibsSubpath()); + // Assume jars would be directly in the libs path (not in a subdirectory). + Path jarDirectory = path.getParent(); + return findModuleRoot(relativePathFromModuleRoot, jarDirectory).orElse(path); + } + + /** + * Find the root of a module build directory. + *

+ * If {@code path} matches a known build directory sub-path, return the base directory. + *

+ * Example: + *

+ */ + private static Optional findModuleRoot(Path relativePathFromModuleRoot, Path path) { + if (path.endsWith(relativePathFromModuleRoot)) { + int relativeDepth = relativePathFromModuleRoot.getNameCount(); + Path parentPath = path.subpath(0, path.getNameCount() - relativeDepth); + if (path.getRoot() != null) { // TODO: test case + parentPath = path.getRoot().resolve(parentPath); + } + return Optional.of(parentPath); + } else { + logger.warn("does not seem to be in a build directory {}", path); + return Optional.empty(); + } + } + + /** + * Convert a URL to a Path without checked exceptions. + *

+ * {@link URISyntaxException} is a rare edge case, not worth losing the ability to use this in a mapping function + * or the noise of try/catch blocks around every usage. + */ + private static Path fromUrl(URL url) { + try { + return Paths.get(url.toURI()); + } catch (RuntimeException | URISyntaxException e) { + throw new RuntimeException("Failed getting URL " + url, e); + } + } + + static class ClassesInModule implements Predicate> { private final Set classpaths; diff --git a/engine/src/main/java/org/terasology/engine/core/module/ModuleManager.java b/engine/src/main/java/org/terasology/engine/core/module/ModuleManager.java index 51e0647ebeb..1116e11e4ef 100644 --- a/engine/src/main/java/org/terasology/engine/core/module/ModuleManager.java +++ b/engine/src/main/java/org/terasology/engine/core/module/ModuleManager.java @@ -13,6 +13,7 @@ import org.terasology.engine.config.SystemConfig; import org.terasology.engine.core.PathManager; import org.terasology.engine.core.TerasologyConstants; +import org.terasology.engine.utilities.Jvm; import org.terasology.gestalt.module.Module; import org.terasology.gestalt.module.ModuleEnvironment; import org.terasology.gestalt.module.ModuleFactory; @@ -44,6 +45,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -74,6 +76,9 @@ public ModuleManager(String masterServerAddress, List> classesOnClasspa engineModule = loadAndConfigureEngineModule(moduleFactory, classesOnClasspathsToAddToEngine); registry.add(engineModule); + if (isLoadingClasspathModules()) { + loadModulesFromClassPath(); + } loadModulesFromApplicationPath(PathManager.getInstance()); ensureModulesDependOnEngine(); @@ -91,11 +96,16 @@ public ModuleManager(Config config, List> classesOnClasspathsToAddToEng this(config.getNetwork().getMasterServer(), classesOnClasspathsToAddToEngine); } + protected static boolean isLoadingClasspathModules() { + return Boolean.getBoolean(LOAD_CLASSPATH_MODULES_PROPERTY); + }; + /** Create a ModuleFactory configured for Terasology modules. */ private static ModuleFactory newModuleFactory(ModuleMetadataJsonAdapter metadataReader) { final ModuleFactory moduleFactory; - if (Boolean.getBoolean(LOAD_CLASSPATH_MODULES_PROPERTY)) { + if (isLoadingClasspathModules()) { moduleFactory = new ClasspathCompromisingModuleFactory(); + Jvm.logClasspath(logger); } else { moduleFactory = new ModuleFactory(); } @@ -136,6 +146,35 @@ private void loadModulesFromApplicationPath(PathManager pathManager) { scanner.scan(registry, paths); } + private void loadModulesFromClassPath() { + ClasspathCompromisingModuleFactory moduleFactory = (ClasspathCompromisingModuleFactory) this.moduleFactory; + for (String metadataName : moduleFactory.getModuleMetadataLoaderMap().keySet()) { + Enumeration urls; + try { + urls = ClassLoader.getSystemResources(metadataName); + } catch (IOException e) { + throw new RuntimeException(e); + } + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + logger.debug("Probably a module in U:{}", url); + Path path = moduleFactory.canonicalModuleLocation(metadataName, url); + Module module; + try { + module = moduleFactory.createModule(path.toFile()); + } catch (IOException e) { + logger.warn("Failed to create module from {}", path, e); + continue; + } + if (registry.add(module)) { + logger.info("Loaded {} from {}", module.getId(), path); + } else { + logger.info("Module {} from {} was a duplicate; not registering this copy.", module.getId(), path); + } + } + } + } + /** * Load and configure the engine module. *

diff --git a/engine/src/main/java/org/terasology/engine/core/subsystem/headless/mode/StateHeadlessSetup.java b/engine/src/main/java/org/terasology/engine/core/subsystem/headless/mode/StateHeadlessSetup.java index ce3c01478ab..c9be58d5226 100644 --- a/engine/src/main/java/org/terasology/engine/core/subsystem/headless/mode/StateHeadlessSetup.java +++ b/engine/src/main/java/org/terasology/engine/core/subsystem/headless/mode/StateHeadlessSetup.java @@ -6,42 +6,23 @@ import org.slf4j.LoggerFactory; import org.terasology.engine.config.Config; import org.terasology.engine.config.WorldGenerationConfig; -import org.terasology.engine.context.Context; -import org.terasology.engine.core.ComponentSystemManager; import org.terasology.engine.core.GameEngine; import org.terasology.engine.core.LoggingContext; import org.terasology.engine.core.SimpleUri; import org.terasology.engine.core.TerasologyConstants; -import org.terasology.engine.core.bootstrap.EntitySystemSetupUtil; -import org.terasology.engine.core.modes.GameState; +import org.terasology.engine.core.modes.AbstractState; import org.terasology.engine.core.modes.StateLoading; import org.terasology.engine.core.module.ModuleManager; import org.terasology.engine.core.module.StandardModuleExtension; -import org.terasology.engine.entitySystem.entity.EntityRef; -import org.terasology.engine.entitySystem.entity.internal.EngineEntityManager; -import org.terasology.engine.entitySystem.event.internal.EventSystem; import org.terasology.engine.game.GameManifest; import org.terasology.engine.input.InputSystem; -import org.terasology.engine.logic.console.Console; -import org.terasology.engine.logic.console.ConsoleImpl; -import org.terasology.engine.logic.console.ConsoleSystem; -import org.terasology.engine.logic.console.commands.CoreCommands; -import org.terasology.engine.logic.players.LocalPlayer; -import org.terasology.engine.network.ClientComponent; import org.terasology.engine.network.NetworkMode; -import org.terasology.engine.recording.DirectionAndOriginPosRecorderList; -import org.terasology.engine.recording.RecordAndReplayCurrentStatus; -import org.terasology.engine.registry.CoreRegistry; -import org.terasology.engine.rendering.nui.NUIManager; -import org.terasology.engine.rendering.nui.internal.NUIManagerInternal; -import org.terasology.engine.rendering.nui.internal.TerasologyCanvasRenderer; import org.terasology.engine.rendering.nui.layers.mainMenu.savedGames.GameInfo; import org.terasology.engine.rendering.nui.layers.mainMenu.savedGames.GameProvider; import org.terasology.engine.world.internal.WorldInfo; import org.terasology.engine.world.time.WorldTime; import org.terasology.gestalt.module.Module; import org.terasology.gestalt.naming.Name; -import org.terasology.nui.canvas.CanvasRenderer; import java.util.List; @@ -49,46 +30,20 @@ * The class is game selection menu replacement for the headless server. * */ -public class StateHeadlessSetup implements GameState { +public class StateHeadlessSetup extends AbstractState { private static final Logger logger = LoggerFactory.getLogger(StateHeadlessSetup.class); - private EngineEntityManager entityManager; - private EventSystem eventSystem; - private ComponentSystemManager componentSystemManager; - private Context context; - public StateHeadlessSetup() { } @Override public void init(GameEngine gameEngine) { context = gameEngine.createChildContext(); - CoreRegistry.setContext(context); - - // let's get the entity event system running - EntitySystemSetupUtil.addEntityManagementRelatedClasses(context); - entityManager = context.get(EngineEntityManager.class); - - eventSystem = context.get(EventSystem.class); - context.put(Console.class, new ConsoleImpl(context)); - - NUIManager nuiManager = new NUIManagerInternal((TerasologyCanvasRenderer) context.get(CanvasRenderer.class), context); - context.put(NUIManager.class, nuiManager); + initEntityAndComponentManagers(); + createLocalPlayer(context); - componentSystemManager = new ComponentSystemManager(context); - context.put(ComponentSystemManager.class, componentSystemManager); - - componentSystemManager.register(new ConsoleSystem(), "engine:ConsoleSystem"); - componentSystemManager.register(new CoreCommands(), "engine:CoreCommands"); componentSystemManager.register(context.get(InputSystem.class), "engine:InputSystem"); - - EntityRef localPlayerEntity = entityManager.create(new ClientComponent()); - LocalPlayer localPlayer = new LocalPlayer(); - localPlayer.setRecordAndReplayClasses(context.get(DirectionAndOriginPosRecorderList.class), context.get(RecordAndReplayCurrentStatus.class)); - context.put(LocalPlayer.class, localPlayer); - localPlayer.setClientEntity(localPlayerEntity); - componentSystemManager.initialise(); GameManifest gameManifest; @@ -147,15 +102,6 @@ public GameManifest createGameManifest() { return gameManifest; } - @Override - public void dispose(boolean shuttingDown) { - eventSystem.process(); - - componentSystemManager.shutdown(); - - entityManager.clear(); - } - @Override public void handleInput(float delta) { } @@ -178,9 +124,4 @@ public boolean isHibernationAllowed() { public String getLoggingPhase() { return LoggingContext.INIT_PHASE; } - - @Override - public Context getContext() { - return context; - } } diff --git a/engine/src/main/java/org/terasology/engine/core/subsystem/lwjgl/LwjglDisplayDevice.java b/engine/src/main/java/org/terasology/engine/core/subsystem/lwjgl/LwjglDisplayDevice.java index c045f325b61..91938e71995 100644 --- a/engine/src/main/java/org/terasology/engine/core/subsystem/lwjgl/LwjglDisplayDevice.java +++ b/engine/src/main/java/org/terasology/engine/core/subsystem/lwjgl/LwjglDisplayDevice.java @@ -36,6 +36,10 @@ public class LwjglDisplayDevice extends AbstractSubscribable implements DisplayD private RenderingConfig config; private DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo("unknown"); + private int windowWidth = 0; + private int windowHeight = 0; + private boolean isWindowDirty = true; + public LwjglDisplayDevice(Context context) { this.config = context.get(Config.class).getRendering(); } @@ -132,16 +136,25 @@ public List getResolutions() { @Override public int getWidth() { - int[] width = new int[1]; - GLFW.glfwGetWindowSize(GLFW.glfwGetCurrentContext(), width, new int[1]); - return width[0]; + updateWindow(); + return this.windowWidth; } @Override public int getHeight() { - int[] height = new int[1]; - GLFW.glfwGetWindowSize(GLFW.glfwGetCurrentContext(), new int[1], height); - return height[0]; + updateWindow(); + return this.windowHeight; + } + + private void updateWindow() { + if (isWindowDirty) { + int[] windowWidth = new int[1]; + int[] windowHeight = new int[1]; + GLFW.glfwGetWindowSize(GLFW.glfwGetCurrentContext(), windowWidth, windowHeight); + this.windowWidth = windowWidth[0]; + this.windowHeight = windowHeight[0]; + isWindowDirty = false; + } } @Override @@ -177,6 +190,8 @@ public DisplayDeviceInfo getInfo() { public void update() { processMessages(); GLFW.glfwSwapBuffers(GLFW.glfwGetCurrentContext()); + isWindowDirty = true; + } private void updateViewport() { diff --git a/engine/src/main/java/org/terasology/engine/core/subsystem/lwjgl/LwjglGraphicsUtil.java b/engine/src/main/java/org/terasology/engine/core/subsystem/lwjgl/LwjglGraphicsUtil.java index d54790112df..1413ffbf3c8 100644 --- a/engine/src/main/java/org/terasology/engine/core/subsystem/lwjgl/LwjglGraphicsUtil.java +++ b/engine/src/main/java/org/terasology/engine/core/subsystem/lwjgl/LwjglGraphicsUtil.java @@ -101,7 +101,6 @@ public static GLFWImage convertToGLFWFormat(BufferedImage image) { public static void initOpenGLParams() { glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); - glEnable(GL_NORMALIZE); glDepthFunc(GL_LEQUAL); } diff --git a/engine/src/main/java/org/terasology/engine/core/subsystem/rendering/ModuleRenderingSubsystem.java b/engine/src/main/java/org/terasology/engine/core/subsystem/rendering/ModuleRenderingSubsystem.java deleted file mode 100644 index d300558d5e8..00000000000 --- a/engine/src/main/java/org/terasology/engine/core/subsystem/rendering/ModuleRenderingSubsystem.java +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.engine.core.subsystem.rendering; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.terasology.engine.context.Context; -import org.terasology.engine.core.ComponentSystemManager; -import org.terasology.engine.core.GameEngine; -import org.terasology.engine.core.modes.GameState; -import org.terasology.engine.core.module.rendering.RenderingModuleRegistry; -import org.terasology.engine.core.subsystem.EngineSubsystem; -import org.terasology.gestalt.assets.module.ModuleAwareAssetTypeManager; - -public class ModuleRenderingSubsystem implements EngineSubsystem { - private static final Logger logger = LoggerFactory.getLogger(ModuleRenderingSubsystem.class); - - private RenderingModuleRegistry renderingModuleRegistry; - - @Override - public String getName() { - return "ModuleRendering"; - } - - @Override - public void preInitialise(Context rootContext) { - - } - - @Override - public void initialise(GameEngine engine, Context rootContext) { - this.renderingModuleRegistry = new RenderingModuleRegistry(); - rootContext.put(RenderingModuleRegistry.class, this.renderingModuleRegistry); - } - - @Override - public void registerCoreAssetTypes(ModuleAwareAssetTypeManager assetTypeManager) { - - } - - @Override - public void postInitialise(Context context) { - - } - - @Override - public void preUpdate(GameState currentState, float delta) { - - } - - @Override - public void postUpdate(GameState currentState, float delta) { - - } - - @Override - public void preShutdown() { - - } - - @Override - public void shutdown() { - - } - - @Override - public void registerSystems(ComponentSystemManager componentSystemManager) { - - } -} diff --git a/engine/src/main/java/org/terasology/engine/logic/characters/CharacterMovementComponent.java b/engine/src/main/java/org/terasology/engine/logic/characters/CharacterMovementComponent.java index 5ca6c8a2964..c4fcfd4c630 100644 --- a/engine/src/main/java/org/terasology/engine/logic/characters/CharacterMovementComponent.java +++ b/engine/src/main/java/org/terasology/engine/logic/characters/CharacterMovementComponent.java @@ -27,7 +27,7 @@ public final class CharacterMovementComponent implements Component { @Range(min = 0, max = 5) public float radius = 0.3f; public CollisionGroup collisionGroup = StandardCollisionGroup.CHARACTER; - public List collidesWith = Lists.newArrayList(StandardCollisionGroup.WORLD, StandardCollisionGroup.SENSOR); + public List collidesWith = Lists.newArrayList(StandardCollisionGroup.WORLD, StandardCollisionGroup.SENSOR, StandardCollisionGroup.CHARACTER); @Range(min = 0, max = 5) public float pickupRadius = 1.5f; diff --git a/engine/src/main/java/org/terasology/engine/logic/characters/CharacterSystem.java b/engine/src/main/java/org/terasology/engine/logic/characters/CharacterSystem.java index f65c33250d7..ab75099a7d2 100644 --- a/engine/src/main/java/org/terasology/engine/logic/characters/CharacterSystem.java +++ b/engine/src/main/java/org/terasology/engine/logic/characters/CharacterSystem.java @@ -30,6 +30,7 @@ import org.terasology.engine.logic.characters.interactions.InteractionUtil; import org.terasology.engine.logic.common.ActivateEvent; import org.terasology.engine.logic.common.DisplayNameComponent; +import org.terasology.engine.logic.common.RangeComponent; import org.terasology.engine.logic.health.BeforeDestroyEvent; import org.terasology.engine.logic.health.DestroyEvent; import org.terasology.engine.logic.health.EngineDamageTypes; @@ -352,7 +353,17 @@ private boolean isPredictionOfEventCorrect(EntityRef character, ActivationReques return false; // can happen if target existed on client } - HitResult result = physics.rayTrace(originPos, direction, characterComponent.interactionRange, Sets.newHashSet(character), + //FIXME This is the same code as in LocalPlayer#activateTargetOrOwnedEntity to derive the actual interaction range from the + // player's character component and the used item's range component... + float interactionRange; + if (event.isOwnedEntityUsage() && event.getUsedOwnedEntity().hasComponent(RangeComponent.class)) { + interactionRange = Math.max(event.getUsedOwnedEntity().getComponent(RangeComponent.class).range, + characterComponent.interactionRange); + } else { + interactionRange = characterComponent.interactionRange; + } + + HitResult result = physics.rayTrace(originPos, direction, interactionRange, Sets.newHashSet(character), DEFAULTPHYSICSFILTER); if (!result.isHit()) { String msg = "Denied activation attempt by {} since at the authority there was nothing to activate at that place"; @@ -383,8 +394,13 @@ private boolean isPredictionOfEventCorrect(EntityRef character, ActivationReques logger.info(msg, getPlayerNameFromCharacter(character)); return false; } - if (!(event.getHitPosition().equals(originPos, 0.0001f))) { - String msg = "Denied activation attempt by {} since the event was not properly labeled as having a hit postion"; + if (event.getHitPosition() != null) { + String msg = "Denied activation attempt by {} since the event was not properly labeled as having a hit position"; + logger.info(msg, getPlayerNameFromCharacter(character)); + return false; + } + if (event.getHitNormal() != null) { + String msg = "Denied activation attempt by {} since the event was not properly labeled as having a hit delta"; logger.info(msg, getPlayerNameFromCharacter(character)); return false; } diff --git a/engine/src/main/java/org/terasology/engine/logic/characters/events/ActivationPredicted.java b/engine/src/main/java/org/terasology/engine/logic/characters/events/ActivationPredicted.java index 781d9f26cfd..13be5a69e7d 100644 --- a/engine/src/main/java/org/terasology/engine/logic/characters/events/ActivationPredicted.java +++ b/engine/src/main/java/org/terasology/engine/logic/characters/events/ActivationPredicted.java @@ -17,7 +17,10 @@ public class ActivationPredicted extends AbstractConsumableEvent { private Vector3f hitNormal; private int activationId; - public ActivationPredicted() { + /** + * INTERNAL: required for serialization. + */ + protected ActivationPredicted() { } public ActivationPredicted(EntityRef instigator, EntityRef target, Vector3f origin, Vector3f direction, @@ -35,6 +38,9 @@ public EntityRef getInstigator() { return instigator; } + /** + * @return the entity that is hit, or {@link EntityRef#NULL} if the activation has no target + */ public EntityRef getTarget() { return target; } @@ -47,10 +53,16 @@ public Vector3f getDirection() { return direction; } + /** + * @return the hit position if {@link #getTarget()} exists, {@code null} otherwise + */ public Vector3f getHitPosition() { return hitPosition; } + /** + * @return the hit normal if {@link #getTarget()} exists, {@code null} otherwise + */ public Vector3f getHitNormal() { return hitNormal; } @@ -59,6 +71,9 @@ public int getActivationId() { return activationId; } + /** + * @return the target location if {@link #getTarget()} exists and has a location, {@code null} otherwise + */ public Vector3f getTargetLocation() { LocationComponent loc = target.getComponent(LocationComponent.class); if (loc != null) { diff --git a/engine/src/main/java/org/terasology/engine/logic/characters/events/ActivationRequest.java b/engine/src/main/java/org/terasology/engine/logic/characters/events/ActivationRequest.java index 19b554f1330..1c9bbd3b0f8 100644 --- a/engine/src/main/java/org/terasology/engine/logic/characters/events/ActivationRequest.java +++ b/engine/src/main/java/org/terasology/engine/logic/characters/events/ActivationRequest.java @@ -27,7 +27,10 @@ public class ActivationRequest extends NetworkEvent { private Vector3f hitNormal; private int activationId; - public ActivationRequest() { + /** + * INTERNAL: required for serialization. + */ + protected ActivationRequest() { } public ActivationRequest(EntityRef instigator, boolean ownedEntityUsage, EntityRef usedOwnedEntity, @@ -57,6 +60,9 @@ public boolean isEventWithTarget() { return eventWithTarget; } + /** + * @return the entity that is hit, or {@link EntityRef#NULL} if the activation has no target + */ public EntityRef getTarget() { return target; } @@ -69,10 +75,16 @@ public Vector3f getDirection() { return direction; } + /** + * @return the hit position if {@link #isEventWithTarget()} is true, {@code null} otherwise + */ public Vector3f getHitPosition() { return hitPosition; } + /** + * @return the hit normal if {@link #isEventWithTarget()} is true, {@code null} otherwise + */ public Vector3f getHitNormal() { return hitNormal; } diff --git a/engine/src/main/java/org/terasology/engine/logic/common/ActivateEvent.java b/engine/src/main/java/org/terasology/engine/logic/common/ActivateEvent.java index d68310b72f7..e699195da84 100644 --- a/engine/src/main/java/org/terasology/engine/logic/common/ActivateEvent.java +++ b/engine/src/main/java/org/terasology/engine/logic/common/ActivateEvent.java @@ -43,6 +43,9 @@ public EntityRef getInstigator() { return instigator; } + /** + * @return the entity that is targeted, or {@link EntityRef#NULL} if the activation has no target + */ public EntityRef getTarget() { return target; } @@ -55,10 +58,16 @@ public Vector3f getDirection() { return direction; } + /** + * @return the hit position if {@link #getTarget()} exists, {@code null} otherwise + */ public Vector3f getHitPosition() { return hitPosition; } + /** + * @return the hit normal if {@link #getTarget()} exists, {@code null} otherwise + */ public Vector3f getHitNormal() { return hitNormal; } @@ -67,6 +76,9 @@ public int getActivationId() { return activationId; } + /** + * @return the target location if {@link #getTarget()} exists and has a location, {@code null} otherwise + */ public Vector3f getTargetLocation() { LocationComponent loc = target.getComponent(LocationComponent.class); if (loc != null) { @@ -90,4 +102,17 @@ public Vector3f getInstigatorLocation() { } return new Vector3f(); } + + @Override + public String toString() { + return "ActivateEvent{" + + "instigator=" + instigator + + ", target=" + target + + ", origin=" + origin + + ", direction=" + direction + + ", hitPosition=" + hitPosition + + ", hitNormal=" + hitNormal + + ", activationId=" + activationId + + '}'; + } } diff --git a/engine/src/main/java/org/terasology/engine/logic/common/RangeComponent.java b/engine/src/main/java/org/terasology/engine/logic/common/RangeComponent.java new file mode 100644 index 00000000000..2a7f6fc05d2 --- /dev/null +++ b/engine/src/main/java/org/terasology/engine/logic/common/RangeComponent.java @@ -0,0 +1,18 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.engine.logic.common; + +import org.terasology.engine.entitySystem.Component; + +/** + * Indicate that an entity has a range. + *

+ * This can be used to give items an extended range for activation, but may also be used to indicate any other ranged ability of an entity. + */ +public class RangeComponent implements Component { + /** + * The range measured in in-game blocks. + */ + public float range; +} diff --git a/engine/src/main/java/org/terasology/engine/logic/players/LocalPlayer.java b/engine/src/main/java/org/terasology/engine/logic/players/LocalPlayer.java index 161dabf7841..96b58d613b8 100644 --- a/engine/src/main/java/org/terasology/engine/logic/players/LocalPlayer.java +++ b/engine/src/main/java/org/terasology/engine/logic/players/LocalPlayer.java @@ -10,6 +10,7 @@ import org.terasology.engine.logic.characters.CharacterMovementComponent; import org.terasology.engine.logic.characters.CharacterSystem; import org.terasology.engine.logic.common.ActivateEvent; +import org.terasology.engine.logic.common.RangeComponent; import org.terasology.engine.logic.location.LocationComponent; import org.terasology.engine.logic.characters.events.ActivationPredicted; import org.terasology.engine.logic.characters.events.ActivationRequest; @@ -236,24 +237,29 @@ private boolean activateTargetOrOwnedEntity(EntityRef usedOwnedEntity) { boolean ownedEntityUsage = usedOwnedEntity.exists(); int activationId = nextActivationId++; Physics physics = CoreRegistry.get(Physics.class); - HitResult result = physics.rayTrace(originPos, direction, characterComponent.interactionRange, + + //FIXME This is the same code as in CharacterSystem#isPredictionOfEventCorrect to derive the actual interaction range from the + // player's character component and the used item's range component... + float interactionRange; + if (ownedEntityUsage && usedOwnedEntity.hasComponent(RangeComponent.class)) { + interactionRange = Math.max(usedOwnedEntity.getComponent(RangeComponent.class).range, characterComponent.interactionRange); + } else { + interactionRange = characterComponent.interactionRange; + } + + HitResult result = physics.rayTrace(originPos, direction, interactionRange, Sets.newHashSet(character), CharacterSystem.DEFAULTPHYSICSFILTER); boolean eventWithTarget = result.isHit(); - if (eventWithTarget) { + + if (ownedEntityUsage || eventWithTarget) { EntityRef activatedObject = usedOwnedEntity.exists() ? usedOwnedEntity : result.getEntity(); activatedObject.send(new ActivationPredicted(character, result.getEntity(), originPos, direction, result.getHitPoint(), result.getHitNormal(), activationId)); + character.send(new ActivationRequest(character, ownedEntityUsage, usedOwnedEntity, eventWithTarget, result.getEntity(), originPos, direction, result.getHitPoint(), result.getHitNormal(), activationId)); return true; - } else if (ownedEntityUsage) { - usedOwnedEntity.send(new ActivationPredicted(character, EntityRef.NULL, originPos, direction, - originPos, new Vector3f(), activationId)); - character.send(new ActivationRequest(character, ownedEntityUsage, usedOwnedEntity, eventWithTarget, - EntityRef.NULL, - originPos, direction, originPos, new Vector3f(), activationId)); - return true; } return false; } diff --git a/engine/src/main/java/org/terasology/engine/logic/players/LocalPlayerSystem.java b/engine/src/main/java/org/terasology/engine/logic/players/LocalPlayerSystem.java index 2aef5b077b9..806276cf671 100644 --- a/engine/src/main/java/org/terasology/engine/logic/players/LocalPlayerSystem.java +++ b/engine/src/main/java/org/terasology/engine/logic/players/LocalPlayerSystem.java @@ -43,14 +43,12 @@ import org.terasology.engine.logic.characters.events.OnItemUseEvent; import org.terasology.engine.logic.characters.events.ScaleToRequest; import org.terasology.engine.logic.characters.interactions.InteractionUtil; -import org.terasology.engine.logic.delay.DelayManager; import org.terasology.engine.logic.location.LocationComponent; import org.terasology.engine.logic.players.event.LocalPlayerInitializedEvent; import org.terasology.engine.logic.players.event.OnPlayerSpawnedEvent; import org.terasology.engine.network.ClientComponent; import org.terasology.engine.network.NetworkMode; import org.terasology.engine.network.NetworkSystem; -import org.terasology.engine.physics.engine.PhysicsEngine; import org.terasology.engine.registry.In; import org.terasology.engine.rendering.AABBRenderer; import org.terasology.engine.rendering.cameras.Camera; @@ -78,10 +76,6 @@ public class LocalPlayerSystem extends BaseComponentSystem implements UpdateSubs private LocalPlayer localPlayer; @In private WorldProvider worldProvider; - @In - private PhysicsEngine physics; - @In - private DelayManager delayManager; @In private Config config; @@ -263,6 +257,14 @@ public void updateRotationPitch(RotationPitchAxis event, EntityRef entity) { event.consume(); } + @ReceiveEvent(components = CharacterComponent.class) + public void setRotation(SetDirectionEvent event, EntityRef entity) { + if (localPlayer.getCharacterEntity().equals(entity)) { + lookPitch = event.getPitch(); + lookYaw = event.getYaw(); + } + } + @ReceiveEvent(components = {CharacterComponent.class, CharacterMovementComponent.class}) public void onJump(JumpButton event, EntityRef entity) { if (event.isDown()) { diff --git a/engine/src/main/java/org/terasology/engine/logic/players/SetDirectionEvent.java b/engine/src/main/java/org/terasology/engine/logic/players/SetDirectionEvent.java new file mode 100644 index 00000000000..39751411670 --- /dev/null +++ b/engine/src/main/java/org/terasology/engine/logic/players/SetDirectionEvent.java @@ -0,0 +1,31 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 +package org.terasology.engine.logic.players; + +import org.terasology.engine.entitySystem.event.Event; +import org.terasology.engine.network.OwnerEvent; + +/** + * Trigger event that sets the direction a player is facing. + */ +@OwnerEvent +public class SetDirectionEvent implements Event { + private float yaw; + private float pitch; + + protected SetDirectionEvent() { + } + + public SetDirectionEvent(float yaw, float pitch) { + this.yaw = yaw; + this.pitch = pitch; + } + + public float getYaw() { + return yaw; + } + + public float getPitch() { + return pitch; + } +} diff --git a/engine/src/main/java/org/terasology/engine/network/internal/pipelineFactory/InfoRequestPipelineFactory.java b/engine/src/main/java/org/terasology/engine/network/internal/pipelineFactory/InfoRequestPipelineFactory.java index 39c1befda8a..93079fbf095 100644 --- a/engine/src/main/java/org/terasology/engine/network/internal/pipelineFactory/InfoRequestPipelineFactory.java +++ b/engine/src/main/java/org/terasology/engine/network/internal/pipelineFactory/InfoRequestPipelineFactory.java @@ -7,11 +7,11 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; -import io.netty.handler.codec.compression.JdkZlibDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.handler.codec.compression.Lz4FrameDecoder; +import io.netty.handler.codec.compression.Lz4FrameEncoder; import io.netty.handler.codec.protobuf.ProtobufDecoder; import io.netty.handler.codec.protobuf.ProtobufEncoder; -import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; -import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; import org.terasology.engine.network.internal.ClientHandshakeHandler; import org.terasology.engine.network.internal.JoinStatusImpl; import org.terasology.engine.network.internal.MetricRecordingHandler; @@ -30,13 +30,14 @@ protected void initChannel(Channel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(MetricRecordingHandler.NAME, new MetricRecordingHandler()); + p.addLast("inflateDecoder", new Lz4FrameDecoder()); p.addLast("lengthFrameDecoder", new LengthFieldBasedFrameDecoder(8388608, 0, 3, 0, 3)); - p.addLast("inflateDecoder", new JdkZlibDecoder()); - p.addLast("frameDecoder", new ProtobufVarint32FrameDecoder()); p.addLast("protobufDecoder", new ProtobufDecoder(NetData.NetMessage.getDefaultInstance())); - p.addLast("frameEncoder", new ProtobufVarint32LengthFieldPrepender()); + p.addLast("deflateEncoder", new Lz4FrameEncoder(true)); + p.addLast("frameLengthEncoder", new LengthFieldPrepender(3)); p.addLast("protobufEncoder", new ProtobufEncoder()); + p.addLast("authenticationHandler", new ClientHandshakeHandler(joinStatus)); p.addLast("connectionHandler", new ServerInfoRequestHandler()); } diff --git a/engine/src/main/java/org/terasology/engine/network/internal/pipelineFactory/TerasologyClientPipelineFactory.java b/engine/src/main/java/org/terasology/engine/network/internal/pipelineFactory/TerasologyClientPipelineFactory.java index ab463d96024..dd0a3edede1 100644 --- a/engine/src/main/java/org/terasology/engine/network/internal/pipelineFactory/TerasologyClientPipelineFactory.java +++ b/engine/src/main/java/org/terasology/engine/network/internal/pipelineFactory/TerasologyClientPipelineFactory.java @@ -7,11 +7,11 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; -import io.netty.handler.codec.compression.JdkZlibDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.handler.codec.compression.Lz4FrameDecoder; +import io.netty.handler.codec.compression.Lz4FrameEncoder; import io.netty.handler.codec.protobuf.ProtobufDecoder; import io.netty.handler.codec.protobuf.ProtobufEncoder; -import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; -import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; import org.terasology.engine.network.internal.ClientConnectionHandler; import org.terasology.engine.network.internal.ClientHandler; import org.terasology.engine.network.internal.ClientHandshakeHandler; @@ -38,13 +38,14 @@ protected void initChannel(Channel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(MetricRecordingHandler.NAME, new MetricRecordingHandler()); + p.addLast("inflateDecoder", new Lz4FrameDecoder()); p.addLast("lengthFrameDecoder", new LengthFieldBasedFrameDecoder(8388608, 0, 3, 0, 3)); - p.addLast("inflateDecoder", new JdkZlibDecoder()); - p.addLast("frameDecoder", new ProtobufVarint32FrameDecoder()); p.addLast("protobufDecoder", new ProtobufDecoder(NetData.NetMessage.getDefaultInstance())); - p.addLast("frameEncoder", new ProtobufVarint32LengthFieldPrepender()); + p.addLast("deflateEncoder", new Lz4FrameEncoder(true)); + p.addLast("frameLengthEncoder", new LengthFieldPrepender(3)); p.addLast("protobufEncoder", new ProtobufEncoder()); + p.addLast("authenticationHandler", new ClientHandshakeHandler(joinStatus)); p.addLast("connectionHandler", new ClientConnectionHandler(joinStatus, networkSystem)); p.addLast("handler", new ClientHandler(networkSystem)); diff --git a/engine/src/main/java/org/terasology/engine/network/internal/pipelineFactory/TerasologyServerPipelineFactory.java b/engine/src/main/java/org/terasology/engine/network/internal/pipelineFactory/TerasologyServerPipelineFactory.java index d8b847cf46a..1aef69f9a7b 100644 --- a/engine/src/main/java/org/terasology/engine/network/internal/pipelineFactory/TerasologyServerPipelineFactory.java +++ b/engine/src/main/java/org/terasology/engine/network/internal/pipelineFactory/TerasologyServerPipelineFactory.java @@ -6,12 +6,12 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; -import io.netty.handler.codec.compression.JdkZlibEncoder; +import io.netty.handler.codec.compression.Lz4FrameDecoder; +import io.netty.handler.codec.compression.Lz4FrameEncoder; import io.netty.handler.codec.protobuf.ProtobufDecoder; import io.netty.handler.codec.protobuf.ProtobufEncoder; -import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; -import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; import org.terasology.engine.network.internal.MetricRecordingHandler; import org.terasology.engine.network.internal.NetworkSystemImpl; import org.terasology.engine.network.internal.ServerConnectionHandler; @@ -35,12 +35,12 @@ protected void initChannel(Channel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(MetricRecordingHandler.NAME, new MetricRecordingHandler()); - p.addLast("frameDecoder", new ProtobufVarint32FrameDecoder()); + p.addLast("inflateDecoder", new Lz4FrameDecoder()); + p.addLast("lengthFrameDecoder", new LengthFieldBasedFrameDecoder(8388608, 0, 3, 0, 3)); p.addLast("protobufDecoder", new ProtobufDecoder(NetData.NetMessage.getDefaultInstance())); + p.addLast("deflateEncoder", new Lz4FrameEncoder(true)); p.addLast("frameLengthEncoder", new LengthFieldPrepender(3)); - p.addLast("deflateEncoder", new JdkZlibEncoder()); - p.addLast("frameEncoder", new ProtobufVarint32LengthFieldPrepender()); p.addLast("protobufEncoder", new ProtobufEncoder()); p.addLast("authenticationHandler", new ServerHandshakeHandler()); diff --git a/engine/src/main/java/org/terasology/engine/rendering/assets/mesh/resource/IndexResource.java b/engine/src/main/java/org/terasology/engine/rendering/assets/mesh/resource/IndexResource.java index a26cf370524..375a91ec714 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/assets/mesh/resource/IndexResource.java +++ b/engine/src/main/java/org/terasology/engine/rendering/assets/mesh/resource/IndexResource.java @@ -36,6 +36,7 @@ public void reserveElements(int elements) { public void rewind() { posIndex = 0; + inIndices = 0; } public void put(int value) { diff --git a/engine/src/main/java/org/terasology/engine/rendering/assets/texture/TextureData.java b/engine/src/main/java/org/terasology/engine/rendering/assets/texture/TextureData.java index d7516da9d83..54e2e869282 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/assets/texture/TextureData.java +++ b/engine/src/main/java/org/terasology/engine/rendering/assets/texture/TextureData.java @@ -4,6 +4,8 @@ import com.google.common.math.IntMath; import org.terasology.gestalt.assets.AssetData; +import org.terasology.nui.Color; +import org.terasology.nui.Colorc; import java.nio.ByteBuffer; import java.util.Arrays; @@ -70,6 +72,17 @@ public TextureData(TextureData fromCopy) { } } + /** + * Look up the color of a given pixel in the base mipmap level. + */ + public Colorc getPixel(int x, int y, Color result) { + ByteBuffer baseMipmap = data[0]; + int idx = BYTES_PER_PIXEL * (x + y * width); + int pixel = baseMipmap.getInt(idx); + result.set(pixel); + return result; + } + public int getWidth() { return width; } diff --git a/engine/src/main/java/org/terasology/engine/rendering/logic/SkeletalMeshComponent.java b/engine/src/main/java/org/terasology/engine/rendering/logic/SkeletalMeshComponent.java index 500fc25c833..17b2cabeec5 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/logic/SkeletalMeshComponent.java +++ b/engine/src/main/java/org/terasology/engine/rendering/logic/SkeletalMeshComponent.java @@ -20,7 +20,10 @@ @ForceBlockActive public class SkeletalMeshComponent implements VisualComponent { + @Replicate public SkeletalMesh mesh; + + @Replicate public Material material; /** diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/editor/layers/AbstractEditorScreen.java b/engine/src/main/java/org/terasology/engine/rendering/nui/editor/layers/AbstractEditorScreen.java index 11631528276..e511bde1108 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/editor/layers/AbstractEditorScreen.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/editor/layers/AbstractEditorScreen.java @@ -19,9 +19,6 @@ import org.terasology.gestalt.assets.ResourceUrn; import org.terasology.gestalt.assets.exceptions.InvalidUrnException; import org.terasology.gestalt.assets.format.AssetDataFile; -import org.terasology.gestalt.module.Module; -import org.terasology.gestalt.module.resources.DirectoryFileSource; -import org.terasology.gestalt.naming.Name; import org.terasology.input.Keyboard; import org.terasology.input.device.KeyboardDevice; import org.terasology.nui.Canvas; @@ -46,11 +43,10 @@ import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static com.google.common.base.Verify.verifyNotNull; - /** * A base screen for the NUI screen/skin editors. */ @@ -339,24 +335,16 @@ private void saveToFile(BufferedOutputStream outputStream) throws IOException { * @return path to asset or null if module not present as source */ protected Path getPath(AssetDataFile source) { - List path = source.getPath(); - Name moduleName = new Name(path.get(0)); - Module module = verifyNotNull(moduleManager.getEnvironment().get(moduleName), - "Module \"%s\" not found in current module environment.", moduleName); - // TODO: Checking whether the module is present as source should not be done in `getPath()` as this has no - // knowledge about what the path will be used for (read vs write access) - if (module.getResources() instanceof DirectoryFileSource) { - path.add(source.getFilename()); - String[] pathArray = path.toArray(new String[path.size()]); - - // Copy all the elements after the first to a separate array for getPath(). - String first = pathArray[0]; - String[] more = Arrays.copyOfRange(pathArray, 1, pathArray.length); - return Paths.get("", moduleManager.getEnvironment().getResources() - .getFile(first, more) - .orElseThrow(()-> new RuntimeException("Cannot get path for " + source.getFilename())).getPath().stream().toArray(String[]::new)); - } - return null; + List path = new ArrayList<>(source.getPath()); + path.add(source.getFilename()); + String[] pathArray = path.toArray(new String[path.size()]); + + // Copy all the elements after the first to a separate array for getPath(). + String first = pathArray[0]; + String[] more = Arrays.copyOfRange(pathArray, 1, pathArray.length); + return Paths.get("", moduleManager.getEnvironment().getResources() + .getFile(first, more) + .orElseThrow(()-> new RuntimeException("Cannot get path for " + source.getFilename())).getPath().stream().toArray(String[]::new)); } /** diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/internal/LineRenderer.java b/engine/src/main/java/org/terasology/engine/rendering/nui/internal/LineRenderer.java index 0c1dc58dc61..dfee8b09614 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/internal/LineRenderer.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/internal/LineRenderer.java @@ -2,15 +2,21 @@ // SPDX-License-Identifier: Apache-2.0 package org.terasology.engine.rendering.nui.internal; -import org.lwjgl.BufferUtils; +import org.joml.Vector3f; +import org.joml.Vector4f; import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GL20; +import org.terasology.engine.rendering.assets.mesh.Mesh; +import org.terasology.engine.rendering.assets.mesh.StandardMeshData; +import org.terasology.engine.rendering.assets.mesh.resource.AllocationType; +import org.terasology.engine.rendering.assets.mesh.resource.DrawingMode; +import org.terasology.engine.utilities.Assets; +import org.terasology.nui.Color; import org.terasology.nui.Colorc; -import java.nio.FloatBuffer; - public final class LineRenderer { + private static StandardMeshData lineMeshData = new StandardMeshData(DrawingMode.TRIANGLE_STRIP, AllocationType.STREAM); + private static Mesh lineMesh = null; private LineRenderer() { @@ -32,10 +38,10 @@ private LineRenderer() { * */ public static void draw(float x1, float y1, float x2, float y2, float width, Colorc color, Colorc background, float alpha) { - GL20.glUseProgram(0); + if(lineMesh == null) { + lineMesh = Assets.generateAsset(lineMeshData, Mesh.class); + } GL11.glDisable(GL11.GL_CULL_FACE); - GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY); - GL11.glEnableClientState(GL11.GL_COLOR_ARRAY); float t = 0; float r = 0; @@ -171,112 +177,89 @@ public static void draw(float x1, float y1, float x2, float y2, float width, Col } } - //draw the line by triangle strip - float[] lineVertex = - { - x1 - tx - rx, y1 - ty - ry, //fading edge1 - x2 - tx - rx, y2 - ty - ry, - x1 - tx, y1 - ty, //core - x2 - tx, y2 - ty, - x1 + tx, y1 + ty, - x2 + tx, y2 + ty, - x1 + tx + rx, y1 + ty + ry, //fading edge2 - x2 + tx + rx, y2 + ty + ry - }; - GL11.glVertexPointer(2, 0, 0, wrap(lineVertex)); + lineMeshData.reallocate(0, 0); + lineMeshData.indices.rewind(); + lineMeshData.position.rewind(); + lineMeshData.color0.rewind(); - if (!alphaBlend) { - float[] lineColor = - { - bRed, bGreen, bBlue, - bRed, bGreen, bBlue, - cRed, cGreen, cBlue, - cRed, cGreen, cBlue, - cRed, cGreen, cBlue, - cRed, cGreen, cBlue, - bRed, bGreen, bBlue, - bRed, bGreen, bBlue - }; - GL11.glColorPointer(3, 0, 0, wrap(lineColor)); - } else { - float[] lineColor = - { - cRed, cGreen, cBlue, 0, - cRed, cGreen, cBlue, 0, - cRed, cGreen, cBlue, a, - cRed, cGreen, cBlue, a, - cRed, cGreen, cBlue, a, - cRed, cGreen, cBlue, a, - cRed, cGreen, cBlue, 0, - cRed, cGreen, cBlue, 0 - }; - GL11.glColorPointer(4, 0, 0, wrap(lineColor)); + + Vector3f v1 = new Vector3f(); + Vector4f v2 = new Vector4f(); + + lineMeshData.position.put(v1.set(x1 - tx - rx, y1 - ty - ry, 0.0f)); + lineMeshData.position.put(v1.set(x2 - tx - rx, y2 - ty - ry, 0.0f)); + lineMeshData.position.put(v1.set(x1 - tx, y1 - ty, 0.0f)); + lineMeshData.position.put(v1.set(x2 - tx, y2 - ty, 0.0f)); + lineMeshData.position.put(v1.set(x1 + tx, y1 + ty, 0.0f)); + lineMeshData.position.put(v1.set(x2 + tx, y2 + ty, 0.0f)); + + if (!((Math.abs(dx) < epsilon || Math.abs(dy) < epsilon) && width <= 1.0)) { + lineMeshData.position.put(v1.set(x1 + tx + rx, y1 + ty + ry, 0.0f)); + lineMeshData.position.put(v1.set(x2 + tx + rx, y2 + ty + ry, 0.0f)); } - if ((Math.abs(dx) < epsilon || Math.abs(dy) < epsilon) && width <= 1.0) { - GL11.glDrawArrays(GL11.GL_TRIANGLE_STRIP, 0, 6); + Color c = new Color(); + if (!alphaBlend) { + lineMeshData.color0.put(c.set(v1.set(bRed, bGreen, bBlue))); + lineMeshData.color0.put(c.set(v1.set(bRed, bGreen, bBlue))); + lineMeshData.color0.put(c.set(v1.set(cRed, cGreen, cBlue))); + lineMeshData.color0.put(c.set(v1.set(cRed, cGreen, cBlue))); + lineMeshData.color0.put(c.set(v1.set(cRed, cGreen, cBlue))); + lineMeshData.color0.put(c.set(v1.set(cRed, cGreen, cBlue))); + lineMeshData.color0.put(c.set(v1.set(bRed, bGreen, bBlue))); } else { - GL11.glDrawArrays(GL11.GL_TRIANGLE_STRIP, 0, 8); + lineMeshData.color0.put(c.set(v2.set(bRed, bGreen, bBlue, 0.0f))); + lineMeshData.color0.put(c.set(v2.set(bRed, bGreen, bBlue, 0.0f))); + lineMeshData.color0.put(c.set(v2.set(cRed, cGreen, cBlue, a))); + lineMeshData.color0.put(c.set(v2.set(cRed, cGreen, cBlue, a))); + lineMeshData.color0.put(c.set(v2.set(cRed, cGreen, cBlue, a))); + lineMeshData.color0.put(c.set(v2.set(cRed, cGreen, cBlue, 0.0f))); + lineMeshData.color0.put(c.set(v2.set(bRed, bGreen, bBlue, 0.0f))); + } + lineMesh.reload(lineMeshData); + lineMesh.render(); //cap (do not draw if too thin) if (width >= 3) { - //draw cap - lineVertex = new float[] - { - x1 - rx + cx, y1 - ry + cy, //cap1 - x1 + rx + cx, y1 + ry + cy, - x1 - tx - rx, y1 - ty - ry, - x1 + tx + rx, y1 + ty + ry, - x2 - rx - cx, y2 - ry - cy, //cap2 - x2 + rx - cx, y2 + ry - cy, - x2 - tx - rx, y2 - ty - ry, - x2 + tx + rx, y2 + ty + ry - }; - GL11.glVertexPointer(2, 0, 0, wrap(lineVertex)); + lineMeshData.reallocate(0, 0); + lineMeshData.indices.rewind(); + lineMeshData.position.rewind(); + lineMeshData.color0.rewind(); + + lineMeshData.position.put(v1.set( x1 - rx + cx, y1 - ry + cy, 0.0f)); + lineMeshData.position.put(v1.set( x1 + rx + cx, y1 + ry + cy, 0.0f)); + lineMeshData.position.put(v1.set( x1 - tx - rx, y1 - ty - ry, 0.0f)); + lineMeshData.position.put(v1.set( x1 + tx + rx, y1 + ty + ry, 0.0f)); + lineMeshData.position.put(v1.set( x2 - rx - cx, y2 - ry - cy, 0.0f)); + lineMeshData.position.put(v1.set( x2 + rx - cx, y2 + ry - cy, 0.0f)); + lineMeshData.position.put(v1.set( x2 - tx - rx, y2 - ty - ry, 0.0f)); + lineMeshData.position.put(v1.set( x2 + tx + rx, y2 + ty + ry, 0.0f)); if (!alphaBlend) { - float[] lineColor = - { - bRed, bGreen, bBlue, //cap1 - bRed, bGreen, bBlue, - cRed, cGreen, cBlue, - cRed, cGreen, cBlue, - bRed, bGreen, bBlue, //cap2 - bRed, bGreen, bBlue, - cRed, cGreen, cBlue, - cRed, cGreen, cBlue - }; - GL11.glColorPointer(3, 0, 0, wrap(lineColor)); + lineMeshData.color0.put(c.set(v1.set(bRed, bGreen, bBlue))); + lineMeshData.color0.put(c.set(v1.set(bRed, bGreen, bBlue))); + lineMeshData.color0.put(c.set(v1.set(cRed, cGreen, cBlue))); + lineMeshData.color0.put(c.set(v1.set(cRed, cGreen, cBlue))); + lineMeshData.color0.put(c.set(v1.set(bRed, bGreen, bBlue))); + lineMeshData.color0.put(c.set(v1.set(bRed, bGreen, bBlue))); + lineMeshData.color0.put(c.set(v1.set(cRed, cGreen, cBlue))); } else { - float[] lineColor = - { - cRed, cGreen, cBlue, 0, //cap1 - cRed, cGreen, cBlue, 0, - cRed, cGreen, cBlue, a, - cRed, cGreen, cBlue, a, - cRed, cGreen, cBlue, 0, //cap2 - cRed, cGreen, cBlue, 0, - cRed, cGreen, cBlue, a, - cRed, cGreen, cBlue, a - }; - GL11.glColorPointer(4, 0, 0, wrap(lineColor)); - } - GL11.glDrawArrays(GL11.GL_TRIANGLE_STRIP, 0, 4); - GL11.glDrawArrays(GL11.GL_TRIANGLE_STRIP, 4, 4); + lineMeshData.color0.put(c.set(v2.set(cRed, cGreen, cBlue, 0))); + lineMeshData.color0.put(c.set(v2.set(cRed, cGreen, cBlue, 0))); + lineMeshData.color0.put(c.set(v2.set(cRed, cGreen, cBlue, a))); + lineMeshData.color0.put(c.set(v2.set(cRed, cGreen, cBlue, a))); + lineMeshData.color0.put(c.set(v2.set(cRed, cGreen, cBlue, 0))); + lineMeshData.color0.put(c.set(v2.set(cRed, cGreen, cBlue, 0))); + lineMeshData.color0.put(c.set(v2.set(cRed, cGreen, cBlue, a))); + } + lineMesh.reload(lineMeshData); + lineMesh.render(); } - GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY); - GL11.glDisableClientState(GL11.GL_COLOR_ARRAY); GL11.glEnable(GL11.GL_CULL_FACE); } - private static FloatBuffer wrap(float[] data) { - FloatBuffer buf = BufferUtils.createFloatBuffer(data.length); - buf.put(data); - buf.rewind(); - return buf; - } } diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/internal/LwjglCanvasRenderer.java b/engine/src/main/java/org/terasology/engine/rendering/nui/internal/LwjglCanvasRenderer.java index 2041740fcd2..a2a2650afb2 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/internal/LwjglCanvasRenderer.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/internal/LwjglCanvasRenderer.java @@ -68,6 +68,7 @@ public class LwjglCanvasRenderer implements TerasologyCanvasRenderer, PropertyCh private Mesh billboard; private Material textureMat; + private Material fillMaterial; private final FontMeshBuilder fontMeshBuilder; @@ -91,11 +92,18 @@ public class LwjglCanvasRenderer implements TerasologyCanvasRenderer, PropertyCh private Matrix4f projMatrix = new Matrix4f(); public LwjglCanvasRenderer(Context context) { + // TODO use context to get assets instead of static methods this.textureMat = Assets.getMaterial("engine:UITexture").orElseThrow( // Extra attention to how this is reported because it's often the first texture // engine tries to load; the build is probably broken. () -> new RuntimeException("Failing to find engine textures")); + + this.fillMaterial = Assets.getMaterial("engine:white").orElseThrow( + // Extra attention to how this is reported because it's often the first texture + // engine tries to load; the build is probably broken. + () -> new RuntimeException("Failing to find engine textures")); + this.billboard = Assets.getMesh("engine:UIBillboard").get(); this.fontMeshBuilder = new FontMeshBuilder(context.get(AssetManager.class).getAsset("engine:UIUnderline", Material.class).get()); @@ -209,8 +217,8 @@ public void drawMesh(Mesh mesh, Material material, Rectanglei drawRegion, Rectan } @Override - public org.joml.Vector2i getTargetSize() { - return new org.joml.Vector2i(displayDevice.getWidth(), displayDevice.getHeight()); + public Vector2i getTargetSize() { + return new Vector2i(displayDevice.getWidth(), displayDevice.getHeight()); } @Override @@ -228,6 +236,9 @@ public void drawMaterialAt(Material material, Rectanglei drawRegion) { @Override public void drawLine(int sx, int sy, int ex, int ey, Colorc color) { + fillMaterial.setMatrix4("projectionMatrix", projMatrix); + fillMaterial.setMatrix4("modelViewMatrix", modelMatrixStack); + fillMaterial.enable(); LineRenderer.draw(sx, sy, ex, ey, 2, color, color, 0); } diff --git a/engine/src/main/java/org/terasology/engine/rendering/opengl/GLSLMaterial.java b/engine/src/main/java/org/terasology/engine/rendering/opengl/GLSLMaterial.java index bc5123eca44..3a65a2d0bc3 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/opengl/GLSLMaterial.java +++ b/engine/src/main/java/org/terasology/engine/rendering/opengl/GLSLMaterial.java @@ -103,6 +103,7 @@ public void bindTextures() { for (int slot : textureMap.keys()) { Texture texture = textureMap.get(slot); if (texture.isDisposed()) { + textureMap.remove(slot); logger.error("Attempted to bind disposed texture {}", texture); } else { shaderManager.bindTexture(slot, texture); diff --git a/engine/src/main/java/org/terasology/engine/rendering/opengl/OpenGLMesh.java b/engine/src/main/java/org/terasology/engine/rendering/opengl/OpenGLMesh.java index 6a63dab7f19..8266cd466e6 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/opengl/OpenGLMesh.java +++ b/engine/src/main/java/org/terasology/engine/rendering/opengl/OpenGLMesh.java @@ -79,7 +79,11 @@ public void render() { if (!isDisposed()) { updateState(state); GL30.glBindVertexArray(disposalAction.vao); - GL30.glDrawElements(drawMode.glCall, this.indexCount, GL_UNSIGNED_INT, 0); + if(this.indexCount == 0) { + GL30.glDrawArrays(drawMode.glCall, 0, positions.elements()); + } else { + GL30.glDrawElements(drawMode.glCall, this.indexCount, GL_UNSIGNED_INT, 0); + } GL30.glBindVertexArray(0); } else { logger.error("Attempted to render disposed mesh: {}", getUrn()); diff --git a/engine/src/main/java/org/terasology/engine/rendering/primitives/BlockMeshGeneratorSingleShape.java b/engine/src/main/java/org/terasology/engine/rendering/primitives/BlockMeshGeneratorSingleShape.java index 95f8430233a..0cdd12a4104 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/primitives/BlockMeshGeneratorSingleShape.java +++ b/engine/src/main/java/org/terasology/engine/rendering/primitives/BlockMeshGeneratorSingleShape.java @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 package org.terasology.engine.rendering.primitives; +import org.joml.Vector3i; import org.joml.Vector3ic; import org.terasology.gestalt.assets.ResourceUrn; import org.terasology.engine.math.Side; @@ -12,6 +13,8 @@ import org.terasology.engine.world.block.BlockManager; import org.terasology.engine.world.block.BlockPart; import org.terasology.engine.world.block.shapes.BlockMeshPart; +import org.terasology.nui.Color; +import org.terasology.nui.Colorc; public class BlockMeshGeneratorSingleShape implements BlockMeshGenerator { @@ -25,6 +28,9 @@ public BlockMeshGeneratorSingleShape(Block block) { @Override public void generateChunkMesh(ChunkView view, ChunkMesh chunkMesh, int x, int y, int z) { final Block selfBlock = view.getBlock(x, y, z); + Vector3i pos = new Vector3i(x, y, z); + pos = view.toWorldPos(pos); + Color colorCache = new Color(); // Gather adjacent blocks Block[] adjacentBlocks = new Block[Side.values().length]; @@ -33,15 +39,15 @@ public void generateChunkMesh(ChunkView view, ChunkMesh chunkMesh, int x, int y, Block blockToCheck = view.getBlock(x + offset.x(), y + offset.y(), z + offset.z()); adjacentBlocks[side.ordinal()] = blockToCheck; } + + final ChunkMesh.RenderType renderType = getRenderType(selfBlock); + final BlockAppearance blockAppearance = selfBlock.getPrimaryAppearance(); + final ChunkVertexFlag vertexFlag = getChunkVertexFlag(view, x, y, z, selfBlock); + boolean isRendered = false; + for (final Side side : Side.values()) { if (isSideVisibleForBlockTypes(adjacentBlocks[side.ordinal()], selfBlock, side)) { - final ChunkMesh.RenderType renderType = getRenderType(selfBlock); - final BlockAppearance blockAppearance = selfBlock.getPrimaryAppearance(); - final ChunkVertexFlag vertexFlag = getChunkVertexFlag(view, x, y, z, selfBlock); - - if (blockAppearance.getPart(BlockPart.CENTER) != null) { - blockAppearance.getPart(BlockPart.CENTER).appendTo(chunkMesh, view, x, y, z, renderType, vertexFlag); - } + isRendered = true; BlockMeshPart blockMeshPart = blockAppearance.getPart(BlockPart.fromSide(side)); @@ -70,10 +76,26 @@ public void generateChunkMesh(ChunkView view, ChunkMesh chunkMesh, int x, int y, if (selfBlock.isGrass() && side != Side.TOP && side != Side.BOTTOM) { sideVertexFlag = ChunkVertexFlag.COLOR_MASK; } - blockMeshPart.appendTo(chunkMesh, view, x, y, z, renderType, sideVertexFlag); + Colorc colorOffset = selfBlock.getColorOffset(BlockPart.fromSide(side)); + Colorc colorSource = selfBlock.getColorSource(BlockPart.fromSide(side)).calcColor(pos.x, pos.y, pos.z); + colorCache.setRed(colorSource.rf() * colorOffset.rf()) + .setGreen(colorSource.gf() * colorOffset.gf()) + .setBlue(colorSource.bf() * colorOffset.bf()) + .setAlpha(colorSource.af() * colorOffset.af()); + blockMeshPart.appendTo(chunkMesh, view, x, y, z, renderType, colorCache, sideVertexFlag); } } } + + if (isRendered && blockAppearance.getPart(BlockPart.CENTER) != null) { + Colorc colorOffset = selfBlock.getColorOffset(BlockPart.CENTER); + Colorc colorSource = selfBlock.getColorSource(BlockPart.CENTER).calcColor(pos.x, pos.y, pos.z); + colorCache.setRed(colorSource.rf() * colorOffset.rf()) + .setGreen(colorSource.gf() * colorOffset.gf()) + .setBlue(colorSource.bf() * colorOffset.bf()) + .setAlpha(colorSource.af() * colorOffset.af()); + blockAppearance.getPart(BlockPart.CENTER).appendTo(chunkMesh, view, x, y, z, renderType, colorCache, vertexFlag); + } } private ChunkVertexFlag getChunkVertexFlag(ChunkView view, int x, int y, int z, Block selfBlock) { diff --git a/engine/src/main/java/org/terasology/engine/rendering/primitives/ChunkMesh.java b/engine/src/main/java/org/terasology/engine/rendering/primitives/ChunkMesh.java index 5bbb3c5a65e..2802e433c1b 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/primitives/ChunkMesh.java +++ b/engine/src/main/java/org/terasology/engine/rendering/primitives/ChunkMesh.java @@ -17,6 +17,8 @@ import org.terasology.engine.rendering.assets.mesh.resource.VertexResource; import org.terasology.engine.rendering.assets.mesh.resource.VertexResourceBuilder; import org.terasology.gestalt.module.sandbox.API; +import org.terasology.nui.Color; +import org.terasology.nui.Colorc; import java.util.concurrent.locks.ReentrantLock; @@ -312,6 +314,8 @@ public static class VertexElements { public static final int BLOCK_INDEX = 6; // float public static final int AMBIENT_OCCLUSION_INDEX = 7; // float + public static final int COLOR_INDEX = 8; // vec4 + public final VertexResource buffer; public final IndexResource indices = new IndexResource(); @@ -319,8 +323,7 @@ public static class VertexElements { public final VertexAttributeBinding normals; public final VertexAttributeBinding uv0; - // color data is unused something to consider later - // public final VertexAttributeBinding color; + public final VertexAttributeBinding color; public final VertexIntegerAttributeBinding flags; public final VertexIntegerAttributeBinding frames; @@ -344,6 +347,8 @@ public static class VertexElements { blockLight = builder.add(BLOCK_INDEX, GLAttributes.FLOAT_1_VERTEX_ATTRIBUTE); ambientOcclusion = builder.add(AMBIENT_OCCLUSION_INDEX, GLAttributes.FLOAT_1_VERTEX_ATTRIBUTE); + color = builder.add(COLOR_INDEX, GLAttributes.COLOR_4_F_VERTEX_ATTRIBUTE); + buffer = builder.build(); } } diff --git a/engine/src/main/java/org/terasology/engine/rendering/primitives/Tessellator.java b/engine/src/main/java/org/terasology/engine/rendering/primitives/Tessellator.java index b8c18b28d74..6d3dde87b94 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/primitives/Tessellator.java +++ b/engine/src/main/java/org/terasology/engine/rendering/primitives/Tessellator.java @@ -99,8 +99,8 @@ private void addMeshPart(BlockMeshPart part, boolean doubleSided) { } if (doubleSided) { for (int i = 0; i < part.indicesSize(); i += 3) { - meshData.indices.put(nextIndex + part.getIndex(i)); meshData.indices.put(nextIndex + part.getIndex(i + 1)); + meshData.indices.put(nextIndex + part.getIndex(i)); meshData.indices.put(nextIndex + part.getIndex(i + 2)); } } diff --git a/engine/src/main/java/org/terasology/engine/rendering/world/WorldRendererImpl.java b/engine/src/main/java/org/terasology/engine/rendering/world/WorldRendererImpl.java index 59eaa54f373..e4c541a446c 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/world/WorldRendererImpl.java +++ b/engine/src/main/java/org/terasology/engine/rendering/world/WorldRendererImpl.java @@ -334,10 +334,6 @@ private void preRenderUpdate(RenderingStage renderingStage) { */ @Override public void render(RenderingStage renderingStage) { - // If no rendering module populated renderGraph, throw an exception. - /* if (renderGraph.getNodeMapSize() < 1) { - throw new RuntimeException("Render graph is not ready to render. Did you use a rendering module?"); - } */ preRenderUpdate(renderingStage); @@ -350,9 +346,6 @@ public void render(RenderingStage renderingStage) { glDisable(GL_CULL_FACE); FBO lastUpdatedGBuffer = displayResolutionDependentFbo.getGBufferPair().getLastUpdatedFbo(); glViewport(0, 0, lastUpdatedGBuffer.width(), lastUpdatedGBuffer.height()); - // glDisable(GL_DEPTH_TEST); - // glDisable(GL_NORMALIZE); // currently keeping these as they are, until we find where they are used. - // glDepthFunc(GL_LESS); renderPipelineTaskList.forEach(RenderPipelineTask::process); diff --git a/engine/src/main/java/org/terasology/engine/utilities/procedural/PerlinNoise.java b/engine/src/main/java/org/terasology/engine/utilities/procedural/PerlinNoise.java index 9febe18611d..1c096c03263 100644 --- a/engine/src/main/java/org/terasology/engine/utilities/procedural/PerlinNoise.java +++ b/engine/src/main/java/org/terasology/engine/utilities/procedural/PerlinNoise.java @@ -6,12 +6,22 @@ import org.terasology.math.TeraMath; /** - * Improved Perlin noise based on the reference implementation by Ken Perlin. - * @deprecated Prefer using {@link SimplexNoise}, it is comparable to Perlin noise - * (fewer directional artifacts, lower computational overhead for higher dimensions). + * Domain-rotated Perlin noise. Based on reference implementation by Ken Perlin, with domain rotation by K.jpg * + * Perlin noise is an older form of noise designed without isotropy in mind. It produces significant square-aligned + * directional artifacts, when evaluated on planes aligned to its internal coordinate grid. However, when used for + * 2D-focused use-cases of 3D such as flat-world voxel terrain with overhangs, its results can be greatly improved + * by using a rotated coordinate space. Here in this implementation, Y is rotated to point up the main diagonal of + * the noise grid, while X and Z span the planes perpendicular to that. Square bias is effectively hidden, and + * visible directional bias is greatly reduced. + * + * It is worthwhile to note that this technique results in different input coordinates being differently useful + * for different purposes. When using the noise in 2D-based 3D voxel world, Y should be the vertical direction. + * For a 2D animation, Y should be the time variable. To generate 2D-only noise, use X and Z only, with Y fixed. + * This enables best utilization of the orientation of the noise grid. + * + * Note that this technique requires the 3D noise to produce nice 2D results. */ -@Deprecated public class PerlinNoise extends AbstractNoise implements Noise2D, Noise3D { private final int[] noisePermutations; @@ -28,16 +38,14 @@ public PerlinNoise(long seed) { /** * Init. a new generator with a given seed value and grid dimension. - * Supports tileable noise generation * * @param seed The seed value - * @param gridDim gridDim x gridDim will be the size of the perlin's grid of vectors, - * noise will be tiled if an input coordinate crosses a multiple of gridDim + * @param permCount The size of the permutation table. */ - public PerlinNoise(long seed, int gridDim) { + public PerlinNoise(long seed, int permCount) { FastRandom rand = new FastRandom(seed); - permCount = gridDim; + this.permCount = permCount; noisePermutations = new int[permCount * 2]; int[] noiseTable = new int[permCount]; @@ -63,22 +71,41 @@ public PerlinNoise(long seed, int gridDim) { } /** - * Returns the noise value at the given position. + * Returns the domain-rotated noise value at the given position. + * If generating noise with a clearly defined vertical direction, assign that to Y. {@code noise(horz0, vert, horz1)} + * If generating noise to animate a 2D plane, use Y as your time variable. {@code noise(horz0, time, horz1)} + * If generating 2D-only noise, use X and Z, with Y set to a constant value. {@code noise(horz0, const, horz1)} + * Y should always be the "different" direction in whatever your use case is. + * The way the noise changes along Y is slightly different from its behavior in X/Z. * - * @param posX Position on the x-axis - * @param posY Position on the y-axis - * @param posZ Position on the z-axis + * @param posX Position on the x-axis (horizontal) + * @param posY Position on the y-axis (vertical, time, or fixed) + * @param posZ Position on the z-axis (horizontal) * @return The noise value */ @Override public float noise(float posX, float posY, float posZ) { - int xInt = Math.floorMod(TeraMath.floorToInt(posX), permCount); - int yInt = Math.floorMod(TeraMath.floorToInt(posY), permCount); - int zInt = Math.floorMod(TeraMath.floorToInt(posZ), permCount); - float x = posX - TeraMath.fastFloor(posX); - float y = posY - TeraMath.fastFloor(posY); - float z = posZ - TeraMath.fastFloor(posZ); + // Domain rotation removes Perlin's characteristic square artifacts from the XZ planes, by pointing Y up the grid's main diagonal. + // Ordinarily, X can be said to move in the unit vector direction <1, 0, 0>, Y in <0, 1, 0>, and Z in <0, 0, 1>. With this rotation, + // moving along the input for Y now moves in the unit direction <0.577, 0.577, 0.577> in the noise's internal coordinate space. + // Perpendicular to that, X and Z move in the directions <0.789, -0.577, -0.211> and <-0.211, -0.577, 0.789>. These vectors form a + // rotation matrix. The code is a simplification of the multiplication of this rotation matrix by the input coordinate, taking + // advantage of the many repetitions of 0.577 and the fact that 0.789 = 1-0.211. + float xz = posX + posZ; + float s2 = xz * -0.211324865405187f; + float yy = posY * 0.577350269189626f; + float rPosX = posX + (s2 + yy); + float rPosY = xz * -0.577350269189626f + yy; + float rPosZ = posZ + (s2 + yy); + + int xInt = Math.floorMod(TeraMath.floorToInt(rPosX), permCount); + int yInt = Math.floorMod(TeraMath.floorToInt(rPosY), permCount); + int zInt = Math.floorMod(TeraMath.floorToInt(rPosZ), permCount); + + float x = rPosX - TeraMath.fastFloor(rPosX); + float y = rPosY - TeraMath.fastFloor(rPosY); + float z = rPosZ - TeraMath.fastFloor(rPosZ); float u = TeraMath.fadePerlin(x); float v = TeraMath.fadePerlin(y); diff --git a/engine/src/main/java/org/terasology/engine/world/block/Block.java b/engine/src/main/java/org/terasology/engine/world/block/Block.java index 97884a24d9d..351342a9eac 100644 --- a/engine/src/main/java/org/terasology/engine/world/block/Block.java +++ b/engine/src/main/java/org/terasology/engine/world/block/Block.java @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 package org.terasology.engine.world.block; +import com.google.common.collect.Maps; import org.joml.Quaternionf; import org.joml.RoundingMode; import org.joml.Vector3f; @@ -23,6 +24,8 @@ import org.terasology.gestalt.assets.ResourceUrn; import org.terasology.joml.geom.AABBf; import org.terasology.math.TeraMath; +import org.terasology.nui.Color; +import org.terasology.nui.Colorc; import java.util.Map; import java.util.Optional; @@ -62,6 +65,9 @@ public final class Block { private boolean waving; private byte luminance; private Vector3f tint = new Vector3f(0, 0, 0); + private Map colorSource = Maps.newEnumMap(BlockPart.class); + private Map colorOffsets = Maps.newEnumMap(BlockPart.class); + // Collision related private boolean penetrable; @@ -93,6 +99,16 @@ public final class Block { private Vector3f collisionOffset; private AABBf bounds = new AABBf(); + /** + * Init. a new block with default properties in place. + */ + public Block() { + for (BlockPart part : BlockPart.values()) { + colorSource.put(part, DefaultColorSource.DEFAULT); + colorOffsets.put(part, Color.white); + } + } + public short getId() { return id; } @@ -478,6 +494,33 @@ public void setPrimaryAppearance(BlockAppearance appearence) { this.primaryAppearance = appearence; } + public BlockColorSource getColorSource(BlockPart part) { + return colorSource.get(part); + } + + public void setColorSource(BlockColorSource colorSource) { + for (BlockPart part : BlockPart.values()) { + this.colorSource.put(part, colorSource); + } + } + + public void setColorSource(BlockPart part, BlockColorSource value) { + this.colorSource.put(part, value); + } + + public Colorc getColorOffset(BlockPart part) { + return colorOffsets.get(part); + } + + public void setColorOffset(BlockPart part, Colorc color) { + colorOffsets.put(part, color); + } + + public void setColorOffsets(Colorc color) { + for (BlockPart part : BlockPart.values()) { + colorOffsets.put(part, color); + } + } /** * @return Standalone mesh diff --git a/engine/src/main/java/org/terasology/engine/world/block/BlockColorSource.java b/engine/src/main/java/org/terasology/engine/world/block/BlockColorSource.java new file mode 100644 index 00000000000..45a2250e95f --- /dev/null +++ b/engine/src/main/java/org/terasology/engine/world/block/BlockColorSource.java @@ -0,0 +1,24 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.engine.world.block; + +import org.terasology.nui.Colorc; + +/** + * Used to determine a multiplicative color for certain blocks based on the block's world conditions. + */ +@FunctionalInterface +public interface BlockColorSource { + + default Colorc calcColor() { + return calcColor(0, 0, 0); + }; + + default Colorc calcColor(int x, int z) { + return calcColor(x, 0, z); + }; + + Colorc calcColor(int x, int y, int z); + +} diff --git a/engine/src/main/java/org/terasology/engine/world/block/ColorProvider.java b/engine/src/main/java/org/terasology/engine/world/block/ColorProvider.java new file mode 100644 index 00000000000..f4953d94ac2 --- /dev/null +++ b/engine/src/main/java/org/terasology/engine/world/block/ColorProvider.java @@ -0,0 +1,21 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.engine.world.block; + +import org.terasology.nui.Colorc; + +/** + * A system for looking up foliage and grass colors at a block, used by DefaultColorSource. + */ +public interface ColorProvider { + /** + * Looks up the color for grass at a given position. + */ + Colorc colorLut(int x, int y, int z); + + /** + * Looks up the color for foliage at a given position. + */ + Colorc foliageLut(int x, int y, int z); +} diff --git a/engine/src/main/java/org/terasology/engine/world/block/DefaultColorSource.java b/engine/src/main/java/org/terasology/engine/world/block/DefaultColorSource.java new file mode 100644 index 00000000000..593f683ecaf --- /dev/null +++ b/engine/src/main/java/org/terasology/engine/world/block/DefaultColorSource.java @@ -0,0 +1,39 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.engine.world.block; + +import org.terasology.engine.registry.CoreRegistry; +import org.terasology.nui.Color; +import org.terasology.nui.Colorc; + +public enum DefaultColorSource implements BlockColorSource { + DEFAULT { + @Override + public Colorc calcColor(int x, int y, int z) { + return Color.white; + } + }, + COLOR_LUT { + @Override + public Colorc calcColor(int x, int y, int z) { + ColorProvider colorProvider = CoreRegistry.get(ColorProvider.class); + // Return white as default if there aren't any color providers + if (colorProvider == null) { + return Color.white; + } + return colorProvider.colorLut(x, y, z); + } + }, + FOLIAGE_LUT { + @Override + public Colorc calcColor(int x, int y, int z) { + ColorProvider colorProvider = CoreRegistry.get(ColorProvider.class); + // Return white as default if there aren't any color providers + if (colorProvider == null) { + return Color.white; + } + return colorProvider.foliageLut(x, y, z); + } + }; +} diff --git a/engine/src/main/java/org/terasology/engine/world/block/internal/BlockBuilder.java b/engine/src/main/java/org/terasology/engine/world/block/internal/BlockBuilder.java index d1eb584dbd4..41b574cd05a 100644 --- a/engine/src/main/java/org/terasology/engine/world/block/internal/BlockBuilder.java +++ b/engine/src/main/java/org/terasology/engine/world/block/internal/BlockBuilder.java @@ -21,6 +21,7 @@ import org.terasology.engine.world.block.shapes.BlockShape; import org.terasology.engine.world.block.tiles.BlockTile; import org.terasology.engine.world.block.tiles.WorldAtlas; +import org.terasology.nui.Color; import java.util.Map; @@ -125,6 +126,11 @@ public Block constructCustomBlock(String defaultName, BlockShape shape, Rotation setBlockFullSides(block, shape, rotation); block.setCollision(shape.getCollisionOffset(rotation), shape.getCollisionShape(rotation)); + for (BlockPart part : BlockPart.values()) { + block.setColorSource(part, section.getColorSources().get(part)); + block.setColorOffset(part, new Color().set(section.getColorOffsets().get(part))); + } + block.setUri(uri); block.setBlockFamily(blockFamily); diff --git a/engine/src/main/java/org/terasology/engine/world/block/loader/BlockFamilyDefinitionFormat.java b/engine/src/main/java/org/terasology/engine/world/block/loader/BlockFamilyDefinitionFormat.java index 2bdcfd44a1a..6ffd487c2ff 100644 --- a/engine/src/main/java/org/terasology/engine/world/block/loader/BlockFamilyDefinitionFormat.java +++ b/engine/src/main/java/org/terasology/engine/world/block/loader/BlockFamilyDefinitionFormat.java @@ -19,6 +19,7 @@ import com.google.gson.stream.JsonWriter; import org.joml.Vector3f; import org.joml.Vector4f; +import org.terasology.engine.world.block.DefaultColorSource; import org.terasology.gestalt.assets.Asset; import org.terasology.gestalt.assets.ResourceUrn; import org.terasology.engine.entitySystem.prefab.Prefab; @@ -183,6 +184,8 @@ private void deserializeSectionDefinitionData(SectionDefinitionData data, JsonOb setObject(data::setTint, jsonObject, "tint", Vector3f.class, context); readBlockPartMap(jsonObject, "tile", "tiles", data::getBlockTiles, BlockTile.class, context); + readBlockPartMap(jsonObject, "colorSource", "colorSources", data::getColorSources, DefaultColorSource.class, context); + readBlockPartMap(jsonObject, "colorOffset", "colorOffsets", data::getColorOffsets, Vector4f.class, context); setFloat(data::setMass, jsonObject, "mass"); setBoolean(data::setDebrisOnDestroy, jsonObject, "debrisOnDestroy"); diff --git a/engine/src/main/java/org/terasology/engine/world/block/loader/SectionDefinitionData.java b/engine/src/main/java/org/terasology/engine/world/block/loader/SectionDefinitionData.java index 308af84c951..8a343d3eaf3 100644 --- a/engine/src/main/java/org/terasology/engine/world/block/loader/SectionDefinitionData.java +++ b/engine/src/main/java/org/terasology/engine/world/block/loader/SectionDefinitionData.java @@ -4,6 +4,8 @@ import com.google.common.collect.Maps; import org.joml.Vector3f; +import org.joml.Vector4f; +import org.terasology.engine.world.block.DefaultColorSource; import org.terasology.engine.world.block.shapes.BlockShape; import org.terasology.engine.world.block.sounds.BlockSounds; import org.terasology.gestalt.module.sandbox.API; @@ -38,6 +40,9 @@ public class SectionDefinitionData { private Vector3f tint = new Vector3f(); private EnumMap blockTiles = Maps.newEnumMap(BlockPart.class); + private EnumMap colorSources; + // Vector4f is used because it's deserializable, it will be converted into a Color later + private EnumMap colorOffsets; private float mass = 10f; private boolean debrisOnDestroy = true; @@ -53,6 +58,12 @@ public class SectionDefinitionData { private boolean ice; public SectionDefinitionData() { + colorSources = Maps.newEnumMap(BlockPart.class); + colorOffsets = Maps.newEnumMap(BlockPart.class); + for (BlockPart part : BlockPart.values()) { + colorSources.put(part, DefaultColorSource.DEFAULT); + colorOffsets.put(part, new Vector4f(1, 1, 1, 1)); + } } public SectionDefinitionData(SectionDefinitionData other) { @@ -79,6 +90,8 @@ public SectionDefinitionData(SectionDefinitionData other) { this.tint = new Vector3f(other.tint); this.blockTiles = new EnumMap<>(other.blockTiles); + this.colorSources = new EnumMap<>(other.colorSources); + this.colorOffsets = new EnumMap<>(other.colorOffsets); this.mass = other.mass; this.debrisOnDestroy = other.debrisOnDestroy; @@ -241,6 +254,26 @@ public void setAllTiles(BlockTile tile) { } } + public EnumMap getColorSources() { + return colorSources; + } + + public void setAllColorSources(DefaultColorSource source) { + for (BlockPart part : BlockPart.values()) { + colorSources.put(part, source); + } + } + + public EnumMap getColorOffsets() { + return colorOffsets; + } + + public void setAllColorOffsets(Vector4f offset) { + for (BlockPart part : BlockPart.values()) { + colorOffsets.put(part, offset); + } + } + public float getMass() { return mass; } diff --git a/engine/src/main/java/org/terasology/engine/world/block/shapes/BlockMeshPart.java b/engine/src/main/java/org/terasology/engine/world/block/shapes/BlockMeshPart.java index 23ce2f8615f..516a547a7fc 100644 --- a/engine/src/main/java/org/terasology/engine/world/block/shapes/BlockMeshPart.java +++ b/engine/src/main/java/org/terasology/engine/world/block/shapes/BlockMeshPart.java @@ -12,6 +12,7 @@ import org.terasology.engine.world.ChunkView; import org.terasology.engine.world.block.Block; import org.terasology.math.TeraMath; +import org.terasology.nui.Colorc; import java.util.Arrays; @@ -80,7 +81,7 @@ public BlockMeshPart mapTexCoords(Vector2f offset, float width, int frames) { } public void appendTo(ChunkMesh chunk, ChunkView chunkView, int offsetX, int offsetY, int offsetZ, - ChunkMesh.RenderType renderType, ChunkVertexFlag flags) { + ChunkMesh.RenderType renderType, Colorc colorOffset, ChunkVertexFlag flags) { ChunkMesh.VertexElements elements = chunk.getVertexElements(renderType); for (Vector2f texCoord : texCoords) { elements.uv0.put(texCoord); @@ -90,6 +91,7 @@ public void appendTo(ChunkMesh chunk, ChunkView chunkView, int offsetX, int offs elements.buffer.reserveElements(nextIndex + vertices.length); Vector3f pos = new Vector3f(); for (int vIdx = 0; vIdx < vertices.length; ++vIdx) { + elements.color.put(colorOffset); elements.position.put(pos.set(vertices[vIdx]).add(offsetX, offsetY, offsetZ)); elements.normals.put(normals[vIdx]); elements.flags.put(flags.getValue()); diff --git a/engine/src/main/resources/org/terasology/engine/module.txt b/engine/src/main/resources/org/terasology/engine/module.txt index 06e971f1fe6..bca3a5be1b8 100644 --- a/engine/src/main/resources/org/terasology/engine/module.txt +++ b/engine/src/main/resources/org/terasology/engine/module.txt @@ -1,6 +1,6 @@ { "id" : "engine", - "version" : "5.0.0", + "version" : "5.1.0", "displayName" : "Terasology Engine", "description" : "Core engine content" }