Compare commits
No commits in common. "master" and "TechDemo" have entirely different histories.
2
.gitattributes
vendored
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
|
|
||||||
|
49
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
49
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,49 +0,0 @@
|
|||||||
name: Bug Report
|
|
||||||
description: Let us know about a problem
|
|
||||||
labels: [bug]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Use this template to report identified problems. If the game suddenly crashed, and you need help identifying the cause of a crashreport, please use the Investigate Crashreport template instead.
|
|
||||||
|
|
||||||
**Do not forget to give your issue a descriptive title.**
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Steps to reproduce
|
|
||||||
description: |
|
|
||||||
What did you do just before the problem appeared, or where can it be observed?
|
|
||||||
|
|
||||||
If the issue occurs unreliably, please estimate the probability of the crash.
|
|
||||||
placeholder: |
|
|
||||||
1. Look at a cow
|
|
||||||
2. Wait for the cow to look back
|
|
||||||
3. The cow turns away after a minute
|
|
||||||
|
|
||||||
The issue occurs approximately once every 10 attempts.
|
|
||||||
|
|
||||||
Player inventory must be empty. Does not occur in multiplayer. Occurred inside a virtual machine.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Expected behaviour
|
|
||||||
description: |
|
|
||||||
What should happen?
|
|
||||||
placeholder: |
|
|
||||||
The cow should stare back indefinitely, because obviously it should, and because comedy.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Other information
|
|
||||||
description: What else can you tell us about this bug?
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Crashreport
|
|
||||||
description: If you have a relevant crashreport, paste the contents of the crashreport file here
|
|
||||||
render: text
|
|
||||||
validations:
|
|
||||||
required: false
|
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,5 +0,0 @@
|
|||||||
blank_issues_enabled: true
|
|
||||||
contact_links:
|
|
||||||
- name: Official Progressia Discord
|
|
||||||
url: https://discord.gg/TWuXbVmX23
|
|
||||||
about: Ask developers or community members for help
|
|
41
.github/ISSUE_TEMPLATE/crashreport.yml
vendored
41
.github/ISSUE_TEMPLATE/crashreport.yml
vendored
@ -1,41 +0,0 @@
|
|||||||
name: Investigate Crashreport
|
|
||||||
description: Submit a crashreport to determine the problem
|
|
||||||
labels: [bug, crashreport]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Use this template to submit crashreports for initial analisys. If you don't have a crashreport, or you have a good understanding of the underlying cause, please use the Bug Report template instead.
|
|
||||||
|
|
||||||
Crashreports can be found in `WORKING-DIRECTORY/crashreports` (take notice of the timestamp). `WORKING-DIRECTORY` is `C:\Users\<windows-user>\AppData\Roaming\Progressia` on Windows when using the installer.
|
|
||||||
|
|
||||||
**Do not forget to give your issue a descriptive title.**
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Steps to reproduce
|
|
||||||
description: |
|
|
||||||
What did you do just before the crash? How could someone else reproduce it? What other context might be relevant?
|
|
||||||
|
|
||||||
If the issue occurs unreliably, please estimate the probability of the crash.
|
|
||||||
placeholder: |
|
|
||||||
1. Enter a new world
|
|
||||||
2. Turn around
|
|
||||||
3. Wait 5 minutes
|
|
||||||
4. Game crashes half of the time
|
|
||||||
|
|
||||||
Player inventory must be empty. Does not crash in multiplayer. Occurred inside a virtual machine.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Other information
|
|
||||||
description: What else can you tell us about this crashreport?
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Crashreport
|
|
||||||
description: Paste the contents of the crashreport file here
|
|
||||||
render: text
|
|
||||||
validations:
|
|
||||||
required: true
|
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -30,8 +30,6 @@ build_packages/*
|
|||||||
!build_packages/NSIS
|
!build_packages/NSIS
|
||||||
build_packages/NSIS/*
|
build_packages/NSIS/*
|
||||||
!build_packages/NSIS/ProgressiaInstaller.nsi
|
!build_packages/NSIS/ProgressiaInstaller.nsi
|
||||||
!build_packages/NSIS/logo.ico
|
|
||||||
!build_packages/NSIS/left_side.bmp
|
|
||||||
|
|
||||||
# ... and except build_packages/DEB/template
|
# ... and except build_packages/DEB/template
|
||||||
!build_packages/DEB
|
!build_packages/DEB
|
||||||
|
@ -16,7 +16,7 @@ temperature mechanics and a parallelism-capable server.
|
|||||||
- GNU/Linux (x64, arm32 or arm64), Windows XP or later (x64 or x86) or MacOS (x64)
|
- GNU/Linux (x64, arm32 or arm64), Windows XP or later (x64 or x86) or MacOS (x64)
|
||||||
- Java 8 or later
|
- Java 8 or later
|
||||||
- OpenGL 2.1 or later
|
- OpenGL 2.1 or later
|
||||||
- Probably about 0.5 GiB RAM
|
- Probably at least 4 GiB RAM
|
||||||
- Less than 1 GiB of storage space
|
- Less than 1 GiB of storage space
|
||||||
|
|
||||||
See [Build Guide](docs/building/BuildGuide.md) for compilation requirements.
|
See [Build Guide](docs/building/BuildGuide.md) for compilation requirements.
|
||||||
|
511
build.gradle
511
build.gradle
@ -1,31 +1,25 @@
|
|||||||
/*
|
/*
|
||||||
* Build logic for Progressia
|
* build.gradle for Progressia
|
||||||
* build.gradle
|
|
||||||
*
|
|
||||||
* Please refer to
|
|
||||||
*
|
|
||||||
* docs/building/BuildScriptReference.md
|
|
||||||
*
|
|
||||||
* for user reference.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
// Apply the java-library plugin to add support for Java Library
|
||||||
|
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
|
/*
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
* We're Java 8 for now.
|
||||||
|
* 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 {
|
||||||
@ -35,378 +29,287 @@ 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'])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set encoding
|
|
||||||
*/
|
|
||||||
|
|
||||||
compileJava {
|
|
||||||
options.encoding = 'utf8'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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:31.0.1-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 'org.apache.logging.log4j:log4j-api:2.17.1'
|
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.3'
|
||||||
implementation 'org.apache.logging.log4j:log4j-core:2.17.1'
|
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.3'
|
||||||
|
|
||||||
// JUnit
|
// JUnit
|
||||||
// A unit-testing library
|
// A unit-testing library
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.12'
|
||||||
|
|
||||||
// Also see LWJGL dependencies in build_logic/lwjgl.gradle
|
// See LWJGL dependencies below
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Version resolution
|
* Progressia uses the following LWJGL libraries:
|
||||||
|
* - Core libraries
|
||||||
|
* - OpenGL
|
||||||
|
* - OpenAL
|
||||||
|
* - GLFW
|
||||||
|
* - STB
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import org.ajoberstar.grgit.*
|
/*
|
||||||
|
* LWJGL
|
||||||
|
* (auto-generated script)
|
||||||
|
* ((here be dragons))
|
||||||
|
*/
|
||||||
|
|
||||||
// Pattern: vMAJOR.MINOR.PATCH[-SUFFIX]
|
import org.gradle.internal.os.OperatingSystem
|
||||||
project.ext.tagFormat = /^v(\d+)\.(\d+)\.(\d+)(-[\w\-]*)?$/
|
|
||||||
|
|
||||||
String version_parseVersion(String tag, boolean increment) {
|
project.ext.lwjglVersion = "3.2.3"
|
||||||
try {
|
|
||||||
|
|
||||||
def data = (tag =~ tagFormat)[0]
|
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
|
||||||
|
}
|
||||||
|
|
||||||
def major = data[1]
|
dependencies {
|
||||||
def minor = data[2]
|
implementation platform("org.lwjgl:lwjgl-bom:$lwjglVersion")
|
||||||
def patch = data[3] as int
|
|
||||||
def suffix = data[4] ?: ''
|
|
||||||
|
|
||||||
if (increment) {
|
implementation "org.lwjgl:lwjgl"
|
||||||
def oldVersion = "$major.$minor.$patch$suffix"
|
implementation "org.lwjgl:lwjgl-glfw"
|
||||||
patch++
|
implementation "org.lwjgl:lwjgl-openal"
|
||||||
def newVersion = "$major.$minor.$patch$suffix"
|
implementation "org.lwjgl:lwjgl-opengl"
|
||||||
|
implementation "org.lwjgl:lwjgl-stb"
|
||||||
|
|
||||||
logger.info "Version parsed from Git: $oldVersion, incremented to $newVersion"
|
runtimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
|
||||||
return newVersion
|
runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives"
|
||||||
} else {
|
runtimeOnly "org.lwjgl:lwjgl-openal::$lwjglNatives"
|
||||||
def newVersion = "$major.$minor.$patch$suffix"
|
runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
|
||||||
logger.info "Version parsed from Git: $newVersion"
|
runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
|
||||||
return newVersion
|
}
|
||||||
|
|
||||||
|
// LWJGL END
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tasks
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Additional native libraries specification
|
||||||
|
*/
|
||||||
|
|
||||||
|
project.ext.platforms = new HashSet<>()
|
||||||
|
|
||||||
|
task addNativeDependencies {
|
||||||
|
doFirst {
|
||||||
|
def archs = project.ext.platforms
|
||||||
|
|
||||||
|
switch (archs.size()) {
|
||||||
|
case 0:
|
||||||
|
println "Adding LWJGL native dependencies for local platform only:\n\t$lwjglNatives"
|
||||||
|
archs.add project.ext.lwjglNatives
|
||||||
|
break
|
||||||
|
case 1:
|
||||||
|
println "Adding LWJGL native dependencies for platform\n\t" + archs.get(0)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
println "Adding LWJGL native dependencies for platforms:\n\t" + archs.join("\n\t")
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (any) {
|
if (project.ext.lwjglNatives.isEmpty()) println "WTF"
|
||||||
logger.warn "Could not parse version from tag \"$tag\""
|
|
||||||
return tag
|
dependencies {
|
||||||
|
archs.each { arch ->
|
||||||
|
runtimeOnly "org.lwjgl:lwjgl::$arch"
|
||||||
|
runtimeOnly "org.lwjgl:lwjgl-glfw::$arch"
|
||||||
|
runtimeOnly "org.lwjgl:lwjgl-openal::$arch"
|
||||||
|
runtimeOnly "org.lwjgl:lwjgl-opengl::$arch"
|
||||||
|
runtimeOnly "org.lwjgl:lwjgl-stb::$arch"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileJava.mustRunAfter addNativeDependencies // Make sure runtimeOnly has not been resolved
|
||||||
|
|
||||||
|
task requestLinuxDependencies {
|
||||||
|
description 'Adds linux, linux-arm64 and linux-arm32 native libraries to built artifacts.'
|
||||||
|
doFirst {
|
||||||
|
project.ext.platforms.addAll(['natives-linux', 'natives-linux-arm64', 'natives-linux-arm32'])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task requestWindowsDependencies {
|
||||||
|
description 'Adds windows and windows-x86 native libraries to built artifacts.'
|
||||||
|
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'])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Tag version_findRelevantTag(Grgit git) {
|
def dependencySpecificationTasks = tasks.findAll { task -> task.name.startsWith('request') && task.name.endsWith('Dependencies') }
|
||||||
def tags = git.tag.list()
|
|
||||||
|
|
||||||
def commits = [ git.head() ]
|
task requestCrossPlatformDependencies {
|
||||||
def visited = new HashSet<>()
|
description 'Adds native libraries for all available platforms to built artifacts.'
|
||||||
|
dependsOn dependencySpecificationTasks
|
||||||
while (true) {
|
|
||||||
if (commits.isEmpty()) return null
|
|
||||||
|
|
||||||
def nextCommits = new HashSet<>()
|
|
||||||
|
|
||||||
def formatSpecificationPrinted = false
|
|
||||||
for (def commit : commits) {
|
|
||||||
def tag = tags.findAll { it.commit == commit } ?.max { it.dateTime }
|
|
||||||
|
|
||||||
if (tag != null) {
|
|
||||||
if (tag.name ==~ tagFormat) {
|
|
||||||
return tag
|
|
||||||
} else {
|
|
||||||
if (!formatSpecificationPrinted) {
|
|
||||||
formatSpecificationPrinted = true
|
|
||||||
logger.info 'Expecting tag format: vMAJOR.MINOR.PATCH[-SUFFIX]'
|
|
||||||
}
|
|
||||||
logger.info 'Ignoring tag due to invalid format: {}', tag.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nextCommits.addAll commit.parentIds.collect(git.resolve.&toCommit)
|
|
||||||
}
|
|
||||||
|
|
||||||
visited.addAll commits
|
|
||||||
nextCommits.removeAll visited
|
|
||||||
commits = nextCommits
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task resolveVersion {
|
addNativeDependencies.mustRunAfter dependencySpecificationTasks
|
||||||
description 'Resolves version information from Git repository or project properties.'
|
|
||||||
|
/*
|
||||||
|
* Determines if the provided dependency should be packaged
|
||||||
|
*/
|
||||||
|
def isDependencyRequested(String dep) {
|
||||||
|
if (dep.endsWith(".jar")) {
|
||||||
|
dep = dep.substring(0, dep.length() - ".jar".length())
|
||||||
|
}
|
||||||
|
|
||||||
|
return !dep.contains("natives-") ||
|
||||||
|
project.ext.platforms.contains(dep.substring(dep.indexOf("natives-"), dep.length()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Manifest specification
|
||||||
|
*/
|
||||||
|
|
||||||
|
task specifyLocalManifest {
|
||||||
|
dependsOn addNativeDependencies // Make sure all native dependencies are specified
|
||||||
|
|
||||||
doFirst {
|
doFirst {
|
||||||
|
def classPath = []
|
||||||
|
|
||||||
project.ext.commit = System.env.GIT_COMMIT
|
configurations.runtimeClasspath.each {
|
||||||
project.ext.branch = System.env.GIT_BRANCH
|
if (isDependencyRequested(it.getName())) {
|
||||||
|
classPath.add("lib/" + it.getName())
|
||||||
try {
|
|
||||||
def git = Grgit.open(dir: project.projectDir)
|
|
||||||
|
|
||||||
project.ext.commit = commit ?: git.head().id
|
|
||||||
project.ext.branch = branch ?: git.branch.current().name
|
|
||||||
|
|
||||||
if (project.version != 'unspecified') {
|
|
||||||
// Leave version as-is
|
|
||||||
} else {
|
} else {
|
||||||
|
println "\tRemoving from JAR classpath (not requested): " + it.getName()
|
||||||
// Resolve version from Git
|
|
||||||
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 = commit ?: '-'
|
|
||||||
project.ext.branch = 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (branch.contains '/') {
|
if (classPath.size() == configurations.runtimeClasspath.size()) {
|
||||||
// Strip remote - no one wants that
|
println "Nothing removed from JAR classpath"
|
||||||
project.ext.branch = branch.takeAfter '/'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!project.hasProperty('buildId')) {
|
jar {
|
||||||
project.ext.buildId = '-'
|
manifest {
|
||||||
|
attributes(
|
||||||
|
"Main-Class": "ru.windcorp.progressia.client.ProgressiaClientMain",
|
||||||
|
"Class-Path": configurations.runtimeClasspath.collect { "lib/" + it.getName() } .findAll { isDependencyRequested(it) } .join(' ')
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doLast {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jar.dependsOn specifyLocalManifest
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Apply LWJGL logic
|
* Library export
|
||||||
*/
|
|
||||||
apply from: 'build_logic/lwjgl.gradle'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copy libraries into build/libs/lib directory, next to Progressia.jar
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
task exportLibs(type: Sync) {
|
task exportLibs(type: Sync) {
|
||||||
description 'Copies runtime libraries into a subdirectory next to the output JAR.'
|
mustRunAfter addNativeDependencies
|
||||||
|
|
||||||
jar.dependsOn exportLibs
|
into libsDirectory.get().getAsFile().getPath() + "/lib"
|
||||||
dependsOn lwjgl_addNativesToRuntimeOnly
|
exclude { !isDependencyRequested(it.getName()) }
|
||||||
|
from configurations.runtimeClasspath
|
||||||
// from defined in configureManifest
|
|
||||||
into 'build/libs/lib'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jar.dependsOn(exportLibs)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Configure JAR manifest
|
* Packaging
|
||||||
*/
|
*/
|
||||||
|
|
||||||
task configureManifest {
|
task packageDebian(type: Exec) {
|
||||||
description 'Populates JAR manifest with Main-Class, Class-Path and version metadata.'
|
description 'Builds the project and creates a Debain package.'
|
||||||
|
group 'Progressia'
|
||||||
jar.dependsOn configureManifest
|
|
||||||
dependsOn resolveVersion
|
|
||||||
dependsOn lwjgl_addNativesToRuntimeOnly
|
|
||||||
|
|
||||||
doFirst {
|
|
||||||
def classPath = project.lwjgl.replaceNativesIn(configurations.runtimeClasspath)
|
|
||||||
|
|
||||||
exportLibs.from classPath
|
|
||||||
|
|
||||||
jar.manifest.attributes(
|
|
||||||
'Main-Class': 'ru.windcorp.progressia.client.ProgressiaClientMain',
|
|
||||||
'Class-Path': classPath.collect { "lib/${java.net.URLEncoder.encode it.name}" } .join(' '),
|
|
||||||
|
|
||||||
'Specification-Title': 'Progressia',
|
|
||||||
|
|
||||||
'Implementation-Title': 'Progressia',
|
|
||||||
'Implementation-Version': project.version,
|
|
||||||
'Implementation-Version-Git-Commit': project.commit,
|
|
||||||
'Implementation-Version-Git-Branch': project.branch,
|
|
||||||
'Implementation-Version-BuildId': project.buildId,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Packaging working directory configuration
|
|
||||||
*/
|
|
||||||
|
|
||||||
import java.nio.file.*
|
|
||||||
import java.nio.file.attribute.*
|
|
||||||
|
|
||||||
task deletePackagingDirs(type: Delete) {
|
|
||||||
description 'Deletes build/tmp/packaging directory.'
|
|
||||||
followSymlinks = false
|
|
||||||
delete 'build/tmp/packaging'
|
|
||||||
}
|
|
||||||
|
|
||||||
task linkBuildOutputForPackaging {
|
|
||||||
description 'Symlinks the contents of build/libs into packaging working directory.'
|
|
||||||
|
|
||||||
dependsOn build
|
dependsOn build
|
||||||
mustRunAfter deletePackagingDirs
|
dependsOn requestLinuxDependencies
|
||||||
|
|
||||||
onlyIf { preparePackaging.ext.mode == 'symlink' }
|
commandLine './buildPackages.sh', 'debian'
|
||||||
|
|
||||||
ext.from = 'build/libs'
|
|
||||||
ext.into = "build/tmp/packaging/workingDir/${ -> preparePackaging.ext.buildDest}"
|
|
||||||
|
|
||||||
inputs.dir from
|
|
||||||
outputs.dir into
|
|
||||||
|
|
||||||
doLast {
|
doLast {
|
||||||
def fromPath = Paths.get from
|
println "Debian package available in build_packages/"
|
||||||
def intoPath = Paths.get into
|
|
||||||
|
|
||||||
Files.createDirectories intoPath
|
|
||||||
|
|
||||||
Files.list(fromPath).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 intoPath.resolve(it.fileName), intoPath.relativize(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task copyBuildOutputForPackaging(type: Copy) {
|
task packageWindows(type: Exec) {
|
||||||
description 'Copies the contents of build/libs into packaging working directory.'
|
description 'Builds the project and creates a Windows installer.'
|
||||||
|
group 'Progressia'
|
||||||
|
|
||||||
dependsOn build
|
dependsOn build
|
||||||
mustRunAfter deletePackagingDirs
|
dependsOn requestWindowsDependencies
|
||||||
|
|
||||||
onlyIf { preparePackaging.ext.mode == 'copy' }
|
commandLine './buildPackages.sh', 'windows'
|
||||||
|
|
||||||
from 'build/libs'
|
doLast {
|
||||||
filesMatching("${project.name}*.jar") {
|
println "Windows installer available in build_packages/"
|
||||||
include tasks.jar.archiveFileName.get()
|
|
||||||
}
|
}
|
||||||
into "build/tmp/packaging/workingDir/${ -> preparePackaging.ext.buildDest}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task preparePackaging {
|
|
||||||
preparePackaging.ext.buildDest = ''
|
|
||||||
preparePackaging.ext.mode = 'symlink'
|
|
||||||
|
|
||||||
dependsOn deletePackagingDirs
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convenience build tasks
|
|
||||||
*/
|
|
||||||
|
|
||||||
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 {
|
||||||
logger.info 'Native libraries for all platforms have been added'
|
println "Native libraries for all platforms have been added"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task buildLocal {
|
task buildLocal {
|
||||||
description "Builds the project including only native libraries for current platform (${lwjgl.localArch})."
|
description "Builds the project including only native libraries for current platform ($lwjglNatives)."
|
||||||
group 'Progressia'
|
group 'Progressia'
|
||||||
|
|
||||||
dependsOn build
|
dependsOn build
|
||||||
|
|
||||||
doLast {
|
doLast {
|
||||||
logger.info "Native libraries only for platform ${lwjgl.localArch} have been added"
|
println "Native libraries only for platform $lwjglNatives have been added"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
187
buildPackages.sh
Executable file
187
buildPackages.sh
Executable file
@ -0,0 +1,187 @@
|
|||||||
|
#!/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' &&
|
||||||
|
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
|
||||||
|
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
|
@ -1,149 +0,0 @@
|
|||||||
/*
|
|
||||||
* Build logic for Progressia
|
|
||||||
* LWJGL dependency logic
|
|
||||||
*/
|
|
||||||
|
|
||||||
project.ext.lwjgl = new HashMap<>()
|
|
||||||
|
|
||||||
// Version of LWJGL
|
|
||||||
lwjgl.version = '3.3.0'
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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<String>()
|
|
||||||
|
|
||||||
// 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 = System.getProperty('os.arch').startsWith('aarch64') ? 'macos-arm64' : 'macos'
|
|
||||||
break
|
|
||||||
case OperatingSystem.WINDOWS:
|
|
||||||
def osArch = System.getProperty('os.arch')
|
|
||||||
lwjgl.localArch = osArch.contains('64')
|
|
||||||
? "windows${osArch.startsWith('aarch64') ? '-arm64' : ''}"
|
|
||||||
: 'windows-x86'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
logger.info "Unknown or unsupported OS type according to Gradle's org.gradle.internal.os.OperatingSystem: ${OperatingSystem.current()}"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
configurations {
|
|
||||||
create 'lwjglNatives'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Declare pure-Java dependencies
|
|
||||||
dependencies {
|
|
||||||
// BOM
|
|
||||||
|
|
||||||
def bom = platform("org.lwjgl:lwjgl-bom:${lwjgl.version}")
|
|
||||||
implementation bom
|
|
||||||
lwjglNatives bom
|
|
||||||
|
|
||||||
// Core
|
|
||||||
implementation 'org.lwjgl:lwjgl'
|
|
||||||
// Local natives for core
|
|
||||||
runtimeOnly "org.lwjgl:lwjgl::natives-${lwjgl.localArch}"
|
|
||||||
|
|
||||||
// Components
|
|
||||||
lwjgl.libraries.each { lib ->
|
|
||||||
implementation "org.lwjgl:lwjgl-$lib"
|
|
||||||
// Local natives for component
|
|
||||||
runtimeOnly "org.lwjgl:lwjgl-$lib::natives-${lwjgl.localArch}"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Adds LWJGL native libraries to lwjglNatives configuration
|
|
||||||
*/
|
|
||||||
task 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(', ')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lwjgl.targets.contains(null)) {
|
|
||||||
if (lwjgl.localArch != null) {
|
|
||||||
throw new GradleException("Requested local LWJGL natives; could not determine local architecture for OS ${OperatingSystem.current()} with os.arch ${System.getProperty('os.arch')}")
|
|
||||||
} else {
|
|
||||||
throw new GradleException("LWJGL targets resolved to ${lwjgl.targets}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
lwjgl.targets.each { target ->
|
|
||||||
lwjglNatives "org.lwjgl:lwjgl::natives-$target"
|
|
||||||
lwjgl.libraries.each { lib ->
|
|
||||||
lwjglNatives "org.lwjgl:lwjgl-$lib::natives-$target"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replaces LWJGL natives in the given configuration with the requested ones
|
|
||||||
lwjgl.replaceNativesIn = { config ->
|
|
||||||
new ArrayList<File>().tap {
|
|
||||||
addAll config
|
|
||||||
removeIf { it.name ==~ /.*lwjgl.*natives.*/ }
|
|
||||||
addAll project.configurations.lwjglNatives
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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-arm64', 'windows-x86'
|
|
||||||
requestTask 'MacOS', 'macos', 'macos-arm64'
|
|
@ -1,42 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
doFirst {
|
|
||||||
mkdir 'build/packages'
|
|
||||||
}
|
|
||||||
|
|
||||||
executable 'dpkg-deb'
|
|
||||||
args '--root-owner-group'
|
|
||||||
args '--build', 'build/tmp/packaging/workingDir'
|
|
||||||
args 'build/packages'
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
doFirst {
|
|
||||||
mkdir 'build/packages'
|
|
||||||
}
|
|
||||||
|
|
||||||
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'
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
doFirst {
|
|
||||||
mkdir 'build/packages'
|
|
||||||
}
|
|
||||||
|
|
||||||
from 'build/tmp/packaging/workingDir'
|
|
||||||
destinationDirectory = file('build/packages')
|
|
||||||
}
|
|
@ -1,8 +1,8 @@
|
|||||||
Package: progressia
|
Package: progressia-techdemo
|
||||||
Version: ${version}
|
Version: 1.0
|
||||||
Section: custom
|
Section: custom
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Maintainer: Javapony <kvadropups@gmail.com>
|
Maintainer: Javapony <kvadropups@gmail.com>
|
||||||
Depends: java8-runtime
|
Depends: java8-runtime
|
||||||
Description: Progressia - a 3D sandbox survival game
|
Description: Progressia Techdemo release
|
100
build_packages/NSIS/ProgressiaInstaller.nsi
Normal file
100
build_packages/NSIS/ProgressiaInstaller.nsi
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
;NSIS Modern User Interface
|
||||||
|
;Welcome/Finish Page Example Script
|
||||||
|
;Written by Joost Verburg
|
||||||
|
|
||||||
|
;--------------------------------
|
||||||
|
;Include Modern UI
|
||||||
|
|
||||||
|
!include "MUI2.nsh"
|
||||||
|
|
||||||
|
;--------------------------------
|
||||||
|
;General
|
||||||
|
|
||||||
|
;Name and file
|
||||||
|
Name "Progressia"
|
||||||
|
OutFile "ProgressiaInstaller.exe"
|
||||||
|
Unicode True
|
||||||
|
|
||||||
|
;Default installation folder
|
||||||
|
InstallDir "$PROGRAMFILES\Progressia"
|
||||||
|
|
||||||
|
;Get installation folder from registry if available
|
||||||
|
InstallDirRegKey HKLM "Software\Progressia" "Install_Dir"
|
||||||
|
|
||||||
|
;Request application privileges for Windows Vista
|
||||||
|
RequestExecutionLevel admin
|
||||||
|
|
||||||
|
;--------------------------------
|
||||||
|
;Interface Settings
|
||||||
|
|
||||||
|
!define MUI_ABORTWARNING
|
||||||
|
|
||||||
|
;--------------------------------
|
||||||
|
;Pages
|
||||||
|
|
||||||
|
!insertmacro MUI_PAGE_WELCOME
|
||||||
|
;!insertmacro MUI_PAGE_LICENSE "${NSISDIR}\Docs\Modern UI\License.txt"
|
||||||
|
!insertmacro MUI_PAGE_COMPONENTS
|
||||||
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
|
!insertmacro MUI_PAGE_FINISH
|
||||||
|
|
||||||
|
!insertmacro MUI_UNPAGE_WELCOME
|
||||||
|
!insertmacro MUI_UNPAGE_CONFIRM
|
||||||
|
!insertmacro MUI_UNPAGE_INSTFILES
|
||||||
|
!insertmacro MUI_UNPAGE_FINISH
|
||||||
|
|
||||||
|
;--------------------------------
|
||||||
|
;Languages
|
||||||
|
|
||||||
|
!insertmacro MUI_LANGUAGE "English"
|
||||||
|
|
||||||
|
;--------------------------------
|
||||||
|
;Installer Sections
|
||||||
|
|
||||||
|
Section "Install Progressia" SecDummy
|
||||||
|
|
||||||
|
SetOutPath "$INSTDIR"
|
||||||
|
|
||||||
|
;Files
|
||||||
|
File Progressia.jar
|
||||||
|
File /r lib
|
||||||
|
|
||||||
|
;Store installation folder
|
||||||
|
WriteRegStr HKLM SOFTWARE\Progressia "Install_Dir" "$INSTDIR"
|
||||||
|
|
||||||
|
;Create uninstaller
|
||||||
|
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Progressia" "DisplayName" "Progressia (remove only)"
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Progressia" "UninstallString" "$INSTDIR\Uninstall.exe"
|
||||||
|
WriteUninstaller "$INSTDIR\Uninstall.exe"
|
||||||
|
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
;--------------------------------
|
||||||
|
;Descriptions
|
||||||
|
|
||||||
|
;Language strings
|
||||||
|
LangString DESC_SecDummy ${LANG_ENGLISH} "A test section."
|
||||||
|
|
||||||
|
;Assign language strings to sections
|
||||||
|
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
|
||||||
|
!insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} $(DESC_SecDummy)
|
||||||
|
!insertmacro MUI_FUNCTION_DESCRIPTION_END
|
||||||
|
|
||||||
|
;--------------------------------
|
||||||
|
;Uninstaller Section
|
||||||
|
|
||||||
|
Section "Uninstall"
|
||||||
|
|
||||||
|
;ADD YOUR OWN FILES HERE...
|
||||||
|
|
||||||
|
Delete $INSTDIR\Uninstall.exe
|
||||||
|
Delete $INSTDIR\Progressia.jar
|
||||||
|
Delete $INSTDIR\lib\*.*
|
||||||
|
|
||||||
|
RMDir /r "$INSTDIR"
|
||||||
|
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Progressia"
|
||||||
|
DeleteRegKey HKLM "Software\Progressia"
|
||||||
|
|
||||||
|
SectionEnd
|
@ -1,69 +0,0 @@
|
|||||||
# Contributing Guidelines
|
|
||||||
|
|
||||||
This document lists conventions adopted by Progressia developers.
|
|
||||||
|
|
||||||
## git
|
|
||||||
|
|
||||||
### Branches
|
|
||||||
Progressia repository contains a `master` branch and several "feature" branches.
|
|
||||||
|
|
||||||
`master` is expected to contain a version of the game suitable for demonstration and forking/branching. Do not commit directly to `master` without OLEGSHA's approval.
|
|
||||||
- `master` must always correctly build without compiler warnings (see below).
|
|
||||||
- `master` must always pass all unit tests.
|
|
||||||
- `master` must always be able to launch successfully.
|
|
||||||
- `master` must always only contain working features.
|
|
||||||
- `master` should not contain excessive debug code.
|
|
||||||
- `master` must always have its code and filenames formatted (see below).
|
|
||||||
|
|
||||||
"Feature" branches are branches dedicated to the development of a single feature. When the feature reaches completion the branch is merged into `master` and removed. Intermediate merges into `master` may occur when some fitting milestone is reached. Intermediate merges from `master` may be done as necessary. Merges between "feature" branches should generally be avoided.
|
|
||||||
|
|
||||||
When beginning work on a new feature, create a new branch based on `master` (or on another "feature" branch if absolutely necessary). Use `all-small-with-dashes` to name the branch: `add-trees` or `rebalance-plastics` are good names. Do not fix unrelated bugs or work on unrelated features in a "feature" branch - create a new one, switch to an existing one or commit directly to `master` if the changes are small enough.
|
|
||||||
|
|
||||||
"Feature" branches may not be formatted properly. Formatting is required before merging into `master` or other branches.
|
|
||||||
|
|
||||||
### Commits
|
|
||||||
- Commits must leave the branch in a state that builds without compiler warnings (see below).
|
|
||||||
- Changes should be grouped in commits semantically. Avoid committing many small related changes in sequence; if necessary, wait and accumulate them. Avoid committing unrelated changes together; if necessary, split staged changes into several commits. This should normally result in about 1-2 commits for a day's work.
|
|
||||||
- Commit bulk changes (renaming, formatting, ...) separately. Don't ever commit whitespace changes outside formatting commits.
|
|
||||||
- Message format:
|
|
||||||
|
|
||||||
```
|
|
||||||
Short description of changes
|
|
||||||
<empty line>
|
|
||||||
- Enumeration of changes
|
|
||||||
- Nest when appropriate
|
|
||||||
- Use dashes only
|
|
||||||
- List not needed for small commits
|
|
||||||
```
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
Changed packages for relations, renamed Face to ShapePart
|
|
||||||
|
|
||||||
- Added BlockRelation as an abstract superclass to existing relations
|
|
||||||
- Must be given an absolute "up" direction before use
|
|
||||||
- Moved AbsFace, AbsRelation and BlockRelation to .world.rels
|
|
||||||
- Renamed Face to ShapePart to reduce confusion with AbsFace
|
|
||||||
```
|
|
||||||
|
|
||||||
- Only commit changes described in the commit message. Please double-check staged files before committing.
|
|
||||||
- Avoid merge conflicts. Pull before committing.
|
|
||||||
- Better sign commits than not. See: [git](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work), [GitHub](https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification).
|
|
||||||
|
|
||||||
## Code
|
|
||||||
|
|
||||||
### Warnings
|
|
||||||
Make sure that all committed code contains no compiler warnings. This specifically includes unused imports, unused private members, missing `@Override`s and warnings related to generics.
|
|
||||||
|
|
||||||
Warnings about unknown tokens in `@SuppressWarnings` are temporarily ignored. Please disable them in your IDE.
|
|
||||||
|
|
||||||
### Code Style
|
|
||||||
Formatting code is important.
|
|
||||||
|
|
||||||
- The format is specified within the files inside `/templates_and_presets/eclipse_ide`. Import the specifications into Eclipse or IntelliJ IDEA and use the IDEs' format feature. Alternatively format the code manually in accordance with existing files.
|
|
||||||
- Only use tabs for indentation. Never indent with spaces even when wrapping lines. This is to ensure that indentation does not break when tab width is different.
|
|
||||||
- Don't use `I` prefix for interfaces (not `IDoable` but `Doable`).
|
|
||||||
- Prioritize readability over compactness. Do not hesitate to use (very) long identifiers if they aid comprehension.
|
|
||||||
- Document all mathematics unless it is trivial, especially when using math notation for variable names.
|
|
||||||
- Use proper English when writing comments. Avoid boxes in comments. Use `//` for single-line comments.
|
|
@ -1,12 +0,0 @@
|
|||||||
# 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,10 +1,8 @@
|
|||||||
# Build Guide
|
# Build Guide
|
||||||
|
|
||||||
This document is a guide to building Progressia from source. For quick reference, see
|
This document is a guide to building Progressia from source.
|
||||||
[Build Script Reference](BuildScriptReference.md).
|
|
||||||
|
|
||||||
Compilation should be possible on all platforms that support JDK 8 or later, however, packaging scripts require
|
Compilation should be possible on all platforms that support JDK 8 or later, however, packaging scripts require Bash.
|
||||||
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.
|
||||||
|
|
||||||
@ -143,8 +141,8 @@ build.
|
|||||||
Some users might find the need to build for a specific set of platforms. Inclusion of GNU/Linux, Windows or MacOS
|
Some users might find the need to build for a specific set of platforms. Inclusion of GNU/Linux, Windows or MacOS
|
||||||
dependencies individually can be controlled with the following arguments to the `./gradlew build` command:
|
dependencies individually can be controlled with the following arguments to the `./gradlew build` command:
|
||||||
- `requestLinuxDependencies` requests that `natives-linux`, `natives-linux-arm32` and `natives-linux-arm64` binaries are included;
|
- `requestLinuxDependencies` requests that `natives-linux`, `natives-linux-arm32` and `natives-linux-arm64` binaries are included;
|
||||||
- `requestWindowsDependencies` requests that `natives-windows`, `natives-windows-arm64` and `natives-windows-x86` binaries are included;
|
- `requestWindowsDependencies` requests that `natives-windows` and `natives-windows-x86` binaries are included;
|
||||||
- `requestMacOSDependencies` requests that `natives-macos`, `natives-macos-arm64` binaries are included.
|
- `requestMacOSDependencies` requests that `natives-macos` binaries are included.
|
||||||
These requests can be applied in any combination. For example, the following command produces a build containing only
|
These requests can be applied in any combination. For example, the following command produces a build containing only
|
||||||
GNU/Linux and Windows natives:
|
GNU/Linux and Windows natives:
|
||||||
|
|
||||||
@ -152,46 +150,54 @@ 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 universal ZIP distribution, a Debian package and a Windows NSIS installer may be created automatically by the build
|
A Debian package and a Windows installer can be created automatically on systems that support Bash. These tasks are delegated
|
||||||
script.
|
by Gradle to `buildPackages.sh` in repository root. This script checks the environment and assembles the requested output; the
|
||||||
|
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 packageDeb
|
./gradlew packageDebian
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
||||||
`dpkg-deb`. Commands `dpkg-deb` must be available in system path in order to build the package.
|
`./buildPackages.sh debian`. Commands `dpkg-deb` and `fakeroot` must be available in system path in order to build the package.
|
||||||
|
|
||||||
### Creating a Windows installer
|
### Creating a Windows installer
|
||||||
|
|
||||||
A Windows NSIS installer can be created with the following Gradle task:
|
A Windows installer can be created with the following Gradle task:
|
||||||
|
|
||||||
```
|
```
|
||||||
./gradlew packageNsis
|
./gradlew packageWindows
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
||||||
`makensis`.
|
`./buildPackages.sh windows`.
|
||||||
|
|
||||||
Windows installers are implemented with [NSIS](https://nsis.sourceforge.io/). [ImageMagick](https://imagemagick.org),
|
Windows installers are implemented with [NSIS](https://nsis.sourceforge.io/). Command `makensis` must be available in system
|
||||||
a command-line image editing tool, is used to generate some assets for the installer. Commands `makensis` and
|
path in order to build the installer.
|
||||||
`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.
|
@ -1,112 +0,0 @@
|
|||||||
# 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`, `natives-windows-arm64` and `natives-windows-x86` binaries are included when building.
|
|
||||||
- `requestMacOSDependencies` – requests that `natives-macos` and `natives-macos-arm64` 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 is determined from `GIT_COMMIT` environment variable if it exists, or the state of the local Git
|
|
||||||
repository, if any, or `-`.
|
|
||||||
|
|
||||||
Git branch is determined from `GIT_BRANCH` environment variable if it exists, or the state of the local Git
|
|
||||||
repository, if any, or `-`.
|
|
||||||
|
|
||||||
The names of the environment variables are picked to assist Jenkins builds.
|
|
||||||
|
|
||||||
### Build ID
|
|
||||||
|
|
||||||
Build ID uniquely identifies artifacts produced by automated build systems. Build ID must be provided explicitly; it
|
|
||||||
is `-` unless specified otherwise.
|
|
||||||
|
|
||||||
The proposed scheme for naming build IDs is `<builder><build-system><build attempt no.>`. For example, builds
|
|
||||||
executed by WindCorp Jenkins suite have build IDs like `WJ3` or `WJ142`.
|
|
||||||
|
|
||||||
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-arm64` | `requestWindowsDependencies` |
|
|
||||||
| `windows-x86` | `requestWindowsDependencies` |
|
|
||||||
| `macos` | `requestMacOSDependencies` |
|
|
||||||
| `macos-arm64` | `requestMacOSDependencies` |
|
|
@ -42,16 +42,3 @@ Run configurations are used by Intellij IDEA to specify how a project must be ru
|
|||||||
9. Click 'Apply' to save changes.
|
9. Click 'Apply' to save changes.
|
||||||
|
|
||||||
Step 8 is required to specify that the game must run in some directory other than the project root, which is the default in Intellij IDEA.
|
Step 8 is required to specify that the game must run in some directory other than the project root, which is the default in Intellij IDEA.
|
||||||
|
|
||||||
### Applying formatting templates
|
|
||||||
|
|
||||||
Windcorp's Progressia repository is formatted with a style defined for Eclipse IDE (sic) in
|
|
||||||
`templates_and_presets/eclipse_ide`.
|
|
||||||
Please apply these templates to the project to automatically format the source in a similar fashion.
|
|
||||||
|
|
||||||
1. In project context menu, click 'File->Properties'. (`Ctrl+Alt+S`)
|
|
||||||
2. In 'Editor' > 'Code Style' > 'Java', press gear icon, then click 'Import Scheme' > 'Eclipse code style'
|
|
||||||
3. In Scheme select 'Project'
|
|
||||||
4. Open the file `templates_and_presets/eclipse_ide/FormatterProfile.xml` in 'Select Path'.
|
|
||||||
5. Inside 'Import Scheme' widow click 'Current Scheme' check box after press OK
|
|
||||||
|
|
||||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
259
gradlew
vendored
259
gradlew
vendored
@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright © 2015-2021 the original authors.
|
# Copyright 2015 the original author or authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -17,113 +17,78 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
##
|
||||||
# Gradle start up script for POSIX generated by Gradle.
|
## Gradle start up script for UN*X
|
||||||
#
|
##
|
||||||
# Important for running:
|
|
||||||
#
|
|
||||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
|
||||||
# noncompliant, but you have some other compliant shell such as ksh or
|
|
||||||
# bash, then to run this script, type that shell name before the whole
|
|
||||||
# command line, like:
|
|
||||||
#
|
|
||||||
# ksh Gradle
|
|
||||||
#
|
|
||||||
# Busybox and similar reduced shells will NOT work, because this script
|
|
||||||
# requires all of these POSIX shell features:
|
|
||||||
# * functions;
|
|
||||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
|
||||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
|
||||||
# * compound commands having a testable exit status, especially «case»;
|
|
||||||
# * various built-in commands including «command», «set», and «ulimit».
|
|
||||||
#
|
|
||||||
# Important for patching:
|
|
||||||
#
|
|
||||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
|
||||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
|
||||||
#
|
|
||||||
# The "traditional" practice of packing multiple parameters into a
|
|
||||||
# space-separated string is a well documented source of bugs and security
|
|
||||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
|
||||||
# options in "$@", and eventually passing that to Java.
|
|
||||||
#
|
|
||||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
|
||||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
|
||||||
# see the in-line comments for details.
|
|
||||||
#
|
|
||||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
|
||||||
# Darwin, MinGW, and NonStop.
|
|
||||||
#
|
|
||||||
# (3) This script is generated from the Groovy template
|
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
|
||||||
# within the Gradle project.
|
|
||||||
#
|
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
|
||||||
#
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
# Resolve links: $0 may be a link
|
||||||
app_path=$0
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
# Need this for daisy-chained symlinks.
|
while [ -h "$PRG" ] ; do
|
||||||
while
|
ls=`ls -ld "$PRG"`
|
||||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
[ -h "$app_path" ]
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
do
|
PRG="$link"
|
||||||
ls=$( ls -ld "$app_path" )
|
else
|
||||||
link=${ls#*' -> '}
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
case $link in #(
|
fi
|
||||||
/*) app_path=$link ;; #(
|
|
||||||
*) app_path=$APP_HOME$link ;;
|
|
||||||
esac
|
|
||||||
done
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
APP_NAME="Gradle"
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD="maximum"
|
||||||
|
|
||||||
warn () {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
} >&2
|
}
|
||||||
|
|
||||||
die () {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
exit 1
|
exit 1
|
||||||
} >&2
|
}
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
# OS specific support (must be 'true' or 'false').
|
||||||
cygwin=false
|
cygwin=false
|
||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "$( uname )" in #(
|
case "`uname`" in
|
||||||
CYGWIN* ) cygwin=true ;; #(
|
CYGWIN* )
|
||||||
Darwin* ) darwin=true ;; #(
|
cygwin=true
|
||||||
MSYS* | MINGW* ) msys=true ;; #(
|
;;
|
||||||
NONSTOP* ) nonstop=true ;;
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
else
|
else
|
||||||
JAVACMD=$JAVA_HOME/bin/java
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
fi
|
fi
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
@ -132,7 +97,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD="java"
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
@ -140,95 +105,79 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
case $MAX_FD in #(
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
max*)
|
if [ $? -eq 0 ] ; then
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
warn "Could not query maximum file descriptor limit"
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
esac
|
fi
|
||||||
case $MAX_FD in #(
|
ulimit -n $MAX_FD
|
||||||
'' | soft) :;; #(
|
if [ $? -ne 0 ] ; then
|
||||||
*)
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
ulimit -n "$MAX_FD" ||
|
fi
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
else
|
||||||
esac
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Collect all arguments for the java command, stacking in reverse order:
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
# * args from the command line
|
if $darwin; then
|
||||||
# * the main class name
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
# * -classpath
|
fi
|
||||||
# * -D...appname settings
|
|
||||||
# * --module-path (only if needed)
|
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if "$cygwin" || "$msys" ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
SEP=""
|
||||||
for arg do
|
for dir in $ROOTDIRSRAW ; do
|
||||||
if
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
case $arg in #(
|
SEP="|"
|
||||||
-*) false ;; # don't mess with options #(
|
|
||||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
|
||||||
[ -e "$t" ] ;; #(
|
|
||||||
*) false ;;
|
|
||||||
esac
|
|
||||||
then
|
|
||||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
|
||||||
fi
|
|
||||||
# Roll the args list around exactly as many times as the number of
|
|
||||||
# args, so each arg winds up back in the position where it started, but
|
|
||||||
# possibly modified.
|
|
||||||
#
|
|
||||||
# NB: a `for` loop captures its iteration list before it begins, so
|
|
||||||
# changing the positional parameters here affects neither the number of
|
|
||||||
# iterations, nor the values presented in `arg`.
|
|
||||||
shift # remove old arg
|
|
||||||
set -- "$@" "$arg" # push replacement arg
|
|
||||||
done
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=`expr $i + 1`
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
0) set -- ;;
|
||||||
|
1) set -- "$args0" ;;
|
||||||
|
2) set -- "$args0" "$args1" ;;
|
||||||
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
# Escape application args
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
save () {
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
# double quotes to make sure that they get re-expanded; and
|
echo " "
|
||||||
# * put everything else in single quotes, so that it's not re-expanded.
|
}
|
||||||
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
set -- \
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
-classpath "$CLASSPATH" \
|
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
|
||||||
"$@"
|
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
|
||||||
#
|
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
|
||||||
#
|
|
||||||
# In Bash we could simply go:
|
|
||||||
#
|
|
||||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
|
||||||
# set -- "${ARGS[@]}" "$@"
|
|
||||||
#
|
|
||||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
|
||||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
|
||||||
# character that might be a shell metacharacter, then use eval to reverse
|
|
||||||
# that process (while maintaining the separation between arguments), and wrap
|
|
||||||
# the whole thing up as a single "set" statement.
|
|
||||||
#
|
|
||||||
# This will of course break if any of these variables contains a newline or
|
|
||||||
# an unmatched quote.
|
|
||||||
#
|
|
||||||
|
|
||||||
eval "set -- $(
|
|
||||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
|
||||||
xargs -n1 |
|
|
||||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
|
||||||
tr '\n' ' '
|
|
||||||
)" '"$@"'
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
25
gradlew.bat
vendored
25
gradlew.bat
vendored
@ -29,9 +29,6 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
|||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
|
||||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@ -40,7 +37,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@ -54,7 +51,7 @@ goto fail
|
|||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
@ -64,14 +61,28 @@ echo location of your Java installation.
|
|||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
* JPUtil
|
|
||||||
* Copyright (C) 2019-2021 OLEGSHA/Javapony 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.jputil;
|
|
||||||
|
|
||||||
public class ConstantsMapException extends RuntimeException {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -4298704891780063127L;
|
|
||||||
|
|
||||||
public ConstantsMapException() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConstantsMapException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
|
||||||
super(message, cause, enableSuppression, writableStackTrace);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConstantsMapException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConstantsMapException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConstantsMapException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,307 +0,0 @@
|
|||||||
/*
|
|
||||||
* JPUtil
|
|
||||||
* Copyright (C) 2019-2022 OLEGSHA/Javapony 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.jputil;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.function.IntPredicate;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class IntConstantsMap {
|
|
||||||
|
|
||||||
private final Map<Integer, String> namesByValue;
|
|
||||||
private final Map<String, Integer> valuesByName;
|
|
||||||
|
|
||||||
protected IntConstantsMap(Map<Integer, String> namesByValue, Map<String, Integer> valuesByName) {
|
|
||||||
this.namesByValue = namesByValue;
|
|
||||||
this.valuesByName = valuesByName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getValue(String name) {
|
|
||||||
Integer value = valuesByName.get(name);
|
|
||||||
if (value == null) {
|
|
||||||
throw new NoSuchElementException("No constant with name " + name);
|
|
||||||
}
|
|
||||||
return value.intValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasConstant(String name) {
|
|
||||||
return valuesByName.containsKey(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName(int value) {
|
|
||||||
String name = namesByValue.get(value);
|
|
||||||
if (name == null) {
|
|
||||||
throw new NoSuchElementException("No constant with value " + value);
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasConstant(int value) {
|
|
||||||
return namesByValue.containsKey(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Integer> getAll() {
|
|
||||||
return valuesByName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return valuesByName.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder from(Class<?> clazz) {
|
|
||||||
return new Builder(clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public static interface Filter {
|
|
||||||
boolean test(String name, int value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ConstantSpec {
|
|
||||||
public String name;
|
|
||||||
public int value;
|
|
||||||
|
|
||||||
public void drop() {
|
|
||||||
if (!extra.contains(name)) {
|
|
||||||
name = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final List<Consumer<ConstantSpec>> transforms = new ArrayList<>();
|
|
||||||
private final Set<String> extra = new HashSet<>();
|
|
||||||
|
|
||||||
private final Class<?> source;
|
|
||||||
|
|
||||||
public Builder(Class<?> source) {
|
|
||||||
this.source = source;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder apply(Consumer<ConstantSpec> transform) {
|
|
||||||
transforms.add(transform);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder only(Filter filter) {
|
|
||||||
return apply(s -> {
|
|
||||||
if (!filter.test(s.name, s.value)) {
|
|
||||||
s.drop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder only(Predicate<String> nameFilter) {
|
|
||||||
return apply(s -> {
|
|
||||||
if (!nameFilter.test(s.name)) {
|
|
||||||
s.drop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder onlyValued(IntPredicate valueFilter) {
|
|
||||||
return apply(s -> {
|
|
||||||
if (!valueFilter.test(s.value)) {
|
|
||||||
s.drop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder regex(String regex) {
|
|
||||||
return only(Pattern.compile(regex).asPredicate());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder prefix(String prefix) {
|
|
||||||
return only(n -> n.startsWith(prefix) && n.length() > prefix.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder exclude(Filter filter) {
|
|
||||||
return only((n, v) -> !filter.test(n, v));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder exclude(Predicate<String> nameFilter) {
|
|
||||||
return only(nameFilter.negate());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder exclude(String... names) {
|
|
||||||
Set<String> excluded = new HashSet<>();
|
|
||||||
for (String name : names) {
|
|
||||||
excluded.add(name);
|
|
||||||
}
|
|
||||||
return exclude(excluded::contains);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder excludeRegex(String... nameRegexes) {
|
|
||||||
List<Predicate<String>> tests = new ArrayList<>();
|
|
||||||
for (String regex : nameRegexes) {
|
|
||||||
tests.add(Pattern.compile(regex).asPredicate());
|
|
||||||
}
|
|
||||||
return only((n, v) -> {
|
|
||||||
for (Predicate<String> test : tests) {
|
|
||||||
if (test.test(n)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder extra(String... names) {
|
|
||||||
for (String name : names) {
|
|
||||||
extra.add(name);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder rename(Function<String, String> renamer) {
|
|
||||||
apply(s -> {
|
|
||||||
s.name = renamer.apply(s.name);
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder stripPrefix(String prefix) {
|
|
||||||
return apply(s -> {
|
|
||||||
if (s.name.startsWith(prefix)) {
|
|
||||||
s.name = s.name.substring(prefix.length());
|
|
||||||
} else if (extra.contains(s.name)) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
s.drop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public IntConstantsMap scan() {
|
|
||||||
return build(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IntConstantsMap scanAll() {
|
|
||||||
return build(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntConstantsMap build(boolean onlyPublic) {
|
|
||||||
Map<Integer, String> namesByValue = new HashMap<>();
|
|
||||||
Map<String, Integer> valuesByName = new HashMap<>();
|
|
||||||
|
|
||||||
BiConsumer<String, Integer> putter = (name, value) -> {
|
|
||||||
if (namesByValue.containsKey(value)) {
|
|
||||||
throw newDuplicateException("value", value, name, namesByValue.get(value));
|
|
||||||
}
|
|
||||||
if (valuesByName.containsKey(name)) {
|
|
||||||
throw newDuplicateException("name", name, value, valuesByName.get(name));
|
|
||||||
}
|
|
||||||
namesByValue.put(value, name);
|
|
||||||
valuesByName.put(name, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (Field field : source.getDeclaredFields()) {
|
|
||||||
processField(field, putter, onlyPublic);
|
|
||||||
}
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new ConstantsMapException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IntConstantsMap(
|
|
||||||
Collections.unmodifiableMap(namesByValue),
|
|
||||||
Collections.unmodifiableMap(valuesByName)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processField(Field field, BiConsumer<String, Integer> putter, boolean onlyPublic)
|
|
||||||
throws IllegalAccessException {
|
|
||||||
if (!Modifier.isStatic(field.getModifiers())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!Modifier.isFinal(field.getModifiers())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean clearAccessible = false;
|
|
||||||
if (!Modifier.isPublic(field.getModifiers())) {
|
|
||||||
if (onlyPublic) {
|
|
||||||
return;
|
|
||||||
} else if (!isAccessibleFlagSet(field)) {
|
|
||||||
field.setAccessible(true);
|
|
||||||
clearAccessible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
ConstantSpec spec = new ConstantSpec();
|
|
||||||
spec.name = field.getName();
|
|
||||||
spec.value = field.getInt(null);
|
|
||||||
|
|
||||||
for (Consumer<ConstantSpec> t : transforms) {
|
|
||||||
t.accept(spec);
|
|
||||||
if (spec.name == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
putter.accept(spec.name, spec.value);
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
if (clearAccessible) {
|
|
||||||
field.setAccessible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Yes, this method exists only so that neither Java 8 nor Java 9 complain about deprecation.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
private boolean isAccessibleFlagSet(Field f) {
|
|
||||||
return f.isAccessible();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ConstantsMapException newDuplicateException(String what, Object common, Object current, Object old) {
|
|
||||||
return new ConstantsMapException(
|
|
||||||
String.format(
|
|
||||||
"Duplicate %1$s: %2$s -> %3$s and %2$s -> %4$s",
|
|
||||||
what,
|
|
||||||
common,
|
|
||||||
current,
|
|
||||||
old
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -18,177 +18,6 @@
|
|||||||
|
|
||||||
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,44 +18,28 @@
|
|||||||
|
|
||||||
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.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;
|
||||||
import ru.windcorp.progressia.common.util.crash.providers.*;
|
import ru.windcorp.progressia.common.util.crash.providers.*;
|
||||||
import ru.windcorp.progressia.test.LayerTitle;
|
|
||||||
|
|
||||||
public class ProgressiaLauncher {
|
public class ProgressiaLauncher {
|
||||||
|
|
||||||
public static String[] arguments;
|
public static String[] arguments;
|
||||||
private static Proxy proxy;
|
|
||||||
|
|
||||||
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;
|
|
||||||
GUI.addTopLayer(new LayerTitle("Title"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Proxy getProxy() {
|
|
||||||
return proxy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
||||||
CrashReports.registerProvider(new OpenALContextProvider());
|
CrashReports.registerProvider(new OpenALContextProvider());
|
||||||
CrashReports.registerProvider(new ArgsContextProvider());
|
CrashReports.registerProvider(new ArgsContextProvider());
|
||||||
CrashReports.registerProvider(new LanguageContextProvider());
|
CrashReports.registerProvider(new LanguageContextProvider());
|
||||||
CrashReports.registerProvider(new ScreenContextProvider());
|
|
||||||
// Analyzers
|
// Analyzers
|
||||||
CrashReports.registerAnalyzer(new OutOfMemoryAnalyzer());
|
CrashReports.registerAnalyzer(new OutOfMemoryAnalyzer());
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import ru.windcorp.progressia.client.graphics.world.Camera;
|
|||||||
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
|
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
|
||||||
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
|
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
|
||||||
import ru.windcorp.progressia.client.world.WorldRender;
|
import ru.windcorp.progressia.client.world.WorldRender;
|
||||||
import ru.windcorp.progressia.common.world.DefaultWorldData;
|
import ru.windcorp.progressia.common.world.WorldData;
|
||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
|
|
||||||
public class Client {
|
public class Client {
|
||||||
@ -36,7 +36,7 @@ public class Client {
|
|||||||
|
|
||||||
private final ServerCommsChannel comms;
|
private final ServerCommsChannel comms;
|
||||||
|
|
||||||
public Client(DefaultWorldData world, ServerCommsChannel comms) {
|
public Client(WorldData world, ServerCommsChannel comms) {
|
||||||
this.world = new WorldRender(world, this);
|
this.world = new WorldRender(world, this);
|
||||||
this.comms = comms;
|
this.comms = comms;
|
||||||
|
|
||||||
|
@ -25,22 +25,19 @@ import ru.windcorp.progressia.client.graphics.backend.RenderTaskQueue;
|
|||||||
import ru.windcorp.progressia.client.graphics.flat.FlatRenderProgram;
|
import ru.windcorp.progressia.client.graphics.flat.FlatRenderProgram;
|
||||||
import ru.windcorp.progressia.client.graphics.font.GNUUnifontLoader;
|
import ru.windcorp.progressia.client.graphics.font.GNUUnifontLoader;
|
||||||
import ru.windcorp.progressia.client.graphics.font.Typefaces;
|
import ru.windcorp.progressia.client.graphics.font.Typefaces;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.ColorScheme;
|
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Atlases;
|
import ru.windcorp.progressia.client.graphics.texture.Atlases;
|
||||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||||
import ru.windcorp.progressia.client.localization.Localizer;
|
import ru.windcorp.progressia.client.localization.Localizer;
|
||||||
import ru.windcorp.progressia.common.resource.ResourceManager;
|
import ru.windcorp.progressia.common.resource.ResourceManager;
|
||||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||||
|
import ru.windcorp.progressia.server.ServerState;
|
||||||
import ru.windcorp.progressia.test.TestContent;
|
import ru.windcorp.progressia.test.TestContent;
|
||||||
import ru.windcorp.progressia.test.TestMusicPlayer;
|
|
||||||
|
|
||||||
public class ClientProxy implements Proxy {
|
public class ClientProxy implements Proxy {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
|
||||||
GraphicsBackend.initialize();
|
GraphicsBackend.initialize();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
RenderTaskQueue.waitAndInvoke(FlatRenderProgram::init);
|
RenderTaskQueue.waitAndInvoke(FlatRenderProgram::init);
|
||||||
RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init);
|
RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init);
|
||||||
@ -52,7 +49,6 @@ public class ClientProxy implements Proxy {
|
|||||||
throw CrashReports.report(e, "ClientProxy failed");
|
throw CrashReports.report(e, "ClientProxy failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
ColorScheme.load(ResourceManager.getResource("assets/default.colorScheme"));
|
|
||||||
Localizer.getInstance().setLanguage("en-US");
|
Localizer.getInstance().setLanguage("en-US");
|
||||||
|
|
||||||
TestContent.registerContent();
|
TestContent.registerContent();
|
||||||
@ -61,6 +57,8 @@ public class ClientProxy implements Proxy {
|
|||||||
|
|
||||||
AudioSystem.initialize();
|
AudioSystem.initialize();
|
||||||
|
|
||||||
TestMusicPlayer.start();
|
ServerState.startServer();
|
||||||
|
ClientState.connectToLocalServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,10 @@ package ru.windcorp.progressia.client;
|
|||||||
|
|
||||||
import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel;
|
import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel;
|
||||||
import ru.windcorp.progressia.client.graphics.GUI;
|
import ru.windcorp.progressia.client.graphics.GUI;
|
||||||
import ru.windcorp.progressia.client.graphics.Layer;
|
|
||||||
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
|
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
|
||||||
import ru.windcorp.progressia.common.world.DefaultWorldData;
|
import ru.windcorp.progressia.common.world.WorldData;
|
||||||
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
|
||||||
import ru.windcorp.progressia.server.ServerState;
|
import ru.windcorp.progressia.server.ServerState;
|
||||||
import ru.windcorp.progressia.test.LayerAbout;
|
import ru.windcorp.progressia.test.LayerAbout;
|
||||||
import ru.windcorp.progressia.test.LayerTestText;
|
|
||||||
import ru.windcorp.progressia.test.LayerTestUI;
|
import ru.windcorp.progressia.test.LayerTestUI;
|
||||||
import ru.windcorp.progressia.test.TestContent;
|
import ru.windcorp.progressia.test.TestContent;
|
||||||
|
|
||||||
@ -44,7 +41,7 @@ public class ClientState {
|
|||||||
|
|
||||||
public static void connectToLocalServer() {
|
public static void connectToLocalServer() {
|
||||||
|
|
||||||
DefaultWorldData world = new DefaultWorldData();
|
WorldData world = new WorldData();
|
||||||
|
|
||||||
LocalServerCommsChannel channel = new LocalServerCommsChannel(
|
LocalServerCommsChannel channel = new LocalServerCommsChannel(
|
||||||
ServerState.getInstance()
|
ServerState.getInstance()
|
||||||
@ -55,39 +52,11 @@ public class ClientState {
|
|||||||
channel.connect(TestContent.PLAYER_LOGIN);
|
channel.connect(TestContent.PLAYER_LOGIN);
|
||||||
|
|
||||||
setInstance(client);
|
setInstance(client);
|
||||||
displayLoadingScreen();
|
|
||||||
|
|
||||||
}
|
GUI.addBottomLayer(new LayerWorld(client));
|
||||||
|
GUI.addTopLayer(new LayerTestUI());
|
||||||
|
GUI.addTopLayer(new LayerAbout());
|
||||||
|
|
||||||
private static void displayLoadingScreen() {
|
|
||||||
GUI.addTopLayer(new LayerTestText("Text", new MutableStringLocalized("LayerText.Load"), layer -> {
|
|
||||||
Client client = ClientState.getInstance();
|
|
||||||
|
|
||||||
// TODO refacetor and remove
|
|
||||||
if (client != null) {
|
|
||||||
client.getComms().processPackets();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client != null && client.getLocalPlayer().hasEntity()) {
|
|
||||||
GUI.removeLayer(layer);
|
|
||||||
|
|
||||||
// TODO refactor, this shouldn't be here
|
|
||||||
LayerWorld layerWorld = new LayerWorld(client);
|
|
||||||
LayerTestUI layerUI = new LayerTestUI();
|
|
||||||
LayerAbout layerAbout = new LayerAbout();
|
|
||||||
GUI.addBottomLayer(layerWorld);
|
|
||||||
GUI.addTopLayer(layerUI);
|
|
||||||
GUI.addTopLayer(layerAbout);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void disconnectFromLocalServer() {
|
|
||||||
getInstance().getComms().disconnect();
|
|
||||||
|
|
||||||
for (Layer layer : GUI.getLayers()) {
|
|
||||||
GUI.removeLayer(layer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientState() {
|
private ClientState() {
|
||||||
|
@ -23,7 +23,6 @@ import ru.windcorp.progressia.client.audio.backend.AudioReader;
|
|||||||
import ru.windcorp.progressia.client.audio.backend.Listener;
|
import ru.windcorp.progressia.client.audio.backend.Listener;
|
||||||
import ru.windcorp.progressia.client.audio.backend.SoundType;
|
import ru.windcorp.progressia.client.audio.backend.SoundType;
|
||||||
import ru.windcorp.progressia.client.audio.backend.Speaker;
|
import ru.windcorp.progressia.client.audio.backend.Speaker;
|
||||||
import ru.windcorp.progressia.common.resource.Resource;
|
|
||||||
|
|
||||||
import static org.lwjgl.openal.AL11.*;
|
import static org.lwjgl.openal.AL11.*;
|
||||||
import static org.lwjgl.openal.ALC10.*;
|
import static org.lwjgl.openal.ALC10.*;
|
||||||
@ -41,6 +40,7 @@ public class AudioManager {
|
|||||||
|
|
||||||
private static List<Speaker> soundSpeakers = new ArrayList<>(SOUNDS_NUM);
|
private static List<Speaker> soundSpeakers = new ArrayList<>(SOUNDS_NUM);
|
||||||
private static Speaker musicSpeaker;
|
private static Speaker musicSpeaker;
|
||||||
|
private static ArrayList<SoundType> soundsBuffer = new ArrayList<>();
|
||||||
|
|
||||||
public static void initAL() {
|
public static void initAL() {
|
||||||
String defaultDeviceName = alcGetString(
|
String defaultDeviceName = alcGetString(
|
||||||
@ -82,19 +82,31 @@ public class AudioManager {
|
|||||||
return speaker;
|
return speaker;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Speaker initSpeaker(SoundType st) {
|
private static SoundType findSoundType(String soundID) throws Exception {
|
||||||
|
for (SoundType s : soundsBuffer) {
|
||||||
|
if (s.getId().equals(soundID)) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Exception(
|
||||||
|
"ERROR: The selected sound is not loaded or" +
|
||||||
|
" not exists"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Speaker initSpeaker(String soundID) {
|
||||||
Speaker speaker = getLastSpeaker();
|
Speaker speaker = getLastSpeaker();
|
||||||
try {
|
try {
|
||||||
st.initSpeaker(speaker);
|
findSoundType(soundID).initSpeaker(speaker);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
}
|
}
|
||||||
return speaker;
|
return speaker;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Speaker initMusicSpeaker(SoundType st) {
|
public static Speaker initMusicSpeaker(String soundID) {
|
||||||
try {
|
try {
|
||||||
st.initSpeaker(musicSpeaker);
|
findSoundType(soundID).initSpeaker(musicSpeaker);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
}
|
}
|
||||||
@ -108,11 +120,11 @@ public class AudioManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void loadSound(Resource resource, String id, AudioFormat format) {
|
public static void loadSound(String path, String id, AudioFormat format) {
|
||||||
if (format == AudioFormat.MONO) {
|
if (format == AudioFormat.MONO) {
|
||||||
AudioRegistry.getInstance().register(AudioReader.readAsMono(resource, id));
|
soundsBuffer.add(AudioReader.readAsMono(path, id));
|
||||||
} else {
|
} else {
|
||||||
AudioRegistry.getInstance().register(AudioReader.readAsStereo(resource, id));
|
soundsBuffer.add(AudioReader.readAsStereo(path, id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,34 +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.client.audio;
|
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.audio.backend.SoundType;
|
|
||||||
import ru.windcorp.progressia.common.util.namespaces.NamespacedInstanceRegistry;
|
|
||||||
|
|
||||||
public class AudioRegistry extends NamespacedInstanceRegistry<SoundType> {
|
|
||||||
|
|
||||||
private static final AudioRegistry INSTANCE = new AudioRegistry();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the instance
|
|
||||||
*/
|
|
||||||
public static AudioRegistry getInstance() {
|
|
||||||
return INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -18,8 +18,6 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.audio;
|
package ru.windcorp.progressia.client.audio;
|
||||||
|
|
||||||
import ru.windcorp.progressia.common.resource.ResourceManager;
|
|
||||||
|
|
||||||
public class AudioSystem {
|
public class AudioSystem {
|
||||||
static public void initialize() {
|
static public void initialize() {
|
||||||
AudioManager.initAL();
|
AudioManager.initAL();
|
||||||
@ -30,7 +28,7 @@ public class AudioSystem {
|
|||||||
|
|
||||||
static void loadAudioData() {
|
static void loadAudioData() {
|
||||||
AudioManager.loadSound(
|
AudioManager.loadSound(
|
||||||
ResourceManager.getResource("assets/sounds/block_destroy_clap.ogg"),
|
"assets/sounds/block_destroy_clap.ogg",
|
||||||
"Progressia:BlockDestroy",
|
"Progressia:BlockDestroy",
|
||||||
AudioFormat.MONO
|
AudioFormat.MONO
|
||||||
);
|
);
|
||||||
|
@ -19,37 +19,72 @@
|
|||||||
package ru.windcorp.progressia.client.audio;
|
package ru.windcorp.progressia.client.audio;
|
||||||
|
|
||||||
import glm.vec._3.Vec3;
|
import glm.vec._3.Vec3;
|
||||||
import ru.windcorp.progressia.client.audio.backend.SoundType;
|
|
||||||
import ru.windcorp.progressia.client.audio.backend.Speaker;
|
import ru.windcorp.progressia.client.audio.backend.Speaker;
|
||||||
|
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
||||||
|
|
||||||
public class Music
|
public class Music extends Namespaced {
|
||||||
extends Sound {
|
private Vec3 position = new Vec3();
|
||||||
|
private Vec3 velocity = new Vec3();
|
||||||
|
private float pitch = 1.0f;
|
||||||
|
private float gain = 1.0f;
|
||||||
public Music(SoundType soundType, int timeLength, float pitch, float gain) {
|
|
||||||
super(soundType, timeLength, new Vec3(), new Vec3(), pitch, gain);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Music(SoundType soundType) {
|
|
||||||
super(soundType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Music(String id, int timeLength, float pitch, float gain) {
|
|
||||||
super(id, timeLength, new Vec3(), new Vec3(), pitch, gain);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Music(String id) {
|
public Music(String id) {
|
||||||
super(id);
|
super(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public Music(
|
||||||
protected Speaker initSpeaker() {
|
String id,
|
||||||
return AudioManager.initMusicSpeaker(soundType);
|
Vec3 position,
|
||||||
|
Vec3 velocity,
|
||||||
|
float pitch,
|
||||||
|
float gain
|
||||||
|
) {
|
||||||
|
this(id);
|
||||||
|
this.position = position;
|
||||||
|
this.velocity = velocity;
|
||||||
|
this.pitch = pitch;
|
||||||
|
this.gain = gain;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void play(boolean loop) {
|
||||||
public void setPosition(Vec3 position) {
|
Speaker speaker = AudioManager.initMusicSpeaker(this.getId());
|
||||||
throw new UnsupportedOperationException();
|
speaker.setGain(gain);
|
||||||
|
speaker.setPitch(pitch);
|
||||||
|
speaker.setPosition(position);
|
||||||
|
speaker.setVelocity(velocity);
|
||||||
|
|
||||||
|
if (loop) {
|
||||||
|
speaker.playLoop();
|
||||||
|
} else {
|
||||||
|
speaker.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGain(float gain) {
|
||||||
|
this.gain = gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPitch(float pitch) {
|
||||||
|
this.pitch = pitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVelocity(Vec3 velocity) {
|
||||||
|
this.velocity = velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vec3 getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getGain() {
|
||||||
|
return gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vec3 getVelocity() {
|
||||||
|
return velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getPitch() {
|
||||||
|
return pitch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,30 +19,23 @@
|
|||||||
package ru.windcorp.progressia.client.audio;
|
package ru.windcorp.progressia.client.audio;
|
||||||
|
|
||||||
import glm.vec._3.Vec3;
|
import glm.vec._3.Vec3;
|
||||||
import ru.windcorp.progressia.client.audio.backend.SoundType;
|
|
||||||
import ru.windcorp.progressia.client.audio.backend.Speaker;
|
import ru.windcorp.progressia.client.audio.backend.Speaker;
|
||||||
|
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
||||||
|
|
||||||
public class Sound {
|
public class SoundEffect
|
||||||
|
extends Namespaced {
|
||||||
|
|
||||||
protected Vec3 position = new Vec3(0f, 0f, 0f);
|
private Vec3 position = new Vec3();
|
||||||
protected Vec3 velocity = new Vec3(0f, 0f, 0f);
|
private Vec3 velocity = new Vec3();
|
||||||
protected float pitch = 1.0f;
|
private float pitch = 1.0f;
|
||||||
protected float gain = 1.0f;
|
private float gain = 1.0f;
|
||||||
protected int timeLength = 0;
|
|
||||||
|
|
||||||
protected SoundType soundType;
|
public SoundEffect(String id) {
|
||||||
|
super(id);
|
||||||
public Sound(SoundType soundType) {
|
|
||||||
this.soundType = soundType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Sound(String id) {
|
public SoundEffect(
|
||||||
this(AudioRegistry.getInstance().get(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Sound(
|
|
||||||
String id,
|
String id,
|
||||||
int timeLength,
|
|
||||||
Vec3 position,
|
Vec3 position,
|
||||||
Vec3 velocity,
|
Vec3 velocity,
|
||||||
float pitch,
|
float pitch,
|
||||||
@ -55,27 +48,8 @@ public class Sound {
|
|||||||
this.gain = gain;
|
this.gain = gain;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Sound(
|
|
||||||
SoundType soundType,
|
|
||||||
int timeLength,
|
|
||||||
Vec3 position,
|
|
||||||
Vec3 velocity,
|
|
||||||
float pitch,
|
|
||||||
float gain
|
|
||||||
) {
|
|
||||||
this(soundType);
|
|
||||||
this.position = position;
|
|
||||||
this.velocity = velocity;
|
|
||||||
this.pitch = pitch;
|
|
||||||
this.gain = gain;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Speaker initSpeaker() {
|
|
||||||
return AudioManager.initSpeaker(soundType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void play(boolean loop) {
|
public void play(boolean loop) {
|
||||||
Speaker speaker = initSpeaker();
|
Speaker speaker = AudioManager.initSpeaker(this.getId());
|
||||||
speaker.setGain(gain);
|
speaker.setGain(gain);
|
||||||
speaker.setPitch(pitch);
|
speaker.setPitch(pitch);
|
||||||
speaker.setPosition(position);
|
speaker.setPosition(position);
|
||||||
@ -119,9 +93,4 @@ public class Sound {
|
|||||||
public float getPitch() {
|
public float getPitch() {
|
||||||
return pitch;
|
return pitch;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getDuration() {
|
|
||||||
return soundType.getDuration();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -33,11 +33,13 @@ public class AudioReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO fix converting from mono-stereo
|
// TODO fix converting from mono-stereo
|
||||||
private static SoundType readAsSpecified(Resource resource, String id, int format) {
|
private static SoundType readAsSpecified(String path, String id, int format) {
|
||||||
IntBuffer channelBuffer = BufferUtils.createIntBuffer(1);
|
IntBuffer channelBuffer = BufferUtils.createIntBuffer(1);
|
||||||
IntBuffer rateBuffer = BufferUtils.createIntBuffer(1);
|
IntBuffer rateBuffer = BufferUtils.createIntBuffer(1);
|
||||||
|
|
||||||
ShortBuffer rawAudio = decodeVorbis(resource, channelBuffer, rateBuffer);
|
Resource res = ResourceManager.getResource(path);
|
||||||
|
|
||||||
|
ShortBuffer rawAudio = decodeVorbis(res, channelBuffer, rateBuffer);
|
||||||
|
|
||||||
return new SoundType(
|
return new SoundType(
|
||||||
id,
|
id,
|
||||||
@ -47,12 +49,12 @@ public class AudioReader {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SoundType readAsMono(Resource resource, String id) {
|
public static SoundType readAsMono(String path, String id) {
|
||||||
return readAsSpecified(resource, id, AL_FORMAT_MONO16);
|
return readAsSpecified(path, id, AL_FORMAT_MONO16);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SoundType readAsStereo(Resource resource, String id) {
|
public static SoundType readAsStereo(String path, String id) {
|
||||||
return readAsSpecified(resource, id, AL_FORMAT_STEREO16);
|
return readAsSpecified(path, id, AL_FORMAT_STEREO16);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ShortBuffer decodeVorbis(
|
private static ShortBuffer decodeVorbis(
|
||||||
|
@ -21,9 +21,6 @@ package ru.windcorp.progressia.client.audio.backend;
|
|||||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
||||||
|
|
||||||
import java.nio.ShortBuffer;
|
import java.nio.ShortBuffer;
|
||||||
|
|
||||||
import org.lwjgl.openal.AL10;
|
|
||||||
|
|
||||||
import static org.lwjgl.openal.AL11.*;
|
import static org.lwjgl.openal.AL11.*;
|
||||||
|
|
||||||
public class SoundType extends Namespaced {
|
public class SoundType extends Namespaced {
|
||||||
@ -32,7 +29,6 @@ public class SoundType extends Namespaced {
|
|||||||
private int sampleRate;
|
private int sampleRate;
|
||||||
private int format;
|
private int format;
|
||||||
private int audioBuffer;
|
private int audioBuffer;
|
||||||
private double duration;
|
|
||||||
|
|
||||||
public SoundType(
|
public SoundType(
|
||||||
String id,
|
String id,
|
||||||
@ -50,14 +46,9 @@ public class SoundType extends Namespaced {
|
|||||||
private void createAudioBuffer() {
|
private void createAudioBuffer() {
|
||||||
this.audioBuffer = alGenBuffers();
|
this.audioBuffer = alGenBuffers();
|
||||||
alBufferData(audioBuffer, format, rawAudio, sampleRate);
|
alBufferData(audioBuffer, format, rawAudio, sampleRate);
|
||||||
duration = rawAudio.limit() / (double) sampleRate / (format == AL10.AL_FORMAT_STEREO16 ? 2 : 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initSpeaker(Speaker speaker) {
|
public void initSpeaker(Speaker speaker) {
|
||||||
speaker.setAudioData(audioBuffer);
|
speaker.setAudioData(audioBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getDuration() {
|
|
||||||
return duration;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -120,7 +120,6 @@ public class Speaker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setAudioData(int audioData) {
|
public void setAudioData(int audioData) {
|
||||||
stop();
|
|
||||||
this.audioData = audioData;
|
this.audioData = audioData;
|
||||||
alSourcei(this.sourceData, AL_BUFFER, audioData);
|
alSourcei(this.sourceData, AL_BUFFER, audioData);
|
||||||
}
|
}
|
||||||
|
@ -143,82 +143,6 @@ public class ControlTriggers {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ControlTriggerInputBased localOf(
|
|
||||||
String id,
|
|
||||||
Consumer<InputEvent> action,
|
|
||||||
Predicate<InputEvent> predicate
|
|
||||||
) {
|
|
||||||
return new ControlTriggerLocalLambda(id, predicate, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ControlTriggerInputBased localOf(
|
|
||||||
String id,
|
|
||||||
Runnable action,
|
|
||||||
Predicate<InputEvent> predicate
|
|
||||||
) {
|
|
||||||
return localOf(
|
|
||||||
id,
|
|
||||||
input -> action.run(),
|
|
||||||
predicate
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SafeVarargs
|
|
||||||
public static <I extends InputEvent> ControlTriggerInputBased localOf(
|
|
||||||
String id,
|
|
||||||
Class<I> inputType,
|
|
||||||
Consumer<I> action,
|
|
||||||
Predicate<I>... predicates
|
|
||||||
) {
|
|
||||||
return localOf(
|
|
||||||
id,
|
|
||||||
createCheckedAction(inputType, action),
|
|
||||||
createCheckedCompoundPredicate(inputType, predicates)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SafeVarargs
|
|
||||||
public static <I extends InputEvent> ControlTriggerInputBased localOf(
|
|
||||||
String id,
|
|
||||||
Class<I> inputType,
|
|
||||||
Runnable action,
|
|
||||||
Predicate<I>... predicates
|
|
||||||
) {
|
|
||||||
return localOf(
|
|
||||||
id,
|
|
||||||
inputType,
|
|
||||||
input -> action.run(),
|
|
||||||
predicates
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SafeVarargs
|
|
||||||
public static ControlTriggerInputBased localOf(
|
|
||||||
String id,
|
|
||||||
Consumer<InputEvent> action,
|
|
||||||
Predicate<InputEvent>... predicates
|
|
||||||
) {
|
|
||||||
return localOf(
|
|
||||||
id,
|
|
||||||
InputEvent.class,
|
|
||||||
action,
|
|
||||||
predicates
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SafeVarargs
|
|
||||||
public static <I extends InputEvent> ControlTriggerInputBased localOf(
|
|
||||||
String id,
|
|
||||||
Runnable action,
|
|
||||||
Predicate<InputEvent>... predicates
|
|
||||||
) {
|
|
||||||
return of(
|
|
||||||
id,
|
|
||||||
input -> action.run(),
|
|
||||||
predicates
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <I extends InputEvent> BiConsumer<InputEvent, ControlData> createCheckedDataWriter(
|
private static <I extends InputEvent> BiConsumer<InputEvent, ControlData> createCheckedDataWriter(
|
||||||
Class<I> inputType,
|
Class<I> inputType,
|
||||||
BiConsumer<I, ControlData> dataWriter
|
BiConsumer<I, ControlData> dataWriter
|
||||||
@ -226,13 +150,6 @@ public class ControlTriggers {
|
|||||||
return (inputEvent, control) -> dataWriter.accept(inputType.cast(inputEvent), control);
|
return (inputEvent, control) -> dataWriter.accept(inputType.cast(inputEvent), control);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <I extends InputEvent> Consumer<InputEvent> createCheckedAction(
|
|
||||||
Class<I> inputType,
|
|
||||||
Consumer<I> action
|
|
||||||
) {
|
|
||||||
return inputEvent -> action.accept(inputType.cast(inputEvent));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <I extends InputEvent> Predicate<InputEvent> createCheckedCompoundPredicate(
|
private static <I extends InputEvent> Predicate<InputEvent> createCheckedCompoundPredicate(
|
||||||
Class<I> inputType,
|
Class<I> inputType,
|
||||||
Predicate<I>[] predicates
|
Predicate<I>[] predicates
|
||||||
|
@ -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.InputEvent;
|
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||||
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(InputEvent event) {
|
public void handleInput(Input input) {
|
||||||
for (ControlTriggerInputBased c : controls) {
|
for (ControlTriggerInputBased c : controls) {
|
||||||
Packet packet = c.onInputEvent(event);
|
Packet packet = c.onInputEvent(input.getEvent());
|
||||||
|
|
||||||
if (packet != null) {
|
if (packet != null) {
|
||||||
event.consume();
|
input.consume();
|
||||||
client.getComms().sendPacket(packet);
|
client.getComms().sendPacket(packet);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ package ru.windcorp.progressia.client.comms.localhost;
|
|||||||
import ru.windcorp.progressia.client.comms.ServerCommsChannel;
|
import ru.windcorp.progressia.client.comms.ServerCommsChannel;
|
||||||
import ru.windcorp.progressia.common.comms.packets.Packet;
|
import ru.windcorp.progressia.common.comms.packets.Packet;
|
||||||
import ru.windcorp.progressia.server.Server;
|
import ru.windcorp.progressia.server.Server;
|
||||||
import ru.windcorp.progressia.server.ServerState;
|
|
||||||
|
|
||||||
public class LocalServerCommsChannel extends ServerCommsChannel {
|
public class LocalServerCommsChannel extends ServerCommsChannel {
|
||||||
|
|
||||||
@ -55,7 +54,7 @@ public class LocalServerCommsChannel extends ServerCommsChannel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disconnect() {
|
public void disconnect() {
|
||||||
ServerState.getInstance().getClientManager().disconnectClient(localClient);
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,15 @@ package ru.windcorp.progressia.client.graphics;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
@ -39,31 +44,28 @@ 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() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void addBottomLayer(Layer layer) {
|
public static void addBottomLayer(Layer layer) {
|
||||||
Objects.requireNonNull(layer, "layer");
|
modify(layers -> layers.add(layer));
|
||||||
modify(layers -> {
|
|
||||||
layers.add(layer);
|
|
||||||
layer.onAdded();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void addTopLayer(Layer layer) {
|
public static void addTopLayer(Layer layer) {
|
||||||
Objects.requireNonNull(layer, "layer");
|
modify(layers -> layers.add(0, layer));
|
||||||
modify(layers -> {
|
|
||||||
layers.add(0, layer);
|
|
||||||
layer.onAdded();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void removeLayer(Layer layer) {
|
public static void removeLayer(Layer layer) {
|
||||||
Objects.requireNonNull(layer, "layer");
|
modify(layers -> layers.remove(layer));
|
||||||
modify(layers -> {
|
|
||||||
layers.remove(layer);
|
|
||||||
layer.onRemoved();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void modify(LayerStackModification mod) {
|
private static void modify(LayerStackModification mod) {
|
||||||
@ -76,33 +78,12 @@ public class GUI {
|
|||||||
|
|
||||||
public static void render() {
|
public static void render() {
|
||||||
synchronized (LAYERS) {
|
synchronized (LAYERS) {
|
||||||
|
MODIFICATION_QUEUE.forEach(action -> action.affect(LAYERS));
|
||||||
if (!MODIFICATION_QUEUE.isEmpty()) {
|
MODIFICATION_QUEUE.clear();
|
||||||
MODIFICATION_QUEUE.forEach(action -> action.affect(LAYERS));
|
|
||||||
MODIFICATION_QUEUE.clear();
|
|
||||||
|
|
||||||
boolean isMouseCurrentlyCaptured = GraphicsInterface.isMouseCaptured();
|
|
||||||
Layer.CursorPolicy policy = Layer.CursorPolicy.REQUIRE;
|
|
||||||
|
|
||||||
for (Layer layer : LAYERS) {
|
|
||||||
Layer.CursorPolicy currentPolicy = layer.getCursorPolicy();
|
|
||||||
|
|
||||||
if (currentPolicy != Layer.CursorPolicy.INDIFFERENT) {
|
|
||||||
policy = currentPolicy;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean shouldCaptureMouse = (policy == Layer.CursorPolicy.FORBID);
|
|
||||||
if (shouldCaptureMouse != isMouseCurrentlyCaptured) {
|
|
||||||
GraphicsInterface.setMouseCaptured(shouldCaptureMouse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = LAYERS.size() - 1; i >= 0; --i) {
|
for (int i = LAYERS.size() - 1; i >= 0; --i) {
|
||||||
LAYERS.get(i).render();
|
LAYERS.get(i).render();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,12 +91,43 @@ public class GUI {
|
|||||||
LAYERS.forEach(Layer::invalidate);
|
LAYERS.forEach(Layer::invalidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void dispatchInput(InputEvent event) {
|
private static void dispatchInputEvent(InputEvent event) {
|
||||||
synchronized (LAYERS) {
|
Input.Target target;
|
||||||
for (int i = 0; i < LAYERS.size(); ++i) {
|
|
||||||
LAYERS.get(i).handleInput(event);
|
if (event instanceof KeyEvent) {
|
||||||
|
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.InputEvent;
|
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||||
|
|
||||||
public abstract class Layer {
|
public abstract class Layer {
|
||||||
|
|
||||||
@ -31,52 +31,15 @@ public abstract class Layer {
|
|||||||
|
|
||||||
private final AtomicBoolean isValid = new AtomicBoolean(false);
|
private final AtomicBoolean isValid = new AtomicBoolean(false);
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents various requests that a {@link Layer} can make regarding the
|
|
||||||
* presence of a visible cursor. The value of the highest layer that is not
|
|
||||||
* {@link #INDIFFERENT} is used.
|
|
||||||
*/
|
|
||||||
public static enum CursorPolicy {
|
|
||||||
/**
|
|
||||||
* Require that a cursor is visible.
|
|
||||||
*/
|
|
||||||
REQUIRE,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link Layer} should not affect the presence or absence of a
|
|
||||||
* visible cursor; lower layers should be consulted.
|
|
||||||
*/
|
|
||||||
INDIFFERENT,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Forbid a visible cursor.
|
|
||||||
*/
|
|
||||||
FORBID
|
|
||||||
}
|
|
||||||
|
|
||||||
private CursorPolicy cursorPolicy = CursorPolicy.INDIFFERENT;
|
|
||||||
|
|
||||||
public Layer(String name) {
|
public Layer(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Layer " + name;
|
return "Layer " + name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CursorPolicy getCursorPolicy() {
|
|
||||||
return cursorPolicy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCursorPolicy(CursorPolicy cursorPolicy) {
|
|
||||||
this.cursorPolicy = cursorPolicy;
|
|
||||||
}
|
|
||||||
|
|
||||||
void render() {
|
void render() {
|
||||||
GraphicsInterface.startNextLayer();
|
GraphicsInterface.startNextLayer();
|
||||||
|
|
||||||
@ -106,7 +69,7 @@ public abstract class Layer {
|
|||||||
|
|
||||||
protected abstract void doRender();
|
protected abstract void doRender();
|
||||||
|
|
||||||
public abstract void handleInput(InputEvent input);
|
protected abstract void handleInput(Input input);
|
||||||
|
|
||||||
protected int getWidth() {
|
protected int getWidth() {
|
||||||
return GraphicsInterface.getFrameWidth();
|
return GraphicsInterface.getFrameWidth();
|
||||||
@ -116,12 +79,4 @@ public abstract class Layer {
|
|||||||
return GraphicsInterface.getFrameHeight();
|
return GraphicsInterface.getFrameHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onAdded() {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onRemoved() {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* Progressia
|
|
||||||
* Copyright (C) 2020-2022 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.backend;
|
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
import org.lwjgl.glfw.GLFWErrorCallback;
|
|
||||||
|
|
||||||
import ru.windcorp.jputil.ConstantsMapException;
|
|
||||||
import ru.windcorp.jputil.IntConstantsMap;
|
|
||||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
|
||||||
|
|
||||||
public class GLFWErrorHandler {
|
|
||||||
|
|
||||||
private static final IntConstantsMap ERROR_CODES;
|
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
ERROR_CODES = IntConstantsMap.from(GLFW.class)
|
|
||||||
.stripPrefix("GLFW_")
|
|
||||||
.onlyValued(i -> i >= 0x10000 && i <= 0x1FFFF)
|
|
||||||
.extra("GLFW_NO_ERROR")
|
|
||||||
.scan();
|
|
||||||
} catch (ConstantsMapException e) {
|
|
||||||
throw CrashReports.report(e, "Could not analyze GLFW error codes");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onError(int errorCode, long descriptionPointer) {
|
|
||||||
String description = GLFWErrorCallback.getDescription(descriptionPointer);
|
|
||||||
|
|
||||||
String errorCodeName;
|
|
||||||
if (ERROR_CODES.hasConstant(errorCode)) {
|
|
||||||
errorCodeName = ERROR_CODES.getName(errorCode);
|
|
||||||
} else {
|
|
||||||
errorCodeName = "<unknown " + Integer.toHexString(errorCode) + ">";
|
|
||||||
}
|
|
||||||
|
|
||||||
throw CrashReports.report(null, "GLFW error detected: " + errorCodeName + " %s", description);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -18,12 +18,11 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.backend;
|
package ru.windcorp.progressia.client.graphics.backend;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.GL11.*;
|
||||||
|
|
||||||
import glm.vec._2.i.Vec2i;
|
import glm.vec._2.i.Vec2i;
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFWVidMode;
|
|
||||||
|
|
||||||
import static org.lwjgl.glfw.GLFW.*;
|
import static org.lwjgl.glfw.GLFW.*;
|
||||||
import static org.lwjgl.opengl.GL11.*;
|
|
||||||
|
|
||||||
public class GraphicsBackend {
|
public class GraphicsBackend {
|
||||||
|
|
||||||
@ -39,37 +38,9 @@ public class GraphicsBackend {
|
|||||||
|
|
||||||
private static boolean faceCullingEnabled = false;
|
private static boolean faceCullingEnabled = false;
|
||||||
|
|
||||||
private static boolean isFullscreen = false;
|
|
||||||
private static boolean vSyncEnabled = false;
|
|
||||||
private static boolean isGLFWInitialized = false;
|
|
||||||
private static boolean isOpenGLInitialized = false;
|
|
||||||
|
|
||||||
private static boolean allowDisablingCursor;
|
|
||||||
static {
|
|
||||||
String key = GraphicsBackend.class.getName() + ".allowDisablingCursor";
|
|
||||||
allowDisablingCursor = Boolean.parseBoolean(System.getProperty(key, "true"));
|
|
||||||
}
|
|
||||||
private static boolean forceCursorToCenter = false;
|
|
||||||
|
|
||||||
private GraphicsBackend() {
|
private GraphicsBackend() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isGLFWInitialized() {
|
|
||||||
return isGLFWInitialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void setGLFWInitialized(boolean isGLFWInitialized) {
|
|
||||||
GraphicsBackend.isGLFWInitialized = isGLFWInitialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isOpenGLInitialized() {
|
|
||||||
return isOpenGLInitialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void setOpenGLInitialized(boolean isOpenGLInitialized) {
|
|
||||||
GraphicsBackend.isOpenGLInitialized = isOpenGLInitialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void initialize() {
|
public static void initialize() {
|
||||||
startRenderThread();
|
startRenderThread();
|
||||||
}
|
}
|
||||||
@ -122,10 +93,6 @@ public class GraphicsBackend {
|
|||||||
frameLength = now - frameStart;
|
frameLength = now - frameStart;
|
||||||
frameStart = now;
|
frameStart = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forceCursorToCenter) {
|
|
||||||
glfwSetCursorPos(windowHandle, FRAME_SIZE.x / 2.0, FRAME_SIZE.y / 2.0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void endFrame() {
|
static void endFrame() {
|
||||||
@ -161,69 +128,4 @@ public class GraphicsBackend {
|
|||||||
faceCullingEnabled = useFaceCulling;
|
faceCullingEnabled = useFaceCulling;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isFullscreen() {
|
|
||||||
return isFullscreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isVSyncEnabled() {
|
|
||||||
return vSyncEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setFullscreen() {
|
|
||||||
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
|
||||||
glfwSetWindowMonitor(
|
|
||||||
getWindowHandle(),
|
|
||||||
glfwGetPrimaryMonitor(),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
vidmode.width(),
|
|
||||||
vidmode.height(),
|
|
||||||
0);
|
|
||||||
isFullscreen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setWindowed() {
|
|
||||||
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
|
||||||
glfwSetWindowMonitor(
|
|
||||||
getWindowHandle(),
|
|
||||||
0,
|
|
||||||
(vidmode.width() - getFrameWidth()) / 2,
|
|
||||||
(vidmode.height() - getFrameHeight()) / 2,
|
|
||||||
getFrameWidth(),
|
|
||||||
getFrameHeight(),
|
|
||||||
0);
|
|
||||||
isFullscreen = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setVSyncEnabled(boolean enable) {
|
|
||||||
glfwSwapInterval(enable ? 1 : 0);
|
|
||||||
vSyncEnabled = enable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getRefreshRate() {
|
|
||||||
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
|
||||||
return vidmode.refreshRate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isMouseCaptured() {
|
|
||||||
if (!allowDisablingCursor) {
|
|
||||||
return forceCursorToCenter;
|
|
||||||
}
|
|
||||||
return glfwGetInputMode(windowHandle, GLFW_CURSOR) == GLFW_CURSOR_DISABLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setMouseCaptured(boolean capture) {
|
|
||||||
if (!allowDisablingCursor) {
|
|
||||||
forceCursorToCenter = capture;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int mode = capture ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL;
|
|
||||||
glfwSetInputMode(windowHandle, GLFW_CURSOR, mode);
|
|
||||||
|
|
||||||
if (!capture) {
|
|
||||||
glfwSetCursorPos(windowHandle, FRAME_SIZE.x / 2.0, FRAME_SIZE.y / 2.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -69,29 +69,8 @@ public class GraphicsInterface {
|
|||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void makeFullscreen(boolean state) {
|
|
||||||
if (state) {
|
|
||||||
GraphicsBackend.setFullscreen();
|
|
||||||
} else {
|
|
||||||
GraphicsBackend.setWindowed();
|
|
||||||
}
|
|
||||||
GraphicsBackend.setVSyncEnabled(GraphicsBackend.isVSyncEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isMouseCaptured() {
|
|
||||||
return GraphicsBackend.isMouseCaptured();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setMouseCaptured(boolean capture) {
|
|
||||||
GraphicsBackend.setMouseCaptured(capture);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,6 @@ 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;
|
||||||
@ -60,7 +59,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);
|
||||||
INPUT_EVENT_BUS.post(THE_KEY_EVENT);
|
dispatch(THE_KEY_EVENT);
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case GLFW.GLFW_PRESS:
|
case GLFW.GLFW_PRESS:
|
||||||
@ -91,7 +90,6 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +109,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);
|
||||||
INPUT_EVENT_BUS.post(THE_CURSOR_MOVE_EVENT);
|
dispatch(THE_CURSOR_MOVE_EVENT);
|
||||||
|
|
||||||
InputTracker.getCursorPosition().set(x, y);
|
InputTracker.getCursorPosition().set(x, y);
|
||||||
}
|
}
|
||||||
@ -126,7 +124,6 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +139,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);
|
||||||
INPUT_EVENT_BUS.post(THE_WHEEL_SCROLL_EVENT);
|
dispatch(THE_WHEEL_SCROLL_EVENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FrameResizeEvent
|
// FrameResizeEvent
|
||||||
@ -155,7 +152,6 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,17 +167,17 @@ public class InputHandler {
|
|||||||
int height
|
int height
|
||||||
) {
|
) {
|
||||||
THE_FRAME_RESIZE_EVENT.initialize(width, height);
|
THE_FRAME_RESIZE_EVENT.initialize(width, height);
|
||||||
INPUT_EVENT_BUS.post(THE_FRAME_RESIZE_EVENT);
|
dispatch(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,24 +22,9 @@ 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.GLFWErrorCallback;
|
|
||||||
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 {
|
||||||
|
|
||||||
@ -56,7 +41,6 @@ class LWJGLInitializer {
|
|||||||
setupWindowCallbacks();
|
setupWindowCallbacks();
|
||||||
|
|
||||||
glfwShowWindow(GraphicsBackend.getWindowHandle());
|
glfwShowWindow(GraphicsBackend.getWindowHandle());
|
||||||
GraphicsBackend.onFrameResized(GraphicsBackend.getWindowHandle(), 800, 600);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkEnvironment() {
|
private static void checkEnvironment() {
|
||||||
@ -64,31 +48,26 @@ class LWJGLInitializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void initializeGLFW() {
|
private static void initializeGLFW() {
|
||||||
GLFWErrorCallback.create(new GLFWErrorHandler()::onError).set();
|
// TODO Do GLFW error handling: check glfwInit, setup error callback
|
||||||
|
glfwInit();
|
||||||
if (!glfwInit()) {
|
|
||||||
throw CrashReports.report(null, "GLFW could not be initialized: glfwInit() has failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
GraphicsBackend.setGLFWInitialized(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void createWindow() {
|
private static void createWindow() {
|
||||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||||
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
|
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
|
||||||
glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE);
|
glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE);
|
||||||
|
glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
|
||||||
|
|
||||||
String windowTitle = Progressia.getName() + " " + Progressia.getFullerVersion();
|
long handle = glfwCreateWindow(900, 900, "ProgressiaTest", NULL, NULL);
|
||||||
long handle = glfwCreateWindow(800, 600, windowTitle, NULL, NULL);
|
|
||||||
|
|
||||||
if (handle == 0) {
|
// TODO Check that handle != NULL
|
||||||
throw CrashReports.report(null, "Could not create game window");
|
|
||||||
}
|
|
||||||
|
|
||||||
GraphicsBackend.setWindowHandle(handle);
|
GraphicsBackend.setWindowHandle(handle);
|
||||||
|
|
||||||
|
glfwSetInputMode(handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
||||||
|
|
||||||
glfwMakeContextCurrent(handle);
|
glfwMakeContextCurrent(handle);
|
||||||
glfwSwapInterval(0); // TODO: remove after config system is added
|
glfwSwapInterval(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void positionWindow() {
|
private static void positionWindow() {
|
||||||
@ -97,30 +76,8 @@ class LWJGLInitializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void createWindowIcons() {
|
private static void createWindowIcons() {
|
||||||
if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) {
|
// TODO Auto-generated method stub
|
||||||
// Wayland does not support changing window icons
|
|
||||||
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() {
|
||||||
@ -130,7 +87,6 @@ class LWJGLInitializer {
|
|||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
RenderTaskQueue.schedule(OpenGLObjectTracker::deleteEnqueuedObjects);
|
RenderTaskQueue.schedule(OpenGLObjectTracker::deleteEnqueuedObjects);
|
||||||
GraphicsBackend.setOpenGLInitialized(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setupWindowCallbacks() {
|
private static void setupWindowCallbacks() {
|
||||||
@ -151,20 +107,7 @@ class LWJGLInitializer {
|
|||||||
|
|
||||||
glfwSetScrollCallback(handle, InputHandler::handleWheelScroll);
|
glfwSetScrollCallback(handle, InputHandler::handleWheelScroll);
|
||||||
|
|
||||||
GraphicsInterface.subscribeToInputEvents(new Object() {
|
GraphicsInterface.subscribeToInputEvents(GUI.getEventSubscriber());
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onFrameResized(FrameResizeEvent event) {
|
|
||||||
GUI.invalidateEverything();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onInputEvent(InputEvent event) {
|
|
||||||
GUI.dispatchInput(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,8 @@ import glm.vec._3.Vec3;
|
|||||||
import glm.vec._4.Vec4;
|
import glm.vec._4.Vec4;
|
||||||
import ru.windcorp.progressia.client.graphics.Colors;
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapePart;
|
import ru.windcorp.progressia.client.graphics.model.Face;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
|
import ru.windcorp.progressia.client.graphics.model.Faces;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
@ -84,7 +84,7 @@ public class RenderTarget {
|
|||||||
|
|
||||||
private final Deque<TransformedMask> maskStack = new LinkedList<>();
|
private final Deque<TransformedMask> maskStack = new LinkedList<>();
|
||||||
private final Deque<Mat4> transformStack = new LinkedList<>();
|
private final Deque<Mat4> transformStack = new LinkedList<>();
|
||||||
private final List<ShapePart> currentClipFaces = new ArrayList<>();
|
private final List<Face> currentClipFaces = new ArrayList<>();
|
||||||
|
|
||||||
private int depth = 0;
|
private int depth = 0;
|
||||||
|
|
||||||
@ -94,8 +94,8 @@ public class RenderTarget {
|
|||||||
|
|
||||||
protected void assembleCurrentClipFromFaces() {
|
protected void assembleCurrentClipFromFaces() {
|
||||||
if (!currentClipFaces.isEmpty()) {
|
if (!currentClipFaces.isEmpty()) {
|
||||||
ShapePart[] faces = currentClipFaces.toArray(
|
Face[] faces = currentClipFaces.toArray(
|
||||||
new ShapePart[currentClipFaces.size()]
|
new Face[currentClipFaces.size()]
|
||||||
);
|
);
|
||||||
currentClipFaces.clear();
|
currentClipFaces.clear();
|
||||||
|
|
||||||
@ -189,13 +189,16 @@ public class RenderTarget {
|
|||||||
|
|
||||||
public void addCustomRenderer(Renderable renderable) {
|
public void addCustomRenderer(Renderable renderable) {
|
||||||
assembleCurrentClipFromFaces();
|
assembleCurrentClipFromFaces();
|
||||||
|
assembled.add(
|
||||||
float depth = this.depth--;
|
new Clip(
|
||||||
Mat4 transform = new Mat4().translate(0, 0, depth).mul(getTransform());
|
maskStack,
|
||||||
assembled.add(new Clip(maskStack, transform, renderable));
|
getTransform(),
|
||||||
|
renderable
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addFaceToCurrentClip(ShapePart face) {
|
protected void addFaceToCurrentClip(Face face) {
|
||||||
currentClipFaces.add(face);
|
currentClipFaces.add(face);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +270,7 @@ public class RenderTarget {
|
|||||||
fill(Colors.toVector(color));
|
fill(Colors.toVector(color));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShapePart createRectagleFace(
|
public Face createRectagleFace(
|
||||||
int x,
|
int x,
|
||||||
int y,
|
int y,
|
||||||
int width,
|
int width,
|
||||||
@ -277,7 +280,7 @@ public class RenderTarget {
|
|||||||
) {
|
) {
|
||||||
float depth = this.depth--;
|
float depth = this.depth--;
|
||||||
|
|
||||||
return ShapeParts.createRectangle(
|
return Faces.createRectangle(
|
||||||
FlatRenderProgram.getDefault(),
|
FlatRenderProgram.getDefault(),
|
||||||
texture,
|
texture,
|
||||||
color,
|
color,
|
||||||
@ -288,7 +291,7 @@ public class RenderTarget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShapePart createRectagleFace(
|
public Face createRectagleFace(
|
||||||
int x,
|
int x,
|
||||||
int y,
|
int y,
|
||||||
int width,
|
int width,
|
||||||
|
@ -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, false);
|
private static final TextureSettings TEXTURE_SETTINGS = new TextureSettings(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();
|
||||||
|
@ -33,8 +33,8 @@ import gnu.trove.stack.TIntStack;
|
|||||||
import gnu.trove.stack.array.TIntArrayStack;
|
import gnu.trove.stack.array.TIntArrayStack;
|
||||||
import ru.windcorp.progressia.client.graphics.Colors;
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapePart;
|
import ru.windcorp.progressia.client.graphics.model.Face;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
|
import ru.windcorp.progressia.client.graphics.model.Faces;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||||
@ -144,7 +144,7 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
return new Shape(
|
return new Shape(
|
||||||
Usage.STATIC,
|
Usage.STATIC,
|
||||||
getProgram(),
|
getProgram(),
|
||||||
ShapeParts.createRectangle(
|
Faces.createRectangle(
|
||||||
getProgram(),
|
getProgram(),
|
||||||
getTexture(c),
|
getTexture(c),
|
||||||
Colors.WHITE,
|
Colors.WHITE,
|
||||||
@ -167,7 +167,7 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
private final Renderable unitLine = new Shape(
|
private final Renderable unitLine = new Shape(
|
||||||
Usage.STATIC,
|
Usage.STATIC,
|
||||||
getProgram(),
|
getProgram(),
|
||||||
ShapeParts.createRectangle(
|
Faces.createRectangle(
|
||||||
getProgram(),
|
getProgram(),
|
||||||
null,
|
null,
|
||||||
Vectors.UNIT_4,
|
Vectors.UNIT_4,
|
||||||
@ -257,7 +257,7 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
|
|
||||||
private class SDWorkspace extends SpriteTypeface.Workspace {
|
private class SDWorkspace extends SpriteTypeface.Workspace {
|
||||||
|
|
||||||
private final Collection<ShapePart> faces = new ArrayList<>();
|
private final Collection<Face> faces = new ArrayList<>();
|
||||||
|
|
||||||
private final Vec3 origin = new Vec3();
|
private final Vec3 origin = new Vec3();
|
||||||
private final Vec3 width = new Vec3();
|
private final Vec3 width = new Vec3();
|
||||||
@ -298,7 +298,7 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
workspace.height.sub(workspace.origin);
|
workspace.height.sub(workspace.origin);
|
||||||
|
|
||||||
workspace.faces.add(
|
workspace.faces.add(
|
||||||
ShapeParts.createRectangle(
|
Faces.createRectangle(
|
||||||
getProgram(),
|
getProgram(),
|
||||||
texture,
|
texture,
|
||||||
color,
|
color,
|
||||||
@ -314,7 +314,7 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
return new Shape(
|
return new Shape(
|
||||||
Usage.STATIC,
|
Usage.STATIC,
|
||||||
getProgram(),
|
getProgram(),
|
||||||
workspace.faces.toArray(new ShapePart[workspace.faces.size()])
|
workspace.faces.toArray(new Face[workspace.faces.size()])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,155 +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.client.graphics.gui;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.ButtonEvent;
|
|
||||||
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.HoverEvent;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
|
||||||
|
|
||||||
public abstract class BasicButton extends Component {
|
|
||||||
|
|
||||||
private final Label label;
|
|
||||||
|
|
||||||
private boolean isPressed = false;
|
|
||||||
private final Collection<Consumer<BasicButton>> actions = Collections.synchronizedCollection(new ArrayList<>());
|
|
||||||
|
|
||||||
public BasicButton(String name, Label label) {
|
|
||||||
super(name);
|
|
||||||
this.label = label;
|
|
||||||
|
|
||||||
setLayout(new LayoutAlign(10));
|
|
||||||
addChild(this.label);
|
|
||||||
|
|
||||||
setFocusable(true);
|
|
||||||
reassembleAt(ARTrigger.HOVER, ARTrigger.FOCUS, ARTrigger.ENABLE);
|
|
||||||
|
|
||||||
// Click triggers
|
|
||||||
addInputListener(KeyEvent.class, e -> {
|
|
||||||
if (e.isRepeat())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (
|
|
||||||
e.isLeftMouseButton() ||
|
|
||||||
e.getKey() == GLFW.GLFW_KEY_SPACE ||
|
|
||||||
e.getKey() == GLFW.GLFW_KEY_ENTER
|
|
||||||
) {
|
|
||||||
setPressed(e.isPress());
|
|
||||||
e.consume();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
addListener(new Object() {
|
|
||||||
|
|
||||||
// Release when losing focus
|
|
||||||
@Subscribe
|
|
||||||
public void onFocusChange(FocusEvent e) {
|
|
||||||
if (!e.getNewState()) {
|
|
||||||
setPressed(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release when hover ends
|
|
||||||
@Subscribe
|
|
||||||
public void onHoverEnded(HoverEvent e) {
|
|
||||||
if (!e.isNowHovered()) {
|
|
||||||
setPressed(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release when disabled
|
|
||||||
@Subscribe
|
|
||||||
public void onDisabled(EnableEvent e) {
|
|
||||||
if (!e.getComponent().isEnabled()) {
|
|
||||||
setPressed(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger virtualClick when button is released
|
|
||||||
@Subscribe
|
|
||||||
public void onRelease(ButtonEvent.Release e) {
|
|
||||||
virtualClick();
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public BasicButton(String name, String label, Font labelFont) {
|
|
||||||
this(name, new Label(name + ".Label", labelFont, label));
|
|
||||||
}
|
|
||||||
|
|
||||||
public BasicButton(String name, String label) {
|
|
||||||
this(name, label, new Font());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPressed() {
|
|
||||||
return isPressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void click() {
|
|
||||||
setPressed(true);
|
|
||||||
setPressed(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPressed(boolean isPressed) {
|
|
||||||
if (this.isPressed != isPressed) {
|
|
||||||
this.isPressed = isPressed;
|
|
||||||
requestReassembly();
|
|
||||||
|
|
||||||
if (isPressed) {
|
|
||||||
takeFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatchEvent(ButtonEvent.create(this, this.isPressed));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BasicButton addAction(Consumer<BasicButton> action) {
|
|
||||||
this.actions.add(Objects.requireNonNull(action, "action"));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean removeAction(Consumer<BasicButton> action) {
|
|
||||||
return this.actions.remove(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void virtualClick() {
|
|
||||||
this.actions.forEach(action -> {
|
|
||||||
action.accept(this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Label getLabel() {
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,62 +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.client.graphics.gui;
|
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
|
||||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
|
||||||
|
|
||||||
public class Button extends BasicButton {
|
|
||||||
|
|
||||||
public Button(String name, String label, Font labelFont) {
|
|
||||||
super(name, label, labelFont);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Button(String name, Label label) {
|
|
||||||
super(name, label);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Button(String name, String label) {
|
|
||||||
this(name, label, new Font());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void assembleSelf(RenderTarget target) {
|
|
||||||
String state;
|
|
||||||
if (!isEnabled()) {
|
|
||||||
state = "Disabled";
|
|
||||||
} else if (isPressed()) {
|
|
||||||
state = "Pressed";
|
|
||||||
} else if (isHovered()) {
|
|
||||||
state = "Hovered";
|
|
||||||
} else if (isFocused()) {
|
|
||||||
state = "Focused";
|
|
||||||
} else {
|
|
||||||
state = "Inactive";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Border
|
|
||||||
target.fill(getX(), getY(), getWidth(), getHeight(), ColorScheme.get("Core:ButtonBorder" + state));
|
|
||||||
|
|
||||||
// Inside area
|
|
||||||
target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, ColorScheme.get("Core:ButtonFill" + state));
|
|
||||||
|
|
||||||
// Change label font color
|
|
||||||
getLabel().setFont(getLabel().getFont().withColor(ColorScheme.get("Core:ButtonText" + state)));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,140 +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.client.graphics.gui;
|
|
||||||
|
|
||||||
import glm.vec._2.i.Vec2i;
|
|
||||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
|
||||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
|
||||||
import ru.windcorp.progressia.client.graphics.font.Typefaces;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
|
|
||||||
|
|
||||||
public class Checkbox extends BasicButton {
|
|
||||||
|
|
||||||
private class Tick extends Component {
|
|
||||||
|
|
||||||
public Tick() {
|
|
||||||
super(Checkbox.this.getName() + ".Tick");
|
|
||||||
|
|
||||||
setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void assembleSelf(RenderTarget target) {
|
|
||||||
|
|
||||||
int size = getPreferredSize().x;
|
|
||||||
int x = getX();
|
|
||||||
int y = getY() + (getHeight() - size) / 2;
|
|
||||||
|
|
||||||
String state;
|
|
||||||
if (!Checkbox.this.isEnabled()) {
|
|
||||||
state = "Disabled";
|
|
||||||
} else if (Checkbox.this.isPressed()) {
|
|
||||||
state = "Pressed";
|
|
||||||
} else if (Checkbox.this.isHovered()) {
|
|
||||||
state = "Hovered";
|
|
||||||
} else if (Checkbox.this.isFocused()) {
|
|
||||||
state = "Focused";
|
|
||||||
} else {
|
|
||||||
state = "Inactive";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Border
|
|
||||||
target.fill(x, y, size, size, ColorScheme.get("Core:CheckboxBorder" + state));
|
|
||||||
|
|
||||||
// Inside area
|
|
||||||
target.fill(x + 2, y + 2, size - 4, size - 4, ColorScheme.get("Core:CheckboxFill" + state));
|
|
||||||
|
|
||||||
// "Tick"
|
|
||||||
if (Checkbox.this.isChecked()) {
|
|
||||||
target.fill(x + 4, y + 4, size - 8, size - 8, ColorScheme.get("Core:CheckboxCheck" + state));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checked;
|
|
||||||
|
|
||||||
public Checkbox(String name, String label, Font labelFont, boolean check) {
|
|
||||||
super(name, label, labelFont);
|
|
||||||
this.checked = check;
|
|
||||||
|
|
||||||
assert getChildren().size() == 1 : "Checkbox expects that BasicButton contains exactly one child";
|
|
||||||
Component basicChild = getChild(0);
|
|
||||||
|
|
||||||
Group group = new Group(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10));
|
|
||||||
removeChild(basicChild);
|
|
||||||
setLayout(new LayoutAlign(0, 0.5f, 10));
|
|
||||||
group.setLayoutHint(basicChild.getLayoutHint());
|
|
||||||
group.addChild(new Tick());
|
|
||||||
group.addChild(basicChild);
|
|
||||||
addChild(group);
|
|
||||||
|
|
||||||
addAction(b -> switchState());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Checkbox(String name, String label, Font labelFont) {
|
|
||||||
this(name, label, labelFont, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Checkbox(String name, String label, boolean check) {
|
|
||||||
this(name, label, new Font(), check);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Checkbox(String name, String label) {
|
|
||||||
this(name, label, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void switchState() {
|
|
||||||
setChecked(!isChecked());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the checked
|
|
||||||
*/
|
|
||||||
public boolean isChecked() {
|
|
||||||
return checked;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param checked the checked to set
|
|
||||||
*/
|
|
||||||
public void setChecked(boolean checked) {
|
|
||||||
this.checked = checked;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void assembleSelf(RenderTarget target) {
|
|
||||||
String state;
|
|
||||||
if (!Checkbox.this.isEnabled()) {
|
|
||||||
state = "Disabled";
|
|
||||||
} else if (Checkbox.this.isPressed()) {
|
|
||||||
state = "Pressed";
|
|
||||||
} else if (Checkbox.this.isHovered()) {
|
|
||||||
state = "Hovered";
|
|
||||||
} else if (Checkbox.this.isFocused()) {
|
|
||||||
state = "Focused";
|
|
||||||
} else {
|
|
||||||
state = "Inactive";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change label font color
|
|
||||||
getLabel().setFont(getLabel().getFont().withColor(ColorScheme.get("Core:CheckboxText" + state)));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,145 +0,0 @@
|
|||||||
/*
|
|
||||||
* Progressia
|
|
||||||
* Copyright (C) 2020-2022 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.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
|
|
||||||
import glm.vec._4.Vec4;
|
|
||||||
import ru.windcorp.jputil.SyntaxException;
|
|
||||||
import ru.windcorp.jputil.chars.StringUtil;
|
|
||||||
import ru.windcorp.progressia.common.resource.Resource;
|
|
||||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
|
||||||
import ru.windcorp.progressia.common.util.namespaces.IllegalIdException;
|
|
||||||
import ru.windcorp.progressia.common.util.namespaces.NamespacedUtil;
|
|
||||||
|
|
||||||
public class ColorScheme {
|
|
||||||
|
|
||||||
private static Map<String, Vec4> defaultScheme;
|
|
||||||
|
|
||||||
public static Vec4 get(String key) {
|
|
||||||
Vec4 color = defaultScheme.get(key);
|
|
||||||
if (color == null) {
|
|
||||||
throw CrashReports.report(null, "ColorScheme does not contain color %s", key);
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void load(Resource resource) {
|
|
||||||
Map<String, Vec4> tmpMap = new HashMap<>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (String fullLine : StringUtil.split(resource.readAsString(), '\n')) {
|
|
||||||
String line = fullLine.trim();
|
|
||||||
if (line.isEmpty() || line.startsWith("#")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] parts = StringUtil.split(line, '=', 2);
|
|
||||||
if (parts[1] == null) {
|
|
||||||
throw new SyntaxException("Could not parse \"" + line + "\": '=' not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
String key = parts[0].trim();
|
|
||||||
checkKeyName(key);
|
|
||||||
if (tmpMap.containsKey(key)) {
|
|
||||||
throw new SyntaxException("Duplicate key " + key);
|
|
||||||
}
|
|
||||||
|
|
||||||
String value = parts[1].trim();
|
|
||||||
Vec4 color;
|
|
||||||
|
|
||||||
if (value.startsWith("#")) {
|
|
||||||
color = parseValueAsHex(value);
|
|
||||||
} else if (value.startsWith("$")) {
|
|
||||||
color = parseValueAsReference(value, tmpMap);
|
|
||||||
} else {
|
|
||||||
throw new SyntaxException("Unknown value format \"" + value + "\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpMap.put(key, color);
|
|
||||||
}
|
|
||||||
} catch (SyntaxException e) {
|
|
||||||
throw CrashReports.report(e, "Could not load ColorScheme from %s", resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultScheme = ImmutableMap.copyOf(tmpMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkKeyName(String key) throws SyntaxException {
|
|
||||||
try {
|
|
||||||
NamespacedUtil.checkId(key);
|
|
||||||
} catch (IllegalIdException e) {
|
|
||||||
throw new SyntaxException("Illegal key name \"" + key + "\"", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int parseHex(String from, int start, int length) {
|
|
||||||
return Integer.parseInt(from.substring(start, start + length), 0x10);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Vec4 parseValueAsHex(String value) throws SyntaxException {
|
|
||||||
int a = 0xFF;
|
|
||||||
int r, g, b;
|
|
||||||
|
|
||||||
switch (value.length() - 1) {
|
|
||||||
case 3:
|
|
||||||
// #RGB
|
|
||||||
r = parseHex(value, 1, 1) * 0x11;
|
|
||||||
g = parseHex(value, 2, 1) * 0x11;
|
|
||||||
b = parseHex(value, 3, 1) * 0x11;
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
// #ARGB
|
|
||||||
a = parseHex(value, 1, 1) * 0x11;
|
|
||||||
r = parseHex(value, 2, 1) * 0x11;
|
|
||||||
g = parseHex(value, 3, 1) * 0x11;
|
|
||||||
b = parseHex(value, 4, 1) * 0x11;
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
// #RRGGBB
|
|
||||||
r = parseHex(value, 1, 2);
|
|
||||||
g = parseHex(value, 3, 2);
|
|
||||||
b = parseHex(value, 5, 2);
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
// #AARRGGBB
|
|
||||||
a = parseHex(value, 1, 2);
|
|
||||||
r = parseHex(value, 3, 2);
|
|
||||||
g = parseHex(value, 5, 2);
|
|
||||||
b = parseHex(value, 7, 2);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new SyntaxException("Could not parse hex color \"" + value + "\": expecting #RGB, #ARGB, #RRGGBB or #AARRGGBB");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Vec4(r / 255.0, g / 255.0, b / 255.0, a / 255.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Vec4 parseValueAsReference(String value, Map<String, Vec4> map) throws SyntaxException {
|
|
||||||
String otherKey = value.substring(1);
|
|
||||||
checkKeyName(otherKey);
|
|
||||||
if (!map.containsKey(otherKey)) {
|
|
||||||
throw new SyntaxException("Key $" + otherKey + " not found");
|
|
||||||
}
|
|
||||||
return map.get(otherKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -19,28 +19,24 @@
|
|||||||
package ru.windcorp.progressia.client.graphics.gui;
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
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 glm.vec._2.i.Vec2i;
|
import glm.vec._2.i.Vec2i;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
|
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
|
||||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.ChildAddedEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.ChildAddedEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.ChildRemovedEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.ChildRemovedEvent;
|
||||||
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.KeyMatcher;
|
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||||
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;
|
||||||
@ -54,7 +50,7 @@ public class Component extends Named {
|
|||||||
private Component parent = null;
|
private Component parent = null;
|
||||||
|
|
||||||
private EventBus eventBus = null;
|
private EventBus eventBus = null;
|
||||||
private final InputBus inputBus = new InputBus(this);
|
private InputBus inputBus = null;
|
||||||
|
|
||||||
private int x, y;
|
private int x, y;
|
||||||
private int width, height;
|
private int width, height;
|
||||||
@ -66,8 +62,6 @@ public class Component extends Named {
|
|||||||
private Object layoutHint = null;
|
private Object layoutHint = null;
|
||||||
private Layout layout = null;
|
private Layout layout = null;
|
||||||
|
|
||||||
private boolean isEnabled = true;
|
|
||||||
|
|
||||||
private boolean isFocusable = false;
|
private boolean isFocusable = false;
|
||||||
private boolean isFocused = false;
|
private boolean isFocused = false;
|
||||||
|
|
||||||
@ -75,9 +69,6 @@ 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() {
|
||||||
@ -294,31 +285,10 @@ public class Component extends Named {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether this component is focusable. A component needs to be
|
|
||||||
* focusable to become focused. A component that is focusable may not
|
|
||||||
* necessarily be ready to gain focus (see {@link #canGainFocusNow()}).
|
|
||||||
*
|
|
||||||
* @return {@code true} iff the component is focusable
|
|
||||||
* @see #canGainFocusNow()
|
|
||||||
*/
|
|
||||||
public boolean isFocusable() {
|
public boolean isFocusable() {
|
||||||
return isFocusable;
|
return isFocusable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether this component can become focused at this moment.
|
|
||||||
* <p>
|
|
||||||
* The implementation of this method in {@link Component} considers the
|
|
||||||
* component a focus candidate if it is both focusable and enabled.
|
|
||||||
*
|
|
||||||
* @return {@code true} iff the component can receive focus
|
|
||||||
* @see #isFocusable()
|
|
||||||
*/
|
|
||||||
public boolean canGainFocusNow() {
|
|
||||||
return isFocusable() && isEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Component setFocusable(boolean focusable) {
|
public Component setFocusable(boolean focusable) {
|
||||||
this.isFocusable = focusable;
|
this.isFocusable = focusable;
|
||||||
return this;
|
return this;
|
||||||
@ -367,7 +337,7 @@ public class Component extends Named {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.canGainFocusNow()) {
|
if (component.isFocusable()) {
|
||||||
setFocused(false);
|
setFocused(false);
|
||||||
component.setFocused(true);
|
component.setFocused(true);
|
||||||
return;
|
return;
|
||||||
@ -409,7 +379,7 @@ public class Component extends Named {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.canGainFocusNow()) {
|
if (component.isFocusable()) {
|
||||||
setFocused(false);
|
setFocused(false);
|
||||||
component.setFocused(true);
|
component.setFocused(true);
|
||||||
return;
|
return;
|
||||||
@ -463,51 +433,12 @@ public class Component extends Named {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return isEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables or disables this component. An {@link EnableEvent} is dispatched
|
|
||||||
* if the state changes.
|
|
||||||
*
|
|
||||||
* @param enabled {@code true} to enable the component, {@code false} to
|
|
||||||
* disable the component
|
|
||||||
* @see #setEnabledRecursively(boolean)
|
|
||||||
*/
|
|
||||||
public void setEnabled(boolean enabled) {
|
|
||||||
if (this.isEnabled != enabled) {
|
|
||||||
if (isFocused() && isEnabled()) {
|
|
||||||
focusNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEnabled()) {
|
|
||||||
setHovered(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isEnabled = enabled;
|
|
||||||
dispatchEvent(new EnableEvent(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables or disables this component and all of its children recursively.
|
|
||||||
*
|
|
||||||
* @param enabled {@code true} to enable the components, {@code false} to
|
|
||||||
* disable the components
|
|
||||||
* @see #setEnabled(boolean)
|
|
||||||
*/
|
|
||||||
public void setEnabledRecursively(boolean enabled) {
|
|
||||||
setEnabled(enabled);
|
|
||||||
getChildren().forEach(c -> c.setEnabledRecursively(enabled));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isHovered() {
|
public boolean isHovered() {
|
||||||
return isHovered;
|
return isHovered;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setHovered(boolean isHovered) {
|
protected void setHovered(boolean isHovered) {
|
||||||
if (this.isHovered != isHovered && isEnabled()) {
|
if (this.isHovered != isHovered) {
|
||||||
this.isHovered = isHovered;
|
this.isHovered = isHovered;
|
||||||
|
|
||||||
if (!isHovered && !getChildren().isEmpty()) {
|
if (!isHovered && !getChildren().isEmpty()) {
|
||||||
@ -524,10 +455,6 @@ public class Component extends Named {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
eventBus = ReportingEventBus.create(getName());
|
eventBus = ReportingEventBus.create(getName());
|
||||||
@ -548,32 +475,121 @@ public class Component extends Named {
|
|||||||
eventBus.post(event);
|
eventBus.post(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends InputEvent> void addInputListener(Class<? extends T> type, InputListener<T> listener, InputBus.Option... options) {
|
public <T extends InputEvent> void addListener(
|
||||||
inputBus.register(type, listener, options);
|
Class<? extends T> type,
|
||||||
|
boolean handlesConsumed,
|
||||||
|
InputListener<T> listener
|
||||||
|
) {
|
||||||
|
if (inputBus == null) {
|
||||||
|
inputBus = new InputBus();
|
||||||
|
}
|
||||||
|
|
||||||
|
inputBus.register(type, handlesConsumed, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addKeyListener(KeyMatcher matcher, InputListener<? super KeyEvent> listener, InputBus.Option... options) {
|
public <T extends InputEvent> void addListener(Class<? extends T> type, InputListener<T> listener) {
|
||||||
inputBus.register(matcher, listener, options);
|
if (inputBus == null) {
|
||||||
|
inputBus = new InputBus();
|
||||||
|
}
|
||||||
|
|
||||||
|
inputBus.register(type, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeInputListener(InputListener<?> listener) {
|
public void removeListener(InputListener<?> listener) {
|
||||||
if (inputBus != null) {
|
if (inputBus != null) {
|
||||||
inputBus.unregister(listener);
|
inputBus.unregister(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean passInputToChildren(InputEvent e) {
|
protected void handleInput(Input input) {
|
||||||
return true;
|
if (inputBus != null) {
|
||||||
|
inputBus.dispatch(input);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InputBus getInputBus() {
|
public void dispatchInput(Input input) {
|
||||||
return inputBus;
|
try {
|
||||||
|
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();
|
||||||
@ -582,17 +598,6 @@ public class Component extends Named {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Schedules the reassembly to occur.
|
|
||||||
* <p>
|
|
||||||
* This method is invoked in root components whenever a
|
|
||||||
* {@linkplain #requestReassembly() reassembly request} is made by one of
|
|
||||||
* its children. When creating the dedicated root component, override this
|
|
||||||
* method to perform any implementation-specific actions that will cause a
|
|
||||||
* reassembly as soon as possible.
|
|
||||||
* <p>
|
|
||||||
* The default implementation of this method does nothing.
|
|
||||||
*/
|
|
||||||
protected void handleReassemblyRequest() {
|
protected void handleReassemblyRequest() {
|
||||||
// To be overridden
|
// To be overridden
|
||||||
}
|
}
|
||||||
@ -633,135 +638,6 @@ public class Component extends Named {
|
|||||||
getChildren().forEach(child -> child.assemble(target));
|
getChildren().forEach(child -> child.assemble(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Automatic Reassembly
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The various kinds of changes that may be used with
|
|
||||||
* {@link Component#reassembleAt(ARTrigger...)}.
|
|
||||||
*/
|
|
||||||
protected static enum ARTrigger {
|
|
||||||
/**
|
|
||||||
* Reassemble the component whenever its hover status changes, e.g.
|
|
||||||
* whenever the pointer enters or leaves its bounds.
|
|
||||||
*/
|
|
||||||
HOVER,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reassemble the component whenever it gains or loses focus.
|
|
||||||
* <p>
|
|
||||||
* <em>Component must be focusable to be able to gain focus.</em> The
|
|
||||||
* component will not be reassembled unless
|
|
||||||
* {@link Component#setFocusable(boolean) setFocusable(true)} has been
|
|
||||||
* invoked.
|
|
||||||
*/
|
|
||||||
FOCUS,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reassemble the component whenever it is enabled or disabled.
|
|
||||||
*/
|
|
||||||
ENABLE
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All trigger objects (event listeners) that are currently registered with
|
|
||||||
* {@link #eventBus}. The field is {@code null} until the first trigger is
|
|
||||||
* installed.
|
|
||||||
*/
|
|
||||||
private Map<ARTrigger, Object> autoReassemblyTriggerObjects = null;
|
|
||||||
|
|
||||||
private Object createTriggerObject(ARTrigger type) {
|
|
||||||
switch (type) {
|
|
||||||
case HOVER:
|
|
||||||
return new Object() {
|
|
||||||
@Subscribe
|
|
||||||
public void onHoverChanged(HoverEvent e) {
|
|
||||||
requestReassembly();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
case FOCUS:
|
|
||||||
return new Object() {
|
|
||||||
@Subscribe
|
|
||||||
public void onFocusChanged(FocusEvent e) {
|
|
||||||
requestReassembly();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
case ENABLE:
|
|
||||||
return new Object() {
|
|
||||||
@Subscribe
|
|
||||||
public void onEnabled(EnableEvent e) {
|
|
||||||
requestReassembly();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
throw new NullPointerException("type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests that {@link #requestReassembly()} is invoked on this component
|
|
||||||
* whenever any of the specified changes occur. Duplicate attempts to
|
|
||||||
* register the same trigger are silently ignored.
|
|
||||||
* <p>
|
|
||||||
* {@code triggers} may be empty, which results in a no-op. It must not be
|
|
||||||
* {@code null}.
|
|
||||||
*
|
|
||||||
* @param triggers the {@linkplain ARTrigger triggers} to
|
|
||||||
* request reassembly with.
|
|
||||||
* @see #disableAutoReassemblyAt(ARTrigger...)
|
|
||||||
*/
|
|
||||||
protected synchronized void reassembleAt(ARTrigger... triggers) {
|
|
||||||
|
|
||||||
Objects.requireNonNull(triggers, "triggers");
|
|
||||||
if (triggers.length == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (autoReassemblyTriggerObjects == null) {
|
|
||||||
autoReassemblyTriggerObjects = new EnumMap<>(ARTrigger.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (ARTrigger trigger : triggers) {
|
|
||||||
if (!autoReassemblyTriggerObjects.containsKey(trigger)) {
|
|
||||||
Object triggerObject = createTriggerObject(trigger);
|
|
||||||
addListener(triggerObject);
|
|
||||||
autoReassemblyTriggerObjects.put(trigger, triggerObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests that {@link #requestReassembly()} is no longer invoked on this
|
|
||||||
* component whenever any of the specified changes occur. After a trigger is
|
|
||||||
* removed, it may be reinstalled with
|
|
||||||
* {@link #reassembleAt(ARTrigger...)}. Attempts to remove a
|
|
||||||
* nonexistant trigger are silently ignored.
|
|
||||||
* <p>
|
|
||||||
* {@code triggers} may be empty, which results in a no-op. It must not be
|
|
||||||
* {@code null}.
|
|
||||||
*
|
|
||||||
* @param triggers the {@linkplain ARTrigger triggers} to remove
|
|
||||||
* @see #reassemblyAt(ARTrigger...)
|
|
||||||
*/
|
|
||||||
protected synchronized void disableAutoReassemblyAt(ARTrigger... triggers) {
|
|
||||||
|
|
||||||
Objects.requireNonNull(triggers, "triggers");
|
|
||||||
if (triggers.length == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (autoReassemblyTriggerObjects == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (ARTrigger trigger : triggers) {
|
|
||||||
Object triggerObject = autoReassemblyTriggerObjects.remove(trigger);
|
|
||||||
if (triggerObject != null) {
|
|
||||||
removeListener(trigger);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * Returns a component that displays this component in its center.
|
// * Returns a component that displays this component in its center.
|
||||||
// * @return a {@link Aligner} initialized to center this component
|
// * @return a {@link Aligner} initialized to center this component
|
||||||
|
@ -1,77 +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.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();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -18,17 +18,9 @@
|
|||||||
|
|
||||||
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.InputEvent;
|
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||||
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 {
|
||||||
|
|
||||||
@ -41,9 +33,7 @@ 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() {
|
||||||
@ -57,85 +47,9 @@ 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
|
||||||
public void handleInput(InputEvent event) {
|
protected void handleInput(Input input) {
|
||||||
StashingStack<EventHandlingFrame> path = pathCache.grab();
|
getRoot().dispatchInput(input);
|
||||||
|
|
||||||
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
|
||||||
|
@ -83,10 +83,6 @@ public class Label extends Component {
|
|||||||
return font;
|
return font;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFont(Font font) {
|
|
||||||
this.font = font;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCurrentText() {
|
public String getCurrentText() {
|
||||||
return currentText;
|
return currentText;
|
||||||
}
|
}
|
||||||
@ -100,7 +96,11 @@ public class Label extends Component {
|
|||||||
float startX = getX() + font.getAlign() * (getWidth() - currentSize.x);
|
float startX = getX() + font.getAlign() * (getWidth() - currentSize.x);
|
||||||
|
|
||||||
target.pushTransform(
|
target.pushTransform(
|
||||||
new Mat4().identity().translate(startX, getY(), 0).scale(2)
|
new Mat4().identity().translate(startX, getY(), -1000) // TODO wtf
|
||||||
|
// is this
|
||||||
|
// magic
|
||||||
|
// <---
|
||||||
|
.scale(2)
|
||||||
);
|
);
|
||||||
|
|
||||||
target.addCustomRenderer(font.assemble(currentText, maxWidth));
|
target.addCustomRenderer(font.assemble(currentText, maxWidth));
|
||||||
|
59
src/main/java/ru/windcorp/progressia/client/graphics/gui/Panel.java
Normal file → Executable file
59
src/main/java/ru/windcorp/progressia/client/graphics/gui/Panel.java
Normal file → Executable file
@ -15,65 +15,14 @@
|
|||||||
* 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.Objects;
|
public class Panel extends Component {
|
||||||
|
|
||||||
import glm.vec._4.Vec4;
|
|
||||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
|
||||||
|
|
||||||
public class Panel extends Group {
|
|
||||||
|
|
||||||
private Vec4 fill;
|
|
||||||
private Vec4 border;
|
|
||||||
|
|
||||||
public Panel(String name, Layout layout, Vec4 fill, Vec4 border) {
|
|
||||||
super(name, layout);
|
|
||||||
|
|
||||||
this.fill = Objects.requireNonNull(fill, "fill");
|
|
||||||
this.border = border;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Panel(String name, Layout layout) {
|
public Panel(String name, Layout layout) {
|
||||||
this(name, layout, ColorScheme.get("Core:Background"), ColorScheme.get("Core:Border"));
|
super(name);
|
||||||
}
|
setLayout(layout);
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the fill
|
|
||||||
*/
|
|
||||||
public Vec4 getFill() {
|
|
||||||
return fill;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param fill the fill to set
|
|
||||||
*/
|
|
||||||
public void setFill(Vec4 fill) {
|
|
||||||
this.fill = Objects.requireNonNull(fill, "fill");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the border
|
|
||||||
*/
|
|
||||||
public Vec4 getBorder() {
|
|
||||||
return border;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param border the border to set
|
|
||||||
*/
|
|
||||||
public void setBorder(Vec4 border) {
|
|
||||||
this.border = border;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void assembleSelf(RenderTarget target) {
|
|
||||||
if (border == null) {
|
|
||||||
target.fill(getX(), getY(), getWidth(), getHeight(), fill);
|
|
||||||
} else {
|
|
||||||
target.fill(getX(), getY(), getWidth(), getHeight(), border);
|
|
||||||
target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, fill);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,196 +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.client.graphics.gui;
|
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
import glm.vec._2.i.Vec2i;
|
|
||||||
import glm.vec._4.Vec4;
|
|
||||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
|
||||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
|
||||||
import ru.windcorp.progressia.client.graphics.font.Typefaces;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
|
||||||
|
|
||||||
public class RadioButton extends BasicButton {
|
|
||||||
|
|
||||||
private class Tick extends Component {
|
|
||||||
|
|
||||||
public Tick() {
|
|
||||||
super(RadioButton.this.getName() + ".Tick");
|
|
||||||
|
|
||||||
setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cross(RenderTarget target, int x, int y, int size, Vec4 color) {
|
|
||||||
target.fill(x + 4, y, size - 8, size, color);
|
|
||||||
target.fill(x + 2, y + 2, size - 4, size - 4, color);
|
|
||||||
target.fill(x, y + 4, size, size - 8, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void assembleSelf(RenderTarget target) {
|
|
||||||
|
|
||||||
int size = getPreferredSize().x;
|
|
||||||
int x = getX();
|
|
||||||
int y = getY() + (getHeight() - size) / 2;
|
|
||||||
|
|
||||||
String state;
|
|
||||||
if (!RadioButton.this.isEnabled()) {
|
|
||||||
state = "Disabled";
|
|
||||||
} else if (RadioButton.this.isPressed()) {
|
|
||||||
state = "Pressed";
|
|
||||||
} else if (RadioButton.this.isHovered()) {
|
|
||||||
state = "Hovered";
|
|
||||||
} else if (RadioButton.this.isFocused()) {
|
|
||||||
state = "Focused";
|
|
||||||
} else {
|
|
||||||
state = "Inactive";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Border
|
|
||||||
cross(target, x, y, size, ColorScheme.get("Core:RadioButtonBorder" + state));
|
|
||||||
|
|
||||||
// Inside area
|
|
||||||
cross(target, x + 2, y + 2, size - 4, ColorScheme.get("Core:RadioButtonFill" + state));
|
|
||||||
|
|
||||||
// "Tick"
|
|
||||||
if (RadioButton.this.isChecked()) {
|
|
||||||
cross(target, x + 4, y + 4, size - 8, ColorScheme.get("Core:RadioButtonCheck" + state));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checked;
|
|
||||||
|
|
||||||
private RadioButtonGroup group = null;
|
|
||||||
|
|
||||||
public RadioButton(String name, String label, Font labelFont, boolean check) {
|
|
||||||
super(name, label, labelFont);
|
|
||||||
this.checked = check;
|
|
||||||
|
|
||||||
assert getChildren().size() == 1 : "RadioButton expects that BasicButton contains exactly one child";
|
|
||||||
Component basicChild = getChild(0);
|
|
||||||
|
|
||||||
Group group = new Group(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10));
|
|
||||||
removeChild(basicChild);
|
|
||||||
setLayout(new LayoutAlign(0, 0.5f, 10));
|
|
||||||
group.setLayoutHint(basicChild.getLayoutHint());
|
|
||||||
group.addChild(new Tick());
|
|
||||||
group.addChild(basicChild);
|
|
||||||
addChild(group);
|
|
||||||
|
|
||||||
addInputListener(KeyEvent.class, e -> {
|
|
||||||
if (e.isRelease()) return;
|
|
||||||
|
|
||||||
if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) {
|
|
||||||
if (this.group != null) {
|
|
||||||
this.group.selectPrevious();
|
|
||||||
this.group.getSelected().takeFocus();
|
|
||||||
}
|
|
||||||
e.consume();
|
|
||||||
} else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
|
|
||||||
if (this.group != null) {
|
|
||||||
this.group.selectNext();
|
|
||||||
this.group.getSelected().takeFocus();
|
|
||||||
}
|
|
||||||
e.consume();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
addAction(b -> setChecked(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
public RadioButton(String name, String label, Font labelFont) {
|
|
||||||
this(name, label, labelFont, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RadioButton(String name, String label, boolean check) {
|
|
||||||
this(name, label, new Font(), check);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RadioButton(String name, String label) {
|
|
||||||
this(name, label, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param group the group to set
|
|
||||||
*/
|
|
||||||
public RadioButton setGroup(RadioButtonGroup group) {
|
|
||||||
|
|
||||||
if (this.group != null) {
|
|
||||||
group.selectNext();
|
|
||||||
removeAction(group.listener);
|
|
||||||
group.buttons.remove(this);
|
|
||||||
// Clear reference if this was the only button in the group
|
|
||||||
group.getSelected();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.group = group;
|
|
||||||
|
|
||||||
if (this.group != null) {
|
|
||||||
group.buttons.add(this);
|
|
||||||
addAction(group.listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
setChecked(false);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the checked
|
|
||||||
*/
|
|
||||||
public boolean isChecked() {
|
|
||||||
return checked;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param checked the checked to set
|
|
||||||
*/
|
|
||||||
public void setChecked(boolean checked) {
|
|
||||||
this.checked = checked;
|
|
||||||
|
|
||||||
if (group != null) {
|
|
||||||
group.listener.accept(this); // Failsafe for manual invocations of
|
|
||||||
// setChecked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void assembleSelf(RenderTarget target) {
|
|
||||||
String state;
|
|
||||||
if (!RadioButton.this.isEnabled()) {
|
|
||||||
state = "Disabled";
|
|
||||||
} else if (RadioButton.this.isPressed()) {
|
|
||||||
state = "Pressed";
|
|
||||||
} else if (RadioButton.this.isHovered()) {
|
|
||||||
state = "Hovered";
|
|
||||||
} else if (RadioButton.this.isFocused()) {
|
|
||||||
state = "Focused";
|
|
||||||
} else {
|
|
||||||
state = "Inactive";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change label font color
|
|
||||||
getLabel().setFont(getLabel().getFont().withColor(ColorScheme.get("Core:RadioButtonText" + state)));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,119 +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.client.graphics.gui;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class RadioButtonGroup {
|
|
||||||
|
|
||||||
private final Collection<Consumer<RadioButtonGroup>> actions = Collections.synchronizedCollection(new ArrayList<>());
|
|
||||||
final List<RadioButton> buttons = Collections.synchronizedList(new ArrayList<>());
|
|
||||||
|
|
||||||
private RadioButton selected = null;
|
|
||||||
|
|
||||||
Consumer<BasicButton> listener = b -> {
|
|
||||||
if (b instanceof RadioButton && ((RadioButton) b).isChecked() && buttons.contains(b)) {
|
|
||||||
select((RadioButton) b);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public RadioButtonGroup addAction(Consumer<RadioButtonGroup> action) {
|
|
||||||
this.actions.add(Objects.requireNonNull(action, "action"));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean removeAction(Consumer<BasicButton> action) {
|
|
||||||
return this.actions.remove(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<RadioButton> getButtons() {
|
|
||||||
return Collections.unmodifiableList(buttons);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized RadioButton getSelected() {
|
|
||||||
if (!buttons.contains(selected)) {
|
|
||||||
selected = null;
|
|
||||||
}
|
|
||||||
return selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void select(RadioButton button) {
|
|
||||||
if (button != null && !buttons.contains(button)) {
|
|
||||||
throw new IllegalArgumentException("Button " + button + " is not in the group");
|
|
||||||
}
|
|
||||||
|
|
||||||
getSelected(); // Clear if invalid
|
|
||||||
|
|
||||||
if (selected == button) {
|
|
||||||
return; // Terminate listener-setter recursion
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected != null) {
|
|
||||||
selected.setChecked(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
selected = button;
|
|
||||||
|
|
||||||
if (selected != null) {
|
|
||||||
selected.setChecked(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
actions.forEach(action -> action.accept(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void selectNext() {
|
|
||||||
selectNeighbour(+1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void selectPrevious() {
|
|
||||||
selectNeighbour(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void selectNeighbour(int direction) {
|
|
||||||
if (getSelected() == null) {
|
|
||||||
if (buttons.isEmpty()) {
|
|
||||||
throw new IllegalStateException("Cannot select neighbour button: group empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
select(buttons.get(0));
|
|
||||||
} else {
|
|
||||||
RadioButton button;
|
|
||||||
int index = buttons.indexOf(selected);
|
|
||||||
|
|
||||||
do {
|
|
||||||
index += direction;
|
|
||||||
|
|
||||||
if (index >= buttons.size()) {
|
|
||||||
index = 0;
|
|
||||||
} else if (index < 0) {
|
|
||||||
index = buttons.size() - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
button = buttons.get(index);
|
|
||||||
} while (button != getSelected() && !button.isEnabled());
|
|
||||||
|
|
||||||
select(button);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,60 +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.client.graphics.gui.event;
|
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.BasicButton;
|
|
||||||
|
|
||||||
public class ButtonEvent extends ComponentEvent {
|
|
||||||
|
|
||||||
public static class Press extends ButtonEvent {
|
|
||||||
public Press(BasicButton button) {
|
|
||||||
super(button, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Release extends ButtonEvent {
|
|
||||||
public Release(BasicButton button) {
|
|
||||||
super(button, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final boolean isPress;
|
|
||||||
|
|
||||||
protected ButtonEvent(BasicButton button, boolean isPress) {
|
|
||||||
super(button);
|
|
||||||
this.isPress = isPress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ButtonEvent create(BasicButton button, boolean isPress) {
|
|
||||||
if (isPress) {
|
|
||||||
return new Press(button);
|
|
||||||
} else {
|
|
||||||
return new Release(button);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPress() {
|
|
||||||
return isPress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRelease() {
|
|
||||||
return !isPress;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,58 +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.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,28 +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.client.graphics.gui.event;
|
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
|
||||||
|
|
||||||
public class DragStartEvent extends ComponentEvent {
|
|
||||||
|
|
||||||
public DragStartEvent(Component component) {
|
|
||||||
super(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,44 +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.client.graphics.gui.event;
|
|
||||||
|
|
||||||
import glm.vec._2.d.Vec2d;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
|
||||||
|
|
||||||
public class DragStopEvent extends ComponentEvent {
|
|
||||||
|
|
||||||
private final Vec2d totalChange = new Vec2d();
|
|
||||||
|
|
||||||
public DragStopEvent(Component component, Vec2d totalChange) {
|
|
||||||
super(component);
|
|
||||||
this.totalChange.set(totalChange.x, totalChange.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Vec2d getTotalChange() {
|
|
||||||
return totalChange;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getTotalChangeX() {
|
|
||||||
return totalChange.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getTotalChangeY() {
|
|
||||||
return totalChange.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package ru.windcorp.progressia.client.graphics.gui.event;
|
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
|
||||||
|
|
||||||
public class EnableEvent extends ComponentEvent {
|
|
||||||
|
|
||||||
public EnableEvent(Component component) {
|
|
||||||
super(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,78 +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.client.graphics.gui.layout;
|
|
||||||
|
|
||||||
import static java.lang.Math.max;
|
|
||||||
|
|
||||||
import glm.vec._2.i.Vec2i;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.Layout;
|
|
||||||
|
|
||||||
public class LayoutFill implements Layout {
|
|
||||||
|
|
||||||
private final int margin;
|
|
||||||
|
|
||||||
public LayoutFill(int margin) {
|
|
||||||
this.margin = margin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LayoutFill() {
|
|
||||||
this(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void layout(Component c) {
|
|
||||||
c.getChildren().forEach(child -> {
|
|
||||||
|
|
||||||
int cWidth = c.getWidth() - 2 * margin;
|
|
||||||
int cHeight = c.getHeight() - 2 * margin;
|
|
||||||
|
|
||||||
child.setBounds(
|
|
||||||
c.getX() + margin,
|
|
||||||
c.getY() + margin,
|
|
||||||
cWidth,
|
|
||||||
cHeight
|
|
||||||
);
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Vec2i calculatePreferredSize(Component c) {
|
|
||||||
Vec2i result = new Vec2i(0, 0);
|
|
||||||
|
|
||||||
c.getChildren().stream()
|
|
||||||
.map(child -> child.getPreferredSize())
|
|
||||||
.forEach(size -> {
|
|
||||||
result.x = max(size.x, result.x);
|
|
||||||
result.y = max(size.y, result.y);
|
|
||||||
});
|
|
||||||
|
|
||||||
result.x += 2 * margin;
|
|
||||||
result.y += 2 * margin;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return getClass().getSimpleName() + "(" + margin + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -98,26 +98,15 @@ public class LayoutGrid implements Layout {
|
|||||||
if (!isSummed)
|
if (!isSummed)
|
||||||
throw new IllegalStateException("Not summed yet");
|
throw new IllegalStateException("Not summed yet");
|
||||||
|
|
||||||
int width, height;
|
|
||||||
|
|
||||||
if (column == columns.length - 1) {
|
|
||||||
width = parent.getWidth() - margin - columns[column];
|
|
||||||
} else {
|
|
||||||
width = columns[column + 1] - columns[column] - gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (row == rows.length - 1) {
|
|
||||||
height = parent.getHeight() - margin - rows[row];
|
|
||||||
} else {
|
|
||||||
height = rows[row + 1] - rows[row] - gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
child.setBounds(
|
child.setBounds(
|
||||||
parent.getX() + columns[column],
|
parent.getX() + columns[column],
|
||||||
parent.getY() + parent.getHeight() - (rows[row] + height),
|
parent.getY() + rows[row],
|
||||||
|
|
||||||
width,
|
(column != (columns.length - 1) ? (columns[column + 1] - columns[column] - gap)
|
||||||
height
|
: (parent.getWidth() - margin - columns[column])),
|
||||||
|
|
||||||
|
(row != (rows.length - 1) ? (rows[row + 1] - rows[row] - gap)
|
||||||
|
: (parent.getHeight() - margin - rows[row]))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,9 +132,10 @@ public class LayoutGrid implements Layout {
|
|||||||
GridDimensions grid = calculateGrid(c);
|
GridDimensions grid = calculateGrid(c);
|
||||||
grid.sum();
|
grid.sum();
|
||||||
|
|
||||||
|
int[] coords;
|
||||||
for (Component child : c.getChildren()) {
|
for (Component child : c.getChildren()) {
|
||||||
Vec2i coords = (Vec2i) child.getLayoutHint();
|
coords = (int[]) child.getLayoutHint();
|
||||||
grid.setBounds(coords.x, coords.y, child, c);
|
grid.setBounds(coords[0], coords[1], child, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,10 +149,11 @@ public class LayoutGrid implements Layout {
|
|||||||
|
|
||||||
private GridDimensions calculateGrid(Component parent) {
|
private GridDimensions calculateGrid(Component parent) {
|
||||||
GridDimensions result = new GridDimensions();
|
GridDimensions result = new GridDimensions();
|
||||||
|
int[] coords;
|
||||||
|
|
||||||
for (Component child : parent.getChildren()) {
|
for (Component child : parent.getChildren()) {
|
||||||
Vec2i coords = (Vec2i) child.getLayoutHint();
|
coords = (int[]) child.getLayoutHint();
|
||||||
result.add(coords.x, coords.y, child.getPreferredSize());
|
result.add(coords[0], coords[1], child.getPreferredSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -1,114 +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.client.graphics.gui.menu;
|
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
import glm.vec._2.i.Vec2i;
|
|
||||||
import ru.windcorp.progressia.client.graphics.GUI;
|
|
||||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.ColorScheme;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.Label;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.Layout;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.Panel;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
|
|
||||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
|
||||||
import ru.windcorp.progressia.client.localization.MutableString;
|
|
||||||
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
|
||||||
|
|
||||||
public class MenuLayer extends GUILayer {
|
|
||||||
|
|
||||||
private final Component content;
|
|
||||||
private final Component background;
|
|
||||||
|
|
||||||
private final Runnable closeAction = () -> {
|
|
||||||
GUI.removeLayer(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
public MenuLayer(String name, Component content) {
|
|
||||||
super(name, new LayoutFill(0));
|
|
||||||
|
|
||||||
setCursorPolicy(CursorPolicy.REQUIRE);
|
|
||||||
|
|
||||||
this.background = new Panel(name + ".Background", new LayoutAlign(10), ColorScheme.get("Core:MenuLayerTint"), null);
|
|
||||||
this.content = content;
|
|
||||||
|
|
||||||
background.addChild(content);
|
|
||||||
getRoot().addChild(background);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MenuLayer(String name, Layout contentLayout) {
|
|
||||||
this(name, new Panel(name + ".Content", contentLayout));
|
|
||||||
}
|
|
||||||
|
|
||||||
public MenuLayer(String name) {
|
|
||||||
this(name, new LayoutVertical(20, 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Component getContent() {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Component getBackground() {
|
|
||||||
return background;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void addTitle() {
|
|
||||||
String translationKey = "Layer" + getName() + ".Title";
|
|
||||||
MutableString titleText = new MutableStringLocalized(translationKey);
|
|
||||||
Font titleFont = new Font().deriveBold().withColor(ColorScheme.get("Core:Text")).withAlign(0.5f);
|
|
||||||
|
|
||||||
Label label = new Label(getName() + ".Title", titleFont, titleText);
|
|
||||||
getContent().addChild(label);
|
|
||||||
|
|
||||||
Panel panel = new Panel(getName() + ".Title.Underscore", null, ColorScheme.get("Core:Accent"), null);
|
|
||||||
panel.setLayout(new LayoutFill() {
|
|
||||||
@Override
|
|
||||||
public Vec2i calculatePreferredSize(Component c) {
|
|
||||||
return new Vec2i(label.getPreferredSize().x + 40, 4);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
getContent().addChild(panel);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Runnable getCloseAction() {
|
|
||||||
return closeAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleInput(InputEvent event) {
|
|
||||||
|
|
||||||
if (!event.isConsumed()) {
|
|
||||||
if (event instanceof KeyEvent) {
|
|
||||||
KeyEvent keyEvent = (KeyEvent) event;
|
|
||||||
if (keyEvent.isPress() && keyEvent.getKey() == GLFW.GLFW_KEY_ESCAPE) {
|
|
||||||
getCloseAction().run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.handleInput(event);
|
|
||||||
event.consume();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
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 {
|
||||||
@ -80,10 +81,7 @@ public class CursorMoveEvent extends CursorEvent {
|
|||||||
return getNewY() - getPreviousY();
|
return getNewY() - getPreviousY();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vec2d getChange(Vec2d result) {
|
public Vec2 getChange(Vec2 result) {
|
||||||
if (result == null) {
|
|
||||||
result = new Vec2d();
|
|
||||||
}
|
|
||||||
return result.set(getChangeX(), getChangeY());
|
return result.set(getChangeX(), getChangeY());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,32 +18,10 @@
|
|||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
@ -58,16 +36,4 @@ 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,75 +18,16 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.input;
|
package ru.windcorp.progressia.client.graphics.input;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.function.Predicate;
|
||||||
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;
|
||||||
|
|
||||||
public KeyMatcher(int key, int mods) {
|
protected 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;
|
||||||
}
|
}
|
||||||
@ -102,15 +43,6 @@ 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;
|
||||||
}
|
}
|
||||||
@ -119,24 +51,48 @@ public class KeyMatcher {
|
|||||||
return mods;
|
return mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyMatcher with(int modifier) {
|
public static KeyMatcher.Builder of(int key) {
|
||||||
return new KeyMatcher(key, mods | modifier);
|
return new KeyMatcher.Builder(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyMatcher withShift() {
|
public static class Builder {
|
||||||
return with(GLFW.GLFW_MOD_SHIFT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyMatcher withCtrl() {
|
private final int key;
|
||||||
return with(GLFW.GLFW_MOD_CONTROL);
|
private int mods = 0;
|
||||||
}
|
|
||||||
|
|
||||||
public KeyMatcher withAlt() {
|
public Builder(int key) {
|
||||||
return with(GLFW.GLFW_MOD_ALT);
|
this.key = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
@ -16,38 +16,47 @@
|
|||||||
* 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.comms.controls;
|
package ru.windcorp.progressia.client.graphics.input.bus;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
import ru.windcorp.progressia.common.comms.controls.PacketControl;
|
|
||||||
|
|
||||||
public class ControlTriggerLocalLambda extends ControlTriggerInputBased {
|
public class Input {
|
||||||
|
|
||||||
private final Predicate<InputEvent> predicate;
|
public static enum Target {
|
||||||
private final Consumer<InputEvent> action;
|
FOCUSED, HOVERED, ALL
|
||||||
|
|
||||||
public ControlTriggerLocalLambda(
|
|
||||||
String id,
|
|
||||||
Predicate<InputEvent> predicate,
|
|
||||||
Consumer<InputEvent> action
|
|
||||||
) {
|
|
||||||
super(id);
|
|
||||||
|
|
||||||
this.predicate = predicate;
|
|
||||||
this.action = action;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private InputEvent event;
|
||||||
public PacketControl onInputEvent(InputEvent event) {
|
|
||||||
if (!predicate.test(event))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
action.accept(event);
|
private boolean isConsumed;
|
||||||
|
|
||||||
return null;
|
private Target target;
|
||||||
|
|
||||||
|
protected void initialize(InputEvent event, Target target) {
|
||||||
|
this.event = event;
|
||||||
|
this.target = target;
|
||||||
|
|
||||||
|
this.isConsumed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputEvent getEvent() {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConsumed() {
|
||||||
|
return isConsumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConsumed(boolean isConsumed) {
|
||||||
|
this.isConsumed = isConsumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void consume() {
|
||||||
|
setConsumed(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Target getTarget() {
|
||||||
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -20,396 +20,68 @@ 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 dropIfConsumed,
|
boolean handleConsumed,
|
||||||
YesNoDefault dropIfNotHovered,
|
|
||||||
YesNoDefault dropIfNotFocused,
|
|
||||||
InputListener<?> listener
|
InputListener<?> listener
|
||||||
) {
|
) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.dropIfConsumed = dropIfConsumed;
|
this.handleConsumed = handleConsumed;
|
||||||
this.dropIfNotHovered = dropIfNotHovered;
|
|
||||||
this.dropIfNotFocused = dropIfNotFocused;
|
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handles(InputEvent input) {
|
private boolean handles(Input input) {
|
||||||
if (dropIfConsumed && input.isConsumed())
|
return (!input.isConsumed() || handleConsumed) &&
|
||||||
return false;
|
type.isInstance(input.getEvent());
|
||||||
|
|
||||||
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(InputEvent event) {
|
public void handle(Input input) {
|
||||||
if (handles(event)) {
|
if (handles(input)) {
|
||||||
// A runtime check of types has been performed; this is safe.
|
boolean consumed = ((InputListener<InputEvent>) listener)
|
||||||
InputListener<InputEvent> castListener = (InputListener<InputEvent>) listener;
|
.handle(
|
||||||
|
(InputEvent) type.cast(input.getEvent())
|
||||||
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
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
input.setConsumed(consumed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
||||||
* Creates a new input bus that consults the specified {@link Component} to
|
listeners.forEach(l -> l.handle(input));
|
||||||
* 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,
|
||||||
InputListener<T> listener,
|
boolean handlesConsumed,
|
||||||
Option... options
|
InputListener<T> listener
|
||||||
) {
|
) {
|
||||||
Objects.requireNonNull(type, "type");
|
listeners.add(new WrappedListener(type, handlesConsumed, listener));
|
||||||
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(
|
||||||
* Registers a {@link KeyEvent} listener on this bus. An event has to match
|
Class<? extends T> type,
|
||||||
* the provided {@link KeyMatcher} to be delivered to the listener.
|
InputListener<T> listener
|
||||||
* <p>
|
) {
|
||||||
* Zero or more {@link Option}s may be specified to enable or disable the
|
register(type, false, listener);
|
||||||
* 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> {
|
||||||
|
|
||||||
void handle(T event);
|
boolean handle(T event);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,23 +18,23 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.model;
|
package ru.windcorp.progressia.client.graphics.model;
|
||||||
|
|
||||||
import static ru.windcorp.progressia.common.world.rels.AbsFace.*;
|
import static ru.windcorp.progressia.common.world.block.BlockFace.*;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
import glm.vec._3.Vec3;
|
import glm.vec._3.Vec3;
|
||||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||||
|
|
||||||
class BlockFaceVectors {
|
class BlockFaceVectors {
|
||||||
|
|
||||||
private static BlockFaceVectors createInner(BlockFaceVectors outer) {
|
private static BlockFaceVectors createInner(BlockFaceVectors outer) {
|
||||||
ImmutableMap.Builder<AbsFace, Vec3> originBuilder = ImmutableMap.builder();
|
ImmutableMap.Builder<BlockFace, Vec3> originBuilder = ImmutableMap.builder();
|
||||||
|
|
||||||
ImmutableMap.Builder<AbsFace, Vec3> widthBuilder = ImmutableMap.builder();
|
ImmutableMap.Builder<BlockFace, Vec3> widthBuilder = ImmutableMap.builder();
|
||||||
|
|
||||||
ImmutableMap.Builder<AbsFace, Vec3> heightBuilder = ImmutableMap.builder();
|
ImmutableMap.Builder<BlockFace, Vec3> heightBuilder = ImmutableMap.builder();
|
||||||
|
|
||||||
for (AbsFace face : getFaces()) {
|
for (BlockFace face : getFaces()) {
|
||||||
Vec3 width = outer.getWidth(face);
|
Vec3 width = outer.getWidth(face);
|
||||||
Vec3 height = outer.getHeight(face);
|
Vec3 height = outer.getHeight(face);
|
||||||
|
|
||||||
@ -59,36 +59,36 @@ class BlockFaceVectors {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
OUTER = new BlockFaceVectors(
|
OUTER = new BlockFaceVectors(
|
||||||
ImmutableMap.<AbsFace, Vec3>builder()
|
ImmutableMap.<BlockFace, Vec3>builder()
|
||||||
|
|
||||||
.put(POS_Z, new Vec3(-0.5f, +0.5f, +0.5f))
|
.put(TOP, new Vec3(-0.5f, +0.5f, +0.5f))
|
||||||
.put(NEG_Z, new Vec3(-0.5f, -0.5f, -0.5f))
|
.put(BOTTOM, new Vec3(-0.5f, -0.5f, -0.5f))
|
||||||
.put(POS_X, new Vec3(+0.5f, -0.5f, -0.5f))
|
.put(NORTH, new Vec3(+0.5f, -0.5f, -0.5f))
|
||||||
.put(NEG_X, new Vec3(-0.5f, +0.5f, -0.5f))
|
.put(SOUTH, new Vec3(-0.5f, +0.5f, -0.5f))
|
||||||
.put(POS_Y, new Vec3(+0.5f, +0.5f, -0.5f))
|
.put(WEST, new Vec3(+0.5f, +0.5f, -0.5f))
|
||||||
.put(NEG_Y, new Vec3(-0.5f, -0.5f, -0.5f))
|
.put(EAST, new Vec3(-0.5f, -0.5f, -0.5f))
|
||||||
|
|
||||||
.build(),
|
.build(),
|
||||||
|
|
||||||
ImmutableMap.<AbsFace, Vec3>builder()
|
ImmutableMap.<BlockFace, Vec3>builder()
|
||||||
|
|
||||||
.put(POS_Z, new Vec3(0, -1, 0))
|
.put(TOP, new Vec3(0, -1, 0))
|
||||||
.put(NEG_Z, new Vec3(0, +1, 0))
|
.put(BOTTOM, new Vec3(0, +1, 0))
|
||||||
.put(POS_X, new Vec3(0, +1, 0))
|
.put(NORTH, new Vec3(0, +1, 0))
|
||||||
.put(NEG_X, new Vec3(0, -1, 0))
|
.put(SOUTH, new Vec3(0, -1, 0))
|
||||||
.put(POS_Y, new Vec3(-1, 0, 0))
|
.put(WEST, new Vec3(-1, 0, 0))
|
||||||
.put(NEG_Y, new Vec3(+1, 0, 0))
|
.put(EAST, new Vec3(+1, 0, 0))
|
||||||
|
|
||||||
.build(),
|
.build(),
|
||||||
|
|
||||||
ImmutableMap.<AbsFace, Vec3>builder()
|
ImmutableMap.<BlockFace, Vec3>builder()
|
||||||
|
|
||||||
.put(POS_Z, new Vec3(+1, 0, 0))
|
.put(TOP, new Vec3(+1, 0, 0))
|
||||||
.put(NEG_Z, new Vec3(+1, 0, 0))
|
.put(BOTTOM, new Vec3(+1, 0, 0))
|
||||||
.put(POS_X, new Vec3(0, 0, +1))
|
.put(NORTH, new Vec3(0, 0, +1))
|
||||||
.put(NEG_X, new Vec3(0, 0, +1))
|
.put(SOUTH, new Vec3(0, 0, +1))
|
||||||
.put(POS_Y, new Vec3(0, 0, +1))
|
.put(WEST, new Vec3(0, 0, +1))
|
||||||
.put(NEG_Y, new Vec3(0, 0, +1))
|
.put(EAST, new Vec3(0, 0, +1))
|
||||||
|
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
@ -100,29 +100,29 @@ class BlockFaceVectors {
|
|||||||
return inner ? INNER : OUTER;
|
return inner ? INNER : OUTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ImmutableMap<AbsFace, Vec3> origins;
|
private final ImmutableMap<BlockFace, Vec3> origins;
|
||||||
private final ImmutableMap<AbsFace, Vec3> widths;
|
private final ImmutableMap<BlockFace, Vec3> widths;
|
||||||
private final ImmutableMap<AbsFace, Vec3> heights;
|
private final ImmutableMap<BlockFace, Vec3> heights;
|
||||||
|
|
||||||
public BlockFaceVectors(
|
public BlockFaceVectors(
|
||||||
ImmutableMap<AbsFace, Vec3> origins,
|
ImmutableMap<BlockFace, Vec3> origins,
|
||||||
ImmutableMap<AbsFace, Vec3> widths,
|
ImmutableMap<BlockFace, Vec3> widths,
|
||||||
ImmutableMap<AbsFace, Vec3> heights
|
ImmutableMap<BlockFace, Vec3> heights
|
||||||
) {
|
) {
|
||||||
this.origins = origins;
|
this.origins = origins;
|
||||||
this.widths = widths;
|
this.widths = widths;
|
||||||
this.heights = heights;
|
this.heights = heights;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vec3 getOrigin(AbsFace face) {
|
public Vec3 getOrigin(BlockFace face) {
|
||||||
return origins.get(face);
|
return origins.get(face);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vec3 getWidth(AbsFace face) {
|
public Vec3 getWidth(BlockFace face) {
|
||||||
return widths.get(face);
|
return widths.get(face);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vec3 getHeight(AbsFace face) {
|
public Vec3 getHeight(BlockFace face) {
|
||||||
return heights.get(face);
|
return heights.get(face);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ import java.util.Objects;
|
|||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
|
|
||||||
public class ShapePart implements Comparable<ShapePart> {
|
public class Face implements Comparable<Face> {
|
||||||
|
|
||||||
private static final ShortBuffer GENERATE_SUCCESSIVE_LATER = null;
|
private static final ShortBuffer GENERATE_SUCCESSIVE_LATER = null;
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ public class ShapePart implements Comparable<ShapePart> {
|
|||||||
private ShortBuffer userIndices;
|
private ShortBuffer userIndices;
|
||||||
private boolean userIndicesUpdated = true;
|
private boolean userIndicesUpdated = true;
|
||||||
|
|
||||||
public ShapePart(
|
public Face(
|
||||||
Texture texture,
|
Texture texture,
|
||||||
ByteBuffer vertices,
|
ByteBuffer vertices,
|
||||||
ShortBuffer indices
|
ShortBuffer indices
|
||||||
@ -50,7 +50,7 @@ public class ShapePart implements Comparable<ShapePart> {
|
|||||||
setIndices(indices);
|
setIndices(indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShapePart(
|
public Face(
|
||||||
Texture texture,
|
Texture texture,
|
||||||
ByteBuffer vertices
|
ByteBuffer vertices
|
||||||
) {
|
) {
|
||||||
@ -155,7 +155,7 @@ public class ShapePart implements Comparable<ShapePart> {
|
|||||||
return vertices;
|
return vertices;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShapePart setVertices(ByteBuffer vertices) {
|
public Face setVertices(ByteBuffer vertices) {
|
||||||
this.vertices = Objects.requireNonNull(vertices, "vertices");
|
this.vertices = Objects.requireNonNull(vertices, "vertices");
|
||||||
markForVertexUpdate();
|
markForVertexUpdate();
|
||||||
return this;
|
return this;
|
||||||
@ -202,7 +202,7 @@ public class ShapePart implements Comparable<ShapePart> {
|
|||||||
return userIndices.remaining();
|
return userIndices.remaining();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShapePart setIndices(ShortBuffer indices) {
|
public Face setIndices(ShortBuffer indices) {
|
||||||
if (indices == null) {
|
if (indices == null) {
|
||||||
indices = GENERATE_SUCCESSIVE_LATER;
|
indices = GENERATE_SUCCESSIVE_LATER;
|
||||||
}
|
}
|
||||||
@ -245,7 +245,7 @@ public class ShapePart implements Comparable<ShapePart> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(ShapePart o) {
|
public int compareTo(Face o) {
|
||||||
return Integer.compare(getSortingIndex(), o.getSortingIndex());
|
return Integer.compare(getSortingIndex(), o.getSortingIndex());
|
||||||
}
|
}
|
||||||
|
|
@ -21,13 +21,13 @@ package ru.windcorp.progressia.client.graphics.model;
|
|||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
import ru.windcorp.progressia.client.graphics.texture.TexturePrimitive;
|
import ru.windcorp.progressia.client.graphics.texture.TexturePrimitive;
|
||||||
|
|
||||||
public class ShapePartGroup {
|
public class FaceGroup {
|
||||||
|
|
||||||
private final TexturePrimitive texture;
|
private final TexturePrimitive texture;
|
||||||
private final int indexCount;
|
private final int indexCount;
|
||||||
private final int byteOffsetOfIndices;
|
private final int byteOffsetOfIndices;
|
||||||
|
|
||||||
ShapePartGroup(ShapePart[] faces, int start, int end) {
|
FaceGroup(Face[] faces, int start, int end) {
|
||||||
|
|
||||||
Texture t = faces[start].getTexture();
|
Texture t = faces[start].getTexture();
|
||||||
this.texture = t == null ? null : t.getSprite().getPrimitive();
|
this.texture = t == null ? null : t.getSprite().getPrimitive();
|
||||||
@ -36,7 +36,7 @@ public class ShapePartGroup {
|
|||||||
int indexCount = 0;
|
int indexCount = 0;
|
||||||
|
|
||||||
for (int i = start; i < end; ++i) {
|
for (int i = start; i < end; ++i) {
|
||||||
ShapePart face = faces[i];
|
Face face = faces[i];
|
||||||
|
|
||||||
assert this.texture == null
|
assert this.texture == null
|
||||||
? (face.getTexture() == null)
|
? (face.getTexture() == null)
|
@ -25,15 +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.block.BlockFace;
|
||||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
|
||||||
|
|
||||||
public class ShapeParts {
|
public class Faces {
|
||||||
|
|
||||||
private ShapeParts() {
|
private Faces() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ShapePart createRectangle(
|
public static Face createRectangle(
|
||||||
ShapeRenderProgram program,
|
ShapeRenderProgram program,
|
||||||
Texture texture,
|
Texture texture,
|
||||||
Vec4 colorMultiplier,
|
Vec4 colorMultiplier,
|
||||||
@ -41,66 +40,26 @@ public class ShapeParts {
|
|||||||
Vec3 width,
|
Vec3 width,
|
||||||
Vec3 height,
|
Vec3 height,
|
||||||
boolean flip
|
boolean flip
|
||||||
) {
|
|
||||||
return createRectangle(program, texture, colorMultiplier, origin, width, height, flip, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ShapePart createRectangle(
|
|
||||||
ShapeRenderProgram program,
|
|
||||||
Texture texture,
|
|
||||||
Vec4 colorMultiplier,
|
|
||||||
Vec3 origin,
|
|
||||||
Vec3 width,
|
|
||||||
Vec3 height,
|
|
||||||
boolean flip,
|
|
||||||
Vec3 forcedNormals
|
|
||||||
) {
|
) {
|
||||||
VertexBuilder builder = program.getVertexBuilder();
|
VertexBuilder builder = program.getVertexBuilder();
|
||||||
|
|
||||||
if (forcedNormals != null && builder instanceof WRPVertexBuilder) {
|
builder.addVertex(
|
||||||
((WRPVertexBuilder) builder).addVertex(
|
origin,
|
||||||
origin,
|
colorMultiplier,
|
||||||
colorMultiplier,
|
new Vec2(0, 0)
|
||||||
new Vec2(0, 0),
|
).addVertex(
|
||||||
forcedNormals
|
origin.add_(height),
|
||||||
);
|
colorMultiplier,
|
||||||
((WRPVertexBuilder) builder).addVertex(
|
new Vec2(0, 1)
|
||||||
origin.add_(height),
|
).addVertex(
|
||||||
colorMultiplier,
|
origin.add_(width),
|
||||||
new Vec2(0, 1),
|
colorMultiplier,
|
||||||
forcedNormals
|
new Vec2(1, 0)
|
||||||
);
|
).addVertex(
|
||||||
((WRPVertexBuilder) builder).addVertex(
|
origin.add_(width).add(height),
|
||||||
origin.add_(width),
|
colorMultiplier,
|
||||||
colorMultiplier,
|
new Vec2(1, 1)
|
||||||
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[] {
|
||||||
@ -123,19 +82,19 @@ public class ShapeParts {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return new ShapePart(
|
return new Face(
|
||||||
texture,
|
texture,
|
||||||
builder.assemble(),
|
builder.assemble(),
|
||||||
buffer
|
buffer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ShapePart createBlockFace(
|
public static Face createBlockFace(
|
||||||
ShapeRenderProgram program,
|
ShapeRenderProgram program,
|
||||||
Texture texture,
|
Texture texture,
|
||||||
Vec4 colorMultiplier,
|
Vec4 colorMultiplier,
|
||||||
Vec3 blockCenter,
|
Vec3 blockCenter,
|
||||||
AbsFace face,
|
BlockFace face,
|
||||||
boolean inner
|
boolean inner
|
||||||
) {
|
) {
|
||||||
BlockFaceVectors vectors = BlockFaceVectors.get(inner);
|
BlockFaceVectors vectors = BlockFaceVectors.get(inner);
|
@ -30,10 +30,10 @@ import ru.windcorp.progressia.client.graphics.backend.VertexBufferObject;
|
|||||||
public class Shape implements Renderable {
|
public class Shape implements Renderable {
|
||||||
|
|
||||||
private final ShapeRenderProgram program;
|
private final ShapeRenderProgram program;
|
||||||
private final ShapePart[] parts;
|
private final Face[] faces;
|
||||||
private final Usage usage;
|
private final Usage usage;
|
||||||
|
|
||||||
private ShapePartGroup[] groups;
|
private FaceGroup[] groups;
|
||||||
|
|
||||||
private ByteBuffer vertices;
|
private ByteBuffer vertices;
|
||||||
private ShortBuffer indices;
|
private ShortBuffer indices;
|
||||||
@ -45,33 +45,33 @@ public class Shape implements Renderable {
|
|||||||
private VertexBufferObject verticesVbo;
|
private VertexBufferObject verticesVbo;
|
||||||
private VertexBufferObject indicesVbo;
|
private VertexBufferObject indicesVbo;
|
||||||
|
|
||||||
public Shape(Usage usage, ShapeRenderProgram program, ShapePart... parts) {
|
public Shape(Usage usage, ShapeRenderProgram program, Face... faces) {
|
||||||
this.program = program;
|
this.program = program;
|
||||||
this.parts = parts;
|
this.faces = faces;
|
||||||
this.usage = usage;
|
this.usage = usage;
|
||||||
|
|
||||||
configureParts();
|
configureFaces();
|
||||||
program.preprocess(this);
|
program.preprocess(this);
|
||||||
|
|
||||||
assembleBuffers();
|
assembleBuffers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureParts() {
|
private void configureFaces() {
|
||||||
for (ShapePart part : parts) {
|
for (Face face : faces) {
|
||||||
part.setShape(this);
|
face.setShape(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assembleBuffers() {
|
private void assembleBuffers() {
|
||||||
// TODO optimize: only update faces that requested it
|
// TODO optimize: only update faces that requested it
|
||||||
|
|
||||||
sortParts();
|
sortFaces();
|
||||||
resizeBuffers();
|
resizeBuffers();
|
||||||
|
|
||||||
for (ShapePart part : parts) {
|
for (Face face : faces) {
|
||||||
assembleVertices(part);
|
assembleVertices(face);
|
||||||
assembleIndices(part);
|
assembleIndices(face);
|
||||||
part.resetUpdateFlags();
|
face.resetUpdateFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.vertices.flip();
|
this.vertices.flip();
|
||||||
@ -85,110 +85,110 @@ public class Shape implements Renderable {
|
|||||||
|
|
||||||
private void resizeBuffers() {
|
private void resizeBuffers() {
|
||||||
int verticesRequired = 0, indicesRequired = 0;
|
int verticesRequired = 0, indicesRequired = 0;
|
||||||
for (ShapePart part : parts) {
|
for (Face face : faces) {
|
||||||
verticesRequired += part.getVertices().remaining();
|
verticesRequired += face.getVertices().remaining();
|
||||||
indicesRequired += part.getIndices().remaining();
|
indicesRequired += face.getIndices().remaining();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vertices == null || vertices.capacity() < verticesRequired) {
|
if (this.vertices == null || vertices.capacity() < verticesRequired) {
|
||||||
this.vertices = BufferUtils.createByteBuffer(verticesRequired);
|
this.vertices = BufferUtils.createByteBuffer(verticesRequired);
|
||||||
} else {
|
} else {
|
||||||
vertices.position(0).limit(verticesRequired);
|
this.vertices.position(0).limit(verticesRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (indices == null || indices.capacity() < indicesRequired) {
|
if (this.indices == null || this.indices.capacity() < indicesRequired) {
|
||||||
this.indices = BufferUtils.createShortBuffer(indicesRequired);
|
this.indices = BufferUtils.createShortBuffer(indicesRequired);
|
||||||
} else {
|
} else {
|
||||||
indices.position(0).limit(indicesRequired);
|
this.indices.position(0).limit(indicesRequired);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assembleVertices(ShapePart part) {
|
private void assembleVertices(Face face) {
|
||||||
part.locationOfVertices = this.vertices.position();
|
face.locationOfVertices = this.vertices.position();
|
||||||
|
|
||||||
insertVertices(part);
|
insertVertices(face);
|
||||||
linkVerticesWith(part);
|
linkVerticesWith(face);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertVertices(ShapePart part) {
|
private void insertVertices(Face face) {
|
||||||
ByteBuffer partVertices = part.getVertices();
|
ByteBuffer faceVertices = face.getVertices();
|
||||||
|
|
||||||
partVertices.mark();
|
faceVertices.mark();
|
||||||
this.vertices.put(partVertices);
|
this.vertices.put(faceVertices);
|
||||||
partVertices.reset();
|
faceVertices.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void linkVerticesWith(ShapePart part) {
|
private void linkVerticesWith(Face face) {
|
||||||
int limit = vertices.limit();
|
int limit = vertices.limit();
|
||||||
int position = vertices.position();
|
int position = vertices.position();
|
||||||
|
|
||||||
vertices.limit(position).position(part.getLocationOfVertices());
|
vertices.limit(position).position(face.getLocationOfVertices());
|
||||||
part.vertices = vertices.slice();
|
face.vertices = vertices.slice();
|
||||||
|
|
||||||
vertices.position(position).limit(limit);
|
vertices.position(position).limit(limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assembleIndices(ShapePart part) {
|
private void assembleIndices(Face face) {
|
||||||
short vertexOffset = (short) (part.getLocationOfVertices() / program.getBytesPerVertex());
|
short vertexOffset = (short) (face.getLocationOfVertices() / program.getBytesPerVertex());
|
||||||
|
|
||||||
part.locationOfIndices = indices.position();
|
face.locationOfIndices = indices.position();
|
||||||
|
|
||||||
ShortBuffer partIndices = part.getIndices();
|
ShortBuffer faceIndices = face.getIndices();
|
||||||
|
|
||||||
if (partIndices == null) {
|
if (faceIndices == null) {
|
||||||
for (int i = 0; i < part.getVertexCount(); ++i) {
|
for (int i = 0; i < face.getVertexCount(); ++i) {
|
||||||
this.indices.put((short) (vertexOffset + i));
|
this.indices.put((short) (vertexOffset + i));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = partIndices.position(); i < partIndices.limit(); ++i) {
|
for (int i = faceIndices.position(); i < faceIndices.limit(); ++i) {
|
||||||
short partIndex = partIndices.get(i);
|
short faceIndex = faceIndices.get(i);
|
||||||
partIndex += vertexOffset;
|
faceIndex += vertexOffset;
|
||||||
this.indices.put(partIndex);
|
this.indices.put(faceIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sortParts() {
|
private void sortFaces() {
|
||||||
Arrays.sort(parts);
|
Arrays.sort(faces);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assembleGroups() {
|
private void assembleGroups() {
|
||||||
int unique = countUniqueParts();
|
int unique = countUniqueFaces();
|
||||||
this.groups = new ShapePartGroup[unique];
|
this.groups = new FaceGroup[unique];
|
||||||
|
|
||||||
if (parts.length == 0)
|
if (faces.length == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int previousHandle = parts[0].getSortingIndex();
|
int previousHandle = faces[0].getSortingIndex();
|
||||||
int start = 0;
|
int start = 0;
|
||||||
int groupIndex = 0;
|
int groupIndex = 0;
|
||||||
|
|
||||||
for (int i = 1; i < parts.length; ++i) {
|
for (int i = 1; i < faces.length; ++i) {
|
||||||
if (previousHandle != parts[i].getSortingIndex()) {
|
if (previousHandle != faces[i].getSortingIndex()) {
|
||||||
|
|
||||||
groups[groupIndex] = new ShapePartGroup(parts, start, i);
|
groups[groupIndex] = new FaceGroup(faces, start, i);
|
||||||
start = i;
|
start = i;
|
||||||
groupIndex++;
|
groupIndex++;
|
||||||
|
|
||||||
previousHandle = parts[i].getSortingIndex();
|
previousHandle = faces[i].getSortingIndex();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert groupIndex == groups.length - 1;
|
assert groupIndex == groups.length - 1;
|
||||||
groups[groupIndex] = new ShapePartGroup(parts, start, parts.length);
|
groups[groupIndex] = new FaceGroup(faces, start, faces.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int countUniqueParts() {
|
private int countUniqueFaces() {
|
||||||
if (parts.length == 0)
|
if (faces.length == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
int result = 1;
|
int result = 1;
|
||||||
int previousHandle = parts[0].getSortingIndex();
|
int previousHandle = faces[0].getSortingIndex();
|
||||||
|
|
||||||
for (int i = 1; i < parts.length; ++i) {
|
for (int i = 1; i < faces.length; ++i) {
|
||||||
if (previousHandle != parts[i].getSortingIndex()) {
|
if (previousHandle != faces[i].getSortingIndex()) {
|
||||||
result++;
|
result++;
|
||||||
previousHandle = parts[i].getSortingIndex();
|
previousHandle = faces[i].getSortingIndex();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,11 +238,11 @@ public class Shape implements Renderable {
|
|||||||
return program;
|
return program;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShapePart[] getParts() {
|
public Face[] getFaces() {
|
||||||
return parts;
|
return faces;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShapePartGroup[] getGroups() {
|
public FaceGroup[] getGroups() {
|
||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,248 +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.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());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -116,7 +116,7 @@ public class ShapeRenderProgram extends Program {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
enableAttributes();
|
enableAttributes();
|
||||||
for (ShapePartGroup group : shape.getGroups()) {
|
for (FaceGroup group : shape.getGroups()) {
|
||||||
renderFaceGroup(group);
|
renderFaceGroup(group);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -182,7 +182,7 @@ public class ShapeRenderProgram extends Program {
|
|||||||
indices.bind(BindTarget.ELEMENT_ARRAY);
|
indices.bind(BindTarget.ELEMENT_ARRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void renderFaceGroup(ShapePartGroup group) {
|
protected void renderFaceGroup(FaceGroup group) {
|
||||||
TexturePrimitive texture = group.getTexture();
|
TexturePrimitive texture = group.getTexture();
|
||||||
|
|
||||||
if (texture != null) {
|
if (texture != null) {
|
||||||
@ -206,12 +206,12 @@ public class ShapeRenderProgram extends Program {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void preprocess(Shape shape) {
|
public void preprocess(Shape shape) {
|
||||||
for (ShapePart face : shape.getParts()) {
|
for (Face face : shape.getFaces()) {
|
||||||
applySprites(face);
|
applySprites(face);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applySprites(ShapePart face) {
|
private void applySprites(Face face) {
|
||||||
if (face.getTexture() == null)
|
if (face.getTexture() == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -20,13 +20,11 @@ package ru.windcorp.progressia.client.graphics.model;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import glm.mat._4.Mat4;
|
|
||||||
import glm.vec._3.Vec3;
|
import glm.vec._3.Vec3;
|
||||||
import glm.vec._4.Vec4;
|
import glm.vec._4.Vec4;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
import ru.windcorp.progressia.common.util.VectorUtil;
|
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
|
||||||
|
|
||||||
public class Shapes {
|
public class Shapes {
|
||||||
|
|
||||||
@ -52,7 +50,7 @@ public class Shapes {
|
|||||||
boolean flip
|
boolean flip
|
||||||
) {
|
) {
|
||||||
|
|
||||||
ShapePart top = ShapeParts.createRectangle(
|
Face top = Faces.createRectangle(
|
||||||
program,
|
program,
|
||||||
topTexture,
|
topTexture,
|
||||||
colorMultiplier,
|
colorMultiplier,
|
||||||
@ -62,7 +60,7 @@ public class Shapes {
|
|||||||
flip
|
flip
|
||||||
);
|
);
|
||||||
|
|
||||||
ShapePart bottom = ShapeParts.createRectangle(
|
Face bottom = Faces.createRectangle(
|
||||||
program,
|
program,
|
||||||
bottomTexture,
|
bottomTexture,
|
||||||
colorMultiplier,
|
colorMultiplier,
|
||||||
@ -72,7 +70,7 @@ public class Shapes {
|
|||||||
flip
|
flip
|
||||||
);
|
);
|
||||||
|
|
||||||
ShapePart north = ShapeParts.createRectangle(
|
Face north = Faces.createRectangle(
|
||||||
program,
|
program,
|
||||||
northTexture,
|
northTexture,
|
||||||
colorMultiplier,
|
colorMultiplier,
|
||||||
@ -82,7 +80,7 @@ public class Shapes {
|
|||||||
flip
|
flip
|
||||||
);
|
);
|
||||||
|
|
||||||
ShapePart south = ShapeParts.createRectangle(
|
Face south = Faces.createRectangle(
|
||||||
program,
|
program,
|
||||||
southTexture,
|
southTexture,
|
||||||
colorMultiplier,
|
colorMultiplier,
|
||||||
@ -92,7 +90,7 @@ public class Shapes {
|
|||||||
flip
|
flip
|
||||||
);
|
);
|
||||||
|
|
||||||
ShapePart east = ShapeParts.createRectangle(
|
Face east = Faces.createRectangle(
|
||||||
program,
|
program,
|
||||||
eastTexture,
|
eastTexture,
|
||||||
colorMultiplier,
|
colorMultiplier,
|
||||||
@ -102,7 +100,7 @@ public class Shapes {
|
|||||||
flip
|
flip
|
||||||
);
|
);
|
||||||
|
|
||||||
ShapePart west = ShapeParts.createRectangle(
|
Face west = Faces.createRectangle(
|
||||||
program,
|
program,
|
||||||
westTexture,
|
westTexture,
|
||||||
colorMultiplier,
|
colorMultiplier,
|
||||||
@ -167,16 +165,16 @@ public class Shapes {
|
|||||||
|
|
||||||
public PppBuilder(
|
public PppBuilder(
|
||||||
ShapeRenderProgram program,
|
ShapeRenderProgram program,
|
||||||
Map<AbsFace, Texture> textureMap
|
Map<BlockFace, Texture> textureMap
|
||||||
) {
|
) {
|
||||||
this(
|
this(
|
||||||
program,
|
program,
|
||||||
textureMap.get(AbsFace.POS_Z),
|
textureMap.get(BlockFace.TOP),
|
||||||
textureMap.get(AbsFace.NEG_Z),
|
textureMap.get(BlockFace.BOTTOM),
|
||||||
textureMap.get(AbsFace.POS_X),
|
textureMap.get(BlockFace.NORTH),
|
||||||
textureMap.get(AbsFace.NEG_X),
|
textureMap.get(BlockFace.SOUTH),
|
||||||
textureMap.get(AbsFace.NEG_Y),
|
textureMap.get(BlockFace.EAST),
|
||||||
textureMap.get(AbsFace.POS_Y)
|
textureMap.get(BlockFace.WEST)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,34 +260,6 @@ public class Shapes {
|
|||||||
return this.setSize(size, size, size);
|
return this.setSize(size, size, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PppBuilder centerAt(float x, float y, float z) {
|
|
||||||
origin.set(x, y, z);
|
|
||||||
|
|
||||||
origin.mul(2);
|
|
||||||
origin.sub(width);
|
|
||||||
origin.sub(height);
|
|
||||||
origin.sub(depth);
|
|
||||||
origin.div(2);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PppBuilder apply(Mat4 transform) {
|
|
||||||
VectorUtil.applyMat4(origin, transform);
|
|
||||||
VectorUtil.rotateOnly(width, transform);
|
|
||||||
VectorUtil.rotateOnly(height, transform);
|
|
||||||
VectorUtil.rotateOnly(depth, transform);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PppBuilder scale(float factor) {
|
|
||||||
origin.mul(factor);
|
|
||||||
width.mul(factor);
|
|
||||||
height.mul(factor);
|
|
||||||
depth.mul(factor);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PppBuilder flip() {
|
public PppBuilder flip() {
|
||||||
this.flip = true;
|
this.flip = true;
|
||||||
return this;
|
return this;
|
||||||
|
@ -38,6 +38,10 @@ public class StaticModel extends Model {
|
|||||||
this.transforms = transforms;
|
this.transforms = transforms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StaticModel(Builder builder) {
|
||||||
|
this(builder.getParts(), builder.getTransforms());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Mat4 getTransform(int partIndex) {
|
protected Mat4 getTransform(int partIndex) {
|
||||||
return transforms[partIndex];
|
return transforms[partIndex];
|
||||||
@ -79,10 +83,6 @@ public class StaticModel extends Model {
|
|||||||
return transforms.toArray(new Mat4[transforms.size()]);
|
return transforms.toArray(new Mat4[transforms.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public StaticModel build() {
|
|
||||||
return new StaticModel(getParts(), getTransforms());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -163,7 +163,7 @@ public class Atlases {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final TextureSettings SETTINGS = new TextureSettings(false, false);
|
private static final TextureSettings SETTINGS = new TextureSettings(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();
|
||||||
|
@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.graphics.texture;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import glm.vec._2.Vec2;
|
import glm.vec._2.Vec2;
|
||||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||||
|
|
||||||
public class ComplexTexture {
|
public class ComplexTexture {
|
||||||
|
|
||||||
@ -54,14 +54,14 @@ public class ComplexTexture {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<AbsFace, Texture> getCuboidTextures(
|
public Map<BlockFace, Texture> getCuboidTextures(
|
||||||
int x,
|
int x,
|
||||||
int y,
|
int y,
|
||||||
int width,
|
int width,
|
||||||
int height,
|
int height,
|
||||||
int depth
|
int depth
|
||||||
) {
|
) {
|
||||||
return AbsFace.mapToFaces(
|
return BlockFace.mapToFaces(
|
||||||
get(
|
get(
|
||||||
x + depth + width,
|
x + depth + width,
|
||||||
y + height + depth,
|
y + height + depth,
|
||||||
@ -86,7 +86,7 @@ public class ComplexTexture {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<AbsFace, Texture> getCuboidTextures(
|
public Map<BlockFace, Texture> getCuboidTextures(
|
||||||
int x,
|
int x,
|
||||||
int y,
|
int y,
|
||||||
int size
|
int size
|
||||||
|
@ -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, false);
|
private static final TextureSettings SETTINGS = new TextureSettings(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;
|
||||||
|
|
||||||
public class TextureData {
|
class TextureData {
|
||||||
|
|
||||||
private final ByteBuffer data;
|
private final ByteBuffer data;
|
||||||
|
|
||||||
|
@ -28,7 +28,6 @@ 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 {
|
||||||
|
|
||||||
@ -42,16 +41,8 @@ public class TextureLoader {
|
|||||||
int width = readResult.getWidth();
|
int width = readResult.getWidth();
|
||||||
int height = readResult.getHeight();
|
int height = readResult.getHeight();
|
||||||
|
|
||||||
int bufferWidth;
|
int bufferWidth = BinUtil.roundToGreaterPowerOf2(width);
|
||||||
int bufferHeight;
|
int bufferHeight = BinUtil.roundToGreaterPowerOf2(height);
|
||||||
|
|
||||||
if (settings.allocateExactBuffer()) {
|
|
||||||
bufferWidth = width;
|
|
||||||
bufferHeight = height;
|
|
||||||
} else {
|
|
||||||
bufferWidth = BinUtil.roundToGreaterPowerOf2(width);
|
|
||||||
bufferHeight = BinUtil.roundToGreaterPowerOf2(height);
|
|
||||||
}
|
|
||||||
|
|
||||||
WritableRaster raster = TextureUtil.createRaster(
|
WritableRaster raster = TextureUtil.createRaster(
|
||||||
bufferWidth,
|
bufferWidth,
|
||||||
@ -108,12 +99,7 @@ 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,19 +21,13 @@ 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, boolean allocateExactBuffer) {
|
public TextureSettings(boolean isFiltered) {
|
||||||
this.isFiltered = isFiltered;
|
this.isFiltered = isFiltered;
|
||||||
this.allocateExactBuffer = allocateExactBuffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFiltered() {
|
public boolean isFiltered() {
|
||||||
return isFiltered;
|
return isFiltered;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean allocateExactBuffer() {
|
|
||||||
return allocateExactBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -29,9 +29,6 @@ import glm.mat._4.Mat4;
|
|||||||
import glm.vec._3.Vec3;
|
import glm.vec._3.Vec3;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||||
import ru.windcorp.progressia.client.graphics.world.Camera.Anchor.Mode;
|
import ru.windcorp.progressia.client.graphics.world.Camera.Anchor.Mode;
|
||||||
import ru.windcorp.progressia.client.world.entity.NPedModel;
|
|
||||||
import ru.windcorp.progressia.common.util.Matrices;
|
|
||||||
import ru.windcorp.progressia.common.util.Vectors;
|
|
||||||
|
|
||||||
public class Camera {
|
public class Camera {
|
||||||
|
|
||||||
@ -63,13 +60,13 @@ public class Camera {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3 getCameraPosition(Vec3 output);
|
void getCameraPosition(Vec3 output);
|
||||||
|
|
||||||
Vec3 getCameraVelocity(Vec3 output);
|
void getCameraVelocity(Vec3 output);
|
||||||
|
|
||||||
Vec3 getLookingAt(Vec3 output);
|
float getCameraYaw();
|
||||||
|
|
||||||
Vec3 getUpVector(Vec3 output);
|
float getCameraPitch();
|
||||||
|
|
||||||
Collection<Mode> getCameraModes();
|
Collection<Mode> getCameraModes();
|
||||||
|
|
||||||
@ -87,11 +84,14 @@ public class Camera {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
private final Vec3 lastAnchorPosition = new Vec3();
|
private final Vec3 lastAnchorPosition = new Vec3();
|
||||||
private final Vec3 lastAnchorLookingAt = new Vec3();
|
private float lastAnchorYaw;
|
||||||
private final Vec3 lastAnchorUpVector = new Vec3();
|
private float lastAnchorPitch;
|
||||||
|
|
||||||
private final Mat4 lastCameraMatrix = new Mat4();
|
private final Mat4 lastCameraMatrix = new Mat4();
|
||||||
|
|
||||||
|
private final Vec3 lastAnchorLookingAt = new Vec3();
|
||||||
|
private final Vec3 lastAnchorUp = new Vec3();
|
||||||
|
|
||||||
{
|
{
|
||||||
invalidateCache();
|
invalidateCache();
|
||||||
}
|
}
|
||||||
@ -108,9 +108,6 @@ public class Camera {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public void apply(WorldRenderHelper helper) {
|
public void apply(WorldRenderHelper helper) {
|
||||||
if (NPedModel.flag) {
|
|
||||||
// System.out.println("Camera.apply()");
|
|
||||||
}
|
|
||||||
applyPerspective(helper);
|
applyPerspective(helper);
|
||||||
rotateCoordinateSystem(helper);
|
rotateCoordinateSystem(helper);
|
||||||
|
|
||||||
@ -152,34 +149,26 @@ public class Camera {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void applyDirection(WorldRenderHelper helper) {
|
private void applyDirection(WorldRenderHelper helper) {
|
||||||
anchor.getLookingAt(lastAnchorLookingAt);
|
float pitch = anchor.getCameraPitch();
|
||||||
anchor.getUpVector(lastAnchorUpVector);
|
float yaw = anchor.getCameraYaw();
|
||||||
|
|
||||||
lookAt(helper.pushViewTransform());
|
helper.pushViewTransform()
|
||||||
}
|
.rotateY(-pitch)
|
||||||
|
.rotateZ(-yaw);
|
||||||
|
|
||||||
private void lookAt(Mat4 result) {
|
this.lastAnchorYaw = yaw;
|
||||||
Vec3 f = this.lastAnchorLookingAt;
|
this.lastAnchorPitch = pitch;
|
||||||
Vec3 s = Vectors.grab3();
|
|
||||||
Vec3 u = Vectors.grab3();
|
|
||||||
|
|
||||||
f.cross(this.lastAnchorUpVector, s);
|
this.lastAnchorLookingAt.set(
|
||||||
s.normalize();
|
cos(pitch) * cos(yaw),
|
||||||
|
cos(pitch) * sin(yaw),
|
||||||
s.cross(f, u);
|
sin(pitch)
|
||||||
|
);
|
||||||
Mat4 workspace = Matrices.grab4();
|
this.lastAnchorUp.set(
|
||||||
workspace.set(
|
cos(pitch + PI_F / 2) * cos(yaw),
|
||||||
+f.x, -s.x, +u.x, 0,
|
cos(pitch + PI_F / 2) * sin(yaw),
|
||||||
+f.y, -s.y, +u.y, 0,
|
sin(pitch + PI_F / 2)
|
||||||
+f.z, -s.z, +u.z, 0,
|
|
||||||
0, 0, 0, 1
|
|
||||||
);
|
);
|
||||||
result.mul(workspace);
|
|
||||||
Matrices.release(workspace);
|
|
||||||
|
|
||||||
Vectors.release(s);
|
|
||||||
Vectors.release(u);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyPosition(WorldRenderHelper helper) {
|
private void applyPosition(WorldRenderHelper helper) {
|
||||||
@ -258,6 +247,8 @@ public class Camera {
|
|||||||
|
|
||||||
private void invalidateCache() {
|
private void invalidateCache() {
|
||||||
this.lastAnchorPosition.set(Float.NaN);
|
this.lastAnchorPosition.set(Float.NaN);
|
||||||
|
this.lastAnchorYaw = Float.NaN;
|
||||||
|
this.lastAnchorPitch = Float.NaN;
|
||||||
|
|
||||||
this.lastCameraMatrix.set(
|
this.lastCameraMatrix.set(
|
||||||
Float.NaN,
|
Float.NaN,
|
||||||
@ -279,7 +270,7 @@ public class Camera {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.lastAnchorLookingAt.set(Float.NaN);
|
this.lastAnchorLookingAt.set(Float.NaN);
|
||||||
this.lastAnchorUpVector.set(Float.NaN);
|
this.lastAnchorUp.set(Float.NaN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Anchor.Mode getMode() {
|
public Anchor.Mode getMode() {
|
||||||
@ -298,6 +289,14 @@ public class Camera {
|
|||||||
return currentModeIndex;
|
return currentModeIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float getLastAnchorYaw() {
|
||||||
|
return lastAnchorYaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getLastAnchorPitch() {
|
||||||
|
return lastAnchorPitch;
|
||||||
|
}
|
||||||
|
|
||||||
public Vec3 getLastAnchorPosition() {
|
public Vec3 getLastAnchorPosition() {
|
||||||
return lastAnchorPosition;
|
return lastAnchorPosition;
|
||||||
}
|
}
|
||||||
@ -311,7 +310,7 @@ public class Camera {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Vec3 getLastAnchorUp() {
|
public Vec3 getLastAnchorUp() {
|
||||||
return lastAnchorUpVector;
|
return lastAnchorUp;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -59,32 +59,24 @@ public class EntityAnchor implements Anchor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Vec3 getCameraPosition(Vec3 output) {
|
public void getCameraPosition(Vec3 output) {
|
||||||
if (output == null) output = new Vec3();
|
|
||||||
model.getViewPoint(output);
|
model.getViewPoint(output);
|
||||||
output.add(model.getPosition());
|
output.add(entity.getPosition());
|
||||||
return output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Vec3 getCameraVelocity(Vec3 output) {
|
public void getCameraVelocity(Vec3 output) {
|
||||||
if (output == null) output = new Vec3();
|
|
||||||
output.set(entity.getVelocity());
|
output.set(entity.getVelocity());
|
||||||
return output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Vec3 getLookingAt(Vec3 output) {
|
public float getCameraYaw() {
|
||||||
if (output == null) output = new Vec3();
|
return entity.getYaw();
|
||||||
model.getLookingAt(output);
|
|
||||||
return output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Vec3 getUpVector(Vec3 output) {
|
public float getCameraPitch() {
|
||||||
if (output == null) output = new Vec3();
|
return entity.getPitch();
|
||||||
model.getUpVector(output);
|
|
||||||
return output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -92,13 +84,4 @@ public class EntityAnchor implements Anchor {
|
|||||||
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.InputEvent;
|
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||||
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;
|
||||||
@ -41,11 +41,9 @@ import ru.windcorp.progressia.common.Units;
|
|||||||
import ru.windcorp.progressia.common.collision.Collideable;
|
import ru.windcorp.progressia.common.collision.Collideable;
|
||||||
import ru.windcorp.progressia.common.collision.colliders.Collider;
|
import ru.windcorp.progressia.common.collision.colliders.Collider;
|
||||||
import ru.windcorp.progressia.common.util.FloatMathUtil;
|
import ru.windcorp.progressia.common.util.FloatMathUtil;
|
||||||
import ru.windcorp.progressia.common.util.Vectors;
|
|
||||||
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.controls.TestPlayerControls;
|
import ru.windcorp.progressia.test.TestPlayerControls;
|
||||||
|
|
||||||
public class LayerWorld extends Layer {
|
public class LayerWorld extends Layer {
|
||||||
|
|
||||||
@ -59,8 +57,6 @@ public class LayerWorld extends Layer {
|
|||||||
super("World");
|
super("World");
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.inputBasedControls = new InputBasedControls(client);
|
this.inputBasedControls = new InputBasedControls(client);
|
||||||
|
|
||||||
setCursorPolicy(CursorPolicy.FORBID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -76,8 +72,6 @@ public class LayerWorld extends Layer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doRender() {
|
protected void doRender() {
|
||||||
client.getComms().processPackets();
|
|
||||||
|
|
||||||
Camera camera = client.getCamera();
|
Camera camera = client.getCamera();
|
||||||
if (camera.hasAnchor()) {
|
if (camera.hasAnchor()) {
|
||||||
renderWorld();
|
renderWorld();
|
||||||
@ -194,7 +188,7 @@ public class LayerWorld extends Layer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.build();
|
return new StaticModel(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final float FRICTION_COEFF = Units.get("1e-5f kg/s");
|
private static final float FRICTION_COEFF = Units.get("1e-5f kg/s");
|
||||||
@ -203,34 +197,27 @@ public class LayerWorld extends Layer {
|
|||||||
entity.getVelocity().mul((float) Math.exp(-FRICTION_COEFF / entity.getCollisionMass() * tickLength));
|
entity.getVelocity().mul((float) Math.exp(-FRICTION_COEFF / entity.getCollisionMass() * tickLength));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final float MC_g = Units.get("32 m/s^2");
|
||||||
|
private static final float IRL_g = Units.get("9.8 m/s^2");
|
||||||
|
|
||||||
private void tmp_applyGravity(EntityData entity, float tickLength) {
|
private void tmp_applyGravity(EntityData entity, float tickLength) {
|
||||||
GravityModel gm = ClientState.getInstance().getWorld().getData().getGravityModel();
|
|
||||||
|
|
||||||
Vec3 upVector = Vectors.grab3();
|
|
||||||
gm.getUp(entity.getPosition(), upVector);
|
|
||||||
entity.changeUpVector(upVector);
|
|
||||||
Vectors.release(upVector);
|
|
||||||
|
|
||||||
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();
|
final float gravitationalAcceleration = tmp_testControls.useMinecraftGravity() ? MC_g : IRL_g;
|
||||||
gm.getGravity(entity.getPosition(), gravitationalAcceleration);
|
entity.getVelocity().add(0, 0, -gravitationalAcceleration * tickLength);
|
||||||
|
|
||||||
gravitationalAcceleration.mul(tickLength);
|
|
||||||
entity.getVelocity().add(gravitationalAcceleration);
|
|
||||||
|
|
||||||
Vectors.release(gravitationalAcceleration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleInput(InputEvent event) {
|
protected void handleInput(Input input) {
|
||||||
if (!event.isConsumed()) {
|
if (input.isConsumed())
|
||||||
inputBasedControls.handleInput(event);
|
return;
|
||||||
|
|
||||||
|
tmp_testControls.handleInput(input);
|
||||||
|
|
||||||
|
if (!input.isConsumed()) {
|
||||||
|
inputBasedControls.handleInput(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,13 +23,13 @@ import glm.vec._3.Vec3;
|
|||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.client.world.WorldRender;
|
import ru.windcorp.progressia.client.world.WorldRender;
|
||||||
import ru.windcorp.progressia.common.world.BlockRay;
|
import ru.windcorp.progressia.common.world.BlockRay;
|
||||||
|
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
|
||||||
|
|
||||||
public class Selection {
|
public class Selection {
|
||||||
|
|
||||||
private final Vec3i block = new Vec3i();
|
private final Vec3i block = new Vec3i();
|
||||||
private AbsFace surface = null;
|
private BlockFace surface = null;
|
||||||
private final Vec2 pointOnSurface = new Vec2(0.5f, 0.5f);
|
private final Vec2 pointOnSurface = new Vec2(0.5f, 0.5f);
|
||||||
private final Vec3 point = new Vec3();
|
private final Vec3 point = new Vec3();
|
||||||
|
|
||||||
@ -38,9 +38,10 @@ public class Selection {
|
|||||||
private BlockRay ray = new BlockRay();
|
private BlockRay ray = new BlockRay();
|
||||||
|
|
||||||
public void update(WorldRender world, EntityData player) {
|
public void update(WorldRender world, EntityData player) {
|
||||||
|
Vec3 direction = new Vec3();
|
||||||
Vec3 start = new Vec3();
|
Vec3 start = new Vec3();
|
||||||
Vec3 direction = player.getLookingAt();
|
|
||||||
|
|
||||||
|
player.getLookingAtVector(direction);
|
||||||
world.getEntityRenderable(player).getViewPoint(start);
|
world.getEntityRenderable(player).getViewPoint(start);
|
||||||
start.add(player.getPosition());
|
start.add(player.getPosition());
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ public class Selection {
|
|||||||
return exists ? point : null;
|
return exists ? point : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AbsFace getSurface() {
|
public BlockFace getSurface() {
|
||||||
return exists ? surface : null;
|
return exists ? surface : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ import glm.vec._4.Vec4;
|
|||||||
import ru.windcorp.progressia.client.graphics.backend.VertexBufferObject;
|
import ru.windcorp.progressia.client.graphics.backend.VertexBufferObject;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.shaders.attributes.*;
|
import ru.windcorp.progressia.client.graphics.backend.shaders.attributes.*;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.shaders.uniforms.*;
|
import ru.windcorp.progressia.client.graphics.backend.shaders.uniforms.*;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapePart;
|
import ru.windcorp.progressia.client.graphics.model.Face;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||||
@ -138,12 +138,12 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
|||||||
public void preprocess(Shape shape) {
|
public void preprocess(Shape shape) {
|
||||||
super.preprocess(shape);
|
super.preprocess(shape);
|
||||||
|
|
||||||
for (ShapePart face : shape.getParts()) {
|
for (Face face : shape.getFaces()) {
|
||||||
computeNormals(face);
|
computeNormals(face);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void computeNormals(ShapePart face) {
|
private void computeNormals(Face face) {
|
||||||
Vec3 a = Vectors.grab3();
|
Vec3 a = Vectors.grab3();
|
||||||
Vec3 b = Vectors.grab3();
|
Vec3 b = Vectors.grab3();
|
||||||
Vec3 c = Vectors.grab3();
|
Vec3 c = Vectors.grab3();
|
||||||
@ -183,7 +183,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
|||||||
normal.normalize();
|
normal.normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadVertexPosition(ShapePart face, int index, Vec3 result) {
|
private void loadVertexPosition(Face face, int index, Vec3 result) {
|
||||||
ByteBuffer vertices = face.getVertices();
|
ByteBuffer vertices = face.getVertices();
|
||||||
int offset = vertices.position() + index * getBytesPerVertex();
|
int offset = vertices.position() + index * getBytesPerVertex();
|
||||||
|
|
||||||
@ -194,16 +194,12 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveVertexNormal(ShapePart face, int index, Vec3 normal) {
|
private void saveVertexNormal(Face face, int index, Vec3 normal) {
|
||||||
ByteBuffer vertices = face.getVertices();
|
ByteBuffer vertices = face.getVertices();
|
||||||
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);
|
||||||
vertices.putFloat(offset + 2 * Float.BYTES, normal.z);
|
vertices.putFloat(offset + 2 * Float.BYTES, normal.z);
|
||||||
@ -216,7 +212,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
|||||||
return new WRPVertexBuilder();
|
return new WRPVertexBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class WRPVertexBuilder implements VertexBuilder {
|
private 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
|
||||||
|
|
||||||
@ -224,17 +220,11 @@ 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,24 +292,6 @@ 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() {
|
||||||
ByteBuffer result = BufferUtils.createByteBuffer(
|
ByteBuffer result = BufferUtils.createByteBuffer(
|
||||||
@ -337,9 +309,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(v.normals.x)
|
.putFloat(Float.NaN)
|
||||||
.putFloat(v.normals.y)
|
.putFloat(Float.NaN)
|
||||||
.putFloat(v.normals.z);
|
.putFloat(Float.NaN);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.flip();
|
result.flip();
|
||||||
|
@ -73,12 +73,6 @@ public class Localizer {
|
|||||||
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) {
|
||||||
throw new IllegalStateException("Localizer not yet initialized");
|
throw new IllegalStateException("Localizer not yet initialized");
|
||||||
|
@ -18,41 +18,51 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.world;
|
package ru.windcorp.progressia.client.world;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import glm.mat._4.Mat4;
|
||||||
|
import glm.vec._3.Vec3;
|
||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.Model;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.StaticModel;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.StaticModel.Builder;
|
||||||
import ru.windcorp.progressia.client.world.block.BlockRender;
|
import ru.windcorp.progressia.client.world.block.BlockRender;
|
||||||
|
import ru.windcorp.progressia.client.world.block.BlockRenderNone;
|
||||||
import ru.windcorp.progressia.client.world.block.BlockRenderRegistry;
|
import ru.windcorp.progressia.client.world.block.BlockRenderRegistry;
|
||||||
|
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer;
|
||||||
|
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSupplier;
|
||||||
|
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizers;
|
||||||
import ru.windcorp.progressia.client.world.tile.TileRender;
|
import ru.windcorp.progressia.client.world.tile.TileRender;
|
||||||
import ru.windcorp.progressia.client.world.tile.TileRenderReference;
|
|
||||||
import ru.windcorp.progressia.client.world.tile.TileRenderRegistry;
|
import ru.windcorp.progressia.client.world.tile.TileRenderRegistry;
|
||||||
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
|
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
|
||||||
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
import ru.windcorp.progressia.common.world.ChunkData;
|
||||||
import ru.windcorp.progressia.common.world.TileDataReference;
|
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||||
import ru.windcorp.progressia.common.world.TileDataStack;
|
import ru.windcorp.progressia.common.world.generic.GenericChunk;
|
||||||
import ru.windcorp.progressia.common.world.generic.ChunkGenericRO;
|
import ru.windcorp.progressia.common.world.tile.TileData;
|
||||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
import ru.windcorp.progressia.common.world.tile.TileDataStack;
|
||||||
import ru.windcorp.progressia.common.world.rels.BlockFace;
|
|
||||||
import ru.windcorp.progressia.common.world.rels.RelFace;
|
|
||||||
|
|
||||||
public class ChunkRender
|
public class ChunkRender
|
||||||
implements ChunkGenericRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender> {
|
implements GenericChunk<ChunkRender, BlockRender, TileRender, TileRenderStack> {
|
||||||
|
|
||||||
private final WorldRender world;
|
private final WorldRender world;
|
||||||
private final DefaultChunkData data;
|
private final ChunkData data;
|
||||||
|
|
||||||
private final ChunkRenderModel model;
|
private Model model = null;
|
||||||
|
|
||||||
private final Map<TileDataStack, TileRenderStackImpl> tileRenderLists = Collections
|
private final Map<TileDataStack, TileRenderStackImpl> tileRenderLists = Collections
|
||||||
.synchronizedMap(new WeakHashMap<>());
|
.synchronizedMap(new WeakHashMap<>());
|
||||||
|
|
||||||
public ChunkRender(WorldRender world, DefaultChunkData data) {
|
public ChunkRender(WorldRender world, ChunkData data) {
|
||||||
this.world = world;
|
this.world = world;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.model = new ChunkRenderModel(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -60,11 +70,6 @@ public class ChunkRender
|
|||||||
return getData().getPosition();
|
return getData().getPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public AbsFace getUp() {
|
|
||||||
return getData().getUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BlockRender getBlock(Vec3i posInChunk) {
|
public BlockRender getBlock(Vec3i posInChunk) {
|
||||||
return BlockRenderRegistry.getInstance().get(
|
return BlockRenderRegistry.getInstance().get(
|
||||||
@ -93,45 +98,177 @@ public class ChunkRender
|
|||||||
return world;
|
return world;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultChunkData getData() {
|
public ChunkData getData() {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markForUpdate() {
|
public synchronized void markForUpdate() {
|
||||||
getWorld().markChunkForUpdate(getPosition());
|
getWorld().markChunkForUpdate(getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void render(ShapeRenderHelper renderer) {
|
public synchronized void render(ShapeRenderHelper renderer) {
|
||||||
|
if (model == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.pushTransform().translate(
|
||||||
|
data.getX() * ChunkData.BLOCKS_PER_CHUNK,
|
||||||
|
data.getY() * ChunkData.BLOCKS_PER_CHUNK,
|
||||||
|
data.getZ() * ChunkData.BLOCKS_PER_CHUNK
|
||||||
|
);
|
||||||
|
|
||||||
model.render(renderer);
|
model.render(renderer);
|
||||||
|
|
||||||
|
renderer.popTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void update() {
|
public synchronized void update() {
|
||||||
model.update();
|
Collection<ChunkRenderOptimizer> optimizers = ChunkRenderOptimizers.getAllSuppliers().stream()
|
||||||
|
.map(ChunkRenderOptimizerSupplier::createOptimizer)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
optimizers.forEach(bro -> bro.startRender(this));
|
||||||
|
|
||||||
|
StaticModel.Builder builder = StaticModel.builder();
|
||||||
|
|
||||||
|
Vec3i cursor = new Vec3i();
|
||||||
|
for (int x = 0; x < ChunkData.BLOCKS_PER_CHUNK; ++x) {
|
||||||
|
for (int y = 0; y < ChunkData.BLOCKS_PER_CHUNK; ++y) {
|
||||||
|
for (int z = 0; z < ChunkData.BLOCKS_PER_CHUNK; ++z) {
|
||||||
|
cursor.set(x, y, z);
|
||||||
|
|
||||||
|
buildBlock(cursor, optimizers, builder);
|
||||||
|
buildBlockTiles(cursor, optimizers, builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
optimizers.stream()
|
||||||
|
.map(ChunkRenderOptimizer::endRender)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.forEach(builder::addPart);
|
||||||
|
|
||||||
|
model = new StaticModel(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildBlock(
|
||||||
|
Vec3i cursor,
|
||||||
|
Collection<ChunkRenderOptimizer> optimizers,
|
||||||
|
Builder builder
|
||||||
|
) {
|
||||||
|
BlockRender block = getBlock(cursor);
|
||||||
|
|
||||||
|
if (block instanceof BlockRenderNone) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
forwardBlockToOptimizers(block, cursor, optimizers);
|
||||||
|
|
||||||
|
if (!block.needsOwnRenderable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addBlockRenderable(block, cursor, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void forwardBlockToOptimizers(
|
||||||
|
BlockRender block,
|
||||||
|
Vec3i cursor,
|
||||||
|
Collection<ChunkRenderOptimizer> optimizers
|
||||||
|
) {
|
||||||
|
optimizers.forEach(cro -> cro.processBlock(block, cursor));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addBlockRenderable(
|
||||||
|
BlockRender block,
|
||||||
|
Vec3i cursor,
|
||||||
|
Builder builder
|
||||||
|
) {
|
||||||
|
Renderable renderable = block.createRenderable();
|
||||||
|
|
||||||
|
if (renderable == null) {
|
||||||
|
renderable = block::render;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.addPart(
|
||||||
|
renderable,
|
||||||
|
new Mat4().identity().translate(cursor.x, cursor.y, cursor.z)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildBlockTiles(
|
||||||
|
Vec3i cursor,
|
||||||
|
Collection<ChunkRenderOptimizer> optimizers,
|
||||||
|
Builder builder
|
||||||
|
) {
|
||||||
|
for (BlockFace face : BlockFace.getFaces()) {
|
||||||
|
buildFaceTiles(cursor, face, optimizers, builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildFaceTiles(
|
||||||
|
Vec3i cursor,
|
||||||
|
BlockFace face,
|
||||||
|
Collection<ChunkRenderOptimizer> optimizers,
|
||||||
|
Builder builder
|
||||||
|
) {
|
||||||
|
List<TileData> tiles = getData().getTilesOrNull(cursor, face);
|
||||||
|
|
||||||
|
if (tiles == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int layer = 0; layer < tiles.size(); ++layer) {
|
||||||
|
|
||||||
|
if (tiles.get(layer) == null) {
|
||||||
|
System.out.println(tiles.get(layer).getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTile(
|
||||||
|
cursor,
|
||||||
|
face,
|
||||||
|
TileRenderRegistry.getInstance().get(
|
||||||
|
tiles.get(layer).getId()
|
||||||
|
),
|
||||||
|
layer,
|
||||||
|
optimizers,
|
||||||
|
builder
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildTile(
|
||||||
|
Vec3i cursor,
|
||||||
|
BlockFace face,
|
||||||
|
TileRender tile,
|
||||||
|
int layer,
|
||||||
|
Collection<ChunkRenderOptimizer> optimizers,
|
||||||
|
Builder builder
|
||||||
|
) {
|
||||||
|
// TODO implement
|
||||||
|
|
||||||
|
Vec3 pos = new Vec3(cursor.x, cursor.y, cursor.z);
|
||||||
|
|
||||||
|
optimizers.forEach(cro -> cro.processTile(tile, cursor, face));
|
||||||
|
|
||||||
|
if (!tile.needsOwnRenderable())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Vec3 offset = new Vec3(
|
||||||
|
face.getVector().x,
|
||||||
|
face.getVector().y,
|
||||||
|
face.getVector().z
|
||||||
|
);
|
||||||
|
|
||||||
|
pos.add(offset.mul(1f / 64));
|
||||||
|
|
||||||
|
builder.addPart(
|
||||||
|
tile.createRenderable(face),
|
||||||
|
new Mat4().identity().translate(pos)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TileRenderStackImpl extends TileRenderStack {
|
private class TileRenderStackImpl extends TileRenderStack {
|
||||||
private class TileRenderReferenceImpl implements TileRenderReference {
|
|
||||||
private final TileDataReference parent;
|
|
||||||
|
|
||||||
public TileRenderReferenceImpl(TileDataReference parent) {
|
|
||||||
this.parent = parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TileRender get() {
|
|
||||||
return TileRenderRegistry.getInstance().get(parent.get().getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIndex() {
|
|
||||||
return parent.getIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TileRenderStack getStack() {
|
|
||||||
return TileRenderStackImpl.this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final TileDataStack parent;
|
private final TileDataStack parent;
|
||||||
|
|
||||||
@ -150,25 +287,10 @@ public class ChunkRender
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RelFace getFace() {
|
public BlockFace getFace() {
|
||||||
return parent.getFace();
|
return parent.getFace();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public TileRenderReference getReference(int index) {
|
|
||||||
return new TileRenderReferenceImpl(parent.getReference(index));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIndexByTag(int tag) {
|
|
||||||
return parent.getIndexByTag(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTagByIndex(int index) {
|
|
||||||
return parent.getTagByIndex(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TileRender get(int index) {
|
public TileRender get(int index) {
|
||||||
return TileRenderRegistry.getInstance().get(parent.get(index).getId());
|
return TileRenderRegistry.getInstance().get(parent.get(index).getId());
|
||||||
|
@ -1,167 +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.client.world;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import glm.mat._4.Mat4;
|
|
||||||
import glm.vec._3.i.Vec3i;
|
|
||||||
import ru.windcorp.progressia.client.graphics.model.Model;
|
|
||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
|
||||||
import ru.windcorp.progressia.client.graphics.model.StaticModel;
|
|
||||||
import ru.windcorp.progressia.client.graphics.model.StaticModel.Builder;
|
|
||||||
import ru.windcorp.progressia.client.world.block.BlockRender;
|
|
||||||
import ru.windcorp.progressia.client.world.block.BlockRenderNone;
|
|
||||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer;
|
|
||||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerRegistry;
|
|
||||||
import ru.windcorp.progressia.client.world.tile.TileRender;
|
|
||||||
import ru.windcorp.progressia.client.world.tile.TileRenderNone;
|
|
||||||
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
|
|
||||||
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
|
||||||
import ru.windcorp.progressia.common.world.generic.GenericChunks;
|
|
||||||
import ru.windcorp.progressia.common.world.rels.AxisRotations;
|
|
||||||
import ru.windcorp.progressia.common.world.rels.RelFace;
|
|
||||||
|
|
||||||
public class ChunkRenderModel implements Renderable {
|
|
||||||
|
|
||||||
private final ChunkRender chunk;
|
|
||||||
|
|
||||||
private final Collection<ChunkRenderOptimizer> optimizers = new ArrayList<>();
|
|
||||||
private Model model = null;
|
|
||||||
|
|
||||||
public ChunkRenderModel(ChunkRender chunk) {
|
|
||||||
this.chunk = chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(ShapeRenderHelper renderer) {
|
|
||||||
if (model == null) return;
|
|
||||||
|
|
||||||
float offset = DefaultChunkData.BLOCKS_PER_CHUNK / 2 - 0.5f;
|
|
||||||
|
|
||||||
renderer.pushTransform().translate(
|
|
||||||
chunk.getX() * DefaultChunkData.BLOCKS_PER_CHUNK,
|
|
||||||
chunk.getY() * DefaultChunkData.BLOCKS_PER_CHUNK,
|
|
||||||
chunk.getZ() * DefaultChunkData.BLOCKS_PER_CHUNK
|
|
||||||
).translate(offset, offset, offset)
|
|
||||||
.mul(AxisRotations.getResolutionMatrix4(chunk.getUp()))
|
|
||||||
.translate(-offset, -offset, -offset);
|
|
||||||
|
|
||||||
model.render(renderer);
|
|
||||||
|
|
||||||
renderer.popTransform();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update() {
|
|
||||||
setupCROs();
|
|
||||||
|
|
||||||
StaticModel.Builder sink = StaticModel.builder();
|
|
||||||
|
|
||||||
optimizers.forEach(ChunkRenderOptimizer::startRender);
|
|
||||||
|
|
||||||
GenericChunks.forEachBiC(relBlockInChunk -> {
|
|
||||||
processBlockAndTiles(relBlockInChunk, sink);
|
|
||||||
});
|
|
||||||
|
|
||||||
for (ChunkRenderOptimizer optimizer : optimizers) {
|
|
||||||
Renderable renderable = optimizer.endRender();
|
|
||||||
if (renderable != null) {
|
|
||||||
sink.addPart(renderable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.model = sink.build();
|
|
||||||
this.optimizers.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupCROs() {
|
|
||||||
Set<String> ids = ChunkRenderOptimizerRegistry.getInstance().keySet();
|
|
||||||
|
|
||||||
for (String id : ids) {
|
|
||||||
ChunkRenderOptimizer optimizer = ChunkRenderOptimizerRegistry.getInstance().create(id);
|
|
||||||
optimizer.setup(chunk);
|
|
||||||
this.optimizers.add(optimizer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processBlockAndTiles(Vec3i relBlockInChunk, Builder sink) {
|
|
||||||
processBlock(relBlockInChunk, sink);
|
|
||||||
|
|
||||||
for (RelFace face : RelFace.getFaces()) {
|
|
||||||
processTileStack(relBlockInChunk, face, sink);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processBlock(Vec3i relBlockInChunk, Builder sink) {
|
|
||||||
BlockRender block = chunk.getBlockRel(relBlockInChunk);
|
|
||||||
|
|
||||||
if (block instanceof BlockRenderNone) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block.needsOwnRenderable()) {
|
|
||||||
sink.addPart(
|
|
||||||
block.createRenderable(chunk.getData(), relBlockInChunk),
|
|
||||||
new Mat4().identity().translate(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
processBlockWithCROs(block, relBlockInChunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processBlockWithCROs(BlockRender block, Vec3i relBlockInChunk) {
|
|
||||||
for (ChunkRenderOptimizer optimizer : optimizers) {
|
|
||||||
optimizer.addBlock(block, relBlockInChunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processTileStack(Vec3i relBlockInChunk, RelFace face, Builder sink) {
|
|
||||||
TileRenderStack trs = chunk.getTilesOrNullRel(relBlockInChunk, face);
|
|
||||||
|
|
||||||
if (trs == null || trs.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
trs.forEach(tile -> processTile(tile, relBlockInChunk, face, sink));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processTile(TileRender tile, Vec3i relBlockInChunk, RelFace face, Builder sink) {
|
|
||||||
if (tile instanceof TileRenderNone) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tile.needsOwnRenderable()) {
|
|
||||||
sink.addPart(
|
|
||||||
tile.createRenderable(chunk.getData(), relBlockInChunk, face),
|
|
||||||
new Mat4().identity().translate(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
processTileWithCROs(tile, relBlockInChunk, face);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processTileWithCROs(TileRender tile, Vec3i relBlockInChunk, RelFace face) {
|
|
||||||
for (ChunkRenderOptimizer optimizer : optimizers) {
|
|
||||||
optimizer.addTile(tile, relBlockInChunk, face);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user