Compare commits

...
This repository has been archived on 2022-10-09. You can view files and clone it, but cannot push or open issues or pull requests.

11 Commits

Author SHA1 Message Date
5472576606
Fixed a compilation issue under Windows and MacOS, added safeguards 2022-01-15 19:45:53 +03:00
4a6aa5dbf7
Set source code encoding in build.gradle 2022-01-14 13:28:02 +03:00
c3bbc8661d
Added GLFW error handling and fixed a bug
- GLFW errors are now detected
- Added IntConstantsMap
- Removed window maximization on init
2022-01-14 00:28:52 +03:00
eadc85bfda
Fixed GitHub issue forms 2022-01-12 21:23:53 +03:00
ca8ac6e7b6
Added issue forms for GitHub 2022-01-12 17:23:46 +03:00
0638794a80
Added ColorScheme to manage GUI colors; changed BG color slightly 2022-01-12 12:09:55 +03:00
e6e55a6c40
Fixed 'HEAD' in detected Git branch on Jenkins 2022-01-10 23:59:09 +03:00
57a86b544e
Fixed dependency detection for external tools 2022-01-10 21:19:31 +03:00
b18eac44b8
Updated all dependencies
- Updated LWJGL to 3.3.0
  - Targets windows-arm64 and macos-arm64 are now available
- Updated Guava to 31.0.1
- Updated Log4j to 2.17.1
2022-01-10 19:54:14 +03:00
c3c8a6e5e0
Updated Gradle wrapper to 7.3.3 2022-01-09 21:09:02 +03:00
fe01e1b81c
Fixed first-time packaging issue in build script 2022-01-09 21:02:56 +03:00
30 changed files with 1195 additions and 405 deletions

49
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,49 @@
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 Normal file
View File

@ -0,0 +1,5 @@
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 Normal file
View File

@ -0,0 +1,41 @@
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

View File

@ -42,6 +42,16 @@ compileJava {
/*
* Set encoding
*/
compileJava {
options.encoding = 'utf8'
}
/*
* Dependencies
*/
@ -60,7 +70,7 @@ repositories {
dependencies {
// Google Guava
// A generic utilities library
implementation 'com.google.guava:guava:30.0-jre'
implementation 'com.google.guava:guava:31.0.1-jre'
// Trove4j
// Provides optimized Collections for primitive types
@ -73,8 +83,8 @@ dependencies {
// Log4j
// A logging library
implementation 'org.apache.logging.log4j:log4j-api:2.17.0'
implementation 'org.apache.logging.log4j:log4j-core:2.17.0'
implementation 'org.apache.logging.log4j:log4j-api:2.17.1'
implementation 'org.apache.logging.log4j:log4j-core:2.17.1'
// JUnit
// A unit-testing library
@ -163,17 +173,21 @@ task resolveVersion {
description 'Resolves version information from Git repository or project properties.'
doFirst {
project.ext.commit = System.env.GIT_COMMIT
project.ext.branch = System.env.GIT_BRANCH
try {
def git = Grgit.open(dir: project.projectDir)
project.ext.commit = git.head().id
project.ext.branch = git.branch.current().name
project.ext.commit = commit ?: git.head().id
project.ext.branch = branch ?: git.branch.current().name
if (project.version != 'unspecified') {
// Leave version as-is
return
}
} else {
// Resolve version from Git
def tag = version_findRelevantTag(git)
if (tag == null) {
@ -192,20 +206,52 @@ task resolveVersion {
project.version = version_parseVersion(tag.name, tag.commit != git.head())
}
}
} catch (org.eclipse.jgit.errors.RepositoryNotFoundException e) {
if (project.version == 'unspecified') project.version = 'dev'
project.ext.commit = '-'
project.ext.branch = '-'
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 '/') {
// Strip remote - no one wants that
project.ext.branch = branch.takeAfter '/'
}
doLast {
if (!project.hasProperty('buildId')) {
project.ext.buildId = '-'
}
}
doLast {
}
}
/*
* Apply LWJGL logic
*/
apply from: 'build_logic/lwjgl.gradle'
/*
* Copy libraries into build/libs/lib directory, next to Progressia.jar
*/
task exportLibs(type: Sync) {
description 'Copies runtime libraries into a subdirectory next to the output JAR.'
jar.dependsOn exportLibs
dependsOn lwjgl_addNativesToRuntimeOnly
// from defined in configureManifest
into 'build/libs/lib'
}
@ -219,11 +265,16 @@ task configureManifest {
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': configurations.runtimeClasspath.collect { "lib/${it.name}" } .join(' '),
'Class-Path': classPath.collect { "lib/${java.net.URLEncoder.encode it.name}" } .join(' '),
'Specification-Title': 'Progressia',
@ -238,28 +289,6 @@ task configureManifest {
/*
* Copy libraries into buil/libs/lib directory, next to Progressia.jar
*/
task exportLibs(type: Sync) {
description 'Copies runtime libraries into a subdirectory next to the output JAR.'
jar.dependsOn exportLibs
from configurations.runtimeClasspath
into 'build/libs/lib'
}
/*
* Apply LWJGL logic
*/
apply from: 'build_logic/lwjgl.gradle'
/*
* Packaging working directory configuration
*/
@ -267,64 +296,40 @@ apply from: 'build_logic/lwjgl.gradle'
import java.nio.file.*
import java.nio.file.attribute.*
task createPackagingDirs() {
description 'Resets build/tmp/packaging directory.'
doLast {
def tmpDir = buildDir.toPath().resolve 'tmp/packaging'
def nuke = new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
if (e == null) {
Files.delete(dir);
return FileVisitResult.CONTINUE;
} else {
// directory iteration failed
throw e;
}
}
}
// Fckn nuke tmpDir from orbit
// I'm so done with deleting file trees in Java/Groovy/whatever
// ...Not using File.deleteDir() because the latter recurses into symlinks, and we don't want to wipe build/libs/lib
if (Files.exists(tmpDir)) {
Files.walkFileTree tmpDir, nuke
}
Files.createDirectories tmpDir.resolve('workingDir')
Files.createDirectories buildDir.toPath().resolve('packages')
}
task deletePackagingDirs(type: Delete) {
description 'Deletes build/tmp/packaging directory.'
followSymlinks = false
delete 'build/tmp/packaging'
}
task linkBuildOutputForPackaging() {
task linkBuildOutputForPackaging {
description 'Symlinks the contents of build/libs into packaging working directory.'
dependsOn build
dependsOn createPackagingDirs
mustRunAfter deletePackagingDirs
onlyIf { preparePackaging.ext.mode == 'symlink' }
ext.from = 'build/libs'
ext.into = "build/tmp/packaging/workingDir/${ -> preparePackaging.ext.buildDest}"
inputs.dir from
outputs.dir into
doLast {
def from = buildDir.toPath().resolve 'libs'
def into = buildDir.toPath().resolve "tmp/packaging/workingDir/${preparePackaging.ext.buildDest}"
def fromPath = Paths.get from
def intoPath = Paths.get into
Files.createDirectories into
Files.createDirectories intoPath
Files.list(from).each {
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 into.resolve(it.fileName), it
Files.createSymbolicLink intoPath.resolve(it.fileName), intoPath.relativize(it)
}
}
}
@ -333,7 +338,7 @@ task copyBuildOutputForPackaging(type: Copy) {
description 'Copies the contents of build/libs into packaging working directory.'
dependsOn build
dependsOn createPackagingDirs
mustRunAfter deletePackagingDirs
onlyIf { preparePackaging.ext.mode == 'copy' }
@ -348,7 +353,7 @@ task preparePackaging {
preparePackaging.ext.buildDest = ''
preparePackaging.ext.mode = 'symlink'
dependsOn createPackagingDirs
dependsOn deletePackagingDirs
dependsOn linkBuildOutputForPackaging
dependsOn copyBuildOutputForPackaging
}

View File

@ -6,14 +6,14 @@
project.ext.lwjgl = new HashMap<>()
// Version of LWJGL
lwjgl.version = '3.2.3'
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<>()
lwjgl.targets = new HashSet<String>()
// LWJGL components. To include org.lwjgl:lwjgl-foobar, add 'foobar' to this list.
lwjgl.libraries = [
@ -33,34 +33,49 @@ switch (OperatingSystem.current()) {
: 'linux'
break
case OperatingSystem.MAC_OS:
lwjgl.localArch = 'macos'
lwjgl.localArch = System.getProperty('os.arch').startsWith('aarch64') ? 'macos-arm64' : 'macos'
break
case OperatingSystem.WINDOWS:
lwjgl.localArch = System.getProperty('os.arch').contains('64') ? 'windows' : 'windows-x86'
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
implementation platform("org.lwjgl:lwjgl-bom:${lwjgl.version}")
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 { implementation "org.lwjgl:lwjgl-$it" }
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 runtimeOnly configuration
* Adds LWJGL native libraries to lwjglNatives configuration
*/
task lwjgl_addNativesToRuntimeOnly {
// Make sure runtimeOnly has not been resolved
compileJava.dependsOn lwjgl_addNativesToRuntimeOnly
configureManifest.dependsOn lwjgl_addNativesToRuntimeOnly
exportLibs.dependsOn lwjgl_addNativesToRuntimeOnly
doFirst {
if (project.hasProperty('forceTargets')) {
try {
@ -82,17 +97,34 @@ task lwjgl_addNativesToRuntimeOnly {
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 ->
runtimeOnly "org.lwjgl:lwjgl::natives-$target"
lwjglNatives "org.lwjgl:lwjgl::natives-$target"
lwjgl.libraries.each { lib ->
runtimeOnly "org.lwjgl:lwjgl-$lib::natives-$target"
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.'
@ -113,5 +145,5 @@ def requestTask(String name, String... targets) {
}
requestTask 'Linux', 'linux', 'linux-arm32', 'linux-arm64'
requestTask 'Windows', 'windows', 'windows-x86'
requestTask 'MacOS', 'macos'
requestTask 'Windows', 'windows', 'windows-arm64', 'windows-x86'
requestTask 'MacOS', 'macos', 'macos-arm64'

View File

@ -15,7 +15,7 @@ task packageDeb_configure() {
preparePackaging.mustRunAfter packageDeb_configure
doLast {
tasks.preparePackaging.ext.buildDest = '/usr/share/progressia'
tasks.preparePackaging.ext.buildDest = 'usr/share/progressia'
tasks.preparePackaging.ext.mode = 'copy'
}
}
@ -31,6 +31,10 @@ task packageDeb(type: Exec) {
dependsOn packageDeb_processResources
doFirst {
mkdir 'build/packages'
}
executable 'dpkg-deb'
args '--root-owner-group'
args '--build', 'build/tmp/packaging/workingDir'

View File

@ -40,6 +40,10 @@ task packageNsis(type: Exec) {
dependsOn packageNsis_generateIcon
dependsOn packageNsis_generateLeftSide
doFirst {
mkdir 'build/packages'
}
executable 'makensis'
args '-NOCONFIG'
args "-DPROJECT_NAME=${project.name}"

View File

@ -36,6 +36,10 @@ task packageZip(type: Zip) {
archiveVersion = project.version
}
doFirst {
mkdir 'build/packages'
}
from 'build/tmp/packaging/workingDir'
destinationDirectory = file('build/packages')
}

View File

@ -143,8 +143,8 @@ build.
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:
- `requestLinuxDependencies` requests that `natives-linux`, `natives-linux-arm32` and `natives-linux-arm64` binaries are included;
- `requestWindowsDependencies` requests that `natives-windows` and `natives-windows-x86` binaries are included;
- `requestMacOSDependencies` requests that `natives-macos` binaries are included.
- `requestWindowsDependencies` requests that `natives-windows`, `natives-windows-arm64` and `natives-windows-x86` binaries are included;
- `requestMacOSDependencies` requests that `natives-macos`, `natives-macos-arm64` binaries are included.
These requests can be applied in any combination. For example, the following command produces a build containing only
GNU/Linux and Windows natives:

View File

@ -12,8 +12,8 @@ This document is a user's reference for the build script of Progressia. For a be
- `packageDeb` creates a Debian package. Incompatible with other `package` tasks.
- `packageNsis` creates a Windows NSIS installer. Incompatible with other `package` tasks.
- `requestLinuxDependencies` requests that `natives-linux`, `natives-linux-arm32` and `natives-linux-arm64` binaries are included when building.
- `requestWindowsDependencies` requests that `natives-windows` and `natives-windows-x86` binaries are included when building.
- `requestMacOSDependencies` requests that `natives-macos` binaries are included when building.
- `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>`.
@ -60,14 +60,21 @@ In all other cases, a fallback dummy value is used for version, appended with bu
### Git metadata
Git commit and Git branch are correspond to the state of the local Git repository, if any. In case Git metadata is
unavailable, `-` fallback is used for both fields.
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. For example, builds executed by WindCorp
Jenkins suite have build IDs like `WJ3` or `WJ142`. Build ID must be provided explicitly; it is `-` unless specified
otherwise.
Build ID 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`
@ -94,10 +101,12 @@ 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` |

Binary file not shown.

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

269
gradlew vendored
View File

@ -1,7 +1,7 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright 2015 the original author or authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -17,78 +17,113 @@
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# 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
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${0##*/}
# 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"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -97,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
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.
Please set the JAVA_HOME variable in your environment to match the
@ -105,79 +140,95 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
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`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
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" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) 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
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-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" "$@"

25
gradlew.bat vendored
View File

@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
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.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -51,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -61,28 +64,14 @@ echo location of your Java installation.
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
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell

View File

@ -0,0 +1,45 @@
/*
* 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);
}
}

View File

@ -0,0 +1,307 @@
/*
* 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
)
);
}
}
}

View File

@ -25,6 +25,7 @@ import ru.windcorp.progressia.client.graphics.backend.RenderTaskQueue;
import ru.windcorp.progressia.client.graphics.flat.FlatRenderProgram;
import ru.windcorp.progressia.client.graphics.font.GNUUnifontLoader;
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.world.WorldRenderProgram;
import ru.windcorp.progressia.client.localization.Localizer;
@ -51,6 +52,7 @@ public class ClientProxy implements Proxy {
throw CrashReports.report(e, "ClientProxy failed");
}
ColorScheme.load(ResourceManager.getResource("assets/default.colorScheme"));
Localizer.getInstance().setLanguage("en-US");
TestContent.registerContent();

View File

@ -34,13 +34,7 @@ public class Colors {
DEBUG_BLUE = toVector(0xFF0000FF),
DEBUG_CYAN = toVector(0xFF00FFFF),
DEBUG_MAGENTA = toVector(0xFFFF00FF),
DEBUG_YELLOW = toVector(0xFFFFFF00),
LIGHT_GRAY = toVector(0xFFCBCBD0),
BLUE = toVector(0xFF37A2E6),
HOVER_BLUE = toVector(0xFFC3E4F7),
DISABLED_GRAY = toVector(0xFFE5E5E5),
DISABLED_BLUE = toVector(0xFFB2D8ED);
DEBUG_YELLOW = toVector(0xFFFFFF00);
public static Vec4 toVector(int argb) {
return toVector(argb, new Vec4());

View File

@ -0,0 +1,56 @@
/*
* 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);
}
}

View File

@ -24,6 +24,7 @@ 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;
@ -55,6 +56,7 @@ class LWJGLInitializer {
setupWindowCallbacks();
glfwShowWindow(GraphicsBackend.getWindowHandle());
GraphicsBackend.onFrameResized(GraphicsBackend.getWindowHandle(), 800, 600);
}
private static void checkEnvironment() {
@ -62,8 +64,12 @@ class LWJGLInitializer {
}
private static void initializeGLFW() {
// TODO Do GLFW error handling: check glfwInit, setup error callback
glfwInit();
GLFWErrorCallback.create(new GLFWErrorHandler()::onError).set();
if (!glfwInit()) {
throw CrashReports.report(null, "GLFW could not be initialized: glfwInit() has failed");
}
GraphicsBackend.setGLFWInitialized(true);
}
@ -71,17 +77,13 @@ class LWJGLInitializer {
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE);
glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
long handle = glfwCreateWindow(
800,
600,
Progressia.getName() + " " + Progressia.getFullerVersion(),
NULL,
NULL
);
String windowTitle = Progressia.getName() + " " + Progressia.getFullerVersion();
long handle = glfwCreateWindow(800, 600, windowTitle, NULL, NULL);
// TODO Check that handle != NULL
if (handle == 0) {
throw CrashReports.report(null, "Could not create game window");
}
GraphicsBackend.setWindowHandle(handle);
@ -95,8 +97,8 @@ class LWJGLInitializer {
}
private static void createWindowIcons() {
if (glfwGetVersionString().toLowerCase().contains("wayland")) {
// glfwSetWindowIcon is not supported on Wayland
if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) {
// Wayland does not support changing window icons
return;
}

View File

@ -18,10 +18,8 @@
package ru.windcorp.progressia.client.graphics.gui;
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.Colors;
public class Button extends BasicButton {
@ -39,45 +37,26 @@ public class Button extends BasicButton {
@Override
protected void assembleSelf(RenderTarget target) {
// Border
Vec4 borderColor;
if (isPressed() || isHovered() || isFocused()) {
borderColor = Colors.BLUE;
String state;
if (!isEnabled()) {
state = "Disabled";
} else if (isPressed()) {
state = "Pressed";
} else if (isHovered()) {
state = "Hovered";
} else if (isFocused()) {
state = "Focused";
} else {
borderColor = Colors.LIGHT_GRAY;
state = "Inactive";
}
target.fill(getX(), getY(), getWidth(), getHeight(), borderColor);
// Border
target.fill(getX(), getY(), getWidth(), getHeight(), ColorScheme.get("Core:ButtonBorder" + state));
// Inside area
if (isPressed()) {
// Do nothing
} else {
Vec4 backgroundColor;
if (isHovered() && isEnabled()) {
backgroundColor = Colors.HOVER_BLUE;
} else {
backgroundColor = Colors.WHITE;
}
target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, backgroundColor);
}
target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, ColorScheme.get("Core:ButtonFill" + state));
// Change label font color
if (isPressed()) {
getLabel().setFont(getLabel().getFont().withColor(Colors.WHITE));
} else {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
}
}
@Override
protected void postAssembleSelf(RenderTarget target) {
// Apply disable tint
if (!isEnabled()) {
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
}
getLabel().setFont(getLabel().getFont().withColor(ColorScheme.get("Core:ButtonText" + state)));
}
}

View File

@ -18,8 +18,6 @@
package ru.windcorp.progressia.client.graphics.gui;
import glm.vec._2.i.Vec2i;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.font.Typefaces;
@ -43,34 +41,28 @@ public class Checkbox extends BasicButton {
int x = getX();
int y = getY() + (getHeight() - size) / 2;
// Border
Vec4 borderColor;
if (Checkbox.this.isPressed() || Checkbox.this.isHovered() || Checkbox.this.isFocused()) {
borderColor = Colors.BLUE;
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 {
borderColor = Colors.LIGHT_GRAY;
state = "Inactive";
}
target.fill(x, y, size, size, borderColor);
// Border
target.fill(x, y, size, size, ColorScheme.get("Core:CheckboxBorder" + state));
// Inside area
if (Checkbox.this.isPressed()) {
// Do nothing
} else {
Vec4 backgroundColor;
if (Checkbox.this.isHovered() && Checkbox.this.isEnabled()) {
backgroundColor = Colors.HOVER_BLUE;
} else {
backgroundColor = Colors.WHITE;
}
target.fill(x + 2, y + 2, size - 4, size - 4, backgroundColor);
}
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, Colors.BLUE);
target.fill(x + 4, y + 4, size - 8, size - 8, ColorScheme.get("Core:CheckboxCheck" + state));
}
}
@ -128,22 +120,21 @@ public class Checkbox extends BasicButton {
@Override
protected void assembleSelf(RenderTarget target) {
// Change label font color
if (isPressed()) {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE));
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 {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
}
state = "Inactive";
}
@Override
protected void postAssembleSelf(RenderTarget target) {
// Apply disable tint
if (!isEnabled()) {
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
}
// Change label font color
getLabel().setFont(getLabel().getFont().withColor(ColorScheme.get("Core:CheckboxText" + state)));
}
}

View File

@ -0,0 +1,145 @@
/*
* 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);
}
}

View File

@ -20,7 +20,6 @@ package ru.windcorp.progressia.client.graphics.gui;
import java.util.Objects;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
public class Panel extends Group {
@ -36,7 +35,7 @@ public class Panel extends Group {
}
public Panel(String name, Layout layout) {
this(name, layout, Colors.WHITE, Colors.LIGHT_GRAY);
this(name, layout, ColorScheme.get("Core:Background"), ColorScheme.get("Core:Border"));
}
/**

View File

@ -21,7 +21,6 @@ import org.lwjgl.glfw.GLFW;
import glm.vec._2.i.Vec2i;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.font.Typefaces;
@ -52,34 +51,28 @@ public class RadioButton extends BasicButton {
int x = getX();
int y = getY() + (getHeight() - size) / 2;
// Border
Vec4 borderColor;
if (RadioButton.this.isPressed() || RadioButton.this.isHovered() || RadioButton.this.isFocused()) {
borderColor = Colors.BLUE;
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 {
borderColor = Colors.LIGHT_GRAY;
state = "Inactive";
}
cross(target, x, y, size, borderColor);
// Border
cross(target, x, y, size, ColorScheme.get("Core:RadioButtonBorder" + state));
// Inside area
if (RadioButton.this.isPressed()) {
// Do nothing
} else {
Vec4 backgroundColor;
if (RadioButton.this.isHovered() && RadioButton.this.isEnabled()) {
backgroundColor = Colors.HOVER_BLUE;
} else {
backgroundColor = Colors.WHITE;
}
cross(target, x + 2, y + 2, size - 4, backgroundColor);
}
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, Colors.BLUE);
cross(target, x + 4, y + 4, size - 8, ColorScheme.get("Core:RadioButtonCheck" + state));
}
}
@ -146,8 +139,8 @@ public class RadioButton extends BasicButton {
group.selectNext();
removeAction(group.listener);
group.buttons.remove(this);
group.getSelected(); // Clear reference if this was the only button
// in the group
// Clear reference if this was the only button in the group
group.getSelected();
}
this.group = group;
@ -183,22 +176,21 @@ public class RadioButton extends BasicButton {
@Override
protected void assembleSelf(RenderTarget target) {
// Change label font color
if (isPressed()) {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE));
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 {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
}
state = "Inactive";
}
@Override
protected void postAssembleSelf(RenderTarget target) {
// Apply disable tint
if (!isEnabled()) {
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
}
// Change label font color
getLabel().setFont(getLabel().getFont().withColor(ColorScheme.get("Core:RadioButtonText" + state)));
}
}

View File

@ -20,9 +20,9 @@ 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.Colors;
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;
@ -50,7 +50,7 @@ public class MenuLayer extends GUILayer {
setCursorPolicy(CursorPolicy.REQUIRE);
this.background = new Panel(name + ".Background", new LayoutAlign(10), Colors.toVector(0x66000000), null);
this.background = new Panel(name + ".Background", new LayoutAlign(10), ColorScheme.get("Core:MenuLayerTint"), null);
this.content = content;
background.addChild(content);
@ -76,12 +76,12 @@ public class MenuLayer extends GUILayer {
protected void addTitle() {
String translationKey = "Layer" + getName() + ".Title";
MutableString titleText = new MutableStringLocalized(translationKey);
Font titleFont = new Font().deriveBold().withColor(Colors.BLACK).withAlign(0.5f);
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, Colors.BLUE, null);
Panel panel = new Panel(getName() + ".Title.Underscore", null, ColorScheme.get("Core:Accent"), null);
panel.setLayout(new LayoutFill() {
@Override
public Vec2i calculatePreferredSize(Component c) {

View File

@ -22,6 +22,7 @@ import ru.windcorp.progressia.Progressia;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.font.Typeface;
import ru.windcorp.progressia.client.graphics.gui.ColorScheme;
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.Group;
@ -37,7 +38,7 @@ public class LayerAbout extends GUILayer {
Group group = new Group("ControlDisplays", new LayoutVertical(5));
Font font = new Font().withColor(Colors.WHITE).deriveOutlined().withAlign(Typeface.ALIGN_RIGHT);
Font aboutFont = font.withColor(0xFF37A3E6).deriveBold();
Font aboutFont = font.withColor(ColorScheme.get("Core:Accent")).deriveBold();
group.addChild(
new Label(

View File

@ -20,11 +20,11 @@ package ru.windcorp.progressia.test;
import java.util.Collection;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.Button;
import ru.windcorp.progressia.client.graphics.gui.Checkbox;
import ru.windcorp.progressia.client.graphics.gui.ColorScheme;
import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.RadioButton;
import ru.windcorp.progressia.client.graphics.gui.RadioButtonGroup;
@ -65,7 +65,7 @@ public class LayerButtonTest extends MenuLayer {
getContent().getChild(getContent().getChildren().size() - 1).setEnabled(false);
getContent().addChild(new Label("Hint", new Font().withColor(Colors.LIGHT_GRAY), "This is a MenuLayer"));
getContent().addChild(new Label("Hint", new Font().withColor(ColorScheme.get("Core:Border")), "This is a MenuLayer"));
getContent().addChild(new Button("Continue", "Continue").addAction(b -> {
getCloseAction().run();

View File

@ -1,11 +1,13 @@
package ru.windcorp.progressia.test;
import java.util.function.Consumer;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.ColorScheme;
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
import ru.windcorp.progressia.client.graphics.gui.Label;
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.localization.MutableString;
public class LayerTestText extends GUILayer {
@ -13,11 +15,13 @@ public class LayerTestText extends GUILayer {
private final Consumer<LayerTestText> remover;
public LayerTestText(String name, MutableString value, Consumer<LayerTestText> remover) {
super(name, new LayoutAlign(15));
super(name, new LayoutFill());
this.remover = remover;
Font titleFont = new Font().deriveBold().withColor(Colors.BLACK).withAlign(0.5f);
getRoot().addChild(new Label(name + ".Text", titleFont, value));
Panel panel = new Panel(name + ".Background", new LayoutAlign(15), ColorScheme.get("Core:Background"), null);
Font titleFont = new Font().deriveBold().withColor(ColorScheme.get("Core:Text")).withAlign(0.5f);
panel.addChild(new Label(name + ".Text", titleFont, value));
getRoot().addChild(panel);
}
@Override

View File

@ -0,0 +1,80 @@
# Default color scheme for Progressia
#
# Line format:
# KEY = VALUE
# KEY must be a legal ID
# VALUE may be one of the following:
# #RGB
# #ARGB
# #RRGGBB
# #AARRGGBB
# $OTHER_KEY
Core:Background = #EFF0F1
Core:Border = #CBCBD0
Core:DisabledBackground = $Core:Background
Core:DisabledBorder = #DEDFE1
Core:Accent = #37A3E6
Core:AccentLighter = #C3E4F7
Core:Text = #31363B
Core:ButtonBorderDisabled = $Core:DisabledBorder
Core:ButtonBorderPressed = $Core:Accent
Core:ButtonBorderHovered = $Core:Accent
Core:ButtonBorderFocused = $Core:Accent
Core:ButtonBorderInactive = $Core:Border
Core:ButtonFillDisabled = $Core:DisabledBackground
Core:ButtonFillPressed = $Core:Accent
Core:ButtonFillHovered = $Core:AccentLighter
Core:ButtonFillFocused = $Core:Background
Core:ButtonFillInactive = $Core:Background
Core:ButtonTextDisabled = $Core:DisabledBorder
Core:ButtonTextPressed = $Core:Background
Core:ButtonTextHovered = $Core:Text
Core:ButtonTextFocused = $Core:Text
Core:ButtonTextInactive = $Core:Text
Core:CheckboxBorderDisabled = $Core:DisabledBorder
Core:CheckboxBorderPressed = $Core:Accent
Core:CheckboxBorderHovered = $Core:Accent
Core:CheckboxBorderFocused = $Core:Accent
Core:CheckboxBorderInactive = $Core:Border
Core:CheckboxFillDisabled = $Core:DisabledBackground
Core:CheckboxFillPressed = $Core:Accent
Core:CheckboxFillHovered = $Core:AccentLighter
Core:CheckboxFillFocused = $Core:Background
Core:CheckboxFillInactive = $Core:Background
Core:CheckboxTextDisabled = $Core:DisabledBorder
Core:CheckboxTextPressed = $Core:Accent
Core:CheckboxTextHovered = $Core:Text
Core:CheckboxTextFocused = $Core:Text
Core:CheckboxTextInactive = $Core:Text
Core:CheckboxCheckDisabled = $Core:DisabledBorder
Core:CheckboxCheckPressed = $Core:Accent
Core:CheckboxCheckHovered = $Core:Accent
Core:CheckboxCheckFocused = $Core:Accent
Core:CheckboxCheckInactive = $Core:Accent
Core:RadioButtonBorderDisabled = $Core:DisabledBorder
Core:RadioButtonBorderPressed = $Core:Accent
Core:RadioButtonBorderHovered = $Core:Accent
Core:RadioButtonBorderFocused = $Core:Accent
Core:RadioButtonBorderInactive = $Core:Border
Core:RadioButtonFillDisabled = $Core:DisabledBackground
Core:RadioButtonFillPressed = $Core:Accent
Core:RadioButtonFillHovered = $Core:AccentLighter
Core:RadioButtonFillFocused = $Core:Background
Core:RadioButtonFillInactive = $Core:Background
Core:RadioButtonTextDisabled = $Core:DisabledBorder
Core:RadioButtonTextPressed = $Core:Accent
Core:RadioButtonTextHovered = $Core:Text
Core:RadioButtonTextFocused = $Core:Text
Core:RadioButtonTextInactive = $Core:Text
Core:RadioButtonCheckDisabled = $Core:DisabledBorder
Core:RadioButtonCheckPressed = $Core:Accent
Core:RadioButtonCheckHovered = $Core:Accent
Core:RadioButtonCheckFocused = $Core:Accent
Core:RadioButtonCheckInactive = $Core:Accent
Core:MenuLayerTint = #6000