feat: Multiplayer (#3)
Reviewed-on: https://gitea.local.jzitnik.dev/jzitnik/game/pulls/3 Co-authored-by: jzitnik-dev <email@jzitnik.dev> Co-committed-by: jzitnik-dev <email@jzitnik.dev>
This commit is contained in:
42
game/.gitignore
vendored
Normal file
42
game/.gitignore
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
.kotlin
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
.idea/FuzzierSettings.xml
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
|
||||
logs
|
||||
3
game/.idea/.gitignore
generated
vendored
Normal file
3
game/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
13
game/.idea/encodings.xml
generated
Normal file
13
game/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/common/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/common/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/game/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/game/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/server/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/server/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
12
game/.idea/misc.xml
generated
Normal file
12
game/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="openjdk-25" project-jdk-type="JavaSDK" />
|
||||
</project>
|
||||
6
game/.idea/vcs.xml
generated
Normal file
6
game/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
190
game/pom.xml
Normal file
190
game/pom.xml
Normal file
@@ -0,0 +1,190 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>cz.jzitnik</groupId>
|
||||
<artifactId>game-parent</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>game</artifactId>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.38</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<source>25</source>
|
||||
<target>25</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>cz.jzitnik.client.Main</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<shadedArtifactAttached>false</shadedArtifactAttached>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>java</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<mainClass>cz.jzitnik.client.Main</mainClass>
|
||||
<classpathScope>compile</classpathScope>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>be.0110.repo-releases</id>
|
||||
<name>0110.be repository</name>
|
||||
<url>https://mvn.0110.be/releases</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cz.jzitnik</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.42</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.reflections</groupId>
|
||||
<artifactId>reflections</artifactId>
|
||||
<version>0.10.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>33.5.0-jre</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>tools.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>3.0.4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>tools.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-yaml</artifactId>
|
||||
<version>3.0.4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>6.0.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>6.0.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.17</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.5.25</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.trilarion</groupId>
|
||||
<artifactId>java-vorbis-support</artifactId>
|
||||
<version>1.2.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.googlecode.lanterna</groupId>
|
||||
<artifactId>lanterna</artifactId>
|
||||
<version>3.1.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>javacv-platform</artifactId>
|
||||
<version>1.5.12</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>ffmpeg-platform</artifactId>
|
||||
<version>7.1.1-1.5.12</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>be.tarsos.dsp</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>2.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>be.tarsos.dsp</groupId>
|
||||
<artifactId>jvm</artifactId>
|
||||
<version>2.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.glassfish.tyrus.bundles</groupId>
|
||||
<artifactId>tyrus-standalone-client</artifactId>
|
||||
<version>2.2.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
69
game/src/main/java/cz/jzitnik/client/Cli.java
Normal file
69
game/src/main/java/cz/jzitnik/client/Cli.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package cz.jzitnik.client;
|
||||
|
||||
import com.googlecode.lanterna.input.KeyStroke;
|
||||
import com.googlecode.lanterna.screen.TerminalScreen;
|
||||
import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
|
||||
import com.googlecode.lanterna.terminal.MouseCaptureMode;
|
||||
import cz.jzitnik.client.annotations.Dependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.events.KeyboardPressEvent;
|
||||
import cz.jzitnik.client.events.MouseAction;
|
||||
import cz.jzitnik.client.events.TerminalResizeEvent;
|
||||
import cz.jzitnik.client.states.RunningState;
|
||||
import cz.jzitnik.client.states.TerminalState;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Slf4j
|
||||
@Dependency
|
||||
public class Cli implements Runnable {
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
|
||||
@InjectState
|
||||
private RunningState runningState;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Start event manager thread
|
||||
try (TerminalScreen terminal = new DefaultTerminalFactory()
|
||||
.setMouseCaptureMode(MouseCaptureMode.CLICK_RELEASE_DRAG_MOVE)
|
||||
.createScreen()) {
|
||||
terminalState.setTerminalScreen(terminal);
|
||||
terminalState.setTextGraphics(terminal.newTextGraphics());
|
||||
|
||||
terminal.setCursorPosition(null);
|
||||
terminal.doResizeIfNecessary();
|
||||
|
||||
terminal.getTerminal().addResizeListener((ignored, terminalSize) -> {
|
||||
terminal.doResizeIfNecessary();
|
||||
eventManager.emitEvent(new TerminalResizeEvent(terminalSize));
|
||||
});
|
||||
|
||||
terminal.startScreen();
|
||||
|
||||
eventManager.emitEvent(new TerminalResizeEvent(terminal.getTerminalSize()));
|
||||
|
||||
while (runningState.isRunning()) {
|
||||
KeyStroke keyStroke = terminal.readInput();
|
||||
if (keyStroke != null) {
|
||||
if (keyStroke instanceof com.googlecode.lanterna.input.MouseAction mouse) {
|
||||
eventManager.emitEvent(new MouseAction(mouse));
|
||||
continue;
|
||||
}
|
||||
|
||||
eventManager.emitEvent(new KeyboardPressEvent(keyStroke));
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Terminal error occurred, shutting down CLI thread.", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
49
game/src/main/java/cz/jzitnik/client/Game.java
Normal file
49
game/src/main/java/cz/jzitnik/client/Game.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package cz.jzitnik.client;
|
||||
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.game.setup.GameSetup;
|
||||
import cz.jzitnik.client.socket.Client;
|
||||
import cz.jzitnik.client.socket.SocketEventManager;
|
||||
import cz.jzitnik.client.utils.DependencyManager;
|
||||
import cz.jzitnik.client.utils.GlobalIOHandlerRepository;
|
||||
import cz.jzitnik.client.utils.ScheduledTaskManager;
|
||||
import cz.jzitnik.client.utils.ThreadManager;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import jakarta.websocket.DeploymentException;
|
||||
import org.reflections.Reflections;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Game {
|
||||
private final DependencyManager dependencyManager = new DependencyManager(new Reflections("cz.jzitnik.client"));
|
||||
|
||||
@InjectDependency
|
||||
private GameSetup gameSetup;
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
@InjectDependency
|
||||
private SocketEventManager socketEventManager;
|
||||
@InjectDependency
|
||||
private Cli cli;
|
||||
@InjectDependency
|
||||
private ThreadManager threadManager;
|
||||
@InjectDependency
|
||||
private ScheduledTaskManager scheduledTaskManager;
|
||||
@InjectDependency
|
||||
private GlobalIOHandlerRepository globalIOHandlerRepository;
|
||||
|
||||
public void start() throws IOException {
|
||||
dependencyManager.inject(this);
|
||||
|
||||
eventManager.start();
|
||||
socketEventManager.start();
|
||||
|
||||
threadManager.startAll();
|
||||
scheduledTaskManager.startAll();
|
||||
globalIOHandlerRepository.setup();
|
||||
|
||||
gameSetup.setup();
|
||||
|
||||
cli.run();
|
||||
}
|
||||
}
|
||||
11
game/src/main/java/cz/jzitnik/client/Main.java
Normal file
11
game/src/main/java/cz/jzitnik/client/Main.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package cz.jzitnik.client;
|
||||
|
||||
// events/handlers/MouseMoveEventHandler.java
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws IOException {
|
||||
new Game().start();
|
||||
}
|
||||
}
|
||||
12
game/src/main/java/cz/jzitnik/client/annotations/Config.java
Normal file
12
game/src/main/java/cz/jzitnik/client/annotations/Config.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package cz.jzitnik.client.annotations;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.ElementType;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Config {
|
||||
String value();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cz.jzitnik.client.annotations;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.ElementType;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Dependency {
|
||||
/** Custom alias **/
|
||||
Class<?> value() default Object.class;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cz.jzitnik.client.annotations;
|
||||
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.ElementType;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface EventHandler {
|
||||
Class<? extends Event> value();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cz.jzitnik.client.annotations;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.ElementType;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface PostInit {
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cz.jzitnik.client.annotations;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface ScheduledTask {
|
||||
long rate();
|
||||
TimeUnit rateUnit();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cz.jzitnik.client.annotations;
|
||||
|
||||
import cz.jzitnik.common.socket.SocketMessage;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface SocketEventHandler {
|
||||
Class<? extends SocketMessage> value();
|
||||
}
|
||||
11
game/src/main/java/cz/jzitnik/client/annotations/State.java
Normal file
11
game/src/main/java/cz/jzitnik/client/annotations/State.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package cz.jzitnik.client.annotations;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.ElementType;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface State {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cz.jzitnik.client.annotations;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.ElementType;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface ThreadRegistry {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cz.jzitnik.client.annotations.injectors;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD})
|
||||
public @interface InjectConfig {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cz.jzitnik.client.annotations.injectors;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD})
|
||||
public @interface InjectDependency {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cz.jzitnik.client.annotations.injectors;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD})
|
||||
public @interface InjectState {
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cz.jzitnik.client.annotations.ui;
|
||||
|
||||
import com.googlecode.lanterna.input.KeyType;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Repeatable(KeyboardPressHandlers.class)
|
||||
public @interface KeyboardPressHandler {
|
||||
KeyType keyType() default KeyType.Character;
|
||||
char character() default '\0';
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package cz.jzitnik.client.annotations.ui;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface KeyboardPressHandlers {
|
||||
KeyboardPressHandler[] value();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package cz.jzitnik.client.annotations.ui;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.ElementType;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface MouseHandler {
|
||||
MouseHandlerType value();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package cz.jzitnik.client.annotations.ui;
|
||||
|
||||
public enum MouseHandlerType {
|
||||
CLICK,
|
||||
MOVE,
|
||||
ELSE,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cz.jzitnik.client.annotations.ui;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.ElementType;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface Render {
|
||||
}
|
||||
11
game/src/main/java/cz/jzitnik/client/annotations/ui/UI.java
Normal file
11
game/src/main/java/cz/jzitnik/client/annotations/ui/UI.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package cz.jzitnik.client.annotations.ui;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.ElementType;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface UI {
|
||||
}
|
||||
15
game/src/main/java/cz/jzitnik/client/config/CoreLogic.java
Normal file
15
game/src/main/java/cz/jzitnik/client/config/CoreLogic.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package cz.jzitnik.client.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import cz.jzitnik.client.annotations.Config;
|
||||
|
||||
@Config("core_logic.yaml")
|
||||
public record CoreLogic(int itemDropDisappearMinutes) {
|
||||
@JsonCreator
|
||||
public CoreLogic(
|
||||
@JsonProperty("itemDropDisappearMinutes") int itemDropDisappearMinutes
|
||||
) {
|
||||
this.itemDropDisappearMinutes = itemDropDisappearMinutes;
|
||||
}
|
||||
}
|
||||
19
game/src/main/java/cz/jzitnik/client/config/Debugging.java
Normal file
19
game/src/main/java/cz/jzitnik/client/config/Debugging.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package cz.jzitnik.client.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import cz.jzitnik.client.annotations.Config;
|
||||
|
||||
@Config("debugging.yaml")
|
||||
public record Debugging(boolean renderColliders, boolean renderPlayerCollider, boolean showPlayerCordsLogs) {
|
||||
@JsonCreator
|
||||
public Debugging(
|
||||
@JsonProperty("renderColliders") boolean renderColliders,
|
||||
@JsonProperty("renderPlayerCollider") boolean renderPlayerCollider,
|
||||
@JsonProperty("showPlayerCordsLogs") boolean showPlayerCordsLogs
|
||||
) {
|
||||
this.renderColliders = renderColliders;
|
||||
this.renderPlayerCollider = renderPlayerCollider;
|
||||
this.showPlayerCordsLogs = showPlayerCordsLogs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cz.jzitnik.client.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import cz.jzitnik.client.annotations.Config;
|
||||
|
||||
@Config("microphone.yaml")
|
||||
public record MicrophoneConfig(float volumeThreshold) {
|
||||
@JsonCreator
|
||||
public MicrophoneConfig(
|
||||
@JsonProperty("volumeThreshold") float volumeThreshold
|
||||
) {
|
||||
this.volumeThreshold = volumeThreshold;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cz.jzitnik.client.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import cz.jzitnik.client.annotations.Config;
|
||||
import cz.jzitnik.client.events.handlers.PlayerMoveEventHandler;
|
||||
|
||||
@Config("player.yaml")
|
||||
public record PlayerConfig(
|
||||
double playerReach,
|
||||
int playerMoveDistance,
|
||||
int playerMoveDistanceSprinting,
|
||||
PlayerMoveEventHandler.SprintKey sprintKey,
|
||||
int swingTimeMs,
|
||||
int staminaIncreaseRateMs,
|
||||
int staminaDelayMs
|
||||
) {
|
||||
@JsonCreator
|
||||
public PlayerConfig(
|
||||
@JsonProperty("playerReach") double playerReach,
|
||||
@JsonProperty("playerMoveDistance") int playerMoveDistance,
|
||||
@JsonProperty("playerMoveDistanceSprinting") int playerMoveDistanceSprinting,
|
||||
@JsonProperty("sprintKey") PlayerMoveEventHandler.SprintKey sprintKey,
|
||||
@JsonProperty("swingTimeMs") int swingTimeMs,
|
||||
@JsonProperty("staminaIncreaseRateMs") int staminaIncreaseRateMs,
|
||||
@JsonProperty("staminaDelayMs") int staminaDelayMs
|
||||
) {
|
||||
this.playerReach = playerReach;
|
||||
this.playerMoveDistance = playerMoveDistance;
|
||||
this.playerMoveDistanceSprinting = playerMoveDistanceSprinting;
|
||||
this.sprintKey = sprintKey;
|
||||
this.swingTimeMs = swingTimeMs;
|
||||
this.staminaIncreaseRateMs = staminaIncreaseRateMs;
|
||||
this.staminaDelayMs = staminaDelayMs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package cz.jzitnik.client.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import cz.jzitnik.client.annotations.Config;
|
||||
|
||||
@Config("threads.yaml")
|
||||
public record ThreadPoolConfig(int eventThreadCount, int taskThreadCount) {
|
||||
@JsonCreator
|
||||
public ThreadPoolConfig(
|
||||
@JsonProperty("eventThreadCount") int eventThreadCount,
|
||||
@JsonProperty("taskThreadCount") int taskThreadCount
|
||||
) {
|
||||
this.eventThreadCount = eventThreadCount;
|
||||
this.taskThreadCount = taskThreadCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package cz.jzitnik.client.events;
|
||||
|
||||
import cz.jzitnik.client.game.objects.DroppedItem;
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
|
||||
public record DroppedItemRerender(DroppedItem droppedItem) implements Event {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package cz.jzitnik.client.events;
|
||||
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
|
||||
/** Custom event without any handler **/
|
||||
public class ExitEvent implements Event {
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package cz.jzitnik.client.events;
|
||||
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
|
||||
public class FullRedrawEvent implements Event {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cz.jzitnik.client.events;
|
||||
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
public class FullRoomDraw implements Event {
|
||||
private boolean fullRerender = false;
|
||||
|
||||
public FullRoomDraw(boolean fullRerender) {
|
||||
this.fullRerender = fullRerender;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package cz.jzitnik.client.events;
|
||||
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
|
||||
public class InventoryRerender implements Event {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package cz.jzitnik.client.events;
|
||||
|
||||
import com.googlecode.lanterna.input.KeyStroke;
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class KeyboardPressEvent implements Event {
|
||||
private KeyStroke keyStroke;
|
||||
}
|
||||
15
game/src/main/java/cz/jzitnik/client/events/MouseAction.java
Normal file
15
game/src/main/java/cz/jzitnik/client/events/MouseAction.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package cz.jzitnik.client.events;
|
||||
|
||||
import com.googlecode.lanterna.TerminalPosition;
|
||||
import com.googlecode.lanterna.input.MouseActionType;
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
|
||||
public class MouseAction extends com.googlecode.lanterna.input.MouseAction implements Event {
|
||||
public MouseAction(MouseActionType actionType, int button, TerminalPosition position) {
|
||||
super(actionType, button, position);
|
||||
}
|
||||
|
||||
public MouseAction(com.googlecode.lanterna.input.MouseAction mouseAction) {
|
||||
this(mouseAction.getActionType(), mouseAction.getButton(), mouseAction.getPosition());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cz.jzitnik.client.events;
|
||||
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class MouseMoveEvent implements Event {
|
||||
private MouseAction mouseAction;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package cz.jzitnik.client.events;
|
||||
|
||||
import com.googlecode.lanterna.input.KeyStroke;
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class PlayerMoveEvent implements Event {
|
||||
private KeyStroke keyStroke;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package cz.jzitnik.client.events;
|
||||
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
|
||||
public class RenderStats implements Event {
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cz.jzitnik.client.events;
|
||||
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class RerenderPart implements Event {
|
||||
private int forStartX;
|
||||
private int forEndX;
|
||||
private int forStartY;
|
||||
private int forEndY;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package cz.jzitnik.client.events;
|
||||
|
||||
import com.googlecode.lanterna.TerminalPosition;
|
||||
import com.googlecode.lanterna.TerminalSize;
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
public record RerenderScreen(ScreenPart[] parts) implements Event {
|
||||
public RerenderScreen(ScreenPart part) {
|
||||
this(new ScreenPart[]{part});
|
||||
}
|
||||
|
||||
public static RerenderScreen full(TerminalSize terminalSize) {
|
||||
return new RerenderScreen(new ScreenPart[]{ScreenPart.full(terminalSize)});
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class ScreenPart {
|
||||
private TerminalPosition start;
|
||||
private TerminalPosition end;
|
||||
|
||||
public static ScreenPart full(TerminalSize terminalSize) {
|
||||
return new ScreenPart(
|
||||
new TerminalPosition(0, 0),
|
||||
new TerminalPosition(terminalSize.getColumns() - 1, terminalSize.getRows() * 2 - 1)
|
||||
);
|
||||
}
|
||||
|
||||
public boolean isWithin(TerminalPosition terminalPosition) {
|
||||
return
|
||||
terminalPosition.getColumn() >= start.getColumn() &&
|
||||
terminalPosition.getColumn() <= end.getColumn() &&
|
||||
terminalPosition.getRow() >= start.getRow() &&
|
||||
terminalPosition.getRow() <= end.getRow();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package cz.jzitnik.client.events;
|
||||
|
||||
import cz.jzitnik.client.events.handlers.FullRoomDrawHandler;
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
|
||||
public record RoomChangeEvent(FullRoomDrawHandler.DoorPosition door) implements Event {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package cz.jzitnik.client.events;
|
||||
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
import cz.jzitnik.common.socket.SocketMessage;
|
||||
|
||||
public record SendSocketMessageEvent(SocketMessage message) implements Event {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package cz.jzitnik.client.events;
|
||||
|
||||
import com.googlecode.lanterna.TerminalSize;
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class TerminalResizeEvent implements Event {
|
||||
private TerminalSize newSize;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package cz.jzitnik.client.events;
|
||||
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
|
||||
public class TerminalTooSmallEvent implements Event {
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package cz.jzitnik.client.events.handlers;
|
||||
|
||||
import com.googlecode.lanterna.TextColor;
|
||||
import com.googlecode.lanterna.graphics.TextGraphics;
|
||||
import cz.jzitnik.client.annotations.EventHandler;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.events.RerenderScreen;
|
||||
import cz.jzitnik.client.game.Constants;
|
||||
import cz.jzitnik.client.states.RenderState;
|
||||
import cz.jzitnik.client.states.ScreenBuffer;
|
||||
import cz.jzitnik.client.states.TerminalState;
|
||||
import cz.jzitnik.client.ui.pixels.AlphaPixel;
|
||||
import cz.jzitnik.client.ui.pixels.ColoredPixel;
|
||||
import cz.jzitnik.client.ui.pixels.Empty;
|
||||
import cz.jzitnik.client.ui.pixels.Pixel;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Slf4j
|
||||
@EventHandler(RerenderScreen.class)
|
||||
public class CliHandler extends AbstractEventHandler<RerenderScreen> {
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
|
||||
@InjectState
|
||||
private ScreenBuffer screenBuffer;
|
||||
|
||||
@InjectState
|
||||
private RenderState renderState;
|
||||
|
||||
@Override
|
||||
public void handle(RerenderScreen event) {
|
||||
if (renderState.isTerminalTooSmall()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var parts = event.parts();
|
||||
var buffer = screenBuffer.getRenderedBuffer();
|
||||
var globalOverrideBuffer = screenBuffer.getGlobalOverrideBuffer();
|
||||
var terminalScreen = terminalState.getTerminalScreen();
|
||||
var tg = terminalState.getTextGraphics();
|
||||
|
||||
for (RerenderScreen.ScreenPart part : parts) {
|
||||
var start = part.getStart();
|
||||
int startYNormalized = (start.getRow() / 2) * 2; // Round to multiple of 2 down
|
||||
var end = part.getEnd();
|
||||
int endYNormalized = ((end.getRow() - 1) / 2) * 2; // Round to multiple of 2 ceil
|
||||
|
||||
for (int y = startYNormalized; y <= endYNormalized; y += 2) {
|
||||
for (int x = start.getColumn(); x <= end.getColumn(); x++) {
|
||||
try {
|
||||
Pixel topPixel = getPixel(buffer[y][x], globalOverrideBuffer[y][x]);
|
||||
Pixel bottomPixel = (y + 1 <= end.getRow())
|
||||
? getPixel(buffer[y + 1][x], globalOverrideBuffer[y + 1][x])
|
||||
: new Empty();
|
||||
|
||||
TextColor topColor = topPixel instanceof Empty
|
||||
? Constants.BACKGROUND_COLOR
|
||||
: topPixel.getColor();
|
||||
|
||||
TextColor bottomColor = bottomPixel instanceof Empty
|
||||
? Constants.BACKGROUND_COLOR
|
||||
: bottomPixel.getColor();
|
||||
|
||||
drawHalfPixel(tg, x, y / 2, topColor, bottomColor);
|
||||
} catch (ArrayIndexOutOfBoundsException ignored) {
|
||||
// Random error, ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
terminalScreen.refresh();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Pixel getPixel(Pixel buffer, AlphaPixel globalOverride) {
|
||||
if (globalOverride instanceof Empty) {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
if (buffer instanceof Empty) {
|
||||
return getPixel(new ColoredPixel(Constants.BACKGROUND_COLOR), globalOverride);
|
||||
}
|
||||
|
||||
TextColor blended = blendColors(
|
||||
buffer.getColor(),
|
||||
globalOverride.getColor(),
|
||||
globalOverride.getAlpha()
|
||||
);
|
||||
|
||||
return new ColoredPixel(blended);
|
||||
}
|
||||
|
||||
private TextColor blendColors(TextColor base, TextColor overlay, float alpha) {
|
||||
int r = blend(base.getRed(), overlay.getRed(), alpha);
|
||||
int g = blend(base.getGreen(), overlay.getGreen(), alpha);
|
||||
int b = blend(base.getBlue(), overlay.getBlue(), alpha);
|
||||
|
||||
return new TextColor.RGB(r, g, b);
|
||||
}
|
||||
|
||||
private int blend(int base, int overlay, float alpha) {
|
||||
return Math.round(base * (1 - alpha) + overlay * alpha);
|
||||
}
|
||||
|
||||
private void drawHalfPixel(TextGraphics tg, int x, int y,
|
||||
TextColor topColor,
|
||||
TextColor bottomColor) {
|
||||
tg.setBackgroundColor(topColor); // upper half
|
||||
tg.setForegroundColor(bottomColor); // lower half
|
||||
tg.setCharacter(x, y, '▄');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package cz.jzitnik.client.events.handlers;
|
||||
|
||||
import com.googlecode.lanterna.TerminalPosition;
|
||||
import com.googlecode.lanterna.TerminalSize;
|
||||
import com.googlecode.lanterna.TextColor;
|
||||
import cz.jzitnik.client.annotations.EventHandler;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.events.RerenderScreen;
|
||||
import cz.jzitnik.client.game.dialog.Dialog;
|
||||
import cz.jzitnik.client.game.dialog.OnEnd;
|
||||
import cz.jzitnik.client.states.DialogState;
|
||||
import cz.jzitnik.client.states.ScreenBuffer;
|
||||
import cz.jzitnik.client.states.TerminalState;
|
||||
import cz.jzitnik.client.ui.pixels.AlphaPixel;
|
||||
import cz.jzitnik.client.ui.pixels.ColoredPixel;
|
||||
import cz.jzitnik.client.ui.pixels.Empty;
|
||||
import cz.jzitnik.client.utils.DependencyManager;
|
||||
import cz.jzitnik.client.utils.TextRenderer;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@Slf4j
|
||||
@EventHandler(Dialog.class)
|
||||
public class DialogEventHandler extends AbstractEventHandler<Dialog> {
|
||||
|
||||
@InjectState
|
||||
private DialogState dialogState;
|
||||
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
|
||||
@InjectState
|
||||
private ScreenBuffer screenBuffer;
|
||||
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectDependency
|
||||
private TextRenderer textRenderer;
|
||||
|
||||
@InjectDependency
|
||||
private DependencyManager dependencyManager;
|
||||
|
||||
private static final int WIDTH = 350;
|
||||
private static final int MARGIN_BOTTOM = 15;
|
||||
public static final int PADDING = 7;
|
||||
private static final int BUTTON_TEXT_PADDING = 4;
|
||||
private static final int QUESTION_ACTIONS_GAP = 10;
|
||||
public static final int BUTTON_HEIGHT = 15;
|
||||
public static final int BUTTON_PADDING = 5;
|
||||
private static final float FONT_SIZE = 15f;
|
||||
|
||||
public static int calculateButtonHeight(Dialog dialog) {
|
||||
if (dialog.getOnEnd() instanceof OnEnd.AskQuestion(OnEnd.AskQuestion.Answer[] answers)) {
|
||||
return answers.length * BUTTON_HEIGHT + (answers.length - 1) * BUTTON_PADDING;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getYStartButtons(TextRenderer textRenderer, Dialog dialog) {
|
||||
var textSize = textRenderer.measureText(dialog.getText(), WIDTH - PADDING * 2, FONT_SIZE);
|
||||
|
||||
return PADDING + textSize.height + BUTTON_PADDING;
|
||||
}
|
||||
|
||||
public static TerminalSize getSize(TextRenderer textRenderer, Dialog dialog) {
|
||||
var textSize = textRenderer.measureText(dialog.getText(), WIDTH - PADDING * 2, FONT_SIZE);
|
||||
|
||||
return new TerminalSize(300, PADDING + textSize.height + (
|
||||
dialog.getOnEnd() instanceof OnEnd.AskQuestion ? BUTTON_PADDING + calculateButtonHeight(dialog) : 0
|
||||
) + PADDING);
|
||||
}
|
||||
|
||||
public static TerminalPosition getStart(TerminalSize terminalSize, TerminalSize size) {
|
||||
int startY = terminalSize.getRows() * 2 - MARGIN_BOTTOM - size.getRows();
|
||||
int startX = (terminalSize.getColumns() / 2) - (size.getColumns() / 2);
|
||||
|
||||
return new TerminalPosition(startX, startY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Dialog event) {
|
||||
boolean onlyLast = dialogState.getCurrentDialog() == event;
|
||||
dialogState.setCurrentDialog(event);
|
||||
TerminalSize terminalSize = terminalState.getTerminalScreen().getTerminalSize();
|
||||
var overrideBuffer = screenBuffer.getGlobalOverrideBuffer();
|
||||
var size = getSize(textRenderer, event);
|
||||
|
||||
var start = getStart(terminalSize, size);
|
||||
|
||||
var animation = textRenderer.renderTypingAnimation(event.getText(), size.getColumns() - PADDING * 2, size.getRows() - PADDING * 2, Color.WHITE, FONT_SIZE);
|
||||
var textSize = textRenderer.measureText(event.getText(), size.getColumns() - PADDING * 2, FONT_SIZE);
|
||||
OnEnd onEnd = event.getOnEnd();
|
||||
|
||||
List<AlphaPixel[][]> answersBuf = new ArrayList<>();
|
||||
|
||||
if (onEnd instanceof OnEnd.AskQuestion(
|
||||
OnEnd.AskQuestion.Answer[] answers
|
||||
)) {
|
||||
for (OnEnd.AskQuestion.Answer answer : answers) {
|
||||
answersBuf.add(textRenderer.renderText(answer.answer(), size.getColumns() - PADDING * 2, BUTTON_HEIGHT, Color.BLACK, FONT_SIZE, false));
|
||||
}
|
||||
}
|
||||
|
||||
dialogState.setRenderInProgress(true);
|
||||
try {
|
||||
for (int i = onlyLast ? animation.length : 0; i <= animation.length; i++) {
|
||||
var buf = animation[Math.min(i, animation.length - 1)];
|
||||
for (int y = 0; y < size.getRows(); y++) {
|
||||
for (int x = 0; x < size.getColumns(); x++) {
|
||||
var textPixel = buf[Math.min(Math.max(0, y - PADDING), buf.length - 1)][Math.min(Math.max(0, x - PADDING), buf[0].length - 1)];
|
||||
if (textPixel instanceof Empty || y < PADDING || x < PADDING || x >= size.getColumns() - PADDING || y >= size.getRows() - PADDING) {
|
||||
if (i == animation.length && y - 2 > textSize.height + QUESTION_ACTIONS_GAP && onEnd instanceof OnEnd.AskQuestion(
|
||||
OnEnd.AskQuestion.Answer[] answers
|
||||
)) {
|
||||
int buttonsY = y - textSize.height - QUESTION_ACTIONS_GAP - 2;
|
||||
int buttonIndex = buttonsY / (BUTTON_HEIGHT + BUTTON_PADDING);
|
||||
int rest = buttonsY % (BUTTON_HEIGHT + BUTTON_PADDING);
|
||||
|
||||
if (buttonIndex < answers.length && rest < BUTTON_HEIGHT && x >= PADDING && x < size.getColumns() - PADDING) {
|
||||
int localY = rest - BUTTON_TEXT_PADDING;
|
||||
int localX = x - PADDING - BUTTON_TEXT_PADDING;
|
||||
var buttonBuf = answersBuf.get(buttonIndex);
|
||||
var buttonTextPixel = buttonBuf[Math.min(Math.max(0, localY), buttonBuf.length - 1)][Math.min(Math.max(0, localX), buttonBuf[0].length - 1)];
|
||||
|
||||
if (buttonTextPixel instanceof Empty || localY < 0 || localX < 0 || localY >= buttonBuf.length || localX >= buttonBuf[0].length) {
|
||||
overrideBuffer[start.getRow() + y][start.getColumn() + x] = new ColoredPixel(new TextColor.RGB(255, 255, 255), dialogState.getHoveredButtonIndex() == buttonIndex ? 0.8f : 0.6f);
|
||||
} else {
|
||||
overrideBuffer[start.getRow() + y][start.getColumn() + x] = buttonTextPixel;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
overrideBuffer[start.getRow() + y][start.getColumn() + x] = new ColoredPixel(new TextColor.RGB(0, 0, 0), 0.6f);
|
||||
continue;
|
||||
}
|
||||
|
||||
overrideBuffer[start.getRow() + y][start.getColumn() + x] = textPixel;
|
||||
}
|
||||
}
|
||||
|
||||
eventManager.emitEvent(
|
||||
new RerenderScreen(
|
||||
new RerenderScreen.ScreenPart(
|
||||
start,
|
||||
new TerminalPosition(start.getColumn() + size.getColumns(), start.getRow() + size.getRows())
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
Thread.sleep(1000 / event.getTypingSpeed());
|
||||
}
|
||||
|
||||
dialogState.setRenderInProgress(false);
|
||||
|
||||
next(onEnd, start, size);
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void next(OnEnd onEnd, TerminalPosition start, TerminalSize size) throws InterruptedException {
|
||||
if (onEnd instanceof OnEnd.Continue(Dialog nextDialog)) {
|
||||
Thread.sleep(1000);
|
||||
for (int y = start.getRow(); y < start.getRow() + size.getRows(); y++) {
|
||||
for (int x = start.getColumn(); x < start.getColumn() + size.getColumns(); x++) {
|
||||
screenBuffer.getGlobalOverrideBuffer()[y][x] = new Empty();
|
||||
}
|
||||
}
|
||||
|
||||
if (nextDialog == null) {
|
||||
dialogState.setCurrentDialog(null);
|
||||
eventManager.emitEvent(
|
||||
new RerenderScreen(
|
||||
new RerenderScreen.ScreenPart(
|
||||
start,
|
||||
new TerminalPosition(start.getColumn() + size.getColumns(), start.getRow() + size.getRows())
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
eventManager.emitEvent(nextDialog);
|
||||
}
|
||||
} else if (onEnd instanceof OnEnd.RunCode(Runnable runnable, OnEnd end)) {
|
||||
dependencyManager.inject(runnable);
|
||||
runnable.run();
|
||||
next(end, start, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cz.jzitnik.client.events.handlers;
|
||||
|
||||
import cz.jzitnik.client.annotations.EventHandler;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.events.DroppedItemRerender;
|
||||
import cz.jzitnik.client.events.RerenderPart;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
@EventHandler(DroppedItemRerender.class)
|
||||
public class DroppedItemRerenderHandler extends AbstractEventHandler<DroppedItemRerender> {
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@Override
|
||||
public void handle(DroppedItemRerender event) {
|
||||
RoomCords droppedItemCords = event.droppedItem().getCords();
|
||||
BufferedImage droppedItemTexture = event.droppedItem().getTexture();
|
||||
|
||||
eventManager.emitEvent(new RerenderPart(
|
||||
droppedItemCords.getX(),
|
||||
droppedItemCords.getX() + droppedItemTexture.getWidth(),
|
||||
droppedItemCords.getY(),
|
||||
droppedItemCords.getY() + droppedItemTexture.getHeight()
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cz.jzitnik.client.events.handlers;
|
||||
|
||||
import cz.jzitnik.client.annotations.EventHandler;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.events.ExitEvent;
|
||||
import cz.jzitnik.client.states.RunningState;
|
||||
import cz.jzitnik.client.utils.ScheduledTaskManager;
|
||||
import cz.jzitnik.client.utils.ThreadManager;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
import cz.jzitnik.client.utils.roomtasks.RoomTaskScheduler;
|
||||
|
||||
@EventHandler(ExitEvent.class)
|
||||
public class ExitEventHandler extends AbstractEventHandler<ExitEvent> {
|
||||
@InjectDependency
|
||||
private ThreadManager threadManager;
|
||||
|
||||
@InjectState
|
||||
private RunningState runningState;
|
||||
|
||||
@InjectDependency
|
||||
private RoomTaskScheduler roomTaskScheduler;
|
||||
|
||||
@InjectDependency
|
||||
private ScheduledTaskManager scheduledTaskManager;
|
||||
|
||||
@Override
|
||||
public void handle(ExitEvent event) {
|
||||
threadManager.shutdownAll();
|
||||
scheduledTaskManager.shutdown();
|
||||
roomTaskScheduler.finalShutdown();
|
||||
runningState.setRunning(false);
|
||||
//System.exit(0); // Pls don't blame me
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cz.jzitnik.client.events.handlers;
|
||||
|
||||
import com.googlecode.lanterna.screen.Screen;
|
||||
import cz.jzitnik.client.annotations.EventHandler;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.events.FullRedrawEvent;
|
||||
import cz.jzitnik.client.events.FullRoomDraw;
|
||||
import cz.jzitnik.client.states.TerminalState;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@EventHandler(FullRedrawEvent.class)
|
||||
public class FullRedrawEventHandler extends AbstractEventHandler<FullRedrawEvent> {
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
|
||||
@Override
|
||||
public void handle(FullRedrawEvent event) {
|
||||
terminalState.getTerminalScreen().clear();
|
||||
try {
|
||||
terminalState.getTerminalScreen().refresh(Screen.RefreshType.COMPLETE);
|
||||
} catch (IOException _) {
|
||||
}
|
||||
eventManager.emitEvent(new FullRoomDraw(true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package cz.jzitnik.client.events.handlers;
|
||||
|
||||
import com.googlecode.lanterna.TerminalPosition;
|
||||
import com.googlecode.lanterna.TerminalSize;
|
||||
import com.googlecode.lanterna.screen.TerminalScreen;
|
||||
import cz.jzitnik.client.annotations.EventHandler;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectConfig;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.config.Debugging;
|
||||
import cz.jzitnik.client.events.FullRoomDraw;
|
||||
import cz.jzitnik.client.events.RerenderScreen;
|
||||
import cz.jzitnik.client.events.TerminalTooSmallEvent;
|
||||
import cz.jzitnik.client.game.GameRoom;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.game.Player;
|
||||
import cz.jzitnik.client.game.ResourceManager;
|
||||
import cz.jzitnik.client.states.RenderState;
|
||||
import cz.jzitnik.client.states.ScreenBuffer;
|
||||
import cz.jzitnik.client.states.TerminalState;
|
||||
import cz.jzitnik.client.utils.GlobalIOHandlerRepository;
|
||||
import cz.jzitnik.client.utils.RerenderUtils;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import cz.jzitnik.client.utils.roomtasks.RoomTaskScheduler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@EventHandler(FullRoomDraw.class)
|
||||
public class FullRoomDrawHandler extends AbstractEventHandler<FullRoomDraw> {
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
@InjectState
|
||||
private ScreenBuffer screenBuffer;
|
||||
@InjectDependency
|
||||
private ResourceManager resourceManager;
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
@InjectState
|
||||
private RenderState renderState;
|
||||
@InjectConfig
|
||||
private Debugging debugging;
|
||||
@InjectDependency
|
||||
private RoomTaskScheduler roomTaskScheduler;
|
||||
@InjectDependency
|
||||
private GlobalIOHandlerRepository globalIOHandlerRepository;
|
||||
|
||||
@Override
|
||||
public void handle(FullRoomDraw event) {
|
||||
try {
|
||||
log.debug("Rendering full room");
|
||||
TerminalScreen terminalScreen = terminalState.getTerminalScreen();
|
||||
List<RerenderScreen.ScreenPart> partsToRerender = new ArrayList<>();
|
||||
GameRoom currentRoom = gameState.getCurrentRoom();
|
||||
|
||||
BufferedImage room = resourceManager.getResource(currentRoom.getTexture());
|
||||
Player player = gameState.getPlayer();
|
||||
BufferedImage playerTexture = player.getTexture(resourceManager);
|
||||
TerminalSize terminalSize = terminalScreen.getTerminalSize();
|
||||
|
||||
int width = room.getWidth();
|
||||
int height = room.getHeight();
|
||||
|
||||
var start = RerenderUtils.getStart(room, terminalSize);
|
||||
int startX = start.getX();
|
||||
int startY = start.getY();
|
||||
|
||||
RerenderUtils.rerenderPart(0, width - 1, 0, height - 1, startX, startY, currentRoom, room, player, screenBuffer, resourceManager, debugging, gameState.getOtherPlayers());
|
||||
if (event.isFullRerender()) {
|
||||
globalIOHandlerRepository.renderAll();
|
||||
}
|
||||
|
||||
partsToRerender.add(new RerenderScreen.ScreenPart(
|
||||
new TerminalPosition(startX, startY),
|
||||
new TerminalPosition(startX + width, startY + height - 1)
|
||||
));
|
||||
|
||||
if (renderState.isFirstRender() || event.isFullRerender()) {
|
||||
eventManager.emitEvent(RerenderScreen.full(terminalSize));
|
||||
renderState.setFirstRender(false);
|
||||
scheduler.schedule(() -> roomTaskScheduler.setupNewSchedulers(currentRoom), 200, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
eventManager.emitEvent(new RerenderScreen(partsToRerender.toArray(RerenderScreen.ScreenPart[]::new)));
|
||||
}
|
||||
|
||||
renderState.setTerminalTooSmall(false);
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
// Screen too small to fit the room
|
||||
eventManager.emitEvent(new TerminalTooSmallEvent());
|
||||
renderState.setTerminalTooSmall(true);
|
||||
roomTaskScheduler.shutdownTasks();
|
||||
//log.error("Terminal too small", e);
|
||||
}
|
||||
}
|
||||
|
||||
public enum DoorPosition {
|
||||
TOP,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
BOTTOM,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cz.jzitnik.client.events.handlers;
|
||||
|
||||
import com.googlecode.lanterna.TerminalPosition;
|
||||
import cz.jzitnik.client.annotations.EventHandler;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.events.InventoryRerender;
|
||||
import cz.jzitnik.client.events.RerenderScreen;
|
||||
import cz.jzitnik.client.ui.Inventory;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
|
||||
@EventHandler(InventoryRerender.class)
|
||||
public class InventoryRerenderHandler extends AbstractEventHandler<InventoryRerender> {
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectDependency
|
||||
private Inventory inventory;
|
||||
|
||||
@Override
|
||||
public void handle(InventoryRerender event) {
|
||||
inventory.renderInventoryRerender();
|
||||
eventManager.emitEvent(new RerenderScreen(new RerenderScreen.ScreenPart(
|
||||
new TerminalPosition(inventory.getOffsetX(), inventory.getOffsetY()),
|
||||
new TerminalPosition(inventory.getOffsetX() + Inventory.INVENTORY_WIDTH, inventory.getOffsetY() + Inventory.INVENTORY_HEIGHT)
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cz.jzitnik.client.events.handlers;
|
||||
|
||||
import cz.jzitnik.client.annotations.EventHandler;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.events.KeyboardPressEvent;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.utils.GlobalIOHandlerRepository;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
|
||||
@EventHandler(KeyboardPressEvent.class)
|
||||
public class KeyboardPressEventHandler extends AbstractEventHandler<KeyboardPressEvent> {
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
|
||||
@InjectDependency
|
||||
private GlobalIOHandlerRepository globalIOHandlerRepository;
|
||||
|
||||
@Override
|
||||
public void handle(KeyboardPressEvent event) {
|
||||
if (gameState.getScreen() != null) {
|
||||
gameState.getScreen().handleKeyboardAction(event);
|
||||
return;
|
||||
}
|
||||
|
||||
globalIOHandlerRepository.keyboard(event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package cz.jzitnik.client.events.handlers;
|
||||
|
||||
import cz.jzitnik.client.annotations.EventHandler;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectConfig;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.config.PlayerConfig;
|
||||
import cz.jzitnik.client.events.MouseAction;
|
||||
import cz.jzitnik.client.events.MouseMoveEvent;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.game.utils.Selectable;
|
||||
import cz.jzitnik.client.states.RenderState;
|
||||
import cz.jzitnik.client.utils.DependencyManager;
|
||||
import cz.jzitnik.client.utils.UIRoomClickHandlerRepository;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@EventHandler(MouseAction.class)
|
||||
public class MouseActionEventHandler extends AbstractEventHandler<MouseAction> {
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectDependency
|
||||
private UIRoomClickHandlerRepository uiRoomClickHandlerRepository;
|
||||
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
|
||||
@InjectState
|
||||
private RenderState renderState;
|
||||
|
||||
@InjectConfig
|
||||
private PlayerConfig playerConfig;
|
||||
|
||||
@InjectDependency
|
||||
private DependencyManager dependencyManager;
|
||||
|
||||
@Override
|
||||
public void handle(MouseAction event) {
|
||||
if (gameState.getScreen() != null) {
|
||||
gameState.getScreen().handleMouseAction(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (renderState.isTerminalTooSmall()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.getActionType()) {
|
||||
case MOVE -> {
|
||||
boolean registered = uiRoomClickHandlerRepository.handleMove(event);
|
||||
|
||||
if (!registered) {
|
||||
eventManager.emitEvent(new MouseMoveEvent(event));
|
||||
}
|
||||
}
|
||||
case CLICK_RELEASE -> {
|
||||
boolean clicked = uiRoomClickHandlerRepository.handleClick(event);
|
||||
|
||||
if (clicked || gameState.getPlayer().isSwinging()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Stream<? extends Selectable> combined = Stream.concat(
|
||||
Stream.concat(
|
||||
gameState.getCurrentRoom().getMobs().stream(),
|
||||
gameState.getCurrentRoom().getObjects().stream()),
|
||||
gameState.getCurrentRoom().getDroppedItems().stream()
|
||||
);
|
||||
|
||||
Optional<? extends Selectable> object = combined.filter(Selectable::isSelected).findFirst();
|
||||
|
||||
gameState.getPlayer().swing(playerConfig.swingTimeMs());
|
||||
|
||||
object.ifPresent(selectable -> {
|
||||
dependencyManager.inject(selectable);
|
||||
selectable.interact();
|
||||
});
|
||||
}
|
||||
default -> uiRoomClickHandlerRepository.handleElse(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package cz.jzitnik.client.events.handlers;
|
||||
|
||||
import com.googlecode.lanterna.TerminalPosition;
|
||||
import com.googlecode.lanterna.input.MouseActionType;
|
||||
import cz.jzitnik.client.annotations.EventHandler;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectConfig;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.config.Debugging;
|
||||
import cz.jzitnik.client.config.PlayerConfig;
|
||||
import cz.jzitnik.client.events.MouseAction;
|
||||
import cz.jzitnik.client.events.MouseMoveEvent;
|
||||
import cz.jzitnik.client.events.RerenderScreen;
|
||||
import cz.jzitnik.client.game.GameRoom;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.game.Player;
|
||||
import cz.jzitnik.client.game.ResourceManager;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.client.game.utils.Selectable;
|
||||
import cz.jzitnik.client.states.ScreenBuffer;
|
||||
import cz.jzitnik.client.states.TerminalState;
|
||||
import cz.jzitnik.client.utils.RerenderUtils;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Slf4j
|
||||
@EventHandler(MouseMoveEvent.class)
|
||||
public class MouseMoveEventHandler extends AbstractEventHandler<MouseMoveEvent> {
|
||||
private MouseMoveEvent lastEvent = new MouseMoveEvent(new MouseAction(MouseActionType.MOVE, 1, new TerminalPosition(0, 0)));
|
||||
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
|
||||
@InjectState
|
||||
private ScreenBuffer screenBuffer;
|
||||
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
|
||||
@InjectDependency
|
||||
private ResourceManager resourceManager;
|
||||
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectConfig
|
||||
private Debugging debugging;
|
||||
|
||||
@InjectConfig
|
||||
private PlayerConfig playerConfig;
|
||||
|
||||
private double distancePointToRect(
|
||||
double px, double py,
|
||||
double rectTopLeftX, double rectTopLeftY,
|
||||
double rectBottomRightX, double rectBottomRightY) {
|
||||
double minX = Math.min(rectTopLeftX, rectBottomRightX);
|
||||
double maxX = Math.max(rectTopLeftX, rectBottomRightX);
|
||||
double minY = Math.min(rectTopLeftY, rectBottomRightY);
|
||||
double maxY = Math.max(rectTopLeftY, rectBottomRightY);
|
||||
|
||||
boolean isInside = (px > minX && px < maxX && py > minY && py < maxY);
|
||||
|
||||
if (isInside) {
|
||||
return 0;
|
||||
} else {
|
||||
double closestX = Math.max(minX, Math.min(px, maxX));
|
||||
double closestY = Math.max(minY, Math.min(py, maxY));
|
||||
double dx = px - closestX;
|
||||
double dy = py - closestY;
|
||||
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MouseMoveEvent event) {
|
||||
if (event.getMouseAction() != null) {
|
||||
this.lastEvent = event;
|
||||
}
|
||||
|
||||
MouseAction mouseAction = lastEvent.getMouseAction();
|
||||
int mouseX = mouseAction.getPosition().getColumn();
|
||||
int mouseY = mouseAction.getPosition().getRow();
|
||||
|
||||
GameRoom currentRoom = gameState.getCurrentRoom();
|
||||
BufferedImage room = resourceManager.getResource(currentRoom.getTexture());
|
||||
Player player = gameState.getPlayer();
|
||||
RoomCords playerCords = player.getPlayerCords();
|
||||
BufferedImage playerTexture = player.getTexture(resourceManager);
|
||||
|
||||
var start = RerenderUtils.getStart(room, terminalState.getTerminalScreen().getTerminalSize());
|
||||
int startX = start.getX();
|
||||
int startY = start.getY();
|
||||
|
||||
/*List<? extends Selectable> _combinedObjects = Stream.of(
|
||||
currentRoom.getObjects().stream(),
|
||||
currentRoom.getMobs().stream(),
|
||||
currentRoom.getDroppedItems().stream())
|
||||
.flatMap(Function.identity()).toList();*/ // For some reason doesn't compile
|
||||
|
||||
List<? extends Selectable> combinedObjects = Stream.concat(Stream.concat(
|
||||
currentRoom.getObjects().stream(),
|
||||
currentRoom.getMobs().stream()
|
||||
), currentRoom.getDroppedItems().stream()).toList();
|
||||
|
||||
Set<Selectable> selectedObjects = combinedObjects.stream().filter(gameObject -> {
|
||||
if (!gameObject.isSelectable()) return false;
|
||||
BufferedImage texture = gameObject.getTexture();
|
||||
RoomCords cords = gameObject.getCords();
|
||||
|
||||
int relativeMouseX = mouseX - startX;
|
||||
int relativeMouseY = (mouseY * 2 - startY);
|
||||
int objXStart = cords.getX();
|
||||
int objYStart = cords.getY();
|
||||
int objXEnd = cords.getX() + texture.getWidth();
|
||||
int objYEnd = cords.getY() + texture.getHeight();
|
||||
|
||||
int playerMiddleX = playerCords.getX() + (playerTexture.getWidth() / 2);
|
||||
int playerMiddleY = playerCords.getY() + (playerTexture.getHeight() / 2);
|
||||
|
||||
double distance = distancePointToRect(
|
||||
playerMiddleX, playerMiddleY,
|
||||
objXStart, objYStart,
|
||||
objXEnd, objYEnd
|
||||
);
|
||||
|
||||
if (distance > playerConfig.playerReach()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return
|
||||
relativeMouseX >= cords.getX() &&
|
||||
relativeMouseX < cords.getX() + texture.getWidth() &&
|
||||
relativeMouseY >= cords.getY() &&
|
||||
relativeMouseY < cords.getY() + texture.getHeight();
|
||||
}).collect(Collectors.toSet());
|
||||
|
||||
Set<Selectable> changedObjects = new HashSet<>();
|
||||
|
||||
for (Selectable object : combinedObjects) {
|
||||
boolean newValue = selectedObjects.contains(object);
|
||||
boolean changed = object.isSelected() != newValue;
|
||||
|
||||
if (changed) {
|
||||
object.setSelected(newValue);
|
||||
changedObjects.add(object);
|
||||
}
|
||||
}
|
||||
|
||||
List<RerenderScreen.ScreenPart> parts = new ArrayList<>();
|
||||
|
||||
for (Selectable object : changedObjects) {
|
||||
RoomCords cords = object.getCords();
|
||||
BufferedImage objectTexture = object.getTexture();
|
||||
|
||||
int forStartX = cords.getX();
|
||||
int forStartY = cords.getY();
|
||||
int forEndX = cords.getX() + objectTexture.getWidth() - 1;
|
||||
int forEndY = cords.getY() + objectTexture.getHeight();
|
||||
|
||||
RerenderUtils.rerenderPart(
|
||||
forStartX,
|
||||
forEndX,
|
||||
forStartY,
|
||||
forEndY,
|
||||
startX,
|
||||
startY,
|
||||
currentRoom,
|
||||
room,
|
||||
player,
|
||||
screenBuffer,
|
||||
resourceManager,
|
||||
debugging,
|
||||
gameState.getOtherPlayers()
|
||||
);
|
||||
|
||||
parts.add(new RerenderScreen.ScreenPart(
|
||||
new TerminalPosition(forStartX, forStartY),
|
||||
new TerminalPosition(forEndX * 2 + 1 + startX, forEndY + startY)
|
||||
));
|
||||
}
|
||||
|
||||
if (!parts.isEmpty()) {
|
||||
eventManager.emitEvent(new RerenderScreen(parts.toArray(RerenderScreen.ScreenPart[]::new)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package cz.jzitnik.client.events.handlers;
|
||||
|
||||
import com.googlecode.lanterna.TerminalPosition;
|
||||
import com.googlecode.lanterna.input.KeyStroke;
|
||||
import cz.jzitnik.client.annotations.EventHandler;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectConfig;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.config.Debugging;
|
||||
import cz.jzitnik.client.config.PlayerConfig;
|
||||
import cz.jzitnik.client.events.*;
|
||||
import cz.jzitnik.client.game.*;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.client.states.RenderState;
|
||||
import cz.jzitnik.client.states.ScreenBuffer;
|
||||
import cz.jzitnik.client.states.PlayerMovementState;
|
||||
import cz.jzitnik.client.states.TerminalState;
|
||||
import cz.jzitnik.client.ui.Stats;
|
||||
import cz.jzitnik.client.utils.RerenderUtils;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import cz.jzitnik.common.socket.messages.player.PlayerMove;
|
||||
import cz.jzitnik.common.socket.messages.player.PlayerRotation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
@Slf4j
|
||||
@EventHandler(PlayerMoveEvent.class)
|
||||
public class PlayerMoveEventHandler extends AbstractEventHandler<PlayerMoveEvent> {
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
@InjectState
|
||||
private ScreenBuffer screenBuffer;
|
||||
@InjectDependency
|
||||
private ResourceManager resourceManager;
|
||||
@InjectConfig
|
||||
private Debugging debugging;
|
||||
@InjectConfig
|
||||
private PlayerConfig playerConfig;
|
||||
@InjectState
|
||||
private RenderState renderState;
|
||||
@InjectState
|
||||
private PlayerMovementState playerMovementState;
|
||||
@InjectDependency
|
||||
private Stats stats;
|
||||
|
||||
@Override
|
||||
public void handle(PlayerMoveEvent event) {
|
||||
if (renderState.isTerminalTooSmall()) {
|
||||
return;
|
||||
}
|
||||
|
||||
KeyStroke keyStroke = event.getKeyStroke();
|
||||
Player player = gameState.getPlayer();
|
||||
RoomCords playerCords = player.getPlayerCords();
|
||||
GameRoom currentRoom = gameState.getCurrentRoom();
|
||||
|
||||
boolean isSprinting = player.getStamina() > 0 && switch (playerConfig.sprintKey()) {
|
||||
case CTRL -> event.getKeyStroke().isCtrlDown();
|
||||
case SHIFT -> event.getKeyStroke().isShiftDown();
|
||||
case ALT -> event.getKeyStroke().isAltDown();
|
||||
};
|
||||
|
||||
int moveStep = isSprinting ? playerConfig.playerMoveDistanceSprinting() : playerConfig.playerMoveDistance();
|
||||
|
||||
int originalPlayerX = playerCords.getX();
|
||||
int originalPlayerY = playerCords.getY();
|
||||
switch (keyStroke.getCharacter()) {
|
||||
case 'w' -> {
|
||||
if (originalPlayerY <= 10) {
|
||||
if (originalPlayerX >= 80 && originalPlayerX <= 105) {
|
||||
player.setPlayerRotation(PlayerRotation.BACK);
|
||||
eventManager.emitEvent(new RoomChangeEvent(FullRoomDrawHandler.DoorPosition.TOP));
|
||||
}
|
||||
return;
|
||||
}
|
||||
playerCords.updateCordsWithColliders(currentRoom.getColliders(), player.getPlayerCords().getX(), playerCords.getY() - moveStep, player.getCollider());
|
||||
player.setPlayerRotation(PlayerRotation.BACK);
|
||||
}
|
||||
case 'a' -> {
|
||||
if (originalPlayerX <= 30) {
|
||||
if (originalPlayerY >= 35 && originalPlayerY <= 65) {
|
||||
player.setPlayerRotation(PlayerRotation.LEFT);
|
||||
eventManager.emitEvent(new RoomChangeEvent(FullRoomDrawHandler.DoorPosition.LEFT));
|
||||
}
|
||||
return;
|
||||
}
|
||||
playerCords.updateCordsWithColliders(currentRoom.getColliders(), player.getPlayerCords().getX() - moveStep, playerCords.getY(), player.getCollider());
|
||||
player.setPlayerRotation(PlayerRotation.LEFT);
|
||||
}
|
||||
case 's' -> {
|
||||
if (originalPlayerY >= 110) {
|
||||
if (originalPlayerX >= 75 && originalPlayerX <= 105) {
|
||||
player.setPlayerRotation(PlayerRotation.FRONT);
|
||||
eventManager.emitEvent(new RoomChangeEvent(FullRoomDrawHandler.DoorPosition.BOTTOM));
|
||||
}
|
||||
return;
|
||||
}
|
||||
playerCords.updateCordsWithColliders(currentRoom.getColliders(), player.getPlayerCords().getX(), playerCords.getY() + moveStep, player.getCollider());
|
||||
player.setPlayerRotation(PlayerRotation.FRONT);
|
||||
}
|
||||
case 'd' -> {
|
||||
if (originalPlayerX >= 155) {
|
||||
if (originalPlayerY >= 40 && originalPlayerY <= 60) {
|
||||
player.setPlayerRotation(PlayerRotation.RIGHT);
|
||||
eventManager.emitEvent(new RoomChangeEvent(FullRoomDrawHandler.DoorPosition.RIGHT));
|
||||
}
|
||||
return;
|
||||
}
|
||||
playerCords.updateCordsWithColliders(currentRoom.getColliders(), player.getPlayerCords().getX() + moveStep, playerCords.getY(), player.getCollider());
|
||||
player.setPlayerRotation(PlayerRotation.RIGHT);
|
||||
}
|
||||
}
|
||||
playerMovementState.setLastMovement(System.currentTimeMillis());
|
||||
if (isSprinting) {
|
||||
player.decreaseStamina();
|
||||
stats.rerender();
|
||||
}
|
||||
|
||||
int newPlayerX = playerCords.getX();
|
||||
int newPlayerY = playerCords.getY();
|
||||
if (debugging.showPlayerCordsLogs()) {
|
||||
log.debug("x: {}, y: {}", newPlayerX, newPlayerY);
|
||||
}
|
||||
BufferedImage playerTexture = player.getTexture(resourceManager);
|
||||
int forStartX = Math.min(originalPlayerX, newPlayerX);
|
||||
int forStartY = Math.min(originalPlayerY, newPlayerY);
|
||||
int forEndX = Math.max(originalPlayerX, newPlayerX) + playerTexture.getWidth();
|
||||
int forEndY = Math.max(originalPlayerY, newPlayerY) + playerTexture.getHeight();
|
||||
|
||||
BufferedImage room = resourceManager.getResource(currentRoom.getTexture());
|
||||
var start = RerenderUtils.getStart(room, terminalState.getTerminalScreen().getTerminalSize());
|
||||
int startX = start.getX();
|
||||
int startY = start.getY();
|
||||
|
||||
RerenderUtils.rerenderPart(forStartX, forEndX, forStartY, forEndY, startX, startY, currentRoom, room, player, screenBuffer, resourceManager, debugging, gameState.getOtherPlayers());
|
||||
|
||||
eventManager.emitEvent(new Event[]{
|
||||
new SendSocketMessageEvent(new PlayerMove(playerCords, player.getPlayerRotation())),
|
||||
new MouseMoveEvent(null),
|
||||
new RerenderScreen(
|
||||
new RerenderScreen.ScreenPart[]{
|
||||
new RerenderScreen.ScreenPart(
|
||||
new TerminalPosition(forStartX + startX, forStartY + startY),
|
||||
new TerminalPosition(forEndX + 1 + startX, forEndY + startY)
|
||||
),
|
||||
new RerenderScreen.ScreenPart(
|
||||
new TerminalPosition(Stats.OFFSET_X, Stats.OFFSET_X),
|
||||
new TerminalPosition(Stats.OFFSET_X + Stats.WIDTH, Stats.OFFSET_Y + Stats.HEIGHT)
|
||||
)
|
||||
}
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
public static enum SprintKey {
|
||||
CTRL,
|
||||
SHIFT,
|
||||
ALT
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cz.jzitnik.client.events.handlers;
|
||||
|
||||
import com.googlecode.lanterna.TerminalPosition;
|
||||
import cz.jzitnik.client.annotations.EventHandler;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.events.RenderStats;
|
||||
import cz.jzitnik.client.events.RerenderScreen;
|
||||
import cz.jzitnik.client.ui.Stats;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
|
||||
@EventHandler(RenderStats.class)
|
||||
public class RenderStatsHandler extends AbstractEventHandler<RenderStats> {
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectDependency
|
||||
private Stats stats;
|
||||
|
||||
@Override
|
||||
public void handle(RenderStats event) {
|
||||
stats.rerender();
|
||||
eventManager.emitEvent(new RerenderScreen(new RerenderScreen.ScreenPart(
|
||||
new TerminalPosition(Stats.OFFSET_X, Stats.OFFSET_X),
|
||||
new TerminalPosition(Stats.OFFSET_X + Stats.WIDTH, Stats.OFFSET_Y + Stats.HEIGHT)
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package cz.jzitnik.client.events.handlers;
|
||||
|
||||
import com.googlecode.lanterna.TerminalPosition;
|
||||
import cz.jzitnik.client.annotations.EventHandler;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectConfig;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.config.Debugging;
|
||||
import cz.jzitnik.client.events.RerenderPart;
|
||||
import cz.jzitnik.client.events.RerenderScreen;
|
||||
import cz.jzitnik.client.game.GameRoom;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.game.ResourceManager;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.client.states.ScreenBuffer;
|
||||
import cz.jzitnik.client.states.TerminalState;
|
||||
import cz.jzitnik.client.utils.RerenderUtils;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
@EventHandler(RerenderPart.class)
|
||||
public class RerenderPartHandler extends AbstractEventHandler<RerenderPart> {
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
|
||||
@InjectState
|
||||
private ScreenBuffer screenBuffer;
|
||||
|
||||
@InjectConfig
|
||||
private Debugging debugging;
|
||||
|
||||
@InjectDependency
|
||||
private ResourceManager resourceManager;
|
||||
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@Override
|
||||
public void handle(RerenderPart event) {
|
||||
int forStartX = event.getForStartX();
|
||||
int forEndX = event.getForEndX();
|
||||
int forStartY = event.getForStartY();
|
||||
int forEndY = event.getForEndY();
|
||||
|
||||
GameRoom currentRoom = gameState.getCurrentRoom();
|
||||
BufferedImage room = resourceManager.getResource(currentRoom.getTexture());
|
||||
|
||||
RoomCords start = RerenderUtils.getStart(room, terminalState.getTerminalScreen().getTerminalSize());
|
||||
|
||||
RerenderUtils.rerenderPart(
|
||||
forStartX,
|
||||
forEndX,
|
||||
forStartY,
|
||||
forEndY,
|
||||
start.getX(),
|
||||
start.getY(),
|
||||
currentRoom,
|
||||
room,
|
||||
gameState.getPlayer(),
|
||||
screenBuffer,
|
||||
resourceManager,
|
||||
debugging,
|
||||
gameState.getOtherPlayers()
|
||||
);
|
||||
|
||||
eventManager.emitEvent(
|
||||
new RerenderScreen(new RerenderScreen.ScreenPart(
|
||||
new TerminalPosition(forStartX, forStartY),
|
||||
new TerminalPosition(forEndX * 2 + 1 + start.getX(), forEndY + start.getY())
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package cz.jzitnik.client.events.handlers;
|
||||
|
||||
import cz.jzitnik.client.annotations.EventHandler;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.events.RoomChangeEvent;
|
||||
import cz.jzitnik.client.events.SendSocketMessageEvent;
|
||||
import cz.jzitnik.client.game.GameRoom;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import cz.jzitnik.client.utils.roomtasks.RoomTaskScheduler;
|
||||
import cz.jzitnik.common.socket.messages.room.MovePlayerRoom;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@EventHandler(RoomChangeEvent.class)
|
||||
public class RoomChangeEventHandler extends AbstractEventHandler<RoomChangeEvent> {
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
@InjectDependency
|
||||
private RoomTaskScheduler roomTaskScheduler;
|
||||
|
||||
@Override
|
||||
public void handle(RoomChangeEvent event) {
|
||||
RoomCords playerCords = gameState.getPlayer().getPlayerCords();
|
||||
RoomCords oldCords = playerCords.clone();
|
||||
GameRoom currentRoom = gameState.getCurrentRoom();
|
||||
GameRoom newRoom = switch (event.door()) {
|
||||
case LEFT -> currentRoom.getLeft();
|
||||
case RIGHT -> currentRoom.getRight();
|
||||
case TOP -> currentRoom.getUp();
|
||||
case BOTTOM -> currentRoom.getDown();
|
||||
};
|
||||
|
||||
if (newRoom == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.door()) {
|
||||
case LEFT -> playerCords.updateCords(155, playerCords.getY());
|
||||
case RIGHT -> playerCords.updateCords(30, playerCords.getY());
|
||||
case TOP -> playerCords.updateCords(playerCords.getX(), 110);
|
||||
case BOTTOM -> playerCords.updateCords(playerCords.getX(), 10);
|
||||
}
|
||||
|
||||
eventManager.emitEvent(new SendSocketMessageEvent(new MovePlayerRoom(newRoom.getId(), oldCords, playerCords)));
|
||||
|
||||
gameState.setCurrentRoom(newRoom);
|
||||
scheduler.schedule(() -> roomTaskScheduler.setupNewSchedulers(newRoom), 200, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cz.jzitnik.client.events.handlers;
|
||||
|
||||
import cz.jzitnik.client.annotations.EventHandler;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.events.SendSocketMessageEvent;
|
||||
import cz.jzitnik.client.socket.Client;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@EventHandler(SendSocketMessageEvent.class)
|
||||
public class SendSocketMessageEventHandler extends AbstractEventHandler<SendSocketMessageEvent> {
|
||||
@InjectDependency
|
||||
private Client client;
|
||||
|
||||
@Override
|
||||
public void handle(SendSocketMessageEvent event) {
|
||||
try {
|
||||
client.send(event.message());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package cz.jzitnik.client.events.handlers;
|
||||
|
||||
import com.googlecode.lanterna.TerminalSize;
|
||||
import cz.jzitnik.client.annotations.EventHandler;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.events.FullRoomDraw;
|
||||
import cz.jzitnik.client.events.TerminalResizeEvent;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.states.ScreenBuffer;
|
||||
import cz.jzitnik.client.ui.pixels.AlphaPixel;
|
||||
import cz.jzitnik.client.ui.pixels.Empty;
|
||||
import cz.jzitnik.client.ui.pixels.Pixel;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@EventHandler(TerminalResizeEvent.class)
|
||||
public class TerminalResizeEventHandler extends AbstractEventHandler<TerminalResizeEvent> {
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectState
|
||||
private ScreenBuffer screenBuffer;
|
||||
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
|
||||
private boolean screenRerendering = false;
|
||||
|
||||
@Override
|
||||
public void handle(TerminalResizeEvent event) {
|
||||
TerminalSize size = event.getNewSize();
|
||||
int width = size.getColumns();
|
||||
int height = size.getRows() * 2;
|
||||
|
||||
Pixel[][] buffer = new Pixel[height][width];
|
||||
AlphaPixel[][] globalOverride = new AlphaPixel[height][width];
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
buffer[y][x] = new Empty();
|
||||
globalOverride[y][x] = new Empty();
|
||||
}
|
||||
}
|
||||
screenBuffer.setRenderedBuffer(buffer);
|
||||
screenBuffer.setGlobalOverrideBuffer(globalOverride);
|
||||
|
||||
if (gameState.getScreen() != null) {
|
||||
if (screenRerendering) {
|
||||
return;
|
||||
} else {
|
||||
screenRerendering = true;
|
||||
gameState.getScreen().fullRender();
|
||||
screenRerendering = false;
|
||||
}
|
||||
} else {
|
||||
eventManager.emitEvent(new FullRoomDraw(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package cz.jzitnik.client.events.handlers;
|
||||
|
||||
import com.googlecode.lanterna.SGR;
|
||||
import com.googlecode.lanterna.TerminalSize;
|
||||
import com.googlecode.lanterna.TextColor;
|
||||
import com.googlecode.lanterna.screen.Screen;
|
||||
import com.googlecode.lanterna.screen.TerminalScreen;
|
||||
import cz.jzitnik.client.annotations.EventHandler;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.events.TerminalTooSmallEvent;
|
||||
import cz.jzitnik.client.states.TerminalState;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.EnumSet;
|
||||
|
||||
@EventHandler(TerminalTooSmallEvent.class)
|
||||
public class TerminalTooSmallEventHandler extends AbstractEventHandler<TerminalTooSmallEvent> {
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
|
||||
@Override
|
||||
public void handle(TerminalTooSmallEvent event) {
|
||||
// Directly render the message for the user
|
||||
TerminalScreen terminalScreen = terminalState.getTerminalScreen();
|
||||
terminalScreen.clear();
|
||||
var tg = terminalState.getTextGraphics();
|
||||
|
||||
TerminalSize terminalSize = terminalScreen.getTerminalSize();
|
||||
String text = "Terminal too small!";
|
||||
String subText = "Please resize your terminal";
|
||||
|
||||
int startY = terminalSize.getRows() / 2;
|
||||
|
||||
int textStartX = (terminalSize.getColumns() / 2) - (text.length() / 2);
|
||||
for (char character : text.toCharArray()) {
|
||||
tg.setForegroundColor(TextColor.ANSI.RED);
|
||||
tg.setBackgroundColor(TextColor.ANSI.DEFAULT);
|
||||
tg.setModifiers(EnumSet.of(SGR.BOLD));
|
||||
tg.setCharacter(textStartX++, startY, character);
|
||||
}
|
||||
int subTextStartX = (terminalSize.getColumns() / 2) - (subText.length() / 2);
|
||||
for (char character : subText.toCharArray()) {
|
||||
tg.setForegroundColor(TextColor.ANSI.BLACK_BRIGHT);
|
||||
tg.setBackgroundColor(TextColor.ANSI.DEFAULT);
|
||||
tg.setCharacter(subTextStartX++, startY + 1, character);
|
||||
}
|
||||
|
||||
try {
|
||||
terminalScreen.refresh(Screen.RefreshType.COMPLETE);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
game/src/main/java/cz/jzitnik/client/game/Constants.java
Normal file
7
game/src/main/java/cz/jzitnik/client/game/Constants.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package cz.jzitnik.client.game;
|
||||
|
||||
import com.googlecode.lanterna.TextColor;
|
||||
|
||||
public class Constants {
|
||||
public static final TextColor BACKGROUND_COLOR = new TextColor.RGB(4, 4, 16);
|
||||
}
|
||||
10
game/src/main/java/cz/jzitnik/client/game/GamePlayer.java
Normal file
10
game/src/main/java/cz/jzitnik/client/game/GamePlayer.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package cz.jzitnik.client.game;
|
||||
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
public interface GamePlayer {
|
||||
RoomCords getPlayerCords();
|
||||
BufferedImage getTexture(ResourceManager resourceManager);
|
||||
}
|
||||
88
game/src/main/java/cz/jzitnik/client/game/GameRoom.java
Normal file
88
game/src/main/java/cz/jzitnik/client/game/GameRoom.java
Normal file
@@ -0,0 +1,88 @@
|
||||
package cz.jzitnik.client.game;
|
||||
|
||||
import com.fasterxml.jackson.annotation.*;
|
||||
import cz.jzitnik.client.game.mobs.Mob;
|
||||
import cz.jzitnik.client.game.objects.DroppedItem;
|
||||
import cz.jzitnik.client.game.objects.GameObject;
|
||||
import cz.jzitnik.client.ui.pixels.Empty;
|
||||
import cz.jzitnik.client.ui.pixels.Pixel;
|
||||
import cz.jzitnik.common.models.coordinates.RoomPart;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@JsonIdentityInfo(
|
||||
generator = ObjectIdGenerators.PropertyGenerator.class,
|
||||
property = "id"
|
||||
)
|
||||
@Getter
|
||||
public class GameRoom {
|
||||
private final String id;
|
||||
@JsonIgnore
|
||||
private final Pixel[][] overrideBuffer;
|
||||
@JsonIgnore
|
||||
private final ResourceManager.Resource texture;
|
||||
@JsonIgnore
|
||||
private final List<GameObject> objects = new ArrayList<>();
|
||||
@JsonIgnore
|
||||
private final List<Mob> mobs = new ArrayList<>();
|
||||
@JsonIgnore
|
||||
private final Set<DroppedItem> droppedItems = new HashSet<>();
|
||||
@JsonIgnore
|
||||
private final List<RoomPart> colliders = new ArrayList<>();
|
||||
|
||||
private GameRoom left;
|
||||
private GameRoom right;
|
||||
private GameRoom up;
|
||||
private GameRoom down;
|
||||
|
||||
@JsonCreator
|
||||
public GameRoom(
|
||||
@JsonProperty("id") String id,
|
||||
@JsonProperty("objects") List<GameObject> objects,
|
||||
@JsonProperty("colliders") List<RoomPart> colliders,
|
||||
@JsonProperty("mobs") List<Mob> mobs,
|
||||
@JsonProperty("texture") ResourceManager.Resource texture
|
||||
) {
|
||||
this.id = id;
|
||||
this.texture = texture;
|
||||
|
||||
if (objects != null) this.objects.addAll(objects);
|
||||
if (colliders != null) this.colliders.addAll(colliders);
|
||||
if (mobs != null) this.mobs.addAll(mobs);
|
||||
|
||||
int height = 225;
|
||||
int width = 225 * 2;
|
||||
|
||||
Pixel[][] overrideBuffer = new Pixel[height][width];
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
overrideBuffer[y][x] = new Empty();
|
||||
}
|
||||
}
|
||||
this.overrideBuffer = overrideBuffer;
|
||||
}
|
||||
|
||||
@JsonSetter("west")
|
||||
public void setWest(GameRoom west) {
|
||||
if (west != null) this.left = west;
|
||||
}
|
||||
|
||||
@JsonSetter("east")
|
||||
public void setEast(GameRoom east) {
|
||||
if (east != null) this.right = east;
|
||||
}
|
||||
|
||||
@JsonSetter("north")
|
||||
public void setNorth(GameRoom north) {
|
||||
if (north != null) this.up = north;
|
||||
}
|
||||
|
||||
@JsonSetter("south")
|
||||
public void setSouth(GameRoom south) {
|
||||
if (south != null) this.down = south;
|
||||
}
|
||||
}
|
||||
54
game/src/main/java/cz/jzitnik/client/game/GameState.java
Normal file
54
game/src/main/java/cz/jzitnik/client/game/GameState.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package cz.jzitnik.client.game;
|
||||
|
||||
import cz.jzitnik.client.annotations.State;
|
||||
import cz.jzitnik.client.game.objects.Interactable;
|
||||
import cz.jzitnik.client.screens.Screen;
|
||||
import cz.jzitnik.client.utils.DependencyManager;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@State
|
||||
public class GameState {
|
||||
private final DependencyManager dependencyManager; // Maybe transient in the future
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private GameRoom currentRoom;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private List<GameRoom> allRooms;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private Player player;
|
||||
|
||||
private final List<OtherPlayer> otherPlayers = new ArrayList<>();
|
||||
|
||||
public List<OtherPlayer> getOtherPlayers() {
|
||||
return otherPlayers.stream().filter(OtherPlayer::isVisible).toList();
|
||||
}
|
||||
|
||||
public List<OtherPlayer> getAllOtherPlayers() {
|
||||
return otherPlayers;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private Interactable interacting;
|
||||
|
||||
@Getter
|
||||
private Screen screen;
|
||||
|
||||
public void setScreen(Screen screen) {
|
||||
if (screen != null) {
|
||||
dependencyManager.inject(screen);
|
||||
}
|
||||
this.screen = screen;
|
||||
}
|
||||
}
|
||||
41
game/src/main/java/cz/jzitnik/client/game/OtherPlayer.java
Normal file
41
game/src/main/java/cz/jzitnik/client/game/OtherPlayer.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package cz.jzitnik.client.game;
|
||||
|
||||
import cz.jzitnik.client.game.mobs.HittableMob;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.common.models.player.PlayerCreation;
|
||||
import cz.jzitnik.common.socket.messages.player.PlayerRotation;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
@Getter
|
||||
public class OtherPlayer implements GamePlayer {
|
||||
private final int id;
|
||||
private boolean hitAnimationOn = false;
|
||||
private final RoomCords playerCords;
|
||||
@Setter
|
||||
private PlayerRotation playerRotation = PlayerRotation.FRONT;
|
||||
@Setter
|
||||
private boolean visible;
|
||||
|
||||
public OtherPlayer(PlayerCreation playerCreation) {
|
||||
this.id = playerCreation.getId();
|
||||
this.playerCords = playerCreation.getPlayerCords();
|
||||
}
|
||||
|
||||
public BufferedImage getTexture(ResourceManager resourceManager) {
|
||||
BufferedImage resource = resourceManager.getResource(switch (playerRotation) {
|
||||
case FRONT -> ResourceManager.Resource.PLAYER_FRONT;
|
||||
case BACK -> ResourceManager.Resource.PLAYER_BACK;
|
||||
case LEFT -> ResourceManager.Resource.PLAYER_LEFT;
|
||||
case RIGHT -> ResourceManager.Resource.PLAYER_RIGHT;
|
||||
});
|
||||
|
||||
if (hitAnimationOn) {
|
||||
return HittableMob.applyRedFactor(resource);
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
159
game/src/main/java/cz/jzitnik/client/game/Player.java
Normal file
159
game/src/main/java/cz/jzitnik/client/game/Player.java
Normal file
@@ -0,0 +1,159 @@
|
||||
package cz.jzitnik.client.game;
|
||||
|
||||
import cz.jzitnik.client.events.RerenderPart;
|
||||
import cz.jzitnik.client.game.items.GameItem;
|
||||
import cz.jzitnik.client.game.items.types.interfaces.WeaponInterface;
|
||||
import cz.jzitnik.client.game.mobs.HittableMob;
|
||||
import cz.jzitnik.common.models.coordinates.RoomPart;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.client.ui.Inventory;
|
||||
import cz.jzitnik.client.utils.DependencyManager;
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import cz.jzitnik.common.models.player.PlayerCreation;
|
||||
import cz.jzitnik.common.socket.messages.player.PlayerRotation;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Getter
|
||||
@Slf4j
|
||||
public class Player implements GamePlayer {
|
||||
private final int id;
|
||||
public static final int MAX_STAMINA = 20;
|
||||
public static final int MAX_HEALTH = 30;
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
|
||||
private final RoomCords playerCords;
|
||||
private final RoomPart collider;
|
||||
private final GameItem[] inventory = new GameItem[Inventory.ITEMS_X * Inventory.ITEMS_Y];
|
||||
@Setter
|
||||
private PlayerRotation playerRotation = PlayerRotation.FRONT;
|
||||
@Setter
|
||||
private GameItem selectedItem;
|
||||
private int health = MAX_HEALTH;
|
||||
private int stamina = MAX_STAMINA;
|
||||
private boolean swinging = false;
|
||||
private boolean hitAnimationOn = false;
|
||||
private ScheduledFuture<?> currentTimeoutHitAnimation = null;
|
||||
|
||||
public Player(PlayerCreation playerCreation) {
|
||||
this.playerCords = playerCreation.getPlayerCords();
|
||||
this.collider = playerCreation.getCollider();
|
||||
this.id = playerCreation.getId();
|
||||
}
|
||||
|
||||
public void increaseStamina() {
|
||||
stamina++;
|
||||
}
|
||||
|
||||
public void decreaseStamina() {
|
||||
stamina--;
|
||||
}
|
||||
|
||||
public void addHealth(int amount) {
|
||||
health = Math.min(MAX_HEALTH, health + amount);
|
||||
}
|
||||
|
||||
public boolean dealDamage(int amount, DependencyManager dependencyManager) {
|
||||
if (health - amount <= 0) {
|
||||
health = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
health -= amount;
|
||||
|
||||
if (hitAnimationOn) {
|
||||
if (currentTimeoutHitAnimation != null && !currentTimeoutHitAnimation.isDone()) {
|
||||
currentTimeoutHitAnimation.cancel(false);
|
||||
}
|
||||
} else {
|
||||
hitAnimationOn = true;
|
||||
rerender(dependencyManager);
|
||||
}
|
||||
|
||||
currentTimeoutHitAnimation = scheduler.schedule(() -> {
|
||||
hitAnimationOn = false;
|
||||
rerender(dependencyManager);
|
||||
}, 250, TimeUnit.MILLISECONDS);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void rerender(DependencyManager dependencyManager) {
|
||||
ResourceManager resourceManager = dependencyManager.getDependencyOrThrow(ResourceManager.class);
|
||||
EventManager eventManager = dependencyManager.getDependencyOrThrow(EventManager.class);
|
||||
|
||||
int forStartX = playerCords.getX();
|
||||
int forStartY = playerCords.getY();
|
||||
|
||||
BufferedImage playerTexture = getTexture(resourceManager);
|
||||
int forEndX = playerCords.getX() + playerTexture.getWidth() - 1;
|
||||
int forEndY = playerCords.getY() + playerTexture.getHeight();
|
||||
|
||||
eventManager.emitEvent(new Event[]{
|
||||
new RerenderPart(forStartX, forEndX, forStartY, forEndY),
|
||||
});
|
||||
}
|
||||
|
||||
public BufferedImage getTexture(ResourceManager resourceManager) {
|
||||
BufferedImage resource = resourceManager.getResource(switch (playerRotation) {
|
||||
case FRONT -> ResourceManager.Resource.PLAYER_FRONT;
|
||||
case BACK -> ResourceManager.Resource.PLAYER_BACK;
|
||||
case LEFT -> ResourceManager.Resource.PLAYER_LEFT;
|
||||
case RIGHT -> ResourceManager.Resource.PLAYER_RIGHT;
|
||||
});
|
||||
|
||||
if (hitAnimationOn) {
|
||||
return HittableMob.applyRedFactor(resource);
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
public int getDamageDeal() {
|
||||
int damage = 1;
|
||||
|
||||
// Probably in the future, there will be more logic like potions, etc.
|
||||
log.debug("Selected item: {}", selectedItem);
|
||||
if (selectedItem != null && selectedItem.getType() instanceof WeaponInterface weapon) {
|
||||
damage = weapon.getDamageDeal();
|
||||
}
|
||||
|
||||
return damage;
|
||||
}
|
||||
|
||||
public boolean addItem(GameItem item) {
|
||||
boolean added = false;
|
||||
for (int i = 0; i < inventory.length; i++) {
|
||||
if (inventory[i] == null) {
|
||||
inventory[i] = item;
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
public void swing(int delayMs) {
|
||||
if (swinging) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("Started swinging");
|
||||
swinging = true;
|
||||
|
||||
scheduler.schedule(() -> {
|
||||
swinging = false;
|
||||
log.debug("Swinging done");
|
||||
}, delayMs, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package cz.jzitnik.client.game;
|
||||
|
||||
import cz.jzitnik.client.annotations.Dependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
|
||||
@Dependency
|
||||
public class ResourceManager {
|
||||
@InjectDependency
|
||||
private ClassLoader classLoader;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum Resource {
|
||||
ROOM1("rooms/1.png"),
|
||||
ROOM2("rooms/2.png"),
|
||||
ROOM3("rooms/3.png"),
|
||||
ROOM4("rooms/4.png"),
|
||||
ROOM_FROZEN("rooms/frozen.png"),
|
||||
|
||||
PLAYER_FRONT("player/front.png"),
|
||||
PLAYER_BACK("player/back.png"),
|
||||
PLAYER_LEFT("player/left.png"),
|
||||
PLAYER_RIGHT("player/right.png"),
|
||||
|
||||
CHEST("chest.png"),
|
||||
|
||||
WOODEN_SWORD("tools/wooden_sword.png"),
|
||||
|
||||
APPLE("food/apple.png"),
|
||||
|
||||
DOORS("rooms/doors.png"),
|
||||
|
||||
STAMINA("ui/stamina.png"),
|
||||
HEART("ui/heart.png");
|
||||
|
||||
private final String path;
|
||||
}
|
||||
|
||||
private final HashMap<Resource, BufferedImage> resourceCache = new HashMap<>();
|
||||
|
||||
public BufferedImage getResource(Resource resource) {
|
||||
if (resourceCache.containsKey(resource)) {
|
||||
return resourceCache.get(resource);
|
||||
}
|
||||
|
||||
InputStream is = classLoader.getResourceAsStream("textures/" + resource.getPath());
|
||||
if (is == null) {
|
||||
throw new RuntimeException("Image not found in resources!");
|
||||
}
|
||||
|
||||
try {
|
||||
BufferedImage image = ImageIO.read(is);
|
||||
resourceCache.put(resource, image);
|
||||
return image;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public BufferedImage getResource(String path) {
|
||||
InputStream is = classLoader.getResourceAsStream(path);
|
||||
if (is == null) {
|
||||
throw new RuntimeException("Image not found in resources!");
|
||||
}
|
||||
|
||||
try {
|
||||
return ImageIO.read(is);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public InputStream getResourceAsStream(String path) {
|
||||
return classLoader.getResourceAsStream(path);
|
||||
}
|
||||
}
|
||||
18
game/src/main/java/cz/jzitnik/client/game/dialog/Dialog.java
Normal file
18
game/src/main/java/cz/jzitnik/client/game/dialog/Dialog.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package cz.jzitnik.client.game.dialog;
|
||||
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public class Dialog implements Event {
|
||||
/**
|
||||
* Characters per second
|
||||
*/
|
||||
private int typingSpeed = 10;
|
||||
private final String text;
|
||||
private final OnEnd onEnd;
|
||||
}
|
||||
13
game/src/main/java/cz/jzitnik/client/game/dialog/OnEnd.java
Normal file
13
game/src/main/java/cz/jzitnik/client/game/dialog/OnEnd.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package cz.jzitnik.client.game.dialog;
|
||||
|
||||
public interface OnEnd {
|
||||
record RunCode(Runnable runnable, OnEnd onEnd) implements OnEnd {}
|
||||
|
||||
record Continue(Dialog nextDialog) implements OnEnd {
|
||||
}
|
||||
|
||||
record AskQuestion(Answer[] answers) implements OnEnd {
|
||||
public record Answer(String answer, Dialog dialog) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cz.jzitnik.client.game.exceptions;
|
||||
|
||||
public class InvalidCoordinatesException extends RuntimeException {
|
||||
public InvalidCoordinatesException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public InvalidCoordinatesException() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cz.jzitnik.client.game.items;
|
||||
|
||||
import com.fasterxml.jackson.annotation.*;
|
||||
import cz.jzitnik.client.game.ResourceManager;
|
||||
import cz.jzitnik.client.game.items.types.ItemType;
|
||||
import cz.jzitnik.client.game.utils.Renderable;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
@Getter
|
||||
public class GameItem implements Renderable {
|
||||
private final ItemType<?> type;
|
||||
@JsonIgnore
|
||||
private final BufferedImage texture;
|
||||
private final String name;
|
||||
private final int id;
|
||||
|
||||
@JsonCreator
|
||||
public GameItem(
|
||||
@JsonProperty("id") int id,
|
||||
@JsonProperty("name") String name,
|
||||
@JsonProperty("type") ItemType<?> type,
|
||||
@JsonProperty("texture") ResourceManager.Resource resource,
|
||||
@JacksonInject ResourceManager resourceManager
|
||||
) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.texture = resourceManager.getResource(resource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package cz.jzitnik.client.game.items.types;
|
||||
|
||||
import cz.jzitnik.client.utils.DependencyManager;
|
||||
import cz.jzitnik.client.utils.StateManager;
|
||||
|
||||
public interface InteractableItem {
|
||||
InteractableItemResponse interact(DependencyManager dependencyManager, StateManager stateManager);
|
||||
|
||||
enum InteractableItemResponse {
|
||||
CLEAR_ITEM,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cz.jzitnik.client.game.items.types;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import cz.jzitnik.client.game.items.types.food.Food;
|
||||
import cz.jzitnik.client.game.items.types.weapons.Sword;
|
||||
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
property = "name"
|
||||
)
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = Food.class, name = "food"),
|
||||
@JsonSubTypes.Type(value = Sword.class, name = "weapon_sword")
|
||||
})
|
||||
public interface ItemType<T> {
|
||||
Class<T> getItemType();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cz.jzitnik.client.game.items.types.food;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import cz.jzitnik.client.events.RenderStats;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.game.items.types.InteractableItem;
|
||||
import cz.jzitnik.client.game.items.types.ItemType;
|
||||
import cz.jzitnik.client.utils.DependencyManager;
|
||||
import cz.jzitnik.client.utils.StateManager;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
|
||||
public class Food implements InteractableItem, ItemType<Food> {
|
||||
private final int addHealth;
|
||||
|
||||
@JsonCreator
|
||||
public Food(
|
||||
@JsonProperty("addHealth") int addHealth
|
||||
) {
|
||||
this.addHealth = addHealth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractableItemResponse interact(DependencyManager dependencyManager, StateManager stateManager) {
|
||||
GameState gameState = stateManager.getOrThrow(GameState.class);
|
||||
EventManager eventManager = dependencyManager.getDependencyOrThrow(EventManager.class);
|
||||
|
||||
gameState.getPlayer().addHealth(addHealth);
|
||||
eventManager.emitEvent(new RenderStats());
|
||||
|
||||
return InteractableItemResponse.CLEAR_ITEM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Food> getItemType() {
|
||||
return Food.class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package cz.jzitnik.client.game.items.types.interfaces;
|
||||
|
||||
public interface WeaponInterface {
|
||||
int getDamageDeal();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cz.jzitnik.client.game.items.types.weapons;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class Sword extends Weapon {
|
||||
@JsonCreator
|
||||
public Sword(
|
||||
@JsonProperty("dealDamage") int dealDamage
|
||||
) {
|
||||
super(dealDamage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cz.jzitnik.client.game.items.types.weapons;
|
||||
|
||||
import cz.jzitnik.client.game.items.types.ItemType;
|
||||
import cz.jzitnik.client.game.items.types.interfaces.WeaponInterface;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
public abstract class Weapon implements ItemType<Weapon>, WeaponInterface {
|
||||
private final int dealDamage;
|
||||
|
||||
@Override
|
||||
public final Class<Weapon> getItemType() {
|
||||
return Weapon.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDamageDeal() {
|
||||
return dealDamage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package cz.jzitnik.client.game.mobs;
|
||||
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.common.models.coordinates.RoomPart;
|
||||
import cz.jzitnik.client.game.dialog.Dialog;
|
||||
import cz.jzitnik.client.game.mobs.tasks.MobRoomTask;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.client.states.DialogState;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
@Slf4j
|
||||
public abstract class DialogMob extends Mob {
|
||||
protected Dialog dialog;
|
||||
|
||||
public DialogMob(BufferedImage texture, MobRoomTask[] tasks, RoomCords cords, RoomPart collider, Dialog dialog) {
|
||||
super(texture, tasks, cords, collider);
|
||||
this.dialog = dialog;
|
||||
}
|
||||
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectState
|
||||
private DialogState dialogState;
|
||||
|
||||
@Override
|
||||
public void interact() {
|
||||
log.debug("Interacting with dialog mob!");
|
||||
if (dialogState.getCurrentDialog() == null) {
|
||||
eventManager.emitEvent(dialog);
|
||||
}
|
||||
}
|
||||
}
|
||||
122
game/src/main/java/cz/jzitnik/client/game/mobs/HittableMob.java
Normal file
122
game/src/main/java/cz/jzitnik/client/game/mobs/HittableMob.java
Normal file
@@ -0,0 +1,122 @@
|
||||
package cz.jzitnik.client.game.mobs;
|
||||
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.events.RerenderPart;
|
||||
import cz.jzitnik.common.models.coordinates.RoomPart;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.game.mobs.tasks.MobRoomTask;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import cz.jzitnik.client.utils.roomtasks.RoomTask;
|
||||
import cz.jzitnik.client.utils.roomtasks.RoomTaskScheduler;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
public abstract class HittableMob extends Mob {
|
||||
public abstract void onKilled();
|
||||
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
@Getter
|
||||
protected int health;
|
||||
private ScheduledFuture<?> currentTimeoutHitAnimation = null;
|
||||
|
||||
private boolean hitAnimationOn = false;
|
||||
|
||||
@Override
|
||||
public BufferedImage getTexture() {
|
||||
if (hitAnimationOn) {
|
||||
return applyRedFactor(texture);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
public static BufferedImage applyRedFactor(BufferedImage src) {
|
||||
final float redFactor = 2f;
|
||||
int width = src.getWidth();
|
||||
int height = src.getHeight();
|
||||
|
||||
BufferedImage copy = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
|
||||
byte[] srcPixels = ((DataBufferByte) src.getRaster().getDataBuffer()).getData();
|
||||
byte[] dstPixels = ((DataBufferByte) copy.getRaster().getDataBuffer()).getData();
|
||||
|
||||
System.arraycopy(srcPixels, 0, dstPixels, 0, srcPixels.length);
|
||||
|
||||
for (int i = 0; i < dstPixels.length; i += 4) {
|
||||
int red = dstPixels[i + 3] & 0xFF;
|
||||
red = (int) (red * redFactor);
|
||||
if (red > 255) red = 255;
|
||||
dstPixels[i + 3] = (byte) red;
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
@InjectDependency
|
||||
private RoomTaskScheduler roomTaskScheduler;
|
||||
|
||||
public HittableMob(BufferedImage texture, MobRoomTask[] tasks, RoomCords cords, RoomPart collider, int health) {
|
||||
super(texture, tasks, cords, collider);
|
||||
this.health = health;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void interact() {
|
||||
health -= gameState.getPlayer().getDamageDeal();
|
||||
|
||||
log.debug("Health: {}", health);
|
||||
|
||||
if (health <= 0) {
|
||||
onKilled();
|
||||
for (RoomTask task : tasks) {
|
||||
roomTaskScheduler.stopTask(task);
|
||||
}
|
||||
gameState.getCurrentRoom().getMobs().remove(this);
|
||||
rerender();
|
||||
return;
|
||||
}
|
||||
|
||||
if (hitAnimationOn) {
|
||||
if (currentTimeoutHitAnimation != null && !currentTimeoutHitAnimation.isDone()) {
|
||||
currentTimeoutHitAnimation.cancel(false);
|
||||
}
|
||||
} else {
|
||||
hitAnimationOn = true;
|
||||
log.debug("Hitting start");
|
||||
rerender();
|
||||
}
|
||||
|
||||
currentTimeoutHitAnimation = scheduler.schedule(() -> {
|
||||
hitAnimationOn = false;
|
||||
log.debug("Hitting end");
|
||||
rerender();
|
||||
}, 250, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void rerender() {
|
||||
int forStartX = cords.getX();
|
||||
int forStartY = cords.getY();
|
||||
int forEndX = cords.getX() + texture.getWidth() - 1;
|
||||
int forEndY = cords.getY() + texture.getHeight();
|
||||
|
||||
eventManager.emitEvent(new RerenderPart(
|
||||
forStartX,
|
||||
forEndX,
|
||||
forStartY,
|
||||
forEndY
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package cz.jzitnik.client.game.mobs;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.events.DroppedItemRerender;
|
||||
import cz.jzitnik.client.events.InventoryRerender;
|
||||
import cz.jzitnik.client.game.*;
|
||||
import cz.jzitnik.client.game.items.GameItem;
|
||||
import cz.jzitnik.client.game.mobs.tasks.MobRoomTask;
|
||||
import cz.jzitnik.client.game.objects.DroppedItem;
|
||||
import cz.jzitnik.common.models.coordinates.RoomPart;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class HittableMobDrops extends HittableMob {
|
||||
private static final int DROP_ITEM_ON_GROUND_RADIUS = 30;
|
||||
private final GameItem[] itemsDrops;
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@JsonCreator
|
||||
public HittableMobDrops(
|
||||
@JsonProperty("texture") ResourceManager.Resource texture,
|
||||
@JsonProperty("tasks") MobRoomTask[] tasks,
|
||||
@JsonProperty("cords") RoomCords cords,
|
||||
@JsonProperty("collider") RoomPart collider,
|
||||
@JsonProperty("health") int health,
|
||||
@JsonProperty("itemsDrops") GameItem[] itemsDrops,
|
||||
@JacksonInject ResourceManager resourceManager
|
||||
) {
|
||||
super(resourceManager.getResource(texture), tasks, cords, collider, health);
|
||||
this.itemsDrops = itemsDrops == null ? new GameItem[]{} : itemsDrops;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be overwritten by an extending class
|
||||
**/
|
||||
public void afterKill() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onKilled() {
|
||||
boolean addedIntoInventory = false;
|
||||
Player player = gameState.getPlayer();
|
||||
RoomCords enemyCords = getCords();
|
||||
BufferedImage enemyTexture = getTexture();
|
||||
GameRoom currentRoom = gameState.getCurrentRoom();
|
||||
|
||||
int roomX = enemyCords.getX() + enemyTexture.getWidth() / 2;
|
||||
int roomY = enemyCords.getY() + enemyTexture.getHeight() / 2;
|
||||
|
||||
List<Event> events = new ArrayList<>();
|
||||
|
||||
for (GameItem item : itemsDrops) {
|
||||
boolean added = player.addItem(item);
|
||||
|
||||
if (added) {
|
||||
if (!addedIntoInventory) {
|
||||
addedIntoInventory = true;
|
||||
events.add(new InventoryRerender());
|
||||
}
|
||||
} else {
|
||||
double angle = ThreadLocalRandom.current().nextDouble(0, Math.PI * 2);
|
||||
double radius = ThreadLocalRandom.current().nextDouble(0, DROP_ITEM_ON_GROUND_RADIUS);
|
||||
int randomX = roomX + (int) (Math.cos(angle) * radius);
|
||||
int randomY = roomY + (int) (Math.sin(angle) * radius);
|
||||
RoomCords itemCords = new RoomCords(randomX, randomY);
|
||||
DroppedItem droppedItem = new DroppedItem(currentRoom, itemCords, item);
|
||||
currentRoom.getDroppedItems().add(droppedItem);
|
||||
events.add(new DroppedItemRerender(droppedItem));
|
||||
}
|
||||
}
|
||||
|
||||
eventManager.emitEvent(events, this::afterKill);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cz.jzitnik.client.game.mobs;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import cz.jzitnik.common.models.coordinates.RoomPart;
|
||||
import cz.jzitnik.client.game.ResourceManager;
|
||||
import cz.jzitnik.client.game.mobs.tasks.MobRoomTask;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
|
||||
public class HittableMobNoDrops extends HittableMob {
|
||||
@JsonCreator
|
||||
public HittableMobNoDrops(
|
||||
@JsonProperty("texture") ResourceManager.Resource texture,
|
||||
@JsonProperty("tasks") MobRoomTask[] tasks,
|
||||
@JsonProperty("cords") RoomCords cords,
|
||||
@JsonProperty("collider") RoomPart collider,
|
||||
@JsonProperty("health") int health,
|
||||
@JacksonInject ResourceManager resourceManager
|
||||
) {
|
||||
super(resourceManager.getResource(texture), tasks, cords, collider, health);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onKilled() {
|
||||
|
||||
}
|
||||
}
|
||||
64
game/src/main/java/cz/jzitnik/client/game/mobs/Mob.java
Normal file
64
game/src/main/java/cz/jzitnik/client/game/mobs/Mob.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package cz.jzitnik.client.game.mobs;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.common.models.coordinates.RoomPart;
|
||||
import cz.jzitnik.client.game.mobs.tasks.MobRoomTask;
|
||||
import cz.jzitnik.client.game.utils.Renderable;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.client.game.utils.Selectable;
|
||||
import cz.jzitnik.client.utils.roomtasks.RoomTaskScheduler;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
property = "type"
|
||||
)
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = DialogMob.class, name = "dialog"),
|
||||
@JsonSubTypes.Type(value = HittableMobDrops.class, name = "hittable_drops"),
|
||||
@JsonSubTypes.Type(value = HittableMobNoDrops.class, name = "hittable_no_drops")
|
||||
})
|
||||
@Getter
|
||||
public abstract class Mob implements Renderable, Selectable {
|
||||
@JsonIgnore
|
||||
protected final BufferedImage texture;
|
||||
|
||||
@JsonIgnore
|
||||
protected MobRoomTask[] tasks;
|
||||
@JsonIgnore
|
||||
protected final RoomCords cords;
|
||||
@JsonIgnore
|
||||
protected final RoomPart collider;
|
||||
|
||||
@InjectDependency
|
||||
private RoomTaskScheduler roomTaskScheduler;
|
||||
|
||||
protected void updateTasks(MobRoomTask[] tasks) {
|
||||
var oldTasks = this.tasks;
|
||||
this.tasks = tasks;
|
||||
|
||||
roomTaskScheduler.registerNewMob(this, oldTasks);
|
||||
}
|
||||
|
||||
public Mob(BufferedImage texture, MobRoomTask[] tasks, RoomCords cords, RoomPart collider) {
|
||||
this.texture = texture;
|
||||
this.tasks = tasks == null ? new MobRoomTask[] {} : tasks;
|
||||
this.cords = cords;
|
||||
this.collider = collider;
|
||||
|
||||
if (tasks != null) {
|
||||
for (MobRoomTask task : tasks) {
|
||||
task.setOwner(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Setter
|
||||
private boolean selected = false;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package cz.jzitnik.client.game.mobs.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectConfig;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.config.Debugging;
|
||||
import cz.jzitnik.client.config.MicrophoneConfig;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.game.ResourceManager;
|
||||
import cz.jzitnik.client.game.mobs.Mob;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.client.states.MicrophoneState;
|
||||
import cz.jzitnik.client.states.ScreenBuffer;
|
||||
import cz.jzitnik.client.states.TerminalState;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class BlindMobFollowingPlayerTask extends MobRoomTask {
|
||||
private final Task task;
|
||||
|
||||
@JsonCreator
|
||||
public BlindMobFollowingPlayerTask(
|
||||
@JsonProperty("speed") int speed,
|
||||
@JsonProperty("updateRateMs") int updateRateMs
|
||||
) {
|
||||
Task task = new Task(speed);
|
||||
super(task, updateRateMs, TimeUnit.MILLISECONDS);
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOwner(Mob mob) {
|
||||
task.setMob(mob);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class Task implements Runnable {
|
||||
@Setter
|
||||
private Mob mob;
|
||||
private final int speed;
|
||||
private RoomCords playerCords;
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectDependency
|
||||
private ResourceManager resourceManager;
|
||||
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
|
||||
@InjectState
|
||||
private ScreenBuffer screenBuffer;
|
||||
|
||||
@InjectConfig
|
||||
private Debugging debugging;
|
||||
|
||||
@InjectState
|
||||
private MicrophoneState microphoneState;
|
||||
|
||||
@InjectConfig
|
||||
private MicrophoneConfig microphoneConfig;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (playerCords == null || (microphoneState.isMicrophoneSetup() && microphoneState.getMicrophoneVolume() > microphoneConfig.volumeThreshold())) {
|
||||
playerCords = gameState.getPlayer().getPlayerCords().clone();
|
||||
}
|
||||
|
||||
MobFollowingPlayerTask.Task.moveMob(playerCords, mob, gameState, speed, resourceManager, terminalState, screenBuffer, debugging, eventManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package cz.jzitnik.client.game.mobs.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.events.RenderStats;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.game.mobs.Mob;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.client.utils.DependencyManager;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
public class EnemyPlayerAttackingTask extends MobRoomTask {
|
||||
private final Task task;
|
||||
|
||||
@JsonCreator
|
||||
public EnemyPlayerAttackingTask(
|
||||
@JsonProperty("updateRateMs") long updateRateMs,
|
||||
@JsonProperty("reach") double reach,
|
||||
@JsonProperty("damage") int damage
|
||||
) {
|
||||
Task task = new Task(reach, damage);
|
||||
super(task, updateRateMs, TimeUnit.MILLISECONDS);
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOwner(Mob mob) {
|
||||
task.setMob(mob);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class Task implements Runnable {
|
||||
private final double reach;
|
||||
private final int damage;
|
||||
@Setter
|
||||
private Mob mob;
|
||||
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectDependency
|
||||
private DependencyManager dependencyManager;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
RoomCords playerCords = gameState.getPlayer().getPlayerCords();
|
||||
RoomCords mobCords = mob.getCords();
|
||||
double distance = playerCords.calculateDistance(mobCords);
|
||||
|
||||
if (distance > reach) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isDead = gameState.getPlayer().dealDamage(damage, dependencyManager);
|
||||
eventManager.emitEvent(new RenderStats());
|
||||
|
||||
log.debug("Is dead: {}", isDead);
|
||||
// TODO: Death screen
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package cz.jzitnik.client.game.mobs.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.googlecode.lanterna.TerminalPosition;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectConfig;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.config.Debugging;
|
||||
import cz.jzitnik.client.events.MouseMoveEvent;
|
||||
import cz.jzitnik.client.events.RerenderScreen;
|
||||
import cz.jzitnik.common.models.coordinates.RoomPart;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.game.Player;
|
||||
import cz.jzitnik.client.game.ResourceManager;
|
||||
import cz.jzitnik.client.game.mobs.Mob;
|
||||
import cz.jzitnik.client.game.mobs.tasks.utils.AStarAlg;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.client.states.ScreenBuffer;
|
||||
import cz.jzitnik.client.states.TerminalState;
|
||||
import cz.jzitnik.client.utils.RerenderUtils;
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
public class MobFollowingPlayerTask extends MobRoomTask {
|
||||
private final Task task;
|
||||
|
||||
@JsonCreator
|
||||
public MobFollowingPlayerTask(
|
||||
@JsonProperty("speed") int speed,
|
||||
@JsonProperty("updateRateMs") int updateRateMs
|
||||
) {
|
||||
Task task = new Task(speed);
|
||||
super(task, updateRateMs, TimeUnit.MILLISECONDS);
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOwner(Mob mob) {
|
||||
task.setMob(mob);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
static class Task implements Runnable {
|
||||
private final int speed;
|
||||
@Setter
|
||||
private Mob mob;
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectDependency
|
||||
private ResourceManager resourceManager;
|
||||
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
|
||||
@InjectState
|
||||
private ScreenBuffer screenBuffer;
|
||||
|
||||
@InjectConfig
|
||||
private Debugging debugging;
|
||||
|
||||
protected static void moveMob(RoomCords playerCords, Mob mob, GameState gameState, int speed, ResourceManager resourceManager, TerminalState terminalState, ScreenBuffer screenBuffer, Debugging debugging, EventManager eventManager) {
|
||||
RoomCords mobCords = mob.getCords();
|
||||
List<RoomPart> solidParts = gameState.getCurrentRoom().getColliders();
|
||||
List<RoomCords> path = AStarAlg.findPath(mobCords, playerCords, solidParts, mob.getCollider());
|
||||
|
||||
if (path.size() > 1) {
|
||||
int targetIndex = Math.min(speed, path.size() - 1);
|
||||
|
||||
RoomCords newCords = path.get(targetIndex);
|
||||
|
||||
mob.getCords().updateCords(newCords.getX(), newCords.getY());
|
||||
|
||||
int forStartX = Math.min(mobCords.getX(), newCords.getX());
|
||||
int forStartY = Math.min(mobCords.getY(), newCords.getY());
|
||||
int forEndX = Math.max(mobCords.getX(), newCords.getX()) + mob.getTexture().getWidth() + 1;
|
||||
int forEndY = Math.max(mobCords.getY(), newCords.getY()) + mob.getTexture().getHeight() + 1;
|
||||
|
||||
BufferedImage room = resourceManager.getResource(gameState.getCurrentRoom().getTexture());
|
||||
var start = RerenderUtils.getStart(room, terminalState.getTerminalScreen().getTerminalSize());
|
||||
int startX = start.getX();
|
||||
int startY = start.getY();
|
||||
|
||||
Player player = gameState.getPlayer();
|
||||
BufferedImage playerTexture = player.getTexture(resourceManager);
|
||||
|
||||
RerenderUtils.rerenderPart(forStartX, forEndX, forStartY, forEndY, startX, startY, gameState.getCurrentRoom(), room, player, screenBuffer, resourceManager, debugging, gameState.getOtherPlayers());
|
||||
|
||||
eventManager.emitEvent(new Event[]{
|
||||
new MouseMoveEvent(null),
|
||||
new RerenderScreen(
|
||||
new RerenderScreen.ScreenPart(
|
||||
new TerminalPosition(forStartX + startX, forStartY + startY),
|
||||
new TerminalPosition(forEndX + 1 + startX, forEndY + startY)
|
||||
)
|
||||
)
|
||||
});
|
||||
} else {
|
||||
log.debug("Mob is effectively at the target or trapped.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
moveMob(gameState.getPlayer().getPlayerCords(), mob, gameState, speed, resourceManager, terminalState, screenBuffer, debugging, eventManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cz.jzitnik.client.game.mobs.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import cz.jzitnik.client.game.mobs.Mob;
|
||||
import cz.jzitnik.client.utils.roomtasks.RoomTask;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
property = "type"
|
||||
)
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = BlindMobFollowingPlayerTask.class, name = "blind_following_player"),
|
||||
@JsonSubTypes.Type(value = MobFollowingPlayerTask.class, name = "following_player"),
|
||||
@JsonSubTypes.Type(value = EnemyPlayerAttackingTask.class, name = "attacking_player")
|
||||
})
|
||||
public abstract class MobRoomTask extends RoomTask {
|
||||
public MobRoomTask(Runnable task, long rate, TimeUnit rateUnit) {
|
||||
super(task, rate, rateUnit);
|
||||
}
|
||||
|
||||
public abstract void setOwner(Mob mob);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package cz.jzitnik.client.game.mobs.tasks.utils;
|
||||
|
||||
import cz.jzitnik.common.models.coordinates.RoomPart;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class AStarAlg {
|
||||
|
||||
private static final int MIN_X = 30;
|
||||
private static final int MAX_X = 155;
|
||||
private static final int MIN_Y = 10;
|
||||
private static final int MAX_Y = 113;
|
||||
|
||||
public static List<RoomCords> findPath(RoomCords start, RoomCords target, List<RoomPart> colliders, RoomPart mobCollider) {
|
||||
PriorityQueue<Node> openSet = new PriorityQueue<>(Comparator.comparingInt(n -> n.f));
|
||||
Set<String> closedSet = new HashSet<>();
|
||||
|
||||
Node startNode = new Node(start.getX(), start.getY(), 0, getHeuristic(start, target), null);
|
||||
openSet.add(startNode);
|
||||
|
||||
while (!openSet.isEmpty()) {
|
||||
Node current = openSet.poll();
|
||||
|
||||
if (current.x == target.getX() && current.y == target.getY()) {
|
||||
return reconstructPath(current);
|
||||
}
|
||||
|
||||
String key = current.x + "," + current.y;
|
||||
if (closedSet.contains(key)) continue;
|
||||
closedSet.add(key);
|
||||
|
||||
for (Node neighbor : getNeighbors(current, target)) {
|
||||
String neighborKey = neighbor.x + "," + neighbor.y;
|
||||
if (closedSet.contains(neighborKey)) continue;
|
||||
|
||||
if (!isValidPosition(neighbor.x, neighbor.y, colliders, mobCollider)) {
|
||||
if (neighbor.x != target.getX() || neighbor.y != target.getY()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int newG = current.g + 1;
|
||||
neighbor.g = newG;
|
||||
neighbor.f = newG + neighbor.h;
|
||||
neighbor.parent = current;
|
||||
|
||||
openSet.add(neighbor);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
private static List<Node> getNeighbors(Node current, RoomCords target) {
|
||||
List<Node> neighbors = new ArrayList<>();
|
||||
|
||||
int[][] directions = {
|
||||
{0, 1}, {0, -1}, {1, 0}, {-1, 0}, // Up, Down, Right, Left
|
||||
{1, 1}, {1, -1}, {-1, 1}, {-1, -1} // Diagonals
|
||||
};
|
||||
|
||||
for (int[] dir : directions) {
|
||||
int newX = current.x + dir[0];
|
||||
int newY = current.y + dir[1];
|
||||
int h = getHeuristic(new RoomCords(newX, newY), target);
|
||||
neighbors.add(new Node(newX, newY, 0, h, null));
|
||||
}
|
||||
return neighbors;
|
||||
}
|
||||
|
||||
private static boolean isValidPosition(int x, int y, List<RoomPart> colliders, RoomPart mobCollider) {
|
||||
if (x < MIN_X || x > MAX_X) return false;
|
||||
|
||||
if (y < MIN_Y || y > MAX_Y) return false;
|
||||
|
||||
var temp = new RoomPart(
|
||||
new RoomCords(mobCollider.getStart().getX() + x, mobCollider.getStart().getY() + y),
|
||||
new RoomCords(mobCollider.getEnd().getX() + x, mobCollider.getEnd().getY() + y)
|
||||
);
|
||||
for (RoomPart part : colliders) {
|
||||
if (part.isOverlapping(temp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<RoomCords> reconstructPath(Node endNode) {
|
||||
List<RoomCords> path = new ArrayList<>();
|
||||
Node current = endNode;
|
||||
while (current != null) {
|
||||
path.add(new RoomCords(current.x, current.y));
|
||||
current = current.parent;
|
||||
}
|
||||
Collections.reverse(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
private static int getHeuristic(RoomCords a, RoomCords b) {
|
||||
int dx = Math.abs(a.getX() - b.getX());
|
||||
int dy = Math.abs(a.getY() - b.getY());
|
||||
return Math.max(dx, dy);
|
||||
}
|
||||
|
||||
private static class Node {
|
||||
int x, y;
|
||||
int g, h, f;
|
||||
Node parent;
|
||||
|
||||
public Node(int x, int y, int g, int h, Node parent) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.g = g;
|
||||
this.h = h;
|
||||
this.f = g + h;
|
||||
this.parent = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
283
game/src/main/java/cz/jzitnik/client/game/objects/Chest.java
Normal file
283
game/src/main/java/cz/jzitnik/client/game/objects/Chest.java
Normal file
@@ -0,0 +1,283 @@
|
||||
package cz.jzitnik.client.game.objects;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.googlecode.lanterna.TerminalPosition;
|
||||
import com.googlecode.lanterna.TextColor;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectConfig;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.config.Debugging;
|
||||
import cz.jzitnik.client.events.*;
|
||||
import cz.jzitnik.client.game.GameRoom;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.game.Player;
|
||||
import cz.jzitnik.client.game.ResourceManager;
|
||||
import cz.jzitnik.client.game.items.GameItem;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.client.states.ScreenBuffer;
|
||||
import cz.jzitnik.client.states.TerminalState;
|
||||
import cz.jzitnik.client.ui.Inventory;
|
||||
import cz.jzitnik.client.ui.utils.Grid;
|
||||
import cz.jzitnik.client.ui.pixels.ColoredPixel;
|
||||
import cz.jzitnik.client.ui.pixels.Empty;
|
||||
import cz.jzitnik.client.ui.pixels.Pixel;
|
||||
import cz.jzitnik.client.utils.RerenderUtils;
|
||||
import cz.jzitnik.client.utils.UIRoomClickHandlerRepository;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import cz.jzitnik.common.socket.messages.items.ItemTookFromChest;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.net.Socket;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public final class Chest extends GameObject implements UIClickHandler {
|
||||
private static final int RENDER_PADDING = 1;
|
||||
|
||||
@Getter
|
||||
private final List<GameItem> items;
|
||||
|
||||
private boolean rendered;
|
||||
|
||||
private int listenerHashCode;
|
||||
|
||||
private int actualDisplayStartX;
|
||||
private int actualDisplayStartY;
|
||||
|
||||
private int chestUISizeX;
|
||||
private int chestUISizeY;
|
||||
|
||||
@InjectDependency
|
||||
private ResourceManager resourceManager;
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
@InjectDependency
|
||||
private UIRoomClickHandlerRepository uiRoomClickHandlerRepository;
|
||||
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
@InjectState
|
||||
private ScreenBuffer screenBuffer;
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
|
||||
@InjectConfig
|
||||
private Debugging debugging;
|
||||
|
||||
@JsonCreator
|
||||
public Chest(
|
||||
@JsonProperty("cords") RoomCords cords,
|
||||
@JsonProperty("items") GameItem[] items,
|
||||
@JacksonInject ResourceManager resourceManager
|
||||
) {
|
||||
super(resourceManager.getResource(ResourceManager.Resource.CHEST), cords, true);
|
||||
this.items = Lists.newArrayList(items == null ? new GameItem[] {} : items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interact() {
|
||||
log.debug("Interacted with chest");
|
||||
render(false);
|
||||
}
|
||||
|
||||
private Grid createGrid(int itemCount) {
|
||||
return new Grid(
|
||||
Math.max(1, itemCount), // Items X
|
||||
1, // Items Y
|
||||
2, // Outer Border
|
||||
2, // Inner Border
|
||||
16, // Item Size
|
||||
1, // Item Padding (Changed to 1 as per your request example)
|
||||
Inventory.BORDER_COLOR,
|
||||
Inventory.BACKGROUND_COLOR
|
||||
);
|
||||
}
|
||||
|
||||
private void render(boolean clear) {
|
||||
GameRoom currentRoom = gameState.getCurrentRoom();
|
||||
Player player = gameState.getPlayer();
|
||||
|
||||
BufferedImage roomTexture = resourceManager.getResource(currentRoom.getTexture());
|
||||
BufferedImage chestTexture = getTexture();
|
||||
|
||||
var buffer = screenBuffer.getRenderedBuffer();
|
||||
var overrideBuffer = currentRoom.getOverrideBuffer();
|
||||
|
||||
RoomCords start = RerenderUtils.getStart(
|
||||
roomTexture,
|
||||
terminalState.getTerminalScreen().getTerminalSize()
|
||||
);
|
||||
|
||||
Grid currentGrid = createGrid(items.size());
|
||||
this.chestUISizeX = currentGrid.getWidth();
|
||||
this.chestUISizeY = currentGrid.getHeight();
|
||||
|
||||
int chestUIStartX = getCords().getX();
|
||||
int chestUIStartY = getCords().getY();
|
||||
|
||||
int guiStartX = chestUIStartX + (chestTexture.getWidth() / 2) - (chestUISizeX / 2);
|
||||
int guiStartY = chestUIStartY - chestUISizeY - 1;
|
||||
|
||||
actualDisplayStartX = guiStartX + start.getX();
|
||||
actualDisplayStartY = guiStartY + start.getY();
|
||||
|
||||
int renderMinX = actualDisplayStartX;
|
||||
int renderMinY = actualDisplayStartY;
|
||||
int renderMaxX = actualDisplayStartX + chestUISizeX;
|
||||
int renderMaxY = actualDisplayStartY + chestUISizeY;
|
||||
|
||||
if (clear) {
|
||||
Grid previousGrid = createGrid(items.size() + 1);
|
||||
int prevWidth = previousGrid.getWidth();
|
||||
|
||||
int prevGuiStartX = chestUIStartX + (chestTexture.getWidth() / 2) - (prevWidth / 2);
|
||||
int prevDisplayStartX = prevGuiStartX + start.getX();
|
||||
|
||||
renderMinX = Math.min(renderMinX, prevDisplayStartX);
|
||||
renderMaxX = Math.max(renderMaxX, prevDisplayStartX + prevWidth);
|
||||
|
||||
clearPreviousUI(
|
||||
currentRoom, roomTexture, player, resourceManager,
|
||||
buffer, overrideBuffer, start,
|
||||
guiStartY,
|
||||
prevGuiStartX,
|
||||
prevWidth,
|
||||
chestUISizeY
|
||||
);
|
||||
}
|
||||
|
||||
TerminalPosition guiStart = new TerminalPosition(renderMinX - RENDER_PADDING, renderMinY - RENDER_PADDING);
|
||||
TerminalPosition guiEnd = new TerminalPosition(renderMaxX + RENDER_PADDING, renderMaxY + RENDER_PADDING);
|
||||
RerenderScreen.ScreenPart sp = new RerenderScreen.ScreenPart(guiStart, guiEnd);
|
||||
|
||||
if (!items.isEmpty()) {
|
||||
drawUI(currentGrid, buffer, overrideBuffer, start, guiStartX, guiStartY);
|
||||
listenerHashCode = uiRoomClickHandlerRepository.registerCurrentRoomHandler(sp, this);
|
||||
}
|
||||
|
||||
eventManager.emitEvent(new RerenderPart(
|
||||
guiStart.getColumn() - start.getX(),
|
||||
guiEnd.getColumn() - start.getX(),
|
||||
guiStart.getRow() - start.getY(),
|
||||
guiEnd.getRow() - start.getY()
|
||||
));
|
||||
rendered = true;
|
||||
}
|
||||
|
||||
private void clearPreviousUI(
|
||||
GameRoom room,
|
||||
BufferedImage roomTexture,
|
||||
Player player,
|
||||
ResourceManager resourceManager,
|
||||
Pixel[][] buffer,
|
||||
Pixel[][] overrideBuffer,
|
||||
RoomCords start,
|
||||
int guiStartY,
|
||||
int clearStartX,
|
||||
int clearWidth,
|
||||
int clearHeight
|
||||
) {
|
||||
for (int y = guiStartY; y < guiStartY + clearHeight; y++) {
|
||||
for (int x = clearStartX; x < clearStartX + clearWidth; x++) {
|
||||
int pixel = RerenderUtils.getPixel(
|
||||
room,
|
||||
roomTexture,
|
||||
null,
|
||||
new HashSet<>(),
|
||||
player,
|
||||
resourceManager,
|
||||
x,
|
||||
y,
|
||||
debugging,
|
||||
gameState.getOtherPlayers()
|
||||
).pixel();
|
||||
|
||||
buffer[y + start.getY()][x + start.getX()] = pixelToColored(pixel);
|
||||
overrideBuffer[y][x] = new Empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void drawUI(
|
||||
Grid grid,
|
||||
Pixel[][] buffer,
|
||||
Pixel[][] overrideBuffer,
|
||||
RoomCords start,
|
||||
int guiStartX,
|
||||
int guiStartY
|
||||
) {
|
||||
BufferedImage[] textures = items.stream()
|
||||
.map(GameItem::getTexture)
|
||||
.toArray(BufferedImage[]::new);
|
||||
|
||||
Pixel[][] uiPixels = grid.render(textures);
|
||||
|
||||
for (int y = 0; y < grid.getHeight(); y++) {
|
||||
for (int x = 0; x < grid.getWidth(); x++) {
|
||||
Pixel pixel = uiPixels[y][x];
|
||||
|
||||
int targetY = guiStartY + y + start.getY();
|
||||
int targetX = guiStartX + x + start.getX();
|
||||
|
||||
if (targetY >= 0 && targetY < buffer.length && targetX >= 0 && targetX < buffer[0].length) {
|
||||
buffer[targetY][targetX] = pixel;
|
||||
overrideBuffer[guiStartY + y][guiStartX + x] = pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Pixel pixelToColored(int argb) {
|
||||
int r = (argb >> 16) & 0xff;
|
||||
int g = (argb >> 8) & 0xff;
|
||||
int b = argb & 0xff;
|
||||
return new ColoredPixel(new TextColor.RGB(r, g, b));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleClick(MouseAction mouseAction) {
|
||||
int mouseX = mouseAction.getPosition().getColumn();
|
||||
int mouseY = mouseAction.getPosition().getRow();
|
||||
|
||||
int localX = mouseX - actualDisplayStartX;
|
||||
int localY = (mouseY * 2) - actualDisplayStartY;
|
||||
|
||||
Grid grid = createGrid(items.size());
|
||||
|
||||
int itemIndex = grid.getItemIndexAt(localX, localY);
|
||||
|
||||
if (itemIndex == -1 || itemIndex >= items.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GameItem item = items.get(itemIndex);
|
||||
boolean added = gameState.getPlayer().addItem(item);
|
||||
if (!added) {
|
||||
return true;
|
||||
}
|
||||
|
||||
eventManager.emitEvent(new SendSocketMessageEvent(new ItemTookFromChest(gameState.getCurrentRoom().getId(), item.getId())));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void handleItemRemoval(GameItem item) {
|
||||
items.remove(item);
|
||||
|
||||
if (!rendered) {
|
||||
return;
|
||||
}
|
||||
|
||||
eventManager.emitEvent(new InventoryRerender());
|
||||
if (items.isEmpty()) {
|
||||
uiRoomClickHandlerRepository.removeHandlerForCurrentRoom(listenerHashCode);
|
||||
}
|
||||
render(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package cz.jzitnik.client.game.objects;
|
||||
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.events.DroppedItemRerender;
|
||||
import cz.jzitnik.client.events.InventoryRerender;
|
||||
import cz.jzitnik.client.game.GameRoom;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.game.items.GameItem;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.client.game.utils.Selectable;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class DroppedItem implements Selectable, Serializable {
|
||||
private final GameRoom room;
|
||||
private final RoomCords cords;
|
||||
private final GameItem item;
|
||||
@Setter
|
||||
private boolean isSelected = false;
|
||||
|
||||
@Override
|
||||
public BufferedImage getTexture() {
|
||||
return item.getTexture();
|
||||
}
|
||||
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@Override
|
||||
public void interact() {
|
||||
if (!gameState.getPlayer().addItem(item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
gameState.getCurrentRoom().getDroppedItems().remove(this);
|
||||
eventManager.emitEvent(new InventoryRerender());
|
||||
eventManager.emitEvent(new DroppedItemRerender(this));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cz.jzitnik.client.game.objects;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import cz.jzitnik.client.game.utils.Renderable;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.client.game.utils.Selectable;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
property = "objectType"
|
||||
)
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = Chest.class, name = "chest")
|
||||
})
|
||||
public sealed abstract class GameObject implements Renderable, Selectable permits Chest {
|
||||
@JsonIgnore
|
||||
private final BufferedImage texture;
|
||||
@JsonIgnore
|
||||
private final RoomCords cords;
|
||||
@JsonIgnore
|
||||
private final boolean selectable;
|
||||
|
||||
@JsonIgnore
|
||||
@Setter
|
||||
private boolean selected = false;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package cz.jzitnik.client.game.objects;
|
||||
|
||||
public interface Interactable {
|
||||
void interact();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cz.jzitnik.client.game.objects;
|
||||
|
||||
import cz.jzitnik.client.events.MouseAction;
|
||||
|
||||
public interface UIClickHandler {
|
||||
boolean handleClick(MouseAction mouseAction);
|
||||
|
||||
default boolean handleMove(MouseAction ignoredMouseAction) {
|
||||
return false;
|
||||
}
|
||||
default boolean handleElse(MouseAction ignoredMouseAction) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package cz.jzitnik.client.game.setup;
|
||||
|
||||
import cz.jzitnik.client.annotations.Dependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.events.SendSocketMessageEvent;
|
||||
import cz.jzitnik.client.game.GameRoom;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.game.Player;
|
||||
import cz.jzitnik.client.game.ResourceManager;
|
||||
import cz.jzitnik.client.game.setup.scenes.connect.ServerChoose;
|
||||
import cz.jzitnik.client.utils.DependencyManager;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import cz.jzitnik.common.socket.messages.game.creation.CreateGame;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import tools.jackson.core.type.TypeReference;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.ObjectReader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Dependency
|
||||
public class GameSetup {
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
|
||||
@InjectDependency
|
||||
private ResourceManager resourceManager;
|
||||
|
||||
@InjectDependency
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@InjectDependency
|
||||
private DependencyManager dependencyManager;
|
||||
|
||||
public void setup() throws IOException {
|
||||
gameState.setScreen(new ServerChoose(dependencyManager));
|
||||
|
||||
ObjectReader roomsReader = objectMapper.readerFor(
|
||||
new TypeReference<List<GameRoom>>() {
|
||||
}
|
||||
).with(dependencyManager);
|
||||
List<GameRoom> rooms = roomsReader.readValue(
|
||||
resourceManager.getResourceAsStream("setup/rooms.yaml")
|
||||
);
|
||||
|
||||
gameState.setCurrentRoom(rooms.getFirst());
|
||||
gameState.setAllRooms(rooms);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package cz.jzitnik.client.game.setup.scenes;
|
||||
|
||||
import com.googlecode.lanterna.input.KeyType;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.events.FullRoomDraw;
|
||||
import cz.jzitnik.client.events.KeyboardPressEvent;
|
||||
import cz.jzitnik.client.events.MouseAction;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.screens.Screen;
|
||||
import cz.jzitnik.client.screens.scenes.BasicImageScene;
|
||||
import cz.jzitnik.client.screens.scenes.Scene;
|
||||
import cz.jzitnik.client.sound.SoundPlayer;
|
||||
import cz.jzitnik.client.utils.DependencyManager;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
|
||||
public class GameMenuScene extends Scene {
|
||||
private static class GameMenuAudioScreen extends Screen {
|
||||
protected final SoundPlayer soundPlayer = new SoundPlayer();
|
||||
protected boolean play = true;
|
||||
|
||||
@Override
|
||||
public void fullRender() {
|
||||
// No render here just basic audio playback
|
||||
new Thread(() -> {
|
||||
while (play) {
|
||||
soundPlayer.playSound("audio/menu.ogg", 30, 100);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMouseAction(MouseAction event) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleKeyboardAction(KeyboardPressEvent event) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ImageScene extends BasicImageScene {
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
private final GameMenuAudioScreen gameMenuAudioScreen;
|
||||
|
||||
public ImageScene(String filePath, GameMenuAudioScreen gameMenuAudioScreen) {
|
||||
super(filePath);
|
||||
this.gameMenuAudioScreen = gameMenuAudioScreen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleKeyboardAction(KeyboardPressEvent event) {
|
||||
if (event.getKeyStroke().getKeyType() == KeyType.Enter) {
|
||||
gameMenuAudioScreen.play = false;
|
||||
gameMenuAudioScreen.soundPlayer.stopCurrentSound();
|
||||
gameState.setScreen(null);
|
||||
eventManager.emitEvent(new FullRoomDraw(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GameMenuScene(DependencyManager dependencyManager) {
|
||||
GameMenuAudioScreen gameMenuScreen = new GameMenuAudioScreen();
|
||||
ImageScene basicImageScene = new ImageScene("menu.png", gameMenuScreen);
|
||||
|
||||
super(new Screen[]{gameMenuScreen, basicImageScene}, new OnEndAction.Repeat());
|
||||
|
||||
dependencyManager.inject(this);
|
||||
dependencyManager.inject(basicImageScene);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cz.jzitnik.client.game.setup.scenes;
|
||||
|
||||
import cz.jzitnik.client.screens.Screen;
|
||||
import cz.jzitnik.client.screens.scenes.Scene;
|
||||
import cz.jzitnik.client.screens.scenes.VideoSceneWithAudio;
|
||||
import cz.jzitnik.client.utils.DependencyManager;
|
||||
|
||||
public class IntroScene extends Scene {
|
||||
public IntroScene(DependencyManager dependencyManager) {
|
||||
super(
|
||||
new Screen[]{
|
||||
new VideoSceneWithAudio("video.mp4", "audio.ogg")
|
||||
},
|
||||
new OnEndAction.SwitchToScreen(new GameMenuScene(dependencyManager))
|
||||
);
|
||||
dependencyManager.inject(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,483 @@
|
||||
package cz.jzitnik.client.game.setup.scenes.connect;
|
||||
|
||||
import com.googlecode.lanterna.TerminalSize;
|
||||
import com.googlecode.lanterna.TextColor;
|
||||
import com.googlecode.lanterna.graphics.TextGraphics;
|
||||
import com.googlecode.lanterna.input.KeyType;
|
||||
import com.googlecode.lanterna.screen.TerminalScreen;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.events.KeyboardPressEvent;
|
||||
import cz.jzitnik.client.events.MouseAction;
|
||||
import cz.jzitnik.client.events.SendSocketMessageEvent;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.screens.Screen;
|
||||
import cz.jzitnik.client.screens.scenes.Scene;
|
||||
import cz.jzitnik.client.socket.Client;
|
||||
import cz.jzitnik.client.sound.SoundPlayer;
|
||||
import cz.jzitnik.client.states.TerminalState;
|
||||
import cz.jzitnik.client.ui.Inventory;
|
||||
import cz.jzitnik.client.ui.pixels.AlphaPixel;
|
||||
import cz.jzitnik.client.ui.pixels.Empty;
|
||||
import cz.jzitnik.client.ui.utils.Input;
|
||||
import cz.jzitnik.client.utils.DependencyManager;
|
||||
import cz.jzitnik.client.utils.TextRenderer;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import cz.jzitnik.common.socket.messages.game.connection.ConnectToAGame;
|
||||
import cz.jzitnik.common.socket.messages.game.creation.CreateGame;
|
||||
import jakarta.websocket.DeploymentException;
|
||||
import cz.jzitnik.client.ui.utils.Button;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
|
||||
@Slf4j
|
||||
public class ServerChoose extends Scene {
|
||||
public ServerChoose(DependencyManager dependencyManager) {
|
||||
GameMenuAudioScreen gameMenuScreen = new GameMenuAudioScreen();
|
||||
ServerSelector serverSelector = new ServerSelector();
|
||||
|
||||
super(new Screen[]{gameMenuScreen, serverSelector}, new OnEndAction.Repeat());
|
||||
|
||||
dependencyManager.inject(this);
|
||||
dependencyManager.inject(serverSelector);
|
||||
}
|
||||
|
||||
private static class GameMenuAudioScreen extends Screen {
|
||||
protected final SoundPlayer soundPlayer = new SoundPlayer();
|
||||
protected boolean play = true;
|
||||
|
||||
@Override
|
||||
public void fullRender() {
|
||||
// No render here just basic audio playback
|
||||
new Thread(() -> {
|
||||
while (play) {
|
||||
soundPlayer.playSound("audio/menu.ogg", 30, 100);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMouseAction(MouseAction event) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleKeyboardAction(KeyboardPressEvent event) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ServerSelector extends Screen {
|
||||
private final StringBuilder ipBuffer = new StringBuilder();
|
||||
private boolean connecting = false;
|
||||
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectDependency
|
||||
private Client client;
|
||||
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
|
||||
@InjectDependency
|
||||
private TextRenderer textRenderer;
|
||||
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
|
||||
@InjectDependency
|
||||
private DependencyManager dependencyManager;
|
||||
|
||||
private void renderInput(boolean refresh) {
|
||||
var tg = terminalState.getTextGraphics();
|
||||
TerminalScreen screen = terminalState.getTerminalScreen();
|
||||
Input input = new Input(ipBuffer.toString(), 18, 100);
|
||||
var inputBuffer = input.render(textRenderer);
|
||||
TerminalSize termSize = screen.getTerminalSize();
|
||||
int renderPixelWidth = inputBuffer[0].length;
|
||||
int renderPixelHeight = inputBuffer.length;
|
||||
int renderCharWidth = renderPixelWidth;
|
||||
int renderCharHeight = (renderPixelHeight + 1) / 2;
|
||||
int startX = (termSize.getColumns() - renderCharWidth) / 2;
|
||||
int startY = (termSize.getRows() - renderCharHeight) / 2;
|
||||
|
||||
for (int y = 0; y < inputBuffer.length; y += 2) {
|
||||
for (int x = 0; x < inputBuffer[y].length; x++) {
|
||||
AlphaPixel bottomPixel;
|
||||
AlphaPixel topPixel = inputBuffer[y][x];
|
||||
if (y + 1 < inputBuffer.length) {
|
||||
bottomPixel = inputBuffer[y + 1][x];
|
||||
} else {
|
||||
bottomPixel = new Empty();
|
||||
}
|
||||
|
||||
int termX = startX + x;
|
||||
int termY = startY + (y / 2);
|
||||
|
||||
tg.setBackgroundColor(topPixel instanceof Empty ? TextColor.ANSI.BLACK : topPixel.getColor());
|
||||
tg.setForegroundColor(bottomPixel instanceof Empty ? TextColor.ANSI.BLACK : bottomPixel.getColor());
|
||||
tg.setCharacter(termX, termY, '▄');
|
||||
}
|
||||
}
|
||||
|
||||
if (refresh) {
|
||||
try {
|
||||
screen.refresh(com.googlecode.lanterna.screen.Screen.RefreshType.DELTA);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fullRender() {
|
||||
TerminalScreen screen = terminalState.getTerminalScreen();
|
||||
var tg = terminalState.getTextGraphics();
|
||||
screen.clear();
|
||||
TerminalSize terminalSize = screen.getTerminalSize();
|
||||
|
||||
for (int y = 0; y < terminalSize.getRows(); y += 1) {
|
||||
for (int x = 0; x < terminalSize.getColumns(); x++) {
|
||||
tg.setBackgroundColor(TextColor.ANSI.BLACK);
|
||||
tg.setForegroundColor(TextColor.ANSI.BLACK);
|
||||
tg.setCharacter(x, y, '▄');
|
||||
}
|
||||
}
|
||||
|
||||
AlphaPixel[][] selectServer = textRenderer.renderText("Enter server IP", terminalSize.getColumns(), 20, Color.WHITE, 15f, true);
|
||||
|
||||
render(selectServer, 0, 10, tg);
|
||||
|
||||
renderInput(false);
|
||||
|
||||
try {
|
||||
screen.refresh(com.googlecode.lanterna.screen.Screen.RefreshType.COMPLETE);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleKeyboardAction(KeyboardPressEvent event) {
|
||||
if (connecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getKeyStroke().getKeyType() == KeyType.Enter) {
|
||||
try {
|
||||
connecting = true;
|
||||
client.connect(ipBuffer.toString());
|
||||
|
||||
Screen screen = new ActionSelector();
|
||||
dependencyManager.inject(screen);
|
||||
gameState.setScreen(screen);
|
||||
screen.fullRender();
|
||||
} catch (DeploymentException | IOException e) {
|
||||
connecting = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getKeyStroke().getKeyType() == KeyType.Backspace) {
|
||||
if (ipBuffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ipBuffer.deleteCharAt(ipBuffer.length() - 1);
|
||||
renderInput(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getKeyStroke().getKeyType() == KeyType.Character && !event.getKeyStroke().isCtrlDown()) {
|
||||
ipBuffer.append(event.getKeyStroke().getCharacter());
|
||||
renderInput(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMouseAction(MouseAction event) {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ActionSelector extends Screen {
|
||||
@InjectDependency
|
||||
private TextRenderer textRenderer;
|
||||
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
|
||||
private int selectedIndex = -1;
|
||||
|
||||
@Override
|
||||
public void fullRender() {
|
||||
TerminalScreen screen = terminalState.getTerminalScreen();
|
||||
var tg = terminalState.getTextGraphics();
|
||||
screen.clear();
|
||||
TerminalSize terminalSize = screen.getTerminalSize();
|
||||
|
||||
for (int y = 0; y < terminalSize.getRows(); y += 1) {
|
||||
for (int x = 0; x < terminalSize.getColumns(); x++) {
|
||||
tg.setBackgroundColor(TextColor.ANSI.BLACK);
|
||||
tg.setForegroundColor(TextColor.ANSI.BLACK);
|
||||
tg.setCharacter(x, y, '▄');
|
||||
}
|
||||
}
|
||||
|
||||
AlphaPixel[][] selectAction = textRenderer.renderText("Select action", terminalSize.getColumns(), 20, Color.WHITE, 15f, true);
|
||||
|
||||
render(selectAction, 0, 10, tg);
|
||||
renderButtons();
|
||||
|
||||
try {
|
||||
screen.refresh(com.googlecode.lanterna.screen.Screen.RefreshType.COMPLETE);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int BUTTON_HEIGHT = 20;
|
||||
private static final int BUTTON_WIDTH = 200;
|
||||
private static final int BUTTON_GAP = 10;
|
||||
private static final int BUTTON_COUNT = 2;
|
||||
private static final int BUTTONS_HEIGHT = BUTTON_HEIGHT * BUTTON_COUNT + (BUTTON_COUNT - 1) * BUTTON_GAP;
|
||||
|
||||
private void renderButtons() {
|
||||
var tg = terminalState.getTextGraphics();
|
||||
TerminalSize terminalSize = terminalState.getTerminalScreen().getTerminalSize();
|
||||
|
||||
final int BUTTON_PAD_X = terminalSize.getColumns() / 2 - BUTTON_WIDTH / 2;
|
||||
final int BUTTON_PAD_Y = terminalSize.getRows() - BUTTONS_HEIGHT / 2;
|
||||
|
||||
Button button = new Button(
|
||||
Inventory.BORDER_COLOR,
|
||||
Inventory.BACKGROUND_COLOR,
|
||||
Inventory.BACKGROUND_COLOR_HOVERED,
|
||||
Color.WHITE,
|
||||
BUTTON_HEIGHT,
|
||||
BUTTON_WIDTH,
|
||||
1,
|
||||
15f,
|
||||
textRenderer
|
||||
);
|
||||
|
||||
render(button.render("Create a world", selectedIndex == 0), BUTTON_PAD_X, BUTTON_PAD_Y, tg);
|
||||
render(button.render("Connect to an existing world", selectedIndex == 1), BUTTON_PAD_X, BUTTON_PAD_Y + (BUTTON_HEIGHT + BUTTON_GAP), tg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMouseAction(MouseAction event) {
|
||||
TerminalSize terminalSize = terminalState.getTerminalScreen().getTerminalSize();
|
||||
|
||||
final int BUTTON_START_X = terminalSize.getColumns() / 2 - BUTTON_WIDTH / 2;
|
||||
final int BUTTON_START_Y = terminalSize.getRows() - BUTTONS_HEIGHT / 2;
|
||||
final int BUTTON_END_X = BUTTON_START_X + BUTTON_WIDTH;
|
||||
final int BUTTON_END_Y = BUTTON_START_Y + BUTTONS_HEIGHT;
|
||||
|
||||
final int TERMINAL_X = event.getPosition().getColumn();
|
||||
final int TERMINAL_Y = event.getPosition().getRow() * 2;
|
||||
|
||||
final int SINGLE_BUTTON_HEIGHT = BUTTON_HEIGHT + BUTTON_GAP;
|
||||
|
||||
int index = (TERMINAL_Y - BUTTON_START_Y) / SINGLE_BUTTON_HEIGHT;
|
||||
int rest = (TERMINAL_Y - BUTTON_START_Y) % SINGLE_BUTTON_HEIGHT;
|
||||
|
||||
if (!(TERMINAL_X >= BUTTON_START_X && TERMINAL_Y >= BUTTON_START_Y && TERMINAL_X < BUTTON_END_X && TERMINAL_Y < BUTTON_END_Y) || rest > BUTTON_HEIGHT) {
|
||||
if (selectedIndex != -1) {
|
||||
selectedIndex = -1;
|
||||
renderButtons();
|
||||
refresh();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.getActionType()) {
|
||||
case MOVE -> {
|
||||
selectedIndex = index;
|
||||
renderButtons();
|
||||
refresh();
|
||||
}
|
||||
case CLICK_RELEASE -> {
|
||||
switch (index) {
|
||||
case 0 -> eventManager.emitEvent(new SendSocketMessageEvent(new CreateGame()));
|
||||
case 1 -> {
|
||||
Screen screen = new ConnectWorld();
|
||||
gameState.setScreen(screen);
|
||||
screen.fullRender();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
try {
|
||||
terminalState.getTerminalScreen().refresh(com.googlecode.lanterna.screen.Screen.RefreshType.DELTA);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleKeyboardAction(KeyboardPressEvent event) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class ConnectWorld extends Screen {
|
||||
private final StringBuilder passBuffer = new StringBuilder();
|
||||
private boolean connecting = false;
|
||||
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectDependency
|
||||
private Client client;
|
||||
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
|
||||
@InjectDependency
|
||||
private TextRenderer textRenderer;
|
||||
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
|
||||
@InjectDependency
|
||||
private DependencyManager dependencyManager;
|
||||
|
||||
private void renderInput(boolean refresh) {
|
||||
var tg = terminalState.getTextGraphics();
|
||||
TerminalScreen screen = terminalState.getTerminalScreen();
|
||||
Input input = new Input(passBuffer.toString(), 18, 100);
|
||||
var inputBuffer = input.render(textRenderer);
|
||||
TerminalSize termSize = screen.getTerminalSize();
|
||||
int renderPixelWidth = inputBuffer[0].length;
|
||||
int renderPixelHeight = inputBuffer.length;
|
||||
int renderCharWidth = renderPixelWidth;
|
||||
int renderCharHeight = (renderPixelHeight + 1) / 2;
|
||||
int startX = (termSize.getColumns() - renderCharWidth) / 2;
|
||||
int startY = (termSize.getRows() - renderCharHeight) / 2;
|
||||
|
||||
for (int y = 0; y < inputBuffer.length; y += 2) {
|
||||
for (int x = 0; x < inputBuffer[y].length; x++) {
|
||||
AlphaPixel bottomPixel;
|
||||
AlphaPixel topPixel = inputBuffer[y][x];
|
||||
if (y + 1 < inputBuffer.length) {
|
||||
bottomPixel = inputBuffer[y + 1][x];
|
||||
} else {
|
||||
bottomPixel = new Empty();
|
||||
}
|
||||
|
||||
int termX = startX + x;
|
||||
int termY = startY + (y / 2);
|
||||
|
||||
tg.setBackgroundColor(topPixel instanceof Empty ? TextColor.ANSI.BLACK : topPixel.getColor());
|
||||
tg.setForegroundColor(bottomPixel instanceof Empty ? TextColor.ANSI.BLACK : bottomPixel.getColor());
|
||||
tg.setCharacter(termX, termY, '▄');
|
||||
}
|
||||
}
|
||||
|
||||
if (refresh) {
|
||||
try {
|
||||
screen.refresh(com.googlecode.lanterna.screen.Screen.RefreshType.DELTA);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fullRender() {
|
||||
TerminalScreen screen = terminalState.getTerminalScreen();
|
||||
var tg = terminalState.getTextGraphics();
|
||||
screen.clear();
|
||||
TerminalSize terminalSize = screen.getTerminalSize();
|
||||
|
||||
for (int y = 0; y < terminalSize.getRows(); y += 1) {
|
||||
for (int x = 0; x < terminalSize.getColumns(); x++) {
|
||||
tg.setBackgroundColor(TextColor.ANSI.BLACK);
|
||||
tg.setForegroundColor(TextColor.ANSI.BLACK);
|
||||
tg.setCharacter(x, y, '▄');
|
||||
}
|
||||
}
|
||||
|
||||
AlphaPixel[][] selectServer = textRenderer.renderText("Enter world password", terminalSize.getColumns(), 20, Color.WHITE, 15f, true);
|
||||
|
||||
render(selectServer, 0, 10, tg);
|
||||
|
||||
renderInput(false);
|
||||
|
||||
try {
|
||||
screen.refresh(com.googlecode.lanterna.screen.Screen.RefreshType.COMPLETE);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleKeyboardAction(KeyboardPressEvent event) {
|
||||
if (connecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getKeyStroke().getKeyType() == KeyType.Enter) {
|
||||
connecting = true;
|
||||
String pass = passBuffer.toString();
|
||||
eventManager.emitEvent(new SendSocketMessageEvent(new ConnectToAGame(pass)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getKeyStroke().getKeyType() == KeyType.Backspace) {
|
||||
if (passBuffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
passBuffer.deleteCharAt(passBuffer.length() - 1);
|
||||
renderInput(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getKeyStroke().getKeyType() == KeyType.Character && !event.getKeyStroke().isCtrlDown()) {
|
||||
passBuffer.append(event.getKeyStroke().getCharacter());
|
||||
renderInput(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMouseAction(MouseAction event) {
|
||||
}
|
||||
}
|
||||
|
||||
private static void render(AlphaPixel[][] buffer, int padX, int padY, TextGraphics tg) {
|
||||
for (int y = 0; y < buffer.length; y += 2) {
|
||||
for (int x = 0; x < buffer[y].length; x++) {
|
||||
AlphaPixel topPixel = buffer[y][x];
|
||||
|
||||
AlphaPixel bottomPixel;
|
||||
if (y + 1 < buffer.length) {
|
||||
bottomPixel = buffer[y + 1][x];
|
||||
} else {
|
||||
bottomPixel = new Empty();
|
||||
}
|
||||
|
||||
int termX = padX + x;
|
||||
int termY = padY / 2 + y / 2;
|
||||
|
||||
tg.setBackgroundColor(topPixel instanceof Empty ? TextColor.ANSI.BLACK : topPixel.getColor());
|
||||
tg.setForegroundColor(bottomPixel instanceof Empty ? TextColor.ANSI.BLACK : bottomPixel.getColor());
|
||||
tg.setCharacter(termX, termY, '▄');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user