diff --git a/.github/workflows/buildRelease.yml b/.github/workflows/buildRelease.yml index 004bdf2..daa5083 100644 --- a/.github/workflows/buildRelease.yml +++ b/.github/workflows/buildRelease.yml @@ -21,7 +21,7 @@ jobs: - name: Build release run: | chmod +x gradlew - ./gradlew clean buildRelease + ./gradlew clean release - name: Upload to Release assets uses: softprops/action-gh-release@v2 diff --git a/.gitignore b/.gitignore index 480ccad..6ea09bd 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ target/ ### IntelliJ IDEA ### .idea/ -!.idea/misc.xml *.iws *.iml *.ipr @@ -53,4 +52,5 @@ bin/ **/cache/ **/prefs/ -### Workspace \ No newline at end of file +### Workspace +/settings.*.toml \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 4901ee9..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.run/build.run.xml b/.run/build.run.xml new file mode 100644 index 0000000..8ff4ec3 --- /dev/null +++ b/.run/build.run.xml @@ -0,0 +1,24 @@ + + + + + + + true + true + false + false + + + \ No newline at end of file diff --git a/.run/bytecode.run.xml b/.run/bytecode.run.xml deleted file mode 100644 index e40b2cb..0000000 --- a/.run/bytecode.run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/.run/release.run.xml b/.run/release.run.xml index 532ab9c..6f7638a 100644 --- a/.run/release.run.xml +++ b/.run/release.run.xml @@ -1,24 +1,24 @@ - - - - - - true - true - false - false - - + + + + + + true + true + false + false + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c9af3fa --- /dev/null +++ b/README.md @@ -0,0 +1,121 @@ +# Bounceverse - A Modern Arkanoid Clone +### Báo cáo Bài tập lớn OOP + +**Bounceverse** là một bản làm lại hiện đại của tựa game brick-breaker kinh điển, được xây dựng hoàn toàn bằng **Java** và game engine **FXGL**. Dự án này tập trung vào việc áp dụng kiến trúc **Entity-Component-System (ECS)** tiên tiến, được hỗ trợ bởi các nguyên tắc Lập trình Hướng đối tượng, để tạo ra một cấu trúc game linh hoạt, dễ bảo trì và mở rộng. + +Game không chỉ tái hiện lối chơi gốc mà còn bổ sung nhiều tính năng nâng cao như hệ thống sinh màn chơi ngẫu nhiên, hệ thống vật phẩm (power-up) đa dạng, kỹ năng chủ động cho Paddle và các hiệu ứng "game feel" hiện đại để mang lại trải nghiệm hấp dẫn. + +--- + +![Preview](https://s12.gifyu.com/images/b9Mf5.png) + +--- + +## ✨ Tính năng nổi bật + +Dự án Bounceverse có các tính năng nâng cao như sau: + +* **Sinh màn chơi ngẫu nhiên:** Mỗi lần chơi là một trải nghiệm mới với thuật toán sinh map dựa trên `FastNoiseLite`, tạo ra các layout gạch độc đáo (`GameSystem.java`). + +* **Đa dạng các loại gạch:** + * **Gạch Thường (Normal Brick):** Gạch 1 HP. + * **Gạch Cứng (Strong Brick):** Gạch nhiều HP, có hiệu ứng nứt vỡ trực quan (`StrongBrickTextureUpdater`) khi nhận sát thương. + * **Gạch Khiên (Shield Brick):** Chỉ có thể bị phá từ một hướng nhất định. + * **Gạch Nổ (Exploding Brick):** Khi bị phá hủy sẽ tạo ra một vụ nổ (`Explosion`), gây sát thương cho các gạch xung quanh. + * **Gạch Chìa khóa (Key Brick):** Luôn luôn rơi ra vật phẩm (`Power-Up`) khi bị phá hủy. + +* **Hệ thống Vật phẩm (Power-Ups) phong phú:** + * `GUN`: Trang bị súng cho Paddle, tự động bắn đạn lên trên để phá gạch. + * `EXPAND_PADDLE`: Tăng kích thước thanh đỡ tạm thời. + * `SHRINK_PADDLE`: Thu nhỏ kích thước thanh đỡ tạm thời (vật phẩm bất lợi). + * `REVERSE_PADDLE`: Đảo ngược điều khiển của thanh đỡ (vật phẩm bất lợi). + * `MULTIPLE_BALL`: Nhân đôi tất cả bóng đang có trên màn hình. + * `FAST_BALL` / `SLOW_BALL`: Tăng hoặc giảm tốc độ của bóng. + * `SHIELD`: Tạo một tấm khiên ở đáy màn hình, ngăn bóng không bị rơi ra ngoài trong một thời gian. + +* **Hệ thống UI/Menu hoàn chỉnh và hiện đại:** + * Menu chính và menu tạm dừng được thiết kế riêng (`scenes/Menu.java`) với hiệu ứng hạt (particle effects) và hoạt ảnh mượt mà. + * Màn hình Game Over (`DeathSubscene`) hiển thị điểm số và cho phép nhập tên nếu lọt vào top 10. + * Giao diện trong game (HUD) hiển thị điểm số và mạng sống (`Hearts`, `HorizontalPositiveInteger`). + +* **Hệ thống Điểm cao (Leaderboard):** + * Sử dụng `LeaderboardManager` để tự động **lưu và tải 10 điểm số cao nhất** vào file `leaderboard.dat`. + +* **Hiệu ứng & "Game Feel" nâng cao:** + * **Nền động:** Màu nền của game thay đổi mượt mà (`BackgroundColorManager`) dựa trên tiến độ phá gạch của người chơi. + * **Giao diện Synthwave:** Toàn bộ giao diện game được thiết kế với phong cách neon-synthwave độc đáo (`UISystem.java`). + * **Âm thanh:** Quản lý âm thanh đầy đủ cho các hành động trong game. + +--- + +## 🎮 Hướng dẫn chơi + +* **Mục tiêu:** Phá vỡ tất cả gạch để đạt điểm cao nhất. +* **Điều khiển:** + * `Mũi tên Trái/Phải`: Di chuyển thanh đỡ (Paddle). + * `SPACE`: Phóng bóng khi bóng đang dính vào thanh đỡ (đầu màn/sau khi mất mạng). + * `ESC`: Tạm dừng game. + * `S`: Kích hoạt kỹ năng chủ động của Paddle. +--- + +## 🏗 Cấu trúc dự án (Thiết kế ECS & OOP) + +Dự án được xây dựng trên nền tảng game engine **FXGL**, tuân thủ chặt chẽ kiến trúc **Entity-Component-System (ECS)**, kết hợp với các nguyên tắc OOP (Đóng gói, Kế thừa, Đa hình) và các Mẫu thiết kế phần mềm (Design Patterns). + +### 1. Gói `systems` (Hệ thống & Vòng đời game) +* **Bộ não của game:** Thay vì một lớp `GameManager` (God Class) khổng lồ, logic cốt lõi được chia nhỏ thành các hệ thống độc lập, kế thừa từ `InitialSystem`. +* `Bounceverse.java`: Lớp chính điều phối các vòng đời của FXGL (`initGame`, `initPhysics`, `initUI`...). +* `GameSystem`: Khởi tạo các thực thể ban đầu (gạch, paddle, bóng). +* `PhysicSystem`: Định nghĩa toàn bộ logic va chạm trong game. +* `InputSystem`: Quản lý toàn bộ input từ người chơi. +* **Mẫu thiết kế Singleton:** Các lớp System và Manager (`LeaderboardManager`, `UserSettingsManager`) được triển khai theo mẫu Singleton (sử dụng inner class `Holder`) để đảm bảo chỉ có một thực thể duy nhất và có thể truy cập toàn cục. + +### 2. Gói `factory.entities` (Sản xuất Thực thể) +* **Mẫu thiết kế Factory:** Logic tạo ra các đối tượng trong game (`Entity`) được tách biệt hoàn toàn khỏi logic chính. +* Mỗi lớp (`BallFactory`, `BrickFactory`, `PaddleFactory`...) chịu trách nhiệm xây dựng một loại `Entity` cụ thể, đóng gói các components và thuộc tính cần thiết cho thực thể đó. + +### 3. Gói `components` (Hành vi & Thuộc tính) +* **Trái tim của kiến trúc ECS:** Logic và dữ liệu của một `Entity` được định nghĩa bởi các `Component` mà nó chứa. +* **`Behavior` (Hành vi):** Các lớp kế thừa từ `Behavior` định nghĩa những gì một `Entity` có thể **làm**. + * `Attack.java`: Hành vi gây sát thương. + * `HealthDeath.java`: Hành vi "chết" khi hết máu. + * `PaddleShooting.java`: Hành vi bắn đạn của Paddle. + * `Explosion.java`: Hành vi nổ của gạch. +* **`Property` (Thuộc tính):** Các lớp kế thừa từ `Property` định nghĩa những **dữ liệu** mà một `Entity` có. + * `Attributes.java`: Chứa các chỉ số như phòng thủ. + * `Shield.java`: Chứa thông tin về các mặt được bảo vệ của gạch khiên. + +### 4. Gói `scenes` & `ui` (Giao diện & Trải nghiệm) +* Chịu trách nhiệm về mọi thứ người chơi nhìn thấy. +* `Menu.java`: Một lớp tùy chỉnh hoàn toàn, kế thừa `FXGLMenu`, để tạo ra menu chính và menu tạm dừng với phong cách riêng. +* `ViewElement`: Lớp cơ sở cho các thành phần UI độc lập như `Hearts` (hiển thị mạng) và `HorizontalPositiveInteger` (hiển thị điểm số). + +--- + +### Yêu cầu: +* Java JDK 24 (hoặc cao hơn). +* IDE. +* Gradle được tích hợp sẵn trong dự án. + +## 🛠 Công nghệ sử dụng + +* **Ngôn ngữ:** Java (Eclipse Temurin 24) +* **Framework:** **FXGL (FX Game Library)** - một game engine xây dựng trên nền tảng JavaFX. +* **Công cụ Build:** Gradle. +* **IDE:** IntelliJ IDEA / Visual Studio Code. + +--- + +## 👨‍💻 Thành viên nhóm +| Leader Mai Hải Thành | 24021627 | [@thnhmai06](https://github.com/thnhmai06) | +| --------------------- | -------- | ------------------------------------------------ | +| Trần Mạnh Tân | 24021619 | [@ManhTanTran](https://github.com/ManhTanTran) | +| Nguyễn Huỳnh Anh Tuấn | 24021659 | [@huynhtuan372](https://github.com/huynhtuan372) | + +Phân chia công việc: + +| @thnhmai06 | @ManhTanTran | @huynhtuan372 | +| ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| Đồng đảm nhiệm Power-up, thiết lập và quản lý các lớp cơ sở, quản lý chung project và hệ thống level. | Đảm nhiệm hệ thống vật lý, va chạm, Brick, Paddle, đồng đảm nhiệm và dẫn xuất Power-up. | Thiết kế UI và assets, các menu, leaderboard, xử lý input, hoàn thiện Ball. | + +Ball được thiết lập và phát triển ban đầu bởi minngoc123. diff --git a/build.gradle b/build.gradle index d41bd96..740877d 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,7 @@ dependencies { implementation 'com.github.almasb:fxgl:21.1' implementation 'com.google.guava:guava:33.5.0-jre' implementation 'com.moandjiezana.toml:toml4j:0.7.2' + implementation 'com.google.guava:guava:33.5.0-jre' implementation 'com.google.auto.service:auto-service-annotations:1.1.1' annotationProcessor 'com.google.auto.service:auto-service:1.1.1' } @@ -30,13 +31,16 @@ dependencies { application { mainClass = 'com.github.codestorm.bounceverse.Bounceverse' applicationDefaultJvmArgs = [ - "--enable-native-access=javafx.graphics" + "--enable-native-access=javafx.graphics", + "--enable-native-access=javafx.media", + '-Dprism.forceGPU=true', + '-Dmedia.gpu.decoder=true' ] } defaultTasks('run') -tasks.register('buildRelease', Jar) { +tasks.register('release', Jar) { group = 'build' description = "Test this project and assemble a release JAR file." @@ -63,16 +67,24 @@ javafx { } spotless { - enforceCheck = false - format 'misc', { - target '*.gradle', '.gitattributes', '.gitignore' + enforceCheck = false - trimTrailingWhitespace() - leadingSpacesToTabs() - endWithNewline() - } - java { - googleJavaFormat().aosp().reflowLongStrings() - formatAnnotations() - } + format 'misc', { + target '*.gradle', '.gitattributes', '.gitignore' + trimTrailingWhitespace() + endWithNewline() + } + + java { + target 'src/**/*.java' + targetExclude 'src/**/libs/**/*'; + + googleJavaFormat('1.17.0') + .aosp() + .reorderImports(true) + .reflowLongStrings(true) + .formatJavadoc(true) + formatAnnotations() + removeUnusedImports() + } } diff --git a/gradle.properties b/gradle.properties index 45cac2a..8b36c8a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,4 +3,4 @@ # name=Bounceverse group=com.github.codestorm -version=1.0.0-dev \ No newline at end of file +version=1.1.0-dev \ No newline at end of file diff --git a/src/main/java/com/github/codestorm/bounceverse/AssetsPath.java b/src/main/java/com/github/codestorm/bounceverse/AssetsPath.java new file mode 100644 index 0000000..d6d4e8d --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/AssetsPath.java @@ -0,0 +1,221 @@ +package com.github.codestorm.bounceverse; + +import com.github.codestorm.bounceverse.typing.enums.BrickType; +import com.google.common.collect.ImmutableList; + +import javafx.scene.paint.Color; + +import org.jspecify.annotations.NonNull; + +import java.util.Map; +import java.util.NavigableMap; +import java.util.Objects; +import java.util.TreeMap; + +/** + * + * + *

{@link AssetsPath}

+ * + * Nơi lưu trữ các đường dẫn tới assets (texture, âm thanh, video, v.v.). + */ +public final class AssetsPath { + + private AssetsPath() {} + + private static final String ROOT = "/assets"; + + public static final class Video { + private Video() {} + + private static final String ROOT = AssetsPath.ROOT + "/videos"; + public static final String INTRO = "/intro.mp4"; + } + + public static final class Sounds { + private Sounds() {} + + private static final String ROOT = AssetsPath.ROOT + "/sounds"; + + public static final class Music { + private Music() {} + + private static final String ROOT = Sounds.ROOT + "/music"; + + public static final String IN_GAME = ROOT + "/in_game.wav"; + public static final String MENU = ROOT + "/menu.ogg"; + } + + public static final class SFX { + private SFX() {} + + private static final String ROOT = Sounds.ROOT + "/sfx"; + + public static final String BLIP = ROOT + "/blip.wav"; + public static final String BRICK_HIT_1 = ROOT + "/brick-hit-1.wav"; + public static final String BRICK_HIT_2 = ROOT + "/brick-hit-2.wav"; + public static final String BUZZ = ROOT + "/buzz.wav"; + public static final String CLICK_TINY = ROOT + "/click_tiny.wav"; + public static final String CONFIRM = ROOT + "/confirm.wav"; + public static final String ELECTRIC = ROOT + "/electric.wav"; + public static final String HIGH_SCORE = ROOT + "/high_score.wav"; + public static final String HURT = ROOT + "/hurt.wav"; + public static final String KEY_OPEN = ROOT + "/key_open.wav"; + public static final String LOSE = ROOT + "/lose.wav"; + public static final String NO_SELECT = ROOT + "/no-select.wav"; + public static final String PADDLE_HIT = ROOT + "/paddle_hit.wav"; + public static final String PAUSE = ROOT + "/pause.wav"; + public static final String POWER_UP = ROOT + "/power_up.wav"; + public static final String RECOVER = ROOT + "/recover.wav"; + public static final String SCORE = ROOT + "/score.wav"; + public static final String SELECT = ROOT + "/select.wav"; + public static final String SHRINK = ROOT + "/shrink.wav"; + public static final String SWITCH2 = ROOT + "/switch2.wav"; + public static final String VICTORY = ROOT + "/victory.wav"; + public static final String WALL_HIT = ROOT + "/wall_hit.wav"; + public static final String WIN3 = ROOT + "/win3.wav"; + + public static final ImmutableList<@NonNull String> BRICK_HITS = + ImmutableList.of(BRICK_HIT_1, BRICK_HIT_2); + } + } + + public static final class Textures { + private Textures() {} + + public static final class Bricks { + + private Bricks() {} + + private static final String ROOT = "bricks"; + + public static final Map COLORS; + + static { + COLORS = + Map.of( + "blue", new ColorAssets(Color.BLUE), + "green", new ColorAssets(Color.GREEN), + "orange", new ColorAssets(Color.ORANGE), + "pink", new ColorAssets(Color.PINK), + "red", new ColorAssets(Color.RED), + "yellow", new ColorAssets(Color.YELLOW)); + } + + public static final class ColorAssets { + + private static final NavigableMap NORMAL = new TreeMap<>(); + private static final NavigableMap SHIELD = new TreeMap<>(); + private static final NavigableMap STRONG = new TreeMap<>(); + private static final NavigableMap KEY = new TreeMap<>(); + private static final NavigableMap EXPLODING = new TreeMap<>(); + + private final Color color; + + public Color getColor() { + return color; + } + + private ColorAssets(Color color) { + this.color = color; + } + + static { + // Normal + NORMAL.put(0.0, "/normal.png"); + + // Shield + SHIELD.put(0.0, "/shield.png"); + + // Strong + STRONG.put(1.0, "/strong.png"); + STRONG.put(2.0 / 3, "/strongFirstHit.png"); + STRONG.put(1.0 / 3, "/strongSecondHit.png"); + STRONG.put(0.0, "/strongThirdHit.png"); + + // Key Brick + KEY.put(0.0, "/keybrick.png"); + + // Exploding + EXPLODING.put(0.0, "/explode.png"); + } + + public String getColorName() { + if (color.equals(Color.BLUE)) return "blue"; + if (color.equals(Color.GREEN)) return "green"; + if (color.equals(Color.ORANGE)) return "orange"; + if (color.equals(Color.PINK)) return "pink"; + if (color.equals(Color.RED)) return "red"; + if (color.equals(Color.YELLOW)) return "yellow"; + throw new IllegalArgumentException("Unsupported color: " + color); + } + + public String getRoot() { + return ROOT + "/" + getColorName(); + } + + /** + * Lấy texture path dựa trên BrickType và HP percent. + * + * @param brickType Loại brick + * @param hpPercent HP phần trăm (0.0 - 1.0) + * @return Đường dẫn đến texture + */ + public String getTexture(BrickType brickType, double hpPercent) { + var map = + switch (brickType) { + case NORMAL -> NORMAL; + case SHIELD -> SHIELD; + case STRONG -> STRONG; + case KEY -> KEY; + case EXPLODING -> EXPLODING; // fallback dùng texture thường + }; + return getRoot() + map.floorEntry(hpPercent).getValue(); + } + + public Color color() { + return color; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (ColorAssets) obj; + return Objects.equals(this.color, that.color); + } + + @Override + public int hashCode() { + return Objects.hash(color); + } + + @Override + public String toString() { + return "ColorAssets[color=" + color + ']'; + } + } + } + + public static final Map NUMBERS = + Map.of( + 0, "numbers/0.png", + 1, "numbers/1.png", + 2, "numbers/2.png", + 3, "numbers/3.png", + 4, "numbers/4.png", + 5, "numbers/5.png", + 6, "numbers/6.png", + 7, "numbers/7.png", + 8, "numbers/8.png", + 9, "numbers/9.png"); + + public static final String HEART = "/heart.png"; + } + + public static final class Other { + private Other() {} + + public static final String CREDITS = AssetsPath.ROOT + "/credits.txt"; + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/Bounceverse.java b/src/main/java/com/github/codestorm/bounceverse/Bounceverse.java index 802f063..e8b9080 100644 --- a/src/main/java/com/github/codestorm/bounceverse/Bounceverse.java +++ b/src/main/java/com/github/codestorm/bounceverse/Bounceverse.java @@ -1,45 +1,68 @@ package com.github.codestorm.bounceverse; import com.almasb.fxgl.app.GameApplication; -import com.github.codestorm.bounceverse.core.*; -import com.github.codestorm.bounceverse.core.systems.*; +import com.almasb.fxgl.app.GameSettings; +import com.almasb.fxgl.dsl.FXGL; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUpManager; +import com.github.codestorm.bounceverse.systems.init.AppEventSystem; +import com.github.codestorm.bounceverse.systems.init.GameSystem; +import com.github.codestorm.bounceverse.systems.init.InputSystem; +import com.github.codestorm.bounceverse.systems.init.PhysicSystem; +import com.github.codestorm.bounceverse.systems.init.UISystem; +import com.github.codestorm.bounceverse.systems.manager.settings.GameSettingsManager; +import com.github.codestorm.bounceverse.systems.manager.settings.LaunchOptionsManager; +import com.github.codestorm.bounceverse.systems.manager.settings.UserSettingsManager; import com.github.codestorm.bounceverse.typing.exceptions.BounceverseException; + +import javafx.util.Duration; + import java.io.IOException; +import java.util.Map; /** * * *

{@link Bounceverse}

* - * Phần Hệ thống Chương trình chính của game, nơi mà mọi thứ bắt đầu từ {@link #main(String[])}... - *
- * Game {@link Bounceverse} được lấy cảm hứng từ game Arkanoid nổi tiếng, nơi người chơi điều - * khiển một thanh để đỡ bóng và phá vỡ các viên gạch. Mục tiêu của game là phá vỡ tất cả các viên - * gạch và dành được điểm số cao nhất. Nhưng liệu mọi thứ chỉ đơn giản như vậy? + * Game chính — quản lý vòng đời khởi tạo và vòng lặp của trò chơi. */ public final class Bounceverse extends GameApplication { + public static void main(String[] args) { - LaunchOptions.load(args); + LaunchOptionsManager.getInstance().load(args); launch(args); } @Override - protected void initSettings(com.almasb.fxgl.app.GameSettings settings) { + protected void initSettings(GameSettings settings) { + UserSettingsManager.getInstance().load(); try { - SettingsManager.load(settings); + GameSettingsManager.load(settings); } catch (IOException e) { throw new BounceverseException(e); } } @Override - protected void initGame() { - GameSystem.getInstance().apply(); + protected void initInput() { + InputSystem.getInstance().apply(); } @Override - protected void initInput() { - InputSystem.getInstance().apply(); + protected void onPreInit() { + AppEventSystem.getInstance().apply(); + } + + @Override + protected void initGameVars(Map vars) { + GameSystem.Variables.loadDefault(FXGL.getWorldProperties()); + } + + @Override + protected void initGame() { + GameSystem.UI.getInstance().dispose(); + UISystem.getInstance().dispose(); + FXGL.runOnce(() -> GameSystem.getInstance().apply(), Duration.seconds(0.1)); } @Override @@ -49,6 +72,14 @@ protected void initPhysics() { @Override protected void initUI() { + FXGL.getGameScene().setBackgroundColor(javafx.scene.paint.Color.web("#0d0b1a")); UISystem.getInstance().apply(); + FXGL.getGameScene().getRoot().getStylesheets().add("assets/ui/powerup.css"); + } + + @Override + protected void onUpdate(double tpf) { + PowerUpManager.getInstance().onUpdate(tpf); + GameSystem.UI.getInstance().onUpdate(); } } diff --git a/src/main/java/com/github/codestorm/bounceverse/Utilities.java b/src/main/java/com/github/codestorm/bounceverse/Utilities.java index c4b17fc..2cb9fda 100644 --- a/src/main/java/com/github/codestorm/bounceverse/Utilities.java +++ b/src/main/java/com/github/codestorm/bounceverse/Utilities.java @@ -2,17 +2,16 @@ import com.almasb.fxgl.dsl.FXGL; import com.almasb.fxgl.entity.Entity; -import com.almasb.fxgl.entity.component.Component; -import com.almasb.fxgl.time.TimerAction; -import com.github.codestorm.bounceverse.typing.annotations.ForEntity; +import com.almasb.fxgl.entity.SpawnData; +import com.github.codestorm.bounceverse.components.Component; +import com.github.codestorm.bounceverse.typing.annotations.OnlyForEntity; import com.github.codestorm.bounceverse.typing.enums.DirectionUnit; import com.github.codestorm.bounceverse.typing.enums.EntityType; -import java.io.IOException; -import java.io.InputStream; -import java.util.*; + import javafx.geometry.Rectangle2D; -import javafx.scene.shape.Circle; -import javafx.util.Duration; + +import java.io.*; +import java.util.*; /** Utilities. */ public final class Utilities { @@ -20,55 +19,35 @@ private Utilities() {} /** Input/Output utilities. */ public static final class IO { + // ... (Nội dung của lớp IO giữ nguyên, không cần thay đổi) private IO() {} - /** - * Load .properties file. - * - * @param path Relative path - * @return Parsed properties - * @throws IOException if an error occurred when reading from the input stream. - */ public static Properties loadProperties(String path) throws IOException { - InputStream fileStream = IO.class.getResourceAsStream(path); + var fileStream = IO.class.getResourceAsStream(path); if (fileStream == null) { throw new IOException("Cannot open InputStream on " + path); } - Properties prop = new Properties(); + var prop = new Properties(); prop.load(fileStream); fileStream.close(); return prop; } - /** - * Convert an array of key=value pairs into a hashmap. The string "key=" maps key onto "", - * while just "key" maps key onto null. The value may contain '=' characters, only the first - * "=" is a delimiter.
- * Source code from here. - * - * @param args command-line arguments in the key=value format (or just key= or key) - * @param defaults a map of default values, may be null. Mappings to null are not copied to - * the resulting map. - * @param whiteList if not null, the keys not present in this map cause an exception (and - * keys mapped to null are ok) - * @return a map that maps these keys onto the corresponding values. - */ public static HashMap parseArgs( String[] args, HashMap defaults, HashMap whiteList) { - // HashMap allows null values - HashMap res = new HashMap<>(); + var res = new HashMap(); if (defaults != null) { - for (Map.Entry e : defaults.entrySet()) { + for (var e : defaults.entrySet()) { if (e.getValue() != null) { res.put(e.getKey(), e.getValue()); } } } - for (String s : args) { - String[] kv = s.split("=", 2); + for (var s : args) { + var kv = s.split("=", 2); if (whiteList != null && !whiteList.containsKey(kv[0])) { continue; } @@ -77,166 +56,24 @@ public static HashMap parseArgs( return res; } - /** - * Read text file (txt) and put all lines into {@link List}. - * - * @param path File path - * @return All lines in text file - */ - public static List readTextFile(String path) { - var res = new ArrayList(); - var scanner = new Scanner(path); - while (scanner.hasNext()) { - res.add(scanner.next()); + public static List readTextFile(String path) throws IOException { + final var res = new ArrayList(); + final var stream = IO.class.getResourceAsStream(path); + if (stream == null) { + throw new FileNotFoundException(path); + } + final var scanner = new Scanner(stream); + while (scanner.hasNextLine()) { + res.add(scanner.nextLine()); } scanner.close(); + stream.close(); return res; } } - public static final class Time { - /** - * Thời gian hồi để thực hiện lại gì đó. Thực hiện thông qua {@link #current} - * - * @see ActiveCooldown - */ - public static final class Cooldown { - private final ActiveCooldown current = new ActiveCooldown(); - private Duration duration = Duration.INDEFINITE; - - public Duration getDuration() { - return duration; - } - - /** - * Đặt thời lượng cooldown mới.
- * Lưu ý: Chỉ áp dụng cho cooldown mới. - * - * @param duration Thời lượng mới - */ - public void setDuration(Duration duration) { - this.duration = duration; - } - - public ActiveCooldown getCurrent() { - return current; - } - - public Cooldown() {} - - public Cooldown(Duration duration) { - this.duration = duration; - } - - /** Cooldown thời điểm hiện tại. Giống như một wrapper của {@link TimerAction}. */ - public final class ActiveCooldown { - private TimerAction waiter = null; - private double timestamp = Double.NaN; - private Runnable onExpiredCallback = null; - - /** Hành động khi cooldown hết. */ - private void onExpired() { - timestamp = Double.NaN; - if (onExpiredCallback != null) { - onExpiredCallback.run(); - } - } - - /** - * Callback thực thi khi cooldown hết hạn. - * - * @param callback Callback sẽ thực thi - */ - public void setOnExpired(Runnable callback) { - this.onExpiredCallback = callback; - } - - /** - * Kiểm tra Cooldown hiện tại hết hạn chưa. - * - * @return {@code true} nếu hết hạn, ngược lại {@code false}. - */ - public boolean expired() { - return (waiter == null) || waiter.isExpired(); - } - - /** Khiến cooldown hết hạn ngay (nếu có). */ - public void expire() { - if (!expired()) { - waiter.expire(); - } - } - - /** Set một cooldown mới. */ - public void makeNew() { - expire(); - - final var gameTimer = FXGL.getGameTimer(); - waiter = gameTimer.runOnceAfter(this::onExpired, duration); - timestamp = gameTimer.getNow(); - } - - /** Tạm dừng cooldown. */ - public void pause() { - if (!expired()) { - waiter.pause(); - } - } - - /** Tiếp tục cooldown. */ - public void resume() { - if (!expired()) { - waiter.resume(); - } - } - - public boolean isPaused() { - return !expired() && waiter.isPaused(); - } - - /** - * Lấy thời gian còn lại của cooldown. - * - * @return Thời gian còn lại - */ - public Duration getTimeLeft() { - if (expired()) { - return Duration.ZERO; - } - final var elapsed = Duration.millis(FXGL.getGameTimer().getNow() - timestamp); - return duration.subtract(elapsed); - } - - /** - * Giảm thời gian hồi đi một lượng thời gian. - * - * @param duration Thời lượng giảm. - */ - public void reduce(Duration duration) { - if (!expired()) { - waiter.update(duration.toMillis()); - } - } - - private ActiveCooldown() {} - } - } - } - public static final class Geometric { - /** - * Lọc các Entity trong phạm vi Hình tròn. - * - * @param circle Hình tròn - * @return Các entity - */ - public static List getEntityInCircle(Circle circle) { - final var cx = circle.getCenterX(); - final var cy = circle.getCenterY(); - final var radius = circle.getRadius(); - - return getEntityInCircle(cx, cy, radius); - } + private Geometric() {} /** * Lọc các Entity trong phạm vi Hình tròn. @@ -247,33 +84,49 @@ public static List getEntityInCircle(Circle circle) { * @return Các entity */ public static List getEntityInCircle(double cx, double cy, double radius) { - final Rectangle2D outRect = - new Rectangle2D(cx - radius, cy - radius, 2 * radius, 2 * radius); + final var outRect = new Rectangle2D(cx - radius, cy - radius, 2 * radius, 2 * radius); return FXGL.getGameWorld().getEntitiesInRange(outRect).stream() .filter( e -> { - double nearestX = + var nearestX = Math.max(e.getX(), Math.min(cx, e.getX() + e.getWidth())); - double nearestY = + var nearestY = Math.max(e.getY(), Math.min(cy, e.getY() + e.getHeight())); - double dx = cx - nearestX; - double dy = cy - nearestY; + var dx = cx - nearestX; + var dy = cy - nearestY; return (dx * dx + dy * dy) <= radius * radius; }) .toList(); } + + /** + * Lọc các Entity trong phạm vi Hình chữ nhật. + * + * @param centerX Tâm X của hình chữ nhật + * @param centerY Tâm Y của hình chữ nhật + * @param width Chiều rộng của hình chữ nhật + * @param height Chiều cao của hình chữ nhật + * @return Danh sách các entity nằm trong khu vực đó + */ + public static List getEntitiesInRectangle( + double centerX, double centerY, double width, double height) { + var topLeftX = centerX - width / 2; + var topLeftY = centerY - height / 2; + var explosionArea = new Rectangle2D(topLeftX, topLeftY, width, height); + return FXGL.getGameWorld().getEntitiesInRange(explosionArea); + } } + // ... (Các lớp Collision, Compatibility, Typing giữ nguyên, không cần thay đổi) public static final class Collision { + private Collision() {} + public static DirectionUnit getCollisionDirection(Entity source, Entity target) { var fromBox = source.getBoundingBoxComponent(); var toBox = target.getBoundingBoxComponent(); - var fCenter = fromBox.getCenterWorld(); var tCenter = toBox.getCenterWorld(); - var direction = tCenter.subtract(fCenter); - return Math.abs(direction.getX()) > Math.abs(direction.getY()) ? direction.getX() > 0 ? DirectionUnit.RIGHT : DirectionUnit.LEFT : direction.getY() > 0 ? DirectionUnit.DOWN : DirectionUnit.UP; @@ -281,72 +134,49 @@ public static DirectionUnit getCollisionDirection(Entity source, Entity target) } public static final class Compatibility { - /** - * Throw {@link IllegalArgumentException} nếu như có component trong {@code params} không - * phù hợp với {@code onlyFor}. - * - * @param onlyFor {@link EntityType} muốn kiểm tra tương thích - * @param params Các component cần kiểm tra - */ - public static void throwIfNotCompatible(EntityType onlyFor, Component... params) { - for (var param : params) { - final var annotation = param.getClass().getAnnotation(ForEntity.class); - if (annotation != null) { - final var paramSet = EnumSet.copyOf(Arrays.asList(annotation.value())); - if (paramSet.isEmpty() || paramSet.contains(onlyFor)) { - continue; - } + private Compatibility() {} + + public static void throwIfNotCompatible( + EntityType entityType, com.almasb.fxgl.entity.component.Component... components) { + for (var param : components) { + final var annotation = param.getClass().getAnnotation(OnlyForEntity.class); + if (annotation == null) { + continue; + } + final var paramEntityTypeSet = EnumSet.copyOf(Arrays.asList(annotation.value())); + if (!paramEntityTypeSet.contains(entityType)) { + throw new IllegalArgumentException( + String.format( + "Class '%s' does not compatible for entity has '%s'" + + " entityType.", + param.getClass().getSimpleName(), entityType.name())); } - throw new IllegalArgumentException( - String.format( - "Class '%s' does not compatible for entity has '%s' type.", - param.getClass().getSimpleName(), onlyFor.name())); } } - /** - * {@link #throwIfNotCompatible(EntityType, Component...)} nhưng không throw exception. - * - * @param onlyFor {@link EntityType} muốn kiểm tra tương thích - * @param params Các component cần kiểm tra - * @return {@code true} nếu tất cả tương thích, ngược lại {@code false}. - */ - public static boolean isCompatible(EntityType onlyFor, Component... params) { + public static boolean isCompatible(EntityType entityType, Component... params) { try { - throwIfNotCompatible(onlyFor, params); + throwIfNotCompatible(entityType, params); return true; } catch (IllegalArgumentException e) { return false; } } + } - /** - * Throw {@link IllegalArgumentException} nếu như có component trong {@code params} không - * phù hợp đồng thời tất cả với {@code onlyFor}. - * - * @param onlyFor Các {@link EntityType} muốn kiểm tra tương thích - * @param params Các component cần kiểm tra - */ - public static void throwIfNotCompatible(EntityType[] onlyFor, Component... params) { - for (var only : onlyFor) { - throwIfNotCompatible(only, params); + public static final class Typing { + private Typing() {} + + public static T getOr(SpawnData data, String key, T ifNot) { + if (data.hasKey(key)) { + return data.get(key); } + return ifNot; } - /** - * {@link #throwIfNotCompatible(EntityType[], Component...)} nhưng không throw exception. - * - * @param onlyFor Các {@link EntityType} muốn kiểm tra tương thích - * @param params Các component cần kiểm tra - * @return {@code true} nếu tất cả tương thích, ngược lại {@code false}. - */ - public static boolean isCompatible(EntityType[] onlyFor, Component... params) { - try { - throwIfNotCompatible(onlyFor, params); - return true; - } catch (IllegalArgumentException e) { - return false; - } + @SafeVarargs + public static T[] toArray(T... varargs) { + return varargs; } } } diff --git a/src/main/java/com/github/codestorm/bounceverse/components/Behavior.java b/src/main/java/com/github/codestorm/bounceverse/components/Behavior.java new file mode 100644 index 0000000..fe7e28a --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/Behavior.java @@ -0,0 +1,18 @@ +package com.github.codestorm.bounceverse.components; + +import com.almasb.fxgl.entity.Entity; +import com.github.codestorm.bounceverse.typing.interfaces.Executable; + +/** + * + * + *

{@link Behavior}

+ * + *
+ * Một {@link Component} biểu diễn + * hành vi (behavior) của {@link Entity}. + * + * @see Property + */ +public abstract non-sealed class Behavior extends Component implements Executable {} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/Component.java b/src/main/java/com/github/codestorm/bounceverse/components/Component.java new file mode 100644 index 0000000..c4e5256 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/Component.java @@ -0,0 +1,23 @@ +package com.github.codestorm.bounceverse.components; + +import com.github.codestorm.bounceverse.Bounceverse; +import com.github.codestorm.bounceverse.Utilities; +import com.github.codestorm.bounceverse.typing.enums.EntityType; + +/** + * + * + *

{@link Component}

+ * + * Một thành phần lưu trữ trên Entity trong game {@link Bounceverse}.
+ * Giống như phiên bản class của {@link com.almasb.fxgl.entity.component.Component}. + * + * @see com.almasb.fxgl.entity.component.Component + */ +public abstract sealed class Component extends com.almasb.fxgl.entity.component.Component + permits Behavior, Property { + @Override + public void onAdded() { + Utilities.Compatibility.throwIfNotCompatible((EntityType) entity.getType(), this); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/Property.java b/src/main/java/com/github/codestorm/bounceverse/components/Property.java similarity index 56% rename from src/main/java/com/github/codestorm/bounceverse/components/properties/Property.java rename to src/main/java/com/github/codestorm/bounceverse/components/Property.java index 016058e..19fa2cc 100644 --- a/src/main/java/com/github/codestorm/bounceverse/components/properties/Property.java +++ b/src/main/java/com/github/codestorm/bounceverse/components/Property.java @@ -1,8 +1,6 @@ -package com.github.codestorm.bounceverse.components.properties; +package com.github.codestorm.bounceverse.components; import com.almasb.fxgl.entity.Entity; -import com.almasb.fxgl.entity.component.Component; -import com.github.codestorm.bounceverse.components.behaviors.Behavior; /** * @@ -16,4 +14,4 @@ * * @see Behavior */ -public abstract class Property extends Component {} +public abstract non-sealed class Property extends Component {} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/Attachment.java b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/Attachment.java new file mode 100644 index 0000000..5edd808 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/Attachment.java @@ -0,0 +1,80 @@ +package com.github.codestorm.bounceverse.components.behaviors; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.entity.Entity; +import com.almasb.fxgl.entity.component.Component; +import com.almasb.fxgl.physics.PhysicsComponent; +import com.github.codestorm.bounceverse.factory.entities.BallFactory; +import com.github.codestorm.bounceverse.typing.enums.EntityType; + +import javafx.geometry.Point2D; + +/** Gắn bóng vào paddle và điều khiển khi người chơi chưa bắn ra. */ +public class Attachment extends Component { + + private Entity paddle; + private boolean attached = true; + private PhysicsComponent physics; + + private boolean move = false; + private double direction = 1; + private double currentOffset = 0; + private double lastPaddleX; + + @Override + public void onAdded() { + paddle = FXGL.getGameWorld().getSingleton(EntityType.PADDLE); + physics = getEntity().getComponent(PhysicsComponent.class); + lastPaddleX = paddle.getX(); + } + + @Override + public void onUpdate(double tpf) { + if (attached && paddle != null) { + var paddleCenterX = paddle.getCenter().getX(); + var paddleTopY = paddle.getY(); + + var deltaX = paddle.getX() - lastPaddleX; + lastPaddleX = paddle.getX(); + + if (!move && Math.abs(deltaX) > 0.5) { + move = true; + direction = Math.signum(deltaX); + } + + if (move) { + double moveSpeed = 50; + currentOffset += direction * moveSpeed * tpf; + double maxOffset = 50; + if (Math.abs(currentOffset) > maxOffset) { + direction *= -1; + } + } + + var x = paddleCenterX - entity.getWidth() / 2 + currentOffset + 10; + var y = paddleTopY - BallFactory.DEFAULT_RADIUS * 2 + 5; + + entity.setPosition(x, y); + physics.setLinearVelocity(Point2D.ZERO); + } + } + + public void releaseBall() { + if (!attached) return; + + attached = false; + physics.overwritePosition(entity.getPosition()); + physics.getBody().setAwake(true); + + double speed = 350; + double vx = 0; + var vy = -speed; + + physics.setLinearVelocity(new Point2D(vx, vy)); + move = false; + } + + public boolean isAttached() { + return attached; + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/Attack.java b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/Attack.java index 10db568..c45902d 100644 --- a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/Attack.java +++ b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/Attack.java @@ -2,8 +2,9 @@ import com.almasb.fxgl.dsl.components.HealthIntComponent; import com.almasb.fxgl.entity.Entity; +import com.github.codestorm.bounceverse.components.Behavior; import com.github.codestorm.bounceverse.components.properties.Attributes; -import com.github.codestorm.bounceverse.typing.annotations.ForEntity; + import java.util.List; /** @@ -13,14 +14,13 @@ * * Hành động tấn công/gây sát thương của {@link Entity}.
*/ -@ForEntity({}) public class Attack extends Behavior { public static final int DEFAULT_DAMAGE = 1; private int damage = DEFAULT_DAMAGE; @Override public void execute(List data) { - List entities = + var entities = data.stream().filter(obj -> obj instanceof Entity).map(e -> (Entity) e).toList(); for (var obj : entities) { final var theirHealth = obj.getComponentOptional(HealthIntComponent.class); diff --git a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/Behavior.java b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/Behavior.java deleted file mode 100644 index 71153ad..0000000 --- a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/Behavior.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.github.codestorm.bounceverse.components.behaviors; - -import com.almasb.fxgl.entity.Entity; -import com.almasb.fxgl.entity.component.Component; -import com.github.codestorm.bounceverse.components.properties.Property; -import com.github.codestorm.bounceverse.typing.interfaces.CanExecute; - -/** - * - * - *

{@link Behavior}

- * - *
- * Một {@link Component} biểu diễn - * hành vi (behavior) của {@link Entity}. - * - * @see Property - */ -public abstract class Behavior extends Component implements CanExecute {} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/CooldownBehavior.java b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/CooldownBehavior.java deleted file mode 100644 index 0b50a16..0000000 --- a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/CooldownBehavior.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.github.codestorm.bounceverse.components.behaviors; - -import com.github.codestorm.bounceverse.Utilities.Time.Cooldown; -import com.github.codestorm.bounceverse.typing.interfaces.CanExecute; -import java.util.List; -import javafx.util.Duration; - -/** - * - * - *

{@link CooldownBehavior}

- * - * {@link Behavior} sau khi thực thi sẽ mất thời gian để có thể {@link #executeLogic(List)} lại. - */ -public abstract class CooldownBehavior extends Behavior implements CanExecute { - private final Cooldown cooldown = new Cooldown(); - - public Cooldown getCooldown() { - return cooldown; - } - - /** - * Logic bên trong {@link CanExecute#execute(List)}. - * - * @param data Dữ liệu truyền vào - * @return {@code true} nếu cho phép thực thi, ngược lại {@code false} - */ - protected abstract boolean executeLogic(List data); - - @Override - public final void execute(List data) { - if (!cooldown.getCurrent().expired()) { - return; - } - if (executeLogic(data)) { - cooldown.getCurrent().makeNew(); - } - } - - protected CooldownBehavior() {} - - protected CooldownBehavior(Duration duration) { - cooldown.setDuration(duration); - } -} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/Explosion.java b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/Explosion.java index 11c89a2..3cb7ff0 100644 --- a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/Explosion.java +++ b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/Explosion.java @@ -1,54 +1,61 @@ package com.github.codestorm.bounceverse.components.behaviors; -import com.almasb.fxgl.entity.Entity; import com.almasb.fxgl.entity.component.Required; import com.github.codestorm.bounceverse.Utilities; import com.github.codestorm.bounceverse.components.properties.Attributes; -import com.github.codestorm.bounceverse.typing.annotations.ForEntity; +import com.github.codestorm.bounceverse.typing.annotations.OnlyForEntity; +import com.github.codestorm.bounceverse.typing.enums.EntityType; + import java.util.List; -/** - * - * - *

{@link Explosion}

- * - *
- * Hành vi nổ của {@link Entity}, có thể gây sát thương hoặc hồi máu cho những đối tượng xung quanh. - *
- * Yêu cầu entity có {@link Attributes} trước. - */ +/** Hành vi nổ của Brick – gây damage cho các đối tượng xung quanh. */ @Required(Attributes.class) -@ForEntity({}) +@OnlyForEntity({EntityType.BRICK}) public final class Explosion extends Attack { - public static final int DEFAULT_RADIUS = 1; - private int radius = DEFAULT_RADIUS; + + private double explosionWidth; + private double explosionHeight; @Override public void execute(List data) { - final var attributes = entity.getComponent(Attributes.class); - final double cx = getEntity().getCenter().getX(); - final double cy = getEntity().getCenter().getY(); + var cx = getEntity().getCenter().getX(); + var cy = getEntity().getCenter().getY(); + + var nearEntities = + Utilities.Geometric.getEntitiesInRectangle(cx, cy, explosionWidth, explosionHeight); + + var filteredEntities = + nearEntities.stream() + .filter(e -> !e.equals(getEntity())) + .map(e -> (Object) e) + .toList(); - final var nearEntities = Utilities.Geometric.getEntityInCircle(cx, cy, radius); - super.execute(nearEntities.stream().map(e -> (Object) e).toList()); + super.execute(filteredEntities); } @Override public void onRemoved() { - execute(null); + execute(List.of()); } - public int getRadius() { - return radius; + public Explosion(double width, double height) { + this.explosionWidth = width; + this.explosionHeight = height; } - public void setRadius(int radius) { - this.radius = Math.abs(radius); + public double getExplosionWidth() { + return explosionWidth; } - public Explosion() {} + public void setExplosionWidth(double explosionWidth) { + this.explosionWidth = explosionWidth; + } + + public double getExplosionHeight() { + return explosionHeight; + } - public Explosion(int radius) { - setRadius(radius); + public void setExplosionHeight(double explosionHeight) { + this.explosionHeight = explosionHeight; } } diff --git a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/FallingComponent.java b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/FallingComponent.java new file mode 100644 index 0000000..6412b93 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/FallingComponent.java @@ -0,0 +1,14 @@ +package com.github.codestorm.bounceverse.components.behaviors; + +import com.almasb.fxgl.entity.component.Component; + +/** Cho Power-Up rơi thẳng xuống với tốc độ cố định. */ +public final class FallingComponent extends Component { + + private static final double SPEED = 150; + + @Override + public void onUpdate(double tpf) { + entity.translateY(SPEED * tpf); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/HealthDeath.java b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/HealthDeath.java index e3f1e49..ea9e136 100644 --- a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/HealthDeath.java +++ b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/HealthDeath.java @@ -1,26 +1,63 @@ +// C:\Users\Admin\Documents\bounceverse\src\main\java\com\github\codestorm\bounceverse\components\behaviors\HealthDeath.java package com.github.codestorm.bounceverse.components.behaviors; +import com.almasb.fxgl.dsl.FXGL; import com.almasb.fxgl.dsl.components.HealthIntComponent; import com.almasb.fxgl.entity.component.Required; -import com.github.codestorm.bounceverse.typing.annotations.ForEntity; +import com.github.codestorm.bounceverse.components.Behavior; +import com.github.codestorm.bounceverse.components.properties.brick.BrickTextureManager; +import com.github.codestorm.bounceverse.systems.init.GameSystem; +import com.github.codestorm.bounceverse.systems.init.UISystem; +import com.github.codestorm.bounceverse.typing.enums.EntityType; + +import javafx.util.Duration; + import java.util.List; -/** - * - * - *

{@link HealthDeath}

- * - * Hành động chết vì yêu :< vì hết máu.
- * Yêu cầu entity có {@link HealthIntComponent} trước. - */ @Required(HealthIntComponent.class) -@ForEntity({}) public class HealthDeath extends Behavior { + @Override public void execute(List data) { - final var health = entity.getComponent(HealthIntComponent.class); + var health = entity.getComponent(HealthIntComponent.class); if (health != null && health.isZero()) { + if (entity.isType(EntityType.BRICK)) { + entity.getComponentOptional(BrickTextureManager.class) + .ifPresent( + textureManager -> { + var brickColor = textureManager.getColor(); + + UISystem.getInstance().addColorToWave(brickColor); + + var scoreToAdd = 0; + switch (textureManager.brickType) { + case NORMAL, KEY: + scoreToAdd = 10; + break; + case SHIELD: + scoreToAdd = 20; + break; + case STRONG: + scoreToAdd = 30; + break; + case EXPLODING: + break; + } + + if (scoreToAdd > 0) { + FXGL.inc("score", scoreToAdd); + } + }); + } entity.removeFromWorld(); + + FXGL.runOnce( + () -> { + if (FXGL.getGameWorld().getEntitiesByType(EntityType.BRICK).isEmpty()) { + GameSystem.nextLevel(); + } + }, + Duration.seconds(0.1)); } } diff --git a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/ScaleChange.java b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/ScaleChange.java index a54f4e3..92c61c6 100644 --- a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/ScaleChange.java +++ b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/ScaleChange.java @@ -1,51 +1,79 @@ package com.github.codestorm.bounceverse.components.behaviors; -import com.almasb.fxgl.entity.Entity; -import com.almasb.fxgl.entity.components.TransformComponent; -import com.github.codestorm.bounceverse.typing.annotations.ForEntity; +import com.almasb.fxgl.time.TimerAction; +import com.github.codestorm.bounceverse.components.Behavior; +import com.github.codestorm.bounceverse.typing.interfaces.Undoable; + +import javafx.util.Duration; + import java.util.ArrayList; import java.util.List; -/** - * - * - *

{@link ScaleChange}

- * - * Hành vi ScaleChange. Có thể {@link #execute(List)} nhiều lần nhưng sẽ không thể stack lên nhau - * (và không làm mới).
- * ScaleChange sẽ áp dụng thông qua {@link Entity#getTransformComponent()} (tức cả view và - * bounding). - * - * @see TransformComponent - */ -@ForEntity({}) -public class ScaleChange extends UndoableBehavior { +/** Thay đổi kích thước entity tạm thời và có thể hoàn tác (undo). */ +public class ScaleChange extends Behavior implements Undoable { + public static final double DONT_CHANGE = 1; private double scaleWidth = DONT_CHANGE; private double scaleHeight = DONT_CHANGE; + private final boolean removeWhenUndo; + private final Duration duration; + private List modified = null; + private TimerAction current; + + @Override + public Duration getDuration() { + return duration; + } + @Override - protected List executeLogic(List data) { - if (scaleWidth <= 0 || scaleHeight <= 0) { - return null; - } - final var transform = entity.getTransformComponent(); + public boolean isRemoveWhenUndo() { + return removeWhenUndo; + } + + @Override + public List getModified() { + return modified; + } + + @Override + public void setModified(List modified) { + this.modified = modified; + } + + @Override + public TimerAction getCurrent() { + return current; + } + + @Override + public void setCurrent(TimerAction current) { + this.current = current; + } + + @Override + public List executeLogic(List data) { + if (scaleWidth <= 0 || scaleHeight <= 0) return null; + var transform = entity.getTransformComponent(); transform.setScaleX(transform.getScaleX() * scaleWidth); transform.setScaleY(transform.getScaleY() * scaleHeight); return new ArrayList<>(); } @Override - protected boolean undoLogic(List data) { - if (scaleWidth <= 0 || scaleHeight <= 0) { - return false; - } - final var transform = entity.getTransformComponent(); + public boolean undoLogic(List data) { + if (scaleWidth <= 0 || scaleHeight <= 0) return false; + var transform = entity.getTransformComponent(); transform.setScaleX(transform.getScaleX() / scaleWidth); transform.setScaleY(transform.getScaleY() / scaleHeight); return true; } + @Override + public void onRemoved() { + undo(); + } + public double getScaleWidth() { return scaleWidth; } @@ -61,4 +89,9 @@ public double getScaleHeight() { public void setScaleHeight(double scaleHeight) { this.scaleHeight = scaleHeight; } + + public ScaleChange(Duration duration) { + this.duration = duration; + this.removeWhenUndo = true; + } } diff --git a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/Special.java b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/Special.java new file mode 100644 index 0000000..550610a --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/Special.java @@ -0,0 +1,21 @@ +package com.github.codestorm.bounceverse.components.behaviors; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.entity.component.Component; +import com.github.codestorm.bounceverse.systems.init.PowerUpSpawner; +import com.github.codestorm.bounceverse.typing.annotations.OnlyForEntity; +import com.github.codestorm.bounceverse.typing.enums.EntityType; + +import javafx.util.Duration; + +/** Khi brick bị phá thì spawn PowerUp. */ +@OnlyForEntity({EntityType.BRICK}) +public class Special extends Component { + + @Override + public void onRemoved() { + var pos = getEntity().getCenter(); + + FXGL.runOnce(() -> PowerUpSpawner.spawnRandom(pos.add(0, 10)), Duration.seconds(0.017)); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/UndoableBehavior.java b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/UndoableBehavior.java deleted file mode 100644 index ab9341a..0000000 --- a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/UndoableBehavior.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.github.codestorm.bounceverse.components.behaviors; - -import com.almasb.fxgl.dsl.FXGL; -import com.almasb.fxgl.time.TimerAction; -import com.github.codestorm.bounceverse.typing.interfaces.CanUndo; -import java.util.List; -import javafx.util.Duration; - -/** - * - * - *

{@link UndoableBehavior}

- * - * {@link Behavior} có thể thực thi và hoàn tác được. - * - * @see CanUndo - */ -public abstract class UndoableBehavior extends Behavior implements CanUndo { - protected Duration duration = Duration.INDEFINITE; - private List modified = null; - private TimerAction current; - - /** - * Có phải hành động đã được thực thi không. - * - * @return {@code true} nếu đã thực thi, {@code false} nếu chưa - */ - public final boolean isActive() { - return current != null && current.isExpired() && modified != null; - } - - /** - * Logic bên trong {@link #execute(List)}. - * - * @param data Dữ liệu truyền vào - * @return {@code null} nếu không thực thi, {@link List} các dữ liệu cần hoàn tác lại sau này - * nếu ngược lại - */ - protected abstract List executeLogic(List data); - - /** - * Logic bên trong {@link CanUndo#undo()}. - * - * @param data Dữ liệu cần hoàn tác - * @return {@code true} nếu cho phép hoàn tác, {@code false} nếu không. - */ - protected abstract boolean undoLogic(List data); - - @Override - public final void execute(List data) { - if (isActive()) { - return; - } - - modified = executeLogic(data); - if (modified != null) { - current = FXGL.getGameTimer().runOnceAfter(this::undo, duration); - } - } - - @Override - public final void undo() { - if (!isActive()) { - return; - } - - if (undoLogic(modified)) { - current = null; - modified = null; - } - } - - public Duration getDuration() { - return duration; - } - - public void setDuration(Duration duration) { - this.duration = duration; - } -} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/brick/BrickDropPowerUp.java b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/brick/BrickDropPowerUp.java new file mode 100644 index 0000000..d8c355a --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/brick/BrickDropPowerUp.java @@ -0,0 +1,24 @@ +package com.github.codestorm.bounceverse.components.behaviors.brick; + +import com.almasb.fxgl.dsl.FXGL; +import com.github.codestorm.bounceverse.components.Behavior; +import com.github.codestorm.bounceverse.typing.annotations.OnlyForEntity; +import com.github.codestorm.bounceverse.typing.enums.EntityType; + +import java.util.List; + +/** Khi brick bị phá thì spawn PowerUp. */ +@OnlyForEntity({EntityType.BRICK}) +public final class BrickDropPowerUp extends Behavior { + // TODO: Thêm Component PowerUpContainer + + @Override + public void execute(List data) { + FXGL.spawn("powerUp", getEntity().getCenter()); + } + + @Override + public void onRemoved() { + execute(List.of()); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/brick/StrongBrickTextureUpdater.java b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/brick/StrongBrickTextureUpdater.java new file mode 100644 index 0000000..7b0066b --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/brick/StrongBrickTextureUpdater.java @@ -0,0 +1,58 @@ +package com.github.codestorm.bounceverse.components.behaviors.brick; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.dsl.components.HealthIntComponent; +import com.almasb.fxgl.entity.component.Component; +import com.github.codestorm.bounceverse.AssetsPath; +import com.github.codestorm.bounceverse.typing.enums.BrickType; + +import javafx.scene.paint.Color; + +/** Tự động đổi texture của Strong Brick dựa theo phần trăm HP còn lại. */ +public class StrongBrickTextureUpdater extends Component { + + private final BrickType brickType = BrickType.STRONG; + private Color color = Color.BLUE; + + @Override + public void onAdded() { + var health = getEntity().getComponent(HealthIntComponent.class); + health.valueProperty().addListener((obs, oldVal, newVal) -> updateTexture()); + updateTexture(); + } + + private void updateTexture() { + var health = getEntity().getComponent(HealthIntComponent.class); + var hpPercent = Math.max(0.0, health.getValue() / (double) health.getMaxValue()); + + // Lấy texture tương ứng với HP còn lại + var colorAssets = AssetsPath.Textures.Bricks.COLORS.get(getColorName()); + var texPath = colorAssets.getTexture(brickType, hpPercent); + + var tex = FXGL.texture(texPath); + + // Giữ nguyên kích thước ban đầu của brick + var width = getEntity().getWidth(); + var height = getEntity().getHeight(); + tex.setFitWidth(width); + tex.setFitHeight(height); + + getEntity().getViewComponent().clearChildren(); + getEntity().getViewComponent().addChild(tex); + } + + private String getColorName() { + if (color.equals(Color.BLUE)) return "blue"; + if (color.equals(Color.GREEN)) return "green"; + if (color.equals(Color.ORANGE)) return "orange"; + if (color.equals(Color.PINK)) return "pink"; + if (color.equals(Color.RED)) return "red"; + if (color.equals(Color.YELLOW)) return "yellow"; + return "blue"; + } + + public StrongBrickTextureUpdater withColor(Color color) { + this.color = color; + return this; + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/paddle/PaddleShooting.java b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/paddle/PaddleShooting.java index a310f86..efd6753 100644 --- a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/paddle/PaddleShooting.java +++ b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/paddle/PaddleShooting.java @@ -3,15 +3,17 @@ import com.almasb.fxgl.core.math.Vec2; import com.almasb.fxgl.dsl.FXGL; import com.almasb.fxgl.entity.SpawnData; -import com.github.codestorm.bounceverse.Utilities.Time.Cooldown; +import com.github.codestorm.bounceverse.components.Behavior; import com.github.codestorm.bounceverse.components.behaviors.Attack; -import com.github.codestorm.bounceverse.components.behaviors.Behavior; -import com.github.codestorm.bounceverse.typing.annotations.ForEntity; +import com.github.codestorm.bounceverse.typing.annotations.OnlyForEntity; import com.github.codestorm.bounceverse.typing.enums.DirectionUnit; import com.github.codestorm.bounceverse.typing.enums.EntityType; -import java.util.List; +import com.github.codestorm.bounceverse.typing.structures.Cooldown; + import javafx.util.Duration; +import java.util.List; + /** * * @@ -19,8 +21,8 @@ * * Khả năng {@link EntityType#PADDLE} có thể bắn ra {@link EntityType#BULLET}. */ -@ForEntity(EntityType.PADDLE) -public class PaddleShooting extends Behavior { +@OnlyForEntity(EntityType.PADDLE) +public final class PaddleShooting extends Behavior { private static final double OFFSET_LEFT = 4; private static final double OFFSET_RIGHT = -8; private static final double OFFSET_HEIGHT = -10; @@ -38,12 +40,12 @@ public PaddleShooting(Duration cooldown) { @Override public void execute(List data) { - if (!cooldown.getCurrent().expired()) { + if (!cooldown.getCurrent().isExpired()) { return; } - double leftX = entity.getX() + OFFSET_LEFT; - double rightX = entity.getRightX() + OFFSET_RIGHT; - double y = entity.getY() + OFFSET_HEIGHT; + var leftX = entity.getX() + OFFSET_LEFT; + var rightX = entity.getRightX() + OFFSET_RIGHT; + var y = entity.getY() + OFFSET_HEIGHT; final var attack = entity.getComponentOptional(Attack.class); if (attack.isEmpty()) { @@ -61,7 +63,7 @@ public void execute(List data) { FXGL.spawn("paddleBullet", leftData); FXGL.spawn("paddleBullet", rightData); - cooldown.getCurrent().makeNew(); + cooldown.getCurrent().createNew(); } public Cooldown getCooldown() { diff --git a/src/main/java/com/github/codestorm/bounceverse/components/behaviors/paddle/ReverseControlComponent.java b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/paddle/ReverseControlComponent.java new file mode 100644 index 0000000..e98d612 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/behaviors/paddle/ReverseControlComponent.java @@ -0,0 +1,11 @@ +package com.github.codestorm.bounceverse.components.behaviors.paddle; + +import com.almasb.fxgl.entity.component.Component; + +/** + * Component đảo ngược điều khiển của paddle trong thời gian ngắn. Chỉ đóng vai trò "flag" để + * PaddleMovement đọc và đảo hướng. + */ +public final class ReverseControlComponent extends Component { + // Mặc định: đảo ngược điều khiển +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/Attributes.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/Attributes.java index 371d526..09eef03 100644 --- a/src/main/java/com/github/codestorm/bounceverse/components/properties/Attributes.java +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/Attributes.java @@ -1,16 +1,15 @@ package com.github.codestorm.bounceverse.components.properties; import com.almasb.fxgl.entity.Entity; -import com.github.codestorm.bounceverse.typing.annotations.ForEntity; +import com.github.codestorm.bounceverse.components.Property; /** * * *

{@link Attributes}

* - * Các chỉ số thuộc tính chung (chưa cụ thể thành class) của {@link Entity}. + * Các chỉ số thuộc tính chung (chưa cụ thể thành class) của {@link Entity} nói chung. */ -@ForEntity({}) public final class Attributes extends Property { public static final int DEFAULT_DEFENSE = 0; private int defense = DEFAULT_DEFENSE; diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/Shield.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/Shield.java index 7a71c8e..8bb31e0 100644 --- a/src/main/java/com/github/codestorm/bounceverse/components/properties/Shield.java +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/Shield.java @@ -1,10 +1,12 @@ package com.github.codestorm.bounceverse.components.properties; import com.almasb.fxgl.entity.Entity; -import com.github.codestorm.bounceverse.typing.annotations.ForEntity; +import com.github.codestorm.bounceverse.components.Property; + +import javafx.geometry.Side; + import java.util.Arrays; import java.util.EnumSet; -import javafx.geometry.Side; /** * @@ -13,8 +15,7 @@ * * Khiên bảo vệ {@link Entity}. Khiên có thể bảo vệ một hoặc nhiều phía khỏi bị tấn công. */ -@ForEntity({}) -public class Shield extends Property { +public final class Shield extends Property { private EnumSet sides = EnumSet.noneOf(Side.class); /** diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/brick/BrickTextureManager.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/brick/BrickTextureManager.java new file mode 100644 index 0000000..57f3c1d --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/brick/BrickTextureManager.java @@ -0,0 +1,86 @@ +package com.github.codestorm.bounceverse.components.properties.brick; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.dsl.components.HealthIntComponent; +import com.almasb.fxgl.entity.component.CoreComponent; +import com.almasb.fxgl.entity.component.Required; +import com.github.codestorm.bounceverse.AssetsPath; +import com.github.codestorm.bounceverse.components.Property; +import com.github.codestorm.bounceverse.typing.annotations.OnlyForEntity; +import com.github.codestorm.bounceverse.typing.enums.BrickType; +import com.github.codestorm.bounceverse.typing.enums.EntityType; + +import javafx.scene.Node; +import javafx.scene.paint.Color; + +/** + * + * + *

{@link BrickTextureManager}

+ * + * Sự thay đổi texture của {@link EntityType#BRICK} theo {@link HealthIntComponent}; + */ +@CoreComponent +@Required(HealthIntComponent.class) +@OnlyForEntity({EntityType.BRICK}) +public final class BrickTextureManager extends Property { + private String oldTexturePath; + private Node oldTexture; + public final BrickType brickType; + public final Color color; + + public BrickTextureManager(BrickType brickType, Color color) { + this.brickType = brickType; + this.color = color; + } + + private static Node makeView(String texturePath, int width, int height) { + var view = FXGL.getAssetLoader().loadTexture(texturePath, width, height); + view.setSmooth(false); + view.setPreserveRatio(false); + return view; + } + + private String getColorName() { + if (color.equals(Color.BLUE)) return "blue"; + if (color.equals(Color.GREEN)) return "green"; + if (color.equals(Color.ORANGE)) return "orange"; + if (color.equals(Color.PINK)) return "pink"; + if (color.equals(Color.RED)) return "red"; + if (color.equals(Color.YELLOW)) return "yellow"; + return "blue"; + } + + @Override + public void onUpdate(double tpf) { + final var health = entity.getComponent(HealthIntComponent.class); + final var percent = health.getValuePercent() / 100; + + // SỬA ĐỔI Ở ĐÂY + final var colorKey = getColorName(); // Chuyển Color object thành String key + final var colorTextures = + AssetsPath.Textures.Bricks.COLORS.get(colorKey); // Dùng key String để tra cứu + + // Đoạn mã còn lại giữ nguyên + final var texturePath = colorTextures.getTexture(brickType, percent); + + final var views = entity.getViewComponent(); + if (oldTexturePath == null) { + oldTexturePath = texturePath; + oldTexture = makeView(texturePath, (int) entity.getWidth(), (int) entity.getHeight()); + views.addChild(oldTexture); + } else { + if (!oldTexturePath.equals(texturePath)) { + views.removeChild(oldTexture); + oldTexturePath = texturePath; + oldTexture = + makeView(texturePath, (int) entity.getWidth(), (int) entity.getHeight()); + views.addChild(oldTexture); + } + } + } + + public Color getColor() { + return this.color; + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/paddle/PaddlePowerComponent.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/paddle/PaddlePowerComponent.java new file mode 100644 index 0000000..1bf2d72 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/paddle/PaddlePowerComponent.java @@ -0,0 +1,65 @@ +package com.github.codestorm.bounceverse.components.properties.paddle; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.entity.component.Component; +import com.github.codestorm.bounceverse.components.properties.powerup.ball.FastBallPowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.ball.MultipleBallPowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.misc.ExtraLifePowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.misc.ShieldPowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.paddle.DuplicatePaddlePowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.paddle.GunPowerUp; +import com.github.codestorm.bounceverse.typing.structures.Cooldown; + +import javafx.util.Duration; + +public class PaddlePowerComponent extends Component { + + private final Cooldown powerCooldown = new Cooldown(Duration.seconds(20)); + private static final Duration POWER_VISUAL_DURATION = Duration.seconds(1.0); + + /** Phương thức chính để kích hoạt sức mạnh đặc biệt của paddle. */ + public void activatePower() { + if (!powerCooldown.getCurrent().isExpired()) { + System.out.println("Power is on cooldown!"); + return; + } + + var paddle = getEntity(); + var color = FXGL.gets("paddleColor"); + var viewManager = paddle.getComponent(PaddleViewManager.class); + + System.out.println("Activating power for " + color + " paddle!"); + + switch (color) { + case "red": + new GunPowerUp().apply(paddle); + break; + case "green": + new ExtraLifePowerUp().apply(paddle); + break; + case "blue": + new ShieldPowerUp().apply(paddle); + break; + case "orange": + new MultipleBallPowerUp().apply(paddle); + break; + case "pink": + new FastBallPowerUp().apply(paddle); + break; + case "yellow": + new DuplicatePaddlePowerUp().apply(paddle); + break; + default: + return; + } + + viewManager.setPowerState(); + FXGL.getGameTimer().runOnceAfter(viewManager::setNormalState, POWER_VISUAL_DURATION); + + powerCooldown.getCurrent().createNew(); + } + + public Cooldown getPowerCooldown() { + return powerCooldown; + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/paddle/PaddleViewManager.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/paddle/PaddleViewManager.java new file mode 100644 index 0000000..8c42c81 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/paddle/PaddleViewManager.java @@ -0,0 +1,133 @@ +package com.github.codestorm.bounceverse.components.properties.paddle; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.entity.Entity; +import com.almasb.fxgl.entity.component.Component; +import com.almasb.fxgl.entity.components.BoundingBoxComponent; +import com.almasb.fxgl.physics.BoundingShape; +import com.almasb.fxgl.physics.HitBox; +import com.almasb.fxgl.texture.Texture; + +import java.util.ArrayList; +import java.util.List; + +/** + * Component duy nhất quản lý TOÀN BỘ hình ảnh và kích thước của Paddle. Nó thay thế cho cả + * PaddleSizeManager và PaddleTextureManager. + */ +public class PaddleViewManager extends Component { + + private String color; + private Texture paddleTexture; + private BoundingBoxComponent bbox; + + private double originalWidth; + private double originalHeight; + + private String currentState = "normal"; + private double currentSizeFactor = 1.0; + + private final List clones = new ArrayList<>(); + + // Các hệ số kích thước cho từng trạng thái + private static final double EXPAND_FACTOR = 1.5; + private static final double SHRINK_FACTOR = 0.7; + + @Override + public void onAdded() { + this.color = FXGL.gets("paddleColor"); + this.bbox = entity.getBoundingBoxComponent(); + + this.originalWidth = entity.getWidth(); + this.originalHeight = entity.getHeight(); + + // Khởi tạo Texture và thêm vào entity + this.paddleTexture = FXGL.texture(getTexturePath("normal")); + entity.getViewComponent().addChild(paddleTexture); + + // Cập nhật trạng thái ban đầu + updateViewState("normal", 1.0); + } + + // Các hàm công khai để các PowerUp gọi + public void setNormalState() { + updateViewState("normal", 1.0); + } + + public void setExpandState() { + updateViewState("expand", EXPAND_FACTOR); + } + + public void setShrinkState() { + updateViewState("shrink", SHRINK_FACTOR); + } + + public void setPowerState() { + updateViewState("power", 1.0); + } + + public void reset() { + setNormalState(); + } + + public void registerClone(Entity clone) { + clones.add(clone); + // Ngay khi đăng ký, đồng bộ hóa trạng thái hiện tại cho bản sao + updateViewState(currentState, currentSizeFactor); + } + + public void clearClones() { + clones.clear(); + } + + /** + * Hàm trung tâm, chịu trách nhiệm cập nhật MỌI THỨ. + * + * @param state Tên trạng thái ("normal", "expand", "shrink") + * @param sizeFactor Hệ số nhân kích thước (1.0 cho normal) + */ + private void updateViewState(String state, double sizeFactor) { + this.currentState = state; + this.currentSizeFactor = sizeFactor; + + var newWidth = originalWidth * sizeFactor; + + bbox.clearHitBoxes(); + bbox.addHitBox(new HitBox(BoundingShape.box(newWidth, originalHeight))); + paddleTexture.setImage(FXGL.getAssetLoader().loadImage(getTexturePath(state))); + paddleTexture.setFitWidth(newWidth); + paddleTexture.setFitHeight(originalHeight); + + for (var clone : clones) { + if (clone.isActive()) { + // Cập nhật hitbox cho bản sao + clone.getBoundingBoxComponent().clearHitBoxes(); + clone.getBoundingBoxComponent() + .addHitBox(new HitBox(BoundingShape.box(newWidth, originalHeight))); + + // Cập nhật hình ảnh cho bản sao + var cloneTexture = (Texture) clone.getViewComponent().getChildren().getFirst(); + cloneTexture.setImage(FXGL.getAssetLoader().loadImage(getTexturePath(state))); + cloneTexture.setFitWidth(newWidth); + cloneTexture.setFitHeight(originalHeight); + } + } + + paddleTexture.setImage(FXGL.getAssetLoader().loadImage(getTexturePath(state))); + + paddleTexture.setFitWidth(newWidth); + paddleTexture.setFitHeight(originalHeight); + } + + private String getTexturePath(String state) { + var colorCapitalized = color.substring(0, 1).toUpperCase() + color.substring(1); + String textureName; + if ("normal".equals(state)) { + textureName = colorCapitalized + " Paddle.png"; + } else { + var stateCapitalized = state.substring(0, 1).toUpperCase() + state.substring(1); + textureName = colorCapitalized + " " + stateCapitalized + " Paddle.png"; + } + return "paddle/" + state + "/" + color + "/" + textureName; + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/PowerUp.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/PowerUp.java new file mode 100644 index 0000000..0ec3b1c --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/PowerUp.java @@ -0,0 +1,17 @@ +package com.github.codestorm.bounceverse.components.properties.powerup; + +import com.almasb.fxgl.entity.Entity; +import com.almasb.fxgl.entity.component.Component; + +/** Base class cho mọi Power-Up có thể áp dụng hiệu ứng. */ +public abstract class PowerUp extends Component { + + protected final String name; + + protected PowerUp(String name) { + this.name = name; + } + + /** Kích hoạt hiệu ứng lên entity nhận (paddle, ball, v.v.) */ + public abstract void apply(Entity target); +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/PowerUpContainer.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/PowerUpContainer.java new file mode 100644 index 0000000..4d0e777 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/PowerUpContainer.java @@ -0,0 +1,63 @@ +package com.github.codestorm.bounceverse.components.properties.powerup; + +import com.almasb.fxgl.entity.Entity; +import com.almasb.fxgl.entity.component.Component; +import com.almasb.fxgl.entity.component.CoreComponent; +import com.github.codestorm.bounceverse.components.Property; +import com.github.codestorm.bounceverse.typing.annotations.OnlyForEntity; +import com.github.codestorm.bounceverse.typing.enums.EntityType; +import com.google.common.collect.MutableClassToInstanceMap; + +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +/** + * + * + *

{@link PowerUpContainer}

+ * + * Giống như một cái túi, nơi lưu trữ {@link Component} bên trong {@link Entity} và không kích hoạt + * logic của các component đó. + */ +@OnlyForEntity({EntityType.POWER_UP}) +@CoreComponent +public final class PowerUpContainer extends Property { + private MutableClassToInstanceMap container = MutableClassToInstanceMap.create(); + + /** + * Gán trực tiếp các {@link Component} lên trên {@link Entity}. + * + * @param entity Entity. + */ + public void addTo(Entity entity) { + for (var entry : container.entrySet()) { + final var component = entry.getValue(); + entity.addComponent(component); + } + } + + /** + * Thêm các {@link Component} vào {@link #container}. + * + * @param components Các component + * @see MutableClassToInstanceMap#putAll(Map) + */ + public void put(@NotNull Component... components) { + for (var component : components) { + container.put(component.getClass(), component); + } + } + + public PowerUpContainer(Component... components) { + put(components); + } + + public MutableClassToInstanceMap getContainer() { + return container; + } + + public void setContainer(MutableClassToInstanceMap container) { + this.container = container; + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/PowerUpManager.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/PowerUpManager.java new file mode 100644 index 0000000..fa9835a --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/PowerUpManager.java @@ -0,0 +1,161 @@ +package com.github.codestorm.bounceverse.components.properties.powerup; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.time.TimerAction; + +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; +import javafx.util.Duration; + +import java.util.HashMap; +import java.util.Map; + +/** + * Quản lý các Power-Up đang hoạt động: thời gian, gia hạn, và hiển thị HUD đếm ngược. Dùng + * singleton. + */ +public final class PowerUpManager { + + /** + * Lớp nội bộ để theo dõi mỗi power-up đang hoạt động. Sửa đổi: 'timer' không còn là final để có + * thể cập nhật khi cộng dồn thời gian. + */ + private static final class ActivePowerUp { + TimerAction timer; + final Runnable onExpire; + final Text label; + double timeLeft; + + ActivePowerUp(TimerAction timer, Runnable onExpire, Text label, double durationSeconds) { + this.timer = timer; + this.onExpire = onExpire; + this.label = label; + this.timeLeft = durationSeconds; + } + } + + private final Map activePowerUps = new HashMap<>(); + private final VBox hudBox = new VBox(6); + private boolean hudAdded = false; + + private static final PowerUpManager INSTANCE = new PowerUpManager(); + + private PowerUpManager() { + hudBox.setTranslateX(FXGL.getAppWidth() - 220); + hudBox.setTranslateY(20); + } + + public static PowerUpManager getInstance() { + return INSTANCE; + } + + private void ensureHUDAdded() { + if (!hudAdded) { + FXGL.getGameScene().addUINode(hudBox); + hudAdded = true; + } + } + + /** + * Kích hoạt hoặc gia hạn (cộng dồn) thời gian cho một Power-Up. + * + * @param name Tên power-up + * @param duration Thời gian hiệu lực của power-up mới + * @param onActivate Logic khi kích hoạt LẦN ĐẦU + * @param onExpire Logic khi hết hiệu lực hoàn toàn + */ + public void activate(String name, Duration duration, Runnable onActivate, Runnable onExpire) { + ensureHUDAdded(); + var newDurationSeconds = duration.toSeconds(); + + if (activePowerUps.containsKey(name)) { + var existing = activePowerUps.get(name); + + existing.timer.expire(); + + var newTimeLeft = existing.timeLeft + newDurationSeconds; + existing.timeLeft = newTimeLeft; + + existing.timer = + FXGL.getGameTimer() + .runOnceAfter( + () -> { + onExpire.run(); + hudBox.getChildren().remove(existing.label); + activePowerUps.remove(name); + }, + Duration.seconds(newTimeLeft)); + + return; + } + + onActivate.run(); + + var label = + FXGL.getUIFactoryService() + .newText(name + " " + String.format("%.1fs", newDurationSeconds), 18); + label.getStyleClass().add("powerup-text"); + hudBox.getChildren().add(label); + + var timer = + FXGL.getGameTimer() + .runOnceAfter( + () -> { + onExpire.run(); + hudBox.getChildren().remove(label); + activePowerUps.remove(name); + }, + duration); + + activePowerUps.put(name, new ActivePowerUp(timer, onExpire, label, newDurationSeconds)); + } + + public void onUpdate(double tpf) { + ensureHUDAdded(); + + for (var entry : activePowerUps.values()) { + entry.timeLeft -= tpf; + if (entry.timeLeft < 0) entry.timeLeft = 0; + + var powerUpName = entry.label.getText().split(" ")[0]; + entry.label.setText(String.format("%s %.1fs", powerUpName, entry.timeLeft)); + } + } + + public void clearAll() { + ensureHUDAdded(); + + activePowerUps + .values() + .forEach( + entry -> { + if (!entry.timer.isExpired()) { + entry.timer.expire(); + } + entry.onExpire.run(); + hudBox.getChildren().remove(entry.label); + }); + activePowerUps.clear(); + } + + /** + * Hủy bỏ và dọn dẹp một Power-Up cụ thể đang hoạt động theo tên. + * + * @param name Tên của power-up cần hủy + */ + public void clearPowerUp(String name) { + if (activePowerUps.containsKey(name)) { + var existing = activePowerUps.get(name); + + if (!existing.timer.isExpired()) { + existing.timer.expire(); + } + + existing.onExpire.run(); + + hudBox.getChildren().remove(existing.label); + + activePowerUps.remove(name); + } + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/ball/FastBallPowerUp.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/ball/FastBallPowerUp.java new file mode 100644 index 0000000..38d460d --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/ball/FastBallPowerUp.java @@ -0,0 +1,50 @@ +package com.github.codestorm.bounceverse.components.properties.powerup.ball; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.entity.Entity; +import com.almasb.fxgl.physics.PhysicsComponent; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUpManager; +import com.github.codestorm.bounceverse.systems.init.GameSystem; +import com.github.codestorm.bounceverse.typing.enums.EntityType; + +import javafx.util.Duration; + +/** Làm nhanh bóng trong một khoảng thời gian. */ +public class FastBallPowerUp extends PowerUp { + + private static final Duration DURATION = Duration.seconds(6); + private static final double SPEED_MULTIPLIER = 1.5; + + public FastBallPowerUp() { + super("FastBall"); + } + + @Override + public void apply(Entity paddle) { + PowerUpManager.getInstance().activate(name, DURATION, this::speedUpBalls, this::resetBalls); + } + + private void speedUpBalls() { + var currentSpeed = FXGL.getd("ballSpeed"); + FXGL.set("ballSpeed", currentSpeed * SPEED_MULTIPLIER); + updateAllBallSpeeds(); + } + + private void resetBalls() { + FXGL.set("ballSpeed", GameSystem.Variables.DEFAULT_BALL_SPEED); + updateAllBallSpeeds(); + } + + private void updateAllBallSpeeds() { + var newSpeed = FXGL.getd("ballSpeed"); + FXGL.getGameWorld() + .getEntitiesByType(EntityType.BALL) + .forEach( + ball -> { + var physics = ball.getComponent(PhysicsComponent.class); + physics.setLinearVelocity( + physics.getLinearVelocity().normalize().multiply(newSpeed)); + }); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/ball/MultipleBallPowerUp.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/ball/MultipleBallPowerUp.java new file mode 100644 index 0000000..c11fefb --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/ball/MultipleBallPowerUp.java @@ -0,0 +1,71 @@ +package com.github.codestorm.bounceverse.components.properties.powerup.ball; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.entity.Entity; +import com.almasb.fxgl.entity.SpawnData; +import com.almasb.fxgl.physics.PhysicsComponent; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUp; +import com.github.codestorm.bounceverse.typing.enums.EntityType; + +import javafx.geometry.Point2D; + +import java.util.List; + +/** + * PowerUp nhân đôi toàn bộ bóng hiện có. Mỗi bóng sinh thêm 1 bóng mới tách từ vị trí của nó và bay + * lệch nhẹ sang 2 hướng. + */ +public final class MultipleBallPowerUp extends PowerUp { + + public MultipleBallPowerUp() { + super("MultipleBall"); + } + + @Override + public void apply(Entity paddle) { + var balls = FXGL.getGameWorld().getEntitiesByType(EntityType.BALL); + var currentBalls = List.copyOf(balls); + + for (var ball : currentBalls) { + ball.getComponentOptional(PhysicsComponent.class) + .ifPresent( + phys -> { + var pos = ball.getCenter(); + var velocity = phys.getLinearVelocity(); + + if (velocity.magnitude() < 1e-3) return; + + // chỉ nhân đôi: mỗi bóng cũ sinh thêm 1 bản sao duy nhất + spawnDuplicateBall(pos, velocity); + }); + } + } + + private void spawnDuplicateBall(Point2D pos, Point2D velocity) { + // lệch nhẹ góc bay để tránh trùng hướng + var newVelocity = rotateVector(velocity); + + // spawn tại vị trí bóng gốc (dịch nhẹ 1 chút) + var spawnPos = pos.add(newVelocity.normalize().multiply(8)); + + var newBall = FXGL.spawn("ball", new SpawnData(spawnPos.getX(), spawnPos.getY())); + + newBall.getComponentOptional(PhysicsComponent.class) + .ifPresent( + newPhys -> { + newPhys.setLinearVelocity(newVelocity); + + // giữ velocity ổn định sau 1 frame + FXGL.runOnce( + () -> newPhys.setLinearVelocity(newVelocity), + javafx.util.Duration.millis(20)); + }); + } + + private static Point2D rotateVector(Point2D v) { + var rad = Math.toRadians(15); + var cos = Math.cos(rad); + var sin = Math.sin(rad); + return new Point2D(v.getX() * cos - v.getY() * sin, v.getX() * sin + v.getY() * cos); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/ball/SlowBallPowerUp.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/ball/SlowBallPowerUp.java new file mode 100644 index 0000000..665bbaf --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/ball/SlowBallPowerUp.java @@ -0,0 +1,50 @@ +package com.github.codestorm.bounceverse.components.properties.powerup.ball; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.entity.Entity; +import com.almasb.fxgl.physics.PhysicsComponent; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUpManager; +import com.github.codestorm.bounceverse.systems.init.GameSystem; +import com.github.codestorm.bounceverse.typing.enums.EntityType; + +import javafx.util.Duration; + +/** Làm chậm tốc độ tất cả bóng tạm thời. */ +public class SlowBallPowerUp extends PowerUp { + + private static final Duration DURATION = Duration.seconds(6); + private static final double SPEED_MULTIPLIER = 0.7; + + public SlowBallPowerUp() { + super("SlowBall"); + } + + @Override + public void apply(Entity paddle) { + PowerUpManager.getInstance().activate(name, DURATION, this::slowBalls, this::resetBalls); + } + + private void slowBalls() { + var currentSpeed = FXGL.getd("ballSpeed"); + FXGL.set("ballSpeed", currentSpeed * SPEED_MULTIPLIER); + updateAllBallSpeeds(); + } + + private void resetBalls() { + FXGL.set("ballSpeed", GameSystem.Variables.DEFAULT_BALL_SPEED); + updateAllBallSpeeds(); + } + + private void updateAllBallSpeeds() { + var newSpeed = FXGL.getd("ballSpeed"); + FXGL.getGameWorld() + .getEntitiesByType(EntityType.BALL) + .forEach( + ball -> { + var physics = ball.getComponent(PhysicsComponent.class); + physics.setLinearVelocity( + physics.getLinearVelocity().normalize().multiply(newSpeed)); + }); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/misc/ExtraLifePowerUp.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/misc/ExtraLifePowerUp.java new file mode 100644 index 0000000..e2ed909 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/misc/ExtraLifePowerUp.java @@ -0,0 +1,21 @@ +package com.github.codestorm.bounceverse.components.properties.powerup.misc; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.entity.Entity; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUp; +import com.github.codestorm.bounceverse.typing.structures.HealthIntValue; + +/** Power-up cộng thêm một mạng cho người chơi. Hiệu ứng xảy ra ngay lập tức. */ +public final class ExtraLifePowerUp extends PowerUp { + + public ExtraLifePowerUp() { + super("ExtraLife"); + } + + @Override + public void apply(Entity target) { + HealthIntValue lives = FXGL.getWorldProperties().getObject("lives"); + + lives.restore(1); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/misc/ShieldPowerUp.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/misc/ShieldPowerUp.java new file mode 100644 index 0000000..86e26e4 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/misc/ShieldPowerUp.java @@ -0,0 +1,52 @@ +package com.github.codestorm.bounceverse.components.properties.powerup.misc; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.entity.Entity; +import com.almasb.fxgl.physics.BoundingShape; +import com.almasb.fxgl.physics.HitBox; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUpManager; +import com.github.codestorm.bounceverse.typing.enums.PowerUpType; + +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import javafx.util.Duration; + +/** Tạo một tấm chắn ở dưới đáy ngăn bóng rơi trong vài giây. */ +public class ShieldPowerUp extends PowerUp { + + private static final Duration DURATION = Duration.seconds(8); + private Entity shieldEntity; + + public ShieldPowerUp() { + super("Shield"); + } + + @Override + public void apply(Entity paddle) { + PowerUpManager.getInstance() + .activate(name, DURATION, this::spawnShield, this::removeShield); + } + + private void spawnShield() { + double appWidth = FXGL.getAppWidth(); + double appHeight = FXGL.getAppHeight(); + + var shieldHeight = 20.0; + + var view = new Rectangle(appWidth, 8, Color.AQUA); + + shieldEntity = + FXGL.entityBuilder() + .at(0, appHeight - 10) + .type(PowerUpType.SHIELD) + .view(view) + .bbox(new HitBox(BoundingShape.box(appWidth, shieldHeight))) + .collidable() + .buildAndAttach(); + } + + private void removeShield() { + if (shieldEntity != null) shieldEntity.removeFromWorld(); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/paddle/DuplicatePaddlePowerUp.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/paddle/DuplicatePaddlePowerUp.java new file mode 100644 index 0000000..07cff82 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/paddle/DuplicatePaddlePowerUp.java @@ -0,0 +1,98 @@ +package com.github.codestorm.bounceverse.components.properties.powerup.paddle; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.entity.Entity; +import com.almasb.fxgl.physics.BoundingShape; +import com.almasb.fxgl.physics.HitBox; +import com.almasb.fxgl.physics.PhysicsComponent; +import com.almasb.fxgl.physics.box2d.dynamics.BodyType; +import com.github.codestorm.bounceverse.components.properties.paddle.PaddleViewManager; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUpManager; +import com.github.codestorm.bounceverse.typing.enums.EntityType; + +import javafx.util.Duration; + +import java.util.ArrayList; +import java.util.List; + +public final class DuplicatePaddlePowerUp extends PowerUp { + + private static final Duration DURATION = Duration.seconds(10); + private static final double PADDLE_GAP = 25.0; + + public static final String CLONES_LIST_KEY = "clonedPaddles"; + + public DuplicatePaddlePowerUp() { + super("DuplicatePaddle"); + } + + @Override + public void apply(Entity paddle) { + PowerUpManager.getInstance().clearPowerUp(this.name); + PowerUpManager.getInstance() + .activate(name, DURATION, () -> spawnClones(paddle), () -> removeClones(paddle)); + } + + private void spawnClones(Entity mainPaddle) { + var mainViewManager = mainPaddle.getComponent(PaddleViewManager.class); + var width = mainPaddle.getWidth(); + var mainPaddleX = mainPaddle.getX(); + var mainPaddleY = mainPaddle.getY(); + + // Tạo bản sao bên trái + var leftClone = createClone(mainPaddle); + // THAY ĐỔI QUAN TRỌNG: KHÔNG DÙNG .bind(), chỉ đặt vị trí ban đầu + leftClone.setPosition(mainPaddleX - width - PADDLE_GAP, mainPaddleY); + FXGL.getGameWorld().addEntity(leftClone); + mainViewManager.registerClone(leftClone); + + // Tạo bản sao bên phải + var rightClone = createClone(mainPaddle); + // THAY ĐỔI QUAN TRỌNG: KHÔNG DÙNG .bind(), chỉ đặt vị trí ban đầu + rightClone.setPosition(mainPaddleX + width + PADDLE_GAP, mainPaddleY); + FXGL.getGameWorld().addEntity(rightClone); + mainViewManager.registerClone(rightClone); + + // Lưu danh sách vào biến chung để InputSystem điều khiển + List clones = new ArrayList<>(); + clones.add(leftClone); + clones.add(rightClone); + FXGL.set(CLONES_LIST_KEY, clones); + } + + private void removeClones(Entity mainPaddle) { + if (mainPaddle != null && mainPaddle.isActive()) { + mainPaddle.getComponent(PaddleViewManager.class).clearClones(); + } + + if (FXGL.getWorldProperties().exists(CLONES_LIST_KEY)) { + List clones = FXGL.geto(CLONES_LIST_KEY); + for (var clone : clones) { + if (clone.isActive()) { + // Không cần unbind vì chúng ta không bind ngay từ đầu + clone.removeFromWorld(); + } + } + FXGL.getWorldProperties().remove(CLONES_LIST_KEY); + } + } + + private Entity createClone(Entity mainPaddle) { + var color = FXGL.gets("paddleColor"); + var colorCapitalized = color.substring(0, 1).toUpperCase() + color.substring(1); + var texturePath = "paddle/power/" + color + "/" + colorCapitalized + " Power Paddle.png"; + var texture = FXGL.texture(texturePath, mainPaddle.getWidth(), mainPaddle.getHeight()); + + var physics = new PhysicsComponent(); + physics.setBodyType(BodyType.KINEMATIC); + + return FXGL.entityBuilder() + .type(EntityType.PADDLE_CLONE) + .view(texture) + .bbox(new HitBox(BoundingShape.box(mainPaddle.getWidth(), mainPaddle.getHeight()))) + .collidable() + .with(physics) + .build(); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/paddle/ExpandPaddlePowerUp.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/paddle/ExpandPaddlePowerUp.java new file mode 100644 index 0000000..82e2c7e --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/paddle/ExpandPaddlePowerUp.java @@ -0,0 +1,26 @@ +package com.github.codestorm.bounceverse.components.properties.powerup.paddle; + +import com.almasb.fxgl.entity.Entity; +import com.github.codestorm.bounceverse.components.properties.paddle.PaddleViewManager; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUpManager; + +import javafx.util.Duration; + +/** Power-Up mở rộng Paddle tạm thời. */ +public final class ExpandPaddlePowerUp extends PowerUp { + + private static final Duration DURATION = Duration.seconds(8); + + public ExpandPaddlePowerUp() { + super("ExpandPaddle"); + } + + @Override + public void apply(Entity paddle) { + var viewManager = paddle.getComponent(PaddleViewManager.class); + + PowerUpManager.getInstance() + .activate(name, DURATION, viewManager::setExpandState, viewManager::setNormalState); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/paddle/GunPowerUp.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/paddle/GunPowerUp.java new file mode 100644 index 0000000..6852afa --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/paddle/GunPowerUp.java @@ -0,0 +1,107 @@ +package com.github.codestorm.bounceverse.components.properties.powerup.paddle; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.entity.Entity; +import com.almasb.fxgl.time.TimerAction; +import com.github.codestorm.bounceverse.components.behaviors.paddle.PaddleShooting; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUpManager; + +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import javafx.util.Duration; + +// Power up tạo ra gun. +public final class GunPowerUp extends PowerUp { + + private static final Duration DURATION = Duration.seconds(10); + private static final double GUN_WIDTH = 6; + private static final double GUN_HEIGHT = 10; + private static final double OFFSET_Y = -10; + private static final double OFFSET_X_LEFT = 4; + private static final double OFFSET_X_RIGHT = 4; + + public GunPowerUp() { + super("Gun"); + } + + @Override + public void apply(Entity paddle) { + PowerUpManager.getInstance().clearPowerUp(this.name); + + PowerUpManager.getInstance() + .activate( + name, + DURATION, + () -> { + var shooting = + paddle.getComponentOptional(PaddleShooting.class) + .orElseGet( + () -> { + var newShooting = new PaddleShooting(0.25); + paddle.addComponent(newShooting); + return newShooting; + }); + + var leftGun = createGunEntity(paddle); + var rightGun = createGunEntity(paddle); + + var pT = paddle.getTransformComponent(); + var lT = leftGun.getTransformComponent(); + var rT = rightGun.getTransformComponent(); + + lT.xProperty().bind(pT.xProperty().add(OFFSET_X_LEFT)); + lT.yProperty().bind(pT.yProperty().add(OFFSET_Y)); + rT.xProperty() + .bind( + pT.xProperty() + .add( + paddle.getBoundingBoxComponent() + .widthProperty()) + .subtract(GUN_WIDTH) + .subtract(OFFSET_X_RIGHT)); + rT.yProperty().bind(pT.yProperty().add(OFFSET_Y)); + + var shootingTask = + FXGL.getGameTimer() + .runAtInterval( + () -> shooting.execute(null), + Duration.seconds(0.25)); + + // Lưu lại các đối tượng cần dọn dẹp vào paddle + paddle.setProperty("gun.left", leftGun); + paddle.setProperty("gun.right", rightGun); + paddle.setProperty("gun.task", shootingTask); + }, + () -> { + Entity leftGun = paddle.getObject("gun.left"); + Entity rightGun = paddle.getObject("gun.right"); + TimerAction shootingTask = paddle.getObject("gun.task"); + + if (shootingTask != null) { + shootingTask.expire(); + } + + if (leftGun != null && leftGun.isActive()) { + leftGun.getTransformComponent().xProperty().unbind(); + leftGun.getTransformComponent().yProperty().unbind(); + leftGun.removeFromWorld(); + } + if (rightGun != null && rightGun.isActive()) { + rightGun.getTransformComponent().xProperty().unbind(); + rightGun.getTransformComponent().yProperty().unbind(); + rightGun.removeFromWorld(); + } + if (paddle.hasComponent(PaddleShooting.class)) { + paddle.removeComponent(PaddleShooting.class); + } + }); + } + + private Entity createGunEntity(Entity owner) { + return FXGL.entityBuilder() + .view(new Rectangle(GUN_WIDTH, GUN_HEIGHT, Color.DARKRED)) + .zIndex(owner.getZIndex() + 1) + .buildAndAttach(); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/paddle/ReversePaddlePowerUp.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/paddle/ReversePaddlePowerUp.java new file mode 100644 index 0000000..8396896 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/paddle/ReversePaddlePowerUp.java @@ -0,0 +1,38 @@ +package com.github.codestorm.bounceverse.components.properties.powerup.paddle; + +import com.almasb.fxgl.entity.Entity; +import com.github.codestorm.bounceverse.components.behaviors.paddle.ReverseControlComponent; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUpManager; + +import javafx.util.Duration; + +/** Power-Up: Đảo ngược điều khiển của paddle trong thời gian ngắn. */ +public final class ReversePaddlePowerUp extends PowerUp { + + private static final Duration DURATION = Duration.seconds(10); + + public ReversePaddlePowerUp() { + super("ReversePaddle"); + } + + @Override + public void apply(Entity paddle) { + PowerUpManager.getInstance() + .activate( + name, + DURATION, + () -> { + // Nếu paddle chưa có component thì thêm + if (!paddle.hasComponent(ReverseControlComponent.class)) { + paddle.addComponent(new ReverseControlComponent()); + } + }, + () -> { + // Khi hết hạn thì gỡ component nếu còn + if (paddle.hasComponent(ReverseControlComponent.class)) { + paddle.removeComponent(ReverseControlComponent.class); + } + }); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/paddle/ShrinkPaddlePowerUp.java b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/paddle/ShrinkPaddlePowerUp.java new file mode 100644 index 0000000..7087c2d --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/components/properties/powerup/paddle/ShrinkPaddlePowerUp.java @@ -0,0 +1,26 @@ +package com.github.codestorm.bounceverse.components.properties.powerup.paddle; + +import com.almasb.fxgl.entity.Entity; +import com.github.codestorm.bounceverse.components.properties.paddle.PaddleViewManager; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUpManager; + +import javafx.util.Duration; + +/** Power-Up thu nhỏ Paddle tạm thời. */ +public final class ShrinkPaddlePowerUp extends PowerUp { + + private static final Duration DURATION = Duration.seconds(8); + + public ShrinkPaddlePowerUp() { + super("ShrinkPaddle"); + } + + @Override + public void apply(Entity paddle) { + var viewManager = paddle.getComponent(PaddleViewManager.class); + + PowerUpManager.getInstance() + .activate(name, DURATION, viewManager::setShrinkState, viewManager::setNormalState); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/core/LaunchOptions.java b/src/main/java/com/github/codestorm/bounceverse/core/LaunchOptions.java deleted file mode 100644 index 688a754..0000000 --- a/src/main/java/com/github/codestorm/bounceverse/core/LaunchOptions.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.github.codestorm.bounceverse.core; - -import com.github.codestorm.bounceverse.Utilities; - -/** - * - * - *

{@link LaunchOptions}

- * - * Các tùy chọn khi khởi động được áp dụng trong game.
- * Sử dụng {@link #load(String...)} để tải các options. - */ -public final class LaunchOptions { - private static boolean debug = false; - - private LaunchOptions() {} - - /** - * Tải các launch options từ Command-line Arguments. - * - * @param args Command-Line Arguments - được truyền vào từ hàm {@code main()} - */ - public static void load(String... args) { - final var map = Utilities.IO.parseArgs(args, null, null); - - debug = Boolean.parseBoolean(map.getOrDefault("debug", "false")); - } - - public static boolean isDebug() { - return debug; - } -} diff --git a/src/main/java/com/github/codestorm/bounceverse/core/SettingsManager.java b/src/main/java/com/github/codestorm/bounceverse/core/SettingsManager.java deleted file mode 100644 index f557990..0000000 --- a/src/main/java/com/github/codestorm/bounceverse/core/SettingsManager.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.github.codestorm.bounceverse.core; - -import com.almasb.fxgl.app.ApplicationMode; -import com.almasb.fxgl.app.GameSettings; -import com.github.codestorm.bounceverse.Utilities; -import com.github.codestorm.bounceverse.factory.SceneFactory; -import java.io.IOException; - -/** - * - * - *

{@link SettingsManager}

- * - * Nơi quản lý các thiết lập trong game. - * - * @see GameSettings - */ -public final class SettingsManager { - private SettingsManager() {} - - /** - * Tải các settings từ file đã thiết lập vào CTDL. - * - *

Chú ý: - * - *
    - *
  • Cần load {@link LaunchOptions#load(String...)} trước khi tải. - *
- * - * @param target Nơi tải vào - * @throws IOException if an error occurred when reading from the input stream. - */ - public static void load(GameSettings target) throws IOException { - final var gameSettings = Utilities.IO.loadProperties("/settings.properties"); - final var userSettings = UserSetting.getSetting(); - - // ? General - target.setTitle(gameSettings.getProperty("general.name")); - target.setVersion(gameSettings.getProperty("general.version")); - target.setCredits(Utilities.IO.readTextFile("credits.txt")); - target.setApplicationMode( - Boolean.parseBoolean(gameSettings.getProperty("general.devMode")) - ? ApplicationMode.DEVELOPER - : (LaunchOptions.isDebug()) - ? ApplicationMode.DEBUG - : ApplicationMode.RELEASE); - - // ? Display - target.setWidth(userSettings.getVideo().getWidth()); - target.setHeight(userSettings.getVideo().getHeight()); - target.setFullScreenAllowed(true); - - // ? In-game - target.setSceneFactory(new SceneFactory()); - target.setMainMenuEnabled(true); - target.setIntroEnabled(true); - } -} diff --git a/src/main/java/com/github/codestorm/bounceverse/core/UserSetting.java b/src/main/java/com/github/codestorm/bounceverse/core/UserSetting.java deleted file mode 100644 index 097d63f..0000000 --- a/src/main/java/com/github/codestorm/bounceverse/core/UserSetting.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.github.codestorm.bounceverse.core; - -import com.almasb.fxgl.logging.Logger; -import com.moandjiezana.toml.Toml; -import com.moandjiezana.toml.TomlWriter; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; - -/** - * - * - *

{@link UserSetting}

- * - * Thiết lập game của người chơi. - */ -public final class UserSetting { - private static final String FORMAT = "toml"; - private Video video = new Video(); - - private UserSetting() {} - - /** - * Lấy tên file setting tương ứng của người chơi. - * - * @return Tên file - */ - public static String getFilename() { - final String username = System.getProperty("user.name"); - return String.format("config.%s.%s", username, FORMAT); - } - - /** - * Lấy địa chỉ file setting tương ứng của người chơi. - * - * @return Địa chỉ file setting tuyệt đối - */ - public static Path getFilepath() { - return Paths.get(getFilename()).toAbsolutePath(); - } - - /** - * Lấy thiết lập game của người chơi. - * - * @return Thiết lập game của người chơi - */ - public static UserSetting getSetting() { - final var filepath = getFilepath(); - try { - final var file = new Toml().read(filepath.toString()); - return file.to(UserSetting.class); - } catch (IllegalStateException e) { - Logger.get(UserSetting.class) - .warning( - "Using default config because the user config file cannot be opened: " - + filepath, - e); - return new UserSetting(); - } - } - - /** - * Lưu thiết lập game thành file. - * - * @throws IOException if any file operations fail - */ - public void save() throws IOException { - final var filepath = getFilepath(); - TomlWriter writer = new TomlWriter(); - writer.write(this, new File(filepath.toUri())); - Logger.get(UserSetting.class).infof("Saved user config to: %s", filepath); - } - - public Video getVideo() { - return video; - } - - public void setVideo(Video video) { - this.video = video; - } - - /** Setting về Hình ảnh. */ - public static final class Video { - private int width = 1024; - private int height = 768; - - private Video() {} - - public int getWidth() { - return width; - } - - public void setWidth(int width) { - this.width = width; - } - - public int getHeight() { - return height; - } - - public void setHeight(int height) { - this.height = height; - } - } -} diff --git a/src/main/java/com/github/codestorm/bounceverse/core/systems/GameSystem.java b/src/main/java/com/github/codestorm/bounceverse/core/systems/GameSystem.java deleted file mode 100644 index 9ed5117..0000000 --- a/src/main/java/com/github/codestorm/bounceverse/core/systems/GameSystem.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.github.codestorm.bounceverse.core.systems; - -import com.almasb.fxgl.dsl.FXGL; -import com.almasb.fxgl.entity.EntityFactory; -import com.github.codestorm.bounceverse.factory.entities.*; - -public final class GameSystem extends System { - private GameSystem() {} - - public static GameSystem getInstance() { - return GameSystem.Holder.INSTANCE; - } - - private static void addFactory(EntityFactory... factories) { - for (var factory : factories) { - FXGL.getGameWorld().addEntityFactory(factory); - } - } - - private static void spawnWalls() { - FXGL.spawn("wallTop"); - FXGL.spawn("wallBottom"); - FXGL.spawn("wallLeft"); - FXGL.spawn("wallRight"); - } - - @Override - public void apply() { - addFactory( - new WallFactory(), - new BrickFactory(), - new BulletFactory(), - new PaddleFactory(), - new BallFactory()); - - // Wall - spawnWalls(); - - // Brick - for (int y = 1; y <= 6; y++) { - for (int x = 1; x <= 10; x++) { - FXGL.spawn("normalBrick", 85 * x, 35 * y); - } - } - - // Paddle - double px = FXGL.getAppWidth() / 2.0 - 60; - double py = FXGL.getAppHeight() - 40; - FXGL.spawn("paddle", px, py); - - // Ball - FXGL.spawn("ball"); - } - - /** - * Lazy-loaded singleton holder.
- * Follow - * Initialization-on-demand holder idiom. - */ - private static final class Holder { - static final GameSystem INSTANCE = new GameSystem(); - } -} diff --git a/src/main/java/com/github/codestorm/bounceverse/core/systems/InputSystem.java b/src/main/java/com/github/codestorm/bounceverse/core/systems/InputSystem.java deleted file mode 100644 index b7f0f5c..0000000 --- a/src/main/java/com/github/codestorm/bounceverse/core/systems/InputSystem.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.github.codestorm.bounceverse.core.systems; - -import com.almasb.fxgl.dsl.FXGL; -import com.almasb.fxgl.input.UserAction; -import com.github.codestorm.bounceverse.typing.enums.DirectionUnit; -import com.github.codestorm.bounceverse.typing.enums.EntityType; -import javafx.scene.input.KeyCode; - -/** - * - * - *

{@link InputSystem}

- * - * {@link System} quản lý Input trong game.
- * Đây là một Singleton, cần lấy instance thông qua {@link #getInstance()}. - */ -public class InputSystem extends System { - public static InputSystem getInstance() { - return InputSystem.Holder.INSTANCE; - } - - @Override - public void apply() { - FXGL.getInput() - .addAction( - new UserAction("Move Left") { - @Override - protected void onAction() { - FXGL.getGameWorld() - .getEntitiesByType(EntityType.PADDLE) - .forEach( - e -> - e.translate( - DirectionUnit.LEFT - .getVector() - .mul(10))); - } - }, - KeyCode.LEFT); - - FXGL.getInput() - .addAction( - new UserAction("Move Right") { - @Override - protected void onAction() { - FXGL.getGameWorld() - .getEntitiesByType(EntityType.PADDLE) - .forEach( - e -> - e.translate( - DirectionUnit.RIGHT - .getVector() - .mul(10))); - } - }, - KeyCode.RIGHT); - } - - /** - * Lazy-loaded singleton holder.
- * Follow - * Initialization-on-demand holder idiom. - */ - private static final class Holder { - static final InputSystem INSTANCE = new InputSystem(); - } -} diff --git a/src/main/java/com/github/codestorm/bounceverse/core/systems/PhysicSystem.java b/src/main/java/com/github/codestorm/bounceverse/core/systems/PhysicSystem.java deleted file mode 100644 index f51b326..0000000 --- a/src/main/java/com/github/codestorm/bounceverse/core/systems/PhysicSystem.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.github.codestorm.bounceverse.core.systems; - -import com.almasb.fxgl.dsl.FXGL; -import com.almasb.fxgl.entity.Entity; -import com.almasb.fxgl.physics.CollisionHandler; -import com.almasb.fxgl.physics.PhysicsComponent; -import com.github.codestorm.bounceverse.Utilities; -import com.github.codestorm.bounceverse.components.behaviors.Attack; -import com.github.codestorm.bounceverse.typing.enums.EntityType; -import java.util.List; - -/** - * - * - *

{@link PhysicSystem}

- * - * {@link System} quản lý Vật lý trong game.
- * Đây là một Singleton, cần lấy instance thông qua {@link #getInstance()}. - * - * @see System - */ -public final class PhysicSystem extends System { - private PhysicSystem() {} - - public static PhysicSystem getInstance() { - return Holder.INSTANCE; - } - - @Override - public void apply() { - final var physicWorld = FXGL.getPhysicsWorld(); - physicWorld.setGravity(0, 0); - - // Bullet vs Brick - physicWorld.addCollisionHandler( - new CollisionHandler(EntityType.BULLET, EntityType.BRICK) { - @Override - protected void onCollisionBegin(Entity bullet, Entity brick) { - final var atk = bullet.getComponentOptional(Attack.class); - if (atk.isEmpty()) { - return; - } - atk.get().execute(List.of(brick)); - } - }); - - // Ball vs Brick - physicWorld.addCollisionHandler( - new CollisionHandler(EntityType.BALL, EntityType.BRICK) { - @Override - protected void onCollisionBegin(Entity ball, Entity brick) { - // collision - final var collisionDirection = - Utilities.Collision.getCollisionDirection(ball, brick); - - final var physics = ball.getComponent(PhysicsComponent.class); - switch (collisionDirection) { - case UP, DOWN -> - physics.setLinearVelocity( - physics.getVelocityX(), -physics.getVelocityY()); - case LEFT, RIGHT -> - physics.setLinearVelocity( - -physics.getVelocityX(), physics.getVelocityY()); - } - - // damage - final var atk = ball.getComponent(Attack.class); - atk.execute(List.of(brick)); - } - }); - - // Ball vs Paddle - physicWorld.addCollisionHandler( - new CollisionHandler(EntityType.BALL, EntityType.PADDLE) { - @Override - protected void onCollisionBegin(Entity ball, Entity paddle) { - final var collisionDirection = - Utilities.Collision.getCollisionDirection(ball, paddle); - - final var physics = ball.getComponent(PhysicsComponent.class); - switch (collisionDirection) { - case UP, DOWN -> - physics.setLinearVelocity( - physics.getVelocityX(), -physics.getVelocityY()); - case LEFT, RIGHT -> - physics.setLinearVelocity( - -physics.getVelocityX(), physics.getVelocityY()); - } - } - }); - - // Ball vs Wall - physicWorld.addCollisionHandler( - new CollisionHandler(EntityType.BALL, EntityType.WALL) { - @Override - protected void onCollisionBegin(Entity ball, Entity wall) { - final var collisionDirection = - Utilities.Collision.getCollisionDirection(ball, wall); - - final var physics = ball.getComponent(PhysicsComponent.class); - switch (collisionDirection) { - case UP, DOWN -> - physics.setLinearVelocity( - physics.getVelocityX(), -physics.getVelocityY()); - case LEFT, RIGHT -> - physics.setLinearVelocity( - -physics.getVelocityX(), physics.getVelocityY()); - } - } - }); - } - - /** - * Lazy-loaded singleton holder.
- * Follow - * Initialization-on-demand holder idiom. - */ - private static final class Holder { - static final PhysicSystem INSTANCE = new PhysicSystem(); - } -} diff --git a/src/main/java/com/github/codestorm/bounceverse/core/systems/UISystem.java b/src/main/java/com/github/codestorm/bounceverse/core/systems/UISystem.java deleted file mode 100644 index ca92148..0000000 --- a/src/main/java/com/github/codestorm/bounceverse/core/systems/UISystem.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.github.codestorm.bounceverse.core.systems; - -import com.almasb.fxgl.dsl.FXGL; -import javafx.scene.paint.Color; - -/** - * - * - *

{@link UISystem}

- * - * {@link System} quản lý Giao diện trong game.
- * Đây là một Singleton, cần lấy instance thông qua {@link #getInstance()}. - */ -public final class UISystem extends System { - public static UISystem getInstance() { - return UISystem.Holder.INSTANCE; - } - - @Override - public void apply() { - FXGL.getGameScene().setBackgroundColor(Color.web("#2B2B2B")); - } - - /** - * Lazy-loaded singleton holder.
- * Follow - * Initialization-on-demand holder idiom. - */ - private static final class Holder { - static final UISystem INSTANCE = new UISystem(); - } -} diff --git a/src/main/java/com/github/codestorm/bounceverse/factory/SceneFactory.java b/src/main/java/com/github/codestorm/bounceverse/factory/SceneFactory.java index 3cb01bf..f5afb11 100644 --- a/src/main/java/com/github/codestorm/bounceverse/factory/SceneFactory.java +++ b/src/main/java/com/github/codestorm/bounceverse/factory/SceneFactory.java @@ -1,8 +1,12 @@ package com.github.codestorm.bounceverse.factory; import com.almasb.fxgl.app.scene.FXGLMenu; +import com.almasb.fxgl.app.scene.IntroScene; +import com.almasb.fxgl.app.scene.MenuType; import com.almasb.fxgl.scene.Scene; -import com.github.codestorm.bounceverse.scenes.MainMenu; +import com.github.codestorm.bounceverse.scenes.Intro; +import com.github.codestorm.bounceverse.scenes.Menu; + import org.jetbrains.annotations.NotNull; /** @@ -17,6 +21,11 @@ public final class SceneFactory extends com.almasb.fxgl.app.scene.SceneFactory { @NotNull @Override public FXGLMenu newMainMenu() { - return new MainMenu(); + return new Menu(MenuType.MAIN_MENU); + } + + @NotNull @Override + public IntroScene newIntro() { + return new Intro(); } } diff --git a/src/main/java/com/github/codestorm/bounceverse/factory/entities/BallFactory.java b/src/main/java/com/github/codestorm/bounceverse/factory/entities/BallFactory.java index 6a152ce..48dfef9 100644 --- a/src/main/java/com/github/codestorm/bounceverse/factory/entities/BallFactory.java +++ b/src/main/java/com/github/codestorm/bounceverse/factory/entities/BallFactory.java @@ -1,70 +1,100 @@ package com.github.codestorm.bounceverse.factory.entities; +import com.almasb.fxgl.dsl.EntityBuilder; import com.almasb.fxgl.dsl.FXGL; import com.almasb.fxgl.entity.Entity; -import com.almasb.fxgl.entity.EntityFactory; import com.almasb.fxgl.entity.SpawnData; import com.almasb.fxgl.entity.Spawns; import com.almasb.fxgl.physics.PhysicsComponent; import com.almasb.fxgl.physics.box2d.dynamics.BodyType; import com.almasb.fxgl.physics.box2d.dynamics.FixtureDef; +import com.github.codestorm.bounceverse.Utilities; +import com.github.codestorm.bounceverse.components.behaviors.Attachment; import com.github.codestorm.bounceverse.components.behaviors.Attack; import com.github.codestorm.bounceverse.typing.enums.EntityType; + import javafx.geometry.Point2D; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; -/** - * - * - *

{@link BallFactory}

- * - *

This class defines and spawns a new {@link EntityType#BALL} entity in the game world. - * - *

By default, the spawned ball has: - * - *

    - *
  • Radius = {@link #DEFAULT_RADIUS} - *
  • Position: {@link #DEFAULT_POS} - *
  • Color: {@link #DEFAULT_COLOR} - *
- * - * @author minngoc1213 - */ -public final class BallFactory implements EntityFactory { - public static final int DEFAULT_RADIUS = 10; - public static final Point2D DEFAULT_POS = new Point2D(400, 500); +/** Factory tạo bóng trong trò chơi. Hỗ trợ spawn bóng gắn (attached) hoặc tự do (free). */ +public final class BallFactory extends EntityFactory { + + public static final double DEFAULT_RADIUS = 10; public static final Color DEFAULT_COLOR = Color.RED; - @Spawns("ball") - public Entity spawnBall(SpawnData data) { - PhysicsComponent physics = new PhysicsComponent(); + @Override + protected EntityBuilder getBuilder(SpawnData data) { + final boolean attached = Utilities.Typing.getOr(data, "attached", false); + var physics = new PhysicsComponent(); var fixture = new FixtureDef(); fixture.setDensity(1.0f); - fixture.setFriction(0f); - fixture.setRestitution(1f); - + fixture.setFriction(0.0f); + fixture.setRestitution(1.0f); physics.setFixtureDef(fixture); physics.setBodyType(BodyType.DYNAMIC); - // set ball doesn't rotate physics.setOnPhysicsInitialized( () -> { - physics.setLinearVelocity(200, 200); - physics.setAngularVelocity(0); + physics.getBody().setGravityScale(0f); physics.getBody().setFixedRotation(true); physics.getBody().setLinearDamping(0f); physics.getBody().setAngularDamping(0f); + physics.getBody().setBullet(true); + + if (attached) { + physics.setLinearVelocity(Point2D.ZERO); + } else { + var initialSpeed = FXGL.getd("ballSpeed"); + var initialVelocity = new Point2D(1, -1).normalize().multiply(initialSpeed); + physics.setLinearVelocity(initialVelocity); + } }); - return FXGL.entityBuilder(data) - .type(EntityType.BALL) - .at(DEFAULT_POS) - .viewWithBBox(new Circle(DEFAULT_RADIUS, DEFAULT_COLOR)) - .collidable() - .with(physics, new Attack()) - .anchorFromCenter() - .buildAndAttach(); + var builder = + FXGL.entityBuilder(data) + .type(EntityType.BALL) + .viewWithBBox(new Circle(DEFAULT_RADIUS, DEFAULT_COLOR)) + .collidable() + .with(physics, new Attack()); + + if (attached) { + builder.with(new Attachment()); + } + + return builder; + } + + @Spawns("ball") + public Entity spawnBall(SpawnData data) { + final var attached = data.hasKey("attached") && Boolean.TRUE.equals(data.get("attached")); + + Point2D pos = null; + if (data.hasKey("x") && data.hasKey("y")) { + pos = new Point2D(data.getX(), data.getY()); + } else if (data.hasKey("position")) { + pos = data.get("position"); + } + + if (pos == null) { + var paddleOpt = + FXGL.getGameWorld().getEntitiesByType(EntityType.PADDLE).stream().findFirst(); + if (paddleOpt.isPresent()) { + var paddle = paddleOpt.get(); + pos = + new Point2D( + paddle.getCenter().getX() - DEFAULT_RADIUS, + paddle.getY() - DEFAULT_RADIUS * 2); + } else { + // Fallback an toàn nếu không tìm thấy paddle + pos = new Point2D(FXGL.getAppWidth() / 2.0, FXGL.getAppHeight() / 2.0); + } + } + + data.put("attached", attached); + data.put("x", pos.getX()); + data.put("y", pos.getY()); + return getBuilder(data).buildAndAttach(); } } diff --git a/src/main/java/com/github/codestorm/bounceverse/factory/entities/BrickFactory.java b/src/main/java/com/github/codestorm/bounceverse/factory/entities/BrickFactory.java index 0c7cf19..e3aad1a 100644 --- a/src/main/java/com/github/codestorm/bounceverse/factory/entities/BrickFactory.java +++ b/src/main/java/com/github/codestorm/bounceverse/factory/entities/BrickFactory.java @@ -1,93 +1,200 @@ package com.github.codestorm.bounceverse.factory.entities; +import com.almasb.fxgl.dsl.EntityBuilder; import com.almasb.fxgl.dsl.FXGL; import com.almasb.fxgl.dsl.components.HealthIntComponent; import com.almasb.fxgl.entity.Entity; -import com.almasb.fxgl.entity.EntityFactory; import com.almasb.fxgl.entity.SpawnData; import com.almasb.fxgl.entity.Spawns; -import com.almasb.fxgl.entity.component.Component; +import com.almasb.fxgl.physics.BoundingShape; import com.almasb.fxgl.physics.PhysicsComponent; import com.almasb.fxgl.physics.box2d.dynamics.BodyType; import com.almasb.fxgl.physics.box2d.dynamics.FixtureDef; +import com.github.codestorm.bounceverse.AssetsPath; import com.github.codestorm.bounceverse.Utilities; +import com.github.codestorm.bounceverse.components.behaviors.Explosion; import com.github.codestorm.bounceverse.components.behaviors.HealthDeath; -import com.github.codestorm.bounceverse.components.behaviors.paddle.PaddleShooting; +import com.github.codestorm.bounceverse.components.behaviors.Special; +import com.github.codestorm.bounceverse.components.behaviors.brick.StrongBrickTextureUpdater; +import com.github.codestorm.bounceverse.components.properties.Attributes; +import com.github.codestorm.bounceverse.components.properties.Shield; +import com.github.codestorm.bounceverse.components.properties.brick.BrickTextureManager; +import com.github.codestorm.bounceverse.typing.enums.BrickType; +import com.github.codestorm.bounceverse.typing.enums.CollisionGroup; import com.github.codestorm.bounceverse.typing.enums.EntityType; + import javafx.geometry.Point2D; +import javafx.geometry.Side; import javafx.scene.paint.Color; -import javafx.scene.shape.Rectangle; -import javafx.util.Duration; -import org.jetbrains.annotations.NotNull; - -/** - * - * - *

{@link BrickFactory}

- * - *
- * Factory để tạo các entity loại {@link EntityType#BRICK} trong trò chơi. - * - * @see EntityFactory - */ -public final class BrickFactory implements EntityFactory { + +import java.util.List; +import java.util.Random; + +public final class BrickFactory extends EntityFactory { + private static final int DEFAULT_WIDTH = 80; private static final int DEFAULT_HEIGHT = 30; - private static final Color DEFAULT_COLOR = Color.LIGHTBLUE; private static final int DEFAULT_HP = 1; + private static final Random RANDOM = new Random(); + private static final List COLORS = + List.of("blue", "green", "orange", "pink", "red", "yellow"); + + private String getColorName(Color color) { + if (color.equals(Color.BLUE)) { + return "blue"; + } + if (color.equals(Color.GREEN)) { + return "green"; + } + if (color.equals(Color.ORANGE)) { + return "orange"; + } + if (color.equals(Color.PINK)) { + return "pink"; + } + if (color.equals(Color.RED)) { + return "red"; + } + if (color.equals(Color.YELLOW)) { + return "yellow"; + } + return "blue"; + } + + // Phương thức trợ giúp để lấy Color object từ SpawnData + private Color getSpawnColor(SpawnData data) { + var colorValue = data.get("color"); + if (colorValue instanceof Color) { + return (Color) colorValue; + } else if (colorValue instanceof String) { + return switch ((String) colorValue) { + case "green" -> Color.GREEN; + case "orange" -> Color.ORANGE; + case "pink" -> Color.PINK; + case "red" -> Color.RED; + case "yellow" -> Color.YELLOW; + default -> Color.BLUE; + }; + } + return Color.BLUE; // Mặc định + } + + @Override + protected EntityBuilder getBuilder(SpawnData data) { + var hp = ((Number) Utilities.Typing.getOr(data, "hp", DEFAULT_HP)).intValue(); + var width = ((Number) Utilities.Typing.getOr(data, "width", DEFAULT_WIDTH)).intValue(); + var height = ((Number) Utilities.Typing.getOr(data, "height", DEFAULT_HEIGHT)).intValue(); + + Point2D pos = data.hasKey("pos") ? data.get("pos") : new Point2D(data.getX(), data.getY()); - /** - * Tạo mới một entity brick. - * - * @param pos Vị trí - * @param hp HP - * @param view Khung nhìn - * @param components Các components thêm vào - * @return Entity Brick mới tạo - */ - @NotNull private static Entity newBrick(Point2D pos, int hp, Rectangle view, Component... components) { - Utilities.Compatibility.throwIfNotCompatible(EntityType.BRICK, components); + // CHỈ LẤY STRING KEY: Đảm bảo chỉ làm việc với String ở đây + String colorKey; + var colorValue = data.get("color"); + + if (colorValue instanceof Color) { + colorKey = getColorName((Color) colorValue); // Chuyển Color object thành String key + } else if (colorValue instanceof String) { + colorKey = (String) colorValue; + } else { + colorKey = COLORS.get(RANDOM.nextInt(COLORS.size())); + } + + var colorAsset = AssetsPath.Textures.Bricks.COLORS.get(colorKey); + if (colorAsset == null) { + colorAsset = + AssetsPath.Textures.Bricks.COLORS.values().stream() + .findFirst() + .orElseThrow( + () -> + new IllegalStateException( + "No ColorAssets available for bricks")); + } + + var type = Utilities.Typing.getOr(data, "type", BrickType.NORMAL); + var texturePath = colorAsset.getTexture(type, 1.0); + var texture = FXGL.texture(texturePath); + texture.setFitWidth(width); + texture.setFitHeight(height); var physics = new PhysicsComponent(); var fixture = new FixtureDef(); fixture.setFriction(0f); fixture.setRestitution(1f); - + fixture.getFilter().categoryBits = CollisionGroup.BRICK.bits; + fixture.getFilter().maskBits = CollisionGroup.BALL.bits | CollisionGroup.BULLET.bits; physics.setFixtureDef(fixture); physics.setBodyType(BodyType.STATIC); - return FXGL.entityBuilder() + return FXGL.entityBuilder(data) .type(EntityType.BRICK) + .bbox(BoundingShape.box(width, height)) + .viewWithBBox(texture) .at(pos) - .viewWithBBox(view) .collidable() - .with(physics, new HealthIntComponent(hp), new HealthDeath()) - .with(components) - .build(); + .with(physics, new Attributes(), new HealthIntComponent(hp), new HealthDeath()); + } + + @Spawns("normalBrick") + public Entity newNormalBrick(SpawnData data) { + data.put("type", BrickType.NORMAL); + data.put("hp", (double) DEFAULT_HP); + var color = getSpawnColor(data); + data.put("color", getColorName(color)); + return getBuilder(data) + .with(new BrickTextureManager(BrickType.NORMAL, color)) + .buildAndAttach(); + } + + @Spawns("strongBrick") + public Entity newStrongBrick(SpawnData data) { + data.put("type", BrickType.STRONG); + data.put("hp", DEFAULT_HP + 2); + var color = getSpawnColor(data); + data.put("color", getColorName(color)); + return getBuilder(data) + .with(new StrongBrickTextureUpdater().withColor(color)) + .with(new BrickTextureManager(BrickType.STRONG, color)) + .buildAndAttach(); + } + + @Spawns("shieldBrick") + public Entity newShieldBrick(SpawnData data) { + data.put("type", BrickType.SHIELD); + data.put("hp", (double) (DEFAULT_HP)); + var shield = new Shield(Side.LEFT, Side.RIGHT, Side.BOTTOM); + var color = getSpawnColor(data); + data.put("color", getColorName(color)); + return getBuilder(data) + .with(shield) + .with(new BrickTextureManager(BrickType.SHIELD, color)) + .buildAndAttach(); } - /** - * Tạo mới một entity brick với khung nhìn mặc định. - * - * @param pos Vị trí - * @param hp HP - * @param components Các components thêm vào - * @return Entity Brick mới tạo - */ - @NotNull private static Entity newBrick(Point2D pos, int hp, Component... components) { - return newBrick( - pos, hp, new Rectangle(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_COLOR), components); + @Spawns("explodingBrick") + public Entity newExplodingBrick(SpawnData data) { + data.put("type", BrickType.EXPLODING); + data.put("hp", (double) DEFAULT_HP); + var explosionWidth = DEFAULT_WIDTH * 1.5; + var explosionHeight = DEFAULT_HEIGHT * 2.5; + var explosion = new Explosion(explosionWidth, explosionHeight); + var color = getSpawnColor(data); + data.put("color", getColorName(color)); + return getBuilder(data) + .with(explosion) + .with(new BrickTextureManager(BrickType.EXPLODING, color)) + .buildAndAttach(); } - /** - * Tạo entity Brick bình thường. - * - * @param pos Vị trí - * @return Entity Brick mới tạo - */ - @NotNull @Spawns("normalBrick") - public static Entity newNormalBrick(SpawnData pos) { - return newBrick( - new Point2D(pos.getX(), pos.getY()), DEFAULT_HP, new PaddleShooting(Duration.ONE)); + @Spawns("keyBrick") + public Entity newKeyBrick(SpawnData data) { + data.put("type", BrickType.KEY); + data.put("hp", (double) DEFAULT_HP); + var special = new Special(); + var color = getSpawnColor(data); + data.put("color", getColorName(color)); + return getBuilder(data) + .with(special) + .with(new BrickTextureManager(BrickType.KEY, color)) + .buildAndAttach(); } } diff --git a/src/main/java/com/github/codestorm/bounceverse/factory/entities/BulletFactory.java b/src/main/java/com/github/codestorm/bounceverse/factory/entities/BulletFactory.java index 60aa51e..cd4d656 100644 --- a/src/main/java/com/github/codestorm/bounceverse/factory/entities/BulletFactory.java +++ b/src/main/java/com/github/codestorm/bounceverse/factory/entities/BulletFactory.java @@ -3,35 +3,60 @@ import static com.almasb.fxgl.dsl.FXGL.entityBuilder; import com.almasb.fxgl.core.math.Vec2; +import com.almasb.fxgl.dsl.EntityBuilder; import com.almasb.fxgl.entity.Entity; -import com.almasb.fxgl.entity.EntityFactory; import com.almasb.fxgl.entity.SpawnData; import com.almasb.fxgl.entity.Spawns; import com.almasb.fxgl.physics.PhysicsComponent; +import com.almasb.fxgl.physics.box2d.dynamics.BodyType; // Thêm import này +import com.almasb.fxgl.physics.box2d.dynamics.FixtureDef; +import com.github.codestorm.bounceverse.Utilities; +import com.github.codestorm.bounceverse.components.behaviors.Attack; +import com.github.codestorm.bounceverse.typing.enums.CollisionGroup; import com.github.codestorm.bounceverse.typing.enums.EntityType; + +import javafx.geometry.Point2D; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; -/** - * - * - *

{@link BulletFactory}

- * - * Factory để tạo các entity loại {@link EntityType#BULLET} trong trò chơi. - * - * @see EntityFactory - */ -public final class BulletFactory implements EntityFactory { +/** Factory để tạo các entity loại BULLET trong trò chơi. */ +public final class BulletFactory extends EntityFactory { + public static final double DEFAULT_RADIUS = 4; + public static final Color DEFAULT_COLOR = Color.YELLOW; + + @Override + protected EntityBuilder getBuilder(SpawnData data) { + var physics = new PhysicsComponent(); + var fixture = new FixtureDef(); + + // Lọc va chạm: Đạn thuộc nhóm BULLET và chỉ va chạm với BRICK, WALL + fixture.getFilter().categoryBits = CollisionGroup.BULLET.bits; + fixture.getFilter().maskBits = CollisionGroup.BRICK.bits | CollisionGroup.WALL.bits; + + physics.setFixtureDef(fixture); + physics.setBodyType(BodyType.DYNAMIC); + + physics.setOnPhysicsInitialized( + () -> { + Vec2 velocity = data.get("velocity"); + physics.setLinearVelocity(velocity.toPoint2D()); + physics.getBody().setGravityScale(0f); + physics.getBody().setBullet(true); + }); + + return entityBuilder(data).type(EntityType.BULLET).collidable().with(physics); + } + @Spawns("paddleBullet") public Entity newBullet(SpawnData data) { - final var physics = new PhysicsComponent(); - physics.setLinearVelocity(((Vec2) data.get("velocity")).toPoint2D()); - - return entityBuilder(data) - .type(EntityType.BULLET) - .viewWithBBox(new Circle(4, Color.YELLOW)) - .collidable() - .with(physics) - .build(); + final var pos = new Point2D(data.getX(), data.getY()); + final double radius = Utilities.Typing.getOr(data, "radius", DEFAULT_RADIUS); + final var color = Utilities.Typing.getOr(data, "color", DEFAULT_COLOR); + final int damage = Utilities.Typing.getOr(data, "damage", Attack.DEFAULT_DAMAGE); + final var attackComponent = new Attack(damage); + + final var bbox = new Circle(radius, color); + + return getBuilder(data).at(pos).viewWithBBox(bbox).with(attackComponent).buildAndAttach(); } } diff --git a/src/main/java/com/github/codestorm/bounceverse/factory/entities/EntityFactory.java b/src/main/java/com/github/codestorm/bounceverse/factory/entities/EntityFactory.java new file mode 100644 index 0000000..369bbb0 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/factory/entities/EntityFactory.java @@ -0,0 +1,23 @@ +package com.github.codestorm.bounceverse.factory.entities; + +import com.almasb.fxgl.dsl.EntityBuilder; +import com.almasb.fxgl.entity.SpawnData; + +/** + * + * + *

{@link EntityFactory}

+ * + * Một Factory chỉ chuyên sản xuất một loại Entity nào đó. + * + * @see com.almasb.fxgl.entity.EntityFactory + */ +abstract class EntityFactory implements com.almasb.fxgl.entity.EntityFactory { + /** + * Lấy {@link EntityBuilder} chung của entity tương ứng. + * + * @param data Dữ liệu (chung nhất) phục vụ spawn entity + * @return Entity builder + */ + protected abstract EntityBuilder getBuilder(SpawnData data); +} diff --git a/src/main/java/com/github/codestorm/bounceverse/factory/entities/PaddleFactory.java b/src/main/java/com/github/codestorm/bounceverse/factory/entities/PaddleFactory.java index 67c1f2f..6616ffa 100644 --- a/src/main/java/com/github/codestorm/bounceverse/factory/entities/PaddleFactory.java +++ b/src/main/java/com/github/codestorm/bounceverse/factory/entities/PaddleFactory.java @@ -1,45 +1,57 @@ package com.github.codestorm.bounceverse.factory.entities; +import com.almasb.fxgl.dsl.EntityBuilder; import com.almasb.fxgl.dsl.FXGL; import com.almasb.fxgl.entity.Entity; -import com.almasb.fxgl.entity.EntityFactory; import com.almasb.fxgl.entity.SpawnData; import com.almasb.fxgl.entity.Spawns; -import com.github.codestorm.bounceverse.components.behaviors.ScaleChange; +import com.almasb.fxgl.physics.BoundingShape; +import com.almasb.fxgl.physics.HitBox; +import com.almasb.fxgl.physics.PhysicsComponent; +import com.almasb.fxgl.physics.box2d.dynamics.BodyType; +import com.github.codestorm.bounceverse.Utilities; +import com.github.codestorm.bounceverse.components.behaviors.Attack; import com.github.codestorm.bounceverse.components.behaviors.paddle.PaddleShooting; +import com.github.codestorm.bounceverse.components.properties.paddle.PaddlePowerComponent; +import com.github.codestorm.bounceverse.components.properties.paddle.PaddleViewManager; import com.github.codestorm.bounceverse.typing.enums.EntityType; + import javafx.scene.paint.Color; -import javafx.scene.shape.Rectangle; import javafx.util.Duration; -/** - * - * - *

{@link PaddleFactory}

- * - * Factory để tạo các entity loại {@link EntityType#PADDLE} trong trò chơi. - * - * @see EntityFactory - */ -public final class PaddleFactory implements EntityFactory { +/** Factory để tạo các entity loại PADDLE trong trò chơi. */ +public final class PaddleFactory extends EntityFactory { + private static final double DEFAULT_WIDTH = 150; - private static final double DEFAULT_HEIGHT = 20; + private static final double DEFAULT_HEIGHT = 40; private static final double DEFAULT_ARC_WIDTH = 14; private static final double DEFAULT_ARC_HEIGHT = 14; + public static final Color DEFAULT_COLOR = Color.LIGHTBLUE; private static final Duration DEFAULT_SHOOT_COOLDOWN = Duration.ZERO; + @Override + protected EntityBuilder getBuilder(SpawnData data) { + return FXGL.entityBuilder(data).type(EntityType.PADDLE).collidable(); + } + @Spawns("paddle") public Entity newPaddle(SpawnData data) { - Rectangle view = new Rectangle(DEFAULT_WIDTH, DEFAULT_HEIGHT, Color.LIGHTBLUE); - view.setArcWidth(DEFAULT_ARC_WIDTH); - view.setArcHeight(DEFAULT_ARC_HEIGHT); - - return FXGL.entityBuilder(data) - .type(EntityType.PADDLE) - .viewWithBBox(view) - .collidable() - .with(new ScaleChange(), new PaddleShooting(DEFAULT_SHOOT_COOLDOWN)) + final double width = Utilities.Typing.getOr(data, "width", DEFAULT_WIDTH); + final double height = Utilities.Typing.getOr(data, "height", DEFAULT_HEIGHT); + + var physics = new PhysicsComponent(); + physics.setBodyType(BodyType.KINEMATIC); + var px = FXGL.getAppWidth() / 2.0 - width / 2.0; + var py = FXGL.getAppHeight() - height - 40; + + return getBuilder(data) + .at(px, py) + .bbox(new HitBox(BoundingShape.box(width, height))) + .with(physics) + .with(new PaddleShooting(DEFAULT_SHOOT_COOLDOWN)) + .with(new Attack()) + .with(new PaddleViewManager()) + .with(new PaddlePowerComponent()) .build(); - // TODO: Thêm điều chỉnh tốc độ } } diff --git a/src/main/java/com/github/codestorm/bounceverse/factory/entities/PowerUpFactory.java b/src/main/java/com/github/codestorm/bounceverse/factory/entities/PowerUpFactory.java new file mode 100644 index 0000000..4628083 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/factory/entities/PowerUpFactory.java @@ -0,0 +1,72 @@ +package com.github.codestorm.bounceverse.factory.entities; + +import com.almasb.fxgl.dsl.EntityBuilder; +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.entity.Entity; +import com.almasb.fxgl.entity.SpawnData; +import com.almasb.fxgl.entity.Spawns; +import com.almasb.fxgl.physics.BoundingShape; +import com.almasb.fxgl.physics.HitBox; +import com.almasb.fxgl.texture.Texture; +import com.github.codestorm.bounceverse.Utilities; +import com.github.codestorm.bounceverse.components.behaviors.FallingComponent; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUpContainer; +import com.github.codestorm.bounceverse.typing.enums.EntityType; + +import javafx.geometry.Point2D; + +/** Factory tạo Power-Up entity (rơi thẳng xuống, không dùng physics). */ +public final class PowerUpFactory extends EntityFactory { + + public static final double DEFAULT_RADIUS = 10; + + @Override + protected EntityBuilder getBuilder(SpawnData data) { + return FXGL.entityBuilder().type(EntityType.POWER_UP).collidable(); + } + + /** + * Tạo mới một PowerUp entity. + * + * @param data dữ liệu spawn + * @return entity PowerUp + */ + @Spawns("powerUp") + public Entity newPowerUp(SpawnData data) { + final double radius = Utilities.Typing.getOr(data, "radius", DEFAULT_RADIUS); + + // 🔹 Lấy danh sách power-up chứa bên trong (1 hoặc nhiều) + final var containsData = data.hasKey("contains") ? data.get("contains") : new PowerUp[0]; + final var contains = + containsData instanceof PowerUp p ? new PowerUp[] {p} : (PowerUp[]) containsData; + + // 🔹 Lấy vị trí spawn (có thể từ pos hoặc x,y) + final Point2D pos = + data.hasKey("pos") ? data.get("pos") : new Point2D(data.getX(), data.getY()); + + // 🔹 Lấy texture (hoặc mặc định) + final Texture texture = + data.hasKey("texture") + ? data.get("texture") + : FXGL.texture("power/paddle/Expand Paddle.png"); + + // 🔹 Giới hạn kích thước hiển thị + texture.setFitWidth(42); + texture.setFitHeight(42); + texture.setPreserveRatio(true); + + // 🔹 Căn tâm ảnh để khi xoay hoặc va chạm không bị lệch + texture.setTranslateX(-texture.getFitWidth() / 2); + texture.setTranslateY(-texture.getFitHeight() / 2); + + final var hitbox = new HitBox(BoundingShape.circle(radius)); + + return getBuilder(data) + .bbox(hitbox) + .at(pos) + .view(texture) + .with(new FallingComponent(), new PowerUpContainer(contains)) + .buildAndAttach(); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/factory/entities/WallFactory.java b/src/main/java/com/github/codestorm/bounceverse/factory/entities/WallFactory.java index d637c9f..64832c9 100644 --- a/src/main/java/com/github/codestorm/bounceverse/factory/entities/WallFactory.java +++ b/src/main/java/com/github/codestorm/bounceverse/factory/entities/WallFactory.java @@ -1,50 +1,75 @@ package com.github.codestorm.bounceverse.factory.entities; +import com.almasb.fxgl.dsl.EntityBuilder; import com.almasb.fxgl.dsl.FXGL; import com.almasb.fxgl.entity.Entity; -import com.almasb.fxgl.entity.EntityFactory; import com.almasb.fxgl.entity.SpawnData; import com.almasb.fxgl.entity.Spawns; import com.almasb.fxgl.physics.PhysicsComponent; import com.almasb.fxgl.physics.box2d.dynamics.BodyType; import com.almasb.fxgl.physics.box2d.dynamics.FixtureDef; +import com.github.codestorm.bounceverse.Utilities; import com.github.codestorm.bounceverse.typing.enums.AnchorPoint; +import com.github.codestorm.bounceverse.typing.enums.CollisionGroup; import com.github.codestorm.bounceverse.typing.enums.EntityType; + import javafx.geometry.Point2D; +import javafx.geometry.Side; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; -/** - * - * - *

{@link WallFactory}

- * - *

This class spawns {@link EntityType#WALL} entity for the game world. - * - *

By default, 4 walls top, left, bottom, right will be spawn at the beginning. - * - * @author minngoc1213 - */ -public final class WallFactory implements EntityFactory { +/** Factory tạo 4 tường vật lý bao quanh màn chơi. */ +public final class WallFactory extends EntityFactory { + public static final double DEFAULT_THICKNESS = 5; + public static final Color DEFAULT_COLOR = Color.GRAY; + + @Override + protected EntityBuilder getBuilder(SpawnData data) { + var appShape = new Rectangle(FXGL.getAppWidth(), FXGL.getAppHeight()); + Side side = data.get("side"); + + double thickness = Utilities.Typing.getOr(data, "thickness", DEFAULT_THICKNESS); + var color = Utilities.Typing.getOr(data, "color", DEFAULT_COLOR); - /** - * Create new Entity Wall with ing-game physic. - * - * @param pos position - * @param width width - * @param height height - * @return Wall entity at pos - */ - public static Entity createWall(Point2D pos, double width, double height) { - Rectangle rect = new Rectangle(width, height, Color.GRAY); + Point2D pos; + double width, height; - PhysicsComponent physics = new PhysicsComponent(); + switch (side) { + case TOP -> { + pos = AnchorPoint.TOP_LEFT.of(appShape); + width = appShape.getWidth(); + height = thickness; + } + case BOTTOM -> { + pos = AnchorPoint.BOTTOM_LEFT.of(appShape).subtract(0, thickness); + width = appShape.getWidth(); + height = thickness; + } + case LEFT -> { + pos = AnchorPoint.TOP_LEFT.of(appShape); + width = thickness; + height = appShape.getHeight(); + } + case RIGHT -> { + pos = AnchorPoint.TOP_RIGHT.of(appShape).subtract(thickness, 0); + width = thickness; + height = appShape.getHeight(); + } + default -> throw new IllegalArgumentException("Invalid side for wall: " + side); + } - FixtureDef fixture = new FixtureDef(); + var rect = new Rectangle(width, height, color); + var physics = new PhysicsComponent(); + var fixture = new FixtureDef(); fixture.setFriction(0f); fixture.setRestitution(1f); + // Lọc va chạm: Tường thuộc nhóm WALL và va chạm với BALL, BULLET, PADDLE + fixture.getFilter().categoryBits = CollisionGroup.WALL.bits; + fixture.getFilter().maskBits = + CollisionGroup.BALL.bits | CollisionGroup.BULLET.bits | CollisionGroup.PADDLE.bits; + physics.setFixtureDef(fixture); physics.setBodyType(BodyType.STATIC); @@ -54,73 +79,34 @@ public static Entity createWall(Point2D pos, double width, double height) { .viewWithBBox(rect) .collidable() .with(physics) - .anchorFromCenter() - .build(); + .with("side", side); } - /** - * Create Top {@link EntityType#WALL}. ` - * - * @param data Spawn data - * @return Top wall - */ + /** Tạo tường trên ({@link EntityType#WALL}). */ @Spawns("wallTop") public Entity newWallTop(SpawnData data) { - final var appShape = new Rectangle(FXGL.getAppWidth(), FXGL.getAppHeight()); - final double thickness = - data.hasKey("thickness") ? data.get("thickness") : DEFAULT_THICKNESS; - - return createWall(AnchorPoint.TOP_LEFT.of(appShape), appShape.getWidth(), thickness); + data.put("side", Side.TOP); + return getBuilder(data).buildAndAttach(); } - /** - * Create Bottom {@link EntityType#WALL}. ` - * - * @param data Spawn data - * @return Botton wall - */ + /** Tạo tường dưới ({@link EntityType#WALL}). */ @Spawns("wallBottom") public Entity newWallBottom(SpawnData data) { - final var appShape = new Rectangle(FXGL.getAppWidth(), FXGL.getAppHeight()); - final double thickness = - data.hasKey("thickness") ? data.get("thickness") : DEFAULT_THICKNESS; - - return createWall( - AnchorPoint.BOTTOM_LEFT.of(appShape).subtract(0, thickness), - appShape.getWidth(), - thickness); + data.put("side", Side.BOTTOM); + return getBuilder(data).buildAndAttach(); } - /** - * Create Left {@link EntityType#WALL}. ` - * - * @param data Spawn data - * @return Left wall - */ + /** Tạo tường trái ({@link EntityType#WALL}). */ @Spawns("wallLeft") public Entity newWallLeft(SpawnData data) { - final var appShape = new Rectangle(FXGL.getAppWidth(), FXGL.getAppHeight()); - final double thickness = - data.hasKey("thickness") ? data.get("thickness") : DEFAULT_THICKNESS; - - return createWall(AnchorPoint.TOP_LEFT.of(appShape), thickness, appShape.getHeight()); + data.put("side", Side.LEFT); + return getBuilder(data).buildAndAttach(); } - /** - * Create Right {@link EntityType#WALL}. ` - * - * @param data Spawn data - * @return Left wall - */ + /** Tạo tường phải ({@link EntityType#WALL}). */ @Spawns("wallRight") public Entity newWallRight(SpawnData data) { - final var appShape = new Rectangle(FXGL.getAppWidth(), FXGL.getAppHeight()); - final double thickness = - data.hasKey("thickness") ? data.get("thickness") : DEFAULT_THICKNESS; - - return createWall( - AnchorPoint.TOP_RIGHT.of(appShape).subtract(thickness, 0), - thickness, - appShape.getHeight()); + data.put("side", Side.RIGHT); + return getBuilder(data).buildAndAttach(); } } diff --git a/src/main/java/com/github/codestorm/bounceverse/scenes/Intro.java b/src/main/java/com/github/codestorm/bounceverse/scenes/Intro.java new file mode 100644 index 0000000..dd19ade --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/scenes/Intro.java @@ -0,0 +1,101 @@ +package com.github.codestorm.bounceverse.scenes; + +import com.almasb.fxgl.app.scene.IntroScene; +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.logging.Logger; +import com.github.codestorm.bounceverse.AssetsPath; + +import javafx.geometry.Pos; +import javafx.scene.input.MouseButton; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.util.Duration; + +/** + * + * + *

{@link Intro}

+ * + * Intro của game. + * + * @see IntroScene + */ +public final class Intro extends IntroScene { + @Override + public void startIntro() { + // Background + setBackgroundColor(Color.BLACK); + setCursorInvisible(); + + try { + // Tải video + final var video = FXGL.getAssetLoader().loadVideo(AssetsPath.Video.INTRO); + video.setFitWidth(FXGL.getAppWidth()); + video.setFitHeight(FXGL.getAppHeight()); + addChild(video); + + final var player = video.getMediaPlayer(); + + // Xử lý khi video kết thúc thành công + player.setOnEndOfMedia( + () -> { + player.dispose(); + finishIntro(); + }); + + // SỬA ĐỔI: Xử lý khi có lỗi xảy ra + player.setOnError( + () -> { + Logger.get(Intro.class).fatal("Cannot play intro video", player.getError()); + + // Hiển thị thông báo lỗi thay vì bỏ qua ngay lập tức + showErrorAndExit(); + + player.dispose(); // Dọn dẹp media player + }); + + // Bỏ qua intro khi người dùng click chuột + getContentRoot() + .setOnMouseClicked( + event -> { + if (event.getButton() == MouseButton.PRIMARY) { + player.getOnEndOfMedia().run(); + } + }); + + // Bắt đầu phát khi video đã sẵn sàng + player.setOnReady(player::play); + + } catch (Exception e) { + // Bắt lỗi nếu ngay cả việc tải video ban đầu cũng thất bại (ví dụ file không tồn tại) + Logger.get(Intro.class).fatal("Failed to load intro video asset.", e); + } + } + + /** Hiển thị một thông báo lỗi trên màn hình trong 3 giây rồi kết thúc intro. */ + private void showErrorAndExit() { + // Xóa các thành phần cũ (nếu có) + getContentRoot().getChildren().clear(); + + // Tạo văn bản thông báo lỗi + var errorText = + FXGL.getUIFactoryService() + .newText( + "Lỗi: Không thể tải video intro.\n" + + "Vui lòng kiểm tra file tại\n" + + "src/main/resources/assets/videos/intro.mp4", + Color.WHITE, + 22.0); + errorText.setTextAlignment(javafx.scene.text.TextAlignment.CENTER); + + // Đặt văn bản vào giữa màn hình + var layout = new StackPane(errorText); + layout.setPrefSize(FXGL.getAppWidth(), FXGL.getAppHeight()); + StackPane.setAlignment(errorText, Pos.CENTER); + + addChild(layout); + + // Tự động kết thúc intro sau 3.5 giây để người dùng kịp đọc + FXGL.getGameTimer().runOnceAfter(this::finishIntro, Duration.seconds(3.5)); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/scenes/MainMenu.java b/src/main/java/com/github/codestorm/bounceverse/scenes/MainMenu.java deleted file mode 100644 index 4e1f726..0000000 --- a/src/main/java/com/github/codestorm/bounceverse/scenes/MainMenu.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.github.codestorm.bounceverse.scenes; - -import com.almasb.fxgl.app.scene.FXGLDefaultMenu; -import com.almasb.fxgl.app.scene.FXGLMenu; -import com.almasb.fxgl.app.scene.MenuType; - -/** - * - * - *

{@link MainMenu}

- * - * {@link FXGLMenu} loại {@link MenuType#MAIN_MENU} được sử dụng trong trò chơi. - */ -public class MainMenu extends FXGLDefaultMenu { - public MainMenu() { - super(MenuType.MAIN_MENU); - // TODO: Customize Main Menu - } -} diff --git a/src/main/java/com/github/codestorm/bounceverse/scenes/Menu.java b/src/main/java/com/github/codestorm/bounceverse/scenes/Menu.java new file mode 100644 index 0000000..bf62d1b --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/scenes/Menu.java @@ -0,0 +1,1095 @@ +/* + * FXGL - JavaFX Game Library. The MIT License (MIT). + * Copyright (c) AlmasB (almaslvl@gmail.com). + * See LICENSE for details. + * + * Modified by Mai Thành (@thnhmai06), 2025. + */ +package com.github.codestorm.bounceverse.scenes; + +// ... (Các import giữ nguyên) +import com.almasb.fxgl.animation.Animation; +import com.almasb.fxgl.animation.Interpolators; +import com.almasb.fxgl.app.ApplicationMode; +import com.almasb.fxgl.app.MenuItem; +import com.almasb.fxgl.app.scene.FXGLMenu; +import com.almasb.fxgl.app.scene.MenuType; +import com.almasb.fxgl.core.math.FXGLMath; +import com.almasb.fxgl.core.util.InputPredicates; +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.input.Input; +import com.almasb.fxgl.input.InputModifier; +import com.almasb.fxgl.input.Trigger; +import com.almasb.fxgl.input.UserAction; +import com.almasb.fxgl.input.view.TriggerView; +import com.almasb.fxgl.localization.Language; +import com.almasb.fxgl.logging.Logger; +import com.almasb.fxgl.particle.ParticleEmitters; +import com.almasb.fxgl.particle.ParticleSystem; +import com.almasb.fxgl.profile.SaveFile; +import com.almasb.fxgl.scene.SubScene; +import com.almasb.fxgl.ui.FXGLScrollPane; +import com.almasb.fxgl.ui.FontType; +import com.github.codestorm.bounceverse.systems.manager.metrics.LeaderboardManager; + +import javafx.animation.FadeTransition; +import javafx.beans.binding.Bindings; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.geometry.Point2D; +import javafx.geometry.Pos; +import javafx.geometry.VPos; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.Tooltip; +import javafx.scene.effect.BlendMode; +import javafx.scene.effect.GaussianBlur; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.RowConstraints; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.paint.CycleMethod; +import javafx.scene.paint.LinearGradient; +import javafx.scene.paint.Paint; +import javafx.scene.paint.Stop; +import javafx.scene.shape.Polygon; +import javafx.scene.shape.Rectangle; +import javafx.scene.text.Text; +import javafx.util.Duration; +import javafx.util.StringConverter; + +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; + +public class Menu extends FXGLMenu { + + // ... (Các biến thành viên giữ nguyên) + private static final Logger log = Logger.get(Menu.class); + + private final ParticleSystem particleSystem = new ParticleSystem(); + + private final SimpleObjectProperty titleColor = new SimpleObjectProperty<>(Color.WHITE); + private final Pane menuRoot = new Pane(); + private final Pane menuContentRoot = new Pane(); + private final MenuContent defaultContent = new MenuContent(); + private final PressAnyKeyState pressAnyKeyState = new PressAnyKeyState(); + private final MenuBox menu; + private final List> animations = new ArrayList<>(); + private double t = 0.0; + + public Menu(MenuType type) { + super(type); + + if (getAppWidth() < 800 || getAppHeight() < 600) { + log.warning("Menu is not designed for resolutions < 800x600"); + } + + if (type == MenuType.MAIN_MENU) { + menu = createMenuBodyMainMenu(); + } else { + menu = createMenuBodyGameMenu(); + } + + var menuX = 50.0; + var menuY = getAppHeight() / 2.0 - menu.getLayoutHeight() / 2.0; + + menuRoot.setTranslateX(menuX); + menuRoot.setTranslateY(menuY); + + menuContentRoot.setTranslateX(getAppWidth() - 500.0); + menuContentRoot.setTranslateY(menuY); + + initParticles(); + + menuRoot.getChildren().add(menu); + menuContentRoot.getChildren().add(defaultContent); + + getContentRoot() + .getChildren() + .addAll( + createBackground(getAppWidth(), getAppHeight()), + createTitleView(FXGL.getSettings().getTitle()), + createVersionView(makeVersionString()), + particleSystem.getPane(), + menuRoot, + menuContentRoot); + } + + // ... (Các phương thức khác giữ nguyên cho đến createMenuBodyMainMenu) + private void initParticles() { + // particle smoke + var t = FXGL.texture("particles/smoke.png", 128.0, 128.0).brighter().brighter(); + + var emitter = ParticleEmitters.newFireEmitter(); + emitter.setBlendMode(BlendMode.SRC_OVER); + emitter.setSourceImage(t.getImage()); + emitter.setSize(150.0, 220.0); + emitter.setNumParticles(10); + emitter.setEmissionRate(0.01); + emitter.setVelocityFunction( + (v) -> new Point2D(FXGL.random() * 2.5, -FXGL.random() * FXGL.random(80, 120))); + emitter.setExpireFunction((v) -> Duration.seconds(FXGL.random(4, 7))); + emitter.setScaleFunction((v) -> new Point2D(0.15, 0.10)); + emitter.setSpawnPointFunction( + (v) -> new Point2D(FXGL.random(0.0, getAppWidth() - 200.0), 120.0)); + + particleSystem.addParticleEmitter(emitter, 0.0, FXGL.getAppHeight()); + } + + @Override + public void onCreate() { + animations.clear(); + + var menuBox = (MenuBox) menuRoot.getChildren().getFirst(); + + for (var i = 0; i < menuBox.getChildren().size(); i++) { + var node = menuBox.getChildren().get(i); + node.setTranslateX(-250.0); + + var animation = + FXGL.animationBuilder() + .delay(Duration.seconds(i * 0.07)) + .interpolator(Interpolators.EXPONENTIAL.EASE_OUT()) + .duration(Duration.seconds(0.66)) + .translate(node) + .from(new Point2D(-250.0, 0.0)) + .to(new Point2D(0.0, 0.0)) + .build(); + + animations.add(animation); + + animation.stop(); + animation.start(); + } + } + + @Override + public void onDestroy() { + // the scene is no longer active so reset everything + // so that next time scene is active everything is loaded properly + switchMenuTo(menu); + switchMenuContentTo(defaultContent); + } + + @Override + public void onUpdate(double tpf) { + if (getType() == MenuType.MAIN_MENU + && FXGL.getSettings().isUserProfileEnabled() + && "DEFAULT".equals(FXGL.getSettings().getProfileName().get())) { + showProfileDialog(); + } + + animations.forEach((a) -> a.onUpdate(tpf)); + + var frequency = 1.7; + + t += tpf * frequency; + + particleSystem.onUpdate(tpf); + + var color = Color.color(1.0, 1.0, 1.0, FXGLMath.noise1D(t)); + titleColor.set(color); + } + + private Rectangle createBackground(double width, double height) { + var bg = new Rectangle(width, height); + bg.setFill(Color.rgb(10, 1, 1, getType() == MenuType.GAME_MENU ? 0.5 : 1.0)); + return bg; + } + + private StackPane createTitleView(String title) { + var text = FXGL.getUIFactoryService().newText(title.substring(0, 1), 50.0); + text.setFill(null); + text.strokeProperty().bind(titleColor); + text.setStrokeWidth(1.5); + + var text2 = FXGL.getUIFactoryService().newText(title.substring(1), 50.0); + text2.setFill(null); + text2.setStroke(titleColor.get()); + text2.setStrokeWidth(1.5); + + var textWidth = text.getLayoutBounds().getWidth() + text2.getLayoutBounds().getWidth(); + + var border = new Rectangle(textWidth + 30, 65.0, null); + border.setStroke(Color.WHITE); + border.setStrokeWidth(4.0); + border.setArcWidth(25.0); + border.setArcHeight(25.0); + + var emitter = ParticleEmitters.newExplosionEmitter(50); + emitter.setBlendMode(BlendMode.ADD); + emitter.setSourceImage(FXGL.image("particles/trace_horizontal.png", 64.0, 64.0)); + emitter.setMaxEmissions(Integer.MAX_VALUE); + emitter.setSize(18.0, 22.0); + emitter.setNumParticles(2); + emitter.setEmissionRate(0.2); + emitter.setVelocityFunction( + i -> { + if (i % 2 == 0) { + return new Point2D(FXGL.random(-10.0, 0.0), 0.0); + } else { + return new Point2D(FXGL.random(0.0, 10.0), 0.0); + } + }); + emitter.setExpireFunction((v) -> Duration.seconds(FXGL.random(4.0, 6.0))); + emitter.setScaleFunction((v) -> new Point2D(-0.03, -0.03)); + emitter.setSpawnPointFunction((v) -> new Point2D(0, 0)); + + var box = new HBox(text, text2); + box.setAlignment(Pos.CENTER); + + var titleRoot = new StackPane(border, box); + titleRoot.setTranslateX(getAppWidth() / 2.0 - (textWidth + 30) / 2.0); + titleRoot.setTranslateY(50.0); + + if (!FXGL.getSettings().isNative()) { + particleSystem.addParticleEmitter( + emitter, + getAppWidth() / 2.0 - 30, + titleRoot.getTranslateY() + border.getHeight() - 16); + } + + return titleRoot; + } + + private Text createVersionView(String version) { + var view = FXGL.getUIFactoryService().newText(version); + view.setTranslateY(getAppHeight() - 2.0); + return view; + } + + private MenuBox createMenuBodyMainMenu() { + log.debug("createMenuBodyMainMenu()"); + + var box = new MenuBox(); + + Set enabledItems = FXGL.getSettings().getEnabledMenuItems(); + + var itemNewGame = new MenuButton("menu.newGame"); + itemNewGame.setOnAction(e -> fireNewGame()); + box.add(itemNewGame); + + // << --- THÊM NÚT LOAD GAME --- >> + if (enabledItems.contains(MenuItem.SAVE_LOAD)) { + var itemLoad = new MenuButton("menu.load"); + itemLoad.setMenuContent(this::createContentLoad, false); + box.add(itemLoad); + } + + var itemOptions = new MenuButton("menu.options"); + itemOptions.setChild(createOptionsMenu()); + box.add(itemOptions); + + if (enabledItems.contains(MenuItem.EXTRA)) { + var itemExtra = new MenuButton("menu.extra"); + itemExtra.setChild(createExtraMenu()); + box.add(itemExtra); + } + + var itemExit = new MenuButton("menu.exit"); + itemExit.setOnAction(e -> fireExit()); + box.add(itemExit); + + return box; + } + + private MenuBox createMenuBodyGameMenu() { + log.debug("createMenuBodyGameMenu()"); + + var box = new MenuBox(); + + var enabledItems = FXGL.getSettings().getEnabledMenuItems(); + + var itemResume = new MenuButton("menu.resume"); + itemResume.setOnAction(e -> fireResume()); + box.add(itemResume); + + if (enabledItems.contains(MenuItem.SAVE_LOAD)) { + var itemSave = new MenuButton("menu.save"); + itemSave.setOnAction(e -> fireSave()); + + var itemLoad = new MenuButton("menu.load"); + itemLoad.setMenuContent(this::createContentLoad, false); + + box.add(itemSave); + box.add(itemLoad); + } + + var itemOptions = new MenuButton("menu.options"); + itemOptions.setChild(createOptionsMenu()); + box.add(itemOptions); + + if (enabledItems.contains(MenuItem.EXTRA)) { + var itemExtra = new MenuButton("menu.extra"); + itemExtra.setChild(createExtraMenu()); + box.add(itemExtra); + } + + if (FXGL.getSettings().isMainMenuEnabled()) { + var itemExit = new MenuButton("menu.mainMenu"); + itemExit.setOnAction(e -> fireExitToMainMenu()); + box.add(itemExit); + } else { + var itemExit = new MenuButton("menu.exit"); + itemExit.setOnAction(e -> fireExit()); + box.add(itemExit); + } + + return box; + } + + private MenuBox createOptionsMenu() { + log.debug("createOptionsMenu()"); + + // var itemGameplay = new MenuButton("menu.gameplay"); + // itemGameplay.setMenuContent(this::createContentGameplay); + var itemControls = new MenuButton("menu.controls"); + itemControls.setMenuContent(this::createContentControls); + + var itemVideo = new MenuButton("menu.video"); + itemVideo.setMenuContent(this::createContentVideo); + var itemAudio = new MenuButton("menu.audio"); + itemAudio.setMenuContent(this::createContentAudio); + + // var btnRestore = createRestoreButton(); + return new MenuBox(itemControls, itemVideo, itemAudio); + } + + private MenuButton createRestoreButton() { + var btnRestore = new MenuButton("menu.restore"); + btnRestore.setOnAction( + e -> + FXGL.getDialogService() + .showConfirmationBox( + FXGL.localize("menu.settingsRestore"), + yes -> { + if (yes) { + switchMenuContentTo(defaultContent); + // TODO: Restore Default Settings + restoreDefaultSettings(); + } + })); + return btnRestore; + } + + private MenuBox createExtraMenu() { + log.debug("createExtraMenu()"); + + // var itemAchievements = new MenuButton("menu.trophies"); + // itemAchievements.setMenuContent(this::createContentAchievements); + var itemLeaderboard = new MenuButton("temp.key"); + itemLeaderboard.btn.textProperty().unbind(); + itemLeaderboard.btn.setText("LEADERBOARD"); + itemLeaderboard.setMenuContent(this::createContentLeaderboard); + + var itemCredits = new MenuButton("menu.credits"); + itemCredits.setMenuContent(this::createContentCredits); + + return new MenuBox(itemLeaderboard, itemCredits); + } + + private void switchMenuTo(Node menuNode) { + var oldMenu = menuRoot.getChildren().getFirst(); + + var ft = new FadeTransition(Duration.seconds(0.33), oldMenu); + ft.setToValue(0.0); + ft.setOnFinished( + e -> { + menuNode.setOpacity(0.0); + menuRoot.getChildren().set(0, menuNode); + oldMenu.setOpacity(1.0); + + var ft2 = new FadeTransition(Duration.seconds(0.33), menuNode); + ft2.setToValue(1.0); + ft2.play(); + }); + ft.play(); + } + + private void switchMenuContentTo(Node content) { + menuContentRoot.getChildren().set(0, content); + } + + /** + * @return full version string + */ + private String makeVersionString() { + return "v" + + FXGL.getSettings().getVersion() + + (FXGL.getSettings().getApplicationMode() == ApplicationMode.RELEASE + ? "" + : "-" + FXGL.getSettings().getApplicationMode()); + } + + protected MenuContent createContentLoad() { + log.debug("createContentLoad()"); + + ListView list = FXGL.getUIFactoryService().newListView(); + + final var FONT_SIZE = 16.0; + + list.setCellFactory( + e -> + new ListCell<>() { + @Override + protected void updateItem(SaveFile item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setText(null); + setGraphic(null); + } else { + var nameDate = + String.format( + "%-25.25s %s", + item.getName(), + item.getDateTime() + .format( + DateTimeFormatter.ofPattern( + "dd-MM-yyyy HH-mm"))); + + var text = + FXGL.getUIFactoryService() + .newText( + nameDate, + Color.WHITE, + FontType.MONO, + FONT_SIZE); + + setGraphic(text); + } + } + }); + + var task = + getSaveLoadService() + .readSaveFilesTask("./", FXGL.getSettings().getSaveFileExt()) + .onSuccess(files -> list.getItems().addAll(files)); + + FXGL.getTaskService().runAsyncFXWithDialog(task, FXGL.localize("menu.load")); + + list.prefHeightProperty().bind(Bindings.size(list.getItems()).multiply(FONT_SIZE).add(16)); + + var btnLoad = + FXGL.getUIFactoryService().newButton(FXGL.localizedStringProperty("menu.load")); + btnLoad.disableProperty().bind(list.getSelectionModel().selectedItemProperty().isNull()); + + btnLoad.setOnAction( + e -> { + var saveFile = list.getSelectionModel().getSelectedItem(); + fireLoad(saveFile); + }); + + var btnDelete = + FXGL.getUIFactoryService().newButton(FXGL.localizedStringProperty("menu.delete")); + btnDelete.disableProperty().bind(list.getSelectionModel().selectedItemProperty().isNull()); + + btnDelete.setOnAction( + e -> { + var saveFile = list.getSelectionModel().getSelectedItem(); + fireDelete(saveFile); + }); + + var hbox = new HBox(50.0, btnLoad, btnDelete); + hbox.setAlignment(Pos.CENTER); + + return new MenuContent(list, hbox); + } + + protected MenuContent createContentGameplay() { + log.debug("createContentGameplay()"); + + return new MenuContent(); + } + + protected MenuContent createContentControls() { + log.debug("createContentControls()"); + + var grid = new GridPane(); + grid.setAlignment(Pos.CENTER); + grid.setHgap(10.0); + grid.setVgap(10.0); + grid.setPadding(new Insets(10.0, 10.0, 10.0, 10.0)); + grid.getColumnConstraints() + .add(new ColumnConstraints(200.0, 200.0, 200.0, Priority.ALWAYS, HPos.LEFT, true)); + grid.getRowConstraints() + .add(new RowConstraints(40.0, 40.0, 40.0, Priority.ALWAYS, VPos.CENTER, true)); + + grid.setUserData(0); + + for (var e : FXGL.getInput().getAllBindings().entrySet()) { + addNewInputBinding(e.getKey(), e.getValue(), grid); + } + + var scroll = new FXGLScrollPane(grid); + scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS); + scroll.setMaxHeight(getAppHeight() / 2.5); + + var hbox = new HBox(scroll); + hbox.setAlignment(Pos.CENTER); + + return new MenuContent(hbox); + } + + private void addNewInputBinding(UserAction action, Trigger trigger, GridPane grid) { + var actionName = FXGL.getUIFactoryService().newText(action.getName(), Color.WHITE, 18.0); + + var triggerView = new TriggerView(trigger); + triggerView.triggerProperty().bind(FXGL.getInput().triggerProperty(action)); + + triggerView.setOnMouseClicked( + e -> { + if (pressAnyKeyState.isActive) { + return; + } + + pressAnyKeyState.isActive = true; + pressAnyKeyState.actionContext = action; + FXGL.getSceneService().pushSubScene(pressAnyKeyState); + }); + + var hBox = new HBox(); + hBox.setPrefWidth(100.0); + hBox.setAlignment(Pos.CENTER); + hBox.getChildren().add(triggerView); + + var controlsRow = (int) grid.getUserData(); + grid.addRow(controlsRow++, actionName, hBox); + grid.setUserData(controlsRow); + } + + protected MenuContent createContentVideo() { + log.debug("createContentVideo()"); + + var languageBox = + FXGL.getUIFactoryService() + .newChoiceBox( + FXCollections.observableArrayList( + FXGL.getSettings().getSupportedLanguages())); + languageBox.setValue(FXGL.getSettings().getLanguage().get()); + languageBox.setConverter( + new StringConverter<>() { + @Override + public String toString(Language object) { + return object.getNativeName(); + } + + @Override + public Language fromString(String string) { + return FXGL.getSettings().getSupportedLanguages().stream() + .filter(l -> l.getNativeName().equals(string)) + .findFirst() + .orElse(Language.NONE); + } + }); + + FXGL.getSettings().getLanguage().bindBidirectional(languageBox.valueProperty()); + + var vbox = new VBox(); + + if (FXGL.getSettings().isFullScreenAllowed()) { + var cbFullScreen = FXGL.getUIFactoryService().newCheckBox(); + cbFullScreen.selectedProperty().bindBidirectional(FXGL.getSettings().getFullScreen()); + + vbox.getChildren() + .add( + new HBox( + 25.0, + FXGL.getUIFactoryService() + .newText(FXGL.localize("menu.fullscreen") + ": "), + cbFullScreen)); + } + + return new MenuContent( + new HBox( + 25.0, + FXGL.getUIFactoryService() + .newText(FXGL.localizedStringProperty("menu.language").concat(":")), + languageBox), + vbox); + } + + protected MenuContent createContentAudio() { + log.debug("createContentAudio()"); + + var sliderMusic = FXGL.getUIFactoryService().newSlider(); + sliderMusic.setMin(0.0); + sliderMusic.setMax(1.0); + sliderMusic + .valueProperty() + .bindBidirectional(FXGL.getSettings().globalMusicVolumeProperty()); + + var textMusic = + FXGL.getUIFactoryService() + .newText(FXGL.localizedStringProperty("menu.music.volume").concat(": ")); + var percentMusic = FXGL.getUIFactoryService().newText(""); + percentMusic + .textProperty() + .bind(sliderMusic.valueProperty().multiply(100).asString("%.0f")); + + var sliderSound = FXGL.getUIFactoryService().newSlider(); + sliderSound.setMin(0.0); + sliderSound.setMax(1.0); + sliderSound + .valueProperty() + .bindBidirectional(FXGL.getSettings().globalSoundVolumeProperty()); + + var textSound = + FXGL.getUIFactoryService() + .newText(FXGL.localizedStringProperty("menu.sound.volume").concat(": ")); + var percentSound = FXGL.getUIFactoryService().newText(""); + percentSound + .textProperty() + .bind(sliderSound.valueProperty().multiply(100).asString("%.0f")); + + var gridPane = new GridPane(); + gridPane.setHgap(15.0); + gridPane.addRow(0, textMusic, sliderMusic, percentMusic); + gridPane.addRow(1, textSound, sliderSound, percentSound); + + return new MenuContent(gridPane); + } + + protected MenuContent createContentCredits() { + log.debug("createContentCredits()"); + + var pane = new FXGLScrollPane(); + pane.setPrefWidth(500.0); + pane.setPrefHeight(getAppHeight() / 2.0); + pane.setStyle("-fx-background:black;"); + + var vbox = new VBox(); + vbox.setAlignment(Pos.CENTER_LEFT); + vbox.setPrefWidth(pane.getPrefWidth() - 15); + + var credits = new ArrayList<>(FXGL.getSettings().getCredits()); + credits.add(""); + credits.add("Powered by FXGL " + FXGL.getVersion()); + credits.add("Author: Almas Baimagambetov"); + credits.add("https://github.com/AlmasB/FXGL"); + credits.add(""); + + for (var credit : credits) { + if (credit.length() > 45) { + log.warning("Credit color length > 45: " + credit); + } + + vbox.getChildren().add(FXGL.getUIFactoryService().newText(credit)); + } + + pane.setContent(vbox); + + return new MenuContent(pane); + } + + protected MenuContent createContentAchievements() { + log.debug("createContentAchievements()"); + + var content = new MenuContent(); + + FXGL.getAchievementService() + .getAchievementsCopy() + .forEach( + a -> { + var checkBox = new CheckBox(); + checkBox.setDisable(true); + checkBox.selectedProperty().bind(a.achievedProperty()); + + var text = FXGL.getUIFactoryService().newText(a.getName()); + var tooltip = new Tooltip(a.getDescription()); + tooltip.setShowDelay(Duration.seconds(0.1)); + + Tooltip.install(text, tooltip); + + var box = new HBox(25.0, text, checkBox); + box.setAlignment(Pos.CENTER_RIGHT); + + content.getChildren().add(box); + }); + + return content; + } + + protected MenuContent createContentLeaderboard() { + log.debug("createContentLeaderboard()"); + + var leaderboardManager = LeaderboardManager.getInstance(); + + var endlessScores = leaderboardManager.getViewLeaderboard(); + + var contentBox = new VBox(20); + contentBox.setAlignment(Pos.TOP_CENTER); + + var title = + FXGL.getUIFactoryService() + .newText("LEADERBOARD", Color.ORANGE, FontType.MONO, 27.0); + + var grid = new GridPane(); + grid.setAlignment(Pos.TOP_LEFT); + grid.setHgap(10); + grid.setVgap(10); + grid.setPadding(new Insets(10, 10, 10, 10)); + + grid.getColumnConstraints() + .add(new ColumnConstraints(40, 40, 40, Priority.NEVER, HPos.CENTER, false)); // Rank + grid.getColumnConstraints() + .add( + new ColumnConstraints( + 120, 120, 120, Priority.NEVER, HPos.LEFT, false)); // Name + grid.getColumnConstraints() + .add(new ColumnConstraints(70, 70, 70, Priority.NEVER, HPos.RIGHT, false)); // Score + grid.getColumnConstraints() + .add( + new ColumnConstraints( + 50, 50, 50, Priority.NEVER, HPos.CENTER, false)); // Level + grid.getColumnConstraints() + .add( + new ColumnConstraints( + 130, 130, 130, Priority.NEVER, HPos.CENTER, false)); // Date + + grid.addRow( + 0, + FXGL.getUIFactoryService().newText("Rank", Color.NAVAJOWHITE, FontType.MONO, 18.0), + FXGL.getUIFactoryService() + .newText("Player", Color.NAVAJOWHITE, FontType.MONO, 18.0), + FXGL.getUIFactoryService().newText("Score", Color.NAVAJOWHITE, FontType.MONO, 18.0), + FXGL.getUIFactoryService().newText("Level", Color.NAVAJOWHITE, FontType.MONO, 18.0), + FXGL.getUIFactoryService().newText("Date", Color.NAVAJOWHITE, FontType.MONO, 18.0)); + + var rank = 1; + if (endlessScores.isEmpty()) { + var noDataText = + FXGL.getUIFactoryService() + .newText("No data available.", Color.GRAY, FontType.UI, 16.0); + grid.add(noDataText, 0, 1); + GridPane.setColumnSpan(noDataText, 5); + } else { + for (var score : endlessScores) { + var dateStr = + score.timestamp() + .atZone(java.time.ZoneId.systemDefault()) + .format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")); + + grid.addRow( + rank, + FXGL.getUIFactoryService() + .newText("#" + rank, Color.YELLOW, FontType.MONO, 16.0), + FXGL.getUIFactoryService() + .newText(score.name(), Color.CYAN, FontType.MONO, 16.0), + FXGL.getUIFactoryService() + .newText( + String.valueOf(score.score()), + Color.LIGHTGREEN, + FontType.MONO, + 16.0), + FXGL.getUIFactoryService() + .newText( + String.valueOf(score.level()), + Color.ORANGE, + FontType.MONO, + 16.0), + FXGL.getUIFactoryService() + .newText(dateStr, Color.LIGHTGRAY, FontType.MONO, 16.0)); + rank++; + } + } + + var scrollPane = new FXGLScrollPane(grid); + scrollPane.setPrefHeight(getAppHeight() / 2.0); + scrollPane.setPrefWidth(480); + scrollPane.setStyle("-fx-background: black;"); + + contentBox.getChildren().addAll(title, scrollPane); + + return new MenuContent(contentBox); + } + + private void showProfileDialog() { + ChoiceBox profilesBox = + FXGL.getUIFactoryService().newChoiceBox(FXCollections.observableArrayList()); + + var btnNew = + FXGL.getUIFactoryService() + .newButton(FXGL.localizedStringProperty("multiplayer.new")); + var btnSelect = + FXGL.getUIFactoryService() + .newButton(FXGL.localizedStringProperty("multiplayer.select")); + btnSelect.disableProperty().bind(profilesBox.valueProperty().isNull()); + var btnDelete = + FXGL.getUIFactoryService().newButton(FXGL.localizedStringProperty("menu.delete")); + btnDelete.disableProperty().bind(profilesBox.valueProperty().isNull()); + + btnNew.setOnAction( + e -> + FXGL.getDialogService() + .showInputBox( + FXGL.localize("profile.new"), + InputPredicates.ALPHANUM, + s -> { + // TODO: implement profile creation tasks if needed + })); + + btnSelect.setOnAction( + e -> { + var name = profilesBox.getValue(); + FXGL.getSettings().getProfileName().set(name); + // TODO: load profile details + }); + + // The rest of profile loading is commented out in original Kotlin + } + + private static class MenuBox extends VBox { + + public MenuBox(MenuButton... items) { + super(); + for (var item : items) { + add(item); + } + } + + public double getLayoutHeight() { + return 10 * getChildren().size(); + } + + public void add(MenuButton item) { + item.setParent(this); + getChildren().addAll(item); + } + } + + public static class MenuContent extends VBox { + + public int maxW = 0; + private Runnable onOpen; + private Runnable onClose; + + public MenuContent(Node... items) { + super(); + + if (items != null && items.length > 0) { + maxW = (int) items[0].getLayoutBounds().getWidth(); + + for (var n : items) { + var w = (int) n.getLayoutBounds().getWidth(); + if (w > maxW) { + maxW = w; + } + } + + for (var item : items) { + getChildren().addAll(item); + } + } + + sceneProperty() + .addListener( + (v, oldS, newS) -> { + if (newS != null) { + onOpen(); + } else { + onClose(); + } + }); + } + + public void setOnOpen(Runnable onOpenAction) { + this.onOpen = onOpenAction; + } + + public void setOnClose(Runnable onCloseAction) { + this.onClose = onCloseAction; + } + + private void onOpen() { + if (onOpen != null) { + onOpen.run(); + } + } + + private void onClose() { + if (onClose != null) { + onClose.run(); + } + } + } + + private class MenuButton extends Pane { + + public final Button btn; + private MenuBox parent; + private MenuContent cachedContent; + private boolean isAnimating = false; + + public MenuButton(String stringKey) { + btn = FXGL.getUIFactoryService().newButton(FXGL.localizedStringProperty(stringKey)); + btn.setAlignment(Pos.CENTER_LEFT); + btn.setStyle("-fx-background-color: transparent"); + + btn.setPrefWidth(280); + + final var p = new Polygon(0.0, 0.0, 270.0, 0.0, 300.0, 35.0, 0.0, 35.0); + p.setMouseTransparent(true); + + var g = + new LinearGradient( + 0.0, + 1.0, + 1.0, + 0.2, + true, + CycleMethod.NO_CYCLE, + new Stop(0.6, Color.color(1.0, 0.8, 0.0, 0.34)), + new Stop(0.85, Color.color(1.0, 0.8, 0.0, 0.74)), + new Stop(1.0, Color.WHITE)); + + p.fillProperty() + .bind( + Bindings.when(btn.pressedProperty()) + .then((Paint) Color.color(1.0, 0.8, 0.0, 0.75)) + .otherwise(g)); + + p.setStroke(Color.color(0.1, 0.1, 0.1, 0.15)); + + if (!FXGL.getSettings().isNative()) { + p.setEffect(new GaussianBlur()); + } + + p.visibleProperty().bind(btn.hoverProperty()); + + getChildren().addAll(btn, p); + + btn.focusedProperty() + .addListener( + (v, e, isFocused) -> { + if (isFocused) { + var isOK = + animations.stream().noneMatch(Animation::isAnimating) + && !isAnimating; + if (isOK) { + isAnimating = true; + + FXGL.animationBuilder() + .onFinished(() -> isAnimating = false) + .bobbleDown(this) + .buildAndPlay(Menu.this); + } + } + }); + } + + public void setOnAction(EventHandler e) { + btn.setOnAction(e); + } + + public void setParent(MenuBox menu) { + parent = menu; + } + + public void setMenuContent(Supplier contentSupplier) { + setMenuContent(contentSupplier, true); + } + + public void setMenuContent(Supplier contentSupplier, boolean isCached) { + btn.addEventHandler( + ActionEvent.ACTION, + e -> { + if (cachedContent == null || !isCached) { + cachedContent = contentSupplier.get(); + } + + switchMenuContentTo(cachedContent); + }); + } + + public void setChild(MenuBox menu) { + var back = new MenuButton("menu.back"); + menu.getChildren().addFirst(back); + + back.addEventHandler(ActionEvent.ACTION, e -> switchMenuTo(this.parent)); + + btn.addEventHandler(ActionEvent.ACTION, e -> switchMenuTo(menu)); + } + } + + private class PressAnyKeyState extends SubScene { + + private UserAction actionContext; + + private boolean isActive = false; + + public PressAnyKeyState() { + getInput() + .addEventFilter( + KeyEvent.KEY_PRESSED, + e -> { + if (Input.isIllegal(e.getCode())) { + return; + } + + var rebound = + FXGL.getInput() + .rebind( + actionContext, + e.getCode(), + InputModifier.from(e)); + + if (rebound) { + FXGL.getSceneService().popSubScene(); + isActive = false; + } + }); + + getInput() + .addEventFilter( + MouseEvent.MOUSE_PRESSED, + e -> { + var rebound = + FXGL.getInput() + .rebind( + actionContext, + e.getButton(), + InputModifier.from(e)); + + if (rebound) { + FXGL.getSceneService().popSubScene(); + isActive = false; + } + }); + + var rect = new Rectangle(250.0, 100.0); + rect.setStroke(Color.color(0.85, 0.9, 0.9, 0.95)); + rect.setStrokeWidth(10.0); + rect.setArcWidth(15.0); + rect.setArcHeight(15.0); + + var text = FXGL.getUIFactoryService().newText("", 24.0); + text.textProperty().bind(FXGL.localizedStringProperty("menu.pressAnyKey")); + + var pane = new StackPane(rect, text); + pane.setTranslateX(getAppWidth() / 2.0 - 125); + pane.setTranslateY(getAppHeight() / 2.0 - 50); + + getContentRoot().getChildren().add(pane); + } + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/scenes/subscenes/ingame/DeathSubscene.java b/src/main/java/com/github/codestorm/bounceverse/scenes/subscenes/ingame/DeathSubscene.java new file mode 100644 index 0000000..80fac60 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/scenes/subscenes/ingame/DeathSubscene.java @@ -0,0 +1,151 @@ +package com.github.codestorm.bounceverse.scenes.subscenes.ingame; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.scene.SubScene; +import com.almasb.fxgl.ui.FontType; +import com.github.codestorm.bounceverse.systems.manager.metrics.LeaderboardManager; +import com.github.codestorm.bounceverse.typing.records.EndlessScore; + +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.TextField; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +/** + * + * + *

{@link DeathSubscene}

+ * + * {@link SubScene} hiển thị khi người chơi thua (Game Over).
+ * Hiển thị điểm số và cho phép lưu vào leaderboard nếu đạt top. + */ +public class DeathSubscene extends SubScene { + private Rectangle overlay; + private VBox mainContainer; + private final int score; + private final int level; + + public final List onGotoMainMenuListeners = new ArrayList<>(); + + public DeathSubscene(int score, int level) { + this.score = score; + this.level = level; + final var canSavedToLeaderboard = LeaderboardManager.getInstance().isTopScore(score); + + final var root = new StackPane(); + + // Lớp phủ màu đỏ trong suốt + final var overlay = new Rectangle(FXGL.getAppWidth(), FXGL.getAppHeight()); + overlay.setFill(Color.rgb(255, 0, 0, 0.3)); + + // Container chính + final var mainContainer = new VBox(20); + mainContainer.setAlignment(Pos.CENTER); + mainContainer.setMaxWidth(500); + + // Tiêu đề "GAME OVER" + final var gameOverText = + FXGL.getUIFactoryService().newText("GAME OVER", Color.RED, FontType.MONO, 48.0); + final var scoreText = + FXGL.getUIFactoryService() + .newText("Score: " + score, Color.WHITE, FontType.MONO, 24.0); + final var levelText = + FXGL.getUIFactoryService() + .newText("Level: " + level, Color.WHITE, FontType.MONO, 24.0); + + mainContainer.getChildren().addAll(gameOverText, scoreText, levelText); + + if (canSavedToLeaderboard) { + final var congratsText = + FXGL.getUIFactoryService() + .newText( + "Congratulations! You made it to the Top " + + LeaderboardManager.MAX_SIZE + + "!", + Color.YELLOW, + FontType.MONO, + 18.0); + congratsText.setWrappingWidth(450); + + final var enterNameText = + FXGL.getUIFactoryService() + .newText("Enter your name:", Color.WHITE, FontType.UI, 16.0); + final var nameField = new TextField(); + nameField.setPromptText("Enter name (alphanumeric only)"); + nameField.setMaxWidth(300); + nameField.setStyle( + "-fx-background-color: rgba(255, 255, 255, 0.9);" + + "-fx-text-fill: black;" + + "-fx-font-size: 16px;" + + "-fx-padding: 10px;"); + nameField + .textProperty() + .addListener( + (obs, oldVal, newVal) -> { + if (!newVal.matches("[a-zA-Z0-9]*")) { + nameField.setText(oldVal); + } + // Giới hạn độ dài tên + if (newVal.length() > 20) { + nameField.setText(oldVal); + } + }); + final var saveButton = new Button("Save Score"); + saveButton.setStyle( + "-fx-background-color: #4CAF50;" + + "-fx-text-fill: white;" + + "-fx-font-size: 16px;" + + "-fx-padding: 10px 30px;" + + "-fx-cursor: hand;"); + saveButton.setOnAction( + e -> { + var name = nameField.getText().trim(); + if (name.isEmpty()) { + name = "Anonymous"; + } + saveToLeaderboard(name); + returnToMainMenu(); + }); + + saveButton.disableProperty().bind(nameField.textProperty().isEmpty()); + mainContainer.getChildren().addAll(congratsText, enterNameText, nameField, saveButton); + } + final var mainMenuButton = new Button("Return to Main Menu"); + mainMenuButton.setStyle( + "-fx-background-color: #f44336;" + + "-fx-text-fill: white;" + + "-fx-font-size: 16px;" + + "-fx-padding: 10px 30px;" + + "-fx-cursor: hand;"); + mainMenuButton.setOnAction(e -> returnToMainMenu()); + mainContainer.getChildren().add(mainMenuButton); + + root.getChildren().addAll(overlay, mainContainer); + root.setAlignment(Pos.CENTER); + getContentRoot().getChildren().add(root); + } + + /** + * Lưu điểm số vào leaderboard. + * + * @param playerName Tên người chơi + */ + private void saveToLeaderboard(String playerName) { + final var newScore = new EndlessScore(playerName, score, level, Instant.now()); + LeaderboardManager.getInstance().addScore(newScore); + FXGL.getNotificationService().pushNotification("Score saved to leaderboard!"); + } + + /** Trở về màn hình chính. */ + private void returnToMainMenu() { + onGotoMainMenuListeners.forEach(Runnable::run); + FXGL.getGameController().gotoMainMenu(); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/systems/System.java b/src/main/java/com/github/codestorm/bounceverse/systems/System.java new file mode 100644 index 0000000..3a186ca --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/systems/System.java @@ -0,0 +1,10 @@ +package com.github.codestorm.bounceverse.systems; + +/** + * + * + *

{@link System}

+ * + * Hệ thống game đảm nhiệm chức năng nào đó. + */ +public abstract class System {} diff --git a/src/main/java/com/github/codestorm/bounceverse/systems/init/AppEventSystem.java b/src/main/java/com/github/codestorm/bounceverse/systems/init/AppEventSystem.java new file mode 100644 index 0000000..d464e92 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/systems/init/AppEventSystem.java @@ -0,0 +1,58 @@ +package com.github.codestorm.bounceverse.systems.init; + +import com.almasb.fxgl.app.GameApplication; +import com.almasb.fxgl.dsl.FXGL; +import com.github.codestorm.bounceverse.systems.manager.metrics.LeaderboardManager; +import com.github.codestorm.bounceverse.systems.manager.settings.UserSettingsManager; + +/** + * + * + *

{@link AppEventSystem}

+ * + * {@link InitialSystem} quản lý các sự kiện trên {@link GameApplication}.
+ * Đừng nhầm lẫn với {@link GameSystem}. + * + * @apiNote Đây là một Singleton, cần lấy instance thông qua {@link #getInstance()}. + */ +public final class AppEventSystem extends InitialSystem { + + private AppEventSystem() {} + + public static AppEventSystem getInstance() { + return Holder.INSTANCE; + } + + /** Sự kiện khởi động game. */ + private void onStart() { + // Áp dụng cài đặt người dùng + UserSettingsManager.getInstance().apply(); + + // Đăng ký Save/Load handler + FXGL.getSaveLoadService().addHandler(GameSystem.Variables.SAVE_LOAD_HANDLER); + } + + /** Sự kiện thoát game. */ + private void onExit() { + // Lưu cài đặt + UserSettingsManager.getInstance().save(); + + // Lưu bảng xếp hạng + LeaderboardManager.getInstance().save(); + } + + @Override + public void apply() { + onStart(); + Runtime.getRuntime().addShutdownHook(new Thread(this::onExit)); + } + + /** + * Lazy-loaded singleton holder.
+ * Follow + * Initialization-on-demand holder idiom. + */ + private static final class Holder { + static final AppEventSystem INSTANCE = new AppEventSystem(); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/systems/init/GameSystem.java b/src/main/java/com/github/codestorm/bounceverse/systems/init/GameSystem.java new file mode 100644 index 0000000..14a478b --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/systems/init/GameSystem.java @@ -0,0 +1,467 @@ +package com.github.codestorm.bounceverse.systems.init; + +import com.almasb.fxgl.core.math.FXGLMath; +import com.almasb.fxgl.core.serialization.Bundle; +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.entity.Entity; +import com.almasb.fxgl.entity.SpawnData; +import com.almasb.fxgl.profile.DataFile; +import com.almasb.fxgl.profile.SaveLoadHandler; +import com.github.codestorm.bounceverse.AssetsPath; +import com.github.codestorm.bounceverse.AssetsPath.Textures.Bricks.ColorAssets; +import com.github.codestorm.bounceverse.components.properties.paddle.PaddlePowerComponent; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUpManager; +import com.github.codestorm.bounceverse.factory.entities.*; +import com.github.codestorm.bounceverse.scenes.subscenes.ingame.DeathSubscene; +import com.github.codestorm.bounceverse.typing.enums.EntityType; +import com.github.codestorm.bounceverse.typing.structures.HealthIntValue; +import com.github.codestorm.bounceverse.ui.elements.HorizontalPositiveInteger; +import com.github.codestorm.bounceverse.ui.elements.ingame.Hearts; + +import javafx.geometry.Point2D; +import javafx.scene.paint.Color; +import javafx.scene.text.Text; +import javafx.util.Duration; + +import libs.FastNoiseLite; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Random; + +public final class GameSystem extends InitialSystem { + + private GameSystem() {} + + public static GameSystem getInstance() { + return Holder.INSTANCE; + } + + public static void nextLevel() { + FXGL.getGameTimer().clear(); + PowerUpManager.getInstance().clearAll(); + + FXGL.inc("level", 1); + FXGL.set("seed", FXGL.geti("seed") + 1); + + FXGL.getNotificationService().pushNotification("Level " + FXGL.geti("level")); + + // Dọn dẹp tất cả thực thể động + FXGL.getGameWorld() + .getEntitiesByType( + EntityType.BALL, EntityType.BRICK, EntityType.POWER_UP, EntityType.BULLET) + .forEach(Entity::removeFromWorld); + + // Tạo lại màn chơi sau một khoảng trễ nhỏ để đảm bảo mọi thứ đã được dọn dẹp + FXGL.runOnce( + () -> { + EntitySpawn.paddle(); + EntitySpawn.ball(); + EntitySpawn.bricks(); + }, + Duration.seconds(0.1)); + } + + /** Reset lại toàn bộ game về trạng thái ban đầu. */ + public static void resetGame() { + // Cách làm đúng và an toàn nhất là yêu cầu engine bắt đầu lại từ đầu + FXGL.getGameController().startNewGame(); + } + + public static final class Variables { + + private Variables() {} + + public static final int MAX_LIVES = 3; + public static final int DEFAULT_LIVES = 3; + public static final int DEFAULT_SCORE = 0; + public static final double DEFAULT_BALL_SPEED = 300; + private static final List PADDLE_COLORS = + List.of("blue", "green", "orange", "pink", "red", "yellow"); + + public static final SaveLoadHandler SAVE_LOAD_HANDLER = + new SaveLoadHandler() { + @Override + public void onSave(@NotNull DataFile dataFile) { + final var properties = FXGL.getWorldProperties(); + final HealthIntValue lives = properties.getObject("lives"); + final int score = properties.getInt("score"); + final int level = properties.getInt("level"); + final int seed = properties.getInt("seed"); + final var bundle = new Bundle("game"); + final var paddleColor = properties.getString("paddleColor"); + final var ballSpeed = properties.getDouble("ballSpeed"); + + bundle.put("lives", lives.getValue()); + bundle.put("score", score); + bundle.put("level", level); + bundle.put("seed", seed); + bundle.put("paddleColor", paddleColor); + bundle.put("ballSpeed", ballSpeed); + dataFile.putBundle(bundle); + } + + @Override + public void onLoad(@NotNull DataFile dataFile) { + final var bundle = dataFile.getBundle("game"); + final int intLives = bundle.get("lives"); + final var lives = + new HealthIntValue(GameSystem.Variables.MAX_LIVES, intLives); + final int score = bundle.get("score"); + final int level = bundle.get("level"); + final int seed = bundle.get("seed"); + final var paddleColor = bundle.get("paddleColor"); + final var ballSpeed = bundle.get("ballSpeed"); + + final var vars = FXGL.getWorldProperties(); + vars.clear(); + vars.setValue("lives", lives); + vars.setValue("score", score); + vars.setValue("level", level); + vars.setValue("seed", seed); + vars.setValue("paddleColor", paddleColor); + vars.setValue("ballSpeed", ballSpeed); + } + }; + + public static void loadDefault(com.almasb.fxgl.core.collection.PropertyMap vars) { + final var lives = new HealthIntValue(MAX_LIVES, DEFAULT_LIVES); + vars.clear(); + vars.setValue("lives", lives); + vars.setValue("score", DEFAULT_SCORE); + vars.setValue("level", 1); + vars.setValue("seed", (int) (System.currentTimeMillis() & 0x7FFFFFFF)); + vars.setValue("ballSpeed", DEFAULT_BALL_SPEED); + var randomColor = PADDLE_COLORS.get(FXGLMath.random(0, PADDLE_COLORS.size() - 1)); + vars.setValue("paddleColor", randomColor); + } + + public static void hookDeathSubscene() { + final HealthIntValue livesProperty = FXGL.getWorldProperties().getObject("lives"); + livesProperty.onZeroListeners.add( + () -> { + final var score = FXGL.getWorldProperties().getInt("score"); + final var level = FXGL.getWorldProperties().getInt("level"); + final var deathSubscene = new DeathSubscene(score, level); + deathSubscene.onGotoMainMenuListeners.add( + () -> { + FXGL.getSceneService().popSubScene(); + FXGL.getGameController().resumeEngine(); + }); + FXGL.getSceneService().pushSubScene(deathSubscene); + FXGL.getGameController().pauseEngine(); + }); + } + } + + private static final class EntitySpawn { + + private EntitySpawn() {} + + public static void addFactory() { + FXGL.getGameWorld().addEntityFactory(new WallFactory()); + FXGL.getGameWorld().addEntityFactory(new BrickFactory()); + FXGL.getGameWorld().addEntityFactory(new BulletFactory()); + FXGL.getGameWorld().addEntityFactory(new PaddleFactory()); + FXGL.getGameWorld().addEntityFactory(new BallFactory()); + FXGL.getGameWorld().addEntityFactory(new PowerUpFactory()); + } + + public static void walls() { + FXGL.spawn("wallTop"); + FXGL.spawn("wallBottom"); + FXGL.spawn("wallLeft"); + FXGL.spawn("wallRight"); + } + + public static void bricks() { + final var seed = FXGL.getWorldProperties().getInt("seed"); + var noise = new FastNoiseLite(seed); + + var colors = + AssetsPath.Textures.Bricks.COLORS.values().stream() + .map(ColorAssets::color) + .toArray(Color[]::new); + + noise.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2); + noise.SetFrequency(0.2f); + noise.SetFractalType(FastNoiseLite.FractalType.FBm); + noise.SetFractalOctaves(4); + noise.SetFractalLacunarity(2.1f); + noise.SetFractalGain(0.6f); + noise.SetDomainWarpType(FastNoiseLite.DomainWarpType.OpenSimplex2); + noise.SetDomainWarpAmp(6.0f); + + final var rows = 5; + final var cols = 10; + final var amount = 36; + final var appWidth = FXGL.getAppWidth(); + final var appHeight = FXGL.getAppHeight(); + final double marginX = 40; + final double startY = 60; + final var verticalCoverageRatio = 0.30; + final double spacingX = 5; + final double spacingY = 5; + final var aspect = 30.0 / 80.0; + var availableWidth = appWidth - 2 * marginX; + var wFromWidth = (availableWidth - (cols - 1) * spacingX) / cols; + var hFromWidth = wFromWidth * aspect; + var targetBrickAreaHeight = appHeight * verticalCoverageRatio; + var hMaxByHeight = (targetBrickAreaHeight - (rows - 1) * spacingY) / rows; + var brickW = wFromWidth; + var brickH = hFromWidth; + if (brickH > hMaxByHeight) { + brickH = hMaxByHeight; + brickW = brickH / aspect; + } + double minW = 40, maxW = 160; + if (brickW < minW) { + brickW = minW; + } + if (brickW > maxW) { + brickW = maxW; + } + brickH = brickW * aspect; + var totalH = rows * brickH + (rows - 1) * spacingY; + if (totalH > targetBrickAreaHeight) { + brickH = hMaxByHeight; + brickW = brickH / aspect; + } + var brickWidth = (int) Math.round(brickW); + var brickHeight = (int) Math.round(brickH); + var totalW = cols * brickWidth + (cols - 1) * spacingX; + var startX = Math.max(marginX, (appWidth - totalW) / 2.0); + + record Cell(int gx, int gy, double x, double y, float n) {} + + var cells = new ArrayList(rows * cols); + for (var gy = 0; gy < rows; gy++) { + for (var gx = 0; gx < cols; gx++) { + var posX = startX + gx * (brickWidth + spacingX); + var posY = startY + gy * (brickHeight + spacingY); + var n = noise.GetNoise(gx, gy); + cells.add(new Cell(gx, gy, posX, posY, n)); + } + } + cells.sort(Comparator.comparing(Cell::n).reversed()); + + // *** BẮT ĐẦU THAY ĐỔI LOGIC TẠI ĐÂY *** + // 1. Danh sách các loại gạch BẮT BUỘC. Bạn có thể thêm/bớt ở đây. + final List requiredTypes = + new ArrayList<>( + List.of( + "explodingBrick", // Thêm gạch nổ + "explodingBrick", + "explodingBrick", + "keyBrick", // Thêm gạch chìa khóa + "keyBrick", + "shieldBrick", + "strongBrick", + "normalBrick")); + // Xáo trộn danh sách để vị trí của chúng ngẫu nhiên + Collections.shuffle(requiredTypes, new Random(seed)); + + var target = Math.min(amount, cells.size()); + var colorAssignments = new Color[target]; + System.arraycopy(colors, 0, colorAssignments, 0, Math.min(colors.length, target)); + for (var i = colors.length; i < target; i++) { + var n = cells.get(i).n(); + var normalizedN = (n + 1.0f) / 2.0f; + var colorIndex = (int) (normalizedN * colors.length); + if (colorIndex >= colors.length) { + colorIndex = colors.length - 1; + } + colorAssignments[i] = colors[colorIndex]; + } + for (var i = 0; i < target; i++) { + var c = cells.get(i); + var n = c.n(); + final String type; + + // 2. Ưu tiên spawn các loại gạch bắt buộc + if (i < requiredTypes.size()) { + type = requiredTypes.get(i); + } else { + // 3. Logic ngẫu nhiên cho các viên gạch còn lại, có thêm gạch nổ + if (n > 0.6f) { + type = "shieldBrick"; + } else if (n > 0.3f) { + type = "strongBrick"; + } else if (n > 0.0f) { + type = "explodingBrick"; + } else { + type = "normalBrick"; + } + } + + var data = new SpawnData(); + data.put("pos", new Point2D(c.x(), c.y())); + data.put("width", brickWidth); + data.put("height", brickHeight); + data.put("color", colorAssignments[i]); + FXGL.spawn(type, data); + } + // *** KẾT THÚC THAY ĐỔI *** + } + + public static void paddle() { + FXGL.getGameWorld() + .getEntitiesByType(EntityType.PADDLE) + .forEach(Entity::removeFromWorld); + FXGL.spawn("paddle"); + } + + public static void ball() { + if (FXGL.getGameWorld().getEntitiesByType(EntityType.BALL).isEmpty()) { + var paddle = FXGL.getGameWorld().getSingleton(EntityType.PADDLE); + var x = paddle.getCenter().getX() - BallFactory.DEFAULT_RADIUS; + var y = paddle.getY() - BallFactory.DEFAULT_RADIUS + 1; + FXGL.spawn("ball", new SpawnData(x, y).put("attached", true)); + FXGL.set("ballAttached", true); + } + } + } + + public static final class UI { + + private UI() {} + + private boolean isInitialized = false; + private Hearts heartsDisplay; + private HorizontalPositiveInteger scoreDisplay; + private Text levelDisplay; + private Text cooldownText; + + public static UI getInstance() { + return Holder.INSTANCE; + } + + public void addScoreDisplay() { + if (scoreDisplay == null) { + final var scoreProperty = FXGL.getWorldProperties().intProperty("score"); + scoreDisplay = new HorizontalPositiveInteger(scoreProperty); + scoreDisplay.setMinDigits(6); + } + FXGL.getGameScene().addUINode(scoreDisplay.getView()); + scoreDisplay.getView().setTranslateX(20); + scoreDisplay.getView().setTranslateY(20); + } + + public void addHeartsDisplay() { + if (heartsDisplay == null) { + heartsDisplay = new Hearts(); + } + FXGL.getGameScene().addUINode(heartsDisplay.getView()); + heartsDisplay.getView().setTranslateX(20); + heartsDisplay.getView().setTranslateY(FXGL.getAppHeight() - 52); + } + + public void addLevelDisplay() { + if (levelDisplay == null) { + final var levelProperty = FXGL.getWorldProperties().intProperty("level"); + levelDisplay = FXGL.getUIFactoryService().newText("", Color.WHITE, 24.0); + levelDisplay.textProperty().bind(levelProperty.asString("LEVEL %d")); + } + FXGL.getGameScene().addUINode(levelDisplay); + levelDisplay.setTranslateX(FXGL.getAppWidth() - 120); + levelDisplay.setTranslateY(FXGL.getAppHeight() - 30); + } + + public void addCooldownDisplay() { + if (cooldownText == null) { + cooldownText = FXGL.getUIFactoryService().newText("", 18); + cooldownText.getStyleClass().add("powerup-text"); + } + FXGL.getGameScene().addUINode(cooldownText); + cooldownText.setTranslateX(FXGL.getAppWidth() - 220); + cooldownText.setTranslateY((double) FXGL.getAppHeight() / 2); + } + + public void onUpdate() { + if (!isInitialized || cooldownText == null) { + return; + } + var paddleOpt = FXGL.getGameWorld().getSingletonOptional(EntityType.PADDLE); + if (paddleOpt.isEmpty()) { + cooldownText.setText(""); + return; + } + var powerCompOpt = paddleOpt.get().getComponentOptional(PaddlePowerComponent.class); + if (powerCompOpt.isEmpty()) { + cooldownText.setText(""); + return; + } + var powerComp = powerCompOpt.get(); + var powerCooldown = powerComp.getPowerCooldown(); + var timeLeft = powerCooldown.getTimeLeft(); + if (timeLeft == null || timeLeft.toMillis() <= 0) { + cooldownText.setText("Power: Start"); + } else { + cooldownText.setText("Power: Cooldown"); + } + } + + public void addAll() { + if (isInitialized) { + return; + } + addScoreDisplay(); + addHeartsDisplay(); + addLevelDisplay(); + addCooldownDisplay(); + isInitialized = true; + } + + public void removeAll() { + if (!isInitialized) { + return; + } + if (scoreDisplay != null) { + FXGL.getGameScene().removeUINode(scoreDisplay.getView()); + } + if (heartsDisplay != null) { + FXGL.getGameScene().removeUINode(heartsDisplay.getView()); + } + if (levelDisplay != null) { + FXGL.getGameScene().removeUINode(levelDisplay); + } + if (cooldownText != null) { + FXGL.getGameScene().removeUINode(cooldownText); + } + isInitialized = false; + } + + public void dispose() { + removeAll(); + scoreDisplay = null; + heartsDisplay = null; + levelDisplay = null; + cooldownText = null; + } + + private static final class Holder { + + static final UI INSTANCE = new UI(); + } + } + + @Override + public void apply() { + Variables.hookDeathSubscene(); + EntitySpawn.addFactory(); + EntitySpawn.walls(); + EntitySpawn.bricks(); + EntitySpawn.paddle(); + EntitySpawn.ball(); + UI.getInstance().addAll(); + } + + private static final class Holder { + + static final GameSystem INSTANCE = new GameSystem(); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/core/systems/System.java b/src/main/java/com/github/codestorm/bounceverse/systems/init/InitialSystem.java similarity index 67% rename from src/main/java/com/github/codestorm/bounceverse/core/systems/System.java rename to src/main/java/com/github/codestorm/bounceverse/systems/init/InitialSystem.java index 61bb3ed..0fe6acc 100644 --- a/src/main/java/com/github/codestorm/bounceverse/core/systems/System.java +++ b/src/main/java/com/github/codestorm/bounceverse/systems/init/InitialSystem.java @@ -1,13 +1,14 @@ -package com.github.codestorm.bounceverse.core.systems; +package com.github.codestorm.bounceverse.systems.init; import com.github.codestorm.bounceverse.Bounceverse; +import com.github.codestorm.bounceverse.systems.System; /** * * - *

{@link System}

+ *

{@link InitialSystem}

* - * Hệ thống của game, đảm nhiệm cho một thành phần nào đó của game.
+ * Hệ thống khởi tạo của game, đảm nhiệm cho việc khởi tạo chức năng nào đó của game
*
* Các lớp kế thừa nên thiết kế dựa trên (lazy-loaded) Singleton.
* Tất cả logic của hệ thống được áp dụng thông qua {@link #apply()}. @@ -20,8 +21,8 @@ * A: Tất cả là tại abstract/interface không cho ghi đè static đó!!! 😭😭😭 * */ -abstract class System { - protected System() {} +abstract class InitialSystem extends System { + protected InitialSystem() {} /** * Áp dụng logic của hệ thống vào game.
diff --git a/src/main/java/com/github/codestorm/bounceverse/systems/init/InputSystem.java b/src/main/java/com/github/codestorm/bounceverse/systems/init/InputSystem.java new file mode 100644 index 0000000..3fe5ed9 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/systems/init/InputSystem.java @@ -0,0 +1,159 @@ +package com.github.codestorm.bounceverse.systems.init; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.entity.Entity; +import com.almasb.fxgl.input.UserAction; +import com.almasb.fxgl.physics.PhysicsComponent; +import com.github.codestorm.bounceverse.components.behaviors.Attachment; +import com.github.codestorm.bounceverse.components.behaviors.paddle.ReverseControlComponent; +import com.github.codestorm.bounceverse.components.properties.paddle.PaddlePowerComponent; +import com.github.codestorm.bounceverse.components.properties.powerup.paddle.DuplicatePaddlePowerUp; +import com.github.codestorm.bounceverse.factory.entities.WallFactory; +import com.github.codestorm.bounceverse.typing.enums.EntityType; + +import javafx.scene.input.KeyCode; + +import java.util.List; + +/** Quản lý Input cho game. */ +public final class InputSystem extends InitialSystem { + + private static final double MOVE_SPEED = 400.0; + private static final double WALL_THICKNESS = WallFactory.DEFAULT_THICKNESS; + + private InputSystem() {} + + public static InputSystem getInstance() { + return Holder.INSTANCE; + } + + private void movePaddle(double inputDirection) { + // TODO: Sửa lỗi chọn reset game khi đang giữ di chuyển di chuyển + var mainPaddle = FXGL.getGameWorld().getSingleton(EntityType.PADDLE); + var leftmostPaddle = mainPaddle; + var rightmostPaddle = mainPaddle; + + if (FXGL.getWorldProperties().exists(DuplicatePaddlePowerUp.CLONES_LIST_KEY)) { + List clones = FXGL.geto(DuplicatePaddlePowerUp.CLONES_LIST_KEY); + if (!clones.isEmpty() && clones.get(0).isActive()) { + leftmostPaddle = clones.get(0); + rightmostPaddle = clones.get(1); + } + } + + var isReversed = mainPaddle.hasComponent(ReverseControlComponent.class); + var actualMoveDirection = isReversed ? -inputDirection : inputDirection; + + if (actualMoveDirection < 0) { + if (leftmostPaddle.getX() <= WALL_THICKNESS) { + setAllPaddlesVelocity(0); + return; + } + } else { + if (rightmostPaddle.getRightX() >= FXGL.getAppWidth() - WALL_THICKNESS) { + setAllPaddlesVelocity(0); + return; + } + } + setAllPaddlesVelocity(actualMoveDirection * MOVE_SPEED); + } + + private void setAllPaddlesVelocity(double velocity) { + var mainPaddle = FXGL.getGameWorld().getSingleton(EntityType.PADDLE); + mainPaddle.getComponent(PhysicsComponent.class).setVelocityX(velocity); + + if (FXGL.getWorldProperties().exists(DuplicatePaddlePowerUp.CLONES_LIST_KEY)) { + List clones = FXGL.geto(DuplicatePaddlePowerUp.CLONES_LIST_KEY); + for (var clone : clones) { + clone.getComponent(PhysicsComponent.class).setVelocityX(velocity); + } + } + } + + @Override + public void apply() { + FXGL.getInput() + .addAction( + new UserAction("Move Left") { + @Override + protected void onAction() { + movePaddle(-1.0); + } + + @Override + protected void onActionEnd() { + setAllPaddlesVelocity(0); + } + }, + KeyCode.LEFT); + + FXGL.getInput() + .addAction( + new UserAction("Move Right") { + @Override + protected void onAction() { + movePaddle(1.0); + } + + @Override + protected void onActionEnd() { + setAllPaddlesVelocity(0); + } + }, + KeyCode.RIGHT); + + FXGL.getInput() + .addAction( + new UserAction("Launch Ball") { + @Override + protected void onActionBegin() { + FXGL.getGameWorld() + .getEntitiesByType(EntityType.BALL) + .forEach( + ball -> + ball.getComponentOptional(Attachment.class) + .ifPresent( + a -> { + if (a.isAttached()) { + a.releaseBall(); + FXGL.set( + "ballAttached", + false); + } + })); + } + }, + KeyCode.SPACE); + + FXGL.getInput() + .addAction( + new UserAction("Activate Power") { + @Override + protected void onActionBegin() { + FXGL.getGameWorld() + .getSingletonOptional(EntityType.PADDLE) + .flatMap( + paddle -> + paddle.getComponentOptional( + PaddlePowerComponent.class)) + .ifPresent(PaddlePowerComponent::activatePower); + } + }, + KeyCode.S); + + FXGL.getInput() + .addAction( + new UserAction("Reset Game") { + @Override + protected void onActionBegin() { + GameSystem.resetGame(); + } + }, + KeyCode.R); + } + + private static final class Holder { + + static final InputSystem INSTANCE = new InputSystem(); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/systems/init/PhysicSystem.java b/src/main/java/com/github/codestorm/bounceverse/systems/init/PhysicSystem.java new file mode 100644 index 0000000..a82ac5e --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/systems/init/PhysicSystem.java @@ -0,0 +1,321 @@ +package com.github.codestorm.bounceverse.systems.init; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.entity.Entity; +import com.almasb.fxgl.entity.SpawnData; +import com.almasb.fxgl.physics.CollisionHandler; +import com.almasb.fxgl.physics.PhysicsComponent; +import com.github.codestorm.bounceverse.Utilities; +import com.github.codestorm.bounceverse.components.behaviors.Attack; +import com.github.codestorm.bounceverse.components.properties.Shield; +import com.github.codestorm.bounceverse.components.properties.paddle.PaddleViewManager; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUpContainer; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUpManager; +import com.github.codestorm.bounceverse.factory.entities.BallFactory; +import com.github.codestorm.bounceverse.typing.enums.DirectionUnit; +import com.github.codestorm.bounceverse.typing.enums.EntityType; +import com.github.codestorm.bounceverse.typing.enums.PowerUpType; +import com.github.codestorm.bounceverse.typing.structures.HealthIntValue; + +import javafx.geometry.Point2D; +import javafx.geometry.Side; + +import java.util.List; + +/** + * + * + *

PhysicSystem

+ * + * Quản lý toàn bộ logic va chạm vật lý trong game (ball, paddle, wall, power-up...). + */ +public final class PhysicSystem extends InitialSystem { + + private PhysicSystem() {} + + public static PhysicSystem getInstance() { + return Holder.INSTANCE; + } + + @Override + public void apply() { + var world = FXGL.getPhysicsWorld(); + world.setGravity(0, 0); + + // Bullet vs Brick + world.addCollisionHandler( + new CollisionHandler(EntityType.BULLET, EntityType.BRICK) { + @Override + protected void onCollisionBegin(Entity bullet, Entity brick) { + var bulletPhysics = bullet.getComponent(PhysicsComponent.class); + var velocity = bulletPhysics.getLinearVelocity(); + + Side hitSide; + + if (Math.abs(velocity.getY()) > Math.abs(velocity.getX())) { + if (velocity.getY() < 0) { + hitSide = Side.BOTTOM; + } else { + hitSide = Side.TOP; + } + } else { + if (velocity.getX() < 0) { + hitSide = Side.RIGHT; + } else { + hitSide = Side.LEFT; + } + } + + var shieldOpt = brick.getComponentOptional(Shield.class); + + // Kiểm tra xem mặt vừa bị va chạm có được bảo vệ không + if (shieldOpt.isPresent() && shieldOpt.get().hasSide(hitSide)) { + // Có khiên bảo vệ, không gây sát thương + } else { + // Không có khiên, gây sát thương + bullet.getComponentOptional(Attack.class) + .ifPresent(a -> a.execute(List.of(brick))); + } + + // Viên đạn luôn tự hủy sau va chạm + bullet.removeFromWorld(); + } + }); + + // Bullet vs Wall + world.addCollisionHandler( + new CollisionHandler(EntityType.BULLET, EntityType.WALL) { + @Override + protected void onCollisionBegin(Entity bullet, Entity wall) { + // Đơn giản là xóa viên đạn khi nó chạm vào bất kỳ bức tường nào + bullet.removeFromWorld(); + } + }); + + // Ball vs Brick + world.addCollisionHandler( + new CollisionHandler(EntityType.BALL, EntityType.BRICK) { + @Override + protected void onCollisionBegin(Entity ball, Entity brick) { + var physics = ball.getComponent(PhysicsComponent.class); + var dir = Utilities.Collision.getCollisionDirection(ball, brick); + + var shieldOpt = brick.getComponentOptional(Shield.class); + if (shieldOpt.isPresent() && shieldOpt.get().hasSide(dir.toSide())) { + bounce(physics, dir); + return; + } + + bounce(physics, dir); + ball.getComponentOptional( + com.github.codestorm.bounceverse.components.behaviors.Attack + .class) + .ifPresent(a -> a.execute(java.util.List.of(brick))); + } + }); + + // Ball vs Paddle + world.addCollisionHandler( + new CollisionHandler(EntityType.BALL, EntityType.PADDLE) { + @Override + protected void onCollisionBegin(Entity ball, Entity paddle) { + var physics = ball.getComponent(PhysicsComponent.class); + var offset = + (ball.getCenter().getX() - paddle.getCenter().getX()) + / (paddle.getWidth() / 2.0); + offset = Math.max(-1.0, Math.min(1.0, offset)); + var angle = Math.toRadians(90 - 45 * offset); + var speed = FXGL.getd("ballSpeed"); + physics.setLinearVelocity( + speed * Math.cos(angle), -speed * Math.sin(angle)); + } + }); + + // Ball vs Shield (bottom shield power-up) + world.addCollisionHandler( + new CollisionHandler(EntityType.BALL, PowerUpType.SHIELD) { + @Override + protected void onCollisionBegin(Entity ball, Entity shield) { + ball.getComponentOptional(PhysicsComponent.class) + .ifPresent( + phys -> { + var velocity = phys.getLinearVelocity(); + phys.setLinearVelocity( + new Point2D( + velocity.getX(), + -Math.abs(velocity.getY()))); + }); + } + }); + + // Ball vs Wall + world.addCollisionHandler( + new CollisionHandler(EntityType.BALL, EntityType.WALL) { + @Override + protected void onCollisionBegin(Entity ball, Entity wall) { + var phys = ball.getComponent(PhysicsComponent.class); + var v = phys.getLinearVelocity(); + Side side = wall.getObject("side"); + var eps = 0.5; // khoảng đệm nhỏ + + switch (side) { + case LEFT: + { + ball.setX(wall.getRightX() + eps); + phys.setLinearVelocity(Math.abs(v.getX()), v.getY()); + break; + } + case RIGHT: + { + ball.setX(wall.getX() - ball.getWidth() - eps); + phys.setLinearVelocity(-Math.abs(v.getX()), v.getY()); + break; + } + case TOP: + { + ball.setY(wall.getBottomY() + eps); + phys.setLinearVelocity(v.getX(), Math.abs(v.getY())); + break; + } + case BOTTOM: + { + // KIỂM TRA: Có tấm khiên nào đang hoạt động trên màn hình + // không? + if (FXGL.getGameWorld() + .getEntitiesByType(PowerUpType.SHIELD) + .isEmpty()) { + + // --- LOGIC CŨ: KHÔNG CÓ KHIÊN --- + // Tường đáy là "tường chết", hủy bóng và trừ mạng. + ball.removeFromWorld(); + + if (FXGL.getGameWorld() + .getEntitiesByType(EntityType.BALL) + .isEmpty()) { + HealthIntValue lives = + FXGL.getWorldProperties().getObject("lives"); + lives.damage(1); + + FXGL.getGameTimer() + .runOnceAfter( + () -> { + if (lives.getValue() > 0) { + var paddle = + FXGL.getGameWorld() + .getSingleton( + EntityType + .PADDLE); + paddle.getComponent( + PaddleViewManager + .class) + .reset(); + PowerUpManager.getInstance() + .clearAll(); + var x = + paddle.getCenter() + .getX() + - BallFactory + .DEFAULT_RADIUS; + var y = + paddle.getY() + - BallFactory + .DEFAULT_RADIUS + * 2; + FXGL.spawn( + "ball", + new SpawnData(x, y) + .put( + "attached", + true)); + FXGL.set("ballAttached", true); + } + }, + javafx.util.Duration.millis(100)); + } + + } else { + // Đảo ngược vận tốc Y để bóng nảy lên + var vel = phys.getLinearVelocity(); + phys.setLinearVelocity(vel.getX(), -Math.abs(vel.getY())); + } + break; + } + } + + var currentVelocity = phys.getLinearVelocity(); + var currentSpeed = currentVelocity.magnitude(); + var targetSpeed = FXGL.getd("ballSpeed"); + + // Đặt một khoảng an toàn để tránh điều chỉnh liên tục do sai số vật lý + var minAllowedSpeed = targetSpeed * 0.95; + var maxAllowedSpeed = targetSpeed * 1.05; + + var newSpeed = currentSpeed; + + if (currentSpeed < minAllowedSpeed) { + newSpeed = minAllowedSpeed; + } else if (currentSpeed > maxAllowedSpeed) { + newSpeed = maxAllowedSpeed; + } + + // Chỉ cập nhật lại vận tốc nếu tốc độ thực sự bị thay đổi + if (Math.abs(newSpeed - currentSpeed) > 1e-3) { + phys.setLinearVelocity(currentVelocity.normalize().multiply(newSpeed)); + } + } + }); + + world.addCollisionHandler( + new CollisionHandler(EntityType.PADDLE, EntityType.POWER_UP) { + @Override + protected void onCollisionBegin(Entity paddle, Entity powerUp) { + if (paddle.isActive() && powerUp.isActive()) { + powerUp.getComponentOptional(PowerUpContainer.class) + .ifPresent( + container -> { + container.addTo(paddle); + container + .getContainer() + .values() + .forEach( + c -> { + if (c instanceof PowerUp p) { + p.apply(paddle); + } + }); + }); + powerUp.removeFromWorld(); + } + } + }); + } + + /** + * Đảo hướng vận tốc theo hướng va chạm. + * + * @param phys a {@link PhysicsComponent} + * @param dir a {@link DirectionUnit} + */ + private static void bounce(PhysicsComponent phys, DirectionUnit dir) { + if (dir == null) { + return; + } + + switch (dir) { + case UP, DOWN: + phys.setLinearVelocity(phys.getVelocityX(), -phys.getVelocityY()); + break; + case LEFT, RIGHT: + phys.setLinearVelocity(-phys.getVelocityX(), phys.getVelocityY()); + break; + default: + break; + } + } + + private static final class Holder { + + static final PhysicSystem INSTANCE = new PhysicSystem(); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/systems/init/PowerUpSpawner.java b/src/main/java/com/github/codestorm/bounceverse/systems/init/PowerUpSpawner.java new file mode 100644 index 0000000..8e71b18 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/systems/init/PowerUpSpawner.java @@ -0,0 +1,114 @@ +package com.github.codestorm.bounceverse.systems.init; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.logging.Logger; +import com.almasb.fxgl.texture.Texture; +import com.github.codestorm.bounceverse.components.properties.powerup.PowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.ball.*; +import com.github.codestorm.bounceverse.components.properties.powerup.misc.ExtraLifePowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.misc.ShieldPowerUp; +import com.github.codestorm.bounceverse.components.properties.powerup.paddle.*; +import com.github.codestorm.bounceverse.typing.enums.PowerUpType; + +import javafx.geometry.Point2D; + +import java.util.*; + +/** + * + * + *

PowerUpSpawner

+ * + * Random loại Power-Up theo trọng số. + */ +public final class PowerUpSpawner { + + private static final Logger LOGGER = Logger.get(PowerUpSpawner.class); + + private static final Map WEIGHTS = + Map.ofEntries( + Map.entry(PowerUpType.EXPAND_PADDLE, 0.15), + Map.entry(PowerUpType.SHRINK_PADDLE, 0.10), + Map.entry(PowerUpType.MULTI_BALL, 0.10), + Map.entry(PowerUpType.SLOW_BALL, 0.10), + Map.entry(PowerUpType.FAST_BALL, 0.10), + Map.entry(PowerUpType.SHIELD, 0.10), + Map.entry(PowerUpType.GUN, 0.10), + Map.entry(PowerUpType.REVERSE_PADDLE, 0.15), + Map.entry(PowerUpType.EXTRA_LIFE, 0.08)); + + private static final Random RANDOM = new Random(); + + private PowerUpSpawner() {} + + /** Random theo trọng số */ + public static PowerUpType getRandomPowerUpType() { + var totalWeight = WEIGHTS.values().stream().mapToDouble(Double::doubleValue).sum(); + var r = RANDOM.nextDouble() * totalWeight; + var cumulative = 0.0; + for (var entry : WEIGHTS.entrySet()) { + cumulative += entry.getValue(); + if (r <= cumulative) return entry.getKey(); + } + LOGGER.warning("PowerUp random fallback triggered."); + return PowerUpType.EXPAND_PADDLE; + } + + /** Tạo instance Power-Up */ + public static PowerUp getPowerUpInstance(PowerUpType type) { + return switch (type) { + // Paddle + case EXPAND_PADDLE -> new ExpandPaddlePowerUp(); + case SHRINK_PADDLE -> new ShrinkPaddlePowerUp(); + case GUN -> new GunPowerUp(); + case REVERSE_PADDLE -> new ReversePaddlePowerUp(); + + // Ball + case MULTI_BALL -> new MultipleBallPowerUp(); + case SLOW_BALL -> new SlowBallPowerUp(); + case FAST_BALL -> new FastBallPowerUp(); + + // Misc + case SHIELD -> new ShieldPowerUp(); + case EXTRA_LIFE -> new ExtraLifePowerUp(); + }; + } + + /** Lấy texture theo loại Power-Up */ + public static Texture getPowerUpTexture(PowerUpType type) { + var path = + switch (type) { + // Paddle + case EXPAND_PADDLE -> "power/paddle/Expand Paddle.png"; + case SHRINK_PADDLE -> "power/paddle/Shrink Paddle.png"; + case GUN -> "power/paddle/Gun.png"; + case REVERSE_PADDLE -> "power/paddle/Reverse Paddle.png"; + + // Ball + case MULTI_BALL -> "power/ball/x2 Ball.png"; + case SLOW_BALL -> "power/ball/Slow Ball.png"; + case FAST_BALL -> "power/ball/Fast Ball.png"; + + // Misc + case SHIELD -> "power/misc/shield.png"; + case EXTRA_LIFE -> "power/misc/extra_life.png"; + }; + return FXGL.texture(path); + } + + /** Random một Power-Up (cả instance + texture) */ + public static Map.Entry nextPowerUp() { + var type = getRandomPowerUpType(); + return Map.entry(getPowerUpInstance(type), getPowerUpTexture(type)); + } + + /** Spawn random Power-Up tại vị trí */ + public static void spawnRandom(Point2D position) { + var pair = nextPowerUp(); + FXGL.spawn( + "powerUp", + new com.almasb.fxgl.entity.SpawnData(position) + .put("texture", pair.getValue()) + .put("contains", pair.getKey())); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/systems/init/UISystem.java b/src/main/java/com/github/codestorm/bounceverse/systems/init/UISystem.java new file mode 100644 index 0000000..7310b3a --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/systems/init/UISystem.java @@ -0,0 +1,239 @@ +package com.github.codestorm.bounceverse.systems.init; + +import com.almasb.fxgl.dsl.FXGL; + +import javafx.animation.AnimationTimer; +import javafx.geometry.Point2D; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.effect.BlendMode; +import javafx.scene.effect.GaussianBlur; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.CubicCurveTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.Path; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.Set; + +/** + * UISystem với hiệu ứng sóng neon và nền "Siri" động. Hệ thống này vẽ hiệu ứng LÊN TRÊN nền đen có + * sẵn của GameScene. + */ +public final class UISystem extends InitialSystem { + + // --- Hằng số điều khiển Sóng Neon --- + private static final double WAVE_Y_POSITION = FXGL.getAppHeight() / 2.0; + private static final double WAVE_AMPLITUDE = 50.0; + private static final double WAVE_STROKE_WIDTH = 3.0; + private static final double WAVE_SEPARATION = 12.0; + private static final double WAVE_TIME_OFFSET = 0.4; + private static final double WAVE_STEP = 20.0; + private static final double WAVE_SPEED = 0.8; + private static final double WAVE_BLUR_AMOUNT = 15.0; + + // --- Hằng số điều khiển hiệu ứng nền Siri --- + private static final int BLOB_COUNT = 5; + private static final double BLOB_BASE_SIZE = 250.0; + private static final double BLOB_MAX_SPEED = 0.4; + private static final double BLOB_GAUSSIAN_BLUR = 150.0; + + // --- Hằng số mục tiêu --- + private static final Set TARGET_COLORS = + Set.of(Color.BLUE, Color.GREEN, Color.ORANGE, Color.PINK, Color.RED, Color.YELLOW); + + // --- Các biến thành viên --- + private Group backgroundBlobLayer; + private List blobs; + private final Random random = new Random(); + + private Group waveLayer; + private double time = 0; + private final Set collectedColors = new java.util.LinkedHashSet<>(); + private boolean allColorsCollected = false; + + private AnimationTimer animationTimer; + + private UISystem() {} + + public static UISystem getInstance() { + return Holder.INSTANCE; + } + + /** Dọn dẹp và reset lại toàn bộ trạng thái của UISystem. */ + public void dispose() { + // 1. Dừng animation timer cũ nếu nó đang chạy + if (animationTimer != null) { + animationTimer.stop(); + animationTimer = null; + } + + // 2. Dọn dẹp nội dung của các layer và đặt chúng về null + // Không cần xóa chúng khỏi parent, vì toàn bộ scene sẽ được tạo lại. + if (waveLayer != null) { + waveLayer.getChildren().clear(); + waveLayer = null; + } + if (backgroundBlobLayer != null) { + backgroundBlobLayer.getChildren().clear(); + backgroundBlobLayer = null; + } + + // 3. Reset lại tất cả các biến trạng thái về giá trị ban đầu + time = 0; + collectedColors.clear(); + allColorsCollected = false; + } + + @Override + public void apply() { + // 1. Khởi tạo các layer hiệu ứng (KHÔNG CÒN TẠO NỀN ĐEN Ở ĐÂY) + createSiriBlobs(); + createWaveLayer(); + + // 2. Thêm các layer hiệu ứng vào GỐC của scene để chúng nằm dưới các thực thể game + FXGL.getGameScene() + .getRoot() + .getChildren() + .addAll(0, java.util.Arrays.asList(backgroundBlobLayer, waveLayer)); + + // 3. Bắt đầu vòng lặp animation + startAnimationLoop(); + } + + private void createSiriBlobs() { + backgroundBlobLayer = new Group(); + blobs = new ArrayList<>(); + var siriColors = + new Color[] {Color.web("#26F3FF"), Color.web("#A942FF"), Color.web("#FF34BB")}; + for (var i = 0; i < BLOB_COUNT; i++) { + var blob = new Circle(); + blob.setRadius(BLOB_BASE_SIZE + random.nextDouble() * 100); + blob.setCenterX(random.nextDouble() * FXGL.getAppWidth()); + blob.setCenterY(random.nextDouble() * FXGL.getAppHeight()); + blob.setFill(siriColors[i % siriColors.length].deriveColor(0, 1, 1, 0.3)); + var velocity = + new Point2D( + (random.nextDouble() - 0.5) * 2 * BLOB_MAX_SPEED, + (random.nextDouble() - 0.5) * 2 * BLOB_MAX_SPEED); + blob.setUserData(velocity); + blobs.add(blob); + } + backgroundBlobLayer.getChildren().addAll(blobs); + backgroundBlobLayer.setEffect(new GaussianBlur(BLOB_GAUSSIAN_BLUR)); + backgroundBlobLayer.setBlendMode(BlendMode.ADD); + backgroundBlobLayer.setVisible(false); + } + + private void createWaveLayer() { + waveLayer = new Group(); + waveLayer.setOpacity(0.9); + waveLayer.setEffect(new GaussianBlur(WAVE_BLUR_AMOUNT)); + waveLayer.setBlendMode(BlendMode.ADD); + } + + private void updateSiriBackground() { + if (!allColorsCollected) { + return; + } + + if (!backgroundBlobLayer.isVisible()) { + backgroundBlobLayer.setVisible(true); + } + + double appW = FXGL.getAppWidth(); + double appH = FXGL.getAppHeight(); + for (var node : blobs) { + var blob = (Circle) node; + var v = (Point2D) blob.getUserData(); + blob.setCenterX(blob.getCenterX() + v.getX()); + blob.setCenterY(blob.getCenterY() + v.getY()); + if (blob.getCenterX() - blob.getRadius() < 0 + || blob.getCenterX() + blob.getRadius() > appW) { + v = new Point2D(-v.getX(), v.getY()); + } + if (blob.getCenterY() - blob.getRadius() < 0 + || blob.getCenterY() + blob.getRadius() > appH) { + v = new Point2D(v.getX(), -v.getY()); + } + blob.setUserData(v); + } + } + + private Path createWavePath(Color color, double timeOffset, double yOffset) { + var wavePath = new Path(); + wavePath.setStroke(color); + wavePath.setStrokeWidth(WAVE_STROKE_WIDTH); + wavePath.setFill(null); + wavePath.setSmooth(true); + double width = FXGL.getAppWidth(); + var step = WAVE_STEP; + var adjustedTime = time + timeOffset; + var baseY = WAVE_Y_POSITION + yOffset; + var firstX = -step; + var firstY = baseY + Math.sin(firstX / 100 + adjustedTime * WAVE_SPEED) * WAVE_AMPLITUDE; + wavePath.getElements().add(new MoveTo(firstX, firstY)); + for (double x = 0; x <= width + step; x += step) { + var prevX = x - step; + var prevY = baseY + Math.sin(prevX / 100 + adjustedTime * WAVE_SPEED) * WAVE_AMPLITUDE; + var currentY = baseY + Math.sin(x / 100 + adjustedTime * WAVE_SPEED) * WAVE_AMPLITUDE; + wavePath.getElements() + .add( + new CubicCurveTo( + prevX + step / 2, prevY, x - step / 2, currentY, x, currentY)); + } + return wavePath; + } + + public void addColorToWave(Color color) { + if (color != null && !allColorsCollected) { + if (collectedColors.add(color)) { + if (collectedColors.containsAll(TARGET_COLORS)) { + allColorsCollected = true; + System.out.println("Tất cả các màu đã được thu thập! Kích hoạt nền động."); + } + } + } + } + + private void updateWaves() { + waveLayer.getChildren().clear(); + var whiteWave = createWavePath(Color.WHITE, 0.0, 0.0); + waveLayer.getChildren().add(whiteWave); + var i = 0; + for (var brickColor : collectedColors) { + var yOffset = (i + 1) * WAVE_SEPARATION; + var timeOffsetValue = (i + 1) * WAVE_TIME_OFFSET; + var coloredWave = createWavePath(brickColor, timeOffsetValue, yOffset); + waveLayer.getChildren().add(coloredWave); + i++; + } + } + + private void startAnimationLoop() { + // Dừng timer cũ trước khi tạo timer mới để tránh chạy nhiều timer song song + if (animationTimer != null) { + animationTimer.stop(); + } + + // Gán timer mới cho biến thành viên + animationTimer = + new AnimationTimer() { + @Override + public void handle(long now) { + time += 0.01; + updateWaves(); + updateSiriBackground(); + } + }; + animationTimer.start(); + } + + private static final class Holder { + + static final UISystem INSTANCE = new UISystem(); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/systems/manager/Manager.java b/src/main/java/com/github/codestorm/bounceverse/systems/manager/Manager.java new file mode 100644 index 0000000..ffa854d --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/systems/manager/Manager.java @@ -0,0 +1,12 @@ +package com.github.codestorm.bounceverse.systems.manager; + +import com.github.codestorm.bounceverse.systems.System; + +/** + * + * + *

{@link Manager}

+ * + * Hệ thống quản lý một thuộc tính nào đó tồn tại trong game. + */ +public abstract class Manager extends System {} diff --git a/src/main/java/com/github/codestorm/bounceverse/systems/manager/metrics/LeaderboardManager.java b/src/main/java/com/github/codestorm/bounceverse/systems/manager/metrics/LeaderboardManager.java new file mode 100644 index 0000000..fc703c5 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/systems/manager/metrics/LeaderboardManager.java @@ -0,0 +1,129 @@ +package com.github.codestorm.bounceverse.systems.manager.metrics; + +import com.almasb.fxgl.core.serialization.Bundle; +import com.almasb.fxgl.logging.Logger; +import com.github.codestorm.bounceverse.typing.records.EndlessScore; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.MinMaxPriorityQueue; + +import org.jspecify.annotations.NonNull; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Comparator; + +/** + * + * + *

{@link LeaderboardManager}

+ * + * Quản lý Bảng xếp hạng. + * + * @apiNote Đây là một Singleton, cần lấy instance thông qua {@link #getInstance()}. + */ +public final class LeaderboardManager extends MetricsManager { + public static final int MAX_SIZE = 10; + public static final String FILENAME = "leaderboard.dat"; + private static final String ENDLESS = "endless"; + + public static LeaderboardManager getInstance() { + return Holder.INSTANCE; + } + + /** Tải lại thông tin BXH. */ + public void reload() { + final var file = new File(FILENAME); + try { + final var ois = new ObjectInputStream(new FileInputStream(file)); + final var bundle = (Bundle) ois.readObject(); + + final ArrayList<@NonNull EndlessScore> endlessLB = bundle.get(ENDLESS); + + endlessLeaderboard.clear(); + endlessLeaderboard.addAll(endlessLB); + + Logger.get(LeaderboardManager.class) + .infof("Loaded leaderboard from: %s", file.getAbsolutePath()); + + } catch (Exception e) { + Logger.get(LeaderboardManager.class) + .warning("Leaderboard file not found or corrupted. Using new one.", e); + } + } + + /** Lưu lại BXH. */ + public void save() { + final var leaderboard = new Bundle("leaderboard"); + leaderboard.put(ENDLESS, new ArrayList<>(endlessLeaderboard)); + + try { + final var file = new File(FILENAME); + final var oos = new ObjectOutputStream(new FileOutputStream(file)); + oos.writeObject(leaderboard); + Logger.get(LeaderboardManager.class) + .infof("Saved leaderboard to: %s", file.getAbsolutePath()); + oos.close(); + } catch (Exception e) { + Logger.get(LeaderboardManager.class).warning("Cannot save leaderboard ", e); + } + } + + private LeaderboardManager() { + reload(); + } + + private final MinMaxPriorityQueue<@NonNull EndlessScore> endlessLeaderboard = + MinMaxPriorityQueue.orderedBy(Comparator.reverseOrder()) + .maximumSize(MAX_SIZE) + .create(); + + /** + * Kiểm tra xem điểm số có nằm trong top leaderboard không. + * + * @param score Điểm số cần kiểm tra + * @return true nếu điểm số đủ để vào leaderboard + */ + public boolean isTopScore(int score) { + if (endlessLeaderboard.size() < LeaderboardManager.MAX_SIZE) { + return true; + } + final var lowestScore = endlessLeaderboard.peek(); + assert lowestScore != null; + return score > lowestScore.score(); + } + + /** + * Thêm một điểm số mới vào leaderboard nếu đủ điều kiện. + * + * @param newScore Điểm số mới + */ + public void addScore(EndlessScore newScore) { + if (!isTopScore(newScore.score())) { + return; + } + endlessLeaderboard.add(newScore); + save(); + } + + /** + * Lấy danh sách leaderboard hiện tại dưới dạng {@link ImmutableList}. + * + * @return Danh sách leaderboard + */ + public ImmutableList<@NonNull EndlessScore> getViewLeaderboard() { + return ImmutableList.copyOf(endlessLeaderboard); + } + + /** + * Lazy-loaded singleton holder.
+ * Follow + * Initialization-on-demand holder idiom. + */ + private static final class Holder { + static final LeaderboardManager INSTANCE = new LeaderboardManager(); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/systems/manager/metrics/MetricsManager.java b/src/main/java/com/github/codestorm/bounceverse/systems/manager/metrics/MetricsManager.java new file mode 100644 index 0000000..ed43c08 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/systems/manager/metrics/MetricsManager.java @@ -0,0 +1,12 @@ +package com.github.codestorm.bounceverse.systems.manager.metrics; + +import com.github.codestorm.bounceverse.systems.manager.Manager; + +/** + * + * + *

{@link MetricsManager}

+ * + * Quản lý các thông số (sau khi chơi) đến từ người chơi. + */ +public abstract class MetricsManager extends Manager {} diff --git a/src/main/java/com/github/codestorm/bounceverse/systems/manager/settings/GameSettingsManager.java b/src/main/java/com/github/codestorm/bounceverse/systems/manager/settings/GameSettingsManager.java new file mode 100644 index 0000000..6077fde --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/systems/manager/settings/GameSettingsManager.java @@ -0,0 +1,75 @@ +package com.github.codestorm.bounceverse.systems.manager.settings; + +import com.almasb.fxgl.app.ApplicationMode; +import com.almasb.fxgl.app.GameSettings; +import com.almasb.fxgl.app.MenuItem; +import com.almasb.fxgl.app.ReadOnlyGameSettings; +import com.almasb.fxgl.dsl.FXGL; +import com.github.codestorm.bounceverse.Utilities; +import com.github.codestorm.bounceverse.factory.SceneFactory; + +import javafx.stage.StageStyle; + +import java.io.IOException; +import java.util.EnumSet; + +/** + * + * + *

{@link GameSettingsManager}

+ * + * Trình quản lý việc apply {@link GameSettings}.
+ * + * @see UserSettingsManager + */ +public final class GameSettingsManager extends SettingsManager { + private GameSettingsManager() {} + + /** + * Tải các settings từ file đã thiết lập vào bộ nhớ.
+ * Cần tải {@link LaunchOptionsManager#load(String...)} và {@link UserSettingsManager#load()} + * trước khi dùng. + * + * @param settings Nơi tải vào + * @throws IOException if an error occurred when reading from the input stream. + */ + public static void load(GameSettings settings) throws IOException { + final var gameSettings = Utilities.IO.loadProperties("/settings.properties"); + + // ? General + settings.setTitle(gameSettings.getProperty("general.name")); + settings.setVersion(gameSettings.getProperty("general.version")); + settings.setCredits(Utilities.IO.readTextFile("/assets/credits.txt")); + settings.setApplicationMode( + Boolean.parseBoolean(gameSettings.getProperty("general.devMode")) + ? ApplicationMode.DEVELOPER + : (LaunchOptionsManager.getInstance().get().debug()) + ? ApplicationMode.DEBUG + : ApplicationMode.RELEASE); + + // ? Display + settings.setWidth(Integer.parseInt(gameSettings.getProperty("video.width"))); + settings.setHeight(Integer.parseInt(gameSettings.getProperty("video.height"))); + settings.setStageStyle(StageStyle.DECORATED); + settings.setManualResizeEnabled(false); + settings.setFullScreenAllowed(true); + settings.setFullScreenFromStart( + UserSettingsManager.getInstance().get().getVideo().isFullscreen()); + + // ? Game + settings.setSceneFactory(new SceneFactory()); + settings.setMainMenuEnabled(true); + // QUAN TRỌNG: Đảm bảo dòng này là 'true' + settings.setIntroEnabled(true); + settings.setEnabledMenuItems(EnumSet.of(MenuItem.EXTRA, MenuItem.SAVE_LOAD)); + } + + /** + * Lây settings trong game. + * + * @return Settings trong game (Read-only) + */ + public static ReadOnlyGameSettings get() { + return FXGL.getSettings(); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/systems/manager/settings/LaunchOptionsManager.java b/src/main/java/com/github/codestorm/bounceverse/systems/manager/settings/LaunchOptionsManager.java new file mode 100644 index 0000000..0b5bc48 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/systems/manager/settings/LaunchOptionsManager.java @@ -0,0 +1,48 @@ +package com.github.codestorm.bounceverse.systems.manager.settings; + +import com.github.codestorm.bounceverse.Utilities; +import com.github.codestorm.bounceverse.typing.records.LaunchOptions; + +/** + * + * + *

{@link LaunchOptionsManager}

+ * + * Các tùy chọn khi khởi động được áp dụng trong game.
+ * Sử dụng {@link #load(String...)} để tải các options.
+ * + * @apiNote Đây là một Singleton, cần lấy instance thông qua {@link #getInstance()}. + */ +public final class LaunchOptionsManager extends SettingsManager { + public static LaunchOptionsManager getInstance() { + return Holder.INSTANCE; + } + + private LaunchOptions options; + + private LaunchOptionsManager() {} + + /** + * Tải các launch options từ Command-line Arguments. + * + * @param args Command-Line Arguments - được truyền vào từ hàm {@code main()} + */ + public void load(String... args) { + final var map = Utilities.IO.parseArgs(args, null, null); + + options = new LaunchOptions(Boolean.parseBoolean(map.getOrDefault("debug", "false"))); + } + + public LaunchOptions get() { + return options; + } + + /** + * Lazy-loaded singleton holder.
+ * Follow + * Initialization-on-demand holder idiom. + */ + private static final class Holder { + static final LaunchOptionsManager INSTANCE = new LaunchOptionsManager(); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/systems/manager/settings/SettingsManager.java b/src/main/java/com/github/codestorm/bounceverse/systems/manager/settings/SettingsManager.java new file mode 100644 index 0000000..786b58d --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/systems/manager/settings/SettingsManager.java @@ -0,0 +1,12 @@ +package com.github.codestorm.bounceverse.systems.manager.settings; + +import com.github.codestorm.bounceverse.systems.manager.Manager; + +/** + * + * + *

{@link SettingsManager}

+ * + * Quản lý các thiết lập của trò chơi/application. + */ +abstract class SettingsManager extends Manager {} diff --git a/src/main/java/com/github/codestorm/bounceverse/systems/manager/settings/UserSettingsManager.java b/src/main/java/com/github/codestorm/bounceverse/systems/manager/settings/UserSettingsManager.java new file mode 100644 index 0000000..f4e0bad --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/systems/manager/settings/UserSettingsManager.java @@ -0,0 +1,166 @@ +package com.github.codestorm.bounceverse.systems.manager.settings; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.logging.Logger; +import com.github.codestorm.bounceverse.typing.records.UserSettings; +import com.moandjiezana.toml.Toml; +import com.moandjiezana.toml.TomlWriter; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * + * + *

{@link UserSettingsManager}

+ * + * Quản lý {@link UserSettings}.
+ * + * @apiNote Đây là một Singleton, cần lấy instance thông qua {@link #getInstance()}. + * @see GameSettingsManager + */ +public final class UserSettingsManager extends SettingsManager { + private static final String FORMAT = "toml"; + + public static UserSettingsManager getInstance() { + return Holder.INSTANCE; + } + + private UserSettings profile; + + private UserSettingsManager() {} + + /** + * Lấy tên file setting tương ứng của người chơi. + * + * @return Tên file + */ + public static String getSettingsFilename() { + final var username = System.getProperty("user.name"); + return String.format("settings.%s.%s", username, FORMAT); + } + + /** + * Lấy địa chỉ file settings tương ứng của người chơi. + * + * @return Địa chỉ file setting tuyệt đối + */ + public static Path getSettingsFilepath() { + return Paths.get(getSettingsFilename()).toAbsolutePath(); + } + + /** + * Áp dụng setting {@link #profile} mà đã được lưu vào bộ nhớ. + * + * @apiNote Chỉ thực thi khi {@link FXGL} có thể sử dụng an toàn + */ + public void apply() { + // ? Video + FXGL.getPrimaryStage().setFullScreen(profile.getVideo().isFullscreen()); + + // ? Audio + FXGL.getSettings().setGlobalMusicVolume(profile.getAudio().getMusic()); + FXGL.getSettings().setGlobalSoundVolume(profile.getAudio().getSound()); + } + + /** + * Load settings của file {@link #getSettingsFilepath()} vào bộ nhớ {@link #profile}. + * + * @apiNote Chỉ load vào bộ nhớ, không áp dụng. Hãy dùng {@link #apply()}. + */ + public void load() { + final var filepath = getSettingsFilepath(); + try { + final var file = new File(filepath.toUri()); + final var parsed = new Toml().read(file); + profile = parsed.to(UserSettings.class); + Logger.get(UserSettingsManager.class).infof("Read user settings in: %s", filepath); + return; + } catch (Exception e) { + Logger.get(UserSettingsManager.class) + .warning("Cannot read user settings in: " + filepath, e); + } + Logger.get(UserSettingsManager.class).info("Using default user settings"); + profile = new UserSettings(); + } + + /** + * Cập nhật {@link #profile} trong instance với giá trị thực tế. + * + * @apiNote Chỉ thực thi khi {@link FXGL} có thể sử dụng an toàn + */ + public void sync() { + // ? Video + profile.getVideo().setFullscreen(FXGL.getSettings().getFullScreen().get()); + + // ? Audio + profile.getAudio().setMusic(FXGL.getSettings().getGlobalMusicVolume()); + profile.getAudio().setSound(FXGL.getSettings().getGlobalSoundVolume()); + + // ? Controls + syncControls(); + } + + /** Đồng bộ controls settings từ input bindings hiện tại. */ + private void syncControls() { + var bindings = FXGL.getInput().getAllBindings(); + + for (var entry : bindings.entrySet()) { + var action = entry.getKey(); + var trigger = entry.getValue(); + + var triggerStr = trigger.toString(); + + switch (action.getName()) { + case "Move Left": + profile.getControls().setMoveLeft(triggerStr); + break; + case "Move Right": + profile.getControls().setMoveRight(triggerStr); + break; + case "Launch Ball": + profile.getControls().setLaunchBall(triggerStr); + break; + } + } + } + + /** + * Lấy Settings người dùng {@link #profile} trong bộ nhớ. + * + * @apiNote Sử dụng {@link #sync()} để cập nhật settings với giá trị thực tế. + * @return Settings người dùng + */ + public UserSettings get() { + return profile; + } + + /** + * Lưu thiết lập game tại {@link #getSettingsFilepath()}. + * + * @apiNote Cập nhật settings đến thời điểm mới nhất bằng {@link #sync()} + */ + public void save() { + final var filepath = getSettingsFilepath(); + final var file = new File(filepath.toUri()); + final var writer = new TomlWriter(); + try { + writer.write(profile, file); + Logger.get(UserSettingsManager.class).infof("Saved user settings to: %s", filepath); + } catch (IOException e) { + Logger.get(UserSettingsManager.class) + .fatal("Cannot save user settings to: " + filepath, e); + } + } + + /** + * Lazy-loaded singleton holder.
+ * Follow + * Initialization-on-demand holder idiom. + */ + private static final class Holder { + static final UserSettingsManager INSTANCE = new UserSettingsManager(); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/typing/annotations/ForEntity.java b/src/main/java/com/github/codestorm/bounceverse/typing/annotations/OnlyForEntity.java similarity index 58% rename from src/main/java/com/github/codestorm/bounceverse/typing/annotations/ForEntity.java rename to src/main/java/com/github/codestorm/bounceverse/typing/annotations/OnlyForEntity.java index 5d92ae2..e4ad424 100644 --- a/src/main/java/com/github/codestorm/bounceverse/typing/annotations/ForEntity.java +++ b/src/main/java/com/github/codestorm/bounceverse/typing/annotations/OnlyForEntity.java @@ -3,6 +3,7 @@ import com.almasb.fxgl.entity.component.Component; import com.github.codestorm.bounceverse.Utilities; import com.github.codestorm.bounceverse.typing.enums.EntityType; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -11,18 +12,26 @@ /** * * - *

@{@link ForEntity}

+ *

@{@link OnlyForEntity}

* - * Đánh dấu class chỉ định là phù hợp cho entity nào.
- * Nếu chỉ định tất cả entity, hãy truyền vào mảng rỗng {@code {}}.
+ * Ràng buộc đánh dấu class chỉ định là chỉ phù hợp cho entity nào.
*
* Sử dụng {@link Utilities.Compatibility#throwIfNotCompatible(EntityType, Component...)} để kiểm - * tra. + * tra.
+ *
+ * + *

Ví dụ

+ * + *
    + *
  • Cho phép tất cả: Không thêm annotation này. + *
  • Cho phép cụ thể: {@code @OnlyForEntity(A)} hoặc {@code @OnlyForEntity({A, B, C})}. + *
  • Không cho phép: {@code @OnlyForEntity({})}. + *
* * @see EntityType */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) -public @interface ForEntity { +public @interface OnlyForEntity { EntityType[] value(); } diff --git a/src/main/java/com/github/codestorm/bounceverse/typing/enums/BrickType.java b/src/main/java/com/github/codestorm/bounceverse/typing/enums/BrickType.java new file mode 100644 index 0000000..3f32df4 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/typing/enums/BrickType.java @@ -0,0 +1,16 @@ +package com.github.codestorm.bounceverse.typing.enums; + +/** + * + * + *

{@link BrickType}

+ * + * Các loại {@link EntityType#BRICK}. + */ +public enum BrickType { + NORMAL, + STRONG, + SHIELD, + EXPLODING, + KEY +} diff --git a/src/main/java/com/github/codestorm/bounceverse/typing/enums/CollisionGroup.java b/src/main/java/com/github/codestorm/bounceverse/typing/enums/CollisionGroup.java new file mode 100644 index 0000000..38a0fbf --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/typing/enums/CollisionGroup.java @@ -0,0 +1,19 @@ +package com.github.codestorm.bounceverse.typing.enums; + +/** + * Định nghĩa các nhóm va chạm vật lý cho Collision Filtering. Mỗi giá trị là một lũy thừa của 2 để + * có thể kết hợp bằng phép toán bitwise OR. + */ +public enum CollisionGroup { + BALL(1), // 2^0 + BULLET(2), // 2^1 + PADDLE(4), // 2^2 + BRICK(8), // 2^3 + WALL(16); // 2^4 + + public final int bits; + + CollisionGroup(int bits) { + this.bits = bits; + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/typing/enums/DirectionUnit.java b/src/main/java/com/github/codestorm/bounceverse/typing/enums/DirectionUnit.java index 6e6fa4c..f1a4e7c 100644 --- a/src/main/java/com/github/codestorm/bounceverse/typing/enums/DirectionUnit.java +++ b/src/main/java/com/github/codestorm/bounceverse/typing/enums/DirectionUnit.java @@ -2,13 +2,16 @@ import com.almasb.fxgl.core.math.Vec2; +import javafx.geometry.Side; + /** * * - *

#{@link DirectionUnit}

+ *

{@link DirectionUnit}

* - * Vector đơn vị đại diện cho hướng di chuyển. + * Vector đơn vị đại diện cho hướng di chuyển. * + * @apiNote Tránh nhầm lẫn với {@link javafx.geometry.Side} * @see Vec2 */ public enum DirectionUnit { @@ -24,12 +27,22 @@ public enum DirectionUnit { this.vector = new Vec2(vx, vy).normalize(); } - /** - * Lấy vector {@link Vec2} tương ứng. - * - * @return Vector - */ + /** Lấy vector {@link Vec2} tương ứng. */ public Vec2 getVector() { return vector; } + + /** + * Chuyển hướng logic (DirectionUnit) sang hướng hình học (Side). Dùng trong PhysicSystem để + * kiểm tra va chạm với Shield. + */ + public Side toSide() { + return switch (this) { + case UP -> Side.TOP; + case DOWN -> Side.BOTTOM; + case LEFT -> Side.LEFT; + case RIGHT -> Side.RIGHT; + case STAND -> null; + }; + } } diff --git a/src/main/java/com/github/codestorm/bounceverse/typing/enums/EntityType.java b/src/main/java/com/github/codestorm/bounceverse/typing/enums/EntityType.java index 284d6ac..d98f86c 100644 --- a/src/main/java/com/github/codestorm/bounceverse/typing/enums/EntityType.java +++ b/src/main/java/com/github/codestorm/bounceverse/typing/enums/EntityType.java @@ -9,9 +9,9 @@ *

#{@link EntityType}

* * Loại của {@link Entity}, dùng để phân biệt giữa các entity có loại khác nhau.
- * Sử dụng {@link EntityBuilder#type(Enum)} để gán cho entity và {@link Entity#getType()} để truy - * xuất, hoặc {@link Entity#isType(Object)} để kiểm tra. * + * @apiNote Sử dụng {@link EntityBuilder#type(Enum)} để gán cho entity và {@link Entity#getType()} + * để truy xuất, hoặc {@link Entity#isType(Object)} để kiểm tra. * @see EntityBuilder * @see Entity */ @@ -21,5 +21,6 @@ public enum EntityType { BALL, POWER_UP, BULLET, - WALL + WALL, + PADDLE_CLONE } diff --git a/src/main/java/com/github/codestorm/bounceverse/typing/enums/PowerUpType.java b/src/main/java/com/github/codestorm/bounceverse/typing/enums/PowerUpType.java new file mode 100644 index 0000000..0a68865 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/typing/enums/PowerUpType.java @@ -0,0 +1,13 @@ +package com.github.codestorm.bounceverse.typing.enums; + +public enum PowerUpType { + EXPAND_PADDLE, + SHRINK_PADDLE, + REVERSE_PADDLE, + MULTI_BALL, + SLOW_BALL, + FAST_BALL, + SHIELD, + GUN, + EXTRA_LIFE +} diff --git a/src/main/java/com/github/codestorm/bounceverse/typing/interfaces/CanUndo.java b/src/main/java/com/github/codestorm/bounceverse/typing/interfaces/CanUndo.java deleted file mode 100644 index d9f5302..0000000 --- a/src/main/java/com/github/codestorm/bounceverse/typing/interfaces/CanUndo.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.codestorm.bounceverse.typing.interfaces; - -import com.github.codestorm.bounceverse.components.behaviors.UndoableBehavior; - -/** - * - * - *

%{@link CanUndo}

- * - * Có thể hoàn tác trạng thái về lúc trước khi thực thi. - * - * @see UndoableBehavior - */ -public interface CanUndo extends CanExecute { - /** Hoàn tác trạng thái về trước đó. */ - void undo(); -} diff --git a/src/main/java/com/github/codestorm/bounceverse/typing/interfaces/CanExecute.java b/src/main/java/com/github/codestorm/bounceverse/typing/interfaces/Executable.java similarity index 82% rename from src/main/java/com/github/codestorm/bounceverse/typing/interfaces/CanExecute.java rename to src/main/java/com/github/codestorm/bounceverse/typing/interfaces/Executable.java index 762d7c4..7825d66 100644 --- a/src/main/java/com/github/codestorm/bounceverse/typing/interfaces/CanExecute.java +++ b/src/main/java/com/github/codestorm/bounceverse/typing/interfaces/Executable.java @@ -5,11 +5,11 @@ /** * * - *

%{@link CanExecute}

+ *

%{@link Executable}

* * Có thể thực thi hành động nào đó. */ -public interface CanExecute { +public interface Executable { /** * Thực thi hành động. * diff --git a/src/main/java/com/github/codestorm/bounceverse/typing/interfaces/HasCooldown.java b/src/main/java/com/github/codestorm/bounceverse/typing/interfaces/HasCooldown.java new file mode 100644 index 0000000..f6db2fe --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/typing/interfaces/HasCooldown.java @@ -0,0 +1,41 @@ +package com.github.codestorm.bounceverse.typing.interfaces; + +import com.github.codestorm.bounceverse.typing.structures.Cooldown; + +import java.util.List; + +/** + * + * + *

%{@link HasCooldown}

+ * + * {@link Executable} nhưng bị giới hạn hồi chiêu ({@link Cooldown}) + * + * @see Cooldown + */ +public interface HasCooldown extends Executable { + Cooldown getCooldown(); + + /** + * Thực thi Logic bên trong wrapper {@link Executable#execute(List)}. + * + * @param data Dữ liệu truyền vào + * @return {@code true} nếu cho phép thực thi, ngược lại {@code false} + */ + boolean executeLogic(List data); + + /** + * @deprecated Viết logic lên {@link #executeLogic(List)} thay vì method này. + * @param data Dữ liệu truyền vào + */ + @Deprecated + @Override + default void execute(List data) { + if (!getCooldown().getCurrent().isExpired()) { + return; + } + if (executeLogic(data)) { + getCooldown().getCurrent().createNew(); + } + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/typing/interfaces/ScoreLike.java b/src/main/java/com/github/codestorm/bounceverse/typing/interfaces/ScoreLike.java new file mode 100644 index 0000000..1720921 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/typing/interfaces/ScoreLike.java @@ -0,0 +1,27 @@ +package com.github.codestorm.bounceverse.typing.interfaces; + +import java.io.Serializable; +import java.time.Instant; + +/** + * + * + *

%{@link ScoreLike}

+ * + * Giống Điểm số trong trò chơi. + * + * @param Loại Điểm số + */ +public interface ScoreLike> extends Serializable, Comparable { + int score(); + + Instant timestamp(); + + @Override + default int compareTo(T o) { + if (score() != o.score()) { + return Integer.compare(score(), o.score()); + } + return -timestamp().compareTo(o.timestamp()); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/typing/interfaces/Undoable.java b/src/main/java/com/github/codestorm/bounceverse/typing/interfaces/Undoable.java new file mode 100644 index 0000000..c802534 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/typing/interfaces/Undoable.java @@ -0,0 +1,119 @@ +package com.github.codestorm.bounceverse.typing.interfaces; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.time.TimerAction; +import com.github.codestorm.bounceverse.components.Behavior; + +import javafx.util.Duration; + +import java.util.List; + +/** + * + * + *

%{@link Undoable}

+ * + * Có thể thực thi và hoàn tác được. + */ +public interface Undoable extends Executable { + /** + * Lấy duration để tự động hoàn tác. + * + * @return Duration + */ + Duration getDuration(); + + /** + * Lấy trạng thái có xóa behavior khi undo không. + * + * @return {@code true} nếu xóa, {@code false} nếu không + */ + boolean isRemoveWhenUndo(); + + /** + * Lấy dữ liệu đã được modified. + * + * @return Dữ liệu modified + */ + List getModified(); + + /** + * Set dữ liệu modified. + * + * @param modified Dữ liệu modified + */ + void setModified(List modified); + + /** + * Lấy timer action hiện tại. + * + * @return TimerAction + */ + TimerAction getCurrent(); + + /** + * Set timer action hiện tại. + * + * @param current TimerAction + */ + void setCurrent(TimerAction current); + + /** + * Có phải hành động đã được thực thi không. + * + * @return {@code true} nếu đã thực thi, {@code false} nếu chưa + */ + default boolean isActive() { + return getCurrent() != null && getCurrent().isExpired() && getModified() != null; + } + + /** + * Logic bên trong {@link #execute(List)}. + * + * @param data Dữ liệu truyền vào + * @return {@code null} nếu không thực thi, {@link List} các dữ liệu cần hoàn tác lại sau này + * nếu ngược lại + */ + List executeLogic(List data); + + /** + * Logic bên trong {@link Undoable#undo()}. + * + * @param data Dữ liệu cần hoàn tác + * @return {@code true} nếu cho phép hoàn tác, {@code false} nếu không. + */ + boolean undoLogic(List data); + + /** + * @deprecated Viết logic lên {@link #executeLogic(List)} thay vì method này. + * @param data Dữ liệu truyền vào + */ + @Deprecated + @Override + default void execute(List data) { + if (isActive()) { + return; + } + + var modified = executeLogic(data); + if (modified != null) { + setModified(modified); + setCurrent(FXGL.getGameTimer().runOnceAfter(this::undo, getDuration())); + } + } + + /** Hoàn tác trạng thái về trước đó. */ + default void undo() { + if (!isActive()) { + return; + } + + if (undoLogic(getModified())) { + setCurrent(null); + setModified(null); + if (isRemoveWhenUndo() && this instanceof Behavior behavior) { + behavior.getEntity().removeComponent(behavior.getClass()); + } + } + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/typing/records/EndlessScore.java b/src/main/java/com/github/codestorm/bounceverse/typing/records/EndlessScore.java new file mode 100644 index 0000000..64843a8 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/typing/records/EndlessScore.java @@ -0,0 +1,20 @@ +package com.github.codestorm.bounceverse.typing.records; + +import com.github.codestorm.bounceverse.typing.interfaces.ScoreLike; + +import java.time.Instant; + +/** + * + * + *

${@link EndlessScore}

+ * + * Biểu diễn điểm số Endless mode. + * + * @param name Tên người chơi + * @param score Điểm số + * @param level Cấp độ đạt được + * @param timestamp Thời điểm ghi điểm + */ +public record EndlessScore(String name, int score, int level, Instant timestamp) + implements ScoreLike {} diff --git a/src/main/java/com/github/codestorm/bounceverse/typing/records/LaunchOptions.java b/src/main/java/com/github/codestorm/bounceverse/typing/records/LaunchOptions.java new file mode 100644 index 0000000..fd0976e --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/typing/records/LaunchOptions.java @@ -0,0 +1,12 @@ +package com.github.codestorm.bounceverse.typing.records; + +/** + * + * + *

${@link LaunchOptions}

+ * + * Biểu diễn Launch Options. + * + * @param debug Bật/Tắt chế độ DEBUG + */ +public record LaunchOptions(boolean debug) {} diff --git a/src/main/java/com/github/codestorm/bounceverse/typing/records/UserSettings.java b/src/main/java/com/github/codestorm/bounceverse/typing/records/UserSettings.java new file mode 100644 index 0000000..bf82a23 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/typing/records/UserSettings.java @@ -0,0 +1,114 @@ +package com.github.codestorm.bounceverse.typing.records; + +import com.github.codestorm.bounceverse.systems.manager.settings.UserSettingsManager; + +/** + * + * + *

{@link UserSettings}

+ * + * Biểu diễn Settings của người dùng. + * + * @see UserSettingsManager + */ +public final class UserSettings { + private final Video video = new Video(); + private final Audio audio = new Audio(); + private final Controls controls = new Controls(); + + public Video getVideo() { + return video; + } + + public Audio getAudio() { + return audio; + } + + public Controls getControls() { + return controls; + } + + /** Setting về Hình ảnh. */ + public static final class Video { + private double width = 1280; + private double height = 960; + private boolean fullscreen = false; + + public double getWidth() { + return width; + } + + public void setWidth(double width) { + this.width = width; + } + + public double getHeight() { + return height; + } + + public void setHeight(double height) { + this.height = height; + } + + public boolean isFullscreen() { + return fullscreen; + } + + public void setFullscreen(boolean fullscreen) { + this.fullscreen = fullscreen; + } + } + + /** Setting về Âm thanh */ + public static final class Audio { + private double music = 1; + private double sound = 1; + + public double getMusic() { + return music; + } + + public void setMusic(double music) { + this.music = music; + } + + public double getSound() { + return sound; + } + + public void setSound(double sound) { + this.sound = sound; + } + } + + /** Setting về Controls/Phím điều khiển */ + public static final class Controls { + private String moveLeft = "LEFT"; + private String moveRight = "RIGHT"; + private String launchBall = "SPACE"; + + public String getMoveLeft() { + return moveLeft; + } + + public void setMoveLeft(String moveLeft) { + this.moveLeft = moveLeft; + } + + public String getMoveRight() { + return moveRight; + } + + public void setMoveRight(String moveRight) { + this.moveRight = moveRight; + } + + public String getLaunchBall() { + return launchBall; + } + + public void setLaunchBall(String launchBall) { + this.launchBall = launchBall; + } + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/typing/structures/Cooldown.java b/src/main/java/com/github/codestorm/bounceverse/typing/structures/Cooldown.java new file mode 100644 index 0000000..04ea173 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/typing/structures/Cooldown.java @@ -0,0 +1,107 @@ +package com.github.codestorm.bounceverse.typing.structures; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.time.TimerAction; + +import javafx.util.Duration; + +/** + * Lớp Cooldown đã được sửa lỗi dứt điểm. Logic tính toán thời gian còn lại đã được chuyển ra đúng + * chỗ và hoạt động chính xác. + */ +public class Cooldown { + + protected final ActiveCooldown current = new ActiveCooldown(); + protected Duration duration = Duration.INDEFINITE; + + public Duration getDuration() { + return duration; + } + + public void setDuration(Duration duration) { + this.duration = duration; + } + + public ActiveCooldown getCurrent() { + return current; + } + + public Duration getTimeLeft() { + if (current.isExpired()) { + return Duration.ZERO; + } + + // Tính thời gian đã trôi qua kể từ khi cooldown bắt đầu + final var elapsed = Duration.millis(FXGL.getGameTimer().getNow() - current.timestamp); + + // Lấy tổng thời gian trừ đi thời gian đã trôi qua + var timeLeft = duration.subtract(elapsed); + + // Đảm bảo không bao giờ trả về giá trị âm + return timeLeft.lessThan(Duration.ZERO) ? Duration.ZERO : timeLeft; + } + + public Cooldown() {} + + public Cooldown(Duration duration) { + this.duration = duration; + } + + // Lớp con ActiveCooldown giờ chỉ tập trung quản lý trạng thái (start, stop, pause) + public class ActiveCooldown { + + protected TimerAction waiter = null; + protected double timestamp = Double.NaN; // Thời điểm cooldown bắt đầu + protected Runnable onExpiredCallback = null; + + protected void onExpired() { + timestamp = Double.NaN; + if (onExpiredCallback != null) { + onExpiredCallback.run(); + } + } + + public void setOnExpired(Runnable callback) { + this.onExpiredCallback = callback; + } + + public boolean isExpired() { + return (waiter == null) || waiter.isExpired(); + } + + public void expire() { + if (!isExpired()) { + waiter.expire(); + } + } + + public void createNew() { + if (waiter != null && !waiter.isExpired()) { + waiter.expire(); + } + + var gameTimer = FXGL.getGameTimer(); + waiter = gameTimer.runOnceAfter(() -> timestamp = Double.NaN, duration); + + timestamp = gameTimer.getNow(); + } + + public void pause() { + if (!isExpired()) { + waiter.pause(); + } + } + + public void resume() { + if (!isExpired()) { + waiter.resume(); + } + } + + public boolean isPaused() { + return !isExpired() && waiter.isPaused(); + } + + protected ActiveCooldown() {} + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/typing/structures/HealthIntValue.java b/src/main/java/com/github/codestorm/bounceverse/typing/structures/HealthIntValue.java new file mode 100644 index 0000000..173e7e7 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/typing/structures/HealthIntValue.java @@ -0,0 +1,179 @@ +package com.github.codestorm.bounceverse.typing.structures; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * + *

{@link HealthIntValue}

+ * + * Giá trị máu (health) kiểu số nguyên với giới hạn tối đa.
+ * Cung cấp các phương thức để quản lý máu như damage, restore, và kiểm tra trạng thái. + * + * @see com.almasb.fxgl.dsl.components.HealthIntComponent + */ +public class HealthIntValue { + private int value; + private int maxValue; + + /** Danh sách callbacks được gọi khi giá trị máu thay đổi. */ + public final List onChangedListeners = new ArrayList<>(); + + /** Danh sách callbacks được gọi khi nhận sát thương. */ + public final List onDamageListeners = new ArrayList<>(); + + /** Danh sách callbacks được gọi khi hồi máu. */ + public final List onHealListeners = new ArrayList<>(); + + /** Danh sách callbacks được gọi khi máu về 0. */ + public final List onZeroListeners = new ArrayList<>(); + + /** Danh sách callbacks được gọi khi máu lên full. */ + public final List onFullListeners = new ArrayList<>(); + + /** + * Khởi tạo HealthIntValue với giá trị tối đa. + * + * @param maxValue Giá trị máu tối đa + */ + public HealthIntValue(int maxValue) { + this.maxValue = maxValue; + this.value = maxValue; + } + + public HealthIntValue(int maxValue, int value) { + this(maxValue); + this.value = value; + } + + /** + * Lấy giá trị máu hiện tại. + * + * @return Giá trị máu hiện tại + */ + public int getValue() { + return value; + } + + /** + * Set giá trị máu hiện tại. Giá trị sẽ được giới hạn trong khoảng [0, maxValue]. + * + * @param value Giá trị máu mới + */ + public void setValue(int value) { + var oldValue = this.value; + this.value = Math.max(0, Math.min(value, maxValue)); + + if (oldValue != this.value) { + triggerEvents(onChangedListeners); + + if (isZero()) { + triggerEvents(onZeroListeners); + } + if (isFull()) { + triggerEvents(onFullListeners); + } + } + } + + /** + * Lấy giá trị máu tối đa. + * + * @return Giá trị máu tối đa + */ + public int getMaxValue() { + return maxValue; + } + + /** + * Set giá trị máu tối đa. Nếu giá trị hiện tại vượt quá maxValue mới, nó sẽ được điều chỉnh. + * + * @param maxValue Giá trị máu tối đa mới + */ + public void setMaxValue(int maxValue) { + this.maxValue = Math.max(0, maxValue); + if (value > maxValue) { + value = maxValue; + } + } + + /** + * Gây sát thương (giảm máu). + * + * @param damage Lượng sát thương (giá trị dương) + */ + public void damage(int damage) { + var oldValue = getValue(); + setValue(getValue() - damage); + if (getValue() < oldValue) { + triggerEvents(onDamageListeners); + } + } + + /** + * Hồi máu. + * + * @param amount Lượng máu hồi (giá trị dương) + */ + public void restore(int amount) { + var oldValue = getValue(); + setValue(getValue() + amount); + if (getValue() > oldValue) { + triggerEvents(onHealListeners); + } + } + + /** Hồi máu về tối đa. */ + public void restoreToMax() { + setValue(maxValue); + } + + /** + * Kiểm tra máu có bằng 0 không. + * + * @return {@code true} nếu máu bằng 0, ngược lại {@code false} + */ + public boolean isZero() { + return value == 0; + } + + /** + * Kiểm tra máu có đầy không. + * + * @return {@code true} nếu máu bằng maxValue, ngược lại {@code false} + */ + public boolean isFull() { + return value == maxValue; + } + + /** + * Lấy phần trăm máu hiện tại (0-100). + * + * @return Phần trăm máu (0-100) + */ + public double getValuePercent() { + if (maxValue == 0) { + return 0; + } + return (double) value / maxValue * 100.0; + } + + /** + * Kích hoạt tất cả event callbacks trong danh sách. + * + * @param listeners Danh sách callbacks cần kích hoạt + */ + private void triggerEvents(List listeners) { + for (var listener : listeners) { + if (listener != null) { + listener.run(); + } + } + } + + @Override + public String toString() { + return "HealthIntValue{" + "value=" + value + ", maxValue=" + maxValue + '}'; + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/ui/elements/HorizontalPositiveInteger.java b/src/main/java/com/github/codestorm/bounceverse/ui/elements/HorizontalPositiveInteger.java new file mode 100644 index 0000000..848917e --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/ui/elements/HorizontalPositiveInteger.java @@ -0,0 +1,125 @@ +package com.github.codestorm.bounceverse.ui.elements; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.texture.Texture; +import com.github.codestorm.bounceverse.AssetsPath; + +import javafx.beans.property.ReadOnlyIntegerProperty; +import javafx.beans.value.ChangeListener; +import javafx.scene.Node; +import javafx.scene.layout.HBox; + +/** + * + * + *

{@link HorizontalPositiveInteger}

+ * + * Hiển thị {@link Integer} dương theo chiều ngang, sử dụng các {@link Texture} chữ số.
+ */ +public class HorizontalPositiveInteger extends ViewElement { + private static final int DEFAULT_MIN_DIGITS = 0; + private static final double DEFAULT_SPACING = 2; + private static final double DEFAULT_DIGIT_WIDTH = 16; + private static final double DEFAULT_DIGIT_HEIGHT = 24; + + private final ChangeListener listener = (obs, oldVal, newVal) -> update(); + private final HBox container = new HBox(DEFAULT_SPACING); + private int minDigits = DEFAULT_MIN_DIGITS; + private ReadOnlyIntegerProperty value; + private String lastDisplayedValue = ""; + + public HorizontalPositiveInteger(ReadOnlyIntegerProperty value) { + setValue(value); + } + + @Override + public Node getView() { + return container; + } + + @Override + public void update() { + final var score = value.get(); + final var positiveScore = Math.max(0, score); + final var actualDigits = String.valueOf(positiveScore).length(); + final var digitsAmount = Math.max(minDigits, actualDigits); + final var scoreStr = String.format("%0" + digitsAmount + "d", positiveScore); + + final var oldLength = lastDisplayedValue.length(); + final var newLength = scoreStr.length(); + + if (newLength > oldLength) { + // Thêm + for (var i = 0; i < newLength - oldLength; i++) { + var digit = Character.getNumericValue(scoreStr.charAt(i)); + var digitTexture = createDigitTexture(digit); + container.getChildren().addFirst(digitTexture); + } + } else if (newLength < oldLength) { + // Bớt + container.getChildren().remove(0, oldLength - newLength); + } + + // Cập nhật những chữ số thay đổi + var startIndex = Math.max(0, newLength - oldLength); + for (var index = startIndex; index < newLength; index++) { + var oldIndex = index - (newLength - oldLength); + if (oldIndex < 0 + || oldIndex >= oldLength + || scoreStr.charAt(index) != lastDisplayedValue.charAt(oldIndex)) { + updateDigitAt(index, Character.getNumericValue(scoreStr.charAt(index))); + } + } + + lastDisplayedValue = scoreStr; + } + + /** + * Update một chữ số tại vị trí cụ thể. + * + * @param index Vị trí chữ số + * @param digit Chữ số mới (0-9) + */ + private void updateDigitAt(int index, int digit) { + if (index >= container.getChildren().size()) { + return; + } + + var digitTexture = createDigitTexture(digit); + container.getChildren().set(index, digitTexture); + } + + /** + * Tạo texture cho một chữ số. + * + * @param digit Chữ số (0-9) + * @return Texture của chữ số + */ + private Texture createDigitTexture(int digit) { + var texturePath = AssetsPath.Textures.NUMBERS.get(digit); + return FXGL.getAssetLoader() + .loadTexture(texturePath, DEFAULT_DIGIT_WIDTH, DEFAULT_DIGIT_HEIGHT); + } + + public int getMinDigits() { + return minDigits; + } + + public void setMinDigits(int minDigits) { + this.minDigits = minDigits; + update(); + } + + public ReadOnlyIntegerProperty getValue() { + return value; + } + + public void setValue(ReadOnlyIntegerProperty value) { + if (this.value != null) { + this.value.removeListener(listener); + } + this.value = value; + this.value.addListener(listener); + update(); + } +} diff --git a/src/main/java/com/github/codestorm/bounceverse/ui/elements/ViewElement.java b/src/main/java/com/github/codestorm/bounceverse/ui/elements/ViewElement.java new file mode 100644 index 0000000..2c82cba --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/ui/elements/ViewElement.java @@ -0,0 +1,22 @@ +package com.github.codestorm.bounceverse.ui.elements; + +import com.almasb.fxgl.entity.Entity; +import com.almasb.fxgl.entity.components.ViewComponent; + +import javafx.scene.Node; + +/** + * + * + *

%{@link ViewElement}

+ * + * Phần tử UI chứa logic riêng, độc lập và có thể hiển thị trên màn hình.
+ * Đừng nhầm lẫn với {@link ViewComponent} - nó là một thành phần của {@link Entity}, không độc + * lập. + */ +public abstract class ViewElement { + public abstract Node getView(); + + /** Cập nhật Element. */ + public void update() {} +} diff --git a/src/main/java/com/github/codestorm/bounceverse/ui/elements/ingame/Hearts.java b/src/main/java/com/github/codestorm/bounceverse/ui/elements/ingame/Hearts.java new file mode 100644 index 0000000..ab8a0d4 --- /dev/null +++ b/src/main/java/com/github/codestorm/bounceverse/ui/elements/ingame/Hearts.java @@ -0,0 +1,62 @@ +package com.github.codestorm.bounceverse.ui.elements.ingame; + +import com.almasb.fxgl.dsl.FXGL; +import com.almasb.fxgl.texture.Texture; +import com.github.codestorm.bounceverse.AssetsPath; +import com.github.codestorm.bounceverse.typing.structures.HealthIntValue; +import com.github.codestorm.bounceverse.ui.elements.ViewElement; + +import javafx.scene.Node; +import javafx.scene.layout.HBox; +import javafx.scene.shape.Rectangle; + +/** + * + * + *

{@link Hearts}

+ * + * Hiển thị số lượng trái tim (lives) của người chơi trên UI. + */ +public class Hearts extends ViewElement { + public static final double DEFAULT_SPACING = 5; + public static final Rectangle DEFAULT_SIZE = new Rectangle(32, 32); + + private final Texture single; + private final HBox container = new HBox(DEFAULT_SPACING); + + public Hearts() { + single = + FXGL.getAssetLoader() + .loadTexture( + AssetsPath.Textures.HEART, + DEFAULT_SIZE.getWidth(), + DEFAULT_SIZE.getHeight()); + + update(); + final HealthIntValue livesProps = FXGL.getWorldProperties().getObject("lives"); + livesProps.onChangedListeners.add(this::update); + } + + @Override + public void update() { + final HealthIntValue livesProps = FXGL.getWorldProperties().getObject("lives"); + final var lives = livesProps.getValue(); + + if (lives < 0) { + container.getChildren().clear(); + return; + } + while (container.getChildren().size() > lives) { + container.getChildren().removeLast(); + } + while (container.getChildren().size() < lives) { + var heart = single.copy(); + container.getChildren().add(heart); + } + } + + @Override + public Node getView() { + return container; + } +} diff --git a/src/main/java/libs/FastNoiseLite.java b/src/main/java/libs/FastNoiseLite.java new file mode 100644 index 0000000..a958d81 --- /dev/null +++ b/src/main/java/libs/FastNoiseLite.java @@ -0,0 +1,2611 @@ +// MIT License +// +// Copyright(c) 2023 Jordan Peck (jordan.me2@gmail.com) +// Copyright(c) 2023 Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// .'',;:cldxkO00KKXXNNWWWNNXKOkxdollcc::::::;:::ccllloooolllllllllooollc:,'... ...........',;cldxkO000Okxdlc::;;;,,;;;::cclllllll +// ..',;:ldxO0KXXNNNNNNNNXXK0kxdolcc::::::;;;,,,,,,;;;;;;;;;;:::cclllllc:;'.... ...........',;:ldxO0KXXXK0Okxdolc::;;;;::cllodddddo +// ...',:loxO0KXNNNNNXXKK0Okxdolc::;::::::::;;;,,'''''.....''',;:clllllc:;,'............''''''''',;:loxO0KXNNNNNXK0Okxdollccccllodxxxxxxd +// ....';:ldkO0KXXXKK00Okxdolcc:;;;;;::cclllcc:;;,''..... ....',;clooddolcc:;;;;,,;;;;;::::;;;;;;:cloxk0KXNWWWWWWNXKK0Okxddoooddxxkkkkkxx +// .....';:ldxkOOOOOkxxdolcc:;;;,,,;;:cllooooolcc:;'... ..,:codxkkkxddooollloooooooollcc:::::clodkO0KXNWWWWWWNNXK00Okxxxxxxxxkkkkxxx +// . ....';:cloddddo___________,,,,;;:clooddddoolc:,... ..,:ldx__00OOOkkk___kkkkkkxxdollc::::cclodkO0KXXNNNNNNXXK0OOkxxxxxxxxxxxxddd +// .......',;:cccc:| |,,,;;:cclooddddoll:;'.. ..';cox| \KKK000| |KK00OOkxdocc___;::clldxxkO0KKKKK00Okkxdddddddddddddddoo +// .......'',,,,,''| ________|',,;;::cclloooooolc:;'......___:ldk| \KK000| |XKKK0Okxolc| |;;::cclodxxkkkkxxdoolllcclllooodddooooo +// ''......''''....| | ....'',,,,;;;::cclloooollc:;,''.'| |oxk| \OOO0| |KKK00Oxdoll|___|;;;;;::ccllllllcc::;;,,;;;:cclloooooooo +// ;;,''.......... | |_____',,;;;____:___cllo________.___| |___| \xkk| |KK_______ool___:::;________;;;_______...'',;;:ccclllloo +// c:;,''......... | |:::/ ' |lo/ | | \dx| |0/ \d| |cc/ |'/ \......',,;;:ccllo +// ol:;,'..........| _____|ll/ __ |o/ ______|____ ___| | \o| |/ ___ \| |o/ ______|/ ___ \ .......'',;:clo +// dlc;,...........| |::clooo| / | |x\___ \KXKKK0| |dol| |\ \| | | | | |d\___ \..| | / / ....',:cl +// xoc;'... .....'| |llodddd| \__| |_____\ \KKK0O| |lc:| |'\ | |___| | |_____\ \.| |_/___/... ...',;:c +// dlc;'... ....',;| |oddddddo\ | |Okkx| |::;| |..\ |\ /| | | \ |... ....',;:c +// ol:,'.......',:c|___|xxxddollc\_____,___|_________/ddoll|___|,,,|___|...\_____|:\ ______/l|___|_________/...\________|'........',;::cc +// c:;'.......';:codxxkkkkxxolc::;::clodxkOO0OOkkxdollc::;;,,''''',,,,''''''''''',,'''''',;:loxkkOOkxol:;,'''',,;:ccllcc:;,'''''',;::ccll +// ;,'.......',:codxkOO0OOkxdlc:;,,;;:cldxxkkxxdolc:;;,,''.....'',;;:::;;,,,'''''........,;cldkO0KK0Okdoc::;;::cloodddoolc:;;;;;::ccllooo +// .........',;:lodxOO0000Okdoc:,,',,;:clloddoolc:;,''.......'',;:clooollc:;;,,''.......',:ldkOKXNNXX0Oxdolllloddxxxxxxdolccccccllooodddd +// . .....';:cldxkO0000Okxol:;,''',,;::cccc:;,,'.......'',;:cldxxkkxxdolc:;;,'.......';coxOKXNWWWNXKOkxddddxxkkkkkkxdoollllooddxxxxkkk +// ....',;:codxkO000OOxdoc:;,''',,,;;;;,''.......',,;:clodkO00000Okxolc::;,,''..',;:ldxOKXNWWWNNK0OkkkkkkkkkkkxxddooooodxxkOOOOO000 +// ....',;;clodxkkOOOkkdolc:;,,,,,,,,'..........,;:clodxkO0KKXKK0Okxdolcc::;;,,,;;:codkO0XXNNNNXKK0OOOOOkkkkxxdoollloodxkO0KKKXXXXX +// +// VERSION: 1.1.1 +// https://github.com/Auburn/FastNoiseLite + +// To switch between using floats or doubles for input position, +// perform a file-wide replace on the following strings (including /*FNLfloat*/) +// /*FNLfloat*/ float +// /*FNLfloat*/ double + +package libs; + +public class FastNoiseLite +{ + public enum NoiseType + { + OpenSimplex2, + OpenSimplex2S, + Cellular, + Perlin, + ValueCubic, + Value + }; + + public enum RotationType3D + { + None, + ImproveXYPlanes, + ImproveXZPlanes + }; + + public enum FractalType + { + None, + FBm, + Ridged, + PingPong, + DomainWarpProgressive, + DomainWarpIndependent + }; + + public enum CellularDistanceFunction + { + Euclidean, + EuclideanSq, + Manhattan, + Hybrid + }; + + public enum CellularReturnType + { + CellValue, + Distance, + Distance2, + Distance2Add, + Distance2Sub, + Distance2Mul, + Distance2Div + }; + + public enum DomainWarpType + { + OpenSimplex2, + OpenSimplex2Reduced, + BasicGrid + }; + + private enum TransformType3D + { + None, + ImproveXYPlanes, + ImproveXZPlanes, + DefaultOpenSimplex2 + }; + + private int mSeed = 1337; + private float mFrequency = 0.01f; + private NoiseType mNoiseType = NoiseType.OpenSimplex2; + private RotationType3D mRotationType3D = RotationType3D.None; + private TransformType3D mTransformType3D = TransformType3D.DefaultOpenSimplex2; + + private FractalType mFractalType = FractalType.None; + private int mOctaves = 3; + private float mLacunarity = 2.0f; + private float mGain = 0.5f; + private float mWeightedStrength = 0.0f; + private float mPingPongStrength = 2.0f; + + private float mFractalBounding = 1 / 1.75f; + + private CellularDistanceFunction mCellularDistanceFunction = CellularDistanceFunction.EuclideanSq; + private CellularReturnType mCellularReturnType = CellularReturnType.Distance; + private float mCellularJitterModifier = 1.0f; + + private DomainWarpType mDomainWarpType = DomainWarpType.OpenSimplex2; + private TransformType3D mWarpTransformType3D = TransformType3D.DefaultOpenSimplex2; + private float mDomainWarpAmp = 1.0f; + + /// + /// Create new FastNoise object with default seed + /// + public FastNoiseLite() { } + + /// + /// Create new FastNoise object with specified seed + /// + public FastNoiseLite(int seed) + { + SetSeed(seed); + } + + /// + /// Sets seed used for all noise types + /// + /// + /// Default: 1337 + /// + public void SetSeed(int seed) { mSeed = seed; } + + /// + /// Sets frequency for all noise types + /// + /// + /// Default: 0.01 + /// + public void SetFrequency(float frequency) { mFrequency = frequency; } + + /// + /// Sets noise algorithm used for GetNoise(...) + /// + /// + /// Default: OpenSimplex2 + /// + public void SetNoiseType(NoiseType noiseType) + { + mNoiseType = noiseType; + UpdateTransformType3D(); + } + + /// + /// Sets domain rotation type for 3D Noise and 3D DomainWarp. + /// Can aid in reducing directional artifacts when sampling a 2D plane in 3D + /// + /// + /// Default: None + /// + public void SetRotationType3D(RotationType3D rotationType3D) + { + mRotationType3D = rotationType3D; + UpdateTransformType3D(); + UpdateWarpTransformType3D(); + } + + /// + /// Sets method for combining octaves in all fractal noise types + /// + /// + /// Default: None + /// Note: FractalType.DomainWarp... only affects DomainWarp(...) + /// + public void SetFractalType(FractalType fractalType) { mFractalType = fractalType; } + + /// + /// Sets octave count for all fractal noise types + /// + /// + /// Default: 3 + /// + public void SetFractalOctaves(int octaves) + { + mOctaves = octaves; + CalculateFractalBounding(); + } + + /// + /// Sets octave lacunarity for all fractal noise types + /// + /// + /// Default: 2.0 + /// + public void SetFractalLacunarity(float lacunarity) { mLacunarity = lacunarity; } + + /// + /// Sets octave gain for all fractal noise types + /// + /// + /// Default: 0.5 + /// + public void SetFractalGain(float gain) + { + mGain = gain; + CalculateFractalBounding(); + } + + /// + /// Sets octave weighting for all none DomainWarp fratal types + /// + /// + /// Default: 0.0 + /// Note: Keep between 0...1 to maintain -1...1 output bounding + /// + public void SetFractalWeightedStrength(float weightedStrength) { mWeightedStrength = weightedStrength; } + + /// + /// Sets strength of the fractal ping pong effect + /// + /// + /// Default: 2.0 + /// + public void SetFractalPingPongStrength(float pingPongStrength) { mPingPongStrength = pingPongStrength; } + + + /// + /// Sets distance function used in cellular noise calculations + /// + /// + /// Default: Distance + /// + public void SetCellularDistanceFunction(CellularDistanceFunction cellularDistanceFunction) { mCellularDistanceFunction = cellularDistanceFunction; } + + /// + /// Sets return type from cellular noise calculations + /// + /// + /// Default: EuclideanSq + /// + public void SetCellularReturnType(CellularReturnType cellularReturnType) { mCellularReturnType = cellularReturnType; } + + /// + /// Sets the maximum distance a cellular point can move from it's grid position + /// + /// + /// Default: 1.0 + /// Note: Setting this higher than 1 will cause artifacts + /// + public void SetCellularJitter(float cellularJitter) { mCellularJitterModifier = cellularJitter; } + + + /// + /// Sets the warp algorithm when using DomainWarp(...) + /// + /// + /// Default: OpenSimplex2 + /// + public void SetDomainWarpType(DomainWarpType domainWarpType) + { + mDomainWarpType = domainWarpType; + UpdateWarpTransformType3D(); + } + + + /// + /// Sets the maximum warp distance from original position when using DomainWarp(...) + /// + /// + /// Default: 1.0 + /// + public void SetDomainWarpAmp(float domainWarpAmp) { mDomainWarpAmp = domainWarpAmp; } + + + /// + /// 2D noise at given position using current settings + /// + /// + /// Noise output bounded between -1...1 + /// + public float GetNoise(/*FNLfloat*/ double x, /*FNLfloat*/ double y) + { + x *= mFrequency; + y *= mFrequency; + + switch (mNoiseType) + { + case OpenSimplex2: + case OpenSimplex2S: + { + final /*FNLfloat*/ double SQRT3 = (/*FNLfloat*/ double)1.7320508075688772935274463415059; + final /*FNLfloat*/ double F2 = 0.5f * (SQRT3 - 1); + /*FNLfloat*/ double t = (x + y) * F2; + x += t; + y += t; + } + break; + default: + break; + } + + switch (mFractalType) + { + default: + return GenNoiseSingle(mSeed, x, y); + case FBm: + return GenFractalFBm(x, y); + case Ridged: + return GenFractalRidged(x, y); + case PingPong: + return GenFractalPingPong(x, y); + } + } + + /// + /// 3D noise at given position using current settings + /// + /// + /// Noise output bounded between -1...1 + /// + public float GetNoise(/*FNLfloat*/ double x, /*FNLfloat*/ double y, /*FNLfloat*/ double z) + { + x *= mFrequency; + y *= mFrequency; + z *= mFrequency; + + switch (mTransformType3D) + { + case ImproveXYPlanes: + { + /*FNLfloat*/ double xy = x + y; + /*FNLfloat*/ double s2 = xy * -(/*FNLfloat*/ double)0.211324865405187; + z *= (/*FNLfloat*/ double)0.577350269189626; + x += s2 - z; + y = y + s2 - z; + z += xy * (/*FNLfloat*/ double)0.577350269189626; + } + break; + case ImproveXZPlanes: + { + /*FNLfloat*/ double xz = x + z; + /*FNLfloat*/ double s2 = xz * -(/*FNLfloat*/ double)0.211324865405187; + y *= (/*FNLfloat*/ double)0.577350269189626; + x += s2 - y; + z += s2 - y; + y += xz * (/*FNLfloat*/ double)0.577350269189626; + } + break; + case DefaultOpenSimplex2: + { + final /*FNLfloat*/ double R3 = (/*FNLfloat*/ double)(2.0 / 3.0); + /*FNLfloat*/ double r = (x + y + z) * R3; // Rotation, not skew + x = r - x; + y = r - y; + z = r - z; + } + break; + default: + break; + } + + switch (mFractalType) + { + default: + return GenNoiseSingle(mSeed, x, y, z); + case FBm: + return GenFractalFBm(x, y, z); + case Ridged: + return GenFractalRidged(x, y, z); + case PingPong: + return GenFractalPingPong(x, y, z); + } + } + + + /// + /// 2D warps the input position using current domain warp settings + /// + /// + /// Example usage with GetNoise + /// DomainWarp(coord) + /// noise = GetNoise(x, y) + /// + public void DomainWarp(Vector2 coord) + { + switch (mFractalType) + { + default: + DomainWarpSingle(coord); + break; + case DomainWarpProgressive: + DomainWarpFractalProgressive(coord); + break; + case DomainWarpIndependent: + DomainWarpFractalIndependent(coord); + break; + } + } + + /// + /// 3D warps the input position using current domain warp settings + /// + /// + /// Example usage with GetNoise + /// DomainWarp(coord) + /// noise = GetNoise(x, y, z) + /// + public void DomainWarp(Vector3 coord) + { + switch (mFractalType) + { + default: + DomainWarpSingle(coord); + break; + case DomainWarpProgressive: + DomainWarpFractalProgressive(coord); + break; + case DomainWarpIndependent: + DomainWarpFractalIndependent(coord); + break; + } + } + + + private static final float[] Gradients2D = { + 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, + 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, + 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, + -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, + -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, + -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, + 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, + 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, + 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, + -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, + -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, + -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, + 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, + 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, + 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, + -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, + -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, + -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, + 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, + 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, + 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, + -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, + -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, + -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, + 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, + 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, + 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, + -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, + -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, + -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, + 0.38268343236509f, 0.923879532511287f, 0.923879532511287f, 0.38268343236509f, 0.923879532511287f, -0.38268343236509f, 0.38268343236509f, -0.923879532511287f, + -0.38268343236509f, -0.923879532511287f, -0.923879532511287f, -0.38268343236509f, -0.923879532511287f, 0.38268343236509f, -0.38268343236509f, 0.923879532511287f, + }; + + private static final float[] RandVecs2D = { + -0.2700222198f, -0.9628540911f, 0.3863092627f, -0.9223693152f, 0.04444859006f, -0.999011673f, -0.5992523158f, -0.8005602176f, -0.7819280288f, 0.6233687174f, 0.9464672271f, 0.3227999196f, -0.6514146797f, -0.7587218957f, 0.9378472289f, 0.347048376f, + -0.8497875957f, -0.5271252623f, -0.879042592f, 0.4767432447f, -0.892300288f, -0.4514423508f, -0.379844434f, -0.9250503802f, -0.9951650832f, 0.0982163789f, 0.7724397808f, -0.6350880136f, 0.7573283322f, -0.6530343002f, -0.9928004525f, -0.119780055f, + -0.0532665713f, 0.9985803285f, 0.9754253726f, -0.2203300762f, -0.7665018163f, 0.6422421394f, 0.991636706f, 0.1290606184f, -0.994696838f, 0.1028503788f, -0.5379205513f, -0.84299554f, 0.5022815471f, -0.8647041387f, 0.4559821461f, -0.8899889226f, + -0.8659131224f, -0.5001944266f, 0.0879458407f, -0.9961252577f, -0.5051684983f, 0.8630207346f, 0.7753185226f, -0.6315704146f, -0.6921944612f, 0.7217110418f, -0.5191659449f, -0.8546734591f, 0.8978622882f, -0.4402764035f, -0.1706774107f, 0.9853269617f, + -0.9353430106f, -0.3537420705f, -0.9992404798f, 0.03896746794f, -0.2882064021f, -0.9575683108f, -0.9663811329f, 0.2571137995f, -0.8759714238f, -0.4823630009f, -0.8303123018f, -0.5572983775f, 0.05110133755f, -0.9986934731f, -0.8558373281f, -0.5172450752f, + 0.09887025282f, 0.9951003332f, 0.9189016087f, 0.3944867976f, -0.2439375892f, -0.9697909324f, -0.8121409387f, -0.5834613061f, -0.9910431363f, 0.1335421355f, 0.8492423985f, -0.5280031709f, -0.9717838994f, -0.2358729591f, 0.9949457207f, 0.1004142068f, + 0.6241065508f, -0.7813392434f, 0.662910307f, 0.7486988212f, -0.7197418176f, 0.6942418282f, -0.8143370775f, -0.5803922158f, 0.104521054f, -0.9945226741f, -0.1065926113f, -0.9943027784f, 0.445799684f, -0.8951327509f, 0.105547406f, 0.9944142724f, + -0.992790267f, 0.1198644477f, -0.8334366408f, 0.552615025f, 0.9115561563f, -0.4111755999f, 0.8285544909f, -0.5599084351f, 0.7217097654f, -0.6921957921f, 0.4940492677f, -0.8694339084f, -0.3652321272f, -0.9309164803f, -0.9696606758f, 0.2444548501f, + 0.08925509731f, -0.996008799f, 0.5354071276f, -0.8445941083f, -0.1053576186f, 0.9944343981f, -0.9890284586f, 0.1477251101f, 0.004856104961f, 0.9999882091f, 0.9885598478f, 0.1508291331f, 0.9286129562f, -0.3710498316f, -0.5832393863f, -0.8123003252f, + 0.3015207509f, 0.9534596146f, -0.9575110528f, 0.2883965738f, 0.9715802154f, -0.2367105511f, 0.229981792f, 0.9731949318f, 0.955763816f, -0.2941352207f, 0.740956116f, 0.6715534485f, -0.9971513787f, -0.07542630764f, 0.6905710663f, -0.7232645452f, + -0.290713703f, -0.9568100872f, 0.5912777791f, -0.8064679708f, -0.9454592212f, -0.325740481f, 0.6664455681f, 0.74555369f, 0.6236134912f, 0.7817328275f, 0.9126993851f, -0.4086316587f, -0.8191762011f, 0.5735419353f, -0.8812745759f, -0.4726046147f, + 0.9953313627f, 0.09651672651f, 0.9855650846f, -0.1692969699f, -0.8495980887f, 0.5274306472f, 0.6174853946f, -0.7865823463f, 0.8508156371f, 0.52546432f, 0.9985032451f, -0.05469249926f, 0.1971371563f, -0.9803759185f, 0.6607855748f, -0.7505747292f, + -0.03097494063f, 0.9995201614f, -0.6731660801f, 0.739491331f, -0.7195018362f, -0.6944905383f, 0.9727511689f, 0.2318515979f, 0.9997059088f, -0.0242506907f, 0.4421787429f, -0.8969269532f, 0.9981350961f, -0.061043673f, -0.9173660799f, -0.3980445648f, + -0.8150056635f, -0.5794529907f, -0.8789331304f, 0.4769450202f, 0.0158605829f, 0.999874213f, -0.8095464474f, 0.5870558317f, -0.9165898907f, -0.3998286786f, -0.8023542565f, 0.5968480938f, -0.5176737917f, 0.8555780767f, -0.8154407307f, -0.5788405779f, + 0.4022010347f, -0.9155513791f, -0.9052556868f, -0.4248672045f, 0.7317445619f, 0.6815789728f, -0.5647632201f, -0.8252529947f, -0.8403276335f, -0.5420788397f, -0.9314281527f, 0.363925262f, 0.5238198472f, 0.8518290719f, 0.7432803869f, -0.6689800195f, + -0.985371561f, -0.1704197369f, 0.4601468731f, 0.88784281f, 0.825855404f, 0.5638819483f, 0.6182366099f, 0.7859920446f, 0.8331502863f, -0.553046653f, 0.1500307506f, 0.9886813308f, -0.662330369f, -0.7492119075f, -0.668598664f, 0.743623444f, + 0.7025606278f, 0.7116238924f, -0.5419389763f, -0.8404178401f, -0.3388616456f, 0.9408362159f, 0.8331530315f, 0.5530425174f, -0.2989720662f, -0.9542618632f, 0.2638522993f, 0.9645630949f, 0.124108739f, -0.9922686234f, -0.7282649308f, -0.6852956957f, + 0.6962500149f, 0.7177993569f, -0.9183535368f, 0.3957610156f, -0.6326102274f, -0.7744703352f, -0.9331891859f, -0.359385508f, -0.1153779357f, -0.9933216659f, 0.9514974788f, -0.3076565421f, -0.08987977445f, -0.9959526224f, 0.6678496916f, 0.7442961705f, + 0.7952400393f, -0.6062947138f, -0.6462007402f, -0.7631674805f, -0.2733598753f, 0.9619118351f, 0.9669590226f, -0.254931851f, -0.9792894595f, 0.2024651934f, -0.5369502995f, -0.8436138784f, -0.270036471f, -0.9628500944f, -0.6400277131f, 0.7683518247f, + -0.7854537493f, -0.6189203566f, 0.06005905383f, -0.9981948257f, -0.02455770378f, 0.9996984141f, -0.65983623f, 0.751409442f, -0.6253894466f, -0.7803127835f, -0.6210408851f, -0.7837781695f, 0.8348888491f, 0.5504185768f, -0.1592275245f, 0.9872419133f, + 0.8367622488f, 0.5475663786f, -0.8675753916f, -0.4973056806f, -0.2022662628f, -0.9793305667f, 0.9399189937f, 0.3413975472f, 0.9877404807f, -0.1561049093f, -0.9034455656f, 0.4287028224f, 0.1269804218f, -0.9919052235f, -0.3819600854f, 0.924178821f, + 0.9754625894f, 0.2201652486f, -0.3204015856f, -0.9472818081f, -0.9874760884f, 0.1577687387f, 0.02535348474f, -0.9996785487f, 0.4835130794f, -0.8753371362f, -0.2850799925f, -0.9585037287f, -0.06805516006f, -0.99768156f, -0.7885244045f, -0.6150034663f, + 0.3185392127f, -0.9479096845f, 0.8880043089f, 0.4598351306f, 0.6476921488f, -0.7619021462f, 0.9820241299f, 0.1887554194f, 0.9357275128f, -0.3527237187f, -0.8894895414f, 0.4569555293f, 0.7922791302f, 0.6101588153f, 0.7483818261f, 0.6632681526f, + -0.7288929755f, -0.6846276581f, 0.8729032783f, -0.4878932944f, 0.8288345784f, 0.5594937369f, 0.08074567077f, 0.9967347374f, 0.9799148216f, -0.1994165048f, -0.580730673f, -0.8140957471f, -0.4700049791f, -0.8826637636f, 0.2409492979f, 0.9705377045f, + 0.9437816757f, -0.3305694308f, -0.8927998638f, -0.4504535528f, -0.8069622304f, 0.5906030467f, 0.06258973166f, 0.9980393407f, -0.9312597469f, 0.3643559849f, 0.5777449785f, 0.8162173362f, -0.3360095855f, -0.941858566f, 0.697932075f, -0.7161639607f, + -0.002008157227f, -0.9999979837f, -0.1827294312f, -0.9831632392f, -0.6523911722f, 0.7578824173f, -0.4302626911f, -0.9027037258f, -0.9985126289f, -0.05452091251f, -0.01028102172f, -0.9999471489f, -0.4946071129f, 0.8691166802f, -0.2999350194f, 0.9539596344f, + 0.8165471961f, 0.5772786819f, 0.2697460475f, 0.962931498f, -0.7306287391f, -0.6827749597f, -0.7590952064f, -0.6509796216f, -0.907053853f, 0.4210146171f, -0.5104861064f, -0.8598860013f, 0.8613350597f, 0.5080373165f, 0.5007881595f, -0.8655698812f, + -0.654158152f, 0.7563577938f, -0.8382755311f, -0.545246856f, 0.6940070834f, 0.7199681717f, 0.06950936031f, 0.9975812994f, 0.1702942185f, -0.9853932612f, 0.2695973274f, 0.9629731466f, 0.5519612192f, -0.8338697815f, 0.225657487f, -0.9742067022f, + 0.4215262855f, -0.9068161835f, 0.4881873305f, -0.8727388672f, -0.3683854996f, -0.9296731273f, -0.9825390578f, 0.1860564427f, 0.81256471f, 0.5828709909f, 0.3196460933f, -0.9475370046f, 0.9570913859f, 0.2897862643f, -0.6876655497f, -0.7260276109f, + -0.9988770922f, -0.047376731f, -0.1250179027f, 0.992154486f, -0.8280133617f, 0.560708367f, 0.9324863769f, -0.3612051451f, 0.6394653183f, 0.7688199442f, -0.01623847064f, -0.9998681473f, -0.9955014666f, -0.09474613458f, -0.81453315f, 0.580117012f, + 0.4037327978f, -0.9148769469f, 0.9944263371f, 0.1054336766f, -0.1624711654f, 0.9867132919f, -0.9949487814f, -0.100383875f, -0.6995302564f, 0.7146029809f, 0.5263414922f, -0.85027327f, -0.5395221479f, 0.841971408f, 0.6579370318f, 0.7530729462f, + 0.01426758847f, -0.9998982128f, -0.6734383991f, 0.7392433447f, 0.639412098f, -0.7688642071f, 0.9211571421f, 0.3891908523f, -0.146637214f, -0.9891903394f, -0.782318098f, 0.6228791163f, -0.5039610839f, -0.8637263605f, -0.7743120191f, -0.6328039957f, + }; + + private static final float[] Gradients3D = { + 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, + 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, + 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, + 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, + 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, + 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, + 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, + 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, + 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, + 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, + 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, + 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, + 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, + 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, + 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, + 1, 1, 0, 0, 0,-1, 1, 0, -1, 1, 0, 0, 0,-1,-1, 0 + }; + + private static final float[] RandVecs3D = { + -0.7292736885f, -0.6618439697f, 0.1735581948f, 0, 0.790292081f, -0.5480887466f, -0.2739291014f, 0, 0.7217578935f, 0.6226212466f, -0.3023380997f, 0, 0.565683137f, -0.8208298145f, -0.0790000257f, 0, 0.760049034f, -0.5555979497f, -0.3370999617f, 0, 0.3713945616f, 0.5011264475f, 0.7816254623f, 0, -0.1277062463f, -0.4254438999f, -0.8959289049f, 0, -0.2881560924f, -0.5815838982f, 0.7607405838f, 0, + 0.5849561111f, -0.662820239f, -0.4674352136f, 0, 0.3307171178f, 0.0391653737f, 0.94291689f, 0, 0.8712121778f, -0.4113374369f, -0.2679381538f, 0, 0.580981015f, 0.7021915846f, 0.4115677815f, 0, 0.503756873f, 0.6330056931f, -0.5878203852f, 0, 0.4493712205f, 0.601390195f, 0.6606022552f, 0, -0.6878403724f, 0.09018890807f, -0.7202371714f, 0, -0.5958956522f, -0.6469350577f, 0.475797649f, 0, + -0.5127052122f, 0.1946921978f, -0.8361987284f, 0, -0.9911507142f, -0.05410276466f, -0.1212153153f, 0, -0.2149721042f, 0.9720882117f, -0.09397607749f, 0, -0.7518650936f, -0.5428057603f, 0.3742469607f, 0, 0.5237068895f, 0.8516377189f, -0.02107817834f, 0, 0.6333504779f, 0.1926167129f, -0.7495104896f, 0, -0.06788241606f, 0.3998305789f, 0.9140719259f, 0, -0.5538628599f, -0.4729896695f, -0.6852128902f, 0, + -0.7261455366f, -0.5911990757f, 0.3509933228f, 0, -0.9229274737f, -0.1782808786f, 0.3412049336f, 0, -0.6968815002f, 0.6511274338f, 0.3006480328f, 0, 0.9608044783f, -0.2098363234f, -0.1811724921f, 0, 0.06817146062f, -0.9743405129f, 0.2145069156f, 0, -0.3577285196f, -0.6697087264f, -0.6507845481f, 0, -0.1868621131f, 0.7648617052f, -0.6164974636f, 0, -0.6541697588f, 0.3967914832f, 0.6439087246f, 0, + 0.6993340405f, -0.6164538506f, 0.3618239211f, 0, -0.1546665739f, 0.6291283928f, 0.7617583057f, 0, -0.6841612949f, -0.2580482182f, -0.6821542638f, 0, 0.5383980957f, 0.4258654885f, 0.7271630328f, 0, -0.5026987823f, -0.7939832935f, -0.3418836993f, 0, 0.3202971715f, 0.2834415347f, 0.9039195862f, 0, 0.8683227101f, -0.0003762656404f, -0.4959995258f, 0, 0.791120031f, -0.08511045745f, 0.6057105799f, 0, + -0.04011016052f, -0.4397248749f, 0.8972364289f, 0, 0.9145119872f, 0.3579346169f, -0.1885487608f, 0, -0.9612039066f, -0.2756484276f, 0.01024666929f, 0, 0.6510361721f, -0.2877799159f, -0.7023778346f, 0, -0.2041786351f, 0.7365237271f, 0.644859585f, 0, -0.7718263711f, 0.3790626912f, 0.5104855816f, 0, -0.3060082741f, -0.7692987727f, 0.5608371729f, 0, 0.454007341f, -0.5024843065f, 0.7357899537f, 0, + 0.4816795475f, 0.6021208291f, -0.6367380315f, 0, 0.6961980369f, -0.3222197429f, 0.641469197f, 0, -0.6532160499f, -0.6781148932f, 0.3368515753f, 0, 0.5089301236f, -0.6154662304f, -0.6018234363f, 0, -0.1635919754f, -0.9133604627f, -0.372840892f, 0, 0.52408019f, -0.8437664109f, 0.1157505864f, 0, 0.5902587356f, 0.4983817807f, -0.6349883666f, 0, 0.5863227872f, 0.494764745f, 0.6414307729f, 0, + 0.6779335087f, 0.2341345225f, 0.6968408593f, 0, 0.7177054546f, -0.6858979348f, 0.120178631f, 0, -0.5328819713f, -0.5205125012f, 0.6671608058f, 0, -0.8654874251f, -0.0700727088f, -0.4960053754f, 0, -0.2861810166f, 0.7952089234f, 0.5345495242f, 0, -0.04849529634f, 0.9810836427f, -0.1874115585f, 0, -0.6358521667f, 0.6058348682f, 0.4781800233f, 0, 0.6254794696f, -0.2861619734f, 0.7258696564f, 0, + -0.2585259868f, 0.5061949264f, -0.8227581726f, 0, 0.02136306781f, 0.5064016808f, -0.8620330371f, 0, 0.200111773f, 0.8599263484f, 0.4695550591f, 0, 0.4743561372f, 0.6014985084f, -0.6427953014f, 0, 0.6622993731f, -0.5202474575f, -0.5391679918f, 0, 0.08084972818f, -0.6532720452f, 0.7527940996f, 0, -0.6893687501f, 0.0592860349f, 0.7219805347f, 0, -0.1121887082f, -0.9673185067f, 0.2273952515f, 0, + 0.7344116094f, 0.5979668656f, -0.3210532909f, 0, 0.5789393465f, -0.2488849713f, 0.7764570201f, 0, 0.6988182827f, 0.3557169806f, -0.6205791146f, 0, -0.8636845529f, -0.2748771249f, -0.4224826141f, 0, -0.4247027957f, -0.4640880967f, 0.777335046f, 0, 0.5257722489f, -0.8427017621f, 0.1158329937f, 0, 0.9343830603f, 0.316302472f, -0.1639543925f, 0, -0.1016836419f, -0.8057303073f, -0.5834887393f, 0, + -0.6529238969f, 0.50602126f, -0.5635892736f, 0, -0.2465286165f, -0.9668205684f, -0.06694497494f, 0, -0.9776897119f, -0.2099250524f, -0.007368825344f, 0, 0.7736893337f, 0.5734244712f, 0.2694238123f, 0, -0.6095087895f, 0.4995678998f, 0.6155736747f, 0, 0.5794535482f, 0.7434546771f, 0.3339292269f, 0, -0.8226211154f, 0.08142581855f, 0.5627293636f, 0, -0.510385483f, 0.4703667658f, 0.7199039967f, 0, + -0.5764971849f, -0.07231656274f, -0.8138926898f, 0, 0.7250628871f, 0.3949971505f, -0.5641463116f, 0, -0.1525424005f, 0.4860840828f, -0.8604958341f, 0, -0.5550976208f, -0.4957820792f, 0.667882296f, 0, -0.1883614327f, 0.9145869398f, 0.357841725f, 0, 0.7625556724f, -0.5414408243f, -0.3540489801f, 0, -0.5870231946f, -0.3226498013f, -0.7424963803f, 0, 0.3051124198f, 0.2262544068f, -0.9250488391f, 0, + 0.6379576059f, 0.577242424f, -0.5097070502f, 0, -0.5966775796f, 0.1454852398f, -0.7891830656f, 0, -0.658330573f, 0.6555487542f, -0.3699414651f, 0, 0.7434892426f, 0.2351084581f, 0.6260573129f, 0, 0.5562114096f, 0.8264360377f, -0.0873632843f, 0, -0.3028940016f, -0.8251527185f, 0.4768419182f, 0, 0.1129343818f, -0.985888439f, -0.1235710781f, 0, 0.5937652891f, -0.5896813806f, 0.5474656618f, 0, + 0.6757964092f, -0.5835758614f, -0.4502648413f, 0, 0.7242302609f, -0.1152719764f, 0.6798550586f, 0, -0.9511914166f, 0.0753623979f, -0.2992580792f, 0, 0.2539470961f, -0.1886339355f, 0.9486454084f, 0, 0.571433621f, -0.1679450851f, -0.8032795685f, 0, -0.06778234979f, 0.3978269256f, 0.9149531629f, 0, 0.6074972649f, 0.733060024f, -0.3058922593f, 0, -0.5435478392f, 0.1675822484f, 0.8224791405f, 0, + -0.5876678086f, -0.3380045064f, -0.7351186982f, 0, -0.7967562402f, 0.04097822706f, -0.6029098428f, 0, -0.1996350917f, 0.8706294745f, 0.4496111079f, 0, -0.02787660336f, -0.9106232682f, -0.4122962022f, 0, -0.7797625996f, -0.6257634692f, 0.01975775581f, 0, -0.5211232846f, 0.7401644346f, -0.4249554471f, 0, 0.8575424857f, 0.4053272873f, -0.3167501783f, 0, 0.1045223322f, 0.8390195772f, -0.5339674439f, 0, + 0.3501822831f, 0.9242524096f, -0.1520850155f, 0, 0.1987849858f, 0.07647613266f, 0.9770547224f, 0, 0.7845996363f, 0.6066256811f, -0.1280964233f, 0, 0.09006737436f, -0.9750989929f, -0.2026569073f, 0, -0.8274343547f, -0.542299559f, 0.1458203587f, 0, -0.3485797732f, -0.415802277f, 0.840000362f, 0, -0.2471778936f, -0.7304819962f, -0.6366310879f, 0, -0.3700154943f, 0.8577948156f, 0.3567584454f, 0, + 0.5913394901f, -0.548311967f, -0.5913303597f, 0, 0.1204873514f, -0.7626472379f, -0.6354935001f, 0, 0.616959265f, 0.03079647928f, 0.7863922953f, 0, 0.1258156836f, -0.6640829889f, -0.7369967419f, 0, -0.6477565124f, -0.1740147258f, -0.7417077429f, 0, 0.6217889313f, -0.7804430448f, -0.06547655076f, 0, 0.6589943422f, -0.6096987708f, 0.4404473475f, 0, -0.2689837504f, -0.6732403169f, -0.6887635427f, 0, + -0.3849775103f, 0.5676542638f, 0.7277093879f, 0, 0.5754444408f, 0.8110471154f, -0.1051963504f, 0, 0.9141593684f, 0.3832947817f, 0.131900567f, 0, -0.107925319f, 0.9245493968f, 0.3654593525f, 0, 0.377977089f, 0.3043148782f, 0.8743716458f, 0, -0.2142885215f, -0.8259286236f, 0.5214617324f, 0, 0.5802544474f, 0.4148098596f, -0.7008834116f, 0, -0.1982660881f, 0.8567161266f, -0.4761596756f, 0, + -0.03381553704f, 0.3773180787f, -0.9254661404f, 0, -0.6867922841f, -0.6656597827f, 0.2919133642f, 0, 0.7731742607f, -0.2875793547f, -0.5652430251f, 0, -0.09655941928f, 0.9193708367f, -0.3813575004f, 0, 0.2715702457f, -0.9577909544f, -0.09426605581f, 0, 0.2451015704f, -0.6917998565f, -0.6792188003f, 0, 0.977700782f, -0.1753855374f, 0.1155036542f, 0, -0.5224739938f, 0.8521606816f, 0.02903615945f, 0, + -0.7734880599f, -0.5261292347f, 0.3534179531f, 0, -0.7134492443f, -0.269547243f, 0.6467878011f, 0, 0.1644037271f, 0.5105846203f, -0.8439637196f, 0, 0.6494635788f, 0.05585611296f, 0.7583384168f, 0, -0.4711970882f, 0.5017280509f, -0.7254255765f, 0, -0.6335764307f, -0.2381686273f, -0.7361091029f, 0, -0.9021533097f, -0.270947803f, -0.3357181763f, 0, -0.3793711033f, 0.872258117f, 0.3086152025f, 0, + -0.6855598966f, -0.3250143309f, 0.6514394162f, 0, 0.2900942212f, -0.7799057743f, -0.5546100667f, 0, -0.2098319339f, 0.85037073f, 0.4825351604f, 0, -0.4592603758f, 0.6598504336f, -0.5947077538f, 0, 0.8715945488f, 0.09616365406f, -0.4807031248f, 0, -0.6776666319f, 0.7118504878f, -0.1844907016f, 0, 0.7044377633f, 0.312427597f, 0.637304036f, 0, -0.7052318886f, -0.2401093292f, -0.6670798253f, 0, + 0.081921007f, -0.7207336136f, -0.6883545647f, 0, -0.6993680906f, -0.5875763221f, -0.4069869034f, 0, -0.1281454481f, 0.6419895885f, 0.7559286424f, 0, -0.6337388239f, -0.6785471501f, -0.3714146849f, 0, 0.5565051903f, -0.2168887573f, -0.8020356851f, 0, -0.5791554484f, 0.7244372011f, -0.3738578718f, 0, 0.1175779076f, -0.7096451073f, 0.6946792478f, 0, -0.6134619607f, 0.1323631078f, 0.7785527795f, 0, + 0.6984635305f, -0.02980516237f, -0.715024719f, 0, 0.8318082963f, -0.3930171956f, 0.3919597455f, 0, 0.1469576422f, 0.05541651717f, -0.9875892167f, 0, 0.708868575f, -0.2690503865f, 0.6520101478f, 0, 0.2726053183f, 0.67369766f, -0.68688995f, 0, -0.6591295371f, 0.3035458599f, -0.6880466294f, 0, 0.4815131379f, -0.7528270071f, 0.4487723203f, 0, 0.9430009463f, 0.1675647412f, -0.2875261255f, 0, + 0.434802957f, 0.7695304522f, -0.4677277752f, 0, 0.3931996188f, 0.594473625f, 0.7014236729f, 0, 0.7254336655f, -0.603925654f, 0.3301814672f, 0, 0.7590235227f, -0.6506083235f, 0.02433313207f, 0, -0.8552768592f, -0.3430042733f, 0.3883935666f, 0, -0.6139746835f, 0.6981725247f, 0.3682257648f, 0, -0.7465905486f, -0.5752009504f, 0.3342849376f, 0, 0.5730065677f, 0.810555537f, -0.1210916791f, 0, + -0.9225877367f, -0.3475211012f, -0.167514036f, 0, -0.7105816789f, -0.4719692027f, -0.5218416899f, 0, -0.08564609717f, 0.3583001386f, 0.929669703f, 0, -0.8279697606f, -0.2043157126f, 0.5222271202f, 0, 0.427944023f, 0.278165994f, 0.8599346446f, 0, 0.5399079671f, -0.7857120652f, -0.3019204161f, 0, 0.5678404253f, -0.5495413974f, -0.6128307303f, 0, -0.9896071041f, 0.1365639107f, -0.04503418428f, 0, + -0.6154342638f, -0.6440875597f, 0.4543037336f, 0, 0.1074204368f, -0.7946340692f, 0.5975094525f, 0, -0.3595449969f, -0.8885529948f, 0.28495784f, 0, -0.2180405296f, 0.1529888965f, 0.9638738118f, 0, -0.7277432317f, -0.6164050508f, -0.3007234646f, 0, 0.7249729114f, -0.00669719484f, 0.6887448187f, 0, -0.5553659455f, -0.5336586252f, 0.6377908264f, 0, 0.5137558015f, 0.7976208196f, -0.3160000073f, 0, + -0.3794024848f, 0.9245608561f, -0.03522751494f, 0, 0.8229248658f, 0.2745365933f, -0.4974176556f, 0, -0.5404114394f, 0.6091141441f, 0.5804613989f, 0, 0.8036581901f, -0.2703029469f, 0.5301601931f, 0, 0.6044318879f, 0.6832968393f, 0.4095943388f, 0, 0.06389988817f, 0.9658208605f, -0.2512108074f, 0, 0.1087113286f, 0.7402471173f, -0.6634877936f, 0, -0.713427712f, -0.6926784018f, 0.1059128479f, 0, + 0.6458897819f, -0.5724548511f, -0.5050958653f, 0, -0.6553931414f, 0.7381471625f, 0.159995615f, 0, 0.3910961323f, 0.9188871375f, -0.05186755998f, 0, -0.4879022471f, -0.5904376907f, 0.6429111375f, 0, 0.6014790094f, 0.7707441366f, -0.2101820095f, 0, -0.5677173047f, 0.7511360995f, 0.3368851762f, 0, 0.7858573506f, 0.226674665f, 0.5753666838f, 0, -0.4520345543f, -0.604222686f, -0.6561857263f, 0, + 0.002272116345f, 0.4132844051f, -0.9105991643f, 0, -0.5815751419f, -0.5162925989f, 0.6286591339f, 0, -0.03703704785f, 0.8273785755f, 0.5604221175f, 0, -0.5119692504f, 0.7953543429f, -0.3244980058f, 0, -0.2682417366f, -0.9572290247f, -0.1084387619f, 0, -0.2322482736f, -0.9679131102f, -0.09594243324f, 0, 0.3554328906f, -0.8881505545f, 0.2913006227f, 0, 0.7346520519f, -0.4371373164f, 0.5188422971f, 0, + 0.9985120116f, 0.04659011161f, -0.02833944577f, 0, -0.3727687496f, -0.9082481361f, 0.1900757285f, 0, 0.91737377f, -0.3483642108f, 0.1925298489f, 0, 0.2714911074f, 0.4147529736f, -0.8684886582f, 0, 0.5131763485f, -0.7116334161f, 0.4798207128f, 0, -0.8737353606f, 0.18886992f, -0.4482350644f, 0, 0.8460043821f, -0.3725217914f, 0.3814499973f, 0, 0.8978727456f, -0.1780209141f, -0.4026575304f, 0, + 0.2178065647f, -0.9698322841f, -0.1094789531f, 0, -0.1518031304f, -0.7788918132f, -0.6085091231f, 0, -0.2600384876f, -0.4755398075f, -0.8403819825f, 0, 0.572313509f, -0.7474340931f, -0.3373418503f, 0, -0.7174141009f, 0.1699017182f, -0.6756111411f, 0, -0.684180784f, 0.02145707593f, -0.7289967412f, 0, -0.2007447902f, 0.06555605789f, -0.9774476623f, 0, -0.1148803697f, -0.8044887315f, 0.5827524187f, 0, + -0.7870349638f, 0.03447489231f, 0.6159443543f, 0, -0.2015596421f, 0.6859872284f, 0.6991389226f, 0, -0.08581082512f, -0.10920836f, -0.9903080513f, 0, 0.5532693395f, 0.7325250401f, -0.396610771f, 0, -0.1842489331f, -0.9777375055f, -0.1004076743f, 0, 0.0775473789f, -0.9111505856f, 0.4047110257f, 0, 0.1399838409f, 0.7601631212f, -0.6344734459f, 0, 0.4484419361f, -0.845289248f, 0.2904925424f, 0 + }; + + + private static float FastMin(float a, float b) { return a < b ? a : b; } + + private static float FastMax(float a, float b) { return a > b ? a : b; } + + private static float FastAbs(float f) { return f < 0 ? -f : f; } + + private static float FastSqrt(float f) { return (float)Math.sqrt(f); } + + private static int FastFloor(/*FNLfloat*/ double f) { return f >= 0 ? (int)f : (int)f - 1; } + + private static int FastRound(/*FNLfloat*/ double f) { return f >= 0 ? (int)(f + 0.5f) : (int)(f - 0.5f); } + + private static float Lerp(float a, float b, float t) { return a + t * (b - a); } + + private static float InterpHermite(float t) { return t * t * (3 - 2 * t); } + + private static float InterpQuintic(float t) { return t * t * t * (t * (t * 6 - 15) + 10); } + + private static float CubicLerp(float a, float b, float c, float d, float t) + { + float p = (d - c) - (a - b); + return t * t * t * p + t * t * ((a - b) - p) + t * (c - a) + b; + } + + private static float PingPong(float t) + { + t -= (int)(t * 0.5f) * 2; + return t < 1 ? t : 2 - t; + } + + private void CalculateFractalBounding() + { + float gain = FastAbs(mGain); + float amp = gain; + float ampFractal = 1.0f; + for (int i = 1; i < mOctaves; i++) + { + ampFractal += amp; + amp *= gain; + } + mFractalBounding = 1 / ampFractal; + } + + // Hashing + private static final int PrimeX = 501125321; + private static final int PrimeY = 1136930381; + private static final int PrimeZ = 1720413743; + + private static int Hash(int seed, int xPrimed, int yPrimed) + { + int hash = seed ^ xPrimed ^ yPrimed; + + hash *= 0x27d4eb2d; + return hash; + } + + private static int Hash(int seed, int xPrimed, int yPrimed, int zPrimed) + { + int hash = seed ^ xPrimed ^ yPrimed ^ zPrimed; + + hash *= 0x27d4eb2d; + return hash; + } + + private static float ValCoord(int seed, int xPrimed, int yPrimed) + { + int hash = Hash(seed, xPrimed, yPrimed); + + hash *= hash; + hash ^= hash << 19; + return hash * (1 / 2147483648.0f); + } + + private static float ValCoord(int seed, int xPrimed, int yPrimed, int zPrimed) + { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + + hash *= hash; + hash ^= hash << 19; + return hash * (1 / 2147483648.0f); + } + + private static float GradCoord(int seed, int xPrimed, int yPrimed, float xd, float yd) + { + int hash = Hash(seed, xPrimed, yPrimed); + hash ^= hash >> 15; + hash &= 127 << 1; + + float xg = Gradients2D[hash]; + float yg = Gradients2D[hash | 1]; + + return xd * xg + yd * yg; + } + + private static float GradCoord(int seed, int xPrimed, int yPrimed, int zPrimed, float xd, float yd, float zd) + { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + hash ^= hash >> 15; + hash &= 63 << 2; + + float xg = Gradients3D[hash]; + float yg = Gradients3D[hash | 1]; + float zg = Gradients3D[hash | 2]; + + return xd * xg + yd * yg + zd * zg; + } + + + // Generic noise gen + + private float GenNoiseSingle(int seed, /*FNLfloat*/ double x, /*FNLfloat*/ double y) + { + switch (mNoiseType) + { + case OpenSimplex2: + return SingleSimplex(seed, x, y); + case OpenSimplex2S: + return SingleOpenSimplex2S(seed, x, y); + case Cellular: + return SingleCellular(seed, x, y); + case Perlin: + return SinglePerlin(seed, x, y); + case ValueCubic: + return SingleValueCubic(seed, x, y); + case Value: + return SingleValue(seed, x, y); + default: + return 0; + } + } + + private float GenNoiseSingle(int seed, /*FNLfloat*/ double x, /*FNLfloat*/ double y, /*FNLfloat*/ double z) + { + switch (mNoiseType) + { + case OpenSimplex2: + return SingleOpenSimplex2(seed, x, y, z); + case OpenSimplex2S: + return SingleOpenSimplex2S(seed, x, y, z); + case Cellular: + return SingleCellular(seed, x, y, z); + case Perlin: + return SinglePerlin(seed, x, y, z); + case ValueCubic: + return SingleValueCubic(seed, x, y, z); + case Value: + return SingleValue(seed, x, y, z); + default: + return 0; + } + } + + + // Noise Coordinate Transforms (frequency, and possible skew or rotation) + + private void UpdateTransformType3D() + { + switch (mRotationType3D) + { + case ImproveXYPlanes: + mTransformType3D = TransformType3D.ImproveXYPlanes; + break; + case ImproveXZPlanes: + mTransformType3D = TransformType3D.ImproveXZPlanes; + break; + default: + switch (mNoiseType) + { + case OpenSimplex2: + case OpenSimplex2S: + mTransformType3D = TransformType3D.DefaultOpenSimplex2; + break; + default: + mTransformType3D = TransformType3D.None; + break; + } + break; + } + } + + private void UpdateWarpTransformType3D() + { + switch (mRotationType3D) + { + case ImproveXYPlanes: + mWarpTransformType3D = TransformType3D.ImproveXYPlanes; + break; + case ImproveXZPlanes: + mWarpTransformType3D = TransformType3D.ImproveXZPlanes; + break; + default: + switch (mDomainWarpType) + { + case OpenSimplex2: + case OpenSimplex2Reduced: + mWarpTransformType3D = TransformType3D.DefaultOpenSimplex2; + break; + default: + mWarpTransformType3D = TransformType3D.None; + break; + } + break; + } + } + + + // Fractal FBm + + private float GenFractalFBm(/*FNLfloat*/ double x, /*FNLfloat*/ double y) + { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) + { + float noise = GenNoiseSingle(seed++, x, y); + sum += noise * amp; + amp *= Lerp(1.0f, FastMin(noise + 1, 2) * 0.5f, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + private float GenFractalFBm(/*FNLfloat*/ double x, /*FNLfloat*/ double y, /*FNLfloat*/ double z) + { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) + { + float noise = GenNoiseSingle(seed++, x, y, z); + sum += noise * amp; + amp *= Lerp(1.0f, (noise + 1) * 0.5f, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + z *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + + // Fractal Ridged + + private float GenFractalRidged(/*FNLfloat*/ double x, /*FNLfloat*/ double y) + { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) + { + float noise = FastAbs(GenNoiseSingle(seed++, x, y)); + sum += (noise * -2 + 1) * amp; + amp *= Lerp(1.0f, 1 - noise, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + private float GenFractalRidged(/*FNLfloat*/ double x, /*FNLfloat*/ double y, /*FNLfloat*/ double z) + { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) + { + float noise = FastAbs(GenNoiseSingle(seed++, x, y, z)); + sum += (noise * -2 + 1) * amp; + amp *= Lerp(1.0f, 1 - noise, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + z *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + + // Fractal PingPong + + private float GenFractalPingPong(/*FNLfloat*/ double x, /*FNLfloat*/ double y) + { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) + { + float noise = PingPong((GenNoiseSingle(seed++, x, y) + 1) * mPingPongStrength); + sum += (noise - 0.5f) * 2 * amp; + amp *= Lerp(1.0f, noise, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + private float GenFractalPingPong(/*FNLfloat*/ double x, /*FNLfloat*/ double y, /*FNLfloat*/ double z) + { + int seed = mSeed; + float sum = 0; + float amp = mFractalBounding; + + for (int i = 0; i < mOctaves; i++) + { + float noise = PingPong((GenNoiseSingle(seed++, x, y, z) + 1) * mPingPongStrength); + sum += (noise - 0.5f) * 2 * amp; + amp *= Lerp(1.0f, noise, mWeightedStrength); + + x *= mLacunarity; + y *= mLacunarity; + z *= mLacunarity; + amp *= mGain; + } + + return sum; + } + + + // Simplex/OpenSimplex2 Noise + + private float SingleSimplex(int seed, /*FNLfloat*/ double x, /*FNLfloat*/ double y) + { + // 2D OpenSimplex2 case uses the same algorithm as ordinary Simplex. + + final float SQRT3 = 1.7320508075688772935274463415059f; + final float G2 = (3 - SQRT3) / 6; + + /* + * --- Skew moved to switch statements before fractal evaluation --- + * final FNLfloat F2 = 0.5f * (SQRT3 - 1); + * FNLfloat s = (x + y) * F2; + * x += s; y += s; + */ + + int i = FastFloor(x); + int j = FastFloor(y); + float xi = (float)(x - i); + float yi = (float)(y - j); + + float t = (xi + yi) * G2; + float x0 = (float)(xi - t); + float y0 = (float)(yi - t); + + i *= PrimeX; + j *= PrimeY; + + float n0, n1, n2; + + float a = 0.5f - x0 * x0 - y0 * y0; + if (a <= 0) n0 = 0; + else + { + n0 = (a * a) * (a * a) * GradCoord(seed, i, j, x0, y0); + } + + float c = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a); + if (c <= 0) n2 = 0; + else + { + float x2 = x0 + (2 * (float)G2 - 1); + float y2 = y0 + (2 * (float)G2 - 1); + n2 = (c * c) * (c * c) * GradCoord(seed, i + PrimeX, j + PrimeY, x2, y2); + } + + if (y0 > x0) + { + float x1 = x0 + (float)G2; + float y1 = y0 + ((float)G2 - 1); + float b = 0.5f - x1 * x1 - y1 * y1; + if (b <= 0) n1 = 0; + else + { + n1 = (b * b) * (b * b) * GradCoord(seed, i, j + PrimeY, x1, y1); + } + } + else + { + float x1 = x0 + ((float)G2 - 1); + float y1 = y0 + (float)G2; + float b = 0.5f - x1 * x1 - y1 * y1; + if (b <= 0) n1 = 0; + else + { + n1 = (b * b) * (b * b) * GradCoord(seed, i + PrimeX, j, x1, y1); + } + } + + return (n0 + n1 + n2) * 99.83685446303647f; + } + + private float SingleOpenSimplex2(int seed, /*FNLfloat*/ double x, /*FNLfloat*/ double y, /*FNLfloat*/ double z) + { + // 3D OpenSimplex2 case uses two offset rotated cube grids. + + /* + * --- Rotation moved to switch statements before fractal evaluation --- + * final FNLfloat R3 = (FNLfloat)(2.0 / 3.0); + * FNLfloat r = (x + y + z) * R3; // Rotation, not skew + * x = r - x; y = r - y; z = r - z; + */ + + int i = FastRound(x); + int j = FastRound(y); + int k = FastRound(z); + float x0 = (float)(x - i); + float y0 = (float)(y - j); + float z0 = (float)(z - k); + + int xNSign = (int)(-1.0f - x0) | 1; + int yNSign = (int)(-1.0f - y0) | 1; + int zNSign = (int)(-1.0f - z0) | 1; + + float ax0 = xNSign * -x0; + float ay0 = yNSign * -y0; + float az0 = zNSign * -z0; + + i *= PrimeX; + j *= PrimeY; + k *= PrimeZ; + + float value = 0; + float a = (0.6f - x0 * x0) - (y0 * y0 + z0 * z0); + + for (int l = 0; ; l++) + { + if (a > 0) + { + value += (a * a) * (a * a) * GradCoord(seed, i, j, k, x0, y0, z0); + } + + if (ax0 >= ay0 && ax0 >= az0) + { + float b = a + ax0 + ax0; + if (b > 1) + { + b -= 1; + value += (b * b) * (b * b) * GradCoord(seed, i - xNSign * PrimeX, j, k, x0 + xNSign, y0, z0); + } + } + else if (ay0 > ax0 && ay0 >= az0) + { + float b = a + ay0 + ay0; + if (b > 1) + { + b -= 1; + value += (b * b) * (b * b) * GradCoord(seed, i, j - yNSign * PrimeY, k, x0, y0 + yNSign, z0); + } + } + else + { + float b = a + az0 + az0; + if (b > 1) + { + b -= 1; + value += (b * b) * (b * b) * GradCoord(seed, i, j, k - zNSign * PrimeZ, x0, y0, z0 + zNSign); + } + } + + if (l == 1) break; + + ax0 = 0.5f - ax0; + ay0 = 0.5f - ay0; + az0 = 0.5f - az0; + + x0 = xNSign * ax0; + y0 = yNSign * ay0; + z0 = zNSign * az0; + + a += (0.75f - ax0) - (ay0 + az0); + + i += (xNSign >> 1) & PrimeX; + j += (yNSign >> 1) & PrimeY; + k += (zNSign >> 1) & PrimeZ; + + xNSign = -xNSign; + yNSign = -yNSign; + zNSign = -zNSign; + + seed = ~seed; + } + + return value * 32.69428253173828125f; + } + + + // OpenSimplex2S Noise + + private float SingleOpenSimplex2S(int seed, /*FNLfloat*/ double x, /*FNLfloat*/ double y) + { + // 2D OpenSimplex2S case is a modified 2D simplex noise. + + final /*FNLfloat*/ double SQRT3 = (/*FNLfloat*/ double)1.7320508075688772935274463415059; + final /*FNLfloat*/ double G2 = (3 - SQRT3) / 6; + + /* + * --- Skew moved to TransformNoiseCoordinate method --- + * final FNLfloat F2 = 0.5f * (SQRT3 - 1); + * FNLfloat s = (x + y) * F2; + * x += s; y += s; + */ + + int i = FastFloor(x); + int j = FastFloor(y); + float xi = (float)(x - i); + float yi = (float)(y - j); + + i *= PrimeX; + j *= PrimeY; + int i1 = i + PrimeX; + int j1 = j + PrimeY; + + float t = (xi + yi) * (float)G2; + float x0 = xi - t; + float y0 = yi - t; + + float a0 = (2.0f / 3.0f) - x0 * x0 - y0 * y0; + float value = (a0 * a0) * (a0 * a0) * GradCoord(seed, i, j, x0, y0); + + float a1 = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a0); + float x1 = x0 - (float)(1 - 2 * G2); + float y1 = y0 - (float)(1 - 2 * G2); + value += (a1 * a1) * (a1 * a1) * GradCoord(seed, i1, j1, x1, y1); + + // Nested conditionals were faster than compact bit logic/arithmetic. + float xmyi = xi - yi; + if (t > G2) + { + if (xi + xmyi > 1) + { + float x2 = x0 + (float)(3 * G2 - 2); + float y2 = y0 + (float)(3 * G2 - 1); + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) + { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i + (PrimeX << 1), j + PrimeY, x2, y2); + } + } + else + { + float x2 = x0 + (float)G2; + float y2 = y0 + (float)(G2 - 1); + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) + { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j + PrimeY, x2, y2); + } + } + + if (yi - xmyi > 1) + { + float x3 = x0 + (float)(3 * G2 - 1); + float y3 = y0 + (float)(3 * G2 - 2); + float a3 = (2.0f / 3.0f) - x3 * x3 - y3 * y3; + if (a3 > 0) + { + value += (a3 * a3) * (a3 * a3) * GradCoord(seed, i + PrimeX, j + (PrimeY << 1), x3, y3); + } + } + else + { + float x3 = x0 + (float)(G2 - 1); + float y3 = y0 + (float)G2; + float a3 = (2.0f / 3.0f) - x3 * x3 - y3 * y3; + if (a3 > 0) + { + value += (a3 * a3) * (a3 * a3) * GradCoord(seed, i + PrimeX, j, x3, y3); + } + } + } + else + { + if (xi + xmyi < 0) + { + float x2 = x0 + (float)(1 - G2); + float y2 = y0 - (float)G2; + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) + { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i - PrimeX, j, x2, y2); + } + } + else + { + float x2 = x0 + (float)(G2 - 1); + float y2 = y0 + (float)G2; + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) + { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i + PrimeX, j, x2, y2); + } + } + + if (yi < xmyi) + { + float x2 = x0 - (float)G2; + float y2 = y0 - (float)(G2 - 1); + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) + { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j - PrimeY, x2, y2); + } + } + else + { + float x2 = x0 + (float)G2; + float y2 = y0 + (float)(G2 - 1); + float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; + if (a2 > 0) + { + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j + PrimeY, x2, y2); + } + } + } + + return value * 18.24196194486065f; + } + + private float SingleOpenSimplex2S(int seed, /*FNLfloat*/ double x, /*FNLfloat*/ double y, /*FNLfloat*/ double z) + { + // 3D OpenSimplex2S case uses two offset rotated cube grids. + + /* + * --- Rotation moved to TransformNoiseCoordinate method --- + * final FNLfloat R3 = (FNLfloat)(2.0 / 3.0); + * FNLfloat r = (x + y + z) * R3; // Rotation, not skew + * x = r - x; y = r - y; z = r - z; + */ + + int i = FastFloor(x); + int j = FastFloor(y); + int k = FastFloor(z); + float xi = (float)(x - i); + float yi = (float)(y - j); + float zi = (float)(z - k); + + i *= PrimeX; + j *= PrimeY; + k *= PrimeZ; + int seed2 = seed + 1293373; + + int xNMask = (int)(-0.5f - xi); + int yNMask = (int)(-0.5f - yi); + int zNMask = (int)(-0.5f - zi); + + float x0 = xi + xNMask; + float y0 = yi + yNMask; + float z0 = zi + zNMask; + float a0 = 0.75f - x0 * x0 - y0 * y0 - z0 * z0; + float value = (a0 * a0) * (a0 * a0) * GradCoord(seed, + i + (xNMask & PrimeX), j + (yNMask & PrimeY), k + (zNMask & PrimeZ), x0, y0, z0); + + float x1 = xi - 0.5f; + float y1 = yi - 0.5f; + float z1 = zi - 0.5f; + float a1 = 0.75f - x1 * x1 - y1 * y1 - z1 * z1; + value += (a1 * a1) * (a1 * a1) * GradCoord(seed2, + i + PrimeX, j + PrimeY, k + PrimeZ, x1, y1, z1); + + float xAFlipMask0 = ((xNMask | 1) << 1) * x1; + float yAFlipMask0 = ((yNMask | 1) << 1) * y1; + float zAFlipMask0 = ((zNMask | 1) << 1) * z1; + float xAFlipMask1 = (-2 - (xNMask << 2)) * x1 - 1.0f; + float yAFlipMask1 = (-2 - (yNMask << 2)) * y1 - 1.0f; + float zAFlipMask1 = (-2 - (zNMask << 2)) * z1 - 1.0f; + + boolean skip5 = false; + float a2 = xAFlipMask0 + a0; + if (a2 > 0) + { + float x2 = x0 - (xNMask | 1); + float y2 = y0; + float z2 = z0; + value += (a2 * a2) * (a2 * a2) * GradCoord(seed, + i + (~xNMask & PrimeX), j + (yNMask & PrimeY), k + (zNMask & PrimeZ), x2, y2, z2); + } + else + { + float a3 = yAFlipMask0 + zAFlipMask0 + a0; + if (a3 > 0) + { + float x3 = x0; + float y3 = y0 - (yNMask | 1); + float z3 = z0 - (zNMask | 1); + value += (a3 * a3) * (a3 * a3) * GradCoord(seed, + i + (xNMask & PrimeX), j + (~yNMask & PrimeY), k + (~zNMask & PrimeZ), x3, y3, z3); + } + + float a4 = xAFlipMask1 + a1; + if (a4 > 0) + { + float x4 = (xNMask | 1) + x1; + float y4 = y1; + float z4 = z1; + value += (a4 * a4) * (a4 * a4) * GradCoord(seed2, + i + (xNMask & (PrimeX * 2)), j + PrimeY, k + PrimeZ, x4, y4, z4); + skip5 = true; + } + } + + boolean skip9 = false; + float a6 = yAFlipMask0 + a0; + if (a6 > 0) + { + float x6 = x0; + float y6 = y0 - (yNMask | 1); + float z6 = z0; + value += (a6 * a6) * (a6 * a6) * GradCoord(seed, + i + (xNMask & PrimeX), j + (~yNMask & PrimeY), k + (zNMask & PrimeZ), x6, y6, z6); + } + else + { + float a7 = xAFlipMask0 + zAFlipMask0 + a0; + if (a7 > 0) + { + float x7 = x0 - (xNMask | 1); + float y7 = y0; + float z7 = z0 - (zNMask | 1); + value += (a7 * a7) * (a7 * a7) * GradCoord(seed, + i + (~xNMask & PrimeX), j + (yNMask & PrimeY), k + (~zNMask & PrimeZ), x7, y7, z7); + } + + float a8 = yAFlipMask1 + a1; + if (a8 > 0) + { + float x8 = x1; + float y8 = (yNMask | 1) + y1; + float z8 = z1; + value += (a8 * a8) * (a8 * a8) * GradCoord(seed2, + i + PrimeX, j + (yNMask & (PrimeY << 1)), k + PrimeZ, x8, y8, z8); + skip9 = true; + } + } + + boolean skipD = false; + float aA = zAFlipMask0 + a0; + if (aA > 0) + { + float xA = x0; + float yA = y0; + float zA = z0 - (zNMask | 1); + value += (aA * aA) * (aA * aA) * GradCoord(seed, + i + (xNMask & PrimeX), j + (yNMask & PrimeY), k + (~zNMask & PrimeZ), xA, yA, zA); + } + else + { + float aB = xAFlipMask0 + yAFlipMask0 + a0; + if (aB > 0) + { + float xB = x0 - (xNMask | 1); + float yB = y0 - (yNMask | 1); + float zB = z0; + value += (aB * aB) * (aB * aB) * GradCoord(seed, + i + (~xNMask & PrimeX), j + (~yNMask & PrimeY), k + (zNMask & PrimeZ), xB, yB, zB); + } + + float aC = zAFlipMask1 + a1; + if (aC > 0) + { + float xC = x1; + float yC = y1; + float zC = (zNMask | 1) + z1; + value += (aC * aC) * (aC * aC) * GradCoord(seed2, + i + PrimeX, j + PrimeY, k + (zNMask & (PrimeZ << 1)), xC, yC, zC); + skipD = true; + } + } + + if (!skip5) + { + float a5 = yAFlipMask1 + zAFlipMask1 + a1; + if (a5 > 0) + { + float x5 = x1; + float y5 = (yNMask | 1) + y1; + float z5 = (zNMask | 1) + z1; + value += (a5 * a5) * (a5 * a5) * GradCoord(seed2, + i + PrimeX, j + (yNMask & (PrimeY << 1)), k + (zNMask & (PrimeZ << 1)), x5, y5, z5); + } + } + + if (!skip9) + { + float a9 = xAFlipMask1 + zAFlipMask1 + a1; + if (a9 > 0) + { + float x9 = (xNMask | 1) + x1; + float y9 = y1; + float z9 = (zNMask | 1) + z1; + value += (a9 * a9) * (a9 * a9) * GradCoord(seed2, + i + (xNMask & (PrimeX * 2)), j + PrimeY, k + (zNMask & (PrimeZ << 1)), x9, y9, z9); + } + } + + if (!skipD) + { + float aD = xAFlipMask1 + yAFlipMask1 + a1; + if (aD > 0) + { + float xD = (xNMask | 1) + x1; + float yD = (yNMask | 1) + y1; + float zD = z1; + value += (aD * aD) * (aD * aD) * GradCoord(seed2, + i + (xNMask & (PrimeX << 1)), j + (yNMask & (PrimeY << 1)), k + PrimeZ, xD, yD, zD); + } + } + + return value * 9.046026385208288f; + } + + + // Cellular Noise + + private float SingleCellular(int seed, /*FNLfloat*/ double x, /*FNLfloat*/ double y) + { + int xr = FastRound(x); + int yr = FastRound(y); + + float distance0 = Float.MAX_VALUE; + float distance1 = Float.MAX_VALUE; + int closestHash = 0; + + float cellularJitter = 0.43701595f * mCellularJitterModifier; + + int xPrimed = (xr - 1) * PrimeX; + int yPrimedBase = (yr - 1) * PrimeY; + + switch (mCellularDistanceFunction) + { + default: + case Euclidean: + case EuclideanSq: + for (int xi = xr - 1; xi <= xr + 1; xi++) + { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) + { + int hash = Hash(seed, xPrimed, yPrimed); + int idx = hash & (255 << 1); + + float vecX = (float)(xi - x) + RandVecs2D[idx] * cellularJitter; + float vecY = (float)(yi - y) + RandVecs2D[idx | 1] * cellularJitter; + + float newDistance = vecX * vecX + vecY * vecY; + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) + { + distance0 = newDistance; + closestHash = hash; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + case Manhattan: + for (int xi = xr - 1; xi <= xr + 1; xi++) + { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) + { + int hash = Hash(seed, xPrimed, yPrimed); + int idx = hash & (255 << 1); + + float vecX = (float)(xi - x) + RandVecs2D[idx] * cellularJitter; + float vecY = (float)(yi - y) + RandVecs2D[idx | 1] * cellularJitter; + + float newDistance = FastAbs(vecX) + FastAbs(vecY); + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) + { + distance0 = newDistance; + closestHash = hash; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + case Hybrid: + for (int xi = xr - 1; xi <= xr + 1; xi++) + { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) + { + int hash = Hash(seed, xPrimed, yPrimed); + int idx = hash & (255 << 1); + + float vecX = (float)(xi - x) + RandVecs2D[idx] * cellularJitter; + float vecY = (float)(yi - y) + RandVecs2D[idx | 1] * cellularJitter; + + float newDistance = (FastAbs(vecX) + FastAbs(vecY)) + (vecX * vecX + vecY * vecY); + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) + { + distance0 = newDistance; + closestHash = hash; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + } + + if (mCellularDistanceFunction == CellularDistanceFunction.Euclidean && mCellularReturnType != CellularReturnType.CellValue) + { + distance0 = FastSqrt(distance0); + + if (mCellularReturnType != CellularReturnType.Distance) + { + distance1 = FastSqrt(distance1); + } + } + + switch (mCellularReturnType) + { + case CellValue: + return closestHash * (1 / 2147483648.0f); + case Distance: + return distance0 - 1; + case Distance2: + return distance1 - 1; + case Distance2Add: + return (distance1 + distance0) * 0.5f - 1; + case Distance2Sub: + return distance1 - distance0 - 1; + case Distance2Mul: + return distance1 * distance0 * 0.5f - 1; + case Distance2Div: + return distance0 / distance1 - 1; + default: + return 0; + } + } + + private float SingleCellular(int seed, /*FNLfloat*/ double x, /*FNLfloat*/ double y, /*FNLfloat*/ double z) + { + int xr = FastRound(x); + int yr = FastRound(y); + int zr = FastRound(z); + + float distance0 = Float.MAX_VALUE; + float distance1 = Float.MAX_VALUE; + int closestHash = 0; + + float cellularJitter = 0.39614353f * mCellularJitterModifier; + + int xPrimed = (xr - 1) * PrimeX; + int yPrimedBase = (yr - 1) * PrimeY; + int zPrimedBase = (zr - 1) * PrimeZ; + + switch (mCellularDistanceFunction) + { + case Euclidean: + case EuclideanSq: + for (int xi = xr - 1; xi <= xr + 1; xi++) + { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) + { + int zPrimed = zPrimedBase; + + for (int zi = zr - 1; zi <= zr + 1; zi++) + { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + int idx = hash & (255 << 2); + + float vecX = (float)(xi - x) + RandVecs3D[idx] * cellularJitter; + float vecY = (float)(yi - y) + RandVecs3D[idx | 1] * cellularJitter; + float vecZ = (float)(zi - z) + RandVecs3D[idx | 2] * cellularJitter; + + float newDistance = vecX * vecX + vecY * vecY + vecZ * vecZ; + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) + { + distance0 = newDistance; + closestHash = hash; + } + zPrimed += PrimeZ; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + case Manhattan: + for (int xi = xr - 1; xi <= xr + 1; xi++) + { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) + { + int zPrimed = zPrimedBase; + + for (int zi = zr - 1; zi <= zr + 1; zi++) + { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + int idx = hash & (255 << 2); + + float vecX = (float)(xi - x) + RandVecs3D[idx] * cellularJitter; + float vecY = (float)(yi - y) + RandVecs3D[idx | 1] * cellularJitter; + float vecZ = (float)(zi - z) + RandVecs3D[idx | 2] * cellularJitter; + + float newDistance = FastAbs(vecX) + FastAbs(vecY) + FastAbs(vecZ); + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) + { + distance0 = newDistance; + closestHash = hash; + } + zPrimed += PrimeZ; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + case Hybrid: + for (int xi = xr - 1; xi <= xr + 1; xi++) + { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) + { + int zPrimed = zPrimedBase; + + for (int zi = zr - 1; zi <= zr + 1; zi++) + { + int hash = Hash(seed, xPrimed, yPrimed, zPrimed); + int idx = hash & (255 << 2); + + float vecX = (float)(xi - x) + RandVecs3D[idx] * cellularJitter; + float vecY = (float)(yi - y) + RandVecs3D[idx | 1] * cellularJitter; + float vecZ = (float)(zi - z) + RandVecs3D[idx | 2] * cellularJitter; + + float newDistance = (FastAbs(vecX) + FastAbs(vecY) + FastAbs(vecZ)) + (vecX * vecX + vecY * vecY + vecZ * vecZ); + + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) + { + distance0 = newDistance; + closestHash = hash; + } + zPrimed += PrimeZ; + } + yPrimed += PrimeY; + } + xPrimed += PrimeX; + } + break; + default: + break; + } + + if (mCellularDistanceFunction == CellularDistanceFunction.Euclidean && mCellularReturnType != CellularReturnType.CellValue) + { + distance0 = FastSqrt(distance0); + + if (mCellularReturnType != CellularReturnType.Distance) + { + distance1 = FastSqrt(distance1); + } + } + + switch (mCellularReturnType) + { + case CellValue: + return closestHash * (1 / 2147483648.0f); + case Distance: + return distance0 - 1; + case Distance2: + return distance1 - 1; + case Distance2Add: + return (distance1 + distance0) * 0.5f - 1; + case Distance2Sub: + return distance1 - distance0 - 1; + case Distance2Mul: + return distance1 * distance0 * 0.5f - 1; + case Distance2Div: + return distance0 / distance1 - 1; + default: + return 0; + } + } + + + // Perlin Noise + + private float SinglePerlin(int seed, /*FNLfloat*/ double x, /*FNLfloat*/ double y) + { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + + float xd0 = (float)(x - x0); + float yd0 = (float)(y - y0); + float xd1 = xd0 - 1; + float yd1 = yd0 - 1; + + float xs = InterpQuintic(xd0); + float ys = InterpQuintic(yd0); + + x0 *= PrimeX; + y0 *= PrimeY; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + + float xf0 = Lerp(GradCoord(seed, x0, y0, xd0, yd0), GradCoord(seed, x1, y0, xd1, yd0), xs); + float xf1 = Lerp(GradCoord(seed, x0, y1, xd0, yd1), GradCoord(seed, x1, y1, xd1, yd1), xs); + + return Lerp(xf0, xf1, ys) * 1.4247691104677813f; + } + + private float SinglePerlin(int seed, /*FNLfloat*/ double x, /*FNLfloat*/ double y, /*FNLfloat*/ double z) + { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + int z0 = FastFloor(z); + + float xd0 = (float)(x - x0); + float yd0 = (float)(y - y0); + float zd0 = (float)(z - z0); + float xd1 = xd0 - 1; + float yd1 = yd0 - 1; + float zd1 = zd0 - 1; + + float xs = InterpQuintic(xd0); + float ys = InterpQuintic(yd0); + float zs = InterpQuintic(zd0); + + x0 *= PrimeX; + y0 *= PrimeY; + z0 *= PrimeZ; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + int z1 = z0 + PrimeZ; + + float xf00 = Lerp(GradCoord(seed, x0, y0, z0, xd0, yd0, zd0), GradCoord(seed, x1, y0, z0, xd1, yd0, zd0), xs); + float xf10 = Lerp(GradCoord(seed, x0, y1, z0, xd0, yd1, zd0), GradCoord(seed, x1, y1, z0, xd1, yd1, zd0), xs); + float xf01 = Lerp(GradCoord(seed, x0, y0, z1, xd0, yd0, zd1), GradCoord(seed, x1, y0, z1, xd1, yd0, zd1), xs); + float xf11 = Lerp(GradCoord(seed, x0, y1, z1, xd0, yd1, zd1), GradCoord(seed, x1, y1, z1, xd1, yd1, zd1), xs); + + float yf0 = Lerp(xf00, xf10, ys); + float yf1 = Lerp(xf01, xf11, ys); + + return Lerp(yf0, yf1, zs) * 0.964921414852142333984375f; + } + + + // Value Cubic Noise + + private float SingleValueCubic(int seed, /*FNLfloat*/ double x, /*FNLfloat*/ double y) + { + int x1 = FastFloor(x); + int y1 = FastFloor(y); + + float xs = (float)(x - x1); + float ys = (float)(y - y1); + + x1 *= PrimeX; + y1 *= PrimeY; + int x0 = x1 - PrimeX; + int y0 = y1 - PrimeY; + int x2 = x1 + PrimeX; + int y2 = y1 + PrimeY; + int x3 = x1 + (PrimeX << 1); + int y3 = y1 + (PrimeY << 1); + + return CubicLerp( + CubicLerp(ValCoord(seed, x0, y0), ValCoord(seed, x1, y0), ValCoord(seed, x2, y0), ValCoord(seed, x3, y0), + xs), + CubicLerp(ValCoord(seed, x0, y1), ValCoord(seed, x1, y1), ValCoord(seed, x2, y1), ValCoord(seed, x3, y1), + xs), + CubicLerp(ValCoord(seed, x0, y2), ValCoord(seed, x1, y2), ValCoord(seed, x2, y2), ValCoord(seed, x3, y2), + xs), + CubicLerp(ValCoord(seed, x0, y3), ValCoord(seed, x1, y3), ValCoord(seed, x2, y3), ValCoord(seed, x3, y3), + xs), + ys) * (1 / (1.5f * 1.5f)); + } + + private float SingleValueCubic(int seed, /*FNLfloat*/ double x, /*FNLfloat*/ double y, /*FNLfloat*/ double z) + { + int x1 = FastFloor(x); + int y1 = FastFloor(y); + int z1 = FastFloor(z); + + float xs = (float)(x - x1); + float ys = (float)(y - y1); + float zs = (float)(z - z1); + + x1 *= PrimeX; + y1 *= PrimeY; + z1 *= PrimeZ; + + int x0 = x1 - PrimeX; + int y0 = y1 - PrimeY; + int z0 = z1 - PrimeZ; + int x2 = x1 + PrimeX; + int y2 = y1 + PrimeY; + int z2 = z1 + PrimeZ; + int x3 = x1 + (PrimeX << 1); + int y3 = y1 + (PrimeY << 1); + int z3 = z1 + (PrimeZ << 1); + + + return CubicLerp( + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z0), ValCoord(seed, x1, y0, z0), ValCoord(seed, x2, y0, z0), ValCoord(seed, x3, y0, z0), xs), + CubicLerp(ValCoord(seed, x0, y1, z0), ValCoord(seed, x1, y1, z0), ValCoord(seed, x2, y1, z0), ValCoord(seed, x3, y1, z0), xs), + CubicLerp(ValCoord(seed, x0, y2, z0), ValCoord(seed, x1, y2, z0), ValCoord(seed, x2, y2, z0), ValCoord(seed, x3, y2, z0), xs), + CubicLerp(ValCoord(seed, x0, y3, z0), ValCoord(seed, x1, y3, z0), ValCoord(seed, x2, y3, z0), ValCoord(seed, x3, y3, z0), xs), + ys), + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z1), ValCoord(seed, x1, y0, z1), ValCoord(seed, x2, y0, z1), ValCoord(seed, x3, y0, z1), xs), + CubicLerp(ValCoord(seed, x0, y1, z1), ValCoord(seed, x1, y1, z1), ValCoord(seed, x2, y1, z1), ValCoord(seed, x3, y1, z1), xs), + CubicLerp(ValCoord(seed, x0, y2, z1), ValCoord(seed, x1, y2, z1), ValCoord(seed, x2, y2, z1), ValCoord(seed, x3, y2, z1), xs), + CubicLerp(ValCoord(seed, x0, y3, z1), ValCoord(seed, x1, y3, z1), ValCoord(seed, x2, y3, z1), ValCoord(seed, x3, y3, z1), xs), + ys), + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z2), ValCoord(seed, x1, y0, z2), ValCoord(seed, x2, y0, z2), ValCoord(seed, x3, y0, z2), xs), + CubicLerp(ValCoord(seed, x0, y1, z2), ValCoord(seed, x1, y1, z2), ValCoord(seed, x2, y1, z2), ValCoord(seed, x3, y1, z2), xs), + CubicLerp(ValCoord(seed, x0, y2, z2), ValCoord(seed, x1, y2, z2), ValCoord(seed, x2, y2, z2), ValCoord(seed, x3, y2, z2), xs), + CubicLerp(ValCoord(seed, x0, y3, z2), ValCoord(seed, x1, y3, z2), ValCoord(seed, x2, y3, z2), ValCoord(seed, x3, y3, z2), xs), + ys), + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z3), ValCoord(seed, x1, y0, z3), ValCoord(seed, x2, y0, z3), ValCoord(seed, x3, y0, z3), xs), + CubicLerp(ValCoord(seed, x0, y1, z3), ValCoord(seed, x1, y1, z3), ValCoord(seed, x2, y1, z3), ValCoord(seed, x3, y1, z3), xs), + CubicLerp(ValCoord(seed, x0, y2, z3), ValCoord(seed, x1, y2, z3), ValCoord(seed, x2, y2, z3), ValCoord(seed, x3, y2, z3), xs), + CubicLerp(ValCoord(seed, x0, y3, z3), ValCoord(seed, x1, y3, z3), ValCoord(seed, x2, y3, z3), ValCoord(seed, x3, y3, z3), xs), + ys), + zs) * (1 / (1.5f * 1.5f * 1.5f)); + } + + + // Value Noise + + private float SingleValue(int seed, /*FNLfloat*/ double x, /*FNLfloat*/ double y) + { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + + float xs = InterpHermite((float)(x - x0)); + float ys = InterpHermite((float)(y - y0)); + + x0 *= PrimeX; + y0 *= PrimeY; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + + float xf0 = Lerp(ValCoord(seed, x0, y0), ValCoord(seed, x1, y0), xs); + float xf1 = Lerp(ValCoord(seed, x0, y1), ValCoord(seed, x1, y1), xs); + + return Lerp(xf0, xf1, ys); + } + + private float SingleValue(int seed, /*FNLfloat*/ double x, /*FNLfloat*/ double y, /*FNLfloat*/ double z) + { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + int z0 = FastFloor(z); + + float xs = InterpHermite((float)(x - x0)); + float ys = InterpHermite((float)(y - y0)); + float zs = InterpHermite((float)(z - z0)); + + x0 *= PrimeX; + y0 *= PrimeY; + z0 *= PrimeZ; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + int z1 = z0 + PrimeZ; + + float xf00 = Lerp(ValCoord(seed, x0, y0, z0), ValCoord(seed, x1, y0, z0), xs); + float xf10 = Lerp(ValCoord(seed, x0, y1, z0), ValCoord(seed, x1, y1, z0), xs); + float xf01 = Lerp(ValCoord(seed, x0, y0, z1), ValCoord(seed, x1, y0, z1), xs); + float xf11 = Lerp(ValCoord(seed, x0, y1, z1), ValCoord(seed, x1, y1, z1), xs); + + float yf0 = Lerp(xf00, xf10, ys); + float yf1 = Lerp(xf01, xf11, ys); + + return Lerp(yf0, yf1, zs); + } + + + // Domain Warp + + private void DoSingleDomainWarp(int seed, float amp, float freq, /*FNLfloat*/ double x, /*FNLfloat*/ double y, Vector2 coord) + { + switch (mDomainWarpType) + { + case OpenSimplex2: + SingleDomainWarpSimplexGradient(seed, amp * 38.283687591552734375f, freq, x, y, coord, false); + break; + case OpenSimplex2Reduced: + SingleDomainWarpSimplexGradient(seed, amp * 16.0f, freq, x, y, coord, true); + break; + case BasicGrid: + SingleDomainWarpBasicGrid(seed, amp, freq, x, y, coord); + break; + } + } + + private void DoSingleDomainWarp(int seed, float amp, float freq, /*FNLfloat*/ double x, /*FNLfloat*/ double y, /*FNLfloat*/ double z, Vector3 coord) + { + switch (mDomainWarpType) + { + case OpenSimplex2: + SingleDomainWarpOpenSimplex2Gradient(seed, amp * 32.69428253173828125f, freq, x, y, z, coord, false); + break; + case OpenSimplex2Reduced: + SingleDomainWarpOpenSimplex2Gradient(seed, amp * 7.71604938271605f, freq, x, y, z, coord, true); + break; + case BasicGrid: + SingleDomainWarpBasicGrid(seed, amp, freq, x, y, z, coord); + break; + } + } + + + // Domain Warp Single Wrapper + + private void DomainWarpSingle(Vector2 coord) + { + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + /*FNLfloat*/ double xs = coord.x; + /*FNLfloat*/ double ys = coord.y; + switch (mDomainWarpType) + { + case OpenSimplex2: + case OpenSimplex2Reduced: + { + final /*FNLfloat*/ double SQRT3 = (/*FNLfloat*/ double)1.7320508075688772935274463415059; + final /*FNLfloat*/ double F2 = 0.5f * (SQRT3 - 1); + /*FNLfloat*/ double t = (xs + ys) * F2; + xs += t; ys += t; + } + break; + default: + break; + } + + DoSingleDomainWarp(seed, amp, freq, xs, ys, coord); + } + + private void DomainWarpSingle(Vector3 coord) + { + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + /*FNLfloat*/ double xs = coord.x; + /*FNLfloat*/ double ys = coord.y; + /*FNLfloat*/ double zs = coord.z; + switch (mWarpTransformType3D) + { + case ImproveXYPlanes: + { + /*FNLfloat*/ double xy = xs + ys; + /*FNLfloat*/ double s2 = xy * -(/*FNLfloat*/ double)0.211324865405187; + zs *= (/*FNLfloat*/ double)0.577350269189626; + xs += s2 - zs; + ys = ys + s2 - zs; + zs += xy * (/*FNLfloat*/ double)0.577350269189626; + } + break; + case ImproveXZPlanes: + { + /*FNLfloat*/ double xz = xs + zs; + /*FNLfloat*/ double s2 = xz * -(/*FNLfloat*/ double)0.211324865405187; + ys *= (/*FNLfloat*/ double)0.577350269189626; + xs += s2 - ys; zs += s2 - ys; + ys += xz * (/*FNLfloat*/ double)0.577350269189626; + } + break; + case DefaultOpenSimplex2: + { + final /*FNLfloat*/ double R3 = (/*FNLfloat*/ double)(2.0 / 3.0); + /*FNLfloat*/ double r = (xs + ys + zs) * R3; // Rotation, not skew + xs = r - xs; + ys = r - ys; + zs = r - zs; + } + break; + default: + break; + } + + DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, coord); + } + + + // Domain Warp Fractal Progressive + + private void DomainWarpFractalProgressive(Vector2 coord) + { + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + for (int i = 0; i < mOctaves; i++) + { + /*FNLfloat*/ double xs = coord.x; + /*FNLfloat*/ double ys = coord.y; + switch (mDomainWarpType) + { + case OpenSimplex2: + case OpenSimplex2Reduced: + { + final /*FNLfloat*/ double SQRT3 = (/*FNLfloat*/ double)1.7320508075688772935274463415059; + final /*FNLfloat*/ double F2 = 0.5f * (SQRT3 - 1); + /*FNLfloat*/ double t = (xs + ys) * F2; + xs += t; ys += t; + } + break; + default: + break; + } + + DoSingleDomainWarp(seed, amp, freq, xs, ys, coord); + + seed++; + amp *= mGain; + freq *= mLacunarity; + } + } + + private void DomainWarpFractalProgressive(Vector3 coord) + { + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + for (int i = 0; i < mOctaves; i++) + { + /*FNLfloat*/ double xs = coord.x; + /*FNLfloat*/ double ys = coord.y; + /*FNLfloat*/ double zs = coord.z; + switch (mWarpTransformType3D) + { + case ImproveXYPlanes: + { + /*FNLfloat*/ double xy = xs + ys; + /*FNLfloat*/ double s2 = xy * -(/*FNLfloat*/ double)0.211324865405187; + zs *= (/*FNLfloat*/ double)0.577350269189626; + xs += s2 - zs; + ys = ys + s2 - zs; + zs += xy * (/*FNLfloat*/ double)0.577350269189626; + } + break; + case ImproveXZPlanes: + { + /*FNLfloat*/ double xz = xs + zs; + /*FNLfloat*/ double s2 = xz * -(/*FNLfloat*/ double)0.211324865405187; + ys *= (/*FNLfloat*/ double)0.577350269189626; + xs += s2 - ys; zs += s2 - ys; + ys += xz * (/*FNLfloat*/ double)0.577350269189626; + } + break; + case DefaultOpenSimplex2: + { + final /*FNLfloat*/ double R3 = (/*FNLfloat*/ double)(2.0 / 3.0); + /*FNLfloat*/ double r = (xs + ys + zs) * R3; // Rotation, not skew + xs = r - xs; + ys = r - ys; + zs = r - zs; + } + break; + default: + break; + } + + DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, coord); + + seed++; + amp *= mGain; + freq *= mLacunarity; + } + } + + + // Domain Warp Fractal Independant + private void DomainWarpFractalIndependent(Vector2 coord) + { + /*FNLfloat*/ double xs = coord.x; + /*FNLfloat*/ double ys = coord.y; + switch (mDomainWarpType) + { + case OpenSimplex2: + case OpenSimplex2Reduced: + { + final /*FNLfloat*/ double SQRT3 = (/*FNLfloat*/ double)1.7320508075688772935274463415059; + final /*FNLfloat*/ double F2 = 0.5f * (SQRT3 - 1); + /*FNLfloat*/ double t = (xs + ys) * F2; + xs += t; ys += t; + } + break; + default: + break; + } + + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + for (int i = 0; i < mOctaves; i++) + { + DoSingleDomainWarp(seed, amp, freq, xs, ys, coord); + + seed++; + amp *= mGain; + freq *= mLacunarity; + } + } + + private void DomainWarpFractalIndependent(Vector3 coord) + { + /*FNLfloat*/ double xs = coord.x; + /*FNLfloat*/ double ys = coord.y; + /*FNLfloat*/ double zs = coord.z; + switch (mWarpTransformType3D) + { + case ImproveXYPlanes: + { + /*FNLfloat*/ double xy = xs + ys; + /*FNLfloat*/ double s2 = xy * -(/*FNLfloat*/ double)0.211324865405187; + zs *= (/*FNLfloat*/ double)0.577350269189626; + xs += s2 - zs; + ys = ys + s2 - zs; + zs += xy * (/*FNLfloat*/ double)0.577350269189626; + } + break; + case ImproveXZPlanes: + { + /*FNLfloat*/ double xz = xs + zs; + /*FNLfloat*/ double s2 = xz * -(/*FNLfloat*/ double)0.211324865405187; + ys *= (/*FNLfloat*/ double)0.577350269189626; + xs += s2 - ys; zs += s2 - ys; + ys += xz * (/*FNLfloat*/ double)0.577350269189626; + } + break; + case DefaultOpenSimplex2: + { + final /*FNLfloat*/ double R3 = (/*FNLfloat*/ double)(2.0 / 3.0); + /*FNLfloat*/ double r = (xs + ys + zs) * R3; // Rotation, not skew + xs = r - xs; + ys = r - ys; + zs = r - zs; + } + break; + default: + break; + } + + int seed = mSeed; + float amp = mDomainWarpAmp * mFractalBounding; + float freq = mFrequency; + + for (int i = 0; i < mOctaves; i++) + { + DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, coord); + + seed++; + amp *= mGain; + freq *= mLacunarity; + } + } + + + // Domain Warp Basic Grid + + private void SingleDomainWarpBasicGrid(int seed, float warpAmp, float frequency, /*FNLfloat*/ double x, /*FNLfloat*/ double y, Vector2 coord) + { + /*FNLfloat*/ double xf = x * frequency; + /*FNLfloat*/ double yf = y * frequency; + + int x0 = FastFloor(xf); + int y0 = FastFloor(yf); + + float xs = InterpHermite((float)(xf - x0)); + float ys = InterpHermite((float)(yf - y0)); + + x0 *= PrimeX; + y0 *= PrimeY; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + + int hash0 = Hash(seed, x0, y0) & (255 << 1); + int hash1 = Hash(seed, x1, y0) & (255 << 1); + + float lx0x = Lerp(RandVecs2D[hash0], RandVecs2D[hash1], xs); + float ly0x = Lerp(RandVecs2D[hash0 | 1], RandVecs2D[hash1 | 1], xs); + + hash0 = Hash(seed, x0, y1) & (255 << 1); + hash1 = Hash(seed, x1, y1) & (255 << 1); + + float lx1x = Lerp(RandVecs2D[hash0], RandVecs2D[hash1], xs); + float ly1x = Lerp(RandVecs2D[hash0 | 1], RandVecs2D[hash1 | 1], xs); + + coord.x += Lerp(lx0x, lx1x, ys) * warpAmp; + coord.y += Lerp(ly0x, ly1x, ys) * warpAmp; + } + + private void SingleDomainWarpBasicGrid(int seed, float warpAmp, float frequency, /*FNLfloat*/ double x, /*FNLfloat*/ double y, /*FNLfloat*/ double z, Vector3 coord) + { + /*FNLfloat*/ double xf = x * frequency; + /*FNLfloat*/ double yf = y * frequency; + /*FNLfloat*/ double zf = z * frequency; + + int x0 = FastFloor(xf); + int y0 = FastFloor(yf); + int z0 = FastFloor(zf); + + float xs = InterpHermite((float)(xf - x0)); + float ys = InterpHermite((float)(yf - y0)); + float zs = InterpHermite((float)(zf - z0)); + + x0 *= PrimeX; + y0 *= PrimeY; + z0 *= PrimeZ; + int x1 = x0 + PrimeX; + int y1 = y0 + PrimeY; + int z1 = z0 + PrimeZ; + + int hash0 = Hash(seed, x0, y0, z0) & (255 << 2); + int hash1 = Hash(seed, x1, y0, z0) & (255 << 2); + + float lx0x = Lerp(RandVecs3D[hash0], RandVecs3D[hash1], xs); + float ly0x = Lerp(RandVecs3D[hash0 | 1], RandVecs3D[hash1 | 1], xs); + float lz0x = Lerp(RandVecs3D[hash0 | 2], RandVecs3D[hash1 | 2], xs); + + hash0 = Hash(seed, x0, y1, z0) & (255 << 2); + hash1 = Hash(seed, x1, y1, z0) & (255 << 2); + + float lx1x = Lerp(RandVecs3D[hash0], RandVecs3D[hash1], xs); + float ly1x = Lerp(RandVecs3D[hash0 | 1], RandVecs3D[hash1 | 1], xs); + float lz1x = Lerp(RandVecs3D[hash0 | 2], RandVecs3D[hash1 | 2], xs); + + float lx0y = Lerp(lx0x, lx1x, ys); + float ly0y = Lerp(ly0x, ly1x, ys); + float lz0y = Lerp(lz0x, lz1x, ys); + + hash0 = Hash(seed, x0, y0, z1) & (255 << 2); + hash1 = Hash(seed, x1, y0, z1) & (255 << 2); + + lx0x = Lerp(RandVecs3D[hash0], RandVecs3D[hash1], xs); + ly0x = Lerp(RandVecs3D[hash0 | 1], RandVecs3D[hash1 | 1], xs); + lz0x = Lerp(RandVecs3D[hash0 | 2], RandVecs3D[hash1 | 2], xs); + + hash0 = Hash(seed, x0, y1, z1) & (255 << 2); + hash1 = Hash(seed, x1, y1, z1) & (255 << 2); + + lx1x = Lerp(RandVecs3D[hash0], RandVecs3D[hash1], xs); + ly1x = Lerp(RandVecs3D[hash0 | 1], RandVecs3D[hash1 | 1], xs); + lz1x = Lerp(RandVecs3D[hash0 | 2], RandVecs3D[hash1 | 2], xs); + + coord.x += Lerp(lx0y, Lerp(lx0x, lx1x, ys), zs) * warpAmp; + coord.y += Lerp(ly0y, Lerp(ly0x, ly1x, ys), zs) * warpAmp; + coord.z += Lerp(lz0y, Lerp(lz0x, lz1x, ys), zs) * warpAmp; + } + + + // Domain Warp Simplex/OpenSimplex2 + private void SingleDomainWarpSimplexGradient(int seed, float warpAmp, float frequency, /*FNLfloat*/ double x, /*FNLfloat*/ double y, Vector2 coord, boolean outGradOnly) + { + final float SQRT3 = 1.7320508075688772935274463415059f; + final float G2 = (3 - SQRT3) / 6; + + x *= frequency; + y *= frequency; + + /* + * --- Skew moved to switch statements before fractal evaluation --- + * final FNLfloat F2 = 0.5f * (SQRT3 - 1); + * FNLfloat s = (x + y) * F2; + * x += s; y += s; + */ + + int i = FastFloor(x); + int j = FastFloor(y); + float xi = (float)(x - i); + float yi = (float)(y - j); + + float t = (xi + yi) * G2; + float x0 = (float)(xi - t); + float y0 = (float)(yi - t); + + i *= PrimeX; + j *= PrimeY; + + float vx, vy; + vx = vy = 0; + + float a = 0.5f - x0 * x0 - y0 * y0; + if (a > 0) + { + float aaaa = (a * a) * (a * a); + float xo, yo; + if (outGradOnly) + { + int hash = Hash(seed, i, j) & (255 << 1); + xo = RandVecs2D[hash]; + yo = RandVecs2D[hash | 1]; + } + else + { + int hash = Hash(seed, i, j); + int index1 = hash & (127 << 1); + int index2 = (hash >> 7) & (255 << 1); + float xg = Gradients2D[index1]; + float yg = Gradients2D[index1 | 1]; + float value = x0 * xg + y0 * yg; + float xgo = RandVecs2D[index2]; + float ygo = RandVecs2D[index2 | 1]; + xo = value * xgo; + yo = value * ygo; + } + vx += aaaa * xo; + vy += aaaa * yo; + } + + float c = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a); + if (c > 0) + { + float x2 = x0 + (2 * (float)G2 - 1); + float y2 = y0 + (2 * (float)G2 - 1); + float cccc = (c * c) * (c * c); + float xo, yo; + if (outGradOnly) + { + int hash = Hash(seed, i + PrimeX, j + PrimeY) & (255 << 1); + xo = RandVecs2D[hash]; + yo = RandVecs2D[hash | 1]; + } + else + { + int hash = Hash(seed, i + PrimeX, j + PrimeY); + int index1 = hash & (127 << 1); + int index2 = (hash >> 7) & (255 << 1); + float xg = Gradients2D[index1]; + float yg = Gradients2D[index1 | 1]; + float value = x2 * xg + y2 * yg; + float xgo = RandVecs2D[index2]; + float ygo = RandVecs2D[index2 | 1]; + xo = value * xgo; + yo = value * ygo; + } + vx += cccc * xo; + vy += cccc * yo; + } + + if (y0 > x0) + { + float x1 = x0 + (float)G2; + float y1 = y0 + ((float)G2 - 1); + float b = 0.5f - x1 * x1 - y1 * y1; + if (b > 0) + { + float bbbb = (b * b) * (b * b); + float xo, yo; + if (outGradOnly) + { + int hash = Hash(seed, i, j + PrimeY) & (255 << 1); + xo = RandVecs2D[hash]; + yo = RandVecs2D[hash | 1]; + } + else + { + int hash = Hash(seed, i, j + PrimeY); + int index1 = hash & (127 << 1); + int index2 = (hash >> 7) & (255 << 1); + float xg = Gradients2D[index1]; + float yg = Gradients2D[index1 | 1]; + float value = x1 * xg + y1 * yg; + float xgo = RandVecs2D[index2]; + float ygo = RandVecs2D[index2 | 1]; + xo = value * xgo; + yo = value * ygo; + } + vx += bbbb * xo; + vy += bbbb * yo; + } + } + else + { + float x1 = x0 + ((float)G2 - 1); + float y1 = y0 + (float)G2; + float b = 0.5f - x1 * x1 - y1 * y1; + if (b > 0) + { + float bbbb = (b * b) * (b * b); + float xo, yo; + if (outGradOnly) + { + int hash = Hash(seed, i + PrimeX, j) & (255 << 1); + xo = RandVecs2D[hash]; + yo = RandVecs2D[hash | 1]; + } + else + { + int hash = Hash(seed, i + PrimeX, j); + int index1 = hash & (127 << 1); + int index2 = (hash >> 7) & (255 << 1); + float xg = Gradients2D[index1]; + float yg = Gradients2D[index1 | 1]; + float value = x1 * xg + y1 * yg; + float xgo = RandVecs2D[index2]; + float ygo = RandVecs2D[index2 | 1]; + xo = value * xgo; + yo = value * ygo; + } + vx += bbbb * xo; + vy += bbbb * yo; + } + } + + coord.x += vx * warpAmp; + coord.y += vy * warpAmp; + } + + private void SingleDomainWarpOpenSimplex2Gradient(int seed, float warpAmp, float frequency, /*FNLfloat*/ double x, /*FNLfloat*/ double y, /*FNLfloat*/ double z, Vector3 coord, boolean outGradOnly) + { + x *= frequency; + y *= frequency; + z *= frequency; + + /* + * --- Rotation moved to switch statements before fractal evaluation --- + * final FNLfloat R3 = (FNLfloat)(2.0 / 3.0); + * FNLfloat r = (x + y + z) * R3; // Rotation, not skew + * x = r - x; y = r - y; z = r - z; + */ + + int i = FastRound(x); + int j = FastRound(y); + int k = FastRound(z); + float x0 = (float)x - i; + float y0 = (float)y - j; + float z0 = (float)z - k; + + int xNSign = (int)(-x0 - 1.0f) | 1; + int yNSign = (int)(-y0 - 1.0f) | 1; + int zNSign = (int)(-z0 - 1.0f) | 1; + + float ax0 = xNSign * -x0; + float ay0 = yNSign * -y0; + float az0 = zNSign * -z0; + + i *= PrimeX; + j *= PrimeY; + k *= PrimeZ; + + float vx, vy, vz; + vx = vy = vz = 0; + + float a = (0.6f - x0 * x0) - (y0 * y0 + z0 * z0); + for (int l = 0; ; l++) + { + if (a > 0) + { + float aaaa = (a * a) * (a * a); + float xo, yo, zo; + if (outGradOnly) + { + int hash = Hash(seed, i, j, k) & (255 << 2); + xo = RandVecs3D[hash]; + yo = RandVecs3D[hash | 1]; + zo = RandVecs3D[hash | 2]; + } + else + { + int hash = Hash(seed, i, j, k); + int index1 = hash & (63 << 2); + int index2 = (hash >> 6) & (255 << 2); + float xg = Gradients3D[index1]; + float yg = Gradients3D[index1 | 1]; + float zg = Gradients3D[index1 | 2]; + float value = x0 * xg + y0 * yg + z0 * zg; + float xgo = RandVecs3D[index2]; + float ygo = RandVecs3D[index2 | 1]; + float zgo = RandVecs3D[index2 | 2]; + xo = value * xgo; + yo = value * ygo; + zo = value * zgo; + } + vx += aaaa * xo; + vy += aaaa * yo; + vz += aaaa * zo; + } + + float b = a; + int i1 = i; + int j1 = j; + int k1 = k; + float x1 = x0; + float y1 = y0; + float z1 = z0; + + if (ax0 >= ay0 && ax0 >= az0) + { + x1 += xNSign; + b = b + ax0 + ax0; + i1 -= xNSign * PrimeX; + } + else if (ay0 > ax0 && ay0 >= az0) + { + y1 += yNSign; + b = b + ay0 + ay0; + j1 -= yNSign * PrimeY; + } + else + { + z1 += zNSign; + b = b + az0 + az0; + k1 -= zNSign * PrimeZ; + } + + if (b > 1) + { + b -= 1; + float bbbb = (b * b) * (b * b); + float xo, yo, zo; + if (outGradOnly) + { + int hash = Hash(seed, i1, j1, k1) & (255 << 2); + xo = RandVecs3D[hash]; + yo = RandVecs3D[hash | 1]; + zo = RandVecs3D[hash | 2]; + } + else + { + int hash = Hash(seed, i1, j1, k1); + int index1 = hash & (63 << 2); + int index2 = (hash >> 6) & (255 << 2); + float xg = Gradients3D[index1]; + float yg = Gradients3D[index1 | 1]; + float zg = Gradients3D[index1 | 2]; + float value = x1 * xg + y1 * yg + z1 * zg; + float xgo = RandVecs3D[index2]; + float ygo = RandVecs3D[index2 | 1]; + float zgo = RandVecs3D[index2 | 2]; + xo = value * xgo; + yo = value * ygo; + zo = value * zgo; + } + vx += bbbb * xo; + vy += bbbb * yo; + vz += bbbb * zo; + } + + if (l == 1) break; + + ax0 = 0.5f - ax0; + ay0 = 0.5f - ay0; + az0 = 0.5f - az0; + + x0 = xNSign * ax0; + y0 = yNSign * ay0; + z0 = zNSign * az0; + + a += (0.75f - ax0) - (ay0 + az0); + + i += (xNSign >> 1) & PrimeX; + j += (yNSign >> 1) & PrimeY; + k += (zNSign >> 1) & PrimeZ; + + xNSign = -xNSign; + yNSign = -yNSign; + zNSign = -zNSign; + + seed += 1293373; + } + + coord.x += vx * warpAmp; + coord.y += vy * warpAmp; + coord.z += vz * warpAmp; + } + + public static class Vector2 + { + public /*FNLfloat*/ double x; + public /*FNLfloat*/ double y; + public Vector2(/*FNLfloat*/ double x, /*FNLfloat*/ double y) + { + this.x = x; + this.y = y; + } + } + + public static class Vector3 + { + public /*FNLfloat*/ double x; + public /*FNLfloat*/ double y; + public /*FNLfloat*/ double z; + public Vector3(/*FNLfloat*/ double x, /*FNLfloat*/ double y, /*FNLfloat*/ double z) + { + this.x = x; + this.y = y; + this.z = z; + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/credits.txt b/src/main/resources/assets/credits.txt new file mode 100644 index 0000000..deb2af5 --- /dev/null +++ b/src/main/resources/assets/credits.txt @@ -0,0 +1,7 @@ +CodeStorm Team +Leader: +- Mai Thanh (@thnhmai06) +Members: +- Manh Tan (@ManhTanTran) +- Anh Tuan (@huynhtuan372) +Thank you for playing! <3 diff --git a/src/main/resources/assets/sounds/music/in_game.wav b/src/main/resources/assets/sounds/music/in_game.wav new file mode 100644 index 0000000..916f273 Binary files /dev/null and b/src/main/resources/assets/sounds/music/in_game.wav differ diff --git a/src/main/resources/assets/sounds/music/menu.ogg b/src/main/resources/assets/sounds/music/menu.ogg new file mode 100644 index 0000000..d457631 Binary files /dev/null and b/src/main/resources/assets/sounds/music/menu.ogg differ diff --git a/src/main/resources/assets/sounds/sfx/blip.wav b/src/main/resources/assets/sounds/sfx/blip.wav new file mode 100644 index 0000000..9fdee0b Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/blip.wav differ diff --git a/src/main/resources/assets/sounds/sfx/brick-hit-1.wav b/src/main/resources/assets/sounds/sfx/brick-hit-1.wav new file mode 100644 index 0000000..9ce943f Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/brick-hit-1.wav differ diff --git a/src/main/resources/assets/sounds/sfx/brick-hit-2.wav b/src/main/resources/assets/sounds/sfx/brick-hit-2.wav new file mode 100644 index 0000000..ecdf4bb Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/brick-hit-2.wav differ diff --git a/src/main/resources/assets/sounds/sfx/buzz.wav b/src/main/resources/assets/sounds/sfx/buzz.wav new file mode 100644 index 0000000..1978bfc Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/buzz.wav differ diff --git a/src/main/resources/assets/sounds/sfx/click_tiny.wav b/src/main/resources/assets/sounds/sfx/click_tiny.wav new file mode 100644 index 0000000..2f7d044 Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/click_tiny.wav differ diff --git a/src/main/resources/assets/sounds/sfx/confirm.wav b/src/main/resources/assets/sounds/sfx/confirm.wav new file mode 100644 index 0000000..b0c5a71 Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/confirm.wav differ diff --git a/src/main/resources/assets/sounds/sfx/electric.wav b/src/main/resources/assets/sounds/sfx/electric.wav new file mode 100644 index 0000000..9e39104 Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/electric.wav differ diff --git a/src/main/resources/assets/sounds/sfx/high_score.wav b/src/main/resources/assets/sounds/sfx/high_score.wav new file mode 100644 index 0000000..9702f67 Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/high_score.wav differ diff --git a/src/main/resources/assets/sounds/sfx/hurt.wav b/src/main/resources/assets/sounds/sfx/hurt.wav new file mode 100644 index 0000000..78defe3 Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/hurt.wav differ diff --git a/src/main/resources/assets/sounds/sfx/key_open.wav b/src/main/resources/assets/sounds/sfx/key_open.wav new file mode 100644 index 0000000..4c3ffcb Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/key_open.wav differ diff --git a/src/main/resources/assets/sounds/sfx/lose.wav b/src/main/resources/assets/sounds/sfx/lose.wav new file mode 100644 index 0000000..cc74e03 Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/lose.wav differ diff --git a/src/main/resources/assets/sounds/sfx/no-select.wav b/src/main/resources/assets/sounds/sfx/no-select.wav new file mode 100644 index 0000000..38bcf3e Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/no-select.wav differ diff --git a/src/main/resources/assets/sounds/sfx/paddle_hit.wav b/src/main/resources/assets/sounds/sfx/paddle_hit.wav new file mode 100644 index 0000000..68483ba Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/paddle_hit.wav differ diff --git a/src/main/resources/assets/sounds/sfx/pause.wav b/src/main/resources/assets/sounds/sfx/pause.wav new file mode 100644 index 0000000..1a99da0 Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/pause.wav differ diff --git a/src/main/resources/assets/sounds/sfx/power_up.wav b/src/main/resources/assets/sounds/sfx/power_up.wav new file mode 100644 index 0000000..ccbad18 Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/power_up.wav differ diff --git a/src/main/resources/assets/sounds/sfx/recover.wav b/src/main/resources/assets/sounds/sfx/recover.wav new file mode 100644 index 0000000..387db55 Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/recover.wav differ diff --git a/src/main/resources/assets/sounds/sfx/score.wav b/src/main/resources/assets/sounds/sfx/score.wav new file mode 100644 index 0000000..420d868 Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/score.wav differ diff --git a/src/main/resources/assets/sounds/sfx/select.wav b/src/main/resources/assets/sounds/sfx/select.wav new file mode 100644 index 0000000..ccfeda3 Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/select.wav differ diff --git a/src/main/resources/assets/sounds/sfx/shrink.wav b/src/main/resources/assets/sounds/sfx/shrink.wav new file mode 100644 index 0000000..ef11ed6 Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/shrink.wav differ diff --git a/src/main/resources/assets/sounds/sfx/switch2.wav b/src/main/resources/assets/sounds/sfx/switch2.wav new file mode 100644 index 0000000..c19e3d4 Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/switch2.wav differ diff --git a/src/main/resources/assets/sounds/sfx/victory.wav b/src/main/resources/assets/sounds/sfx/victory.wav new file mode 100644 index 0000000..11cbea7 Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/victory.wav differ diff --git a/src/main/resources/assets/sounds/sfx/wall_hit.wav b/src/main/resources/assets/sounds/sfx/wall_hit.wav new file mode 100644 index 0000000..b8eb985 Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/wall_hit.wav differ diff --git a/src/main/resources/assets/sounds/sfx/win3.wav b/src/main/resources/assets/sounds/sfx/win3.wav new file mode 100644 index 0000000..e050c78 Binary files /dev/null and b/src/main/resources/assets/sounds/sfx/win3.wav differ diff --git a/src/main/resources/assets/textures/ball/blue/Blue Ball.png b/src/main/resources/assets/textures/ball/blue/Blue Ball.png new file mode 100644 index 0000000..6d33bdc Binary files /dev/null and b/src/main/resources/assets/textures/ball/blue/Blue Ball.png differ diff --git a/src/main/resources/assets/textures/ball/green/Green Ball.png b/src/main/resources/assets/textures/ball/green/Green Ball.png new file mode 100644 index 0000000..2a75873 Binary files /dev/null and b/src/main/resources/assets/textures/ball/green/Green Ball.png differ diff --git a/src/main/resources/assets/textures/ball/orange/Orange Ball.png b/src/main/resources/assets/textures/ball/orange/Orange Ball.png new file mode 100644 index 0000000..c28e25a Binary files /dev/null and b/src/main/resources/assets/textures/ball/orange/Orange Ball.png differ diff --git a/src/main/resources/assets/textures/ball/pink/Pink Ball.png b/src/main/resources/assets/textures/ball/pink/Pink Ball.png new file mode 100644 index 0000000..797a555 Binary files /dev/null and b/src/main/resources/assets/textures/ball/pink/Pink Ball.png differ diff --git a/src/main/resources/assets/textures/ball/red/Red Ball.png b/src/main/resources/assets/textures/ball/red/Red Ball.png new file mode 100644 index 0000000..4b40762 Binary files /dev/null and b/src/main/resources/assets/textures/ball/red/Red Ball.png differ diff --git a/src/main/resources/assets/textures/ball/start ball/Start Ball.png b/src/main/resources/assets/textures/ball/start ball/Start Ball.png new file mode 100644 index 0000000..be0e7c8 Binary files /dev/null and b/src/main/resources/assets/textures/ball/start ball/Start Ball.png differ diff --git a/src/main/resources/assets/textures/ball/yellow/Yellow Ball.png b/src/main/resources/assets/textures/ball/yellow/Yellow Ball.png new file mode 100644 index 0000000..fd3299d Binary files /dev/null and b/src/main/resources/assets/textures/ball/yellow/Yellow Ball.png differ diff --git a/src/main/resources/assets/textures/bricks/blue/explode.png b/src/main/resources/assets/textures/bricks/blue/explode.png new file mode 100644 index 0000000..b7f7024 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/blue/explode.png differ diff --git a/src/main/resources/assets/textures/bricks/blue/keybrick.png b/src/main/resources/assets/textures/bricks/blue/keybrick.png new file mode 100644 index 0000000..d25f36e Binary files /dev/null and b/src/main/resources/assets/textures/bricks/blue/keybrick.png differ diff --git a/src/main/resources/assets/textures/bricks/blue/normal.png b/src/main/resources/assets/textures/bricks/blue/normal.png new file mode 100644 index 0000000..0840abd Binary files /dev/null and b/src/main/resources/assets/textures/bricks/blue/normal.png differ diff --git a/src/main/resources/assets/textures/bricks/blue/shield.png b/src/main/resources/assets/textures/bricks/blue/shield.png new file mode 100644 index 0000000..432eb10 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/blue/shield.png differ diff --git a/src/main/resources/assets/textures/bricks/blue/strong.png b/src/main/resources/assets/textures/bricks/blue/strong.png new file mode 100644 index 0000000..a8bf01d Binary files /dev/null and b/src/main/resources/assets/textures/bricks/blue/strong.png differ diff --git a/src/main/resources/assets/textures/bricks/blue/strongFirstHit.png b/src/main/resources/assets/textures/bricks/blue/strongFirstHit.png new file mode 100644 index 0000000..fd5ceb7 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/blue/strongFirstHit.png differ diff --git a/src/main/resources/assets/textures/bricks/blue/strongSecondHit.png b/src/main/resources/assets/textures/bricks/blue/strongSecondHit.png new file mode 100644 index 0000000..174d81f Binary files /dev/null and b/src/main/resources/assets/textures/bricks/blue/strongSecondHit.png differ diff --git a/src/main/resources/assets/textures/bricks/blue/strongThirdHit.png b/src/main/resources/assets/textures/bricks/blue/strongThirdHit.png new file mode 100644 index 0000000..7477f2d Binary files /dev/null and b/src/main/resources/assets/textures/bricks/blue/strongThirdHit.png differ diff --git a/src/main/resources/assets/textures/bricks/green/explode.png b/src/main/resources/assets/textures/bricks/green/explode.png new file mode 100644 index 0000000..00108bc Binary files /dev/null and b/src/main/resources/assets/textures/bricks/green/explode.png differ diff --git a/src/main/resources/assets/textures/bricks/green/keybrick.png b/src/main/resources/assets/textures/bricks/green/keybrick.png new file mode 100644 index 0000000..9700512 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/green/keybrick.png differ diff --git a/src/main/resources/assets/textures/bricks/green/normal.png b/src/main/resources/assets/textures/bricks/green/normal.png new file mode 100644 index 0000000..85df743 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/green/normal.png differ diff --git a/src/main/resources/assets/textures/bricks/green/shield.png b/src/main/resources/assets/textures/bricks/green/shield.png new file mode 100644 index 0000000..557d354 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/green/shield.png differ diff --git a/src/main/resources/assets/textures/bricks/green/strong.png b/src/main/resources/assets/textures/bricks/green/strong.png new file mode 100644 index 0000000..a6774be Binary files /dev/null and b/src/main/resources/assets/textures/bricks/green/strong.png differ diff --git a/src/main/resources/assets/textures/bricks/green/strongFirstHit.png b/src/main/resources/assets/textures/bricks/green/strongFirstHit.png new file mode 100644 index 0000000..cbe4161 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/green/strongFirstHit.png differ diff --git a/src/main/resources/assets/textures/bricks/green/strongSecondHit.png b/src/main/resources/assets/textures/bricks/green/strongSecondHit.png new file mode 100644 index 0000000..d179a35 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/green/strongSecondHit.png differ diff --git a/src/main/resources/assets/textures/bricks/green/strongThirdHit.png b/src/main/resources/assets/textures/bricks/green/strongThirdHit.png new file mode 100644 index 0000000..3386dd2 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/green/strongThirdHit.png differ diff --git a/src/main/resources/assets/textures/bricks/orange/explode.png b/src/main/resources/assets/textures/bricks/orange/explode.png new file mode 100644 index 0000000..4a48293 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/orange/explode.png differ diff --git a/src/main/resources/assets/textures/bricks/orange/keybrick.png b/src/main/resources/assets/textures/bricks/orange/keybrick.png new file mode 100644 index 0000000..0536665 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/orange/keybrick.png differ diff --git a/src/main/resources/assets/textures/bricks/orange/normal.png b/src/main/resources/assets/textures/bricks/orange/normal.png new file mode 100644 index 0000000..f1823f7 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/orange/normal.png differ diff --git a/src/main/resources/assets/textures/bricks/orange/shield.png b/src/main/resources/assets/textures/bricks/orange/shield.png new file mode 100644 index 0000000..58c91e2 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/orange/shield.png differ diff --git a/src/main/resources/assets/textures/bricks/orange/strong.png b/src/main/resources/assets/textures/bricks/orange/strong.png new file mode 100644 index 0000000..8c58ac7 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/orange/strong.png differ diff --git a/src/main/resources/assets/textures/bricks/orange/strongFirstHit.png b/src/main/resources/assets/textures/bricks/orange/strongFirstHit.png new file mode 100644 index 0000000..af4f120 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/orange/strongFirstHit.png differ diff --git a/src/main/resources/assets/textures/bricks/orange/strongSecondHit.png b/src/main/resources/assets/textures/bricks/orange/strongSecondHit.png new file mode 100644 index 0000000..3d6c355 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/orange/strongSecondHit.png differ diff --git a/src/main/resources/assets/textures/bricks/orange/strongThirdHit.png b/src/main/resources/assets/textures/bricks/orange/strongThirdHit.png new file mode 100644 index 0000000..4092668 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/orange/strongThirdHit.png differ diff --git a/src/main/resources/assets/textures/bricks/pink/explode.png b/src/main/resources/assets/textures/bricks/pink/explode.png new file mode 100644 index 0000000..6c58af7 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/pink/explode.png differ diff --git a/src/main/resources/assets/textures/bricks/pink/keybrick.png b/src/main/resources/assets/textures/bricks/pink/keybrick.png new file mode 100644 index 0000000..f0078e9 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/pink/keybrick.png differ diff --git a/src/main/resources/assets/textures/bricks/pink/normal.png b/src/main/resources/assets/textures/bricks/pink/normal.png new file mode 100644 index 0000000..8ec0034 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/pink/normal.png differ diff --git a/src/main/resources/assets/textures/bricks/pink/shield.png b/src/main/resources/assets/textures/bricks/pink/shield.png new file mode 100644 index 0000000..690b632 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/pink/shield.png differ diff --git a/src/main/resources/assets/textures/bricks/pink/strong.png b/src/main/resources/assets/textures/bricks/pink/strong.png new file mode 100644 index 0000000..647c9a6 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/pink/strong.png differ diff --git a/src/main/resources/assets/textures/bricks/pink/strongFirstHit.png b/src/main/resources/assets/textures/bricks/pink/strongFirstHit.png new file mode 100644 index 0000000..4a7ca6b Binary files /dev/null and b/src/main/resources/assets/textures/bricks/pink/strongFirstHit.png differ diff --git a/src/main/resources/assets/textures/bricks/pink/strongSecondHit.png b/src/main/resources/assets/textures/bricks/pink/strongSecondHit.png new file mode 100644 index 0000000..3a70a66 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/pink/strongSecondHit.png differ diff --git a/src/main/resources/assets/textures/bricks/pink/strongThirdHit.png b/src/main/resources/assets/textures/bricks/pink/strongThirdHit.png new file mode 100644 index 0000000..9b1f0b5 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/pink/strongThirdHit.png differ diff --git a/src/main/resources/assets/textures/bricks/red/explode.png b/src/main/resources/assets/textures/bricks/red/explode.png new file mode 100644 index 0000000..2442015 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/red/explode.png differ diff --git a/src/main/resources/assets/textures/bricks/red/keybrick.png b/src/main/resources/assets/textures/bricks/red/keybrick.png new file mode 100644 index 0000000..c59cc8e Binary files /dev/null and b/src/main/resources/assets/textures/bricks/red/keybrick.png differ diff --git a/src/main/resources/assets/textures/bricks/red/normal.png b/src/main/resources/assets/textures/bricks/red/normal.png new file mode 100644 index 0000000..137632c Binary files /dev/null and b/src/main/resources/assets/textures/bricks/red/normal.png differ diff --git a/src/main/resources/assets/textures/bricks/red/shield.png b/src/main/resources/assets/textures/bricks/red/shield.png new file mode 100644 index 0000000..83837ae Binary files /dev/null and b/src/main/resources/assets/textures/bricks/red/shield.png differ diff --git a/src/main/resources/assets/textures/bricks/red/strong.png b/src/main/resources/assets/textures/bricks/red/strong.png new file mode 100644 index 0000000..e00b227 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/red/strong.png differ diff --git a/src/main/resources/assets/textures/bricks/red/strongFirstHit.png b/src/main/resources/assets/textures/bricks/red/strongFirstHit.png new file mode 100644 index 0000000..8fd14ea Binary files /dev/null and b/src/main/resources/assets/textures/bricks/red/strongFirstHit.png differ diff --git a/src/main/resources/assets/textures/bricks/red/strongSecondHit.png b/src/main/resources/assets/textures/bricks/red/strongSecondHit.png new file mode 100644 index 0000000..6ce8ebb Binary files /dev/null and b/src/main/resources/assets/textures/bricks/red/strongSecondHit.png differ diff --git a/src/main/resources/assets/textures/bricks/red/strongThirdHit.png b/src/main/resources/assets/textures/bricks/red/strongThirdHit.png new file mode 100644 index 0000000..5fd3164 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/red/strongThirdHit.png differ diff --git a/src/main/resources/assets/textures/bricks/yellow/explode.png b/src/main/resources/assets/textures/bricks/yellow/explode.png new file mode 100644 index 0000000..d141b47 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/yellow/explode.png differ diff --git a/src/main/resources/assets/textures/bricks/yellow/keybrick.png b/src/main/resources/assets/textures/bricks/yellow/keybrick.png new file mode 100644 index 0000000..e07fe93 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/yellow/keybrick.png differ diff --git a/src/main/resources/assets/textures/bricks/yellow/normal.png b/src/main/resources/assets/textures/bricks/yellow/normal.png new file mode 100644 index 0000000..5aeb023 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/yellow/normal.png differ diff --git a/src/main/resources/assets/textures/bricks/yellow/shield.png b/src/main/resources/assets/textures/bricks/yellow/shield.png new file mode 100644 index 0000000..6f6976d Binary files /dev/null and b/src/main/resources/assets/textures/bricks/yellow/shield.png differ diff --git a/src/main/resources/assets/textures/bricks/yellow/strong.png b/src/main/resources/assets/textures/bricks/yellow/strong.png new file mode 100644 index 0000000..b76bbd9 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/yellow/strong.png differ diff --git a/src/main/resources/assets/textures/bricks/yellow/strongFirstHit.png b/src/main/resources/assets/textures/bricks/yellow/strongFirstHit.png new file mode 100644 index 0000000..a372ff8 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/yellow/strongFirstHit.png differ diff --git a/src/main/resources/assets/textures/bricks/yellow/strongSecondHit.png b/src/main/resources/assets/textures/bricks/yellow/strongSecondHit.png new file mode 100644 index 0000000..42cdb7d Binary files /dev/null and b/src/main/resources/assets/textures/bricks/yellow/strongSecondHit.png differ diff --git a/src/main/resources/assets/textures/bricks/yellow/strongThirdHit.png b/src/main/resources/assets/textures/bricks/yellow/strongThirdHit.png new file mode 100644 index 0000000..4528f77 Binary files /dev/null and b/src/main/resources/assets/textures/bricks/yellow/strongThirdHit.png differ diff --git a/src/main/resources/assets/textures/heart.png b/src/main/resources/assets/textures/heart.png new file mode 100644 index 0000000..461b398 Binary files /dev/null and b/src/main/resources/assets/textures/heart.png differ diff --git a/src/main/resources/assets/textures/numbers/0.png b/src/main/resources/assets/textures/numbers/0.png new file mode 100644 index 0000000..60d8e91 Binary files /dev/null and b/src/main/resources/assets/textures/numbers/0.png differ diff --git a/src/main/resources/assets/textures/numbers/1.png b/src/main/resources/assets/textures/numbers/1.png new file mode 100644 index 0000000..23ab362 Binary files /dev/null and b/src/main/resources/assets/textures/numbers/1.png differ diff --git a/src/main/resources/assets/textures/numbers/2.png b/src/main/resources/assets/textures/numbers/2.png new file mode 100644 index 0000000..db7a1ec Binary files /dev/null and b/src/main/resources/assets/textures/numbers/2.png differ diff --git a/src/main/resources/assets/textures/numbers/3.png b/src/main/resources/assets/textures/numbers/3.png new file mode 100644 index 0000000..a833c61 Binary files /dev/null and b/src/main/resources/assets/textures/numbers/3.png differ diff --git a/src/main/resources/assets/textures/numbers/4.png b/src/main/resources/assets/textures/numbers/4.png new file mode 100644 index 0000000..c0579e0 Binary files /dev/null and b/src/main/resources/assets/textures/numbers/4.png differ diff --git a/src/main/resources/assets/textures/numbers/5.png b/src/main/resources/assets/textures/numbers/5.png new file mode 100644 index 0000000..e983a21 Binary files /dev/null and b/src/main/resources/assets/textures/numbers/5.png differ diff --git a/src/main/resources/assets/textures/numbers/6.png b/src/main/resources/assets/textures/numbers/6.png new file mode 100644 index 0000000..cbc1222 Binary files /dev/null and b/src/main/resources/assets/textures/numbers/6.png differ diff --git a/src/main/resources/assets/textures/numbers/7.png b/src/main/resources/assets/textures/numbers/7.png new file mode 100644 index 0000000..3936b98 Binary files /dev/null and b/src/main/resources/assets/textures/numbers/7.png differ diff --git a/src/main/resources/assets/textures/numbers/8.png b/src/main/resources/assets/textures/numbers/8.png new file mode 100644 index 0000000..ddd4039 Binary files /dev/null and b/src/main/resources/assets/textures/numbers/8.png differ diff --git a/src/main/resources/assets/textures/numbers/9.png b/src/main/resources/assets/textures/numbers/9.png new file mode 100644 index 0000000..1f1a125 Binary files /dev/null and b/src/main/resources/assets/textures/numbers/9.png differ diff --git a/src/main/resources/assets/textures/paddle/expand/blue/Blue Expand Paddle.png b/src/main/resources/assets/textures/paddle/expand/blue/Blue Expand Paddle.png new file mode 100644 index 0000000..95c73d7 Binary files /dev/null and b/src/main/resources/assets/textures/paddle/expand/blue/Blue Expand Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/expand/green/Green Expand Paddle.png b/src/main/resources/assets/textures/paddle/expand/green/Green Expand Paddle.png new file mode 100644 index 0000000..b31181a Binary files /dev/null and b/src/main/resources/assets/textures/paddle/expand/green/Green Expand Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/expand/orange/Orange Expand Paddle.png b/src/main/resources/assets/textures/paddle/expand/orange/Orange Expand Paddle.png new file mode 100644 index 0000000..734e461 Binary files /dev/null and b/src/main/resources/assets/textures/paddle/expand/orange/Orange Expand Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/expand/pink/Pink Expand Paddle.png b/src/main/resources/assets/textures/paddle/expand/pink/Pink Expand Paddle.png new file mode 100644 index 0000000..5d2b236 Binary files /dev/null and b/src/main/resources/assets/textures/paddle/expand/pink/Pink Expand Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/expand/red/Red Expand Paddle.png b/src/main/resources/assets/textures/paddle/expand/red/Red Expand Paddle.png new file mode 100644 index 0000000..747228a Binary files /dev/null and b/src/main/resources/assets/textures/paddle/expand/red/Red Expand Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/expand/yellow/Yellow Expand Paddle.png b/src/main/resources/assets/textures/paddle/expand/yellow/Yellow Expand Paddle.png new file mode 100644 index 0000000..9cd4bb7 Binary files /dev/null and b/src/main/resources/assets/textures/paddle/expand/yellow/Yellow Expand Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/normal/blue/Blue Paddle.png b/src/main/resources/assets/textures/paddle/normal/blue/Blue Paddle.png new file mode 100644 index 0000000..fc5f96f Binary files /dev/null and b/src/main/resources/assets/textures/paddle/normal/blue/Blue Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/normal/green/Green Paddle.png b/src/main/resources/assets/textures/paddle/normal/green/Green Paddle.png new file mode 100644 index 0000000..bd969ef Binary files /dev/null and b/src/main/resources/assets/textures/paddle/normal/green/Green Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/normal/orange/Orange Paddle.png b/src/main/resources/assets/textures/paddle/normal/orange/Orange Paddle.png new file mode 100644 index 0000000..d941cff Binary files /dev/null and b/src/main/resources/assets/textures/paddle/normal/orange/Orange Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/normal/pink/Pink Paddle.png b/src/main/resources/assets/textures/paddle/normal/pink/Pink Paddle.png new file mode 100644 index 0000000..866d779 Binary files /dev/null and b/src/main/resources/assets/textures/paddle/normal/pink/Pink Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/normal/red/Red Paddle.png b/src/main/resources/assets/textures/paddle/normal/red/Red Paddle.png new file mode 100644 index 0000000..8522ac3 Binary files /dev/null and b/src/main/resources/assets/textures/paddle/normal/red/Red Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/normal/yellow/Yellow Paddle.png b/src/main/resources/assets/textures/paddle/normal/yellow/Yellow Paddle.png new file mode 100644 index 0000000..86d691c Binary files /dev/null and b/src/main/resources/assets/textures/paddle/normal/yellow/Yellow Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/power/blue/Blue Power Paddle.png b/src/main/resources/assets/textures/paddle/power/blue/Blue Power Paddle.png new file mode 100644 index 0000000..c5b0fef Binary files /dev/null and b/src/main/resources/assets/textures/paddle/power/blue/Blue Power Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/power/green/Green Power Paddle.png b/src/main/resources/assets/textures/paddle/power/green/Green Power Paddle.png new file mode 100644 index 0000000..a057702 Binary files /dev/null and b/src/main/resources/assets/textures/paddle/power/green/Green Power Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/power/orange/Orange Power Paddle.png b/src/main/resources/assets/textures/paddle/power/orange/Orange Power Paddle.png new file mode 100644 index 0000000..48a66ec Binary files /dev/null and b/src/main/resources/assets/textures/paddle/power/orange/Orange Power Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/power/pink/Pink Power Paddle.png b/src/main/resources/assets/textures/paddle/power/pink/Pink Power Paddle.png new file mode 100644 index 0000000..cf61c0d Binary files /dev/null and b/src/main/resources/assets/textures/paddle/power/pink/Pink Power Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/power/red/Red Power Paddle.png b/src/main/resources/assets/textures/paddle/power/red/Red Power Paddle.png new file mode 100644 index 0000000..ef71ff5 Binary files /dev/null and b/src/main/resources/assets/textures/paddle/power/red/Red Power Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/power/yellow/Yellow Power Paddle.png b/src/main/resources/assets/textures/paddle/power/yellow/Yellow Power Paddle.png new file mode 100644 index 0000000..c265202 Binary files /dev/null and b/src/main/resources/assets/textures/paddle/power/yellow/Yellow Power Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/shrink/blue/Blue Shrink Paddle.png b/src/main/resources/assets/textures/paddle/shrink/blue/Blue Shrink Paddle.png new file mode 100644 index 0000000..063a65f Binary files /dev/null and b/src/main/resources/assets/textures/paddle/shrink/blue/Blue Shrink Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/shrink/green/Green Shrink Paddle.png b/src/main/resources/assets/textures/paddle/shrink/green/Green Shrink Paddle.png new file mode 100644 index 0000000..7ae9ade Binary files /dev/null and b/src/main/resources/assets/textures/paddle/shrink/green/Green Shrink Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/shrink/orange/Orange Shrink Paddle.png b/src/main/resources/assets/textures/paddle/shrink/orange/Orange Shrink Paddle.png new file mode 100644 index 0000000..e62d490 Binary files /dev/null and b/src/main/resources/assets/textures/paddle/shrink/orange/Orange Shrink Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/shrink/pink/Pink Shrink Paddle.png b/src/main/resources/assets/textures/paddle/shrink/pink/Pink Shrink Paddle.png new file mode 100644 index 0000000..26fe6ef Binary files /dev/null and b/src/main/resources/assets/textures/paddle/shrink/pink/Pink Shrink Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/shrink/red/Red Shrink Paddle.png b/src/main/resources/assets/textures/paddle/shrink/red/Red Shrink Paddle.png new file mode 100644 index 0000000..efe1ccd Binary files /dev/null and b/src/main/resources/assets/textures/paddle/shrink/red/Red Shrink Paddle.png differ diff --git a/src/main/resources/assets/textures/paddle/shrink/yellow/Yellow Shrink Paddle.png b/src/main/resources/assets/textures/paddle/shrink/yellow/Yellow Shrink Paddle.png new file mode 100644 index 0000000..3db0b24 Binary files /dev/null and b/src/main/resources/assets/textures/paddle/shrink/yellow/Yellow Shrink Paddle.png differ diff --git a/src/main/resources/assets/textures/power/ball/Fast Ball.png b/src/main/resources/assets/textures/power/ball/Fast Ball.png new file mode 100644 index 0000000..c560b8f Binary files /dev/null and b/src/main/resources/assets/textures/power/ball/Fast Ball.png differ diff --git a/src/main/resources/assets/textures/power/ball/Slow Ball.png b/src/main/resources/assets/textures/power/ball/Slow Ball.png new file mode 100644 index 0000000..7941ae0 Binary files /dev/null and b/src/main/resources/assets/textures/power/ball/Slow Ball.png differ diff --git a/src/main/resources/assets/textures/power/ball/x2 Ball.png b/src/main/resources/assets/textures/power/ball/x2 Ball.png new file mode 100644 index 0000000..697979c Binary files /dev/null and b/src/main/resources/assets/textures/power/ball/x2 Ball.png differ diff --git a/src/main/resources/assets/textures/power/misc/extra_life.png b/src/main/resources/assets/textures/power/misc/extra_life.png new file mode 100644 index 0000000..a83a3d5 Binary files /dev/null and b/src/main/resources/assets/textures/power/misc/extra_life.png differ diff --git a/src/main/resources/assets/textures/power/misc/score_boost.png b/src/main/resources/assets/textures/power/misc/score_boost.png new file mode 100644 index 0000000..1e7f0d2 Binary files /dev/null and b/src/main/resources/assets/textures/power/misc/score_boost.png differ diff --git a/src/main/resources/assets/textures/power/misc/shield.png b/src/main/resources/assets/textures/power/misc/shield.png new file mode 100644 index 0000000..fbc95a0 Binary files /dev/null and b/src/main/resources/assets/textures/power/misc/shield.png differ diff --git a/src/main/resources/assets/textures/power/paddle/Expand Paddle.png b/src/main/resources/assets/textures/power/paddle/Expand Paddle.png new file mode 100644 index 0000000..09bb999 Binary files /dev/null and b/src/main/resources/assets/textures/power/paddle/Expand Paddle.png differ diff --git a/src/main/resources/assets/textures/power/paddle/Gun.png b/src/main/resources/assets/textures/power/paddle/Gun.png new file mode 100644 index 0000000..7c817db Binary files /dev/null and b/src/main/resources/assets/textures/power/paddle/Gun.png differ diff --git a/src/main/resources/assets/textures/power/paddle/Reverse Paddle.png b/src/main/resources/assets/textures/power/paddle/Reverse Paddle.png new file mode 100644 index 0000000..f0be531 Binary files /dev/null and b/src/main/resources/assets/textures/power/paddle/Reverse Paddle.png differ diff --git a/src/main/resources/assets/textures/power/paddle/Shrink Paddle.png b/src/main/resources/assets/textures/power/paddle/Shrink Paddle.png new file mode 100644 index 0000000..1494b09 Binary files /dev/null and b/src/main/resources/assets/textures/power/paddle/Shrink Paddle.png differ diff --git a/src/main/resources/assets/ui/powerup.css b/src/main/resources/assets/ui/powerup.css new file mode 100644 index 0000000..83bd554 --- /dev/null +++ b/src/main/resources/assets/ui/powerup.css @@ -0,0 +1,6 @@ +.powerup-text { + -fx-fill: #F1C40F; + -fx-font-weight: bold; + -fx-font-size: 18px; + -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.7), 5, 0, 0, 1); +} diff --git a/src/main/resources/assets/videos/intro.mp4 b/src/main/resources/assets/videos/intro.mp4 new file mode 100644 index 0000000..75669b8 Binary files /dev/null and b/src/main/resources/assets/videos/intro.mp4 differ diff --git a/src/main/resources/credits.txt b/src/main/resources/credits.txt deleted file mode 100644 index 2c67d4f..0000000 --- a/src/main/resources/credits.txt +++ /dev/null @@ -1,12 +0,0 @@ -CodeStorm - -Leader: -Mai Thành (@thnhmai06) -Members: -Mạnh Tân (@ManhTanTran) -Minh Ngọc (@minngoc1213) -Anh Tuấn (@huynhtuan372) - --o0o- - -Thank you for playing! <3 \ No newline at end of file diff --git a/src/main/resources/settings.properties b/src/main/resources/settings.properties index 7c4b419..22ff206 100644 --- a/src/main/resources/settings.properties +++ b/src/main/resources/settings.properties @@ -3,6 +3,10 @@ # # General general.name=Bounceverse -general.version=1.0.0 +general.version=1.1.0 general.devMode=true +# Video +video.width=1280 +video.height=720 + diff --git a/system/Readme.txt b/system/Readme.txt new file mode 100644 index 0000000..6578c70 --- /dev/null +++ b/system/Readme.txt @@ -0,0 +1 @@ +This directory contains FXGL system data files. \ No newline at end of file diff --git a/system/fxgl.bundle b/system/fxgl.bundle new file mode 100644 index 0000000..a8ad53e Binary files /dev/null and b/system/fxgl.bundle differ