Compare commits
30 Commits
v0.3.0
...
title-scre
Author | SHA1 | Date | |
---|---|---|---|
22a744f65a | |||
c6de19cf19 | |||
c1a57f7d7a | |||
162b2249b1
|
|||
dececb4589
|
|||
ec17eb7065
|
|||
e4d0570200
|
|||
92f9639c4b
|
|||
f350467942
|
|||
23b0af1731 | |||
0389f97e59 | |||
706800218c
|
|||
ca2c7b58d8
|
|||
359879b0fe
|
|||
a06d8ee056
|
|||
c8138faabe
|
|||
49d283e7a3
|
|||
c93f0df30d
|
|||
75ea7baf9c
|
|||
c4a9a17c7c | |||
b4ff5114bd
|
|||
efc6a8ecd0
|
|||
4b10dc82ed
|
|||
c04894a0c9 | |||
576cfed99f
|
|||
5af1b7309d | |||
0f0a94811f | |||
28b19c8f35
|
|||
51bcca1499 | |||
ce9e95e5ce |
2
.gitattributes
vendored
@ -6,4 +6,4 @@
|
|||||||
* text=auto eol=lf
|
* text=auto eol=lf
|
||||||
|
|
||||||
*.bat text eol=crlf
|
*.bat text eol=crlf
|
||||||
|
*.nsi text eol=crlf
|
||||||
|
582
build.gradle
@ -1,25 +1,31 @@
|
|||||||
/*
|
/*
|
||||||
* build.gradle for Progressia
|
* Build logic for Progressia
|
||||||
|
* build.gradle
|
||||||
|
*
|
||||||
|
* Please refer to
|
||||||
|
*
|
||||||
|
* docs/building/BuildScriptReference.md
|
||||||
|
*
|
||||||
|
* for user reference.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
// Apply the java-library plugin to add support for Java Library
|
id 'java'
|
||||||
id 'java-library'
|
|
||||||
|
// GrGit
|
||||||
/*
|
// A JGit wrapper for Groovy used to access git commit info
|
||||||
* Uncomment the following line to enable the Eclipse plugin.
|
id 'org.ajoberstar.grgit' version '4.1.1'
|
||||||
* This is only necessary if you don't use Buildship plugin from the IDE
|
|
||||||
*/
|
|
||||||
//id 'eclipse'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Configure Java version
|
||||||
|
*/
|
||||||
|
|
||||||
java {
|
java {
|
||||||
/*
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
* We're Java 8 for now.
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
* Why? As of 2020, most users have Oracle Java, which only supports Java 8.
|
|
||||||
*/
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compileJava {
|
compileJava {
|
||||||
@ -29,287 +35,373 @@ compileJava {
|
|||||||
* However, on JDK 9 and later versions, '--release' option is required,
|
* However, on JDK 9 and later versions, '--release' option is required,
|
||||||
* which is missing on JDK 8.
|
* which is missing on JDK 8.
|
||||||
*/
|
*/
|
||||||
if (JavaVersion.current() != JavaVersion.VERSION_1_8) {
|
if (JavaVersion.current() != JavaVersion.VERSION_1_8) {
|
||||||
options.compilerArgs.addAll(['--release', '8'])
|
options.compilerArgs.addAll(['--release', '8'])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dependencies
|
* Dependencies
|
||||||
*/
|
*/
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
|
||||||
|
/*
|
||||||
/*
|
* Windcorp Maven repository
|
||||||
* Specify Windcorp Maven repository
|
* Currently used by:
|
||||||
* Currently used by:
|
* - ru.windcorp.fork.io.github.java-graphics:glm:1.0.1
|
||||||
* - ru.windcorp.fork.io.github.java-graphics:glm:1.0.1
|
*/
|
||||||
*/
|
maven { url 'https://windcorp.ru/./maven' }
|
||||||
maven { url 'https://windcorp.ru/./maven' }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Google Guava
|
// Google Guava
|
||||||
// A generic utilities library
|
// A generic utilities library
|
||||||
implementation 'com.google.guava:guava:30.0-jre'
|
implementation 'com.google.guava:guava:30.0-jre'
|
||||||
|
|
||||||
// Trove4j
|
// Trove4j
|
||||||
// Provides optimized Collections for primitive types
|
// Provides optimized Collections for primitive types
|
||||||
implementation 'net.sf.trove4j:trove4j:3.0.3'
|
implementation 'net.sf.trove4j:trove4j:3.0.3'
|
||||||
|
|
||||||
// java-graphics
|
// java-graphics
|
||||||
// A GLM (OpenGL Mathematics) port to Java
|
// A GLM (OpenGL Mathematics) port to Java
|
||||||
// Unfortunately, Maven Central Repository provides an outdated version of this library, which contains several critical bugs
|
// Unfortunately, Maven Central Repository provides an outdated version of this library, which contains several critical bugs
|
||||||
implementation 'ru.windcorp.fork.io.github.java-graphics:glm:1.0.1'
|
implementation 'ru.windcorp.fork.io.github.java-graphics:glm:1.0.1'
|
||||||
|
|
||||||
// Log4j
|
// Log4j
|
||||||
// A logging library
|
// A logging library
|
||||||
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.3'
|
implementation 'org.apache.logging.log4j:log4j-api:2.17.0'
|
||||||
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.3'
|
implementation 'org.apache.logging.log4j:log4j-core:2.17.0'
|
||||||
|
|
||||||
// JUnit
|
// JUnit
|
||||||
// A unit-testing library
|
// A unit-testing library
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
|
||||||
|
// Also see LWJGL dependencies in build_logic/lwjgl.gradle
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Version resolution
|
||||||
|
*/
|
||||||
|
|
||||||
|
import org.ajoberstar.grgit.*
|
||||||
|
|
||||||
|
// Pattern: vMAJOR.MINOR.PATCH[-SUFFIX]
|
||||||
|
project.ext.tagFormat = /^v(\d+)\.(\d+)\.(\d+)(-[\w\-]*)?$/
|
||||||
|
|
||||||
|
String version_parseVersion(String tag, boolean increment) {
|
||||||
|
try {
|
||||||
|
|
||||||
// See LWJGL dependencies below
|
def data = (tag =~ tagFormat)[0]
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Progressia uses the following LWJGL libraries:
|
|
||||||
* - Core libraries
|
|
||||||
* - OpenGL
|
|
||||||
* - OpenAL
|
|
||||||
* - GLFW
|
|
||||||
* - STB
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* LWJGL
|
|
||||||
* (auto-generated script)
|
|
||||||
* ((here be dragons))
|
|
||||||
*/
|
|
||||||
|
|
||||||
import org.gradle.internal.os.OperatingSystem
|
|
||||||
|
|
||||||
project.ext.lwjglVersion = "3.2.3"
|
|
||||||
|
|
||||||
switch (OperatingSystem.current()) {
|
|
||||||
case OperatingSystem.LINUX:
|
|
||||||
def osArch = System.getProperty("os.arch")
|
|
||||||
project.ext.lwjglNatives = osArch.startsWith("arm") || osArch.startsWith("aarch64")
|
|
||||||
? "natives-linux-${osArch.contains("64") || osArch.startsWith("armv8") ? "arm64" : "arm32"}"
|
|
||||||
: "natives-linux"
|
|
||||||
break
|
|
||||||
case OperatingSystem.MAC_OS:
|
|
||||||
project.ext.lwjglNatives = "natives-macos"
|
|
||||||
break
|
|
||||||
case OperatingSystem.WINDOWS:
|
|
||||||
project.ext.lwjglNatives = System.getProperty("os.arch").contains("64") ? "natives-windows" : "natives-windows-x86"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation platform("org.lwjgl:lwjgl-bom:$lwjglVersion")
|
|
||||||
|
|
||||||
implementation "org.lwjgl:lwjgl"
|
|
||||||
implementation "org.lwjgl:lwjgl-glfw"
|
|
||||||
implementation "org.lwjgl:lwjgl-openal"
|
|
||||||
implementation "org.lwjgl:lwjgl-opengl"
|
|
||||||
implementation "org.lwjgl:lwjgl-stb"
|
|
||||||
|
|
||||||
runtimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
|
|
||||||
runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives"
|
|
||||||
runtimeOnly "org.lwjgl:lwjgl-openal::$lwjglNatives"
|
|
||||||
runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
|
|
||||||
runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
|
|
||||||
}
|
|
||||||
|
|
||||||
// LWJGL END
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Tasks
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Additional native libraries specification
|
|
||||||
*/
|
|
||||||
|
|
||||||
project.ext.platforms = new HashSet<>()
|
|
||||||
|
|
||||||
task addNativeDependencies {
|
|
||||||
doFirst {
|
|
||||||
def archs = project.ext.platforms
|
|
||||||
|
|
||||||
switch (archs.size()) {
|
def major = data[1]
|
||||||
case 0:
|
def minor = data[2]
|
||||||
println "Adding LWJGL native dependencies for local platform only:\n\t$lwjglNatives"
|
def patch = data[3] as int
|
||||||
archs.add project.ext.lwjglNatives
|
def suffix = data[4] ?: ''
|
||||||
break
|
|
||||||
case 1:
|
if (increment) {
|
||||||
println "Adding LWJGL native dependencies for platform\n\t" + archs.get(0)
|
def oldVersion = "$major.$minor.$patch$suffix"
|
||||||
break
|
patch++
|
||||||
default:
|
def newVersion = "$major.$minor.$patch$suffix"
|
||||||
println "Adding LWJGL native dependencies for platforms:\n\t" + archs.join("\n\t")
|
|
||||||
|
logger.info "Version parsed from Git: $oldVersion, incremented to $newVersion"
|
||||||
|
return newVersion
|
||||||
|
} else {
|
||||||
|
def newVersion = "$major.$minor.$patch$suffix"
|
||||||
|
logger.info "Version parsed from Git: $newVersion"
|
||||||
|
return newVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
if (project.ext.lwjglNatives.isEmpty()) println "WTF"
|
} catch (any) {
|
||||||
|
logger.warn "Could not parse version from tag \"$tag\""
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tag version_findRelevantTag(Grgit git) {
|
||||||
|
def tags = git.tag.list()
|
||||||
|
|
||||||
|
def commits = [ git.head() ]
|
||||||
|
def visited = new HashSet<>()
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (commits.isEmpty()) return null
|
||||||
|
|
||||||
|
def nextCommits = new HashSet<>()
|
||||||
|
|
||||||
dependencies {
|
def formatSpecificationPrinted = false
|
||||||
archs.each { arch ->
|
for (def commit : commits) {
|
||||||
runtimeOnly "org.lwjgl:lwjgl::$arch"
|
def tag = tags.findAll { it.commit == commit } ?.max { it.dateTime }
|
||||||
runtimeOnly "org.lwjgl:lwjgl-glfw::$arch"
|
|
||||||
runtimeOnly "org.lwjgl:lwjgl-openal::$arch"
|
if (tag != null) {
|
||||||
runtimeOnly "org.lwjgl:lwjgl-opengl::$arch"
|
if (tag.name ==~ tagFormat) {
|
||||||
runtimeOnly "org.lwjgl:lwjgl-stb::$arch"
|
return tag
|
||||||
}
|
} else {
|
||||||
}
|
if (!formatSpecificationPrinted) {
|
||||||
}
|
formatSpecificationPrinted = true
|
||||||
}
|
logger.info 'Expecting tag format: vMAJOR.MINOR.PATCH[-SUFFIX]'
|
||||||
|
}
|
||||||
compileJava.mustRunAfter addNativeDependencies // Make sure runtimeOnly has not been resolved
|
logger.info 'Ignoring tag due to invalid format: {}', tag.name
|
||||||
|
}
|
||||||
task requestLinuxDependencies {
|
}
|
||||||
description 'Adds linux, linux-arm64 and linux-arm32 native libraries to built artifacts.'
|
|
||||||
doFirst {
|
nextCommits.addAll commit.parentIds.collect(git.resolve.&toCommit)
|
||||||
project.ext.platforms.addAll(['natives-linux', 'natives-linux-arm64', 'natives-linux-arm32'])
|
}
|
||||||
}
|
|
||||||
}
|
visited.addAll commits
|
||||||
task requestWindowsDependencies {
|
nextCommits.removeAll visited
|
||||||
description 'Adds windows and windows-x86 native libraries to built artifacts.'
|
commits = nextCommits
|
||||||
doFirst {
|
|
||||||
project.ext.platforms.addAll(['natives-windows', 'natives-windows-x86'])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
task requestMacOSDependencies {
|
|
||||||
description 'Adds macos native libraries to built artifacts.'
|
|
||||||
doFirst {
|
|
||||||
project.ext.platforms.addAll(['natives-macos'])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def dependencySpecificationTasks = tasks.findAll { task -> task.name.startsWith('request') && task.name.endsWith('Dependencies') }
|
task resolveVersion {
|
||||||
|
description 'Resolves version information from Git repository or project properties.'
|
||||||
|
|
||||||
task requestCrossPlatformDependencies {
|
doFirst {
|
||||||
description 'Adds native libraries for all available platforms to built artifacts.'
|
try {
|
||||||
dependsOn dependencySpecificationTasks
|
def git = Grgit.open(dir: project.projectDir)
|
||||||
}
|
|
||||||
|
project.ext.commit = git.head().id
|
||||||
addNativeDependencies.mustRunAfter dependencySpecificationTasks
|
project.ext.branch = git.branch.current().name
|
||||||
|
|
||||||
/*
|
if (project.version != 'unspecified') {
|
||||||
* Determines if the provided dependency should be packaged
|
// Leave version as-is
|
||||||
*/
|
return
|
||||||
def isDependencyRequested(String dep) {
|
}
|
||||||
if (dep.endsWith(".jar")) {
|
|
||||||
dep = dep.substring(0, dep.length() - ".jar".length())
|
def tag = version_findRelevantTag(git)
|
||||||
|
if (tag == null) {
|
||||||
|
|
||||||
|
String suffix
|
||||||
|
if (project.hasProperty('buildId')) {
|
||||||
|
suffix = project.buildId;
|
||||||
|
} else {
|
||||||
|
suffix = java.time.ZonedDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern('yyyy_MM_dd'))
|
||||||
|
}
|
||||||
|
project.version = "999.0.0-$suffix"
|
||||||
|
|
||||||
|
logger.warn 'Git repository does not contain an applicable tag, using dummy version {}\nSpecify version with -Pversion=1.2.3 or create a Git tag named v1.2.3', project.version
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
project.version = version_parseVersion(tag.name, tag.commit != git.head())
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (org.eclipse.jgit.errors.RepositoryNotFoundException e) {
|
||||||
|
if (project.version == 'unspecified') project.version = 'dev'
|
||||||
|
project.ext.commit = '-'
|
||||||
|
project.ext.branch = '-'
|
||||||
|
|
||||||
|
logger.warn 'No Git repository found in project root, using dummy version {}\nSpecify version with -Pversion=1.2.3 or create a Git tag named v1.2.3', project.version
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !dep.contains("natives-") ||
|
doLast {
|
||||||
project.ext.platforms.contains(dep.substring(dep.indexOf("natives-"), dep.length()))
|
if (!project.hasProperty('buildId')) {
|
||||||
|
project.ext.buildId = '-'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Manifest specification
|
* Configure JAR manifest
|
||||||
*/
|
*/
|
||||||
|
|
||||||
task specifyLocalManifest {
|
task configureManifest {
|
||||||
dependsOn addNativeDependencies // Make sure all native dependencies are specified
|
description 'Populates JAR manifest with Main-Class, Class-Path and version metadata.'
|
||||||
|
|
||||||
|
jar.dependsOn configureManifest
|
||||||
|
dependsOn resolveVersion
|
||||||
|
|
||||||
doFirst {
|
doFirst {
|
||||||
def classPath = []
|
jar.manifest.attributes(
|
||||||
|
'Main-Class': 'ru.windcorp.progressia.client.ProgressiaClientMain',
|
||||||
configurations.runtimeClasspath.each {
|
'Class-Path': configurations.runtimeClasspath.collect { "lib/${it.name}" } .join(' '),
|
||||||
if (isDependencyRequested(it.getName())) {
|
|
||||||
classPath.add("lib/" + it.getName())
|
'Specification-Title': 'Progressia',
|
||||||
} else {
|
|
||||||
println "\tRemoving from JAR classpath (not requested): " + it.getName()
|
'Implementation-Title': 'Progressia',
|
||||||
|
'Implementation-Version': project.version,
|
||||||
|
'Implementation-Version-Git-Commit': project.commit,
|
||||||
|
'Implementation-Version-Git-Branch': project.branch,
|
||||||
|
'Implementation-Version-BuildId': project.buildId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copy libraries into buil/libs/lib directory, next to Progressia.jar
|
||||||
|
*/
|
||||||
|
|
||||||
|
task exportLibs(type: Sync) {
|
||||||
|
description 'Copies runtime libraries into a subdirectory next to the output JAR.'
|
||||||
|
|
||||||
|
jar.dependsOn exportLibs
|
||||||
|
|
||||||
|
from configurations.runtimeClasspath
|
||||||
|
into 'build/libs/lib'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Apply LWJGL logic
|
||||||
|
*/
|
||||||
|
apply from: 'build_logic/lwjgl.gradle'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Packaging working directory configuration
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.nio.file.*
|
||||||
|
import java.nio.file.attribute.*
|
||||||
|
|
||||||
|
task createPackagingDirs() {
|
||||||
|
description 'Resets build/tmp/packaging directory.'
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
def tmpDir = buildDir.toPath().resolve 'tmp/packaging'
|
||||||
|
|
||||||
|
def nuke = new SimpleFileVisitor<Path>() {
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
|
Files.delete(file);
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
|
||||||
|
if (e == null) {
|
||||||
|
Files.delete(dir);
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
} else {
|
||||||
|
// directory iteration failed
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (classPath.size() == configurations.runtimeClasspath.size()) {
|
// Fckn nuke tmpDir from orbit
|
||||||
println "Nothing removed from JAR classpath"
|
// I'm so done with deleting file trees in Java/Groovy/whatever
|
||||||
|
// ...Not using File.deleteDir() because the latter recurses into symlinks, and we don't want to wipe build/libs/lib
|
||||||
|
if (Files.exists(tmpDir)) {
|
||||||
|
Files.walkFileTree tmpDir, nuke
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Files.createDirectories tmpDir.resolve('workingDir')
|
||||||
|
Files.createDirectories buildDir.toPath().resolve('packages')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task linkBuildOutputForPackaging() {
|
||||||
|
description 'Symlinks the contents of build/libs into packaging working directory.'
|
||||||
|
|
||||||
jar {
|
dependsOn build
|
||||||
manifest {
|
dependsOn createPackagingDirs
|
||||||
attributes(
|
|
||||||
"Main-Class": "ru.windcorp.progressia.client.ProgressiaClientMain",
|
onlyIf { preparePackaging.ext.mode == 'symlink' }
|
||||||
"Class-Path": configurations.runtimeClasspath.collect { "lib/" + it.getName() } .findAll { isDependencyRequested(it) } .join(' ')
|
|
||||||
)
|
doLast {
|
||||||
}
|
def from = buildDir.toPath().resolve 'libs'
|
||||||
}
|
def into = buildDir.toPath().resolve "tmp/packaging/workingDir/${preparePackaging.ext.buildDest}"
|
||||||
|
|
||||||
|
Files.createDirectories into
|
||||||
|
|
||||||
|
Files.list(from).each {
|
||||||
|
def fileName = it.fileName.toString()
|
||||||
|
|
||||||
|
// Exclude all JARs except the current one
|
||||||
|
if (fileName ==~ "${project.name}.*\\.jar" && fileName != tasks.jar.archiveFileName.get())
|
||||||
|
return
|
||||||
|
|
||||||
|
Files.createSymbolicLink into.resolve(it.fileName), it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task copyBuildOutputForPackaging(type: Copy) {
|
||||||
|
description 'Copies the contents of build/libs into packaging working directory.'
|
||||||
|
|
||||||
|
dependsOn build
|
||||||
|
dependsOn createPackagingDirs
|
||||||
|
|
||||||
|
onlyIf { preparePackaging.ext.mode == 'copy' }
|
||||||
|
|
||||||
|
from 'build/libs'
|
||||||
|
filesMatching("${project.name}*.jar") {
|
||||||
|
include tasks.jar.archiveFileName.get()
|
||||||
|
}
|
||||||
|
into "build/tmp/packaging/workingDir/${ -> preparePackaging.ext.buildDest}"
|
||||||
|
}
|
||||||
|
|
||||||
|
task preparePackaging {
|
||||||
|
preparePackaging.ext.buildDest = ''
|
||||||
|
preparePackaging.ext.mode = 'symlink'
|
||||||
|
|
||||||
|
dependsOn createPackagingDirs
|
||||||
|
dependsOn linkBuildOutputForPackaging
|
||||||
|
dependsOn copyBuildOutputForPackaging
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Apply all packaging scripts
|
||||||
|
*/
|
||||||
|
|
||||||
|
new File(projectDir, 'build_logic/packaging').list().each {
|
||||||
|
apply from: "build_logic/packaging/$it/script.gradle"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure no more than one packaging task is scheduled
|
||||||
|
*/
|
||||||
|
|
||||||
|
gradle.taskGraph.whenReady { graph ->
|
||||||
|
if (graph.allTasks.count { it.name ==~ /package[^_]*/ } > 1) {
|
||||||
|
def offenders = graph.allTasks.findAll { it.name ==~ /package[^_]*/ }
|
||||||
|
throw new GradleException("Cannot execute multiple package tasks within a single build\n" +
|
||||||
|
"\tOffending tasks: $offenders")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jar.dependsOn specifyLocalManifest
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Library export
|
* Convenience build tasks
|
||||||
*/
|
*/
|
||||||
|
|
||||||
task exportLibs(type: Sync) {
|
|
||||||
mustRunAfter addNativeDependencies
|
|
||||||
|
|
||||||
into libsDirectory.get().getAsFile().getPath() + "/lib"
|
|
||||||
exclude { !isDependencyRequested(it.getName()) }
|
|
||||||
from configurations.runtimeClasspath
|
|
||||||
}
|
|
||||||
|
|
||||||
jar.dependsOn(exportLibs)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Packaging
|
|
||||||
*/
|
|
||||||
|
|
||||||
task packageDebian(type: Exec) {
|
|
||||||
description 'Builds the project and creates a Debain package.'
|
|
||||||
group 'Progressia'
|
|
||||||
|
|
||||||
dependsOn build
|
|
||||||
dependsOn requestLinuxDependencies
|
|
||||||
|
|
||||||
commandLine './buildPackages.sh', 'debian'
|
|
||||||
|
|
||||||
doLast {
|
|
||||||
println "Debian package available in build_packages/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task packageWindows(type: Exec) {
|
|
||||||
description 'Builds the project and creates a Windows installer.'
|
|
||||||
group 'Progressia'
|
|
||||||
|
|
||||||
dependsOn build
|
|
||||||
dependsOn requestWindowsDependencies
|
|
||||||
|
|
||||||
commandLine './buildPackages.sh', 'windows'
|
|
||||||
|
|
||||||
doLast {
|
|
||||||
println "Windows installer available in build_packages/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task buildCrossPlatform {
|
task buildCrossPlatform {
|
||||||
description 'Builds the project including native libraries for all available platforms.'
|
description 'Builds the project including native libraries for all available platforms.'
|
||||||
group 'Progressia'
|
group 'Progressia'
|
||||||
|
|
||||||
dependsOn requestCrossPlatformDependencies
|
dependsOn requestCrossPlatformDependencies
|
||||||
dependsOn build
|
dependsOn build
|
||||||
|
|
||||||
doLast {
|
doLast {
|
||||||
println "Native libraries for all platforms have been added"
|
logger.info 'Native libraries for all platforms have been added'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task buildLocal {
|
task buildLocal {
|
||||||
description "Builds the project including only native libraries for current platform ($lwjglNatives)."
|
description "Builds the project including only native libraries for current platform (${lwjgl.localArch})."
|
||||||
group 'Progressia'
|
group 'Progressia'
|
||||||
|
|
||||||
dependsOn build
|
dependsOn build
|
||||||
|
|
||||||
doLast {
|
doLast {
|
||||||
println "Native libraries only for platform $lwjglNatives have been added"
|
logger.info "Native libraries only for platform ${lwjgl.localArch} have been added"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
191
buildPackages.sh
@ -1,191 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
#
|
|
||||||
# Progressia
|
|
||||||
# Copyright (C) 2020-2021 Wind Corporation and contributors
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
|
|
||||||
echoerr() { echo "$@" 1>&2; }
|
|
||||||
|
|
||||||
buildDebianPackage() {
|
|
||||||
|
|
||||||
# Commands that must be available to execute this action
|
|
||||||
requiredCommands='dpkg-deb fakeroot'
|
|
||||||
|
|
||||||
# Package name. Sync with control file manually!
|
|
||||||
name='progressia-techdemo'
|
|
||||||
# Version that the package will receive. Sync with control file manually!
|
|
||||||
version='1.0_all'
|
|
||||||
|
|
||||||
# This directory will be copied into $tmpDir
|
|
||||||
templateDirectory="build_packages/DEB/template"
|
|
||||||
|
|
||||||
# Files that must be present
|
|
||||||
requiredFiles="$templateDirectory/DEBIAN/control"
|
|
||||||
|
|
||||||
nameAndVersion="$name-$version"
|
|
||||||
tmpDir="build_packages/DEB/$nameAndVersion"
|
|
||||||
outputFile="build_packages/DEB/$nameAndVersion.deb"
|
|
||||||
|
|
||||||
echo "Checking environment to build Debian package"
|
|
||||||
|
|
||||||
for item in $requiredCommands; do
|
|
||||||
if command -v "$item" &> /dev/null; then
|
|
||||||
echo "- $item found"
|
|
||||||
else
|
|
||||||
echoerr "Command $item not found, cannot package"
|
|
||||||
exit 100
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
for file in $requiredFiles; do
|
|
||||||
if ! [ -r "$file" ]; then
|
|
||||||
echoerr "$file is missing or not readable, cannot package"
|
|
||||||
exit 101
|
|
||||||
else
|
|
||||||
echo "- $file is present and readable"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Environment OK; packaging Debian package"
|
|
||||||
exitCode=0
|
|
||||||
|
|
||||||
{
|
|
||||||
shareDir="$tmpDir/usr/share/progressia"
|
|
||||||
|
|
||||||
mkdir -p "$tmpDir" &&
|
|
||||||
mkdir -p "$shareDir" &&
|
|
||||||
cp -r "$templateDirectory"/* "$tmpDir" &&
|
|
||||||
cp -r 'build/libs/lib' "$shareDir/lib" &&
|
|
||||||
cp 'build/libs/Progressia.jar' "$shareDir/Progressia.jar" &&
|
|
||||||
echo "------ DPKG-DEB ------" &&
|
|
||||||
fakeroot dpkg-deb --build "$tmpDir" &&
|
|
||||||
echo "---- DPKG-DEB END ----" &&
|
|
||||||
mv "$outputFile" build_packages
|
|
||||||
} || {
|
|
||||||
echoerr "Could not create Debian package"
|
|
||||||
exitCode=1
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
if [ -d "$tmpDir" ]; then
|
|
||||||
rm -r "$tmpDir"
|
|
||||||
fi
|
|
||||||
echo "Cleaned up"
|
|
||||||
} || {
|
|
||||||
echoerr "Could not clean up after packaging Debian package"
|
|
||||||
exitCode=2
|
|
||||||
}
|
|
||||||
|
|
||||||
exit "$exitCode"
|
|
||||||
}
|
|
||||||
|
|
||||||
buildWindowsInstaller() {
|
|
||||||
|
|
||||||
# Commands that must be available to execute this action
|
|
||||||
requiredCommands='makensis'
|
|
||||||
|
|
||||||
# NSIS configuration file that must be present
|
|
||||||
configurationFile='build_packages/NSIS/ProgressiaInstaller.nsi'
|
|
||||||
|
|
||||||
# File that will be output
|
|
||||||
outputFile='build_packages/NSIS/ProgressiaInstaller.exe'
|
|
||||||
|
|
||||||
echo "Checking environment to build Windows installer"
|
|
||||||
|
|
||||||
for item in $requiredCommands; do
|
|
||||||
if command -v "$item" &> /dev/null; then
|
|
||||||
echo "- $item found"
|
|
||||||
else
|
|
||||||
echoerr "Command $item not found, cannot build"
|
|
||||||
exit 100
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if ! [ -r "$configurationFile" ]; then
|
|
||||||
echoerr "$configurationFile is missing or not readable, cannot build"
|
|
||||||
exit 101
|
|
||||||
else
|
|
||||||
echo "- $configurationFile is present and readable"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Environment OK; building Windows installer"
|
|
||||||
exitCode=0
|
|
||||||
|
|
||||||
{
|
|
||||||
cp -r 'build/libs/lib' 'build_packages/NSIS/lib' &&
|
|
||||||
cp 'build/libs/Progressia.jar' 'build_packages/NSIS/Progressia.jar' &&
|
|
||||||
cp 'LICENSE' 'build_packages/NSIS/LICENSE.txt' &&
|
|
||||||
echo "------ NSIS ------" &&
|
|
||||||
makensis "$configurationFile" &&
|
|
||||||
echo "---- NSIS END ----" &&
|
|
||||||
mv "$outputFile" build_packages
|
|
||||||
} || {
|
|
||||||
echoerr "Could not build Windows installer"
|
|
||||||
exitCode=1
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
if [ -d 'build_packages/NSIS/lib' ]; then
|
|
||||||
rm -r 'build_packages/NSIS/lib'
|
|
||||||
fi
|
|
||||||
if [ -e 'build_packages/NSIS/Progressia.jar' ]; then
|
|
||||||
rm 'build_packages/NSIS/Progressia.jar'
|
|
||||||
fi
|
|
||||||
if [ -e 'build_packages/NSIS/LICENSE.txt' ]; then
|
|
||||||
rm 'build_packages/NSIS/LICENSE.txt'
|
|
||||||
fi
|
|
||||||
echo "Cleaned up"
|
|
||||||
} || {
|
|
||||||
echoerr "Could not clean up after building Windows installer"
|
|
||||||
exitCode=2
|
|
||||||
}
|
|
||||||
|
|
||||||
exit "$exitCode"
|
|
||||||
}
|
|
||||||
|
|
||||||
printUsage() {
|
|
||||||
echoerr "Usage: $0 TARGET"
|
|
||||||
echoerr " where TARGET is 'debian' or 'windows'"
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ -n "$2" ]; then
|
|
||||||
echoerr "Too many arguments."
|
|
||||||
printUsage
|
|
||||||
exit 202
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
"debian")
|
|
||||||
buildDebianPackage
|
|
||||||
;;
|
|
||||||
"windows")
|
|
||||||
buildWindowsInstaller
|
|
||||||
;;
|
|
||||||
"")
|
|
||||||
echoerr "No action specified"
|
|
||||||
printUsage
|
|
||||||
exit 200
|
|
||||||
;;
|
|
||||||
"--help" | "-help" | "help" | "?")
|
|
||||||
printUsage
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echoerr "Unknown action '$1'"
|
|
||||||
printUsage
|
|
||||||
exit 201
|
|
||||||
;;
|
|
||||||
esac
|
|
117
build_logic/lwjgl.gradle
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* Build logic for Progressia
|
||||||
|
* LWJGL dependency logic
|
||||||
|
*/
|
||||||
|
|
||||||
|
project.ext.lwjgl = new HashMap<>()
|
||||||
|
|
||||||
|
// Version of LWJGL
|
||||||
|
lwjgl.version = '3.2.3'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Target platforms for current operation.
|
||||||
|
* This is filled in by the request* tasks. This is referenced by the addLwjglNatives task.
|
||||||
|
* When empty, current platform is assumed.
|
||||||
|
*/
|
||||||
|
lwjgl.targets = new HashSet<>()
|
||||||
|
|
||||||
|
// LWJGL components. To include org.lwjgl:lwjgl-foobar, add 'foobar' to this list.
|
||||||
|
lwjgl.libraries = [
|
||||||
|
'opengl',
|
||||||
|
'glfw',
|
||||||
|
'openal',
|
||||||
|
'stb'
|
||||||
|
]
|
||||||
|
|
||||||
|
// Determine the architecture of the build environment
|
||||||
|
import org.gradle.internal.os.OperatingSystem
|
||||||
|
switch (OperatingSystem.current()) {
|
||||||
|
case OperatingSystem.LINUX:
|
||||||
|
def osArch = System.getProperty('os.arch')
|
||||||
|
lwjgl.localArch = osArch.startsWith('arm') || osArch.startsWith('aarch64')
|
||||||
|
? "linux-${osArch.contains('64') || osArch.startsWith('armv8') ? 'arm64' : 'arm32'}"
|
||||||
|
: 'linux'
|
||||||
|
break
|
||||||
|
case OperatingSystem.MAC_OS:
|
||||||
|
lwjgl.localArch = 'macos'
|
||||||
|
break
|
||||||
|
case OperatingSystem.WINDOWS:
|
||||||
|
lwjgl.localArch = System.getProperty('os.arch').contains('64') ? 'windows' : 'windows-x86'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declare pure-Java dependencies
|
||||||
|
dependencies {
|
||||||
|
// BOM
|
||||||
|
implementation platform("org.lwjgl:lwjgl-bom:${lwjgl.version}")
|
||||||
|
|
||||||
|
// Core
|
||||||
|
implementation 'org.lwjgl:lwjgl'
|
||||||
|
|
||||||
|
// Components
|
||||||
|
lwjgl.libraries.each { implementation "org.lwjgl:lwjgl-$it" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Adds LWJGL native libraries to runtimeOnly configuration
|
||||||
|
*/
|
||||||
|
task lwjgl_addNativesToRuntimeOnly {
|
||||||
|
// Make sure runtimeOnly has not been resolved
|
||||||
|
compileJava.dependsOn lwjgl_addNativesToRuntimeOnly
|
||||||
|
configureManifest.dependsOn lwjgl_addNativesToRuntimeOnly
|
||||||
|
exportLibs.dependsOn lwjgl_addNativesToRuntimeOnly
|
||||||
|
|
||||||
|
doFirst {
|
||||||
|
if (project.hasProperty('forceTargets')) {
|
||||||
|
try {
|
||||||
|
def oldTargets = lwjgl.targets.join(',')
|
||||||
|
|
||||||
|
lwjgl.targets.clear()
|
||||||
|
lwjgl.targets.addAll project.forceTargets.split(',')*.trim().collect { it == 'local' ? lwjgl.localArch : it }
|
||||||
|
|
||||||
|
logger.info 'Overriding selected platforms {} with {}', oldTargets, lwjgl.targets.join(',')
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new GradleException("Could not parse forceTargets \"${project.forceTargets}\", expecting platform-1,platform-2,local", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lwjgl.targets.isEmpty()) {
|
||||||
|
logger.info 'Adding LWJGL native dependencies for local platform only: {}', lwjgl.localArch
|
||||||
|
lwjgl.targets.add lwjgl.localArch
|
||||||
|
} else {
|
||||||
|
logger.info 'Adding LWJGL native dependencies for platforms: {}', lwjgl.targets.sort().join(', ')
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
lwjgl.targets.each { target ->
|
||||||
|
runtimeOnly "org.lwjgl:lwjgl::natives-$target"
|
||||||
|
lwjgl.libraries.each { lib ->
|
||||||
|
runtimeOnly "org.lwjgl:lwjgl-$lib::natives-$target"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task requestCrossPlatformDependencies {
|
||||||
|
description 'Adds LWJGL natives for all available platforms.'
|
||||||
|
|
||||||
|
lwjgl_addNativesToRuntimeOnly.mustRunAfter requestCrossPlatformDependencies
|
||||||
|
}
|
||||||
|
|
||||||
|
def requestTask(String name, String... targets) {
|
||||||
|
def theTask = task "request${name}Dependencies"
|
||||||
|
|
||||||
|
theTask.doFirst {
|
||||||
|
lwjgl.targets.addAll targets
|
||||||
|
}
|
||||||
|
|
||||||
|
theTask.description "Adds LWJGL natives for $name (${targets.join(', ')})."
|
||||||
|
|
||||||
|
requestCrossPlatformDependencies.dependsOn theTask
|
||||||
|
lwjgl_addNativesToRuntimeOnly.mustRunAfter theTask
|
||||||
|
}
|
||||||
|
|
||||||
|
requestTask 'Linux', 'linux', 'linux-arm32', 'linux-arm64'
|
||||||
|
requestTask 'Windows', 'windows', 'windows-x86'
|
||||||
|
requestTask 'MacOS', 'macos'
|
38
build_logic/packaging/deb/script.gradle
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
task packageDeb_processResources(type: Copy) {
|
||||||
|
dependsOn resolveVersion
|
||||||
|
dependsOn preparePackaging
|
||||||
|
|
||||||
|
from 'src/packaging/deb'
|
||||||
|
|
||||||
|
filesMatching('DEBIAN/control') {
|
||||||
|
expand(version: { -> project.version})
|
||||||
|
}
|
||||||
|
|
||||||
|
into 'build/tmp/packaging/workingDir'
|
||||||
|
}
|
||||||
|
|
||||||
|
task packageDeb_configure() {
|
||||||
|
preparePackaging.mustRunAfter packageDeb_configure
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
tasks.preparePackaging.ext.buildDest = '/usr/share/progressia'
|
||||||
|
tasks.preparePackaging.ext.mode = 'copy'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task packageDeb(type: Exec) {
|
||||||
|
description 'Builds the project and creates a Debian package.'
|
||||||
|
group 'Progressia'
|
||||||
|
|
||||||
|
dependsOn packageDeb_configure
|
||||||
|
dependsOn requestLinuxDependencies
|
||||||
|
dependsOn build
|
||||||
|
dependsOn preparePackaging
|
||||||
|
|
||||||
|
dependsOn packageDeb_processResources
|
||||||
|
|
||||||
|
executable 'dpkg-deb'
|
||||||
|
args '--root-owner-group'
|
||||||
|
args '--build', 'build/tmp/packaging/workingDir'
|
||||||
|
args 'build/packages'
|
||||||
|
}
|
50
build_logic/packaging/nsis/script.gradle
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
task packageNsis_processResources(type: Copy) {
|
||||||
|
dependsOn preparePackaging
|
||||||
|
|
||||||
|
from ('src/packaging/nsis') {
|
||||||
|
exclude 'left_side.png'
|
||||||
|
}
|
||||||
|
from('LICENSE') {
|
||||||
|
rename 'LICENSE', 'LICENSE.txt'
|
||||||
|
}
|
||||||
|
into 'build/tmp/packaging/workingDir'
|
||||||
|
}
|
||||||
|
|
||||||
|
task packageNsis_generateIcon(type: Exec) {
|
||||||
|
mustRunAfter preparePackaging
|
||||||
|
|
||||||
|
executable 'convert'
|
||||||
|
args files('src/main/resources/assets/icons/*.original.png').files*.path
|
||||||
|
args 'build/tmp/packaging/workingDir/logo.ico'
|
||||||
|
}
|
||||||
|
|
||||||
|
task packageNsis_generateLeftSide(type: Exec) {
|
||||||
|
mustRunAfter preparePackaging
|
||||||
|
|
||||||
|
executable 'convert'
|
||||||
|
args 'src/packaging/nsis/left_side.png'
|
||||||
|
args '-alpha', 'off'
|
||||||
|
args 'BMP3:build/tmp/packaging/workingDir/left_side.bmp'
|
||||||
|
}
|
||||||
|
|
||||||
|
task packageNsis(type: Exec) {
|
||||||
|
description 'Builds the project and creates a Windows NSIS installer.'
|
||||||
|
group 'Progressia'
|
||||||
|
|
||||||
|
dependsOn requestWindowsDependencies
|
||||||
|
dependsOn build
|
||||||
|
dependsOn resolveVersion
|
||||||
|
dependsOn preparePackaging
|
||||||
|
|
||||||
|
dependsOn packageNsis_processResources
|
||||||
|
dependsOn packageNsis_generateIcon
|
||||||
|
dependsOn packageNsis_generateLeftSide
|
||||||
|
|
||||||
|
executable 'makensis'
|
||||||
|
args '-NOCONFIG'
|
||||||
|
args "-DPROJECT_NAME=${project.name}"
|
||||||
|
args "-DPROJECT_VERSION=${ -> project.version}"
|
||||||
|
args "-DMAIN_JAR_FILE=${ -> project.tasks.jar.archiveFileName.get()}"
|
||||||
|
args "-DOUTPUT_DIR=${project.buildDir.absolutePath}/packages"
|
||||||
|
args 'build/tmp/packaging/workingDir/config.nsi'
|
||||||
|
}
|
41
build_logic/packaging/zip/script.gradle
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
task packageZip_processResources(type: Copy) {
|
||||||
|
dependsOn preparePackaging
|
||||||
|
|
||||||
|
from ('src/packaging/zip') {
|
||||||
|
filesMatching('start.*') {
|
||||||
|
filter(
|
||||||
|
org.apache.tools.ant.filters.ReplaceTokens,
|
||||||
|
tokens: [mainJarFile: project.tasks.jar.archiveFileName.get()]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
from ('src/main/resource/assets/icons/logo256.original.png') {
|
||||||
|
rename 'logo256.original.png', 'logo.png'
|
||||||
|
}
|
||||||
|
from('LICENSE') {
|
||||||
|
rename 'LICENSE', 'LICENSE.txt'
|
||||||
|
}
|
||||||
|
into 'build/tmp/packaging/workingDir'
|
||||||
|
}
|
||||||
|
|
||||||
|
task packageZip(type: Zip) {
|
||||||
|
description 'Builds the project and creates a cross-platform ZIP package.'
|
||||||
|
group 'Progressia'
|
||||||
|
|
||||||
|
dependsOn resolveVersion
|
||||||
|
dependsOn requestCrossPlatformDependencies
|
||||||
|
dependsOn build
|
||||||
|
dependsOn preparePackaging
|
||||||
|
|
||||||
|
dependsOn packageZip_processResources
|
||||||
|
|
||||||
|
archiveBaseName = project.name
|
||||||
|
archiveAppendix = 'universal'
|
||||||
|
|
||||||
|
doFirst {
|
||||||
|
archiveVersion = project.version
|
||||||
|
}
|
||||||
|
|
||||||
|
from 'build/tmp/packaging/workingDir'
|
||||||
|
destinationDirectory = file('build/packages')
|
||||||
|
}
|
Before Width: | Height: | Size: 151 KiB |
Before Width: | Height: | Size: 187 KiB |
12
docs/ProgressiaRegion.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Progressia Region File
|
||||||
|
## Description
|
||||||
|
The `.progressia_region` file type is used for all region files in the game Progressia. Each region file contains a cube of 16x16x16 chunks.
|
||||||
|
## Header
|
||||||
|
The header of the file is 16 400 bytes. Every file starts with the string byte sequence `\x50\x52\x4F\x47` (UTF-8 for `PROG`), followed with the three integer values of the region position, in region coordinates. After this, there is exactly 16KiB of space in the header, which stores the offsets to the chunks' data. This space holds an integer, 4 bytes, for each chunk in the region. The integer value starts at 0 for every chunk, and is changed to the location of the chunk data once created. These are indexed in order by flattening the 3D in-chunk coordinates into a number between 0 and 4095 according to the formula `offset = 256*x+ 16*y + z` for chunk at (x, y, z). To convert from this offset value to the offset in bytes, use `byte_offset = 16400 + 64*n`.
|
||||||
|
## Sectors
|
||||||
|
Sectors are what is used to store chunk data, and are not linear, but are followed until they reach an ending block. Each is 64 bytes, which is used in the header section to find the byte offset. Each sector starts with a identification byte, followed by the sector data.
|
||||||
|
|
||||||
|
0. Ending - This sector is empty, and marks the end of the chunk data (This may change in the future.
|
||||||
|
1. Data - This sector contains chunk data for a single chunk. The second byte of this sector contains a counter byte, which is a form of "checksum" to make sure that the program is reading the proper sectors in order. This starts at 0 for the first data sector and increments by one for each new data sector.
|
||||||
|
2. Partition Link - This sector only contains another offset value, which is where the next sector is. This allows for infinite chunk size, avoiding "chunk dupes" as were present in Minecraft without reverting any chunks.
|
||||||
|
3. Bulk Data - These would be used for many chunks in the same region that contain exactly the same data, e.g. all solid chunks underground. Exists so the program knows not to overwrite them, and just make new chunk data if modified. Not yet implemented.
|
@ -1,8 +1,10 @@
|
|||||||
# Build Guide
|
# Build Guide
|
||||||
|
|
||||||
This document is a guide to building Progressia from source.
|
This document is a guide to building Progressia from source. For quick reference, see
|
||||||
|
[Build Script Reference](BuildScriptReference.md).
|
||||||
|
|
||||||
Compilation should be possible on all platforms that support JDK 8 or later, however, packaging scripts require Bash.
|
Compilation should be possible on all platforms that support JDK 8 or later, however, packaging scripts require
|
||||||
|
additional programs in `PATH`.
|
||||||
|
|
||||||
This guide assumes you are familiar with using a terminal or Windows Command Prompt or PowerShell.
|
This guide assumes you are familiar with using a terminal or Windows Command Prompt or PowerShell.
|
||||||
|
|
||||||
@ -150,54 +152,46 @@ GNU/Linux and Windows natives:
|
|||||||
./gradlew build requestLinuxDependencies requestWindowsDependencies
|
./gradlew build requestLinuxDependencies requestWindowsDependencies
|
||||||
```
|
```
|
||||||
|
|
||||||
For finer control please edit `build.gradle` manually by adding the desired natives to the `project.ext.platforms` set like so:
|
|
||||||
|
|
||||||
```
|
|
||||||
project.ext.platforms = new HashSet<>()
|
|
||||||
project.ext.platforms.add 'natives-windows-x86'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Packaging
|
## Packaging
|
||||||
|
|
||||||
A Debian package and a Windows installer can be created automatically on systems that support Bash. These tasks are delegated
|
A universal ZIP distribution, a Debian package and a Windows NSIS installer may be created automatically by the build
|
||||||
by Gradle to `buildPackages.sh` in repository root. This script checks the environment and assembles the requested output; the
|
script.
|
||||||
resulting files are moved into `build_packages`.
|
|
||||||
|
### Creating a universal ZIP package
|
||||||
|
|
||||||
|
A universal cross-platform ZIP archive can be created with the following Gradle task:
|
||||||
|
|
||||||
|
```
|
||||||
|
./gradlew packageZip
|
||||||
|
```
|
||||||
|
|
||||||
|
Gradle will then build all artifacts necessary to run the game on all available platforms and package game files,
|
||||||
|
libraries, launch scripts, etc. into a compressed ZIP archive.
|
||||||
|
|
||||||
|
The resulting file can be found in `build/packages/`
|
||||||
|
|
||||||
### Creating a Debian package
|
### Creating a Debian package
|
||||||
|
|
||||||
A Debian package can be created with the following Gradle task:
|
A Debian package can be created with the following Gradle task:
|
||||||
|
|
||||||
```
|
```
|
||||||
./gradlew packageDebian
|
./gradlew packageDeb
|
||||||
```
|
```
|
||||||
|
|
||||||
Gradle will then build all artifacts necessary to run the game on GNU/Linux (all three architectures) and invoke
|
Gradle will then build all artifacts necessary to run the game on GNU/Linux (all three architectures) and invoke
|
||||||
`./buildPackages.sh debian`. Commands `dpkg-deb` and `fakeroot` must be available in system path in order to build the package.
|
`dpkg-deb`. Commands `dpkg-deb` must be available in system path in order to build the package.
|
||||||
|
|
||||||
### Creating a Windows installer
|
### Creating a Windows installer
|
||||||
|
|
||||||
A Windows installer can be created with the following Gradle task:
|
A Windows NSIS installer can be created with the following Gradle task:
|
||||||
|
|
||||||
```
|
```
|
||||||
./gradlew packageWindows
|
./gradlew packageNsis
|
||||||
```
|
```
|
||||||
|
|
||||||
Gradle will then build all artifacts necessary to run the game on Windows (both x64 and x86 architectures) and invoke
|
Gradle will then build all artifacts necessary to run the game on Windows (both x64 and x86 architectures) and invoke
|
||||||
`./buildPackages.sh windows`.
|
`makensis`.
|
||||||
|
|
||||||
Windows installers are implemented with [NSIS](https://nsis.sourceforge.io/). Command `makensis` must be available in system
|
Windows installers are implemented with [NSIS](https://nsis.sourceforge.io/). [ImageMagick](https://imagemagick.org),
|
||||||
path in order to build the installer.
|
a command-line image editing tool, is used to generate some assets for the installer. Commands `makensis` and
|
||||||
|
`convert` (from ImageMagick) must be available in system path in order to build the installer.
|
||||||
## Gradle tasks summary
|
|
||||||
|
|
||||||
- `buildLocal` – creates a build optimized for current platform. Use this to quickly build the game during development.
|
|
||||||
- `buildCrossPlatform` – creates a build that supports all known architectures. Use this to build a universal version of the game.
|
|
||||||
- `build` – currently a synonym of `buildLocal`; creates a default build.
|
|
||||||
- `packageDebian` – creates a Debian package. Do not invoke together with `packageWindows`.
|
|
||||||
- `packageWindows` – creates a Windows installer. Do not invoke together with `packageDebian`.
|
|
||||||
- `requestLinuxDependencies` – requests that `natives-linux`, `natives-linux-arm32` and `natives-linux-arm64` binaries are included when building.
|
|
||||||
- `requestWindowsDependencies` – requests that `natives-windows` and `natives-windows-x86` binaries are included when building.
|
|
||||||
- `requestMacOSDependencies` – requests that `natives-macos` binaries are included when building.
|
|
||||||
- `requestCrossPlatformDependencies` – requests that all binaries are included when building.
|
|
||||||
|
|
||||||
All other basic and Java-related Gradle tasks are available as well.
|
|
||||||
|
103
docs/building/BuildScriptReference.md
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
# Build Script Reference
|
||||||
|
|
||||||
|
This document is a user's reference for the build script of Progressia. For a beginner-friendly guide, see
|
||||||
|
[Build Guide](BuildGuide.md).
|
||||||
|
|
||||||
|
## Gradle tasks summary
|
||||||
|
|
||||||
|
- `buildLocal` – creates a build optimized for current platform. Use this to quickly build the game during development.
|
||||||
|
- `buildCrossPlatform` – creates a build that supports all known architectures. Use this to build a universal version of the game.
|
||||||
|
- `build` – currently a synonym of `buildLocal`; creates a default build.
|
||||||
|
- `packageZip` – creates a universal ZIP. Incompatible with other `package` tasks.
|
||||||
|
- `packageDeb` – creates a Debian package. Incompatible with other `package` tasks.
|
||||||
|
- `packageNsis` – creates a Windows NSIS installer. Incompatible with other `package` tasks.
|
||||||
|
- `requestLinuxDependencies` – requests that `natives-linux`, `natives-linux-arm32` and `natives-linux-arm64` binaries are included when building.
|
||||||
|
- `requestWindowsDependencies` – requests that `natives-windows` and `natives-windows-x86` binaries are included when building.
|
||||||
|
- `requestMacOSDependencies` – requests that `natives-macos` binaries are included when building.
|
||||||
|
- `requestCrossPlatformDependencies` – requests that all binaries are included when building.
|
||||||
|
|
||||||
|
To execute a task, run `./gradlew <task-name>`.
|
||||||
|
|
||||||
|
`build`-type tasks output the executable JAR and all libraries required at runtime into `build/libs`. `package`-type
|
||||||
|
tasks output packages into `build/packages`.
|
||||||
|
|
||||||
|
## Packaging tasks
|
||||||
|
|
||||||
|
Some packaging tasks require additional software in `PATH`.
|
||||||
|
|
||||||
|
| Task | Commands required in `PATH` |
|
||||||
|
|---------------|------------------------------------------|
|
||||||
|
| `packageDeb` | `dpkg-deb` |
|
||||||
|
| `packageZip` | _none_ |
|
||||||
|
| `packageNsis` | `makensis`, `convert` (from ImageMagick) |
|
||||||
|
|
||||||
|
## Version and metadata
|
||||||
|
|
||||||
|
### Version scheme
|
||||||
|
|
||||||
|
Progressia builds are identified by four parameters: version, Git commit, Git branch and build ID.
|
||||||
|
|
||||||
|
Versions roughly follow [semantic versioning](https://semver.org/spec/v2.0.0.html), with each version fitting the
|
||||||
|
`MAJOR.MINOR.PATCH[-SUFFIX]` pattern. Depending on the build environment (see below), version is either "real" with
|
||||||
|
no metadata (e.g. `0.43.2` or `1.2.1-beta`) or a dummy fallback with build metadata (e.g. `999.0.0-2021_07_23` or
|
||||||
|
`999.0.0-WJ3`).
|
||||||
|
|
||||||
|
### Version detection
|
||||||
|
|
||||||
|
Build script considers three scenarios when determining the version:
|
||||||
|
|
||||||
|
1. `version` project property is set explicitly. This may be done in a variety of ways, for example with command line
|
||||||
|
argument `-Pversion=1.2.3`
|
||||||
|
(see [Gradle docs](https://docs.gradle.org/current/userguide/build_environment.html#sec:project_properties))
|
||||||
|
2. Local Git repository is found, and HEAD is tagged appropriately: version is the tag name with leading `v`
|
||||||
|
stripped. Example: `v1.2.3` is version `1.2.3`
|
||||||
|
3. Local Git repository is found, and some ancestor of HEAD is tagged appropriately: version is the tag name with
|
||||||
|
leading `v` stripped and PATCH incremented by one. Example: `v1.2.3` is version `1.2.4`
|
||||||
|
|
||||||
|
Tags not named like `vMAJOR.MINOR.PATCH[-SUFFIX]` are ignored for cases 2 and 3.
|
||||||
|
|
||||||
|
In all other cases, a fallback dummy value is used for version, appended with build ID or current date.
|
||||||
|
|
||||||
|
### Git metadata
|
||||||
|
|
||||||
|
Git commit and Git branch are correspond to the state of the local Git repository, if any. In case Git metadata is
|
||||||
|
unavailable, `-` fallback is used for both fields.
|
||||||
|
|
||||||
|
### Build ID
|
||||||
|
|
||||||
|
Build ID uniquely identifies artifacts produced by automated build systems. For example, builds executed by WindCorp
|
||||||
|
Jenkins suite have build IDs like `WJ3` or `WJ142`. Build ID must be provided explicitly; it is `-` unless specified
|
||||||
|
otherwise.
|
||||||
|
|
||||||
|
Build ID may be set with `buildId` project property. This may be done in a variety of ways, for example with command
|
||||||
|
line argument `-PbuildId=WJ3`
|
||||||
|
(see [Gradle docs](https://docs.gradle.org/current/userguide/build_environment.html#sec:project_properties)).
|
||||||
|
|
||||||
|
## Native libraries
|
||||||
|
|
||||||
|
LWJGL uses native libraries. Build script declares platform-specific dependencies based on the set of target
|
||||||
|
platforms, `project.ext.lwjgl.targets` (aka `lwjgl.targets`). These dependencies are added to `runtimeOnly`
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
When this set is empty, the script selects natives for current platform. Otherwise, all platforms in the set are
|
||||||
|
included.
|
||||||
|
|
||||||
|
`lwjgl.targets` is populated automatically by packaging tasks and by `buildCrossPlatform`. To add extra targets,
|
||||||
|
``requestXxxDependencies` tasks may be used.
|
||||||
|
|
||||||
|
Target selection mechanism may be overridden with `forceTargets` project property. This may be done in a variety of
|
||||||
|
ways, for example with command line argument `-PforceTargets=windows-x86,local`
|
||||||
|
(see [Gradle docs](https://docs.gradle.org/current/userguide/build_environment.html#sec:project_properties)). The
|
||||||
|
value is a comma-separated list of target architectures. `local` target will be replaced with the automatically
|
||||||
|
detected current architecture.
|
||||||
|
|
||||||
|
### Available targets
|
||||||
|
|
||||||
|
| Name | Task |
|
||||||
|
|---------------|------------------------------|
|
||||||
|
| `linux` | `requestLinuxDependencies` |
|
||||||
|
| `linux-arm32` | `requestLinuxDependencies` |
|
||||||
|
| `linux-arm64` | `requestLinuxDependencies` |
|
||||||
|
| `windows` | `requestWindowsDependencies` |
|
||||||
|
| `windows-x86` | `requestWindowsDependencies` |
|
||||||
|
| `macos` | `requestMacOSDependencies` |
|
1
logs/game.log
Normal file
@ -0,0 +1 @@
|
|||||||
|
22:26:25.948 [Music Thread ] WARN ru.windcorp.progressia.test.TestMusicPlayer > No music found
|
@ -15,9 +15,180 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia;
|
package ru.windcorp.progressia;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.jar.Attributes;
|
||||||
|
import java.util.jar.Manifest;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class providing access to build metadata.
|
||||||
|
*/
|
||||||
public class Progressia {
|
public class Progressia {
|
||||||
|
|
||||||
|
private static final String NAME = "Progressia";
|
||||||
|
private static String version;
|
||||||
|
private static String gitCommit;
|
||||||
|
private static String gitBranch;
|
||||||
|
private static String buildId;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
Manifest manifest = findManifest();
|
||||||
|
|
||||||
|
if (manifest == null) {
|
||||||
|
setDevelopmentMetadata();
|
||||||
|
LogManager.getLogger().info(
|
||||||
|
"Manifest with Specification-Title not found. "
|
||||||
|
+ "Either you are in a development environment or something has gone horribly wrong with classloaders."
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
fillMetadata(manifest);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
CrashReports.crash(t, "Something went wrong while loading metadata");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Manifest findManifest() {
|
||||||
|
try {
|
||||||
|
Enumeration<URL> resources = Progressia.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
|
||||||
|
Collection<IOException> exceptions = new ArrayList<>();
|
||||||
|
|
||||||
|
while (resources.hasMoreElements()) {
|
||||||
|
URL url = resources.nextElement();
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
Manifest manifest = new Manifest(url.openStream());
|
||||||
|
Attributes mainAttributes = manifest.getMainAttributes();
|
||||||
|
if (NAME.equals(mainAttributes.getValue("Specification-Title"))) {
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
exceptions.add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exceptions.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
IOException scapegoat = null;
|
||||||
|
for (IOException e : exceptions) {
|
||||||
|
if (scapegoat == null) {
|
||||||
|
scapegoat = e;
|
||||||
|
} else {
|
||||||
|
scapegoat.addSuppressed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw CrashReports.report(scapegoat, "Could not read manifest");
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw CrashReports.report(e, "Could not read manifest");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setDevelopmentMetadata() {
|
||||||
|
version = "dev";
|
||||||
|
gitCommit = "-";
|
||||||
|
gitBranch = "-";
|
||||||
|
buildId = "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void fillMetadata(Manifest manifest) {
|
||||||
|
version = getAttributeOrCrash(manifest, "Implementation-Version");
|
||||||
|
gitCommit = getAttributeOrCrash(manifest, "Implementation-Version-Git-Commit");
|
||||||
|
gitBranch = getAttributeOrCrash(manifest, "Implementation-Version-Git-Branch");
|
||||||
|
buildId = getAttributeOrCrash(manifest, "Implementation-Version-BuildId");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getAttributeOrCrash(Manifest manifest, String key) {
|
||||||
|
String result = manifest.getMainAttributes().getValue(key);
|
||||||
|
if (result == null) {
|
||||||
|
throw CrashReports.report(null, "Manifest exists but attribute " + key + " not found");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getName() {
|
||||||
|
return NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the version of the game as a String. Version data is retrieved
|
||||||
|
* from a {@code META-INF/MANIFEST.MF} file located in the main JAR. Version
|
||||||
|
* format depends on way the game was built:
|
||||||
|
* <ul>
|
||||||
|
* <li><code>dev</code> if no matching manifest was found, e.g. when launching from an IDE</li>
|
||||||
|
* <li>The value of <code>Implementation-Version</code> specified in the manifest:
|
||||||
|
* <ul>
|
||||||
|
* <li>[Stage-]Major.Minor.Patch, e.g. <code>alpha-0.3.2</code> or <code>1.4.2</code>, for released versions</li>
|
||||||
|
* <li>BuildId, e.g. <code>WJ7</code>, for snapshots built by automation systems</li>
|
||||||
|
* <li>YYYY-MM-DD, e.g. <code>2021-12-32</code>, for snapshots built manually</li>
|
||||||
|
* </ul>
|
||||||
|
* </li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @return the version
|
||||||
|
*/
|
||||||
|
public static String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getFullerVersion() {
|
||||||
|
if (isDefaultGitBranch() || "-".equals(gitBranch)) {
|
||||||
|
return version;
|
||||||
|
} else {
|
||||||
|
return String.format("%s/%s", version, gitBranch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the buildId or <code>"-"</code>
|
||||||
|
*/
|
||||||
|
public static String getBuildId() {
|
||||||
|
return buildId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the Git commit or <code>"-"</code>
|
||||||
|
*/
|
||||||
|
public static String getGitCommit() {
|
||||||
|
return gitCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getGitCommitShort() {
|
||||||
|
if (gitCommit == null || "-".equals(gitCommit)) {
|
||||||
|
return gitCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return gitCommit.substring(0, Math.min(7, gitCommit.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the Git branch or <code>"-"</code>
|
||||||
|
*/
|
||||||
|
public static String getGitBranch() {
|
||||||
|
return gitBranch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDefaultGitBranch() {
|
||||||
|
return "master".equals(gitBranch) || "main".equals(gitBranch);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getFullVersion() {
|
||||||
|
return String.format("%s/%s/%s/%s", version, gitBranch, getGitCommitShort(), buildId);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia;
|
package ru.windcorp.progressia;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.GUI;
|
import ru.windcorp.progressia.client.graphics.GUI;
|
||||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||||
import ru.windcorp.progressia.common.util.crash.analyzers.OutOfMemoryAnalyzer;
|
import ru.windcorp.progressia.common.util.crash.analyzers.OutOfMemoryAnalyzer;
|
||||||
@ -32,6 +34,8 @@ public class ProgressiaLauncher {
|
|||||||
public static void launch(String[] args, Proxy proxy) {
|
public static void launch(String[] args, Proxy proxy) {
|
||||||
arguments = args.clone();
|
arguments = args.clone();
|
||||||
setupCrashReports();
|
setupCrashReports();
|
||||||
|
|
||||||
|
LogManager.getRootLogger().info("Launching " + Progressia.getName() + " version " + Progressia.getFullVersion());
|
||||||
|
|
||||||
proxy.initialize();
|
proxy.initialize();
|
||||||
ProgressiaLauncher.proxy = proxy;
|
ProgressiaLauncher.proxy = proxy;
|
||||||
@ -44,6 +48,7 @@ public class ProgressiaLauncher {
|
|||||||
|
|
||||||
private static void setupCrashReports() {
|
private static void setupCrashReports() {
|
||||||
// Context providers
|
// Context providers
|
||||||
|
CrashReports.registerProvider(new VersionProvider());
|
||||||
CrashReports.registerProvider(new OSContextProvider());
|
CrashReports.registerProvider(new OSContextProvider());
|
||||||
CrashReports.registerProvider(new RAMContextProvider());
|
CrashReports.registerProvider(new RAMContextProvider());
|
||||||
CrashReports.registerProvider(new JavaVersionContextProvider());
|
CrashReports.registerProvider(new JavaVersionContextProvider());
|
||||||
|
@ -143,20 +143,6 @@ public class ControlTriggers {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
///
|
|
||||||
///
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
public static ControlTriggerInputBased localOf(
|
public static ControlTriggerInputBased localOf(
|
||||||
String id,
|
String id,
|
||||||
Consumer<InputEvent> action,
|
Consumer<InputEvent> action,
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
package ru.windcorp.progressia.client.comms.controls;
|
package ru.windcorp.progressia.client.comms.controls;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.Client;
|
import ru.windcorp.progressia.client.Client;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
import ru.windcorp.progressia.common.comms.packets.Packet;
|
import ru.windcorp.progressia.common.comms.packets.Packet;
|
||||||
|
|
||||||
public class InputBasedControls {
|
public class InputBasedControls {
|
||||||
@ -36,12 +36,12 @@ public class InputBasedControls {
|
|||||||
.toArray(ControlTriggerInputBased[]::new);
|
.toArray(ControlTriggerInputBased[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleInput(Input input) {
|
public void handleInput(InputEvent event) {
|
||||||
for (ControlTriggerInputBased c : controls) {
|
for (ControlTriggerInputBased c : controls) {
|
||||||
Packet packet = c.onInputEvent(input.getEvent());
|
Packet packet = c.onInputEvent(event);
|
||||||
|
|
||||||
if (packet != null) {
|
if (packet != null) {
|
||||||
input.consume();
|
event.consume();
|
||||||
client.getComms().sendPacket(packet);
|
client.getComms().sendPacket(packet);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -23,15 +23,8 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||||
import ru.windcorp.progressia.client.graphics.input.CursorEvent;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.WheelEvent;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
|
||||||
|
|
||||||
public class GUI {
|
public class GUI {
|
||||||
|
|
||||||
@ -46,15 +39,6 @@ public class GUI {
|
|||||||
private static final List<LayerStackModification> MODIFICATION_QUEUE = Collections
|
private static final List<LayerStackModification> MODIFICATION_QUEUE = Collections
|
||||||
.synchronizedList(new ArrayList<>());
|
.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
private static class ModifiableInput extends Input {
|
|
||||||
@Override
|
|
||||||
public void initialize(InputEvent event, Target target) {
|
|
||||||
super.initialize(event, target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final ModifiableInput THE_INPUT = new ModifiableInput();
|
|
||||||
|
|
||||||
private GUI() {
|
private GUI() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,43 +110,12 @@ public class GUI {
|
|||||||
LAYERS.forEach(Layer::invalidate);
|
LAYERS.forEach(Layer::invalidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void dispatchInputEvent(InputEvent event) {
|
public static void dispatchInput(InputEvent event) {
|
||||||
Input.Target target;
|
synchronized (LAYERS) {
|
||||||
|
for (int i = 0; i < LAYERS.size(); ++i) {
|
||||||
if (event instanceof KeyEvent) {
|
LAYERS.get(i).handleInput(event);
|
||||||
if (((KeyEvent) event).isMouse()) {
|
|
||||||
target = Input.Target.HOVERED;
|
|
||||||
} else {
|
|
||||||
target = Input.Target.FOCUSED;
|
|
||||||
}
|
}
|
||||||
} else if (event instanceof CursorEvent) {
|
|
||||||
target = Input.Target.HOVERED;
|
|
||||||
} else if (event instanceof WheelEvent) {
|
|
||||||
target = Input.Target.HOVERED;
|
|
||||||
} else if (event instanceof FrameResizeEvent) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
target = Input.Target.ALL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
THE_INPUT.initialize(event, target);
|
|
||||||
LAYERS.forEach(l -> l.handleInput(THE_INPUT));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Object getEventSubscriber() {
|
|
||||||
return new Object() {
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onFrameResized(FrameResizeEvent event) {
|
|
||||||
GUI.invalidateEverything();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onInput(InputEvent event) {
|
|
||||||
dispatchInputEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.graphics;
|
|||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
|
|
||||||
public abstract class Layer {
|
public abstract class Layer {
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ public abstract class Layer {
|
|||||||
|
|
||||||
protected abstract void doRender();
|
protected abstract void doRender();
|
||||||
|
|
||||||
protected abstract void handleInput(Input input);
|
public abstract void handleInput(InputEvent input);
|
||||||
|
|
||||||
protected int getWidth() {
|
protected int getWidth() {
|
||||||
return GraphicsInterface.getFrameWidth();
|
return GraphicsInterface.getFrameWidth();
|
||||||
|
@ -68,6 +68,10 @@ public class GraphicsInterface {
|
|||||||
public static void subscribeToInputEvents(Object listener) {
|
public static void subscribeToInputEvents(Object listener) {
|
||||||
InputHandler.register(listener);
|
InputHandler.register(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void unsubscribeFromInputEvents(Object listener) {
|
||||||
|
InputHandler.unregister(listener);
|
||||||
|
}
|
||||||
|
|
||||||
public static void startNextLayer() {
|
public static void startNextLayer() {
|
||||||
GraphicsBackend.startNextLayer();
|
GraphicsBackend.startNextLayer();
|
||||||
|
@ -39,6 +39,7 @@ public class InputHandler {
|
|||||||
|
|
||||||
public void initialize(int key, int scancode, int action, int mods) {
|
public void initialize(int key, int scancode, int action, int mods) {
|
||||||
this.setTime(GraphicsInterface.getTime());
|
this.setTime(GraphicsInterface.getTime());
|
||||||
|
this.setConsumed(false);
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.scancode = scancode;
|
this.scancode = scancode;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
@ -59,7 +60,7 @@ public class InputHandler {
|
|||||||
if (GraphicsBackend.getWindowHandle() != window)
|
if (GraphicsBackend.getWindowHandle() != window)
|
||||||
return;
|
return;
|
||||||
THE_KEY_EVENT.initialize(key, scancode, action, mods);
|
THE_KEY_EVENT.initialize(key, scancode, action, mods);
|
||||||
dispatch(THE_KEY_EVENT);
|
INPUT_EVENT_BUS.post(THE_KEY_EVENT);
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case GLFW.GLFW_PRESS:
|
case GLFW.GLFW_PRESS:
|
||||||
@ -90,6 +91,7 @@ public class InputHandler {
|
|||||||
|
|
||||||
public void initialize(double x, double y) {
|
public void initialize(double x, double y) {
|
||||||
this.setTime(GraphicsInterface.getTime());
|
this.setTime(GraphicsInterface.getTime());
|
||||||
|
this.setConsumed(false);
|
||||||
getNewPosition().set(x, y);
|
getNewPosition().set(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +111,7 @@ public class InputHandler {
|
|||||||
InputTracker.initializeCursorPosition(x, y);
|
InputTracker.initializeCursorPosition(x, y);
|
||||||
|
|
||||||
THE_CURSOR_MOVE_EVENT.initialize(x, y);
|
THE_CURSOR_MOVE_EVENT.initialize(x, y);
|
||||||
dispatch(THE_CURSOR_MOVE_EVENT);
|
INPUT_EVENT_BUS.post(THE_CURSOR_MOVE_EVENT);
|
||||||
|
|
||||||
InputTracker.getCursorPosition().set(x, y);
|
InputTracker.getCursorPosition().set(x, y);
|
||||||
}
|
}
|
||||||
@ -124,6 +126,7 @@ public class InputHandler {
|
|||||||
|
|
||||||
public void initialize(double xOffset, double yOffset) {
|
public void initialize(double xOffset, double yOffset) {
|
||||||
this.setTime(GraphicsInterface.getTime());
|
this.setTime(GraphicsInterface.getTime());
|
||||||
|
this.setConsumed(false);
|
||||||
this.getOffset().set(xOffset, yOffset);
|
this.getOffset().set(xOffset, yOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +142,7 @@ public class InputHandler {
|
|||||||
if (GraphicsBackend.getWindowHandle() != window)
|
if (GraphicsBackend.getWindowHandle() != window)
|
||||||
return;
|
return;
|
||||||
THE_WHEEL_SCROLL_EVENT.initialize(xoffset, yoffset);
|
THE_WHEEL_SCROLL_EVENT.initialize(xoffset, yoffset);
|
||||||
dispatch(THE_WHEEL_SCROLL_EVENT);
|
INPUT_EVENT_BUS.post(THE_WHEEL_SCROLL_EVENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FrameResizeEvent
|
// FrameResizeEvent
|
||||||
@ -152,6 +155,7 @@ public class InputHandler {
|
|||||||
|
|
||||||
public void initialize(int width, int height) {
|
public void initialize(int width, int height) {
|
||||||
this.setTime(GraphicsInterface.getTime());
|
this.setTime(GraphicsInterface.getTime());
|
||||||
|
this.setConsumed(false);
|
||||||
this.getNewSize().set(width, height);
|
this.getNewSize().set(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,17 +171,17 @@ public class InputHandler {
|
|||||||
int height
|
int height
|
||||||
) {
|
) {
|
||||||
THE_FRAME_RESIZE_EVENT.initialize(width, height);
|
THE_FRAME_RESIZE_EVENT.initialize(width, height);
|
||||||
dispatch(THE_FRAME_RESIZE_EVENT);
|
INPUT_EVENT_BUS.post(THE_FRAME_RESIZE_EVENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
|
|
||||||
private static void dispatch(InputEvent event) {
|
|
||||||
INPUT_EVENT_BUS.post(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void register(Object listener) {
|
public static void register(Object listener) {
|
||||||
INPUT_EVENT_BUS.register(listener);
|
INPUT_EVENT_BUS.register(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void unregister(Object listener) {
|
||||||
|
INPUT_EVENT_BUS.unregister(listener);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,16 +15,30 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.backend;
|
package ru.windcorp.progressia.client.graphics.backend;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL11.*;
|
import static org.lwjgl.opengl.GL11.*;
|
||||||
import static org.lwjgl.glfw.GLFW.*;
|
import static org.lwjgl.glfw.GLFW.*;
|
||||||
import static org.lwjgl.system.MemoryUtil.*;
|
import static org.lwjgl.system.MemoryUtil.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.GLFWImage;
|
||||||
import org.lwjgl.opengl.GL;
|
import org.lwjgl.opengl.GL;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.Progressia;
|
||||||
import ru.windcorp.progressia.client.graphics.GUI;
|
import ru.windcorp.progressia.client.graphics.GUI;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.texture.TextureDataEditor;
|
||||||
|
import ru.windcorp.progressia.client.graphics.texture.TextureLoader;
|
||||||
|
import ru.windcorp.progressia.client.graphics.texture.TextureSettings;
|
||||||
|
import ru.windcorp.progressia.common.resource.Resource;
|
||||||
|
import ru.windcorp.progressia.common.resource.ResourceManager;
|
||||||
|
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||||
|
|
||||||
class LWJGLInitializer {
|
class LWJGLInitializer {
|
||||||
|
|
||||||
@ -59,14 +73,20 @@ class LWJGLInitializer {
|
|||||||
glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE);
|
glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE);
|
||||||
glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
|
glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
|
||||||
|
|
||||||
long handle = glfwCreateWindow(900, 900, "ProgressiaTest", NULL, NULL);
|
long handle = glfwCreateWindow(
|
||||||
|
800,
|
||||||
|
600,
|
||||||
|
Progressia.getName() + " " + Progressia.getFullerVersion(),
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
// TODO Check that handle != NULL
|
// TODO Check that handle != NULL
|
||||||
|
|
||||||
GraphicsBackend.setWindowHandle(handle);
|
GraphicsBackend.setWindowHandle(handle);
|
||||||
|
|
||||||
glfwMakeContextCurrent(handle);
|
glfwMakeContextCurrent(handle);
|
||||||
glfwSwapInterval(0); // TODO: remove after config system is added
|
glfwSwapInterval(0); // TODO: remove after config system is added
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void positionWindow() {
|
private static void positionWindow() {
|
||||||
@ -75,8 +95,30 @@ class LWJGLInitializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void createWindowIcons() {
|
private static void createWindowIcons() {
|
||||||
// TODO Auto-generated method stub
|
if (glfwGetVersionString().toLowerCase().contains("wayland")) {
|
||||||
|
// glfwSetWindowIcon is not supported on Wayland
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String prefix = "assets/icons/";
|
||||||
|
|
||||||
|
String[] sizes = ResourceManager.getResource(prefix + "logoSizes.txt").readAsString().split(" ");
|
||||||
|
|
||||||
|
try (GLFWImage.Buffer buffer = GLFWImage.malloc(sizes.length)) {
|
||||||
|
for (int i = 0; i < sizes.length; ++i) {
|
||||||
|
Resource resource = ResourceManager.getResource(prefix + "logo" + sizes[i].trim() + ".png");
|
||||||
|
TextureDataEditor icon = TextureLoader.loadPixels(resource, new TextureSettings(false, true));
|
||||||
|
|
||||||
|
buffer.position(i)
|
||||||
|
.width(icon.getContentWidth())
|
||||||
|
.height(icon.getContentHeight())
|
||||||
|
.pixels(icon.getData().getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
glfwSetWindowIcon(GraphicsBackend.getWindowHandle(), buffer);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw CrashReports.report(e, "Could not load window icons");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void initializeOpenGL() {
|
private static void initializeOpenGL() {
|
||||||
@ -107,7 +149,20 @@ class LWJGLInitializer {
|
|||||||
|
|
||||||
glfwSetScrollCallback(handle, InputHandler::handleWheelScroll);
|
glfwSetScrollCallback(handle, InputHandler::handleWheelScroll);
|
||||||
|
|
||||||
GraphicsInterface.subscribeToInputEvents(GUI.getEventSubscriber());
|
GraphicsInterface.subscribeToInputEvents(new Object() {
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onFrameResized(FrameResizeEvent event) {
|
||||||
|
GUI.invalidateEverything();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onInputEvent(InputEvent event) {
|
||||||
|
GUI.dispatchInput(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ public class GNUUnifontLoader {
|
|||||||
|
|
||||||
private static final AtlasGroup ATLAS_GROUP_GNU_UNIFONT = new AtlasGroup("GNUUnifont", 1 << 12);
|
private static final AtlasGroup ATLAS_GROUP_GNU_UNIFONT = new AtlasGroup("GNUUnifont", 1 << 12);
|
||||||
|
|
||||||
private static final TextureSettings TEXTURE_SETTINGS = new TextureSettings(false);
|
private static final TextureSettings TEXTURE_SETTINGS = new TextureSettings(false, false);
|
||||||
|
|
||||||
private static final int BITS_PER_HEX_DIGIT = 4;
|
private static final int BITS_PER_HEX_DIGIT = 4;
|
||||||
private static final int PREFIX_LENGTH = "0000:".length();
|
private static final int PREFIX_LENGTH = "0000:".length();
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
|
import glm.mat._4.Mat4;
|
||||||
|
import glm.vec._3.Vec3;
|
||||||
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
|
|
||||||
|
public class Background extends GUILayer {
|
||||||
|
|
||||||
|
protected Texture backgroundTexture;
|
||||||
|
|
||||||
|
public Background(String name, Layout layout, Texture inTexture) {
|
||||||
|
super(name, layout);
|
||||||
|
|
||||||
|
backgroundTexture = inTexture;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assemble(RenderTarget target) {
|
||||||
|
getRoot().setBounds(0, 0, getWidth(), getHeight());
|
||||||
|
getRoot().invalidate();
|
||||||
|
target.pushTransform(new Mat4(1).translate(new Vec3(0,0,500)));
|
||||||
|
target.drawTexture(0, 0, getWidth(), getHeight(), backgroundTexture);
|
||||||
|
target.popTransform();
|
||||||
|
getRoot().assemble(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -54,18 +54,17 @@ public abstract class BasicButton extends Component {
|
|||||||
reassembleAt(ARTrigger.HOVER, ARTrigger.FOCUS, ARTrigger.ENABLE);
|
reassembleAt(ARTrigger.HOVER, ARTrigger.FOCUS, ARTrigger.ENABLE);
|
||||||
|
|
||||||
// Click triggers
|
// Click triggers
|
||||||
addListener(KeyEvent.class, e -> {
|
addInputListener(KeyEvent.class, e -> {
|
||||||
if (e.isRepeat()) {
|
if (e.isRepeat())
|
||||||
return false;
|
return;
|
||||||
} else if (
|
|
||||||
|
if (
|
||||||
e.isLeftMouseButton() ||
|
e.isLeftMouseButton() ||
|
||||||
e.getKey() == GLFW.GLFW_KEY_SPACE ||
|
e.getKey() == GLFW.GLFW_KEY_SPACE ||
|
||||||
e.getKey() == GLFW.GLFW_KEY_ENTER
|
e.getKey() == GLFW.GLFW_KEY_ENTER
|
||||||
) {
|
) {
|
||||||
setPressed(e.isPress());
|
setPressed(e.isPress());
|
||||||
return true;
|
e.consume();
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -23,6 +23,10 @@ import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
|||||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||||
import ru.windcorp.progressia.client.graphics.Colors;
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
|
|
||||||
|
/** Class for a traditional button that gets clicked to activate
|
||||||
|
*
|
||||||
|
* @author opfromthestart
|
||||||
|
*/
|
||||||
public class Button extends BasicButton {
|
public class Button extends BasicButton {
|
||||||
|
|
||||||
public Button(String name, String label, Font labelFont) {
|
public Button(String name, String label, Font labelFont) {
|
||||||
@ -51,9 +55,7 @@ public class Button extends BasicButton {
|
|||||||
|
|
||||||
// Inside area
|
// Inside area
|
||||||
|
|
||||||
if (isPressed()) {
|
if (!isPressed()) {
|
||||||
// Do nothing
|
|
||||||
} else {
|
|
||||||
Vec4 backgroundColor;
|
Vec4 backgroundColor;
|
||||||
if (isHovered() && isEnabled()) {
|
if (isHovered() && isEnabled()) {
|
||||||
backgroundColor = Colors.HOVER_BLUE;
|
backgroundColor = Colors.HOVER_BLUE;
|
||||||
|
@ -25,8 +25,6 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
import com.google.common.eventbus.EventBus;
|
import com.google.common.eventbus.EventBus;
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
|
||||||
@ -39,9 +37,10 @@ import ru.windcorp.progressia.client.graphics.gui.event.EnableEvent;
|
|||||||
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
|
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.InputListener;
|
import ru.windcorp.progressia.client.graphics.input.bus.InputListener;
|
||||||
import ru.windcorp.progressia.common.util.Named;
|
import ru.windcorp.progressia.common.util.Named;
|
||||||
@ -55,7 +54,7 @@ public class Component extends Named {
|
|||||||
private Component parent = null;
|
private Component parent = null;
|
||||||
|
|
||||||
private EventBus eventBus = null;
|
private EventBus eventBus = null;
|
||||||
private InputBus inputBus = null;
|
private final InputBus inputBus = new InputBus(this);
|
||||||
|
|
||||||
private int x, y;
|
private int x, y;
|
||||||
private int width, height;
|
private int width, height;
|
||||||
@ -76,6 +75,9 @@ public class Component extends Named {
|
|||||||
|
|
||||||
public Component(String name) {
|
public Component(String name) {
|
||||||
super(name);
|
super(name);
|
||||||
|
|
||||||
|
// Update hover flag when cursor moves
|
||||||
|
addInputListener(CursorMoveEvent.class, this::updateHoverFlag, InputBus.Option.ALWAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Component getParent() {
|
public Component getParent() {
|
||||||
@ -521,6 +523,10 @@ public class Component extends Named {
|
|||||||
dispatchEvent(new HoverEvent(this, isHovered));
|
dispatchEvent(new HoverEvent(this, isHovered));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateHoverFlag(CursorMoveEvent e) {
|
||||||
|
setHovered(contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY()));
|
||||||
|
}
|
||||||
|
|
||||||
public void addListener(Object listener) {
|
public void addListener(Object listener) {
|
||||||
if (eventBus == null) {
|
if (eventBus == null) {
|
||||||
@ -542,121 +548,32 @@ public class Component extends Named {
|
|||||||
eventBus.post(event);
|
eventBus.post(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends InputEvent> void addListener(
|
public <T extends InputEvent> void addInputListener(Class<? extends T> type, InputListener<T> listener, InputBus.Option... options) {
|
||||||
Class<? extends T> type,
|
inputBus.register(type, listener, options);
|
||||||
boolean handlesConsumed,
|
}
|
||||||
InputListener<T> listener
|
|
||||||
) {
|
public void addKeyListener(KeyMatcher matcher, InputListener<? super KeyEvent> listener, InputBus.Option... options) {
|
||||||
if (inputBus == null) {
|
inputBus.register(matcher, listener, options);
|
||||||
inputBus = new InputBus();
|
|
||||||
}
|
|
||||||
|
|
||||||
inputBus.register(type, handlesConsumed, listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends InputEvent> void addListener(Class<? extends T> type, InputListener<T> listener) {
|
public void removeInputListener(InputListener<?> listener) {
|
||||||
if (inputBus == null) {
|
|
||||||
inputBus = new InputBus();
|
|
||||||
}
|
|
||||||
|
|
||||||
inputBus.register(type, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeListener(InputListener<?> listener) {
|
|
||||||
if (inputBus != null) {
|
if (inputBus != null) {
|
||||||
inputBus.unregister(listener);
|
inputBus.unregister(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleInput(Input input) {
|
protected boolean passInputToChildren(InputEvent e) {
|
||||||
if (inputBus != null && isEnabled()) {
|
return true;
|
||||||
inputBus.dispatch(input);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispatchInput(Input input) {
|
InputBus getInputBus() {
|
||||||
try {
|
return inputBus;
|
||||||
switch (input.getTarget()) {
|
|
||||||
case FOCUSED:
|
|
||||||
dispatchInputToFocused(input);
|
|
||||||
break;
|
|
||||||
case HOVERED:
|
|
||||||
dispatchInputToHovered(input);
|
|
||||||
break;
|
|
||||||
case ALL:
|
|
||||||
default:
|
|
||||||
dispatchInputToAll(input);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw CrashReports.report(e, "Could not dispatch input to Component %s", this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dispatchInputToFocused(Input input) {
|
|
||||||
Component c = findFocused();
|
|
||||||
|
|
||||||
if (c == null)
|
|
||||||
return;
|
|
||||||
if (attemptFocusTransfer(input, c))
|
|
||||||
return;
|
|
||||||
|
|
||||||
while (c != null) {
|
|
||||||
c.handleInput(input);
|
|
||||||
c = c.getParent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void dispatchInputToHovered(Input input) {
|
|
||||||
getChildren().forEach(child -> {
|
|
||||||
if (child.containsCursor()) {
|
|
||||||
child.setHovered(true);
|
|
||||||
|
|
||||||
if (!input.isConsumed()) {
|
|
||||||
child.dispatchInput(input);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
child.setHovered(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
handleInput(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void dispatchInputToAll(Input input) {
|
|
||||||
getChildren().forEach(c -> c.dispatchInput(input));
|
|
||||||
handleInput(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean attemptFocusTransfer(Input input, Component focused) {
|
|
||||||
if (input.isConsumed())
|
|
||||||
return false;
|
|
||||||
if (!(input.getEvent() instanceof KeyEvent))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
KeyEvent keyInput = (KeyEvent) input.getEvent();
|
|
||||||
|
|
||||||
if (keyInput.getKey() == GLFW.GLFW_KEY_TAB && !keyInput.isRelease()) {
|
|
||||||
input.consume();
|
|
||||||
if (keyInput.hasShift()) {
|
|
||||||
focused.focusPrevious();
|
|
||||||
} else {
|
|
||||||
focused.focusNext();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized boolean contains(int x, int y) {
|
public synchronized boolean contains(int x, int y) {
|
||||||
return x >= getX() && x < getX() + getWidth() && y >= getY() && y < getY() + getHeight();
|
return x >= getX() && x < getX() + getWidth() && y >= getY() && y < getY() + getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean containsCursor() {
|
|
||||||
return contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void requestReassembly() {
|
public void requestReassembly() {
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
parent.requestReassembly();
|
parent.requestReassembly();
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import glm.vec._2.d.Vec2d;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.event.DragEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.event.DragStartEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.event.DragStopEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
|
||||||
|
|
||||||
|
public class DragManager {
|
||||||
|
|
||||||
|
private Component component;
|
||||||
|
|
||||||
|
private boolean isDragged = false;
|
||||||
|
private final Vec2d change = new Vec2d();
|
||||||
|
|
||||||
|
public void install(Component c) {
|
||||||
|
Objects.requireNonNull(c, "c");
|
||||||
|
if (c == component) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (component != null) {
|
||||||
|
throw new IllegalStateException("Already installed on " + component + "; attempted to install on " + c);
|
||||||
|
}
|
||||||
|
|
||||||
|
component = c;
|
||||||
|
|
||||||
|
c.addInputListener(CursorMoveEvent.class, this::onCursorMove, InputBus.Option.ALWAYS);
|
||||||
|
c.addKeyListener(KeyMatcher.LMB, this::onLMB, InputBus.Option.ALWAYS, InputBus.Option.IGNORE_ACTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCursorMove(CursorMoveEvent e) {
|
||||||
|
if (isDragged) {
|
||||||
|
Vec2d currentChange = e.getChange(null);
|
||||||
|
change.add(currentChange);
|
||||||
|
component.dispatchEvent(new DragEvent(component, currentChange, change));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onLMB(KeyEvent e) {
|
||||||
|
if (isDragged && e.isRelease()) {
|
||||||
|
|
||||||
|
isDragged = false;
|
||||||
|
component.dispatchEvent(new DragStopEvent(component, change));
|
||||||
|
|
||||||
|
} else if (!isDragged && !e.isConsumed() && e.isPress() && component.isHovered()) {
|
||||||
|
|
||||||
|
isDragged = true;
|
||||||
|
change.set(0, 0);
|
||||||
|
component.dispatchEvent(new DragStartEvent(component));
|
||||||
|
e.consume();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -15,12 +15,20 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.gui;
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer;
|
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer;
|
||||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
|
||||||
|
import ru.windcorp.progressia.common.util.LowOverheadCache;
|
||||||
|
import ru.windcorp.progressia.common.util.StashingStack;
|
||||||
|
|
||||||
public abstract class GUILayer extends AssembledFlatLayer {
|
public abstract class GUILayer extends AssembledFlatLayer {
|
||||||
|
|
||||||
@ -33,7 +41,9 @@ public abstract class GUILayer extends AssembledFlatLayer {
|
|||||||
|
|
||||||
public GUILayer(String name, Layout layout) {
|
public GUILayer(String name, Layout layout) {
|
||||||
super(name);
|
super(name);
|
||||||
|
|
||||||
getRoot().setLayout(layout);
|
getRoot().setLayout(layout);
|
||||||
|
getRoot().addInputListener(KeyEvent.class, this::attemptFocusTransfer, InputBus.Option.IGNORE_FOCUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Component getRoot() {
|
public Component getRoot() {
|
||||||
@ -47,9 +57,85 @@ public abstract class GUILayer extends AssembledFlatLayer {
|
|||||||
getRoot().assemble(target);
|
getRoot().assemble(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stack frame for {@link #handleInput(InputEvent)}.
|
||||||
|
*/
|
||||||
|
private static class EventHandlingFrame {
|
||||||
|
Component component;
|
||||||
|
Iterator<Component> children;
|
||||||
|
|
||||||
|
void init(Component c) {
|
||||||
|
component = c;
|
||||||
|
children = c.getChildren().iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
component = null;
|
||||||
|
children = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stacks for {@link #handleInput(InputEvent)}.
|
||||||
|
*/
|
||||||
|
private final LowOverheadCache<StashingStack<EventHandlingFrame>> pathCache = new LowOverheadCache<>(
|
||||||
|
() -> new StashingStack<>(64, EventHandlingFrame::new)
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is essentially a depth-first iteration of the component tree. The
|
||||||
|
* recursive procedure has been unrolled to reduce call stack length.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void handleInput(Input input) {
|
public void handleInput(InputEvent event) {
|
||||||
getRoot().dispatchInput(input);
|
StashingStack<EventHandlingFrame> path = pathCache.grab();
|
||||||
|
|
||||||
|
if (!path.isEmpty()) {
|
||||||
|
throw new IllegalStateException("path is not empty: " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
path.push().init(root);
|
||||||
|
|
||||||
|
while (!path.isEmpty()) {
|
||||||
|
|
||||||
|
Iterator<Component> it = path.peek().children;
|
||||||
|
if (it.hasNext()) {
|
||||||
|
|
||||||
|
Component c = it.next();
|
||||||
|
|
||||||
|
if (c.isEnabled()) {
|
||||||
|
if (c.getChildren().isEmpty() || !c.passInputToChildren(event)) {
|
||||||
|
c.getInputBus().dispatch(event);
|
||||||
|
} else {
|
||||||
|
path.push().init(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
path.peek().component.getInputBus().dispatch(event);
|
||||||
|
path.pop().reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pathCache.release(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attemptFocusTransfer(KeyEvent e) {
|
||||||
|
Component focused = getRoot().findFocused();
|
||||||
|
|
||||||
|
if (focused == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.getKey() == GLFW.GLFW_KEY_TAB && !e.isRelease()) {
|
||||||
|
e.consume();
|
||||||
|
if (e.hasShift()) {
|
||||||
|
focused.focusPrevious();
|
||||||
|
} else {
|
||||||
|
focused.focusNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -104,26 +104,22 @@ public class RadioButton extends BasicButton {
|
|||||||
group.addChild(basicChild);
|
group.addChild(basicChild);
|
||||||
addChild(group);
|
addChild(group);
|
||||||
|
|
||||||
addListener(KeyEvent.class, e -> {
|
addInputListener(KeyEvent.class, e -> {
|
||||||
if (e.isRelease())
|
if (e.isRelease()) return;
|
||||||
return false;
|
|
||||||
|
|
||||||
if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) {
|
if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) {
|
||||||
if (this.group != null) {
|
if (this.group != null) {
|
||||||
this.group.selectPrevious();
|
this.group.selectPrevious();
|
||||||
this.group.getSelected().takeFocus();
|
this.group.getSelected().takeFocus();
|
||||||
}
|
}
|
||||||
|
e.consume();
|
||||||
return true;
|
|
||||||
} else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
|
} else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
|
||||||
if (this.group != null) {
|
if (this.group != null) {
|
||||||
this.group.selectNext();
|
this.group.selectNext();
|
||||||
this.group.getSelected().takeFocus();
|
this.group.getSelected().takeFocus();
|
||||||
}
|
}
|
||||||
return true;
|
e.consume();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
addAction(b -> setChecked(true));
|
addAction(b -> setChecked(true));
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
|
|
||||||
|
public class TextureComponent extends Component {
|
||||||
|
|
||||||
|
private final Texture texture;
|
||||||
|
|
||||||
|
public TextureComponent(String name, Texture texture2) {
|
||||||
|
super(name);
|
||||||
|
|
||||||
|
texture = texture2;
|
||||||
|
setPreferredSize(texture.getSprite().getWidth(),texture.getSprite().getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assembleSelf(RenderTarget target)
|
||||||
|
{
|
||||||
|
target.drawTexture(getX(), getY(), getWidth(), getHeight(), texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui.event;
|
||||||
|
|
||||||
|
import glm.vec._2.d.Vec2d;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||||
|
|
||||||
|
public class DragEvent extends ComponentEvent {
|
||||||
|
|
||||||
|
private final Vec2d currentChange = new Vec2d();
|
||||||
|
private final Vec2d totalChange = new Vec2d();
|
||||||
|
|
||||||
|
public DragEvent(Component component, Vec2d currentChange, Vec2d totalChange) {
|
||||||
|
super(component);
|
||||||
|
this.currentChange.set(currentChange.x, currentChange.y);
|
||||||
|
this.totalChange.set(totalChange.x, totalChange.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vec2d getCurrentChange() {
|
||||||
|
return currentChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getCurrentChangeX() {
|
||||||
|
return currentChange.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getCurrentChangeY() {
|
||||||
|
return currentChange.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vec2d getTotalChange() {
|
||||||
|
return totalChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getTotalChangeX() {
|
||||||
|
return totalChange.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getTotalChangeY() {
|
||||||
|
return totalChange.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui.event;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||||
|
|
||||||
|
public class DragStartEvent extends ComponentEvent {
|
||||||
|
|
||||||
|
public DragStartEvent(Component component) {
|
||||||
|
super(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -15,48 +15,30 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui.event;
|
||||||
package ru.windcorp.progressia.client.graphics.input.bus;
|
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
import glm.vec._2.d.Vec2d;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||||
|
|
||||||
public class Input {
|
public class DragStopEvent extends ComponentEvent {
|
||||||
|
|
||||||
public static enum Target {
|
private final Vec2d totalChange = new Vec2d();
|
||||||
FOCUSED, HOVERED, ALL
|
|
||||||
|
public DragStopEvent(Component component, Vec2d totalChange) {
|
||||||
|
super(component);
|
||||||
|
this.totalChange.set(totalChange.x, totalChange.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InputEvent event;
|
public Vec2d getTotalChange() {
|
||||||
|
return totalChange;
|
||||||
private boolean isConsumed;
|
|
||||||
|
|
||||||
private Target target;
|
|
||||||
|
|
||||||
protected void initialize(InputEvent event, Target target) {
|
|
||||||
this.event = event;
|
|
||||||
this.target = target;
|
|
||||||
|
|
||||||
this.isConsumed = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public InputEvent getEvent() {
|
public double getTotalChangeX() {
|
||||||
return event;
|
return totalChange.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isConsumed() {
|
public double getTotalChangeY() {
|
||||||
return isConsumed;
|
return totalChange.y;
|
||||||
}
|
|
||||||
|
|
||||||
public void setConsumed(boolean isConsumed) {
|
|
||||||
this.isConsumed = isConsumed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void consume() {
|
|
||||||
setConsumed(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Target getTarget() {
|
|
||||||
return target;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -27,8 +27,8 @@ import ru.windcorp.progressia.client.graphics.gui.Layout;
|
|||||||
|
|
||||||
public class LayoutAlign implements Layout {
|
public class LayoutAlign implements Layout {
|
||||||
|
|
||||||
private final int margin;
|
protected final int margin;
|
||||||
private double alignX, alignY;
|
protected double alignX, alignY;
|
||||||
|
|
||||||
public LayoutAlign(double alignX, double alignY, int margin) {
|
public LayoutAlign(double alignX, double alignY, int margin) {
|
||||||
this.alignX = alignX;
|
this.alignX = alignX;
|
||||||
@ -72,7 +72,7 @@ public class LayoutAlign implements Layout {
|
|||||||
Vec2i result = new Vec2i(0, 0);
|
Vec2i result = new Vec2i(0, 0);
|
||||||
|
|
||||||
c.getChildren().stream()
|
c.getChildren().stream()
|
||||||
.map(child -> child.getPreferredSize())
|
.map(Component::getPreferredSize)
|
||||||
.forEach(size -> {
|
.forEach(size -> {
|
||||||
result.x = max(size.x, result.x);
|
result.x = max(size.x, result.x);
|
||||||
result.y = max(size.y, result.y);
|
result.y = max(size.y, result.y);
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
package ru.windcorp.progressia.client.graphics.gui.layout;
|
||||||
|
|
||||||
|
import glm.vec._2.i.Vec2i;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Layout;
|
||||||
|
import static java.lang.Math.max;
|
||||||
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
|
public class LayoutEdges implements Layout {
|
||||||
|
|
||||||
|
private final int margin;
|
||||||
|
|
||||||
|
public LayoutEdges(int margin) {
|
||||||
|
this.margin = margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void layout(Component c) {
|
||||||
|
for (int i=0;i<2;i++)
|
||||||
|
{
|
||||||
|
Component child = c.getChild(i);
|
||||||
|
|
||||||
|
Vec2i size = child.getPreferredSize();
|
||||||
|
|
||||||
|
int cWidth = c.getWidth() - 2 * margin;
|
||||||
|
int cHeight = c.getHeight() - 2 * margin;
|
||||||
|
|
||||||
|
size.x = min(size.x, cWidth);
|
||||||
|
size.y = min(size.y, cHeight);
|
||||||
|
|
||||||
|
if (i==0) {
|
||||||
|
child.setBounds(
|
||||||
|
c.getX() + margin,
|
||||||
|
c.getY(),
|
||||||
|
size
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
child.setBounds(
|
||||||
|
1920 - size.x - margin,
|
||||||
|
c.getY(),
|
||||||
|
size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Vec2i calculatePreferredSize(Component c) {
|
||||||
|
Vec2i result = new Vec2i(1920,0);
|
||||||
|
c.getChildren().stream()
|
||||||
|
.map(Component::getPreferredSize)
|
||||||
|
.forEach(size -> result.y = max(Math.abs(size.y), result.y));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@ -33,7 +33,6 @@ import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
|
|||||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
||||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
|
||||||
import ru.windcorp.progressia.client.localization.MutableString;
|
import ru.windcorp.progressia.client.localization.MutableString;
|
||||||
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
||||||
|
|
||||||
@ -97,11 +96,9 @@ public class MenuLayer extends GUILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleInput(Input input) {
|
public void handleInput(InputEvent event) {
|
||||||
|
|
||||||
if (!input.isConsumed()) {
|
if (!event.isConsumed()) {
|
||||||
InputEvent event = input.getEvent();
|
|
||||||
|
|
||||||
if (event instanceof KeyEvent) {
|
if (event instanceof KeyEvent) {
|
||||||
KeyEvent keyEvent = (KeyEvent) event;
|
KeyEvent keyEvent = (KeyEvent) event;
|
||||||
if (keyEvent.isPress() && keyEvent.getKey() == GLFW.GLFW_KEY_ESCAPE) {
|
if (keyEvent.isPress() && keyEvent.getKey() == GLFW.GLFW_KEY_ESCAPE) {
|
||||||
@ -110,8 +107,8 @@ public class MenuLayer extends GUILayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
super.handleInput(input);
|
super.handleInput(event);
|
||||||
input.consume();
|
event.consume();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.input;
|
package ru.windcorp.progressia.client.graphics.input;
|
||||||
|
|
||||||
import glm.vec._2.Vec2;
|
|
||||||
import glm.vec._2.d.Vec2d;
|
import glm.vec._2.d.Vec2d;
|
||||||
|
|
||||||
public class CursorMoveEvent extends CursorEvent {
|
public class CursorMoveEvent extends CursorEvent {
|
||||||
@ -81,7 +80,10 @@ public class CursorMoveEvent extends CursorEvent {
|
|||||||
return getNewY() - getPreviousY();
|
return getNewY() - getPreviousY();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vec2 getChange(Vec2 result) {
|
public Vec2d getChange(Vec2d result) {
|
||||||
|
if (result == null) {
|
||||||
|
result = new Vec2d();
|
||||||
|
}
|
||||||
return result.set(getChangeX(), getChangeY());
|
return result.set(getChangeX(), getChangeY());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,13 +15,35 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.input;
|
package ru.windcorp.progressia.client.graphics.input;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An instance of user input.
|
||||||
|
* <p>
|
||||||
|
* User input events are typically generated by graphics backend between frames
|
||||||
|
* and passed to the graphics layers from top to bottom. Layers that use
|
||||||
|
* {@link Component}s will forward this event through the Component hierarchy.
|
||||||
|
* <p>
|
||||||
|
* Events have a {@code consumed} flag. A freshly-generated event will have this
|
||||||
|
* flag set to {@code false}. Event listeners that process the event will
|
||||||
|
* usually choose to raise the flag ("consume the event") to ask future
|
||||||
|
* listeners to ignore this event. This is done to avoid multiple UI interfaces
|
||||||
|
* reacting to single input. By default, listeners will not receive consumed
|
||||||
|
* events; however, some listeners may choose to receive, handle and even
|
||||||
|
* un-consume the event.
|
||||||
|
* <p>
|
||||||
|
* {@code InputEvent} objects may be reused for future input events after their
|
||||||
|
* processing is complete; to obtain a static copy, use {@link #snapshot()}.
|
||||||
|
*/
|
||||||
public abstract class InputEvent {
|
public abstract class InputEvent {
|
||||||
|
|
||||||
private double time;
|
private double time;
|
||||||
|
|
||||||
|
private boolean isConsumed = false;
|
||||||
|
|
||||||
public InputEvent(double time) {
|
public InputEvent(double time) {
|
||||||
this.time = time;
|
this.time = time;
|
||||||
}
|
}
|
||||||
@ -36,4 +58,16 @@ public abstract class InputEvent {
|
|||||||
|
|
||||||
public abstract InputEvent snapshot();
|
public abstract InputEvent snapshot();
|
||||||
|
|
||||||
|
public boolean isConsumed() {
|
||||||
|
return isConsumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConsumed(boolean isConsumed) {
|
||||||
|
this.isConsumed = isConsumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void consume() {
|
||||||
|
setConsumed(true);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,16 +18,75 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.input;
|
package ru.windcorp.progressia.client.graphics.input;
|
||||||
|
|
||||||
import java.util.function.Predicate;
|
import java.util.Map;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
public class KeyMatcher {
|
public class KeyMatcher {
|
||||||
|
|
||||||
|
private static final Pattern DECLAR_SPLIT_REGEX = Pattern.compile("\\s*\\+\\s*");
|
||||||
|
private static final Map<String, Integer> MOD_TOKENS = ImmutableMap.of(
|
||||||
|
"SHIFT", GLFW.GLFW_MOD_SHIFT,
|
||||||
|
"CONTROL", GLFW.GLFW_MOD_CONTROL,
|
||||||
|
"ALT", GLFW.GLFW_MOD_ALT,
|
||||||
|
"SUPER", GLFW.GLFW_MOD_SUPER
|
||||||
|
);
|
||||||
|
|
||||||
|
public static final KeyMatcher LMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_LEFT);
|
||||||
|
public static final KeyMatcher RMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_RIGHT);
|
||||||
|
public static final KeyMatcher MMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_MIDDLE);
|
||||||
|
|
||||||
private final int key;
|
private final int key;
|
||||||
private final int mods;
|
private final int mods;
|
||||||
|
|
||||||
protected KeyMatcher(int key, int mods) {
|
public KeyMatcher(int key, int mods) {
|
||||||
|
this.key = key;
|
||||||
|
this.mods = mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyMatcher(int key) {
|
||||||
|
this.key = key;
|
||||||
|
this.mods = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyMatcher(String declar) {
|
||||||
|
String[] tokens = DECLAR_SPLIT_REGEX.split(declar);
|
||||||
|
if (tokens.length == 0) {
|
||||||
|
throw new IllegalArgumentException("No tokens found in \"" + declar + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
int key = -1;
|
||||||
|
int mods = 0;
|
||||||
|
|
||||||
|
for (String token : tokens) {
|
||||||
|
token = token.toUpperCase();
|
||||||
|
|
||||||
|
if (MOD_TOKENS.containsKey(token)) {
|
||||||
|
int mod = MOD_TOKENS.get(token);
|
||||||
|
if ((mods & mod) != 0) {
|
||||||
|
throw new IllegalArgumentException("Duplicate modifier \"" + token + "\" in \"" + declar + "\"");
|
||||||
|
}
|
||||||
|
mods |= mod;
|
||||||
|
} else if (key != -1) {
|
||||||
|
throw new IllegalArgumentException("Too many non-modifier tokens in \"" + declar + "\": maximum one key, first offender: \"" + token + "\"");
|
||||||
|
} else {
|
||||||
|
token = token.replace(' ', '_');
|
||||||
|
|
||||||
|
if (token.startsWith("KEYPAD_")) {
|
||||||
|
token = "KP_" + token.substring("KEYPAD_".length());
|
||||||
|
}
|
||||||
|
|
||||||
|
key = Keys.getCode(token);
|
||||||
|
|
||||||
|
if (key == -1) {
|
||||||
|
throw new IllegalArgumentException("Unknown token \"" + token + "\" in \"" + declar + "\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.mods = mods;
|
this.mods = mods;
|
||||||
}
|
}
|
||||||
@ -42,6 +101,15 @@ public class KeyMatcher {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean matchesIgnoringAction(KeyEvent event) {
|
||||||
|
if (event.getKey() != getKey())
|
||||||
|
return false;
|
||||||
|
if ((event.getMods() & getMods()) != getMods())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public int getKey() {
|
public int getKey() {
|
||||||
return key;
|
return key;
|
||||||
@ -50,49 +118,25 @@ public class KeyMatcher {
|
|||||||
public int getMods() {
|
public int getMods() {
|
||||||
return mods;
|
return mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KeyMatcher.Builder of(int key) {
|
public KeyMatcher with(int modifier) {
|
||||||
return new KeyMatcher.Builder(key);
|
return new KeyMatcher(key, mods | modifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public KeyMatcher withShift() {
|
||||||
|
return with(GLFW.GLFW_MOD_SHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
private final int key;
|
public KeyMatcher withCtrl() {
|
||||||
private int mods = 0;
|
return with(GLFW.GLFW_MOD_CONTROL);
|
||||||
|
}
|
||||||
|
|
||||||
public Builder(int key) {
|
public KeyMatcher withAlt() {
|
||||||
this.key = key;
|
return with(GLFW.GLFW_MOD_ALT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder with(int modifier) {
|
|
||||||
this.mods += modifier;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withShift() {
|
|
||||||
return with(GLFW.GLFW_MOD_SHIFT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withCtrl() {
|
|
||||||
return with(GLFW.GLFW_MOD_CONTROL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withAlt() {
|
|
||||||
return with(GLFW.GLFW_MOD_ALT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withSuper() {
|
|
||||||
return with(GLFW.GLFW_MOD_SUPER);
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyMatcher build() {
|
|
||||||
return new KeyMatcher(key, mods);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Predicate<KeyEvent> matcher() {
|
|
||||||
return build()::matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public KeyMatcher withSuper() {
|
||||||
|
return with(GLFW.GLFW_MOD_SUPER);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,7 @@ public class Keys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int getCode(String internalName) {
|
public static int getCode(String internalName) {
|
||||||
if (NAMES_TO_CODES.containsKey(internalName)) {
|
if (!NAMES_TO_CODES.containsKey(internalName)) {
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
return NAMES_TO_CODES.get(internalName);
|
return NAMES_TO_CODES.get(internalName);
|
||||||
|
@ -15,73 +15,401 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.input.bus;
|
package ru.windcorp.progressia.client.graphics.input.bus;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import ru.windcorp.jputil.ArrayUtil;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.CursorEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.WheelEvent;
|
||||||
|
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event bus optionally related to a {@link Component} that delivers input
|
||||||
|
* events to input listeners. This bus may skip listeners based on circumstance;
|
||||||
|
* behavior can be customized for each listener with {@link Option}s.
|
||||||
|
* <p>
|
||||||
|
* By default, events are filtered by four checks before being delivered to each
|
||||||
|
* listener:
|
||||||
|
* <ol>
|
||||||
|
* <li><em>Consumption check</em>: unless {@link Option#RECEIVE_CONSUMED
|
||||||
|
* RECEIVE_CONSUMED} is set, events that are consumed will not be
|
||||||
|
* delivered.</li>
|
||||||
|
* <li><em>Hover check</em>: for certain event types (for example,
|
||||||
|
* {@link WheelEvent} or {@link KeyEvent} that {@link KeyEvent#isMouse()
|
||||||
|
* isMouse()}), the event will only be delivered if the component is hovered.
|
||||||
|
* This check may be bypassed with option {@link Option#IGNORE_HOVER
|
||||||
|
* IGNORE_HOVER} or made mandatory for all events with
|
||||||
|
* {@link Option#REQUIRE_HOVER REQUIRE_HOVER}. Hover check automatically
|
||||||
|
* succeeds if no component is provided.</li>
|
||||||
|
* <li><em>Focus check</em>: for certain event types (for example,
|
||||||
|
* {@link KeyEvent} that {@code !isMouse()}), the event will only be delivered
|
||||||
|
* if the component has focus. This check may be bypassed with option
|
||||||
|
* {@link Option#IGNORE_FOCUS IGNORE_FOCUS} or made mandatory for all events
|
||||||
|
* with {@link Option#REQUIRE_FOCUS REQUIRE_FOCUS}. Focus check automatically
|
||||||
|
* succeeds if no component is provided.</li>
|
||||||
|
* <li><em>Type check</em>: events of type {@code E} are only delivered to
|
||||||
|
* listeners registered with event type {@code T} if objects of type {@code E}
|
||||||
|
* can be cast to {@code T}.</li>
|
||||||
|
* </ol>
|
||||||
|
* Checks 1-3 are bypassed when option {@link Option#ALWAYS ALWAYS} is
|
||||||
|
* specified.
|
||||||
|
*/
|
||||||
public class InputBus {
|
public class InputBus {
|
||||||
|
|
||||||
private static class WrappedListener {
|
/**
|
||||||
|
* Options that allow customization of checks for listeners.
|
||||||
|
*/
|
||||||
|
public enum Option {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ignore checks for consumed events, hover and focus; deliver event if
|
||||||
|
* at all possible. This is shorthand for {@link #RECEIVE_CONSUMED},
|
||||||
|
* {@link #IGNORE_HOVER} and {@link #IGNORE_FOCUS}.
|
||||||
|
*/
|
||||||
|
ALWAYS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive events that were previously consumed.
|
||||||
|
*/
|
||||||
|
RECEIVE_CONSUMED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not process events if the listener is registered with a component
|
||||||
|
* and the component is not hovered.
|
||||||
|
*/
|
||||||
|
REQUIRE_HOVER,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deliver events even if the event is limited to hovered components by
|
||||||
|
* default.
|
||||||
|
*/
|
||||||
|
IGNORE_HOVER,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not process events if the listener is registered with a component
|
||||||
|
* and the component is not focused.
|
||||||
|
*/
|
||||||
|
REQUIRE_FOCUS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deliver events even if the event is limited to focused components by
|
||||||
|
* default.
|
||||||
|
*/
|
||||||
|
IGNORE_FOCUS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deliver events according to
|
||||||
|
* {@link KeyMatcher#matchesIgnoringAction(KeyEvent)} rather than
|
||||||
|
* {@link KeyMatcher#matches(KeyEvent)} when a {@link KeyMatcher} is
|
||||||
|
* specified.
|
||||||
|
*/
|
||||||
|
IGNORE_ACTION;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum YesNoDefault {
|
||||||
|
YES, NO, DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener with check preferences resolved and type specified.
|
||||||
|
*/
|
||||||
|
private class WrappedListener {
|
||||||
|
|
||||||
private final Class<?> type;
|
private final Class<?> type;
|
||||||
private final boolean handleConsumed;
|
|
||||||
|
private final boolean dropIfConsumed;
|
||||||
|
private final YesNoDefault dropIfNotHovered;
|
||||||
|
private final YesNoDefault dropIfNotFocused;
|
||||||
|
|
||||||
private final InputListener<?> listener;
|
private final InputListener<?> listener;
|
||||||
|
|
||||||
public WrappedListener(
|
public WrappedListener(
|
||||||
Class<?> type,
|
Class<?> type,
|
||||||
boolean handleConsumed,
|
boolean dropIfConsumed,
|
||||||
|
YesNoDefault dropIfNotHovered,
|
||||||
|
YesNoDefault dropIfNotFocused,
|
||||||
InputListener<?> listener
|
InputListener<?> listener
|
||||||
) {
|
) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.handleConsumed = handleConsumed;
|
this.dropIfConsumed = dropIfConsumed;
|
||||||
|
this.dropIfNotHovered = dropIfNotHovered;
|
||||||
|
this.dropIfNotFocused = dropIfNotFocused;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handles(Input input) {
|
private boolean handles(InputEvent input) {
|
||||||
return (!input.isConsumed() || handleConsumed) &&
|
if (dropIfConsumed && input.isConsumed())
|
||||||
type.isInstance(input.getEvent());
|
return false;
|
||||||
|
|
||||||
|
switch (dropIfNotHovered) {
|
||||||
|
case YES:
|
||||||
|
if (!isHovered())
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case NO:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
|
||||||
|
if (isHovered())
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (input instanceof KeyEvent && ((KeyEvent) input).isMouse())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (input instanceof CursorEvent)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (input instanceof WheelEvent)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (dropIfNotFocused) {
|
||||||
|
case YES:
|
||||||
|
if (!isFocused())
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case NO:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
|
||||||
|
if (isFocused())
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (input instanceof KeyEvent && !((KeyEvent) input).isMouse())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!type.isInstance(input))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes the listener if the event is deemed appropriate by the four
|
||||||
|
* checks.
|
||||||
|
*
|
||||||
|
* @param event the event to deliver
|
||||||
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void handle(Input input) {
|
public void handle(InputEvent event) {
|
||||||
if (handles(input)) {
|
if (handles(event)) {
|
||||||
boolean consumed = ((InputListener<InputEvent>) listener)
|
// A runtime check of types has been performed; this is safe.
|
||||||
.handle(
|
InputListener<InputEvent> castListener = (InputListener<InputEvent>) listener;
|
||||||
(InputEvent) type.cast(input.getEvent())
|
|
||||||
);
|
|
||||||
|
|
||||||
input.setConsumed(consumed);
|
try {
|
||||||
|
castListener.handle(event);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw CrashReports.report(
|
||||||
|
e,
|
||||||
|
"InputListener %s for component %s has failed to receive event %s",
|
||||||
|
listener,
|
||||||
|
owner,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The component queried for focus and hover. May be {@code null}.
|
||||||
|
*/
|
||||||
|
private final Component owner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registered listeners.
|
||||||
|
*/
|
||||||
private final Collection<WrappedListener> listeners = new ArrayList<>(4);
|
private final Collection<WrappedListener> listeners = new ArrayList<>(4);
|
||||||
|
|
||||||
public void dispatch(Input input) {
|
/**
|
||||||
listeners.forEach(l -> l.handle(input));
|
* Creates a new input bus that consults the specified {@link Component} to
|
||||||
|
* determine hover and focus.
|
||||||
|
*
|
||||||
|
* @param owner the component to use for hover and focus tests
|
||||||
|
* @see #InputBus()
|
||||||
|
*/
|
||||||
|
public InputBus(Component owner) {
|
||||||
|
this.owner = Objects.requireNonNull(owner, "owner");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new input bus that assumes all hover and focus checks are
|
||||||
|
* successful.
|
||||||
|
*
|
||||||
|
* @see #InputBus(Component)
|
||||||
|
*/
|
||||||
|
public InputBus() {
|
||||||
|
this.owner = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether hover should be assumed for this event bus.
|
||||||
|
*
|
||||||
|
* @return {@code true} iff no component is linked or the linked component
|
||||||
|
* is hovered
|
||||||
|
*/
|
||||||
|
private boolean isHovered() {
|
||||||
|
return owner == null ? true : owner.isHovered();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether focus should be assumed for this event bus.
|
||||||
|
*
|
||||||
|
* @return {@code true} iff no component is linked or the linked component
|
||||||
|
* is focused
|
||||||
|
*/
|
||||||
|
private boolean isFocused() {
|
||||||
|
return owner == null ? true : owner.isFocused();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches (delivers) the provided event to all appropriate listeners.
|
||||||
|
*
|
||||||
|
* @param event the event to process
|
||||||
|
*/
|
||||||
|
public void dispatch(InputEvent event) {
|
||||||
|
Objects.requireNonNull(event, "event");
|
||||||
|
for (WrappedListener listener : listeners) {
|
||||||
|
listener.handle(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a listener on this bus.
|
||||||
|
* <p>
|
||||||
|
* {@code type} specifies the class of events that should be passed to this
|
||||||
|
* listener. Only events of types that extend, implement or equal
|
||||||
|
* {@code type} are processed.
|
||||||
|
* <p>
|
||||||
|
* Zero or more {@link Option}s may be specified to enable or disable the
|
||||||
|
* processing of certain events in certain circumstances. See
|
||||||
|
* {@linkplain InputBus class description} for a detailed breakdown of the
|
||||||
|
* checks performed and the effects of various options. When providing
|
||||||
|
* options to this method, later options override the effects of previous
|
||||||
|
* options.
|
||||||
|
* <p>
|
||||||
|
* Option {@link Option#IGNORE_ACTION IGNORE_ACTION} is ignored silently.
|
||||||
|
*
|
||||||
|
* @param type the event class to deliver
|
||||||
|
* @param listener the listener
|
||||||
|
* @param options the options for this listener
|
||||||
|
*/
|
||||||
public <T extends InputEvent> void register(
|
public <T extends InputEvent> void register(
|
||||||
Class<? extends T> type,
|
Class<? extends T> type,
|
||||||
boolean handlesConsumed,
|
InputListener<T> listener,
|
||||||
InputListener<T> listener
|
Option... options
|
||||||
) {
|
) {
|
||||||
listeners.add(new WrappedListener(type, handlesConsumed, listener));
|
Objects.requireNonNull(type, "type");
|
||||||
|
Objects.requireNonNull(listener, "listener");
|
||||||
|
|
||||||
|
boolean dropIfConsumed = true;
|
||||||
|
YesNoDefault dropIfNotHovered = YesNoDefault.DEFAULT;
|
||||||
|
YesNoDefault dropIfNotFocused = YesNoDefault.DEFAULT;
|
||||||
|
|
||||||
|
if (options != null) {
|
||||||
|
for (Option option : options) {
|
||||||
|
switch (option) {
|
||||||
|
case ALWAYS:
|
||||||
|
dropIfConsumed = false;
|
||||||
|
dropIfNotHovered = YesNoDefault.NO;
|
||||||
|
dropIfNotFocused = YesNoDefault.NO;
|
||||||
|
break;
|
||||||
|
case RECEIVE_CONSUMED:
|
||||||
|
dropIfConsumed = false;
|
||||||
|
break;
|
||||||
|
case REQUIRE_HOVER:
|
||||||
|
dropIfNotHovered = YesNoDefault.YES;
|
||||||
|
break;
|
||||||
|
case IGNORE_HOVER:
|
||||||
|
dropIfNotFocused = YesNoDefault.NO;
|
||||||
|
break;
|
||||||
|
case REQUIRE_FOCUS:
|
||||||
|
dropIfNotHovered = YesNoDefault.YES;
|
||||||
|
break;
|
||||||
|
case IGNORE_FOCUS:
|
||||||
|
dropIfNotFocused = YesNoDefault.NO;
|
||||||
|
break;
|
||||||
|
case IGNORE_ACTION:
|
||||||
|
// Ignore
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unexpected option " + option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listeners.add(new WrappedListener(type, dropIfConsumed, dropIfNotHovered, dropIfNotFocused, listener));
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends InputEvent> void register(
|
/**
|
||||||
Class<? extends T> type,
|
* Registers a {@link KeyEvent} listener on this bus. An event has to match
|
||||||
InputListener<T> listener
|
* the provided {@link KeyMatcher} to be delivered to the listener.
|
||||||
) {
|
* <p>
|
||||||
register(type, false, listener);
|
* Zero or more {@link Option}s may be specified to enable or disable the
|
||||||
|
* processing of certain events in certain circumstances. See
|
||||||
|
* {@linkplain InputBus class description} for a detailed breakdown of the
|
||||||
|
* checks performed and the effects of various options. When providing
|
||||||
|
* options to this method, later options override the effects of previous
|
||||||
|
* options.
|
||||||
|
* <p>
|
||||||
|
* Option {@link Option#IGNORE_ACTION IGNORE_ACTION} requests that events
|
||||||
|
* are delivered according to
|
||||||
|
* {@link KeyMatcher#matchesIgnoringAction(KeyEvent)} rather than
|
||||||
|
* {@link KeyMatcher#matches(KeyEvent)}.
|
||||||
|
* specified.
|
||||||
|
*
|
||||||
|
* @param matcher an event filter
|
||||||
|
* @param listener the listener
|
||||||
|
* @param options the options for this listener
|
||||||
|
*/
|
||||||
|
public void register(KeyMatcher matcher, InputListener<? super KeyEvent> listener, Option... options) {
|
||||||
|
Objects.requireNonNull(matcher, "matcher");
|
||||||
|
Objects.requireNonNull(listener, "listener");
|
||||||
|
|
||||||
|
InputListener<KeyEvent> filteringListener;
|
||||||
|
|
||||||
|
if (ArrayUtil.firstIndexOf(options, Option.IGNORE_ACTION) != -1) {
|
||||||
|
filteringListener = e -> {
|
||||||
|
if (matcher.matchesIgnoringAction(e)) {
|
||||||
|
listener.handle(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
filteringListener = e -> {
|
||||||
|
if (matcher.matches(e)) {
|
||||||
|
listener.handle(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
register(KeyEvent.class, filteringListener, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all occurrences of the provided listener from this bus.
|
||||||
|
*
|
||||||
|
* @param listener the listener to unregister
|
||||||
|
*/
|
||||||
public void unregister(InputListener<?> listener) {
|
public void unregister(InputListener<?> listener) {
|
||||||
|
if (listener == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
listeners.removeIf(l -> l.listener == listener);
|
listeners.removeIf(l -> l.listener == listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,6 @@ import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
|||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface InputListener<T extends InputEvent> {
|
public interface InputListener<T extends InputEvent> {
|
||||||
|
|
||||||
boolean handle(T event);
|
void handle(T event);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,13 +25,14 @@ import glm.vec._3.Vec3;
|
|||||||
import glm.vec._4.Vec4;
|
import glm.vec._4.Vec4;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
|
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram.WRPVertexBuilder;
|
||||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
|
||||||
public class ShapeParts {
|
public class ShapeParts {
|
||||||
|
|
||||||
private ShapeParts() {
|
private ShapeParts() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ShapePart createRectangle(
|
public static ShapePart createRectangle(
|
||||||
ShapeRenderProgram program,
|
ShapeRenderProgram program,
|
||||||
Texture texture,
|
Texture texture,
|
||||||
@ -41,25 +42,65 @@ public class ShapeParts {
|
|||||||
Vec3 height,
|
Vec3 height,
|
||||||
boolean flip
|
boolean flip
|
||||||
) {
|
) {
|
||||||
VertexBuilder builder = program.getVertexBuilder();
|
return createRectangle(program, texture, colorMultiplier, origin, width, height, flip, null);
|
||||||
|
}
|
||||||
|
|
||||||
builder.addVertex(
|
public static ShapePart createRectangle(
|
||||||
origin,
|
ShapeRenderProgram program,
|
||||||
colorMultiplier,
|
Texture texture,
|
||||||
new Vec2(0, 0)
|
Vec4 colorMultiplier,
|
||||||
).addVertex(
|
Vec3 origin,
|
||||||
origin.add_(height),
|
Vec3 width,
|
||||||
colorMultiplier,
|
Vec3 height,
|
||||||
new Vec2(0, 1)
|
boolean flip,
|
||||||
).addVertex(
|
Vec3 forcedNormals
|
||||||
origin.add_(width),
|
) {
|
||||||
colorMultiplier,
|
VertexBuilder builder = program.getVertexBuilder();
|
||||||
new Vec2(1, 0)
|
|
||||||
).addVertex(
|
if (forcedNormals != null && builder instanceof WRPVertexBuilder) {
|
||||||
origin.add_(width).add(height),
|
((WRPVertexBuilder) builder).addVertex(
|
||||||
colorMultiplier,
|
origin,
|
||||||
new Vec2(1, 1)
|
colorMultiplier,
|
||||||
);
|
new Vec2(0, 0),
|
||||||
|
forcedNormals
|
||||||
|
);
|
||||||
|
((WRPVertexBuilder) builder).addVertex(
|
||||||
|
origin.add_(height),
|
||||||
|
colorMultiplier,
|
||||||
|
new Vec2(0, 1),
|
||||||
|
forcedNormals
|
||||||
|
);
|
||||||
|
((WRPVertexBuilder) builder).addVertex(
|
||||||
|
origin.add_(width),
|
||||||
|
colorMultiplier,
|
||||||
|
new Vec2(1, 0),
|
||||||
|
forcedNormals
|
||||||
|
);
|
||||||
|
((WRPVertexBuilder) builder).addVertex(
|
||||||
|
origin.add_(width).add(height),
|
||||||
|
colorMultiplier,
|
||||||
|
new Vec2(1, 1),
|
||||||
|
forcedNormals
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
builder.addVertex(
|
||||||
|
origin,
|
||||||
|
colorMultiplier,
|
||||||
|
new Vec2(0, 0)
|
||||||
|
).addVertex(
|
||||||
|
origin.add_(height),
|
||||||
|
colorMultiplier,
|
||||||
|
new Vec2(0, 1)
|
||||||
|
).addVertex(
|
||||||
|
origin.add_(width),
|
||||||
|
colorMultiplier,
|
||||||
|
new Vec2(1, 0)
|
||||||
|
).addVertex(
|
||||||
|
origin.add_(width).add(height),
|
||||||
|
colorMultiplier,
|
||||||
|
new Vec2(1, 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
ShortBuffer buffer = flip ? ShortBuffer.wrap(
|
ShortBuffer buffer = flip ? ShortBuffer.wrap(
|
||||||
new short[] {
|
new short[] {
|
||||||
@ -112,6 +153,6 @@ public class ShapeParts {
|
|||||||
height,
|
height,
|
||||||
inner
|
inner
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,248 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.client.graphics.model;
|
||||||
|
|
||||||
|
import java.nio.ShortBuffer;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import glm.mat._3.Mat3;
|
||||||
|
import glm.mat._4.Mat4;
|
||||||
|
import glm.vec._2.Vec2;
|
||||||
|
import glm.vec._3.Vec3;
|
||||||
|
import glm.vec._4.Vec4;
|
||||||
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
|
||||||
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
|
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||||
|
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram.WRPVertexBuilder;
|
||||||
|
import ru.windcorp.progressia.common.util.StashingStack;
|
||||||
|
import ru.windcorp.progressia.common.util.VectorUtil;
|
||||||
|
|
||||||
|
public class ShapePrototype {
|
||||||
|
|
||||||
|
private final ShapeRenderProgram program;
|
||||||
|
|
||||||
|
private Texture texture;
|
||||||
|
|
||||||
|
private final Vec3[] positions;
|
||||||
|
private final Vec4[] colorMultipliers;
|
||||||
|
private final Vec2[] textureCoords;
|
||||||
|
private final Vec3[] forcedNormals;
|
||||||
|
|
||||||
|
private ShortBuffer indices;
|
||||||
|
private ShortBuffer flippedIndices = null;
|
||||||
|
|
||||||
|
protected static final int TRANSFORM_STACK_SIZE = 64;
|
||||||
|
private final StashingStack<Mat4> transformStack = new StashingStack<>(
|
||||||
|
TRANSFORM_STACK_SIZE,
|
||||||
|
Mat4::new
|
||||||
|
);
|
||||||
|
|
||||||
|
public ShapePrototype(
|
||||||
|
ShapeRenderProgram program,
|
||||||
|
Texture texture,
|
||||||
|
Vec3[] positions,
|
||||||
|
Vec4[] colorMultipliers,
|
||||||
|
Vec2[] textureCoords,
|
||||||
|
Vec3[] forcedNormals,
|
||||||
|
ShortBuffer indices
|
||||||
|
) {
|
||||||
|
this.program = Objects.requireNonNull(program, "program");
|
||||||
|
this.texture = texture;
|
||||||
|
this.indices = Objects.requireNonNull(indices, "indices");
|
||||||
|
|
||||||
|
Objects.requireNonNull(positions, "positions");
|
||||||
|
Objects.requireNonNull(colorMultipliers, "colorMultipliers");
|
||||||
|
Objects.requireNonNull(textureCoords, "textureCoords");
|
||||||
|
|
||||||
|
if (forcedNormals != null && !(program instanceof WorldRenderProgram)) {
|
||||||
|
throw new IllegalArgumentException("Cannot force normals on non-WorldRenderPrograms cuz javahorse stupiddd");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forcedNormals == null) {
|
||||||
|
forcedNormals = new Vec3[positions.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.positions = positions;
|
||||||
|
this.colorMultipliers = colorMultipliers;
|
||||||
|
this.textureCoords = textureCoords;
|
||||||
|
this.forcedNormals = forcedNormals;
|
||||||
|
|
||||||
|
if (positions.length != colorMultipliers.length) {
|
||||||
|
throw new IllegalArgumentException("positions.length (" + positions.length + ") != colorMultipliers.length (" + colorMultipliers + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (positions.length != textureCoords.length) {
|
||||||
|
throw new IllegalArgumentException("positions.length (" + positions.length + ") != textureCoords.length (" + textureCoords + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (positions.length != forcedNormals.length) {
|
||||||
|
throw new IllegalArgumentException("positions.length (" + positions.length + ") != forcedNormals.length (" + forcedNormals + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
transformStack.push().identity();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShapePart build() {
|
||||||
|
VertexBuilder builder = program.getVertexBuilder();
|
||||||
|
|
||||||
|
Vec3 transformedPosition = new Vec3();
|
||||||
|
|
||||||
|
for (int i = 0; i < positions.length; ++i) {
|
||||||
|
|
||||||
|
transformedPosition.set(positions[i]);
|
||||||
|
if (transformStack.getSize() > 1) {
|
||||||
|
VectorUtil.applyMat4(transformedPosition, transformStack.getHead());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forcedNormals[i] != null && builder instanceof WRPVertexBuilder) {
|
||||||
|
((WRPVertexBuilder) builder).addVertex(
|
||||||
|
transformedPosition, colorMultipliers[i], textureCoords[i], forcedNormals[i]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
builder.addVertex(transformedPosition, colorMultipliers[i], textureCoords[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ShapePart(texture, builder.assemble(), indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShapePrototype apply(Mat4 transform) {
|
||||||
|
for (Vec3 vector : positions) {
|
||||||
|
VectorUtil.applyMat4(vector, transform);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShapePrototype apply(Mat3 transform) {
|
||||||
|
for (Vec3 vector : positions) {
|
||||||
|
transform.mul(vector);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mat4 push() {
|
||||||
|
Mat4 previous = transformStack.getHead();
|
||||||
|
return transformStack.push().set(previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShapePrototype pop() {
|
||||||
|
transformStack.pop();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShapePrototype setTexture(Texture texture) {
|
||||||
|
this.texture = texture;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShapePrototype deleteTexture() {
|
||||||
|
this.texture = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShapePrototype resetColorMultiplier() {
|
||||||
|
for (Vec4 color : colorMultipliers) {
|
||||||
|
color.set(Colors.WHITE);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShapePrototype addColorMultiplier(Vec4 color) {
|
||||||
|
for (Vec4 c : colorMultipliers) {
|
||||||
|
c.mul(color);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShapePrototype flip() {
|
||||||
|
ShortBuffer tmp = indices;
|
||||||
|
indices = flippedIndices;
|
||||||
|
flippedIndices = tmp;
|
||||||
|
|
||||||
|
if (indices == null) {
|
||||||
|
int length = flippedIndices.limit();
|
||||||
|
indices = ShortBuffer.allocate(length);
|
||||||
|
for (int i = 0; i < length; ++i) {
|
||||||
|
indices.put(i, flippedIndices.get(length - i - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShapePrototype makeDoubleSided() {
|
||||||
|
int length = indices.limit();
|
||||||
|
ShortBuffer newIndices = ShortBuffer.allocate(length * 2);
|
||||||
|
|
||||||
|
for (int i = 0; i < length; ++i) {
|
||||||
|
newIndices.put(i, indices.get(i));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < length; ++i) {
|
||||||
|
newIndices.put(i + length, indices.get(length - i - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
indices = flippedIndices = newIndices;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ShapePrototype unitSquare(Texture texture, Vec4 color, ShapeRenderProgram program) {
|
||||||
|
return new ShapePrototype(
|
||||||
|
program,
|
||||||
|
texture,
|
||||||
|
new Vec3[] {
|
||||||
|
new Vec3(0, 0, 0),
|
||||||
|
new Vec3(0, 1, 0),
|
||||||
|
new Vec3(1, 0, 0),
|
||||||
|
new Vec3(1, 1, 0)
|
||||||
|
},
|
||||||
|
new Vec4[] {
|
||||||
|
new Vec4(color),
|
||||||
|
new Vec4(color),
|
||||||
|
new Vec4(color),
|
||||||
|
new Vec4(color)
|
||||||
|
},
|
||||||
|
new Vec2[] {
|
||||||
|
new Vec2(0, 0),
|
||||||
|
new Vec2(0, 1),
|
||||||
|
new Vec2(1, 0),
|
||||||
|
new Vec2(1, 1)
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
ShortBuffer.wrap(new short[] {3, 1, 0, 2, 3, 0})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ShapePrototype unitSquare(Texture texture, Vec4 color) {
|
||||||
|
return unitSquare(texture, color, WorldRenderProgram.getDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ShapePrototype unitSquare(Texture texture) {
|
||||||
|
return unitSquare(texture, Colors.WHITE, WorldRenderProgram.getDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ShapePrototype unitSquare(Vec4 color) {
|
||||||
|
return unitSquare(null, color, WorldRenderProgram.getDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ShapePrototype unitSquare() {
|
||||||
|
return unitSquare(null, Colors.WHITE, WorldRenderProgram.getDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -163,7 +163,7 @@ public class Atlases {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final TextureSettings SETTINGS = new TextureSettings(false);
|
private static final TextureSettings SETTINGS = new TextureSettings(false, false);
|
||||||
|
|
||||||
private static final Map<Resource, Sprite> LOADED = new HashMap<>();
|
private static final Map<Resource, Sprite> LOADED = new HashMap<>();
|
||||||
private static final Multimap<AtlasGroup, Atlas> ATLASES = MultimapBuilder.hashKeys().arrayListValues().build();
|
private static final Multimap<AtlasGroup, Atlas> ATLASES = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||||
|
@ -28,7 +28,7 @@ import ru.windcorp.progressia.common.util.crash.CrashReports;
|
|||||||
|
|
||||||
public class SimpleTextures {
|
public class SimpleTextures {
|
||||||
|
|
||||||
private static final TextureSettings SETTINGS = new TextureSettings(false);
|
private static final TextureSettings SETTINGS = new TextureSettings(false, false);
|
||||||
|
|
||||||
private static final Map<Resource, Texture> TEXTURES = new HashMap<>();
|
private static final Map<Resource, Texture> TEXTURES = new HashMap<>();
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ import static org.lwjgl.opengl.GL12.*;
|
|||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
class TextureData {
|
public class TextureData {
|
||||||
|
|
||||||
private final ByteBuffer data;
|
private final ByteBuffer data;
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ import javax.imageio.ImageIO;
|
|||||||
|
|
||||||
import ru.windcorp.progressia.common.resource.Resource;
|
import ru.windcorp.progressia.common.resource.Resource;
|
||||||
import ru.windcorp.progressia.common.util.BinUtil;
|
import ru.windcorp.progressia.common.util.BinUtil;
|
||||||
|
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||||
|
|
||||||
public class TextureLoader {
|
public class TextureLoader {
|
||||||
|
|
||||||
@ -40,9 +41,17 @@ public class TextureLoader {
|
|||||||
|
|
||||||
int width = readResult.getWidth();
|
int width = readResult.getWidth();
|
||||||
int height = readResult.getHeight();
|
int height = readResult.getHeight();
|
||||||
|
|
||||||
|
int bufferWidth;
|
||||||
|
int bufferHeight;
|
||||||
|
|
||||||
int bufferWidth = BinUtil.roundToGreaterPowerOf2(width);
|
if (settings.allocateExactBuffer()) {
|
||||||
int bufferHeight = BinUtil.roundToGreaterPowerOf2(height);
|
bufferWidth = width;
|
||||||
|
bufferHeight = height;
|
||||||
|
} else {
|
||||||
|
bufferWidth = BinUtil.roundToGreaterPowerOf2(width);
|
||||||
|
bufferHeight = BinUtil.roundToGreaterPowerOf2(height);
|
||||||
|
}
|
||||||
|
|
||||||
WritableRaster raster = TextureUtil.createRaster(
|
WritableRaster raster = TextureUtil.createRaster(
|
||||||
bufferWidth,
|
bufferWidth,
|
||||||
@ -99,7 +108,12 @@ public class TextureLoader {
|
|||||||
TextureSettings settings
|
TextureSettings settings
|
||||||
)
|
)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
return loadPixels(resource.getInputStream(), settings);
|
|
||||||
|
InputStream stream = resource.getInputStream();
|
||||||
|
if (stream == null) {
|
||||||
|
throw CrashReports.report(null, "Texture \"%s\" not found", resource.getName());
|
||||||
|
}
|
||||||
|
return loadPixels(stream, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextureLoader() {
|
private TextureLoader() {
|
||||||
|
@ -21,13 +21,19 @@ package ru.windcorp.progressia.client.graphics.texture;
|
|||||||
public class TextureSettings {
|
public class TextureSettings {
|
||||||
|
|
||||||
private final boolean isFiltered;
|
private final boolean isFiltered;
|
||||||
|
private final boolean allocateExactBuffer;
|
||||||
|
|
||||||
public TextureSettings(boolean isFiltered) {
|
public TextureSettings(boolean isFiltered, boolean allocateExactBuffer) {
|
||||||
this.isFiltered = isFiltered;
|
this.isFiltered = isFiltered;
|
||||||
|
this.allocateExactBuffer = allocateExactBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFiltered() {
|
public boolean isFiltered() {
|
||||||
return isFiltered;
|
return isFiltered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean allocateExactBuffer() {
|
||||||
|
return allocateExactBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -91,5 +91,14 @@ public class EntityAnchor implements Anchor {
|
|||||||
public Collection<Mode> getCameraModes() {
|
public Collection<Mode> getCameraModes() {
|
||||||
return modes;
|
return modes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EntityData getEntity() {
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Anchor for entity " + entity;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ import ru.windcorp.progressia.client.comms.controls.InputBasedControls;
|
|||||||
import ru.windcorp.progressia.client.graphics.Layer;
|
import ru.windcorp.progressia.client.graphics.Layer;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.FaceCulling;
|
import ru.windcorp.progressia.client.graphics.backend.FaceCulling;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shapes.PppBuilder;
|
import ru.windcorp.progressia.client.graphics.model.Shapes.PppBuilder;
|
||||||
@ -45,7 +45,7 @@ import ru.windcorp.progressia.common.util.Vectors;
|
|||||||
import ru.windcorp.progressia.common.world.GravityModel;
|
import ru.windcorp.progressia.common.world.GravityModel;
|
||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
import ru.windcorp.progressia.test.CollisionModelRenderer;
|
import ru.windcorp.progressia.test.CollisionModelRenderer;
|
||||||
import ru.windcorp.progressia.test.TestPlayerControls;
|
import ru.windcorp.progressia.test.controls.TestPlayerControls;
|
||||||
|
|
||||||
public class LayerWorld extends Layer {
|
public class LayerWorld extends Layer {
|
||||||
|
|
||||||
@ -214,6 +214,9 @@ public class LayerWorld extends Layer {
|
|||||||
if (ClientState.getInstance().getLocalPlayer().getEntity() == entity && tmp_testControls.isFlying()) {
|
if (ClientState.getInstance().getLocalPlayer().getEntity() == entity && tmp_testControls.isFlying()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (entity.getId().equals("Test:NoclipCamera")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Vec3 gravitationalAcceleration = Vectors.grab3();
|
Vec3 gravitationalAcceleration = Vectors.grab3();
|
||||||
gm.getGravity(entity.getPosition(), gravitationalAcceleration);
|
gm.getGravity(entity.getPosition(), gravitationalAcceleration);
|
||||||
@ -225,14 +228,9 @@ public class LayerWorld extends Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleInput(Input input) {
|
public void handleInput(InputEvent event) {
|
||||||
if (input.isConsumed())
|
if (!event.isConsumed()) {
|
||||||
return;
|
inputBasedControls.handleInput(event);
|
||||||
|
|
||||||
tmp_testControls.handleInput(input);
|
|
||||||
|
|
||||||
if (!input.isConsumed()) {
|
|
||||||
inputBasedControls.handleInput(input);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,6 +199,10 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
|||||||
int offset = vertices.position() + index * getBytesPerVertex() + (3 * Float.BYTES +
|
int offset = vertices.position() + index * getBytesPerVertex() + (3 * Float.BYTES +
|
||||||
4 * Float.BYTES +
|
4 * Float.BYTES +
|
||||||
2 * Float.BYTES);
|
2 * Float.BYTES);
|
||||||
|
|
||||||
|
if (!Float.isNaN(vertices.getFloat(offset + 0 * Float.BYTES))) {
|
||||||
|
return; // normals are forced
|
||||||
|
}
|
||||||
|
|
||||||
vertices.putFloat(offset + 0 * Float.BYTES, normal.x);
|
vertices.putFloat(offset + 0 * Float.BYTES, normal.x);
|
||||||
vertices.putFloat(offset + 1 * Float.BYTES, normal.y);
|
vertices.putFloat(offset + 1 * Float.BYTES, normal.y);
|
||||||
@ -212,7 +216,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
|||||||
return new WRPVertexBuilder();
|
return new WRPVertexBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class WRPVertexBuilder implements VertexBuilder {
|
public static class WRPVertexBuilder implements VertexBuilder {
|
||||||
// TODO Throw VertexBuilders out the window and rewrite completely.
|
// TODO Throw VertexBuilders out the window and rewrite completely.
|
||||||
// I want to _extend_ VBs, not re-implement them for children of SRP
|
// I want to _extend_ VBs, not re-implement them for children of SRP
|
||||||
|
|
||||||
@ -220,11 +224,17 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
|||||||
final Vec3 position;
|
final Vec3 position;
|
||||||
final Vec4 colorMultiplier;
|
final Vec4 colorMultiplier;
|
||||||
final Vec2 textureCoords;
|
final Vec2 textureCoords;
|
||||||
|
final Vec3 normals;
|
||||||
|
|
||||||
Vertex(Vec3 position, Vec4 colorMultiplier, Vec2 textureCoords) {
|
Vertex(Vec3 position, Vec4 colorMultiplier, Vec2 textureCoords) {
|
||||||
|
this(position, colorMultiplier, textureCoords, new Vec3(Float.NaN, Float.NaN, Float.NaN));
|
||||||
|
}
|
||||||
|
|
||||||
|
Vertex(Vec3 position, Vec4 colorMultiplier, Vec2 textureCoords, Vec3 normals) {
|
||||||
this.position = position;
|
this.position = position;
|
||||||
this.colorMultiplier = colorMultiplier;
|
this.colorMultiplier = colorMultiplier;
|
||||||
this.textureCoords = textureCoords;
|
this.textureCoords = textureCoords;
|
||||||
|
this.normals = normals;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,6 +301,24 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
|||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public VertexBuilder addVertex(
|
||||||
|
Vec3 position,
|
||||||
|
Vec4 colorMultiplier,
|
||||||
|
Vec2 textureCoords,
|
||||||
|
Vec3 normals
|
||||||
|
) {
|
||||||
|
vertices.add(
|
||||||
|
new Vertex(
|
||||||
|
new Vec3(position),
|
||||||
|
new Vec4(colorMultiplier),
|
||||||
|
new Vec2(textureCoords),
|
||||||
|
new Vec3(normals)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBuffer assemble() {
|
public ByteBuffer assemble() {
|
||||||
@ -309,9 +337,9 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
|||||||
.putFloat(v.colorMultiplier.w)
|
.putFloat(v.colorMultiplier.w)
|
||||||
.putFloat(v.textureCoords.x)
|
.putFloat(v.textureCoords.x)
|
||||||
.putFloat(v.textureCoords.y)
|
.putFloat(v.textureCoords.y)
|
||||||
.putFloat(Float.NaN)
|
.putFloat(v.normals.x)
|
||||||
.putFloat(Float.NaN)
|
.putFloat(v.normals.y)
|
||||||
.putFloat(Float.NaN);
|
.putFloat(v.normals.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.flip();
|
result.flip();
|
||||||
|
@ -72,6 +72,12 @@ public class Localizer {
|
|||||||
public synchronized String getLanguage() {
|
public synchronized String getLanguage() {
|
||||||
return language;
|
return language;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getLanguages() {
|
||||||
|
List<String> result = new ArrayList<>(langList.keySet());
|
||||||
|
result.sort(null);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized String getValue(String key) {
|
public synchronized String getValue(String key) {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
|
@ -42,7 +42,7 @@ public class EntityRenderRegistry extends NamespacedInstanceRegistry<EntityRende
|
|||||||
ResourceManager.getTextureResource(
|
ResourceManager.getTextureResource(
|
||||||
"entities/" + name
|
"entities/" + name
|
||||||
),
|
),
|
||||||
new TextureSettings(false)
|
new TextureSettings(false, false)
|
||||||
).getData()
|
).getData()
|
||||||
);
|
);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -114,7 +114,8 @@ public class TileRenderCross extends TileRender implements TileOptimizedCustom {
|
|||||||
origin,
|
origin,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
false
|
false,
|
||||||
|
new Vec3(0, 0, 1)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
output.accept(
|
output.accept(
|
||||||
@ -125,7 +126,8 @@ public class TileRenderCross extends TileRender implements TileOptimizedCustom {
|
|||||||
origin,
|
origin,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
true
|
true,
|
||||||
|
new Vec3(0, 0, 1)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -105,6 +105,9 @@ public class Collider {
|
|||||||
// For every pair of colls
|
// For every pair of colls
|
||||||
for (int i = 0; i < colls.size(); ++i) {
|
for (int i = 0; i < colls.size(); ++i) {
|
||||||
Collideable a = colls.get(i);
|
Collideable a = colls.get(i);
|
||||||
|
if (a.getCollisionModel() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
tuneWorldCollisionHelper(a, tickLength, world, workspace);
|
tuneWorldCollisionHelper(a, tickLength, world, workspace);
|
||||||
|
|
||||||
@ -115,6 +118,10 @@ public class Collider {
|
|||||||
|
|
||||||
for (int j = i + 1; j < colls.size(); ++j) {
|
for (int j = i + 1; j < colls.size(); ++j) {
|
||||||
Collideable b = colls.get(j);
|
Collideable b = colls.get(j);
|
||||||
|
if (b.getCollisionModel() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
Collision collision = getCollision(a, b, tickLength, workspace);
|
Collision collision = getCollision(a, b, tickLength, workspace);
|
||||||
result = workspace.updateLatestCollision(result, collision);
|
result = workspace.updateLatestCollision(result, collision);
|
||||||
}
|
}
|
||||||
|
@ -235,7 +235,7 @@ public abstract class StatefulObject extends Namespaced implements Encodable {
|
|||||||
|
|
||||||
StatefulObject statefulObj = (StatefulObject) obj;
|
StatefulObject statefulObj = (StatefulObject) obj;
|
||||||
|
|
||||||
if (statefulObj.getId().equals(this.getId()))
|
if (!statefulObj.getId().equals(this.getId()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.common.util.crash.providers;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.Progressia;
|
||||||
|
import ru.windcorp.progressia.common.util.crash.ContextProvider;
|
||||||
|
|
||||||
|
public class VersionProvider implements ContextProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void provideContext(Map<String, String> output) {
|
||||||
|
output.put("Version", Progressia.getVersion());
|
||||||
|
output.put("Git commit", Progressia.getGitCommit());
|
||||||
|
output.put("Git branch", Progressia.getGitBranch());
|
||||||
|
output.put("Build ID", Progressia.getBuildId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Version Provider";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -379,5 +379,15 @@ public class EntityData extends StatefulObject implements Collideable, EntityGen
|
|||||||
|
|
||||||
super.read(input, context);
|
super.read(input, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return this == obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Long.hashCode(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -43,10 +43,8 @@ public class Region {
|
|||||||
|
|
||||||
private static final boolean RESET_CORRUPTED = true;
|
private static final boolean RESET_CORRUPTED = true;
|
||||||
|
|
||||||
public int loadedChunks;
|
private final AtomicBoolean isUsing = new AtomicBoolean(false);
|
||||||
|
private final AtomicBoolean isClosed = new AtomicBoolean(false);
|
||||||
private AtomicBoolean isUsing = new AtomicBoolean(false);
|
|
||||||
private AtomicBoolean isClosed = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
private final RegionFile file;
|
private final RegionFile file;
|
||||||
|
|
||||||
@ -60,6 +58,7 @@ public class Region {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
||||||
RegionWorldContainer.LOG.debug("Uh the file broke");
|
RegionWorldContainer.LOG.debug("Uh the file broke");
|
||||||
|
RegionWorldContainer.LOG.debug(e.getLocalizedMessage());
|
||||||
if (RESET_CORRUPTED) {
|
if (RESET_CORRUPTED) {
|
||||||
this.file.makeHeader(regionCoords);
|
this.file.makeHeader(regionCoords);
|
||||||
}
|
}
|
||||||
@ -132,7 +131,7 @@ public class Region {
|
|||||||
DecodingException {
|
DecodingException {
|
||||||
isUsing.set(true);
|
isUsing.set(true);
|
||||||
|
|
||||||
int dataOffset = 0;
|
int dataOffset;
|
||||||
Vec3i pos = RegionWorldContainer.getInRegionCoords(chunkPos);
|
Vec3i pos = RegionWorldContainer.getInRegionCoords(chunkPos);
|
||||||
|
|
||||||
if (hasOffset(pos)) {
|
if (hasOffset(pos)) {
|
||||||
|
@ -26,9 +26,9 @@ public class RegionFile {
|
|||||||
private static final int ID_HEADER_SIZE = 16;
|
private static final int ID_HEADER_SIZE = 16;
|
||||||
private static final byte[] HEADER_ID = {'P','R','O','G'};
|
private static final byte[] HEADER_ID = {'P','R','O','G'};
|
||||||
|
|
||||||
final byte endBytes[] = new byte[SECTOR_SIZE];
|
final byte[] endBytes = new byte[SECTOR_SIZE];
|
||||||
|
|
||||||
public static enum SectorType {
|
public enum SectorType {
|
||||||
Ending(0), // Just an empty block
|
Ending(0), // Just an empty block
|
||||||
Data(1), // has a byte counting up in position 1, and then
|
Data(1), // has a byte counting up in position 1, and then
|
||||||
PartitionLink(2),
|
PartitionLink(2),
|
||||||
@ -54,7 +54,7 @@ public class RegionFile {
|
|||||||
|
|
||||||
public void confirmHeaderHealth(ChunkMap<Integer> offsets, Vec3i regionCoords) throws IOException {
|
public void confirmHeaderHealth(ChunkMap<Integer> offsets, Vec3i regionCoords) throws IOException {
|
||||||
|
|
||||||
Set<Integer> used = new HashSet<Integer>();
|
Set<Integer> used = new HashSet<>();
|
||||||
int maxUsed = 0;
|
int maxUsed = 0;
|
||||||
final int chunksPerRegion = REGION_DIAMETER * REGION_DIAMETER * REGION_DIAMETER;
|
final int chunksPerRegion = REGION_DIAMETER * REGION_DIAMETER * REGION_DIAMETER;
|
||||||
|
|
||||||
@ -63,9 +63,10 @@ public class RegionFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
char prog;
|
byte prog;
|
||||||
|
file.seek(0);
|
||||||
for (int i=0;i<4;i++) {
|
for (int i=0;i<4;i++) {
|
||||||
prog = file.readChar();
|
prog = file.readByte();
|
||||||
if (prog != HEADER_ID[i])
|
if (prog != HEADER_ID[i])
|
||||||
{
|
{
|
||||||
throw new IOException("File is not a .progressia_chunk file");
|
throw new IOException("File is not a .progressia_chunk file");
|
||||||
@ -109,7 +110,7 @@ public class RegionFile {
|
|||||||
throw new IOException("A sector is used twice");
|
throw new IOException("A sector is used twice");
|
||||||
}
|
}
|
||||||
|
|
||||||
file.seek(HEADER_SIZE + SECTOR_SIZE * offset);
|
file.seek(HEADER_SIZE + (long) SECTOR_SIZE * offset);
|
||||||
byte type = file.readByte();
|
byte type = file.readByte();
|
||||||
|
|
||||||
if (type == SectorType.Data.data) {
|
if (type == SectorType.Data.data) {
|
||||||
@ -131,25 +132,26 @@ public class RegionFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void makeHeader(Vec3i regionCoords) throws IOException {
|
public void makeHeader(Vec3i regionCoords) throws IOException {
|
||||||
|
file.seek(0);
|
||||||
|
for (int i = 0; i < HEADER_SIZE; i++) {
|
||||||
|
file.write(0);
|
||||||
|
}
|
||||||
file.seek(0);
|
file.seek(0);
|
||||||
file.write(HEADER_ID);
|
file.write(HEADER_ID);
|
||||||
file.writeInt(regionCoords.x);
|
file.writeInt(regionCoords.x);
|
||||||
file.writeInt(regionCoords.y);
|
file.writeInt(regionCoords.y);
|
||||||
file.writeInt(regionCoords.z);
|
file.writeInt(regionCoords.z);
|
||||||
for (int i = 0; i < HEADER_SIZE; i++) {
|
|
||||||
file.write(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeBuffer(byte[] buffer, int dataOffset, Vec3i pos) throws IOException {
|
public void writeBuffer(byte[] buffer, int dataOffset, Vec3i pos) throws IOException {
|
||||||
file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset);
|
file.seek(HEADER_SIZE + (long) SECTOR_SIZE * dataOffset);
|
||||||
int loc = 0;
|
int loc = 0;
|
||||||
byte tempBuffer[] = new byte[SECTOR_SIZE];
|
byte[] tempBuffer = new byte[SECTOR_SIZE];
|
||||||
byte counter = 0;
|
byte counter = 0;
|
||||||
boolean isDone = false;
|
boolean isDone = false;
|
||||||
while (!isDone) {
|
while (!isDone) {
|
||||||
if (file.length() > HEADER_SIZE + SECTOR_SIZE * (dataOffset + 1)) {
|
if (file.length() > HEADER_SIZE + (long) SECTOR_SIZE * (dataOffset + 1)) {
|
||||||
file.seek(HEADER_SIZE + SECTOR_SIZE * (dataOffset + 1));
|
file.seek(HEADER_SIZE + (long) SECTOR_SIZE * (dataOffset + 1));
|
||||||
byte header = file.readByte();
|
byte header = file.readByte();
|
||||||
if (header == SectorType.Data.data) {
|
if (header == SectorType.Data.data) {
|
||||||
byte fileCounter = file.readByte();
|
byte fileCounter = file.readByte();
|
||||||
@ -157,7 +159,7 @@ public class RegionFile {
|
|||||||
// partition place
|
// partition place
|
||||||
{
|
{
|
||||||
int newOffset = allocateEmptySector();
|
int newOffset = allocateEmptySector();
|
||||||
file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset);
|
file.seek(HEADER_SIZE + (long) SECTOR_SIZE * dataOffset);
|
||||||
file.write(2);
|
file.write(2);
|
||||||
file.writeInt(newOffset);
|
file.writeInt(newOffset);
|
||||||
dataOffset = newOffset;
|
dataOffset = newOffset;
|
||||||
@ -179,7 +181,7 @@ public class RegionFile {
|
|||||||
if (file.getFilePointer() < 256)
|
if (file.getFilePointer() < 256)
|
||||||
LogManager.getLogger("Region")
|
LogManager.getLogger("Region")
|
||||||
.debug("at {}, ({},{},{}), {}", file.getFilePointer(), pos.x, pos.y, pos.z, dataOffset);
|
.debug("at {}, ({},{},{}), {}", file.getFilePointer(), pos.x, pos.y, pos.z, dataOffset);
|
||||||
file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset);
|
file.seek(HEADER_SIZE + (long) SECTOR_SIZE * dataOffset);
|
||||||
dataOffset++;
|
dataOffset++;
|
||||||
file.write(tempBuffer);
|
file.write(tempBuffer);
|
||||||
}
|
}
|
||||||
@ -197,7 +199,7 @@ public class RegionFile {
|
|||||||
file.seek(definitionOffset);
|
file.seek(definitionOffset);
|
||||||
file.writeInt(dataOffset + 1);
|
file.writeInt(dataOffset + 1);
|
||||||
|
|
||||||
file.setLength(HEADER_SIZE + dataOffset * SECTOR_SIZE);
|
file.setLength(HEADER_SIZE + (long) dataOffset * SECTOR_SIZE);
|
||||||
return dataOffset;
|
return dataOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,17 +208,17 @@ public class RegionFile {
|
|||||||
|
|
||||||
int dataOffset = (int) Math.ceil((double) (outputLen - HEADER_SIZE) / SECTOR_SIZE);
|
int dataOffset = (int) Math.ceil((double) (outputLen - HEADER_SIZE) / SECTOR_SIZE);
|
||||||
|
|
||||||
file.setLength(HEADER_SIZE + dataOffset * SECTOR_SIZE);
|
file.setLength(HEADER_SIZE + (long) dataOffset * SECTOR_SIZE);
|
||||||
|
|
||||||
return dataOffset;
|
return dataOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] readBuffer(int dataOffset) throws IOException {
|
public byte[] readBuffer(int dataOffset) throws IOException {
|
||||||
file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset);
|
file.seek(HEADER_SIZE + (long) SECTOR_SIZE * dataOffset);
|
||||||
|
|
||||||
int bufferPos = 0;
|
int bufferPos = 0;
|
||||||
byte buffer[] = new byte[SECTOR_SIZE * 16];
|
byte[] buffer = new byte[SECTOR_SIZE * 16];
|
||||||
byte tempBuffer[] = new byte[SECTOR_SIZE];
|
byte[] tempBuffer = new byte[SECTOR_SIZE];
|
||||||
|
|
||||||
boolean reachedEnd = false;
|
boolean reachedEnd = false;
|
||||||
byte counter = 0;
|
byte counter = 0;
|
||||||
@ -229,31 +231,24 @@ public class RegionFile {
|
|||||||
if (tempBuffer[0] == SectorType.Data.data) {
|
if (tempBuffer[0] == SectorType.Data.data) {
|
||||||
if (tempBuffer[1] != counter) {
|
if (tempBuffer[1] != counter) {
|
||||||
throw new IOException(
|
throw new IOException(
|
||||||
"Sectors were read out of order\nExpected chunk number " + Byte.toString(counter)
|
"Sectors were read out of order\nExpected chunk number " + counter
|
||||||
+ " but encountered number " + Byte.toString(tempBuffer[1])
|
+ " but encountered number " + tempBuffer[1]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
counter++;
|
counter++;
|
||||||
if (buffer.length - bufferPos < SECTOR_SIZE - SECTOR_HEADER_SIZE - 1) {
|
if (buffer.length - bufferPos < SECTOR_SIZE - SECTOR_HEADER_SIZE - 1) {
|
||||||
byte newBuffer[] = new byte[buffer.length + SECTOR_SIZE * 16];
|
byte[] newBuffer = new byte[buffer.length + SECTOR_SIZE * 16];
|
||||||
for (int i = 0; i < buffer.length; i++) // TODO dedicated
|
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
|
||||||
// copy, java-y at
|
|
||||||
// least
|
|
||||||
{
|
|
||||||
newBuffer[i] = buffer[i];
|
|
||||||
}
|
|
||||||
buffer = newBuffer;
|
buffer = newBuffer;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < SECTOR_SIZE - SECTOR_HEADER_SIZE - 1; i++) {
|
System.arraycopy(tempBuffer, 2, buffer, bufferPos, SECTOR_SIZE - SECTOR_HEADER_SIZE - 1);
|
||||||
buffer[bufferPos + i] = tempBuffer[i + 2];
|
|
||||||
}
|
|
||||||
bufferPos += SECTOR_SIZE - SECTOR_HEADER_SIZE - 1;
|
bufferPos += SECTOR_SIZE - SECTOR_HEADER_SIZE - 1;
|
||||||
} else if (tempBuffer[0] == SectorType.Ending.data) {
|
} else if (tempBuffer[0] == SectorType.Ending.data) {
|
||||||
reachedEnd = true;
|
reachedEnd = true;
|
||||||
} else if (tempBuffer[0] == SectorType.PartitionLink.data) {
|
} else if (tempBuffer[0] == SectorType.PartitionLink.data) {
|
||||||
ByteBuffer intBuffer = ByteBuffer.wrap(tempBuffer);
|
ByteBuffer intBuffer = ByteBuffer.wrap(tempBuffer);
|
||||||
int newOffset = intBuffer.getInt(1);
|
int newOffset = intBuffer.getInt(1);
|
||||||
file.seek(HEADER_SIZE + SECTOR_SIZE * newOffset);
|
file.seek(HEADER_SIZE + (long) SECTOR_SIZE * newOffset);
|
||||||
} else {
|
} else {
|
||||||
throw new IOException("Invalid sector ID.");
|
throw new IOException("Invalid sector ID.");
|
||||||
}
|
}
|
||||||
|
108
src/main/java/ru/windcorp/progressia/test/CubeComponent.java
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package ru.windcorp.progressia.test;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import glm.mat._4.Mat4;
|
||||||
|
import glm.vec._3.Vec3;
|
||||||
|
import glm.vec._4.Vec4;
|
||||||
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||||
|
|
||||||
|
public class CubeComponent extends Component {
|
||||||
|
|
||||||
|
private final Mat4[] transforms;
|
||||||
|
private final Vec4[] normals;
|
||||||
|
private final long startTime;
|
||||||
|
|
||||||
|
private final double r3 = Math.sqrt(3+.01);
|
||||||
|
|
||||||
|
private final int size;
|
||||||
|
|
||||||
|
public CubeComponent(String name, int size) {
|
||||||
|
super(name);
|
||||||
|
this.size = size;
|
||||||
|
transforms = new Mat4[6];
|
||||||
|
normals = new Vec4[6];
|
||||||
|
setPreferredSize((int) Math.ceil(r3*size),(int) Math.ceil(r3*size));
|
||||||
|
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
|
||||||
|
executor.scheduleAtFixedRate(this::requestReassembly, 1, 60, TimeUnit.MILLISECONDS);
|
||||||
|
startTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notes to me
|
||||||
|
// z axis is through the screen
|
||||||
|
// y is horizontal spin
|
||||||
|
// x is vertical spin
|
||||||
|
|
||||||
|
|
||||||
|
private void computeTransforms()
|
||||||
|
{
|
||||||
|
//Creates all of the sides
|
||||||
|
transforms[0] = new Mat4(1);
|
||||||
|
transforms[1] = new Mat4(1);
|
||||||
|
transforms[2] = new Mat4(1);
|
||||||
|
transforms[3] = new Mat4(1);
|
||||||
|
transforms[4] = new Mat4(1);
|
||||||
|
transforms[5] = new Mat4(1);
|
||||||
|
|
||||||
|
//Gets time since creation(for rotation amount)
|
||||||
|
long time = System.currentTimeMillis()-startTime;
|
||||||
|
|
||||||
|
//Initializes the way each face is facing
|
||||||
|
normals[0] = new Vec4(0,0,-1,0);
|
||||||
|
normals[1] = new Vec4(0,1,0,0);
|
||||||
|
normals[2] = new Vec4(1,0,0,0);
|
||||||
|
normals[3] = new Vec4(0,0,1,0);
|
||||||
|
normals[4] = new Vec4(0,-1,0,0);
|
||||||
|
normals[5] = new Vec4(-1,0,0,0);
|
||||||
|
|
||||||
|
for (int i=0;i<6;i++)
|
||||||
|
{
|
||||||
|
//Rotates given side with the time one first, then ot get it on its off axis, then gets the image of each axis under the given rotation
|
||||||
|
//The rotate functions do change the transforms, but the multiplication does not
|
||||||
|
normals[i] = transforms[i].rotate((float) (time%(6000*6.28) )/ 6000, new Vec3(0,1,0)).rotate((float) 24, new Vec3(1,.5,0)).mul_(normals[i]);
|
||||||
|
}
|
||||||
|
double pi2 = Math.PI / 2;
|
||||||
|
|
||||||
|
//Move and rotate the sides from the middle of the cube to the appropriate edges
|
||||||
|
transforms[0].translate(new Vec3(-size/2f,-size/2f,size/2f));
|
||||||
|
transforms[1].translate(new Vec3(-size/2f,-size/2f,-size/2f)).rotate((float) pi2, new Vec3(1,0,0));
|
||||||
|
transforms[2].translate(new Vec3(-size/2f,-size/2f,size/2f)).rotate((float) pi2, new Vec3(0,1,0));
|
||||||
|
transforms[3].translate(new Vec3(-size/2f,-size/2f,-size/2f));
|
||||||
|
transforms[4].translate(new Vec3(-size/2f,size/2f,-size/2f)).rotate((float) pi2, new Vec3(1,0,0));
|
||||||
|
transforms[5].translate(new Vec3(size/2f,-size/2f,size/2f)).rotate((float) pi2, new Vec3(0,1,0));
|
||||||
|
|
||||||
|
for (int i=0;i<6;i++) // I have no clue why this is necessary, without it the sides of the cube mess up; may need to be changed if the title screen changes position.
|
||||||
|
{
|
||||||
|
transforms[i] = transforms[i].translate(new Vec3(0,0,17.5-3*(i<2 ? 1 : 0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assembleSelf(RenderTarget target)
|
||||||
|
{
|
||||||
|
computeTransforms();
|
||||||
|
|
||||||
|
setPosition(750,780);
|
||||||
|
|
||||||
|
target.pushTransform(new Mat4(1).translate(new Vec3(getX()+size*r3/2,getY()-size*r3/2,0))); //-size*r3/2
|
||||||
|
|
||||||
|
for (int b=0; b<6;b++)
|
||||||
|
{
|
||||||
|
target.pushTransform(transforms[b]);
|
||||||
|
|
||||||
|
float dot = normals[b].dot(new Vec4(-1,0,0,0)); //Gets the "amount" the given side is pointing in the -x direction
|
||||||
|
|
||||||
|
Vec4 color = new Vec4(.4+.3*dot, .4+.3*dot, .6+.4*dot,1.0); //More aligned means brighter color
|
||||||
|
|
||||||
|
target.fill(0,0, size, size, color);
|
||||||
|
|
||||||
|
target.popTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
target.popTransform();
|
||||||
|
}
|
||||||
|
}
|
147
src/main/java/ru/windcorp/progressia/test/Flowers.java
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.test;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
|
import ru.windcorp.progressia.client.world.tile.TileRenderRegistry;
|
||||||
|
import ru.windcorp.progressia.client.world.tile.TileRenderTransparentSurface;
|
||||||
|
import ru.windcorp.progressia.common.world.tile.TileData;
|
||||||
|
import ru.windcorp.progressia.common.world.tile.TileDataRegistry;
|
||||||
|
import ru.windcorp.progressia.server.world.tile.HangingTileLogic;
|
||||||
|
import ru.windcorp.progressia.server.world.tile.TileLogicRegistry;
|
||||||
|
|
||||||
|
public class Flowers {
|
||||||
|
|
||||||
|
private static final int HERB_MAX_COUNT = 3;
|
||||||
|
private static final int TINY_MAX_COUNT = 8;
|
||||||
|
private static final float TINY_SIZE = 0.5f;
|
||||||
|
|
||||||
|
public enum FlowerVariant {
|
||||||
|
|
||||||
|
FLAT("Flat"),
|
||||||
|
TINY("Tiny"),
|
||||||
|
HERB("Herb");
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
private FlowerVariant(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Flower {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1 disables spawning, 0 spawns everywhere
|
||||||
|
*/
|
||||||
|
private final float rarity;
|
||||||
|
|
||||||
|
private final FlowerVariant[] requestedVariants;
|
||||||
|
private final Map<FlowerVariant, TileData> tiles = new HashMap<>();
|
||||||
|
|
||||||
|
public Flower(String name, float rarity, FlowerVariant... variants) {
|
||||||
|
Objects.requireNonNull(name, "name");
|
||||||
|
Objects.requireNonNull(variants, "variants");
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
|
this.rarity = rarity;
|
||||||
|
this.requestedVariants = variants;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getRarity() {
|
||||||
|
return rarity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TileData getTile(FlowerVariant variant) {
|
||||||
|
return tiles.get(variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<TileData> getTiles() {
|
||||||
|
return tiles.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void register() {
|
||||||
|
for (FlowerVariant variant : requestedVariants) {
|
||||||
|
|
||||||
|
String fullName = "Flower" + name + variant.getName();
|
||||||
|
String id = "Test:" + fullName;
|
||||||
|
|
||||||
|
TileData tileData = new TileData(id);
|
||||||
|
tiles.put(variant, tileData);
|
||||||
|
TileDataRegistry.getInstance().register(tileData);
|
||||||
|
|
||||||
|
Texture texture = TileRenderRegistry.getTileTexture(fullName);
|
||||||
|
|
||||||
|
TileLogicRegistry logic = TileLogicRegistry.getInstance();
|
||||||
|
TileRenderRegistry render = TileRenderRegistry.getInstance();
|
||||||
|
|
||||||
|
switch (variant) {
|
||||||
|
case HERB:
|
||||||
|
logic.register(new HangingTileLogic(id));
|
||||||
|
render.register(new TileRenderHerb(id, texture, HERB_MAX_COUNT));
|
||||||
|
break;
|
||||||
|
case TINY:
|
||||||
|
logic.register(new HangingTileLogic(id));
|
||||||
|
render.register(new TileRenderTinyFlower(id, texture, TINY_MAX_COUNT, TINY_SIZE));
|
||||||
|
break;
|
||||||
|
case FLAT:
|
||||||
|
default:
|
||||||
|
logic.register(new TestTileLogicGrass(id));
|
||||||
|
render.register(new TileRenderTransparentSurface(id, texture));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<String, Flower> flowersByName = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
|
public Flower create(String name, float rarity, FlowerVariant... variants) {
|
||||||
|
Flower flower = new Flower(name, rarity, variants);
|
||||||
|
flowersByName.put(name, flower);
|
||||||
|
return flower;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerAllFlowers() {
|
||||||
|
getFlowers().forEach(Flower::register);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Flower> getFlowers() {
|
||||||
|
return flowersByName.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.test;
|
package ru.windcorp.progressia.test;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.Progressia;
|
||||||
import ru.windcorp.progressia.client.graphics.Colors;
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||||
import ru.windcorp.progressia.client.graphics.font.Typeface;
|
import ru.windcorp.progressia.client.graphics.font.Typeface;
|
||||||
@ -30,6 +31,8 @@ import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
|||||||
|
|
||||||
public class LayerAbout extends GUILayer {
|
public class LayerAbout extends GUILayer {
|
||||||
|
|
||||||
|
public static String version = "pre-alpha 3";
|
||||||
|
|
||||||
public LayerAbout() {
|
public LayerAbout() {
|
||||||
super("LayerAbout", new LayoutAlign(1, 1, 5));
|
super("LayerAbout", new LayoutAlign(1, 1, 5));
|
||||||
|
|
||||||
@ -50,7 +53,7 @@ public class LayerAbout extends GUILayer {
|
|||||||
new Label(
|
new Label(
|
||||||
"Version",
|
"Version",
|
||||||
font,
|
font,
|
||||||
new MutableStringLocalized("LayerAbout.Version").format("pre-alpha 3")
|
new MutableStringLocalized("LayerAbout.Version").format(Progressia.getFullerVersion())
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
|||||||
import ru.windcorp.progressia.server.Player;
|
import ru.windcorp.progressia.server.Player;
|
||||||
import ru.windcorp.progressia.server.Server;
|
import ru.windcorp.progressia.server.Server;
|
||||||
import ru.windcorp.progressia.server.ServerState;
|
import ru.windcorp.progressia.server.ServerState;
|
||||||
|
import ru.windcorp.progressia.test.controls.TestPlayerControls;
|
||||||
|
|
||||||
public class LayerButtonTest extends MenuLayer {
|
public class LayerButtonTest extends MenuLayer {
|
||||||
|
|
||||||
|
261
src/main/java/ru/windcorp/progressia/test/LayerDebug.java
Executable file
@ -0,0 +1,261 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.windcorp.progressia.test;
|
||||||
|
|
||||||
|
import glm.vec._3.Vec3;
|
||||||
|
import ru.windcorp.progressia.client.Client;
|
||||||
|
import ru.windcorp.progressia.client.ClientState;
|
||||||
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
|
||||||
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||||
|
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.DynamicLabel;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Label;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Group;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
||||||
|
import ru.windcorp.progressia.client.localization.Localizer;
|
||||||
|
import ru.windcorp.progressia.client.localization.MutableString;
|
||||||
|
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
||||||
|
import ru.windcorp.progressia.client.world.WorldRender;
|
||||||
|
import ru.windcorp.progressia.common.Units;
|
||||||
|
import ru.windcorp.progressia.common.util.dynstr.DynamicStrings;
|
||||||
|
import ru.windcorp.progressia.server.Server;
|
||||||
|
import ru.windcorp.progressia.server.ServerState;
|
||||||
|
import ru.windcorp.progressia.test.controls.TestPlayerControls;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class LayerDebug extends GUILayer {
|
||||||
|
|
||||||
|
private final List<Runnable> updateTriggers = new ArrayList<>();
|
||||||
|
|
||||||
|
public LayerDebug() {
|
||||||
|
super("LayerDebug", new LayoutAlign(0, 1, 5));
|
||||||
|
getRoot().addChild(new Group("Displays", new LayoutVertical(5)));
|
||||||
|
|
||||||
|
TestPlayerControls tpc = TestPlayerControls.getInstance();
|
||||||
|
|
||||||
|
addDynamicDisplay(
|
||||||
|
"FPSDisplay",
|
||||||
|
DynamicStrings.builder()
|
||||||
|
.addDyn(new MutableStringLocalized("LayerDebug.FPSDisplay"))
|
||||||
|
.addDyn(() -> FPS_RECORD.update(GraphicsInterface.getFPS()), 5, 1)
|
||||||
|
.addDyn(() -> GraphicsBackend.isFullscreen() ? " Fullscreen" : "")
|
||||||
|
.addDyn(() -> GraphicsBackend.isVSyncEnabled() ? " VSync" : "")
|
||||||
|
.buildSupplier()
|
||||||
|
);
|
||||||
|
|
||||||
|
addDynamicDisplay("TPSDisplay", LayerDebug::getTPS);
|
||||||
|
|
||||||
|
addDynamicDisplay(
|
||||||
|
"ChunkStatsDisplay",
|
||||||
|
DynamicStrings.builder()
|
||||||
|
.addDyn(new MutableStringLocalized("LayerDebug.ChunkStatsDisplay"))
|
||||||
|
.addDyn(() -> {
|
||||||
|
if (ClientState.getInstance() == null) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
WorldRender world = ClientState.getInstance().getWorld();
|
||||||
|
return world.getChunks().size() - world.getPendingChunkUpdates();
|
||||||
|
}
|
||||||
|
}, 4)
|
||||||
|
.add('/')
|
||||||
|
.addDyn(() -> {
|
||||||
|
if (ClientState.getInstance() == null) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return ClientState.getInstance().getWorld().getPendingChunkUpdates();
|
||||||
|
}
|
||||||
|
}, 4)
|
||||||
|
.add('/')
|
||||||
|
.addDyn(() -> {
|
||||||
|
if (ServerState.getInstance() == null) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return ServerState.getInstance().getWorld().getChunks().size();
|
||||||
|
}
|
||||||
|
}, 4)
|
||||||
|
.buildSupplier()
|
||||||
|
);
|
||||||
|
|
||||||
|
addDynamicDisplay("PosDisplay", LayerDebug::getPos);
|
||||||
|
|
||||||
|
addDisplay("SelectedBlockDisplay", () -> tpc.isBlockSelected() ? ">" : " ", () -> tpc.getSelectedBlock().getId());
|
||||||
|
addDisplay("SelectedTileDisplay", () -> tpc.isBlockSelected() ? " " : ">", () -> tpc.getSelectedTile().getId());
|
||||||
|
addDisplay("PlacementModeHint", () -> "\u2B04");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDisplay(String name, Supplier<?>... params) {
|
||||||
|
Font font = new Font().withColor(Colors.WHITE).deriveOutlined();
|
||||||
|
Label component = new Label(name, font, tmp_dynFormat("LayerDebug." + name, params));
|
||||||
|
getRoot().getChild(0).addChild(component);
|
||||||
|
|
||||||
|
for (Supplier<?> param : params) {
|
||||||
|
if (param == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTriggers.add(new Runnable() {
|
||||||
|
|
||||||
|
private Object displayedValue;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Object newValue = param.get();
|
||||||
|
if (!Objects.equals(newValue, displayedValue)) {
|
||||||
|
component.update();
|
||||||
|
}
|
||||||
|
displayedValue = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDynamicDisplay(String name, Supplier<CharSequence> contents) {
|
||||||
|
Font font = new Font().withColor(Colors.WHITE).deriveOutlined();
|
||||||
|
DynamicLabel component = new DynamicLabel(name, font, contents, 128);
|
||||||
|
getRoot().getChild(0).addChild(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doRender() {
|
||||||
|
updateTriggers.forEach(Runnable::run);
|
||||||
|
super.doRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Averager {
|
||||||
|
|
||||||
|
private static final int DISPLAY_INERTIA = 32;
|
||||||
|
private static final double UPDATE_INTERVAL = Units.get(50.0, "ms");
|
||||||
|
|
||||||
|
private final double[] values = new double[DISPLAY_INERTIA];
|
||||||
|
private int size;
|
||||||
|
private int head;
|
||||||
|
|
||||||
|
private long lastUpdate;
|
||||||
|
|
||||||
|
public void add(double value) {
|
||||||
|
if (size == values.length) {
|
||||||
|
values[head] = value;
|
||||||
|
head++;
|
||||||
|
if (head == values.length)
|
||||||
|
head = 0;
|
||||||
|
} else {
|
||||||
|
values[size] = value;
|
||||||
|
size++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double average() {
|
||||||
|
double product = 1;
|
||||||
|
|
||||||
|
if (size == values.length) {
|
||||||
|
for (double d : values)
|
||||||
|
product *= d;
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < size; ++i)
|
||||||
|
product *= values[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.pow(product, 1.0 / size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double update(double value) {
|
||||||
|
long now = (long) (GraphicsInterface.getTime() / UPDATE_INTERVAL);
|
||||||
|
if (lastUpdate != now) {
|
||||||
|
lastUpdate = now;
|
||||||
|
add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return average();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String[] CLOCK_CHARS = "\u2591\u2598\u259d\u2580\u2596\u258c\u259e\u259b\u2597\u259a\u2590\u259c\u2584\u2599\u259f\u2588"
|
||||||
|
.chars().mapToObj(c -> ((char) c) + "").toArray(String[]::new);
|
||||||
|
|
||||||
|
private static String getTPSClockChar() {
|
||||||
|
return CLOCK_CHARS[(int) (ServerState.getInstance().getUptimeTicks() % CLOCK_CHARS.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Averager FPS_RECORD = new Averager();
|
||||||
|
private static final Averager TPS_RECORD = new Averager();
|
||||||
|
|
||||||
|
private static final Supplier<CharSequence> TPS_STRING = DynamicStrings.builder()
|
||||||
|
.addDyn(new MutableStringLocalized("LayerDebug.TPSDisplay"))
|
||||||
|
.addDyn(() -> TPS_RECORD.update(ServerState.getInstance().getTPS()), 5, 1)
|
||||||
|
.add(' ')
|
||||||
|
.addDyn(LayerDebug::getTPSClockChar)
|
||||||
|
.buildSupplier();
|
||||||
|
|
||||||
|
private static final Supplier<CharSequence> POS_STRING = DynamicStrings.builder()
|
||||||
|
.addDyn(new MutableStringLocalized("LayerDebug.PosDisplay"))
|
||||||
|
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().x, 7, 1)
|
||||||
|
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().y, 7, 1)
|
||||||
|
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().z, 7, 1)
|
||||||
|
.buildSupplier();
|
||||||
|
|
||||||
|
private static CharSequence getTPS() {
|
||||||
|
Server server = ServerState.getInstance();
|
||||||
|
if (server == null)
|
||||||
|
return Localizer.getInstance().getValue("LayerDebug.TPSDisplay.NA");
|
||||||
|
|
||||||
|
return TPS_STRING.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CharSequence getPos() {
|
||||||
|
Client client = ClientState.getInstance();
|
||||||
|
if (client == null)
|
||||||
|
return Localizer.getInstance().getValue("LayerDebug.PosDisplay.NA.Client");
|
||||||
|
|
||||||
|
Vec3 pos = client.getCamera().getLastAnchorPosition();
|
||||||
|
if (Float.isNaN(pos.x)) {
|
||||||
|
return Localizer.getInstance().getValue("LayerDebug.PosDisplay.NA.Entity");
|
||||||
|
} else {
|
||||||
|
return POS_STRING.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MutableString tmp_dynFormat(String formatKey, Supplier<?>... suppliers) {
|
||||||
|
return new MutableStringLocalized(formatKey).apply(s -> {
|
||||||
|
Object[] args = new Object[suppliers.length];
|
||||||
|
|
||||||
|
for (int i = 0; i < suppliers.length; ++i) {
|
||||||
|
Supplier<?> supplier = suppliers[i];
|
||||||
|
|
||||||
|
Object value = supplier != null ? supplier.get() : "null";
|
||||||
|
if (!(value instanceof Number)) {
|
||||||
|
value = Objects.toString(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
args[i] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return String.format(s, args);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
53
src/main/java/ru/windcorp/progressia/test/LayerOptions.java
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package ru.windcorp.progressia.test;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
|
import ru.windcorp.progressia.client.graphics.GUI;
|
||||||
|
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.*;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
||||||
|
import ru.windcorp.progressia.client.graphics.texture.SimpleTextures;
|
||||||
|
import ru.windcorp.progressia.client.localization.Localizer;
|
||||||
|
import ru.windcorp.progressia.client.localization.MutableString;
|
||||||
|
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class LayerOptions extends Background {
|
||||||
|
public LayerOptions(String name) {
|
||||||
|
super(name, new LayoutAlign(0, 1f, 15), SimpleTextures.get("title/background"));
|
||||||
|
|
||||||
|
Group content = new Group("Layer" + name + ".Group", new LayoutVertical(15));
|
||||||
|
|
||||||
|
Font font = new Font().withColor(Colors.BLUE).withAlign(0.5f);
|
||||||
|
|
||||||
|
MutableString languageText = new MutableStringLocalized("Layer" + name + ".Language");
|
||||||
|
content.addChild(new Button(name + ".Language", new Label(name + ".Language", font, languageText)).addAction(this::toggleLanguage));
|
||||||
|
|
||||||
|
MutableString playText = new MutableStringLocalized("Layer" + name + ".Return");
|
||||||
|
content.addChild(new Button(name + ".Return", new Label(name + ".Return", font, playText)).addAction(this::openTitle));
|
||||||
|
|
||||||
|
getRoot().addChild(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openTitle(BasicButton basicButton) {
|
||||||
|
GUI.removeLayer(this);
|
||||||
|
GUI.addTopLayer(new LayerTitle("Title"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleLanguage(BasicButton basicButton)
|
||||||
|
{
|
||||||
|
String curLang = Localizer.getInstance().getLanguage();
|
||||||
|
List<String> allLangs = Localizer.getInstance().getLanguages();
|
||||||
|
int pos = allLangs.indexOf(curLang);
|
||||||
|
pos++;
|
||||||
|
if (pos >= allLangs.size())
|
||||||
|
{
|
||||||
|
Localizer.getInstance().setLanguage(allLangs.get(0));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Localizer.getInstance().setLanguage(allLangs.get(pos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,422 +0,0 @@
|
|||||||
/*
|
|
||||||
* Progressia
|
|
||||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ru.windcorp.progressia.test;
|
|
||||||
|
|
||||||
import glm.vec._3.Vec3;
|
|
||||||
import glm.vec._4.Vec4;
|
|
||||||
import ru.windcorp.progressia.client.Client;
|
|
||||||
import ru.windcorp.progressia.client.ClientState;
|
|
||||||
import ru.windcorp.progressia.client.graphics.Colors;
|
|
||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
|
|
||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
|
||||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.DynamicLabel;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.Label;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.Group;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
|
||||||
import ru.windcorp.progressia.client.localization.Localizer;
|
|
||||||
import ru.windcorp.progressia.client.localization.MutableString;
|
|
||||||
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
|
||||||
import ru.windcorp.progressia.client.world.WorldRender;
|
|
||||||
import ru.windcorp.progressia.common.Units;
|
|
||||||
import ru.windcorp.progressia.common.util.dynstr.DynamicStrings;
|
|
||||||
import ru.windcorp.progressia.server.Server;
|
|
||||||
import ru.windcorp.progressia.server.ServerState;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
public class LayerTestGUI extends GUILayer {
|
|
||||||
|
|
||||||
public LayerTestGUI() {
|
|
||||||
super("LayerTestGui", new LayoutAlign(0, 1, 5));
|
|
||||||
|
|
||||||
Group group = new Group("ControlDisplays", new LayoutVertical(5));
|
|
||||||
|
|
||||||
Vec4 color = Colors.WHITE;
|
|
||||||
Font font = new Font().withColor(color).deriveOutlined();
|
|
||||||
|
|
||||||
TestPlayerControls tpc = TestPlayerControls.getInstance();
|
|
||||||
|
|
||||||
group.addChild(
|
|
||||||
new Label(
|
|
||||||
"IsFlyingDisplay",
|
|
||||||
font,
|
|
||||||
tmp_dynFormat("LayerTestGUI.IsFlyingDisplay", tpc::isFlying)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
group.addChild(
|
|
||||||
new Label(
|
|
||||||
"IsSprintingDisplay",
|
|
||||||
font,
|
|
||||||
tmp_dynFormat("LayerTestGUI.IsSprintingDisplay", tpc::isSprinting)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
group.addChild(
|
|
||||||
new Label(
|
|
||||||
"CameraModeDisplay",
|
|
||||||
font,
|
|
||||||
tmp_dynFormat(
|
|
||||||
"LayerTestGUI.CameraModeDisplay",
|
|
||||||
ClientState.getInstance().getCamera()::getCurrentModeIndex
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
group.addChild(
|
|
||||||
new Label(
|
|
||||||
"LanguageDisplay",
|
|
||||||
font,
|
|
||||||
tmp_dynFormat("LayerTestGUI.LanguageDisplay", Localizer.getInstance()::getLanguage)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
group.addChild(
|
|
||||||
new Label(
|
|
||||||
"FullscreenDisplay",
|
|
||||||
font,
|
|
||||||
tmp_dynFormat("LayerTestGUI.IsFullscreen", GraphicsBackend::isFullscreen)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
group.addChild(
|
|
||||||
new Label(
|
|
||||||
"VSyncDisplay",
|
|
||||||
font,
|
|
||||||
tmp_dynFormat("LayerTestGUI.IsVSync", GraphicsBackend::isVSyncEnabled)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
group.addChild(
|
|
||||||
new DynamicLabel(
|
|
||||||
"FPSDisplay",
|
|
||||||
font,
|
|
||||||
DynamicStrings.builder()
|
|
||||||
.addDyn(new MutableStringLocalized("LayerTestGUI.FPSDisplay"))
|
|
||||||
.addDyn(() -> FPS_RECORD.update(GraphicsInterface.getFPS()), 5, 1)
|
|
||||||
.buildSupplier(),
|
|
||||||
128
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
group.addChild(
|
|
||||||
new DynamicLabel(
|
|
||||||
"TPSDisplay",
|
|
||||||
font,
|
|
||||||
LayerTestGUI::getTPS,
|
|
||||||
128
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
group.addChild(
|
|
||||||
new DynamicLabel(
|
|
||||||
"ChunkStatsDisplay",
|
|
||||||
font,
|
|
||||||
DynamicStrings.builder()
|
|
||||||
.addDyn(new MutableStringLocalized("LayerTestGUI.ChunkStatsDisplay"))
|
|
||||||
.addDyn(() -> {
|
|
||||||
if (ClientState.getInstance() == null) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
WorldRender world = ClientState.getInstance().getWorld();
|
|
||||||
return world.getChunks().size() - world.getPendingChunkUpdates();
|
|
||||||
}
|
|
||||||
}, 4)
|
|
||||||
.add('/')
|
|
||||||
.addDyn(() -> {
|
|
||||||
if (ClientState.getInstance() == null) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return ClientState.getInstance().getWorld().getPendingChunkUpdates();
|
|
||||||
}
|
|
||||||
}, 4)
|
|
||||||
.add('/')
|
|
||||||
.addDyn(() -> {
|
|
||||||
if (ServerState.getInstance() == null) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return ServerState.getInstance().getWorld().getChunks().size();
|
|
||||||
}
|
|
||||||
}, 4)
|
|
||||||
.buildSupplier(),
|
|
||||||
128
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
group.addChild(
|
|
||||||
new DynamicLabel(
|
|
||||||
"PosDisplay",
|
|
||||||
font,
|
|
||||||
LayerTestGUI::getPos,
|
|
||||||
128
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
group.addChild(
|
|
||||||
new Label(
|
|
||||||
"SelectedBlockDisplay",
|
|
||||||
font,
|
|
||||||
tmp_dynFormat(
|
|
||||||
"LayerTestGUI.SelectedBlockDisplay",
|
|
||||||
() -> tpc.isBlockSelected() ? ">" : " ",
|
|
||||||
() -> tpc.getSelectedBlock().getId()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
group.addChild(
|
|
||||||
new Label(
|
|
||||||
"SelectedTileDisplay",
|
|
||||||
font,
|
|
||||||
tmp_dynFormat(
|
|
||||||
"LayerTestGUI.SelectedTileDisplay",
|
|
||||||
() -> tpc.isBlockSelected() ? " " : ">",
|
|
||||||
() -> tpc.getSelectedTile().getId()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
group.addChild(
|
|
||||||
new Label(
|
|
||||||
"PlacementModeHint",
|
|
||||||
font,
|
|
||||||
new MutableStringLocalized("LayerTestGUI.PlacementModeHint").format("\u2B04")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
getRoot().addChild(group);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Runnable getUpdateCallback() {
|
|
||||||
Collection<Label> labels = new ArrayList<>();
|
|
||||||
getRoot().getChild(0).getChildren().forEach(c -> {
|
|
||||||
if (c instanceof Label) {
|
|
||||||
labels.add((Label) c);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return () -> labels.forEach(Label::update);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Averager {
|
|
||||||
|
|
||||||
private static final int DISPLAY_INERTIA = 32;
|
|
||||||
private static final double UPDATE_INTERVAL = Units.get(50.0, "ms");
|
|
||||||
|
|
||||||
private final double[] values = new double[DISPLAY_INERTIA];
|
|
||||||
private int size;
|
|
||||||
private int head;
|
|
||||||
|
|
||||||
private long lastUpdate;
|
|
||||||
|
|
||||||
public void add(double value) {
|
|
||||||
if (size == values.length) {
|
|
||||||
values[head] = value;
|
|
||||||
head++;
|
|
||||||
if (head == values.length)
|
|
||||||
head = 0;
|
|
||||||
} else {
|
|
||||||
values[size] = value;
|
|
||||||
size++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public double average() {
|
|
||||||
double product = 1;
|
|
||||||
|
|
||||||
if (size == values.length) {
|
|
||||||
for (double d : values)
|
|
||||||
product *= d;
|
|
||||||
} else {
|
|
||||||
for (int i = 0; i < size; ++i)
|
|
||||||
product *= values[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.pow(product, 1.0 / size);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double update(double value) {
|
|
||||||
long now = (long) (GraphicsInterface.getTime() / UPDATE_INTERVAL);
|
|
||||||
if (lastUpdate != now) {
|
|
||||||
lastUpdate = now;
|
|
||||||
add(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return average();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String[] CLOCK_CHARS = "\u2591\u2598\u259d\u2580\u2596\u258c\u259e\u259b\u2597\u259a\u2590\u259c\u2584\u2599\u259f\u2588"
|
|
||||||
.chars().mapToObj(c -> ((char) c) + "").toArray(String[]::new);
|
|
||||||
|
|
||||||
private static String getTPSClockChar() {
|
|
||||||
return CLOCK_CHARS[(int) (ServerState.getInstance().getUptimeTicks() % CLOCK_CHARS.length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Averager FPS_RECORD = new Averager();
|
|
||||||
private static final Averager TPS_RECORD = new Averager();
|
|
||||||
|
|
||||||
private static final Supplier<CharSequence> TPS_STRING = DynamicStrings.builder()
|
|
||||||
.addDyn(new MutableStringLocalized("LayerTestGUI.TPSDisplay"))
|
|
||||||
.addDyn(() -> TPS_RECORD.update(ServerState.getInstance().getTPS()), 5, 1)
|
|
||||||
.add(' ')
|
|
||||||
.addDyn(LayerTestGUI::getTPSClockChar)
|
|
||||||
.buildSupplier();
|
|
||||||
|
|
||||||
private static final Supplier<CharSequence> POS_STRING = DynamicStrings.builder()
|
|
||||||
.addDyn(new MutableStringLocalized("LayerTestGUI.PosDisplay"))
|
|
||||||
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().x, 7, 1)
|
|
||||||
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().y, 7, 1)
|
|
||||||
.addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().z, 7, 1)
|
|
||||||
.buildSupplier();
|
|
||||||
|
|
||||||
private static CharSequence getTPS() {
|
|
||||||
Server server = ServerState.getInstance();
|
|
||||||
if (server == null)
|
|
||||||
return Localizer.getInstance().getValue("LayerTestGUI.TPSDisplay.NA");
|
|
||||||
|
|
||||||
return TPS_STRING.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CharSequence getPos() {
|
|
||||||
Client client = ClientState.getInstance();
|
|
||||||
if (client == null)
|
|
||||||
return Localizer.getInstance().getValue("LayerTestGUI.PosDisplay.NA.Client");
|
|
||||||
|
|
||||||
Vec3 pos = client.getCamera().getLastAnchorPosition();
|
|
||||||
if (Float.isNaN(pos.x)) {
|
|
||||||
return Localizer.getInstance().getValue("LayerTestGUI.PosDisplay.NA.Entity");
|
|
||||||
} else {
|
|
||||||
return POS_STRING.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MutableString tmp_dynFormat(String formatKey, Supplier<?>... suppliers) {
|
|
||||||
return new MutableStringLocalized(formatKey).apply(s -> {
|
|
||||||
Object[] args = new Object[suppliers.length];
|
|
||||||
|
|
||||||
for (int i = 0; i < suppliers.length; ++i) {
|
|
||||||
Supplier<?> supplier = suppliers[i];
|
|
||||||
|
|
||||||
Object value = supplier != null ? supplier.get() : "null";
|
|
||||||
if (!(value instanceof Number)) {
|
|
||||||
value = Objects.toString(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
args[i] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return String.format(s, args);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// private static class DebugComponent extends Component {
|
|
||||||
// private final int color;
|
|
||||||
//
|
|
||||||
// public DebugComponent(String name, Vec2i size, int color) {
|
|
||||||
// super(name);
|
|
||||||
// this.color = color;
|
|
||||||
//
|
|
||||||
// setPreferredSize(size);
|
|
||||||
//
|
|
||||||
// addListener(new Object() {
|
|
||||||
// @Subscribe
|
|
||||||
// public void onHoverChanged(HoverEvent e) {
|
|
||||||
// requestReassembly();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// addListener(KeyEvent.class, this::onClicked);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private boolean onClicked(KeyEvent event) {
|
|
||||||
// if (!event.isMouse()) {
|
|
||||||
// return false;
|
|
||||||
// } else if (event.isPress() && event.isLeftMouseButton()) {
|
|
||||||
// System.out.println("You pressed a Component!");
|
|
||||||
// }
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// protected void assembleSelf(RenderTarget target) {
|
|
||||||
// target.fill(getX(), getY(), getWidth(), getHeight(), Colors.BLACK);
|
|
||||||
//
|
|
||||||
// target.fill(
|
|
||||||
// getX() + 2, getY() + 2,
|
|
||||||
// getWidth() - 4, getHeight() - 4,
|
|
||||||
// isHovered() ? Colors.DEBUG_YELLOW : color
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public LayerTestGUI() {
|
|
||||||
// super("LayerTestGui", new LayoutAlign(1, 0.75, 5));
|
|
||||||
//
|
|
||||||
// Panel panel = new Panel("Alex", new LayoutVertical(5));
|
|
||||||
//
|
|
||||||
// panel.addChild(new DebugComponent("Bravo", new Vec2i(200, 100), 0x44FF44));
|
|
||||||
//
|
|
||||||
// Component charlie = new DebugComponent("Charlie", null, 0x222222);
|
|
||||||
// charlie.setLayout(new LayoutVertical(5));
|
|
||||||
//
|
|
||||||
// //Debug
|
|
||||||
// Localizer.getInstance().setLanguage("ru-RU");
|
|
||||||
// MutableString epsilon = new MutableStringLocalized("Epsilon")
|
|
||||||
// .addListener(() -> ((Label)charlie.getChild(0)).update()).format(34, "thirty-four");
|
|
||||||
// // These two are swapped in code due to a bug in layouts, fixing ATM
|
|
||||||
// charlie.addChild(
|
|
||||||
// new Label(
|
|
||||||
// "Delta",
|
|
||||||
// new Font().withColor(0xCCBB44).deriveShadow().deriveBold(),
|
|
||||||
// "Пре-альфа!"
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// charlie.addChild(
|
|
||||||
// new Label(
|
|
||||||
// "Epsilon",
|
|
||||||
// new Font().withColor(0x4444BB).deriveItalic(),
|
|
||||||
// () -> epsilon.get().concat("\u269b")
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// panel.addChild(charlie);
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// charlie.addListener(KeyEvent.class, e -> {
|
|
||||||
// if(e.isPress() && e.getKey() == GLFW.GLFW_KEY_L) {
|
|
||||||
// Localizer localizer = Localizer.getInstance();
|
|
||||||
// if (localizer.getLanguage().equals("ru-RU")) {
|
|
||||||
// localizer.setLanguage("en-US");
|
|
||||||
// } else {
|
|
||||||
// localizer.setLanguage("ru-RU");
|
|
||||||
// }
|
|
||||||
// return true;
|
|
||||||
// } return false;
|
|
||||||
// });
|
|
||||||
// charlie.setFocusable(true);
|
|
||||||
// charlie.takeFocus();
|
|
||||||
//
|
|
||||||
// getRoot().addChild(panel);
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
@ -27,8 +27,8 @@ import ru.windcorp.progressia.client.graphics.Colors;
|
|||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||||
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer;
|
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer;
|
||||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
|
||||||
|
|
||||||
public class LayerTestUI extends AssembledFlatLayer {
|
public class LayerTestUI extends AssembledFlatLayer {
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ public class LayerTestUI extends AssembledFlatLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleInput(Input input) {
|
public void handleInput(InputEvent event) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
package ru.windcorp.progressia.test;
|
package ru.windcorp.progressia.test;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.Progressia;
|
||||||
import ru.windcorp.progressia.client.ClientState;
|
import ru.windcorp.progressia.client.ClientState;
|
||||||
import ru.windcorp.progressia.client.graphics.Colors;
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
import ru.windcorp.progressia.client.graphics.GUI;
|
import ru.windcorp.progressia.client.graphics.GUI;
|
||||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Background;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.BasicButton;
|
import ru.windcorp.progressia.client.graphics.gui.BasicButton;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.Button;
|
import ru.windcorp.progressia.client.graphics.gui.Button;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.Group;
|
import ru.windcorp.progressia.client.graphics.gui.Group;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.Label;
|
import ru.windcorp.progressia.client.graphics.gui.Label;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.TextureComponent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutEdges;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
||||||
|
import ru.windcorp.progressia.client.graphics.texture.SimpleTextures;
|
||||||
import ru.windcorp.progressia.client.localization.MutableString;
|
import ru.windcorp.progressia.client.localization.MutableString;
|
||||||
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
||||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||||
@ -24,34 +28,58 @@ import java.nio.file.Paths;
|
|||||||
import java.nio.file.SimpleFileVisitor;
|
import java.nio.file.SimpleFileVisitor;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
|
||||||
public class LayerTitle extends GUILayer {
|
public class LayerTitle extends Background {
|
||||||
|
|
||||||
private final BasicButton resetButton;
|
private final BasicButton resetButton;
|
||||||
|
|
||||||
public LayerTitle(String name) {
|
public LayerTitle(String name) {
|
||||||
super(name, new LayoutAlign(0.5f, 0.7f, 15));
|
super(name, new LayoutAlign(0, 1f, 15), SimpleTextures.get("title/background"));
|
||||||
Group content = new Group("Layer" + name + ".Group", new LayoutVertical(15));
|
Group content = new Group("Layer" + name + ".Group", new LayoutVertical(15));
|
||||||
|
Group info = new Group("Layer"+name+".InfoGroup", new LayoutEdges(30));
|
||||||
|
Group buttonContent = new Group("Layer" + name + ".ButtonGroup", new LayoutColumn(15, 320));
|
||||||
|
|
||||||
MutableString title = new MutableStringLocalized("Layer" + name + ".Title");
|
Font titleFont = new Font().deriveBold().withColor(Colors.BLUE).withAlign(0.5f);
|
||||||
Font titleFont = new Font().deriveBold().withColor(Colors.BLACK).withAlign(0.5f);
|
content.addChild(new TextureComponent(name + ".Title", SimpleTextures.get("title/progressia")));
|
||||||
content.addChild(new Label(name + ".Title", titleFont, title));
|
|
||||||
|
info.addChild(new Label(
|
||||||
|
"About",
|
||||||
|
titleFont,
|
||||||
|
new MutableStringLocalized("LayerAbout.Title")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
info.addChild(
|
||||||
|
new Label(
|
||||||
|
"Version",
|
||||||
|
titleFont,
|
||||||
|
Progressia.getFullerVersion()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
content.addChild(info);
|
||||||
|
|
||||||
Font buttonFont = titleFont.deriveNotBold();
|
Font buttonFont = titleFont.deriveNotBold();
|
||||||
MutableString playText = new MutableStringLocalized("Layer" + name + ".Play");
|
MutableString playText = new MutableStringLocalized("Layer" + name + ".Play");
|
||||||
content.addChild(new Button(name + ".Play", new Label(name + ".Play", buttonFont, playText)).addAction(this::startGame));
|
buttonContent.addChild(new Button(name + ".Play", new Label(name + ".Play", buttonFont, playText)).addAction(this::startGame));
|
||||||
|
|
||||||
MutableString resetText = new MutableStringLocalized("Layer" + name + ".Reset");
|
MutableString resetText = new MutableStringLocalized("Layer" + name + ".Reset");
|
||||||
this.resetButton = new Button(name + ".Reset", new Label(name + ".Reset", buttonFont, resetText)).addAction(this::resetWorld);
|
this.resetButton = new Button(name + ".Reset", new Label(name + ".Reset", buttonFont, resetText)).addAction(this::resetWorld);
|
||||||
content.addChild(resetButton);
|
buttonContent.addChild(resetButton);
|
||||||
|
|
||||||
updateResetButton();
|
updateResetButton();
|
||||||
|
|
||||||
|
MutableString settingsText = new MutableStringLocalized("Layer" + name + ".Options");
|
||||||
|
buttonContent.addChild(new Button(name + ".Options", new Label(name + ".Options", buttonFont, settingsText)).addAction(this::openOptions));
|
||||||
|
|
||||||
MutableString quitText = new MutableStringLocalized("Layer" + name + ".Quit");
|
MutableString quitText = new MutableStringLocalized("Layer" + name + ".Quit");
|
||||||
content.addChild(new Button(name + "Quit", new Label(name + ".Quit", buttonFont, quitText)).addAction(b -> {
|
buttonContent.addChild(new Button(name + "Quit", new Label(name + ".Quit", buttonFont, quitText)).addAction(b -> System.exit(0)));
|
||||||
System.exit(0);
|
|
||||||
}));
|
content.addChild(buttonContent);
|
||||||
|
|
||||||
getRoot().addChild(content);
|
getRoot().addChild(content);
|
||||||
|
buttonContent.setPreferredSize(500, 1000);
|
||||||
|
|
||||||
|
CubeComponent cube = new CubeComponent(name+".Cube",300);
|
||||||
|
|
||||||
|
getRoot().addChild(cube);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateResetButton() {
|
private void updateResetButton() {
|
||||||
@ -67,7 +95,7 @@ public class LayerTitle extends GUILayer {
|
|||||||
throw CrashReports.report(e, "Problem with loading server");
|
throw CrashReports.report(e, "Problem with loading server");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetWorld(BasicButton basicButton) {
|
private void resetWorld(BasicButton basicButton) {
|
||||||
Path rootPath = Paths.get("tmp_world");
|
Path rootPath = Paths.get("tmp_world");
|
||||||
|
|
||||||
@ -88,8 +116,13 @@ public class LayerTitle extends GUILayer {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw CrashReports.report(e, "Could not reset world");
|
throw CrashReports.report(e, "Could not reset world");
|
||||||
}
|
}
|
||||||
|
|
||||||
updateResetButton();
|
updateResetButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void openOptions(BasicButton basicButton) {
|
||||||
|
GUI.removeLayer(this);
|
||||||
|
GUI.addTopLayer(new LayerOptions("Options"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
33
src/main/java/ru/windcorp/progressia/test/LayoutColumn.java
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package ru.windcorp.progressia.test;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
||||||
|
|
||||||
|
public class LayoutColumn extends LayoutVertical {
|
||||||
|
|
||||||
|
protected int maxWidth;
|
||||||
|
private final int margin;
|
||||||
|
|
||||||
|
public LayoutColumn(int gap, int maxWidth)
|
||||||
|
{
|
||||||
|
super(gap);
|
||||||
|
this.maxWidth = maxWidth;
|
||||||
|
margin = gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void layout(Component c) {
|
||||||
|
int x = c.getX() + margin,
|
||||||
|
y = c.getY() + c.getHeight();
|
||||||
|
|
||||||
|
synchronized (c.getChildren()) {
|
||||||
|
for (Component child : c.getChildren()) {
|
||||||
|
|
||||||
|
int height = child.getPreferredSize().y;
|
||||||
|
y -= margin + height;
|
||||||
|
child.setBounds(x, y, maxWidth, height);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,15 +28,7 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
import glm.vec._3.i.Vec3i;
|
|
||||||
import ru.windcorp.progressia.client.ClientState;
|
|
||||||
import ru.windcorp.progressia.client.audio.Sound;
|
|
||||||
import ru.windcorp.progressia.client.comms.controls.*;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
|
||||||
import ru.windcorp.progressia.client.graphics.world.Selection;
|
|
||||||
import ru.windcorp.progressia.client.world.block.*;
|
import ru.windcorp.progressia.client.world.block.*;
|
||||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerRegistry;
|
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerRegistry;
|
||||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSimple;
|
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSimple;
|
||||||
@ -45,25 +37,22 @@ import ru.windcorp.progressia.client.world.entity.*;
|
|||||||
import ru.windcorp.progressia.client.world.tile.*;
|
import ru.windcorp.progressia.client.world.tile.*;
|
||||||
import ru.windcorp.progressia.common.collision.AABB;
|
import ru.windcorp.progressia.common.collision.AABB;
|
||||||
import ru.windcorp.progressia.common.collision.CollisionModel;
|
import ru.windcorp.progressia.common.collision.CollisionModel;
|
||||||
import ru.windcorp.progressia.common.comms.controls.*;
|
|
||||||
import ru.windcorp.progressia.common.state.StatefulObjectRegistry.Factory;
|
import ru.windcorp.progressia.common.state.StatefulObjectRegistry.Factory;
|
||||||
import ru.windcorp.progressia.common.world.GravityModelRegistry;
|
import ru.windcorp.progressia.common.world.GravityModelRegistry;
|
||||||
import ru.windcorp.progressia.common.world.block.*;
|
import ru.windcorp.progressia.common.world.block.*;
|
||||||
import ru.windcorp.progressia.common.world.entity.*;
|
import ru.windcorp.progressia.common.world.entity.*;
|
||||||
import ru.windcorp.progressia.common.world.io.ChunkIO;
|
import ru.windcorp.progressia.common.world.io.ChunkIO;
|
||||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
|
||||||
import ru.windcorp.progressia.common.world.tile.*;
|
import ru.windcorp.progressia.common.world.tile.*;
|
||||||
import ru.windcorp.progressia.server.Server;
|
|
||||||
import ru.windcorp.progressia.server.comms.controls.*;
|
|
||||||
import ru.windcorp.progressia.server.world.block.*;
|
import ru.windcorp.progressia.server.world.block.*;
|
||||||
import ru.windcorp.progressia.server.world.context.ServerBlockContext;
|
|
||||||
import ru.windcorp.progressia.server.world.context.ServerTileContext;
|
|
||||||
import ru.windcorp.progressia.server.world.context.ServerTileStackContext;
|
|
||||||
import ru.windcorp.progressia.server.world.entity.*;
|
import ru.windcorp.progressia.server.world.entity.*;
|
||||||
import ru.windcorp.progressia.server.world.generation.planet.PlanetGravityModel;
|
import ru.windcorp.progressia.server.world.generation.planet.PlanetGravityModel;
|
||||||
import ru.windcorp.progressia.server.world.tile.*;
|
import ru.windcorp.progressia.server.world.tile.*;
|
||||||
|
import ru.windcorp.progressia.test.Flowers.FlowerVariant;
|
||||||
import ru.windcorp.progressia.test.Rocks.RockType;
|
import ru.windcorp.progressia.test.Rocks.RockType;
|
||||||
|
import ru.windcorp.progressia.test.controls.TestPlayerControls;
|
||||||
import ru.windcorp.progressia.test.gen.TestGravityModel;
|
import ru.windcorp.progressia.test.gen.TestGravityModel;
|
||||||
|
import ru.windcorp.progressia.test.trees.BlockRenderLeavesHazel;
|
||||||
|
import ru.windcorp.progressia.test.trees.BlockRenderLeavesPine;
|
||||||
|
|
||||||
public class TestContent {
|
public class TestContent {
|
||||||
|
|
||||||
@ -75,6 +64,7 @@ public class TestContent {
|
|||||||
public static final List<TileData> PLACEABLE_TILES = new ArrayList<>();
|
public static final List<TileData> PLACEABLE_TILES = new ArrayList<>();
|
||||||
|
|
||||||
public static final Rocks ROCKS = new Rocks();
|
public static final Rocks ROCKS = new Rocks();
|
||||||
|
public static final Flowers FLOWERS = new Flowers();
|
||||||
|
|
||||||
public static void registerContent() {
|
public static void registerContent() {
|
||||||
registerWorldContent();
|
registerWorldContent();
|
||||||
@ -129,7 +119,7 @@ public class TestContent {
|
|||||||
register(new BlockLogic("Test:Log"));
|
register(new BlockLogic("Test:Log"));
|
||||||
|
|
||||||
register(new BlockData("Test:TemporaryLeaves"));
|
register(new BlockData("Test:TemporaryLeaves"));
|
||||||
register(new BlockRenderTransparentCube("Test:TemporaryLeaves", getBlockTexture("TemporaryLeaves")));
|
register(new BlockRenderLeavesHazel("Test:TemporaryLeaves", getBlockTexture("LeavesHazel")));
|
||||||
// Sic, using Glass logic for leaves because Test
|
// Sic, using Glass logic for leaves because Test
|
||||||
register(new TestBlockLogicGlass("Test:TemporaryLeaves"));
|
register(new TestBlockLogicGlass("Test:TemporaryLeaves"));
|
||||||
|
|
||||||
@ -140,6 +130,11 @@ public class TestContent {
|
|||||||
register(new BlockData("Test:Tux"));
|
register(new BlockData("Test:Tux"));
|
||||||
register(new TestBlockRenderTux("Test:Tux"));
|
register(new TestBlockRenderTux("Test:Tux"));
|
||||||
register(new BlockLogic("Test:Tux"));
|
register(new BlockLogic("Test:Tux"));
|
||||||
|
|
||||||
|
register(new BlockData("Test:LeavesPine"));
|
||||||
|
register(new BlockRenderLeavesPine("Test:LeavesPine", getBlockTexture("LeavesPine")));
|
||||||
|
// Sic, using Glass logic for leaves because Test
|
||||||
|
register(new TestBlockLogicGlass("Test:LeavesPine"));
|
||||||
|
|
||||||
BlockDataRegistry.getInstance().values().forEach(PLACEABLE_BLOCKS::add);
|
BlockDataRegistry.getInstance().values().forEach(PLACEABLE_BLOCKS::add);
|
||||||
PLACEABLE_BLOCKS.removeIf(b -> placeableBlacklist.contains(b.getId()));
|
PLACEABLE_BLOCKS.removeIf(b -> placeableBlacklist.contains(b.getId()));
|
||||||
@ -183,15 +178,8 @@ public class TestContent {
|
|||||||
);
|
);
|
||||||
register(new TestTileLogicGrass(id));
|
register(new TestTileLogicGrass(id));
|
||||||
});
|
});
|
||||||
|
|
||||||
Arrays.asList(
|
registerFlowers();
|
||||||
"Yellow",
|
|
||||||
"White",
|
|
||||||
"Purple",
|
|
||||||
"Blue"
|
|
||||||
).forEach(color -> {
|
|
||||||
registerSimplestTransparentTile(color + "Flowers");
|
|
||||||
});
|
|
||||||
|
|
||||||
registerSimplestTransparentTile("Stones");
|
registerSimplestTransparentTile("Stones");
|
||||||
|
|
||||||
@ -216,21 +204,12 @@ public class TestContent {
|
|||||||
registerSimplestOpaqueTile("TilesLarge");
|
registerSimplestOpaqueTile("TilesLarge");
|
||||||
registerSimplestOpaqueTile("TilesSmall");
|
registerSimplestOpaqueTile("TilesSmall");
|
||||||
|
|
||||||
registerHerb("LowGrass", 6);
|
registerHerb("GrassMeadow0", 6);
|
||||||
registerHerb("MediumGrass", 6);
|
registerHerb("GrassMeadow1", 6);
|
||||||
registerHerb("TallGrass", 6);
|
registerHerb("GrassMeadow2", 6);
|
||||||
|
registerHerb("GrassBluegrass0", 6);
|
||||||
Arrays.asList(
|
registerHerb("GrassBluegrass1", 6);
|
||||||
"Dandelion",
|
registerHerb("GrassBluegrass2", 6);
|
||||||
"Lavander"
|
|
||||||
).forEach(variant -> {
|
|
||||||
String fullName = "Tiny" + variant + "Flowers";
|
|
||||||
String id = "Test:" + fullName;
|
|
||||||
|
|
||||||
register(new TileData(id));
|
|
||||||
register(new TileRenderTinyFlower(id, getTileTexture(fullName), 8, 0.5f));
|
|
||||||
register(new HangingTileLogic(id));
|
|
||||||
});
|
|
||||||
|
|
||||||
registerHerb("Bush", 1);
|
registerHerb("Bush", 1);
|
||||||
registerHerb("Fern", 3);
|
registerHerb("Fern", 3);
|
||||||
@ -240,6 +219,21 @@ public class TestContent {
|
|||||||
PLACEABLE_TILES.sort(Comparator.comparing(TileData::getId));
|
PLACEABLE_TILES.sort(Comparator.comparing(TileData::getId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void registerFlowers() {
|
||||||
|
|
||||||
|
final float common = 0.8f;
|
||||||
|
|
||||||
|
FLOWERS.create("Clover", common, FlowerVariant.HERB, FlowerVariant.TINY, FlowerVariant.FLAT);
|
||||||
|
FLOWERS.create("Dandelion", common, FlowerVariant.TINY, FlowerVariant.FLAT);
|
||||||
|
FLOWERS.create("Geranium", common, FlowerVariant.TINY, FlowerVariant.FLAT);
|
||||||
|
FLOWERS.create("Knapweed", common, FlowerVariant.TINY, FlowerVariant.FLAT);
|
||||||
|
FLOWERS.create("YellowPea", common, FlowerVariant.TINY, FlowerVariant.FLAT);
|
||||||
|
FLOWERS.create("Daisy", common, FlowerVariant.TINY, FlowerVariant.FLAT);
|
||||||
|
FLOWERS.create("Lavander", common, FlowerVariant.TINY, FlowerVariant.FLAT);
|
||||||
|
|
||||||
|
FLOWERS.registerAllFlowers();
|
||||||
|
}
|
||||||
|
|
||||||
private static void registerSimplestBlock(String name) {
|
private static void registerSimplestBlock(String name) {
|
||||||
String id = "Test:" + name;
|
String id = "Test:" + name;
|
||||||
register(new BlockData(id));
|
register(new BlockData(id));
|
||||||
@ -280,55 +274,9 @@ public class TestContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void regsiterControls() {
|
private static void regsiterControls() {
|
||||||
ControlDataRegistry data = ControlDataRegistry.getInstance();
|
TestPlayerControls.getInstance().registerControls();
|
||||||
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
|
|
||||||
ControlLogicRegistry logic = ControlLogicRegistry.getInstance();
|
|
||||||
|
|
||||||
data.register("Test:BreakBlock", ControlBreakBlockData::new);
|
|
||||||
triggers.register(
|
|
||||||
ControlTriggers.of(
|
|
||||||
"Test:BreakBlock",
|
|
||||||
KeyEvent.class,
|
|
||||||
TestContent::onBlockBreakTrigger,
|
|
||||||
KeyMatcher.of(GLFW.GLFW_MOUSE_BUTTON_LEFT).matcher(),
|
|
||||||
i -> isAnythingSelected()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
logic.register(ControlLogic.of("Test:BreakBlock", TestContent::onBlockBreakReceived));
|
|
||||||
|
|
||||||
data.register("Test:PlaceBlock", ControlPlaceBlockData::new);
|
|
||||||
triggers.register(
|
|
||||||
ControlTriggers.of(
|
|
||||||
"Test:PlaceBlock",
|
|
||||||
KeyEvent.class,
|
|
||||||
TestContent::onBlockPlaceTrigger,
|
|
||||||
KeyMatcher.of(GLFW.GLFW_MOUSE_BUTTON_RIGHT).matcher(),
|
|
||||||
i -> isAnythingSelected() && TestPlayerControls.getInstance().isBlockSelected()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
logic.register(ControlLogic.of("Test:PlaceBlock", TestContent::onBlockPlaceReceived));
|
|
||||||
|
|
||||||
data.register("Test:PlaceTile", ControlPlaceTileData::new);
|
|
||||||
triggers.register(
|
|
||||||
ControlTriggers.of(
|
|
||||||
"Test:PlaceTile",
|
|
||||||
KeyEvent.class,
|
|
||||||
TestContent::onTilePlaceTrigger,
|
|
||||||
KeyMatcher.of(GLFW.GLFW_MOUSE_BUTTON_RIGHT).matcher(),
|
|
||||||
i -> isAnythingSelected() && !TestPlayerControls.getInstance().isBlockSelected()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
logic.register(ControlLogic.of("Test:PlaceTile", TestContent::onTilePlaceReceived));
|
|
||||||
|
|
||||||
triggers.register(
|
|
||||||
ControlTriggers.localOf(
|
|
||||||
"Test:StartNextMusic",
|
|
||||||
KeyEvent.class,
|
|
||||||
TestMusicPlayer::startNextNow,
|
|
||||||
KeyMatcher.of(GLFW.GLFW_KEY_M).matcher()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void register(BlockData x) {
|
private static void register(BlockData x) {
|
||||||
@ -384,95 +332,6 @@ public class TestContent {
|
|||||||
EntityLogicRegistry.getInstance().register(x);
|
EntityLogicRegistry.getInstance().register(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Selection getSelection() {
|
|
||||||
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
|
|
||||||
if (client == null || !client.isReady())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return client.getLocalPlayer().getSelection();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isAnythingSelected() {
|
|
||||||
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
|
|
||||||
if (client == null || !client.isReady())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return client.getLocalPlayer().getSelection().exists();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void onBlockBreakTrigger(ControlData control) {
|
|
||||||
((ControlBreakBlockData) control).setBlockInWorld(getSelection().getBlock());
|
|
||||||
Sound sfx = new Sound("Progressia:BlockDestroy");
|
|
||||||
sfx.setPosition(getSelection().getPoint());
|
|
||||||
sfx.setPitch((float) (Math.random() + 1 * 0.5));
|
|
||||||
sfx.play(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void onBlockBreakReceived(
|
|
||||||
Server server,
|
|
||||||
PacketControl packet,
|
|
||||||
ru.windcorp.progressia.server.comms.Client client
|
|
||||||
) {
|
|
||||||
Vec3i blockInWorld = ((ControlBreakBlockData) packet.getControl()).getBlockInWorld();
|
|
||||||
server.createAbsoluteContext().setBlock(blockInWorld, BlockDataRegistry.getInstance().get("Test:Air"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void onBlockPlaceTrigger(ControlData control) {
|
|
||||||
((ControlPlaceBlockData) control).set(
|
|
||||||
TestPlayerControls.getInstance().getSelectedBlock(),
|
|
||||||
getSelection().getBlock().add_(getSelection().getSurface().getVector())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void onBlockPlaceReceived(
|
|
||||||
Server server,
|
|
||||||
PacketControl packet,
|
|
||||||
ru.windcorp.progressia.server.comms.Client client
|
|
||||||
) {
|
|
||||||
ControlPlaceBlockData controlData = ((ControlPlaceBlockData) packet.getControl());
|
|
||||||
BlockData block = controlData.getBlock();
|
|
||||||
Vec3i blockInWorld = controlData.getBlockInWorld();
|
|
||||||
if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null)
|
|
||||||
return;
|
|
||||||
server.createAbsoluteContext().setBlock(blockInWorld, block);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void onTilePlaceTrigger(ControlData control) {
|
|
||||||
((ControlPlaceTileData) control).set(
|
|
||||||
TestPlayerControls.getInstance().getSelectedTile(),
|
|
||||||
getSelection().getBlock(),
|
|
||||||
getSelection().getSurface()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void onTilePlaceReceived(
|
|
||||||
Server server,
|
|
||||||
PacketControl packet,
|
|
||||||
ru.windcorp.progressia.server.comms.Client client
|
|
||||||
) {
|
|
||||||
ControlPlaceTileData controlData = ((ControlPlaceTileData) packet.getControl());
|
|
||||||
TileData tile = controlData.getTile();
|
|
||||||
Vec3i blockInWorld = controlData.getBlockInWorld();
|
|
||||||
AbsFace face = controlData.getFace();
|
|
||||||
|
|
||||||
if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (server.getWorld().getData().getTiles(blockInWorld, face).isFull()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerBlockContext context = server.createContext(blockInWorld);
|
|
||||||
ServerTileStackContext tsContext = context.push(context.toContext(face));
|
|
||||||
ServerTileContext tileContext = tsContext.push(tsContext.getTileCount());
|
|
||||||
|
|
||||||
TileLogic logic = TileLogicRegistry.getInstance().get(tile.getId());
|
|
||||||
if (!logic.canOccupyFace(tileContext)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tileContext.addTile(tile);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void registerMisc() {
|
private static void registerMisc() {
|
||||||
ChunkIO.registerCodec(new TestChunkCodec());
|
ChunkIO.registerCodec(new TestChunkCodec());
|
||||||
|
|
||||||
|
@ -110,6 +110,7 @@ public class TestMusicPlayer implements Runnable {
|
|||||||
String file = it.next().toString();
|
String file = it.next().toString();
|
||||||
if (!file.endsWith(".ogg") && !file.endsWith(".oga")) {
|
if (!file.endsWith(".ogg") && !file.endsWith(".oga")) {
|
||||||
LogManager.getLogger().warn("Skipping " + file + ": not .ogg nor .oga");
|
LogManager.getLogger().warn("Skipping " + file + ": not .ogg nor .oga");
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
String id = "Progressia:Music" + (i++);
|
String id = "Progressia:Music" + (i++);
|
||||||
|
@ -1,469 +0,0 @@
|
|||||||
/*
|
|
||||||
* Progressia
|
|
||||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ru.windcorp.progressia.test;
|
|
||||||
|
|
||||||
import glm.Glm;
|
|
||||||
import glm.mat._3.Mat3;
|
|
||||||
import glm.mat._4.Mat4;
|
|
||||||
import glm.vec._3.Vec3;
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
import ru.windcorp.progressia.client.ClientState;
|
|
||||||
import ru.windcorp.progressia.client.graphics.GUI;
|
|
||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
|
|
||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
|
||||||
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.WheelScrollEvent;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
|
||||||
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
|
|
||||||
import ru.windcorp.progressia.client.localization.Localizer;
|
|
||||||
import ru.windcorp.progressia.common.Units;
|
|
||||||
import ru.windcorp.progressia.common.util.Matrices;
|
|
||||||
import ru.windcorp.progressia.common.util.VectorUtil;
|
|
||||||
import ru.windcorp.progressia.common.util.Vectors;
|
|
||||||
import ru.windcorp.progressia.common.world.block.BlockData;
|
|
||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
|
||||||
import ru.windcorp.progressia.common.world.tile.TileData;
|
|
||||||
import ru.windcorp.progressia.server.ServerState;
|
|
||||||
|
|
||||||
public class TestPlayerControls {
|
|
||||||
|
|
||||||
private static TestPlayerControls instance = new TestPlayerControls();
|
|
||||||
|
|
||||||
public static TestPlayerControls getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final double MODE_SWITCH_MAX_DELAY = 300 * Units.MILLISECONDS;
|
|
||||||
private static final double MODE_SPRINT_SWITCH_MAX_DELAY = 100 * Units.MILLISECONDS;
|
|
||||||
private static final double MIN_JUMP_DELAY = 300 * Units.MILLISECONDS;
|
|
||||||
|
|
||||||
// Horizontal and vertical max control speed when flying
|
|
||||||
private static final float FLYING_SPEED = Units.get("6 m/s");
|
|
||||||
|
|
||||||
// (0; 1], 1 is instant change, 0 is no control authority
|
|
||||||
private static final float FLYING_CONTROL_AUTHORITY = Units.get("2 1/s");
|
|
||||||
|
|
||||||
// Horizontal max control speed when walking
|
|
||||||
private static final float WALKING_SPEED = Units.get("4 m/s");
|
|
||||||
|
|
||||||
// Horizontal max control speed when sprinting
|
|
||||||
private static final float SPRINTING_SPEED = Units.get("6 m/s");
|
|
||||||
|
|
||||||
// (0; 1], 1 is instant change, 0 is no control authority
|
|
||||||
private static final float WALKING_CONTROL_AUTHORITY = Units.get("15 1/s");
|
|
||||||
|
|
||||||
// Vertical velocity instantly added to player when they jump
|
|
||||||
private static final float JUMP_VELOCITY = 5f * Units.METERS_PER_SECOND;
|
|
||||||
|
|
||||||
private boolean isFlying = true;
|
|
||||||
private boolean isSprinting = false;
|
|
||||||
|
|
||||||
private int movementForward = 0;
|
|
||||||
private int movementRight = 0;
|
|
||||||
private int movementUp = 0;
|
|
||||||
|
|
||||||
private double lastSpacePress = Double.NEGATIVE_INFINITY;
|
|
||||||
private double lastSprintPress = Double.NEGATIVE_INFINITY;
|
|
||||||
|
|
||||||
private int selectedBlock = 0;
|
|
||||||
private int selectedTile = 0;
|
|
||||||
private boolean isBlockSelected = true;
|
|
||||||
|
|
||||||
private LayerTestGUI debugLayer = null;
|
|
||||||
private Runnable updateCallback = null;
|
|
||||||
|
|
||||||
public static void resetInstance() {
|
|
||||||
instance = new TestPlayerControls();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void applyPlayerControls() {
|
|
||||||
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
EntityData player = getEntity();
|
|
||||||
|
|
||||||
final float speed, authority;
|
|
||||||
|
|
||||||
if (isFlying) {
|
|
||||||
speed = FLYING_SPEED;
|
|
||||||
authority = FLYING_CONTROL_AUTHORITY;
|
|
||||||
} else {
|
|
||||||
speed = isSprinting ? SPRINTING_SPEED : WALKING_SPEED;
|
|
||||||
authority = WALKING_CONTROL_AUTHORITY;
|
|
||||||
}
|
|
||||||
|
|
||||||
Mat3 movementTransform = getMovementTransform(player, null);
|
|
||||||
Vec3 desiredVelocity = new Vec3(movementForward, movementRight, 0);
|
|
||||||
if (movementForward != 0 && movementRight != 0) {
|
|
||||||
desiredVelocity.normalize();
|
|
||||||
}
|
|
||||||
desiredVelocity.z = movementUp;
|
|
||||||
movementTransform.mul_(desiredVelocity); // bug in jglm, .mul() and .mul_() are
|
|
||||||
// swapped
|
|
||||||
desiredVelocity.mul(speed);
|
|
||||||
|
|
||||||
Vec3 newVelocity = new Vec3()
|
|
||||||
.set(desiredVelocity)
|
|
||||||
.sub(player.getVelocity())
|
|
||||||
.mul((float) Math.exp(-authority * GraphicsInterface.getFrameLength()))
|
|
||||||
.negate()
|
|
||||||
.add(desiredVelocity);
|
|
||||||
|
|
||||||
if (!isFlying) {
|
|
||||||
Vec3 up = player.getUpVector();
|
|
||||||
Vec3 wantedVertical = VectorUtil.projectOnVector(player.getVelocity(), up, null);
|
|
||||||
VectorUtil.projectOnSurface(newVelocity, up).add(wantedVertical);
|
|
||||||
}
|
|
||||||
|
|
||||||
player.getVelocity().set(newVelocity);
|
|
||||||
|
|
||||||
// THIS IS TERRIBLE TEST
|
|
||||||
EntityData serverEntity = ServerState.getInstance().getWorld().getData()
|
|
||||||
.getEntity(TestContent.PLAYER_ENTITY_ID);
|
|
||||||
if (serverEntity != null) {
|
|
||||||
serverEntity.setPosition(player.getPosition());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private Mat3 getMovementTransform(EntityData player, Mat3 mat) {
|
|
||||||
if (mat == null) {
|
|
||||||
mat = new Mat3();
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec3 f = player.getForwardVector(null);
|
|
||||||
Vec3 u = player.getUpVector();
|
|
||||||
Vec3 s = u.cross_(f);
|
|
||||||
|
|
||||||
return mat.set(
|
|
||||||
+f.x, +f.y, +f.z,
|
|
||||||
-s.x, -s.y, -s.z,
|
|
||||||
+u.x, +u.y, +u.z
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleInput(Input input) {
|
|
||||||
InputEvent event = input.getEvent();
|
|
||||||
|
|
||||||
if (event instanceof KeyEvent) {
|
|
||||||
if (onKeyEvent((KeyEvent) event)) {
|
|
||||||
input.consume();
|
|
||||||
}
|
|
||||||
} else if (event instanceof CursorMoveEvent) {
|
|
||||||
onMouseMoved((CursorMoveEvent) event);
|
|
||||||
input.consume();
|
|
||||||
} else if (event instanceof WheelScrollEvent) {
|
|
||||||
onWheelScroll((WheelScrollEvent) event);
|
|
||||||
input.consume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean onKeyEvent(KeyEvent event) {
|
|
||||||
if (event.isRepeat())
|
|
||||||
return false;
|
|
||||||
int multiplier = event.isPress() ? 1 : -1;
|
|
||||||
switch (event.getKey()) {
|
|
||||||
case GLFW.GLFW_KEY_W:
|
|
||||||
movementForward += +1 * multiplier;
|
|
||||||
handleSprint(event);
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_KEY_S:
|
|
||||||
movementForward += -1 * multiplier;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_KEY_A:
|
|
||||||
movementRight += -1 * multiplier;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_KEY_D:
|
|
||||||
movementRight += +1 * multiplier;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_KEY_SPACE:
|
|
||||||
handleSpace(multiplier);
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_KEY_LEFT_SHIFT:
|
|
||||||
handleShift(multiplier);
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_KEY_ESCAPE:
|
|
||||||
if (!event.isPress())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
handleEscape();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GLFW.GLFW_KEY_F11:
|
|
||||||
if (!event.isPress())
|
|
||||||
return false;
|
|
||||||
GraphicsInterface.makeFullscreen(!GraphicsBackend.isFullscreen());
|
|
||||||
updateGUI();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GLFW.GLFW_KEY_F12:
|
|
||||||
if (!event.isPress())
|
|
||||||
return false;
|
|
||||||
GraphicsBackend.setVSyncEnabled(!GraphicsBackend.isVSyncEnabled());
|
|
||||||
updateGUI();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GLFW.GLFW_KEY_F3:
|
|
||||||
if (!event.isPress())
|
|
||||||
return false;
|
|
||||||
handleDebugLayerSwitch();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GLFW.GLFW_KEY_F5:
|
|
||||||
if (!event.isPress())
|
|
||||||
return false;
|
|
||||||
handleCameraMode();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GLFW.GLFW_KEY_L:
|
|
||||||
if (!event.isPress())
|
|
||||||
return false;
|
|
||||||
handleLanguageSwitch();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GLFW.GLFW_MOUSE_BUTTON_3:
|
|
||||||
if (!event.isPress())
|
|
||||||
return false;
|
|
||||||
switchPlacingMode();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleSpace(int multiplier) {
|
|
||||||
boolean isPressed = multiplier > 0;
|
|
||||||
|
|
||||||
double timeSinceLastSpacePress = GraphicsInterface.getTime() - lastSpacePress;
|
|
||||||
|
|
||||||
if (isPressed && timeSinceLastSpacePress < MODE_SWITCH_MAX_DELAY) {
|
|
||||||
isSprinting = false;
|
|
||||||
isFlying = !isFlying;
|
|
||||||
updateGUI();
|
|
||||||
movementUp = +1;
|
|
||||||
} else {
|
|
||||||
if (isFlying) {
|
|
||||||
movementUp += +1 * multiplier;
|
|
||||||
} else {
|
|
||||||
if (isPressed && timeSinceLastSpacePress > MIN_JUMP_DELAY) {
|
|
||||||
jump();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastSpacePress = GraphicsInterface.getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleSprint(KeyEvent event) {
|
|
||||||
|
|
||||||
double timeSinceLastSpacePress = GraphicsInterface.getTime() - lastSprintPress;
|
|
||||||
|
|
||||||
if (event.isPress() && timeSinceLastSpacePress < MODE_SPRINT_SWITCH_MAX_DELAY && !isFlying) {
|
|
||||||
isSprinting = !isSprinting;
|
|
||||||
updateGUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
lastSprintPress = GraphicsInterface.getTime();
|
|
||||||
|
|
||||||
if (isSprinting && event.isRelease()) {
|
|
||||||
isSprinting = false;
|
|
||||||
updateGUI();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void jump() {
|
|
||||||
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec3 up = getEntity().getUpVector();
|
|
||||||
|
|
||||||
getEntity().getVelocity().add(
|
|
||||||
up.x * JUMP_VELOCITY,
|
|
||||||
up.y * JUMP_VELOCITY,
|
|
||||||
up.z * JUMP_VELOCITY
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleShift(int multiplier) {
|
|
||||||
if (isFlying) {
|
|
||||||
movementUp += -1 * multiplier;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleEscape() {
|
|
||||||
movementForward = 0;
|
|
||||||
movementRight = 0;
|
|
||||||
movementUp = 0;
|
|
||||||
GUI.addTopLayer(new LayerButtonTest());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleDebugLayerSwitch() {
|
|
||||||
if (debugLayer == null) {
|
|
||||||
this.debugLayer = new LayerTestGUI();
|
|
||||||
this.updateCallback = debugLayer.getUpdateCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GUI.getLayers().contains(debugLayer)) {
|
|
||||||
GUI.removeLayer(debugLayer);
|
|
||||||
} else {
|
|
||||||
GUI.addTopLayer(debugLayer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleCameraMode() {
|
|
||||||
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ClientState.getInstance().getCamera().hasAnchor()) {
|
|
||||||
ClientState.getInstance().getCamera().selectNextMode();
|
|
||||||
updateGUI();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleLanguageSwitch() {
|
|
||||||
Localizer localizer = Localizer.getInstance();
|
|
||||||
if (localizer.getLanguage().equals("ru-RU")) {
|
|
||||||
localizer.setLanguage("en-US");
|
|
||||||
} else {
|
|
||||||
localizer.setLanguage("ru-RU");
|
|
||||||
}
|
|
||||||
|
|
||||||
updateGUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onMouseMoved(CursorMoveEvent event) {
|
|
||||||
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final double yawScale = -0.002f;
|
|
||||||
final double pitchScale = -yawScale;
|
|
||||||
final double pitchExtremum = Math.PI/2 * 0.95f;
|
|
||||||
|
|
||||||
double yawChange = event.getChangeX() * yawScale;
|
|
||||||
double pitchChange = event.getChangeY() * pitchScale;
|
|
||||||
|
|
||||||
EntityData player = getEntity();
|
|
||||||
|
|
||||||
double startPitch = player.getPitch();
|
|
||||||
double endPitch = startPitch + pitchChange;
|
|
||||||
endPitch = Glm.clamp(endPitch, -pitchExtremum, +pitchExtremum);
|
|
||||||
pitchChange = endPitch - startPitch;
|
|
||||||
|
|
||||||
Mat4 mat = Matrices.grab4();
|
|
||||||
Vec3 lookingAt = Vectors.grab3();
|
|
||||||
Vec3 rightVector = Vectors.grab3();
|
|
||||||
|
|
||||||
rightVector.set(player.getLookingAt()).cross(player.getUpVector()).normalize();
|
|
||||||
|
|
||||||
mat.identity()
|
|
||||||
.rotate((float) yawChange, player.getUpVector())
|
|
||||||
.rotate((float) pitchChange, rightVector);
|
|
||||||
|
|
||||||
VectorUtil.applyMat4(player.getLookingAt(), mat, lookingAt);
|
|
||||||
player.setLookingAt(lookingAt);
|
|
||||||
|
|
||||||
Vectors.release(rightVector);
|
|
||||||
Vectors.release(lookingAt);
|
|
||||||
Matrices.release(mat);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onWheelScroll(WheelScrollEvent event) {
|
|
||||||
if (event.hasHorizontalMovement()) {
|
|
||||||
switchPlacingMode();
|
|
||||||
}
|
|
||||||
if (InputTracker.isKeyPressed(GLFW.GLFW_KEY_LEFT_CONTROL)) {
|
|
||||||
switchPlacingMode();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isBlockSelected) {
|
|
||||||
selectedBlock += event.isUp() ? +1 : -1;
|
|
||||||
|
|
||||||
int size = TestContent.PLACEABLE_BLOCKS.size();
|
|
||||||
|
|
||||||
if (selectedBlock < 0) {
|
|
||||||
selectedBlock = size - 1;
|
|
||||||
} else if (selectedBlock >= size) {
|
|
||||||
selectedBlock = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
selectedTile += event.isUp() ? +1 : -1;
|
|
||||||
|
|
||||||
int size = TestContent.PLACEABLE_TILES.size();
|
|
||||||
|
|
||||||
if (selectedTile < 0) {
|
|
||||||
selectedTile = size - 1;
|
|
||||||
} else if (selectedTile >= size) {
|
|
||||||
selectedTile = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateGUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void switchPlacingMode() {
|
|
||||||
isBlockSelected = !isBlockSelected;
|
|
||||||
updateGUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
public EntityData getEntity() {
|
|
||||||
return getPlayer().getEntity();
|
|
||||||
}
|
|
||||||
|
|
||||||
public LocalPlayer getPlayer() {
|
|
||||||
return ClientState.getInstance().getLocalPlayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateGUI() {
|
|
||||||
if (this.updateCallback != null) {
|
|
||||||
this.updateCallback.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFlying() {
|
|
||||||
return isFlying;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSprinting() {
|
|
||||||
return isSprinting;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BlockData getSelectedBlock() {
|
|
||||||
return TestContent.PLACEABLE_BLOCKS.get(selectedBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TileData getSelectedTile() {
|
|
||||||
return TestContent.PLACEABLE_TILES.get(selectedTile);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isBlockSelected() {
|
|
||||||
return isBlockSelected;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.test.controls;
|
||||||
|
|
||||||
|
import glm.Glm;
|
||||||
|
import glm.mat._4.Mat4;
|
||||||
|
import glm.vec._3.Vec3;
|
||||||
|
import ru.windcorp.progressia.client.ClientState;
|
||||||
|
import ru.windcorp.progressia.client.comms.controls.ControlTriggerRegistry;
|
||||||
|
import ru.windcorp.progressia.client.comms.controls.ControlTriggers;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||||
|
import ru.windcorp.progressia.client.graphics.world.Camera;
|
||||||
|
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
|
||||||
|
import ru.windcorp.progressia.common.util.Matrices;
|
||||||
|
import ru.windcorp.progressia.common.util.VectorUtil;
|
||||||
|
import ru.windcorp.progressia.common.util.Vectors;
|
||||||
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
|
|
||||||
|
public class CameraControls {
|
||||||
|
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
// No instance fields; futureproofing
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerControls() {
|
||||||
|
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
|
||||||
|
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.localOf(
|
||||||
|
"Test:TurnCameraWithCursor",
|
||||||
|
CursorMoveEvent.class,
|
||||||
|
this::turnCameraWithCursor
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.localOf(
|
||||||
|
"Test:SwitchCameraMode",
|
||||||
|
KeyEvent.class,
|
||||||
|
this::switchCameraMode,
|
||||||
|
new KeyMatcher("F5")::matches
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
NoclipCamera.register();
|
||||||
|
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.localOf(
|
||||||
|
"Test:ToggleNoclip",
|
||||||
|
KeyEvent.class,
|
||||||
|
NoclipCamera::toggleNoclip,
|
||||||
|
new KeyMatcher("V")::matches
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void turnCameraWithCursor(CursorMoveEvent event) {
|
||||||
|
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Camera.Anchor cameraAnchor = ClientState.getInstance().getCamera().getAnchor();
|
||||||
|
if (!(cameraAnchor instanceof EntityAnchor)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityData player = ((EntityAnchor) cameraAnchor).getEntity();
|
||||||
|
|
||||||
|
final double yawScale = -0.002f;
|
||||||
|
final double pitchScale = -yawScale;
|
||||||
|
final double pitchExtremum = Math.PI / 2 * 0.995f;
|
||||||
|
|
||||||
|
double yawChange = event.getChangeX() * yawScale;
|
||||||
|
double pitchChange = event.getChangeY() * pitchScale;
|
||||||
|
|
||||||
|
double startPitch = player.getPitch();
|
||||||
|
double endPitch = startPitch + pitchChange;
|
||||||
|
endPitch = Glm.clamp(endPitch, -pitchExtremum, +pitchExtremum);
|
||||||
|
pitchChange = endPitch - startPitch;
|
||||||
|
|
||||||
|
Mat4 mat = Matrices.grab4();
|
||||||
|
Vec3 lookingAt = Vectors.grab3();
|
||||||
|
Vec3 rightVector = Vectors.grab3();
|
||||||
|
|
||||||
|
rightVector.set(player.getLookingAt()).cross(player.getUpVector()).normalize();
|
||||||
|
|
||||||
|
mat.identity()
|
||||||
|
.rotate((float) yawChange, player.getUpVector())
|
||||||
|
.rotate((float) pitchChange, rightVector);
|
||||||
|
|
||||||
|
VectorUtil.applyMat4(player.getLookingAt(), mat, lookingAt);
|
||||||
|
player.setLookingAt(lookingAt);
|
||||||
|
|
||||||
|
Vectors.release(rightVector);
|
||||||
|
Vectors.release(lookingAt);
|
||||||
|
Matrices.release(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void switchCameraMode() {
|
||||||
|
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ClientState.getInstance().getCamera().hasAnchor()) {
|
||||||
|
ClientState.getInstance().getCamera().selectNextMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.test;
|
package ru.windcorp.progressia.test.controls;
|
||||||
|
|
||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.common.comms.controls.ControlData;
|
import ru.windcorp.progressia.common.comms.controls.ControlData;
|
@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.test;
|
package ru.windcorp.progressia.test.controls;
|
||||||
|
|
||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.common.comms.controls.ControlData;
|
import ru.windcorp.progressia.common.comms.controls.ControlData;
|
@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.test;
|
package ru.windcorp.progressia.test.controls;
|
||||||
|
|
||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.common.comms.controls.ControlData;
|
import ru.windcorp.progressia.common.comms.controls.ControlData;
|
@ -0,0 +1,264 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.test.controls;
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import glm.vec._3.i.Vec3i;
|
||||||
|
import ru.windcorp.progressia.client.ClientState;
|
||||||
|
import ru.windcorp.progressia.client.audio.Sound;
|
||||||
|
import ru.windcorp.progressia.client.comms.controls.ControlTriggerRegistry;
|
||||||
|
import ru.windcorp.progressia.client.comms.controls.ControlTriggers;
|
||||||
|
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.WheelScrollEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.world.Selection;
|
||||||
|
import ru.windcorp.progressia.common.comms.controls.ControlData;
|
||||||
|
import ru.windcorp.progressia.common.comms.controls.ControlDataRegistry;
|
||||||
|
import ru.windcorp.progressia.common.comms.controls.PacketControl;
|
||||||
|
import ru.windcorp.progressia.common.world.block.BlockData;
|
||||||
|
import ru.windcorp.progressia.common.world.block.BlockDataRegistry;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
import ru.windcorp.progressia.common.world.tile.TileData;
|
||||||
|
import ru.windcorp.progressia.server.Server;
|
||||||
|
import ru.windcorp.progressia.server.comms.controls.ControlLogic;
|
||||||
|
import ru.windcorp.progressia.server.comms.controls.ControlLogicRegistry;
|
||||||
|
import ru.windcorp.progressia.server.world.context.ServerBlockContext;
|
||||||
|
import ru.windcorp.progressia.server.world.context.ServerTileContext;
|
||||||
|
import ru.windcorp.progressia.server.world.context.ServerTileStackContext;
|
||||||
|
import ru.windcorp.progressia.server.world.tile.TileLogic;
|
||||||
|
import ru.windcorp.progressia.server.world.tile.TileLogicRegistry;
|
||||||
|
import ru.windcorp.progressia.test.TestContent;
|
||||||
|
|
||||||
|
public class InteractionControls {
|
||||||
|
|
||||||
|
private int selectedBlock;
|
||||||
|
private int selectedTile;
|
||||||
|
private boolean isBlockSelected;
|
||||||
|
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
selectedBlock = 0;
|
||||||
|
selectedTile = 0;
|
||||||
|
isBlockSelected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerControls() {
|
||||||
|
ControlDataRegistry data = ControlDataRegistry.getInstance();
|
||||||
|
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
|
||||||
|
ControlLogicRegistry logic = ControlLogicRegistry.getInstance();
|
||||||
|
|
||||||
|
data.register("Test:BreakBlock", ControlBreakBlockData::new);
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.of(
|
||||||
|
"Test:BreakBlock",
|
||||||
|
KeyEvent.class,
|
||||||
|
this::onBlockBreakTrigger,
|
||||||
|
KeyMatcher.LMB::matches,
|
||||||
|
i -> isAnythingSelected()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
logic.register(ControlLogic.of("Test:BreakBlock", InteractionControls::onBlockBreakReceived));
|
||||||
|
|
||||||
|
data.register("Test:PlaceBlock", ControlPlaceBlockData::new);
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.of(
|
||||||
|
"Test:PlaceBlock",
|
||||||
|
KeyEvent.class,
|
||||||
|
this::onBlockPlaceTrigger,
|
||||||
|
KeyMatcher.RMB::matches,
|
||||||
|
i -> isAnythingSelected() && isBlockSelected()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
logic.register(ControlLogic.of("Test:PlaceBlock", InteractionControls::onBlockPlaceReceived));
|
||||||
|
|
||||||
|
data.register("Test:PlaceTile", ControlPlaceTileData::new);
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.of(
|
||||||
|
"Test:PlaceTile",
|
||||||
|
KeyEvent.class,
|
||||||
|
this::onTilePlaceTrigger,
|
||||||
|
KeyMatcher.RMB::matches,
|
||||||
|
i -> isAnythingSelected() && !isBlockSelected()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
logic.register(ControlLogic.of("Test:PlaceTile", InteractionControls::onTilePlaceReceived));
|
||||||
|
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.localOf(
|
||||||
|
"Test:SwitchPlacingModeMMB",
|
||||||
|
KeyEvent.class,
|
||||||
|
this::switchPlacingMode,
|
||||||
|
KeyMatcher.MMB::matches
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.localOf(
|
||||||
|
"Test:SwitchPlacingModeWheel",
|
||||||
|
WheelScrollEvent.class,
|
||||||
|
this::switchPlacingMode,
|
||||||
|
e -> e.hasHorizontalMovement() || InputTracker.isKeyPressed(GLFW.GLFW_KEY_LEFT_CONTROL)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.localOf(
|
||||||
|
"Test:SelectNextBlockOrTile",
|
||||||
|
WheelScrollEvent.class,
|
||||||
|
this::selectNextBlockOrTile,
|
||||||
|
e -> !e.hasHorizontalMovement() && !InputTracker.isKeyPressed(GLFW.GLFW_KEY_LEFT_CONTROL)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Selection getSelection() {
|
||||||
|
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
|
||||||
|
if (client == null || !client.isReady())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return client.getLocalPlayer().getSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAnythingSelected() {
|
||||||
|
ru.windcorp.progressia.client.Client client = ClientState.getInstance();
|
||||||
|
if (client == null || !client.isReady())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return client.getLocalPlayer().getSelection().exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onBlockBreakTrigger(ControlData control) {
|
||||||
|
((ControlBreakBlockData) control).setBlockInWorld(getSelection().getBlock());
|
||||||
|
Sound sfx = new Sound("Progressia:BlockDestroy");
|
||||||
|
sfx.setPosition(getSelection().getPoint());
|
||||||
|
sfx.setPitch((float) (Math.random() + 1 * 0.5));
|
||||||
|
sfx.play(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onBlockBreakReceived(
|
||||||
|
Server server,
|
||||||
|
PacketControl packet,
|
||||||
|
ru.windcorp.progressia.server.comms.Client client
|
||||||
|
) {
|
||||||
|
Vec3i blockInWorld = ((ControlBreakBlockData) packet.getControl()).getBlockInWorld();
|
||||||
|
server.createAbsoluteContext().setBlock(blockInWorld, BlockDataRegistry.getInstance().get("Test:Air"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onBlockPlaceTrigger(ControlData control) {
|
||||||
|
((ControlPlaceBlockData) control).set(
|
||||||
|
getSelectedBlock(),
|
||||||
|
getSelection().getBlock().add_(getSelection().getSurface().getVector())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onBlockPlaceReceived(
|
||||||
|
Server server,
|
||||||
|
PacketControl packet,
|
||||||
|
ru.windcorp.progressia.server.comms.Client client
|
||||||
|
) {
|
||||||
|
ControlPlaceBlockData controlData = ((ControlPlaceBlockData) packet.getControl());
|
||||||
|
BlockData block = controlData.getBlock();
|
||||||
|
Vec3i blockInWorld = controlData.getBlockInWorld();
|
||||||
|
if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null)
|
||||||
|
return;
|
||||||
|
server.createAbsoluteContext().setBlock(blockInWorld, block);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onTilePlaceTrigger(ControlData control) {
|
||||||
|
((ControlPlaceTileData) control).set(
|
||||||
|
getSelectedTile(),
|
||||||
|
getSelection().getBlock(),
|
||||||
|
getSelection().getSurface()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onTilePlaceReceived(
|
||||||
|
Server server,
|
||||||
|
PacketControl packet,
|
||||||
|
ru.windcorp.progressia.server.comms.Client client
|
||||||
|
) {
|
||||||
|
ControlPlaceTileData controlData = ((ControlPlaceTileData) packet.getControl());
|
||||||
|
TileData tile = controlData.getTile();
|
||||||
|
Vec3i blockInWorld = controlData.getBlockInWorld();
|
||||||
|
AbsFace face = controlData.getFace();
|
||||||
|
|
||||||
|
if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (server.getWorld().getData().getTiles(blockInWorld, face).isFull()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerBlockContext context = server.createContext(blockInWorld);
|
||||||
|
ServerTileStackContext tsContext = context.push(context.toContext(face));
|
||||||
|
ServerTileContext tileContext = tsContext.push(tsContext.getTileCount());
|
||||||
|
|
||||||
|
TileLogic logic = TileLogicRegistry.getInstance().get(tile.getId());
|
||||||
|
if (!logic.canOccupyFace(tileContext)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tileContext.addTile(tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void switchPlacingMode() {
|
||||||
|
isBlockSelected = !isBlockSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void selectNextBlockOrTile(WheelScrollEvent event) {
|
||||||
|
if (isBlockSelected) {
|
||||||
|
selectedBlock += event.isUp() ? +1 : -1;
|
||||||
|
|
||||||
|
int size = TestContent.PLACEABLE_BLOCKS.size();
|
||||||
|
|
||||||
|
if (selectedBlock < 0) {
|
||||||
|
selectedBlock = size - 1;
|
||||||
|
} else if (selectedBlock >= size) {
|
||||||
|
selectedBlock = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedTile += event.isUp() ? +1 : -1;
|
||||||
|
|
||||||
|
int size = TestContent.PLACEABLE_TILES.size();
|
||||||
|
|
||||||
|
if (selectedTile < 0) {
|
||||||
|
selectedTile = size - 1;
|
||||||
|
} else if (selectedTile >= size) {
|
||||||
|
selectedTile = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockData getSelectedBlock() {
|
||||||
|
return TestContent.PLACEABLE_BLOCKS.get(selectedBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TileData getSelectedTile() {
|
||||||
|
return TestContent.PLACEABLE_TILES.get(selectedTile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBlockSelected() {
|
||||||
|
return isBlockSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,316 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.test.controls;
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import glm.mat._3.Mat3;
|
||||||
|
import glm.vec._3.Vec3;
|
||||||
|
import ru.windcorp.progressia.client.ClientState;
|
||||||
|
import ru.windcorp.progressia.client.comms.controls.ControlTriggerRegistry;
|
||||||
|
import ru.windcorp.progressia.client.comms.controls.ControlTriggers;
|
||||||
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||||
|
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||||
|
import ru.windcorp.progressia.client.graphics.world.Camera;
|
||||||
|
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
|
||||||
|
import ru.windcorp.progressia.common.Units;
|
||||||
|
import ru.windcorp.progressia.common.util.VectorUtil;
|
||||||
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
|
import ru.windcorp.progressia.server.ServerState;
|
||||||
|
import ru.windcorp.progressia.test.TestContent;
|
||||||
|
|
||||||
|
public class MovementControls {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max delay between space presses that can toggle flying
|
||||||
|
*/
|
||||||
|
private static final double FLYING_SWITCH_MAX_DELAY = Units.get("300 ms");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max delay between W presses that can toggle sprinting
|
||||||
|
*/
|
||||||
|
private static final double SPRINTING_SWITCH_MAX_DELAY = Units.get("300 ms");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Min delay between jumps
|
||||||
|
*/
|
||||||
|
private static final double JUMP_MIN_DELAY = Units.get("300 ms");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Horizontal and vertical max control speed when flying
|
||||||
|
*/
|
||||||
|
private static final float FLYING_SPEED = Units.get("6 m/s");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (0; 1], 1 is instant change, 0 is no control authority
|
||||||
|
*/
|
||||||
|
private static final float FLYING_CONTROL_AUTHORITY = Units.get("2 1/s");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Horizontal max control speed when walking
|
||||||
|
*/
|
||||||
|
private static final float WALKING_SPEED = Units.get("4 m/s");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Horizontal max control speed when sprinting
|
||||||
|
*/
|
||||||
|
private static final float SPRINTING_SPEED = Units.get("6 m/s");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (0; 1], 1 is instant change, 0 is no control authority
|
||||||
|
*/
|
||||||
|
private static final float WALKING_CONTROL_AUTHORITY = Units.get("15 1/s");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vertical velocity instantly added to player when they jump
|
||||||
|
*/
|
||||||
|
private static final float JUMP_VELOCITY = Units.get("5 m/s");
|
||||||
|
|
||||||
|
private boolean isFlying;
|
||||||
|
private boolean isSprinting;
|
||||||
|
|
||||||
|
private double lastSpacePress;
|
||||||
|
private double lastSprintPress;
|
||||||
|
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyPlayerControls() {
|
||||||
|
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Camera.Anchor cameraAnchor = ClientState.getInstance().getCamera().getAnchor();
|
||||||
|
if (!(cameraAnchor instanceof EntityAnchor)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityData player = ((EntityAnchor) cameraAnchor).getEntity();
|
||||||
|
|
||||||
|
boolean isFlying = this.isFlying || player.getId().equals("Test:NoclipCamera");
|
||||||
|
boolean isSprinting = this.isSprinting || player.getId().equals("Test:NoclipCamera");
|
||||||
|
|
||||||
|
final float speed, authority;
|
||||||
|
|
||||||
|
if (isFlying) {
|
||||||
|
speed = FLYING_SPEED;
|
||||||
|
authority = FLYING_CONTROL_AUTHORITY;
|
||||||
|
} else {
|
||||||
|
speed = isSprinting ? SPRINTING_SPEED : WALKING_SPEED;
|
||||||
|
authority = WALKING_CONTROL_AUTHORITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mat3 movementTransform = getMovementTransform(player, null);
|
||||||
|
Vec3 desiredVelocity = getDesiredVelocity(movementTransform, speed, isFlying);
|
||||||
|
Vec3 newVelocity = getNewVelocity(
|
||||||
|
desiredVelocity,
|
||||||
|
player.getVelocity(),
|
||||||
|
authority,
|
||||||
|
player.getUpVector(),
|
||||||
|
isFlying
|
||||||
|
);
|
||||||
|
player.getVelocity().set(newVelocity);
|
||||||
|
|
||||||
|
tmp_syncServerEntity();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tmp_syncServerEntity() {
|
||||||
|
// THIS IS TERRIBLE TEST
|
||||||
|
EntityData serverEntity = ServerState.getInstance().getWorld().getData()
|
||||||
|
.getEntity(TestContent.PLAYER_ENTITY_ID);
|
||||||
|
if (serverEntity != null) {
|
||||||
|
EntityData clientEntity = ClientState.getInstance().getLocalPlayer().getEntity();
|
||||||
|
|
||||||
|
clientEntity.copy(serverEntity);
|
||||||
|
serverEntity.setLookingAt(clientEntity.getLookingAt());
|
||||||
|
serverEntity.setUpVector(clientEntity.getUpVector());
|
||||||
|
serverEntity.setPosition(clientEntity.getPosition());
|
||||||
|
serverEntity.setVelocity(clientEntity.getVelocity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mat3 getMovementTransform(EntityData player, Mat3 mat) {
|
||||||
|
if (mat == null) {
|
||||||
|
mat = new Mat3();
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 f = player.getForwardVector(null);
|
||||||
|
Vec3 u = player.getUpVector();
|
||||||
|
Vec3 s = f.cross_(u);
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
return mat.set(
|
||||||
|
f.x, f.y, f.z,
|
||||||
|
s.x, s.y, s.z,
|
||||||
|
u.x, u.y, u.z
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vec3 getDesiredVelocity(Mat3 movementTransform, float speed, boolean isFlying) {
|
||||||
|
int forward = 0;
|
||||||
|
int right = 0;
|
||||||
|
int up = 0;
|
||||||
|
|
||||||
|
forward += InputTracker.isKeyPressed(GLFW.GLFW_KEY_W) ? 1 : 0;
|
||||||
|
forward -= InputTracker.isKeyPressed(GLFW.GLFW_KEY_S) ? 1 : 0;
|
||||||
|
|
||||||
|
right += InputTracker.isKeyPressed(GLFW.GLFW_KEY_D) ? 1 : 0;
|
||||||
|
right -= InputTracker.isKeyPressed(GLFW.GLFW_KEY_A) ? 1 : 0;
|
||||||
|
|
||||||
|
if (isFlying) {
|
||||||
|
up += InputTracker.isKeyPressed(GLFW.GLFW_KEY_SPACE) ? 1 : 0;
|
||||||
|
up -= InputTracker.isKeyPressed(GLFW.GLFW_KEY_LEFT_SHIFT) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 desiredVelocity = new Vec3(forward, right, 0);
|
||||||
|
if (forward != 0 && right != 0) {
|
||||||
|
desiredVelocity.normalize();
|
||||||
|
}
|
||||||
|
desiredVelocity.z = up;
|
||||||
|
|
||||||
|
// bug in jglm, .mul() and .mul_() are swapped
|
||||||
|
movementTransform.mul_(desiredVelocity);
|
||||||
|
desiredVelocity.mul(speed);
|
||||||
|
|
||||||
|
return desiredVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vec3 getNewVelocity(Vec3 desiredVelocity, Vec3 oldVelocity, float authority, Vec3 up, boolean isFlying) {
|
||||||
|
|
||||||
|
// newVelocity = oldVelocity + small change toward desiredVelocity
|
||||||
|
Vec3 newVelocity = new Vec3()
|
||||||
|
.set(desiredVelocity)
|
||||||
|
.sub(oldVelocity)
|
||||||
|
.mul((float) Math.exp(-authority * GraphicsInterface.getFrameLength()))
|
||||||
|
.negate()
|
||||||
|
.add(desiredVelocity);
|
||||||
|
|
||||||
|
// If we aren't flying, don't change vertical component
|
||||||
|
if (!isFlying) {
|
||||||
|
Vec3 wantedVertical = VectorUtil.projectOnVector(oldVelocity, up, null);
|
||||||
|
VectorUtil.projectOnSurface(newVelocity, up);
|
||||||
|
newVelocity.add(wantedVertical);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
isFlying = true;
|
||||||
|
isSprinting = false;
|
||||||
|
lastSpacePress = Double.NEGATIVE_INFINITY;
|
||||||
|
lastSprintPress = Double.NEGATIVE_INFINITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerControls() {
|
||||||
|
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
|
||||||
|
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.localOf(
|
||||||
|
"Test:JumpOrToggleFlight",
|
||||||
|
KeyEvent.class,
|
||||||
|
this::handleSpacePress,
|
||||||
|
new KeyMatcher("Space")::matches
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.localOf(
|
||||||
|
"Test:ToggleSprint",
|
||||||
|
KeyEvent.class,
|
||||||
|
this::toggleSprint,
|
||||||
|
|
||||||
|
new KeyMatcher("W")::matches,
|
||||||
|
e -> !isFlying
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.localOf(
|
||||||
|
"Test:DisableSprint",
|
||||||
|
KeyEvent.class,
|
||||||
|
this::disableSprint,
|
||||||
|
|
||||||
|
new KeyMatcher("W")::matchesIgnoringAction,
|
||||||
|
KeyEvent::isRelease
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSpacePress(KeyEvent e) {
|
||||||
|
if (
|
||||||
|
ClientState.getInstance().getCamera().getAnchor() instanceof EntityAnchor
|
||||||
|
&& ((EntityAnchor) ClientState.getInstance().getCamera().getAnchor()).getEntity().getId()
|
||||||
|
.equals("Test:NoclipCamera")
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double timeSinceLastSpacePress = e.getTime() - lastSpacePress;
|
||||||
|
|
||||||
|
if (timeSinceLastSpacePress < FLYING_SWITCH_MAX_DELAY) {
|
||||||
|
isSprinting = false;
|
||||||
|
isFlying = !isFlying;
|
||||||
|
} else if (!isFlying && timeSinceLastSpacePress >= JUMP_MIN_DELAY) {
|
||||||
|
jump();
|
||||||
|
}
|
||||||
|
|
||||||
|
lastSpacePress = e.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void jump() {
|
||||||
|
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityData player = ClientState.getInstance().getLocalPlayer().getEntity();
|
||||||
|
assert player != null;
|
||||||
|
|
||||||
|
Vec3 up = player.getUpVector();
|
||||||
|
|
||||||
|
player.getVelocity().add(
|
||||||
|
up.x * JUMP_VELOCITY,
|
||||||
|
up.y * JUMP_VELOCITY,
|
||||||
|
up.z * JUMP_VELOCITY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleSprint(KeyEvent e) {
|
||||||
|
if (e.getTime() - lastSprintPress < SPRINTING_SWITCH_MAX_DELAY) {
|
||||||
|
isSprinting = !isSprinting;
|
||||||
|
}
|
||||||
|
lastSprintPress = e.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disableSprint(KeyEvent e) {
|
||||||
|
isSprinting = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFlying() {
|
||||||
|
return isFlying;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSprinting() {
|
||||||
|
return isSprinting;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.test.controls;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import glm.vec._3.Vec3;
|
||||||
|
import ru.windcorp.progressia.client.ClientState;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||||
|
import ru.windcorp.progressia.client.graphics.world.Camera;
|
||||||
|
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
|
||||||
|
import ru.windcorp.progressia.client.world.WorldRender;
|
||||||
|
import ru.windcorp.progressia.client.world.entity.EntityRender;
|
||||||
|
import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry;
|
||||||
|
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
|
||||||
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
|
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
|
||||||
|
|
||||||
|
public class NoclipCamera {
|
||||||
|
|
||||||
|
private static class NoclipEntityRender extends EntityRender {
|
||||||
|
|
||||||
|
public NoclipEntityRender() {
|
||||||
|
super("Test:NoclipCamera");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EntityRenderable createRenderable(EntityData entity) {
|
||||||
|
return new NoclipEntityRenderable(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class NoclipEntityRenderable extends EntityRenderable {
|
||||||
|
|
||||||
|
public NoclipEntityRenderable(EntityData data) {
|
||||||
|
super(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doRender(ShapeRenderHelper renderer) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void register() {
|
||||||
|
EntityDataRegistry.getInstance().register("Test:NoclipCamera");
|
||||||
|
EntityRenderRegistry.getInstance().register(new NoclipEntityRender());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void toggleNoclip() {
|
||||||
|
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Camera camera = ClientState.getInstance().getCamera();
|
||||||
|
WorldRender world = ClientState.getInstance().getWorld();
|
||||||
|
EntityData player = ClientState.getInstance().getLocalPlayer().getEntity();
|
||||||
|
|
||||||
|
List<EntityData> existingCameras = world.getData().getEntities().stream().filter(e -> e.getId().equals("Test:NoclipCamera")).collect(Collectors.toList());
|
||||||
|
if (!existingCameras.isEmpty()) {
|
||||||
|
existingCameras.forEach(world.getData()::removeEntity);
|
||||||
|
camera.setAnchor(new EntityAnchor(world.getEntityRenderable(player)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityData noclipCamera = EntityDataRegistry.getInstance().create("Test:NoclipCamera");
|
||||||
|
|
||||||
|
noclipCamera.setLookingAt(player.getLookingAt());
|
||||||
|
noclipCamera.setUpVector(player.getUpVector());
|
||||||
|
noclipCamera.setPosition(player.getPosition());
|
||||||
|
noclipCamera.setVelocity(player.getVelocity());
|
||||||
|
noclipCamera.setEntityId(new Random().nextLong());
|
||||||
|
|
||||||
|
player.setVelocity(new Vec3(0));
|
||||||
|
|
||||||
|
world.getData().addEntity(noclipCamera);
|
||||||
|
camera.setAnchor(new EntityAnchor(world.getEntityRenderable(noclipCamera)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,207 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.windcorp.progressia.test.controls;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.ClientState;
|
||||||
|
import ru.windcorp.progressia.client.comms.controls.ControlTriggerRegistry;
|
||||||
|
import ru.windcorp.progressia.client.comms.controls.ControlTriggers;
|
||||||
|
import ru.windcorp.progressia.client.graphics.GUI;
|
||||||
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
|
||||||
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||||
|
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
|
||||||
|
import ru.windcorp.progressia.client.localization.Localizer;
|
||||||
|
import ru.windcorp.progressia.common.world.block.BlockData;
|
||||||
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
|
import ru.windcorp.progressia.common.world.tile.TileData;
|
||||||
|
import ru.windcorp.progressia.test.LayerButtonTest;
|
||||||
|
import ru.windcorp.progressia.test.LayerDebug;
|
||||||
|
import ru.windcorp.progressia.test.TestMusicPlayer;
|
||||||
|
|
||||||
|
public class TestPlayerControls {
|
||||||
|
|
||||||
|
private static final TestPlayerControls INSTANCE = new TestPlayerControls();
|
||||||
|
|
||||||
|
public static TestPlayerControls getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final MovementControls movementControls = new MovementControls();
|
||||||
|
private final CameraControls cameraControls = new CameraControls();
|
||||||
|
private final InteractionControls interactionControls = new InteractionControls();
|
||||||
|
|
||||||
|
private LayerDebug debugLayer = null;
|
||||||
|
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void resetInstance() {
|
||||||
|
INSTANCE.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset() {
|
||||||
|
movementControls.reset();
|
||||||
|
cameraControls.reset();
|
||||||
|
interactionControls.reset();
|
||||||
|
|
||||||
|
debugLayer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyPlayerControls() {
|
||||||
|
movementControls.applyPlayerControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerControls() {
|
||||||
|
movementControls.registerControls();
|
||||||
|
cameraControls.registerControls();
|
||||||
|
interactionControls.registerControls();
|
||||||
|
|
||||||
|
ControlTriggerRegistry triggers = ControlTriggerRegistry.getInstance();
|
||||||
|
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.localOf(
|
||||||
|
"Test:PauseGame",
|
||||||
|
KeyEvent.class,
|
||||||
|
this::pauseGame,
|
||||||
|
new KeyMatcher("Escape")::matches
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.localOf(
|
||||||
|
"Test:ToggleFullscreen",
|
||||||
|
KeyEvent.class,
|
||||||
|
this::toggleFullscreen,
|
||||||
|
new KeyMatcher("F11")::matches
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.localOf(
|
||||||
|
"Test:ToggleVSync",
|
||||||
|
KeyEvent.class,
|
||||||
|
this::toggleVSync,
|
||||||
|
new KeyMatcher("F12")::matches
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.localOf(
|
||||||
|
"Test:ToggleDebugLayer",
|
||||||
|
KeyEvent.class,
|
||||||
|
this::toggleDebugLayer,
|
||||||
|
new KeyMatcher("F3")::matches
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.localOf(
|
||||||
|
"Test:SwitchLanguage",
|
||||||
|
KeyEvent.class,
|
||||||
|
this::switchLanguage,
|
||||||
|
new KeyMatcher("L")::matches
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
triggers.register(
|
||||||
|
ControlTriggers.localOf(
|
||||||
|
"Test:StartNextMusic",
|
||||||
|
KeyEvent.class,
|
||||||
|
TestMusicPlayer::startNextNow,
|
||||||
|
new KeyMatcher("M")::matches
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pauseGame() {
|
||||||
|
GUI.addTopLayer(new LayerButtonTest());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleFullscreen() {
|
||||||
|
GraphicsInterface.makeFullscreen(!GraphicsBackend.isFullscreen());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleVSync() {
|
||||||
|
GraphicsBackend.setVSyncEnabled(!GraphicsBackend.isVSyncEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleDebugLayer() {
|
||||||
|
if (debugLayer == null) {
|
||||||
|
this.debugLayer = new LayerDebug();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUI.getLayers().contains(debugLayer)) {
|
||||||
|
GUI.removeLayer(debugLayer);
|
||||||
|
} else {
|
||||||
|
GUI.addTopLayer(debugLayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void switchLanguage() {
|
||||||
|
Localizer localizer = Localizer.getInstance();
|
||||||
|
List<String> languages = localizer.getLanguages();
|
||||||
|
|
||||||
|
int index = languages.indexOf(localizer.getLanguage());
|
||||||
|
|
||||||
|
if (index == languages.size() - 1) {
|
||||||
|
index = 0;
|
||||||
|
} else {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
localizer.setLanguage(languages.get(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityData getEntity() {
|
||||||
|
return getPlayer().getEntity();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalPlayer getPlayer() {
|
||||||
|
return ClientState.getInstance().getLocalPlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MovementControls getMovementControls() {
|
||||||
|
return movementControls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockData getSelectedBlock() {
|
||||||
|
return interactionControls.getSelectedBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TileData getSelectedTile() {
|
||||||
|
return interactionControls.getSelectedTile();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBlockSelected() {
|
||||||
|
return interactionControls.isBlockSelected();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFlying() {
|
||||||
|
return movementControls.isFlying();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSprinting() {
|
||||||
|
return movementControls.isSprinting();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -218,6 +218,10 @@ public class Fields {
|
|||||||
public static Field anti(Field f) {
|
public static Field anti(Field f) {
|
||||||
return tweak(f, 1, -1, 1);
|
return tweak(f, 1, -1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Field rarify(Field f, float rarity) {
|
||||||
|
return (x, y) -> Math.max((f.compute(x, y) - rarity) / (1 - rarity), 0);
|
||||||
|
}
|
||||||
|
|
||||||
public static Field octaves(Field f, double scaleFactor, double amplitudeFactor, int octaves) {
|
public static Field octaves(Field f, double scaleFactor, double amplitudeFactor, int octaves) {
|
||||||
return (x, y) -> {
|
return (x, y) -> {
|
||||||
|
@ -26,6 +26,7 @@ import java.util.function.Function;
|
|||||||
import ru.windcorp.progressia.common.Units;
|
import ru.windcorp.progressia.common.Units;
|
||||||
import ru.windcorp.progressia.common.util.noise.discrete.WorleyProceduralNoise;
|
import ru.windcorp.progressia.common.util.noise.discrete.WorleyProceduralNoise;
|
||||||
import ru.windcorp.progressia.common.world.Coordinates;
|
import ru.windcorp.progressia.common.world.Coordinates;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
import ru.windcorp.progressia.server.Server;
|
import ru.windcorp.progressia.server.Server;
|
||||||
import ru.windcorp.progressia.server.world.generation.WorldGenerator;
|
import ru.windcorp.progressia.server.world.generation.WorldGenerator;
|
||||||
import ru.windcorp.progressia.server.world.generation.planet.Planet;
|
import ru.windcorp.progressia.server.world.generation.planet.Planet;
|
||||||
@ -125,19 +126,16 @@ public class TestGenerationConfig {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
Function<String, SurfaceFloatField> floweriness = flowerName -> fields.register(
|
Function<AbsFace, Field> floweriness = f -> multiply(
|
||||||
"Test:Flower" + flowerName,
|
amplify(withMin(squash(scale(octaves(fields.primitive(), 2, 3), 100), 5), 0), 2),
|
||||||
f -> multiply(
|
tweak(fields.get("Test:Forest", f), 1, -1, 1.1),
|
||||||
selectPositive(squash(scale(octaves(fields.primitive(), 2, 3), 100), 2), 1, 0.5),
|
anti(squash(fields.get("Test:Cliff", f), 10))
|
||||||
tweak(fields.get("Test:Forest", f), 1, -1, 1.1),
|
|
||||||
anti(squash(fields.get("Test:Cliff", f), 10))
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
features.add(new TestBushFeature("Test:BushFeature", forestiness));
|
features.add(new TestBushFeature("Test:BushFeature", forestiness));
|
||||||
features.add(new TestTreeFeature("Test:TreeFeature", forestiness));
|
features.add(new TestTreeFeature("Test:TreeFeature", forestiness));
|
||||||
features.add(new TestGrassFeature("Test:GrassFeature", grassiness));
|
features.add(new TestGrassFeature("Test:GrassFeature", grassiness));
|
||||||
features.add(new TestFlowerFeature("Test:FlowerFeature", floweriness));
|
features.add(new TestFlowerFeature("Test:FlowerFeature", TestContent.FLOWERS, floweriness, fields));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -38,10 +38,12 @@ public class TestBushFeature extends MultiblockVegetationFeature {
|
|||||||
|
|
||||||
Vec3i center = context.getLocation().add_(0, 0, 1);
|
Vec3i center = context.getLocation().add_(0, 0, 1);
|
||||||
|
|
||||||
context.setBlock(center, trunk);
|
if (size > 0.8) {
|
||||||
context.setBlock(center.add_(0, 0, 1), leaves);
|
context.setBlock(center, trunk);
|
||||||
|
context.setBlock(center.add_(0, 0, 1), leaves);
|
||||||
|
}
|
||||||
|
|
||||||
iterateBlob(center, stretch(size, 1.3, 2.5), stretch(size, 0.6, 1.5), 0.7, 2, p -> {
|
iterateBlob(center, stretch(size, 0.5, 2.5), stretch(size, 0.6, 1.5), 0.7, 2, p -> {
|
||||||
setLeaves(context, p, leaves);
|
setLeaves(context, p, leaves);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -17,35 +17,73 @@
|
|||||||
*/
|
*/
|
||||||
package ru.windcorp.progressia.test.gen.feature;
|
package ru.windcorp.progressia.test.gen.feature;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
import ru.windcorp.progressia.common.world.rels.RelFace;
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
import ru.windcorp.progressia.common.world.tile.TileData;
|
import ru.windcorp.progressia.common.world.tile.TileData;
|
||||||
import ru.windcorp.progressia.common.world.tile.TileDataRegistry;
|
|
||||||
import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField;
|
import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField;
|
||||||
import ru.windcorp.progressia.server.world.generation.surface.SurfaceTopLayerFeature;
|
import ru.windcorp.progressia.server.world.generation.surface.SurfaceTopLayerFeature;
|
||||||
import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext;
|
import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext;
|
||||||
|
import ru.windcorp.progressia.test.Flowers;
|
||||||
|
import ru.windcorp.progressia.test.Flowers.Flower;
|
||||||
|
import ru.windcorp.progressia.test.Flowers.FlowerVariant;
|
||||||
import ru.windcorp.progressia.test.TestContent;
|
import ru.windcorp.progressia.test.TestContent;
|
||||||
|
import ru.windcorp.progressia.test.gen.Fields;
|
||||||
|
import ru.windcorp.progressia.test.gen.Fields.Field;
|
||||||
|
|
||||||
public class TestFlowerFeature extends SurfaceTopLayerFeature {
|
public class TestFlowerFeature extends SurfaceTopLayerFeature {
|
||||||
|
|
||||||
private static class FlowerGenerator {
|
private static class FlowerGenerator {
|
||||||
private final TileData tile;
|
|
||||||
|
private final TileData[] variants;
|
||||||
private final SurfaceFloatField floweriness;
|
private final SurfaceFloatField floweriness;
|
||||||
|
|
||||||
public FlowerGenerator(TileData tile, Function<String, SurfaceFloatField> flowerinessGenerator) {
|
public FlowerGenerator(Flower flower, Function<AbsFace, Field> flowerinessGenerator, Fields fields) {
|
||||||
this.tile = tile;
|
this.floweriness = fields.register(
|
||||||
this.floweriness = flowerinessGenerator.apply(tile.getName());
|
"Test:Flower" + flower.getName(),
|
||||||
|
f -> Fields.rarify(flowerinessGenerator.apply(f), flower.getRarity())
|
||||||
|
);
|
||||||
|
|
||||||
|
List<TileData> tiles = new ArrayList<>();
|
||||||
|
for (FlowerVariant variant : FlowerVariant.values()) {
|
||||||
|
TileData tile = flower.getTile(variant);
|
||||||
|
if (tile == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles.add(tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.variants = tiles.toArray(new TileData[tiles.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void generate(SurfaceBlockContext context) {
|
public void generate(SurfaceBlockContext context) {
|
||||||
if (context.getRandom().nextDouble() < floweriness.get(context)) {
|
float floweriness = this.floweriness.get(context);
|
||||||
context.addTile(RelFace.UP, tile);
|
|
||||||
|
if (floweriness <= 0) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float random = context.getRandom().nextFloat();
|
||||||
|
|
||||||
|
int variant = (int) Math.floor((random + floweriness - 1) * variants.length);
|
||||||
|
|
||||||
|
if (variant < 0) {
|
||||||
|
return;
|
||||||
|
} else if (variant >= variants.length) {
|
||||||
|
// Screw doing float math properly, just clamp it
|
||||||
|
variant = variants.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.addTile(RelFace.UP, variants[variant]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Set<String> soilWhitelist;
|
private final Set<String> soilWhitelist;
|
||||||
@ -58,12 +96,11 @@ public class TestFlowerFeature extends SurfaceTopLayerFeature {
|
|||||||
|
|
||||||
private final FlowerGenerator[] flowers;
|
private final FlowerGenerator[] flowers;
|
||||||
|
|
||||||
public TestFlowerFeature(String id, Function<String, SurfaceFloatField> flowerinessGenerator) {
|
public TestFlowerFeature(String id, Flowers flowers, Function<AbsFace, Field> flowerinessGenerator, Fields fields) {
|
||||||
super(id);
|
super(id);
|
||||||
|
|
||||||
this.flowers = TileDataRegistry.getInstance().values().stream()
|
this.flowers = flowers.getFlowers().stream()
|
||||||
.filter(tile -> tile.getName().endsWith("Flowers"))
|
.map(flower -> new FlowerGenerator(flower, flowerinessGenerator, fields))
|
||||||
.map(tile -> new FlowerGenerator(tile, flowerinessGenerator))
|
|
||||||
.toArray(FlowerGenerator[]::new);
|
.toArray(FlowerGenerator[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ package ru.windcorp.progressia.test.gen.feature;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
@ -57,12 +58,26 @@ public class TestGrassFeature extends SurfaceTopLayerFeature {
|
|||||||
flatGrasses.put(0.1f, 0.2f, TileDataRegistry.getInstance().get("Test:GrassWeb"));
|
flatGrasses.put(0.1f, 0.2f, TileDataRegistry.getInstance().get("Test:GrassWeb"));
|
||||||
flatGrasses.put(0.05f, 0.1f, TileDataRegistry.getInstance().get("Test:GrassThreads"));
|
flatGrasses.put(0.05f, 0.1f, TileDataRegistry.getInstance().get("Test:GrassThreads"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Function<SurfaceBlockContext, TileData> randomPicker(String commonId, String rareId) {
|
||||||
|
TileData common = TileDataRegistry.getInstance().get(commonId);
|
||||||
|
TileData rare = TileDataRegistry.getInstance().get(rareId);
|
||||||
|
final float rareChance = 0.2f;
|
||||||
|
|
||||||
|
return context -> {
|
||||||
|
if (context.getRandom().nextFloat() < rareChance) {
|
||||||
|
return rare;
|
||||||
|
} else {
|
||||||
|
return common;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private final FloatRangeMap<TileData> herbGrasses = new ArrayFloatRangeMap<>();
|
private final FloatRangeMap<Function<SurfaceBlockContext, TileData>> herbGrasses = new ArrayFloatRangeMap<>();
|
||||||
{
|
{
|
||||||
herbGrasses.put(0.6f, 1, TileDataRegistry.getInstance().get("Test:TallGrass"));
|
herbGrasses.put(0.6f, 1, randomPicker("Test:GrassMeadow2", "Test:GrassBluegrass2"));
|
||||||
herbGrasses.put(0.4f, 0.6f, TileDataRegistry.getInstance().get("Test:MediumGrass"));
|
herbGrasses.put(0.4f, 0.6f, randomPicker("Test:GrassMeadow1", "Test:GrassBluegrass1"));
|
||||||
herbGrasses.put(0.1f, 0.4f, TileDataRegistry.getInstance().get("Test:LowGrass"));
|
herbGrasses.put(0.1f, 0.4f, randomPicker("Test:GrassMeadow0", "Test:GrassBluegrass0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private final List<TileData> scatter = ImmutableList.of(
|
private final List<TileData> scatter = ImmutableList.of(
|
||||||
@ -134,9 +149,9 @@ public class TestGrassFeature extends SurfaceTopLayerFeature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (context.getRandom().nextDouble() < grassiness) {
|
if (context.getRandom().nextDouble() < grassiness) {
|
||||||
TileData herbGrass = herbGrasses.get((float) grassiness);
|
Function<SurfaceBlockContext, TileData> herbGrass = herbGrasses.get((float) grassiness);
|
||||||
if (herbGrass != null) {
|
if (herbGrass != null) {
|
||||||
context.addTile(RelFace.UP, herbGrass);
|
context.addTile(RelFace.UP, herbGrass.apply(context));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,33 +24,44 @@ import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField;
|
|||||||
import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext;
|
import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext;
|
||||||
|
|
||||||
public class TestTreeFeature extends MultiblockVegetationFeature {
|
public class TestTreeFeature extends MultiblockVegetationFeature {
|
||||||
|
|
||||||
private final BlockData trunk = BlockDataRegistry.getInstance().get("Test:Log");
|
private final BlockData trunk = BlockDataRegistry.getInstance().get("Test:Log");
|
||||||
private final BlockData leaves = BlockDataRegistry.getInstance().get("Test:TemporaryLeaves");
|
private final BlockData leaves = BlockDataRegistry.getInstance().get("Test:LeavesPine");
|
||||||
|
|
||||||
public TestTreeFeature(String id, SurfaceFloatField selector) {
|
public TestTreeFeature(String id, SurfaceFloatField selector) {
|
||||||
super(id, selector, 10 * 10);
|
super(id, selector, 7 * 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void grow(SurfaceBlockContext context, double selectorValue) {
|
protected void grow(SurfaceBlockContext context, double selectorValue) {
|
||||||
|
|
||||||
Vec3i start = context.getLocation().add_(0, 0, 1);
|
Vec3i start = context.getLocation().add_(0, 0, 1);
|
||||||
Vec3i center = start.add_(0);
|
Vec3i center = start.add_(0);
|
||||||
|
|
||||||
double size = selectorValue * randomDouble(context, 0.8, 1.2);
|
double size = selectorValue * randomDouble(context, 0.8, 1.2);
|
||||||
|
double volume = stretch(size, 2.5, 1);
|
||||||
int height = (int) stretch(size, 3, 7);
|
|
||||||
|
int height = (int) stretch(size, 8, 12);
|
||||||
for (; center.z < start.z + height; ++center.z) {
|
for (; center.z < start.z + height; ++center.z) {
|
||||||
context.setBlock(center, trunk);
|
context.setBlock(center, trunk);
|
||||||
}
|
}
|
||||||
|
--center.z;
|
||||||
double branchHorDistance = 0;
|
|
||||||
|
iterateBlob(center, 1.5 * volume, 1.75, 0.5, 3, p -> {
|
||||||
|
setLeaves(context, p, leaves);
|
||||||
|
});
|
||||||
|
|
||||||
|
int branchCount = 1 + context.getRandom().nextInt(2) + (int) (stretch(size, 0, 4));
|
||||||
|
|
||||||
|
for (int i = 0; i < branchCount; ++i) {
|
||||||
|
|
||||||
do {
|
|
||||||
double branchSize = 0.5 + randomDouble(context, 1, 2) * size;
|
|
||||||
double branchHorAngle = randomDouble(context, 0, 2 * Math.PI);
|
double branchHorAngle = randomDouble(context, 0, 2 * Math.PI);
|
||||||
int branchVertOffset = (int) randomDouble(context, -2, 0);
|
double branchHorDistance = randomDouble(context, 0.7, 1.5) * volume;
|
||||||
|
|
||||||
|
int branchVertOffset = (int) randomDouble(context, -height / 3.0, -height / 1.5);
|
||||||
|
|
||||||
|
double branchSize = randomDouble(context, 1, 2) * volume;
|
||||||
|
double branchHeight = 1.5;
|
||||||
|
|
||||||
Vec3i branchCenter = center.add_(
|
Vec3i branchCenter = center.add_(
|
||||||
(int) (Math.sin(branchHorAngle) * branchHorDistance),
|
(int) (Math.sin(branchHorAngle) * branchHorDistance),
|
||||||
@ -58,12 +69,12 @@ public class TestTreeFeature extends MultiblockVegetationFeature {
|
|||||||
branchVertOffset
|
branchVertOffset
|
||||||
);
|
);
|
||||||
|
|
||||||
iterateBlob(branchCenter, 1 * branchSize, 2.3 * branchSize, 0.5, 3, p -> {
|
iterateBlob(branchCenter, branchSize, branchHeight, 0.5, 3, p -> {
|
||||||
setLeaves(context, p, leaves);
|
setLeaves(context, p, leaves);
|
||||||
});
|
});
|
||||||
|
|
||||||
branchHorDistance = randomDouble(context, 0.7, 1.5);
|
}
|
||||||
} while (context.getRandom().nextInt(8) > 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.test.trees;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import glm.vec._3.i.Vec3i;
|
||||||
|
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.ShapePart;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.ShapePrototype;
|
||||||
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
|
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||||
|
import ru.windcorp.progressia.client.world.block.BlockRender;
|
||||||
|
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSimple.BlockOptimizedSimple;
|
||||||
|
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||||
|
|
||||||
|
public class BlockRenderLeavesHazel extends BlockRender implements BlockOptimizedSimple {
|
||||||
|
|
||||||
|
private final Texture texture;
|
||||||
|
|
||||||
|
public BlockRenderLeavesHazel(String id, Texture texture) {
|
||||||
|
super(id);
|
||||||
|
this.texture = texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getShapeParts(DefaultChunkData chunk, Vec3i bic, Consumer<ShapePart> output) {
|
||||||
|
ShapePrototype sp = ShapePrototype.unitSquare(texture).makeDoubleSided();
|
||||||
|
sp.push().translate(bic.x, bic.y, bic.z);
|
||||||
|
|
||||||
|
Random random = new Random(chunk.getX() ^ bic.x);
|
||||||
|
random.setSeed(random.nextLong() ^ chunk.getY() ^ bic.y);
|
||||||
|
random.setSeed(random.nextLong() ^ chunk.getZ() ^ bic.z);
|
||||||
|
|
||||||
|
final float angle = (float) (Math.PI / 4);
|
||||||
|
|
||||||
|
sp.push().translate(0, 0, random.nextFloat() * 0.5f)
|
||||||
|
.rotateZ(random.nextFloat() * Math.PI)
|
||||||
|
.rotateX(-0.3f + random.nextFloat() * 0.6)
|
||||||
|
.scale(1.3f);
|
||||||
|
|
||||||
|
sp.push().translate(0, 0, -0.5f).rotateX(angle).translate(-0.5f, -0.5f, 0);
|
||||||
|
output.accept(sp.build());
|
||||||
|
sp.pop();
|
||||||
|
|
||||||
|
sp.push().translate(0, 0, -0.5f).rotateX(-angle).translate(-0.5f, -0.5f, 0);
|
||||||
|
output.accept(sp.build());
|
||||||
|
sp.pop();
|
||||||
|
|
||||||
|
sp.push().translate(0, 0, -0.5f).rotateY(angle).translate(-0.5f, -0.5f, 0);
|
||||||
|
output.accept(sp.build());
|
||||||
|
sp.pop();
|
||||||
|
|
||||||
|
sp.push().translate(0, 0, -0.5f).rotateY(-angle).translate(-0.5f, -0.5f, 0);
|
||||||
|
output.accept(sp.build());
|
||||||
|
sp.pop();
|
||||||
|
|
||||||
|
sp.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk) {
|
||||||
|
Collection<ShapePart> parts = new ArrayList<>(6);
|
||||||
|
|
||||||
|
getShapeParts(chunk, blockInChunk, parts::add);
|
||||||
|
|
||||||
|
return new Shape(
|
||||||
|
Usage.STATIC,
|
||||||
|
WorldRenderProgram.getDefault(),
|
||||||
|
parts.toArray(new ShapePart[parts.size()])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean needsOwnRenderable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.test.trees;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import glm.vec._3.i.Vec3i;
|
||||||
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
|
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.ShapePart;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.ShapePrototype;
|
||||||
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
|
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||||
|
import ru.windcorp.progressia.client.world.block.BlockRender;
|
||||||
|
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSimple.BlockOptimizedSimple;
|
||||||
|
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||||
|
|
||||||
|
public class BlockRenderLeavesPine extends BlockRender implements BlockOptimizedSimple {
|
||||||
|
|
||||||
|
private final Texture texture;
|
||||||
|
|
||||||
|
public BlockRenderLeavesPine(String id, Texture texture) {
|
||||||
|
super(id);
|
||||||
|
this.texture = texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getShapeParts(DefaultChunkData chunk, Vec3i bic, Consumer<ShapePart> output) {
|
||||||
|
Random random = new Random(chunk.getX() ^ bic.x);
|
||||||
|
random.setSeed(random.nextLong() ^ chunk.getY() ^ bic.y);
|
||||||
|
random.setSeed(random.nextLong() ^ chunk.getZ() ^ bic.z);
|
||||||
|
|
||||||
|
ShapePrototype sp = ShapePrototype.unitSquare(texture).makeDoubleSided();
|
||||||
|
sp.push().translate(
|
||||||
|
bic.x + random.nextFloat() * 0.3f - 0.15f,
|
||||||
|
bic.y + random.nextFloat() * 0.3f - 0.15f,
|
||||||
|
bic.z
|
||||||
|
);
|
||||||
|
|
||||||
|
final float angle = 0.5f;
|
||||||
|
|
||||||
|
for (float offset : new float[] { -0.25f, -0.75f }) {
|
||||||
|
sp.push().translate(0, 0, offset + random.nextFloat() * 0.5f)
|
||||||
|
.rotateZ(-0.3f + random.nextFloat() * 0.6)
|
||||||
|
.rotateX(-0.3f + random.nextFloat() * 0.6);
|
||||||
|
|
||||||
|
sp.push().translate(0, 0, -0.3f).rotateX(angle).translate(-0.5f, -0.5f, 0.5f);
|
||||||
|
output.accept(sp.build());
|
||||||
|
sp.pop();
|
||||||
|
|
||||||
|
sp.push().translate(0, 0, -0.3f).rotateX(-angle).translate(-0.5f, -0.5f, 0.5f);
|
||||||
|
output.accept(sp.build());
|
||||||
|
sp.pop();
|
||||||
|
|
||||||
|
sp.push().translate(0, 0, -0.3f).rotateY(angle).translate(-0.5f, -0.5f, 0.5f);
|
||||||
|
output.accept(sp.build());
|
||||||
|
sp.pop();
|
||||||
|
|
||||||
|
sp.push().translate(0, 0, -0.3f).rotateY(-angle).translate(-0.5f, -0.5f, 0.5f);
|
||||||
|
output.accept(sp.build());
|
||||||
|
sp.pop();
|
||||||
|
|
||||||
|
sp.pop();
|
||||||
|
sp.addColorMultiplier(Colors.GRAY_A);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk) {
|
||||||
|
Collection<ShapePart> parts = new ArrayList<>(6);
|
||||||
|
|
||||||
|
getShapeParts(chunk, blockInChunk, parts::add);
|
||||||
|
|
||||||
|
return new Shape(
|
||||||
|
Usage.STATIC,
|
||||||
|
WorldRenderProgram.getDefault(),
|
||||||
|
parts.toArray(new ShapePart[parts.size()])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean needsOwnRenderable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
BIN
src/main/resources/assets/icons/logo128.original.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
src/main/resources/assets/icons/logo128.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/main/resources/assets/icons/logo16.original.png
Executable file
After Width: | Height: | Size: 342 B |
BIN
src/main/resources/assets/icons/logo16.png
Normal file
After Width: | Height: | Size: 424 B |
BIN
src/main/resources/assets/icons/logo20.original.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
src/main/resources/assets/icons/logo20.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
src/main/resources/assets/icons/logo22.original.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
src/main/resources/assets/icons/logo22.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
src/main/resources/assets/icons/logo24.original.png
Executable file
After Width: | Height: | Size: 8.5 KiB |