Compare commits
82 Commits
62154687c0
...
d3cf34a6c5
Author | SHA1 | Date | |
---|---|---|---|
d3cf34a6c5 | |||
![]() |
fc61c206c6 | ||
![]() |
e8ae89b635 | ||
aa5d6febc5 | |||
46ab8f4ead | |||
6d0e0fed03 | |||
41e99659be | |||
6c653f1aee | |||
89b04be4b7 | |||
2d02c9f41a | |||
c614af3d8d | |||
87577d5779 | |||
9d7ce76e56 | |||
6432412780 | |||
f2693a6ed2 | |||
![]() |
bc1613c448 | ||
67773ee2b3 | |||
8137f51617 | |||
c7b48c6011 | |||
![]() |
7b6e55b801 | ||
![]() |
ed7e9d61aa | ||
![]() |
8463903296 | ||
![]() |
9aa3bbae74 | ||
![]() |
b9e0718dc9 | ||
480d4b5353 | |||
4d2ab94290 | |||
d954db3189 | |||
b1455b5f5a | |||
8b2a2c400f | |||
6914e6fee7 | |||
7bc9aff799 | |||
b0ff831932 | |||
2bb14b6842 | |||
1214db6627 | |||
c71d5b3336 | |||
a55240f029 | |||
9baf7f8079 | |||
b2e506d87a | |||
fa81d56b71 | |||
55f4a01381 | |||
![]() |
0e394614c0 | ||
7b07c5980b | |||
33af2c9791 | |||
5e3db1d0a5 | |||
b0e6df12c8 | |||
0fcf612841 | |||
f16ae15858 | |||
dd76511af2 | |||
5f05501e6a | |||
17ade468a3 | |||
cae83fe2f9 | |||
04524a8a0b | |||
54eeef1c0c | |||
a0dac7f924 | |||
f1654c7e1f | |||
3a374e2f05 | |||
![]() |
fde17ed9e1 | ||
a1767897d2 | |||
0c2190baea | |||
![]() |
b17cb49114 | ||
d7e2e3d7f0 | |||
1b0cce5a48 | |||
f022e0e0c7 | |||
![]() |
4307ff48dc | ||
![]() |
7a7a0b192b | ||
cf7f22163c | |||
d0763ccf53 | |||
b35a19274a | |||
![]() |
153d0223db | ||
92b95501c9 | |||
fc09a982e4 | |||
b7bd3c7ade | |||
![]() |
29195be75d | ||
![]() |
cbe674bb0b | ||
![]() |
1fbdd40dda | ||
79f4ea3fe1 | |||
bc3ccb6e81 | |||
f530f0af9f | |||
7a0f30f8b7 | |||
8d7ab3e6e9 | |||
0ced7694e1 | |||
![]() |
f29e623a00 |
@ -41,4 +41,6 @@ test:
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
junit: "**/build/test-results/test/**/TEST-*.xml"
|
||||
junit:
|
||||
- "**/build/test-results/test/**/TEST-*.xml"
|
||||
- "**/build/reports/dependency-check-junit.xml"
|
||||
|
48
build.gradle
48
build.gradle
@ -1,48 +0,0 @@
|
||||
subprojects {
|
||||
group = 'eu.jonahbauer'
|
||||
version = '1.0-SNAPSHOT'
|
||||
|
||||
apply plugin: 'java'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
compileJava {
|
||||
sourceCompatibility = 17
|
||||
targetCompatibility = 17
|
||||
}
|
||||
|
||||
ext {
|
||||
log4j2 = "2.14.1"
|
||||
lombok = "1.18.22"
|
||||
|
||||
junit = "5.8.1"
|
||||
mockito = "4.0.0"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains:annotations:22.0.0"
|
||||
implementation "com.google.code.gson:gson:2.8.8"
|
||||
|
||||
implementation "org.apache.logging.log4j:log4j-api:$log4j2"
|
||||
implementation "org.apache.logging.log4j:log4j-core:$log4j2"
|
||||
implementation "org.apache.logging.log4j:log4j-slf4j-impl:$log4j2"
|
||||
|
||||
testImplementation "org.junit.jupiter:junit-jupiter:$junit"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-engine:$junit"
|
||||
testImplementation "org.mockito:mockito-inline:$mockito"
|
||||
testImplementation "org.mockito:mockito-junit-jupiter:$mockito"
|
||||
|
||||
compileOnly "org.projectlombok:lombok:$lombok"
|
||||
annotationProcessor "org.projectlombok:lombok:$lombok"
|
||||
|
||||
testCompileOnly "org.projectlombok:lombok:$lombok"
|
||||
testAnnotationProcessor "org.projectlombok:lombok:$lombok"
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
maxHeapSize = '256M'
|
||||
}
|
||||
}
|
74
build.gradle.kts
Normal file
74
build.gradle.kts
Normal file
@ -0,0 +1,74 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.owasp:dependency-check-gradle:6.5.0.1")
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply(plugin = "java-library")
|
||||
apply(plugin = "org.owasp.dependencycheck")
|
||||
|
||||
group = "eu.jonahbauer"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
val implementation by configurations
|
||||
val testImplementation by configurations
|
||||
val compileOnly by configurations
|
||||
val annotationProcessor by configurations
|
||||
val testCompileOnly by configurations
|
||||
val testAnnotationProcessor by configurations
|
||||
val runtimeOnly by configurations
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(Annotations.id)
|
||||
|
||||
implementation(Log4j2.api)
|
||||
runtimeOnly(Log4j2.core)
|
||||
runtimeOnly(Log4j2.slf4j)
|
||||
|
||||
testImplementation(JUnit.jupiter)
|
||||
testImplementation(JUnit.jupiter_engine)
|
||||
testImplementation(Mockito.inline)
|
||||
testImplementation(Mockito.jupiter)
|
||||
|
||||
compileOnly(Lombok.id)
|
||||
annotationProcessor(Lombok.id)
|
||||
|
||||
testCompileOnly(Lombok.id)
|
||||
testAnnotationProcessor(Lombok.id)
|
||||
}
|
||||
|
||||
tasks {
|
||||
withType<JavaCompile> {
|
||||
sourceCompatibility = "17"
|
||||
targetCompatibility = "17"
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
withType<Jar> {
|
||||
onlyIf {
|
||||
!project.the<SourceSetContainer>()["main"].allSource.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
named<Test>("test") {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
named("check") {
|
||||
dependsOn("dependencyCheckAnalyze")
|
||||
}
|
||||
}
|
||||
|
||||
configure<org.owasp.dependencycheck.gradle.extension.DependencyCheckExtension> {
|
||||
format = org.owasp.dependencycheck.reporting.ReportGenerator.Format.JUNIT
|
||||
}
|
||||
}
|
12
buildSrc/build.gradle.kts
Normal file
12
buildSrc/build.gradle.kts
Normal file
@ -0,0 +1,12 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "1.6.0"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.badlogicgames.gdx:gdx-tools:1.10.0")
|
||||
implementation(gradleApi())
|
||||
}
|
101
buildSrc/src/main/kotlin/Dependencies.kt
Normal file
101
buildSrc/src/main/kotlin/Dependencies.kt
Normal file
@ -0,0 +1,101 @@
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
object Log4j2 {
|
||||
const val version = "2.17.1"
|
||||
const val group = "org.apache.logging.log4j"
|
||||
|
||||
const val api = "$group:log4j-api:$version"
|
||||
const val core = "$group:log4j-core:$version"
|
||||
const val slf4j = "$group:log4j-slf4j-impl:$version"
|
||||
const val jul = "$group:log4j-jul:$version"
|
||||
}
|
||||
|
||||
object Lombok {
|
||||
const val version = "1.18.22"
|
||||
const val group = "org.projectlombok"
|
||||
|
||||
const val id = "$group:lombok:$version"
|
||||
}
|
||||
|
||||
object JUnit {
|
||||
const val version = "5.8.1"
|
||||
const val group = "org.junit.jupiter"
|
||||
|
||||
const val jupiter = "$group:junit-jupiter:$version"
|
||||
const val jupiter_engine = "$group:junit-jupiter-engine:$version"
|
||||
}
|
||||
|
||||
object Mockito {
|
||||
const val version = "4.0.0"
|
||||
const val group = "org.mockito"
|
||||
|
||||
const val inline = "$group:mockito-inline:$version"
|
||||
const val jupiter = "$group:mockito-junit-jupiter:$version"
|
||||
}
|
||||
|
||||
object Jackson {
|
||||
const val version = "2.13.1"
|
||||
|
||||
const val databind = "com.fasterxml.jackson.core:jackson-databind:$version"
|
||||
const val module_parameter_names = "com.fasterxml.jackson.module:jackson-module-parameter-names:$version"
|
||||
}
|
||||
|
||||
object Annotations {
|
||||
const val version = "22.0.0"
|
||||
const val group = "org.jetbrains"
|
||||
|
||||
const val id = "$group:annotations:$version"
|
||||
}
|
||||
|
||||
object LibGDX {
|
||||
const val version = "1.10.0"
|
||||
const val group = "com.badlogicgames.gdx"
|
||||
|
||||
const val api = "$group:gdx:$version"
|
||||
|
||||
const val backend_lwjgl3 = "$group:gdx-backend-lwjgl3:$version"
|
||||
const val platform_desktop = "$group:gdx-platform:$version:natives-desktop"
|
||||
|
||||
const val tools = "$group:gdx-tools:$version"
|
||||
}
|
||||
|
||||
object PicoCLI {
|
||||
const val version = "4.6.2"
|
||||
const val group = "info.picocli"
|
||||
|
||||
const val core = "$group:picocli:$version"
|
||||
const val shell_jline3 = "$group:picocli-shell-jline3:$version"
|
||||
const val codegen = "$group:picocli-codegen:$version"
|
||||
}
|
||||
|
||||
object JLine {
|
||||
const val version = "3.20.0"
|
||||
const val group = "org.jline"
|
||||
const val id = "$group:jline:$version"
|
||||
}
|
||||
|
||||
object Jansi {
|
||||
const val version = "2.4.0"
|
||||
const val group = "org.fusesource.jansi"
|
||||
const val id = "$group:jansi:$version"
|
||||
}
|
||||
|
||||
object JavaWebSocket {
|
||||
const val version = "1.5.2"
|
||||
const val group = "org.java-websocket"
|
||||
const val id = "$group:Java-WebSocket:$version"
|
||||
}
|
||||
|
||||
object SpringBoot {
|
||||
const val version = "2.6.2"
|
||||
const val group = "org.springframework.boot"
|
||||
const val plugin = group
|
||||
const val starterWebsocket = "$group:spring-boot-starter-websocket"
|
||||
const val starterTomcat = "$group:spring-boot-starter-tomcat"
|
||||
const val starterLog4j2 = "$group:spring-boot-starter-log4j2"
|
||||
}
|
||||
|
||||
object SpringDependencyManagement {
|
||||
const val version = "1.0.11.RELEASE"
|
||||
const val plugin = "io.spring.dependency-management"
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
import com.badlogic.gdx.tools.texturepacker.TexturePacker
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.tasks.InputDirectory
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.work.Incremental
|
||||
import org.gradle.work.InputChanges
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.collections.HashSet
|
||||
|
||||
abstract class TexturePackerTask : DefaultTask() {
|
||||
@get:Incremental
|
||||
@get:InputDirectory
|
||||
abstract val input : DirectoryProperty
|
||||
|
||||
@get:OutputDirectory
|
||||
abstract val resourceOutput : DirectoryProperty
|
||||
|
||||
@get:OutputDirectory
|
||||
abstract val generatedSourceOutput : DirectoryProperty
|
||||
|
||||
@TaskAction
|
||||
fun pack(changes: InputChanges) {
|
||||
val dirs = HashSet<File>()
|
||||
val inputDir = input.asFile.get()
|
||||
|
||||
if (changes.isIncremental) {
|
||||
val root = inputDir.toPath()
|
||||
for (change in changes.getFileChanges(input)) {
|
||||
if (!change.file.isFile) continue
|
||||
dirs.add(root.resolve(root.relativize(change.file.toPath()).subpath(0, 1)).toFile())
|
||||
}
|
||||
} else {
|
||||
inputDir.listFiles()?.filter { it.isDirectory }?.forEach { dirs.add(it) }
|
||||
}
|
||||
|
||||
val outputDir = resourceOutput.get().asFile
|
||||
for (dir in dirs) {
|
||||
TexturePacker.process(dir.path, outputDir.path, dir.name)
|
||||
|
||||
val atlas = File(outputDir, dir.name + ".atlas")
|
||||
if (atlas.exists()) {
|
||||
val name = dir.name[0].uppercaseChar() + dir.name.substring(1) + "Atlas"
|
||||
val path = outputDir.toPath().relativize(atlas.toPath()).toString()
|
||||
|
||||
val builder = StringBuilder()
|
||||
builder.append("""
|
||||
package eu.jonahbauer.wizard.client.libgdx;
|
||||
public class $name {
|
||||
public static final String ${'$'}PATH = "$path";
|
||||
|
||||
""".trimIndent())
|
||||
|
||||
val content = atlas.readText(Charsets.UTF_8)
|
||||
val matcher = Pattern.compile("(?m)^([^:]*?)\$\\n {2}").matcher(content)
|
||||
val fields = HashSet<String>()
|
||||
while (matcher.find()) {
|
||||
val texture = matcher.group(1)
|
||||
val field = texture.replace("-", "_").replace("/", "_").uppercase(Locale.ROOT)
|
||||
fields.add(" public static final String $field = \"$texture\";\n")
|
||||
}
|
||||
|
||||
fields.forEach(builder::append)
|
||||
builder.append("}\n")
|
||||
|
||||
val out = generatedSourceOutput.file("eu/jonahbauer/wizard/client/libgdx/${name}.java").get().asFile
|
||||
out.parentFile.mkdirs()
|
||||
out.writeText(builder.toString(), Charsets.UTF_8)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 3.6 MiB |
Binary file not shown.
Before Width: | Height: | Size: 3.3 MiB |
Binary file not shown.
Before Width: | Height: | Size: 3.4 MiB |
Binary file not shown.
Before Width: | Height: | Size: 3.4 MiB |
@ -1,11 +0,0 @@
|
||||
/*
|
||||
* This file was generated by the Gradle 'init' task.
|
||||
*/
|
||||
|
||||
rootProject.name = 'wizard'
|
||||
include(':wizard-common')
|
||||
include(':wizard-client:wizard-client-cli')
|
||||
include(':wizard-client:wizard-client-libgdx:core')
|
||||
include(':wizard-client:wizard-client-libgdx:desktop')
|
||||
include(':wizard-server')
|
||||
include(':wizard-core')
|
11
settings.gradle.kts
Normal file
11
settings.gradle.kts
Normal file
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* This file was generated by the Gradle 'init' task.
|
||||
*/
|
||||
|
||||
rootProject.name = "wizard"
|
||||
include(":wizard-common")
|
||||
include(":wizard-client:wizard-client-cli")
|
||||
include(":wizard-client:wizard-client-libgdx:core")
|
||||
include(":wizard-client:wizard-client-libgdx:desktop")
|
||||
include(":wizard-server")
|
||||
include(":wizard-core")
|
@ -1,6 +0,0 @@
|
||||
|
||||
dependencies {
|
||||
implementation project(':wizard-common')
|
||||
}
|
||||
|
||||
description = 'wizard-client'
|
22
wizard-client/wizard-client-cli/build.gradle.kts
Normal file
22
wizard-client/wizard-client-cli/build.gradle.kts
Normal file
@ -0,0 +1,22 @@
|
||||
plugins {
|
||||
application
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":wizard-common"))
|
||||
|
||||
implementation(JLine.id)
|
||||
implementation(Jansi.id)
|
||||
implementation(JavaWebSocket.id)
|
||||
implementation("org.ajbrown:name-machine:1.0.0")
|
||||
|
||||
implementation(PicoCLI.core)
|
||||
implementation(PicoCLI.shell_jline3) {
|
||||
exclude(group = JLine.group) // prevent duplicates with org.jline:jline
|
||||
}
|
||||
annotationProcessor(PicoCLI.codegen)
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("eu.jonahbauer.wizard.client.cli.Client")
|
||||
}
|
@ -0,0 +1,273 @@
|
||||
package eu.jonahbauer.wizard.client.cli;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.state.ClientState;
|
||||
import eu.jonahbauer.wizard.client.cli.state.Menu;
|
||||
import eu.jonahbauer.wizard.client.cli.util.DelegateCompleter;
|
||||
import eu.jonahbauer.wizard.client.cli.util.StateAwareFactory;
|
||||
import eu.jonahbauer.wizard.common.machine.TimeoutContext;
|
||||
import eu.jonahbauer.wizard.common.messages.client.ClientMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
import lombok.Getter;
|
||||
import org.java_websocket.framing.CloseFrame;
|
||||
import org.jline.reader.*;
|
||||
import org.jline.reader.impl.DefaultParser;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.terminal.TerminalBuilder;
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Model.CommandSpec;
|
||||
import picocli.shell.jline3.PicocliJLineCompleter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static picocli.CommandLine.*;
|
||||
|
||||
@Command
|
||||
public class Client extends TimeoutContext<ClientState, Client> implements Runnable {
|
||||
@Option(names = "-v")
|
||||
@Getter
|
||||
private boolean verbose;
|
||||
|
||||
private LineReader reader;
|
||||
|
||||
@Getter
|
||||
private ClientSocket socket;
|
||||
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
private final Condition ready = lock.newCondition();
|
||||
private boolean isReady = true;
|
||||
|
||||
private final DelegateCompleter completer = new DelegateCompleter();
|
||||
private CommandSpec spec;
|
||||
|
||||
public static void main(String[] args) {
|
||||
new CommandLine(new Client()).execute(args);
|
||||
}
|
||||
|
||||
public Client() {
|
||||
super(new Menu());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
update();
|
||||
|
||||
Parser parser = new DefaultParser();
|
||||
try (Terminal terminal = TerminalBuilder.builder().build()) {
|
||||
this.reader = LineReaderBuilder.builder()
|
||||
.terminal(terminal)
|
||||
.completer(completer)
|
||||
.parser(parser)
|
||||
.build();
|
||||
|
||||
String prompt = "> ";
|
||||
|
||||
String line;
|
||||
while (true) {
|
||||
lock.lock();
|
||||
try {
|
||||
while (!isReady) {
|
||||
ready.await();
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
try {
|
||||
line = reader.readLine(prompt).trim();
|
||||
terminal.flush();
|
||||
|
||||
if (line.isEmpty() || line.startsWith("#")) continue;
|
||||
if (line.equals("exit") || line.equals("quit")) break;
|
||||
|
||||
var parsedLine = parser.parse(line, 0);
|
||||
execute(() -> execute(spec, parsedLine));
|
||||
terminal.flush();
|
||||
} catch (UserInterruptException e) {
|
||||
// ignore
|
||||
} catch (EndOfFileException e) {
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(reader.getTerminal().writer());
|
||||
terminal.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
|
||||
if (socket != null && socket.isOpen()) {
|
||||
socket.close(CloseFrame.GOING_AWAY);
|
||||
}
|
||||
shutdownNow();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleError(Throwable t) {
|
||||
t.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTransition(ClientState from, ClientState to) {
|
||||
update(to);
|
||||
}
|
||||
|
||||
//<editor-fold desc="Socket" defaultstate="collapsed">
|
||||
public void setSocket(ClientSocket socket) {
|
||||
this.socket = socket;
|
||||
if (socket != null) {
|
||||
this.socket.setAttachment(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void onOpen() {
|
||||
execute(s -> s.onOpen(this));
|
||||
}
|
||||
|
||||
public void onMessage(ServerMessage message) {
|
||||
if (verbose) {
|
||||
println(message.toString());
|
||||
}
|
||||
execute(s -> s.onMessage(this, message));
|
||||
}
|
||||
|
||||
public void onClose(int code, String reason, boolean remote) {
|
||||
execute(s -> s.onClose(this, code, reason, remote));
|
||||
}
|
||||
|
||||
public void send(ClientMessage message) {
|
||||
getSocket().send(message.toString());
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
//<editor-fold desc="Output" defaultstate="collapsed">
|
||||
public void println(String str) {
|
||||
reader.printAbove(str);
|
||||
}
|
||||
|
||||
public void println(Object object) {
|
||||
println(Objects.toString(object));
|
||||
}
|
||||
|
||||
public void printfln(String format, Object...args) {
|
||||
reader.printAbove(format.formatted(args));
|
||||
}
|
||||
|
||||
public void print(String[]...columns) {
|
||||
print(null, null, columns);
|
||||
}
|
||||
|
||||
public void print(String prefix, String suffix, String[]...columns) {
|
||||
int[] length = new int[columns.length];
|
||||
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
length[i] = 0;
|
||||
for (int j = 0; j < columns[i].length; j++) {
|
||||
if (columns[i][j] == null) columns[i][j] = "null";
|
||||
var str = columns[i][j].length();
|
||||
if (str > length[i]) {
|
||||
length[i] = str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String format = IntStream.range(0, length.length)
|
||||
.mapToObj(i -> "%" + (i + 1) + "$" + (length[i]) + "s")
|
||||
.collect(Collectors.joining(" "));
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (prefix != null) {
|
||||
builder.append(prefix).append('\n');
|
||||
}
|
||||
String[] row = new String[columns.length];
|
||||
for (int j = 0; j < columns[0].length; j++) {
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
row[i] = columns[i][j];
|
||||
}
|
||||
builder.append(format.formatted((Object[]) row)).append('\n');
|
||||
}
|
||||
if (suffix != null) {
|
||||
builder.append(suffix);
|
||||
}
|
||||
|
||||
println(builder.toString());
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
public void ready() {
|
||||
lock.lock();
|
||||
try {
|
||||
isReady = true;
|
||||
ready.signalAll();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForReady() {
|
||||
lock.lock();
|
||||
try {
|
||||
isReady = false;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void update() {
|
||||
update(getState());
|
||||
}
|
||||
|
||||
private void update(ClientState state) {
|
||||
synchronized (completer) {
|
||||
var commandLine = new CommandLine(state.getCommand(), new StateAwareFactory(this, state));
|
||||
var spec = commandLine.getCommandSpec();
|
||||
if (this.spec != spec) {
|
||||
this.spec = spec;
|
||||
completer.setDelegate(new PicocliJLineCompleter(spec));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<ClientState> execute(CommandSpec spec, ParsedLine line) {
|
||||
var commandLine = spec.commandLine();
|
||||
|
||||
AtomicReference<Exception> exceptionRef = new AtomicReference<>();
|
||||
commandLine.setExecutionExceptionHandler((ex, cl, parseResult) -> {
|
||||
exceptionRef.set(ex);
|
||||
return -1;
|
||||
});
|
||||
commandLine.execute(line.words().toArray(String[]::new));
|
||||
|
||||
Exception exception = exceptionRef.get();
|
||||
if (exception == null) {
|
||||
Object result;
|
||||
ParseResult parseResult = commandLine.getParseResult();
|
||||
if (parseResult.subcommand() != null) {
|
||||
CommandLine sub = parseResult.subcommand().commandSpec().commandLine();
|
||||
result = sub.getExecutionResult();
|
||||
} else {
|
||||
result = commandLine.getExecutionResult();
|
||||
}
|
||||
|
||||
if (result instanceof ClientState state) {
|
||||
return Optional.of(state);
|
||||
} else if (result instanceof Optional) {
|
||||
//noinspection unchecked
|
||||
return (Optional<ClientState>) result;
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
} else {
|
||||
handleError(exception);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package eu.jonahbauer.wizard.client.cli;
|
||||
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
import org.java_websocket.client.WebSocketClient;
|
||||
import org.java_websocket.framing.CloseFrame;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.net.URI;
|
||||
|
||||
public class ClientSocket extends WebSocketClient {
|
||||
public ClientSocket(URI serverUri) {
|
||||
super(serverUri);
|
||||
|
||||
if ("wss".equals(getURI().getScheme())) {
|
||||
setSocketFactory(SSLSocketFactory.getDefault());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(ServerHandshake serverHandshake) {
|
||||
getClient().onOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String s) {
|
||||
ServerMessage message = ServerMessage.parse(s);
|
||||
getClient().onMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(int i, String s, boolean b) {
|
||||
getClient().onClose(i, s, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception e) {
|
||||
getClient().println(e.getMessage());
|
||||
close(CloseFrame.ABNORMAL_CLOSE, e.getMessage());
|
||||
}
|
||||
|
||||
private Client getClient() {
|
||||
return getAttachment();
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package eu.jonahbauer.wizard.client.cli.commands;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.client.cli.state.Game;
|
||||
import eu.jonahbauer.wizard.common.messages.client.InteractionMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.JuggleMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PickTrumpMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PlayCardMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PredictMessage;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static picocli.CommandLine.*;
|
||||
|
||||
@Command(name = "\b", subcommands = {HelpCommand.class, QuitCommand.class, ShowCommand.class})
|
||||
public class GameCommand {
|
||||
private final Client client;
|
||||
|
||||
public GameCommand(Client client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Command(name = "play")
|
||||
public void play(
|
||||
@Parameters(index = "0", paramLabel = "<card>", completionCandidates = HandCompletion.class) Card card
|
||||
) {
|
||||
this.client.send(new InteractionMessage(new PlayCardMessage(card)));
|
||||
this.client.waitForReady();
|
||||
}
|
||||
|
||||
@Command(name = "predict")
|
||||
public void predict(
|
||||
@Parameters(index = "0", paramLabel = "<prediction>") int prediction
|
||||
) {
|
||||
this.client.send(new InteractionMessage(new PredictMessage(prediction)));
|
||||
this.client.waitForReady();
|
||||
}
|
||||
|
||||
@Command(name = "trump")
|
||||
public void trump(
|
||||
@Parameters(index = "0", paramLabel = "<suit>") Card.Suit suit
|
||||
) {
|
||||
this.client.send(new InteractionMessage(new PickTrumpMessage(suit)));
|
||||
this.client.waitForReady();
|
||||
}
|
||||
|
||||
@Command(name = "juggle")
|
||||
public void juggle(
|
||||
@Parameters(index = "0", paramLabel = "<card>", completionCandidates = JuggleCompletion.class) Card card
|
||||
) {
|
||||
this.client.send(new InteractionMessage(new JuggleMessage(card)));
|
||||
this.client.waitForReady();
|
||||
}
|
||||
|
||||
public static class HandCompletion implements Iterable<String> {
|
||||
private final Game game;
|
||||
|
||||
public HandCompletion(Game game) {
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return game.getHands().get(game.getSelf()).stream().flatMap(c -> {
|
||||
if (c == Card.CLOUD) {
|
||||
return Stream.of(Card.CLOUD_BLUE, Card.CLOUD_GREEN, Card.CLOUD_RED, Card.CLOUD_YELLOW);
|
||||
} else if (c == Card.JUGGLER) {
|
||||
return Stream.of(Card.JUGGLER_BLUE, Card.JUGGLER_GREEN, Card.JUGGLER_RED, Card.JUGGLER_YELLOW);
|
||||
} else if (c == Card.CHANGELING) {
|
||||
return Stream.of(Card.CHANGELING_JESTER, Card.CHANGELING_WIZARD);
|
||||
} else {
|
||||
return Stream.of(c);
|
||||
}
|
||||
}).map(Card::toString).iterator();
|
||||
}
|
||||
}
|
||||
|
||||
public static class JuggleCompletion implements Iterable<String> {
|
||||
private final Game game;
|
||||
|
||||
public JuggleCompletion(Game game) {
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return game.getHands().get(game.getSelf()).stream().map(Card::toString).iterator();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package eu.jonahbauer.wizard.client.cli.commands;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.client.cli.state.AwaitingJoinSession;
|
||||
import eu.jonahbauer.wizard.client.cli.state.Lobby;
|
||||
import eu.jonahbauer.wizard.client.cli.state.Menu;
|
||||
import eu.jonahbauer.wizard.common.messages.client.CreateSessionMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.client.JoinSessionMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.client.RejoinMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.data.SessionData;
|
||||
import eu.jonahbauer.wizard.common.model.Configuration;
|
||||
import lombok.Getter;
|
||||
import org.ajbrown.namemachine.NameGenerator;
|
||||
import org.java_websocket.framing.CloseFrame;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.UUID;
|
||||
|
||||
import static picocli.CommandLine.*;
|
||||
|
||||
@Command(name = "\b", subcommands = {HelpCommand.class, QuitCommand.class})
|
||||
public class LobbyCommand {
|
||||
@Getter(lazy = true)
|
||||
private static final NameGenerator nameGenerator = new NameGenerator();
|
||||
|
||||
private final Client client;
|
||||
private final Lobby lobby;
|
||||
|
||||
public LobbyCommand(Client client, Lobby lobby) {
|
||||
this.client = client;
|
||||
this.lobby = lobby;
|
||||
}
|
||||
|
||||
@Command(name = "list", description = "Shows a list of available sessions")
|
||||
public void list() {
|
||||
var sessions = lobby.getSessions();
|
||||
var count = sessions.size();
|
||||
if (count > 0) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
if (count == 1) builder.append("There is one open session:\n");
|
||||
else builder.append("There are ").append(count).append(" open sessions:\n");
|
||||
|
||||
sessions.forEach((uuid, session) -> builder.append(session).append('\n'));
|
||||
client.println(builder);
|
||||
} else {
|
||||
client.println("No sessions.");
|
||||
}
|
||||
}
|
||||
|
||||
@Command(name = "disconnect", description = "Disconnects from the server")
|
||||
public Menu disconnect() {
|
||||
client.getSocket().close(CloseFrame.GOING_AWAY);
|
||||
client.setSocket(null);
|
||||
return new Menu();
|
||||
}
|
||||
|
||||
@Command(name = "join", description = "Joins the specified session")
|
||||
public AwaitingJoinSession join(
|
||||
@Parameters(index = "0", paramLabel = "<session>", description = "session uuid", completionCandidates = SessionCompleter.class) UUID session,
|
||||
@Option(names = "--name", description = "user name") String userName
|
||||
) {
|
||||
if (userName == null) {
|
||||
userName = getNameGenerator().generateName().getFirstName();
|
||||
client.println("Your name will be " + userName + ".");
|
||||
}
|
||||
|
||||
client.send(new JoinSessionMessage(session, userName));
|
||||
return new AwaitingJoinSession();
|
||||
}
|
||||
|
||||
@Command(name = "create")
|
||||
public AwaitingJoinSession create(
|
||||
@Parameters(index = "0", paramLabel = "<session>", description = "human readable session name") String sessionName,
|
||||
@Option(names = "--name", description = "user name") String userName,
|
||||
@Option(names = "--configuration", description = "game configuration", defaultValue = "DEFAULT") Configuration configuration,
|
||||
@Option(names = "--timeout", description = "interaction timeout", defaultValue = "60000") long timeout
|
||||
) {
|
||||
if (userName == null) {
|
||||
userName = getNameGenerator().generateName().getFirstName();
|
||||
client.println("Your name will be " + userName + ".");
|
||||
}
|
||||
|
||||
client.send(new CreateSessionMessage(sessionName, userName, timeout, configuration));
|
||||
return new AwaitingJoinSession();
|
||||
}
|
||||
|
||||
@Command(name = "rejoin")
|
||||
public AwaitingJoinSession rejoin(
|
||||
@Parameters(index = "0", paramLabel = "<session>", description = "session uuid", completionCandidates = SessionCompleter.class) UUID session,
|
||||
@Parameters(index = "1", paramLabel = "<player>", description = "player uuid") UUID player,
|
||||
@Parameters(index = "2", paramLabel = "<secret>", description = "player secret") String secret
|
||||
) {
|
||||
client.send(new RejoinMessage(session, player, secret));
|
||||
return new AwaitingJoinSession();
|
||||
}
|
||||
|
||||
public static class SessionCompleter implements Iterable<String> {
|
||||
private final Lobby lobby;
|
||||
|
||||
private SessionCompleter(Lobby lobby) {
|
||||
this.lobby = lobby;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return lobby.getSessions().values().stream().map(SessionData::getUuid).map(UUID::toString).iterator();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package eu.jonahbauer.wizard.client.cli.commands;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.client.cli.ClientSocket;
|
||||
import eu.jonahbauer.wizard.client.cli.state.AwaitingConnection;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import static picocli.CommandLine.*;
|
||||
|
||||
@Command(name = "\b", subcommands = {HelpCommand.class, QuitCommand.class})
|
||||
public class MenuCommand {
|
||||
private final Client client;
|
||||
|
||||
public MenuCommand(Client client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Command(name = "connect", description = "Connects to the specified server")
|
||||
public AwaitingConnection connect(
|
||||
@Parameters(index = "0", paramLabel = "<uri>", description = "server uri") URI uri
|
||||
) {
|
||||
ClientSocket socket = new ClientSocket(uri);
|
||||
client.setSocket(socket);
|
||||
socket.connect();
|
||||
return new AwaitingConnection();
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package eu.jonahbauer.wizard.client.cli.commands;
|
||||
|
||||
import static picocli.CommandLine.Command;
|
||||
|
||||
@Command(name = "quit", aliases = "exit")
|
||||
public class QuitCommand {
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package eu.jonahbauer.wizard.client.cli.commands;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.client.cli.state.AwaitingJoinLobby;
|
||||
import eu.jonahbauer.wizard.client.cli.state.Session;
|
||||
import eu.jonahbauer.wizard.common.messages.client.LeaveSessionMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.client.ReadyMessage;
|
||||
|
||||
import static picocli.CommandLine.*;
|
||||
|
||||
@Command(name = "\b", subcommands = {HelpCommand.class, QuitCommand.class})
|
||||
public class SessionCommand {
|
||||
private final Client client;
|
||||
private final Session session;
|
||||
|
||||
public SessionCommand(Client client, Session session) {
|
||||
this.client = client;
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Command(name = "ready")
|
||||
public void ready(
|
||||
@Parameters(index = "0", paramLabel = "<ready>", defaultValue = "true") boolean ready
|
||||
) {
|
||||
session.setNextReady(ready);
|
||||
client.send(new ReadyMessage(ready));
|
||||
client.waitForReady();
|
||||
}
|
||||
|
||||
@Command(name = "leave", description = "Leaves the current session and returns to the lobby")
|
||||
public AwaitingJoinLobby leave() {
|
||||
client.send(new LeaveSessionMessage());
|
||||
return new AwaitingJoinLobby();
|
||||
}
|
||||
|
||||
@Command(name = "list", description = "Shows a list of players in the current session")
|
||||
public void list() {
|
||||
var players = session.getPlayers();
|
||||
var count = players.size();
|
||||
if (count > 0) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
if (count == 1) builder.append("There is one player in this session:\n");
|
||||
else builder.append("There are ").append(count).append(" players in this session:\n");
|
||||
|
||||
players.forEach((id, session) -> builder.append(session).append('\n'));
|
||||
client.println(builder);
|
||||
} else {
|
||||
client.println("There are no players in this session.");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package eu.jonahbauer.wizard.client.cli.commands;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.client.cli.state.Game;
|
||||
import eu.jonahbauer.wizard.client.cli.util.Pair;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import static picocli.CommandLine.Command;
|
||||
|
||||
@Command(name = "show")
|
||||
public class ShowCommand {
|
||||
private final Client client;
|
||||
private final Game game;
|
||||
|
||||
public ShowCommand(Client client, Game game) {
|
||||
this.client = client;
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
@Command(name = "players")
|
||||
public void players() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Map.Entry<UUID, String> player : game.getPlayers().entrySet()) {
|
||||
builder.append(player.getValue()).append('\t').append(player.getKey()).append('\n');
|
||||
}
|
||||
client.println(builder.toString());
|
||||
}
|
||||
|
||||
@Command(name = "stack")
|
||||
public void stack() {
|
||||
var stack = game.getStack();
|
||||
String[] col0 = new String[stack.size()];
|
||||
String[] col1 = new String[stack.size()];
|
||||
int i = 0;
|
||||
for (Pair<UUID, Card> entry : game.getStack()) {
|
||||
col0[i] = Objects.toString(entry.getValue());
|
||||
col1[i] = Objects.toString(game.nameOf(entry.getKey()));
|
||||
i++;
|
||||
}
|
||||
|
||||
client.print(col0, col1);
|
||||
}
|
||||
|
||||
@Command(name = "hand")
|
||||
public void hand() {
|
||||
client.println(game.getHands().get(game.getSelf()));
|
||||
}
|
||||
|
||||
@Command(name = "predictions", aliases = "tricks")
|
||||
public void predictions() {
|
||||
var players = game.getPlayers().keySet();
|
||||
String[] col0 = new String[players.size() + 1];
|
||||
String[] col1 = new String[players.size() + 1];
|
||||
String[] col2 = new String[players.size() + 1];
|
||||
col0[0] = "";
|
||||
col1[0] = "Prediction";
|
||||
col2[0] = "Tricks";
|
||||
int i = 1;
|
||||
for (UUID player : players) {
|
||||
col0[i] = game.nameOf(player);
|
||||
col1[i] = Objects.toString(game.getPredictions().get(player));
|
||||
col2[i] = Objects.toString(game.getTricks().getOrDefault(player, List.of()).stream().filter(l -> !l.contains(Card.BOMB)).count());
|
||||
i++;
|
||||
}
|
||||
|
||||
client.print(col0, col1, col2);
|
||||
}
|
||||
|
||||
@Command(name = "trump")
|
||||
public void trump() {
|
||||
client.printfln("%s (%s)", game.getTrumpSuit(), game.getTrumpCard());
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package eu.jonahbauer.wizard.client.cli.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
import lombok.SneakyThrows;
|
||||
import picocli.CommandLine.Model.CommandSpec;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class Awaiting extends BaseState implements ClientState {
|
||||
private static final CommandSpec COMMAND_SPEC = CommandSpec.create();
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||
return unexpectedMessage(client, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Optional<ClientState> onEnter(Client client) {
|
||||
client.waitForReady();
|
||||
client.timeout(this, 10_000);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onTimeout(Client client) {
|
||||
client.println("Timed out. Returning to menu");
|
||||
return Optional.of(new Menu());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCommand() {
|
||||
return COMMAND_SPEC;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package eu.jonahbauer.wizard.client.cli.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class AwaitingConnection extends Awaiting {
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onEnter(Client client) {
|
||||
client.println("Awaiting connection...");
|
||||
return super.onEnter(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onOpen(Client client) {
|
||||
client.println("Connection established.");
|
||||
return Optional.of(new AwaitingJoinLobby());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onClose(Client client, int code, String reason, boolean remote) {
|
||||
client.printfln("Connection could not be established. (code=%d, reason=%s)", code, reason);
|
||||
return Optional.of(new Menu());
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package eu.jonahbauer.wizard.client.cli.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.SessionListMessage;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class AwaitingJoinLobby extends Awaiting {
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onEnter(Client client) {
|
||||
client.println("Waiting for session list...");
|
||||
return super.onEnter(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||
if (message instanceof SessionListMessage list) {
|
||||
return Optional.of(new Lobby(list.getSessions()));
|
||||
} else {
|
||||
return super.onMessage(client, message);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package eu.jonahbauer.wizard.client.cli.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.common.messages.server.*;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class AwaitingJoinSession extends Awaiting {
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onEnter(Client client) {
|
||||
client.println("Waiting for acknowledgment...");
|
||||
return super.onEnter(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||
if (message instanceof SessionJoinedMessage joined) {
|
||||
return Optional.of(new Session(joined));
|
||||
} else if (message instanceof NackMessage nack) {
|
||||
switch (nack.getCode()) {
|
||||
case NackMessage.GAME_ALREADY_STARTED -> client.println("Error: Game has already started.");
|
||||
case NackMessage.SESSION_FULL -> client.println("Error: The session is full.");
|
||||
case NackMessage.SESSION_NOT_FOUND -> client.println("Error: Session not found.");
|
||||
case NackMessage.PLAYER_NAME_TAKEN -> client.println("Player name already taken.");
|
||||
case NackMessage.PLAYER_NAME_NOT_ALLOWED -> client.println("Player name not allowed.");
|
||||
case NackMessage.SESSION_NAME_TAKEN -> client.println("Session name already taken.");
|
||||
case NackMessage.SESSION_NAME_NOT_ALLOWED -> client.println("Session name not allowed.");
|
||||
default -> client.println("Nack " + nack.getCode() + ": " + nack.getMessage());
|
||||
}
|
||||
return Optional.of(new AwaitingJoinLobby());
|
||||
} else if (message instanceof SessionModifiedMessage || message instanceof SessionRemovedMessage) {
|
||||
// drop
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return super.onMessage(client, message);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package eu.jonahbauer.wizard.client.cli.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class BaseState implements ClientState {
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onEnter(Client context) {
|
||||
context.ready();
|
||||
return ClientState.super.onEnter(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onOpen(Client client) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onClose(Client client, int code, String reason, boolean remote) {
|
||||
if (remote) {
|
||||
client.printfln("Lost connection (code=%d, reason=%s).", code, reason);
|
||||
} else {
|
||||
client.printfln("Connection closed (code=%d, reason=%s).", code, reason);
|
||||
}
|
||||
return Optional.of(new Menu());
|
||||
}
|
||||
|
||||
protected static Optional<ClientState> unexpectedMessage(Client client, ServerMessage message) {
|
||||
// return to menu on unexpected message
|
||||
client.println("Fatal: Unexpected message " + message + ". Returning to menu.");
|
||||
if (client.isVerbose()) {
|
||||
new Exception().printStackTrace();
|
||||
}
|
||||
return Optional.of(new Menu());
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package eu.jonahbauer.wizard.client.cli.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.common.machine.TimeoutState;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ClientState extends TimeoutState<ClientState, Client> {
|
||||
Optional<ClientState> onOpen(Client client);
|
||||
|
||||
Optional<ClientState> onMessage(Client client, ServerMessage message);
|
||||
|
||||
Optional<ClientState> onClose(Client client, int code, String reason, boolean remote);
|
||||
|
||||
Object getCommand();
|
||||
}
|
@ -0,0 +1,208 @@
|
||||
package eu.jonahbauer.wizard.client.cli.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.client.cli.commands.GameCommand;
|
||||
import eu.jonahbauer.wizard.client.cli.util.Pair;
|
||||
import eu.jonahbauer.wizard.common.messages.client.InteractionMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.data.PlayerData;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.*;
|
||||
import eu.jonahbauer.wizard.common.messages.player.ContinueMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.AckMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.GameMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.NackMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
public final class Game extends BaseState {
|
||||
private final UUID self;
|
||||
private final UUID session;
|
||||
private final String secret;
|
||||
|
||||
private final Map<UUID, String> players;
|
||||
private final Map<UUID, Integer> scores = new HashMap<>();
|
||||
|
||||
private int round = -1;
|
||||
private final Map<UUID, Integer> predictions = new HashMap<>();
|
||||
private final Map<UUID, List<Card>> hands = new HashMap<>();
|
||||
private final Map<UUID, List<List<Card>>> tricks = new HashMap<>();
|
||||
|
||||
private int trick = -1;
|
||||
private final List<Pair<UUID, Card>> stack = new ArrayList<>();
|
||||
|
||||
private Card trumpCard;
|
||||
private Card.Suit trumpSuit;
|
||||
|
||||
public Game(UUID self, UUID session, String secret, Map<UUID, String> players) {
|
||||
this.self = self;
|
||||
this.session = session;
|
||||
this.secret = secret;
|
||||
this.players = players;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||
if (message instanceof GameMessage game) {
|
||||
var observerMessage = game.getObserverMessage();
|
||||
if (observerMessage instanceof StateMessage state) {
|
||||
switch (state.getState()) {
|
||||
case "starting_round" -> {
|
||||
client.printfln("Round %d is starting...", ++round);
|
||||
predictions.clear();
|
||||
tricks.clear();
|
||||
trumpSuit = null;
|
||||
trumpCard = null;
|
||||
stack.clear();
|
||||
trick = -1;
|
||||
}
|
||||
case "starting_trick" -> {
|
||||
client.printfln("Trick %d is starting...", ++trick);
|
||||
stack.clear();
|
||||
}
|
||||
case "finished" -> {
|
||||
client.println("The game has finished.");
|
||||
return returnToSession();
|
||||
}
|
||||
case "error" -> {
|
||||
client.println("The game has finished with an error.");
|
||||
return returnToSession();
|
||||
}
|
||||
}
|
||||
} else if (observerMessage instanceof HandMessage hand) {
|
||||
hands.put(hand.getPlayer(), new ArrayList<>(hand.getHand()));
|
||||
if (hand.getPlayer().equals(self)) {
|
||||
client.printfln("Your hand cards are: %s", hand.getHand());
|
||||
} else {
|
||||
client.printfln("%s's hand cards are: %s", nameOf(hand.getPlayer()), hand.getHand());
|
||||
}
|
||||
} else if (observerMessage instanceof PredictionMessage prediction) {
|
||||
predictions.put(prediction.getPlayer(), prediction.getPrediction());
|
||||
if (prediction.getPlayer().equals(self)) {
|
||||
client.printfln("You predicted: %d", prediction.getPrediction());
|
||||
} else {
|
||||
client.printfln("%s predicted: %d", nameOf(prediction.getPlayer()), prediction.getPrediction());
|
||||
}
|
||||
} else if (observerMessage instanceof TrumpMessage trump) {
|
||||
trumpCard = trump.getCard();
|
||||
trumpSuit = trump.getSuit();
|
||||
if (trumpCard == null) {
|
||||
client.println("There is no trump in this round.");
|
||||
} else {
|
||||
client.printfln("The trump suit is %s (%s).", trumpSuit, trumpCard);
|
||||
}
|
||||
} else if (observerMessage instanceof TrickMessage trick) {
|
||||
this.stack.clear();
|
||||
this.tricks.computeIfAbsent(trick.getPlayer(), player -> new ArrayList<>())
|
||||
.add(trick.getCards());
|
||||
client.printfln("This trick %s goes to %s.", trick.getCards(), nameOf(trick.getPlayer()));
|
||||
} else if (observerMessage instanceof CardMessage card) {
|
||||
this.stack.add(Pair.of(card.getPlayer(), card.getCard()));
|
||||
|
||||
var hand = this.hands.get(card.getPlayer());
|
||||
if (hand != null) {
|
||||
switch (card.getCard()) {
|
||||
case CHANGELING_JESTER, CHANGELING_WIZARD -> hand.remove(Card.CHANGELING);
|
||||
case JUGGLER_BLUE, JUGGLER_GREEN, JUGGLER_RED, JUGGLER_YELLOW -> hand.remove(Card.JUGGLER);
|
||||
case CLOUD_BLUE, CLOUD_GREEN, CLOUD_RED, CLOUD_YELLOW -> hand.remove(Card.CLOUD);
|
||||
default -> hand.remove(card.getCard());
|
||||
}
|
||||
}
|
||||
|
||||
if (card.getPlayer().equals(self)) {
|
||||
client.printfln("You played %s.", card.getCard());
|
||||
} else {
|
||||
client.printfln("%s played %s.", nameOf(card.getPlayer()), card.getCard());
|
||||
}
|
||||
} else if (observerMessage instanceof ScoreMessage score) {
|
||||
score.getPoints().forEach((player, points) -> scores.merge(player, points, Integer::sum));
|
||||
|
||||
String[] col0 = new String[players.size()];
|
||||
String[] col1 = new String[players.size()];
|
||||
|
||||
int i = 0;
|
||||
for (UUID player : players.keySet()) {
|
||||
col0[i] = nameOf(player);
|
||||
col1[i] = Objects.toString(scores.getOrDefault(player, 0));
|
||||
i++;
|
||||
}
|
||||
|
||||
client.print("The scores are as follows:", "", col0, col1);
|
||||
} else if (observerMessage instanceof UserInputMessage input) {
|
||||
if (input.getAction() == UserInputMessage.Action.SYNC) {
|
||||
client.send(new InteractionMessage(new ContinueMessage()));
|
||||
} else if (self.equals(input.getPlayer())) {
|
||||
client.printfln("It is your turn to %s. You have time until %s.", switch (input.getAction()) {
|
||||
case CHANGE_PREDICTION -> "change your prediction";
|
||||
case JUGGLE_CARD -> "juggle a card";
|
||||
case PLAY_CARD -> "play a card";
|
||||
case PICK_TRUMP -> "pick the trump suit";
|
||||
case MAKE_PREDICTION -> "make a prediction";
|
||||
default -> throw new AssertionError();
|
||||
}, LocalDateTime.ofInstant(Instant.ofEpochMilli(input.getTimeout()), ZoneId.systemDefault()));
|
||||
} else {
|
||||
client.printfln(
|
||||
"Waiting for input %s from %s. (times out at %s)",
|
||||
input.getAction(),
|
||||
nameOf(input.getPlayer()),
|
||||
LocalDateTime.ofInstant(Instant.ofEpochMilli(input.getTimeout()),
|
||||
ZoneId.systemDefault()
|
||||
)
|
||||
);
|
||||
}
|
||||
} else if (observerMessage instanceof TimeoutMessage) {
|
||||
client.println("Timed out.");
|
||||
} else {
|
||||
throw new AssertionError("Unknown observer message " + observerMessage.getClass().getSimpleName() + "");
|
||||
}
|
||||
return Optional.empty();
|
||||
} else if (message instanceof NackMessage nack) {
|
||||
int code = nack.getCode();
|
||||
if (code == NackMessage.ILLEGAL_ARGUMENT || code == NackMessage.ILLEGAL_STATE) {
|
||||
client.println("Error: " + nack.getMessage());
|
||||
client.ready();
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return unexpectedMessage(client, message);
|
||||
}
|
||||
} else if (message instanceof AckMessage) {
|
||||
client.ready();
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return unexpectedMessage(client, message);
|
||||
}
|
||||
}
|
||||
|
||||
public String nameOf(UUID player) {
|
||||
if (player == null) {
|
||||
return "all players";
|
||||
} else {
|
||||
return players.get(player);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCommand() {
|
||||
return GameCommand.class;
|
||||
}
|
||||
|
||||
private Optional<ClientState> returnToSession() {
|
||||
return Optional.of(new Session(
|
||||
self,
|
||||
session,
|
||||
secret,
|
||||
false,
|
||||
players.entrySet().stream()
|
||||
.collect(Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
entry -> new PlayerData(entry.getKey(), entry.getValue(), false)
|
||||
))
|
||||
));
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package eu.jonahbauer.wizard.client.cli.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.client.cli.commands.LobbyCommand;
|
||||
import eu.jonahbauer.wizard.common.messages.data.SessionData;
|
||||
import eu.jonahbauer.wizard.common.messages.server.*;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public final class Lobby extends BaseState {
|
||||
@Getter
|
||||
private final Map<UUID, SessionData> sessions = new HashMap<>();
|
||||
|
||||
public Lobby(Collection<SessionData> sessions) {
|
||||
sessions.forEach(session -> this.sessions.put(session.getUuid(), session));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onEnter(Client client) {
|
||||
if (sessions.size() == 1) {
|
||||
client.println("Successfully joined the server. There is one open session.");
|
||||
} else {
|
||||
client.printfln("Successfully joined the server. There are %d open sessions.", sessions.size());
|
||||
}
|
||||
return super.onEnter(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||
if (message instanceof SessionCreatedMessage created) {
|
||||
var session = created.getSession();
|
||||
sessions.put(session.getUuid(), session);
|
||||
return Optional.empty();
|
||||
} else if (message instanceof SessionRemovedMessage removed) {
|
||||
sessions.remove(removed.getSession());
|
||||
return Optional.empty();
|
||||
} else if (message instanceof SessionModifiedMessage modified) {
|
||||
var session = modified.getSession();
|
||||
sessions.put(session.getUuid(), session);
|
||||
return Optional.empty();
|
||||
} else if (message instanceof SessionListMessage list) {
|
||||
sessions.clear();
|
||||
list.getSessions().forEach(session -> sessions.put(session.getUuid(), session));
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return unexpectedMessage(client, message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCommand() {
|
||||
return LobbyCommand.class;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package eu.jonahbauer.wizard.client.cli.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.client.cli.commands.MenuCommand;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
import lombok.SneakyThrows;
|
||||
import org.java_websocket.framing.CloseFrame;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class Menu extends BaseState {
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Optional<ClientState> onEnter(Client client) {
|
||||
if (client.getSocket() != null && client.getSocket().isOpen()) {
|
||||
client.getSocket().close(CloseFrame.GOING_AWAY);
|
||||
client.waitForReady();
|
||||
return Optional.empty();
|
||||
}
|
||||
return super.onEnter(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||
// it is possible that there are messages still queued after
|
||||
// returning to the menu as a result of a previous message
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onClose(Client client, int code, String reason, boolean remote) {
|
||||
client.ready();
|
||||
super.onClose(client, code, reason, remote);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCommand() {
|
||||
return MenuCommand.class;
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
package eu.jonahbauer.wizard.client.cli.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.client.cli.commands.SessionCommand;
|
||||
import eu.jonahbauer.wizard.common.messages.data.PlayerData;
|
||||
import eu.jonahbauer.wizard.common.messages.server.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import picocli.CommandLine.Model.CommandSpec;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
public final class Session extends BaseState {
|
||||
private static final CommandSpec COMMAND_SPEC = CommandSpec.forAnnotatedObject(new SessionCommand(null, null));
|
||||
|
||||
private final UUID self;
|
||||
private final UUID session;
|
||||
private final String secret;
|
||||
private final Map<UUID, PlayerData> players = new HashMap<>();
|
||||
|
||||
private boolean ready;
|
||||
@Setter
|
||||
private Boolean nextReady;
|
||||
|
||||
public Session(SessionJoinedMessage joined) {
|
||||
this.self = joined.getPlayer();
|
||||
this.session = joined.getSession();
|
||||
this.ready = false;
|
||||
this.secret = joined.getSecret();
|
||||
joined.getPlayers().forEach(player -> players.put(player.getUuid(), player));
|
||||
}
|
||||
|
||||
public Session(UUID self, UUID session, String secret, boolean ready, Map<UUID, PlayerData> players) {
|
||||
this.self = self;
|
||||
this.session = session;
|
||||
this.secret = secret;
|
||||
this.players.putAll(players);
|
||||
this.ready = ready;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onEnter(Client client) {
|
||||
if (players.size() - 1 == 1) {
|
||||
client.printfln("Successfully joined session %s. There is one other player.", session);
|
||||
} else {
|
||||
client.printfln("Successfully joined session %s. There are %d other players.", session, players.size() - 1);
|
||||
}
|
||||
client.printfln("You are %s. Your secret is %s", self, secret);
|
||||
return super.onEnter(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||
if (message instanceof PlayerJoinedMessage join) {
|
||||
var player = join.getPlayer();
|
||||
players.put(player.getUuid(), player);
|
||||
client.printfln("Player \"%s\" joined the session.", player.getName());
|
||||
return Optional.empty();
|
||||
} else if (message instanceof PlayerLeftMessage leave) {
|
||||
var player = players.remove(leave.getPlayer());
|
||||
client.printfln("Player \"%s\" left the session.", player.getName());
|
||||
return Optional.empty();
|
||||
} else if (message instanceof PlayerModifiedMessage modified) {
|
||||
var player = modified.getPlayer();
|
||||
players.put(player.getUuid(), player);
|
||||
client.printfln("Player \"%s\" was modified.", player.getName());
|
||||
return Optional.empty();
|
||||
} else if (message instanceof StartingGameMessage) {
|
||||
return Optional.of(new Game(
|
||||
self,
|
||||
session,
|
||||
secret,
|
||||
players.values().stream().collect(Collectors.toMap(PlayerData::getUuid, PlayerData::getName))
|
||||
));
|
||||
} else if (nextReady != null && message instanceof NackMessage nack) {
|
||||
client.println("Error: " + nack.getMessage());
|
||||
nextReady = null;
|
||||
client.ready();
|
||||
return Optional.empty();
|
||||
} else if (nextReady != null && message instanceof AckMessage) {
|
||||
ready = nextReady;
|
||||
nextReady = null;
|
||||
client.ready();
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return unexpectedMessage(client, message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCommand() {
|
||||
return SessionCommand.class;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package eu.jonahbauer.wizard.client.cli.util;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Delegate;
|
||||
import org.jline.reader.Completer;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class DelegateCompleter implements Completer {
|
||||
@Delegate
|
||||
private Completer delegate;
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package eu.jonahbauer.wizard.client.cli.util;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public record Pair<F,S>(F first, S second) implements Map.Entry<F, S> {
|
||||
public static <F,S> Pair<F,S> of(F first, S second) {
|
||||
return new Pair<>(first, second);
|
||||
}
|
||||
|
||||
@Override
|
||||
public F getKey() {
|
||||
return first();
|
||||
}
|
||||
|
||||
@Override
|
||||
public S getValue() {
|
||||
return second();
|
||||
}
|
||||
|
||||
@Override
|
||||
public S setValue(S value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package eu.jonahbauer.wizard.client.cli.util;
|
||||
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.client.cli.state.ClientState;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Parameter;
|
||||
|
||||
public class StateAwareFactory implements CommandLine.IFactory {
|
||||
private final CommandLine.IFactory defaultFactory = CommandLine.defaultFactory();
|
||||
private final Client client;
|
||||
private final ClientState clientState;
|
||||
|
||||
public StateAwareFactory(Client client, ClientState clientState) {
|
||||
this.client = client;
|
||||
this.clientState = clientState;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <K> K create(Class<K> cls) throws Exception {
|
||||
try {
|
||||
return defaultFactory.create(cls);
|
||||
} catch (Exception e) {
|
||||
c: for (Constructor<K> constructor : (Constructor<K>[]) cls.getDeclaredConstructors()) {
|
||||
Object[] values = new Object[constructor.getParameterCount()];
|
||||
Parameter[] parameters = constructor.getParameters();
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
Parameter parameter = parameters[i];
|
||||
if (ClientState.class.isAssignableFrom(parameter.getType())) {
|
||||
values[i] = clientState;
|
||||
} else if (Client.class.isAssignableFrom(parameter.getType())) {
|
||||
values[i] = client;
|
||||
} else {
|
||||
continue c;
|
||||
}
|
||||
}
|
||||
|
||||
constructor.setAccessible(true);
|
||||
return constructor.newInstance(values);
|
||||
}
|
||||
|
||||
var e2 = new NoSuchMethodException("Could not find a suitable constructor for class " + cls.getName());
|
||||
e2.addSuppressed(e);
|
||||
throw e2;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
subprojects {
|
||||
ext {
|
||||
gdxVersion = '1.10.0'
|
||||
roboVMVersion = '2.3.12'
|
||||
box2DLightsVersion = '1.5'
|
||||
ashleyVersion = '1.7.3'
|
||||
aiVersion = '1.8.2'
|
||||
gdxControllersVersion = '2.1.0'
|
||||
}
|
||||
}
|
||||
|
||||
project(":wizard-client:wizard-client-libgdx:desktop") {
|
||||
apply plugin: "java-library"
|
||||
|
||||
dependencies {
|
||||
implementation project(":wizard-client:wizard-client-libgdx:core")
|
||||
api "com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion"
|
||||
api "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop"
|
||||
api "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-desktop"
|
||||
}
|
||||
}
|
||||
|
||||
project(":wizard-client:wizard-client-libgdx:core") {
|
||||
apply plugin: "java-library"
|
||||
|
||||
dependencies {
|
||||
api "com.badlogicgames.gdx:gdx:$gdxVersion"
|
||||
api "com.badlogicgames.gdx:gdx-box2d:$gdxVersion"
|
||||
}
|
||||
}
|
20
wizard-client/wizard-client-libgdx/build.gradle.kts
Normal file
20
wizard-client/wizard-client-libgdx/build.gradle.kts
Normal file
@ -0,0 +1,20 @@
|
||||
|
||||
project(":wizard-client:wizard-client-libgdx:desktop") {
|
||||
apply(plugin = "java-library")
|
||||
|
||||
dependencies {
|
||||
implementation(project(":wizard-client:wizard-client-libgdx:core"))
|
||||
api(LibGDX.backend_lwjgl3)
|
||||
api(LibGDX.platform_desktop)
|
||||
}
|
||||
}
|
||||
|
||||
project(":wizard-client:wizard-client-libgdx:core") {
|
||||
apply(plugin = "java-library")
|
||||
|
||||
dependencies {
|
||||
api(LibGDX.api)
|
||||
implementation(project(":wizard-common"))
|
||||
implementation(JavaWebSocket.id)
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
|
22
wizard-client/wizard-client-libgdx/core/build.gradle.kts
Normal file
22
wizard-client/wizard-client-libgdx/core/build.gradle.kts
Normal file
@ -0,0 +1,22 @@
|
||||
val texturePackerSource = "src/main/textures"
|
||||
val texturePackerResources = "$buildDir/generated/sources/texturePacker/resources/main"
|
||||
val texturePackerGeneratedSources = "$buildDir/generated/sources/texturePacker/java/main"
|
||||
|
||||
sourceSets.main.get().java.srcDir(texturePackerGeneratedSources)
|
||||
sourceSets.main.get().resources.srcDir(texturePackerResources)
|
||||
|
||||
tasks {
|
||||
val packTextures = register<TexturePackerTask>("packTextures") {
|
||||
input.set(file(texturePackerSource))
|
||||
resourceOutput.set(file(texturePackerResources))
|
||||
generatedSourceOutput.set(file(texturePackerGeneratedSources))
|
||||
}
|
||||
|
||||
processResources {
|
||||
dependsOn(packTextures)
|
||||
}
|
||||
|
||||
compileJava {
|
||||
dependsOn(packTextures)
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx;
|
||||
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.ErrorScreen;
|
||||
import eu.jonahbauer.wizard.client.libgdx.state.ClientState;
|
||||
import eu.jonahbauer.wizard.client.libgdx.state.Menu;
|
||||
import eu.jonahbauer.wizard.common.machine.TimeoutContext;
|
||||
import eu.jonahbauer.wizard.common.messages.client.ClientMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
@Log4j2
|
||||
public class Client extends TimeoutContext<ClientState, Client> {
|
||||
|
||||
@Getter
|
||||
private final WizardGame game;
|
||||
|
||||
@Getter
|
||||
private ClientSocket socket;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean error = false;
|
||||
|
||||
public Client(WizardGame game) {
|
||||
super(new Menu());
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleError(Throwable t) {
|
||||
// TODO better error handling
|
||||
log.error("An error occurred.", t);
|
||||
|
||||
game.setScreen(new ErrorScreen(game, t.getMessage()));
|
||||
error = true;
|
||||
|
||||
var menu = new Menu();
|
||||
forceTransition(menu);
|
||||
menu.onEnter(this);
|
||||
}
|
||||
|
||||
public void setSocket(ClientSocket socket) {
|
||||
this.socket = socket;
|
||||
if (socket != null) {
|
||||
this.socket.setAttachment(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void onOpen() {
|
||||
try {
|
||||
execute(s -> s.onOpen(this));
|
||||
} catch (Throwable t) {
|
||||
handleError(t);
|
||||
}
|
||||
}
|
||||
|
||||
public void onClose(int code, String reason, boolean remote) {
|
||||
try {
|
||||
execute(s -> s.onClose(this, code, reason, remote));
|
||||
} catch (Throwable t) {
|
||||
handleError(t);
|
||||
}
|
||||
}
|
||||
|
||||
public void onMessage(ServerMessage message) {
|
||||
try {
|
||||
execute(s -> s.onMessage(this, message));
|
||||
} catch (Throwable t) {
|
||||
handleError(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTransition(ClientState from, ClientState to) {
|
||||
log.debug("Transition from {} to {}.", from.getClass().getSimpleName(), to.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
public void send(ClientMessage message) {
|
||||
getSocket().send(message.toString());
|
||||
}
|
||||
|
||||
public <T extends ClientState> void execute(Class<T> stateClass, BiFunction<T, Client, Optional<ClientState>> transition) {
|
||||
execute(s -> {
|
||||
if (stateClass.isInstance(s)) {
|
||||
return transition.apply(stateClass.cast(s), this);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.java_websocket.client.WebSocketClient;
|
||||
import org.java_websocket.framing.CloseFrame;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.net.URI;
|
||||
|
||||
@Log4j2
|
||||
public class ClientSocket extends WebSocketClient {
|
||||
public ClientSocket(URI serverUri) {
|
||||
super(serverUri);
|
||||
|
||||
if ("wss".equals(getURI().getScheme())) {
|
||||
setSocketFactory(SSLSocketFactory.getDefault());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(ServerHandshake serverHandshake) {
|
||||
Gdx.app.postRunnable(() -> getClient().onOpen());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String s) {
|
||||
log.debug("Received: {}", s);
|
||||
ServerMessage message = ServerMessage.parse(s);
|
||||
Gdx.app.postRunnable(() -> getClient().onMessage(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(int i, String s, boolean b) {
|
||||
Gdx.app.postRunnable(() -> getClient().onClose(i, s, b));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception e) {
|
||||
log.error("", e);
|
||||
close(CloseFrame.ABNORMAL_CLOSE, e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(String text) {
|
||||
log.debug("Sending: {}", text);
|
||||
super.send(text);
|
||||
}
|
||||
|
||||
private Client getClient() {
|
||||
return getAttachment();
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +0,0 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx;
|
||||
|
||||
import com.badlogic.gdx.ApplicationAdapter;
|
||||
import com.badlogic.gdx.graphics.Texture;
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
|
||||
import com.badlogic.gdx.utils.ScreenUtils;
|
||||
|
||||
public class MyGdxGame extends ApplicationAdapter {
|
||||
SpriteBatch batch;
|
||||
Texture img;
|
||||
|
||||
@Override
|
||||
public void create () {
|
||||
batch = new SpriteBatch();
|
||||
img = new Texture("badlogic.jpg");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render () {
|
||||
ScreenUtils.clear(1, 0, 0, 1);
|
||||
batch.begin();
|
||||
batch.draw(img, 0, 0);
|
||||
batch.end();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose () {
|
||||
batch.dispose();
|
||||
img.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx;
|
||||
|
||||
import com.badlogic.gdx.Game;
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.Graphics;
|
||||
import com.badlogic.gdx.Input;
|
||||
import com.badlogic.gdx.audio.Music;
|
||||
import com.badlogic.gdx.graphics.Pixmap;
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.MainMenuScreen;
|
||||
import eu.jonahbauer.wizard.client.libgdx.util.SavedData;
|
||||
import eu.jonahbauer.wizard.client.libgdx.util.WizardAssetManager;
|
||||
import lombok.Getter;
|
||||
|
||||
public class WizardGame extends Game {
|
||||
public static final boolean DEBUG = false;
|
||||
public static final int WIDTH = 1920;
|
||||
public static final int HEIGHT = 1080;
|
||||
|
||||
public SpriteBatch batch;
|
||||
public WizardAssetManager assets;
|
||||
public final SavedData storage = new SavedData();
|
||||
|
||||
private boolean fullscreenToggle;
|
||||
private int oldHeight, oldWidth;
|
||||
|
||||
@Getter
|
||||
private final Client client = new Client(this);
|
||||
|
||||
@Override
|
||||
public void create() {
|
||||
batch = new SpriteBatch();
|
||||
|
||||
assets = new WizardAssetManager();
|
||||
assets.loadShared();
|
||||
assets.finishLoading();
|
||||
|
||||
// background music
|
||||
Music backgroundMusic = assets.get(WizardAssetManager.MUSIC_BACKGROUND, Music.class);
|
||||
backgroundMusic.setLooping(true);
|
||||
backgroundMusic.setVolume(0.07f);
|
||||
backgroundMusic.play();
|
||||
|
||||
// set cursor
|
||||
Pixmap cursor = assets.get(WizardAssetManager.CURSOR, Pixmap.class);
|
||||
Gdx.graphics.setCursor(Gdx.graphics.newCursor(cursor, 0, 0));
|
||||
assets.unload(WizardAssetManager.CURSOR);
|
||||
|
||||
this.setScreen(new MainMenuScreen(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render() {
|
||||
super.render();
|
||||
|
||||
// alt + enter shortcut for fullscreen
|
||||
var enter = Gdx.input.isKeyPressed(Input.Keys.ENTER);
|
||||
var alt = (Gdx.input.isKeyPressed(Input.Keys.ALT_LEFT) || Gdx.input.isKeyPressed(Input.Keys.ALT_RIGHT));
|
||||
var toggle = enter && alt;
|
||||
|
||||
if (toggle && !this.fullscreenToggle) {
|
||||
this.fullscreenToggle = true;
|
||||
var fullscreen = Gdx.graphics.isFullscreen();
|
||||
Graphics.DisplayMode displayMode = Gdx.graphics.getDisplayMode();
|
||||
if (fullscreen) {
|
||||
Gdx.graphics.setWindowedMode(oldWidth, oldHeight);
|
||||
} else {
|
||||
oldWidth = Gdx.graphics.getWidth();
|
||||
oldHeight = Gdx.graphics.getHeight();
|
||||
Gdx.graphics.setFullscreenMode(displayMode);
|
||||
}
|
||||
} else if (!toggle) {
|
||||
this.fullscreenToggle = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose () {
|
||||
batch.dispose();
|
||||
assets.dispose();
|
||||
client.shutdownNow();
|
||||
var socket = client.getSocket();
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actions;
|
||||
|
||||
import com.badlogic.gdx.math.Vector2;
|
||||
import com.badlogic.gdx.scenes.scene2d.Action;
|
||||
import com.badlogic.gdx.scenes.scene2d.Group;
|
||||
import com.badlogic.gdx.utils.Pools;
|
||||
import lombok.Setter;
|
||||
|
||||
public class ChangeParentAction extends Action {
|
||||
private final Vector2 pos = Pools.obtain(Vector2.class);
|
||||
|
||||
@Setter
|
||||
private Group parent;
|
||||
private boolean finished;
|
||||
|
||||
public boolean act(float delta) {
|
||||
if (!finished) {
|
||||
finished = true;
|
||||
pos.set(target.getX(), target.getY());
|
||||
if (target.hasParent()) {
|
||||
target.getParent().localToStageCoordinates(pos);
|
||||
}
|
||||
parent.stageToLocalCoordinates(pos);
|
||||
parent.addActor(target);
|
||||
target.setPosition(pos.x, pos.y);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void restart () {
|
||||
finished = false;
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actions;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Action;
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.Group;
|
||||
import com.badlogic.gdx.scenes.scene2d.actions.*;
|
||||
|
||||
public class MyActions extends Actions {
|
||||
public static ChangeParentAction changeParent(Group parent) {
|
||||
var action = action(ChangeParentAction.class);
|
||||
action.setParent(parent);
|
||||
return action;
|
||||
}
|
||||
|
||||
public static SilentlyRemoveActorAction removeActorSilently () {
|
||||
return action(SilentlyRemoveActorAction.class);
|
||||
}
|
||||
|
||||
public static WaitAction delay(Actor actor) {
|
||||
var action = action(WaitAction.class);
|
||||
action.setTarget(actor);
|
||||
return action;
|
||||
}
|
||||
|
||||
public static void finish(Action action) {
|
||||
if (action instanceof ParallelAction parallel) {
|
||||
var subactions = parallel.getActions();
|
||||
for (int i = 0; i < subactions.size; i++) {
|
||||
finish(subactions.get(i));
|
||||
}
|
||||
} else if (action instanceof DelayAction delay) {
|
||||
delay.finish();
|
||||
finish(delay.getAction());
|
||||
} else if (action instanceof DelegateAction delegate) {
|
||||
var subaction = delegate.getAction();
|
||||
finish(subaction);
|
||||
} else if (action instanceof TemporalAction temporal) {
|
||||
temporal.finish();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actions;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Action;
|
||||
|
||||
/** Removes an actor from the stage without immediately invalidating its parents. */
|
||||
public class SilentlyRemoveActorAction extends Action {
|
||||
private boolean removed;
|
||||
|
||||
public boolean act (float delta) {
|
||||
if (!removed) {
|
||||
removed = true;
|
||||
if (target.getParent() != null) {
|
||||
target.getParent().getChildren().removeValue(target, true);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void restart () {
|
||||
removed = false;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actions;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Action;
|
||||
|
||||
public class WaitAction extends Action {
|
||||
@Override
|
||||
public boolean act(float delta) {
|
||||
return getTarget().getActions().size == 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actions.overlay;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
|
||||
import eu.jonahbauer.wizard.client.libgdx.actors.CardActor;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ChangePredictionOverlay extends MakePredictionOverlay {
|
||||
|
||||
public ChangePredictionOverlay(@NotNull GameScreen gameScreen, long timeout, int round, int oldPrediction) {
|
||||
super(
|
||||
gameScreen,
|
||||
timeout,
|
||||
oldPrediction == 0 ? new int[] {oldPrediction + 1}
|
||||
: oldPrediction == round + 1 ? new int[] {oldPrediction - 1}
|
||||
: new int[] {oldPrediction - 1, oldPrediction + 1}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VerticalGroup createContent() {
|
||||
var root = super.createContent();
|
||||
|
||||
var card = new CardActor(Card.CLOUD, atlas);
|
||||
root.addActorAt(0, card);
|
||||
root.padTop(- CardActor.PREF_HEIGHT);
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actions.overlay;
|
||||
|
||||
public interface InteractionOverlay {
|
||||
void close();
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actions.overlay;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.Group;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.HorizontalGroup;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
|
||||
import eu.jonahbauer.wizard.client.libgdx.actors.PadOfTruth;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.run;
|
||||
|
||||
public class MakePredictionOverlay extends Overlay implements InteractionOverlay {
|
||||
|
||||
private final int[] values;
|
||||
private final TextButton[] buttons;
|
||||
|
||||
private final @NotNull PadOfTruth padOfTruth;
|
||||
|
||||
public MakePredictionOverlay(@NotNull GameScreen gameScreen, long timeout, int round) {
|
||||
this(gameScreen, timeout, IntStream.range(0, round + 2).toArray());
|
||||
}
|
||||
|
||||
protected MakePredictionOverlay(@NotNull GameScreen gameScreen, long timeout, int[] values) {
|
||||
super(gameScreen, timeout);
|
||||
this.values = values;
|
||||
this.buttons = new TextButton[values.length];
|
||||
this.padOfTruth = Objects.requireNonNull(gameScreen.getPadOfTruth());
|
||||
}
|
||||
|
||||
@Override
|
||||
public VerticalGroup createContent() {
|
||||
var root = new VerticalGroup().columnCenter().space(10);
|
||||
|
||||
var prompt = new Label(messages.get("game.overlay.make_prediction.prompt"), skin);
|
||||
var buttonGroup = new HorizontalGroup().space(20);
|
||||
|
||||
var listener = new ChangeListener() {
|
||||
@Override
|
||||
public void changed(ChangeEvent event, Actor actor) {
|
||||
if (isClosing()) return;
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
if (actor == buttons[i]) {
|
||||
screen.onPredictionMade(values[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
buttons[i] = new TextButton(String.valueOf(values[i]), skin);
|
||||
buttons[i].addListener(listener);
|
||||
buttonGroup.addActor(buttons[i]);
|
||||
}
|
||||
|
||||
root.addActor(prompt);
|
||||
root.addActor(buttonGroup);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void show(Group parent) {
|
||||
super.show(parent);
|
||||
|
||||
padOfTruth.addAction(run(() -> screen.getOverlayRoot().addActor(padOfTruth)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClosing() {
|
||||
super.onClosing();
|
||||
|
||||
padOfTruth.addAction(run(() -> screen.getContentRoot().addActor(padOfTruth)));
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actions.overlay;
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
|
||||
import com.badlogic.gdx.scenes.scene2d.Action;
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.Group;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Container;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
|
||||
import com.badlogic.gdx.utils.I18NBundle;
|
||||
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.run;
|
||||
|
||||
public abstract class Overlay extends Action {
|
||||
protected final GameScreen screen;
|
||||
protected final Skin skin;
|
||||
protected final I18NBundle messages;
|
||||
protected final TextureAtlas atlas;
|
||||
private long timeout;
|
||||
|
||||
private Container<?> root;
|
||||
private boolean started;
|
||||
@Getter(AccessLevel.PROTECTED)
|
||||
private boolean closing;
|
||||
private boolean closed;
|
||||
|
||||
public Overlay(@NotNull GameScreen gameScreen, long timeout) {
|
||||
this.screen = gameScreen;
|
||||
this.skin = gameScreen.getSkin();
|
||||
this.messages = gameScreen.getMessages();
|
||||
this.atlas = gameScreen.getAtlas();
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean act(float delta) {
|
||||
if (!started) {
|
||||
if (System.currentTimeMillis() > timeout) {
|
||||
return true;
|
||||
}
|
||||
|
||||
started = true;
|
||||
show(screen.getOverlayRoot());
|
||||
}
|
||||
|
||||
if (System.currentTimeMillis() > timeout && !closing) {
|
||||
closing = true;
|
||||
onClosing();
|
||||
}
|
||||
|
||||
return closed;
|
||||
}
|
||||
|
||||
protected abstract Actor createContent();
|
||||
|
||||
protected Container<?> getRoot() {
|
||||
if (root == null) {
|
||||
root = new Container<>(createContent());
|
||||
root.setSize(WizardGame.WIDTH, WizardGame.HEIGHT);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
protected void show(Group parent) {
|
||||
parent.addActor(getRoot());
|
||||
}
|
||||
|
||||
protected final void onClosed() {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
getRoot().remove();
|
||||
}
|
||||
|
||||
protected void onClosing() {
|
||||
getRoot().addAction(run(this::onClosed));
|
||||
}
|
||||
|
||||
public void close() {
|
||||
timeout = 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actions.overlay;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.InputEvent;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.HorizontalGroup;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
|
||||
import eu.jonahbauer.wizard.client.libgdx.actors.CardActor;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.EnumMap;
|
||||
|
||||
public class PickTrumpOverlay extends Overlay implements InteractionOverlay {
|
||||
|
||||
private final boolean allowNone;
|
||||
|
||||
private final EnumMap<Card.Suit, CardActor> cards = new EnumMap<>(Card.Suit.class);
|
||||
|
||||
public PickTrumpOverlay(@NotNull GameScreen gameScreen, long timeout, boolean allowNone) {
|
||||
super(gameScreen, timeout);
|
||||
this.allowNone = allowNone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Actor createContent() {
|
||||
var root = new VerticalGroup().columnCenter().space(10);
|
||||
|
||||
var prompt = new Label(messages.get("game.overlay.pick_trump.prompt"), skin);
|
||||
var cardGroup = new HorizontalGroup().space(20);
|
||||
|
||||
cards.put(Card.Suit.RED, new CardActor(Card.Suit.RED, atlas));
|
||||
cards.put(Card.Suit.GREEN, new CardActor(Card.Suit.GREEN, atlas));
|
||||
cards.put(Card.Suit.BLUE, new CardActor(Card.Suit.BLUE, atlas));
|
||||
cards.put(Card.Suit.YELLOW, new CardActor(Card.Suit.YELLOW, atlas));
|
||||
if (allowNone) {
|
||||
cards.put(Card.Suit.NONE, new CardActor(Card.Suit.NONE, atlas));
|
||||
}
|
||||
|
||||
cards.values().forEach(cardGroup::addActor);
|
||||
|
||||
cardGroup.addListener(new ClickListener() {
|
||||
@Override
|
||||
public void clicked(InputEvent event, float x, float y) {
|
||||
if (isClosing()) return;
|
||||
var target = event.getTarget();
|
||||
for (Card.Suit suit : Card.Suit.values()) {
|
||||
if (cards.get(suit) == target) {
|
||||
screen.onSuitClicked(suit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
root.addActor(prompt);
|
||||
root.addActor(cardGroup);
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actions.overlay;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.InputEvent;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.HorizontalGroup;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
|
||||
import eu.jonahbauer.wizard.client.libgdx.actors.CardActor;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.EnumMap;
|
||||
|
||||
public class PlayChangelingOverlay extends Overlay implements InteractionOverlay {
|
||||
public PlayChangelingOverlay(@NotNull GameScreen gameScreen, long timeout) {
|
||||
super(gameScreen, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Actor createContent() {
|
||||
var root = new VerticalGroup().columnCenter().space(10);
|
||||
|
||||
var prompt = new Label(messages.get("game.overlay.play_changeling.prompt"), skin);
|
||||
var cardGroup = new HorizontalGroup().space(20);
|
||||
|
||||
var wizard = new CardActor(Card.CHANGELING_WIZARD, atlas);
|
||||
var jester = new CardActor(Card.CHANGELING_JESTER, atlas);
|
||||
cardGroup.addActor(wizard);
|
||||
cardGroup.addActor(jester);
|
||||
|
||||
cardGroup.addListener(new ClickListener() {
|
||||
@Override
|
||||
public void clicked(InputEvent event, float x, float y) {
|
||||
if (isClosing()) return;
|
||||
var target = event.getTarget();
|
||||
if (target == wizard) {
|
||||
screen.onCardClicked(Card.CHANGELING_WIZARD);
|
||||
} else if (target == jester) {
|
||||
screen.onCardClicked(Card.CHANGELING_JESTER);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
root.addActor(prompt);
|
||||
root.addActor(cardGroup);
|
||||
|
||||
var cancel = new TextButton(messages.get("game.overlay.play_changeling.cancel"), skin, "simple");
|
||||
cancel.addListener(new ChangeListener() {
|
||||
@Override
|
||||
public void changed(ChangeEvent event, Actor actor) {
|
||||
close();
|
||||
}
|
||||
});
|
||||
root.addActor(cancel);
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actions.overlay;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.InputEvent;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.HorizontalGroup;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
|
||||
import eu.jonahbauer.wizard.client.libgdx.actors.CardActor;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.EnumMap;
|
||||
|
||||
public class PlayColoredCardOverlay extends Overlay implements InteractionOverlay {
|
||||
|
||||
private final EnumMap<Card.Suit, CardActor> actors = new EnumMap<>(Card.Suit.class);
|
||||
private final EnumMap<Card.Suit, Card> cards = new EnumMap<>(Card.Suit.class);
|
||||
|
||||
private final Card card;
|
||||
|
||||
public PlayColoredCardOverlay(@NotNull GameScreen gameScreen, long timeout, @NotNull Card card,
|
||||
@NotNull Card red, @NotNull Card green, @NotNull Card blue, @NotNull Card yellow)
|
||||
{
|
||||
super(gameScreen, timeout);
|
||||
this.card = card;
|
||||
this.cards.put(Card.Suit.RED, red);
|
||||
this.cards.put(Card.Suit.GREEN, green);
|
||||
this.cards.put(Card.Suit.BLUE, blue);
|
||||
this.cards.put(Card.Suit.YELLOW, yellow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Actor createContent() {
|
||||
var root = new VerticalGroup().columnCenter().space(10);
|
||||
|
||||
var prompt = new Label(messages.get("game.overlay.play_colored_card.prompt"), skin);
|
||||
var cardGroup = new HorizontalGroup().space(20);
|
||||
|
||||
var card = new CardActor(this.card, atlas);
|
||||
root.addActorAt(0, card);
|
||||
root.padTop(- CardActor.PREF_HEIGHT);
|
||||
|
||||
actors.put(Card.Suit.RED, new CardActor(Card.Suit.RED, atlas));
|
||||
actors.put(Card.Suit.GREEN, new CardActor(Card.Suit.GREEN, atlas));
|
||||
actors.put(Card.Suit.BLUE, new CardActor(Card.Suit.BLUE, atlas));
|
||||
actors.put(Card.Suit.YELLOW, new CardActor(Card.Suit.YELLOW, atlas));
|
||||
actors.values().forEach(cardGroup::addActor);
|
||||
|
||||
cardGroup.addListener(new ClickListener() {
|
||||
@Override
|
||||
public void clicked(InputEvent event, float x, float y) {
|
||||
if (isClosing()) return;
|
||||
var target = event.getTarget();
|
||||
for (Card.Suit suit : Card.Suit.values()) {
|
||||
if (actors.get(suit) == target) {
|
||||
screen.onCardClicked(cards.get(suit));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
root.addActor(prompt);
|
||||
root.addActor(cardGroup);
|
||||
|
||||
var cancel = new TextButton(messages.get("game.overlay.play_colored_card.cancel"), skin, "simple");
|
||||
cancel.addListener(new ChangeListener() {
|
||||
@Override
|
||||
public void changed(ChangeEvent event, Actor actor) {
|
||||
close();
|
||||
}
|
||||
});
|
||||
root.addActor(cancel);
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actions.overlay;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.Group;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
|
||||
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
|
||||
import eu.jonahbauer.wizard.client.libgdx.actors.PadOfTruth;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
||||
import eu.jonahbauer.wizard.client.libgdx.util.AnimationTimings;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
|
||||
import static eu.jonahbauer.wizard.client.libgdx.actions.MyActions.changeParent;
|
||||
import static eu.jonahbauer.wizard.client.libgdx.screens.GameScreen.PAD_OF_TRUTH_POSITION;
|
||||
import static eu.jonahbauer.wizard.client.libgdx.util.AnimationTimings.OVERLAY_SHARED_ELEMENT;
|
||||
|
||||
public class ScoreOverlay extends Overlay {
|
||||
private final boolean finalScores;
|
||||
|
||||
private final PadOfTruth padOfTruth;
|
||||
private TextButton back;
|
||||
|
||||
public ScoreOverlay(@NotNull GameScreen gameScreen, boolean finalScores) {
|
||||
super(gameScreen, Long.MAX_VALUE);
|
||||
this.finalScores = finalScores;
|
||||
this.padOfTruth = Objects.requireNonNull(gameScreen.getPadOfTruth());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Actor createContent() {
|
||||
var group = new Group();
|
||||
group.addActor(padOfTruth);
|
||||
|
||||
if (finalScores) {
|
||||
back = new TextButton(messages.get("game.overlay.scores.return_to_session"), skin);
|
||||
back.setPosition(
|
||||
(WizardGame.WIDTH - back.getWidth()) / 2,
|
||||
(WizardGame.HEIGHT - padOfTruth.getHeight()) / 2 + 30 - back.getHeight()
|
||||
);
|
||||
back.setVisible(false);
|
||||
back.addListener(new ChangeListener() {
|
||||
@Override
|
||||
public void changed(ChangeEvent event, Actor actor) {
|
||||
screen.showSessionScreen();
|
||||
}
|
||||
});
|
||||
group.addActor(back);
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void show(Group parent) {
|
||||
var root = getRoot().fill();
|
||||
super.show(parent);
|
||||
|
||||
if (finalScores) {
|
||||
root.addAction(sequence(
|
||||
run(() -> padOfTruth.setEnabled(false)),
|
||||
parallel(
|
||||
targeting(padOfTruth, scaleTo(1, 1, OVERLAY_SHARED_ELEMENT)),
|
||||
targeting(padOfTruth, moveTo((WizardGame.WIDTH - padOfTruth.getWidth()) / 2,(WizardGame.HEIGHT - padOfTruth.getHeight()) / 2 + 50, OVERLAY_SHARED_ELEMENT))
|
||||
),
|
||||
delay(AnimationTimings.OVERLAY_HOLD),
|
||||
targeting(back, visible(true))
|
||||
));
|
||||
} else {
|
||||
root.addAction(sequence(
|
||||
run(() -> padOfTruth.setEnabled(false)),
|
||||
parallel(
|
||||
targeting(padOfTruth, scaleTo(1, 1, OVERLAY_SHARED_ELEMENT)),
|
||||
targeting(padOfTruth, moveTo((WizardGame.WIDTH - padOfTruth.getWidth()) / 2,(WizardGame.HEIGHT - padOfTruth.getHeight()) / 2, OVERLAY_SHARED_ELEMENT))
|
||||
),
|
||||
delay(AnimationTimings.OVERLAY_HOLD),
|
||||
parallel(
|
||||
targeting(padOfTruth, scaleTo(PadOfTruth.COLLAPSED_SCALE, PadOfTruth.COLLAPSED_SCALE, OVERLAY_SHARED_ELEMENT)),
|
||||
targeting(padOfTruth, moveTo(PAD_OF_TRUTH_POSITION.x, PAD_OF_TRUTH_POSITION.y, OVERLAY_SHARED_ELEMENT) )
|
||||
),
|
||||
targeting(padOfTruth, changeParent(screen.getContentRoot())),
|
||||
run(() -> padOfTruth.setEnabled(true)),
|
||||
run(this::close)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actions.overlay;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.Group;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
|
||||
import eu.jonahbauer.wizard.client.libgdx.util.AnimationTimings;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
|
||||
|
||||
public class StartRoundOverlay extends Overlay {
|
||||
|
||||
private final int round;
|
||||
|
||||
public StartRoundOverlay(@NotNull GameScreen gameScreen, int round) {
|
||||
super(gameScreen, Long.MAX_VALUE);
|
||||
this.round = round;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Actor createContent() {
|
||||
var root = new VerticalGroup().columnCenter().space(10f);
|
||||
|
||||
var label = new Label(messages.format("game.overlay.round.title", round + 1), skin, "enchanted");
|
||||
label.setFontScale(1.5f);
|
||||
root.addActor(label);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(Group parent) {
|
||||
super.show(parent);
|
||||
|
||||
var root = getRoot();
|
||||
root.addAction(sequence(
|
||||
delay(AnimationTimings.OVERLAY_HOLD),
|
||||
run(this::close)
|
||||
));
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actions.overlay;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.Group;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.HorizontalGroup;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
|
||||
import eu.jonahbauer.wizard.client.libgdx.util.AnimationTimings;
|
||||
import eu.jonahbauer.wizard.client.libgdx.actions.MyActions;
|
||||
import eu.jonahbauer.wizard.client.libgdx.actors.CardActor;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
||||
import eu.jonahbauer.wizard.client.libgdx.util.CardUtil;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
|
||||
import static eu.jonahbauer.wizard.client.libgdx.actions.MyActions.*;
|
||||
import static eu.jonahbauer.wizard.client.libgdx.screens.GameScreen.*;
|
||||
import static eu.jonahbauer.wizard.client.libgdx.util.AnimationTimings.OVERLAY_SHARED_ELEMENT;
|
||||
|
||||
public class TrumpOverlay extends Overlay {
|
||||
|
||||
private final @Nullable String player;
|
||||
private final @Nullable Card card;
|
||||
private final @Nullable Card.Suit suit;
|
||||
|
||||
private final @NotNull CardActor trumpCardActor;
|
||||
private final @NotNull CardActor trumpSuitActor;
|
||||
|
||||
private boolean animateCard = true;
|
||||
private boolean animateSuit = true;
|
||||
|
||||
public TrumpOverlay(@NotNull GameScreen gameScreen, @Nullable String player, @Nullable Card card, @Nullable Card.Suit suit) {
|
||||
super(gameScreen, Long.MAX_VALUE);
|
||||
this.player = player;
|
||||
this.card = card;
|
||||
this.suit = suit;
|
||||
this.trumpCardActor = Objects.requireNonNull(gameScreen.getTrumpCardActor());
|
||||
this.trumpSuitActor = Objects.requireNonNull(gameScreen.getTrumpSuitActor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Actor createContent() {
|
||||
var root = new VerticalGroup().columnCenter().space(10f);
|
||||
|
||||
String text;
|
||||
|
||||
if (player == null) {
|
||||
text = messages.get(suit != null ? switch (suit) {
|
||||
case YELLOW -> "game.overlay.trump.yellow";
|
||||
case GREEN -> "game.overlay.trump.green";
|
||||
case BLUE -> "game.overlay.trump.blue";
|
||||
case RED -> "game.overlay.trump.red";
|
||||
default -> "game.overlay.trump.none";
|
||||
} : "game.overlay.trump.unknown");
|
||||
} else {
|
||||
text = messages.format(suit != null ? switch (suit) {
|
||||
case YELLOW -> "game.overlay.trump.yellow.player";
|
||||
case GREEN -> "game.overlay.trump.green.player";
|
||||
case BLUE -> "game.overlay.trump.blue.player";
|
||||
case RED -> "game.overlay.trump.red.player";
|
||||
default -> "game.overlay.trump.none.player";
|
||||
} : "game.overlay.trump.unknown.player", player);
|
||||
}
|
||||
|
||||
var label = new Label(text, skin);
|
||||
label.getStyle().font.getData().markupEnabled = true;
|
||||
label.setFontScale(1.5f);
|
||||
root.addActor(label);
|
||||
|
||||
var cardGroup = new HorizontalGroup().space(20);
|
||||
root.addActor(cardGroup);
|
||||
|
||||
if (trumpCardActor.hasParent() && trumpCardActor.getCard() == card && suit != null) {
|
||||
// if card actor is already correct then dont change it
|
||||
animateCard = false;
|
||||
} else {
|
||||
cardGroup.addActor(trumpCardActor);
|
||||
trumpCardActor.setCard(card != null ? card : Card.HIDDEN);
|
||||
}
|
||||
|
||||
animateSuit = suit != null && suit != CardUtil.getDefaultTrumpSuit(card);
|
||||
if (animateSuit) {
|
||||
trumpSuitActor.setRotation(0);
|
||||
cardGroup.addActor(trumpSuitActor);
|
||||
trumpSuitActor.setCard(suit);
|
||||
} else {
|
||||
trumpSuitActor.remove();
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(Group parent) {
|
||||
super.show(parent);
|
||||
|
||||
boolean cardVisible = trumpCardActor.hasParent();
|
||||
|
||||
var cardAnimation = sequence();
|
||||
|
||||
// remove from parent
|
||||
var parallel = parallel();
|
||||
if (animateSuit) {
|
||||
parallel.addAction(targeting(trumpSuitActor, removeActorSilently()));
|
||||
}
|
||||
if (animateCard) {
|
||||
parallel.addAction(targeting(trumpCardActor, removeActorSilently()));
|
||||
}
|
||||
cardAnimation.addAction(parallel);
|
||||
|
||||
// change parent in correct order
|
||||
parallel = parallel();
|
||||
if (animateSuit) {
|
||||
parallel.addAction(targeting(trumpSuitActor, changeParent(screen.getOverlayRoot())));
|
||||
}
|
||||
if (cardVisible) {
|
||||
parallel.addAction(targeting(trumpCardActor, changeParent(screen.getOverlayRoot())));
|
||||
}
|
||||
cardAnimation.addAction(parallel);
|
||||
|
||||
// animate
|
||||
parallel = parallel();
|
||||
if (animateSuit) {
|
||||
parallel.addAction(targeting(trumpSuitActor, rotateTo(TRUMP_SUIT_ROTATION, OVERLAY_SHARED_ELEMENT)));
|
||||
parallel.addAction(
|
||||
targeting(trumpSuitActor, moveTo(TRUMP_SUIT_POSITION.x, TRUMP_SUIT_POSITION.y, OVERLAY_SHARED_ELEMENT))
|
||||
);
|
||||
}
|
||||
if (animateCard) {
|
||||
parallel.addAction(targeting(trumpCardActor, moveTo(TRUMP_CARD_POSITION.x, TRUMP_CARD_POSITION.y, OVERLAY_SHARED_ELEMENT)));
|
||||
}
|
||||
cardAnimation.addAction(parallel);
|
||||
|
||||
// change parent in correct order
|
||||
parallel = parallel();
|
||||
if (animateSuit) {
|
||||
parallel.addAction(targeting(trumpSuitActor, changeParent(screen.getContentRoot())));
|
||||
}
|
||||
if (cardVisible) {
|
||||
parallel.addAction(targeting(trumpCardActor, changeParent(screen.getContentRoot())));
|
||||
}
|
||||
cardAnimation.addAction(parallel);
|
||||
|
||||
var root = getRoot();
|
||||
root.addAction(sequence(
|
||||
delay(AnimationTimings.OVERLAY_HOLD),
|
||||
cardAnimation,
|
||||
run(this::close)
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClosing() {
|
||||
var actions = getRoot().getActions();
|
||||
if (actions.size > 0) MyActions.finish(actions.get(0));
|
||||
onClosed();
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actors;
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.Batch;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import eu.jonahbauer.wizard.client.libgdx.GameAtlas;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CardActor extends Actor {
|
||||
public static final float ASPECT_RATIO = 400f / 260f;
|
||||
public static final float PREF_WIDTH = 135;
|
||||
public static final float PREF_HEIGHT = 208;
|
||||
|
||||
private static final Map<Card, String> ATLAS_PATHS;
|
||||
|
||||
static {
|
||||
var paths = new EnumMap<Card, String>(Card.class);
|
||||
for (Card card : Card.values()) {
|
||||
paths.put(card, switch (card) {
|
||||
case BLUE_1 -> GameAtlas.CARDS_BLUE_1;
|
||||
case BLUE_2 -> GameAtlas.CARDS_BLUE_2;
|
||||
case BLUE_3 -> GameAtlas.CARDS_BLUE_3;
|
||||
case BLUE_4 -> GameAtlas.CARDS_BLUE_4;
|
||||
case BLUE_5 -> GameAtlas.CARDS_BLUE_5;
|
||||
case BLUE_6 -> GameAtlas.CARDS_BLUE_6;
|
||||
case BLUE_7 -> GameAtlas.CARDS_BLUE_7;
|
||||
case BLUE_8 -> GameAtlas.CARDS_BLUE_8;
|
||||
case BLUE_9 -> GameAtlas.CARDS_BLUE_9;
|
||||
case BLUE_10 -> GameAtlas.CARDS_BLUE_10;
|
||||
case BLUE_11 -> GameAtlas.CARDS_BLUE_11;
|
||||
case BLUE_12 -> GameAtlas.CARDS_BLUE_12;
|
||||
case BLUE_13 -> GameAtlas.CARDS_BLUE_13;
|
||||
case RED_1 -> GameAtlas.CARDS_RED_1;
|
||||
case RED_2 -> GameAtlas.CARDS_RED_2;
|
||||
case RED_3 -> GameAtlas.CARDS_RED_3;
|
||||
case RED_4 -> GameAtlas.CARDS_RED_4;
|
||||
case RED_5 -> GameAtlas.CARDS_RED_5;
|
||||
case RED_6 -> GameAtlas.CARDS_RED_6;
|
||||
case RED_7 -> GameAtlas.CARDS_RED_7;
|
||||
case RED_8 -> GameAtlas.CARDS_RED_8;
|
||||
case RED_9 -> GameAtlas.CARDS_RED_9;
|
||||
case RED_10 -> GameAtlas.CARDS_RED_10;
|
||||
case RED_11 -> GameAtlas.CARDS_RED_11;
|
||||
case RED_12 -> GameAtlas.CARDS_RED_12;
|
||||
case RED_13 -> GameAtlas.CARDS_RED_13;
|
||||
case YELLOW_1 -> GameAtlas.CARDS_YELLOW_1;
|
||||
case YELLOW_2 -> GameAtlas.CARDS_YELLOW_2;
|
||||
case YELLOW_3 -> GameAtlas.CARDS_YELLOW_3;
|
||||
case YELLOW_4 -> GameAtlas.CARDS_YELLOW_4;
|
||||
case YELLOW_5 -> GameAtlas.CARDS_YELLOW_5;
|
||||
case YELLOW_6 -> GameAtlas.CARDS_YELLOW_6;
|
||||
case YELLOW_7 -> GameAtlas.CARDS_YELLOW_7;
|
||||
case YELLOW_8 -> GameAtlas.CARDS_YELLOW_8;
|
||||
case YELLOW_9 -> GameAtlas.CARDS_YELLOW_9;
|
||||
case YELLOW_10 -> GameAtlas.CARDS_YELLOW_10;
|
||||
case YELLOW_11 -> GameAtlas.CARDS_YELLOW_11;
|
||||
case YELLOW_12 -> GameAtlas.CARDS_YELLOW_12;
|
||||
case YELLOW_13 -> GameAtlas.CARDS_YELLOW_13;
|
||||
case GREEN_1 -> GameAtlas.CARDS_GREEN_1;
|
||||
case GREEN_2 -> GameAtlas.CARDS_GREEN_2;
|
||||
case GREEN_3 -> GameAtlas.CARDS_GREEN_3;
|
||||
case GREEN_4 -> GameAtlas.CARDS_GREEN_4;
|
||||
case GREEN_5 -> GameAtlas.CARDS_GREEN_5;
|
||||
case GREEN_6 -> GameAtlas.CARDS_GREEN_6;
|
||||
case GREEN_7 -> GameAtlas.CARDS_GREEN_7;
|
||||
case GREEN_8 -> GameAtlas.CARDS_GREEN_8;
|
||||
case GREEN_9 -> GameAtlas.CARDS_GREEN_9;
|
||||
case GREEN_10 -> GameAtlas.CARDS_GREEN_10;
|
||||
case GREEN_11 -> GameAtlas.CARDS_GREEN_11;
|
||||
case GREEN_12 -> GameAtlas.CARDS_GREEN_12;
|
||||
case GREEN_13 -> GameAtlas.CARDS_GREEN_13;
|
||||
case JUGGLER -> GameAtlas.CARDS_JUGGLER;
|
||||
case JUGGLER_BLUE -> GameAtlas.CARDS_JUGGLER_BLUE;
|
||||
case JUGGLER_RED -> GameAtlas.CARDS_JUGGLER_RED;
|
||||
case JUGGLER_GREEN -> GameAtlas.CARDS_JUGGLER_GREEN;
|
||||
case JUGGLER_YELLOW -> GameAtlas.CARDS_JUGGLER_YELLOW;
|
||||
case CLOUD -> GameAtlas.CARDS_CLOUD;
|
||||
case CLOUD_GREEN -> GameAtlas.CARDS_CLOUD_GREEN;
|
||||
case CLOUD_RED -> GameAtlas.CARDS_CLOUD_RED;
|
||||
case CLOUD_YELLOW -> GameAtlas.CARDS_CLOUD_YELLOW;
|
||||
case CLOUD_BLUE -> GameAtlas.CARDS_CLOUD_BLUE;
|
||||
case BOMB -> GameAtlas.CARDS_BOMB;
|
||||
case FAIRY -> GameAtlas.CARDS_FAIRY;
|
||||
case DRAGON -> GameAtlas.CARDS_DRAGON;
|
||||
case CHANGELING -> GameAtlas.CARDS_CHANGELING;
|
||||
case CHANGELING_WIZARD -> GameAtlas.CARDS_CHANGELING_WIZARD;
|
||||
case CHANGELING_JESTER -> GameAtlas.CARDS_CHANGELING_JESTER;
|
||||
case BLUE_JESTER -> GameAtlas.CARDS_BLUE_JESTER;
|
||||
case RED_JESTER -> GameAtlas.CARDS_RED_JESTER;
|
||||
case GREEN_JESTER -> GameAtlas.CARDS_GREEN_JESTER;
|
||||
case YELLOW_JESTER -> GameAtlas.CARDS_YELLOW_JESTER;
|
||||
case RED_WIZARD -> GameAtlas.CARDS_RED_WIZARD;
|
||||
case GREEN_WIZARD -> GameAtlas.CARDS_GREEN_WIZARD;
|
||||
case YELLOW_WIZARD -> GameAtlas.CARDS_YELLOW_WIZARD;
|
||||
case BLUE_WIZARD -> GameAtlas.CARDS_BLUE_WIZARD;
|
||||
case WEREWOLF -> GameAtlas.CARDS_WEREWOLF;
|
||||
default -> GameAtlas.CARDS_BACKGROUND;
|
||||
});
|
||||
}
|
||||
ATLAS_PATHS = Collections.unmodifiableMap(paths);
|
||||
}
|
||||
|
||||
private final TextureAtlas atlas;
|
||||
|
||||
private Card card;
|
||||
private TextureRegion background;
|
||||
|
||||
private CardActor(TextureAtlas atlas) {
|
||||
this.atlas = atlas;
|
||||
setWidth(PREF_WIDTH);
|
||||
setHeight(PREF_HEIGHT);
|
||||
setOrigin(PREF_WIDTH / 2, PREF_HEIGHT / 2);
|
||||
}
|
||||
|
||||
public CardActor(@NotNull Card card, @NotNull TextureAtlas atlas) {
|
||||
this(atlas);
|
||||
setCard(card);
|
||||
}
|
||||
|
||||
public CardActor(@NotNull Card.Suit suit, @NotNull TextureAtlas atlas) {
|
||||
this(atlas);
|
||||
setCard(suit);
|
||||
}
|
||||
|
||||
public void setCard(@NotNull Card card) {
|
||||
this.card = card;
|
||||
this.background = atlas.findRegion(ATLAS_PATHS.get(card));
|
||||
if (this.background == null) throw new NoSuchElementException("Could not find texture for card " + card + ".");
|
||||
}
|
||||
|
||||
public void setCard(@NotNull Card.Suit suit) {
|
||||
this.card = null;
|
||||
this.background = atlas.findRegion(switch (suit) {
|
||||
case NONE -> GameAtlas.CARDS_BACKGROUND;
|
||||
case GREEN -> GameAtlas.CARDS_GREEN;
|
||||
case YELLOW -> GameAtlas.CARDS_YELLOW;
|
||||
case BLUE -> GameAtlas.CARDS_BLUE;
|
||||
case RED -> GameAtlas.CARDS_RED;
|
||||
});
|
||||
if (this.background == null) throw new NoSuchElementException("Could not find texture for suit " + suit + ".");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Batch batch, float parentAlpha) {
|
||||
var color = getColor();
|
||||
batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
|
||||
|
||||
float height = getWidth() * ASPECT_RATIO;
|
||||
batch.draw(background, getX(), getY() + getHeight() - height, getOriginX(), getOriginY(), getWidth(), height, getScaleX(), getScaleY(), getRotation());
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actors;
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
|
||||
import com.badlogic.gdx.scenes.scene2d.*;
|
||||
import eu.jonahbauer.wizard.client.libgdx.util.AnimationTimings;
|
||||
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
|
||||
|
||||
public class CardStack extends Group {
|
||||
private static final float EXPANDED_ROTATION_DEVIATION = 10;
|
||||
private static final float COLLAPSED_ROTATION_DEVIATION = 60;
|
||||
private static final float COLLAPSED_POSITION_DEVIATION = 15;
|
||||
|
||||
private final Random random = new Random();
|
||||
private final List<Entry> cards = new ArrayList<>();
|
||||
|
||||
private final Actor hover = new Actor();
|
||||
private boolean expanded;
|
||||
|
||||
public CardStack() {
|
||||
this.addActor(hover);
|
||||
this.addListener(new InputListener() {
|
||||
|
||||
@Override
|
||||
public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) {
|
||||
if (pointer == -1 && event.getTarget() == hover && expanded) {
|
||||
expanded = false;
|
||||
cards.forEach(Entry::collapse);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) {
|
||||
if (pointer == -1 && event.getTarget() == hover && !expanded) {
|
||||
expanded = true;
|
||||
cards.forEach(Entry::expand);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void add(GameScreen.Seat seat, CardActor card) {
|
||||
var entry = new Entry(
|
||||
card,
|
||||
seat,
|
||||
(float) random.nextGaussian((WizardGame.WIDTH - card.getWidth()) / 2, COLLAPSED_POSITION_DEVIATION),
|
||||
(float) random.nextGaussian((WizardGame.HEIGHT - card.getHeight()) / 2, COLLAPSED_POSITION_DEVIATION),
|
||||
(float) random.nextGaussian(0, COLLAPSED_ROTATION_DEVIATION),
|
||||
(float) random.nextGaussian(0, EXPANDED_ROTATION_DEVIATION)
|
||||
);
|
||||
super.addActor(card);
|
||||
if (expanded) entry.expand();
|
||||
else entry.collapse();
|
||||
cards.add(entry);
|
||||
}
|
||||
|
||||
public void add(GameScreen.Seat seat, Card card, TextureAtlas atlas) {
|
||||
var actor = new CardActor(card, atlas);
|
||||
|
||||
var x = (float) random.nextGaussian((WizardGame.WIDTH - actor.getWidth()) / 2, COLLAPSED_POSITION_DEVIATION);
|
||||
var y = (float) random.nextGaussian((WizardGame.HEIGHT - actor.getHeight()) / 2, COLLAPSED_POSITION_DEVIATION);
|
||||
var collapsedRotation = (float) random.nextGaussian(0, COLLAPSED_ROTATION_DEVIATION);
|
||||
var expandedRotation = (float) random.nextGaussian(0, EXPANDED_ROTATION_DEVIATION);
|
||||
|
||||
if (expanded) {
|
||||
actor.setPosition(seat.getFrontX(), seat.getFrontY());
|
||||
actor.setRotation(expandedRotation);
|
||||
} else {
|
||||
actor.setPosition(x, y);
|
||||
actor.setRotation(collapsedRotation);
|
||||
}
|
||||
|
||||
var entry = new Entry(actor, seat, x, y, collapsedRotation, expandedRotation);
|
||||
super.addActor(actor);
|
||||
cards.add(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void childrenChanged() {
|
||||
hover.toFront();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearChildren(boolean unfocus) {
|
||||
super.clearChildren(unfocus);
|
||||
cards.clear();
|
||||
addActor(hover);
|
||||
}
|
||||
|
||||
public List<CardActor> removeAll() {
|
||||
var out = cards.stream().map(Entry::getActor).toList();
|
||||
clearChildren(true);
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Actor removeActorAt(int index, boolean unfocus) {
|
||||
var actor = super.removeActorAt(index, unfocus);
|
||||
cards.remove(index);
|
||||
return actor;
|
||||
}
|
||||
|
||||
public void setHoverBounds(float x, float y, float width, float height) {
|
||||
hover.setBounds(x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void addActor(Actor actor) {
|
||||
super.addActor(actor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void addActorAfter(Actor actorAfter, Actor actor) {
|
||||
super.addActorAfter(actorAfter, actor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void addActorAt(int index, Actor actor) {
|
||||
super.addActorAt(index, actor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void addActorBefore(Actor actorBefore, Actor actor) {
|
||||
super.addActorBefore(actorBefore, actor);
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class Entry {
|
||||
private final CardActor actor;
|
||||
private final GameScreen.Seat seat;
|
||||
private final float x;
|
||||
private final float y;
|
||||
private final float rotation;
|
||||
private final float expandedRotation;
|
||||
private Action action;
|
||||
|
||||
public void expand() {
|
||||
if (action != null) actor.removeAction(action);
|
||||
|
||||
action = parallel(
|
||||
moveTo(
|
||||
seat.getFrontX() - actor.getWidth() / 2,
|
||||
seat.getFrontY() - actor.getHeight() / 2,
|
||||
AnimationTimings.STACK_EXPAND
|
||||
),
|
||||
rotateTo(expandedRotation, AnimationTimings.STACK_EXPAND)
|
||||
);
|
||||
|
||||
actor.addAction(action);
|
||||
}
|
||||
|
||||
public void collapse() {
|
||||
if (action != null) actor.removeAction(action);
|
||||
|
||||
action = parallel(
|
||||
moveTo(x, y, AnimationTimings.STACK_COLLAPSE),
|
||||
rotateTo(rotation, AnimationTimings.STACK_COLLAPSE)
|
||||
);
|
||||
|
||||
actor.addAction(action);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,249 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actors;
|
||||
|
||||
import com.badlogic.gdx.Input;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
|
||||
import com.badlogic.gdx.math.MathUtils;
|
||||
import com.badlogic.gdx.math.Vector2;
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.InputEvent;
|
||||
import com.badlogic.gdx.scenes.scene2d.InputListener;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup;
|
||||
import com.badlogic.gdx.utils.Pools;
|
||||
import eu.jonahbauer.wizard.client.libgdx.util.AnimationTimings;
|
||||
import eu.jonahbauer.wizard.client.libgdx.util.Pair;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.moveTo;
|
||||
|
||||
public class CardsGroup extends WidgetGroup {
|
||||
private static final float TARGET_SPACING = -50f;
|
||||
|
||||
@Getter
|
||||
private final float prefWidth = 0;
|
||||
@Getter
|
||||
private final float prefHeight = CardActor.PREF_HEIGHT;
|
||||
|
||||
private final TextureAtlas atlas;
|
||||
|
||||
private float[] cardX;
|
||||
private float spacing;
|
||||
private float cardWidth;
|
||||
private boolean animate;
|
||||
|
||||
private CardActor target;
|
||||
|
||||
private boolean dragging;
|
||||
private final float touchSlop = 5;
|
||||
private CardActor dragTarget;
|
||||
private float dragStartX;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
private Card selected;
|
||||
|
||||
private Consumer<CardActor> onClickListener;
|
||||
|
||||
public CardsGroup(List<Card> cards, TextureAtlas atlas) {
|
||||
this.atlas = atlas;
|
||||
update(cards);
|
||||
|
||||
setFillParent(true);
|
||||
|
||||
this.addListener(new InputListener() {
|
||||
|
||||
@Override
|
||||
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
|
||||
if (pointer != 0) return false;
|
||||
if (button != Input.Buttons.LEFT) return false;
|
||||
if (event.getTarget() instanceof CardActor card) {
|
||||
dragTarget = card;
|
||||
dragStartX = x;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void touchDragged(InputEvent event, float x, float y, int pointer) {
|
||||
if (pointer != 0) return;
|
||||
if (!dragging && Math.abs(x - dragStartX) > touchSlop) {
|
||||
dragging = true;
|
||||
} else if (dragging) {
|
||||
int index = Arrays.binarySearch(cardX, x - (cardWidth + spacing));
|
||||
if (index < 0) {
|
||||
index = -(index + 1);
|
||||
if (index >= getChildren().size) return;
|
||||
}
|
||||
if (getChild(index) != dragTarget) {
|
||||
float oldX = dragTarget.getX();
|
||||
getChildren().removeValue(dragTarget, true);
|
||||
getChildren().insert(index, dragTarget);
|
||||
invalidate();
|
||||
dragTarget.setHeight(1.3f * getHeight());
|
||||
dragStartX += (dragTarget.getX() - oldX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
|
||||
if (pointer != 0) return;
|
||||
if (button != Input.Buttons.LEFT) return;
|
||||
if (!dragging && dragTarget != null) {
|
||||
if (onClickListener != null) {
|
||||
onClickListener.accept(dragTarget);
|
||||
}
|
||||
}
|
||||
dragging = false;
|
||||
dragTarget = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) {
|
||||
if (pointer != -1) return;
|
||||
if (event.getTarget() instanceof CardActor card) {
|
||||
target = card;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) {
|
||||
if (pointer != -1) return;
|
||||
if (event.getTarget() == target) {
|
||||
target = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void act(float delta) {
|
||||
super.act(delta);
|
||||
|
||||
float offset = 0.3f * getHeight();
|
||||
float speed = 10 * offset;
|
||||
|
||||
for (var child : getChildren()) {
|
||||
if (child instanceof CardActor card) {
|
||||
float height = card.getHeight();
|
||||
if ((child == dragTarget || dragTarget == null && child == target) || card.getCard() == selected) {
|
||||
height += speed * delta;
|
||||
} else {
|
||||
height -= speed * delta;
|
||||
}
|
||||
height = MathUtils.clamp(height, getHeight(), getHeight() + offset);
|
||||
card.setHeight(height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layout() {
|
||||
var children = getChildren();
|
||||
var count = children.size;
|
||||
|
||||
float height = getHeight();
|
||||
float width = getWidth();
|
||||
if (cardX == null || cardX.length != count) {
|
||||
cardX = new float[count];
|
||||
}
|
||||
|
||||
cardWidth = height / CardActor.ASPECT_RATIO;
|
||||
spacing = width - count * cardWidth;
|
||||
spacing /= count - 1;
|
||||
spacing = Math.min(spacing, TARGET_SPACING);
|
||||
|
||||
float x = Math.max(0, (width - count * cardWidth - (count - 1) * TARGET_SPACING) / 2);
|
||||
float y = 0;
|
||||
|
||||
for (int i = 0; i < children.size; i++) {
|
||||
var child = children.get(i);
|
||||
|
||||
// position
|
||||
if (animate) {
|
||||
child.addAction(moveTo(x, y, AnimationTimings.HAND_LAYOUT));
|
||||
} else {
|
||||
child.setPosition(x, y);
|
||||
}
|
||||
|
||||
// size
|
||||
child.setWidth(cardWidth);
|
||||
if (child != dragTarget && child != target) {
|
||||
child.setHeight(height);
|
||||
}
|
||||
|
||||
cardX[i] = x;
|
||||
x += cardWidth + spacing;
|
||||
}
|
||||
animate = false;
|
||||
}
|
||||
|
||||
public @NotNull Pair<List<CardActor>, List<CardActor>> update(@NotNull List<Card> cards) {
|
||||
var added = new ArrayList<>(cards);
|
||||
var removed = new ArrayList<Card>();
|
||||
|
||||
for (var child : getChildren()) {
|
||||
if (child instanceof CardActor actor) {
|
||||
var card = actor.getCard();
|
||||
|
||||
if (!added.remove(card)) {
|
||||
removed.add(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var removedActors = removed.stream().map(this::remove).toList();
|
||||
var addedActors = added.stream().sorted().map(card -> new CardActor(card, atlas)).toList();
|
||||
|
||||
addedActors.forEach(this::addActor);
|
||||
layout();
|
||||
|
||||
return Pair.of(removedActors, addedActors);
|
||||
}
|
||||
|
||||
public @Nullable CardActor remove(Card card) {
|
||||
var actor = find(card);
|
||||
if (actor == null) return null;
|
||||
|
||||
// adjust actor
|
||||
actor.setY(getY() + actor.getHeight() - getHeight());
|
||||
var pos = localToStageCoordinates(Pools.get(Vector2.class).obtain().set(actor.getX(), actor.getY()));
|
||||
actor.setHeight(getHeight());
|
||||
actor.setPosition(pos.x, pos.y);
|
||||
if (target == actor) {
|
||||
target = null;
|
||||
}
|
||||
|
||||
// remove actor
|
||||
animate = true;
|
||||
actor.clearActions();
|
||||
actor.remove();
|
||||
return actor;
|
||||
}
|
||||
|
||||
public @Nullable CardActor find(Card card) {
|
||||
var children = getChildren();
|
||||
for (int i = 0; i < children.size; i++) {
|
||||
if (children.get(i) instanceof CardActor cardActor && cardActor.getCard() == card) {
|
||||
return cardActor;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMinHeight() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void setOnClickListener(Consumer<CardActor> onClickListener) {
|
||||
this.onClickListener = onClickListener;
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.actors;
|
||||
|
||||
import com.badlogic.gdx.math.MathUtils;
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.InputEvent;
|
||||
import com.badlogic.gdx.scenes.scene2d.InputListener;
|
||||
import com.badlogic.gdx.scenes.scene2d.Touchable;
|
||||
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
|
||||
import com.badlogic.gdx.scenes.scene2d.actions.ScaleToAction;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
|
||||
import com.badlogic.gdx.utils.Align;
|
||||
import eu.jonahbauer.wizard.client.libgdx.util.AnimationTimings;
|
||||
import lombok.Setter;
|
||||
import org.jetbrains.annotations.Range;
|
||||
|
||||
public class PadOfTruth extends Table {
|
||||
public static final float EXTENDED_WIDTH = 636;
|
||||
public static final float EXTENDED_HEIGHT = 824;
|
||||
public static final float COLLAPSED_SCALE = CardActor.PREF_HEIGHT / EXTENDED_HEIGHT;
|
||||
|
||||
private final Label[] names = new Label[6];
|
||||
private final Label[][] predictions = new Label[20][];
|
||||
private final Label[][] scores = new Label[20][];
|
||||
|
||||
@Setter
|
||||
private boolean enabled = true;
|
||||
|
||||
public PadOfTruth(Skin skin, Drawable background) {
|
||||
super(skin);
|
||||
setTouchable(Touchable.enabled);
|
||||
setBackground(background);
|
||||
|
||||
setWidth(EXTENDED_WIDTH);
|
||||
setHeight(EXTENDED_HEIGHT);
|
||||
|
||||
setTransform(true);
|
||||
setScale(COLLAPSED_SCALE);
|
||||
|
||||
addListener(new InputListener() {
|
||||
private ScaleToAction action;
|
||||
|
||||
@Override
|
||||
public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) {
|
||||
if (!enabled) return;
|
||||
if (fromActor != null && isAscendantOf(fromActor)) return;
|
||||
if (action != null) removeAction(action);
|
||||
action = Actions.scaleTo(1, 1, AnimationTimings.PAD_OF_TRUTH_EXPAND);
|
||||
addAction(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) {
|
||||
if (!enabled) return;
|
||||
if (toActor != null && isAscendantOf(toActor)) return;
|
||||
if (action != null) removeAction(action);
|
||||
action = Actions.scaleTo(COLLAPSED_SCALE, COLLAPSED_SCALE, AnimationTimings.PAD_OF_TRUTH_COLLAPSE);
|
||||
addAction(action);
|
||||
}
|
||||
});
|
||||
|
||||
setRound(false);
|
||||
pad(20, 45, 25, 25);
|
||||
align(Align.topLeft);
|
||||
clip();
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
columnDefaults(2 * i).width(57f).center().pad(2, 4, 2, 4);
|
||||
columnDefaults(2 * i + 1).width(22f).center().pad(2, 4, 2, 4);
|
||||
}
|
||||
|
||||
for (int player = 0; player < 6; player++) {
|
||||
var cell = add("", "handwritten").height(46f).width(87f).colspan(2);
|
||||
names[player] = cell.getActor();
|
||||
names[player].setEllipsis(true);
|
||||
}
|
||||
row();
|
||||
|
||||
for (int round = 0; round < 20; round++) {
|
||||
int players = MathUtils.clamp(60 / (round + 1), 3, 6);
|
||||
predictions[round] = new Label[players];
|
||||
scores[round] = new Label[players];
|
||||
for (int player = 0; player < players; player++) {
|
||||
scores[round][player] = add("", "handwritten").height(32.5f).center().getActor();
|
||||
predictions[round][player] = add("", "handwritten").height(32.5f).center().getActor();
|
||||
|
||||
scores[round][player].setAlignment(Align.center);
|
||||
predictions[round][player].setAlignment(Align.center);
|
||||
}
|
||||
row();
|
||||
}
|
||||
}
|
||||
|
||||
public void setName(int player, String name) {
|
||||
names[player].setText(name);
|
||||
}
|
||||
|
||||
public void setScore(int player, @Range(from = 0, to = 19) int round, int score) {
|
||||
scores[round][player].setText(String.valueOf(score));
|
||||
}
|
||||
|
||||
public void setPrediction(int player, @Range(from = 0, to = 19) int round, int prediction) {
|
||||
predictions[round][player].setText(String.valueOf(prediction));
|
||||
}
|
||||
|
||||
public void checkPosition(int player, int round) {
|
||||
if (round < 0 || round >= predictions.length || round >= scores.length) throw new ArrayIndexOutOfBoundsException(round);
|
||||
if (player < 0 || player >= predictions[round].length || player >= scores[round].length) throw new ArrayIndexOutOfBoundsException(player);
|
||||
}
|
||||
|
||||
public void clearValues() {
|
||||
for (var row : predictions) {
|
||||
for (var label : row) {
|
||||
label.setText(null);
|
||||
}
|
||||
}
|
||||
|
||||
for (var row : scores) {
|
||||
for (var label : row) {
|
||||
label.setText(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.listeners;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.InputEvent;
|
||||
import com.badlogic.gdx.scenes.scene2d.InputListener;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane;
|
||||
|
||||
public class AutoFocusListener extends InputListener {
|
||||
@Override
|
||||
public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) {
|
||||
var target = event.getTarget();
|
||||
|
||||
while (target != null && !(target instanceof ScrollPane)) {
|
||||
target = target.getParent();
|
||||
}
|
||||
|
||||
if (target instanceof ScrollPane pane) {
|
||||
event.getStage().setScrollFocus(pane);
|
||||
} else {
|
||||
event.getStage().setScrollFocus(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) {
|
||||
var target = event.getTarget();
|
||||
|
||||
while (target != null && !(target instanceof ScrollPane)) {
|
||||
target = target.getParent();
|
||||
}
|
||||
|
||||
if (target == null || toActor == null || !toActor.isDescendantOf(target)) {
|
||||
event.getStage().setScrollFocus(null);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.listeners;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.InputEvent;
|
||||
import com.badlogic.gdx.scenes.scene2d.InputListener;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Button;
|
||||
|
||||
public class ButtonKeyListener extends InputListener {
|
||||
|
||||
@Override
|
||||
public boolean keyTyped(InputEvent event, char character) {
|
||||
if ((character == '\n' || character == ' ') && event.getTarget() instanceof Button button) {
|
||||
button.toggle();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.listeners;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.InputEvent;
|
||||
import com.badlogic.gdx.scenes.scene2d.InputListener;
|
||||
import com.badlogic.gdx.scenes.scene2d.Stage;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextField;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.UIUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class KeyboardFocusManager extends InputListener {
|
||||
private final List<Actor> focusOrder;
|
||||
|
||||
public KeyboardFocusManager(Actor...focusOrder) {
|
||||
this.focusOrder = List.of(focusOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyTyped(InputEvent event, char character) {
|
||||
var stage = event.getStage();
|
||||
if (character == '\t') {
|
||||
var currentFocus = stage.getKeyboardFocus();
|
||||
var index = currentFocus == null ? -1 : focusOrder.indexOf(currentFocus);
|
||||
var count = focusOrder.size();
|
||||
if (count == 0) return true;
|
||||
|
||||
Actor nextFocus;
|
||||
if (index == -1) {
|
||||
nextFocus = focusOrder.get(UIUtils.shift() ? count - 1 : 0);
|
||||
} else {
|
||||
var direction = UIUtils.shift() ? -1 : 1;
|
||||
nextFocus = focusOrder.get(((index + direction) % count + count) % count);
|
||||
}
|
||||
|
||||
if (nextFocus instanceof TextField textField) {
|
||||
textField.selectAll();
|
||||
}
|
||||
|
||||
stage.setKeyboardFocus(nextFocus);
|
||||
event.stop();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.listeners;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.*;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
|
||||
|
||||
public class ResetErrorListener extends ChangeListener {
|
||||
private final Skin skin;
|
||||
private final Actor target;
|
||||
private final String style;
|
||||
|
||||
public ResetErrorListener(Skin skin) {
|
||||
this(skin, null);
|
||||
}
|
||||
|
||||
public ResetErrorListener(Skin skin, Actor target) {
|
||||
this(skin, target, "default");
|
||||
}
|
||||
|
||||
public ResetErrorListener(Skin skin, Actor target, String style) {
|
||||
this.skin = skin;
|
||||
this.target = target;
|
||||
this.style = style;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changed(ChangeEvent event, Actor actor) {
|
||||
var target = this.target != null ? this.target : event.getTarget();
|
||||
if (target instanceof TextField textField) {
|
||||
textField.setStyle(skin.get(style, TextField.TextFieldStyle.class));
|
||||
} else if (target instanceof SelectBox<?> box) {
|
||||
box.setStyle(skin.get(style, SelectBox.SelectBoxStyle.class));
|
||||
} else if (target instanceof List<?> list) {
|
||||
list.setStyle(skin.get(style, List.ListStyle.class));
|
||||
} else if (target instanceof ScrollPane scrollPane) {
|
||||
scrollPane.setStyle(skin.get(style, ScrollPane.ScrollPaneStyle.class));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.listeners;
|
||||
|
||||
import com.badlogic.gdx.Input;
|
||||
import com.badlogic.gdx.math.MathUtils;
|
||||
import com.badlogic.gdx.scenes.scene2d.InputEvent;
|
||||
import com.badlogic.gdx.scenes.scene2d.InputListener;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.SelectBox;
|
||||
|
||||
public class SelectBoxListener extends InputListener {
|
||||
private final SelectBox<?> selectBox;
|
||||
|
||||
public SelectBoxListener(SelectBox<?> selectBox) {
|
||||
this.selectBox = selectBox;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyDown(InputEvent event, int keycode) {
|
||||
var size = selectBox.getItems().size;
|
||||
if (size == 0) return false;
|
||||
|
||||
switch (keycode) {
|
||||
case Input.Keys.UP -> {
|
||||
var index = selectBox.getSelectedIndex();
|
||||
if (index == -1) {
|
||||
selectBox.setSelectedIndex(size - 1);
|
||||
} else {
|
||||
selectBox.setSelectedIndex(MathUtils.clamp(index - 1, 0, size - 1));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case Input.Keys.DOWN -> {
|
||||
var index = selectBox.getSelectedIndex();
|
||||
if (index == -1) {
|
||||
selectBox.setSelectedIndex(0);
|
||||
} else {
|
||||
selectBox.setSelectedIndex(MathUtils.clamp(index + 1, 0, size - 1));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.screens;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextField;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
|
||||
import com.badlogic.gdx.utils.Align;
|
||||
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
|
||||
import eu.jonahbauer.wizard.client.libgdx.listeners.KeyboardFocusManager;
|
||||
import eu.jonahbauer.wizard.client.libgdx.listeners.ResetErrorListener;
|
||||
import eu.jonahbauer.wizard.client.libgdx.state.Menu;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
public class ConnectScreen extends MenuScreen {
|
||||
private TextButton buttonBack;
|
||||
private TextButton buttonConnect;
|
||||
|
||||
private TextField uriField;
|
||||
|
||||
public ConnectScreen(WizardGame game) {
|
||||
super(game);
|
||||
}
|
||||
|
||||
private final ChangeListener listener = new ChangeListener() {
|
||||
@Override
|
||||
public void changed(ChangeEvent event, Actor actor) {
|
||||
if (actor == buttonBack) {
|
||||
game.getClient().execute(Menu.class, Menu::showMenuScreen);
|
||||
sfxClick();
|
||||
} else if (actor == buttonConnect) {
|
||||
try {
|
||||
var uriString = ConnectScreen.this.uriField.getText();
|
||||
var uri = new URI(uriString);
|
||||
game.storage.uri = uriString;
|
||||
game.getClient().execute(Menu.class, (s, c) -> s.connect(c, uri));
|
||||
} catch (URISyntaxException e) {
|
||||
uriField.setStyle(getTextFieldErrorStyle());
|
||||
}
|
||||
|
||||
sfxClick();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
|
||||
buttonBack = new TextButton(messages.get("menu.connect.back"), skin);
|
||||
buttonBack.addListener(listener);
|
||||
getButtonGroup().addActor(buttonBack);
|
||||
|
||||
buttonConnect = new TextButton(messages.get("menu.connect.connect"), skin);
|
||||
buttonConnect.addListener(listener);
|
||||
getButtonGroup().addActor(buttonConnect);
|
||||
|
||||
var label = new Label(messages.get("menu.connect.address.label"), skin);
|
||||
label.setSize(0.4f * WizardGame.WIDTH, 64);
|
||||
label.setAlignment(Align.center);
|
||||
label.setPosition(0.5f * (WizardGame.WIDTH - label.getWidth()), 0.55f * (WizardGame.HEIGHT - label.getHeight()));
|
||||
|
||||
// TODO sensible default value
|
||||
uriField = new TextField(game.storage.uri, skin);
|
||||
uriField.setMessageText(messages.get("menu.connect.uri.hint"));
|
||||
uriField.setSize(0.4f * WizardGame.WIDTH, 64);
|
||||
uriField.setPosition(0.5f * (WizardGame.WIDTH - uriField.getWidth()), 0.45f * (WizardGame.HEIGHT - uriField.getHeight()));
|
||||
uriField.addListener(new ResetErrorListener(skin));
|
||||
|
||||
stage.addActor(uriField);
|
||||
stage.addActor(label);
|
||||
stage.addCaptureListener(new KeyboardFocusManager(uriField, buttonBack, buttonConnect));
|
||||
|
||||
buttonBack.setName("button_back");
|
||||
buttonConnect.setName("button_connect");
|
||||
uriField.setName("uri");
|
||||
}
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.screens;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.*;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
|
||||
import eu.jonahbauer.wizard.client.libgdx.listeners.KeyboardFocusManager;
|
||||
import eu.jonahbauer.wizard.client.libgdx.listeners.ResetErrorListener;
|
||||
import eu.jonahbauer.wizard.client.libgdx.listeners.SelectBoxListener;
|
||||
import eu.jonahbauer.wizard.client.libgdx.state.Lobby;
|
||||
import eu.jonahbauer.wizard.common.model.Configuration;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
@Log4j2
|
||||
public class CreateGameScreen extends MenuScreen {
|
||||
private static final int MAX_SESSION_NAME_LENGTH = 20;
|
||||
|
||||
private TextButton buttonBack;
|
||||
private TextButton buttonContinue;
|
||||
|
||||
private TextField sessionName;
|
||||
private TextField playerName;
|
||||
private TextField timeOut;
|
||||
private SelectBox<String> configurations;
|
||||
|
||||
private String oldPlayerName;
|
||||
|
||||
private final ChangeListener listener = new ChangeListener() {
|
||||
@Override
|
||||
public void changed(ChangeEvent event, Actor actor) {
|
||||
if (actor == buttonBack) {
|
||||
game.getClient().execute(Lobby.class, Lobby::showListScreen);
|
||||
sfxClick();
|
||||
} else if (actor == buttonContinue) {
|
||||
create();
|
||||
sfxClick();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public CreateGameScreen(WizardGame game) {
|
||||
super(game);
|
||||
oldPlayerName = game.storage.playerName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
|
||||
buttonBack = new TextButton(messages.get("menu.create_game.back"), skin);
|
||||
buttonBack.addListener(listener);
|
||||
getButtonGroup().addActor(buttonBack);
|
||||
|
||||
buttonContinue = new TextButton(messages.get("menu.create_game.create"), skin);
|
||||
buttonContinue.addListener(listener);
|
||||
getButtonGroup().addActor(buttonContinue);
|
||||
|
||||
var errorListener = new ResetErrorListener(skin);
|
||||
|
||||
sessionName = new TextField("", skin);
|
||||
sessionName.setPosition(WizardGame.WIDTH * 0.3f, WizardGame.HEIGHT * 0.5f);
|
||||
sessionName.setSize(0.4f * WizardGame.WIDTH, 64);
|
||||
sessionName.setMaxLength(MAX_SESSION_NAME_LENGTH);
|
||||
sessionName.addListener(errorListener);
|
||||
sessionName.setProgrammaticChangeEvents(true);
|
||||
|
||||
playerName = new TextField(oldPlayerName, skin);
|
||||
playerName.setPosition(WizardGame.WIDTH * 0.3f, WizardGame.HEIGHT * 0.45f);
|
||||
playerName.setSize(0.4f * WizardGame.WIDTH, 64);
|
||||
playerName.addListener(errorListener);
|
||||
var playerNameListener = new ChangeListener() {
|
||||
private final String format = messages.get("menu.create_game.session_name.default");
|
||||
|
||||
@Override
|
||||
public void changed(ChangeEvent event, Actor actor) {
|
||||
var player = playerName.getText();
|
||||
var session = sessionName.getText();
|
||||
var previousSuggestion = format.formatted(oldPlayerName);
|
||||
|
||||
boolean shouldApplySuggestion = session.isEmpty()
|
||||
|| previousSuggestion.startsWith(session) && (session.length() == MAX_SESSION_NAME_LENGTH || session.length() == previousSuggestion.length());
|
||||
if (shouldApplySuggestion) {
|
||||
if (player.isEmpty()) {
|
||||
sessionName.setText("");
|
||||
} else {
|
||||
sessionName.setText(format.formatted(player));
|
||||
}
|
||||
}
|
||||
|
||||
game.storage.playerName = player;
|
||||
oldPlayerName = player;
|
||||
}
|
||||
};
|
||||
playerName.addListener(playerNameListener);
|
||||
playerNameListener.changed(null, null);
|
||||
|
||||
timeOut = new TextField("", skin);
|
||||
timeOut.setPosition(WizardGame.WIDTH * 0.3f, WizardGame.HEIGHT * 0.4f);
|
||||
timeOut.setSize(0.4f * WizardGame.WIDTH, 64);
|
||||
timeOut.setTextFieldFilter(new TextField.TextFieldFilter.DigitsOnlyFilter());
|
||||
timeOut.addListener(errorListener);
|
||||
|
||||
configurations = new SelectBox<>(skin);
|
||||
configurations.setSize(400, 64);
|
||||
configurations.setPosition(WizardGame.WIDTH * 0.3f, WizardGame.HEIGHT * 0.3f);
|
||||
configurations.addListener(errorListener);
|
||||
configurations.addListener(new SelectBoxListener(configurations));
|
||||
|
||||
Array<String> values = new Array<>();
|
||||
for (Configuration value : Configuration.values()) {
|
||||
values.add(value.toString());
|
||||
}
|
||||
configurations.setItems(values);
|
||||
|
||||
var contentTable = new Table(skin).center().left();
|
||||
contentTable.columnDefaults(0).growX().width(0.4f * WizardGame.WIDTH - 20);
|
||||
contentTable.setSize(0.4f * WizardGame.WIDTH - 20, 400);
|
||||
contentTable.setPosition(WizardGame.WIDTH * 0.3f, WizardGame.HEIGHT * 0.3f);
|
||||
|
||||
contentTable.add(messages.get("menu.create_game.player_name.label")).row();
|
||||
contentTable.add(playerName).row();
|
||||
contentTable.add(messages.get("menu.create_game.session_name.label")).row();
|
||||
contentTable.add(sessionName).row();
|
||||
contentTable.add(messages.get("menu.create_game.session_timeout.label")).row();
|
||||
contentTable.add(timeOut).row();
|
||||
contentTable.add(messages.get("menu.create_game.session_configuration.label")).row();
|
||||
contentTable.add(configurations).row();
|
||||
|
||||
stage.addActor(contentTable);
|
||||
stage.addCaptureListener(new KeyboardFocusManager(playerName, sessionName, timeOut, configurations, buttonBack, buttonContinue));
|
||||
|
||||
buttonBack.setName("button_back");
|
||||
buttonContinue.setName("button_continue");
|
||||
sessionName.setName("session_name");
|
||||
playerName.setName("player_name");
|
||||
timeOut.setName("timeout");
|
||||
configurations.setName("configurations");
|
||||
}
|
||||
|
||||
private void create() {
|
||||
boolean error = false;
|
||||
|
||||
String sessionName = this.sessionName.getText();
|
||||
if (sessionName.isBlank()) {
|
||||
log.warn("Please choose a session name.");
|
||||
this.sessionName.setStyle(getTextFieldErrorStyle());
|
||||
error = true;
|
||||
}
|
||||
|
||||
String playerName = this.playerName.getText();
|
||||
if (playerName.isBlank()) {
|
||||
log.warn("Please choose a name.");
|
||||
this.playerName.setStyle(getTextFieldErrorStyle());
|
||||
error = true;
|
||||
}
|
||||
|
||||
long timeout = 0;
|
||||
try {
|
||||
timeout = Long.parseLong(this.timeOut.getText());
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("Please choose a valid timeout.");
|
||||
this.timeOut.setStyle(getTextFieldErrorStyle());
|
||||
error = true;
|
||||
}
|
||||
|
||||
Configuration config = null;
|
||||
try {
|
||||
int selected = configurations.getSelectedIndex();
|
||||
config = Configuration.values()[selected];
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
log.warn("Please select a valid configuration.");
|
||||
this.configurations.setStyle(getSelectBoxErrorStyle());
|
||||
error = true;
|
||||
}
|
||||
|
||||
var fConfig = config;
|
||||
var fTimeout = timeout;
|
||||
if (!error) {
|
||||
var client = game.getClient();
|
||||
client.execute(Lobby.class, (s, c) -> s.createSession(c, sessionName, fConfig, 1000 * fTimeout, playerName));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.screens;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
|
||||
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
|
||||
import eu.jonahbauer.wizard.client.libgdx.listeners.KeyboardFocusManager;
|
||||
import eu.jonahbauer.wizard.client.libgdx.state.BaseState;
|
||||
|
||||
public class ErrorScreen extends MenuScreen {
|
||||
private final String message;
|
||||
private TextButton buttonBack;
|
||||
|
||||
private final ChangeListener listener = new ChangeListener() {
|
||||
@Override
|
||||
public void changed(ChangeEvent event, Actor actor) {
|
||||
if (actor == buttonBack) {
|
||||
game.getClient().execute(BaseState.class, BaseState::dismissErrorScreen);
|
||||
sfxClick();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public ErrorScreen(WizardGame game, String message) {
|
||||
super(game);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
|
||||
buttonBack = new TextButton(messages.get("menu.error.back"), skin);
|
||||
var label = new Label(message, skin);
|
||||
|
||||
var content = new VerticalGroup();
|
||||
content.setPosition(WizardGame.WIDTH * 0.5f, WizardGame.HEIGHT*0.5f);
|
||||
content.addActor(label);
|
||||
content.addActor(buttonBack);
|
||||
|
||||
buttonBack.addListener(listener);
|
||||
|
||||
stage.addActor(content);
|
||||
stage.addCaptureListener(new KeyboardFocusManager(buttonBack));
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,285 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.screens;
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
|
||||
import com.badlogic.gdx.math.MathUtils;
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.*;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
|
||||
import com.badlogic.gdx.utils.Align;
|
||||
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
|
||||
import eu.jonahbauer.wizard.client.libgdx.actors.CardActor;
|
||||
import eu.jonahbauer.wizard.client.libgdx.listeners.KeyboardFocusManager;
|
||||
import eu.jonahbauer.wizard.client.libgdx.state.Menu;
|
||||
import eu.jonahbauer.wizard.client.libgdx.util.WizardAssetManager;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
|
||||
public class InstructionScreen extends MenuScreen {
|
||||
private static final int MAX_PAGE = 3;
|
||||
|
||||
private TextButton buttonBack;
|
||||
private VerticalGroup content;
|
||||
private ScrollPane scrollPane;
|
||||
private TextButton nextPageButton;
|
||||
private TextButton previousPageButton;
|
||||
|
||||
private int currentPage = 0;
|
||||
|
||||
private TextureAtlas atlas;
|
||||
|
||||
private final ChangeListener listener = new ChangeListener() {
|
||||
@Override
|
||||
public void changed(ChangeEvent event, Actor actor) {
|
||||
if (actor == buttonBack) {
|
||||
game.getClient().execute(Menu.class, Menu::showMenuScreen);
|
||||
sfxClick();
|
||||
} else if (actor == nextPageButton) {
|
||||
currentPage = MathUtils.clamp(currentPage + 1, 0, MAX_PAGE);
|
||||
showPage(currentPage);
|
||||
sfxClick();
|
||||
} else if (actor == previousPageButton) {
|
||||
currentPage = MathUtils.clamp(currentPage - 1, 0, MAX_PAGE);
|
||||
showPage(currentPage);
|
||||
sfxClick();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public InstructionScreen(WizardGame game) {
|
||||
super(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void load() {
|
||||
super.load();
|
||||
assets.loadGame();
|
||||
assets.finishLoading();
|
||||
|
||||
atlas = assets.get(WizardAssetManager.ATLAS_GAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
|
||||
content = new VerticalGroup().left().grow().pad(20);
|
||||
|
||||
getTitle().moveBy(0, 80);
|
||||
getButtonGroup().moveBy(0, - 80);
|
||||
|
||||
previousPageButton = new TextButton(messages.get("menu.instruction.previousPageButton"), skin);
|
||||
previousPageButton.addListener(listener);
|
||||
getButtonGroup().addActor(previousPageButton);
|
||||
|
||||
buttonBack = new TextButton(messages.get("menu.instruction.back"), skin);
|
||||
buttonBack.addListener(listener);
|
||||
getButtonGroup().addActor(buttonBack);
|
||||
|
||||
nextPageButton = new TextButton(messages.get("menu.instruction.nextPageButton"), skin);
|
||||
nextPageButton.addListener(listener);
|
||||
getButtonGroup().addActor(nextPageButton);
|
||||
|
||||
scrollPane = new ScrollPane(content, skin);
|
||||
scrollPane.setPosition(0.175f * WizardGame.WIDTH, 0.2f * WizardGame.HEIGHT);
|
||||
scrollPane.setSize(0.65f * WizardGame.WIDTH, 400 + 0.1f * WizardGame.HEIGHT + 80);
|
||||
stage.addActor(scrollPane);
|
||||
|
||||
stage.addCaptureListener(new KeyboardFocusManager(scrollPane, previousPageButton, buttonBack, nextPageButton));
|
||||
|
||||
showFirstPage();
|
||||
}
|
||||
|
||||
private void showPage(int page) {
|
||||
switch (page) {
|
||||
case 0 -> showFirstPage();
|
||||
case 1 -> showSecondPage();
|
||||
case 2 -> showThirdPage();
|
||||
case 3 -> showFourthPage();
|
||||
default -> throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
private void showFirstPage() {
|
||||
currentPage = 0;
|
||||
reset();
|
||||
|
||||
startSection("menu.instruction.main.title");
|
||||
startSubsection("menu.instruction.main.welcome.title");
|
||||
addParagraph("menu.instruction.main.welcome.par0");
|
||||
startSubsection("menu.instruction.main.general.title");
|
||||
addParagraph("menu.instruction.main.general.par0");
|
||||
}
|
||||
|
||||
private void showSecondPage() {
|
||||
currentPage = 1;
|
||||
reset();
|
||||
|
||||
startSection("menu.instruction.standard.title");
|
||||
addParagraph("menu.instruction.standard.par0");
|
||||
|
||||
var colors = new HorizontalGroup().space(10).pad(10).center();
|
||||
for (Card.Suit suit : Card.Suit.values()) {
|
||||
if (suit != Card.Suit.NONE) {
|
||||
VerticalGroup suitGroup = new VerticalGroup().space(10);
|
||||
suitGroup.addActor(new CardActor(suit, atlas));
|
||||
suitGroup.addActor(new Label(switch (suit) {
|
||||
case RED -> messages.get("menu.instruction.suit.red");
|
||||
case GREEN -> messages.get("menu.instruction.suit.green");
|
||||
case BLUE -> messages.get("menu.instruction.suit.blue");
|
||||
case YELLOW -> messages.get("menu.instruction.suit.yellow");
|
||||
default -> suit.toString();
|
||||
}, skin));
|
||||
colors.addActor(suitGroup);
|
||||
}
|
||||
}
|
||||
content.addActor(colors);
|
||||
|
||||
addParagraph("menu.instruction.standard.par1");
|
||||
|
||||
var specialCards = new HorizontalGroup().space(10).pad(10).center();
|
||||
|
||||
var wizard = new VerticalGroup();
|
||||
wizard.addActor(new CardActor(Card.BLUE_WIZARD, atlas));
|
||||
wizard.addActor(new Label(messages.get("menu.instruction.wizard"), skin));
|
||||
specialCards.addActor(wizard);
|
||||
|
||||
var jester = new VerticalGroup();
|
||||
jester.addActor(new CardActor(Card.BLUE_JESTER, atlas));
|
||||
jester.addActor(new Label(messages.get("menu.instruction.jester"), skin));
|
||||
specialCards.addActor(jester);
|
||||
|
||||
content.addActor(specialCards);
|
||||
|
||||
addParagraph("menu.instruction.standard.par2");
|
||||
|
||||
startSubsection("menu.instruction.standard.trick.title");
|
||||
addParagraph("menu.instruction.standard.trick.par0");
|
||||
|
||||
startSubsection("menu.instruction.standard.special_cases.title");
|
||||
addParagraph("menu.instruction.standard.special_cases.par0");
|
||||
|
||||
startSubsection("menu.instruction.standard.scoring.title");
|
||||
addParagraph("menu.instruction.standard.scoring.par0");
|
||||
}
|
||||
|
||||
private void showThirdPage() {
|
||||
currentPage = 2;
|
||||
reset();
|
||||
|
||||
startSection("menu.instruction.variant.title");
|
||||
addParagraph("menu.instruction.variant.intro");
|
||||
|
||||
Table table = new Table(skin).padTop(10);
|
||||
table.defaults().space(10.0f).left().top();
|
||||
table.columnDefaults(1).grow();
|
||||
table.add(messages.get("menu.instruction.variant.default"));
|
||||
table.add(messages.get("menu.instruction.variant.default.description")).getActor().setWrap(true);
|
||||
table.row();
|
||||
table.add(messages.get("menu.instruction.variant.defaultpm1"));
|
||||
table.add(messages.get("menu.instruction.variant.defaultpm1.description")).getActor().setWrap(true);
|
||||
table.row();
|
||||
table.add(messages.get("menu.instruction.variant.anniversary2016"));
|
||||
table.add(messages.get("menu.instruction.variant.anniversary2016.description")).getActor().setWrap(true);
|
||||
table.row();
|
||||
table.add(messages.get("menu.instruction.variant.anniversary2016pm1"));
|
||||
table.add(messages.get("menu.instruction.variant.anniversary2016pm1.description")).getActor().setWrap(true);
|
||||
table.row();
|
||||
table.add(messages.get("menu.instruction.variant.anniversary2021"));
|
||||
table.add(messages.get("menu.instruction.variant.anniversary2021.description")).getActor().setWrap(true);
|
||||
table.row();
|
||||
table.add(messages.get("menu.instruction.variant.anniversary2021pm1"));
|
||||
table.add(messages.get("menu.instruction.variant.anniversary2021pm1.description")).getActor().setWrap(true);
|
||||
table.row();
|
||||
content.addActor(table);
|
||||
}
|
||||
|
||||
private void showFourthPage() {
|
||||
currentPage = 3;
|
||||
reset();
|
||||
|
||||
startSection("menu.instruction.special_card.title");
|
||||
addParagraph("menu.instruction.special_card.intro");
|
||||
|
||||
Table table = new Table(skin).padTop(10);
|
||||
table.defaults().space(10.0f).left().top();
|
||||
table.columnDefaults(1).grow();
|
||||
table.add(new CardActor((Card.DRAGON), atlas));
|
||||
table.add(messages.get("menu.instruction.special_card.dragon")).getActor().setWrap(true);
|
||||
table.row();
|
||||
table.add(new CardActor((Card.FAIRY), atlas));
|
||||
table.add(messages.get("menu.instruction.special_card.fairy")).getActor().setWrap(true);
|
||||
table.row();
|
||||
table.add(new CardActor((Card.BOMB), atlas));
|
||||
table.add(messages.get("menu.instruction.special_card.bomb")).getActor().setWrap(true);
|
||||
table.row();
|
||||
table.add(new CardActor((Card.WEREWOLF), atlas));
|
||||
table.add(messages.get("menu.instruction.special_card.werewolf")).getActor().setWrap(true);
|
||||
table.row();
|
||||
table.add(new CardActor((Card.JUGGLER), atlas));
|
||||
table.add(messages.get("menu.instruction.special_card.juggler")).getActor().setWrap(true);
|
||||
table.row();
|
||||
table.add(new CardActor((Card.CLOUD), atlas));
|
||||
table.add(messages.get("menu.instruction.special_card.cloud")).getActor().setWrap(true);
|
||||
table.row();
|
||||
table.add(new CardActor((Card.CHANGELING), atlas));
|
||||
table.add(messages.get("menu.instruction.special_card.changeling")).getActor().setWrap(true);
|
||||
table.row();
|
||||
content.addActor(table);
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
updateButtons();
|
||||
content.clearChildren();
|
||||
scrollPane.setScrollY(0);
|
||||
scrollPane.updateVisualScroll();
|
||||
}
|
||||
|
||||
private void updateButtons() {
|
||||
previousPageButton.setDisabled(currentPage == 0);
|
||||
nextPageButton.setDisabled(currentPage == MAX_PAGE);
|
||||
}
|
||||
|
||||
private void startSection(String title) {
|
||||
if (content.hasChildren()) {
|
||||
var spacerPre = new Actor();
|
||||
spacerPre.setHeight(20);
|
||||
spacerPre.setName("spacer");
|
||||
content.addActor(spacerPre);
|
||||
}
|
||||
|
||||
var label = new Label(messages.get(title).trim(), skin, "enchanted");
|
||||
label.setAlignment(Align.center);
|
||||
content.addActor(label);
|
||||
|
||||
var spacerPost = new Actor();
|
||||
spacerPost.setHeight(10);
|
||||
spacerPost.setName("spacer");
|
||||
content.addActor(spacerPost);
|
||||
}
|
||||
|
||||
private void startSubsection(String title) {
|
||||
if (content.hasChildren() && !"spacer".equals(content.getChild(content.getChildren().size - 1).getName())) {
|
||||
var spacerPre = new Actor();
|
||||
spacerPre.setHeight(10);
|
||||
spacerPre.setName("spacer");
|
||||
content.addActor(spacerPre);
|
||||
}
|
||||
|
||||
var label = new Label(messages.get(title), skin);
|
||||
label.setFontScale(2.0f);
|
||||
label.setAlignment(Align.center);
|
||||
content.addActor(label);
|
||||
}
|
||||
|
||||
private void addParagraph(String paragraph) {
|
||||
var label = new Label(messages.get(paragraph), skin);
|
||||
label.setAlignment(Align.left);
|
||||
label.setWrap(true);
|
||||
content.addActor(label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
assets.unloadGame();
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.screens;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
|
||||
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
|
||||
|
||||
public class LoadingScreen extends MenuScreen {
|
||||
private final String key;
|
||||
|
||||
@Deprecated
|
||||
public LoadingScreen(WizardGame game) {
|
||||
this(game, "menu.loading.loading");
|
||||
}
|
||||
|
||||
public LoadingScreen(WizardGame game, String key) {
|
||||
super(game);
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
|
||||
var label = new Label(messages.get(key), skin);
|
||||
|
||||
var content = new VerticalGroup();
|
||||
content.setPosition(WizardGame.WIDTH * 0.5f, WizardGame.HEIGHT*0.5f);
|
||||
content.addActor(label);
|
||||
|
||||
stage.addActor(content);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,242 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.screens;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.*;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
|
||||
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
|
||||
import eu.jonahbauer.wizard.client.libgdx.listeners.KeyboardFocusManager;
|
||||
import eu.jonahbauer.wizard.client.libgdx.listeners.ResetErrorListener;
|
||||
import eu.jonahbauer.wizard.client.libgdx.state.Lobby;
|
||||
import eu.jonahbauer.wizard.common.messages.data.SessionData;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Log4j2
|
||||
public class LobbyScreen extends MenuScreen {
|
||||
|
||||
private TextButton buttonBack;
|
||||
private TextButton buttonJoin;
|
||||
private TextButton buttonRejoin;
|
||||
private TextButton buttonCreate;
|
||||
|
||||
private TextField playerName;
|
||||
private Label labelSessionName;
|
||||
private Label labelSessionPlayerCount;
|
||||
private Label labelSessionConfiguration;
|
||||
|
||||
private UUID selectedSession;
|
||||
private List<SessionData> sessions;
|
||||
private ScrollPane sessionListContainer;
|
||||
|
||||
private final ChangeListener listener = new ChangeListener() {
|
||||
@Override
|
||||
public void changed(ChangeEvent event, Actor actor) {
|
||||
if (actor == buttonBack) {
|
||||
game.getClient().execute(Lobby.class, Lobby::disconnect);
|
||||
sfxClick();
|
||||
} else if (actor == buttonJoin) {
|
||||
join();
|
||||
sfxClick();
|
||||
} else if (actor == buttonCreate) {
|
||||
game.getClient().execute(Lobby.class, Lobby::showCreateScreen);
|
||||
sfxClick();
|
||||
} else if (actor == buttonRejoin) {
|
||||
game.getClient().execute(Lobby.class, Lobby::showRejoinScreen);
|
||||
sfxClick();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public LobbyScreen(WizardGame game) {
|
||||
super(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
|
||||
buttonBack = new TextButton(messages.get("menu.lobby.back"), skin);
|
||||
buttonBack.addListener(listener);
|
||||
getButtonGroup().addActor(buttonBack);
|
||||
|
||||
buttonCreate = new TextButton(messages.get("menu.lobby.create"), skin);
|
||||
buttonCreate.addListener(listener);
|
||||
getButtonGroup().addActor(buttonCreate);
|
||||
|
||||
buttonRejoin = new TextButton(messages.get("menu.lobby.rejoin"), skin);
|
||||
buttonRejoin.addListener(listener);
|
||||
getButtonGroup().addActor(buttonRejoin);
|
||||
|
||||
buttonJoin = new TextButton(messages.get("menu.lobby.join"), skin);
|
||||
buttonJoin.addListener(listener);
|
||||
getButtonGroup().addActor(buttonJoin);
|
||||
|
||||
getButtonGroup().setWidth(0.55f * WizardGame.WIDTH);
|
||||
|
||||
sessions = new List<>(skin) {
|
||||
@Override
|
||||
public String toString(SessionData session) {
|
||||
return session.getName();
|
||||
}
|
||||
};
|
||||
sessions.addListener(new ChangeListener() {
|
||||
@Override
|
||||
public void changed(ChangeEvent event, Actor actor) {
|
||||
var selected = sessions.getSelected();
|
||||
updateData(selected);
|
||||
}
|
||||
});
|
||||
|
||||
sessionListContainer = new ScrollPane(sessions, skin);
|
||||
sessionListContainer.layout();
|
||||
|
||||
sessions.addListener(new ResetErrorListener(skin, sessionListContainer));
|
||||
|
||||
var content = new HorizontalGroup().grow().space(20);
|
||||
content.setPosition(0.25f * WizardGame.WIDTH, 0.3f * WizardGame.HEIGHT);
|
||||
content.setSize(0.5f * WizardGame.WIDTH, 400);
|
||||
content.addActor(new Container<>(sessionListContainer).width(0.2f * WizardGame.WIDTH).height(400));
|
||||
content.addActor(createInfoTable());
|
||||
content.layout();
|
||||
|
||||
stage.addActor(content);
|
||||
stage.addCaptureListener(new KeyboardFocusManager(
|
||||
sessions, playerName, buttonBack, buttonCreate, buttonRejoin, buttonJoin
|
||||
));
|
||||
|
||||
buttonBack.setName("button_back");
|
||||
buttonJoin.setName("button_join");
|
||||
buttonJoin.setName("button_rejoin");
|
||||
buttonCreate.setName("button_create");
|
||||
sessions.setName("session_list");
|
||||
playerName.setName("player_name");
|
||||
labelSessionName.setName("session_name");
|
||||
labelSessionConfiguration.setName("session_configuration");
|
||||
labelSessionPlayerCount.setName("session_player_count");
|
||||
}
|
||||
|
||||
private Table createInfoTable() {
|
||||
float infoTableWidth = 0.3f * WizardGame.WIDTH - 20;
|
||||
|
||||
playerName = new TextField(game.storage.playerName, skin);
|
||||
playerName.addListener(new ChangeListener() {
|
||||
@Override
|
||||
public void changed(ChangeEvent event, Actor actor) {
|
||||
game.storage.playerName = playerName.getText();
|
||||
}
|
||||
});
|
||||
playerName.setMaxLength(20);
|
||||
playerName.addListener(new ResetErrorListener(skin));
|
||||
|
||||
labelSessionName = new Label("", skin, "textfield");
|
||||
labelSessionConfiguration = new Label("", skin, "textfield");
|
||||
labelSessionPlayerCount = new Label("", skin, "textfield");
|
||||
|
||||
labelSessionName.setEllipsis(true);
|
||||
labelSessionConfiguration.setEllipsis(true);
|
||||
labelSessionPlayerCount.setEllipsis(true);
|
||||
|
||||
var infoTable = new Table().center().left();
|
||||
infoTable.columnDefaults(0).growX().width(infoTableWidth);
|
||||
infoTable.setSize(infoTableWidth, 400);
|
||||
|
||||
infoTable.add(new Label(messages.get("menu.lobby.player_name.label"), skin)).row();
|
||||
infoTable.add(playerName).row();
|
||||
infoTable.add(new Label(messages.get("menu.lobby.session_name.label"), skin)).row();
|
||||
infoTable.add(labelSessionName).row();
|
||||
infoTable.add(new Label(messages.get("menu.lobby.session_configuration.label"), skin)).row();
|
||||
infoTable.add(labelSessionConfiguration).row();
|
||||
infoTable.add(new Label(messages.get("menu.lobby.session_player_count.label"), skin)).row();
|
||||
infoTable.add(labelSessionPlayerCount).row();
|
||||
|
||||
return infoTable;
|
||||
}
|
||||
|
||||
public void addSession(SessionData session) {
|
||||
this.sessions.getItems().add(session);
|
||||
this.sessions.invalidateHierarchy();
|
||||
}
|
||||
|
||||
public void removeSession(UUID session) {
|
||||
var index = indexOf(session);
|
||||
if (index != -1) {
|
||||
this.sessions.getItems().removeIndex(index);
|
||||
this.sessions.invalidateHierarchy();
|
||||
}
|
||||
|
||||
if (selectedSession != null && selectedSession.equals(session)) {
|
||||
updateData(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void modifySession(SessionData session) {
|
||||
var index = indexOf(session.getUuid());
|
||||
this.sessions.getItems().set(index, session);
|
||||
this.sessions.invalidateHierarchy();
|
||||
|
||||
if (selectedSession != null && selectedSession.equals(session.getUuid())) {
|
||||
updateData(session);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSessions(SessionData... sessions) {
|
||||
var items = this.sessions.getItems();
|
||||
items.clear();
|
||||
items.addAll(sessions);
|
||||
this.selectedSession = null;
|
||||
this.sessions.invalidateHierarchy();
|
||||
}
|
||||
|
||||
private void updateData(SessionData data) {
|
||||
if (data != null) {
|
||||
labelSessionName.setText(data.getName());
|
||||
labelSessionPlayerCount.setText(Integer.toString(data.getPlayerCount()));
|
||||
labelSessionConfiguration.setText(data.getConfiguration().toString());
|
||||
selectedSession = data.getUuid();
|
||||
} else {
|
||||
labelSessionName.setText("");
|
||||
labelSessionPlayerCount.setText("");
|
||||
labelSessionConfiguration.setText("");
|
||||
selectedSession = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void join() {
|
||||
boolean error = false;
|
||||
|
||||
String playerName = this.playerName.getText();
|
||||
if (playerName.isBlank()) {
|
||||
log.warn("Please choose a player name");
|
||||
this.playerName.setStyle(getTextFieldErrorStyle());
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (selectedSession == null) {
|
||||
log.warn("Please select a session.");
|
||||
this.sessionListContainer.setStyle(getScrollPaneErrorStyle());
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
var client = game.getClient();
|
||||
try {
|
||||
client.execute(Lobby.class, (s, c) -> s.joinSession(c, selectedSession, playerName));
|
||||
} catch (IllegalArgumentException e) {
|
||||
// only if session is not known
|
||||
log.warn(e.getMessage());
|
||||
this.sessionListContainer.setStyle(getScrollPaneErrorStyle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int indexOf(UUID session) {
|
||||
var items = this.sessions.getItems();
|
||||
for (int i = 0; i < items.size; i++) {
|
||||
if (items.get(i).getUuid().equals(session)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.screens;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Image;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.Layout;
|
||||
import eu.jonahbauer.wizard.client.libgdx.UiskinAtlas;
|
||||
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
|
||||
import eu.jonahbauer.wizard.client.libgdx.listeners.KeyboardFocusManager;
|
||||
import eu.jonahbauer.wizard.client.libgdx.state.Menu;
|
||||
|
||||
public class MainMenuScreen extends MenuScreen {
|
||||
|
||||
private TextButton buttonPlay;
|
||||
private TextButton buttonQuit;
|
||||
private TextButton buttonInstruction;
|
||||
|
||||
private final ChangeListener listener = new ChangeListener() {
|
||||
@Override
|
||||
public void changed(ChangeEvent event, Actor actor) {
|
||||
if (actor == buttonPlay) {
|
||||
game.getClient().execute(Menu.class, Menu::showConnectScreen);
|
||||
sfxClick();
|
||||
} else if (actor == buttonQuit) {
|
||||
sfxClick();
|
||||
Gdx.app.exit();
|
||||
} else if (actor == buttonInstruction) {
|
||||
game.getClient().execute(Menu.class, Menu::showInstructionScreen);
|
||||
sfxClick();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public MainMenuScreen(WizardGame game) {
|
||||
super(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
|
||||
int width = 160, height = 224;
|
||||
int left = 384, right = 384, top = 384, bottom = 192;
|
||||
var symbols = new Image[]{
|
||||
new Image(skin.getRegion(UiskinAtlas.SYMBOL_0)),
|
||||
new Image(skin.getRegion(UiskinAtlas.SYMBOL_1)),
|
||||
new Image(skin.getRegion(UiskinAtlas.SYMBOL_2)),
|
||||
new Image(skin.getRegion(UiskinAtlas.SYMBOL_3))
|
||||
};
|
||||
symbols[0].setPosition(left, bottom);
|
||||
symbols[1].setPosition(left, WizardGame.HEIGHT - top - height);
|
||||
symbols[2].setPosition(WizardGame.WIDTH - right - width, bottom);
|
||||
symbols[3].setPosition(WizardGame.WIDTH - right - width, WizardGame.HEIGHT - top - height);
|
||||
for (var symbol : symbols) {
|
||||
symbol.setSize(width, height);
|
||||
stage.addActor(symbol);
|
||||
}
|
||||
|
||||
var buttonGroup = new VerticalGroup() {
|
||||
@Override
|
||||
public void layout() {
|
||||
float contentHeight = 0;
|
||||
for (Actor child : getChildren()) {
|
||||
if (child instanceof Layout layout) {
|
||||
contentHeight += layout.getPrefHeight();
|
||||
} else {
|
||||
contentHeight += child.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
space(Math.max(0, (getHeight() - contentHeight) / (getChildren().size - 1)));
|
||||
super.layout();
|
||||
}
|
||||
}.center().fill();
|
||||
buttonGroup.setPosition(WizardGame.WIDTH * 0.25f, 192 + 60f);
|
||||
buttonGroup.setSize(WizardGame.WIDTH * 0.5f, 504 - 120f);
|
||||
stage.addActor(buttonGroup);
|
||||
|
||||
buttonPlay = new TextButton(messages.get("menu.main.play"), skin);
|
||||
buttonPlay.addListener(listener);
|
||||
buttonGroup.addActor(buttonPlay);
|
||||
|
||||
buttonInstruction = new TextButton(messages.get("menu.main.instructions"), skin);
|
||||
buttonInstruction.addListener(listener);
|
||||
buttonGroup.addActor(buttonInstruction);
|
||||
|
||||
buttonQuit = new TextButton(messages.get("menu.main.quit"), skin);
|
||||
buttonQuit.addListener(listener);
|
||||
buttonGroup.addActor(buttonQuit);
|
||||
|
||||
stage.addCaptureListener(new KeyboardFocusManager(buttonPlay, buttonInstruction, buttonQuit));
|
||||
|
||||
buttonPlay.setName("button_player");
|
||||
buttonInstruction.setName("button_instruction");
|
||||
buttonQuit.setName("button_quit");
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.screens;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.*;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.Layout;
|
||||
import eu.jonahbauer.wizard.client.libgdx.UiskinAtlas;
|
||||
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
|
||||
public abstract class MenuScreen extends WizardScreen {
|
||||
private Image[] corners;
|
||||
@Getter(value = AccessLevel.PROTECTED, lazy = true)
|
||||
private final HorizontalGroup buttonGroup = createButtonGroup();
|
||||
|
||||
@Getter(value = AccessLevel.PROTECTED, lazy = true)
|
||||
private final TextField.TextFieldStyle textFieldErrorStyle = skin.get("error", TextField.TextFieldStyle.class);
|
||||
@Getter(value = AccessLevel.PROTECTED, lazy = true)
|
||||
private final ScrollPane.ScrollPaneStyle scrollPaneErrorStyle = skin.get("error", ScrollPane.ScrollPaneStyle.class);
|
||||
@Getter(value = AccessLevel.PROTECTED, lazy = true)
|
||||
private final SelectBox.SelectBoxStyle selectBoxErrorStyle = skin.get("error", SelectBox.SelectBoxStyle.class);
|
||||
|
||||
@Getter(AccessLevel.PROTECTED)
|
||||
private Image title;
|
||||
|
||||
public MenuScreen(WizardGame game) {
|
||||
super(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
|
||||
corners = new Image[4];
|
||||
corners[0] = new Image(skin.getRegion(UiskinAtlas.CORNER_TOP_LEFT));
|
||||
corners[1] = new Image(skin.getRegion(UiskinAtlas.CORNER_BOTTOM_LEFT));
|
||||
corners[2] = new Image(skin.getRegion(UiskinAtlas.CORNER_BOTTOM_RIGHT));
|
||||
corners[3] = new Image(skin.getRegion(UiskinAtlas.CORNER_TOP_RIGHT));
|
||||
for (var corner : corners) {
|
||||
stage.addActor(corner);
|
||||
}
|
||||
|
||||
title = new Image(skin.getRegion(UiskinAtlas.TITLE));
|
||||
title.setSize(810, 192);
|
||||
title.setPosition(555, WizardGame.HEIGHT - 192 - 96);
|
||||
stage.addActor(title);
|
||||
}
|
||||
|
||||
private HorizontalGroup createButtonGroup() {
|
||||
var group = new HorizontalGroup() {
|
||||
@Override
|
||||
public void layout() {
|
||||
float contentWidth = 0;
|
||||
for (Actor child : getChildren()) {
|
||||
if (child instanceof Layout layout) {
|
||||
contentWidth += layout.getPrefWidth();
|
||||
} else {
|
||||
contentWidth += child.getWidth();
|
||||
}
|
||||
}
|
||||
|
||||
space(Math.max(0, (getWidth() - contentWidth) / (getChildren().size - 1)));
|
||||
super.layout();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sizeChanged() {
|
||||
setPosition((WizardGame.WIDTH - getWidth()) / 2f, getY());
|
||||
}
|
||||
};
|
||||
group.setSize(WizardGame.WIDTH * 0.45f, 125);
|
||||
group.setY(WizardGame.HEIGHT * 0.15f);
|
||||
group.center();
|
||||
stage.addActor(group);
|
||||
return group;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resize(int width, int height) {
|
||||
super.resize(width, height);
|
||||
|
||||
var worldWidth = viewport.getWorldWidth();
|
||||
var worldHeight = viewport.getWorldHeight();
|
||||
var offsetX = (worldWidth - WizardGame.WIDTH) / 2;
|
||||
var offsetY = (worldHeight - WizardGame.HEIGHT) / 2;
|
||||
|
||||
corners[0].setPosition(- offsetX, worldHeight - corners[0].getHeight() - offsetY);
|
||||
corners[1].setPosition(- offsetX, - offsetY);
|
||||
corners[2].setPosition(worldWidth - corners[2].getWidth() - offsetX, - offsetY);
|
||||
corners[3].setPosition(worldWidth - corners[3].getWidth() - offsetX, worldHeight - corners[3].getHeight() - offsetY);
|
||||
}
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.screens;
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.*;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
|
||||
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
|
||||
import eu.jonahbauer.wizard.client.libgdx.listeners.KeyboardFocusManager;
|
||||
import eu.jonahbauer.wizard.client.libgdx.listeners.ResetErrorListener;
|
||||
import eu.jonahbauer.wizard.client.libgdx.state.Lobby;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Log4j2
|
||||
public class RejoinScreen extends MenuScreen {
|
||||
|
||||
private TextButton buttonBack;
|
||||
private TextButton buttonContinue;
|
||||
|
||||
private TextField sessionUUID;
|
||||
private TextField playerUUID;
|
||||
private TextField secret;
|
||||
|
||||
private final ChangeListener listener = new ChangeListener() {
|
||||
@Override
|
||||
public void changed(ChangeEvent event, Actor actor) {
|
||||
if (actor == buttonBack) {
|
||||
game.getClient().execute(Lobby.class, Lobby::showListScreen);
|
||||
sfxClick();
|
||||
} else if (actor == buttonContinue) {
|
||||
rejoin();
|
||||
sfxClick();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public RejoinScreen(WizardGame game) {
|
||||
super(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
|
||||
var credentials = game.storage.credentials;
|
||||
|
||||
buttonBack = new TextButton(messages.get("menu.rejoin.back"), skin);
|
||||
buttonBack.addListener(listener);
|
||||
getButtonGroup().addActor(buttonBack);
|
||||
|
||||
buttonContinue = new TextButton(messages.get("menu.rejoin.continue"), skin);
|
||||
buttonContinue.addListener(listener);
|
||||
getButtonGroup().addActor(buttonContinue);
|
||||
|
||||
var errorListener = new ResetErrorListener(skin);
|
||||
|
||||
sessionUUID = new TextField(credentials != null ? credentials.session().toString() : null , skin);
|
||||
sessionUUID.setPosition(WizardGame.WIDTH * 0.3f, WizardGame.HEIGHT * 0.5f);
|
||||
sessionUUID.setSize(0.4f * WizardGame.WIDTH, 64);
|
||||
sessionUUID.addListener(errorListener);
|
||||
sessionUUID.setProgrammaticChangeEvents(true);
|
||||
|
||||
playerUUID = new TextField(credentials != null ? credentials.player().toString() : null, skin);
|
||||
playerUUID.setPosition(WizardGame.WIDTH * 0.3f, WizardGame.HEIGHT * 0.45f);
|
||||
playerUUID.setSize(0.4f * WizardGame.WIDTH, 64);
|
||||
playerUUID.addListener(errorListener);
|
||||
|
||||
secret = new TextField(credentials != null ? credentials.secret() : null, skin);
|
||||
secret.setPosition(WizardGame.WIDTH * 0.3f, WizardGame.HEIGHT * 0.4f);
|
||||
secret.setSize(0.4f * WizardGame.WIDTH, 64);
|
||||
secret.addListener(errorListener);
|
||||
|
||||
var contentTable = new Table(skin).center().left();
|
||||
contentTable.columnDefaults(0).growX().width(0.4f * WizardGame.WIDTH - 20);
|
||||
contentTable.setSize(0.4f * WizardGame.WIDTH - 20, 400);
|
||||
contentTable.setPosition(WizardGame.WIDTH * 0.3f, WizardGame.HEIGHT * 0.3f);
|
||||
|
||||
contentTable.add(messages.get("menu.rejoin.session_uuid.label")).row();
|
||||
contentTable.add(sessionUUID).row();
|
||||
contentTable.add(messages.get("menu.rejoin.player_uuid.label")).row();
|
||||
contentTable.add(playerUUID).row();
|
||||
contentTable.add(messages.get("menu.rejoin.player_secret.label")).row();
|
||||
contentTable.add(secret).row();
|
||||
|
||||
stage.addActor(contentTable);
|
||||
stage.addCaptureListener(new KeyboardFocusManager(sessionUUID, playerUUID, secret, buttonBack, buttonContinue));
|
||||
|
||||
buttonBack.setName("button_back");
|
||||
buttonContinue.setName("button_continue");
|
||||
sessionUUID.setName("session_uuid");
|
||||
playerUUID.setName("player_uuid");
|
||||
secret.setName("player_secret");
|
||||
}
|
||||
|
||||
private void rejoin() {
|
||||
boolean error = false;
|
||||
|
||||
String sessionUUIDText = this.sessionUUID.getText();
|
||||
UUID sessionUUID = null;
|
||||
if (sessionUUIDText.isBlank()) {
|
||||
log.warn("Please enter the session uuid.");
|
||||
this.sessionUUID.setStyle(getTextFieldErrorStyle());
|
||||
error = true;
|
||||
} else {
|
||||
try {
|
||||
sessionUUID = UUID.fromString(sessionUUIDText);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Please enter a valid session uuid.");
|
||||
this.sessionUUID.setStyle(getTextFieldErrorStyle());
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
String playerUUIDText = this.playerUUID.getText();
|
||||
UUID playerUUID = null;
|
||||
if (playerUUIDText.isBlank()) {
|
||||
log.warn("Please enter the player uuid.");
|
||||
this.playerUUID.setStyle(getTextFieldErrorStyle());
|
||||
error = true;
|
||||
} else {
|
||||
try {
|
||||
playerUUID = UUID.fromString(playerUUIDText);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Please enter a valid player uuid.");
|
||||
this.playerUUID.setStyle(getTextFieldErrorStyle());
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
String playerSecret = this.secret.getText();
|
||||
if (playerSecret.isBlank()) {
|
||||
log.warn("Please enter the player secret.");
|
||||
this.secret.setStyle(getTextFieldErrorStyle());
|
||||
error = true;
|
||||
}
|
||||
|
||||
var fPlayerUUID = playerUUID;
|
||||
var fSessionUUID = sessionUUID;
|
||||
if (!error) {
|
||||
var client = game.getClient();
|
||||
try {
|
||||
client.execute(Lobby.class, (s, c) -> s.rejoinSession(c, fSessionUUID, fPlayerUUID, playerSecret));
|
||||
} catch (IllegalArgumentException e) {
|
||||
// only if session is not known
|
||||
log.warn(e.getMessage());
|
||||
this.sessionUUID.setStyle(getTextFieldErrorStyle());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.screens;
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.Batch;
|
||||
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
||||
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.*;
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
|
||||
import com.badlogic.gdx.utils.Align;
|
||||
import eu.jonahbauer.wizard.client.libgdx.UiskinAtlas;
|
||||
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
|
||||
import eu.jonahbauer.wizard.client.libgdx.listeners.KeyboardFocusManager;
|
||||
import eu.jonahbauer.wizard.client.libgdx.state.Session;
|
||||
import eu.jonahbauer.wizard.common.messages.data.PlayerData;
|
||||
import eu.jonahbauer.wizard.common.model.Configuration;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class WaitingScreen extends MenuScreen {
|
||||
|
||||
private TextButton buttonLeave;
|
||||
private TextButton buttonReady;
|
||||
|
||||
private Label labelSessionName;
|
||||
private Label labelSessionUUID;
|
||||
private Label labelSessionConfiguration;
|
||||
private Label labelPlayerName;
|
||||
|
||||
private List<PlayerData> players;
|
||||
|
||||
private final ChangeListener listener = new ChangeListener() {
|
||||
@Override
|
||||
public void changed(ChangeEvent event, Actor actor) {
|
||||
if (actor == buttonLeave) {
|
||||
game.getClient().execute(Session.class, Session::leave);
|
||||
sfxClick();
|
||||
} else if (actor == buttonReady) {
|
||||
game.getClient().execute(Session.class, Session::toggleReady);
|
||||
sfxClick();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public WaitingScreen(WizardGame game) {
|
||||
super(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
|
||||
buttonLeave = new TextButton(messages.get("menu.waiting.leave"), skin);
|
||||
buttonLeave.addListener(listener);
|
||||
getButtonGroup().addActor(buttonLeave);
|
||||
|
||||
buttonReady = new TextButton(messages.get("menu.waiting.ready"), skin);
|
||||
buttonReady.addListener(listener);
|
||||
getButtonGroup().addActor(buttonReady);
|
||||
|
||||
getButtonGroup().setWidth(0.55f * WizardGame.WIDTH);
|
||||
|
||||
players = new List<>(skin) {
|
||||
private final TextureRegion ready = skin.getRegion(UiskinAtlas.READY);
|
||||
private final TextureRegion notReady = skin.getRegion(UiskinAtlas.NOT_READY);
|
||||
|
||||
@Override
|
||||
public String toString(PlayerData player) {
|
||||
return player.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
protected GlyphLayout drawItem(Batch batch, BitmapFont font, int index, PlayerData item, float x, float y, float width) {
|
||||
String string = toString(item);
|
||||
var height = font.getCapHeight();
|
||||
if (item.isReady()) {
|
||||
batch.draw(ready, x, y - height, height, height);
|
||||
} else {
|
||||
batch.draw(notReady, x, y - height, height, height);
|
||||
}
|
||||
return font.draw(batch, string, x + height + 8, y, 0, string.length(), width - height - 8, Align.left, false, "...");
|
||||
}
|
||||
};
|
||||
|
||||
var listContainer = new ScrollPane(players, skin);
|
||||
listContainer.layout();
|
||||
|
||||
var content = new HorizontalGroup().grow().space(20);
|
||||
content.setPosition(0.25f * WizardGame.WIDTH, 0.3f * WizardGame.HEIGHT);
|
||||
content.setSize(0.5f * WizardGame.WIDTH, 400);
|
||||
content.addActor(new Container<>(listContainer).width(0.2f * WizardGame.WIDTH).height(400));
|
||||
content.addActor(createInfoTable());
|
||||
content.layout();
|
||||
|
||||
stage.addActor(content);
|
||||
stage.addCaptureListener(new KeyboardFocusManager(buttonLeave, buttonReady));
|
||||
|
||||
buttonLeave.setName("button_leave");
|
||||
buttonReady.setName("button_ready");
|
||||
labelSessionName.setName("session_name");
|
||||
labelSessionUUID.setName("session_uuid");
|
||||
labelSessionConfiguration.setName("session_configuration");
|
||||
labelPlayerName.setName("player_name");
|
||||
}
|
||||
|
||||
private Table createInfoTable() {
|
||||
float infoTableWidth = 0.3f * WizardGame.WIDTH - 20;
|
||||
|
||||
labelSessionName = new Label("", skin, "textfield");
|
||||
labelSessionUUID = new Label("", skin, "textfield");
|
||||
labelSessionConfiguration = new Label("", skin, "textfield");
|
||||
labelPlayerName = new Label("", skin, "textfield");
|
||||
|
||||
labelSessionName.setEllipsis(true);
|
||||
labelSessionUUID.setEllipsis(true);
|
||||
labelSessionConfiguration.setEllipsis(true);
|
||||
labelPlayerName.setEllipsis(true);
|
||||
|
||||
var infoTable = new Table(skin).center().left();
|
||||
infoTable.columnDefaults(0).growX().width(infoTableWidth);
|
||||
infoTable.setSize(infoTableWidth, 400);
|
||||
|
||||
infoTable.add(messages.get("menu.waiting.session_name.label")).row();
|
||||
infoTable.add(labelSessionName).row();
|
||||
infoTable.add(messages.get("menu.waiting.session_uuid.label")).row();
|
||||
infoTable.add(labelSessionUUID).row();
|
||||
infoTable.add(messages.get("menu.waiting.session_configuration.label")).row();
|
||||
infoTable.add(labelSessionConfiguration).row();
|
||||
infoTable.add(messages.get("menu.waiting.player_name.label")).row();
|
||||
infoTable.add(labelPlayerName).row();
|
||||
|
||||
return infoTable;
|
||||
}
|
||||
|
||||
public void setSending(boolean sending) {
|
||||
buttonReady.setDisabled(sending);
|
||||
}
|
||||
|
||||
public void setReady(boolean ready) {
|
||||
buttonReady.setText(messages.get(ready ? "menu.waiting.not_ready" : "menu.waiting.ready"));
|
||||
}
|
||||
|
||||
public void addPlayer(PlayerData player) {
|
||||
this.players.getItems().add(player);
|
||||
this.players.invalidateHierarchy();
|
||||
}
|
||||
|
||||
public void removePlayer(UUID player) {
|
||||
var index = indexOf(player);
|
||||
if (index != -1) {
|
||||
this.players.getItems().removeIndex(index);
|
||||
this.players.invalidateHierarchy();
|
||||
}
|
||||
}
|
||||
|
||||
public void modifyPlayer(PlayerData data) {
|
||||
var index = indexOf(data.getUuid());
|
||||
this.players.getItems().set(index, data);
|
||||
this.players.invalidateHierarchy();
|
||||
}
|
||||
|
||||
public void setPlayers(PlayerData... players) {
|
||||
var items = this.players.getItems();
|
||||
items.clear();
|
||||
items.addAll(players);
|
||||
this.players.invalidateHierarchy();
|
||||
}
|
||||
|
||||
public void setSession(UUID uuid, String name, Configuration configuration) {
|
||||
this.labelSessionName.setText(name);
|
||||
this.labelSessionUUID.setText(uuid.toString());
|
||||
this.labelSessionConfiguration.setText(configuration.toString());
|
||||
}
|
||||
|
||||
public void setPlayerName(String name) {
|
||||
this.labelPlayerName.setText(name);
|
||||
}
|
||||
|
||||
private int indexOf(UUID player) {
|
||||
var items = this.players.getItems();
|
||||
for (int i = 0; i < items.size; i++) {
|
||||
if (items.get(i).getUuid().equals(player)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.screens;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.Screen;
|
||||
import com.badlogic.gdx.audio.Sound;
|
||||
import com.badlogic.gdx.graphics.GL20;
|
||||
import com.badlogic.gdx.graphics.Texture;
|
||||
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
||||
import com.badlogic.gdx.scenes.scene2d.Stage;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Image;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
|
||||
import com.badlogic.gdx.utils.I18NBundle;
|
||||
import com.badlogic.gdx.utils.Scaling;
|
||||
import com.badlogic.gdx.utils.viewport.ExtendViewport;
|
||||
import com.badlogic.gdx.utils.viewport.Viewport;
|
||||
import eu.jonahbauer.wizard.client.libgdx.UiskinAtlas;
|
||||
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
|
||||
import eu.jonahbauer.wizard.client.libgdx.listeners.AutoFocusListener;
|
||||
import eu.jonahbauer.wizard.client.libgdx.listeners.ButtonKeyListener;
|
||||
import eu.jonahbauer.wizard.client.libgdx.util.WizardAssetManager;
|
||||
import org.jetbrains.annotations.MustBeInvokedByOverriders;
|
||||
|
||||
public abstract class WizardScreen implements Screen {
|
||||
protected final WizardGame game;
|
||||
protected final WizardAssetManager assets;
|
||||
|
||||
protected Skin skin;
|
||||
protected I18NBundle messages;
|
||||
|
||||
protected Stage stage;
|
||||
protected Viewport viewport;
|
||||
|
||||
private Image background;
|
||||
private Sound sfxClick;
|
||||
|
||||
protected float offsetX;
|
||||
protected float offsetY;
|
||||
protected float worldWidth;
|
||||
protected float worldHeight;
|
||||
|
||||
protected WizardScreen(WizardGame game) {
|
||||
this.game = game;
|
||||
this.assets = game.assets;
|
||||
}
|
||||
|
||||
@MustBeInvokedByOverriders
|
||||
protected void load() {
|
||||
messages = assets.get(WizardAssetManager.MESSAGES);
|
||||
skin = assets.get(WizardAssetManager.SKIN);
|
||||
skin.getAll(BitmapFont.class).forEach(entry -> {
|
||||
entry.value.getRegion().getTexture().setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
|
||||
});
|
||||
|
||||
viewport = new ExtendViewport(WizardGame.WIDTH, WizardGame.HEIGHT);
|
||||
|
||||
stage = new Stage(viewport);
|
||||
stage.addListener(new ButtonKeyListener());
|
||||
stage.addListener(new AutoFocusListener());
|
||||
stage.setDebugAll(WizardGame.DEBUG);
|
||||
|
||||
Gdx.input.setInputProcessor(stage);
|
||||
|
||||
sfxClick = assets.get(WizardAssetManager.SFX_CLICK);
|
||||
}
|
||||
|
||||
@Override
|
||||
@MustBeInvokedByOverriders
|
||||
public void show() {
|
||||
load();
|
||||
|
||||
background = new Image(skin.getRegion(UiskinAtlas.BACKGROUND));
|
||||
background.setScaling(Scaling.fill);
|
||||
background.setPosition(0,0);
|
||||
stage.addActor(background);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void render(float delta) {
|
||||
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT | (Gdx.graphics.getBufferFormat().coverageSampling?GL20.GL_COVERAGE_BUFFER_BIT_NV:0));
|
||||
|
||||
stage.act(delta);
|
||||
stage.draw();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resize(int width, int height) {
|
||||
viewport.update(width, height, true);
|
||||
|
||||
worldWidth = viewport.getWorldWidth();
|
||||
worldHeight = viewport.getWorldHeight();
|
||||
offsetX = (worldWidth - WizardGame.WIDTH) / 2;
|
||||
offsetY = (worldHeight - WizardGame.HEIGHT) / 2;
|
||||
|
||||
stage.getCamera().position.x -= offsetX;
|
||||
stage.getCamera().position.y -= offsetY;
|
||||
background.setBounds(-offsetX, -offsetY, worldWidth, worldHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause() {}
|
||||
|
||||
@Override
|
||||
public void resume() {}
|
||||
|
||||
@Override
|
||||
public void hide() {}
|
||||
|
||||
@Override
|
||||
@MustBeInvokedByOverriders
|
||||
public void dispose() {
|
||||
stage.dispose();
|
||||
}
|
||||
|
||||
protected void sfxClick() {
|
||||
sfxClick.play(0.6f);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.libgdx.Client;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.jetbrains.annotations.MustBeInvokedByOverriders;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Log4j2
|
||||
public abstract class Awaiting extends BaseState implements ClientState {
|
||||
private static final int TIMEOUT_MILLIS = 10_000;
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@MustBeInvokedByOverriders
|
||||
public Optional<ClientState> onEnter(Client client) {
|
||||
client.timeout(this, TIMEOUT_MILLIS);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onTimeout(Client client) {
|
||||
log.error("Timed out. Returning to menu.");
|
||||
return Optional.of(new Menu());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.libgdx.Client;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.LoadingScreen;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Log4j2
|
||||
public final class AwaitingConnection extends Awaiting {
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onEnter(Client client) {
|
||||
log.info("Awaiting connection...");
|
||||
if (!client.isError()) showScreen(client);
|
||||
return super.onEnter(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onErrorDismissed(Client client) {
|
||||
showScreen(client);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onOpen(Client client) {
|
||||
log.info("Connection established.");
|
||||
return Optional.of(new AwaitingJoinLobby());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||
return unexpectedMessage(client, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onClose(Client client, int code, String reason, boolean remote) {
|
||||
log.error("Connection could not be established. (code={}, reason={}, remote={})", code, reason, remote);
|
||||
showErrorScreen(client, "Connection could not be established. (code=%d, reason=%s, remote=%b)".formatted(code, reason, remote));
|
||||
return Optional.of(new Menu());
|
||||
}
|
||||
|
||||
private void showScreen(Client client) {
|
||||
client.getGame().setScreen(new LoadingScreen(client.getGame(), "menu.loading.connecting"));
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.libgdx.Client;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.LoadingScreen;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.ObserverMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.AckMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.GameMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.StartingGameMessage;
|
||||
import eu.jonahbauer.wizard.common.model.Configuration;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Log4j2
|
||||
@RequiredArgsConstructor
|
||||
public final class AwaitingGameLog extends BaseState {
|
||||
private static final int TIMEOUT_MILLIS = 10_000;
|
||||
|
||||
private final UUID self;
|
||||
private final UUID session;
|
||||
private final String sessionName;
|
||||
private final Configuration configuration;
|
||||
|
||||
private final LinkedHashMap<UUID, String> players;
|
||||
|
||||
private final List<ObserverMessage> messages = new ArrayList<>();
|
||||
|
||||
private boolean started = false;
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onEnter(Client client) {
|
||||
log.info("Waiting for game log...");
|
||||
if (!client.isError()) showScreen(client);
|
||||
client.timeout(this, TIMEOUT_MILLIS);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onErrorDismissed(Client client) {
|
||||
showScreen(client);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||
if (!started) {
|
||||
if (message instanceof StartingGameMessage) {
|
||||
started = true;
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return unexpectedMessage(client, message);
|
||||
}
|
||||
} else if (message instanceof GameMessage gameMessage) {
|
||||
messages.add(gameMessage.getObserverMessage());
|
||||
return Optional.empty();
|
||||
} else if (message instanceof AckMessage) {
|
||||
var game = new Game(self, session, sessionName, configuration, players);
|
||||
game.init(messages);
|
||||
return Optional.of(game);
|
||||
} else {
|
||||
return unexpectedMessage(client, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void showScreen(Client client) {
|
||||
client.getGame().setScreen(new LoadingScreen(client.getGame(), "menu.loading.rejoining"));
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.libgdx.Client;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.LoadingScreen;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.SessionListMessage;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Log4j2
|
||||
public final class AwaitingJoinLobby extends Awaiting {
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onEnter(Client client) {
|
||||
log.info("Waiting for session list...");
|
||||
if (!client.isError()) showScreen(client);
|
||||
return super.onEnter(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onErrorDismissed(Client client) {
|
||||
showScreen(client);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||
if (message instanceof SessionListMessage list) {
|
||||
log.info("There are {} open sessions.", list.getSessions().size());
|
||||
return Optional.of(new Lobby(list.getSessions()));
|
||||
} else {
|
||||
return unexpectedMessage(client, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void showScreen(Client client) {
|
||||
client.getGame().setScreen(new LoadingScreen(client.getGame(), "menu.loading.joining_lobby"));
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.libgdx.Client;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.LoadingScreen;
|
||||
import eu.jonahbauer.wizard.client.libgdx.util.SavedData;
|
||||
import eu.jonahbauer.wizard.common.messages.data.SessionData;
|
||||
import eu.jonahbauer.wizard.common.messages.server.*;
|
||||
import eu.jonahbauer.wizard.common.model.Configuration;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Log4j2
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class AwaitingJoinSession extends Awaiting {
|
||||
|
||||
private final @Nullable UUID session;
|
||||
private final @NotNull String sessionName;
|
||||
private final @NotNull Configuration configuration;
|
||||
private final @NotNull Source source;
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onEnter(Client client) {
|
||||
log.info("Waiting for acknowledgment...");
|
||||
if (!client.isError()) showScreen(client);
|
||||
return super.onEnter(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onErrorDismissed(Client client) {
|
||||
showScreen(client);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||
if (message instanceof SessionJoinedMessage joined) {
|
||||
var session = joined.getSession();
|
||||
if (this.session != null && !this.session.equals(session)) {
|
||||
return unexpectedMessage(client, message);
|
||||
} else {
|
||||
var players = joined.getPlayers();
|
||||
var player = joined.getPlayer();
|
||||
var secret = joined.getSecret();
|
||||
|
||||
log.info("There are {} players in this session.", players.size());
|
||||
log.info("Your uuid is {}.", player);
|
||||
log.info("Your secret is {}.", secret);
|
||||
|
||||
client.getGame().storage.credentials = new SavedData.SessionCredentials(session, player, secret);
|
||||
|
||||
if (source == Source.REJOIN) {
|
||||
var playerMap = new LinkedHashMap<UUID, String>();
|
||||
players.forEach(p -> playerMap.put(p.getUuid(), p.getName()));
|
||||
return Optional.of(new AwaitingGameLog(
|
||||
player,
|
||||
session,
|
||||
sessionName,
|
||||
configuration,
|
||||
playerMap
|
||||
));
|
||||
} else {
|
||||
return Optional.of(new Session(
|
||||
new SessionData(session, sessionName, -1, configuration),
|
||||
players,
|
||||
player
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if (message instanceof NackMessage nack) {
|
||||
switch (nack.getCode()) {
|
||||
case NackMessage.GAME_ALREADY_STARTED -> log.error("Game has already started.");
|
||||
case NackMessage.SESSION_FULL -> log.error("The session is full.");
|
||||
case NackMessage.SESSION_NOT_FOUND -> log.error("Session not found.");
|
||||
case NackMessage.PLAYER_NAME_TAKEN -> log.error("Player name already taken.");
|
||||
case NackMessage.PLAYER_NAME_NOT_ALLOWED -> log.error("Player name not allowed.");
|
||||
case NackMessage.SESSION_NAME_TAKEN -> log.error("Session name already taken.");
|
||||
case NackMessage.SESSION_NAME_NOT_ALLOWED -> log.error("Session name not allowed.");
|
||||
default -> log.error("Nack {}: {}", nack.getCode(), nack.getMessage());
|
||||
}
|
||||
showErrorScreen(client, nack.getMessage());
|
||||
return Optional.of(new AwaitingJoinLobby());
|
||||
} else if (message instanceof SessionModifiedMessage || message instanceof SessionRemovedMessage) {
|
||||
// drop
|
||||
log.debug("Dropped message {}.", message);
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return unexpectedMessage(client, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void showScreen(Client client) {
|
||||
client.getGame().setScreen(new LoadingScreen(client.getGame(), "menu.loading.joining_session"));
|
||||
}
|
||||
|
||||
public enum Source {
|
||||
JOIN, CREATE, REJOIN
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.libgdx.Client;
|
||||
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.ErrorScreen;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Log4j2
|
||||
public abstract class BaseState implements ClientState {
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onEnter(Client context) {
|
||||
return ClientState.super.onEnter(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onOpen(Client client) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onClose(Client client, int code, String reason, boolean remote) {
|
||||
if (remote) {
|
||||
log.error("Lost connection (code={}, reason={})", code, reason);
|
||||
} else {
|
||||
log.info("Connection closed (code={}, reason={})", code, reason);
|
||||
}
|
||||
return Optional.of(new Menu());
|
||||
}
|
||||
|
||||
protected Optional<ClientState> unexpectedMessage(Client client, ServerMessage message) {
|
||||
// return to menu on unexpected message
|
||||
log.fatal("Unexpected message {}. Returning to menu.", message);
|
||||
showErrorScreen(client, "Unexpected message %s. Returning to menu.".formatted(message));
|
||||
return Optional.of(new Menu());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void showErrorScreen(Client client, String message) {
|
||||
WizardGame game = client.getGame();
|
||||
client.setError(true);
|
||||
game.setScreen(new ErrorScreen(game, message));
|
||||
}
|
||||
|
||||
public Optional<ClientState> dismissErrorScreen(Client client) {
|
||||
client.setError(false);
|
||||
return onErrorDismissed(client);
|
||||
}
|
||||
|
||||
protected abstract Optional<ClientState> onErrorDismissed(Client client);
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.libgdx.Client;
|
||||
import eu.jonahbauer.wizard.common.machine.TimeoutState;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ClientState extends TimeoutState<ClientState, Client> {
|
||||
Optional<ClientState> onOpen(Client client);
|
||||
|
||||
Optional<ClientState> onMessage(Client client, ServerMessage message);
|
||||
|
||||
Optional<ClientState> onClose(Client client, int code, String reason, boolean remote);
|
||||
|
||||
}
|
@ -0,0 +1,626 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.libgdx.Client;
|
||||
import eu.jonahbauer.wizard.client.libgdx.actions.overlay.InteractionOverlay;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
||||
import eu.jonahbauer.wizard.client.libgdx.util.Pair;
|
||||
import eu.jonahbauer.wizard.common.messages.client.InteractionMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.data.PlayerData;
|
||||
import eu.jonahbauer.wizard.common.messages.data.SessionData;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.*;
|
||||
import eu.jonahbauer.wizard.common.messages.player.*;
|
||||
import eu.jonahbauer.wizard.common.messages.server.AckMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.GameMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.NackMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import eu.jonahbauer.wizard.common.model.Configuration;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Range;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.*;
|
||||
|
||||
@Log4j2
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class Game extends BaseState {
|
||||
private final UUID self;
|
||||
private final UUID session;
|
||||
private final String sessionName;
|
||||
private final Configuration configuration;
|
||||
|
||||
private List<ObserverMessage> pendingMessages;
|
||||
|
||||
private final LinkedHashMap<UUID, String> players;
|
||||
private final Map<Integer, @Unmodifiable Map<UUID, Integer>> scores = new HashMap<>();
|
||||
private final Map<Integer, Map<UUID, Integer>> predictions = new HashMap<>();
|
||||
|
||||
private int round = -1;
|
||||
private final Map<UUID, List<Card>> hands = new HashMap<>();
|
||||
private final Map<UUID, List<List<Card>>> tricks = new HashMap<>();
|
||||
|
||||
private int trick = -1;
|
||||
private final List<Pair<UUID, Card>> stack = new ArrayList<>();
|
||||
private Interaction currentInteraction;
|
||||
private int pendingClearActivePlayer = 0;
|
||||
|
||||
private Card trumpCard;
|
||||
private Card.Suit trumpSuit;
|
||||
|
||||
private @Nullable GameScreen gameScreen;
|
||||
private final AtomicBoolean sending = new AtomicBoolean(false);
|
||||
|
||||
private boolean juggling;
|
||||
private Card juggleCard;
|
||||
private boolean werewolf;
|
||||
|
||||
private int finishing = 0;
|
||||
|
||||
public void init(List<ObserverMessage> messages) {
|
||||
pendingMessages = messages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onEnter(Client client) {
|
||||
var out = handlePendingMessages(client);
|
||||
if (out.isPresent()) return out;
|
||||
|
||||
gameScreen = new GameScreen(client.getGame(), self, players);
|
||||
client.getGame().setScreen(gameScreen);
|
||||
updateScreen();
|
||||
return super.onEnter(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onErrorDismissed(Client client) {
|
||||
return Optional.of(new Menu());
|
||||
}
|
||||
|
||||
//<editor-fold desc="onMessage">
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||
try {
|
||||
if (message instanceof GameMessage game) {
|
||||
var observerMessage = game.getObserverMessage();
|
||||
return onMessage(client, observerMessage);
|
||||
} else if (message instanceof NackMessage nack) {
|
||||
return onNackMessage(client, nack);
|
||||
} else if (message instanceof AckMessage) {
|
||||
onAckMessage();
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return unexpectedMessage(client, message);
|
||||
}
|
||||
} finally {
|
||||
executeDelayedFinishInteraction();
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<ClientState> onMessage(Client client, ObserverMessage message) {
|
||||
if (finishing != 0) return onMessageWhileFinishing(client, message);
|
||||
|
||||
if (message instanceof StateMessage state) {
|
||||
switch (state.getState()) {
|
||||
case "starting_round" -> {
|
||||
return onStartRound(client);
|
||||
}
|
||||
case "starting_trick" -> onStartTrick();
|
||||
case "juggling" -> onJuggle();
|
||||
case "finishing_round" -> onFinishingRound();
|
||||
case "finished" -> {
|
||||
return onFinished();
|
||||
}
|
||||
case "error" -> {
|
||||
return onError(client);
|
||||
}
|
||||
}
|
||||
} else if (message instanceof HandMessage hand) {
|
||||
onHandMessage(hand.getPlayer(), hand.getHand());
|
||||
} else if (message instanceof PredictionMessage prediction) {
|
||||
onPredictionMessage(prediction.getPlayer(), prediction.getPrediction());
|
||||
} else if (message instanceof TrumpMessage trump) {
|
||||
onTrumpMessage(trump.getCard(), trump.getSuit());
|
||||
} else if (message instanceof TrickMessage trick) {
|
||||
onTrickMessage(trick.getPlayer(), trick.getCards());
|
||||
} else if (message instanceof CardMessage card) {
|
||||
onCardMessage(card.getPlayer(), card.getCard());
|
||||
} else if (message instanceof ScoreMessage score) {
|
||||
onScoreMessage(score.getPoints(), true);
|
||||
} else if (message instanceof UserInputMessage input) {
|
||||
onUserInputMessage(input.getPlayer(), input.getAction(), input.getTimeout());
|
||||
} else if (message instanceof TimeoutMessage) {
|
||||
onTimeoutMessage();
|
||||
} else {
|
||||
return unexpectedMessage(client, new GameMessage(message));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<ClientState> onMessageWhileFinishing(Client client, ObserverMessage message) {
|
||||
if (finishing == 1) { // last "finishing_round" has been received
|
||||
if (message instanceof ScoreMessage score) {
|
||||
onScoreMessage(score.getPoints(), false);
|
||||
return Optional.empty();
|
||||
} else if (message instanceof StateMessage state && "finishing".equals(state.getState())) {
|
||||
finishing++;
|
||||
return Optional.empty();
|
||||
}
|
||||
} else if (finishing == 2) { // "finishing" has been received
|
||||
if (message instanceof ScoreMessage) {
|
||||
return Optional.empty();
|
||||
} else if (message instanceof StateMessage state && "finished".equals(state.getState())) {
|
||||
onFinished();
|
||||
finishing++;
|
||||
return returnToSession();
|
||||
}
|
||||
}
|
||||
|
||||
return unexpectedMessage(client, new GameMessage(message));
|
||||
}
|
||||
|
||||
private Optional<ClientState> onStartRound(Client client) {
|
||||
if (isLastRound()) {
|
||||
log.fatal("Cannot start round {} with {} players", round + 1, players.size());
|
||||
return unexpectedMessage(client, new GameMessage(new StateMessage("starting_round")));
|
||||
}
|
||||
log.info("Round {} is starting...", round + 1);
|
||||
|
||||
round ++;
|
||||
tricks.clear();
|
||||
trumpSuit = null;
|
||||
trumpCard = null;
|
||||
stack.clear();
|
||||
trick = -1;
|
||||
|
||||
if (gameScreen != null) gameScreen.startRound(round);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private void onStartTrick() {
|
||||
log.info("Trick {} is starting...", trick + 1);
|
||||
trick ++;
|
||||
stack.clear();
|
||||
finishInteraction();
|
||||
if (gameScreen != null) gameScreen.startTrick();
|
||||
}
|
||||
|
||||
private void onJuggle() {
|
||||
juggling = true;
|
||||
juggleCard = null;
|
||||
}
|
||||
|
||||
private void onFinishingRound() {
|
||||
if (isLastRound()) finishing = 1; // start finish procedure
|
||||
}
|
||||
|
||||
private Optional<ClientState> onFinished() {
|
||||
log.info("The game has finished.");
|
||||
if (gameScreen != null) {
|
||||
gameScreen.showScoreOverlay(true);
|
||||
}
|
||||
return returnToSession();
|
||||
}
|
||||
|
||||
private Optional<ClientState> onError(Client client) {
|
||||
log.error("The game has finished with an error.");
|
||||
showErrorScreen(client, "The game has finished with an error.");
|
||||
return returnToSession();
|
||||
}
|
||||
|
||||
private void onHandMessage(@NotNull UUID player, @Unmodifiable @NotNull List<@NotNull Card> hand) {
|
||||
checkPlayer(player);
|
||||
log.info("{} hand cards are: {}", nameOf(player, true, true), hand);
|
||||
|
||||
if (juggling) checkActivePlayer(player, JUGGLE_CARD);
|
||||
finishInteraction();
|
||||
|
||||
hands.put(player, new ArrayList<>(hand));
|
||||
if (gameScreen != null) {
|
||||
gameScreen.setSelectedCard(null);
|
||||
gameScreen.setHand(player, hand, juggling);
|
||||
}
|
||||
juggling = false;
|
||||
}
|
||||
|
||||
private void onPredictionMessage(@NotNull UUID player, @Range(from = 0, to = Integer.MAX_VALUE) int prediction) {
|
||||
checkPlayer(player);
|
||||
checkActivePlayer(player, MAKE_PREDICTION, CHANGE_PREDICTION);
|
||||
log.info("{} predicted: {}", nameOf(player, true, false), prediction);
|
||||
|
||||
boolean changed = currentInteraction != null && currentInteraction.action() == CHANGE_PREDICTION;
|
||||
finishInteraction();
|
||||
|
||||
predictions.computeIfAbsent(round, r -> new HashMap<>()).put(player, prediction);
|
||||
if (gameScreen != null) gameScreen.addPrediction(round, player, prediction, changed);
|
||||
}
|
||||
|
||||
private void onTrumpMessage(@Nullable Card trumpCard, @Nullable Card.Suit trumpSuit) {
|
||||
if (trumpCard == null) {
|
||||
log.info("There is no trump in this round.");
|
||||
} else {
|
||||
log.info("The trump suit is {} ({}).", trumpSuit != null ? trumpSuit : "yet to be determined.", trumpCard);
|
||||
}
|
||||
var player = currentInteraction != null && currentInteraction.action() == PICK_TRUMP ? currentInteraction.player() : null;
|
||||
finishInteraction();
|
||||
|
||||
this.trumpCard = trumpCard;
|
||||
this.trumpSuit = trumpSuit;
|
||||
if (trumpCard == Card.WEREWOLF && trumpSuit == null) {
|
||||
werewolf = true;
|
||||
} else {
|
||||
werewolf = false;
|
||||
if (gameScreen != null) gameScreen.showTrumpOverlay(player, trumpCard, trumpSuit);
|
||||
}
|
||||
}
|
||||
|
||||
private void onTrickMessage(@NotNull UUID player, @Unmodifiable @NotNull List<@NotNull Card> cards) {
|
||||
checkPlayer(player);
|
||||
log.info("This trick {} goes to {}.", cards, nameOf(player));
|
||||
|
||||
this.stack.clear();
|
||||
var playerTricks = this.tricks.computeIfAbsent(player, p -> new ArrayList<>());
|
||||
playerTricks.add(cards);
|
||||
|
||||
if (gameScreen != null) gameScreen.finishTrick(player, playerTricks.size());
|
||||
}
|
||||
|
||||
private void onCardMessage(@NotNull UUID player, @NotNull Card card) {
|
||||
checkPlayer(player);
|
||||
checkActivePlayer(player, PLAY_CARD);
|
||||
log.info("{} played {}.", nameOf(player, true, false), card);
|
||||
finishInteraction();
|
||||
|
||||
this.stack.add(Pair.of(player, card));
|
||||
|
||||
var handCard = switch (card) {
|
||||
case CHANGELING_JESTER, CHANGELING_WIZARD -> Card.CHANGELING;
|
||||
case JUGGLER_BLUE, JUGGLER_GREEN, JUGGLER_RED, JUGGLER_YELLOW -> Card.JUGGLER;
|
||||
case CLOUD_BLUE, CLOUD_GREEN, CLOUD_RED, CLOUD_YELLOW -> Card.CLOUD;
|
||||
default -> card;
|
||||
};
|
||||
|
||||
var hand = this.hands.get(player);
|
||||
if (hand != null) {
|
||||
hand.remove(handCard);
|
||||
}
|
||||
|
||||
if (gameScreen != null) gameScreen.playCard(player, handCard, card);
|
||||
}
|
||||
|
||||
private void onScoreMessage(@Unmodifiable Map<@NotNull UUID, @NotNull Integer> points, boolean showOverlay) {
|
||||
log.info("The scores are as follows: " + points);
|
||||
scores.put(round, points);
|
||||
if (gameScreen != null) {
|
||||
gameScreen.addScores(round, points);
|
||||
if (showOverlay) gameScreen.showScoreOverlay(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void onUserInputMessage(@Nullable UUID player, @NotNull UserInputMessage.Action action, long timeout) {
|
||||
checkPlayer(player);
|
||||
log.info(
|
||||
"Waiting for input {} from {}. (times out at {})",
|
||||
action,
|
||||
nameOf(player),
|
||||
LocalDateTime.ofInstant(Instant.ofEpochMilli(timeout), ZoneId.systemDefault())
|
||||
);
|
||||
|
||||
if (action == UserInputMessage.Action.SYNC) {
|
||||
if (gameScreen != null) gameScreen.sync();
|
||||
} else {
|
||||
currentInteraction = new Interaction(player, action, timeout);
|
||||
|
||||
showCurrentInteraction();
|
||||
|
||||
if (werewolf && action == PICK_TRUMP) {
|
||||
werewolf = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onTimeoutMessage() {
|
||||
log.info("The previous interaction timed out.");
|
||||
delayedFinishInteraction();
|
||||
if (gameScreen != null) gameScreen.timeout();
|
||||
}
|
||||
|
||||
private Optional<ClientState> onNackMessage(Client client, @NotNull NackMessage nack) {
|
||||
sending.set(false);
|
||||
|
||||
if (isActive() && currentInteraction.action() == JUGGLE_CARD && juggleCard != null) {
|
||||
juggleCard = null;
|
||||
}
|
||||
|
||||
int code = nack.getCode();
|
||||
if (code == NackMessage.ILLEGAL_ARGUMENT || code == NackMessage.ILLEGAL_STATE) {
|
||||
log.error(nack.getMessage());
|
||||
if (gameScreen != null) {
|
||||
gameScreen.addMessage(true, "game.message.literal", nack.getMessage());
|
||||
gameScreen.ready(false);
|
||||
}
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return unexpectedMessage(client, nack);
|
||||
}
|
||||
}
|
||||
|
||||
private void onAckMessage() {
|
||||
log.info("OK");
|
||||
sending.set(false);
|
||||
|
||||
if (isActive() && currentInteraction.action() == JUGGLE_CARD && juggleCard != null) {
|
||||
if (gameScreen != null) gameScreen.setSelectedCard(juggleCard);
|
||||
juggleCard = null;
|
||||
}
|
||||
|
||||
if (gameScreen != null) gameScreen.ready(true);
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
//<editor-fold desc="Screen Callbacks" defaultstate="collapsed">
|
||||
public Optional<ClientState> onCardClicked(Client client, @NotNull Card card) {
|
||||
assert gameScreen != null;
|
||||
if (isActive()) {
|
||||
if (currentInteraction.action() == PLAY_CARD) {
|
||||
if (card == Card.CLOUD || card == Card.JUGGLER || card == Card.CHANGELING) {
|
||||
var oldOverlay = currentInteraction.overlay();
|
||||
if (oldOverlay != null) oldOverlay.close();
|
||||
|
||||
currentInteraction.overlay(gameScreen.showSpecialCardOverlay(card, currentInteraction.timeout()));
|
||||
} else {
|
||||
send(client, new PlayCardMessage(card));
|
||||
}
|
||||
return Optional.empty();
|
||||
} else if (currentInteraction.action() == JUGGLE_CARD) {
|
||||
if (send(client, new JuggleMessage(card))) {
|
||||
juggleCard = card;
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
gameScreen.addMessage(true, "game.message.nack.not_allowed");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<ClientState> onSuitClicked(Client client, @NotNull Card.Suit suit) {
|
||||
assert gameScreen != null;
|
||||
if (isActive() && currentInteraction.action() == PICK_TRUMP) {
|
||||
send(client, new PickTrumpMessage(suit));
|
||||
} else {
|
||||
gameScreen.addMessage(true, "game.message.nack.not_allowed");
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<ClientState> onPredictionMade(Client client, int prediction) {
|
||||
assert gameScreen != null;
|
||||
if (isActive()) {
|
||||
if (currentInteraction.action() == MAKE_PREDICTION || currentInteraction.action() == CHANGE_PREDICTION) {
|
||||
send(client, new PredictMessage(prediction));
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
gameScreen.addMessage(true, "game.message.nack.not_allowed");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<ClientState> sync(Client client) {
|
||||
assert gameScreen != null;
|
||||
send(client, new ContinueMessage());
|
||||
return Optional.empty();
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
public Optional<ClientState> returnToMenu() {
|
||||
return Optional.of(new Menu());
|
||||
}
|
||||
|
||||
private Optional<ClientState> returnToSession() {
|
||||
return Optional.of(new Session(
|
||||
new SessionData(session, sessionName, -1, configuration),
|
||||
players.entrySet().stream()
|
||||
.map(entry -> new PlayerData(entry.getKey(), entry.getValue(), false))
|
||||
.toList(),
|
||||
self,
|
||||
true
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message. Only one message may be sent at a time until without receiving an answer.
|
||||
* @return {@code true} iff the message was sent
|
||||
*/
|
||||
private boolean send(Client client, PlayerMessage message) {
|
||||
if (message instanceof ContinueMessage || !sending.getAndSet(true)) {
|
||||
client.send(new InteractionMessage(message));
|
||||
return true;
|
||||
} else {
|
||||
if (gameScreen != null) gameScreen.addMessage(true, "game.message.nack.too_fast");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//<editor-fold desc="Interactions" defaultstate="collapsed">
|
||||
/**
|
||||
* Checks whether some action from the player is expected.
|
||||
*/
|
||||
private boolean isActive() {
|
||||
return currentInteraction != null && (currentInteraction.player() == null || self.equals(currentInteraction.player()));
|
||||
}
|
||||
|
||||
private void showCurrentInteraction() {
|
||||
if (gameScreen == null) return;
|
||||
|
||||
var player = currentInteraction.player();
|
||||
var action = currentInteraction.action();
|
||||
var timeout = currentInteraction.timeout();
|
||||
|
||||
gameScreen.setActivePlayer(player, action, timeout);
|
||||
|
||||
if (player != null && werewolf && action == PICK_TRUMP) {
|
||||
gameScreen.swapTrumpCard(player);
|
||||
}
|
||||
|
||||
if (isActive()) {
|
||||
switch (action) {
|
||||
case PICK_TRUMP -> currentInteraction.overlay(gameScreen.showPickTrumpOverlay(timeout, werewolf));
|
||||
case MAKE_PREDICTION -> currentInteraction.overlay(gameScreen.showMakePredictionOverlay(round, timeout));
|
||||
case CHANGE_PREDICTION -> currentInteraction.overlay(gameScreen.showChangePredictionOverlay(round, predictions.get(round).get(player), timeout ));
|
||||
case PLAY_CARD -> gameScreen.setPersistentMessage("game.message.play_card.self");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the interaction overlay associated with the current interaction and reset the current interaction.
|
||||
*/
|
||||
private void finishInteraction() {
|
||||
if (currentInteraction != null) {
|
||||
var overlay = currentInteraction.overlay();
|
||||
if (overlay != null) overlay.close();
|
||||
}
|
||||
|
||||
currentInteraction = null;
|
||||
if (gameScreen != null) gameScreen.clearActivePlayer();
|
||||
}
|
||||
|
||||
/**
|
||||
* UI-wise equivalent to {@link #finishInteraction()} but information about the interaction is kept until after the
|
||||
* next message.
|
||||
*/
|
||||
private void delayedFinishInteraction() {
|
||||
pendingClearActivePlayer = 2;
|
||||
if (currentInteraction != null) {
|
||||
var overlay = currentInteraction.overlay();
|
||||
if (overlay != null) overlay.close();
|
||||
}
|
||||
if (gameScreen != null) gameScreen.clearActivePlayer();
|
||||
}
|
||||
|
||||
private void executeDelayedFinishInteraction() {
|
||||
if (pendingClearActivePlayer > 0 && --pendingClearActivePlayer == 0) {
|
||||
finishInteraction();
|
||||
}
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
private Optional<ClientState> handlePendingMessages(Client client) {
|
||||
if (pendingMessages != null) {
|
||||
for (var message : pendingMessages) {
|
||||
var result = onMessage(client, message);
|
||||
if (result.isPresent()) {
|
||||
return result;
|
||||
}
|
||||
executeDelayedFinishInteraction();
|
||||
}
|
||||
pendingMessages = null;
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private void updateScreen() {
|
||||
if (gameScreen == null) return;
|
||||
gameScreen.clear();
|
||||
|
||||
gameScreen.setPredictions(predictions);
|
||||
gameScreen.setScores(scores);
|
||||
gameScreen.setTrump(trumpCard, trumpSuit);
|
||||
gameScreen.setStack(stack);
|
||||
|
||||
var hand = hands.get(self);
|
||||
if (hand != null) {
|
||||
gameScreen.setHand(self, hand, false);
|
||||
}
|
||||
|
||||
if (currentInteraction != null) {
|
||||
showCurrentInteraction();
|
||||
} else {
|
||||
gameScreen.clearActivePlayer();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLastRound() {
|
||||
return round + 1 >= 60 / getPlayers().size();
|
||||
}
|
||||
|
||||
//<editor-fold desc="Logging" defaultState="collapsed">
|
||||
|
||||
/**
|
||||
* Checks whether the given player is known and logs errors.
|
||||
*/
|
||||
private void checkPlayer(UUID player) {
|
||||
if (player != null && !players.containsKey(player)) {
|
||||
log.error("Unknown player {}.", player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether one of the given actions is currently expected from the given player and logs errors.
|
||||
*/
|
||||
private void checkActivePlayer(UUID player, UserInputMessage.Action...actions) {
|
||||
if (currentInteraction != null && (currentInteraction.player() == null || currentInteraction.player().equals(player))) {
|
||||
for (var action : actions) {
|
||||
if (currentInteraction.action() == action) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.warn("Received message does not match the previous user input message.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the given player.
|
||||
*/
|
||||
private String nameOf(UUID player) {
|
||||
return nameOf(player, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the given player, optionally with a capitalized first letter and/or a possessive suffix ("'s")
|
||||
*/
|
||||
private String nameOf(UUID player, boolean capitalize, boolean possessive) {
|
||||
if (player == null) {
|
||||
return (capitalize ? "A" : "a") + "ll players" + (possessive ? "'" : "");
|
||||
} else if (self.equals(player)) {
|
||||
return (capitalize ? "Y" : "y") + "ou" + (possessive ? "r" : "");
|
||||
} else {
|
||||
return players.get(player) + (possessive ? "'s" : "");
|
||||
}
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
@Data
|
||||
@Accessors(fluent = true)
|
||||
public static final class Interaction {
|
||||
private final UUID player;
|
||||
private final UserInputMessage.Action action;
|
||||
private final long timeout;
|
||||
private WeakReference<InteractionOverlay> overlay;
|
||||
|
||||
public void overlay(InteractionOverlay overlay) {
|
||||
this.overlay = new WeakReference<>(overlay);
|
||||
}
|
||||
|
||||
public InteractionOverlay overlay() {
|
||||
if (overlay == null) return null;
|
||||
else return overlay.get();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.libgdx.Client;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.*;
|
||||
import eu.jonahbauer.wizard.common.messages.client.CreateSessionMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.client.JoinSessionMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.client.RejoinMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.data.SessionData;
|
||||
import eu.jonahbauer.wizard.common.messages.server.*;
|
||||
import eu.jonahbauer.wizard.common.model.Configuration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static eu.jonahbauer.wizard.client.libgdx.state.AwaitingJoinSession.Source.*;
|
||||
|
||||
public final class Lobby extends BaseState {
|
||||
private final Map<UUID, SessionData> sessions = new HashMap<>();
|
||||
|
||||
private LobbyScreen lobbyScreen;
|
||||
|
||||
public Lobby(Collection<SessionData> list) {
|
||||
list.forEach(s -> sessions.put(s.getUuid(), s));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onEnter(Client client) {
|
||||
if (!client.isError()) showListScreen(client);
|
||||
return super.onEnter(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onErrorDismissed(Client client) {
|
||||
showListScreen(client);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||
if (message instanceof SessionCreatedMessage created) {
|
||||
var session = created.getSession();
|
||||
sessions.put(session.getUuid(), session);
|
||||
if (lobbyScreen != null) {
|
||||
lobbyScreen.addSession(session);
|
||||
}
|
||||
return Optional.empty();
|
||||
} else if (message instanceof SessionRemovedMessage removed) {
|
||||
var session = removed.getSession();
|
||||
sessions.remove(session);
|
||||
if (lobbyScreen != null) {
|
||||
lobbyScreen.removeSession(session);
|
||||
}
|
||||
return Optional.empty();
|
||||
} else if (message instanceof SessionModifiedMessage modified) {
|
||||
var session = modified.getSession();
|
||||
sessions.put(session.getUuid(), session);
|
||||
if (lobbyScreen != null) {
|
||||
lobbyScreen.modifySession(session);
|
||||
}
|
||||
return Optional.empty();
|
||||
} else if (message instanceof SessionListMessage list) {
|
||||
list.getSessions().forEach(s -> sessions.put(s.getUuid(), s));
|
||||
if (lobbyScreen != null) {
|
||||
lobbyScreen.setSessions(list.getSessions().toArray(new SessionData[0]));
|
||||
}
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return unexpectedMessage(client, message);
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<ClientState> disconnect(@SuppressWarnings("unused") Client client) {
|
||||
return Optional.of(new Menu());
|
||||
}
|
||||
|
||||
public Optional<ClientState> createSession(Client client, @NotNull String sessionName, @NotNull Configuration config, long timeout, @NotNull String playerName) {
|
||||
client.send(new CreateSessionMessage(sessionName, playerName, timeout, config));
|
||||
return Optional.of(new AwaitingJoinSession(null, sessionName, config, CREATE));
|
||||
}
|
||||
|
||||
public Optional<ClientState> joinSession(Client client, UUID sessionUUID, String playerName) {
|
||||
var session = sessions.get(sessionUUID);
|
||||
if (session != null) {
|
||||
client.send(new JoinSessionMessage(sessionUUID, playerName));
|
||||
return Optional.of(new AwaitingJoinSession(session.getUuid(), session.getName(), session.getConfiguration(), JOIN));
|
||||
} else {
|
||||
throw new IllegalArgumentException("Session does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<ClientState> rejoinSession(Client client, @NotNull UUID sessionUUID, @NotNull UUID playerUUID, @NotNull String secret) {
|
||||
var session = sessions.get(sessionUUID);
|
||||
if (session != null) {
|
||||
client.send(new RejoinMessage(sessionUUID, playerUUID, secret));
|
||||
return Optional.of(new AwaitingJoinSession(sessionUUID, session.getName(), session.getConfiguration(), REJOIN));
|
||||
} else {
|
||||
throw new IllegalArgumentException("Session does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<ClientState> showCreateScreen(Client client) {
|
||||
var game = client.getGame();
|
||||
lobbyScreen = null;
|
||||
game.setScreen(new CreateGameScreen(game));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<ClientState> showListScreen(Client client) {
|
||||
var game = client.getGame();
|
||||
lobbyScreen = new LobbyScreen(game);
|
||||
game.setScreen(lobbyScreen);
|
||||
lobbyScreen.setSessions(sessions.values().toArray(SessionData[]::new));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<ClientState> showRejoinScreen(Client client) {
|
||||
var game = client.getGame();
|
||||
lobbyScreen = null;
|
||||
game.setScreen(new RejoinScreen(game));
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.libgdx.Client;
|
||||
import eu.jonahbauer.wizard.client.libgdx.ClientSocket;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.ConnectScreen;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.InstructionScreen;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.MainMenuScreen;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.java_websocket.framing.CloseFrame;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
|
||||
@Log4j2
|
||||
public final class Menu extends BaseState {
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Optional<ClientState> onEnter(Client client) {
|
||||
if (client.getSocket() != null && client.getSocket().isOpen()) {
|
||||
client.getSocket().close(CloseFrame.GOING_AWAY);
|
||||
}
|
||||
if (!client.isError()) showMenuScreen(client);
|
||||
return super.onEnter(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onErrorDismissed(Client client) {
|
||||
showMenuScreen(client);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||
// it is possible that there are messages still queued after
|
||||
// returning to the menu as a result of a previous message
|
||||
log.debug("Dropped message {}.", message);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onClose(Client client, int code, String reason, boolean remote) {
|
||||
super.onClose(client, code, reason, remote);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<ClientState> showConnectScreen(Client client) {
|
||||
var game = client.getGame();
|
||||
game.setScreen(new ConnectScreen(game));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<ClientState> showMenuScreen(Client client) {
|
||||
var game = client.getGame();
|
||||
game.setScreen(new MainMenuScreen(game));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<ClientState> showInstructionScreen(Client client) {
|
||||
var game = client.getGame();
|
||||
game.setScreen(new InstructionScreen(game));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<ClientState> connect(Client client, URI uri) {
|
||||
ClientSocket socket = new ClientSocket(uri);
|
||||
client.setSocket(socket);
|
||||
socket.connect();
|
||||
return Optional.of(new AwaitingConnection());
|
||||
}
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.libgdx.Client;
|
||||
import eu.jonahbauer.wizard.client.libgdx.screens.WaitingScreen;
|
||||
import eu.jonahbauer.wizard.common.messages.client.LeaveSessionMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.client.ReadyMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.data.PlayerData;
|
||||
import eu.jonahbauer.wizard.common.messages.data.SessionData;
|
||||
import eu.jonahbauer.wizard.common.messages.server.*;
|
||||
import eu.jonahbauer.wizard.common.model.Configuration;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Log4j2
|
||||
@Getter
|
||||
public final class Session extends BaseState {
|
||||
private WaitingScreen sessionScreen;
|
||||
|
||||
private final UUID self;
|
||||
|
||||
private final UUID session;
|
||||
private final String sessionName;
|
||||
private final Configuration configuration;
|
||||
private final LinkedHashMap<UUID, PlayerData> players = new LinkedHashMap<>();
|
||||
private final boolean dontSwitchScreen;
|
||||
|
||||
private boolean sending;
|
||||
|
||||
public Session(SessionData session, Collection<PlayerData> players, UUID self) {
|
||||
this(session, players, self, false);
|
||||
}
|
||||
|
||||
public Session(SessionData session, Collection<PlayerData> players, UUID self, boolean dontSwitchScreen) {
|
||||
this.session = session.getUuid();
|
||||
this.sessionName = session.getName();
|
||||
this.configuration = session.getConfiguration();
|
||||
players.forEach(p -> this.players.put(p.getUuid(), p));
|
||||
this.dontSwitchScreen = dontSwitchScreen;
|
||||
|
||||
this.self = self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onEnter(Client client) {
|
||||
if (!dontSwitchScreen) {
|
||||
showInfoScreen(client);
|
||||
}
|
||||
return super.onEnter(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onErrorDismissed(Client client) {
|
||||
showInfoScreen(client);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||
if (message instanceof PlayerJoinedMessage join) {
|
||||
var player = join.getPlayer();
|
||||
log.info("Player {} joined the session.", player.getName());
|
||||
players.put(player.getUuid(), player);
|
||||
if (sessionScreen != null) sessionScreen.addPlayer(player);
|
||||
return Optional.empty();
|
||||
} else if (message instanceof PlayerLeftMessage leave) {
|
||||
var uuid = leave.getPlayer();
|
||||
var player = players.remove(uuid);
|
||||
log.info("Player {} left the session.", player.getName());
|
||||
if (sessionScreen != null) sessionScreen.removePlayer(uuid);
|
||||
return Optional.empty();
|
||||
} else if (message instanceof PlayerModifiedMessage modified) {
|
||||
var player = modified.getPlayer();
|
||||
log.info("Player {} was modified.", player.getName());
|
||||
players.put(player.getUuid(), player);
|
||||
if (sessionScreen != null) {
|
||||
sessionScreen.modifyPlayer(player);
|
||||
if (self.equals(player.getUuid())) {
|
||||
sessionScreen.setReady(player.isReady());
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
} else if (message instanceof StartingGameMessage) {
|
||||
var players = new LinkedHashMap<UUID, String>();
|
||||
this.players.forEach((uuid, player) -> players.put(uuid, player.getName()));
|
||||
return Optional.of(new Game(self, session, sessionName, configuration, players));
|
||||
} else if (sending && message instanceof NackMessage nack) {
|
||||
// TODO display error
|
||||
log.error(nack.getMessage());
|
||||
sending = false;
|
||||
if (sessionScreen != null) sessionScreen.setSending(false);
|
||||
return Optional.empty();
|
||||
} else if (sending && message instanceof AckMessage) {
|
||||
sending = false;
|
||||
if (sessionScreen != null) sessionScreen.setSending(false);
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return unexpectedMessage(client, message);
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<ClientState> setReady(Client client, boolean ready) {
|
||||
if (sending) {
|
||||
log.warn("Please slow down");
|
||||
} else {
|
||||
sending = true;
|
||||
if (sessionScreen != null) sessionScreen.setSending(true);
|
||||
client.send(new ReadyMessage(ready));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<ClientState> toggleReady(Client client) {
|
||||
return setReady(client, !isReady());
|
||||
}
|
||||
|
||||
public Optional<ClientState> leave(Client client) {
|
||||
client.send(new LeaveSessionMessage());
|
||||
return Optional.of(new AwaitingJoinLobby());
|
||||
}
|
||||
|
||||
public Optional<ClientState> showInfoScreen(Client client) {
|
||||
sessionScreen = new WaitingScreen(client.getGame());
|
||||
client.getGame().setScreen(sessionScreen);
|
||||
sessionScreen.setPlayers(players.values().toArray(new PlayerData[0]));
|
||||
sessionScreen.setReady(players.get(self).isReady());
|
||||
sessionScreen.setPlayerName(getName());
|
||||
sessionScreen.setSession(session, sessionName, configuration);
|
||||
sessionScreen.setSending(sending);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private boolean isReady() {
|
||||
return players.get(self).isReady();
|
||||
}
|
||||
|
||||
private String getName() {
|
||||
return players.get(self).getName();
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.util;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public class AnimationTimings {
|
||||
private static final float FACTOR = 1;
|
||||
|
||||
public static final float JUGGLE = 0.25f * FACTOR;
|
||||
|
||||
public static final float STACK_EXPAND = 0.25f * FACTOR;
|
||||
public static final float STACK_COLLAPSE = 0.25f * FACTOR;
|
||||
|
||||
public static final float STACK_FINISH_MOVE = 0.25f * FACTOR;
|
||||
public static final float STACK_FINISH_ROTATE = 0.1f * FACTOR;
|
||||
public static final float STACK_FINISH_FADE = 0.5f * FACTOR;
|
||||
public static final float STACK_HOLD = 0.5f * FACTOR;
|
||||
|
||||
public static final float WEREWOLF_SWAP = 0.25f * FACTOR;
|
||||
|
||||
public static final float PAD_OF_TRUTH_EXPAND = 0.25f * FACTOR;
|
||||
public static final float PAD_OF_TRUTH_COLLAPSE = 0.25f * FACTOR;
|
||||
|
||||
public static final float HAND_LAYOUT = 0.15f * FACTOR;
|
||||
|
||||
public static final float OVERLAY_HOLD = 3f * FACTOR;
|
||||
public static final float OVERLAY_SHARED_ELEMENT = .3f * FACTOR;
|
||||
|
||||
public static final float MESSAGE_HOLD = 1.5f * FACTOR;
|
||||
public static final float MESSAGE_FADE = 0.5f * FACTOR;
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.util;
|
||||
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@UtilityClass
|
||||
public class CardUtil {
|
||||
@SuppressWarnings("RedundantTypeArguments")
|
||||
private static final Map<Card, Card.Suit> DEFAULT_SUITES = Map.<Card, Card.Suit>ofEntries(
|
||||
Map.entry(Card.BLUE_1, Card.Suit.BLUE),
|
||||
Map.entry(Card.BLUE_2, Card.Suit.BLUE),
|
||||
Map.entry(Card.BLUE_3, Card.Suit.BLUE),
|
||||
Map.entry(Card.BLUE_4, Card.Suit.BLUE),
|
||||
Map.entry(Card.BLUE_5, Card.Suit.BLUE),
|
||||
Map.entry(Card.BLUE_6, Card.Suit.BLUE),
|
||||
Map.entry(Card.BLUE_7, Card.Suit.BLUE),
|
||||
Map.entry(Card.BLUE_8, Card.Suit.BLUE),
|
||||
Map.entry(Card.BLUE_9, Card.Suit.BLUE),
|
||||
Map.entry(Card.BLUE_10, Card.Suit.BLUE),
|
||||
Map.entry(Card.BLUE_11, Card.Suit.BLUE),
|
||||
Map.entry(Card.BLUE_12, Card.Suit.BLUE),
|
||||
Map.entry(Card.BLUE_13, Card.Suit.BLUE),
|
||||
Map.entry(Card.RED_1, Card.Suit.RED),
|
||||
Map.entry(Card.RED_2, Card.Suit.RED),
|
||||
Map.entry(Card.RED_3, Card.Suit.RED),
|
||||
Map.entry(Card.RED_4, Card.Suit.RED),
|
||||
Map.entry(Card.RED_5, Card.Suit.RED),
|
||||
Map.entry(Card.RED_6, Card.Suit.RED),
|
||||
Map.entry(Card.RED_7, Card.Suit.RED),
|
||||
Map.entry(Card.RED_8, Card.Suit.RED),
|
||||
Map.entry(Card.RED_9, Card.Suit.RED),
|
||||
Map.entry(Card.RED_10, Card.Suit.RED),
|
||||
Map.entry(Card.RED_11, Card.Suit.RED),
|
||||
Map.entry(Card.RED_12, Card.Suit.RED),
|
||||
Map.entry(Card.RED_13, Card.Suit.RED),
|
||||
Map.entry(Card.GREEN_1, Card.Suit.GREEN),
|
||||
Map.entry(Card.GREEN_2, Card.Suit.GREEN),
|
||||
Map.entry(Card.GREEN_3, Card.Suit.GREEN),
|
||||
Map.entry(Card.GREEN_4, Card.Suit.GREEN),
|
||||
Map.entry(Card.GREEN_5, Card.Suit.GREEN),
|
||||
Map.entry(Card.GREEN_6, Card.Suit.GREEN),
|
||||
Map.entry(Card.GREEN_7, Card.Suit.GREEN),
|
||||
Map.entry(Card.GREEN_8, Card.Suit.GREEN),
|
||||
Map.entry(Card.GREEN_9, Card.Suit.GREEN),
|
||||
Map.entry(Card.GREEN_10, Card.Suit.GREEN),
|
||||
Map.entry(Card.GREEN_11, Card.Suit.GREEN),
|
||||
Map.entry(Card.GREEN_12, Card.Suit.GREEN),
|
||||
Map.entry(Card.GREEN_13, Card.Suit.GREEN),
|
||||
Map.entry(Card.YELLOW_1, Card.Suit.YELLOW),
|
||||
Map.entry(Card.YELLOW_2, Card.Suit.YELLOW),
|
||||
Map.entry(Card.YELLOW_3, Card.Suit.YELLOW),
|
||||
Map.entry(Card.YELLOW_4, Card.Suit.YELLOW),
|
||||
Map.entry(Card.YELLOW_5, Card.Suit.YELLOW),
|
||||
Map.entry(Card.YELLOW_6, Card.Suit.YELLOW),
|
||||
Map.entry(Card.YELLOW_7, Card.Suit.YELLOW),
|
||||
Map.entry(Card.YELLOW_8, Card.Suit.YELLOW),
|
||||
Map.entry(Card.YELLOW_9, Card.Suit.YELLOW),
|
||||
Map.entry(Card.YELLOW_10, Card.Suit.YELLOW),
|
||||
Map.entry(Card.YELLOW_11, Card.Suit.YELLOW),
|
||||
Map.entry(Card.YELLOW_12, Card.Suit.YELLOW),
|
||||
Map.entry(Card.YELLOW_13, Card.Suit.YELLOW),
|
||||
Map.entry(Card.RED_JESTER, Card.Suit.NONE),
|
||||
Map.entry(Card.GREEN_JESTER, Card.Suit.NONE),
|
||||
Map.entry(Card.BLUE_JESTER, Card.Suit.NONE),
|
||||
Map.entry(Card.YELLOW_JESTER, Card.Suit.NONE),
|
||||
Map.entry(Card.BOMB, Card.Suit.NONE),
|
||||
Map.entry(Card.FAIRY, Card.Suit.NONE)
|
||||
);
|
||||
|
||||
public Card.Suit getDefaultTrumpSuit(Card card) {
|
||||
return card == null ? Card.Suit.NONE : DEFAULT_SUITES.get(card);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.util;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public record Pair<F,S>(F first, S second) implements Map.Entry<F,S> {
|
||||
|
||||
public static <F,S> Pair<F,S> of(F first, S second) {
|
||||
return new Pair<>(first, second);
|
||||
}
|
||||
|
||||
@Override
|
||||
public F getKey() {
|
||||
return first();
|
||||
}
|
||||
|
||||
@Override
|
||||
public S getValue() {
|
||||
return second();
|
||||
}
|
||||
|
||||
@Override
|
||||
public S setValue(S value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class SavedData {
|
||||
public @NotNull String uri = "wss://webdev.jonahbauer.eu/wizard/";
|
||||
public @Nullable String playerName;
|
||||
public @Nullable SessionCredentials credentials;
|
||||
|
||||
public record SessionCredentials(@NotNull UUID session, @NotNull UUID player, @NotNull String secret) {}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package eu.jonahbauer.wizard.client.libgdx.util;
|
||||
|
||||
import com.badlogic.gdx.assets.AssetManager;
|
||||
import com.badlogic.gdx.assets.loaders.I18NBundleLoader;
|
||||
import com.badlogic.gdx.assets.loaders.SkinLoader;
|
||||
import com.badlogic.gdx.audio.Music;
|
||||
import com.badlogic.gdx.audio.Sound;
|
||||
import com.badlogic.gdx.graphics.Pixmap;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
|
||||
import com.badlogic.gdx.utils.I18NBundle;
|
||||
import eu.jonahbauer.wizard.client.libgdx.GameAtlas;
|
||||
import eu.jonahbauer.wizard.client.libgdx.UiskinAtlas;
|
||||
import lombok.experimental.Delegate;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class WizardAssetManager {
|
||||
public static final String SKIN = "uiskin.json";
|
||||
public static final String ATLAS_SKIN = UiskinAtlas.$PATH;
|
||||
public static final String ATLAS_GAME = GameAtlas.$PATH;
|
||||
|
||||
public static final String SFX_CLICK = "button_click_s.mp3";
|
||||
public static final String MUSIC_BACKGROUND = "background.mp3";
|
||||
|
||||
public static final String CURSOR = "cursor.png";
|
||||
public static final String MESSAGES = "i18n/messages";
|
||||
|
||||
@Delegate
|
||||
private final AssetManager manager = new AssetManager();
|
||||
|
||||
public void loadShared() {
|
||||
manager.load(CURSOR, Pixmap.class);
|
||||
manager.load(MESSAGES, I18NBundle.class, new I18NBundleLoader.I18NBundleParameter(Locale.getDefault()));
|
||||
manager.load(SKIN, Skin.class, new SkinLoader.SkinParameter(ATLAS_SKIN));
|
||||
|
||||
manager.load(MUSIC_BACKGROUND, Music.class);
|
||||
manager.load(SFX_CLICK, Sound.class);
|
||||
}
|
||||
|
||||
public void loadGame() {
|
||||
manager.load(ATLAS_GAME, TextureAtlas.class);
|
||||
}
|
||||
|
||||
public void unloadGame() {
|
||||
manager.unload(ATLAS_GAME);
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 67 KiB |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user