From 01f558dfd2185bb51af6d4042aad4f763987cc0d Mon Sep 17 00:00:00 2001 From: Samiker <168203446+Samiker69@users.noreply.github.com> Date: Sun, 1 Feb 2026 18:38:15 +0300 Subject: [PATCH] =?UTF-8?q?Feat=20(Client):=20=D0=98=D0=BD=D1=82=D0=B5?= =?UTF-8?q?=D0=B3=D1=80=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=D0=BD=D0=B8=D0=BC=D0=B0=D0=B5=D1=82=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D1=84=D0=B8=D0=B3=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8.=20-=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20?= =?UTF-8?q?=D0=B0=D0=B2=D1=82=D0=BE=D0=BC=D0=B0=D1=82=D0=B8=D1=87=D0=B5?= =?UTF-8?q?=D1=81=D0=BA=D0=B8=20=D0=BE=D0=BF=D1=80=D0=B5=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D1=8F=D0=B5=D1=82=D1=81=D1=8F=20=D1=82=D0=B8=D0=BF=20=D1=83?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=20=20?= =?UTF-8?q?-=20=D0=B2=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA?= =?UTF-8?q?=D0=B0=D1=85=20=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D1=82=D1=8C=20=D1=82=D0=B8=D0=BF=20=D1=83?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20-=20?= =?UTF-8?q?=D0=B5=D1=81=D0=BB=D0=B8=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BF=D1=83=D1=89=D0=B5=D0=BD=20=D1=81=20=D1=84?= =?UTF-8?q?=D0=BB=D0=B0=D0=B3=D0=BE=D0=BC=20`--integrated`,=20=D1=82=D0=BE?= =?UTF-8?q?=20=D0=BE=D0=BD=20=D0=B1=D1=83=D0=B4=D0=B5=D1=82=20=D0=B1=D0=B5?= =?UTF-8?q?=D0=B7=20=D0=BF=D1=80=D0=B8=D1=82=D0=B5=D0=BD=D0=B7=D0=B8=D0=B9?= =?UTF-8?q?=20=D0=BF=D1=80=D0=B8=D0=BD=D0=B8=D0=BC=D0=B0=D1=82=D1=8C=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=BA=D0=B5=D1=82=D1=8B=20=D0=B8=D0=B3=D1=80=D0=BE?= =?UTF-8?q?=D0=BA=D0=B0=20(TODO)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 19 +-- .../client/ClientLauncher.java | 10 +- .../client/network/GameClient.java | 9 +- .../client/screens/GameScreen.java | 110 +++++++++++------- .../client/screens/GenerationScreen.java | 18 +-- .../client/screens/MainMenuScreen.java | 10 -- .../client/screens/SettingsScreen.java | 44 ++++++- .../client/settings/ClientSettings.java | 2 +- .../theendlessweave/core/GameData.java | 6 + .../core/gamemap/MapGenerator.java | 26 ----- .../core/gamemap/MapGeneratorSettings.java | 12 ++ .../core/network/NetworkRegister.java | 1 + .../network/packets/PacketLoginRequest.java | 33 +++++- .../core/settings/SettingsManager.java | 1 - .../server/ServerLauncher.java | 24 ++-- .../theendlessweave/server/ServerLoop.java | 2 + .../server/network/GameServer.java | 24 +++- .../server/systems/ServerDebugSystem.java | 2 +- .../systems/ServerEntityEventSystem.java | 5 +- .../systems/ServerSnapshotSenderSystem.java | 6 +- 20 files changed, 237 insertions(+), 127 deletions(-) diff --git a/README.md b/README.md index 2990430..fac1f6f 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,10 @@ java -jar filename-client.jar --server - [X] (!) клиент НЕ должен самостоятельно вызывать что-либо в Game, он должен отправлять пакет и получать подтверждение - [X] синхронизировать игровую карту (клиент отправляет параметры (GameData), сервер хавает) - [X] если сервер НЕ должен возвращать карту, то пусть сервер передаст шанс рандома, чтобы клиент сам создал карту с таким рандомом. - - [ ] Перенести соло игру на интегрированный сервер. - - клиент инициализирует и запускает локальный сервер в GameScreen - - [ ] конфиг генерации мира с клиента должен + - [X] Перенести соло игру на интегрированный сервер. + - [X] клиент инициализирует и запускает локальный сервер в GameScreen + - [X] конфиг генерации мира с клиента должен применяться на сервере + - ? Оптимизации пакетов. ктож знает какие задержки могут быть вне локального сервера. - [ ] НеЗабитьНеЗабитьНеЗабитьНеЗабитьНеЗабитьНеЗабитьНеЗабитьНеЗабитьНеЗабить - [ ] Hello LibGDX! - Javafx слишком много срёт на слабых устройствах и в целом не подходит для текущих целей. нужно переписать рендер: @@ -48,11 +49,13 @@ java -jar filename-client.jar --server - [X] `client.systems.` - [X] создать ветку и настроить `build.gradle.kts`. - Баги/проблемы (исправить) - - [ ] Из-за написанного кода под javafx, на клиенте могут возникать проблемы с логикой из-за другой системы координат в libgdx, например, при просчёте логики полёта пуль, необходимо домножать скорость на размер тайлов. - - [ ] сервер не удаляет игрока, если он отключился - - [ ] клиент рендерит других игроков как врагов - - [ ] ui не масштабируется при изменении размера окна. - + - ? Из-за написанного кода под javafx, на клиенте могут возникать проблемы с логикой из-за другой системы координат в libgdx, например, при просчёте логики полёта пуль, необходимо домножать скорость на размер тайлов. + - [X] сервер не удаляет игрока, если он отключился. + - [ ] клиент рендерит других игроков как врагов. + - [X] ui не масштабируется как надо при изменении размера окна. + - [ ] при очень высоких значениях fps плавное движение камеры менее заметно. +- [ ] Сделать возможность подключения к внешним серверам. Обезопасить интегрированный сервер от не локальных подключений. +- ! Вернуться к фазовым планам. --- ### Фаза 1: Фундамент и архитектура (done) diff --git a/src/main/java/xyz/samiker/theendlessweave/client/ClientLauncher.java b/src/main/java/xyz/samiker/theendlessweave/client/ClientLauncher.java index d3f54b4..f242532 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/ClientLauncher.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/ClientLauncher.java @@ -17,16 +17,16 @@ public static void main(String[] args) { Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); try { SettingsManager.loadSettings(); - String[] parts = SettingsManager.getString(ClientSettings.RESOLUTION).split("x"); - config.setWindowedMode(Integer.parseInt(parts[0]), Integer.parseInt(parts[1])); if (SettingsManager.getBoolean(ClientSettings.VSYNC)) { config.useVsync(SettingsManager.getBoolean(ClientSettings.VSYNC)); - } else { + } + if (SettingsManager.getBoolean(ClientSettings.FPS_LIMIT_ENABLED)) { config.setForegroundFPS(SettingsManager.getInt(ClientSettings.FPS_LIMIT)); } - config.setIdleFPS(15); + config.setResizable(false); + config.setTitle("TheEndlessWeave"); new Lwjgl3Application(new Main(), config); } catch (Throwable t) { @@ -34,7 +34,7 @@ public static void main(String[] args) { } } private static void logError(Throwable t) { - try (FileWriter fw = new FileWriter("CRASH_REPORT.txt", true)) { + try (FileWriter fw = new FileWriter("CRASH_REPORT.txt", false)) { fw.write("\n=====================\n"); fw.write("Crash Date: " + LocalDateTime.now() + "\n"); 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 c9b5bf7..bb3e9a9 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/network/GameClient.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/network/GameClient.java @@ -4,6 +4,7 @@ import com.esotericsoftware.kryonet.Client; import com.esotericsoftware.kryonet.Connection; import com.esotericsoftware.kryonet.Listener; +import xyz.samiker.theendlessweave.core.GameData; import xyz.samiker.theendlessweave.core.network.NetworkRegister; import xyz.samiker.theendlessweave.core.network.packets.PacketLoginRequest; import xyz.samiker.theendlessweave.core.network.packets.PacketLoginResponse; @@ -54,10 +55,16 @@ public void received(Connection connection, Object object) { } } }); + } + public void connect(String ip, GameData data, Consumer onJoin) throws IOException { + connect(ip, onJoin); PacketLoginRequest login = new PacketLoginRequest(); + int m = 0; + login.mask = m |= PacketLoginRequest.HAS_GAMEDATA; login.username = "Player"; - client.sendTCP(login); + login.gameData = data; + sendTCP(login); } public void disconnect() { diff --git a/src/main/java/xyz/samiker/theendlessweave/client/screens/GameScreen.java b/src/main/java/xyz/samiker/theendlessweave/client/screens/GameScreen.java index 4e1ce5e..caf18d2 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/screens/GameScreen.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/screens/GameScreen.java @@ -18,6 +18,7 @@ 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.settings.ClientSettings; import xyz.samiker.theendlessweave.client.systems.*; import xyz.samiker.theendlessweave.core.GameData; import xyz.samiker.theendlessweave.core.entities.Entity; @@ -26,7 +27,6 @@ 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.client.settings.ClientSettings; import xyz.samiker.theendlessweave.core.settings.SettingsManager; import xyz.samiker.theendlessweave.server.ServerLauncher; @@ -35,6 +35,7 @@ public class GameScreen implements Screen { private final Main mainApp; + private GameData data; private SpriteBatch batch; private OrthographicCamera gameCamera; @@ -63,6 +64,12 @@ public class GameScreen implements Screen { private InputMultiplexer inputMultiplexer; + public GameScreen(Main mainApp, GameData data) { + this.mainApp = mainApp; + this.data = data; + this.assets = mainApp.getAssetManager(); + } + public GameScreen(Main mainApp) { this.mainApp = mainApp; this.assets = mainApp.getAssetManager(); @@ -113,11 +120,8 @@ private void setupUI() { uiStage.addActor(hudTable); - isMobile = !SettingsManager.getString(ClientSettings.CONTROL).equals("touch");// || Main.isMobile(); - if (isMobile) { - setupMobileControls(); - } setupPauseMenu(); + setupMobileControls(); debugLabel = new Label("", skin); debugLabel.setPosition(10, Gdx.graphics.getHeight() / 2f); @@ -126,31 +130,44 @@ private void setupUI() { } private void setupMobileControls() { + String controlMode = SettingsManager.getString(ClientSettings.CONTROL); + if ("auto".equals(controlMode)) { + controlMode = (Gdx.app.getType() == Application.ApplicationType.Android || + Gdx.app.getType() == Application.ApplicationType.iOS) + ? "mobile_aim_joystick" : "pc"; + } + + if ("pc".equals(controlMode)) return; + Touchpad.TouchpadStyle touchpadStyle = skin.get(Touchpad.TouchpadStyle.class); moveStick = new Touchpad(10, touchpadStyle); moveStick.setBounds(50, 50, 200, 200); + uiStage.addActor(moveStick); - aimStick = new Touchpad(10, touchpadStyle); - aimStick.setBounds(Gdx.graphics.getWidth() - 250, 50, 200, 200); - attackBtn = new TextButton("ATK", skin); - attackBtn.setSize(100, 100); - attackBtn.setPosition(Gdx.graphics.getWidth() - 150, Gdx.graphics.getHeight() - 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); - } - }); + if (controlMode.equals("mobile_aim_joystick") || controlMode.equals("mobile_aim_all")) { + aimStick = new Touchpad(10, touchpadStyle); + aimStick.setBounds(Gdx.graphics.getWidth() - 250, 50, 200, 200); + uiStage.addActor(aimStick); + } - uiStage.addActor(moveStick); - uiStage.addActor(aimStick); - uiStage.addActor(attackBtn); + if (controlMode.equals("mobile_aim_button") || controlMode.equals("mobile_aim_all")) { + attackBtn = new TextButton("ATK", skin); + attackBtn.setSize(100, 100); + attackBtn.setPosition(Gdx.graphics.getWidth() - 150, controlMode.equals("mobile_aim_all") ? Gdx.graphics.getHeight() - 250 : 250); + attackBtn.addListener(new ClickListener() { + @Override + public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { + InputManager.simulateMousePress(Input.Buttons.LEFT); + return true; + } + @Override + public void touchUp(InputEvent event, float x, float y, int pointer, int button) { + InputManager.simulateMouseRelease(Input.Buttons.LEFT); + } + }); + uiStage.addActor(attackBtn); + } } private void updateUI() { @@ -181,7 +198,8 @@ public void changed(ChangeEvent event, Actor actor) { @Override public void changed(ChangeEvent event, Actor actor) { try { - if (client != null) client.disconnect(); + dispose(); + if (ServerLauncher.isIntegrated) ServerLauncher.shutdown(); mainApp.setScreen(new MainMenuScreen(mainApp)); } catch (Exception e) { e.printStackTrace(); @@ -200,19 +218,27 @@ private void connectToServer() { client = new GameClient(); new Thread(() -> { try { - String[] args = new String[]{"--integrated"}; //TODO: я что-то хотел сделать с этим. ладно + String[] args = new String[]{"--integrated"}; ServerLauncher.main(args); - while (true) { - if (ServerLauncher.isStarted) break; + + long startTime = System.currentTimeMillis(); + while (!ServerLauncher.isStarted) { + Thread.sleep(100); + if (System.currentTimeMillis() - startTime > 10000) { + throw new IOException("Server start timeout"); + } } - client.connect("127.0.0.1", (response) -> { + client.connect("127.0.0.1", data, (response) -> { Gdx.app.postRunnable(() -> initializeGameSession(response)); }); } catch (IOException e) { + ServerLauncher.shutdown(); Gdx.app.postRunnable(() -> { loadingLabel.setText("Connection failed: " + e.getMessage()); }); e.printStackTrace(); + } catch (InterruptedException e) { + throw new RuntimeException(e); } }).start(); } @@ -274,19 +300,21 @@ public void render(float delta) { gameCamera.update(); batch.setProjectionMatrix(gameCamera.combined); - if (isMobile && moveStick != null && aimStick != null) { + if (isMobile) { 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); + if (moveStick != null) { + InputManager.handleAxis(moveStick.getKnobPercentX(), deadzone, Input.Keys.D, Input.Keys.A); + InputManager.handleAxis(moveStick.getKnobPercentY(), deadzone, Input.Keys.W, Input.Keys.S); + } + if (aimStick != null) { + 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); + } } } diff --git a/src/main/java/xyz/samiker/theendlessweave/client/screens/GenerationScreen.java b/src/main/java/xyz/samiker/theendlessweave/client/screens/GenerationScreen.java index 7e27493..6e9e2c1 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/screens/GenerationScreen.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/screens/GenerationScreen.java @@ -12,6 +12,7 @@ import com.badlogic.gdx.utils.ScreenUtils; import com.badlogic.gdx.utils.viewport.ScreenViewport; import xyz.samiker.theendlessweave.Main; +import xyz.samiker.theendlessweave.core.GameData; import xyz.samiker.theendlessweave.core.gamemap.MapGeneratorSettings; import java.util.Random; @@ -111,7 +112,7 @@ public void clicked(InputEvent event, float x, float y) { content.add(new Label("Preset:", skin)).right(); presetComboBox = new SelectBox<>(skin); - presetComboBox.setItems("Default", "Diverse", "Hardcore", "Custom"); + presetComboBox.setItems("Default", "Diverse", "Open", "Hardcore", "Custom"); presetComboBox.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { @@ -142,7 +143,7 @@ public void changed(ChangeEvent event, Actor actor) { diffHard.addListener(diffListener); content.add(new Label("Map Size:", skin)).right(); - mapSizeSlider = new Slider(50, 500, 10, false, skin); + mapSizeSlider = new Slider(50, 2000, 10, false, skin); mapSizeLabel = new Label("100x100", skin); mapSizeSlider.addListener(new ChangeListener() { @Override @@ -264,6 +265,7 @@ private void loadPreset(String presetName) { MapGeneratorSettings settings = switch (presetName) { case "Diverse" -> MapGeneratorSettings.diverse(); case "Hardcore" -> MapGeneratorSettings.hardcore(); + case "Open" -> MapGeneratorSettings.openMap(); case "Custom" -> null; default -> MapGeneratorSettings.defaults(); }; @@ -339,14 +341,14 @@ private long getSeed() { } private void onStartGame() { -// MapGeneratorSettings settings = createSettingsFromUI(); -// int width = parseInt(widthField.getText(), 100); -// int height = parseInt(heightField.getText(), 100); -// long seed = getSeed(); + 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: должен отправляться запрос на сервер с данным конфигом + GameData gameData = new GameData(settings, width <= 0 ? 100 : width, height <= 0 ? 100 : height, seed); //TODO: должен отправляться запрос на сервер с данным конфигом - game.setScreen(new GameScreen(game)); + game.setScreen(new GameScreen(game, gameData)); } @Override diff --git a/src/main/java/xyz/samiker/theendlessweave/client/screens/MainMenuScreen.java b/src/main/java/xyz/samiker/theendlessweave/client/screens/MainMenuScreen.java index 6978f64..557f4b8 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/screens/MainMenuScreen.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/screens/MainMenuScreen.java @@ -34,7 +34,6 @@ public void show() { 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() { @@ -52,13 +51,6 @@ public void clicked(InputEvent event, float x, float y) { } }); - 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) { @@ -70,8 +62,6 @@ public void clicked(InputEvent event, float x, float y) { 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); } diff --git a/src/main/java/xyz/samiker/theendlessweave/client/screens/SettingsScreen.java b/src/main/java/xyz/samiker/theendlessweave/client/screens/SettingsScreen.java index 3a3485d..dc46ed7 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/screens/SettingsScreen.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/screens/SettingsScreen.java @@ -24,6 +24,7 @@ public class SettingsScreen implements Screen { private Table contentContainer; private TextButton generalTabBtn; private TextButton videoTabBtn; + private TextButton controlsTabBtn; public SettingsScreen(Main game) { this.game = game; @@ -56,12 +57,15 @@ private void buildLayout() { generalTabBtn = new TextButton("General", skin, "toggle"); videoTabBtn = new TextButton("Video", skin, "toggle"); + controlsTabBtn = new TextButton("Control", skin, "toggle"); tabsGroup.add(generalTabBtn); tabsGroup.add(videoTabBtn); + tabsGroup.add(controlsTabBtn); tabsTable.add(generalTabBtn).width(150).fill(); tabsTable.add(videoTabBtn).width(150).fill(); + tabsTable.add(controlsTabBtn).width(150).fill(); root.add(tabsTable).padBottom(20).colspan(2).row(); @@ -91,6 +95,13 @@ public void changed(ChangeEvent event, Actor actor) { if (videoTabBtn.isChecked()) showVideoSettings(); } }); + + controlsTabBtn.addListener(new ChangeListener() { + @Override + public void changed(ChangeEvent event, Actor actor) { + if (controlsTabBtn.isChecked()) showControlSettings(); + } + }); } private void showGeneralSettings() { @@ -146,11 +157,13 @@ private void showVideoSettings() { resolutionSelect.setSelected(SettingsManager.getString(ClientSettings.RESOLUTION)); table.add(resolutionSelect).width(200).row(); + boolean fscr = SettingsManager.getBoolean(ClientSettings.FULLSCREEN); + boolean brdr = SettingsManager.getBoolean(ClientSettings.BORDERLESS); CheckBox fullscreenCheck = new CheckBox(" Fullscreen", skin); - fullscreenCheck.setChecked(SettingsManager.getBoolean(ClientSettings.FULLSCREEN)); + fullscreenCheck.setChecked(fscr && !brdr); CheckBox borderlessCheck = new CheckBox(" Borderless Window", skin); - borderlessCheck.setChecked(SettingsManager.getBoolean(ClientSettings.BORDERLESS)); + borderlessCheck.setChecked(brdr && !fscr); table.add(fullscreenCheck).colspan(2).row(); table.add(borderlessCheck).colspan(2).row(); @@ -219,6 +232,33 @@ public void clicked(InputEvent event, float x, float y) { contentContainer.add(table).top(); } + private void showControlSettings() { + contentContainer.clearChildren(); + + Table table = new Table(); + table.pad(20); + table.defaults().left().pad(5); + + table.add(new Label("Control type:", skin)); + SelectBox controlSelect = new SelectBox<>(skin); + controlSelect.setItems("auto", "pc", "mobile_aim_joystick", "mobile_aim_button", "mobile_aim_all"); //TODO надо въебошить картинки + controlSelect.setSelected(SettingsManager.getString(ClientSettings.CONTROL)); + table.add(controlSelect).width(200).row(); + + TextButton applyBtn = new TextButton("Apply Control Settings", skin); + applyBtn.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + SettingsManager.set(ClientSettings.CONTROL, controlSelect.getSelected()); + SettingsManager.saveSettings(); + } + }); + + table.add(applyBtn).padTop(20).colspan(2).center(); + + contentContainer.add(table).top(); + } + private void applyGraphicsSettings() { String resStr = SettingsManager.getString(ClientSettings.RESOLUTION); String[] parts = resStr.split("x"); diff --git a/src/main/java/xyz/samiker/theendlessweave/client/settings/ClientSettings.java b/src/main/java/xyz/samiker/theendlessweave/client/settings/ClientSettings.java index 9c79a55..d07f77d 100644 --- a/src/main/java/xyz/samiker/theendlessweave/client/settings/ClientSettings.java +++ b/src/main/java/xyz/samiker/theendlessweave/client/settings/ClientSettings.java @@ -11,7 +11,7 @@ public enum ClientSettings { ANTIALIASING("video.antiAliasing", false), MODS_DIR("general.mods_dir", ""), - CONTROL("general.control_type", "auto"), + CONTROL("general.control_type", "auto"), //auto, pc, mobile_aim_joystick, mobile_aim_button, mobile_aim_all MUSIC_VOLUME("audio.music", 100), SFX_VOLUME("audio.sfx", 100); diff --git a/src/main/java/xyz/samiker/theendlessweave/core/GameData.java b/src/main/java/xyz/samiker/theendlessweave/core/GameData.java index 69efe64..b2af1cf 100644 --- a/src/main/java/xyz/samiker/theendlessweave/core/GameData.java +++ b/src/main/java/xyz/samiker/theendlessweave/core/GameData.java @@ -34,6 +34,12 @@ public static GameData defaults() { ); } + public static GameData waitingMap() { + return new GameData( + MapGeneratorSettings.waitingMap(), 20, 20, 0 + ); + } + /** * Создаёт GameData с конкретным seed. */ diff --git a/src/main/java/xyz/samiker/theendlessweave/core/gamemap/MapGenerator.java b/src/main/java/xyz/samiker/theendlessweave/core/gamemap/MapGenerator.java index c13ad65..12a639b 100644 --- a/src/main/java/xyz/samiker/theendlessweave/core/gamemap/MapGenerator.java +++ b/src/main/java/xyz/samiker/theendlessweave/core/gamemap/MapGenerator.java @@ -56,9 +56,6 @@ public GameMap generate() { spawnEnemies(rooms); - System.out.println("=== Map Generation Complete ==="); - printRoomStatistics(rooms); - return new GameMap(width, height, map, rooms); } @@ -794,29 +791,6 @@ private void placeRoomObjects(Tile[][] map, Array rooms) { } } - /** - * Выводит статистику по комнатам. - */ - private void printRoomStatistics(Array rooms) { - int[] typeCounts = new int[RoomType.values().length]; - int totalEnemies = 0; - - for (int i = 0; i < rooms.size; i++) { - Room room = rooms.get(i); - typeCounts[room.getType().ordinal()]++; - totalEnemies += room.getEnemies().size; - } - - System.out.println("Room Types:"); - for (RoomType type : RoomType.values()) { - int count = typeCounts[type.ordinal()]; - if (count > 0) { - System.out.printf(" %s: %d%n", type, count); - } - } - System.out.printf("Total Enemies: %d%n", totalEnemies); - } - public boolean isInBounds(int x, int y) { return x >= 0 && x < width && y >= 0 && y < height; } diff --git a/src/main/java/xyz/samiker/theendlessweave/core/gamemap/MapGeneratorSettings.java b/src/main/java/xyz/samiker/theendlessweave/core/gamemap/MapGeneratorSettings.java index 94858de..82670f3 100644 --- a/src/main/java/xyz/samiker/theendlessweave/core/gamemap/MapGeneratorSettings.java +++ b/src/main/java/xyz/samiker/theendlessweave/core/gamemap/MapGeneratorSettings.java @@ -153,6 +153,18 @@ public static MapGeneratorSettings openMap() { ); } + public static MapGeneratorSettings waitingMap() { + return new MapGeneratorSettings( + true, + 0, + 0, 0, 0, + false, + 0, false, + false, 0.0, + 0, 0, true + ); + } + @Override public String toString() { return "MapGeneratorSettings{" + 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 81aa1da..f72abb4 100644 --- a/src/main/java/xyz/samiker/theendlessweave/core/network/NetworkRegister.java +++ b/src/main/java/xyz/samiker/theendlessweave/core/network/NetworkRegister.java @@ -27,6 +27,7 @@ public static void register(EndPoint endPoint) { kryo.register(PacketSpawnEntity.class); kryo.register(PacketDestroyEntity.class); + // --- Data --- kryo.register(MapGeneratorSettings.class); kryo.register(GameData.class); kryo.register(PositionComponent.class); diff --git a/src/main/java/xyz/samiker/theendlessweave/core/network/packets/PacketLoginRequest.java b/src/main/java/xyz/samiker/theendlessweave/core/network/packets/PacketLoginRequest.java index 95bdb3f..b3cf5c3 100644 --- a/src/main/java/xyz/samiker/theendlessweave/core/network/packets/PacketLoginRequest.java +++ b/src/main/java/xyz/samiker/theendlessweave/core/network/packets/PacketLoginRequest.java @@ -1,6 +1,35 @@ package xyz.samiker.theendlessweave.core.network.packets; -public class PacketLoginRequest extends Packet { +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.GameData; + +public class PacketLoginRequest extends Packet implements KryoSerializable { public String username; - // Можно передать желаемый seed, но сервер решит окончательно + public GameData gameData; + + public int mask; + public static final int HAS_GAMEDATA = 1; + + @Override + public void write(Kryo kryo, Output output) { + output.writeString(username); + output.writeInt(mask); + + if ((mask & HAS_GAMEDATA) != 0) { + kryo.writeObject(output, gameData); + } + } + + @Override + public void read(Kryo kryo, Input input) { + username = input.readString(); + mask = input.readInt(); + + if ((mask & HAS_GAMEDATA) != 0) { + gameData = kryo.readObject(input, GameData.class); + } + } } \ No newline at end of file diff --git a/src/main/java/xyz/samiker/theendlessweave/core/settings/SettingsManager.java b/src/main/java/xyz/samiker/theendlessweave/core/settings/SettingsManager.java index 791ddbb..9df19ff 100644 --- a/src/main/java/xyz/samiker/theendlessweave/core/settings/SettingsManager.java +++ b/src/main/java/xyz/samiker/theendlessweave/core/settings/SettingsManager.java @@ -120,7 +120,6 @@ public static void setWidth(int width) { width_height.put("width", width); } public static void setHeight(int height) { - System.out.println(width_height); width_height.put("height", height); } diff --git a/src/main/java/xyz/samiker/theendlessweave/server/ServerLauncher.java b/src/main/java/xyz/samiker/theendlessweave/server/ServerLauncher.java index 8eeafb4..1d5c55d 100644 --- a/src/main/java/xyz/samiker/theendlessweave/server/ServerLauncher.java +++ b/src/main/java/xyz/samiker/theendlessweave/server/ServerLauncher.java @@ -1,23 +1,14 @@ package xyz.samiker.theendlessweave.server; -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.*; import java.io.IOException; -import java.util.Random; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; public class ServerLauncher { private static GameServer server; - public static boolean isIntegrated = false; - public static boolean isStarted = false; + public static volatile boolean isIntegrated = false; + public static volatile boolean isStarted = false; public static void main(String[] args) { if (args.length > 0 && args[0].equalsIgnoreCase("--integrated")) isIntegrated = true; @@ -31,7 +22,16 @@ public static void init() { isStarted = true; System.out.println("Server started as " + (isIntegrated ? "integrated" : "dedicated")); } catch (IOException e) { - throw new RuntimeException("Unable to start server"+e); + e.printStackTrace(); + throw new RuntimeException("Unable to start server: " + e.getMessage()); } } + + public static void shutdown() { + if (server != null) { + server.dispose(); + server = null; + } + isStarted = false; + } } diff --git a/src/main/java/xyz/samiker/theendlessweave/server/ServerLoop.java b/src/main/java/xyz/samiker/theendlessweave/server/ServerLoop.java index 646c291..6ad0ba4 100644 --- a/src/main/java/xyz/samiker/theendlessweave/server/ServerLoop.java +++ b/src/main/java/xyz/samiker/theendlessweave/server/ServerLoop.java @@ -21,6 +21,7 @@ public void start() { running = true; logicThread = new Thread(this::runLogicLoop, "TheEndlessWeave-ServerLogic-Thread"); + logicThread.setDaemon(true); logicThread.start(); } @@ -28,6 +29,7 @@ public void stop() { running = false; try { if (logicThread != null) { + logicThread.interrupt(); logicThread.join(1000); } } catch (InterruptedException e) { diff --git a/src/main/java/xyz/samiker/theendlessweave/server/network/GameServer.java b/src/main/java/xyz/samiker/theendlessweave/server/network/GameServer.java index bd89bf3..61df6d2 100644 --- a/src/main/java/xyz/samiker/theendlessweave/server/network/GameServer.java +++ b/src/main/java/xyz/samiker/theendlessweave/server/network/GameServer.java @@ -31,6 +31,10 @@ public class GameServer { public final Map players = new ConcurrentHashMap<>(4); public final Map inputs = new ConcurrentHashMap<>(); + public boolean isReady = false; + + ExecutorService aiExecutor; + public GameServer() { this.server = new Server(32768, 32768); } @@ -61,7 +65,7 @@ public void disconnected(Connection connection) { if (players.isEmpty() && ServerLauncher.isIntegrated) { //TODO: логика сохранения когда-нибудь данных в будущем, но вероятно это стоит делать на клиенте (?) System.out.println("Closing integrated server"); - dispose(); + new Thread(ServerLauncher::shutdown, "Server-Shutdown-Thread").start(); } } } @@ -69,13 +73,23 @@ public void disconnected(Connection connection) { } public void dispose() { - server.stop(); loop.dispose(); + server.stop(); + if (aiExecutor != null) { + aiExecutor.close(); + } } + private void handleLogin(Connection connection, PacketLoginRequest req) { System.out.println("Login Request: " + req.username); + if ((req.mask & PacketLoginRequest.HAS_GAMEDATA) != 0 && ServerLauncher.isIntegrated) { + initGame(req.gameData); + } else { + initGame(GameData.withSettings(MapGeneratorSettings.openMap())); + } + Entity player = game.createPlayer(); PositionComponent pos = player.getComponent(PositionComponent.class); @@ -112,9 +126,7 @@ public void initGame(GameData data) { public void initGame() { Random rnd = new Random(); - initGame(createServerGame(new GameData( - MapGeneratorSettings.openMap(), 200, 200, rnd.nextLong()) - )); + initGame(createServerGame(GameData.waitingMap())); } public Game createServerGame(GameData data) { @@ -123,7 +135,7 @@ public Game createServerGame(GameData data) { public void initSystems() { var map = game.getGameMap(); - ExecutorService aiExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + aiExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); game.addLogicSystem(new ServerInputSystem(this, game)); game.addLogicSystem(new ServerDebugSystem(game.getProfiler())); 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 662822d..f6ee0f2 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 index ddab428..ab20b59 100644 --- a/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerEntityEventSystem.java +++ b/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerEntityEventSystem.java @@ -1,7 +1,10 @@ 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.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.logic.Game; import xyz.samiker.theendlessweave.core.network.packets.PacketSpawnEntity; import xyz.samiker.theendlessweave.core.systems.ISystem; 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 82c9820..34b9137 100644 --- a/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerSnapshotSenderSystem.java +++ b/src/main/java/xyz/samiker/theendlessweave/server/systems/ServerSnapshotSenderSystem.java @@ -2,8 +2,10 @@ 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.*; +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.network.utils.PacketPool; import xyz.samiker.theendlessweave.core.systems.ISystem; import xyz.samiker.theendlessweave.core.utils.Array;