diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index ffb62bd..cd7f874 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -1,4 +1,4 @@ -name: Build Multi-Platform JavaFX +name: Build Multi-Platform on: push: @@ -8,73 +8,36 @@ on: jobs: build: - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - name: Linux-x64 - file_suffix: linux-x64 - java_arch: x64 - - os: windows-latest - name: Windows-x64 - file_suffix: win-x64 - java_arch: x64 - - os: macos-latest - name: macOS-arm64 (Apple Silicon) - file_suffix: mac-arm64 - java_arch: aarch64 - - runs-on: ${{ matrix.os }} - name: Build on ${{ matrix.name }} + runs-on: ubuntu-latest + name: Build Universal Jar steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Set up JDK 21 (${{ matrix.java_arch }}) + - name: Set up JDK 21 uses: actions/setup-java@v4 with: java-version: '21' distribution: 'temurin' - architecture: ${{ matrix.java_arch }} - name: Grant execute permission for gradlew - if: runner.os != 'Windows' run: chmod +x gradlew - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - - name: Build Client with Gradle - run: ./gradlew shadowJar jpackage - - - name: Build Server - if: matrix.os == 'ubuntu-latest' - run: ./gradlew buildServer + - name: Build Client (ShadowJar) & Server + run: ./gradlew shadowJar buildServer - - name: Upload Shadow Jar + - name: Upload Client Shadow Jar uses: actions/upload-artifact@v4 with: - name: TheEndlessWeave-${{ matrix.file_suffix }}-ShadowJar - path: build/libs/*-all.jar + name: TheEndlessWeave-Client-Universal + path: build/libs/*-client.jar - name: Upload Server Jar - if: matrix.os == 'ubuntu-latest' - uses: actions/upload-artifact@v4 - with: - name: TheEndlessWeave-Server - path: build/libs/*-server.jar - - - name: Upload JPackage Installers uses: actions/upload-artifact@v4 with: - name: TheEndlessWeave-${{ matrix.file_suffix }}-Installer - if-no-files-found: error - path: | - build/jpackage/*.exe - build/jpackage/*.msi - build/jpackage/*.dmg - build/jpackage/*.pkg - build/jpackage/*.deb - build/jpackage/*.rpm \ No newline at end of file + name: TheEndlessWeave-Server-x64 + path: build/libs/*-server.jar \ No newline at end of file diff --git a/README.md b/README.md index 838863c..2990430 100644 --- a/README.md +++ b/README.md @@ -6,19 +6,18 @@ --- ## Client -Требуется 64бит железо (спасибо javafx). Есть два пути билда: -- ShadowJar (всё в одной Jar) - `./gradlew shadowJar` -- бинарные файлы (через установщик или напрямую через папку с нативами) - `./gradlew jpackage` +Собрать полный jar со всеми бинарниками под архитектуру - `./gradlew shadowJar` -> ! Для windows может потребоваться тулкит чтобы собрать бинарные (`.\gralew jpackage`) файлы. - -> ! Андроид билды не собираются в actions, но их можно собрать самому на телефоне в termux. хоть это и не имеет смысла на текущий момент, ну ладно. --- ## Server -Вы можете отдельно собрать только сервер, он кроссплатформленный и не зависит от архитектуры (может быть x32 итд) +Вы можете отдельно собрать только сервер. ```shell ./gradlew buildServer ``` +Также сервер можно запустить из клиента: +```shell +java -jar filename-client.jar --server +``` --- @@ -34,19 +33,25 @@ - [X] Сделать Game абсолютно независимым, чтобы мог быть инициализирован как клиентом, так и сервером - [X] Изменить архитектуру проекта на core (общее: компоненты, системы, генератор карты), client (текущий view) и server (серверный цикл) - [X] Сделать минимальный Hi Hello сетевой стек. - - [ ] Адаптировать логику клиента и сервера для работы с сетью - - [ ] (!) клиент НЕ должен самостоятельно вызывать что-либо в Game, он должен отправлять пакет и получать подтверждение + - [X] Адаптировать логику клиента и сервера для работы с сетью + - [X] (!) клиент НЕ должен самостоятельно вызывать что-либо в Game, он должен отправлять пакет и получать подтверждение - [X] синхронизировать игровую карту (клиент отправляет параметры (GameData), сервер хавает) - [X] если сервер НЕ должен возвращать карту, то пусть сервер передаст шанс рандома, чтобы клиент сам создал карту с таким рандомом. - - [ ] Перенести логику игры на интегрированный сервер. Это исправит многие боли, наверное. (связан с пунктом выше, синхронизация игровой карты) + - [ ] Перенести соло игру на интегрированный сервер. + - клиент инициализирует и запускает локальный сервер в GameScreen + - [ ] конфиг генерации мира с клиента должен - [ ] НеЗабитьНеЗабитьНеЗабитьНеЗабитьНеЗабитьНеЗабитьНеЗабитьНеЗабитьНеЗабить - [ ] Hello LibGDX! - Javafx слишком много срёт на слабых устройствах и в целом не подходит для текущих целей. нужно переписать рендер: - - [ ] `client.controllers` - - [ ] `client.custom.*` - - [ ] `client.systems.` - - [ ] создать ветку и настроить `build.gradle.kts`. - - [ ] ... + - [X] `client.controllers` -> `client.screens` + - [X] `client.custom.*` + - [X] `client.systems.` + - [X] создать ветку и настроить `build.gradle.kts`. + - Баги/проблемы (исправить) + - [ ] Из-за написанного кода под javafx, на клиенте могут возникать проблемы с логикой из-за другой системы координат в libgdx, например, при просчёте логики полёта пуль, необходимо домножать скорость на размер тайлов. + - [ ] сервер не удаляет игрока, если он отключился + - [ ] клиент рендерит других игроков как врагов + - [ ] ui не масштабируется при изменении размера окна. --- diff --git a/build.gradle.kts b/build.gradle.kts index 3b16323..369598d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,24 +3,18 @@ import java.util.Date plugins { java application - id("org.javamodularity.moduleplugin") version "1.8.15" - id("org.openjfx.javafxplugin") version "0.0.13" - id("org.beryx.jlink") version "3.2.0" + id("com.github.johnrengelman.shadow") version "8.1.1" } group = "xyz.samiker" -version = "0.2.0" +version = "0.4.0" + +val gdxVersion = "1.14.0" repositories { mavenCentral() -} - -sourceSets { - main { - resources { - srcDir(layout.buildDirectory.dir("resources/generateResourceManifest")) - } - } + maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/") } + maven { url = uri("https://oss.sonatype.org/content/repositories/releases/") } } java { @@ -30,28 +24,30 @@ java { } } -tasks.withType { - options.encoding = "UTF-8" -} - application { - mainModule.set("xyz.samiker.theendlessweave") mainClass.set("xyz.samiker.theendlessweave.Launcher") } -javafx { - version = "21.0.6" - modules = listOf("javafx.controls", "javafx.fxml", "javafx.media") -} - dependencies { - implementation("org.controlsfx:controlsfx:11.2.1") + implementation("com.badlogicgames.gdx:gdx:$gdxVersion") + implementation("com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion") + implementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") implementation("com.esotericsoftware:kryonet:2.22.0-RC1") } +tasks.withType { + options.encoding = "UTF-8" +} + +tasks.named("run") { + if (org.gradle.internal.os.OperatingSystem.current().isMacOsX) { + jvmArgs("-XstartOnFirstThread") + } +} + tasks.register("buildServer") { group = "build" - description = "билдит сервер без всякого fx говна" + description = "Билдит сервер без графического бэкенда и нативов" archiveClassifier.set("server") @@ -61,34 +57,34 @@ tasks.register("buildServer") { from(sourceSets.main.get().output) { exclude("xyz/samiker/theendlessweave/client/**") + exclude("assets/**") - exclude("css/**") - exclude("fxml/**") - exclude("*.dll", "*.so", "*.dylib") } val serverClasspath = configurations.runtimeClasspath.get().filter { file -> val name = file.name.lowercase() - !name.contains("javafx") && - !name.contains("controlsfx") && - !name.contains("native") && - !name.contains("win") && - !name.contains("mac") && - !name.contains("linux") + !name.contains("gdx-backend-lwjgl3") && + !name.contains("natives") } from(serverClasspath.map { if (it.isDirectory) it else zipTree(it) }) { - exclude("module-info.class") exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA") exclude("META-INF/MANIFEST.MF") - exclude("**/*.dll", "**/*.so", "**/*.dylib") + exclude("module-info.class") } duplicatesStrategy = DuplicatesStrategy.EXCLUDE } -tasks.withType { - useJUnitPlatform() +tasks.named("shadowJar") { + archiveClassifier.set("client") + configurations = listOf(project.configurations.runtimeClasspath.get()) + + mergeServiceFiles() + + manifest { + attributes["Main-Class"] = "xyz.samiker.theendlessweave.Launcher" + } } tasks.register("generateResourceManifest") { @@ -104,7 +100,6 @@ tasks.register("generateResourceManifest") { doLast { println("Generating resource manifest to: ${manifestFile.asFile.path}") - val manifestContent = StringBuilder() manifestContent.append("# Resource Manifest\n") manifestContent.append("# Generated on: ${Date()}\n\n") @@ -116,62 +111,23 @@ tasks.register("generateResourceManifest") { manifestContent.append("resource.path=${relativePath.replace(File.separatorChar, '/')}\n\n") } } - outputDir.get().asFile.mkdirs() manifestFile.asFile.writeText(manifestContent.toString()) } } -tasks.named("processResources") { - dependsOn(tasks.named("generateResourceManifest")) -} - -tasks.register("shadowJar") { - group = "build" - description = "Builds an uber-jar (fat jar) with all dependencies." - - archiveClassifier.set("all") - - manifest { - attributes["Main-Class"] = "xyz.samiker.theendlessweave.Launcher" - } - - from(sourceSets.main.get().output) - - from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) { - exclude("module-info.class", "META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA", "META-INF/substrate/config/*", "META-INF/LICENSE", "META-INF/NOTICE") +sourceSets { + main { + resources { + srcDir(layout.buildDirectory.dir("resources/generateResourceManifest")) + } } - - duplicatesStrategy = DuplicatesStrategy.EXCLUDE } -jlink { - imageZip.set(layout.buildDirectory.file("/distributions/app-${javafx.platform.classifier}.zip")) - options.set(listOf("--strip-debug", "--compress", "zip-8", "--no-header-files", "--no-man-pages")) - launcher { - name = "TheEndlessWeave" - } - - val javaToolchains = project.extensions.getByType(JavaToolchainService::class) - val javaLauncher = javaToolchains.launcherFor(java.toolchain) - javaHome.set(javaLauncher.map { it.metadata.installationPath }) - - jpackage { - imageName = "TheEndlessWeave" - if (org.gradle.internal.os.OperatingSystem.current().isMacOsX) { - val ver = version as String - appVersion = if (ver.startsWith("0.")) "1" + ver.substring(1) else ver - } else { - appVersion = version as String? - } +tasks.named("processResources") { + dependsOn(tasks.named("generateResourceManifest")) +} - if (org.gradle.internal.os.OperatingSystem.current().isWindows) { - installerOptions = listOf( - "--win-shortcut", - "--win-menu", - "--win-dir-chooser", - "--win-menu-group", "The Endless Weave" - ) - } - } +tasks.withType { + useJUnitPlatform() } \ No newline at end of file diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java deleted file mode 100644 index 69ae379..0000000 --- a/src/main/java/module-info.java +++ /dev/null @@ -1,24 +0,0 @@ -module xyz.samiker.theendlessweave { - requires javafx.controls; - requires javafx.fxml; - requires javafx.media; - requires jdk.jfr; - requires jdk.management; - requires jdk.management.agent; - requires jdk.management.jfr; - requires javafx.graphics; - requires kryo; - requires kryonet; - - opens xyz.samiker.theendlessweave to javafx.fxml; - opens xyz.samiker.theendlessweave.client.controllers to javafx.fxml; - - exports xyz.samiker.theendlessweave; - exports xyz.samiker.theendlessweave.client.assets; - exports xyz.samiker.theendlessweave.client.controllers; - exports xyz.samiker.theendlessweave.server; - opens xyz.samiker.theendlessweave.server to javafx.fxml; - exports xyz.samiker.theendlessweave.client; - opens xyz.samiker.theendlessweave.client to javafx.fxml; - -} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/Main.java b/src/main/java/xyz/samiker/theendlessweave/Main.java index 7a00bdf..537207a 100644 --- a/src/main/java/xyz/samiker/theendlessweave/Main.java +++ b/src/main/java/xyz/samiker/theendlessweave/Main.java @@ -1,197 +1,65 @@ package xyz.samiker.theendlessweave; -import javafx.application.Application; -import javafx.application.Platform; -import javafx.fxml.FXMLLoader; -import javafx.geometry.Rectangle2D; -import javafx.scene.Scene; -import javafx.scene.input.KeyCombination; -import javafx.stage.Screen; -import javafx.stage.Stage; -import javafx.stage.StageStyle; +import com.badlogic.gdx.Game; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; import xyz.samiker.theendlessweave.client.assets.ManifestAssetManager; -import xyz.samiker.theendlessweave.client.controllers.AssetAware; -import xyz.samiker.theendlessweave.client.controllers.GameDataAware; -import xyz.samiker.theendlessweave.client.custom.InputManager; -import xyz.samiker.theendlessweave.client.initialization.LoadingContext; -import xyz.samiker.theendlessweave.core.GameData; +import xyz.samiker.theendlessweave.client.assets.ResourceManifest; +import xyz.samiker.theendlessweave.client.screens.LoadingScreen; import xyz.samiker.theendlessweave.core.settings.SettingsEnum; import xyz.samiker.theendlessweave.core.settings.SettingsManager; import java.io.IOException; -import java.net.URL; -public class Main extends Application implements AssetAware { - private static Stage primaryStage; - private static Scene mainScene; - private static ManifestAssetManager assetManager; - private static final SettingsManager.Resolution resolution = new SettingsManager.Resolution(SettingsManager.getString(SettingsEnum.RESOLUTION)); - private static boolean isMobile = false; - - private static boolean isProgrammaticResize = false; +public class Main extends Game { + public SpriteBatch batch; + private ManifestAssetManager assetManager; @Override - public void start(Stage stage) throws IOException { - primaryStage = stage; - primaryStage.setOnCloseRequest(event -> { - Platform.exit(); - System.exit(0); - }); - initOS(true); - stage.setTitle("The Endless Weave"); - - FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/xyz/samiker/theendlessweave/fxml/loading-screen.fxml")); - mainScene = new Scene(fxmlLoader.load()); - - InputManager.initialize(mainScene); - - setupStageListeners(primaryStage); - boolean isBorderless = SettingsManager.getBoolean(SettingsEnum.BORDERLESS); - primaryStage.initStyle(isBorderless ? StageStyle.UNDECORATED : StageStyle.DECORATED); - - applyResolutionToStage(stage); - primaryStage.setScene(mainScene); - - updateSceneStyles("/xyz/samiker/theendlessweave/css/loading-screen.css"); - - primaryStage.show(); - } - - /** - * Настраивает слушатели событий для окна. - */ - private static void setupStageListeners(Stage stage) { - stage.widthProperty().addListener((obs, oldVal, newVal) -> { - if (!stage.isFullScreen() && !isProgrammaticResize) { - resolution.setWidth(newVal.intValue()); - } - }); - - stage.heightProperty().addListener((obs, oldVal, newVal) -> { - if (!stage.isFullScreen() && !isProgrammaticResize) { - resolution.setHeight(newVal.intValue()); - } - }); - - stage.fullScreenProperty().addListener((obs, wasFullScreen, isFullScreen) -> { - Rectangle2D bounds = Screen.getPrimary().getBounds(); - resolution.setHeight((int) bounds.getHeight()); - resolution.setWidth((int) bounds.getWidth()); - }); - } - - public static void setAssetManager(LoadingContext context) { - assetManager = context.get("assetManager"); - } - - public static void showScene(String fxmlPath, String cssPath) throws IOException { - FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource(fxmlPath)); - mainScene.setRoot(fxmlLoader.load()); - - Object controller = fxmlLoader.getController(); - if (controller instanceof AssetAware) { - ((AssetAware) controller).setAssets(assetManager); + public void create() { + batch = new SpriteBatch(); + try { + assetManager = new ManifestAssetManager(ResourceManifest.load()); + } catch (IOException e) { + throw new RuntimeException(e); } - updateSceneStyles(cssPath); - applyStageSettings(); + applySettings(); + this.setScreen(new LoadingScreen(this)); } - public static T showSceneAndGetController(String fxmlPath, String cssPath) throws IOException { - FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource(fxmlPath)); - mainScene.setRoot(fxmlLoader.load()); - - T controller = fxmlLoader.getController(); - if (controller instanceof AssetAware) { - ((AssetAware) controller).setAssets(assetManager); - } - - updateSceneStyles(cssPath); - applyStageSettings(); - - return controller; + @Override + public void render() { + super.render(); } - public static void showGameScene(GameData data, String path, String csspath) throws IOException { - FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource(path)); - mainScene.setRoot(fxmlLoader.load()); - - Object controller = fxmlLoader.getController(); - if (controller instanceof AssetAware) { - ((AssetAware) controller).setAssets(assetManager); - } - if (controller instanceof GameDataAware) { - ((GameDataAware) controller).setData(data); + @Override + public void dispose() { + if (batch != null) batch.dispose(); + if (assetManager != null) { + assetManager.dispose(); } - - updateSceneStyles(csspath); - applyStageSettings(); + if (screen != null) screen.dispose(); } - private static void updateSceneStyles(String cssPath) { - mainScene.getStylesheets().clear(); - URL customCssUrl = Main.class.getResource(cssPath); - if (customCssUrl != null) { - mainScene.getStylesheets().add(customCssUrl.toExternalForm()); - } else { - System.err.println("WARNING: Custom stylesheet " + cssPath + " not found in resources."); - } + public ManifestAssetManager getAssetManager() { + return assetManager; } - public static void applyStageSettings() { + public void applySettings() { boolean isFullscreen = SettingsManager.getBoolean(SettingsEnum.FULLSCREEN); + boolean isBorderless = SettingsManager.getBoolean(SettingsEnum.BORDERLESS); - isProgrammaticResize = true; - - try { - if (primaryStage.isFullScreen() != isFullscreen) { - primaryStage.setFullScreenExitHint(""); - primaryStage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH); - primaryStage.setFullScreen(isFullscreen); - } + if (isFullscreen) { + Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode()); + } else { + int width = Integer.parseInt(SettingsManager.getString(SettingsEnum.RESOLUTION).split("x")[0]); + int height = Integer.parseInt(SettingsManager.getString(SettingsEnum.RESOLUTION).split("x")[1]); + Gdx.graphics.setWindowedMode(width, height); - if (!isFullscreen) { - if (primaryStage.getWidth() != resolution.getWidth() || primaryStage.getHeight() != resolution.getHeight()) { - primaryStage.setWidth(resolution.getWidth()); - primaryStage.setHeight(resolution.getHeight()); - primaryStage.centerOnScreen(); - } + if (isBorderless) { + Gdx.graphics.setUndecorated(true); } - } finally { - isProgrammaticResize = false; - } - } - - private void applyResolutionToStage(Stage stage) { - stage.setWidth(resolution.getWidth()); - stage.setHeight(resolution.getHeight()); - } - - public static void initOS(boolean asMobile) { - String os = System.getProperty("os.name").toLowerCase(); - if (os.contains("android") || os.contains("ios") || asMobile) { - isMobile = true; } } - - public static boolean isMobile() { - return isMobile; - } - - public static Stage getPrimaryStage() { - return primaryStage; - } - - public static Scene getMainScene() { - return mainScene; - } - - public static SettingsManager.Resolution getResolution() { - return resolution; - } - - @Override - public void setAssets(ManifestAssetManager assets) { - assetManager = assets; - } } \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/ClientLauncher.java b/src/main/java/xyz/samiker/theendlessweave/client/ClientLauncher.java index 9449bd1..16b0336 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/ClientLauncher.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/ClientLauncher.java @@ -1,6 +1,7 @@ package xyz.samiker.theendlessweave.client; -import javafx.application.Application; +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; import xyz.samiker.theendlessweave.Main; import xyz.samiker.theendlessweave.core.settings.SettingsEnum; import xyz.samiker.theendlessweave.core.settings.SettingsManager; @@ -13,15 +14,18 @@ public class ClientLauncher { public static void main(String[] args) { + Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); try { SettingsManager.loadSettings(); + String[] parts = SettingsManager.getString(SettingsEnum.RESOLUTION).split("x"); + config.setWindowedMode(Integer.parseInt(parts[0]), Integer.parseInt(parts[1])); if (!SettingsManager.getBoolean(SettingsEnum.VSYNC)) { - System.setProperty("prism.vsync", "false"); - System.setProperty("javafx.animation.framerate", String.valueOf(SettingsManager.getInt(SettingsEnum.FPS_LIMIT))); + config.useVsync(SettingsManager.getBoolean(SettingsEnum.VSYNC)); + config.setForegroundFPS(SettingsManager.getInt(SettingsEnum.FPS_LIMIT)); } - Application.launch(Main.class, args); + new Lwjgl3Application(new Main(), config); } catch (Throwable t) { logError(t); } diff --git a/src/main/java/xyz/samiker/theendlessweave/client/assets/ManifestAssetManager.java b/src/main/java/xyz/samiker/theendlessweave/client/assets/ManifestAssetManager.java index b0f7df2..20cf2cc 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/assets/ManifestAssetManager.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/assets/ManifestAssetManager.java @@ -1,129 +1,80 @@ package xyz.samiker.theendlessweave.client.assets; -import java.io.IOException; -import java.util.List; +import com.badlogic.gdx.assets.AssetManager; +import com.badlogic.gdx.assets.loaders.TextureLoader; +import com.badlogic.gdx.audio.Music; +import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.graphics.Texture; -public class ManifestAssetManager { +import java.util.HashMap; +import java.util.Map; +public class ManifestAssetManager { private final ResourceManifest manifest; - private final TextureManager textureManager = new TextureManager(); - private final SoundManager soundManager = new SoundManager(); + private final AssetManager gdxManager; - private static final String TEXTURES_DIR = "xyz/samiker/theendlessweave/assets/textures/"; - private static final String SOUNDS_DIR = "xyz/samiker/theendlessweave/assets/sounds/"; - //private static final String MODELS_DIR = "xyz/samiker/theendlessweave/assets/models/"; + private final TextureManager textureManager; + private final SoundManager soundManager; + + private final Map idToPath = new HashMap<>(); public ManifestAssetManager(ResourceManifest manifest) { this.manifest = manifest; + this.gdxManager = new AssetManager(); + this.textureManager = new TextureManager(this); + this.soundManager = new SoundManager(this); } - public void loadAll(AssetLoadCallback callback) throws IOException { - loadTextures(callback); - loadSounds(callback); - // loadModels(callback); - } - - public void loadTextures(AssetLoadCallback callback) { - callback.onCategoryStart("Textures"); - List textures = manifest.findByDirectoryRecursive(TEXTURES_DIR); - - textures = textures.stream() - .filter(e -> e.getName().endsWith(".png") || - e.getName().endsWith(".jpg") || - e.getName().endsWith(".jpeg")) - .toList(); - - callback.log(AssetLoadCallback.LogLevel.INFO, - "Found " + textures.size() + " textures in manifest"); - - for (ResourceManifest.ResourceEntry entry : textures) { - String textureId = getAssetId(entry); - textureManager.load(textureId, entry, callback); - } - - callback.onCategoryComplete("Textures"); - } - - public void loadSounds(AssetLoadCallback callback) { - callback.onCategoryStart("Sounds"); + public void queueAll() { + TextureLoader.TextureParameter texParam = new TextureLoader.TextureParameter(); + texParam.minFilter = Texture.TextureFilter.Nearest; + texParam.magFilter = Texture.TextureFilter.Nearest; - List sounds = - manifest.findByDirectoryRecursive(SOUNDS_DIR); + for (ResourceManifest.ResourceEntry entry : manifest.getAllResources()) { + String path = entry.getPath(); + String name = entry.getName(); - sounds = sounds.stream() - .filter(e -> e.getName().endsWith(".wav") || - e.getName().endsWith(".mp3") || - e.getName().endsWith(".ogg")) - .toList(); + String id = generateId(path); + idToPath.put(id, path); - callback.log(AssetLoadCallback.LogLevel.INFO, - "Found " + sounds.size() + " sounds in manifest"); - - for (ResourceManifest.ResourceEntry entry : sounds) { - String soundId = getAssetId(entry); - soundManager.load(soundId, entry, callback); - } - - callback.onCategoryComplete("Sounds"); - } - - /** - * Загрузить ассеты конкретной категории. - * @param category Название категории (например, "enemies", "tiles") - */ - public void loadCategory(String category, AssetLoadCallback callback) { - String categoryPath = TEXTURES_DIR + category + "/"; - List entries = - manifest.findByDirectory(categoryPath); - - callback.onCategoryStart("Category: " + category); - - for (ResourceManifest.ResourceEntry entry : entries) { - if (entry.getName().endsWith(".png") || entry.getName().endsWith(".jpg")) { - String textureId = category + "/" + getFileNameWithoutExt(entry.getName()); - textureManager.load(textureId, entry, callback); + if (name.endsWith(".png") || name.endsWith(".jpg")) { + gdxManager.load(path, Texture.class, texParam); + } + else if (name.endsWith(".wav")) { + gdxManager.load(path, Sound.class); + } + else if (name.endsWith(".mp3") || name.endsWith(".ogg")) { + gdxManager.load(path, Music.class); } } - - callback.onCategoryComplete("Category: " + category); } /** - * Генерирует ID ассета из пути. - * Например: "xyz/.../textures/enemies/goblin.png" -> "enemies/goblin" + * @return true, если загрузка завершена */ - private String getAssetId(ResourceManifest.ResourceEntry entry) { - String path = entry.getPath(); - - // Убираем базовый путь - if (path.startsWith(TEXTURES_DIR)) { - path = path.substring(TEXTURES_DIR.length()); - } else if (path.startsWith(SOUNDS_DIR)) { - path = path.substring(SOUNDS_DIR.length()); - } - - return getFileNameWithoutExt(path); + public boolean update() { + return gdxManager.update(); } - private String getFileNameWithoutExt(String filename) { - int lastDot = filename.lastIndexOf('.'); - return lastDot > 0 ? filename.substring(0, lastDot) : filename; + public float getProgress() { + return gdxManager.getProgress(); } - public TextureManager textures() { - return textureManager; - } - - public SoundManager sounds() { - return soundManager; + public T get(String id, Class type) { + String path = idToPath.get(id); + if (path != null && gdxManager.isLoaded(path)) { + return gdxManager.get(path, type); + } + return null; } - public void dispose() { - textureManager.clear(); - soundManager.stopAll(); + private String generateId(String path) { + String filename = path.substring(path.lastIndexOf('/') + 1); + int dotIndex = filename.lastIndexOf('.'); + return dotIndex > 0 ? filename.substring(0, dotIndex) : filename; } - public ResourceManifest manifest() { //костыль - return manifest; - } + public TextureManager textures() { return textureManager; } + public SoundManager sounds() { return soundManager; } + public void dispose() { gdxManager.dispose(); } } \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/assets/ResourceManifest.java b/src/main/java/xyz/samiker/theendlessweave/client/assets/ResourceManifest.java index 0630f30..622a88d 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/assets/ResourceManifest.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/assets/ResourceManifest.java @@ -1,54 +1,45 @@ package xyz.samiker.theendlessweave.client.assets; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.files.FileHandle; + import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; import java.util.*; import java.util.stream.Collectors; public class ResourceManifest { - private static final String MANIFEST_PATH = "/resources-manifest.txt"; + private static final String MANIFEST_PATH = "resources-manifest.txt"; private final Map resourcesByName = new HashMap<>(); private final Map> resourcesByPath = new HashMap<>(); - /** - * Загрузить и распарсить манифест. - */ public static ResourceManifest load() throws IOException { ResourceManifest manifest = new ResourceManifest(); - try (InputStream is = ResourceManifest.class.getResourceAsStream(MANIFEST_PATH)) { - if (is == null) { - throw new IOException("Manifest file not found: " + MANIFEST_PATH); - } - - try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { - - String line; - String currentName = null; - String currentPath = null; + FileHandle file = Gdx.files.internal(MANIFEST_PATH); + if (!file.exists()) { + throw new IOException("Manifest file not found: " + MANIFEST_PATH); + } - while ((line = reader.readLine()) != null) { - line = line.trim(); + try (BufferedReader reader = new BufferedReader(file.reader("UTF-8"))) { + String line; + String currentName = null; + String currentPath = null; - if (line.isEmpty() || line.startsWith("#")) { - continue; - } + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("#")) continue; - if (line.startsWith("resource.name=")) { - currentName = line.substring("resource.name=".length()); - } else if (line.startsWith("resource.path=")) { - currentPath = line.substring("resource.path=".length()); + if (line.startsWith("resource.name=")) { + currentName = line.substring("resource.name=".length()); + } else if (line.startsWith("resource.path=")) { + currentPath = line.substring("resource.path=".length()); - if (currentName != null && currentPath != null) { - ResourceEntry entry = new ResourceEntry(currentName, currentPath); - manifest.addEntry(entry); - currentName = null; - currentPath = null; - } + if (currentName != null) { + manifest.addEntry(new ResourceEntry(currentName, currentPath)); + currentName = null; + currentPath = null; } } } @@ -57,120 +48,37 @@ public static ResourceManifest load() throws IOException { } private void addEntry(ResourceEntry entry) { - resourcesByName.put(entry.name, entry); - String directory = getDirectory(entry.path); + resourcesByName.put(entry.getName(), entry); + String directory = getDirectory(entry.getPath()); resourcesByPath.computeIfAbsent(directory, k -> new ArrayList<>()).add(entry); } - /** - * Найти ресурс по имени файла. - * @param name Имя файла (например, "player.png") - * @return Entry или null - */ - public ResourceEntry findByName(String name) { - return resourcesByName.get(name); - } - - /** - * Найти все ресурсы в указанной директории. - * @param directory Путь к директории (например, "xyz/samiker/theendlessweave/css/") - * @return Список ресурсов в этой директории - */ public List findByDirectory(String directory) { - // Нормализуем путь (убираем начальный слэш, добавляем конечный) - String normalizedDir = normalizePath(directory); - return resourcesByPath.getOrDefault(normalizedDir, Collections.emptyList()); + return resourcesByPath.getOrDefault(normalizePath(directory), Collections.emptyList()); } - /** - * Найти все ресурсы в указанной директории и всех поддиректориях (рекурсивно). - * @param directory Путь к директории (например, "xyz/samiker/theendlessweave/assets/textures/") - * @return Список всех ресурсов в этой директории и вложенных - */ public List findByDirectoryRecursive(String directory) { String normalizedDir = normalizePath(directory); - return resourcesByName.values().stream() - .filter(entry -> entry.path.startsWith(normalizedDir)) + .filter(entry -> entry.getPath().startsWith(normalizedDir)) .collect(Collectors.toList()); } - /** - * Найти все ресурсы с указанным расширением. - * @param extension Расширение файла (например, ".png", ".json") - * @return Список ресурсов с этим расширением - */ - public List findByExtension(String extension) { - String ext = extension.startsWith(".") ? extension : "." + extension; - return resourcesByName.values().stream() - .filter(e -> e.name.endsWith(ext)) - .collect(Collectors.toList()); - } - - /** - * Найти все ресурсы в директории с указанным расширением. - */ - public List findByDirectoryAndExtension(String directory, String extension) { - String ext = extension.startsWith(".") ? extension : "." + extension; - return findByDirectory(directory).stream() - .filter(e -> e.name.endsWith(ext)) - .collect(Collectors.toList()); - } - - /** - * Получить все ресурсы. - */ public Collection getAllResources() { return resourcesByName.values(); } - /** - * Получить статистику манифеста. - */ - public ManifestStats getStats() { - Map byExtension = new HashMap<>(); - Map byDirectory = new HashMap<>(); - - for (ResourceEntry entry : resourcesByName.values()) { - String ext = getExtension(entry.name); - byExtension.merge(ext, 1, Integer::sum); - - String dir = getDirectory(entry.path); - byDirectory.merge(dir, 1, Integer::sum); - } - - return new ManifestStats( - resourcesByName.size(), - byExtension, - byDirectory - ); - } - - // Утилиты - private String getDirectory(String path) { int lastSlash = path.lastIndexOf('/'); return lastSlash > 0 ? path.substring(0, lastSlash + 1) : ""; } - private String getExtension(String filename) { - int lastDot = filename.lastIndexOf('.'); - return lastDot > 0 ? filename.substring(lastDot) : ""; - } - private String normalizePath(String path) { - if (path.startsWith("/")) { - path = path.substring(1); - } - if (!path.isEmpty() && !path.endsWith("/")) { - path = path + "/"; - } + if (path.startsWith("/")) path = path.substring(1); + if (!path.isEmpty() && !path.endsWith("/")) path = path + "/"; return path; } - /** - * Запись в манифесте. - */ public static class ResourceEntry { private final String name; private final String path; @@ -180,77 +88,10 @@ public ResourceEntry(String name, String path) { this.path = path; } - public String getName() { - return name; - } - - public String getPath() { - return path; - } - - /** - * Получить полный путь с начальным слэшем для загрузки. - */ - public String getFullPath() { - return "/" + path; - } - - /** - * Открыть InputStream для этого ресурса. - */ - public InputStream openStream() throws IOException { - InputStream is = ResourceManifest.class.getResourceAsStream(getFullPath()); - if (is == null) { - throw new IOException("Resource not found: " + getFullPath()); - } - return is; - } + public String getName() { return name; } + public String getPath() { return path; } @Override - public String toString() { - return "ResourceEntry{name='" + name + "', path='" + path + "'}"; - } - } - - /** - * Статистика манифеста. - */ - public static class ManifestStats { - private final int totalResources; - private final Map byExtension; - private final Map byDirectory; - - public ManifestStats(int totalResources, - Map byExtension, - Map byDirectory) { - this.totalResources = totalResources; - this.byExtension = byExtension; - this.byDirectory = byDirectory; - } - - public int getTotalResources() { - return totalResources; - } - - public Map getByExtension() { - return byExtension; - } - - public Map getByDirectory() { - return byDirectory; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Total resources: ").append(totalResources).append("\n"); - sb.append("By extension:\n"); - byExtension.forEach((ext, count) -> - sb.append(" ").append(ext).append(": ").append(count).append("\n")); - sb.append("By directory:\n"); - byDirectory.forEach((dir, count) -> - sb.append(" ").append(dir).append(": ").append(count).append("\n")); - return sb.toString(); - } + public String toString() { return "ResourceEntry{name='" + name + "', path='" + path + "'}"; } } } \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/assets/SoundManager.java b/src/main/java/xyz/samiker/theendlessweave/client/assets/SoundManager.java index 376048e..8adab91 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/assets/SoundManager.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/assets/SoundManager.java @@ -1,89 +1,56 @@ package xyz.samiker.theendlessweave.client.assets; -import javafx.scene.media.AudioClip; -import javafx.scene.media.Media; -import javafx.scene.media.MediaPlayer; +import com.badlogic.gdx.audio.Music; +import com.badlogic.gdx.audio.Sound; import java.util.HashMap; import java.util.Map; -/** - * Упрощённый менеджер звуков: - * - короткие SFX через AudioClip (лёгкие) - * - музыка через переиспользуемый MediaPlayer - */ public class SoundManager { - private final Map clips = new HashMap<>(); - private final Map mediaMap = new HashMap<>(); - private final Map musicPlayers = new HashMap<>(); + private final ManifestAssetManager manager; + private final Map activeMusic = new HashMap<>(); - public void load(String id, ResourceManifest.ResourceEntry entry, AssetLoadCallback callback) { - callback.onAssetLoadStart(id, entry.getPath()); - try { - String path = entry.getFullPath(); - String url = getClass().getResource(path).toExternalForm(); - - if (path.endsWith(".wav") || path.endsWith(".ogg")) { - // короткие эффекты — AudioClip - AudioClip clip = new AudioClip(url); - clips.put(id, clip); - } else { - // длинные треки — Media (для музыки) - Media media = new Media(url); - mediaMap.put(id, media); - } + public SoundManager(ManifestAssetManager manager) { + this.manager = manager; + } - callback.onAssetLoadComplete(id); - } catch (Exception e) { - callback.onAssetLoadError(id, e); - System.err.println("Failed to load sound: " + id + " - " + e.getMessage()); + public long playClip(String id, float volume) { + Sound sound = manager.get(id, Sound.class); + if (sound != null) { + return sound.play(volume); } + return -1; } - // SFX - public void playClip(String id, double volume) { - AudioClip clip = clips.get(id); - if (clip != null) { - clip.setVolume(volume); - clip.play(); - } else { - System.err.println("AudioClip not found: " + id); - } + public long playClip(String id) { + return playClip(id, 1.0f); } - // Music (one player per track, reuse) - public void playMusic(String id, double volume, boolean loop) { - MediaPlayer player = musicPlayers.get(id); - if (player == null) { - Media media = mediaMap.get(id); - if (media == null) { - System.err.println("Media not loaded: " + id); - return; - } - player = new MediaPlayer(media); - musicPlayers.put(id, player); + public void playMusic(String id, float volume, boolean loop) { + if (activeMusic.containsKey(id)) { + activeMusic.get(id).stop(); + } + + Music music = manager.get(id, Music.class); + if (music != null) { + music.setVolume(volume); + music.setLooping(loop); + music.play(); + activeMusic.put(id, music); } - player.setVolume(volume); - player.setCycleCount(loop ? MediaPlayer.INDEFINITE : 1); - player.play(); } public void stopMusic(String id) { - MediaPlayer player = musicPlayers.get(id); - if (player != null) { - player.stop(); - player.dispose(); - musicPlayers.remove(id); + if (activeMusic.containsKey(id)) { + activeMusic.get(id).stop(); + activeMusic.remove(id); } } public void stopAll() { - for (MediaPlayer p : musicPlayers.values()) { - p.stop(); - p.dispose(); + for (Music m : activeMusic.values()) { + m.stop(); } - musicPlayers.clear(); - clips.clear(); - mediaMap.clear(); + activeMusic.clear(); } } \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/assets/TextureManager.java b/src/main/java/xyz/samiker/theendlessweave/client/assets/TextureManager.java index 5cd8894..067cff7 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/assets/TextureManager.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/assets/TextureManager.java @@ -1,69 +1,21 @@ package xyz.samiker.theendlessweave.client.assets; -import javafx.scene.image.Image; - -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; +import com.badlogic.gdx.graphics.Texture; public class TextureManager { - private final Map textures = new HashMap<>(); - - public void load(String id, ResourceManifest.ResourceEntry entry, AssetLoadCallback callback) { - callback.onAssetLoadStart(id, entry.getPath()); - try { - InputStream stream = entry.openStream(); - - // ВАЖНО: Загружаем синхронно (backgroundLoading = false) - // Иначе текстуры будут пустыми/белыми! - Image image = new Image(stream, 0, 0, true, false); - - // Ждём завершения загрузки - if (image.isError()) { - throw new Exception("Failed to load image: " + image.getException().getMessage()); - } - - textures.put(id, image); - callback.onAssetLoadComplete(id); - - // Закрываем stream - stream.close(); - } catch (Exception e) { - callback.onAssetLoadError(id, e); - // Не бросаем исключение - продолжаем загрузку других ассетов - System.err.println("Failed to load texture: " + id + " - " + e.getMessage()); - e.printStackTrace(); - } - } + private final ManifestAssetManager manager; - public Image get(String id) { - Image texture = textures.get(id); - if (texture == null) { - throw new IllegalArgumentException("Texture not loaded: " + id + "\nAvailable textures: " + textures.keySet()); - } - return texture; + public TextureManager(ManifestAssetManager manager) { + this.manager = manager; } - - public Image getOrDefault(String id, Image defaultTexture) { - return textures.getOrDefault(id, defaultTexture); + public Texture get(String id) { + return manager.get(id, Texture.class); } - public boolean has(String id) { - return textures.containsKey(id); - } - - public void clear() { - textures.clear(); - } - - /** - * Для отладки: вывести все загруженные ID. - */ - public void printLoadedTextures() { - System.out.println("=== Loaded Textures ==="); - textures.keySet().forEach(id -> - System.out.println(" " + id + " (" + - textures.get(id).getWidth() + "x" + - textures.get(id).getHeight() + ")")); + try { + return get(id) != null; + } catch (Exception e) { + return false; + } } -} +} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/controllers/AssetAware.java b/src/main/java/xyz/samiker/theendlessweave/client/controllers/AssetAware.java deleted file mode 100644 index 9e13129..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/controllers/AssetAware.java +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.samiker.theendlessweave.client.controllers; - -import xyz.samiker.theendlessweave.client.assets.ManifestAssetManager; - -/** - * Интерфейс для контроллеров, которым нужен доступ к AssetManager. - * Реализуй этот интерфейс в любом контроллере, где нужны ассеты. - */ -public interface AssetAware { - /** - * Вызывается автоматически из Main.showScene() после загрузки FXML. - * @param assets Менеджер ассетов - */ - void setAssets(ManifestAssetManager assets); -} - diff --git a/src/main/java/xyz/samiker/theendlessweave/client/controllers/GameController.java b/src/main/java/xyz/samiker/theendlessweave/client/controllers/GameController.java deleted file mode 100644 index dc27a23..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/controllers/GameController.java +++ /dev/null @@ -1,175 +0,0 @@ -package xyz.samiker.theendlessweave.client.controllers; - -import javafx.fxml.FXML; -import javafx.fxml.Initializable; -import javafx.scene.canvas.Canvas; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.ProgressBar; -import javafx.scene.input.KeyCode; -import javafx.scene.input.MouseButton; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.Pane; -import javafx.scene.layout.VBox; -import javafx.scene.paint.Color; -import xyz.samiker.theendlessweave.Main; -import xyz.samiker.theendlessweave.client.assets.ManifestAssetManager; -import xyz.samiker.theendlessweave.client.custom.InputManager; -import xyz.samiker.theendlessweave.client.custom.VirtualJoystick; -import xyz.samiker.theendlessweave.client.network.GameClient; -import xyz.samiker.theendlessweave.client.systems.*; -import xyz.samiker.theendlessweave.core.GameData; -import xyz.samiker.theendlessweave.core.entities.Entity; -import xyz.samiker.theendlessweave.core.entities.components.PositionComponent; -import xyz.samiker.theendlessweave.core.logic.Game; -import xyz.samiker.theendlessweave.core.logic.GameState; -import xyz.samiker.theendlessweave.core.logic.Loop; -import xyz.samiker.theendlessweave.core.network.packets.PacketLoginResponse; -import xyz.samiker.theendlessweave.core.settings.SettingsEnum; -import xyz.samiker.theendlessweave.core.settings.SettingsManager; - -import java.io.IOException; -import java.net.URL; -import java.util.ResourceBundle; - -public class GameController implements Initializable, AssetAware, GameDataAware { - @FXML public Pane overlayUIPane; - @FXML public AnchorPane mobileControlsPane; - @FXML public Button attackBtn; - @FXML private Pane gamePane; - @FXML private Pane worldUIPane; - @FXML private ProgressBar healthBar; - @FXML private Label healthLabel; - @FXML private Label scoreLabel; - @FXML private Label levelLabel; - @FXML private VBox pauseMenu; - @FXML private Label loadingLabel; - - private Game game; - private Loop gameLoop; - private GameClient client; - private ManifestAssetManager assets; - - @Override - public void setAssets(ManifestAssetManager assets) { - this.assets = assets; - } - - @Override - public void setData(GameData initialData) { - initControl(); - client = new GameClient(); - try { - if (loadingLabel != null) loadingLabel.setVisible(true); - client.connect("127.0.0.1", (response) -> { - initializeGameSession(response); - }); - } catch (IOException e) { - e.printStackTrace(); - // TODO: Показать ошибку подключения пользователю и вернуться в меню - throw new RuntimeException("Server unreachable"); - } - } - - private void initializeGameSession(PacketLoginResponse serverData) { - if (loadingLabel != null) loadingLabel.setVisible(false); - - GameData finalData = new GameData( - serverData.gameData.settings, - serverData.gameData.width, - serverData.gameData.height, - serverData.gameData.seed - ); - - game = new Game(finalData); - Entity player = game.createPlayer(serverData.playerEntityId); - player.getComponent(PositionComponent.class).set(serverData.spawnX, serverData.spawnY); - - Canvas tileCanvas = new Canvas(finalData.width, finalData.height); - Canvas entityCanvas = new Canvas(finalData.width, finalData.height); - - tileCanvas.widthProperty().bind(gamePane.widthProperty()); - tileCanvas.heightProperty().bind(gamePane.heightProperty()); - entityCanvas.widthProperty().bind(gamePane.widthProperty()); - entityCanvas.heightProperty().bind(gamePane.heightProperty()); - - gamePane.getChildren().clear(); - gamePane.getChildren().addAll(tileCanvas, entityCanvas); - - CameraSystem cameraSystem = new CameraSystem(); - var camera = cameraSystem.getCamera(); - var map = game.getGameMap(); - var profiler = game.getProfiler(); - - game.addLogicSystem(new ClientNetworkEventSystem(client, game)); - game.addLogicSystem(new InputSystem(client, game, camera)); - game.addLogicSystem(new ClientNetworkReceiverSystem(client, game)); - - game.addRenderSystem(new DebugRenderSystem(overlayUIPane, profiler)); - game.addRenderSystem(new TileRenderSystem(tileCanvas, map, camera, assets)); - game.addRenderSystem(new RenderSystem(entityCanvas, camera)); - game.addRenderSystem(new WorldUIRenderer(worldUIPane, camera)); - game.addRenderSystem(new HudRenderSystem(healthBar, healthLabel, scoreLabel, levelLabel)); - game.addRenderSystem(cameraSystem); - - gameLoop = new Loop(game); - gameLoop.start(); - - InputManager.registerKeyPressedAction(KeyCode.ESCAPE, this::togglePause); - } - - private void initControl() { - boolean isMobile = SettingsManager.getString(SettingsEnum.CONTROL).equals("touch") || Main.isMobile(); - mobileControlsPane.setVisible(isMobile); - - if (isMobile) { - VirtualJoystick moveStick = new VirtualJoystick(80, Color.TRANSPARENT, Color.WHITE); - - AnchorPane.setBottomAnchor(moveStick, 50.0); - AnchorPane.setLeftAnchor(moveStick, 50.0); - - moveStick.setOnDrag((x, y) -> { - double deadzone = 0.3; - InputManager.handleAxis(x, deadzone, KeyCode.D, KeyCode.A); - InputManager.handleAxis(y, deadzone, KeyCode.S, KeyCode.W); - }); - - VirtualJoystick aimStick = new VirtualJoystick(60, Color.TRANSPARENT, Color.WHITE); - - AnchorPane.setBottomAnchor(aimStick, 150.0); - AnchorPane.setRightAnchor(aimStick, 50.0); - - aimStick.setOnDrag((dx, dy) -> { - double centerX = mobileControlsPane.getWidth() / 2.0; - double centerY = mobileControlsPane.getHeight() / 2.0; - double radius = 200.0; - - InputManager.setMousePosition(centerX + dx * radius, centerY + dy * radius); - }); - - mobileControlsPane.getChildren().addAll(moveStick, aimStick); - - attackBtn.setOnMousePressed(e -> InputManager.simulateMousePress(MouseButton.PRIMARY)); - attackBtn.setOnMouseReleased(e -> InputManager.simulateMouseRelease(MouseButton.PRIMARY)); - } - } - - private void togglePause() { - gameLoop.togglePause(); - pauseMenu.setVisible(gameLoop.getGameState() != GameState.PLAYING); - } - - @FXML - public void onResume() { - this.togglePause(); - } - - @FXML - public void onBackToMenu() throws IOException { - Main.showScene("/xyz/samiker/theendlessweave/fxml/main-menu.fxml", "/xyz/samiker/theendlessweave/css/main-menu.css"); - } - - @Override - public void initialize(URL url, ResourceBundle resourceBundle) { - } -} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/controllers/GameDataAware.java b/src/main/java/xyz/samiker/theendlessweave/client/controllers/GameDataAware.java deleted file mode 100644 index 1eb263f..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/controllers/GameDataAware.java +++ /dev/null @@ -1,14 +0,0 @@ -package xyz.samiker.theendlessweave.client.controllers; - -import xyz.samiker.theendlessweave.core.GameData; - -/** - * Интерфейс для контроллеров, которым нужно знать имя/параметры уровня. - */ -public interface GameDataAware { - /** - * Вызывается из Main.showGameScene() для передачи имени уровня. - * @param data Имя файла уровня (например, "level1.json") - */ - void setData(GameData data); -} diff --git a/src/main/java/xyz/samiker/theendlessweave/client/controllers/GenerationSceneController.java b/src/main/java/xyz/samiker/theendlessweave/client/controllers/GenerationSceneController.java deleted file mode 100644 index 32e8f42..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/controllers/GenerationSceneController.java +++ /dev/null @@ -1,287 +0,0 @@ -package xyz.samiker.theendlessweave.client.controllers; - -import javafx.fxml.FXML; -import javafx.scene.control.*; -import xyz.samiker.theendlessweave.Main; -import xyz.samiker.theendlessweave.core.GameData; -import xyz.samiker.theendlessweave.core.gamemap.MapGeneratorSettings; - -import java.util.Random; - -/** - * Контроллер сцены генерации мира. - */ -public class GenerationSceneController { - - @FXML public CheckBox inverseGeneration; - @FXML public Spinner wallsNoiseSpinner; - // ============================ - // ГЛАВНАЯ ВКЛАДКА - // ============================ - @FXML private ComboBox presetComboBox; - @FXML private ToggleButton difficultyEasy; - @FXML private ToggleButton difficultyNormal; - @FXML private ToggleButton difficultyHard; - @FXML private ToggleButton gameModeSandbox; - @FXML private ToggleButton gameModeProgression; - @FXML private Slider mapSizeSlider; - @FXML private Label mapSizeLabel; - - // ============================ - // ДЕТАЛЬНАЯ НАСТРОЙКА - // ============================ - @FXML private Spinner widthSpinner; - @FXML private Spinner heightSpinner; - @FXML private Spinner minRoomSizeSpinner; - @FXML private Spinner maxRoomSizeSpinner; - @FXML private Spinner maxRoomsSpinner; - @FXML private CheckBox randomShapesCheckBox; - @FXML private Spinner corridorWidthSpinner; - @FXML private CheckBox variedCorridorsCheckBox; - @FXML private CheckBox roomNoiseCheckBox; - @FXML private Slider noiseIntensitySlider; - @FXML private Label noiseIntensityLabel; - @FXML private Slider enemySpawnChanceSlider; - @FXML private Label enemySpawnChanceLabel; - @FXML private Spinner minEnemyDistanceSpinner; - @FXML private CheckBox spawnOnlyCombatCheckBox; - @FXML private TextField seedTextField; - - private ToggleGroup difficultyGroup; - private ToggleGroup gameModeGroup; - - @FXML - public void initialize() { - setupToggleGroups(); - setupSpinners(); - setupSliders(); - setupPresetComboBox(); - loadDefaultSettings(); - } - - /** - * Настройка toggle групп для кнопок. - */ - private void setupToggleGroups() { - // Группа сложности - difficultyGroup = new ToggleGroup(); - difficultyEasy.setToggleGroup(difficultyGroup); - difficultyNormal.setToggleGroup(difficultyGroup); - difficultyHard.setToggleGroup(difficultyGroup); - - // Группа режима игры - gameModeGroup = new ToggleGroup(); - gameModeSandbox.setToggleGroup(gameModeGroup); - gameModeProgression.setToggleGroup(gameModeGroup); - - // Слушатель изменения сложности - difficultyGroup.selectedToggleProperty().addListener((obs, oldVal, newVal) -> { - if (newVal != null) { - updateEnemySettingsByDifficulty(); - } - }); - } - - /** - * Настройка spinners. - */ - private void setupSpinners() { - widthSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(50, 10000, 100, 10)); - heightSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(50, 10000, 100, 10)); - minRoomSizeSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(5, 1000, 8, 1)); - maxRoomSizeSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(8, 1000, 12, 1)); - maxRoomsSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(5, 1000, 15, 1)); - corridorWidthSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 10, 1, 1)); - minEnemyDistanceSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 100, 3, 1)); - } - - /** - * Настройка sliders и их labels. - */ - private void setupSliders() { - // Map size slider - mapSizeSlider.valueProperty().addListener((obs, oldVal, newVal) -> { - int size = newVal.intValue(); - mapSizeLabel.setText(size + "x" + size); - widthSpinner.getValueFactory().setValue(size); - heightSpinner.getValueFactory().setValue(size); - }); - - // Noise intensity slider - noiseIntensitySlider.valueProperty().addListener((obs, oldVal, newVal) -> { - noiseIntensityLabel.setText(String.format("%.2f", newVal.doubleValue())); - }); - - // Enemy spawn chance slider - enemySpawnChanceSlider.valueProperty().addListener((obs, oldVal, newVal) -> { - enemySpawnChanceLabel.setText(String.format("%.2f", newVal.doubleValue())); - }); - } - - /** - * Настройка ComboBox пресетов. - */ - private void setupPresetComboBox() { - presetComboBox.getSelectionModel().selectFirst(); - presetComboBox.valueProperty().addListener((obs, oldVal, newVal) -> { - if (newVal != null) { - loadPreset(newVal); - } - }); - } - - /** - * Загружает настройки по умолчанию. - */ - private void loadDefaultSettings() { - loadSettingsFromRecord(MapGeneratorSettings.defaults()); - } - - /** - * Загружает пресет по названию. - */ - private void loadPreset(String presetName) { - MapGeneratorSettings settings = switch (presetName) { - case "Разнообразный" -> MapGeneratorSettings.diverse(); - case "Хардкор" -> MapGeneratorSettings.hardcore(); - case "Пользовательский" -> null; // Не меняем настройки - default -> MapGeneratorSettings.defaults(); - }; - - if (settings != null) { - loadSettingsFromRecord(settings); - } - } - - /** - * Загружает настройки из MapGeneratorSettings в UI. - */ - private void loadSettingsFromRecord(MapGeneratorSettings settings) { - inverseGeneration.setSelected(settings.isInverseGeneration()); - wallsNoiseSpinner.getValueFactory().setValue(settings.getWallsNoise()); - minRoomSizeSpinner.getValueFactory().setValue(settings.getMinRoomSize()); - maxRoomSizeSpinner.getValueFactory().setValue(settings.getMaxRoomSize()); - maxRoomsSpinner.getValueFactory().setValue(settings.getMaxRooms()); - randomShapesCheckBox.setSelected(settings.isUseRandomShapes()); - corridorWidthSpinner.getValueFactory().setValue(settings.getCorridorWidth()); - variedCorridorsCheckBox.setSelected(settings.isUseVariedCorridors()); - roomNoiseCheckBox.setSelected(settings.isUseRoomNoise()); - noiseIntensitySlider.setValue(settings.getNoiseIntensity()); - enemySpawnChanceSlider.setValue(settings.getEnemySpawnChance()); - minEnemyDistanceSpinner.getValueFactory().setValue(settings.getMinEnemyDistance()); - spawnOnlyCombatCheckBox.setSelected(settings.isSpawnOnlyInCombat()); - } - - /** - * Обновляет настройки врагов в зависимости от сложности. - */ - private void updateEnemySettingsByDifficulty() { - Toggle selected = difficultyGroup.getSelectedToggle(); - - if (selected == difficultyEasy) { - enemySpawnChanceSlider.setValue(0.3); - minEnemyDistanceSpinner.getValueFactory().setValue(4); - } else if (selected == difficultyNormal) { - enemySpawnChanceSlider.setValue(0.5); - minEnemyDistanceSpinner.getValueFactory().setValue(3); - } else if (selected == difficultyHard) { - enemySpawnChanceSlider.setValue(0.7); - minEnemyDistanceSpinner.getValueFactory().setValue(2); - } - } - - /** - * Создаёт MapGeneratorSettings из текущих настроек UI. - */ - private MapGeneratorSettings createSettingsFromUI() { - return new MapGeneratorSettings( - inverseGeneration.isSelected(), - wallsNoiseSpinner.getValue(), - minRoomSizeSpinner.getValue(), - maxRoomSizeSpinner.getValue(), - maxRoomsSpinner.getValue(), - randomShapesCheckBox.isSelected(), - corridorWidthSpinner.getValue(), - variedCorridorsCheckBox.isSelected(), - roomNoiseCheckBox.isSelected(), - noiseIntensitySlider.getValue(), - enemySpawnChanceSlider.getValue(), - minEnemyDistanceSpinner.getValue(), - spawnOnlyCombatCheckBox.isSelected() - ); - } - - /** - * Получает seed из текстового поля или генерирует новый. - */ - private long getSeed() { - String seedText = seedTextField.getText().trim(); - - if (seedText.isEmpty()) { - return System.currentTimeMillis(); - } - - try { - return Long.parseLong(seedText); - } catch (NumberFormatException e) { - return seedText.hashCode(); - } - } - - /** - * Обработчик кнопки "Начать игру". - */ - @FXML - private void onStartGame() { - MapGeneratorSettings settings = createSettingsFromUI(); - int width = widthSpinner.getValue(); - int height = heightSpinner.getValue(); - long seed = getSeed(); - - GameData gameData = new GameData(settings, width, height, seed); - - try { - Main.showGameScene( - gameData, - "/xyz/samiker/theendlessweave/fxml/game-scene.fxml", - "/xyz/samiker/theendlessweave/css/game-scene.css" - ); - } catch (Exception e) { - showError("Ошибка запуска", "Не удалось запустить игру: " + e.getMessage()); - e.printStackTrace(); - } - } - - /** - * Обработчик кнопки "Сбросить настройки". - */ - @FXML - private void onResetSettings() { - loadDefaultSettings(); - presetComboBox.getSelectionModel().selectFirst(); - difficultyNormal.setSelected(true); - gameModeSandbox.setSelected(true); - mapSizeSlider.setValue(100); - seedTextField.clear(); - } - - /** - * Обработчик кнопки "Случайный seed". - */ - @FXML - private void onRandomSeed() { - long randomSeed = new Random().nextLong(); - seedTextField.setText(String.valueOf(randomSeed)); - } - - /** - * Показывает диалог с ошибкой. - */ - private void showError(String title, String message) { - Alert alert = new Alert(Alert.AlertType.ERROR); - alert.setTitle(title); - alert.setHeaderText(null); - alert.setContentText(message); - alert.showAndWait(); - } -} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/controllers/LoadingScreenController.java b/src/main/java/xyz/samiker/theendlessweave/client/controllers/LoadingScreenController.java deleted file mode 100644 index f2ad0c0..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/controllers/LoadingScreenController.java +++ /dev/null @@ -1,164 +0,0 @@ -package xyz.samiker.theendlessweave.client.controllers; - -import javafx.application.Platform; -import javafx.fxml.FXML; -import javafx.fxml.Initializable; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.ProgressBar; -import javafx.scene.control.TextArea; -import xyz.samiker.theendlessweave.Main; -import xyz.samiker.theendlessweave.client.initialization.InitializationManager; -import xyz.samiker.theendlessweave.client.initialization.LoadingCallback; -import xyz.samiker.theendlessweave.client.initialization.stages.AssetLoadStage; -import xyz.samiker.theendlessweave.client.initialization.stages.SettingsLoadStage; -import xyz.samiker.theendlessweave.core.utils.Array; - -import java.io.IOException; -import java.net.URL; -import java.util.ResourceBundle; -import java.util.concurrent.Future; - -public class LoadingScreenController implements Initializable { - - @FXML - private ProgressBar progressBar; - - @FXML - private Label statusLabel; - - @FXML - private TextArea logTextArea; - - @FXML - private Button cancelButton; - - private InitializationManager initManager; - private Future initFuture; - - @Override - public void initialize(URL location, ResourceBundle resources) { - initManager = new InitializationManager(); - - initManager.registerStage(new SettingsLoadStage()); - initManager.registerStage(new AssetLoadStage()); - // initManager.registerStage(new MapGenerationStage()); - - LoadingCallback callback = new LoadingCallback() { - @Override - public void updateMessage(String message) { - Platform.runLater(() -> statusLabel.setText(message)); - } - - @Override - public void updateProgress(long current, long total) { - Platform.runLater(() -> { - if (total > 0) { - progressBar.setProgress((double) current / total); - } - }); - } - - @Override - public void log(LogLevel level, String message) { - Platform.runLater(() -> { - String prefix = switch (level) { - case INFO -> "[INFO] "; - case WARN -> "[WARN] "; - case ERROR -> "[ERROR] "; - case DEBUG -> "[DEBUG] "; - }; - logTextArea.appendText(prefix + message + "\n"); - }); - } - }; - - initFuture = initManager.initialize(callback); - new Thread(() -> { - try { - InitializationManager.InitializationResult result = initFuture.get(); - - Platform.runLater(() -> { - if (result.isSuccess()) { - onInitializationSuccess(); - } else { - onInitializationFailure(result); - } - }); - } catch (Exception e) { - Platform.runLater(() -> { - logError("Initialization crashed: " + e.getMessage()); - e.printStackTrace(); - }); - } - }).start(); - if (cancelButton != null) { - cancelButton.setOnAction(e -> { - initManager.cancel(); - try { - Main.showScene( - "/xyz/samiker/theendlessweave/fxml/main-menu.fxml", - "/xyz/samiker/theendlessweave/css/main-menu.css" - ); - } catch (IOException ex) { - ex.printStackTrace(); - } - }); - } - } - - /** - * Вызывается при успешной инициализации. - */ - private void onInitializationSuccess() { - statusLabel.setText("Initialization complete!"); - progressBar.setProgress(1.0); - - Main.setAssetManager(initManager.getContext()); - - new Thread(() -> { - try { - Thread.sleep(500); - Platform.runLater(() -> { - try { - Main.showScene( - "/xyz/samiker/theendlessweave/fxml/main-menu.fxml", - "/xyz/samiker/theendlessweave/css/main-menu.css" - ); - } catch (IOException e) { - e.printStackTrace(); - } - }); - } catch (InterruptedException e) { - e.printStackTrace(); - } - }).start(); - } - - /** - * Вызывается при неудачной инициализации. - */ - private void onInitializationFailure(InitializationManager.InitializationResult result) { - statusLabel.setText("Initialization failed!"); - progressBar.setProgress(0.0); - - logError("=== INITIALIZATION FAILED ==="); - logError("Reason: " + result.getFailureReason()); - - Array errors = result.getErrors(); - for (int i = 0; i < errors.size; i++) { - InitializationManager.StageError error = errors.get(i); - logError("Stage: " + error.getStage().getStageName()); - logError("Error: " + error.getException().getMessage()); - } - - if (cancelButton != null) { - cancelButton.setText("Return to Menu"); - cancelButton.setVisible(true); - } - } - - private void logError(String message) { - logTextArea.appendText("[ERROR] " + message + "\n"); - } -} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/controllers/MainMenuController.java b/src/main/java/xyz/samiker/theendlessweave/client/controllers/MainMenuController.java deleted file mode 100644 index 0e2121f..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/controllers/MainMenuController.java +++ /dev/null @@ -1,32 +0,0 @@ -package xyz.samiker.theendlessweave.client.controllers; - -import javafx.event.ActionEvent; -import xyz.samiker.theendlessweave.Main; -import xyz.samiker.theendlessweave.core.GameData; -import xyz.samiker.theendlessweave.core.gamemap.MapGeneratorSettings; - -import java.io.IOException; - -public class MainMenuController { - public void play(ActionEvent event) throws IOException { - Main.showScene( - "/xyz/samiker/theendlessweave/fxml/generation-scene.fxml", - "/xyz/samiker/theendlessweave/css/generation-scene.css" - ); - } - - public void toSettings(ActionEvent event) throws IOException { - Main.showScene( - "/xyz/samiker/theendlessweave/fxml/settings.fxml", - "/xyz/samiker/theendlessweave/css/settings.css" - ); - } - - public void toDevStage(ActionEvent event) throws IOException { - Main.showGameScene( - new GameData(MapGeneratorSettings.defaults(), 50, 50, System.currentTimeMillis()), - "/xyz/samiker/theendlessweave/fxml/game-scene.fxml", - "/xyz/samiker/theendlessweave/css/game-scene.css" - ); - } -} diff --git a/src/main/java/xyz/samiker/theendlessweave/client/controllers/SettingsController.java b/src/main/java/xyz/samiker/theendlessweave/client/controllers/SettingsController.java deleted file mode 100644 index 5058ba4..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/controllers/SettingsController.java +++ /dev/null @@ -1,82 +0,0 @@ -package xyz.samiker.theendlessweave.client.controllers; - -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.fxml.Initializable; -import javafx.scene.Node; -import javafx.scene.control.Tab; -import javafx.scene.control.TabPane; -import xyz.samiker.theendlessweave.Main; -import xyz.samiker.theendlessweave.client.assets.ManifestAssetManager; -import xyz.samiker.theendlessweave.client.assets.ResourceManifest; - -import java.io.IOException; -import java.net.URL; -import java.util.ResourceBundle; - -public class SettingsController implements Initializable, AssetAware { - - @FXML - private TabPane settingsTabPane; - - @Override - public void initialize(URL location, ResourceBundle resources) { - } - - @FXML - private void onBack() throws IOException { - System.out.println("Back button clicked"); - Main.showScene("/xyz/samiker/theendlessweave/fxml/main-menu.fxml", "/xyz/samiker/theendlessweave/css/main-menu.css"); - } - - @Override - public void setAssets(ManifestAssetManager assets) { - var resources = assets.manifest().getAllResources(); - loadTab("Общие", "settings-general.fxml", resources); - loadTab("Видео", "settings-video.fxml", resources); - } - - private void loadTab(String title, String fxmlName, java.util.Collection resources) { - String resourcePath = null; - - for (ResourceManifest.ResourceEntry entry : resources) { - String name = extractName(entry); - if (name != null && name.equals(fxmlName)) { - resourcePath = extractPath(entry); - break; - } - } - - if (resourcePath != null) { - try { - URL fxmlUrl = getClass().getClassLoader().getResource(resourcePath); - if (fxmlUrl == null) { - System.err.println("Не удалось найти файл в classpath: " + resourcePath); - return; - } - - Node content = FXMLLoader.load(fxmlUrl); - Tab tab = new Tab(title, content); - settingsTabPane.getTabs().add(tab); - - } catch (IOException e) { - System.err.println("Ошибка загрузки вкладки " + title + ": " + e.getMessage()); - e.printStackTrace(); - } - } else { - System.err.println("Ресурс " + fxmlName + " не найден в манифесте."); - } - } - - private String extractName(ResourceManifest.ResourceEntry entry) { - try { - return entry.getName(); - } catch (Exception e) { return null; } - } - - private String extractPath(ResourceManifest.ResourceEntry entry) { - try { - return entry.getPath(); - } catch (Exception e) { return null; } - } -} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/controllers/settings/GeneralSettingsController.java b/src/main/java/xyz/samiker/theendlessweave/client/controllers/settings/GeneralSettingsController.java deleted file mode 100644 index 54b0122..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/controllers/settings/GeneralSettingsController.java +++ /dev/null @@ -1,41 +0,0 @@ -package xyz.samiker.theendlessweave.client.controllers.settings; - -import javafx.fxml.FXML; -import javafx.fxml.Initializable; -import javafx.scene.control.TextField; -import javafx.stage.DirectoryChooser; -import xyz.samiker.theendlessweave.Main; -import xyz.samiker.theendlessweave.core.settings.SettingsEnum; -import xyz.samiker.theendlessweave.core.settings.SettingsManager; - -import java.io.File; -import java.net.URL; -import java.util.ResourceBundle; - -public class GeneralSettingsController implements Initializable { - @FXML - private TextField modsDirField; - - @Override - public void initialize(URL location, ResourceBundle resources) { - modsDirField.setText(SettingsManager.getString(SettingsEnum.MODS_DIR)); - } - - @FXML - private void chooseModDir() { - DirectoryChooser directoryChooser = new DirectoryChooser(); - directoryChooser.setTitle("Выберите папку с модами"); - File selectedDirectory = directoryChooser.showDialog(Main.getPrimaryStage()); - - if (selectedDirectory != null) { - modsDirField.setText(selectedDirectory.getAbsolutePath()); - } - } - - @FXML - private void saveGeneral() { - SettingsManager.set(SettingsEnum.MODS_DIR, modsDirField.getText()); - SettingsManager.saveSettings(); - System.out.println("Общие настройки сохранены."); - } -} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/controllers/settings/VideoSettingsController.java b/src/main/java/xyz/samiker/theendlessweave/client/controllers/settings/VideoSettingsController.java deleted file mode 100644 index 9a59a27..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/controllers/settings/VideoSettingsController.java +++ /dev/null @@ -1,84 +0,0 @@ -package xyz.samiker.theendlessweave.client.controllers.settings; - -import javafx.collections.FXCollections; -import javafx.fxml.FXML; -import javafx.fxml.Initializable; -import javafx.scene.control.*; -import xyz.samiker.theendlessweave.Main; -import xyz.samiker.theendlessweave.core.settings.SettingsEnum; -import xyz.samiker.theendlessweave.core.settings.SettingsManager; - -import java.net.URL; -import java.util.ResourceBundle; - -public class VideoSettingsController implements Initializable { - - @FXML private ComboBox resolutionCombo; - @FXML private CheckBox fullscreenCheck; - @FXML private CheckBox borderlessCheck; - @FXML private CheckBox vsyncCheck; - @FXML private CheckBox fpsLimitEnabledCheck; - @FXML private TextField fpsField; - @FXML private Slider brightnessSlider; - @FXML private Label brightnessValueLabel; - - @Override - public void initialize(URL location, ResourceBundle resources) { - loadCurrentSettings(); - setupListeners(); - } - - private void loadCurrentSettings() { - resolutionCombo.setItems(FXCollections.observableArrayList( - "1920x1200", "1920x1080", "1600x900", "1366x768", "1280x720", "800x600" - )); - resolutionCombo.setValue(SettingsManager.getString(SettingsEnum.RESOLUTION)); - - fullscreenCheck.setSelected(SettingsManager.getBoolean(SettingsEnum.FULLSCREEN)); - borderlessCheck.setSelected(SettingsManager.getBoolean(SettingsEnum.BORDERLESS)); - vsyncCheck.setSelected(SettingsManager.getBoolean(SettingsEnum.VSYNC)); - - fpsLimitEnabledCheck.setSelected(SettingsManager.getBoolean(SettingsEnum.FPS_LIMIT_ENABLED)); - fpsField.setText(String.valueOf(SettingsManager.getInt(SettingsEnum.FPS_LIMIT))); - fpsField.setDisable(!SettingsManager.getBoolean(SettingsEnum.FPS_LIMIT_ENABLED)); - - brightnessSlider.setValue(SettingsManager.getDouble(SettingsEnum.BRIGHTNESS)); - brightnessValueLabel.setText(SettingsManager.getDouble(SettingsEnum.BRIGHTNESS) + "%"); - } - - private void setupListeners() { - fpsLimitEnabledCheck.selectedProperty().addListener((obs, oldVal, newVal) -> { - fpsField.setDisable(!newVal); - }); - - brightnessSlider.valueProperty().addListener((obs, oldVal, newVal) -> { - brightnessValueLabel.setText(newVal.intValue() + "%"); - }); - - fpsField.textProperty().addListener((observable, oldValue, newValue) -> { - if (!newValue.matches("\\d*")) { - fpsField.setText(newValue.replaceAll("[^\\d]", "")); - } - }); - } - - @FXML - private void applySettings() { - SettingsManager.set(SettingsEnum.RESOLUTION, resolutionCombo.getValue()); - SettingsManager.set(SettingsEnum.FULLSCREEN, fullscreenCheck.isSelected()); - SettingsManager.set(SettingsEnum.BORDERLESS, borderlessCheck.isSelected()); - SettingsManager.set(SettingsEnum.VSYNC, vsyncCheck.isSelected()); - SettingsManager.set(SettingsEnum.FPS_LIMIT_ENABLED, fpsLimitEnabledCheck.isSelected()); - - try { - int fps = Integer.parseInt(fpsField.getText()); - SettingsManager.set(SettingsEnum.FPS_LIMIT, fps); - } catch (NumberFormatException ignored) { - } - SettingsManager.set(SettingsEnum.BRIGHTNESS, brightnessSlider.getValue()); - SettingsManager.saveSettings(); - Main.getResolution().setResolution(resolutionCombo.getValue()); - - Main.applyStageSettings(); - } -} diff --git a/src/main/java/xyz/samiker/theendlessweave/client/custom/HPBar.java b/src/main/java/xyz/samiker/theendlessweave/client/custom/HPBar.java deleted file mode 100644 index 5610001..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/custom/HPBar.java +++ /dev/null @@ -1,75 +0,0 @@ -package xyz.samiker.theendlessweave.client.custom; - -import javafx.scene.layout.Region; -import javafx.scene.paint.Color; -import javafx.scene.shape.Rectangle; -import xyz.samiker.theendlessweave.core.entities.Entity; -import xyz.samiker.theendlessweave.core.entities.components.HealthComponent; - -public class HPBar extends Region { - private final Rectangle backgroundRect; - private final Rectangle healthRect; - - private static final double BAR_WIDTH = 50; - private static final double BAR_HEIGHT = 7; - - private static final Color COLOR_HIGH = Color.rgb(76, 175, 80); - private static final Color COLOR_MEDIUM = Color.rgb(255, 152, 0); - private static final Color COLOR_LOW = Color.rgb(244, 67, 54); - private static final Color COLOR_BG = Color.rgb(40, 20, 20); - - public HPBar(Entity entity) { - backgroundRect = new Rectangle(BAR_WIDTH, BAR_HEIGHT); - backgroundRect.setFill(COLOR_BG); - backgroundRect.setStroke(Color.BLACK); - backgroundRect.setArcWidth(4); - backgroundRect.setArcHeight(4); - - // Инициализация передней полоски - healthRect = new Rectangle(BAR_WIDTH, BAR_HEIGHT); - healthRect.setArcWidth(4); - healthRect.setArcHeight(4); - healthRect.setFill(COLOR_HIGH); - - getChildren().addAll(backgroundRect, healthRect); - - update(entity); - } - - public void update(Entity entity) { - HealthComponent healthCmp = entity.getComponent(HealthComponent.class); - - if (healthCmp == null) { - setVisible(false); - return; - } - - double currentHealth = healthCmp.health; - double maxHealth = healthCmp.maxHealth; - boolean shouldBeVisible = currentHealth < maxHealth && currentHealth > 0; - - if (isVisible() != shouldBeVisible) { - setVisible(shouldBeVisible); - } - - if (!shouldBeVisible) return; - - double percent = currentHealth / maxHealth; - if (percent < 0) percent = 0; - if (percent > 1) percent = 1; - - healthRect.setWidth(BAR_WIDTH * percent); - - if (percent > 0.5) { - if (healthRect.getFill() != COLOR_HIGH) healthRect.setFill(COLOR_HIGH); - } else if (percent > 0.25) { - if (healthRect.getFill() != COLOR_MEDIUM) healthRect.setFill(COLOR_MEDIUM); - } else { - if (healthRect.getFill() != COLOR_LOW) healthRect.setFill(COLOR_LOW); - } - } - - public double getBarWidth() { - return BAR_WIDTH; - } -} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/custom/InputManager.java b/src/main/java/xyz/samiker/theendlessweave/client/custom/InputManager.java index daa0dd2..c04389c 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/custom/InputManager.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/custom/InputManager.java @@ -1,170 +1,152 @@ package xyz.samiker.theendlessweave.client.custom; -import javafx.scene.Scene; -import javafx.scene.input.KeyCode; -import javafx.scene.input.MouseButton; -import javafx.scene.input.MouseEvent; -import javafx.scene.input.ScrollEvent; -import javafx.scene.input.TouchEvent; -import javafx.scene.input.TouchPoint; -import javafx.event.EventType; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.InputAdapter; +import com.badlogic.gdx.InputProcessor; import xyz.samiker.theendlessweave.core.utils.Array; +import xyz.samiker.theendlessweave.core.utils.IntObjectMap; import java.util.Arrays; import java.util.function.Consumer; @SuppressWarnings("unchecked") public final class InputManager { - private static final int KEYS_COUNT = KeyCode.values().length; - private static final int BUTTONS_COUNT = MouseButton.values().length; + private static final IntObjectMap> keyPressedActions = new IntObjectMap<>(64); + private static final IntObjectMap> keyReleasedActions = new IntObjectMap<>(64); - private static final Array[] keyPressedActions = new Array[KEYS_COUNT]; - private static final Array[] keyReleasedActions = new Array[KEYS_COUNT]; - private static final boolean[] pressedKeys = new boolean[KEYS_COUNT]; + private static final boolean[] pressedKeys = new boolean[512]; - private static final Array>[] mousePressedActions = new Array[BUTTONS_COUNT]; - private static final Array>[] mouseReleasedActions = new Array[BUTTONS_COUNT]; - private static final boolean[] pressedButtons = new boolean[BUTTONS_COUNT]; + private static final IntObjectMap>> mousePressedActions = new IntObjectMap<>(8); + private static final IntObjectMap>> mouseReleasedActions = new IntObjectMap<>(8); + private static final boolean[] pressedButtons = new boolean[16]; private static final Array> scrollActions = new Array<>(16); private static double sceneMouseX = 0; private static double sceneMouseY = 0; - private static double verticalInput = 0; - private static double horizontalInput = 0; + private static float verticalInput = 0; + private static float horizontalInput = 0; - private InputManager() {} + private static InputProcessor processor; - public static void initialize(Scene scene) { - scene.setOnKeyPressed(event -> { - KeyCode code = event.getCode(); - int ordinal = code.ordinal(); + private InputManager() {} - if (ordinal < KEYS_COUNT) { - if (!pressedKeys[ordinal]) { - pressedKeys[ordinal] = true; - updateDigitalInput(code, true); - runActions(keyPressedActions[ordinal]); + public static void initialize() { + if (processor != null) return; + + processor = new InputAdapter() { + @Override + public boolean keyDown(int keycode) { + if (keycode >= 0 && keycode < pressedKeys.length) { + if (!pressedKeys[keycode]) { + pressedKeys[keycode] = true; + updateDigitalInput(keycode, true); + runActions(keyPressedActions.get(keycode)); + } } + return false; } - }); - scene.setOnKeyReleased(event -> { - KeyCode code = event.getCode(); - int ordinal = code.ordinal(); - - if (ordinal < KEYS_COUNT) { - pressedKeys[ordinal] = false; - updateDigitalInput(code, false); - runActions(keyReleasedActions[ordinal]); + @Override + public boolean keyUp(int keycode) { + if (keycode >= 0 && keycode < pressedKeys.length) { + pressedKeys[keycode] = false; + updateDigitalInput(keycode, false); + runActions(keyReleasedActions.get(keycode)); + } + return false; } - }); - scene.setOnMousePressed(event -> { - if (event.isSynthesized()) return; + @Override + public boolean touchDown(int screenX, int screenY, int pointer, int button) { + updateMousePosition(screenX, screenY); - MouseButton button = event.getButton(); - int ordinal = button.ordinal(); - updateMousePosition(event.getSceneX(), event.getSceneY()); - - if (ordinal < BUTTONS_COUNT) { - pressedButtons[ordinal] = true; - runMouseActions(mousePressedActions[ordinal], event); + if (button >= 0 && button < pressedButtons.length) { + pressedButtons[button] = true; + MouseEvent event = new MouseEvent(screenX, screenY, button, MouseEvent.Type.PRESSED); + runMouseActions(mousePressedActions.get(button), event); + } + return false; } - }); - scene.setOnMouseReleased(event -> { - if (event.isSynthesized()) return; + @Override + public boolean touchUp(int screenX, int screenY, int pointer, int button) { + updateMousePosition(screenX, screenY); - MouseButton button = event.getButton(); - int ordinal = button.ordinal(); - updateMousePosition(event.getSceneX(), event.getSceneY()); + if (button >= 0 && button < pressedButtons.length) { + pressedButtons[button] = false; + MouseEvent event = new MouseEvent(screenX, screenY, button, MouseEvent.Type.RELEASED); + runMouseActions(mouseReleasedActions.get(button), event); + } + return false; + } - if (ordinal < BUTTONS_COUNT) { - pressedButtons[ordinal] = false; - runMouseActions(mouseReleasedActions[ordinal], event); + @Override + public boolean touchDragged(int screenX, int screenY, int pointer) { + updateMousePosition(screenX, screenY); + return false; } - }); - scene.setOnMouseMoved(e -> updateMousePosition(e.getSceneX(), e.getSceneY())); - scene.setOnMouseDragged(e -> updateMousePosition(e.getSceneX(), e.getSceneY())); + @Override + public boolean mouseMoved(int screenX, int screenY) { + updateMousePosition(screenX, screenY); + return false; + } - scene.setOnScroll((ScrollEvent event) -> { - updateMousePosition(event.getSceneX(), event.getSceneY()); - runScrollActions(event); - }); + @Override + public boolean scrolled(float amountX, float amountY) { + updateMousePosition(Gdx.input.getX(), Gdx.input.getY()); - scene.setOnTouchPressed(InputManager::handleTouch); - scene.setOnTouchMoved(InputManager::handleTouch); - scene.setOnTouchReleased(InputManager::handleTouch); + if (!scrollActions.isEmpty()) { + ScrollEvent event = new ScrollEvent(amountX, amountY, sceneMouseX, sceneMouseY); + runScrollActions(event); + } + return false; + } + }; } - private static void handleTouch(TouchEvent event) { - if (!event.getTouchPoints().isEmpty()) { - TouchPoint primary = event.getTouchPoints().getFirst(); - updateMousePosition(primary.getSceneX(), primary.getSceneY()); - } + public static InputProcessor getInputProcessor() { + if (processor == null) initialize(); + return processor; } public static void setMousePosition(double x, double y) { updateMousePosition(x, y); } - /** - * Симулирует нажатие клавиши клавиатуры - */ - public static void simulateKeyPress(KeyCode code) { - int ordinal = code.ordinal(); - if (ordinal < KEYS_COUNT && !pressedKeys[ordinal]) { - pressedKeys[ordinal] = true; - updateDigitalInput(code, true); - runActions(keyPressedActions[ordinal]); + public static void simulateKeyPress(int keycode) { + if (keycode >= 0 && keycode < pressedKeys.length && !pressedKeys[keycode]) { + pressedKeys[keycode] = true; + updateDigitalInput(keycode, true); + runActions(keyPressedActions.get(keycode)); } } - - /** - * Симулирует отпускание клавиши клавиатуры - */ - public static void simulateKeyRelease(KeyCode code) { - int ordinal = code.ordinal(); - if (ordinal < KEYS_COUNT && pressedKeys[ordinal]) { - pressedKeys[ordinal] = false; - updateDigitalInput(code, false); - runActions(keyReleasedActions[ordinal]); + public static void simulateKeyRelease(int keycode) { + if (keycode >= 0 && keycode < pressedKeys.length && pressedKeys[keycode]) { + pressedKeys[keycode] = false; + updateDigitalInput(keycode, false); + runActions(keyReleasedActions.get(keycode)); } } - - /** - * Симулирует нажатие кнопки мыши. - * Создает фейковое событие MouseEvent с текущими координатами. - */ - public static void simulateMousePress(MouseButton button) { - int ordinal = button.ordinal(); - if (ordinal < BUTTONS_COUNT && !pressedButtons[ordinal]) { - pressedButtons[ordinal] = true; - MouseEvent fakeEvent = createSyntheticMouseEvent(MouseEvent.MOUSE_PRESSED, button); - runMouseActions(mousePressedActions[ordinal], fakeEvent); + public static void simulateMousePress(int button) { + if (button >= 0 && button < pressedButtons.length && !pressedButtons[button]) { + pressedButtons[button] = true; + MouseEvent fakeEvent = new MouseEvent(sceneMouseX, sceneMouseY, button, MouseEvent.Type.PRESSED); + runMouseActions(mousePressedActions.get(button), fakeEvent); } } - - /** - * Симулирует отпускание кнопки мыши. - */ - public static void simulateMouseRelease(MouseButton button) { - int ordinal = button.ordinal(); - if (ordinal < BUTTONS_COUNT && pressedButtons[ordinal]) { - pressedButtons[ordinal] = false; - MouseEvent fakeEvent = createSyntheticMouseEvent(MouseEvent.MOUSE_RELEASED, button); - runMouseActions(mouseReleasedActions[ordinal], fakeEvent); + public static void simulateMouseRelease(int button) { + if (button >= 0 && button < pressedButtons.length && pressedButtons[button]) { + pressedButtons[button] = false; + MouseEvent fakeEvent = new MouseEvent(sceneMouseX, sceneMouseY, button, MouseEvent.Type.RELEASED); + runMouseActions(mouseReleasedActions.get(button), fakeEvent); } } - /** - * Прямая установка вектора ввода (для аналогового джойстика). - * x, y - значения от -1.0 до 1.0 - */ - public static void setInputVector(double x, double y) { + public static void setInputVector(float x, float y) { horizontalInput = x; verticalInput = y; } @@ -174,53 +156,39 @@ public static void updateMousePosition(double x, double y) { sceneMouseY = y; } - private static MouseEvent createSyntheticMouseEvent(EventType type, MouseButton button) { - return new MouseEvent( - type, - sceneMouseX, sceneMouseY, // x, y относительно сцены - sceneMouseX, sceneMouseY, // x, y относительно экрана - button, - 1, - false, false, false, false, // (shift, ctrl, alt, meta) - true, // primaryButtonDown (если левая) - false, // middle - false, // secondary - true, // synthesized - false, // popupTrigger - false, // stillSincePress - null // pickResult - ); - } - - private static void updateDigitalInput(KeyCode code, boolean isPressed) { - switch (code) { - case W: verticalInput = isPressed ? -1 : (isKeyPressed(KeyCode.S) ? 1 : 0); break; - case S: verticalInput = isPressed ? 1 : (isKeyPressed(KeyCode.W) ? -1 : 0); break; - case A: horizontalInput = isPressed ? -1 : (isKeyPressed(KeyCode.D) ? 1 : 0); break; - case D: horizontalInput = isPressed ? 1 : (isKeyPressed(KeyCode.A) ? -1 : 0); break; + private static void updateDigitalInput(int keycode, boolean isPressed) { + if (keycode == Input.Keys.W) { + verticalInput = isPressed ? 1 : (isKeyPressed(Input.Keys.S) ? -1 : 0); + } else if (keycode == Input.Keys.S) { + verticalInput = isPressed ? -1 : (isKeyPressed(Input.Keys.W) ? 1 : 0); + } + else if (keycode == Input.Keys.A) { + horizontalInput = isPressed ? -1 : (isKeyPressed(Input.Keys.D) ? 1 : 0); + } else if (keycode == Input.Keys.D) { + horizontalInput = isPressed ? 1 : (isKeyPressed(Input.Keys.A) ? -1 : 0); } } - public static void handleAxis(double value, double threshold, KeyCode positive, KeyCode negative) { + public static void handleAxis(double value, double threshold, int positiveKey, int negativeKey) { if (Math.abs(value) > threshold) { if (value > 0) { - simulateKeyPress(positive); - simulateKeyRelease(negative); + simulateKeyPress(positiveKey); + simulateKeyRelease(negativeKey); } else { - simulateKeyPress(negative); - simulateKeyRelease(positive); + simulateKeyPress(negativeKey); + simulateKeyRelease(positiveKey); } } else { - simulateKeyRelease(positive); - simulateKeyRelease(negative); + simulateKeyRelease(positiveKey); + simulateKeyRelease(negativeKey); } } public static double getMouseX() { return sceneMouseX; } public static double getMouseY() { return sceneMouseY; } - public static double getHorizontalInput() { return horizontalInput; } - public static double getVerticalInput() { return verticalInput; } + public static float getHorizontalInput() { return horizontalInput; } + public static float getVerticalInput() { return verticalInput; } private static void runScrollActions(ScrollEvent event) { if (scrollActions.size > 0) { @@ -249,44 +217,48 @@ private static void runMouseActions(Array> actions, MouseEv } } - public static boolean isKeyPressed(KeyCode code) { - return code != null && pressedKeys[code.ordinal()]; + public static boolean isKeyPressed(int keycode) { + return keycode >= 0 && keycode < pressedKeys.length && pressedKeys[keycode]; } - public static void registerKeyPressedAction(KeyCode code, Runnable action) { - int ordinal = code.ordinal(); - if (keyPressedActions[ordinal] == null) { - keyPressedActions[ordinal] = new Array<>(4); + public static void registerKeyPressedAction(int keycode, Runnable action) { + Array actions = keyPressedActions.get(keycode); + if (actions == null) { + actions = new Array<>(4); + keyPressedActions.put(keycode, actions); } - keyPressedActions[ordinal].add(action); + actions.add(action); } - public static void registerKeyReleasedAction(KeyCode code, Runnable action) { - int ordinal = code.ordinal(); - if (keyReleasedActions[ordinal] == null) { - keyReleasedActions[ordinal] = new Array<>(4); + public static void registerKeyReleasedAction(int keycode, Runnable action) { + Array actions = keyReleasedActions.get(keycode); + if (actions == null) { + actions = new Array<>(4); + keyReleasedActions.put(keycode, actions); } - keyReleasedActions[ordinal].add(action); + actions.add(action); } - public static boolean isMouseButtonPressed(MouseButton button) { - return button != null && pressedButtons[button.ordinal()]; + public static boolean isMouseButtonPressed(int button) { + return button >= 0 && button < pressedButtons.length && pressedButtons[button]; } - public static void registerMousePressedAction(MouseButton button, Consumer action) { - int ordinal = button.ordinal(); - if (mousePressedActions[ordinal] == null) { - mousePressedActions[ordinal] = new Array<>(4); + public static void registerMousePressedAction(int button, Consumer action) { + Array> actions = mousePressedActions.get(button); + if (actions == null) { + actions = new Array<>(4); + mousePressedActions.put(button, actions); } - mousePressedActions[ordinal].add(action); + actions.add(action); } - public static void registerMouseReleasedAction(MouseButton button, Consumer action) { - int ordinal = button.ordinal(); - if (mouseReleasedActions[ordinal] == null) { - mouseReleasedActions[ordinal] = new Array<>(4); + public static void registerMouseReleasedAction(int button, Consumer action) { + Array> actions = mouseReleasedActions.get(button); + if (actions == null) { + actions = new Array<>(4); + mouseReleasedActions.put(button, actions); } - mouseReleasedActions[ordinal].add(action); + actions.add(action); } public static void registerScrollAction(Consumer action) { @@ -294,29 +266,61 @@ public static void registerScrollAction(Consumer action) { } public static void clearAllActions() { - clearArray(keyPressedActions); - clearArray(keyReleasedActions); - clearArray(mousePressedActions); - clearArray(mouseReleasedActions); - + keyPressedActions.clear(); + keyReleasedActions.clear(); + mousePressedActions.clear(); + mouseReleasedActions.clear(); scrollActions.clear(); Arrays.fill(pressedKeys, false); Arrays.fill(pressedButtons, false); + horizontalInput = 0; verticalInput = 0; } public static void clearPressedStatusActions() { - clearArray(keyPressedActions); - clearArray(keyReleasedActions); + keyPressedActions.clear(); + keyReleasedActions.clear(); } - private static void clearArray(Array[] array) { - for (int i = 0; i < array.length; i++) { - if (array[i] != null) { - array[i].clear(); - } + public static class MouseEvent { + public enum Type { PRESSED, RELEASED, MOVED } + + private final double x; + private final double y; + private final int button; + private final Type type; + + public MouseEvent(double x, double y, int button, Type type) { + this.x = x; + this.y = y; + this.button = button; + this.type = type; } + + public double getX() { return x; } + public double getY() { return y; } + public int getButton() { return button; } + public Type getType() { return type; } + } + + public static class ScrollEvent { + private final float amountX; + private final float amountY; + private final double mouseX; + private final double mouseY; + + public ScrollEvent(float amountX, float amountY, double mouseX, double mouseY) { + this.amountX = amountX; + this.amountY = amountY; + this.mouseX = mouseX; + this.mouseY = mouseY; + } + + public float getAmountX() { return amountX; } + public float getAmountY() { return amountY; } + public double getMouseX() { return mouseX; } + public double getMouseY() { return mouseY; } } } \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/custom/NodePool.java b/src/main/java/xyz/samiker/theendlessweave/client/custom/NodePool.java deleted file mode 100644 index 70b4f36..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/custom/NodePool.java +++ /dev/null @@ -1,61 +0,0 @@ -package xyz.samiker.theendlessweave.client.custom; - -import javafx.scene.Node; -import javafx.scene.layout.Pane; - -import java.util.ArrayDeque; -import java.util.function.Supplier; - -public class NodePool { - private final ArrayDeque available = new ArrayDeque<>(); - private final Supplier factory; - private final Pane container; - private final int maxCapacity; - - public NodePool(Pane container, Supplier factory, int initialSize, int maxCapacity) { - this.container = container; - this.factory = factory; - this.maxCapacity = maxCapacity; - - for (int i = 0; i < initialSize; i++) { - createAndStore(); - } - } - - private void createAndStore() { - Node node = factory.get(); - node.setVisible(false); - container.getChildren().add(node); - available.push(node); - } - - public Node obtain() { - if (available.isEmpty()) { - Node node = factory.get(); - container.getChildren().add(node); - node.setVisible(true); - return node; - } - - Node node = available.pop(); - node.setVisible(true); // Просто включаем видимость - return node; - } - - public void free(Node node) { - if (available.size() < maxCapacity) { - node.setVisible(false); - node.setRotate(0); - node.setScaleX(1); - node.setScaleY(1); - available.push(node); - } else { - container.getChildren().remove(node); - } - } - - public void clear() { - container.getChildren().removeAll(available); - available.clear(); - } -} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/custom/VirtualJoystick.java b/src/main/java/xyz/samiker/theendlessweave/client/custom/VirtualJoystick.java deleted file mode 100644 index 1dcbfba..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/custom/VirtualJoystick.java +++ /dev/null @@ -1,86 +0,0 @@ -package xyz.samiker.theendlessweave.client.custom; - -import javafx.scene.layout.Pane; -import javafx.scene.paint.Color; -import javafx.scene.shape.Circle; - -public class VirtualJoystick extends Pane { - private final Circle base; - private final Circle knob; - private final double radius; - - private java.util.function.BiConsumer onDragCallback; - private Runnable onReleaseCallback; - - public VirtualJoystick(double radius, Color baseColor, Color knobColor) { - this.radius = radius; - - base = new Circle(radius, baseColor); - base.getStyleClass().add("joystick-base"); - - knob = new Circle(radius / 3, knobColor); - knob.getStyleClass().add("joystick-knob"); - - setPrefSize(radius * 2, radius * 2); - base.setCenterX(radius); - base.setCenterY(radius); - knob.setCenterX(radius); - knob.setCenterY(radius); - - getChildren().addAll(base, knob); - - initEvents(); - } - - private void initEvents() { - this.setOnMousePressed(e -> { - updateKnobPosition(e.getX(), e.getY()); - e.consume(); - }); - - this.setOnMouseDragged(e -> { - updateKnobPosition(e.getX(), e.getY()); - e.consume(); - }); - - this.setOnMouseReleased(e -> { - knob.setCenterX(radius); - knob.setCenterY(radius); - if (onDragCallback != null) { - onDragCallback.accept(0.0, 0.0); - } - if (onReleaseCallback != null) { - onReleaseCallback.run(); - } - e.consume(); - }); - } - - private void updateKnobPosition(double touchX, double touchY) { - double dx = touchX - radius; - double dy = touchY - radius; - - double distance = Math.sqrt(dx * dx + dy * dy); - - if (distance > radius) { - double ratio = radius / distance; - dx *= ratio; - dy *= ratio; - } - - knob.setCenterX(radius + dx); - knob.setCenterY(radius + dy); - - if (onDragCallback != null) { - onDragCallback.accept(dx / radius, dy / radius); - } - } - - public void setOnDrag(java.util.function.BiConsumer callback) { - this.onDragCallback = callback; - } - - public void setOnRelease(Runnable callback) { - this.onReleaseCallback = callback; - } -} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/initialization/InitializationManager.java b/src/main/java/xyz/samiker/theendlessweave/client/initialization/InitializationManager.java deleted file mode 100644 index cbc7aeb..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/initialization/InitializationManager.java +++ /dev/null @@ -1,162 +0,0 @@ -package xyz.samiker.theendlessweave.client.initialization; - -import xyz.samiker.theendlessweave.core.utils.Array; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -public class InitializationManager { - - private final Array stages = new Array<>(5); - private final LoadingContext context = new LoadingContext(); - private ExecutorService executor; - - /** - * Регистрация этапа загрузки. - * Этапы будут выполняться в порядке регистрации. - */ - public void registerStage(LoadingStage stage) { - stages.add(stage); - } - - /** - * Запустить инициализацию всех зарегистрированных этапов. - * @param callback Коллбэк для обновления UI - * @return Future, который можно использовать для ожидания завершения или отмены - */ - public Future initialize(LoadingCallback callback) { - executor = Executors.newSingleThreadExecutor(); - - return executor.submit(() -> { - Array errors = new Array<>(4); - - double totalWeight = 0.0; - for (int i = 0; i < stages.size; i++) { - totalWeight += stages.get(i).getWeight(); - } - - double currentProgress = 0.0; - - callback.log(LoadingCallback.LogLevel.INFO, - "=== Initialization started ==="); - callback.log(LoadingCallback.LogLevel.INFO, - "Total stages: " + stages.size); - - for (int i = 0; i < stages.size; i++) { - if (context.isCancelled()) { - callback.log(LoadingCallback.LogLevel.WARN, - "Initialization cancelled by user"); - return new InitializationResult(false, errors, "Cancelled by user"); - } - - LoadingStage stage = stages.get(i); - callback.log(LoadingCallback.LogLevel.INFO, - String.format("[%d/%d] Starting: %s", i + 1, stages.size, stage.getStageName())); - - try { - stage.execute(context, callback); - callback.log(LoadingCallback.LogLevel.INFO, - "✓ Completed: " + stage.getStageName()); - } catch (Exception e) { - StageError error = new StageError(stage, e); - errors.add(error); - - callback.log(LoadingCallback.LogLevel.ERROR, - "✗ Failed: " + stage.getStageName() + " - " + e.getMessage()); - - if (stage.isCritical()) { - callback.log(LoadingCallback.LogLevel.ERROR, - "Critical stage failed. Initialization aborted."); - return new InitializationResult(false, errors, - "Critical stage failed: " + stage.getStageName()); - } else { - callback.log(LoadingCallback.LogLevel.WARN, - "Non-critical stage failed, continuing..."); - } - } - - currentProgress += stage.getWeight(); - callback.updateProgress((long)(currentProgress * 1000), (long)(totalWeight * 1000)); - } - - callback.log(LoadingCallback.LogLevel.INFO, - "=== Initialization complete ==="); - - boolean success = true; - for (int i = 0; i < errors.size; i++) { - if (errors.get(i).getStage().isCritical()) { - success = false; - break; - } - } - - return new InitializationResult(success, errors, null); - }); - } - - /** - * Отменить инициализацию. - */ - public void cancel() { - context.cancel(); - if (executor != null && !executor.isShutdown()) { - executor.shutdownNow(); - } - } - - /** - * Получить контекст загрузки (для доступа к загруженным данным после инициализации). - */ - public LoadingContext getContext() { - return context; - } - - /** - * Результат инициализации. - */ - public static class InitializationResult { - private final boolean success; - private final Array errors; - private final String failureReason; - - public InitializationResult(boolean success, Array errors, String failureReason) { - this.success = success; - this.errors = errors; - this.failureReason = failureReason; - } - - public boolean isSuccess() { - return success; - } - - public Array getErrors() { - return errors; - } - - public String getFailureReason() { - return failureReason; - } - } - - /** - * Информация об ошибке этапа. - */ - public static class StageError { - private final LoadingStage stage; - private final Exception exception; - - public StageError(LoadingStage stage, Exception exception) { - this.stage = stage; - this.exception = exception; - } - - public LoadingStage getStage() { - return stage; - } - - public Exception getException() { - return exception; - } - } -} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/initialization/LoadingCallback.java b/src/main/java/xyz/samiker/theendlessweave/client/initialization/LoadingCallback.java deleted file mode 100644 index 0783ad5..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/initialization/LoadingCallback.java +++ /dev/null @@ -1,37 +0,0 @@ -package xyz.samiker.theendlessweave.client.initialization; - -/** - * Интерфейс для передачи обновлений прогресса загрузки в UI. - */ -public interface LoadingCallback { - - /** - * Обновить сообщение о текущем действии. - * @param message Текст сообщения (например, "Loading level_1.json...") - */ - void updateMessage(String message); - - /** - * Обновить прогресс текущего этапа. - * @param current Текущее значение (например, загружено файлов) - * @param total Общее количество (например, всего файлов) - */ - void updateProgress(long current, long total); - - /** - * Логировать сообщение в консоль загрузки. - * @param level Уровень логирования (INFO, WARN, ERROR) - * @param message Текст сообщения - */ - void log(LogLevel level, String message); - - /** - * Уровни логирования для визуального разделения в UI. - */ - enum LogLevel { - INFO, // Обычная информация (зелёный) - WARN, // Предупреждения (жёлтый) - ERROR, // Ошибки (красный) - DEBUG // Отладочная информация (серый) - } -} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/initialization/LoadingContext.java b/src/main/java/xyz/samiker/theendlessweave/client/initialization/LoadingContext.java deleted file mode 100644 index ae59fa4..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/initialization/LoadingContext.java +++ /dev/null @@ -1,53 +0,0 @@ -package xyz.samiker.theendlessweave.client.initialization; - -import java.util.HashMap; -import java.util.Map; - -/** - * Контекст загрузки, доступный всем этапам. - * Позволяет этапам обмениваться данными и кэшировать результаты. - */ -public class LoadingContext { - - private final Map sharedData = new HashMap<>(); - private volatile boolean cancelled = false; - - /** - * Сохранить данные для использования другими этапами. - * Например, AssetLoadStage может сохранить загруженные текстуры, - * которые потом использует MapGenerationStage. - */ - public void put(String key, Object value) { - sharedData.put(key, value); - } - - /** - * Получить данные, сохранённые другим этапом. - */ - @SuppressWarnings("unchecked") - public T get(String key) { - return (T) sharedData.get(key); - } - - /** - * Проверить наличие данных. - */ - public boolean contains(String key) { - return sharedData.containsKey(key); - } - - /** - * Отменить загрузку. - */ - public void cancel() { - this.cancelled = true; - } - - /** - * Проверить, была ли отменена загрузка. - * Этапы должны периодически проверять это и прерывать работу. - */ - public boolean isCancelled() { - return cancelled; - } -} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/initialization/LoadingStage.java b/src/main/java/xyz/samiker/theendlessweave/client/initialization/LoadingStage.java deleted file mode 100644 index 8fa41be..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/initialization/LoadingStage.java +++ /dev/null @@ -1,39 +0,0 @@ -package xyz.samiker.theendlessweave.client.initialization; - -/** - * Интерфейс этапа загрузки/инициализации. - * Каждый модуль игры должен реализовать этот интерфейс для своей инициализации. - */ -public interface LoadingStage { - - /** - * Имя этапа для отображения в UI. - * @return Читаемое название этапа (например, "Loading textures...") - */ - String getStageName(); - - /** - * Выполнить инициализацию. - * @param context Общий контекст загрузки с доступом к настройкам и общим ресурсам - * @param callback Коллбэк для отправки обновлений прогресса в UI - * @throws Exception Если инициализация не удалась - */ - void execute(LoadingContext context, LoadingCallback callback) throws Exception; - - /** - * Можно ли пропустить этот этап при ошибке. - * @return true если этап критичный (игра не запустится без него) - */ - default boolean isCritical() { - return true; - } - - /** - * Примерный вес этапа для расчета общего прогресса. - * Сумма весов всех этапов = 1.0 - * @return Вес от 0.0 до 1.0 - */ - default double getWeight() { - return 1.0; - } -} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/initialization/stages/AssetLoadStage.java b/src/main/java/xyz/samiker/theendlessweave/client/initialization/stages/AssetLoadStage.java deleted file mode 100644 index 2ccd6a7..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/initialization/stages/AssetLoadStage.java +++ /dev/null @@ -1,113 +0,0 @@ -package xyz.samiker.theendlessweave.client.initialization.stages; - -import xyz.samiker.theendlessweave.client.assets.AssetLoadCallback; -import xyz.samiker.theendlessweave.client.assets.ManifestAssetManager; -import xyz.samiker.theendlessweave.client.assets.ResourceManifest; -import xyz.samiker.theendlessweave.client.initialization.LoadingCallback; -import xyz.samiker.theendlessweave.client.initialization.LoadingContext; -import xyz.samiker.theendlessweave.client.initialization.LoadingStage; - -/** - * Этап загрузки игровых ассетов из манифеста. - */ -public class AssetLoadStage implements LoadingStage { - @Override - public String getStageName() { - return "Loading assets"; - } - - @Override - public void execute(LoadingContext context, LoadingCallback callback) throws Exception { - callback.updateMessage("Reading resource manifest..."); - callback.log(LoadingCallback.LogLevel.INFO, "Loading resource manifest..."); - - ResourceManifest manifest = ResourceManifest.load(); - - ResourceManifest.ManifestStats stats = manifest.getStats(); - callback.log(LoadingCallback.LogLevel.INFO, - "Manifest loaded: " + stats.getTotalResources() + " resources"); - - for (var entry : stats.getByExtension().entrySet()) { - callback.log(LoadingCallback.LogLevel.DEBUG, - " " + entry.getKey() + ": " + entry.getValue() + " files"); - } - - ManifestAssetManager assetManager = new ManifestAssetManager(manifest); - - AssetLoadCallback assetCallback = createAssetCallback(callback); - - callback.updateMessage("Loading game assets..."); - assetManager.loadAll(assetCallback); - - context.put("assetManager", assetManager); - context.put("manifest", manifest); - - callback.log(LoadingCallback.LogLevel.INFO, - "All assets loaded successfully"); - } - - /** - * Создаёт адаптер между AssetLoadCallback и LoadingCallback. - */ - private AssetLoadCallback createAssetCallback(LoadingCallback loadingCallback) { - return new AssetLoadCallback() { - private int totalAssets = 0; - private int loadedAssets = 0; - private int failedAssets = 0; - - @Override - public void onCategoryStart(String category) { - loadingCallback.updateMessage("Loading " + category.toLowerCase() + "..."); - loadingCallback.log(LoadingCallback.LogLevel.INFO, - "=== " + category + " ==="); - } - - @Override - public void onCategoryComplete(String category) { - loadingCallback.log(LoadingCallback.LogLevel.INFO, - "✓ " + category + " completed"); - } - - @Override - public void onAssetLoadStart(String id, String path) { - totalAssets++; - loadingCallback.log(LoadingCallback.LogLevel.DEBUG, - "Loading: " + id); - } - - @Override - public void onAssetLoadComplete(String id) { - loadedAssets++; - loadingCallback.updateProgress(loadedAssets, totalAssets); - } - - @Override - public void onAssetLoadError(String id, Exception e) { - failedAssets++; - loadingCallback.log(LoadingCallback.LogLevel.ERROR, - "✗ Failed: " + id + " - " + e.getMessage()); - } - - @Override - public void log(LogLevel level, String message) { - LoadingCallback.LogLevel mappedLevel = switch (level) { - case INFO -> LoadingCallback.LogLevel.INFO; - case WARN -> LoadingCallback.LogLevel.WARN; - case ERROR -> LoadingCallback.LogLevel.ERROR; - case DEBUG -> LoadingCallback.LogLevel.DEBUG; - }; - loadingCallback.log(mappedLevel, message); - } - }; - } - - @Override - public boolean isCritical() { - return true; // Без ассетов игра не запустится - } - - @Override - public double getWeight() { - return 0.40; // 40% от общей загрузки - } -} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/initialization/stages/SettingsLoadStage.java b/src/main/java/xyz/samiker/theendlessweave/client/initialization/stages/SettingsLoadStage.java deleted file mode 100644 index 6bb81f8..0000000 --- a/src/main/java/xyz/samiker/theendlessweave/client/initialization/stages/SettingsLoadStage.java +++ /dev/null @@ -1,46 +0,0 @@ -package xyz.samiker.theendlessweave.client.initialization.stages; - -import xyz.samiker.theendlessweave.client.initialization.LoadingCallback; -import xyz.samiker.theendlessweave.client.initialization.LoadingContext; -import xyz.samiker.theendlessweave.client.initialization.LoadingStage; -import xyz.samiker.theendlessweave.core.settings.SettingsEnum; -import xyz.samiker.theendlessweave.core.settings.SettingsManager; - -/** - * Этап загрузки настроек игры. - */ -public class SettingsLoadStage implements LoadingStage { - - @Override - public String getStageName() { - return "Loading settings"; - } - - @Override - public void execute(LoadingContext context, LoadingCallback callback) { - callback.updateMessage("Loading game settings..."); - callback.log(LoadingCallback.LogLevel.DEBUG, "Searching for settings.properties..."); - - callback.log(LoadingCallback.LogLevel.INFO, - "Resolution: " + SettingsManager.getString(SettingsEnum.RESOLUTION)); - callback.log(LoadingCallback.LogLevel.INFO, - "Fullscreen: " + SettingsManager.getBoolean(SettingsEnum.FULLSCREEN)); - callback.log(LoadingCallback.LogLevel.INFO, - "VSync: " + SettingsManager.getBoolean(SettingsEnum.VSYNC)); - - // Сохраняем в контекст для доступа другими этапами - context.put("settings_loaded", true); - - callback.updateProgress(1, 1); - } - - @Override - public boolean isCritical() { - return false; - } - - @Override - public double getWeight() { - return 0.05; - } -} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/network/GameClient.java b/src/main/java/xyz/samiker/theendlessweave/client/network/GameClient.java index c32a32c..c9b5bf7 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/network/GameClient.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/network/GameClient.java @@ -1,9 +1,9 @@ package xyz.samiker.theendlessweave.client.network; +import com.badlogic.gdx.Gdx; import com.esotericsoftware.kryonet.Client; import com.esotericsoftware.kryonet.Connection; import com.esotericsoftware.kryonet.Listener; -import javafx.application.Platform; import xyz.samiker.theendlessweave.core.network.NetworkRegister; import xyz.samiker.theendlessweave.core.network.packets.PacketLoginRequest; import xyz.samiker.theendlessweave.core.network.packets.PacketLoginResponse; @@ -40,7 +40,7 @@ public void received(Connection connection, Object object) { if (response.accepted && callbackToRun != null) { onJoinCallback = null; - Platform.runLater(() -> { + Gdx.app.postRunnable(()-> { callbackToRun.accept(response); }); } @@ -60,6 +60,10 @@ public void received(Connection connection, Object object) { client.sendTCP(login); } + public void disconnect() { + client.close(); + } + public void sendUDP(Object o) { client.sendUDP(o); } public void sendTCP(Object o) { client.sendTCP(o); } } \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/screens/AssetAware.java b/src/main/java/xyz/samiker/theendlessweave/client/screens/AssetAware.java new file mode 100644 index 0000000..7870e30 --- /dev/null +++ b/src/main/java/xyz/samiker/theendlessweave/client/screens/AssetAware.java @@ -0,0 +1,13 @@ +package xyz.samiker.theendlessweave.client.screens; + +import xyz.samiker.theendlessweave.client.assets.ManifestAssetManager; + +import java.net.URL; +import java.util.ResourceBundle; + +public interface AssetAware { + void setAssets(ManifestAssetManager assets); + + void initialize(URL url, ResourceBundle resourceBundle); +} + diff --git a/src/main/java/xyz/samiker/theendlessweave/client/screens/GameDataAware.java b/src/main/java/xyz/samiker/theendlessweave/client/screens/GameDataAware.java new file mode 100644 index 0000000..ff51991 --- /dev/null +++ b/src/main/java/xyz/samiker/theendlessweave/client/screens/GameDataAware.java @@ -0,0 +1,7 @@ +package xyz.samiker.theendlessweave.client.screens; + +import xyz.samiker.theendlessweave.core.GameData; + +public interface GameDataAware { + void setData(GameData data); +} diff --git a/src/main/java/xyz/samiker/theendlessweave/client/screens/GameScreen.java b/src/main/java/xyz/samiker/theendlessweave/client/screens/GameScreen.java new file mode 100644 index 0000000..8bc2610 --- /dev/null +++ b/src/main/java/xyz/samiker/theendlessweave/client/screens/GameScreen.java @@ -0,0 +1,359 @@ +package xyz.samiker.theendlessweave.client.screens; + +import com.badlogic.gdx.*; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.scenes.scene2d.Actor; +import com.badlogic.gdx.scenes.scene2d.InputEvent; +import com.badlogic.gdx.scenes.scene2d.Stage; +import com.badlogic.gdx.scenes.scene2d.ui.*; +import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; +import com.badlogic.gdx.utils.Align; +import com.badlogic.gdx.utils.ScreenUtils; +import com.badlogic.gdx.utils.viewport.ExtendViewport; +import com.badlogic.gdx.utils.viewport.ScreenViewport; +import com.badlogic.gdx.utils.viewport.Viewport; +import xyz.samiker.theendlessweave.Main; +import xyz.samiker.theendlessweave.client.assets.ManifestAssetManager; +import xyz.samiker.theendlessweave.client.custom.InputManager; +import xyz.samiker.theendlessweave.client.network.GameClient; +import xyz.samiker.theendlessweave.client.systems.*; +import xyz.samiker.theendlessweave.core.GameData; +import xyz.samiker.theendlessweave.core.entities.Entity; +import xyz.samiker.theendlessweave.core.entities.components.PositionComponent; +import xyz.samiker.theendlessweave.core.logic.Game; +import xyz.samiker.theendlessweave.core.logic.GameState; +import xyz.samiker.theendlessweave.core.logic.Loop; +import xyz.samiker.theendlessweave.core.network.packets.PacketLoginResponse; +import xyz.samiker.theendlessweave.core.settings.SettingsEnum; +import xyz.samiker.theendlessweave.core.settings.SettingsManager; + +import java.io.IOException; + +public class GameScreen implements Screen { + + private final Main mainApp; + + private SpriteBatch batch; + private OrthographicCamera gameCamera; + private Viewport gameViewport; + + private Stage uiStage; + private Skin skin; + private Table pauseMenuTable; + private Label debugLabel; + private Label loadingLabel; + + private ProgressBar healthBar; + private Label scoreLabel; + private Label levelLabel; + + private Touchpad moveStick; + private Touchpad aimStick; + private boolean isMobile; + + private Game gameLogic; + private Loop gameLoop; + private GameClient client; + private ManifestAssetManager assets; + private boolean isGameRunning = false; + + private InputMultiplexer inputMultiplexer; + + public GameScreen(Main mainApp) { + this.mainApp = mainApp; + this.assets = mainApp.getAssetManager(); + } + + @Override + public void show() { + batch = new SpriteBatch(); + + float worldWidth = 1920; + float worldHeight = 1080; + gameCamera = new OrthographicCamera(); + gameViewport = new ExtendViewport(worldWidth, worldHeight, gameCamera); + + uiStage = new Stage(new ScreenViewport()); + + skin = new Skin(Gdx.files.internal("xyz/samiker/theendlessweave/assets/ui/uiskin.json")); + + setupUI(); + + inputMultiplexer = new InputMultiplexer(); + inputMultiplexer.addProcessor(uiStage); + inputMultiplexer.addProcessor(InputManager.getInputProcessor()); + inputMultiplexer.addProcessor(new GameInputProcessor()); + Gdx.input.setInputProcessor(inputMultiplexer); + + connectToServer(); + } + + private void setupUI() { + loadingLabel = new Label("Connecting...", skin); + loadingLabel.setPosition(Gdx.graphics.getWidth() / 2f, Gdx.graphics.getHeight() / 2f, Align.center); + uiStage.addActor(loadingLabel); + + Table hudTable = new Table(); + hudTable.setFillParent(true); + hudTable.top().left(); + + healthBar = new ProgressBar(0, 100, 1, false, skin); + scoreLabel = new Label("Score: 0", skin); + levelLabel = new Label("Lvl: 1", skin); + + hudTable.add(healthBar).width(200).pad(10); + hudTable.row(); + hudTable.add(scoreLabel).pad(10).left(); + hudTable.row(); + hudTable.add(levelLabel).pad(10).left(); + + uiStage.addActor(hudTable); + + isMobile = SettingsManager.getString(SettingsEnum.CONTROL).equals("touch");// || Main.isMobile(); + if (isMobile) { + setupMobileControls(); + } + + setupPauseMenu(); + + debugLabel = new Label("", skin); + debugLabel.setPosition(10, Gdx.graphics.getHeight() - 30); + debugLabel.setAlignment(Align.left); + uiStage.addActor(debugLabel); + } + + private void setupMobileControls() { + Touchpad.TouchpadStyle touchpadStyle = skin.get(Touchpad.TouchpadStyle.class); + + moveStick = new Touchpad(10, touchpadStyle); + moveStick.setBounds(50, 50, 200, 200); + + aimStick = new Touchpad(10, touchpadStyle); + aimStick.setBounds(Gdx.graphics.getWidth() - 250, 50, 200, 200); + TextButton attackBtn = new TextButton("ATK", skin); + attackBtn.setSize(100, 100); + attackBtn.setPosition(Gdx.graphics.getWidth() - 150, 300); + attackBtn.addListener(new ClickListener() { + @Override + public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { + InputManager.simulateMousePress(0); + return true; + } + @Override + public void touchUp(InputEvent event, float x, float y, int pointer, int button) { + InputManager.simulateMouseRelease(0); + } + }); + + uiStage.addActor(moveStick); + uiStage.addActor(aimStick); + uiStage.addActor(attackBtn); + } + + private void setupPauseMenu() { + pauseMenuTable = new Table(); + pauseMenuTable.setFillParent(true); + pauseMenuTable.setVisible(false); + pauseMenuTable.setBackground(skin.newDrawable("white", 0, 0, 0, 0.7f)); + + TextButton resumeBtn = new TextButton("Resume", skin); + TextButton exitBtn = new TextButton("Exit to Menu", skin); + + resumeBtn.addListener(new ChangeListener() { + @Override + public void changed(ChangeEvent event, Actor actor) { + togglePause(); + } + }); + + exitBtn.addListener(new ChangeListener() { + @Override + public void changed(ChangeEvent event, Actor actor) { + try { + if (client != null) client.disconnect(); + mainApp.setScreen(new MainMenuScreen(mainApp)); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + + pauseMenuTable.add(new Label("PAUSED", skin)).pad(20).row(); + pauseMenuTable.add(resumeBtn).width(200).pad(10).row(); + pauseMenuTable.add(exitBtn).width(200).pad(10).row(); + + uiStage.addActor(pauseMenuTable); + } + + private void connectToServer() { + client = new GameClient(); + new Thread(() -> { + try { + client.connect("127.0.0.1", (response) -> { + Gdx.app.postRunnable(() -> initializeGameSession(response)); + }); + } catch (IOException e) { + Gdx.app.postRunnable(() -> { + loadingLabel.setText("Connection failed: " + e.getMessage()); + }); + e.printStackTrace(); + } + }).start(); + } + + private void initializeGameSession(PacketLoginResponse serverData) { + loadingLabel.setVisible(false); + + GameData finalData = new GameData( + serverData.gameData.settings, + serverData.gameData.width, + serverData.gameData.height, + serverData.gameData.seed + ); + + gameLogic = new Game(finalData); + Entity player = gameLogic.createPlayer(serverData.playerEntityId); + player.getComponent(PositionComponent.class).set(serverData.spawnX, serverData.spawnY); + + var map = gameLogic.getGameMap(); + var profiler = gameLogic.getProfiler(); + InputSystem inputSystem = new InputSystem(client, gameLogic, gameCamera); + inputSystem.setInputMode(isMobile); + + gameLogic.addLogicSystem(new ClientNetworkEventSystem(client, gameLogic)); + gameLogic.addLogicSystem(inputSystem); + gameLogic.addLogicSystem(new ClientProjectileSystem()); + gameLogic.addLogicSystem(new ClientNetworkReceiverSystem(client, gameLogic)); + + gameLogic.addRenderSystem(new TileRenderSystem(batch, map, gameCamera, assets)); + gameLogic.addRenderSystem(new RenderSystem(batch, gameCamera)); + gameLogic.addRenderSystem(new WorldUIRenderer(batch, gameCamera)); + gameLogic.addRenderSystem(new HudRenderSystem(healthBar, scoreLabel, levelLabel)); + gameLogic.addRenderSystem(new DebugRenderSystem(debugLabel, profiler)); + gameLogic.addRenderSystem(new CameraSystem(gameCamera)); + + gameLoop = new Loop(gameLogic); + gameLoop.start(); + + isGameRunning = true; + } + + private void togglePause() { + if (gameLoop == null) return; + gameLoop.togglePause(); + boolean isPaused = gameLoop.getGameState() != GameState.PLAYING; + pauseMenuTable.setVisible(isPaused); + } + + @Override + public void render(float delta) { + ScreenUtils.clear(0, 0, 0, 1); + + if (!isGameRunning) { + uiStage.act(delta); + uiStage.draw(); + return; + } + + gameCamera.update(); + batch.setProjectionMatrix(gameCamera.combined); + + if (isMobile && moveStick != null && aimStick != null) { + double deadzone = 0.3; + InputManager.handleAxis(moveStick.getKnobPercentX(), deadzone, Input.Keys.D, Input.Keys.A); + InputManager.handleAxis(moveStick.getKnobPercentY(), deadzone, Input.Keys.W, Input.Keys.S); + + float aimX = aimStick.getKnobPercentX(); + float aimY = aimStick.getKnobPercentY(); + + if (Math.abs(aimX) > deadzone || Math.abs(aimY) > deadzone) { + InputManager.setInputVector(aimX, aimY); + InputManager.simulateMousePress(Input.Buttons.LEFT); + } else { + InputManager.simulateMouseRelease(Input.Buttons.LEFT); + } + } + + gameLogic.render(delta); + uiStage.act(delta); + uiStage.draw(); + } + + @Override + public void resize(int width, int height) { + gameViewport.update(width, height, false); + uiStage.getViewport().update(width, height, true); + if (isMobile) { + setupMobileControls(); + } + } + + @Override + public void pause() { + if (gameLoop != null && gameLoop.getGameState() == GameState.PLAYING) { + togglePause(); + } + } + + @Override + public void resume() { + } + + @Override + public void hide() { + if (gameLoop != null) { + gameLoop.stop(); + } + } + + @Override + public void dispose() { + if (batch != null) batch.dispose(); + if (uiStage != null) uiStage.dispose(); + if (client != null) { + client.disconnect(); + } + InputManager.clearAllActions(); + } + + private class GameInputProcessor implements InputProcessor { + @Override + public boolean keyDown(int keycode) { + if (keycode == Input.Keys.ESCAPE) { + togglePause(); + return true; + } + InputManager.simulateMousePress(keycode); + return false; + } + + @Override + public boolean keyUp(int keycode) { + InputManager.simulateMouseRelease(keycode); + return false; + } + + @Override public boolean keyTyped(char character) { return false; } + + @Override + public boolean touchDown(int screenX, int screenY, int pointer, int button) { + // Vector3 worldPos = gameCamera.unproject(new Vector3(screenX, screenY, 0)); + // InputManager.setMousePosition(worldPos.x, worldPos.y); + return false; + } + + @Override public boolean touchUp(int screenX, int screenY, int pointer, int button) { return false; } + @Override public boolean touchDragged(int screenX, int screenY, int pointer) { return false; } + + @Override + public boolean mouseMoved(int screenX, int screenY) { + // Vector3 worldPos = gameCamera.unproject(new Vector3(screenX, screenY, 0)); + // InputManager.setMousePosition(worldPos.x, worldPos.y); + return false; + } + @Override public boolean scrolled(float amountX, float amountY) { return false; } + @Override public boolean touchCancelled(int screenX, int screenY, int pointer, int button) { return false; } + } +} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/screens/GenerationScreen.java b/src/main/java/xyz/samiker/theendlessweave/client/screens/GenerationScreen.java new file mode 100644 index 0000000..7e27493 --- /dev/null +++ b/src/main/java/xyz/samiker/theendlessweave/client/screens/GenerationScreen.java @@ -0,0 +1,379 @@ +package xyz.samiker.theendlessweave.client.screens; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Screen; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.scenes.scene2d.Actor; +import com.badlogic.gdx.scenes.scene2d.InputEvent; +import com.badlogic.gdx.scenes.scene2d.Stage; +import com.badlogic.gdx.scenes.scene2d.ui.*; +import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; +import com.badlogic.gdx.utils.ScreenUtils; +import com.badlogic.gdx.utils.viewport.ScreenViewport; +import xyz.samiker.theendlessweave.Main; +import xyz.samiker.theendlessweave.core.gamemap.MapGeneratorSettings; + +import java.util.Random; + +public class GenerationScreen implements Screen { + + private final Main game; + private Stage stage; + private Skin skin; + + // UI Elements + private CheckBox inverseGeneration; + private TextField wallsNoiseField; // Spinner emulation + private SelectBox presetComboBox; + + // Toggles + private ButtonGroup difficultyGroup; + private TextButton diffEasy, diffNormal, diffHard; + + private ButtonGroup gameModeGroup; + private TextButton modeSandbox, modeProgression; + + private Slider mapSizeSlider; + private Label mapSizeLabel; + + // Detailed settings + private TextField widthField; + private TextField heightField; + private TextField minRoomSizeField; + private TextField maxRoomSizeField; + private TextField maxRoomsField; + private CheckBox randomShapesCheckBox; + private TextField corridorWidthField; + private CheckBox variedCorridorsCheckBox; + private CheckBox roomNoiseCheckBox; + + private Slider noiseIntensitySlider; + private Label noiseIntensityLabel; + + private Slider enemySpawnChanceSlider; + private Label enemySpawnChanceLabel; + + private TextField minEnemyDistanceField; + private CheckBox spawnOnlyCombatCheckBox; + private TextField seedTextField; + + public GenerationScreen(Main game) { + this.game = game; + } + + @Override + public void show() { + stage = new Stage(new ScreenViewport()); + Gdx.input.setInputProcessor(stage); + + skin = new Skin(Gdx.files.internal("xyz/samiker/theendlessweave/assets/ui/uiskin.json")); + + buildUI(); + loadDefaultSettings(); + } + + private void buildUI() { + Table root = new Table(); + root.setFillParent(true); + root.pad(20); + // root.setDebug(true); // Uncomment for debug lines + + Table content = new Table(); + ScrollPane scrollPane = new ScrollPane(content, skin); + scrollPane.setFadeScrollBars(false); + root.add(scrollPane).grow().row(); + + Table footer = new Table(); + TextButton startBtn = new TextButton("START GAME", skin); + startBtn.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + onStartGame(); + } + }); + + TextButton backBtn = new TextButton("Back", skin); + backBtn.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + game.setScreen(new MainMenuScreen(game)); + } + }); + + footer.add(backBtn).width(150).padRight(20); + footer.add(startBtn).width(200); + root.add(footer).padTop(20); + + stage.addActor(root); + + content.defaults().left().pad(5); + + content.add(new Label("Preset:", skin)).right(); + presetComboBox = new SelectBox<>(skin); + presetComboBox.setItems("Default", "Diverse", "Hardcore", "Custom"); + presetComboBox.addListener(new ChangeListener() { + @Override + public void changed(ChangeEvent event, Actor actor) { + loadPreset(presetComboBox.getSelected()); + } + }); + content.add(presetComboBox).width(200).row(); + + content.add(new Label("Difficulty:", skin)).right(); + Table diffTable = new Table(); + diffEasy = new TextButton("Easy", skin, "toggle"); + diffNormal = new TextButton("Normal", skin, "toggle"); + diffHard = new TextButton("Hard", skin, "toggle"); + difficultyGroup = new ButtonGroup<>(diffEasy, diffNormal, diffHard); + diffTable.add(diffEasy).width(80); + diffTable.add(diffNormal).width(80); + diffTable.add(diffHard).width(80); + content.add(diffTable).row(); + + ChangeListener diffListener = new ChangeListener() { + @Override + public void changed(ChangeEvent event, Actor actor) { + if (((TextButton)actor).isChecked()) updateEnemySettingsByDifficulty(); + } + }; + diffEasy.addListener(diffListener); + diffNormal.addListener(diffListener); + diffHard.addListener(diffListener); + + content.add(new Label("Map Size:", skin)).right(); + mapSizeSlider = new Slider(50, 500, 10, false, skin); + mapSizeLabel = new Label("100x100", skin); + mapSizeSlider.addListener(new ChangeListener() { + @Override + public void changed(ChangeEvent event, Actor actor) { + int size = (int) mapSizeSlider.getValue(); + mapSizeLabel.setText(size + "x" + size); + widthField.setText(String.valueOf(size)); + heightField.setText(String.valueOf(size)); + } + }); + Table sizeTable = new Table(); + sizeTable.add(mapSizeSlider).width(200); + sizeTable.add(mapSizeLabel).padLeft(10).width(80); + content.add(sizeTable).row(); + + content.add(new Separator(skin)).colspan(2).fillX().pad(10).row(); + + Table details = new Table(); + details.defaults().left().pad(5); + + widthField = createIntField("Width:", details); + heightField = createIntField("Height:", details); + details.row(); + + minRoomSizeField = createIntField("Min Room:", details); + maxRoomSizeField = createIntField("Max Room:", details); + details.row(); + + maxRoomsField = createIntField("Max Rooms:", details); + corridorWidthField = createIntField("Corr. Width:", details); + details.row(); + + inverseGeneration = new CheckBox(" Inverse Generation", skin); + randomShapesCheckBox = new CheckBox(" Random Shapes", skin); + variedCorridorsCheckBox = new CheckBox(" Varied Corridors", skin); + roomNoiseCheckBox = new CheckBox(" Room Noise", skin); + + details.add(inverseGeneration).colspan(2).row(); + details.add(randomShapesCheckBox).colspan(2).row(); + details.add(variedCorridorsCheckBox).colspan(2).row(); + details.add(roomNoiseCheckBox).colspan(2).row(); + + details.add(new Label("Noise Intensity:", skin)).right(); + noiseIntensitySlider = new Slider(0, 1, 0.01f, false, skin); + noiseIntensityLabel = new Label("0.0", skin); + setupSliderLabel(noiseIntensitySlider, noiseIntensityLabel); + Table noiseTable = new Table(); + noiseTable.add(noiseIntensitySlider).width(150); + noiseTable.add(noiseIntensityLabel).padLeft(5).width(40); + details.add(noiseTable).row(); + + details.add(new Label("Enemy Spawn %:", skin)).right(); + enemySpawnChanceSlider = new Slider(0, 1, 0.01f, false, skin); + enemySpawnChanceLabel = new Label("0.0", skin); + setupSliderLabel(enemySpawnChanceSlider, enemySpawnChanceLabel); + Table enemyTable = new Table(); + enemyTable.add(enemySpawnChanceSlider).width(150); + enemyTable.add(enemySpawnChanceLabel).padLeft(5).width(40); + details.add(enemyTable).row(); + + minEnemyDistanceField = createIntField("Min Enemy Dist:", details); + details.row(); + + wallsNoiseField = createDoubleField("Walls Noise:", details); // Double field + details.row(); + + spawnOnlyCombatCheckBox = new CheckBox(" Spawn only in combat", skin); + details.add(spawnOnlyCombatCheckBox).colspan(2).row(); + + details.add(new Label("Seed:", skin)).right(); + Table seedTable = new Table(); + seedTextField = new TextField("", skin); + seedTextField.setMessageText("Leave empty for random"); + TextButton rndSeedBtn = new TextButton("Rnd", skin); + rndSeedBtn.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + seedTextField.setText(String.valueOf(new Random().nextLong())); + } + }); + seedTable.add(seedTextField).width(150); + seedTable.add(rndSeedBtn).padLeft(5); + details.add(seedTable).colspan(1).row(); + + content.add(details).colspan(2); + } + + private TextField createIntField(String label, Table parent) { + parent.add(new Label(label, skin)).right(); + TextField field = new TextField("0", skin); + field.setTextFieldFilter(new TextField.TextFieldFilter.DigitsOnlyFilter()); + parent.add(field).width(80); + return field; + } + + private TextField createDoubleField(String label, Table parent) { + parent.add(new Label(label, skin)).right(); + TextField field = new TextField("0.0", skin); + parent.add(field).width(80); + return field; + } + + private void setupSliderLabel(Slider slider, Label label) { + slider.addListener(new ChangeListener() { + @Override + public void changed(ChangeEvent event, Actor actor) { + label.setText(String.format("%.2f", slider.getValue())); + } + }); + } + + private void loadDefaultSettings() { + loadSettingsFromRecord(MapGeneratorSettings.defaults()); + presetComboBox.setSelected("Default"); + diffNormal.setChecked(true); + } + + private void loadPreset(String presetName) { + MapGeneratorSettings settings = switch (presetName) { + case "Diverse" -> MapGeneratorSettings.diverse(); + case "Hardcore" -> MapGeneratorSettings.hardcore(); + case "Custom" -> null; + default -> MapGeneratorSettings.defaults(); + }; + + if (settings != null) { + loadSettingsFromRecord(settings); + } + } + + private void loadSettingsFromRecord(MapGeneratorSettings settings) { + inverseGeneration.setChecked(settings.isInverseGeneration()); + wallsNoiseField.setText(String.valueOf(settings.getWallsNoise())); + minRoomSizeField.setText(String.valueOf(settings.getMinRoomSize())); + maxRoomSizeField.setText(String.valueOf(settings.getMaxRoomSize())); + maxRoomsField.setText(String.valueOf(settings.getMaxRooms())); + randomShapesCheckBox.setChecked(settings.isUseRandomShapes()); + corridorWidthField.setText(String.valueOf(settings.getCorridorWidth())); + variedCorridorsCheckBox.setChecked(settings.isUseVariedCorridors()); + roomNoiseCheckBox.setChecked(settings.isUseRoomNoise()); + noiseIntensitySlider.setValue((float) settings.getNoiseIntensity()); + enemySpawnChanceSlider.setValue((float) settings.getEnemySpawnChance()); + minEnemyDistanceField.setText(String.valueOf(settings.getMinEnemyDistance())); + spawnOnlyCombatCheckBox.setChecked(settings.isSpawnOnlyInCombat()); + + noiseIntensityLabel.setText(String.format("%.2f", noiseIntensitySlider.getValue())); + enemySpawnChanceLabel.setText(String.format("%.2f", enemySpawnChanceSlider.getValue())); + } + + private void updateEnemySettingsByDifficulty() { + if (diffEasy.isChecked()) { + enemySpawnChanceSlider.setValue(0.3f); + minEnemyDistanceField.setText("4"); + } else if (diffNormal.isChecked()) { + enemySpawnChanceSlider.setValue(0.5f); + minEnemyDistanceField.setText("3"); + } else if (diffHard.isChecked()) { + enemySpawnChanceSlider.setValue(0.7f); + minEnemyDistanceField.setText("2"); + } + } + + private MapGeneratorSettings createSettingsFromUI() { + return new MapGeneratorSettings( + inverseGeneration.isChecked(), + parseDouble(wallsNoiseField.getText(), 0.1), + parseInt(minRoomSizeField.getText(), 5), + parseInt(maxRoomSizeField.getText(), 10), + parseInt(maxRoomsField.getText(), 10), + randomShapesCheckBox.isChecked(), + parseInt(corridorWidthField.getText(), 1), + variedCorridorsCheckBox.isChecked(), + roomNoiseCheckBox.isChecked(), + noiseIntensitySlider.getValue(), + enemySpawnChanceSlider.getValue(), + parseInt(minEnemyDistanceField.getText(), 3), + spawnOnlyCombatCheckBox.isChecked() + ); + } + + private int parseInt(String text, int def) { + try { return Integer.parseInt(text); } catch (Exception e) { return def; } + } + + private double parseDouble(String text, double def) { + try { return Double.parseDouble(text); } catch (Exception e) { return def; } + } + + private long getSeed() { + String text = seedTextField.getText().trim(); + if (text.isEmpty()) return System.currentTimeMillis(); + try { return Long.parseLong(text); } + catch (NumberFormatException e) { return text.hashCode(); } + } + + private void onStartGame() { +// MapGeneratorSettings settings = createSettingsFromUI(); +// int width = parseInt(widthField.getText(), 100); +// int height = parseInt(heightField.getText(), 100); +// long seed = getSeed(); + + //GameData gameData = new GameData(settings, width, height, seed); //TODO: должен отправляться запрос на сервер с данным конфигом + + game.setScreen(new GameScreen(game)); + } + + @Override + public void render(float delta) { + ScreenUtils.clear(0.1f, 0.1f, 0.1f, 1); + stage.act(delta); + stage.draw(); + } + + @Override + public void resize(int width, int height) { + stage.getViewport().update(width, height, true); + } + + @Override public void pause() {} + @Override public void resume() {} + @Override public void hide() {} + + @Override + public void dispose() { + stage.dispose(); + } + + private static class Separator extends Image { + public Separator(Skin skin) { + super(skin.newDrawable("white", Color.GRAY)); + setHeight(2); + } + } +} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/screens/LoadingScreen.java b/src/main/java/xyz/samiker/theendlessweave/client/screens/LoadingScreen.java new file mode 100644 index 0000000..e06869b --- /dev/null +++ b/src/main/java/xyz/samiker/theendlessweave/client/screens/LoadingScreen.java @@ -0,0 +1,80 @@ +package xyz.samiker.theendlessweave.client.screens; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.ScreenAdapter; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.scenes.scene2d.Stage; +import com.badlogic.gdx.scenes.scene2d.ui.Label; +import com.badlogic.gdx.scenes.scene2d.ui.ProgressBar; +import com.badlogic.gdx.scenes.scene2d.ui.Skin; +import com.badlogic.gdx.scenes.scene2d.ui.Table; +import com.badlogic.gdx.utils.ScreenUtils; +import com.badlogic.gdx.utils.viewport.ScreenViewport; +import xyz.samiker.theendlessweave.Main; +import xyz.samiker.theendlessweave.core.settings.SettingsManager; + +public class LoadingScreen extends ScreenAdapter { + private final Main game; + private Stage stage; + private ProgressBar progressBar; + private Label statusLabel; + + private boolean assetsQueued = false; + + public LoadingScreen(Main game) { + this.game = game; + } + + @Override + public void show() { + stage = new Stage(new ScreenViewport()); + Skin skin = new Skin(Gdx.files.internal("xyz/samiker/theendlessweave/assets/ui/uiskin.json")); + + Table table = new Table(); + table.setFillParent(true); + stage.addActor(table); + + statusLabel = new Label("Initializing settings...", skin); + progressBar = new ProgressBar(0, 1, 0.01f, false, skin); + + table.add(statusLabel).padBottom(10).row(); + table.add(progressBar).width(Gdx.graphics.getWidth() * 0.8f); + SettingsManager.loadSettings(); + game.getAssetManager().queueAll(); + assetsQueued = true; + } + + @Override + public void render(float delta) { + ScreenUtils.clear(Color.BLACK); + + if (assetsQueued) { + boolean finished = game.getAssetManager().update(); + + float progress = game.getAssetManager().getProgress(); + progressBar.setValue(progress); + + if (progress < 1.0f) { + statusLabel.setText("Loading assets... " + (int)(progress * 100) + "%"); + } else { + statusLabel.setText("Finalizing..."); + } + if (finished) { + game.setScreen(new MainMenuScreen(game)); + } + } + + stage.act(delta); + stage.draw(); + } + + @Override + public void resize(int width, int height) { + stage.getViewport().update(width, height, true); + } + + @Override + public void dispose() { + stage.dispose(); + } +} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/screens/MainMenuScreen.java b/src/main/java/xyz/samiker/theendlessweave/client/screens/MainMenuScreen.java new file mode 100644 index 0000000..6978f64 --- /dev/null +++ b/src/main/java/xyz/samiker/theendlessweave/client/screens/MainMenuScreen.java @@ -0,0 +1,102 @@ +package xyz.samiker.theendlessweave.client.screens; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Screen; +import com.badlogic.gdx.scenes.scene2d.InputEvent; +import com.badlogic.gdx.scenes.scene2d.Stage; +import com.badlogic.gdx.scenes.scene2d.ui.Skin; +import com.badlogic.gdx.scenes.scene2d.ui.Table; +import com.badlogic.gdx.scenes.scene2d.ui.TextButton; +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; +import com.badlogic.gdx.utils.ScreenUtils; +import com.badlogic.gdx.utils.viewport.ScreenViewport; +import xyz.samiker.theendlessweave.Main; + +public class MainMenuScreen implements Screen { + private final Main game; + private Stage stage; + private Skin skin; + + public MainMenuScreen(Main game) { + this.game = game; + } + + @Override + public void show() { + stage = new Stage(new ScreenViewport()); + Gdx.input.setInputProcessor(stage); + skin = new Skin(Gdx.files.internal("xyz/samiker/theendlessweave/assets/ui/uiskin.json")); + + Table table = new Table(); + table.setFillParent(true); + // table.setDebug(true); + stage.addActor(table); + + TextButton playBtn = new TextButton("Play", skin); + TextButton settingsBtn = new TextButton("Settings", skin); + TextButton devBtn = new TextButton("Dev Stage", skin); + TextButton exitBtn = new TextButton("Exit", skin); + + playBtn.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + game.setScreen(new GenerationScreen(game)); + System.out.println("Go to Generation Screen"); + } + }); + + settingsBtn.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + game.setScreen(new SettingsScreen(game)); + } + }); + + devBtn.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + game.setScreen(new GameScreen(game)); + } + }); + + exitBtn.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + Gdx.app.exit(); + } + }); + + table.add(playBtn).fillX().uniformX().pad(10); + table.row(); + table.add(settingsBtn).fillX().uniformX().pad(10); + table.row(); + table.add(devBtn).fillX().uniformX().pad(10); + table.row(); + table.add(exitBtn).fillX().uniformX().pad(10); + } + + @Override + public void render(float delta) { + ScreenUtils.clear(0.1f, 0.1f, 0.1f, 1); + + stage.act(delta); + stage.draw(); + } + + @Override + public void resize(int width, int height) { + stage.getViewport().update(width, height, true); + } + + @Override + public void pause() {} + @Override + public void resume() {} + @Override + public void hide() {} + + @Override + public void dispose() { + if (stage != null) stage.dispose(); + } +} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/screens/SettingsScreen.java b/src/main/java/xyz/samiker/theendlessweave/client/screens/SettingsScreen.java new file mode 100644 index 0000000..8c89970 --- /dev/null +++ b/src/main/java/xyz/samiker/theendlessweave/client/screens/SettingsScreen.java @@ -0,0 +1,282 @@ +package xyz.samiker.theendlessweave.client.screens; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Graphics; +import com.badlogic.gdx.Screen; +import com.badlogic.gdx.scenes.scene2d.Actor; +import com.badlogic.gdx.scenes.scene2d.InputEvent; +import com.badlogic.gdx.scenes.scene2d.Stage; +import com.badlogic.gdx.scenes.scene2d.ui.*; +import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; +import com.badlogic.gdx.utils.ScreenUtils; +import com.badlogic.gdx.utils.viewport.ScreenViewport; +import xyz.samiker.theendlessweave.Main; +import xyz.samiker.theendlessweave.core.settings.SettingsEnum; +import xyz.samiker.theendlessweave.core.settings.SettingsManager; + +public class SettingsScreen implements Screen { + + private final Main game; + private Stage stage; + private Skin skin; + + private Table contentContainer; + private TextButton generalTabBtn; + private TextButton videoTabBtn; + + public SettingsScreen(Main game) { + this.game = game; + } + + @Override + public void show() { + stage = new Stage(new ScreenViewport()); + Gdx.input.setInputProcessor(stage); + + skin = new Skin(Gdx.files.internal("xyz/samiker/theendlessweave/assets/ui/uiskin.json")); + buildLayout(); + + showGeneralSettings(); + generalTabBtn.setChecked(true); + } + + private void buildLayout() { + Table root = new Table(); + root.setFillParent(true); + root.pad(20); + stage.addActor(root); + + Label title = new Label("Settings", skin); + title.setFontScale(1.5f); + root.add(title).padBottom(20).colspan(2).row(); + + Table tabsTable = new Table(); + ButtonGroup tabsGroup = new ButtonGroup<>(); + + generalTabBtn = new TextButton("General", skin, "toggle"); + videoTabBtn = new TextButton("Video", skin, "toggle"); + + tabsGroup.add(generalTabBtn); + tabsGroup.add(videoTabBtn); + + tabsTable.add(generalTabBtn).width(150).fill(); + tabsTable.add(videoTabBtn).width(150).fill(); + + root.add(tabsTable).padBottom(20).colspan(2).row(); + + contentContainer = new Table(); + contentContainer.setBackground(skin.newDrawable("white", 0.1f, 0.1f, 0.1f, 0.5f)); // Серый фон + root.add(contentContainer).grow().colspan(2).row(); + + TextButton backBtn = new TextButton("Back to Menu", skin); + backBtn.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + game.setScreen(new MainMenuScreen(game)); + } + }); + root.add(backBtn).width(200).padTop(20).colspan(2); + + generalTabBtn.addListener(new ChangeListener() { + @Override + public void changed(ChangeEvent event, Actor actor) { + if (generalTabBtn.isChecked()) showGeneralSettings(); + } + }); + + videoTabBtn.addListener(new ChangeListener() { + @Override + public void changed(ChangeEvent event, Actor actor) { + if (videoTabBtn.isChecked()) showVideoSettings(); + } + }); + } + + private void showGeneralSettings() { + contentContainer.clearChildren(); + + Table table = new Table(); + table.pad(20); + + Label dirLabel = new Label("Mods Directory:", skin); + TextField modsDirField = new TextField(SettingsManager.getString(SettingsEnum.MODS_DIR), skin); + TextButton chooseBtn = new TextButton("...", skin); + TextButton saveBtn = new TextButton("Save", skin); + + chooseBtn.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + // В LibGDX нет встроенного DirectoryChooser. + // Варианты: + // 1. Использовать AWT (работает не везде): JFileChooser + // 2. Использовать библиотеку tinyfiledialogs + // 3. Просто позволить пользователю вводить путь вручную (реализовано здесь) + System.out.println("Directory Chooser not implemented in pure LibGDX core."); + } + }); + + saveBtn.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + SettingsManager.set(SettingsEnum.MODS_DIR, modsDirField.getText()); + SettingsManager.saveSettings(); + System.out.println("General settings saved."); + } + }); + + table.add(dirLabel).left().row(); + table.add(modsDirField).width(400).padRight(10); + table.add(chooseBtn).width(50).row(); + table.add(saveBtn).padTop(20).colspan(3); + + contentContainer.add(table).top(); + } + + private void showVideoSettings() { + contentContainer.clearChildren(); + + Table table = new Table(); + table.pad(20); + table.defaults().left().pad(5); + + table.add(new Label("Resolution:", skin)); + SelectBox resolutionSelect = new SelectBox<>(skin); + resolutionSelect.setItems("1920x1200", "1920x1080", "1600x900", "1366x768", "1280x720", "800x600"); + resolutionSelect.setSelected(SettingsManager.getString(SettingsEnum.RESOLUTION)); + table.add(resolutionSelect).width(200).row(); + + CheckBox fullscreenCheck = new CheckBox(" Fullscreen", skin); + fullscreenCheck.setChecked(SettingsManager.getBoolean(SettingsEnum.FULLSCREEN)); + + CheckBox borderlessCheck = new CheckBox(" Borderless Window", skin); + borderlessCheck.setChecked(SettingsManager.getBoolean(SettingsEnum.BORDERLESS)); + + table.add(fullscreenCheck).colspan(2).row(); + table.add(borderlessCheck).colspan(2).row(); + + CheckBox vsyncCheck = new CheckBox(" VSync", skin); + vsyncCheck.setChecked(SettingsManager.getBoolean(SettingsEnum.VSYNC)); + table.add(vsyncCheck).colspan(2).row(); + + CheckBox fpsLimitCheck = new CheckBox(" Limit FPS", skin); + fpsLimitCheck.setChecked(SettingsManager.getBoolean(SettingsEnum.FPS_LIMIT_ENABLED)); + + TextField fpsField = new TextField(String.valueOf(SettingsManager.getInt(SettingsEnum.FPS_LIMIT)), skin); + fpsField.setTextFieldFilter(new TextField.TextFieldFilter.DigitsOnlyFilter()); + fpsField.setDisabled(!fpsLimitCheck.isChecked()); + + fpsLimitCheck.addListener(new ChangeListener() { + @Override + public void changed(ChangeEvent event, Actor actor) { + fpsField.setDisabled(!fpsLimitCheck.isChecked()); + } + }); + + Table fpsTable = new Table(); + fpsTable.add(fpsLimitCheck).padRight(10); + fpsTable.add(fpsField).width(60); + table.add(fpsTable).colspan(2).row(); + + table.add(new Label("Brightness:", skin)); + Slider brightnessSlider = new Slider(0, 100, 1, false, skin); + brightnessSlider.setValue((float) SettingsManager.getDouble(SettingsEnum.BRIGHTNESS)); + Label brightnessValLabel = new Label((int)brightnessSlider.getValue() + "%", skin); + + brightnessSlider.addListener(new ChangeListener() { + @Override + public void changed(ChangeEvent event, Actor actor) { + brightnessValLabel.setText((int)brightnessSlider.getValue() + "%"); + } + }); + + Table brightTable = new Table(); + brightTable.add(brightnessSlider).width(200); + brightTable.add(brightnessValLabel).width(50).padLeft(10); + table.add(brightTable).row(); + + TextButton applyBtn = new TextButton("Apply Video Settings", skin); + applyBtn.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + SettingsManager.set(SettingsEnum.RESOLUTION, resolutionSelect.getSelected()); + SettingsManager.set(SettingsEnum.FULLSCREEN, fullscreenCheck.isChecked()); + SettingsManager.set(SettingsEnum.BORDERLESS, borderlessCheck.isChecked()); + SettingsManager.set(SettingsEnum.VSYNC, vsyncCheck.isChecked()); + SettingsManager.set(SettingsEnum.FPS_LIMIT_ENABLED, fpsLimitCheck.isChecked()); + try { + SettingsManager.set(SettingsEnum.FPS_LIMIT, Integer.parseInt(fpsField.getText())); + } catch (NumberFormatException ignored) {} + SettingsManager.set(SettingsEnum.BRIGHTNESS, brightnessSlider.getValue()); + SettingsManager.saveSettings(); + + applyGraphicsSettings(); + } + }); + + table.add(applyBtn).padTop(20).colspan(2).center(); + + contentContainer.add(table).top(); + } + + private void applyGraphicsSettings() { + String resStr = SettingsManager.getString(SettingsEnum.RESOLUTION); + String[] parts = resStr.split("x"); + int width = Integer.parseInt(parts[0]); + int height = Integer.parseInt(parts[1]); + boolean isFullscreen = SettingsManager.getBoolean(SettingsEnum.FULLSCREEN); + boolean isBorderless = SettingsManager.getBoolean(SettingsEnum.BORDERLESS); + boolean vsync = SettingsManager.getBoolean(SettingsEnum.VSYNC); + + Gdx.graphics.setVSync(vsync); + + if (isFullscreen) { + Graphics.DisplayMode mode = null; + for (Graphics.DisplayMode m : Gdx.graphics.getDisplayModes()) { + if (m.width == width && m.height == height) { + mode = m; + break; + } + } + + if (mode == null) mode = Gdx.graphics.getDisplayMode(); + Gdx.graphics.setFullscreenMode(mode); + } else { + Gdx.graphics.setWindowedMode(width, height); + if (isBorderless) { + Gdx.graphics.setUndecorated(true); + } else { + Gdx.graphics.setUndecorated(false); + } + } + + if (SettingsManager.getBoolean(SettingsEnum.FPS_LIMIT_ENABLED)) { + Gdx.graphics.setForegroundFPS(SettingsManager.getInt(SettingsEnum.FPS_LIMIT)); + } else { + Gdx.graphics.setForegroundFPS(0); + } + + System.out.println("Video settings applied."); + } + + @Override + public void render(float delta) { + ScreenUtils.clear(0.15f, 0.15f, 0.15f, 1); + stage.act(delta); + stage.draw(); + } + + @Override + public void resize(int width, int height) { + stage.getViewport().update(width, height, true); + } + + @Override public void pause() {} + @Override public void resume() {} + @Override public void hide() {} + + @Override + public void dispose() { + if (stage != null) stage.dispose(); + } +} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/systems/CameraSystem.java b/src/main/java/xyz/samiker/theendlessweave/client/systems/CameraSystem.java index 6ca84fe..b25aadf 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/systems/CameraSystem.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/systems/CameraSystem.java @@ -1,5 +1,8 @@ package xyz.samiker.theendlessweave.client.systems; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Vector2; import xyz.samiker.theendlessweave.client.custom.InputManager; import xyz.samiker.theendlessweave.core.entities.Entity; import xyz.samiker.theendlessweave.core.entities.components.CameraTargetComponent; @@ -8,50 +11,37 @@ import xyz.samiker.theendlessweave.core.utils.Array; import static xyz.samiker.theendlessweave.core.utils.Constants.ENTITY_SIZE; -import static xyz.samiker.theendlessweave.core.utils.Constants.SPRITE_SIZE; public class CameraSystem implements ISystem { - private final Camera camera; + private final OrthographicCamera camera; - private static final double CAMERA_SMOOTHING = 0.08; - private static final double ZOOM_SMOOTHING = 0.1; + private static final float CAMERA_SMOOTHING = 0.1f; + private static final float ZOOM_SMOOTHING = 0.1f; + private static final float MAX_ZOOM = 1.5f; + private static final float MIN_ZOOM = 0.5f; - private static final double MAX_SCALE = 3.0; - private static final double MIN_SCALE = 0.5; + private float targetZoom = 1.0f; - private double targetZoom; - private final double startZoomFactor; - private boolean isInitialized = false; + private float shakeIntensity = 0; + private float shakeDuration = 0; + private float currentShakeX = 0; + private float currentShakeY = 0; - private double shakeIntensity = 0; - private double shakeDuration = 0; + private final Vector2 logicPosition = new Vector2(); - private double shakeOffsetX = 0; - private double shakeOffsetY = 0; + public CameraSystem(OrthographicCamera camera) { + this.camera = camera; + this.logicPosition.set(camera.position.x, camera.position.y); + this.targetZoom = camera.zoom; - public CameraSystem() { - this.camera = new Camera(); - - double startZoom = SPRITE_SIZE / ENTITY_SIZE; - this.startZoomFactor = startZoom; - - this.camera.zoom = startZoom; - this.targetZoom = startZoom; - - InputManager.registerScrollAction(event -> { - double deltaY = event.getDeltaY(); - double zoomFactor = 1.1; - - if (deltaY < 0) { - zoomFactor = 1 / zoomFactor; + InputManager.registerScrollAction((event) -> { + float zoomFactor = 1.1f; + if (event.getAmountY() > 0) { + targetZoom *= zoomFactor; + } else { + targetZoom /= zoomFactor; } - - double newTargetZoom = targetZoom * zoomFactor; - - if (newTargetZoom > MAX_SCALE) newTargetZoom = MAX_SCALE; - if (newTargetZoom < MIN_SCALE * startZoomFactor) newTargetZoom = MIN_SCALE * startZoomFactor; - - targetZoom = newTargetZoom; + targetZoom = MathUtils.clamp(targetZoom, MIN_ZOOM, MAX_ZOOM); }); } @@ -62,33 +52,29 @@ public void update(Array entities, double deltaTime) { PositionComponent targetPos = target.getComponent(PositionComponent.class); - double targetWorldX = targetPos.x + ENTITY_SIZE / 2.0; - double targetWorldY = targetPos.y + ENTITY_SIZE / 2.0; + float targetWorldX = (float) (targetPos.x + ENTITY_SIZE / 2.0); + float targetWorldY = (float) (targetPos.y + ENTITY_SIZE / 2.0); - if (!isInitialized) { - camera.x = targetWorldX; - camera.y = targetWorldY; - isInitialized = true; - } - - if (Math.abs(targetZoom - camera.zoom) > 0.001) { + if (Math.abs(targetZoom - camera.zoom) > 0.001f) { camera.zoom += (targetZoom - camera.zoom) * ZOOM_SMOOTHING; } else { camera.zoom = targetZoom; } - camera.x += (targetWorldX - camera.x) * CAMERA_SMOOTHING; - camera.y += (targetWorldY - camera.y) * CAMERA_SMOOTHING; + logicPosition.x += (targetWorldX - logicPosition.x) * CAMERA_SMOOTHING; + logicPosition.y += (targetWorldY - logicPosition.y) * CAMERA_SMOOTHING; if (shakeDuration > 0) { - shakeOffsetX = (Math.random() - 0.5) * shakeIntensity; - shakeOffsetY = (Math.random() - 0.5) * shakeIntensity; - shakeDuration -= deltaTime; - shakeIntensity *= 0.9; + currentShakeX = (MathUtils.random() - 0.5f) * shakeIntensity; + currentShakeY = (MathUtils.random() - 0.5f) * shakeIntensity; + shakeDuration -= (float) deltaTime; + shakeIntensity *= 0.9f; } else { - shakeOffsetX = 0; - shakeOffsetY = 0; + currentShakeX = 0; + currentShakeY = 0; } + camera.position.set(logicPosition.x + currentShakeX, logicPosition.y + currentShakeY, 0); + camera.update(); } private Entity findCameraTarget(Array entities) { @@ -101,26 +87,12 @@ private Entity findCameraTarget(Array entities) { return null; } - public void shake(double intensity, double duration) { + public void shake(float intensity, float duration) { this.shakeIntensity = intensity; this.shakeDuration = duration; } - public CameraSystem.Camera getCamera() { - return this.camera; - } - - public class Camera { - public double x = 0; - public double y = 0; - public double zoom = 1.0; - - public double getRenderX() { - return x + shakeOffsetX; - } - - public double getRenderY() { - return y + shakeOffsetY; - } + public boolean isVisible(float x, float y, float width, float height) { + return camera.frustum.boundsInFrustum(x + width/2, y + height/2, 0, width/2, height/2, 0); } } \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/systems/ClientNetworkEventSystem.java b/src/main/java/xyz/samiker/theendlessweave/client/systems/ClientNetworkEventSystem.java index c93d5c2..cf52afa 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/systems/ClientNetworkEventSystem.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/systems/ClientNetworkEventSystem.java @@ -2,12 +2,16 @@ import xyz.samiker.theendlessweave.client.network.GameClient; import xyz.samiker.theendlessweave.core.entities.Entity; +import xyz.samiker.theendlessweave.core.entities.components.ProjectileComponent; +import xyz.samiker.theendlessweave.core.entities.components.VelocityComponent; import xyz.samiker.theendlessweave.core.logic.Game; import xyz.samiker.theendlessweave.core.network.packets.PacketDestroyEntity; import xyz.samiker.theendlessweave.core.network.packets.PacketSpawnEntity; import xyz.samiker.theendlessweave.core.systems.ISystem; import xyz.samiker.theendlessweave.core.utils.Array; +import static xyz.samiker.theendlessweave.core.utils.Constants.TILE_SIZE; + public class ClientNetworkEventSystem implements ISystem { private final GameClient client; private final Game game; @@ -37,14 +41,50 @@ public void update(Array entities, double dt) { private void processEvent(Object event, Array entities) { if (event instanceof PacketSpawnEntity pkt) { - System.out.println(pkt.type); switch (pkt.type) { - case ENEMY -> game.createEnemy(pkt.id, pkt.x, pkt.y); - case PROJECTILE -> game.createProjectile(pkt.id, pkt.x, pkt.y, Math.sqrt(pkt.dx * pkt.dx + pkt.dy * pkt.dy), pkt.dx, pkt.dy); - default -> { - return; + case ENEMY -> { + if ((pkt.mask & PacketSpawnEntity.HAS_VEL) != 0) { + Entity e = game.createEnemy(pkt.id, pkt.x, pkt.y); + e.addComponent(new VelocityComponent(pkt.speed, pkt.dx, pkt.dy)); + } } - }; + case PROJECTILE -> { + if ((pkt.mask & PacketSpawnEntity.HAS_PROJ_DATA) != 0) { + double speed = pkt.speed * TILE_SIZE; + Entity e = game.createProjectile(pkt.id, pkt.x, pkt.y, speed, pkt.dx, pkt.dy); + VelocityComponent vc = e.getComponent(VelocityComponent.class); + if (vc != null) { + vc.speed = speed; + vc.initialSpeed = speed; + vc.dx = pkt.dx; + vc.dy = pkt.dy; + } else { + e.addComponent(new VelocityComponent(speed, pkt.dx, pkt.dy)); + } + + ProjectileComponent pc = e.getComponent(ProjectileComponent.class); + if (pc != null) { + pc.movementType = pkt.movementType; + pc.initialAngle = pkt.initialAngle; + pc.frequency = pkt.frequency; + pc.amplitude = pkt.amplitude; + pc.phaseOffset = pkt.phaseOffset; + pc.maxSpeed = pkt.maxSpeed; + pc.acceleration = pkt.acceleration; + pc.spawnDelay = pkt.spawnDelay; + pc.lifetime = pkt.lifetime; + pc.timeAlive = 0; + + if (pkt.dx != 0 || pkt.dy != 0) { + double angleFromVel = Math.atan2(pkt.dy, pkt.dx); + if (Math.abs(pc.initialAngle - angleFromVel) > 0.01) { + pc.initialAngle = angleFromVel; + } + } + } + } + } + } } else if (event instanceof PacketDestroyEntity pkt) { for (int i = 0; i < entities.size; i++) { diff --git a/src/main/java/xyz/samiker/theendlessweave/client/systems/ClientNetworkReceiverSystem.java b/src/main/java/xyz/samiker/theendlessweave/client/systems/ClientNetworkReceiverSystem.java index efd9d29..9a8bc89 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/systems/ClientNetworkReceiverSystem.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/systems/ClientNetworkReceiverSystem.java @@ -67,7 +67,6 @@ private void applyDestroy(PacketDestroyEntity packet) { Entity e = entityLookup.get(packet.id); if (e != null) { e.dead = true; - } } diff --git a/src/main/java/xyz/samiker/theendlessweave/client/systems/ClientProjectileSystem.java b/src/main/java/xyz/samiker/theendlessweave/client/systems/ClientProjectileSystem.java new file mode 100644 index 0000000..73fcb0b --- /dev/null +++ b/src/main/java/xyz/samiker/theendlessweave/client/systems/ClientProjectileSystem.java @@ -0,0 +1,142 @@ +package xyz.samiker.theendlessweave.client.systems; + +import xyz.samiker.theendlessweave.core.entities.Entity; +import xyz.samiker.theendlessweave.core.entities.components.PlayerTagComponent; +import xyz.samiker.theendlessweave.core.entities.components.PositionComponent; +import xyz.samiker.theendlessweave.core.entities.components.ProjectileComponent; +import xyz.samiker.theendlessweave.core.entities.components.VelocityComponent; +import xyz.samiker.theendlessweave.core.systems.ISystem; +import xyz.samiker.theendlessweave.core.utils.Array; + +public class ClientProjectileSystem implements ISystem { + private Entity cachedPlayer; + + @Override + public void update(Array entities, double deltaTime) { + updatePlayerCache(entities); + + for (int i = 0; i < entities.size; i++) { + Entity entity = entities.get(i); + ProjectileComponent projectile = entity.getComponent(ProjectileComponent.class); + PositionComponent pos = entity.getComponent(PositionComponent.class); + VelocityComponent vel = entity.getComponent(VelocityComponent.class); + + if (projectile == null || vel == null || pos == null) continue; + projectile.timeAlive += deltaTime; + if (projectile.timeAlive < projectile.spawnDelay) continue; + + switch (projectile.movementType) { + case SINE_WAVE -> updateSineWave(projectile, vel); + case SPIRAL -> updateSpiral(projectile, vel); + case HOMING -> updateHoming(projectile, pos, vel, deltaTime, cachedPlayer); + case BOOMERANG -> updateBoomerang(projectile, vel); + case ACCELERATING -> updateAccelerating(projectile, vel, deltaTime); + } + + pos.x += vel.dx * vel.speed * deltaTime; + pos.y += vel.dy * vel.speed * deltaTime; + } + } + + private void updatePlayerCache(Array entities) { + if (cachedPlayer != null && !cachedPlayer.dead) { + return; + } + for (int i = 0; i < entities.size; i++) { + Entity entity = entities.get(i); + if (entity.hasComponent(PlayerTagComponent.class)) { + cachedPlayer = entity; + return; + } + } + cachedPlayer = null; + } + + private void updateAccelerating(ProjectileComponent projectile, VelocityComponent vel, double deltaTime) { + vel.speed += projectile.acceleration * deltaTime; + if (vel.speed > projectile.maxSpeed) vel.speed = projectile.maxSpeed; + } + + private void updateSineWave(ProjectileComponent projectile, VelocityComponent vel) { + double baseAngle = projectile.initialAngle; + + double forwardX = Math.cos(baseAngle); + double forwardY = Math.sin(baseAngle); + + double sideX = -forwardY; + double sideY = forwardX; + + double waveCos = Math.cos(projectile.frequency * projectile.timeAlive + projectile.phaseOffset); + double waveFactor = waveCos * projectile.amplitude * projectile.frequency; + + vel.dx = forwardX + (sideX * waveFactor / 100.0); + vel.dy = forwardY + (sideY * waveFactor / 100.0); + } + + private void updateSpiral(ProjectileComponent projectile, VelocityComponent vel) { + double time = projectile.timeAlive; + double baseAngle = projectile.initialAngle; + + double angle = baseAngle + (projectile.frequency * time); + + vel.dx = Math.cos(angle) - Math.sin(angle) * projectile.frequency * time; + vel.dy = Math.sin(angle) + Math.cos(angle) * projectile.frequency * time; + + double len = Math.sqrt(vel.dx * vel.dx + vel.dy * vel.dy); + if (len != 0) { + vel.dx /= len; + vel.dy /= len; + } + } + + private void updateHoming(ProjectileComponent projectile, PositionComponent pos, + VelocityComponent vel, double deltaTime, Entity player) { + if (player == null) return; + + PositionComponent targetPos = player.getComponent(PositionComponent.class); + if (targetPos == null) return; + + double dx = targetPos.x - pos.x; + double dy = targetPos.y - pos.y; + double targetAngle = Math.atan2(dy, dx); + double currentAngle = Math.atan2(vel.dy, vel.dx); + + double angleDiff = targetAngle - currentAngle; + while (angleDiff > Math.PI) angleDiff -= 6.28318530718; // 2 * PI + while (angleDiff < -Math.PI) angleDiff += 6.28318530718; + + double turnSpeed = projectile.homingStrength * deltaTime; + double step = Math.signum(angleDiff) * Math.min(Math.abs(angleDiff), turnSpeed); + double newAngle = currentAngle + step; + + vel.dx = Math.cos(newAngle); + vel.dy = Math.sin(newAngle); + } + + private void updateBoomerang(ProjectileComponent projectile, VelocityComponent vel) { + double time = projectile.timeAlive; + double lifetime = projectile.lifetime; + double halfLife = lifetime / 2.0; + + double initialSpeed = vel.initialSpeed; + + if (time < halfLife) { + double slowdown = 1.0 - (time / halfLife); + vel.speed = initialSpeed * slowdown; + } else { + if (!projectile.isReturning) { + vel.dx = -vel.dx; + vel.dy = -vel.dy; + projectile.isReturning = true; + } + double speedup = (time - halfLife) / halfLife; + vel.speed = initialSpeed * speedup; + } + } + + private static double getSineOffset(ProjectileComponent projectile) { + return projectile.amplitude * Math.sin( + projectile.frequency * projectile.timeAlive + projectile.phaseOffset + ); + } +} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/systems/DebugRenderSystem.java b/src/main/java/xyz/samiker/theendlessweave/client/systems/DebugRenderSystem.java index 4338273..913125e 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/systems/DebugRenderSystem.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/systems/DebugRenderSystem.java @@ -1,11 +1,6 @@ package xyz.samiker.theendlessweave.client.systems; -import javafx.scene.control.Label; -import javafx.scene.effect.DropShadow; -import javafx.scene.layout.Pane; -import javafx.scene.paint.Color; -import javafx.scene.text.Font; -import javafx.scene.text.FontWeight; +import com.badlogic.gdx.scenes.scene2d.ui.Label; import xyz.samiker.theendlessweave.core.entities.Entity; import xyz.samiker.theendlessweave.core.logic.PerformanceMonitor; import xyz.samiker.theendlessweave.core.systems.ISystem; @@ -14,26 +9,11 @@ public class DebugRenderSystem implements ISystem { private final PerformanceMonitor profiler; private final Label debugLabel; - private String lastRenderedText = ""; - public DebugRenderSystem(Pane uiPane, PerformanceMonitor profiler) { + public DebugRenderSystem(Label debugLabel, PerformanceMonitor profiler) { + this.debugLabel = debugLabel; this.profiler = profiler; - - this.debugLabel = new Label(); - this.debugLabel.setTextFill(Color.LIME); - this.debugLabel.setFont(Font.font("Consolas", FontWeight.BOLD, 13)); - this.debugLabel.setTranslateX(10); - this.debugLabel.setTranslateY(10); - - DropShadow ds = new DropShadow(); - ds.setOffsetY(1.0f); - ds.setColor(Color.BLACK); - this.debugLabel.setEffect(ds); - - this.debugLabel.setMouseTransparent(true); this.debugLabel.setVisible(false); - - uiPane.getChildren().add(debugLabel); } @Override @@ -44,11 +24,6 @@ public void update(Array entities, double deltaTime) { } if (!debugLabel.isVisible()) debugLabel.setVisible(true); - - String currentReport = profiler.getLastReport(); - if (!currentReport.equals(lastRenderedText)) { - debugLabel.setText(currentReport); - lastRenderedText = currentReport; - } + debugLabel.setText(profiler.getLastReport()); } } \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/systems/HudRenderSystem.java b/src/main/java/xyz/samiker/theendlessweave/client/systems/HudRenderSystem.java index b279a15..76272af 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/systems/HudRenderSystem.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/systems/HudRenderSystem.java @@ -1,7 +1,7 @@ package xyz.samiker.theendlessweave.client.systems; -import javafx.scene.control.Label; -import javafx.scene.control.ProgressBar; +import com.badlogic.gdx.scenes.scene2d.ui.Label; +import com.badlogic.gdx.scenes.scene2d.ui.ProgressBar; import xyz.samiker.theendlessweave.core.entities.Entity; import xyz.samiker.theendlessweave.core.entities.components.HealthComponent; import xyz.samiker.theendlessweave.core.entities.components.LevelComponent; @@ -12,19 +12,17 @@ public class HudRenderSystem implements ISystem { private final ProgressBar healthBar; - private final Label healthLabel; private final Label scoreLabel; private final Label levelLabel; - private double lastRenderedHp = -1; - private double lastRenderedMaxHp = -1; + private float lastRenderedHp = -1; + private float lastRenderedMaxHp = -1; private int lastRenderedScore = -1; private int lastRenderedLevel = -1; private Entity player; - public HudRenderSystem(ProgressBar healthBar, Label healthLabel, Label scoreLabel, Label levelLabel) { + public HudRenderSystem(ProgressBar healthBar, Label scoreLabel, Label levelLabel) { this.healthBar = healthBar; - this.healthLabel = healthLabel; this.scoreLabel = scoreLabel; this.levelLabel = levelLabel; } @@ -32,9 +30,7 @@ public HudRenderSystem(ProgressBar healthBar, Label healthLabel, Label scoreLabe @Override public void update(Array entities, double deltaTime) { updatePlayerCache(entities); - if (player == null) return; - updateUI(player); } @@ -44,39 +40,35 @@ private void updateUI(Entity player) { final LevelComponent level = player.getComponent(LevelComponent.class); if (health != null) { - if (Math.abs(health.health - lastRenderedHp) > 0.1 || Math.abs(health.maxHealth - lastRenderedMaxHp) > 0.1) { - lastRenderedHp = health.health; - lastRenderedMaxHp = health.maxHealth; + if (Math.abs(health.health - lastRenderedHp) > 0.1f || Math.abs(health.maxHealth - lastRenderedMaxHp) > 0.1f) { + lastRenderedHp = (float) health.health; + lastRenderedMaxHp = (float) health.maxHealth; - double percent = lastRenderedHp / lastRenderedMaxHp; - healthLabel.setText((int) lastRenderedHp + "/" + (int) lastRenderedMaxHp); - healthBar.setProgress(percent); - healthBar.getStyleClass().removeAll("hp-high", "hp-medium", "hp-low"); + float percent = lastRenderedHp / lastRenderedMaxHp; + healthBar.setValue(percent); - if (percent > 0.5) { - healthBar.getStyleClass().add("hp-high"); - } else if (percent > 0.25) { - healthBar.getStyleClass().add("hp-medium"); - } else { - healthBar.getStyleClass().add("hp-low"); - } + /* + if (percent > 0.5f) healthBar.setColor(Color.GREEN); + else if (percent > 0.25f) healthBar.setColor(Color.ORANGE); + else healthBar.setColor(Color.RED); + */ } } if (score != null && score.getScore() != lastRenderedScore) { lastRenderedScore = score.getScore(); - scoreLabel.setText(String.valueOf(lastRenderedScore)); + scoreLabel.setText("Score: " + lastRenderedScore); } if (level != null && level.getLevel() != lastRenderedLevel) { lastRenderedLevel = level.getLevel(); - levelLabel.setText(String.valueOf(lastRenderedLevel)); + levelLabel.setText("Lvl: " + lastRenderedLevel); } } + private void updatePlayerCache(Array entities) { - if (player != null && !player.dead) { - return; - } + if (player != null && !player.dead) return; + for (int i = 0; i < entities.size; i++) { Entity entity = entities.get(i); if (entity.hasComponent(PlayerTagComponent.class)) { diff --git a/src/main/java/xyz/samiker/theendlessweave/client/systems/InputSystem.java b/src/main/java/xyz/samiker/theendlessweave/client/systems/InputSystem.java index e82c5e2..b0e3122 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/systems/InputSystem.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/systems/InputSystem.java @@ -1,8 +1,10 @@ package xyz.samiker.theendlessweave.client.systems; -import javafx.scene.input.KeyCode; -import javafx.scene.input.MouseButton; -import xyz.samiker.theendlessweave.Main; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Vector3; import xyz.samiker.theendlessweave.client.custom.InputManager; import xyz.samiker.theendlessweave.client.network.GameClient; import xyz.samiker.theendlessweave.core.entities.Entity; @@ -16,17 +18,16 @@ public class InputSystem implements ISystem { private final GameClient client; private final Game game; - private final CameraSystem.Camera camera; + private final OrthographicCamera camera; private Entity player; - //TODO сделать настройки и оттуда крч да private boolean isMobile = false; + private float lastAngle = 0; - public InputSystem(GameClient client, Game game, CameraSystem.Camera camera) { + public InputSystem(GameClient client, Game game, OrthographicCamera camera) { this.client = client; this.game = game; this.camera = camera; - init(); } @@ -40,11 +41,11 @@ public void update(Array entities, double deltaTime) { if (player != null) { PacketInput input = new PacketInput(); - input.up = InputManager.isKeyPressed(KeyCode.W); - input.down = InputManager.isKeyPressed(KeyCode.S); - input.left = InputManager.isKeyPressed(KeyCode.A); - input.right = InputManager.isKeyPressed(KeyCode.D); - input.shoot = InputManager.isMouseButtonPressed(MouseButton.PRIMARY); + input.up = InputManager.isKeyPressed(Input.Keys.W); + input.down = InputManager.isKeyPressed(Input.Keys.S); + input.left = InputManager.isKeyPressed(Input.Keys.A); + input.right = InputManager.isKeyPressed(Input.Keys.D); + input.shoot = InputManager.isMouseButtonPressed(Input.Buttons.LEFT); PositionComponent pos = player.getComponent(PositionComponent.class); if (pos != null) { @@ -60,37 +61,26 @@ public void update(Array entities, double deltaTime) { } private float calculateDesktopAngle(PositionComponent playerPos) { - double screenMouseX = InputManager.getMouseX(); - double screenMouseY = InputManager.getMouseY(); - - double screenW = Main.getResolution().getWidth(); - double screenH = Main.getResolution().getHeight(); - - double worldMouseX = (screenMouseX - screenW / 2.0) / camera.zoom + camera.getRenderX(); - double worldMouseY = (screenMouseY - screenH / 2.0) / camera.zoom + camera.getRenderY(); + float screenX = Gdx.input.getX(); + float screenY = Gdx.input.getY(); + Vector3 worldMouse = camera.unproject(new Vector3(screenX, screenY, 0)); - return (float) Math.atan2(worldMouseY - playerPos.y, worldMouseX - playerPos.x); + return MathUtils.atan2(worldMouse.y - (float)playerPos.y, worldMouse.x - (float)playerPos.x); } private float calculateMobileAngle() { - double screenW = Main.getResolution().getWidth(); - double screenH = Main.getResolution().getHeight(); - - double centerX = screenW / 2.0; - double centerY = screenH / 2.0; - - double dx = InputManager.getMouseX() - centerX; - double dy = InputManager.getMouseY() - centerY; + float dx = InputManager.getHorizontalInput(); + float dy = InputManager.getVerticalInput(); - if (Math.abs(dx) < 0.1 && Math.abs(dy) < 0.1) { - return 0; + if (Math.abs(dx) < 0.1f && Math.abs(dy) < 0.1f) { + return lastAngle; } - return (float) Math.atan2(dy, dx); + return lastAngle = MathUtils.atan2(dy, dx); } private void init() { - InputManager.registerKeyPressedAction(KeyCode.F5, game::toggleDebug); + InputManager.registerKeyPressedAction(Input.Keys.F5, game::toggleDebug); } private void updatePlayerCache(Array entities) { diff --git a/src/main/java/xyz/samiker/theendlessweave/client/systems/RenderSystem.java b/src/main/java/xyz/samiker/theendlessweave/client/systems/RenderSystem.java index fdd4f7d..1e18bfa 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/systems/RenderSystem.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/systems/RenderSystem.java @@ -1,8 +1,11 @@ package xyz.samiker.theendlessweave.client.systems; -import javafx.scene.canvas.Canvas; -import javafx.scene.canvas.GraphicsContext; -import javafx.scene.paint.Color; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.TextureRegion; import xyz.samiker.theendlessweave.core.entities.Entity; import xyz.samiker.theendlessweave.core.entities.components.PositionComponent; import xyz.samiker.theendlessweave.core.entities.components.ProjectileComponent; @@ -15,108 +18,67 @@ import static xyz.samiker.theendlessweave.core.utils.Constants.PROJECTILE_SIZE; public class RenderSystem implements ISystem { - private final Canvas canvas; - private final GraphicsContext gc; - private final CameraSystem.Camera camera; + private final SpriteBatch batch; + private final OrthographicCamera camera; + private final TextureRegion whitePixel; - private final double[] xPoints = new double[4]; - private final double[] yPoints = new double[4]; - - public RenderSystem(Canvas canvas, CameraSystem.Camera camera) { - this.canvas = canvas; - this.gc = canvas.getGraphicsContext2D(); + public RenderSystem(SpriteBatch batch, OrthographicCamera camera) { + this.batch = batch; this.camera = camera; + + Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888); + pixmap.setColor(Color.WHITE); + pixmap.fill(); + this.whitePixel = new TextureRegion(new Texture(pixmap)); + pixmap.dispose(); } @Override public void update(Array entities, double deltaTime) { - double canvasWidth = canvas.getWidth(); - double canvasHeight = canvas.getHeight(); - - gc.clearRect(0, 0, canvasWidth, canvasHeight); - - double zoom = Math.max(camera.zoom, 0.01); - double camX = camera.getRenderX(); - double camY = camera.getRenderY(); - - gc.save(); - gc.translate(canvasWidth / 2.0, canvasHeight / 2.0); - gc.scale(zoom, zoom); - gc.translate(-camX, -camY); - - double halfWidthWorld = (canvasWidth / 2.0) / zoom; - double halfHeightWorld = (canvasHeight / 2.0) / zoom; - double viewMinX = camX - halfWidthWorld - 100; - double viewMaxX = camX + halfWidthWorld + 100; - double viewMinY = camY - halfHeightWorld - 100; - double viewMaxY = camY + halfHeightWorld + 100; + batch.setProjectionMatrix(camera.combined); + batch.begin(); for (int i = 0; i < entities.size; i++) { Entity entity = entities.get(i); PositionComponent pos = entity.getComponent(PositionComponent.class); - ProjectileComponent proj = entity.getComponent(ProjectileComponent.class); - if (entity.hasComponent(ProjectileComponent.class) && proj.timeAlive < proj.spawnDelay) continue; if (pos == null) continue; - if (pos.x < viewMinX || pos.x > viewMaxX || pos.y < viewMinY || pos.y > viewMaxY) { + ProjectileComponent proj = entity.getComponent(ProjectileComponent.class); + if (proj != null && proj.timeAlive < proj.spawnDelay) continue; + + if (!camera.frustum.boundsInFrustum((float)pos.x + 25, (float)pos.y + 25, 0, 50, 50, 0)) { continue; } RenderComponent render = entity.getComponent(RenderComponent.class); - if (render == null || !render.isVisible) { - if(render == null) continue; - } + if (render == null || !render.isVisible) continue; drawEntity(entity, render, pos); } - gc.restore(); + batch.setColor(Color.WHITE); + batch.end(); } private void drawEntity(Entity entity, RenderComponent render, PositionComponent pos) { RotationComponent rot = entity.getComponent(RotationComponent.class); - double angle = (rot != null) ? rot.angle : 0; + float angle = (rot != null) ? (float) rot.angle : 0; - double cx = pos.x + ENTITY_SIZE / 2.0; - double cy = pos.y + ENTITY_SIZE / 2.0; - double halfSize = ENTITY_SIZE / 2.0; + float size = (float) ((render.type == RenderComponent.RenderType.PROJECTILE) ? PROJECTILE_SIZE : ENTITY_SIZE); + float halfSize = size / 2f; switch (render.type) { - case PROJECTILE: gc.setFill(Color.WHITE); break; - case ENEMY: gc.setFill(Color.RED); break; - case PLAYER: gc.setFill(Color.BLUE); break; - } - - if (angle == 0) { - double size = (render.type == RenderComponent.RenderType.PROJECTILE) ? PROJECTILE_SIZE : ENTITY_SIZE; - double offset = size / 2.0; - gc.fillRect(cx - offset, cy - offset, size, size); - } - else { - double rad = Math.toRadians(angle); - double cos = Math.cos(rad); - double sin = Math.sin(rad); - - // x' = x*cos - y*sin - // y' = x*sin + y*cos - // Top-Left - xPoints[0] = cx + (-halfSize * cos - -halfSize * sin); - yPoints[0] = cy + (-halfSize * sin + -halfSize * cos); - - // Top-Right - xPoints[1] = cx + (halfSize * cos - -halfSize * sin); - yPoints[1] = cy + (halfSize * sin + -halfSize * cos); - - // Bottom-Right - xPoints[2] = cx + (halfSize * cos - halfSize * sin); - yPoints[2] = cy + (halfSize * sin + halfSize * cos); - - // Bottom-Left - xPoints[3] = cx + (-halfSize * cos - halfSize * sin); - yPoints[3] = cy + (-halfSize * sin + halfSize * cos); - - gc.fillPolygon(xPoints, yPoints, 4); + case PROJECTILE: batch.setColor(Color.WHITE); break; + case ENEMY: batch.setColor(Color.RED); break; + case PLAYER: batch.setColor(Color.BLUE); break; + default: batch.setColor(Color.GRAY); } + batch.draw(whitePixel, + (float) pos.x, (float) pos.y, + halfSize, halfSize, + size, size, + 1, 1, + angle); } } \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/systems/TileRenderSystem.java b/src/main/java/xyz/samiker/theendlessweave/client/systems/TileRenderSystem.java index 24fa3d0..86cc7cf 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/systems/TileRenderSystem.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/systems/TileRenderSystem.java @@ -1,8 +1,8 @@ package xyz.samiker.theendlessweave.client.systems; -import javafx.scene.canvas.Canvas; -import javafx.scene.canvas.GraphicsContext; -import javafx.scene.image.Image; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.TextureRegion; import xyz.samiker.theendlessweave.client.assets.ManifestAssetManager; import xyz.samiker.theendlessweave.core.entities.Entity; import xyz.samiker.theendlessweave.core.gamemap.GameMap; @@ -16,55 +16,52 @@ import static xyz.samiker.theendlessweave.core.utils.Constants.TILE_SIZE; public class TileRenderSystem implements ISystem { - private final Canvas canvas; - private final GraphicsContext gc; + private final SpriteBatch batch; private final GameMap gameMap; - private final CameraSystem.Camera camera; + private final OrthographicCamera camera; - private final EnumMap tileTextures; + private final EnumMap tileTextures; - public TileRenderSystem(Canvas canvas, GameMap gameMap, CameraSystem.Camera camera, ManifestAssetManager assets) { - this.canvas = canvas; - this.gc = canvas.getGraphicsContext2D(); + public TileRenderSystem(SpriteBatch batch, GameMap gameMap, OrthographicCamera camera, ManifestAssetManager assets) { + this.batch = batch; this.gameMap = gameMap; this.camera = camera; - - this.gc.setImageSmoothing(false); - this.tileTextures = new EnumMap<>(TileType.class); initTextureCache(assets); } private void initTextureCache(ManifestAssetManager assets) { - tileTextures.put(TileType.WALL, assets.textures().get("tiles/wall")); - tileTextures.put(TileType.FLOOR, assets.textures().get("tiles/floor_1")); - tileTextures.put(TileType.DOOR, assets.textures().get("tiles/door")); - // VOID не должен здесь быть. в данный момент это просто затычка. максимум прозрачную текстуру. + try { + TextureRegion wallTex = new TextureRegion(assets.textures().get("wall")); + TextureRegion floorTex = new TextureRegion(assets.textures().get("floor_1")); + TextureRegion doorTex = new TextureRegion(assets.textures().get("door")); + + tileTextures.put(TileType.WALL, wallTex); + tileTextures.put(TileType.FLOOR, floorTex); + tileTextures.put(TileType.DOOR, doorTex); + } catch (Exception e) { + System.err.println("Tile textures missing or init failed: " + e.getMessage()); + e.printStackTrace(); + } } @Override public void update(Array entities, double deltaTime) { - double canvasWidth = canvas.getWidth(); - double canvasHeight = canvas.getHeight(); - - gc.clearRect(0, 0, canvasWidth, canvasHeight); + batch.setProjectionMatrix(camera.combined); + batch.begin(); + batch.setColor(1, 1, 1, 1); - double zoom = Math.max(camera.zoom, 0.01); - double camX = camera.getRenderX(); - double camY = camera.getRenderY(); + float camX = camera.position.x; + float camY = camera.position.y; - gc.save(); - gc.translate(canvasWidth / 2.0, canvasHeight / 2.0); - gc.scale(zoom, zoom); - gc.translate(-camX, -camY); - - double halfWidth = (canvasWidth / 2.0) / zoom; - double halfHeight = (canvasHeight / 2.0) / zoom; + float halfWidth = (camera.viewportWidth * camera.zoom) / 2f; + float halfHeight = (camera.viewportHeight * camera.zoom) / 2f; int mapWidth = gameMap.getWidth(); int mapHeight = gameMap.getHeight(); int margin = 2; + int startTileX = Math.max(0, (int) ((camX - halfWidth) / TILE_SIZE) - margin); int startTileY = Math.max(0, (int) ((camY - halfHeight) / TILE_SIZE) - margin); int endTileX = Math.min(mapWidth - 1, (int) ((camX + halfWidth) / TILE_SIZE) + margin); @@ -75,16 +72,15 @@ public void update(Array entities, double deltaTime) { Tile tile = gameMap.getTile(x, y); if (tile == null) continue; - Image texture = tileTextures.get(tile.getType()); - + TextureRegion texture = tileTextures.get(tile.getType()); if (texture != null) { - double drawX = tile.getX() - TILE_SIZE / 2.0; - double drawY = tile.getY() - TILE_SIZE / 2.0; + float drawX = x * (float)TILE_SIZE; + float drawY = y * (float)TILE_SIZE; - gc.drawImage(texture, drawX, drawY, TILE_SIZE, TILE_SIZE); + batch.draw(texture, drawX, drawY, (float)TILE_SIZE, (float)TILE_SIZE); } } } - gc.restore(); + batch.end(); } } \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/client/systems/WorldUIRenderer.java b/src/main/java/xyz/samiker/theendlessweave/client/systems/WorldUIRenderer.java index 01e7607..cc68046 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/systems/WorldUIRenderer.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/systems/WorldUIRenderer.java @@ -1,198 +1,99 @@ package xyz.samiker.theendlessweave.client.systems; -import javafx.scene.Node; -import javafx.scene.control.Label; -import javafx.scene.layout.Pane; -import javafx.scene.layout.Region; -import xyz.samiker.theendlessweave.Main; -import xyz.samiker.theendlessweave.client.custom.HPBar; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.utils.Align; import xyz.samiker.theendlessweave.core.entities.Entity; import xyz.samiker.theendlessweave.core.entities.components.*; import xyz.samiker.theendlessweave.core.systems.ISystem; import xyz.samiker.theendlessweave.core.utils.Array; -import java.util.HashMap; -import java.util.Map; - import static xyz.samiker.theendlessweave.core.utils.Constants.ENTITY_SIZE; public class WorldUIRenderer implements ISystem { - private static final String LABEL_STYLE = - "-fx-text-fill: white;" + - "-fx-font-weight: bold;" + - "-fx-font-size: 12px;" + - "-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.8), 2, 0.5, 0, 1);"; - - private final Pane worldUIPane; - private final CameraSystem.Camera camera; - - private static class EntityUIView { - HPBar hpBar; - Label nameTag; - boolean visible = true; - long lastUpdateFrame = -1; - - void setVisible(boolean state) { - if (this.visible != state) { - this.visible = state; - if (hpBar != null) hpBar.setVisible(state); - if (nameTag != null) nameTag.setVisible(state); - } - } - } - private final Map uiViews = new HashMap<>(); - private final Array trackedEntities = new Array<>(128); - private final Array entitiesToRemoveBuffer = new Array<>(64); + private final SpriteBatch batch; + private final OrthographicCamera camera; + private final BitmapFont font; + private final TextureRegion whitePixel; - private final Array nodesToAdd = new Array<>(64); - private final Array nodesToRemove = new Array<>(64); - - private long frameCounter = 0; - - public WorldUIRenderer(Pane worldUIPane, CameraSystem.Camera camera) { - this.worldUIPane = worldUIPane; + public WorldUIRenderer(SpriteBatch batch, OrthographicCamera camera) { + this.batch = batch; this.camera = camera; - this.worldUIPane.setPickOnBounds(false); - this.worldUIPane.setMouseTransparent(true); + this.font = new BitmapFont(); + this.font.setUseIntegerPositions(false); + this.font.getData().setScale(1.0f); + + Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888); + pixmap.setColor(Color.WHITE); + pixmap.fill(); + this.whitePixel = new TextureRegion(new Texture(pixmap)); + pixmap.dispose(); } @Override public void update(Array entities, double deltaTime) { - frameCounter++; - - double zoom = Math.max(0.01, camera.zoom); - double camX = camera.getRenderX(); - double camY = camera.getRenderY(); - - double halfScreenW = Main.getResolution().getWidth() / 2.0; - double halfScreenH = Main.getResolution().getHeight() / 2.0; - - double margin = 100 / zoom; - double viewMinX = camX - (halfScreenW / zoom) - margin; - double viewMaxX = camX + (halfScreenW / zoom) + margin; - double viewMinY = camY - (halfScreenH / zoom) - margin; - double viewMaxY = camY + (halfScreenH / zoom) + margin; + batch.setProjectionMatrix(camera.combined); + batch.begin(); for (int i = 0; i < entities.size; i++) { Entity entity = entities.get(i); - PositionComponent posComp = entity.getComponent(PositionComponent.class); - if (posComp == null) continue; - - boolean isVisible = (posComp.x >= viewMinX) && (posComp.x <= viewMaxX) && - (posComp.y >= viewMinY) && (posComp.y <= viewMaxY); - - EntityUIView view = uiViews.get(entity); - - if (!isVisible) { - if (view != null) view.setVisible(false); + PositionComponent pos = entity.getComponent(PositionComponent.class); + if (pos == null) continue; + if (!camera.frustum.boundsInFrustum((float)pos.x, (float)pos.y, 0, 50, 50, 0)) { continue; } - if (view == null) { - boolean needHP = shouldHaveHealthBar(entity); - boolean needName = shouldHaveNameTag(entity); - - if (needHP || needName) { - view = new EntityUIView(); - if (needHP) createHealthBar(entity, view); - if (needName) createNameTag(entity, view); - uiViews.put(entity, view); - trackedEntities.add(entity); - } else { - continue; - } - } - - view.setVisible(true); - view.lastUpdateFrame = frameCounter; - - double entityCenterX = posComp.x + (ENTITY_SIZE / 2.0); - double entityTopY = posComp.y; - - double screenX = (entityCenterX - camX) * zoom + halfScreenW; - double screenY = (entityTopY - camY) * zoom + halfScreenH; - - if (view.nameTag != null) { - view.nameTag.setScaleX(zoom); - view.nameTag.setScaleY(zoom); - view.nameTag.setLayoutX(screenX - (view.nameTag.getWidth() / 2)); - view.nameTag.setLayoutY(screenY - (30 * zoom)); + if (entity.hasComponent(HealthComponent.class) && !entity.hasComponent(PlayerTagComponent.class)) { + drawHealthBar(entity, pos); } - if (view.hpBar != null) { - view.hpBar.setScaleX(zoom); - view.hpBar.setScaleY(zoom); - view.hpBar.setLayoutX(screenX - (view.hpBar.getBarWidth() / 2)); - view.hpBar.setLayoutY(screenY - (15 * zoom)); - view.hpBar.update(entity); + if (entity.hasComponent(NameComponent.class) && entity.hasComponent(ShowNameTagComponent.class)) { + drawNameTag(entity, pos); } } - performCleanup(); + batch.setColor(Color.WHITE); + batch.end(); } - private void applySceneGraphChanges() { - if (nodesToAdd.size > 0) { - for (int i = 0; i < nodesToAdd.size; i++) { - worldUIPane.getChildren().add(nodesToAdd.get(i)); - } - nodesToAdd.clear(); - } - if (nodesToRemove.size > 0) { - for (int i = 0; i < nodesToRemove.size; i++) { - worldUIPane.getChildren().remove(nodesToRemove.get(i)); - } - nodesToRemove.clear(); - } - } + private void drawHealthBar(Entity entity, PositionComponent pos) { + HealthComponent health = entity.getComponent(HealthComponent.class); + if (health.maxHealth <= 0) return; - private void performCleanup() { - entitiesToRemoveBuffer.clear(); - for (int i = 0; i < trackedEntities.size; i++) { - Entity entity = trackedEntities.get(i); - EntityUIView view = uiViews.get(entity); - - if (view == null || view.lastUpdateFrame != frameCounter) { - if (view != null) { - if (view.hpBar != null) nodesToRemove.add(view.hpBar); - if (view.nameTag != null) nodesToRemove.add(view.nameTag); - } - entitiesToRemoveBuffer.add(entity); - trackedEntities.removeSwap(i); - i--; - } - } + float percent = (float) (health.health / health.maxHealth); + if (percent < 0) percent = 0; + if (percent > 1) percent = 1; - for (int i = 0; i < entitiesToRemoveBuffer.size; i++) { - uiViews.remove(entitiesToRemoveBuffer.get(i)); - } + float barWidth = 40f; + float barHeight = 6f; + float x = (float) (pos.x + (ENTITY_SIZE / 2f) - (barWidth / 2f)); + float y = (float) (pos.y - 10f); - applySceneGraphChanges(); - } + batch.setColor(0, 0, 0, 0.8f); + batch.draw(whitePixel, x, y, barWidth, barHeight); - private void createHealthBar(Entity entity, EntityUIView view) { - HPBar bar = new HPBar(entity); - view.hpBar = bar; - nodesToAdd.add(bar); - } + if (percent > 0.5f) batch.setColor(Color.GREEN); + else if (percent > 0.25f) batch.setColor(Color.ORANGE); + else batch.setColor(Color.RED); - private void createNameTag(Entity entity, EntityUIView view) { - NameComponent name = entity.getComponent(NameComponent.class); - Label label = new Label(); - label.setText(name.name); - label.setStyle(LABEL_STYLE); - label.setCache(true); - label.setMinWidth(Region.USE_PREF_SIZE); - view.nameTag = label; - nodesToAdd.add(label); + if (percent > 0) { + batch.draw(whitePixel, x + 1, y + 1, (barWidth - 2) * percent, barHeight - 2); + } } - private boolean shouldHaveHealthBar(Entity entity) { - return entity.hasComponent(HealthComponent.class) && !entity.hasComponent(PlayerTagComponent.class); - } + private void drawNameTag(Entity entity, PositionComponent pos) { + NameComponent nameComp = entity.getComponent(NameComponent.class); + + float x = (float) (pos.x + ENTITY_SIZE / 2f); + float y = (float) (pos.y - 15f); - private boolean shouldHaveNameTag(Entity entity) { - return entity.hasComponent(NameComponent.class) && entity.hasComponent(ShowNameTagComponent.class); + font.setColor(Color.WHITE); + font.draw(batch, nameComp.name, x, y, 0, Align.center, false); } } \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/core/entities/Entity.java b/src/main/java/xyz/samiker/theendlessweave/core/entities/Entity.java index a648c3a..91f6339 100644 --- a/src/main/java/xyz/samiker/theendlessweave/core/entities/Entity.java +++ b/src/main/java/xyz/samiker/theendlessweave/core/entities/Entity.java @@ -2,6 +2,7 @@ import xyz.samiker.theendlessweave.core.entities.components.ComponentType; +import java.util.Arrays; import java.util.BitSet; public class Entity { @@ -49,4 +50,15 @@ public boolean equals(Object o) { public int hashCode() { return id; } + + @Override + public String toString() { + return "Entity{" + + "id=" + id + + ", components=" + Arrays.toString(components) + + ", componentBits=" + componentBits + + ", dead=" + dead + + ", active=" + active + + '}'; + } } \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/core/entities/components/ScaleComponent.java b/src/main/java/xyz/samiker/theendlessweave/core/entities/components/ScaleComponent.java index f5b1f00..9345b70 100644 --- a/src/main/java/xyz/samiker/theendlessweave/core/entities/components/ScaleComponent.java +++ b/src/main/java/xyz/samiker/theendlessweave/core/entities/components/ScaleComponent.java @@ -1,22 +1,11 @@ package xyz.samiker.theendlessweave.core.entities.components; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.SimpleDoubleProperty; - public class ScaleComponent implements Component { - private final DoubleProperty scaleX = new SimpleDoubleProperty(1.0); - private final DoubleProperty scaleY = new SimpleDoubleProperty(1.0); + public double scaleX = 1; + public double scaleY = 1; public ScaleComponent(double scaleX, double scaleY) { - this.scaleX.set(scaleX); - this.scaleY.set(scaleY); + this.scaleX = scaleX; + this.scaleY = scaleY; } - - public double getScaleX() { return scaleX.get(); } - public void setScaleX(double value) { scaleX.set(value); } - public DoubleProperty scaleXProperty() { return scaleX; } - - public double getScaleY() { return scaleY.get(); } - public void setScaleY(double value) { scaleY.set(value); } - public DoubleProperty scaleYProperty() { return scaleY; } } diff --git a/src/main/java/xyz/samiker/theendlessweave/core/logic/Game.java b/src/main/java/xyz/samiker/theendlessweave/core/logic/Game.java index 5cd98d5..4950be5 100644 --- a/src/main/java/xyz/samiker/theendlessweave/core/logic/Game.java +++ b/src/main/java/xyz/samiker/theendlessweave/core/logic/Game.java @@ -26,7 +26,7 @@ public class Game { private final Array logicSystems = new Array<>(5); private final Array renderSystems = new Array<>(7); private final Array entities = new Array<>(2048); - private final Array queueAddEntities = new Array<>(512); + public final Array queueAddEntities = new Array<>(512); private int nextEntityId = 0; private Entity player; diff --git a/src/main/java/xyz/samiker/theendlessweave/core/logic/Loop.java b/src/main/java/xyz/samiker/theendlessweave/core/logic/Loop.java index 00c2ffa..382fe7f 100644 --- a/src/main/java/xyz/samiker/theendlessweave/core/logic/Loop.java +++ b/src/main/java/xyz/samiker/theendlessweave/core/logic/Loop.java @@ -1,13 +1,10 @@ package xyz.samiker.theendlessweave.core.logic; -import javafx.animation.AnimationTimer; - import static xyz.samiker.theendlessweave.core.utils.Constants.TARGET_TPS; import static xyz.samiker.theendlessweave.core.utils.Constants.TIME_PER_TICK; public class Loop { private final Game game; - private final AnimationTimer renderTimer; private Thread logicThread; private volatile boolean running = false; @@ -15,31 +12,6 @@ public class Loop { public Loop(Game game) { this.game = game; - - this.renderTimer = new AnimationTimer() { - private long lastRenderTime = 0; - - @Override - public void handle(long now) { - if (lastRenderTime == 0) { - lastRenderTime = now; - return; - } - - double renderDeltaTime = (now - lastRenderTime) / 1_000_000_000.0; - lastRenderTime = now; - - if (currentState == GameState.PLAYING) { - game.render(renderDeltaTime); - } - } - - @Override - public void start() { - super.start(); - lastRenderTime = 0; - } - }; } public void start() { @@ -49,13 +21,10 @@ public void start() { logicThread = new Thread(this::runLogicLoop, "Game-Logic-Thread"); logicThread.setDaemon(true); logicThread.start(); - - renderTimer.start(); } public void stop() { running = false; - renderTimer.stop(); try { if (logicThread != null) { logicThread.join(1000); @@ -87,6 +56,10 @@ private void runLogicLoop() { accumulator += updateLength; + if (accumulator > 0.25 * 1_000_000_000) { + accumulator = 0.25 * 1_000_000_000; + } + while (accumulator >= TIME_PER_TICK) { game.updateLogic(1.0 / TARGET_TPS); accumulator -= TIME_PER_TICK; diff --git a/src/main/java/xyz/samiker/theendlessweave/core/network/NetworkRegister.java b/src/main/java/xyz/samiker/theendlessweave/core/network/NetworkRegister.java index 73209fb..81aa1da 100644 --- a/src/main/java/xyz/samiker/theendlessweave/core/network/NetworkRegister.java +++ b/src/main/java/xyz/samiker/theendlessweave/core/network/NetworkRegister.java @@ -4,6 +4,7 @@ import com.esotericsoftware.kryonet.EndPoint; import xyz.samiker.theendlessweave.core.GameData; import xyz.samiker.theendlessweave.core.entities.components.PositionComponent; +import xyz.samiker.theendlessweave.core.entities.components.ProjectileComponent; import xyz.samiker.theendlessweave.core.entities.components.RenderComponent; import xyz.samiker.theendlessweave.core.entities.components.VelocityComponent; import xyz.samiker.theendlessweave.core.gamemap.MapGeneratorSettings; @@ -32,6 +33,8 @@ public static void register(EndPoint endPoint) { kryo.register(VelocityComponent.class); kryo.register(RenderComponent.class); kryo.register(RenderComponent.RenderType.class); + kryo.register(ProjectileComponent.class); + kryo.register(ProjectileComponent.MovementType.class); } } diff --git a/src/main/java/xyz/samiker/theendlessweave/core/network/packets/PacketSpawnEntity.java b/src/main/java/xyz/samiker/theendlessweave/core/network/packets/PacketSpawnEntity.java index c19e81f..35e827e 100644 --- a/src/main/java/xyz/samiker/theendlessweave/core/network/packets/PacketSpawnEntity.java +++ b/src/main/java/xyz/samiker/theendlessweave/core/network/packets/PacketSpawnEntity.java @@ -1,10 +1,85 @@ package xyz.samiker.theendlessweave.core.network.packets; +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.KryoSerializable; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import xyz.samiker.theendlessweave.core.entities.components.ProjectileComponent; import xyz.samiker.theendlessweave.core.entities.components.RenderComponent; -public class PacketSpawnEntity extends Packet { +public class PacketSpawnEntity extends Packet implements KryoSerializable { public int id; public RenderComponent.RenderType type; public float x, y; + + public int mask; + public float dx, dy; + public float speed; + + public ProjectileComponent.MovementType movementType; + public float maxSpeed; + public float acceleration; + public float initialAngle; + public float frequency; + public float amplitude; + public float phaseOffset; + public float spawnDelay; + public float lifetime; + + public static final int HAS_VEL = 1; // 0001 + public static final int HAS_PROJ_DATA = 2; // 0010 + + @Override + public void write(Kryo kryo, Output output) { + output.writeInt(id); + kryo.writeObject(output, type); + output.writeFloat(x); + output.writeFloat(y); + output.writeInt(mask); + + if ((mask & HAS_VEL) != 0) { + output.writeFloat(dx); + output.writeFloat(dy); + output.writeFloat(speed); + } + + if ((mask & HAS_PROJ_DATA) != 0) { + kryo.writeObject(output, movementType); + output.writeFloat(maxSpeed); + output.writeFloat(acceleration); + output.writeFloat(initialAngle); + output.writeFloat(frequency); + output.writeFloat(amplitude); + output.writeFloat(phaseOffset); + output.writeFloat(spawnDelay); + } + } + + @Override + public void read(Kryo kryo, Input input) { + id = input.readInt(); + type = kryo.readObject(input, RenderComponent.RenderType.class); + x = input.readFloat(); + y = input.readFloat(); + + mask = input.readInt(); + + if ((mask & HAS_VEL) != 0) { + dx = input.readFloat(); + dy = input.readFloat(); + speed = input.readFloat(); + } + + if ((mask & HAS_PROJ_DATA) != 0) { + movementType = kryo.readObject(input, ProjectileComponent.MovementType.class); + maxSpeed = input.readFloat(); + acceleration = input.readFloat(); + initialAngle = input.readFloat(); + frequency = input.readFloat(); + amplitude = input.readFloat(); + phaseOffset = input.readFloat(); + spawnDelay = input.readFloat(); + } + } } \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/core/settings/SettingsEnum.java b/src/main/java/xyz/samiker/theendlessweave/core/settings/SettingsEnum.java index 8c90daa..dc0897c 100644 --- a/src/main/java/xyz/samiker/theendlessweave/core/settings/SettingsEnum.java +++ b/src/main/java/xyz/samiker/theendlessweave/core/settings/SettingsEnum.java @@ -10,15 +10,6 @@ public enum SettingsEnum { BRIGHTNESS("video.brightness", 50.0), ANTIALIASING("video.antiAliasing", false), - //prism engine settings (хуйня какая-то) - RENDERER("prism.order", "d3d"), //es2, sw - TEXT_RENDERER("prism.text", "gray"), //lcd - HIDPI("prism.allowhidpi", false), - DIRTY_OPTS("prism.dirtyopts", true), - //DEBUG - SHOW_DIRTY("prism.showdirty", false), - SHOW_FPS("debug.showFps", false), - MODS_DIR("general.mods_dir", ""), CONTROL("general.control_type", "auto"), diff --git a/src/main/java/xyz/samiker/theendlessweave/core/utils/Array.java b/src/main/java/xyz/samiker/theendlessweave/core/utils/Array.java index c62f721..1f4db64 100644 --- a/src/main/java/xyz/samiker/theendlessweave/core/utils/Array.java +++ b/src/main/java/xyz/samiker/theendlessweave/core/utils/Array.java @@ -1,6 +1,7 @@ package xyz.samiker.theendlessweave.core.utils; import java.util.Arrays; +import java.util.function.Predicate; @SuppressWarnings("unchecked") public class Array { @@ -83,6 +84,16 @@ public void addAll(Array other) { size += other.size; } + public int count(Predicate condition) { + int count = 0; + for (int i = 0; i < size; i++) { + if (condition.test((T) data[i])) { + count++; + } + } + return count; + } + private void resize(int newCapacity) { data = Arrays.copyOf(data, newCapacity); } diff --git a/src/main/java/xyz/samiker/theendlessweave/server/ServerLauncher.java b/src/main/java/xyz/samiker/theendlessweave/server/ServerLauncher.java index 028dd7a..ee630f8 100644 --- a/src/main/java/xyz/samiker/theendlessweave/server/ServerLauncher.java +++ b/src/main/java/xyz/samiker/theendlessweave/server/ServerLauncher.java @@ -3,6 +3,8 @@ import xyz.samiker.theendlessweave.core.GameData; import xyz.samiker.theendlessweave.core.gamemap.MapGeneratorSettings; import xyz.samiker.theendlessweave.core.logic.Game; +import xyz.samiker.theendlessweave.core.network.packets.PacketSpawnEntity; +import xyz.samiker.theendlessweave.core.utils.Array; import xyz.samiker.theendlessweave.server.network.GameServer; import xyz.samiker.theendlessweave.server.systems.*; @@ -47,5 +49,6 @@ public static void initSystems(Game game, GameServer server) { game.addLogicSystem(new MovementSystem(map)); game.addLogicSystem(new DamageSystem()); game.addLogicSystem(new ServerSnapshotSenderSystem(server)); + game.addLogicSystem(new ServerEntityEventSystem(server, game)); } } diff --git a/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerDebugSystem.java b/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerDebugSystem.java index f6ee0f2..662822d 100644 --- a/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerDebugSystem.java +++ b/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerDebugSystem.java @@ -15,7 +15,7 @@ public ServerDebugSystem(PerformanceMonitor profiler) { @Override public void update(Array entities, double deltaTime) { - if (initialized) { + if (!initialized) { profiler.setConsoleMode(true); profiler.setActive(true); System.out.println("[ServerDebugSystem] Performance monitoring started."); diff --git a/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerEntityEventSystem.java b/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerEntityEventSystem.java new file mode 100644 index 0000000..ddab428 --- /dev/null +++ b/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerEntityEventSystem.java @@ -0,0 +1,75 @@ +package xyz.samiker.theendlessweave.server.systems; + +import xyz.samiker.theendlessweave.core.entities.Entity; +import xyz.samiker.theendlessweave.core.entities.components.*; +import xyz.samiker.theendlessweave.core.logic.Game; +import xyz.samiker.theendlessweave.core.network.packets.PacketSpawnEntity; +import xyz.samiker.theendlessweave.core.systems.ISystem; +import xyz.samiker.theendlessweave.core.utils.Array; +import xyz.samiker.theendlessweave.server.network.GameServer; + +public class ServerEntityEventSystem implements ISystem { + private final GameServer server; + private final Game game; + + public ServerEntityEventSystem(GameServer server, Game game) { + this.server = server; + this.game = game; + } + + public void update(Array entities, double dt) { + Array pending = game.queueAddEntities; + if (pending.isEmpty()) return; + + for (int i = 0; i < pending.size; i++) { + Entity e = pending.get(i); + if (e.dead) continue; + + PacketSpawnEntity pkt = new PacketSpawnEntity(); + + pkt.id = e.id; + + PositionComponent pos = e.getComponent(PositionComponent.class); + if (pos != null) { + pkt.x = (float) pos.x; + pkt.y = (float) pos.y; + } + + RenderComponent render = e.getComponent(RenderComponent.class); + if (render != null) { + pkt.type = render.type; + } else { + pkt.type = RenderComponent.RenderType.ENEMY; + } + + int mask = 0; + + VelocityComponent vel = e.getComponent(VelocityComponent.class); + ProjectileComponent proj = e.getComponent(ProjectileComponent.class); + + if (vel != null) { + mask |= PacketSpawnEntity.HAS_VEL; + pkt.dx = (float) vel.dx; + pkt.dy = (float) vel.dy; + pkt.speed = (float) vel.speed; + } + + if (proj != null) { + mask |= PacketSpawnEntity.HAS_PROJ_DATA; + pkt.movementType = proj.movementType; + pkt.initialAngle = (float) proj.initialAngle; + pkt.frequency = (float) proj.frequency; + pkt.amplitude = (float) proj.amplitude; + pkt.phaseOffset = (float) proj.phaseOffset; + pkt.spawnDelay = (float) proj.spawnDelay; + pkt.lifetime = (float) proj.lifetime; + pkt.maxSpeed = (float) proj.maxSpeed; + pkt.acceleration = (float) proj.acceleration; + } + + pkt.mask = mask; + + server.sendToAll(pkt); + } + } +} \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerInputSystem.java b/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerInputSystem.java index e3f344b..cd040e9 100644 --- a/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerInputSystem.java +++ b/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerInputSystem.java @@ -50,8 +50,9 @@ private void applyInput(Entity player, PacketInput input) { vel.dx = 0; vel.dy = 0; - if (input.up) vel.dy = -speed; - if (input.down) vel.dy = speed; + if (input.up) vel.dy = speed; + if (input.down) vel.dy = -speed; + if (input.left) vel.dx = -speed; if (input.right) vel.dx = speed; diff --git a/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerSnapshotSenderSystem.java b/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerSnapshotSenderSystem.java index 16c5b8b..82c9820 100644 --- a/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerSnapshotSenderSystem.java +++ b/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerSnapshotSenderSystem.java @@ -1,21 +1,19 @@ package xyz.samiker.theendlessweave.server.systems; import xyz.samiker.theendlessweave.core.entities.Entity; -import xyz.samiker.theendlessweave.core.entities.components.HealthComponent; -import xyz.samiker.theendlessweave.core.entities.components.PositionComponent; -import xyz.samiker.theendlessweave.core.entities.components.RotationComponent; -import xyz.samiker.theendlessweave.core.entities.components.VelocityComponent; -import xyz.samiker.theendlessweave.core.network.packets.Packet; -import xyz.samiker.theendlessweave.core.network.packets.PacketDestroyEntity; -import xyz.samiker.theendlessweave.core.network.packets.PacketEntityUpdate; -import xyz.samiker.theendlessweave.core.network.packets.PacketSnapshot; +import xyz.samiker.theendlessweave.core.entities.components.*; +import xyz.samiker.theendlessweave.core.logic.Game; +import xyz.samiker.theendlessweave.core.network.packets.*; import xyz.samiker.theendlessweave.core.network.utils.PacketPool; import xyz.samiker.theendlessweave.core.systems.ISystem; import xyz.samiker.theendlessweave.core.utils.Array; import xyz.samiker.theendlessweave.server.network.GameServer; +import java.util.Arrays; + public class ServerSnapshotSenderSystem implements ISystem { private final GameServer server; + private final Array packetBuffer = new Array<>(1024); public ServerSnapshotSenderSystem(GameServer server) { this.server = server; @@ -23,19 +21,21 @@ public ServerSnapshotSenderSystem(GameServer server) { @Override public void update(Array entities, double dt) { - PacketSnapshot snapshot = new PacketSnapshot(); - snapshot.entities = new Packet[entities.size]; + packetBuffer.clear(); for (int i = 0; i < entities.size; i++) { Entity e = entities.get(i); - if (e.dead) { PacketDestroyEntity pkt = new PacketDestroyEntity(); pkt.id = e.id; - snapshot.entities[i] = pkt; + packetBuffer.add(pkt); continue; } + ProjectileComponent proj = e.getComponent(ProjectileComponent.class); + if (proj != null && proj.movementType != ProjectileComponent.MovementType.HOMING) { + continue; + } PacketEntityUpdate update = PacketPool.obtainUpdate(); update.id = e.id; int mask = 0; @@ -67,9 +67,14 @@ public void update(Array entities, double dt) { } update.mask = mask; - snapshot.entities[i] = update; + packetBuffer.add(update); } + if (packetBuffer.size == 0) return; + + PacketSnapshot snapshot = new PacketSnapshot(); + snapshot.entities = Arrays.copyOf(packetBuffer.data, packetBuffer.size, Packet[].class); + server.sendToAll(snapshot); PacketPool.freeAll(snapshot.entities); } diff --git a/src/main/resources/xyz/samiker/theendlessweave/assets/ui/default.fnt b/src/main/resources/xyz/samiker/theendlessweave/assets/ui/default.fnt new file mode 100644 index 0000000..81d3449 --- /dev/null +++ b/src/main/resources/xyz/samiker/theendlessweave/assets/ui/default.fnt @@ -0,0 +1,101 @@ +info face="Droid Sans" size=17 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 +common lineHeight=20 base=18 scaleW=256 scaleH=128 pages=1 packed=0 +page id=0 file="default.png" +chars count=96 +char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=16 xadvance=4 page=0 chnl=0 +char id=124 x=0 y=0 width=6 height=20 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0 +char id=106 x=6 y=0 width=9 height=20 xoffset=-4 yoffset=3 xadvance=4 page=0 chnl=0 +char id=81 x=15 y=0 width=15 height=19 xoffset=-2 yoffset=3 xadvance=12 page=0 chnl=0 +char id=74 x=30 y=0 width=11 height=19 xoffset=-5 yoffset=3 xadvance=4 page=0 chnl=0 +char id=125 x=41 y=0 width=10 height=18 xoffset=-3 yoffset=3 xadvance=6 page=0 chnl=0 +char id=123 x=51 y=0 width=10 height=18 xoffset=-3 yoffset=3 xadvance=6 page=0 chnl=0 +char id=93 x=61 y=0 width=8 height=18 xoffset=-3 yoffset=3 xadvance=5 page=0 chnl=0 +char id=91 x=69 y=0 width=8 height=18 xoffset=-2 yoffset=3 xadvance=5 page=0 chnl=0 +char id=41 x=77 y=0 width=9 height=18 xoffset=-3 yoffset=3 xadvance=5 page=0 chnl=0 +char id=40 x=86 y=0 width=9 height=18 xoffset=-3 yoffset=3 xadvance=5 page=0 chnl=0 +char id=64 x=95 y=0 width=18 height=17 xoffset=-3 yoffset=3 xadvance=14 page=0 chnl=0 +char id=121 x=113 y=0 width=13 height=17 xoffset=-3 yoffset=6 xadvance=8 page=0 chnl=0 +char id=113 x=126 y=0 width=13 height=17 xoffset=-3 yoffset=6 xadvance=9 page=0 chnl=0 +char id=112 x=139 y=0 width=13 height=17 xoffset=-2 yoffset=6 xadvance=9 page=0 chnl=0 +char id=103 x=152 y=0 width=13 height=17 xoffset=-3 yoffset=6 xadvance=8 page=0 chnl=0 +char id=38 x=165 y=0 width=16 height=16 xoffset=-3 yoffset=3 xadvance=11 page=0 chnl=0 +char id=37 x=181 y=0 width=18 height=16 xoffset=-3 yoffset=3 xadvance=14 page=0 chnl=0 +char id=36 x=199 y=0 width=12 height=16 xoffset=-2 yoffset=3 xadvance=9 page=0 chnl=0 +char id=63 x=211 y=0 width=11 height=16 xoffset=-3 yoffset=3 xadvance=7 page=0 chnl=0 +char id=33 x=222 y=0 width=7 height=16 xoffset=-2 yoffset=3 xadvance=4 page=0 chnl=0 +char id=48 x=229 y=0 width=13 height=16 xoffset=-3 yoffset=3 xadvance=9 page=0 chnl=0 +char id=57 x=242 y=0 width=13 height=16 xoffset=-3 yoffset=3 xadvance=9 page=0 chnl=0 +char id=56 x=0 y=20 width=13 height=16 xoffset=-3 yoffset=3 xadvance=9 page=0 chnl=0 +char id=54 x=13 y=20 width=13 height=16 xoffset=-3 yoffset=3 xadvance=9 page=0 chnl=0 +char id=53 x=26 y=20 width=12 height=16 xoffset=-2 yoffset=3 xadvance=9 page=0 chnl=0 +char id=51 x=38 y=20 width=13 height=16 xoffset=-3 yoffset=3 xadvance=9 page=0 chnl=0 +char id=100 x=51 y=20 width=13 height=16 xoffset=-3 yoffset=3 xadvance=9 page=0 chnl=0 +char id=98 x=64 y=20 width=13 height=16 xoffset=-2 yoffset=3 xadvance=9 page=0 chnl=0 +char id=85 x=77 y=20 width=14 height=16 xoffset=-2 yoffset=3 xadvance=11 page=0 chnl=0 +char id=83 x=91 y=20 width=13 height=16 xoffset=-3 yoffset=3 xadvance=8 page=0 chnl=0 +char id=79 x=104 y=20 width=15 height=16 xoffset=-2 yoffset=3 xadvance=12 page=0 chnl=0 +char id=71 x=119 y=20 width=14 height=16 xoffset=-2 yoffset=3 xadvance=11 page=0 chnl=0 +char id=67 x=133 y=20 width=13 height=16 xoffset=-2 yoffset=3 xadvance=10 page=0 chnl=0 +char id=127 x=146 y=20 width=12 height=15 xoffset=-2 yoffset=3 xadvance=10 page=0 chnl=0 +char id=35 x=158 y=20 width=15 height=15 xoffset=-3 yoffset=3 xadvance=10 page=0 chnl=0 +char id=92 x=173 y=20 width=11 height=15 xoffset=-3 yoffset=3 xadvance=6 page=0 chnl=0 +char id=47 x=184 y=20 width=11 height=15 xoffset=-3 yoffset=3 xadvance=6 page=0 chnl=0 +char id=59 x=195 y=20 width=8 height=15 xoffset=-3 yoffset=6 xadvance=4 page=0 chnl=0 +char id=55 x=203 y=20 width=13 height=15 xoffset=-3 yoffset=3 xadvance=9 page=0 chnl=0 +char id=52 x=216 y=20 width=14 height=15 xoffset=-3 yoffset=3 xadvance=9 page=0 chnl=0 +char id=50 x=230 y=20 width=13 height=15 xoffset=-3 yoffset=3 xadvance=9 page=0 chnl=0 +char id=49 x=243 y=20 width=9 height=15 xoffset=-2 yoffset=3 xadvance=9 page=0 chnl=0 +char id=116 x=0 y=36 width=10 height=15 xoffset=-3 yoffset=4 xadvance=5 page=0 chnl=0 +char id=108 x=10 y=36 width=6 height=15 xoffset=-2 yoffset=3 xadvance=4 page=0 chnl=0 +char id=107 x=16 y=36 width=12 height=15 xoffset=-2 yoffset=3 xadvance=8 page=0 chnl=0 +char id=105 x=28 y=36 width=7 height=15 xoffset=-2 yoffset=3 xadvance=4 page=0 chnl=0 +char id=104 x=35 y=36 width=12 height=15 xoffset=-2 yoffset=3 xadvance=10 page=0 chnl=0 +char id=102 x=47 y=36 width=11 height=15 xoffset=-3 yoffset=3 xadvance=5 page=0 chnl=0 +char id=90 x=58 y=36 width=13 height=15 xoffset=-3 yoffset=3 xadvance=9 page=0 chnl=0 +char id=89 x=71 y=36 width=13 height=15 xoffset=-3 yoffset=3 xadvance=8 page=0 chnl=0 +char id=88 x=84 y=36 width=14 height=15 xoffset=-3 yoffset=3 xadvance=9 page=0 chnl=0 +char id=87 x=98 y=36 width=19 height=15 xoffset=-3 yoffset=3 xadvance=15 page=0 chnl=0 +char id=86 x=117 y=36 width=14 height=15 xoffset=-3 yoffset=3 xadvance=9 page=0 chnl=0 +char id=84 x=131 y=36 width=13 height=15 xoffset=-3 yoffset=3 xadvance=8 page=0 chnl=0 +char id=82 x=144 y=36 width=13 height=15 xoffset=-2 yoffset=3 xadvance=10 page=0 chnl=0 +char id=80 x=157 y=36 width=12 height=15 xoffset=-2 yoffset=3 xadvance=9 page=0 chnl=0 +char id=78 x=169 y=36 width=14 height=15 xoffset=-2 yoffset=3 xadvance=12 page=0 chnl=0 +char id=77 x=183 y=36 width=17 height=15 xoffset=-2 yoffset=3 xadvance=14 page=0 chnl=0 +char id=76 x=200 y=36 width=11 height=15 xoffset=-2 yoffset=3 xadvance=8 page=0 chnl=0 +char id=75 x=211 y=36 width=13 height=15 xoffset=-2 yoffset=3 xadvance=9 page=0 chnl=0 +char id=73 x=224 y=36 width=10 height=15 xoffset=-3 yoffset=3 xadvance=5 page=0 chnl=0 +char id=72 x=234 y=36 width=14 height=15 xoffset=-2 yoffset=3 xadvance=11 page=0 chnl=0 +char id=70 x=0 y=51 width=11 height=15 xoffset=-2 yoffset=3 xadvance=8 page=0 chnl=0 +char id=69 x=11 y=51 width=11 height=15 xoffset=-2 yoffset=3 xadvance=8 page=0 chnl=0 +char id=68 x=22 y=51 width=14 height=15 xoffset=-2 yoffset=3 xadvance=11 page=0 chnl=0 +char id=66 x=36 y=51 width=13 height=15 xoffset=-2 yoffset=3 xadvance=10 page=0 chnl=0 +char id=65 x=49 y=51 width=15 height=15 xoffset=-3 yoffset=3 xadvance=10 page=0 chnl=0 +char id=58 x=64 y=51 width=7 height=13 xoffset=-2 yoffset=6 xadvance=4 page=0 chnl=0 +char id=117 x=71 y=51 width=12 height=13 xoffset=-2 yoffset=6 xadvance=10 page=0 chnl=0 +char id=115 x=83 y=51 width=11 height=13 xoffset=-3 yoffset=6 xadvance=7 page=0 chnl=0 +char id=111 x=94 y=51 width=13 height=13 xoffset=-3 yoffset=6 xadvance=9 page=0 chnl=0 +char id=101 x=107 y=51 width=13 height=13 xoffset=-3 yoffset=6 xadvance=9 page=0 chnl=0 +char id=99 x=120 y=51 width=12 height=13 xoffset=-3 yoffset=6 xadvance=7 page=0 chnl=0 +char id=97 x=132 y=51 width=12 height=13 xoffset=-3 yoffset=6 xadvance=9 page=0 chnl=0 +char id=60 x=144 y=51 width=13 height=12 xoffset=-3 yoffset=5 xadvance=9 page=0 chnl=0 +char id=122 x=157 y=51 width=11 height=12 xoffset=-3 yoffset=6 xadvance=7 page=0 chnl=0 +char id=120 x=168 y=51 width=13 height=12 xoffset=-3 yoffset=6 xadvance=8 page=0 chnl=0 +char id=119 x=181 y=51 width=17 height=12 xoffset=-3 yoffset=6 xadvance=12 page=0 chnl=0 +char id=118 x=198 y=51 width=13 height=12 xoffset=-3 yoffset=6 xadvance=8 page=0 chnl=0 +char id=114 x=211 y=51 width=10 height=12 xoffset=-2 yoffset=6 xadvance=6 page=0 chnl=0 +char id=110 x=221 y=51 width=12 height=12 xoffset=-2 yoffset=6 xadvance=10 page=0 chnl=0 +char id=109 x=233 y=51 width=17 height=12 xoffset=-2 yoffset=6 xadvance=15 page=0 chnl=0 +char id=94 x=0 y=66 width=13 height=11 xoffset=-3 yoffset=3 xadvance=9 page=0 chnl=0 +char id=62 x=13 y=66 width=13 height=11 xoffset=-3 yoffset=5 xadvance=9 page=0 chnl=0 +char id=42 x=26 y=66 width=13 height=10 xoffset=-3 yoffset=3 xadvance=9 page=0 chnl=0 +char id=43 x=39 y=66 width=13 height=10 xoffset=-3 yoffset=6 xadvance=9 page=0 chnl=0 +char id=61 x=52 y=66 width=13 height=8 xoffset=-3 yoffset=7 xadvance=9 page=0 chnl=0 +char id=39 x=65 y=66 width=6 height=8 xoffset=-2 yoffset=3 xadvance=3 page=0 chnl=0 +char id=34 x=71 y=66 width=9 height=8 xoffset=-2 yoffset=3 xadvance=6 page=0 chnl=0 +char id=44 x=80 y=66 width=8 height=7 xoffset=-3 yoffset=14 xadvance=4 page=0 chnl=0 +char id=126 x=88 y=66 width=13 height=6 xoffset=-3 yoffset=8 xadvance=9 page=0 chnl=0 +char id=46 x=101 y=66 width=7 height=6 xoffset=-2 yoffset=13 xadvance=4 page=0 chnl=0 +char id=96 x=108 y=66 width=8 height=6 xoffset=0 yoffset=2 xadvance=9 page=0 chnl=0 +char id=45 x=116 y=66 width=9 height=5 xoffset=-3 yoffset=10 xadvance=5 page=0 chnl=0 +char id=95 x=125 y=66 width=13 height=4 xoffset=-4 yoffset=17 xadvance=6 page=0 chnl=0 +kernings count=-1 diff --git a/src/main/resources/xyz/samiker/theendlessweave/assets/ui/default.png b/src/main/resources/xyz/samiker/theendlessweave/assets/ui/default.png new file mode 100644 index 0000000..a60669c Binary files /dev/null and b/src/main/resources/xyz/samiker/theendlessweave/assets/ui/default.png differ diff --git a/src/main/resources/xyz/samiker/theendlessweave/assets/ui/uiskin.atlas b/src/main/resources/xyz/samiker/theendlessweave/assets/ui/uiskin.atlas new file mode 100644 index 0000000..d1f3db6 --- /dev/null +++ b/src/main/resources/xyz/samiker/theendlessweave/assets/ui/uiskin.atlas @@ -0,0 +1,200 @@ + +uiskin.png +size: 256,128 +format: RGBA8888 +filter: Linear,Linear +repeat: none +check-off + rotate: false + xy: 11, 5 + size: 14, 14 + orig: 14, 14 + offset: 0, 0 + index: -1 +textfield + rotate: false + xy: 11, 5 + size: 14, 14 + split: 3, 3, 3, 3 + orig: 14, 14 + offset: 0, 0 + index: -1 +check-on + rotate: false + xy: 125, 35 + size: 14, 14 + orig: 14, 14 + offset: 0, 0 + index: -1 +cursor + rotate: false + xy: 23, 1 + size: 3, 3 + split: 1, 1, 1, 1 + orig: 3, 3 + offset: 0, 0 + index: -1 +default + rotate: false + xy: 1, 50 + size: 254, 77 + orig: 254, 77 + offset: 0, 0 + index: -1 +default-pane + rotate: false + xy: 11, 1 + size: 5, 3 + split: 1, 1, 1, 1 + orig: 5, 3 + offset: 0, 0 + index: -1 +default-rect-pad + rotate: false + xy: 11, 1 + size: 5, 3 + split: 1, 1, 1, 1 + orig: 5, 3 + offset: 0, 0 + index: -1 +default-pane-noborder + rotate: false + xy: 170, 44 + size: 1, 1 + split: 0, 0, 0, 0 + orig: 1, 1 + offset: 0, 0 + index: -1 +default-rect + rotate: false + xy: 38, 25 + size: 3, 3 + split: 1, 1, 1, 1 + orig: 3, 3 + offset: 0, 0 + index: -1 +default-rect-down + rotate: false + xy: 170, 46 + size: 3, 3 + split: 1, 1, 1, 1 + orig: 3, 3 + offset: 0, 0 + index: -1 +default-round + rotate: false + xy: 112, 29 + size: 12, 20 + split: 5, 5, 5, 4 + pad: 4, 4, 1, 1 + orig: 12, 20 + offset: 0, 0 + index: -1 +default-round-down + rotate: false + xy: 99, 29 + size: 12, 20 + split: 5, 5, 5, 4 + pad: 4, 4, 1, 1 + orig: 12, 20 + offset: 0, 0 + index: -1 +default-round-large + rotate: false + xy: 57, 29 + size: 20, 20 + split: 5, 5, 5, 4 + orig: 20, 20 + offset: 0, 0 + index: -1 +default-scroll + rotate: false + xy: 78, 29 + size: 20, 20 + split: 2, 2, 2, 2 + orig: 20, 20 + offset: 0, 0 + index: -1 +default-select + rotate: false + xy: 29, 29 + size: 27, 20 + split: 4, 14, 4, 4 + orig: 27, 20 + offset: 0, 0 + index: -1 +default-select-selection + rotate: false + xy: 26, 16 + size: 3, 3 + split: 1, 1, 1, 1 + orig: 3, 3 + offset: 0, 0 + index: -1 +default-slider + rotate: false + xy: 29, 20 + size: 8, 8 + split: 2, 2, 2, 2 + orig: 8, 8 + offset: 0, 0 + index: -1 +default-slider-knob + rotate: false + xy: 1, 1 + size: 9, 18 + orig: 9, 18 + offset: 0, 0 + index: -1 +default-splitpane + rotate: false + xy: 17, 1 + size: 5, 3 + split: 0, 5, 0, 0 + orig: 5, 3 + offset: 0, 0 + index: -1 +default-splitpane-vertical + rotate: false + xy: 125, 29 + size: 3, 5 + split: 0, 0, 0, 5 + orig: 3, 5 + offset: 0, 0 + index: -1 +default-window + rotate: false + xy: 1, 20 + size: 27, 29 + split: 4, 3, 20, 3 + orig: 27, 29 + offset: 0, 0 + index: -1 +selection + rotate: false + xy: 174, 48 + size: 1, 1 + orig: 1, 1 + offset: 0, 0 + index: -1 +tree-minus + rotate: false + xy: 140, 35 + size: 14, 14 + orig: 14, 14 + offset: 0, 0 + index: -1 +tree-plus + rotate: false + xy: 155, 35 + size: 14, 14 + orig: 14, 14 + offset: 0, 0 + index: -1 +white + rotate: false + xy: 129, 31 + size: 3, 3 + orig: 3, 3 + offset: 0, 0 + index: -1 diff --git a/src/main/resources/xyz/samiker/theendlessweave/assets/ui/uiskin.json b/src/main/resources/xyz/samiker/theendlessweave/assets/ui/uiskin.json new file mode 100644 index 0000000..2030cc8 --- /dev/null +++ b/src/main/resources/xyz/samiker/theendlessweave/assets/ui/uiskin.json @@ -0,0 +1,71 @@ +{ +BitmapFont: { default-font: { file: default.fnt } }, +Color: { + green: { a: 1, b: 0, g: 1, r: 0 }, + white: { a: 1, b: 1, g: 1, r: 1 }, + red: { a: 1, b: 0, g: 0, r: 1 }, + black: { a: 1, b: 0, g: 0, r: 0 }, + gray: { a: 1, b: 0.8, g: 0.8, r: 0.8 }, +}, +TintedDrawable: { + dialogDim: { name: white, color: { r: 0, g: 0, b: 0, a: 0.45 } }, +}, +ButtonStyle: { + default: { down: default-round-down, up: default-round, disabled: default-round }, + toggle: { parent: default, checked: default-round-down } +}, +TextButtonStyle: { + default: { parent: default, font: default-font, fontColor: white, disabledFontColor: gray }, + toggle: { parent: default, checked: default-round-down, downFontColor: red } +}, +ScrollPaneStyle: { + default: { vScroll: default-scroll, hScrollKnob: default-round-large, background: default-rect, hScroll: default-scroll, vScrollKnob: default-round-large } +}, +SelectBoxStyle: { + default: { + font: default-font, fontColor: white, background: default-select, + scrollStyle: default, + listStyle: { font: default-font, selection: default-select-selection } + } +}, +SplitPaneStyle: { + default-vertical: { handle: default-splitpane-vertical }, + default-horizontal: { handle: default-splitpane } +}, +WindowStyle: { + default: { titleFont: default-font, background: default-window, titleFontColor: white }, + dialog: { parent: default, stageBackground: dialogDim } +}, +ProgressBarStyle: { + default-horizontal: { background: default-slider, knob: default-slider-knob }, + default-vertical: { background: default-slider, knob: default-round-large } +}, +SliderStyle: { + default-horizontal: { parent: default-horizontal }, + default-vertical: { parent: default-vertical } +}, +LabelStyle: { + default: { font: default-font, fontColor: white } +}, +TextFieldStyle: { + default: { selection: selection, background: textfield, font: default-font, fontColor: white, cursor: cursor } +}, +CheckBoxStyle: { + default: { checkboxOn: check-on, checkboxOff: check-off, font: default-font, fontColor: white } +}, +ListStyle: { + default: { fontColorUnselected: white, selection: selection, fontColorSelected: white, font: default-font } +}, +TouchpadStyle: { + default: { background: default-pane, knob: default-round-large } +}, +TreeStyle: { + default: { minus: tree-minus, plus: tree-plus, selection: default-select-selection } +}, +TextTooltipStyle: { + default: { + label: { font: default-font, fontColor: white }, + background: default-pane, wrapWidth: 150 + } +}, +} diff --git a/src/main/resources/xyz/samiker/theendlessweave/assets/ui/uiskin.png b/src/main/resources/xyz/samiker/theendlessweave/assets/ui/uiskin.png new file mode 100644 index 0000000..c1e5f1a Binary files /dev/null and b/src/main/resources/xyz/samiker/theendlessweave/assets/ui/uiskin.png differ diff --git a/src/main/resources/xyz/samiker/theendlessweave/css/game-scene.css b/src/main/resources/xyz/samiker/theendlessweave/css/game-scene.css deleted file mode 100644 index bc1751c..0000000 --- a/src/main/resources/xyz/samiker/theendlessweave/css/game-scene.css +++ /dev/null @@ -1,246 +0,0 @@ -/* ============================================ - ОСНОВНЫЕ СТИЛИ - ============================================ */ - -.root { - -fx-background-color: #000000; -} - -.game-pane { - -fx-background-color: #1a1a1a; -} - -/* ============================================ - ВЕРХНЯЯ ПАНЕЛЬ (HUD) - ============================================ */ - -.top-panel { - -fx-background-color: linear-gradient(to bottom, - rgba(29, 29, 29, 0.26) 0%, - rgba(19, 14, 20, 0.207) 100%); - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.8), 20, 0.5, 0, 5); - -fx-border-color: rgba(22, 22, 25, 0.3); - -fx-border-width: 0 0 2 0; -} - -.stat-container { - -fx-background-color: rgba(40, 40, 50, 0.6); - -fx-background-radius: 8; - -fx-padding: 8 15 8 15; - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.5), 10, 0.3, 0, 2); -} - -.stat-label { - -fx-text-fill: #b8b8d0; - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.8), 2, 0.5, 0, 1); -} - -.stat-value { - -fx-text-fill: #ffffff; - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.8), 2, 0.5, 0, 1); -} - -.stat-icon { - -fx-effect: dropshadow(gaussian, rgba(255, 215, 0, 0.5), 5, 0.5, 0, 0); -} - -.level-value { - -fx-text-fill: linear-gradient(to bottom, #ffd700, #ffa500); - -fx-effect: dropshadow(gaussian, rgba(255, 165, 0, 0.8), 5, 0.5, 0, 0); -} - -/* ============================================ - HEALTH BAR (Прогресс бар здоровья) - ============================================ */ - -.health-bar { - -fx-accent: #ff4444; -} - -.health-bar .bar { - -fx-background-color: linear-gradient(to right, - #ff0000 0%, - #ff4444 50%, - #ff6666 100%); - -fx-background-insets: 0; - -fx-background-radius: 4; - -fx-effect: dropshadow(gaussian, rgba(255, 68, 68, 0.6), 8, 0.5, 0, 0); -} - -.health-bar.hp-high > .bar { - -fx-accent: #44ff44; -} -.health-bar.hp-medium > .bar { - -fx-accent: #ffaa00; -} -.health-bar.hp-low > .bar { - -fx-accent: #ff4444; -} - -.health-bar .track { - -fx-background-color: rgba(100, 20, 20, 0.5); - -fx-background-insets: 0; - -fx-background-radius: 4; - -fx-border-color: rgba(255, 68, 68, 0.3); - -fx-border-width: 1; - -fx-border-radius: 4; -} - -/* ============================================ - НИЖНЯЯ ПАНЕЛЬ (Способности и инвентарь) - ============================================ */ - -.bottom-panel { - -fx-background-color: linear-gradient(to bottom, - rgba(29, 29, 29, 0.26) 0%, - rgba(19, 14, 20, 0.207) 100%); - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.8), 20, 0.5, 0, 5); - -fx-border-color: rgba(22, 22, 25, 0.3); - -fx-border-width: 0 0 2 0; -} - -/* ============================================ - СЛОТЫ СПОСОБНОСТЕЙ - ============================================ */ - -.ability-bar { - -fx-background-color: rgba(30, 30, 40, 0.6); - -fx-background-radius: 10; - -fx-padding: 8; - -fx-effect: innershadow(gaussian, rgba(0, 0, 0, 0.7), 10, 0.3, 0, 2); -} - -.ability-slot { - -fx-min-width: 64; - -fx-min-height: 64; - -fx-max-width: 64; - -fx-max-height: 64; - -fx-background-color: linear-gradient(to bottom, - rgba(50, 50, 70, 0.9), - rgba(30, 30, 50, 0.9)); - -fx-background-radius: 8; - -fx-border-color: linear-gradient(to bottom, - rgba(100, 100, 150, 0.5), - rgba(60, 60, 100, 0.5)); - -fx-border-width: 2; - -fx-border-radius: 8; - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.6), 8, 0.4, 0, 3); -} - -.ability-slot:hover { - -fx-background-color: linear-gradient(to bottom, - rgba(70, 70, 100, 0.9), - rgba(50, 50, 80, 0.9)); - -fx-border-color: rgba(150, 150, 200, 0.7); - -fx-effect: dropshadow(gaussian, rgba(100, 100, 200, 0.8), 12, 0.6, 0, 0); - -fx-cursor: hand; -} - -.ability-key { - -fx-text-fill: #aaaacc; - -fx-font-size: 14; - -fx-font-weight: bold; - -fx-translate-x: 4; - -fx-translate-y: 4; - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.8), 2, 0.5, 0, 1); -} - -/* ============================================ - СЛОТЫ ИНВЕНТАРЯ - ============================================ */ - -.inventory-bar { - -fx-background-color: rgba(30, 30, 40, 0.6); - -fx-background-radius: 10; - -fx-padding: 8; - -fx-effect: innershadow(gaussian, rgba(0, 0, 0, 0.7), 10, 0.3, 0, 2); -} - -.inventory-slot { - -fx-min-width: 56; - -fx-min-height: 56; - -fx-max-width: 56; - -fx-max-height: 56; - -fx-background-color: linear-gradient(to bottom, - rgba(40, 40, 50, 0.9), - rgba(25, 25, 35, 0.9)); - -fx-background-radius: 6; - -fx-border-color: rgba(80, 80, 100, 0.5); - -fx-border-width: 2; - -fx-border-radius: 6; - -fx-effect: innershadow(gaussian, rgba(0, 0, 0, 0.8), 5, 0.5, 0, 2); -} - -.inventory-slot:hover { - -fx-background-color: linear-gradient(to bottom, - rgba(60, 60, 80, 0.9), - rgba(40, 40, 60, 0.9)); - -fx-border-color: rgba(150, 150, 180, 0.7); - -fx-cursor: hand; -} - -.inventory-key { - -fx-text-fill: #888899; - -fx-font-size: 12; - -fx-font-weight: bold; - -fx-translate-x: 4; - -fx-translate-y: 4; - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.8), 2, 0.5, 0, 1); -} - -/* ============================================ - ЭФФЕКТЫ ДЛЯ АКТИВНЫХ СПОСОБНОСТЕЙ - ============================================ */ - -.ability-slot.active { - -fx-background-color: linear-gradient(to bottom, - rgba(100, 100, 150, 0.9), - rgba(60, 60, 120, 0.9)); - -fx-border-color: rgba(150, 200, 255, 1.0); - -fx-effect: dropshadow(gaussian, rgba(150, 200, 255, 1.0), 15, 0.8, 0, 0); -} - -.ability-slot.cooldown { - -fx-opacity: 0.4; -} - -/* ============================================ - АНИМАЦИИ (через JavaFX transitions) - ============================================ */ - -.ability-slot.pulsing { - -fx-effect: dropshadow(gaussian, rgba(255, 200, 100, 0.8), 20, 0.8, 0, 0); -} - - -.mobile-attack-btn { - -fx-background-color: rgba(255, 68, 68, 0.6); - -fx-text-fill: white; - -fx-font-size: 24px; - -fx-font-weight: bold; - -fx-background-radius: 50%; - -fx-min-width: 80px; - -fx-min-height: 80px; - -fx-max-width: 80px; - -fx-max-height: 80px; - -fx-border-color: rgba(255, 255, 255, 0.5); - -fx-border-radius: 50%; - -fx-border-width: 2px; -} - -.mobile-attack-btn:pressed { - -fx-background-color: rgba(255, 0, 0, 0.8); - -fx-scale-x: 0.95; - -fx-scale-y: 0.95; -} - -.joystick-base { - -fx-fill: rgba(0, 0, 0, 0.3); - -fx-stroke: rgba(255, 255, 255, 0.4); - -fx-stroke-width: 2px; -} - -.joystick-knob { - -fx-fill: rgba(255, 255, 255, 0.8); - -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.5), 5, 0, 0, 0); -} \ No newline at end of file diff --git a/src/main/resources/xyz/samiker/theendlessweave/css/generation-scene.css b/src/main/resources/xyz/samiker/theendlessweave/css/generation-scene.css deleted file mode 100644 index 77ccece..0000000 --- a/src/main/resources/xyz/samiker/theendlessweave/css/generation-scene.css +++ /dev/null @@ -1,510 +0,0 @@ -.root-container { - /* Почти черный фон вместо синего */ - -fx-background-color: #050505; - -fx-padding: 20; -} - -.header-container { - /* Очень темный серый */ - -fx-background-color: #111111; - -fx-padding: 20; - -fx-background-radius: 10; - /* Тень более строгая, черная */ - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.8), 15, 0, 0, 4); - -fx-border-color: #222222; - -fx-border-width: 1; - -fx-border-radius: 10; -} - -.header-title { - -fx-font-size: 32px; - -fx-font-weight: bold; - -fx-fill: #e0e0e0; - -fx-effect: dropshadow(gaussian, rgba(183, 28, 28, 0.3), 10, 0, 0, 0); -} - -/* ============================================ */ -/* ВКЛАДКИ (Исправлено) */ -/* ============================================ */ - -/* 1. Сам контейнер TabPane */ -.main-tab-pane { - -fx-background-color: transparent; -} - -.main-tab-pane .tab-header-area { - -fx-background-color: #111111; - -fx-padding: 5 5 0 5; -} - -.main-tab-pane .tab-header-background { - -fx-background-color: #111111; -} - -/* 4. Сама вкладка (неактивная) */ -.main-tab-pane .tab { - -fx-background-color: #1a1a1a; - -fx-background-radius: 5 5 0 0; - -fx-background-insets: 0 1 0 0; - -fx-padding: 8 20; - -fx-border-color: #333333; - -fx-border-width: 1 1 0 1; - -fx-border-radius: 5 5 0 0; -} - -.main-tab-pane .tab:selected { - -fx-background-color: #111111; - -fx-border-color: #b71c1c; - -fx-border-width: 2 0 0 0; - -fx-background-insets: 0; -} - -.main-tab-pane .tab .tab-label { - -fx-text-fill: #808080; - -fx-font-size: 14px; - -fx-font-weight: bold; -} - -.main-tab-pane .tab:selected .tab-label { - -fx-text-fill: #ffffff; -} - -.main-tab-pane .tab-content-area { - -fx-background-color: #111111; - -fx-border-color: #333333; - -fx-border-width: 0 1 1 1; - -fx-padding: 0; -} - -/* ============================================ */ -/* СЕКЦИИ */ -/* ============================================ */ - -.section { - -fx-background-color: #161616; - -fx-padding: 20; - -fx-background-radius: 8; - -fx-border-color: #252525; - -fx-border-width: 1; - -fx-border-radius: 8; - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.5), 10, 0, 0, 2); -} - -.section-label { - -fx-font-size: 18px; - -fx-font-weight: bold; - -fx-text-fill: #ef5350; -} - -.section-hint { - -fx-fill: #666666; - -fx-font-size: 12px; - -fx-font-style: italic; -} - -.detailed-section { - -fx-background-color: #161616; - -fx-padding: 15; - -fx-background-radius: 8; - -fx-border-color: #252525; - -fx-border-width: 1; -} - -.detailed-section-label { - -fx-font-size: 16px; - -fx-font-weight: bold; - -fx-text-fill: #ef5350; -} - -/* ============================================ */ -/* COMBOBOX */ -/* ============================================ */ - -.preset-combo { - -fx-background-color: #000000; - -fx-text-fill: #ffffff; - -fx-font-size: 14px; - -fx-pref-height: 40; - -fx-background-radius: 5; - /* Темно-серый бордюр вместо яркого */ - -fx-border-color: #424242; - -fx-border-width: 1; - -fx-border-radius: 5; -} - -.preset-combo:hover { - -fx-border-color: #757575; -} - -.preset-combo .list-cell { - -fx-background-color: #1a1a1a; - -fx-text-fill: #b0b0b0; -} - -.preset-combo .list-cell:hover { - -fx-background-color: #333333; - -fx-text-fill: #ffffff; -} - -.preset-combo .list-cell:selected { - -fx-background-color: #b71c1c; - -fx-text-fill: #ffffff; -} - -/* ============================================ */ -/* КНОПКИ СЛОЖНОСТИ */ -/* ============================================ */ - -.difficulty-button { - -fx-background-color: #1a1a1a; - -fx-text-fill: #808080; - -fx-font-size: 14px; - -fx-font-weight: bold; - -fx-pref-width: 130; - -fx-pref-height: 40; - -fx-background-radius: 5; - -fx-border-width: 1; - -fx-border-radius: 5; - -fx-cursor: hand; -} - -.difficulty-easy { - -fx-border-color: #2e7d32; /* Темно-зеленый */ -} - -.difficulty-easy:selected { - -fx-background-color: #1b5e20; - -fx-text-fill: #e8f5e9; - -fx-border-color: #4caf50; -} - -.difficulty-normal { - -fx-border-color: #f9a825; /* Темно-желтый */ -} - -.difficulty-normal:selected { - -fx-background-color: #e65100; - -fx-text-fill: #fff3e0; - -fx-border-color: #ff9800; -} - -.difficulty-hard { - -fx-border-color: #c62828; /* Темно-красный */ -} - -.difficulty-hard:selected { - -fx-background-color: #b71c1c; - -fx-text-fill: #ffebee; - -fx-border-color: #ff5252; -} - -.difficulty-button:hover { - -fx-background-color: #252525; -} - -.difficulty-button:selected:hover { - -fx-effect: dropshadow(gaussian, rgba(255, 255, 255, 0.1), 5, 0, 0, 0); -} - -/* ============================================ */ -/* КНОПКИ РЕЖИМА ИГРЫ */ -/* ============================================ */ - -.mode-button { - -fx-background-color: #1a1a1a; - -fx-text-fill: #a0a0a0; - -fx-font-size: 14px; - -fx-font-weight: bold; - -fx-pref-width: 150; - -fx-pref-height: 40; - -fx-background-radius: 5; - -fx-border-color: #424242; - -fx-border-width: 1; - -fx-border-radius: 5; - -fx-cursor: hand; -} - -.mode-button:selected { - -fx-background-color: #b71c1c; /* Активный красный */ - -fx-text-fill: #ffffff; - -fx-border-color: #d32f2f; -} - -.mode-button:disabled { - -fx-opacity: 0.3; - -fx-cursor: default; - -fx-background-color: #0f0f0f; -} - -.mode-button:hover { - -fx-border-color: #ef5350; -} - -.mode-description { - -fx-fill: #cccccc; - -fx-font-size: 12px; -} - -.mode-description-disabled { - -fx-fill: #444444; - -fx-font-size: 12px; - -fx-font-style: italic; -} - -/* ============================================ */ -/* СЛАЙДЕРЫ */ -/* ============================================ */ - -.slider { - -fx-pref-width: 300; -} - -.slider .track { - -fx-background-color: #2c2c2c; /* Темный трек */ - -fx-background-radius: 3; -} - -.slider .thumb { - -fx-background-color: #b71c1c; /* Красный ползунок */ - -fx-background-radius: 10; - -fx-pref-width: 20; - -fx-pref-height: 20; - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.5), 3, 0, 0, 1); -} - -.slider .thumb:hover { - -fx-background-color: #d32f2f; - -fx-effect: dropshadow(gaussian, rgba(211, 47, 47, 0.4), 8, 0, 0, 0); -} - -.map-size-slider, -.intensity-slider, -.spawn-slider { - -fx-pref-width: 250; -} - -/* ============================================ */ -/* SPINNERS */ -/* ============================================ */ - -.spinner { - -fx-background-color: #000000; - -fx-background-radius: 5; - -fx-border-color: #333333; - -fx-border-width: 1; - -fx-border-radius: 5; -} - -.spinner .text-field { - -fx-background-color: #000000; - -fx-text-fill: #ffffff; - -fx-font-size: 14px; - -fx-background-radius: 4 0 0 4; -} - -.spinner .increment-arrow-button, -.spinner .decrement-arrow-button { - -fx-background-color: #1a1a1a; - -fx-background-radius: 0; -} - -.spinner .increment-arrow-button:hover, -.spinner .decrement-arrow-button:hover { - -fx-background-color: #b71c1c; -} - -.spinner .increment-arrow-button .increment-arrow, -.spinner .decrement-arrow-button .decrement-arrow { - -fx-background-color: #ffffff; -} - -.detail-spinner { - -fx-pref-width: 120; -} - -/* ============================================ */ -/* CHECKBOX */ -/* ============================================ */ - -.check-box { - -fx-text-fill: #cccccc; - -fx-font-size: 13px; -} - -.check-box .box { - -fx-background-color: #000000; - -fx-border-color: #555555; - -fx-border-width: 1; - -fx-border-radius: 3; - -fx-background-radius: 3; -} - -.check-box:selected .mark { - -fx-background-color: #ef5350; /* Красная галочка */ -} - -.check-box:hover .box { - -fx-border-color: #ef5350; -} - -.detail-checkbox { - -fx-font-size: 13px; -} - -/* ============================================ */ -/* TEXTFIELD */ -/* ============================================ */ - -.text-field { - -fx-background-color: #000000; - -fx-text-fill: #ffffff; - -fx-font-size: 14px; - -fx-background-radius: 5; - -fx-border-color: #333333; - -fx-border-width: 1; - -fx-border-radius: 5; - -fx-padding: 8; -} - -.text-field:focused { - -fx-border-color: #ef5350; - -fx-effect: dropshadow(gaussian, rgba(239, 83, 80, 0.2), 8, 0, 0, 0); -} - -.seed-textfield { - -fx-pref-width: 300; -} - -.seed-hint { - -fx-fill: #666666; - -fx-font-size: 11px; - -fx-font-style: italic; -} - -/* ============================================ */ -/* LABELS */ -/* ============================================ */ - -.label { - -fx-text-fill: #e0e0e0; - -fx-font-size: 13px; -} - -.quick-label { - -fx-font-weight: bold; - -fx-text-fill: #ffffff; -} - -.quick-value-label, -.slider-value-label { - -fx-font-weight: bold; - -fx-text-fill: #ef5350; - -fx-font-size: 14px; - -fx-min-width: 50; -} - -/* ============================================ */ -/* КНОПКИ */ -/* ============================================ */ - -.button { - -fx-background-radius: 5; - -fx-font-size: 14px; - -fx-font-weight: bold; - -fx-cursor: hand; - -fx-padding: 10 20; -} - -.start-button { - /* Градиент от темно-красного к красному */ - -fx-background-color: linear-gradient(to bottom, #d32f2f, #b71c1c); - -fx-text-fill: #ffffff; - -fx-pref-height: 50; - -fx-font-size: 16px; - -fx-border-color: #b71c1c; - -fx-border-width: 1; - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.6), 10, 0, 0, 2); -} - -.start-button:hover { - -fx-background-color: linear-gradient(to bottom, #ef5350, #c62828); - -fx-effect: dropshadow(gaussian, rgba(211, 47, 47, 0.4), 15, 0, 0, 1); -} - -.start-button:pressed { - -fx-background-color: #8e0000; - -fx-translate-y: 2; -} - -.reset-button { - -fx-background-color: transparent; - -fx-text-fill: #b0b0b0; - -fx-border-color: #424242; - -fx-border-width: 1; - -fx-border-radius: 5; -} - -.reset-button:hover { - -fx-background-color: #1a1a1a; - -fx-text-fill: #ffffff; - -fx-border-color: #757575; -} - -.random-seed-button { - -fx-background-color: #212121; - -fx-text-fill: #ffffff; - -fx-border-color: #424242; - -fx-border-width: 1; -} - -.random-seed-button:hover { - -fx-background-color: #333333; - -fx-border-color: #666666; -} - -/* ============================================ */ -/* SCROLLPANE */ -/* ============================================ */ - -.scroll-pane { - -fx-background-color: transparent; - -fx-background: transparent; -} - -.scroll-pane .viewport { - -fx-background-color: transparent; -} - -.scroll-pane .scroll-bar:vertical { - -fx-background-color: #0f0f0f; - -fx-border-color: #222222; - -fx-border-width: 0 0 0 1; -} - -.scroll-pane .scroll-bar:vertical .thumb { - -fx-background-color: #333333; - -fx-background-radius: 5; -} - -.scroll-pane .scroll-bar:vertical .thumb:hover { - -fx-background-color: #b71c1c; -} - -.detailed-scroll { - -fx-fit-to-width: true; -} - -/* ============================================ */ -/* ДОПОЛНИТЕЛЬНО */ -/* ============================================ */ - -.button-container { - -fx-padding: 20 0 0 0; -} - -/* Hover эффекты */ -.spinner:hover, -.text-field:hover, -.check-box:hover { - -fx-cursor: hand; -} \ No newline at end of file diff --git a/src/main/resources/xyz/samiker/theendlessweave/css/loading-screen.css b/src/main/resources/xyz/samiker/theendlessweave/css/loading-screen.css deleted file mode 100644 index 1e87db0..0000000 --- a/src/main/resources/xyz/samiker/theendlessweave/css/loading-screen.css +++ /dev/null @@ -1,23 +0,0 @@ -.root { - -fx-background-color: #1a1a1a; -} - -.progress-bar { - -fx-accent: #007bff; -} - -.log-text-area { - -fx-background-color: rgba(0, 0, 0, 0.4); - -fx-border-color: #333333; - -fx-border-width: 1px; -} - -.log-text-area .content { - -fx-background-color: transparent; -} - -.log-text-area .text { - -fx-fill: #99ff99; /* Светло-зеленый текст логов */ - -fx-font-family: "Consolas", monospace; - -fx-font-size: 11px; -} \ No newline at end of file diff --git a/src/main/resources/xyz/samiker/theendlessweave/css/main-menu.css b/src/main/resources/xyz/samiker/theendlessweave/css/main-menu.css deleted file mode 100644 index 595a458..0000000 --- a/src/main/resources/xyz/samiker/theendlessweave/css/main-menu.css +++ /dev/null @@ -1,30 +0,0 @@ -.root { - -fx-background-color: #1a1a1a; -} - -.menu-container { - -fx-min-width: 20%; - -fx-max-width: 400px; - -fx-pref-width: 30%; -} - -.menu-button { - -fx-background-color: #272348; - -fx-text-fill: #e6e6e6; - -fx-font-size: 1.2em; - -fx-font-weight: bold; - -fx-padding: 1em 2em; - -fx-background-radius: 0.5em; - -fx-cursor: hand; -} - -.menu-button:hover { - -fx-background-color: #1c1a32; - -fx-scale-x: 1.05; - -fx-scale-y: 1.05; -} - -.menu-button:pressed { - -fx-scale-x: 0.95; - -fx-scale-y: 0.95; -} \ No newline at end of file diff --git a/src/main/resources/xyz/samiker/theendlessweave/css/settings.css b/src/main/resources/xyz/samiker/theendlessweave/css/settings.css deleted file mode 100644 index d359fed..0000000 --- a/src/main/resources/xyz/samiker/theendlessweave/css/settings.css +++ /dev/null @@ -1,110 +0,0 @@ -.root { - -fx-background-color: #1a1a1a; - -fx-font-family: "Segoe UI", sans-serif; -} - -/* --- TabPane Styling --- */ -.tab-pane .tab-header-area .tab-header-background { - -fx-background-color: #252525; -} - -.tab-pane .tab { - -fx-background-color: #333333; - -fx-text-fill: #aaaaaa; - -fx-padding: 10 20; -} - -.tab-pane .tab:selected { - -fx-background-color: #1a1a1a; /* Цвет фона сцены, чтобы сливалось */ - -fx-border-color: #0078d7 transparent transparent transparent; /* Акцент сверху */ - -fx-border-width: 2; -} - -.tab-pane .tab:selected .tab-label { - -fx-text-fill: white; -} - -.tab-pane .tab .tab-label { - -fx-text-fill: #bbbbbb; - -fx-font-size: 14px; -} - -/* --- Controls Styling --- */ -.label { - -fx-text-fill: white; - -fx-font-size: 14px; -} - -.header-label { - -fx-font-size: 18px; - -fx-font-weight: bold; - -fx-text-fill: #0078d7; - -fx-padding: 0 0 10 0; -} - -.button { - -fx-background-color: #333333; - -fx-text-fill: white; - -fx-border-color: #555555; - -fx-border-radius: 3; - -fx-background-radius: 3; - -fx-cursor: hand; -} - -.button:hover { - -fx-background-color: #444444; - -fx-border-color: #0078d7; -} - -.button:pressed { - -fx-background-color: #0078d7; -} - -.check-box .box { - -fx-background-color: #333333; - -fx-border-color: #555555; -} - -.check-box:selected .mark { - -fx-background-color: white; -} - -.check-box .text { - -fx-fill: white; -} - -.combo-box { - -fx-background-color: #333333; - -fx-text-fill: white; - -fx-border-color: #555555; -} - -.combo-box .cell { - -fx-text-fill: white; -} - -.combo-box-popup .list-view { - -fx-background-color: #252525; - -fx-text-fill: white; -} - -.combo-box-popup .list-cell { - -fx-text-fill: white; -} - -.combo-box-popup .list-cell:hover { - -fx-background-color: #444444; -} - -.text-field { - -fx-background-color: #333333; - -fx-text-fill: white; - -fx-border-color: #555555; -} - -.slider .track { - -fx-background-color: #333333; -} -.slider .thumb { - -fx-background-color: #0078d7; -} \ No newline at end of file diff --git a/src/main/resources/xyz/samiker/theendlessweave/fxml/game-scene.fxml b/src/main/resources/xyz/samiker/theendlessweave/fxml/game-scene.fxml deleted file mode 100644 index bac516b..0000000 --- a/src/main/resources/xyz/samiker/theendlessweave/fxml/game-scene.fxml +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/xyz/samiker/theendlessweave/fxml/main-menu.fxml b/src/main/resources/xyz/samiker/theendlessweave/fxml/main-menu.fxml deleted file mode 100644 index e7ba140..0000000 --- a/src/main/resources/xyz/samiker/theendlessweave/fxml/main-menu.fxml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - -
- - - - - - - -
-
\ No newline at end of file diff --git a/src/main/resources/xyz/samiker/theendlessweave/fxml/settings.fxml b/src/main/resources/xyz/samiker/theendlessweave/fxml/settings.fxml deleted file mode 100644 index bf407d6..0000000 --- a/src/main/resources/xyz/samiker/theendlessweave/fxml/settings.fxml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - -
- -
- - - -